@x-otto/runtime 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 +108 -0
- package/dist/index.d.ts +2973 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2973 @@
|
|
|
1
|
+
import { CostSummary, Message, Model, ModelResolution, ProviderRegistry, StreamEvent, ThinkingLevel } from "@x-otto/ai";
|
|
2
|
+
import { HookRegistry, HookSpec, SystemPromptTransformInput, ToolExecuteAfterInput } from "@x-otto/hooks";
|
|
3
|
+
import { EventBus, Events, Logger, TypedEventEmitter } from "@x-otto/shared";
|
|
4
|
+
import { AppendLog, PanelStateDraftEntry, PanelStateEditedFile, PanelStatePersistence, PanelStateSnapshot, PanelStateSubagentEntry, PanelStateTodoItem, PanelStateTurnSummary, PasteStatePersistence, PasteStateSnapshot } from "@x-otto/persistence";
|
|
5
|
+
import { InMemorySession, LeaseInspection, Session, SessionEntry, SessionPersistence, SessionSnapshot } from "@x-otto/session";
|
|
6
|
+
import { OttoProcessInfo } from "@x-otto/env";
|
|
7
|
+
import { Agent, AgentEvent, AgentMessage, AgentSessionEvent, AgentSessionEventMap, AgentSessionEventType, AgentSessionSubscriber, AgentTool, ApprovalRisk, AskUserQuestion, ClockPort, DescribeImagesPort, GrillAnswer, GrillQuestion, GrillRequest, MemoryPort, MemoryPort as MemoryPort$1, StreamFunction, StreamRetryConfig, ToolHookExecutor, ToolResult, TraceEvent, TraceRecorder, applyMemoryTransform } from "@x-otto/agent";
|
|
8
|
+
import * as _$_x_otto_session_contract0 from "@x-otto/session-contract";
|
|
9
|
+
import { ResidencyBudgetConfig, TurnRecord, TurnRecord as TurnRecord$1 } from "@x-otto/session-contract";
|
|
10
|
+
import { ChildProcess, SpawnOptions } from "node:child_process";
|
|
11
|
+
import { Devtools } from "@x-otto/devtools";
|
|
12
|
+
|
|
13
|
+
//#region src/context-source.d.ts
|
|
14
|
+
interface ContextSource {
|
|
15
|
+
/** 本源的唯一标识。重复注册→覆盖前一次同 id 源。 */
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** 排序权重(越小越靠前,与 HookSpec.priority 同语义)。缺省 50。 */
|
|
18
|
+
readonly priority?: number;
|
|
19
|
+
/**
|
|
20
|
+
* 计算本轮注入的上下文片段。由 session-stable 底座做会话内冻结;
|
|
21
|
+
* 返回空/undefined 视为此轮无贡献。
|
|
22
|
+
*/
|
|
23
|
+
contribute(ctx: ContextBuildInput): Promise<string | undefined> | string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* RFC-067 M113(A3/D3):memory 的**上下文注入**语义端口(三端口之一)。此前是 ContextBuildInput.memory
|
|
27
|
+
* 上的内联匿名类型,消费方靠 `typeof loadMemory==='function'` 鸭子探测还原——现显式命名,供 typed 装配。
|
|
28
|
+
*/
|
|
29
|
+
interface ContextInjectionPort {
|
|
30
|
+
loadMemory(): Promise<void>;
|
|
31
|
+
getMemoryPrompt(): string;
|
|
32
|
+
}
|
|
33
|
+
interface ContextBuildInput {
|
|
34
|
+
readonly sessionId: string;
|
|
35
|
+
/** 工作区目录(用于读取文件/检测环境)。 */
|
|
36
|
+
readonly workspaceDir?: string;
|
|
37
|
+
/** 可选的 memory 上下文注入端口。 */
|
|
38
|
+
readonly memory?: ContextInjectionPort;
|
|
39
|
+
}
|
|
40
|
+
declare class ContextSourceRegistry {
|
|
41
|
+
private readonly sources;
|
|
42
|
+
private sealed;
|
|
43
|
+
register(source: ContextSource): void;
|
|
44
|
+
list(): ReadonlyArray<ContextSource>;
|
|
45
|
+
/**
|
|
46
|
+
* 产出 HookSpec[](经 session-stable 包装),可注册进 hookRegistry。
|
|
47
|
+
* 调用后 registry 只读——不允许运行时增删源。
|
|
48
|
+
*
|
|
49
|
+
* `workspaceDir` 和 `memory` 在 hook 构建期闭包捕获(由 createAgentRuntime 传入);
|
|
50
|
+
* `sessionId` 由 hook 运行时 SystemPromptTransformInput 提供(每个会话独立)。
|
|
51
|
+
*
|
|
52
|
+
* RFC-129 D4:重命名自 `buildHooks()`(纯改名,行为不变——调用即 seal)。命名变化表达
|
|
53
|
+
* 调用方必须保证的不变量:插件贡献的 context source(若有)必须在调用本方法**之前**
|
|
54
|
+
* 完成 `register()`(App 侧调用顺序见 RFC-129 D4 精确插入位置);本方法自身不感知
|
|
55
|
+
* 插件、不做任何"等待插件"的逻辑——它仍是"此刻立即 seal 并构建"的同步语义。
|
|
56
|
+
*/
|
|
57
|
+
sealAndBuildHooks(workspaceDir?: string, memory?: ContextBuildInput['memory']): HookSpec[];
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/context-sources/memory-delta.d.ts
|
|
61
|
+
/**
|
|
62
|
+
* memory-delta(#3 volatile memory delta)
|
|
63
|
+
*
|
|
64
|
+
* session-stable 的 memory-index 把 memory 注入按 sessionId 冻结在 turn-1 快照(cache 锚点),
|
|
65
|
+
* 代价是会话中途 `memory_record`/编辑 AGENTS.md 的更新要到下一会话才生效。
|
|
66
|
+
*
|
|
67
|
+
* 本钩子补这个缺口:每轮把「相对 turn-1 基线发生变化的 memory 文件」以 **volatile 尾段**
|
|
68
|
+
* (output.systemTail,落在 prompt cache 断点之后)即时注入——既即时生效又不击穿会话稳定的
|
|
69
|
+
* memory 前缀。基线在首轮(memory 加载后)快照,session.deleted 驱逐。
|
|
70
|
+
*
|
|
71
|
+
* 注意:priority 必须 > memory-index(35),确保首轮捕获基线时 memory 已加载。
|
|
72
|
+
*
|
|
73
|
+
* **预算门(system prompt 体量治理 P0 的旁路缺口修复)**:`buildMemoryInjection`
|
|
74
|
+
* (`@x-otto/memory`)给会话首轮的稳定 memory 前缀加了总预算门,但本文件独立格式化
|
|
75
|
+
* volatile delta、不经过那道门——会话内多次 `memory_record` 大内容或反复编辑大文件,
|
|
76
|
+
* delta 段可无界累积进 `systemTail`。`runtime` 不依赖 `@x-otto/memory`(避免引入跨包
|
|
77
|
+
* 耦合/潜在循环依赖),故预算逻辑在本文件内自持一份最小实现,不与 `@x-otto/memory`
|
|
78
|
+
* 共享代码——两处判断口径一致(同款 CJK 加权估算 + 字符边界截断),但物理独立。
|
|
79
|
+
*/
|
|
80
|
+
interface MemoryDeltaPort {
|
|
81
|
+
/** 当前 memory 源映射(path → content)。MemoryManager.getMemories() 即此形态。 */
|
|
82
|
+
getMemories(): ReadonlyMap<string, string>;
|
|
83
|
+
}
|
|
84
|
+
declare function createMemoryDeltaHooks(memory: MemoryDeltaPort, priority?: 60): HookSpec[];
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/session/archive-recall-port.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* archive-recall-port.ts — RFC-340 M2:会话归档召回的语义端口。
|
|
89
|
+
*
|
|
90
|
+
* 第四个 memory 语义端口(前三个:`MemoryPort` 压缩/裁剪、`ContextInjectionPort`
|
|
91
|
+
* 注入、`MemoryDeltaPort` 增量)。分立而非并入既有端口的理由与 RFC-067 M113 的
|
|
92
|
+
* 端口切分同源——**消费方需要的能力子集不同**:压缩路径不需要召回、召回路径不需要
|
|
93
|
+
* 压缩,合并会让两侧都被迫依赖用不到的方法。
|
|
94
|
+
*
|
|
95
|
+
* 唯一实现仍是 `@x-otto/memory` 的 `MemoryManager`(结构上已满足,无需改动它)。
|
|
96
|
+
*/
|
|
97
|
+
interface ArchiveRecallPort {
|
|
98
|
+
/**
|
|
99
|
+
* 读取一段被压缩折叠的历史全文。`archivePath` 形如 `archive:<sessionId>:<n>`。
|
|
100
|
+
*
|
|
101
|
+
* 实现侧优先从会话条目按 compaction 边界重建(RFC-159 保证 entries 永不删行,
|
|
102
|
+
* 故历史始终可重建),失败才回退归档存储。
|
|
103
|
+
*/
|
|
104
|
+
readArchive(archivePath: string): Promise<string>;
|
|
105
|
+
/** 列举某会话已发生的压缩归档(供定位要读哪一段)。 */
|
|
106
|
+
listArchives(sessionId: string): Promise<Array<{
|
|
107
|
+
path: string;
|
|
108
|
+
timestamp: number;
|
|
109
|
+
messageCount: number;
|
|
110
|
+
summary: string;
|
|
111
|
+
}>>;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/session/memory-transform.d.ts
|
|
115
|
+
/**
|
|
116
|
+
* RFC-067 M113(A3/D3) + RFC-340 M2:完整 memory 子系统 = 四语义端口的交集——
|
|
117
|
+
* `MemoryPort`(compaction/prune)+ `ContextInjectionPort`(loadMemory/getMemoryPrompt)
|
|
118
|
+
* + `MemoryDeltaPort`(getMemories)+ `ArchiveRecallPort`(readArchive/listArchives,
|
|
119
|
+
* RFC-340 M2 补上"压缩折叠的历史可召回"这一环)。唯一实现 `@x-otto/memory` 的
|
|
120
|
+
* `MemoryManager` 结构上满足四者。
|
|
121
|
+
*
|
|
122
|
+
* 此前整个 seam(App.memory / context registry / hooks 注册)只把它当单端口 `MemoryProcessor`
|
|
123
|
+
* (=MemoryPort) 穿,injection/delta facet 被静态擦除→消费方靠 `typeof getMemories==='function'`
|
|
124
|
+
* 鸭子探测 + `as never` 还原。改用本 typed 交集后,seam 直接拿全能力,删全部 duck-typing/as never。
|
|
125
|
+
*
|
|
126
|
+
* 仅用于「需要注入/delta facet 的 seam」;纯压缩消费方(session-manager/agent-session)照用窄
|
|
127
|
+
* `MemoryPort`(MemorySubsystem 可赋给它)。
|
|
128
|
+
*/
|
|
129
|
+
interface MemorySubsystem extends MemoryPort$1, ContextInjectionPort, MemoryDeltaPort, ArchiveRecallPort {}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/session/types.d.ts
|
|
132
|
+
/**
|
|
133
|
+
* 经过宿主完整解析后的 session 配置。
|
|
134
|
+
* 所有业务字段均已确定,无需再做 merge。
|
|
135
|
+
* Manager 层只接受此类型来创建 session,不持有业务默认值。
|
|
136
|
+
*/
|
|
137
|
+
interface ResolvedSessionConfig {
|
|
138
|
+
model: Model;
|
|
139
|
+
/**
|
|
140
|
+
* RFC-078 M134:标题生成用的便宜模型(宿主经 RFC-061 解析为同 provider 最便宜真实模型)。
|
|
141
|
+
* 缺省 → AgentSession 不启用 LLM 标题生成(仅 derived 占位)。
|
|
142
|
+
*/
|
|
143
|
+
titleModel?: Model;
|
|
144
|
+
/**
|
|
145
|
+
* 模型决策溯源——选中模型由哪一层决定(explicit/slot/category/default/fallback)
|
|
146
|
+
* 及被 usability gate 拒绝的候选。供 trace/inspector 解释「为何用这个模型」。宿主解析时填充。
|
|
147
|
+
*/
|
|
148
|
+
modelResolution?: ModelResolution;
|
|
149
|
+
agentName?: string;
|
|
150
|
+
systemPrompt: string;
|
|
151
|
+
/**
|
|
152
|
+
* 会话恒定的 volatile system 尾段(落在 prompt cache 断点之后)。宿主据配置填充——典型用例:
|
|
153
|
+
* append 模式子代理角色块(base system prompt 进缓存共享,角色差异走断点后尾段)。
|
|
154
|
+
*/
|
|
155
|
+
systemTail?: string[];
|
|
156
|
+
tools: AgentTool[];
|
|
157
|
+
thinkingLevel: ThinkingLevel;
|
|
158
|
+
maxToolTurns: number;
|
|
159
|
+
maxToolTurnExtensions?: number;
|
|
160
|
+
/** RFC-104 D3:per-prompt output-token 预算(配置后轮次上限 ×4 退位安全阀)。 */
|
|
161
|
+
promptOutputTokenBudget?: number;
|
|
162
|
+
/** RFC-104 D3:per-prompt 墙钟预算(ms)。 */
|
|
163
|
+
promptWallClockBudgetMs?: number;
|
|
164
|
+
/**
|
|
165
|
+
* RFC-203/RFC-204 D4:早期停滞检测配置(第四层透传——ResolvedSessionConfig 是
|
|
166
|
+
* `SessionManager.createSession()` 真实接收的类型,与 AgentSessionOptions 是两份独立
|
|
167
|
+
* 结构,`buildAgentSession()` 逐字段映射;实现时才发现的第四道断链,同批修复)。
|
|
168
|
+
* 缺省启用;`false` 显式关闭。
|
|
169
|
+
*/
|
|
170
|
+
stallDetection?: {
|
|
171
|
+
windowTurns?: number;
|
|
172
|
+
repeatThreshold?: number;
|
|
173
|
+
} | false;
|
|
174
|
+
/** RFC-204 D1/D2:会话累计成本软预算(美元)。缺省 = 不启用检测。 */
|
|
175
|
+
sessionCostBudgetUSD?: number;
|
|
176
|
+
/** RFC-094:todo 停机闭环硬闸配置(缺省从 env 解析:默认开、cap=3)。 */
|
|
177
|
+
todoContinuation?: {
|
|
178
|
+
enabled: boolean;
|
|
179
|
+
max: number;
|
|
180
|
+
};
|
|
181
|
+
promptRefresh?: PromptRefresh;
|
|
182
|
+
workspaceDir: string;
|
|
183
|
+
workspaceId?: string;
|
|
184
|
+
transient?: boolean;
|
|
185
|
+
depth?: number;
|
|
186
|
+
interactive?: boolean;
|
|
187
|
+
retryAccelerate?: {
|
|
188
|
+
skip: boolean;
|
|
189
|
+
};
|
|
190
|
+
/** RFC-230:流式重试策略投影(从宿主 `ResilienceConfig.stream` 而来)。缺省 = agent 包内部默认值。 */
|
|
191
|
+
streamRetry?: StreamRetryConfig;
|
|
192
|
+
/** RFC-230:网络断连重试策略投影(从宿主 `ResilienceConfig.networkDisconnect` 而来)。缺省 = agent 包内部默认值。 */
|
|
193
|
+
networkDisconnectRetry?: StreamRetryConfig;
|
|
194
|
+
/**
|
|
195
|
+
* 会话级「始终允许」工具名(弹窗运行时授权)。随快照持久化,--continue 回灌 PermissionRegistry
|
|
196
|
+
* 会话级集合——一次性授权跟随会话、不泄漏到项目其它会话。宿主(App)据此桥接 registry↔持久化。
|
|
197
|
+
*/
|
|
198
|
+
permissionAlwaysAllow?: string[];
|
|
199
|
+
/**
|
|
200
|
+
* 会话级「账号可用模型 id」集合(实时拉取/刷新确认)。随快照持久化、--continue 回灌;
|
|
201
|
+
* list_models 工具据此标记/过滤可用模型,供大模型为 subagent 选型。
|
|
202
|
+
*/
|
|
203
|
+
availableModels?: string[];
|
|
204
|
+
/**
|
|
205
|
+
* RFC-181 D3/M3:图像委托描述端口(宿主 VisionDelegateService 实现,coding 层的
|
|
206
|
+
* SessionConfigResolver 装配时注入)。缺省 = 主模型不支持视觉时,image block 降级为
|
|
207
|
+
* 占位文本(见 @x-otto/agent 的 vision-gate.ts 完整设计说明)。
|
|
208
|
+
*/
|
|
209
|
+
describeImages?: DescribeImagesPort;
|
|
210
|
+
}
|
|
211
|
+
interface AgentSessionOptions {
|
|
212
|
+
id?: string;
|
|
213
|
+
/**
|
|
214
|
+
* RFC-324 D1/R5:turn-gated 压缩的超时兜底(毫秒)惰性读数。
|
|
215
|
+
*
|
|
216
|
+
* 必须是 getter 而非静态值——宿主 settings 在 App 构造**之后**才 load,
|
|
217
|
+
* 构造期快照会永远读到默认值(这正是 `compaction_timeout_ms` 此前零消费的成因)。
|
|
218
|
+
* 未注入时回退 `SESSION_COMPACTION_TIMEOUT_MS`(其本身已含 env 覆盖)。
|
|
219
|
+
*/
|
|
220
|
+
getCompactionTimeoutMs?: () => number | undefined;
|
|
221
|
+
model: Model;
|
|
222
|
+
/** 模型决策溯源(为何选中此 model)——透传给观测层供 inspector 显示。 */
|
|
223
|
+
modelResolution?: ModelResolution;
|
|
224
|
+
agentName?: string;
|
|
225
|
+
systemPrompt?: string;
|
|
226
|
+
/** 会话恒定的 volatile system 尾段种子(append 子代理角色块等);透传至 PromptParams.systemTail。 */
|
|
227
|
+
systemTail?: string[];
|
|
228
|
+
tools?: AgentTool[];
|
|
229
|
+
thinkingLevel?: ThinkingLevel;
|
|
230
|
+
maxToolTurns?: number;
|
|
231
|
+
maxToolTurnExtensions?: number;
|
|
232
|
+
/** RFC-104 D3:per-prompt output-token 预算(AgentSessionOptions 透传面)。 */
|
|
233
|
+
promptOutputTokenBudget?: number;
|
|
234
|
+
/** RFC-104 D3:per-prompt 墙钟预算(ms)。 */
|
|
235
|
+
promptWallClockBudgetMs?: number;
|
|
236
|
+
/** RFC-104 D2 缺省:顶层默认开、子 agent 默认关;OTTO_BUDGET_STEER_WRAPUP=1/0 覆盖。 */
|
|
237
|
+
budgetSteerWrapUp?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* RFC-203/RFC-204 D4:早期停滞检测配置透传(第三层,此前 AgentConfig/agent.ts 两层
|
|
240
|
+
* 断链已在 RFC-204 M2 修复)。缺省启用(走 ENGINE_DEFAULTS);`false` 显式关闭。
|
|
241
|
+
*/
|
|
242
|
+
stallDetection?: {
|
|
243
|
+
windowTurns?: number;
|
|
244
|
+
repeatThreshold?: number;
|
|
245
|
+
} | false;
|
|
246
|
+
/**
|
|
247
|
+
* RFC-204 D1/D2:会话累计成本软预算(美元)。超过阈值时一次性 emit
|
|
248
|
+
* `session.cost-budget-exceeded`(不阻断)。缺省 = 不启用预算检测。
|
|
249
|
+
*/
|
|
250
|
+
sessionCostBudgetUSD?: number;
|
|
251
|
+
/**
|
|
252
|
+
* RFC-094:todo 停机闭环硬闸——prompt 结束后若 todoList 仍有未完成项,自动续跑(cap 轮内)。
|
|
253
|
+
* 缺省从 OTTO_TODO_CONTINUE / OTTO_TODO_CONTINUE_MAX 解析(默认开、cap=3);depth>0 恒不启闸。
|
|
254
|
+
*/
|
|
255
|
+
todoContinuation?: {
|
|
256
|
+
enabled: boolean;
|
|
257
|
+
max: number;
|
|
258
|
+
};
|
|
259
|
+
workspaceDir?: string;
|
|
260
|
+
/** workspace 逻辑标识(`workspaceRef.id`),按 workspace 过滤会话列表时使用。 */
|
|
261
|
+
workspaceId?: string;
|
|
262
|
+
transient?: boolean;
|
|
263
|
+
hookRegistry: HookRegistry;
|
|
264
|
+
stream: StreamFunction;
|
|
265
|
+
/**
|
|
266
|
+
* RFC-078 M134:标题生成用的便宜模型(app 层经 RFC-061 `category:'search'` + usability gate 解析)。
|
|
267
|
+
* 缺省 → 不启用 LLM 标题生成(仅保留 derived 启发式占位)。禁用对话模型/claude-OAuth(R3)。
|
|
268
|
+
*/
|
|
269
|
+
titleModel?: Model;
|
|
270
|
+
logger: Logger;
|
|
271
|
+
devtools?: Devtools;
|
|
272
|
+
promptRefresh?: PromptRefresh;
|
|
273
|
+
memory?: MemoryPort;
|
|
274
|
+
recorder?: TraceRecorder;
|
|
275
|
+
/** RFC-145 D3:nondet batch 冲洗回调(透传给 Agent → work-loop)。缺省 = 不聚合。 */
|
|
276
|
+
nondetFlush?: () => void;
|
|
277
|
+
clock?: ClockPort;
|
|
278
|
+
depth?: number;
|
|
279
|
+
interactive?: boolean;
|
|
280
|
+
retryAccelerate?: {
|
|
281
|
+
skip: boolean;
|
|
282
|
+
};
|
|
283
|
+
/** RFC-230:流式重试策略投影,见 `ResolvedSessionConfig.streamRetry` 同名字段注释。 */
|
|
284
|
+
streamRetry?: StreamRetryConfig;
|
|
285
|
+
/** RFC-230:网络断连重试策略投影,见 `ResolvedSessionConfig.networkDisconnectRetry` 同名字段注释。 */
|
|
286
|
+
networkDisconnectRetry?: StreamRetryConfig;
|
|
287
|
+
/**
|
|
288
|
+
* RFC-181 D3/M3:图像委托描述端口(宿主 VisionDelegateService 实现,coding 层在会话
|
|
289
|
+
* 装配时注入)。缺省 = 主模型不支持视觉时,image block 降级为占位文本(见
|
|
290
|
+
* @x-otto/agent 的 vision-gate.ts 完整设计说明)。
|
|
291
|
+
*/
|
|
292
|
+
describeImages?: DescribeImagesPort;
|
|
293
|
+
}
|
|
294
|
+
interface AgentSessionInfo {
|
|
295
|
+
id: string;
|
|
296
|
+
title?: string;
|
|
297
|
+
messageCount: number;
|
|
298
|
+
createdAt: Date;
|
|
299
|
+
updatedAt?: Date;
|
|
300
|
+
/** 分配模型 id(listSessions 填充自 AgentSession.model.id;service /sessions 读作 title 回退)。 */
|
|
301
|
+
model?: string;
|
|
302
|
+
status: string;
|
|
303
|
+
/** 创建此会话的 SDK 版本(`@x-otto/env` package version)。旧会话可能无此字段。 */
|
|
304
|
+
sdkVersion?: string;
|
|
305
|
+
/** workspace 逻辑标识(与 workspaceRef.id 对应)。按 workspace 过滤时使用。 */
|
|
306
|
+
workspaceId?: string;
|
|
307
|
+
/** 是否置顶(/resume 列表排序权重)。缺省 false。 */
|
|
308
|
+
pinned?: boolean;
|
|
309
|
+
/** 置顶分组名(RFC-158)。仅 pinned=true 时生效;缺省落入默认组(面板显示"置顶")。 */
|
|
310
|
+
pinGroup?: string;
|
|
311
|
+
/**
|
|
312
|
+
* 是否被另一存活进程持有写租约(RFC-159 D3)——切进去将只读,改动不会保存。
|
|
313
|
+
* 缺省 false/undefined(无租约机制的后端,如远端/内存,恒不标注)。
|
|
314
|
+
*/
|
|
315
|
+
writeLocked?: boolean;
|
|
316
|
+
/**
|
|
317
|
+
* 会话真实条目总数(不同于 messageCount——本地冷会话枚举路径 listColdSessionsOverride
|
|
318
|
+
* 出于性能考虑,messageCount 恒填 0 占位,不逐条查询)。用于区分"从未 prompt 过的
|
|
319
|
+
* 真空会话"与"有内容但未加载进内存的冷会话",供 /resume 列表过滤真空会话展示。
|
|
320
|
+
* 缺省 undefined = 未知(内存态 listSessions 用 messages().length 即真实值,无需
|
|
321
|
+
* 额外填充;仅本地冷会话/远端会话路径才会显式填充本字段)。
|
|
322
|
+
*/
|
|
323
|
+
entryCount?: number;
|
|
324
|
+
}
|
|
325
|
+
interface PromptOptions {
|
|
326
|
+
images?: Array<{
|
|
327
|
+
type: 'image';
|
|
328
|
+
data: string;
|
|
329
|
+
mediaType: string;
|
|
330
|
+
}>;
|
|
331
|
+
streamingBehavior?: 'steer' | 'followUp';
|
|
332
|
+
}
|
|
333
|
+
type PromptRefresh = () => Promise<string | undefined>;
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/session/agent-session.d.ts
|
|
336
|
+
declare function defaultMaxToolTurnExtensions(depth: number, transient?: boolean): number;
|
|
337
|
+
/**
|
|
338
|
+
* RFC-094 D4:todo 硬闸续跑配置解析(env,语义延续 RFC-042 D5-r)——
|
|
339
|
+
* 默认开(opt-out):显式 `OTTO_TODO_CONTINUE=0/off/false/no` 才关;
|
|
340
|
+
* cap 默认 3,允许 0(=不续),NaN/Infinity/负数/非法 → 回落 3。
|
|
341
|
+
*/
|
|
342
|
+
declare function resolveTodoContinuationConfig(env: {
|
|
343
|
+
OTTO_TODO_CONTINUE?: string;
|
|
344
|
+
OTTO_TODO_CONTINUE_MAX?: string;
|
|
345
|
+
}): {
|
|
346
|
+
enabled: boolean;
|
|
347
|
+
max: number;
|
|
348
|
+
};
|
|
349
|
+
declare class AgentSession extends EventBus<AgentSessionEventMap> {
|
|
350
|
+
readonly session: Session<AgentMessage>;
|
|
351
|
+
readonly workspaceDir: string;
|
|
352
|
+
readonly workspaceId?: string;
|
|
353
|
+
private agent;
|
|
354
|
+
private disposed;
|
|
355
|
+
private promptGate;
|
|
356
|
+
/** RFC-078 M134:会话标题生成(post-RFC-092 review F7 拆出为独立 concern)。 */
|
|
357
|
+
private readonly titleManager;
|
|
358
|
+
/**
|
|
359
|
+
* 本次 prompt 是否已真正进入过 in-flight run(见过 status→streaming)。用于把 promptGate 期细分为
|
|
360
|
+
* 「prep 期(run 即将来)」vs「收尾期(run 已结束,钩子 chat.message.after/session.idle/session.error
|
|
361
|
+
* 仍在 await)」——两者 agent.status 相同(上一/本回合终态)、isExecuting 都为 false,无法用状态区分。
|
|
362
|
+
* abort 只在 prep 期才允许 arm pendingAbort;收尾期 arm 会毒化下一回合(completed/error/aborted
|
|
363
|
+
* settling 静默黑洞)。每次 prompt 入口重置,status→streaming 时置 true。
|
|
364
|
+
*/
|
|
365
|
+
private runStartedThisPrompt;
|
|
366
|
+
/**
|
|
367
|
+
* RFC-194 D1:in-flight prompt 生命周期 Promise——clearContext() busy 守卫的 settle
|
|
368
|
+
* 依赖。prompt() 入口(isBusy 守卫后)赋值,finally 清除;空闲时 undefined。
|
|
369
|
+
*/
|
|
370
|
+
private currentPrompt;
|
|
371
|
+
/** RFC-324 D1 M2:逃生阀压缩的 pending Promise——requestEscapeValveCompaction 写回,
|
|
372
|
+
* runPromptRound 开头 await。解决 fire-and-forget 压缩不保证收敛的问题(缺口 A)。
|
|
373
|
+
* 仅 gate 逃生阀压缩(字节维度);主循环压缩已在 prompt 管线内同步 await。 */
|
|
374
|
+
compactionPending: Promise<{
|
|
375
|
+
compacted: boolean;
|
|
376
|
+
before: number;
|
|
377
|
+
after: number;
|
|
378
|
+
}> | undefined;
|
|
379
|
+
/** RFC-324 D1/R5:turn-gate 超时兜底的惰性读数(见 AgentSessionOptions 同名字段)。 */
|
|
380
|
+
private readonly getCompactionTimeoutMs?;
|
|
381
|
+
model: Model;
|
|
382
|
+
/** 模型决策溯源(哪层选中 + 拒了谁)——供 task-runner 透传观测层。 */
|
|
383
|
+
modelResolution?: ModelResolution;
|
|
384
|
+
tools: AgentTool[];
|
|
385
|
+
systemPrompt: string;
|
|
386
|
+
/** 会话恒定的 volatile system 尾段种子(append 子代理角色块等);透传至每轮 PromptParams.systemTail。 */
|
|
387
|
+
systemTail?: string[];
|
|
388
|
+
agentName: string;
|
|
389
|
+
depth: number;
|
|
390
|
+
maxToolTurns: number;
|
|
391
|
+
maxToolTurnExtensions: number;
|
|
392
|
+
/** RFC-104 D3:per-prompt 预算(fork 拷贝面;depth>0 构造时已被剥除)。 */
|
|
393
|
+
promptOutputTokenBudget?: number;
|
|
394
|
+
promptWallClockBudgetMs?: number;
|
|
395
|
+
thinkingLevel: ThinkingLevel;
|
|
396
|
+
promptRefresh?: PromptRefresh;
|
|
397
|
+
private logger;
|
|
398
|
+
private hookRegistry;
|
|
399
|
+
private devtools?;
|
|
400
|
+
private memory?;
|
|
401
|
+
private readonly recorder?;
|
|
402
|
+
private readonly describeImages?;
|
|
403
|
+
/** RFC-145 D3:nondet batch 冲洗回调。 */
|
|
404
|
+
private readonly nondetFlush?;
|
|
405
|
+
private readonly clock?;
|
|
406
|
+
/** RFC-337 D2:steer 消息 id 单调计数器(会话内唯一,供 ESC 撤回按序移除)。 */
|
|
407
|
+
private steerSeq;
|
|
408
|
+
private readonly persistence;
|
|
409
|
+
/**
|
|
410
|
+
* RFC-336 D2:输入边界落盘回调(由 `PersistenceSync.track()` 注入,同
|
|
411
|
+
* `setReplacementStripEligibility` 的既有模式)。未注入 = 该会话不做输入边界落盘
|
|
412
|
+
* (transient 子会话、in-memory 后端、snapshot 远端后端均属此列)。
|
|
413
|
+
*/
|
|
414
|
+
private inputBoundaryFlusher?;
|
|
415
|
+
/** RFC-336 D2:供 SessionPool 侧注入输入边界落盘策略;见 `inputBoundaryFlusher`。 */
|
|
416
|
+
setInputBoundaryFlusher(flush: () => void): void;
|
|
417
|
+
/** HITL 三 gate(approval/grill/plan)生命周期,RFC-199 D3 提取(agent-session-gates.ts)。 */
|
|
418
|
+
private readonly gates;
|
|
419
|
+
private readonly interactive;
|
|
420
|
+
private readonly agentUnsubs;
|
|
421
|
+
private readonly costTracker;
|
|
422
|
+
/** RFC-204 D1/D2:会话级成本软预算阈值(美元);undefined = 不启用检测。 */
|
|
423
|
+
private readonly sessionCostBudgetUSD?;
|
|
424
|
+
/** RFC-204 D2:本会话是否已越过成本预算并提醒过(一次性,session 生命周期内不重复)。 */
|
|
425
|
+
private sessionCrossedCostBudget;
|
|
426
|
+
private lastTokenUsage;
|
|
427
|
+
/**
|
|
428
|
+
* RFC-019 M1:本回合缓存命中率(R1 = cacheRead / totalInput)。
|
|
429
|
+
* `null` = 供应商无缓存 API 或本回合无 usage 数据 —— 消费方(TUI summary 行)据此隐藏该字段,
|
|
430
|
+
* 不展示可能永远是 0 的误导性数字(见 docs/rfc/RFC-019-cache-hit-rate-metrics.md)。
|
|
431
|
+
*/
|
|
432
|
+
private lastCacheHitRatio;
|
|
433
|
+
/** RFC-094:todo 停机闭环硬闸(post-RFC-092 review F7 拆出为独立 concern)。 */
|
|
434
|
+
private readonly todoGateRunner;
|
|
435
|
+
/**
|
|
436
|
+
* RFC-094 D4(评审细化):闸门循环轮间隙(isExecuting=false)的 abort 捕获——
|
|
437
|
+
* 既有 abort() 守卫在间隙期是 no-op,此标记让闸门循环下一轮续跑前感知中断。
|
|
438
|
+
*/
|
|
439
|
+
private abortRequested;
|
|
440
|
+
/**
|
|
441
|
+
* RFC-180 D2:turn 边界软停止请求标志(与 abortRequested 同构但语义独立)。置位后
|
|
442
|
+
* workLoop 在下一个安全的 turn 边界(onTurnPersist 已落盘)正常 return,不检查
|
|
443
|
+
* followUp、不触发 todo 闸门续跑。区别于 abort:不打断 in-flight 执行,会话保持
|
|
444
|
+
* 在正常可续跑状态(`Agent.status` 仍是 'completed',非 'aborted')。
|
|
445
|
+
*
|
|
446
|
+
* `isBusy` 判定本身不受本标志影响(正交状态)——暂停生效后 `promptGate` 复位为
|
|
447
|
+
* false、`isExecuting` 为 false,`isBusy` 天然变回 false。这是 D6 判忙链路必须
|
|
448
|
+
* 额外 OR `isPaused` 的原因,也是 D9 SessionPool 驱逐必须额外豁免 `isPaused` 的
|
|
449
|
+
* 原因(否则暂停态会话在 isBusy 视角下与"已空闲"完全无法区分)。
|
|
450
|
+
*/
|
|
451
|
+
private pauseRequested;
|
|
452
|
+
get id(): string;
|
|
453
|
+
get title(): string | undefined;
|
|
454
|
+
get status(): string;
|
|
455
|
+
/** 置顶状态(/resume 列表排序权重,不影响是否可输入)。 */
|
|
456
|
+
get pinned(): boolean;
|
|
457
|
+
/** 置顶分组名(RFC-158)。仅 pinned=true 时有意义;未置顶或默认组时为 undefined。 */
|
|
458
|
+
get pinGroup(): string | undefined;
|
|
459
|
+
/**
|
|
460
|
+
* 暴露底层 Agent 供观测层接入(只读;attachAgentObserver 用)。
|
|
461
|
+
* task-runner 在跑 task_delegate/fork 子会话时据此把运行态接入 globalAgentObservability。
|
|
462
|
+
*/
|
|
463
|
+
get coreAgent(): Agent;
|
|
464
|
+
get isExecuting(): boolean;
|
|
465
|
+
/**
|
|
466
|
+
* prompt 全生命周期忙判定。isExecuting 只覆盖 streaming/tool_executing,
|
|
467
|
+
* 漏掉 promptRefresh/buildContext/hooks 等流式开始前的窗口(此时 promptGate 已置位);
|
|
468
|
+
* 池驱逐(idle/LRU)必须用本判定,否则会 dispose 掉一个 prompt 进行中的会话。
|
|
469
|
+
*/
|
|
470
|
+
get isBusy(): boolean;
|
|
471
|
+
/**
|
|
472
|
+
* RFC-090 M0:renderer pull 式获取 session 元数据,替代 session.ready 推事件(避免竞态)。
|
|
473
|
+
* 返回纯数据对象,同进程直接调用;进程分离后(RFC-091)通过一次性请求/响应透传。
|
|
474
|
+
*/
|
|
475
|
+
getSessionInfo(): {
|
|
476
|
+
sessionId: string;
|
|
477
|
+
modelId: string;
|
|
478
|
+
modelName: string;
|
|
479
|
+
systemPromptLength: number;
|
|
480
|
+
tools: string[];
|
|
481
|
+
};
|
|
482
|
+
constructor(session: Session<AgentMessage>, options: AgentSessionOptions);
|
|
483
|
+
/**
|
|
484
|
+
* 由 SessionPool / 宿主类在 session.created hook 执行完毕后调用。
|
|
485
|
+
* 此时 EventBridge 已完成订阅,session.start 事件可被正确接收。
|
|
486
|
+
*/
|
|
487
|
+
notifyCreated(): void;
|
|
488
|
+
onStreamEvent: ({
|
|
489
|
+
event
|
|
490
|
+
}: {
|
|
491
|
+
event: StreamEvent;
|
|
492
|
+
}) => Promise<void>;
|
|
493
|
+
private onTurnStart;
|
|
494
|
+
private onTurnEnd;
|
|
495
|
+
private onToolCallStart;
|
|
496
|
+
private onToolCallEnd;
|
|
497
|
+
/**
|
|
498
|
+
* RFC-094 D2:todoList 会话持久化单源基线——write_todos 成功后把权威快照
|
|
499
|
+
* (result.details.todos,set/update 均为合并后完整列表)写进 session metadata。
|
|
500
|
+
* 修活非 CLI 前端(service/ACP)的 todo-reminder:其数据源 = metadata().config.todoList,
|
|
501
|
+
* 此前全仓只有 CLI 投影层回写,service 模式下 reminder 永不注入。
|
|
502
|
+
* 保序:publish 之前同步写 baseline;CLI 订阅回调随后覆写 turn-enriched 版(携回合号)。
|
|
503
|
+
* 既有条目的 turn 按 id 保留,runtime 不知道回合投影、不清 CLI 写入的 turn。
|
|
504
|
+
*/
|
|
505
|
+
private syncTodoListFromToolResult;
|
|
506
|
+
/** 本次 prompt 是否触发了预算收尾 steering(供 runPrintMode 在优雅路径发提示)。 */
|
|
507
|
+
budgetSteeredThisPrompt: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* 归档当前会话:将会话状态置为 archived,禁止后续输入。
|
|
510
|
+
* 仅 idle/completed/error/aborted 等终态可归档。
|
|
511
|
+
* 同步写 `metadata.archived` 持久化镜像(运行时权威仍是 Agent 状态机),
|
|
512
|
+
* 供跨重启/驱逐 restore 后回放归档态(见 `PersistenceSync.restore`)。
|
|
513
|
+
*/
|
|
514
|
+
archive(): void;
|
|
515
|
+
/**
|
|
516
|
+
* 取消归档:将会话从 archived 恢复为 idle。
|
|
517
|
+
*/
|
|
518
|
+
unarchive(): void;
|
|
519
|
+
/**
|
|
520
|
+
* 置顶会话(/resume 列表排序权重)。与归档状态正交,不影响是否可输入。
|
|
521
|
+
* `group`(RFC-158):置顶分组名,空白字符串规整为默认组(`undefined`)。
|
|
522
|
+
*/
|
|
523
|
+
pin(group?: string): void;
|
|
524
|
+
/** 取消置顶。无条件清空分组(不保留旧分组,见 RFC-158 §4)。 */
|
|
525
|
+
unpin(): void;
|
|
526
|
+
prompt(text: string, options?: PromptOptions): Promise<void>;
|
|
527
|
+
/** prompt() 的执行体(RFC-194 D1 拆出——prompt() 需在 isBusy 守卫后拿到本次执行的 Promise)。 */
|
|
528
|
+
private executePrompt;
|
|
529
|
+
/** 跑一轮完整 prompt 管线(buildContext → workLoop → persist),回填会话级参数。
|
|
530
|
+
* hadToolActivity:本轮是否有真实工具调用产出(assistant tool_call 消息),供闸门
|
|
531
|
+
* 无进展判定区分"零动作卡死"(放行)与"有产出但没同步 todo 状态"(定向续跑一次)。
|
|
532
|
+
* images(RFC-111 D4b):仅首轮(外层 `prompt()` 调用)传入,todo 续跑等内部触发轮
|
|
533
|
+
* 不重复携带图片——避免同一张图片在续跑轮里被重复注入上下文。 */
|
|
534
|
+
private runPromptRound;
|
|
535
|
+
/**
|
|
536
|
+
* RFC-337 D2:注入一条 steer 消息并返回其稳定 `id`(供 cli 侧持有以便 ESC 撤回)。
|
|
537
|
+
*
|
|
538
|
+
* id 用会话内单调计数器生成(`steer-<n>`),不裸调 `randomUUID()`/`Date.now()`——steer 是
|
|
539
|
+
* UI 侧信道、不在确定性重放路径上,计数器已足够在队列生命周期内唯一,且天然确定性。
|
|
540
|
+
* id 写入 `Message.uuid`,`Agent.removeSteer(uuid)` 据此按序移除。
|
|
541
|
+
*/
|
|
542
|
+
steer(text: string): {
|
|
543
|
+
id: string;
|
|
544
|
+
};
|
|
545
|
+
/**
|
|
546
|
+
* RFC-337 D2:按 id 移除一条尚未被模型消费的 steer 消息,返回是否命中。
|
|
547
|
+
* 未命中(已被 `drainSteeringQueue` 交付进 LLM 上下文)时返回 `false`——调用方据此退化
|
|
548
|
+
* 为「中止整回合」(见 RFC-337 §D3)。转调 `Agent.removeSteer`,只操作内存态队列。
|
|
549
|
+
*/
|
|
550
|
+
removeSteer(id: string): boolean;
|
|
551
|
+
followUp(text: string): void;
|
|
552
|
+
abort(): void;
|
|
553
|
+
/**
|
|
554
|
+
* RFC-180 D4:请求在当前 turn 边界后暂停(不影响当前 in-flight turn,安全落盘后生效)。
|
|
555
|
+
* 空闲(`!isBusy`)时 no-op 并返回 false——暂停语义只对"正在跑"的 agent 有意义;
|
|
556
|
+
* 弹窗竞态场景(Esc 弹窗打开期间 agent 恰好跑完)静默忽略,与 abort() 的
|
|
557
|
+
* `inFlight`/`inPrep` 双重早退判定对齐。返回值供调用方感知(独立评审 F5):
|
|
558
|
+
* `false` 时 CLI 层应给出"Agent 已完成当前回合,无需暂停"的 toast,而非让用户
|
|
559
|
+
* 以为按钮没反应。
|
|
560
|
+
*/
|
|
561
|
+
pauseRun(): boolean;
|
|
562
|
+
/**
|
|
563
|
+
* RFC-186 D4:取消等待中的暂停请求——仅在 turn 边界尚未到达时有效(isBusy=true)。
|
|
564
|
+
* 暂停已生效(workLoop 已 break,isBusy=false)时返回 false——此时只能走 resumeRun(),
|
|
565
|
+
* 不能通过"取消"跳过已暂停态直接进入下一回合。返回值供调用方感知:false 时 CLI 层
|
|
566
|
+
* 应引导用户使用 /continue(而非 /pause),或 toast 提示"暂停已生效,请用 /continue 恢复"。
|
|
567
|
+
*/
|
|
568
|
+
cancelPauseRun(): boolean;
|
|
569
|
+
/**
|
|
570
|
+
* RFC-180 D4:解除暂停。未处于暂停态时 no-op(不重复 publish 事件)。清空标志后,
|
|
571
|
+
* `isBusy` 判定的暂停附加项自动消失——TUI 侧 `busyWithPause` 判忙链路下一次读取
|
|
572
|
+
* 即感知空闲,`drainQueue` 可正常出队。
|
|
573
|
+
*/
|
|
574
|
+
resumeRun(): void;
|
|
575
|
+
/** RFC-180 D4:是否已请求(或已生效)turn 边界暂停。与 `isBusy` 正交,见字段注释。 */
|
|
576
|
+
get isPaused(): boolean;
|
|
577
|
+
compact(summary: string, compactedCount: number): Promise<void>;
|
|
578
|
+
compactNow(signal?: AbortSignal): Promise<{
|
|
579
|
+
compacted: boolean;
|
|
580
|
+
before: number;
|
|
581
|
+
after: number;
|
|
582
|
+
}>;
|
|
583
|
+
private compactionDeps;
|
|
584
|
+
/**
|
|
585
|
+
* `/clear`——会话状态重置(非清屏)。在状态核打一道 clear 边界:
|
|
586
|
+
* · buildContext()(送 LLM)此后真空——模型上下文立即释放;
|
|
587
|
+
* · visibleMessages()(TUI/--continue 重绘)此后为空——界面不再重现旧内容;
|
|
588
|
+
* · messages()(完整转录)保留全部——时间旅行/审计仍可回溯 clear 前。
|
|
589
|
+
* 同时驱逐 L2 工作记忆桶(previousSummaries),保 L1 持久知识、不动 L3 归档。
|
|
590
|
+
* 不换 sessionId、不销毁会话(契合 otto 显式 id 的 --continue)。
|
|
591
|
+
* publish 'session.cleared' → persistence-sync 立即落盘(保证新进程 --continue 反映清后态)。
|
|
592
|
+
*/
|
|
593
|
+
clearContext(): Promise<void>;
|
|
594
|
+
/** 清除本轮临时面板状态(todo/编辑文件/子代理列表/回合摘要),保留对话历史。 */
|
|
595
|
+
clearTurnPanels(): void;
|
|
596
|
+
/** 重命名会话(custom 等级,永不被 ai/derived 覆盖)。 */
|
|
597
|
+
renameSession(title: string): void;
|
|
598
|
+
private promptUsage;
|
|
599
|
+
/** H5-20:本会话累计用量 + 成本汇总(供 /cost 命令 / TUI 状态栏消费)。 */
|
|
600
|
+
getCostSummary(): CostSummary;
|
|
601
|
+
/**
|
|
602
|
+
* RFC-019 M1:最近一回合的缓存命中率(R1)。`null` = 供应商无缓存 API 或本回合无 usage 数据
|
|
603
|
+
* ——消费方应据此隐藏该字段,而非展示误导性的 0。供 `/cache-stats`、summary 行消费。
|
|
604
|
+
*/
|
|
605
|
+
getLastCacheHitRatio(): number | null;
|
|
606
|
+
subscribe(callback: AgentSessionSubscriber): () => void;
|
|
607
|
+
subscribe<K extends AgentSessionEventType>(type: K, callback: AgentSessionEventMap[K], once?: boolean): () => void;
|
|
608
|
+
requestApproval(toolName: string, args: Record<string, unknown>, description: string, risk?: ApprovalRisk): Promise<boolean>;
|
|
609
|
+
resolveApproval(approvalId: string, approved: boolean): void;
|
|
610
|
+
requestGrill(q: Omit<GrillQuestion, 'id'>): Promise<GrillAnswer>;
|
|
611
|
+
resolveAskUser(requestId: string, answer: GrillAnswer | null): void;
|
|
612
|
+
requestPlanApproval(planContent: string): Promise<{
|
|
613
|
+
action: string;
|
|
614
|
+
feedback?: string;
|
|
615
|
+
}>;
|
|
616
|
+
resolvePlanApproval(requestId: string, action: string, feedback?: string): void;
|
|
617
|
+
private rejectPendingGates;
|
|
618
|
+
private ensureNotDisposed;
|
|
619
|
+
/**
|
|
620
|
+
* RFC-078 M135-01:显式回血——对 title 空/derived 的(已加载)会话强制重生 ai 标题,
|
|
621
|
+
* 绕过「前 3 回合」成本闸(回血是一次性批处理意图)。custom 仍受守卫保护。
|
|
622
|
+
*/
|
|
623
|
+
backfillTitle(): void;
|
|
624
|
+
dispose(): void;
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/session/checkpoint.d.ts
|
|
628
|
+
type RestoreMode = 'conversation' | 'code' | 'both';
|
|
629
|
+
interface FileEntrySnapshot {
|
|
630
|
+
path: string;
|
|
631
|
+
content: string | null;
|
|
632
|
+
}
|
|
633
|
+
interface FileSnapshot {
|
|
634
|
+
files: FileEntrySnapshot[];
|
|
635
|
+
}
|
|
636
|
+
interface CheckpointData {
|
|
637
|
+
sessionId: string;
|
|
638
|
+
label?: string;
|
|
639
|
+
createdAt: number;
|
|
640
|
+
traceSeq: number;
|
|
641
|
+
conversation: SessionSnapshot<AgentMessage>;
|
|
642
|
+
files?: FileSnapshot;
|
|
643
|
+
}
|
|
644
|
+
interface Checkpoint extends CheckpointData {
|
|
645
|
+
seq: number;
|
|
646
|
+
}
|
|
647
|
+
declare function captureFiles(workspaceDir: string, paths: string[]): Promise<FileSnapshot>;
|
|
648
|
+
declare function restoreFiles(workspaceDir: string, snapshot: FileSnapshot): Promise<void>;
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/session/sandbox.d.ts
|
|
651
|
+
type ReplayLevel = 'L1' | 'L2';
|
|
652
|
+
interface SandboxToolCall {
|
|
653
|
+
toolCallId: string;
|
|
654
|
+
name: string;
|
|
655
|
+
arguments: Record<string, unknown>;
|
|
656
|
+
}
|
|
657
|
+
interface SandboxToolResult {
|
|
658
|
+
toolCallId: string;
|
|
659
|
+
result: ToolResult;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 可插拔沙箱执行器。x-web-container 将提供实现(VFS / worker / syscall / capability 隔离)。
|
|
663
|
+
* L2 重执行经此接口在隔离环境真实重跑工具调用,保证宿主工作区不被重放副作用污染。
|
|
664
|
+
*/
|
|
665
|
+
interface SandboxRunner {
|
|
666
|
+
/** 在隔离沙箱内执行一次工具调用,返回真实结果(含副作用,但作用域限于沙箱)。 */
|
|
667
|
+
execute(call: SandboxToolCall): Promise<SandboxToolResult>;
|
|
668
|
+
/** 释放沙箱资源(VFS / worker 等)。 */
|
|
669
|
+
dispose(): Promise<void>;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* 请求 L2 但未注入 SandboxRunner 时抛出。明确"接口缝已留、实现待 x-web-container",避免静默降级到 L1
|
|
673
|
+
* 而让调用方误以为副作用被真实重跑。
|
|
674
|
+
*/
|
|
675
|
+
declare class SandboxUnavailableError extends Error {
|
|
676
|
+
constructor();
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/session/traceback.d.ts
|
|
680
|
+
interface TracebackView {
|
|
681
|
+
toSeq: number;
|
|
682
|
+
baselineCheckpointSeq: number | null;
|
|
683
|
+
fromTraceSeq: number;
|
|
684
|
+
events: TraceEvent[];
|
|
685
|
+
messages: Message[];
|
|
686
|
+
}
|
|
687
|
+
declare function messagesFromSnapshot(snapshot: SessionSnapshot): Message[];
|
|
688
|
+
declare function reconstructMessages(baseline: Message[], events: Iterable<TraceEvent>): Message[];
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/session/time-travel-controller.d.ts
|
|
691
|
+
interface TimeTravelDeps {
|
|
692
|
+
logger: Logger;
|
|
693
|
+
traceStore?: AppendLog<TraceEvent>;
|
|
694
|
+
checkpointStore?: AppendLog<CheckpointData>;
|
|
695
|
+
getSession: (id: string) => AgentSession | undefined;
|
|
696
|
+
getConfig: (id: string) => ResolvedSessionConfig | undefined;
|
|
697
|
+
/** replaySession 走 Manager.createSession 正门(容量/登记/hook/active 维护)。 */
|
|
698
|
+
createSession: (config: ResolvedSessionConfig & {
|
|
699
|
+
id?: string;
|
|
700
|
+
}, ports?: SessionPorts) => Promise<AgentSession>;
|
|
701
|
+
/** fork 容量预检(同名冲突 + 容量阈值驱逐),在复制 trace/checkpoint 流之前执行。 */
|
|
702
|
+
ensureCapacity: (id: string) => void;
|
|
703
|
+
/**
|
|
704
|
+
* fork 池登记(两路 fork 共用):构建 AgentSession + 登记 + 持久化追踪 + session.created hook
|
|
705
|
+
* + notifyCreated(一等可观测 + 落盘持久;不改 active——与历史 fork 行为一致)。
|
|
706
|
+
*/
|
|
707
|
+
adoptForkedSession: (raw: InMemorySession<AgentMessage>, config: ResolvedSessionConfig) => Promise<AgentSession>;
|
|
708
|
+
/**
|
|
709
|
+
* RFC-160 D3:剥离态 compaction 的 replacement 真身回填(持久层能力,缺省 no-op 直通)。
|
|
710
|
+
* checkpoint 捕获前必须调用(剥离态固化进冷存储 = 永久丢 replacement);restore 后
|
|
711
|
+
* 必须调用(旧 compaction 复位为末位边界,进 buildContext 的必须真身,RFC-142)。
|
|
712
|
+
*/
|
|
713
|
+
hydrateEntries?: (sessionId: string, entries: readonly SessionEntry<AgentMessage>[]) => Promise<SessionEntry<AgentMessage>[]>;
|
|
714
|
+
}
|
|
715
|
+
declare class TimeTravelController {
|
|
716
|
+
private readonly deps;
|
|
717
|
+
constructor(deps: TimeTravelDeps);
|
|
718
|
+
private replayPorts;
|
|
719
|
+
/**
|
|
720
|
+
* 确定性重放(L1)——以录制会话的 trace 构造新会话,引擎的非确定性输入全部回灌:
|
|
721
|
+
* clock/random/id(端口)+ LLM 流 + 工具结果(不真实调用 provider/工具)。
|
|
722
|
+
* 在该会话上重跑同一提示,产出与录制逐字节一致(含时间戳/assistant 内容/工具结果)。
|
|
723
|
+
* 复用源会话已解析的配置 + 读源会话 trace。
|
|
724
|
+
*
|
|
725
|
+
* **重现层级**:缺省 level='L1'(回灌录制工具结果,不真实执行副作用)。level='L2'
|
|
726
|
+
* 为全确定性沙箱重执行(沙箱内真实重跑工具),需注入 SandboxRunner(x-web-container 提供);未注入
|
|
727
|
+
* 即抛 SandboxUnavailableError(不静默降级到 L1)。L2 的实际执行接线待 x-web-container,见 sandbox.ts。
|
|
728
|
+
*
|
|
729
|
+
* @param sourceSessionId 已录制(且仍在 configs 中)的源会话 id
|
|
730
|
+
* @param newId 重放会话 id(缺省自动生成)
|
|
731
|
+
* @param opts 重现层级与(L2 时)沙箱 runner
|
|
732
|
+
*/
|
|
733
|
+
replaySession(sourceSessionId: string, newId?: string, opts?: {
|
|
734
|
+
level?: ReplayLevel;
|
|
735
|
+
sandbox?: SandboxRunner;
|
|
736
|
+
}): Promise<AgentSession>;
|
|
737
|
+
/**
|
|
738
|
+
* 建立 checkpoint —— 会话快照(必)+ 被追踪文件的内容快照(opts.files 提供时)。
|
|
739
|
+
* 是回溯/回滚的锚点。文件集由调用方给出(变更文件),属增量快照。
|
|
740
|
+
*/
|
|
741
|
+
createCheckpoint(sessionId: string, opts?: {
|
|
742
|
+
label?: string;
|
|
743
|
+
files?: string[];
|
|
744
|
+
}): Promise<Checkpoint>;
|
|
745
|
+
listCheckpoints(sessionId: string): Promise<Checkpoint[]>;
|
|
746
|
+
/**
|
|
747
|
+
* 恢复到某 checkpoint,三模式隔离——
|
|
748
|
+
* conversation:仅还原会话消息(restoreEntries 截断/替换);code:仅写回文件快照;both:两者。
|
|
749
|
+
* 各模式只动该恢复的部分(gate:模式隔离)。回滚/分叉在此之上。
|
|
750
|
+
*/
|
|
751
|
+
restoreCheckpoint(sessionId: string, seq: number, mode?: RestoreMode): Promise<void>;
|
|
752
|
+
/**
|
|
753
|
+
* RFC-160 D3/D4:出口 hydrate 统一入口。无剥离态 → 原样直通(快路径);有剥离态且
|
|
754
|
+
* hydrateEntries 能力在场 → 回填真身;有剥离态但能力缺席 → 断言炸(D4 硬闸门:
|
|
755
|
+
* 接线遗漏必须立即暴露,绝不静默把剥离态放行到冷存储/模型上下文)。
|
|
756
|
+
*/
|
|
757
|
+
private ensureHydrated;
|
|
758
|
+
private readCheckpoint;
|
|
759
|
+
/**
|
|
760
|
+
* **原地回滚(destructive rollback)**——在 restore 三模式(会话/代码)之上,再把 trace
|
|
761
|
+
* **截断**到该 checkpoint 的 traceSeq(丢弃其后的事件)。这是
|
|
762
|
+
* "trace 级截断/分叉":回滚后 traceback/replay 不再看到被回滚分支,且 live 会话续跑的 trace
|
|
763
|
+
* 从 traceSeq 续号(recorder 经 store.append 自动续号,无需 rebind)。
|
|
764
|
+
*
|
|
765
|
+
* 与 restoreCheckpoint 的区别:restoreCheckpoint 只动状态、不动 trace(只读历史仍可见旧分支);
|
|
766
|
+
* rollbackToCheckpoint 额外截断 trace(旧分支被丢弃)——"原地回退"语义。
|
|
767
|
+
*/
|
|
768
|
+
rollbackToCheckpoint(sessionId: string, seq: number, mode?: RestoreMode): Promise<void>;
|
|
769
|
+
/**
|
|
770
|
+
* **从 checkpoint 分叉(fork)**——以源会话某 checkpoint 为起点开出一个新分支会话,源会话
|
|
771
|
+
* 保持不变(会话分支 / fork-from-checkpoint 语义)。新会话:
|
|
772
|
+
* - 会话消息 = checkpoint 的会话快照(分支点之前的历史);
|
|
773
|
+
* - trace = 复制源会话 [0, traceSeq) 的事件到新 stream(使分叉会话的 traceback/replay 自足、独立);
|
|
774
|
+
* - checkpoint = 复制源会话 traceSeq ≤ 分支点 的 checkpoint 到新 stream(分叉会话保有基线锚点);
|
|
775
|
+
* - 工作区/代码 = 共享源会话 workspaceDir(与既有 forkSession 一致;隔离工作区属 L2 沙箱范畴)。
|
|
776
|
+
* 新会话经 session.created hook 成为一等会话(service 据此挂 trace/event 桥,可观测)。
|
|
777
|
+
*/
|
|
778
|
+
forkFromCheckpoint(sessionId: string, seq: number, newId?: string): Promise<AgentSession>;
|
|
779
|
+
private countTraceEvents;
|
|
780
|
+
/**
|
|
781
|
+
* 重放 trace 到某 seq(含)——返回 [from, toSeq] 区间的 trace 事件(design TimeTravel.replay)。
|
|
782
|
+
* toSeq 缺省=全量。是回溯的底层读法(不改动会话,纯读 trace)。
|
|
783
|
+
*/
|
|
784
|
+
replayTrace(sessionId: string, toSeq?: number, fromSeq?: number): Promise<TraceEvent[]>;
|
|
785
|
+
/**
|
|
786
|
+
* 回溯——重建某 trace seq 时刻的历史会话状态(只读,不改动 live 会话)。
|
|
787
|
+
* 选 traceSeq ≤ toSeq 的最近 checkpoint 作基线,把其后到 toSeq 的 trace 事件折叠回消息列表
|
|
788
|
+
* (user←prompt.before.text、assistant←stream done、tool_result←tool.call.end)。无 checkpoint 则从 seq 0 折叠。
|
|
789
|
+
*/
|
|
790
|
+
traceback(sessionId: string, toSeq: number): Promise<TracebackView>;
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region src/session/panel-state-store.d.ts
|
|
794
|
+
/**
|
|
795
|
+
* RFC-108 D3:面板 UI 状态读写门面。
|
|
796
|
+
*
|
|
797
|
+
* 内部调用 `PanelStatePersistence` 的 `load`/`save` 做 per-session 的四个面板
|
|
798
|
+
* 字段存取。每个 get 方法 load 后取对应字段返回;每个 set 方法先 load 现有快照
|
|
799
|
+
* (若无则初始化空对象)、更新对应字段、再 save。
|
|
800
|
+
*
|
|
801
|
+
* 当前为简单实现,不做额外缓存层——每次 get/set 都直接走持久化后端。
|
|
802
|
+
* 未来可根据 profiling 数据决定是否需要内存缓存层。
|
|
803
|
+
*
|
|
804
|
+
* RFC-338:paste 缓存经**独立的** `pasteStatePersistence` 存取(见 getPaste/setPaste),
|
|
805
|
+
* 不进 `PanelStateSnapshot`——原因见下方方法注释与 paste-state.ts 头注释。
|
|
806
|
+
*/
|
|
807
|
+
declare class PanelStateStore {
|
|
808
|
+
private readonly persistence;
|
|
809
|
+
/**
|
|
810
|
+
* RFC-338:paste 缓存的独立后端。可选——未注入时 paste 存取降级为
|
|
811
|
+
* 「读恒空 / 写 no-op」,与 in-memory/remote 后端不构造 PanelStateStore 的
|
|
812
|
+
* 既有降级策略同构(不 throw,不阻断输入)。
|
|
813
|
+
*/
|
|
814
|
+
private readonly pasteStatePersistence?;
|
|
815
|
+
constructor(persistence: PanelStatePersistence,
|
|
816
|
+
/**
|
|
817
|
+
* RFC-338:paste 缓存的独立后端。可选——未注入时 paste 存取降级为
|
|
818
|
+
* 「读恒空 / 写 no-op」,与 in-memory/remote 后端不构造 PanelStateStore 的
|
|
819
|
+
* 既有降级策略同构(不 throw,不阻断输入)。
|
|
820
|
+
*/
|
|
821
|
+
|
|
822
|
+
pasteStatePersistence?: PasteStatePersistence | undefined);
|
|
823
|
+
getTodoList(sessionId: string): Promise<PanelStateTodoItem[] | undefined>;
|
|
824
|
+
setTodoList(sessionId: string, todos: PanelStateTodoItem[]): Promise<void>;
|
|
825
|
+
getEditedFiles(sessionId: string): Promise<PanelStateEditedFile[] | undefined>;
|
|
826
|
+
setEditedFiles(sessionId: string, files: PanelStateEditedFile[]): Promise<void>;
|
|
827
|
+
getSubagents(sessionId: string): Promise<PanelStateSubagentEntry[] | undefined>;
|
|
828
|
+
setSubagents(sessionId: string, entries: PanelStateSubagentEntry[]): Promise<void>;
|
|
829
|
+
getDrafts(sessionId: string): Promise<PanelStateDraftEntry[] | undefined>;
|
|
830
|
+
setDrafts(sessionId: string, entries: PanelStateDraftEntry[]): Promise<void>;
|
|
831
|
+
getTurnSummaries(sessionId: string): Promise<PanelStateTurnSummary[] | undefined>;
|
|
832
|
+
setTurnSummaries(sessionId: string, entries: PanelStateTurnSummary[]): Promise<void>;
|
|
833
|
+
/**
|
|
834
|
+
* RFC-338:读取 paste 缓存。
|
|
835
|
+
*
|
|
836
|
+
* **不经 `loadOrInit`、不碰 `PanelStateSnapshot`**——这正是 D1 的要点:
|
|
837
|
+
* 面板态的每个 setter 都是「全量 load → 改一字段 → 全量 save」,而
|
|
838
|
+
* `setEditedFiles` 每次文件编辑就触发一次。若 paste(最大 8MB)与面板态同处
|
|
839
|
+
* 一份 JSON,每次编辑都要多序列化一遍这 8MB。两者物理分离后互不拖累。
|
|
840
|
+
*
|
|
841
|
+
* 返回值恒为规范化后的合法状态(fail-open):后端未注入、文件缺失、内容损坏、
|
|
842
|
+
* 字段非法一律降级为空状态而非抛错——粘贴缓存丢失只该让 chip 失效(有 toast
|
|
843
|
+
* 提示的安全失败),绝不该让会话打不开。
|
|
844
|
+
*/
|
|
845
|
+
getPaste(sessionId: string): Promise<PasteStateSnapshot>;
|
|
846
|
+
/**
|
|
847
|
+
* RFC-338:写入 paste 缓存(全量覆写,非 merge)。
|
|
848
|
+
*
|
|
849
|
+
* 调用方持有内存中的权威 LRU 顺序,整批写入即可。
|
|
850
|
+
* fail-soft:落盘失败吞错——粘贴/提交是用户主路径,不能因缓存写盘失败被阻断。
|
|
851
|
+
*
|
|
852
|
+
* @returns 是否真正落盘成功(供调用方按需记日志;不影响主流程)
|
|
853
|
+
*/
|
|
854
|
+
setPaste(sessionId: string, snapshot: PasteStateSnapshot): Promise<boolean>;
|
|
855
|
+
/** RFC-338:删除某会话的 paste 缓存(会话删除时的连带清理)。 */
|
|
856
|
+
deletePaste(sessionId: string): Promise<boolean>;
|
|
857
|
+
/**
|
|
858
|
+
* 批量加载——直接返回 `PanelStateSnapshot`,供 persistence-sync 恢复/迁移路径使用。
|
|
859
|
+
* 比分别调用 4 次 get 方法更高效(一次 load 而非四次)。
|
|
860
|
+
*/
|
|
861
|
+
loadAll(sessionId: string): Promise<PanelStateSnapshot | null>;
|
|
862
|
+
/**
|
|
863
|
+
* 批量保存——全量覆写(非 merge),供 persistence-sync 落盘路径使用。
|
|
864
|
+
* 调用方(doSave/saveSnapshot)总是读取 live session 的全部 4 个 getter 值后整批写入,
|
|
865
|
+
* 故无需与已有持久化数据做 per-field merge。
|
|
866
|
+
* RFC-118 小修(review 观察项):sessionId 参数此前被忽略(`_` 前缀),API 不对称——
|
|
867
|
+
* 改为防御性校验,snapshot 串号(写 A 会话数据到 B 键)时 fail-fast 而非静默错写。
|
|
868
|
+
*/
|
|
869
|
+
saveAll(sessionId: string, snapshot: PanelStateSnapshot): Promise<void>;
|
|
870
|
+
private loadOrInit;
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/session/residency-governor.d.ts
|
|
874
|
+
/**
|
|
875
|
+
* residency-governor.ts — RFC-178 触发器 C:内存压力驱动的会话驻留收紧。
|
|
876
|
+
*
|
|
877
|
+
* 把 MemoryGovernor 的 warning/critical 分级信号翻译成**驻留维度**的动态收紧系数:
|
|
878
|
+
* - healthy → 系数 1(cap 用全值:条数 2000 / 字节 256MB)
|
|
879
|
+
* - warning → 系数 1/2(条数 1000 / 字节 128MB)
|
|
880
|
+
* - critical → 系数 1/4(条数 500 / 字节 64MB)
|
|
881
|
+
*
|
|
882
|
+
* 设计约束(RFC-178 D1/D4):
|
|
883
|
+
* - **离散阶梯**而非连续值——内存采样受 GC 时机影响非确定,阶梯化把非确定性收敛到
|
|
884
|
+
* "处于哪一档";同一档位下裁剪结果完全确定(执行器不变式)。
|
|
885
|
+
* - 本类不做采样、不订阅 MemoryGovernor——纯状态容器 + 系数换算,由组装层
|
|
886
|
+
* (app-lifecycle)在 MemoryGovernor 回调里调用 `setLevel`,接线放 app 层使
|
|
887
|
+
* headless/serve 同享(不依赖 TUI)。
|
|
888
|
+
* - 所有收紧只影响内存视图(capStoredHistory* 的入参),DB append-forever 不动。
|
|
889
|
+
*/
|
|
890
|
+
type ResidencyPressureLevel = 'healthy' | 'warning' | 'critical';
|
|
891
|
+
declare const LEVEL_DIVISOR: Record<ResidencyPressureLevel, number>;
|
|
892
|
+
declare class ResidencyGovernor {
|
|
893
|
+
private level;
|
|
894
|
+
/** 由组装层在 MemoryGovernor 分级回调(onLevelChange 语义)里调用。幂等。 */
|
|
895
|
+
setLevel(level: ResidencyPressureLevel): void;
|
|
896
|
+
get currentLevel(): ResidencyPressureLevel;
|
|
897
|
+
/** 当前压力档位下的有效条数上限(base=SESSION_MAX_HISTORY_MESSAGES)。至少 1。 */
|
|
898
|
+
effectiveMaxMessages(base: number): number;
|
|
899
|
+
/** 当前压力档位下的有效字节预算(base=SESSION_MAX_CONTENT_BYTES)。至少 1。 */
|
|
900
|
+
effectiveMaxBytes(base: number): number;
|
|
901
|
+
}
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/session/session-manager.d.ts
|
|
904
|
+
/**
|
|
905
|
+
* Manager 只持有基础设施配置(持久化、网络、日志)。
|
|
906
|
+
* 业务配置(model、systemPrompt、tools 等)由宿主(coding/第二宿主)解析后
|
|
907
|
+
* 经 ResolvedSessionConfig 传入 createSession,Manager 不做二次 merge。
|
|
908
|
+
*/
|
|
909
|
+
interface ResidencyDiagnostics {
|
|
910
|
+
activeSessionCount: number;
|
|
911
|
+
totalResidentBytes: number;
|
|
912
|
+
globalPressure: number;
|
|
913
|
+
isGlobalPressure: boolean;
|
|
914
|
+
maxBytesPerSession: number;
|
|
915
|
+
pressureLevel: 'healthy' | 'warning' | 'critical';
|
|
916
|
+
}
|
|
917
|
+
interface SessionPoolOptions {
|
|
918
|
+
hookRegistry: HookRegistry;
|
|
919
|
+
providerRegistry: ProviderRegistry;
|
|
920
|
+
maxSessions?: number;
|
|
921
|
+
idleTimeoutMs?: number;
|
|
922
|
+
threshold?: number;
|
|
923
|
+
logger: Logger;
|
|
924
|
+
devtools?: Devtools;
|
|
925
|
+
/**
|
|
926
|
+
* 自动上下文管理器。注入后由引擎 pipeline 的 context-transform 阶段驱动
|
|
927
|
+
* needsPrune/needsCompaction→process。app 级基础设施,跨所有 session 共享。
|
|
928
|
+
*/
|
|
929
|
+
memory?: MemoryPort;
|
|
930
|
+
/**
|
|
931
|
+
* trace 落盘 store。app 级共享,per-session 以 sessionId 分流。注入后 manager 为每个
|
|
932
|
+
* session 构造绑定 sessionId 的 TraceRecorder 并透传引擎;未注入即不录制。
|
|
933
|
+
*/
|
|
934
|
+
traceStore?: AppendLog<TraceEvent>;
|
|
935
|
+
/**
|
|
936
|
+
* checkpoint 落盘 store。app 级共享,per-session 以 sessionId 分流(append-only,seq=checkpoint 序号)。
|
|
937
|
+
* 注入后 createCheckpoint/restoreCheckpoint 可用;未注入则相关方法抛错。
|
|
938
|
+
*/
|
|
939
|
+
checkpointStore?: AppendLog<CheckpointData>;
|
|
940
|
+
/**
|
|
941
|
+
* 确定性时钟端口。缺省=真实系统时钟(行为不变);replay 由 trace 回灌。
|
|
942
|
+
*/
|
|
943
|
+
clock?: ClockPort;
|
|
944
|
+
/**
|
|
945
|
+
* 仅用于 restore / restoreAll 场景。
|
|
946
|
+
* 正常 createSession 路径必须传入完整 ResolvedSessionConfig,不使用此字段。
|
|
947
|
+
* 使用 factory function 而非静态快照,确保每次 restore 拿到最新配置。
|
|
948
|
+
* 可接 agentName,restore 据此重解析子代理 prompt 身份(缺省=main)。
|
|
949
|
+
*/
|
|
950
|
+
configProvider?: (agentName?: string, modelId?: string) => ResolvedSessionConfig;
|
|
951
|
+
/**
|
|
952
|
+
* RFC-108 D3:面板态(todoList/editedFiles/subagents/drafts)独立持久化门店。
|
|
953
|
+
* 未注入时优雅降级为不独立持久化——面板态仅存在于内存,重启后丢失但不报错崩溃。
|
|
954
|
+
*/
|
|
955
|
+
panelStateStore?: PanelStateStore;
|
|
956
|
+
/**
|
|
957
|
+
* RFC-178 触发器 C:驻留收紧系数源(可选)。组装层把 MemoryGovernor 分级信号写入
|
|
958
|
+
* 其 setLevel;未注入时 cap 恒用全值(行为与此前一致)。透传给 PersistenceSync。
|
|
959
|
+
*/
|
|
960
|
+
residencyGovernor?: ResidencyGovernor;
|
|
961
|
+
/** RFC-323 M4:驻留预算运行时配置 getter(settings 注入,透传给 PersistenceSync)。 */
|
|
962
|
+
getResidencyConfig?: () => _$_x_otto_session_contract0.ResidencyBudgetConfig | undefined;
|
|
963
|
+
/**
|
|
964
|
+
* RFC-146:本地高效冷会话枚举策略(可选注入)。仅本地 SQLite 后端在
|
|
965
|
+
* `session-pool-factory.ts` 构造期注入(内部持有 `SqliteSessionRepository`,
|
|
966
|
+
* 单次 SQL 查询即拿到完整 `AgentSessionInfo[]`,远快于"逐个 HTTP load()");
|
|
967
|
+
* 远端后端(`otto serve` / `remote-persistence-server`)不注入,缺省走
|
|
968
|
+
* `listAllPersistedSessions` 的 `persistence.listPaginated()+load()` 兜底路径。
|
|
969
|
+
* `SessionPool` 只判断"策略是否已注入",不感知底层是什么仓储实现——
|
|
970
|
+
* 消灭对具体仓储类型的依赖(见 RFC-146 §6b「sessionRepoRef 迁移策略」)。
|
|
971
|
+
*/
|
|
972
|
+
listColdSessionsOverride?: (workspaceKey: string) => Promise<AgentSessionInfo[]>;
|
|
973
|
+
/**
|
|
974
|
+
* 只读探测(可选注入):某会话当前是否被**另一存活进程**持有写租约(RFC-159 D3
|
|
975
|
+
* SessionWriteLeaseManager.peekLockedByOther 的薄封装,无副作用)。仅本地 SQLite 后端
|
|
976
|
+
* 注入(`session-pool-factory.ts`);未注入时 `listSessions`/`listAllPersistedSessions`
|
|
977
|
+
* 恒不标注只读(远端/内存后端无租约机制,行为与迁移前一致)。用于 `/resume` 列表
|
|
978
|
+
* 提前展示"切进去会是只读",而非等到真正 save 被拒才事后感知。
|
|
979
|
+
*/
|
|
980
|
+
isSessionWriteLocked?: (sessionId: string) => boolean;
|
|
981
|
+
/**
|
|
982
|
+
* RFC-171 D4:写租约只读诊断(可选注入)——`SessionWriteLeaseManager.inspect` 的薄封装。
|
|
983
|
+
* 仅本地 SQLite 后端注入。供 `/session unlock` 只读诊断模式消费(持有者 pid/hostname/
|
|
984
|
+
* 存活性/mtime 距今);未注入时恒返回 `{ locked: false }`(远端/内存后端无租约机制)。
|
|
985
|
+
*/
|
|
986
|
+
inspectSessionWriteLease?: (sessionId: string) => LeaseInspection;
|
|
987
|
+
/**
|
|
988
|
+
* RFC-171 D4:强制释放写租约(可选注入)——`SessionWriteLeaseManager.forceRelease` 的
|
|
989
|
+
* 薄封装。跳过 token 实核,仅供用户主动触发的 `/session unlock --force` 使用。
|
|
990
|
+
*/
|
|
991
|
+
forceReleaseSessionWriteLease?: (sessionId: string) => void;
|
|
992
|
+
}
|
|
993
|
+
interface DiskSessionPoolOptions extends SessionPoolOptions {
|
|
994
|
+
sessionDir: string;
|
|
995
|
+
}
|
|
996
|
+
interface RemoteSessionPoolOptions extends SessionPoolOptions {
|
|
997
|
+
sessionUrl: string;
|
|
998
|
+
/** fetch 实现(必填)。在浏览器环境可传 globalThis.fetch,Node.js 需传兼容实现。 */
|
|
999
|
+
fetch: typeof globalThis.fetch;
|
|
1000
|
+
/** 返回认证 token 的工厂函数(必填)。 */
|
|
1001
|
+
getAuth: () => Promise<{
|
|
1002
|
+
token: string;
|
|
1003
|
+
}>;
|
|
1004
|
+
timeoutMs?: number;
|
|
1005
|
+
/** user identity id,非空时请求带 `X-Otto-User-Id` header。 */
|
|
1006
|
+
userId?: string;
|
|
1007
|
+
/** workspace 分区键(缺省 'default')——修复(N3):remote 后端此前恒 'default',多本地
|
|
1008
|
+
* workspace 共享同一 --session-url 时互相冲突,见 RemoteSessionPersistenceOptions.wsKey。
|
|
1009
|
+
* 支持惰性 getter(App 构造期 workspaceRef 尚未解析完成)。 */
|
|
1010
|
+
wsKey?: string | (() => string | undefined);
|
|
1011
|
+
}
|
|
1012
|
+
interface SessionPorts {
|
|
1013
|
+
clock?: ClockPort;
|
|
1014
|
+
recorder?: TraceRecorder;
|
|
1015
|
+
/** RFC-145 D3:nondet batch 冲洗回调(RecordingPorts.flushNondetBatch)。缺省 = 不聚合。 */
|
|
1016
|
+
nondetFlush?: () => void;
|
|
1017
|
+
stream?: StreamFunction;
|
|
1018
|
+
tools?: AgentTool[];
|
|
1019
|
+
disableMemory?: boolean;
|
|
1020
|
+
}
|
|
1021
|
+
declare class SessionPool {
|
|
1022
|
+
/** RFC-338 D7:面板态门店引用(供 panelState getter 暴露给 cli)。 */
|
|
1023
|
+
private readonly panelStateStoreRef;
|
|
1024
|
+
private readonly sessions;
|
|
1025
|
+
private readonly configs;
|
|
1026
|
+
private readonly persistence;
|
|
1027
|
+
private readonly maxSessions;
|
|
1028
|
+
private readonly idleTimeoutMs;
|
|
1029
|
+
private readonly threshold;
|
|
1030
|
+
private activeSessionId?;
|
|
1031
|
+
readonly stream: StreamFunction;
|
|
1032
|
+
private readonly hookRegistry;
|
|
1033
|
+
private readonly logger;
|
|
1034
|
+
private readonly devtools?;
|
|
1035
|
+
private readonly memory?;
|
|
1036
|
+
private readonly traceStore?;
|
|
1037
|
+
private readonly checkpointStore?;
|
|
1038
|
+
private readonly clock?;
|
|
1039
|
+
private readonly residencyGovernor?;
|
|
1040
|
+
private readonly getResidencyConfig?;
|
|
1041
|
+
private readonly configProvider?;
|
|
1042
|
+
/** RFC-146:本地高效冷会话枚举策略(可选,见 SessionPoolOptions.listColdSessionsOverride)。 */
|
|
1043
|
+
private readonly listColdSessionsOverride?;
|
|
1044
|
+
/** 只读探测(可选,见 SessionPoolOptions.isSessionWriteLocked)。 */
|
|
1045
|
+
private readonly isSessionWriteLocked?;
|
|
1046
|
+
/** RFC-171 D4:写租约诊断/强制释放(可选,见 SessionPoolOptions 对应字段)。 */
|
|
1047
|
+
private readonly inspectSessionWriteLeaseFn?;
|
|
1048
|
+
private readonly forceReleaseSessionWriteLeaseFn?;
|
|
1049
|
+
/**
|
|
1050
|
+
* 持久化是否为「自动行为」。disk/remote 后端 = true(prompt 终态增量落盘 +
|
|
1051
|
+
* 宿主 start/stop 自动 restoreAll/saveAll);in-memory 缺省 = false(行为与历史一致,
|
|
1052
|
+
* 不向 in-memory persistence 写无意义快照)。遵循"写入是默认行为非手动 API"
|
|
1053
|
+
* 的原则(会话持续落盘 + --continue 恢复)。
|
|
1054
|
+
*/
|
|
1055
|
+
readonly autoPersist: boolean;
|
|
1056
|
+
/** 时间旅行编排控制器(checkpoint/rollback/fork/replay/traceback 的实现归属)。 */
|
|
1057
|
+
readonly timeTravel: TimeTravelController;
|
|
1058
|
+
/** 快照持久化同步(增量落盘 + 重启恢复,第二变更轴)。 */
|
|
1059
|
+
private readonly persistenceSync;
|
|
1060
|
+
constructor(options: SessionPoolOptions, persistence: SessionPersistence<AgentMessage>, autoPersist?: boolean);
|
|
1061
|
+
/**
|
|
1062
|
+
* RFC-338 D7:面板态门店的只读暴露。
|
|
1063
|
+
*
|
|
1064
|
+
* cli 侧需要它来读写 paste 缓存(`getPaste`/`setPaste`)——粘贴缓存的加载/落盘
|
|
1065
|
+
* 时机由 UI 事件驱动(启动、切会话、fork、提交后),不在 PersistenceSync 的
|
|
1066
|
+
* 回合级落盘节奏上,故不能只走 persistence-sync 内部通路。
|
|
1067
|
+
* 只读 getter:调用方不得替换门店实例。未注入后端时为 undefined(优雅降级)。
|
|
1068
|
+
*/
|
|
1069
|
+
get panelState(): PanelStateStore | undefined;
|
|
1070
|
+
get active(): AgentSession | undefined;
|
|
1071
|
+
/**
|
|
1072
|
+
* 会话级「始终允许」工具名读写(宿主桥接 PermissionRegistry↔持久化用)。写入会话当前
|
|
1073
|
+
* ResolvedSessionConfig(= getConfig 同一引用),随 autoPersist 落盘、restore 经 applyPersistedConfig
|
|
1074
|
+
* 回灌;未知会话为 no-op / 空数组。空集合写入即清除该字段。
|
|
1075
|
+
*/
|
|
1076
|
+
getSessionPermissionAllow(id: string): string[];
|
|
1077
|
+
setSessionPermissionAllow(id: string, tools: readonly string[]): void;
|
|
1078
|
+
/** 会话级「账号可用模型」读写(同 permissionAlwaysAllow 持久化语义)。 */
|
|
1079
|
+
getSessionAvailableModels(id: string): string[];
|
|
1080
|
+
setSessionAvailableModels(id: string, models: readonly string[]): void;
|
|
1081
|
+
/**
|
|
1082
|
+
* 保持既有 private 外观(createSession/fork/restore 调用点不变)。
|
|
1083
|
+
*/
|
|
1084
|
+
private trackPersistence;
|
|
1085
|
+
/**
|
|
1086
|
+
* RFC-336 D1/D1-c:新会话(createSession / fork adopt)落盘行预建。
|
|
1087
|
+
*
|
|
1088
|
+
* 与 `trackPersistence` 成对出现——track 订阅"此后的变更",本方法保证"此刻的存在性"。
|
|
1089
|
+
* 两条新建路径都需要:`adoptForkedSession` 此前同样只 track 不写 DB,fork 出的会话在
|
|
1090
|
+
* 首个事件前被强杀同样蒸发(D1-c)。restore 路径**不需要**——那条路径的会话按定义
|
|
1091
|
+
* 已在 DB 中。
|
|
1092
|
+
*/
|
|
1093
|
+
private precreateSessionRow;
|
|
1094
|
+
/**
|
|
1095
|
+
* 两路 fork——`forkSession`(当前 leaf 全量)与 `forkFromCheckpoint`(历史 checkpoint 点)——
|
|
1096
|
+
* 共用的会话采纳:登记 + 持久化追踪 + `session.created` hook + `notifyCreated`。使 fork 出的会话
|
|
1097
|
+
* **既一等可观测**(service/桥接据 created 挂 trace/event)**又落盘持久**(重启经 restoreAll 恢复)。
|
|
1098
|
+
* 不改 active(fork 不抢用户当前会话,与历史行为一致);transient=false(fork 是一等持久分支,落盘)。
|
|
1099
|
+
*/
|
|
1100
|
+
private adoptForkedSession;
|
|
1101
|
+
private buildAgentSession;
|
|
1102
|
+
private recordPorts;
|
|
1103
|
+
/**
|
|
1104
|
+
* 使用由宿主完整解析好的配置创建 session。
|
|
1105
|
+
* Manager 层不再进行任何业务默认值填充。
|
|
1106
|
+
*/
|
|
1107
|
+
createSession(config: ResolvedSessionConfig & {
|
|
1108
|
+
id?: string;
|
|
1109
|
+
}, ports?: SessionPorts): Promise<AgentSession>;
|
|
1110
|
+
replaySession(sourceSessionId: string, newId?: string, opts?: {
|
|
1111
|
+
level?: ReplayLevel;
|
|
1112
|
+
sandbox?: SandboxRunner;
|
|
1113
|
+
}): Promise<AgentSession>;
|
|
1114
|
+
createCheckpoint(sessionId: string, opts?: {
|
|
1115
|
+
label?: string;
|
|
1116
|
+
files?: string[];
|
|
1117
|
+
}): Promise<Checkpoint>;
|
|
1118
|
+
listCheckpoints(sessionId: string): Promise<Checkpoint[]>;
|
|
1119
|
+
restoreCheckpoint(sessionId: string, seq: number, mode?: RestoreMode): Promise<void>;
|
|
1120
|
+
rollbackToCheckpoint(sessionId: string, seq: number, mode?: RestoreMode): Promise<void>;
|
|
1121
|
+
forkFromCheckpoint(sessionId: string, seq: number, newId?: string): Promise<AgentSession>;
|
|
1122
|
+
replayTrace(sessionId: string, toSeq?: number, fromSeq?: number): Promise<TraceEvent[]>;
|
|
1123
|
+
traceback(sessionId: string, toSeq: number): Promise<TracebackView>;
|
|
1124
|
+
getSession(id: string): AgentSession | undefined;
|
|
1125
|
+
/** RFC-325 M5:仅暴露聚合驻留状态,供受控 doctor attach 读取;不泄露会话内容。 */
|
|
1126
|
+
getResidencyDiagnostics(): ResidencyDiagnostics;
|
|
1127
|
+
listSessions(filter?: {
|
|
1128
|
+
workspaceId?: string;
|
|
1129
|
+
}): AgentSessionInfo[];
|
|
1130
|
+
/**
|
|
1131
|
+
* RFC-146:枚举"持久化后端已知、但未必已加载进内存"的会话(本地磁盘冷会话 /
|
|
1132
|
+
* 远端历史会话)——`listSessions()` 的超集,供 `/resume` 一类需要看到完整历史的
|
|
1133
|
+
* 场景使用。统一返回 `AgentSessionInfo[]`,调用方不感知底层是本地 SQLite 直连
|
|
1134
|
+
* 还是远端 HTTP 分页拉取。
|
|
1135
|
+
*
|
|
1136
|
+
* 两条实现路径(见 RFC-146 §6b「sessionRepoRef 迁移策略」,只统一契约不统一实现):
|
|
1137
|
+
* - `listColdSessionsOverride` 已注入(本地场景):直接委托,内部走高效 SQL 查询。
|
|
1138
|
+
* - 未注入(远端场景,含 otto serve 与 remote-persistence-server 两种):走
|
|
1139
|
+
* `persistence.listPaginated()` 取 id 列表(天然限流,不新发明 cap 机制)+
|
|
1140
|
+
* 逐个 `load()` 补 title/createdAt(远端会话缺 messages(),取 metadata.title
|
|
1141
|
+
* 回落 id 前 8 位,与本地冷会话降级展示模式一致)。
|
|
1142
|
+
*
|
|
1143
|
+
* 失败语义(两种失败互不掩盖,见 RFC-146 §6b):
|
|
1144
|
+
* - `listPaginated()` 整体失败 → 返回空数组 + warn(批量不可用,非单条问题)。
|
|
1145
|
+
* - 单条 `load()` 失败 → 跳过该条、不中止其余(数据损坏/网络抖动等局部问题)。
|
|
1146
|
+
*
|
|
1147
|
+
* @param filter.workspaceKey 已转换好的分区键(`ws_<id>` 或 `'default'`)——身份→
|
|
1148
|
+
* 分区键的转换由调用方(App)负责,本方法不感知 `workspaceId`、不依赖 `@x-otto/workspace`。
|
|
1149
|
+
*/
|
|
1150
|
+
listAllPersistedSessions(filter?: {
|
|
1151
|
+
workspaceKey?: string;
|
|
1152
|
+
}): Promise<AgentSessionInfo[]>;
|
|
1153
|
+
/**
|
|
1154
|
+
* RFC-078 M135-01:回血标题——对所有已加载会话中 title 为空/derived 的,fire-and-forget
|
|
1155
|
+
* 触发 ai 重生(跳过 custom)。便宜模型 + 各会话自带 abort/超时,非阻塞。返回触发计数。
|
|
1156
|
+
* 冷会话(未 restore 进内存)经 listSessions/resume 读取时已懒解析为 derived(resolveSessionTitle)。
|
|
1157
|
+
*/
|
|
1158
|
+
backfillTitles(): {
|
|
1159
|
+
triggered: number;
|
|
1160
|
+
skipped: number;
|
|
1161
|
+
};
|
|
1162
|
+
archiveSession(id: string): Promise<boolean>;
|
|
1163
|
+
removeSession(id: string): Promise<boolean>;
|
|
1164
|
+
/**
|
|
1165
|
+
* 删除会话的 trace/checkpoint 落盘数据。此前 removeSession 只删 session 快照,
|
|
1166
|
+
* trace JSONL(delta 级、单会话可达数百 MB)与 checkpoint 永久残留——
|
|
1167
|
+
* ~/.otto/sessions/trace 累积数十 GB 的直接来源。失败仅告警(孤儿文件可后续清理,
|
|
1168
|
+
* 不阻塞会话删除本身)。
|
|
1169
|
+
*/
|
|
1170
|
+
private clearSessionStores;
|
|
1171
|
+
/**
|
|
1172
|
+
* fork 直接复用 source session 自身的已解析配置,
|
|
1173
|
+
* 消息通过复制 snapshot entries 完整迁移。
|
|
1174
|
+
*/
|
|
1175
|
+
/**
|
|
1176
|
+
* RFC-160 D3/D4:出口 hydrate(fork 用)。无剥离态直通;有剥离态且持久层具备能力则回填;
|
|
1177
|
+
* 能力缺席 → 断言炸(接线遗漏立即暴露,绝不把剥离态固化为新会话正文)。
|
|
1178
|
+
*/
|
|
1179
|
+
private ensureEntriesHydrated;
|
|
1180
|
+
forkSession(sourceId: string, newId?: string): Promise<AgentSession | undefined>;
|
|
1181
|
+
save(id: string): Promise<void>;
|
|
1182
|
+
saveAll(): Promise<void>;
|
|
1183
|
+
/**
|
|
1184
|
+
* RFC-171 D4:写租约只读诊断(无副作用)——持有者 pid/hostname/存活性/mtime 距今,
|
|
1185
|
+
* 供 `/session unlock` 只读模式展示。未注入策略(远端/内存后端)恒返回 `{ locked: false }`。
|
|
1186
|
+
*/
|
|
1187
|
+
inspectSessionWriteLease(sessionId: string): LeaseInspection;
|
|
1188
|
+
/**
|
|
1189
|
+
* RFC-171 D4:强制释放写租约——跳过 token 实核,仅供用户主动触发的 `/session unlock
|
|
1190
|
+
* --force` 使用。未注入策略(远端/内存后端)为 no-op(无租约机制,无需处理)。
|
|
1191
|
+
*/
|
|
1192
|
+
forceReleaseSessionWriteLease(sessionId: string): void;
|
|
1193
|
+
restore(id: string): Promise<AgentSession | null>;
|
|
1194
|
+
/** RFC-305 D3:消费本次恢复检出的崩溃缺口(CLI resume 路径在订阅建立后读取)。 */
|
|
1195
|
+
consumeCrashGap(id: string): {
|
|
1196
|
+
lostTurns: number;
|
|
1197
|
+
} | undefined;
|
|
1198
|
+
restoreAll(): Promise<number>;
|
|
1199
|
+
/**
|
|
1200
|
+
* 修复(不一致性,2026-07-12 独立 review 后续核实):此前 idle 驱逐只 dispose+delete,
|
|
1201
|
+
* 完全不落盘——若最近一次 auto-persist(prompt.end 触发)因瞬时故障失败(DB 抖动,仅
|
|
1202
|
+
* `logger.warn` 无重试,见 `track()` persist 闭包的 catch 分支),内存里的未落盘改动会
|
|
1203
|
+
* 被静默丢弃。`evictLeastRecent`(LRU 驱逐)早已对称处理这一点(驱逐前同步捕获快照
|
|
1204
|
+
* 入 `saveSnapshot` 链),本方法此前遗漏同样的防御,是纯粹的不一致而非有意设计。
|
|
1205
|
+
*/
|
|
1206
|
+
private collectIdle;
|
|
1207
|
+
/** 会话条目数(含全部分支)。空判定不能用 messages()——它只走当前分支,多分支会话会被误判为空。 */
|
|
1208
|
+
private entryCount;
|
|
1209
|
+
/**
|
|
1210
|
+
* 满池兜底:collectIdle 之后仍满池时,按 lastActiveAt LRU 驱逐会话。
|
|
1211
|
+
* 不可驱逐:active、busy(prompt 全生命周期,含流式前窗口)、transient(M18-PR-02:
|
|
1212
|
+
* 不许落盘,且生命周期归 orchestrator 管,中途 dispose 会打断编排)。
|
|
1213
|
+
* autoPersist 后端驱逐前同步捕获快照入 save 链,内存驱逐零数据损失(restore(id) 可复活);
|
|
1214
|
+
* in-memory 后端驱逐非空会话=丢数据,只允许驱逐空会话(从未 prompt 过,无可丢内容)。
|
|
1215
|
+
*/
|
|
1216
|
+
private evictLeastRecent;
|
|
1217
|
+
private prepareCapacity;
|
|
1218
|
+
private canAccommodate;
|
|
1219
|
+
dispose(): void;
|
|
1220
|
+
}
|
|
1221
|
+
declare function createDiskSessionPool(options: DiskSessionPoolOptions): SessionPool;
|
|
1222
|
+
declare function createRemoteSessionPool(options: RemoteSessionPoolOptions): SessionPool;
|
|
1223
|
+
declare function createInMemorySessionPool(options: SessionPoolOptions): SessionPool;
|
|
1224
|
+
//#endregion
|
|
1225
|
+
//#region src/create-agent-runtime.d.ts
|
|
1226
|
+
type SessionStorageConfig = {
|
|
1227
|
+
kind: 'in-memory';
|
|
1228
|
+
} | {
|
|
1229
|
+
kind: 'disk';
|
|
1230
|
+
sessionDir: string;
|
|
1231
|
+
} | {
|
|
1232
|
+
kind: 'remote';
|
|
1233
|
+
sessionUrl: string;
|
|
1234
|
+
fetch: typeof globalThis.fetch;
|
|
1235
|
+
getAuth: () => Promise<{
|
|
1236
|
+
token: string;
|
|
1237
|
+
}>;
|
|
1238
|
+
timeoutMs?: number; /** user identity id,非空时请求带 `X-Otto-User-Id` header 实现 session 分区。 */
|
|
1239
|
+
userId?: string;
|
|
1240
|
+
/** workspace 分区键(缺省 'default')——修复(N3):remote 后端此前恒 'default',
|
|
1241
|
+
* 与本地 SQLite 后端按 workspace_key 分区行为不一致。见 RemoteSessionPoolOptions.wsKey。
|
|
1242
|
+
* 支持惰性 getter(App 构造期 workspaceRef 尚未解析完成)。 */
|
|
1243
|
+
wsKey?: string | (() => string | undefined);
|
|
1244
|
+
} | {
|
|
1245
|
+
/**
|
|
1246
|
+
* 调用方注入的持久化后端(关系化 SQLite 等联合内建无法表达的后端)。让 createAgentRuntime
|
|
1247
|
+
* 成为唯一装配权威——宿主不再手搓平行 AgentRuntime 字面量(消除装配漂移)。
|
|
1248
|
+
* trace/checkpoint store 由调用方经 options 注入(ownsXxx=false,dispose 不替其 close)。
|
|
1249
|
+
*/
|
|
1250
|
+
kind: 'custom';
|
|
1251
|
+
persistence: SessionPersistence<AgentMessage>; /** 缺省 true(与关系化后端既有行为一致)。 */
|
|
1252
|
+
autoPersist?: boolean; /** 后端专属资源释放(如 SqliteSessionRepository.close());dispose 时在 sessions.dispose 后调用。 */
|
|
1253
|
+
onDispose?: () => void | Promise<void>;
|
|
1254
|
+
/**
|
|
1255
|
+
* RFC-146:本地高效冷会话枚举策略透传(见 SessionPoolOptions.listColdSessionsOverride)。
|
|
1256
|
+
* 关系化 SQLite 分支(session-pool-factory.ts)经此注入,走 SqliteSessionRepository 单次
|
|
1257
|
+
* SQL 查询而非通用 listPaginated+load 慢路径。
|
|
1258
|
+
*/
|
|
1259
|
+
listColdSessionsOverride?: (workspaceKey: string) => Promise<AgentSessionInfo[]>;
|
|
1260
|
+
/**
|
|
1261
|
+
* 只读探测策略透传(见 SessionPoolOptions.isSessionWriteLocked)。关系化 SQLite 分支
|
|
1262
|
+
* 经此注入 SessionWriteLeaseManager.peekLockedByOther 的薄封装。
|
|
1263
|
+
*/
|
|
1264
|
+
isSessionWriteLocked?: (sessionId: string) => boolean;
|
|
1265
|
+
/**
|
|
1266
|
+
* RFC-171 D4:写租约诊断/强制释放策略透传(见 SessionPoolOptions 对应字段)。
|
|
1267
|
+
* 关系化 SQLite 分支经此注入 SessionWriteLeaseManager.inspect/forceRelease 的薄封装。
|
|
1268
|
+
*/
|
|
1269
|
+
inspectSessionWriteLease?: (sessionId: string) => LeaseInspection;
|
|
1270
|
+
forceReleaseSessionWriteLease?: (sessionId: string) => void;
|
|
1271
|
+
/**
|
|
1272
|
+
* RFC-108 D3 面板态(todoList/editedFiles/subagents/drafts)独立持久化的落盘目录。
|
|
1273
|
+
* 2026-07-21 修复:`kind:'custom'`(coding 默认的关系化 SQLite 后端)此前不构造
|
|
1274
|
+
* PanelStateStore——面板态仅存内存,进程重启即丢(drafts e2e 恒红暴露的真实生产
|
|
1275
|
+
* 缺口,非测试环境问题)。调用方传入目录(惯例 `<sessionDir>/panel-state`)即启用,
|
|
1276
|
+
* 缺省不构造(保持 in-memory/remote 的既有降级行为)。
|
|
1277
|
+
*/
|
|
1278
|
+
panelStateDir?: string;
|
|
1279
|
+
/**
|
|
1280
|
+
* RFC-345 §D8:显式注入的面板态/粘贴态持久化后端(影子模式用 InMemory 实现)。
|
|
1281
|
+
* 优先于 panelStateDir——两者同时缺省时不构造 PanelStateStore(既有降级)。
|
|
1282
|
+
* 由 §D1 白名单点 session-pool-factory 在影子态注入内存实现,磁盘零写。
|
|
1283
|
+
*/
|
|
1284
|
+
panelStatePersistence?: PanelStatePersistence;
|
|
1285
|
+
pasteStatePersistence?: PasteStatePersistence;
|
|
1286
|
+
};
|
|
1287
|
+
interface RuntimeOptions {
|
|
1288
|
+
/** LLM provider 注册表。缺省=createDefaultProviderRegistry()。 */
|
|
1289
|
+
providerRegistry?: ProviderRegistry;
|
|
1290
|
+
/** hook 注册表(tool.execute / system.prompt.transform 等的容器)。缺省=default preset。 */
|
|
1291
|
+
hookRegistry?: HookRegistry;
|
|
1292
|
+
logger?: Logger;
|
|
1293
|
+
/** 工作区目录(environment ContextSource + 会话缺省根)。缺省=process.cwd()。 */
|
|
1294
|
+
workspaceDir?: string;
|
|
1295
|
+
/** 确定性时钟端口。缺省=真实系统时钟。 */
|
|
1296
|
+
clock?: ClockPort;
|
|
1297
|
+
/** trace 落盘 store。缺省=内存 append-log(零磁盘、实时观测)。 */
|
|
1298
|
+
traceStore?: AppendLog<TraceEvent>;
|
|
1299
|
+
/** checkpoint 落盘 store。缺省=内存 append-log。 */
|
|
1300
|
+
checkpointStore?: AppendLog<CheckpointData>;
|
|
1301
|
+
/** 自动上下文管理端口。注入后引擎按 token 阈值在循环内压缩;未注入即不自动压缩。 */
|
|
1302
|
+
memory?: MemorySubsystem;
|
|
1303
|
+
/** 会话池容量上限。缺省由 SessionPool 取 env SESSION_MAX。 */
|
|
1304
|
+
maxSessions?: number;
|
|
1305
|
+
idleTimeoutMs?: number;
|
|
1306
|
+
/** 会话持久化后端。缺省=纯内存(不落盘)。 */
|
|
1307
|
+
storage?: SessionStorageConfig;
|
|
1308
|
+
/** devtools 实例(可选调试端口)。缺省=无调试。 */
|
|
1309
|
+
devtools?: Devtools;
|
|
1310
|
+
/** 仅用于 restore / restoreAll 路径的配置工厂。同一 runtime 所有会话共享同一缺省配置模板。 */
|
|
1311
|
+
configProvider?: (agentName?: string, modelId?: string) => ResolvedSessionConfig;
|
|
1312
|
+
/**
|
|
1313
|
+
* RFC-178 触发器 C:驻留收紧系数源(可选)。组装层(app-lifecycle)把 MemoryGovernor
|
|
1314
|
+
* 分级信号写入其 setLevel,PersistenceSync 的 cap 执行读取其收紧系数。未注入=恒全值。
|
|
1315
|
+
*/
|
|
1316
|
+
residencyGovernor?: ResidencyGovernor;
|
|
1317
|
+
/** RFC-323 M4:驻留预算运行时配置 getter(settings 注入透传)。 */
|
|
1318
|
+
getResidencyConfig?: () => _$_x_otto_session_contract0.ResidencyBudgetConfig | undefined;
|
|
1319
|
+
}
|
|
1320
|
+
interface AgentRuntime {
|
|
1321
|
+
/** 统一派生入口:子 runtime / 宿主 / swarm / task-runner 皆经此(§4-11)。 */
|
|
1322
|
+
createSession(config: ResolvedSessionConfig & {
|
|
1323
|
+
id?: string;
|
|
1324
|
+
}, ports?: SessionPorts): Promise<AgentSession>;
|
|
1325
|
+
/** 会话池(fork/replay/checkpoint/持久化同步等编排门面)。 */
|
|
1326
|
+
readonly sessions: SessionPool;
|
|
1327
|
+
readonly hookRegistry: HookRegistry;
|
|
1328
|
+
/** 引擎 stream(从 providerRegistry 派生,§4-11:子 runtime 用之而非各自 createStreamFunction)。 */
|
|
1329
|
+
readonly stream: StreamFunction;
|
|
1330
|
+
/** ContextSource 有序注册表(D33):通用源已内置注册,coding 策略源由宿主追加。 */
|
|
1331
|
+
readonly contextSources: ContextSourceRegistry;
|
|
1332
|
+
readonly traceStore: AppendLog<TraceEvent>;
|
|
1333
|
+
readonly checkpointStore: AppendLog<CheckpointData>;
|
|
1334
|
+
readonly clock?: ClockPort;
|
|
1335
|
+
dispose(): Promise<void>;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* 通用 ContextSource 注册表(D33 单一来源):environment + memory-index(有注入面时)。
|
|
1339
|
+
* createAgentRuntime 内置调用之;宿主的离线装配路径(不经 createAgentRuntime)亦复用,
|
|
1340
|
+
* 确保「App 经 rt.contextSources 构建注入 hooks」对所有后端一致——消灭 App 端的二次造表。
|
|
1341
|
+
*/
|
|
1342
|
+
declare function createDefaultContextSourceRegistry(workspaceDir: string, memory?: MemorySubsystem): ContextSourceRegistry;
|
|
1343
|
+
declare function createAgentRuntime(options?: RuntimeOptions): AgentRuntime;
|
|
1344
|
+
//#endregion
|
|
1345
|
+
//#region src/engine-stores.d.ts
|
|
1346
|
+
/**
|
|
1347
|
+
* trace/checkpoint AppendLog 后端选择的**单一权威**。
|
|
1348
|
+
*
|
|
1349
|
+
* 坑:remote 后端必须用 Http 变体,否则远端会话的 trace 落内存、重启即丢。
|
|
1350
|
+
*/
|
|
1351
|
+
interface EngineStoreLocation {
|
|
1352
|
+
/** 会话快照落盘目录(disk 后端):trace/checkpoint 落 <dir>/trace、<dir>/checkpoint。最高优先。 */
|
|
1353
|
+
sessionDir?: string;
|
|
1354
|
+
/** 远端 AppendLog 选项(remote 后端,HttpAppendLog);sessionDir 在场时被忽略。
|
|
1355
|
+
* fetch 可选——HttpAppendLog 内部对缺省回落 globalThis.fetch(兼容 App 的可选 remote.fetch)。 */
|
|
1356
|
+
remoteLog?: {
|
|
1357
|
+
baseUrl: string;
|
|
1358
|
+
getAuth: () => Promise<{
|
|
1359
|
+
token: string;
|
|
1360
|
+
}>;
|
|
1361
|
+
fetch?: typeof globalThis.fetch;
|
|
1362
|
+
timeoutMs?: number;
|
|
1363
|
+
};
|
|
1364
|
+
/**
|
|
1365
|
+
* RFC-345 §D8:影子模式旗标。为真时**强制** trace/checkpoint 走 MemoryAppendLog,
|
|
1366
|
+
* 即便 sessionDir/remoteLog 在场。
|
|
1367
|
+
*
|
|
1368
|
+
* 为何需要独立旗标而非"不传 sessionDir":sessionDir 对其他子系统(如 settings、
|
|
1369
|
+
* lesson 等非会话存储的目录惯例)仍可能有用途,不能靠"不给目录"顺带关掉 trace 落盘。
|
|
1370
|
+
* ephemeral 让 devtools.inspect() 的实时观测(LiveTap 内存旁路)在影子态仍可用,
|
|
1371
|
+
* 而磁盘零写入。由 §D1 白名单装配点(storage-wiring / create-agent-runtime)注入。
|
|
1372
|
+
*/
|
|
1373
|
+
ephemeral?: boolean;
|
|
1374
|
+
}
|
|
1375
|
+
interface EngineStoreOverrides {
|
|
1376
|
+
/** 显式注入的 trace store(最高优先,覆盖一切后端选择)。 */
|
|
1377
|
+
traceStore?: AppendLog<TraceEvent>;
|
|
1378
|
+
/** 显式注入的 checkpoint store(最高优先)。 */
|
|
1379
|
+
checkpointStore?: AppendLog<CheckpointData>;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* 选择 trace/checkpoint 后端。优先级:显式 override > ephemeral(Memory) >
|
|
1383
|
+
* sessionDir(File) > remoteLog(Http) > Memory。
|
|
1384
|
+
*/
|
|
1385
|
+
declare function buildEngineStores(loc: EngineStoreLocation, overrides?: EngineStoreOverrides): {
|
|
1386
|
+
traceStore: AppendLog<TraceEvent>;
|
|
1387
|
+
checkpointStore: AppendLog<CheckpointData>;
|
|
1388
|
+
};
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/defaults.d.ts
|
|
1391
|
+
declare const RUNTIME_DEFAULTS: {
|
|
1392
|
+
/** 工具轮次预算缺省(RFC-057 M89-01 单源):引用 agent 核心 ENGINE_DEFAULTS.maxToolTurns,
|
|
1393
|
+
* 不再硬编码——改其一全生效,消除散落 `?? 50` 漂移。 */
|
|
1394
|
+
readonly maxToolTurns: 120;
|
|
1395
|
+
};
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region src/memory-governor.d.ts
|
|
1398
|
+
/**
|
|
1399
|
+
* memory-governor.ts — RFC-115 M1:长驻进程内存自我感知。
|
|
1400
|
+
*
|
|
1401
|
+
* 背景:otto 支持 `--continue` 长驻会话,进程可连续运行数十小时。此前对自身堆内存
|
|
1402
|
+
* 增长零监控——唯一"处理机制"是 V8 自己在 old-space 撞上限时 `FatalProcessOutOfMemory`
|
|
1403
|
+
* 直接 SIGABRT,靠 RFC-085 重启陪跑被动兜底(且该窗口内 `app.stop()`/`saveAll` 完全没有
|
|
1404
|
+
* 机会执行,存在状态丢失风险)。见 RFC-115 §1。
|
|
1405
|
+
*
|
|
1406
|
+
* M1 范围:只做周期采样 + 分级 + 回调触发,不实现任何降级/重启动作本身(回调是否执行
|
|
1407
|
+
* 压缩/弹窗由调用方注入,M2/M3 接线)——这是刻意的里程碑切分(RFC-115-todo.md M1)。
|
|
1408
|
+
*
|
|
1409
|
+
* 不引入通用 `HealthMetricSampler` 接口(RFC-115 D7,经独立评审 P2-1 撤回):本类是
|
|
1410
|
+
* 内存专用的具体实现,YAGNI——渲染帧率等其他维度若未来需要,从两个具体实现里再提炼
|
|
1411
|
+
* 共性,不预先猜测未来的抽象形状。
|
|
1412
|
+
*/
|
|
1413
|
+
type MemoryPressureLevel = 'healthy' | 'warning' | 'critical';
|
|
1414
|
+
interface MemoryGovernorOptions {
|
|
1415
|
+
/** 告警阈值(占 V8 heap_size_limit 的百分比)。默认 70,经 OTTO_MEMORY_WARN_PCT 覆盖。 */
|
|
1416
|
+
warnThresholdPct?: number;
|
|
1417
|
+
/** 危险阈值(占 V8 heap_size_limit 的百分比)。默认 85,经 OTTO_MEMORY_HARD_PCT 覆盖。 */
|
|
1418
|
+
hardThresholdPct?: number;
|
|
1419
|
+
/** 采样间隔(ms)。默认 30_000,经 OTTO_MEMORY_SAMPLE_INTERVAL_MS 覆盖。 */
|
|
1420
|
+
sampleIntervalMs?: number;
|
|
1421
|
+
/** warning 分级下的采样间隔(ms)。默认 10_000,经 OTTO_MEMORY_WARNING_INTERVAL_MS 覆盖。RFC-133 D7。 */
|
|
1422
|
+
warningIntervalMs?: number;
|
|
1423
|
+
/** critical 分级下的采样间隔(ms)。默认 5_000,经 OTTO_MEMORY_CRITICAL_INTERVAL_MS 覆盖。RFC-133 D7。 */
|
|
1424
|
+
criticalIntervalMs?: number;
|
|
1425
|
+
/**
|
|
1426
|
+
* 软降级触发冷却期(ms,RFC-115 D4)。默认 300_000(5 分钟)。防止 usedPct 在阈值附近
|
|
1427
|
+
* 抖动(如 69%↔71%)导致 onWarning 被高频重复触发——压缩本身有 CPU/延迟代价,且刚压缩完
|
|
1428
|
+
* 内存短期回升是正常现象。冷却期内即使分级变化仍触发(healthy↔warning 往返),
|
|
1429
|
+
* 只是不调用 onWarning;level 状态本身不受冷却期影响,仍如实反映当前采样结果。
|
|
1430
|
+
* 只作用于 warning 级别;critical 级别(Layer 2 硬阈值)不受冷却期约束,应立即响应。
|
|
1431
|
+
*/
|
|
1432
|
+
warnCooldownMs?: number;
|
|
1433
|
+
/** 分级变化时触发(level 从非 warning/critical 变为该级别时各触发一次,非每次采样都触发)。 */
|
|
1434
|
+
onWarning?: () => void;
|
|
1435
|
+
/**
|
|
1436
|
+
* critical 分级触发,且当前不处于重启后宽限期(见 `postRestartGraceMs`)时调用。
|
|
1437
|
+
* 传入触发时刻的采样结果(`tick()` 内部已算好,避免调用方重复 `sample()` 造成值漂移
|
|
1438
|
+
* 与多余开销——经独立评审 P1-2 修正)。
|
|
1439
|
+
*/
|
|
1440
|
+
onCritical?: (sample: MemorySample) => void;
|
|
1441
|
+
/**
|
|
1442
|
+
* critical 分级触发,但当前处于重启后宽限期内时调用(RFC-115 D5 残留风险缓解)——
|
|
1443
|
+
* 调用方应降级为非阻塞提示(如 TUI 常驻通知区),而非再次弹出确认重启 Dialog,
|
|
1444
|
+
* 避免"重启→同样内存状态→立刻又弹窗"的连续打断。`onCritical`/`onCriticalDuringGrace`
|
|
1445
|
+
* 互斥,同一次触发只调用其一。同样传入触发时刻的采样结果。
|
|
1446
|
+
*/
|
|
1447
|
+
onCriticalDuringGrace?: (sample: MemorySample) => void;
|
|
1448
|
+
/**
|
|
1449
|
+
* RFC-116 M3:任意分级变化时触发(含恢复到 healthy 的降级方向)——`onWarning`/
|
|
1450
|
+
* `onCritical`/`onCriticalDuringGrace` 均只在"变为 warning/critical"时触发,缺少
|
|
1451
|
+
* "从 warning/critical 恢复到 healthy"这个方向的信号。footer 常驻指示器(异常时
|
|
1452
|
+
* 才显示、健康时零宽度)需要感知这个恢复事件才能自行清除,故新增本回调覆盖全部
|
|
1453
|
+
* 变化方向。不受冷却期/宽限期约束(纯粹的状态变化通知,不驱动任何降级动作)。
|
|
1454
|
+
*/
|
|
1455
|
+
onLevelChange?: (level: MemoryPressureLevel) => void;
|
|
1456
|
+
/**
|
|
1457
|
+
* RFC-115 追补:每次 tick 都触发(不像 onWarning/onCritical 只在分级变化时触发)——
|
|
1458
|
+
* 供调用方把完整采样时间序列落 trace(诊断专用,见 engine-nodes.ts 的
|
|
1459
|
+
* 'process.memory.sample' 节点)。MemoryGovernor 本身不知道 trace 系统的存在,
|
|
1460
|
+
* 只负责把采样结果透传出去,写入哪里由调用方(App 层)决定,保持职责分离。
|
|
1461
|
+
*/
|
|
1462
|
+
onSample?: (sample: MemorySample) => void;
|
|
1463
|
+
logger?: Logger;
|
|
1464
|
+
/** 时间源注入(测试用;对齐 scheduler.ts 的 `deps.now` 模式)。默认 `Date.now`。 */
|
|
1465
|
+
now?: () => number;
|
|
1466
|
+
/**
|
|
1467
|
+
* 重启后宽限期(ms,RFC-115 D5)。默认 600_000(10 分钟)。宽限期从 MemoryGovernor
|
|
1468
|
+
* **构造时刻**起算——调用方(App)应只在检测到本次启动是重启恢复(如 `--continue`)
|
|
1469
|
+
* 时传入非零值;全新会话不传(默认 0 = 无宽限期,立即响应 critical)。
|
|
1470
|
+
*/
|
|
1471
|
+
postRestartGraceMs?: number;
|
|
1472
|
+
/**
|
|
1473
|
+
* RFC-167 D1/D5:critical 分级"持续时长"超时阈值(ms)。默认 900_000(15 分钟),
|
|
1474
|
+
* 经 `OTTO_MEMORY_CRITICAL_TIMEOUT_MS` 覆盖。
|
|
1475
|
+
*
|
|
1476
|
+
* 背景:`onCritical` 只在状态**跃迁**(healthy/warning → critical)时触发一次;若
|
|
1477
|
+
* critical 分级持续存在(内存单调爬升不再回落,不产生第二次跃迁),用户错过那一次
|
|
1478
|
+
* 弹窗后不会再收到任何提醒,直至 V8 物理 OOM(2026-07-15 事故实测:跃迁触发 3 小时
|
|
1479
|
+
* 20 分钟后崩溃,期间零第二次通知)。本字段驱动 `onCriticalTimeout` 的触发阈值,
|
|
1480
|
+
* 与"状态跃迁检测"(`onCritical`)完全独立、互不影响。
|
|
1481
|
+
*/
|
|
1482
|
+
criticalTimeoutMs?: number;
|
|
1483
|
+
/**
|
|
1484
|
+
* RFC-167 D1:critical 分级持续超过 `criticalTimeoutMs` 时触发(与 `onCritical` 的
|
|
1485
|
+
* "状态跃迁触发"正交——即使从未再次跃迁,只要持续时长超过阈值就会触发一次)。
|
|
1486
|
+
*
|
|
1487
|
+
* 同一次 critical 区间内只触发一次(`criticalEnteredAt` 归零前不重复触发,避免
|
|
1488
|
+
* 每个 tick 都调用);恢复到非 critical 分级后 `criticalEnteredAt` 清空,下次重新
|
|
1489
|
+
* 进入 critical 会重新计时、可以再次触发一次超时。
|
|
1490
|
+
*
|
|
1491
|
+
* 调用方(interactive-memory-governor.ts)据此实现"Dialog 从未被响应则自动执行
|
|
1492
|
+
* 用户已看到过的默认建议(重启);已响应过(无论确认还是取消)则改走持续通知 +
|
|
1493
|
+
* 二级兜底"的两分支路由(RFC-167 D2/D3)。
|
|
1494
|
+
*/
|
|
1495
|
+
onCriticalTimeout?: (sample: MemorySample) => void;
|
|
1496
|
+
}
|
|
1497
|
+
/** 单次采样结果——供 M1 日志与后续里程碑(M2/M3)复用同一份读数,不重复采样。 */
|
|
1498
|
+
interface MemorySample {
|
|
1499
|
+
level: MemoryPressureLevel;
|
|
1500
|
+
heapUsed: number;
|
|
1501
|
+
heapSizeLimit: number;
|
|
1502
|
+
usedPct: number;
|
|
1503
|
+
/**
|
|
1504
|
+
* RFC-178 P2(观察维度,不参与分级):常驻集大小(进程全部内存含 V8 堆外)。
|
|
1505
|
+
* 分级仍只看 heapUsed/heap_size_limit(针对 V8 FatalProcessOutOfMemory 这一实证
|
|
1506
|
+
* 事故形态,指标已两次验证正确);rss/external 仅落 trace 供诊断——覆盖容器
|
|
1507
|
+
* OOM killer(按 RSS 杀)与图片 Buffer/ArrayBuffer(external,不计入 heapUsed)
|
|
1508
|
+
* 两类 V8 堆内指标的盲区。同一 `process.memoryUsage()` 调用返回值,零额外开销。
|
|
1509
|
+
*/
|
|
1510
|
+
rss: number;
|
|
1511
|
+
/** V8 堆外的 C++ 对象/Buffer 内存(见 rss 注释——观察维度,不参与分级)。 */
|
|
1512
|
+
external: number;
|
|
1513
|
+
/**
|
|
1514
|
+
* 采样时刻(epoch ms,独立评审 P2-2)——用 MemoryGovernor 内部注入的 `now`(测试可
|
|
1515
|
+
* mock),而非消费方(如 onSample 回调)自行调用 `Date.now()`。避免测试用 fake timer
|
|
1516
|
+
* 时,MemoryGovernor 采样用的是 mock 时间,但 trace 事件时间戳却是真实系统时间的
|
|
1517
|
+
* 不一致。
|
|
1518
|
+
*/
|
|
1519
|
+
ts: number;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* 进程内存自我感知组件——App 生命周期内单例,`start()`/`dispose()` 对齐既有子系统模式
|
|
1523
|
+
* (如 `SchedulerService` 的 unref ticker,见 `packages/schedule/src/scheduler.ts`,
|
|
1524
|
+
* RFC-161 M2 已迁出 @x-otto/coding)。
|
|
1525
|
+
*/
|
|
1526
|
+
declare class MemoryGovernor {
|
|
1527
|
+
/** RFC-324 D2:非 readonly——支持 settings load 后晚绑定(与 onWarning 等回调的晚绑定模式一致)。
|
|
1528
|
+
* 构造期先走 env/默认值;settings load 后若 env 未设,由 app 层调 updateThresholds 覆盖。 */
|
|
1529
|
+
private warnThresholdPct;
|
|
1530
|
+
private hardThresholdPct;
|
|
1531
|
+
private readonly sampleIntervalMs;
|
|
1532
|
+
private readonly warningIntervalMs;
|
|
1533
|
+
private readonly criticalIntervalMs;
|
|
1534
|
+
private readonly warnCooldownMs;
|
|
1535
|
+
/** 非 readonly——支持构造后晚绑定(session 在 App 构造时尚不存在,对齐 `setScheduleService` 的晚绑定模式)。 */
|
|
1536
|
+
private onWarning;
|
|
1537
|
+
private onCritical;
|
|
1538
|
+
private readonly logger;
|
|
1539
|
+
private readonly now;
|
|
1540
|
+
private readonly postRestartGraceMs;
|
|
1541
|
+
private ticker;
|
|
1542
|
+
private lastLevel;
|
|
1543
|
+
/** 上一次 onWarning 实际触发的时间戳(epoch ms);undefined = 从未触发过。RFC-115 D4 冷却期基准。 */
|
|
1544
|
+
private lastWarningFiredAt;
|
|
1545
|
+
/** 宽限期起点(epoch ms);undefined = 不处于宽限期(全新会话,或宽限期未由调用方设置)。 */
|
|
1546
|
+
private graceStartedAt;
|
|
1547
|
+
private onCriticalDuringGrace;
|
|
1548
|
+
private onLevelChange;
|
|
1549
|
+
private onSample;
|
|
1550
|
+
/** RFC-133 D7:dispose 竞态防护——tick() 执行期间同步 dispose 后防止继续调度新定时器。 */
|
|
1551
|
+
private disposed;
|
|
1552
|
+
/** RFC-167 D1:critical 持续时长超时阈值(ms)。 */
|
|
1553
|
+
private readonly criticalTimeoutMs;
|
|
1554
|
+
private onCriticalTimeout;
|
|
1555
|
+
/** 首次进入 critical 分级的时间戳(epoch ms);恢复非 critical 时清空。RFC-167 D1。 */
|
|
1556
|
+
private criticalEnteredAt;
|
|
1557
|
+
/** 本次 critical 区间内是否已触发过一次 onCriticalTimeout(同一区间只触发一次)。 */
|
|
1558
|
+
private criticalTimeoutFired;
|
|
1559
|
+
constructor(options?: MemoryGovernorOptions);
|
|
1560
|
+
/** 当前分级(供外部只读查询,如 TUI 状态展示)。 */
|
|
1561
|
+
get currentLevel(): MemoryPressureLevel;
|
|
1562
|
+
/**
|
|
1563
|
+
* 晚绑定 onWarning/onCritical/onCriticalDuringGrace(App 构造时活跃 session 尚不存在,
|
|
1564
|
+
* M2/M3 在 session 就绪后调用注入)。对齐 `App.setScheduleService` 的晚绑定模式,
|
|
1565
|
+
* 非构造期可选依赖。
|
|
1566
|
+
*/
|
|
1567
|
+
/**
|
|
1568
|
+
* 合并式更新(每个字段独立覆盖,未传的字段保留原值)——RFC-115 追补:`app.ts`
|
|
1569
|
+
* 启动期注入 `onSample`(trace 落盘)后,`interactive.ts` 会话就绪时再调用一次
|
|
1570
|
+
* 注入 `onWarning`/`onCritical` 等;若采用整体覆盖语义,后一次调用会把前一次
|
|
1571
|
+
* 设置的 `onSample` 清空。多个调用方各自只关心自己负责的字段,改为逐字段合并,
|
|
1572
|
+
* 避免调用顺序耦合。
|
|
1573
|
+
*/
|
|
1574
|
+
setCallbacks(callbacks: {
|
|
1575
|
+
onWarning?: () => void;
|
|
1576
|
+
onCritical?: (sample: MemorySample) => void;
|
|
1577
|
+
onCriticalDuringGrace?: (sample: MemorySample) => void;
|
|
1578
|
+
onLevelChange?: (level: MemoryPressureLevel) => void;
|
|
1579
|
+
onSample?: (sample: MemorySample) => void;
|
|
1580
|
+
onCriticalTimeout?: (sample: MemorySample) => void;
|
|
1581
|
+
}): void;
|
|
1582
|
+
/**
|
|
1583
|
+
* RFC-115 D5:标记本次启动为重启恢复(如 `--continue`),从当前时刻起算宽限期
|
|
1584
|
+
* `postRestartGraceMs`。宽限期内 critical 触发走 `onCriticalDuringGrace`(降级通知)
|
|
1585
|
+
* 而非 `onCritical`(弹窗)。调用方(interactive.ts)应仅在检测到 `--continue` 时调用;
|
|
1586
|
+
* 全新会话不调用,`onCritical` 立即生效无宽限期。
|
|
1587
|
+
*/
|
|
1588
|
+
markRestarted(): void;
|
|
1589
|
+
/** RFC-324 D2:settings load 后晚绑定阈值(优先级 env > config > 默认)。
|
|
1590
|
+
* 仅当对应 env 未设时才覆盖——env 设了则构造期已读 env 值,此处不覆盖。 */
|
|
1591
|
+
updateThresholds(opts: {
|
|
1592
|
+
warnThresholdPct?: number;
|
|
1593
|
+
hardThresholdPct?: number;
|
|
1594
|
+
}): void;
|
|
1595
|
+
/** 当前是否处于重启后宽限期内(供测试/可观测查询)。 */
|
|
1596
|
+
get inPostRestartGrace(): boolean;
|
|
1597
|
+
/** 单次采样 + 分级(不依赖 ticker,供测试与 onDemand 查询复用)。 */
|
|
1598
|
+
sample(): MemorySample;
|
|
1599
|
+
private grade;
|
|
1600
|
+
/** 启动周期采样(unref,不阻止进程退出)。幂等——重复调用是 no-op。 */
|
|
1601
|
+
start(): void;
|
|
1602
|
+
private tick;
|
|
1603
|
+
/** 停止采样,清 timeout。App.stop() 中调用。幂等。 */
|
|
1604
|
+
dispose(): void;
|
|
1605
|
+
}
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region src/fleet-monitor.d.ts
|
|
1608
|
+
interface FleetSample {
|
|
1609
|
+
/** 全机 otto 主进程数。 */
|
|
1610
|
+
readonly processCount: number;
|
|
1611
|
+
/** 总 RSS(MB,`ps` 口径含共享库——趋势观测用,勿作精算)。 */
|
|
1612
|
+
readonly totalRssMb: number;
|
|
1613
|
+
/** 疑似孤儿计数(ppid==1 且无 TTY 的枚举近似,非租约级判定)。 */
|
|
1614
|
+
readonly suspectedOrphanCount: number;
|
|
1615
|
+
/** 进程中最大代际计数(--restart-generation,M3 后有值;无则 0)。 */
|
|
1616
|
+
readonly maxGeneration: number;
|
|
1617
|
+
/** 采样时刻(epoch ms,注入 now 时间源)。 */
|
|
1618
|
+
readonly ts: number;
|
|
1619
|
+
}
|
|
1620
|
+
interface FleetMonitorOptions {
|
|
1621
|
+
/** 采样间隔(ms)。默认 600_000(10 分钟),经 OTTO_FLEET_SAMPLE_INTERVAL_MS 覆盖。 */
|
|
1622
|
+
sampleIntervalMs?: number;
|
|
1623
|
+
/** 进程数预算。默认 8,经 OTTO_FLEET_MAX_PROCESSES 覆盖。 */
|
|
1624
|
+
maxProcesses?: number;
|
|
1625
|
+
/** 总 RSS 预算(MB)。默认 8192,经 OTTO_FLEET_MAX_TOTAL_RSS_MB 覆盖。 */
|
|
1626
|
+
maxTotalRssMb?: number;
|
|
1627
|
+
/** 每次采样触发(落 trace 用,完整时间序列)。 */
|
|
1628
|
+
onSample?: (sample: FleetSample) => void;
|
|
1629
|
+
/** 预算首次超限触发(去重后);恢复到预算内后再次超限会再触发。 */
|
|
1630
|
+
onBudgetExceeded?: (sample: FleetSample, reason: 'process-count' | 'total-rss') => void;
|
|
1631
|
+
/** 进程枚举(测试注入)。默认 listOttoProcesses。 */
|
|
1632
|
+
listProcesses?: () => OttoProcessInfo[];
|
|
1633
|
+
logger?: Logger;
|
|
1634
|
+
/** 时间源注入(测试用,对齐 MemoryGovernor 的 deps.now 模式)。 */
|
|
1635
|
+
now?: () => number;
|
|
1636
|
+
}
|
|
1637
|
+
declare class FleetMonitor {
|
|
1638
|
+
private readonly sampleIntervalMs;
|
|
1639
|
+
private readonly maxProcesses;
|
|
1640
|
+
private readonly maxTotalRssMb;
|
|
1641
|
+
private readonly listProcesses;
|
|
1642
|
+
private readonly logger;
|
|
1643
|
+
private readonly now;
|
|
1644
|
+
private onSample;
|
|
1645
|
+
private onBudgetExceeded;
|
|
1646
|
+
private ticker;
|
|
1647
|
+
private disposed;
|
|
1648
|
+
/** 超限通知去重:当前是否处于"已通知过的超限区间"内。恢复预算内后清零可再触发。 */
|
|
1649
|
+
private budgetExceededNotified;
|
|
1650
|
+
constructor(options?: FleetMonitorOptions);
|
|
1651
|
+
/** 合并式回调更新(逐字段,对齐 MemoryGovernor.setCallbacks 语义)。 */
|
|
1652
|
+
setCallbacks(callbacks: {
|
|
1653
|
+
onSample?: (sample: FleetSample) => void;
|
|
1654
|
+
onBudgetExceeded?: (sample: FleetSample, reason: 'process-count' | 'total-rss') => void;
|
|
1655
|
+
}): void;
|
|
1656
|
+
/** 单次采样(不依赖 ticker,供测试/onDemand 查询)。枚举失败返回 undefined(fail-soft)。 */
|
|
1657
|
+
sample(): FleetSample | undefined;
|
|
1658
|
+
private tick;
|
|
1659
|
+
/** 启动周期采样(unref 不阻止进程退出)。幂等。首轮延迟一个完整间隔(启动期不抢 CPU)。 */
|
|
1660
|
+
start(): void;
|
|
1661
|
+
/** 停止采样。幂等。 */
|
|
1662
|
+
dispose(): void;
|
|
1663
|
+
}
|
|
1664
|
+
//#endregion
|
|
1665
|
+
//#region src/process-registry.d.ts
|
|
1666
|
+
type BackgroundProcessStatus = 'running' | 'killing' | 'exited' | 'killed';
|
|
1667
|
+
/**
|
|
1668
|
+
* RFC-223:委托后台进程的服务协议分类——从运行时输出识别,best-effort。
|
|
1669
|
+
* 结构上与 `@x-otto/tools` 的 `background-types.ts` 同名类型一致,但**不 import 它**——
|
|
1670
|
+
* 沿用本文件头注释既定的 tools↔runtime 解耦策略(两者都依赖 agent,互不依赖,靠结构化
|
|
1671
|
+
* 类型满足契约,避免循环依赖)。
|
|
1672
|
+
*/
|
|
1673
|
+
type ServiceProtocol = 'http' | 'ws' | 'tcp';
|
|
1674
|
+
/** RFC-223:从后台进程运行时输出识别到的服务信息(url 优先,退化到纯端口)。 */
|
|
1675
|
+
interface DetectedService {
|
|
1676
|
+
url?: string;
|
|
1677
|
+
port?: number;
|
|
1678
|
+
protocol: ServiceProtocol;
|
|
1679
|
+
}
|
|
1680
|
+
interface BackgroundProcess {
|
|
1681
|
+
/** 返回给模型的句柄,如 bg_xxx。 */
|
|
1682
|
+
id: string;
|
|
1683
|
+
sessionId: string;
|
|
1684
|
+
/** 归属宿主(App)标识:App.stop 据此只杀本 App 起的进程,不越界杀兄弟 App。register 时打标,
|
|
1685
|
+
* 与 session pool 成员无关 → 会话被驱逐后其进程仍归本 App。缺省 undefined(未分宿主)。 */
|
|
1686
|
+
owner?: string;
|
|
1687
|
+
pid: number;
|
|
1688
|
+
/** 进程组 id;unix detached 下 == pid,killpg 用。 */
|
|
1689
|
+
pgid: number;
|
|
1690
|
+
command: string;
|
|
1691
|
+
cwd: string;
|
|
1692
|
+
/** 从参数/输出 best-effort 嗅探(可能缺)。 */
|
|
1693
|
+
port?: number;
|
|
1694
|
+
url?: string;
|
|
1695
|
+
/** 输出落临时文件路径(P1 由 bash 工具填)。 */
|
|
1696
|
+
logPath?: string;
|
|
1697
|
+
startedAt: number;
|
|
1698
|
+
lastUsed: number;
|
|
1699
|
+
status: BackgroundProcessStatus;
|
|
1700
|
+
exitCode?: number;
|
|
1701
|
+
/** 显式保护,免被上限 LRU 驱逐。 */
|
|
1702
|
+
keepAlive?: boolean;
|
|
1703
|
+
/** RFC-223:运行时输出识别到的服务信息(D2 去重规则见 `updateDetectedService`)。 */
|
|
1704
|
+
detectedService?: DetectedService;
|
|
1705
|
+
}
|
|
1706
|
+
interface RegisterInput {
|
|
1707
|
+
id: string;
|
|
1708
|
+
sessionId: string;
|
|
1709
|
+
/** 归属宿主(App)标识,见 BackgroundProcess.owner。 */
|
|
1710
|
+
owner?: string;
|
|
1711
|
+
/** 子进程句柄:用于监听自然退出 + 推导 pgid。 */
|
|
1712
|
+
child: ChildProcess;
|
|
1713
|
+
command: string;
|
|
1714
|
+
cwd: string;
|
|
1715
|
+
logPath?: string;
|
|
1716
|
+
port?: number;
|
|
1717
|
+
url?: string;
|
|
1718
|
+
keepAlive?: boolean;
|
|
1719
|
+
/** 注入时钟,便于测试;缺省 Date.now。 */
|
|
1720
|
+
now?: number;
|
|
1721
|
+
}
|
|
1722
|
+
interface ProcessRegistryOptions {
|
|
1723
|
+
maxProcesses?: number;
|
|
1724
|
+
graceMs?: number;
|
|
1725
|
+
/** 可注入的时钟,仅用于 lastUsed/startedAt 戳(测试用)。 */
|
|
1726
|
+
clock?: () => number;
|
|
1727
|
+
}
|
|
1728
|
+
type ProcessListener = (process: BackgroundProcess) => void;
|
|
1729
|
+
declare class ProcessRegistry {
|
|
1730
|
+
private readonly entries;
|
|
1731
|
+
private readonly maxProcesses;
|
|
1732
|
+
private readonly graceMs;
|
|
1733
|
+
private readonly clock;
|
|
1734
|
+
private readonly spawnListeners;
|
|
1735
|
+
private readonly exitListeners;
|
|
1736
|
+
constructor(options?: ProcessRegistryOptions);
|
|
1737
|
+
/** 订阅后台进程登记事件。返回退订函数。 */
|
|
1738
|
+
onSpawn(listener: ProcessListener): () => void;
|
|
1739
|
+
/**
|
|
1740
|
+
* 订阅后台进程退出事件(自然退出或被杀都触发一次,单一来源)。返回退订函数。
|
|
1741
|
+
* 注意:宿主优雅关闭时应先退订再 killAll,避免 teardown 期误发通知。
|
|
1742
|
+
*/
|
|
1743
|
+
onExit(listener: ProcessListener): () => void;
|
|
1744
|
+
private notify;
|
|
1745
|
+
/**
|
|
1746
|
+
* 登记后台进程。撞满上限时先驱逐最久未用的非 keepAlive 进程;若无可驱逐则抛错
|
|
1747
|
+
* (提示模型先 kill_shell)。
|
|
1748
|
+
*/
|
|
1749
|
+
register(input: RegisterInput): BackgroundProcess;
|
|
1750
|
+
get(id: string): BackgroundProcess | undefined;
|
|
1751
|
+
getForSession(sessionId: string, id: string): BackgroundProcess | undefined;
|
|
1752
|
+
list(): BackgroundProcess[];
|
|
1753
|
+
listForSession(sessionId: string): BackgroundProcess[];
|
|
1754
|
+
touch(id: string): void;
|
|
1755
|
+
/**
|
|
1756
|
+
* RFC-223 D2:运行时输出识别到新的服务信息时调用。去重规则——"最近一条更优先":
|
|
1757
|
+
* - 新结果含 url 而当前无 url → 覆盖。
|
|
1758
|
+
* - 新旧都含 url 但内容不同(如 localhost → 局域网地址)→ 覆盖为最新一条
|
|
1759
|
+
* (dev server 常见先打印 Local 后打印 Network,后者对想跨设备访问的用户更有用)。
|
|
1760
|
+
* - 新结果仅 port(无 url)而当前已有 url → 不覆盖(避免更差信息覆盖更好信息)。
|
|
1761
|
+
* - 其余情况(当前为空,或新旧完全相同)→ 直接采用新结果。
|
|
1762
|
+
* 进程已不在册(已退出/被驱逐/从未注册过该 id)时静默忽略,不抛错——识别回调是
|
|
1763
|
+
* fire-and-forget 的旁路信号,注册表状态是唯一真源,找不到 entry 不代表错误。
|
|
1764
|
+
*/
|
|
1765
|
+
updateDetectedService(id: string, service: DetectedService): void;
|
|
1766
|
+
remove(id: string): void;
|
|
1767
|
+
size(): number;
|
|
1768
|
+
/** 优雅杀单个:SIGTERM → grace → SIGKILL。status 机防重复杀(BP7)。 */
|
|
1769
|
+
kill(id: string): Promise<void>;
|
|
1770
|
+
/** 优雅杀光(宿主 SIGINT/SIGTERM 等优雅退出路径用)。 */
|
|
1771
|
+
killAll(): Promise<void>;
|
|
1772
|
+
/**
|
|
1773
|
+
* 优雅杀某宿主(App)起的全部进程(App.stop 用)。按 register 时打的 owner 标过滤——多 App 同进程时
|
|
1774
|
+
* 不越界杀兄弟 App,且涵盖会话已被 pool 驱逐但进程仍在的(owner 与 pool 成员无关)。
|
|
1775
|
+
*/
|
|
1776
|
+
killAllForOwner(owner: string): Promise<void>;
|
|
1777
|
+
/** 优雅杀某会话全部(SessionPool.dispose 用)。 */
|
|
1778
|
+
killAllForSession(sessionId: string): Promise<void>;
|
|
1779
|
+
/**
|
|
1780
|
+
* 同步杀光(`process.on('exit')` 兜底用——退出回调**不能** await)。
|
|
1781
|
+
* 直接 SIGKILL 整组,best-effort。
|
|
1782
|
+
*/
|
|
1783
|
+
killAllSync(): void;
|
|
1784
|
+
/** 同步 SIGKILL 单个;已退/已杀则跳过。 */
|
|
1785
|
+
private killEntrySync;
|
|
1786
|
+
/**
|
|
1787
|
+
* 撞满上限时驱逐:优先已退出的,其次最久未用的非 keepAlive 进程(同步杀 + 移除)。
|
|
1788
|
+
* 全为 keepAlive 时不驱逐(register 随后抛错)。
|
|
1789
|
+
*/
|
|
1790
|
+
private evictLeastRecent;
|
|
1791
|
+
private killEntry;
|
|
1792
|
+
private waitForExit;
|
|
1793
|
+
}
|
|
1794
|
+
/** 进程级单例:宿主与工具层共享同一注册表。 */
|
|
1795
|
+
declare const globalProcessRegistry: ProcessRegistry;
|
|
1796
|
+
/**
|
|
1797
|
+
* 安装宿主退出兜底:`process.on('exit')` → 同步 killAllSync。
|
|
1798
|
+
* 幂等。**不**安装 SIGINT/SIGTERM(交互式 TUI 自有处理;非交互宿主在各自
|
|
1799
|
+
* shutdown 路径里调 killAll)。
|
|
1800
|
+
*/
|
|
1801
|
+
declare function installProcessReaper(registry?: ProcessRegistry): void;
|
|
1802
|
+
//#endregion
|
|
1803
|
+
//#region src/process-runtime.d.ts
|
|
1804
|
+
type ProcessCategory = 'mcp' | 'lsp' | 'shell' | 'plugin' | 'browser' | 'tool' | 'cli';
|
|
1805
|
+
type ProcessStatus = 'starting' | 'running' | 'killing' | 'killed' | 'exited' | 'errored';
|
|
1806
|
+
type ProcessLifecycle = 'pinned' | 'evictable';
|
|
1807
|
+
interface ProcessOwner {
|
|
1808
|
+
/** 归属类型:mcp-server / lsp-server / plugin-hook / plugin-script / agent-tool / cli-shell / cli-chaperone / browser / notification / background-bash */
|
|
1809
|
+
type: string;
|
|
1810
|
+
/** 归属 id:mcp server name / plugin id / session id */
|
|
1811
|
+
id: string;
|
|
1812
|
+
}
|
|
1813
|
+
interface ProcessSpawnConfig {
|
|
1814
|
+
command: string;
|
|
1815
|
+
args?: string[];
|
|
1816
|
+
options?: SpawnOptions;
|
|
1817
|
+
/** 必填:谁 spawn 的 */
|
|
1818
|
+
owner: ProcessOwner;
|
|
1819
|
+
/** 关联 session */
|
|
1820
|
+
sessionId?: string;
|
|
1821
|
+
/** 进程分类 */
|
|
1822
|
+
category: ProcessCategory;
|
|
1823
|
+
/** 人类可读标签(调试/日志) */
|
|
1824
|
+
label?: string;
|
|
1825
|
+
/** 生命周期:pinned(常驻,免 LRU 驱逐)/ evictable(短命,可驱逐)。默认 evictable */
|
|
1826
|
+
lifecycle?: ProcessLifecycle;
|
|
1827
|
+
/** detached + 免批量 kill(OAuth 浏览器 / notify launcher)。与 lifecycle 正交 */
|
|
1828
|
+
keepAlive?: boolean;
|
|
1829
|
+
/** 覆盖全局默认 SIGTERM→SIGKILL 宽限期(默认 2000ms) */
|
|
1830
|
+
killGraceMs?: number;
|
|
1831
|
+
/** 硬上限(默认 30000ms) */
|
|
1832
|
+
killTimeoutMs?: number;
|
|
1833
|
+
/** 外部取消信号 */
|
|
1834
|
+
signal?: AbortSignal;
|
|
1835
|
+
/**
|
|
1836
|
+
* 环境变量覆盖。spawn() 会以此为 override 调 buildAllowedEnv(规则 7):
|
|
1837
|
+
* 白名单基座(PATH/HOME/LANG/… strip 凭证)+ 本 override,凭证永不 bypass。
|
|
1838
|
+
* 注意:registerChild 路径不经此(子进程已由调用方 spawn,env 已定)。
|
|
1839
|
+
*/
|
|
1840
|
+
env?: Record<string, string>;
|
|
1841
|
+
cwd?: string;
|
|
1842
|
+
}
|
|
1843
|
+
interface KillOptions {
|
|
1844
|
+
graceMs?: number;
|
|
1845
|
+
timeoutMs?: number;
|
|
1846
|
+
reason?: string;
|
|
1847
|
+
}
|
|
1848
|
+
interface ExitResult {
|
|
1849
|
+
code: number | null;
|
|
1850
|
+
signal: NodeJS.Signals | null;
|
|
1851
|
+
timedOut: boolean;
|
|
1852
|
+
}
|
|
1853
|
+
interface ProcessFilter {
|
|
1854
|
+
owner?: ProcessOwner;
|
|
1855
|
+
sessionId?: string;
|
|
1856
|
+
category?: ProcessCategory;
|
|
1857
|
+
}
|
|
1858
|
+
interface ManagedProcess {
|
|
1859
|
+
readonly id: string;
|
|
1860
|
+
readonly pid: number | undefined;
|
|
1861
|
+
/** 进程组 id;unix detached 下 == pid,非-detached 下可能不等于 pid */
|
|
1862
|
+
readonly pgid: number | undefined;
|
|
1863
|
+
readonly status: ProcessStatus;
|
|
1864
|
+
readonly config: ProcessSpawnConfig;
|
|
1865
|
+
/** 原始字节流 stdin/stdout/stderr(规则 5/H2:非行缓冲,帧解析由消费者负责) */
|
|
1866
|
+
readonly stdin: NodeJS.WritableStream | null;
|
|
1867
|
+
readonly stdout: NodeJS.ReadableStream | null;
|
|
1868
|
+
readonly stderr: NodeJS.ReadableStream | null;
|
|
1869
|
+
/** 便利层;LSP/MCP 等帧协议应直接消费字节流。 */
|
|
1870
|
+
onStdoutLine(cb: (line: string) => void): () => void;
|
|
1871
|
+
onStderrLine(cb: (line: string) => void): () => void;
|
|
1872
|
+
onExit(cb: (result: ExitResult) => void): () => void;
|
|
1873
|
+
onError(cb: (err: Error) => void): () => void;
|
|
1874
|
+
/** 优雅杀:SIGTERM → grace → SIGKILL(Windows 走 taskkill;复用 signalGroup 单 pid 回退——C1) */
|
|
1875
|
+
kill(opts?: KillOptions): Promise<void>;
|
|
1876
|
+
waitForExit(timeoutMs?: number): Promise<ExitResult>;
|
|
1877
|
+
/** 上次活跃时间戳(LRU 驱逐用) */
|
|
1878
|
+
lastUsed: number;
|
|
1879
|
+
}
|
|
1880
|
+
interface ProcessRuntimeOptions {
|
|
1881
|
+
maxProcesses?: number;
|
|
1882
|
+
killGraceMs?: number;
|
|
1883
|
+
killTimeoutMs?: number;
|
|
1884
|
+
clock?: () => number;
|
|
1885
|
+
}
|
|
1886
|
+
declare class ProcessRuntime {
|
|
1887
|
+
private readonly _entries;
|
|
1888
|
+
private readonly _maxProcesses;
|
|
1889
|
+
readonly killGraceMs: number;
|
|
1890
|
+
readonly killTimeoutMs: number;
|
|
1891
|
+
private readonly _clock;
|
|
1892
|
+
private readonly _spawnListeners;
|
|
1893
|
+
private readonly _exitListeners;
|
|
1894
|
+
constructor(options?: ProcessRuntimeOptions);
|
|
1895
|
+
onSpawn(listener: (proc: ManagedProcess) => void): () => void;
|
|
1896
|
+
onExit(listener: (proc: ManagedProcess) => void): () => void;
|
|
1897
|
+
/** 池空时触发。 */
|
|
1898
|
+
onAllExited(listener: () => void): () => void;
|
|
1899
|
+
/**
|
|
1900
|
+
* 启动子进程并返回 ManagedProcess 句柄。
|
|
1901
|
+
* 非-keepAlive 子进程强制 `detached: true`(C1/规则 4:使子进程成为组长,-pgid 生效)。
|
|
1902
|
+
*/
|
|
1903
|
+
spawn(config: ProcessSpawnConfig): ManagedProcess;
|
|
1904
|
+
get(id: string): ManagedProcess | undefined;
|
|
1905
|
+
list(filter?: ProcessFilter): ManagedProcess[];
|
|
1906
|
+
listForSession(sessionId: string): ManagedProcess[];
|
|
1907
|
+
listForOwner(owner: ProcessOwner): ManagedProcess[];
|
|
1908
|
+
touch(id: string): void;
|
|
1909
|
+
remove(id: string): void;
|
|
1910
|
+
size(): number;
|
|
1911
|
+
/** kill 满足 filter 的非 keepAlive 进程。pinned 进程也杀——killAll 是显式操作非 LRU 驱逐。 */
|
|
1912
|
+
killAll(filter?: ProcessFilter, opts?: KillOptions): Promise<void>;
|
|
1913
|
+
/**
|
|
1914
|
+
* 同步 SIGKILL 所有非 keepAlive 进程。
|
|
1915
|
+
* `process.on('exit')` 兜底用——不声称覆盖信号退出(C2/规则 3)。
|
|
1916
|
+
* 复用 `signalGroup` 单 pid 回退(C1/规则 4),禁裸 `process.kill(-pid)`。
|
|
1917
|
+
* Windows 单次批量 taskkill(规则 9)。
|
|
1918
|
+
*/
|
|
1919
|
+
killAllSync(): void;
|
|
1920
|
+
killAllForOwner(ownerType: string, ownerId: string): Promise<void>;
|
|
1921
|
+
killAllForSession(sessionId: string): Promise<void>;
|
|
1922
|
+
/**
|
|
1923
|
+
* 低级注册:包装一个已被外部 `child_process.spawn()` 创建的 ChildProcess 为 ManagedProcess。
|
|
1924
|
+
* 用于兼容 shim(旧 ProcessRegistry API 预 spawn 了 child)和后续子系统渐进迁移。
|
|
1925
|
+
*
|
|
1926
|
+
* 规则 4/C1:非-keepAlive 若未 detached,调用方应确保进程组长语义正确。
|
|
1927
|
+
*/
|
|
1928
|
+
registerChild(config: ProcessSpawnConfig, child: ChildProcess): ManagedProcess;
|
|
1929
|
+
private _evictLeastRecent;
|
|
1930
|
+
}
|
|
1931
|
+
/** 全局进程运行时单例。 */
|
|
1932
|
+
declare const globalProcessRuntime: ProcessRuntime;
|
|
1933
|
+
/**
|
|
1934
|
+
* 安装宿主退出兜底:`process.on('exit')` → 同步 `killAllSync()`。
|
|
1935
|
+
* 幂等。不覆盖 SIGINT/SIGTERM——交互式 TUI 自有处理。
|
|
1936
|
+
*
|
|
1937
|
+
* **C2/规则 3**:不声称覆盖信号退出——`process.on('exit')` 在 SIGTERM/SIGKILL/SIGHUP 下不触发。
|
|
1938
|
+
* 正常 `process.exit()` 和可捕获崩溃路径才触发。
|
|
1939
|
+
*/
|
|
1940
|
+
declare function installExitReaper(rt?: ProcessRuntime): void;
|
|
1941
|
+
//#endregion
|
|
1942
|
+
//#region src/git-worktree.d.ts
|
|
1943
|
+
/**
|
|
1944
|
+
* git-worktree.ts —— RFC-023 Phase 1:agent-session 作业的 git worktree 隔离。
|
|
1945
|
+
*
|
|
1946
|
+
* 后台 agent 作业**绝不能**背着用户改主工作区(本会话 cli.ts 被并发改的事故即反例)。
|
|
1947
|
+
* 每个作业跑在独立 worktree(临时分支):agent 在 worktree 内自由改 → 产出 diff → 主会话
|
|
1948
|
+
* 评审后带冲突 apply(worktreePath/worktreeBranch 隔离 + apply-patch 回主区)。
|
|
1949
|
+
*
|
|
1950
|
+
* 纯 git 子进程封装(execFileSync 数组参数,无 shell 注入);不持状态。
|
|
1951
|
+
*/
|
|
1952
|
+
interface Worktree {
|
|
1953
|
+
/** worktree 绝对路径(agent 作业的 cwd / workspaceDir)。 */
|
|
1954
|
+
path: string;
|
|
1955
|
+
/** 临时分支名。 */
|
|
1956
|
+
branch: string;
|
|
1957
|
+
/** 建 worktree 时主仓 HEAD 的 sha(apply 时检漂移)。 */
|
|
1958
|
+
base: string;
|
|
1959
|
+
/** 主仓根(remove 时用)。 */
|
|
1960
|
+
repoRoot: string;
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* 为作业建一个 worktree:基于主仓当前 HEAD 拉临时分支,落在系统临时目录(不污染主仓树)。
|
|
1964
|
+
*
|
|
1965
|
+
* L2 注意:worktree 落在 `os.tmpdir()` 下**有意为之**——沙箱可写根含 tmpdir(computeSandboxRoots),
|
|
1966
|
+
* 故沙箱开启时 agent 对 worktree 的写恰好被放行。若改 worktree 位置,须同步把该路径加入沙箱可写根,
|
|
1967
|
+
* 否则沙箱模式下 agent 作业写文件会被拒。
|
|
1968
|
+
*/
|
|
1969
|
+
declare function createWorktree(repoRoot: string, jobId: string): Worktree;
|
|
1970
|
+
/**
|
|
1971
|
+
* 捕获 agent 在 worktree 内的全部改动为统一 diff(含未跟踪文件 + 删除)。
|
|
1972
|
+
* worktree 分支 HEAD == base(作业不 commit),故 stage 全部后 `diff --cached` 即作业产物。
|
|
1973
|
+
*
|
|
1974
|
+
* M2 已知限制:`git add -A` **遵守 .gitignore**——agent 创建的被忽略文件不进 diff、apply 不带过去
|
|
1975
|
+
* (这通常是想要的:忽略文件本就不该入库)。若将来需带忽略产物,改用 `add -A --force`。
|
|
1976
|
+
*/
|
|
1977
|
+
declare function worktreeDiff(wt: Worktree): string;
|
|
1978
|
+
interface ApplyDiffResult {
|
|
1979
|
+
applied: boolean;
|
|
1980
|
+
/** 冲突/失败的文件路径(best-effort 从 git stderr 解析)。 */
|
|
1981
|
+
conflictPaths: string[];
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* review J6:worktree 建立后主仓 HEAD 是否已经前进(`Worktree.base` 字段此前"apply 时检漂移"
|
|
1985
|
+
* 的注释所指,但从未被消费——apply 只靠 `git apply --check` 判定能否干净应用)。这是一个
|
|
1986
|
+
* **提示性**信号(`applyDiff` 的 `--check` 才是真正的冲突判定),用于向用户解释"为什么这个
|
|
1987
|
+
* apply 可能出乎意料地冲突/无冲突",不用来拒绝 apply。
|
|
1988
|
+
*/
|
|
1989
|
+
declare function hasRepoDrifted(repoRoot: string, base: string): boolean;
|
|
1990
|
+
/**
|
|
1991
|
+
* 把作业 diff apply 到主仓工作区。**check-first,不强改**:先 `git apply --check` 验证能否干净
|
|
1992
|
+
* 应用——能才真 apply;不能(主仓漂移/冲突)则返回 applied=false + conflictPaths,**绝不**往主树
|
|
1993
|
+
* 写冲突标记(区别于 `--3way` 会留 `>>>>>>>` 撕裂工作区)。让用户在 worktree 解决后重试或 cancel。
|
|
1994
|
+
*/
|
|
1995
|
+
declare function applyDiff(repoRoot: string, diff: string): ApplyDiffResult;
|
|
1996
|
+
/**
|
|
1997
|
+
* 把已 apply 的 diff 从主仓工作区**反向撤销**(`git apply --reverse`)。RFC-026 §4.2 回退。
|
|
1998
|
+
* 同样 **check-first,不强改**:工作树自 apply 后漂移(用户又改了那些文件)→ reverse check 失败 →
|
|
1999
|
+
* 返回 applied=false + conflictPaths,绝不强撤。干净才真撤。
|
|
2000
|
+
*/
|
|
2001
|
+
declare function revertDiff(repoRoot: string, diff: string): ApplyDiffResult;
|
|
2002
|
+
/**
|
|
2003
|
+
* 移除 worktree + 删临时分支(幂等 best-effort:已不在/已删不报错)。
|
|
2004
|
+
*
|
|
2005
|
+
* review J4:三步仍各自 best-effort(单步失败不阻塞后续——分支已被 prune 掉的场景下
|
|
2006
|
+
* `branch -D` 失败是正常噪音),但**不再完全静默**——失败会经可选 `onError` 报告,调用方
|
|
2007
|
+
* 可接 logger 观测到"清理不彻底"(此前三个空 catch 让权限/NFS stale handle/进程占用等
|
|
2008
|
+
* 真实失败无声吞掉,运维完全无感知)。`onError` 缺省不传时行为与修复前一致(纯静默)。
|
|
2009
|
+
*/
|
|
2010
|
+
declare function removeWorktree(wt: Worktree, onError?: (op: 'worktree remove' | 'branch delete' | 'worktree prune', err: unknown) => void): void;
|
|
2011
|
+
/**
|
|
2012
|
+
* 扫描并清理孤儿 worktree(SIGKILL/OOM 未走 exit reaper 留下的残留)。
|
|
2013
|
+
* 用 `git -C repoRoot worktree list --porcelain` 列出该仓的全部已注册 worktree,
|
|
2014
|
+
* 筛选出:① 路径匹配 tmpdir 下 `otto-wt-*` 模式的(本系统创建的),
|
|
2015
|
+
* ② 分支名匹配 `otto/job-*` 的,
|
|
2016
|
+
* 且这些 worktree 不在 `activeJobIds`(当前存活作业 id 集合,调用方传入,避免误删正在跑的)里对应的路径中。
|
|
2017
|
+
* 对匹配到的孤儿逐个 `git worktree remove --force` + `git branch -D`,
|
|
2018
|
+
* 最后 `git worktree prune`。全程 best-effort(单条失败不中断其余)。
|
|
2019
|
+
* 返回被清理的路径列表(供日志/测试断言)。
|
|
2020
|
+
*/
|
|
2021
|
+
declare function pruneOrphanWorktrees(repoRoot: string, activeJobIds: Iterable<string>): string[];
|
|
2022
|
+
//#endregion
|
|
2023
|
+
//#region src/governance/forbidden-zone.d.ts
|
|
2024
|
+
/**
|
|
2025
|
+
* forbidden-zone.ts —— RFC-026 §4.0 治理禁区锁(自我进化的前提铁律)。
|
|
2026
|
+
*
|
|
2027
|
+
* 自我修改系统最大的风险是**改写绑住自己的绳子**:一个 delegate 的 diff 里夹带改自主分级、
|
|
2028
|
+
* 出网 allowlist、权限/沙箱、apply/revert 能力、设置 schema……于是任何"自主级"最终都能解除
|
|
2029
|
+
* 约束自己的规则。本模块提供一个**独立守卫**:扫 diff 触及的路径 ∩ 禁区集合 → 命中即"高危"。
|
|
2030
|
+
*
|
|
2031
|
+
* 用法(分级):
|
|
2032
|
+
* - 手动 apply(L0,人即闸):命中只**高危警示**,仍可应用(人已在审)。
|
|
2033
|
+
* - 未来自主层(L≥2 自动 apply):命中**强制降 L0、拒绝自动落地**(不可被任何自主级旁路)。
|
|
2034
|
+
*
|
|
2035
|
+
* **自含**:禁区清单包含本文件自身(`governance/forbidden-zone`)——改禁区清单的 diff 自身即被
|
|
2036
|
+
* 标记,故无法被悄悄缩小/移除。终局还应把清单**外置/签名**(本阶段先在引擎内 + 自含兜底)。
|
|
2037
|
+
*/
|
|
2038
|
+
/** 禁区路径模式(匹配 repo 相对路径)。保守从宽——宁可多标人审,不可漏放(RFC §4.0)。 */
|
|
2039
|
+
declare const FORBIDDEN_ZONE_PATTERNS: readonly RegExp[];
|
|
2040
|
+
/**
|
|
2041
|
+
* 从统一 diff 里提取触及的文件路径(repo 相对)。覆盖 diff --git / +++ / --- 行;跳过 /dev/null。
|
|
2042
|
+
* 还原 git quotepath(引号化/八进制转义)——否则特殊字符文件名绕过治理守卫(fail-open)。
|
|
2043
|
+
*/
|
|
2044
|
+
declare function diffTouchedPaths(diff: string): string[];
|
|
2045
|
+
/**
|
|
2046
|
+
* 某 diff 触及的**禁区文件**(空数组=未触禁区)。调用方据此:手动 apply 高危警示 / 自主层强制 L0。
|
|
2047
|
+
*/
|
|
2048
|
+
declare function touchesForbiddenZone(diff: string): string[];
|
|
2049
|
+
//#endregion
|
|
2050
|
+
//#region src/governance/capability-surfaces.d.ts
|
|
2051
|
+
/**
|
|
2052
|
+
* capability-surfaces.ts —— RFC-026 阶段2:识别一个 diff 是否触及"能力面"。
|
|
2053
|
+
*
|
|
2054
|
+
* delegate 给 otto 加能力(MCP / provider / 声明式 agent·skill·team)= 改项目级配置/声明文件。
|
|
2055
|
+
* apply 后若 diff 触及能力面 → 自动热重载(reloadCapabilities),不重启即认到新能力。
|
|
2056
|
+
*
|
|
2057
|
+
* delegate 的 diff 只能改 worktree(repo)内文件,故能力新增必落在项目级 `.otto/`(agents/skills/
|
|
2058
|
+
* teams/config)或代码里——全局 `~/.otto/` 不在 worktree 范围。这里只认项目级配置/声明面。
|
|
2059
|
+
*/
|
|
2060
|
+
/** 能力面路径模式(匹配 repo 相对路径)。命中即该 diff 改动了可热重载的能力配置/声明。 */
|
|
2061
|
+
declare const CAPABILITY_SURFACE_PATTERNS: readonly RegExp[];
|
|
2062
|
+
/** 某 diff 触及的**能力面文件**(空数组=未触;非空=apply 后应热重载)。 */
|
|
2063
|
+
declare function touchesCapabilitySurface(diff: string): string[];
|
|
2064
|
+
//#endregion
|
|
2065
|
+
//#region src/governance/apply-policy.d.ts
|
|
2066
|
+
/**
|
|
2067
|
+
* apply-policy.ts —— 自主 apply 策略层的**纯决策核**。
|
|
2068
|
+
*
|
|
2069
|
+
* 自我进化闭环里那道「闸」:一个 delegate 作业产出的 diff 要不要自动落主仓。本模块只回答
|
|
2070
|
+
* "怎么落"(自动 / 建议人审 / 强制人审 / 禁区拒绝),**不执行任何 apply**——执行由调用方
|
|
2071
|
+
* (agent-job-service.apply 回路)据 Verdict 决定。纯函数:无 IO、无副作用,全 L0–L4 × 信号
|
|
2072
|
+
* 矩阵可单测覆盖。
|
|
2073
|
+
*
|
|
2074
|
+
* 铁律(第一判定,不可旁路):
|
|
2075
|
+
* origin='main'(自主)+ 触禁区 → 永远 forbid,不存在任何 autonomy 级/配置能让它自动落地。
|
|
2076
|
+
* 禁区清单见 governance/forbidden-zone(自含:改禁区清单的 diff 自身即触禁区)。
|
|
2077
|
+
*
|
|
2078
|
+
* 人手路径(origin='user'):人即闸——只产建议(advise),从不 forbid/auto。
|
|
2079
|
+
*/
|
|
2080
|
+
/** 自主分级。默认 0 = 人即闸 = 现状,零行为变化。 */
|
|
2081
|
+
type AutonomyLevel = 0 | 1 | 2 | 3 | 4;
|
|
2082
|
+
/** decideApply 的输入信号——全部由调用方计算后注入(保持本函数纯粹)。 */
|
|
2083
|
+
interface ApplySignals {
|
|
2084
|
+
/** touchesForbiddenZone(diff) 的结果(空=未触禁区)。 */
|
|
2085
|
+
forbiddenZone: string[];
|
|
2086
|
+
/** touchesCapabilitySurface(diff) 的结果(空=未触能力面)。 */
|
|
2087
|
+
capabilityTouched: string[];
|
|
2088
|
+
/** diff 规模(增/删行)。 */
|
|
2089
|
+
diffStat: {
|
|
2090
|
+
added: number;
|
|
2091
|
+
removed: number;
|
|
2092
|
+
};
|
|
2093
|
+
/** applyDiff --check 预检:true=有冲突,不可干净落地。 */
|
|
2094
|
+
conflict: boolean;
|
|
2095
|
+
/** 发起方:user=人手 /job apply;main=模型自主作业;plugin=插件代码自主发起(RFC-287 R2)。 */
|
|
2096
|
+
origin: 'user' | 'main' | 'plugin';
|
|
2097
|
+
}
|
|
2098
|
+
interface ApplyPolicy {
|
|
2099
|
+
level: AutonomyLevel;
|
|
2100
|
+
/** 自动落地的 diff 规模上限(增+删行);超过则降人手。默认 DEFAULT_MAX_AUTO_APPLY_LINES。 */
|
|
2101
|
+
maxAutoApplyLines?: number;
|
|
2102
|
+
}
|
|
2103
|
+
type Verdict = /** 自动落地(仅 origin='main' + level≥2 + 全部低风险条件满足)。 */{
|
|
2104
|
+
action: 'auto';
|
|
2105
|
+
} /** 人手落地 + 推荐(人即闸或 L1)。recommend=apply/review。 */ | {
|
|
2106
|
+
action: 'advise';
|
|
2107
|
+
recommend: 'apply' | 'review';
|
|
2108
|
+
reasons: string[];
|
|
2109
|
+
} /** 强制人手(自主但被降级:L0 / 冲突 / 超规模 / 能力面越级)。 */ | {
|
|
2110
|
+
action: 'gate';
|
|
2111
|
+
reasons: string[];
|
|
2112
|
+
} /** 禁区命中:拒绝自动落地(铁律,自主路径专属)。 */ | {
|
|
2113
|
+
action: 'forbid';
|
|
2114
|
+
reasons: string[];
|
|
2115
|
+
};
|
|
2116
|
+
/** 自动落地默认规模上限(保守)。超过即便低风险也降人手——大改动人审。 */
|
|
2117
|
+
declare const DEFAULT_MAX_AUTO_APPLY_LINES = 200;
|
|
2118
|
+
/**
|
|
2119
|
+
* 纯决策:据信号 + 策略产出 Verdict。无副作用。
|
|
2120
|
+
*
|
|
2121
|
+
* 判定顺序(铁律优先):
|
|
2122
|
+
* 1. origin='main'|'plugin' + 触禁区 → forbid(第一判定,任何级别不可旁路)。
|
|
2123
|
+
* 2. origin='plugin' → 永不自动落地(RFC-287 R2):触禁区已被上一条 forbid,其余一律
|
|
2124
|
+
* gate(强制人手)。插件代码自主发起的作业不享受 user 的"人即闸 advise"待遇(那条
|
|
2125
|
+
* 分支从不 forbid),也不参与 main 的 autonomy 自动落地——独立且最保守的一档。
|
|
2126
|
+
* 3. origin='user' → advise(人即闸;有风险信号则 recommend=review,否则 apply)。
|
|
2127
|
+
* 4. origin='main' + level≤1 → 不自动:L0=gate(必须人手),L1=advise(带推荐)。
|
|
2128
|
+
* 5. origin='main' + level≥2 → 低风险则 auto;否则 gate(列出 blocker)。
|
|
2129
|
+
* 低风险 = 无冲突 ∧ 规模≤上限 ∧(L2:未触能力面 / L3+:允许能力面)。
|
|
2130
|
+
*/
|
|
2131
|
+
declare function decideApply(signals: ApplySignals, policy: ApplyPolicy): Verdict;
|
|
2132
|
+
//#endregion
|
|
2133
|
+
//#region src/agent-job-registry.d.ts
|
|
2134
|
+
type AgentJobStatus = 'running' | 'needs_input' | 'ready' | 'applied' | 'failed' | 'canceled';
|
|
2135
|
+
/**
|
|
2136
|
+
* 作业来源(RFC-026):user = 用户 /delegate 委派的;main = 主对话(模型)在答复过程中起的。
|
|
2137
|
+
*
|
|
2138
|
+
* RFC-287 D2/R2 新增 `plugin`:插件代码经 `PluginHostCapabilities.startJob` 自主发起的作业
|
|
2139
|
+
* (需高危能力 `agent.dispatch`)。**必须是独立取值、不可复用 user/main**——`decideApply`
|
|
2140
|
+
* (governance/apply-policy)按 origin 分流:`'user'` 视为"人即闸"从不 forbid、`'main'` 在
|
|
2141
|
+
* 高 autonomy 下可 auto 落地,两者都不适用于"插件代码自主发起"这一档信任级别。独立取值
|
|
2142
|
+
* 使插件作业在策略层永远走最保守分支(见 apply-policy `decideApply`)。
|
|
2143
|
+
*/
|
|
2144
|
+
type JobOrigin = 'user' | 'main' | 'plugin';
|
|
2145
|
+
interface AgentSessionJob {
|
|
2146
|
+
id: string;
|
|
2147
|
+
sessionId: string;
|
|
2148
|
+
type: 'agent-session';
|
|
2149
|
+
title: string;
|
|
2150
|
+
/** 来源:用户委派 vs 主对话委派(面板分组用)。 */
|
|
2151
|
+
origin: JobOrigin;
|
|
2152
|
+
status: AgentJobStatus;
|
|
2153
|
+
/** 隔离 worktree(产物所在)。 */
|
|
2154
|
+
worktree: Worktree;
|
|
2155
|
+
/** 转录输出文件(logPath 式;/jobs tail + 完成通知指针)。 */
|
|
2156
|
+
outputPath: string;
|
|
2157
|
+
startedAt: number;
|
|
2158
|
+
/** ready 后填:worktree vs base 的统一 diff。 */
|
|
2159
|
+
diff?: string;
|
|
2160
|
+
/** failed 时填。 */
|
|
2161
|
+
error?: string;
|
|
2162
|
+
}
|
|
2163
|
+
interface RegisterAgentJobInput {
|
|
2164
|
+
id: string;
|
|
2165
|
+
sessionId: string;
|
|
2166
|
+
title: string;
|
|
2167
|
+
worktree: Worktree;
|
|
2168
|
+
outputPath: string;
|
|
2169
|
+
/** 来源(缺省 user)。 */
|
|
2170
|
+
origin?: JobOrigin;
|
|
2171
|
+
/** 取消该作业的 detached run(spawnDetachedRun 返回的 abort)。 */
|
|
2172
|
+
abort: () => void;
|
|
2173
|
+
now?: number;
|
|
2174
|
+
}
|
|
2175
|
+
type AgentJobListener = (job: AgentSessionJob) => void;
|
|
2176
|
+
interface AgentJobRegistryOptions {
|
|
2177
|
+
maxJobs?: number;
|
|
2178
|
+
clock?: () => number;
|
|
2179
|
+
/**
|
|
2180
|
+
* worktree 清理失败上报(review J4)。缺省不传时 `removeWorktree` 的三步失败仍完全静默
|
|
2181
|
+
* (行为等同修复前)——传入后可接 logger 观测权限/NFS stale handle/进程占用等真实失败。
|
|
2182
|
+
*/
|
|
2183
|
+
onWorktreeCleanupError?: (jobId: string, op: 'worktree remove' | 'branch delete' | 'worktree prune', err: unknown) => void;
|
|
2184
|
+
}
|
|
2185
|
+
declare class AgentJobRegistry {
|
|
2186
|
+
private readonly entries;
|
|
2187
|
+
private readonly maxJobs;
|
|
2188
|
+
private readonly clock;
|
|
2189
|
+
private onWorktreeCleanupError?;
|
|
2190
|
+
private readonly spawnListeners;
|
|
2191
|
+
private readonly updateListeners;
|
|
2192
|
+
private readonly exitListeners;
|
|
2193
|
+
private readonly removeListeners;
|
|
2194
|
+
private readonly retained;
|
|
2195
|
+
constructor(options?: AgentJobRegistryOptions);
|
|
2196
|
+
/**
|
|
2197
|
+
* 补设 worktree 清理失败上报(review J4)。`globalAgentJobRegistry` 是模块级单例,构造时
|
|
2198
|
+
* 无法从 app 层注入 logger——app 装配阶段调用本方法一次即可后接。
|
|
2199
|
+
*/
|
|
2200
|
+
setWorktreeCleanupErrorHandler(handler: AgentJobRegistryOptions['onWorktreeCleanupError']): void;
|
|
2201
|
+
onSpawn(listener: AgentJobListener): () => void;
|
|
2202
|
+
/** 状态/diff 变更(running→ready/needs_input 等)。 */
|
|
2203
|
+
onUpdate(listener: AgentJobListener): () => void;
|
|
2204
|
+
/** 终态(applied/failed/canceled)通知一次。 */
|
|
2205
|
+
onExit(listener: AgentJobListener): () => void;
|
|
2206
|
+
/** 作业从表中移除(手动删除/容量驱逐后)。cli 据此重推 /jobs(作业消失)。 */
|
|
2207
|
+
onRemove(listener: AgentJobListener): () => void;
|
|
2208
|
+
/**
|
|
2209
|
+
* R4:查看某作业时 retain;停止查看时 off。
|
|
2210
|
+
*
|
|
2211
|
+
* 2026-07-30 语义变更(applied 后 3 秒条目蒸发的终局修复):终态作业**不再定时驱逐**,
|
|
2212
|
+
* 保留至用户手动 `d` 删除(onBackgroundDelete → remove)或容量压力驱逐(register 满员
|
|
2213
|
+
* 时 evictTerminal)。retain 从"阻断驱逐定时器"改为"容量驱逐保护"——正在被查看的
|
|
2214
|
+
* 终态作业不会被 evictTerminal 腾退。对照社区(Claude Code / Roo Code):终态任务历史
|
|
2215
|
+
* 持续可见;原 3s graceMs 系对 CC STOPPED_DISPLAY_MS 的语义误用(那是 UI 显示宽限,
|
|
2216
|
+
* 不是数据条目驱逐宽限)。
|
|
2217
|
+
*/
|
|
2218
|
+
retain(id: string, on: boolean): void;
|
|
2219
|
+
private notify;
|
|
2220
|
+
/**
|
|
2221
|
+
* 登记作业。撞满上限时先驱逐已终态的;若全在跑则抛错(提示先 cancel)。
|
|
2222
|
+
* **不**驱逐在跑的 agent 作业(不像进程 LRU——杀活作业会丢工作)。
|
|
2223
|
+
*/
|
|
2224
|
+
register(input: RegisterAgentJobInput): AgentSessionJob;
|
|
2225
|
+
get(id: string): AgentSessionJob | undefined;
|
|
2226
|
+
getForSession(sessionId: string, id: string): AgentSessionJob | undefined;
|
|
2227
|
+
list(): AgentSessionJob[];
|
|
2228
|
+
listForSession(sessionId: string): AgentSessionJob[];
|
|
2229
|
+
size(): number;
|
|
2230
|
+
/**
|
|
2231
|
+
* 更新作业状态/diff/error(running→ready 等)。终态(applied/failed/canceled)额外触发 onExit
|
|
2232
|
+
* 并清理 worktree(applied/failed;canceled 由 cancel() 自己清)。
|
|
2233
|
+
*/
|
|
2234
|
+
update(id: string, patch: Partial<Pick<AgentSessionJob, 'status' | 'diff' | 'error'>>): void;
|
|
2235
|
+
/**
|
|
2236
|
+
* 取消作业:abort detached run + removeWorktree + status=canceled(onExit 一次)。
|
|
2237
|
+
*
|
|
2238
|
+
* review J5:改走 `update()` 流转状态(而非直改 `e.info.status` + 手动发 onExit),使
|
|
2239
|
+
* canceled 与 applied/failed 一样触发 onUpdate——此前 onUpdate 订阅者(如需据状态变化刷新
|
|
2240
|
+
* UI 的消费者)会漏收 canceled 事件。worktree 仍在此处**先行**清理(update() 对已是
|
|
2241
|
+
* 'canceled' 的状态会跳过它自己的 removeWorktree,避免重复清理)。
|
|
2242
|
+
*/
|
|
2243
|
+
cancel(id: string): void;
|
|
2244
|
+
/**
|
|
2245
|
+
* 从表中移除(用户 `d` 手动删除 / 容量驱逐后调用)。清 retain 并通知 onRemove。
|
|
2246
|
+
* 防御性兜底:再调一次 removeWorktree(review P1:正常终态流转在 update()/cancel() 已清理过,
|
|
2247
|
+
* 这里是"清理被跳过"异常路径的兜底——removeWorktree 内部 try/catch 逐步执行,对已清理/
|
|
2248
|
+
* 不存在的 worktree 是安全空操作,不会因重复调用报错或产生副作用)。
|
|
2249
|
+
*/
|
|
2250
|
+
remove(id: string): void;
|
|
2251
|
+
/**
|
|
2252
|
+
* 撞上限时驱逐已终态作业(不动在跑的;跳过 retained——正被查看的终态作业不腾退)。
|
|
2253
|
+
* 走 remove 以发 onRemove。终态不再定时驱逐后,这里是唯一的自动驱逐路径(容量压力)。
|
|
2254
|
+
*/
|
|
2255
|
+
private evictTerminal;
|
|
2256
|
+
/** 宿主退出兜底:同步 cancel 所有在跑作业(abort + removeWorktree),防 worktree 泄漏。 */
|
|
2257
|
+
cleanupAll(): void;
|
|
2258
|
+
}
|
|
2259
|
+
/** 进程级单例:宿主与作业服务共享。 */
|
|
2260
|
+
declare const globalAgentJobRegistry: AgentJobRegistry;
|
|
2261
|
+
/** 安装宿主退出兜底:process.on('exit') → cleanupAll(清 worktree)。幂等。 */
|
|
2262
|
+
declare function installAgentJobReaper(registry?: AgentJobRegistry, repoRoot?: string): void;
|
|
2263
|
+
//#endregion
|
|
2264
|
+
//#region src/agent-observability.d.ts
|
|
2265
|
+
/**
|
|
2266
|
+
* 观测态:比引擎 AgentStatus 多两个派生态——`awaiting_gate`(等审批)、
|
|
2267
|
+
* `tool_waiting`(RFC-144 M1:有 lifecycleAsync 工具在后台执行,loop 未阻塞但也非常规
|
|
2268
|
+
* streaming/tool_executing,供宿主面板区分"模型在想"与"后台任务在跑,模型可继续其他工作")。
|
|
2269
|
+
*
|
|
2270
|
+
* `tool_waiting` 是接口占位(M1 设计,M3 才由 workLoop 控制流真正驱动状态转换)——
|
|
2271
|
+
* 当前无生产者调用 `setToolWaiting`/`clearToolWaiting`(下方新增方法),这是刻意的:
|
|
2272
|
+
* M1 的范围是"pending 追踪"而非"控制流改造"(后者需要 runTurnsUntilTerminate 在
|
|
2273
|
+
* post-runTools 检查 pending 非空后 yield,这是 M3 的工作)。
|
|
2274
|
+
*/
|
|
2275
|
+
type AgentObsStatus = 'streaming' | 'tool_executing' | 'awaiting_gate' | 'tool_waiting' | 'done' | 'error' | 'aborted';
|
|
2276
|
+
/** 阻塞类别——映射「四类无界等待」中子代理可见的三类(外部进程超时化为 D 类 backlog)。 */
|
|
2277
|
+
type BlockClass = 'model_io' | 'approval' | 'process';
|
|
2278
|
+
/** 一个被观测 Agent 的累积运行态(不含派生量)。 */
|
|
2279
|
+
interface AgentObservation {
|
|
2280
|
+
/** 唯一观测键(swarm=`${runId}:${memberId}`,detached=runId)。 */
|
|
2281
|
+
id: string;
|
|
2282
|
+
agentName: string;
|
|
2283
|
+
title?: string;
|
|
2284
|
+
/** 完整的委托提示词(供 SubagentsPane / 详情面板展示)。 */
|
|
2285
|
+
prompt?: string;
|
|
2286
|
+
origin: 'user' | 'main';
|
|
2287
|
+
depth: number;
|
|
2288
|
+
status: AgentObsStatus;
|
|
2289
|
+
turnIndex: number;
|
|
2290
|
+
toolUseCount: number;
|
|
2291
|
+
/** 累积文件编辑次数(edit/write/edit_file/write_file 工具调用)。 */
|
|
2292
|
+
filesEdited: number;
|
|
2293
|
+
/** 累积输出 token(逐 turn.end 累加)。 */
|
|
2294
|
+
tokenCount: number;
|
|
2295
|
+
inputTokens: number;
|
|
2296
|
+
/** 分配模型 id(供 SubagentsPane 面板显示;缺省空串)。 */
|
|
2297
|
+
model?: string;
|
|
2298
|
+
/** 模型决策层(explicit/slot/category/default/fallback)——面板显示「为何用这个模型」。 */
|
|
2299
|
+
modelDecidedBy?: string;
|
|
2300
|
+
/** 人类可读的当前活动:thinking / responding / using <tool> / reconnecting / done。 */
|
|
2301
|
+
lastActivity: string;
|
|
2302
|
+
/** 最近一次事件时刻(含高频 stream delta)——stall 判定基准。 */
|
|
2303
|
+
lastEventAt: number;
|
|
2304
|
+
/** 当前等待类别(仅在「停住」时有诊断意义;配合 stall 级别看)。 */
|
|
2305
|
+
blockedOn?: BlockClass;
|
|
2306
|
+
blockDetail?: string;
|
|
2307
|
+
/** 父 abort 能否中断它(fork_call 修复前曾为 false;现恒 true,保留字段以暴露未来回归)。 */
|
|
2308
|
+
abortable: boolean;
|
|
2309
|
+
startedAt: number;
|
|
2310
|
+
endedAt?: number;
|
|
2311
|
+
}
|
|
2312
|
+
/** 快照 = 观测态 + 派生量(idleMs / stall 级别)。 */
|
|
2313
|
+
interface AgentObservationSnapshot extends AgentObservation {
|
|
2314
|
+
/** 距上次事件毫秒(已结束则 0)。 */
|
|
2315
|
+
idleMs: number;
|
|
2316
|
+
/** stall 级别:none / warn(>warnMs)/ stalled(>stalledMs);已结束恒 none。 */
|
|
2317
|
+
stall: 'none' | 'warn' | 'stalled';
|
|
2318
|
+
}
|
|
2319
|
+
interface AgentObservationMeta {
|
|
2320
|
+
id: string;
|
|
2321
|
+
agentName: string;
|
|
2322
|
+
title?: string;
|
|
2323
|
+
/** 完整的委托提示词(供 SubagentsPane / 详情面板展示)。 */
|
|
2324
|
+
prompt?: string;
|
|
2325
|
+
/** 分配模型 id(供 SubagentsPane 面板显示)。 */
|
|
2326
|
+
model?: string;
|
|
2327
|
+
/** 模型决策层(供面板显示选型溯源)。 */
|
|
2328
|
+
modelDecidedBy?: string;
|
|
2329
|
+
origin?: 'user' | 'main';
|
|
2330
|
+
depth?: number;
|
|
2331
|
+
/** 中止该 agent 的回调(swarm=agent.abort;detached=清 pending approval + agent.abort)。
|
|
2332
|
+
* 提供即 `abortable=true`;面板 `x`/`k` 经 `registry.abort(id)` 调用它中止卡死的子代理。 */
|
|
2333
|
+
abort?: () => void;
|
|
2334
|
+
/** 显式覆盖 abortable(缺省由 abort 是否提供推断)。 */
|
|
2335
|
+
abortable?: boolean;
|
|
2336
|
+
/** 视图A:子代理的 live 转录数组引用(=AgentRunContext.messages,agent 原地 push)。
|
|
2337
|
+
* registry 只持引用、读取时取快照;供「对话」视图渲染。不进 list() 轻量快照(按需 getMessages)。
|
|
2338
|
+
* 可传**取值函数**——AgentSession 子代理(task_delegate/fork 等)无稳定 live 数组,
|
|
2339
|
+
* 传 `() => session.session.messages()` 让 getMessages 每次取最新(task-runner finally 再 setMessages 冻结终态)。 */
|
|
2340
|
+
messages?: readonly AgentMessage[] | (() => readonly AgentMessage[]);
|
|
2341
|
+
}
|
|
2342
|
+
interface AgentObservabilityOptions {
|
|
2343
|
+
now?: () => number;
|
|
2344
|
+
/** stall 黄阈(默认 3s)。 */
|
|
2345
|
+
warnMs?: number;
|
|
2346
|
+
/** stall 红阈(默认 30s)。 */
|
|
2347
|
+
stalledMs?: number;
|
|
2348
|
+
/** 结束后保留多久再驱逐(默认 15s)。 */
|
|
2349
|
+
evictMs?: number;
|
|
2350
|
+
}
|
|
2351
|
+
type ObservabilityEvents = Events & {
|
|
2352
|
+
change: () => void;
|
|
2353
|
+
};
|
|
2354
|
+
declare class AgentObservabilityRegistry extends TypedEventEmitter<ObservabilityEvents> {
|
|
2355
|
+
private readonly observations;
|
|
2356
|
+
private readonly abortFns;
|
|
2357
|
+
private readonly messagesRefs;
|
|
2358
|
+
private readonly streamingTexts;
|
|
2359
|
+
private readonly turns;
|
|
2360
|
+
private readonly evictTimers;
|
|
2361
|
+
/** 被 pin 的 id 不受驱逐 timer 影响(用于 s 键查看 sub-agent 时阻止消息被清除)。 */
|
|
2362
|
+
private readonly pinned;
|
|
2363
|
+
private readonly now;
|
|
2364
|
+
private readonly warnMs;
|
|
2365
|
+
private readonly stalledMs;
|
|
2366
|
+
private readonly evictMs;
|
|
2367
|
+
constructor(opts?: AgentObservabilityOptions);
|
|
2368
|
+
/** 登记一个待观测 Agent(attachAgentObserver 首先调用)。重复 id 覆盖(同 id 复用即续命)。 */
|
|
2369
|
+
register(meta: AgentObservationMeta): void;
|
|
2370
|
+
/**
|
|
2371
|
+
* 中止一个被观测 agent(面板 `x`/`k` → 这里)。调用其 abort 回调(swarm=agent.abort / detached=
|
|
2372
|
+
* 清 pending approval + agent.abort);已结束或无回调返回 false。中止后状态由 abort/status.change 事件收敛。
|
|
2373
|
+
*/
|
|
2374
|
+
abort(id: string): boolean;
|
|
2375
|
+
/** 摄入一个引擎事件并归约到观测态。高频 stream delta 只更 lastEventAt、不 notify(防风暴)。 */
|
|
2376
|
+
record(id: string, event: AgentEvent): void;
|
|
2377
|
+
/** detached /job 的审批挂起——经此显式标记(裸 Agent 不在事件总线上发 approval)。 */
|
|
2378
|
+
setAwaitingApproval(id: string, detail: string): void;
|
|
2379
|
+
clearAwaitingApproval(id: string): void;
|
|
2380
|
+
/**
|
|
2381
|
+
* RFC-144 M1 接口占位(M3 接线):lifecycleAsync 工具在后台执行、loop 已 yield 等待。
|
|
2382
|
+
* 与 `setAwaitingApproval` 同构(外部显式标记 + 显式清除),供 M3 的 workLoop 控制流
|
|
2383
|
+
* 在 post-runTools 检测到 `pendingLifecycleTools` 非空时调用。
|
|
2384
|
+
*/
|
|
2385
|
+
setToolWaiting(id: string, detail: string): void;
|
|
2386
|
+
clearToolWaiting(id: string): void;
|
|
2387
|
+
/** 兜底收尾(wiring 站点 finally 调用):若仍活跃则置 done。status.change('completed') 通常已覆盖。 */
|
|
2388
|
+
markEnded(id: string, status?: AgentObsStatus): void;
|
|
2389
|
+
/** 阻止 id 关联的数据被驱逐。取消已排队的 evict timer 并标记为 pinned。
|
|
2390
|
+
* - 已终态 agent:timer 被取消,数据保留到 unpin 后重启 timer。
|
|
2391
|
+
* - 运行中 agent:无 timer 可取消,仅标记 pinned,未来 markTerminal 的 scheduleEvict 会跳过。
|
|
2392
|
+
* 重复 pin 幂等。 */
|
|
2393
|
+
pin(id: string): void;
|
|
2394
|
+
/** 取消 id 的 pin 保护。若 agent 已终态(endedAt 存在),重启驱逐 timer(默认 evictMs)。
|
|
2395
|
+
* 若仍在运行中(未结束),不操作——终态时 markTerminal 会自行 scheduleEvict。
|
|
2396
|
+
* 重复 unpin 幂等。 */
|
|
2397
|
+
unpin(id: string): void;
|
|
2398
|
+
/**
|
|
2399
|
+
* 从表中立即移除(面板手动删除一项,不等自动驱逐 timer)。终态/运行中皆可调——运行中直接
|
|
2400
|
+
* 静默移除观测记录(不中止真实 agent,中止走 abort();本方法只影响 UI 可见性)。
|
|
2401
|
+
* 清 evictTimer/abortFn/messagesRef/streamingText/turns/pinned,幂等(不存在的 id 安全跳过)。
|
|
2402
|
+
*/
|
|
2403
|
+
remove(id: string): void;
|
|
2404
|
+
get(id: string): AgentObservationSnapshot | undefined;
|
|
2405
|
+
/** P1 视图A:取某 agent 转录的**快照副本**(防消费方读到后被 agent 继续 mutate 的活引用)。
|
|
2406
|
+
* ref 可为数组或取值函数(AgentSession 子代理传 getter,每次取最新)。 */
|
|
2407
|
+
getMessages(id: string): AgentMessage[] | undefined;
|
|
2408
|
+
/** 冻结某 agent 转录终态快照。task-runner 在 ephemeral removeSession 前调,
|
|
2409
|
+
* 使切入对话/Transcript 在子会话被 dispose 后仍可读最终内容(驱逐前 evictMs 窗口内)。 */
|
|
2410
|
+
setMessages(id: string, msgs: readonly AgentMessage[]): void;
|
|
2411
|
+
/** P1 视图A:当前 turn 尚未落入 messages 的 in-flight 流式文本(无则空串)。 */
|
|
2412
|
+
getStreamingText(id: string): string;
|
|
2413
|
+
/** P2 视图B:单轮记录(turn timeline)副本;无则空数组。 */
|
|
2414
|
+
getTurns(id: string): TurnRecord$1[];
|
|
2415
|
+
/** 全部观测快照(含派生 idleMs/stall)。 */
|
|
2416
|
+
list(): AgentObservationSnapshot[];
|
|
2417
|
+
/** 仅活跃(未结束)的观测。 */
|
|
2418
|
+
listActive(): AgentObservationSnapshot[];
|
|
2419
|
+
subscribe(listener: () => void): () => void;
|
|
2420
|
+
/** 测试用:清空。 */
|
|
2421
|
+
clear(): void;
|
|
2422
|
+
private applyTerminal;
|
|
2423
|
+
private markTerminal;
|
|
2424
|
+
private scheduleEvict;
|
|
2425
|
+
private toSnapshot;
|
|
2426
|
+
}
|
|
2427
|
+
/** 全局单例(照 globalProcessRegistry / globalAgentJobRegistry)。 */
|
|
2428
|
+
declare const globalAgentObservability: AgentObservabilityRegistry;
|
|
2429
|
+
/**
|
|
2430
|
+
* ① AgentObserver:把一个裸 Agent 的事件转发进观测注册表(路 B——不升格 AgentSession、不动执行语义、
|
|
2431
|
+
* 保留转录隔离;observer 只读)。在 swarm-runtime / detached-run 的 `new Agent` 后调用;wiring 站点
|
|
2432
|
+
* 应在 `await agent.run()` 的 finally 里调返回的 detach + `registry.markEnded(id)` 兜底收尾。
|
|
2433
|
+
*
|
|
2434
|
+
* @returns detach 函数:解订阅所有事件监听(不删观测,结束态由 status.change/abort/error + markEnded 处理)。
|
|
2435
|
+
*/
|
|
2436
|
+
declare function attachAgentObserver(agent: Agent, meta: AgentObservationMeta, registry?: AgentObservabilityRegistry): () => void;
|
|
2437
|
+
//#endregion
|
|
2438
|
+
//#region src/context-sources/environment.d.ts
|
|
2439
|
+
declare function createEnvironmentContextSource(workspaceDir: string): ContextSource;
|
|
2440
|
+
//#endregion
|
|
2441
|
+
//#region src/context-sources/memory-index.d.ts
|
|
2442
|
+
declare function createMemoryIndexContextSource(_workspaceDir: string, memory?: ContextBuildInput['memory']): ContextSource;
|
|
2443
|
+
//#endregion
|
|
2444
|
+
//#region src/context-sources/todo-reminder.d.ts
|
|
2445
|
+
/**
|
|
2446
|
+
* todo-reminder(RFC-042)
|
|
2447
|
+
*
|
|
2448
|
+
* 病灶:`✓ done` 是回合生命周期标记,与 write_todos 的 todo 完成度零耦合——模型可在 todo 仍有
|
|
2449
|
+
* pending/in_progress 时 end_turn,UI 却照显 done(见 RFC-042 根因 1/3)。
|
|
2450
|
+
*
|
|
2451
|
+
* 本钩子实现 todo 护栏:每次 buildContext 时,若当前会话 todoList 仍有未完成项,就把最新
|
|
2452
|
+
* todo 状态作为 **volatile 尾段**(output.systemTail,落在 prompt cache 断点之后)注入一个
|
|
2453
|
+
* `<system-reminder>`,让模型在下一次推理时"看见"未竟工作从而继续/纠偏。
|
|
2454
|
+
*
|
|
2455
|
+
* 关键约束(RFC-042 重要事项规则 1/3):
|
|
2456
|
+
* - 只进 volatile systemTail,绝不进 stable 前缀——否则逐轮击穿 prompt cache([[RFC-018]])。
|
|
2457
|
+
* - 数据源 = session.metadata().config?.todoList(持久化真源,经稳定 Session 接口读取),
|
|
2458
|
+
* 不耦合 CLI projState;故无 per-session 基线、无状态、无 cleanup(与 memory-delta 不同)。
|
|
2459
|
+
* - buildContext 每用户 prompt 只跑一次,故 reminder 在"即将停"的当轮无效——它靠续轮(M74-04)
|
|
2460
|
+
* 触发的下一次 buildContext 生效,二者是耦合闭环(RFC-042 D1)。
|
|
2461
|
+
*/
|
|
2462
|
+
interface TodoReminderPort {
|
|
2463
|
+
/**
|
|
2464
|
+
* 取指定会话的 todo 快照(read-only)。返回 undefined / 空数组表示无 todo。
|
|
2465
|
+
* 形态 = session.metadata().config?.todoList。
|
|
2466
|
+
*/
|
|
2467
|
+
getTodoList(sessionId: string): ReadonlyArray<{
|
|
2468
|
+
id?: string;
|
|
2469
|
+
title?: string;
|
|
2470
|
+
status?: string;
|
|
2471
|
+
}> | undefined;
|
|
2472
|
+
}
|
|
2473
|
+
declare function createTodoReminderHooks(port: TodoReminderPort, priority?: 62): HookSpec[];
|
|
2474
|
+
//#endregion
|
|
2475
|
+
//#region src/context-sources/doc-sync-reminder.d.ts
|
|
2476
|
+
/**
|
|
2477
|
+
* doc-sync-reminder(RFC-100)
|
|
2478
|
+
*
|
|
2479
|
+
* 病灶:write_todos(会话级 UI 状态)与外部持久化文档(如 RFC/TODO/milestone markdown)是两套
|
|
2480
|
+
* 完全独立、不会自动互相同步的记账系统。模型在多阶段任务中容易把 write_todos 当"第一阶段的一次性
|
|
2481
|
+
* 草稿本"用完即弃,转而只维护文档,导致 UI 常驻 todo 清单停留在早期快照,与仓库真实进度脱节
|
|
2482
|
+
* (lesson_50 记录的真实复现案例)。
|
|
2483
|
+
*
|
|
2484
|
+
* 本钩子抄 todo-reminder.ts 同一模式(system-reminder 注入护栏),但检测的是"外部文档同步"而非
|
|
2485
|
+
* "todo 内部完成度"——两者互补,不重叠:
|
|
2486
|
+
* - todo-reminder 检测「todoList 内部有 pending/in_progress 却想结束回合」
|
|
2487
|
+
* - doc-sync-reminder 检测「本回合写过被判定为需同步的文档,但没有调用 write_todos」
|
|
2488
|
+
*
|
|
2489
|
+
* 判定"什么算需要同步的文档变更"完全下放给调用方注入的 DocSyncPort(不硬编码任何本仓库专属的
|
|
2490
|
+
* 文件名/内容模式到 runtime 通用层——对不使用这套约定的项目零污染,不注册这个 hook 即可)。
|
|
2491
|
+
*
|
|
2492
|
+
* 状态机:
|
|
2493
|
+
* tool.execute.after → 累积"本轮是否触碰过需同步文档" + "本轮是否调用过 write_todos"
|
|
2494
|
+
* chat.message.before → 新用户消息到达 = 新回合开始,重置累积状态
|
|
2495
|
+
* system.prompt.transform → 若"触碰过需同步文档"且"未调用 write_todos",注入提醒
|
|
2496
|
+
* session.deleted → 驱逐会话状态(同 memory-delta.ts 模式)
|
|
2497
|
+
*/
|
|
2498
|
+
interface DocSyncPort {
|
|
2499
|
+
/**
|
|
2500
|
+
* 判定一次工具调用是否构成"需要同步 write_todos 的文档变更"。
|
|
2501
|
+
* 典型实现:检查 toolName 是否为 edit/write、args.path 是否匹配约定的文档命名模式(如
|
|
2502
|
+
* `*-milestone.md`)、写入的新内容是否包含状态翻转标记(如 `Status: Done`)。
|
|
2503
|
+
* 返回 true 即计入"本轮触碰过需同步文档"。
|
|
2504
|
+
*/
|
|
2505
|
+
isDocSyncTrigger(input: ToolExecuteAfterInput): boolean;
|
|
2506
|
+
}
|
|
2507
|
+
declare function formatDocSyncReminder(toolName: string): string;
|
|
2508
|
+
declare function createDocSyncReminderHooks(port: DocSyncPort, priority?: 64): HookSpec[];
|
|
2509
|
+
//#endregion
|
|
2510
|
+
//#region src/context-sources/section-order.d.ts
|
|
2511
|
+
/**
|
|
2512
|
+
* RFC-020 Phase 3(M54)P3-04:system-prompt 注入 section 的**唯一有序声明**。
|
|
2513
|
+
*
|
|
2514
|
+
* 治病灶——此前各 section 的 priority 数字(20/25/35/38/40/60)散在 coding hooks 与 runtime
|
|
2515
|
+
* context-sources 两包里,"system prompt 各段什么顺序"得捞齐多处数字才能拼出。现收敛为此处一张表:
|
|
2516
|
+
* - 数组/字段顺序即 priority 升序 = 实际 `system.prompt.transform` hook 的执行/拼接顺序;
|
|
2517
|
+
* - `lane` 标明该段落 **stable 前缀**(systemPrompt,进 prompt cache)还是 **volatile 尾段**
|
|
2518
|
+
* (systemTail,落在 cache 断点之后,逐轮可变不击穿缓存);
|
|
2519
|
+
* - runtime context-sources(environment/memory-index/memory-delta)与 coding hooks
|
|
2520
|
+
* (tool-guidance/lesson/skill-catalog)均从此处取 priority —— 单一真相源,改序只此一处。
|
|
2521
|
+
*
|
|
2522
|
+
* 放在 @x-otto/runtime(coding 依赖 runtime、反向不依赖):coding 侧 hook 经 `@x-otto/runtime` import。
|
|
2523
|
+
* 注:本表是**排序权威**(方案 H:section 仍由各自 hook 自注册,priority 数字降级为对本表的引用);
|
|
2524
|
+
* 不强行把 `SYSTEM_PROMPT_SECTIONS` 各 section 物理并入单一 registry(那超出外科尺度、非目标)——
|
|
2525
|
+
* section 数量以该常量的字段数为准,不在本段注释里重复写死具体数字(history: 建表时 7 个,
|
|
2526
|
+
* 后追加 docSyncReminder 成 8 个,写死数字会重犯本次发现的漂移)。
|
|
2527
|
+
*/
|
|
2528
|
+
type SystemPromptLane = 'stable' | 'volatile';
|
|
2529
|
+
interface SystemPromptSectionSpec {
|
|
2530
|
+
/** 对应 hook / ContextSource 的 name/id(与注册名一致,供 drift guard 比对)。 */
|
|
2531
|
+
readonly name: string;
|
|
2532
|
+
readonly priority: number;
|
|
2533
|
+
readonly lane: SystemPromptLane;
|
|
2534
|
+
}
|
|
2535
|
+
declare const SYSTEM_PROMPT_SECTIONS: {
|
|
2536
|
+
readonly toolGuidance: {
|
|
2537
|
+
readonly name: "tool-guidance-injection";
|
|
2538
|
+
readonly priority: 20;
|
|
2539
|
+
readonly lane: "stable";
|
|
2540
|
+
};
|
|
2541
|
+
/**
|
|
2542
|
+
* RFC-318 D7:三分流判别(缺工具 / 流程重复 / 其余)。紧跟 toolGuidance——它是对
|
|
2543
|
+
* "现有工具够不够用"的元判断,语义上属工具指引的延伸;内容恒定故走 stable lane。
|
|
2544
|
+
*/
|
|
2545
|
+
readonly skillLoopGuidance: {
|
|
2546
|
+
readonly name: "skill-loop-guidance-injection";
|
|
2547
|
+
readonly priority: 21;
|
|
2548
|
+
readonly lane: "stable";
|
|
2549
|
+
};
|
|
2550
|
+
readonly environment: {
|
|
2551
|
+
readonly name: "runtime:environment";
|
|
2552
|
+
readonly priority: 25;
|
|
2553
|
+
readonly lane: "stable";
|
|
2554
|
+
};
|
|
2555
|
+
readonly memoryIndex: {
|
|
2556
|
+
readonly name: "runtime:memory-index";
|
|
2557
|
+
readonly priority: 35;
|
|
2558
|
+
readonly lane: "stable";
|
|
2559
|
+
};
|
|
2560
|
+
readonly skillCatalog: {
|
|
2561
|
+
readonly name: "skill-catalog-injection";
|
|
2562
|
+
readonly priority: 38;
|
|
2563
|
+
readonly lane: "stable";
|
|
2564
|
+
};
|
|
2565
|
+
readonly lesson: {
|
|
2566
|
+
readonly name: "lesson-injection";
|
|
2567
|
+
readonly priority: 40;
|
|
2568
|
+
readonly lane: "stable";
|
|
2569
|
+
};
|
|
2570
|
+
readonly memoryDelta: {
|
|
2571
|
+
readonly name: "memory-delta-injection";
|
|
2572
|
+
readonly priority: 60;
|
|
2573
|
+
readonly lane: "volatile";
|
|
2574
|
+
};
|
|
2575
|
+
readonly todoReminder: {
|
|
2576
|
+
readonly name: "todo-reminder-injection";
|
|
2577
|
+
readonly priority: 62;
|
|
2578
|
+
readonly lane: "volatile";
|
|
2579
|
+
};
|
|
2580
|
+
readonly docSyncReminder: {
|
|
2581
|
+
readonly name: "doc-sync-reminder-injection";
|
|
2582
|
+
readonly priority: 64;
|
|
2583
|
+
readonly lane: "volatile";
|
|
2584
|
+
};
|
|
2585
|
+
};
|
|
2586
|
+
//#endregion
|
|
2587
|
+
//#region src/session-stable.d.ts
|
|
2588
|
+
interface SessionStableInjectionOptions {
|
|
2589
|
+
name: string;
|
|
2590
|
+
priority: number;
|
|
2591
|
+
/** 首轮计算注入块;返回空/undefined 表示本会话无注入(也会被冻结,不再重算)。 */
|
|
2592
|
+
compute: (input: SystemPromptTransformInput) => Promise<string | undefined> | string | undefined;
|
|
2593
|
+
}
|
|
2594
|
+
declare function createSessionStableInjectionHooks(options: SessionStableInjectionOptions): HookSpec[];
|
|
2595
|
+
//#endregion
|
|
2596
|
+
//#region src/session/derive-title.d.ts
|
|
2597
|
+
/**
|
|
2598
|
+
* 从会话消息列表中派生标题(RFC-030 P1)。
|
|
2599
|
+
*
|
|
2600
|
+
* 回落链:meta.title → deriveSessionTitle(messages()) → id.slice(0, 8)
|
|
2601
|
+
*
|
|
2602
|
+
* 策略:
|
|
2603
|
+
* - 找第一条 role==='user' 的真实消息(跳过 system/assistant/summary 标记消息)。
|
|
2604
|
+
* - 跳过 compact_boundary 子类型的系统消息(summary 边界标记,不是用户输入)。
|
|
2605
|
+
* - 文本归一:换行/多空格 → 单空格,trim,截 MAX_TITLE_LENGTH 字符。
|
|
2606
|
+
* - 空则返回 undefined,由调用方继续回落到 id.slice(0, 8)。
|
|
2607
|
+
*/
|
|
2608
|
+
declare function deriveSessionTitle(messages: readonly AgentMessage[]): string | undefined;
|
|
2609
|
+
/**
|
|
2610
|
+
* 统一标题回落链(RFC-078 D1/R5 单一真源)——三表面(service / 终端 / resume)共用。
|
|
2611
|
+
*
|
|
2612
|
+
* 回落:persistedTitle(meta.title,已是最高优先来源) → deriveSessionTitle(首条 user 消息) → id 前 8 位。
|
|
2613
|
+
*
|
|
2614
|
+
* 说明:`titleSource` 只在**写入**时决定覆盖(见 InMemorySession.setTitle 等级守卫),
|
|
2615
|
+
* 故读取时 `meta.title` 必为当前最高优先值,本函数无需再分辨来源。
|
|
2616
|
+
*/
|
|
2617
|
+
declare function resolveSessionTitle(persistedTitle: string | undefined, messages: readonly AgentMessage[], id: string): string;
|
|
2618
|
+
//#endregion
|
|
2619
|
+
//#region src/session/hooks.d.ts
|
|
2620
|
+
interface ToolHookOptions {
|
|
2621
|
+
hookRegistry: HookRegistry;
|
|
2622
|
+
agentName: string;
|
|
2623
|
+
sessionId: string;
|
|
2624
|
+
}
|
|
2625
|
+
declare function createToolHookExecutor(options: ToolHookOptions): ToolHookExecutor;
|
|
2626
|
+
//#endregion
|
|
2627
|
+
//#region src/session/persistence-sync.d.ts
|
|
2628
|
+
interface PersistenceSyncDeps {
|
|
2629
|
+
persistence: SessionPersistence<AgentMessage>;
|
|
2630
|
+
logger: Logger;
|
|
2631
|
+
/** disk/remote 后端 = true(prompt 终态增量落盘);in-memory 缺省 false。 */
|
|
2632
|
+
autoPersist: boolean;
|
|
2633
|
+
getSession: (id: string) => AgentSession | undefined;
|
|
2634
|
+
hasSession: (id: string) => boolean;
|
|
2635
|
+
listSessionIds: () => Iterable<string>;
|
|
2636
|
+
/** 容量预检(含 idle 驱逐 + 满池 LRU 驱逐)。restore 单条不足时抛错。 */
|
|
2637
|
+
canAccommodate: (id: string) => boolean;
|
|
2638
|
+
/** 剩余容量(纯查询,无驱逐副作用)。restoreAll 用它截断入池数量,不挤掉在池会话。 */
|
|
2639
|
+
capacityLeft: () => number;
|
|
2640
|
+
/** restore 路径池登记:构建 AgentSession + 登记进池(不改 active;track 由本类负责)。 */
|
|
2641
|
+
adoptRestoredSession: (raw: InMemorySession<AgentMessage>, config: ResolvedSessionConfig) => Promise<AgentSession>;
|
|
2642
|
+
/** restoreAll 的 active 选举(恢复后无 active 时设为最近活跃者)。 */
|
|
2643
|
+
setActiveIfNone: (id: string) => void;
|
|
2644
|
+
/** 仅 restore/restoreAll 使用;未提供则两方法分别返回 null / 0。 */
|
|
2645
|
+
/** restore 模板工厂。传持久化 agentName 以重解析子代理 prompt 身份(缺省=main)。 */
|
|
2646
|
+
configProvider?: (agentName?: string, modelId?: string) => ResolvedSessionConfig;
|
|
2647
|
+
/** save 时取会话当前解析后配置,抽取 per-session 子集入快照 metadata。 */
|
|
2648
|
+
getConfig?: (id: string) => ResolvedSessionConfig | undefined;
|
|
2649
|
+
/**
|
|
2650
|
+
* RFC-178 触发器 C:驻留收紧系数源(可选——未提供时恒用全值 cap,行为与此前一致)。
|
|
2651
|
+
* 由组装层(app-lifecycle)把 MemoryGovernor 分级信号写入其 setLevel。
|
|
2652
|
+
*/
|
|
2653
|
+
residencyGovernor?: ResidencyGovernor;
|
|
2654
|
+
/**
|
|
2655
|
+
* RFC-323 M4:驻留预算运行时配置(settings.json 覆盖经此注入,缺省回退 env/常量)。
|
|
2656
|
+
* 惰性 getter——app 层 ensureSettingsLoaded 后才有值,故每次读而非构造期快照。
|
|
2657
|
+
*/
|
|
2658
|
+
getResidencyConfig?: () => _$_x_otto_session_contract0.ResidencyBudgetConfig | undefined;
|
|
2659
|
+
/**
|
|
2660
|
+
* RFC-108 D3:面板态独立持久化门店(可选——未提供时降级为不独立持久化,
|
|
2661
|
+
* 面板态仅存在于内存,重启后丢失但不报错崩溃)。
|
|
2662
|
+
*/
|
|
2663
|
+
panelStateStore?: PanelStateStore;
|
|
2664
|
+
/**
|
|
2665
|
+
* RFC-305 D3:trace↔DB 崩溃对账数据源(可选——未提供时对账整体跳过,restore 行为
|
|
2666
|
+
* 与此前一致)。trace append-log 逐事件实时落盘(独立于 session DB 的 save 链),
|
|
2667
|
+
* 崩溃时 trace 记录的 turn 覆盖领先于 DB——restore 时比对可检出"已发生未落库"缺口。
|
|
2668
|
+
*/
|
|
2669
|
+
traceStore?: AppendLog<TraceEvent>;
|
|
2670
|
+
/**
|
|
2671
|
+
* RFC-305 M3:持久化后端 turn-flush 能力(缺省 'incremental',由装配层透传
|
|
2672
|
+
* persistence.turnFlush)。'snapshot' 后端(远端 PUT 整份快照/旧 blob 逃生口整份覆写)
|
|
2673
|
+
* 对 turn 级落库节流,'incremental'(本地 relational 游标增量)每 turn 落库。
|
|
2674
|
+
* 这是后端能力事实,不是用户配置——未来远端支持 append-tail 后改声明即可自动升级。
|
|
2675
|
+
*/
|
|
2676
|
+
turnFlush?: 'incremental' | 'snapshot';
|
|
2677
|
+
}
|
|
2678
|
+
declare class PersistenceSync {
|
|
2679
|
+
private readonly deps;
|
|
2680
|
+
private readonly saveChains;
|
|
2681
|
+
/**
|
|
2682
|
+
* RFC-328 D4:崩溃缺口对账已提取为独立可替换单元(`CrashGapDetector`)。
|
|
2683
|
+
* 本类保留**唯一发布 owner** 身份:detector 只缓存事实,事件/提示由 `consumeCrashGap()`
|
|
2684
|
+
* 沿既有恢复通知链发出——恢复通知层不得绕过本类直接读 detector。
|
|
2685
|
+
*/
|
|
2686
|
+
private readonly crashGapDetector;
|
|
2687
|
+
/**
|
|
2688
|
+
* RFC-328 D4:面板态恢复与遗留 metadata 迁移已提取为独立单元。
|
|
2689
|
+
* 它只写 PanelStateStore 与内存 panel setter,不碰主快照/写租约。
|
|
2690
|
+
*/
|
|
2691
|
+
private readonly panelStateRestorer;
|
|
2692
|
+
/** RFC-305 M3:snapshot 后端 per-session turn 节流状态(仅 turnFlush='snapshot' 使用)。 */
|
|
2693
|
+
private readonly turnSaveState;
|
|
2694
|
+
/**
|
|
2695
|
+
* 2026-07-14:per-session "此前是否处于写租约被拒态"标记——用于探测"denied→恢复可写"
|
|
2696
|
+
* 的状态转换,据此 publish 对称的 session.write-lease-restored 事件(仅在真实发生
|
|
2697
|
+
* 状态转换时发一次,不随每次成功 save 重复发)。会话销毁/驱逐时清理
|
|
2698
|
+
* (forgetSession/flushPending),避免无限期残留。
|
|
2699
|
+
*/
|
|
2700
|
+
private readonly leaseDeniedState;
|
|
2701
|
+
/** RFC-178 D5:per-session 上次在线 cap 评估时的消息条数基准(在线间隔判定用)。 */
|
|
2702
|
+
/**
|
|
2703
|
+
* M1 远端权威:per-session 已知的服务端 revision。每次 CAS 成功后更新;
|
|
2704
|
+
* 409 reload/rebase 后用最新 revision 重 PUT。本地后端不使用此字段。
|
|
2705
|
+
*/
|
|
2706
|
+
private readonly remoteRevisions;
|
|
2707
|
+
/**
|
|
2708
|
+
* RFC-328 D3:驻留动作的输入收集、executor 消费与动作执行已提取为
|
|
2709
|
+
* `ResidencyActionCoordinator`——它拥有在线评估基线、发布基线、压缩失败计数、
|
|
2710
|
+
* 逃生阀节流基线这四个**只与驻留治理相关**的 per-session 状态机。
|
|
2711
|
+
*
|
|
2712
|
+
* 本类保留 `session.history-capped` 的**唯一发布 owner** 身份:coordinator 只返回
|
|
2713
|
+
* 发布所需事实,事件由 `applyResidencyCaps` 沿既有出口发出。
|
|
2714
|
+
*/
|
|
2715
|
+
private readonly residencyCoordinator;
|
|
2716
|
+
constructor(deps: PersistenceSyncDeps);
|
|
2717
|
+
/**
|
|
2718
|
+
* RFC-178 三触发一执行器的**执行汇聚点**:条数 cap(触发器 A,含触发器 C 的压力收紧
|
|
2719
|
+
* 系数)+ 字节 cap(触发器 B 硬段,同受 C 收紧)。所有裁剪只影响内存视图(DB
|
|
2720
|
+
* append-forever),capped 时 publish `session.history-capped` 供前端提示。
|
|
2721
|
+
* doSave 与在线评估(D5)共用本方法,保证两条触发路径行为一致。
|
|
2722
|
+
*/
|
|
2723
|
+
private applyResidencyCaps;
|
|
2724
|
+
/**
|
|
2725
|
+
* RFC-321 D1-a′ 逃生阀执行体:请求一次强制压缩(fire-and-forget,不阻塞落盘)。
|
|
2726
|
+
*
|
|
2727
|
+
* **必须节流**:`activeOversized` 在活跃区持续超限期间会**每次 save/在线评估都为真**,
|
|
2728
|
+
* 无节流将导致每次落盘都发起一次 LLM 摘要调用(成本与限流灾难)。节流语义与
|
|
2729
|
+
* `capPublishBaseline` 同构——自上次请求以来消息数须再涨一个滞回增量才允许再请求;
|
|
2730
|
+
* 压缩成功会使活跃区骤降,信号自然消失,故正常情况下每个"超限期"只请求一次。
|
|
2731
|
+
*
|
|
2732
|
+
* 失败不上抛:压缩失败(LLM 异常/无可压缩内容)只记日志,下一个增量窗口会再试;
|
|
2733
|
+
* 裁剪结果与落盘均不受影响。
|
|
2734
|
+
*/
|
|
2735
|
+
private requestEscapeValveCompaction;
|
|
2736
|
+
/**
|
|
2737
|
+
* RFC-178 D5(收编 RFC-147 E-2):在线 cap 评估——消息条数自上次评估以来增长超过
|
|
2738
|
+
* `CAP_INLINE_CHECK_INTERVAL` 时立即执行驻留裁剪,不等 doSave 节奏。挂在 turn.end
|
|
2739
|
+
* 订阅(每 turn 一次 O(n) 计数,非每条消息),把"两次 save 之间无界增长"收敛到有界。
|
|
2740
|
+
*/
|
|
2741
|
+
private maybeInlineCapEval;
|
|
2742
|
+
/**
|
|
2743
|
+
* autoPersist 时订阅 prompt 终态(prompt.end / error)与 session.cleared(clear 后增量落盘清空态)→ 增量落盘。
|
|
2744
|
+
* fire-and-forget + 告警日志:落盘失败不阻塞会话主流程,stop() 的 saveAll 仍兜底。
|
|
2745
|
+
* transient(orchestrator ephemeral 子会话)不订阅——避免写盘后又 removeSession
|
|
2746
|
+
* 删、且被 restoreAll 当一等会话复活。
|
|
2747
|
+
*
|
|
2748
|
+
* RFC-305 D1(2026-07-31):turn.end 每次入链落 entries 增量(doSaveEntries,无节流、
|
|
2749
|
+
* 不含 panelState)——长循环 DB 崩溃窗口收窄到毫秒级,见常量头注释与 RFC-305 §3。
|
|
2750
|
+
*/
|
|
2751
|
+
track(agentSession: AgentSession, transient: boolean): void;
|
|
2752
|
+
flushPending(id: string): Promise<void>;
|
|
2753
|
+
/**
|
|
2754
|
+
* P2 缓解(独立 review 补充,2026-07-12):会话可能经 idle 驱逐(`collectIdle`)或满池 LRU
|
|
2755
|
+
* 驱逐(`evictLeastRecent`)消失,这两条路径不经过 `removeSession`/`flushPending`(前者是
|
|
2756
|
+
* 同步批量收集不适合 await 落盘完成,后者已自行 fire-and-forget 触发 `saveSnapshot`)——
|
|
2757
|
+
* 提供一个轻量同步纯清理方法,供这两条驱逐路径调用,与 `flushPending` 共享同一清理意图
|
|
2758
|
+
* 但不附带"等待落盘完成"的语义(驱逐场景不需要,也不应阻塞驱逐循环)。
|
|
2759
|
+
*/
|
|
2760
|
+
/**
|
|
2761
|
+
* RFC-323 D4:计算用于预算分配的活跃会话数(排除 transient)。
|
|
2762
|
+
*
|
|
2763
|
+
* transient 会话(orchestrator ephemeral 子会话)不订阅 persistence track、
|
|
2764
|
+
* 不被 applyResidencyCaps 管理,但存在于 sessions Map 中(listSessionIds 会返回)。
|
|
2765
|
+
* 若计入 N 会摊薄正常会话的预算。故过滤 configs.get(id)?.transient === true。
|
|
2766
|
+
*/
|
|
2767
|
+
private countActiveSessionsForAllocation;
|
|
2768
|
+
/**
|
|
2769
|
+
* RFC-325 M2:汇总所有活跃会话的总估算字节驻留——供 GlobalResidencyEngine 做压力计算。
|
|
2770
|
+
* 只读 estimatedContentBytes 增量计数器(O(会话数),不遍历消息)。
|
|
2771
|
+
*
|
|
2772
|
+
* 口径:**含被评估的目标会话本身**。pressure 是"全局已用/总预算"的比值,
|
|
2773
|
+
* 目标会话的驻留同样占用全局预算,排除它会低估压力。与 countActiveSessionsForAllocation
|
|
2774
|
+
* 一致地排除 transient(不订阅持久化、不参与预算分摊)。
|
|
2775
|
+
*/
|
|
2776
|
+
private sumEstimatedContentBytes;
|
|
2777
|
+
forgetSession(id: string): void;
|
|
2778
|
+
/**
|
|
2779
|
+
* RFC-352 D2:驱逐的最终 snapshot 仍在队列中时不能提前清 residency 状态。
|
|
2780
|
+
* 若同 id 已被 restore/recreate,则新 session 已取得该 id 的状态所有权,旧驱逐完成
|
|
2781
|
+
* 不得反向清掉它。
|
|
2782
|
+
*/
|
|
2783
|
+
forgetEvictedSession(id: string): void;
|
|
2784
|
+
save(id: string): Promise<void>;
|
|
2785
|
+
/**
|
|
2786
|
+
* RFC-336 D1:会话行预建——会话创建即落 `sessions` 一行(无 entries),不等首条消息。
|
|
2787
|
+
*
|
|
2788
|
+
* **要解决的问题**(实测复现,2026-08-10):`repo.createSession()` 的唯一调用点是
|
|
2789
|
+
* `RelationalSessionPersistence.save()`,而 save 由 `track()` 订阅的 `turn.end`/
|
|
2790
|
+
* `prompt.end` 触发——两者都发生在**首个模型回合完成之后**。因此"用户已回车、模型还没
|
|
2791
|
+
* 回任何东西"这段窗口(≈ 一次完整模型往返,数秒~数十秒)内进程若被强杀(SIGKILL/OOM/
|
|
2792
|
+
* 断电),DB 里连 `sessions` 行都不存在 → 整个会话蒸发,`/resume` 完全看不到,
|
|
2793
|
+
* 且 `detectCrashGap` 因 `load()` 返回 null 而根本不执行(RFC-305 D3 的盲区)。
|
|
2794
|
+
*
|
|
2795
|
+
* **为什么复用 doSaveEntries 而非直调 repo**(D1-b,RFC-159 D3):写租约检查在
|
|
2796
|
+
* `RelationalSessionPersistence.save()` 入口,是所有会话写路径的单一汇聚点。直调
|
|
2797
|
+
* `repo.createSession()` 会开一条无租约的写路径先例。走这里则天然经 `enqueue` 串行链
|
|
2798
|
+
* → `doSaveEntries` → `persistence.save()`,租约与写序语义全部继承,零新增写路径。
|
|
2799
|
+
* 空 entries 快照在 save 内部即 `createSession()` + 零次 `appendEntry()`,正是所需语义。
|
|
2800
|
+
*
|
|
2801
|
+
* **幂等**:`createSession` 是 `ON CONFLICT DO NOTHING`;重复调用安全。
|
|
2802
|
+
* **fire-and-forget**(规则 3):失败只 warn,绝不阻塞会话创建——预建是可用性增强,
|
|
2803
|
+
* 不是创建流程的正确性前提(失败时退化为改动前行为:首个 turn 后才落行)。
|
|
2804
|
+
*/
|
|
2805
|
+
/**
|
|
2806
|
+
* RFC-336 D2:输入边界落盘——prep 成功、模型往返开始**之前**把新增 entries 落库。
|
|
2807
|
+
*
|
|
2808
|
+
* 要解决的问题:落库此前只由 `turn.end`/`prompt.end` 触发(RFC-305 D1),两者都在首个
|
|
2809
|
+
* 模型回合完成之后。M1 的会话行预建保证了"会话不蒸发",但那条 user message 本身仍要
|
|
2810
|
+
* 等首个 turn 才进 DB——强杀窗口 ≈ 一次完整模型往返(数秒~数十秒)。本方法把该窗口
|
|
2811
|
+
* 压到"单条入链在途(毫秒级)",与 RFC-305 对 turn 2+ 的保证对齐。
|
|
2812
|
+
*
|
|
2813
|
+
* **T336-8 后端能力分流**(与 `persistOnTurnEnd` 同源判据 `deps.turnFlush`):
|
|
2814
|
+
* - `'incremental'`(本地 relational SQLite,缺省):游标增量 append,O(新增条数),
|
|
2815
|
+
* WAL 单事务毫秒级 → 每次输入边界都落,拿满收益。
|
|
2816
|
+
* - `'snapshot'`(远端 PUT / 旧 blob 逃生口):每次落库都是**整份快照上传/覆写**,
|
|
2817
|
+
* 随会话线性变贵。为一条 user message 付全量上传代价不划算,且远端本就有
|
|
2818
|
+
* 30s/5turn 节流的既定策略(RFC-305 M3)→ **豁免**,维持 `prompt.end` 强制落库
|
|
2819
|
+
* 兜底。这不是能力缺失,是后端能力边界下的正确取舍(RFC-336 §D2 约束)。
|
|
2820
|
+
*
|
|
2821
|
+
* fire-and-forget(规则 3)+ 复用 `enqueue` 串行链(规则 2/9):与预建、turn 落盘、
|
|
2822
|
+
* prompt 终态落盘共用同一条 per-session 链,天然串行、写租约语义一致。
|
|
2823
|
+
*/
|
|
2824
|
+
requestInputBoundaryFlush(agentSession: AgentSession, transient: boolean): void;
|
|
2825
|
+
precreateSessionRow(agentSession: AgentSession, transient: boolean): void;
|
|
2826
|
+
/**
|
|
2827
|
+
* 驱逐路径专用:用调用方**同步捕获**的快照入链落盘。
|
|
2828
|
+
* 会话即将出池,doSave 经 getSession 会扑空;同链串行保证不被在途旧写覆盖。
|
|
2829
|
+
*
|
|
2830
|
+
* 终局 review 复核(2026-07-21):曾怀疑"若同一会话有更早入链、尚未完成的 doSave job,
|
|
2831
|
+
* 本方法用调用方在驱逐时刻捕获的(可能更旧的)metadata 覆盖 doSave 写入的更新值"——
|
|
2832
|
+
* 追踪 enqueue() 的调用序保证后确认**不成立**,理由:
|
|
2833
|
+
* ① 调用方(session-manager 的 collectIdle/evictLeastRecent)捕获快照与紧随其后的
|
|
2834
|
+
* `agentSession.dispose()` 之间没有任何 await/yield 点,JS 单线程保证这段同步区间
|
|
2835
|
+
* 会话状态不可能再变化——本方法拿到的 snapshot 是驱逐那一刻真正最新的状态;
|
|
2836
|
+
* ② `enqueue()` 严格按调用顺序串行执行同一 session 的 job 链——若有更早的 doSave job
|
|
2837
|
+
* 仍在途(尚未执行到位),它必然排在本方法的 job **之前**执行,不可能反过来;
|
|
2838
|
+
* ③ `dispose()` 会 unsub 掉 `prompt.end`/`turn.end` 订阅,故此后不会再有新 doSave 入链;
|
|
2839
|
+
* ④ 即便某个更早的 doSave job 直到 dispose 之后才真正开始执行,届时 `getSession(id)`
|
|
2840
|
+
* 已因会话被 `sessions.delete(id)` 而扑空,`doSave` 提前 return(no-op),不写入
|
|
2841
|
+
* 任何数据——不存在"新值被旧值覆盖"的路径。
|
|
2842
|
+
* 结论:本方法在当前单进程同步模型下天然是该会话 job 链的最后一环,写入的是最新状态,
|
|
2843
|
+
* 无需额外的新鲜度校验。跨进程并发写由写租约(RFC-159 D3)独立兜底,与本方法无关。
|
|
2844
|
+
*/
|
|
2845
|
+
saveSnapshot(id: string, snapshot: Omit<SessionSnapshot<AgentMessage>, 'version' | 'id'>, onSettled?: () => void): Promise<void>;
|
|
2846
|
+
/**
|
|
2847
|
+
* 合并快照 metadata.config——外部解析配置(getConfig→pickPersistableConfig)
|
|
2848
|
+
* 与快照内已有的 inputHistory 叠加。两条落盘路径(saveSnapshot/doSave)共用,
|
|
2849
|
+
* 避免合并逻辑 split-brain。无任何来源时原样返回 metadata。
|
|
2850
|
+
*
|
|
2851
|
+
* RFC-108 D3:todoList/editedFiles/subagents/drafts 四个面板字段已从 metadata.config
|
|
2852
|
+
* 分离至 PanelStateStore,不再在此合并——落盘时由 doSave/saveSnapshot 额外调用
|
|
2853
|
+
* panelStateStore.saveAll() 独立持久化。
|
|
2854
|
+
*
|
|
2855
|
+
* inputHistory(@deprecated RFC-088 M4b)保留现有迁移路径不变。
|
|
2856
|
+
*/
|
|
2857
|
+
private mergeSnapshotConfig;
|
|
2858
|
+
private enqueue;
|
|
2859
|
+
/**
|
|
2860
|
+
* RFC-305 D2:entries 档——residency cap 评估 + 快照增量 append(游标式,O(新消息))。
|
|
2861
|
+
* turn.end 高频路径走此档;panelState(每次全量重写 5 个存储)不在其中,避免每 turn
|
|
2862
|
+
* 纯写放大。residency cap 保留在两档共同前缀(RFC-178 D5 在线评估语义不变)。
|
|
2863
|
+
*/
|
|
2864
|
+
private doSaveEntries;
|
|
2865
|
+
/** M1:duck-type 检测远端 CAS 能力——避免硬 import RemoteSessionPersistence(保持 runtime 不反向依赖 session 具体类)。 */
|
|
2866
|
+
private isRemoteCasPersistence;
|
|
2867
|
+
/**
|
|
2868
|
+
* M1 远端权威写入:CAS + 409 自动 reload/rebase/单次重试。
|
|
2869
|
+
*
|
|
2870
|
+
* 策略:
|
|
2871
|
+
* 1. 首次 PUT 带本进程已知的 `expectedRevision`(首次写入时 undefined = 无条件建)。
|
|
2872
|
+
* 2. 409 冲突 → 从服务端 load 最新快照 + revision。
|
|
2873
|
+
* 3. rebase:以服务端 entries 为基线,追加本进程内存中比基线更新的 entry(按 entry_id 去重)。
|
|
2874
|
+
* 4. 用新 revision 重 PUT 一次。再失败则抛出(不递归重试,防死循环)。
|
|
2875
|
+
*
|
|
2876
|
+
* rebase 语义对齐本地 append-only 模型:不删除服务端已有 entry,只追加本进程的新尾。
|
|
2877
|
+
* 这与 RelationalSessionPersistence 的"cursor 增量 append"同构——区别仅在远端是整份
|
|
2878
|
+
* PUT 而非 per-entry INSERT,所以 rebase 后仍需整份上传(直到远端支持 append-tail API)。
|
|
2879
|
+
*/
|
|
2880
|
+
private saveRemoteWithCas;
|
|
2881
|
+
/**
|
|
2882
|
+
* M1 rebase:把本地新尾追加到服务端快照之后。
|
|
2883
|
+
*
|
|
2884
|
+
* 按 entry_id 去重——服务端已有的 entry 保留,本地比服务端新的 entry 追加到尾部。
|
|
2885
|
+
* 不删除任何服务端 entry(append-only 红线在远端同样适用)。
|
|
2886
|
+
* metadata 取本地版本(本地有最新的 lastActiveAt/config 等可变字段)。
|
|
2887
|
+
*/
|
|
2888
|
+
private rebaseSnapshotAgainstRemote;
|
|
2889
|
+
/** RFC-305 D2:完整档 = entries + panelState。prompt 终态/clear/驱逐/saveAll 走此档。 */
|
|
2890
|
+
private doSave;
|
|
2891
|
+
saveAll(): Promise<void>;
|
|
2892
|
+
/**
|
|
2893
|
+
* 从持久化存储恢复单个 session。
|
|
2894
|
+
* 若未提供 configProvider 则返回 null。
|
|
2895
|
+
*/
|
|
2896
|
+
restore(id: string): Promise<AgentSession | null>;
|
|
2897
|
+
/**
|
|
2898
|
+
* RFC-305 D3:restore 时 trace↔DB 对账——检出"上次进程异常终止时已发生但未落库"的
|
|
2899
|
+
* turn 缺口并告警。对账基准 = trace append-log(逐事件实时落盘,独立于 save 链):
|
|
2900
|
+
* DB 最后一条已持久化 entry 时间戳之后,trace 仍有 `turn.end` lifecycle 事件 →
|
|
2901
|
+
* 这些 turn 的消息只进过内存、从未进 DB,已随崩溃永久丢失。
|
|
2902
|
+
*
|
|
2903
|
+
* 用时间戳而非 turn 号对账:trace 的 turn 字段 per-prompt 从 0 重置,跨 prompt 不可比;
|
|
2904
|
+
* ts 与 entry.timestamp 同源(Date.now 系时钟),单机单调可比。
|
|
2905
|
+
*
|
|
2906
|
+
* 容忍度:正常退出路径(prompt.end 立即落盘 + turn 级入链)下 trace turn.end 与 DB
|
|
2907
|
+
* 落库几乎同时,容忍 CRASH_GAP_TOLERANCE_MS 内的尾差(在途写 + 时钟粒度),超出才告警。
|
|
2908
|
+
*
|
|
2909
|
+
* fail-soft:traceStore 缺失/读取异常/无 turn.end 事件 → 静默跳过(debug 日志),
|
|
2910
|
+
* 绝不阻塞 restore、绝不产生假告警。
|
|
2911
|
+
*/
|
|
2912
|
+
/**
|
|
2913
|
+
* RFC-305 D3:对账结果查询口——restore/restoreAll 内部把本次恢复检出的崩溃缺口记入
|
|
2914
|
+
* per-session 缓存,调用方(CLI resume 路径)在订阅建立后读取并 toast。缓存随
|
|
2915
|
+
* forgetSession 清理,避免陈旧告警在后续 restore 重复弹出。
|
|
2916
|
+
*/
|
|
2917
|
+
consumeCrashGap(id: string): {
|
|
2918
|
+
lostTurns: number;
|
|
2919
|
+
} | undefined;
|
|
2920
|
+
/**
|
|
2921
|
+
* 从持久化存储批量恢复 session:最近活跃优先,最多填到剩余容量。
|
|
2922
|
+
* 装不下的更旧快照留在盘上(restore(id) 可按需复活),不挤掉在池会话。
|
|
2923
|
+
* 若未提供 configProvider 则直接返回 0。
|
|
2924
|
+
*/
|
|
2925
|
+
restoreAll(): Promise<number>;
|
|
2926
|
+
}
|
|
2927
|
+
//#endregion
|
|
2928
|
+
//#region src/session/residency-allocator.d.ts
|
|
2929
|
+
/**
|
|
2930
|
+
* RFC-323 D3:两层叠加——分配层 × 压力档缩放。
|
|
2931
|
+
*
|
|
2932
|
+
* 分配层解决"并发数不确定"(结构性、确定性);
|
|
2933
|
+
* 压力层解决"真实内存压力超预期"(反应式、RFC-178 触发器 C 保留)。
|
|
2934
|
+
*
|
|
2935
|
+
* 两者相乘:`allocateResidencyBudget(N) ÷ 压力档除数(1/2/4)`。
|
|
2936
|
+
* 下界 clamp(MIN ÷ 除数, 1) 防止极端收紧塌到 0。
|
|
2937
|
+
*/
|
|
2938
|
+
declare function effectiveResidencyBudget(activeSessionCount: number, pressureDivisor: number, config?: ResidencyBudgetConfig): number;
|
|
2939
|
+
//#endregion
|
|
2940
|
+
//#region src/session/global-residency-engine.d.ts
|
|
2941
|
+
/** 引擎运行的快照——调用方提供,引擎不持有状态(RN 严格)。 */
|
|
2942
|
+
interface EngineSnapshot {
|
|
2943
|
+
/** 当前活跃会话数(estimatedBytes > 0 的非 transient 会话)。 */
|
|
2944
|
+
activeSessionCount: number;
|
|
2945
|
+
/** 当前所有会话的总估算字节驻留(来自 InMemorySession.estimatedContentBytes 的汇总)。 */
|
|
2946
|
+
currentTotalResidentBytes: number;
|
|
2947
|
+
/** 压力档除数(来自 ResidencyGovernor,healthy=1 / warning=2 / critical=4)。 */
|
|
2948
|
+
pressureDivisor: number;
|
|
2949
|
+
/** 运行时注入的 residency 配置覆盖。 */
|
|
2950
|
+
config?: ResidencyBudgetConfig;
|
|
2951
|
+
}
|
|
2952
|
+
/** 引擎产出的治理决策(所有字段确定性)。 */
|
|
2953
|
+
interface ResidencyDecision {
|
|
2954
|
+
/** 单会话当前有效字节预算(已/处分配层 × 压力档)。 */
|
|
2955
|
+
maxBytesPerSession: number;
|
|
2956
|
+
/** 当前全局驻留压力比例(0.0 ~ 1.0)。 */
|
|
2957
|
+
globalPressure: number;
|
|
2958
|
+
/** 是否处于全局超限压力(高压力下才需要紧急治理)。 */
|
|
2959
|
+
isGlobalPressure: boolean;
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* 计算“每个会话当前 应该 byte budget — 给定 snapshot,治理一个值。
|
|
2963
|
+
*
|
|
2964
|
+
* 特性:
|
|
2965
|
+
* - 负反馈:currentTotalResidentBytes 越大 pressure 越高 → 每个会话预算*越低*。
|
|
2966
|
+
* - 下界 MIN_PER_SESSION:防止分配结果不可用作 (如果 N 极大, SESSION_MAX=50×16=800MB < totalBudget).
|
|
2967
|
+
* - 上界 MAX_PER_SESSION:模型折叠输入的最大(imageasher, window by compression)约为 214MB/会话。
|
|
2968
|
+
* - 确定性:相同 snapshot → 相同 decision(R4,皱纹是稳定 的。
|
|
2969
|
+
*/
|
|
2970
|
+
declare function decide(snapshot: EngineSnapshot): ResidencyDecision;
|
|
2971
|
+
//#endregion
|
|
2972
|
+
export { type AgentJobListener, AgentJobRegistry, type AgentJobRegistryOptions, type AgentJobStatus, type AgentObsStatus, type AgentObservabilityOptions, AgentObservabilityRegistry, type AgentObservation, type AgentObservationMeta, type AgentObservationSnapshot, type AgentRuntime, AgentSession, type AgentSessionEvent, type AgentSessionEventMap, type AgentSessionEventType, type AgentSessionInfo, type AgentSessionJob, type AgentSessionOptions, type AgentSessionSubscriber, type ApplyDiffResult, type ApplyPolicy, type ApplySignals, type ArchiveRecallPort, type AskUserQuestion, type AutonomyLevel, type BackgroundProcess, type BackgroundProcessStatus, CAPABILITY_SURFACE_PATTERNS, type Checkpoint, type CheckpointData, type ContextBuildInput, type ContextInjectionPort, type ContextSource, ContextSourceRegistry, DEFAULT_MAX_AUTO_APPLY_LINES, type DiskSessionPoolOptions, type DocSyncPort, type EngineStoreLocation, type EngineStoreOverrides, type ExitResult, FORBIDDEN_ZONE_PATTERNS, type FileEntrySnapshot, type FileSnapshot, FleetMonitor, type FleetMonitorOptions, type FleetSample, type GrillAnswer, type GrillQuestion, type GrillRequest, type KillOptions, LEVEL_DIVISOR, type ManagedProcess, type MemoryDeltaPort, MemoryGovernor, type MemoryGovernorOptions, type MemoryPort, type MemoryPressureLevel, type MemorySample, type MemorySubsystem, PanelStateStore, PersistenceSync, type PersistenceSyncDeps, type ProcessCategory, type ProcessFilter, type ProcessLifecycle, type ProcessListener, type ProcessOwner, ProcessRegistry, type ProcessRegistryOptions, ProcessRuntime, type ProcessRuntimeOptions, type ProcessSpawnConfig, type ProcessStatus, type PromptOptions, type PromptRefresh, RUNTIME_DEFAULTS, type RegisterAgentJobInput, type RegisterInput, type RemoteSessionPoolOptions, type ReplayLevel, type ResidencyDiagnostics, ResidencyGovernor, type ResidencyPressureLevel, type ResolvedSessionConfig, type RestoreMode, type RuntimeOptions, SYSTEM_PROMPT_SECTIONS, type SandboxRunner, type SandboxToolCall, type SandboxToolResult, SandboxUnavailableError, SessionPool, type SessionPoolOptions, type SessionPorts, type SessionStableInjectionOptions, type SessionStorageConfig, type SystemPromptSectionSpec, TimeTravelController, type TimeTravelDeps, type TodoReminderPort, type TracebackView, type TurnRecord, type Verdict, type Worktree, applyDiff, applyMemoryTransform, attachAgentObserver, buildEngineStores, captureFiles, createAgentRuntime, createDefaultContextSourceRegistry, createDiskSessionPool, createDocSyncReminderHooks, createEnvironmentContextSource, createInMemorySessionPool, createMemoryDeltaHooks, createMemoryIndexContextSource, createRemoteSessionPool, createSessionStableInjectionHooks, createTodoReminderHooks, createToolHookExecutor, createWorktree, decideApply, decide as decideResidency, defaultMaxToolTurnExtensions, deriveSessionTitle, diffTouchedPaths, effectiveResidencyBudget, formatDocSyncReminder, globalAgentJobRegistry, globalAgentObservability, globalProcessRegistry, globalProcessRuntime, hasRepoDrifted, installAgentJobReaper, installExitReaper, installProcessReaper, messagesFromSnapshot, pruneOrphanWorktrees, reconstructMessages, removeWorktree, resolveSessionTitle, resolveTodoContinuationConfig, restoreFiles, revertDiff, touchesCapabilitySurface, touchesForbiddenZone, worktreeDiff };
|
|
2973
|
+
//# sourceMappingURL=index.d.ts.map
|