lark-remote 0.4.3 → 0.4.5

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 (106) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/binder.d.ts +11 -10
  3. package/dist/binder.js +17 -16
  4. package/dist/bridge/inbound-media.d.ts +1 -27
  5. package/dist/bridge/inbound-media.js +13 -14
  6. package/dist/bridge/index.d.ts +49 -49
  7. package/dist/bridge/index.js +99 -92
  8. package/dist/bridge/queue-manager.d.ts +33 -26
  9. package/dist/bridge/queue-manager.js +117 -117
  10. package/dist/card/bash-renderer.js +4 -4
  11. package/dist/card/card-shared.d.ts +1 -1
  12. package/dist/card/card-shared.js +2 -2
  13. package/dist/card/run-renderer.js +8 -8
  14. package/dist/card/run-state.js +81 -31
  15. package/dist/card/tool-render.js +6 -2
  16. package/dist/cli.js +5 -6
  17. package/dist/clone.d.ts +1 -6
  18. package/dist/clone.js +4 -4
  19. package/dist/common/clamp.d.ts +2 -0
  20. package/dist/common/clamp.js +5 -0
  21. package/dist/common/fs.d.ts +4 -3
  22. package/dist/common/fs.js +5 -4
  23. package/dist/config/dir.d.ts +4 -5
  24. package/dist/config/dir.js +7 -8
  25. package/dist/config/wizard.js +2 -2
  26. package/dist/connector/index.d.ts +0 -2
  27. package/dist/connector/index.js +5 -5
  28. package/dist/inbound/receipt.d.ts +1 -3
  29. package/dist/inbound/receipt.js +2 -2
  30. package/dist/inbound/turn-assembler.d.ts +1 -3
  31. package/dist/inbound/turn-assembler.js +2 -2
  32. package/dist/index.js +30 -22
  33. package/dist/instance-lock.js +4 -4
  34. package/dist/persistence/atomic-write.js +3 -3
  35. package/dist/platform/agent-stopper.d.ts +1 -1
  36. package/dist/platform/terminator-posix.js +5 -5
  37. package/dist/platform/terminator-win32.js +1 -1
  38. package/dist/router/card-helpers.d.ts +1 -3
  39. package/dist/router/card-helpers.js +2 -2
  40. package/dist/router/config/codex.js +1 -1
  41. package/dist/router/config/common/model-patch.d.ts +1 -2
  42. package/dist/router/config/common/model-patch.js +1 -2
  43. package/dist/router/config/opencode.js +1 -1
  44. package/dist/router/config/pi.js +1 -1
  45. package/dist/router/index.d.ts +22 -22
  46. package/dist/router/index.js +71 -52
  47. package/dist/router/order-exec-dispatch.d.ts +3 -10
  48. package/dist/router/order-exec-dispatch.js +3 -3
  49. package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
  50. package/dist/router/{utils.js → usage-format.js} +3 -8
  51. package/dist/runner/claude/runner.d.ts +1 -1
  52. package/dist/runner/claude/runner.js +1 -2
  53. package/dist/runner/claude/session.d.ts +2 -4
  54. package/dist/runner/claude/session.js +7 -9
  55. package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +2 -8
  56. package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +4 -3
  57. package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
  58. package/dist/runner/common/acp/base-acp-runner.js +1 -1
  59. package/dist/runner/common/acp/base-translator.d.ts +43 -0
  60. package/dist/runner/common/acp/base-translator.js +83 -18
  61. package/dist/runner/common/connection-based-runner.d.ts +4 -4
  62. package/dist/runner/common/connection-based-runner.js +3 -3
  63. package/dist/runner/common/jsonrpc/connection-manager.d.ts +12 -12
  64. package/dist/runner/common/jsonrpc/connection-manager.js +36 -36
  65. package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
  66. package/dist/runner/common/{usage.js → result-usage.js} +2 -2
  67. package/dist/runner/common/spawning-runner.d.ts +2 -2
  68. package/dist/runner/common/spawning-runner.js +8 -8
  69. package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
  70. package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
  71. package/dist/runner/dsh/client.d.ts +1 -1
  72. package/dist/runner/dsh/client.js +1 -1
  73. package/dist/runner/dsh/runner.js +1 -1
  74. package/dist/runner/kimi/acp/runner.d.ts +1 -1
  75. package/dist/runner/kimi/acp/runner.js +1 -1
  76. package/dist/runner/kimi/acp/translator.js +1 -1
  77. package/dist/runner/opencode/acp/runner.d.ts +2 -2
  78. package/dist/runner/opencode/acp/runner.js +1 -1
  79. package/dist/runner/opencode/acp/translator.d.ts +22 -1
  80. package/dist/runner/opencode/acp/translator.js +61 -1
  81. package/dist/runner/pi/rpc/runner.d.ts +1 -1
  82. package/dist/runner/pi/rpc/translator.d.ts +0 -1
  83. package/dist/runner/pi/rpc/translator.js +0 -2
  84. package/dist/runner/registry.d.ts +3 -3
  85. package/dist/runner/registry.js +3 -3
  86. package/dist/session/claude/session-index.d.ts +1 -1
  87. package/dist/session/claude/session-index.js +1 -1
  88. package/dist/session/claude/session-reader.d.ts +1 -1
  89. package/dist/session/claude/session-reader.js +1 -1
  90. package/dist/session/codex/rollout-reader.js +2 -2
  91. package/dist/session/dsh/sessions.d.ts +1 -1
  92. package/dist/session/dsh/sessions.js +1 -1
  93. package/dist/session/kimi/sessions.d.ts +0 -4
  94. package/dist/session/kimi/sessions.js +2 -8
  95. package/dist/session/opencode/sessions.js +3 -3
  96. package/dist/session/registry.d.ts +1 -1
  97. package/dist/session/registry.js +1 -1
  98. package/dist/session/session-store.d.ts +2 -3
  99. package/dist/session/session-store.js +2 -4
  100. package/dist/update/index.d.ts +1 -1
  101. package/dist/update/index.js +1 -1
  102. package/dist/update/version-check.d.ts +5 -3
  103. package/dist/update/version-check.js +10 -8
  104. package/dist/workspace/index.d.ts +4 -4
  105. package/dist/workspace/index.js +5 -5
  106. package/package.json +1 -1
@@ -34,7 +34,7 @@ export declare function newSessionButton(): object;
34
34
  /**
35
35
  * Create a CardKit 2.0 Compact button for any runCompact-capable runner
36
36
  * (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
37
- * Triggers the compact flow via the codex.compact callback(handler 按
37
+ * Triggers the compact flow via the compact callback(handler 按
38
38
  * runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
39
39
  */
40
40
  export declare function compactButton(runId: string): object;
@@ -84,11 +84,11 @@ export function newSessionButton() {
84
84
  /**
85
85
  * Create a CardKit 2.0 Compact button for any runCompact-capable runner
86
86
  * (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
87
- * Triggers the compact flow via the codex.compact callback(handler 按
87
+ * Triggers the compact flow via the compact callback(handler 按
88
88
  * runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
89
89
  */
90
90
  export function compactButton(runId) {
91
- return cardButton('🗜 Compact', { cmd: 'codex.compact', runId }, { type: 'primary' });
91
+ return cardButton('🗜 Compact', { cmd: 'compact', runId }, { type: 'primary' });
92
92
  }
93
93
  /**
94
94
  * Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
@@ -3,7 +3,7 @@ import { collapsibleMarkdownPanel, markdownDiv } from './collapsible.js';
3
3
  import { toolBodyMd, toolHeaderText } from './tool-render.js';
4
4
  import { truncateUtf8, truncateMarkdownTables, CARD_BUDGET_BYTES } from './text-truncate.js';
5
5
  import { formatTimestamp } from './time.js';
6
- import { formatUsageStats } from '../router/utils.js';
6
+ import { formatUsageStats } from '../router/usage-format.js';
7
7
  import { renderApprovalArea } from './approval-render.js';
8
8
  const REASONING_BYTES = 4_500;
9
9
  const TEXT_BYTES = 10_000;
@@ -318,8 +318,8 @@ function assembleRunCard(state, options, elements) {
318
318
  schema: '2.0',
319
319
  config: { wide_screen_mode: true, update_multi: true },
320
320
  header: {
321
- template: headerTemplate2(state),
322
- title: { content: headerTitle2(state, options), tag: 'plain_text' },
321
+ template: headerTemplate(state),
322
+ title: { content: headerTitle(state, options), tag: 'plain_text' },
323
323
  },
324
324
  body: { elements },
325
325
  };
@@ -762,7 +762,7 @@ function buildSummaryContent(state, options = {}) {
762
762
  // 压缩(operationKind='compaction')不会返回 agent 正文——引擎只压缩上下文
763
763
  // 窗口,空内容属正常,不能沿用普通 run 的「(未返回内容)」异常信号
764
764
  // (2026-09 用户反馈)。所有会压缩的 agent(codex/claude/kimi/opencode/pi)
765
- // 都经 streamCodexCompact 走这条共享渲染路径,一处覆盖全部。
765
+ // 都经 streamCompact 走这条共享渲染路径,一处覆盖全部。
766
766
  const empty = hasContent
767
767
  ? ''
768
768
  : state.operationKind === 'compaction'
@@ -802,9 +802,9 @@ function buildDurationInfo(state) {
802
802
  // 与标题一致:审批等待期间状态行显示「等待审批中」,而不是「工具调用中」。
803
803
  if (hasPendingApproval(state))
804
804
  return '✋ 等待审批中';
805
- return footerText2(state.footer);
805
+ return footerText(state.footer);
806
806
  }
807
- function footerText2(footer) {
807
+ function footerText(footer) {
808
808
  if (footer === 'tool_running')
809
809
  return '🧰 工具调用中';
810
810
  if (footer === 'streaming')
@@ -816,7 +816,7 @@ function statusTagLabel(terminal) {
816
816
  return terminalToLabel(terminal);
817
817
  }
818
818
  /** CardKit 2.0 header title — agent-aware via options.agentKind. */
819
- function headerTitle2(state, options = {}) {
819
+ function headerTitle(state, options = {}) {
820
820
  const agent = agentDisplayName(options.agentKind ?? 'claude');
821
821
  if (state.terminal === 'done')
822
822
  return `✅ ${agent} · 已完成`;
@@ -845,7 +845,7 @@ function hasPendingApproval(state) {
845
845
  return (state.approvals ?? []).some((a) => !a.expired);
846
846
  }
847
847
  /** CardKit 2.0 header template color */
848
- function headerTemplate2(state) {
848
+ function headerTemplate(state) {
849
849
  return terminalToColor(state.terminal);
850
850
  }
851
851
  //# sourceMappingURL=run-renderer.js.map
@@ -388,31 +388,67 @@ function reduceAssistantEvent(state, event) {
388
388
  // string as the old per-render parse, so truncation-broken JSON still
389
389
  // yields null (over-cap input renders no summary — behavior preserved).
390
390
  const inputStr = truncateDetail(stringifyUnknown(content.input));
391
- const capped = [
392
- ...markThinkingInactive(next.blocks),
393
- {
394
- kind: 'tool',
395
- tool: {
396
- id: content.id,
397
- name: content.name,
398
- input: inputStr,
399
- parsedInput: tryParseRecord(inputStr),
400
- ...(content.summary !== undefined ? { summary: content.summary } : {}),
401
- status: 'running',
402
- startedAt: event.timestamp,
403
- },
404
- },
405
- ];
406
- next = {
407
- ...next,
408
- blocks: keepLatestBlocks(capped),
409
- omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
410
- footer: 'tool_running',
391
+ const incomingTool = {
392
+ id: content.id,
393
+ name: content.name,
394
+ input: inputStr,
395
+ parsedInput: tryParseRecord(inputStr),
396
+ ...(content.summary !== undefined ? { summary: content.summary } : {}),
397
+ status: 'running',
398
+ startedAt: event.timestamp,
411
399
  };
400
+ const blocks = markThinkingInactive(next.blocks);
401
+ // A tool_use patch (same id) enriches an existing tool block in place:
402
+ // opencode emits the initial tool_call while args are still streaming,
403
+ // then re-reports the command/path in later tool_call_updates. Merging
404
+ // keeps one panel per tool call; a first-seen id still appends.
405
+ const existingIndex = blocks.findIndex((block) => block.kind === 'tool' && block.tool.id === content.id);
406
+ if (existingIndex >= 0) {
407
+ const existing = blocks[existingIndex];
408
+ const merged = blocks.slice();
409
+ merged[existingIndex] = {
410
+ kind: 'tool',
411
+ tool: mergeToolUse(existing.tool, incomingTool),
412
+ };
413
+ next = { ...next, blocks: keepLatestBlocks(merged), footer: 'tool_running' };
414
+ }
415
+ else {
416
+ const capped = [...blocks, { kind: 'tool', tool: incomingTool }];
417
+ next = {
418
+ ...next,
419
+ blocks: keepLatestBlocks(capped),
420
+ omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
421
+ footer: 'tool_running',
422
+ };
423
+ }
412
424
  }
413
425
  }
414
426
  return next;
415
427
  }
428
+ /**
429
+ * Merge a same-id tool_use patch into the existing tool block. Only fields the
430
+ * patch can legitimately enrich are overwritten:
431
+ * - `input`/`parsedInput` — replaced only when the patch carries a non-empty
432
+ * object (an empty `{}` patch must not erase a command already rendered);
433
+ * - `summary` — replaced only when the patch supplies one;
434
+ * - `name` — kept from first sight (a completed opencode update has no kind,
435
+ * so its patch cannot know the mapped name).
436
+ * Everything else (`status`, `output`, `completedAt`, `toolHint`) is preserved,
437
+ * so an out-of-order patch after the tool_result cannot reset status to running.
438
+ */
439
+ function mergeToolUse(existing, incoming) {
440
+ const incomingHasInput = incoming.parsedInput !== undefined &&
441
+ incoming.parsedInput !== null &&
442
+ Object.keys(incoming.parsedInput).length > 0;
443
+ return {
444
+ ...existing,
445
+ input: incomingHasInput ? incoming.input : existing.input,
446
+ parsedInput: incomingHasInput ? incoming.parsedInput : existing.parsedInput,
447
+ name: existing.name || incoming.name,
448
+ ...(incoming.summary !== undefined ? { summary: incoming.summary } : {}),
449
+ startedAt: existing.startedAt ?? incoming.startedAt,
450
+ };
451
+ }
416
452
  /** Handle tool_result content blocks (user message with tool results).
417
453
  * P3-4: rebuild ONLY the block matching `tool_use_id`, copying every other
418
454
  * block by reference. The old `.map()` recreated all block objects per
@@ -589,26 +625,40 @@ function tryParseRecord(input) {
589
625
  function keepLatestBlocks(blocks) {
590
626
  return blocks.length <= MAX_BLOCKS ? blocks : blocks.slice(-MAX_BLOCKS);
591
627
  }
628
+ /** 截断标记前缀(`keepTailMarked` 产出,含已丢弃正文计数)。 */
629
+ const TRUNCATION_MARKER_RE = /^…(前 (\d+) 字符已省略)\n/;
592
630
  /**
593
631
  * keepTail + 截断标记:被去头的存储内容以前缀如实标注(信息保真红线:禁止静默丢弃)。
594
632
  * 标记计入 maxChars 预算(存储总量仍 ≤ maxChars,保持既有长度不变量)。
633
+ *
634
+ * 计数口径(2026-10 修正):只统计**真实丢弃的正文**,不含标记自身开销。
635
+ * 关键点是增量流下 `value` 往往已带上一次的标记(`marker + kept`);若直接
636
+ * 用 `value.length - kept.length` 计费,会把上一帧的标记文本也算进「已省略」,
637
+ * 造成每帧显示十几字符的虚高。这里先剥离旧标记,把 N 定义为**从该块起始累计
638
+ * 丢弃的正文数**(单调不减),再按「marker 计入预算」的不动点求最小 N。
595
639
  */
596
640
  function keepTailMarked(value, maxChars) {
597
- if (value.length <= maxChars)
641
+ const prior = TRUNCATION_MARKER_RE.exec(value);
642
+ const priorDropped = prior ? Number(prior[1]) : 0;
643
+ const body = prior ? value.slice(prior[0].length) : value;
644
+ // 从未截断且当前正文未超预算:原样返回(不误标)。
645
+ if (priorDropped === 0 && body.length <= maxChars)
598
646
  return value;
599
- // marker 计入 maxChars 预算(总量不变量)。省略数 = value.length - kept.length,
600
- // 而 kept 长度取决于 marker 长度、marker 长度又取决于省略数的位数——
601
- // 不动点迭代(marker 固定部分 12 字符,位数至多几次即收敛)。
602
- let dropped = value.length - maxChars + 12;
647
+ // 求最小累计丢弃数 N(≥ priorDropped),使 marker(N) + 保留正文 ≤ maxChars:
648
+ // body.length - (N - priorDropped) + (12 + digits(N)) ≤ maxChars
649
+ // ⟺ N ≥ priorDropped + body.length - maxChars + 12 + digits(N)
650
+ // 即 N = K + digits(N) 的不动点(K 为固定项,位数至多几次即收敛)。
651
+ const K = priorDropped + body.length - maxChars + 12;
652
+ let totalDropped = Math.max(priorDropped, K);
603
653
  for (;;) {
604
- const next = value.length - maxChars + 12 + String(dropped).length;
605
- if (next === dropped)
654
+ const next = Math.max(priorDropped, K + String(totalDropped).length);
655
+ if (next === totalDropped)
606
656
  break;
607
- dropped = next;
657
+ totalDropped = next;
608
658
  }
609
- const marker = `…(前 ${dropped} 字符已省略)\n`;
610
- const kept = keepTail(value, Math.max(0, maxChars - marker.length));
611
- return marker + kept;
659
+ const newlyDropped = totalDropped - priorDropped;
660
+ const kept = body.slice(newlyDropped);
661
+ return `…(前 ${totalDropped} 字符已省略)\n${kept}`;
612
662
  }
613
663
  function truncateDetail(value) {
614
664
  return value.length <= MAX_TOOL_DETAIL_CHARS
@@ -39,9 +39,13 @@ export function toolHeaderText(tool) {
39
39
  const blockSummary = tool.summary
40
40
  ? truncate(tool.summary.replace(/\s+/g, ' ').trim(), HEADER_SUMMARY_MAX, { suffix: '' })
41
41
  : '';
42
+ // opencode carries the same value in both slots (title = command/path, and
43
+ // the args-derived summary resolves to it too); dedupe so the header reads
44
+ // `✅ **Bash** — ls` instead of `✅ **Bash** — ls — ls`.
42
45
  const parts = [summary, blockSummary].filter(Boolean);
43
- return parts.length > 0
44
- ? `${icon} **${tool.name}** — ${parts.join(' — ')}`
46
+ const uniqueParts = parts.filter((part, i) => parts.indexOf(part) === i);
47
+ return uniqueParts.length > 0
48
+ ? `${icon} **${tool.name}** — ${uniqueParts.join(' — ')}`
45
49
  : `${icon} **${tool.name}**`;
46
50
  }
47
51
  /**
package/dist/cli.js CHANGED
@@ -2,9 +2,8 @@
2
2
  /**
3
3
  * CLI 引导层(bootstrap):在 import 应用模块图之前决定运行时(bun/node)并拉起它。
4
4
  *
5
- * **这是全仓唯一被认可的 spawn 收口例外**(即 AGENTS.md「子进程 spawn 只走
6
- * platform/spawn.ts」红线的显式豁免)。三个理由缺一不可:
7
- * 1. 时序:preflight(Node 版本守卫、-v/-h/--advance-help)必须在 import 应用
5
+ *XX * 缺一不可:
6
+ * 1. 时序:preflight(Node 版本守卫、-v/-h/--advanced-help)必须在 import 应用
8
7
  * 模块图之前跑完——dist/cli.js 刻意不使用 ES2020+ 语法以便在 node 12 上仍
9
8
  * 可被解析,而 dist/index.js 是 ES2022;先 import platform/spawn 就等于先把
10
9
  * 应用模块图拖进来,守卫当场失效(报错堆栈全落在 Node 内部)。
@@ -20,7 +19,7 @@ import fs from 'node:fs';
20
19
  import path from 'node:path';
21
20
  import { spawn, spawnSync } from 'node:child_process';
22
21
  import { fileURLToPath, pathToFileURL } from 'node:url';
23
- import { printAdvanceHelp, printHelp, printVersion } from './config/dir.js';
22
+ import { printAdvancedHelp, printHelp, printVersion } from './config/dir.js';
24
23
  const entryUrl = new URL('./index.js', import.meta.url);
25
24
  const entry = fileURLToPath(entryUrl);
26
25
  /**
@@ -49,8 +48,8 @@ export function handlePreflight(args, nodeVersion = process.versions.node) {
49
48
  printHelp();
50
49
  return 0;
51
50
  }
52
- if (arg === '--advance-help') {
53
- printAdvanceHelp();
51
+ if (arg === '--advanced-help') {
52
+ printAdvancedHelp();
54
53
  return 0;
55
54
  }
56
55
  }
package/dist/clone.d.ts CHANGED
@@ -59,11 +59,6 @@ type RegisterAppFn = (options: {
59
59
  status: string;
60
60
  }) => void;
61
61
  }) => Promise<RegisterAppResult>;
62
- /**
63
- * 默认新实例拉起:同入口 + 同参数,仅替换 --config-dir(detached,返回 pid)。
64
- * spawn 骨架(log fd / windowsHide / error 兜底)单源于 restart.ts。
65
- */
66
- export declare function spawnDetachedCloneBridge(targetDir: string): number | undefined;
67
62
  export interface CloneSessionOptions {
68
63
  connector: CloneConnector;
69
64
  /** 当前实例的 config.yaml 路径(配置复制源)。 */
@@ -111,7 +106,7 @@ export declare class CloneSession {
111
106
  /**
112
107
  * 扫码成功后的自动绑定落地:
113
108
  * 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
114
- * 2. 同步 workspace / 常用指令 / 会话状态(session 按 openId 重绑);
109
+ * 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
115
110
  * 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
116
111
  * 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
117
112
  * 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
package/dist/clone.js CHANGED
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import YAML from 'yaml';
7
7
  import { getLogger } from './logger/index.js';
8
8
  import { renderQrImage } from './config/qr.js';
9
- import { silentlyUnlink } from './common/fs.js';
9
+ import { bestEffortUnlink } from './common/fs.js';
10
10
  import { atomicWrite, atomicWriteJson } from './persistence/atomic-write.js';
11
11
  import { spawnDetachedBridge } from './restart.js';
12
12
  /** 退出命令(不区分大小写)。 */
@@ -48,7 +48,7 @@ export function generateCloneSuffix() {
48
48
  * 默认新实例拉起:同入口 + 同参数,仅替换 --config-dir(detached,返回 pid)。
49
49
  * spawn 骨架(log fd / windowsHide / error 兜底)单源于 restart.ts。
50
50
  */
51
- export function spawnDetachedCloneBridge(targetDir) {
51
+ function spawnDetachedCloneBridge(targetDir) {
52
52
  const args = [];
53
53
  const argv = process.argv.slice(2);
54
54
  for (let i = 0; i < argv.length; i++) {
@@ -238,7 +238,7 @@ export class CloneSession {
238
238
  return;
239
239
  }
240
240
  finally {
241
- silentlyUnlink(qrPath);
241
+ bestEffortUnlink(qrPath);
242
242
  }
243
243
  await this.connector
244
244
  .sendWithRetry(ctx.chatId, {
@@ -264,7 +264,7 @@ export class CloneSession {
264
264
  /**
265
265
  * 扫码成功后的自动绑定落地:
266
266
  * 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
267
- * 2. 同步 workspace / 常用指令 / 会话状态(session 按 openId 重绑);
267
+ * 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
268
268
  * 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
269
269
  * 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
270
270
  * 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
@@ -0,0 +1,2 @@
1
+ /** Clamp `v` into [min, max]. */
2
+ export declare function clampInt(v: number, min: number, max: number): number;
@@ -0,0 +1,5 @@
1
+ /** Clamp `v` into [min, max]. */
2
+ export function clampInt(v, min, max) {
3
+ return Math.min(Math.max(v, min), max);
4
+ }
5
+ //# sourceMappingURL=clamp.js.map
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Unlink a file, silently ignoring ENOENT and other errors.
2
+ * Unlink a file, swallowing errors instead of throwing to the caller.
3
3
  *
4
4
  * Use for cleanup paths (pid files, temp files, stale locks) where the
5
5
  * file may already be gone or was never created — a missing file is not
6
- * an error worth propagating.
6
+ * an error worth propagating. Not truly silent: gives up on busy files
7
+ * with a warning log (see below).
7
8
  */
8
- export declare function silentlyUnlink(path: string): void;
9
+ export declare function bestEffortUnlink(path: string): void;
9
10
  export declare function isBusyError(err: unknown): boolean;
10
11
  export interface BusyRetryOptions {
11
12
  /** 平台注入(测试用);默认当前宿主。占用重试仅 win32 生效 */
package/dist/common/fs.js CHANGED
@@ -2,20 +2,21 @@ import fs from 'node:fs';
2
2
  import { getLogger } from '../logger/index.js';
3
3
  import { currentPlatform, isWin32 } from '../platform/select.js';
4
4
  /**
5
- * Unlink a file, silently ignoring ENOENT and other errors.
5
+ * Unlink a file, swallowing errors instead of throwing to the caller.
6
6
  *
7
7
  * Use for cleanup paths (pid files, temp files, stale locks) where the
8
8
  * file may already be gone or was never created — a missing file is not
9
- * an error worth propagating.
9
+ * an error worth propagating. Not truly silent: gives up on busy files
10
+ * with a warning log (see below).
10
11
  */
11
- export function silentlyUnlink(path) {
12
+ export function bestEffortUnlink(path) {
12
13
  try {
13
14
  withBusyRetry(() => fs.unlinkSync(path));
14
15
  }
15
16
  catch (err) {
16
17
  // 占用放弃(win32 重试耗尽 / posix 首次占用即失败):按设计「告警,
17
18
  // 不静默吞」,但清理路径不向调用方抛错(保持本函数契约)
18
- getLogger().warn(`[fs] silentlyUnlink gave up on busy file ${path}: ${String(err)}`);
19
+ getLogger().warn(`[fs] bestEffortUnlink gave up on busy file ${path}: ${String(err)}`);
19
20
  }
20
21
  }
21
22
  /**
@@ -5,7 +5,7 @@ interface CliArgs {
5
5
  help?: boolean;
6
6
  version?: boolean;
7
7
  update?: boolean;
8
- advanceHelp?: boolean;
8
+ advancedHelp?: boolean;
9
9
  }
10
10
  export declare function resolveConfigDir(configDirArg: string | undefined): string;
11
11
  export declare function getConfigDir(): string;
@@ -18,11 +18,10 @@ export declare function printVersion(): void;
18
18
  /** Print CLI help to stdout. */
19
19
  export declare function printHelp(): void;
20
20
  /**
21
- * Print advanced-config reference to stdout (CLI `--advance-help`).
21
+ * Print advanced-config reference to stdout (CLI `--advanced-help`).
22
22
  *
23
23
  * 目标读者是 coding agent(用户让 agent 代改 config.yaml):披露所有
24
- * /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。飞书侧
25
- * 等价命令为 /advancehelp(内容同源,卡片渲染)。
24
+ * /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。
26
25
  */
27
- export declare function printAdvanceHelp(): void;
26
+ export declare function printAdvancedHelp(): void;
28
27
  export {};
@@ -52,8 +52,8 @@ export function parseCliArgs(args = process.argv.slice(2)) {
52
52
  else if (arg === '--dev') {
53
53
  result.dev = true;
54
54
  }
55
- else if (arg === '--advance-help') {
56
- result.advanceHelp = true;
55
+ else if (arg === '--advanced-help') {
56
+ result.advancedHelp = true;
57
57
  }
58
58
  else if (arg === '--update' || arg === 'update') {
59
59
  // 裸子命令等价形式:`lark-remote update` 与 `--update` 同义。
@@ -94,24 +94,23 @@ export function printHelp() {
94
94
  ' --settings <path> 指定 Claude 配置文件路径',
95
95
  ' --dev 开发模式:标记从源码 bun src/index.ts 启动(看门狗据此选择拉起方式)',
96
96
  ' --update 升级到最新版本后退出(用于 cron/脚本自动化升级)',
97
- ' --advance-help 高级配置参考:config.yaml 全部高级字段的语义与改法(面向 coding agent)',
97
+ ' --advanced-help 高级配置参考:config.yaml 全部高级字段的语义与改法(面向 coding agent)',
98
98
  ' -h, --help 显示本帮助信息',
99
99
  ' -v, --version 显示版本号',
100
100
  ];
101
101
  process.stdout.write(lines.join('\n') + '\n');
102
102
  }
103
103
  /**
104
- * Print advanced-config reference to stdout (CLI `--advance-help`).
104
+ * Print advanced-config reference to stdout (CLI `--advanced-help`).
105
105
  *
106
106
  * 目标读者是 coding agent(用户让 agent 代改 config.yaml):披露所有
107
- * /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。飞书侧
108
- * 等价命令为 /advancehelp(内容同源,卡片渲染)。
107
+ * /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。
109
108
  */
110
- export function printAdvanceHelp() {
109
+ export function printAdvancedHelp() {
111
110
  const configDir = resolveConfigDir(parseCliArgs().configDir);
112
111
  const configPath = configDir + '/config.yaml';
113
112
  const lines = [
114
- 'lark-remote 高级配置参考(--advance-help)',
113
+ 'lark-remote 高级配置参考(--advanced-help)',
115
114
  '',
116
115
  '目标读者:coding agent。用户把本输出交给 agent,agent 据此代改配置。',
117
116
  '',
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import YAML from 'yaml';
5
5
  import { AppConfigSchema } from './index.js';
6
6
  import { needsQrImage, renderQrImage, renderTerminalQr } from './qr.js';
7
- import { silentlyUnlink } from '../common/fs.js';
7
+ import { bestEffortUnlink } from '../common/fs.js';
8
8
  import { atomicWrite } from '../persistence/atomic-write.js';
9
9
  /** Where the fallback QR image is written (next to the config it will seed). */
10
10
  const QR_IMAGE_NAME = 'qr-code.gif';
@@ -77,7 +77,7 @@ async function runRegistrationWizard(configPath) {
77
77
  }
78
78
  finally {
79
79
  if (wroteImage)
80
- silentlyUnlink(imagePath);
80
+ bestEffortUnlink(imagePath);
81
81
  }
82
82
  }
83
83
  function isInteractiveTerminal() {
@@ -107,8 +107,6 @@ type CardActionHandler = (action: CardActionEvent) => void | CardActionResponse
107
107
  * 卡片 patch 的重试上限。含义:1 次原始尝试 + 最多 PATCH_MAX_RETRIES 次重试。
108
108
  */
109
109
  export declare const PATCH_MAX_RETRIES = 3;
110
- /** 重试退避基数(ms);第 n 次重试前等待 n × 基数,给对端恢复的时间。 */
111
- export declare const PATCH_RETRY_BASE_DELAY_MS = 150;
112
110
  export declare class FeishuConnector {
113
111
  /** 飞书 channel 实例(public:测试直接访问/注入 mock,替代 as unknown as)。 */
114
112
  channel: LarkChannel;
@@ -7,7 +7,7 @@ import { sleep } from '../common/sleep.js';
7
7
  import { isTransientTransportError } from '../error-classification.js';
8
8
  import axios from 'axios';
9
9
  import fs from 'node:fs';
10
- import { silentlyUnlink } from '../common/fs.js';
10
+ import { bestEffortUnlink } from '../common/fs.js';
11
11
  import { displayName } from '../platform/path.js';
12
12
  import https from 'node:https';
13
13
  import os from 'node:os';
@@ -145,7 +145,7 @@ function shouldRetrySendError(err) {
145
145
  */
146
146
  export const PATCH_MAX_RETRIES = 3;
147
147
  /** 重试退避基数(ms);第 n 次重试前等待 n × 基数,给对端恢复的时间。 */
148
- export const PATCH_RETRY_BASE_DELAY_MS = 150;
148
+ const PATCH_RETRY_BASE_DELAY_MS = 150;
149
149
  /** 记日志用的错误摘要(不打印 axios 巨型循环对象)。 */
150
150
  function formatErrorForLog(err) {
151
151
  if (err instanceof Error) {
@@ -403,9 +403,9 @@ export class FeishuConnector {
403
403
  for (const res of msg.resources) {
404
404
  const tmpPath = path.join(os.tmpdir(), `lark-remote-inbound-${randomUUID()}`);
405
405
  try {
406
- const { contentType, bytesWritten } = await withTimeout(this.channel.downloadResourceToFile(msg.messageId, res.fileKey, res.type, tmpPath), timeoutMs, `downloadResource fileKey=${res.fileKey}`, () => silentlyUnlink(tmpPath));
406
+ const { contentType, bytesWritten } = await withTimeout(this.channel.downloadResourceToFile(msg.messageId, res.fileKey, res.type, tmpPath), timeoutMs, `downloadResource fileKey=${res.fileKey}`, () => bestEffortUnlink(tmpPath));
407
407
  if (bytesWritten > maxBytes) {
408
- silentlyUnlink(tmpPath);
408
+ bestEffortUnlink(tmpPath);
409
409
  const limitMb = maxBytes / (1024 * 1024);
410
410
  failures.push({
411
411
  kind: res.kind,
@@ -429,7 +429,7 @@ export class FeishuConnector {
429
429
  catch (err) {
430
430
  // 超时不在这里删文件:上面已把清理挂到底层传输的 settle 上。
431
431
  if (!(err instanceof ResourceTimeoutError))
432
- silentlyUnlink(tmpPath);
432
+ bestEffortUnlink(tmpPath);
433
433
  getLogger().warn(`[feishu] downloadResource failed fileKey=${res.fileKey} type=${res.type}:`, err.message);
434
434
  failures.push({
435
435
  kind: res.kind,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 入站回执文案(设计依据见 `docs/zh/architecture/inbound-unified-input-design.md`)。
2
+ * 入站回执文案(`inbound-unified-input-design.md` §5.7)。
3
3
  *
4
4
  * 两类回执合并成一条:
5
5
  * 1. 无文本纯附件(决策 2):不自动起 turn,只回已保存路径 + 引导语;
@@ -10,8 +10,6 @@
10
10
  */
11
11
  import type { PlaceholderKind } from './placeholder.js';
12
12
  import type { RejectedItem } from './turn.js';
13
- /** 单条回执最多列出的文件数;超出折叠为 "… 等 N 个文件"(沿用旧提示口径)。 */
14
- export declare const MAX_PATHS_IN_RECEIPT = 10;
15
13
  /** 「不支持」回执句式(§5.7 表:不支持类型统一「暂不支持 X」)。 */
16
14
  export declare function unsupportedReason(kind: PlaceholderKind): string;
17
15
  export interface ReceiptInput {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 入站回执文案(设计依据见 `docs/zh/architecture/inbound-unified-input-design.md`)。
2
+ * 入站回执文案(`inbound-unified-input-design.md` §5.7)。
3
3
  *
4
4
  * 两类回执合并成一条:
5
5
  * 1. 无文本纯附件(决策 2):不自动起 turn,只回已保存路径 + 引导语;
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import path from 'node:path';
12
12
  /** 单条回执最多列出的文件数;超出折叠为 "… 等 N 个文件"(沿用旧提示口径)。 */
13
- export const MAX_PATHS_IN_RECEIPT = 10;
13
+ const MAX_PATHS_IN_RECEIPT = 10;
14
14
  /** 占位符种类 → 面向人的名称。 */
15
15
  const PLACEHOLDER_LABELS = {
16
16
  image: '图片',
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * InboundTurnAssembler:统一静默期窗口 + 合并 + 下载等待 + commit。
3
3
  *
4
- * 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4/§5.2。
4
+ * 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4、§5.2。
5
5
  *
6
6
  * 规则:
7
7
  * - 任何事件到达 → **重置** 700ms 定时器(按 `userId:chatId` 分桶),保证图/文任意顺序等价;
@@ -13,8 +13,6 @@
13
13
  */
14
14
  import type { InboundEvent } from './event.js';
15
15
  import { type CommitReason, type InboundTurn } from './turn.js';
16
- /** 统一静默期窗口(ms)——决策 1:不做「纯文本零延迟」快路径。 */
17
- export declare const DEFAULT_TURN_WINDOW_MS = 700;
18
16
  export interface AssemblerContext {
19
17
  userId: string;
20
18
  chatId: string;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * InboundTurnAssembler:统一静默期窗口 + 合并 + 下载等待 + commit。
3
3
  *
4
- * 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4/§5.2。
4
+ * 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4、§5.2。
5
5
  *
6
6
  * 规则:
7
7
  * - 任何事件到达 → **重置** 700ms 定时器(按 `userId:chatId` 分桶),保证图/文任意顺序等价;
@@ -17,7 +17,7 @@ import { buildPrompt } from './prompt.js';
17
17
  import { buildReceipt, unsupportedReason } from './receipt.js';
18
18
  import { bucketKey, } from './turn.js';
19
19
  /** 统一静默期窗口(ms)——决策 1:不做「纯文本零延迟」快路径。 */
20
- export const DEFAULT_TURN_WINDOW_MS = 700;
20
+ const DEFAULT_TURN_WINDOW_MS = 700;
21
21
  export class InboundTurnAssembler {
22
22
  deps;
23
23
  buckets = new Map();