lark-remote 0.4.4 → 0.5.0

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 (170) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/binder.d.ts +11 -10
  3. package/dist/binder.js +17 -16
  4. package/dist/bridge/approval-coordinator.d.ts +1 -1
  5. package/dist/bridge/approval-coordinator.js +3 -3
  6. package/dist/bridge/inbound-media.d.ts +1 -27
  7. package/dist/bridge/inbound-media.js +23 -21
  8. package/dist/bridge/index.d.ts +57 -57
  9. package/dist/bridge/index.js +136 -129
  10. package/dist/bridge/queue-manager.d.ts +45 -38
  11. package/dist/bridge/queue-manager.js +136 -136
  12. package/dist/card/approval-render.d.ts +1 -1
  13. package/dist/card/approval-render.js +6 -6
  14. package/dist/card/bash-card-session.d.ts +3 -3
  15. package/dist/card/bash-card-session.js +3 -3
  16. package/dist/card/bash-renderer.js +5 -5
  17. package/dist/card/card-session.d.ts +4 -4
  18. package/dist/card/card-session.js +6 -6
  19. package/dist/card/card-shared.d.ts +1 -1
  20. package/dist/card/card-shared.js +2 -2
  21. package/dist/card/run-card-session.js +2 -2
  22. package/dist/card/run-renderer.d.ts +1 -1
  23. package/dist/card/run-renderer.js +22 -22
  24. package/dist/card/run-state.d.ts +2 -2
  25. package/dist/card/run-state.js +3 -3
  26. package/dist/card/tool-render.d.ts +1 -1
  27. package/dist/card/tool-render.js +6 -6
  28. package/dist/cli.js +5 -6
  29. package/dist/clone.d.ts +6 -9
  30. package/dist/clone.js +12 -8
  31. package/dist/common/clamp.d.ts +2 -0
  32. package/dist/common/clamp.js +5 -0
  33. package/dist/common/fs.d.ts +4 -3
  34. package/dist/common/fs.js +5 -4
  35. package/dist/common/truncate.d.ts +1 -1
  36. package/dist/common/truncate.js +2 -2
  37. package/dist/common/ttl-cache.d.ts +2 -2
  38. package/dist/common/ttl-cache.js +2 -2
  39. package/dist/config/codex-config.d.ts +9 -9
  40. package/dist/config/codex-config.js +18 -18
  41. package/dist/config/dir.d.ts +4 -5
  42. package/dist/config/dir.js +12 -10
  43. package/dist/config/index.d.ts +3 -3
  44. package/dist/config/index.js +6 -6
  45. package/dist/config/opencode-config.js +3 -3
  46. package/dist/config/wizard.js +2 -2
  47. package/dist/connector/index.d.ts +5 -7
  48. package/dist/connector/index.js +29 -16
  49. package/dist/inbound/event.d.ts +1 -1
  50. package/dist/inbound/event.js +1 -1
  51. package/dist/inbound/placeholder.d.ts +2 -2
  52. package/dist/inbound/placeholder.js +2 -2
  53. package/dist/inbound/receipt.d.ts +1 -3
  54. package/dist/inbound/receipt.js +2 -2
  55. package/dist/inbound/turn-assembler.d.ts +1 -3
  56. package/dist/inbound/turn-assembler.js +2 -2
  57. package/dist/index.js +38 -30
  58. package/dist/instance-lock.js +8 -7
  59. package/dist/logger/index.d.ts +1 -1
  60. package/dist/logger/index.js +4 -4
  61. package/dist/persistence/atomic-write.d.ts +1 -1
  62. package/dist/persistence/atomic-write.js +9 -5
  63. package/dist/platform/agent-stopper.d.ts +2 -2
  64. package/dist/platform/identity.d.ts +7 -1
  65. package/dist/platform/identity.js +2 -2
  66. package/dist/platform/path.d.ts +6 -0
  67. package/dist/platform/path.js +14 -8
  68. package/dist/platform/select.d.ts +1 -1
  69. package/dist/platform/select.js +1 -1
  70. package/dist/platform/shell.d.ts +2 -3
  71. package/dist/platform/spawn.d.ts +2 -2
  72. package/dist/platform/spawn.js +2 -2
  73. package/dist/platform/terminator-posix.js +6 -6
  74. package/dist/platform/terminator-win32.js +3 -3
  75. package/dist/platform/terminator.d.ts +3 -3
  76. package/dist/platform/terminator.js +2 -2
  77. package/dist/platform/types.d.ts +1 -1
  78. package/dist/restart.d.ts +1 -1
  79. package/dist/restart.js +5 -3
  80. package/dist/router/card-helpers.d.ts +1 -3
  81. package/dist/router/card-helpers.js +2 -2
  82. package/dist/router/config/codex.d.ts +1 -1
  83. package/dist/router/config/codex.js +3 -3
  84. package/dist/router/config/common/model-patch.d.ts +1 -2
  85. package/dist/router/config/common/model-patch.js +1 -2
  86. package/dist/router/config/opencode.js +2 -2
  87. package/dist/router/config/pi.js +1 -1
  88. package/dist/router/index.d.ts +30 -30
  89. package/dist/router/index.js +103 -84
  90. package/dist/router/order-exec-dispatch.d.ts +3 -10
  91. package/dist/router/order-exec-dispatch.js +5 -5
  92. package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
  93. package/dist/router/{utils.js → usage-format.js} +3 -8
  94. package/dist/runner/bash/runner.d.ts +2 -2
  95. package/dist/runner/bash/runner.js +6 -6
  96. package/dist/runner/claude/runner.d.ts +1 -1
  97. package/dist/runner/claude/runner.js +2 -3
  98. package/dist/runner/claude/session.d.ts +4 -6
  99. package/dist/runner/claude/session.js +21 -23
  100. package/dist/runner/codex/app-server/runner.js +3 -3
  101. package/dist/runner/codex/app-server/translator.d.ts +2 -2
  102. package/dist/runner/codex/app-server/translator.js +5 -5
  103. package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +4 -10
  104. package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +7 -6
  105. package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
  106. package/dist/runner/common/acp/base-acp-runner.js +2 -2
  107. package/dist/runner/common/acp/base-translator.d.ts +3 -3
  108. package/dist/runner/common/acp/base-translator.js +4 -4
  109. package/dist/runner/common/acp/protocol-types.js +1 -1
  110. package/dist/runner/common/connection-based-runner.d.ts +5 -5
  111. package/dist/runner/common/connection-based-runner.js +5 -5
  112. package/dist/runner/common/jsonl-stream.d.ts +2 -2
  113. package/dist/runner/common/jsonl-stream.js +12 -12
  114. package/dist/runner/common/jsonrpc/client.d.ts +1 -1
  115. package/dist/runner/common/jsonrpc/client.js +1 -1
  116. package/dist/runner/common/jsonrpc/connection-manager.d.ts +13 -13
  117. package/dist/runner/common/jsonrpc/connection-manager.js +37 -37
  118. package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
  119. package/dist/runner/common/jsonrpc/transport.js +2 -2
  120. package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
  121. package/dist/runner/common/{usage.js → result-usage.js} +2 -2
  122. package/dist/runner/common/spawning-runner.d.ts +23 -10
  123. package/dist/runner/common/spawning-runner.js +48 -22
  124. package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
  125. package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
  126. package/dist/runner/dsh/client.d.ts +1 -1
  127. package/dist/runner/dsh/client.js +1 -1
  128. package/dist/runner/dsh/runner.js +3 -3
  129. package/dist/runner/kimi/acp/runner.d.ts +4 -4
  130. package/dist/runner/kimi/acp/runner.js +10 -10
  131. package/dist/runner/kimi/acp/translator.d.ts +2 -2
  132. package/dist/runner/kimi/acp/translator.js +5 -5
  133. package/dist/runner/opencode/acp/runner.d.ts +4 -4
  134. package/dist/runner/opencode/acp/runner.js +4 -4
  135. package/dist/runner/opencode/acp/translator.js +2 -2
  136. package/dist/runner/pi/rpc/runner.d.ts +1 -1
  137. package/dist/runner/pi/rpc/runner.js +2 -2
  138. package/dist/runner/pi/rpc/translator.d.ts +1 -2
  139. package/dist/runner/pi/rpc/translator.js +4 -6
  140. package/dist/runner/registry.d.ts +3 -3
  141. package/dist/runner/registry.js +3 -3
  142. package/dist/runner/types.d.ts +1 -1
  143. package/dist/session/claude/session-index.d.ts +13 -1
  144. package/dist/session/claude/session-index.js +21 -6
  145. package/dist/session/claude/session-reader.d.ts +1 -1
  146. package/dist/session/claude/session-reader.js +1 -1
  147. package/dist/session/claude/sessions.js +10 -10
  148. package/dist/session/codex/rollout-reader.d.ts +4 -4
  149. package/dist/session/codex/rollout-reader.js +14 -14
  150. package/dist/session/common/two-pass.js +2 -2
  151. package/dist/session/common/usage-accumulator.d.ts +2 -2
  152. package/dist/session/common/usage-accumulator.js +2 -2
  153. package/dist/session/dsh/sessions.d.ts +1 -1
  154. package/dist/session/dsh/sessions.js +3 -3
  155. package/dist/session/kimi/sessions.d.ts +2 -6
  156. package/dist/session/kimi/sessions.js +13 -19
  157. package/dist/session/opencode/sessions.d.ts +3 -3
  158. package/dist/session/opencode/sessions.js +15 -13
  159. package/dist/session/pi/sessions.js +13 -12
  160. package/dist/session/registry.d.ts +1 -1
  161. package/dist/session/registry.js +1 -1
  162. package/dist/session/session-store.d.ts +3 -4
  163. package/dist/session/session-store.js +3 -5
  164. package/dist/update/index.d.ts +1 -1
  165. package/dist/update/index.js +1 -1
  166. package/dist/update/version-check.d.ts +5 -3
  167. package/dist/update/version-check.js +10 -8
  168. package/dist/workspace/index.d.ts +4 -4
  169. package/dist/workspace/index.js +5 -5
  170. package/package.json +1 -1
@@ -22,7 +22,7 @@
22
22
  import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
23
23
  import { OpencodeAcpTranslator } from './translator.js';
24
24
  import { RpcErrorCode, } from '../../common/acp/protocol-types.js';
25
- import { OPENCODE_APPROVAL_KINDS, buildAcpPermissionOutcome, } from '../../common/acp/protocol-helpers.js';
25
+ import { OPENCODE_APPROVAL_KINDS, buildAcpPermissionOutcome, } from '../../common/acp/acp-approval.js';
26
26
  import { getLogger } from '../../../logger/index.js';
27
27
  import { BaseAcpRunner } from '../../common/acp/base-acp-runner.js';
28
28
  import { OpencodeLogErrorMonitor, resolveOpencodeLogPath } from './error-monitor.js';
@@ -30,7 +30,7 @@ import { OpencodeLogErrorMonitor, resolveOpencodeLogPath } from './error-monitor
30
30
  // Runner
31
31
  // =============================================================================
32
32
  export class OpencodeAcpRunner extends BaseAcpRunner {
33
- /** Configured session mode applied via session/set_mode (§P5). */
33
+ /** Configured session mode applied via session/set_mode. */
34
34
  configuredMode;
35
35
  /** Current session mode id, parsed from session/new|resume configOptions. */
36
36
  currentModeId;
@@ -103,7 +103,7 @@ export class OpencodeAcpRunner extends BaseAcpRunner {
103
103
  return new OpencodeAcpTranslator();
104
104
  }
105
105
  async applyTurnSettings(client, sessionId) {
106
- // §P5: apply the configured mode (build/plan). If the session already
106
+ // apply the configured mode (build/plan). If the session already
107
107
  // runs it (from session/new|resume configOptions), skip the wire call;
108
108
  // otherwise send session/set_mode and refresh the local view (opencode
109
109
  // sends no notification for set_mode).
@@ -160,7 +160,7 @@ export class OpencodeAcpRunner extends BaseAcpRunner {
160
160
  };
161
161
  }
162
162
  /**
163
- * Hot-apply a mode change to the live ACP session (§P5).
163
+ * Hot-apply a mode change to the live ACP session.
164
164
  *
165
165
  * opencode's session/set_mode sends no notification — the client refreshes
166
166
  * the local mode view itself. The local mode is always updated so
@@ -24,7 +24,7 @@
24
24
  * prompt other stopReason/error → result error
25
25
  * session/request_permission → approval_requested (optionId echo)
26
26
  */
27
- import { deriveAcpAvailableDecisions, truncateWithEllipsis, } from '../../common/acp/protocol-helpers.js';
27
+ import { deriveAcpAvailableDecisions, truncateWithEllipsis, } from '../../common/acp/acp-approval.js';
28
28
  import { getLogger } from '../../../logger/index.js';
29
29
  import { BaseAcpTranslator } from '../../common/acp/base-translator.js';
30
30
  // =============================================================================
@@ -176,7 +176,7 @@ export class OpencodeAcpTranslator extends BaseAcpTranslator {
176
176
  kind: 'command',
177
177
  command: params.toolCall.title ?? undefined,
178
178
  reason: rawInput ? truncateWithEllipsis(stringify(rawInput), 200) : undefined,
179
- // §P4: 从服务端 options kind 派生——带 allow_always(或 approve_always)
179
+ // 从服务端 options kind 派生——带 allow_always(或 approve_always)
180
180
  // 才提供「本会话总是允许」(acceptForSession)。
181
181
  availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
182
182
  };
@@ -30,7 +30,7 @@ export interface PiRpcRunnerOptions {
30
30
  thinking?: string;
31
31
  /** Tool allowlist. */
32
32
  tools?: string[];
33
- workspace: string;
33
+ cwd: string;
34
34
  sessionReader: AgentSessionReader;
35
35
  /** Path to the pi binary. Defaults to `pi`. */
36
36
  binary?: string;
@@ -53,7 +53,7 @@ export class PiRpcRunner extends ConnectionBasedRunner {
53
53
  idleTtlMs: opts.idleTtlMs,
54
54
  logTag: 'pi-rpc-connection-manager',
55
55
  });
56
- // 协议停止通道(design §3.3):pi 的 cancel 是 `{type:'abort'}` 通知,已由
56
+ // 协议停止通道:pi 的 cancel 是 `{type:'abort'}` 通知,已由
57
57
  // cancelCurrentTurn 实现;登记后 win32 优雅停止才有通道可用(否则一路树杀)。
58
58
  this.manager.stopper = ({ client }) => this.buildCooperativeStop(client);
59
59
  }
@@ -139,7 +139,7 @@ export class PiRpcRunner extends ConnectionBasedRunner {
139
139
  this.promptSettled = false;
140
140
  const response = await client.request({ type: 'prompt', message });
141
141
  if (!response.success) {
142
- // CC-08: pi 返回 success:false(无效模型/provider 错误/忙)时立即失败,
142
+ // pi 返回 success:false(无效模型/provider 错误/忙)时立即失败,
143
143
  // 不能当成成功 ACK 继续等 agent_settled(否则最长 turn idle timeout)。
144
144
  throw new Error(`pi prompt failed: ${response.error ?? 'unknown error'}`);
145
145
  }
@@ -25,7 +25,6 @@ export declare class PiRpcTranslator {
25
25
  private currentContentIndex;
26
26
  private sessionId;
27
27
  private operationKind;
28
- private currentTurnId;
29
28
  private accInput;
30
29
  private accOutput;
31
30
  private accCacheRead;
@@ -33,7 +32,7 @@ export declare class PiRpcTranslator {
33
32
  private accReasoning;
34
33
  private lastTotalTokens;
35
34
  private hasUsage;
36
- /** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use(C3.4)。 */
35
+ /** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use。 */
37
36
  private pendingToolCall;
38
37
  /** Provider failure (stopReason="error") on the final assistant message. */
39
38
  private lastAssistantStopReason;
@@ -15,7 +15,6 @@ export class PiRpcTranslator {
15
15
  currentContentIndex = -1;
16
16
  sessionId = '';
17
17
  operationKind = 'turn';
18
- currentTurnId = '';
19
18
  // ccusage-aligned usage accumulation (per-message, summed across the turn).
20
19
  accInput = 0;
21
20
  accOutput = 0;
@@ -24,7 +23,7 @@ export class PiRpcTranslator {
24
23
  accReasoning = 0;
25
24
  lastTotalTokens;
26
25
  hasUsage = false;
27
- /** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use(C3.4)。 */
26
+ /** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use。 */
28
27
  pendingToolCall = false;
29
28
  /** Provider failure (stopReason="error") on the final assistant message. */
30
29
  lastAssistantStopReason;
@@ -99,7 +98,7 @@ export class PiRpcTranslator {
99
98
  return [];
100
99
  }
101
100
  case 'compaction_start': {
102
- // 信息保真 C3.4:压缩过程用户可见(info 通知),不再静默。
101
+ // 信息保真 压缩过程用户可见(info 通知),不再静默。
103
102
  const c = evt;
104
103
  return [
105
104
  {
@@ -126,7 +125,7 @@ export class PiRpcTranslator {
126
125
  }
127
126
  default:
128
127
  // turn_start/turn_end, agent_start/agent_end, tool_execution_*, and
129
- // unknown types are ignored. (compaction_start/end 自 C3.4 起映射为
128
+ // unknown types are ignored. (compaction_start/end 映射为
130
129
  // notice 事件,不再静默。)
131
130
  return [];
132
131
  }
@@ -134,7 +133,6 @@ export class PiRpcTranslator {
134
133
  /** Self-produced turn_started (no wire equivalent). */
135
134
  produceTurnStarted(sessionId, turnId) {
136
135
  this.sessionId = sessionId;
137
- this.currentTurnId = turnId;
138
136
  return {
139
137
  type: 'turn_started',
140
138
  threadId: sessionId,
@@ -271,7 +269,7 @@ export class PiRpcTranslator {
271
269
  }
272
270
  break;
273
271
  case 'toolcall_start':
274
- // 信息保真 C3.4:不再 push 空壳 tool_use(空 id/name 会被渲染成
272
+ // 信息保真 不再 push 空壳 tool_use(空 id/name 会被渲染成
275
273
  // 无意义工具块)。只保留 bookkeeping,toolcall_end 补发完整事件。
276
274
  this.pendingToolCall = true;
277
275
  break;
@@ -1,7 +1,7 @@
1
1
  import type { AgentKind, Runner } from './index.js';
2
2
  /**
3
3
  * Registry of agent factories, keyed by `AgentKind`. `index.ts` registers one
4
- * factory per available agent at startup; `Bridge.getRunner(workspace)` looks
4
+ * factory per available agent at startup; `Bridge.getRunner(cwd)` looks
5
5
  * up `config.defaultAgent` here to pick the concrete runner.
6
6
  *
7
7
  * Singleton caching: the factory MAY cache its instance internally. All
@@ -17,7 +17,7 @@ export declare class AgentRegistry {
17
17
  private readonly displayNames;
18
18
  /** Container for runtime config updates (pi provider dynamic reload). */
19
19
  private configContainer?;
20
- register(kind: AgentKind, factory: (workspace: string) => Runner): void;
20
+ register(kind: AgentKind, factory: (cwd: string) => Runner): void;
21
21
  /**
22
22
  * 注册 agent 的显示名。应在 register(kind, factory) 后调用。
23
23
  * 用于 agentDisplayName 等全局查询,实现单点真相。
@@ -50,5 +50,5 @@ export declare class AgentRegistry {
50
50
  getConfigContainer(): {
51
51
  current: unknown;
52
52
  } | undefined;
53
- get(kind: AgentKind, workspace: string): Runner;
53
+ get(kind: AgentKind, cwd: string): Runner;
54
54
  }
@@ -5,7 +5,7 @@
5
5
  let globalRegistry;
6
6
  /**
7
7
  * Registry of agent factories, keyed by `AgentKind`. `index.ts` registers one
8
- * factory per available agent at startup; `Bridge.getRunner(workspace)` looks
8
+ * factory per available agent at startup; `Bridge.getRunner(cwd)` looks
9
9
  * up `config.defaultAgent` here to pick the concrete runner.
10
10
  *
11
11
  * Singleton caching: the factory MAY cache its instance internally. All
@@ -64,12 +64,12 @@ export class AgentRegistry {
64
64
  getConfigContainer() {
65
65
  return this.configContainer;
66
66
  }
67
- get(kind, workspace) {
67
+ get(kind, cwd) {
68
68
  const factory = this.factories.get(kind);
69
69
  if (!factory) {
70
70
  throw new Error(`agent not registered: ${kind}`);
71
71
  }
72
- return factory(workspace);
72
+ return factory(cwd);
73
73
  }
74
74
  }
75
75
  //# sourceMappingURL=registry.js.map
@@ -395,7 +395,7 @@ export interface Runner {
395
395
  killOrphan(): void;
396
396
  registerExitHandlers(): void;
397
397
  /**
398
- * Remove the runner from the process-level exit dispatcher (P1-1). Called by
398
+ * Remove the runner from the process-level exit dispatcher. Called by
399
399
  * the bridge when the (cwd, kind) cache slot is evicted so the instance is
400
400
  * not retained by the singleton dispatcher. Optional so test stubs that only
401
401
  * satisfy `registerExitHandlers` remain structurally valid.
@@ -5,11 +5,22 @@ export interface SessionIndexEntry {
5
5
  fingerprint: string;
6
6
  mtimeMs: number;
7
7
  summary: string;
8
+ /**
9
+ * cwd 判等键集合(PathKit.pathKey 归一形态,win32/darwin 大小写+分隔符
10
+ * 不敏感):索引的 byCwd 倒排与 cwdSet.has 查询都按这个形态比对。注意与
11
+ * {@link parseSessionJsonl} 返回的 raw cwdSet 区分——后者保持 JSONL 原样,
12
+ * 归一只发生在索引边界(见 normalizeCwdSet)。
13
+ */
8
14
  cwdSet: Set<string>;
9
15
  }
10
16
  export interface SessionIndexOptions {
11
17
  /** Minimum interval between refresh scans (ms). Default 5000. */
12
18
  refreshIntervalMs?: number;
19
+ /**
20
+ * 平台注入(测试用);默认当前宿主。cwd 判等键(pathKey)按它取归一口径,
21
+ * win32 语义(大小写 + `\`/`/` 分隔符不敏感)可在任意宿主上单测。
22
+ */
23
+ platform?: NodeJS.Platform;
13
24
  }
14
25
  /** Claude-specific field-name mapping for content block extraction. */
15
26
  export declare const CLAUDE_MAPPING: ContentBlockMapping;
@@ -36,7 +47,7 @@ export interface ParsedSession {
36
47
  /**
37
48
  * Unified parser: single streaming scan of a JSONL file producing both
38
49
  * the complete set of cwd values AND the first real user message summary.
39
- * Replaces the split readCwdFromJsonl (first-cwd) + jsonlContainsCwd (any-cwd).
50
+ * Replaces the split readCwdFromJsonl (first-cwd) + fileContainsCwd (any-cwd).
40
51
  */
41
52
  export declare function parseSessionJsonl(filePath: string): ParsedSession;
42
53
  /**
@@ -57,6 +68,7 @@ export declare function buildFingerprint(filePath: string): string;
57
68
  export declare class SessionIndex {
58
69
  private readonly projectsDir;
59
70
  private readonly refreshIntervalMs;
71
+ private readonly platform;
60
72
  private readonly byPath;
61
73
  private readonly bySessionId;
62
74
  private readonly byCwd;
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
  import { scanJsonlLines } from '../common/jsonl.js';
4
4
  import { sortByRecencyDesc } from '../common/recency.js';
5
5
  import { extractContentBlocks } from '../common/content-blocks.js';
6
+ import { pathKey } from '../../platform/path.js';
7
+ import { currentPlatform } from '../../platform/select.js';
6
8
  import { getLogger } from '../../logger/index.js';
7
9
  // ─── Claude content block mapping (shared with sessions.ts) ─
8
10
  /** Claude-specific field-name mapping for content block extraction. */
@@ -42,7 +44,7 @@ export function extractText(content) {
42
44
  /**
43
45
  * Unified parser: single streaming scan of a JSONL file producing both
44
46
  * the complete set of cwd values AND the first real user message summary.
45
- * Replaces the split readCwdFromJsonl (first-cwd) + jsonlContainsCwd (any-cwd).
47
+ * Replaces the split readCwdFromJsonl (first-cwd) + fileContainsCwd (any-cwd).
46
48
  */
47
49
  export function parseSessionJsonl(filePath) {
48
50
  const cwdSet = new Set();
@@ -115,6 +117,17 @@ export function buildFingerprint(filePath) {
115
117
  return parts.join('|');
116
118
  }
117
119
  // ─── SessionIndex ───────────────────────────────────────────
120
+ /**
121
+ * raw cwd → pathKey 判等键。只允许在「解析结果进入索引」的边界调用:
122
+ * JSONL 里的 cwd 形态(大小写/分隔符/尾斜杠)由写方决定,与查询侧
123
+ * pathKey(cwd) 不保证逐字符一致(win32 尤甚),索引键控必须两侧同口径归一。
124
+ */
125
+ function normalizeCwdSet(raw, platform) {
126
+ const out = new Set();
127
+ for (const cwd of raw)
128
+ out.add(pathKey(cwd, { platform }));
129
+ return out;
130
+ }
118
131
  /**
119
132
  * In-memory session index with three query views:
120
133
  * byPath: path → entry
@@ -127,6 +140,7 @@ export function buildFingerprint(filePath) {
127
140
  export class SessionIndex {
128
141
  projectsDir;
129
142
  refreshIntervalMs;
143
+ platform;
130
144
  byPath = new Map();
131
145
  bySessionId = new Map();
132
146
  byCwd = new Map();
@@ -139,6 +153,7 @@ export class SessionIndex {
139
153
  constructor(projectsDir, opts = {}) {
140
154
  this.projectsDir = projectsDir;
141
155
  this.refreshIntervalMs = opts.refreshIntervalMs ?? 5000;
156
+ this.platform = opts.platform ?? currentPlatform;
142
157
  }
143
158
  /** Whether a full scan has ever completed successfully (readdir succeeded). */
144
159
  get isBuilt() {
@@ -162,7 +177,7 @@ export class SessionIndex {
162
177
  }
163
178
  // ── Query: listByCwd ────────────────────────────────────
164
179
  listByCwd(cwd, opts = {}) {
165
- const paths = this.byCwd.get(cwd);
180
+ const paths = this.byCwd.get(pathKey(cwd, { platform: this.platform }));
166
181
  if (!paths || paths.size === 0)
167
182
  return [];
168
183
  // Gather entries, deduplicate by sessionId (keep mtime newest)
@@ -211,7 +226,7 @@ export class SessionIndex {
211
226
  }
212
227
  if (currentFp === entry.fingerprint) {
213
228
  // Fingerprint matches → trust index
214
- if (entry.cwdSet.has(cwd))
229
+ if (entry.cwdSet.has(pathKey(cwd, { platform: this.platform })))
215
230
  return entry;
216
231
  continue;
217
232
  }
@@ -226,11 +241,11 @@ export class SessionIndex {
226
241
  fingerprint: currentFp,
227
242
  mtimeMs: Number(st.mtimeMs),
228
243
  summary: parsed.summary.replace(/\s+/g, ' ').trim(),
229
- cwdSet: parsed.cwdSet,
244
+ cwdSet: normalizeCwdSet(parsed.cwdSet, this.platform),
230
245
  };
231
246
  this.updateEntry(p, updated);
232
247
  // Re-check cwd after update
233
- if (updated.cwdSet.has(cwd))
248
+ if (updated.cwdSet.has(pathKey(cwd, { platform: this.platform })))
234
249
  return updated;
235
250
  }
236
251
  catch {
@@ -304,7 +319,7 @@ export class SessionIndex {
304
319
  fingerprint: fpAfter,
305
320
  mtimeMs: Number(fileStat.mtimeMs),
306
321
  summary: parsed.summary.replace(/\s+/g, ' ').trim(),
307
- cwdSet: parsed.cwdSet,
322
+ cwdSet: normalizeCwdSet(parsed.cwdSet, this.platform),
308
323
  };
309
324
  this.updateEntry(fullPath, newEntry);
310
325
  }
@@ -8,7 +8,7 @@ import type { AgentSession, AgentSessionReader, SessionContent } from '../../run
8
8
  * injects a reader configured with its test `projectsDir`; production code
9
9
  * uses the default (`~/.claude/projects/`).
10
10
  *
11
- * All methods are thin wrappers around `claude-sessions.ts` exports — zero
11
+ * All methods are thin wrappers around `sessions.ts` exports — zero
12
12
  * behavior change. Router and bridge reach session data exclusively through
13
13
  * this reader (via `SessionReaderRegistry`); the underlying functions remain
14
14
  * exported for tests and the reader itself.
@@ -9,7 +9,7 @@ import { listClaudeSessions, getNewestSession, readSessionContent, isClaudeSessi
9
9
  * injects a reader configured with its test `projectsDir`; production code
10
10
  * uses the default (`~/.claude/projects/`).
11
11
  *
12
- * All methods are thin wrappers around `claude-sessions.ts` exports — zero
12
+ * All methods are thin wrappers around `sessions.ts` exports — zero
13
13
  * behavior change. Router and bridge reach session data exclusively through
14
14
  * this reader (via `SessionReaderRegistry`); the underlying functions remain
15
15
  * exported for tests and the reader itself.
@@ -30,7 +30,7 @@ function projectDirForCwd(cwd, projectsDir) {
30
30
  * to actually belong to the requested cwd. A relocated session contains
31
31
  * MULTIPLE cwd values (pre- and post-relocate) — accept when ANY `cwd` field
32
32
  * matches. Early-stops on the first match (findJsonlLine), keeping the
33
- * P2-2 single-pass parse-count anchors intact on the fallback path.
33
+ * single-pass parse-count anchors intact on the fallback path.
34
34
  * Regression 2026-08-04 EnterWorktree relocate.
35
35
  */
36
36
  function fileContainsCwd(filePath, cwd) {
@@ -109,8 +109,8 @@ function ensureSessionIndex(projectsDir) {
109
109
  * the project directory doesn't exist or no jsonl has a matching cwd field.
110
110
  */
111
111
  export function listClaudeSessions(cwd, opts = {}) {
112
- // 不传 limit = 返回全集不切片;仅显式传值才 slice(契约迁移 plan §2.1,
113
- // reader 拿全集算 total 后再自己按 offset/limit 切片)。
112
+ // 不传 limit = 返回全集不切片;仅显式传值才 slice
113
+ // reader 拿全集算 total 后再自己按 offset/limit 切片。
114
114
  const limit = opts.limit;
115
115
  const projectsDir = opts.projectsDir ?? defaultProjectsDir();
116
116
  const index = ensureSessionIndex(projectsDir);
@@ -218,7 +218,7 @@ export function isClaudeSessionActive(sessionId, cwd, opts = {}) {
218
218
  }
219
219
  }
220
220
  /**
221
- * P2-2 + P2-5 first-pass scan: stream the file once via `scanJsonlLines`
221
+ * first-pass scan: stream the file once via `scanJsonlLines`
222
222
  * (no `string[]` materialized), parse each line exactly once, and collect
223
223
  * all metadata needed by readSessionContent (usage, tailOffset, aiTitle,
224
224
  * recap, lastUserMessage). Does NOT retain parsed objects — the events tail
@@ -320,7 +320,7 @@ function scalarScan(filePath) {
320
320
  const l = acc.last;
321
321
  // contextLength = current context-window occupancy. Prefer compact_boundary
322
322
  // postTokens (the precise post-compact size); otherwise fall back to the
323
- // last turn's input + cacheRead + cacheCreation (review P2-8 unified
323
+ // last turn's input + cacheRead + cacheCreation (unified
324
324
  // contract — excludes output/reasoning, which are generated, not part of
325
325
  // the input window). Take the max so a pre-compact turn larger than the
326
326
  // post-compact size is not under-reported.
@@ -353,12 +353,12 @@ function scalarScan(filePath) {
353
353
  };
354
354
  }
355
355
  /**
356
- * P2-2 + P2-5 second-pass tail mapper: expand one parsed tail line into
356
+ * second-pass tail mapper: expand one parsed tail line into
357
357
  * content-block events. Re-parses only tail lines (scanTailEvents does the
358
358
  * offset re-read + line filtering), so both raw line-string memory and
359
- * parsed-object memory are O(tail), not O(whole file) — P3-1 + P2-5.
359
+ * parsed-object memory are O(tail), not O(whole file) —.
360
360
  *
361
- * `maxEvents` capping (review P2-7 — unified "last N events" contract across
361
+ * `maxEvents` capping (unified "last N events" contract across
362
362
  * all readers) is applied by scanTailEvents.
363
363
  */
364
364
  function mapClaudeTailLine(obj) {
@@ -411,7 +411,7 @@ export function readSessionContent(sessionId, cwd, opts = {}) {
411
411
  return { events: [] };
412
412
  }
413
413
  }
414
- // P2-2 + P3-1 + P2-5: First pass STREAMS the file once via
414
+ // First pass STREAMS the file once via
415
415
  // `scanJsonlLines` (no `string[]` materialized), parsing each line once to
416
416
  // collect scalars (usage, tailOffset, aiTitle, recap, lastUserMessage)
417
417
  // while retaining NO parsed objects and NO whole-file line array. The
@@ -425,7 +425,7 @@ export function readSessionContent(sessionId, cwd, opts = {}) {
425
425
  // full-file scan + tail re-parse of the usually-short post-user lines).
426
426
  // When the session has NO user message, tailOffset stays -1 and the tail
427
427
  // IS the whole file → scan(N) + tail(N) = 2.0× — the known asymptotic
428
- // upper bound of the body's two-phase design (still better than pre-P2-2's
428
+ // upper bound of the body's two-phase design (still better than pre-'s
429
429
  // ~3×). Total parse count adds the index-miss fallback guard's fixed
430
430
  // early-stop overhead (~2 parses, independent of line count).
431
431
  const scan = scalarScan(filePath);
@@ -53,10 +53,10 @@ export declare function readCodexRollout(filePath: string): CodexRolloutEntry |
53
53
  * Returns `{ entries, total }`: `total` is the size of the full cwd-matched
54
54
  * set before pagination; `entries` are the newest `limit` entries by mtime.
55
55
  *
56
- * A global order over the full set is established first (plan §1.4: no early
56
+ * A global order over the full set is established first (no early
57
57
  * termination before ordering) using the lightweight session index — full
58
58
  * walk + first-line session_meta parse + stat mtime, 5s TTL cache. Only the
59
- * returned page is fully parsed for summary/usage (plan §2.2).
59
+ * returned page is fully parsed for summary/usage.
60
60
  */
61
61
  export declare function listCodexRollouts(opts?: ListCodexRolloutsOptions): {
62
62
  entries: CodexRolloutEntry[];
@@ -65,7 +65,7 @@ export declare function listCodexRollouts(opts?: ListCodexRolloutsOptions): {
65
65
  /**
66
66
  * Read the full content of a specific session by its threadId.
67
67
  *
68
- * P2-4: Uses session index for O(1) file lookup instead of walking the
68
+ * Uses session index for O(1) file lookup instead of walking the
69
69
  * entire directory tree and fully parsing every file. Only the matching
70
70
  * file is fully parsed.
71
71
  *
@@ -94,7 +94,7 @@ export declare function readCodexSessionSummary(sessionId: string, opts?: {
94
94
  /**
95
95
  * Check if a session is still active (recently updated).
96
96
  *
97
- * P2-4: Uses session index for direct file lookup, then checks mtime
97
+ * Uses session index for direct file lookup, then checks mtime
98
98
  * via statSync — no full rollout parse required. This reduces the
99
99
  * cost from "walk entire directory + full-parse every file" to a single
100
100
  * statSync call for the matching file.
@@ -148,7 +148,7 @@ function scanCodexRollout(filePath, includeEvents) {
148
148
  // token_count 增量是 input/cached/output 全 0、只有 total_tokens 有值
149
149
  // (窗口被摘要+replacement history 占据),现有 zero-filter 会跳过它,
150
150
  // 因此这里必须单独捕获,不能复用 lastRawUsage。
151
- // 单位口径(review P3-9):前后两侧统一用 total_tokens —— 压缩前取最近一次
151
+ // 单位口径:前后两侧统一用 total_tokens —— 压缩前取最近一次
152
152
  // 非零 token_count 的 total_tokens(input+output,即该 turn 在窗口内的全部
153
153
  // token),压缩后取收尾事件的 total_tokens(压缩后窗口)。不得一边 input
154
154
  // 一边 total,否则「压缩前 X → 压缩后 Y」两边单位不一致。
@@ -279,7 +279,7 @@ function scanCodexRollout(filePath, includeEvents) {
279
279
  * (display-only, never added to total).
280
280
  * contextLength = input_tokens (= (input_tokens-cached) + cached + 0),
281
281
  * i.e. input + cacheRead + cacheCreation — the unified context-window
282
- * occupancy contract across all five readers (review P2-8, excludes
282
+ * occupancy contract across all five readers (excludes
283
283
  * output/reasoning).
284
284
  */
285
285
  function buildUsageFromScan(scan) {
@@ -325,10 +325,10 @@ function buildUsageFromScan(scan) {
325
325
  * Returns `{ entries, total }`: `total` is the size of the full cwd-matched
326
326
  * set before pagination; `entries` are the newest `limit` entries by mtime.
327
327
  *
328
- * A global order over the full set is established first (plan §1.4: no early
328
+ * A global order over the full set is established first (no early
329
329
  * termination before ordering) using the lightweight session index — full
330
330
  * walk + first-line session_meta parse + stat mtime, 5s TTL cache. Only the
331
- * returned page is fully parsed for summary/usage (plan §2.2).
331
+ * returned page is fully parsed for summary/usage.
332
332
  */
333
333
  export function listCodexRollouts(opts = {}) {
334
334
  const codexHome = resolveCodexHome(opts.codexHome);
@@ -338,7 +338,7 @@ export function listCodexRollouts(opts = {}) {
338
338
  const matched = [];
339
339
  for (const entry of index.values()) {
340
340
  // Subagent thread rollouts are part of a thread tree, not standalone
341
- // sessions: exclude them explicitly (plan §2.1) so a pure-subagent
341
+ // sessions: exclude them explicitly so a pure-subagent
342
342
  // session (main file missing) never pollutes the list or total.
343
343
  if (entry.isSubagent)
344
344
  continue;
@@ -361,15 +361,15 @@ export function listCodexRollouts(opts = {}) {
361
361
  return { entries, total };
362
362
  }
363
363
  /**
364
- * W2.6 单源:三处(readCodexSessionContent / readCodexSessionSummary /
364
+ * 单源:三处(readCodexSessionContent / readCodexSessionSummary /
365
365
  * isCodexSessionActive)共用的「索引查找 + miss 强制刷新重查」。
366
366
  * cwd 守卫与 subagent 过滤语义各异,留在调用方。
367
367
  */
368
368
  function resolveRolloutEntry(sessionId, codexHome) {
369
- // P2-4: Use session index for direct file lookup
369
+ // Use session index for direct file lookup
370
370
  let index = getSessionIndex(codexHome);
371
371
  let entry = index.get(sessionId);
372
- // P3-2: On miss, the index may be stale (a rollout file created after the
372
+ // On miss, the index may be stale (a rollout file created after the
373
373
  // cache was built but within the 5s TTL). Force a refresh and recheck so a
374
374
  // brand-new session is found without waiting for TTL expiry.
375
375
  if (!entry) {
@@ -381,7 +381,7 @@ function resolveRolloutEntry(sessionId, codexHome) {
381
381
  /**
382
382
  * Read the full content of a specific session by its threadId.
383
383
  *
384
- * P2-4: Uses session index for O(1) file lookup instead of walking the
384
+ * Uses session index for O(1) file lookup instead of walking the
385
385
  * entire directory tree and fully parsing every file. Only the matching
386
386
  * file is fully parsed.
387
387
  *
@@ -399,9 +399,9 @@ export function readCodexSessionContent(sessionId, opts = {}) {
399
399
  }
400
400
  // Cwd guard: when a cwd is provided, the session's working directory must
401
401
  // match. Without this, /resume <id> finds sessions by global ID lookup and
402
- // allows resuming a session from workspace B while the user is in workspace A.
402
+ // allows resuming a session from cwd B while the user is in cwd A.
403
403
  // Codex has no relocation (no EnterWorktree equivalent), so a simple equality
404
- // check suffices — unlike claude which needs jsonlContainsCwd to handle
404
+ // check suffices — unlike claude which needs fileContainsCwd to handle
405
405
  // relocated sessions with multiple cwd values.
406
406
  if (opts.cwd && !samePath(entry.cwd, opts.cwd)) {
407
407
  return { events: [] };
@@ -457,7 +457,7 @@ export function readCodexSessionSummary(sessionId, opts = {}) {
457
457
  /**
458
458
  * Check if a session is still active (recently updated).
459
459
  *
460
- * P2-4: Uses session index for direct file lookup, then checks mtime
460
+ * Uses session index for direct file lookup, then checks mtime
461
461
  * via statSync — no full rollout parse required. This reduces the
462
462
  * cost from "walk entire directory + full-parse every file" to a single
463
463
  * statSync call for the matching file.
@@ -467,14 +467,14 @@ export function isCodexSessionActive(sessionId, opts = {}) {
467
467
  // Unify with the other readers' 1-hour stale window (claude/pi/opencode/kimi
468
468
  // all use STALE_MS). Previously codex used a divergent 10-minute default.
469
469
  const threshold = opts.activeThresholdMs ?? STALE_MS;
470
- // P2-4: Use session index for direct file lookup + mtime check
470
+ // Use session index for direct file lookup + mtime check
471
471
  const entry = resolveRolloutEntry(sessionId, codexHome);
472
472
  if (!entry) {
473
473
  return false;
474
474
  }
475
475
  // Subagent thread rollouts are never "active sessions": their mtime
476
476
  // reflects thread-tree activity, not the parent session's liveness
477
- // (plan §2.1). A pure-subagent session must report inactive.
477
+ // A pure-subagent session must report inactive.
478
478
  if (entry.isSubagent) {
479
479
  return false;
480
480
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Two-pass scan skeleton shared by the claude / pi session readers (P2-2 +
3
- * P2-5/P2-6 lineage): pass 1 STREAMS the whole file once via `scanJsonlLines`
2
+ * Two-pass scan skeleton shared by the claude / pi session readers (
3
+ * lineage): pass 1 STREAMS the whole file once via `scanJsonlLines`
4
4
  * (no `string[]` materialized), parsing each line exactly once to collect
5
5
  * scalars (usage, tail offset, title) while retaining NO parsed objects; pass
6
6
  * 2 re-reads ONLY the tail from the recorded offset and re-parses it for
@@ -55,13 +55,13 @@ export declare class UsageAccumulator {
55
55
  */
56
56
  bumpCompact(): void;
57
57
  }
58
- /** P2-8 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
58
+ /** 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
59
59
  export declare function contextWindowOccupancy(l: {
60
60
  input: number;
61
61
  cacheRead: number;
62
62
  cacheCreation: number;
63
63
  }): number;
64
- /** P2-8 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
64
+ /** 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
65
65
  * codex 走 last_token_usage 口径、opencode 保留自己的条件赋值,原为逐字段复制的 4 连拷贝)。 */
66
66
  export declare function cumulativeUsageFields(t: {
67
67
  input: number;
@@ -69,11 +69,11 @@ export class UsageAccumulator {
69
69
  this._compactCount++;
70
70
  }
71
71
  }
72
- /** P2-8 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
72
+ /** 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
73
73
  export function contextWindowOccupancy(l) {
74
74
  return l.input + l.cacheRead + l.cacheCreation;
75
75
  }
76
- /** P2-8 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
76
+ /** 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
77
77
  * codex 走 last_token_usage 口径、opencode 保留自己的条件赋值,原为逐字段复制的 4 连拷贝)。 */
78
78
  export function cumulativeUsageFields(t) {
79
79
  return {
@@ -36,7 +36,7 @@ export declare class DshSessionReader implements AgentSessionReader {
36
36
  total: number;
37
37
  };
38
38
  getNewestSession(cwd: string): AgentSession | null;
39
- readSessionContent(sessionId: string, cwd: string, opts?: {
39
+ readSessionContent(sessionId: string, _cwd: string, opts?: {
40
40
  maxEvents?: number;
41
41
  }): SessionContent;
42
42
  isSessionActive(sessionId: string, cwd: string): boolean;