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
@@ -9,7 +9,7 @@ import { BashCardSession, capBashOutput } from '../card/bash-card-session.js';
9
9
  import { buildSessionHistoryCard } from '../router/card-helpers.js';
10
10
  import { agentDisplayName, resumeUseButton } from '../card/card-shared.js';
11
11
  import { enforceCardBudget } from '../card/card-budget.js';
12
- import { normalizeResultUsage } from '../runner/common/usage.js';
12
+ import { normalizeResultUsage } from '../runner/common/result-usage.js';
13
13
  import { BashProcessRunner } from '../runner/index.js';
14
14
  import { QueueManager, } from './queue-manager.js';
15
15
  import { ApprovalCoordinator, decisionToApprovalAction } from './approval-coordinator.js';
@@ -62,10 +62,10 @@ function runnerHasRunCompact(runner) {
62
62
  *
63
63
  * The router delegates non-command messages here via `forwardToClaude` and
64
64
  * sends command results via `sendResult`. Serial processing is enforced per
65
- * workspace by `enqueue`: each workspace has its own Promise chain, so at
66
- * most one agent process runs per workspace at a time, while different
65
+ * cwd by `enqueue`: each cwd has its own Promise chain, so at
66
+ * most one agent process runs per cwd at a time, while different
67
67
  * workspaces run in parallel (see `activeRuns`, §9.6). This is a load-bearing
68
- * invariant — concurrent spawns within one workspace would corrupt session
68
+ * invariant — concurrent spawns within one cwd would corrupt session
69
69
  * state. Most card actions go through `enqueueImmediate` (fire-and-forget,
70
70
  * outside the serial chain); only run-forwarding is serialized.
71
71
  *
@@ -99,7 +99,7 @@ export class Bridge {
99
99
  runners = new Map();
100
100
  /**
101
101
  * Runner 槽位在「活跃运行期间配置变更」时被标 stale(CC-06/P1):clearRunners() 对
102
- * 活跃 workspace 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
102
+ * 活跃 cwd 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
103
103
  * finalizeRun 安全 evict+dispose,下一轮 getRunner 创建新配置的 runner。
104
104
  * key 为 `${cwd}\u0000${agentKind}`。
105
105
  */
@@ -107,7 +107,7 @@ export class Bridge {
107
107
  /** Queue manager for per-workspace serial processing queue. */
108
108
  /** 普通消息队列管理器(public:测试注入 queue-card 状态用)。 */
109
109
  queueManager;
110
- /** Active runs per workspace (cwd). Multiple workspaces can run in parallel. */
110
+ /** Active runs per cwd (cwd). Multiple workspaces can run in parallel. */
111
111
  activeRuns = new Map();
112
112
  /** Approval coordinators keyed by runId. */
113
113
  approvalCoordinators = new Map();
@@ -115,11 +115,11 @@ export class Bridge {
115
115
  * 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
116
116
  * activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
117
117
  */
118
- lastCompactableCodexRun = new Map();
118
+ lastCompactableRun = new Map();
119
119
  /**
120
120
  * Active `!` bash runs, keyed by runId (NOT cwd). Bash runs bypass the serial
121
121
  * queue and may run in parallel with claude runs / other bash runs in the same
122
- * workspace, so they live outside `activeRuns`. Tracked so `/stop` can reach
122
+ * cwd, so they live outside `activeRuns`. Tracked so `/stop` can reach
123
123
  * the per-run BashRunner instance via interruptCurrentRun.
124
124
  */
125
125
  activeBashRuns = new Map();
@@ -131,29 +131,29 @@ export class Bridge {
131
131
  this.idleTimeoutMs = deps.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
132
132
  this.agentRegistry = deps.agentRegistry;
133
133
  this.sessionReaderRegistry = deps.sessionReaderRegistry;
134
- this.queueManager = new QueueManager((workspace) => this.activeRuns.has(workspace), (chatId, card, opts) => this.connector.sendWithRetry(chatId, { card }, { replyTo: opts?.replyTo }), (messageId, card) => this.connector.updateCard(messageId, card));
134
+ this.queueManager = new QueueManager((cwd) => this.activeRuns.has(cwd), (chatId, card, opts) => this.connector.sendWithRetry(chatId, { card }, { replyTo: opts?.replyTo }), (messageId, card) => this.connector.updateCard(messageId, card));
135
135
  this.mediaHandler = new InboundMediaHandler({
136
136
  resolveCwd: (userId) => this.resolveCwd(userId),
137
137
  getConfig: () => this.config,
138
138
  });
139
139
  }
140
140
  /**
141
- * Get or create a runner for the given workspace, keyed by current defaultAgent.
141
+ * Get or create a runner for the given cwd, keyed by current defaultAgent.
142
142
  *
143
143
  * Fix 4 (2026-07-18): Cache key includes agentKind, so switching defaultAgent
144
144
  * creates a NEW runner under a different key without evicting the old one.
145
145
  * This eliminates the need for clearRunners() on agent switch.
146
146
  */
147
- getRunner(workspace, kind = this.config.defaultAgent) {
148
- // Fix 4: Check for runner under (workspace, kind) key
149
- const workspaceMap = this.runners.get(workspace);
147
+ getRunner(cwd, kind = this.config.defaultAgent) {
148
+ // Fix 4: Check for runner under (cwd, kind) key
149
+ const workspaceMap = this.runners.get(cwd);
150
150
  if (workspaceMap?.has(kind)) {
151
151
  return workspaceMap.get(kind);
152
152
  }
153
153
  // review P3-3:缓存超过上限且是新工作区时才回收死槽位——刚创建未 run
154
154
  // 的 runner 同样是 isRunning=false,小缓存下立即回收会破坏「创建即注册」
155
155
  // 的既有语义(exit-dispatcher 计数回归)。
156
- if (this.runners.size >= MAX_CACHED_RUNNER_WORKSPACES && !this.runners.has(workspace)) {
156
+ if (this.runners.size >= MAX_CACHED_RUNNER_WORKSPACES && !this.runners.has(cwd)) {
157
157
  for (const [cwd, slotMap] of [...this.runners.entries()]) {
158
158
  if (this.activeRuns.has(cwd))
159
159
  continue;
@@ -164,8 +164,8 @@ export class Bridge {
164
164
  }
165
165
  }
166
166
  }
167
- // Create new runner for this (workspace, kind) slot
168
- const runner = this.agentRegistry.get(kind, workspace);
167
+ // Create new runner for this (cwd, kind) slot
168
+ const runner = this.agentRegistry.get(kind, cwd);
169
169
  // Call lifecycle methods for agent runners created via factory
170
170
  if (runner && typeof runner.killOrphan === 'function') {
171
171
  runner.killOrphan();
@@ -173,9 +173,9 @@ export class Bridge {
173
173
  if (runner && typeof runner.registerExitHandlers === 'function') {
174
174
  runner.registerExitHandlers();
175
175
  }
176
- // Store in nested map: Map<workspace, Map<kind, Runner>>
176
+ // Store in nested map: Map<cwd, Map<kind, Runner>>
177
177
  if (!workspaceMap) {
178
- this.runners.set(workspace, new Map([[kind, runner]]));
178
+ this.runners.set(cwd, new Map([[kind, runner]]));
179
179
  }
180
180
  else {
181
181
  workspaceMap.set(kind, runner);
@@ -241,17 +241,17 @@ export class Bridge {
241
241
  return undefined;
242
242
  }
243
243
  }
244
- /** Get the current runner for a workspace. Used by router for agent-specific commands (P3.2). */
245
- getCurrentRunner(workspace) {
246
- return this.getRunner(workspace);
244
+ /** Get the current runner for a cwd. Used by router for agent-specific commands (P3.2). */
245
+ getCurrentRunner(cwd) {
246
+ return this.getRunner(cwd);
247
247
  }
248
248
  /**
249
249
  * Whether the runner for the given agent kind has runCompact capability.
250
250
  * Used by router for compact button gating (design doc §6.2-2: duck-typing
251
251
  * instead of hardcoding agentKind === 'codex').
252
252
  */
253
- hasRunCompact(workspace, kind = this.config.defaultAgent) {
254
- const runner = this.getRunner(workspace, kind);
253
+ hasRunCompact(cwd, kind = this.config.defaultAgent) {
254
+ const runner = this.getRunner(cwd, kind);
255
255
  return runnerHasRunCompact(runner);
256
256
  }
257
257
  /** 入队时刻快照:当前 defaultAgent + 该 agent 的 sessionId(无 session 则 undefined)。
@@ -259,7 +259,11 @@ export class Bridge {
259
259
  * 执行时刻,避免 /new、/config 在排队期间改写 live 状态导致语义漂移(方案 D4)。 */
260
260
  currentBinding(userId) {
261
261
  const agent = this.config.defaultAgent;
262
- return { agent, sessionId: this.sessionStore.getSessionId(userId, agent) };
262
+ return {
263
+ agent,
264
+ sessionId: this.sessionStore.getSessionId(userId, agent),
265
+ sessionEpoch: this.sessionStore.getSessionEpoch(userId, agent),
266
+ };
263
267
  }
264
268
  /**
265
269
  * Clear all cached runner instances. Call after config changes that affect runner config.
@@ -274,7 +278,7 @@ export class Bridge {
274
278
  // ends, and the next run picks up the new config/agent via getRunner.
275
279
  for (const cwd of [...this.runners.keys()]) {
276
280
  if (this.activeRuns.has(cwd)) {
277
- // CC-06/P1: 活跃 workspace 不能立即 evict(会误杀长驻连接),但必须标记 stale,
281
+ // CC-06/P1: 活跃 cwd 不能立即 evict(会误杀长驻连接),但必须标记 stale,
278
282
  // 否则 finalizeRun 对 workspace-lifetime runner 也不 evict → 下一轮复用旧配置实例。
279
283
  // 这里只标待淘汰,当前 run 结束后由 finalizeRun 安全 evict。
280
284
  for (const kind of [...this.runners.get(cwd).keys()]) {
@@ -365,48 +369,48 @@ export class Bridge {
365
369
  * Enqueue a task into the workspace-level serial bridge queue.
366
370
  * Delegates to QueueManager for actual implementation.
367
371
  */
368
- enqueue(workspace, task, opts) {
369
- this.queueManager.enqueue(workspace, task, opts);
372
+ enqueue(cwd, task, opts) {
373
+ this.queueManager.enqueue(cwd, task, opts);
370
374
  }
371
375
  /**
372
376
  * Execute a task immediately without going through the queue.
373
377
  * Used for / commands that should respond immediately.
374
378
  */
375
- enqueueImmediate(workspace, task) {
376
- this.queueManager.enqueueImmediate(workspace, task);
379
+ enqueueImmediate(cwd, task) {
380
+ this.queueManager.enqueueImmediate(cwd, task);
377
381
  }
378
- /** Get queue info for a workspace. */
379
- getQueueInfo(workspace) {
380
- return this.queueManager.getQueueInfo(workspace);
382
+ /** Get queue info for a cwd. */
383
+ getQueueInfo(cwd) {
384
+ return this.queueManager.getQueueInfo(cwd);
381
385
  }
382
386
  /** Remove a task from the queue by messageId. Returns true if found and removed. */
383
- removeFromQueue(workspace, messageId) {
384
- return this.queueManager.removeFromQueue(workspace, messageId);
387
+ removeFromQueue(cwd, messageId) {
388
+ return this.queueManager.removeFromQueue(cwd, messageId);
385
389
  }
386
390
  /**
387
391
  * One-shot: replace the execution closure of an existing queued task in
388
392
  * place, preserving its queue position. Used by queue.immediate after the
389
393
  * user edits a queued message (see router handleQueueImmediate).
390
394
  */
391
- setTaskReplacement(workspace, messageId, task) {
392
- this.queueManager.setTaskReplacement(workspace, messageId, task);
395
+ setTaskReplacement(cwd, messageId, task) {
396
+ this.queueManager.setTaskReplacement(cwd, messageId, task);
393
397
  }
394
398
  /** Update queue card to "cancelled" status when user clicks 撤销. */
395
- async updateQueueCardToCancelled(workspace, messageId) {
396
- await this.queueManager.updateQueueCardToCancelled(workspace, messageId);
399
+ async updateQueueCardToCancelled(cwd, messageId) {
400
+ await this.queueManager.updateQueueCardToCancelled(cwd, messageId);
397
401
  }
398
402
  /** Mark a queued task's card as executing immediately (grey out buttons).
399
403
  * Called by handleQueueImmediate so the user sees feedback right away
400
404
  * instead of waiting for the queue callback. Idempotent: the queue
401
405
  * callback's own updateQueueCardToExecuting call becomes a no-op because
402
406
  * updateQueueCardToExecuting deletes the card-message mapping in finally. */
403
- async markQueueCardExecuting(workspace, messageId) {
404
- const task = this.getQueuedTask(workspace, messageId);
405
- await this.queueManager.updateQueueCardToExecuting(workspace, messageId, task?.messagePreview ?? '', false);
407
+ async markQueueCardExecuting(cwd, messageId) {
408
+ const task = this.getQueuedTask(cwd, messageId);
409
+ await this.queueManager.updateQueueCardToExecuting(cwd, messageId, task?.messagePreview ?? '', false);
406
410
  }
407
411
  /** Get task metadata from queue. */
408
- getQueuedTask(workspace, messageId) {
409
- return this.queueManager.getQueuedTask(workspace, messageId);
412
+ getQueuedTask(cwd, messageId) {
413
+ return this.queueManager.getQueuedTask(cwd, messageId);
410
414
  }
411
415
  /**
412
416
  * Whether a task with the given messageId has begun executing and has not
@@ -418,9 +422,9 @@ export class Bridge {
418
422
  hasTaskBegan(messageId) {
419
423
  return this.queueManager.hasBegan(messageId);
420
424
  }
421
- /** Get all queued tasks for a workspace. Returns a copy to prevent aliasing bugs. */
422
- getQueuedTasks(workspace) {
423
- return this.queueManager.getQueuedTasks(workspace);
425
+ /** Get all queued tasks for a cwd. Returns a copy to prevent aliasing bugs. */
426
+ getQueuedTasks(cwd) {
427
+ return this.queueManager.getQueuedTasks(cwd);
424
428
  }
425
429
  /**
426
430
  * Update the messagePreview for a queued task and build the updated queue
@@ -429,18 +433,18 @@ export class Bridge {
429
433
  * queued. Returning the card via the callback response avoids the
430
434
  * PATCH-vs-callback-response race that left the card stuck in edit state.
431
435
  */
432
- async updateMessagePreview(workspace, messageId, newMessage) {
433
- const updated = this.queueManager.updateQueuedTaskMessage(workspace, messageId, newMessage);
436
+ async updateMessagePreview(cwd, messageId, newMessage) {
437
+ const updated = this.queueManager.updateQueuedTaskMessage(cwd, messageId, newMessage);
434
438
  if (!updated)
435
439
  return null;
436
- return this.queueManager.buildQueueCardForEdit(workspace, messageId, newMessage);
440
+ return this.queueManager.buildQueueCardForEdit(cwd, messageId, newMessage);
437
441
  }
438
- /** Whether a claude run is currently in progress in the given workspace. */
439
- isBusyFor(workspace) {
440
- return this.activeRuns.has(workspace);
442
+ /** Whether a claude run is currently in progress in the given cwd. */
443
+ isBusyFor(cwd) {
444
+ return this.activeRuns.has(cwd);
441
445
  }
442
- getActiveRunFor(workspace) {
443
- const active = this.activeRuns.get(workspace);
446
+ getActiveRunFor(cwd) {
447
+ const active = this.activeRuns.get(cwd);
444
448
  if (!active)
445
449
  return undefined;
446
450
  const state = active.session.currentState;
@@ -457,7 +461,7 @@ export class Bridge {
457
461
  cacheCreationTokens: state.cacheCreationTokens,
458
462
  };
459
463
  }
460
- /** Whether any claude run is currently in progress (any workspace). */
464
+ /** Whether any claude run is currently in progress (any cwd). */
461
465
  get isBusy() {
462
466
  return this.activeRuns.size > 0;
463
467
  }
@@ -515,7 +519,7 @@ export class Bridge {
515
519
  async interruptCurrentRun(input) {
516
520
  // Find the active claude run matching this user/chat
517
521
  for (const [cwd, active] of this.activeRuns) {
518
- if (input.workspace !== undefined && cwd !== input.workspace)
522
+ if (input.cwd !== undefined && cwd !== input.cwd)
519
523
  continue;
520
524
  if (active.userId === input.userId && active.chatId === input.chatId) {
521
525
  if (input.runId && active.runId !== input.runId)
@@ -542,7 +546,7 @@ export class Bridge {
542
546
  ]);
543
547
  // The chain may have advanced while the stop was in flight: a new run
544
548
  // (from a task that began during the stop window) may now occupy this
545
- // workspace. Only delete the entry we actually stopped.
549
+ // cwd. Only delete the entry we actually stopped.
546
550
  if (this.activeRuns.get(cwd) === active) {
547
551
  this.activeRuns.delete(cwd);
548
552
  }
@@ -570,7 +574,7 @@ export class Bridge {
570
574
  // Then check active bash runs (! commands). These bypass the serial queue
571
575
  // and are tracked separately; stop the per-run BashRunner directly.
572
576
  for (const [runId, b] of this.activeBashRuns) {
573
- if (input.workspace !== undefined && b.cwd !== input.workspace)
577
+ if (input.cwd !== undefined && b.cwd !== input.cwd)
574
578
  continue;
575
579
  if (b.userId === input.userId && b.chatId === input.chatId) {
576
580
  if (input.runId && runId !== input.runId)
@@ -679,7 +683,7 @@ export class Bridge {
679
683
  }
680
684
  /**
681
685
  * Resolve the working directory for a user.
682
- * First checks sessionStore (set by /cd), then falls back to first workspace.
686
+ * First checks sessionStore (set by /cd), then falls back to first cwd.
683
687
  * Returns undefined if no cwd is available.
684
688
  */
685
689
  resolveCwd(userId) {
@@ -691,7 +695,7 @@ export class Bridge {
691
695
  // NOTE: fallback uses insertion order, not sort preference — by design
692
696
  // (cwd fallback stays insertion-order for now)
693
697
  cwd = workspaces[0].path;
694
- getLogger().info(`[lark-remote] resolveCwd fallback cwd=${cwd} (first saved workspace)`);
698
+ getLogger().info(`[lark-remote] resolveCwd fallback cwd=${cwd} (first saved cwd)`);
695
699
  }
696
700
  }
697
701
  return cwd;
@@ -707,7 +711,7 @@ export class Bridge {
707
711
  async forwardToClaude(message, ctx, opts) {
708
712
  // D5: agentKind 一律来自绑定。无 binding 时保持现状(live defaultAgent)。
709
713
  const agentKind = opts?.binding?.agent ?? this.config.defaultAgent;
710
- // P1-14: lane 与执行 cwd 同源。消息入队时(index.ts 闭包)捕获的 workspace
714
+ // P1-14: lane 与执行 cwd 同源。消息入队时(index.ts 闭包)捕获的 cwd
711
715
  // 作为 cwdOverride 显式传入;执行时若 sessionStore cwd 已被 /cd 等命令改写,
712
716
  // 以 lane 为准 —— 否则旧 lane 的排队消息会被 busy-drop 静默丢失。空串视为
713
717
  // 未提供(与入队前无 cwd 的兜底语义一致)。
@@ -720,10 +724,10 @@ export class Bridge {
720
724
  const entry = this.sessionStore.get(ctx.userId);
721
725
  getLogger().debug(`[lark-remote] forward entry userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} ` +
722
726
  `agent=${agentKind} sessionId=${opts?.binding?.sessionId ?? entry?.sessions?.get(agentKind) ?? '(none)'} message=${message.slice(0, 100)}`);
723
- // Check if current workspace already has a run in progress
727
+ // Check if current cwd already has a run in progress
724
728
  if (this.activeRuns.has(cwd)) {
725
- getLogger().warn(`[lark-remote] workspace busy, dropping message userId=${ctx.userId} cwd=${cwd}`);
726
- await this.sendResult({ text: '此 workspace 正在处理中,请 /stop 后重试' }, ctx);
729
+ getLogger().warn(`[lark-remote] cwd busy, dropping message userId=${ctx.userId} cwd=${cwd}`);
730
+ await this.sendResult({ text: '此工作目录正在处理中,请 /stop 后重试' }, ctx);
727
731
  return;
728
732
  }
729
733
  // Note: Multiple workspaces can have runs in parallel. If runner is busy,
@@ -731,10 +735,13 @@ export class Bridge {
731
735
  // D2/D5: sessionId 钉死语义——binding 有 sessionId 时用它(入队时刻快照),
732
736
  // 否则跟随 live store(无 binding 或入队时无 session 的正常路径)。
733
737
  const sessionId = opts?.binding?.sessionId ?? this.sessionStore.getSessionId(ctx.userId, agentKind);
734
- // 会话代际快照(2026-08-09):run 在途时 /new、/cd、/resume 会 bump epoch,
735
- // 该 run 后续 system.init 的 sessionId 写回即判 stale 跳过。在执行起点
736
- // (而非入队点)捕获,保持 binding「钉死 resume」既有语义。
737
- const sessionEpochAtStart = this.sessionStore.getSessionEpoch(ctx.userId, agentKind);
738
+ // 会话代际快照(2026-08-09,2026-10-07 前移):/new、/cd、/resume 会 bump
739
+ // epoch,该 run 后续 system.init 的 sessionId 写回即判 stale 跳过。
740
+ // 快照点优先取 binding.sessionEpoch(入队/commit 时刻 T0,与 sessionId 同刻):
741
+ // 排队消息在「commit 后、开跑前」被 reset 时,执行起点捕获会漏判(epoch 已是
742
+ // bump 后的值)→ 旧 sessionId 被写回复活 reset。无 binding 时回退执行起点捕获,
743
+ // 保持非排队路径(如 /compact)的既有语义。
744
+ const sessionEpochAtStart = opts?.binding?.sessionEpoch ?? this.sessionStore.getSessionEpoch(ctx.userId, agentKind);
738
745
  // Resolve the runner BEFORE creating the run session so the exact instance
739
746
  // that will run is captured into activeRun. interruptCurrentRun stops THAT
740
747
  // instance directly -- never a fresh runner re-resolved from a cache that
@@ -872,7 +879,7 @@ export class Bridge {
872
879
  // Reasoning tokens (pi usage.reasoning) from the live result event.
873
880
  let finalReasoningTokens;
874
881
  // 统一的 usage meta 构造(finalize 各分支共享,本方法内原先 5 处手写 11-15
875
- // 字段对象;第 6 处 streamCodexCompact 形状不同未收敛)。
882
+ // 字段对象;第 6 处 streamCompact 形状不同未收敛)。
876
883
  // catch 路径需覆盖 flow 字段时在展开后覆写即可。catch error 路径亦复用此
877
884
  // 闭包,相比旧手写对象补齐了累计 cache 字段(cumulativeCacheReadTokens/
878
885
  // cumulativeCacheCreationTokens),属有意的口径对齐(向 done 路径看齐)。
@@ -956,12 +963,12 @@ export class Bridge {
956
963
  const realCwd = event.cwd && event.cwd.length > 0 ? event.cwd : cwd;
957
964
  if (hasCwd && event.cwd && event.cwd !== cwd) {
958
965
  // Session has its own cwd (e.g. EnterWorktree relocate). Keep the
959
- // workspace cwd unchanged; record the session's actual cwd in sessionCwds.
960
- getLogger().info(`[lark-remote] system.init: workspace cwd=${cwd}, session cwd=${event.cwd}`);
966
+ // cwd unchanged; record the session's actual cwd in sessionCwds.
967
+ getLogger().info(`[lark-remote] system.init: cwd=${cwd}, session cwd=${event.cwd}`);
961
968
  this.sessionStore.setSessionIdAndSessionCwd(ctx.userId, agentKind, event.session_id, realCwd);
962
969
  }
963
970
  else {
964
- // First use (no workspace cwd yet) or event.cwd matches runner cwd:
971
+ // First use (no cwd yet) or event.cwd matches runner cwd:
965
972
  // set sessionId + cwd as before; on first use also bootstrap sessionCwds.
966
973
  const bootstrapSessionCwd = !hasCwd && event.cwd && event.cwd.length > 0 ? event.cwd : undefined;
967
974
  this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.session_id, realCwd, bootstrapSessionCwd);
@@ -1295,7 +1302,7 @@ export class Bridge {
1295
1302
  // P1-13: cleanup must survive mid-finalize errors. The only unguarded
1296
1303
  // expression in finalizeRun is the renderRunCard(...) argument evaluated
1297
1304
  // at the sendResult call site; if it throws, the run must still release
1298
- // its activeRuns slot — otherwise the workspace is permanently busy
1305
+ // its activeRuns slot — otherwise the cwd is permanently busy
1299
1306
  // (every later message busy-dropped until a manual /stop).
1300
1307
  if (this.activeRuns.get(cwd) === activeRun) {
1301
1308
  this.activeRuns.delete(cwd);
@@ -1328,7 +1335,7 @@ export class Bridge {
1328
1335
  const compactSessionId = cardSession.currentState.sessionId ??
1329
1336
  this.sessionStore.getSessionId(ctx.userId, activeRun.agentKind);
1330
1337
  if (compactSessionId) {
1331
- this.lastCompactableCodexRun.set(cwd, {
1338
+ this.lastCompactableRun.set(cwd, {
1332
1339
  runId: activeRun.runId,
1333
1340
  sessionId: compactSessionId,
1334
1341
  agentKind: activeRun.agentKind,
@@ -1475,14 +1482,14 @@ export class Bridge {
1475
1482
  return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerNote({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
1476
1483
  }
1477
1484
  /**
1478
- * Handle codex.compact card action — trigger a compaction request for any
1485
+ * Handle compact card action — trigger a compaction request for any
1479
1486
  * runCompact-capable runner(codex/kimi/opencode/pi/claude,按
1480
- * lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
1487
+ * lastCompactableRun 记录的 agentKind 路由)。Validates the run exists
1481
1488
  * and is in a terminal state, then calls the runner's runCompact().
1482
1489
  */
1483
- async handleCodexCompact(value, ctx) {
1490
+ async handleCompactAction(value, ctx) {
1484
1491
  const log = getLogger();
1485
- log.info(`[lark-remote] handleCodexCompact userId=${ctx.userId} runId=${value.runId?.slice(0, 8)}...`);
1492
+ log.info(`[lark-remote] handleCompactAction userId=${ctx.userId} runId=${value.runId?.slice(0, 8)}...`);
1486
1493
  const { runId } = value;
1487
1494
  if (!runId) {
1488
1495
  await this.sendResult({ text: '⚠️ 无效的 Compact 请求,缺少 runId' }, ctx);
@@ -1493,8 +1500,8 @@ export class Bridge {
1493
1500
  await this.sendResult({ text: '⚠️ 未设置工作目录' }, ctx);
1494
1501
  return;
1495
1502
  }
1496
- // 校验:run 结束后 activeRuns 已清空,用 lastCompactableCodexRun 对照 runId。
1497
- const last = this.lastCompactableCodexRun.get(cwd);
1503
+ // 校验:run 结束后 activeRuns 已清空,用 lastCompactableRun 对照 runId。
1504
+ const last = this.lastCompactableRun.get(cwd);
1498
1505
  if (!last || last.runId !== runId) {
1499
1506
  await this.sendResult({ text: '⚠️ 该任务已结束或不属于当前会话' }, ctx);
1500
1507
  return;
@@ -1513,8 +1520,8 @@ export class Bridge {
1513
1520
  await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
1514
1521
  return;
1515
1522
  }
1516
- log.info(`[lark-remote] handleCodexCompact executing runCompact for runId=${runId} cwd=${cwd}`);
1517
- await this.streamCodexCompact({
1523
+ log.info(`[lark-remote] handleCompactAction executing runCompact for runId=${runId} cwd=${cwd}`);
1524
+ await this.streamCompact({
1518
1525
  sessionId: last.sessionId,
1519
1526
  cwd,
1520
1527
  agentKind: last.agentKind,
@@ -1556,16 +1563,16 @@ export class Bridge {
1556
1563
  * Stream a compaction to a card: start a RunCardSession with
1557
1564
  * operationKind='compaction' (no recursive Compact button), consume
1558
1565
  * runner.runCompact() events, read authoritative jsonl usage, and finish
1559
- * the card. Shared by handleCodexCompact (run card button) and
1566
+ * the card. Shared by handleCompactAction (run card button) and
1560
1567
  * handleResumeCompact (resume cards) so both flows stay in sync.
1561
1568
  *
1562
1569
  * Precondition: callers have already validated that `runner` implements
1563
1570
  * runCompact and that the session exists in `cwd`.
1564
1571
  */
1565
- async streamCodexCompact(opts) {
1572
+ async streamCompact(opts) {
1566
1573
  const log = getLogger();
1567
1574
  const { sessionId, cwd, agentKind, runner, ctx } = opts;
1568
- log.info(`[lark-remote] streamCodexCompact sessionId=${sessionId} cwd=${cwd}`);
1575
+ log.info(`[lark-remote] streamCompact sessionId=${sessionId} cwd=${cwd}`);
1569
1576
  // 防御性能力检查:调用方已校验(runId / resume 两条路径),此处双保险并
1570
1577
  // 让 TS 收窄 runner 类型('runCompact' in runner 之后的 cast 才合法)。
1571
1578
  if (!('runCompact' in runner) ||
@@ -1577,7 +1584,7 @@ export class Bridge {
1577
1584
  // 记录,令 /stop 只停到最后注册的那个。
1578
1585
  const busyWith = this.activeRuns.get(cwd);
1579
1586
  if (busyWith) {
1580
- log.info(`[lark-remote] compact refused: workspace busy cwd=${cwd} runId=${busyWith.runId}`);
1587
+ log.info(`[lark-remote] compact refused: cwd busy cwd=${cwd} runId=${busyWith.runId}`);
1581
1588
  await this.sendResult({ text: '⚠️ 该工作区正在运行任务,请等待结束或先 /stop' }, ctx);
1582
1589
  return;
1583
1590
  }
@@ -1675,10 +1682,10 @@ export class Bridge {
1675
1682
  cumulativeCacheReadTokens: finalUsage?.cumulativeCacheReadTokens,
1676
1683
  cumulativeCacheCreationTokens: finalUsage?.cumulativeCacheCreationTokens,
1677
1684
  });
1678
- log.info(`[lark-remote] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
1685
+ log.info(`[lark-remote] streamCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
1679
1686
  }
1680
1687
  catch (err) {
1681
- log.error(`[lark-remote] streamCodexCompact failed: ${errorMessage(err)}`);
1688
+ log.error(`[lark-remote] streamCompact failed: ${errorMessage(err)}`);
1682
1689
  // 终态守卫(与 runAgentStreamToEnd 同口径):running/finalizing 才转 error,
1683
1690
  // 已终态(interrupted/idle_timeout)保留首终态,不被迟到的异常改写。
1684
1691
  const catchTerminal = cardSession.currentState.terminal;
@@ -1705,7 +1712,7 @@ export class Bridge {
1705
1712
  * Handle resume.compact card action — compact a session directly from a
1706
1713
  * resume card (auto-resume / `/resume <id>`), without a runId.
1707
1714
  *
1708
- * Unlike handleCodexCompact (which validates against the last finished run's
1715
+ * Unlike handleCompactAction (which validates against the last finished run's
1709
1716
  * runId), the resume card carries the sessionId + agent it was rendered for.
1710
1717
  * Validation: the session must exist in the current cwd (same rule as
1711
1718
  * cmdResume / resume.use) and the runner must implement runCompact.
@@ -1749,7 +1756,7 @@ export class Bridge {
1749
1756
  await this.sendResult({ text: `⚠️ 未找到 session ${sessionId}(当前目录: ${cwd})` }, ctx);
1750
1757
  return;
1751
1758
  }
1752
- // §5.3: kimi 压缩在途检测(与 handleCodexCompact 同一入口)。
1759
+ // §5.3: kimi 压缩在途检测(与 handleCompactAction 同一入口)。
1753
1760
  if (await this.rejectIfCompactionInFlight(sessionId, cwd, agentKind, ctx))
1754
1761
  return;
1755
1762
  // Check that the runner has runCompact(codex/kimi/opencode/pi/claude 鸭子探测)。
@@ -1759,7 +1766,7 @@ export class Bridge {
1759
1766
  return;
1760
1767
  }
1761
1768
  log.info(`[lark-remote] handleResumeCompact executing runCompact sessionId=${sessionId} cwd=${cwd}`);
1762
- await this.streamCodexCompact({ sessionId, cwd, agentKind, runner, ctx });
1769
+ await this.streamCompact({ sessionId, cwd, agentKind, runner, ctx });
1763
1770
  }
1764
1771
  /**
1765
1772
  * Execute a bash command and stream output to a card.
@@ -1812,7 +1819,7 @@ export class Bridge {
1812
1819
  });
1813
1820
  // Track this bash run independently from claude's `activeRuns`. Bash runs
1814
1821
  // bypass the serial queue and may run in parallel with a claude run in the
1815
- // same workspace, so they must NOT share activeRuns (which is keyed by cwd
1822
+ // same cwd, so they must NOT share activeRuns (which is keyed by cwd
1816
1823
  // and would let bash/claude overwrite each other). Keyed by runId so
1817
1824
  // multiple `!` commands can run concurrently. Tracked so /stop can reach
1818
1825
  // the bashRunner (which is otherwise a local) via interruptCurrentRun.