Skip to content

feat(channel): add daemon bridge wire-up#4261

Closed
chiga0 wants to merge 1 commit into
mainfrom
feat/channel-daemon-wireup-draft
Closed

feat(channel): add daemon bridge wire-up#4261
chiga0 wants to merge 1 commit into
mainfrom
feat/channel-daemon-wireup-draft

Conversation

@chiga0

@chiga0 chiga0 commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • What changed: Adds an opt-in daemon bridge path for qwen channel start via --daemon-url, QWEN_CHANNEL_DAEMON_URL, or QWEN_DAEMON_URL. The existing ACP bridge remains the default. Channel internals now depend on a structural ChannelBridge interface so either the existing AcpBridge or the new DaemonChannelBridge can drive sessions.
  • Why it changed: Provides a local validation path for channel/web clients to consume daemon sessions through HTTP + SSE without replacing the current channel service by default.
  • Reviewer focus: Confirm the default AcpBridge path is unchanged, daemon mode is explicit, and routing still stays owned by SessionRouter rather than daemon-level single-session coalescing.

Validation

  • Commands run:
    npm run build --workspace=@qwen-code/channel-base
    npm run dev -- channel start --help
    cd packages/cli && npx tsc --noEmit --pretty false
  • Prompts / inputs used:
    • channel start --help
  • Expected result:
    • Channel base builds.
    • qwen channel start advertises the daemon bridge flags.
    • Without daemon flags/env vars, startup still instantiates the existing ACP bridge.
  • Observed result:
    • Channel base build passed.
    • Help output showed --daemon-url and --daemon-token.
    • After rebasing onto current main, package-local CLI typecheck is blocked by unrelated goalCommand errors from files not touched by this PR: GoalTerminalKind no longer accepts "failed".
  • Quickest reviewer verification path:
    npm run dev -- serve --port 4170 --workspace "$PWD"
    QWEN_CHANNEL_DAEMON_URL=http://127.0.0.1:4170 npm run dev -- channel start <configured-channel-name>
  • Evidence (output, logs, screenshots, video, JSON, before/after, etc.):
    • Local help output confirmed the opt-in flags are registered.

Scope / Risk

  • Main risk or tradeoff: This introduces a shared bridge interface used by channel adapters. The surface is intentionally the existing ACP bridge surface so built-in channels keep their behavior.
  • Not covered / not validated: A real external channel service was not connected in this commit.
  • Breaking changes / migration notes: None expected for default users. Daemon mode requires an explicit flag or environment variable.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️ ⚠️
npx ⚠️ ⚠️
Docker N/A N/A N/A
Podman N/A N/A N/A
Seatbelt N/A N/A N/A

Testing matrix notes:

  • Validated on macOS via package build and npm run dev -- channel start --help.

Linked Issues / Bugs

@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces an opt-in daemon bridge path for qwen channel start via --daemon-url, QWEN_CHANNEL_DAEMON_URL, or QWEN_DAEMON_URL. The implementation creates a ChannelBridge interface that abstracts both the existing AcpBridge and the new DaemonChannelBridge, allowing either to drive channel sessions. The default ACP bridge path remains unchanged, and daemon mode requires explicit flags or environment variables. Overall, this is a well-structured refactoring that maintains backward compatibility while enabling new functionality.

🔍 General Feedback

  • Good abstraction design: The ChannelBridge interface cleanly separates the bridge contract from implementations, enabling both ACP and daemon modes without duplicating channel adapter code.
  • Backward compatibility: Default behavior is preserved—daemon mode is strictly opt-in via CLI flags or environment variables.
  • Consistent refactoring: All channel adapters (Dingtalk, Telegram, Weixin, MockPlugin) were updated uniformly to use ChannelBridge instead of AcpBridge.
  • Session routing preserved: Daemon sessions still route through SessionRouter, maintaining the existing session management architecture.
  • Comprehensive daemon event handling: DaemonChannelBridge handles a wide range of daemon events (session updates, permission requests, model switches, errors) with proper cleanup.

🎯 Specific Feedback

🔴 Critical

  • packages/cli/src/commands/channel/start.ts:194-195 - Environment variable fallback chain for daemon token may be confusing: The code uses QWEN_SERVER_TOKEN for the daemon token, but the URL uses QWEN_CHANNEL_DAEMON_URL or QWEN_DAEMON_URL. This inconsistency could cause configuration errors. Consider using QWEN_CHANNEL_DAEMON_TOKEN for consistency with the URL naming pattern.

  • packages/channels/base/src/DaemonChannelBridge.ts:230-231 - Unbounded session accumulation risk: The sessions Map grows without bounds as sessions are created. While dropSession cleans up, there's no explicit session limit or TTL. In a high-throughput channel scenario, this could lead to memory pressure. Consider adding a configurable session limit or LRU eviction policy.

🟡 High

  • packages/cli/src/commands/channel/start.ts:188 - Hard-coded session scope mapping loses semantic information: The toDaemonSessionScope function ignores the provided scope and always returns 'thread'. While the comment explains the reasoning (to avoid daemon-level coalescing), this should be more explicit. Consider documenting this in the function name (e.g., toDaemonIsolatedSessionScope) or adding a guard that validates the scope won't cause issues.

  • packages/channels/base/src/DaemonChannelBridge.ts:467-476 - Race condition in event pump: The isCurrentPump check compares both session identity and controller signal, but there's a window where a session could be replaced between the check and event handling. This is partially mitigated by dropSession cleanup, but the pattern could be clearer. Consider using a generation counter or explicit pump ID to make staleness detection more robust.

  • packages/cli/src/commands/channel/start.ts:284-289 - Bridge recreation on crash doesn't preserve in-flight operations: When the bridge is recreated after a crash, the SessionRouter and channel adapters get the new bridge, but any in-flight prompts or tool calls are lost. This is acceptable for crash recovery, but should be documented. Consider emitting a warning event or log message when this happens.

🟢 Medium

  • packages/cli/src/commands/channel/start.ts:23-30 - Interface definitions duplicated: ChannelStartRuntimeOptions and ChannelStartArgs are defined locally but could benefit from being exported to @qwen-code/channel-base for reuse in tests or other CLI commands that might need to start channels programmatically.

  • packages/channels/base/src/DaemonChannelBridge.ts:158-162 - Magic number for permission request cache: MAX_RESPONDED_PERMISSION_REQUESTS = 256 is reasonable, but the LRU eviction in rememberRespondedPermissionRequest is O(n) due to iterator-based key removal. For a cache this small, it's acceptable, but consider using a proper LRU map or documenting the performance characteristic.

  • packages/channels/base/src/DaemonChannelBridge.ts:307-314 - Error handling in respondToPermission could be more granular: The catch block deletes both requestToSession and respondedRequestToSession entries, but if the error is transient (e.g., network timeout), the request ID could be valid for retry. Consider distinguishing between permanent failures (invalid request ID) and transient errors.

  • packages/cli/src/commands/channel/start.ts:570-572 - Daemon URL resolution order is implicit: The priority is CLI flag → QWEN_CHANNEL_DAEMON_URLQWEN_DAEMON_URL. This should be documented in the help text or a comment, as users may not know which takes precedence.

🔵 Low

  • packages/channels/base/src/AcpBridge.ts:38-52 - Interface documentation: The ChannelBridge interface lacks JSDoc comments explaining its purpose and when to use it vs. the concrete implementations. Adding brief documentation would help future maintainers understand the abstraction's intent.

  • packages/channels/base/src/DaemonChannelBridge.ts:1 - TODO comment should reference an issue: The comment // TODO(daemon-roadmap): replace this bounded client-side drain... should link to a GitHub issue or roadmap item for tracking.

  • packages/cli/src/commands/channel/start.ts:560 - Help text could mention default behavior: The daemon-url option description says "use a running qwen serve daemon instead of spawning an ACP bridge" but doesn't explicitly state "default: spawn ACP bridge" to reinforce that this is opt-in.

  • packages/channels/base/src/DaemonChannelBridge.ts:483-485 - Inconsistent error type handling: getReason and getError methods have similar patterns but handle different field names. Consider unifying into a single helper or documenting why they differ.

✅ Highlights

  • Excellent interface design: The ChannelBridge interface is well-thought-out, capturing all necessary methods while remaining implementable by both local (ACP) and remote (daemon) backends.
  • Thorough event handling in DaemonChannelBridge: The class handles a comprehensive set of daemon events with proper cleanup, error propagation, and state management.
  • Clean crash recovery pattern: The restart logic in start.ts properly reattaches the bridge to router and channels, maintaining the crash recovery semantics of the original ACP implementation.
  • Good separation of concerns: The createBridge factory function cleanly encapsulates the daemon vs. ACP decision, making the rest of the code agnostic to which bridge is in use.
  • Type-safe refactoring: All channel adapters were updated consistently, and TypeScript types flow correctly through the abstraction layer.

@chiga0 chiga0 force-pushed the feat/channel-daemon-wireup-draft branch from 50d5416 to 933a9d8 Compare May 18, 2026 03:08
@chiga0 chiga0 changed the base branch from feat/channel-web-daemon-adapter-draft to main May 18, 2026 03:08

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: Daemon bridge path has zero test coverage. createBridge daemon branch (dynamic import of @qwen-code/sdk, DaemonClient construction, DaemonChannelBridge creation with sessionFactory) is completely untested. resolveDaemonUrl, resolveDaemonToken, toDaemonSessionScope are pure functions with no unit tests. The @qwen-code/channel-base vi.mock does not export DaemonChannelBridge. All mock objects in SessionRouter.test.ts, ChannelBase.test.ts, and start.test.ts do not satisfy the ChannelBridge interface (missing cancelSession, stop, isConnected).

writeStdoutLine(`[Channel] "${name}" is running. Press Ctrl+C to stop.`);

const attachDisconnectHandler = (b: AcpBridge): void => {
const attachDisconnectHandler = (b: ChannelBridge): void => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Crash recovery never triggers for daemon bridge — DaemonChannelBridge never emits disconnected

attachDisconnectHandler listens for bridge.on('disconnected', ...). AcpBridge emits 'disconnected' when the child process exits. DaemonChannelBridge has no child process and never emits 'disconnected' — across all 12 emit() calls in the class, none are 'disconnected'. When the daemon connection is lost (SSE stream error, network blip, daemon restart), the crash recovery loop never fires. The channel process stays alive with isConnected === true but is permanently non-functional.

Suggested change
const attachDisconnectHandler = (b: ChannelBridge): void => {
// DaemonChannelBridge must emit 'disconnected' when connectivity is lost.
// Trigger point: when the last session is dropped due to a transport-level
// error (not a graceful end). In dropSession(), after all sessions are gone:
if (this.sessions.size === 0 && this.connected) {
this.connected = false;
this.emit('disconnected');
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

bridge = new AcpBridge(bridgeOpts);
await bridge.start();
bridge = await createBridge(bridgeOpts, options, config.sessionScope);
router.setBridge(bridge);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Single restart failure = channel permanently zombie

The catch block in the disconnected handler logs the error and returns — it does not retry, does not re-attach the disconnected listener, and does not exit the process. The old bridge reference b is dead. The channel's await new Promise(() => {}) still runs — process stays alive but the channel is non-operational. Combined with the daemon disconnected gap, a single transient failure leaves a permanently dead channel with no recovery path.

Suggested change
router.setBridge(bridge);
// Add retry with backoff before giving up:
let retries = 0;
while (retries < 3) {
try {
bridge = await createBridge(bridgeOpts, options, config.sessionScope);
router.setBridge(bridge);
channel.setBridge(bridge);
registerToolCallDispatch(bridge, router, channels);
attachDisconnectHandler(bridge);
break;
} catch (err) {
retries++;
if (retries >= 3) throw err;
await new Promise((r) => setTimeout(r, RESTART_DELAY_MS * retries));
}
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

return bridge;
}

const { DaemonClient, DaemonSessionClient } = await import('@qwen-code/sdk');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Dynamic import of @qwen-code/sdk has no error handling

When daemonUrl is set, createBridge does await import('@qwen-code/sdk') without try/catch. If the package is missing or incompatible, the raw Cannot find module error propagates to the caller with no context about why the import was attempted or how to fix it. An operator who sets QWEN_DAEMON_URL inadvertently gets a cryptic error with no mention of daemon or the URL.

Suggested change
const { DaemonClient, DaemonSessionClient } = await import('@qwen-code/sdk');
try {
const { DaemonClient, DaemonSessionClient } = await import('@qwen-code/sdk');
} catch (err) {
throw new Error(
`Daemon bridge requires @qwen-code/sdk (daemon URL: ${options.daemonUrl}). ` +
`Ensure the SDK package is installed. Original error: ${err instanceof Error ? err.message : String(err)}`
);
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

return cliDaemonToken || process.env['QWEN_SERVER_TOKEN'];
}

function toDaemonSessionScope(scope: SessionScope | undefined): 'thread' {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] toDaemonSessionScope silently discards user configuration

This function always returns 'thread' regardless of input. void scope; exists solely to suppress the unused-variable warning. If a channel is configured with sessionScope: 'user' or 'single' and uses the daemon bridge, that configuration is silently dropped — no warning, no error. Meanwhile SessionRouter still routes sessions using the configured scope, creating a mismatch that is nearly impossible to diagnose.

Suggested change
function toDaemonSessionScope(scope: SessionScope | undefined): 'thread' {
// Rename to make intent explicit:
function forceThreadScopeForDaemon(channelScope?: SessionScope): 'thread' {
if (channelScope && channelScope !== 'thread') {
writeStderrLine(
`[Channel] Daemon bridge forces 'thread' session scope ` +
`(channel configured '${channelScope}').`
);
}
return 'thread';
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

}

export class AcpBridge extends EventEmitter {
export interface ChannelBridge extends EventEmitter {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] DaemonChannelBridge does not declare implements ChannelBridge

AcpBridge declares implements ChannelBridge but DaemonChannelBridge relies on structural typing only. If ChannelBridge gains a new method or changes a signature, TypeScript will not flag DaemonChannelBridge — the mismatch will only surface as a runtime error at the createBridge(): Promise<ChannelBridge> return site.

Suggested change
export interface ChannelBridge extends EventEmitter {
export class DaemonChannelBridge extends EventEmitter implements ChannelBridge {

— DeepSeek/deepseek-v4-pro via Qwen Code /review


export class AcpBridge extends EventEmitter {
export interface ChannelBridge extends EventEmitter {
readonly availableCommands: AvailableCommand[];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] availableCommands semantics diverge between bridge implementations

AcpBridge.availableCommands returns globally stored commands shared across all sessions. DaemonChannelBridge.availableCommands returns commands from the most recent session that emitted available_commands_update. In a multi-session daemon deployment (multiple users/chats active), /help may display commands from a different user's session — a race-condition-level bug that is intermittent and hard to reproduce.

Suggested change
readonly availableCommands: AvailableCommand[];
// Option A: Make availableCommands session-aware in the interface:
readonly availableCommands: AvailableCommand[]; // Keep for backward compat
// Option B: DaemonChannelBridge should merge commands across sessions
// instead of tracking only the latest session's commands.
// Option C: ChannelBase should call bridge.getAvailableCommands(sessionId)
// before /help logic instead of using the session-less getter.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@@ -254,8 +336,7 @@ async function startSingle(name: string, proxy?: string): Promise<void> {
await new Promise((r) => setTimeout(r, RESTART_DELAY_MS));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Old bridge event listeners leak on crash recovery

Crash recovery creates a new bridge and overwrites the bridge variable without calling removeAllListeners() on the old instance. The old bridge's disconnected and toolCall listeners — which close over router, channels, and shuttingDown — prevent GC of the old bridge and all its internal state.

Suggested change
await new Promise((r) => setTimeout(r, RESTART_DELAY_MS));
const oldBridge = bridge;
bridge = await createBridge(bridgeOpts, options, config.sessionScope);
oldBridge.removeAllListeners();
router.setBridge(bridge);

— DeepSeek/deepseek-v4-pro via Qwen Code /review

},
});
await bridge.start();
writeStdoutLine(`[Channel] Using daemon bridge at ${options.daemonUrl}.`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Daemon URL logged to stdout leaks embedded credentials

writeStdoutLine(\[Channel] Using daemon bridge at ${options.daemonUrl}.`) outputs the full URL. If users embed credentials in the URL (http://user:pass@host`), they are written to stdout, potentially captured in logs, CI output, or terminal scrollback.

Suggested change
writeStdoutLine(`[Channel] Using daemon bridge at ${options.daemonUrl}.`);
try {
const origin = new URL(options.daemonUrl).origin;
writeStdoutLine(`[Channel] Using daemon bridge at ${origin}.`);
} catch {
writeStdoutLine(`[Channel] Using daemon bridge.`);
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

function resolveDaemonToken(cliDaemonToken?: string): string | undefined {
return cliDaemonToken || process.env['QWEN_SERVER_TOKEN'];
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] QWEN_SERVER_TOKEN co-opted as daemon token creates confused deputy

resolveDaemonToken falls back to QWEN_SERVER_TOKEN. If this env var is set for another purpose (e.g., API authentication), it is silently sent to the daemon URL as a Bearer token. Combined with an attacker-controlled QWEN_DAEMON_URL, this exfiltrates the server token.

Suggested change
// Use a daemon-specific env var instead:
function resolveDaemonToken(cliDaemonToken?: string): string | undefined {
return cliDaemonToken || process.env['QWEN_DAEMON_TOKEN'];
}
// If backward compat with QWEN_SERVER_TOKEN is needed, add a deprecation warning.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

return await DaemonSessionClient.createOrAttach(client, {
workspaceCwd: req.workspaceCwd,
modelServiceId: req.modelServiceId,
sessionScope: toDaemonSessionScope(req.sessionScope),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] This only works against daemons that honor the per-request sessionScope override, but the client never checks that capability. The SDK documents that older daemons silently ignore sessionScope; those daemons default to single, so multiple distinct channel conversations can be coalesced into one daemon session while SessionRouter believes it created separate sessions. That breaks the core routing invariant this code is trying to enforce. Please call client.capabilities() before enabling daemon mode and fail fast unless features includes session_scope_override (or use a compatibility path that cannot coalesce sessions).

— gpt-5.5 via Qwen Code /review

baseUrl: options.daemonUrl,
token: options.daemonToken,
});
const bridge = new DaemonChannelBridge({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] The daemon bridge emits permissionRequest and exposes respondToPermission(), but this wire-up never registers a handler for it. The ACP bridge currently auto-approves requestPermission, so any permission-gated tool works in the default path; in daemon mode, the same prompt will leave the permission request unanswered until the daemon times out or cancels it. Please preserve the existing channel behavior by wiring a daemon permissionRequest listener that selects the same proceed_once/first-option policy and calls bridge.respondToPermission(...), or move that policy into the shared bridge abstraction so both bridges implement it consistently.

— gpt-5.5 via Qwen Code /review

@chiga0

chiga0 commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Architecture update after the 2026-05-19 decision: this draft no longer matches the current mainline. Channel should continue to use the existing --acp path by default; daemon channel bridge work is future/behind-flag evaluation only.

Given that #4203 already provides a default-off server-side bridge spike, I do not think this wire-up should move toward merge as-is. Recommendation: close/defer this draft, and only mine reusable pieces later if/when channel daemon migration is reopened.

Generated by GPT-5 Codex

Comment thread packages/cli/package.json
"@modelcontextprotocol/sdk": "^1.25.1",
"@qwen-code/channel-base": "file:../channels/base",
"@qwen-code/channel-dingtalk": "file:../channels/dingtalk",
"@qwen-code/sdk": "file:../sdk-typescript",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] @qwen-code/sdk should be an optional dependency, not a hard dependency

The SDK is only needed for the opt-in daemon bridge path. Every user pays the install cost (and transitive deps like @modelcontextprotocol/sdk, zod) even when using the default ACP bridge. The code already uses await import('@qwen-code/sdk') (dynamic import) — signaling the intent is optional. If the SDK fails to build or introduces a breaking change, it blocks the default ACP path for all channel users.

Suggested change
"@qwen-code/sdk": "file:../sdk-typescript",

Move "@qwen-code/sdk": "file:../sdk-typescript" from dependencies to optionalDependencies.

— qwen-latest-series-invite-beta-v28 via Qwen Code /review

return (
cliDaemonUrl ||
process.env['QWEN_CHANNEL_DAEMON_URL'] ||
process.env['QWEN_DAEMON_URL']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] QWEN_DAEMON_URL env var is too generic — risks silent activation of daemon bridge

resolveDaemonUrl() checks three sources: --daemon-urlQWEN_CHANNEL_DAEMON_URLQWEN_DAEMON_URL. The last one is a very generic env var name. If a user has QWEN_DAEMON_URL set for an unrelated purpose (another tool, a CI pipeline variable), qwen channel start will silently activate the daemon bridge instead of the default ACP bridge, resulting in confusing connection failures.

Suggested change
process.env['QWEN_DAEMON_URL']
function resolveDaemonUrl(cliDaemonUrl?: string): string | undefined {
return (
cliDaemonUrl ||
process.env['QWEN_CHANNEL_DAEMON_URL']
);
}

Remove the QWEN_DAEMON_URL fallback, keeping only the unambiguous QWEN_CHANNEL_DAEMON_URL and the explicit --daemon-url CLI flag.

— qwen-latest-series-invite-beta-v28 via Qwen Code /review

};
let bridge = new AcpBridge(bridgeOpts);
await bridge.start();
let bridge = await createBridge(bridgeOpts, options, 'thread');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] startAll hardcodes 'thread' scope — inconsistent with startSingle

startSingle (line 282) passes config.sessionScope to createBridge, while startAll hardcodes 'thread'. Today this is masked because toDaemonSessionScope() discards the argument entirely. But when that function is fixed to respect its input, the multi-channel daemon path in startAll will still use 'thread' regardless of per-channel configuration.

Consider deriving the bridge-level scope from parsed configs, or let createBridge not take a sessionScope parameter and let the daemon bridge resolve scope per-session from DaemonChannelSessionFactoryRequest (which already carries sessionScope).

— qwen-latest-series-invite-beta-v28 via Qwen Code /review

@chiga0

chiga0 commented May 19, 2026

Copy link
Copy Markdown
Collaborator Author

Closing this draft per the latest daemon roadmap decision: channel should keep the existing ACP path for now, while daemon client validation moves to remote web chat + web terminal first. This branch remains useful only as a future/reference spike if channel daemon migration is reopened.

Generated by GPT-5 Codex

@chiga0 chiga0 closed this May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants