lark-remote 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/index.d.ts +0 -7
- package/dist/bridge/index.js +21 -63
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +13 -1
- package/dist/card/card-shared.js +21 -30
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- package/dist/config/codex-config.js +2 -0
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -11
- package/dist/config/kimi-config.js +1 -0
- package/dist/config/opencode-config.js +13 -2
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -3
- package/dist/index.js +14 -41
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +66 -6
- package/dist/router/index.js +450 -315
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -1
- package/dist/runner/claude/session.js +18 -4
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +39 -0
- package/dist/runner/common/acp/protocol-helpers.js +34 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +45 -42
- package/dist/runner/kimi/acp/runner.js +140 -293
- package/dist/runner/kimi/acp/translator.d.ts +18 -100
- package/dist/runner/kimi/acp/translator.js +56 -242
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +73 -256
- package/dist/runner/opencode/acp/translator.d.ts +28 -103
- package/dist/runner/opencode/acp/translator.js +51 -236
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/sessions.js +34 -57
- package/dist/session/codex/rollout-reader.d.ts +10 -0
- package/dist/session/codex/rollout-reader.js +123 -41
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/dsh/sessions.js +6 -5
- package/dist/session/kimi/sessions.d.ts +6 -0
- package/dist/session/kimi/sessions.js +50 -73
- package/dist/session/opencode/sessions.js +26 -8
- package/dist/session/pi/sessions.js +54 -73
- package/dist/update/index.d.ts +1 -2
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -6
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
package/dist/card/run-state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent, ApprovalView } from '../runner/index.js';
|
|
1
|
+
import type { AgentEvent, ApprovalView, ToolHint } from '../runner/index.js';
|
|
2
2
|
export type RunTerminal = 'running' | 'finalizing' | 'done' | 'error' | 'interrupted' | 'idle_timeout';
|
|
3
3
|
export type RunFooter = 'thinking' | 'tool_running' | 'streaming' | null;
|
|
4
4
|
/** 中断来源(interrupted 终态的原因),供卡片如实归因。 */
|
|
@@ -22,6 +22,10 @@ export interface ToolEntry {
|
|
|
22
22
|
status: ToolStatus;
|
|
23
23
|
startedAt?: string;
|
|
24
24
|
completedAt?: string;
|
|
25
|
+
/** 工具渲染 hint(turn_diff 首见时携带;渲染层只消费不推断)。 */
|
|
26
|
+
toolHint?: ToolHint;
|
|
27
|
+
/** 工具原始 title(ACP kind 映射后与 name 不同时存在;渲染做副标题)。 */
|
|
28
|
+
summary?: string;
|
|
25
29
|
}
|
|
26
30
|
export type RunBlock = {
|
|
27
31
|
kind: 'thinking';
|
|
@@ -56,6 +60,14 @@ export type RunBlock = {
|
|
|
56
60
|
timestamp?: string;
|
|
57
61
|
completedAt?: string;
|
|
58
62
|
};
|
|
63
|
+
/** 运行期通知(warning / 重路由 / 压缩提示)。保留到达时间,渲染层可将其
|
|
64
|
+
* 按时间线插入内容流当普通消息;无时间戳时兜底置于内容流末尾。 */
|
|
65
|
+
export interface RunNotice {
|
|
66
|
+
level: 'info' | 'warn' | 'error';
|
|
67
|
+
code?: string;
|
|
68
|
+
text: string;
|
|
69
|
+
timestamp?: string;
|
|
70
|
+
}
|
|
59
71
|
export interface RunState {
|
|
60
72
|
runId: string;
|
|
61
73
|
terminal: RunTerminal;
|
|
@@ -90,6 +102,20 @@ export interface RunState {
|
|
|
90
102
|
/** 会话累计 cache creation token(所有 run 之和)。 */
|
|
91
103
|
cumulativeCacheCreationTokens?: number;
|
|
92
104
|
plan?: string;
|
|
105
|
+
/** 实际生效模型(system.init 携带;失败兜底 synthetic init 为 '',渲染须容忍)。 */
|
|
106
|
+
model?: string;
|
|
107
|
+
/** 本次 run 花费(USD,result 事件 total_cost_usd)。 */
|
|
108
|
+
costUsd?: number;
|
|
109
|
+
/** 运行期通知(warning / 重路由 / 压缩提示),按到达顺序。 */
|
|
110
|
+
notices?: RunNotice[];
|
|
111
|
+
/** 被 MAX_BLOCKS 静默丢弃的早期块计数(>0 时渲染「已省略 N 个早期步骤」)。 */
|
|
112
|
+
omittedBlocks?: number;
|
|
113
|
+
/** 会话标题(ACP session_info_update.title)。 */
|
|
114
|
+
sessionTitle?: string;
|
|
115
|
+
/** 当前模式(ACP current_mode_update.currentModeId)。 */
|
|
116
|
+
mode?: string;
|
|
117
|
+
/** 推理 token(pi usage.reasoning 累加,经 finish meta 写入)。 */
|
|
118
|
+
reasoningTokens?: number;
|
|
93
119
|
/** Codex 操作类型:'turn' 表示普通 run,'compaction' 表示 compact 运行。 */
|
|
94
120
|
operationKind?: 'turn' | 'compaction';
|
|
95
121
|
/** 压缩前上下文水位(codex compact 卡展示「压缩前 X → 压缩后 Y」)。 */
|
|
@@ -135,6 +161,8 @@ export interface FinishMeta {
|
|
|
135
161
|
cumulativeOutputTokens?: number;
|
|
136
162
|
cumulativeCacheReadTokens?: number;
|
|
137
163
|
cumulativeCacheCreationTokens?: number;
|
|
164
|
+
/** 推理 token(pi usage.reasoning 累加,经 usageMeta 透传)。 */
|
|
165
|
+
reasoningTokens?: number;
|
|
138
166
|
}
|
|
139
167
|
export declare function createInitialRunState(runId: string): RunState;
|
|
140
168
|
export declare function reduceRunState(state: RunState, event: AgentEvent): RunState;
|
package/dist/card/run-state.js
CHANGED
|
@@ -33,6 +33,10 @@ export function reduceRunState(state, event) {
|
|
|
33
33
|
return reducePlanEvent(state, event);
|
|
34
34
|
if (event.type === 'file_change')
|
|
35
35
|
return reduceFileChangeEvent(state, event);
|
|
36
|
+
if (event.type === 'notice')
|
|
37
|
+
return reduceNoticeEvent(state, event);
|
|
38
|
+
if (event.type === 'session_info')
|
|
39
|
+
return reduceSessionInfoEvent(state, event);
|
|
36
40
|
if (event.type === 'turn_started')
|
|
37
41
|
return reduceTurnStartedEvent(state, event);
|
|
38
42
|
if (event.type === 'turn_diff')
|
|
@@ -97,96 +101,116 @@ function reduceTurnDiffEvent(state, event) {
|
|
|
97
101
|
const complete = event.complete === true;
|
|
98
102
|
if (event.text !== undefined) {
|
|
99
103
|
const blocks = markThinkingInactive(next.blocks);
|
|
104
|
+
const capped = upsertSnapshotBlock(blocks, (b) => b.kind === 'text' && b.itemId === event.itemId, () => ({
|
|
105
|
+
kind: 'text',
|
|
106
|
+
itemId: event.itemId,
|
|
107
|
+
content: keepTailMarked(event.text, MAX_TEXT_CHARS),
|
|
108
|
+
timestamp: ts,
|
|
109
|
+
...(complete ? { completedAt: ts } : {}),
|
|
110
|
+
}), (existing) => {
|
|
111
|
+
const textBlock = existing;
|
|
112
|
+
return {
|
|
113
|
+
...textBlock,
|
|
114
|
+
content: keepTailMarked(event.text, MAX_TEXT_CHARS),
|
|
115
|
+
// kimi:块锚点时间改为本次 diff 时间(整轮累积流不断续写,
|
|
116
|
+
// 标题应反映“最后写入时刻”而非首见时刻;move-to-end 后位置
|
|
117
|
+
// 与标题才一致)。codex app-server 不置 refreshTimestamp,
|
|
118
|
+
// 保持“首见时间戳不刷新”的既有契约。
|
|
119
|
+
...(event.refreshTimestamp ? { timestamp: ts } : {}),
|
|
120
|
+
...(complete ? { completedAt: ts } : {}),
|
|
121
|
+
};
|
|
122
|
+
}, !complete);
|
|
100
123
|
next = {
|
|
101
124
|
...next,
|
|
102
|
-
blocks: keepLatestBlocks(
|
|
103
|
-
|
|
104
|
-
itemId: event.itemId,
|
|
105
|
-
content: keepTail(event.text, MAX_TEXT_CHARS),
|
|
106
|
-
timestamp: ts,
|
|
107
|
-
...(complete ? { completedAt: ts } : {}),
|
|
108
|
-
}), (existing) => {
|
|
109
|
-
const textBlock = existing;
|
|
110
|
-
return {
|
|
111
|
-
...textBlock,
|
|
112
|
-
content: keepTail(event.text, MAX_TEXT_CHARS),
|
|
113
|
-
...(complete ? { completedAt: ts } : {}),
|
|
114
|
-
};
|
|
115
|
-
}, !complete)),
|
|
125
|
+
blocks: keepLatestBlocks(capped),
|
|
126
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
116
127
|
footer: 'streaming',
|
|
117
128
|
};
|
|
118
129
|
}
|
|
119
130
|
if (event.reasoning !== undefined) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const capped = upsertSnapshotBlock(next.blocks, (b) => b.kind === 'thinking' && b.itemId === event.itemId, () => ({
|
|
132
|
+
kind: 'thinking',
|
|
133
|
+
itemId: event.itemId,
|
|
134
|
+
content: keepTailMarked(event.reasoning, MAX_REASONING_CHARS),
|
|
135
|
+
active: !complete,
|
|
136
|
+
timestamp: ts,
|
|
137
|
+
...(complete ? { completedAt: ts } : {}),
|
|
138
|
+
}), (existing) => {
|
|
139
|
+
const thinkingBlock = existing;
|
|
140
|
+
return {
|
|
141
|
+
...thinkingBlock,
|
|
142
|
+
content: keepTailMarked(event.reasoning, MAX_REASONING_CHARS),
|
|
126
143
|
active: !complete,
|
|
127
|
-
timestamp: ts,
|
|
144
|
+
...(event.refreshTimestamp ? { timestamp: ts } : {}),
|
|
128
145
|
...(complete ? { completedAt: ts } : {}),
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
...(complete ? { completedAt: ts } : {}),
|
|
136
|
-
};
|
|
137
|
-
}, !complete)),
|
|
146
|
+
};
|
|
147
|
+
}, !complete);
|
|
148
|
+
next = {
|
|
149
|
+
...next,
|
|
150
|
+
blocks: keepLatestBlocks(capped),
|
|
151
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
138
152
|
footer: 'thinking',
|
|
139
153
|
};
|
|
140
154
|
}
|
|
141
155
|
if (event.toolOutput !== undefined) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
const capped = upsertSnapshotBlock(next.blocks, (b) => b.kind === 'tool' && b.tool.id === event.itemId, () => {
|
|
157
|
+
// 身份只在首见时写入(existing 分支不动):toolName/toolInput/toolHint。
|
|
158
|
+
const inputStr = truncateDetail(stringifyUnknown(event.toolInput ?? {}));
|
|
159
|
+
return {
|
|
145
160
|
kind: 'tool',
|
|
146
161
|
tool: {
|
|
147
162
|
id: event.itemId,
|
|
148
|
-
name: 'command',
|
|
149
|
-
input:
|
|
163
|
+
name: event.toolName ?? 'command',
|
|
164
|
+
input: inputStr,
|
|
165
|
+
parsedInput: tryParseRecord(inputStr),
|
|
166
|
+
...(event.toolHint !== undefined ? { toolHint: event.toolHint } : {}),
|
|
150
167
|
output: keepTail(event.toolOutput, MAX_TOOL_DETAIL_CHARS),
|
|
151
168
|
status: complete ? (event.toolStatus ?? 'ok') : 'running',
|
|
152
169
|
startedAt: ts,
|
|
153
170
|
...(complete ? { completedAt: ts } : {}),
|
|
154
171
|
},
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
}
|
|
172
|
+
};
|
|
173
|
+
}, (existing) => ({
|
|
174
|
+
kind: 'tool',
|
|
175
|
+
tool: {
|
|
176
|
+
...existing.tool,
|
|
177
|
+
output: keepTail(event.toolOutput, MAX_TOOL_DETAIL_CHARS),
|
|
178
|
+
status: complete
|
|
179
|
+
? (event.toolStatus ?? 'ok')
|
|
180
|
+
: existing.tool.status,
|
|
181
|
+
...(complete ? { completedAt: ts } : {}),
|
|
182
|
+
},
|
|
183
|
+
}), !complete);
|
|
184
|
+
next = {
|
|
185
|
+
...next,
|
|
186
|
+
blocks: keepLatestBlocks(capped),
|
|
187
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
166
188
|
footer: 'tool_running',
|
|
167
189
|
};
|
|
168
190
|
}
|
|
169
191
|
if (event.plan !== undefined) {
|
|
170
|
-
const newPlan =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
const newPlan = keepTailMarked(event.plan, MAX_REASONING_CHARS * 2);
|
|
193
|
+
const capped = upsertSnapshotBlock(next.blocks, (b) => b.kind === 'plan' && b.itemId === event.itemId, () => ({
|
|
194
|
+
kind: 'plan',
|
|
195
|
+
itemId: event.itemId,
|
|
196
|
+
content: newPlan,
|
|
197
|
+
active: !complete,
|
|
198
|
+
timestamp: ts,
|
|
199
|
+
...(complete ? { completedAt: ts } : {}),
|
|
200
|
+
}), (existing) => {
|
|
201
|
+
const planBlock = existing;
|
|
202
|
+
return {
|
|
203
|
+
...planBlock,
|
|
177
204
|
content: newPlan,
|
|
178
205
|
active: !complete,
|
|
179
|
-
timestamp: ts,
|
|
180
206
|
...(complete ? { completedAt: ts } : {}),
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
}, !complete)),
|
|
207
|
+
};
|
|
208
|
+
}, !complete);
|
|
209
|
+
next = {
|
|
210
|
+
...next,
|
|
211
|
+
plan: newPlan,
|
|
212
|
+
blocks: keepLatestBlocks(capped),
|
|
213
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
190
214
|
};
|
|
191
215
|
}
|
|
192
216
|
if (event.fileChanges !== undefined) {
|
|
@@ -204,7 +228,12 @@ function reduceTurnDiffEvent(state, event) {
|
|
|
204
228
|
// 同一 item 的同一路径原地替换(权威 diff 校正),新路径/新 item 追加。
|
|
205
229
|
blocks = upsertSnapshotBlock(blocks, (b) => b.kind === 'file_change' && b.itemId === event.itemId && b.path === fc.path, () => fileBlock, () => fileBlock, !complete);
|
|
206
230
|
}
|
|
207
|
-
|
|
231
|
+
const capped = blocks;
|
|
232
|
+
next = {
|
|
233
|
+
...next,
|
|
234
|
+
blocks: keepLatestBlocks(capped),
|
|
235
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
236
|
+
};
|
|
208
237
|
}
|
|
209
238
|
return next;
|
|
210
239
|
}
|
|
@@ -237,7 +266,7 @@ function reduceSystemEvent(state, event) {
|
|
|
237
266
|
if (event.type !== 'system')
|
|
238
267
|
return state;
|
|
239
268
|
if (event.subtype === 'init') {
|
|
240
|
-
return { ...state, sessionId: event.session_id, footer: 'thinking' };
|
|
269
|
+
return { ...state, sessionId: event.session_id, model: event.model, footer: 'thinking' };
|
|
241
270
|
}
|
|
242
271
|
if (event.subtype === 'compact_boundary' && event.compactMetadata) {
|
|
243
272
|
return {
|
|
@@ -285,6 +314,10 @@ function reduceResultEvent(state, event) {
|
|
|
285
314
|
blocks: markThinkingInactive(state.blocks),
|
|
286
315
|
resultSubtype: mergedSubtype,
|
|
287
316
|
errorMsg: mergedErrorMsg,
|
|
317
|
+
// costUsd:首个非 undefined 胜出(与 errorMsg 合并语义一致)
|
|
318
|
+
...(event.total_cost_usd !== undefined && state.costUsd === undefined
|
|
319
|
+
? { costUsd: event.total_cost_usd }
|
|
320
|
+
: {}),
|
|
288
321
|
};
|
|
289
322
|
}
|
|
290
323
|
/** Handle assistant thinking/text/tool_use content blocks. */
|
|
@@ -300,7 +333,7 @@ function reduceAssistantEvent(state, event) {
|
|
|
300
333
|
...next.blocks.slice(0, -1),
|
|
301
334
|
{
|
|
302
335
|
kind: 'thinking',
|
|
303
|
-
content:
|
|
336
|
+
content: keepTailMarked(last.content + '\n' + content.thinking, MAX_REASONING_CHARS),
|
|
304
337
|
active: true,
|
|
305
338
|
timestamp: last.timestamp ?? event.timestamp,
|
|
306
339
|
},
|
|
@@ -309,7 +342,7 @@ function reduceAssistantEvent(state, event) {
|
|
|
309
342
|
...next.blocks,
|
|
310
343
|
{
|
|
311
344
|
kind: 'thinking',
|
|
312
|
-
content:
|
|
345
|
+
content: keepTailMarked(content.thinking, MAX_REASONING_CHARS),
|
|
313
346
|
active: true,
|
|
314
347
|
timestamp: event.timestamp,
|
|
315
348
|
},
|
|
@@ -317,6 +350,7 @@ function reduceAssistantEvent(state, event) {
|
|
|
317
350
|
next = {
|
|
318
351
|
...next,
|
|
319
352
|
blocks: keepLatestBlocks(newBlocks),
|
|
353
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, newBlocks.length - MAX_BLOCKS),
|
|
320
354
|
footer: 'thinking',
|
|
321
355
|
};
|
|
322
356
|
}
|
|
@@ -328,7 +362,7 @@ function reduceAssistantEvent(state, event) {
|
|
|
328
362
|
...blocks.slice(0, -1),
|
|
329
363
|
{
|
|
330
364
|
kind: 'text',
|
|
331
|
-
content:
|
|
365
|
+
content: keepTailMarked(last.content + content.text, MAX_TEXT_CHARS),
|
|
332
366
|
timestamp: last.timestamp ?? event.timestamp,
|
|
333
367
|
},
|
|
334
368
|
]
|
|
@@ -336,13 +370,14 @@ function reduceAssistantEvent(state, event) {
|
|
|
336
370
|
...blocks,
|
|
337
371
|
{
|
|
338
372
|
kind: 'text',
|
|
339
|
-
content:
|
|
373
|
+
content: keepTailMarked(content.text, MAX_TEXT_CHARS),
|
|
340
374
|
timestamp: event.timestamp,
|
|
341
375
|
},
|
|
342
376
|
];
|
|
343
377
|
next = {
|
|
344
378
|
...next,
|
|
345
379
|
blocks: keepLatestBlocks(newBlocks),
|
|
380
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, newBlocks.length - MAX_BLOCKS),
|
|
346
381
|
footer: 'streaming',
|
|
347
382
|
};
|
|
348
383
|
}
|
|
@@ -353,22 +388,25 @@ function reduceAssistantEvent(state, event) {
|
|
|
353
388
|
// string as the old per-render parse, so truncation-broken JSON still
|
|
354
389
|
// yields null (over-cap input renders no summary — behavior preserved).
|
|
355
390
|
const inputStr = truncateDetail(stringifyUnknown(content.input));
|
|
391
|
+
const capped = [
|
|
392
|
+
...markThinkingInactive(next.blocks),
|
|
393
|
+
{
|
|
394
|
+
kind: 'tool',
|
|
395
|
+
tool: {
|
|
396
|
+
id: content.id,
|
|
397
|
+
name: content.name,
|
|
398
|
+
input: inputStr,
|
|
399
|
+
parsedInput: tryParseRecord(inputStr),
|
|
400
|
+
...(content.summary !== undefined ? { summary: content.summary } : {}),
|
|
401
|
+
status: 'running',
|
|
402
|
+
startedAt: event.timestamp,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
];
|
|
356
406
|
next = {
|
|
357
407
|
...next,
|
|
358
|
-
blocks: keepLatestBlocks(
|
|
359
|
-
|
|
360
|
-
{
|
|
361
|
-
kind: 'tool',
|
|
362
|
-
tool: {
|
|
363
|
-
id: content.id,
|
|
364
|
-
name: content.name,
|
|
365
|
-
input: inputStr,
|
|
366
|
-
parsedInput: tryParseRecord(inputStr),
|
|
367
|
-
status: 'running',
|
|
368
|
-
startedAt: event.timestamp,
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
]),
|
|
408
|
+
blocks: keepLatestBlocks(capped),
|
|
409
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
372
410
|
footer: 'tool_running',
|
|
373
411
|
};
|
|
374
412
|
}
|
|
@@ -409,12 +447,39 @@ function reduceToolResultEvent(state, event) {
|
|
|
409
447
|
}
|
|
410
448
|
return next;
|
|
411
449
|
}
|
|
450
|
+
/** Handle notice event — append a runtime notice (warning / reroute / compaction). */
|
|
451
|
+
function reduceNoticeEvent(state, event) {
|
|
452
|
+
if (event.type !== 'notice')
|
|
453
|
+
return state;
|
|
454
|
+
return {
|
|
455
|
+
...state,
|
|
456
|
+
notices: [
|
|
457
|
+
...(state.notices ?? []),
|
|
458
|
+
{
|
|
459
|
+
level: event.level,
|
|
460
|
+
...(event.code !== undefined ? { code: event.code } : {}),
|
|
461
|
+
text: event.text,
|
|
462
|
+
...(event.timestamp !== undefined ? { timestamp: event.timestamp } : {}),
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/** Handle session_info event — update sessionTitle / mode only when present. */
|
|
468
|
+
function reduceSessionInfoEvent(state, event) {
|
|
469
|
+
if (event.type !== 'session_info')
|
|
470
|
+
return state;
|
|
471
|
+
return {
|
|
472
|
+
...state,
|
|
473
|
+
...(event.title !== undefined ? { sessionTitle: event.title } : {}),
|
|
474
|
+
...(event.mode !== undefined ? { mode: event.mode } : {}),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
412
477
|
/** Handle plan event — accumulate plan text for Codex-style agents. */
|
|
413
478
|
function reducePlanEvent(state, event) {
|
|
414
479
|
if (event.type !== 'plan')
|
|
415
480
|
return state;
|
|
416
481
|
const currentPlan = state.plan ?? '';
|
|
417
|
-
const newPlan =
|
|
482
|
+
const newPlan = keepTailMarked(currentPlan + '\n' + event.plan, MAX_REASONING_CHARS * 2);
|
|
418
483
|
// Plan is a single evolving document — replace the existing plan block
|
|
419
484
|
// instead of appending a new one (avoids N overlapping blocks after N events).
|
|
420
485
|
const withoutOldPlan = state.blocks.filter((b) => b.kind !== 'plan');
|
|
@@ -426,6 +491,7 @@ function reducePlanEvent(state, event) {
|
|
|
426
491
|
...state,
|
|
427
492
|
plan: newPlan,
|
|
428
493
|
blocks: keepLatestBlocks(newBlocks),
|
|
494
|
+
omittedBlocks: (state.omittedBlocks ?? 0) + Math.max(0, newBlocks.length - MAX_BLOCKS),
|
|
429
495
|
};
|
|
430
496
|
}
|
|
431
497
|
/** Handle file_change event — emit a file_change block for rendering (Codex-style agents). */
|
|
@@ -446,6 +512,7 @@ function reduceFileChangeEvent(state, event) {
|
|
|
446
512
|
return {
|
|
447
513
|
...state,
|
|
448
514
|
blocks: keepLatestBlocks(newBlocks),
|
|
515
|
+
omittedBlocks: (state.omittedBlocks ?? 0) + Math.max(0, newBlocks.length - MAX_BLOCKS),
|
|
449
516
|
};
|
|
450
517
|
}
|
|
451
518
|
export function finishRun(state, terminal, meta = {}) {
|
|
@@ -522,6 +589,27 @@ function tryParseRecord(input) {
|
|
|
522
589
|
function keepLatestBlocks(blocks) {
|
|
523
590
|
return blocks.length <= MAX_BLOCKS ? blocks : blocks.slice(-MAX_BLOCKS);
|
|
524
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* keepTail + 截断标记:被去头的存储内容以前缀如实标注(信息保真红线:禁止静默丢弃)。
|
|
594
|
+
* 标记计入 maxChars 预算(存储总量仍 ≤ maxChars,保持既有长度不变量)。
|
|
595
|
+
*/
|
|
596
|
+
function keepTailMarked(value, maxChars) {
|
|
597
|
+
if (value.length <= maxChars)
|
|
598
|
+
return value;
|
|
599
|
+
// marker 计入 maxChars 预算(总量不变量)。省略数 = value.length - kept.length,
|
|
600
|
+
// 而 kept 长度取决于 marker 长度、marker 长度又取决于省略数的位数——
|
|
601
|
+
// 不动点迭代(marker 固定部分 12 字符,位数至多几次即收敛)。
|
|
602
|
+
let dropped = value.length - maxChars + 12;
|
|
603
|
+
for (;;) {
|
|
604
|
+
const next = value.length - maxChars + 12 + String(dropped).length;
|
|
605
|
+
if (next === dropped)
|
|
606
|
+
break;
|
|
607
|
+
dropped = next;
|
|
608
|
+
}
|
|
609
|
+
const marker = `…(前 ${dropped} 字符已省略)\n`;
|
|
610
|
+
const kept = keepTail(value, Math.max(0, maxChars - marker.length));
|
|
611
|
+
return marker + kept;
|
|
612
|
+
}
|
|
525
613
|
function truncateDetail(value) {
|
|
526
614
|
return value.length <= MAX_TOOL_DETAIL_CHARS
|
|
527
615
|
? value
|
|
@@ -22,15 +22,6 @@ export declare const FEISHU_MAX_TABLES = 5;
|
|
|
22
22
|
* 共用,避免三处各自定义后数值漂移。
|
|
23
23
|
*/
|
|
24
24
|
export declare const CARD_BUDGET_BYTES = 28000;
|
|
25
|
-
/**
|
|
26
|
-
* Max byte budget for a single session `tool_result` event's content when
|
|
27
|
-
* replaying an opencode session (L2 pre-fold). The static card budget enforcer
|
|
28
|
-
* (`enforceCardBudget`, maxPanelContentBytes = 2000) would truncate it again at
|
|
29
|
-
* render time anyway; pre-folding here bounds the in-memory `events[]` array
|
|
30
|
-
* and the intermediate card JSON so a single pathological tool_result (e.g. a
|
|
31
|
-
* 500KB file listing) never inflates the replay payload to megabytes.
|
|
32
|
-
*/
|
|
33
|
-
export declare const TOOL_RESULT_MAX_BYTES = 4000;
|
|
34
25
|
/**
|
|
35
26
|
* Count the number of markdown tables in a string.
|
|
36
27
|
*
|
|
@@ -22,15 +22,6 @@ export const FEISHU_MAX_TABLES = 5;
|
|
|
22
22
|
* 共用,避免三处各自定义后数值漂移。
|
|
23
23
|
*/
|
|
24
24
|
export const CARD_BUDGET_BYTES = 28_000;
|
|
25
|
-
/**
|
|
26
|
-
* Max byte budget for a single session `tool_result` event's content when
|
|
27
|
-
* replaying an opencode session (L2 pre-fold). The static card budget enforcer
|
|
28
|
-
* (`enforceCardBudget`, maxPanelContentBytes = 2000) would truncate it again at
|
|
29
|
-
* render time anyway; pre-folding here bounds the in-memory `events[]` array
|
|
30
|
-
* and the intermediate card JSON so a single pathological tool_result (e.g. a
|
|
31
|
-
* 500KB file listing) never inflates the replay payload to megabytes.
|
|
32
|
-
*/
|
|
33
|
-
export const TOOL_RESULT_MAX_BYTES = 4000;
|
|
34
25
|
/**
|
|
35
26
|
* Count the number of markdown tables in a string.
|
|
36
27
|
*
|
|
@@ -20,6 +20,9 @@ export declare function toolHeaderText(tool: ToolEntry): string;
|
|
|
20
20
|
/**
|
|
21
21
|
* Structured body markdown for a tool call. Renders input fields by tool name
|
|
22
22
|
* (Bash → command, Read/Edit/Write → file_path, etc.) and output in a code
|
|
23
|
-
* block.
|
|
23
|
+
* block. An explicit `toolHint` (declared by the runner, 信息保真 C5) selects
|
|
24
|
+
* the same rendering by family instead of name. There is no raw-JSON fallback
|
|
25
|
+
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
26
|
+
* args through the panel header's `summarizeInput` fallback.
|
|
24
27
|
*/
|
|
25
28
|
export declare function toolBodyMd(tool: ToolEntry): string;
|
package/dist/card/tool-render.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* under formatting noise. `toolHeaderText` produces a compact one-liner header
|
|
10
10
|
* like `✅ **Bash** — pwd` so collapsed panels stay informative.
|
|
11
11
|
*/
|
|
12
|
+
import { truncate } from '../common/truncate.js';
|
|
12
13
|
const HEADER_SUMMARY_MAX = 80;
|
|
13
14
|
const BODY_FIELD_MAX = 600;
|
|
14
15
|
const OUTPUT_MAX = 1200;
|
|
@@ -31,12 +32,25 @@ export function toolHeaderText(tool) {
|
|
|
31
32
|
// P3-6: use the cached parsed record when available (avoids per-render parse).
|
|
32
33
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
33
34
|
const summary = summarizeInput(tool.name, input);
|
|
34
|
-
|
|
35
|
+
// 信息保真 C3.1:ACP kind 映射后 name 与原始 title 不同时,title 以
|
|
36
|
+
// ` — {summary}` 追加在标题末尾(截断沿用 HEADER_SUMMARY_MAX)。
|
|
37
|
+
// 用 truncate(suffix 为空)而非裸 slice:裸 slice 按 UTF-16 码元切,
|
|
38
|
+
// 切点落在 emoji 代理对中间会留下孤立代理项(P2-31 同族)。
|
|
39
|
+
const blockSummary = tool.summary
|
|
40
|
+
? truncate(tool.summary.replace(/\s+/g, ' ').trim(), HEADER_SUMMARY_MAX, { suffix: '' })
|
|
41
|
+
: '';
|
|
42
|
+
const parts = [summary, blockSummary].filter(Boolean);
|
|
43
|
+
return parts.length > 0
|
|
44
|
+
? `${icon} **${tool.name}** — ${parts.join(' — ')}`
|
|
45
|
+
: `${icon} **${tool.name}**`;
|
|
35
46
|
}
|
|
36
47
|
/**
|
|
37
48
|
* Structured body markdown for a tool call. Renders input fields by tool name
|
|
38
49
|
* (Bash → command, Read/Edit/Write → file_path, etc.) and output in a code
|
|
39
|
-
* block.
|
|
50
|
+
* block. An explicit `toolHint` (declared by the runner, 信息保真 C5) selects
|
|
51
|
+
* the same rendering by family instead of name. There is no raw-JSON fallback
|
|
52
|
+
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
53
|
+
* args through the panel header's `summarizeInput` fallback.
|
|
40
54
|
*/
|
|
41
55
|
export function toolBodyMd(tool) {
|
|
42
56
|
const parts = [];
|
|
@@ -58,7 +72,8 @@ export function toolBodyMd(tool) {
|
|
|
58
72
|
const body = parts.join('\n\n');
|
|
59
73
|
if (body.length <= BODY_TOTAL_MAX)
|
|
60
74
|
return body;
|
|
61
|
-
|
|
75
|
+
// 同上:走 truncate 保证切点不落在代理对中间(末尾 '…' 由 truncate 附加以对齐预算)。
|
|
76
|
+
return `${truncate(body, BODY_TOTAL_MAX)}\n\n_(body 已截断,完整内容查日志)_`;
|
|
62
77
|
}
|
|
63
78
|
function summarizeInput(name, input) {
|
|
64
79
|
const rec = asRecord(input);
|
|
@@ -105,6 +120,37 @@ function summarizeInput(name, input) {
|
|
|
105
120
|
return pick('command') || pick('file_path') || pick('path') || pick('query');
|
|
106
121
|
}
|
|
107
122
|
}
|
|
123
|
+
// --- Named render bodies (shared by tool-name cases and toolHint branches) ---
|
|
124
|
+
function renderBashInput(str) {
|
|
125
|
+
const cmd = str('command');
|
|
126
|
+
return cmd ? `**Command**\n\`\`\`bash\n${truncate(cmd, BODY_FIELD_MAX)}\n\`\`\`` : '';
|
|
127
|
+
}
|
|
128
|
+
function renderFileInput(str) {
|
|
129
|
+
const fp = str('file_path') || str('path');
|
|
130
|
+
return fp ? `**File** \`${fp}\`` : '';
|
|
131
|
+
}
|
|
132
|
+
function renderGrepInput(str) {
|
|
133
|
+
const lines = [];
|
|
134
|
+
if (str('pattern'))
|
|
135
|
+
lines.push(`**Pattern** \`${str('pattern')}\``);
|
|
136
|
+
if (str('path'))
|
|
137
|
+
lines.push(`**Path** \`${str('path')}\``);
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
function renderGlobInput(str) {
|
|
141
|
+
const p = str('pattern');
|
|
142
|
+
return p ? `**Pattern** \`${truncate(p, BODY_FIELD_MAX)}\`` : '';
|
|
143
|
+
}
|
|
144
|
+
function renderLsInput(str) {
|
|
145
|
+
const p = str('path');
|
|
146
|
+
return p ? `**Path** \`${p}\`` : '';
|
|
147
|
+
}
|
|
148
|
+
function renderWebFetchInput(str) {
|
|
149
|
+
return str('url') ? `**URL** ${str('url')}` : '';
|
|
150
|
+
}
|
|
151
|
+
function renderWebSearchInput(str) {
|
|
152
|
+
return str('query') ? `**Query** \`${truncate(str('query'), BODY_FIELD_MAX)}\`` : '';
|
|
153
|
+
}
|
|
108
154
|
function renderInput(tool) {
|
|
109
155
|
// P3-6: use the cached parsed record when available (avoids per-render parse).
|
|
110
156
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
@@ -112,44 +158,45 @@ function renderInput(tool) {
|
|
|
112
158
|
if (!rec)
|
|
113
159
|
return '';
|
|
114
160
|
const str = (k) => (typeof rec[k] === 'string' ? rec[k] : '');
|
|
161
|
+
// 信息保真 C5:hint 优先(runner 自愿声明,只消费不推断);缺失时与现状
|
|
162
|
+
// 完全一致。hint 分支与 name case 共用同一渲染函数,无复制粘贴。
|
|
163
|
+
switch (tool.toolHint) {
|
|
164
|
+
case 'shell':
|
|
165
|
+
return renderBashInput(str);
|
|
166
|
+
case 'file':
|
|
167
|
+
case 'patch':
|
|
168
|
+
return renderFileInput(str);
|
|
169
|
+
case 'search':
|
|
170
|
+
return renderGrepInput(str);
|
|
171
|
+
case 'web':
|
|
172
|
+
return renderWebFetchInput(str) || renderWebSearchInput(str);
|
|
173
|
+
default:
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
115
176
|
switch (tool.name) {
|
|
116
177
|
case 'Bash':
|
|
117
|
-
case 'bash':
|
|
118
|
-
|
|
119
|
-
return cmd ? `**Command**\n\`\`\`bash\n${truncate(cmd, BODY_FIELD_MAX)}\n\`\`\`` : '';
|
|
120
|
-
}
|
|
178
|
+
case 'bash':
|
|
179
|
+
return renderBashInput(str);
|
|
121
180
|
case 'Read':
|
|
122
181
|
case 'Edit':
|
|
123
182
|
case 'Write':
|
|
124
183
|
case 'NotebookEdit':
|
|
125
184
|
case 'read':
|
|
126
185
|
case 'edit':
|
|
127
|
-
case 'write':
|
|
128
|
-
|
|
129
|
-
return fp ? `**File** \`${fp}\`` : '';
|
|
130
|
-
}
|
|
186
|
+
case 'write':
|
|
187
|
+
return renderFileInput(str);
|
|
131
188
|
case 'Grep':
|
|
132
|
-
case 'grep':
|
|
133
|
-
|
|
134
|
-
if (str('pattern'))
|
|
135
|
-
lines.push(`**Pattern** \`${str('pattern')}\``);
|
|
136
|
-
if (str('path'))
|
|
137
|
-
lines.push(`**Path** \`${str('path')}\``);
|
|
138
|
-
return lines.join('\n');
|
|
139
|
-
}
|
|
189
|
+
case 'grep':
|
|
190
|
+
return renderGrepInput(str);
|
|
140
191
|
case 'Glob':
|
|
141
|
-
case 'find':
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
case 'ls': {
|
|
146
|
-
const p = str('path');
|
|
147
|
-
return p ? `**Path** \`${p}\`` : '';
|
|
148
|
-
}
|
|
192
|
+
case 'find':
|
|
193
|
+
return renderGlobInput(str);
|
|
194
|
+
case 'ls':
|
|
195
|
+
return renderLsInput(str);
|
|
149
196
|
case 'WebFetch':
|
|
150
|
-
return
|
|
197
|
+
return renderWebFetchInput(str);
|
|
151
198
|
case 'WebSearch':
|
|
152
|
-
return
|
|
199
|
+
return renderWebSearchInput(str);
|
|
153
200
|
default:
|
|
154
201
|
return '';
|
|
155
202
|
}
|
|
@@ -170,7 +217,4 @@ function asRecord(input) {
|
|
|
170
217
|
}
|
|
171
218
|
return null;
|
|
172
219
|
}
|
|
173
|
-
function truncate(s, max) {
|
|
174
|
-
return s.length > max ? `${s.slice(0, max)}…` : s;
|
|
175
|
-
}
|
|
176
220
|
//# sourceMappingURL=tool-render.js.map
|