lark-remote 0.1.7 → 0.1.8
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 +28 -0
- package/dist/bridge/approval-coordinator.d.ts +15 -1
- package/dist/bridge/approval-coordinator.js +35 -2
- package/dist/bridge/index.d.ts +19 -6
- package/dist/bridge/index.js +25 -8
- package/dist/card/approval-render.js +187 -45
- package/dist/card/card-shared.d.ts +6 -4
- package/dist/card/card-shared.js +6 -4
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +22 -11
- package/dist/config/index.d.ts +24 -1
- package/dist/config/index.js +34 -3
- package/dist/index.js +28 -14
- package/dist/router/card-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/config/claude.js +8 -3
- package/dist/router/config/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/index.js +4 -1
- package/dist/router/config/types.d.ts +6 -0
- package/dist/router/index.d.ts +20 -1
- package/dist/router/index.js +113 -7
- package/dist/router/utils.js +12 -2
- package/dist/runner/agent-choices-common.js +3 -0
- package/dist/runner/claude/session.js +24 -5
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -0
- package/dist/runner/codex/app-server/protocol-types.js +1 -1
- package/dist/runner/codex/app-server/runner.d.ts +10 -0
- package/dist/runner/codex/app-server/runner.js +56 -1
- package/dist/runner/codex/app-server/translator.d.ts +7 -0
- package/dist/runner/codex/app-server/translator.js +45 -0
- package/dist/runner/common/acp/protocol-types.d.ts +33 -0
- package/dist/runner/common/acp/protocol-types.js +2 -0
- package/dist/runner/dsh/client.d.ts +95 -0
- package/dist/runner/dsh/client.js +418 -0
- package/dist/runner/dsh/fake-dsh-server.d.ts +60 -0
- package/dist/runner/dsh/fake-dsh-server.js +156 -0
- package/dist/runner/dsh/index.d.ts +3 -0
- package/dist/runner/dsh/index.js +4 -0
- package/dist/runner/dsh/runner.d.ts +57 -0
- package/dist/runner/dsh/runner.js +191 -0
- package/dist/runner/dsh/translator.d.ts +28 -0
- package/dist/runner/dsh/translator.js +98 -0
- package/dist/runner/dsh/types.d.ts +155 -0
- package/dist/runner/dsh/types.js +14 -0
- package/dist/runner/index.d.ts +1 -0
- package/dist/runner/index.js +1 -0
- package/dist/runner/kimi/acp/runner.js +69 -1
- package/dist/runner/kimi/acp/translator.d.ts +14 -0
- package/dist/runner/kimi/acp/translator.js +108 -3
- package/dist/runner/pi/rpc/runner.d.ts +1 -7
- package/dist/runner/pi/rpc/runner.js +2 -11
- package/dist/runner/probe.js +6 -1
- package/dist/runner/question-common.d.ts +26 -0
- package/dist/runner/question-common.js +42 -0
- package/dist/runner/types.d.ts +63 -10
- package/dist/session/claude/sessions.js +37 -15
- package/dist/session/dsh/index.d.ts +1 -0
- package/dist/session/dsh/index.js +2 -0
- package/dist/session/dsh/sessions.d.ts +39 -0
- package/dist/session/dsh/sessions.js +237 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +8 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.8] - 2026-08-19
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- DSH(DeepSeek Harness)Agent 接入:纯 HTTP 连接 DSH Web Host(无本地子进程),支持会话创建/续跑、`selectModel` 模型对齐、SSE mux 事件订阅、会话历史读取
|
|
13
|
+
- `/config` DSH 配置卡:Host 地址 / 会话 preset / 模型 / 推理强度,模型与预设目录预取动态加载(失败回退固定兜底清单)
|
|
14
|
+
- AskUserQuestion 公共契约(`question-common`):标准提问审批事件工厂 + 按序答案映射,claude / codex / kimi / pi 统一接入
|
|
15
|
+
- Codex AskUserQuestion:支持自由文本题、补充说明(user_note)、`autoResolutionMs` 透传为单请求审批超时
|
|
16
|
+
- Kimi elicitation 表单回编 + request_permission 兜底桥
|
|
17
|
+
- 工具权限审批卡(`kind === 'tool'`):ExitPlanMode 等非命令工具展示工具名 + 用途说明,计划审批语义
|
|
18
|
+
- 提问卡单选/多选图标区分(⚪/🔵 vs ⬜/☑️)、自由文本题输入框、自定义答案回显
|
|
19
|
+
|
|
20
|
+
### 变更
|
|
21
|
+
|
|
22
|
+
- claude session usage 扫描对齐 codex 语义:非累计字段为末轮(本 run)scope,累计走 `cumulative*` 字段
|
|
23
|
+
- Compact 按钮能力门控放开到所有 `runCompact` 能力 runner(claude / codex / kimi / opencode / pi)
|
|
24
|
+
- claude `/config` 权限模式下拉排除 `manual`(`default` 的别名,避免等价重复项)
|
|
25
|
+
- workspace 保存即触达:`save` 内部调用 `touch`,新保存的工作区排在列表最前
|
|
26
|
+
- DSH 可用性探测跳过二进制检查(HTTP-only Agent)
|
|
27
|
+
|
|
28
|
+
### 修复
|
|
29
|
+
|
|
30
|
+
- claude session usage 扫描:修复第三方网关(如 DeepSeek)将零 usage 写在占位行、真实 usage 只在末行导致的 per-run 统计全 0(改为按 message id 逐字段 max 聚合)
|
|
31
|
+
- 审批答案重复投递(重复 nonce 二次点击)改为中性提示,不再误报"提交失败"
|
|
32
|
+
- usage 统计一致性护栏:本 run 超出累计时显式标记 `⚠️ 累计异常`(只标记不修正,便于排查数据源问题)
|
|
33
|
+
|
|
8
34
|
## [0.1.7] - 2026-08-18
|
|
9
35
|
|
|
10
36
|
### 新增
|
|
@@ -175,3 +201,5 @@ Initial release.
|
|
|
175
201
|
[0.1.4]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.4
|
|
176
202
|
[0.1.5]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.5
|
|
177
203
|
[0.1.6]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.6
|
|
204
|
+
[0.1.7]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.7
|
|
205
|
+
[0.1.8]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.8
|
|
@@ -13,13 +13,15 @@ import type { ApprovalAction } from '../runner/types.js';
|
|
|
13
13
|
type ApprovalState = 'pending' | 'submitting' | 'resolved' | 'failed' | 'expired';
|
|
14
14
|
export interface TrackedApproval {
|
|
15
15
|
requestId: number | string;
|
|
16
|
-
kind: 'command' | 'file' | 'permissions' | 'question';
|
|
16
|
+
kind: 'command' | 'file' | 'permissions' | 'question' | 'tool';
|
|
17
17
|
state: ApprovalState;
|
|
18
18
|
view: ApprovalView;
|
|
19
19
|
createdAt: number;
|
|
20
20
|
timeoutTimer: ReturnType<typeof setTimeout> | null;
|
|
21
21
|
/** AskUserQuestion 已答问题索引 → 答案 label[](kind === 'question')。 */
|
|
22
22
|
answers?: Map<number, string[]>;
|
|
23
|
+
/** AskUserQuestion 补充说明(Codex user_note):问题索引 → 文本。 */
|
|
24
|
+
notes?: Map<number, string>;
|
|
23
25
|
}
|
|
24
26
|
type ApprovalResponder = (requestId: number | string, response: unknown) => Promise<void>;
|
|
25
27
|
export type { ApprovalAction } from '../runner/types.js';
|
|
@@ -101,6 +103,18 @@ export declare class ApprovalCoordinator {
|
|
|
101
103
|
requestId: number | string;
|
|
102
104
|
nonce?: string;
|
|
103
105
|
}): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* AskUserQuestion 补充说明(Codex user_note):选项之外的可选文本,随答案
|
|
108
|
+
* 提交;不单独构成答案(仍需选项/自定义答案才能完成该问题)。note 更新
|
|
109
|
+
* 后回流视图(卡片回显),同一请求可多次修改(新 nonce 来自新渲染)。
|
|
110
|
+
*/
|
|
111
|
+
answerNote(action: {
|
|
112
|
+
questionIndex: number;
|
|
113
|
+
text: string;
|
|
114
|
+
}, ctx: {
|
|
115
|
+
requestId: number | string;
|
|
116
|
+
nonce?: string;
|
|
117
|
+
}): Promise<void>;
|
|
104
118
|
/**
|
|
105
119
|
* Update a pending approval's view (out-of-order item/started arrives late).
|
|
106
120
|
* Only applies while the approval is still pending — resolved/expired
|
|
@@ -37,9 +37,12 @@ export class ApprovalCoordinator {
|
|
|
37
37
|
existing.view = event.view;
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
// 事件级 timeoutMs 优先(Codex autoResolutionMs / Pi extension timeout),
|
|
41
|
+
// 缺省回落 run 级默认超时。放在事件上而非桥按 agentKind 分支,公共层
|
|
42
|
+
// 保持无 agent 分支。
|
|
40
43
|
const timer = setTimeout(() => {
|
|
41
44
|
this.expireApproval(event.requestId);
|
|
42
|
-
}, this.approvalTimeoutMs);
|
|
45
|
+
}, event.timeoutMs ?? this.approvalTimeoutMs);
|
|
43
46
|
const tracked = {
|
|
44
47
|
requestId: event.requestId,
|
|
45
48
|
kind: event.kind,
|
|
@@ -224,6 +227,28 @@ export class ApprovalCoordinator {
|
|
|
224
227
|
}
|
|
225
228
|
this.pushQuestionViewUpdate(ctx.requestId, tracked);
|
|
226
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* AskUserQuestion 补充说明(Codex user_note):选项之外的可选文本,随答案
|
|
232
|
+
* 提交;不单独构成答案(仍需选项/自定义答案才能完成该问题)。note 更新
|
|
233
|
+
* 后回流视图(卡片回显),同一请求可多次修改(新 nonce 来自新渲染)。
|
|
234
|
+
*/
|
|
235
|
+
async answerNote(action, ctx) {
|
|
236
|
+
const tracked = this.getPendingQuestion(ctx.requestId);
|
|
237
|
+
const questions = tracked.view.questions;
|
|
238
|
+
const q = questions[action.questionIndex];
|
|
239
|
+
if (!q) {
|
|
240
|
+
throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
|
|
241
|
+
}
|
|
242
|
+
const text = action.text.trim();
|
|
243
|
+
if (!text) {
|
|
244
|
+
throw new Error('请输入补充说明');
|
|
245
|
+
}
|
|
246
|
+
this.assertFreshNonce(ctx.requestId, ctx.nonce);
|
|
247
|
+
tracked.notes ??= new Map();
|
|
248
|
+
tracked.notes.set(action.questionIndex, text);
|
|
249
|
+
q.note = text;
|
|
250
|
+
this.pushQuestionViewUpdate(ctx.requestId, tracked);
|
|
251
|
+
}
|
|
227
252
|
/**
|
|
228
253
|
* Update a pending approval's view (out-of-order item/started arrives late).
|
|
229
254
|
* Only applies while the approval is still pending — resolved/expired
|
|
@@ -316,14 +341,22 @@ export class ApprovalCoordinator {
|
|
|
316
341
|
}
|
|
317
342
|
const questions = tracked.view.questions ?? [];
|
|
318
343
|
const answers = {};
|
|
344
|
+
const notes = {};
|
|
319
345
|
for (let i = 0; i < questions.length; i++) {
|
|
320
346
|
const selected = tracked.answers?.get(i) ?? [];
|
|
321
347
|
answers[questions[i].question] = questions[i].multiSelect ? selected : selected[0];
|
|
348
|
+
const note = tracked.notes?.get(i);
|
|
349
|
+
if (note)
|
|
350
|
+
notes[questions[i].question] = note;
|
|
322
351
|
}
|
|
323
352
|
tracked.state = 'submitting';
|
|
324
353
|
this.clearTimer(tracked);
|
|
325
354
|
try {
|
|
326
|
-
await this.responder(ctx.requestId, {
|
|
355
|
+
await this.responder(ctx.requestId, {
|
|
356
|
+
action: 'answer',
|
|
357
|
+
answers,
|
|
358
|
+
...(Object.keys(notes).length > 0 ? { notes } : {}),
|
|
359
|
+
});
|
|
327
360
|
tracked.state = 'resolved';
|
|
328
361
|
this.pushResolved(ctx.requestId);
|
|
329
362
|
}
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -129,8 +129,8 @@ export declare class Bridge {
|
|
|
129
129
|
/** Approval coordinators keyed by runId. */
|
|
130
130
|
private approvalCoordinators;
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
133
|
-
* 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
132
|
+
* 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
|
|
133
|
+
* activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
134
134
|
*/
|
|
135
135
|
private lastCompactableCodexRun;
|
|
136
136
|
/**
|
|
@@ -426,15 +426,28 @@ export declare class Bridge {
|
|
|
426
426
|
nonce: string;
|
|
427
427
|
}): Promise<void>;
|
|
428
428
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
429
|
+
* Codex AskUserQuestion 补充说明(user_note,2026-08-18 live 验证模型理解
|
|
430
|
+
* "user_note: <text>" 条目):记录到 coordinator,随答案一起提交。
|
|
431
|
+
* 路由到 ApprovalCoordinator.answerNote。
|
|
432
|
+
*/
|
|
433
|
+
handleApprovalAnswerNote(opts: {
|
|
434
|
+
runId: string;
|
|
435
|
+
requestId: number | string;
|
|
436
|
+
questionIndex: number;
|
|
437
|
+
text: string;
|
|
438
|
+
nonce: string;
|
|
439
|
+
}): Promise<void>;
|
|
440
|
+
/**
|
|
441
|
+
* Handle codex.compact card action — trigger a compaction request for any
|
|
442
|
+
* runCompact-capable runner(codex/kimi/opencode/pi/claude,按
|
|
443
|
+
* lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
|
|
444
|
+
* and is in a terminal state, then calls the runner's runCompact().
|
|
432
445
|
*/
|
|
433
446
|
handleCodexCompact(value: {
|
|
434
447
|
runId?: string;
|
|
435
448
|
}, ctx: BridgeContext): Promise<void>;
|
|
436
449
|
/**
|
|
437
|
-
* Stream a
|
|
450
|
+
* Stream a compaction to a card: start a RunCardSession with
|
|
438
451
|
* operationKind='compaction' (no recursive Compact button), consume
|
|
439
452
|
* runner.runCompact() events, read authoritative jsonl usage, and finish
|
|
440
453
|
* the card. Shared by handleCodexCompact (run card button) and
|
package/dist/bridge/index.js
CHANGED
|
@@ -97,8 +97,8 @@ export class Bridge {
|
|
|
97
97
|
/** Approval coordinators keyed by runId. */
|
|
98
98
|
approvalCoordinators = new Map();
|
|
99
99
|
/**
|
|
100
|
-
*
|
|
101
|
-
* 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
100
|
+
* 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
|
|
101
|
+
* activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
102
102
|
*/
|
|
103
103
|
lastCompactableCodexRun = new Map();
|
|
104
104
|
/**
|
|
@@ -753,6 +753,9 @@ export class Bridge {
|
|
|
753
753
|
showToolResult: this.config.output.showToolResult,
|
|
754
754
|
// Run card header shows the current agent name (e.g. "Claude · 思考中")
|
|
755
755
|
agentKind,
|
|
756
|
+
// Compact 按钮能力门控与 resume 卡 / auto-resume 卡一致(hasRunCompact
|
|
757
|
+
// 鸭子探测):claude(stream-json,无 turn_started)也能拿到按钮。
|
|
758
|
+
compactSupported: this.hasRunCompact(cwd, agentKind),
|
|
756
759
|
},
|
|
757
760
|
});
|
|
758
761
|
const activeRun = {
|
|
@@ -1111,7 +1114,10 @@ export class Bridge {
|
|
|
1111
1114
|
// 由 formatUsageStats 的 max/兜底公式处理。
|
|
1112
1115
|
}
|
|
1113
1116
|
else {
|
|
1114
|
-
// codex(jsonl 优先)或无 live usage → jsonl 兜底所有 flow
|
|
1117
|
+
// codex(jsonl 优先)或无 live usage → jsonl 兜底所有 flow 字段。
|
|
1118
|
+
// jsonl 的非累计字段已是末轮(本 run)scope(claude reader 修复后对齐
|
|
1119
|
+
// codex `last_token_usage` 语义,见 scalarScan),不再是 session 累计;
|
|
1120
|
+
// session 累计只经 cumulative* 字段透传,卡片"本 run ≤ 累计"不变量成立。
|
|
1115
1121
|
finalInputTokens = finalUsage?.inputTokens ?? liveInputTokens;
|
|
1116
1122
|
finalOutputTokens = finalUsage?.outputTokens ?? liveOutputTokens;
|
|
1117
1123
|
finalCacheReadTokens = finalUsage?.cacheReadTokens ?? finalCacheReadTokens;
|
|
@@ -1485,9 +1491,20 @@ export class Bridge {
|
|
|
1485
1491
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerCustom({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1486
1492
|
}
|
|
1487
1493
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1494
|
+
* Codex AskUserQuestion 补充说明(user_note,2026-08-18 live 验证模型理解
|
|
1495
|
+
* "user_note: <text>" 条目):记录到 coordinator,随答案一起提交。
|
|
1496
|
+
* 路由到 ApprovalCoordinator.answerNote。
|
|
1497
|
+
*/
|
|
1498
|
+
async handleApprovalAnswerNote(opts) {
|
|
1499
|
+
const log = getLogger();
|
|
1500
|
+
log.info(`[bridge] handleApprovalAnswerNote runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1501
|
+
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerNote({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Handle codex.compact card action — trigger a compaction request for any
|
|
1505
|
+
* runCompact-capable runner(codex/kimi/opencode/pi/claude,按
|
|
1506
|
+
* lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
|
|
1507
|
+
* and is in a terminal state, then calls the runner's runCompact().
|
|
1491
1508
|
*/
|
|
1492
1509
|
async handleCodexCompact(value, ctx) {
|
|
1493
1510
|
const log = getLogger();
|
|
@@ -1529,7 +1546,7 @@ export class Bridge {
|
|
|
1529
1546
|
});
|
|
1530
1547
|
}
|
|
1531
1548
|
/**
|
|
1532
|
-
* Stream a
|
|
1549
|
+
* Stream a compaction to a card: start a RunCardSession with
|
|
1533
1550
|
* operationKind='compaction' (no recursive Compact button), consume
|
|
1534
1551
|
* runner.runCompact() events, read authoritative jsonl usage, and finish
|
|
1535
1552
|
* the card. Shared by handleCodexCompact (run card button) and
|
|
@@ -1659,7 +1676,7 @@ export class Bridge {
|
|
|
1659
1676
|
await this.sendResult({ text: `⚠️ 未找到 session ${sessionId}(当前目录: ${cwd})` }, ctx);
|
|
1660
1677
|
return;
|
|
1661
1678
|
}
|
|
1662
|
-
// Check that the runner has runCompact
|
|
1679
|
+
// Check that the runner has runCompact(codex/kimi/opencode/pi/claude 鸭子探测)。
|
|
1663
1680
|
const runner = this.getRunner(cwd, agentKind);
|
|
1664
1681
|
if (!('runCompact' in runner) ||
|
|
1665
1682
|
typeof runner.runCompact !== 'function') {
|
|
@@ -42,6 +42,9 @@ export function renderApprovalArea(approval, opts) {
|
|
|
42
42
|
if (approval.kind === 'command') {
|
|
43
43
|
elements.push(...renderCommandApproval(approval, opts?.expired, opts?.runId));
|
|
44
44
|
}
|
|
45
|
+
else if (approval.kind === 'tool') {
|
|
46
|
+
elements.push(...renderToolApproval(approval, opts?.expired, opts?.runId));
|
|
47
|
+
}
|
|
45
48
|
else if (approval.kind === 'file') {
|
|
46
49
|
elements.push(...renderFileApproval(approval, opts?.expired, opts?.runId));
|
|
47
50
|
}
|
|
@@ -106,6 +109,42 @@ function renderDecisionButtons(approval, expired, runId) {
|
|
|
106
109
|
}));
|
|
107
110
|
}
|
|
108
111
|
// =============================================================================
|
|
112
|
+
// Tool Approval (non-command tools, e.g. ExitPlanMode)
|
|
113
|
+
// =============================================================================
|
|
114
|
+
/**
|
|
115
|
+
* 通用工具权限请求(kind === 'tool'):展示工具名 + 用途说明(reason)+ 原始
|
|
116
|
+
* input(若有)。ExitPlanMode 的 input 为空对象,工具名本身即审批内容,
|
|
117
|
+
* 不再落入 command 槽位显示无意义 `{}`。
|
|
118
|
+
*/
|
|
119
|
+
function renderToolApproval(approval, expired, runId) {
|
|
120
|
+
const elements = [];
|
|
121
|
+
// ExitPlanMode 是「退出计划模式」:标题用计划审批语义;其他工具用通用标题。
|
|
122
|
+
const isPlanExit = approval.toolName === 'ExitPlanMode';
|
|
123
|
+
elements.push({
|
|
124
|
+
tag: 'div',
|
|
125
|
+
text: {
|
|
126
|
+
tag: 'lark_md',
|
|
127
|
+
content: isPlanExit ? '**📋 计划审批**' : '**🔧 工具请求**',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const toolLabel = approval.toolName ?? '(unknown tool)';
|
|
131
|
+
elements.push({
|
|
132
|
+
tag: 'div',
|
|
133
|
+
text: { tag: 'lark_md', content: `🔧 \`${toolLabel}\`` },
|
|
134
|
+
});
|
|
135
|
+
pushIf(elements, reasonDiv(approval));
|
|
136
|
+
// 原始 input(非空时展示,截断保护卡片预算;ExitPlanMode 的 `{}` 不展示)。
|
|
137
|
+
if (approval.toolInput && approval.toolInput !== '{}') {
|
|
138
|
+
const neutralized = approval.toolInput.replace(/`/g, '·');
|
|
139
|
+
elements.push({
|
|
140
|
+
tag: 'div',
|
|
141
|
+
text: { tag: 'lark_md', content: `\`${neutralized.slice(0, 500)}\`` },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
145
|
+
return elements;
|
|
146
|
+
}
|
|
147
|
+
// =============================================================================
|
|
109
148
|
// Command Approval
|
|
110
149
|
// =============================================================================
|
|
111
150
|
function renderCommandApproval(approval, expired, runId) {
|
|
@@ -259,6 +298,14 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
259
298
|
tag: 'div',
|
|
260
299
|
text: { tag: 'lark_md', content: '**❓ 需要你回答**' },
|
|
261
300
|
});
|
|
301
|
+
// Kimi elicitation form:完整题干只在 form message 里合并出现,概要行展示
|
|
302
|
+
// 于提问卡顶部(数据驱动,Kimi runner 翻译时填充 approval.intro)。
|
|
303
|
+
if (approval.intro) {
|
|
304
|
+
elements.push({
|
|
305
|
+
tag: 'div',
|
|
306
|
+
text: { tag: 'lark_md', content: `📝 ${approval.intro}` },
|
|
307
|
+
});
|
|
308
|
+
}
|
|
262
309
|
if (expired) {
|
|
263
310
|
elements.push({
|
|
264
311
|
tag: 'div',
|
|
@@ -271,6 +318,7 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
271
318
|
const q = questions[i];
|
|
272
319
|
const selected = q.selected ?? [];
|
|
273
320
|
const header = q.header || `问题 ${i + 1}`;
|
|
321
|
+
const freeText = (q.options?.length ?? 0) === 0;
|
|
274
322
|
elements.push({
|
|
275
323
|
tag: 'div',
|
|
276
324
|
text: {
|
|
@@ -278,34 +326,26 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
278
326
|
content: `**${header}**${q.multiSelect ? '(可多选)' : ''}\n${q.question}`,
|
|
279
327
|
},
|
|
280
328
|
});
|
|
281
|
-
|
|
329
|
+
if (freeText) {
|
|
330
|
+
// 自由文本题(Codex options:null / Pi extension input):无选项行,
|
|
331
|
+
// 直接渲染题面 + 输入框,提交走 answerCustom(coordinator 不要求有选项)。
|
|
332
|
+
elements.push(renderAnswerInput(approval.requestId, i, q.placeholder ?? '输入回答…', runId));
|
|
333
|
+
// 已答回显:input_value 不跨卡片更新保留,多题场景下用户提交后若还有
|
|
334
|
+
// 其他问题未答,卡片必须展示已答文本(与自定义答案选中态同模式)。
|
|
335
|
+
const answered = q.selected?.find((s) => s.length > 0);
|
|
336
|
+
if (answered) {
|
|
337
|
+
elements.push({
|
|
338
|
+
tag: 'div',
|
|
339
|
+
text: { tag: 'lark_md', content: `✍️ 已答:${answered}` },
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
for (const option of q.options ?? []) {
|
|
282
345
|
const isSelected = selected.includes(option.label);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
tag: 'lark_md',
|
|
287
|
-
content: `${isSelected ? '✅' : '⬜'} ${option.label}${option.description ? `\n_${option.description}_` : ''}`,
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
elements.push({
|
|
291
|
-
tag: 'button',
|
|
292
|
-
text: { tag: 'plain_text', content: isSelected ? '取消选择' : '选择' },
|
|
293
|
-
type: isSelected ? 'default' : 'primary',
|
|
294
|
-
size: 'small',
|
|
295
|
-
behaviors: [
|
|
296
|
-
{
|
|
297
|
-
type: 'callback',
|
|
298
|
-
value: {
|
|
299
|
-
cmd: 'approval.answer',
|
|
300
|
-
requestId: approval.requestId,
|
|
301
|
-
questionIndex: i,
|
|
302
|
-
option: option.label,
|
|
303
|
-
runId,
|
|
304
|
-
nonce: mkNonce(),
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
});
|
|
346
|
+
// 单选/多选图标区分:单选 ⚪/🔵(radio 隐喻),多选 ⬜/☑️(checkbox 隐喻)。
|
|
347
|
+
const icon = q.multiSelect ? (isSelected ? '☑️' : '⬜') : isSelected ? '🔵' : '⚪';
|
|
348
|
+
elements.push(renderOptionRow(approval.requestId, i, option, isSelected, icon, runId));
|
|
309
349
|
}
|
|
310
350
|
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
311
351
|
// 已选文本,避免「点了没反应」的困惑(review P3)。
|
|
@@ -318,24 +358,21 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
318
358
|
}
|
|
319
359
|
// review P3-4:单选问题提供自定义答案(Other)输入——输入文本后点击
|
|
320
360
|
// 输入框右侧 ✓ 提交图标,input_value 经 connector raw 事件回传。
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
});
|
|
361
|
+
// 显隐按 isOther !== false:Kimi form 会丢弃非声明选项值,翻译时置
|
|
362
|
+
// isOther=false 隐藏输入;Claude 未设置该字段默认显示(行为不变)。
|
|
363
|
+
if (!q.multiSelect && q.isOther !== false) {
|
|
364
|
+
elements.push(renderAnswerInput(approval.requestId, i, '自定义答案(Other)…', runId, 'approval.answerCustom'));
|
|
365
|
+
}
|
|
366
|
+
// Codex user_note:选项之外的补充说明(allowNote 数据驱动门控)。
|
|
367
|
+
// 提交走 answerNote(coordinator 记录 note,随答案一起提交),已填回显。
|
|
368
|
+
if (q.allowNote === true) {
|
|
369
|
+
elements.push(renderAnswerInput(approval.requestId, i, '补充说明(可选)…', runId, 'approval.answerNote'));
|
|
370
|
+
if (q.note) {
|
|
371
|
+
elements.push({
|
|
372
|
+
tag: 'div',
|
|
373
|
+
text: { tag: 'lark_md', content: `📝 ${q.note}` },
|
|
374
|
+
});
|
|
375
|
+
}
|
|
339
376
|
}
|
|
340
377
|
// 多选问题:有勾选才给提交按钮(单选在协调器内即时提交)。
|
|
341
378
|
if (q.multiSelect && selected.length > 0) {
|
|
@@ -358,6 +395,111 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
358
395
|
});
|
|
359
396
|
}
|
|
360
397
|
}
|
|
398
|
+
// 提问卡底部统一「跳过回答」:走现有 approval.respond + decision=decline
|
|
399
|
+
// (coordinator 对 question 允许 decline 作为安全兜底),runner 按协议映射
|
|
400
|
+
// 跳过语义(Claude deny / Codex 空 answers / Kimi action decline / Pi cancelled)。
|
|
401
|
+
if (!expired) {
|
|
402
|
+
elements.push({
|
|
403
|
+
tag: 'button',
|
|
404
|
+
text: { tag: 'plain_text', content: '⏭️ 跳过回答' },
|
|
405
|
+
type: 'default',
|
|
406
|
+
behaviors: [
|
|
407
|
+
{
|
|
408
|
+
type: 'callback',
|
|
409
|
+
value: {
|
|
410
|
+
cmd: 'approval.respond',
|
|
411
|
+
decision: 'decline',
|
|
412
|
+
requestId: approval.requestId,
|
|
413
|
+
runId,
|
|
414
|
+
nonce: mkNonce(),
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
}
|
|
361
420
|
return elements;
|
|
362
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* 选项行:左按钮右描述(固定 100px 按钮列保证对齐),单选/多选图标区分。
|
|
424
|
+
* 与 renderAnswerInput 分开以便自由文本题不渲染选项行。
|
|
425
|
+
*/
|
|
426
|
+
function renderOptionRow(requestId, questionIndex, option, isSelected, icon, runId) {
|
|
427
|
+
return {
|
|
428
|
+
tag: 'column_set',
|
|
429
|
+
flex_mode: 'none',
|
|
430
|
+
columns: [
|
|
431
|
+
{
|
|
432
|
+
tag: 'column',
|
|
433
|
+
width: 'auto',
|
|
434
|
+
vertical_align: 'center',
|
|
435
|
+
elements: [
|
|
436
|
+
{
|
|
437
|
+
tag: 'button',
|
|
438
|
+
text: { tag: 'plain_text', content: isSelected ? '取消选择' : '选择' },
|
|
439
|
+
type: isSelected ? 'default' : 'primary',
|
|
440
|
+
size: 'small',
|
|
441
|
+
// 固定宽度让所有选项行的按钮左对齐(描述长短不影响按钮列)。
|
|
442
|
+
width: '100px',
|
|
443
|
+
behaviors: [
|
|
444
|
+
{
|
|
445
|
+
type: 'callback',
|
|
446
|
+
value: {
|
|
447
|
+
cmd: 'approval.answer',
|
|
448
|
+
requestId,
|
|
449
|
+
questionIndex,
|
|
450
|
+
option: option.label,
|
|
451
|
+
runId,
|
|
452
|
+
nonce: mkNonce(),
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
tag: 'column',
|
|
461
|
+
width: 'weighted',
|
|
462
|
+
weight: 1,
|
|
463
|
+
vertical_align: 'center',
|
|
464
|
+
elements: [
|
|
465
|
+
{
|
|
466
|
+
tag: 'div',
|
|
467
|
+
text: {
|
|
468
|
+
tag: 'lark_md',
|
|
469
|
+
content: `${icon} ${option.label}${option.description ? `\n_${option.description}_` : ''}`,
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 文本输入行(Other 输入与自由文本题共用):输入后点击右侧 ✓ 提交图标,
|
|
479
|
+
* input_value 经 connector raw 事件回传(CardKit input 的 input_value 会被
|
|
480
|
+
* SDK normalizer 丢弃,必须走 includeRawEvent 路径)。
|
|
481
|
+
*/
|
|
482
|
+
function renderAnswerInput(requestId, questionIndex, placeholder, runId, cmd = 'approval.answerCustom') {
|
|
483
|
+
// name 必须含 cmd,保证同一题同时渲染多个输入框(如单选选项题的
|
|
484
|
+
// Other 输入 + Note 输入)时元素 name 唯一——飞书对同卡内重复 name
|
|
485
|
+
// 报 ErrCode 11310 拒绝整卡(2026-08-19 线上:name(answer-custom-0-0) duplicate)。
|
|
486
|
+
const kind = cmd === 'approval.answerNote' ? 'note' : 'custom';
|
|
487
|
+
return {
|
|
488
|
+
tag: 'input',
|
|
489
|
+
name: `answer-${kind}-${requestId}-${questionIndex}`,
|
|
490
|
+
placeholder: { tag: 'plain_text', content: placeholder },
|
|
491
|
+
behaviors: [
|
|
492
|
+
{
|
|
493
|
+
type: 'callback',
|
|
494
|
+
value: {
|
|
495
|
+
cmd,
|
|
496
|
+
requestId,
|
|
497
|
+
questionIndex,
|
|
498
|
+
runId,
|
|
499
|
+
nonce: mkNonce(),
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
363
505
|
//# sourceMappingURL=approval-render.js.map
|
|
@@ -23,15 +23,17 @@ export declare function stopButton(runId: string): object;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function newSessionButton(): object;
|
|
25
25
|
/**
|
|
26
|
-
* Create a CardKit 2.0 Compact button for
|
|
27
|
-
*
|
|
26
|
+
* Create a CardKit 2.0 Compact button for any runCompact-capable runner
|
|
27
|
+
* (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
|
|
28
|
+
* Triggers the compact flow via the codex.compact callback(handler 按
|
|
29
|
+
* runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
|
|
28
30
|
*/
|
|
29
31
|
export declare function compactButton(runId: string): object;
|
|
30
32
|
/**
|
|
31
33
|
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
32
34
|
* Unlike compactButton (which targets a finished run's runId), this targets a
|
|
33
|
-
* sessionId directly: the bridge resolves the session and calls the
|
|
34
|
-
*
|
|
35
|
+
* sessionId directly: the bridge resolves the session and calls the runner's
|
|
36
|
+
* runCompact() without a runId. Carries agent so the handler routes
|
|
35
37
|
* to the correct session reader + runner when the card was rendered for a
|
|
36
38
|
* non-default agent.
|
|
37
39
|
*/
|
package/dist/card/card-shared.js
CHANGED
|
@@ -78,8 +78,10 @@ export function newSessionButton() {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Create a CardKit 2.0 Compact button for
|
|
82
|
-
*
|
|
81
|
+
* Create a CardKit 2.0 Compact button for any runCompact-capable runner
|
|
82
|
+
* (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
|
|
83
|
+
* Triggers the compact flow via the codex.compact callback(handler 按
|
|
84
|
+
* runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
|
|
83
85
|
*/
|
|
84
86
|
export function compactButton(runId) {
|
|
85
87
|
return {
|
|
@@ -92,8 +94,8 @@ export function compactButton(runId) {
|
|
|
92
94
|
/**
|
|
93
95
|
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
94
96
|
* Unlike compactButton (which targets a finished run's runId), this targets a
|
|
95
|
-
* sessionId directly: the bridge resolves the session and calls the
|
|
96
|
-
*
|
|
97
|
+
* sessionId directly: the bridge resolves the session and calls the runner's
|
|
98
|
+
* runCompact() without a runId. Carries agent so the handler routes
|
|
97
99
|
* to the correct session reader + runner when the card was rendered for a
|
|
98
100
|
* non-default agent.
|
|
99
101
|
*/
|
|
@@ -8,6 +8,13 @@ export interface RunCardRenderOptions {
|
|
|
8
8
|
* Defaults to 'claude' when not provided; the bridge passes `config.defaultAgent`.
|
|
9
9
|
*/
|
|
10
10
|
agentKind?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the terminal-state Compact button may be rendered. Defaults to true.
|
|
13
|
+
* The bridge passes `runner 有 runCompact`(与 resume 卡 / auto-resume 卡的
|
|
14
|
+
* hasRunCompact 探测一致),保证「有能力才渲染」:claude(stream-json,
|
|
15
|
+
* 无 turn_started → operationKind undefined)同样能拿到按钮。
|
|
16
|
+
*/
|
|
17
|
+
compactSupported?: boolean;
|
|
11
18
|
}
|
|
12
19
|
type BlockGroup = {
|
|
13
20
|
kind: 'thinking';
|