lark-remote 0.4.4 → 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 (101) hide show
  1. package/CHANGELOG.md +10 -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/cli.js +5 -6
  15. package/dist/clone.d.ts +1 -6
  16. package/dist/clone.js +4 -4
  17. package/dist/common/clamp.d.ts +2 -0
  18. package/dist/common/clamp.js +5 -0
  19. package/dist/common/fs.d.ts +4 -3
  20. package/dist/common/fs.js +5 -4
  21. package/dist/config/dir.d.ts +4 -5
  22. package/dist/config/dir.js +7 -8
  23. package/dist/config/wizard.js +2 -2
  24. package/dist/connector/index.d.ts +0 -2
  25. package/dist/connector/index.js +5 -5
  26. package/dist/inbound/receipt.d.ts +1 -3
  27. package/dist/inbound/receipt.js +2 -2
  28. package/dist/inbound/turn-assembler.d.ts +1 -3
  29. package/dist/inbound/turn-assembler.js +2 -2
  30. package/dist/index.js +30 -22
  31. package/dist/instance-lock.js +4 -4
  32. package/dist/persistence/atomic-write.js +3 -3
  33. package/dist/platform/agent-stopper.d.ts +1 -1
  34. package/dist/platform/terminator-posix.js +5 -5
  35. package/dist/platform/terminator-win32.js +1 -1
  36. package/dist/router/card-helpers.d.ts +1 -3
  37. package/dist/router/card-helpers.js +2 -2
  38. package/dist/router/config/codex.js +1 -1
  39. package/dist/router/config/common/model-patch.d.ts +1 -2
  40. package/dist/router/config/common/model-patch.js +1 -2
  41. package/dist/router/config/opencode.js +1 -1
  42. package/dist/router/config/pi.js +1 -1
  43. package/dist/router/index.d.ts +22 -22
  44. package/dist/router/index.js +71 -52
  45. package/dist/router/order-exec-dispatch.d.ts +3 -10
  46. package/dist/router/order-exec-dispatch.js +3 -3
  47. package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
  48. package/dist/router/{utils.js → usage-format.js} +3 -8
  49. package/dist/runner/claude/runner.d.ts +1 -1
  50. package/dist/runner/claude/runner.js +1 -2
  51. package/dist/runner/claude/session.d.ts +2 -4
  52. package/dist/runner/claude/session.js +7 -9
  53. package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +2 -8
  54. package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +4 -3
  55. package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
  56. package/dist/runner/common/acp/base-acp-runner.js +1 -1
  57. package/dist/runner/common/connection-based-runner.d.ts +4 -4
  58. package/dist/runner/common/connection-based-runner.js +3 -3
  59. package/dist/runner/common/jsonrpc/connection-manager.d.ts +12 -12
  60. package/dist/runner/common/jsonrpc/connection-manager.js +36 -36
  61. package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
  62. package/dist/runner/common/{usage.js → result-usage.js} +2 -2
  63. package/dist/runner/common/spawning-runner.d.ts +2 -2
  64. package/dist/runner/common/spawning-runner.js +8 -8
  65. package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
  66. package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
  67. package/dist/runner/dsh/client.d.ts +1 -1
  68. package/dist/runner/dsh/client.js +1 -1
  69. package/dist/runner/dsh/runner.js +1 -1
  70. package/dist/runner/kimi/acp/runner.d.ts +1 -1
  71. package/dist/runner/kimi/acp/runner.js +1 -1
  72. package/dist/runner/kimi/acp/translator.js +1 -1
  73. package/dist/runner/opencode/acp/runner.d.ts +2 -2
  74. package/dist/runner/opencode/acp/runner.js +1 -1
  75. package/dist/runner/opencode/acp/translator.js +1 -1
  76. package/dist/runner/pi/rpc/runner.d.ts +1 -1
  77. package/dist/runner/pi/rpc/translator.d.ts +0 -1
  78. package/dist/runner/pi/rpc/translator.js +0 -2
  79. package/dist/runner/registry.d.ts +3 -3
  80. package/dist/runner/registry.js +3 -3
  81. package/dist/session/claude/session-index.d.ts +1 -1
  82. package/dist/session/claude/session-index.js +1 -1
  83. package/dist/session/claude/session-reader.d.ts +1 -1
  84. package/dist/session/claude/session-reader.js +1 -1
  85. package/dist/session/codex/rollout-reader.js +2 -2
  86. package/dist/session/dsh/sessions.d.ts +1 -1
  87. package/dist/session/dsh/sessions.js +1 -1
  88. package/dist/session/kimi/sessions.d.ts +0 -4
  89. package/dist/session/kimi/sessions.js +2 -8
  90. package/dist/session/opencode/sessions.js +3 -3
  91. package/dist/session/registry.d.ts +1 -1
  92. package/dist/session/registry.js +1 -1
  93. package/dist/session/session-store.d.ts +2 -3
  94. package/dist/session/session-store.js +2 -4
  95. package/dist/update/index.d.ts +1 -1
  96. package/dist/update/index.js +1 -1
  97. package/dist/update/version-check.d.ts +5 -3
  98. package/dist/update/version-check.js +10 -8
  99. package/dist/workspace/index.d.ts +4 -4
  100. package/dist/workspace/index.js +5 -5
  101. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.5] - 2026-10-07
9
+
10
+ ### 修复
11
+
12
+ - **消息不再漂移到新会话**:紧跟在文字消息之后点击「新会话」「切换工作目录」「继续会话」等会变更会话的卡片按钮时,此前消息可能落到新会话里执行。现在这类卡片动作会先等消息合并窗口落地(消息仍绑定旧会话/旧目录),再执行动作;同时入队时刻固定会话代际,堵住「已开新会话后又被旧消息把旧会话复活」的漏判
13
+
14
+ ### 变更
15
+
16
+ - **内部清理重构**:删除生产死代码与重复测试脚手架,统一内部命名,无行为变化
17
+
8
18
  ## [0.4.4] - 2026-10-04
9
19
 
10
20
  ### 修复
package/dist/binder.d.ts CHANGED
@@ -8,7 +8,7 @@ type BindDecision = {
8
8
  kind: 'bind_success';
9
9
  };
10
10
  /**
11
- * Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 openId 可用。
11
+ * Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 userId 可用。
12
12
  *
13
13
  * 设计依据:lark-remote 进程持有用户本机完整权限,唯一合法主体是 owner 本人。
14
14
  * 飞书自建应用的私聊入口本身就是 owner 掌控的(应用只有 owner 能看到/使用,
@@ -17,30 +17,31 @@ type BindDecision = {
17
17
  *
18
18
  * 状态机:
19
19
  * - 未绑定:第一条私聊消息(任意内容)即完成绑定 -> 写入 startup-contact.json。
20
- * - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
20
+ * - 已绑定:仅 userId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
21
21
  *
22
- * cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
22
+ * cardAction 同样校验 operator.openId(飞书 SDK 字段,值即 userId)=== bound.userId;
23
+ * 未绑定也视为非 owner。
23
24
  */
24
25
  export declare class OwnerBinder {
25
26
  private readonly store;
26
27
  private rejectedTotal;
27
28
  constructor(store: StartupContactStore);
28
29
  isBound(): boolean;
29
- /** 已绑定的 owner openId;未绑定时 undefined。 */
30
- boundOpenId(): string | undefined;
31
- /** 卡片操作者是否为已绑定的 owner(未绑定返回 false)。 */
32
- isOwner(openId: string): boolean;
30
+ /** 已绑定的 owner userId;未绑定时 undefined。 */
31
+ boundUserId(): string | undefined;
32
+ /** 用户是否为已绑定的 owner(未绑定返回 false)。 */
33
+ isOwner(userId: string): boolean;
33
34
  /** 累计被拒(非 owner)消息/卡片数,用于 DoS 可观测。 */
34
35
  get rejectedCount(): number;
35
36
  /**
36
37
  * 对入站私聊消息做绑定/授权判定,含副作用:
37
38
  * - `owner`:放行,调用方继续正常处理
38
39
  * - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
39
- * - `bind_success`:未绑定收到首条消息,写入绑定(任意内容均可)
40
+ * - `bind_success`:未绑定收到首条消息,写入绑定(任意消息均可)
40
41
  */
41
- classify(senderId: string, content: string, chatId: string): BindDecision;
42
+ authorize(userId: string, chatId: string): BindDecision;
42
43
  /** 卡片操作被拒时计数(与消息路径共用计数器)。 */
43
- recordRejectedCardAction(openId: string): void;
44
+ recordRejectedCardAction(userId: string): void;
44
45
  }
45
46
  /**
46
47
  * 控制台绑定引导文案(首次未绑定时输出到 stderr;守护模式下被 watchdog
package/dist/binder.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getLogger } from './logger/index.js';
2
2
  /**
3
- * Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 openId 可用。
3
+ * Owner 绑定器:首次私聊发送任意消息完成"认领",此后仅该 userId 可用。
4
4
  *
5
5
  * 设计依据:lark-remote 进程持有用户本机完整权限,唯一合法主体是 owner 本人。
6
6
  * 飞书自建应用的私聊入口本身就是 owner 掌控的(应用只有 owner 能看到/使用,
@@ -9,9 +9,10 @@ import { getLogger } from './logger/index.js';
9
9
  *
10
10
  * 状态机:
11
11
  * - 未绑定:第一条私聊消息(任意内容)即完成绑定 -> 写入 startup-contact.json。
12
- * - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
12
+ * - 已绑定:仅 userId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
13
13
  *
14
- * cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
14
+ * cardAction 同样校验 operator.openId(飞书 SDK 字段,值即 userId)=== bound.userId;
15
+ * 未绑定也视为非 owner。
15
16
  */
16
17
  export class OwnerBinder {
17
18
  store;
@@ -22,14 +23,14 @@ export class OwnerBinder {
22
23
  isBound() {
23
24
  return this.store.getContact() !== undefined;
24
25
  }
25
- /** 已绑定的 owner openId;未绑定时 undefined。 */
26
- boundOpenId() {
26
+ /** 已绑定的 owner userId;未绑定时 undefined。 */
27
+ boundUserId() {
27
28
  return this.store.getContact()?.userId;
28
29
  }
29
- /** 卡片操作者是否为已绑定的 owner(未绑定返回 false)。 */
30
- isOwner(openId) {
30
+ /** 用户是否为已绑定的 owner(未绑定返回 false)。 */
31
+ isOwner(userId) {
31
32
  const bound = this.store.getContact()?.userId;
32
- return bound !== undefined && bound === openId;
33
+ return bound !== undefined && bound === userId;
33
34
  }
34
35
  /** 累计被拒(非 owner)消息/卡片数,用于 DoS 可观测。 */
35
36
  get rejectedCount() {
@@ -39,26 +40,26 @@ export class OwnerBinder {
39
40
  * 对入站私聊消息做绑定/授权判定,含副作用:
40
41
  * - `owner`:放行,调用方继续正常处理
41
42
  * - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
42
- * - `bind_success`:未绑定收到首条消息,写入绑定(任意内容均可)
43
+ * - `bind_success`:未绑定收到首条消息,写入绑定(任意消息均可)
43
44
  */
44
- classify(senderId, content, chatId) {
45
+ authorize(userId, chatId) {
45
46
  const bound = this.store.getContact();
46
47
  if (bound) {
47
- if (senderId === bound.userId)
48
+ if (userId === bound.userId)
48
49
  return { kind: 'owner' };
49
50
  this.rejectedTotal++;
50
- getLogger().debug(`[binder] rejected message from ${senderId} (total rejected=${this.rejectedCount})`);
51
+ getLogger().debug(`[binder] rejected message from ${userId} (total rejected=${this.rejectedCount})`);
51
52
  return { kind: 'rejected' };
52
53
  }
53
54
  // 未绑定:任意内容即绑定(首条消息完成认领)
54
- this.store.save({ chatId, userId: senderId });
55
- getLogger().info(`[binder] owner bound: openId=${senderId} chatId=${chatId}`);
55
+ this.store.save({ chatId, userId });
56
+ getLogger().info(`[binder] owner bound: userId=${userId} chatId=${chatId}`);
56
57
  return { kind: 'bind_success' };
57
58
  }
58
59
  /** 卡片操作被拒时计数(与消息路径共用计数器)。 */
59
- recordRejectedCardAction(openId) {
60
+ recordRejectedCardAction(userId) {
60
61
  this.rejectedTotal++;
61
- getLogger().debug(`[binder] rejected card action from ${openId} (total rejected=${this.rejectedCount})`);
62
+ getLogger().debug(`[binder] rejected card action from ${userId} (total rejected=${this.rejectedCount})`);
62
63
  }
63
64
  }
64
65
  /**
@@ -1,38 +1,12 @@
1
1
  import { type AppConfig } from '../config/index.js';
2
2
  import type { MediaOutcome } from '../inbound/turn.js';
3
- import type { InboundResourceKind, InboundMediaItem, InboundMediaPayload } from '../connector/index.js';
3
+ import type { InboundMediaPayload } from '../connector/index.js';
4
4
  export interface InboundMediaDeps {
5
5
  /** 解析用户 cwd(bridge.resolveCwd 注入,统一三份复制点)。 */
6
6
  resolveCwd: (userId: string) => string | undefined;
7
7
  /** 读取当前配置(活引用,随 /config 保存更新,避免启动快照过期)。 */
8
8
  getConfig: () => AppConfig;
9
9
  }
10
- /**
11
- * 文件名 sanitize:
12
- * path.basename 剥离目录(防 `../` 穿越),再替换控制字符与路径分隔符;
13
- * 空名 / `.` / `..` 视为无效(返回空串由调用方生成兜底名)。
14
- */
15
- export declare function sanitizeFileName(name: string): string;
16
- /**
17
- * 资源扩展名(图片/视频/语音):优先 MIME 映射,未知时按魔数兜底;
18
- * 两者都无法识别时返回 undefined(调用方省略扩展名,避免错误标注格式)。
19
- */
20
- export declare function extensionFor(_kind: InboundResourceKind, mimeType: string | undefined, head: Buffer): string | undefined;
21
- /**
22
- * 文件名长度限制(字节级,保留扩展名),避免 ENAMETOOLONG 整文件失败。
23
- * 极罕见情况(扩展名本身超限)直接截断全名。
24
- */
25
- export declare function limitFileNameLength(name: string, maxBytes?: number): string;
26
- /**
27
- * 生成落盘文件名:
28
- * - image:`image_<HHmmss>_<n>.<ext>`(ext 按 MIME/魔数);
29
- * - 其它有原名的资源(file/video/audio/sticker):保留原名(已 sanitize);
30
- * - 无原名的资源:`<kind>_<HHmmss>_<n>.<ext>`,ext 按 MIME/魔数
31
- * (旧实现落成无扩展名的 `file_HHmmss_n`,mp4/opus/gif 都不可识别)。
32
- */
33
- export declare function buildFileName(item: InboundMediaItem, index: number, receivedAt: Date): string;
34
- /** 同名冲突自动加序号(name-1.ext、name-2.ext…),不覆盖已有文件。 */
35
- export declare function uniqueTargetPath(dir: string, fileName: string): string;
36
10
  export declare class InboundMediaHandler {
37
11
  private readonly deps;
38
12
  constructor(deps: InboundMediaDeps);
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { DEFAULT_INBOUND_MEDIA_DIR_NAME } from '../config/index.js';
4
4
  import { atomicMoveFile } from '../persistence/atomic-write.js';
5
5
  import { getLogger } from '../logger/index.js';
6
- import { silentlyUnlink } from '../common/fs.js';
6
+ import { bestEffortUnlink } from '../common/fs.js';
7
+ import { truncateUtf8 } from '../common/truncate.js';
7
8
  /**
8
9
  * 入站媒体落盘。
9
10
  *
@@ -52,12 +53,12 @@ function hasControlChar(s) {
52
53
  return false;
53
54
  }
54
55
  /** 子目录时间戳:YYYYMMDDHHmm(本地时间,精确到分钟)。 */
55
- function timeStampDir(d) {
56
+ function timestampDir(d) {
56
57
  return (`${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}` +
57
58
  `${pad2(d.getHours())}${pad2(d.getMinutes())}`);
58
59
  }
59
60
  /** 文件名时间戳:HHmmss(本地时间)。 */
60
- function timeStampHms(d) {
61
+ function timestampHms(d) {
61
62
  return `${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
62
63
  }
63
64
  /**
@@ -65,7 +66,7 @@ function timeStampHms(d) {
65
66
  * path.basename 剥离目录(防 `../` 穿越),再替换控制字符与路径分隔符;
66
67
  * 空名 / `.` / `..` 视为无效(返回空串由调用方生成兜底名)。
67
68
  */
68
- export function sanitizeFileName(name) {
69
+ function sanitizeFileName(name) {
69
70
  const base = path.basename(name).trim();
70
71
  const chars = [];
71
72
  for (const ch of base) {
@@ -88,7 +89,7 @@ export function sanitizeFileName(name) {
88
89
  * 资源扩展名(图片/视频/语音):优先 MIME 映射,未知时按魔数兜底;
89
90
  * 两者都无法识别时返回 undefined(调用方省略扩展名,避免错误标注格式)。
90
91
  */
91
- export function extensionFor(_kind, mimeType, head) {
92
+ function extensionFor(_kind, mimeType, head) {
92
93
  const normalized = mimeType?.split(';')[0]?.trim().toLowerCase();
93
94
  if (normalized && MIME_TO_EXT[normalized])
94
95
  return MIME_TO_EXT[normalized];
@@ -137,13 +138,11 @@ export function extensionFor(_kind, mimeType, head) {
137
138
  }
138
139
  return undefined;
139
140
  }
140
- /** 按 UTF-8 字节数截断字符串(避免截在多字节字符中间)。 */
141
- import { truncateUtf8 } from '../common/truncate.js';
142
141
  /**
143
142
  * 文件名长度限制(字节级,保留扩展名),避免 ENAMETOOLONG 整文件失败。
144
143
  * 极罕见情况(扩展名本身超限)直接截断全名。
145
144
  */
146
- export function limitFileNameLength(name, maxBytes = MAX_FILE_NAME_BYTES) {
145
+ function limitFileNameLength(name, maxBytes = MAX_FILE_NAME_BYTES) {
147
146
  if (Buffer.byteLength(name, 'utf8') <= maxBytes)
148
147
  return name;
149
148
  const ext = path.extname(name);
@@ -172,15 +171,15 @@ function readFileHead(filePath, maxBytes = MAGIC_HEAD_BYTES) {
172
171
  * - 无原名的资源:`<kind>_<HHmmss>_<n>.<ext>`,ext 按 MIME/魔数
173
172
  * (旧实现落成无扩展名的 `file_HHmmss_n`,mp4/opus/gif 都不可识别)。
174
173
  */
175
- export function buildFileName(item, index, receivedAt) {
174
+ function buildFileName(item, index, receivedAt) {
176
175
  const original = item.fileName ? limitFileNameLength(sanitizeFileName(item.fileName)) : '';
177
176
  if (item.kind !== 'image' && original)
178
177
  return original;
179
178
  const ext = extensionFor(item.kind, item.mimeType, readFileHead(item.tempPath));
180
- return `${item.kind}_${timeStampHms(receivedAt)}_${index}${ext ? `.${ext}` : ''}`;
179
+ return `${item.kind}_${timestampHms(receivedAt)}_${index}${ext ? `.${ext}` : ''}`;
181
180
  }
182
181
  /** 同名冲突自动加序号(name-1.ext、name-2.ext…),不覆盖已有文件。 */
183
- export function uniqueTargetPath(dir, fileName) {
182
+ function uniqueTargetPath(dir, fileName) {
184
183
  const ext = path.extname(fileName);
185
184
  const stem = fileName.slice(0, fileName.length - ext.length);
186
185
  let candidate = path.join(dir, fileName);
@@ -216,7 +215,7 @@ export class InboundMediaHandler {
216
215
  };
217
216
  }
218
217
  const receivedAt = new Date();
219
- const dir = path.join(cwd, this.safeDirName(), timeStampDir(receivedAt));
218
+ const dir = path.join(cwd, this.safeDirName(), timestampDir(receivedAt));
220
219
  try {
221
220
  fs.mkdirSync(dir, { recursive: true });
222
221
  }
@@ -254,7 +253,7 @@ export class InboundMediaHandler {
254
253
  });
255
254
  }
256
255
  catch (err) {
257
- silentlyUnlink(item.tempPath);
256
+ bestEffortUnlink(item.tempPath);
258
257
  const label = item.fileName
259
258
  ? sanitizeFileName(item.fileName) || item.fileName
260
259
  : `第 ${i + 1} 个`;
@@ -287,7 +286,7 @@ export class InboundMediaHandler {
287
286
  /** 清理未被移动的临时文件(无 cwd / 目录创建失败等提前返回路径)。 */
288
287
  cleanupTemps(payload) {
289
288
  for (const item of payload.media) {
290
- silentlyUnlink(item.tempPath);
289
+ bestEffortUnlink(item.tempPath);
291
290
  }
292
291
  }
293
292
  resolveCwd(userId) {
@@ -28,8 +28,8 @@ interface BridgeChannel extends RunCardChannel {
28
28
  addReaction(messageId: string, emoji: string): Promise<void>;
29
29
  removeReactionByEmoji(messageId: string, emoji: string): Promise<void>;
30
30
  }
31
- /** Caller context for a bridge operation. */
32
- interface BridgeContext {
31
+ /** Caller context shared by bridge operations and router command handling. */
32
+ export interface CommandContext {
33
33
  userId: string;
34
34
  chatId: string;
35
35
  messageId: string;
@@ -40,8 +40,8 @@ interface BridgeContext {
40
40
  */
41
41
  turnMessageIds?: string[];
42
42
  }
43
- /** A sendable result payload (mirror of the router's CommandResult shape). */
44
- interface BridgeResult {
43
+ /** A sendable result payload returned by a command handler. */
44
+ export interface CommandResult {
45
45
  text?: string;
46
46
  markdown?: string;
47
47
  card?: object;
@@ -74,7 +74,7 @@ interface BridgeDeps {
74
74
  /** Override the idle watchdog timeout (default 15 min). Tests use a small value. */
75
75
  idleTimeoutMs?: number;
76
76
  /**
77
- * Multi-agent registry. `getRunner(workspace)` looks up
77
+ * Multi-agent registry. `getRunner(cwd)` looks up
78
78
  * `config.defaultAgent` here.
79
79
  */
80
80
  agentRegistry: AgentRegistry;
@@ -91,10 +91,10 @@ interface BridgeDeps {
91
91
  *
92
92
  * The router delegates non-command messages here via `forwardToClaude` and
93
93
  * sends command results via `sendResult`. Serial processing is enforced per
94
- * workspace by `enqueue`: each workspace has its own Promise chain, so at
95
- * most one agent process runs per workspace at a time, while different
94
+ * cwd by `enqueue`: each cwd has its own Promise chain, so at
95
+ * most one agent process runs per cwd at a time, while different
96
96
  * workspaces run in parallel (see `activeRuns`, §9.6). This is a load-bearing
97
- * invariant — concurrent spawns within one workspace would corrupt session
97
+ * invariant — concurrent spawns within one cwd would corrupt session
98
98
  * state. Most card actions go through `enqueueImmediate` (fire-and-forget,
99
99
  * outside the serial chain); only run-forwarding is serialized.
100
100
  *
@@ -128,7 +128,7 @@ export declare class Bridge {
128
128
  private runners;
129
129
  /**
130
130
  * Runner 槽位在「活跃运行期间配置变更」时被标 stale(CC-06/P1):clearRunners() 对
131
- * 活跃 workspace 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
131
+ * 活跃 cwd 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
132
132
  * finalizeRun 安全 evict+dispose,下一轮 getRunner 创建新配置的 runner。
133
133
  * key 为 `${cwd}\u0000${agentKind}`。
134
134
  */
@@ -136,7 +136,7 @@ export declare class Bridge {
136
136
  /** Queue manager for per-workspace serial processing queue. */
137
137
  /** 普通消息队列管理器(public:测试注入 queue-card 状态用)。 */
138
138
  queueManager: QueueManager;
139
- /** Active runs per workspace (cwd). Multiple workspaces can run in parallel. */
139
+ /** Active runs per cwd (cwd). Multiple workspaces can run in parallel. */
140
140
  private activeRuns;
141
141
  /** Approval coordinators keyed by runId. */
142
142
  private approvalCoordinators;
@@ -144,17 +144,17 @@ export declare class Bridge {
144
144
  * 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
145
145
  * activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
146
146
  */
147
- private lastCompactableCodexRun;
147
+ private lastCompactableRun;
148
148
  /**
149
149
  * Active `!` bash runs, keyed by runId (NOT cwd). Bash runs bypass the serial
150
150
  * queue and may run in parallel with claude runs / other bash runs in the same
151
- * workspace, so they live outside `activeRuns`. Tracked so `/stop` can reach
151
+ * cwd, so they live outside `activeRuns`. Tracked so `/stop` can reach
152
152
  * the per-run BashRunner instance via interruptCurrentRun.
153
153
  */
154
154
  private activeBashRuns;
155
155
  constructor(deps: BridgeDeps);
156
156
  /**
157
- * Get or create a runner for the given workspace, keyed by current defaultAgent.
157
+ * Get or create a runner for the given cwd, keyed by current defaultAgent.
158
158
  *
159
159
  * Fix 4 (2026-07-18): Cache key includes agentKind, so switching defaultAgent
160
160
  * creates a NEW runner under a different key without evicting the old one.
@@ -174,14 +174,14 @@ export declare class Bridge {
174
174
  syncActiveApprovalModes(): void;
175
175
  /** Per-agent approval-mode settings snapshot from the current config. */
176
176
  private approvalModeSettingsFor;
177
- /** Get the current runner for a workspace. Used by router for agent-specific commands (P3.2). */
178
- getCurrentRunner(workspace: string): AgentRunner;
177
+ /** Get the current runner for a cwd. Used by router for agent-specific commands (P3.2). */
178
+ getCurrentRunner(cwd: string): AgentRunner;
179
179
  /**
180
180
  * Whether the runner for the given agent kind has runCompact capability.
181
181
  * Used by router for compact button gating (design doc §6.2-2: duck-typing
182
182
  * instead of hardcoding agentKind === 'codex').
183
183
  */
184
- hasRunCompact(workspace: string, kind?: AgentKind): boolean;
184
+ hasRunCompact(cwd: string, kind?: AgentKind): boolean;
185
185
  /** 入队时刻快照:当前 defaultAgent + 该 agent 的 sessionId(无 session 则 undefined)。
186
186
  * 唯一捕获点:排队消息在 T0 把 agent+session 钉进 AgentBinding,随任务闭包带到 T1
187
187
  * 执行时刻,避免 /new、/config 在排队期间改写 live 状态导致语义漂移(方案 D4)。 */
@@ -221,36 +221,36 @@ export declare class Bridge {
221
221
  * Enqueue a task into the workspace-level serial bridge queue.
222
222
  * Delegates to QueueManager for actual implementation.
223
223
  */
224
- enqueue(workspace: string, task: () => Promise<void>, opts?: EnqueueOptions): void;
224
+ enqueue(cwd: string, task: () => Promise<void>, opts?: EnqueueOptions): void;
225
225
  /**
226
226
  * Execute a task immediately without going through the queue.
227
227
  * Used for / commands that should respond immediately.
228
228
  */
229
- enqueueImmediate(workspace: string, task: () => Promise<void>): void;
230
- /** Get queue info for a workspace. */
231
- getQueueInfo(workspace: string): {
229
+ enqueueImmediate(cwd: string, task: () => Promise<void>): void;
230
+ /** Get queue info for a cwd. */
231
+ getQueueInfo(cwd: string): {
232
232
  position: number;
233
233
  tasksAhead: number;
234
234
  isRunning: boolean;
235
235
  };
236
236
  /** Remove a task from the queue by messageId. Returns true if found and removed. */
237
- removeFromQueue(workspace: string, messageId: string): boolean;
237
+ removeFromQueue(cwd: string, messageId: string): boolean;
238
238
  /**
239
239
  * One-shot: replace the execution closure of an existing queued task in
240
240
  * place, preserving its queue position. Used by queue.immediate after the
241
241
  * user edits a queued message (see router handleQueueImmediate).
242
242
  */
243
- setTaskReplacement(workspace: string, messageId: string, task: () => Promise<void>): void;
243
+ setTaskReplacement(cwd: string, messageId: string, task: () => Promise<void>): void;
244
244
  /** Update queue card to "cancelled" status when user clicks 撤销. */
245
- updateQueueCardToCancelled(workspace: string, messageId: string): Promise<void>;
245
+ updateQueueCardToCancelled(cwd: string, messageId: string): Promise<void>;
246
246
  /** Mark a queued task's card as executing immediately (grey out buttons).
247
247
  * Called by handleQueueImmediate so the user sees feedback right away
248
248
  * instead of waiting for the queue callback. Idempotent: the queue
249
249
  * callback's own updateQueueCardToExecuting call becomes a no-op because
250
250
  * updateQueueCardToExecuting deletes the card-message mapping in finally. */
251
- markQueueCardExecuting(workspace: string, messageId: string): Promise<void>;
251
+ markQueueCardExecuting(cwd: string, messageId: string): Promise<void>;
252
252
  /** Get task metadata from queue. */
253
- getQueuedTask(workspace: string, messageId: string): QueuedTask | undefined;
253
+ getQueuedTask(cwd: string, messageId: string): QueuedTask | undefined;
254
254
  /**
255
255
  * Whether a task with the given messageId has begun executing and has not
256
256
  * settled yet. Lets handleQueueImmediate's final feedback distinguish a
@@ -259,8 +259,8 @@ export declare class Bridge {
259
259
  * "未安排执行").
260
260
  */
261
261
  hasTaskBegan(messageId: string): boolean;
262
- /** Get all queued tasks for a workspace. Returns a copy to prevent aliasing bugs. */
263
- getQueuedTasks(workspace: string): QueuedTask[];
262
+ /** Get all queued tasks for a cwd. Returns a copy to prevent aliasing bugs. */
263
+ getQueuedTasks(cwd: string): QueuedTask[];
264
264
  /**
265
265
  * Update the messagePreview for a queued task and build the updated queue
266
266
  * card. Returns the card object to send back as the cardAction callback
@@ -268,11 +268,11 @@ export declare class Bridge {
268
268
  * queued. Returning the card via the callback response avoids the
269
269
  * PATCH-vs-callback-response race that left the card stuck in edit state.
270
270
  */
271
- updateMessagePreview(workspace: string, messageId: string, newMessage: string): Promise<object | null>;
272
- /** Whether a claude run is currently in progress in the given workspace. */
273
- isBusyFor(workspace: string): boolean;
274
- getActiveRunFor(workspace: string): ActiveRunSnapshot | undefined;
275
- /** Whether any claude run is currently in progress (any workspace). */
271
+ updateMessagePreview(cwd: string, messageId: string, newMessage: string): Promise<object | null>;
272
+ /** Whether a claude run is currently in progress in the given cwd. */
273
+ isBusyFor(cwd: string): boolean;
274
+ getActiveRunFor(cwd: string): ActiveRunSnapshot | undefined;
275
+ /** Whether any claude run is currently in progress (any cwd). */
276
276
  get isBusy(): boolean;
277
277
  /** Get all active runs across all workspaces. */
278
278
  getAllActiveRuns(): Map<string, {
@@ -303,12 +303,12 @@ export declare class Bridge {
303
303
  userId: string;
304
304
  chatId: string;
305
305
  runId?: string;
306
- /** Restrict the match to a single workspace (queue.immediate). When
307
- * omitted, all workspaces are candidates (used by /stop, /t, and card stop button which don't know which workspace to target). */
308
- workspace?: string;
306
+ /** Restrict the match to a single cwd (queue.immediate). When
307
+ * omitted, all workspaces are candidates (used by /stop, /t, and card stop button which don't know which cwd to target). */
308
+ cwd?: string;
309
309
  }): Promise<boolean>;
310
310
  /** Send a result message to the user (§7). Returns true on success, false on failure. */
311
- sendResult(result: BridgeResult, ctx: BridgeContext): Promise<boolean>;
311
+ sendResult(result: CommandResult, ctx: CommandContext): Promise<boolean>;
312
312
  /**
313
313
  * Update an existing card in place (原地更新) instead of sending a new one.
314
314
  *
@@ -322,9 +322,9 @@ export declare class Bridge {
322
322
  *
323
323
  * Returns true if the in-place update succeeded, false if it fell back.
324
324
  */
325
- updateCardInPlace(card: object, ctx: BridgeContext): Promise<boolean>;
325
+ updateCardInPlace(card: object, ctx: CommandContext): Promise<boolean>;
326
326
  /** Send a file to the user via Feishu. */
327
- sendFile(filePath: string, ctx: BridgeContext): Promise<void>;
327
+ sendFile(filePath: string, ctx: CommandContext): Promise<void>;
328
328
  /**
329
329
  * 入站媒体落盘(connector 下载完成后调用):只落盘并回报结果,
330
330
  * 时间语义与回执由 `InboundTurnAssembler` 统一负责。
@@ -332,7 +332,7 @@ export declare class Bridge {
332
332
  saveInboundMedia(payload: InboundMediaPayload): Promise<MediaOutcome>;
333
333
  /**
334
334
  * Resolve the working directory for a user.
335
- * First checks sessionStore (set by /cd), then falls back to first workspace.
335
+ * First checks sessionStore (set by /cd), then falls back to first cwd.
336
336
  * Returns undefined if no cwd is available.
337
337
  */
338
338
  private resolveCwd;
@@ -344,7 +344,7 @@ export declare class Bridge {
344
344
  * work queue. Session is synced from `system.init`; every renderable event
345
345
  * updates the same run card, and terminal events finalize that card.
346
346
  */
347
- forwardToClaude(message: string, ctx: BridgeContext, opts?: {
347
+ forwardToClaude(message: string, ctx: CommandContext, opts?: {
348
348
  cwdOverride?: string;
349
349
  binding?: AgentBinding;
350
350
  }): Promise<void>;
@@ -466,14 +466,14 @@ export declare class Bridge {
466
466
  nonce: string;
467
467
  }): Promise<void>;
468
468
  /**
469
- * Handle codex.compact card action — trigger a compaction request for any
469
+ * Handle compact card action — trigger a compaction request for any
470
470
  * runCompact-capable runner(codex/kimi/opencode/pi/claude,按
471
- * lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
471
+ * lastCompactableRun 记录的 agentKind 路由)。Validates the run exists
472
472
  * and is in a terminal state, then calls the runner's runCompact().
473
473
  */
474
- handleCodexCompact(value: {
474
+ handleCompactAction(value: {
475
475
  runId?: string;
476
- }, ctx: BridgeContext): Promise<void>;
476
+ }, ctx: CommandContext): Promise<void>;
477
477
  /**
478
478
  * §5.3: kimi 压缩在途检测——reader 鸭子实现 `readCompactionState` 时,若
479
479
  * wire 状态机判定当前有未终结的压缩(最后一次 begin 无 terminal 记录),
@@ -485,18 +485,18 @@ export declare class Bridge {
485
485
  * Stream a compaction to a card: start a RunCardSession with
486
486
  * operationKind='compaction' (no recursive Compact button), consume
487
487
  * runner.runCompact() events, read authoritative jsonl usage, and finish
488
- * the card. Shared by handleCodexCompact (run card button) and
488
+ * the card. Shared by handleCompactAction (run card button) and
489
489
  * handleResumeCompact (resume cards) so both flows stay in sync.
490
490
  *
491
491
  * Precondition: callers have already validated that `runner` implements
492
492
  * runCompact and that the session exists in `cwd`.
493
493
  */
494
- private streamCodexCompact;
494
+ private streamCompact;
495
495
  /**
496
496
  * Handle resume.compact card action — compact a session directly from a
497
497
  * resume card (auto-resume / `/resume <id>`), without a runId.
498
498
  *
499
- * Unlike handleCodexCompact (which validates against the last finished run's
499
+ * Unlike handleCompactAction (which validates against the last finished run's
500
500
  * runId), the resume card carries the sessionId + agent it was rendered for.
501
501
  * Validation: the session must exist in the current cwd (same rule as
502
502
  * cmdResume / resume.use) and the runner must implement runCompact.
@@ -504,14 +504,14 @@ export declare class Bridge {
504
504
  handleResumeCompact(value: {
505
505
  sessionId?: string;
506
506
  agent?: string;
507
- }, ctx: BridgeContext): Promise<void>;
507
+ }, ctx: CommandContext): Promise<void>;
508
508
  /**
509
509
  * Execute a bash command and stream output to a card.
510
510
  * `!` commands bypass the serial queue entirely (see body comment + §9.6):
511
511
  * bash runs in parallel with same-workspace agent runs, tracked in
512
512
  * `activeBashRuns` separately from `activeRuns`.
513
513
  */
514
- executeBash(command: string, ctx: BridgeContext): Promise<void>;
514
+ executeBash(command: string, ctx: CommandContext): Promise<void>;
515
515
  /**
516
516
  * Internal execution of bash command (called directly from executeBash,
517
517
  * bypassing the serial queue — see executeBash).