@steerable/agent-shell 0.6.29 → 0.6.31
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/dist/host/runtime.js +7 -1
- package/dist/local-backend/router.d.ts +1 -0
- package/dist/local-backend/router.js +60 -17
- package/dist/local-backend/subagent-profiles.d.ts +54 -6
- package/dist/local-backend/subagent-profiles.js +112 -10
- package/dist/project-home.d.ts +23 -0
- package/dist/project-home.js +47 -0
- package/dist/project-registry.d.ts +12 -5
- package/dist/project-registry.js +24 -4
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.js +19 -0
- package/dist/sidecar/boot.d.ts +1 -1
- package/dist/sidecar/reverse-tools.d.ts +1 -1
- package/dist/sidecar/reverse-tools.js +1 -1
- package/package.json +4 -4
package/dist/host/runtime.js
CHANGED
|
@@ -207,7 +207,13 @@ export async function createHostRuntime(options) {
|
|
|
207
207
|
resolveProjectRoot: async (chatId) => (await localBackendRouter.resolveChatProject(chatId))?.folderPath ?? null,
|
|
208
208
|
// 项目模式下文件读写被围栏在项目目录内;会话附件目录是额外放行的
|
|
209
209
|
// 只读根,保证用户上传的文件即使在项目会话里也能被 agent 读回。
|
|
210
|
-
resolveAdditionalReadRoots: (chatId) =>
|
|
210
|
+
resolveAdditionalReadRoots: async (chatId) => {
|
|
211
|
+
const project = await localBackendRouter.resolveChatProject(chatId);
|
|
212
|
+
return [
|
|
213
|
+
getChatAttachmentsDir(chatId),
|
|
214
|
+
...(project?.sourceFolders ?? []),
|
|
215
|
+
];
|
|
216
|
+
},
|
|
211
217
|
approvalHandler: approvalBridge.handler,
|
|
212
218
|
askUserHandler: askUserBridge.handler,
|
|
213
219
|
// P2b: resume 时 sidecar 把记录里的读证据推给 LocalExecutor 的
|
|
@@ -8,7 +8,7 @@ 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 { buildDelegateDispatchInstruction, buildMentionDelegateRoster, mergeTurnSubagentParam, } from './subagent-profiles.js';
|
|
11
|
+
import { buildAmbientDelegateRoster, buildDelegateDispatchInstruction, buildDelegateRosterHint, buildMentionDelegateRoster, BUILTIN_SUBAGENT_PROFILES, mergeTurnSubagentParam, } from './subagent-profiles.js';
|
|
12
12
|
import { resolveMentionedAgentIds } from './mention-targets.js';
|
|
13
13
|
import { readSidecarHistoryEntries, timelineFromHistoryEntries, } from './task-process.js';
|
|
14
14
|
import { appendTimelineDelta, freezeTimelineReasoning, sealLastTimelineBlock, syncTimelineTools, } from './turn-timeline.js';
|
|
@@ -38,12 +38,18 @@ import { detectInterruptedTurn } from './interrupted-helper.js';
|
|
|
38
38
|
import { dropCurrentUserMessage } from './history-helper.js';
|
|
39
39
|
import { parseImageAttachments, processImageAttachments } from '../image-attachment.js';
|
|
40
40
|
import { loadProjectRuleFiles } from '../project-rules.js';
|
|
41
|
+
import { allocateProjectHome, ensureProjectHome } from '../project-home.js';
|
|
41
42
|
import { registerLiveStream, getLiveStream, removeLiveStream } from './live-stream.js';
|
|
42
43
|
import { beginPackTurnObservers, collectPackExecWritableRoots, collectPackForcedSkillVars, collectPackWorldState, } from './pack-turn-hooks.js';
|
|
43
44
|
import { matchPackBackendRoute } from './pack-backend-routes.js';
|
|
44
45
|
import { collectTurnFiles } from './turn-files.js';
|
|
45
46
|
import { resolveMentionedPaths } from './mentioned-paths.js';
|
|
46
47
|
import { getAuthProvider } from '../auth/index.js';
|
|
48
|
+
function parseSourceFolders(value) {
|
|
49
|
+
if (!Array.isArray(value))
|
|
50
|
+
return [];
|
|
51
|
+
return value.filter((item) => typeof item === 'string' && item.trim() !== '');
|
|
52
|
+
}
|
|
47
53
|
/**
|
|
48
54
|
* 把 CoreLoop/LLM 的底层错误翻译成适合直接显示在助手消息里的中文提示。
|
|
49
55
|
* 原始错误仍保存在 messageMetadata.completionReason 里,便于排查。
|
|
@@ -431,9 +437,17 @@ export class LocalBackendRouter {
|
|
|
431
437
|
if (method === 'POST') {
|
|
432
438
|
const payload = this.toRecord(request.body);
|
|
433
439
|
try {
|
|
440
|
+
const name = String(payload.name || '');
|
|
441
|
+
const sourceFolders = parseSourceFolders(payload.sourceFolders);
|
|
442
|
+
let folderPath = typeof payload.folderPath === 'string' ? payload.folderPath.trim() : '';
|
|
443
|
+
if (!folderPath) {
|
|
444
|
+
folderPath = allocateProjectHome(name);
|
|
445
|
+
ensureProjectHome(folderPath);
|
|
446
|
+
}
|
|
434
447
|
const project = registry.create({
|
|
435
|
-
name
|
|
436
|
-
folderPath
|
|
448
|
+
name,
|
|
449
|
+
folderPath,
|
|
450
|
+
sourceFolders,
|
|
437
451
|
});
|
|
438
452
|
return { status: 200, data: { success: true, project } };
|
|
439
453
|
}
|
|
@@ -458,6 +472,9 @@ export class LocalBackendRouter {
|
|
|
458
472
|
const project = registry.update(projectId, {
|
|
459
473
|
name: typeof payload.name === 'string' ? payload.name : undefined,
|
|
460
474
|
folderPath: typeof payload.folderPath === 'string' ? payload.folderPath : undefined,
|
|
475
|
+
sourceFolders: payload.sourceFolders === undefined
|
|
476
|
+
? undefined
|
|
477
|
+
: parseSourceFolders(payload.sourceFolders),
|
|
461
478
|
});
|
|
462
479
|
return { status: 200, data: { success: true, project } };
|
|
463
480
|
}
|
|
@@ -1989,7 +2006,8 @@ export class LocalBackendRouter {
|
|
|
1989
2006
|
let currentUserMessageId;
|
|
1990
2007
|
// 本轮被点名的智能体:菜单点选带来的 id 优先,手打的 `@名字` 从正文
|
|
1991
2008
|
// 解析补齐(两者缺一,提及就在后端消失,只剩前端徽章)。
|
|
1992
|
-
const
|
|
2009
|
+
const chatAgents = await this.store.listChatAgents();
|
|
2010
|
+
const mentionedAgentIds = resolveMentionedAgentIds(payload, userMessageText, chatAgents);
|
|
1993
2011
|
if (!regenerateMatch && !isResume) {
|
|
1994
2012
|
const userMeta = mentionedAgentIds.length > 0
|
|
1995
2013
|
? JSON.stringify({ mentionedAgentIds })
|
|
@@ -2047,8 +2065,22 @@ export class LocalBackendRouter {
|
|
|
2047
2065
|
available: turnTools.some((t) => t.name === token),
|
|
2048
2066
|
};
|
|
2049
2067
|
}
|
|
2050
|
-
const
|
|
2051
|
-
const
|
|
2068
|
+
const turnToolNames = turnTools.map((tool) => tool.name);
|
|
2069
|
+
const mentionRoster = await buildMentionDelegateRoster(turnAgents.delegates, turnToolNames);
|
|
2070
|
+
// 常驻可委派名单:其余智能体也进画像,技能/提示里的「交给 X」无需用户
|
|
2071
|
+
// `@` 就能落成一次真实委派。父代理自己不进——它就是本轮的执行者,给它
|
|
2072
|
+
// 一个自己的画像只会诱导无意义的自委派(`@自己` 走提及那条路,仍然可以)。
|
|
2073
|
+
const ambientRoster = await buildAmbientDelegateRoster(chatAgents, turnToolNames, {
|
|
2074
|
+
excludeAgentIds: [
|
|
2075
|
+
...mentionRoster.map((row) => row.agentId),
|
|
2076
|
+
...(turnAgents.parent ? [turnAgents.parent.id] : []),
|
|
2077
|
+
],
|
|
2078
|
+
reservedProfileNames: [
|
|
2079
|
+
...Object.keys(BUILTIN_SUBAGENT_PROFILES),
|
|
2080
|
+
...mentionRoster.map((row) => row.profileName),
|
|
2081
|
+
],
|
|
2082
|
+
});
|
|
2083
|
+
const { systemPrompt, messages, skillContext } = await this.buildConversationMessages(chatId, cleanUserMessageText, payload, turnTools, turnAgents, forcedSkillName, chatMode, forcedMcpTool, currentUserMessageId, { mention: mentionRoster, ambient: ambientRoster });
|
|
2052
2084
|
// A4: the sidecar-hosted CoreLoop is the only chat path (the TS loop
|
|
2053
2085
|
// was deleted 2026-08-26 after default-on + canary verification). Tools
|
|
2054
2086
|
// round-trip back to this process over the reverse channel. If the
|
|
@@ -2071,7 +2103,7 @@ export class LocalBackendRouter {
|
|
|
2071
2103
|
shouldGenerateTitle,
|
|
2072
2104
|
firstUserMessageForTitle,
|
|
2073
2105
|
resume: isResume,
|
|
2074
|
-
subagent: mergeTurnSubagentParam(Object.fromEntries(mentionRoster.map((row) => [row.profileName, row.profile]))),
|
|
2106
|
+
subagent: mergeTurnSubagentParam(Object.fromEntries(mentionRoster.map((row) => [row.profileName, row.profile])), Object.fromEntries(ambientRoster.map((row) => [row.profileName, row.profile]))),
|
|
2075
2107
|
parentAgentId: turnAgents.parent?.id ?? null,
|
|
2076
2108
|
});
|
|
2077
2109
|
}
|
|
@@ -2170,7 +2202,11 @@ export class LocalBackendRouter {
|
|
|
2170
2202
|
const project = this.toolRouter.projectRegistry?.get(projectId);
|
|
2171
2203
|
if (!project)
|
|
2172
2204
|
return null;
|
|
2173
|
-
return {
|
|
2205
|
+
return {
|
|
2206
|
+
name: project.name,
|
|
2207
|
+
folderPath: project.folderPath,
|
|
2208
|
+
sourceFolders: project.sourceFolders ?? [],
|
|
2209
|
+
};
|
|
2174
2210
|
}
|
|
2175
2211
|
/**
|
|
2176
2212
|
* 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
|
|
@@ -2206,7 +2242,7 @@ export class LocalBackendRouter {
|
|
|
2206
2242
|
capability: mergeAgentCapabilities(parent ? [parent] : []),
|
|
2207
2243
|
};
|
|
2208
2244
|
}
|
|
2209
|
-
async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId,
|
|
2245
|
+
async buildConversationMessages(chatId, latestUserMessage, payload, turnTools, turnAgents, forcedSkillName, chatMode = 'agent', forcedMcpTool, currentUserMessageId, delegates = { mention: [], ambient: [] }) {
|
|
2210
2246
|
// 跨轮压缩由框架 CoreLoop 拥有:token 压力触发 CompactionHooks(已接真实
|
|
2211
2247
|
// summarizer),压缩边界持久化到 durable record,W6-10 透视 reconcile 保证
|
|
2212
2248
|
// 压缩跨轮存活。桌面把全量原始历史作为种子发给框架——不再维护桌面侧滚动
|
|
@@ -2276,7 +2312,11 @@ export class LocalBackendRouter {
|
|
|
2276
2312
|
.join('\n\n');
|
|
2277
2313
|
const polluted = this.detectToolDenialInHistory(historyAssistantTexts);
|
|
2278
2314
|
const runtimeEnvironment = this.buildRuntimeEnvironmentContext(chatMode);
|
|
2279
|
-
|
|
2315
|
+
// 名录进 realityCheck:两条系统提示拼装路径(技能拼装 / 用户整段覆盖)
|
|
2316
|
+
// 都会带上它,且位置在末尾——不动技能正文那段 prompt cache 前缀。
|
|
2317
|
+
const realityCheck = runtimeEnvironment +
|
|
2318
|
+
this.buildToolRealityCheck(turnTools, polluted, chatMode) +
|
|
2319
|
+
buildDelegateRosterHint([...delegates.mention, ...delegates.ambient]);
|
|
2280
2320
|
// 用户显式覆盖(payload.systemPrompt 优先 / settings.systemPrompt 自定义了且非默认值次之)走
|
|
2281
2321
|
// "整段替换"路径,保持旧行为可被外部完全控制;否则交给 skill-based
|
|
2282
2322
|
// SystemPromptBuilder 根据本轮可用工具动态拼装。
|
|
@@ -2358,11 +2398,14 @@ export class LocalBackendRouter {
|
|
|
2358
2398
|
const chatProject = await this.resolveChatProject(chatId);
|
|
2359
2399
|
if (chatProject) {
|
|
2360
2400
|
systemPrompt +=
|
|
2361
|
-
`\n\n【项目模式】当前对话绑定项目「${chatProject.name}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2401
|
+
`\n\n【项目模式】当前对话绑定项目「${chatProject.name}」,家目录:${chatProject.folderPath}\n` +
|
|
2402
|
+
`你的文件写入(local_write_file)和命令执行(local_exec_shell)都被限制在该家目录内:` +
|
|
2403
|
+
`写入路径越界会被拒绝;命令默认在家目录下运行,显式指定的 cwd 越界也会被拒绝。` +
|
|
2404
|
+
(chatProject.sourceFolders.length > 0
|
|
2405
|
+
? `另有源文件夹(只读):${chatProject.sourceFolders.join('、')}。`
|
|
2406
|
+
: '') +
|
|
2407
|
+
`请一律使用项目目录内的路径(相对路径按家目录解析)。` +
|
|
2408
|
+
`如确需访问项目外的文件,向用户说明该限制,或请其在项目里附加为源文件夹。`;
|
|
2366
2409
|
// W6-5 + W6-7a:项目级规则文件(AGENTS.md / CLAUDE.md)是不可信输入,
|
|
2367
2410
|
// 仅在用户显式信任该项目后才注入模型上下文——未信任一律不读取、不注入
|
|
2368
2411
|
// (打开恶意仓库时,一段构造的规则文件不能劫持 agent)。信任状态持久化、
|
|
@@ -2407,8 +2450,8 @@ export class LocalBackendRouter {
|
|
|
2407
2450
|
if (finalUserImages.notes.length > 0) {
|
|
2408
2451
|
finalUserContent = `${finalUserContent}\n\n【附件图片】\n${finalUserImages.notes.join('\n')}`;
|
|
2409
2452
|
}
|
|
2410
|
-
if (
|
|
2411
|
-
finalUserContent = `${finalUserContent}\n\n${buildDelegateDispatchInstruction(
|
|
2453
|
+
if (delegates.mention.length > 0) {
|
|
2454
|
+
finalUserContent = `${finalUserContent}\n\n${buildDelegateDispatchInstruction(delegates.mention.map((row) => ({
|
|
2412
2455
|
name: row.name,
|
|
2413
2456
|
profileName: row.profileName,
|
|
2414
2457
|
toolFilter: row.profile.toolFilter,
|
|
@@ -29,8 +29,14 @@ export interface SubagentAgentInput extends AgentCapabilityInput {
|
|
|
29
29
|
name: string;
|
|
30
30
|
rolePrompt: string | null;
|
|
31
31
|
description?: string | null;
|
|
32
|
+
/** 画像缓存的失效依据:改配置即换键。缺省则该智能体的画像每轮重建。 */
|
|
33
|
+
updatedAt?: string;
|
|
32
34
|
}
|
|
33
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* 一个可委派对象的画像,连同显示名,供派发指令、名录说明与 sidecar 参数
|
|
37
|
+
* 共用。两个来源共享这个结构:用户 `@` 点名的(强制派发)与常驻可委派
|
|
38
|
+
* 名单里的(模型自主判断)。
|
|
39
|
+
*/
|
|
34
40
|
export interface MentionDelegateProfile {
|
|
35
41
|
/** 被提及智能体的 id——调用方据此认出自提及那一行。 */
|
|
36
42
|
agentId: string;
|
|
@@ -83,6 +89,28 @@ export declare function buildMentionSubagentProfiles(delegates: readonly Subagen
|
|
|
83
89
|
* @returns 提及顺序的画像花名册。
|
|
84
90
|
*/
|
|
85
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;
|
|
86
114
|
/**
|
|
87
115
|
* 注入本轮最后一条用户消息的强制派发指令。
|
|
88
116
|
*
|
|
@@ -99,6 +127,24 @@ export declare function buildDelegateDispatchInstruction(delegates: ReadonlyArra
|
|
|
99
127
|
toolFilter?: string[];
|
|
100
128
|
isSelf?: boolean;
|
|
101
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;
|
|
102
148
|
export interface TurnSubagentParam {
|
|
103
149
|
profiles: Record<string, BuiltinSubagentProfile>;
|
|
104
150
|
maxParallel?: number;
|
|
@@ -106,12 +152,14 @@ export interface TurnSubagentParam {
|
|
|
106
152
|
requiredProfiles?: string[];
|
|
107
153
|
}
|
|
108
154
|
/**
|
|
109
|
-
* 内置画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
|
|
155
|
+
* 内置画像 + 常驻可委派画像 + 本轮提及画像。被 `@` 超过 4 个时抬高池的并行上限。
|
|
110
156
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
157
|
+
* 只有**提及**画像进 `requiredProfiles`:强制派发此前只是提示词里的一句话,
|
|
158
|
+
* 模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。常驻画像刻意不进
|
|
159
|
+
* ——它们是「可以派」,不是「本轮必须派」。
|
|
113
160
|
*
|
|
114
|
-
* @param mentionProfiles
|
|
161
|
+
* @param mentionProfiles 本轮提及转出的画像。
|
|
162
|
+
* @param ambientProfiles 常驻可委派画像;与提及画像同名时以提及为准。
|
|
115
163
|
* @returns 下发给 sidecar 的 `subagent` 参数。
|
|
116
164
|
*/
|
|
117
|
-
export declare function mergeTurnSubagentParam(mentionProfiles: Record<string, BuiltinSubagentProfile>): TurnSubagentParam;
|
|
165
|
+
export declare function mergeTurnSubagentParam(mentionProfiles: Record<string, BuiltinSubagentProfile>, ambientProfiles?: Record<string, BuiltinSubagentProfile>): TurnSubagentParam;
|
|
@@ -148,9 +148,35 @@ export async function buildMentionSubagentProfiles(delegates, parentToolNames) {
|
|
|
148
148
|
* @returns 提及顺序的画像花名册。
|
|
149
149
|
*/
|
|
150
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) {
|
|
151
177
|
const roster = [];
|
|
152
|
-
const used = new Set();
|
|
153
|
-
for (const agent of
|
|
178
|
+
const used = new Set(reserved);
|
|
179
|
+
for (const agent of agents) {
|
|
154
180
|
let profileName = profileNameForAgent(agent);
|
|
155
181
|
if (used.has(profileName)) {
|
|
156
182
|
const suffix = agent.id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8);
|
|
@@ -161,12 +187,52 @@ export async function buildMentionDelegateRoster(delegates, parentToolNames) {
|
|
|
161
187
|
agentId: agent.id,
|
|
162
188
|
name: agent.name,
|
|
163
189
|
profileName,
|
|
164
|
-
profile: await
|
|
190
|
+
profile: await buildOneDelegateProfile(agent, parentToolNames),
|
|
165
191
|
});
|
|
166
192
|
}
|
|
167
193
|
return roster;
|
|
168
194
|
}
|
|
169
|
-
|
|
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) {
|
|
170
236
|
const toolFilter = resolveChildToolFilter(agent.toolPolicy, parentToolNames);
|
|
171
237
|
const domain = toolFilter ?? parentToolNames;
|
|
172
238
|
const readOnly = isReadOnlyToolDomain(domain);
|
|
@@ -232,18 +298,54 @@ export function buildDelegateDispatchInstruction(delegates) {
|
|
|
232
298
|
].join('\n');
|
|
233
299
|
}
|
|
234
300
|
/**
|
|
235
|
-
*
|
|
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 个时抬高池的并行上限。
|
|
236
336
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
337
|
+
* 只有**提及**画像进 `requiredProfiles`:强制派发此前只是提示词里的一句话,
|
|
338
|
+
* 模型跑了别的工具再叙述「已启动」就能蒙过所有既有纪律检查。常驻画像刻意不进
|
|
339
|
+
* ——它们是「可以派」,不是「本轮必须派」。
|
|
239
340
|
*
|
|
240
|
-
* @param mentionProfiles
|
|
341
|
+
* @param mentionProfiles 本轮提及转出的画像。
|
|
342
|
+
* @param ambientProfiles 常驻可委派画像;与提及画像同名时以提及为准。
|
|
241
343
|
* @returns 下发给 sidecar 的 `subagent` 参数。
|
|
242
344
|
*/
|
|
243
|
-
export function mergeTurnSubagentParam(mentionProfiles) {
|
|
345
|
+
export function mergeTurnSubagentParam(mentionProfiles, ambientProfiles = {}) {
|
|
244
346
|
const mentionNames = Object.keys(mentionProfiles);
|
|
245
347
|
return {
|
|
246
|
-
profiles: { ...BUILTIN_SUBAGENT_PROFILES, ...mentionProfiles },
|
|
348
|
+
profiles: { ...BUILTIN_SUBAGENT_PROFILES, ...ambientProfiles, ...mentionProfiles },
|
|
247
349
|
...(mentionNames.length > 4 ? { maxParallel: mentionNames.length } : {}),
|
|
248
350
|
...(mentionNames.length > 0 ? { requiredProfiles: mentionNames } : {}),
|
|
249
351
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目默认家目录:`Documents/<应用名>/<项目名>/`。
|
|
3
|
+
*
|
|
4
|
+
* 新建项目不再等于「选一个已有文件夹」——项目是带名字的容器,
|
|
5
|
+
* 家目录由本模块分配并创建。用户另加的源文件夹是附加只读根,
|
|
6
|
+
* 不替代这个家目录。
|
|
7
|
+
*/
|
|
8
|
+
export declare function sanitizeProjectDirName(name: string): string;
|
|
9
|
+
/** `Documents/<应用显示名>`:该应用下所有托管项目的父目录。 */
|
|
10
|
+
export declare function appProjectsRoot(options?: {
|
|
11
|
+
documentsDir?: string;
|
|
12
|
+
appFolderName?: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* 为项目名分配尚未占用的家目录路径(不落盘)。
|
|
16
|
+
* 已存在同名目录时追加 `-2`、`-3`…
|
|
17
|
+
*/
|
|
18
|
+
export declare function allocateProjectHome(projectName: string, options?: {
|
|
19
|
+
documentsDir?: string;
|
|
20
|
+
appFolderName?: string;
|
|
21
|
+
exists?: (folderPath: string) => boolean;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function ensureProjectHome(folderPath: string): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目默认家目录:`Documents/<应用名>/<项目名>/`。
|
|
3
|
+
*
|
|
4
|
+
* 新建项目不再等于「选一个已有文件夹」——项目是带名字的容器,
|
|
5
|
+
* 家目录由本模块分配并创建。用户另加的源文件夹是附加只读根,
|
|
6
|
+
* 不替代这个家目录。
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { getBrand } from './brand.js';
|
|
11
|
+
import { getDocumentsDir } from './runtime.js';
|
|
12
|
+
const UNSAFE_DIR_CHARS = /[\\/:*?"<>|]/g;
|
|
13
|
+
export function sanitizeProjectDirName(name) {
|
|
14
|
+
const trimmed = name
|
|
15
|
+
.trim()
|
|
16
|
+
.replace(UNSAFE_DIR_CHARS, '-')
|
|
17
|
+
.replace(/\s+/g, ' ')
|
|
18
|
+
.replace(/\.+$/g, '');
|
|
19
|
+
return trimmed || '未命名项目';
|
|
20
|
+
}
|
|
21
|
+
/** `Documents/<应用显示名>`:该应用下所有托管项目的父目录。 */
|
|
22
|
+
export function appProjectsRoot(options) {
|
|
23
|
+
const documents = options?.documentsDir ?? getDocumentsDir();
|
|
24
|
+
const appName = options?.appFolderName ?? getBrand().displayName;
|
|
25
|
+
return path.join(documents, sanitizeProjectDirName(appName));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 为项目名分配尚未占用的家目录路径(不落盘)。
|
|
29
|
+
* 已存在同名目录时追加 `-2`、`-3`…
|
|
30
|
+
*/
|
|
31
|
+
export function allocateProjectHome(projectName, options) {
|
|
32
|
+
const root = appProjectsRoot(options);
|
|
33
|
+
const base = sanitizeProjectDirName(projectName);
|
|
34
|
+
const exists = options?.exists ?? ((folderPath) => fs.existsSync(folderPath));
|
|
35
|
+
let candidate = path.join(root, base);
|
|
36
|
+
if (!exists(candidate))
|
|
37
|
+
return candidate;
|
|
38
|
+
for (let i = 2; i < 1000; i += 1) {
|
|
39
|
+
candidate = path.join(root, `${base}-${i}`);
|
|
40
|
+
if (!exists(candidate))
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
throw new Error('无法分配项目目录:重名过多');
|
|
44
|
+
}
|
|
45
|
+
export function ensureProjectHome(folderPath) {
|
|
46
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
47
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 项目注册表(项目模式)。
|
|
3
3
|
*
|
|
4
|
-
* 项目 = 名字 +
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
|
+
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
|
+
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
+
* 在家目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);源文件夹
|
|
8
|
+
* 只放宽 local_read_file。
|
|
8
9
|
*
|
|
9
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
@@ -14,8 +15,13 @@ export interface ProjectRecord {
|
|
|
14
15
|
id: string;
|
|
15
16
|
/** 用户可见名称(侧边栏分组标题)。 */
|
|
16
17
|
name: string;
|
|
17
|
-
/**
|
|
18
|
+
/** 托管家目录(绝对路径)。默认 `Documents/<应用名>/<项目名>/`。 */
|
|
18
19
|
folderPath: string;
|
|
20
|
+
/**
|
|
21
|
+
* 附加源文件夹(已有代码目录)。只放宽读取,不替代家目录,也不放宽写入。
|
|
22
|
+
* 旧记录没有此字段,读取时按空列表处理。
|
|
23
|
+
*/
|
|
24
|
+
sourceFolders?: string[];
|
|
19
25
|
/**
|
|
20
26
|
* W6-5 项目信任门控:项目目录里的 `AGENTS.md` / `CLAUDE.md` 等规则文件
|
|
21
27
|
* 是「项目作者写给 agent 的指令」——打开一个恶意仓库时,一段精心构造的
|
|
@@ -30,6 +36,7 @@ export interface ProjectRecord {
|
|
|
30
36
|
export interface CreateProjectInput {
|
|
31
37
|
name: string;
|
|
32
38
|
folderPath: string;
|
|
39
|
+
sourceFolders?: string[];
|
|
33
40
|
}
|
|
34
41
|
/** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
|
|
35
42
|
export interface ProjectKvStore {
|
package/dist/project-registry.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 项目注册表(项目模式)。
|
|
3
3
|
*
|
|
4
|
-
* 项目 = 名字 +
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
|
+
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
|
+
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
+
* 在家目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);源文件夹
|
|
8
|
+
* 只放宽 local_read_file。
|
|
8
9
|
*
|
|
9
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
@@ -34,11 +35,13 @@ export class ProjectRegistry {
|
|
|
34
35
|
throw new Error('项目文件夹不能为空');
|
|
35
36
|
if (this.get(name))
|
|
36
37
|
throw new Error(`已存在同名项目「${name}」`);
|
|
38
|
+
const sourceFolders = normalizeSourceFolders(input.sourceFolders, folderPath);
|
|
37
39
|
const now = new Date().toISOString();
|
|
38
40
|
const entry = {
|
|
39
41
|
id: randomUUID(),
|
|
40
42
|
name,
|
|
41
43
|
folderPath,
|
|
44
|
+
...(sourceFolders.length > 0 ? { sourceFolders } : {}),
|
|
42
45
|
createdAt: now,
|
|
43
46
|
updatedAt: now,
|
|
44
47
|
};
|
|
@@ -62,10 +65,14 @@ export class ProjectRegistry {
|
|
|
62
65
|
: current.folderPath;
|
|
63
66
|
if (!nextFolder)
|
|
64
67
|
throw new Error('项目文件夹不能为空');
|
|
68
|
+
const sourceFolders = updates.sourceFolders !== undefined
|
|
69
|
+
? normalizeSourceFolders(updates.sourceFolders, nextFolder)
|
|
70
|
+
: normalizeSourceFolders(current.sourceFolders, nextFolder);
|
|
65
71
|
const next = {
|
|
66
72
|
...current,
|
|
67
73
|
name: nextName,
|
|
68
74
|
folderPath: nextFolder,
|
|
75
|
+
sourceFolders: sourceFolders.length > 0 ? sourceFolders : undefined,
|
|
69
76
|
updatedAt: new Date().toISOString(),
|
|
70
77
|
};
|
|
71
78
|
projects[idx] = next;
|
|
@@ -104,3 +111,16 @@ export class ProjectRegistry {
|
|
|
104
111
|
return next;
|
|
105
112
|
}
|
|
106
113
|
}
|
|
114
|
+
function normalizeSourceFolders(folders, homePath) {
|
|
115
|
+
const home = homePath.trim();
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const raw of folders ?? []) {
|
|
119
|
+
const folder = raw.trim();
|
|
120
|
+
if (!folder || folder === home || seen.has(folder))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(folder);
|
|
123
|
+
out.push(folder);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
export declare function isElectronRuntime(): boolean;
|
|
3
3
|
/** 应用数据目录(SQLite、JSON store、用户技能目录的根)。 */
|
|
4
4
|
export declare function getUserDataDir(): string;
|
|
5
|
+
/**
|
|
6
|
+
* 用户文档目录。项目默认家目录建在这里的「应用名」文件夹下
|
|
7
|
+
* (见 project-home.ts)。测试可用 STEERABLE_DOCUMENTS_DIR 改锚点,
|
|
8
|
+
* 避免写进真实 Documents。
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDocumentsDir(): string;
|
|
5
11
|
/**
|
|
6
12
|
* 注入应用根目录(产品组装根调用)。重复注入不同值抛错(组装期笔误,
|
|
7
13
|
* fail fast——与 setProductBrand 同语义)。
|
package/dist/runtime.js
CHANGED
|
@@ -54,6 +54,25 @@ export function getUserDataDir() {
|
|
|
54
54
|
const dirName = getProductConfig().dataDirName ?? '.agent-shell';
|
|
55
55
|
return path.join(os.homedir(), dirName);
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* 用户文档目录。项目默认家目录建在这里的「应用名」文件夹下
|
|
59
|
+
* (见 project-home.ts)。测试可用 STEERABLE_DOCUMENTS_DIR 改锚点,
|
|
60
|
+
* 避免写进真实 Documents。
|
|
61
|
+
*/
|
|
62
|
+
export function getDocumentsDir() {
|
|
63
|
+
if (process.env.STEERABLE_DOCUMENTS_DIR)
|
|
64
|
+
return process.env.STEERABLE_DOCUMENTS_DIR;
|
|
65
|
+
const app = tryElectronApp();
|
|
66
|
+
if (app) {
|
|
67
|
+
try {
|
|
68
|
+
return app.getPath('documents');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 个别环境 getPath('documents') 不可用,回落到 ~/Documents。
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return path.join(os.homedir(), 'Documents');
|
|
75
|
+
}
|
|
57
76
|
/**
|
|
58
77
|
* 应用根目录(消费产品的仓库/打包根:含 products/manifest.json、assets、
|
|
59
78
|
* scripts 的那层)。3.2 起 shell 是被消费的框架包,本模块自己的位置
|
package/dist/sidecar/boot.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface HostSidecarDeps {
|
|
|
11
11
|
* 项目模式之外额外放行的只读根(会话附件目录等)。文件写入仍只受
|
|
12
12
|
* projectRoot 围栏约束;这些根只放宽 local_read_file 的读取范围。
|
|
13
13
|
*/
|
|
14
|
-
resolveAdditionalReadRoots?: (chatId: string) => string[]
|
|
14
|
+
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
15
15
|
/** W4-1 审批反向通道处理器(宿主审批弹窗的应答入口)。 */
|
|
16
16
|
approvalHandler: ReturnType<typeof createApprovalBridge>['handler'];
|
|
17
17
|
/**
|
|
@@ -24,6 +24,6 @@ export interface ReverseToolDeps {
|
|
|
24
24
|
* 额外放行的只读根(会话附件目录等)。写入仍只受 projectRoot 围栏约束,
|
|
25
25
|
* 这里只放宽 local_read_file 的读取范围。
|
|
26
26
|
*/
|
|
27
|
-
resolveAdditionalReadRoots?: (chatId: string) => string[]
|
|
27
|
+
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
28
28
|
}
|
|
29
29
|
export declare function createToolInvokeHandler(deps: ReverseToolDeps): SidecarReverseHandler;
|
|
@@ -56,7 +56,7 @@ export function createToolInvokeHandler(deps) {
|
|
|
56
56
|
? (await deps.resolveProjectRoot?.(p.context.chatId) ?? null)
|
|
57
57
|
: null;
|
|
58
58
|
const additionalReadRoots = p.context?.chatId
|
|
59
|
-
? (deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
59
|
+
? await Promise.resolve(deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
60
60
|
: [];
|
|
61
61
|
if (name === 'local_exec_shell') {
|
|
62
62
|
const classification = classifyShellCommand(String(toolArgs.command ?? ''));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.31",
|
|
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.31",
|
|
53
|
+
"@steerable/pack-sdk": "0.6.31",
|
|
54
|
+
"@steerable/agent-protocol": "0.6.31"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/better-sqlite3": "^7.6.13",
|