lark-remote 0.5.12 → 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.
Files changed (95) hide show
  1. package/CHANGELOG.md +13 -2
  2. package/README.en.md +1 -14
  3. package/README.md +1 -14
  4. package/dist/bridge/approval-coordinator.d.ts +1 -0
  5. package/dist/bridge/approval-coordinator.js +12 -24
  6. package/dist/bridge/index.d.ts +7 -0
  7. package/dist/bridge/index.js +77 -227
  8. package/dist/bridge/run-approval.d.ts +15 -0
  9. package/dist/bridge/run-approval.js +41 -0
  10. package/dist/bridge/run-usage.d.ts +46 -0
  11. package/dist/bridge/run-usage.js +96 -0
  12. package/dist/bridge/session-writeback.d.ts +18 -0
  13. package/dist/bridge/session-writeback.js +36 -0
  14. package/dist/card/bash-card-session.js +2 -5
  15. package/dist/card/card-session.d.ts +2 -0
  16. package/dist/card/card-session.js +20 -22
  17. package/dist/card/card-shared.d.ts +4 -4
  18. package/dist/card/card-shared.js +3 -9
  19. package/dist/card/collapsible.js +3 -2
  20. package/dist/card/final-answer-overflow.d.ts +7 -0
  21. package/dist/card/final-answer-overflow.js +46 -0
  22. package/dist/card/run-card-session.js +2 -7
  23. package/dist/card/run-renderer.d.ts +14 -0
  24. package/dist/card/run-renderer.js +119 -1
  25. package/dist/card/run-state.d.ts +12 -1
  26. package/dist/card/run-state.js +73 -34
  27. package/dist/config/codex-config.js +4 -4
  28. package/dist/config/index.d.ts +4 -2
  29. package/dist/config/index.js +6 -4
  30. package/dist/inbound/turn-assembler.js +6 -0
  31. package/dist/index.js +15 -409
  32. package/dist/message-handlers.d.ts +26 -0
  33. package/dist/message-handlers.js +275 -0
  34. package/dist/router/card-action-payload.d.ts +1 -1
  35. package/dist/router/card-action-payload.js +1 -1
  36. package/dist/router/commands/ls.d.ts +9 -0
  37. package/dist/router/commands/ls.js +243 -0
  38. package/dist/router/commands/order.d.ts +12 -0
  39. package/dist/router/commands/order.js +178 -0
  40. package/dist/router/commands/resume.d.ts +47 -0
  41. package/dist/router/commands/resume.js +246 -0
  42. package/dist/router/commands/ws.d.ts +15 -0
  43. package/dist/router/commands/ws.js +189 -0
  44. package/dist/router/config/claude.js +3 -3
  45. package/dist/router/config/codex.js +2 -6
  46. package/dist/router/config/common/turn-idle.d.ts +2 -0
  47. package/dist/router/config/common/turn-idle.js +9 -0
  48. package/dist/router/config/kimi.js +2 -6
  49. package/dist/router/config/opencode.js +2 -6
  50. package/dist/router/index.d.ts +14 -22
  51. package/dist/router/index.js +70 -1152
  52. package/dist/router/paging.d.ts +13 -0
  53. package/dist/router/paging.js +20 -0
  54. package/dist/router/path-validation.d.ts +27 -0
  55. package/dist/router/path-validation.js +45 -0
  56. package/dist/runner/claude/runner.d.ts +3 -4
  57. package/dist/runner/claude/runner.js +4 -5
  58. package/dist/runner/claude/session.js +1 -1
  59. package/dist/runner/codex/app-server/translator.d.ts +2 -0
  60. package/dist/runner/codex/app-server/translator.js +65 -9
  61. package/dist/runner/common/acp/base-translator.d.ts +3 -8
  62. package/dist/runner/common/acp/base-translator.js +16 -7
  63. package/dist/runner/common/spawning-runner.js +10 -4
  64. package/dist/runner/common/turn-started.d.ts +2 -0
  65. package/dist/runner/common/turn-started.js +10 -0
  66. package/dist/runner/dsh/auth.js +2 -3
  67. package/dist/runner/dsh/client.d.ts +1 -6
  68. package/dist/runner/dsh/client.js +11 -165
  69. package/dist/runner/dsh/daemon.d.ts +0 -2
  70. package/dist/runner/dsh/daemon.js +1 -6
  71. package/dist/runner/dsh/errors.d.ts +5 -0
  72. package/dist/runner/dsh/errors.js +11 -0
  73. package/dist/runner/dsh/exit-cancel.d.ts +0 -2
  74. package/dist/runner/dsh/exit-cancel.js +1 -1
  75. package/dist/runner/dsh/home.d.ts +2 -0
  76. package/dist/runner/dsh/home.js +8 -0
  77. package/dist/runner/dsh/inflight.d.ts +0 -2
  78. package/dist/runner/dsh/inflight.js +1 -1
  79. package/dist/runner/dsh/mux-stream.d.ts +26 -0
  80. package/dist/runner/dsh/mux-stream.js +157 -0
  81. package/dist/runner/dsh/translator.d.ts +0 -10
  82. package/dist/runner/dsh/translator.js +1 -1
  83. package/dist/runner/index.d.ts +1 -1
  84. package/dist/runner/kimi/acp/translator.js +1 -12
  85. package/dist/runner/opencode/acp/translator.js +1 -12
  86. package/dist/runner/pi/rpc/translator.d.ts +2 -8
  87. package/dist/runner/pi/rpc/translator.js +2 -7
  88. package/dist/runner/registry.d.ts +0 -19
  89. package/dist/runner/registry.js +0 -32
  90. package/dist/runner/types.d.ts +17 -1
  91. package/dist/session/claude/session-index.js +4 -2
  92. package/dist/session/kimi/sessions.js +2 -2
  93. package/dist/session/opencode/sessions.js +2 -1
  94. package/dist/session/pi/sessions.js +3 -2
  95. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { keepTail } from '../common/truncate.js';
2
+ import { asRecord } from '../common/tool-summary.js';
2
3
  const MAX_REASONING_CHARS = 4_000;
3
4
  const MAX_TEXT_CHARS = 12_000;
4
5
  const MAX_TOOL_DETAIL_CHARS = 3_000;
@@ -10,7 +11,7 @@ const MAX_BLOCKS = 24;
10
11
  * 禁止各自再抄一份字面量——三处漂移过一次(goal_continuing 只进白名单不进
11
12
  * 按钮判据),新增终态时必漏一处的风险是结构性的。
12
13
  */
13
- export const RUN_TERMINAL_STATES = [
14
+ const RUN_TERMINAL_STATES = [
14
15
  'done',
15
16
  'error',
16
17
  'interrupted',
@@ -50,6 +51,8 @@ export function reduceRunState(state, event) {
50
51
  return reduceBackgroundJobsEvent(state, event);
51
52
  if (event.type === 'result')
52
53
  return reduceResultEvent(state, event);
54
+ if (event.type === 'final_answer')
55
+ return reduceFinalAnswerEvent(state, event);
53
56
  if (event.type === 'assistant')
54
57
  return reduceAssistantEvent(state, event);
55
58
  if (event.type === 'user')
@@ -187,7 +190,7 @@ function reduceTurnDiffEvent(state, event) {
187
190
  id: event.itemId,
188
191
  name: event.toolName ?? 'command',
189
192
  input: inputStr,
190
- parsedInput: tryParseRecord(inputStr),
193
+ parsedInput: asRecord(inputStr),
191
194
  ...(event.toolHint !== undefined ? { toolHint: event.toolHint } : {}),
192
195
  output: keepTail(event.toolOutput, MAX_TOOL_DETAIL_CHARS),
193
196
  status: complete ? (event.toolStatus ?? 'ok') : 'running',
@@ -320,31 +323,82 @@ function reduceResultEvent(state, event) {
320
323
  // sessionId is set by system.init; undefined means init hasn't arrived yet.
321
324
  if (state.sessionId === undefined)
322
325
  return state;
326
+ const withFinalAnswer = promoteFinalAnswer(state, 'inferred');
323
327
  const resultEvent = event;
324
328
  const incomingSubtype = event.subtype;
325
329
  const incomingErrorMsg = incomingSubtype === 'error' ? (resultEvent.errorMessage ?? 'Agent 返回错误结果') : undefined;
326
330
  // 合并 subtype:error 优先,其次 interrupted(任一为 error 即 error;
327
331
  // 否则任一为 interrupted 即 interrupted;interrupted 不得并入 success)。
328
- const mergedSubtype = state.resultSubtype === 'error' || incomingSubtype === 'error'
332
+ const mergedSubtype = withFinalAnswer.resultSubtype === 'error' || incomingSubtype === 'error'
329
333
  ? 'error'
330
- : state.resultSubtype === 'interrupted' || incomingSubtype === 'interrupted'
334
+ : withFinalAnswer.resultSubtype === 'interrupted' || incomingSubtype === 'interrupted'
331
335
  ? 'interrupted'
332
336
  : 'success';
333
337
  // 合并 errorMsg:首个非 undefined 胜出(existing ?? incoming)
334
- const mergedErrorMsg = state.errorMsg ?? incomingErrorMsg;
338
+ const mergedErrorMsg = withFinalAnswer.errorMsg ?? incomingErrorMsg;
335
339
  return {
336
- ...state,
340
+ ...withFinalAnswer,
337
341
  terminal: 'finalizing',
338
342
  footer: null,
339
- blocks: markThinkingInactive(state.blocks),
343
+ blocks: markThinkingInactive(withFinalAnswer.blocks),
340
344
  resultSubtype: mergedSubtype,
341
345
  errorMsg: mergedErrorMsg,
342
346
  // costUsd:首个非 undefined 胜出(与 errorMsg 合并语义一致)
343
- ...(event.total_cost_usd !== undefined && state.costUsd === undefined
347
+ ...(event.total_cost_usd !== undefined && withFinalAnswer.costUsd === undefined
344
348
  ? { costUsd: event.total_cost_usd }
345
349
  : {}),
346
350
  };
347
351
  }
352
+ /** Record an authoritative protocol final answer and remove its progress copy. */
353
+ function reduceFinalAnswerEvent(state, event) {
354
+ if (event.type !== 'final_answer')
355
+ return state;
356
+ if (!event.text.trim() && !state.finalAnswer)
357
+ return state;
358
+ const blocks = event.itemId
359
+ ? state.blocks.filter((block) => block.kind !== 'text' || block.itemId !== event.itemId)
360
+ : state.blocks;
361
+ const content = event.text.trim() ? event.text : (state.finalAnswer?.content ?? '');
362
+ return {
363
+ ...state,
364
+ blocks,
365
+ finalAnswer: {
366
+ content,
367
+ timestamp: state.finalAnswer?.timestamp ?? event.timestamp,
368
+ complete: event.complete ?? state.finalAnswer?.complete ?? false,
369
+ source: 'protocol',
370
+ },
371
+ footer: 'streaming',
372
+ };
373
+ }
374
+ /**
375
+ * Agents without an explicit final-answer marker use their last non-empty
376
+ * assistant text block. Moving it out of `blocks` protects it from
377
+ * MAX_BLOCKS and lets the renderer assign it first claim on card space.
378
+ */
379
+ function promoteFinalAnswer(state, source) {
380
+ if (state.finalAnswer) {
381
+ return state.finalAnswer.complete
382
+ ? state
383
+ : { ...state, finalAnswer: { ...state.finalAnswer, complete: true } };
384
+ }
385
+ for (let i = state.blocks.length - 1; i >= 0; i--) {
386
+ const block = state.blocks[i];
387
+ if (block.kind !== 'text' || !block.content.trim())
388
+ continue;
389
+ return {
390
+ ...state,
391
+ blocks: state.blocks.filter((_, index) => index !== i),
392
+ finalAnswer: {
393
+ content: block.content,
394
+ timestamp: block.timestamp,
395
+ complete: true,
396
+ source,
397
+ },
398
+ };
399
+ }
400
+ return state;
401
+ }
348
402
  /**
349
403
  * 后台任务名单 → RunState.backgroundJobs。
350
404
  *
@@ -458,7 +512,7 @@ function reduceAssistantEvent(state, event) {
458
512
  id: content.id,
459
513
  name: content.name,
460
514
  input: inputStr,
461
- parsedInput: tryParseRecord(inputStr),
515
+ parsedInput: asRecord(inputStr),
462
516
  ...(content.summary !== undefined ? { summary: content.summary } : {}),
463
517
  status: 'running',
464
518
  startedAt: event.timestamp,
@@ -618,30 +672,33 @@ function reduceFileChangeEvent(state, event) {
618
672
  };
619
673
  }
620
674
  export function finishRun(state, terminal, meta = {}) {
675
+ const withFinalAnswer = promoteFinalAnswer(state, 'inferred');
621
676
  // 允许从 running 或 finalizing finish(/stop、idle watchdog、进程退出 finally)。
622
677
  // finalizing 是非终态(result 已收到,进程未退出),可被 finish 转终态。
623
678
  // 如果已经是终态,仍然允许补充 meta 信息(如 usage 等,bridge finally 的
624
679
  // else-if-sawResult 分支用此路径补充 token 统计)。
625
- if (state.terminal !== 'running' && state.terminal !== 'finalizing') {
680
+ if (withFinalAnswer.terminal !== 'running' && withFinalAnswer.terminal !== 'finalizing') {
626
681
  // Already terminal: apply meta fields if provided (e.g. usage from jsonl)
627
682
  const hasMeta = Object.keys(meta).some((k) => meta[k] !== undefined);
628
683
  if (!hasMeta)
629
- return state;
630
- return { ...state, ...sanitizeMeta(meta) };
684
+ return withFinalAnswer;
685
+ return { ...withFinalAnswer, ...sanitizeMeta(meta) };
631
686
  }
632
687
  // interrupted 终态的中断来源:显式传入优先;否则从顶层持久标记推导
633
688
  // (approval_expired 已置位但调用方未传 reason 的路径,如协调器过期自动
634
689
  // cancel。原因必须在终态确定,不依赖调用方记得传参)。
635
690
  const derivedReason = meta.interruptedReason ??
636
- (terminal === 'interrupted' && state.approvalExpired === true ? 'approval_timeout' : undefined);
691
+ (terminal === 'interrupted' && withFinalAnswer.approvalExpired === true
692
+ ? 'approval_timeout'
693
+ : undefined);
637
694
  return {
638
- ...state,
695
+ ...withFinalAnswer,
639
696
  terminal,
640
697
  footer: null,
641
698
  // 耗时定格:终态之后再渲染(补 token 统计、点「详情」重渲染)都沿用同一
642
699
  // 结束时刻,否则每次 patch 的数字都会变大。
643
- finishedAt: state.finishedAt ?? Date.now(),
644
- blocks: markThinkingInactive(state.blocks),
700
+ finishedAt: withFinalAnswer.finishedAt ?? Date.now(),
701
+ blocks: markThinkingInactive(withFinalAnswer.blocks),
645
702
  ...sanitizeMeta(meta),
646
703
  ...(derivedReason !== undefined ? { interruptedReason: derivedReason } : {}),
647
704
  };
@@ -673,24 +730,6 @@ function stringifyUnknown(value) {
673
730
  return String(value);
674
731
  }
675
732
  }
676
- /**
677
- * parse a (possibly truncated) string into a record, mirroring what
678
- * `tool-render.ts`'s `asRecord` did on every render. Returns `null` when the
679
- * string is empty, non-JSON, or parses to a non-object — so a truncation-broken
680
- * JSON yields `null` exactly as the old per-render parse did. Computed once at
681
- * store time and cached on `ToolEntry.parsedInput`.
682
- */
683
- function tryParseRecord(input) {
684
- if (!input)
685
- return null;
686
- try {
687
- const p = JSON.parse(input);
688
- return p && typeof p === 'object' ? p : null;
689
- }
690
- catch {
691
- return null;
692
- }
693
- }
694
733
  function keepLatestBlocks(blocks) {
695
734
  return blocks.length <= MAX_BLOCKS ? blocks : blocks.slice(-MAX_BLOCKS);
696
735
  }
@@ -519,10 +519,10 @@ export function loadCodexConfig(opts = {}) {
519
519
  }
520
520
  catch {
521
521
  // config.toml 读取/解析失败:readModelCatalogJsonPath 对此返回
522
- // TOML_PARSE_FAILED_SENTINEL(isCodexCatalogMode → false),进入本 catch
523
- // 时 catalogMode 恒为 false——下面的 `if (catalogMode)` 分支实际不可达,
524
- // 仅作防御保留。真正「catalog 声明退化」的场景('' 哨兵)在 parse 之前
525
- // 就已按 FALLBACK 兜底处理,不会走到这里。
522
+ // TOML_PARSE_FAILED_SENTINEL(isCodexCatalogMode → false),因此正常路径下
523
+ // catalogMode 恒为 false,下面的 `if (catalogMode)` 分支只在两次读取之间
524
+ // config.toml 被改写(TOCTOU)时可达;保留该防御,避免把“已声明 catalog、
525
+ // 随后配置损坏”误降级成 bundled 模型。
526
526
  if (catalogMode) {
527
527
  return {
528
528
  currentModel: '',
@@ -22,6 +22,8 @@ export declare const PI_THINKING_LEVELS: readonly ["off", "minimal", "low", "med
22
22
  export declare const DEFAULT_STOP_GRACE_MS: 5000;
23
23
  /** Codex app-server turn idle timeout default (minutes). */
24
24
  export declare const DEFAULT_TURN_IDLE_TIMEOUT_MINUTES: 30;
25
+ /** Approval timeout default (ms), shared by schema and bridge fallback. */
26
+ export declare const DEFAULT_APPROVAL_TIMEOUT_MS: number;
25
27
  /**
26
28
  * 入站媒体默认配置(schema 默认与 connector 兜底共用,避免双源漂移)。
27
29
  *
@@ -48,10 +50,10 @@ declare const ClaudeConfigSchema: z.ZodObject<{
48
50
  permissionMode: z.ZodDefault<z.ZodEnum<{
49
51
  default: "default";
50
52
  plan: "plan";
51
- bypassPermissions: "bypassPermissions";
52
53
  auto: "auto";
53
54
  manual: "manual";
54
55
  acceptEdits: "acceptEdits";
56
+ bypassPermissions: "bypassPermissions";
55
57
  dontAsk: "dontAsk";
56
58
  }>>;
57
59
  approvalTimeoutMs: z.ZodDefault<z.ZodNumber>;
@@ -163,10 +165,10 @@ export declare const AppConfigSchema: z.ZodObject<{
163
165
  permissionMode: z.ZodDefault<z.ZodEnum<{
164
166
  default: "default";
165
167
  plan: "plan";
166
- bypassPermissions: "bypassPermissions";
167
168
  auto: "auto";
168
169
  manual: "manual";
169
170
  acceptEdits: "acceptEdits";
171
+ bypassPermissions: "bypassPermissions";
170
172
  dontAsk: "dontAsk";
171
173
  }>>;
172
174
  approvalTimeoutMs: z.ZodDefault<z.ZodNumber>;
@@ -50,6 +50,8 @@ const DEFAULTS = {
50
50
  export const DEFAULT_STOP_GRACE_MS = DEFAULTS.STOP_GRACE_MS;
51
51
  /** Codex app-server turn idle timeout default (minutes). */
52
52
  export const DEFAULT_TURN_IDLE_TIMEOUT_MINUTES = DEFAULTS.TURN_IDLE_TIMEOUT_MINUTES;
53
+ /** Approval timeout default (ms), shared by schema and bridge fallback. */
54
+ export const DEFAULT_APPROVAL_TIMEOUT_MS = DEFAULTS.APPROVAL_TIMEOUT_MS;
53
55
  /**
54
56
  * 入站媒体默认配置(schema 默认与 connector 兜底共用,避免双源漂移)。
55
57
  *
@@ -83,10 +85,10 @@ const ClaudeConfigSchema = z.object({
83
85
  effort: z.enum(CLAUDE_EFFORTS).default('medium'),
84
86
  /**
85
87
  * Claude 权限模式(官方 --permission-mode 枚举 + 'default'=省略该参数)。
86
- * 默认 bypassPermissions 保持现有行为(无审批卡);配置为其他值后激活
87
- * 交互式审批(control_request → run 卡审批区)。
88
+ * 默认 auto:低风险操作自动放行,高风险操作走审批(control_request →
89
+ * run 卡审批区);bypassPermissions 仍可选,但不再是默认值。
88
90
  */
89
- permissionMode: z.enum(CLAUDE_PERMISSION_MODES).default('bypassPermissions'),
91
+ permissionMode: z.enum(CLAUDE_PERMISSION_MODES).default('auto'),
90
92
  /** 审批请求超时(ms),默认 5 分钟;超时自动发送 cancel 并中断 turn。
91
93
  * 0 = 立即过期(fail-fast,避免审批永久挂起),不推荐;勿随意改短。 */
92
94
  approvalTimeoutMs: z.number().int().min(0).default(DEFAULTS.APPROVAL_TIMEOUT_MS),
@@ -330,7 +332,7 @@ const TEMPLATE = `feishu:
330
332
  claude:
331
333
  model: ${DEFAULTS.CLAUDE_MODEL}
332
334
  effort: medium
333
- permissionMode: bypassPermissions
335
+ permissionMode: auto
334
336
  approvalTimeoutMs: ${DEFAULTS.APPROVAL_TIMEOUT_MS}
335
337
  idleTtlMinutes: ${DEFAULTS.CLAUDE_IDLE_TTL_MINUTES}
336
338
  stopGraceMs: ${DEFAULTS.STOP_GRACE_MS}
@@ -227,6 +227,12 @@ export class InboundTurnAssembler {
227
227
  }
228
228
  catch (err) {
229
229
  getLogger().error('[inbound] turn commit failed:', err?.message ?? String(err));
230
+ try {
231
+ await this.deps.onReceipt?.(ctx, '⚠️ 消息处理失败,请重试;若持续失败请查看 lark-remote 日志。');
232
+ }
233
+ catch (receiptErr) {
234
+ getLogger().error('[inbound] failed to report turn commit failure:', receiptErr?.message ?? String(receiptErr));
235
+ }
230
236
  }
231
237
  }
232
238
  }