尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

qwen-code 会话来源溯源:session source 元数据在生命周期 Hook 载荷中的传播机制

qwen-code 会话来源溯源:session source 元数据在生命周期 Hook 载荷中的传播机制 qwen-code 会话来源溯源session source 元数据在生命周期 Hook 载荷中的传播机制【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文基于设计文档 session-source-lifecycle-hooks.md讲解 qwen-code 如何把 daemon 创建会话时写入_meta[qwen.session.source]的sourceType/sourceId一路带入SessionStart、UserPromptSubmit、Stop、SessionEnd等生命周期事件的 Hook 载荷并逐环节给出解析、存储、注入的源码实现证据。读完你可以理解这条溯源链在代码中的每一处落点并能在 Hook 接收端独立完成会话来源归因例如区分会话来自 channel、定时任务还是 standalone daemon。一、背景Hook 接收端看不到会话来自哪里daemon 的会话创建链路早已支持把可选的sourceType与sourceId转发给 ACP落在会话参数的_meta[qwen.session.source]中。ACP runtime 目前消费 source type 做行为决策——例如为 channel 会话禁用原生 cron 调度器——但这两个值此前从未出现在生命周期 Hook 的 payload 里。这就造成一个时序缺口当SessionStart事件触发时bridge 尚未把会话来源元数据持久化Hook 接收端无法对一个新会话做出来源归因。本次设计的目标因此非常克制在 ACP 会话边界一次性解析既有创建元数据落到会话的Config上再由 Hook 载荷构造器顺带透出——不引入任何新的传输通道。二、ACP 会话边界的解析与校验2.1 元数据键与校验规则来源元数据的常量定义与校验逻辑集中在 session-source.tsexport interface SessionSourceMetadata { sourceType?: string; sourceId?: string; } export const SESSION_SOURCE_META_KEY qwen.session.source; export const SESSION_SOURCE_TYPE_PATTERN /^[a-z][a-z0-9_-]{0,63}$/; export const MAX_SESSION_SOURCE_ID_LENGTH 256;解析入口parseSessionSource(sourceType, sourceId)做两级校验两者同时缺省时直接返回空对象视为“无来源”提供sourceType时必须是匹配[a-z][a-z0-9_-]{0,63}的字符串提供sourceId时必须是非空字符串、长度不超过 256、且不含控制字符charCode ≤ 31 或 127。任何一项不满足都返回{ error: string }结构而不是抛出异常由调用方决定错误处理方式。同一文件还定义了几个已知来源常量保留的standalone来源类型以及定时任务运行标记——任务运行子会话保持default来源类型以便和普通会话并列展示靠sourceId的scheduled_task_run:前缀来识别。从源码结构看这类常量被会话列表与 web-shell 侧边栏逻辑消费。bridge 层在会话创建请求处理中完成解析出错即拒绝请求bridgeClient.tsconst source parseSessionSource(params[sourceType], params[sourceId]); if (error in source) { throw RequestError.invalidParams(undefined, source.error); } // 子会话不允许再覆写 sourceType定时任务运行类型除外 // sourceType is not settable on a sub-session2.2 从_meta读取并写入会话 ConfigACP agent 一侧acpAgent.ts 提供getSessionSource从_meta[SESSION_SOURCE_META_KEY]读取元数据sourceType不是字符串时整体视为无来源同时透传可选的sourceId与daemonOwnedStandaloneCreation标记function getSessionSource(params: { _meta?: unknown }): SessionSource | undefined { const meta isObjectRecord(params._meta) ? params._meta : undefined; const value meta?.[SESSION_SOURCE_META_KEY]; if (!isObjectRecord(value) || typeof value[sourceType] ! string) { return undefined; } return { sourceType: value[sourceType], ...(typeof value[sourceId] string ? { sourceId: value[sourceId] } : {}), ...(value[DAEMON_OWNED_STANDALONE_CREATION_KEY] true ? { daemonOwnedStandaloneCreation: true } : {}), }; }在会话创建路径上Config 构造完成后立即写入来源acpAgent.tsif (sessionSource) { config.setSessionSource(sessionSource.sourceType, sessionSource.sourceId); }Config侧对应的存取方法定义在 config.tssetSessionSource(sourceType: string, sourceId?: string): void { this.sessionSourceType sourceType; this.sessionSourceId sourceId; } getSessionSourceType(): string | undefined { return this.sessionSourceType; } getSessionSourceId(): string | undefined { return this.sessionSourceId; }设计文档要求“expose read-only getters”对外只有getSessionSourceType()/getSessionSourceId()两个读取入口没有会话中途改写的公开方法——来源元数据在会话生命周期内是不可变的这与“一次性解析、一次落盘”的边界设计一致。三、Hook 载荷注入common input builder 统一透出3.1createBaseInput的字段映射核心实现在 hookEventHandler.ts 的私有方法createBaseInputprivate createBaseInput(eventName: HookEventName): HookInput { const transcriptPath this.config.getTranscriptPath(); const sourceType this.config.getSessionSourceType(); const sourceId this.config.getSessionSourceId(); ... return { session_id: this.config.getSessionId(), ...(sourceType ! undefined ? { source_type: sourceType } : {}), ...(sourceId ! undefined ? { source_id: sourceId } : {}), transcript_path: transcriptPath, cwd: this.config.getWorkingDir(), hook_event_name: eventName, timestamp: new Date().toISOString(), permission_mode: approvalModeToPermissionMode(this.config.getApprovalMode()), ...(agentId ? { agent_id: agentId } : {}), ...(promptId ? { prompt_id: promptId } : {}), }; }两个关键细节字段命名转换进程内 camelCase 的sourceType/sourceId在 payload 中映射为 snake_case 的source_type/source_id与 types.ts 中HookInput的既有字段风格一致条件对象展开...(value ! undefined ? { key: value } : {})让缺省值直接缺席payload 中不会出现source_type: null或source_id: 这类空字段。这正是设计文档“Conditional object spreads omit absent values”所指的兼容手段。3.2 所有生命周期事件获得同一份归因因为每个fire*Event方法都先展开createBaseInputSessionStart、UserPromptSubmit、Stop、SessionEnd以及工具、通知等其余事件全部获得同一份来源归因不需要任何按事件单独接线。以fireSessionStartEvent为例hookEventHandler.tsasync fireSessionStartEvent( source: SessionStartSource, model: string, permissionMode?: PermissionMode, agentType?: AgentType, signal?: AbortSignal, ): PromiseAggregatedHookResult { const input: SessionStartInput { ...this.createBaseInput(HookEventName.SessionStart), permission_mode: permissionMode ?? PermissionMode.Default, source, model, agent_type: agentType, }; ... }由此channel 会话的SessionStart载荷形如{ hook_event_name: SessionStart, session_id: session-id, source_type: channel, source_id: feishu-main, source: startup, model: model, permission_mode: default }这里有一个容易混淆的点SessionStartInput自带的source字段是事件自身的启动来源SessionStartSource表示“这次启动是 startup 还是 resume”新增的source_type/source_id描述的是“这个会话由谁创建”。两者语义独立恰好同时出现在同一事件中接收端不应混用。四、边界纯读透传不改变其他契约设计文档的 “Boundaries” 一节明确了这条链路的不变量——它只是对既有创建元数据的一次读透传read-through以下均不受影响REST 侧会话创建请求协议ACP bridge 的元数据键_meta[qwen.session.source]capability negotiation 协商会话持久化与 resume 行为。因此唯一的可观察差异在 Hook payload无来源元数据的会话其 payload 与改动前完全一致有来源的会话payload 上只是多两个可选字段。会话未携带来源元数据时“keeps the previous hook payload shape”这是接收端兼容性承诺。五、验证点与测试落点设计文档 “Verification” 一节列出的三组验证在当前仓库中都能对应到具体测试Hook handler 测试hookEventHandler.test.ts覆盖SessionStartpayload 上来源字段“存在 / 缺席”两种形态——存在时断言source_type: channel与source_id: feishu-main缺席时用not.toHaveProperty保证字段彻底不出现而非空值expect(input).not.toHaveProperty(source_type); expect(input).not.toHaveProperty(source_id);ACP session 测试acpAgent.test.ts用例 “stores ACP session source metadata on the session config” 覆盖 channel 来源元数据从_meta传播进会话Config的完整路径并断言setSessionSource被以解析后的值调用。Channel worker 测试既有的创建元数据覆盖包含“channel 实例名作为sourceId”的场景验证 daemon 侧发送端与上述解析契约一致。六、接收端集成要点结合上述实现在 qwen-code 的 Hook 接收端做来源归因时可以遵循成对读取以source_typesource_id作为组合键source_type的出现本身即表示该会话声明了来源按可选字段处理旧版本 payload 与无来源会话都没有这两个键不要假设其恒在区分语义层次source_type回答“谁创建了会话”如channel、standalone、定时任务运行而各事件自身的字段如SessionStart的source启动来源、Stop的结束上下文回答“本事件为何触发”两者在接收端逻辑中应分属不同分支。这条机制的价值在于Hook 生态审计、路由、统计、按来源触发差异化动作从此可以在SessionStart触发的第一时间拿到与 ACP runtime 相同的归因依据而不必等待 bridge 侧的来源持久化完成。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表