@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.
- package/dist/assets/{abnfDiagram-O67JEVCF-BkYbog6D.js → abnfDiagram-O67JEVCF-CpuRvzQ3.js} +1 -1
- package/dist/assets/{arc-QwTuMsxp.js → arc-ko1e0R6h.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-GdicO2PQ.js → architectureDiagram-NJMV4G6O-OZ3SsOOa.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-Dkr29TFF.js → blockDiagram-BEXU5L5S-DLRkBkjs.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-Dz_3dstx.js → c4Diagram-YGBWAQC7-e8Q3qMnw.js} +1 -1
- package/dist/assets/channel-Lp7erhx-.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CGsfu-PI.js → chunk-2Q5K7J3B-hCDqaM4Q.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CDqI37_6.js → chunk-3FUC2YCW-Dy3Pahu-.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-DnmSuyTb.js → chunk-5VM5RSS4-QMK3iFBF.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dh7tXcJT.js → chunk-GTNCS2PH-6fyp9ERp.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-D_SM1ZrY.js → chunk-GWA4HPMP-DWvOhTaO.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DmeMuTsI.js → chunk-JWPE2WC7-DgohMW2S.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-R1UT9b5f.js → chunk-NETBCI7D-sRGeD2Y7.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-DpPfU1yO.js → chunk-WEXAMYUT-CZslD_VF.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DlihfTkM.js → chunk-XXDRQBXY-D7ocO3VN.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BlTV6lSC.js → classDiagram-v2-NBCMYWYE-T1lLqHHW.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-Br1kPFWw.js → cose-bilkent-JH36ORCC-BdeLiJr3.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-BqPp3ZMd.js → cynefin-OW5HDTMX-BydCA0pv.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DFfs_k8-.js → cynefinDiagram-VND7K2PF-DBxckjwQ.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-Ch3xIJA7.js → dagre-6A5THRUB-CAsvq6Zk.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DIHUD87N.js → diagram-22UHCM2B-DTwH3rA_.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CAgQQdeF.js → diagram-3UASUU5V-BNYj1bey.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-9HVKodrI.js → diagram-ATOU4E4O-CihwYFxb.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DU2kDq_h.js → diagram-CDSNMT55-nouwk3s2.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-D-qTAhHD.js → diagram-MLGK6HIB-BoKewJeA.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-C31SnI9P.js → diagram-MPIPVDR6-CrE1m9DQ.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BHrdiBVd.js → ebnfDiagram-ZINNZB2B-DRj0aU4Y.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DWyljRo5.js → elk-276RUBZZ-Do0JzCG5.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-DyY59XPp.js → erDiagram-OPXOYQCR-CRsI9pXb.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-BevMIeAy.js → flowDiagram-KWPJA3E3-dtD6wm3G.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-Ct18WHf4.js → ganttDiagram-FUAMR5RP-Cn7KyLCo.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-yhixO1N3.js → gitGraphDiagram-X574FWY7-CGH_VWzq.js} +1 -1
- package/dist/assets/index-B2fJMh8N.css +1 -0
- package/dist/assets/{index-ziyH_uer.js → index-BIcziktH.js} +253 -250
- package/dist/assets/{index-BEJ8EKQt.js → index-Bk8iAodB.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-CYa-r_S5.js → infoDiagram-VRGFBTTK-CQKsYTCL.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CnxwM7gY.js → ishikawaDiagram-OU5B5YK6-D5catDe0.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-RKQfXBrp.js → journeyDiagram-ZHPQQLJL-KjY8mki9.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-CSlRDIF6.js → kanban-definition-PNTS6WVX-BKCyJtge.js} +1 -1
- package/dist/assets/{linear-Ch19KVyt.js → linear-B-_obUkq.js} +1 -1
- package/dist/assets/{mermaid.core-Bek7I_t6.js → mermaid.core-DlSzuDKt.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-B_LiP8su.js → mindmap-definition-NLK3R4M7-BFQSaYKh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DhzQeiZf.js → pegDiagram-GJSIUBJH-l51izlev.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-D-BYNKHS.js → pieDiagram-5QR66LMP-6NEXRoZX.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DnCm0heP.js → quadrantDiagram-O4NWA36T-BCC9XlOe.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-COqgYnhn.js → railroadDiagram-XR7U4H2S-Dd4TmJ86.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-Dvt72ajf.js → requirementDiagram-PLB6GJNP-C1E5DaDb.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CisC9nbp.js → sankeyDiagram-IPEJSGJF-Ci6r7abb.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BO8xqvxa.js → sequenceDiagram-PO4LG4MO-BJ-I6WOh.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DLk1jBzf.js → sizeCapture-INFHLROL-Cs5QbLOP.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-C_wutSE7.js → stateDiagram-v2-GCMORJYK-Bw--rdvq.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-hqo1UlEi.js → swimlanes-2SLR337P-B1O9IEz5.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6wGgYKP.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-C_DJldPg.js → timeline-definition-EJHVYXUP-CTZ7DXaw.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-CsidVXMA.js → usecaseDiagram-POWQR4AR-DjwgNG0H.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-Bs-IIqn_.js → vennDiagram-UO4OBE2U-DrdJG55h.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-CXEUyxaj.js → wardleyDiagram-VNRHLVJA-HVq776nz.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B9qvmm4P.js → xychartDiagram-PMCCYNJV-DPf9VEua.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/connector-proxy.js +205 -0
- package/dist-server/container-mcp.js +150 -96
- package/dist-server/drivers/agents-proxy.js +81 -81
- package/dist-server/index.js +7054 -6099
- package/dist-server/openmausbot.js +940 -727
- package/dist-server/pair-cli.js +940 -727
- package/dist-server/server/attachment-mimes.js +58 -0
- package/dist-server/server/attachments.js +28 -67
- package/dist-server/server/avatar-image.js +1 -0
- package/dist-server/server/avatar-subscription.js +74 -0
- package/dist-server/server/cli.js +5 -4
- package/dist-server/server/colima-docker.js +136 -0
- package/dist-server/server/composio.js +1051 -0
- package/dist-server/server/connector-proxy.js +249 -0
- package/dist-server/server/container-computer.js +31 -63
- package/dist-server/server/container-mcp.js +5 -0
- package/dist-server/server/drivers/claude-message.js +12 -0
- package/dist-server/server/drivers/claude.js +9 -12
- package/dist-server/server/drivers/codex-image.js +141 -0
- package/dist-server/server/drivers/codex.js +6 -9
- package/dist-server/server/drivers/openai-chat.js +13 -2
- package/dist-server/server/engine-failover.js +62 -12
- package/dist-server/server/engine-failure.js +2 -2
- package/dist-server/server/harness-import/history.js +5 -5
- package/dist-server/server/harness-import/test-fixtures.js +1 -1
- package/dist-server/server/index.js +75 -36
- package/dist-server/server/mcp-public-registry.js +325 -0
- package/dist-server/server/message-reactions.js +11 -0
- package/dist-server/server/model-evidence-context.js +18 -0
- package/dist-server/server/model-evidence.js +145 -0
- package/dist-server/server/port-in-use.js +21 -0
- package/dist-server/server/session-fallback.js +76 -0
- package/dist-server/server/vault/routes.js +43 -0
- package/dist-server/server/vault/runtime.js +48 -0
- package/dist-server/server/vault/store.js +177 -0
- package/dist-server/shared/connector-availability.js +12 -0
- package/dist-server/shared/desktop-private-port.js +1 -0
- package/dist-server/shared/emoji-catalog.json +1 -0
- package/dist-server/shared/emoji.js +27 -0
- package/dist-server/shared/mascot-bodies.js +9 -1
- package/dist-server/shared/model-evidence.js +25 -0
- package/dist-server/shared/subscription-bots.js +3 -0
- package/dist-server/shared/vault.js +59 -0
- package/dist-server/vps-container-mcp.js +86 -84
- package/package.json +1 -1
- package/dist/assets/channel-D2y0i2ps.js +0 -1
- package/dist/assets/index-DGWeJcck.css +0 -1
- 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
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
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.
|
|
289
|
-
|
|
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
|
|
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
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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: {
|