lark-remote 0.1.6 → 0.1.7

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 (188) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.en.md +9 -1
  3. package/README.md +14 -2
  4. package/dist/bridge/approval-coordinator.d.ts +57 -34
  5. package/dist/bridge/approval-coordinator.js +191 -40
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +78 -7
  9. package/dist/bridge/index.js +202 -79
  10. package/dist/bridge/queue-manager.d.ts +2 -1
  11. package/dist/bridge/queue-manager.js +1 -0
  12. package/dist/card/approval-render.js +152 -36
  13. package/dist/card/bash-card-session.d.ts +2 -24
  14. package/dist/card/bash-card-session.js +6 -73
  15. package/dist/card/bash-renderer.js +2 -3
  16. package/dist/card/card-budget.js +29 -75
  17. package/dist/card/card-session.d.ts +48 -0
  18. package/dist/card/card-session.js +86 -0
  19. package/dist/card/card-shared.d.ts +1 -12
  20. package/dist/card/card-shared.js +1 -26
  21. package/dist/card/run-card-session.d.ts +2 -55
  22. package/dist/card/run-card-session.js +6 -104
  23. package/dist/card/run-renderer.js +99 -232
  24. package/dist/card/run-state.d.ts +13 -0
  25. package/dist/card/run-state.js +25 -16
  26. package/dist/card/text-truncate.d.ts +13 -12
  27. package/dist/card/text-truncate.js +13 -63
  28. package/dist/common/fs.d.ts +8 -0
  29. package/dist/common/fs.js +17 -0
  30. package/dist/common/guards.d.ts +0 -20
  31. package/dist/common/guards.js +0 -43
  32. package/dist/common/sleep.d.ts +2 -0
  33. package/dist/common/sleep.js +5 -0
  34. package/dist/common/truncate.d.ts +39 -0
  35. package/dist/common/truncate.js +92 -0
  36. package/dist/config/codex-config.d.ts +1 -1
  37. package/dist/config/codex-config.js +1 -1
  38. package/dist/config/index.d.ts +88 -23
  39. package/dist/config/index.js +105 -34
  40. package/dist/connector/index.d.ts +69 -2
  41. package/dist/connector/index.js +107 -1
  42. package/dist/index.js +123 -105
  43. package/dist/instance-lock.js +4 -12
  44. package/dist/order/alias-resolve.d.ts +13 -0
  45. package/dist/order/alias-resolve.js +22 -0
  46. package/dist/order/alias-store.d.ts +38 -0
  47. package/dist/order/alias-store.js +106 -0
  48. package/dist/persistence/atomic-write.d.ts +8 -2
  49. package/dist/persistence/atomic-write.js +23 -7
  50. package/dist/restart.js +2 -1
  51. package/dist/router/card-helpers.d.ts +63 -0
  52. package/dist/router/card-helpers.js +146 -0
  53. package/dist/router/config/claude.js +25 -1
  54. package/dist/router/config/codex.js +29 -50
  55. package/dist/router/config/common/model-patch.d.ts +22 -0
  56. package/dist/router/config/common/model-patch.js +26 -0
  57. package/dist/router/config/common/render.js +5 -4
  58. package/dist/router/config/index.d.ts +12 -0
  59. package/dist/router/config/index.js +25 -1
  60. package/dist/router/config/kimi.d.ts +4 -1
  61. package/dist/router/config/kimi.js +27 -0
  62. package/dist/router/config/opencode.js +18 -5
  63. package/dist/router/config/pi.js +4 -5
  64. package/dist/router/config/types.d.ts +11 -2
  65. package/dist/router/index.d.ts +56 -21
  66. package/dist/router/index.js +373 -548
  67. package/dist/router/utils.d.ts +2 -0
  68. package/dist/router/utils.js +4 -0
  69. package/dist/runner/agent-choices-common.d.ts +16 -0
  70. package/dist/runner/agent-choices-common.js +31 -0
  71. package/dist/runner/claude/runner.d.ts +47 -10
  72. package/dist/runner/claude/runner.js +117 -46
  73. package/dist/runner/claude/session.d.ts +166 -0
  74. package/dist/runner/claude/session.js +749 -0
  75. package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
  76. package/dist/runner/codex/app-server/protocol-types.js +1 -12
  77. package/dist/runner/codex/app-server/runner.d.ts +42 -59
  78. package/dist/runner/codex/app-server/runner.js +160 -337
  79. package/dist/runner/codex/app-server/translator.d.ts +1 -21
  80. package/dist/runner/codex/app-server/translator.js +0 -18
  81. package/dist/runner/codex/index.d.ts +0 -2
  82. package/dist/runner/codex/index.js +0 -2
  83. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  84. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  85. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  86. package/dist/runner/common/acp/protocol-types.js +75 -0
  87. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  88. package/dist/runner/common/connection-based-runner.js +261 -0
  89. package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
  90. package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
  91. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  92. package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
  93. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
  94. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
  95. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  96. package/dist/runner/common/jsonrpc/types.js +9 -0
  97. package/dist/runner/common/process-stopper.d.ts +2 -1
  98. package/dist/runner/common/process-stopper.js +1 -0
  99. package/dist/runner/common/runner-utils.d.ts +6 -13
  100. package/dist/runner/common/runner-utils.js +6 -47
  101. package/dist/runner/common/spawning-runner.d.ts +24 -70
  102. package/dist/runner/common/spawning-runner.js +98 -152
  103. package/dist/runner/index.d.ts +4 -4
  104. package/dist/runner/index.js +4 -4
  105. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  106. package/dist/runner/kimi/acp/runner.js +448 -0
  107. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  108. package/dist/runner/kimi/acp/translator.js +336 -0
  109. package/dist/runner/kimi/index.d.ts +1 -1
  110. package/dist/runner/kimi/index.js +1 -1
  111. package/dist/runner/opencode/acp/index.d.ts +1 -0
  112. package/dist/runner/opencode/acp/index.js +2 -0
  113. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  114. package/dist/runner/opencode/acp/runner.js +429 -0
  115. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  116. package/dist/runner/opencode/acp/translator.js +345 -0
  117. package/dist/runner/opencode/index.d.ts +1 -1
  118. package/dist/runner/opencode/index.js +1 -1
  119. package/dist/runner/pi/index.d.ts +1 -1
  120. package/dist/runner/pi/index.js +1 -1
  121. package/dist/runner/pi/rpc/client.d.ts +52 -0
  122. package/dist/runner/pi/rpc/client.js +114 -0
  123. package/dist/runner/pi/rpc/index.d.ts +4 -0
  124. package/dist/runner/pi/rpc/index.js +4 -0
  125. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  126. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  127. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  128. package/dist/runner/pi/rpc/runner.js +199 -0
  129. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  130. package/dist/runner/pi/rpc/translator.js +258 -0
  131. package/dist/runner/registry.d.ts +7 -8
  132. package/dist/runner/registry.js +3 -4
  133. package/dist/runner/resolve-agent-choices.js +18 -59
  134. package/dist/runner/sync-agent-choices.js +16 -67
  135. package/dist/runner/types.d.ts +43 -13
  136. package/dist/session/claude/session-index.js +2 -3
  137. package/dist/session/claude/session-reader.js +3 -6
  138. package/dist/session/claude/sessions.js +6 -13
  139. package/dist/session/codex/rollout-reader.d.ts +2 -2
  140. package/dist/session/codex/rollout-reader.js +14 -17
  141. package/dist/session/codex/sessions.d.ts +1 -1
  142. package/dist/session/codex/sessions.js +2 -2
  143. package/dist/session/common/content-blocks.d.ts +7 -0
  144. package/dist/session/common/content-blocks.js +13 -9
  145. package/dist/session/common/jsonl.d.ts +8 -25
  146. package/dist/session/common/jsonl.js +126 -261
  147. package/dist/session/common/pagination.d.ts +28 -0
  148. package/dist/session/common/pagination.js +33 -0
  149. package/dist/session/kimi/sessions.d.ts +34 -1
  150. package/dist/session/kimi/sessions.js +208 -98
  151. package/dist/session/opencode/sessions.d.ts +17 -3
  152. package/dist/session/opencode/sessions.js +29 -29
  153. package/dist/session/pi/sessions.d.ts +5 -1
  154. package/dist/session/pi/sessions.js +12 -16
  155. package/dist/session/session-store.d.ts +11 -1
  156. package/dist/session/session-store.js +75 -178
  157. package/dist/update/index.d.ts +2 -2
  158. package/dist/update/index.js +2 -2
  159. package/dist/update/install.d.ts +20 -2
  160. package/dist/update/install.js +51 -7
  161. package/package.json +1 -1
  162. package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
  163. package/dist/runner/codex/argv.d.ts +0 -33
  164. package/dist/runner/codex/argv.js +0 -40
  165. package/dist/runner/codex/factory.d.ts +0 -29
  166. package/dist/runner/codex/factory.js +0 -39
  167. package/dist/runner/codex/jsonl.d.ts +0 -37
  168. package/dist/runner/codex/jsonl.js +0 -221
  169. package/dist/runner/codex/runner.d.ts +0 -66
  170. package/dist/runner/codex/runner.js +0 -129
  171. package/dist/runner/common/exec-translator.d.ts +0 -73
  172. package/dist/runner/common/exec-translator.js +0 -105
  173. package/dist/runner/kimi/jsonl.d.ts +0 -28
  174. package/dist/runner/kimi/jsonl.js +0 -143
  175. package/dist/runner/kimi/runner.d.ts +0 -49
  176. package/dist/runner/kimi/runner.js +0 -105
  177. package/dist/runner/opencode/argv.d.ts +0 -32
  178. package/dist/runner/opencode/argv.js +0 -35
  179. package/dist/runner/opencode/jsonl.d.ts +0 -68
  180. package/dist/runner/opencode/jsonl.js +0 -295
  181. package/dist/runner/opencode/runner.d.ts +0 -61
  182. package/dist/runner/opencode/runner.js +0 -93
  183. package/dist/runner/pi/jsonl.d.ts +0 -128
  184. package/dist/runner/pi/jsonl.js +0 -249
  185. package/dist/runner/pi/runner.d.ts +0 -36
  186. package/dist/runner/pi/runner.js +0 -76
  187. package/dist/session/common/catch-up.d.ts +0 -12
  188. package/dist/session/common/catch-up.js +0 -31
@@ -0,0 +1,261 @@
1
+ import { ConnectionLostError } from './jsonrpc/client.js';
2
+ import { getLogger } from '../../logger/index.js';
3
+ import { syntheticInitEvent } from './runner-utils.js';
4
+ /** How long to wait for turn output notifications before failing. */
5
+ export const TURN_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
6
+ export class ConnectionBasedRunner {
7
+ kind;
8
+ sessionReader;
9
+ lifetime = 'workspace';
10
+ currentClient = null;
11
+ currentTurnId = null;
12
+ lastEventAt = 0;
13
+ _isRunning = false;
14
+ turnIdleTimeoutMs;
15
+ /** Notification queue consumed by the active run generator. */
16
+ notificationQueue = [];
17
+ waitResolve = null;
18
+ forceFinish = false;
19
+ /** stop() 在 turn/start 或 prompt 响应前到达时置位,完成后再补发 cancel/interrupt。 */
20
+ stopRequested = false;
21
+ constructor(opts, defaultTurnIdleTimeoutMs = TURN_IDLE_TIMEOUT_MS) {
22
+ this.kind = opts.kind;
23
+ this.sessionReader = opts.sessionReader;
24
+ this.turnIdleTimeoutMs = opts.turnIdleTimeoutMs ?? defaultTurnIdleTimeoutMs;
25
+ }
26
+ get isRunning() {
27
+ return this._isRunning;
28
+ }
29
+ /**
30
+ * Run a message. Delegates to executeTurn() with the standard setup.
31
+ */
32
+ async *run(message, opts) {
33
+ yield* this.executeTurn(opts, () => this.setupTurn(message, opts));
34
+ }
35
+ /**
36
+ * Shared turn lifecycle used by run() and runCompact(): isRunning guard →
37
+ * resetRunState → setupFn (with connection-lost respawn-and-retry-once) →
38
+ * deferred stop → synthetic init → consumeTurn; catch yields synthetic init
39
+ * + error result; finally resets flags + notifyIdle.
40
+ *
41
+ * `setupFn` performs the turn-specific setup (acquire, session new/resume,
42
+ * prompt, etc.) and may push events into the notification queue.
43
+ */
44
+ async *executeTurn(opts, setupFn) {
45
+ if (this._isRunning) {
46
+ throw new Error(`${this.constructor.name} is already running`);
47
+ }
48
+ this._isRunning = true;
49
+ this.resetRunState();
50
+ try {
51
+ try {
52
+ await setupFn();
53
+ }
54
+ catch (err) {
55
+ // 连接在 setup 期间丢失(进程被误杀/异常退出):重拉进程并重试一次。
56
+ if (err instanceof ConnectionLostError && !this.stopRequested) {
57
+ getLogger().warn(`[${this.logTag}] connection lost during turn setup (${err.message}), respawning and retrying once`);
58
+ await this.respawnAfterConnectionLost(opts);
59
+ await setupFn();
60
+ }
61
+ else {
62
+ throw err;
63
+ }
64
+ }
65
+ if (this.stopRequested) {
66
+ // 启动期 /stop:turn/start 或 prompt 尚未完成,补发 cancel/interrupt,
67
+ // 避免 server 端 turn 无人接管继续执行。
68
+ getLogger().warn(`[${this.logTag}] stop requested before turn resolved`);
69
+ try {
70
+ await this.cancelCurrentTurn();
71
+ }
72
+ catch (err) {
73
+ getLogger().warn(`[${this.logTag}] deferred cancel failed: ${err.message}`);
74
+ }
75
+ }
76
+ // §9.22 守卫前提:桥的 pre-init result guard 和 run-state reducer 都以
77
+ // system.init 作为「本轮真实开始」的标记,而这些协议没有 init 事件。
78
+ // 缺了它,成功的 result 会被当作 pre-init 丢弃,卡片终态停在 running。
79
+ yield syntheticInitEvent(this.currentSessionId() ?? opts.sessionId ?? '');
80
+ yield* this.consumeTurn();
81
+ }
82
+ catch (err) {
83
+ getLogger().error(`[${this.logTag}] run error: ${err.message}`);
84
+ yield syntheticInitEvent(this.currentSessionId() ?? opts.sessionId ?? '');
85
+ yield {
86
+ type: 'result',
87
+ subtype: 'error',
88
+ // 优先上报本次 setup 创建/恢复的 session/thread id。
89
+ session_id: this.currentSessionId() ?? opts.sessionId ?? '',
90
+ errorMessage: err.message,
91
+ };
92
+ }
93
+ finally {
94
+ this._isRunning = false;
95
+ this.currentTurnId = null;
96
+ this.stopRequested = false;
97
+ this.clearTurnState();
98
+ this.notifyIdle(opts.cwd);
99
+ }
100
+ }
101
+ /**
102
+ * Stop the current turn by cancelling it. Shared skeleton: if the turn/start
103
+ * or prompt has not yet resolved (shouldDeferStop), mark stopRequested and
104
+ * unblock; otherwise cancel now via cancelCurrentTurn().
105
+ */
106
+ async stop(_opts) {
107
+ if (!this._isRunning || !this.currentClient || !this.currentSessionId())
108
+ return;
109
+ if (this.shouldDeferStop()) {
110
+ // turn/start 或 prompt 尚未响应:先标记,等 run() 完成后再补发 cancel。
111
+ this.stopRequested = true;
112
+ this.forceFinish = true;
113
+ this.wakeWaiters();
114
+ return;
115
+ }
116
+ // turn/start 或 prompt 已 resolved:立即 cancel;同时置 stopRequested 抑制
117
+ // 尚未结算的 prompt 结果翻译(opencode 语义),对已结算方无害。
118
+ this.stopRequested = true;
119
+ try {
120
+ await this.cancelCurrentTurn();
121
+ }
122
+ catch (err) {
123
+ getLogger().warn(`[${this.logTag}] stop error: ${err.message}`);
124
+ }
125
+ // Unblock the turn loop regardless of whether the server sends a
126
+ // completed notification afterwards.
127
+ this.forceFinish = true;
128
+ this.wakeWaiters();
129
+ }
130
+ /**
131
+ * Dispose the runner: release the connection.
132
+ */
133
+ async dispose() {
134
+ try {
135
+ await this.disposeConnections();
136
+ }
137
+ catch (err) {
138
+ getLogger().warn(`[${this.logTag}] dispose error: ${err.message}`);
139
+ }
140
+ }
141
+ getUsageAuthority() {
142
+ return 'live';
143
+ }
144
+ killOrphan() {
145
+ // No-op: the connection is managed by ConnectionManager.
146
+ }
147
+ registerExitHandlers() {
148
+ // No-op: connection manager handles cleanup on exit.
149
+ }
150
+ unregisterExitHandlers() {
151
+ // No-op.
152
+ }
153
+ // =========================================================================
154
+ // Shared turn machinery
155
+ // =========================================================================
156
+ /**
157
+ * Consume the notification queue until a result event arrives or the turn
158
+ * times out / is interrupted. forceFinish drains the queue first (the
159
+ * deferred cancel's own events may still be in it) before yielding the
160
+ * interrupted result.
161
+ */
162
+ async *consumeTurn() {
163
+ while (true) {
164
+ const timedOut = await this.waitForEvents(this.nextTurnIdleDeadline());
165
+ if (timedOut) {
166
+ await this.cancelCurrentTurn();
167
+ getLogger().warn(`[${this.logTag}] turn wait timed out`);
168
+ yield {
169
+ type: 'result',
170
+ subtype: 'error',
171
+ session_id: this.currentSessionId() ?? '',
172
+ errorMessage: this.turnTimeoutErrorMessage,
173
+ };
174
+ return;
175
+ }
176
+ if (this.forceFinish && this.notificationQueue.length === 0) {
177
+ yield {
178
+ type: 'result',
179
+ subtype: 'interrupted',
180
+ session_id: this.currentSessionId() ?? '',
181
+ errorMessage: this.turnInterruptedErrorMessage,
182
+ };
183
+ return;
184
+ }
185
+ while (this.notificationQueue.length > 0) {
186
+ const ev = this.notificationQueue.shift();
187
+ yield ev;
188
+ if (ev.type === 'result') {
189
+ return;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ /** Compute the current idle deadline. 0 disables; otherwise rolling on last event. */
195
+ nextTurnIdleDeadline() {
196
+ if (this.turnIdleTimeoutMs <= 0)
197
+ return Number.POSITIVE_INFINITY;
198
+ return this.lastEventAt + this.turnIdleTimeoutMs;
199
+ }
200
+ async waitForEvents(deadline) {
201
+ while (this.notificationQueue.length === 0 && !this.forceFinish) {
202
+ const remaining = deadline - Date.now();
203
+ if (remaining <= 0)
204
+ return true;
205
+ await new Promise((resolve) => {
206
+ this.waitResolve = resolve;
207
+ setTimeout(() => {
208
+ if (this.waitResolve === resolve) {
209
+ this.waitResolve = null;
210
+ resolve();
211
+ }
212
+ }, Math.min(remaining, 30_000));
213
+ });
214
+ }
215
+ return false;
216
+ }
217
+ pushEvents(events) {
218
+ if (events.length === 0)
219
+ return;
220
+ this.lastEventAt = Date.now();
221
+ this.notificationQueue.push(...events);
222
+ this.wakeWaiters();
223
+ }
224
+ wakeWaiters() {
225
+ if (this.waitResolve) {
226
+ const resolve = this.waitResolve;
227
+ this.waitResolve = null;
228
+ resolve();
229
+ }
230
+ }
231
+ failTurn(message) {
232
+ this.pushEvents([
233
+ {
234
+ type: 'result',
235
+ subtype: 'error',
236
+ session_id: this.currentSessionId() ?? '',
237
+ errorMessage: message,
238
+ },
239
+ ]);
240
+ }
241
+ resetRunState() {
242
+ this.notificationQueue = [];
243
+ this.waitResolve = null;
244
+ this.lastEventAt = Date.now();
245
+ this.forceFinish = false;
246
+ }
247
+ /** Release the connection + detach stale client hooks after a lost connection. */
248
+ async respawnAfterConnectionLost(opts) {
249
+ await this.releaseConnection(opts.cwd);
250
+ const staleClient = this.currentClient;
251
+ if (staleClient?.setHooks) {
252
+ staleClient.setHooks({
253
+ onNotification: () => { },
254
+ onServerRequest: () => { },
255
+ onClose: () => { },
256
+ });
257
+ }
258
+ this.resetRunState();
259
+ }
260
+ }
261
+ //# sourceMappingURL=connection-based-runner.js.map
@@ -1,11 +1,13 @@
1
1
  /**
2
- * JSON-RPC client for the Codex App Server protocol.
2
+ * Generic JSON-RPC client for JSON-line-RPC agent servers
3
+ * (codex app-server, kimi acp, and future ACP-style integrations).
3
4
  *
4
5
  * Manages the handshake, request/response matching, notification dispatch,
5
- * and timeout handling over a JsonlRpcTransport.
6
+ * and timeout handling over a JsonlRpcTransport. The `initialize` params
7
+ * shape is protocol-specific and injected by the caller (constructor arg);
8
+ * the default is the ACP handshake shape (ACP is the reference protocol).
6
9
  */
7
10
  import type { JsonlRpcTransport } from './transport.js';
8
- import { type InitializeResult } from './protocol-types.js';
9
11
  export declare class RpcError extends Error {
10
12
  readonly code: number;
11
13
  readonly data?: unknown;
@@ -22,7 +24,7 @@ export interface ClientHooks {
22
24
  onServerRequest(id: number | string, method: string, params: unknown): void;
23
25
  onClose(): void;
24
26
  }
25
- export declare class CodexAppServerClient {
27
+ export declare class JsonRpcClient<InitializeResult = unknown> {
26
28
  private transport;
27
29
  /**
28
30
  * Lifecycle hooks owned by the connection layer (ConnectionManager): slot
@@ -32,17 +34,18 @@ export declare class CodexAppServerClient {
32
34
  /** Per-run hooks from the runner, layered over baseHooks via setHooks(). */
33
35
  private runHooks;
34
36
  private requestTimeoutMs;
37
+ private initializeParams;
35
38
  private pendingRequests;
36
39
  private nextId;
37
40
  private _ready;
38
41
  private _disposed;
39
- constructor(transport: JsonlRpcTransport, hooks: ClientHooks, requestTimeoutMs?: number);
42
+ constructor(transport: JsonlRpcTransport, hooks: ClientHooks, requestTimeoutMs: number | undefined, initializeParams: object);
40
43
  /**
41
44
  * Replace the per-run hooks (used by the runner to bridge
42
45
  * notifications/server requests into the active turn's translator).
43
46
  * Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
44
47
  * review P2-1 — an overwrite here previously silenced onClose slot cleanup,
45
- * leaving dead slots behind after the app-server process was killed.
48
+ * leaving dead slots behind after the server process was killed.
46
49
  */
47
50
  setHooks(hooks: ClientHooks): void;
48
51
  get ready(): boolean;
@@ -59,8 +62,12 @@ export declare class CodexAppServerClient {
59
62
  connect(): Promise<InitializeResult>;
60
63
  /**
61
64
  * Send a JSON-RPC request and wait for the response.
65
+ * `timeoutMs` overrides the default request timeout — turn-scoped requests
66
+ * (e.g. session/prompt) hold their response open for the whole turn, so
67
+ * callers pass the turn idle timeout (bounded by the runner's turn idle
68
+ * watchdog, not the control-plane request timeout).
62
69
  */
63
- request<P, R>(method: string, params?: P): Promise<R>;
70
+ request<P, R>(method: string, params?: P, timeoutMs?: number): Promise<R>;
64
71
  /**
65
72
  * Send a JSON-RPC notification (no response expected).
66
73
  */
@@ -1,8 +1,11 @@
1
1
  /**
2
- * JSON-RPC client for the Codex App Server protocol.
2
+ * Generic JSON-RPC client for JSON-line-RPC agent servers
3
+ * (codex app-server, kimi acp, and future ACP-style integrations).
3
4
  *
4
5
  * Manages the handshake, request/response matching, notification dispatch,
5
- * and timeout handling over a JsonlRpcTransport.
6
+ * and timeout handling over a JsonlRpcTransport. The `initialize` params
7
+ * shape is protocol-specific and injected by the caller (constructor arg);
8
+ * the default is the ACP handshake shape (ACP is the reference protocol).
6
9
  */
7
10
  import { getLogger } from '../../../logger/index.js';
8
11
  // =============================================================================
@@ -33,7 +36,7 @@ export class ConnectionLostError extends Error {
33
36
  // =============================================================================
34
37
  // Client
35
38
  // =============================================================================
36
- export class CodexAppServerClient {
39
+ export class JsonRpcClient {
37
40
  transport;
38
41
  /**
39
42
  * Lifecycle hooks owned by the connection layer (ConnectionManager): slot
@@ -43,21 +46,23 @@ export class CodexAppServerClient {
43
46
  /** Per-run hooks from the runner, layered over baseHooks via setHooks(). */
44
47
  runHooks = null;
45
48
  requestTimeoutMs;
49
+ initializeParams;
46
50
  pendingRequests = new Map();
47
51
  nextId = 1;
48
52
  _ready = false;
49
53
  _disposed = false;
50
- constructor(transport, hooks, requestTimeoutMs = 60_000) {
54
+ constructor(transport, hooks, requestTimeoutMs = 60_000, initializeParams) {
51
55
  this.transport = transport;
52
56
  this.baseHooks = hooks;
53
57
  this.requestTimeoutMs = requestTimeoutMs;
58
+ this.initializeParams = initializeParams;
54
59
  }
55
60
  /**
56
61
  * Replace the per-run hooks (used by the runner to bridge
57
62
  * notifications/server requests into the active turn's translator).
58
63
  * Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
59
64
  * review P2-1 — an overwrite here previously silenced onClose slot cleanup,
60
- * leaving dead slots behind after the app-server process was killed.
65
+ * leaving dead slots behind after the server process was killed.
61
66
  */
62
67
  setHooks(hooks) {
63
68
  this.runHooks = hooks;
@@ -92,26 +97,27 @@ export class CodexAppServerClient {
92
97
  // ENOENT/启动失败时 transport 已在 start() 内触发 onClose 并置 closed;
93
98
  // 直接 fail-fast,避免 initialize 请求挂满 requestTimeoutMs 才报错。
94
99
  if (this.transport.closed) {
95
- throw new ConnectionLostError('codex app-server failed to start (binary missing or crashed)');
100
+ throw new ConnectionLostError('JSON-RPC server failed to start (binary missing or crashed)');
96
101
  }
97
- // Send initialize request
98
- const initParams = {
99
- clientInfo: {
100
- name: 'lark-remote',
101
- version: '1.0.0',
102
- },
103
- };
104
- const result = await this.request('initialize', initParams);
102
+ // Send initialize request (shape injected by the caller)
103
+ const result = await this.request('initialize', this.initializeParams);
105
104
  this._ready = true;
106
105
  return result;
107
106
  }
108
107
  /**
109
108
  * Send a JSON-RPC request and wait for the response.
109
+ * `timeoutMs` overrides the default request timeout — turn-scoped requests
110
+ * (e.g. session/prompt) hold their response open for the whole turn, so
111
+ * callers pass the turn idle timeout (bounded by the runner's turn idle
112
+ * watchdog, not the control-plane request timeout).
110
113
  */
111
- async request(method, params) {
114
+ async request(method, params, timeoutMs) {
112
115
  if (this._disposed) {
113
116
  throw new ConnectionLostError('client is disposed');
114
117
  }
118
+ if (this.transport.closed) {
119
+ throw new ConnectionLostError('transport closed');
120
+ }
115
121
  const id = this.nextId++;
116
122
  const request = {
117
123
  jsonrpc: '2.0',
@@ -120,10 +126,13 @@ export class CodexAppServerClient {
120
126
  params,
121
127
  };
122
128
  return new Promise((resolve, reject) => {
129
+ // Clamp to setTimeout's max: callers may pass Infinity for turn-scoped
130
+ // requests (session/prompt) — Infinity would fire immediately in Node.
131
+ const effectiveTimeout = Math.min(timeoutMs ?? this.requestTimeoutMs, 2_147_483_647);
123
132
  const timer = setTimeout(() => {
124
133
  this.pendingRequests.delete(id);
125
134
  reject(new RpcTimeoutError(`request timed out: ${method}`));
126
- }, this.requestTimeoutMs);
135
+ }, effectiveTimeout);
127
136
  this.pendingRequests.set(id, {
128
137
  resolve: resolve,
129
138
  reject,
@@ -214,14 +223,14 @@ export class CodexAppServerClient {
214
223
  return;
215
224
  }
216
225
  if ('method' in message && 'id' in message) {
217
- // Server request (incoming request from server)
226
+ // Server request (incoming request from server — reverse RPC)
218
227
  const id = message.id;
219
228
  const method = message.method;
220
229
  this.baseHooks.onServerRequest(id, method, message.params);
221
230
  this.runHooks?.onServerRequest(id, method, message.params);
222
231
  return;
223
232
  }
224
- getLogger().warn(`[codex-client] unknown message type: ${JSON.stringify(message).slice(0, 200)}`);
233
+ getLogger().warn(`[jsonrpc-client] unknown message type: ${JSON.stringify(message).slice(0, 200)}`);
225
234
  }
226
235
  failPending(err) {
227
236
  for (const [_id, pending] of this.pendingRequests) {
@@ -0,0 +1,125 @@
1
+ /**
2
+ * ConnectionManager: manages agent-server connections per workspace, shared by
3
+ * all workspace-lifetime JSON-line runners (codex app-server, kimi acp,
4
+ * opencode acp, and pi rpc).
5
+ *
6
+ * Each workspace gets its own connection (transport + client). Connections are
7
+ * created on demand via `acquire()`, cached for reuse, and released after an
8
+ * idle timeout (30 minutes default).
9
+ *
10
+ * The manager is protocol-agnostic over the concrete client. The default
11
+ * `clientFactory` builds a JsonRpcClient with the `initialize` handshake (the
12
+ * ACP-style reference protocol); protocols that are NOT JSON-RPC (pi's
13
+ * `--mode rpc`, which has no handshake and binds the session at spawn) inject
14
+ * their own `clientFactory` + `buildArgs` + `shouldReuse` + `bindSession`.
15
+ */
16
+ import { JsonlRpcTransport } from './transport.js';
17
+ import { JsonRpcClient } from './client.js';
18
+ /** Minimal client surface the manager needs from any protocol client. */
19
+ export interface ConnectionClient {
20
+ ready: boolean;
21
+ healthy: boolean;
22
+ connect(): Promise<unknown>;
23
+ dispose(): Promise<void>;
24
+ }
25
+ /** Request context for acquire() — currently only an optional session id. */
26
+ export interface AcquireRequest {
27
+ sessionId?: string;
28
+ }
29
+ /** Context passed to clientFactory to build + wire a concrete client. */
30
+ export interface ClientFactoryContext {
31
+ transport: JsonlRpcTransport;
32
+ requestTimeoutMs: number;
33
+ /** Wire into the client's close callback so slot cleanup + onConnectionLost fire. */
34
+ baseOnClose: () => void;
35
+ }
36
+ export interface ConnectionManagerOptions<TClient extends ConnectionClient = JsonRpcClient> {
37
+ /** Path to the server binary. */
38
+ binary: string;
39
+ /**
40
+ * Args to spawn the server with. Defaults to `[]` — each protocol's runner
41
+ * injects its own default (e.g. codex `['app-server', '--stdio']`, kimi
42
+ * `['acp']`).
43
+ */
44
+ args?: string[];
45
+ /**
46
+ * Optional arg builder over the acquire request — used by protocols that bind
47
+ * a session at spawn (pi: `--session-id`). Defaults to `() => this.args`.
48
+ */
49
+ buildArgs?: (req: AcquireRequest) => string[];
50
+ /** Environment variables to pass to the binary. */
51
+ env?: Record<string, string | undefined>;
52
+ /** Request timeout in milliseconds. */
53
+ requestTimeoutMs?: number;
54
+ /** Idle TTL in milliseconds. */
55
+ idleTtlMs?: number;
56
+ /**
57
+ * Initialize handshake params for the default JsonRpcClient path. Ignored
58
+ * when `clientFactory` is provided.
59
+ */
60
+ initializeParams?: object;
61
+ /**
62
+ * Build + wire a concrete protocol client. Defaults to a JsonRpcClient with
63
+ * the initialize handshake. Non-JSON-RPC protocols inject their own client.
64
+ */
65
+ clientFactory?: (ctx: ClientFactoryContext) => TClient;
66
+ /**
67
+ * Whether an existing (cached) connection can be reused for a request.
68
+ * Defaults to `client.ready && client.healthy`. Session-bound protocols (pi)
69
+ * additionally require the requested session id to match the bound one.
70
+ */
71
+ shouldReuse?: (ctx: {
72
+ client: TClient;
73
+ req: AcquireRequest;
74
+ boundSessionId: string | undefined;
75
+ }) => boolean;
76
+ /** Log tag prefix for operational log lines. */
77
+ logTag?: string;
78
+ }
79
+ export declare class ConnectionManager<TClient extends ConnectionClient = JsonRpcClient> {
80
+ private slots;
81
+ private readonly binary;
82
+ private readonly buildArgs;
83
+ private readonly env;
84
+ private readonly requestTimeoutMs;
85
+ private readonly idleTtlMs;
86
+ private readonly initializeParams;
87
+ private readonly clientFactory;
88
+ private readonly shouldReuse;
89
+ private readonly logTag;
90
+ /** Callback when a connection is lost — cleared from slot map. */
91
+ onConnectionLost?: (workspace: string) => void;
92
+ constructor(opts: ConnectionManagerOptions<TClient>);
93
+ /**
94
+ * Acquire a connection for the given workspace.
95
+ * Creates a new connection if one does not exist or the previous one was lost
96
+ * (or, for session-bound protocols, bound to a different session).
97
+ * Serializes creation so concurrent calls share the same connection.
98
+ */
99
+ acquire(workspace: string, req?: AcquireRequest): Promise<TClient>;
100
+ /**
101
+ * Record the session id a fresh connection actually bound to (e.g. discovered
102
+ * via a protocol `get_state` after a fresh spawn). This lets a subsequent run
103
+ * resume the SAME session on the live connection instead of respawning it.
104
+ */
105
+ bindSession(workspace: string, sessionId: string): void;
106
+ /**
107
+ * Release (dispose) a connection for the given workspace.
108
+ */
109
+ release(workspace: string): Promise<void>;
110
+ /**
111
+ * Dispose all connections.
112
+ */
113
+ disposeAll(): Promise<void>;
114
+ /**
115
+ * Notify that the workspace is active — disarm the idle timer.
116
+ */
117
+ notifyActivity(workspace: string): void;
118
+ /**
119
+ * Notify that the workspace is idle — arm the idle timer.
120
+ */
121
+ notifyIdle(workspace: string): void;
122
+ private createClient;
123
+ private clearIdleTimer;
124
+ private disposeSlot;
125
+ }