@steerable/agent-shell 0.6.28 → 0.6.30
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.
- package/contracts/tool-contract.json +8 -0
- package/dist/local-backend/agent-capability.d.ts +4 -5
- package/dist/local-backend/agent-capability.js +3 -4
- package/dist/local-backend/mention-targets.d.ts +41 -0
- package/dist/local-backend/mention-targets.js +88 -0
- package/dist/local-backend/router.d.ts +5 -5
- package/dist/local-backend/router.js +130 -49
- package/dist/local-backend/subagent-profiles.d.ts +136 -1
- package/dist/local-backend/subagent-profiles.js +282 -4
- package/dist/sidecar/types.d.ts +16 -0
- package/package.json +4 -4
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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 {
|
|
11
|
+
import { buildAmbientDelegateRoster, buildDelegateDispatchInstruction, buildDelegateRosterHint, buildMentionDelegateRoster, BUILTIN_SUBAGENT_PROFILES, 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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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,15 @@ 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 chatAgents = await this.store.listChatAgents();
|
|
1993
|
+
const mentionedAgentIds = resolveMentionedAgentIds(payload, userMessageText, chatAgents);
|
|
1952
1994
|
if (!regenerateMatch && !isResume) {
|
|
1953
|
-
const
|
|
1995
|
+
const userMeta = mentionedAgentIds.length > 0
|
|
1996
|
+
? JSON.stringify({ mentionedAgentIds })
|
|
1997
|
+
: null;
|
|
1998
|
+
const userMessage = await this.store.addMessage(chatId, 'user', userMessageText, userMeta);
|
|
1954
1999
|
currentUserMessageId = userMessage.id;
|
|
1955
2000
|
emit(this.sseData({ type: 'user_message', message: userMessage }));
|
|
1956
2001
|
}
|
|
@@ -1959,7 +2004,7 @@ export class LocalBackendRouter {
|
|
|
1959
2004
|
const chatMode = payload.mode === 'plan' ? 'plan' : 'agent';
|
|
1960
2005
|
// 本轮生效的智能体:人设、技能勾选、工具策略同源。必须先解析——工具
|
|
1961
2006
|
// 策略决定工具列表,工具列表又决定技能的触发条件。
|
|
1962
|
-
const turnAgents = await this.resolveTurnAgents(chatId, payload);
|
|
2007
|
+
const turnAgents = await this.resolveTurnAgents(chatId, payload, mentionedAgentIds);
|
|
1963
2008
|
// Wave 2 工具分层:模型可见列表只出 direct 层(内置工具 + tool_search
|
|
1964
2009
|
// 发现缝);MCP 动态工具全在 deferred 层,经 tool_search 命中即调。
|
|
1965
2010
|
// 附带收益:MCP 目录增删不再改动系统提示词里的工具名录,前缀更稳。
|
|
@@ -2003,7 +2048,22 @@ export class LocalBackendRouter {
|
|
|
2003
2048
|
available: turnTools.some((t) => t.name === token),
|
|
2004
2049
|
};
|
|
2005
2050
|
}
|
|
2006
|
-
const
|
|
2051
|
+
const turnToolNames = turnTools.map((tool) => tool.name);
|
|
2052
|
+
const mentionRoster = await buildMentionDelegateRoster(turnAgents.delegates, turnToolNames);
|
|
2053
|
+
// 常驻可委派名单:其余智能体也进画像,技能/提示里的「交给 X」无需用户
|
|
2054
|
+
// `@` 就能落成一次真实委派。父代理自己不进——它就是本轮的执行者,给它
|
|
2055
|
+
// 一个自己的画像只会诱导无意义的自委派(`@自己` 走提及那条路,仍然可以)。
|
|
2056
|
+
const ambientRoster = await buildAmbientDelegateRoster(chatAgents, turnToolNames, {
|
|
2057
|
+
excludeAgentIds: [
|
|
2058
|
+
...mentionRoster.map((row) => row.agentId),
|
|
2059
|
+
...(turnAgents.parent ? [turnAgents.parent.id] : []),
|
|
2060
|
+
],
|
|
2061
|
+
reservedProfileNames: [
|
|
2062
|
+
...Object.keys(BUILTIN_SUBAGENT_PROFILES),
|
|
2063
|
+
...mentionRoster.map((row) => row.profileName),
|
|
2064
|
+
],
|
|
2065
|
+
});
|
|
2066
|
+
const { systemPrompt, messages, skillContext } = await this.buildConversationMessages(chatId, cleanUserMessageText, payload, turnTools, turnAgents, forcedSkillName, chatMode, forcedMcpTool, currentUserMessageId, { mention: mentionRoster, ambient: ambientRoster });
|
|
2007
2067
|
// A4: the sidecar-hosted CoreLoop is the only chat path (the TS loop
|
|
2008
2068
|
// was deleted 2026-08-26 after default-on + canary verification). Tools
|
|
2009
2069
|
// round-trip back to this process over the reverse channel. If the
|
|
@@ -2026,6 +2086,8 @@ export class LocalBackendRouter {
|
|
|
2026
2086
|
shouldGenerateTitle,
|
|
2027
2087
|
firstUserMessageForTitle,
|
|
2028
2088
|
resume: isResume,
|
|
2089
|
+
subagent: mergeTurnSubagentParam(Object.fromEntries(mentionRoster.map((row) => [row.profileName, row.profile])), Object.fromEntries(ambientRoster.map((row) => [row.profileName, row.profile]))),
|
|
2090
|
+
parentAgentId: turnAgents.parent?.id ?? null,
|
|
2029
2091
|
});
|
|
2030
2092
|
}
|
|
2031
2093
|
/**
|
|
@@ -2128,48 +2190,38 @@ export class LocalBackendRouter {
|
|
|
2128
2190
|
/**
|
|
2129
2191
|
* 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
|
|
2130
2192
|
*
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
2133
|
-
*
|
|
2134
|
-
*
|
|
2193
|
+
* 父代理 = payload.agentId(专家下拉)> chat.agentId。`@` 提及只进
|
|
2194
|
+
* delegates,不再顶替人设。点到父自己也照样进 delegates:`@` 的语义是
|
|
2195
|
+
* 「起一个子代理」,点自己就是起一个自己的独立副本,父代理仍是拆分与
|
|
2196
|
+
* 汇总的那一个。
|
|
2135
2197
|
*
|
|
2136
2198
|
* 必须在拼装工具列表之前调用:`toolPolicy` 决定每轮 `turnTools`,
|
|
2137
2199
|
* 而工具列表又反过来决定技能的触发条件。
|
|
2138
2200
|
*
|
|
2139
2201
|
* @param chatId 当前对话 id。
|
|
2140
2202
|
* @param payload 前端提交的流请求体。
|
|
2141
|
-
* @returns
|
|
2203
|
+
* @returns 父代理、委派对象、人设前言与父的能力面。
|
|
2142
2204
|
*/
|
|
2143
|
-
async resolveTurnAgents(chatId, payload) {
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
const
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
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));
|
|
2205
|
+
async resolveTurnAgents(chatId, payload, mentionedAgentIds) {
|
|
2206
|
+
const parentId = (typeof payload.agentId === 'string' && payload.agentId) ||
|
|
2207
|
+
(await this.store.getChat(chatId))?.agentId ||
|
|
2208
|
+
null;
|
|
2209
|
+
const parent = parentId ? await this.store.getChatAgent(parentId) : null;
|
|
2210
|
+
const delegates = [];
|
|
2211
|
+
for (const id of mentionedAgentIds) {
|
|
2212
|
+
const agent = await this.store.getChatAgent(id);
|
|
2213
|
+
if (agent)
|
|
2214
|
+
delegates.push(agent);
|
|
2215
|
+
}
|
|
2162
2216
|
return {
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
identityName: agents[0]?.name.trim() || null,
|
|
2169
|
-
capability: mergeAgentCapabilities(agents),
|
|
2217
|
+
parent,
|
|
2218
|
+
delegates,
|
|
2219
|
+
personaAgents: parent?.rolePrompt ? [parent] : [],
|
|
2220
|
+
identityName: parent?.name.trim() || null,
|
|
2221
|
+
capability: mergeAgentCapabilities(parent ? [parent] : []),
|
|
2170
2222
|
};
|
|
2171
2223
|
}
|
|
2172
|
-
async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId) {
|
|
2224
|
+
async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId, delegates = { mention: [], ambient: [] }) {
|
|
2173
2225
|
// 跨轮压缩由框架 CoreLoop 拥有:token 压力触发 CompactionHooks(已接真实
|
|
2174
2226
|
// summarizer),压缩边界持久化到 durable record,W6-10 透视 reconcile 保证
|
|
2175
2227
|
// 压缩跨轮存活。桌面把全量原始历史作为种子发给框架——不再维护桌面侧滚动
|
|
@@ -2239,7 +2291,11 @@ export class LocalBackendRouter {
|
|
|
2239
2291
|
.join('\n\n');
|
|
2240
2292
|
const polluted = this.detectToolDenialInHistory(historyAssistantTexts);
|
|
2241
2293
|
const runtimeEnvironment = this.buildRuntimeEnvironmentContext(chatMode);
|
|
2242
|
-
|
|
2294
|
+
// 名录进 realityCheck:两条系统提示拼装路径(技能拼装 / 用户整段覆盖)
|
|
2295
|
+
// 都会带上它,且位置在末尾——不动技能正文那段 prompt cache 前缀。
|
|
2296
|
+
const realityCheck = runtimeEnvironment +
|
|
2297
|
+
this.buildToolRealityCheck(turnTools, polluted, chatMode) +
|
|
2298
|
+
buildDelegateRosterHint([...delegates.mention, ...delegates.ambient]);
|
|
2243
2299
|
// 用户显式覆盖(payload.systemPrompt 优先 / settings.systemPrompt 自定义了且非默认值次之)走
|
|
2244
2300
|
// "整段替换"路径,保持旧行为可被外部完全控制;否则交给 skill-based
|
|
2245
2301
|
// SystemPromptBuilder 根据本轮可用工具动态拼装。
|
|
@@ -2370,6 +2426,14 @@ export class LocalBackendRouter {
|
|
|
2370
2426
|
if (finalUserImages.notes.length > 0) {
|
|
2371
2427
|
finalUserContent = `${finalUserContent}\n\n【附件图片】\n${finalUserImages.notes.join('\n')}`;
|
|
2372
2428
|
}
|
|
2429
|
+
if (delegates.mention.length > 0) {
|
|
2430
|
+
finalUserContent = `${finalUserContent}\n\n${buildDelegateDispatchInstruction(delegates.mention.map((row) => ({
|
|
2431
|
+
name: row.name,
|
|
2432
|
+
profileName: row.profileName,
|
|
2433
|
+
toolFilter: row.profile.toolFilter,
|
|
2434
|
+
isSelf: row.agentId === turnAgents.parent?.id,
|
|
2435
|
+
})))}`;
|
|
2436
|
+
}
|
|
2373
2437
|
return {
|
|
2374
2438
|
// W2.8.2: the system prompt travels as a typed fragment param (cap
|
|
2375
2439
|
// enforced sidecar-side), not as a leading system message.
|
|
@@ -2771,7 +2835,7 @@ export class LocalBackendRouter {
|
|
|
2771
2835
|
* the reverse-channel handler registered in main.ts.
|
|
2772
2836
|
*/
|
|
2773
2837
|
async handleCoreLoopTurn(args) {
|
|
2774
|
-
const { chatId, systemPrompt, messages, skillContext, turnTools, toolPolicy, chatMode, payload, emit, signal, shouldGenerateTitle, firstUserMessageForTitle, resume, } = args;
|
|
2838
|
+
const { chatId, systemPrompt, messages, skillContext, turnTools, toolPolicy, chatMode, payload, emit, signal, shouldGenerateTitle, firstUserMessageForTitle, resume, subagent, parentAgentId, } = args;
|
|
2775
2839
|
const supervisor = getSidecarSupervisor();
|
|
2776
2840
|
if (!supervisor) {
|
|
2777
2841
|
emit(this.sse('error', { message: 'coreloop enabled but sidecar is not running' }));
|
|
@@ -2936,7 +3000,7 @@ export class LocalBackendRouter {
|
|
|
2936
3000
|
// 带工具域/轮次/并发/系统提示,模型按画像 description 选委派
|
|
2937
3001
|
// 对象;per-profile model 留给设置面。子代理作为 AgentPool 池化
|
|
2938
3002
|
// 子运行执行,生命周期经 onChildEvent → SSE 进 UI 编排卡片。
|
|
2939
|
-
subagent
|
|
3003
|
+
subagent,
|
|
2940
3004
|
// 六件套(agent_spawn/send/wait/close/list/interrupt)收缩为
|
|
2941
3005
|
// opt-in 高级模式:STEERABLE_ORCHESTRATION=1 开启;开启后与
|
|
2942
3006
|
// delegate 共享同一 AgentPool(同一 maxParallel 预算)。
|
|
@@ -3024,8 +3088,23 @@ export class LocalBackendRouter {
|
|
|
3024
3088
|
onChildEvent: (event) => {
|
|
3025
3089
|
// P3.1: forward child-agent lifecycle to the renderer; it renders
|
|
3026
3090
|
// the orchestration card (spawn → running → terminal status).
|
|
3027
|
-
|
|
3028
|
-
|
|
3091
|
+
// Narrowed to the lifecycle fields: the sidecar's `agent.child`
|
|
3092
|
+
// notification also carries its per-stream `streamId`, which has no
|
|
3093
|
+
// meaning once the stream ends and must not reach the persisted
|
|
3094
|
+
// assistant metadata below.
|
|
3095
|
+
const lifecycle = {
|
|
3096
|
+
kind: event.kind,
|
|
3097
|
+
childId: event.childId,
|
|
3098
|
+
...(event.task !== undefined ? { task: event.task } : {}),
|
|
3099
|
+
...(event.depth !== undefined ? { depth: event.depth } : {}),
|
|
3100
|
+
...(event.status !== undefined ? { status: event.status } : {}),
|
|
3101
|
+
...(event.error !== undefined ? { error: event.error } : {}),
|
|
3102
|
+
...(event.profile !== undefined ? { profile: event.profile } : {}),
|
|
3103
|
+
// 子代理自己的 durable record:右侧过程面板据此回看它的推理与工具。
|
|
3104
|
+
...(event.recordId !== undefined ? { recordId: event.recordId } : {}),
|
|
3105
|
+
};
|
|
3106
|
+
children.push(lifecycle);
|
|
3107
|
+
emit(this.sseData({ type: 'orchestration_child', ...lifecycle }));
|
|
3029
3108
|
},
|
|
3030
3109
|
};
|
|
3031
3110
|
// 自动续跑:`budget_exhausted` 是「被预算墙截停」而不是「模型认为做
|
|
@@ -3131,6 +3210,8 @@ export class LocalBackendRouter {
|
|
|
3131
3210
|
mode: chatMode,
|
|
3132
3211
|
coreloop: true,
|
|
3133
3212
|
traceId: outcomeTraceId,
|
|
3213
|
+
...(parentAgentId ? { agentId: parentAgentId } : {}),
|
|
3214
|
+
...(children.length > 0 ? { orchestrationChildEvents: children } : {}),
|
|
3134
3215
|
...(durationMs != null ? { durationMs } : {}),
|
|
3135
3216
|
...(autoContinuations > 0 ? { autoContinuations } : {}),
|
|
3136
3217
|
...(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,28 @@ 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
|
+
updatedAt?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 一个可委派对象的画像,连同显示名,供派发指令、名录说明与 sidecar 参数
|
|
37
|
+
* 共用。两个来源共享这个结构:用户 `@` 点名的(强制派发)与常驻可委派
|
|
38
|
+
* 名单里的(模型自主判断)。
|
|
39
|
+
*/
|
|
40
|
+
export interface MentionDelegateProfile {
|
|
41
|
+
/** 被提及智能体的 id——调用方据此认出自提及那一行。 */
|
|
42
|
+
agentId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
profileName: string;
|
|
45
|
+
profile: BuiltinSubagentProfile;
|
|
46
|
+
}
|
|
22
47
|
export declare const BUILTIN_SUBAGENT_PROFILES: Record<string, BuiltinSubagentProfile>;
|
|
23
48
|
/**
|
|
24
49
|
* chat.stream 的 `subagent` 参数:内置画像集。调用方(普通回合与
|
|
@@ -28,3 +53,113 @@ export declare const BUILTIN_SUBAGENT_PROFILES: Record<string, BuiltinSubagentPr
|
|
|
28
53
|
export declare function builtinSubagentParam(): {
|
|
29
54
|
profiles: Record<string, BuiltinSubagentProfile>;
|
|
30
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* 画像名:优先稳定 ASCII slug,否则英文名 slugify,再否则 `agent-<id>`。
|
|
58
|
+
*
|
|
59
|
+
* @param agent 被提及的智能体。
|
|
60
|
+
* @returns 可作为 `subagent_type` enum 的画像名。
|
|
61
|
+
*/
|
|
62
|
+
export declare function profileNameForAgent(agent: {
|
|
63
|
+
id: string;
|
|
64
|
+
slug: string | null;
|
|
65
|
+
name: string;
|
|
66
|
+
}): string;
|
|
67
|
+
/**
|
|
68
|
+
* W2 过渡期:子策略放行的工具与父本轮工具面取交集。
|
|
69
|
+
* 交集等于父全集时不设 filter(子继承父的工具域)。
|
|
70
|
+
*
|
|
71
|
+
* @param childPolicy 子智能体自己的工具策略。
|
|
72
|
+
* @param parentToolNames 父本轮已经广告给模型的工具名。
|
|
73
|
+
* @returns 子代理 toolFilter;`undefined` 表示不比父更窄。
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveChildToolFilter(childPolicy: AgentToolPolicy, parentToolNames: readonly string[]): string[] | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* 把本轮被 `@` 的智能体译成 sidecar 画像(含 W1 夹紧)。
|
|
78
|
+
*
|
|
79
|
+
* @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
|
|
80
|
+
* @param parentToolNames 父本轮模型可见工具名。
|
|
81
|
+
* @returns 画像名 → 画像。
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildMentionSubagentProfiles(delegates: readonly SubagentAgentInput[], parentToolNames: readonly string[]): Promise<Record<string, BuiltinSubagentProfile>>;
|
|
84
|
+
/**
|
|
85
|
+
* 与 {@link buildMentionSubagentProfiles} 同源,额外带显示名供派发指令使用。
|
|
86
|
+
*
|
|
87
|
+
* @param delegates 被 `@` 的智能体,按提及顺序(含被点名的父代理本身)。
|
|
88
|
+
* @param parentToolNames 父本轮模型可见工具名。
|
|
89
|
+
* @returns 提及顺序的画像花名册。
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildMentionDelegateRoster(delegates: readonly SubagentAgentInput[], parentToolNames: readonly string[]): Promise<MentionDelegateProfile[]>;
|
|
92
|
+
/**
|
|
93
|
+
* 常驻可委派名单:会话里的其他智能体也转成画像,模型**无需用户 `@`** 就能
|
|
94
|
+
* 委派。技能正文或自定义提示里写「交给 X」因此能落成一次真实
|
|
95
|
+
* `delegate_subagent`——在此之前 `subagent_type` 的 enum 里只有内置三个画像,
|
|
96
|
+
* 这类指令 fail closed 报 `unknown subagent_type`,模型只能在正文里打出名字。
|
|
97
|
+
*
|
|
98
|
+
* 与提及名单的唯一区别是强制性:这里**不进** `requiredProfiles`。派不派由模型
|
|
99
|
+
* 按任务判断——技能的阶段条件(「阶段 C 才交给 Word 智能体」)在拼装画像时
|
|
100
|
+
* 还不可知,一律强制会把不该派的回合反复退回完成门。
|
|
101
|
+
*
|
|
102
|
+
* @param agents 会话可用的智能体(未归档)。
|
|
103
|
+
* @param parentToolNames 父本轮模型可见工具名。
|
|
104
|
+
* @param options.excludeAgentIds 不进名单的智能体:父代理自己与已在提及名单里的。
|
|
105
|
+
* @param options.reservedProfileNames 已占用的画像名(内置画像 + 提及画像),避免顶掉。
|
|
106
|
+
* @returns 画像花名册,按 `agents` 顺序。
|
|
107
|
+
*/
|
|
108
|
+
export declare function buildAmbientDelegateRoster(agents: readonly SubagentAgentInput[], parentToolNames: readonly string[], options?: {
|
|
109
|
+
excludeAgentIds?: readonly string[];
|
|
110
|
+
reservedProfileNames?: readonly string[];
|
|
111
|
+
}): Promise<MentionDelegateProfile[]>;
|
|
112
|
+
/** 清空画像缓存(测试用;生产靠 `updatedAt` 与存活时长失效)。 */
|
|
113
|
+
export declare function resetDelegateProfileCache(): void;
|
|
114
|
+
/**
|
|
115
|
+
* 注入本轮最后一条用户消息的强制派发指令。
|
|
116
|
+
*
|
|
117
|
+
* 被点名的父代理自己也在名单里(`isSelf`):它要起一个独立副本去跑那份
|
|
118
|
+
* 子任务,而不是顺手自己做——否则 `requiredProfiles` 完成门会一直把回合
|
|
119
|
+
* 退回来。
|
|
120
|
+
*
|
|
121
|
+
* @param delegates 被点名的子代理(显示名 + 画像名 + 实际工具面 + 是否是自己)。
|
|
122
|
+
* @returns 派发指令正文。
|
|
123
|
+
*/
|
|
124
|
+
export declare function buildDelegateDispatchInstruction(delegates: ReadonlyArray<{
|
|
125
|
+
name: string;
|
|
126
|
+
profileName: string;
|
|
127
|
+
toolFilter?: string[];
|
|
128
|
+
isSelf?: boolean;
|
|
129
|
+
}>): string;
|
|
130
|
+
/**
|
|
131
|
+
* 可委派智能体名录:注入系统提示(realityCheck 段)。
|
|
132
|
+
*
|
|
133
|
+
* 技能正文与自定义提示里写的是**显示名**(`@Word智能体`),而 `subagent_type`
|
|
134
|
+
* 只认 ASCII 画像名(`word-master`)。没有这张对照表,技能作者只能硬编码画像
|
|
135
|
+
* 名、智能体一改名就断;模型也无从知道正文里的「@某智能体」该落成一次
|
|
136
|
+
* `delegate_subagent`,于是只在回复里打出这个名字然后收尾。
|
|
137
|
+
*
|
|
138
|
+
* 与 {@link buildDelegateDispatchInstruction} 的分工:那份是用户点名后的**强制**
|
|
139
|
+
* 派发指令(配 `requiredProfiles` 完成门兜底),这份只说明「能派谁、怎么派」。
|
|
140
|
+
*
|
|
141
|
+
* @param delegates 本轮可委派的智能体(提及 + 常驻名单)。
|
|
142
|
+
* @returns 名录正文;空名单返回空串,本轮系统提示与改前逐字节一致。
|
|
143
|
+
*/
|
|
144
|
+
export declare function buildDelegateRosterHint(delegates: ReadonlyArray<{
|
|
145
|
+
name: string;
|
|
146
|
+
profileName: string;
|
|
147
|
+
}>): string;
|
|
148
|
+
export interface TurnSubagentParam {
|
|
149
|
+
profiles: Record<string, BuiltinSubagentProfile>;
|
|
150
|
+
maxParallel?: number;
|
|
151
|
+
/** 本轮必须收到委派的画像(被 `@` 的智能体);sidecar 据此挂完成门。 */
|
|
152
|
+
requiredProfiles?: string[];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 内置画像 + 常驻可委派画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
|
|
156
|
+
*
|
|
157
|
+
* 只有**提及**画像进 `requiredProfiles`:强制派发此前只是提示词里的一句话,
|
|
158
|
+
* 模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。常驻画像刻意不进
|
|
159
|
+
* ——它们是「可以派」,不是「本轮必须派」。
|
|
160
|
+
*
|
|
161
|
+
* @param mentionProfiles 本轮提及转出的画像。
|
|
162
|
+
* @param ambientProfiles 常驻可委派画像;与提及画像同名时以提及为准。
|
|
163
|
+
* @returns 下发给 sidecar 的 `subagent` 参数。
|
|
164
|
+
*/
|
|
165
|
+
export declare function mergeTurnSubagentParam(mentionProfiles: Record<string, BuiltinSubagentProfile>, ambientProfiles?: 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,278 @@ 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
|
+
return await buildRoster(delegates, parentToolNames, new Set());
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 常驻可委派名单:会话里的其他智能体也转成画像,模型**无需用户 `@`** 就能
|
|
155
|
+
* 委派。技能正文或自定义提示里写「交给 X」因此能落成一次真实
|
|
156
|
+
* `delegate_subagent`——在此之前 `subagent_type` 的 enum 里只有内置三个画像,
|
|
157
|
+
* 这类指令 fail closed 报 `unknown subagent_type`,模型只能在正文里打出名字。
|
|
158
|
+
*
|
|
159
|
+
* 与提及名单的唯一区别是强制性:这里**不进** `requiredProfiles`。派不派由模型
|
|
160
|
+
* 按任务判断——技能的阶段条件(「阶段 C 才交给 Word 智能体」)在拼装画像时
|
|
161
|
+
* 还不可知,一律强制会把不该派的回合反复退回完成门。
|
|
162
|
+
*
|
|
163
|
+
* @param agents 会话可用的智能体(未归档)。
|
|
164
|
+
* @param parentToolNames 父本轮模型可见工具名。
|
|
165
|
+
* @param options.excludeAgentIds 不进名单的智能体:父代理自己与已在提及名单里的。
|
|
166
|
+
* @param options.reservedProfileNames 已占用的画像名(内置画像 + 提及画像),避免顶掉。
|
|
167
|
+
* @returns 画像花名册,按 `agents` 顺序。
|
|
168
|
+
*/
|
|
169
|
+
export async function buildAmbientDelegateRoster(agents, parentToolNames, options = {}) {
|
|
170
|
+
const excluded = new Set(options.excludeAgentIds ?? []);
|
|
171
|
+
const candidates = agents.filter((agent) => !excluded.has(agent.id));
|
|
172
|
+
if (candidates.length === 0)
|
|
173
|
+
return [];
|
|
174
|
+
return await buildRoster(candidates, parentToolNames, new Set(options.reservedProfileNames ?? []));
|
|
175
|
+
}
|
|
176
|
+
async function buildRoster(agents, parentToolNames, reserved) {
|
|
177
|
+
const roster = [];
|
|
178
|
+
const used = new Set(reserved);
|
|
179
|
+
for (const agent of agents) {
|
|
180
|
+
let profileName = profileNameForAgent(agent);
|
|
181
|
+
if (used.has(profileName)) {
|
|
182
|
+
const suffix = agent.id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8);
|
|
183
|
+
profileName = suffix ? `${profileName}-${suffix}` : `${profileName}-dup`;
|
|
184
|
+
}
|
|
185
|
+
used.add(profileName);
|
|
186
|
+
roster.push({
|
|
187
|
+
agentId: agent.id,
|
|
188
|
+
name: agent.name,
|
|
189
|
+
profileName,
|
|
190
|
+
profile: await buildOneDelegateProfile(agent, parentToolNames),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return roster;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 画像构建缓存的存活时长。
|
|
197
|
+
*
|
|
198
|
+
* 常驻名单让每轮的画像数量从「用户点了几个」变成「用户有几个智能体」,而
|
|
199
|
+
* 每份画像的 `systemPrompt` 都要过一次 `buildSystemPrompt`(内含技能目录
|
|
200
|
+
* 扫描)——不缓存就是每轮多做 N 次磁盘扫描,直接加在首个 token 之前。
|
|
201
|
+
*
|
|
202
|
+
* 缓存键带 `updatedAt`,所以改智能体立即生效;技能**正文**落盘不改
|
|
203
|
+
* `updatedAt`,靠这个存活时长兜底,最迟下一轮生效。
|
|
204
|
+
*/
|
|
205
|
+
const DELEGATE_PROFILE_TTL_MS = 15_000;
|
|
206
|
+
const delegateProfileCache = new Map();
|
|
207
|
+
/** 清空画像缓存(测试用;生产靠 `updatedAt` 与存活时长失效)。 */
|
|
208
|
+
export function resetDelegateProfileCache() {
|
|
209
|
+
delegateProfileCache.clear();
|
|
210
|
+
}
|
|
211
|
+
async function buildOneDelegateProfile(agent, parentToolNames) {
|
|
212
|
+
// 键要完整描述这份画像的输入:身份(改名/换 slug 都会换画像名与人设)、
|
|
213
|
+
// `updatedAt`(改配置即失效)、父工具面(`toolFilter` 与画像 systemPrompt
|
|
214
|
+
// 里的工具名录都由它派生)。只用 id 的话,任何复用 id 的调用方(内存假
|
|
215
|
+
// 存储的测试)都会静默拿到另一个智能体的画像。
|
|
216
|
+
const cacheKey = agent.updatedAt
|
|
217
|
+
? [
|
|
218
|
+
agent.id,
|
|
219
|
+
agent.updatedAt,
|
|
220
|
+
agent.slug ?? '',
|
|
221
|
+
agent.name,
|
|
222
|
+
parentToolNames.join(','),
|
|
223
|
+
].join('\u0000')
|
|
224
|
+
: null;
|
|
225
|
+
if (cacheKey) {
|
|
226
|
+
const hit = delegateProfileCache.get(cacheKey);
|
|
227
|
+
if (hit && Date.now() - hit.builtAtMs < DELEGATE_PROFILE_TTL_MS)
|
|
228
|
+
return hit.profile;
|
|
229
|
+
}
|
|
230
|
+
const profile = await buildProfileUncached(agent, parentToolNames);
|
|
231
|
+
if (cacheKey)
|
|
232
|
+
delegateProfileCache.set(cacheKey, { builtAtMs: Date.now(), profile });
|
|
233
|
+
return profile;
|
|
234
|
+
}
|
|
235
|
+
async function buildProfileUncached(agent, parentToolNames) {
|
|
236
|
+
const toolFilter = resolveChildToolFilter(agent.toolPolicy, parentToolNames);
|
|
237
|
+
const domain = toolFilter ?? parentToolNames;
|
|
238
|
+
const readOnly = isReadOnlyToolDomain(domain);
|
|
239
|
+
const capability = mergeAgentCapabilities([agent]);
|
|
240
|
+
const excludeSkillNames = capability.allowExternalSkills
|
|
241
|
+
? ['plan-mode']
|
|
242
|
+
: resolveSkillExcludes(capability, await loadSkills({ ignoreConditions: true }), ['plan-mode']);
|
|
243
|
+
const personaPreamble = agent.rolePrompt
|
|
244
|
+
? `【当前角色】${agent.name}\n${agent.rolePrompt}`
|
|
245
|
+
: '';
|
|
246
|
+
const built = await buildSystemPrompt({
|
|
247
|
+
personaPreamble,
|
|
248
|
+
identityName: agent.name,
|
|
249
|
+
pinnedSkillNames: capability.pinnedSkills,
|
|
250
|
+
ignoreConditions: capability.loadAllSkills,
|
|
251
|
+
excludeSkillNames,
|
|
252
|
+
toolNames: domain,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
...(toolFilter ? { toolFilter } : {}),
|
|
256
|
+
concurrent: readOnly,
|
|
257
|
+
description: profileDescription(agent),
|
|
258
|
+
systemPrompt: built.prompt,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 注入本轮最后一条用户消息的强制派发指令。
|
|
263
|
+
*
|
|
264
|
+
* 被点名的父代理自己也在名单里(`isSelf`):它要起一个独立副本去跑那份
|
|
265
|
+
* 子任务,而不是顺手自己做——否则 `requiredProfiles` 完成门会一直把回合
|
|
266
|
+
* 退回来。
|
|
267
|
+
*
|
|
268
|
+
* @param delegates 被点名的子代理(显示名 + 画像名 + 实际工具面 + 是否是自己)。
|
|
269
|
+
* @returns 派发指令正文。
|
|
270
|
+
*/
|
|
271
|
+
export function buildDelegateDispatchInstruction(delegates) {
|
|
272
|
+
const names = delegates.map((row) => row.name).join('、');
|
|
273
|
+
const roster = delegates
|
|
274
|
+
.map((row) => {
|
|
275
|
+
const tools = row.toolFilter?.length
|
|
276
|
+
? `本轮可用工具:${row.toolFilter.join('、')}`
|
|
277
|
+
: '本轮可用工具与你相同';
|
|
278
|
+
const self = row.isSelf ? '就是你自己的画像,起一个独立副本去跑;' : '';
|
|
279
|
+
return `- **${row.name}**(subagent_type=\`${row.profileName}\`):${self}${tools}`;
|
|
280
|
+
})
|
|
281
|
+
.join('\n');
|
|
282
|
+
const hasSelf = delegates.some((row) => row.isSelf);
|
|
283
|
+
return [
|
|
284
|
+
`用户点名了 **${names}**。本轮你必须把活交给它们,用 \`delegate_subagent\`(\`subagent_type\` 填画像名),每人至少一份子任务。`,
|
|
285
|
+
'',
|
|
286
|
+
roster,
|
|
287
|
+
'',
|
|
288
|
+
'- 子任务**彼此独立**时:一句话说明分工,紧接着在**同一轮**把全部调用发出,并把可并行的排在一起。',
|
|
289
|
+
'- 子任务**有依赖**时:按依赖顺序分轮派发,并把前一轮的结论写进后一轮的 `task`。',
|
|
290
|
+
'- 每份 `task` 必须自包含(目标、输入、交付形式、验收标准)——子代理看不到本对话。',
|
|
291
|
+
'- 禁止只写分工然后停手;也禁止漏掉任何一位被点名的代理。',
|
|
292
|
+
...(hasSelf
|
|
293
|
+
? [
|
|
294
|
+
'- 名单里包含你自己时同样要发 `delegate_subagent`:那是一个看不到本对话的独立副本,自己顺手做不算完成派发。',
|
|
295
|
+
]
|
|
296
|
+
: []),
|
|
297
|
+
'- 若某子代理注明了本轮可用工具,不要尝试让它调用未列出的工具。',
|
|
298
|
+
].join('\n');
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 可委派智能体名录:注入系统提示(realityCheck 段)。
|
|
302
|
+
*
|
|
303
|
+
* 技能正文与自定义提示里写的是**显示名**(`@Word智能体`),而 `subagent_type`
|
|
304
|
+
* 只认 ASCII 画像名(`word-master`)。没有这张对照表,技能作者只能硬编码画像
|
|
305
|
+
* 名、智能体一改名就断;模型也无从知道正文里的「@某智能体」该落成一次
|
|
306
|
+
* `delegate_subagent`,于是只在回复里打出这个名字然后收尾。
|
|
307
|
+
*
|
|
308
|
+
* 与 {@link buildDelegateDispatchInstruction} 的分工:那份是用户点名后的**强制**
|
|
309
|
+
* 派发指令(配 `requiredProfiles` 完成门兜底),这份只说明「能派谁、怎么派」。
|
|
310
|
+
*
|
|
311
|
+
* @param delegates 本轮可委派的智能体(提及 + 常驻名单)。
|
|
312
|
+
* @returns 名录正文;空名单返回空串,本轮系统提示与改前逐字节一致。
|
|
313
|
+
*/
|
|
314
|
+
export function buildDelegateRosterHint(delegates) {
|
|
315
|
+
if (delegates.length === 0)
|
|
316
|
+
return '';
|
|
317
|
+
const roster = delegates
|
|
318
|
+
.map((row) => `- ${row.name} → \`subagent_type="${row.profileName}"\``)
|
|
319
|
+
.join('\n');
|
|
320
|
+
return [
|
|
321
|
+
'',
|
|
322
|
+
'',
|
|
323
|
+
'## 可委派的智能体',
|
|
324
|
+
'',
|
|
325
|
+
'以下智能体可以通过 `delegate_subagent` 接活,画像名对照:',
|
|
326
|
+
'',
|
|
327
|
+
roster,
|
|
328
|
+
'',
|
|
329
|
+
'- 指令(含技能正文)里出现「@某智能体」或「交给某智能体」时,意思是**用 `delegate_subagent` 把这份活派给它**,`subagent_type` 填上表对应的画像名——不是在回复里打出这个名字。',
|
|
330
|
+
'- 每份 `task` 必须自包含(目标、输入、交付形式、验收标准):子代理看不到本对话。',
|
|
331
|
+
'- 表里没有的名字不要猜着填,`subagent_type` 只接受上表与内置画像。',
|
|
332
|
+
].join('\n');
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* 内置画像 + 常驻可委派画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
|
|
336
|
+
*
|
|
337
|
+
* 只有**提及**画像进 `requiredProfiles`:强制派发此前只是提示词里的一句话,
|
|
338
|
+
* 模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。常驻画像刻意不进
|
|
339
|
+
* ——它们是「可以派」,不是「本轮必须派」。
|
|
340
|
+
*
|
|
341
|
+
* @param mentionProfiles 本轮提及转出的画像。
|
|
342
|
+
* @param ambientProfiles 常驻可委派画像;与提及画像同名时以提及为准。
|
|
343
|
+
* @returns 下发给 sidecar 的 `subagent` 参数。
|
|
344
|
+
*/
|
|
345
|
+
export function mergeTurnSubagentParam(mentionProfiles, ambientProfiles = {}) {
|
|
346
|
+
const mentionNames = Object.keys(mentionProfiles);
|
|
347
|
+
return {
|
|
348
|
+
profiles: { ...BUILTIN_SUBAGENT_PROFILES, ...ambientProfiles, ...mentionProfiles },
|
|
349
|
+
...(mentionNames.length > 4 ? { maxParallel: mentionNames.length } : {}),
|
|
350
|
+
...(mentionNames.length > 0 ? { requiredProfiles: mentionNames } : {}),
|
|
351
|
+
};
|
|
352
|
+
}
|
package/dist/sidecar/types.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.6.30",
|
|
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.
|
|
53
|
-
"@steerable/pack-sdk": "0.6.
|
|
54
|
-
"@steerable/agent-protocol": "0.6.
|
|
52
|
+
"@steerable/agent-harness": "0.6.30",
|
|
53
|
+
"@steerable/pack-sdk": "0.6.30",
|
|
54
|
+
"@steerable/agent-protocol": "0.6.30"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/better-sqlite3": "^7.6.13",
|