@ai-matrx/coding-sessions 0.1.83 → 0.1.85
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/CHANGELOG.md +12 -0
- package/dist/engine/index.cjs +521 -112
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -1
- package/dist/engine/index.d.ts +4 -1
- package/dist/engine/index.js +521 -112
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +2 -2
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-DQ0teQvw.d.cts → index-DEPwyr2j.d.cts} +130 -0
- package/dist/{index-DQ0teQvw.d.ts → index-DEPwyr2j.d.ts} +130 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +666 -408
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +87 -7
- package/dist/react/index.d.ts +87 -7
- package/dist/react/index.js +674 -403
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +588 -127
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +4 -1
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.js +587 -126
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/index-DSWoryQZ.js +72 -0
- package/dist/ui/assets/index-Gp2bMDtx.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +4 -2
- package/dist/ui/assets/index-C-_1qjy9.css +0 -2
- package/dist/ui/assets/index-CmFgFvLa.js +0 -69
package/dist/server/index.cjs
CHANGED
|
@@ -43,17 +43,17 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
43
43
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
44
44
|
|
|
45
45
|
// src/server/index.ts
|
|
46
|
-
var
|
|
46
|
+
var import_node_crypto6 = require("crypto");
|
|
47
47
|
var import_node_fs5 = require("fs");
|
|
48
|
-
var
|
|
48
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
49
49
|
var import_node_http = __toESM(require("http"), 1);
|
|
50
|
-
var
|
|
50
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
51
51
|
var import_node_url2 = require("url");
|
|
52
52
|
var import_ws = require("ws");
|
|
53
53
|
|
|
54
54
|
// src/engine/index.ts
|
|
55
55
|
var import_node_child_process3 = require("child_process");
|
|
56
|
-
var
|
|
56
|
+
var import_node_crypto5 = require("crypto");
|
|
57
57
|
var import_node_fs4 = require("fs");
|
|
58
58
|
var import_format = require("@ai-matrx/kit/format");
|
|
59
59
|
|
|
@@ -114,10 +114,379 @@ function createRawBackupScheduler(options) {
|
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// src/engine/agent-attach.ts
|
|
118
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
119
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
120
|
+
var AGENT_MCP_SERVER_NAME = "matrx-agent";
|
|
121
|
+
var AGENT_PLUGIN_NAME = "matrx-agent";
|
|
122
|
+
function escapeAttr(value) {
|
|
123
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
124
|
+
}
|
|
125
|
+
function versionWords(bundle) {
|
|
126
|
+
return bundle.version_number !== null ? String(bundle.version_number) : "latest";
|
|
127
|
+
}
|
|
128
|
+
function renderAgentInstructions(bundle, options = {}) {
|
|
129
|
+
const included = bundle.skills.filter((skill) => skill.tier === "included");
|
|
130
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
131
|
+
const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
|
|
132
|
+
if (included.length > 0) {
|
|
133
|
+
parts.push(
|
|
134
|
+
"## Skills (always in effect)",
|
|
135
|
+
...included.map((skill) => `### ${skill.label}
|
|
136
|
+
|
|
137
|
+
${skill.body.trim()}`)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (options.claudeSkills && listed2.length > 0) {
|
|
141
|
+
parts.push(
|
|
142
|
+
[
|
|
143
|
+
"## Your skills",
|
|
144
|
+
"When a request matches one of these, load it with your Skill tool and follow it before answering:",
|
|
145
|
+
...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
|
|
146
|
+
].join("\n")
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
|
|
150
|
+
parts.push(bundle.instructions.trim());
|
|
151
|
+
parts.push("</ai_matrx_agent>");
|
|
152
|
+
return `${parts.filter(Boolean).join("\n\n")}
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
function renderCodexSkillsList(bundle, skillsRoot) {
|
|
156
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
157
|
+
if (listed2.length === 0) return "";
|
|
158
|
+
const lines = [
|
|
159
|
+
"<skills_instructions>",
|
|
160
|
+
"## AI Matrx agent skills",
|
|
161
|
+
"A skill is a set of instructions stored in a `SKILL.md` file. Each entry has a name, a description and the absolute path of its file. When a task matches a skill's description, read that file and follow it.",
|
|
162
|
+
"### Available skills",
|
|
163
|
+
...listed2.map(
|
|
164
|
+
(skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
165
|
+
),
|
|
166
|
+
"</skills_instructions>"
|
|
167
|
+
];
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
function tomlString(value) {
|
|
171
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
172
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
173
|
+
}
|
|
174
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
175
|
+
const pluginDir = import_node_path.default.join(dir, "plugin");
|
|
176
|
+
const skillsRoot = import_node_path.default.join(dir, "skills");
|
|
177
|
+
await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
178
|
+
await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
179
|
+
const instructions = renderAgentInstructions(bundle);
|
|
180
|
+
const instructionsFile = import_node_path.default.join(dir, "instructions.md");
|
|
181
|
+
await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
182
|
+
await import_promises2.default.writeFile(
|
|
183
|
+
import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
184
|
+
`${JSON.stringify(
|
|
185
|
+
{
|
|
186
|
+
name: AGENT_PLUGIN_NAME,
|
|
187
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
188
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
189
|
+
},
|
|
190
|
+
null,
|
|
191
|
+
2
|
|
192
|
+
)}
|
|
193
|
+
`,
|
|
194
|
+
{ mode: 384 }
|
|
195
|
+
);
|
|
196
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
197
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
198
|
+
for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
|
|
199
|
+
await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
|
|
200
|
+
for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
|
|
201
|
+
if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
for (const skill of listed2) {
|
|
204
|
+
const skillDir = import_node_path.default.join(root, skill.slug);
|
|
205
|
+
await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
206
|
+
await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
let mcpConfigFile = null;
|
|
210
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
211
|
+
const mcpPath = import_node_path.default.join(dir, "mcp.json");
|
|
212
|
+
if (mcpUrl) {
|
|
213
|
+
mcpConfigFile = mcpPath;
|
|
214
|
+
await import_promises2.default.writeFile(
|
|
215
|
+
mcpPath,
|
|
216
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
217
|
+
`,
|
|
218
|
+
{ mode: 384 }
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
await import_promises2.default.rm(mcpPath, { force: true });
|
|
222
|
+
}
|
|
223
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
224
|
+
return {
|
|
225
|
+
dir,
|
|
226
|
+
instructionsFile,
|
|
227
|
+
pluginDir,
|
|
228
|
+
mcpConfigFile,
|
|
229
|
+
mcpUrl,
|
|
230
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
231
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
232
|
+
|
|
233
|
+
${instructions}` : instructions
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function agentArgsFor(provider, attachment, options) {
|
|
237
|
+
if (provider === "claude") {
|
|
238
|
+
const args2 = [
|
|
239
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
240
|
+
"--append-system-prompt-file",
|
|
241
|
+
attachment.instructionsFile,
|
|
242
|
+
"--system-prompt-snapshot",
|
|
243
|
+
"off",
|
|
244
|
+
"--plugin-dir",
|
|
245
|
+
attachment.pluginDir
|
|
246
|
+
];
|
|
247
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
248
|
+
return args2;
|
|
249
|
+
}
|
|
250
|
+
const args = [];
|
|
251
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
252
|
+
if (attachment.mcpUrl) {
|
|
253
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
254
|
+
args.push(
|
|
255
|
+
"-c",
|
|
256
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
257
|
+
"-c",
|
|
258
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return args;
|
|
262
|
+
}
|
|
263
|
+
function sessionAgentRecord(bundle, options) {
|
|
264
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
265
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
266
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
267
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
268
|
+
for (const tool of bundle.tools) {
|
|
269
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
273
|
+
const loaded = [
|
|
274
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
275
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
276
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
277
|
+
];
|
|
278
|
+
return {
|
|
279
|
+
agentId: bundle.agent_id,
|
|
280
|
+
versionId: bundle.version_id,
|
|
281
|
+
versionNumber: bundle.version_number,
|
|
282
|
+
name: bundle.name,
|
|
283
|
+
definitionHash: bundle.definition_hash,
|
|
284
|
+
loaded,
|
|
285
|
+
unavailable
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/engine/agents.ts
|
|
290
|
+
var import_node_crypto2 = require("crypto");
|
|
291
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
292
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
293
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
294
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
295
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
296
|
+
"agx_get_list_full",
|
|
297
|
+
"agx_search",
|
|
298
|
+
"agx_resolve_agent_address",
|
|
299
|
+
"ues_get_bulk",
|
|
300
|
+
"ues_list",
|
|
301
|
+
"ues_set",
|
|
302
|
+
"ues_touch"
|
|
303
|
+
]);
|
|
304
|
+
var agentsClient = async () => {
|
|
305
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
306
|
+
import("@ai-matrx/agents/matrx"),
|
|
307
|
+
import("@ai-matrx/agents/portable"),
|
|
308
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
309
|
+
]);
|
|
310
|
+
return { ...matrx, ...portable, ...mcp };
|
|
311
|
+
};
|
|
312
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
313
|
+
function userIdOf(credential) {
|
|
314
|
+
if (!credential || credential.kind !== "user") return null;
|
|
315
|
+
const payload = credential.accessToken.split(".")[1];
|
|
316
|
+
if (!payload) return null;
|
|
317
|
+
try {
|
|
318
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
319
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function createAgentConnection(options, dataDir) {
|
|
325
|
+
const credentials = options?.credentials ?? null;
|
|
326
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
327
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
328
|
+
const supabaseKey = options?.supabaseKey ?? null;
|
|
329
|
+
const root = import_node_path2.default.join(dataDir, "agents");
|
|
330
|
+
let mcpBaseUrl = null;
|
|
331
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
332
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
333
|
+
const organizationId = () => {
|
|
334
|
+
const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
|
|
335
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
336
|
+
};
|
|
337
|
+
const transport = async () => (await agentsClient()).createMatrxTransport({
|
|
338
|
+
baseUrl: serverUrl,
|
|
339
|
+
...credentials ? { credentials } : {},
|
|
340
|
+
...organizationId() ? { organizationId: () => organizationId() } : {},
|
|
341
|
+
source: "codingSessionsAgents"
|
|
342
|
+
});
|
|
343
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
344
|
+
const requireSignedIn = async () => {
|
|
345
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
346
|
+
};
|
|
347
|
+
const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
348
|
+
async function meta(sessionId) {
|
|
349
|
+
const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
|
|
350
|
+
const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
351
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
352
|
+
const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
|
|
353
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
354
|
+
await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
355
|
+
return created;
|
|
356
|
+
}
|
|
357
|
+
async function findSessionBySecret(secret) {
|
|
358
|
+
for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
|
|
359
|
+
const found = await import_promises3.default.readFile(import_node_path2.default.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
|
|
360
|
+
if (found) {
|
|
361
|
+
secretToSession.set(secret, name);
|
|
362
|
+
return name;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
async function readSessionBundle(sessionId) {
|
|
368
|
+
return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
async access() {
|
|
372
|
+
const current = await credential().catch(() => null);
|
|
373
|
+
const userId = userIdOf(current);
|
|
374
|
+
return {
|
|
375
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
376
|
+
signedIn: userId !== null,
|
|
377
|
+
userId,
|
|
378
|
+
organizationId: organizationId()
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
async catalogRpc(request) {
|
|
382
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
383
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
384
|
+
}
|
|
385
|
+
const current = await credential();
|
|
386
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
387
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
388
|
+
const query = new URLSearchParams();
|
|
389
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
390
|
+
if (request.range) {
|
|
391
|
+
query.set("offset", String(request.range[0]));
|
|
392
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
393
|
+
}
|
|
394
|
+
const headers = {
|
|
395
|
+
apikey: supabaseKey,
|
|
396
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
397
|
+
"Content-Type": "application/json",
|
|
398
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
399
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
400
|
+
"Content-Profile": "public",
|
|
401
|
+
"Accept-Profile": "public"
|
|
402
|
+
};
|
|
403
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
404
|
+
const qs = query.toString();
|
|
405
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
406
|
+
method: "POST",
|
|
407
|
+
headers,
|
|
408
|
+
body: JSON.stringify(request.args ?? {})
|
|
409
|
+
});
|
|
410
|
+
const text = await response.text();
|
|
411
|
+
const body = text ? JSON.parse(text) : null;
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
const err = body ?? {};
|
|
414
|
+
return {
|
|
415
|
+
data: null,
|
|
416
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const range = response.headers.get("content-range");
|
|
420
|
+
const total = range?.split("/")[1];
|
|
421
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
422
|
+
},
|
|
423
|
+
async details(agentId) {
|
|
424
|
+
await requireSignedIn();
|
|
425
|
+
const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
|
|
426
|
+
const [bundle, versions] = await Promise.all([
|
|
427
|
+
fetchPortableAgent(await transport(), { agentId }),
|
|
428
|
+
fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
|
|
429
|
+
]);
|
|
430
|
+
return {
|
|
431
|
+
agentId: bundle.agent_id,
|
|
432
|
+
name: bundle.name,
|
|
433
|
+
variables: bundle.variables,
|
|
434
|
+
versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
|
|
435
|
+
latestVersionNumber: bundle.version_number
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
async loadBundle(selection) {
|
|
439
|
+
await requireSignedIn();
|
|
440
|
+
const { fetchPortableAgent } = await agentsClient();
|
|
441
|
+
return fetchPortableAgent(
|
|
442
|
+
await transport(),
|
|
443
|
+
selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
|
|
444
|
+
{ variables: selection.variables ?? {} }
|
|
445
|
+
);
|
|
446
|
+
},
|
|
447
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
448
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
449
|
+
await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
450
|
+
await meta(sessionId);
|
|
451
|
+
},
|
|
452
|
+
readSessionBundle,
|
|
453
|
+
sessionDir,
|
|
454
|
+
async mcpUrlFor(sessionId) {
|
|
455
|
+
if (!mcpBaseUrl) return null;
|
|
456
|
+
const { mcpSecret } = await meta(sessionId);
|
|
457
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
458
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
459
|
+
},
|
|
460
|
+
setMcpBaseUrl(baseUrl) {
|
|
461
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
462
|
+
},
|
|
463
|
+
async handleMcp(secret, body) {
|
|
464
|
+
let handler = handlers.get(secret);
|
|
465
|
+
if (!handler) {
|
|
466
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
467
|
+
if (!sessionId) return null;
|
|
468
|
+
const bundle = await readSessionBundle(sessionId);
|
|
469
|
+
if (!bundle) return null;
|
|
470
|
+
const { conversationId } = await meta(sessionId);
|
|
471
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
472
|
+
handler = createAgentToolsMcpHandler({
|
|
473
|
+
bundle,
|
|
474
|
+
credentials,
|
|
475
|
+
organizationId: () => organizationId(),
|
|
476
|
+
baseUrl: serverUrl,
|
|
477
|
+
conversationId
|
|
478
|
+
});
|
|
479
|
+
handlers.set(secret, handler);
|
|
480
|
+
}
|
|
481
|
+
return handler.handle(body);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
117
486
|
// src/engine/index.ts
|
|
118
|
-
var
|
|
487
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
119
488
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
120
|
-
var
|
|
489
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
121
490
|
var import_node_url = require("url");
|
|
122
491
|
|
|
123
492
|
// src/normalize/claude.ts
|
|
@@ -787,7 +1156,7 @@ function describeProblem(provider, problem) {
|
|
|
787
1156
|
var import_node_child_process = require("child_process");
|
|
788
1157
|
var import_node_fs = require("fs");
|
|
789
1158
|
var import_node_os = __toESM(require("os"), 1);
|
|
790
|
-
var
|
|
1159
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
791
1160
|
var SOURCE_LABEL = {
|
|
792
1161
|
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
793
1162
|
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
@@ -836,10 +1205,10 @@ function exeName(name, platform) {
|
|
|
836
1205
|
}
|
|
837
1206
|
function allOnPath(name, pathValue, platform = process.platform) {
|
|
838
1207
|
const found = [];
|
|
839
|
-
for (const dir of pathValue.split(
|
|
1208
|
+
for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
|
|
840
1209
|
if (!dir) continue;
|
|
841
1210
|
for (const file of exeName(name, platform)) {
|
|
842
|
-
const candidate =
|
|
1211
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
843
1212
|
if (executable(candidate)) {
|
|
844
1213
|
found.push(candidate);
|
|
845
1214
|
break;
|
|
@@ -852,17 +1221,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
|
|
|
852
1221
|
if (platform !== "darwin") return [];
|
|
853
1222
|
if (provider === "codex") {
|
|
854
1223
|
return applications.map(
|
|
855
|
-
(apps) =>
|
|
1224
|
+
(apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
856
1225
|
);
|
|
857
1226
|
}
|
|
858
|
-
const root =
|
|
1227
|
+
const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
859
1228
|
let versions = [];
|
|
860
1229
|
try {
|
|
861
1230
|
versions = (0, import_node_fs.readdirSync)(root);
|
|
862
1231
|
} catch {
|
|
863
1232
|
return [];
|
|
864
1233
|
}
|
|
865
|
-
return versions.map((v) =>
|
|
1234
|
+
return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
866
1235
|
}
|
|
867
1236
|
function npmGlobalPrefix(env) {
|
|
868
1237
|
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
@@ -880,13 +1249,13 @@ function declaredVersion(provider, source, file) {
|
|
|
880
1249
|
if (source !== "desktop-app") return null;
|
|
881
1250
|
if (provider === "codex") {
|
|
882
1251
|
try {
|
|
883
|
-
const pkg = JSON.parse((0, import_node_fs.readFileSync)(
|
|
1252
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.dirname(file)), "codex-package.json"), "utf8"));
|
|
884
1253
|
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
885
1254
|
} catch {
|
|
886
1255
|
return null;
|
|
887
1256
|
}
|
|
888
1257
|
}
|
|
889
|
-
return parseVersion(
|
|
1258
|
+
return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
|
|
890
1259
|
}
|
|
891
1260
|
function runVersion(file, env) {
|
|
892
1261
|
return new Promise((resolve) => {
|
|
@@ -900,14 +1269,14 @@ async function discoverBinaries(provider, options) {
|
|
|
900
1269
|
const platform = options.platform ?? process.platform;
|
|
901
1270
|
const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
|
|
902
1271
|
const listed2 = [
|
|
903
|
-
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications",
|
|
1272
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
904
1273
|
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
905
1274
|
];
|
|
906
1275
|
const prefix = await npmGlobalPrefix(options.env);
|
|
907
1276
|
if (prefix) {
|
|
908
|
-
const dir = platform === "win32" ? prefix :
|
|
1277
|
+
const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
|
|
909
1278
|
for (const file of exeName(provider, platform)) {
|
|
910
|
-
const candidate =
|
|
1279
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
911
1280
|
if (executable(candidate)) {
|
|
912
1281
|
listed2.push({ path: candidate, source: "npm-global" });
|
|
913
1282
|
break;
|
|
@@ -953,7 +1322,7 @@ async function resolveBinary(provider, options) {
|
|
|
953
1322
|
|
|
954
1323
|
// src/engine/clean-env.ts
|
|
955
1324
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
956
|
-
var
|
|
1325
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
957
1326
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
958
1327
|
"HOME",
|
|
959
1328
|
"USER",
|
|
@@ -975,11 +1344,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
975
1344
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
976
1345
|
function wellKnownBinDirs(home) {
|
|
977
1346
|
return [
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1347
|
+
import_node_path4.default.join(home, ".local", "bin"),
|
|
1348
|
+
import_node_path4.default.join(home, ".claude", "local"),
|
|
1349
|
+
import_node_path4.default.join(home, ".npm-global", "bin"),
|
|
1350
|
+
import_node_path4.default.join(home, ".bun", "bin"),
|
|
1351
|
+
import_node_path4.default.join(home, ".volta", "bin"),
|
|
983
1352
|
"/opt/homebrew/bin",
|
|
984
1353
|
"/usr/local/bin",
|
|
985
1354
|
"/usr/bin",
|
|
@@ -999,21 +1368,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
999
1368
|
}
|
|
1000
1369
|
const home = out.HOME ?? import_node_os2.default.homedir();
|
|
1001
1370
|
out.HOME = home;
|
|
1002
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1371
|
+
const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
|
|
1003
1372
|
const merged = [];
|
|
1004
1373
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
1005
1374
|
if (!merged.includes(dir)) merged.push(dir);
|
|
1006
1375
|
}
|
|
1007
|
-
out.PATH = merged.join(
|
|
1376
|
+
out.PATH = merged.join(import_node_path4.default.delimiter);
|
|
1008
1377
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
1009
1378
|
return out;
|
|
1010
1379
|
}
|
|
1011
1380
|
|
|
1012
1381
|
// src/engine/files.ts
|
|
1013
1382
|
var import_node_fs2 = require("fs");
|
|
1014
|
-
var
|
|
1383
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1015
1384
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
1016
|
-
var
|
|
1385
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1017
1386
|
|
|
1018
1387
|
// src/engine/activity.ts
|
|
1019
1388
|
var import_node_child_process2 = require("child_process");
|
|
@@ -1082,15 +1451,15 @@ function describeActivity(input) {
|
|
|
1082
1451
|
// src/engine/files.ts
|
|
1083
1452
|
function defaultProviderHomes(home = import_node_os3.default.homedir()) {
|
|
1084
1453
|
return {
|
|
1085
|
-
claudeProjects:
|
|
1086
|
-
codexSessions:
|
|
1087
|
-
codexIndex:
|
|
1454
|
+
claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
|
|
1455
|
+
codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
|
|
1456
|
+
codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
|
|
1088
1457
|
};
|
|
1089
1458
|
}
|
|
1090
1459
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
1091
1460
|
async function safeReaddir(dir) {
|
|
1092
1461
|
try {
|
|
1093
|
-
return await
|
|
1462
|
+
return await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
1094
1463
|
} catch {
|
|
1095
1464
|
return [];
|
|
1096
1465
|
}
|
|
@@ -1099,7 +1468,7 @@ async function statCandidate(provider, file) {
|
|
|
1099
1468
|
const match = UUIDISH.exec(file);
|
|
1100
1469
|
if (!match?.[1]) return null;
|
|
1101
1470
|
try {
|
|
1102
|
-
const stat = await
|
|
1471
|
+
const stat = await import_promises4.default.stat(file);
|
|
1103
1472
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1104
1473
|
} catch {
|
|
1105
1474
|
return null;
|
|
@@ -1109,10 +1478,10 @@ async function claudeCandidates(root) {
|
|
|
1109
1478
|
const out = [];
|
|
1110
1479
|
for (const project of await safeReaddir(root)) {
|
|
1111
1480
|
if (!project.isDirectory()) continue;
|
|
1112
|
-
const dir =
|
|
1481
|
+
const dir = import_node_path5.default.join(root, project.name);
|
|
1113
1482
|
const entries = await safeReaddir(dir);
|
|
1114
1483
|
const stats = await Promise.all(
|
|
1115
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1484
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
|
|
1116
1485
|
);
|
|
1117
1486
|
for (const s of stats) if (s) out.push(s);
|
|
1118
1487
|
}
|
|
@@ -1122,14 +1491,14 @@ async function codexCandidates(root) {
|
|
|
1122
1491
|
const out = [];
|
|
1123
1492
|
for (const year of await safeReaddir(root)) {
|
|
1124
1493
|
if (!year.isDirectory()) continue;
|
|
1125
|
-
for (const month of await safeReaddir(
|
|
1494
|
+
for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
|
|
1126
1495
|
if (!month.isDirectory()) continue;
|
|
1127
|
-
for (const day of await safeReaddir(
|
|
1496
|
+
for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
|
|
1128
1497
|
if (!day.isDirectory()) continue;
|
|
1129
|
-
const dir =
|
|
1498
|
+
const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
|
|
1130
1499
|
const entries = await safeReaddir(dir);
|
|
1131
1500
|
const stats = await Promise.all(
|
|
1132
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1501
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
|
|
1133
1502
|
);
|
|
1134
1503
|
for (const s of stats) if (s) out.push(s);
|
|
1135
1504
|
}
|
|
@@ -1138,7 +1507,7 @@ async function codexCandidates(root) {
|
|
|
1138
1507
|
return out;
|
|
1139
1508
|
}
|
|
1140
1509
|
async function readSlice(file, start, length) {
|
|
1141
|
-
const handle = await
|
|
1510
|
+
const handle = await import_promises4.default.open(file, "r");
|
|
1142
1511
|
try {
|
|
1143
1512
|
const buffer = Buffer.alloc(length);
|
|
1144
1513
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1211,9 +1580,9 @@ async function readCodexIndex(file) {
|
|
|
1211
1580
|
const names = /* @__PURE__ */ new Map();
|
|
1212
1581
|
let text;
|
|
1213
1582
|
try {
|
|
1214
|
-
const stat = await
|
|
1583
|
+
const stat = await import_promises4.default.stat(file);
|
|
1215
1584
|
const budget = 4 * 1024 * 1024;
|
|
1216
|
-
text = stat.size <= budget ? await
|
|
1585
|
+
text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1217
1586
|
} catch {
|
|
1218
1587
|
return names;
|
|
1219
1588
|
}
|
|
@@ -1334,7 +1703,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1334
1703
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1335
1704
|
let size;
|
|
1336
1705
|
try {
|
|
1337
|
-
size = (await
|
|
1706
|
+
size = (await import_promises4.default.stat(file)).size;
|
|
1338
1707
|
} catch {
|
|
1339
1708
|
return {
|
|
1340
1709
|
items: [],
|
|
@@ -1388,9 +1757,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1388
1757
|
}
|
|
1389
1758
|
|
|
1390
1759
|
// src/engine/preferences.ts
|
|
1391
|
-
var
|
|
1392
|
-
var
|
|
1393
|
-
var
|
|
1760
|
+
var import_node_crypto3 = require("crypto");
|
|
1761
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
1762
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1394
1763
|
var PreferenceStore = class {
|
|
1395
1764
|
constructor(dir) {
|
|
1396
1765
|
this.dir = dir;
|
|
@@ -1399,11 +1768,11 @@ var PreferenceStore = class {
|
|
|
1399
1768
|
data = {};
|
|
1400
1769
|
writing = Promise.resolve();
|
|
1401
1770
|
get file() {
|
|
1402
|
-
return
|
|
1771
|
+
return import_node_path6.default.join(this.dir, "preferences.json");
|
|
1403
1772
|
}
|
|
1404
1773
|
async load() {
|
|
1405
1774
|
try {
|
|
1406
|
-
const parsed = JSON.parse(await
|
|
1775
|
+
const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
|
|
1407
1776
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1408
1777
|
} catch {
|
|
1409
1778
|
this.data = {};
|
|
@@ -1441,10 +1810,10 @@ var PreferenceStore = class {
|
|
|
1441
1810
|
persist() {
|
|
1442
1811
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1443
1812
|
this.writing = this.writing.then(async () => {
|
|
1444
|
-
await
|
|
1445
|
-
const tmp = `${this.file}.${(0,
|
|
1446
|
-
await
|
|
1447
|
-
await
|
|
1813
|
+
await import_promises5.default.mkdir(this.dir, { recursive: true });
|
|
1814
|
+
const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
|
|
1815
|
+
await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
1816
|
+
await import_promises5.default.rename(tmp, this.file);
|
|
1448
1817
|
}).catch(() => {
|
|
1449
1818
|
});
|
|
1450
1819
|
}
|
|
@@ -1601,9 +1970,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1601
1970
|
}
|
|
1602
1971
|
|
|
1603
1972
|
// src/engine/store.ts
|
|
1604
|
-
var
|
|
1605
|
-
var
|
|
1606
|
-
var
|
|
1973
|
+
var import_node_crypto4 = require("crypto");
|
|
1974
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
1975
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1607
1976
|
var MAX_RUNS = 2e3;
|
|
1608
1977
|
var RunIndex = class {
|
|
1609
1978
|
constructor(dir) {
|
|
@@ -1613,12 +1982,12 @@ var RunIndex = class {
|
|
|
1613
1982
|
data = { version: 1, sessions: [], runs: [] };
|
|
1614
1983
|
writing = Promise.resolve();
|
|
1615
1984
|
get file() {
|
|
1616
|
-
return
|
|
1985
|
+
return import_node_path7.default.join(this.dir, "index.json");
|
|
1617
1986
|
}
|
|
1618
1987
|
async load() {
|
|
1619
|
-
await
|
|
1988
|
+
await import_promises6.default.mkdir(this.dir, { recursive: true });
|
|
1620
1989
|
try {
|
|
1621
|
-
const parsed = JSON.parse(await
|
|
1990
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
|
|
1622
1991
|
this.data = {
|
|
1623
1992
|
version: 1,
|
|
1624
1993
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1660,9 +2029,9 @@ var RunIndex = class {
|
|
|
1660
2029
|
persist() {
|
|
1661
2030
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1662
2031
|
this.writing = this.writing.then(async () => {
|
|
1663
|
-
const tmp = `${this.file}.${(0,
|
|
1664
|
-
await
|
|
1665
|
-
await
|
|
2032
|
+
const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
2033
|
+
await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
2034
|
+
await import_promises6.default.rename(tmp, this.file);
|
|
1666
2035
|
}).catch(() => {
|
|
1667
2036
|
});
|
|
1668
2037
|
}
|
|
@@ -1672,27 +2041,27 @@ var RunIndex = class {
|
|
|
1672
2041
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1673
2042
|
* driver's scratch state is reused rather than growing per run. */
|
|
1674
2043
|
async identity() {
|
|
1675
|
-
const file =
|
|
2044
|
+
const file = import_node_path7.default.join(this.dir, "identity.json");
|
|
1676
2045
|
try {
|
|
1677
|
-
const parsed = JSON.parse(await
|
|
2046
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
|
|
1678
2047
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1679
2048
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1680
2049
|
}
|
|
1681
2050
|
} catch {
|
|
1682
2051
|
}
|
|
1683
|
-
const identity = { companyId: (0,
|
|
1684
|
-
await
|
|
2052
|
+
const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
|
|
2053
|
+
await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1685
2054
|
return identity;
|
|
1686
2055
|
}
|
|
1687
2056
|
};
|
|
1688
2057
|
|
|
1689
2058
|
// src/engine/user-defaults.ts
|
|
1690
|
-
var
|
|
2059
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
1691
2060
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
1692
|
-
var
|
|
2061
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1693
2062
|
async function readJson(file) {
|
|
1694
2063
|
try {
|
|
1695
|
-
const parsed = JSON.parse(await
|
|
2064
|
+
const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
|
|
1696
2065
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1697
2066
|
} catch {
|
|
1698
2067
|
return null;
|
|
@@ -1703,8 +2072,8 @@ function str3(value) {
|
|
|
1703
2072
|
}
|
|
1704
2073
|
async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
1705
2074
|
const files = [
|
|
1706
|
-
...cwd ? [
|
|
1707
|
-
|
|
2075
|
+
...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
|
|
2076
|
+
import_node_path8.default.join(home, ".claude", "settings.json")
|
|
1708
2077
|
];
|
|
1709
2078
|
let model = null;
|
|
1710
2079
|
let effort = null;
|
|
@@ -1720,7 +2089,7 @@ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
|
1720
2089
|
async function codexDefaults(home = import_node_os4.default.homedir()) {
|
|
1721
2090
|
let text;
|
|
1722
2091
|
try {
|
|
1723
|
-
text = await
|
|
2092
|
+
text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
|
|
1724
2093
|
} catch {
|
|
1725
2094
|
return { model: null, effort: null };
|
|
1726
2095
|
}
|
|
@@ -1744,31 +2113,31 @@ var EFFORT_LEVELS = {
|
|
|
1744
2113
|
// src/engine/index.ts
|
|
1745
2114
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1746
2115
|
function moduleDir() {
|
|
1747
|
-
return
|
|
2116
|
+
return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1748
2117
|
}
|
|
1749
2118
|
function resolveWorkerPath() {
|
|
1750
2119
|
const dir = moduleDir();
|
|
1751
2120
|
for (const candidate of [
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2121
|
+
import_node_path9.default.join(dir, "worker.js"),
|
|
2122
|
+
import_node_path9.default.join(dir, "worker.ts"),
|
|
2123
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.js"),
|
|
2124
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.ts")
|
|
1756
2125
|
]) {
|
|
1757
2126
|
if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
|
|
1758
2127
|
}
|
|
1759
2128
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1760
2129
|
}
|
|
1761
2130
|
async function installShim(dataDir) {
|
|
1762
|
-
const source =
|
|
2131
|
+
const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
1763
2132
|
if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
1764
|
-
const body = await
|
|
1765
|
-
const target =
|
|
1766
|
-
const current = await
|
|
2133
|
+
const body = await import_promises8.default.readFile(source, "utf8");
|
|
2134
|
+
const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
|
|
2135
|
+
const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
|
|
1767
2136
|
if (current !== body) {
|
|
1768
|
-
await
|
|
1769
|
-
await
|
|
2137
|
+
await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
|
|
2138
|
+
await import_promises8.default.writeFile(target, body, { mode: 493 });
|
|
1770
2139
|
}
|
|
1771
|
-
await
|
|
2140
|
+
await import_promises8.default.chmod(target, 493);
|
|
1772
2141
|
return target;
|
|
1773
2142
|
}
|
|
1774
2143
|
function nowIso() {
|
|
@@ -1868,29 +2237,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1868
2237
|
};
|
|
1869
2238
|
}
|
|
1870
2239
|
async function createCodingSessionsEngine(options = {}) {
|
|
1871
|
-
const dataDir = options.dataDir ??
|
|
1872
|
-
const stateDir =
|
|
2240
|
+
const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
|
|
2241
|
+
const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
|
|
1873
2242
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1874
2243
|
const index = new RunIndex(dataDir);
|
|
1875
2244
|
await index.load();
|
|
1876
2245
|
const preferences = new PreferenceStore(dataDir);
|
|
1877
2246
|
await preferences.load();
|
|
1878
|
-
await
|
|
2247
|
+
await import_promises8.default.mkdir(stateDir, { recursive: true });
|
|
1879
2248
|
const identity = await index.identity();
|
|
2249
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1880
2250
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1881
2251
|
sink: options.rawBackup,
|
|
1882
2252
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1883
2253
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1884
2254
|
}) : null;
|
|
1885
2255
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1886
|
-
const healthFile =
|
|
2256
|
+
const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
|
|
1887
2257
|
let health = {};
|
|
1888
2258
|
try {
|
|
1889
|
-
health = JSON.parse(await
|
|
2259
|
+
health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
|
|
1890
2260
|
} catch {
|
|
1891
2261
|
health = {};
|
|
1892
2262
|
}
|
|
1893
|
-
const saveHealth = () =>
|
|
2263
|
+
const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1894
2264
|
function noteRunOutcome(provider, status, texts) {
|
|
1895
2265
|
if (status === "completed") {
|
|
1896
2266
|
if (health[provider]) {
|
|
@@ -2007,7 +2377,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2007
2377
|
async function startRun(session, prompt) {
|
|
2008
2378
|
const provider = session.provider;
|
|
2009
2379
|
const realCommand = await commandFor(provider);
|
|
2010
|
-
const runId = (0,
|
|
2380
|
+
const runId = (0, import_node_crypto5.randomUUID)();
|
|
2011
2381
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
2012
2382
|
const run = {
|
|
2013
2383
|
runId,
|
|
@@ -2045,7 +2415,26 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2045
2415
|
let fileOffset = null;
|
|
2046
2416
|
if (session.providerSessionId) {
|
|
2047
2417
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
2048
|
-
if (file) fileOffset = (await
|
|
2418
|
+
if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
|
|
2419
|
+
}
|
|
2420
|
+
let agentArgs = [];
|
|
2421
|
+
if (session.agent) {
|
|
2422
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2423
|
+
if (!bundle) {
|
|
2424
|
+
const failed = saveRun({
|
|
2425
|
+
...run,
|
|
2426
|
+
status: "failed",
|
|
2427
|
+
endedAt: nowIso(),
|
|
2428
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2429
|
+
});
|
|
2430
|
+
const current = index.session(session.id);
|
|
2431
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2432
|
+
return failed;
|
|
2433
|
+
}
|
|
2434
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2435
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2436
|
+
});
|
|
2437
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
2049
2438
|
}
|
|
2050
2439
|
const child = forkWorker();
|
|
2051
2440
|
const liveRun = {
|
|
@@ -2219,7 +2608,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2219
2608
|
resumeSessionId: session.providerSessionId,
|
|
2220
2609
|
shimPath,
|
|
2221
2610
|
realCommand,
|
|
2222
|
-
identity
|
|
2611
|
+
identity,
|
|
2612
|
+
agentArgs
|
|
2223
2613
|
});
|
|
2224
2614
|
return run;
|
|
2225
2615
|
}
|
|
@@ -2238,7 +2628,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2238
2628
|
}, 3e3);
|
|
2239
2629
|
}
|
|
2240
2630
|
async function pruneDriverScratch() {
|
|
2241
|
-
const runsDir =
|
|
2631
|
+
const runsDir = import_node_path9.default.join(
|
|
2242
2632
|
stateDir,
|
|
2243
2633
|
"instances",
|
|
2244
2634
|
"default",
|
|
@@ -2249,9 +2639,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2249
2639
|
"claude-runtime",
|
|
2250
2640
|
"runs"
|
|
2251
2641
|
);
|
|
2252
|
-
const entries = await
|
|
2642
|
+
const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
|
|
2253
2643
|
await Promise.all(
|
|
2254
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2644
|
+
entries.filter((name) => !live.has(name)).map((name) => import_promises8.default.rm(import_node_path9.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
|
|
2255
2645
|
);
|
|
2256
2646
|
}
|
|
2257
2647
|
async function probe() {
|
|
@@ -2292,7 +2682,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2292
2682
|
function newSession(input) {
|
|
2293
2683
|
const at = nowIso();
|
|
2294
2684
|
return {
|
|
2295
|
-
id: (0,
|
|
2685
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
2296
2686
|
provider: input.provider,
|
|
2297
2687
|
providerSessionId: input.providerSessionId,
|
|
2298
2688
|
cwd: input.cwd,
|
|
@@ -2310,17 +2700,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2310
2700
|
function resolveFolder(raw) {
|
|
2311
2701
|
const trimmed = raw.trim();
|
|
2312
2702
|
if (!trimmed) return null;
|
|
2313
|
-
return
|
|
2703
|
+
return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2314
2704
|
}
|
|
2315
2705
|
async function checkFolder(raw) {
|
|
2316
2706
|
const target = resolveFolder(raw);
|
|
2317
2707
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2318
|
-
const stat = await
|
|
2708
|
+
const stat = await import_promises8.default.stat(target).catch(() => null);
|
|
2319
2709
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2320
2710
|
}
|
|
2321
2711
|
async function assertFolder(cwd) {
|
|
2322
|
-
const resolved =
|
|
2323
|
-
const stat = await
|
|
2712
|
+
const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2713
|
+
const stat = await import_promises8.default.stat(resolved).catch(() => null);
|
|
2324
2714
|
if (!stat?.isDirectory()) {
|
|
2325
2715
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2326
2716
|
}
|
|
@@ -2382,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2382
2772
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2383
2773
|
path: folder,
|
|
2384
2774
|
lastUsedAt,
|
|
2385
|
-
exists: (await
|
|
2775
|
+
exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2386
2776
|
}))
|
|
2387
2777
|
);
|
|
2388
2778
|
},
|
|
@@ -2427,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2427
2817
|
async createFolder(raw) {
|
|
2428
2818
|
const target = resolveFolder(raw);
|
|
2429
2819
|
if (!target) throw new Error("Type a folder path first.");
|
|
2430
|
-
await
|
|
2820
|
+
await import_promises8.default.mkdir(target, { recursive: true });
|
|
2431
2821
|
return checkFolder(target);
|
|
2432
2822
|
},
|
|
2433
2823
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2465,31 +2855,35 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2465
2855
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2466
2856
|
};
|
|
2467
2857
|
}
|
|
2468
|
-
const stat = await
|
|
2858
|
+
const stat = await import_promises8.default.stat(file);
|
|
2469
2859
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2470
2860
|
return {
|
|
2471
2861
|
available: false,
|
|
2472
2862
|
reason: `This transcript is ${(0, import_format.formatFileSize)(stat.size)}, larger than the ${(0, import_format.formatFileSize)(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
|
|
2473
2863
|
};
|
|
2474
2864
|
}
|
|
2475
|
-
const content = await
|
|
2476
|
-
return { available: true, fileName:
|
|
2865
|
+
const content = await import_promises8.default.readFile(file, "utf8");
|
|
2866
|
+
return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
|
|
2477
2867
|
},
|
|
2478
2868
|
async start(input) {
|
|
2479
2869
|
const prompt = input.prompt.trim();
|
|
2480
2870
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2481
2871
|
const cwd = await assertFolder(input.cwd);
|
|
2482
|
-
const
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
)
|
|
2872
|
+
const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
|
|
2873
|
+
const draft = newSession({
|
|
2874
|
+
provider: input.provider,
|
|
2875
|
+
cwd,
|
|
2876
|
+
title: titleFrom(prompt),
|
|
2877
|
+
model: input.model?.trim() || null,
|
|
2878
|
+
effort: input.effort?.trim() || null,
|
|
2879
|
+
origin: "started",
|
|
2880
|
+
providerSessionId: null
|
|
2881
|
+
});
|
|
2882
|
+
if (bundle) {
|
|
2883
|
+
await agents.saveSessionBundle(draft.id, bundle);
|
|
2884
|
+
draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
|
|
2885
|
+
}
|
|
2886
|
+
const session = saveSession(draft);
|
|
2493
2887
|
const run = await startRun(session, prompt);
|
|
2494
2888
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2495
2889
|
},
|
|
@@ -2558,6 +2952,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2558
2952
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2559
2953
|
return saveSession(next);
|
|
2560
2954
|
},
|
|
2955
|
+
async agentAccess() {
|
|
2956
|
+
return agents.access();
|
|
2957
|
+
},
|
|
2958
|
+
async agentCatalogRpc(request) {
|
|
2959
|
+
return agents.catalogRpc(request);
|
|
2960
|
+
},
|
|
2961
|
+
async agentDetails(agentId) {
|
|
2962
|
+
return agents.details(String(agentId ?? ""));
|
|
2963
|
+
},
|
|
2964
|
+
setMcpBaseUrl(baseUrl) {
|
|
2965
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
2966
|
+
},
|
|
2967
|
+
async handleMcp(secret, body) {
|
|
2968
|
+
return agents.handleMcp(secret, body);
|
|
2969
|
+
},
|
|
2561
2970
|
async dispose() {
|
|
2562
2971
|
for (const liveRun of live.values()) {
|
|
2563
2972
|
liveRun.stopRequested = true;
|
|
@@ -2627,7 +3036,10 @@ var BRIDGE_METHODS = [
|
|
|
2627
3036
|
"setChatPreference",
|
|
2628
3037
|
"searchChats",
|
|
2629
3038
|
"openFolder",
|
|
2630
|
-
"setSessionOptions"
|
|
3039
|
+
"setSessionOptions",
|
|
3040
|
+
"agentAccess",
|
|
3041
|
+
"agentCatalogRpc",
|
|
3042
|
+
"agentDetails"
|
|
2631
3043
|
];
|
|
2632
3044
|
var BridgeError = class extends Error {
|
|
2633
3045
|
constructor(message, code) {
|
|
@@ -2671,14 +3083,14 @@ var MIME = {
|
|
|
2671
3083
|
".map": "application/json"
|
|
2672
3084
|
};
|
|
2673
3085
|
function moduleDir2() {
|
|
2674
|
-
return
|
|
3086
|
+
return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
2675
3087
|
}
|
|
2676
3088
|
function defaultUiDir() {
|
|
2677
3089
|
for (const candidate of [
|
|
2678
|
-
|
|
2679
|
-
|
|
3090
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
|
|
3091
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
|
|
2680
3092
|
]) {
|
|
2681
|
-
if ((0, import_node_fs5.existsSync)(
|
|
3093
|
+
if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
|
|
2682
3094
|
}
|
|
2683
3095
|
return null;
|
|
2684
3096
|
}
|
|
@@ -2686,7 +3098,7 @@ function tokensMatch(expected, given) {
|
|
|
2686
3098
|
if (!given) return false;
|
|
2687
3099
|
const a = Buffer.from(expected);
|
|
2688
3100
|
const b = Buffer.from(given);
|
|
2689
|
-
return a.length === b.length && (0,
|
|
3101
|
+
return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
|
|
2690
3102
|
}
|
|
2691
3103
|
var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
|
|
2692
3104
|
<body style="font:15px system-ui;padding:48px;max-width:560px">
|
|
@@ -2701,19 +3113,19 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2701
3113
|
return;
|
|
2702
3114
|
}
|
|
2703
3115
|
const requested = decodeURIComponent(url.pathname);
|
|
2704
|
-
let file =
|
|
3116
|
+
let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
|
|
2705
3117
|
if (!file.startsWith(uiDir)) {
|
|
2706
3118
|
res.writeHead(403).end();
|
|
2707
3119
|
return;
|
|
2708
3120
|
}
|
|
2709
|
-
const stat = await
|
|
2710
|
-
if (!stat || stat.isDirectory()) file =
|
|
2711
|
-
const body = await
|
|
3121
|
+
const stat = await import_promises9.default.stat(file).catch(() => null);
|
|
3122
|
+
if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
|
|
3123
|
+
const body = await import_promises9.default.readFile(file).catch(() => null);
|
|
2712
3124
|
if (!body) {
|
|
2713
3125
|
res.writeHead(404).end();
|
|
2714
3126
|
return;
|
|
2715
3127
|
}
|
|
2716
|
-
const ext =
|
|
3128
|
+
const ext = import_node_path10.default.extname(file);
|
|
2717
3129
|
res.writeHead(200, {
|
|
2718
3130
|
"content-type": MIME[ext] ?? "application/octet-stream",
|
|
2719
3131
|
"cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
|
|
@@ -2722,6 +3134,46 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2722
3134
|
});
|
|
2723
3135
|
res.end(body);
|
|
2724
3136
|
}
|
|
3137
|
+
var MAX_MCP_BODY = 4 * 1024 * 1024;
|
|
3138
|
+
async function handleMcp(engine, secret, req, res, allowedOrigin) {
|
|
3139
|
+
if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
|
|
3140
|
+
res.writeHead(403).end();
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
if (req.method !== "POST") {
|
|
3144
|
+
res.writeHead(405, { allow: "POST" }).end();
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
const chunks = [];
|
|
3148
|
+
let size = 0;
|
|
3149
|
+
for await (const chunk of req) {
|
|
3150
|
+
size += chunk.length;
|
|
3151
|
+
if (size > MAX_MCP_BODY) {
|
|
3152
|
+
res.writeHead(413).end();
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
chunks.push(chunk);
|
|
3156
|
+
}
|
|
3157
|
+
let body;
|
|
3158
|
+
try {
|
|
3159
|
+
body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
3160
|
+
} catch {
|
|
3161
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3162
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
|
|
3166
|
+
if (!reply) {
|
|
3167
|
+
res.writeHead(404).end();
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
if (reply.body === void 0) {
|
|
3171
|
+
res.writeHead(reply.status).end();
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
res.writeHead(reply.status, { "content-type": "application/json" });
|
|
3175
|
+
res.end(JSON.stringify(reply.body));
|
|
3176
|
+
}
|
|
2725
3177
|
function attachSocket(socket, bridge) {
|
|
2726
3178
|
const unsubscribe = bridge.subscribe((event) => {
|
|
2727
3179
|
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
|
|
@@ -2752,7 +3204,7 @@ function attachSocket(socket, bridge) {
|
|
|
2752
3204
|
socket.on("close", unsubscribe);
|
|
2753
3205
|
}
|
|
2754
3206
|
async function startCodingSessionsHost(options = {}) {
|
|
2755
|
-
const token = options.token ?? (0,
|
|
3207
|
+
const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
|
|
2756
3208
|
const engine = options.engine ?? await createCodingSessionsEngine(options);
|
|
2757
3209
|
const bridge = createHostBridge(engine);
|
|
2758
3210
|
const uiDir = options.uiDir ?? defaultUiDir();
|
|
@@ -2763,6 +3215,13 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2763
3215
|
};
|
|
2764
3216
|
const server = import_node_http.default.createServer((req, res) => {
|
|
2765
3217
|
const url2 = new URL(req.url ?? "/", `http://${HOST}`);
|
|
3218
|
+
if (url2.pathname.startsWith("/mcp/")) {
|
|
3219
|
+
handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
|
|
3220
|
+
if (!res.headersSent) res.writeHead(500);
|
|
3221
|
+
res.end();
|
|
3222
|
+
});
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
2766
3225
|
if (url2.pathname === "/health") {
|
|
2767
3226
|
res.writeHead(200, { "content-type": "application/json" });
|
|
2768
3227
|
res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
|
|
@@ -2793,6 +3252,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2793
3252
|
server.off("error", reject);
|
|
2794
3253
|
const address = server.address();
|
|
2795
3254
|
if (address && typeof address === "object") port = address.port;
|
|
3255
|
+
engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
|
|
2796
3256
|
resolve();
|
|
2797
3257
|
});
|
|
2798
3258
|
});
|
|
@@ -2803,6 +3263,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2803
3263
|
token,
|
|
2804
3264
|
engine,
|
|
2805
3265
|
async close() {
|
|
3266
|
+
engine.setMcpBaseUrl?.(null);
|
|
2806
3267
|
for (const client of wss.clients) client.terminate();
|
|
2807
3268
|
await new Promise((resolve) => wss.close(() => resolve()));
|
|
2808
3269
|
await new Promise((resolve) => server.close(() => resolve()));
|