@x-otto/agent 0.0.1-alpha.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/README.md +73 -0
- package/dist/index.d.ts +1625 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1625 @@
|
|
|
1
|
+
import * as _$_x_otto_shared0 from "@x-otto/shared";
|
|
2
|
+
import { AgentError, Logger, TypedEventEmitter } from "@x-otto/shared";
|
|
3
|
+
import { AssistantMessage, Message, Model, StreamEvent, SystemNotificationMessage, ThinkingLevel, ToolCall, ToolResultMessage, Usage, extractToolResultText } from "@x-otto/interchange";
|
|
4
|
+
import { CacheRetention, StreamFunction } from "@x-otto/provider";
|
|
5
|
+
import { AgentMessage, AgentMessage as AgentMessage$1, AgentSessionEvent, AgentSessionEventMap, AgentSessionEventType, AgentSessionSubscriber, AgentTool, AgentTool as AgentTool$1, ApprovalRisk, ApprovalRisk as ApprovalRisk$1, AskUserQuestion, CustomAgentMessages, GrillAnswer, GrillOption, GrillQuestion, GrillRequest, HookInput, HookOutput, InterceptorTiming, ObserverTiming, ToolCallContext, ToolCallEvent, ToolCallEvent as ToolCallEvent$1, ToolResult, ToolResult as ToolResult$1 } from "@x-otto/hook-contracts";
|
|
6
|
+
|
|
7
|
+
//#region src/engine-nodes.d.ts
|
|
8
|
+
type EngineNodeCategory = 'agent_work_loop' | 'work_loop_injection' | 'tool_executor' | 'session_prompt_lifecycle' | 'lifecycle_hook' | 'turn_observable';
|
|
9
|
+
interface EngineNodeDef {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly category: EngineNodeCategory;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly pausable?: boolean;
|
|
14
|
+
readonly hook?: 'interceptor' | 'observer';
|
|
15
|
+
readonly observable?: boolean;
|
|
16
|
+
readonly traceable?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const ENGINE_NODES: readonly [{
|
|
19
|
+
readonly id: "loop.start";
|
|
20
|
+
readonly category: "agent_work_loop";
|
|
21
|
+
readonly label: "Loop Start";
|
|
22
|
+
readonly pausable: true;
|
|
23
|
+
readonly traceable: true;
|
|
24
|
+
}, {
|
|
25
|
+
readonly id: "model.before";
|
|
26
|
+
readonly category: "agent_work_loop";
|
|
27
|
+
readonly label: "Model Before";
|
|
28
|
+
readonly pausable: true;
|
|
29
|
+
}, {
|
|
30
|
+
readonly id: "model.after";
|
|
31
|
+
readonly category: "agent_work_loop";
|
|
32
|
+
readonly label: "Model After";
|
|
33
|
+
readonly pausable: true;
|
|
34
|
+
}, {
|
|
35
|
+
readonly id: "tool.before";
|
|
36
|
+
readonly category: "agent_work_loop";
|
|
37
|
+
readonly label: "Tool Before";
|
|
38
|
+
readonly pausable: true;
|
|
39
|
+
}, {
|
|
40
|
+
readonly id: "tool.after";
|
|
41
|
+
readonly category: "agent_work_loop";
|
|
42
|
+
readonly label: "Tool After";
|
|
43
|
+
readonly pausable: true;
|
|
44
|
+
}, {
|
|
45
|
+
readonly id: "loop.end";
|
|
46
|
+
readonly category: "agent_work_loop";
|
|
47
|
+
readonly label: "Loop End";
|
|
48
|
+
readonly pausable: true;
|
|
49
|
+
readonly traceable: true;
|
|
50
|
+
}, {
|
|
51
|
+
readonly id: "loop.cleanup";
|
|
52
|
+
readonly category: "agent_work_loop";
|
|
53
|
+
readonly label: "Loop Cleanup";
|
|
54
|
+
readonly pausable: true;
|
|
55
|
+
}, {
|
|
56
|
+
readonly id: "agent.aborted";
|
|
57
|
+
readonly category: "agent_work_loop";
|
|
58
|
+
readonly label: "Agent Aborted";
|
|
59
|
+
readonly pausable: true;
|
|
60
|
+
readonly traceable: true;
|
|
61
|
+
}, {
|
|
62
|
+
readonly id: "agent.error";
|
|
63
|
+
readonly category: "agent_work_loop";
|
|
64
|
+
readonly label: "Agent Error";
|
|
65
|
+
readonly pausable: true;
|
|
66
|
+
readonly traceable: true;
|
|
67
|
+
}, {
|
|
68
|
+
readonly id: "agent.done";
|
|
69
|
+
readonly category: "agent_work_loop";
|
|
70
|
+
readonly label: "Agent Done";
|
|
71
|
+
readonly pausable: true;
|
|
72
|
+
readonly traceable: true;
|
|
73
|
+
}, {
|
|
74
|
+
readonly id: "steering.check";
|
|
75
|
+
readonly category: "work_loop_injection";
|
|
76
|
+
readonly label: "Steering Check";
|
|
77
|
+
readonly pausable: true;
|
|
78
|
+
}, {
|
|
79
|
+
readonly id: "follow.up.check";
|
|
80
|
+
readonly category: "work_loop_injection";
|
|
81
|
+
readonly label: "Follow Up Check";
|
|
82
|
+
readonly pausable: true;
|
|
83
|
+
}, {
|
|
84
|
+
readonly id: "context.transform";
|
|
85
|
+
readonly category: "work_loop_injection";
|
|
86
|
+
readonly label: "Context Transform";
|
|
87
|
+
readonly pausable: true;
|
|
88
|
+
}, {
|
|
89
|
+
readonly id: "tool.resolve";
|
|
90
|
+
readonly category: "tool_executor";
|
|
91
|
+
readonly label: "Tool Resolve";
|
|
92
|
+
readonly pausable: true;
|
|
93
|
+
}, {
|
|
94
|
+
readonly id: "tool.validate";
|
|
95
|
+
readonly category: "tool_executor";
|
|
96
|
+
readonly label: "Tool Validate";
|
|
97
|
+
readonly pausable: true;
|
|
98
|
+
}, {
|
|
99
|
+
readonly id: "tool.hook.before";
|
|
100
|
+
readonly category: "tool_executor";
|
|
101
|
+
readonly label: "Tool Hook Before";
|
|
102
|
+
readonly pausable: true;
|
|
103
|
+
}, {
|
|
104
|
+
readonly id: "tool.hook.after";
|
|
105
|
+
readonly category: "tool_executor";
|
|
106
|
+
readonly label: "Tool Hook After";
|
|
107
|
+
readonly pausable: true;
|
|
108
|
+
}, {
|
|
109
|
+
readonly id: "prompt.before";
|
|
110
|
+
readonly category: "session_prompt_lifecycle";
|
|
111
|
+
readonly label: "Prompt Before";
|
|
112
|
+
readonly pausable: true;
|
|
113
|
+
readonly traceable: true;
|
|
114
|
+
}, {
|
|
115
|
+
readonly id: "prompt.after";
|
|
116
|
+
readonly category: "session_prompt_lifecycle";
|
|
117
|
+
readonly label: "Prompt After";
|
|
118
|
+
readonly pausable: true;
|
|
119
|
+
readonly traceable: true;
|
|
120
|
+
}, {
|
|
121
|
+
readonly id: "context.build";
|
|
122
|
+
readonly category: "session_prompt_lifecycle";
|
|
123
|
+
readonly label: "Context Build";
|
|
124
|
+
readonly pausable: true;
|
|
125
|
+
readonly traceable: true;
|
|
126
|
+
}, {
|
|
127
|
+
readonly id: "messages.persist";
|
|
128
|
+
readonly category: "session_prompt_lifecycle";
|
|
129
|
+
readonly label: "Messages Persist";
|
|
130
|
+
readonly pausable: true;
|
|
131
|
+
readonly traceable: true;
|
|
132
|
+
}, {
|
|
133
|
+
readonly id: "memory.prune";
|
|
134
|
+
readonly category: "work_loop_injection";
|
|
135
|
+
readonly label: "Memory Prune";
|
|
136
|
+
readonly traceable: true;
|
|
137
|
+
}, {
|
|
138
|
+
readonly id: "memory.archive";
|
|
139
|
+
readonly category: "session_prompt_lifecycle";
|
|
140
|
+
readonly label: "Memory Archive";
|
|
141
|
+
readonly traceable: true;
|
|
142
|
+
}, {
|
|
143
|
+
readonly id: "chat.message.before";
|
|
144
|
+
readonly category: "lifecycle_hook";
|
|
145
|
+
readonly label: "Chat Message Before";
|
|
146
|
+
readonly hook: "interceptor";
|
|
147
|
+
}, {
|
|
148
|
+
readonly id: "chat.message.after";
|
|
149
|
+
readonly category: "lifecycle_hook";
|
|
150
|
+
readonly label: "Chat Message After";
|
|
151
|
+
readonly hook: "interceptor";
|
|
152
|
+
}, {
|
|
153
|
+
readonly id: "chat.params";
|
|
154
|
+
readonly category: "lifecycle_hook";
|
|
155
|
+
readonly label: "Chat Params";
|
|
156
|
+
readonly hook: "interceptor";
|
|
157
|
+
}, {
|
|
158
|
+
readonly id: "system.prompt.transform";
|
|
159
|
+
readonly category: "lifecycle_hook";
|
|
160
|
+
readonly label: "System Prompt Transform";
|
|
161
|
+
readonly hook: "interceptor";
|
|
162
|
+
}, {
|
|
163
|
+
readonly id: "messages.transform";
|
|
164
|
+
readonly category: "lifecycle_hook";
|
|
165
|
+
readonly label: "Messages Transform";
|
|
166
|
+
readonly hook: "interceptor";
|
|
167
|
+
}, {
|
|
168
|
+
readonly id: "tool.execute.before";
|
|
169
|
+
readonly category: "lifecycle_hook";
|
|
170
|
+
readonly label: "Tool Execute Before";
|
|
171
|
+
readonly hook: "interceptor";
|
|
172
|
+
}, {
|
|
173
|
+
readonly id: "tool.execute.after";
|
|
174
|
+
readonly category: "lifecycle_hook";
|
|
175
|
+
readonly label: "Tool Execute After";
|
|
176
|
+
readonly hook: "interceptor";
|
|
177
|
+
}, {
|
|
178
|
+
readonly id: "memory.prune.before";
|
|
179
|
+
readonly category: "lifecycle_hook";
|
|
180
|
+
readonly label: "Memory Prune Before";
|
|
181
|
+
readonly hook: "interceptor";
|
|
182
|
+
}, {
|
|
183
|
+
readonly id: "session.idle";
|
|
184
|
+
readonly category: "lifecycle_hook";
|
|
185
|
+
readonly label: "Session Idle";
|
|
186
|
+
readonly hook: "observer";
|
|
187
|
+
}, {
|
|
188
|
+
readonly id: "session.error";
|
|
189
|
+
readonly category: "lifecycle_hook";
|
|
190
|
+
readonly label: "Session Error";
|
|
191
|
+
readonly hook: "observer";
|
|
192
|
+
}, {
|
|
193
|
+
readonly id: "status.change";
|
|
194
|
+
readonly category: "turn_observable";
|
|
195
|
+
readonly label: "Status Change";
|
|
196
|
+
readonly observable: true;
|
|
197
|
+
}, {
|
|
198
|
+
readonly id: "turn.start";
|
|
199
|
+
readonly category: "turn_observable";
|
|
200
|
+
readonly label: "Turn Start";
|
|
201
|
+
readonly observable: true;
|
|
202
|
+
readonly traceable: true;
|
|
203
|
+
}, {
|
|
204
|
+
readonly id: "turn.end";
|
|
205
|
+
readonly category: "turn_observable";
|
|
206
|
+
readonly label: "Turn End";
|
|
207
|
+
readonly observable: true;
|
|
208
|
+
readonly traceable: true;
|
|
209
|
+
}, {
|
|
210
|
+
readonly id: "stream.event";
|
|
211
|
+
readonly category: "turn_observable";
|
|
212
|
+
readonly label: "Stream Event";
|
|
213
|
+
readonly observable: true;
|
|
214
|
+
readonly traceable: true;
|
|
215
|
+
}, {
|
|
216
|
+
readonly id: "tool.call.start";
|
|
217
|
+
readonly category: "turn_observable";
|
|
218
|
+
readonly label: "Tool Call Start";
|
|
219
|
+
readonly observable: true;
|
|
220
|
+
readonly traceable: true;
|
|
221
|
+
}, {
|
|
222
|
+
readonly id: "tool.call.end";
|
|
223
|
+
readonly category: "turn_observable";
|
|
224
|
+
readonly label: "Tool Call End";
|
|
225
|
+
readonly observable: true;
|
|
226
|
+
readonly traceable: true;
|
|
227
|
+
}, {
|
|
228
|
+
readonly id: "steering.injected";
|
|
229
|
+
readonly category: "turn_observable";
|
|
230
|
+
readonly label: "Steering Injected";
|
|
231
|
+
readonly observable: true;
|
|
232
|
+
}, {
|
|
233
|
+
readonly id: "follow.up.start";
|
|
234
|
+
readonly category: "turn_observable";
|
|
235
|
+
readonly label: "Follow Up Start";
|
|
236
|
+
readonly observable: true;
|
|
237
|
+
}, {
|
|
238
|
+
readonly id: "max.turns";
|
|
239
|
+
readonly category: "turn_observable";
|
|
240
|
+
readonly label: "Max Tool Turns Reached";
|
|
241
|
+
readonly observable: true;
|
|
242
|
+
readonly traceable: true;
|
|
243
|
+
}, {
|
|
244
|
+
readonly id: "progress.stall";
|
|
245
|
+
readonly category: "turn_observable";
|
|
246
|
+
readonly label: "Progress Stall Detected";
|
|
247
|
+
readonly observable: true;
|
|
248
|
+
readonly traceable: true;
|
|
249
|
+
}, {
|
|
250
|
+
readonly id: "tool.call.mismatch";
|
|
251
|
+
readonly category: "turn_observable";
|
|
252
|
+
readonly label: "Tool Call Protocol Mismatch";
|
|
253
|
+
readonly observable: true;
|
|
254
|
+
readonly traceable: true;
|
|
255
|
+
}, {
|
|
256
|
+
readonly id: "prompt.budget";
|
|
257
|
+
readonly category: "turn_observable";
|
|
258
|
+
readonly label: "Prompt Budget Exceeded";
|
|
259
|
+
readonly observable: true;
|
|
260
|
+
readonly traceable: true;
|
|
261
|
+
}, {
|
|
262
|
+
readonly id: "compaction.reactive";
|
|
263
|
+
readonly category: "turn_observable";
|
|
264
|
+
readonly label: "Reactive Compaction";
|
|
265
|
+
readonly observable: true;
|
|
266
|
+
readonly traceable: true;
|
|
267
|
+
}, {
|
|
268
|
+
readonly id: "image.cap";
|
|
269
|
+
readonly category: "turn_observable";
|
|
270
|
+
readonly label: "Image Cap";
|
|
271
|
+
readonly traceable: true;
|
|
272
|
+
}, {
|
|
273
|
+
readonly id: "image.vision-gate";
|
|
274
|
+
readonly category: "turn_observable";
|
|
275
|
+
readonly label: "Vision Capability Gate";
|
|
276
|
+
readonly traceable: true;
|
|
277
|
+
}, {
|
|
278
|
+
readonly id: "image.vision-delegate";
|
|
279
|
+
readonly category: "turn_observable";
|
|
280
|
+
readonly label: "Vision Delegation Result";
|
|
281
|
+
readonly observable: true;
|
|
282
|
+
readonly traceable: true;
|
|
283
|
+
}, {
|
|
284
|
+
readonly id: "image.degradation";
|
|
285
|
+
readonly category: "turn_observable";
|
|
286
|
+
readonly label: "Image Degradation";
|
|
287
|
+
readonly observable: true;
|
|
288
|
+
readonly traceable: true;
|
|
289
|
+
}, {
|
|
290
|
+
readonly id: "process.memory.sample";
|
|
291
|
+
readonly category: "turn_observable";
|
|
292
|
+
readonly label: "Process Memory Sample";
|
|
293
|
+
readonly traceable: true;
|
|
294
|
+
}, {
|
|
295
|
+
readonly id: "stream.retry";
|
|
296
|
+
readonly category: "turn_observable";
|
|
297
|
+
readonly label: "Stream Retry";
|
|
298
|
+
readonly observable: true;
|
|
299
|
+
readonly traceable: true;
|
|
300
|
+
}, {
|
|
301
|
+
readonly id: "stream.retry.accelerated";
|
|
302
|
+
readonly category: "turn_observable";
|
|
303
|
+
readonly label: "Stream Retry Accelerated";
|
|
304
|
+
readonly observable: true;
|
|
305
|
+
readonly traceable: true;
|
|
306
|
+
}, {
|
|
307
|
+
readonly id: "turn.truncated";
|
|
308
|
+
readonly category: "turn_observable";
|
|
309
|
+
readonly label: "Turn Truncated";
|
|
310
|
+
readonly observable: true;
|
|
311
|
+
readonly traceable: true;
|
|
312
|
+
}, {
|
|
313
|
+
readonly id: "rate.limit.exhausted";
|
|
314
|
+
readonly category: "turn_observable";
|
|
315
|
+
readonly label: "Rate Limit Exhausted";
|
|
316
|
+
readonly observable: true;
|
|
317
|
+
readonly traceable: true;
|
|
318
|
+
}, {
|
|
319
|
+
readonly id: "stream.auth-failed";
|
|
320
|
+
readonly category: "turn_observable";
|
|
321
|
+
readonly label: "Stream Auth Failed";
|
|
322
|
+
readonly observable: true;
|
|
323
|
+
readonly traceable: true;
|
|
324
|
+
}, {
|
|
325
|
+
readonly id: "error";
|
|
326
|
+
readonly category: "turn_observable";
|
|
327
|
+
readonly label: "Error";
|
|
328
|
+
readonly observable: true;
|
|
329
|
+
}, {
|
|
330
|
+
readonly id: "abort";
|
|
331
|
+
readonly category: "turn_observable";
|
|
332
|
+
readonly label: "Abort";
|
|
333
|
+
readonly observable: true;
|
|
334
|
+
}, {
|
|
335
|
+
readonly id: "lifecycle.tool.completed";
|
|
336
|
+
readonly category: "turn_observable";
|
|
337
|
+
readonly label: "Lifecycle Tool Completed";
|
|
338
|
+
readonly observable: true;
|
|
339
|
+
}, {
|
|
340
|
+
readonly id: "lifecycle.tool.completed.crossBoundary";
|
|
341
|
+
readonly category: "turn_observable";
|
|
342
|
+
readonly label: "Lifecycle Tool Completed (Cross Prompt Boundary)";
|
|
343
|
+
readonly observable: true;
|
|
344
|
+
}];
|
|
345
|
+
type EngineNode = (typeof ENGINE_NODES)[number];
|
|
346
|
+
type PausableNode = Extract<EngineNode, {
|
|
347
|
+
pausable: true;
|
|
348
|
+
}>;
|
|
349
|
+
type ObservableNode = Extract<EngineNode, {
|
|
350
|
+
observable: true;
|
|
351
|
+
}>;
|
|
352
|
+
type TraceableNode = Extract<EngineNode, {
|
|
353
|
+
traceable: true;
|
|
354
|
+
}>;
|
|
355
|
+
type LifecycleHookNode = Extract<EngineNode, {
|
|
356
|
+
hook: 'interceptor' | 'observer';
|
|
357
|
+
}>;
|
|
358
|
+
type BreakpointPoint = PausableNode['id'];
|
|
359
|
+
type ObservableNodeId = ObservableNode['id'];
|
|
360
|
+
type TraceableNodeId = TraceableNode['id'];
|
|
361
|
+
type LifecycleTiming = LifecycleHookNode['id'];
|
|
362
|
+
declare function nodesWithCapability(cap: 'pausable' | 'hook' | 'observable' | 'traceable'): readonly EngineNodeDef[];
|
|
363
|
+
declare const ENGINE_LIFECYCLE_TIMINGS: readonly LifecycleTiming[];
|
|
364
|
+
declare const OBSERVABLE_NODE_IDS: readonly ObservableNodeId[];
|
|
365
|
+
declare const TRACEABLE_NODE_IDS: readonly TraceableNodeId[];
|
|
366
|
+
type BreakpointCategory = PausableNode['category'] | 'custom';
|
|
367
|
+
interface BreakpointDefinition {
|
|
368
|
+
point: BreakpointPoint;
|
|
369
|
+
name: string;
|
|
370
|
+
category: BreakpointCategory;
|
|
371
|
+
}
|
|
372
|
+
declare const BREAKPOINT_POINTS: readonly BreakpointDefinition[];
|
|
373
|
+
declare const ENGINE_DEFAULTS: {
|
|
374
|
+
/** 工具轮次预算缺省(单源)。触顶后按 maxToolTurnExtensions 决定是否压缩续跑。
|
|
375
|
+
* agent 核心 + runtime RUNTIME_DEFAULTS 均引用此处,消除散落的硬编码 `?? 120`。 */
|
|
376
|
+
readonly maxToolTurns: 120; /** tool.call.end trace 的 ToolResult 内容截断阈值(字符数)。默认 512。 */
|
|
377
|
+
readonly traceResultMaxChars: 512;
|
|
378
|
+
/** R7:单条工具结果进模型上下文的字符上限(默认 ~100k≈25k token)。超出头尾保留+中段省略,
|
|
379
|
+
* 防全仓 grep 等病态巨型结果撑爆 context window。仅截极端值,正常 read/edit 不受影响。 */
|
|
380
|
+
readonly maxToolResultChars: 100000;
|
|
381
|
+
/**
|
|
382
|
+
* RFC-340 M3 D5-2:单条工具结果的**保底**字符份额。
|
|
383
|
+
*
|
|
384
|
+
* per-prompt 额度耗尽后,后续工具结果的有效预算不再递减到低于此值——否则模型会收到
|
|
385
|
+
* 一堆只剩省略号的结果,比给一个头尾可辨的截断版更难判断刚才发生了什么。配合 clamp
|
|
386
|
+
* 既有的头 70%/尾 30% 保留策略,2000 字符足以看清结果的开头与结尾。
|
|
387
|
+
*/
|
|
388
|
+
readonly minToolResultChars: 2000;
|
|
389
|
+
/**
|
|
390
|
+
* RFC-340 M3 D5-4:单个 prompt 内所有工具结果可占用的上下文比例。
|
|
391
|
+
*
|
|
392
|
+
* 缺口背景:`maxToolResultChars` 是 **per-tool** 固定值,不跨工具累计——20 个工具各
|
|
393
|
+
* 返回 99K 字符全部合法通过,但合计足以撑爆窗口,只能靠 `hardCeiling` 触发压缩事后
|
|
394
|
+
* 补救(= 用丢对话历史偿还工具输出超发,代价错配)。本比例给出 per-prompt 总闸。
|
|
395
|
+
*
|
|
396
|
+
* 取窗口的一半:留另一半给系统前缀 + 对话历史 + 模型输出。按窗口派生而非魔数,
|
|
397
|
+
* 小窗口模型自动收紧,无需逐模型调参。
|
|
398
|
+
*/
|
|
399
|
+
readonly promptToolResultFraction: 0.5;
|
|
400
|
+
/** 单 turn 内可并行工具(readonly 或 isConcurrencySafe)的并发上限。
|
|
401
|
+
* 对齐 CC 的 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 默认 10——防一轮几十个委托/读取压垮资源。 */
|
|
402
|
+
readonly maxToolConcurrency: 10;
|
|
403
|
+
/** RFC-104 D1:进展闸门的签名多样性阈值——纯读窗口 distinct 签名占比 ≥ 此值判「有进展」。
|
|
404
|
+
* 0.5 保守起步(同一调用反复 → ratio→0 → 循环嫌疑;研究型多样 grep → ratio→1)。 */
|
|
405
|
+
readonly progressDistinctRatio: 0.5; /** RFC-104 D1:进展窗口签名 Map 上限;溢出只计 totalCalls(ratio 退化方向=更宽松,不误杀)。 */
|
|
406
|
+
readonly progressSignatureCap: 512;
|
|
407
|
+
/** RFC-104 D1:签名对 args stringify 的截断长度(大 args 如 write 全文件内容只 hash 头部;
|
|
408
|
+
* 截断只影响签名精度且方向是「更易判相同」=更严格,不影响语义)。 */
|
|
409
|
+
readonly progressSignatureArgsMaxChars: 1024; /** RFC-203 D1:早期停滞检测的滑动窗口大小(turn 数)——窗口未满恒不判停。 */
|
|
410
|
+
readonly stallWindowTurns: 8;
|
|
411
|
+
/** RFC-203 D1(评审 H3):同一工具调用签名在窗口内重复 ≥ 此值且零写成功 → 判停滞。
|
|
412
|
+
* 不用 distinctRatio(会误伤"反复 read 同几个文件各 2-3 次"的合法审查),maxRepeat
|
|
413
|
+
* 只命中"同一件事反复做 N 次以上"的真打转。 */
|
|
414
|
+
readonly stallRepeatThreshold: 4;
|
|
415
|
+
};
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/ports.d.ts
|
|
418
|
+
interface ClockPort {
|
|
419
|
+
now(): number;
|
|
420
|
+
}
|
|
421
|
+
interface RandomPort {
|
|
422
|
+
random(): number;
|
|
423
|
+
}
|
|
424
|
+
interface IdPort {
|
|
425
|
+
uuid(): string;
|
|
426
|
+
}
|
|
427
|
+
interface DeterminismPorts {
|
|
428
|
+
clock: ClockPort;
|
|
429
|
+
random: RandomPort;
|
|
430
|
+
id: IdPort;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* RFC-093:工具结果溢出落盘端口。宿主(runtime AgentSession)注入实现(闭包捕获 sessionId),
|
|
434
|
+
* agent 包零存储依赖——与 approval 回调注入同型。
|
|
435
|
+
* 返回磁盘路径(占位符引用);undefined = 未配置/写盘失败 → 调用方 fail-open 回退截断。
|
|
436
|
+
*/
|
|
437
|
+
interface SpillPort {
|
|
438
|
+
(toolCallId: string, text: string): Promise<string | undefined>;
|
|
439
|
+
}
|
|
440
|
+
declare const systemClock: ClockPort;
|
|
441
|
+
declare const systemRandom: RandomPort;
|
|
442
|
+
declare const systemId: IdPort;
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/trace.d.ts
|
|
445
|
+
type TraceEventKind = 'lifecycle' | 'stream' | 'tool' | 'nondet' | 'checkpoint';
|
|
446
|
+
interface TraceEvent {
|
|
447
|
+
ts: number;
|
|
448
|
+
sessionId: string;
|
|
449
|
+
turn?: number;
|
|
450
|
+
kind: TraceEventKind;
|
|
451
|
+
/** 存档/回放事件保留 `string`——历史 trace 与跨版本节点名须容忍读取(生产侧由 Draft 编译期约束)。 */
|
|
452
|
+
node?: string;
|
|
453
|
+
payload?: unknown;
|
|
454
|
+
parentEventId?: string;
|
|
455
|
+
causalChain?: string[];
|
|
456
|
+
evalRunId?: string;
|
|
457
|
+
}
|
|
458
|
+
interface TraceEventDraft {
|
|
459
|
+
kind: TraceEventKind;
|
|
460
|
+
/** 生产侧 trace 锚点编译期绑回 ENGINE_NODES(traceable 子集)——拼错/未登记即编译错,取代正则扫描守。 */
|
|
461
|
+
node?: TraceableNodeId;
|
|
462
|
+
turn?: number;
|
|
463
|
+
payload?: unknown;
|
|
464
|
+
parentEventId?: string;
|
|
465
|
+
causalChain?: string[];
|
|
466
|
+
evalRunId?: string;
|
|
467
|
+
}
|
|
468
|
+
interface TraceRecorder {
|
|
469
|
+
record(draft: TraceEventDraft): void;
|
|
470
|
+
}
|
|
471
|
+
interface TraceSink {
|
|
472
|
+
append(event: TraceEvent): void | Promise<void>;
|
|
473
|
+
}
|
|
474
|
+
interface NondetDraw {
|
|
475
|
+
source: 'clock' | 'random' | 'uuid';
|
|
476
|
+
value: number | string;
|
|
477
|
+
}
|
|
478
|
+
interface TraceRecorderOptions {
|
|
479
|
+
sink: TraceSink;
|
|
480
|
+
sessionId: string;
|
|
481
|
+
clock?: ClockPort;
|
|
482
|
+
onError?: (error: unknown) => void;
|
|
483
|
+
}
|
|
484
|
+
declare function createTraceRecorder(options: TraceRecorderOptions): TraceRecorder;
|
|
485
|
+
declare const noopRecorder: TraceRecorder;
|
|
486
|
+
declare function shouldPersistStreamEvent(type: string): boolean;
|
|
487
|
+
interface StreamTracePersister {
|
|
488
|
+
/** 喂入一个 stream 事件(按到达序);内部按白名单过滤 + 滞后一拍剥离 partial。 */
|
|
489
|
+
push(payload: {
|
|
490
|
+
type?: string;
|
|
491
|
+
} & Record<string, unknown>, turn?: number): void;
|
|
492
|
+
/**
|
|
493
|
+
* 段结束冲洗(abort/流式错误/循环退出时调用;幂等)。
|
|
494
|
+
* 若缓存中还有未落盘的 `*_end`(= 段内无 done 到达),**保留 partial** 写出——
|
|
495
|
+
* replay 对无 done 段 fallback 到最后事件的 partial(replay.ts),无条件剥离会把
|
|
496
|
+
* abort 段回放从「降级」变「完全失败」(RFC-102 D2,真实 trace 8/1306 段无 done)。
|
|
497
|
+
*/
|
|
498
|
+
flush(): void;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* RFC-102 D1+D2:stream 事件的 trace 持久化器。
|
|
502
|
+
* 白名单过滤 delta;`*_end` 缓存一拍——下一事件到达说明段还在继续(其 partial 已被
|
|
503
|
+
* 后续事件覆盖,安全剥离);段边界(flush)到达则保留 partial(abort 保护)。
|
|
504
|
+
*/
|
|
505
|
+
declare function createStreamTracePersister(recorder: TraceRecorder): StreamTracePersister;
|
|
506
|
+
/**
|
|
507
|
+
* RFC-145 D3:录制端口 + nondet 批量冲洗。draw 不再逐条落盘(每条即一次 trace append,
|
|
508
|
+
* 实测占 trace 行数 36%、行数第一——RFC-102 白名单只治理 stream 类,nondet 是其盲区),
|
|
509
|
+
* 改为进程内累积、由宿主在回合边界与 work-loop 退出路径调用 `flushNondetBatch()`
|
|
510
|
+
* 落成单条 batch 事件 `{ kind:'nondet', payload:{ draws: NondetDraw[] } }`。
|
|
511
|
+
*
|
|
512
|
+
* batch 事件不设 node 字段——与既有逐条 nondet 记录一致(TraceEventDraft.node 是
|
|
513
|
+
* TraceableNodeId 编译期联合,nondet 类事件从不设 node);消费侧以
|
|
514
|
+
* `Array.isArray(payload.draws)` 区分 batch 与旧逐条形态(extractNondetDraws 双形态兼容)。
|
|
515
|
+
*
|
|
516
|
+
* 崩溃语义(RFC-145 D3 显式承认):崩溃时当前回合未 flush 的 draws 丢失 → 该回合重放
|
|
517
|
+
* underrun。可接受降级——重放以回合为单元消费(done.message 为终态权威),与 RFC-102 D2
|
|
518
|
+
* 的 abort 段降级语义一致。
|
|
519
|
+
*/
|
|
520
|
+
interface RecordingPorts extends DeterminismPorts {
|
|
521
|
+
/** 把累积的 nondet draws 落成单条 batch 事件并清空(空累积时 no-op,幂等)。 */
|
|
522
|
+
flushNondetBatch(): void;
|
|
523
|
+
}
|
|
524
|
+
declare function createRecordingPorts(base: DeterminismPorts, recorder: TraceRecorder): RecordingPorts;
|
|
525
|
+
declare function createReplayPorts(draws: readonly NondetDraw[]): DeterminismPorts;
|
|
526
|
+
/**
|
|
527
|
+
* RFC-145 D3 双形态兼容:同时接受旧的逐条 NondetDraw(存量 trace)与新的 batch
|
|
528
|
+
* `{draws: NondetDraw[]}`(展开,内部顺序 = 录制顺序)。旧形态兼容在存量 trace
|
|
529
|
+
* 存在期内不许移除(RFC-145 §5 规则 3)。
|
|
530
|
+
*/
|
|
531
|
+
declare function extractNondetDraws(events: Iterable<TraceEvent>): NondetDraw[];
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/debug-port.d.ts
|
|
534
|
+
interface MessageSummaryItem {
|
|
535
|
+
index: number;
|
|
536
|
+
role: 'user' | 'assistant' | 'tool_result' | 'system';
|
|
537
|
+
preview: string;
|
|
538
|
+
toolName?: string;
|
|
539
|
+
tokenEstimate?: number;
|
|
540
|
+
}
|
|
541
|
+
interface DebugSnapshot {
|
|
542
|
+
turn: number;
|
|
543
|
+
point: BreakpointPoint;
|
|
544
|
+
frameDepth: number;
|
|
545
|
+
messagesCount: number;
|
|
546
|
+
lastToolName?: string;
|
|
547
|
+
tokenUsage?: {
|
|
548
|
+
input: number;
|
|
549
|
+
output: number;
|
|
550
|
+
};
|
|
551
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
552
|
+
systemPrompt?: string;
|
|
553
|
+
messagesSummary?: MessageSummaryItem[];
|
|
554
|
+
llmParams?: {
|
|
555
|
+
temperature?: number;
|
|
556
|
+
maxTokens?: number;
|
|
557
|
+
thinkingLevel?: string;
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
interface InjectedMessage {
|
|
561
|
+
role: 'user' | 'system';
|
|
562
|
+
content: string;
|
|
563
|
+
}
|
|
564
|
+
interface PauseResumePayload {
|
|
565
|
+
systemPrompt?: string;
|
|
566
|
+
injectMessages?: InjectedMessage[];
|
|
567
|
+
removeMessageIndices?: number[];
|
|
568
|
+
llmParams?: {
|
|
569
|
+
temperature?: number;
|
|
570
|
+
maxTokens?: number;
|
|
571
|
+
thinkingLevel?: string;
|
|
572
|
+
};
|
|
573
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
574
|
+
}
|
|
575
|
+
type DebugCommand = {
|
|
576
|
+
type: 'next';
|
|
577
|
+
seq: number;
|
|
578
|
+
} | {
|
|
579
|
+
type: 'step';
|
|
580
|
+
seq: number;
|
|
581
|
+
} | {
|
|
582
|
+
type: 'over';
|
|
583
|
+
seq: number;
|
|
584
|
+
depth: number;
|
|
585
|
+
} | {
|
|
586
|
+
type: 'continue';
|
|
587
|
+
seq: number;
|
|
588
|
+
} | {
|
|
589
|
+
type: 'stop';
|
|
590
|
+
seq: number;
|
|
591
|
+
reason?: string;
|
|
592
|
+
};
|
|
593
|
+
interface PauseResult {
|
|
594
|
+
pauseId: string;
|
|
595
|
+
command: DebugCommand;
|
|
596
|
+
payload: PauseResumePayload | null;
|
|
597
|
+
}
|
|
598
|
+
interface DebugPort {
|
|
599
|
+
pause(snapshot: DebugSnapshot): Promise<PauseResult | undefined>;
|
|
600
|
+
}
|
|
601
|
+
declare function summarizeMessages(messages: readonly AgentMessage[], lastN: number): MessageSummaryItem[];
|
|
602
|
+
interface PauseApplyTarget {
|
|
603
|
+
messages?: AgentMessage[];
|
|
604
|
+
setSystemPrompt?: (value: string) => void;
|
|
605
|
+
setTemperature?: (value: number) => void;
|
|
606
|
+
setMaxTokens?: (value: number) => void;
|
|
607
|
+
setThinkingLevel?: (value: string) => void;
|
|
608
|
+
now?: () => number;
|
|
609
|
+
}
|
|
610
|
+
declare function consumePause(result: PauseResult | undefined, target?: PauseApplyTarget): void;
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/tool-executor.d.ts
|
|
613
|
+
interface ToolExecutor {
|
|
614
|
+
execute(toolCall: ToolCall, signal: AbortSignal): Promise<ToolResult>;
|
|
615
|
+
list(): AgentTool[];
|
|
616
|
+
}
|
|
617
|
+
interface ToolExecutorOptions {
|
|
618
|
+
tools: AgentTool[];
|
|
619
|
+
hookExecutor?: ToolHookExecutor;
|
|
620
|
+
agentName?: string;
|
|
621
|
+
sessionId?: string;
|
|
622
|
+
debug?: DebugPort;
|
|
623
|
+
approval?: (toolName: string, args: Record<string, unknown>, reason: string, risk?: ApprovalRisk) => Promise<boolean>;
|
|
624
|
+
clock?: ClockPort;
|
|
625
|
+
depth?: number;
|
|
626
|
+
}
|
|
627
|
+
declare function createToolExecutor(tools: AgentTool[], options?: Omit<ToolExecutorOptions, 'tools'>): ToolExecutor;
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region src/vision-gate.d.ts
|
|
630
|
+
/**
|
|
631
|
+
* 图像委托描述端口(RFC-181 D3)。宿主(coding 包 VisionDelegateService)实现,
|
|
632
|
+
* 引擎侧只消费返回值——不感知模型选择、prompt、缓存。返回值与入参一一对应;
|
|
633
|
+
* `null` 表示该图描述失败(单图失败不连坐,其余图正常处理)。
|
|
634
|
+
*
|
|
635
|
+
* 约定:返回的非 null 字符串是**最终注入文本**(宿主已按 RFC-181 D2 格式拼好
|
|
636
|
+
* `[image seen by vision model <id>]\n<描述>` 前缀),引擎侧原样使用,不做二次
|
|
637
|
+
* 格式化——保持"agent 包零模型路由知识"(RFC-181 §4 规则 5)。
|
|
638
|
+
*/
|
|
639
|
+
type DescribeImagesPort = (images: Array<{
|
|
640
|
+
mime: string;
|
|
641
|
+
source: string;
|
|
642
|
+
contextHint?: string;
|
|
643
|
+
}>, signal?: AbortSignal) => Promise<Array<string | null>>;
|
|
644
|
+
interface VisionGateResult {
|
|
645
|
+
messages: AgentMessage[];
|
|
646
|
+
/** 因能力错配被处理的 image block 总数(含委托成功与占位两类)。 */
|
|
647
|
+
gatedCount: number;
|
|
648
|
+
/** 其中委托成功、替换为描述文本的数量。 */
|
|
649
|
+
describedCount: number;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* 视觉能力门控投影。`model` 支持图像输入时直通(引用原样返回,零开销);
|
|
653
|
+
* 不支持时收集全部 image block(不分新旧、不设边界——模型对任何图像都不可见),
|
|
654
|
+
* 尝试委托描述,按结果逐块替换。
|
|
655
|
+
*/
|
|
656
|
+
declare function applyVisionGate(messages: AgentMessage[], model: Pick<Model, 'input'>, describeImages?: DescribeImagesPort, signal?: AbortSignal): Promise<VisionGateResult>;
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/work-loop-stream-error.d.ts
|
|
659
|
+
interface StreamRetryConfig {
|
|
660
|
+
maxRetries: number;
|
|
661
|
+
initialDelayMs: number;
|
|
662
|
+
backoffFactor: number;
|
|
663
|
+
maxDelayMs: number;
|
|
664
|
+
}
|
|
665
|
+
//#endregion
|
|
666
|
+
//#region src/types.d.ts
|
|
667
|
+
type AgentStatus = 'idle' | 'streaming' | 'tool_executing' | 'completed' | 'error' | 'aborted' | 'archived';
|
|
668
|
+
type AgentEvent = {
|
|
669
|
+
type: 'status.change';
|
|
670
|
+
from: AgentStatus;
|
|
671
|
+
to: AgentStatus;
|
|
672
|
+
}
|
|
673
|
+
/** toolTurnCount/maxToolTurns:当前工具轮次预算窗口的已用/上限(供宿主渲染 x/y 提示)。
|
|
674
|
+
* promptOutputTokens/promptOutputTokenBudget(RFC-104 D3/D5):本 prompt 累计输出 token
|
|
675
|
+
* 与预算(未配置预算时 budget 为 undefined,前端隐藏 n/N tok)。 */
|
|
676
|
+
| {
|
|
677
|
+
type: 'turn.start';
|
|
678
|
+
turnIndex: number;
|
|
679
|
+
toolTurnCount: number;
|
|
680
|
+
maxToolTurns: number;
|
|
681
|
+
promptOutputTokens: number;
|
|
682
|
+
promptOutputTokenBudget?: number;
|
|
683
|
+
} | {
|
|
684
|
+
type: 'turn.end';
|
|
685
|
+
turnIndex: number;
|
|
686
|
+
message: AssistantMessage;
|
|
687
|
+
} | {
|
|
688
|
+
type: 'stream.event';
|
|
689
|
+
event: StreamEvent;
|
|
690
|
+
} | {
|
|
691
|
+
type: 'tool.call.start';
|
|
692
|
+
toolCall: ToolCallEvent$1;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* `contextChars`:本次工具结果**经 clamp/spill 后真正进入模型上下文**的文本字符数
|
|
696
|
+
* (`clampWithSpill` 产物,非原始 `result`)。宿主用它做上下文占用的实时估算——
|
|
697
|
+
* 原始 `result` 可能远大于入模内容(100k 字符上限 + spill 落盘只留 preview),
|
|
698
|
+
* 用原始长度估算会系统性高估,导致状态栏占用条在回合结算时明显回落。
|
|
699
|
+
*/
|
|
700
|
+
| {
|
|
701
|
+
type: 'tool.call.end';
|
|
702
|
+
toolCall: ToolCallEvent$1;
|
|
703
|
+
result: ToolResult$1;
|
|
704
|
+
contextChars?: number;
|
|
705
|
+
} | {
|
|
706
|
+
type: 'steering.injected';
|
|
707
|
+
messages: AgentMessage$1[];
|
|
708
|
+
} | {
|
|
709
|
+
type: 'follow.up.start';
|
|
710
|
+
messages: AgentMessage$1[];
|
|
711
|
+
} /** progressDenied(RFC-104 D1):还有扩展额度但窗口无进展被闸门拒绝(循环嫌疑),区别于额度耗尽。 */ | {
|
|
712
|
+
type: 'max.turns';
|
|
713
|
+
turnIndex: number;
|
|
714
|
+
maxTurns: number;
|
|
715
|
+
extended: boolean;
|
|
716
|
+
steered?: boolean;
|
|
717
|
+
progressDenied?: boolean;
|
|
718
|
+
} /** RFC-104 D3:per-prompt token/时间预算超额(steered=true 收尾轮已注入;false 即将硬停)。 */ | {
|
|
719
|
+
type: 'prompt.budget';
|
|
720
|
+
turnIndex: number;
|
|
721
|
+
dimension: 'tokens' | 'time';
|
|
722
|
+
used: number;
|
|
723
|
+
budget: number;
|
|
724
|
+
steered: boolean;
|
|
725
|
+
} /** RFC-203 D2:滑动窗口停滞检出(零写成功 + 同签名反复 ≥ 阈值);软引导已注入,每 prompt 至多一次。 */ | {
|
|
726
|
+
type: 'progress.stall';
|
|
727
|
+
turnIndex: number;
|
|
728
|
+
windowTurns: number;
|
|
729
|
+
maxRepeat: number;
|
|
730
|
+
} | {
|
|
731
|
+
type: 'tool.call.mismatch';
|
|
732
|
+
turnIndex: number;
|
|
733
|
+
outcome: 'retry' | 'terminate';
|
|
734
|
+
retryCount: number;
|
|
735
|
+
} | {
|
|
736
|
+
type: 'compaction.reactive';
|
|
737
|
+
turnIndex: number;
|
|
738
|
+
messagesBefore: number;
|
|
739
|
+
messagesAfter: number;
|
|
740
|
+
} | {
|
|
741
|
+
type: 'image.degradation';
|
|
742
|
+
turnIndex: number;
|
|
743
|
+
degradedCount: number;
|
|
744
|
+
trigger: 'context-overflow' | 'image-constraint';
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* RFC-181 D6/M3:视觉委托结果(仅在 describeImages 端口被实际调用时触发)——
|
|
748
|
+
* 供宿主(AgentSession→TUI)渲染"N 张图已由 vision 模型代述"提示。
|
|
749
|
+
*/
|
|
750
|
+
| {
|
|
751
|
+
type: 'image.vision-delegate';
|
|
752
|
+
turnIndex: number;
|
|
753
|
+
attemptedCount: number;
|
|
754
|
+
describedCount: number;
|
|
755
|
+
failedCount: number;
|
|
756
|
+
} | {
|
|
757
|
+
type: 'stream.retry';
|
|
758
|
+
turnIndex: number;
|
|
759
|
+
attempt: number;
|
|
760
|
+
maxRetries: number;
|
|
761
|
+
delayMs: number;
|
|
762
|
+
reason: string;
|
|
763
|
+
} | {
|
|
764
|
+
type: 'stream.retry.accelerated';
|
|
765
|
+
turnIndex: number;
|
|
766
|
+
attempt: number;
|
|
767
|
+
maxRetries: number;
|
|
768
|
+
} | {
|
|
769
|
+
type: 'turn.truncated';
|
|
770
|
+
turnIndex: number;
|
|
771
|
+
action: 'escalate' | 'continue' | 'refusal';
|
|
772
|
+
maxTokens?: number;
|
|
773
|
+
} | {
|
|
774
|
+
type: 'rate.limit.exhausted';
|
|
775
|
+
limitType?: 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet' | 'seven_day_overage_included';
|
|
776
|
+
resetsAt?: number;
|
|
777
|
+
message: string;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* 鉴权失败(fatal 分类下的认证子类,见 isAuthError)——本地无凭据或服务端 401 拒绝。
|
|
781
|
+
* 此前该场景在 handleStreamError 直接 rethrow,无任何记录(既不 emit 也不 record),
|
|
782
|
+
* 用户看到 TUI 弹窗后无法回溯"什么时候、哪个 provider/model 失效、失效了几次"。
|
|
783
|
+
* modelId/provider 取自 WorkLoopContext.model,供事后按模型/provider 聚合统计。
|
|
784
|
+
*/
|
|
785
|
+
| {
|
|
786
|
+
type: 'stream.auth-failed';
|
|
787
|
+
turnIndex: number;
|
|
788
|
+
provider: string;
|
|
789
|
+
modelId: string;
|
|
790
|
+
reason: string; /** RFC-139 D3:机器可读失效根因(credential-missing / refresh-failed / server-rejected)。 */
|
|
791
|
+
errorType: string;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* RFC-144 M2:lifecycleAsync 工具真正完成(成功或失败)——`notifyToolCompletion()`
|
|
795
|
+
* 已完成 consume-once 标记,本事件携带完整 `result`,供宿主(TUI 后台面板)感知工具
|
|
796
|
+
* 完成、审计溯源。**不是**落库触发点——真正的落库(`messages.push`)由
|
|
797
|
+
* `work-loop.ts` 的 `runTools()` 直接完成(它持有 `messages` 数组引用),与本事件
|
|
798
|
+
* 的 emit 是并列关系而非因果关系。不用于 auto-trigger(那是 M3 的 workLoop
|
|
799
|
+
* `post-runTools yield` + 外层循环 `waitForPendingLifecycleTools()`,本事件只是
|
|
800
|
+
* 旁路通知)。
|
|
801
|
+
*/
|
|
802
|
+
| {
|
|
803
|
+
type: 'lifecycle.tool.completed';
|
|
804
|
+
toolCallId: string;
|
|
805
|
+
toolName: string;
|
|
806
|
+
isError: boolean;
|
|
807
|
+
result: ToolResult$1;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* RFC-144 M4:lifecycleAsync 工具跨越 prompt 边界后完成——**叠加**于上面的
|
|
811
|
+
* `lifecycle.tool.completed`(非互斥):`lifecycle.tool.completed` 是无条件的通用
|
|
812
|
+
* 审计/旁路通知(consume-once 消费成功即触发,不关心是否跨边界);本事件是额外的、
|
|
813
|
+
* 专供宿主(CLI/TUI)做路由决策的信号——只在跨边界时才会紧跟着触发第二次 emit
|
|
814
|
+
* (见 work-loop.ts `notifyCompletion()` 的 `crossedPromptBoundary` 判定)。
|
|
815
|
+
*
|
|
816
|
+
* 工具启动时所属的 prompt(`run()` 调用)已经结束,一个新的 prompt 已经开始,完成
|
|
817
|
+
* 通知不会注入新 prompt 的 LLM 对话历史(避免污染当前无关任务的上下文)——这是本
|
|
818
|
+
* 事件真正影响的行为:`messages.push(SystemNotificationMessage)` 被跳过。宿主可
|
|
819
|
+
* 订阅本事件决定如何呈现给用户(如 toast,具体接线归后续项,不阻塞本事件类型的
|
|
820
|
+
* 落地)。
|
|
821
|
+
*/
|
|
822
|
+
| {
|
|
823
|
+
type: 'lifecycle.tool.completed.crossBoundary';
|
|
824
|
+
toolCallId: string;
|
|
825
|
+
toolName: string;
|
|
826
|
+
result: ToolResult$1;
|
|
827
|
+
} | {
|
|
828
|
+
type: 'error';
|
|
829
|
+
error: Error;
|
|
830
|
+
} | {
|
|
831
|
+
type: 'abort';
|
|
832
|
+
};
|
|
833
|
+
type AgentEventType = AgentEvent['type'];
|
|
834
|
+
type AgentEventOf<T extends AgentEventType> = Extract<AgentEvent, {
|
|
835
|
+
type: T;
|
|
836
|
+
}>;
|
|
837
|
+
type AgentEventPayload<T extends AgentEventType> = Omit<AgentEventOf<T>, 'type'>;
|
|
838
|
+
type AgentEvents = { [K in AgentEventType]: keyof AgentEventPayload<K> extends never ? () => void : (payload: AgentEventPayload<K>) => void } & _$_x_otto_shared0.Events;
|
|
839
|
+
interface AgentState {
|
|
840
|
+
status: AgentStatus;
|
|
841
|
+
turnCount: number;
|
|
842
|
+
tokenUsage: {
|
|
843
|
+
input: number;
|
|
844
|
+
output: number;
|
|
845
|
+
cacheRead: number;
|
|
846
|
+
};
|
|
847
|
+
isStreaming: boolean;
|
|
848
|
+
currentStreamMessage: AssistantMessage | null;
|
|
849
|
+
error?: Error;
|
|
850
|
+
}
|
|
851
|
+
interface PendingLifecycleTool {
|
|
852
|
+
toolCallId: string;
|
|
853
|
+
toolName: string;
|
|
854
|
+
promise: Promise<void>;
|
|
855
|
+
promptSeq: number;
|
|
856
|
+
consumed: boolean;
|
|
857
|
+
/**
|
|
858
|
+
* RFC-144 M4:per-tool 取消句柄——独立于 workLoop 级别的 AbortController(那个只在
|
|
859
|
+
* 用户主动 abort 当前 prompt 或整个 run() 结束时才会触发,不会因为"新 prompt 开始了"
|
|
860
|
+
* 而自动 abort)。工具启动时(`runTools()`)创建,与 loop-level signal 用
|
|
861
|
+
* `AbortSignal.any()` 组合后一起传给 `toolExecutor.execute()`——工具会同时响应
|
|
862
|
+
* "当前 prompt 被中断"与"这个具体的 lifecycleAsync 调用被跨边界取消"两种信号。
|
|
863
|
+
*
|
|
864
|
+
* `Agent.run()` 每次递增 `promptSeq` 前,会扫描所有未消费、且 `promptSeq` 落后于
|
|
865
|
+
* 新序号的 pending 条目,逐个调用本字段的 `abort()`——底层 `packages/tools/src/shell/
|
|
866
|
+
* process.ts` 的 `spawn()` 已经实现 SIGTERM + grace + SIGKILL(监听 signal 的
|
|
867
|
+
* 'abort' 事件),无需在 Agent 层重复实现进程终止细节,只需正确传播取消意图。
|
|
868
|
+
*/
|
|
869
|
+
abortController: AbortController;
|
|
870
|
+
}
|
|
871
|
+
type ToolBeforeOutcome = {
|
|
872
|
+
kind: 'proceed';
|
|
873
|
+
args: Record<string, unknown>;
|
|
874
|
+
} | {
|
|
875
|
+
kind: 'ask';
|
|
876
|
+
args: Record<string, unknown>;
|
|
877
|
+
reason: string;
|
|
878
|
+
risk?: ApprovalRisk$1;
|
|
879
|
+
} | {
|
|
880
|
+
kind: 'deny';
|
|
881
|
+
reason: string;
|
|
882
|
+
};
|
|
883
|
+
interface ToolHookExecutor {
|
|
884
|
+
executeBeforeHooks(input: {
|
|
885
|
+
toolName: string;
|
|
886
|
+
toolCallId: string;
|
|
887
|
+
args: Record<string, unknown>;
|
|
888
|
+
agentName: string;
|
|
889
|
+
sessionId: string;
|
|
890
|
+
paths?: readonly string[]; /** R1:工具是否只读——sandbox-guard 据此跳过只读工具的写评估。 */
|
|
891
|
+
readonly?: boolean;
|
|
892
|
+
}): Promise<ToolBeforeOutcome>;
|
|
893
|
+
executeAfterHooks(input: {
|
|
894
|
+
toolName: string;
|
|
895
|
+
toolCallId: string;
|
|
896
|
+
args: Record<string, unknown>;
|
|
897
|
+
result: ToolResult$1;
|
|
898
|
+
agentName: string;
|
|
899
|
+
sessionId: string;
|
|
900
|
+
durationMs: number; /** R1:工具是否只读——after-hook 据此识别会改文件的工具,取代硬编码工具名集。 */
|
|
901
|
+
readonly?: boolean;
|
|
902
|
+
}): Promise<{
|
|
903
|
+
result: ToolResult$1;
|
|
904
|
+
metadata: Record<string, unknown>;
|
|
905
|
+
}>;
|
|
906
|
+
}
|
|
907
|
+
interface AgentConfig {
|
|
908
|
+
stream: StreamFunction;
|
|
909
|
+
logger: Logger;
|
|
910
|
+
maxToolTurns?: number;
|
|
911
|
+
maxToolTurnExtensions?: number;
|
|
912
|
+
/** RFC-104 D3:per-prompt 累计 output-token 预算(CC 口径)。配置后轮次上限自动 ×4 放宽(安全阀退位)。 */
|
|
913
|
+
promptOutputTokenBudget?: number;
|
|
914
|
+
/** RFC-104 D3:per-prompt 累计墙钟时间预算(ms)。 */
|
|
915
|
+
promptWallClockBudgetMs?: number;
|
|
916
|
+
/**
|
|
917
|
+
* RFC-050 D5(默认关 opt-in):工具预算耗尽时,先注入收尾 steering 让模型用一轮优雅收尾
|
|
918
|
+
* (budget-limit 收尾提示),而非直接抛 MaxToolTurnsError。收尾轮仍调工具才硬停。
|
|
919
|
+
*/
|
|
920
|
+
budgetSteerWrapUp?: boolean;
|
|
921
|
+
/**
|
|
922
|
+
* RFC-203:早期停滞检测配置(触顶前的滑动窗口打转发现)。缺省启用(走 ENGINE_DEFAULTS);
|
|
923
|
+
* `false` 显式关闭。RFC-204 D4 修复:此前本字段只存在于 LoopBudget(work-loop 参数),
|
|
924
|
+
* AgentConfig 从未声明,agent.ts 组装 budget 对象时逐字段列出却唯独漏了它——三层断链的
|
|
925
|
+
* 第一层(AgentConfig 缺字段)。
|
|
926
|
+
*/
|
|
927
|
+
stallDetection?: {
|
|
928
|
+
windowTurns?: number;
|
|
929
|
+
repeatThreshold?: number;
|
|
930
|
+
} | false;
|
|
931
|
+
agentName?: string;
|
|
932
|
+
sessionId?: string;
|
|
933
|
+
toolHookExecutor?: ToolHookExecutor;
|
|
934
|
+
debug?: DebugPort;
|
|
935
|
+
approval?: (toolName: string, args: Record<string, unknown>, reason: string, risk?: ApprovalRisk$1) => Promise<boolean>;
|
|
936
|
+
clock?: ClockPort;
|
|
937
|
+
recorder?: TraceRecorder;
|
|
938
|
+
/** RFC-145 D3:nondet batch 冲洗回调(RecordingPorts.flushNondetBatch)。缺省 = 不聚合。 */
|
|
939
|
+
nondetFlush?: () => void;
|
|
940
|
+
depth?: number;
|
|
941
|
+
retryAccelerate?: {
|
|
942
|
+
skip: boolean;
|
|
943
|
+
};
|
|
944
|
+
/**
|
|
945
|
+
* RFC-230:流式请求重试策略(429/5xx 指数退避)。缺省 = `DEFAULT_STREAM_RETRY`
|
|
946
|
+
* (`work-loop-stream-error.ts`)。经宿主(`@x-otto/coding` App)的 `ResilienceConfig`
|
|
947
|
+
* 投影而来,未注入时 work-loop 用其内部默认常量。
|
|
948
|
+
*/
|
|
949
|
+
streamRetry?: StreamRetryConfig;
|
|
950
|
+
/**
|
|
951
|
+
* RFC-230:网络断连重试策略(固定间隔,不做指数退避)。缺省 = `NETWORK_DISCONNECT_RETRY`。
|
|
952
|
+
* 语义与 `streamRetry` 同源,见其注释。
|
|
953
|
+
*/
|
|
954
|
+
networkDisconnectRetry?: StreamRetryConfig;
|
|
955
|
+
/** RFC-093:工具结果溢出落盘端口(宿主注入,闭包捕获 sessionId)。缺省 = 纯截断。 */
|
|
956
|
+
spill?: SpillPort;
|
|
957
|
+
}
|
|
958
|
+
interface AgentRunContext {
|
|
959
|
+
model: Model;
|
|
960
|
+
/** 会话内稳定的 system 前缀(进 prompt cache 前缀)。 */
|
|
961
|
+
systemPrompt: string;
|
|
962
|
+
/** 每轮可变的 system 尾段(volatile):落在 cache 断点之后,不破坏缓存。 */
|
|
963
|
+
systemTail?: string[];
|
|
964
|
+
/** prompt cache 保留档(none/short/long);缺省走 provider 默认 5m。 */
|
|
965
|
+
cacheRetention?: CacheRetention;
|
|
966
|
+
messages: AgentMessage$1[];
|
|
967
|
+
tools: AgentTool$1[];
|
|
968
|
+
thinkingLevel: ThinkingLevel;
|
|
969
|
+
maxTokens?: number;
|
|
970
|
+
temperature?: number;
|
|
971
|
+
convertToLLM?: (messages: AgentMessage$1[]) => Message[] | Promise<Message[]>;
|
|
972
|
+
transformContext?: (messages: AgentMessage$1[], signal?: AbortSignal) => Promise<AgentMessage$1[]>;
|
|
973
|
+
reactiveCompact?: (messages: AgentMessage$1[], signal?: AbortSignal) => Promise<AgentMessage$1[] | null>;
|
|
974
|
+
/**
|
|
975
|
+
* RFC-181 D3:图像委托描述端口(宿主 VisionDelegateService 实现)。透传给
|
|
976
|
+
* work-loop 的视觉能力门控——见 `./vision-gate.ts` 完整设计说明。
|
|
977
|
+
*/
|
|
978
|
+
describeImages?: DescribeImagesPort;
|
|
979
|
+
/**
|
|
980
|
+
* RFC-170 D4:turn 边界增量持久化回调(pipeline.ts 每次 prompt() 调用时新建的闭包,
|
|
981
|
+
* 与 transformContext/reactiveCompact 同生命周期——prompt 作用域内共享同一个持久化
|
|
982
|
+
* 游标,三者协同推进同一份"已落树基准",不会互相覆盖或产生重复落树)。
|
|
983
|
+
*
|
|
984
|
+
* work-loop.ts 在每个 turn(LLM 完成 + 工具执行结束)落定后调用一次,把本 turn 新增的
|
|
985
|
+
* 消息立即 append 进 session tree——修复此前"整个 prompt 循环结束才一次性 persistNew"
|
|
986
|
+
* 导致的崩溃丢失窗口(RFC-167 事故实测单 prompt 4.2 小时、101 轮工具循环全部丢失,
|
|
987
|
+
* 因为进程在循环中途被 OOM kill,从未走到 prompt() 尾部的 persistNew 调用)。
|
|
988
|
+
*
|
|
989
|
+
* 与 compaction(onCompaction/transformContext 内部)共享同一持久化游标:无论是"本
|
|
990
|
+
* turn 结束"还是"本轮触发了 compaction",都推进同一个"已落树基准",避免同一批
|
|
991
|
+
* 消息被 turn-persist 与 recordCompaction 的 replacement 各自独立写入两次逻辑重复
|
|
992
|
+
* (物理层面:即便如此仍写两次,RFC-160 D7 的内容寻址 blob 表按 sha256 幂等去重,
|
|
993
|
+
* 不产生重复存储字节;可见层面:buildContext()/messages() 均以 compaction 边界为界
|
|
994
|
+
* 读取,不会重复呈现——已见 RFC-170 M3 设计闸门核实)。
|
|
995
|
+
*
|
|
996
|
+
* 缺省(未注入)= 不做 turn 级增量持久化,退化为此前"prompt 结束才落树"的行为,
|
|
997
|
+
* 与旧代码零差异(fail-open,同 nondetFlush/registerPendingLifecycleTool 等既有回调
|
|
998
|
+
* 的缺省语义)。
|
|
999
|
+
*/
|
|
1000
|
+
onTurnPersist?: () => void;
|
|
1001
|
+
/**
|
|
1002
|
+
* RFC-180:turn 边界暂停请求探针(与 abortRequested 模式同构,但通道独立——本字段
|
|
1003
|
+
* 走 AgentRunContext → WorkLoopContext 通道,随 prompt() → runner.run() 调用透传;
|
|
1004
|
+
* TodoGateRunner 的 isAbortRequested 走独立的构造函数注入通道,只服务闸门循环,不
|
|
1005
|
+
* 进入本接口)。work-loop.ts 的外层 prompt 循环在每个 turn 边界(onTurnPersist 已
|
|
1006
|
+
* 调用、消息安全落盘之后,followUp 检查之前)读取;命中则不检查 followUp、不触发
|
|
1007
|
+
* todo 闸门续跑,workLoop 正常 return(非 AbortError,完整收尾)。
|
|
1008
|
+
*
|
|
1009
|
+
* 缺省(未注入)= 不做暂停检查,与旧代码零差异(fail-open,同 onTurnPersist 等既有
|
|
1010
|
+
* 回调的缺省语义)。
|
|
1011
|
+
*/
|
|
1012
|
+
isPauseRequested?: () => boolean;
|
|
1013
|
+
}
|
|
1014
|
+
//#endregion
|
|
1015
|
+
//#region src/agent.d.ts
|
|
1016
|
+
declare class Agent extends TypedEventEmitter<AgentEvents> {
|
|
1017
|
+
private state;
|
|
1018
|
+
private abortController;
|
|
1019
|
+
private pendingAbort;
|
|
1020
|
+
private steeringQueue;
|
|
1021
|
+
private followUpQueue;
|
|
1022
|
+
/**
|
|
1023
|
+
* RFC-144 M1:lifecycleAsync 工具的执行态追踪,独立于 messages 数组(Agent 实例在两次
|
|
1024
|
+
* run() 之间不持有 messages 引用——由调用方每次通过 AgentRunContext.messages 传入)。
|
|
1025
|
+
*
|
|
1026
|
+
* 键 = toolCallId。存在期:runTools 启动 lifecycleAsync 工具时创建条目(M3 接线);
|
|
1027
|
+
* `notifyToolCompletion()` 消费(`consumed=true`)后条目保留供审计/溯源,不立即 delete
|
|
1028
|
+
* (若需要严格释放内存,调用方可在 prompt 边界批量清理已 consumed 条目)。
|
|
1029
|
+
*
|
|
1030
|
+
* 真正把 system 通知消息写回 messages 数组的逻辑,依赖调用方持有的 messages 引用,
|
|
1031
|
+
* 归属 M2(落库基础设施)+ M3(auto-trigger 控制流集成)——本字段仅提供状态追踪骨架。
|
|
1032
|
+
*/
|
|
1033
|
+
private readonly pendingLifecycleTools;
|
|
1034
|
+
/**
|
|
1035
|
+
* RFC-144 M4:prompt 边界序号——每次 `run()` 被调用(即一次完整的 turn-loop 生命周期
|
|
1036
|
+
* 起点)自增。用于 lifecycleAsync 工具的跨 prompt 隔离判定:工具启动时记录当前
|
|
1037
|
+
* `promptSeq`(见 `PendingLifecycleTool.promptSeq`),完成时若序号已变化(说明工具
|
|
1038
|
+
* 跨越了 prompt 边界——原 prompt 已经结束,一个新的 `run()` 已经开始),其完成通知
|
|
1039
|
+
* 不应注入新 prompt 的 LLM 对话(避免"上一个任务的构建完成了"这类历史事件用一条
|
|
1040
|
+
* mid-conversation system message 打断当前用户正在做的新任务,见 RFC-144 决策 4
|
|
1041
|
+
* Q4 修正——system 角色被模型视为运营方权威事实,混入无关任务上下文比普通消息更
|
|
1042
|
+
* 容易造成困惑或被误解为需要处理的指令)。
|
|
1043
|
+
*
|
|
1044
|
+
* **递增时机边界(重要)**:`Agent.run()` 是 turn-loop 生命周期的唯一入口,包括
|
|
1045
|
+
* `AgentSession` 内部的 todo 续跑(`todoGateRunner`,`internal=true` 场景)——这类
|
|
1046
|
+
* 续跑虽然是"同一用户任务"的延伸,但确实是走完整独立的 `run()` 调用(前一次
|
|
1047
|
+
* `run()` 已经 `completed`/返回),语义上工具若跨越了这次调用边界,模型也确实已经
|
|
1048
|
+
* 推进到了下一轮独立的 turn-loop,此时再看到上一轮的工具完成通知同样可能造成
|
|
1049
|
+
* "为什么突然冒出一个我不认识的构建结果"的困惑——因此续跑同样应该视为新 promptSeq,
|
|
1050
|
+
* 不额外区分 internal,保持递增规则简单统一("每次 run() 调用 = 一个新 promptSeq")。
|
|
1051
|
+
*/
|
|
1052
|
+
private promptSeq;
|
|
1053
|
+
/** 当前 prompt 序号(只读)——`work-loop.ts` 的 lifecycleAsync 工具启动逻辑读取它记录到
|
|
1054
|
+
* `PendingLifecycleTool.promptSeq`;完成时对比判定是否已跨越 prompt 边界。 */
|
|
1055
|
+
get currentPromptSeq(): number;
|
|
1056
|
+
/**
|
|
1057
|
+
* 构造期静态配置(终局审查 2026-07-18 M7 收束;元审查 A 修正为派生类型):`AgentConfig`
|
|
1058
|
+
* 的字段此前逐个拷贝为同名私有字段,构造函数因此膨胀为纯粹的透传列表。收束为单一只读
|
|
1059
|
+
* 配置对象后,构造函数只需一次赋值;下游读取点(`runLoop()` 内两处:`createToolExecutor`
|
|
1060
|
+
* 配置 + `workLoop` 参数)从 `this.xxx` 改为 `this.config.xxx`。
|
|
1061
|
+
*
|
|
1062
|
+
* **类型从 `AgentConfig` 派生而非手写**(元审查 2026-07-18 A 项修正,lesson_46 手抄类型
|
|
1063
|
+
* 副本反模式):`Required<Pick<...>>` 段 = 构造期已消解默认值的字段(maxToolTurns 等
|
|
1064
|
+
* 5 个,运行期非空);`Pick<...>` 段 = 原样透传的可选字段。`AgentConfig` 加字段时本类型
|
|
1065
|
+
* 不会静默漂移——新字段若需进 config 必须显式加进 Pick 列表(编译器在构造函数赋值处
|
|
1066
|
+
* 报错提示)。唯一的重命名字段 `agentLogger`(源自 `AgentConfig.logger`)单独声明。
|
|
1067
|
+
* 不改变公开的 `AgentConfig` 接口,仅内部实现重构(零行为变化)。
|
|
1068
|
+
*/
|
|
1069
|
+
private readonly config;
|
|
1070
|
+
get status(): AgentStatus;
|
|
1071
|
+
get turnCount(): number;
|
|
1072
|
+
constructor(config: AgentConfig);
|
|
1073
|
+
getState(): Readonly<AgentState>;
|
|
1074
|
+
run(context: AgentRunContext): Promise<AssistantMessage>;
|
|
1075
|
+
/**
|
|
1076
|
+
* RFC-144 M4:取消上一个 prompt 遗留、仍未消费的 lifecycleAsync pending 工具执行。
|
|
1077
|
+
*
|
|
1078
|
+
* 触发时机:每次 `run()` 被调用、**递增 `promptSeq` 之前**(新一轮 turn-loop 生命
|
|
1079
|
+
* 周期起点)。由于同一时刻只会有一个 `run()` 在执行(`run()` 顶部对
|
|
1080
|
+
* streaming/tool_executing 状态直接抛错拒绝并发调用),此刻任何仍未消费的 pending
|
|
1081
|
+
* 条目必然是在**上一个** `run()` 调用期间注册的(`promptSeq` 等于递增前的
|
|
1082
|
+
* `this.promptSeq`)——它们理应已经被上一个 `workLoop()` 返回前的兜底等待
|
|
1083
|
+
* (M3,`waitForPendingLifecycleTools()`)消费完毕;仍然存在未消费条目,说明上一个
|
|
1084
|
+
* `workLoop()` 是被 `AbortError` 中断退出的(用户主动 `abort()` 了上一个 prompt),
|
|
1085
|
+
* 没有机会走到那段兜底等待——这正是本方法要处理的真实场景:用户中断了一个启动了
|
|
1086
|
+
* 长任务工具的 prompt,随即发起了全新任务,旧工具仍在后台跑。
|
|
1087
|
+
*
|
|
1088
|
+
* 继续让它在后台跑对用户已无意义(新任务的模型不会再看到它的结果——见 work-loop.ts
|
|
1089
|
+
* `notifyCompletion()` 的 `crossedPromptBoundary` 判定,跨边界完成不落库注入 LLM
|
|
1090
|
+
* 对话),白白消耗资源(CPU/内存/可能仍在运行的子进程),故主动取消。不需要按
|
|
1091
|
+
* `promptSeq` 数值比较——调用时刻的每一个未消费条目都定义上属于"即将成为历史"的
|
|
1092
|
+
* 那个 prompt(若属于当前正要开始的这个新 prompt,此刻它还未被注册——工具注册
|
|
1093
|
+
* 发生在 `runTools()` 内部,晚于本方法的调用时机)。
|
|
1094
|
+
*
|
|
1095
|
+
* 只 abort 每个条目独立的 `abortController`(M4 新增),不影响 loop 级别的
|
|
1096
|
+
* `this.abortController`——那个 controller 属于"已经结束"的上一次 `runLoop()`
|
|
1097
|
+
* 调用,`run()` 即将为本次调用创建一个全新的 controller,两者本就互不干扰;
|
|
1098
|
+
* 用独立的 per-tool controller 是为了在**未来**"上一个 prompt 仍在正常运行、
|
|
1099
|
+
* 只是启动了 lifecycleAsync 工具"的场景下也能精确取消,不会误伤同一个共享
|
|
1100
|
+
* controller 下的其它逻辑。
|
|
1101
|
+
*
|
|
1102
|
+
* 已消费(`consumed:true`)的条目不取消(它们代表已经落库或已判定跨边界抑制的
|
|
1103
|
+
* 工具,取消一个已经完成的工具是无意义操作),但**在此顺便清理**——终局审查
|
|
1104
|
+
* 2026-07-21 发现:`notifyToolCompletion()` 只标记 `consumed=true` 从不 `delete`,
|
|
1105
|
+
* 而 `reset()`(Map 唯一的清理点)在生产代码中从未被调用(`AgentSession` 持有的
|
|
1106
|
+
* `Agent` 实例与会话同生命周期,一辈子不 reset),导致 Map 无界增长(慢性内存
|
|
1107
|
+
* 泄漏,与 RFC-159/160/170/178 治理的"会话级状态无界增长"同类问题,只是发生在
|
|
1108
|
+
* Agent 类私有字段、未被那几轮驻留审计覆盖到)。
|
|
1109
|
+
*
|
|
1110
|
+
* 清理时机安全性:本方法调用时刻严格早于 `this.promptSeq++`(见 `run()`),此刻
|
|
1111
|
+
* Map 中的**全部**条目都必然注册于此前已经结束/被中断的 run() 调用——本次即将
|
|
1112
|
+
* 开始的新 prompt 还未注册任何条目(注册发生在 `runTools()` 内部,晚于此调用)。
|
|
1113
|
+
* 因此已消费的条目在此处删除对"当前 prompt 内的审计可见性"零影响,不需要按
|
|
1114
|
+
* `promptSeq` 数值比较区分"是否跨越了本次边界"——它们全部跨越了。
|
|
1115
|
+
*/
|
|
1116
|
+
private cancelStalePendingLifecycleTools;
|
|
1117
|
+
steer(message: AgentMessage): void;
|
|
1118
|
+
/**
|
|
1119
|
+
* RFC-337 D2:按 `uuid` 从 steering 队列移除一条尚未被消费的 steer 消息,返回是否命中。
|
|
1120
|
+
*
|
|
1121
|
+
* 命中判定即「该 steer 是否仍未被模型消费」的权威真源:`drainSteeringQueue()` 一旦把队列
|
|
1122
|
+
* 清空并交付给 workLoop(消息进入 LLM 上下文),本方法返回 `false`——调用方(cli ESC 撤回)
|
|
1123
|
+
* 据此退化为「中止整回合」(已进历史的消息不可干净撤回,见 RFC-337 §D3 退化路径)。
|
|
1124
|
+
*
|
|
1125
|
+
* 只操作**内存态** `steeringQueue`,不触碰已落库消息、不涉及 save 路径——不违反 RFC-159。
|
|
1126
|
+
*/
|
|
1127
|
+
removeSteer(uuid: string): boolean;
|
|
1128
|
+
followUp(message: AgentMessage): void;
|
|
1129
|
+
/**
|
|
1130
|
+
* RFC-144 M1:注册一个 lifecycleAsync 工具的执行态(runTools 启动工具时调用)。
|
|
1131
|
+
* 幂等:同一 toolCallId 重复注册会覆盖旧条目(不应发生,调用方保证每个 tool_call 唯一)。
|
|
1132
|
+
*
|
|
1133
|
+
* 不在此处挂 promise 完成回调——落库闭环的驱动方是 `work-loop.ts` 的 `runTools()`
|
|
1134
|
+
* (见其 `promise.then(...)`/`.catch(...)` 直接调用 `ctx.notifyToolCompletion`),
|
|
1135
|
+
* 因为它直接持有 `messages` 数组引用,能在 consume-once 通过后立即
|
|
1136
|
+
* `messages.push(SystemNotificationMessage)`。Agent 实例本身不持有 messages,
|
|
1137
|
+
* 若在此处也挂一次完成回调会与 workLoop 的调用重复触发 `notifyToolCompletion`
|
|
1138
|
+
* (consume-once 会吞掉第二次调用,不产生双重落库,但属于职责重复的设计混乱,
|
|
1139
|
+
* 故明确只保留 workLoop 一处驱动)。
|
|
1140
|
+
*/
|
|
1141
|
+
registerPendingLifecycleTool(entry: PendingLifecycleTool): void;
|
|
1142
|
+
/** 当前仍有 lifecycleAsync 工具在等待(M3 用于 post-runTools yield 判定)。 */
|
|
1143
|
+
hasPendingLifecycleTools(): boolean;
|
|
1144
|
+
/**
|
|
1145
|
+
* RFC-144 M3:等待当前所有未消费的 lifecycleAsync pending 条目 settle。
|
|
1146
|
+
*
|
|
1147
|
+
* 用于外层 prompt 循环在内层 turn 循环因 pending 非空 yield 后的等待点——不轮询
|
|
1148
|
+
* `hasPendingLifecycleTools()`(会引入轮询延迟 + CPU 空转),而是直接 await 每个
|
|
1149
|
+
* pending 条目的 `promise` 字段。
|
|
1150
|
+
*
|
|
1151
|
+
* **`entry.promise` 是"落库完成"信号,不是"工具执行完成"信号**(M3 实现时的关键
|
|
1152
|
+
* 修正,见 `PendingLifecycleTool.promise` 类型定义注释与 work-loop.ts 的
|
|
1153
|
+
* `notifyCompletion()` 函数):work-loop.ts 把工具执行 + consume-once 通知 +
|
|
1154
|
+
* `messages.push(SystemNotificationMessage)` 三步包成一个函数,本字段存它的返回值。
|
|
1155
|
+
* 若只等原始工具执行 Promise(不等落库这一步),本方法可能在通知真正写进 `messages`
|
|
1156
|
+
* **之前**就返回,外层循环重新进入 `runTurnsUntilTerminate` 发起下一次 `runTurn` 时
|
|
1157
|
+
* 模型会看不到工具完成结果——这是首版实现遗漏、二版修正的竞态。`entry.promise` 恒
|
|
1158
|
+
* fulfilled(`notifyCompletion()` 内部吞掉所有异常,包括工具执行 reject 的情况),
|
|
1159
|
+
* `Promise.allSettled` 不会因任何 pending 条目而挂起或需要错误处理。
|
|
1160
|
+
*
|
|
1161
|
+
* **快照语义**:只等待调用时刻已注册的 pending 条目——若等待期间又有新的
|
|
1162
|
+
* lifecycleAsync 工具被启动(理论上不会发生,因为外层循环在等待期间不会重新调用
|
|
1163
|
+
* `runTools`),不纳入本次等待范围。这与"等到 `hasPendingLifecycleTools()` 变
|
|
1164
|
+
* false"语义等价,但避免了轮询。
|
|
1165
|
+
*/
|
|
1166
|
+
waitForPendingLifecycleTools(): Promise<void>;
|
|
1167
|
+
/**
|
|
1168
|
+
* RFC-144 M1/M2:工具真正完成后的通知钩子——检查 consume-once、标记已消费、发出携带
|
|
1169
|
+
* 完整 `result` 的事件。M2 起本方法主要由 `registerPendingLifecycleTool` 注册时挂的
|
|
1170
|
+
* promise 完成回调**自动调用**(事件驱动,无需外部轮询);也保留公开可调用(防御性/
|
|
1171
|
+
* 测试场景下手动触发)。
|
|
1172
|
+
*
|
|
1173
|
+
* **不在此处把 system 通知写回 messages**——Agent 实例在两次 run() 之间不持有 messages
|
|
1174
|
+
* 引用(见类字段注释)。真正的落库由订阅方(AgentSession,M2 落库基础设施)在
|
|
1175
|
+
* `lifecycle.tool.completed` 事件回调中完成:拿到 `result` 后自行构造
|
|
1176
|
+
* `SystemNotificationMessage` 并追加到其持有的 messages 数组。
|
|
1177
|
+
*
|
|
1178
|
+
* 返回 `consumed=false` 的情况:toolCallId 未注册(未知调用,防御性容错)或已被消费过
|
|
1179
|
+
* (consume-once 语义,防止重复生成通知——即使 promise 的 `.then`/`.catch` 因竞态被
|
|
1180
|
+
* 调用两次,也只有第一次真正标记 consumed 并 emit 事件)。
|
|
1181
|
+
*/
|
|
1182
|
+
notifyToolCompletion(toolCallId: string, result: ToolResult): Promise<{
|
|
1183
|
+
consumed: boolean;
|
|
1184
|
+
entry?: PendingLifecycleTool;
|
|
1185
|
+
}>;
|
|
1186
|
+
abort(): void;
|
|
1187
|
+
/** 归档:禁止后续 run()。仅终态(idle/completed/error/aborted)可归档。 */
|
|
1188
|
+
archive(): void;
|
|
1189
|
+
/** 取消归档:从 archived 回到 idle。 */
|
|
1190
|
+
unarchive(): void;
|
|
1191
|
+
/**
|
|
1192
|
+
* 强制归零:不经 `VALID_TRANSITIONS` 表——`streaming`/`tool_executing` 没有到
|
|
1193
|
+
* `idle` 的合法边(reset 的语义恰恰是"哪怕正在跑也强行归零",先 abort 掉
|
|
1194
|
+
* in-flight controller 再复位,这与常规状态转换的前提相反),故不能改用
|
|
1195
|
+
* `transitionTo()`(那会在这两个状态下被表拒绝、静默不转换,状态反而卡死)。
|
|
1196
|
+
*
|
|
1197
|
+
* 但此前直接赋值 `this.state.status = 'idle'` 会跳过 `status.change` 事件——
|
|
1198
|
+
* 任何订阅方(TUI 状态栏、Session 层状态镜像)收不到通知,与 Agent 内部真实
|
|
1199
|
+
* 状态漂移(终局审查 2026-07-21 发现)。补发事件但保留强制赋值本身。
|
|
1200
|
+
*/
|
|
1201
|
+
reset(): void;
|
|
1202
|
+
private runLoop;
|
|
1203
|
+
private transitionTo;
|
|
1204
|
+
private drainSteeringQueue;
|
|
1205
|
+
private drainFollowUpQueue;
|
|
1206
|
+
}
|
|
1207
|
+
declare function createAgent(config: AgentConfig): Agent;
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/tool-result-clamp.d.ts
|
|
1210
|
+
/**
|
|
1211
|
+
* RFC-151 D2:image 块内联字节上限(5MB)。超限时走 spill 落盘或占位符丢弃。
|
|
1212
|
+
* 跨包单源 @x-otto/shared PROVIDER_IMAGE_INLINE_MAX_BYTES(C 组收敛,2026-08-09)——re-export
|
|
1213
|
+
* 保持本包公共 API 名不变(消费方/测试 import 自本包)。底层是 provider 单图输入字节约束,
|
|
1214
|
+
* 与 fs/read、mcp、clipboard 同源;shared 常量为纯字面量(不经 env),保证 agent 引擎确定性
|
|
1215
|
+
* 重放不受环境影响(RFC-192)。
|
|
1216
|
+
*/
|
|
1217
|
+
declare const MAX_IMAGE_INLINE_BYTES: number;
|
|
1218
|
+
/**
|
|
1219
|
+
* 剥离 ANSI 转义序列——完整 5 道正则(与 `@x-otto/tui` message-lines.ts 同源同构)。
|
|
1220
|
+
* 本包独立维护不引 tui——agent 不应反向依赖 TUI 包。
|
|
1221
|
+
*
|
|
1222
|
+
* 道 1: OSC 序列(超链接/标题等)
|
|
1223
|
+
* 道 2: 完整 CSI 序列(颜色/光标等,必须有终止字节)
|
|
1224
|
+
* 道 3: 截断的 ESC 前缀(无终止字节的 CSI 碎片,如 `\x1b[38;2;86;182`)
|
|
1225
|
+
* 道 4: 孤儿多参数 SGR 尾(无 ESC 前缀,上游 byte-slice 产物,如 `;194m` / `38;2;86;182;194m`)
|
|
1226
|
+
* 道 5: 孤儿 truecolor 参数(精确匹配 38;2;R;G;Bm / 48;2;R;G;Bm 无 ESC[,英文文本假阳性极低)
|
|
1227
|
+
*/
|
|
1228
|
+
declare function stripAnsi(text: string): string;
|
|
1229
|
+
/**
|
|
1230
|
+
* 源头钩子:工具执行器返回后、任何消费者拿到之前,统一剥离所有 text 块的 ANSI 转义序列。
|
|
1231
|
+
*
|
|
1232
|
+
* 设计意图——替代"散落在各消费路径上的独立 stripAnsi 调用"(打地鼠模式)为"执行边界单次
|
|
1233
|
+
* 归一化"(源头截断)。`tool-turn-runner.ts` 的两个执行入口(sync 工具 + lifecycleAsync 工具)
|
|
1234
|
+
* 在 `toolExecutor.execute()` 返回后立即调用本函数,确保 emitter、recorder、clamp、通知、
|
|
1235
|
+
* trace 等全部下游自动获得洁净文本。下游已有的 stripAnsi 调用保留为纵深防御(对已洁净文本
|
|
1236
|
+
* 是零开销 no-op)。
|
|
1237
|
+
*
|
|
1238
|
+
* 类型设计:泛型 `T extends { type: string; text?: string }` 兼容 `ToolResult.content`
|
|
1239
|
+
* (`@x-otto/hook-contracts`)与 `ToolResultMessage['content']`(`@x-otto/interchange`)
|
|
1240
|
+
* 两套 shape——text 块有 `{ type: 'text', text: string }`,image 块 `{ type: 'image', ... }`
|
|
1241
|
+
* 无 text 字段原样返回。
|
|
1242
|
+
*/
|
|
1243
|
+
declare function normalizeToolResultContent<T extends {
|
|
1244
|
+
type: string;
|
|
1245
|
+
text?: string;
|
|
1246
|
+
}>(content: T[]): T[];
|
|
1247
|
+
/**
|
|
1248
|
+
* R7:钳制工具结果文本进模型上下文的大小。仅截极端值(默认 100k 字符≈25k token):
|
|
1249
|
+
* text 块超阈值时头尾保留、中段以 `[… N chars truncated …]` 替换;非 text 块原样保留。
|
|
1250
|
+
* 防全仓 grep / 巨型文件读等病态结果撑爆 context window;正常 read/edit 远低于阈值不受影响。
|
|
1251
|
+
*
|
|
1252
|
+
* 终局修复(ANSI 泄漏):**钳制前先剥离 ANSI**,再基于洁净文本算 head/tail/slice——
|
|
1253
|
+
* 否则 slice 可能恰好落在 ANSI 序列中间(如 head 落到 `\x1b[38;2;86;182` 中间位置),
|
|
1254
|
+
* 导致下游 stripAnsi 仅剥离前半段 `\x1b[38;2;86;182` 无效序列、后半段 `;194mfunction`
|
|
1255
|
+
* 无 ESC 前缀的残缺参数裸漏进对话流。先 strip → 基于洁净长度钳制 → 用洁净文本拼接。
|
|
1256
|
+
* 钳制输出本就降级为截断预览,丢失 ANSI 颜色是可接受的。
|
|
1257
|
+
*/
|
|
1258
|
+
declare function clampToolResultContent(content: ToolResultMessage['content'], maxChars: number): ToolResultMessage['content'];
|
|
1259
|
+
/**
|
|
1260
|
+
* 统计一段 tool result content 真正进入模型上下文的文本字符数。
|
|
1261
|
+
*
|
|
1262
|
+
* 调用方应传 **clamp/spill 之后**的 content——它才是模型下一轮实际看到的内容。
|
|
1263
|
+
* image 块按 `IMAGE_CONTEXT_CHARS_EQUIVALENT` 折算(图片不是文本,但仍占窗口;
|
|
1264
|
+
* 这里只给宿主一个量级可用的估算基数,精确图片 token 由 provider usage 结算)。
|
|
1265
|
+
*/
|
|
1266
|
+
declare const IMAGE_CONTEXT_CHARS_EQUIVALENT = 4000;
|
|
1267
|
+
declare function toolResultContextChars(content: ToolResultMessage['content']): number;
|
|
1268
|
+
/**
|
|
1269
|
+
* RFC-093:spill-aware 钳制。超预算 text 块先尝试落盘(SpillPort):
|
|
1270
|
+
* 成功 → 占位符含磁盘路径 + 头尾 preview(模型可用 read 工具恢复全文);
|
|
1271
|
+
* 失败/未注入 → fail-open 回退 clampToolResultContent 纯截断(现状行为)。
|
|
1272
|
+
*/
|
|
1273
|
+
declare function clampWithSpill(content: ToolResultMessage['content'], maxChars: number, toolCallId: string, spill: SpillPort | undefined): Promise<ToolResultMessage['content']>;
|
|
1274
|
+
/**
|
|
1275
|
+
* 有上限的并行执行(allSettled 语义)。最多 `limit` 个并发,其余排队;每个任务
|
|
1276
|
+
* 自捕获异常成 PromiseSettledResult,绝不抛出(无孤儿)——调用方据 settled 数组统一传播。
|
|
1277
|
+
* 保留输入顺序对应结果索引。
|
|
1278
|
+
*/
|
|
1279
|
+
declare function runBounded<T>(items: readonly T[], limit: number, fn: (item: T) => Promise<void>): Promise<PromiseSettledResult<void>[]>;
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region src/lifecycle-async-tool.d.ts
|
|
1282
|
+
/**
|
|
1283
|
+
* RFC-144 决策 1 "Q2 修正":placeholder 内容必须是强制性、无歧义的行为约束,而非
|
|
1284
|
+
* 状态陈述——LLM 对结构信号(tool_result 存在即代表完成)的服从度高于对文本指令,
|
|
1285
|
+
* 简单陈述句("已启动,尚未完成")不足以抑制虚假完成信号。已通过真实 Claude Opus 4.8
|
|
1286
|
+
* 端到端验证(2026-07-10,见 RFC M0 执行状态),确认此措辞有效。
|
|
1287
|
+
*/
|
|
1288
|
+
declare function buildLifecycleAsyncPlaceholderText(toolName: string, taskId: string, note?: string): string;
|
|
1289
|
+
/**
|
|
1290
|
+
* 构造 lifecycleAsync 工具启动时的终态 placeholder tool_result。
|
|
1291
|
+
*
|
|
1292
|
+
* **终态**:一次写入后永不被替换或重排(见 RFC-144 决策 1、硬约束 1)——从写入的
|
|
1293
|
+
* 那一刻起就满足 provider 的 tool_call↔tool_result 1:1 配对约束,不再需要任何
|
|
1294
|
+
* 后续的 seal 排除或重排逻辑(原破窗点 #1/#7 因此被架构性消除)。
|
|
1295
|
+
*/
|
|
1296
|
+
declare function buildLifecycleAsyncPlaceholder(toolCallId: string, toolName: string, clock: {
|
|
1297
|
+
now(): number;
|
|
1298
|
+
}, note?: string): ToolResultMessage;
|
|
1299
|
+
/**
|
|
1300
|
+
* 构造工具真正完成时的 mid-conversation system 通知消息(第五轮修正:落回持久数组,
|
|
1301
|
+
* 非临时 context 副本;provider 序列化归属 M2)。
|
|
1302
|
+
*/
|
|
1303
|
+
declare function buildLifecycleAsyncCompletionNotice(toolName: string, toolCallId: string, result: ToolResult, clock: {
|
|
1304
|
+
now(): number;
|
|
1305
|
+
}): SystemNotificationMessage;
|
|
1306
|
+
//#endregion
|
|
1307
|
+
//#region src/pipeline.d.ts
|
|
1308
|
+
interface PipelineHooks {
|
|
1309
|
+
execute<T extends InterceptorTiming>(timing: T, input: HookInput<T>, output: HookOutput<T>): Promise<void>;
|
|
1310
|
+
emit<T extends ObserverTiming>(timing: T, input: HookInput<T>): Promise<void>;
|
|
1311
|
+
}
|
|
1312
|
+
interface PersistencePort {
|
|
1313
|
+
messages(): readonly AgentMessage[];
|
|
1314
|
+
append(message: AgentMessage): void;
|
|
1315
|
+
buildContext(): {
|
|
1316
|
+
messages: AgentMessage[];
|
|
1317
|
+
summary?: string;
|
|
1318
|
+
};
|
|
1319
|
+
persistNew(messages: AgentMessage[], fromIndex: number): void;
|
|
1320
|
+
/**
|
|
1321
|
+
* 自动压缩(keep-recent)写回——把摘要 + 保留的近期消息记入会话树
|
|
1322
|
+
* (replacement_history 模型),使压缩成为持久事件:重启原样恢复、不每轮重算。可选(旧适配器可不实现)。
|
|
1323
|
+
*/
|
|
1324
|
+
recordCompaction?(summary: string, replacement: AgentMessage[]): void;
|
|
1325
|
+
/**
|
|
1326
|
+
* 裁剪(prune)发生时的 UI 通知——无持久化语义,仅供 AgentSession publish memory.pruned 会话事件
|
|
1327
|
+
* 驱动「裁剪已启用」提示。与 recordCompaction 同为「agent 包调、runtime 实现并 publish」的 facade 桥。
|
|
1328
|
+
* 可选(旧适配器/无 UI 时可不实现)。
|
|
1329
|
+
*
|
|
1330
|
+
* `metrics`:裁剪明细(prunedCount/messagesBefore/messagesAfter,可选——供 `/compact log`
|
|
1331
|
+
* 详情面板展示"裁剪了多少条工具输出",而非仅有 tokensSaved 单一数字)。
|
|
1332
|
+
*/
|
|
1333
|
+
notifyPrune?(tokensSaved: number, metrics?: {
|
|
1334
|
+
prunedCount: number;
|
|
1335
|
+
messagesBefore: number;
|
|
1336
|
+
messagesAfter: number;
|
|
1337
|
+
}): void;
|
|
1338
|
+
/**
|
|
1339
|
+
* RFC-283 D2:移除最后一条 append 的 message entry(内存态回滚)。
|
|
1340
|
+
* 用于 prompt 失败时回滚从未到达 LLM 的 user message。
|
|
1341
|
+
* 只移除最后一条 message 类型 entry,回退 _leafId;不触碰 compaction/clear 等结构 entry。
|
|
1342
|
+
* 只在 prompt 执行期间(save 之前)调用,不违反 RFC-159 save 路径不删行约束。
|
|
1343
|
+
*/
|
|
1344
|
+
removeLast?(): void;
|
|
1345
|
+
/**
|
|
1346
|
+
* RFC-336 D2:**输入边界**落盘请求——本轮 user message 已进会话树、且上下文准备
|
|
1347
|
+
* (planPromptContext)已成功,但尚未开始调用模型。宿主据此把新增 entries 落库。
|
|
1348
|
+
*
|
|
1349
|
+
* 要解决的问题:落库此前只由 `turn.end`/`prompt.end` 触发(RFC-305),两者都在**首个
|
|
1350
|
+
* 模型回合完成之后**。故"用户已回车、模型还没吐字"这段窗口(≈ 一次完整模型往返,
|
|
1351
|
+
* 数秒~数十秒)内进程被强杀,那条 user message 从未落库。RFC-336 M1 已保证会话行
|
|
1352
|
+
* 存在(不再整个蒸发),本钩子进一步保证**消息本身**不丢。
|
|
1353
|
+
*
|
|
1354
|
+
* **调用时机固定在 prep 成功之后**(RFC-336 §D3 grill 定案方向 A,规则 7):不得前移到
|
|
1355
|
+
* `append()` 紧后——prep 失败时 RFC-283 D2 要 `removeLast()` 做内存回滚,若此前已落库
|
|
1356
|
+
* 就会造成内存与 DB 不一致。放在 prep 成功后,两者语义天然自洽。
|
|
1357
|
+
*
|
|
1358
|
+
* **契约**:实现方必须 fire-and-forget(不得阻塞工作循环,RFC-336 规则 3)、必须复用
|
|
1359
|
+
* 宿主既有的 per-session 串行写链(不得新开并发写路径,规则 2/9)。可选:旧适配器/
|
|
1360
|
+
* 无持久化场景不实现即退回改动前行为(首个 turn 后才落库)。
|
|
1361
|
+
*/
|
|
1362
|
+
persistInputBoundary?(): void;
|
|
1363
|
+
}
|
|
1364
|
+
interface MemoryPort {
|
|
1365
|
+
needsPrune(messages: readonly Message[]): boolean;
|
|
1366
|
+
needsCompaction(messages: readonly Message[]): boolean;
|
|
1367
|
+
/**
|
|
1368
|
+
* `/clear`——驱逐某会话的工作记忆(滚动压缩摘要桶)。可选:注入式/无 memory
|
|
1369
|
+
* 场景安全跳过。清后下一轮压缩从空摘要重建,不再把已清内容的摘要喂回模型。
|
|
1370
|
+
*/
|
|
1371
|
+
clearSession?(sessionId: string): void;
|
|
1372
|
+
/**
|
|
1373
|
+
* 审计修复:会话销毁/驱逐时遗忘其全部 per-session 状态(摘要桶 + 压缩游标 + archive 计数)。
|
|
1374
|
+
* 区别于 clearSession(/clear 保留 archive 计数,会话继续):forget 是终态清理,防内存无界增长。
|
|
1375
|
+
*/
|
|
1376
|
+
forgetSession?(sessionId: string): void;
|
|
1377
|
+
/**
|
|
1378
|
+
* 模型切换时更新上下文窗口 + 重算 compaction/prune 触发阈值。
|
|
1379
|
+
* 切换低窗口模型后立即调用,防止下一轮 413。
|
|
1380
|
+
*/
|
|
1381
|
+
reconfigure?(model: {
|
|
1382
|
+
contextWindow: number;
|
|
1383
|
+
maxOutput: number;
|
|
1384
|
+
}): void;
|
|
1385
|
+
/**
|
|
1386
|
+
* 主动检查当前消息量是否超过压缩触发线;超过则 force-compact,
|
|
1387
|
+
* 返回压缩后消息 + compacted flag + summary/replacement(供调用方写回 session 树)。
|
|
1388
|
+
*/
|
|
1389
|
+
ensureFitsWindow?(messages: readonly Message[], sessionId?: string, signal?: AbortSignal): Promise<{
|
|
1390
|
+
messages: Message[];
|
|
1391
|
+
compacted: boolean;
|
|
1392
|
+
summary?: string;
|
|
1393
|
+
replacement?: Message[];
|
|
1394
|
+
}>;
|
|
1395
|
+
process(messages: readonly Message[], sessionId?: string, signal?: AbortSignal, options?: {
|
|
1396
|
+
force?: boolean;
|
|
1397
|
+
skipPrune?: boolean;
|
|
1398
|
+
}): Promise<{
|
|
1399
|
+
messages: Message[];
|
|
1400
|
+
pruned: boolean;
|
|
1401
|
+
compacted: boolean;
|
|
1402
|
+
summary?: string;
|
|
1403
|
+
replacement?: Message[];
|
|
1404
|
+
archivePath?: string;
|
|
1405
|
+
pruneMetrics?: {
|
|
1406
|
+
prunedCount: number;
|
|
1407
|
+
tokensSaved: number;
|
|
1408
|
+
messagesBefore: number;
|
|
1409
|
+
messagesAfter: number;
|
|
1410
|
+
};
|
|
1411
|
+
}>;
|
|
1412
|
+
}
|
|
1413
|
+
interface TurnRunner {
|
|
1414
|
+
run(context: AgentRunContext): Promise<AssistantMessage>;
|
|
1415
|
+
}
|
|
1416
|
+
interface PromptParams {
|
|
1417
|
+
systemPrompt: string;
|
|
1418
|
+
thinkingLevel: ThinkingLevel;
|
|
1419
|
+
temperature?: number;
|
|
1420
|
+
maxTokens?: number;
|
|
1421
|
+
/** prompt cache 保留档(none/short/long);由会话据特征选档,透传至 provider。 */
|
|
1422
|
+
cacheRetention?: CacheRetention;
|
|
1423
|
+
/**
|
|
1424
|
+
* 会话恒定的 **volatile system 尾段种子**(落在 cache 断点之后,不击穿 stable 前缀缓存)。
|
|
1425
|
+
* 由宿主据会话配置填充——典型用例:append 模式子代理的角色块(保留共享 base system prompt 进缓存,
|
|
1426
|
+
* 角色差异走断点后的尾段)。transformSystemPrompt 以此为 systemTail 起点,hook(phase/memory-delta)
|
|
1427
|
+
* 续接其后。
|
|
1428
|
+
*/
|
|
1429
|
+
systemTail?: string[];
|
|
1430
|
+
}
|
|
1431
|
+
interface PromptInput {
|
|
1432
|
+
text: string;
|
|
1433
|
+
/**
|
|
1434
|
+
* RFC-111 D4b:可选的图片附件,随本轮用户消息一起注入上下文(如飞书桥接下载的图片)。
|
|
1435
|
+
* 缺省(`undefined`)时行为与改动前完全一致——`createUserMessage` 只产出纯文本 content
|
|
1436
|
+
* (§6 规则7 向后兼容红线)。
|
|
1437
|
+
*/
|
|
1438
|
+
images?: Array<{
|
|
1439
|
+
type: 'image';
|
|
1440
|
+
data: string;
|
|
1441
|
+
mediaType: string;
|
|
1442
|
+
}>;
|
|
1443
|
+
/**
|
|
1444
|
+
* RFC-094:引擎注入的内部触发消息(todo 闸门续跑提示)——模型需要看到、但不渲染给用户
|
|
1445
|
+
* (对齐 RFC-050 REV-1 internal 语义;provider 序列化忽略此字段)。
|
|
1446
|
+
*/
|
|
1447
|
+
internal?: boolean;
|
|
1448
|
+
sessionId: string;
|
|
1449
|
+
agentName: string;
|
|
1450
|
+
model: Model;
|
|
1451
|
+
tools: AgentTool[];
|
|
1452
|
+
params: PromptParams;
|
|
1453
|
+
runner: TurnRunner;
|
|
1454
|
+
hooks: PipelineHooks;
|
|
1455
|
+
persistence: PersistencePort;
|
|
1456
|
+
memory?: MemoryPort;
|
|
1457
|
+
debug?: DebugPort;
|
|
1458
|
+
clock?: ClockPort;
|
|
1459
|
+
recorder?: TraceRecorder;
|
|
1460
|
+
/** RFC-180:turn 边界暂停请求探针——透传给 runner.run() 的 AgentRunContext.isPauseRequested。 */
|
|
1461
|
+
isPauseRequested?: () => boolean;
|
|
1462
|
+
/**
|
|
1463
|
+
* RFC-181 D3/M3:图像委托描述端口(宿主 VisionDelegateService 实现)——透传给
|
|
1464
|
+
* runner.run() 的 AgentRunContext.describeImages(见 work-loop.ts vision-gate.ts
|
|
1465
|
+
* 完整设计说明)。缺省 = 主模型不支持视觉时,image block 降级为占位文本。
|
|
1466
|
+
*/
|
|
1467
|
+
describeImages?: DescribeImagesPort;
|
|
1468
|
+
}
|
|
1469
|
+
interface PromptResult {
|
|
1470
|
+
cancelled: boolean;
|
|
1471
|
+
message?: AssistantMessage;
|
|
1472
|
+
newMessages: AgentMessage[];
|
|
1473
|
+
/** D1:usage 增量聚合结果——AgentSession.promptUsage/hadToolActivity 不再扫描 newMessages 数组。 */
|
|
1474
|
+
newMessagesUsageSummary: NewMessagesAccumulator;
|
|
1475
|
+
params: PromptParams;
|
|
1476
|
+
}
|
|
1477
|
+
/** D1:消息切片元数据聚合——替代 newMessages 数组的 usage/hadTool 扫描路径。 */
|
|
1478
|
+
interface NewMessagesAccumulator {
|
|
1479
|
+
usage: Usage;
|
|
1480
|
+
hadToolActivity: boolean;
|
|
1481
|
+
lastAssistantApi: string | undefined;
|
|
1482
|
+
count: number;
|
|
1483
|
+
}
|
|
1484
|
+
declare function prompt(input: PromptInput): Promise<PromptResult>;
|
|
1485
|
+
//#endregion
|
|
1486
|
+
//#region src/context-pipeline.d.ts
|
|
1487
|
+
/**
|
|
1488
|
+
* RFC-074 R-CTXARG:尾随可选项收成 options 对象(原 recorder/hooks/persistence/sink 四个位置参
|
|
1489
|
+
* 在 apply/force 两函数间顺序不一致 → 易传错位)。memory/messages/sessionId/signal 仍位置参。
|
|
1490
|
+
*/
|
|
1491
|
+
interface MemoryTransformOptions {
|
|
1492
|
+
recorder?: TraceRecorder;
|
|
1493
|
+
hooks?: PipelineHooks;
|
|
1494
|
+
persistence?: PersistencePort;
|
|
1495
|
+
sink?: {
|
|
1496
|
+
compacted: boolean;
|
|
1497
|
+
replacement?: AgentMessage[];
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
declare function applyMemoryTransform(memory: MemoryPort | undefined, messages: AgentMessage[], sessionId: string, signal?: AbortSignal, opts?: MemoryTransformOptions): Promise<AgentMessage[]>;
|
|
1501
|
+
/**
|
|
1502
|
+
* 反应式压缩——本轮 stream 因上下文溢出(413)失败时调用。无条件强制压缩
|
|
1503
|
+
* (绕过 token 阈值闸),返回压缩后的消息数组;若无可压缩内容(消息太少等)返回 null,
|
|
1504
|
+
* 调用方据此放弃重试并上抛原始溢出错误。**不**再跑 messages.transform hook:该 hook 已在
|
|
1505
|
+
* 本轮 transformContext 跑过,重跑可能引入新内容、与「缩小上下文」的目标相悖。
|
|
1506
|
+
*/
|
|
1507
|
+
declare function forceMemoryTransform(memory: MemoryPort | undefined, messages: AgentMessage[], sessionId: string, signal?: AbortSignal, opts?: MemoryTransformOptions): Promise<AgentMessage[] | null>;
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/memory-lifecycle.d.ts
|
|
1510
|
+
/**
|
|
1511
|
+
* 记忆子系统生命周期(原设计稿 docs/design/memory-lifecycle.md 已随 RFC-067 M123-03 文档收敛
|
|
1512
|
+
* 删除,内容并入 packages/memory/ARCHITECTURE.md,见 commit 5d6a9f5b)。
|
|
1513
|
+
*
|
|
1514
|
+
* P1(最小可观测):仅录 trace —— 让裁剪/持久化在 inspector/replay/OTel 现形。
|
|
1515
|
+
* 后续 P2 把本模块扩展为多通道 sink(同一事件再扇出 emitter(AgentEvent) + hooks 干预面),
|
|
1516
|
+
* P3 收编 compaction.* 把压缩三路归一。当前仅 prune/archive 接线(compact 暂留 compaction.*;
|
|
1517
|
+
* load/update 属其它层,待 P1b)。
|
|
1518
|
+
*/
|
|
1519
|
+
type MemoryPhase = 'load' | 'prune' | 'compact' | 'archive' | 'update';
|
|
1520
|
+
type MemoryTrigger = 'startup' | 'in_loop' | 'reactive_overflow' | 'manual' | 'tool';
|
|
1521
|
+
interface MemoryMetrics {
|
|
1522
|
+
prunedCount?: number;
|
|
1523
|
+
tokensSaved?: number;
|
|
1524
|
+
messagesBefore?: number;
|
|
1525
|
+
messagesAfter?: number;
|
|
1526
|
+
archivePath?: string;
|
|
1527
|
+
archivedCount?: number;
|
|
1528
|
+
}
|
|
1529
|
+
interface MemoryLifecycleEvent {
|
|
1530
|
+
phase: MemoryPhase;
|
|
1531
|
+
sessionId: string;
|
|
1532
|
+
turn?: number;
|
|
1533
|
+
trigger: MemoryTrigger;
|
|
1534
|
+
metrics: MemoryMetrics;
|
|
1535
|
+
}
|
|
1536
|
+
/** P1:把一条记忆生命周期事件录成对应 trace 锚点(node 由 PHASE_TRACE_NODE 编译期约束为 traceable)。 */
|
|
1537
|
+
declare function recordMemoryLifecycle(recorder: TraceRecorder, event: MemoryLifecycleEvent): void;
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region src/trace-views.d.ts
|
|
1540
|
+
interface TraceRecord {
|
|
1541
|
+
seq: number;
|
|
1542
|
+
entry: TraceEvent;
|
|
1543
|
+
}
|
|
1544
|
+
interface TraceSummary {
|
|
1545
|
+
totalEvents: number;
|
|
1546
|
+
kinds: Record<string, number>;
|
|
1547
|
+
turns: number;
|
|
1548
|
+
toolCalls: number;
|
|
1549
|
+
firstTs?: number;
|
|
1550
|
+
lastTs?: number;
|
|
1551
|
+
}
|
|
1552
|
+
interface TraceSpan {
|
|
1553
|
+
seq: number;
|
|
1554
|
+
ts: number;
|
|
1555
|
+
kind: TraceEvent['kind'];
|
|
1556
|
+
node?: string;
|
|
1557
|
+
turn?: number;
|
|
1558
|
+
digest?: string;
|
|
1559
|
+
}
|
|
1560
|
+
type Records = AsyncIterable<TraceRecord> | Iterable<TraceRecord>;
|
|
1561
|
+
declare function summarizeTrace(records: Records): Promise<TraceSummary>;
|
|
1562
|
+
declare function traceToSpans(records: Records, options?: {
|
|
1563
|
+
digestMax?: number;
|
|
1564
|
+
}): Promise<TraceSpan[]>;
|
|
1565
|
+
/**
|
|
1566
|
+
* payload 摘要(归一自旧 cli formatDigest 的富格式 + 旧 service 的长度上限语义):
|
|
1567
|
+
* text → 引号包裹;name → `name(args)`;reason → `done:reason`;message → `role:content`;
|
|
1568
|
+
* 其余 JSON 截断。大 payload 只截输出长度(stringify 成本与旧实现持平,不引入新风险)。
|
|
1569
|
+
*
|
|
1570
|
+
* `node` 可选(向后兼容既有 2 参调用点/测试)——传入时优先走 `digestByNode` 精确分发,
|
|
1571
|
+
* 未命中或未传时落回下方形状匹配链(见 digestByNode 上方注释的完整设计说明)。
|
|
1572
|
+
*/
|
|
1573
|
+
declare function digestTracePayload(payload: unknown, max?: number, node?: string): string | undefined;
|
|
1574
|
+
interface CausalNode {
|
|
1575
|
+
eventId: string;
|
|
1576
|
+
seq: number;
|
|
1577
|
+
kind: string;
|
|
1578
|
+
node?: string;
|
|
1579
|
+
turn?: number;
|
|
1580
|
+
parentEventId?: string;
|
|
1581
|
+
causalChain?: string[];
|
|
1582
|
+
}
|
|
1583
|
+
interface CausalDag {
|
|
1584
|
+
nodes: CausalNode[];
|
|
1585
|
+
edges: Array<{
|
|
1586
|
+
from: string;
|
|
1587
|
+
to: string;
|
|
1588
|
+
}>;
|
|
1589
|
+
roots: string[];
|
|
1590
|
+
leaves: string[];
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* 从 trace 事件列表构建因果 DAG(M34-01)。
|
|
1594
|
+
* 利用 TraceEvent 中的 parentEventId / causalChain 字段构建有向无环图。
|
|
1595
|
+
* 不支持字段的事件降级为平铺列表(每个节点独立为根)。
|
|
1596
|
+
*/
|
|
1597
|
+
declare function causalDag(records: TraceRecord[]): CausalDag;
|
|
1598
|
+
declare function traceWhy(records: TraceRecord[], targetSeq: number): string[];
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/errors.d.ts
|
|
1601
|
+
declare class AbortError extends AgentError {
|
|
1602
|
+
constructor(message?: string);
|
|
1603
|
+
}
|
|
1604
|
+
declare class MaxToolTurnsError extends AgentError {
|
|
1605
|
+
readonly maxTurns: number;
|
|
1606
|
+
readonly partialText: string;
|
|
1607
|
+
readonly extensions: number;
|
|
1608
|
+
constructor(maxTurns: number, partialText?: string, extensions?: number);
|
|
1609
|
+
get effectiveMaxTurns(): number;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* RFC-104 D3:per-prompt token/时间预算超额(steer 收尾轮之后仍超才抛)。
|
|
1613
|
+
* 与 MaxToolTurnsError 并存:轮次触顶抛 MaxToolTurnsError(API 兼容),
|
|
1614
|
+
* token/时间超额抛本错误;边界层(run-modes)需对称 catch 两者(RFC-104 评审 A2)。
|
|
1615
|
+
*/
|
|
1616
|
+
declare class PromptBudgetExceededError extends AgentError {
|
|
1617
|
+
readonly dimension: 'tokens' | 'time';
|
|
1618
|
+
readonly used: number;
|
|
1619
|
+
readonly budget: number;
|
|
1620
|
+
readonly partialText: string;
|
|
1621
|
+
constructor(dimension: 'tokens' | 'time', used: number, budget: number, partialText?: string);
|
|
1622
|
+
}
|
|
1623
|
+
//#endregion
|
|
1624
|
+
export { AbortError, Agent, type AgentConfig, type AgentEvent, type AgentEventOf, type AgentEventPayload, type AgentEventType, type AgentEvents, type AgentMessage, type AgentRunContext, type AgentSessionEvent, type AgentSessionEventMap, type AgentSessionEventType, type AgentSessionSubscriber, type AgentState, type AgentStatus, type AgentTool, type ApprovalRisk, type AskUserQuestion, BREAKPOINT_POINTS, type BreakpointCategory, type BreakpointDefinition, type BreakpointPoint, type CausalDag, type CausalNode, type ClockPort, type CustomAgentMessages, type DebugCommand, type DebugPort, type DebugSnapshot, type DescribeImagesPort, type DeterminismPorts, ENGINE_DEFAULTS, ENGINE_LIFECYCLE_TIMINGS, ENGINE_NODES, type EngineNode, type EngineNodeCategory, type EngineNodeDef, type GrillAnswer, type GrillOption, type GrillQuestion, type GrillRequest, IMAGE_CONTEXT_CHARS_EQUIVALENT, type IdPort, type InjectedMessage, type LifecycleTiming, MAX_IMAGE_INLINE_BYTES, MaxToolTurnsError, type MemoryLifecycleEvent, type MemoryMetrics, type MemoryPhase, type MemoryPort, type MemoryTrigger, type MessageSummaryItem, type NewMessagesAccumulator, type NondetDraw, OBSERVABLE_NODE_IDS, type ObservableNodeId, type PauseApplyTarget, type PauseResult, type PauseResumePayload, type PendingLifecycleTool, type PersistencePort, type PipelineHooks, PromptBudgetExceededError, type PromptInput, type PromptParams, type PromptResult, type RandomPort, type RecordingPorts, type StreamFunction, type StreamRetryConfig, type StreamTracePersister, TRACEABLE_NODE_IDS, type ToolBeforeOutcome, type ToolCallContext, type ToolCallEvent, type ToolExecutor, type ToolHookExecutor, type ToolResult, type TraceEvent, type TraceEventDraft, type TraceEventKind, type TraceRecord, type TraceRecorder, type TraceRecorderOptions, type TraceSink, type TraceSpan, type TraceSummary, type TraceableNodeId, type TurnRunner, type VisionGateResult, applyMemoryTransform, applyVisionGate, buildLifecycleAsyncCompletionNotice, buildLifecycleAsyncPlaceholder, buildLifecycleAsyncPlaceholderText, causalDag, clampToolResultContent, clampWithSpill, consumePause, createAgent, createRecordingPorts, createReplayPorts, createStreamTracePersister, createToolExecutor, createTraceRecorder, digestTracePayload, extractNondetDraws, extractToolResultText, forceMemoryTransform, nodesWithCapability, noopRecorder, normalizeToolResultContent, prompt, recordMemoryLifecycle, runBounded, shouldPersistStreamEvent, stripAnsi, summarizeMessages, summarizeTrace, systemClock, systemId, systemRandom, toolResultContextChars, traceToSpans, traceWhy };
|
|
1625
|
+
//# sourceMappingURL=index.d.ts.map
|