lark-remote 0.5.10 → 0.5.13
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 +51 -2
- package/README.en.md +1 -14
- package/README.md +1 -14
- package/dist/binder.js +7 -1
- package/dist/bridge/approval-coordinator.d.ts +1 -0
- package/dist/bridge/approval-coordinator.js +12 -24
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +89 -230
- package/dist/bridge/run-approval.d.ts +15 -0
- package/dist/bridge/run-approval.js +41 -0
- package/dist/bridge/run-usage.d.ts +46 -0
- package/dist/bridge/run-usage.js +96 -0
- package/dist/bridge/session-writeback.d.ts +18 -0
- package/dist/bridge/session-writeback.js +36 -0
- package/dist/card/approval-render.d.ts +14 -0
- package/dist/card/approval-render.js +155 -29
- package/dist/card/bash-card-session.js +10 -5
- package/dist/card/bash-renderer.js +11 -23
- package/dist/card/card-budget.js +24 -4
- package/dist/card/card-session.d.ts +2 -0
- package/dist/card/card-session.js +20 -22
- package/dist/card/card-shared.d.ts +4 -4
- package/dist/card/card-shared.js +3 -9
- package/dist/card/collapsible.js +3 -2
- package/dist/card/final-answer-overflow.d.ts +7 -0
- package/dist/card/final-answer-overflow.js +46 -0
- package/dist/card/run-card-session.js +2 -7
- package/dist/card/run-renderer.d.ts +14 -0
- package/dist/card/run-renderer.js +150 -10
- package/dist/card/run-state.d.ts +12 -1
- package/dist/card/run-state.js +73 -34
- package/dist/common/text-width.js +5 -0
- package/dist/common/tool-summary.js +4 -2
- package/dist/config/codex-config.js +5 -3
- package/dist/config/index.d.ts +4 -2
- package/dist/config/index.js +19 -5
- package/dist/inbound/turn-assembler.js +6 -0
- package/dist/index.js +15 -391
- package/dist/instance-lock.d.ts +9 -1
- package/dist/instance-lock.js +42 -7
- package/dist/message-handlers.d.ts +26 -0
- package/dist/message-handlers.js +275 -0
- package/dist/platform/identity.js +4 -3
- package/dist/platform/path.js +3 -1
- package/dist/platform/terminator-win32.d.ts +13 -1
- package/dist/platform/terminator-win32.js +4 -14
- package/dist/router/card-action-payload.d.ts +1 -1
- package/dist/router/card-action-payload.js +1 -1
- package/dist/router/commands/ls.d.ts +9 -0
- package/dist/router/commands/ls.js +243 -0
- package/dist/router/commands/order.d.ts +12 -0
- package/dist/router/commands/order.js +178 -0
- package/dist/router/commands/resume.d.ts +47 -0
- package/dist/router/commands/resume.js +246 -0
- package/dist/router/commands/ws.d.ts +15 -0
- package/dist/router/commands/ws.js +189 -0
- package/dist/router/config/claude.js +3 -3
- package/dist/router/config/codex.js +2 -6
- package/dist/router/config/common/turn-idle.d.ts +2 -0
- package/dist/router/config/common/turn-idle.js +9 -0
- package/dist/router/config/kimi.js +2 -6
- package/dist/router/config/opencode.js +2 -6
- package/dist/router/index.d.ts +14 -22
- package/dist/router/index.js +93 -1133
- package/dist/router/paging.d.ts +13 -0
- package/dist/router/paging.js +20 -0
- package/dist/router/path-validation.d.ts +27 -0
- package/dist/router/path-validation.js +45 -0
- package/dist/runner/claude/runner.d.ts +3 -4
- package/dist/runner/claude/runner.js +4 -5
- package/dist/runner/claude/session.js +1 -1
- package/dist/runner/codex/app-server/runner.js +12 -0
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +65 -9
- package/dist/runner/common/acp/base-translator.d.ts +15 -8
- package/dist/runner/common/acp/base-translator.js +38 -7
- package/dist/runner/common/jsonl-stream.js +12 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +11 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -1
- package/dist/runner/common/jsonrpc/transport.js +7 -1
- package/dist/runner/common/spawning-runner.js +10 -4
- package/dist/runner/common/turn-started.d.ts +2 -0
- package/dist/runner/common/turn-started.js +10 -0
- package/dist/runner/dsh/approval.js +16 -3
- package/dist/runner/dsh/auth.js +2 -3
- package/dist/runner/dsh/client.d.ts +32 -7
- package/dist/runner/dsh/client.js +115 -149
- package/dist/runner/dsh/daemon.d.ts +0 -2
- package/dist/runner/dsh/daemon.js +1 -6
- package/dist/runner/dsh/errors.d.ts +5 -0
- package/dist/runner/dsh/errors.js +11 -0
- package/dist/runner/dsh/exit-cancel.d.ts +0 -2
- package/dist/runner/dsh/exit-cancel.js +63 -24
- package/dist/runner/dsh/home.d.ts +2 -0
- package/dist/runner/dsh/home.js +8 -0
- package/dist/runner/dsh/inflight.d.ts +0 -2
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.d.ts +26 -0
- package/dist/runner/dsh/mux-stream.js +157 -0
- package/dist/runner/dsh/runner.d.ts +7 -0
- package/dist/runner/dsh/runner.js +42 -5
- package/dist/runner/dsh/translator.d.ts +0 -10
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +42 -5
- package/dist/runner/kimi/acp/translator.d.ts +8 -0
- package/dist/runner/kimi/acp/translator.js +11 -12
- package/dist/runner/opencode/acp/translator.js +1 -12
- package/dist/runner/pi/rpc/runner.js +33 -5
- package/dist/runner/pi/rpc/translator.d.ts +2 -8
- package/dist/runner/pi/rpc/translator.js +2 -7
- package/dist/runner/registry.d.ts +0 -19
- package/dist/runner/registry.js +0 -32
- package/dist/runner/resolve-agent-choices.js +5 -1
- package/dist/runner/sync-agent-choices.js +4 -1
- package/dist/runner/types.d.ts +17 -1
- package/dist/session/claude/session-index.js +13 -4
- package/dist/session/codex/rollout-reader.js +51 -26
- package/dist/session/common/usage-accumulator.d.ts +10 -1
- package/dist/session/common/usage-accumulator.js +10 -2
- package/dist/session/dsh/sessions.js +29 -9
- package/dist/session/kimi/sessions.js +11 -7
- package/dist/session/opencode/sessions.js +15 -7
- package/dist/session/pi/sessions.js +3 -2
- package/dist/session/session-store.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { normalizeResultUsage } from '../runner/common/result-usage.js';
|
|
2
|
+
export class RunUsageCollector {
|
|
3
|
+
contextLength;
|
|
4
|
+
finalContextLength;
|
|
5
|
+
finalContextLimit;
|
|
6
|
+
finalCompactCount;
|
|
7
|
+
finalCacheReadTokens;
|
|
8
|
+
finalCacheCreationTokens;
|
|
9
|
+
finalTotalTokens;
|
|
10
|
+
liveInput;
|
|
11
|
+
liveOutput;
|
|
12
|
+
liveContextLimit;
|
|
13
|
+
finalInputTokens;
|
|
14
|
+
finalOutputTokens;
|
|
15
|
+
finalCumulativeTotalTokens;
|
|
16
|
+
finalCumulativeInputTokens;
|
|
17
|
+
finalCumulativeOutputTokens;
|
|
18
|
+
finalCumulativeCacheReadTokens;
|
|
19
|
+
finalCumulativeCacheCreationTokens;
|
|
20
|
+
finalReasoningTokens;
|
|
21
|
+
resultNotSuccess = false;
|
|
22
|
+
get liveInputTokens() {
|
|
23
|
+
return this.liveInput;
|
|
24
|
+
}
|
|
25
|
+
get liveOutputTokens() {
|
|
26
|
+
return this.liveOutput;
|
|
27
|
+
}
|
|
28
|
+
observeCompaction(postTokens) {
|
|
29
|
+
this.contextLength = postTokens;
|
|
30
|
+
}
|
|
31
|
+
observeResult(event) {
|
|
32
|
+
this.resultNotSuccess = event.subtype !== 'success';
|
|
33
|
+
if (!event.usage)
|
|
34
|
+
return;
|
|
35
|
+
const usage = normalizeResultUsage(event.usage);
|
|
36
|
+
if (!this.contextLength)
|
|
37
|
+
this.contextLength = usage.contextLength;
|
|
38
|
+
this.liveInput = usage.inputTokens;
|
|
39
|
+
this.liveOutput = usage.outputTokens;
|
|
40
|
+
this.liveContextLimit = usage.contextLimit;
|
|
41
|
+
if (usage.cacheReadTokens !== undefined)
|
|
42
|
+
this.finalCacheReadTokens = usage.cacheReadTokens;
|
|
43
|
+
if (usage.cacheCreationTokens !== undefined) {
|
|
44
|
+
this.finalCacheCreationTokens = usage.cacheCreationTokens;
|
|
45
|
+
}
|
|
46
|
+
if (usage.totalTokens !== undefined)
|
|
47
|
+
this.finalTotalTokens = usage.totalTokens;
|
|
48
|
+
if (usage.reasoningTokens !== undefined)
|
|
49
|
+
this.finalReasoningTokens = usage.reasoningTokens;
|
|
50
|
+
}
|
|
51
|
+
finalize(finalUsage, authority) {
|
|
52
|
+
this.finalContextLength = finalUsage?.contextLength ?? this.contextLength;
|
|
53
|
+
this.finalContextLimit = finalUsage?.contextLimit ?? this.liveContextLimit;
|
|
54
|
+
this.finalCompactCount = finalUsage?.compactCount;
|
|
55
|
+
this.finalCumulativeTotalTokens = finalUsage?.cumulativeTotalTokens;
|
|
56
|
+
this.finalCumulativeInputTokens = finalUsage?.cumulativeInputTokens;
|
|
57
|
+
this.finalCumulativeOutputTokens = finalUsage?.cumulativeOutputTokens;
|
|
58
|
+
this.finalCumulativeCacheReadTokens = finalUsage?.cumulativeCacheReadTokens;
|
|
59
|
+
this.finalCumulativeCacheCreationTokens = finalUsage?.cumulativeCacheCreationTokens;
|
|
60
|
+
const useLiveFlowScope = authority !== 'jsonl' && this.liveInput !== undefined && this.liveOutput !== undefined;
|
|
61
|
+
if (useLiveFlowScope) {
|
|
62
|
+
this.finalInputTokens = this.liveInput;
|
|
63
|
+
this.finalOutputTokens = this.liveOutput;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.finalInputTokens = finalUsage?.inputTokens ?? this.liveInput;
|
|
67
|
+
this.finalOutputTokens = finalUsage?.outputTokens ?? this.liveOutput;
|
|
68
|
+
this.finalCacheReadTokens = finalUsage?.cacheReadTokens ?? this.finalCacheReadTokens;
|
|
69
|
+
this.finalCacheCreationTokens =
|
|
70
|
+
finalUsage?.cacheCreationTokens ?? this.finalCacheCreationTokens;
|
|
71
|
+
this.finalTotalTokens = finalUsage?.totalTokens ?? this.finalTotalTokens;
|
|
72
|
+
}
|
|
73
|
+
meta() {
|
|
74
|
+
return {
|
|
75
|
+
contextLength: this.finalContextLength,
|
|
76
|
+
contextLimit: this.finalContextLimit,
|
|
77
|
+
compactCount: this.finalCompactCount,
|
|
78
|
+
cacheReadTokens: this.finalCacheReadTokens,
|
|
79
|
+
cacheCreationTokens: this.finalCacheCreationTokens,
|
|
80
|
+
totalTokens: this.finalTotalTokens,
|
|
81
|
+
inputTokens: this.finalInputTokens,
|
|
82
|
+
outputTokens: this.finalOutputTokens,
|
|
83
|
+
cumulativeTotalTokens: this.finalCumulativeTotalTokens,
|
|
84
|
+
cumulativeInputTokens: this.finalCumulativeInputTokens,
|
|
85
|
+
cumulativeOutputTokens: this.finalCumulativeOutputTokens,
|
|
86
|
+
cumulativeCacheReadTokens: this.finalCumulativeCacheReadTokens,
|
|
87
|
+
cumulativeCacheCreationTokens: this.finalCumulativeCacheCreationTokens,
|
|
88
|
+
reasoningTokens: this.finalReasoningTokens,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
logSummary(runId) {
|
|
92
|
+
const meta = this.meta();
|
|
93
|
+
return `[lark-remote] final usage runId=${runId} contextLength=${meta.contextLength} contextLimit=${meta.contextLimit ?? 'undefined'} compactCount=${meta.compactCount ?? 'undefined'} cacheRead=${meta.cacheReadTokens ?? 'undefined'} cacheCreate=${meta.cacheCreationTokens ?? 'undefined'}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=run-usage.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentKind, TurnStartedEvent } from '../runner/index.js';
|
|
2
|
+
import type { SessionStore } from '../session/index.js';
|
|
3
|
+
interface InitEvent {
|
|
4
|
+
session_id: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}
|
|
7
|
+
interface WritebackContext {
|
|
8
|
+
sessionStore: SessionStore;
|
|
9
|
+
userId: string;
|
|
10
|
+
agentKind: AgentKind;
|
|
11
|
+
cwd: string;
|
|
12
|
+
sessionEpochAtStart: number;
|
|
13
|
+
runId: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function writeBackInitSession(ctx: WritebackContext, event: InitEvent): void;
|
|
16
|
+
export declare function writeBackTurnStarted(ctx: WritebackContext, event: TurnStartedEvent): boolean;
|
|
17
|
+
export declare function writeBackResultSession(ctx: WritebackContext, sessionId: string | undefined): void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getLogger } from '../logger/index.js';
|
|
2
|
+
function pointerMoved(ctx) {
|
|
3
|
+
return ctx.sessionStore.getSessionEpoch(ctx.userId, ctx.agentKind) !== ctx.sessionEpochAtStart;
|
|
4
|
+
}
|
|
5
|
+
export function writeBackInitSession(ctx, event) {
|
|
6
|
+
if (pointerMoved(ctx)) {
|
|
7
|
+
getLogger().info(`[lark-remote] system.init write-back skipped: session pointer moved since run start runId=${ctx.runId} sessionId=${event.session_id}`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const eventCwd = typeof event.cwd === 'string' ? event.cwd : '';
|
|
11
|
+
const hasCwd = !!ctx.sessionStore.getCwd(ctx.userId);
|
|
12
|
+
const realCwd = eventCwd.length > 0 ? eventCwd : ctx.cwd;
|
|
13
|
+
if (hasCwd && eventCwd && eventCwd !== ctx.cwd) {
|
|
14
|
+
getLogger().info(`[lark-remote] system.init: cwd=${ctx.cwd}, session cwd=${eventCwd}`);
|
|
15
|
+
ctx.sessionStore.setSessionIdAndSessionCwd(ctx.userId, ctx.agentKind, event.session_id, realCwd);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const bootstrapSessionCwd = !hasCwd && eventCwd.length > 0 ? eventCwd : undefined;
|
|
19
|
+
ctx.sessionStore.setSessionIdAndCwd(ctx.userId, ctx.agentKind, event.session_id, realCwd, bootstrapSessionCwd);
|
|
20
|
+
}
|
|
21
|
+
export function writeBackTurnStarted(ctx, event) {
|
|
22
|
+
if (pointerMoved(ctx)) {
|
|
23
|
+
getLogger().info(`[lark-remote] turn_started write-back skipped: session pointer moved since run start runId=${ctx.runId} sessionId=${event.threadId}`);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
ctx.sessionStore.setSessionIdAndCwd(ctx.userId, ctx.agentKind, event.threadId, ctx.cwd);
|
|
27
|
+
getLogger().info(`[lark-remote] turn_started session write-back runId=${ctx.runId} sessionId=${event.threadId}`);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
export function writeBackResultSession(ctx, sessionId) {
|
|
31
|
+
if (!sessionId || pointerMoved(ctx))
|
|
32
|
+
return;
|
|
33
|
+
ctx.sessionStore.setSessionIdAndCwd(ctx.userId, ctx.agentKind, sessionId, ctx.cwd);
|
|
34
|
+
getLogger().info(`[lark-remote] result session write-back runId=${ctx.runId} sessionId=${sessionId}`);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=session-writeback.js.map
|
|
@@ -14,3 +14,17 @@ export declare function renderApprovalArea(approval: ApprovalView | undefined, o
|
|
|
14
14
|
runId?: string;
|
|
15
15
|
terminal?: string;
|
|
16
16
|
}): object[];
|
|
17
|
+
/**
|
|
18
|
+
* 紧凑审批区(run 卡 skeleton/truncatedSkeleton 兜底档专用):固定小体积的
|
|
19
|
+
* 「标题 + 摘要 reason + 决策按钮」,不渲染正文/选项/题面。
|
|
20
|
+
*
|
|
21
|
+
* 为什么需要:完整审批区是唯一不受估算器管辖、也不参与 degraded/extreme 分层
|
|
22
|
+
* 裁剪的区域; pathological 输入(超长 reason、题数×选项爆元素数)会让所有兜底
|
|
23
|
+
* 层一起超限,超限卡被飞书整卡拒收(11310)→ 审批不可见。兜底层换用本入口后
|
|
24
|
+
* 结构性保证 ≤ 几个元素 + ≤ 1KB 文本,审批入口(红线)在任何降级档都活着。
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderApprovalAreaCompact(approval: ApprovalView | undefined, opts?: {
|
|
27
|
+
expired?: boolean;
|
|
28
|
+
runId?: string;
|
|
29
|
+
terminal?: string;
|
|
30
|
+
}): object[];
|
|
@@ -8,17 +8,50 @@ import { collapsibleMarkdownPanel } from './collapsible.js';
|
|
|
8
8
|
import { truncateMarkdownTables } from './text-truncate.js';
|
|
9
9
|
import { truncateUtf8 } from '../common/truncate.js';
|
|
10
10
|
import { cardButton } from './card-shared.js';
|
|
11
|
+
import { countCardElements } from './card-budget.js';
|
|
11
12
|
/** Unique nonce for card button callbacks (SDK dedup + router validation). */
|
|
12
13
|
function mkNonce() {
|
|
13
14
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
14
15
|
}
|
|
15
16
|
/** 计划面板正文字节预算(折叠展示 + 截断保护 28KB 卡片预算)。 */
|
|
16
17
|
const PLAN_PANEL_BYTES = 6000;
|
|
17
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* 审批区自由文本字段(reason/note/题面/描述/回执等 agent 产出的散文)的统一
|
|
20
|
+
* 字节预算。这些字段此前无上限:实测 reason 30,000 字符 → 整卡 31,450 字节被
|
|
21
|
+
* 飞书 11310 拒收,审批彻底不可见,run 只能挂到审批超时/看门狗。1KB 足以传达
|
|
22
|
+
* 语义并支撑决策。
|
|
23
|
+
*/
|
|
24
|
+
const APPROVAL_FIELD_BYTES = 1000;
|
|
25
|
+
/** 短标签类字段(题头/工具名/路径)的字节预算。 */
|
|
26
|
+
const APPROVAL_LABEL_BYTES = 300;
|
|
27
|
+
/**
|
|
28
|
+
* 提问批次的渲染上限:题数 × 每题元素(选项行 + 输入框 + 按钮)撑爆的是元素
|
|
29
|
+
* 数预算(实测 8 题 × 5 选项 = 144 元素 > 90 上限 → 11310 整卡报废)。超出的
|
|
30
|
+
* 题折叠为计数提示;被折叠的题无法在卡上作答(整批「跳过」与 Web UI/终端
|
|
31
|
+
* 兜底),仍远好于整卡被拒、一题都答不了。
|
|
32
|
+
*/
|
|
33
|
+
const MAX_RENDERED_QUESTIONS = 5;
|
|
34
|
+
/** 单题选项渲染上限(超过折叠为计数提示;选项 label 是回调 payload,不可截断)。 */
|
|
35
|
+
const MAX_RENDERED_OPTIONS = 8;
|
|
36
|
+
/** 文件变更 / 权限条目的渲染上限(每条 1–2 个元素,无上限时百条级直接爆元素预算)。 */
|
|
37
|
+
const MAX_RENDERED_CHANGE_ITEMS = 20;
|
|
38
|
+
/**
|
|
39
|
+
* 审批区自身的元素数预算:卡总预算(MAX_CARD_ELEMENTS=90)减去状态行 / goal /
|
|
40
|
+
* summary / 操作行等骨架(约 20 个,含极端情况的余量)后的安全份额。按已渲染
|
|
41
|
+
* 元素数**自适应**截断——固定题数 × 选项数的组合上限挡不住「少题多选项」与
|
|
42
|
+
* 「多题少选项」的两种组合(一行选项 = column_set + button + div 共 3 个
|
|
43
|
+
* 元素,5 题 × 5 选项就 ~92 个)。
|
|
44
|
+
*/
|
|
45
|
+
const APPROVAL_AREA_ELEMENT_BUDGET = 50;
|
|
46
|
+
/** 审批区自由文本统一入口:反引号中和(防 11311)+ 字节预算截断(防 11310)。 */
|
|
47
|
+
function capApprovalText(text, maxBytes = APPROVAL_FIELD_BYTES) {
|
|
48
|
+
return truncateUtf8(text.replace(/`/g, '·'), maxBytes);
|
|
49
|
+
}
|
|
50
|
+
/** reason div(3 处共用);反引号中和防 lark_md 解析错误 + 字节预算截断。 */
|
|
18
51
|
function reasonDiv(approval) {
|
|
19
52
|
if (!approval.reason)
|
|
20
53
|
return null;
|
|
21
|
-
const neutralized = approval.reason
|
|
54
|
+
const neutralized = capApprovalText(approval.reason);
|
|
22
55
|
return {
|
|
23
56
|
tag: 'div',
|
|
24
57
|
text: { tag: 'lark_md', content: `💡 ${neutralized}` },
|
|
@@ -41,12 +74,11 @@ export function renderApprovalArea(approval, opts) {
|
|
|
41
74
|
return [];
|
|
42
75
|
// 终态(done/error/interrupted/idle_timeout)下 run 已结束:coordinator 已
|
|
43
76
|
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"。
|
|
44
|
-
if (opts
|
|
77
|
+
if (isTerminalHidden(opts))
|
|
45
78
|
return [];
|
|
46
|
-
}
|
|
47
79
|
const elements = [];
|
|
48
80
|
if (approval.note) {
|
|
49
|
-
const neutralized = approval.note
|
|
81
|
+
const neutralized = capApprovalText(approval.note);
|
|
50
82
|
elements.push({
|
|
51
83
|
tag: 'div',
|
|
52
84
|
text: { tag: 'lark_md', content: `ℹ️ ${neutralized}` },
|
|
@@ -69,7 +101,7 @@ export function renderApprovalArea(approval, opts) {
|
|
|
69
101
|
}
|
|
70
102
|
// 响应发送失败:明确告知原因 + 可重试,而不是静默停在可点状态。
|
|
71
103
|
if (approval.deliveryError) {
|
|
72
|
-
const neutralized = approval.deliveryError
|
|
104
|
+
const neutralized = capApprovalText(approval.deliveryError);
|
|
73
105
|
elements.push({
|
|
74
106
|
tag: 'div',
|
|
75
107
|
text: {
|
|
@@ -80,6 +112,54 @@ export function renderApprovalArea(approval, opts) {
|
|
|
80
112
|
}
|
|
81
113
|
return elements;
|
|
82
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* 紧凑审批区(run 卡 skeleton/truncatedSkeleton 兜底档专用):固定小体积的
|
|
117
|
+
* 「标题 + 摘要 reason + 决策按钮」,不渲染正文/选项/题面。
|
|
118
|
+
*
|
|
119
|
+
* 为什么需要:完整审批区是唯一不受估算器管辖、也不参与 degraded/extreme 分层
|
|
120
|
+
* 裁剪的区域; pathological 输入(超长 reason、题数×选项爆元素数)会让所有兜底
|
|
121
|
+
* 层一起超限,超限卡被飞书整卡拒收(11310)→ 审批不可见。兜底层换用本入口后
|
|
122
|
+
* 结构性保证 ≤ 几个元素 + ≤ 1KB 文本,审批入口(红线)在任何降级档都活着。
|
|
123
|
+
*/
|
|
124
|
+
export function renderApprovalAreaCompact(approval, opts) {
|
|
125
|
+
if (!approval)
|
|
126
|
+
return [];
|
|
127
|
+
if (isTerminalHidden(opts))
|
|
128
|
+
return [];
|
|
129
|
+
const elements = [
|
|
130
|
+
{
|
|
131
|
+
tag: 'div',
|
|
132
|
+
text: {
|
|
133
|
+
tag: 'lark_md',
|
|
134
|
+
content: '✋ **等待审批**(内容过长已折叠,完整内容请查终端 / Web UI)',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
pushIf(elements, reasonDiv(approval));
|
|
139
|
+
if (approval.kind === 'question') {
|
|
140
|
+
if (opts?.expired) {
|
|
141
|
+
elements.push({ tag: 'div', text: { tag: 'lark_md', content: '⏰ **提问已超时**' } });
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// 提问批次的紧凑形态只保留整批跳过(逐题作答 UI 被折叠)。
|
|
145
|
+
elements.push(cardButton('⏭️ 跳过回答', {
|
|
146
|
+
cmd: 'approval.respond',
|
|
147
|
+
decision: 'decline',
|
|
148
|
+
requestId: approval.requestId,
|
|
149
|
+
runId: opts?.runId,
|
|
150
|
+
nonce: mkNonce(),
|
|
151
|
+
}, { type: 'default' }));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
elements.push(...renderDecisionButtons(approval, opts?.expired, opts?.runId));
|
|
156
|
+
}
|
|
157
|
+
return elements;
|
|
158
|
+
}
|
|
159
|
+
/** 终态隐藏判定(renderApprovalArea 与 compact 共用)。 */
|
|
160
|
+
function isTerminalHidden(opts) {
|
|
161
|
+
return !!opts?.terminal && opts.terminal !== 'running' && opts.terminal !== 'finalizing';
|
|
162
|
+
}
|
|
83
163
|
/**
|
|
84
164
|
* Render approval action buttons from the real protocol decision list.
|
|
85
165
|
* accept and decline are always offered (decline is a universal safety
|
|
@@ -150,7 +230,7 @@ function renderToolApproval(approval, expired, runId) {
|
|
|
150
230
|
content: '**🔧 工具请求**',
|
|
151
231
|
},
|
|
152
232
|
});
|
|
153
|
-
const toolLabel = approval.toolName ?? '(unknown tool)';
|
|
233
|
+
const toolLabel = capApprovalText(approval.toolName ?? '(unknown tool)', APPROVAL_LABEL_BYTES);
|
|
154
234
|
elements.push({
|
|
155
235
|
tag: 'div',
|
|
156
236
|
text: { tag: 'lark_md', content: `🔧 \`${toolLabel}\`` },
|
|
@@ -158,10 +238,10 @@ function renderToolApproval(approval, expired, runId) {
|
|
|
158
238
|
pushIf(elements, reasonDiv(approval));
|
|
159
239
|
// 原始 input(非空时展示,截断保护卡片预算;ExitPlanMode 的 `{}` 不展示)。
|
|
160
240
|
if (approval.toolInput && approval.toolInput !== '{}') {
|
|
161
|
-
const neutralized = approval.toolInput
|
|
241
|
+
const neutralized = capApprovalText(approval.toolInput, 500);
|
|
162
242
|
elements.push({
|
|
163
243
|
tag: 'div',
|
|
164
|
-
text: { tag: 'lark_md', content: `\`${neutralized
|
|
244
|
+
text: { tag: 'lark_md', content: `\`${neutralized}\`` },
|
|
165
245
|
});
|
|
166
246
|
}
|
|
167
247
|
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
@@ -184,7 +264,7 @@ function renderPlanApproval(approval, expired, runId) {
|
|
|
184
264
|
});
|
|
185
265
|
pushIf(elements, reasonDiv(approval));
|
|
186
266
|
if (approval.planFilePath) {
|
|
187
|
-
const neutralizedPath = approval.planFilePath
|
|
267
|
+
const neutralizedPath = capApprovalText(approval.planFilePath, APPROVAL_LABEL_BYTES);
|
|
188
268
|
elements.push({
|
|
189
269
|
tag: 'div',
|
|
190
270
|
text: { tag: 'lark_md', content: `📄 ${neutralizedPath}` },
|
|
@@ -215,7 +295,7 @@ function renderPlanApproval(approval, expired, runId) {
|
|
|
215
295
|
if (!expired) {
|
|
216
296
|
elements.push(renderPlanFeedbackInput(approval.requestId, runId));
|
|
217
297
|
if (approval.planFeedback) {
|
|
218
|
-
const neutralized = approval.planFeedback
|
|
298
|
+
const neutralized = capApprovalText(approval.planFeedback);
|
|
219
299
|
elements.push({
|
|
220
300
|
tag: 'div',
|
|
221
301
|
text: { tag: 'lark_md', content: `📝 ${neutralized}` },
|
|
@@ -259,18 +339,17 @@ function renderCommandApproval(approval, expired, runId) {
|
|
|
259
339
|
// Command display
|
|
260
340
|
// 命令里的反引号会提前终止 lark_md 行内代码 span,奇数次
|
|
261
341
|
// 反引号极易触发 11311 解析错误导致整卡失败(同 renderFileDiff 的中和策略)。
|
|
262
|
-
const neutralizedCommand = (approval.command ?? ''
|
|
263
|
-
const cmdContent = neutralizedCommand
|
|
264
|
-
? `\`${neutralizedCommand.slice(0, 500)}\``
|
|
265
|
-
: '(no command)';
|
|
342
|
+
const neutralizedCommand = capApprovalText(approval.command ?? '', 500);
|
|
343
|
+
const cmdContent = neutralizedCommand ? `\`${neutralizedCommand}\`` : '(no command)';
|
|
266
344
|
elements.push({
|
|
267
345
|
tag: 'div',
|
|
268
346
|
text: { tag: 'lark_md', content: `**⚡ 命令审批**\n${cmdContent}` },
|
|
269
347
|
});
|
|
270
348
|
if (approval.commandCwd) {
|
|
349
|
+
const neutralizedCwd = capApprovalText(approval.commandCwd, APPROVAL_LABEL_BYTES);
|
|
271
350
|
elements.push({
|
|
272
351
|
tag: 'div',
|
|
273
|
-
text: { tag: 'lark_md', content: `📂 \`${
|
|
352
|
+
text: { tag: 'lark_md', content: `📂 \`${neutralizedCwd}\`` },
|
|
274
353
|
});
|
|
275
354
|
}
|
|
276
355
|
pushIf(elements, reasonDiv(approval));
|
|
@@ -297,11 +376,16 @@ function renderFileApproval(approval, expired, runId) {
|
|
|
297
376
|
const TOTAL_DIFF_CHARS = 2400;
|
|
298
377
|
let renderedDiffChars = 0;
|
|
299
378
|
let omittedDiffs = 0;
|
|
300
|
-
|
|
379
|
+
// 条目数上限(元素预算):每条 1 path div + 至多 1 diff div,无上限时
|
|
380
|
+
// 百条级变更直接撑爆元素数(>90 → 11310 整卡报废)。
|
|
381
|
+
const changes = approval.fileChanges.slice(0, MAX_RENDERED_CHANGE_ITEMS);
|
|
382
|
+
const omittedChanges = approval.fileChanges.length - changes.length;
|
|
383
|
+
for (const change of changes) {
|
|
301
384
|
const icon = change.kind === 'add' ? '🆕' : change.kind === 'delete' ? '🗑️' : '✏️';
|
|
385
|
+
const path = capApprovalText(change.path, APPROVAL_LABEL_BYTES);
|
|
302
386
|
elements.push({
|
|
303
387
|
tag: 'div',
|
|
304
|
-
text: { tag: 'lark_md', content: `${icon} \`${
|
|
388
|
+
text: { tag: 'lark_md', content: `${icon} \`${path}\` (${change.kind})` },
|
|
305
389
|
});
|
|
306
390
|
if (change.diff) {
|
|
307
391
|
if (renderedDiffChars < TOTAL_DIFF_CHARS) {
|
|
@@ -316,6 +400,12 @@ function renderFileApproval(approval, expired, runId) {
|
|
|
316
400
|
}
|
|
317
401
|
}
|
|
318
402
|
}
|
|
403
|
+
if (omittedChanges > 0) {
|
|
404
|
+
elements.push({
|
|
405
|
+
tag: 'div',
|
|
406
|
+
text: { tag: 'lark_md', content: `_…另有 ${omittedChanges} 个文件变更未展示(卡片预算)_` },
|
|
407
|
+
});
|
|
408
|
+
}
|
|
319
409
|
if (omittedDiffs > 0) {
|
|
320
410
|
elements.push({
|
|
321
411
|
tag: 'div',
|
|
@@ -354,11 +444,16 @@ function renderPermissionsApproval(approval, expired, runId) {
|
|
|
354
444
|
pushIf(elements, reasonDiv(approval));
|
|
355
445
|
// Permission items with toggle buttons
|
|
356
446
|
if (approval.permissions?.items) {
|
|
357
|
-
|
|
447
|
+
// 条目数上限(元素预算):每条 label div + toggle button 共 2 个元素。
|
|
448
|
+
const items = approval.permissions.items.slice(0, MAX_RENDERED_CHANGE_ITEMS);
|
|
449
|
+
const omittedItems = approval.permissions.items.length - items.length;
|
|
450
|
+
for (const item of items) {
|
|
358
451
|
const icon = item.selected ? '✅' : '⬜';
|
|
452
|
+
// label 仅展示(payload 用 item.id),可安全截断。
|
|
453
|
+
const label = capApprovalText(item.label, APPROVAL_LABEL_BYTES);
|
|
359
454
|
elements.push({
|
|
360
455
|
tag: 'div',
|
|
361
|
-
text: { tag: 'lark_md', content: `${icon} ${
|
|
456
|
+
text: { tag: 'lark_md', content: `${icon} ${label}` },
|
|
362
457
|
});
|
|
363
458
|
elements.push(cardButton(item.selected ? '取消授予' : '授予', {
|
|
364
459
|
cmd: 'approval.toggle',
|
|
@@ -369,6 +464,12 @@ function renderPermissionsApproval(approval, expired, runId) {
|
|
|
369
464
|
nonce: mkNonce(),
|
|
370
465
|
}, { type: item.selected ? 'default' : 'primary', size: 'small' }));
|
|
371
466
|
}
|
|
467
|
+
if (omittedItems > 0) {
|
|
468
|
+
elements.push({
|
|
469
|
+
tag: 'div',
|
|
470
|
+
text: { tag: 'lark_md', content: `_…另有 ${omittedItems} 项权限未展示(卡片预算)_` },
|
|
471
|
+
});
|
|
472
|
+
}
|
|
372
473
|
}
|
|
373
474
|
// Action buttons
|
|
374
475
|
// Derived from the real protocol decision list (see renderDecisionButtons).
|
|
@@ -396,7 +497,7 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
396
497
|
if (approval.intro) {
|
|
397
498
|
elements.push({
|
|
398
499
|
tag: 'div',
|
|
399
|
-
text: { tag: 'lark_md', content: `📝 ${approval.intro}` },
|
|
500
|
+
text: { tag: 'lark_md', content: `📝 ${capApprovalText(approval.intro)}` },
|
|
400
501
|
});
|
|
401
502
|
}
|
|
402
503
|
if (expired) {
|
|
@@ -410,22 +511,36 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
410
511
|
if (approval.expiredHint) {
|
|
411
512
|
elements.push({
|
|
412
513
|
tag: 'div',
|
|
413
|
-
text: { tag: 'lark_md', content: approval.expiredHint
|
|
514
|
+
text: { tag: 'lark_md', content: capApprovalText(approval.expiredHint) },
|
|
414
515
|
});
|
|
415
516
|
}
|
|
416
517
|
return elements;
|
|
417
518
|
}
|
|
418
519
|
const questions = approval.questions ?? [];
|
|
520
|
+
let renderedQuestions = 0;
|
|
419
521
|
for (let i = 0; i < questions.length; i++) {
|
|
522
|
+
// 题数粗上限 + 审批区元素预算(自适应):任一触发即折叠剩余题。
|
|
523
|
+
if (i >= MAX_RENDERED_QUESTIONS ||
|
|
524
|
+
countCardElements({ elements }) > APPROVAL_AREA_ELEMENT_BUDGET) {
|
|
525
|
+
elements.push({
|
|
526
|
+
tag: 'div',
|
|
527
|
+
text: {
|
|
528
|
+
tag: 'lark_md',
|
|
529
|
+
content: `_…另有 ${questions.length - renderedQuestions} 题未展示(卡片元素预算);可在终端 / Web UI 回答,或点下方「跳过回答」跳过整批_`,
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
renderedQuestions++;
|
|
420
535
|
const q = questions[i];
|
|
421
536
|
const selected = q.selected ?? [];
|
|
422
|
-
const header = q.header || `问题 ${i + 1}
|
|
537
|
+
const header = capApprovalText(q.header || `问题 ${i + 1}`, APPROVAL_LABEL_BYTES);
|
|
423
538
|
const freeText = (q.options?.length ?? 0) === 0;
|
|
424
539
|
elements.push({
|
|
425
540
|
tag: 'div',
|
|
426
541
|
text: {
|
|
427
542
|
tag: 'lark_md',
|
|
428
|
-
content: `**${header}**${q.multiSelect ? '(可多选)' : ''}\n${q.question}`,
|
|
543
|
+
content: `**${header}**${q.multiSelect ? '(可多选)' : ''}\n${capApprovalText(q.question)}`,
|
|
429
544
|
},
|
|
430
545
|
});
|
|
431
546
|
if (q.detail) {
|
|
@@ -451,24 +566,35 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
451
566
|
if (answered) {
|
|
452
567
|
elements.push({
|
|
453
568
|
tag: 'div',
|
|
454
|
-
text: { tag: 'lark_md', content: `✍️ 已答:${answered}` },
|
|
569
|
+
text: { tag: 'lark_md', content: `✍️ 已答:${capApprovalText(answered)}` },
|
|
455
570
|
});
|
|
456
571
|
}
|
|
457
572
|
continue;
|
|
458
573
|
}
|
|
459
|
-
|
|
574
|
+
// 选项渲染上限(label 是回调 payload 不可截断,只能按条数折叠)。
|
|
575
|
+
const options = q.options ?? [];
|
|
576
|
+
for (const option of options.slice(0, MAX_RENDERED_OPTIONS)) {
|
|
460
577
|
const isSelected = selected.includes(option.label);
|
|
461
578
|
// 单选/多选图标区分:单选 ⚪/🔵(radio 隐喻),多选 ⬜/☑️(checkbox 隐喻)。
|
|
462
579
|
const icon = q.multiSelect ? (isSelected ? '☑️' : '⬜') : isSelected ? '🔵' : '⚪';
|
|
463
580
|
elements.push(renderOptionRow(approval.requestId, i, option, isSelected, icon, runId));
|
|
464
581
|
}
|
|
582
|
+
if (options.length > MAX_RENDERED_OPTIONS) {
|
|
583
|
+
elements.push({
|
|
584
|
+
tag: 'div',
|
|
585
|
+
text: {
|
|
586
|
+
tag: 'lark_md',
|
|
587
|
+
content: `_…另有 ${options.length - MAX_RENDERED_OPTIONS} 个选项未展示(卡片元素预算)_`,
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
}
|
|
465
591
|
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
466
592
|
// 已选文本,避免「点了没反应」的困惑。
|
|
467
593
|
const customSelected = q.selected?.find((s) => !q.options.some((o) => o.label === s));
|
|
468
594
|
if (customSelected) {
|
|
469
595
|
elements.push({
|
|
470
596
|
tag: 'div',
|
|
471
|
-
text: { tag: 'lark_md', content: `✍️ 自定义答案:${customSelected}` },
|
|
597
|
+
text: { tag: 'lark_md', content: `✍️ 自定义答案:${capApprovalText(customSelected)}` },
|
|
472
598
|
});
|
|
473
599
|
}
|
|
474
600
|
// 单选问题提供自定义答案(Other)输入——输入文本后提交
|
|
@@ -486,7 +612,7 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
486
612
|
if (q.note) {
|
|
487
613
|
elements.push({
|
|
488
614
|
tag: 'div',
|
|
489
|
-
text: { tag: 'lark_md', content: `📝 ${q.note}` },
|
|
615
|
+
text: { tag: 'lark_md', content: `📝 ${capApprovalText(q.note)}` },
|
|
490
616
|
});
|
|
491
617
|
}
|
|
492
618
|
}
|
|
@@ -550,7 +676,7 @@ function renderOptionRow(requestId, questionIndex, option, isSelected, icon, run
|
|
|
550
676
|
tag: 'div',
|
|
551
677
|
text: {
|
|
552
678
|
tag: 'lark_md',
|
|
553
|
-
content: `${icon} ${option.label}${option.description ? `\n_${option.description}_` : ''}`,
|
|
679
|
+
content: `${icon} ${option.label}${option.description ? `\n_${capApprovalText(option.description)}_` : ''}`,
|
|
554
680
|
},
|
|
555
681
|
},
|
|
556
682
|
],
|
|
@@ -68,11 +68,16 @@ export class BashCardSession extends CardSession {
|
|
|
68
68
|
}
|
|
69
69
|
/** Transition to a terminal state and patch the card. */
|
|
70
70
|
async finish(terminal, meta = {}) {
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
// 终态守卫(与 run-state 的 finishRun 首终态优先同口径):/stop 路径
|
|
72
|
+
// (bridge.interruptCurrentRun)与 executeBashInternal 的自然收尾路径会对
|
|
73
|
+
// 同一张卡并发 finish——SIGKILL 后 exitCode 为 -1,自然路径的
|
|
74
|
+
// finish('error', { exitCode: -1 }) 会把 interrupt 路径刚写的 'interrupted'
|
|
75
|
+
// 覆盖成「执行失败」,后落者胜出且取决于时序。首终态胜出后忽略后续 finish。
|
|
76
|
+
if (this.state.terminal !== 'running') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// 终态 patch 必须排在已开始的 pre-terminal flush 之后。
|
|
80
|
+
await this.prepareTerminalUpdate();
|
|
76
81
|
this.state = {
|
|
77
82
|
...this.state,
|
|
78
83
|
terminal,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { terminalToColor, terminalToLabel, stopButton } from './card-shared.js';
|
|
2
2
|
import { markdownDiv } from './collapsible.js';
|
|
3
3
|
import { CARD_BUDGET_BYTES } from './text-truncate.js';
|
|
4
|
-
import { truncateUtf8 } from '../common/truncate.js';
|
|
4
|
+
import { truncateUtf8, truncate } from '../common/truncate.js';
|
|
5
5
|
/**
|
|
6
6
|
* 飞书卡片大小限制(安全阈值,与 run-renderer 一致)。
|
|
7
7
|
* 从共享的 text-truncate.ts 导入,避免三处各自定义后数值漂移。
|
|
@@ -20,36 +20,24 @@ const OUTPUT_MAX_BYTES = 12_000;
|
|
|
20
20
|
*/
|
|
21
21
|
const DEGRADED_OUTPUT_BYTES = 4_000;
|
|
22
22
|
/**
|
|
23
|
-
* Truncate long commands for display.
|
|
23
|
+
* Truncate long commands for display. 按码点截断(裸 slice 切在 emoji 中间会
|
|
24
|
+
* 留下孤立代理项进卡片),项目已有 truncate() 实现同语义。
|
|
24
25
|
*/
|
|
25
26
|
function truncateCommand(cmd, maxLen = 40) {
|
|
26
|
-
|
|
27
|
-
return cmd;
|
|
28
|
-
return cmd.slice(0, maxLen - 3) + '...';
|
|
27
|
+
return truncate(cmd, maxLen);
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
|
-
* Build the status row
|
|
30
|
+
* Build the status row element.
|
|
31
|
+
*
|
|
32
|
+
* 单 div + ` · ` 分隔(窄屏硬闸,与 run-renderer 的 statusRow 同款):旧版把
|
|
33
|
+
* 状态标签(2 字短文本)放 `column_set` 的 auto 列、命令放 grow 列——手机
|
|
34
|
+
* 窄屏下 auto 列会被挤成竖排(run 卡踩过同型坑,bash 卡漏改)。
|
|
35
|
+
* 单行文本的换行点只落在 " · " 处,各分段自身不折行。
|
|
32
36
|
*/
|
|
33
37
|
function buildStatusRow(state) {
|
|
34
38
|
const { terminal, exitCode, command } = state;
|
|
35
39
|
const statusLabel = bashStatusTagLabel(terminal, exitCode);
|
|
36
|
-
return {
|
|
37
|
-
tag: 'column_set',
|
|
38
|
-
columns: [
|
|
39
|
-
{
|
|
40
|
-
tag: 'column',
|
|
41
|
-
width: 'auto',
|
|
42
|
-
elements: [{ tag: 'div', text: { content: statusLabel, tag: 'lark_md' } }],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
tag: 'column',
|
|
46
|
-
width: 'grow',
|
|
47
|
-
elements: [
|
|
48
|
-
{ tag: 'div', text: { content: `⏱ ${truncateCommand(command, 40)}`, tag: 'lark_md' } },
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
};
|
|
40
|
+
return markdownDiv(`${statusLabel} · ⏱ ${truncateCommand(command, 40)}`, 'notation');
|
|
53
41
|
}
|
|
54
42
|
/**
|
|
55
43
|
* Build the exit code footer elements (terminal states only).
|