lark-remote 0.4.5 → 0.5.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 +18 -0
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.js +10 -7
- package/dist/bridge/index.d.ts +9 -9
- package/dist/bridge/index.js +41 -41
- package/dist/bridge/queue-manager.d.ts +12 -12
- package/dist/bridge/queue-manager.js +19 -19
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +1 -1
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +14 -14
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/clone.d.ts +5 -3
- package/dist/clone.js +8 -4
- package/dist/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.js +5 -2
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/connector/index.d.ts +5 -5
- package/dist/connector/index.js +24 -11
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- package/dist/index.js +9 -9
- package/dist/instance-lock.js +4 -3
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +6 -2
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +1 -1
- package/dist/platform/terminator-win32.js +2 -2
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +2 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/index.d.ts +9 -9
- package/dist/router/index.js +34 -34
- package/dist/router/order-exec-dispatch.js +2 -2
- package/dist/runner/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -2
- package/dist/runner/claude/session.js +14 -14
- package/dist/runner/codex/app-server/runner.js +3 -3
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/acp-approval.d.ts +2 -2
- package/dist/runner/common/acp/acp-approval.js +3 -3
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +1 -1
- package/dist/runner/common/connection-based-runner.js +2 -2
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- package/dist/runner/common/spawning-runner.d.ts +21 -8
- package/dist/runner/common/spawning-runner.js +41 -15
- package/dist/runner/dsh/runner.js +2 -2
- package/dist/runner/kimi/acp/runner.d.ts +3 -3
- package/dist/runner/kimi/acp/runner.js +9 -9
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +4 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +3 -3
- package/dist/runner/opencode/acp/translator.js +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.js +4 -4
- package/dist/runner/types.d.ts +1 -1
- package/dist/session/claude/session-index.d.ts +12 -0
- package/dist/session/claude/session-index.js +20 -5
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +12 -12
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.js +2 -2
- package/dist/session/kimi/sessions.d.ts +2 -2
- package/dist/session/kimi/sessions.js +11 -11
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +12 -10
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLogger } from '../logger/index.js';
|
|
2
2
|
import { displayName } from '../platform/path.js';
|
|
3
3
|
/**
|
|
4
|
-
* Per-workspace cap on the number of tasks WAITING to execute
|
|
4
|
+
* Per-workspace cap on the number of tasks WAITING to execute.
|
|
5
5
|
* Without a bound, a message flood grows `queuedTasks` / the promise chain /
|
|
6
6
|
* each task's captured `msg.content` closure without limit, and every enqueued
|
|
7
7
|
* task fires a queue card (amplifying outbound API calls). When the waiting
|
|
@@ -21,7 +21,7 @@ export class QueueManager {
|
|
|
21
21
|
/** Per-workspace queued tasks for queue display cards. Key = cwd, value = task list. */
|
|
22
22
|
queuedTasks = new Map();
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* per-workspace `messageId → QueuedTask` index mirroring `queuedTasks`.
|
|
25
25
|
* O(1) lookup for the task-start removal path (replacing the old double
|
|
26
26
|
* `find`+`findIndex` O(N) scan) and for `getQueuedTask`/`removeFromQueue`/
|
|
27
27
|
* `updateQueuedTaskMessage`. The ordered array remains the source of truth
|
|
@@ -48,7 +48,7 @@ export class QueueManager {
|
|
|
48
48
|
* identifies one task's execution period for the interrupt bookkeeping in
|
|
49
49
|
* `executingSlot`/`interruptedSlots` — replacing the unowned skip-credit
|
|
50
50
|
* counter, which any settle could consume and which therefore leaked a
|
|
51
|
-
* re-armed count after repeated resets
|
|
51
|
+
* re-armed count after repeated resets.
|
|
52
52
|
*/
|
|
53
53
|
slotCounter = 0;
|
|
54
54
|
/**
|
|
@@ -72,7 +72,7 @@ export class QueueManager {
|
|
|
72
72
|
/**
|
|
73
73
|
* MessageIds of tasks that have actually begun executing (cancellation check
|
|
74
74
|
* passed, task removed from the queue). Entries are sticky: they remain
|
|
75
|
-
* after the task settles
|
|
75
|
+
* after the task settles. `handleQueueImmediate` step 6 uses this to
|
|
76
76
|
* distinguish "target was cancelled (never began)" from "target began while
|
|
77
77
|
* the interrupt was in flight" so the final feedback can report the true
|
|
78
78
|
* state instead of telling a running task's user that nothing was
|
|
@@ -95,7 +95,7 @@ export class QueueManager {
|
|
|
95
95
|
this.sendCard = sendCard;
|
|
96
96
|
this.updateCard = updateCard;
|
|
97
97
|
}
|
|
98
|
-
/**
|
|
98
|
+
/** register a queued task in the per-workspace `messageId → task` index. */
|
|
99
99
|
indexAdd(cwd, task) {
|
|
100
100
|
let idx = this.taskIndex.get(cwd);
|
|
101
101
|
if (!idx) {
|
|
@@ -104,11 +104,11 @@ export class QueueManager {
|
|
|
104
104
|
}
|
|
105
105
|
idx.set(task.messageId, task);
|
|
106
106
|
}
|
|
107
|
-
/**
|
|
107
|
+
/** drop a task from the per-workspace index by messageId. */
|
|
108
108
|
indexRemove(cwd, messageId) {
|
|
109
109
|
this.taskIndex.get(cwd)?.delete(messageId);
|
|
110
110
|
}
|
|
111
|
-
/**
|
|
111
|
+
/** O(1) lookup of a queued task by messageId via the index. */
|
|
112
112
|
indexGet(cwd, messageId) {
|
|
113
113
|
return this.taskIndex.get(cwd)?.get(messageId);
|
|
114
114
|
}
|
|
@@ -147,7 +147,7 @@ export class QueueManager {
|
|
|
147
147
|
const currentExecutingCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
148
148
|
const currentQueueLength = this.queuedTasks.get(cwd)?.length ?? 0;
|
|
149
149
|
const hasWaitingTasks = currentExecutingCount > 0 || currentQueueLength > 0;
|
|
150
|
-
//
|
|
150
|
+
// bound the waiting backlog. When the queue is full, reject the
|
|
151
151
|
// task with a visible card instead of appending it (which would grow
|
|
152
152
|
// the promise chain + closures unboundedly under a message flood and
|
|
153
153
|
// fire another queue card). Do NOT increment pendingOrExecutingCount —
|
|
@@ -217,7 +217,7 @@ export class QueueManager {
|
|
|
217
217
|
let livePreview = taskMeta?.messagePreview ?? '';
|
|
218
218
|
// Check if this task was cancelled before executing
|
|
219
219
|
if (messageId) {
|
|
220
|
-
//
|
|
220
|
+
// O(1) index lookup replaces the old `find` + `findIndex`
|
|
221
221
|
// double O(N) scan. The ordered array still drives removal (splice
|
|
222
222
|
// preserves queue order for position display); the index stays in
|
|
223
223
|
// sync via indexRemove.
|
|
@@ -290,7 +290,7 @@ export class QueueManager {
|
|
|
290
290
|
.then(() => {
|
|
291
291
|
getLogger().debug(`[queue-manager] task end cwd=${cwd}`);
|
|
292
292
|
// beganMessageIds entry intentionally retained: sticky by design
|
|
293
|
-
//
|
|
293
|
+
// the marker records "has ever begun", not "is currently
|
|
294
294
|
// running", so a task that began and settled quickly still reports
|
|
295
295
|
// "已开始执行" to queue.immediate.
|
|
296
296
|
this.decrementExecutingCount(cwd, slotId);
|
|
@@ -298,7 +298,7 @@ export class QueueManager {
|
|
|
298
298
|
.catch((err) => {
|
|
299
299
|
getLogger().error('[queue-manager] queue task error:', err);
|
|
300
300
|
// Same sticky rationale as the success settle: the marker survives
|
|
301
|
-
// even when the task errors after beginning
|
|
301
|
+
// even when the task errors after beginning.
|
|
302
302
|
this.decrementExecutingCount(cwd, slotId);
|
|
303
303
|
});
|
|
304
304
|
this.queues.set(cwd, newQueue);
|
|
@@ -435,7 +435,7 @@ export class QueueManager {
|
|
|
435
435
|
* state — pending cards enable both, executing/cancelled cards disable
|
|
436
436
|
* both. Centralizing the pair here eliminates the 3-way duplication
|
|
437
437
|
* between `buildQueueStatusCardElements`, `updateQueueCardToExecuting`,
|
|
438
|
-
* and `updateQueueCardToCancelled` (Clean Code
|
|
438
|
+
* and `updateQueueCardToCancelled` (Clean Code).
|
|
439
439
|
*/
|
|
440
440
|
buildQueueActionButtons(cwd, messageId, disabled) {
|
|
441
441
|
return [
|
|
@@ -476,8 +476,8 @@ export class QueueManager {
|
|
|
476
476
|
try {
|
|
477
477
|
// Prefer the live preview: an edited task's messagePreview is updated in
|
|
478
478
|
// place, and the passed-in snapshot may be stale if the card send was in
|
|
479
|
-
// flight while the user edited
|
|
480
|
-
// passes the live preview captured at begin
|
|
479
|
+
// flight while the user edited. The begin path (started=true) already
|
|
480
|
+
// passes the live preview captured at begin, so the fallback is only
|
|
481
481
|
// exercised when the task is no longer queued.
|
|
482
482
|
const liveTask = this.indexGet(cwd, messageId);
|
|
483
483
|
const stillQueued = liveTask !== undefined;
|
|
@@ -527,7 +527,7 @@ export class QueueManager {
|
|
|
527
527
|
const taskList = this.queuedTasks.get(cwd);
|
|
528
528
|
if (!taskList)
|
|
529
529
|
return false;
|
|
530
|
-
//
|
|
530
|
+
// index is the O(1) presence check; array splice keeps order.
|
|
531
531
|
if (!this.indexGet(cwd, messageId))
|
|
532
532
|
return false;
|
|
533
533
|
const index = taskList.findIndex((t) => t.messageId === messageId);
|
|
@@ -582,7 +582,7 @@ export class QueueManager {
|
|
|
582
582
|
}
|
|
583
583
|
/** Get task metadata from queue. */
|
|
584
584
|
getQueuedTask(cwd, messageId) {
|
|
585
|
-
//
|
|
585
|
+
// O(1) index lookup instead of array `find`.
|
|
586
586
|
return this.indexGet(cwd, messageId);
|
|
587
587
|
}
|
|
588
588
|
/**
|
|
@@ -600,7 +600,7 @@ export class QueueManager {
|
|
|
600
600
|
}
|
|
601
601
|
/** Update the messagePreview for a queued task. Returns true if found and updated. */
|
|
602
602
|
updateQueuedTaskMessage(cwd, messageId, newMessage) {
|
|
603
|
-
//
|
|
603
|
+
// O(1) index lookup instead of array `find`.
|
|
604
604
|
const task = this.indexGet(cwd, messageId);
|
|
605
605
|
if (!task)
|
|
606
606
|
return false;
|
|
@@ -697,7 +697,7 @@ export class QueueManager {
|
|
|
697
697
|
* slot no longer matches the interrupted task's slot, the interrupted task
|
|
698
698
|
* already decremented normally — resetting now would zero the count of the
|
|
699
699
|
* running successor and mark ITS slot interrupted, hiding it from the
|
|
700
|
-
* queue card
|
|
700
|
+
* queue card.
|
|
701
701
|
*/
|
|
702
702
|
resetExecutingCount(cwd, expectedSlot) {
|
|
703
703
|
const currentSlot = this.executingSlot.get(cwd);
|
|
@@ -705,7 +705,7 @@ export class QueueManager {
|
|
|
705
705
|
getLogger().debug(`[queue-manager] resetExecutingCount skip (stopped task settled, slot advanced) cwd=${cwd} expectedSlot=${expectedSlot} currentSlot=${currentSlot ?? 'none'}`);
|
|
706
706
|
return;
|
|
707
707
|
}
|
|
708
|
-
//
|
|
708
|
+
// only reset and grant an interrupt slot when there is actually a
|
|
709
709
|
// pending/executing task.
|
|
710
710
|
const currentCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
711
711
|
if (currentCount === 0) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* renderApprovalArea: renders the approval request area in a run card.
|
|
3
3
|
*
|
|
4
|
-
* Renders command/file/permissions approval areas with button mappings
|
|
4
|
+
* Renders command/file/permissions approval areas with button mappings.
|
|
5
5
|
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
6
|
*/
|
|
7
7
|
import type { ApprovalView } from '../runner/types.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* renderApprovalArea: renders the approval request area in a run card.
|
|
3
3
|
*
|
|
4
|
-
* Renders command/file/permissions approval areas with button mappings
|
|
4
|
+
* Renders command/file/permissions approval areas with button mappings.
|
|
5
5
|
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
6
|
*/
|
|
7
7
|
import { collapsibleMarkdownPanel } from './collapsible.js';
|
|
@@ -13,7 +13,7 @@ function mkNonce() {
|
|
|
13
13
|
}
|
|
14
14
|
/** 计划面板正文字节预算(折叠展示 + 截断保护 28KB 卡片预算)。 */
|
|
15
15
|
const PLAN_PANEL_BYTES = 6000;
|
|
16
|
-
/** reason div(3 处共用);反引号中和防 lark_md
|
|
16
|
+
/** reason div(3 处共用);反引号中和防 lark_md 解析错误。 */
|
|
17
17
|
function reasonDiv(approval) {
|
|
18
18
|
if (!approval.reason)
|
|
19
19
|
return null;
|
|
@@ -39,7 +39,7 @@ export function renderApprovalArea(approval, opts) {
|
|
|
39
39
|
if (!approval)
|
|
40
40
|
return [];
|
|
41
41
|
// 终态(done/error/interrupted/idle_timeout)下 run 已结束:coordinator 已
|
|
42
|
-
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"
|
|
42
|
+
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"。
|
|
43
43
|
if (opts?.terminal && opts.terminal !== 'running' && opts.terminal !== 'finalizing') {
|
|
44
44
|
return [];
|
|
45
45
|
}
|
|
@@ -238,7 +238,7 @@ function renderPlanFeedbackInput(requestId, runId) {
|
|
|
238
238
|
function renderCommandApproval(approval, expired, runId) {
|
|
239
239
|
const elements = [];
|
|
240
240
|
// Command display
|
|
241
|
-
//
|
|
241
|
+
// 命令里的反引号会提前终止 lark_md 行内代码 span,奇数次
|
|
242
242
|
// 反引号极易触发 11311 解析错误导致整卡失败(同 renderFileDiff 的中和策略)。
|
|
243
243
|
const neutralizedCommand = (approval.command ?? '').replace(/`/g, '·');
|
|
244
244
|
const cmdContent = neutralizedCommand
|
|
@@ -422,7 +422,7 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
422
422
|
elements.push(renderOptionRow(approval.requestId, i, option, isSelected, icon, runId));
|
|
423
423
|
}
|
|
424
424
|
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
425
|
-
//
|
|
425
|
+
// 已选文本,避免「点了没反应」的困惑。
|
|
426
426
|
const customSelected = q.selected?.find((s) => !q.options.some((o) => o.label === s));
|
|
427
427
|
if (customSelected) {
|
|
428
428
|
elements.push({
|
|
@@ -430,7 +430,7 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
430
430
|
text: { tag: 'lark_md', content: `✍️ 自定义答案:${customSelected}` },
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
|
-
//
|
|
433
|
+
// 单选问题提供自定义答案(Other)输入——输入文本后提交
|
|
434
434
|
// (移动端键盘回车/完成键,桌面端输入框右侧提交图标),input_value 经
|
|
435
435
|
// connector raw 事件回传。
|
|
436
436
|
// 显隐按 isOther !== false:Kimi form 会丢弃非声明选项值,翻译时置
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CardSession, type CardChannel } from './card-session.js';
|
|
2
2
|
import { type BashState, type BashRenderOptions } from './bash-renderer.js';
|
|
3
3
|
/**
|
|
4
|
-
* Store-time
|
|
4
|
+
* Store-time 输出上限(字符,层①)。建议「store-time 截断,
|
|
5
5
|
* 保留尾部,上限如 24~64KB」——取区间下界 24_000 字符(字符上限 ×4 字节最坏
|
|
6
6
|
* ~96KB/字段)。渲染层本就只展示尾部(bash-renderer 截断到 12KB 字节),store 截断
|
|
7
7
|
* 不影响显示内容,只保证内存有界:!yes / !cat 大文件不再把全部输出驻留在 session
|
|
@@ -9,7 +9,7 @@ import { type BashState, type BashRenderOptions } from './bash-renderer.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const BASH_OUTPUT_STORE_CAP = 24000;
|
|
11
11
|
/**
|
|
12
|
-
* Store-time
|
|
12
|
+
* Store-time 截断入口(层①/层④共用)。bridge 本地 output/stderr 与 session
|
|
13
13
|
* state 都走它,保证任意路径驻留的字符串 ≤ BASH_OUTPUT_STORE_CAP 字符。
|
|
14
14
|
* 调用方传入的 value 通常已 ≤ CAP(增量场景 output 恒 ≤ CAP,+chunk 后 ≤ CAP+chunk),
|
|
15
15
|
* slice(-CAP) 成本 O(CAP+chunk),不会把 O(n²) 截断问题带回桥接循环。
|
|
@@ -49,7 +49,7 @@ export declare class BashCardSession extends CardSession<BashState, BashRenderOp
|
|
|
49
49
|
output?: string;
|
|
50
50
|
}): Promise<void>;
|
|
51
51
|
/**
|
|
52
|
-
* 合并 patch 并对 output/stderr 做 store-time
|
|
52
|
+
* 合并 patch 并对 output/stderr 做 store-time 截断(层①)。
|
|
53
53
|
* 截断发生在入口(store 时),渲染层无需再为大 state 付代价。
|
|
54
54
|
*/
|
|
55
55
|
private applyCap;
|
|
@@ -2,7 +2,7 @@ import { CardSession } from './card-session.js';
|
|
|
2
2
|
import { renderBashCard } from './bash-renderer.js';
|
|
3
3
|
import { keepTail } from '../common/truncate.js';
|
|
4
4
|
/**
|
|
5
|
-
* Store-time
|
|
5
|
+
* Store-time 输出上限(字符,层①)。建议「store-time 截断,
|
|
6
6
|
* 保留尾部,上限如 24~64KB」——取区间下界 24_000 字符(字符上限 ×4 字节最坏
|
|
7
7
|
* ~96KB/字段)。渲染层本就只展示尾部(bash-renderer 截断到 12KB 字节),store 截断
|
|
8
8
|
* 不影响显示内容,只保证内存有界:!yes / !cat 大文件不再把全部输出驻留在 session
|
|
@@ -10,7 +10,7 @@ import { keepTail } from '../common/truncate.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export const BASH_OUTPUT_STORE_CAP = 24_000;
|
|
12
12
|
/**
|
|
13
|
-
* Store-time
|
|
13
|
+
* Store-time 截断入口(层①/层④共用)。bridge 本地 output/stderr 与 session
|
|
14
14
|
* state 都走它,保证任意路径驻留的字符串 ≤ BASH_OUTPUT_STORE_CAP 字符。
|
|
15
15
|
* 调用方传入的 value 通常已 ≤ CAP(增量场景 output 恒 ≤ CAP,+chunk 后 ≤ CAP+chunk),
|
|
16
16
|
* slice(-CAP) 成本 O(CAP+chunk),不会把 O(n²) 截断问题带回桥接循环。
|
|
@@ -92,7 +92,7 @@ export class BashCardSession extends CardSession {
|
|
|
92
92
|
this.release();
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* 合并 patch 并对 output/stderr 做 store-time
|
|
95
|
+
* 合并 patch 并对 output/stderr 做 store-time 截断(层①)。
|
|
96
96
|
* 截断发生在入口(store 时),渲染层无需再为大 state 付代价。
|
|
97
97
|
*/
|
|
98
98
|
applyCap(patch) {
|
|
@@ -78,7 +78,7 @@ function assembleCard(state, elements) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Build output/stderr elements shared by normal & degraded paths (
|
|
81
|
+
* Build output/stderr elements shared by normal & degraded paths (单源):
|
|
82
82
|
* 除截断预算(OUTPUT_MAX_BYTES / DEGRADED_OUTPUT_BYTES)外逐行同构。
|
|
83
83
|
*/
|
|
84
84
|
function buildOutputElements(state, maxBytes) {
|
|
@@ -59,7 +59,7 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
59
59
|
* 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
|
|
60
60
|
* coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
|
|
61
61
|
* pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
|
|
62
|
-
* 到达的 push/update 会停留为 stale
|
|
62
|
+
* 到达的 push/update 会停留为 stale 中间帧。
|
|
63
63
|
*/
|
|
64
64
|
protected scheduleFlush(): void;
|
|
65
65
|
/**
|
|
@@ -87,14 +87,14 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
87
87
|
/** The messageId of the streaming card, available after `start()` resolves. */
|
|
88
88
|
get cardMessageId(): string | undefined;
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* 合批窗口(ms)。连续事件各触发一次全卡 render + patch 是流式 CPU 主要
|
|
91
91
|
* 来源;窗口内多个事件复用同一次延迟 flush。窗口大小可通过 constructor 覆盖
|
|
92
92
|
* (测试用小窗口/0 禁用)。run/bash 子类共用的 flush 骨架(scheduleFlush/flush/
|
|
93
93
|
* cancelPendingFlush/settle)下沉到基类。
|
|
94
94
|
*/
|
|
95
95
|
protected readonly coalesceMs: number;
|
|
96
96
|
private flushTimer;
|
|
97
|
-
private flushInFlight; /**
|
|
97
|
+
private flushInFlight; /** in-flight flush 期间到达的 push/update 标记。scheduleFlush 在
|
|
98
98
|
* flushInFlight 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,
|
|
99
99
|
* in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
|
|
100
100
|
* finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
|
|
@@ -102,7 +102,7 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
102
102
|
/** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
|
|
103
103
|
* finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
|
|
104
104
|
* terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
|
|
105
|
-
* pre-terminal 可能在 terminal 之后 resolve 留下"思考中"
|
|
105
|
+
* pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧。 */
|
|
106
106
|
protected flushP: Promise<void> | undefined;
|
|
107
107
|
start(): Promise<void>;
|
|
108
108
|
settle(): Promise<StreamSettleResult>;
|
|
@@ -46,7 +46,7 @@ export class CardSession {
|
|
|
46
46
|
* 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
|
|
47
47
|
* coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
|
|
48
48
|
* pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
|
|
49
|
-
* 到达的 push/update 会停留为 stale
|
|
49
|
+
* 到达的 push/update 会停留为 stale 中间帧。
|
|
50
50
|
*/
|
|
51
51
|
scheduleFlush() {
|
|
52
52
|
if (this.flushTimer)
|
|
@@ -82,7 +82,7 @@ export class CardSession {
|
|
|
82
82
|
this.flushInFlight = false;
|
|
83
83
|
// flushInFlight 守卫保证同一时刻只有一个 in-flight flush,故可无条件清空。
|
|
84
84
|
this.flushP = undefined;
|
|
85
|
-
//
|
|
85
|
+
// in-flight 期间若有 push/update 到达(pendingReschedule),重新调度一次
|
|
86
86
|
// flush 渲染累积事件,避免 stale 中间帧。终态守卫在 flush() 入口已拦截,
|
|
87
87
|
// 故 finish 后不会误触发。
|
|
88
88
|
if (this.pendingReschedule) {
|
|
@@ -117,14 +117,14 @@ export class CardSession {
|
|
|
117
117
|
return this.messageId;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
120
|
+
* 合批窗口(ms)。连续事件各触发一次全卡 render + patch 是流式 CPU 主要
|
|
121
121
|
* 来源;窗口内多个事件复用同一次延迟 flush。窗口大小可通过 constructor 覆盖
|
|
122
122
|
* (测试用小窗口/0 禁用)。run/bash 子类共用的 flush 骨架(scheduleFlush/flush/
|
|
123
123
|
* cancelPendingFlush/settle)下沉到基类。
|
|
124
124
|
*/
|
|
125
125
|
coalesceMs;
|
|
126
126
|
flushTimer;
|
|
127
|
-
flushInFlight = false; /**
|
|
127
|
+
flushInFlight = false; /** in-flight flush 期间到达的 push/update 标记。scheduleFlush 在
|
|
128
128
|
* flushInFlight 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,
|
|
129
129
|
* in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
|
|
130
130
|
* finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
|
|
@@ -132,7 +132,7 @@ export class CardSession {
|
|
|
132
132
|
/** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
|
|
133
133
|
* finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
|
|
134
134
|
* terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
|
|
135
|
-
* pre-terminal 可能在 terminal 之后 resolve 留下"思考中"
|
|
135
|
+
* pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧。 */
|
|
136
136
|
flushP;
|
|
137
137
|
async start() {
|
|
138
138
|
if (this.streamOutcome) {
|
|
@@ -158,7 +158,7 @@ export class CardSession {
|
|
|
158
158
|
// the controller is ready — by which time events pushed before the
|
|
159
159
|
// producer ran (see test_anchor_events_before_controller_ready_...) have
|
|
160
160
|
// already updated `this.state`. Reusing the initial card here would drop
|
|
161
|
-
// those early events. The
|
|
161
|
+
// those early events. The"reuse first render" optimization was
|
|
162
162
|
// rejected for this reason (premise "two renders with identical input"
|
|
163
163
|
// does not hold).
|
|
164
164
|
const stream = this.connector.streamCard(this.chatId, this.safeRenderCard(), async (controller) => {
|
|
@@ -36,7 +36,7 @@ export class RunCardSession extends CardSession {
|
|
|
36
36
|
async push(event) {
|
|
37
37
|
// state 每事件立即 reduce(始终最新),但 updateCard 延迟到合批窗口末尾。
|
|
38
38
|
// 窗口内多次 push 复用同一 flushTimer,只 render + controller.update 一次
|
|
39
|
-
//
|
|
39
|
+
// push 立即 resolve(fire-and-forget render)—— bridge 串行
|
|
40
40
|
// await 不被 render 阻塞,循环快速灌入事件,多个事件落进同一窗口被合并。
|
|
41
41
|
//
|
|
42
42
|
// 首 push 也走窗口(无 immediate-first-flush):start() 已发初始卡(streaming
|
|
@@ -55,7 +55,7 @@ export class RunCardSession extends CardSession {
|
|
|
55
55
|
this.cancelPendingFlush();
|
|
56
56
|
// 若有 in-flight flush 正在 await updateCard,必须等它完成后再发终态 patch。
|
|
57
57
|
// 否则两条并发 controller.update 无 FIFO 保证,in-flight 的 pre-terminal
|
|
58
|
-
// patch 可能在终态 patch 之后 resolve → 用户看到"思考中"
|
|
58
|
+
// patch 可能在终态 patch 之后 resolve → 用户看到"思考中"终帧。
|
|
59
59
|
// await 保证顺序:pre-terminal 先落地 → terminal 后落地 → terminal 胜出。
|
|
60
60
|
if (this.flushP)
|
|
61
61
|
await this.flushP;
|
|
@@ -37,7 +37,7 @@ type BlockGroup = {
|
|
|
37
37
|
diff?: string;
|
|
38
38
|
timestamp?: string;
|
|
39
39
|
};
|
|
40
|
-
/** 每个块的截断后内容(thinking/plan/text
|
|
40
|
+
/** 每个块的截断后内容(thinking/plan/text),测量与渲染共用(建议项)。 */
|
|
41
41
|
type GroupContentPrepared = Map<BlockGroup, string>;
|
|
42
42
|
/** 导出供测试断言估算精度(估算 ≈ 实际渲染字节,见 tests/anchor/run-card/run-card.test.ts)。 */
|
|
43
43
|
export declare function estimateCardBytes(state: RunState, options?: RunCardRenderOptions, shared?: {
|
|
@@ -16,7 +16,7 @@ const EXTREME_TOOL_KEEP = 1; // Extreme: keep last 1 tool panel
|
|
|
16
16
|
// 影子测量:估算 = 精确复刻 normal 路径将要渲染的 JSON 字节。面板外壳、statusRow、
|
|
17
17
|
// summary、header、按钮全部实测(measureJson),块内容经与渲染相同的变换
|
|
18
18
|
// (truncateUtf8 截断 + markdownDiv 的 escapeMarkdown + JSON.stringify)精确测量,
|
|
19
|
-
//
|
|
19
|
+
// 不依赖任何转义因子或拍脑袋常数(第二轮:CJK/ASCII 转义双向精确)。
|
|
20
20
|
const DEGRADED_THRESHOLD = 24_000; // 估算低于此值走正常路径(留 4KB 裕度,相对 CARD_BUDGET_BYTES=28000);否则直接 degraded。调整时同步评估裕度是否仍覆盖估算偏差
|
|
21
21
|
/** 精确测量对象经 JSON.stringify 后的 UTF-8 字节数。 */
|
|
22
22
|
function measureJson(obj) {
|
|
@@ -275,7 +275,7 @@ function actionButtonList(state, options) {
|
|
|
275
275
|
actionButtons.push(newSessionButton());
|
|
276
276
|
return actionButtons;
|
|
277
277
|
}
|
|
278
|
-
/** Bottom action row: spacer + column_set of actionButtonList (
|
|
278
|
+
/** Bottom action row: spacer + column_set of actionButtonList (估算与渲染共用)。 */
|
|
279
279
|
function actionRow(state, options) {
|
|
280
280
|
const actionButtons = actionButtonList(state, options);
|
|
281
281
|
if (actionButtons.length === 0)
|
|
@@ -293,7 +293,7 @@ function actionRow(state, options) {
|
|
|
293
293
|
];
|
|
294
294
|
}
|
|
295
295
|
/** 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
296
|
-
* 例外:skeleton 兜底路径中 approvalArea 在 actionRow
|
|
296
|
+
* 例外:skeleton 兜底路径中 approvalArea 在 actionRow 之前(信息保真),
|
|
297
297
|
* 保证极端降级时审批入口与 stop/new-session 按钮同时可见。 */
|
|
298
298
|
function approvalArea(state) {
|
|
299
299
|
const elements = [];
|
|
@@ -306,7 +306,7 @@ function approvalArea(state) {
|
|
|
306
306
|
}
|
|
307
307
|
return elements;
|
|
308
308
|
}
|
|
309
|
-
/** 底部操作行之前的内容区提示:被 MAX_BLOCKS
|
|
309
|
+
/** 底部操作行之前的内容区提示:被 MAX_BLOCKS 截掉的早期块计数(信息保真)。 */
|
|
310
310
|
function omittedBlocksHint(state) {
|
|
311
311
|
return state.omittedBlocks !== undefined && state.omittedBlocks > 0
|
|
312
312
|
? [markdownDiv(`_💡 已省略 ${state.omittedBlocks} 个早期步骤_`)]
|
|
@@ -346,7 +346,7 @@ function buildFallbackElements(state, options, tier) {
|
|
|
346
346
|
if (toolsOmitted > 0) {
|
|
347
347
|
elements.push(markdownDiv(`_💡 另外 ${toolsOmitted} 个工具调用已省略_`));
|
|
348
348
|
}
|
|
349
|
-
//
|
|
349
|
+
// use object references (like the extreme path) instead of timestamps.
|
|
350
350
|
// RunBlock.timestamp has millisecond precision; ≥2 thinking blocks sharing a
|
|
351
351
|
// timestamp collapse to one Set entry, so `has(block.timestamp)` lets ALL
|
|
352
352
|
// same-timestamp blocks (including ones meant to be omitted) pass through —
|
|
@@ -364,7 +364,7 @@ function buildFallbackElements(state, options, tier) {
|
|
|
364
364
|
}
|
|
365
365
|
return true; // keep all non-thinking, non-tool blocks
|
|
366
366
|
});
|
|
367
|
-
// 信息保真
|
|
367
|
+
// 信息保真 plan/file_change 块在降级路径不渲染内容(正常路径已可见,
|
|
368
368
|
// 降级预算紧张),但必须计数留痕,禁止静默丢弃。
|
|
369
369
|
// 计数在渲染前完成,提示与 thinking/tool 省略提示一并置于卡片顶部——不能等
|
|
370
370
|
// 内容循环结束再追加,否则提示会落在最后一条输出之后,打乱「输出收尾」观感。
|
|
@@ -467,7 +467,7 @@ function buildSkeletonElements(state, options) {
|
|
|
467
467
|
elements.push(markdownDiv('_⚠️ 输出过大且含大量转义字符,已省略全部内容以避免超限;完整内容请查日志_'));
|
|
468
468
|
// Summary (token stats etc.) — static, bounded size
|
|
469
469
|
elements.push(...buildSummaryContent(state));
|
|
470
|
-
// 信息保真
|
|
470
|
+
// 信息保真 skeleton 也渲染审批区——极端兜底不丢审批入口
|
|
471
471
|
//(待审批被静默吞掉 = 用户永远看不到审批请求)。
|
|
472
472
|
elements.push(...approvalArea(state));
|
|
473
473
|
// Bottom action row: stop (if running/finalizing) + compact (if applicable) + new session (always).
|
|
@@ -514,7 +514,7 @@ function bucketThinkingAndTool(blocks) {
|
|
|
514
514
|
* 到 degraded(安全网不丢)。
|
|
515
515
|
*/
|
|
516
516
|
/**
|
|
517
|
-
*
|
|
517
|
+
* 镜像对单源:thinking/plan/file_change 三个 collapsible 面板组的
|
|
518
518
|
* { title, expanded, border, content } 描述,估算(measurePanelBytes)与渲染
|
|
519
519
|
* (collapsibleMarkdownPanel)共用——此前同一套标题/opIcon/内容模板写两遍。
|
|
520
520
|
* text(renderTextBlock)与 tool(renderTool)形态特殊,由调用方各自处理;
|
|
@@ -552,19 +552,19 @@ function describePanelGroup(group, prepared) {
|
|
|
552
552
|
/** 导出供测试断言估算精度(估算 ≈ 实际渲染字节,见 tests/anchor/run-card/run-card.test.ts)。 */
|
|
553
553
|
export function estimateCardBytes(state, options = {}, shared) {
|
|
554
554
|
const finalized = state.terminal !== 'running';
|
|
555
|
-
//
|
|
555
|
+
// renderRunCard 传入共享 groups/prepared 时直接复用(截断只做一次);
|
|
556
556
|
// 独立调用(测试/预算探针)时自建,行为与原先一致。
|
|
557
557
|
const groups = shared?.groups ?? groupBlocks(state.blocks);
|
|
558
558
|
const prepared = shared?.prepared ?? prepareGroupContent(groups);
|
|
559
559
|
// 卡片外壳直接实测 assembleRunCard 空元素产物(schema/config/header/body 结构
|
|
560
|
-
//
|
|
560
|
+
// 与正式渲染完全一致);status/summary/操作行同样实测渲染结构。
|
|
561
561
|
// 注:审批区(approvalArea)不参与估算——待审批通常在途且体积小,超预算由
|
|
562
562
|
// renderRunCard 的 stringify 兜底捕获。
|
|
563
563
|
let total = measureJson(assembleRunCard(state, options, [])) +
|
|
564
564
|
measureJson(statusRow(state)) +
|
|
565
565
|
measureJson(buildSummaryContent(state, { includeNotices: false })) +
|
|
566
566
|
measureJson(actionRow(state, options));
|
|
567
|
-
// 块内容:与 buildChronologicalContent 同源(describePanelGroup
|
|
567
|
+
// 块内容:与 buildChronologicalContent 同源(describePanelGroup 单源)
|
|
568
568
|
let renderedAny = false;
|
|
569
569
|
for (const group of groups) {
|
|
570
570
|
if (group.kind === 'tool') {
|
|
@@ -620,7 +620,7 @@ function measurePanelBytes(title, expanded, border, content) {
|
|
|
620
620
|
*/
|
|
621
621
|
export function renderRunCard(state, options = {}) {
|
|
622
622
|
// 先估后建:用廉价估算预测是否超 28KB 预算,明显超预算直接跳过完整卡构建走
|
|
623
|
-
// degraded。
|
|
623
|
+
// degraded。groups/prepared 只算一次,测量与正式渲染共用同一份截断结果,
|
|
624
624
|
// 避免影子测量 + 正式渲染对每个超限块双倍截断。
|
|
625
625
|
const groups = groupBlocks(state.blocks);
|
|
626
626
|
const prepared = prepareGroupContent(groups);
|
|
@@ -680,9 +680,9 @@ function buildChronologicalContent(state, prepared, groups) {
|
|
|
680
680
|
const elements = [];
|
|
681
681
|
// finalized = 非 running(含 finalizing:主结果已出,thinking 折叠)
|
|
682
682
|
const finalized = state.terminal !== 'running';
|
|
683
|
-
//
|
|
683
|
+
// renderRunCard 传入同一 groups 数组时直接复用(prepared 的键是组对象
|
|
684
684
|
// 引用,重建数组会让 prepared.get 永远 miss,退化为二次截断)。
|
|
685
|
-
//
|
|
685
|
+
// 面板组描述经 describePanelGroup 与估算共享同一份模板。
|
|
686
686
|
// notices 作为普通消息按到达时间插入内容流(有内容时不再沉到 summary)。
|
|
687
687
|
const contentItems = mergeNoticesIntoContent(groups, state.notices);
|
|
688
688
|
for (const item of contentItems) {
|
package/dist/card/run-state.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface ToolEntry {
|
|
|
9
9
|
name: string;
|
|
10
10
|
input: unknown;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* cached parse of `input` (the truncated string) into a record, so
|
|
13
13
|
* `tool-render.ts`'s `asRecord` does not re-`JSON.parse` on every render.
|
|
14
14
|
* Computed once at store time in `reduceAssistantEvent` from the SAME
|
|
15
15
|
* truncated string that the old per-render `asRecord` parsed — so a
|
|
@@ -121,7 +121,7 @@ export interface RunState {
|
|
|
121
121
|
/** 压缩前上下文水位(codex compact 卡展示「压缩前 X → 压缩后 Y」)。 */
|
|
122
122
|
compactPreContextLength?: number;
|
|
123
123
|
/**
|
|
124
|
-
* 待审批请求列表(app-server
|
|
124
|
+
* 待审批请求列表(app-server 模式)。同一 turn 内并发多个审批
|
|
125
125
|
* 时全部渲染,避免后到者顶掉先到者的按钮(单槽会丢 UI,只能等超时 cancel)。
|
|
126
126
|
*/
|
|
127
127
|
approvals?: Array<{
|
package/dist/card/run-state.js
CHANGED
|
@@ -382,7 +382,7 @@ function reduceAssistantEvent(state, event) {
|
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
384
|
else {
|
|
385
|
-
//
|
|
385
|
+
// parse the truncated stored string ONCE here (not on every render)
|
|
386
386
|
// and cache the result on `parsedInput`. tool-render's asRecord then hits
|
|
387
387
|
// the object branch with zero parsing. The parse uses the SAME truncated
|
|
388
388
|
// string as the old per-render parse, so truncation-broken JSON still
|
|
@@ -450,7 +450,7 @@ function mergeToolUse(existing, incoming) {
|
|
|
450
450
|
};
|
|
451
451
|
}
|
|
452
452
|
/** Handle tool_result content blocks (user message with tool results).
|
|
453
|
-
*
|
|
453
|
+
* rebuild ONLY the block matching `tool_use_id`, copying every other
|
|
454
454
|
* block by reference. The old `.map()` recreated all block objects per
|
|
455
455
|
* tool_result (O(N) allocations on the run-card hot path). Footer still
|
|
456
456
|
* advances to 'streaming' on every content block (parity with old behavior);
|
|
@@ -605,7 +605,7 @@ function stringifyUnknown(value) {
|
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
/**
|
|
608
|
-
*
|
|
608
|
+
* parse a (possibly truncated) string into a record, mirroring what
|
|
609
609
|
* `tool-render.ts`'s `asRecord` did on every render. Returns `null` when the
|
|
610
610
|
* string is empty, non-JSON, or parses to a non-object — so a truncation-broken
|
|
611
611
|
* JSON yields `null` exactly as the old per-render parse did. Computed once at
|
|
@@ -20,7 +20,7 @@ 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. An explicit `toolHint` (declared by the runner, 信息保真
|
|
23
|
+
* block. An explicit `toolHint` (declared by the runner, 信息保真) selects
|
|
24
24
|
* the same rendering by family instead of name. There is no raw-JSON fallback
|
|
25
25
|
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
26
26
|
* args through the panel header's `summarizeInput` fallback.
|
package/dist/card/tool-render.js
CHANGED
|
@@ -29,13 +29,13 @@ const BODY_TOTAL_MAX = 2500;
|
|
|
29
29
|
*/
|
|
30
30
|
export function toolHeaderText(tool) {
|
|
31
31
|
const icon = tool.status === 'ok' ? '✅' : tool.status === 'error' ? '❌' : '⏳';
|
|
32
|
-
//
|
|
32
|
+
// use the cached parsed record when available (avoids per-render parse).
|
|
33
33
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
34
34
|
const summary = summarizeInput(tool.name, input);
|
|
35
|
-
// 信息保真
|
|
35
|
+
// 信息保真 ACP kind 映射后 name 与原始 title 不同时,title 以
|
|
36
36
|
// ` — {summary}` 追加在标题末尾(截断沿用 HEADER_SUMMARY_MAX)。
|
|
37
37
|
// 用 truncate(suffix 为空)而非裸 slice:裸 slice 按 UTF-16 码元切,
|
|
38
|
-
// 切点落在 emoji
|
|
38
|
+
// 切点落在 emoji 代理对中间会留下孤立代理项(同族)。
|
|
39
39
|
const blockSummary = tool.summary
|
|
40
40
|
? truncate(tool.summary.replace(/\s+/g, ' ').trim(), HEADER_SUMMARY_MAX, { suffix: '' })
|
|
41
41
|
: '';
|
|
@@ -51,7 +51,7 @@ export function toolHeaderText(tool) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Structured body markdown for a tool call. Renders input fields by tool name
|
|
53
53
|
* (Bash → command, Read/Edit/Write → file_path, etc.) and output in a code
|
|
54
|
-
* block. An explicit `toolHint` (declared by the runner, 信息保真
|
|
54
|
+
* block. An explicit `toolHint` (declared by the runner, 信息保真) selects
|
|
55
55
|
* the same rendering by family instead of name. There is no raw-JSON fallback
|
|
56
56
|
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
57
57
|
* args through the panel header's `summarizeInput` fallback.
|
|
@@ -156,13 +156,13 @@ function renderWebSearchInput(str) {
|
|
|
156
156
|
return str('query') ? `**Query** \`${truncate(str('query'), BODY_FIELD_MAX)}\`` : '';
|
|
157
157
|
}
|
|
158
158
|
function renderInput(tool) {
|
|
159
|
-
//
|
|
159
|
+
// use the cached parsed record when available (avoids per-render parse).
|
|
160
160
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
161
161
|
const rec = asRecord(input);
|
|
162
162
|
if (!rec)
|
|
163
163
|
return '';
|
|
164
164
|
const str = (k) => (typeof rec[k] === 'string' ? rec[k] : '');
|
|
165
|
-
// 信息保真
|
|
165
|
+
// 信息保真 hint 优先(runner 自愿声明,只消费不推断);缺失时与现状
|
|
166
166
|
// 完全一致。hint 分支与 name case 共用同一渲染函数,无复制粘贴。
|
|
167
167
|
switch (tool.toolHint) {
|
|
168
168
|
case 'shell':
|