@steerable/agent-shell 0.6.28 → 0.6.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -295,6 +295,14 @@
295
295
  }
296
296
  },
297
297
  "version": 2,
298
+ "dualImplementations": {
299
+ "todo_write": ["python", "rust"],
300
+ "web_fetch": ["python", "rust"],
301
+ "web_search": ["python", "rust"],
302
+ "run_code": ["python", "rust"],
303
+ "run_js": ["python", "rust"],
304
+ "wait_js": ["python", "rust"]
305
+ },
298
306
  "versionAlgorithm": "sha256-utf8-hex",
299
307
  "versionToken": {
300
308
  "inputField": "expectedVersion",
@@ -14,9 +14,8 @@
14
14
  * 2. sidecar 技能目录 —— `SkillTurnContext.exclude`
15
15
  * 3. 工具面 —— 每轮 `turnTools`、`tool_search` 结果、反向通道分发复检
16
16
  *
17
- * `@提及` 多个智能体的回合按「最宽松」合并:提及额外专家是为了叠加能力,
18
- * 不应该反而让对话失去工具或技能(与 personaPreamble 的「同时具备以下 N 个
19
- * 角色的能力」一致)。
17
+ * 多个智能体配置按「最宽松」合并(父代理自身、或显式传入的列表)。
18
+ * `@` 提及不再合并进父的能力面——被点名的智能体走子代理画像。
20
19
  */
21
20
  export type AgentToolPolicyMode = 'all' | 'allowlist' | 'denylist';
22
21
  export interface AgentToolPolicy {
@@ -30,7 +29,7 @@ export interface AgentCapabilityInput {
30
29
  loadAllSkills: boolean;
31
30
  toolPolicy: AgentToolPolicy;
32
31
  }
33
- /** 本轮生效的能力面(可能是多个被提及智能体的合并结果)。 */
32
+ /** 本轮生效的能力面(可能是多个智能体配置的合并结果)。 */
34
33
  export interface AgentCapability {
35
34
  /** 正文无条件注入的技能别名(dirName / name / displayName 皆可)。 */
36
35
  pinnedSkills: string[];
@@ -74,7 +73,7 @@ export declare function filterToolsByPolicy<T extends {
74
73
  /**
75
74
  * 合并本轮涉及的全部智能体的能力面,取最宽松。
76
75
  *
77
- * @param agents 按提及顺序排列的智能体配置;空数组表示无绑定。
76
+ * @param agents 按顺序排列的智能体配置;空数组表示无绑定。
78
77
  * @returns 本轮生效的能力面;空数组时为 {@link UNRESTRICTED_CAPABILITY}。
79
78
  */
80
79
  export declare function mergeAgentCapabilities(agents: readonly AgentCapabilityInput[]): AgentCapability;
@@ -14,9 +14,8 @@
14
14
  * 2. sidecar 技能目录 —— `SkillTurnContext.exclude`
15
15
  * 3. 工具面 —— 每轮 `turnTools`、`tool_search` 结果、反向通道分发复检
16
16
  *
17
- * `@提及` 多个智能体的回合按「最宽松」合并:提及额外专家是为了叠加能力,
18
- * 不应该反而让对话失去工具或技能(与 personaPreamble 的「同时具备以下 N 个
19
- * 角色的能力」一致)。
17
+ * 多个智能体配置按「最宽松」合并(父代理自身、或显式传入的列表)。
18
+ * `@` 提及不再合并进父的能力面——被点名的智能体走子代理画像。
20
19
  */
21
20
  /** 无智能体绑定(或未配置)的回合:不钉技能、不限制。 */
22
21
  export const UNRESTRICTED_CAPABILITY = {
@@ -102,7 +101,7 @@ function mergeToolPolicies(a, b) {
102
101
  /**
103
102
  * 合并本轮涉及的全部智能体的能力面,取最宽松。
104
103
  *
105
- * @param agents 按提及顺序排列的智能体配置;空数组表示无绑定。
104
+ * @param agents 按顺序排列的智能体配置;空数组表示无绑定。
106
105
  * @returns 本轮生效的能力面;空数组时为 {@link UNRESTRICTED_CAPABILITY}。
107
106
  */
108
107
  export function mergeAgentCapabilities(agents) {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `@提及` → 被点名的智能体 id。
3
+ *
4
+ * 渲染层从 `@` 菜单点选时会把 id 放进 `metadata.mentionedAgentIds`;手打
5
+ * 的 `@名字` 不带 id。只认 id 的话,手打提及在后端等于没提及:不下发子代理
6
+ * 画像、不注入强制派发指令,而 Web 顶栏又按正文扫 `@词` 画徽章——界面显示
7
+ * 点了三个人,实际一个都没派活。这里把正文解析补在后端,两侧同一口径。
8
+ *
9
+ * 中文没有词边界,所以按「最长画像名/slug 前缀」匹配,而不是按空白切词:
10
+ * `@日程规划,帮我…` 里的标点不会被吃进名字。
11
+ */
12
+ /** 匹配用的智能体子集。 */
13
+ export interface MentionAgentRef {
14
+ id: string;
15
+ slug: string | null;
16
+ name: string;
17
+ }
18
+ /**
19
+ * 请求体里显式带来的提及 id(`@` 菜单点选的结果)。
20
+ *
21
+ * @param payload 发送请求体。
22
+ * @returns 去重后的 id 列表,保持 `mentionedAgentId` 优先的原有顺序。
23
+ */
24
+ export declare function collectExplicitMentionIds(payload: Record<string, unknown>): string[];
25
+ /**
26
+ * 从消息正文里解析 `@名字` / `@slug`。
27
+ *
28
+ * @param text 用户消息正文。
29
+ * @param agents 本会话可用的智能体。
30
+ * @returns 命中的智能体 id,按正文出现顺序去重。
31
+ */
32
+ export declare function mentionedAgentIdsFromText(text: string, agents: readonly MentionAgentRef[]): string[];
33
+ /**
34
+ * 本轮生效的提及 id:显式 id 优先,正文解析补齐。
35
+ *
36
+ * @param payload 发送请求体。
37
+ * @param text 用户消息正文。
38
+ * @param agents 本会话可用的智能体。
39
+ * @returns 去重后的 id 列表。
40
+ */
41
+ export declare function resolveMentionedAgentIds(payload: Record<string, unknown>, text: string, agents: readonly MentionAgentRef[]): string[];
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `@提及` → 被点名的智能体 id。
3
+ *
4
+ * 渲染层从 `@` 菜单点选时会把 id 放进 `metadata.mentionedAgentIds`;手打
5
+ * 的 `@名字` 不带 id。只认 id 的话,手打提及在后端等于没提及:不下发子代理
6
+ * 画像、不注入强制派发指令,而 Web 顶栏又按正文扫 `@词` 画徽章——界面显示
7
+ * 点了三个人,实际一个都没派活。这里把正文解析补在后端,两侧同一口径。
8
+ *
9
+ * 中文没有词边界,所以按「最长画像名/slug 前缀」匹配,而不是按空白切词:
10
+ * `@日程规划,帮我…` 里的标点不会被吃进名字。
11
+ */
12
+ /**
13
+ * 请求体里显式带来的提及 id(`@` 菜单点选的结果)。
14
+ *
15
+ * @param payload 发送请求体。
16
+ * @returns 去重后的 id 列表,保持 `mentionedAgentId` 优先的原有顺序。
17
+ */
18
+ export function collectExplicitMentionIds(payload) {
19
+ const listed = Array.isArray(payload.mentionedAgentIds)
20
+ ? payload.mentionedAgentIds.filter((item) => typeof item === 'string' && item.length > 0)
21
+ : [];
22
+ const single = typeof payload.mentionedAgentId === 'string' && payload.mentionedAgentId
23
+ ? [payload.mentionedAgentId]
24
+ : [];
25
+ return dedupe([...single, ...listed]);
26
+ }
27
+ /**
28
+ * 从消息正文里解析 `@名字` / `@slug`。
29
+ *
30
+ * @param text 用户消息正文。
31
+ * @param agents 本会话可用的智能体。
32
+ * @returns 命中的智能体 id,按正文出现顺序去重。
33
+ */
34
+ export function mentionedAgentIdsFromText(text, agents) {
35
+ if (!text.includes('@'))
36
+ return [];
37
+ const labels = mentionLabels(agents);
38
+ if (labels.length === 0)
39
+ return [];
40
+ const ids = [];
41
+ for (let i = 0; i < text.length; i += 1) {
42
+ if (text[i] !== '@')
43
+ continue;
44
+ // 只在行首/空白后开门:邮箱地址与 `a@b` 这类文本不是提及。
45
+ const prev = i === 0 ? '' : text[i - 1];
46
+ if (prev && !/\s/.test(prev))
47
+ continue;
48
+ const rest = text.slice(i + 1);
49
+ const matched = labels.find((entry) => rest.slice(0, entry.label.length).toLowerCase() === entry.label.toLowerCase());
50
+ if (!matched)
51
+ continue;
52
+ ids.push(matched.id);
53
+ i += matched.label.length;
54
+ }
55
+ return dedupe(ids);
56
+ }
57
+ /**
58
+ * 本轮生效的提及 id:显式 id 优先,正文解析补齐。
59
+ *
60
+ * @param payload 发送请求体。
61
+ * @param text 用户消息正文。
62
+ * @param agents 本会话可用的智能体。
63
+ * @returns 去重后的 id 列表。
64
+ */
65
+ export function resolveMentionedAgentIds(payload, text, agents) {
66
+ const explicit = collectExplicitMentionIds(payload);
67
+ const known = new Set(agents.map((agent) => agent.id));
68
+ return dedupe([
69
+ ...explicit,
70
+ ...mentionedAgentIdsFromText(text, agents).filter((id) => known.has(id)),
71
+ ]);
72
+ }
73
+ /** 画像名与 slug 合成匹配表,长名在前——`@调研员组` 不该命中 `调研员`。 */
74
+ function mentionLabels(agents) {
75
+ const labels = [];
76
+ for (const agent of agents) {
77
+ const name = agent.name.trim();
78
+ const slug = agent.slug?.trim() ?? '';
79
+ if (name)
80
+ labels.push({ label: name, id: agent.id });
81
+ if (slug && slug !== name)
82
+ labels.push({ label: slug, id: agent.id });
83
+ }
84
+ return labels.sort((a, b) => b.label.length - a.label.length);
85
+ }
86
+ function dedupe(ids) {
87
+ return ids.filter((id, index, all) => all.indexOf(id) === index);
88
+ }
@@ -110,17 +110,17 @@ export declare class LocalBackendRouter {
110
110
  /**
111
111
  * 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
112
112
  *
113
- * 优先级:payload.mentionedAgentId(@提及)> mentionedAgentIds >
114
- * payload.agentId > chat.agentId > 无。@提及多个时按提及顺序合并——
115
- * rolePrompt 逐段拼装(第一个为主角色),能力面取最宽松
116
- * (见 {@link mergeAgentCapabilities})。
113
+ * 父代理 = payload.agentId(专家下拉)> chat.agentId。`@` 提及只进
114
+ * delegates,不再顶替人设。点到父自己也照样进 delegates:`@` 的语义是
115
+ * 「起一个子代理」,点自己就是起一个自己的独立副本,父代理仍是拆分与
116
+ * 汇总的那一个。
117
117
  *
118
118
  * 必须在拼装工具列表之前调用:`toolPolicy` 决定每轮 `turnTools`,
119
119
  * 而工具列表又反过来决定技能的触发条件。
120
120
  *
121
121
  * @param chatId 当前对话 id。
122
122
  * @param payload 前端提交的流请求体。
123
- * @returns 本轮的智能体顺序、人设智能体、自称,以及合并后的能力面。
123
+ * @returns 父代理、委派对象、人设前言与父的能力面。
124
124
  */
125
125
  private resolveTurnAgents;
126
126
  private buildConversationMessages;
@@ -8,7 +8,9 @@ import { llmService, getSidecarSupervisor, whenSidecarSupervisor } from '../llm/
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  import { buildWorldState, getActiveCoreLoopStreamId, streamCoreLoopTurn, } from './coreloop-stream.js';
10
10
  import { driveWithAutoContinue, resolveAutoContinueMax, } from './auto-continue-helper.js';
11
- import { builtinSubagentParam } from './subagent-profiles.js';
11
+ import { buildDelegateDispatchInstruction, buildMentionDelegateRoster, mergeTurnSubagentParam, } from './subagent-profiles.js';
12
+ import { resolveMentionedAgentIds } from './mention-targets.js';
13
+ import { readSidecarHistoryEntries, timelineFromHistoryEntries, } from './task-process.js';
12
14
  import { appendTimelineDelta, freezeTimelineReasoning, sealLastTimelineBlock, syncTimelineTools, } from './turn-timeline.js';
13
15
  import { turnDurationMs } from './turn-duration.js';
14
16
  import { DEFAULT_SYSTEM_PROMPT, telemetryEnabled } from '../storage/index.js';
@@ -153,6 +155,24 @@ function buildLocalAgent() {
153
155
  updatedAt: now,
154
156
  };
155
157
  }
158
+ function parseMessageMetadata(raw) {
159
+ if (!raw)
160
+ return null;
161
+ try {
162
+ const parsed = JSON.parse(raw);
163
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
164
+ return parsed;
165
+ }
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ return null;
171
+ }
172
+ function persistedAgentIdFromMetadata(raw) {
173
+ const agentId = parseMessageMetadata(raw)?.agentId;
174
+ return typeof agentId === 'string' && agentId ? agentId : undefined;
175
+ }
156
176
  export class LocalBackendRouter {
157
177
  toolRouter;
158
178
  defaultStore;
@@ -513,6 +533,20 @@ export class LocalBackendRouter {
513
533
  },
514
534
  };
515
535
  }
536
+ // 子代理(delegate_subagent)的推理过程:子回合写自己的 durable record
537
+ // (`<父 record>:child:<lineage id>`,随 child_spawned 上报),这里按
538
+ // record 直读重建时间线——与后台任务的过程面板同一套渲染。
539
+ if (method === 'GET' && pathname === '/api/v2/child-process') {
540
+ const recordId = url.searchParams.get('recordId')?.trim();
541
+ if (!recordId) {
542
+ return { status: 400, data: { detail: 'recordId is required' } };
543
+ }
544
+ const entries = readSidecarHistoryEntries(recordId);
545
+ return {
546
+ status: 200,
547
+ data: { recordId, timeline: timelineFromHistoryEntries(entries) },
548
+ };
549
+ }
516
550
  // 4.6c Task×Worktree:合并到主仓 / 丢弃。两个操作都是幂等目标态
517
551
  // (worktreeState 非 pending 时 TaskService 抛错 → 409)。
518
552
  const taskActionMatch = pathname.match(/^\/api\/v2\/tasks\/([^/]+)\/(merge|discard)$/);
@@ -671,14 +705,18 @@ export class LocalBackendRouter {
671
705
  const chatId = messageListMatch[1];
672
706
  const limit = parsePositiveIntParam(url.searchParams.get('limit'), 200);
673
707
  const records = await this.store.listMessages(chatId, limit);
674
- const messages = records.map(item => ({
675
- id: item.id,
676
- chatId: item.chatId,
677
- role: item.role === 'tool' ? 'assistant' : item.role,
678
- content: item.content,
679
- createdAt: item.createdAt,
680
- messageMetadata: item.messageMetadata,
681
- }));
708
+ const messages = records.map(item => {
709
+ const agentId = persistedAgentIdFromMetadata(item.messageMetadata);
710
+ return {
711
+ id: item.id,
712
+ chatId: item.chatId,
713
+ role: item.role === 'tool' ? 'assistant' : item.role,
714
+ content: item.content,
715
+ createdAt: item.createdAt,
716
+ messageMetadata: item.messageMetadata,
717
+ ...(agentId ? { agentId } : {}),
718
+ };
719
+ });
682
720
  // W7-1: 崩溃/强杀中断的 turn 没有 completionStatus 落库——签名是
683
721
  // settings_kv 里残留的 turn_active 标记(turn 开始前写、回复落库后
684
722
  // 才清)。cancelled/failed 都是活进程写下的终态,标记已清,不在此列。
@@ -1949,8 +1987,14 @@ export class LocalBackendRouter {
1949
1987
  // likewise appends nothing: the crashed turn's user message is already
1950
1988
  // persisted (and already in the durable record).
1951
1989
  let currentUserMessageId;
1990
+ // 本轮被点名的智能体:菜单点选带来的 id 优先,手打的 `@名字` 从正文
1991
+ // 解析补齐(两者缺一,提及就在后端消失,只剩前端徽章)。
1992
+ const mentionedAgentIds = resolveMentionedAgentIds(payload, userMessageText, await this.store.listChatAgents());
1952
1993
  if (!regenerateMatch && !isResume) {
1953
- const userMessage = await this.store.addMessage(chatId, 'user', userMessageText);
1994
+ const userMeta = mentionedAgentIds.length > 0
1995
+ ? JSON.stringify({ mentionedAgentIds })
1996
+ : null;
1997
+ const userMessage = await this.store.addMessage(chatId, 'user', userMessageText, userMeta);
1954
1998
  currentUserMessageId = userMessage.id;
1955
1999
  emit(this.sseData({ type: 'user_message', message: userMessage }));
1956
2000
  }
@@ -1959,7 +2003,7 @@ export class LocalBackendRouter {
1959
2003
  const chatMode = payload.mode === 'plan' ? 'plan' : 'agent';
1960
2004
  // 本轮生效的智能体:人设、技能勾选、工具策略同源。必须先解析——工具
1961
2005
  // 策略决定工具列表,工具列表又决定技能的触发条件。
1962
- const turnAgents = await this.resolveTurnAgents(chatId, payload);
2006
+ const turnAgents = await this.resolveTurnAgents(chatId, payload, mentionedAgentIds);
1963
2007
  // Wave 2 工具分层:模型可见列表只出 direct 层(内置工具 + tool_search
1964
2008
  // 发现缝);MCP 动态工具全在 deferred 层,经 tool_search 命中即调。
1965
2009
  // 附带收益:MCP 目录增删不再改动系统提示词里的工具名录,前缀更稳。
@@ -2003,7 +2047,8 @@ export class LocalBackendRouter {
2003
2047
  available: turnTools.some((t) => t.name === token),
2004
2048
  };
2005
2049
  }
2006
- const { systemPrompt, messages, skillContext } = await this.buildConversationMessages(chatId, cleanUserMessageText, payload, turnTools, turnAgents, forcedSkillName, chatMode, forcedMcpTool, currentUserMessageId);
2050
+ const mentionRoster = await buildMentionDelegateRoster(turnAgents.delegates, turnTools.map((tool) => tool.name));
2051
+ const { systemPrompt, messages, skillContext } = await this.buildConversationMessages(chatId, cleanUserMessageText, payload, turnTools, turnAgents, forcedSkillName, chatMode, forcedMcpTool, currentUserMessageId, mentionRoster);
2007
2052
  // A4: the sidecar-hosted CoreLoop is the only chat path (the TS loop
2008
2053
  // was deleted 2026-08-26 after default-on + canary verification). Tools
2009
2054
  // round-trip back to this process over the reverse channel. If the
@@ -2026,6 +2071,8 @@ export class LocalBackendRouter {
2026
2071
  shouldGenerateTitle,
2027
2072
  firstUserMessageForTitle,
2028
2073
  resume: isResume,
2074
+ subagent: mergeTurnSubagentParam(Object.fromEntries(mentionRoster.map((row) => [row.profileName, row.profile]))),
2075
+ parentAgentId: turnAgents.parent?.id ?? null,
2029
2076
  });
2030
2077
  }
2031
2078
  /**
@@ -2128,48 +2175,38 @@ export class LocalBackendRouter {
2128
2175
  /**
2129
2176
  * 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
2130
2177
  *
2131
- * 优先级:payload.mentionedAgentId(@提及)> mentionedAgentIds >
2132
- * payload.agentId > chat.agentId > 无。@提及多个时按提及顺序合并——
2133
- * rolePrompt 逐段拼装(第一个为主角色),能力面取最宽松
2134
- * (见 {@link mergeAgentCapabilities})。
2178
+ * 父代理 = payload.agentId(专家下拉)> chat.agentId。`@` 提及只进
2179
+ * delegates,不再顶替人设。点到父自己也照样进 delegates:`@` 的语义是
2180
+ * 「起一个子代理」,点自己就是起一个自己的独立副本,父代理仍是拆分与
2181
+ * 汇总的那一个。
2135
2182
  *
2136
2183
  * 必须在拼装工具列表之前调用:`toolPolicy` 决定每轮 `turnTools`,
2137
2184
  * 而工具列表又反过来决定技能的触发条件。
2138
2185
  *
2139
2186
  * @param chatId 当前对话 id。
2140
2187
  * @param payload 前端提交的流请求体。
2141
- * @returns 本轮的智能体顺序、人设智能体、自称,以及合并后的能力面。
2188
+ * @returns 父代理、委派对象、人设前言与父的能力面。
2142
2189
  */
2143
- async resolveTurnAgents(chatId, payload) {
2144
- const mentionedAgentIds = Array.isArray(payload.mentionedAgentIds)
2145
- ? payload.mentionedAgentIds.filter((s) => typeof s === 'string')
2146
- : [];
2147
- const orderedAgentIds = [
2148
- ...(typeof payload.mentionedAgentId === 'string' && payload.mentionedAgentId
2149
- ? [payload.mentionedAgentId]
2150
- : []),
2151
- ...mentionedAgentIds,
2152
- ].filter((id, idx, arr) => arr.indexOf(id) === idx);
2153
- if (orderedAgentIds.length === 0) {
2154
- const fallbackId = (typeof payload.agentId === 'string' && payload.agentId) ||
2155
- (await this.store.getChat(chatId))?.agentId ||
2156
- null;
2157
- if (fallbackId)
2158
- orderedAgentIds.push(fallbackId);
2159
- }
2160
- const agents = (await Promise.all(orderedAgentIds.map((id) => this.store.getChatAgent(id))))
2161
- .filter((agent) => Boolean(agent));
2190
+ async resolveTurnAgents(chatId, payload, mentionedAgentIds) {
2191
+ const parentId = (typeof payload.agentId === 'string' && payload.agentId) ||
2192
+ (await this.store.getChat(chatId))?.agentId ||
2193
+ null;
2194
+ const parent = parentId ? await this.store.getChatAgent(parentId) : null;
2195
+ const delegates = [];
2196
+ for (const id of mentionedAgentIds) {
2197
+ const agent = await this.store.getChatAgent(id);
2198
+ if (agent)
2199
+ delegates.push(agent);
2200
+ }
2162
2201
  return {
2163
- orderedAgentIds,
2164
- // 能力面来自全部解析到的智能体,人设前言只用配了 rolePrompt 的那些——
2165
- // 自建智能体可以只勾技能、不写人设。自称始终跟第一个解析到的智能体,
2166
- // 不要求写了人设——否则没 rolePrompt 的角色会回落成产品品牌。
2167
- personaAgents: agents.filter((agent) => Boolean(agent.rolePrompt)),
2168
- identityName: agents[0]?.name.trim() || null,
2169
- capability: mergeAgentCapabilities(agents),
2202
+ parent,
2203
+ delegates,
2204
+ personaAgents: parent?.rolePrompt ? [parent] : [],
2205
+ identityName: parent?.name.trim() || null,
2206
+ capability: mergeAgentCapabilities(parent ? [parent] : []),
2170
2207
  };
2171
2208
  }
2172
- async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId) {
2209
+ async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId, mentionRoster = []) {
2173
2210
  // 跨轮压缩由框架 CoreLoop 拥有:token 压力触发 CompactionHooks(已接真实
2174
2211
  // summarizer),压缩边界持久化到 durable record,W6-10 透视 reconcile 保证
2175
2212
  // 压缩跨轮存活。桌面把全量原始历史作为种子发给框架——不再维护桌面侧滚动
@@ -2370,6 +2407,14 @@ export class LocalBackendRouter {
2370
2407
  if (finalUserImages.notes.length > 0) {
2371
2408
  finalUserContent = `${finalUserContent}\n\n【附件图片】\n${finalUserImages.notes.join('\n')}`;
2372
2409
  }
2410
+ if (mentionRoster.length > 0) {
2411
+ finalUserContent = `${finalUserContent}\n\n${buildDelegateDispatchInstruction(mentionRoster.map((row) => ({
2412
+ name: row.name,
2413
+ profileName: row.profileName,
2414
+ toolFilter: row.profile.toolFilter,
2415
+ isSelf: row.agentId === turnAgents.parent?.id,
2416
+ })))}`;
2417
+ }
2373
2418
  return {
2374
2419
  // W2.8.2: the system prompt travels as a typed fragment param (cap
2375
2420
  // enforced sidecar-side), not as a leading system message.
@@ -2771,7 +2816,7 @@ export class LocalBackendRouter {
2771
2816
  * the reverse-channel handler registered in main.ts.
2772
2817
  */
2773
2818
  async handleCoreLoopTurn(args) {
2774
- const { chatId, systemPrompt, messages, skillContext, turnTools, toolPolicy, chatMode, payload, emit, signal, shouldGenerateTitle, firstUserMessageForTitle, resume, } = args;
2819
+ const { chatId, systemPrompt, messages, skillContext, turnTools, toolPolicy, chatMode, payload, emit, signal, shouldGenerateTitle, firstUserMessageForTitle, resume, subagent, parentAgentId, } = args;
2775
2820
  const supervisor = getSidecarSupervisor();
2776
2821
  if (!supervisor) {
2777
2822
  emit(this.sse('error', { message: 'coreloop enabled but sidecar is not running' }));
@@ -2936,7 +2981,7 @@ export class LocalBackendRouter {
2936
2981
  // 带工具域/轮次/并发/系统提示,模型按画像 description 选委派
2937
2982
  // 对象;per-profile model 留给设置面。子代理作为 AgentPool 池化
2938
2983
  // 子运行执行,生命周期经 onChildEvent → SSE 进 UI 编排卡片。
2939
- subagent: builtinSubagentParam(),
2984
+ subagent,
2940
2985
  // 六件套(agent_spawn/send/wait/close/list/interrupt)收缩为
2941
2986
  // opt-in 高级模式:STEERABLE_ORCHESTRATION=1 开启;开启后与
2942
2987
  // delegate 共享同一 AgentPool(同一 maxParallel 预算)。
@@ -3024,8 +3069,23 @@ export class LocalBackendRouter {
3024
3069
  onChildEvent: (event) => {
3025
3070
  // P3.1: forward child-agent lifecycle to the renderer; it renders
3026
3071
  // the orchestration card (spawn → running → terminal status).
3027
- children.push({ ...event });
3028
- emit(this.sseData({ type: 'orchestration_child', ...event }));
3072
+ // Narrowed to the lifecycle fields: the sidecar's `agent.child`
3073
+ // notification also carries its per-stream `streamId`, which has no
3074
+ // meaning once the stream ends and must not reach the persisted
3075
+ // assistant metadata below.
3076
+ const lifecycle = {
3077
+ kind: event.kind,
3078
+ childId: event.childId,
3079
+ ...(event.task !== undefined ? { task: event.task } : {}),
3080
+ ...(event.depth !== undefined ? { depth: event.depth } : {}),
3081
+ ...(event.status !== undefined ? { status: event.status } : {}),
3082
+ ...(event.error !== undefined ? { error: event.error } : {}),
3083
+ ...(event.profile !== undefined ? { profile: event.profile } : {}),
3084
+ // 子代理自己的 durable record:右侧过程面板据此回看它的推理与工具。
3085
+ ...(event.recordId !== undefined ? { recordId: event.recordId } : {}),
3086
+ };
3087
+ children.push(lifecycle);
3088
+ emit(this.sseData({ type: 'orchestration_child', ...lifecycle }));
3029
3089
  },
3030
3090
  };
3031
3091
  // 自动续跑:`budget_exhausted` 是「被预算墙截停」而不是「模型认为做
@@ -3131,6 +3191,8 @@ export class LocalBackendRouter {
3131
3191
  mode: chatMode,
3132
3192
  coreloop: true,
3133
3193
  traceId: outcomeTraceId,
3194
+ ...(parentAgentId ? { agentId: parentAgentId } : {}),
3195
+ ...(children.length > 0 ? { orchestrationChildEvents: children } : {}),
3134
3196
  ...(durationMs != null ? { durationMs } : {}),
3135
3197
  ...(autoContinuations > 0 ? { autoContinuations } : {}),
3136
3198
  ...(turnFiles.length > 0 ? { turnFiles } : {}),
@@ -1,5 +1,6 @@
1
1
  /**
2
- * 内置 subagent_type 画像集(CC `.claude/agents` parity)。
2
+ * 内置 subagent_type 画像集(CC `.claude/agents` parity)以及
3
+ * ChatAgent → 提及子代理画像的映射(`@` 委派 W1)。
3
4
  *
4
5
  * 桌面不再向 sidecar 传裸 `subagent: true`——命名画像让每个委派带
5
6
  * 工具域 / 轮次上限 / 并发性 / 系统提示,模型按画像 description 选择
@@ -11,7 +12,9 @@
11
12
  * - per-profile model 刻意不设:跟随父模型;模型路由留给设置面。
12
13
  * - 画像系统提示是种进子 loop 的第一条 system 消息(子代理看不到
13
14
  * 主对话的系统提示,画像提示就是它的全部角色设定)。
15
+ * - W2 落地前 toolFilter 必须与父本轮工具面取交集(子 ⊆ 父)。
14
16
  */
17
+ import { type AgentCapabilityInput, type AgentToolPolicy } from './agent-capability.js';
15
18
  export interface BuiltinSubagentProfile {
16
19
  toolFilter?: string[];
17
20
  maxRounds?: number;
@@ -19,6 +22,22 @@ export interface BuiltinSubagentProfile {
19
22
  description: string;
20
23
  systemPrompt?: string;
21
24
  }
25
+ /** 映射用的智能体子集(身份 + 能力面),不依赖存储层类型。 */
26
+ export interface SubagentAgentInput extends AgentCapabilityInput {
27
+ id: string;
28
+ slug: string | null;
29
+ name: string;
30
+ rolePrompt: string | null;
31
+ description?: string | null;
32
+ }
33
+ /** 一次 `@` 提及转出的画像,连同显示名,供派发指令与 sidecar 参数共用。 */
34
+ export interface MentionDelegateProfile {
35
+ /** 被提及智能体的 id——调用方据此认出自提及那一行。 */
36
+ agentId: string;
37
+ name: string;
38
+ profileName: string;
39
+ profile: BuiltinSubagentProfile;
40
+ }
22
41
  export declare const BUILTIN_SUBAGENT_PROFILES: Record<string, BuiltinSubagentProfile>;
23
42
  /**
24
43
  * chat.stream 的 `subagent` 参数:内置画像集。调用方(普通回合与
@@ -28,3 +47,71 @@ export declare const BUILTIN_SUBAGENT_PROFILES: Record<string, BuiltinSubagentPr
28
47
  export declare function builtinSubagentParam(): {
29
48
  profiles: Record<string, BuiltinSubagentProfile>;
30
49
  };
50
+ /**
51
+ * 画像名:优先稳定 ASCII slug,否则英文名 slugify,再否则 `agent-<id>`。
52
+ *
53
+ * @param agent 被提及的智能体。
54
+ * @returns 可作为 `subagent_type` enum 的画像名。
55
+ */
56
+ export declare function profileNameForAgent(agent: {
57
+ id: string;
58
+ slug: string | null;
59
+ name: string;
60
+ }): string;
61
+ /**
62
+ * W2 过渡期:子策略放行的工具与父本轮工具面取交集。
63
+ * 交集等于父全集时不设 filter(子继承父的工具域)。
64
+ *
65
+ * @param childPolicy 子智能体自己的工具策略。
66
+ * @param parentToolNames 父本轮已经广告给模型的工具名。
67
+ * @returns 子代理 toolFilter;`undefined` 表示不比父更窄。
68
+ */
69
+ export declare function resolveChildToolFilter(childPolicy: AgentToolPolicy, parentToolNames: readonly string[]): string[] | undefined;
70
+ /**
71
+ * 把本轮被 `@` 的智能体译成 sidecar 画像(含 W1 夹紧)。
72
+ *
73
+ * @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
74
+ * @param parentToolNames 父本轮模型可见工具名。
75
+ * @returns 画像名 → 画像。
76
+ */
77
+ export declare function buildMentionSubagentProfiles(delegates: readonly SubagentAgentInput[], parentToolNames: readonly string[]): Promise<Record<string, BuiltinSubagentProfile>>;
78
+ /**
79
+ * 与 {@link buildMentionSubagentProfiles} 同源,额外带显示名供派发指令使用。
80
+ *
81
+ * @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
82
+ * @param parentToolNames 父本轮模型可见工具名。
83
+ * @returns 提及顺序的画像花名册。
84
+ */
85
+ export declare function buildMentionDelegateRoster(delegates: readonly SubagentAgentInput[], parentToolNames: readonly string[]): Promise<MentionDelegateProfile[]>;
86
+ /**
87
+ * 注入本轮最后一条用户消息的强制派发指令。
88
+ *
89
+ * 被点名的父代理自己也在名单里(`isSelf`):它要起一个独立副本去跑那份
90
+ * 子任务,而不是顺手自己做——否则 `requiredProfiles` 完成门会一直把回合
91
+ * 退回来。
92
+ *
93
+ * @param delegates 被点名的子代理(显示名 + 画像名 + 实际工具面 + 是否是自己)。
94
+ * @returns 派发指令正文。
95
+ */
96
+ export declare function buildDelegateDispatchInstruction(delegates: ReadonlyArray<{
97
+ name: string;
98
+ profileName: string;
99
+ toolFilter?: string[];
100
+ isSelf?: boolean;
101
+ }>): string;
102
+ export interface TurnSubagentParam {
103
+ profiles: Record<string, BuiltinSubagentProfile>;
104
+ maxParallel?: number;
105
+ /** 本轮必须收到委派的画像(被 `@` 的智能体);sidecar 据此挂完成门。 */
106
+ requiredProfiles?: string[];
107
+ }
108
+ /**
109
+ * 内置画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
110
+ *
111
+ * 提及画像同时作为 `requiredProfiles` 下发:强制派发此前只是提示词里的
112
+ * 一句话,模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。
113
+ *
114
+ * @param mentionProfiles 本轮提及转出的画像;空对象则与 {@link builtinSubagentParam} 相同。
115
+ * @returns 下发给 sidecar 的 `subagent` 参数。
116
+ */
117
+ export declare function mergeTurnSubagentParam(mentionProfiles: Record<string, BuiltinSubagentProfile>): TurnSubagentParam;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * 内置 subagent_type 画像集(CC `.claude/agents` parity)。
2
+ * 内置 subagent_type 画像集(CC `.claude/agents` parity)以及
3
+ * ChatAgent → 提及子代理画像的映射(`@` 委派 W1)。
3
4
  *
4
5
  * 桌面不再向 sidecar 传裸 `subagent: true`——命名画像让每个委派带
5
6
  * 工具域 / 轮次上限 / 并发性 / 系统提示,模型按画像 description 选择
@@ -11,7 +12,11 @@
11
12
  * - per-profile model 刻意不设:跟随父模型;模型路由留给设置面。
12
13
  * - 画像系统提示是种进子 loop 的第一条 system 消息(子代理看不到
13
14
  * 主对话的系统提示,画像提示就是它的全部角色设定)。
15
+ * - W2 落地前 toolFilter 必须与父本轮工具面取交集(子 ⊆ 父)。
14
16
  */
17
+ import { isToolAllowed, mergeAgentCapabilities, normalizeToolPolicy, resolveSkillExcludes, } from './agent-capability.js';
18
+ import { buildSystemPrompt } from './prompt-builder.js';
19
+ import { loadSkills } from './skill-loader.js';
15
20
  /** 只读工具域:探索与调研画像共享的「摸不到写操作」基线。 */
16
21
  const READ_ONLY_TOOLS = [
17
22
  'local_read_file',
@@ -21,10 +26,10 @@ const READ_ONLY_TOOLS = [
21
26
  'web_fetch',
22
27
  'mcp_list_tools',
23
28
  ];
29
+ const READ_ONLY_TOOL_SET = new Set(READ_ONLY_TOOLS);
24
30
  export const BUILTIN_SUBAGENT_PROFILES = {
25
31
  explore: {
26
32
  toolFilter: READ_ONLY_TOOLS,
27
- maxRounds: 12,
28
33
  concurrent: true,
29
34
  description: '只读代码侦察。搜索、读文件、定位「在哪里/是什么」类问题;不做任何修改,返回带路径:行号的发现。',
30
35
  systemPrompt: [
@@ -37,7 +42,6 @@ export const BUILTIN_SUBAGENT_PROFILES = {
37
42
  },
38
43
  research: {
39
44
  toolFilter: [...READ_ONLY_TOOLS, 'local_write_file'],
40
- maxRounds: 16,
41
45
  concurrent: true,
42
46
  description: '深度调研。多轮 web 搜索/抓取并交叉验证,给出带来源 URL 的结论;可把报告写入文件。',
43
47
  systemPrompt: [
@@ -52,7 +56,6 @@ export const BUILTIN_SUBAGENT_PROFILES = {
52
56
  coder: {
53
57
  // 不设 toolFilter:全工具域。写操作串行(concurrent: false),避免两个
54
58
  // 实现代理同时改同一批文件。
55
- maxRounds: 16,
56
59
  concurrent: false,
57
60
  description: '全工具实现代理。改代码、跑命令、自验结果;适合自包含的实现/修复任务。',
58
61
  systemPrompt: [
@@ -72,3 +75,176 @@ export const BUILTIN_SUBAGENT_PROFILES = {
72
75
  export function builtinSubagentParam() {
73
76
  return { profiles: BUILTIN_SUBAGENT_PROFILES };
74
77
  }
78
+ /**
79
+ * 画像名:优先稳定 ASCII slug,否则英文名 slugify,再否则 `agent-<id>`。
80
+ *
81
+ * @param agent 被提及的智能体。
82
+ * @returns 可作为 `subagent_type` enum 的画像名。
83
+ */
84
+ export function profileNameForAgent(agent) {
85
+ const slug = agent.slug?.trim() ?? '';
86
+ if (isStableProfileName(slug))
87
+ return slug;
88
+ const fromName = slugifyProfileName(agent.name);
89
+ if (fromName)
90
+ return fromName;
91
+ const fromId = agent.id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 16);
92
+ return fromId ? `agent-${fromId}` : 'agent';
93
+ }
94
+ function isStableProfileName(value) {
95
+ return /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(value);
96
+ }
97
+ function slugifyProfileName(raw) {
98
+ return raw
99
+ .toLowerCase()
100
+ .replace(/[^a-z0-9]+/g, '-')
101
+ .replace(/^-+|-+$/g, '')
102
+ .slice(0, 40);
103
+ }
104
+ /**
105
+ * W2 过渡期:子策略放行的工具与父本轮工具面取交集。
106
+ * 交集等于父全集时不设 filter(子继承父的工具域)。
107
+ *
108
+ * @param childPolicy 子智能体自己的工具策略。
109
+ * @param parentToolNames 父本轮已经广告给模型的工具名。
110
+ * @returns 子代理 toolFilter;`undefined` 表示不比父更窄。
111
+ */
112
+ export function resolveChildToolFilter(childPolicy, parentToolNames) {
113
+ const policy = normalizeToolPolicy(childPolicy);
114
+ const allowed = parentToolNames.filter((name) => isToolAllowed(policy, name));
115
+ // `all` 且交集等于父全集 → 不设 filter,子代理继承父的工具域。
116
+ // allowlist / denylist 始终下发交集清单,即使碰巧等于父全集——画像
117
+ // roster 要让模型看见该子代理真正收窄后的工具面。
118
+ if (policy.mode === 'all')
119
+ return undefined;
120
+ return allowed;
121
+ }
122
+ function isReadOnlyToolDomain(toolNames) {
123
+ return toolNames.every((name) => READ_ONLY_TOOL_SET.has(name));
124
+ }
125
+ function profileDescription(agent) {
126
+ const summary = (agent.rolePrompt || agent.description || '')
127
+ .replace(/\s+/g, ' ')
128
+ .trim()
129
+ .slice(0, 80);
130
+ return summary ? `${agent.name} — ${summary}` : agent.name;
131
+ }
132
+ /**
133
+ * 把本轮被 `@` 的智能体译成 sidecar 画像(含 W1 夹紧)。
134
+ *
135
+ * @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
136
+ * @param parentToolNames 父本轮模型可见工具名。
137
+ * @returns 画像名 → 画像。
138
+ */
139
+ export async function buildMentionSubagentProfiles(delegates, parentToolNames) {
140
+ const roster = await buildMentionDelegateRoster(delegates, parentToolNames);
141
+ return Object.fromEntries(roster.map((row) => [row.profileName, row.profile]));
142
+ }
143
+ /**
144
+ * 与 {@link buildMentionSubagentProfiles} 同源,额外带显示名供派发指令使用。
145
+ *
146
+ * @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
147
+ * @param parentToolNames 父本轮模型可见工具名。
148
+ * @returns 提及顺序的画像花名册。
149
+ */
150
+ export async function buildMentionDelegateRoster(delegates, parentToolNames) {
151
+ const roster = [];
152
+ const used = new Set();
153
+ for (const agent of delegates) {
154
+ let profileName = profileNameForAgent(agent);
155
+ if (used.has(profileName)) {
156
+ const suffix = agent.id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8);
157
+ profileName = suffix ? `${profileName}-${suffix}` : `${profileName}-dup`;
158
+ }
159
+ used.add(profileName);
160
+ roster.push({
161
+ agentId: agent.id,
162
+ name: agent.name,
163
+ profileName,
164
+ profile: await buildOneMentionProfile(agent, parentToolNames),
165
+ });
166
+ }
167
+ return roster;
168
+ }
169
+ async function buildOneMentionProfile(agent, parentToolNames) {
170
+ const toolFilter = resolveChildToolFilter(agent.toolPolicy, parentToolNames);
171
+ const domain = toolFilter ?? parentToolNames;
172
+ const readOnly = isReadOnlyToolDomain(domain);
173
+ const capability = mergeAgentCapabilities([agent]);
174
+ const excludeSkillNames = capability.allowExternalSkills
175
+ ? ['plan-mode']
176
+ : resolveSkillExcludes(capability, await loadSkills({ ignoreConditions: true }), ['plan-mode']);
177
+ const personaPreamble = agent.rolePrompt
178
+ ? `【当前角色】${agent.name}\n${agent.rolePrompt}`
179
+ : '';
180
+ const built = await buildSystemPrompt({
181
+ personaPreamble,
182
+ identityName: agent.name,
183
+ pinnedSkillNames: capability.pinnedSkills,
184
+ ignoreConditions: capability.loadAllSkills,
185
+ excludeSkillNames,
186
+ toolNames: domain,
187
+ });
188
+ return {
189
+ ...(toolFilter ? { toolFilter } : {}),
190
+ concurrent: readOnly,
191
+ description: profileDescription(agent),
192
+ systemPrompt: built.prompt,
193
+ };
194
+ }
195
+ /**
196
+ * 注入本轮最后一条用户消息的强制派发指令。
197
+ *
198
+ * 被点名的父代理自己也在名单里(`isSelf`):它要起一个独立副本去跑那份
199
+ * 子任务,而不是顺手自己做——否则 `requiredProfiles` 完成门会一直把回合
200
+ * 退回来。
201
+ *
202
+ * @param delegates 被点名的子代理(显示名 + 画像名 + 实际工具面 + 是否是自己)。
203
+ * @returns 派发指令正文。
204
+ */
205
+ export function buildDelegateDispatchInstruction(delegates) {
206
+ const names = delegates.map((row) => row.name).join('、');
207
+ const roster = delegates
208
+ .map((row) => {
209
+ const tools = row.toolFilter?.length
210
+ ? `本轮可用工具:${row.toolFilter.join('、')}`
211
+ : '本轮可用工具与你相同';
212
+ const self = row.isSelf ? '就是你自己的画像,起一个独立副本去跑;' : '';
213
+ return `- **${row.name}**(subagent_type=\`${row.profileName}\`):${self}${tools}`;
214
+ })
215
+ .join('\n');
216
+ const hasSelf = delegates.some((row) => row.isSelf);
217
+ return [
218
+ `用户点名了 **${names}**。本轮你必须把活交给它们,用 \`delegate_subagent\`(\`subagent_type\` 填画像名),每人至少一份子任务。`,
219
+ '',
220
+ roster,
221
+ '',
222
+ '- 子任务**彼此独立**时:一句话说明分工,紧接着在**同一轮**把全部调用发出,并把可并行的排在一起。',
223
+ '- 子任务**有依赖**时:按依赖顺序分轮派发,并把前一轮的结论写进后一轮的 `task`。',
224
+ '- 每份 `task` 必须自包含(目标、输入、交付形式、验收标准)——子代理看不到本对话。',
225
+ '- 禁止只写分工然后停手;也禁止漏掉任何一位被点名的代理。',
226
+ ...(hasSelf
227
+ ? [
228
+ '- 名单里包含你自己时同样要发 `delegate_subagent`:那是一个看不到本对话的独立副本,自己顺手做不算完成派发。',
229
+ ]
230
+ : []),
231
+ '- 若某子代理注明了本轮可用工具,不要尝试让它调用未列出的工具。',
232
+ ].join('\n');
233
+ }
234
+ /**
235
+ * 内置画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
236
+ *
237
+ * 提及画像同时作为 `requiredProfiles` 下发:强制派发此前只是提示词里的
238
+ * 一句话,模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。
239
+ *
240
+ * @param mentionProfiles 本轮提及转出的画像;空对象则与 {@link builtinSubagentParam} 相同。
241
+ * @returns 下发给 sidecar 的 `subagent` 参数。
242
+ */
243
+ export function mergeTurnSubagentParam(mentionProfiles) {
244
+ const mentionNames = Object.keys(mentionProfiles);
245
+ return {
246
+ profiles: { ...BUILTIN_SUBAGENT_PROFILES, ...mentionProfiles },
247
+ ...(mentionNames.length > 4 ? { maxParallel: mentionNames.length } : {}),
248
+ ...(mentionNames.length > 0 ? { requiredProfiles: mentionNames } : {}),
249
+ };
250
+ }
@@ -354,16 +354,26 @@ export interface SidecarChatStreamRequest {
354
354
  subagent?: boolean | {
355
355
  toolFilter?: string[];
356
356
  maxParallel?: number;
357
+ /** Child round / tool-error walls; default to the parent loop's. */
358
+ maxRounds?: number;
359
+ maxToolErrors?: number;
357
360
  profiles?: Record<string, {
358
361
  toolFilter?: string[];
359
362
  model?: string;
360
363
  maxRounds?: number;
364
+ maxToolErrors?: number;
361
365
  concurrent?: boolean;
362
366
  description?: string;
363
367
  /** Profile system prompt, seeded as the child loop's first message
364
368
  * (CC `.claude/agents` body parity). */
365
369
  systemPrompt?: string;
366
370
  }>;
371
+ /**
372
+ * Profiles that MUST each receive a delegation this turn (the host's
373
+ * `@` mentions). A `completed` turn that skipped one is retried — the
374
+ * dispatch instruction alone is prompt text a model can narrate past.
375
+ */
376
+ requiredProfiles?: string[];
367
377
  };
368
378
  /** A6 layered skill disclosure: the sidecar injects the catalog layer
369
379
  * (first-round pre_step, recorded as a hook_action event) and answers
@@ -555,6 +565,12 @@ export interface SidecarChildEvent {
555
565
  status?: string;
556
566
  error?: string;
557
567
  profile?: string;
568
+ /**
569
+ * The child's own durable record (`<parent record>:child:<lineage id>`),
570
+ * present on `child_spawned` when the host wired a history store. Read it
571
+ * back to render the delegation's reasoning + tool calls.
572
+ */
573
+ recordId?: string;
558
574
  }
559
575
  export interface SidecarChatStreamHandlers {
560
576
  onChunk?: (chunk: SidecarStreamChunk) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell",
3
- "version": "0.6.28",
3
+ "version": "0.6.29",
4
4
  "description": "Steerable framework — product-neutral desktop/headless agent host (Tier 5). Electron main + preload + headless HTTP server (BS) sharing one storage/sidecar/tooling core; scenario packs extend it through the @steerable/pack-sdk contract and are composed at build time by the consuming product.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/",
@@ -49,9 +49,9 @@
49
49
  "electron-store": "^8.1.0",
50
50
  "he": "^1.2.0",
51
51
  "node-pty": "^1.1.0",
52
- "@steerable/agent-harness": "0.6.28",
53
- "@steerable/pack-sdk": "0.6.28",
54
- "@steerable/agent-protocol": "0.6.28"
52
+ "@steerable/agent-harness": "0.6.29",
53
+ "@steerable/pack-sdk": "0.6.29",
54
+ "@steerable/agent-protocol": "0.6.29"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/better-sqlite3": "^7.6.13",