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
@@ -10,11 +10,14 @@ import { resolveAlias } from '../order/alias-resolve.js';
10
10
  import { diffConfig, setNestedValue } from './config-diff.js';
11
11
  import { getLogger } from '../logger/index.js';
12
12
  import { stripPlaceholders } from '../inbound/placeholder.js';
13
- import { activeRunUsage, clampInt } from './utils.js';
13
+ import { clampInt } from '../common/clamp.js';
14
+ import { activeRunUsage } from './usage-format.js';
14
15
  import { markdownDiv, buildSessionHistoryCard, paginationBar, searchBar, parseJumpOffset, SEARCH_INPUT_NAME, PAGE_JUMP_INVALID_HINT, } from './card-helpers.js';
15
16
  import { MAX_FILE_UPLOAD_SIZE } from '../connector/file-limits.js';
17
+ import { newSessionButton, resumeCompactButton, agentDisplayName } from '../card/card-shared.js';
18
+ import { displayName } from '../platform/path.js';
16
19
  import { atomicWrite } from '../persistence/atomic-write.js';
17
- import { checkLatestVersion as defaultCheckLatestVersion, isNewer as defaultIsNewer, runInstallLatest as defaultRunInstallLatest, } from '../update/index.js';
20
+ import { checkLatestVersion as defaultCheckLatestVersion, compareVersions as defaultCompareVersions, runInstallLatest as defaultRunInstallLatest, } from '../update/index.js';
18
21
  /** Config card builder - delegates to per-agent builders */
19
22
  import { getConfigBuilder, listRegisteredAgents, sortAgentsForDisplay } from './config/index.js';
20
23
  import { probeAllAgents, getCachedAvailability } from '../runner/probe.js';
@@ -128,8 +131,6 @@ const RESUME_CONTENT_PREFETCH = 5;
128
131
  * `New Session`、空串),都不是真实输入,不得渲染成行标题。
129
132
  */
130
133
  const RESUME_SUMMARY_PLACEHOLDERS = new Set(['', '(no user message)', '(无摘要)', 'New Session']);
131
- import { newSessionButton, resumeCompactButton, agentDisplayName } from '../card/card-shared.js';
132
- import { displayName } from '../platform/path.js';
133
134
  /**
134
135
  * Card-action commands that must run with enqueue({ immediate: true }).
135
136
  * These bypass the serial queue and execute immediately.
@@ -145,7 +146,7 @@ import { displayName } from '../platform/path.js';
145
146
  * stop — interrupt current run
146
147
  * ls.file — send file only
147
148
  * ws.page — paginate /ws list only
148
- * ws.remove — delete workspace alias only
149
+ * ws.remove — delete cwd alias only
149
150
  * resume.use — set sessionId + agent for correct reader routing
150
151
  * help.* — read-only help commands
151
152
  * order.delete — delete order only
@@ -320,6 +321,24 @@ const IMMEDIATE_ACTION_CMDS = new Set([
320
321
  // coordinator 已删响应空转(线上复现:approval.respond 排队卡、审批永不生效)。
321
322
  ...APPROVAL_ACTION_CMDS,
322
323
  ]);
324
+ /**
325
+ * 会话 / cwd / agent 变更类卡片动作(单源)。这些动作会改写 sessionStore 的
326
+ * sessionId / cwd / defaultAgent,必须与入站装配器的 700ms 静默窗口定序:
327
+ * 控制层(index.ts)在执行它们之前先 flush 该 user/chat 的装配窗口,保证窗口内
328
+ * 先到的文本消息在本动作之前 commit(绑定动作前的旧状态)。否则窗口内消息会
329
+ * 漂移到动作之后的新状态(2026-10-07 事故:输入「重新构建」后立即点「新会话」,
330
+ * 消息落到新会话)。
331
+ *
332
+ * 文本命令(/new、/cd、/resume、/config)已由 index.ts 命令分支先 flush,
333
+ * 不在此列——只有卡片动作绕过了该分支。
334
+ */
335
+ export const SESSION_MUTATING_ACTION_CMDS = new Set([
336
+ 'new-session',
337
+ 'resume.use',
338
+ 'ls.switch',
339
+ 'ws.use',
340
+ 'config.save',
341
+ ]);
323
342
  /** W2.8 单源:payload.offset → 钳位 offset(原先 9 处逐字副本)。 */
324
343
  function payloadOffset(value) {
325
344
  return Math.max(0, Math.trunc(Number(value.offset) || 0));
@@ -426,7 +445,7 @@ export class CommandRouter {
426
445
  this.cloneSession = opts.cloneSession;
427
446
  this.updateFns = {
428
447
  checkLatestVersion: opts.updateFns?.checkLatestVersion ?? defaultCheckLatestVersion,
429
- isNewer: opts.updateFns?.isNewer ?? defaultIsNewer,
448
+ compareVersions: opts.updateFns?.compareVersions ?? defaultCompareVersions,
430
449
  runInstallLatest: opts.updateFns?.runInstallLatest ?? defaultRunInstallLatest,
431
450
  };
432
451
  }
@@ -447,7 +466,7 @@ export class CommandRouter {
447
466
  /**
448
467
  * Route a message: if it starts with /, handle as command; if it starts with !, execute as bash; otherwise forward to Claude.
449
468
  *
450
- * 命令识别前置条件(2026-09-15 P0 修复):
469
+ * 命令识别前置条件(2026-09-15 P0 修复,`inbound-unified-input-design.md` §5.6):
451
470
  * 裸前缀判定会被结构占位符命中 —— SDK 把富文本里的图片渲染成 `![image](img_v3_…)`,
452
471
  * 首字符恰好是 `!`,整段用户消息被当 shell 命令执行(`executeBash "[image](img_v3_…"`)。
453
472
  * 因此命令前缀只在「调用方声明纯文本 + 剥离占位符后非空且无占位符 + 首字符是 / 或 !」
@@ -494,7 +513,7 @@ export class CommandRouter {
494
513
  await this.bridge.executeBash(cmd, ctx);
495
514
  return null;
496
515
  }
497
- // Forward to Claude (P1-14: pass the enqueue-time workspace through so the
516
+ // Forward to Claude (P1-14: pass the enqueue-time cwd through so the
498
517
  // run uses the same cwd as the serial queue lane, even if /cd ran while
499
518
  // the message was queued)
500
519
  // 剥离占位符后再转发:agent 只该看到用户文本(附图已转成 attachments 块,
@@ -635,8 +654,8 @@ export class CommandRouter {
635
654
  // 调 updateCardInPlace → Feishu API 乱序到达导致 toggle 卡死。
636
655
  // 2026-07-18: 返回 enqueueConfigAction 的结果以支持 toast 响应
637
656
  return this.enqueueConfigAction(value, ctx);
638
- case 'codex.compact':
639
- await this.bridge.handleCodexCompact(value, ctx);
657
+ case 'compact':
658
+ await this.bridge.handleCompactAction(value, ctx);
640
659
  return;
641
660
  case 'resume.compact':
642
661
  await this.bridge.handleResumeCompact(value, ctx);
@@ -672,13 +691,13 @@ export class CommandRouter {
672
691
  * Routes to the appropriate bridge method based on value.cmd.
673
692
  * Returns a toast response for immediate user feedback.
674
693
  */
675
- /** queue.* 卡片动作通用守卫:workspace/messageId 缺失时回复错误并返回 null。 */
694
+ /** queue.* 卡片动作通用守卫:cwd/messageId 缺失时回复错误并返回 null。 */
676
695
  async queuePayloadOrReply(value, ctx) {
677
- if (!value.workspace || !value.messageId) {
696
+ if (!value.cwd || !value.messageId) {
678
697
  await this.bridge.sendResult({ text: CARD_PAYLOAD_MISSING }, ctx);
679
698
  return null;
680
699
  }
681
- return { workspace: value.workspace, messageId: value.messageId };
700
+ return { cwd: value.cwd, messageId: value.messageId };
682
701
  }
683
702
  /** 答案类操作的统一失败反馈:重复投递(同一 nonce 第二次点击)是已生效的
684
703
  * 中性事件,报 error 会误导(首次点击实际已成功)。 */
@@ -712,11 +731,11 @@ export class CommandRouter {
712
731
  const payload = await this.queuePayloadOrReply(value, ctx);
713
732
  if (!payload)
714
733
  return;
715
- const { workspace, messageId } = payload;
716
- const removed = this.bridge.removeFromQueue(workspace, messageId);
734
+ const { cwd, messageId } = payload;
735
+ const removed = this.bridge.removeFromQueue(cwd, messageId);
717
736
  if (removed) {
718
737
  // Update the queue card in-place to "cancelled" state
719
- await this.bridge.updateQueueCardToCancelled(workspace, messageId);
738
+ await this.bridge.updateQueueCardToCancelled(cwd, messageId);
720
739
  // Send a brief confirmation (user can see the card has been updated)
721
740
  await this.bridge.sendResult({ text: '✅ 已从队列中撤销' }, ctx);
722
741
  }
@@ -732,11 +751,11 @@ export class CommandRouter {
732
751
  const payload = await this.queuePayloadOrReply(value, ctx);
733
752
  if (!payload)
734
753
  return;
735
- const { workspace, messageId } = payload;
754
+ const { cwd, messageId } = payload;
736
755
  // 1. Check target exists BEFORE any await. The queue chain can advance
737
756
  // while later awaits (interruptCurrentRun / markQueueCardExecuting's card
738
757
  // send) are in flight, so the target must be read synchronously at entry.
739
- const targetTask = this.bridge.getQueuedTask(workspace, messageId);
758
+ const targetTask = this.bridge.getQueuedTask(cwd, messageId);
740
759
  if (!targetTask) {
741
760
  // The target already began (or was cancelled) before this handler ran. We
742
761
  // must NOT claim the queue was cleared — nothing is removed in this branch
@@ -760,9 +779,9 @@ export class CommandRouter {
760
779
  const editedContent = targetTask.editedMessage;
761
780
  // D3/Step3: 替换闭包复用原任务的 binding(不重新快照)+ 恢复丢失的
762
781
  // cwdOverride(lane cwd 同源)。否则编辑后重新执行的 run 会丢 cwd 与绑定。
763
- this.bridge.setTaskReplacement(workspace, messageId, async () => {
782
+ this.bridge.setTaskReplacement(cwd, messageId, async () => {
764
783
  await this.handle(editedContent, ctx, {
765
- cwdOverride: workspace,
784
+ cwdOverride: cwd,
766
785
  binding: targetTask.binding,
767
786
  });
768
787
  });
@@ -773,7 +792,7 @@ export class CommandRouter {
773
792
  const stopped = await this.bridge.interruptCurrentRun({
774
793
  userId: ctx.userId,
775
794
  chatId: ctx.chatId,
776
- workspace,
795
+ cwd,
777
796
  });
778
797
  getLogger().info(`[router] queue.immediate: stopped=${stopped}`);
779
798
  // 3. Remove all tasks BEFORE this one (by position in the current queue).
@@ -781,7 +800,7 @@ export class CommandRouter {
781
800
  // advances concurrently): then it is already out of the snapshot and every
782
801
  // remaining queued task is BEHIND it — clearing must stop, not run off the
783
802
  // end and delete tasks queued after the target.
784
- const tasks = this.bridge.getQueuedTasks(workspace);
803
+ const tasks = this.bridge.getQueuedTasks(cwd);
785
804
  const targetIdx = tasks.findIndex((t) => t.messageId === messageId);
786
805
  let removedCount = 0;
787
806
  const removedIds = [];
@@ -792,7 +811,7 @@ export class CommandRouter {
792
811
  // in flight.
793
812
  for (let i = 0; i < targetIdx; i++) {
794
813
  const task = tasks[i];
795
- if (this.bridge.removeFromQueue(workspace, task.messageId)) {
814
+ if (this.bridge.removeFromQueue(cwd, task.messageId)) {
796
815
  removedCount++;
797
816
  removedIds.push(task.messageId);
798
817
  }
@@ -804,7 +823,7 @@ export class CommandRouter {
804
823
  // target are already gone, so any chain advance can only reach the target
805
824
  // itself — the card updates may safely run in the background.
806
825
  for (const removedId of removedIds) {
807
- void this.bridge.updateQueueCardToCancelled(workspace, removedId);
826
+ void this.bridge.updateQueueCardToCancelled(cwd, removedId);
808
827
  }
809
828
  // 4. DO NOT remove the target task - keep it in queue to execute immediately
810
829
  // The queue will naturally execute it after the current task (which we just stopped)
@@ -812,13 +831,13 @@ export class CommandRouter {
812
831
  // re-registering over the same slot). Covers the user editing the message
813
832
  // again while interruptCurrentRun was awaiting; skipped automatically once
814
833
  // the task has begun, because getQueuedTask then returns undefined.
815
- const latestTask = this.bridge.getQueuedTask(workspace, messageId);
834
+ const latestTask = this.bridge.getQueuedTask(cwd, messageId);
816
835
  if (latestTask?.editedMessage) {
817
836
  const editedContent = latestTask.editedMessage;
818
837
  // D3/Step3: 复用原任务 binding + 恢复 cwdOverride(同上)。
819
- this.bridge.setTaskReplacement(workspace, messageId, async () => {
838
+ this.bridge.setTaskReplacement(cwd, messageId, async () => {
820
839
  await this.handle(editedContent, ctx, {
821
- cwdOverride: workspace,
840
+ cwdOverride: cwd,
822
841
  binding: latestTask.binding,
823
842
  });
824
843
  });
@@ -826,9 +845,9 @@ export class CommandRouter {
826
845
  // 4.5/6. The target must only be marked executing (and the toast may only
827
846
  // promise immediate execution) when it is really the next task after the
828
847
  // stop. While interruptCurrentRun was awaiting, a task ahead of the target
829
- // may have begun and now occupy the workspace — the target is still queued
848
+ // may have begun and now occupy the cwd — the target is still queued
830
849
  // but is NOT next, so its card must stay queued and the toast must say so.
831
- const targetStillQueued = this.bridge.getQueuedTask(workspace, messageId) !== undefined;
850
+ const targetStillQueued = this.bridge.getQueuedTask(cwd, messageId) !== undefined;
832
851
  if (!targetStillQueued) {
833
852
  // The target began or was cancelled while earlier awaits were in flight —
834
853
  // a success toast must never promise execution of a task that will not
@@ -847,14 +866,14 @@ export class CommandRouter {
847
866
  }
848
867
  return;
849
868
  }
850
- const workspaceBusy = this.bridge.isBusyFor(workspace);
869
+ const workspaceBusy = this.bridge.isBusyFor(cwd);
851
870
  if (!workspaceBusy) {
852
871
  // Normal path: nothing new began during the stop, the target is next.
853
872
  // Mark its own card as executing now so buttons grey out immediately.
854
873
  // Done BEFORE any removal so getQueuedTask still returns the task with
855
874
  // its (possibly edited) messagePreview for the card. The queue callback's
856
875
  // later updateQueueCardToExecuting call becomes a no-op (idempotent).
857
- await this.bridge.markQueueCardExecuting(workspace, messageId);
876
+ await this.bridge.markQueueCardExecuting(cwd, messageId);
858
877
  const stopPrefix = stopped ? '⚡ 已停止当前任务,' : '';
859
878
  await this.bridge.sendResult({ text: `${stopPrefix}清除了 ${removedCount} 条排队消息。您的消息将立即执行。` }, ctx);
860
879
  }
@@ -871,10 +890,10 @@ export class CommandRouter {
871
890
  const payload = await this.queuePayloadOrReply(value, ctx);
872
891
  if (!payload)
873
892
  return;
874
- const { workspace, messageId } = payload;
893
+ const { cwd, messageId } = payload;
875
894
  const targetUserId = value.userId ?? ctx.userId;
876
- const task = this.bridge.getQueuedTask(workspace, messageId);
877
- const queueInfo = this.bridge.getQueueInfo(workspace);
895
+ const task = this.bridge.getQueuedTask(cwd, messageId);
896
+ const queueInfo = this.bridge.getQueueInfo(cwd);
878
897
  const activeRuns = this.bridge.getAllActiveRuns();
879
898
  // Build diagnostic info
880
899
  const now = new Date().toLocaleString('zh-CN');
@@ -907,7 +926,7 @@ export class CommandRouter {
907
926
  tag: 'div',
908
927
  text: {
909
928
  tag: 'lark_md',
910
- content: `**消息信息**\n- 工作目录: \`${workspace}\`\n- 会话 ID: \`${sessionId ?? '(none)'}\`\n- 消息预览: ${task?.messagePreview ?? '(unknown)'}`,
929
+ content: `**消息信息**\n- 工作目录: \`${cwd}\`\n- 会话 ID: \`${sessionId ?? '(none)'}\`\n- 消息预览: ${task?.messagePreview ?? '(unknown)'}`,
911
930
  },
912
931
  },
913
932
  { tag: 'hr' },
@@ -943,9 +962,9 @@ export class CommandRouter {
943
962
  const payload = await this.queuePayloadOrReply(value, ctx);
944
963
  if (!payload)
945
964
  return;
946
- const { workspace, messageId } = payload;
965
+ const { cwd, messageId } = payload;
947
966
  // Get current message preview
948
- const task = this.bridge.getQueuedTask(workspace, messageId);
967
+ const task = this.bridge.getQueuedTask(cwd, messageId);
949
968
  if (!task) {
950
969
  await this.bridge.sendResult({ text: '⚠️ 该消息不在队列中(可能已开始执行)' }, ctx);
951
970
  return;
@@ -987,7 +1006,7 @@ export class CommandRouter {
987
1006
  placeholder: { tag: 'plain_text', content: '输入新的消息内容...' },
988
1007
  default_value: task.messagePreview,
989
1008
  behaviors: [
990
- { type: 'callback', value: { cmd: 'queue.input', workspace, messageId } },
1009
+ { type: 'callback', value: { cmd: 'queue.input', cwd, messageId } },
991
1010
  ],
992
1011
  },
993
1012
  ],
@@ -1013,7 +1032,7 @@ export class CommandRouter {
1013
1032
  const payload = await this.queuePayloadOrReply(value, ctx);
1014
1033
  if (!payload)
1015
1034
  return;
1016
- const { workspace, messageId } = payload;
1035
+ const { cwd, messageId } = payload;
1017
1036
  if (!newMessage) {
1018
1037
  await this.bridge.sendResult({ text: '⚠️ 缺少新消息内容' }, ctx);
1019
1038
  return;
@@ -1031,7 +1050,7 @@ export class CommandRouter {
1031
1050
  // there is no interleaving window.
1032
1051
  //
1033
1052
  // D3/Step3:复用原任务 binding(不重新快照)+ 恢复 cwdOverride。
1034
- const inputTask = this.bridge.getQueuedTask(workspace, messageId);
1053
+ const inputTask = this.bridge.getQueuedTask(cwd, messageId);
1035
1054
  if (!inputTask) {
1036
1055
  // Task already left the queue (began or was cancelled) before we could
1037
1056
  // register the replacement. Return a toast so the edit-form card gets
@@ -1040,8 +1059,8 @@ export class CommandRouter {
1040
1059
  // toast/card field).
1041
1060
  return { toast: { type: 'info', content: '任务已不在队列中(可能已开始执行或被撤销)' } };
1042
1061
  }
1043
- this.bridge.setTaskReplacement(workspace, messageId, async () => {
1044
- await this.handle(newMessage, ctx, { cwdOverride: workspace, binding: inputTask.binding });
1062
+ this.bridge.setTaskReplacement(cwd, messageId, async () => {
1063
+ await this.handle(newMessage, ctx, { cwdOverride: cwd, binding: inputTask.binding });
1045
1064
  });
1046
1065
  // Update the message preview (and editedMessage so handleQueueImmediate
1047
1066
  // re-enqueues the edited content instead of the stale original closure),
@@ -1054,7 +1073,7 @@ export class CommandRouter {
1054
1073
  // await here: even if the queue chain advances and the replacement is
1055
1074
  // consumed, the user-facing card update is best-effort (the executing card
1056
1075
  // is updated by updateQueueCardToExecuting instead).
1057
- const card = await this.bridge.updateMessagePreview(workspace, messageId, newMessage);
1076
+ const card = await this.bridge.updateMessagePreview(cwd, messageId, newMessage);
1058
1077
  if (!card) {
1059
1078
  // Task left the queue during the await (began or cancelled). The
1060
1079
  // replacement was already consumed (begin path) or cleaned up
@@ -1576,7 +1595,7 @@ export class CommandRouter {
1576
1595
  });
1577
1596
  }
1578
1597
  /**
1579
- * Handle ws.use card action: switch to the workspace, then refresh the /ws
1598
+ * Handle ws.use card action: switch to the cwd, then refresh the /ws
1580
1599
  * list card in place so "recent" sort order is immediately visible.
1581
1600
  *
1582
1601
  * Unlike the command-line `/ws use` (which returns text), the card action
@@ -1586,7 +1605,7 @@ export class CommandRouter {
1586
1605
  async handleWsUse(value, ctx) {
1587
1606
  const name = value.name ?? '';
1588
1607
  const useResult = this.cmdWs(['use', name], ctx);
1589
- // Error cases (workspace not found, path invalid, etc.): relay as error toast
1608
+ // Error cases (cwd not found, path invalid, etc.): relay as error toast
1590
1609
  if (useResult.text && !useResult.text.includes('已切换')) {
1591
1610
  return { toast: { type: 'error', content: useResult.text } };
1592
1611
  }
@@ -1615,7 +1634,7 @@ export class CommandRouter {
1615
1634
  };
1616
1635
  }
1617
1636
  /**
1618
- * Handle ws.remove: remove the workspace alias and refresh the /ws list card
1637
+ * Handle ws.remove: remove the cwd alias and refresh the /ws list card
1619
1638
  * in place (mirrors handleOrderDelete). Without this the stale alias would
1620
1639
  * remain visible on the card the user just clicked.
1621
1640
  */
@@ -2366,7 +2385,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
2366
2385
  catch (err) {
2367
2386
  return { text: `❌ 版本检查失败: ${err.message}` };
2368
2387
  }
2369
- if (!this.updateFns.isNewer(current, latest)) {
2388
+ if (this.updateFns.compareVersions(current, latest) !== 1) {
2370
2389
  return { text: `✅ 已是最新版本 ${current}` };
2371
2390
  }
2372
2391
  if (checkOnly) {
@@ -2398,7 +2417,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
2398
2417
  }
2399
2418
  cmdNew(ctx) {
2400
2419
  // 只清 sessionId 保留 cwd — 否则 /new 之后 /resume 会提示"请先 /cd
2401
- // 设置工作目录",但用户的 workspace 还在。2026-06-21 复盘:用户
2420
+ // 设置工作目录",但用户的 cwd 还在。2026-06-21 复盘:用户
2402
2421
  // /new → /resume 后被误导以为需要重新 /cd。
2403
2422
  const agentName = agentDisplayName(this.config.defaultAgent);
2404
2423
  this.sessionStore.clearSessionId(ctx.userId, this.config.defaultAgent, {
@@ -2667,7 +2686,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
2667
2686
  * @param offset 分页起点(条目数)
2668
2687
  * @param rootDir 浏览起点:卡片「返回」按钮回到这里。省略时默认 = 本次列出的
2669
2688
  * 目录(`/ls <dir>` 的起点即该目录),因此「返回」不会把用户
2670
- * 丢回 workspace cwd(用户明确反馈过)。
2689
+ * 丢回 工作目录(用户明确反馈过)。
2671
2690
  * @param q 关键词筛选:只匹配**当前层**条目名(不递归),大小写不敏感。
2672
2691
  * 省略/空 = 无筛选。
2673
2692
  */
@@ -2709,7 +2728,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
2709
2728
  }
2710
2729
  // 浏览起点(「返回」按钮的目标):`/ls <dir>` 的起点就是该目录本身,之后
2711
2730
  // 进入子目录/翻页/刷新时由卡片 payload 的 root 一路带过来。省略时 = 本次
2712
- // 列出的目录,因此「返回」永远不会把用户丢回 workspace cwd。
2731
+ // 列出的目录,因此「返回」永远不会把用户丢回工作目录。
2713
2732
  const browseRoot = rootDir ? path.resolve(cwd, rootDir) : targetDir;
2714
2733
  try {
2715
2734
  const entries = fs.readdirSync(targetDir, { withFileTypes: true });
@@ -3025,7 +3044,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3025
3044
  text: { tag: 'plain_text', content: '上级' },
3026
3045
  type: 'default',
3027
3046
  size: 'small',
3028
- // 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 workspace cwd
3047
+ // 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 工作目录
3029
3048
  behaviors: [
3030
3049
  { type: 'callback', value: { cmd: 'ls.browse', path: parentDir, root: parentDir } },
3031
3050
  ],
@@ -3145,7 +3164,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3145
3164
  // Pagination calculations (mirror cmdOrder): clamp stale/out-of-range
3146
3165
  // offsets so prev/next always step by WS_PAGE_SIZE.
3147
3166
  const { totalCount, totalPages, safeOffset, currentPage, pageItems: pageEntries, hasPagination, } = pageSlice(visible, offset, WS_PAGE_SIZE);
3148
- // Build body elements: current cwd + workspace list with dividers
3167
+ // Build body elements: current cwd + cwd list with dividers
3149
3168
  const bodyElements = [];
3150
3169
  // Current working directory section
3151
3170
  bodyElements.push({
@@ -1,11 +1,5 @@
1
1
  import type { CommandRouter } from './index.js';
2
- import type { Bridge } from '../bridge/index.js';
3
- /** Context shared by all card-action dispatch paths (file-local; callers pass it structurally). */
4
- interface DispatchContext {
5
- userId: string;
6
- chatId: string;
7
- messageId: string;
8
- }
2
+ import type { Bridge, CommandContext } from '../bridge/index.js';
9
3
  /**
10
4
  * Dispatch an `order.exec` card action as an equivalent queued user message
11
5
  * (Plan A): resolve the order text at the enqueue boundary, then enqueue it
@@ -23,8 +17,7 @@ interface DispatchContext {
23
17
  export declare function dispatchOrderExecForQueue(args: {
24
18
  router: CommandRouter;
25
19
  bridge: Bridge;
26
- workspace: string;
20
+ cwd: string;
27
21
  orderId: string | undefined;
28
- ctx: DispatchContext;
22
+ ctx: CommandContext;
29
23
  }): Promise<'enqueued' | 'missing-id' | 'not-found'>;
30
- export {};
@@ -13,7 +13,7 @@
13
13
  * for the caller to log; performs its own error feedback via `bridge.sendResult`.
14
14
  */
15
15
  export async function dispatchOrderExecForQueue(args) {
16
- const { router, bridge, workspace, orderId, ctx } = args;
16
+ const { router, bridge, cwd, orderId, ctx } = args;
17
17
  if (!orderId) {
18
18
  await bridge.sendResult({ text: '卡片 payload 缺少必要信息' }, ctx);
19
19
  return 'missing-id';
@@ -27,8 +27,8 @@ export async function dispatchOrderExecForQueue(args) {
27
27
  // 避免排队期间 /new、/config 改写 live 状态导致语义漂移。唯一捕获点
28
28
  // Bridge.currentBinding。
29
29
  const binding = bridge.currentBinding(ctx.userId);
30
- bridge.enqueue(workspace, async () => {
31
- await router.handle(resolved.orderText, ctx, { cwdOverride: workspace, binding });
30
+ bridge.enqueue(cwd, async () => {
31
+ await router.handle(resolved.orderText, ctx, { cwdOverride: cwd, binding });
32
32
  }, {
33
33
  taskMeta: {
34
34
  userId: ctx.userId,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Pure utility functions extracted from router/index.ts.
3
- * Path-safety helpers, display formatting, and validation — no Router state.
2
+ * Format session/run usage stats (context, compact count, tokens, cost) into
3
+ * the display strings shown on cards. Pure — no Router state.
4
4
  */
5
5
  import type { ActiveRunSnapshot } from '../bridge/index.js';
6
6
  export interface SessionDisplayUsage {
@@ -12,8 +12,6 @@ export interface SessionDisplayUsage {
12
12
  cacheCreationTokens?: number;
13
13
  }
14
14
  export declare function activeRunUsage(activeRun: ActiveRunSnapshot | undefined): SessionDisplayUsage | undefined;
15
- /** Clamp `v` into [min, max]. */
16
- export declare function clampInt(v: number, min: number, max: number): number;
17
15
  /**
18
16
  * Format usage stats (context length, compact count, cache tokens) into a display string.
19
17
  * Each stat on its own line with blank lines between sections.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Pure utility functions extracted from router/index.ts.
3
- * Path-safety helpers, display formatting, and validation — no Router state.
2
+ * Format session/run usage stats (context, compact count, tokens, cost) into
3
+ * the display strings shown on cards. Pure — no Router state.
4
4
  */
5
5
  export function activeRunUsage(activeRun) {
6
6
  if (!activeRun || activeRun.contextLength === undefined)
@@ -12,11 +12,6 @@ export function activeRunUsage(activeRun) {
12
12
  cacheCreationTokens: activeRun.cacheCreationTokens,
13
13
  };
14
14
  }
15
- // --- Extracted helpers for refactoring ---
16
- /** Clamp `v` into [min, max]. */
17
- export function clampInt(v, min, max) {
18
- return Math.min(Math.max(v, min), max);
19
- }
20
15
  /**
21
16
  * Format number to human-readable unit string.
22
17
  * - >= 1M: one decimal, M suffix (e.g., 1200000 → 1.2M, 25000000 → 25M)
@@ -154,4 +149,4 @@ export function formatUsageStats(usage, options) {
154
149
  }
155
150
  return lines.join('\n');
156
151
  }
157
- //# sourceMappingURL=utils.js.map
152
+ //# sourceMappingURL=usage-format.js.map
@@ -22,7 +22,7 @@ export declare class ClaudeRunner implements AgentRunner {
22
22
  settings?: string;
23
23
  stopGraceMs?: number;
24
24
  pidDir?: string;
25
- workspace: string;
25
+ cwd: string;
26
26
  spawnHeartbeatMs?: number;
27
27
  sessionReader?: AgentSessionReader;
28
28
  /** Claude 权限模式(官方 --permission-mode 枚举;'default'=省略参数)。 */
@@ -44,12 +44,11 @@ export class ClaudeRunner {
44
44
  this.sessionReader = opts.sessionReader ?? new ClaudeSessionReader();
45
45
  this.session = new ClaudeSession({
46
46
  pidDir: opts.pidDir,
47
- workspace: opts.workspace,
47
+ cwd: opts.cwd,
48
48
  stopGraceMs: opts.stopGraceMs,
49
49
  spawnHeartbeatMs: opts.spawnHeartbeatMs,
50
50
  permissionMode: this.permissionMode,
51
51
  settings: opts.settings,
52
- model: this.defaultModel,
53
52
  effort: this.defaultEffort,
54
53
  idleTtlMs: opts.idleTtlMs,
55
54
  });
@@ -22,7 +22,7 @@
22
22
  * - result 事件 subtype 为 compact/compaction 是 turn 中途压缩,不是 turn 结束;
23
23
  * 其余 subtype(success/error/...)才是 turn 终态。
24
24
  *
25
- * 生命周期:一个 workspace 一个长驻进程(lifetime='workspace')。每次
25
+ * 生命周期:一个 cwd 一个长驻进程(lifetime='workspace')。每次
26
26
  * run(message) = 写一条 user 消息 + 消费 stdout 事件直到本 turn 的 result;
27
27
  * 进程在 turn 之间保持存活(stdin 不关闭),/stop / /new / /cd / 看门狗超时
28
28
  * 时经 Terminator 组杀,下条消息按 SessionStore 的 sessionId --resume。
@@ -31,13 +31,12 @@ import { SpawningRunner } from '../common/spawning-runner.js';
31
31
  import type { AgentEvent, SpawnOptions } from '../types.js';
32
32
  export interface ClaudeSessionOptions {
33
33
  pidDir?: string;
34
- workspace: string;
34
+ cwd: string;
35
35
  stopGraceMs?: number;
36
36
  spawnHeartbeatMs?: number;
37
37
  /** Claude 权限模式(官方 --permission-mode 枚举;'default' 省略该参数)。 */
38
38
  permissionMode?: string;
39
39
  settings?: string;
40
- model?: string;
41
40
  effort?: string;
42
41
  /** 会话级空闲回收 TTL(ms):turn 之间无活动超过该窗口则停止进程。0=禁用。 */
43
42
  idleTtlMs?: number;
@@ -61,7 +60,6 @@ export interface PermissionResult {
61
60
  export declare class ClaudeSession extends SpawningRunner {
62
61
  private readonly permissionMode;
63
62
  private readonly settings?;
64
- private readonly defaultModel?;
65
63
  private readonly defaultEffort?;
66
64
  private readonly idleTtlMs;
67
65
  /** 当前 turn 是否在途(防止并发 run 写乱 stdin)。 */
@@ -22,16 +22,16 @@
22
22
  * - result 事件 subtype 为 compact/compaction 是 turn 中途压缩,不是 turn 结束;
23
23
  * 其余 subtype(success/error/...)才是 turn 终态。
24
24
  *
25
- * 生命周期:一个 workspace 一个长驻进程(lifetime='workspace')。每次
25
+ * 生命周期:一个 cwd 一个长驻进程(lifetime='workspace')。每次
26
26
  * run(message) = 写一条 user 消息 + 消费 stdout 事件直到本 turn 的 result;
27
27
  * 进程在 turn 之间保持存活(stdin 不关闭),/stop / /new / /cd / 看门狗超时
28
28
  * 时经 Terminator 组杀,下条消息按 SessionStore 的 sessionId --resume。
29
29
  */
30
30
  import { readFile } from 'node:fs/promises';
31
- import { silentlyUnlink } from '../../common/fs.js';
31
+ import { bestEffortUnlink } from '../../common/fs.js';
32
32
  import { getLogger } from '../../logger/index.js';
33
33
  import { SpawningRunner } from '../common/spawning-runner.js';
34
- import { authErrorEvent, syntheticInitEvent } from '../common/runner-utils.js';
34
+ import { setupErrorEvent, syntheticInitEvent } from '../common/synthetic-events.js';
35
35
  import { makeQuestionApprovalEvent } from '../question-common.js';
36
36
  /** 拒绝权限时的默认提示(进入 claude 上下文,中文用户可读)。 */
37
37
  const DENY_MESSAGE = '用户拒绝了此工具调用,请停止并等待用户指令。';
@@ -50,7 +50,6 @@ const COMPACTION_RESULT_SUBTYPES = new Set(['compact', 'compaction']);
50
50
  export class ClaudeSession extends SpawningRunner {
51
51
  permissionMode;
52
52
  settings;
53
- defaultModel;
54
53
  defaultEffort;
55
54
  idleTtlMs;
56
55
  /** 当前 turn 是否在途(防止并发 run 写乱 stdin)。 */
@@ -92,7 +91,7 @@ export class ClaudeSession extends SpawningRunner {
92
91
  constructor(opts) {
93
92
  super({
94
93
  pidDir: opts.pidDir,
95
- workspace: opts.workspace,
94
+ cwd: opts.cwd,
96
95
  stopGraceMs: opts.stopGraceMs,
97
96
  spawnHeartbeatMs: opts.spawnHeartbeatMs,
98
97
  pidFilePrefix: 'claude',
@@ -103,7 +102,6 @@ export class ClaudeSession extends SpawningRunner {
103
102
  this.binary = 'claude';
104
103
  this.permissionMode = opts.permissionMode ?? 'bypassPermissions';
105
104
  this.settings = opts.settings;
106
- this.defaultModel = opts.model;
107
105
  this.defaultEffort = opts.effort;
108
106
  this.idleTtlMs = opts.idleTtlMs ?? DEFAULT_IDLE_TTL_MS;
109
107
  }
@@ -141,7 +139,7 @@ export class ClaudeSession extends SpawningRunner {
141
139
  // §9.22 守卫:错误 result 前必须补 synthetic init,否则 bridge 的
142
140
  // pre-init result guard 与 run-state reducer 会静默丢弃错误信息。
143
141
  yield syntheticInitEvent(opts.sessionId);
144
- yield authErrorEvent(spawnError);
142
+ yield setupErrorEvent(spawnError);
145
143
  return;
146
144
  }
147
145
  // turn 之间的 idle 噪音(如 prompt_suggestion)不属于本 turn,先清空。
@@ -372,7 +370,7 @@ export class ClaudeSession extends SpawningRunner {
372
370
  // 进程自行结束(崩溃/上游退出):注销协议停止通道,别把死 pid 的
373
371
  // 条目留在注册表里等 pid 复用。
374
372
  this.unregisterStopper();
375
- silentlyUnlink(this.pidFilePath);
373
+ bestEffortUnlink(this.pidFilePath);
376
374
  this.wakeWaiters();
377
375
  }
378
376
  }
@@ -482,7 +480,7 @@ export class ClaudeSession extends SpawningRunner {
482
480
  this.idleTimer = null;
483
481
  if (this.turnActive || !this.isRunning)
484
482
  return;
485
- getLogger().info(`[${this.logTag}] idle timeout ${this.idleTtlMs}ms, stopping session workspace=${this.cwd}`);
483
+ getLogger().info(`[${this.logTag}] idle timeout ${this.idleTtlMs}ms, stopping session cwd=${this.cwd}`);
486
484
  void this.stop().catch((err) => {
487
485
  getLogger().warn(`[${this.logTag}] idle stop failed: ${err.message}`);
488
486
  });
@@ -1,18 +1,12 @@
1
1
  /**
2
- * Shared helper functions for the ACP protocol layer.
2
+ * ACP approval/permission mapping and lifecycle shared by the kimi and
3
+ * opencode ACP runners.
3
4
  *
4
5
  * Extracted from runner.ts and translator.ts to avoid duplication (G5).
5
6
  * Shape source: approval.ts:28-29 — optionId is opaque, echo back as-is.
6
7
  */
7
8
  import type { ApprovalView } from '../../types.js';
8
9
  import type { PermissionOption } from './protocol-types.js';
9
- /**
10
- * Find an option by matching its `kind` against a list of candidate values,
11
- * and return the `optionId` to echo back in the approval response.
12
- * ACP option kind values vary between kimi versions; this handles both
13
- * the documented names and the observed real names (§2.3).
14
- */
15
- export declare function findOptionIdByKind(options: PermissionOption[], candidateKinds: string[]): string | undefined;
16
10
  /** 各 ACP 服务端的 option kind 候选表(kimi/opencode 观测名不同,语义对齐)。 */
17
11
  export interface AcpApprovalKinds {
18
12
  accept: string[];