@x-otto/session-contract 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 +95 -0
- package/dist/index.d.ts +648 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @x-otto/session-contract
|
|
2
|
+
|
|
3
|
+
> Engine↔frontend session projection implementation. Produces neutral `SessionEvent[]` from engine events — the single shared projection for CLI/TUI/web.
|
|
4
|
+
|
|
5
|
+
`@x-otto/session-contract` is the **projection implementation layer** between the engine and frontends. It takes `AgentSessionEvent` (from `@x-otto/interchange`) and produces frontend-neutral `SessionEvent[]` via `projectSessionEvent()`. This single projector is shared by CLI (`reduceToTui`), web (`encodeSessionEvent`), and any future frontend — eliminating the drift that occurred with dual projections.
|
|
6
|
+
|
|
7
|
+
It also defines the `FrontendPort` interface — the explicit contract for frontends consuming engine capabilities (session management, prompting, event subscription, approvals, HITL, model switching, permissions, auth, i18n, hot reload).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @x-otto/session-contract
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { projectSessionEvent, createProjectorState } from '@x-otto/session-contract'
|
|
19
|
+
import type { AgentSessionEvent } from '@x-otto/interchange'
|
|
20
|
+
import type {
|
|
21
|
+
ProjectCtx,
|
|
22
|
+
TurnRecord,
|
|
23
|
+
FrontendPort,
|
|
24
|
+
FrontendSessionPort,
|
|
25
|
+
FrontendModelPort,
|
|
26
|
+
} from '@x-otto/session-contract'
|
|
27
|
+
|
|
28
|
+
// Create a projector (holds accumulated state)
|
|
29
|
+
const state = createProjectorState()
|
|
30
|
+
|
|
31
|
+
// Project engine events to frontend-neutral SessionEvents
|
|
32
|
+
const events: AgentSessionEvent[] = [
|
|
33
|
+
{ type: 'session.start', sessionId: 'abc-123' },
|
|
34
|
+
{ type: 'prompt.start', sessionId: 'abc-123', text: 'hello' },
|
|
35
|
+
]
|
|
36
|
+
for (const event of events) {
|
|
37
|
+
const projected = projectSessionEvent(event, state)
|
|
38
|
+
// projected is SessionEvent[] — neutral, frontend-agnostic
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// FrontendPort — explicit interface for frontends
|
|
42
|
+
interface MyFrontend implements FrontendPort {
|
|
43
|
+
async createSession(options?) { /* ... */ }
|
|
44
|
+
// ... implement all port methods
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### Projection
|
|
51
|
+
- `projectSessionEvent(event, state, ctx?)` — project AgentSessionEvent → SessionEvent[]
|
|
52
|
+
- `createProjectorState()` — create accumulator with delegated & todo list state
|
|
53
|
+
- `ProjectCtx` — context injection (toolResultText, currentModel, turnTokens, cacheHitRatio, isDelegationTool)
|
|
54
|
+
- `SessionEvent` — discriminated union of 20+ event kinds (prompt, stream, tool, tasks, approval, ask, error, session lifecycle, etc.)
|
|
55
|
+
|
|
56
|
+
### Types
|
|
57
|
+
- `TurnRecord` — single turn timeline record (index, timing, toolCount, outputTokens)
|
|
58
|
+
- `TaskItem` / `DelegatedTask` — task list items for frontend rendering
|
|
59
|
+
- `SourceToolCall` / `SourceQuestion` — projected call/question types
|
|
60
|
+
- `SessionCommand` — **frozen/dormant** inbound command contract (prompt/interrupt/approval/stop) — reserved for future web-GUI use
|
|
61
|
+
- `SessionEventKind` — discriminated union of all event kind strings
|
|
62
|
+
- `TodoStatus` — re-exported from `@x-otto/interchange`
|
|
63
|
+
|
|
64
|
+
### Pulse Survey
|
|
65
|
+
- `shouldShowPulseSurvey(currentTurn, config, state, random?)` — pure function trigger decision
|
|
66
|
+
- `PulseSurveyTriggerConfig` / `PulseSurveyTriggerState` — config and state types
|
|
67
|
+
|
|
68
|
+
### Request Decision Logic
|
|
69
|
+
- `autoAdoptAnswer(q)` — cancel/null → auto-adopt recommendation
|
|
70
|
+
- `resolveChoiceAnswer(q, selected, freeform?)` — multi-choice resolution
|
|
71
|
+
- `resolveTextAnswer(q, text)` — free-text resolution
|
|
72
|
+
- `sandboxBypassCommand(description, args, marker)` — sandbox escalation bypass
|
|
73
|
+
|
|
74
|
+
### Frontend Port (FrontendPort)
|
|
75
|
+
- `createSession(options?)` → `FrontendSessionPort`
|
|
76
|
+
- `FrontendSessionPort` — session lifecycle: `prompt()`, `subscribe()`, `resolveApproval()`, `resolveAskUser()`, `clearContext()`, `compactNow()`, `renameSession()`, `abort()`, `dispose()`
|
|
77
|
+
- `FrontendJobObserverPort` — background task observation (list, subscribe to spawn/update/exit/remove)
|
|
78
|
+
- `FrontendModelPort` — model listing, switching, remote refresh
|
|
79
|
+
- `FrontendPermissionPort` — permission mode read/write
|
|
80
|
+
- `FrontendAuthPort` — auth source query (without exposing credentials)
|
|
81
|
+
- `FrontendI18nPort` — locale-aware string lookup
|
|
82
|
+
- `FrontendPort.reloadCapabilities()` — hot reload (MCP/tools/models/agents/i18n)
|
|
83
|
+
|
|
84
|
+
### Constants
|
|
85
|
+
- `SESSION_MAX_HISTORY_MESSAGES` — memory cap for history (default 5000)
|
|
86
|
+
- `SESSION_MAX_CONTENT_BYTES` — byte budget for message content (default 256MB)
|
|
87
|
+
|
|
88
|
+
## Dependencies
|
|
89
|
+
|
|
90
|
+
- Internal: `@x-otto/env`, `@x-otto/interchange`
|
|
91
|
+
- External: none
|
|
92
|
+
|
|
93
|
+
## Related
|
|
94
|
+
|
|
95
|
+
- [Architecture](./ARCHITECTURE.md)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import { AgentSessionEvent, AgentSessionEventType, GrillAnswer, GrillQuestion, TodoStatus, TodoStatus as TodoStatus$1 } from "@x-otto/interchange";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
interface TaskItem {
|
|
5
|
+
id?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
status?: TodoStatus$1;
|
|
8
|
+
/** 该 task 最后被 write_todos 更新时的回合编号(1-based)。供 TaskDetailPane 按 turn 过滤消息。 */
|
|
9
|
+
turn?: number;
|
|
10
|
+
}
|
|
11
|
+
interface DelegatedTask {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
detail?: string;
|
|
15
|
+
status: 'running' | 'done' | 'error';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 单轮记录(turn timeline,如「turn 6 ✓ 3 tools 2.1s」)——RFC-074 D7 单一真源。
|
|
19
|
+
* runtime 观测层产出(AgentObservabilityRegistry.getTurns),tui timeline 渲染消费
|
|
20
|
+
* (formatTurnTimeline)。此前两侧各自手抄(runtime TurnRecord ⟂ tui TurnLike,且 tools
|
|
21
|
+
* 必填性已漂移);下沉到本前端无关契约后,runtime 与 tui 共引一处,漂移编译期不可能。
|
|
22
|
+
*/
|
|
23
|
+
interface TurnRecord {
|
|
24
|
+
index: number;
|
|
25
|
+
startedAt: number;
|
|
26
|
+
/** 完成时刻;未完成(当前 turn)为 undefined。 */
|
|
27
|
+
endedAt?: number;
|
|
28
|
+
toolCount: number;
|
|
29
|
+
/** 本轮按序调用的工具名(供 timeline 显示"这一轮干了啥")。 */
|
|
30
|
+
tools: string[];
|
|
31
|
+
outputTokens: number;
|
|
32
|
+
}
|
|
33
|
+
interface SourceToolCall {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
arguments?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* HITL 问询的视图模型——直接复用协议叶子 `@x-otto/interchange` 的 `GrillQuestion` 单一真相源
|
|
40
|
+
* (原为本包独立重声明,现去重消除手抄漂移)。保留 `SourceQuestion` 别名供
|
|
41
|
+
* 既有消费方无感引用。
|
|
42
|
+
*/
|
|
43
|
+
type SourceQuestion = GrillQuestion;
|
|
44
|
+
type Base = {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
};
|
|
47
|
+
/** 集合投影载荷(= tui setTasks/setDelegatedTasks 的真参型,reduce 喂回零摩擦)。 */
|
|
48
|
+
type TaskItemArg = TaskItem[];
|
|
49
|
+
type DelegatedArg = DelegatedTask[];
|
|
50
|
+
type SessionEvent = (Base & {
|
|
51
|
+
kind: 'prompt.start';
|
|
52
|
+
text: string;
|
|
53
|
+
}) | (Base & {
|
|
54
|
+
kind: 'prompt.end';
|
|
55
|
+
model: string;
|
|
56
|
+
tokens?: number;
|
|
57
|
+
/**
|
|
58
|
+
* RFC-019 M1:本回合缓存命中率(R1 = cacheRead / totalInput,范围 [0,1])。
|
|
59
|
+
* `undefined` = 供应商无缓存 API 或本回合无 usage 数据——消费方应隐藏该字段。
|
|
60
|
+
*/
|
|
61
|
+
cacheHitRatio?: number; /** RFC-235:本回合实际 effort 档(供回合摘要行显示 ·Effort)。 */
|
|
62
|
+
effort?: string; /** 回合结束原因(缺省 = 'completed',向后兼容旧事件)。 */
|
|
63
|
+
stopReason?: 'completed' | 'interrupted' | 'error';
|
|
64
|
+
})
|
|
65
|
+
/**
|
|
66
|
+
* RFC-209 §2.5:满意度脉冲调查展示指令(web-ui 消费)。**不是 `projectSessionEvent`
|
|
67
|
+
* 直接产出的分支**——本事件由 `EventBridge`(`packages/service`)在收到投影后的
|
|
68
|
+
* `prompt.end` 之后,额外调用 `shouldShowPulseSurvey` 判定命中时追加发送(触发判定
|
|
69
|
+
* 依赖宿主运行时状态如触发计数/配置,不是纯投影逻辑,见 RFC-209 §2.4 编排职责边界)。
|
|
70
|
+
* TUI 侧不消费本 SessionEvent 分支(TUI 走独立的 `tui.setPulseSurvey` 直接调用路径,
|
|
71
|
+
* 见 `interactive-events.ts`),只有 web-ui 经 WS 收到本事件后驱动 `PulseSurveyCard`。
|
|
72
|
+
*/
|
|
73
|
+
| (Base & {
|
|
74
|
+
kind: 'pulse-survey.show';
|
|
75
|
+
})
|
|
76
|
+
/**
|
|
77
|
+
* RFC-210 D5(M4):跨端渲染 payload 下发(web-ui 消费)。同 `pulse-survey.show` 模式——
|
|
78
|
+
* **不是 `projectSessionEvent` 产出的分支**:由 `EventBridge` 在投影出 `tool.end` 后额外
|
|
79
|
+
* 执行 `mars.resolveA2uiRendererPayload(message)`(插件 toPayload,宿主运行时状态依赖,
|
|
80
|
+
* 非纯投影),命中时追加发送。`components` 为 a2ui 组件树(可序列化 JSON,React catalog
|
|
81
|
+
* 渲染,未知组件类型降级文本摘要);`forToolCallId` 关联触发的工具调用。
|
|
82
|
+
* TUI 不消费本分支(TUI 在宿主进程内渲染分发时直调 resolveA2uiRendererPayload)。
|
|
83
|
+
*/
|
|
84
|
+
| (Base & {
|
|
85
|
+
kind: 'a2ui.payload';
|
|
86
|
+
forToolCallId?: string;
|
|
87
|
+
components: unknown[];
|
|
88
|
+
}) | (Base & {
|
|
89
|
+
kind: 'stream.text.start';
|
|
90
|
+
}) | (Base & {
|
|
91
|
+
kind: 'stream.text.delta';
|
|
92
|
+
delta: string;
|
|
93
|
+
}) | (Base & {
|
|
94
|
+
kind: 'stream.text.end';
|
|
95
|
+
}) | (Base & {
|
|
96
|
+
kind: 'stream.thinking.start';
|
|
97
|
+
}) | (Base & {
|
|
98
|
+
kind: 'stream.thinking.delta';
|
|
99
|
+
delta: string;
|
|
100
|
+
}) | (Base & {
|
|
101
|
+
kind: 'stream.thinking.end';
|
|
102
|
+
}) | (Base & {
|
|
103
|
+
kind: 'tool.start';
|
|
104
|
+
call: SourceToolCall;
|
|
105
|
+
}) | (Base & {
|
|
106
|
+
kind: 'tool.end';
|
|
107
|
+
call: SourceToolCall;
|
|
108
|
+
isError: boolean;
|
|
109
|
+
resultText?: string;
|
|
110
|
+
resultTokensEstimate?: number;
|
|
111
|
+
suppressMessage?: boolean;
|
|
112
|
+
}) | (Base & {
|
|
113
|
+
kind: 'tasks.update';
|
|
114
|
+
tasks: TaskItemArg;
|
|
115
|
+
title?: string;
|
|
116
|
+
}) | (Base & {
|
|
117
|
+
kind: 'delegate.update';
|
|
118
|
+
tasks: DelegatedArg;
|
|
119
|
+
})
|
|
120
|
+
/** 工具调用预算窗口(每轮 turn.start 投影):toolTurnCount=本窗口已用轮次,maxToolTurns=窗口上限;
|
|
121
|
+
* outputTokens/outputTokenBudget(RFC-104 D5):token 预算用量(未配置时 budget 缺省,前端隐藏)。 */
|
|
122
|
+
| (Base & {
|
|
123
|
+
kind: 'turn.budget';
|
|
124
|
+
toolTurnCount: number;
|
|
125
|
+
maxToolTurns: number;
|
|
126
|
+
outputTokens?: number;
|
|
127
|
+
outputTokenBudget?: number;
|
|
128
|
+
}) | (Base & {
|
|
129
|
+
kind: 'session.activity';
|
|
130
|
+
action: 'created' | 'ended';
|
|
131
|
+
}) | (Base & {
|
|
132
|
+
kind: 'session.status';
|
|
133
|
+
status: 'streaming' | 'idle' | 'compacting';
|
|
134
|
+
model?: string;
|
|
135
|
+
stopReason?: string;
|
|
136
|
+
messageCount?: number;
|
|
137
|
+
retainedCount?: number;
|
|
138
|
+
}) | (Base & {
|
|
139
|
+
kind: 'approval.required';
|
|
140
|
+
id: string;
|
|
141
|
+
toolName: string;
|
|
142
|
+
arguments: Record<string, unknown>;
|
|
143
|
+
description: string; /** HITL 显示风险等级(权限层派生,仅显示提示;缺省下游回退 medium)。 */
|
|
144
|
+
risk?: 'low' | 'medium' | 'high';
|
|
145
|
+
}) | (Base & {
|
|
146
|
+
kind: 'approval.resolved';
|
|
147
|
+
id: string;
|
|
148
|
+
approved: boolean;
|
|
149
|
+
}) | (Base & {
|
|
150
|
+
kind: 'ask.required';
|
|
151
|
+
requestId: string;
|
|
152
|
+
question: SourceQuestion;
|
|
153
|
+
}) | (Base & {
|
|
154
|
+
kind: 'ask.resolved';
|
|
155
|
+
requestId: string;
|
|
156
|
+
}) | (Base & {
|
|
157
|
+
kind: 'plan.approval.required';
|
|
158
|
+
requestId: string;
|
|
159
|
+
planContent: string;
|
|
160
|
+
}) | (Base & {
|
|
161
|
+
kind: 'plan.approval.resolved';
|
|
162
|
+
requestId: string;
|
|
163
|
+
action: string;
|
|
164
|
+
}) | (Base & {
|
|
165
|
+
kind: 'error';
|
|
166
|
+
message: string;
|
|
167
|
+
code: string;
|
|
168
|
+
});
|
|
169
|
+
type SessionEventKind = SessionEvent['kind'];
|
|
170
|
+
type SubmitIntent = 'send' | 'steer';
|
|
171
|
+
/**
|
|
172
|
+
* ❄️ FROZEN/DORMANT:UI→engine 的**入站**命令契约(prompt/interrupt/approval/stop)。
|
|
173
|
+
* `SessionEvent`(出站投影)是 live(cli reduceToTui / service encode);但 `SessionCommand` 入站面
|
|
174
|
+
* **当前零消费者**——gated on 真 web ui→engine 命令发送方(STEP④ 暂缓)。保留契约、勿当死类型删;
|
|
175
|
+
* 激活条件=真实 web-GUI 命令通道落地。
|
|
176
|
+
* RFC-083 D3(2026-06-30)reconfirmed **冻结口径**(与 D2 web 种子冻结一致,非删除):近期不迭代
|
|
177
|
+
* web-ui,真需要时按出站投影对称重建;dead-export gate 不 flag 此类型,冻结零成本。
|
|
178
|
+
*/
|
|
179
|
+
type SessionCommand = {
|
|
180
|
+
kind: 'prompt';
|
|
181
|
+
sessionId: string;
|
|
182
|
+
text: string;
|
|
183
|
+
intent: SubmitIntent;
|
|
184
|
+
mode?: string;
|
|
185
|
+
} | {
|
|
186
|
+
kind: 'interrupt';
|
|
187
|
+
sessionId: string;
|
|
188
|
+
} | {
|
|
189
|
+
kind: 'approval.respond';
|
|
190
|
+
sessionId: string;
|
|
191
|
+
requestId: string;
|
|
192
|
+
approved: boolean;
|
|
193
|
+
scope?: 'once' | 'always';
|
|
194
|
+
} | {
|
|
195
|
+
kind: 'stop';
|
|
196
|
+
sessionId: string;
|
|
197
|
+
};
|
|
198
|
+
/** 投影上下文:把 cli 侧不在事件里的真相(末次工具结果、本轮模型、本回合 token 消耗)注入投影器。 */
|
|
199
|
+
interface ProjectCtx {
|
|
200
|
+
/** cli lastToolResultText() —— tool 消息的 result 来自 AgentSessionEvent 之外。 */
|
|
201
|
+
toolResultText?: (call: SourceToolCall) => string | undefined;
|
|
202
|
+
/** cli session.model.id —— prompt.end summary 行的模型真源。 */
|
|
203
|
+
currentModel?: () => string;
|
|
204
|
+
/** cli 本回合累计 token(prompt+completion)。缺省 → prompt.end.tokens 为 undefined(不显示)。 */
|
|
205
|
+
turnTokens?: () => number | undefined;
|
|
206
|
+
/**
|
|
207
|
+
* cli session.getLastCacheHitRatio() —— 本回合缓存命中率真源(RFC-019 M1)。
|
|
208
|
+
* 缺省或返回 null → prompt.end.cacheHitRatio 为 undefined(不显示)。
|
|
209
|
+
*/
|
|
210
|
+
cacheHitRatio?: () => number | null | undefined;
|
|
211
|
+
/** cli DELEGATION_TOOLS 判定(缺省内置同集)。 */
|
|
212
|
+
isDelegationTool?: (name: string) => boolean;
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/project.d.ts
|
|
216
|
+
interface ProjectorState {
|
|
217
|
+
delegated: Map<string, DelegatedTask>;
|
|
218
|
+
todoList: TaskItem[];
|
|
219
|
+
/** 当前回合编号(1-based)。prompt.start 时自增。 */
|
|
220
|
+
currentTurn: number;
|
|
221
|
+
}
|
|
222
|
+
declare function createProjectorState(): ProjectorState;
|
|
223
|
+
declare const DEFAULT_DELEGATION_TOOLS: Set<string>;
|
|
224
|
+
declare function projectSessionEvent(event: AgentSessionEvent, state: ProjectorState, ctx?: ProjectCtx): SessionEvent[];
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/requests.d.ts
|
|
227
|
+
/**
|
|
228
|
+
* requests.ts — 请求子通道的**纯决策逻辑**(ask_user 自动采纳 / approval 沙箱升级)。
|
|
229
|
+
*
|
|
230
|
+
* cli 的异步处理器(session.subscribe 的 ask.user.required / approval.required)仍持控制流
|
|
231
|
+
* (await UI + session.resolve*),但把「该解析成什么答案」「要不要 bypass」这两段易错的安全关键
|
|
232
|
+
* 决策下沉到这些纯函数 —— 可单测、对齐「取消/留空绝不悬挂工具,自动采纳推荐」纪律。
|
|
233
|
+
*/
|
|
234
|
+
interface AskQuestionLike {
|
|
235
|
+
recommendation: string;
|
|
236
|
+
options?: {
|
|
237
|
+
label: string;
|
|
238
|
+
recommended?: boolean;
|
|
239
|
+
}[];
|
|
240
|
+
}
|
|
241
|
+
interface AskAnswer {
|
|
242
|
+
answer: string;
|
|
243
|
+
selectedOptionLabels?: string[];
|
|
244
|
+
acceptedRecommendation: boolean;
|
|
245
|
+
resolvedBy: 'human' | 'auto_recommendation';
|
|
246
|
+
/** grill_me allowFreeform:用户在选项之外输入的补充文字。 */
|
|
247
|
+
freeformText?: string;
|
|
248
|
+
}
|
|
249
|
+
/** 取消/留空 → 自动采纳推荐(resolvedBy=auto_recommendation;绝不悬挂工具)。 */
|
|
250
|
+
declare function autoAdoptAnswer(q: AskQuestionLike): AskAnswer;
|
|
251
|
+
/** 单/多选题+可选自由文本:据用户已选 label(调用方须先去 ★ 装饰)解析。空选 → 自动采纳。
|
|
252
|
+
* acceptedRecommendation:当且仅当选中集恰等于推荐集(非空)。 */
|
|
253
|
+
declare function resolveChoiceAnswer(q: AskQuestionLike, selected: string[], freeformText?: string): AskAnswer;
|
|
254
|
+
/** 自由文本题:空/取消 → 自动采纳;否则人答(文本等于推荐才算采纳)。 */
|
|
255
|
+
declare function resolveTextAnswer(q: AskQuestionLike, text: string | null | undefined): AskAnswer;
|
|
256
|
+
/** 沙箱升级 approval:描述带 marker 且参数含 command → 返回需 bypass 的命令,否则 null。
|
|
257
|
+
* 调用方仅在「已批准」时据返回值放行(早于 resolveApproval,approval=escalated 语义)。 */
|
|
258
|
+
declare function sandboxBypassCommand(description: unknown, args: Record<string, unknown> | undefined, marker: string): string | null;
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/frontend-port.d.ts
|
|
261
|
+
/** prompt 附带的图片(与 runtime PromptOptions.images 元素同形,纯数据)。 */
|
|
262
|
+
interface FrontendPromptImage {
|
|
263
|
+
type: 'image';
|
|
264
|
+
data: string;
|
|
265
|
+
mediaType: string;
|
|
266
|
+
}
|
|
267
|
+
/** 会话当前模型的最小投影面(同 `ModelEntry`,供 `FrontendSessionPort.model` 只读展示)。 */
|
|
268
|
+
interface FrontendSessionModel {
|
|
269
|
+
id: string;
|
|
270
|
+
name: string;
|
|
271
|
+
provider: string;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 前端可消费的会话端口——AgentSession 面向前端的投影。
|
|
275
|
+
* 事件 payload 复用 @x-otto/interchange 的 AgentSessionEvent(中性 wire 类型)。
|
|
276
|
+
*
|
|
277
|
+
* 终局架构 review 2026-07-10 P0-3:本接口此前只覆盖 prompt/subscribe/审批/HITL 五类
|
|
278
|
+
* 协议原语,第二个真实消费者 feishu-use 的 `/status`/`/clear`/`/compact`/`/rename` 命令
|
|
279
|
+
* 仍直连 `@x-otto/coding` 的 `AgentSession` 具体类型读写 `title`/`agentName`/`model`/
|
|
280
|
+
* `session.messages()`/`clearContext()`/`compactNow()`/`renameSession()`——本次补齐这些
|
|
281
|
+
* 字段/方法,使 `FrontendSessionPort` 真正覆盖前端只读展示 + 会话级操作的完整面,
|
|
282
|
+
* feishu-use 迁移后零 `@x-otto/coding` 值/类型导入(除引擎装配入口 `createFrontendPort`)。
|
|
283
|
+
*/
|
|
284
|
+
interface FrontendSessionPort {
|
|
285
|
+
readonly id: string;
|
|
286
|
+
/** 会话标题(未设置时为 undefined,前端自行决定展示占位符)。 */
|
|
287
|
+
readonly title: string | undefined;
|
|
288
|
+
/** 会话绑定的 agent profile 名(如 'plugin-feishu-use'/'feishu-use-trusted')。 */
|
|
289
|
+
readonly agentName: string;
|
|
290
|
+
/** 当前模型的最小投影(同 `ModelEntry` 形状,供 `/status` 等只读展示)。 */
|
|
291
|
+
readonly model: FrontendSessionModel;
|
|
292
|
+
/** 当前是否有正在执行的回合(切换模型等操作前应先检查)。 */
|
|
293
|
+
readonly isBusy: boolean;
|
|
294
|
+
/** 完整转录消息数(`/status`/`/clear` 展示用,不暴露消息内容本身)。 */
|
|
295
|
+
messageCount(): number;
|
|
296
|
+
/**
|
|
297
|
+
* 最近一条 assistant 消息的纯文本(`prompt()` 完成后取回复内容的通用方式)。
|
|
298
|
+
* 无 assistant 消息或最后一条无有效文本时返回空字符串。前端不需要访问完整消息
|
|
299
|
+
* 数组/内容块结构——这是"取最新回复文本"这一通用需求的最小投影。
|
|
300
|
+
*/
|
|
301
|
+
lastAssistantText(): string;
|
|
302
|
+
prompt(text: string, options?: {
|
|
303
|
+
images?: FrontendPromptImage[];
|
|
304
|
+
}): Promise<void>;
|
|
305
|
+
subscribe<K extends AgentSessionEventType>(type: K, handler: (event: Extract<AgentSessionEvent, {
|
|
306
|
+
type: K;
|
|
307
|
+
}>) => void): () => void;
|
|
308
|
+
resolveApproval(approvalId: string, approved: boolean): void;
|
|
309
|
+
/**
|
|
310
|
+
* RFC-120 D1/§4:解除 grill_me 的 HITL 挂起(`requestGrill()` → `ask.user.required` →
|
|
311
|
+
* 本方法)。必须传真实 `GrillAnswer`——引擎侧 `resolveAskUser(id, null)` 不会解除挂起
|
|
312
|
+
* (`null` 分支设计意图是"放弃/不产生 resolved 事件",而非"真正结束等待"),调用方需要
|
|
313
|
+
* 兜底时应传 `autoAdoptAnswer(question)`(`@x-otto/session-contract` 导出)构造的答案。
|
|
314
|
+
*/
|
|
315
|
+
resolveAskUser(requestId: string, answer: GrillAnswer): void;
|
|
316
|
+
/** `/clear`——清空对话历史(保留会话本身,不换 sessionId)。 */
|
|
317
|
+
clearContext(): Promise<void>;
|
|
318
|
+
/** `/compact`——按需强制压缩上下文,返回压缩前后的消息数。 */
|
|
319
|
+
compactNow(): Promise<{
|
|
320
|
+
compacted: boolean;
|
|
321
|
+
before: number;
|
|
322
|
+
after: number;
|
|
323
|
+
}>;
|
|
324
|
+
/** `/rename`——重命名会话(custom 等级,永不被 ai/derived 覆盖)。 */
|
|
325
|
+
renameSession(title: string): void;
|
|
326
|
+
/**
|
|
327
|
+
* 中断当前正在执行的回合(纯转发 AgentSession.abort()——已存在的底层方法,
|
|
328
|
+
* 本次仅投影到前端契约面)。无头前端(如飞书桥接)经此发出中断信号,零附加逻辑。
|
|
329
|
+
* 中断语义与 HTTP /api/chat/interrupt 端点一致:调用即执行,无二次确认。
|
|
330
|
+
*/
|
|
331
|
+
abort(): void;
|
|
332
|
+
dispose(): void;
|
|
333
|
+
}
|
|
334
|
+
/** 后台任务的前端观察投影(runtime AgentSessionJob 的只读子集)。 */
|
|
335
|
+
interface FrontendJobView {
|
|
336
|
+
id: string;
|
|
337
|
+
sessionId: string;
|
|
338
|
+
title: string;
|
|
339
|
+
status: 'running' | 'needs_input' | 'ready' | 'applied' | 'failed' | 'canceled';
|
|
340
|
+
/** 任务开始时刻(epoch ms),排序/展示用。 */
|
|
341
|
+
startedAt: number;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* 后台任务注册表的只读观察端口(runtime globalAgentJobRegistry 的前端投影)。
|
|
345
|
+
* 只读:不提供 start/cancel——前端不得成为任务创建入口(RFC-117 D6 安全边界)。
|
|
346
|
+
*/
|
|
347
|
+
interface FrontendJobObserverPort {
|
|
348
|
+
listForSession(sessionId: string): FrontendJobView[];
|
|
349
|
+
onSpawn(listener: (job: FrontendJobView) => void): () => void;
|
|
350
|
+
onUpdate(listener: (job: FrontendJobView) => void): () => void;
|
|
351
|
+
onExit(listener: (job: FrontendJobView) => void): () => void;
|
|
352
|
+
onRemove(listener: (job: FrontendJobView) => void): () => void;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* 模型描述最小字段面(供前端选择器消费)。
|
|
356
|
+
* 与 `@x-otto/ai` 的 `Model` 类型解耦——端口只暴露前端真实需要的字段。
|
|
357
|
+
*/
|
|
358
|
+
interface ModelEntry {
|
|
359
|
+
id: string;
|
|
360
|
+
name: string;
|
|
361
|
+
provider: string;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 模型操作端口——列出/切换模型。由宿主侧构造并注入,前端不直接访问 ModelRegistry。
|
|
365
|
+
*/
|
|
366
|
+
interface FrontendModelPort {
|
|
367
|
+
getAll(): ModelEntry[];
|
|
368
|
+
/**
|
|
369
|
+
* 切换指定会话的当前模型。返回 null 表示成功;返回字符串为错误提示(可直接展示给用户)。
|
|
370
|
+
* 实现侧负责 isBusy 检查、模型存在性校验、session.model 赋值。
|
|
371
|
+
*/
|
|
372
|
+
switchModel(sessionId: string, modelId: string): Promise<string | null>;
|
|
373
|
+
/**
|
|
374
|
+
* 从各已认证 provider 实时拉取最新模型列表(RFC-136 D2)——长驻前端进程(如飞书
|
|
375
|
+
* 桥接)启动后模型列表不会自动更新,`/model refresh` 经此触发。裁剪自
|
|
376
|
+
* `App.refreshModels()` 的返回值(去掉 `availableIds`,那是宿主内部同步
|
|
377
|
+
* `availableModelIds` 用,前端不需要)。并发调用天然去重(`ModelCatalogManager.refresh()`
|
|
378
|
+
* 内部保证),多前端并发触发安全。
|
|
379
|
+
*/
|
|
380
|
+
refresh(): Promise<Array<{
|
|
381
|
+
provider: string;
|
|
382
|
+
status: 'ok' | 'skipped' | 'failed';
|
|
383
|
+
added?: number;
|
|
384
|
+
kept?: number;
|
|
385
|
+
total?: number;
|
|
386
|
+
reason?: string;
|
|
387
|
+
}>>;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 权限模式端口——读取/切换审核等级。由宿主侧构造并注入。
|
|
391
|
+
*/
|
|
392
|
+
interface FrontendPermissionPort {
|
|
393
|
+
/** bypass/auto/confirm/strict/readonly。 */
|
|
394
|
+
getMode(): string;
|
|
395
|
+
setMode(mode: string): Promise<void>;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 认证状态端口——查询 provider 凭据来源。不暴露凭据本身,只返回来源描述字符串。
|
|
399
|
+
*/
|
|
400
|
+
interface FrontendAuthPort {
|
|
401
|
+
/**
|
|
402
|
+
* 返回指定 provider 的认证来源描述——null 表示未认证。
|
|
403
|
+
* 来源值如 "environment" / "keychain" / "config" / "oauth" / "api_key"。
|
|
404
|
+
*/
|
|
405
|
+
getAuthSource(providerId: string): Promise<string | null>;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 插件多语言字符串查询端口(终局架构 review 2026-07-10 P0-3:此前只在 feishu-use
|
|
409
|
+
* `command-handler.ts` 手写 inline 类型 `{t(fullKey,locale):string}`,本次收进正式契约,
|
|
410
|
+
* 不再是"consumer 自定义临时接口")。薄封装 `App.i18nRegistry.t()`,不透出整个
|
|
411
|
+
* `PluginI18nRegistry` 类(前端不需要注册/装载能力,只需查询)。
|
|
412
|
+
*/
|
|
413
|
+
interface FrontendI18nPort {
|
|
414
|
+
t(fullKey: string, locale: string): string;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* 前端消费引擎能力的根端口。由引擎宿主侧(进程内嵌入时 = bridge 自己的装配入口)
|
|
418
|
+
* 构造并注入,前端业务代码只依赖本接口,不 import 引擎内部类型。
|
|
419
|
+
*/
|
|
420
|
+
interface FrontendPort {
|
|
421
|
+
/** 宿主实现决定复用策略,如 feishu 的 chatId 路由。 */
|
|
422
|
+
createSession(options?: {
|
|
423
|
+
agentName?: string;
|
|
424
|
+
interactive?: boolean;
|
|
425
|
+
}): Promise<FrontendSessionPort>;
|
|
426
|
+
readonly jobs: FrontendJobObserverPort;
|
|
427
|
+
readonly models: FrontendModelPort;
|
|
428
|
+
readonly permission: FrontendPermissionPort;
|
|
429
|
+
/** provider 凭据来源,不含凭据本身。 */
|
|
430
|
+
readonly auth: FrontendAuthPort;
|
|
431
|
+
/** 前端命令描述等 UI 文案按 locale 查询。 */
|
|
432
|
+
readonly i18n: FrontendI18nPort;
|
|
433
|
+
/**
|
|
434
|
+
* 能力热重载(RFC-136 D2/D3)——不重启进程认到 delegate 新加的 MCP/provider/声明式
|
|
435
|
+
* agent/skills/插件 i18n 字符串。裁剪自 `App.reloadCapabilities()` 的返回值(薄封装,
|
|
436
|
+
* 签名与引擎侧一致,消费方零感知内部编排细节)。解决长驻前端进程(如飞书桥接)模型/
|
|
437
|
+
* 权限模式/插件待授权能力/i18n 字符串多类过期状态。
|
|
438
|
+
*/
|
|
439
|
+
reloadCapabilities(): Promise<{
|
|
440
|
+
mcpTools: number;
|
|
441
|
+
models: number;
|
|
442
|
+
agents: number;
|
|
443
|
+
teams: number; /** RFC-125 D5:因待授权高危能力被跳过的 provider 插件 id 列表。 */
|
|
444
|
+
pendingProviderCapabilities: string[]; /** RFC-138 D6:本轮热重载装配的插件 i18n 字符串条目总数。 */
|
|
445
|
+
i18nEntries: number;
|
|
446
|
+
}>;
|
|
447
|
+
/** 工作区路径(纯只读展示)。 */
|
|
448
|
+
readonly workspaceDir: string;
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/constants.d.ts
|
|
452
|
+
/**
|
|
453
|
+
* 会话历史存储**条数**上限——**结构兜底闸**(RFC-321 D7 闸位重排,2026-08-07)。
|
|
454
|
+
*
|
|
455
|
+
* **不是内存治理主闸**。主闸是 `SESSION_MAX_CONTENT_BYTES`(字节维度,见下);本常量
|
|
456
|
+
* 只防"条目数爆炸"这一字节维度管不到的 CPU 问题:每回合的 token 估算 / walkBranch /
|
|
457
|
+
* cap 判定都是 O(条数) 全量遍历,同样 95MB 数据下 50000 条单次扫描 1.10ms、5000 条仅
|
|
458
|
+
* 0.06ms(18 倍差距,实测)——条数是独立于体积的成本维度,故保留,但退居兜底。
|
|
459
|
+
*
|
|
460
|
+
* **2026-08-07 由 5000 上调至 50000(RFC-321 D7)**:此前 5000 与 256MB 的临界点是
|
|
461
|
+
* 52.4KB/条(256MB÷5000),而真实消息普遍小于该值 → **条数闸几乎总是抢在字节闸之前
|
|
462
|
+
* 触发**(20KB/条时条数闸在 98MB 就裁,256MB 字节闸形同虚设),等于"用一个与内存无关的
|
|
463
|
+
* 数字做内存治理"。上调后条数闸只在真正的条目爆炸(5 万条级)时介入,内存治理交还字节闸。
|
|
464
|
+
*
|
|
465
|
+
* **与模型上下文无关**(RFC-053 规则 1,不可削弱):上下文该看多少历史只由 token 那套
|
|
466
|
+
* 决定(prune 60% + compaction 触发线,均 findCutPoint 工具对齐)。本上限只影响**内存
|
|
467
|
+
* 视图**;DB 侧 append-forever(RFC-159 D1),超限会话 resume 走尾窗口加载,窗口之前的
|
|
468
|
+
* 历史仍完整保留在 DB。
|
|
469
|
+
*
|
|
470
|
+
* **裁剪不会静默丢上下文**(RFC-321 R12):裁剪触及活跃上下文前必须先经压缩建边界,
|
|
471
|
+
* 裁掉的永远是"已被摘要覆盖的死历史";仅当压缩连续失败达阈值才放行有损裁剪,且事件
|
|
472
|
+
* 带 `lossy` 标记。历史上"条数裁剪切在工具往返中间产生孤儿 tool_result → provider 400"
|
|
473
|
+
* 的事故(commit `7e79407f`)由 `canStartHistory` 谓词防御,与本值无关。
|
|
474
|
+
*/
|
|
475
|
+
declare const SESSION_MAX_HISTORY_MESSAGES: number;
|
|
476
|
+
/**
|
|
477
|
+
* 会话消息内容字节估算预算——**回退默认值**(RFC-323 后非主闸)。
|
|
478
|
+
*
|
|
479
|
+
* **语义变更(2026-08-07,C5 事后 review 修正)**:本常量曾在 RFC-321 D7 被定为
|
|
480
|
+
* "内存治理主闸"(由 256MB 下调),但 RFC-323 的 ResidencyAllocator 已成为实际主闸
|
|
481
|
+
* (单会话预算 = clamp(TOTAL/N, 16MB, 256MB))。本常量随后被降级为**分配器缺席 / 注入
|
|
482
|
+
* 路径未接单时的回退默认值**。它与 `SESSION_MAX_PER_SESSION_BYTES`(分配器 MAX)语义
|
|
483
|
+
* 重叠且数值不同(64MB vs 256MB),是 "同一个语义槽却有两个数字" 的缺陷——故将其
|
|
484
|
+
* 重定向为回退默认,主闸语义归分配器。
|
|
485
|
+
*
|
|
486
|
+
* **仍被消费处**:`storage-wiring` 给 memory 的 `maxContentBytes` 构造期静态回退(消费点
|
|
487
|
+
* 实际经 `getMaxContentBytes` 注入后不走此处;getter 缺席时才用)、`history-capped`
|
|
488
|
+
* 事件的 capValue 语义。未改回到 256MB 原因:避免在生产中造成恢复旧的紧预算的隐性行为
|
|
489
|
+
* 变化(64MB 已有历史行为,且分配器运行时已覆盖其缺口)。
|
|
490
|
+
*
|
|
491
|
+
* 为什么主闸是"字节估算"而不是进程内存占比(`heapUsed/heapSizeLimit`)——三条实证
|
|
492
|
+
* (RFC-321 R11,沿用并强化 RFC-178 §1.3 的 grill 否决):
|
|
493
|
+
* 1. **归因**:heapUsed 是进程全局值。实测 200MB Buffer 泄漏全部落在 `external`,
|
|
494
|
+
* heapUsed 几乎无感(0.1%);即便占比升高也无法判断该裁哪个会话——按它裁会裁掉
|
|
495
|
+
* 无辜的小会话,真凶纹丝不动。字节估算逐消息累加,可归因到单会话。
|
|
496
|
+
* 2. **确定性**:同一会话状态下 heapUsed 占比 8 次采样跨度 0.09 个百分点(GC 时机
|
|
497
|
+
* 摆布),阈值落在波动区内会 flaky,突变门无法稳定断言。字节估算零波动。
|
|
498
|
+
* 3. **可测**:JS 无 sizeof;字节估算是唯一可确定计算的内存代理。
|
|
499
|
+
* heapUsed 分级仍有用武之地,但只做**阈值缩放**(RFC-178 触发器 C,`ResidencyGovernor`
|
|
500
|
+
* 按 warning/critical 把 cap 除以 2/4),**永不进入触发链**(R11)。
|
|
501
|
+
*
|
|
502
|
+
* **口径与真实驻留的关系(实测,互异内容避开 V8 字符串共享优化)**:
|
|
503
|
+
* `estimateMessageContentBytes` 计 UTF-16 code unit 数(`String.length`)。
|
|
504
|
+
* - 纯 ASCII 大消息:真实堆 ≈ 估算 × 1.00(5000 条 × 60KB = 287MB 真实 / 286MB 估算)
|
|
505
|
+
* - **CJK(本仓主形态):真实 ≈ 估算 × 2.05**(一个汉字 length=1 但占 2 字节)
|
|
506
|
+
* 故本预算 64MB 对应最坏 ~128MB 真实驻留/会话。
|
|
507
|
+
*
|
|
508
|
+
* **多会话乘数(必须一并考量)**:本预算是 **per-session**,而 `SESSION_MAX=50`
|
|
509
|
+
* (`@x-otto/session/constants.ts`)。理论极限 = 64MB × 50 ≈ 3.2GB 估算 / ~6.4GB 真实,
|
|
510
|
+
* 靠既有 idle 驱逐与满池 LRU 驱逐兜底(非本常量职责,但调整本值时不可忽略该乘数)。
|
|
511
|
+
*
|
|
512
|
+
* **64MB 的取值依据(历史)**:RFC-321 曾定为“20KB/条 ≈ 3200 条触发,行为跃变小;
|
|
513
|
+
* 4GB 堆下单会话占 ~3%;附件/输出形态能拦”。现为主闸分配器 MAX=256MB 的**回退默认**(见上)。
|
|
514
|
+
*/
|
|
515
|
+
declare const SESSION_MAX_CONTENT_BYTES: number;
|
|
516
|
+
/**
|
|
517
|
+
* 会话 resume 时的**尾窗口装载条数**(RFC-321 D7 新增,与条数 cap 解耦)。
|
|
518
|
+
*
|
|
519
|
+
* **为什么不能复用 `SESSION_MAX_HISTORY_MESSAGES`**(此前二者同源,D7 上调 cap 后暴露):
|
|
520
|
+
* cap 是"内存里最多留多少"的**结构兜底**(50000,只防条目爆炸);而 resume 尾窗口是
|
|
521
|
+
* "启动时一次性装载多少",直接决定**启动耗时与首屏驻留**。实测 50000 条 × 2KB 的一次性
|
|
522
|
+
* 装载 = **108MB 驻留 / 113ms**,对启动不可接受;5000 条量级(~11MB)才是合理的首屏预算。
|
|
523
|
+
* 二者语义不同、约束不同,故拆为独立常量——同源是历史巧合(RFC-159 D4 立项时 cap 恰好
|
|
524
|
+
* 是个"合理的窗口大小"),D7 闸位重排后该巧合不再成立。
|
|
525
|
+
*
|
|
526
|
+
* 窗口之前的历史**不丢**:DB append-forever(RFC-159 D1),可经 `/history` 分页回看
|
|
527
|
+
* (`history-pager.ts`)与 RFC-300 的 DB 补页按需加载。切点仍工具边界对齐(窗口首条非
|
|
528
|
+
* 孤儿 tool_result,防 provider 400,历史事故 commit `7e79407f`)。
|
|
529
|
+
*/
|
|
530
|
+
declare const SESSION_RESUME_WINDOW_MESSAGES: number;
|
|
531
|
+
/**
|
|
532
|
+
* RFC-324 D1/R5:turn-gated 压缩的超时兜底(毫秒,默认 30s,G1 已 grill 确认)。
|
|
533
|
+
*
|
|
534
|
+
* 逃生阀压缩 pending 时,下一回合 prompt 前会先 await 其完成;超过本值仍未完成则降级为
|
|
535
|
+
* fire-and-forget 继续发 prompt——**不允许压缩未完成就永久阻塞 turn**(R5 硬约束)。
|
|
536
|
+
*
|
|
537
|
+
* 取值权衡:30s 覆盖正常 LLM 压缩(2-5s)+ 重试余量,超时基本等价于 LLM 故障;
|
|
538
|
+
* 调小会把正常压缩误判为超时,调大则用户在 LLM 故障时等待更久。
|
|
539
|
+
*/
|
|
540
|
+
declare const SESSION_COMPACTION_TIMEOUT_MS: number;
|
|
541
|
+
/**
|
|
542
|
+
* RFC-323:进程级驻留总预算(估算口径)。
|
|
543
|
+
*
|
|
544
|
+
* 单会话的**有效**字节预算不再是一个固定值,而是 `clamp(TOTAL / N, MIN, MAX)`——
|
|
545
|
+
* 独占时可用满 MAX,并发时自动收紧,总量始终有界。
|
|
546
|
+
*
|
|
547
|
+
* **1024MB 的算术依据**(CJK 最坏比值 2.05,真实上界 = 估算 × 2.05):
|
|
548
|
+
*
|
|
549
|
+
* | TOTAL | 真实上界 | +基线 1500MB | vs 4096MB 硬顶 |
|
|
550
|
+
* |-------|---------|-------------|--------------|
|
|
551
|
+
* | 1024MB | 2099MB | 3599MB | ✅ 留 ~500MB |
|
|
552
|
+
* | 1280MB | 2624MB | 4124MB | ❌ 超顶 |
|
|
553
|
+
*
|
|
554
|
+
* 取 1024MB:极端情形(所有会话同时贴满预算)下仍留 ~500MB 余量。
|
|
555
|
+
* 基线 1500MB 是 RFC-178 实测值(healthy 时 heap 已 1.5GB/4.1GB)。
|
|
556
|
+
*/
|
|
557
|
+
declare const SESSION_TOTAL_RESIDENCY_BUDGET: number;
|
|
558
|
+
/**
|
|
559
|
+
* RFC-323:单会话字节预算下界(N 极大时不被摊薄到无法工作)。
|
|
560
|
+
*
|
|
561
|
+
* 低于此值会话会过于频繁触发压缩(每几十条消息就压一次),影响可用性。
|
|
562
|
+
* `SESSION_MAX(50) × MIN(16MB) = 800MB < TOTAL(1024MB)`,故实际不可达。
|
|
563
|
+
*/
|
|
564
|
+
declare const SESSION_MIN_PER_SESSION_BYTES: number;
|
|
565
|
+
/**
|
|
566
|
+
* RFC-323:单会话字节预算上界(N=1 时的封顶)。
|
|
567
|
+
*
|
|
568
|
+
* 受 RFC-321 底线2 约束:压缩输入须 ≤ 模型输入窗口。200K 窗口下上界约 214MB
|
|
569
|
+
* (图片形态),256MB 是留了余量的封顶。1M 窗口模型可放宽到 512MB,但需同步
|
|
570
|
+
* 验证压缩输入不超窗(M6 的 map-reduce 会兜底,但 coverage 会更低)。
|
|
571
|
+
*/
|
|
572
|
+
declare const SESSION_MAX_PER_SESSION_BYTES: number;
|
|
573
|
+
/**
|
|
574
|
+
* RFC-323 M4:驻留预算的运行时配置对象。
|
|
575
|
+
*
|
|
576
|
+
* 存在理由(D6b):上方常量在模块加载时立即求值为 number(env 快照),无法反映
|
|
577
|
+
* app 启动后才 load 的 settings.json。故运行时读值点改从本对象读——由 app 层合并
|
|
578
|
+
* `env(上方常量)> settings > 默认` 后注入。所有字段可选,缺省回退对应常量。
|
|
579
|
+
*/
|
|
580
|
+
interface ResidencyBudgetConfig {
|
|
581
|
+
totalBytes?: number;
|
|
582
|
+
minPerSessionBytes?: number;
|
|
583
|
+
maxPerSessionBytes?: number;
|
|
584
|
+
maxContentBytes?: number;
|
|
585
|
+
maxHistoryMessages?: number;
|
|
586
|
+
resumeWindowMessages?: number;
|
|
587
|
+
/**
|
|
588
|
+
* RFC-324 D1/R5:turn-gated 压缩的超时兜底(毫秒)。逃生阀压缩超过此值仍未完成时,
|
|
589
|
+
* 降级为 fire-and-forget——**不允许压缩未完成就永久阻塞 turn**。
|
|
590
|
+
*/
|
|
591
|
+
compactionTimeoutMs?: number;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* RFC-323 M4:合并 settings 覆盖到 env/默认常量之上,产出完整的 ResidencyBudgetConfig。
|
|
595
|
+
*
|
|
596
|
+
* 优先级:**env(上方常量,已含 env 覆盖)> settings > 默认**。
|
|
597
|
+
* 实现:常量本身已是 `normalizeEnv(env, 默认)` 的结果——若用户设了 env,常量即为 env 值;
|
|
598
|
+
* 否则为默认值。settings 只在"用户**没设 env**"时生效——但模块层无法区分"常量是 env 值
|
|
599
|
+
* 还是默认值"。故约定:**settings 值优先于常量默认,但 env 显式设置时由调用方保证不传
|
|
600
|
+
* settings**(见 app 层 `resolveResidencySettingsOverride`:仅当对应 env 未设时才取 settings)。
|
|
601
|
+
*/
|
|
602
|
+
declare function resolveResidencyBudget(settings?: ResidencyBudgetConfig): Required<ResidencyBudgetConfig>;
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/pulse-survey-trigger.d.ts
|
|
605
|
+
/**
|
|
606
|
+
* pulse-survey-trigger.ts — RFC-208 §2.1/§3.1:满意度脉冲调查触发判定纯函数。
|
|
607
|
+
* RFC-209 M3:从 packages/cli 下沉至此(cli 与 service 均已依赖 @x-otto/session-contract,
|
|
608
|
+
* 而 service 不应依赖 cli——EventBridge 需要复用本函数做 web-ui 侧 `pulse-survey.show`
|
|
609
|
+
* 触发判定,见 RFC-209 milestone 执行阶段 grill 确认)。
|
|
610
|
+
*
|
|
611
|
+
* 纯逻辑类,不依赖 @x-otto/tui/@x-otto/coding 类型——判定「本次 prompt.end 是否应展示脉冲调查」,
|
|
612
|
+
* 实际展示(tui.setPulseSurvey / web-ui pulse-survey.show)与数据写入(FeedbackRegistry)
|
|
613
|
+
* 留在调用方(cli 侧 interactive-events.ts / service 侧 event-bridge.ts)。
|
|
614
|
+
*
|
|
615
|
+
* 触发锚点是 prompt.end(对话轮次真正结束的权威时点),按可配置概率随机抽样而非固定周期——
|
|
616
|
+
* 固定周期容易被用户学习产生"来了、又是这个"的疲劳感;抽样配合最小回合间隔更接近"偶尔冒出,
|
|
617
|
+
* 不成为负担"的调性。判定逻辑必须是纯函数、接受注入的 `random` 参数(RFC-208 重要事项规则 5)
|
|
618
|
+
* ——不得在函数内部直接硬编码调用 `Math.random()`,保证可测试性(CLI 交互层直接用 Math.random
|
|
619
|
+
* 有先例,见 draft-entries.ts,但触发判定这类需要精确验证边界值的逻辑必须可注入固定值测试)。
|
|
620
|
+
*
|
|
621
|
+
* `lastShownTurn` 是进程内存态,不持久化跨会话——重启 otto 后重新从零计间隔,符合"轻量抽样,
|
|
622
|
+
* 不需要跨会话精确统计"的调性,避免为此引入额外的持久化状态文件。
|
|
623
|
+
*/
|
|
624
|
+
interface PulseSurveyTriggerState {
|
|
625
|
+
lastShownTurn?: number;
|
|
626
|
+
}
|
|
627
|
+
interface PulseSurveyTriggerConfig {
|
|
628
|
+
enabled: boolean;
|
|
629
|
+
probability: number;
|
|
630
|
+
minTurnGap: number;
|
|
631
|
+
/**
|
|
632
|
+
* 当前是否有 Band A hard modal(如 forcedUpgrade)在场——为真时恒不触发(RFC-208 §5
|
|
633
|
+
* 风险段"forced-upgrade 并发"缓解措施之一)。调用方传入 `modals.forcedUpgrade` 等值。
|
|
634
|
+
*/
|
|
635
|
+
forcedUpgradeActive: boolean;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* 判定本次 `prompt.end` 是否应展示满意度脉冲调查。
|
|
639
|
+
*
|
|
640
|
+
* @param currentTurn 当前对话轮次(`projState.currentTurn`)
|
|
641
|
+
* @param config 触发配置(来自 `.otto/config.json` 的 `pulse_survey` 块 + 当前 Band A 状态)
|
|
642
|
+
* @param state 触发状态(`lastShownTurn` 进程内存态,调用方持有并在命中时更新)
|
|
643
|
+
* @param random 随机数源,默认 `Math.random`;测试注入固定值验证确定性
|
|
644
|
+
*/
|
|
645
|
+
declare function shouldShowPulseSurvey(currentTurn: number, config: PulseSurveyTriggerConfig, state: PulseSurveyTriggerState, random?: () => number): boolean;
|
|
646
|
+
//#endregion
|
|
647
|
+
export { AskAnswer, AskQuestionLike, DEFAULT_DELEGATION_TOOLS, DelegatedArg, DelegatedTask, FrontendAuthPort, FrontendI18nPort, FrontendJobObserverPort, FrontendJobView, FrontendModelPort, FrontendPermissionPort, FrontendPort, FrontendPromptImage, FrontendSessionModel, FrontendSessionPort, ModelEntry, ProjectCtx, ProjectorState, PulseSurveyTriggerConfig, PulseSurveyTriggerState, ResidencyBudgetConfig, SESSION_COMPACTION_TIMEOUT_MS, SESSION_MAX_CONTENT_BYTES, SESSION_MAX_HISTORY_MESSAGES, SESSION_MAX_PER_SESSION_BYTES, SESSION_MIN_PER_SESSION_BYTES, SESSION_RESUME_WINDOW_MESSAGES, SESSION_TOTAL_RESIDENCY_BUDGET, SessionCommand, SessionEvent, SessionEventKind, SourceQuestion, SourceToolCall, SubmitIntent, TaskItem, TaskItemArg, type TodoStatus, TurnRecord, autoAdoptAnswer, createProjectorState, projectSessionEvent, resolveChoiceAnswer, resolveResidencyBudget, resolveTextAnswer, sandboxBypassCommand, shouldShowPulseSurvey };
|
|
648
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/project.ts","../src/requests.ts","../src/frontend-port.ts","../src/constants.ts","../src/pulse-survey-trigger.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{normalizeEnv as e}from"@x-otto/env";function t(){return{delegated:new Map,todoList:[],currentTurn:0}}const n=new Set([`task_delegate`,`team_run`,`fork_call`]);function r(e,t){if(t.tracked===!1||(typeof t.mode==`string`?t.mode:`sync`)!==`sync`)return!1;let n=Array.isArray(t.taskIds)?t.taskIds.filter(e=>typeof e==`string`):void 0,r;if(n&&n.length>0)r=n.filter(t=>e.todoList.some(e=>e.id===t&&e.status!==`done`));else{let t=e.todoList.filter(e=>e.status===`in_progress`);r=t.length===1?[t[0].id]:[]}return r.length===0?!1:(e.todoList=e.todoList.map(e=>e.id!=null&&r.includes(e.id)?{...e,status:`done`}:e),!0)}function i(e,t,i={}){let o=e.sessionId,s=i.isDelegationTool??(e=>n.has(e));switch(e.type){case`prompt.start`:return t.currentTurn++,[{sessionId:o,kind:`prompt.start`,text:e.text}];case`prompt.end`:{t.delegated.clear();let n=i.cacheHitRatio?.();return[{sessionId:o,kind:`delegate.update`,tasks:[]},{sessionId:o,kind:`prompt.end`,model:e.model??i.currentModel?.()??``,tokens:i.turnTokens?.(),cacheHitRatio:n??void 0,effort:e.thinkingLevel,stopReason:e.stopReason}]}case`turn.start`:return[{sessionId:o,kind:`turn.budget`,toolTurnCount:e.toolTurnCount,maxToolTurns:e.maxToolTurns,outputTokens:e.promptOutputTokens,outputTokenBudget:e.promptOutputTokenBudget}];case`stream.event`:{let t=e.event;switch(t.type){case`text_delta`:return[{sessionId:o,kind:`stream.text.delta`,delta:t.delta??``}];case`thinking_delta`:return[{sessionId:o,kind:`stream.thinking.delta`,delta:t.delta??``}];case`text_start`:return[{sessionId:o,kind:`stream.text.start`}];case`text_end`:return[{sessionId:o,kind:`stream.text.end`}];case`thinking_start`:return[{sessionId:o,kind:`stream.thinking.start`}];case`thinking_end`:return[{sessionId:o,kind:`stream.thinking.end`}];case`error`:return[{sessionId:o,kind:`error`,message:a(t.error),code:`STREAM_ERROR`}];default:return[]}}case`tool.call.start`:{let n=e.toolCall,r=[{sessionId:o,kind:`tool.start`,call:n}];if(s(n.name)){let e=n.arguments??{},i=e.subagent??e.agent??e.category??e.team,a=typeof e.prompt==`string`?e.prompt.replace(/\n/g,` `).slice(0,48):void 0;t.delegated.set(n.id,{id:n.id,name:typeof i==`string`?i:n.name,detail:a,status:`running`}),r.push({sessionId:o,kind:`delegate.update`,tasks:[...t.delegated.values()]})}return r}case`tool.call.end`:{let{toolCall:n,isError:a}=e,s=n.arguments??{},c=t=>({sessionId:o,kind:`tool.end`,call:n,isError:a,resultText:e.resultText??i.toolResultText?.(n),resultTokensEstimate:e.resultTokensEstimate,suppressMessage:t});if(n.name===`write_todos`&&Array.isArray(s.todos)){let e=s.todos;if(s.action===`update`)for(let n of e){let e=t.todoList.findIndex(e=>e.id===n.id);e>=0?t.todoList[e]={...t.todoList[e],...n,turn:t.currentTurn}:t.todoList.push({...n,turn:t.currentTurn})}else t.todoList=e.map(e=>({...e,turn:t.currentTurn}));return[c(!0),{sessionId:o,kind:`tasks.update`,tasks:[...t.todoList],title:s.title}]}if(t.delegated.has(n.id)){let e=t.delegated.get(n.id);t.delegated.set(n.id,{...e,status:a?`error`:`done`});let i=[{sessionId:o,kind:`delegate.update`,tasks:[...t.delegated.values()]},c()];return n.name===`task_delegate`&&!a&&r(t,s)&&i.push({sessionId:o,kind:`tasks.update`,tasks:[...t.todoList]}),i}return[c()]}case`session.start`:return[{sessionId:o,kind:`session.activity`,action:`created`}];case`session.end`:return[{sessionId:o,kind:`session.activity`,action:`ended`}];case`streaming.start`:return[{sessionId:o,kind:`session.status`,status:`streaming`,model:e.model}];case`streaming.end`:{let t=e;return[{sessionId:o,kind:`session.status`,status:`idle`,model:t.model,stopReason:t.stopReason}]}case`compaction.start`:return[{sessionId:o,kind:`session.status`,status:`compacting`,messageCount:e.messageCount}];case`compaction.end`:return[{sessionId:o,kind:`session.status`,status:`idle`,retainedCount:e.retainedCount}];case`approval.required`:{let t=e;return[{sessionId:o,kind:`approval.required`,id:t.id,toolName:t.toolName,arguments:t.arguments,description:t.description,risk:t.risk}]}case`approval.resolved`:{let t=e;return[{sessionId:o,kind:`approval.resolved`,id:t.id,approved:t.approved}]}case`ask.user.required`:{let t=e;return[{sessionId:o,kind:`ask.required`,requestId:t.requestId,question:t.question}]}case`ask.user.resolved`:return[{sessionId:o,kind:`ask.resolved`,requestId:e.requestId}];case`plan.approval.required`:{let t=e;return[{sessionId:o,kind:`plan.approval.required`,requestId:t.requestId,planContent:t.planContent}]}case`plan.approval.resolved`:{let t=e;return[{sessionId:o,kind:`plan.approval.resolved`,requestId:t.requestId,action:t.action}]}case`error`:return[{sessionId:o,kind:`error`,message:a(e.error),code:`SESSION_ERROR`}];case`prompt.continued`:case`messages.persist`:case`agent.status`:case`budget.notice`:case`prompt.budget.notice`:case`progress.stall.notice`:case`tool.call.mismatch.notice`:case`session.cost-budget-exceeded`:case`turn.end`:case`memory.pruned`:case`session.cleared`:case`run.paused`:case`run.resumed`:case`run.pause_requested`:case`run.pause_cancelled`:case`session.history-capped`:case`session.write-lease-denied`:case`session.write-lease-restored`:case`session.crash-gap-detected`:case`image.vision-delegate`:case`a2ui.action`:case`compaction.reactive.notice`:case`image.degradation.notice`:case`steer.consumed`:return[];default:return[]}}function a(e){return e instanceof Error?e.message:typeof e==`string`?e:`unknown error`}function o(e){return{answer:e.recommendation,acceptedRecommendation:!0,resolvedBy:`auto_recommendation`}}function s(e,t,n){if(t.length===0&&!n)return o(e);let r=(e.options??[]).filter(e=>e.recommended).map(e=>e.label),i=r.length>0&&t.length===r.length&&t.every(e=>r.includes(e)),a=t.length>0?t.join(`, `):``;return{answer:n?a?`${a}\n\n补充信息:${n}`:n:a,selectedOptionLabels:t.length>0?t:void 0,acceptedRecommendation:i,resolvedBy:`human`,freeformText:n}}function c(e,t){let n=t?.trim();return n?{answer:n,acceptedRecommendation:n===e.recommendation,resolvedBy:`human`}:o(e)}function l(e,t,n){return typeof e!=`string`||!e.includes(n)?null:(typeof t?.command==`string`?t.command:``)||null}const u=e(process.env.OTTO_SESSION_MAX_HISTORY_MESSAGES,5e4),d=e(process.env.OTTO_SESSION_MAX_CONTENT_BYTES,64*1024*1024),f=e(process.env.OTTO_SESSION_RESUME_WINDOW_MESSAGES,5e3),p=e(process.env.OTTO_SESSION_COMPACTION_TIMEOUT_MS,3e4),m=e(process.env.OTTO_SESSION_TOTAL_RESIDENCY_BUDGET,1024*1024*1024),h=e(process.env.OTTO_SESSION_MIN_PER_SESSION_BYTES,16*1024*1024),g=e(process.env.OTTO_SESSION_MAX_PER_SESSION_BYTES,256*1024*1024);function _(e){return{totalBytes:e?.totalBytes??m,minPerSessionBytes:e?.minPerSessionBytes??h,maxPerSessionBytes:e?.maxPerSessionBytes??g,maxContentBytes:e?.maxContentBytes??d,maxHistoryMessages:e?.maxHistoryMessages??u,resumeWindowMessages:e?.resumeWindowMessages??f,compactionTimeoutMs:e?.compactionTimeoutMs??p}}function v(e,t,n,r=Math.random){return!t.enabled||t.forcedUpgradeActive||n.lastShownTurn!==void 0&&e-n.lastShownTurn<t.minTurnGap?!1:r()<t.probability}export{n as DEFAULT_DELEGATION_TOOLS,p as SESSION_COMPACTION_TIMEOUT_MS,d as SESSION_MAX_CONTENT_BYTES,u as SESSION_MAX_HISTORY_MESSAGES,g as SESSION_MAX_PER_SESSION_BYTES,h as SESSION_MIN_PER_SESSION_BYTES,f as SESSION_RESUME_WINDOW_MESSAGES,m as SESSION_TOTAL_RESIDENCY_BUDGET,o as autoAdoptAnswer,t as createProjectorState,i as projectSessionEvent,s as resolveChoiceAnswer,_ as resolveResidencyBudget,c as resolveTextAnswer,l as sandboxBypassCommand,v as shouldShowPulseSurvey};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/project.ts","../src/requests.ts","../src/constants.ts","../src/pulse-survey-trigger.ts"],"sourcesContent":["/**\n * project.ts — 投影器:AgentSessionEvent/StreamEvent 子集 → SessionEvent[](前端无关事实)。\n *\n * 忠实复刻 cli.ts `session.subscribe` 同步部分。**投影器持有集合累加器**(delegated Map /\n * todoList),发 snapshot —— 合并逻辑只此一份,不让消费端各抄一遍。流式累加归 reducer(消费端)。\n */\nimport type { AgentSessionEvent } from '@x-otto/interchange'\n\nimport type {\n SessionEvent,\n ProjectCtx,\n TaskItem,\n DelegatedTask,\n DelegatedArg,\n} from './types'\n\n// 直接用 ./types 的 canonical TaskItem/DelegatedTask 作累加器元素类型,\n// 取代本地结构同形的重声明(Todo/DelegatedEntry)——后者逼出 `as unknown as DelegatedArg/TaskItemArg`\n// 桥接「同形异名」。统一后 `[...values()]` 直接是 DelegatedArg/TaskItemArg,casts 全消。\nexport interface ProjectorState {\n delegated: Map<string, DelegatedTask>\n todoList: TaskItem[]\n /** 当前回合编号(1-based)。prompt.start 时自增。 */\n currentTurn: number\n}\nexport function createProjectorState(): ProjectorState {\n return { delegated: new Map(), todoList: [], currentTurn: 0 }\n}\n\nexport const DEFAULT_DELEGATION_TOOLS = new Set(['task_delegate', 'team_run', 'fork_call'])\n\n/**\n * 子代理完成后的 todo 自动同步——宿主侧规则的投影镜像(真源:tools/orchestration/\n * dispatched-task.ts `syncTodosOnSubagentComplete`,两处规则必须一致):\n * - 仅 sync 模式且 tracked !== false(background 终态未知;fork/team 走旁路不经宿主同步;\n * untracked 走 callAgent 旁路也不经——RFC-302 D8);\n * - taskIds 显式指定 → 只标这些未 done 的项为 done;\n * - 未指定且恰好一个 in_progress → 标它 done;\n * - 其余(0 或多 in_progress)不同步。\n * 返回是否发生了变更(据此决定是否发 tasks.update)。\n */\nfunction applyDelegateTodoSync(state: ProjectorState, args: Record<string, unknown>): boolean {\n // RFC-302 D8:untracked(tracked:false)走 callAgent 旁路,宿主侧不经\n // syncTodosOnSubagentComplete——镜像必须同门,否则 TUI/session metadata 误标 done。\n if (args.tracked === false) return false\n const mode = typeof args.mode === 'string' ? args.mode : 'sync'\n if (mode !== 'sync') return false\n\n const taskIds = Array.isArray(args.taskIds)\n ? (args.taskIds as unknown[]).filter((x): x is string => typeof x === 'string')\n : undefined\n\n let targetIds: string[]\n if (taskIds && taskIds.length > 0) {\n targetIds = taskIds.filter((id) =>\n state.todoList.some((t) => t.id === id && t.status !== 'done'),\n )\n } else {\n const inProgress = state.todoList.filter((t) => t.status === 'in_progress')\n targetIds = inProgress.length === 1 ? [inProgress[0]!.id!] : []\n }\n if (targetIds.length === 0) return false\n\n state.todoList = state.todoList.map((t) =>\n t.id != null && targetIds.includes(t.id) ? { ...t, status: 'done' as const } : t,\n )\n return true\n}\n\nexport function projectSessionEvent(\n event: AgentSessionEvent,\n state: ProjectorState,\n ctx: ProjectCtx = {},\n): SessionEvent[] {\n const sessionId = event.sessionId\n const isDelegation = ctx.isDelegationTool ?? ((n: string) => DEFAULT_DELEGATION_TOOLS.has(n))\n\n switch (event.type) {\n case 'prompt.start':\n state.currentTurn++\n return [\n {\n sessionId,\n kind: 'prompt.start',\n text: event.text,\n },\n ]\n\n case 'prompt.end': {\n state.delegated.clear()\n const ratio = ctx.cacheHitRatio?.()\n return [\n { sessionId, kind: 'delegate.update', tasks: [] satisfies DelegatedArg },\n {\n sessionId,\n kind: 'prompt.end',\n // 优先用事件自带的模型快照(AgentSession 在 prompt() 入口捕获的\"这一轮实际执行模型\");\n // 缺省(旧事件/未来其它来源)时兜底走 ctx.currentModel() 惰性读取活跃 session.model。\n model: event.model ?? ctx.currentModel?.() ?? '',\n tokens: ctx.turnTokens?.(),\n cacheHitRatio: ratio == null ? undefined : ratio,\n effort: event.thinkingLevel,\n stopReason: event.stopReason,\n },\n ]\n }\n\n case 'turn.start':\n return [\n {\n sessionId,\n kind: 'turn.budget',\n toolTurnCount: event.toolTurnCount,\n maxToolTurns: event.maxToolTurns,\n outputTokens: event.promptOutputTokens,\n outputTokenBudget: event.promptOutputTokenBudget,\n },\n ]\n\n case 'stream.event': {\n const e = event.event\n switch (e.type) {\n case 'text_delta':\n return [{ sessionId, kind: 'stream.text.delta', delta: e.delta ?? '' }]\n case 'thinking_delta':\n return [{ sessionId, kind: 'stream.thinking.delta', delta: e.delta ?? '' }]\n case 'text_start':\n return [{ sessionId, kind: 'stream.text.start' }]\n case 'text_end':\n return [{ sessionId, kind: 'stream.text.end' }]\n case 'thinking_start':\n return [{ sessionId, kind: 'stream.thinking.start' }]\n case 'thinking_end':\n return [{ sessionId, kind: 'stream.thinking.end' }]\n case 'error':\n return [{ sessionId, kind: 'error', message: errMessage(e.error), code: 'STREAM_ERROR' }]\n default:\n return []\n }\n }\n\n case 'tool.call.start': {\n const call = event.toolCall\n const out: SessionEvent[] = [{ sessionId, kind: 'tool.start', call }]\n if (isDelegation(call.name)) {\n const a = call.arguments ?? {}\n const label = a.subagent ?? a.agent ?? a.category ?? a.team\n const detail =\n typeof a.prompt === 'string' ? a.prompt.replace(/\\n/g, ' ').slice(0, 48) : undefined\n state.delegated.set(call.id, {\n id: call.id,\n name: typeof label === 'string' ? label : call.name,\n detail,\n status: 'running',\n })\n out.push({\n sessionId,\n kind: 'delegate.update',\n tasks: [...state.delegated.values()],\n })\n }\n return out\n }\n\n case 'tool.call.end': {\n const { toolCall: call, isError } = event\n const args = call.arguments ?? {}\n const mkToolEnd = (suppressMessage?: boolean): SessionEvent => ({\n sessionId,\n kind: 'tool.end',\n call,\n isError,\n // 残影修复(2026-07):优先用事件自带的 resultText(按本次 toolCall 精确对应,\n // AgentSession.onToolCallEnd 在产生处构造)。ctx.toolResultText 仅作旧数据兼容\n // 回退(如 replay 老 trace 无该字段的场景)——它从 session.messages() 倒查\"最后\n // 一条 tool_result\",在并行/异步工具场景下会与当前 call 错配(残影根因),新代码\n // 路径不应再依赖它。\n resultText: event.resultText ?? ctx.toolResultText?.(call),\n resultTokensEstimate: event.resultTokensEstimate,\n suppressMessage,\n })\n\n if (call.name === 'write_todos' && Array.isArray(args.todos)) {\n // 边界:write_todos 工具实参是未定型 wire 数据;此处单点定型为 TaskItem[]。\n const incoming = args.todos as TaskItem[]\n if (args.action === 'update') {\n for (const t of incoming) {\n const i = state.todoList.findIndex((x) => x.id === t.id)\n if (i >= 0) state.todoList[i] = { ...state.todoList[i], ...t, turn: state.currentTurn }\n else state.todoList.push({ ...t, turn: state.currentTurn })\n }\n } else {\n state.todoList = incoming.map((t) => ({ ...t, turn: state.currentTurn }))\n }\n return [\n mkToolEnd(true),\n {\n sessionId,\n kind: 'tasks.update',\n tasks: [...state.todoList],\n title: args.title as string | undefined,\n },\n ]\n }\n\n if (state.delegated.has(call.id)) {\n const task = state.delegated.get(call.id)!\n state.delegated.set(call.id, { ...task, status: isError ? 'error' : 'done' })\n const out: SessionEvent[] = [\n {\n sessionId,\n kind: 'delegate.update',\n tasks: [...state.delegated.values()],\n },\n mkToolEnd(),\n ]\n // 镜像宿主的子代理 todo 自动同步(tools/orchestration/dispatched-task.ts\n // syncTodosOnSubagentComplete):该同步直调 orchestrator.writeTodos、不产生\n // write_todos 工具事件,若不在此镜像,TUI 面板与 session metadata 永远收不到\n // (泄漏根因)。规则两处必须一致:仅 sync 模式 + 非错误;taskIds 显式 → 标这些项\n // done;未指定且恰好一个 in_progress → 标它 done;其余不动。\n if (call.name === 'task_delegate' && !isError) {\n const synced = applyDelegateTodoSync(state, args)\n if (synced) {\n out.push({ sessionId, kind: 'tasks.update', tasks: [...state.todoList] })\n }\n }\n return out\n }\n\n return [mkToolEnd()]\n }\n\n // —— 会话生命周期 / 状态 ——\n case 'session.start':\n return [{ sessionId, kind: 'session.activity', action: 'created' }]\n case 'session.end':\n return [{ sessionId, kind: 'session.activity', action: 'ended' }]\n case 'streaming.start': {\n const e = event\n return [{ sessionId, kind: 'session.status', status: 'streaming', model: e.model }]\n }\n case 'streaming.end': {\n const e = event\n return [\n { sessionId, kind: 'session.status', status: 'idle', model: e.model, stopReason: e.stopReason },\n ]\n }\n case 'compaction.start': {\n const e = event\n return [{ sessionId, kind: 'session.status', status: 'compacting', messageCount: e.messageCount }]\n }\n case 'compaction.end': {\n const e = event\n return [{ sessionId, kind: 'session.status', status: 'idle', retainedCount: e.retainedCount }]\n }\n\n // —— HITL(审批 / 问询 / 计划批准)——\n case 'approval.required': {\n const e = event\n return [\n {\n sessionId,\n kind: 'approval.required',\n id: e.id,\n toolName: e.toolName,\n arguments: e.arguments,\n description: e.description,\n risk: e.risk,\n },\n ]\n }\n case 'approval.resolved': {\n const e = event\n return [{ sessionId, kind: 'approval.resolved', id: e.id, approved: e.approved }]\n }\n case 'ask.user.required': {\n const e = event\n return [{ sessionId, kind: 'ask.required', requestId: e.requestId, question: e.question }]\n }\n case 'ask.user.resolved': {\n const e = event\n return [{ sessionId, kind: 'ask.resolved', requestId: e.requestId }]\n }\n case 'plan.approval.required': {\n const e = event\n return [\n { sessionId, kind: 'plan.approval.required', requestId: e.requestId, planContent: e.planContent },\n ]\n }\n case 'plan.approval.resolved': {\n const e = event\n return [{ sessionId, kind: 'plan.approval.resolved', requestId: e.requestId, action: e.action }]\n }\n\n // —— 错误 ——\n case 'error': {\n const e = event\n return [{ sessionId, kind: 'error', message: errMessage(e.error), code: 'SESSION_ERROR' }]\n }\n\n // —— 有意不投影的事件(终局审查 2026-07-18 S1:显式列出 + never 穷举守卫)——\n // 此前这些类型静默落入 `default: return []`——问题是新增 AgentSessionEvent 成员时\n // 编译器不会提示投影器漏了处理(静默丢弃,前端永远看不到)。显式空 case + default\n // never 断言后:interchange 新增事件类型 → 此 switch 编译失败 → 强制显式决策\n // (投影 or 声明不投影),\"漏投影\"从运行时静默 bug 变为编译期错误。\n case 'prompt.continued': // TUI 经 AgentSessionEvent 直接消费(todo 闸门续跑提示),无 SessionEvent 投影\n case 'messages.persist': // 持久化内部信号,非用户可见事实\n case 'agent.status': // 引擎状态机内部迁移,前端消费 streaming.start/end 粒度即可\n case 'budget.notice': // TUI 经原始事件直接消费(预算提示条)\n case 'prompt.budget.notice': // 同上\n case 'progress.stall.notice': // RFC-203:早期停滞检出提示,TUI 经原始事件直接消费(同 budget.notice 纹理)\n case 'tool.call.mismatch.notice': // RFC-232:工具调用协议违约提示,TUI 经原始事件直接消费(同上纹理)\n case 'session.cost-budget-exceeded': // RFC-204:会话成本预算提醒,TUI 经原始事件直接消费(同上纹理)\n case 'turn.end': // turn.start 已携带回合边界信息,end 无增量事实\n case 'memory.pruned': // 裁剪可观测性走 trace,非会话转录事实\n case 'session.cleared': // clear 由发起端(命令层)自行反馈,投影层无需重复\n case 'run.paused': // 暂停/恢复由 TUI 经原始事件消费(RFC-180)\n case 'run.resumed':\n case 'run.pause_requested': // 暂停请求/取消同上(RFC-186 D2/D4 事件拆分;feae2243 加事件时漏补本穷举)\n case 'run.pause_cancelled':\n case 'session.history-capped': // 驻留裁剪可观测性由 TUI/trace 经原始事件消费(RFC-178)\n case 'session.write-lease-denied': // 写租约状态由 TUI 经原始事件 toast(RFC-159 D3)\n case 'session.write-lease-restored':\n case 'session.crash-gap-detected': // 崩溃丢失对账告警由 TUI 经原始事件 toast(RFC-305 D3)\n case 'image.vision-delegate': // 视觉委托统计由 TUI 经原始事件消费(RFC-181)\n case 'a2ui.action': // A2UI 组件动作由面板宿主经原始事件消费(RFC-105 D6)\n case 'compaction.reactive.notice': // 反应式压缩由 TUI 经原始事件消费(终局 review 2026-07-21,复用 compact_boundary/compactionLog)\n case 'image.degradation.notice': // 图片降级兜底由 TUI 经原始事件消费 toast(终局 review 2026-07-21)\n case 'steer.consumed': // RFC-337 D3:steer 待定回显转正信号,TUI 经原始事件直接消费(commitPendingSteer);web/service 前端无 pendingSteer UI 概念,无投影\n return []\n\n default: {\n // 编译期穷举守卫:event 在所有显式 case 之后应为 never。新增事件类型漏写 case 时\n // 这行赋值报 TS2322,指向本文件强制处理。\n const _exhaustive: never = event\n void _exhaustive\n return []\n }\n }\n}\n\nfunction errMessage(err: unknown): string {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n return 'unknown error'\n}\n","/**\n * requests.ts — 请求子通道的**纯决策逻辑**(ask_user 自动采纳 / approval 沙箱升级)。\n *\n * cli 的异步处理器(session.subscribe 的 ask.user.required / approval.required)仍持控制流\n * (await UI + session.resolve*),但把「该解析成什么答案」「要不要 bypass」这两段易错的安全关键\n * 决策下沉到这些纯函数 —— 可单测、对齐「取消/留空绝不悬挂工具,自动采纳推荐」纪律。\n */\n\nexport interface AskQuestionLike {\n recommendation: string\n options?: { label: string; recommended?: boolean }[]\n}\n\nexport interface AskAnswer {\n answer: string\n selectedOptionLabels?: string[]\n acceptedRecommendation: boolean\n resolvedBy: 'human' | 'auto_recommendation'\n /** grill_me allowFreeform:用户在选项之外输入的补充文字。 */\n freeformText?: string\n}\n\n/** 取消/留空 → 自动采纳推荐(resolvedBy=auto_recommendation;绝不悬挂工具)。 */\nexport function autoAdoptAnswer(q: AskQuestionLike): AskAnswer {\n return { answer: q.recommendation, acceptedRecommendation: true, resolvedBy: 'auto_recommendation' }\n}\n\n/** 单/多选题+可选自由文本:据用户已选 label(调用方须先去 ★ 装饰)解析。空选 → 自动采纳。\n * acceptedRecommendation:当且仅当选中集恰等于推荐集(非空)。 */\nexport function resolveChoiceAnswer(q: AskQuestionLike, selected: string[], freeformText?: string): AskAnswer {\n if (selected.length === 0 && !freeformText) return autoAdoptAnswer(q)\n const recommended = (q.options ?? []).filter((o) => o.recommended).map((o) => o.label)\n const accepted =\n recommended.length > 0 &&\n selected.length === recommended.length &&\n selected.every((l) => recommended.includes(l))\n const answer = selected.length > 0 ? selected.join(', ') : ''\n const fullAnswer = freeformText ? (answer ? `${answer}\\n\\n补充信息:${freeformText}` : freeformText) : answer\n return { answer: fullAnswer, selectedOptionLabels: selected.length > 0 ? selected : undefined, acceptedRecommendation: accepted, resolvedBy: 'human', freeformText }\n}\n\n/** 自由文本题:空/取消 → 自动采纳;否则人答(文本等于推荐才算采纳)。 */\nexport function resolveTextAnswer(q: AskQuestionLike, text: string | null | undefined): AskAnswer {\n const t = text?.trim()\n if (!t) return autoAdoptAnswer(q)\n return { answer: t, acceptedRecommendation: t === q.recommendation, resolvedBy: 'human' }\n}\n\n/** 沙箱升级 approval:描述带 marker 且参数含 command → 返回需 bypass 的命令,否则 null。\n * 调用方仅在「已批准」时据返回值放行(早于 resolveApproval,approval=escalated 语义)。 */\nexport function sandboxBypassCommand(\n description: unknown,\n args: Record<string, unknown> | undefined,\n marker: string,\n): string | null {\n if (typeof description !== 'string' || !description.includes(marker)) return null\n const cmd = typeof args?.command === 'string' ? args.command : ''\n return cmd || null\n}\n","import { normalizeEnv } from '@x-otto/env'\n\n/**\n * 会话历史存储**条数**上限——**结构兜底闸**(RFC-321 D7 闸位重排,2026-08-07)。\n *\n * **不是内存治理主闸**。主闸是 `SESSION_MAX_CONTENT_BYTES`(字节维度,见下);本常量\n * 只防\"条目数爆炸\"这一字节维度管不到的 CPU 问题:每回合的 token 估算 / walkBranch /\n * cap 判定都是 O(条数) 全量遍历,同样 95MB 数据下 50000 条单次扫描 1.10ms、5000 条仅\n * 0.06ms(18 倍差距,实测)——条数是独立于体积的成本维度,故保留,但退居兜底。\n *\n * **2026-08-07 由 5000 上调至 50000(RFC-321 D7)**:此前 5000 与 256MB 的临界点是\n * 52.4KB/条(256MB÷5000),而真实消息普遍小于该值 → **条数闸几乎总是抢在字节闸之前\n * 触发**(20KB/条时条数闸在 98MB 就裁,256MB 字节闸形同虚设),等于\"用一个与内存无关的\n * 数字做内存治理\"。上调后条数闸只在真正的条目爆炸(5 万条级)时介入,内存治理交还字节闸。\n *\n * **与模型上下文无关**(RFC-053 规则 1,不可削弱):上下文该看多少历史只由 token 那套\n * 决定(prune 60% + compaction 触发线,均 findCutPoint 工具对齐)。本上限只影响**内存\n * 视图**;DB 侧 append-forever(RFC-159 D1),超限会话 resume 走尾窗口加载,窗口之前的\n * 历史仍完整保留在 DB。\n *\n * **裁剪不会静默丢上下文**(RFC-321 R12):裁剪触及活跃上下文前必须先经压缩建边界,\n * 裁掉的永远是\"已被摘要覆盖的死历史\";仅当压缩连续失败达阈值才放行有损裁剪,且事件\n * 带 `lossy` 标记。历史上\"条数裁剪切在工具往返中间产生孤儿 tool_result → provider 400\"\n * 的事故(commit `7e79407f`)由 `canStartHistory` 谓词防御,与本值无关。\n */\nexport const SESSION_MAX_HISTORY_MESSAGES = normalizeEnv(\n process.env['OTTO_SESSION_MAX_HISTORY_MESSAGES'],\n 50_000,\n)\n\n/**\n * 会话消息内容字节估算预算——**回退默认值**(RFC-323 后非主闸)。\n *\n * **语义变更(2026-08-07,C5 事后 review 修正)**:本常量曾在 RFC-321 D7 被定为\n * \"内存治理主闸\"(由 256MB 下调),但 RFC-323 的 ResidencyAllocator 已成为实际主闸\n * (单会话预算 = clamp(TOTAL/N, 16MB, 256MB))。本常量随后被降级为**分配器缺席 / 注入\n * 路径未接单时的回退默认值**。它与 `SESSION_MAX_PER_SESSION_BYTES`(分配器 MAX)语义\n * 重叠且数值不同(64MB vs 256MB),是 \"同一个语义槽却有两个数字\" 的缺陷——故将其\n * 重定向为回退默认,主闸语义归分配器。\n *\n * **仍被消费处**:`storage-wiring` 给 memory 的 `maxContentBytes` 构造期静态回退(消费点\n * 实际经 `getMaxContentBytes` 注入后不走此处;getter 缺席时才用)、`history-capped`\n * 事件的 capValue 语义。未改回到 256MB 原因:避免在生产中造成恢复旧的紧预算的隐性行为\n * 变化(64MB 已有历史行为,且分配器运行时已覆盖其缺口)。\n *\n * 为什么主闸是\"字节估算\"而不是进程内存占比(`heapUsed/heapSizeLimit`)——三条实证\n * (RFC-321 R11,沿用并强化 RFC-178 §1.3 的 grill 否决):\n * 1. **归因**:heapUsed 是进程全局值。实测 200MB Buffer 泄漏全部落在 `external`,\n * heapUsed 几乎无感(0.1%);即便占比升高也无法判断该裁哪个会话——按它裁会裁掉\n * 无辜的小会话,真凶纹丝不动。字节估算逐消息累加,可归因到单会话。\n * 2. **确定性**:同一会话状态下 heapUsed 占比 8 次采样跨度 0.09 个百分点(GC 时机\n * 摆布),阈值落在波动区内会 flaky,突变门无法稳定断言。字节估算零波动。\n * 3. **可测**:JS 无 sizeof;字节估算是唯一可确定计算的内存代理。\n * heapUsed 分级仍有用武之地,但只做**阈值缩放**(RFC-178 触发器 C,`ResidencyGovernor`\n * 按 warning/critical 把 cap 除以 2/4),**永不进入触发链**(R11)。\n *\n * **口径与真实驻留的关系(实测,互异内容避开 V8 字符串共享优化)**:\n * `estimateMessageContentBytes` 计 UTF-16 code unit 数(`String.length`)。\n * - 纯 ASCII 大消息:真实堆 ≈ 估算 × 1.00(5000 条 × 60KB = 287MB 真实 / 286MB 估算)\n * - **CJK(本仓主形态):真实 ≈ 估算 × 2.05**(一个汉字 length=1 但占 2 字节)\n * 故本预算 64MB 对应最坏 ~128MB 真实驻留/会话。\n *\n * **多会话乘数(必须一并考量)**:本预算是 **per-session**,而 `SESSION_MAX=50`\n * (`@x-otto/session/constants.ts`)。理论极限 = 64MB × 50 ≈ 3.2GB 估算 / ~6.4GB 真实,\n * 靠既有 idle 驱逐与满池 LRU 驱逐兜底(非本常量职责,但调整本值时不可忽略该乘数)。\n *\n * **64MB 的取值依据(历史)**:RFC-321 曾定为“20KB/条 ≈ 3200 条触发,行为跃变小;\n * 4GB 堆下单会话占 ~3%;附件/输出形态能拦”。现为主闸分配器 MAX=256MB 的**回退默认**(见上)。\n */\nexport const SESSION_MAX_CONTENT_BYTES = normalizeEnv(\n process.env['OTTO_SESSION_MAX_CONTENT_BYTES'],\n 64 * 1024 * 1024,\n)\n\n/**\n * 会话 resume 时的**尾窗口装载条数**(RFC-321 D7 新增,与条数 cap 解耦)。\n *\n * **为什么不能复用 `SESSION_MAX_HISTORY_MESSAGES`**(此前二者同源,D7 上调 cap 后暴露):\n * cap 是\"内存里最多留多少\"的**结构兜底**(50000,只防条目爆炸);而 resume 尾窗口是\n * \"启动时一次性装载多少\",直接决定**启动耗时与首屏驻留**。实测 50000 条 × 2KB 的一次性\n * 装载 = **108MB 驻留 / 113ms**,对启动不可接受;5000 条量级(~11MB)才是合理的首屏预算。\n * 二者语义不同、约束不同,故拆为独立常量——同源是历史巧合(RFC-159 D4 立项时 cap 恰好\n * 是个\"合理的窗口大小\"),D7 闸位重排后该巧合不再成立。\n *\n * 窗口之前的历史**不丢**:DB append-forever(RFC-159 D1),可经 `/history` 分页回看\n * (`history-pager.ts`)与 RFC-300 的 DB 补页按需加载。切点仍工具边界对齐(窗口首条非\n * 孤儿 tool_result,防 provider 400,历史事故 commit `7e79407f`)。\n */\nexport const SESSION_RESUME_WINDOW_MESSAGES = normalizeEnv(\n process.env['OTTO_SESSION_RESUME_WINDOW_MESSAGES'],\n 5_000,\n)\n\n/**\n * RFC-324 D1/R5:turn-gated 压缩的超时兜底(毫秒,默认 30s,G1 已 grill 确认)。\n *\n * 逃生阀压缩 pending 时,下一回合 prompt 前会先 await 其完成;超过本值仍未完成则降级为\n * fire-and-forget 继续发 prompt——**不允许压缩未完成就永久阻塞 turn**(R5 硬约束)。\n *\n * 取值权衡:30s 覆盖正常 LLM 压缩(2-5s)+ 重试余量,超时基本等价于 LLM 故障;\n * 调小会把正常压缩误判为超时,调大则用户在 LLM 故障时等待更久。\n */\nexport const SESSION_COMPACTION_TIMEOUT_MS = normalizeEnv(\n process.env['OTTO_SESSION_COMPACTION_TIMEOUT_MS'],\n 30_000,\n)\n\n/**\n * RFC-323:进程级驻留总预算(估算口径)。\n *\n * 单会话的**有效**字节预算不再是一个固定值,而是 `clamp(TOTAL / N, MIN, MAX)`——\n * 独占时可用满 MAX,并发时自动收紧,总量始终有界。\n *\n * **1024MB 的算术依据**(CJK 最坏比值 2.05,真实上界 = 估算 × 2.05):\n *\n * | TOTAL | 真实上界 | +基线 1500MB | vs 4096MB 硬顶 |\n * |-------|---------|-------------|--------------|\n * | 1024MB | 2099MB | 3599MB | ✅ 留 ~500MB |\n * | 1280MB | 2624MB | 4124MB | ❌ 超顶 |\n *\n * 取 1024MB:极端情形(所有会话同时贴满预算)下仍留 ~500MB 余量。\n * 基线 1500MB 是 RFC-178 实测值(healthy 时 heap 已 1.5GB/4.1GB)。\n */\nexport const SESSION_TOTAL_RESIDENCY_BUDGET = normalizeEnv(\n process.env['OTTO_SESSION_TOTAL_RESIDENCY_BUDGET'],\n 1024 * 1024 * 1024,\n)\n\n/**\n * RFC-323:单会话字节预算下界(N 极大时不被摊薄到无法工作)。\n *\n * 低于此值会话会过于频繁触发压缩(每几十条消息就压一次),影响可用性。\n * `SESSION_MAX(50) × MIN(16MB) = 800MB < TOTAL(1024MB)`,故实际不可达。\n */\nexport const SESSION_MIN_PER_SESSION_BYTES = normalizeEnv(\n process.env['OTTO_SESSION_MIN_PER_SESSION_BYTES'],\n 16 * 1024 * 1024,\n)\n\n/**\n * RFC-323:单会话字节预算上界(N=1 时的封顶)。\n *\n * 受 RFC-321 底线2 约束:压缩输入须 ≤ 模型输入窗口。200K 窗口下上界约 214MB\n * (图片形态),256MB 是留了余量的封顶。1M 窗口模型可放宽到 512MB,但需同步\n * 验证压缩输入不超窗(M6 的 map-reduce 会兜底,但 coverage 会更低)。\n */\nexport const SESSION_MAX_PER_SESSION_BYTES = normalizeEnv(\n process.env['OTTO_SESSION_MAX_PER_SESSION_BYTES'],\n 256 * 1024 * 1024,\n)\n\n/**\n * RFC-323 M4:驻留预算的运行时配置对象。\n *\n * 存在理由(D6b):上方常量在模块加载时立即求值为 number(env 快照),无法反映\n * app 启动后才 load 的 settings.json。故运行时读值点改从本对象读——由 app 层合并\n * `env(上方常量)> settings > 默认` 后注入。所有字段可选,缺省回退对应常量。\n */\nexport interface ResidencyBudgetConfig {\n totalBytes?: number\n minPerSessionBytes?: number\n maxPerSessionBytes?: number\n maxContentBytes?: number\n maxHistoryMessages?: number\n resumeWindowMessages?: number\n /**\n * RFC-324 D1/R5:turn-gated 压缩的超时兜底(毫秒)。逃生阀压缩超过此值仍未完成时,\n * 降级为 fire-and-forget——**不允许压缩未完成就永久阻塞 turn**。\n */\n compactionTimeoutMs?: number\n}\n\n/**\n * RFC-323 M4:合并 settings 覆盖到 env/默认常量之上,产出完整的 ResidencyBudgetConfig。\n *\n * 优先级:**env(上方常量,已含 env 覆盖)> settings > 默认**。\n * 实现:常量本身已是 `normalizeEnv(env, 默认)` 的结果——若用户设了 env,常量即为 env 值;\n * 否则为默认值。settings 只在\"用户**没设 env**\"时生效——但模块层无法区分\"常量是 env 值\n * 还是默认值\"。故约定:**settings 值优先于常量默认,但 env 显式设置时由调用方保证不传\n * settings**(见 app 层 `resolveResidencySettingsOverride`:仅当对应 env 未设时才取 settings)。\n */\nexport function resolveResidencyBudget(\n settings?: ResidencyBudgetConfig,\n): Required<ResidencyBudgetConfig> {\n return {\n totalBytes: settings?.totalBytes ?? SESSION_TOTAL_RESIDENCY_BUDGET,\n minPerSessionBytes: settings?.minPerSessionBytes ?? SESSION_MIN_PER_SESSION_BYTES,\n maxPerSessionBytes: settings?.maxPerSessionBytes ?? SESSION_MAX_PER_SESSION_BYTES,\n maxContentBytes: settings?.maxContentBytes ?? SESSION_MAX_CONTENT_BYTES,\n maxHistoryMessages: settings?.maxHistoryMessages ?? SESSION_MAX_HISTORY_MESSAGES,\n resumeWindowMessages: settings?.resumeWindowMessages ?? SESSION_RESUME_WINDOW_MESSAGES,\n compactionTimeoutMs: settings?.compactionTimeoutMs ?? SESSION_COMPACTION_TIMEOUT_MS,\n }\n}\n","/**\n * pulse-survey-trigger.ts — RFC-208 §2.1/§3.1:满意度脉冲调查触发判定纯函数。\n * RFC-209 M3:从 packages/cli 下沉至此(cli 与 service 均已依赖 @x-otto/session-contract,\n * 而 service 不应依赖 cli——EventBridge 需要复用本函数做 web-ui 侧 `pulse-survey.show`\n * 触发判定,见 RFC-209 milestone 执行阶段 grill 确认)。\n *\n * 纯逻辑类,不依赖 @x-otto/tui/@x-otto/coding 类型——判定「本次 prompt.end 是否应展示脉冲调查」,\n * 实际展示(tui.setPulseSurvey / web-ui pulse-survey.show)与数据写入(FeedbackRegistry)\n * 留在调用方(cli 侧 interactive-events.ts / service 侧 event-bridge.ts)。\n *\n * 触发锚点是 prompt.end(对话轮次真正结束的权威时点),按可配置概率随机抽样而非固定周期——\n * 固定周期容易被用户学习产生\"来了、又是这个\"的疲劳感;抽样配合最小回合间隔更接近\"偶尔冒出,\n * 不成为负担\"的调性。判定逻辑必须是纯函数、接受注入的 `random` 参数(RFC-208 重要事项规则 5)\n * ——不得在函数内部直接硬编码调用 `Math.random()`,保证可测试性(CLI 交互层直接用 Math.random\n * 有先例,见 draft-entries.ts,但触发判定这类需要精确验证边界值的逻辑必须可注入固定值测试)。\n *\n * `lastShownTurn` 是进程内存态,不持久化跨会话——重启 otto 后重新从零计间隔,符合\"轻量抽样,\n * 不需要跨会话精确统计\"的调性,避免为此引入额外的持久化状态文件。\n */\n\nexport interface PulseSurveyTriggerState {\n lastShownTurn?: number\n}\n\nexport interface PulseSurveyTriggerConfig {\n enabled: boolean\n probability: number\n minTurnGap: number\n /**\n * 当前是否有 Band A hard modal(如 forcedUpgrade)在场——为真时恒不触发(RFC-208 §5\n * 风险段\"forced-upgrade 并发\"缓解措施之一)。调用方传入 `modals.forcedUpgrade` 等值。\n */\n forcedUpgradeActive: boolean\n}\n\n/**\n * 判定本次 `prompt.end` 是否应展示满意度脉冲调查。\n *\n * @param currentTurn 当前对话轮次(`projState.currentTurn`)\n * @param config 触发配置(来自 `.otto/config.json` 的 `pulse_survey` 块 + 当前 Band A 状态)\n * @param state 触发状态(`lastShownTurn` 进程内存态,调用方持有并在命中时更新)\n * @param random 随机数源,默认 `Math.random`;测试注入固定值验证确定性\n */\nexport function shouldShowPulseSurvey(\n currentTurn: number,\n config: PulseSurveyTriggerConfig,\n state: PulseSurveyTriggerState,\n random: () => number = Math.random,\n): boolean {\n if (!config.enabled) return false\n if (config.forcedUpgradeActive) return false\n if (state.lastShownTurn !== undefined && currentTurn - state.lastShownTurn < config.minTurnGap) return false\n return random() < config.probability\n}\n"],"mappings":"2CAyBA,SAAgB,GAAuC,CACrD,MAAO,CAAE,UAAW,IAAI,IAAO,SAAU,EAAE,CAAE,YAAa,EAAG,CAG/D,MAAa,EAA2B,IAAI,IAAI,CAAC,gBAAiB,WAAY,YAAY,CAAC,CAY3F,SAAS,EAAsB,EAAuB,EAAwC,CAK5F,GAFI,EAAK,UAAY,KACR,OAAO,EAAK,MAAS,SAAW,EAAK,KAAO,UAC5C,OAAQ,MAAO,GAE5B,IAAM,EAAU,MAAM,QAAQ,EAAK,QAAQ,CACtC,EAAK,QAAsB,OAAQ,GAAmB,OAAO,GAAM,SAAS,CAC7E,IAAA,GAEA,EACJ,GAAI,GAAW,EAAQ,OAAS,EAC9B,EAAY,EAAQ,OAAQ,GAC1B,EAAM,SAAS,KAAM,GAAM,EAAE,KAAO,GAAM,EAAE,SAAW,OAAO,CAC/D,KACI,CACL,IAAM,EAAa,EAAM,SAAS,OAAQ,GAAM,EAAE,SAAW,cAAc,CAC3E,EAAY,EAAW,SAAW,EAAI,CAAC,EAAW,GAAI,GAAI,CAAG,EAAE,CAOjE,OALI,EAAU,SAAW,EAAU,IAEnC,EAAM,SAAW,EAAM,SAAS,IAAK,GACnC,EAAE,IAAM,MAAQ,EAAU,SAAS,EAAE,GAAG,CAAG,CAAE,GAAG,EAAG,OAAQ,OAAiB,CAAG,EAChF,CACM,IAGT,SAAgB,EACd,EACA,EACA,EAAkB,EAAE,CACJ,CAChB,IAAM,EAAY,EAAM,UAClB,EAAe,EAAI,mBAAsB,GAAc,EAAyB,IAAI,EAAE,EAE5F,OAAQ,EAAM,KAAd,CACE,IAAK,eAEH,MADA,GAAM,cACC,CACL,CACE,YACA,KAAM,eACN,KAAM,EAAM,KACb,CACF,CAEH,IAAK,aAAc,CACjB,EAAM,UAAU,OAAO,CACvB,IAAM,EAAQ,EAAI,iBAAiB,CACnC,MAAO,CACL,CAAE,YAAW,KAAM,kBAAmB,MAAO,EAAE,CAAyB,CACxE,CACE,YACA,KAAM,aAGN,MAAO,EAAM,OAAS,EAAI,gBAAgB,EAAI,GAC9C,OAAQ,EAAI,cAAc,CAC1B,cAAe,GAAgB,IAAA,GAC/B,OAAQ,EAAM,cACd,WAAY,EAAM,WACnB,CACF,CAGH,IAAK,aACH,MAAO,CACL,CACE,YACA,KAAM,cACN,cAAe,EAAM,cACrB,aAAc,EAAM,aACpB,aAAc,EAAM,mBACpB,kBAAmB,EAAM,wBAC1B,CACF,CAEH,IAAK,eAAgB,CACnB,IAAM,EAAI,EAAM,MAChB,OAAQ,EAAE,KAAV,CACE,IAAK,aACH,MAAO,CAAC,CAAE,YAAW,KAAM,oBAAqB,MAAO,EAAE,OAAS,GAAI,CAAC,CACzE,IAAK,iBACH,MAAO,CAAC,CAAE,YAAW,KAAM,wBAAyB,MAAO,EAAE,OAAS,GAAI,CAAC,CAC7E,IAAK,aACH,MAAO,CAAC,CAAE,YAAW,KAAM,oBAAqB,CAAC,CACnD,IAAK,WACH,MAAO,CAAC,CAAE,YAAW,KAAM,kBAAmB,CAAC,CACjD,IAAK,iBACH,MAAO,CAAC,CAAE,YAAW,KAAM,wBAAyB,CAAC,CACvD,IAAK,eACH,MAAO,CAAC,CAAE,YAAW,KAAM,sBAAuB,CAAC,CACrD,IAAK,QACH,MAAO,CAAC,CAAE,YAAW,KAAM,QAAS,QAAS,EAAW,EAAE,MAAM,CAAE,KAAM,eAAgB,CAAC,CAC3F,QACE,MAAO,EAAE,EAIf,IAAK,kBAAmB,CACtB,IAAM,EAAO,EAAM,SACb,EAAsB,CAAC,CAAE,YAAW,KAAM,aAAc,OAAM,CAAC,CACrE,GAAI,EAAa,EAAK,KAAK,CAAE,CAC3B,IAAM,EAAI,EAAK,WAAa,EAAE,CACxB,EAAQ,EAAE,UAAY,EAAE,OAAS,EAAE,UAAY,EAAE,KACjD,EACJ,OAAO,EAAE,QAAW,SAAW,EAAE,OAAO,QAAQ,MAAO,IAAI,CAAC,MAAM,EAAG,GAAG,CAAG,IAAA,GAC7E,EAAM,UAAU,IAAI,EAAK,GAAI,CAC3B,GAAI,EAAK,GACT,KAAM,OAAO,GAAU,SAAW,EAAQ,EAAK,KAC/C,SACA,OAAQ,UACT,CAAC,CACF,EAAI,KAAK,CACP,YACA,KAAM,kBACN,MAAO,CAAC,GAAG,EAAM,UAAU,QAAQ,CAAC,CACrC,CAAC,CAEJ,OAAO,EAGT,IAAK,gBAAiB,CACpB,GAAM,CAAE,SAAU,EAAM,WAAY,EAC9B,EAAO,EAAK,WAAa,EAAE,CAC3B,EAAa,IAA6C,CAC9D,YACA,KAAM,WACN,OACA,UAMA,WAAY,EAAM,YAAc,EAAI,iBAAiB,EAAK,CAC1D,qBAAsB,EAAM,qBAC5B,kBACD,EAED,GAAI,EAAK,OAAS,eAAiB,MAAM,QAAQ,EAAK,MAAM,CAAE,CAE5D,IAAM,EAAW,EAAK,MACtB,GAAI,EAAK,SAAW,SAClB,IAAK,IAAM,KAAK,EAAU,CACxB,IAAM,EAAI,EAAM,SAAS,UAAW,GAAM,EAAE,KAAO,EAAE,GAAG,CACpD,GAAK,EAAG,EAAM,SAAS,GAAK,CAAE,GAAG,EAAM,SAAS,GAAI,GAAG,EAAG,KAAM,EAAM,YAAa,CAClF,EAAM,SAAS,KAAK,CAAE,GAAG,EAAG,KAAM,EAAM,YAAa,CAAC,MAG7D,EAAM,SAAW,EAAS,IAAK,IAAO,CAAE,GAAG,EAAG,KAAM,EAAM,YAAa,EAAE,CAE3E,MAAO,CACL,EAAU,GAAK,CACf,CACE,YACA,KAAM,eACN,MAAO,CAAC,GAAG,EAAM,SAAS,CAC1B,MAAO,EAAK,MACb,CACF,CAGH,GAAI,EAAM,UAAU,IAAI,EAAK,GAAG,CAAE,CAChC,IAAM,EAAO,EAAM,UAAU,IAAI,EAAK,GAAG,CACzC,EAAM,UAAU,IAAI,EAAK,GAAI,CAAE,GAAG,EAAM,OAAQ,EAAU,QAAU,OAAQ,CAAC,CAC7E,IAAM,EAAsB,CAC1B,CACE,YACA,KAAM,kBACN,MAAO,CAAC,GAAG,EAAM,UAAU,QAAQ,CAAC,CACrC,CACD,GAAW,CACZ,CAYD,OANI,EAAK,OAAS,iBAAmB,CAAC,GACrB,EAAsB,EAAO,EAAK,EAE/C,EAAI,KAAK,CAAE,YAAW,KAAM,eAAgB,MAAO,CAAC,GAAG,EAAM,SAAS,CAAE,CAAC,CAGtE,EAGT,MAAO,CAAC,GAAW,CAAC,CAItB,IAAK,gBACH,MAAO,CAAC,CAAE,YAAW,KAAM,mBAAoB,OAAQ,UAAW,CAAC,CACrE,IAAK,cACH,MAAO,CAAC,CAAE,YAAW,KAAM,mBAAoB,OAAQ,QAAS,CAAC,CACnE,IAAK,kBAEH,MAAO,CAAC,CAAE,YAAW,KAAM,iBAAkB,OAAQ,YAAa,MADxD,EACiE,MAAO,CAAC,CAErF,IAAK,gBAAiB,CACpB,IAAM,EAAI,EACV,MAAO,CACL,CAAE,YAAW,KAAM,iBAAkB,OAAQ,OAAQ,MAAO,EAAE,MAAO,WAAY,EAAE,WAAY,CAChG,CAEH,IAAK,mBAEH,MAAO,CAAC,CAAE,YAAW,KAAM,iBAAkB,OAAQ,aAAc,aADzD,EACyE,aAAc,CAAC,CAEpG,IAAK,iBAEH,MAAO,CAAC,CAAE,YAAW,KAAM,iBAAkB,OAAQ,OAAQ,cADnD,EACoE,cAAe,CAAC,CAIhG,IAAK,oBAAqB,CACxB,IAAM,EAAI,EACV,MAAO,CACL,CACE,YACA,KAAM,oBACN,GAAI,EAAE,GACN,SAAU,EAAE,SACZ,UAAW,EAAE,UACb,YAAa,EAAE,YACf,KAAM,EAAE,KACT,CACF,CAEH,IAAK,oBAAqB,CACxB,IAAM,EAAI,EACV,MAAO,CAAC,CAAE,YAAW,KAAM,oBAAqB,GAAI,EAAE,GAAI,SAAU,EAAE,SAAU,CAAC,CAEnF,IAAK,oBAAqB,CACxB,IAAM,EAAI,EACV,MAAO,CAAC,CAAE,YAAW,KAAM,eAAgB,UAAW,EAAE,UAAW,SAAU,EAAE,SAAU,CAAC,CAE5F,IAAK,oBAEH,MAAO,CAAC,CAAE,YAAW,KAAM,eAAgB,UADjC,EAC8C,UAAW,CAAC,CAEtE,IAAK,yBAA0B,CAC7B,IAAM,EAAI,EACV,MAAO,CACL,CAAE,YAAW,KAAM,yBAA0B,UAAW,EAAE,UAAW,YAAa,EAAE,YAAa,CAClG,CAEH,IAAK,yBAA0B,CAC7B,IAAM,EAAI,EACV,MAAO,CAAC,CAAE,YAAW,KAAM,yBAA0B,UAAW,EAAE,UAAW,OAAQ,EAAE,OAAQ,CAAC,CAIlG,IAAK,QAEH,MAAO,CAAC,CAAE,YAAW,KAAM,QAAS,QAAS,EADnC,EACgD,MAAM,CAAE,KAAM,gBAAiB,CAAC,CAQ5F,IAAK,mBACL,IAAK,mBACL,IAAK,eACL,IAAK,gBACL,IAAK,uBACL,IAAK,wBACL,IAAK,4BACL,IAAK,+BACL,IAAK,WACL,IAAK,gBACL,IAAK,kBACL,IAAK,aACL,IAAK,cACL,IAAK,sBACL,IAAK,sBACL,IAAK,yBACL,IAAK,6BACL,IAAK,+BACL,IAAK,6BACL,IAAK,wBACL,IAAK,cACL,IAAK,6BACL,IAAK,2BACL,IAAK,iBACH,MAAO,EAAE,CAEX,QAKE,MAAO,EAAE,EAKf,SAAS,EAAW,EAAsB,CAGxC,OAFI,aAAe,MAAc,EAAI,QACjC,OAAO,GAAQ,SAAiB,EAC7B,gBClUT,SAAgB,EAAgB,EAA+B,CAC7D,MAAO,CAAE,OAAQ,EAAE,eAAgB,uBAAwB,GAAM,WAAY,sBAAuB,CAKtG,SAAgB,EAAoB,EAAoB,EAAoB,EAAkC,CAC5G,GAAI,EAAS,SAAW,GAAK,CAAC,EAAc,OAAO,EAAgB,EAAE,CACrE,IAAM,GAAe,EAAE,SAAW,EAAE,EAAE,OAAQ,GAAM,EAAE,YAAY,CAAC,IAAK,GAAM,EAAE,MAAM,CAChF,EACJ,EAAY,OAAS,GACrB,EAAS,SAAW,EAAY,QAChC,EAAS,MAAO,GAAM,EAAY,SAAS,EAAE,CAAC,CAC1C,EAAS,EAAS,OAAS,EAAI,EAAS,KAAK,KAAK,CAAG,GAE3D,MAAO,CAAE,OADU,EAAgB,EAAS,GAAG,EAAO,WAAW,IAAiB,EAAgB,EACrE,qBAAsB,EAAS,OAAS,EAAI,EAAW,IAAA,GAAW,uBAAwB,EAAU,WAAY,QAAS,eAAc,CAItK,SAAgB,EAAkB,EAAoB,EAA4C,CAChG,IAAM,EAAI,GAAM,MAAM,CAEtB,OADK,EACE,CAAE,OAAQ,EAAG,uBAAwB,IAAM,EAAE,eAAgB,WAAY,QAAS,CAD1E,EAAgB,EAAE,CAMnC,SAAgB,EACd,EACA,EACA,EACe,CAGf,OAFI,OAAO,GAAgB,UAAY,CAAC,EAAY,SAAS,EAAO,CAAS,MACjE,OAAO,GAAM,SAAY,SAAW,EAAK,QAAU,KACjD,KChChB,MAAa,EAA+B,EAC1C,QAAQ,IAAI,kCACZ,IACD,CAyCY,EAA4B,EACvC,QAAQ,IAAI,+BACZ,GAAK,KAAO,KACb,CAgBY,EAAiC,EAC5C,QAAQ,IAAI,oCACZ,IACD,CAWY,EAAgC,EAC3C,QAAQ,IAAI,mCACZ,IACD,CAkBY,EAAiC,EAC5C,QAAQ,IAAI,oCACZ,KAAO,KAAO,KACf,CAQY,EAAgC,EAC3C,QAAQ,IAAI,mCACZ,GAAK,KAAO,KACb,CASY,EAAgC,EAC3C,QAAQ,IAAI,mCACZ,IAAM,KAAO,KACd,CAgCD,SAAgB,EACd,EACiC,CACjC,MAAO,CACL,WAAY,GAAU,YAAc,EACpC,mBAAoB,GAAU,oBAAsB,EACpD,mBAAoB,GAAU,oBAAsB,EACpD,gBAAiB,GAAU,iBAAmB,EAC9C,mBAAoB,GAAU,oBAAsB,EACpD,qBAAsB,GAAU,sBAAwB,EACxD,oBAAqB,GAAU,qBAAuB,EACvD,CCrJH,SAAgB,EACd,EACA,EACA,EACA,EAAuB,KAAK,OACnB,CAIT,MAHI,CAAC,EAAO,SACR,EAAO,qBACP,EAAM,gBAAkB,IAAA,IAAa,EAAc,EAAM,cAAgB,EAAO,WAAmB,GAChG,GAAQ,CAAG,EAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@x-otto/session-contract",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org",
|
|
19
|
+
"tag": "alpha"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@x-otto/env": "0.1.0-alpha.1",
|
|
23
|
+
"@x-otto/interchange": "0.1.0-alpha.1"
|
|
24
|
+
},
|
|
25
|
+
"private": false,
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"typecheck:project": "tsc -p tsconfig.json --noEmit",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
}
|
|
32
|
+
}
|