@armadra/agent 0.7.5 → 0.8.0
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/CHANGELOG.md +34 -0
- package/CHANGELOG.zh-CN.md +34 -0
- package/dist/agent/session-classifier.d.ts +2 -0
- package/dist/agent/session-classifier.js +24 -3
- package/dist/agent/session-extensions.d.ts +1 -0
- package/dist/agent/session-extensions.js +3 -0
- package/dist/agent/session-subagent.js +9 -2
- package/dist/agent/subagent-background.d.ts +5 -1
- package/dist/agent/subagent-background.js +13 -1
- package/dist/agent/subagent-fork.d.ts +10 -5
- package/dist/agent/subagent-fork.js +22 -6
- package/dist/agent/subagent-registry.d.ts +1 -1
- package/dist/agent/subagent-registry.js +1 -1
- package/dist/agents/catalog.d.ts +1 -0
- package/dist/ai/apis/chatgpt-backend.d.ts +3 -1
- package/dist/ai/apis/chatgpt-backend.js +10 -1
- package/dist/ai/apis/max-tokens.d.ts +4 -0
- package/dist/ai/apis/max-tokens.js +12 -1
- package/dist/ai/providers/enrich.d.ts +6 -2
- package/dist/ai/providers/enrich.js +13 -2
- package/dist/ai/providers/max-tokens-cache.d.ts +22 -0
- package/dist/ai/providers/max-tokens-cache.js +95 -0
- package/dist/ai/providers/registry.js +3 -3
- package/dist/bundle/ama.cjs +1250 -375
- package/dist/checkpoints/shadow-git.d.ts +5 -1
- package/dist/checkpoints/shadow-git.js +11 -2
- package/dist/cli/compose-providers.d.ts +1 -0
- package/dist/cli/compose-providers.js +3 -0
- package/dist/cli/compose-store.d.ts +6 -2
- package/dist/cli/compose-store.js +8 -4
- package/dist/cli/compose.js +2 -2
- package/dist/config/json-schema.js +1 -0
- package/dist/config/key-docs.js +1 -0
- package/dist/config/schema-w5.js +2 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types-w5.d.ts +5 -0
- package/dist/drivers/acp/driver.d.ts +22 -1
- package/dist/drivers/acp/driver.js +59 -5
- package/dist/drivers/agents.js +3 -0
- package/dist/drivers/catalog.d.ts +2 -0
- package/dist/drivers/catalog.js +94 -4
- package/dist/drivers/native/claude-normalize.d.ts +8 -0
- package/dist/drivers/native/claude-normalize.js +27 -0
- package/dist/drivers/native/claude-stream.js +16 -1
- package/dist/drivers/native/codex-app-server.js +4 -2
- package/dist/drivers/native/oneshot.js +12 -2
- package/dist/drivers/native/pi-gate.d.ts +19 -0
- package/dist/drivers/native/pi-gate.js +39 -0
- package/dist/drivers/native/pi-rpc.d.ts +29 -0
- package/dist/drivers/native/pi-rpc.js +483 -0
- package/dist/drivers/runner.js +1 -0
- package/dist/drivers/types.d.ts +1 -1
- package/dist/i18n/catalog.d.ts +5 -0
- package/dist/i18n/messages/auth.d.ts +2 -0
- package/dist/i18n/messages/auth.js +2 -0
- package/dist/i18n/messages/config-keys.d.ts +2 -0
- package/dist/i18n/messages/config-keys.js +2 -0
- package/dist/i18n/messages/config.d.ts +2 -0
- package/dist/i18n/messages/drivers.d.ts +4 -0
- package/dist/i18n/messages/drivers.js +4 -0
- package/dist/i18n/messages/interactive.d.ts +2 -0
- package/dist/i18n/messages/interactive.js +2 -0
- package/dist/i18n/messages/session.js +2 -2
- package/dist/modes/interactive/agent-ui.js +7 -0
- package/dist/session/manager.d.ts +3 -1
- package/dist/session/manager.js +5 -0
- package/dist/session/offload.d.ts +10 -2
- package/dist/session/offload.js +27 -4
- package/dist/tools/background-jobs.d.ts +4 -4
- package/dist/tools/background-jobs.js +4 -4
- package/dist/tools/bash.d.ts +2 -1
- package/dist/tools/bash.js +4 -3
- package/dist/tools/task-ctl.js +2 -1
- package/dist/tools/task.d.ts +9 -0
- package/dist/tools/task.js +27 -1
- package/docs/agents.md +70 -14
- package/docs/en/permissions.md +1 -1
- package/docs/en/providers.md +11 -10
- package/docs/en/sessions.md +2 -1
- package/docs/permissions.md +1 -1
- package/docs/providers.md +16 -11
- package/docs/session-format.md +2 -1
- package/docs/sessions.md +2 -1
- package/package.json +1 -1
|
@@ -52,6 +52,7 @@ export declare const en: {
|
|
|
52
52
|
};
|
|
53
53
|
request: {
|
|
54
54
|
notEligible: string;
|
|
55
|
+
modelUnavailable: (model: string) => string;
|
|
55
56
|
flavorMismatch: (current: string, ref: string, pinned: string) => string;
|
|
56
57
|
};
|
|
57
58
|
errors: {
|
|
@@ -150,6 +151,7 @@ export declare const zh: {
|
|
|
150
151
|
};
|
|
151
152
|
request: {
|
|
152
153
|
notEligible: string;
|
|
154
|
+
modelUnavailable: (model: string) => string;
|
|
153
155
|
flavorMismatch: (current: string, ref: string, pinned: string) => string;
|
|
154
156
|
};
|
|
155
157
|
errors: {
|
|
@@ -57,6 +57,7 @@ export const en = {
|
|
|
57
57
|
" 2. Workspace account: Team / Enterprise / Edu workspaces may not offer it\n" +
|
|
58
58
|
" 3. Region restriction, or not yet rolled out during the preview (most likely when a Pro account still gets this error)\n" +
|
|
59
59
|
"You can sign in the other way instead: ama auth login chatgpt --flavor codex",
|
|
60
|
+
modelUnavailable: (model) => `${model} is not available to this ChatGPT account (400 from the codex backend). The backend's model list changes over time: run ama models discover chatgpt to refresh it, then pick a listed model with /model or --model`,
|
|
60
61
|
flavorMismatch: (current, ref, pinned) => `signed in with ${current}, but ${ref} pins the @${pinned} channel. Drop @${pinned} so the channel follows the sign-in, or run ama auth login chatgpt --flavor ${pinned}`,
|
|
61
62
|
},
|
|
62
63
|
errors: {
|
|
@@ -166,6 +167,7 @@ export const zh = {
|
|
|
166
167
|
" 2. 工作空间账户:Team / Enterprise / Edu 工作空间可能未开放\n" +
|
|
167
168
|
" 3. 地区受限,或预览期尚未开放(Pro 账户仍报此错时最可能是这一条)\n" +
|
|
168
169
|
"可以改用另一种登录方式:ama auth login chatgpt --flavor codex",
|
|
170
|
+
modelUnavailable: (model) => `${model} 在这个 ChatGPT 账户下不可用(codex 后端回 400)。后端的模型表会变化:运行 ama models discover chatgpt 刷新,再用 /model 或 --model 选表里的模型`,
|
|
169
171
|
flavorMismatch: (current, ref, pinned) => `当前以 ${current} 方式登录,但 ${ref} 显式指定了 @${pinned} 渠道。去掉 @${pinned} 让渠道跟随登录方式,或运行 ama auth login chatgpt --flavor ${pinned}`,
|
|
170
172
|
},
|
|
171
173
|
errors: {
|
|
@@ -115,6 +115,7 @@ export declare const en: {
|
|
|
115
115
|
"subagents.maxPending": string;
|
|
116
116
|
"subagents.retainSessions": string;
|
|
117
117
|
"subagents.defaultModel": string;
|
|
118
|
+
"subagents.forkMaxContextRatio": string;
|
|
118
119
|
"subagents.background": string;
|
|
119
120
|
"subagents.autoBackgroundAfterMs": string;
|
|
120
121
|
models: string;
|
|
@@ -269,6 +270,7 @@ export declare const zh: {
|
|
|
269
270
|
"subagents.maxPending": string;
|
|
270
271
|
"subagents.retainSessions": string;
|
|
271
272
|
"subagents.defaultModel": string;
|
|
273
|
+
"subagents.forkMaxContextRatio": string;
|
|
272
274
|
"subagents.background": string;
|
|
273
275
|
"subagents.autoBackgroundAfterMs": string;
|
|
274
276
|
models: string;
|
|
@@ -164,6 +164,7 @@ const keysEn = {
|
|
|
164
164
|
"subagents.maxPending": "Queue limit; task fails beyond it",
|
|
165
165
|
"subagents.retainSessions": "Finished sub-sessions kept in memory (least recently used first out); others reopen from their session file when continued",
|
|
166
166
|
"subagents.defaultModel": "Default sub-session model provider/model[@channel]",
|
|
167
|
+
"subagents.forkMaxContextRatio": "Fork sub-agents fall back to fresh when the parent's last request used more than this share of (window − compaction.reserveTokens); 0.05–0.95, default 0.5",
|
|
167
168
|
"subagents.background": "Whether task runs in the background by default: auto = background in the TUI / RPC / ACP and foreground with -p; always / never are fixed. The call argument and the agent type's background: override it; user, project and host level",
|
|
168
169
|
"subagents.autoBackgroundAfterMs": "Move a foreground task to the background after it has run this many milliseconds; 0 disables; user and project level",
|
|
169
170
|
models: "Model aliases and the model picker list; user level only (effective from wave 5 W5-G)",
|
|
@@ -292,6 +293,7 @@ const keysZh = {
|
|
|
292
293
|
"subagents.maxPending": "排队上限,超出时 task 报错",
|
|
293
294
|
"subagents.retainSessions": "内存中保留的已结束子会话数(最久未用的先释放),其余续聊时从会话文件重开",
|
|
294
295
|
"subagents.defaultModel": "子会话缺省模型 provider/model[@channel]",
|
|
296
|
+
"subagents.forkMaxContextRatio": "fork 子 Agent 的回落比例:父上一次请求超过(窗口 − compaction.reserveTokens)的这一比例时改为 fresh;0.05–0.95,缺省 0.5",
|
|
295
297
|
"subagents.background": "task 缺省是否后台:auto = TUI / RPC / ACP 下后台、-p 下前台;always / never 固定。调用参数与类型定义的 background: 优先;用户、项目、宿主级都认",
|
|
296
298
|
"subagents.autoBackgroundAfterMs": "前台任务运行超过该毫秒数自动转后台;0 关闭;用户、项目级都认",
|
|
297
299
|
models: "模型别名与模型选择器清单;只认用户级(第五波 W5-G 起生效)",
|
|
@@ -115,6 +115,7 @@ export declare const en: {
|
|
|
115
115
|
"subagents.maxPending": string;
|
|
116
116
|
"subagents.retainSessions": string;
|
|
117
117
|
"subagents.defaultModel": string;
|
|
118
|
+
"subagents.forkMaxContextRatio": string;
|
|
118
119
|
"subagents.background": string;
|
|
119
120
|
"subagents.autoBackgroundAfterMs": string;
|
|
120
121
|
models: string;
|
|
@@ -383,6 +384,7 @@ export declare const zh: {
|
|
|
383
384
|
"subagents.maxPending": string;
|
|
384
385
|
"subagents.retainSessions": string;
|
|
385
386
|
"subagents.defaultModel": string;
|
|
387
|
+
"subagents.forkMaxContextRatio": string;
|
|
386
388
|
"subagents.background": string;
|
|
387
389
|
"subagents.autoBackgroundAfterMs": string;
|
|
388
390
|
models: string;
|
|
@@ -18,6 +18,7 @@ export declare const en: {
|
|
|
18
18
|
resumeUnsupported: (agent: string) => string;
|
|
19
19
|
resumeNotFound: (agent: string, id: string) => string;
|
|
20
20
|
noMatchingMode: (agent: string, mode: string) => string;
|
|
21
|
+
noMatchingModel: (agent: string, model: string) => string;
|
|
21
22
|
noImages: (agent: string) => string;
|
|
22
23
|
noImagesDriver: (agent: string) => string;
|
|
23
24
|
noImagesOneshot: (agent: string) => string;
|
|
@@ -28,6 +29,7 @@ export declare const en: {
|
|
|
28
29
|
error: (agent: string, message: string, retrying: boolean) => string;
|
|
29
30
|
askedQuestion: (agent: string, tool: string) => string;
|
|
30
31
|
askedQuestionCodex: (agent: string) => string;
|
|
32
|
+
extensionDialog: (agent: string, title: string) => string;
|
|
31
33
|
mcpElicitation: (agent: string) => string;
|
|
32
34
|
};
|
|
33
35
|
/** Hook 配置与输出的警告(给人看;回给模型的阻止理由固定英文)。 */
|
|
@@ -76,6 +78,7 @@ export declare const zh: {
|
|
|
76
78
|
resumeUnsupported: (agent: string) => string;
|
|
77
79
|
resumeNotFound: (agent: string, id: string) => string;
|
|
78
80
|
noMatchingMode: (agent: string, mode: string) => string;
|
|
81
|
+
noMatchingModel: (agent: string, model: string) => string;
|
|
79
82
|
noImages: (agent: string) => string;
|
|
80
83
|
noImagesDriver: (agent: string) => string;
|
|
81
84
|
noImagesOneshot: (agent: string) => string;
|
|
@@ -86,6 +89,7 @@ export declare const zh: {
|
|
|
86
89
|
error: (agent: string, message: string, retrying: boolean) => string;
|
|
87
90
|
askedQuestion: (agent: string, tool: string) => string;
|
|
88
91
|
askedQuestionCodex: (agent: string) => string;
|
|
92
|
+
extensionDialog: (agent: string, title: string) => string;
|
|
89
93
|
mcpElicitation: (agent: string) => string;
|
|
90
94
|
};
|
|
91
95
|
hooks: {
|
|
@@ -19,6 +19,7 @@ export const en = {
|
|
|
19
19
|
resumeUnsupported: (agent) => `${agent} cannot resume sessions; started a new one`,
|
|
20
20
|
resumeNotFound: (agent, id) => `${agent} cannot find session ${id}; started a new one`,
|
|
21
21
|
noMatchingMode: (agent, mode) => `${agent} has no "${mode}" mode; running in its own default mode (actions that need approval still come to you)`,
|
|
22
|
+
noMatchingModel: (agent, model) => `${agent} does not offer model "${model}"; using its own default model`,
|
|
22
23
|
noImages: (agent) => `${agent} does not accept images; omitted`,
|
|
23
24
|
noImagesDriver: (agent) => `the ${agent} driver does not pass images yet; omitted`,
|
|
24
25
|
noImagesOneshot: (agent) => `${agent} one-shot mode does not pass images; omitted`,
|
|
@@ -29,6 +30,7 @@ export const en = {
|
|
|
29
30
|
error: (agent, message, retrying) => `${agent}: ${message}${retrying ? " (retrying)" : ""}`,
|
|
30
31
|
askedQuestion: (agent, tool) => `${agent} wants to ask you a question (${tool}); ama does not answer for you and asked it to put the question in its final reply`,
|
|
31
32
|
askedQuestionCodex: (agent) => `${agent} wants to ask you a question; ama does not answer for you — have it put the question in its final reply`,
|
|
33
|
+
extensionDialog: (agent, title) => `${agent}: an extension asked for input (${title}); ama does not answer for you, cancelled`,
|
|
32
34
|
mcpElicitation: (agent) => `${agent}'s MCP server asked for input; ama does not answer for you, cancelled`,
|
|
33
35
|
},
|
|
34
36
|
/** Hook 配置与输出的警告(给人看;回给模型的阻止理由固定英文)。 */
|
|
@@ -77,6 +79,7 @@ export const zh = {
|
|
|
77
79
|
resumeUnsupported: (agent) => `${agent} 不支持续接会话,已新开`,
|
|
78
80
|
resumeNotFound: (agent, id) => `${agent} 找不到会话 ${id},已新开`,
|
|
79
81
|
noMatchingMode: (agent, mode) => `${agent} 没有「${mode}」模式,按它自己的缺省模式运行(需要授权的操作仍交给你)`,
|
|
82
|
+
noMatchingModel: (agent, model) => `${agent} 没有模型「${model}」,按它自己的缺省模型运行`,
|
|
80
83
|
noImages: (agent) => `${agent} 不接受图片,已省略`,
|
|
81
84
|
noImagesDriver: (agent) => `${agent} 驱动暂不传图片,已省略`,
|
|
82
85
|
noImagesOneshot: (agent) => `${agent} 一次性模式不传图片,已省略`,
|
|
@@ -87,6 +90,7 @@ export const zh = {
|
|
|
87
90
|
error: (agent, message, retrying) => `${agent}:${message}${retrying ? "(重试中)" : ""}`,
|
|
88
91
|
askedQuestion: (agent, tool) => `${agent} 想向你提问(${tool});ama 不代答,已请它把问题写进最终回复`,
|
|
89
92
|
askedQuestionCodex: (agent) => `${agent} 想向你提问;ama 不代答,请让它把问题写进最终回复`,
|
|
93
|
+
extensionDialog: (agent, title) => `${agent}:扩展请求输入(${title});ama 不代答,已取消`,
|
|
90
94
|
mcpElicitation: (agent) => `${agent} 的 MCP 服务器请求输入;ama 不代答,已取消`,
|
|
91
95
|
},
|
|
92
96
|
hooks: {
|
|
@@ -85,6 +85,7 @@ export declare const en: {
|
|
|
85
85
|
backgroundStopped: (jobId: string, command: string) => string;
|
|
86
86
|
compactionFailed: (error: string) => string;
|
|
87
87
|
compactionCancelled: string;
|
|
88
|
+
imageReadBackFailed: (id: string) => string;
|
|
88
89
|
};
|
|
89
90
|
statusLine: {
|
|
90
91
|
usage: string;
|
|
@@ -335,6 +336,7 @@ export declare const zh: {
|
|
|
335
336
|
backgroundStopped: (jobId: string, command: string) => string;
|
|
336
337
|
compactionFailed: (error: string) => string;
|
|
337
338
|
compactionCancelled: string;
|
|
339
|
+
imageReadBackFailed: (id: string) => string;
|
|
338
340
|
};
|
|
339
341
|
statusLine: {
|
|
340
342
|
usage: string;
|
|
@@ -99,6 +99,7 @@ export const en = {
|
|
|
99
99
|
backgroundStopped: (jobId, command) => `Background command ${jobId} stopped: ${command}`,
|
|
100
100
|
compactionFailed: (error) => `Compaction failed: ${error}`,
|
|
101
101
|
compactionCancelled: "Compaction cancelled",
|
|
102
|
+
imageReadBackFailed: (id) => `Session image for entry ${id} could not be read back (file changed); it stays empty`,
|
|
102
103
|
},
|
|
103
104
|
statusLine: {
|
|
104
105
|
usage: "Usage: /statusline [full|compact]",
|
|
@@ -234,6 +235,7 @@ export const zh = {
|
|
|
234
235
|
backgroundStopped: (jobId, command) => `后台命令 ${jobId} 已停止:${command}`,
|
|
235
236
|
compactionFailed: (error) => `压缩失败:${error}`,
|
|
236
237
|
compactionCancelled: "压缩已取消",
|
|
238
|
+
imageReadBackFailed: (id) => `会话文件已改变,条目 ${id} 的图片读不回,保留为空`,
|
|
237
239
|
},
|
|
238
240
|
statusLine: {
|
|
239
241
|
usage: "用法:/statusline [full|compact]",
|
|
@@ -88,7 +88,7 @@ export const en = {
|
|
|
88
88
|
cwdIsHome: "the working directory is the home directory",
|
|
89
89
|
gitNotFound: "git not found",
|
|
90
90
|
tooManyFiles: (files, max) => `the working directory has ${plural(files, "file")} (limit ${max})`,
|
|
91
|
-
tooSlow: (seconds, max) => `
|
|
91
|
+
tooSlow: (seconds, max) => `snapshots took longer than ${max} s twice in a row (latest ${seconds} s)`,
|
|
92
92
|
gitFailed: (command, code, detail) => `git ${command} failed (${code})${detail === "" ? "" : `: ${detail}`}`,
|
|
93
93
|
noShadowCommit: "the checkpoint has no shadow commit",
|
|
94
94
|
shadowCommitMissing: (commit) => `shadow commit ${commit} does not exist`,
|
|
@@ -211,7 +211,7 @@ export const zh = {
|
|
|
211
211
|
cwdIsHome: "工作目录是家目录",
|
|
212
212
|
gitNotFound: "找不到 git",
|
|
213
213
|
tooManyFiles: (files, max) => `工作目录有 ${files} 个文件(上限 ${max})`,
|
|
214
|
-
tooSlow: (seconds, max) =>
|
|
214
|
+
tooSlow: (seconds, max) => `连续两次快照超过 ${max} 秒(最近一次 ${seconds} 秒)`,
|
|
215
215
|
gitFailed: (command, code, detail) => `git ${command} 失败(${code})${detail === "" ? "" : `:${detail}`}`,
|
|
216
216
|
noShadowCommit: "检查点没有影子提交",
|
|
217
217
|
shadowCommitMissing: (commit) => `影子提交 ${commit} 不存在`,
|
|
@@ -45,6 +45,8 @@ export function agentCommandHooks(get) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
const TASK_NOTICE = /^\[task (t\d+)\] ([\s\S]*)$/;
|
|
48
|
+
/** #183:会话图片读不回(`ImageOffload` 的告警)转通知区。 */
|
|
49
|
+
const SESSION_NOTICE = /^cannot read session entry (\S+) back from /;
|
|
48
50
|
export class AgentUi {
|
|
49
51
|
deps;
|
|
50
52
|
plan;
|
|
@@ -164,6 +166,11 @@ export class AgentUi {
|
|
|
164
166
|
this.deps.notice(level === "warn" ? "warn" : "info", `[${agent} · ${taskId}] ${match[2]}`);
|
|
165
167
|
return;
|
|
166
168
|
}
|
|
169
|
+
const image = level === "warn" ? SESSION_NOTICE.exec(message) : null;
|
|
170
|
+
if (image !== null) {
|
|
171
|
+
this.deps.notice("warn", msg().interactive.events.imageReadBackFailed(image[1]));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
167
174
|
original?.call(holder, level, message);
|
|
168
175
|
};
|
|
169
176
|
holder.log = routed;
|
|
@@ -37,7 +37,7 @@ export declare class SessionManager implements SessionManagerApi {
|
|
|
37
37
|
private closed;
|
|
38
38
|
private readonly now;
|
|
39
39
|
private readonly images;
|
|
40
|
-
private
|
|
40
|
+
private warn;
|
|
41
41
|
/** 已知的会话文件字节数(新行的行首偏移)。 */
|
|
42
42
|
private fileBytes;
|
|
43
43
|
private constructor();
|
|
@@ -65,6 +65,8 @@ export declare class SessionManager implements SessionManagerApi {
|
|
|
65
65
|
private syncImages;
|
|
66
66
|
/** 原文条目(已卸载的从文件回读一份副本)。 */
|
|
67
67
|
private original;
|
|
68
|
+
/** 接上(或换掉)告警出口(图片读不回等);`open()` 期间缓冲的告警随即按序冲出(#183)。 */
|
|
69
|
+
setWarn(warn: (message: string) => void): void;
|
|
68
70
|
/** 已卸载图片的条目数(测试与诊断用)。 */
|
|
69
71
|
offloadedCount(): number;
|
|
70
72
|
private leafLine;
|
package/dist/session/manager.js
CHANGED
|
@@ -182,6 +182,11 @@ export class SessionManager {
|
|
|
182
182
|
return entry;
|
|
183
183
|
return this.images.original(this.storage.file, entry);
|
|
184
184
|
}
|
|
185
|
+
/** 接上(或换掉)告警出口(图片读不回等);`open()` 期间缓冲的告警随即按序冲出(#183)。 */
|
|
186
|
+
setWarn(warn) {
|
|
187
|
+
this.warn = warn;
|
|
188
|
+
this.images.setWarn(warn);
|
|
189
|
+
}
|
|
185
190
|
/** 已卸载图片的条目数(测试与诊断用)。 */
|
|
186
191
|
offloadedCount() {
|
|
187
192
|
return this.images.size;
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* `content` 换新数组,image 块换成 `data: ""` 的占位;
|
|
11
11
|
* - 回读:按行位置 `readSync` 一行、`JSON.parse`,校验 id 与内容块数,图片经 `internImage` 驻留后
|
|
12
12
|
* **就地**放回占位所在的数组(之前拿到同一消息对象的调用方随之看到原图);读不回(文件被外部改写)时
|
|
13
|
-
* 保留 `""`
|
|
13
|
+
* 保留 `""` 并告警,不再重试;告警通道可后接(`setWarn`):接上之前的告警先缓冲(最多
|
|
14
|
+
* `PENDING_WARNINGS` 条,其余折叠成一条计数),接上时按序冲出(#183);
|
|
14
15
|
* - `original(entry)`:给 `getEntries` / `fork` 的原文副本,不改内存状态。
|
|
15
16
|
*/
|
|
16
17
|
import type { SessionEntry } from "./types.js";
|
|
@@ -25,12 +26,18 @@ export declare function hasImages(entry: SessionEntry): boolean;
|
|
|
25
26
|
export declare function readLineAt(file: string, locator: LineLocator): unknown;
|
|
26
27
|
/** 文件里全部 `context_edit` 的目标 id(只解析 context_edit 行;读不了时为空集)。 */
|
|
27
28
|
export declare function editTargets(file: string): Set<string>;
|
|
29
|
+
/** 告警通道接上之前最多缓冲的条数;超出的只计数,冲出时折叠成一条。 */
|
|
30
|
+
export declare const PENDING_WARNINGS = 16;
|
|
28
31
|
export declare class ImageOffload {
|
|
29
|
-
private readonly warn;
|
|
30
32
|
/** 含图 message 条目的行位置(只有已落盘的条目才有)。 */
|
|
31
33
|
private readonly locators;
|
|
32
34
|
private readonly offloaded;
|
|
35
|
+
private warn;
|
|
36
|
+
private readonly pending;
|
|
37
|
+
private dropped;
|
|
33
38
|
constructor(warn?: (message: string) => void);
|
|
39
|
+
/** 接上(或换掉)告警通道;之前缓冲的告警按序冲出。 */
|
|
40
|
+
setWarn(warn: (message: string) => void): void;
|
|
34
41
|
/** 记下条目的行位置;不含图片的条目忽略。 */
|
|
35
42
|
track(entry: SessionEntry, locator: LineLocator): void;
|
|
36
43
|
/** 读文件时逐行调用:记下行位置;`strip` 时就地换成占位(刚解析的对象,别处没有引用)。 */
|
|
@@ -48,4 +55,5 @@ export declare class ImageOffload {
|
|
|
48
55
|
sync(file: string, branch: readonly SessionEntry[], index: ReadonlyMap<string, SessionEntry>): void;
|
|
49
56
|
/** 从文件读回条目(新对象);不成功时告警、忘掉行位置并返回 undefined。 */
|
|
50
57
|
private read;
|
|
58
|
+
private report;
|
|
51
59
|
}
|
package/dist/session/offload.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* `content` 换新数组,image 块换成 `data: ""` 的占位;
|
|
11
11
|
* - 回读:按行位置 `readSync` 一行、`JSON.parse`,校验 id 与内容块数,图片经 `internImage` 驻留后
|
|
12
12
|
* **就地**放回占位所在的数组(之前拿到同一消息对象的调用方随之看到原图);读不回(文件被外部改写)时
|
|
13
|
-
* 保留 `""`
|
|
13
|
+
* 保留 `""` 并告警,不再重试;告警通道可后接(`setWarn`):接上之前的告警先缓冲(最多
|
|
14
|
+
* `PENDING_WARNINGS` 条,其余折叠成一条计数),接上时按序冲出(#183);
|
|
14
15
|
* - `original(entry)`:给 `getEntries` / `fork` 的原文副本,不改内存状态。
|
|
15
16
|
*/
|
|
16
17
|
import { closeSync, openSync, readSync } from "node:fs";
|
|
@@ -74,14 +75,28 @@ export function editTargets(file) {
|
|
|
74
75
|
return targets;
|
|
75
76
|
}
|
|
76
77
|
const placeholder = (block) => ({ ...block, data: "" });
|
|
78
|
+
/** 告警通道接上之前最多缓冲的条数;超出的只计数,冲出时折叠成一条。 */
|
|
79
|
+
export const PENDING_WARNINGS = 16;
|
|
77
80
|
export class ImageOffload {
|
|
78
|
-
warn;
|
|
79
81
|
/** 含图 message 条目的行位置(只有已落盘的条目才有)。 */
|
|
80
82
|
locators = new Map();
|
|
81
83
|
offloaded = new Set();
|
|
82
|
-
|
|
84
|
+
warn;
|
|
85
|
+
pending = [];
|
|
86
|
+
dropped = 0;
|
|
87
|
+
constructor(warn) {
|
|
83
88
|
this.warn = warn;
|
|
84
89
|
}
|
|
90
|
+
/** 接上(或换掉)告警通道;之前缓冲的告警按序冲出。 */
|
|
91
|
+
setWarn(warn) {
|
|
92
|
+
this.warn = warn;
|
|
93
|
+
const pending = this.pending.splice(0);
|
|
94
|
+
if (this.dropped > 0)
|
|
95
|
+
pending.push(`and ${this.dropped} more session entries could not be read back`);
|
|
96
|
+
this.dropped = 0;
|
|
97
|
+
for (const message of pending)
|
|
98
|
+
warn(message);
|
|
99
|
+
}
|
|
85
100
|
/** 记下条目的行位置;不含图片的条目忽略。 */
|
|
86
101
|
track(entry, locator) {
|
|
87
102
|
if (hasImages(entry))
|
|
@@ -188,8 +203,16 @@ export class ImageOffload {
|
|
|
188
203
|
catch (error) {
|
|
189
204
|
this.locators.delete(entry.id);
|
|
190
205
|
this.offloaded.delete(entry.id);
|
|
191
|
-
this.
|
|
206
|
+
this.report(`cannot read session entry ${entry.id} back from ${file}: ${String(error)}`);
|
|
192
207
|
return undefined;
|
|
193
208
|
}
|
|
194
209
|
}
|
|
210
|
+
report(message) {
|
|
211
|
+
if (this.warn !== undefined)
|
|
212
|
+
this.warn(message);
|
|
213
|
+
else if (this.pending.length < PENDING_WARNINGS)
|
|
214
|
+
this.pending.push(message);
|
|
215
|
+
else
|
|
216
|
+
this.dropped++;
|
|
217
|
+
}
|
|
195
218
|
}
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* 这里只拿到 ChildProcess;stdout / stderr 直接写进 `outputPath`(文件描述符交给子进程,ama 退出也不
|
|
6
6
|
* 会因管道断开而 SIGPIPE)。POSIX 自成进程组并登记(进程退出时同步 SIGKILL 残留组)。
|
|
7
7
|
* - `wait(id, timeoutMs)`:等到退出或超时;`stop(id)`:杀整棵树(SIGTERM → 宽限 → SIGKILL);
|
|
8
|
-
* `output(id)
|
|
8
|
+
* `output(id, options?)`:读输出文件尾部(缺省 2000 行 / 50 KB,调用方可传字节上限)。
|
|
9
9
|
* - 生命周期事件经 `onChange` 发出(started / exited / stopped);`takeExited()` 取走尚未通知的已退出
|
|
10
10
|
* 任务(提醒通道 reminders.ts 据此告诉模型「后台命令 jobId 已退出(码 N)」)。
|
|
11
11
|
* - 按会话 id 登记(`jobsForSession`);`disposeSessionJobs` 在会话 dispose 时回收进程树。
|
|
12
12
|
*/
|
|
13
13
|
import type { ChildProcess } from "node:child_process";
|
|
14
14
|
import { type ProcessDeps } from "./process-tree.js";
|
|
15
|
-
import { type TruncateResult } from "./truncate.js";
|
|
15
|
+
import { type TruncateOptions, type TruncateResult } from "./truncate.js";
|
|
16
16
|
export type JobStatus = "running" | "exited" | "stopped";
|
|
17
17
|
export interface Job {
|
|
18
18
|
id: string;
|
|
@@ -49,8 +49,8 @@ export declare class BackgroundJobs {
|
|
|
49
49
|
/** 等到退出或超时(`signal` 可提前结束等待,不杀进程)。 */
|
|
50
50
|
wait(id: string, timeoutMs: number, signal?: AbortSignal): Promise<Job | undefined>;
|
|
51
51
|
stop(id: string): Promise<Job | undefined>;
|
|
52
|
-
/**
|
|
53
|
-
output(id: string): (TruncateResult & {
|
|
52
|
+
/** 输出文件尾部(缺省 2000 行 / 50 KB;调用方可传字节上限,#155)。 */
|
|
53
|
+
output(id: string, options?: TruncateOptions): (TruncateResult & {
|
|
54
54
|
job: Job;
|
|
55
55
|
}) | undefined;
|
|
56
56
|
/** 取走已退出、尚未通知的任务(提醒通道用;wait 已看到退出的也算已知)。 */
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 这里只拿到 ChildProcess;stdout / stderr 直接写进 `outputPath`(文件描述符交给子进程,ama 退出也不
|
|
6
6
|
* 会因管道断开而 SIGPIPE)。POSIX 自成进程组并登记(进程退出时同步 SIGKILL 残留组)。
|
|
7
7
|
* - `wait(id, timeoutMs)`:等到退出或超时;`stop(id)`:杀整棵树(SIGTERM → 宽限 → SIGKILL);
|
|
8
|
-
* `output(id)
|
|
8
|
+
* `output(id, options?)`:读输出文件尾部(缺省 2000 行 / 50 KB,调用方可传字节上限)。
|
|
9
9
|
* - 生命周期事件经 `onChange` 发出(started / exited / stopped);`takeExited()` 取走尚未通知的已退出
|
|
10
10
|
* 任务(提醒通道 reminders.ts 据此告诉模型「后台命令 jobId 已退出(码 N)」)。
|
|
11
11
|
* - 按会话 id 登记(`jobsForSession`);`disposeSessionJobs` 在会话 dispose 时回收进程树。
|
|
@@ -119,8 +119,8 @@ export class BackgroundJobs {
|
|
|
119
119
|
await entry.done;
|
|
120
120
|
return entry.job;
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
123
|
-
output(id) {
|
|
122
|
+
/** 输出文件尾部(缺省 2000 行 / 50 KB;调用方可传字节上限,#155)。 */
|
|
123
|
+
output(id, options = {}) {
|
|
124
124
|
const entry = this.entries.get(id);
|
|
125
125
|
if (entry === undefined)
|
|
126
126
|
return undefined;
|
|
@@ -132,7 +132,7 @@ export class BackgroundJobs {
|
|
|
132
132
|
catch {
|
|
133
133
|
// 输出文件被删:按空输出
|
|
134
134
|
}
|
|
135
|
-
return { ...truncateTail(text), job: entry.job };
|
|
135
|
+
return { ...truncateTail(text, options), job: entry.job };
|
|
136
136
|
}
|
|
137
137
|
/** 取走已退出、尚未通知的任务(提醒通道用;wait 已看到退出的也算已知)。 */
|
|
138
138
|
takeExited() {
|
package/dist/tools/bash.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - 注入 `AMA_*` 环境变量(shell.ts)。shell 退出后不等仍占着管道的后台进程。
|
|
10
10
|
* - [W5-H2] 后台命令(§8.3 H6,不加新工具):`{ command, background: true }` 立即返回
|
|
11
11
|
* `{ jobId, outputPath, pid }`;`{ job, action: "wait" | "output" | "stop" }` 查询 / 结束
|
|
12
|
-
* (background-jobs.ts,按会话登记,会话 dispose
|
|
12
|
+
* (background-jobs.ts,按会话登记,会话 dispose 时回收进程树;退出经提醒通道通知)。查询输出同样按
|
|
13
|
+
* 会话结果上限尾截断(#155)。
|
|
13
14
|
* - **进程只在 `spawnShell` 里创建**(前台与后台共用)。[S2] `sandbox.bash: auto` 且有能限制写入的
|
|
14
15
|
* OS 沙箱时在这一处包一层(src/sandbox/bash.ts);`sandbox: false` 不包装(权限管线照常审批)。
|
|
15
16
|
* 失败输出像是被沙箱拒绝时末尾追加一行说明。
|
package/dist/tools/bash.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - 注入 `AMA_*` 环境变量(shell.ts)。shell 退出后不等仍占着管道的后台进程。
|
|
10
10
|
* - [W5-H2] 后台命令(§8.3 H6,不加新工具):`{ command, background: true }` 立即返回
|
|
11
11
|
* `{ jobId, outputPath, pid }`;`{ job, action: "wait" | "output" | "stop" }` 查询 / 结束
|
|
12
|
-
* (background-jobs.ts,按会话登记,会话 dispose
|
|
12
|
+
* (background-jobs.ts,按会话登记,会话 dispose 时回收进程树;退出经提醒通道通知)。查询输出同样按
|
|
13
|
+
* 会话结果上限尾截断(#155)。
|
|
13
14
|
* - **进程只在 `spawnShell` 里创建**(前台与后台共用)。[S2] `sandbox.bash: auto` 且有能限制写入的
|
|
14
15
|
* OS 沙箱时在这一处包一层(src/sandbox/bash.ts);`sandbox: false` 不包装(权限管线照常审批)。
|
|
15
16
|
* 失败输出像是被沙箱拒绝时末尾追加一行说明。
|
|
@@ -304,7 +305,7 @@ async function jobAction(input, ctx, options) {
|
|
|
304
305
|
}
|
|
305
306
|
if (action === "stop") {
|
|
306
307
|
const job = (await jobs.stop(id));
|
|
307
|
-
const out = jobs.output(id);
|
|
308
|
+
const out = jobs.output(id, { maxBytes: toolOutputBytes(ctx.maxResultChars) });
|
|
308
309
|
const text = out?.content ?? "";
|
|
309
310
|
return {
|
|
310
311
|
content: `${text === "" ? "(no output)" : text}\n\n${jobStatusLine(job)}`,
|
|
@@ -317,7 +318,7 @@ async function jobAction(input, ctx, options) {
|
|
|
317
318
|
return fail(`timeoutMs must be between 1 and ${MAX_BASH_TIMEOUT_MS}`);
|
|
318
319
|
await jobs.wait(id, waitMs, ctx.signal);
|
|
319
320
|
}
|
|
320
|
-
const out = jobs.output(id);
|
|
321
|
+
const out = jobs.output(id, { maxBytes: toolOutputBytes(ctx.maxResultChars) });
|
|
321
322
|
if (out === undefined)
|
|
322
323
|
return fail(`Unknown background job ${id}`);
|
|
323
324
|
const job = out.job;
|
package/dist/tools/task-ctl.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { capTaskText, formatTokens, waitDetachedText } from "../agents/result.js";
|
|
14
14
|
import { taskControl } from "../agents/task-control.js";
|
|
15
|
+
import { nestedTaskRejection } from "./task.js";
|
|
15
16
|
export const TASK_CTL_TOOL = "task_ctl";
|
|
16
17
|
export const DEFAULT_WAIT_MS = 30_000;
|
|
17
18
|
export const MAX_WAIT_MS = 600_000;
|
|
@@ -66,7 +67,7 @@ export function createTaskCtlTool() {
|
|
|
66
67
|
annotations: { pollable: true },
|
|
67
68
|
async execute(input, ctx) {
|
|
68
69
|
if (ctx.depth >= 1)
|
|
69
|
-
return fail("task_ctl is not available inside a sub-agent.");
|
|
70
|
+
return fail(nestedTaskRejection(ctx, "task_ctl", "task_ctl is not available inside a sub-agent."));
|
|
70
71
|
const control = taskControl(ctx.sessionId);
|
|
71
72
|
if (input.action === "list") {
|
|
72
73
|
const tasks = control?.list() ?? ctx.tasks?.list() ?? [];
|
package/dist/tools/task.d.ts
CHANGED
|
@@ -20,6 +20,15 @@ import type { ModelThinkingLevel } from "../ai/types.js";
|
|
|
20
20
|
import { AgentCatalog } from "../agents/catalog.js";
|
|
21
21
|
import type { SubagentRequest, ToolContext, ToolDefinition } from "./types.js";
|
|
22
22
|
export declare const MAX_TASK_DEPTH = 1;
|
|
23
|
+
/** <task> 指令首个非空行,超长截断(#191:fork 子会话被拒时提醒模型回到子任务)。 */
|
|
24
|
+
export declare function taskLine(prompt: string): string;
|
|
25
|
+
/** fork 子会话的标记条目(custom,不进上下文):`{ taskLine }`。 */
|
|
26
|
+
export declare const FORK_CUSTOM_TYPE = "ama.fork";
|
|
27
|
+
/**
|
|
28
|
+
* 子会话里 task / task_ctl 的拒绝文案(固定英文)。#191:fork 子会话(有 `ama.fork` 条目)说明身份并
|
|
29
|
+
* 附 <task> 首行;其它情况用 `fallback`(fresh 按深度拒绝的原文案)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function nestedTaskRejection(ctx: ToolContext, tool: string, fallback: string): string;
|
|
23
32
|
export declare const DEFAULT_TASK_MAX_TURNS = 30;
|
|
24
33
|
export interface TaskInput {
|
|
25
34
|
prompt: string;
|
package/dist/tools/task.js
CHANGED
|
@@ -19,6 +19,32 @@
|
|
|
19
19
|
import { AgentCatalog } from "../agents/catalog.js";
|
|
20
20
|
import { TASK_NOTIFICATION_RULE } from "../agents/result.js";
|
|
21
21
|
export const MAX_TASK_DEPTH = 1;
|
|
22
|
+
/** fork 子会话首条 custom 条目里 <task> 指令首行摘要的上限(字符)。 */
|
|
23
|
+
const TASK_LINE_MAX = 120;
|
|
24
|
+
/** <task> 指令首个非空行,超长截断(#191:fork 子会话被拒时提醒模型回到子任务)。 */
|
|
25
|
+
export function taskLine(prompt) {
|
|
26
|
+
const line = prompt
|
|
27
|
+
.split("\n")
|
|
28
|
+
.find((text) => text.trim() !== "")
|
|
29
|
+
?.trim() ?? "";
|
|
30
|
+
return line.length > TASK_LINE_MAX ? `${line.slice(0, TASK_LINE_MAX - 1)}…` : line;
|
|
31
|
+
}
|
|
32
|
+
/** fork 子会话的标记条目(custom,不进上下文):`{ taskLine }`。 */
|
|
33
|
+
export const FORK_CUSTOM_TYPE = "ama.fork";
|
|
34
|
+
/**
|
|
35
|
+
* 子会话里 task / task_ctl 的拒绝文案(固定英文)。#191:fork 子会话(有 `ama.fork` 条目)说明身份并
|
|
36
|
+
* 附 <task> 首行;其它情况用 `fallback`(fresh 按深度拒绝的原文案)。
|
|
37
|
+
*/
|
|
38
|
+
export function nestedTaskRejection(ctx, tool, fallback) {
|
|
39
|
+
const mark = ctx.session.lastCustom(FORK_CUSTOM_TYPE);
|
|
40
|
+
if (mark === undefined || mark === null)
|
|
41
|
+
return fallback;
|
|
42
|
+
const line = typeof mark.taskLine === "string" ? mark.taskLine : "";
|
|
43
|
+
return (`${tool} rejected: you are a sub-agent forked from the main conversation and cannot start ` +
|
|
44
|
+
"or manage sub-agents. Do not call task or task_ctl again; complete the sub-task in your " +
|
|
45
|
+
"<task> block yourself with your other tools, then end with your report." +
|
|
46
|
+
(line !== "" ? ` Your task begins: "${line}"` : ""));
|
|
47
|
+
}
|
|
22
48
|
export const DEFAULT_TASK_MAX_TURNS = 30;
|
|
23
49
|
const THINKING = [
|
|
24
50
|
"off",
|
|
@@ -143,7 +169,7 @@ export function createTaskTool(_options = {}) {
|
|
|
143
169
|
promptGuidelines: [TASK_NOTIFICATION_RULE],
|
|
144
170
|
async execute(input, ctx) {
|
|
145
171
|
if (ctx.depth >= MAX_TASK_DEPTH || ctx.spawnSubagent === undefined) {
|
|
146
|
-
return fail("Sub-agents are not available here (nested tasks are not allowed).");
|
|
172
|
+
return fail(nestedTaskRejection(ctx, "task", "Sub-agents are not available here (nested tasks are not allowed)."));
|
|
147
173
|
}
|
|
148
174
|
const request = buildSubagentRequest(input, ctx);
|
|
149
175
|
if (typeof request === "string")
|