@triflux/remote 10.0.1 → 10.25.1

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 (155) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +68 -7
  3. package/hub/server.mjs +2181 -701
  4. package/hub/store-adapter.mjs +101 -37
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +84 -17
  9. package/hub/team/build-worker-prompt.mjs +42 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +219 -0
  12. package/hub/team/claude-native-bridge.mjs +784 -0
  13. package/hub/team/claude-session-projection.mjs +65 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +154 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +89 -12
  24. package/hub/team/cli/commands/start/start-headless.mjs +127 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/index.mjs +2 -2
  33. package/hub/team/cli/manifest.mjs +38 -8
  34. package/hub/team/cli/render.mjs +52 -30
  35. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  36. package/hub/team/cli/services/hub-client.mjs +45 -17
  37. package/hub/team/cli/services/member-selector.mjs +37 -30
  38. package/hub/team/cli/services/native-control.mjs +144 -116
  39. package/hub/team/cli/services/runtime-mode.mjs +55 -1
  40. package/hub/team/cli/services/state-store.mjs +66 -9
  41. package/hub/team/cli/services/task-model.mjs +40 -30
  42. package/hub/team/codex-review.mjs +521 -0
  43. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  44. package/hub/team/conductor-registry.mjs +121 -0
  45. package/hub/team/conductor.mjs +1379 -671
  46. package/hub/team/consensus-meta.mjs +176 -0
  47. package/hub/team/dashboard-anchor.mjs +4 -5
  48. package/hub/team/dashboard-layout.mjs +3 -1
  49. package/hub/team/dashboard-open.mjs +21 -119
  50. package/hub/team/dashboard.mjs +76 -28
  51. package/hub/team/event-log.mjs +18 -10
  52. package/hub/team/execution-mode.mjs +241 -0
  53. package/hub/team/extract-completion-payload.mjs +110 -0
  54. package/hub/team/git-preflight.mjs +286 -0
  55. package/hub/team/handoff.mjs +49 -18
  56. package/hub/team/headless-bridge-session.mjs +17 -0
  57. package/hub/team/headless.mjs +2065 -1156
  58. package/hub/team/health-probe.mjs +232 -69
  59. package/hub/team/launcher-template.mjs +23 -13
  60. package/hub/team/lead-control.mjs +91 -4
  61. package/hub/team/mcp-selector.mjs +145 -0
  62. package/hub/team/native-supervisor.mjs +90 -26
  63. package/hub/team/native.mjs +74 -35
  64. package/hub/team/nativeProxy.mjs +184 -113
  65. package/hub/team/notify.mjs +147 -78
  66. package/hub/team/orchestrator.mjs +173 -161
  67. package/hub/team/pane.mjs +43 -15
  68. package/hub/team/process-cleanup.mjs +69 -25
  69. package/hub/team/psmux.mjs +1067 -264
  70. package/hub/team/recovery-store.mjs +149 -0
  71. package/hub/team/remote-probe.mjs +69 -53
  72. package/hub/team/remote-session.mjs +131 -59
  73. package/hub/team/remote-watcher.mjs +62 -34
  74. package/hub/team/retry-state-machine.mjs +267 -0
  75. package/hub/team/routing.mjs +51 -25
  76. package/hub/team/runtime-strategy.mjs +200 -0
  77. package/hub/team/schemas/worker-completion.json +39 -0
  78. package/hub/team/sentinel-capture.mjs +127 -0
  79. package/hub/team/session-sync.mjs +153 -6
  80. package/hub/team/session.mjs +605 -611
  81. package/hub/team/shared.mjs +13 -13
  82. package/hub/team/staleState.mjs +72 -30
  83. package/hub/team/swarm-cli.mjs +390 -0
  84. package/hub/team/swarm-hypervisor.mjs +1526 -198
  85. package/hub/team/swarm-intent.mjs +216 -0
  86. package/hub/team/swarm-locks.mjs +140 -25
  87. package/hub/team/swarm-planner.mjs +121 -21
  88. package/hub/team/swarm-preflight.mjs +317 -0
  89. package/hub/team/swarm-reconciler.mjs +128 -23
  90. package/hub/team/synapse-cli.mjs +458 -0
  91. package/hub/team/synapse-http.mjs +60 -0
  92. package/hub/team/synapse-registry.mjs +329 -0
  93. package/hub/team/terminal-opener.mjs +216 -0
  94. package/hub/team/tui-core.mjs +288 -0
  95. package/hub/team/tui-lite.mjs +234 -146
  96. package/hub/team/tui-remote-adapter.mjs +21 -37
  97. package/hub/team/tui-synapse.mjs +223 -0
  98. package/hub/team/tui-viewer.mjs +104 -45
  99. package/hub/team/tui-widgets.mjs +262 -0
  100. package/hub/team/tui.mjs +936 -449
  101. package/hub/team/worker-completion-validator.mjs +84 -0
  102. package/hub/team/worker-sandbox.mjs +93 -0
  103. package/hub/team/worker-signal.mjs +263 -0
  104. package/hub/team/worker-signal.types.d.ts +52 -0
  105. package/hub/team/worktree-lifecycle.mjs +518 -64
  106. package/hub/team/wt-manager.mjs +297 -26
  107. package/hub/tools.mjs +474 -252
  108. package/hub/tray.mjs +411 -368
  109. package/hub/workers/claude-worker.mjs +162 -118
  110. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  111. package/hub/workers/codex-mcp.mjs +724 -507
  112. package/hub/workers/delegator-mcp.mjs +663 -337
  113. package/hub/workers/factory.mjs +100 -21
  114. package/hub/workers/gemini-worker.mjs +179 -86
  115. package/hub/workers/interface.mjs +23 -2
  116. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  117. package/hub/workers/worker-utils.mjs +22 -16
  118. package/package.json +4 -3
  119. package/scripts/lib/agent-json.mjs +27 -0
  120. package/scripts/lib/async.mjs +174 -0
  121. package/scripts/lib/claudemd-scanner.mjs +223 -0
  122. package/scripts/lib/cli-agy.mjs +62 -0
  123. package/scripts/lib/cli-claude.mjs +78 -0
  124. package/scripts/lib/cli-codex.mjs +199 -0
  125. package/scripts/lib/cli-gemini.mjs +67 -0
  126. package/scripts/lib/codex-recovery.sh +50 -0
  127. package/scripts/lib/context.mjs +67 -0
  128. package/scripts/lib/cross-review-utils.mjs +59 -0
  129. package/scripts/lib/doctor-env-checks.mjs +121 -0
  130. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  131. package/scripts/lib/env-probe.mjs +269 -0
  132. package/scripts/lib/env.mjs +14 -0
  133. package/scripts/lib/gemini-profiles.mjs +119 -0
  134. package/scripts/lib/handoff.mjs +187 -0
  135. package/scripts/lib/hook-utils.mjs +16 -0
  136. package/scripts/lib/hub.mjs +48 -0
  137. package/scripts/lib/keyword-rules.mjs +195 -0
  138. package/scripts/lib/logger.mjs +105 -0
  139. package/scripts/lib/mcp-filter.mjs +883 -0
  140. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  141. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  142. package/scripts/lib/mcp-health.mjs +773 -0
  143. package/scripts/lib/mcp-manifest.mjs +89 -0
  144. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  145. package/scripts/lib/pid.mjs +63 -0
  146. package/scripts/lib/process-utils.mjs +26 -0
  147. package/scripts/lib/psmux-info.mjs +179 -0
  148. package/scripts/lib/quota.mjs +47 -0
  149. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  150. package/scripts/lib/skill-state.mjs +220 -0
  151. package/scripts/lib/skill-template.mjs +375 -0
  152. package/scripts/lib/team.mjs +75 -0
  153. package/scripts/lib/timeout.mjs +66 -0
  154. package/scripts/lib/tmp.mjs +26 -0
  155. package/scripts/lib/toml.mjs +113 -0
@@ -0,0 +1,1015 @@
1
+ // hub/workers/codex-app-server-worker.mjs — Codex app-server JSON-RPC 2.0 worker
2
+ //
3
+ // Spawns `codex app-server --listen stdio://` and streams notifications as
4
+ // `agent.progress` publish events. Shares the `WorkerInterface` shape with
5
+ // `codex-mcp.mjs` for factory compatibility but uses a different transport.
6
+ //
7
+ // Phase 3 / PRD-2 — implements AC1..AC17 (except AC9 factory integration which
8
+ // lands in PRD-3 and AC11 regression which is purely "do not touch codex-mcp").
9
+
10
+ import { spawn } from "node:child_process";
11
+ import { createHash, randomUUID } from "node:crypto";
12
+ import { isAbsolute } from "node:path";
13
+ import process from "node:process";
14
+
15
+ import {
16
+ JsonRpcProtocolError,
17
+ JsonRpcStdioClient,
18
+ JsonRpcTransportError,
19
+ } from "./lib/jsonrpc-stdio.mjs";
20
+
21
+ // ── Exit codes ──────────────────────────────────────────────────
22
+ // Reuse codex-mcp convention so factory + retry logic can stay uniform.
23
+ export const CODEX_APP_SERVER_EXECUTION_EXIT_CODE = 1;
24
+ export const CODEX_APP_SERVER_TRANSPORT_EXIT_CODE = 70;
25
+ export const CODEX_APP_SERVER_TIMEOUT_EXIT_CODE = 124;
26
+
27
+ export const DEFAULT_CODEX_APP_SERVER_BOOTSTRAP_TIMEOUT_MS = 10_000;
28
+ export const DEFAULT_CODEX_APP_SERVER_EXECUTION_TIMEOUT_MS = 10 * 60 * 1000;
29
+ export const DEFAULT_UNKNOWN_METHOD_WARN_THRESHOLD = 5;
30
+ /**
31
+ * How long stop() waits for `thread/unsubscribe` response before forcing
32
+ * SIGTERM. Keep small — unsubscribe is a best-effort unload signal.
33
+ */
34
+ export const UNSUBSCRIBE_DEADLINE_MS = 2_000;
35
+
36
+ // ── Error classes ───────────────────────────────────────────────
37
+
38
+ /**
39
+ * Transport/bootstrap failures (spawn ENOENT, initialize timeout, stream close).
40
+ */
41
+ export class CodexAppServerTransportError extends Error {
42
+ /**
43
+ * @param {string} message
44
+ * @param {{ cause?: unknown, stderr?: string }} [options]
45
+ */
46
+ constructor(message, options = {}) {
47
+ super(message, { cause: options.cause });
48
+ this.name = "CodexAppServerTransportError";
49
+ this.stderr = options.stderr || "";
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Protocol-level failures (malformed JSON, max line exceeded, unresolved pending
55
+ * requests on EOF).
56
+ */
57
+ export class CodexAppServerProtocolError extends Error {
58
+ /** @param {string} message @param {{ cause?: unknown }} [options] */
59
+ constructor(message, options = {}) {
60
+ super(message, { cause: options.cause });
61
+ this.name = "CodexAppServerProtocolError";
62
+ }
63
+ }
64
+
65
+ // ── Protocol constants ──────────────────────────────────────────
66
+
67
+ /** 24 notification methods we forward as publish events. */
68
+ export const SUBSCRIBED_METHODS = Object.freeze(
69
+ new Set([
70
+ // thread lifecycle
71
+ "thread/started",
72
+ "thread/closed",
73
+ "thread/tokenUsage/updated",
74
+ "thread/compacted",
75
+ // turn lifecycle
76
+ "turn/started",
77
+ "turn/completed",
78
+ "turn/diff/updated",
79
+ "turn/plan/updated",
80
+ // item lifecycle
81
+ "item/started",
82
+ "item/completed",
83
+ "item/autoApprovalReview/started",
84
+ "item/autoApprovalReview/completed",
85
+ // streaming deltas
86
+ "item/agentMessage/delta",
87
+ "item/reasoning/summaryTextDelta",
88
+ "item/reasoning/textDelta",
89
+ "item/reasoning/summaryPartAdded",
90
+ "item/plan/delta",
91
+ "item/mcpToolCall/progress",
92
+ "command/exec/outputDelta",
93
+ "item/commandExecution/outputDelta",
94
+ "item/commandExecution/terminalInteraction",
95
+ "item/fileChange/outputDelta",
96
+ // errors / model
97
+ "error",
98
+ "model/rerouted",
99
+ "configWarning",
100
+ ]),
101
+ );
102
+
103
+ /** 27 notification methods registered at initialize so codex never emits them. */
104
+ export const OPT_OUT_METHODS = Object.freeze([
105
+ "skills/changed",
106
+ "mcpServer/oauthLogin/completed",
107
+ "mcpServer/startupStatus/updated",
108
+ "account/updated",
109
+ "account/rateLimits/updated",
110
+ "account/login/completed",
111
+ "app/list/updated",
112
+ "fs/changed",
113
+ "thread/realtime/started",
114
+ "thread/realtime/itemAdded",
115
+ "thread/realtime/transcriptUpdated",
116
+ "thread/realtime/outputAudio/delta",
117
+ "thread/realtime/sdp",
118
+ "thread/realtime/error",
119
+ "thread/realtime/closed",
120
+ "fuzzyFileSearch/sessionUpdated",
121
+ "fuzzyFileSearch/sessionCompleted",
122
+ "windows/worldWritableWarning",
123
+ "windowsSandbox/setupCompleted",
124
+ "rawResponseItem/completed",
125
+ "serverRequest/resolved",
126
+ "hook/started",
127
+ "hook/completed",
128
+ "thread/archived",
129
+ "thread/unarchived",
130
+ "thread/name/updated",
131
+ "deprecationNotice",
132
+ ]);
133
+
134
+ /** Method → publish `kind` (13 distinct kinds). */
135
+ export const KIND_MAP = Object.freeze({
136
+ "thread/started": "thread_status",
137
+ "thread/closed": "thread_status",
138
+ "thread/tokenUsage/updated": "thread_status",
139
+ "thread/compacted": "thread_status",
140
+ "turn/started": "turn_status",
141
+ "turn/completed": "turn_status",
142
+ "turn/diff/updated": "turn_status",
143
+ "turn/plan/updated": "turn_status",
144
+ "item/started": "item_status",
145
+ "item/completed": "item_status",
146
+ "item/autoApprovalReview/started": "approval_review",
147
+ "item/autoApprovalReview/completed": "approval_review",
148
+ "item/agentMessage/delta": "text_delta",
149
+ "item/reasoning/summaryTextDelta": "thinking_delta",
150
+ "item/reasoning/textDelta": "thinking_delta",
151
+ "item/reasoning/summaryPartAdded": "thinking_delta",
152
+ "item/plan/delta": "plan_delta",
153
+ "item/mcpToolCall/progress": "tool_progress",
154
+ "command/exec/outputDelta": "exec_delta",
155
+ "item/commandExecution/outputDelta": "exec_delta",
156
+ "item/commandExecution/terminalInteraction": "terminal_interaction",
157
+ "item/fileChange/outputDelta": "file_delta",
158
+ error: "error",
159
+ "model/rerouted": "error",
160
+ configWarning: "error",
161
+ });
162
+
163
+ export const DEFAULT_CODEX_APP_SERVER_ARGS = Object.freeze([
164
+ "--skip-git-repo-check",
165
+ "app-server",
166
+ "--listen",
167
+ "stdio://",
168
+ ]);
169
+
170
+ // ── Helpers ─────────────────────────────────────────────────────
171
+
172
+ function cloneEnv(env = process.env) {
173
+ return Object.fromEntries(
174
+ Object.entries(env).filter(([, value]) => typeof value === "string"),
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Strictly monotonic `Date.now()` — if two publishes collide in the same tick we
180
+ * bump by 1 ms. Keeps AC12 simple without `performance.now` float math.
181
+ */
182
+ function createMonotonicClock() {
183
+ let last = 0;
184
+ return () => {
185
+ const now = Date.now();
186
+ last = now > last ? now : last + 1;
187
+ return last;
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Walk a publish `data` payload and hash any string longer than 32 chars (AC17).
193
+ * Short identifiers (ids, statuses) are kept readable.
194
+ */
195
+ function redactDeep(value) {
196
+ if (value == null) return value;
197
+ if (typeof value === "string") {
198
+ if (value.length <= 32) return value;
199
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
200
+ }
201
+ if (Array.isArray(value)) return value.map(redactDeep);
202
+ if (typeof value === "object") {
203
+ const out = {};
204
+ for (const [k, v] of Object.entries(value)) out[k] = redactDeep(v);
205
+ return out;
206
+ }
207
+ return value;
208
+ }
209
+
210
+ function buildSpawnOptions({ cwd, env, command }) {
211
+ return {
212
+ cwd,
213
+ env,
214
+ stdio: ["pipe", "pipe", "pipe"],
215
+ // Windows needs shell for PATHEXT/ENOENT resolution when command is not absolute
216
+ shell: process.platform === "win32" && !isAbsolute(command),
217
+ windowsHide: true,
218
+ };
219
+ }
220
+
221
+ function buildInitializeParams(clientInfo) {
222
+ return {
223
+ clientInfo,
224
+ capabilities: {
225
+ experimentalApi: true,
226
+ optOutNotificationMethods: [...OPT_OUT_METHODS],
227
+ },
228
+ };
229
+ }
230
+
231
+ function buildThreadStartParams(opts = {}) {
232
+ const params = {
233
+ sandbox: opts.sandbox || "read-only",
234
+ approvalPolicy: opts.approvalPolicy || "never",
235
+ ephemeral: true,
236
+ experimentalRawEvents: false,
237
+ persistExtendedHistory: false,
238
+ };
239
+ if (typeof opts.model === "string" && opts.model) params.model = opts.model;
240
+ if (typeof opts.profile === "string" && opts.profile) {
241
+ params.serviceName = opts.profile;
242
+ }
243
+ if (opts.config && typeof opts.config === "object")
244
+ params.config = opts.config;
245
+ if (typeof opts.cwd === "string" && opts.cwd) params.cwd = opts.cwd;
246
+ if (typeof opts.baseInstructions === "string" && opts.baseInstructions) {
247
+ params.baseInstructions = opts.baseInstructions;
248
+ }
249
+ if (
250
+ typeof opts.developerInstructions === "string" &&
251
+ opts.developerInstructions
252
+ ) {
253
+ params.developerInstructions = opts.developerInstructions;
254
+ }
255
+ return params;
256
+ }
257
+
258
+ function buildTurnStartParams(threadId, prompt) {
259
+ return {
260
+ threadId,
261
+ input: [{ type: "text", text: prompt, text_elements: [] }],
262
+ };
263
+ }
264
+
265
+ function buildWorkerError(code, message, attempts = 1) {
266
+ return Object.freeze({
267
+ code,
268
+ retryable: code === "CODEX_APP_SERVER_TRANSPORT_ERROR",
269
+ attempts,
270
+ category:
271
+ code === "INVALID_INPUT"
272
+ ? "input"
273
+ : code === "CODEX_APP_SERVER_TRANSPORT_ERROR"
274
+ ? "transient"
275
+ : "config",
276
+ recovery:
277
+ code === "INVALID_INPUT"
278
+ ? "Provide a non-empty prompt before invoking the Codex app-server worker."
279
+ : "Review the codex app-server stderr tail and retry.",
280
+ message,
281
+ });
282
+ }
283
+
284
+ function extractThreadIdFromStartResponse(result) {
285
+ if (!result || typeof result !== "object") return null;
286
+ if (typeof result.threadId === "string") return result.threadId;
287
+ if (result.thread && typeof result.thread.id === "string") {
288
+ return result.thread.id;
289
+ }
290
+ return null;
291
+ }
292
+
293
+ function extractThreadIdFromStartedNotif(params) {
294
+ if (!params || typeof params !== "object") return null;
295
+ if (typeof params.threadId === "string") return params.threadId;
296
+ if (params.thread && typeof params.thread.id === "string") {
297
+ return params.thread.id;
298
+ }
299
+ return null;
300
+ }
301
+
302
+ // ── Worker class ────────────────────────────────────────────────
303
+
304
+ /**
305
+ * Codex app-server worker.
306
+ * @implements {import('./interface.mjs').IWorker}
307
+ */
308
+ export class CodexAppServerWorker {
309
+ type = "codex";
310
+ transport = "app-server";
311
+
312
+ /**
313
+ * @param {object} [options]
314
+ * @param {string} [options.command]
315
+ * @param {string[]} [options.args]
316
+ * @param {string} [options.cwd]
317
+ * @param {Record<string, string>} [options.env]
318
+ * @param {{ name: string, version: string }} [options.clientInfo]
319
+ * @param {number} [options.bootstrapTimeoutMs]
320
+ * @param {(msg: object) => void | Promise<void>} [options.publishCallback]
321
+ * @param {string} [options.workerId]
322
+ * @param {(command: string, args: string[], options: object) => import('node:child_process').ChildProcess} [options.spawnFn]
323
+ * @param {boolean} [options.redactSensitive]
324
+ * @param {number} [options.unknownMethodWarnThreshold]
325
+ * @param {(method: string) => void} [options.onUnknownMethod]
326
+ * @param {(label: string, payload: object) => void} [options.warn]
327
+ */
328
+ constructor(options = {}) {
329
+ this.command = options.command || process.env.CODEX_BIN || "codex";
330
+ this.args =
331
+ Array.isArray(options.args) && options.args.length
332
+ ? [...options.args]
333
+ : [...DEFAULT_CODEX_APP_SERVER_ARGS];
334
+ this.cwd = options.cwd || process.cwd();
335
+ this.env = cloneEnv({ ...cloneEnv(process.env), ...cloneEnv(options.env) });
336
+ this.clientInfo = options.clientInfo || {
337
+ name: "triflux-codex-app-server",
338
+ version: "1.0.0",
339
+ };
340
+ this.bootstrapTimeoutMs = Number.isFinite(options.bootstrapTimeoutMs)
341
+ ? options.bootstrapTimeoutMs
342
+ : DEFAULT_CODEX_APP_SERVER_BOOTSTRAP_TIMEOUT_MS;
343
+ this.publishCallback =
344
+ typeof options.publishCallback === "function"
345
+ ? options.publishCallback
346
+ : null;
347
+ this.workerId = options.workerId || `codex-app-server-${randomUUID()}`;
348
+ this._spawnFn = options.spawnFn || spawn;
349
+ this._JsonRpcClientClass =
350
+ typeof options.jsonRpcClientClass === "function"
351
+ ? options.jsonRpcClientClass
352
+ : JsonRpcStdioClient;
353
+ this.redactSensitive =
354
+ options.redactSensitive === true || process.env.TFX_CODEX_REDACT === "1";
355
+ this.unknownMethodWarnThreshold = Number.isFinite(
356
+ options.unknownMethodWarnThreshold,
357
+ )
358
+ ? options.unknownMethodWarnThreshold
359
+ : DEFAULT_UNKNOWN_METHOD_WARN_THRESHOLD;
360
+ this._warn =
361
+ typeof options.warn === "function"
362
+ ? options.warn
363
+ : (label, payload) => {
364
+ console.warn(`[codex-app-server] ${label}`, payload);
365
+ };
366
+ this._onUnknownMethod =
367
+ typeof options.onUnknownMethod === "function"
368
+ ? options.onUnknownMethod
369
+ : null;
370
+
371
+ /** @type {import('node:child_process').ChildProcess | null} */
372
+ this.child = null;
373
+ /** @type {JsonRpcStdioClient | null} */
374
+ this.client = null;
375
+ this.ready = false;
376
+ /** Ring buffer of the last 16 KB of stderr for diagnostics. */
377
+ this.serverStderr = "";
378
+ /** @type {string | null} */
379
+ this.activeThreadId = null;
380
+ this._warnedUnknown = new Set();
381
+ this._monotonicNow = createMonotonicClock();
382
+ this._publishQueue = [];
383
+ this._publishMaxQueue = 64;
384
+ /** @type {Function | null} */
385
+ this._rejectBootstrap = null;
386
+ /**
387
+ * Issue #95 P1 #2 — in-flight execute fail-fast registry.
388
+ * Each execute() registers `(err) => void` here; child `exit`/`error`
389
+ * and JsonRpcTransport/ProtocolError hook drain this to reject immediately
390
+ * instead of waiting for timeoutMs.
391
+ * @type {Set<(err: Error) => void>}
392
+ */
393
+ this._inflightRejectors = new Set();
394
+ /** Unsubscribe for child exit/error listeners wired in start(). */
395
+ this._detachChildLifecycle = null;
396
+ }
397
+
398
+ isReady() {
399
+ return this.ready === true;
400
+ }
401
+
402
+ /**
403
+ * Spawn the codex app-server child, wire JsonRpcStdioClient, and complete the
404
+ * initialize/initialized handshake.
405
+ * @returns {Promise<void>}
406
+ */
407
+ async start() {
408
+ if (this.ready) return;
409
+ await this.stop().catch(() => {});
410
+
411
+ let child;
412
+ try {
413
+ child = this._spawnFn(
414
+ this.command,
415
+ this.args,
416
+ buildSpawnOptions({
417
+ cwd: this.cwd,
418
+ env: this.env,
419
+ command: this.command,
420
+ }),
421
+ );
422
+ } catch (error) {
423
+ throw new CodexAppServerTransportError(
424
+ `codex app-server spawn 실패: ${
425
+ error instanceof Error ? error.message : String(error)
426
+ }`,
427
+ { cause: error },
428
+ );
429
+ }
430
+
431
+ if (!child?.stdin || !child.stdout) {
432
+ throw new CodexAppServerTransportError(
433
+ "codex app-server 자식 프로세스의 stdio 핸들을 가져오지 못했습니다.",
434
+ );
435
+ }
436
+
437
+ this.child = child;
438
+ this.serverStderr = "";
439
+ if (child.stderr && typeof child.stderr.on === "function") {
440
+ child.stderr.on("data", (chunk) => {
441
+ this.serverStderr += String(chunk);
442
+ if (this.serverStderr.length > 16_000) {
443
+ this.serverStderr = this.serverStderr.slice(-16_000);
444
+ }
445
+ });
446
+ }
447
+
448
+ const spawnErrorPromise = new Promise((_, reject) => {
449
+ const onError = (err) => {
450
+ reject(
451
+ new CodexAppServerTransportError(
452
+ `codex app-server 프로세스 오류: ${err?.message || err}`,
453
+ { cause: err, stderr: this.serverStderr },
454
+ ),
455
+ );
456
+ };
457
+ const onExit = (code, signal) => {
458
+ if (this.ready) return;
459
+ reject(
460
+ new CodexAppServerTransportError(
461
+ `codex app-server 프로세스가 조기 종료됨 (code=${code}, signal=${signal || ""})`,
462
+ { stderr: this.serverStderr },
463
+ ),
464
+ );
465
+ };
466
+ child.once("error", onError);
467
+ child.once("exit", onExit);
468
+ this._rejectBootstrap = (err) => {
469
+ child.removeListener("error", onError);
470
+ child.removeListener("exit", onExit);
471
+ reject(err);
472
+ };
473
+ });
474
+
475
+ const JsonRpcClientCtor = this._JsonRpcClientClass;
476
+ this.client = new JsonRpcClientCtor({
477
+ stdin: child.stdout,
478
+ stdout: child.stdin,
479
+ onError: (err) => {
480
+ this._warn("jsonrpc error", { message: err?.message || String(err) });
481
+ // P1 #2 / P1 #3 fail-fast: structural protocol/transport errors during
482
+ // an active turn must reject in-flight execute() immediately instead of
483
+ // silently hanging until timeoutMs. Parse noise without in-flight work
484
+ // stays warn-only via _warn above.
485
+ if (
486
+ (err instanceof JsonRpcProtocolError ||
487
+ err instanceof JsonRpcTransportError) &&
488
+ this._inflightRejectors.size > 0
489
+ ) {
490
+ const transportErr = new CodexAppServerTransportError(
491
+ `codex app-server transport error: ${err.message}`,
492
+ { cause: err, stderr: this.serverStderr },
493
+ );
494
+ this._drainInflight(transportErr);
495
+ }
496
+ },
497
+ });
498
+
499
+ // Initialize handshake with timeout.
500
+ const initPromise = this.client.request(
501
+ "initialize",
502
+ buildInitializeParams(this.clientInfo),
503
+ this.bootstrapTimeoutMs,
504
+ );
505
+
506
+ try {
507
+ await Promise.race([initPromise, spawnErrorPromise]);
508
+ } catch (error) {
509
+ await this.stop().catch(() => {});
510
+ if (error instanceof CodexAppServerTransportError) throw error;
511
+ throw new CodexAppServerTransportError(
512
+ `codex app-server initialize 실패: ${
513
+ error instanceof Error ? error.message : String(error)
514
+ }`,
515
+ { cause: error, stderr: this.serverStderr },
516
+ );
517
+ }
518
+
519
+ this._rejectBootstrap = null;
520
+ try {
521
+ this.client.notify("initialized", {});
522
+ } catch (error) {
523
+ await this.stop().catch(() => {});
524
+ throw new CodexAppServerTransportError(
525
+ `codex app-server initialized notification 실패: ${
526
+ error instanceof Error ? error.message : String(error)
527
+ }`,
528
+ { cause: error, stderr: this.serverStderr },
529
+ );
530
+ }
531
+
532
+ // P1 #2: post-bootstrap lifecycle listeners. Once the handshake succeeds
533
+ // (`ready=true`) the spawn-time `once("exit"/"error")` handlers are
534
+ // consumed by `_rejectBootstrap` — we now install long-lived listeners so
535
+ // that a mid-turn child crash rejects in-flight execute() immediately.
536
+ const onPostBootstrapError = (err) => {
537
+ const transportErr = new CodexAppServerTransportError(
538
+ `codex app-server 프로세스 오류 (mid-turn): ${err?.message || err}`,
539
+ { cause: err, stderr: this.serverStderr },
540
+ );
541
+ this._drainInflight(transportErr);
542
+ };
543
+ const onPostBootstrapExit = (code, signal) => {
544
+ this.ready = false;
545
+ if (this._inflightRejectors.size === 0) return;
546
+ const transportErr = new CodexAppServerTransportError(
547
+ `codex app-server 프로세스가 턴 중 종료됨 (code=${code}, signal=${signal || ""})`,
548
+ { stderr: this.serverStderr },
549
+ );
550
+ this._drainInflight(transportErr);
551
+ };
552
+ child.on("error", onPostBootstrapError);
553
+ child.on("exit", onPostBootstrapExit);
554
+ this._detachChildLifecycle = () => {
555
+ try {
556
+ child.removeListener("error", onPostBootstrapError);
557
+ } catch {}
558
+ try {
559
+ child.removeListener("exit", onPostBootstrapExit);
560
+ } catch {}
561
+ };
562
+
563
+ this.ready = true;
564
+ }
565
+
566
+ /**
567
+ * Fail-fast hook: reject every registered in-flight execute() with `err`.
568
+ * Called on child exit/error, transport/protocol error, or stop().
569
+ * @param {Error} err
570
+ */
571
+ _drainInflight(err) {
572
+ if (this._inflightRejectors.size === 0) return;
573
+ const rejectors = [...this._inflightRejectors];
574
+ this._inflightRejectors.clear();
575
+ for (const reject of rejectors) {
576
+ try {
577
+ reject(err);
578
+ } catch {
579
+ /* never throw out of fail-fast path */
580
+ }
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Run a single prompt through `thread/start` + `turn/start` and resolve when
586
+ * the turn completes (or errors out).
587
+ * @param {string} prompt
588
+ * @param {import('./interface.mjs').WorkerExecuteOptions} [opts]
589
+ * @returns {Promise<import('./interface.mjs').WorkerResult>}
590
+ */
591
+ async execute(prompt, opts = {}) {
592
+ const sessionKey =
593
+ typeof opts.sessionKey === "string" && opts.sessionKey
594
+ ? opts.sessionKey
595
+ : null;
596
+
597
+ if (typeof prompt !== "string" || !prompt.trim()) {
598
+ return {
599
+ output: "prompt는 비어 있을 수 없습니다.",
600
+ exitCode: CODEX_APP_SERVER_EXECUTION_EXIT_CODE,
601
+ threadId: null,
602
+ sessionKey,
603
+ error: buildWorkerError(
604
+ "INVALID_INPUT",
605
+ "prompt는 비어 있을 수 없습니다.",
606
+ ),
607
+ raw: null,
608
+ };
609
+ }
610
+
611
+ try {
612
+ await this.start();
613
+ } catch (error) {
614
+ return {
615
+ output: error instanceof Error ? error.message : String(error),
616
+ exitCode: CODEX_APP_SERVER_TRANSPORT_EXIT_CODE,
617
+ threadId: null,
618
+ sessionKey,
619
+ error: buildWorkerError(
620
+ "CODEX_APP_SERVER_TRANSPORT_ERROR",
621
+ error instanceof Error ? error.message : String(error),
622
+ ),
623
+ raw: null,
624
+ };
625
+ }
626
+
627
+ const client = this.client;
628
+ if (!client) {
629
+ return {
630
+ output: "codex app-server client 미초기화",
631
+ exitCode: CODEX_APP_SERVER_TRANSPORT_EXIT_CODE,
632
+ threadId: null,
633
+ sessionKey,
634
+ error: buildWorkerError(
635
+ "CODEX_APP_SERVER_TRANSPORT_ERROR",
636
+ "client missing",
637
+ ),
638
+ raw: null,
639
+ };
640
+ }
641
+
642
+ const outputParts = [];
643
+ const unknownMethodsThisTurn = new Set();
644
+ let threadId =
645
+ typeof opts.threadId === "string" && opts.threadId ? opts.threadId : null;
646
+ let protocolMismatchWarned = false;
647
+
648
+ /** @type {((value: import('./interface.mjs').WorkerResult) => void) | null} */
649
+ let resolveResult = null;
650
+ const resultPromise = new Promise((resolve) => {
651
+ resolveResult = resolve;
652
+ });
653
+
654
+ const finish = (result) => {
655
+ if (!resolveResult) return;
656
+ const f = resolveResult;
657
+ resolveResult = null;
658
+ f(result);
659
+ };
660
+
661
+ // P1 #2 fail-fast: register ourselves so transport/child lifecycle errors
662
+ // can reject the in-flight turn immediately.
663
+ const inflightReject = (err) => {
664
+ finish({
665
+ output: outputParts.join(""),
666
+ exitCode: CODEX_APP_SERVER_TRANSPORT_EXIT_CODE,
667
+ threadId,
668
+ sessionKey,
669
+ error: buildWorkerError(
670
+ "CODEX_APP_SERVER_TRANSPORT_ERROR",
671
+ err instanceof Error ? err.message : String(err),
672
+ ),
673
+ raw: null,
674
+ });
675
+ };
676
+ this._inflightRejectors.add(inflightReject);
677
+
678
+ const unsubscribers = [];
679
+ const offAll = () => {
680
+ while (unsubscribers.length) {
681
+ const off = unsubscribers.pop();
682
+ try {
683
+ off?.();
684
+ } catch {}
685
+ }
686
+ };
687
+
688
+ /** @type {ReturnType<typeof setTimeout> | null} */
689
+ let timer = null;
690
+
691
+ try {
692
+ const pushPublish = (method, params) => {
693
+ if (!this.publishCallback) return;
694
+ const msg = this._buildPublishMessage(
695
+ method,
696
+ params,
697
+ threadId,
698
+ sessionKey,
699
+ );
700
+ // Fire-and-forget with 64-cap drop-oldest backpressure.
701
+ if (this._publishQueue.length >= this._publishMaxQueue) {
702
+ this._publishQueue.shift();
703
+ }
704
+ this._publishQueue.push(msg);
705
+ try {
706
+ const ret = this.publishCallback(msg);
707
+ if (ret && typeof ret.then === "function") {
708
+ ret.catch(() => {});
709
+ }
710
+ } catch {}
711
+ };
712
+
713
+ const handleNotification = (method, params) => {
714
+ // AC5 defense: opt-out should already be filtered server-side.
715
+ if (OPT_OUT_METHODS.includes(method)) return;
716
+
717
+ if (!SUBSCRIBED_METHODS.has(method)) {
718
+ // AC6 / AC14 / AC16 — unknown method handling
719
+ unknownMethodsThisTurn.add(method);
720
+ if (!this._warnedUnknown.has(method)) {
721
+ this._warnedUnknown.add(method);
722
+ this._warn("unknown notification method", { method });
723
+ this._onUnknownMethod?.(method);
724
+ }
725
+ if (
726
+ !protocolMismatchWarned &&
727
+ unknownMethodsThisTurn.size > this.unknownMethodWarnThreshold
728
+ ) {
729
+ protocolMismatchWarned = true;
730
+ this._warn("protocol_version_mismatch", {
731
+ count: unknownMethodsThisTurn.size,
732
+ threshold: this.unknownMethodWarnThreshold,
733
+ });
734
+ }
735
+ return;
736
+ }
737
+
738
+ // Thread id can arrive on thread/started before thread/start resolves.
739
+ if (method === "thread/started") {
740
+ const tid = extractThreadIdFromStartedNotif(params);
741
+ if (tid && !threadId) threadId = tid;
742
+ }
743
+
744
+ // Agent text accumulation (AC3)
745
+ if (method === "item/agentMessage/delta") {
746
+ const delta = typeof params?.delta === "string" ? params.delta : "";
747
+ outputParts.push(delta);
748
+ }
749
+
750
+ pushPublish(method, params);
751
+
752
+ if (method === "turn/completed") {
753
+ const status = params?.turn?.status;
754
+ if (status === "completed") {
755
+ finish({
756
+ output: outputParts.join(""),
757
+ exitCode: 0,
758
+ threadId,
759
+ sessionKey,
760
+ raw: { turn: params?.turn ?? null },
761
+ });
762
+ } else {
763
+ const msg =
764
+ params?.turn?.error?.message || `turn ${status || "unknown"}`;
765
+ finish({
766
+ output: outputParts.join(""),
767
+ exitCode: CODEX_APP_SERVER_EXECUTION_EXIT_CODE,
768
+ threadId,
769
+ sessionKey,
770
+ error: buildWorkerError("CODEX_APP_SERVER_EXECUTION_ERROR", msg),
771
+ raw: { turn: params?.turn ?? null },
772
+ });
773
+ }
774
+ return;
775
+ }
776
+
777
+ if (method === "error") {
778
+ const msg = params?.message || "codex app-server error";
779
+ finish({
780
+ output: outputParts.join(""),
781
+ exitCode: CODEX_APP_SERVER_EXECUTION_EXIT_CODE,
782
+ threadId,
783
+ sessionKey,
784
+ error: buildWorkerError("CODEX_APP_SERVER_EXECUTION_ERROR", msg),
785
+ raw: { error: params ?? null },
786
+ });
787
+ }
788
+ };
789
+
790
+ // NOTE: JsonRpcStdioClient wildcard subscribers receive (params, method).
791
+ // Match that contract here; targeted subscribers (cb(params)) are unchanged.
792
+ const catchAll = (params, method) => handleNotification(method, params);
793
+ unsubscribers.push(client.onNotification("*", catchAll));
794
+
795
+ // Send thread/start (no prompt) then turn/start (prompt)
796
+ let threadStartResponse;
797
+ try {
798
+ threadStartResponse = await client.request(
799
+ "thread/start",
800
+ buildThreadStartParams(opts),
801
+ this.bootstrapTimeoutMs,
802
+ );
803
+ } catch (error) {
804
+ return {
805
+ output: error instanceof Error ? error.message : String(error),
806
+ exitCode: CODEX_APP_SERVER_TRANSPORT_EXIT_CODE,
807
+ threadId,
808
+ sessionKey,
809
+ error: buildWorkerError(
810
+ "CODEX_APP_SERVER_TRANSPORT_ERROR",
811
+ error instanceof Error ? error.message : String(error),
812
+ ),
813
+ raw: null,
814
+ };
815
+ }
816
+ const respThreadId =
817
+ extractThreadIdFromStartResponse(threadStartResponse);
818
+ if (respThreadId && !threadId) threadId = respThreadId;
819
+ if (!threadId) {
820
+ return {
821
+ output: "thread id를 추출하지 못했습니다.",
822
+ exitCode: CODEX_APP_SERVER_EXECUTION_EXIT_CODE,
823
+ threadId: null,
824
+ sessionKey,
825
+ error: buildWorkerError(
826
+ "CODEX_APP_SERVER_EXECUTION_ERROR",
827
+ "thread id missing",
828
+ ),
829
+ raw: { threadStart: threadStartResponse },
830
+ };
831
+ }
832
+ this.activeThreadId = threadId;
833
+
834
+ try {
835
+ await client.request(
836
+ "turn/start",
837
+ buildTurnStartParams(threadId, prompt),
838
+ this.bootstrapTimeoutMs,
839
+ );
840
+ } catch (error) {
841
+ return {
842
+ output: error instanceof Error ? error.message : String(error),
843
+ exitCode: CODEX_APP_SERVER_TRANSPORT_EXIT_CODE,
844
+ threadId,
845
+ sessionKey,
846
+ error: buildWorkerError(
847
+ "CODEX_APP_SERVER_TRANSPORT_ERROR",
848
+ error instanceof Error ? error.message : String(error),
849
+ ),
850
+ raw: null,
851
+ };
852
+ }
853
+
854
+ // Timeout: SIGTERM + partial WorkerResult (AC8)
855
+ const timeoutMs = Number.isFinite(opts.timeoutMs)
856
+ ? opts.timeoutMs
857
+ : DEFAULT_CODEX_APP_SERVER_EXECUTION_TIMEOUT_MS;
858
+ // Capture current child so a stale timer (post-result) cannot SIGTERM a
859
+ // later reused worker's child process.
860
+ const capturedChild = this.child;
861
+ const timeoutPromise = new Promise((resolve) => {
862
+ timer = setTimeout(() => {
863
+ this._warn("execution timeout", { timeoutMs });
864
+ try {
865
+ capturedChild?.kill?.("SIGTERM");
866
+ } catch {}
867
+ resolve({
868
+ output: outputParts.join(""),
869
+ exitCode: CODEX_APP_SERVER_TIMEOUT_EXIT_CODE,
870
+ threadId,
871
+ sessionKey,
872
+ error: buildWorkerError(
873
+ "CODEX_APP_SERVER_TIMEOUT",
874
+ `codex app-server timeout (${timeoutMs}ms)`,
875
+ ),
876
+ raw: null,
877
+ });
878
+ }, timeoutMs);
879
+ });
880
+
881
+ const result = await Promise.race([resultPromise, timeoutPromise]);
882
+ return result;
883
+ } finally {
884
+ if (timer !== null) {
885
+ clearTimeout(timer);
886
+ timer = null;
887
+ }
888
+ offAll();
889
+ this._inflightRejectors.delete(inflightReject);
890
+ }
891
+ }
892
+
893
+ /**
894
+ * Graceful shutdown — best-effort thread/unsubscribe (request, with timeout),
895
+ * close JSON-RPC client, SIGTERM the child then SIGKILL after 1 s.
896
+ *
897
+ * P1 #1 wire framing: `thread/unsubscribe` is sent as a **request** per the
898
+ * OpenAI App Server API overview (was notification in the original PR #86).
899
+ * P1 #2 lifecycle: we wait up to `UNSUBSCRIBE_DEADLINE_MS` for the response
900
+ * before proceeding to SIGTERM so the server can flush unload work.
901
+ * @returns {Promise<void>}
902
+ */
903
+ async stop() {
904
+ this.ready = false;
905
+ const child = this.child;
906
+ const client = this.client;
907
+ const activeThread = this.activeThreadId;
908
+ this.child = null;
909
+ this.client = null;
910
+ this.activeThreadId = null;
911
+
912
+ if (this._detachChildLifecycle) {
913
+ try {
914
+ this._detachChildLifecycle();
915
+ } catch {}
916
+ this._detachChildLifecycle = null;
917
+ }
918
+ // Reject any still-registered in-flight execute() with a generic transport
919
+ // error so callers don't hang past stop().
920
+ this._drainInflight(
921
+ new CodexAppServerTransportError("codex app-server stop() 호출됨", {
922
+ stderr: this.serverStderr,
923
+ }),
924
+ );
925
+
926
+ if (this._rejectBootstrap) {
927
+ try {
928
+ this._rejectBootstrap(
929
+ new CodexAppServerTransportError("codex app-server stop() 호출됨"),
930
+ );
931
+ } catch {}
932
+ this._rejectBootstrap = null;
933
+ }
934
+
935
+ if (client) {
936
+ if (activeThread) {
937
+ try {
938
+ if (client.isOpen()) {
939
+ // Enter `closing` state so the EOF that follows `close()` doesn't
940
+ // trip the fail-fast path. Send unsubscribe as a request and race
941
+ // it against a hard deadline — some servers (and test fakes) may
942
+ // never respond, and we must not hang stop().
943
+ const unsubPromise = client
944
+ .request(
945
+ "thread/unsubscribe",
946
+ { threadId: activeThread },
947
+ UNSUBSCRIBE_DEADLINE_MS,
948
+ )
949
+ .catch(() => {
950
+ /* best-effort; fall through to close + SIGTERM below */
951
+ });
952
+ client.close("closing");
953
+ const deadline = new Promise((resolve) => {
954
+ setTimeout(resolve, UNSUBSCRIBE_DEADLINE_MS);
955
+ });
956
+ await Promise.race([unsubPromise, deadline]);
957
+ }
958
+ } catch {}
959
+ }
960
+ try {
961
+ client.close();
962
+ } catch {}
963
+ }
964
+
965
+ if (child) {
966
+ try {
967
+ child.stdin?.end?.();
968
+ } catch {}
969
+ try {
970
+ if (child.exitCode === null && !child.killed) child.kill("SIGTERM");
971
+ } catch {}
972
+ if (child.exitCode === null && !child.killed) {
973
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
974
+ try {
975
+ if (child.exitCode === null && !child.killed) child.kill("SIGKILL");
976
+ } catch {}
977
+ }
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Build the `agent.progress` publish envelope for a codex notification.
983
+ * Compatible with `hub/bridge.mjs:buildPublishBody()` so the same object can
984
+ * be handed to `requestJson('/bridge/publish', { body })` or a router shim.
985
+ * @param {string} method
986
+ * @param {unknown} params
987
+ * @param {string | null} threadId
988
+ * @param {string | null} sessionKey
989
+ */
990
+ _buildPublishMessage(method, params, threadId, sessionKey) {
991
+ const kind = KIND_MAP[method] || "unknown";
992
+ const data = this.redactSensitive ? redactDeep(params) : params;
993
+ return {
994
+ from: this.workerId,
995
+ to: "topic:agent.progress",
996
+ topic: "agent.progress",
997
+ type: "event",
998
+ payload: {
999
+ type: "agent.progress",
1000
+ version: 1,
1001
+ agentId: this.workerId,
1002
+ sessionKey: sessionKey || "",
1003
+ threadId: threadId || "",
1004
+ kind,
1005
+ method,
1006
+ timestamp: this._monotonicNow(),
1007
+ data,
1008
+ },
1009
+ };
1010
+ }
1011
+ }
1012
+
1013
+ export function createCodexAppServerWorker(options = {}) {
1014
+ return new CodexAppServerWorker(options);
1015
+ }