@chibitek/chibi 0.1.157 → 0.1.160

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 (107) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-BkYbog6D.js → abnfDiagram-O67JEVCF-CpuRvzQ3.js} +1 -1
  2. package/dist/assets/{arc-QwTuMsxp.js → arc-ko1e0R6h.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-GdicO2PQ.js → architectureDiagram-NJMV4G6O-OZ3SsOOa.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-Dkr29TFF.js → blockDiagram-BEXU5L5S-DLRkBkjs.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-Dz_3dstx.js → c4Diagram-YGBWAQC7-e8Q3qMnw.js} +1 -1
  6. package/dist/assets/channel-Lp7erhx-.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-CGsfu-PI.js → chunk-2Q5K7J3B-hCDqaM4Q.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-CDqI37_6.js → chunk-3FUC2YCW-Dy3Pahu-.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-DnmSuyTb.js → chunk-5VM5RSS4-QMK3iFBF.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-Dh7tXcJT.js → chunk-GTNCS2PH-6fyp9ERp.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-D_SM1ZrY.js → chunk-GWA4HPMP-DWvOhTaO.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-DmeMuTsI.js → chunk-JWPE2WC7-DgohMW2S.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-R1UT9b5f.js → chunk-NETBCI7D-sRGeD2Y7.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-DpPfU1yO.js → chunk-WEXAMYUT-CZslD_VF.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-DlihfTkM.js → chunk-XXDRQBXY-D7ocO3VN.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-BlTV6lSC.js → classDiagram-v2-NBCMYWYE-T1lLqHHW.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-Br1kPFWw.js → cose-bilkent-JH36ORCC-BdeLiJr3.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-BqPp3ZMd.js → cynefin-OW5HDTMX-BydCA0pv.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-DFfs_k8-.js → cynefinDiagram-VND7K2PF-DBxckjwQ.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-Ch3xIJA7.js → dagre-6A5THRUB-CAsvq6Zk.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-DIHUD87N.js → diagram-22UHCM2B-DTwH3rA_.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-CAgQQdeF.js → diagram-3UASUU5V-BNYj1bey.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-9HVKodrI.js → diagram-ATOU4E4O-CihwYFxb.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-DU2kDq_h.js → diagram-CDSNMT55-nouwk3s2.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-D-qTAhHD.js → diagram-MLGK6HIB-BoKewJeA.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-C31SnI9P.js → diagram-MPIPVDR6-CrE1m9DQ.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-BHrdiBVd.js → ebnfDiagram-ZINNZB2B-DRj0aU4Y.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-DWyljRo5.js → elk-276RUBZZ-Do0JzCG5.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-DyY59XPp.js → erDiagram-OPXOYQCR-CRsI9pXb.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-BevMIeAy.js → flowDiagram-KWPJA3E3-dtD6wm3G.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-Ct18WHf4.js → ganttDiagram-FUAMR5RP-Cn7KyLCo.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-yhixO1N3.js → gitGraphDiagram-X574FWY7-CGH_VWzq.js} +1 -1
  33. package/dist/assets/index-B2fJMh8N.css +1 -0
  34. package/dist/assets/{index-ziyH_uer.js → index-BIcziktH.js} +253 -250
  35. package/dist/assets/{index-BEJ8EKQt.js → index-Bk8iAodB.js} +1 -1
  36. package/dist/assets/{infoDiagram-VRGFBTTK-CYa-r_S5.js → infoDiagram-VRGFBTTK-CQKsYTCL.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-OU5B5YK6-CnxwM7gY.js → ishikawaDiagram-OU5B5YK6-D5catDe0.js} +1 -1
  38. package/dist/assets/{journeyDiagram-ZHPQQLJL-RKQfXBrp.js → journeyDiagram-ZHPQQLJL-KjY8mki9.js} +1 -1
  39. package/dist/assets/{kanban-definition-PNTS6WVX-CSlRDIF6.js → kanban-definition-PNTS6WVX-BKCyJtge.js} +1 -1
  40. package/dist/assets/{linear-Ch19KVyt.js → linear-B-_obUkq.js} +1 -1
  41. package/dist/assets/{mermaid.core-Bek7I_t6.js → mermaid.core-DlSzuDKt.js} +5 -5
  42. package/dist/assets/{mindmap-definition-NLK3R4M7-B_LiP8su.js → mindmap-definition-NLK3R4M7-BFQSaYKh.js} +1 -1
  43. package/dist/assets/{pegDiagram-GJSIUBJH-DhzQeiZf.js → pegDiagram-GJSIUBJH-l51izlev.js} +1 -1
  44. package/dist/assets/{pieDiagram-5QR66LMP-D-BYNKHS.js → pieDiagram-5QR66LMP-6NEXRoZX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-O4NWA36T-DnCm0heP.js → quadrantDiagram-O4NWA36T-BCC9XlOe.js} +1 -1
  46. package/dist/assets/{railroadDiagram-XR7U4H2S-COqgYnhn.js → railroadDiagram-XR7U4H2S-Dd4TmJ86.js} +1 -1
  47. package/dist/assets/{requirementDiagram-PLB6GJNP-Dvt72ajf.js → requirementDiagram-PLB6GJNP-C1E5DaDb.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-IPEJSGJF-CisC9nbp.js → sankeyDiagram-IPEJSGJF-Ci6r7abb.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-PO4LG4MO-BO8xqvxa.js → sequenceDiagram-PO4LG4MO-BJ-I6WOh.js} +1 -1
  50. package/dist/assets/{sizeCapture-INFHLROL-DLk1jBzf.js → sizeCapture-INFHLROL-Cs5QbLOP.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-GCMORJYK-C_wutSE7.js → stateDiagram-v2-GCMORJYK-Bw--rdvq.js} +1 -1
  52. package/dist/assets/{swimlanes-2SLR337P-hqo1UlEi.js → swimlanes-2SLR337P-B1O9IEz5.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-TC7HE7FX-C6wGgYKP.js +8 -0
  54. package/dist/assets/{timeline-definition-EJHVYXUP-C_DJldPg.js → timeline-definition-EJHVYXUP-CTZ7DXaw.js} +1 -1
  55. package/dist/assets/{usecaseDiagram-POWQR4AR-CsidVXMA.js → usecaseDiagram-POWQR4AR-DjwgNG0H.js} +1 -1
  56. package/dist/assets/{vennDiagram-UO4OBE2U-Bs-IIqn_.js → vennDiagram-UO4OBE2U-DrdJG55h.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-VNRHLVJA-CXEUyxaj.js → wardleyDiagram-VNRHLVJA-HVq776nz.js} +1 -1
  58. package/dist/assets/{xychartDiagram-PMCCYNJV-B9qvmm4P.js → xychartDiagram-PMCCYNJV-DPf9VEua.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/dist-server/connector-proxy.js +205 -0
  61. package/dist-server/container-mcp.js +150 -96
  62. package/dist-server/drivers/agents-proxy.js +81 -81
  63. package/dist-server/index.js +7054 -6099
  64. package/dist-server/openmausbot.js +940 -727
  65. package/dist-server/pair-cli.js +940 -727
  66. package/dist-server/server/attachment-mimes.js +58 -0
  67. package/dist-server/server/attachments.js +28 -67
  68. package/dist-server/server/avatar-image.js +1 -0
  69. package/dist-server/server/avatar-subscription.js +74 -0
  70. package/dist-server/server/cli.js +5 -4
  71. package/dist-server/server/colima-docker.js +136 -0
  72. package/dist-server/server/composio.js +1051 -0
  73. package/dist-server/server/connector-proxy.js +249 -0
  74. package/dist-server/server/container-computer.js +31 -63
  75. package/dist-server/server/container-mcp.js +5 -0
  76. package/dist-server/server/drivers/claude-message.js +12 -0
  77. package/dist-server/server/drivers/claude.js +9 -12
  78. package/dist-server/server/drivers/codex-image.js +141 -0
  79. package/dist-server/server/drivers/codex.js +6 -9
  80. package/dist-server/server/drivers/openai-chat.js +13 -2
  81. package/dist-server/server/engine-failover.js +62 -12
  82. package/dist-server/server/engine-failure.js +2 -2
  83. package/dist-server/server/harness-import/history.js +5 -5
  84. package/dist-server/server/harness-import/test-fixtures.js +1 -1
  85. package/dist-server/server/index.js +75 -36
  86. package/dist-server/server/mcp-public-registry.js +325 -0
  87. package/dist-server/server/message-reactions.js +11 -0
  88. package/dist-server/server/model-evidence-context.js +18 -0
  89. package/dist-server/server/model-evidence.js +145 -0
  90. package/dist-server/server/port-in-use.js +21 -0
  91. package/dist-server/server/session-fallback.js +76 -0
  92. package/dist-server/server/vault/routes.js +43 -0
  93. package/dist-server/server/vault/runtime.js +48 -0
  94. package/dist-server/server/vault/store.js +177 -0
  95. package/dist-server/shared/connector-availability.js +12 -0
  96. package/dist-server/shared/desktop-private-port.js +1 -0
  97. package/dist-server/shared/emoji-catalog.json +1 -0
  98. package/dist-server/shared/emoji.js +27 -0
  99. package/dist-server/shared/mascot-bodies.js +9 -1
  100. package/dist-server/shared/model-evidence.js +25 -0
  101. package/dist-server/shared/subscription-bots.js +3 -0
  102. package/dist-server/shared/vault.js +59 -0
  103. package/dist-server/vps-container-mcp.js +86 -84
  104. package/package.json +1 -1
  105. package/dist/assets/channel-D2y0i2ps.js +0 -1
  106. package/dist/assets/index-DGWeJcck.css +0 -1
  107. package/dist/assets/swimlanesDiagram-TC7HE7FX-D15JqTL9.js +0 -8
@@ -0,0 +1,249 @@
1
+ // Harness-owned Composio MCP bridge.
2
+ //
3
+ // Provider CLIs only see this stdio server. Ordinary MCP traffic is relayed
4
+ // to the configured Composio Session, but connection requests are converted
5
+ // into first-class OpenMausBot chat cards. The agent never authors an auth
6
+ // URL and credentials never pass through its transcript.
7
+ //
8
+ // stdout is the MCP transport. Never log there.
9
+ import readline from "node:readline";
10
+ import { randomUUID } from "node:crypto";
11
+ const UPSTREAM = process.env.OMB_CONNECTOR_UPSTREAM_URL ?? "";
12
+ const HARNESS = process.env.OMB_HARNESS_URL ?? "http://127.0.0.1:8799";
13
+ const BOT_ID = process.env.OMB_BOT_ID ?? "";
14
+ const THREAD_ID = process.env.OMB_THREAD_ID ?? "";
15
+ const TOKEN = process.env.OMB_CONNECTOR_TOKEN ?? process.env.OMB_COMMS_TOKEN ?? "";
16
+ const MAX_RESPONSE_BYTES = 20 * 1024 * 1024;
17
+ const INITIALIZE_RELAY_TIMEOUT_MS = 1_000;
18
+ const RELAY_TIMEOUT_MS = 10 * 60_000;
19
+ function parsedHeaders() {
20
+ try {
21
+ const value = JSON.parse(process.env.OMB_CONNECTOR_UPSTREAM_HEADERS ?? "{}");
22
+ if (!value || typeof value !== "object" || Array.isArray(value))
23
+ return {};
24
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
25
+ }
26
+ catch {
27
+ return {};
28
+ }
29
+ }
30
+ const upstreamHeaders = parsedHeaders();
31
+ let upstreamSessionId = "";
32
+ const send = (message) => process.stdout.write(`${JSON.stringify(message)}\n`);
33
+ function textResult(id, text, isError = false) {
34
+ return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text }], ...(isError ? { isError: true } : {}) } };
35
+ }
36
+ function jsonRpcError(id, message) {
37
+ return { jsonrpc: "2.0", id, error: { code: -32000, message } };
38
+ }
39
+ function initializeResult(id, protocolVersion) {
40
+ return {
41
+ jsonrpc: "2.0",
42
+ id,
43
+ result: {
44
+ protocolVersion: typeof protocolVersion === "string" && protocolVersion ? protocolVersion : "2024-11-05",
45
+ capabilities: { tools: {} },
46
+ serverInfo: { name: "openmausbot-connectors", version: "1" },
47
+ },
48
+ };
49
+ }
50
+ async function readBounded(response) {
51
+ const declared = Number(response.headers.get("content-length") ?? "0");
52
+ if (declared > MAX_RESPONSE_BYTES)
53
+ throw new Error("connector response exceeded 20 MB");
54
+ if (!response.body)
55
+ return "";
56
+ const reader = response.body.getReader();
57
+ const decoder = new TextDecoder();
58
+ let size = 0;
59
+ let text = "";
60
+ for (;;) {
61
+ const { done, value } = await reader.read();
62
+ if (done)
63
+ break;
64
+ size += value.byteLength;
65
+ if (size > MAX_RESPONSE_BYTES) {
66
+ await reader.cancel();
67
+ throw new Error("connector response exceeded 20 MB");
68
+ }
69
+ text += decoder.decode(value, { stream: true });
70
+ }
71
+ return text + decoder.decode();
72
+ }
73
+ function parseUpstream(text, id) {
74
+ const trimmed = text.trim();
75
+ if (!trimmed)
76
+ return null;
77
+ if (trimmed.startsWith("{"))
78
+ return JSON.parse(trimmed);
79
+ const frames = trimmed
80
+ .split(/\r?\n/)
81
+ .filter((line) => line.startsWith("data:"))
82
+ .map((line) => line.slice(5).trim())
83
+ .filter((line) => line && line !== "[DONE]")
84
+ .flatMap((line) => {
85
+ try {
86
+ return [JSON.parse(line)];
87
+ }
88
+ catch {
89
+ return [];
90
+ }
91
+ });
92
+ return frames.findLast((frame) => frame.id === id) ?? frames.at(-1) ?? null;
93
+ }
94
+ async function relay(message, timeoutMs = RELAY_TIMEOUT_MS) {
95
+ if (!UPSTREAM)
96
+ throw new Error("connected apps are unavailable");
97
+ const response = await fetch(UPSTREAM, {
98
+ method: "POST",
99
+ headers: {
100
+ "content-type": "application/json",
101
+ accept: "application/json, text/event-stream",
102
+ ...upstreamHeaders,
103
+ ...(upstreamSessionId ? { "mcp-session-id": upstreamSessionId } : {}),
104
+ },
105
+ body: JSON.stringify(message),
106
+ signal: AbortSignal.timeout(timeoutMs),
107
+ });
108
+ const nextSession = response.headers.get("mcp-session-id");
109
+ if (nextSession)
110
+ upstreamSessionId = nextSession;
111
+ if (!response.ok)
112
+ throw new Error(`connector service returned HTTP ${response.status}`);
113
+ return parseUpstream(await readBounded(response), message.id);
114
+ }
115
+ function connectorAdds(args) {
116
+ if (!args || typeof args !== "object" || Array.isArray(args))
117
+ return [];
118
+ const toolkits = args.toolkits;
119
+ if (!Array.isArray(toolkits))
120
+ return [];
121
+ const seen = new Set();
122
+ const requests = [];
123
+ for (const item of toolkits) {
124
+ if (typeof item === "string") {
125
+ const slug = item.trim().toLowerCase();
126
+ const key = JSON.stringify([slug, ""]);
127
+ if (!seen.has(key)) {
128
+ seen.add(key);
129
+ requests.push({ slug });
130
+ }
131
+ continue;
132
+ }
133
+ if (!item || typeof item !== "object" || Array.isArray(item))
134
+ continue;
135
+ const row = item;
136
+ const action = String(row.action ?? "add").toLowerCase();
137
+ if (!["add", "connect", "initiate"].includes(action))
138
+ continue;
139
+ const slug = typeof row.toolkit === "string" ? row.toolkit : row.name;
140
+ if (typeof slug !== "string")
141
+ continue;
142
+ const normalized = slug.trim().toLowerCase();
143
+ const alias = (typeof row.alias === "string" ? row.alias : typeof row.account === "string" ? row.account : "").trim();
144
+ const key = JSON.stringify([normalized, alias.toLowerCase()]);
145
+ if (seen.has(key))
146
+ continue;
147
+ seen.add(key);
148
+ requests.push({ slug: normalized, ...(alias ? { alias } : {}) });
149
+ }
150
+ return requests;
151
+ }
152
+ async function showConnectorCards(items) {
153
+ const response = await fetch(`${HARNESS}/api/internal/connectors/request`, {
154
+ method: "POST",
155
+ headers: { "content-type": "application/json", authorization: `Bearer ${TOKEN}` },
156
+ body: JSON.stringify({ botId: BOT_ID, threadId: THREAD_ID, items, resumeKey: randomUUID() }),
157
+ signal: AbortSignal.timeout(30_000),
158
+ });
159
+ if (!response.ok) {
160
+ const body = (await response.json().catch(() => ({})));
161
+ throw new Error(String(body.error ?? `could not show connection card (HTTP ${response.status})`));
162
+ }
163
+ }
164
+ async function handle(message) {
165
+ const id = message.id;
166
+ const method = String(message.method ?? "");
167
+ // OpenCode (and other MCP clients) mark a stdio server failed unless
168
+ // initialize returns capabilities/serverInfo. Relaying that handshake to
169
+ // Composio can time out, return a newer protocolVersion, or throw when the
170
+ // upstream URL never reached the child env — all of which previously
171
+ // surfaced as a tools/call-shaped {content,isError} payload.
172
+ if (method === "notifications/initialized" || method === "initialized") {
173
+ if (UPSTREAM)
174
+ void relay(message).catch(() => { });
175
+ return;
176
+ }
177
+ if (method === "initialize") {
178
+ if (UPSTREAM) {
179
+ try {
180
+ // Capture the upstream session id when the service is healthy, but
181
+ // never let a stalled provider prevent the local MCP client from
182
+ // mounting the connector tools. The client sends initialized only
183
+ // after this bounded attempt and the local initialize response.
184
+ await relay(message, INITIALIZE_RELAY_TIMEOUT_MS);
185
+ }
186
+ catch {
187
+ // Best-effort session setup. The client still needs a valid result.
188
+ }
189
+ }
190
+ if (id !== undefined) {
191
+ const params = (message.params ?? {});
192
+ send(initializeResult(id, params.protocolVersion));
193
+ }
194
+ return;
195
+ }
196
+ if (method === "tools/call") {
197
+ const params = (message.params ?? {});
198
+ const name = String(params.name ?? "");
199
+ const requests = /MANAGE_CONNECTIONS$/i.test(name) ? connectorAdds(params.arguments) : [];
200
+ if (requests.length) {
201
+ await showConnectorCards(requests);
202
+ const labels = requests.map((r) => (r.alias ? `${r.slug} (${r.alias})` : r.slug)).join(", ");
203
+ send(textResult(id, `Atomic showed the user a secure connection card for ${labels}. End this turn now. The app will continue the task automatically after the connection finishes.`));
204
+ return;
205
+ }
206
+ if (/WAIT_FOR_CONNECTIONS$/i.test(name)) {
207
+ send(textResult(id, "Atomic is handling connection completion and will continue the task automatically."));
208
+ return;
209
+ }
210
+ }
211
+ try {
212
+ const response = await relay(message);
213
+ if (response && id !== undefined)
214
+ send(response);
215
+ }
216
+ catch (error) {
217
+ if (id === undefined)
218
+ return;
219
+ const messageText = error instanceof Error ? error.message : String(error);
220
+ if (method === "tools/call")
221
+ send(textResult(id, messageText, true));
222
+ else
223
+ send(jsonRpcError(id, messageText));
224
+ }
225
+ }
226
+ const input = readline.createInterface({ input: process.stdin, terminal: false });
227
+ input.on("line", (line) => {
228
+ const trimmed = line.trim();
229
+ if (!trimmed)
230
+ return;
231
+ let message;
232
+ try {
233
+ message = JSON.parse(trimmed);
234
+ }
235
+ catch {
236
+ return;
237
+ }
238
+ void handle(message).catch((error) => {
239
+ if (message.id === undefined)
240
+ return;
241
+ const method = String(message.method ?? "");
242
+ const messageText = error instanceof Error ? error.message : String(error);
243
+ if (method === "tools/call")
244
+ send(textResult(message.id, messageText, true));
245
+ else
246
+ send(jsonRpcError(message.id, messageText));
247
+ });
248
+ });
249
+ input.on("close", () => process.exit(0));
@@ -6,13 +6,13 @@
6
6
  // `cua-driver mcp` inside the container; this module never reimplements clicks,
7
7
  // typing, screenshots, accessibility, or window discovery.
8
8
  import { execFile } from "node:child_process";
9
- import { existsSync } from "node:fs";
10
9
  import { createHash, randomBytes } from "node:crypto";
11
10
  import { chmod, lstat, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
12
11
  import { tmpdir } from "node:os";
13
- import { dirname, join, resolve, win32 } from "node:path";
12
+ import { dirname, join, resolve } from "node:path";
14
13
  import { promisify } from "node:util";
15
14
  import { augmentedPath, resolveCliSpawn } from "./env-path.js";
15
+ import { colimaStartPlan, dockerEnvForColima, startColima } from "./colima-docker.js";
16
16
  import { spawnOptionsFor } from "./spawn-command.js";
17
17
  import { DATA_DIR } from "./config.js";
18
18
  import { SPAWNED_PROXIES } from "./proxy-paths.js";
@@ -188,7 +188,11 @@ async function sh(cmd, args, timeout = 8000) {
188
188
  timeout,
189
189
  encoding: "utf8",
190
190
  maxBuffer: 16 * 1024 * 1024,
191
- env: { ...process.env, PATH: augmentedPath() },
191
+ // Every docker call on a Mac goes to Colima's socket, whatever docker
192
+ // context Docker Desktop may have left active.
193
+ env: cmd === "docker"
194
+ ? dockerEnvForColima({ ...process.env, PATH: augmentedPath() })
195
+ : { ...process.env, PATH: augmentedPath() },
192
196
  }));
193
197
  return { stdout };
194
198
  }
@@ -233,77 +237,36 @@ export async function containerRuntimeStatus(runner = sh, platform = process.pla
233
237
  daemonUp: healthyIndex >= 0,
234
238
  };
235
239
  }
236
- const COLIMA_START_TIMEOUT_MS = 4 * 60_000;
237
- const DESKTOP_LAUNCH_TIMEOUT_MS = 30_000;
238
- export async function containerRuntimeStartPlan(runner = sh, platform = process.platform, appExists = existsSync, env = process.env) {
239
- if (platform === "darwin") {
240
- const context = await runner("docker", ["context", "show"], 4000).then(({ stdout }) => stdout.trim()).catch(() => "");
241
- const desktopApp = ["/Applications/Docker.app", join(env.HOME ?? "", "Applications", "Docker.app")].some((path) => appExists(path));
242
- const desktop = { label: "Docker Desktop", command: "open", args: ["-a", "Docker"], timeoutMs: DESKTOP_LAUNCH_TIMEOUT_MS };
243
- if (context === "desktop-linux" && desktopApp)
244
- return desktop;
245
- if (await installed("colima", runner, platform)) {
246
- // `colima start` on a running profile is a no-op, so an instance the
247
- // person started with their own flags is left exactly as it is.
248
- const profile = context.startsWith("colima-") ? context.slice("colima-".length) : "";
249
- return {
250
- label: "Colima",
251
- command: "colima",
252
- args: ["start", ...(profile ? ["--profile", profile] : [])],
253
- timeoutMs: COLIMA_START_TIMEOUT_MS,
254
- };
255
- }
256
- return desktopApp ? desktop : null;
257
- }
258
- if (platform === "win32") {
259
- const exe = win32.join(env.ProgramFiles ?? "C:\\Program Files", "Docker", "Docker", "Docker Desktop.exe");
260
- // Start-Process returns at once; Docker Desktop keeps running on its own.
261
- return appExists(exe)
262
- ? {
263
- label: "Docker Desktop",
264
- command: "powershell.exe",
265
- args: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process -FilePath '${exe.replaceAll("'", "''")}'`],
266
- timeoutMs: DESKTOP_LAUNCH_TIMEOUT_MS,
267
- }
268
- : null;
269
- }
270
- return null;
240
+ /** How this machine's stopped Docker engine is started, or null when Atomic
241
+ * must not try: Colima on a Mac where it is installed, never Docker Desktop
242
+ * on any platform (colima-docker.ts). Linux needs root for the daemon and
243
+ * Windows runs the Local VM on Podman, so neither is started here. */
244
+ export async function containerRuntimeStartPlan(runner = sh, platform = process.platform, env = process.env) {
245
+ return platform === "darwin" && await installed("colima", runner, platform) ? colimaStartPlan(env) : null;
271
246
  }
272
247
  let runtimeStartInFlight = null;
273
- /** Start the person's installed Docker engine when it is stopped, then wait,
274
- * bounded, until `docker info` answers. Never installs anything and never
275
- * touches Podman or Apple container. One start at a time: concurrent callers
276
- * (startup and a turn) share the same attempt. Callers decide whether the
277
- * person allowed it; this only does it. */
248
+ let runtimeStartError = null;
249
+ /** Why the last start left the Local VM without an engine; null once docker answers. */
250
+ export const containerRuntimeError = () => runtimeStartError;
251
+ /** Start the person's installed Colima when docker is not answering, with a
252
+ * retry and a restart, bounded (startColima). Never installs anything, never
253
+ * opens Docker Desktop, never touches Podman or Apple container. One start at
254
+ * a time: concurrent callers (startup and a turn) share the same attempt.
255
+ * Callers decide whether the person allowed it; this only does it. */
278
256
  export function ensureContainerRuntime(opts = {}) {
279
257
  if (runtimeStartInFlight)
280
258
  return runtimeStartInFlight;
281
259
  const attempt = (async () => {
282
260
  const runner = opts.runner ?? sh;
283
261
  const platform = opts.platform ?? process.platform;
284
- const sleep = opts.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
285
262
  const before = await containerRuntimeStatus(runner, platform);
286
263
  if (before.daemonUp || before.runtime !== "docker")
287
- return { status: before, started: null };
288
- const plan = await containerRuntimeStartPlan(runner, platform, opts.appExists, opts.env);
289
- if (!plan)
290
- return { status: before, started: null };
291
- try {
292
- await runner(plan.command, plan.args, plan.timeoutMs);
293
- }
294
- catch {
295
- // Colima can exit non-zero while its VM is still coming up, and the
296
- // wait below is the real test; a launch that truly failed times out.
297
- }
298
- const deadline = Date.now() + (opts.waitMs ?? 120_000);
299
- let status = await containerRuntimeStatus(runner, platform);
300
- while (!status.daemonUp && Date.now() < deadline) {
301
- await sleep(opts.pollMs ?? 2000);
302
- status = await containerRuntimeStatus(runner, platform);
303
- }
304
- return { status, started: plan.label };
264
+ return { status: before, started: null, error: null };
265
+ const plan = await containerRuntimeStartPlan(runner, platform, opts.env);
266
+ return startColima(plan, before, { runner, platform, status: () => containerRuntimeStatus(runner, platform), ...opts });
305
267
  })();
306
268
  runtimeStartInFlight = attempt;
269
+ void attempt.then((outcome) => { runtimeStartError = outcome.status.daemonUp ? null : outcome.error; }, () => { });
307
270
  void attempt.finally(() => { if (runtimeStartInFlight === attempt)
308
271
  runtimeStartInFlight = null; }).catch(() => { });
309
272
  return attempt;
@@ -989,6 +952,11 @@ export function containerComputerMcp(runtime, control, target = SHARED_LOCAL_VM_
989
952
  // through `ps` for the life of the bridge.
990
953
  env: {
991
954
  ELECTRON_RUN_AS_NODE: "1",
955
+ // The bridge pins docker to Colima itself too (container-mcp.ts); this
956
+ // keeps a host that spawns it with a scrubbed environment on Colima.
957
+ ...(runtime === "docker" && process.platform === "darwin"
958
+ ? { DOCKER_HOST: dockerEnvForColima(process.env, "darwin").DOCKER_HOST ?? "" }
959
+ : {}),
992
960
  ...(control ? { OMB_CONTROL_URL: control.url, OMB_CONTROL_TOKEN: control.token } : {}),
993
961
  // Where the bridge remembers Cua Driver's tool list, and whether it
994
962
  // must stand in for Cua Driver until the first tool call (the VM is
@@ -1011,7 +979,7 @@ export function setupCommands(runtime, platform = process.platform, target = SHA
1011
979
  : runtime === "podman" && platform !== "linux"
1012
980
  ? "podman machine init; podman machine start"
1013
981
  : runtime === "docker" && platform === "darwin"
1014
- ? "colima start || open -a Docker"
982
+ ? "colima start"
1015
983
  : runtime === "docker" && platform === "linux"
1016
984
  ? "sudo systemctl start docker"
1017
985
  : null;
@@ -3,6 +3,8 @@
3
3
  // piping, drain-safe exit, and watchdog live in mcp-bridge.ts, shared with
4
4
  // the VPS entry point.
5
5
  import { cuaExecArgs } from "./container-computer.js";
6
+ import { dockerEnvForColima } from "./colima-docker.js";
7
+ import { augmentedPath } from "./env-path.js";
6
8
  import { runMcpBridge } from "./mcp-bridge.js";
7
9
  const [runtime, container, socket] = process.argv.slice(2);
8
10
  if (!runtime || !["docker", "podman", "container"].includes(runtime)) {
@@ -27,6 +29,9 @@ runMcpBridge({
27
29
  command: runtime,
28
30
  args: cuaExecArgs(["mcp", "--socket", socket], { container, interactive: true }),
29
31
  label: "Cua Driver",
32
+ // On a Mac, docker always talks to Colima's socket, never to whatever
33
+ // context Docker Desktop left active.
34
+ ...(runtime === "docker" ? { env: dockerEnvForColima({ ...process.env, PATH: augmentedPath() }) } : {}),
30
35
  // No liveness watchdog: the runtime CLI talks to a local daemon and fails
31
36
  // fast on its own — there is no silent WAN peer to wedge on.
32
37
  ...(controlUrl && controlToken ? { gate: { url: controlUrl, token: controlToken } } : {}),
@@ -0,0 +1,12 @@
1
+ // Extract text from the native Claude response before transcript normalization.
2
+ export function firstText(content) {
3
+ if (typeof content === "string")
4
+ return content;
5
+ if (Array.isArray(content)) {
6
+ return content
7
+ .filter((b) => b?.type === "text" && b.text)
8
+ .map((b) => b.text)
9
+ .join("");
10
+ }
11
+ return "";
12
+ }
@@ -1,3 +1,4 @@
1
+ import { firstText } from "./claude-message.js";
1
2
  // Claude driver — upstream ClaudeDriver skeleton over agentcal's
2
3
  // drivers/claude.js runtime (stream-json both directions, prompt over
3
4
  // stdin, completion from a real `result` event — verified against
@@ -811,17 +812,6 @@ function decodeConfig(raw) {
811
812
  ...(disallowedTools !== undefined ? { disallowedTools } : {}),
812
813
  };
813
814
  }
814
- function firstText(content) {
815
- if (typeof content === "string")
816
- return content;
817
- if (Array.isArray(content)) {
818
- return content
819
- .filter((b) => b?.type === "text" && b.text)
820
- .map((b) => b.text)
821
- .join("");
822
- }
823
- return "";
824
- }
825
815
  /** Claude's stream-json input accepts the same image source blocks as the
826
816
  * Anthropic Messages API. Keep the old string form for text-only turns so a
827
817
  * CLI update cannot disturb the overwhelmingly common path. */
@@ -1739,7 +1729,7 @@ export const ClaudeDriver = {
1739
1729
  delivered.sessionId = o.session_id;
1740
1730
  session.sessionId = o.session_id;
1741
1731
  }
1742
- emit({ ...base(threadId, currentTurnId()), type: "session.started", sessionId: o.session_id, model: o.model, ...(retry.rebuilt ? { rebuilt: true } : {}) });
1732
+ emit({ ...base(threadId, currentTurnId()), type: "session.started", sessionId: o.session_id, model: o.model, modelEvidenceLevel: "session-reported", ...(retry.rebuilt ? { rebuilt: true } : {}) });
1743
1733
  }
1744
1734
  else if (o.subtype === "compact_boundary") {
1745
1735
  // The CLI summarized the history, notes included: the next
@@ -1773,6 +1763,13 @@ export const ClaudeDriver = {
1773
1763
  }
1774
1764
  case "assistant": {
1775
1765
  const msg = o.message ?? {};
1766
+ if (o.is_api_error_message !== true && typeof o.error !== "string") {
1767
+ emit({ ...base(threadId, currentTurnId()), type: "model.evidence",
1768
+ callId: typeof msg.id === "string" ? msg.id : newId(),
1769
+ responseId: typeof msg.id === "string" ? msg.id : undefined,
1770
+ model: typeof msg.model === "string" ? msg.model : null,
1771
+ level: typeof msg.model === "string" ? "response-reported" : "unknown" });
1772
+ }
1776
1773
  const text = firstText(msg.content);
1777
1774
  // CLI auth diagnostics enter the shared fallback path, never assistant text.
1778
1775
  if (claudeAuthFailure(o, text)) {
@@ -0,0 +1,141 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { killCliTree, spawnCli } from "../procs.js";
5
+ import { codexConfigMcpServers, codexUserMcpOffArgs } from "./codex-mcp-names.js";
6
+ import { codexPersonalSetupOffArgs } from "./codex-personal.js";
7
+ import { codexToolSurfaceArgs } from "./codex-tool-surface.js";
8
+ /** A separate ephemeral app-server: no chat bus, history, mounted integrations,
9
+ * API billing fallback or provider-supplied file reads. Prompt travels on stdin. */
10
+ export async function codexImage(cli, env, prompt, signal, diagnostic, model) {
11
+ signal.throwIfAborted();
12
+ const cwd = await mkdtemp(join(tmpdir(), "atomic-avatar-"));
13
+ const args = ["app-server", ...codexToolSurfaceArgs(),
14
+ ...codexUserMcpOffArgs(codexConfigMcpServers(env, cwd)), ...codexPersonalSetupOffArgs(env, cwd),
15
+ ...["shell_tool", "unified_exec", "view_image", "apps", "multi_agent", "multi_agent_v2", "code_mode"]
16
+ .flatMap(name => ["-c", `features.${name}=false`]),
17
+ "-c", "features.image_generation=true", "-c", 'web_search="disabled"',
18
+ "-c", 'model_provider="openai"', "-c", 'forced_login_method="chatgpt"',
19
+ "-c", "project_doc_max_bytes=0",
20
+ ];
21
+ let child;
22
+ try {
23
+ child = spawnCli(cli, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
24
+ }
25
+ catch {
26
+ await rm(cwd, { recursive: true, force: true });
27
+ throw new Error("Could not start image generation.");
28
+ }
29
+ const pending = new Map();
30
+ let nextId = 0, buffer = "", image, done = false;
31
+ let resolveImage;
32
+ let rejectImage;
33
+ const result = new Promise((resolve, reject) => { resolveImage = resolve; rejectImage = reject; });
34
+ // The handshake may still be awaiting an RPC when an abort/exit occurs.
35
+ void result.catch(() => { });
36
+ const fail = () => {
37
+ const error = new Error("Codex image generation did not complete.");
38
+ for (const waiter of pending.values())
39
+ waiter.reject(error);
40
+ pending.clear();
41
+ if (!done)
42
+ rejectImage(error);
43
+ done = true;
44
+ };
45
+ const send = (message) => { child.stdin.write(JSON.stringify(message) + "\n", error => { if (error)
46
+ fail(); }); };
47
+ const request = (method, params) => new Promise((resolve, reject) => {
48
+ if (done) {
49
+ reject(new Error("Image session ended."));
50
+ return;
51
+ }
52
+ const id = ++nextId;
53
+ pending.set(id, { resolve, reject });
54
+ send({ id, method, params });
55
+ });
56
+ child.on("error", fail);
57
+ child.on("exit", fail);
58
+ child.stdin.on("error", fail);
59
+ child.stderr.resume();
60
+ child.stdout.setEncoding("utf8");
61
+ child.stdout.on("data", (chunk) => {
62
+ buffer += chunk;
63
+ if (Buffer.byteLength(buffer) > 32 * 1024 * 1024) {
64
+ fail();
65
+ return;
66
+ }
67
+ for (;;) {
68
+ const newline = buffer.indexOf("\n");
69
+ if (newline < 0)
70
+ break;
71
+ const line = buffer.slice(0, newline);
72
+ buffer = buffer.slice(newline + 1);
73
+ try {
74
+ const message = JSON.parse(line);
75
+ if (message.method === "item/completed" || message.method === "turn/completed")
76
+ diagnostic?.({ method: message.method,
77
+ itemType: message.params?.item?.type, status: message.params?.turn?.status ?? message.params?.item?.status,
78
+ imageBytes: message.params?.item?.type === "imageGeneration" ? message.params.item.result?.length ?? 0 : undefined,
79
+ errorTerms: message.params?.turn?.error?.message?.match(/\b(?:usage|limit|quota|expired|auth|model|configuration|tools?|sandbox|rate|429|401|403|400|unsupported|invalid|image_generation|reasoning|summary|does not exist|not supported|not available|gpt-[\w.-]+)\b/gi) ?? undefined });
80
+ if (message.method && message.id !== undefined) {
81
+ // No permission, tool or user-input request can widen this session.
82
+ send({ id: message.id, error: { code: -32601, message: "Not available in avatar generation." } });
83
+ fail();
84
+ return;
85
+ }
86
+ if (message.id !== undefined) {
87
+ const waiter = pending.get(message.id);
88
+ pending.delete(message.id);
89
+ if (message.error)
90
+ waiter?.reject(new Error("Codex image request failed."));
91
+ else
92
+ waiter?.resolve(message.result);
93
+ }
94
+ else if (message.method === "item/completed" && message.params?.item?.type === "imageGeneration") {
95
+ const item = message.params.item;
96
+ if (item.status !== "failed" && typeof item.result === "string")
97
+ image = item.result;
98
+ }
99
+ else if (message.method === "turn/completed") {
100
+ if (message.params?.turn?.status !== "completed" || !image) {
101
+ fail();
102
+ return;
103
+ }
104
+ done = true;
105
+ resolveImage(image);
106
+ }
107
+ }
108
+ catch {
109
+ fail();
110
+ return;
111
+ }
112
+ }
113
+ });
114
+ signal.addEventListener("abort", fail, { once: true });
115
+ try {
116
+ if (signal.aborted)
117
+ fail();
118
+ await request("initialize", { clientInfo: { name: "atomic_avatar", version: "1" }, capabilities: { experimentalApi: true } });
119
+ send({ method: "initialized", params: {} });
120
+ const catalog = await request("model/list", { limit: 100 });
121
+ const choices = Array.isArray(catalog.data) ? catalog.data.filter(item => item && item.hidden !== true &&
122
+ (typeof item.model === "string" || typeof item.id === "string")) : [];
123
+ const choice = choices.find(item => item.id === model || item.model === model) ?? choices.find(item => item.isDefault) ?? choices[0];
124
+ if (!choice)
125
+ throw new Error("No subscription image model is available.");
126
+ const started = await request("thread/start", { cwd, model: choice.model ?? choice.id, modelProvider: "openai", ephemeral: true, approvalPolicy: "never", sandbox: "read-only",
127
+ developerInstructions: "Generate exactly one image with the native image generation tool. Do not run commands, read files, use other tools or ask for credentials." });
128
+ if (typeof started.thread?.id !== "string")
129
+ throw new Error("Missing image session.");
130
+ await request("turn/start", { threadId: started.thread.id, input: [{ type: "text", text: prompt }],
131
+ approvalPolicy: "never", sandboxPolicy: { type: "readOnly" } });
132
+ return await result;
133
+ }
134
+ finally {
135
+ signal.removeEventListener("abort", fail);
136
+ fail();
137
+ const stopped = await killCliTree(child);
138
+ if (stopped)
139
+ await rm(cwd, { recursive: true, force: true });
140
+ }
141
+ }
@@ -1,12 +1,8 @@
1
1
  import { codexToolSurfaceArgs } from "./codex-tool-surface.js";
2
- // Codex driver — upstream CodexDriver skeleton over agentcal's
3
- // drivers/codex.js runtime: the official `codex` CLI headless over its
4
- // app-server JSON-RPC protocol (newline-delimited JSON on stdio).
5
- // Completion is a real `turn/completed` notification; approval requests
6
- // arrive as in-process server→client JSON-RPC requests and surface as
7
- // canonical request.opened events (answered via respondToRequest — no MCP
8
- // proxy or unix socket needed, unlike claude). Verified against
9
- // codex-cli 0.144.4 by agentcal.
2
+ import { codexImage } from "./codex-image.js";
3
+ // Official Codex app-server over newline-delimited JSON-RPC on stdio.
4
+ // Completion comes from turn/completed; native approval requests become
5
+ // canonical request.opened events answered through respondToRequest.
10
6
  //
11
7
  // resumeCursor is the codex thread id; a later turn tries thread/resume
12
8
  // and preserves that history or reports a failed resume.
@@ -1557,7 +1553,7 @@ export const CodexDriver = {
1557
1553
  if (!codexThreadId)
1558
1554
  throw new Error("Codex did not return a native thread id");
1559
1555
  await syncCodexInstructions(threadId, codexThreadId, developerInstructions, resumedNativeThread, request);
1560
- emit({ ...base(threadId, turnId), type: "session.started", sessionId: codexThreadId, model: startedModel ?? turn.model ?? null, ...(rebuiltFromReplay ? { rebuilt: true } : {}) });
1556
+ emit({ ...base(threadId, turnId), type: "session.started", sessionId: codexThreadId, model: startedModel ?? turn.model ?? null, modelEvidenceLevel: startedModel ? "session-reported" : "requested-only", ...(rebuiltFromReplay ? { rebuilt: true } : {}) });
1561
1557
  const note = turn.systemStable !== undefined
1562
1558
  ? volatileNoteFor(threadId, codexThreadId, !resumedNativeThread || rebuiltFromReplay, turn)
1563
1559
  : "";
@@ -1700,6 +1696,7 @@ export const CodexDriver = {
1700
1696
  cancelAuthentication: () => authentication.cancel(),
1701
1697
  signOut: () => authentication.signOut(),
1702
1698
  snapshot,
1699
+ ...(!config.managed ? { generateImage: (prompt, options) => codexImage(config.cli, childEnv(), prompt, options.signal, undefined, decodeCodexSelection(models.default).model ?? undefined) } : {}),
1703
1700
  adapter: {
1704
1701
  provider: DRIVER_KIND,
1705
1702
  capabilities: {