@minhspark/codex-mcp-bridge 1.25.2 → 1.25.3

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.
@@ -1,5 +1,86 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
5
+ const diagnosticCode = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : null;
6
+ const diagnosticId = (value) => typeof value === "string" && /^(?:[a-f0-9]{64}|(?:local_)?[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12})$/i.test(value) ? value : null;
7
+
8
+ export function diagnosticText(value) {
9
+ return String(value ?? "Unknown bridge failure")
10
+ .replace(/\{[\s\S]*\}|\[\s*(?:\{|"|true|false|null|\d)[\s\S]*\]/g, "[structured details omitted]")
11
+ .replace(/\b(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "[credential]")
12
+ .replace(/((?:access[_-]?token|refresh[_-]?token|api[_-]?key|token|secret|password|authorization)\s*["']?\s*[:=]\s*)["']?[^\s,"';}]+["']?/gi, "$1[redacted]")
13
+ .replace(/https?:\/\/[^\s<>"']+/gi, "[url]")
14
+ .replace(/(?:[a-z]:[\\/]|\\\\)[^\r\n"'<>|,;]*/gi, "[path]")
15
+ .replace(/(^|[\s"'(])\/(?:Users|home|tmp|var|private|Volumes|mnt)\/[^\r\n"'<>|,;]*/g, "$1[path]")
16
+ .replace(/[\w.+-]+@[\w.-]+\.[a-z]{2,}/gi, "[email]")
17
+ .replace(/\b(?:sk-[a-z0-9_-]+|gh[pousr]_[a-z0-9_]+)\b/gi, "[credential]")
18
+ .replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 1400);
19
+ }
20
+
21
+ export function bridgeDiagnostic(error, options = {}) {
22
+ const previous = error?.diagnostic?.schemaVersion === 1 ? error.diagnostic : null;
23
+ const chain = [];
24
+ const seen = new Set();
25
+ for (let cause = error; cause && !seen.has(cause) && chain.length < 8; cause = cause.cause) {
26
+ seen.add(cause);
27
+ chain.push(cause);
28
+ }
29
+ const codes = [...new Set(chain.flatMap((cause) => [cause?.preflight?.code, cause?.code, cause?.reasonCode, cause?.diagnostic?.code]).map(diagnosticCode).filter(Boolean))];
30
+ const code = diagnosticCode(previous?.code) ?? diagnosticCode(options.code) ?? codes[0] ?? "BRIDGE_OPERATION_FAILED";
31
+ const state = options.deliveryState ?? previous?.deliveryState ?? error?.deliveryState;
32
+ const deliveryState = state === "not_applicable" || options.readOnly && !previous ? "not_applicable"
33
+ : ["not_sent", "not-sent"].includes(state) ? "not_sent"
34
+ : state === "sent" ? "sent"
35
+ : state === "unknown" || error?.deliveryUncertain ? "unknown"
36
+ : chain.some((cause) => cause?.sent === false || cause?.preflight?.sent === false) ? "not_sent" : "unknown";
37
+ const name = (value, fallback) => typeof value === "string" && /^[a-zA-Z][a-zA-Z0-9_./-]{0,99}$/.test(value) ? value : fallback;
38
+ const ids = {};
39
+ for (const key of ["requestId", "msgId", "deliveryId", "threadId", "taskId", "conversationId"]) {
40
+ const id = diagnosticId(previous?.ids?.[key] ?? options[key] ?? error?.[key]);
41
+ if (id) ids[key] = id;
42
+ }
43
+ const timestamp = previous?.timestamp;
44
+ return {
45
+ schemaVersion: 1,
46
+ reportId: diagnosticId(previous?.reportId) ?? randomUUID(),
47
+ timestamp: typeof timestamp === "string" && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/.test(timestamp) ? timestamp : new Date().toISOString(),
48
+ component: name(previous?.component ?? options.component, "bridge"),
49
+ operation: name(previous?.operation === "unknown" ? options.operation : previous?.operation ?? options.operation, "unknown"),
50
+ version: typeof previous?.version === "string" && /^\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?$/i.test(previous.version) ? previous.version : version,
51
+ platform: process.platform,
52
+ nodeVersion: process.versions.node,
53
+ code,
54
+ causeCodes: [...new Set([...(Array.isArray(previous?.causeCodes) ? previous.causeCodes : []).map(diagnosticCode).filter(Boolean), ...codes])].filter((value) => value !== code).slice(0, 8),
55
+ phase: name(previous?.phase ?? options.phase ?? error?.phase ?? error?.diagnostic?.stage, deliveryState === "not_sent" ? "preflight" : options.readOnly ? "inspection" : "delivery"),
56
+ deliveryState,
57
+ sent: deliveryState === "not_sent" ? false : deliveryState === "sent" ? true : null,
58
+ retrySafe: deliveryState === "not_sent",
59
+ nextAction: deliveryState === "not_sent" ? "Inspect the reported cause and bridge status before retrying. Do not open or create chats to load a relay."
60
+ : deliveryState === "not_applicable" ? "Inspect bridge status and include this diagnostic in the report."
61
+ : "Inspect the original delivery receipt. Do not resend automatically or create a replacement chat.",
62
+ reason: diagnosticText(previous?.reason ?? error?.preflight?.reason ?? error?.message ?? error),
63
+ ids,
64
+ };
65
+ }
66
+
67
+ export function withBridgeDiagnostic(result, error, options = {}) {
68
+ const diagnostic = bridgeDiagnostic({ ...error, cause: error?.cause, message: error?.message ?? String(error ?? "Bridge operation failed"), diagnostic: result?.structuredContent?.diagnostic ?? result?.diagnostic ?? error?.diagnostic }, options);
69
+ const report = ["Bridge diagnostic (include this block in a bug report)", `${diagnostic.code} | ${diagnostic.component} / ${diagnostic.operation}`,
70
+ `Bridge ${diagnostic.version} | ${diagnostic.platform} | Node ${diagnostic.nodeVersion}`,
71
+ `Phase: ${diagnostic.phase} | Delivery: ${diagnostic.deliveryState} | Sent: ${diagnostic.sent ?? "unconfirmed"}`,
72
+ `Reason: ${diagnostic.reason}`, ...(diagnostic.causeCodes.length ? [`Cause codes: ${diagnostic.causeCodes.join(", ")}`] : []),
73
+ ...Object.entries(diagnostic.ids).map(([key, value]) => `${key}: ${value}`),
74
+ `Report: ${diagnostic.reportId} | ${diagnostic.timestamp}`, diagnostic.nextAction].join("\n");
75
+ return { ...result, content: [...(result?.content ?? []).filter((item) => item.type !== "text" || !item.text.startsWith("Bridge diagnostic (")), { type: "text", text: report }],
76
+ structuredContent: { ...result?.structuredContent, diagnostic } };
77
+ }
78
+
1
79
  export function preflightFailure(code, reason) {
2
80
  const error = new Error(`${reason} No message was sent.`);
81
+ error.code = code;
82
+ error.deliveryState = "not_sent";
83
+ error.sent = false;
3
84
  error.preflight = { status: "blocked", code, reason, sent: false };
4
85
  return error;
5
86
  }
package/src/setup.mjs CHANGED
@@ -89,7 +89,7 @@ export function setupEnvironment(env, paths, codex) {
89
89
  }
90
90
 
91
91
  function configurationCheck(label, check, components) {
92
- try { check(); components.push({ name: label, status: "configured" }); }
92
+ try { const details = check(); components.push({ name: label, status: "configured", ...details }); }
93
93
  catch (error) { components.push({ name: label, status: "incomplete", detail: error.message }); }
94
94
  }
95
95
 
@@ -99,8 +99,9 @@ function assertEntry(entry, worker, root, { caller } = {}) {
99
99
  const expected = worker === "index.mjs" || worker === "claude-bridge.mjs" || worker === "native-relay-companion.mjs"
100
100
  ? [path.join(root, "src", "mcp-supervisor.mjs"), worker] : [path.join(root, "src", worker)];
101
101
  if (JSON.stringify(args) !== JSON.stringify(expected)) throw new Error("MCP entry points to a different installation or unsupported launcher");
102
- if (entry.enabled === false || entry.disabled === true) throw new Error("MCP entry is disabled; the saved choice was preserved");
103
102
  if (caller && entry.env?.ANTIGRAVITY_BRIDGE_CALLER !== caller) throw new Error("Antigravity caller registration is missing or conflicting");
103
+ if (entry.enabled === false || entry.disabled === true) return { deliveryEnabled: false,
104
+ notices: ["This MCP registration is intentionally disabled. Its saved choice was preserved; delivery through this connector is disabled."] };
104
105
  }
105
106
 
106
107
  export function checkSetup({ env = process.env, platform = process.platform, root = ROOT, run = execute, resolve = resolveCodexBin } = {}) {
@@ -119,8 +120,9 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
119
120
  const item = inventory?.find((server) => server.name === name);
120
121
  if (!item) throw new Error("MCP registration is missing");
121
122
  const entry = JSON.parse(run(codex, ["mcp", "get", name, "--json"], setupEnvironment(env, paths, codex), 30000));
122
- assertEntry({ ...entry.transport, enabled: entry.enabled }, worker, root, name === "antigravity-bridge" ? { caller: "codex" } : {});
123
+ const details = assertEntry({ ...entry.transport, enabled: entry.enabled }, worker, root, name === "antigravity-bridge" ? { caller: "codex" } : {});
123
124
  if (name === "codex-native-relay" && !entry.transport.env_vars?.includes("CODEX_APP_TOOLS_PIPE_PATH")) throw new Error("Native pipe forwarding is missing");
125
+ return details;
124
126
  }, components);
125
127
  }
126
128
  for (const [label, file, name, worker, caller] of [
@@ -130,14 +132,26 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
130
132
  ["Antigravity / agent-bridge", path.join(paths.gemini, "mcp_config.json"), "agent-bridge", "antigravity-outbound-bridge.mjs"],
131
133
  ]) configurationCheck(label, () => {
132
134
  const entry = readJson(file)?.mcpServers?.[name];
133
- assertEntry(entry, worker, root, { caller });
135
+ const details = assertEntry(entry, worker, root, { caller });
136
+ if (worker === "index.mjs" && entry.env?.CODEX_BRIDGE_OPEN_IN_APP === "1") {
137
+ throw new Error("Managed bridge settings still allow opening Codex Desktop. Run codex-bridge-setup to apply the current silent configuration.");
138
+ }
134
139
  if (name === "codex-bridge-desktop" && entry.env?.CODEX_BRIDGE_DESKTOP_TASKS === "1") {
135
- throw new Error("Shared Desktop chat cannot use native Code routing. Its saved choice was preserved; explicitly set CODEX_BRIDGE_DESKTOP_TASKS=0 for the local app-server route and CODEX_BRIDGE_AUTOSTART=1 if the bridge should start that server.");
140
+ return { ...details, nativeCodeOnly: true, sharedChatDelivery: "unsupported",
141
+ notices: [...(details?.notices ?? []), "The saved native Code-only policy was preserved. Ordinary Claude Desktop Chat cannot deliver through this connector; native delivery uses the registered Claude Desktop Code connector and remains unverified by setup."] };
136
142
  }
143
+ return details;
144
+ }, components);
145
+ configurationCheck("Claude Desktop / background preference", () => {
146
+ if (readJson(paths.desktop)?.preferences?.menuBarEnabled !== true) throw new Error("The managed Claude background preference is not enabled. Run codex-bridge-setup to apply it; live activation is verified separately.");
137
147
  }, components);
138
148
  configurationCheck("Antigravity / codex-bridge sidecar", () => {
139
- assertEntry(readJson(path.join(paths.gemini, "sidecars", "codex-bridge", "sidecar.json")), "antigravity-sidecar.mjs", root);
140
- if (readJson(path.join(paths.gemini, "config.json"))?.sidecars?.["codex-bridge"]?.enabled !== true) throw new Error("Antigravity sidecar is not enabled");
149
+ const details = assertEntry(readJson(path.join(paths.gemini, "sidecars", "codex-bridge", "sidecar.json")), "antigravity-sidecar.mjs", root);
150
+ const enabled = readJson(path.join(paths.gemini, "config.json"))?.sidecars?.["codex-bridge"]?.enabled;
151
+ if (typeof enabled !== "boolean") throw new Error("Antigravity sidecar enabled setting is missing or invalid");
152
+ if (!enabled) return { deliveryEnabled: false,
153
+ notices: [...(details?.notices ?? []), "The Antigravity bridge sidecar is intentionally disabled. Its saved choice was preserved; native Desktop delivery through this sidecar is disabled."] };
154
+ return details;
141
155
  }, components);
142
156
  if (native) configurationCheck("Native relay executor", () => {
143
157
  const relay = readJson(path.join(paths.codex, "native-relay.json"));
@@ -151,7 +165,7 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
151
165
 
152
166
  export function runSetup({ args = [], env = process.env, platform = process.platform, root = ROOT, run = execute, resolve = resolveCodexBin } = {}) {
153
167
  const options = setupArguments(args);
154
- if (options.help) return { help: "Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nGlobal npm installation configures Claude Desktop, Claude Code, Codex and Antigravity automatically. CODEX_BRIDGE_SKIP_SETUP=1 opts out." };
168
+ if (options.help) return { help: "Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nUpdate the package and apply managed settings with: npm install -g @minhspark/codex-mcp-bridge@latest --allow-scripts=@minhspark/codex-mcp-bridge\nGlobal npm installation configures Claude Desktop, Claude Code, Codex and Antigravity automatically. CODEX_BRIDGE_SKIP_SETUP=1 opts out. Saved configuration and loaded app connections are verified separately." };
155
169
  if (options.postinstall && !automaticSetupEnabled(env)) return { skipped: true, reason: "Automatic configuration runs only for a global npm install; use codex-bridge-setup for manual setup." };
156
170
  if (options.check) return checkSetup({ env, platform, root, run, resolve });
157
171
  const native = platform === "win32" || platform === "darwin";
@@ -166,16 +180,18 @@ export function runSetup({ args = [], env = process.env, platform = process.plat
166
180
  const install = (name, script, arguments_, environment = base) => {
167
181
  try {
168
182
  const output = run(process.execPath, [path.join(root, "scripts", script), ...arguments_], environment, 180000);
169
- let notices = [];
170
- try { notices = JSON.parse(output).notices ?? []; } catch {}
171
- components.push({ name, status: "configured", ...(notices.length ? { notices } : {}) });
183
+ let details = {};
184
+ try { details = JSON.parse(output); } catch {}
185
+ const notices = details.notices ?? [];
186
+ components.push({ name, status: "configured", ...(notices.length ? { notices } : {}),
187
+ ...(details.backgroundPreference ? { backgroundPreference: details.backgroundPreference } : {}) });
172
188
  return true;
173
189
  } catch (error) { components.push({ name, status: "incomplete", detail: error.message }); return false; }
174
190
  };
175
- const code = install(planned[0], "install-claude-code.mjs", ["--install", "--desktop-config", paths.desktop], {
191
+ const code = install(planned[0], "install-claude-code.mjs", ["--install", "--setup", "--desktop-config", paths.desktop], {
176
192
  ...base, CODEX_BRIDGE_DESKTOP_TASKS: native ? "1" : "0", CODEX_BRIDGE_AUTOSTART: native ? "0" : "1", CODEX_BRIDGE_THREAD_POLICY: "roots",
177
193
  });
178
- if (code) install(planned[1], "install-claude-desktop.mjs", ["--entry-name", "codex-bridge-desktop", "--quiet"]);
194
+ if (code) install(planned[1], "install-claude-desktop.mjs", ["--entry-name", "codex-bridge-desktop", "--quiet", "--setup"]);
179
195
  if (native) {
180
196
  const relay = readJson(path.join(paths.codex, "native-relay.json"));
181
197
  install("Codex / codex-native-relay and executor", "install-native-relay.mjs", ["--setup", ...(relay?.desktopTasks === undefined ? ["--desktop-tasks"] : [])]);
@@ -185,20 +201,24 @@ export function runSetup({ args = [], env = process.env, platform = process.plat
185
201
  const inspection = checkSetup({ env: base, platform, root, run, resolve: () => runtime.binary });
186
202
  return { configured: components.every((item) => item.status === "configured") && inspection.configured, liveVerified: false,
187
203
  cliInstalled: runtime.installed, components, verification: inspection.components,
188
- nextActions: ["Open or reconnect Claude, Codex and Antigravity after active work finishes so they load the new entries.",
189
- "Use the same saved project in Claude Desktop Code and Codex Desktop. Keep the apps signed in.",
190
- "Verify codex_bridge_status in Claude Code, native_relay_status in Codex, and agent_bridge_status in Antigravity. Shared Claude Desktop chat uses the local app-server route."] };
204
+ nextActions: ["Running MCP processes may still hold earlier settings. Setup does not start apps or open conversations.",
205
+ "Existing compatible supervisors reload installed source when idle; configuration changes are not proof of loaded-process readiness.",
206
+ "Native delivery uses the same saved project and signed-in accounts in Claude Desktop Code and Codex Desktop.",
207
+ "codex_bridge_status, native_relay_status and agent_bridge_status report live readiness separately from this configuration check."] };
191
208
  }
192
209
 
193
210
  export function printSetup(result, { json = false, output = console.log } = {}) {
194
211
  if (json) { output(JSON.stringify(result, null, 2)); return; }
195
212
  if (result.help) { output(result.help); return; }
196
213
  if (result.skipped) { output(result.reason); return; }
197
- output(result.dryRun ? "Bridge setup preview (no changes)" : result.configured ? "Bridge configuration verified. Live connections still require the apps to load it." : "Bridge setup is incomplete. Fix the items below, then run codex-bridge-setup again.");
214
+ output(result.dryRun ? "Bridge setup preview (no changes)" : result.configured ? "Bridge configuration verified. Running app connections have not been verified." : "Bridge setup is incomplete. Fix the items below, then run codex-bridge-setup again.");
198
215
  for (const item of result.components ?? []) {
199
216
  output(`[${item.status}] ${item.name}${item.detail ? `: ${item.detail}` : ""}`);
200
217
  for (const notice of item.notices ?? []) output(` ${notice}`);
201
218
  }
202
- for (const item of result.verification ?? []) if (item.status !== "configured") output(`[${item.status}] ${item.name}: ${item.detail}`);
219
+ for (const item of result.verification ?? []) {
220
+ if (item.status !== "configured") output(`[${item.status}] ${item.name}: ${item.detail}`);
221
+ for (const notice of item.notices ?? []) output(`${item.name}: ${notice}`);
222
+ }
203
223
  for (const action of result.nextActions ?? []) output(action);
204
224
  }
@@ -9,6 +9,7 @@ import { captureCodexRolloutWatermark, inspectCodexNativeTurn, readCodexNativeTu
9
9
  import { captureProjectScope, recheckProjectScope } from "./project-scope.mjs";
10
10
  import { isRolloutChangedDuringRead } from "./rollout-segments.mjs";
11
11
  import { requireClaudeSenderContext } from "./claude-sender-context.mjs";
12
+ import { bridgeDiagnostic } from "./recipient-preflight.mjs";
12
13
 
13
14
  /**
14
15
  * Which backend puts a message into a Codex thread.
@@ -74,7 +75,7 @@ export function matchDesktopProject(projects, cwd, { canonicalize = realpathSync
74
75
  return matches[0];
75
76
  }
76
77
 
77
- export const RELAY_DOWN_GUIDANCE = "The Codex Desktop native relay is not listening after waiting for silent recovery. Its Desktop-owned supervisor restarts crashed relay workers without replaying requests. If Desktop has not loaded the relay MCP, reconnect it in a regular project task; automatic recovery never opens a task or window. Do not open the internal Native Relay executor task.";
78
+ export const RELAY_DOWN_GUIDANCE = "The Codex Desktop native relay is not listening after waiting for silent recovery. Its Desktop-owned supervisor restarts crashed relay workers without replaying requests. An unloaded native host cannot be cold-started by this bridge. Inspect codex_bridge_status and include its diagnostic in a report. Never open an app or an old, new, empty or failed chat to load a relay.";
78
79
 
79
80
  function relayUnreachable(error) {
80
81
  return error?.code === "RELAY_UNREACHABLE" && error.sent === false;
@@ -196,13 +197,17 @@ export class DesktopTaskDelivery {
196
197
  async status() {
197
198
  const accountContext = this.accountContext?.();
198
199
  const socketPath = this.relay.status?.({ accountContext })?.socketPath ?? this.relay.socketPath;
199
- if (this.accountContext && !accountContext) return { available: false, socketPath, reason: "The current Claude and Codex accounts could not both be verified. Finish signing in and inspect the account diagnostics before sending work." };
200
+ if (this.accountContext && !accountContext) {
201
+ const reason = "The current Claude and Codex accounts could not both be verified. Finish signing in and inspect the account diagnostics before sending work.";
202
+ return { available: false, socketPath, code: "BRIDGE_ACCOUNT_UNVERIFIED", reason, diagnostic: bridgeDiagnostic(new Error(reason), { component: "codex-bridge", operation: "codex_bridge_status", code: "BRIDGE_ACCOUNT_UNVERIFIED", readOnly: true }) };
203
+ }
200
204
  try {
201
205
  const response = await this.request("list_projects", {});
202
206
  if (!Array.isArray(response?.projects)) throw new Error("Desktop returned no project list");
203
207
  return { available: true, socketPath, localProjects: response.projects.filter((project) => project.projectKind === "local" && project.hostId === "local").length };
204
208
  } catch (err) {
205
- return { available: false, socketPath, reason: err.message };
209
+ const diagnostic = bridgeDiagnostic(err, { component: "codex-bridge", operation: "codex_bridge_status", readOnly: true });
210
+ return { available: false, socketPath, code: diagnostic.code, reason: err.message, diagnostic };
206
211
  }
207
212
  }
208
213
 
@@ -336,11 +341,11 @@ export class DesktopTaskDelivery {
336
341
  const confirmedId = response?.threadId ?? response?.conversationId;
337
342
  threadId = confirmedId;
338
343
  if (!threadId && (response?.status === "outcome-unknown" || response?.firstTurn?.status === "outcome-unknown")) threadId = response?.clientThreadId;
339
- if (typeof confirmedId !== "string" || !confirmedId.trim() || response?.status === "outcome-unknown" || response.hostId !== "local") throw new Error(`Desktop creation is not confirmed: ${JSON.stringify(response)}`);
344
+ if (typeof confirmedId !== "string" || !confirmedId.trim() || response?.status === "outcome-unknown" || response.hostId !== "local") throw Object.assign(new Error("Desktop creation is not confirmed; inspect the original native receipt."), { code: "CODEX_CREATION_UNCONFIRMED", deliveryState: "unknown", phase: "creation" });
340
345
  await this.receipts.write(identity.key, { ...receipt, state: "known", threadId });
341
346
  creationConfirmed = true;
342
347
  this.security.registerThread(threadId);
343
- if (response.firstTurn && response.firstTurn.status !== "accepted") throw new Error(`Desktop created task ${threadId}; its first turn reports ${response.firstTurn.status ?? "an unconfirmed state"}: ${JSON.stringify(response)}`);
348
+ if (response.firstTurn && response.firstTurn.status !== "accepted") throw Object.assign(new Error(`Desktop created task ${threadId}; its first turn reports ${["failed", "waitingOnApproval", "waitingOnUserInput", "outcome-unknown"].includes(response.firstTurn.status) ? response.firstTurn.status : "an unconfirmed state"}. Inspect the original native receipt.`), { code: "CODEX_FIRST_TURN_NOT_ACCEPTED", deliveryState: "sent", phase: "first_turn" });
344
349
  const created = { threadId, name, cwd, projectId: project.projectId, projectName: project.label, backend: NATIVE_BACKEND };
345
350
  const delivered = { ...created, previousTurnId: null, deliveryStatus: "accepted", responseObservation: {
346
351
  operation: "create_thread", threadId, previousTurnId: null, expectedCwd: cwd,