lark-remote 0.1.5 → 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 (190) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.en.md +5 -1
  3. package/README.md +10 -2
  4. package/dist/bridge/approval-coordinator.d.ts +131 -0
  5. package/dist/bridge/approval-coordinator.js +409 -0
  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 +138 -5
  9. package/dist/bridge/index.js +569 -65
  10. package/dist/bridge/queue-manager.d.ts +11 -1
  11. package/dist/bridge/queue-manager.js +20 -11
  12. package/dist/card/approval-render.d.ts +16 -0
  13. package/dist/card/approval-render.js +363 -0
  14. package/dist/card/bash-card-session.d.ts +2 -24
  15. package/dist/card/bash-card-session.js +6 -73
  16. package/dist/card/bash-renderer.js +2 -3
  17. package/dist/card/card-budget.js +29 -75
  18. package/dist/card/card-session.d.ts +48 -0
  19. package/dist/card/card-session.js +86 -0
  20. package/dist/card/card-shared.d.ts +13 -10
  21. package/dist/card/card-shared.js +27 -24
  22. package/dist/card/run-card-session.d.ts +2 -55
  23. package/dist/card/run-card-session.js +6 -104
  24. package/dist/card/run-renderer.js +140 -202
  25. package/dist/card/run-state.d.ts +38 -3
  26. package/dist/card/run-state.js +221 -11
  27. package/dist/card/text-truncate.d.ts +13 -12
  28. package/dist/card/text-truncate.js +13 -63
  29. package/dist/common/fs.d.ts +8 -0
  30. package/dist/common/fs.js +17 -0
  31. package/dist/common/guards.d.ts +0 -20
  32. package/dist/common/guards.js +0 -43
  33. package/dist/common/sleep.d.ts +2 -0
  34. package/dist/common/sleep.js +5 -0
  35. package/dist/common/truncate.d.ts +39 -0
  36. package/dist/common/truncate.js +92 -0
  37. package/dist/config/codex-config.d.ts +1 -1
  38. package/dist/config/codex-config.js +1 -1
  39. package/dist/config/dir.js +1 -1
  40. package/dist/config/index.d.ts +116 -9
  41. package/dist/config/index.js +122 -29
  42. package/dist/connector/index.d.ts +74 -2
  43. package/dist/connector/index.js +119 -1
  44. package/dist/index.js +139 -99
  45. package/dist/instance-lock.js +4 -12
  46. package/dist/order/alias-resolve.d.ts +13 -0
  47. package/dist/order/alias-resolve.js +22 -0
  48. package/dist/order/alias-store.d.ts +38 -0
  49. package/dist/order/alias-store.js +106 -0
  50. package/dist/persistence/atomic-write.d.ts +8 -2
  51. package/dist/persistence/atomic-write.js +23 -7
  52. package/dist/restart.js +2 -1
  53. package/dist/router/card-helpers.d.ts +63 -0
  54. package/dist/router/card-helpers.js +146 -0
  55. package/dist/router/config/claude.js +25 -1
  56. package/dist/router/config/codex.d.ts +1 -1
  57. package/dist/router/config/codex.js +33 -5
  58. package/dist/router/config/common/model-patch.d.ts +22 -0
  59. package/dist/router/config/common/model-patch.js +26 -0
  60. package/dist/router/config/common/render.js +9 -4
  61. package/dist/router/config/index.d.ts +12 -0
  62. package/dist/router/config/index.js +25 -1
  63. package/dist/router/config/kimi.d.ts +4 -1
  64. package/dist/router/config/kimi.js +27 -0
  65. package/dist/router/config/opencode.js +18 -5
  66. package/dist/router/config/pi.js +4 -5
  67. package/dist/router/config/types.d.ts +12 -3
  68. package/dist/router/index.d.ts +77 -17
  69. package/dist/router/index.js +455 -549
  70. package/dist/router/utils.d.ts +4 -0
  71. package/dist/router/utils.js +9 -2
  72. package/dist/runner/agent-choices-common.d.ts +16 -0
  73. package/dist/runner/agent-choices-common.js +31 -0
  74. package/dist/runner/claude/runner.d.ts +47 -10
  75. package/dist/runner/claude/runner.js +117 -46
  76. package/dist/runner/claude/session.d.ts +166 -0
  77. package/dist/runner/claude/session.js +749 -0
  78. package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
  79. package/dist/runner/codex/app-server/protocol-types.js +56 -0
  80. package/dist/runner/codex/app-server/runner.d.ts +104 -0
  81. package/dist/runner/codex/app-server/runner.js +377 -0
  82. package/dist/runner/codex/app-server/translator.d.ts +89 -0
  83. package/dist/runner/codex/app-server/translator.js +716 -0
  84. package/dist/runner/codex/index.d.ts +1 -1
  85. package/dist/runner/codex/index.js +1 -1
  86. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  87. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  88. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  89. package/dist/runner/common/acp/protocol-types.js +75 -0
  90. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  91. package/dist/runner/common/connection-based-runner.js +261 -0
  92. package/dist/runner/common/jsonrpc/client.d.ts +89 -0
  93. package/dist/runner/common/jsonrpc/client.js +243 -0
  94. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  95. package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
  96. package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
  97. package/dist/runner/common/jsonrpc/transport.js +230 -0
  98. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  99. package/dist/runner/common/jsonrpc/types.js +9 -0
  100. package/dist/runner/common/process-stopper.d.ts +2 -1
  101. package/dist/runner/common/process-stopper.js +1 -0
  102. package/dist/runner/common/runner-utils.d.ts +6 -13
  103. package/dist/runner/common/runner-utils.js +6 -47
  104. package/dist/runner/common/spawning-runner.d.ts +24 -70
  105. package/dist/runner/common/spawning-runner.js +98 -152
  106. package/dist/runner/common/usage.d.ts +2 -0
  107. package/dist/runner/common/usage.js +2 -0
  108. package/dist/runner/index.d.ts +5 -5
  109. package/dist/runner/index.js +4 -4
  110. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  111. package/dist/runner/kimi/acp/runner.js +448 -0
  112. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  113. package/dist/runner/kimi/acp/translator.js +336 -0
  114. package/dist/runner/kimi/index.d.ts +1 -1
  115. package/dist/runner/kimi/index.js +1 -1
  116. package/dist/runner/opencode/acp/index.d.ts +1 -0
  117. package/dist/runner/opencode/acp/index.js +2 -0
  118. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  119. package/dist/runner/opencode/acp/runner.js +429 -0
  120. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  121. package/dist/runner/opencode/acp/translator.js +345 -0
  122. package/dist/runner/opencode/index.d.ts +1 -1
  123. package/dist/runner/opencode/index.js +1 -1
  124. package/dist/runner/pi/index.d.ts +1 -1
  125. package/dist/runner/pi/index.js +1 -1
  126. package/dist/runner/pi/rpc/client.d.ts +52 -0
  127. package/dist/runner/pi/rpc/client.js +114 -0
  128. package/dist/runner/pi/rpc/index.d.ts +4 -0
  129. package/dist/runner/pi/rpc/index.js +4 -0
  130. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  131. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  132. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  133. package/dist/runner/pi/rpc/runner.js +199 -0
  134. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  135. package/dist/runner/pi/rpc/translator.js +258 -0
  136. package/dist/runner/registry.d.ts +7 -8
  137. package/dist/runner/registry.js +3 -4
  138. package/dist/runner/resolve-agent-choices.js +18 -59
  139. package/dist/runner/sync-agent-choices.js +16 -67
  140. package/dist/runner/types.d.ts +153 -2
  141. package/dist/session/claude/session-index.js +2 -3
  142. package/dist/session/claude/session-reader.js +3 -6
  143. package/dist/session/claude/sessions.js +6 -13
  144. package/dist/session/codex/rollout-reader.d.ts +2 -2
  145. package/dist/session/codex/rollout-reader.js +57 -17
  146. package/dist/session/codex/sessions.d.ts +1 -1
  147. package/dist/session/codex/sessions.js +2 -2
  148. package/dist/session/common/content-blocks.d.ts +7 -0
  149. package/dist/session/common/content-blocks.js +13 -9
  150. package/dist/session/common/jsonl.d.ts +8 -25
  151. package/dist/session/common/jsonl.js +126 -261
  152. package/dist/session/common/pagination.d.ts +28 -0
  153. package/dist/session/common/pagination.js +33 -0
  154. package/dist/session/kimi/sessions.d.ts +34 -1
  155. package/dist/session/kimi/sessions.js +208 -98
  156. package/dist/session/opencode/sessions.d.ts +17 -3
  157. package/dist/session/opencode/sessions.js +29 -29
  158. package/dist/session/pi/sessions.d.ts +5 -1
  159. package/dist/session/pi/sessions.js +12 -16
  160. package/dist/session/session-store.d.ts +11 -1
  161. package/dist/session/session-store.js +75 -178
  162. package/dist/update/index.d.ts +2 -2
  163. package/dist/update/index.js +2 -2
  164. package/dist/update/install.d.ts +20 -2
  165. package/dist/update/install.js +51 -7
  166. package/package.json +1 -1
  167. package/dist/runner/codex/argv.d.ts +0 -33
  168. package/dist/runner/codex/argv.js +0 -40
  169. package/dist/runner/codex/jsonl.d.ts +0 -37
  170. package/dist/runner/codex/jsonl.js +0 -221
  171. package/dist/runner/codex/runner.d.ts +0 -63
  172. package/dist/runner/codex/runner.js +0 -124
  173. package/dist/runner/common/exec-translator.d.ts +0 -73
  174. package/dist/runner/common/exec-translator.js +0 -105
  175. package/dist/runner/kimi/jsonl.d.ts +0 -28
  176. package/dist/runner/kimi/jsonl.js +0 -143
  177. package/dist/runner/kimi/runner.d.ts +0 -49
  178. package/dist/runner/kimi/runner.js +0 -105
  179. package/dist/runner/opencode/argv.d.ts +0 -32
  180. package/dist/runner/opencode/argv.js +0 -35
  181. package/dist/runner/opencode/jsonl.d.ts +0 -68
  182. package/dist/runner/opencode/jsonl.js +0 -295
  183. package/dist/runner/opencode/runner.d.ts +0 -61
  184. package/dist/runner/opencode/runner.js +0 -93
  185. package/dist/runner/pi/jsonl.d.ts +0 -128
  186. package/dist/runner/pi/jsonl.js +0 -249
  187. package/dist/runner/pi/runner.d.ts +0 -36
  188. package/dist/runner/pi/runner.js +0 -76
  189. package/dist/session/common/catch-up.d.ts +0 -12
  190. package/dist/session/common/catch-up.js +0 -31
@@ -0,0 +1,377 @@
1
+ /**
2
+ * CodexAppServerRunner: workspace-lifetime runner using the Codex App Server
3
+ * protocol (`codex app-server`, JSON-RPC over stdio).
4
+ *
5
+ * Flow per run: acquire persistent connection → thread/start or thread/resume
6
+ * → turn/start → consume notifications (agent message deltas, approval server
7
+ * requests, …) until turn/completed → synthesize ResultEvent.
8
+ *
9
+ * Shares the ConnectionBasedRunner base with kimi/opencode: same notification
10
+ * queue + waitResolve pump, forceFinish/stopRequested semantics, turn idle
11
+ * watchdog, and ConnectionLostError respawn-and-retry-once.
12
+ */
13
+ import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
14
+ import { CodexAppServerTranslator } from './translator.js';
15
+ import { RpcErrorCode } from '../../common/acp/protocol-types.js';
16
+ import { getLogger } from '../../../logger/index.js';
17
+ import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
18
+ /**
19
+ * Build the protocol response body for a command/file approval decision.
20
+ * Structured decisions (acceptWithExecpolicyAmendment) carry the payload the
21
+ * server originally offered; if it is missing, fall back to plain accept.
22
+ */
23
+ function buildApprovalResponse(action, view) {
24
+ if (action === 'accept_for_session') {
25
+ return { decision: 'acceptForSession' };
26
+ }
27
+ if (action === 'accept_with_execpolicy_amendment') {
28
+ const payload = view.decisionPayloads?.['acceptWithExecpolicyAmendment'];
29
+ if (payload) {
30
+ return { decision: { acceptWithExecpolicyAmendment: payload } };
31
+ }
32
+ getLogger().warn('[codex-app-server-runner] acceptWithExecpolicyAmendment payload missing, falling back to accept');
33
+ return { decision: 'accept' };
34
+ }
35
+ return { decision: action };
36
+ }
37
+ /**
38
+ * Map the client-side SandboxMode enum to the response-side SandboxPolicy
39
+ * object used by `thread/settings/update` (`sandboxPolicy`).
40
+ */
41
+ export function sandboxModeToSandboxPolicy(mode) {
42
+ switch (mode) {
43
+ case 'read-only':
44
+ return { type: 'readOnly', networkAccess: false };
45
+ case 'workspace-write':
46
+ return {
47
+ type: 'workspaceWrite',
48
+ writableRoots: [],
49
+ networkAccess: false,
50
+ excludeTmpdirEnvVar: false,
51
+ excludeSlashTmp: false,
52
+ };
53
+ case 'danger-full-access':
54
+ return { type: 'dangerFullAccess' };
55
+ }
56
+ }
57
+ export class CodexAppServerRunner extends ConnectionBasedRunner {
58
+ connectionManager;
59
+ currentTranslator = null;
60
+ activeThreadId = null;
61
+ model;
62
+ modelProvider;
63
+ reasoningEffort;
64
+ sandboxConfig;
65
+ approvalPolicyConfig;
66
+ /** Pending approval requests: requestId (JSON-RPC id) → kind + view. */
67
+ pendingApprovals = new Map();
68
+ constructor(opts) {
69
+ super({ kind: opts.kind, sessionReader: opts.sessionReader }, opts.turnTimeoutMs);
70
+ this.model = opts.model;
71
+ this.modelProvider = opts.modelProvider;
72
+ this.reasoningEffort = opts.reasoningEffort;
73
+ this.sandboxConfig = opts.sandbox;
74
+ this.approvalPolicyConfig = opts.approvalPolicy;
75
+ const managerOpts = {
76
+ binary: opts.binary ?? 'codex',
77
+ args: opts.appServerArgs ?? ['app-server', '--stdio'],
78
+ env: opts.env,
79
+ requestTimeoutMs: opts.requestTimeoutMs,
80
+ idleTtlMs: opts.idleTtlMs,
81
+ // `experimentalApi: true` is required for `thread/settings/update`, which
82
+ // lets config.save push approval/sandbox changes to a live thread instead
83
+ // of waiting for the workspace-lifetime runner to be recreated.
84
+ initializeParams: {
85
+ clientInfo: {
86
+ name: 'lark-remote',
87
+ version: '1.0.0',
88
+ },
89
+ capabilities: {
90
+ experimentalApi: true,
91
+ },
92
+ },
93
+ };
94
+ this.connectionManager = new ConnectionManager(managerOpts);
95
+ }
96
+ get logTag() {
97
+ return 'codex-app-server-runner';
98
+ }
99
+ get turnTimeoutErrorMessage() {
100
+ return 'Codex app-server turn timed out';
101
+ }
102
+ get turnInterruptedErrorMessage() {
103
+ return 'Codex app-server turn interrupted';
104
+ }
105
+ currentSessionId() {
106
+ return this.activeThreadId;
107
+ }
108
+ shouldDeferStop() {
109
+ return !this.currentTurnId;
110
+ }
111
+ async cancelCurrentTurn() {
112
+ if (!this.currentClient || !this.activeThreadId || !this.currentTurnId)
113
+ return;
114
+ try {
115
+ await this.currentClient.request('turn/interrupt', {
116
+ threadId: this.activeThreadId,
117
+ turnId: this.currentTurnId,
118
+ });
119
+ }
120
+ catch (err) {
121
+ getLogger().warn(`[${this.logTag}] interrupt failed: ${err.message}`);
122
+ }
123
+ }
124
+ clearTurnState() {
125
+ this.currentTranslator = null;
126
+ this.activeThreadId = null;
127
+ }
128
+ async releaseConnection(cwd) {
129
+ await this.connectionManager.release(cwd);
130
+ }
131
+ notifyIdle(cwd) {
132
+ this.connectionManager.notifyIdle(cwd);
133
+ }
134
+ async disposeConnections() {
135
+ await this.connectionManager.disposeAll();
136
+ }
137
+ /**
138
+ * Run a compact operation on the current thread.
139
+ */
140
+ async *runCompact(_message, opts) {
141
+ yield* this.executeTurn(opts, async () => {
142
+ const client = await this.connectionManager.acquire(opts.cwd);
143
+ this.connectionManager.notifyActivity(opts.cwd);
144
+ this.currentClient = client;
145
+ client.setHooks({
146
+ onNotification: (method, params) => this.handleNotification(method, params),
147
+ onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
148
+ onClose: () => this.failTurn('Codex app-server connection closed'),
149
+ });
150
+ if (!opts.sessionId) {
151
+ throw new Error('compact requires a sessionId');
152
+ }
153
+ this.activeThreadId = opts.sessionId;
154
+ const translator = new CodexAppServerTranslator();
155
+ translator.setOperationKind('compact');
156
+ this.currentTranslator = translator;
157
+ // 冷连接兜底:thread/compact/start 要求线程已加载。真实协议里对未加载
158
+ // 的线程直接 compact 返回 -32600 "thread not found"(codex-cli 0.147.0
159
+ // 实测),只有先 thread/resume(或 thread/start)把线程载入内存才能
160
+ // 压缩。连接池复用期间线程本就在内存,resume 幂等无害;连接被
161
+ // /stop、idle TTL 或进程重建后,这一步是 Compact 能工作的前提。
162
+ const resumeParams = {
163
+ threadId: opts.sessionId,
164
+ ...this.buildThreadParams(opts.cwd),
165
+ };
166
+ await client.request('thread/resume', resumeParams);
167
+ await client.request('thread/compact/start', { threadId: opts.sessionId });
168
+ });
169
+ }
170
+ /**
171
+ * Respond to an approval server request. `response` is the bridge
172
+ * ApprovalAction (`{ action: 'accept' | 'accept_for_session' | 'decline' | 'cancel' }`).
173
+ */
174
+ async respondApproval(requestId, response) {
175
+ const client = this.currentClient;
176
+ const pending = this.pendingApprovals.get(requestId);
177
+ if (!client || !pending)
178
+ return;
179
+ const action = response?.action ?? 'decline';
180
+ if (pending.kind === 'permissions') {
181
+ // 权限审批的响应没有 decision 字段(真实协议只回 granted profile +
182
+ // scope):decline/cancel 必须返回空授权(拒绝全部),否则会把用户已
183
+ // 勾选的条目当作授予返回(2026-08-12 review 发现:勾选后点「拒绝」实
184
+ // 际授予了所选权限)。accept/acceptForSession 才带用户勾选的条目。
185
+ const denied = action === 'decline' || action === 'cancel';
186
+ client.respond(requestId, {
187
+ permissions: denied
188
+ ? this.denyAllPermissions()
189
+ : this.buildGrantedPermissions(pending.view),
190
+ scope: action === 'accept_for_session' ? 'session' : 'turn',
191
+ });
192
+ }
193
+ else {
194
+ client.respond(requestId, buildApprovalResponse(action, pending.view));
195
+ }
196
+ this.pendingApprovals.delete(requestId);
197
+ getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} kind=${pending.kind} action=${action}`);
198
+ }
199
+ getStatusInfo() {
200
+ return {
201
+ kind: this.kind,
202
+ model: this.model ?? '(app-server)',
203
+ provider: this.modelProvider,
204
+ reasoning: this.reasoningEffort,
205
+ extras: {
206
+ mode: 'app-server',
207
+ ...(this.sandboxConfig ? { sandbox: this.sandboxConfig } : {}),
208
+ ...(this.approvalPolicyConfig ? { approvalPolicy: String(this.approvalPolicyConfig) } : {}),
209
+ },
210
+ };
211
+ }
212
+ /**
213
+ * Unified approval-mode hot-update entry (called by the bridge via duck
214
+ * typing on active runs). Codex's `thread/settings/update` applies the new
215
+ * approval/sandbox policy to subsequent turns, even while the current turn
216
+ * is still active. This lets `/config` take effect without waiting for this
217
+ * workspace-lifetime runner to be recreated/evicted. Local fields are always
218
+ * updated so `/status` reflects the new config immediately; if there is no
219
+ * live thread, the next `thread/start`/`thread/resume` uses the new values
220
+ * anyway.
221
+ */
222
+ async updateApprovalMode(settings) {
223
+ if (settings.approvalPolicy !== undefined) {
224
+ this.approvalPolicyConfig = settings.approvalPolicy;
225
+ }
226
+ if (settings.sandbox !== undefined) {
227
+ this.sandboxConfig = settings.sandbox;
228
+ }
229
+ if (!this.currentClient || !this.activeThreadId) {
230
+ return;
231
+ }
232
+ const params = {
233
+ threadId: this.activeThreadId,
234
+ ...(settings.approvalPolicy !== undefined ? { approvalPolicy: settings.approvalPolicy } : {}),
235
+ ...(settings.sandbox !== undefined
236
+ ? { sandboxPolicy: sandboxModeToSandboxPolicy(settings.sandbox) }
237
+ : {}),
238
+ };
239
+ try {
240
+ await this.currentClient.request('thread/settings/update', params);
241
+ getLogger().info(`[${this.logTag}] thread/settings/update applied for thread=${this.activeThreadId}`);
242
+ }
243
+ catch (err) {
244
+ // Config save must not fail because a live thread rejected an experimental
245
+ // update (e.g. older Codex without thread/settings/update). The local
246
+ // snapshot is already updated, so /status and the next thread start/resume
247
+ // still use the new values.
248
+ getLogger().warn(`[${this.logTag}] thread/settings/update failed: ${err.message}`);
249
+ }
250
+ }
251
+ // =========================================================================
252
+ // Internal
253
+ // =========================================================================
254
+ buildThreadParams(cwd) {
255
+ const params = {
256
+ cwd,
257
+ ...(this.model ? { model: this.model } : {}),
258
+ ...(this.modelProvider ? { modelProvider: this.modelProvider } : {}),
259
+ ...(this.approvalPolicyConfig ? { approvalPolicy: this.approvalPolicyConfig } : {}),
260
+ ...(this.sandboxConfig ? { sandbox: this.sandboxConfig } : {}),
261
+ };
262
+ return params;
263
+ }
264
+ buildTurnParams(message, opts) {
265
+ const params = {
266
+ threadId: this.activeThreadId ?? '',
267
+ input: [{ type: 'text', text: message }],
268
+ ...((opts.model ?? this.model) ? { model: opts.model ?? this.model } : {}),
269
+ ...((opts.reasoningEffort ?? this.reasoningEffort)
270
+ ? { effort: opts.reasoningEffort ?? this.reasoningEffort }
271
+ : {}),
272
+ };
273
+ return params;
274
+ }
275
+ /**
276
+ * Acquire the connection and set up the turn: thread/start(新会话)或
277
+ * thread/resume(按 sessionId 恢复既有线程)→ turn/start。
278
+ */
279
+ async setupTurn(message, opts) {
280
+ const client = await this.connectionManager.acquire(opts.cwd);
281
+ this.connectionManager.notifyActivity(opts.cwd);
282
+ this.currentClient = client;
283
+ client.setHooks({
284
+ onNotification: (method, params) => this.handleNotification(method, params),
285
+ onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
286
+ onClose: () => {
287
+ getLogger().warn(`[${this.logTag}] client connection closed`);
288
+ this.failTurn('Codex app-server connection closed');
289
+ },
290
+ });
291
+ const threadParams = this.buildThreadParams(opts.cwd);
292
+ let threadId;
293
+ if (opts.sessionId) {
294
+ const resumeParams = {
295
+ threadId: opts.sessionId,
296
+ ...threadParams,
297
+ };
298
+ await client.request('thread/resume', resumeParams);
299
+ threadId = opts.sessionId;
300
+ }
301
+ else {
302
+ const threadResult = await client.request('thread/start', threadParams);
303
+ // 会话键假设(review P2-4):主线程 thread.id === session_meta.session_id,
304
+ // 故 thread.id 可同时用作协议 threadId 与 store/session reader 的 session
305
+ // 键(bridge 用 turn_started 通知的 threadId 写回,/resume、Compact 按此
306
+ // 键定位 JSONL)。forked/subagent 线程二者会分叉(openai/codex#29327),
307
+ // 桥只把主线程作为顶层会话,不在此链路内。
308
+ threadId = threadResult.thread.id;
309
+ }
310
+ this.activeThreadId = threadId;
311
+ const translator = new CodexAppServerTranslator();
312
+ this.currentTranslator = translator;
313
+ const turnParams = this.buildTurnParams(message, opts);
314
+ const turnResult = await client.request('turn/start', turnParams);
315
+ this.currentTurnId = turnResult.turn.id;
316
+ }
317
+ handleNotification(method, params) {
318
+ const events = this.currentTranslator?.handleNotification(method, params) ?? [];
319
+ this.pushEvents(events);
320
+ }
321
+ handleServerRequest(id, method, params) {
322
+ const events = this.currentTranslator?.handleServerRequest(id, method, params) ?? [];
323
+ if (events.length === 0) {
324
+ // 未处理/不支持的服务端请求必须显式响应(error 即"拒绝",turn 继续),
325
+ // 否则服务端会一直等待响应(applyPatchApproval、item/tool/requestUserInput、
326
+ // mcpServer/elicitation/request 等)。
327
+ this.currentClient?.respondError(id, RpcErrorCode.METHOD_NOT_FOUND, `Unsupported server request: ${method}`);
328
+ return;
329
+ }
330
+ for (const ev of events) {
331
+ if (ev.type === 'approval_requested') {
332
+ this.pendingApprovals.set(ev.requestId, { kind: ev.kind, view: ev.view });
333
+ getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
334
+ }
335
+ }
336
+ this.pushEvents(events);
337
+ }
338
+ buildGrantedPermissions(view) {
339
+ const items = view.permissions?.items ?? [];
340
+ const read = [];
341
+ const write = [];
342
+ let networkEnabled = false;
343
+ for (const item of items) {
344
+ if (!item.selected)
345
+ continue;
346
+ if (item.target.kind === 'fsRead') {
347
+ read.push(item.target.path);
348
+ }
349
+ else if (item.target.kind === 'fsWrite') {
350
+ write.push(item.target.path);
351
+ }
352
+ else if (item.target.kind === 'network') {
353
+ networkEnabled = true;
354
+ }
355
+ }
356
+ // 真实 GrantedPermissionProfile 形状(v1 schema):fileSystem 用
357
+ // entries[{path, access}](read/write 为 legacy 字符串数组,即将移除),
358
+ // network 是 { enabled } 布尔开关,不是 host 列表。
359
+ return {
360
+ fileSystem: {
361
+ entries: [
362
+ ...read.map((path) => ({ path, access: 'read' })),
363
+ ...write.map((path) => ({ path, access: 'write' })),
364
+ ],
365
+ },
366
+ network: { enabled: networkEnabled },
367
+ };
368
+ }
369
+ /** 拒绝权限审批:空 fileSystem + 关闭网络(真实 GrantedPermissionProfile)。 */
370
+ denyAllPermissions() {
371
+ return {
372
+ fileSystem: { entries: [] },
373
+ network: { enabled: false },
374
+ };
375
+ }
376
+ }
377
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1,89 @@
1
+ /**
2
+ * CodexAppServerTranslator: translates protocol notifications and server
3
+ * requests into AgentEvents.
4
+ *
5
+ * The translator is stateful per turn — it tracks the current turn's state,
6
+ * accumulates deltas, and synthesizes the final ResultEvent when the turn
7
+ * completes. Method names and parameter shapes follow the real Codex
8
+ * app-server v2 protocol (see protocol-types.ts).
9
+ */
10
+ import type { AgentEvent, TurnStartedEvent, TurnDiffEvent } from '../../types.js';
11
+ import type { ApprovalRequestedEvent, ApprovalResolvedEvent } from '../../types.js';
12
+ import { type TurnCompletedNotification } from './protocol-types.js';
13
+ export type TranslatorEvent = AgentEvent | TurnStartedEvent | TurnDiffEvent | ApprovalRequestedEvent | ApprovalResolvedEvent;
14
+ export declare class CodexAppServerTranslator {
15
+ private turnId;
16
+ private threadId;
17
+ private turnStatus;
18
+ /** Full agentMessage text per item id (item-scoped snapshot). */
19
+ private textByItem;
20
+ /**
21
+ * Reasoning content per item id: paragraphs keyed by `contentIndex`
22
+ * (textDelta) plus the summary stream (summaryTextDelta). Each reasoning
23
+ * item is isolated so interleaved items keep their own chronology.
24
+ */
25
+ private reasoningByItem;
26
+ /** Full command output per item id (item-scoped snapshot). */
27
+ private toolOutputByItem;
28
+ /** Full plan text per item id (item-scoped snapshot). */
29
+ private planByItem;
30
+ /** commandExecution items that emitted item/started (tool block position anchor). */
31
+ private startedToolItems;
32
+ /** fileChange items seen this turn, keyed by item id (item/started). */
33
+ private fileChangeItems;
34
+ /** File approvals whose item/started has not arrived yet (out-of-order flow). */
35
+ private pendingFileApprovalByItemId;
36
+ private usage;
37
+ private operationKind;
38
+ /**
39
+ * Handle a notification from the server and return events.
40
+ */
41
+ handleNotification(method: string, params: unknown): TranslatorEvent[];
42
+ /**
43
+ * Handle a server request (incoming JSON-RPC request from server, e.g. approval).
44
+ */
45
+ handleServerRequest(id: number | string, method: string, params: unknown): TranslatorEvent[];
46
+ /**
47
+ * Synthesize a ResultEvent from the current turn state.
48
+ * Called when the turn completes.
49
+ */
50
+ handleTurnCompleted(params?: TurnCompletedNotification['params']): TranslatorEvent[];
51
+ /**
52
+ * Get the current usage stats.
53
+ */
54
+ getUsage(): {
55
+ inputTokens: number;
56
+ outputTokens: number;
57
+ };
58
+ /**
59
+ * Get the current turn status.
60
+ */
61
+ getTurnStatus(): string;
62
+ /**
63
+ * Set the operation kind (exec vs compact).
64
+ */
65
+ setOperationKind(kind: 'exec' | 'compact'): void;
66
+ private handleTurnStarted;
67
+ private handleAgentMessageDelta;
68
+ private handleReasoningDelta;
69
+ private handleReasoningPartAdded;
70
+ private handleItemCompleted;
71
+ private handleReasoningTextDelta;
72
+ private handleCommandOutputDelta;
73
+ private handlePlanUpdated;
74
+ private handleTokenUsageUpdated;
75
+ private handleError;
76
+ private handleThreadCompacted;
77
+ private handleServerRequestResolved;
78
+ private handleCommandApproval;
79
+ private handleFileApproval;
80
+ private handleItemStarted;
81
+ private handlePermissionsApproval;
82
+ private buildPermissionItems;
83
+ /** Get (or lazily create) the reasoning accumulator for one item. */
84
+ private reasoningEntry;
85
+ /** Compose the display snapshot for one reasoning item (content + summary). */
86
+ private reasoningSnapshotFor;
87
+ /** Receive-time stamp, aligned with the exec JSONL parse path. */
88
+ private now;
89
+ }