@armadra/agent 0.7.2 → 0.7.4
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 +41 -0
- package/CHANGELOG.zh-CN.md +41 -0
- package/dist/agent/retry.d.ts +14 -5
- package/dist/agent/retry.js +30 -8
- package/dist/agent/session-cache-key.d.ts +8 -0
- package/dist/agent/session-cache-key.js +46 -0
- package/dist/agent/session-cache.d.ts +2 -3
- package/dist/agent/session-cache.js +19 -37
- package/dist/agent/session-classifier.d.ts +3 -2
- package/dist/agent/session-classifier.js +36 -10
- package/dist/agent/session-compaction.d.ts +30 -5
- package/dist/agent/session-compaction.js +69 -29
- package/dist/agent/session-core.d.ts +2 -0
- package/dist/agent/session-run.d.ts +12 -0
- package/dist/agent/session-run.js +29 -10
- package/dist/agent/session-subagent.d.ts +8 -1
- package/dist/agent/session-subagent.js +37 -14
- package/dist/agent/session-sync.d.ts +8 -2
- package/dist/agent/session-sync.js +7 -10
- package/dist/agent/session-tools.js +3 -2
- package/dist/agent/subagent-fork.d.ts +67 -0
- package/dist/agent/subagent-fork.js +87 -0
- package/dist/agent/subagent-registry.d.ts +2 -2
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/tool-availability.d.ts +8 -0
- package/dist/agent/tool-availability.js +8 -0
- package/dist/agent/tool-runner.js +13 -4
- package/dist/agent/worktree.d.ts +8 -0
- package/dist/agent/worktree.js +51 -4
- package/dist/agents/parse.js +6 -1
- package/dist/agents/task-record.js +3 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/ai/apis/anthropic-messages.js +10 -5
- package/dist/ai/apis/anthropic-request.d.ts +2 -1
- package/dist/ai/apis/anthropic-request.js +9 -3
- package/dist/ai/apis/google-generative-ai.js +4 -2
- package/dist/ai/apis/max-tokens.d.ts +42 -0
- package/dist/ai/apis/max-tokens.js +121 -0
- package/dist/ai/apis/openai-completions.js +10 -5
- package/dist/ai/apis/openai-request.js +1 -1
- package/dist/ai/apis/openai-responses-request.js +1 -1
- package/dist/ai/apis/openai-responses.js +9 -5
- package/dist/ai/apis/shared.d.ts +3 -1
- package/dist/ai/apis/shared.js +21 -3
- package/dist/ai/cache/fingerprint.d.ts +4 -1
- package/dist/ai/cache/fingerprint.js +18 -1
- package/dist/ai/cache/miss.d.ts +2 -1
- package/dist/ai/cache/miss.js +9 -3
- package/dist/ai/cache/reporting.js +5 -1
- package/dist/ai/cache/types.d.ts +10 -2
- package/dist/ai/cache/warmer.d.ts +6 -1
- package/dist/ai/cache/warmer.js +7 -0
- package/dist/ai/context.d.ts +12 -4
- package/dist/ai/context.js +43 -24
- package/dist/ai/fake/fake-provider.d.ts +8 -1
- package/dist/ai/fake/fake-provider.js +19 -7
- package/dist/ai/fake/fake-script.d.ts +2 -0
- package/dist/ai/http.d.ts +17 -9
- package/dist/ai/http.js +33 -13
- package/dist/ai/image-intern.d.ts +29 -0
- package/dist/ai/image-intern.js +58 -0
- package/dist/ai/json-body.d.ts +31 -0
- package/dist/ai/json-body.js +208 -0
- package/dist/ai/overflow.js +5 -0
- package/dist/ai/providers/catalog-data.js +13 -13
- package/dist/ai/providers/catalog.d.ts +33 -0
- package/dist/ai/providers/catalog.js +105 -6
- package/dist/ai/providers/enrich.d.ts +13 -3
- package/dist/ai/providers/enrich.js +87 -31
- package/dist/ai/sse.d.ts +3 -2
- package/dist/ai/sse.js +3 -2
- package/dist/ai/types.d.ts +9 -2
- package/dist/bundle/ama.cjs +3142 -1805
- package/dist/cli/compose-agents.js +3 -0
- package/dist/cli/compose-extensions.js +2 -0
- package/dist/cli/compose-request.d.ts +20 -0
- package/dist/cli/compose-request.js +37 -0
- package/dist/cli/compose-session.js +2 -2
- package/dist/cli/subcommands/model-meta.d.ts +2 -1
- package/dist/cli/subcommands/model-meta.js +3 -2
- package/dist/codemode/host-side.d.ts +11 -4
- package/dist/codemode/host-side.js +25 -6
- package/dist/codemode/tool.d.ts +2 -0
- package/dist/codemode/tool.js +4 -0
- package/dist/compaction/prefix-estimate.d.ts +3 -1
- package/dist/compaction/prefix-estimate.js +7 -3
- package/dist/config/json-schema.js +9 -2
- package/dist/config/key-docs.js +13 -4
- package/dist/config/schema-w5.js +9 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +19 -3
- package/dist/config/settings-registry.js +6 -0
- package/dist/config/types-w5.d.ts +2 -0
- package/dist/config/types.d.ts +19 -1
- package/dist/config/types.js +4 -0
- package/dist/i18n/catalog.d.ts +15 -2
- package/dist/i18n/messages/cli.d.ts +2 -0
- package/dist/i18n/messages/cli.js +2 -0
- package/dist/i18n/messages/config-keys.d.ts +10 -0
- package/dist/i18n/messages/config-keys.js +12 -2
- package/dist/i18n/messages/config.d.ts +14 -0
- package/dist/i18n/messages/config.js +4 -0
- package/dist/i18n/messages/errors.d.ts +3 -2
- package/dist/i18n/messages/errors.js +15 -2
- package/dist/i18n/messages/report-context.d.ts +4 -0
- package/dist/i18n/messages/report-context.js +4 -0
- package/dist/i18n/messages/report.d.ts +6 -0
- package/dist/i18n/messages/report.js +2 -0
- package/dist/i18n/messages/settings.d.ts +4 -0
- package/dist/i18n/messages/settings.js +6 -2
- package/dist/i18n/messages/subcommands.d.ts +2 -2
- package/dist/i18n/messages/subcommands.js +4 -2
- package/dist/modes/acp/acp-server.js +15 -5
- package/dist/modes/acp/acp-sessions.d.ts +9 -0
- package/dist/modes/acp/acp-sessions.js +25 -0
- package/dist/modes/context-report.d.ts +2 -0
- package/dist/modes/context-report.js +25 -8
- package/dist/modes/print/json-event.d.ts +12 -1
- package/dist/modes/print/json-event.js +48 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -0
- package/dist/modes/rpc/rpc-mode.js +9 -2
- package/dist/modes/session-report.d.ts +1 -1
- package/dist/modes/session-report.js +12 -6
- package/dist/rpc.d.ts +5 -2
- package/dist/session/line-reader.d.ts +28 -0
- package/dist/session/line-reader.js +82 -0
- package/dist/session/list.d.ts +14 -0
- package/dist/session/list.js +150 -0
- package/dist/session/manager.d.ts +7 -3
- package/dist/session/manager.js +21 -48
- package/dist/session/migrate.d.ts +2 -1
- package/dist/session/migrate.js +2 -1
- package/dist/session/projection.d.ts +16 -3
- package/dist/session/projection.js +96 -29
- package/dist/session/scan.d.ts +3 -3
- package/dist/session/scan.js +9 -18
- package/dist/session/store.d.ts +3 -0
- package/dist/session/store.js +37 -31
- package/dist/session/types.d.ts +7 -2
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.js +5 -4
- package/dist/tools/glob.d.ts +2 -2
- package/dist/tools/glob.js +2 -2
- package/dist/tools/grep.js +4 -6
- package/dist/tools/image-file.js +2 -1
- package/dist/tools/read-lines.d.ts +32 -0
- package/dist/tools/read-lines.js +147 -0
- package/dist/tools/read.d.ts +6 -2
- package/dist/tools/read.js +47 -27
- package/dist/tools/task.d.ts +9 -0
- package/dist/tools/task.js +21 -8
- package/dist/tools/truncate.d.ts +7 -0
- package/dist/tools/truncate.js +11 -0
- package/dist/tools/types.d.ts +13 -0
- package/dist/trace/build-index.js +13 -1
- package/dist/tui/ansi.d.ts +2 -0
- package/dist/tui/ansi.js +7 -3
- package/dist/tui/components/editor-buffer.js +2 -2
- package/dist/tui/components/editor.js +5 -6
- package/dist/tui/keys.js +2 -2
- package/dist/tui/stdin-buffer.js +3 -3
- package/docs/acp.md +5 -1
- package/docs/agents.md +42 -6
- package/docs/codemode.md +4 -2
- package/docs/en/acp.md +3 -2
- package/docs/en/host-api.md +1 -1
- package/docs/en/permissions.md +2 -2
- package/docs/en/providers.md +54 -20
- package/docs/en/rpc.md +23 -7
- package/docs/en/sessions.md +4 -2
- package/docs/en/tui.md +3 -3
- package/docs/permissions.md +4 -1
- package/docs/providers.md +85 -22
- package/docs/rpc.md +22 -6
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +3 -1
- package/docs/tui.md +2 -2
- package/package.json +2 -2
|
@@ -18,12 +18,16 @@
|
|
|
18
18
|
* (只统计,不写分支、不改请求);附档一 / 档二阈值。压缩调度本身仍只看 `estimate()`。
|
|
19
19
|
* [W3-C1b] 阈值 / 手动压缩与分支摘要优先走会话前缀续写(缓存控制器给前缀),溢出恢复不走
|
|
20
20
|
* (前缀本身已超窗口);续写失败回落独立请求并记 warning。
|
|
21
|
+
* [ME-B] D6 档一 / 档二边界:先干跑档一;裁完仍超预算且续写可用(同模型、前缀成立、缓存未冷)→
|
|
22
|
+
* 不裁、直接续写摘要(被裁内容本来就进摘要,续写比全价独立请求便宜一个数量级);裁完够 → 只裁;
|
|
23
|
+
* 缓存已冷 → 照旧先裁,摘要走独立请求。D16 软窗口:`compaction.contextBudget` 设了时档一 / 档二 /
|
|
24
|
+
* 熔断 / context_pressure 的窗口取 min(模型窗口, 它)。
|
|
21
25
|
*/
|
|
22
26
|
import { AmaError } from "../errors.js";
|
|
23
27
|
import { CompactionBreaker } from "../compaction/breaker.js";
|
|
24
28
|
import { estimateMessageTokens, estimateProjectedTokens, } from "../compaction/estimate.js";
|
|
25
|
-
import {
|
|
26
|
-
import { planPrune, prunePolicy } from "../compaction/prune-tier.js";
|
|
29
|
+
import { prefixSystemMessage, withPrefixBaseline } from "../compaction/prefix-estimate.js";
|
|
30
|
+
import { planPrune, prunePolicy, } from "../compaction/prune-tier.js";
|
|
27
31
|
import { createProtection, skillLocations } from "../compaction/protect.js";
|
|
28
32
|
import { buildPostCompactBlock } from "../compaction/post-compact.js";
|
|
29
33
|
import { prepareCompaction, prepareCompactionAt, runCompaction, } from "../compaction/summarize-tier.js";
|
|
@@ -37,6 +41,16 @@ export const DEFAULT_COMPACTION_SETTINGS = {
|
|
|
37
41
|
};
|
|
38
42
|
/** 保留区不超过 (窗口 − 预留) 的 40%,否则小窗口模型上摘要后仍然溢出。 */
|
|
39
43
|
export const KEEP_RECENT_MAX_RATIO = 0.4;
|
|
44
|
+
/**
|
|
45
|
+
* [ME-B] D16 软窗口:模型窗口与 `compaction.contextBudget` 取小;窗口未知仍 undefined(不压缩)。
|
|
46
|
+
* `compaction` 是会话选项里展开的 config 段(`contextBudget` 不在 CompactionSettings 类型上)。
|
|
47
|
+
*/
|
|
48
|
+
export function effectiveWindow(contextWindow, compaction) {
|
|
49
|
+
const budget = compaction?.contextBudget;
|
|
50
|
+
if (contextWindow === undefined || budget === undefined)
|
|
51
|
+
return contextWindow;
|
|
52
|
+
return Math.min(contextWindow, budget);
|
|
53
|
+
}
|
|
40
54
|
/** Hook additionalContext 的 custom 类型(与 UserPromptSubmit 的相同)。 */
|
|
41
55
|
const HOOK_CONTEXT_CUSTOM_TYPE = "ama.hook_context";
|
|
42
56
|
export class CompactionController {
|
|
@@ -56,14 +70,18 @@ export class CompactionController {
|
|
|
56
70
|
this.pruneConfig.prune = extra.prune;
|
|
57
71
|
if (extra.pruneExclude !== undefined)
|
|
58
72
|
this.pruneConfig.pruneExclude = extra.pruneExclude;
|
|
59
|
-
this.breaker = new CompactionBreaker(this.settings.enabled,
|
|
73
|
+
this.breaker = new CompactionBreaker(this.settings.enabled, this.window());
|
|
74
|
+
}
|
|
75
|
+
/** 档一 / 档二 / 熔断用的窗口(软窗口生效后的值)。 */
|
|
76
|
+
window() {
|
|
77
|
+
return effectiveWindow(this.core.model().contextWindow, this.settings);
|
|
60
78
|
}
|
|
61
79
|
get isCompacting() {
|
|
62
80
|
return this.compacting;
|
|
63
81
|
}
|
|
64
82
|
/** 模型或开关变化后调用。 */
|
|
65
83
|
refresh() {
|
|
66
|
-
this.breaker.configure(this.settings.enabled, this.
|
|
84
|
+
this.breaker.configure(this.settings.enabled, this.window());
|
|
67
85
|
}
|
|
68
86
|
setAuto(enabled) {
|
|
69
87
|
this.settings = { ...this.settings, enabled };
|
|
@@ -96,17 +114,10 @@ export class CompactionController {
|
|
|
96
114
|
}
|
|
97
115
|
/** 将要发送的系统提示 + 工具声明的估算(与首条 system 消息同样装配,不落盘)。 */
|
|
98
116
|
prefixTokens() {
|
|
99
|
-
|
|
100
|
-
const tools = core
|
|
101
|
-
.activeToolNames()
|
|
102
|
-
.map((name) => core.activeTool(name))
|
|
103
|
-
.filter((tool) => tool !== undefined);
|
|
104
|
-
const system = core.childBase?.().system ?? core.options.system ?? {};
|
|
105
|
-
const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
|
|
106
|
-
return estimatePrefixTokens(sections, toolDecls(tools));
|
|
117
|
+
return estimateMessageTokens(pendingPrefixMessage(this.core));
|
|
107
118
|
}
|
|
108
119
|
budget() {
|
|
109
|
-
const window = this.
|
|
120
|
+
const window = this.window();
|
|
110
121
|
return window === undefined ? undefined : Math.max(0, window - this.settings.reserveTokens);
|
|
111
122
|
}
|
|
112
123
|
keepRecent() {
|
|
@@ -115,7 +126,10 @@ export class CompactionController {
|
|
|
115
126
|
return this.settings.keepRecentTokens;
|
|
116
127
|
return Math.max(1, Math.min(this.settings.keepRecentTokens, Math.floor(budget * KEEP_RECENT_MAX_RATIO)));
|
|
117
128
|
}
|
|
118
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* 摘要请求的公共选项;`continuation` 时附上会话前缀续写的材料(传入对象 = 调用方已算好的那份,
|
|
131
|
+
* 避免重算)。
|
|
132
|
+
*/
|
|
119
133
|
async summarizer(signal, instructions, continuation) {
|
|
120
134
|
const core = this.core;
|
|
121
135
|
const options = {
|
|
@@ -127,7 +141,9 @@ export class CompactionController {
|
|
|
127
141
|
onFallback: (reason) => core.log("warn", `summary by prefix continuation failed (${reason}); used a separate request`),
|
|
128
142
|
onInvalid: (reason) => core.log("warn", `${reason}; kept it after one retry`),
|
|
129
143
|
};
|
|
130
|
-
if (continuation)
|
|
144
|
+
if (typeof continuation === "object")
|
|
145
|
+
options.continuation = continuation;
|
|
146
|
+
else if (continuation)
|
|
131
147
|
options.continuation = core.cache?.summaryContinuation();
|
|
132
148
|
return options;
|
|
133
149
|
}
|
|
@@ -136,10 +152,8 @@ export class CompactionController {
|
|
|
136
152
|
const budget = this.budget();
|
|
137
153
|
return budget === undefined ? undefined : prunePolicy(budget, this.pruneConfig.prune);
|
|
138
154
|
}
|
|
139
|
-
/**
|
|
140
|
-
|
|
141
|
-
*/
|
|
142
|
-
prune(policy, need) {
|
|
155
|
+
/** 档一干跑;`need` = 清到目标还要省多少(undefined = 全部候选)。 */
|
|
156
|
+
prunePlan(policy, need) {
|
|
143
157
|
const core = this.core;
|
|
144
158
|
const items = buildProjection(core.manager.branch()).items;
|
|
145
159
|
const isProtected = createProtection({
|
|
@@ -148,7 +162,10 @@ export class CompactionController {
|
|
|
148
162
|
exclude: this.pruneConfig.pruneExclude ?? [],
|
|
149
163
|
keepInContext: (name) => core.tool(name)?.annotations?.keepInContext === true,
|
|
150
164
|
});
|
|
151
|
-
|
|
165
|
+
return planPrune(items, { policy, need, outputDir: core.outputDir(), isProtected });
|
|
166
|
+
}
|
|
167
|
+
/** 档一落盘:写 context_edit 并重载消息。返回省下的 token 估算(0 = 未裁)。 */
|
|
168
|
+
applyPrune(plan) {
|
|
152
169
|
for (const item of plan.items) {
|
|
153
170
|
this.core.appendEntry({
|
|
154
171
|
type: "context_edit",
|
|
@@ -161,7 +178,10 @@ export class CompactionController {
|
|
|
161
178
|
this.core.reloadMessages();
|
|
162
179
|
return plan.savedTokens;
|
|
163
180
|
}
|
|
164
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* 阈值检查(档一 → 档二,[ME-B] D6)。失败只记录,不打断 run。
|
|
183
|
+
* 先干跑档一:裁完仍超预算、熔断允许且续写可用 → 跳过裁剪直接续写摘要;否则照旧先裁。
|
|
184
|
+
*/
|
|
165
185
|
async checkThreshold(signal) {
|
|
166
186
|
if (!this.breaker.autoEnabled || this.compacting || signal.aborted)
|
|
167
187
|
return;
|
|
@@ -173,17 +193,24 @@ export class CompactionController {
|
|
|
173
193
|
let tokens = this.estimate().tokens;
|
|
174
194
|
// 缓存已冷(C3):前缀反正要重写,未到阈值也把候选一次换掉(仍要可省 ≥ clearAtLeast)
|
|
175
195
|
const cold = this.core.cache?.isCold() ?? false;
|
|
196
|
+
let continuation;
|
|
176
197
|
if (tokens > policy.triggerTokens || cold) {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
198
|
+
const plan = this.prunePlan(policy, cold ? undefined : tokens - policy.targetTokens);
|
|
199
|
+
if (!cold && tokens - plan.savedTokens > budget && this.summaryAllowed(budget))
|
|
200
|
+
continuation = this.core.cache?.summaryContinuation();
|
|
201
|
+
if (continuation === undefined && this.applyPrune(plan) > 0)
|
|
179
202
|
tokens = this.estimate().tokens;
|
|
180
203
|
}
|
|
181
204
|
if (tokens <= budget)
|
|
182
205
|
return;
|
|
183
|
-
|
|
184
|
-
if (!this.breaker.canSummarize())
|
|
206
|
+
if (!this.summaryAllowed(budget))
|
|
185
207
|
return this.warnBlocked();
|
|
186
|
-
await this.summarize("threshold", signal);
|
|
208
|
+
await this.summarize("threshold", signal, undefined, undefined, continuation);
|
|
209
|
+
}
|
|
210
|
+
/** 熔断是否允许自动摘要(顺带更新「固定前缀超预算」)。 */
|
|
211
|
+
summaryAllowed(budget) {
|
|
212
|
+
this.breaker.setPrefixOverflow(this.fixedPrefixTokens() > budget);
|
|
213
|
+
return this.breaker.canSummarize();
|
|
187
214
|
}
|
|
188
215
|
/** 回注块(todo、计划、已加载 Skill、最近文件、转录与 outputs 路径;不含文件正文)。 */
|
|
189
216
|
postCompactBlock() {
|
|
@@ -256,7 +283,7 @@ export class CompactionController {
|
|
|
256
283
|
async recoverOverflow(signal) {
|
|
257
284
|
if (!this.settings.enabled)
|
|
258
285
|
return { retry: false, warning: "context overflow: auto-compaction is disabled" };
|
|
259
|
-
if (this.
|
|
286
|
+
if (this.window() === undefined) {
|
|
260
287
|
return {
|
|
261
288
|
retry: false,
|
|
262
289
|
warning: "context overflow: model has no contextWindow, auto-compaction is off",
|
|
@@ -314,7 +341,7 @@ export class CompactionController {
|
|
|
314
341
|
details: draft.details,
|
|
315
342
|
});
|
|
316
343
|
}
|
|
317
|
-
async summarize(trigger, signal, instructions, cutAt) {
|
|
344
|
+
async summarize(trigger, signal, instructions, cutAt, continuation) {
|
|
318
345
|
const core = this.core;
|
|
319
346
|
this.compacting = true;
|
|
320
347
|
core.emit({ type: "compaction_start", trigger });
|
|
@@ -366,7 +393,7 @@ export class CompactionController {
|
|
|
366
393
|
if (plan === undefined)
|
|
367
394
|
return fail("nothing to compact");
|
|
368
395
|
try {
|
|
369
|
-
const options = await this.summarizer(signal, customInstructions, trigger
|
|
396
|
+
const options = await this.summarizer(signal, customInstructions, trigger === "overflow" ? false : (continuation ?? true));
|
|
370
397
|
const draft = await runCompaction(plan, options);
|
|
371
398
|
if (signal.aborted)
|
|
372
399
|
return fail("aborted", true);
|
|
@@ -411,3 +438,16 @@ export class CompactionController {
|
|
|
411
438
|
}
|
|
412
439
|
}
|
|
413
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* 首次请求前将要发送的首条 `system` 消息(系统提示 + 工具声明,与真正发送时同样装配)。只用于统计:
|
|
443
|
+
* `getStats` 的启动基线与 `/context` 的分类明细共用它,口径一致;不写分支、不改请求体。
|
|
444
|
+
*/
|
|
445
|
+
export function pendingPrefixMessage(core) {
|
|
446
|
+
const tools = core
|
|
447
|
+
.activeToolNames()
|
|
448
|
+
.map((name) => core.activeTool(name))
|
|
449
|
+
.filter((tool) => tool !== undefined);
|
|
450
|
+
const system = core.childBase?.().system ?? core.options.system ?? {};
|
|
451
|
+
const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
|
|
452
|
+
return prefixSystemMessage(sections, toolDecls(tools));
|
|
453
|
+
}
|
|
@@ -35,6 +35,8 @@ export interface AgentSessionOptions {
|
|
|
35
35
|
tools?: readonly ToolDefinition[];
|
|
36
36
|
/** 活动集;缺省 = tools 全部。 */
|
|
37
37
|
activeTools?: readonly string[];
|
|
38
|
+
/** [ME-C0] 留在请求工具表里、执行时拒绝的工具(fork 子会话的 tools / disallowed-tools)。 */
|
|
39
|
+
unavailableTools?: readonly string[];
|
|
38
40
|
/** 权限管线(B3);缺省 = 全部放行。 */
|
|
39
41
|
permission?: PermissionPipelineApi;
|
|
40
42
|
/** 审批回答者链:宿主 broker → UI broker;都返回 undefined → deny(无人值守)。 */
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - 最终失败后若 followUp 队列非空,照常投递。
|
|
13
13
|
* - [W5-H2] 模型回退:可重试错误在 overloaded 时、或重试用尽后,若配置了 `fallbackModel` 就切过去
|
|
14
14
|
* 重试一次本请求(`model_fallback` 事件),回退模型回复后切回主模型;每个周期最多一次。
|
|
15
|
+
* [ME-C] overloaded 先快速重试一次(1 s 基数 + 抖动,计一次 attempt),仍失败才回退(D8)。
|
|
16
|
+
* - [ME-C] 退避取 `retryDelayMs(attempt, settings, 失败消息的 retryAfterMs)`;限流(rate_limited)的
|
|
17
|
+
* 重试上限 = maxRetries + 2,`auto_retry_start.maxAttempts` 随之。
|
|
15
18
|
* - [W5-H2] 扩展经 `noteSettleWarning` 登记的 warning(预算到限 `limit_reached`)写进本周期的 agent_settled;
|
|
16
19
|
* `limit_reached:` 开头的错误回复(预算拦下请求)不重试。
|
|
17
20
|
* - [W5-H2] run 带 `warning`(重复调用检测 `repeated_tool_call`):不跑 Stop Hook,agent_settled 带该 warning。
|
|
@@ -19,6 +22,7 @@
|
|
|
19
22
|
import type { AssistantMessage, ImageBlock, UserMessage } from "../ai/types.js";
|
|
20
23
|
import type { AgentMessage } from "../session/types.js";
|
|
21
24
|
import type { RunOutcome } from "./loop.js";
|
|
25
|
+
import { type FailureKind } from "./retry.js";
|
|
22
26
|
import type { SessionCore } from "./session-core.js";
|
|
23
27
|
import type { CompactionController } from "./session-compaction.js";
|
|
24
28
|
import type { PromptDisposition, RetrySettings } from "./types.js";
|
|
@@ -34,6 +38,12 @@ export type RunDecision = {
|
|
|
34
38
|
} | {
|
|
35
39
|
kind: "retry";
|
|
36
40
|
errorMessage: string;
|
|
41
|
+
/** [ME-C] 本次失败的种类(决定重试上限)。 */
|
|
42
|
+
failure?: FailureKind;
|
|
43
|
+
/** [ME-C] overloaded 回退前的快速重试:1 s 基数。 */
|
|
44
|
+
quick?: boolean;
|
|
45
|
+
/** [ME-C] 失败响应的 Retry-After(毫秒)。 */
|
|
46
|
+
retryAfterMs?: number;
|
|
37
47
|
} | {
|
|
38
48
|
kind: "overflow";
|
|
39
49
|
} | {
|
|
@@ -61,6 +71,8 @@ export interface RunCycleDeps {
|
|
|
61
71
|
afterSettled?(): Promise<void>;
|
|
62
72
|
}
|
|
63
73
|
export declare function decideAfterRun(deps: RunCycleDeps, outcome: RunOutcome, retryAttempt: number, overflowRecovered: boolean, fallbackUsed?: boolean): RunDecision;
|
|
74
|
+
/** [ME-C] overloaded 快速重试的退避基数(设置的基数更小时取设置)。 */
|
|
75
|
+
export declare const QUICK_RETRY_BASE_MS = 1000;
|
|
64
76
|
/** 用 context_edit 把失败尝试(助手消息与其后补的结果)从上下文剔除。 */
|
|
65
77
|
export declare function excludeFailedAttempt(core: SessionCore, failed: AssistantMessage | undefined, reason: "retry" | "overflow"): void;
|
|
66
78
|
export declare function recordAbort(core: SessionCore): void;
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
* - 最终失败后若 followUp 队列非空,照常投递。
|
|
13
13
|
* - [W5-H2] 模型回退:可重试错误在 overloaded 时、或重试用尽后,若配置了 `fallbackModel` 就切过去
|
|
14
14
|
* 重试一次本请求(`model_fallback` 事件),回退模型回复后切回主模型;每个周期最多一次。
|
|
15
|
+
* [ME-C] overloaded 先快速重试一次(1 s 基数 + 抖动,计一次 attempt),仍失败才回退(D8)。
|
|
16
|
+
* - [ME-C] 退避取 `retryDelayMs(attempt, settings, 失败消息的 retryAfterMs)`;限流(rate_limited)的
|
|
17
|
+
* 重试上限 = maxRetries + 2,`auto_retry_start.maxAttempts` 随之。
|
|
15
18
|
* - [W5-H2] 扩展经 `noteSettleWarning` 登记的 warning(预算到限 `limit_reached`)写进本周期的 agent_settled;
|
|
16
19
|
* `limit_reached:` 开头的错误回复(预算拦下请求)不重试。
|
|
17
20
|
* - [W5-H2] run 带 `warning`(重复调用检测 `repeated_tool_call`):不跑 Stop Hook,agent_settled 带该 warning。
|
|
18
21
|
*/
|
|
19
22
|
import { AmaError } from "../errors.js";
|
|
20
23
|
import { formatModelRef, modelRefOf } from "../ai/providers/channels.js";
|
|
21
|
-
import { classifyFailure, retryDelayMs, sleep } from "./retry.js";
|
|
24
|
+
import { classifyFailure, isRetryableKind, maxRetriesFor, retryDelayMs, sleep, } from "./retry.js";
|
|
22
25
|
export const MAX_STOP_HOOK_CONTINUATIONS = 3;
|
|
23
26
|
export const ABORTED_CUSTOM_TYPE = "ama.aborted";
|
|
24
27
|
/** [W5-H2] 预算拦下请求时错误回复的前缀(limits.ts):判为最终失败,不重试、不回退。 */
|
|
@@ -59,17 +62,34 @@ export function decideAfterRun(deps, outcome, retryAttempt, overflowRecovered, f
|
|
|
59
62
|
return recoverable ? { kind: "overflow" } : { kind: "failed", errorMessage };
|
|
60
63
|
}
|
|
61
64
|
const settings = deps.retry();
|
|
62
|
-
const canFallback = kind
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const canFallback = isRetryableKind(kind) && !fallbackUsed && fallbackTarget(deps.core) !== undefined;
|
|
66
|
+
const retry = { kind: "retry", errorMessage, failure: kind };
|
|
67
|
+
if (last.retryAfterMs !== undefined)
|
|
68
|
+
retry.retryAfterMs = last.retryAfterMs;
|
|
69
|
+
if (canFallback && OVERLOADED.test(errorMessage)) {
|
|
70
|
+
return retryAttempt === 0 && settings.enabled
|
|
71
|
+
? { ...retry, quick: true }
|
|
72
|
+
: { kind: "fallback", errorMessage };
|
|
73
|
+
}
|
|
74
|
+
if (isRetryableKind(kind) && settings.enabled && retryAttempt < maxRetriesFor(kind, settings)) {
|
|
75
|
+
return retry;
|
|
67
76
|
}
|
|
68
77
|
if (canFallback)
|
|
69
78
|
return { kind: "fallback", errorMessage };
|
|
70
79
|
return { kind: "failed", errorMessage };
|
|
71
80
|
}
|
|
72
81
|
const OVERLOADED = /overloaded/i;
|
|
82
|
+
/** [ME-C] overloaded 快速重试的退避基数(设置的基数更小时取设置)。 */
|
|
83
|
+
export const QUICK_RETRY_BASE_MS = 1000;
|
|
84
|
+
/** 本次重试的等待与上限(`auto_retry_start` 的 delayMs / maxAttempts)。 */
|
|
85
|
+
function retryPlan(decision, attempt, settings) {
|
|
86
|
+
const quickBase = Math.min(QUICK_RETRY_BASE_MS, settings.baseDelayMs);
|
|
87
|
+
const base = decision.quick === true ? { ...settings, baseDelayMs: quickBase } : settings;
|
|
88
|
+
return {
|
|
89
|
+
delayMs: retryDelayMs(decision.quick === true ? 1 : attempt, base, decision.retryAfterMs),
|
|
90
|
+
maxAttempts: maxRetriesFor(decision.failure ?? "retryable", settings),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
73
93
|
/** 配置了与当前模型不同、且能找到的回退模型时返回它的引用。 */
|
|
74
94
|
function fallbackTarget(core) {
|
|
75
95
|
const target = core.options.fallbackModel?.trim();
|
|
@@ -225,19 +245,18 @@ async function runSettledCycle(deps, prompts, signal) {
|
|
|
225
245
|
retryAttempt++;
|
|
226
246
|
deps.setRetrying(true);
|
|
227
247
|
excludeFailedAttempt(core, outcome.lastAssistant, "retry");
|
|
228
|
-
const
|
|
229
|
-
const delayMs = retryDelayMs(retryAttempt, settings);
|
|
248
|
+
const { delayMs, maxAttempts } = retryPlan(current, retryAttempt, deps.retry());
|
|
230
249
|
core.emit({
|
|
231
250
|
type: "auto_retry_start",
|
|
232
251
|
attempt: retryAttempt,
|
|
233
|
-
maxAttempts
|
|
252
|
+
maxAttempts,
|
|
234
253
|
delayMs,
|
|
235
254
|
errorMessage: current.errorMessage,
|
|
236
255
|
});
|
|
237
256
|
void core.runHook("Notification", {
|
|
238
257
|
notification: {
|
|
239
258
|
kind: "retry",
|
|
240
|
-
message: `retry ${retryAttempt}/${
|
|
259
|
+
message: `retry ${retryAttempt}/${maxAttempts}: ${current.errorMessage}`,
|
|
241
260
|
},
|
|
242
261
|
});
|
|
243
262
|
try {
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* 声明了 `tools` / `disallowed-tools`(或调用参数给了 `tools`)时工具表才不同。
|
|
12
12
|
* - 只读类型(`permission-mode: plan`):子会话用一条 plan 模式的权限管线(规则同父),ask 一律转
|
|
13
13
|
* deny——写类工具与识别不了的 bash 被拒且不弹审批;`inherit` 共用父的管线(不会比父宽)。
|
|
14
|
-
* - 轮数耗尽且最后停在工具结果上:以 `
|
|
14
|
+
* - 轮数耗尽且最后停在工具结果上:以 `maxTurns: 1` 再跑一轮要最终报告(只靠提示、不发 `toolChoice`,
|
|
15
|
+
* [ME-A] D13 请求选项不变才能命中缓存;仍调用工具则结果回落为「没有文本」),状态 `max_turns`。
|
|
16
|
+
* - [ME-A] `context: "fork"`(D1–D3,subagent-fork.ts):子会话复制父分支到发起调用的 assistant 之前,
|
|
17
|
+
* 不写 `role` 节、工具表与父相同、类型限制改由 `unavailableTools` 在执行层拒绝;任务以 `<task>` 消息
|
|
18
|
+
* 追加在继承的历史之后。不满足条件时回落 fresh(记 info 日志),结果的 `context` 标实际模式。
|
|
15
19
|
* - broker 包一层,请求带 `context{depth, parentToolCallId, taskId}`,审批事件转发到父会话;
|
|
16
20
|
* [RW-B] 父的检查点钩子(worktree 里运行时不记);[W3-C1b] 子会话命中与重计费汇总进父。
|
|
17
21
|
* - [W6-A] 句柄外露 `observe` / `entries`(子 Agent 视图跟随)与 `message`(视图里直接发的消息,
|
|
@@ -23,6 +27,7 @@ import { SessionManager } from "../session/manager.js";
|
|
|
23
27
|
import type { AgentMessage } from "../session/types.js";
|
|
24
28
|
import type { MessageOrigin } from "../ai/types.js";
|
|
25
29
|
import type { SubagentRequest, SubagentResult, SubagentRunner } from "../tools/types.js";
|
|
30
|
+
import type { RequestRecord } from "../ai/cache/types.js";
|
|
26
31
|
import type { AgentSessionOptions } from "./session-core.js";
|
|
27
32
|
import { DEFAULT_SUBAGENT_CONCURRENCY, SubagentPool, TASK_CUSTOM_TYPE, type AmaRunnerSpec, type RegistryHost } from "./subagent-registry.js";
|
|
28
33
|
import type { SessionEvent, SessionStats } from "./types.js";
|
|
@@ -47,6 +52,8 @@ export interface SubagentParent extends RegistryHost {
|
|
|
47
52
|
cacheRead: number;
|
|
48
53
|
prompt: number;
|
|
49
54
|
}, reBilledTokens: number): void;
|
|
55
|
+
/** [ME-A] 父会话上一次真实请求(fork 的前提与上下文比例)。 */
|
|
56
|
+
readonly lastTurn?: RequestRecord | undefined;
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
export interface ChildSession {
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* 声明了 `tools` / `disallowed-tools`(或调用参数给了 `tools`)时工具表才不同。
|
|
12
12
|
* - 只读类型(`permission-mode: plan`):子会话用一条 plan 模式的权限管线(规则同父),ask 一律转
|
|
13
13
|
* deny——写类工具与识别不了的 bash 被拒且不弹审批;`inherit` 共用父的管线(不会比父宽)。
|
|
14
|
-
* - 轮数耗尽且最后停在工具结果上:以 `
|
|
14
|
+
* - 轮数耗尽且最后停在工具结果上:以 `maxTurns: 1` 再跑一轮要最终报告(只靠提示、不发 `toolChoice`,
|
|
15
|
+
* [ME-A] D13 请求选项不变才能命中缓存;仍调用工具则结果回落为「没有文本」),状态 `max_turns`。
|
|
16
|
+
* - [ME-A] `context: "fork"`(D1–D3,subagent-fork.ts):子会话复制父分支到发起调用的 assistant 之前,
|
|
17
|
+
* 不写 `role` 节、工具表与父相同、类型限制改由 `unavailableTools` 在执行层拒绝;任务以 `<task>` 消息
|
|
18
|
+
* 追加在继承的历史之后。不满足条件时回落 fresh(记 info 日志),结果的 `context` 标实际模式。
|
|
15
19
|
* - broker 包一层,请求带 `context{depth, parentToolCallId, taskId}`,审批事件转发到父会话;
|
|
16
20
|
* [RW-B] 父的检查点钩子(worktree 里运行时不记);[W3-C1b] 子会话命中与重计费汇总进父。
|
|
17
21
|
* - [W6-A] 句柄外露 `observe` / `entries`(子 Agent 视图跟随)与 `message`(视图里直接发的消息,
|
|
@@ -22,6 +26,7 @@ import { PermissionPipeline } from "../permissions/pipeline.js";
|
|
|
22
26
|
import { SessionManager } from "../session/manager.js";
|
|
23
27
|
import { agentRole } from "../agents/builtin.js";
|
|
24
28
|
import { ZERO_USAGE } from "./loop.js";
|
|
29
|
+
import { forkBrief, forkPlan, requestedContext } from "./subagent-fork.js";
|
|
25
30
|
import { DEFAULT_SUBAGENT_CONCURRENCY, SubagentPool, TASK_CUSTOM_TYPE, subagentRegistryFor, } from "./subagent-registry.js";
|
|
26
31
|
export { DEFAULT_SUBAGENT_CONCURRENCY, SubagentPool, TASK_CUSTOM_TYPE };
|
|
27
32
|
export const DEFAULT_SUBAGENT_MAX_TURNS = 30;
|
|
@@ -130,22 +135,29 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
130
135
|
throw new AmaError("model_not_found", `unknown model ${spec.modelRef}`);
|
|
131
136
|
model = lookup.model;
|
|
132
137
|
}
|
|
133
|
-
const
|
|
138
|
+
const thinkingLevel = spec.request.thinkingLevel ?? spec.agent.thinking ?? base.thinkingLevel ?? "off";
|
|
139
|
+
const wanted = spec.resumeFile === undefined && requestedContext(spec.request, spec.agent) === "fork";
|
|
140
|
+
const plan = wanted ? forkPlan(parent, spec, model, thinkingLevel) : undefined;
|
|
141
|
+
if (plan !== undefined && "fallback" in plan)
|
|
142
|
+
parent.log("info", `task ${spec.taskId}: fork falls back to fresh (${plan.fallback})`);
|
|
143
|
+
const forked = plan !== undefined && "manager" in plan ? plan.manager : undefined;
|
|
144
|
+
const mode = !wanted ? undefined : forked ? "fork" : "fresh";
|
|
145
|
+
const manager = forked ?? childManager(parent, spec);
|
|
134
146
|
const checkpointHooks = spec.isolated ? undefined : parent.checkpointHooks?.();
|
|
135
|
-
let finalRound = false;
|
|
136
|
-
const finalRoundExtension = () => ({
|
|
137
|
-
id: "ama.subagent-final-round",
|
|
138
|
-
wrapStream: (stream) => (m, context, options) => stream(m, context, finalRound ? { ...options, toolChoice: "none" } : options),
|
|
139
|
-
});
|
|
140
147
|
const maxTurns = spec.request.maxTurns ?? spec.agent.maxTurns ?? DEFAULT_SUBAGENT_MAX_TURNS;
|
|
148
|
+
const allowed = childToolNames(parent, spec, base.activeTools);
|
|
149
|
+
// fork:工具表 = 父活动集(前缀不变),类型限制的工具在执行层拒绝
|
|
150
|
+
const inherited = base.activeTools ?? (parent.options.tools ?? []).map((tool) => tool.name);
|
|
151
|
+
const unavailable = inherited.filter((name) => !allowed.includes(name) && !PARENT_ONLY_TOOLS.includes(name));
|
|
141
152
|
const options = {
|
|
142
153
|
...parent.options,
|
|
143
154
|
...base,
|
|
144
|
-
|
|
155
|
+
// fork:系统提示与父相同(base.system,不加 role 节)
|
|
156
|
+
...(forked === undefined ? { system: { ...base.system, role: agentRole(spec.agent) } } : {}),
|
|
145
157
|
sessionManager: manager,
|
|
146
158
|
model,
|
|
147
|
-
thinkingLevel
|
|
148
|
-
activeTools:
|
|
159
|
+
thinkingLevel,
|
|
160
|
+
...(forked === undefined ? { activeTools: allowed } : {}),
|
|
149
161
|
brokers: brokersForChild(parent.options.brokers, {
|
|
150
162
|
depth: parent.depth + 1,
|
|
151
163
|
parentToolCallId: spec.request.parentToolCallId,
|
|
@@ -154,8 +166,9 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
154
166
|
depth: parent.depth + 1,
|
|
155
167
|
maxTurns,
|
|
156
168
|
subagents: false,
|
|
157
|
-
extensions: [...(parent.options.extensions ?? []), finalRoundExtension],
|
|
158
169
|
};
|
|
170
|
+
if (forked !== undefined && unavailable.length > 0)
|
|
171
|
+
options.unavailableTools = [...(parent.options.unavailableTools ?? []), ...unavailable];
|
|
159
172
|
delete options.checkpointHooks;
|
|
160
173
|
if (checkpointHooks !== undefined)
|
|
161
174
|
options.checkpointHooks = checkpointHooks;
|
|
@@ -182,6 +195,8 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
182
195
|
else if (event.type === "permission_request" || event.type === "permission_resolved")
|
|
183
196
|
parent.emit(event);
|
|
184
197
|
});
|
|
198
|
+
if (mode !== undefined)
|
|
199
|
+
run.onEvent({ type: "context", mode });
|
|
185
200
|
const onAbort = () => void child.abort();
|
|
186
201
|
run.signal.addEventListener("abort", onAbort, { once: true });
|
|
187
202
|
let billed = { cacheRead: 0, prompt: 0, reBilled: 0 };
|
|
@@ -212,7 +227,6 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
212
227
|
active = false;
|
|
213
228
|
const exhausted = error === undefined && !run.signal.aborted && turns - startTurns >= maxTurns && toolUse;
|
|
214
229
|
if (exhausted) {
|
|
215
|
-
finalRound = true;
|
|
216
230
|
options.maxTurns = 1;
|
|
217
231
|
try {
|
|
218
232
|
await child.prompt(FINAL_REPORT_PROMPT);
|
|
@@ -221,18 +235,27 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
221
235
|
error = caught instanceof Error ? caught.message : String(caught);
|
|
222
236
|
}
|
|
223
237
|
finally {
|
|
224
|
-
finalRound = false;
|
|
225
238
|
options.maxTurns = maxTurns;
|
|
226
239
|
}
|
|
227
240
|
}
|
|
228
241
|
const result = collect(child, error, exhausted, run.signal.aborted);
|
|
242
|
+
if (mode !== undefined)
|
|
243
|
+
result.context = mode;
|
|
229
244
|
billed = addCache(parent, child.getStats(), billed);
|
|
230
245
|
const file = manager.file();
|
|
231
246
|
if (file !== undefined)
|
|
232
247
|
result.sessionFile = file;
|
|
233
248
|
return result;
|
|
234
249
|
};
|
|
235
|
-
|
|
250
|
+
const first = forked === undefined
|
|
251
|
+
? run.prompt
|
|
252
|
+
: forkBrief({
|
|
253
|
+
prompt: run.prompt,
|
|
254
|
+
role: spec.agent.prompt,
|
|
255
|
+
unavailable,
|
|
256
|
+
...(spec.isolated ? { worktree: { cwd: spec.cwd, parentCwd: parent.cwd } } : {}),
|
|
257
|
+
});
|
|
258
|
+
let current = runOnce(first, spec.origin);
|
|
236
259
|
const handle = {
|
|
237
260
|
id: manager.id,
|
|
238
261
|
model: `${model.provider}/${model.id}`,
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* - `persistMessage`:Agent 的 message_end → 会话条目(LLM 消息 → `message`;custom → `custom_message`)。
|
|
5
5
|
* - `recordModelState`:路径上最近的 model_change / thinking_level_change 与当前不同则补记。
|
|
6
6
|
* - `syncSystemMessage`:目标系统提示 + 工具表与转录重放的状态比对,落全量(首条)或补丁;
|
|
7
|
-
*
|
|
7
|
+
* 对话开始后 tools / rules 节冻结为已发送的文本([ME-B] D5:工具增删只经请求工具表与尾部提醒体现,
|
|
8
|
+
* 开头永不改写,§9.1)。
|
|
8
9
|
* - `runHookWithEvents`:调 HookDispatcher(公共字段按本会话的 depth / sessionId / sessionFile 覆盖),
|
|
9
10
|
* 逐条发 hook_executed;Hook 自身出错记 warning、按无决策处理。
|
|
10
11
|
*/
|
|
@@ -15,13 +16,18 @@ import type { ToolDefinition } from "../tools/types.js";
|
|
|
15
16
|
import type { SessionManager } from "../session/manager.js";
|
|
16
17
|
import type { SessionCore } from "./session-core.js";
|
|
17
18
|
import type { SessionEvent } from "./types.js";
|
|
18
|
-
import { type SystemPromptInput } from "./system-prompt.js";
|
|
19
|
+
import { currentSystemState, type SystemPromptInput } from "./system-prompt.js";
|
|
19
20
|
export declare function persistMessage(core: SessionCore, message: AgentMessage): SessionEntry | undefined;
|
|
20
21
|
export declare function recordModelState(core: SessionCore, model: Model, thinkingLevel: ModelThinkingLevel): void;
|
|
21
22
|
/** 落一条 model_change,返回模型引用(`setModel` 发 model_changed 用)。 */
|
|
22
23
|
export declare function appendModelChange(core: SessionCore, model: Model): ModelRef;
|
|
23
24
|
export declare function findModelOrThrow(providers: ProviderRegistryApi, ref: string): Model;
|
|
24
25
|
export declare function syncSystemMessage(core: SessionCore, input: Omit<SystemPromptInput, "tools" | "cwd">, tools: readonly ToolDefinition[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* 对话开始后保留已发送的 tools / rules 节:新工具的声明经请求工具表末尾追加,移除的工具保留声明、
|
|
28
|
+
* 尾部提醒「no longer available」,system 文本不变,前缀缓存不失效(设计 §9.1、[ME-B] D5)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function keepSectionsAfterStart(current: NonNullable<ReturnType<typeof currentSystemState>>, sections: Record<string, string>): void;
|
|
25
31
|
export declare function runHookWithEvents(core: Pick<SessionCore, "options" | "emit" | "log" | "depth" | "manager">, event: HookEvent, payload: HookEventPayload, signal?: AbortSignal): Promise<HookOutcome | undefined>;
|
|
26
32
|
/** session_start 事件(bootstrap / SDK 在会话就绪后发一次)。 */
|
|
27
33
|
export declare function sessionStartEvent(manager: SessionManager, reason: "startup" | "resume" | "new" | "fork"): SessionEvent;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* - `persistMessage`:Agent 的 message_end → 会话条目(LLM 消息 → `message`;custom → `custom_message`)。
|
|
5
5
|
* - `recordModelState`:路径上最近的 model_change / thinking_level_change 与当前不同则补记。
|
|
6
6
|
* - `syncSystemMessage`:目标系统提示 + 工具表与转录重放的状态比对,落全量(首条)或补丁;
|
|
7
|
-
*
|
|
7
|
+
* 对话开始后 tools / rules 节冻结为已发送的文本([ME-B] D5:工具增删只经请求工具表与尾部提醒体现,
|
|
8
|
+
* 开头永不改写,§9.1)。
|
|
8
9
|
* - `runHookWithEvents`:调 HookDispatcher(公共字段按本会话的 depth / sessionId / sessionFile 覆盖),
|
|
9
10
|
* 逐条发 hook_executed;Hook 自身出错记 warning、按无决策处理。
|
|
10
11
|
*/
|
|
@@ -71,8 +72,8 @@ export function syncSystemMessage(core, input, tools) {
|
|
|
71
72
|
recordModelState(core, core.model(), core.thinkingLevel());
|
|
72
73
|
const sections = definedSections(assembleSections({ ...input, tools, cwd: core.cwd }));
|
|
73
74
|
const current = currentSystemState(core.agent.messages);
|
|
74
|
-
if (current !== undefined)
|
|
75
|
-
|
|
75
|
+
if (current !== undefined && core.agent.messages.some((message) => message.role !== "system"))
|
|
76
|
+
keepSectionsAfterStart(current, sections);
|
|
76
77
|
const patch = diffSystem(current, sections, toolDecls(tools));
|
|
77
78
|
if (patch === undefined)
|
|
78
79
|
return;
|
|
@@ -80,14 +81,10 @@ export function syncSystemMessage(core, input, tools) {
|
|
|
80
81
|
core.agent.messages.push(patch);
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* 有工具被移除时照常重写这两节。
|
|
84
|
+
* 对话开始后保留已发送的 tools / rules 节:新工具的声明经请求工具表末尾追加,移除的工具保留声明、
|
|
85
|
+
* 尾部提醒「no longer available」,system 文本不变,前缀缓存不失效(设计 §9.1、[ME-B] D5)。
|
|
86
86
|
*/
|
|
87
|
-
function
|
|
88
|
-
const now = new Set(tools.map((tool) => tool.name));
|
|
89
|
-
if (current.tools.some((tool) => !now.has(tool.name)))
|
|
90
|
-
return;
|
|
87
|
+
export function keepSectionsAfterStart(current, sections) {
|
|
91
88
|
for (const name of ["tools", "rules"]) {
|
|
92
89
|
const sent = current.sections[name];
|
|
93
90
|
if (sent === undefined)
|
|
@@ -16,6 +16,7 @@ import { previewAction } from "../permissions/preview.js";
|
|
|
16
16
|
import { PermissionPipeline } from "../permissions/pipeline.js";
|
|
17
17
|
import { classifierRequest } from "./session-classifier.js";
|
|
18
18
|
import { runSingleToolCall } from "./tool-runner.js";
|
|
19
|
+
import { toolUnavailableText } from "./tool-availability.js";
|
|
19
20
|
/** 外层是这个工具时,嵌套调用的 Hook 输入带 `viaCodemode: true`。 */
|
|
20
21
|
const CODEMODE_TOOL_NAME = "codemode";
|
|
21
22
|
function textOf(content) {
|
|
@@ -128,8 +129,8 @@ function safePreview(core, request) {
|
|
|
128
129
|
}
|
|
129
130
|
export async function gateToolCall(core, call, ctx) {
|
|
130
131
|
const tool = ctx.tool;
|
|
131
|
-
if (tool === undefined)
|
|
132
|
-
return { block: true, reason:
|
|
132
|
+
if (tool === undefined || core.options.unavailableTools?.includes(call.name) === true)
|
|
133
|
+
return { block: true, reason: toolUnavailableText(call.name) };
|
|
133
134
|
let input = call.arguments;
|
|
134
135
|
let replaced = false;
|
|
135
136
|
let hookDecision;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fork 式子 Agent(docs/model-efficiency-plan.md §2.1,D1–D3)。[ME-A]
|
|
3
|
+
*
|
|
4
|
+
* - fork 点 = 发出本次 `task` 调用的那条 assistant **之前一条**条目(= 父会话上一次真实请求的转录);
|
|
5
|
+
* 同一条 assistant 里并行的多个 fork 任务共享同一个 fork 点。
|
|
6
|
+
* - `forkPlan`:满足条件时用 `SessionManager.fork(entryId, { head })` 复制分支,`head` 是
|
|
7
|
+
* `custom{ama.task, context:"fork", forkedFrom}`(子会话首条仍是 `ama.task`);否则给出回落原因:
|
|
8
|
+
* 与父不同的模型 / 思考级别、父还没有真实请求、父上一次请求超过 `FORK_MAX_CONTEXT_RATIO` ×
|
|
9
|
+
* (窗口 − reserveTokens)、找不到发起调用的 assistant。续聊与 resume 不经过这里(照原文件重开)。
|
|
10
|
+
* - `forkBrief`:角色说明 + 任务合成一条 user 消息(固定英文),追加在继承的历史之后;子会话不写
|
|
11
|
+
* `role` 节,工具表与父逐字节相同,类型限制的工具在执行层拒绝(`unavailableTools`)。
|
|
12
|
+
*/
|
|
13
|
+
import type { Model, ModelThinkingLevel } from "../ai/types.js";
|
|
14
|
+
import type { AgentDefinition } from "../agents/types.js";
|
|
15
|
+
import type { SessionManager } from "../session/manager.js";
|
|
16
|
+
import type { SessionEntry } from "../session/types.js";
|
|
17
|
+
import type { SubagentRequest } from "../tools/types.js";
|
|
18
|
+
import type { AgentSessionOptions } from "./session-core.js";
|
|
19
|
+
/** 父上一次请求的输入 token 超过(子模型窗口 − reserveTokens)的这个比例时回落 fresh。 */
|
|
20
|
+
export declare const FORK_MAX_CONTEXT_RATIO = 0.5;
|
|
21
|
+
export type ContextMode = "fork" | "fresh";
|
|
22
|
+
/** 请求的上下文模式:调用参数 > 类型 frontmatter > `fresh`。 */
|
|
23
|
+
export declare function requestedContext(request: Pick<SubagentRequest, "context">, agent: Pick<AgentDefinition, "context">): ContextMode;
|
|
24
|
+
/** 父分支里发出 `parentToolCallId` 的 assistant 之前一条条目的 id;找不到(或它是首条)返回 undefined。 */
|
|
25
|
+
export declare function forkPoint(branch: readonly SessionEntry[], parentToolCallId: string): string | undefined;
|
|
26
|
+
export interface ForkParent {
|
|
27
|
+
readonly manager: SessionManager;
|
|
28
|
+
readonly options: Pick<AgentSessionOptions, "compaction">;
|
|
29
|
+
readonly cache?: {
|
|
30
|
+
readonly lastTurn?: {
|
|
31
|
+
readonly promptTokens: number;
|
|
32
|
+
} | undefined;
|
|
33
|
+
};
|
|
34
|
+
childBase(): Pick<AgentSessionOptions, "model" | "thinkingLevel">;
|
|
35
|
+
}
|
|
36
|
+
export interface ForkSpec {
|
|
37
|
+
taskId: string;
|
|
38
|
+
agent: Pick<AgentDefinition, "name">;
|
|
39
|
+
request: Pick<SubagentRequest, "parentToolCallId" | "description">;
|
|
40
|
+
/** 子会话 cwd(隔离时是 worktree)。 */
|
|
41
|
+
cwd: string;
|
|
42
|
+
}
|
|
43
|
+
export type ForkPlan = {
|
|
44
|
+
manager: SessionManager;
|
|
45
|
+
forkedFrom: string;
|
|
46
|
+
} | {
|
|
47
|
+
fallback: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* D3 的回落条件依次检查,全部通过才复制分支(复制即落盘:父有文件时子会话文件立即写出)。
|
|
51
|
+
* `model` / `thinking` 是子会话按类型与参数解析出的值。
|
|
52
|
+
*/
|
|
53
|
+
export declare function forkPlan(parent: ForkParent, spec: ForkSpec, model: Model, thinking: ModelThinkingLevel): ForkPlan;
|
|
54
|
+
export interface ForkBriefInput {
|
|
55
|
+
prompt: string;
|
|
56
|
+
/** 类型正文(不含通用前言:这里的首句已说明子 Agent 身份)。 */
|
|
57
|
+
role: string;
|
|
58
|
+
/** 不可用(执行层拒绝)的工具名。 */
|
|
59
|
+
unavailable: readonly string[];
|
|
60
|
+
/** 隔离运行时:子会话 cwd 与父 cwd。 */
|
|
61
|
+
worktree?: {
|
|
62
|
+
cwd: string;
|
|
63
|
+
parentCwd: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** fork 子会话的首条 user 消息(固定英文,发给模型)。 */
|
|
67
|
+
export declare function forkBrief(input: ForkBriefInput): string;
|