@ai-matrx/coding-sessions 0.1.81 → 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 +24 -0
- package/dist/engine/index.cjs +724 -139
- 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 +717 -132
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +14 -2
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-RYOb-YZT.d.cts → index-DEPwyr2j.d.cts} +148 -0
- package/dist/{index-RYOb-YZT.d.ts → index-DEPwyr2j.d.ts} +148 -0
- package/dist/index.cjs +21 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -1
- package/dist/index.d.ts +105 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +722 -410
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +105 -7
- package/dist/react/index.d.ts +105 -7
- package/dist/react/index.js +730 -405
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +792 -155
- 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 +783 -146
- 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-B3Ic2ziA.js +0 -69
- package/dist/ui/assets/index-BAkpc01s.css +0 -2
package/dist/engine/index.cjs
CHANGED
|
@@ -60,9 +60,9 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
60
60
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
61
61
|
|
|
62
62
|
// src/engine/index.ts
|
|
63
|
-
var
|
|
64
|
-
var
|
|
65
|
-
var
|
|
63
|
+
var import_node_child_process3 = require("child_process");
|
|
64
|
+
var import_node_crypto5 = require("crypto");
|
|
65
|
+
var import_node_fs4 = require("fs");
|
|
66
66
|
var import_format = require("@ai-matrx/kit/format");
|
|
67
67
|
|
|
68
68
|
// src/engine/raw-backup.ts
|
|
@@ -159,10 +159,379 @@ function createAiMatrxRawBackupSink(options) {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// src/engine/agent-attach.ts
|
|
163
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
164
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
165
|
+
var AGENT_MCP_SERVER_NAME = "matrx-agent";
|
|
166
|
+
var AGENT_PLUGIN_NAME = "matrx-agent";
|
|
167
|
+
function escapeAttr(value) {
|
|
168
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
169
|
+
}
|
|
170
|
+
function versionWords(bundle) {
|
|
171
|
+
return bundle.version_number !== null ? String(bundle.version_number) : "latest";
|
|
172
|
+
}
|
|
173
|
+
function renderAgentInstructions(bundle, options = {}) {
|
|
174
|
+
const included = bundle.skills.filter((skill) => skill.tier === "included");
|
|
175
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
176
|
+
const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
|
|
177
|
+
if (included.length > 0) {
|
|
178
|
+
parts.push(
|
|
179
|
+
"## Skills (always in effect)",
|
|
180
|
+
...included.map((skill) => `### ${skill.label}
|
|
181
|
+
|
|
182
|
+
${skill.body.trim()}`)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (options.claudeSkills && listed2.length > 0) {
|
|
186
|
+
parts.push(
|
|
187
|
+
[
|
|
188
|
+
"## Your skills",
|
|
189
|
+
"When a request matches one of these, load it with your Skill tool and follow it before answering:",
|
|
190
|
+
...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
|
|
191
|
+
].join("\n")
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
|
|
195
|
+
parts.push(bundle.instructions.trim());
|
|
196
|
+
parts.push("</ai_matrx_agent>");
|
|
197
|
+
return `${parts.filter(Boolean).join("\n\n")}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
function renderCodexSkillsList(bundle, skillsRoot) {
|
|
201
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
202
|
+
if (listed2.length === 0) return "";
|
|
203
|
+
const lines = [
|
|
204
|
+
"<skills_instructions>",
|
|
205
|
+
"## AI Matrx agent skills",
|
|
206
|
+
"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.",
|
|
207
|
+
"### Available skills",
|
|
208
|
+
...listed2.map(
|
|
209
|
+
(skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
210
|
+
),
|
|
211
|
+
"</skills_instructions>"
|
|
212
|
+
];
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
function tomlString(value) {
|
|
216
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
217
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
218
|
+
}
|
|
219
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
220
|
+
const pluginDir = import_node_path.default.join(dir, "plugin");
|
|
221
|
+
const skillsRoot = import_node_path.default.join(dir, "skills");
|
|
222
|
+
await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
223
|
+
await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
224
|
+
const instructions = renderAgentInstructions(bundle);
|
|
225
|
+
const instructionsFile = import_node_path.default.join(dir, "instructions.md");
|
|
226
|
+
await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
227
|
+
await import_promises2.default.writeFile(
|
|
228
|
+
import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
229
|
+
`${JSON.stringify(
|
|
230
|
+
{
|
|
231
|
+
name: AGENT_PLUGIN_NAME,
|
|
232
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
233
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
234
|
+
},
|
|
235
|
+
null,
|
|
236
|
+
2
|
|
237
|
+
)}
|
|
238
|
+
`,
|
|
239
|
+
{ mode: 384 }
|
|
240
|
+
);
|
|
241
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
242
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
243
|
+
for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
|
|
244
|
+
await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
|
|
245
|
+
for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
|
|
246
|
+
if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
for (const skill of listed2) {
|
|
249
|
+
const skillDir = import_node_path.default.join(root, skill.slug);
|
|
250
|
+
await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
251
|
+
await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
let mcpConfigFile = null;
|
|
255
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
256
|
+
const mcpPath = import_node_path.default.join(dir, "mcp.json");
|
|
257
|
+
if (mcpUrl) {
|
|
258
|
+
mcpConfigFile = mcpPath;
|
|
259
|
+
await import_promises2.default.writeFile(
|
|
260
|
+
mcpPath,
|
|
261
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
262
|
+
`,
|
|
263
|
+
{ mode: 384 }
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
await import_promises2.default.rm(mcpPath, { force: true });
|
|
267
|
+
}
|
|
268
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
269
|
+
return {
|
|
270
|
+
dir,
|
|
271
|
+
instructionsFile,
|
|
272
|
+
pluginDir,
|
|
273
|
+
mcpConfigFile,
|
|
274
|
+
mcpUrl,
|
|
275
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
276
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
277
|
+
|
|
278
|
+
${instructions}` : instructions
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function agentArgsFor(provider, attachment, options) {
|
|
282
|
+
if (provider === "claude") {
|
|
283
|
+
const args2 = [
|
|
284
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
285
|
+
"--append-system-prompt-file",
|
|
286
|
+
attachment.instructionsFile,
|
|
287
|
+
"--system-prompt-snapshot",
|
|
288
|
+
"off",
|
|
289
|
+
"--plugin-dir",
|
|
290
|
+
attachment.pluginDir
|
|
291
|
+
];
|
|
292
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
293
|
+
return args2;
|
|
294
|
+
}
|
|
295
|
+
const args = [];
|
|
296
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
297
|
+
if (attachment.mcpUrl) {
|
|
298
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
299
|
+
args.push(
|
|
300
|
+
"-c",
|
|
301
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
302
|
+
"-c",
|
|
303
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return args;
|
|
307
|
+
}
|
|
308
|
+
function sessionAgentRecord(bundle, options) {
|
|
309
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
310
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
311
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
312
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
313
|
+
for (const tool of bundle.tools) {
|
|
314
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
318
|
+
const loaded = [
|
|
319
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
320
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
321
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
322
|
+
];
|
|
323
|
+
return {
|
|
324
|
+
agentId: bundle.agent_id,
|
|
325
|
+
versionId: bundle.version_id,
|
|
326
|
+
versionNumber: bundle.version_number,
|
|
327
|
+
name: bundle.name,
|
|
328
|
+
definitionHash: bundle.definition_hash,
|
|
329
|
+
loaded,
|
|
330
|
+
unavailable
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/engine/agents.ts
|
|
335
|
+
var import_node_crypto2 = require("crypto");
|
|
336
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
337
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
338
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
339
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
340
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
341
|
+
"agx_get_list_full",
|
|
342
|
+
"agx_search",
|
|
343
|
+
"agx_resolve_agent_address",
|
|
344
|
+
"ues_get_bulk",
|
|
345
|
+
"ues_list",
|
|
346
|
+
"ues_set",
|
|
347
|
+
"ues_touch"
|
|
348
|
+
]);
|
|
349
|
+
var agentsClient = async () => {
|
|
350
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
351
|
+
import("@ai-matrx/agents/matrx"),
|
|
352
|
+
import("@ai-matrx/agents/portable"),
|
|
353
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
354
|
+
]);
|
|
355
|
+
return { ...matrx, ...portable, ...mcp };
|
|
356
|
+
};
|
|
357
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
358
|
+
function userIdOf(credential) {
|
|
359
|
+
if (!credential || credential.kind !== "user") return null;
|
|
360
|
+
const payload = credential.accessToken.split(".")[1];
|
|
361
|
+
if (!payload) return null;
|
|
362
|
+
try {
|
|
363
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
364
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function createAgentConnection(options, dataDir) {
|
|
370
|
+
const credentials = options?.credentials ?? null;
|
|
371
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
372
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
373
|
+
const supabaseKey = options?.supabaseKey ?? null;
|
|
374
|
+
const root = import_node_path2.default.join(dataDir, "agents");
|
|
375
|
+
let mcpBaseUrl = null;
|
|
376
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
377
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
378
|
+
const organizationId = () => {
|
|
379
|
+
const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
|
|
380
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
381
|
+
};
|
|
382
|
+
const transport = async () => (await agentsClient()).createMatrxTransport({
|
|
383
|
+
baseUrl: serverUrl,
|
|
384
|
+
...credentials ? { credentials } : {},
|
|
385
|
+
...organizationId() ? { organizationId: () => organizationId() } : {},
|
|
386
|
+
source: "codingSessionsAgents"
|
|
387
|
+
});
|
|
388
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
389
|
+
const requireSignedIn = async () => {
|
|
390
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
391
|
+
};
|
|
392
|
+
const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
393
|
+
async function meta(sessionId) {
|
|
394
|
+
const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
|
|
395
|
+
const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
396
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
397
|
+
const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
|
|
398
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
399
|
+
await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
400
|
+
return created;
|
|
401
|
+
}
|
|
402
|
+
async function findSessionBySecret(secret) {
|
|
403
|
+
for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
|
|
404
|
+
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);
|
|
405
|
+
if (found) {
|
|
406
|
+
secretToSession.set(secret, name);
|
|
407
|
+
return name;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
async function readSessionBundle(sessionId) {
|
|
413
|
+
return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
async access() {
|
|
417
|
+
const current = await credential().catch(() => null);
|
|
418
|
+
const userId = userIdOf(current);
|
|
419
|
+
return {
|
|
420
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
421
|
+
signedIn: userId !== null,
|
|
422
|
+
userId,
|
|
423
|
+
organizationId: organizationId()
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
async catalogRpc(request) {
|
|
427
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
428
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
429
|
+
}
|
|
430
|
+
const current = await credential();
|
|
431
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
432
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
433
|
+
const query = new URLSearchParams();
|
|
434
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
435
|
+
if (request.range) {
|
|
436
|
+
query.set("offset", String(request.range[0]));
|
|
437
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
438
|
+
}
|
|
439
|
+
const headers = {
|
|
440
|
+
apikey: supabaseKey,
|
|
441
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
442
|
+
"Content-Type": "application/json",
|
|
443
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
444
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
445
|
+
"Content-Profile": "public",
|
|
446
|
+
"Accept-Profile": "public"
|
|
447
|
+
};
|
|
448
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
449
|
+
const qs = query.toString();
|
|
450
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers,
|
|
453
|
+
body: JSON.stringify(request.args ?? {})
|
|
454
|
+
});
|
|
455
|
+
const text = await response.text();
|
|
456
|
+
const body = text ? JSON.parse(text) : null;
|
|
457
|
+
if (!response.ok) {
|
|
458
|
+
const err = body ?? {};
|
|
459
|
+
return {
|
|
460
|
+
data: null,
|
|
461
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const range = response.headers.get("content-range");
|
|
465
|
+
const total = range?.split("/")[1];
|
|
466
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
467
|
+
},
|
|
468
|
+
async details(agentId) {
|
|
469
|
+
await requireSignedIn();
|
|
470
|
+
const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
|
|
471
|
+
const [bundle, versions] = await Promise.all([
|
|
472
|
+
fetchPortableAgent(await transport(), { agentId }),
|
|
473
|
+
fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
|
|
474
|
+
]);
|
|
475
|
+
return {
|
|
476
|
+
agentId: bundle.agent_id,
|
|
477
|
+
name: bundle.name,
|
|
478
|
+
variables: bundle.variables,
|
|
479
|
+
versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
|
|
480
|
+
latestVersionNumber: bundle.version_number
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
async loadBundle(selection) {
|
|
484
|
+
await requireSignedIn();
|
|
485
|
+
const { fetchPortableAgent } = await agentsClient();
|
|
486
|
+
return fetchPortableAgent(
|
|
487
|
+
await transport(),
|
|
488
|
+
selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
|
|
489
|
+
{ variables: selection.variables ?? {} }
|
|
490
|
+
);
|
|
491
|
+
},
|
|
492
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
493
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
494
|
+
await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
495
|
+
await meta(sessionId);
|
|
496
|
+
},
|
|
497
|
+
readSessionBundle,
|
|
498
|
+
sessionDir,
|
|
499
|
+
async mcpUrlFor(sessionId) {
|
|
500
|
+
if (!mcpBaseUrl) return null;
|
|
501
|
+
const { mcpSecret } = await meta(sessionId);
|
|
502
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
503
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
504
|
+
},
|
|
505
|
+
setMcpBaseUrl(baseUrl) {
|
|
506
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
507
|
+
},
|
|
508
|
+
async handleMcp(secret, body) {
|
|
509
|
+
let handler = handlers.get(secret);
|
|
510
|
+
if (!handler) {
|
|
511
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
512
|
+
if (!sessionId) return null;
|
|
513
|
+
const bundle = await readSessionBundle(sessionId);
|
|
514
|
+
if (!bundle) return null;
|
|
515
|
+
const { conversationId } = await meta(sessionId);
|
|
516
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
517
|
+
handler = createAgentToolsMcpHandler({
|
|
518
|
+
bundle,
|
|
519
|
+
credentials,
|
|
520
|
+
organizationId: () => organizationId(),
|
|
521
|
+
baseUrl: serverUrl,
|
|
522
|
+
conversationId
|
|
523
|
+
});
|
|
524
|
+
handlers.set(secret, handler);
|
|
525
|
+
}
|
|
526
|
+
return handler.handle(body);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
162
531
|
// src/engine/index.ts
|
|
163
|
-
var
|
|
164
|
-
var
|
|
165
|
-
var
|
|
532
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
533
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
534
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
166
535
|
var import_node_url = require("url");
|
|
167
536
|
|
|
168
537
|
// src/normalize/claude.ts
|
|
@@ -828,9 +1197,177 @@ function describeProblem(provider, problem) {
|
|
|
828
1197
|
};
|
|
829
1198
|
}
|
|
830
1199
|
|
|
831
|
-
// src/engine/
|
|
1200
|
+
// src/engine/binaries.ts
|
|
1201
|
+
var import_node_child_process = require("child_process");
|
|
1202
|
+
var import_node_fs = require("fs");
|
|
832
1203
|
var import_node_os = __toESM(require("os"), 1);
|
|
833
|
-
var
|
|
1204
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
1205
|
+
var SOURCE_LABEL = {
|
|
1206
|
+
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
1207
|
+
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
1208
|
+
};
|
|
1209
|
+
function sourceLabel(provider, source) {
|
|
1210
|
+
return SOURCE_LABEL[provider][source];
|
|
1211
|
+
}
|
|
1212
|
+
function parseVersion(text) {
|
|
1213
|
+
const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
|
|
1214
|
+
return match ? match[0] : null;
|
|
1215
|
+
}
|
|
1216
|
+
function compareVersions(a, b) {
|
|
1217
|
+
const [aCore = "", aPre] = a.split("-", 2);
|
|
1218
|
+
const [bCore = "", bPre] = b.split("-", 2);
|
|
1219
|
+
const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1220
|
+
const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1221
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1222
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1223
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1224
|
+
}
|
|
1225
|
+
if (aPre && !bPre) return -1;
|
|
1226
|
+
if (!aPre && bPre) return 1;
|
|
1227
|
+
return 0;
|
|
1228
|
+
}
|
|
1229
|
+
function pickBinary(candidates) {
|
|
1230
|
+
let best = null;
|
|
1231
|
+
for (const c of candidates) {
|
|
1232
|
+
if (!best) {
|
|
1233
|
+
best = c;
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
|
|
1237
|
+
}
|
|
1238
|
+
return best;
|
|
1239
|
+
}
|
|
1240
|
+
function executable(file) {
|
|
1241
|
+
try {
|
|
1242
|
+
(0, import_node_fs.accessSync)(file, import_node_fs.constants.X_OK);
|
|
1243
|
+
return true;
|
|
1244
|
+
} catch {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function exeName(name, platform) {
|
|
1249
|
+
return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
|
|
1250
|
+
}
|
|
1251
|
+
function allOnPath(name, pathValue, platform = process.platform) {
|
|
1252
|
+
const found = [];
|
|
1253
|
+
for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
|
|
1254
|
+
if (!dir) continue;
|
|
1255
|
+
for (const file of exeName(name, platform)) {
|
|
1256
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
1257
|
+
if (executable(candidate)) {
|
|
1258
|
+
found.push(candidate);
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return found;
|
|
1264
|
+
}
|
|
1265
|
+
function desktopAppCandidates(provider, platform, home, applications) {
|
|
1266
|
+
if (platform !== "darwin") return [];
|
|
1267
|
+
if (provider === "codex") {
|
|
1268
|
+
return applications.map(
|
|
1269
|
+
(apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1273
|
+
let versions = [];
|
|
1274
|
+
try {
|
|
1275
|
+
versions = (0, import_node_fs.readdirSync)(root);
|
|
1276
|
+
} catch {
|
|
1277
|
+
return [];
|
|
1278
|
+
}
|
|
1279
|
+
return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
1280
|
+
}
|
|
1281
|
+
function npmGlobalPrefix(env) {
|
|
1282
|
+
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
1283
|
+
if (fromEnv) return Promise.resolve(fromEnv);
|
|
1284
|
+
const npm = allOnPath("npm", env.PATH ?? "")[0];
|
|
1285
|
+
if (!npm) return Promise.resolve(null);
|
|
1286
|
+
return new Promise((resolve) => {
|
|
1287
|
+
const child = (0, import_node_child_process.execFile)(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
|
|
1288
|
+
resolve(error ? null : String(stdout).trim() || null);
|
|
1289
|
+
});
|
|
1290
|
+
child.stdin?.end();
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
function declaredVersion(provider, source, file) {
|
|
1294
|
+
if (source !== "desktop-app") return null;
|
|
1295
|
+
if (provider === "codex") {
|
|
1296
|
+
try {
|
|
1297
|
+
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"));
|
|
1298
|
+
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
1299
|
+
} catch {
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
|
|
1304
|
+
}
|
|
1305
|
+
function runVersion(file, env) {
|
|
1306
|
+
return new Promise((resolve) => {
|
|
1307
|
+
const child = (0, import_node_child_process.execFile)(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
|
|
1308
|
+
resolve(error ? null : parseVersion(String(stdout)));
|
|
1309
|
+
});
|
|
1310
|
+
child.stdin?.end();
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
async function discoverBinaries(provider, options) {
|
|
1314
|
+
const platform = options.platform ?? process.platform;
|
|
1315
|
+
const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
|
|
1316
|
+
const listed2 = [
|
|
1317
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
1318
|
+
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
1319
|
+
];
|
|
1320
|
+
const prefix = await npmGlobalPrefix(options.env);
|
|
1321
|
+
if (prefix) {
|
|
1322
|
+
const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
|
|
1323
|
+
for (const file of exeName(provider, platform)) {
|
|
1324
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
1325
|
+
if (executable(candidate)) {
|
|
1326
|
+
listed2.push({ path: candidate, source: "npm-global" });
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1332
|
+
const unique = listed2.filter((c) => {
|
|
1333
|
+
let real = c.path;
|
|
1334
|
+
try {
|
|
1335
|
+
real = (0, import_node_fs.realpathSync)(c.path);
|
|
1336
|
+
} catch {
|
|
1337
|
+
}
|
|
1338
|
+
if (seen.has(real)) return false;
|
|
1339
|
+
seen.add(real);
|
|
1340
|
+
return true;
|
|
1341
|
+
});
|
|
1342
|
+
return Promise.all(
|
|
1343
|
+
unique.map(async (c) => ({
|
|
1344
|
+
...c,
|
|
1345
|
+
version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
|
|
1346
|
+
}))
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
async function resolveBinary(provider, options) {
|
|
1350
|
+
const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
|
|
1351
|
+
const best = pickBinary(candidates);
|
|
1352
|
+
const inUse = options.forced ? {
|
|
1353
|
+
path: options.forced,
|
|
1354
|
+
source: "configured",
|
|
1355
|
+
version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
|
|
1356
|
+
} : best;
|
|
1357
|
+
if (!inUse) return null;
|
|
1358
|
+
const newer = best && best.path !== inUse.path && best.version && (!inUse.version || compareVersions(best.version, inUse.version) > 0) ? { path: best.path, version: best.version, sourceLabel: sourceLabel(provider, best.source) } : null;
|
|
1359
|
+
return {
|
|
1360
|
+
path: inUse.path,
|
|
1361
|
+
version: inUse.version,
|
|
1362
|
+
source: inUse.source,
|
|
1363
|
+
sourceLabel: sourceLabel(provider, inUse.source),
|
|
1364
|
+
newer
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/engine/clean-env.ts
|
|
1369
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
1370
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
834
1371
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
835
1372
|
"HOME",
|
|
836
1373
|
"USER",
|
|
@@ -852,11 +1389,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
852
1389
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
853
1390
|
function wellKnownBinDirs(home) {
|
|
854
1391
|
return [
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1392
|
+
import_node_path4.default.join(home, ".local", "bin"),
|
|
1393
|
+
import_node_path4.default.join(home, ".claude", "local"),
|
|
1394
|
+
import_node_path4.default.join(home, ".npm-global", "bin"),
|
|
1395
|
+
import_node_path4.default.join(home, ".bun", "bin"),
|
|
1396
|
+
import_node_path4.default.join(home, ".volta", "bin"),
|
|
860
1397
|
"/opt/homebrew/bin",
|
|
861
1398
|
"/usr/local/bin",
|
|
862
1399
|
"/usr/bin",
|
|
@@ -874,31 +1411,31 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
874
1411
|
if (typeof value !== "string") continue;
|
|
875
1412
|
if (isAllowedEnvName(name)) out[name] = value;
|
|
876
1413
|
}
|
|
877
|
-
const home = out.HOME ??
|
|
1414
|
+
const home = out.HOME ?? import_node_os2.default.homedir();
|
|
878
1415
|
out.HOME = home;
|
|
879
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1416
|
+
const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
|
|
880
1417
|
const merged = [];
|
|
881
1418
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
882
1419
|
if (!merged.includes(dir)) merged.push(dir);
|
|
883
1420
|
}
|
|
884
|
-
out.PATH = merged.join(
|
|
1421
|
+
out.PATH = merged.join(import_node_path4.default.delimiter);
|
|
885
1422
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
886
1423
|
return out;
|
|
887
1424
|
}
|
|
888
1425
|
|
|
889
1426
|
// src/engine/files.ts
|
|
890
|
-
var
|
|
891
|
-
var
|
|
892
|
-
var
|
|
893
|
-
var
|
|
1427
|
+
var import_node_fs2 = require("fs");
|
|
1428
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1429
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
1430
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
894
1431
|
|
|
895
1432
|
// src/engine/activity.ts
|
|
896
|
-
var
|
|
1433
|
+
var import_node_child_process2 = require("child_process");
|
|
897
1434
|
var RECENT_WRITE_MS = 2 * 60 * 1e3;
|
|
898
1435
|
var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
|
|
899
1436
|
function listProcesses() {
|
|
900
1437
|
return new Promise((resolve) => {
|
|
901
|
-
(0,
|
|
1438
|
+
(0, import_node_child_process2.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
|
|
902
1439
|
if (error) {
|
|
903
1440
|
resolve([]);
|
|
904
1441
|
return;
|
|
@@ -957,17 +1494,17 @@ function describeActivity(input) {
|
|
|
957
1494
|
}
|
|
958
1495
|
|
|
959
1496
|
// src/engine/files.ts
|
|
960
|
-
function defaultProviderHomes(home =
|
|
1497
|
+
function defaultProviderHomes(home = import_node_os3.default.homedir()) {
|
|
961
1498
|
return {
|
|
962
|
-
claudeProjects:
|
|
963
|
-
codexSessions:
|
|
964
|
-
codexIndex:
|
|
1499
|
+
claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
|
|
1500
|
+
codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
|
|
1501
|
+
codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
|
|
965
1502
|
};
|
|
966
1503
|
}
|
|
967
1504
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
968
1505
|
async function safeReaddir(dir) {
|
|
969
1506
|
try {
|
|
970
|
-
return await
|
|
1507
|
+
return await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
971
1508
|
} catch {
|
|
972
1509
|
return [];
|
|
973
1510
|
}
|
|
@@ -976,7 +1513,7 @@ async function statCandidate(provider, file) {
|
|
|
976
1513
|
const match = UUIDISH.exec(file);
|
|
977
1514
|
if (!match?.[1]) return null;
|
|
978
1515
|
try {
|
|
979
|
-
const stat = await
|
|
1516
|
+
const stat = await import_promises4.default.stat(file);
|
|
980
1517
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
981
1518
|
} catch {
|
|
982
1519
|
return null;
|
|
@@ -986,10 +1523,10 @@ async function claudeCandidates(root) {
|
|
|
986
1523
|
const out = [];
|
|
987
1524
|
for (const project of await safeReaddir(root)) {
|
|
988
1525
|
if (!project.isDirectory()) continue;
|
|
989
|
-
const dir =
|
|
1526
|
+
const dir = import_node_path5.default.join(root, project.name);
|
|
990
1527
|
const entries = await safeReaddir(dir);
|
|
991
1528
|
const stats = await Promise.all(
|
|
992
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1529
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
|
|
993
1530
|
);
|
|
994
1531
|
for (const s of stats) if (s) out.push(s);
|
|
995
1532
|
}
|
|
@@ -999,14 +1536,14 @@ async function codexCandidates(root) {
|
|
|
999
1536
|
const out = [];
|
|
1000
1537
|
for (const year of await safeReaddir(root)) {
|
|
1001
1538
|
if (!year.isDirectory()) continue;
|
|
1002
|
-
for (const month of await safeReaddir(
|
|
1539
|
+
for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
|
|
1003
1540
|
if (!month.isDirectory()) continue;
|
|
1004
|
-
for (const day of await safeReaddir(
|
|
1541
|
+
for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
|
|
1005
1542
|
if (!day.isDirectory()) continue;
|
|
1006
|
-
const dir =
|
|
1543
|
+
const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
|
|
1007
1544
|
const entries = await safeReaddir(dir);
|
|
1008
1545
|
const stats = await Promise.all(
|
|
1009
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1546
|
+
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)))
|
|
1010
1547
|
);
|
|
1011
1548
|
for (const s of stats) if (s) out.push(s);
|
|
1012
1549
|
}
|
|
@@ -1015,7 +1552,7 @@ async function codexCandidates(root) {
|
|
|
1015
1552
|
return out;
|
|
1016
1553
|
}
|
|
1017
1554
|
async function readSlice(file, start, length) {
|
|
1018
|
-
const handle = await
|
|
1555
|
+
const handle = await import_promises4.default.open(file, "r");
|
|
1019
1556
|
try {
|
|
1020
1557
|
const buffer = Buffer.alloc(length);
|
|
1021
1558
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1088,9 +1625,9 @@ async function readCodexIndex(file) {
|
|
|
1088
1625
|
const names = /* @__PURE__ */ new Map();
|
|
1089
1626
|
let text;
|
|
1090
1627
|
try {
|
|
1091
|
-
const stat = await
|
|
1628
|
+
const stat = await import_promises4.default.stat(file);
|
|
1092
1629
|
const budget = 4 * 1024 * 1024;
|
|
1093
|
-
text = stat.size <= budget ? await
|
|
1630
|
+
text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1094
1631
|
} catch {
|
|
1095
1632
|
return names;
|
|
1096
1633
|
}
|
|
@@ -1211,7 +1748,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1211
1748
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1212
1749
|
let size;
|
|
1213
1750
|
try {
|
|
1214
|
-
size = (await
|
|
1751
|
+
size = (await import_promises4.default.stat(file)).size;
|
|
1215
1752
|
} catch {
|
|
1216
1753
|
return {
|
|
1217
1754
|
items: [],
|
|
@@ -1227,7 +1764,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
|
|
|
1227
1764
|
let droppedFirst = start > 0;
|
|
1228
1765
|
let carry = "";
|
|
1229
1766
|
await new Promise((resolve, reject) => {
|
|
1230
|
-
const stream = (0,
|
|
1767
|
+
const stream = (0, import_node_fs2.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
|
|
1231
1768
|
stream.on("data", (chunk) => {
|
|
1232
1769
|
const text = carry + String(chunk);
|
|
1233
1770
|
const lines = text.split("\n");
|
|
@@ -1265,9 +1802,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1265
1802
|
}
|
|
1266
1803
|
|
|
1267
1804
|
// src/engine/preferences.ts
|
|
1268
|
-
var
|
|
1269
|
-
var
|
|
1270
|
-
var
|
|
1805
|
+
var import_node_crypto3 = require("crypto");
|
|
1806
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
1807
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1271
1808
|
var PreferenceStore = class {
|
|
1272
1809
|
constructor(dir) {
|
|
1273
1810
|
this.dir = dir;
|
|
@@ -1276,11 +1813,11 @@ var PreferenceStore = class {
|
|
|
1276
1813
|
data = {};
|
|
1277
1814
|
writing = Promise.resolve();
|
|
1278
1815
|
get file() {
|
|
1279
|
-
return
|
|
1816
|
+
return import_node_path6.default.join(this.dir, "preferences.json");
|
|
1280
1817
|
}
|
|
1281
1818
|
async load() {
|
|
1282
1819
|
try {
|
|
1283
|
-
const parsed = JSON.parse(await
|
|
1820
|
+
const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
|
|
1284
1821
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1285
1822
|
} catch {
|
|
1286
1823
|
this.data = {};
|
|
@@ -1318,10 +1855,10 @@ var PreferenceStore = class {
|
|
|
1318
1855
|
persist() {
|
|
1319
1856
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1320
1857
|
this.writing = this.writing.then(async () => {
|
|
1321
|
-
await
|
|
1322
|
-
const tmp = `${this.file}.${(0,
|
|
1323
|
-
await
|
|
1324
|
-
await
|
|
1858
|
+
await import_promises5.default.mkdir(this.dir, { recursive: true });
|
|
1859
|
+
const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
|
|
1860
|
+
await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
1861
|
+
await import_promises5.default.rename(tmp, this.file);
|
|
1325
1862
|
}).catch(() => {
|
|
1326
1863
|
});
|
|
1327
1864
|
}
|
|
@@ -1331,7 +1868,7 @@ var PreferenceStore = class {
|
|
|
1331
1868
|
};
|
|
1332
1869
|
|
|
1333
1870
|
// src/engine/search.ts
|
|
1334
|
-
var
|
|
1871
|
+
var import_node_fs3 = require("fs");
|
|
1335
1872
|
function findInLine(provider, value, needle) {
|
|
1336
1873
|
const items = provider === "claude" ? createClaudeNormalizer().push(value) : createCodexRolloutNormalizer().push(value);
|
|
1337
1874
|
for (const item of items) {
|
|
@@ -1372,7 +1909,7 @@ async function scanFile(provider, file, needle, raw, deadline) {
|
|
|
1372
1909
|
return new Promise((resolve) => {
|
|
1373
1910
|
let carry = Buffer.alloc(0);
|
|
1374
1911
|
let done = false;
|
|
1375
|
-
const stream = (0,
|
|
1912
|
+
const stream = (0, import_node_fs3.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
|
|
1376
1913
|
const finish = (value) => {
|
|
1377
1914
|
if (done) return;
|
|
1378
1915
|
done = true;
|
|
@@ -1478,9 +2015,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1478
2015
|
}
|
|
1479
2016
|
|
|
1480
2017
|
// src/engine/store.ts
|
|
1481
|
-
var
|
|
1482
|
-
var
|
|
1483
|
-
var
|
|
2018
|
+
var import_node_crypto4 = require("crypto");
|
|
2019
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
2020
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1484
2021
|
var MAX_RUNS = 2e3;
|
|
1485
2022
|
var RunIndex = class {
|
|
1486
2023
|
constructor(dir) {
|
|
@@ -1490,12 +2027,12 @@ var RunIndex = class {
|
|
|
1490
2027
|
data = { version: 1, sessions: [], runs: [] };
|
|
1491
2028
|
writing = Promise.resolve();
|
|
1492
2029
|
get file() {
|
|
1493
|
-
return
|
|
2030
|
+
return import_node_path7.default.join(this.dir, "index.json");
|
|
1494
2031
|
}
|
|
1495
2032
|
async load() {
|
|
1496
|
-
await
|
|
2033
|
+
await import_promises6.default.mkdir(this.dir, { recursive: true });
|
|
1497
2034
|
try {
|
|
1498
|
-
const parsed = JSON.parse(await
|
|
2035
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
|
|
1499
2036
|
this.data = {
|
|
1500
2037
|
version: 1,
|
|
1501
2038
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1537,9 +2074,9 @@ var RunIndex = class {
|
|
|
1537
2074
|
persist() {
|
|
1538
2075
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1539
2076
|
this.writing = this.writing.then(async () => {
|
|
1540
|
-
const tmp = `${this.file}.${(0,
|
|
1541
|
-
await
|
|
1542
|
-
await
|
|
2077
|
+
const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
2078
|
+
await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
2079
|
+
await import_promises6.default.rename(tmp, this.file);
|
|
1543
2080
|
}).catch(() => {
|
|
1544
2081
|
});
|
|
1545
2082
|
}
|
|
@@ -1549,27 +2086,27 @@ var RunIndex = class {
|
|
|
1549
2086
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1550
2087
|
* driver's scratch state is reused rather than growing per run. */
|
|
1551
2088
|
async identity() {
|
|
1552
|
-
const file =
|
|
2089
|
+
const file = import_node_path7.default.join(this.dir, "identity.json");
|
|
1553
2090
|
try {
|
|
1554
|
-
const parsed = JSON.parse(await
|
|
2091
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
|
|
1555
2092
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1556
2093
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1557
2094
|
}
|
|
1558
2095
|
} catch {
|
|
1559
2096
|
}
|
|
1560
|
-
const identity = { companyId: (0,
|
|
1561
|
-
await
|
|
2097
|
+
const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
|
|
2098
|
+
await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1562
2099
|
return identity;
|
|
1563
2100
|
}
|
|
1564
2101
|
};
|
|
1565
2102
|
|
|
1566
2103
|
// src/engine/user-defaults.ts
|
|
1567
|
-
var
|
|
1568
|
-
var
|
|
1569
|
-
var
|
|
2104
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
2105
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
2106
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1570
2107
|
async function readJson(file) {
|
|
1571
2108
|
try {
|
|
1572
|
-
const parsed = JSON.parse(await
|
|
2109
|
+
const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
|
|
1573
2110
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1574
2111
|
} catch {
|
|
1575
2112
|
return null;
|
|
@@ -1578,10 +2115,10 @@ async function readJson(file) {
|
|
|
1578
2115
|
function str3(value) {
|
|
1579
2116
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1580
2117
|
}
|
|
1581
|
-
async function claudeDefaults(cwd, home =
|
|
2118
|
+
async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
1582
2119
|
const files = [
|
|
1583
|
-
...cwd ? [
|
|
1584
|
-
|
|
2120
|
+
...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
|
|
2121
|
+
import_node_path8.default.join(home, ".claude", "settings.json")
|
|
1585
2122
|
];
|
|
1586
2123
|
let model = null;
|
|
1587
2124
|
let effort = null;
|
|
@@ -1594,10 +2131,10 @@ async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
|
|
|
1594
2131
|
}
|
|
1595
2132
|
return { model, effort };
|
|
1596
2133
|
}
|
|
1597
|
-
async function codexDefaults(home =
|
|
2134
|
+
async function codexDefaults(home = import_node_os4.default.homedir()) {
|
|
1598
2135
|
let text;
|
|
1599
2136
|
try {
|
|
1600
|
-
text = await
|
|
2137
|
+
text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
|
|
1601
2138
|
} catch {
|
|
1602
2139
|
return { model: null, effort: null };
|
|
1603
2140
|
}
|
|
@@ -1621,45 +2158,33 @@ var EFFORT_LEVELS = {
|
|
|
1621
2158
|
// src/engine/index.ts
|
|
1622
2159
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1623
2160
|
function moduleDir() {
|
|
1624
|
-
return
|
|
2161
|
+
return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1625
2162
|
}
|
|
1626
2163
|
function resolveWorkerPath() {
|
|
1627
2164
|
const dir = moduleDir();
|
|
1628
2165
|
for (const candidate of [
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
2166
|
+
import_node_path9.default.join(dir, "worker.js"),
|
|
2167
|
+
import_node_path9.default.join(dir, "worker.ts"),
|
|
2168
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.js"),
|
|
2169
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.ts")
|
|
1633
2170
|
]) {
|
|
1634
|
-
if ((0,
|
|
2171
|
+
if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
|
|
1635
2172
|
}
|
|
1636
2173
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1637
2174
|
}
|
|
1638
2175
|
async function installShim(dataDir) {
|
|
1639
|
-
const source =
|
|
1640
|
-
if (!(0,
|
|
1641
|
-
const body = await
|
|
1642
|
-
const target =
|
|
1643
|
-
const current = await
|
|
2176
|
+
const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
2177
|
+
if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
2178
|
+
const body = await import_promises8.default.readFile(source, "utf8");
|
|
2179
|
+
const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
|
|
2180
|
+
const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
|
|
1644
2181
|
if (current !== body) {
|
|
1645
|
-
await
|
|
1646
|
-
await
|
|
2182
|
+
await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
|
|
2183
|
+
await import_promises8.default.writeFile(target, body, { mode: 493 });
|
|
1647
2184
|
}
|
|
1648
|
-
await
|
|
2185
|
+
await import_promises8.default.chmod(target, 493);
|
|
1649
2186
|
return target;
|
|
1650
2187
|
}
|
|
1651
|
-
function whichOnPath(name, pathValue) {
|
|
1652
|
-
for (const dir of pathValue.split(import_node_path6.default.delimiter)) {
|
|
1653
|
-
if (!dir) continue;
|
|
1654
|
-
const candidate = import_node_path6.default.join(dir, name);
|
|
1655
|
-
try {
|
|
1656
|
-
(0, import_node_fs3.accessSync)(candidate, import_node_fs3.constants.X_OK);
|
|
1657
|
-
return candidate;
|
|
1658
|
-
} catch {
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
return null;
|
|
1662
|
-
}
|
|
1663
2188
|
function nowIso() {
|
|
1664
2189
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1665
2190
|
}
|
|
@@ -1698,12 +2223,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
|
|
|
1698
2223
|
function describeAuthStatus(provider, probe) {
|
|
1699
2224
|
const label = PROVIDER_LABEL[provider];
|
|
1700
2225
|
const installed = probe?.installed === true;
|
|
1701
|
-
const
|
|
2226
|
+
const binary = probe?.binary ?? null;
|
|
2227
|
+
const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
|
|
1702
2228
|
const base = {
|
|
1703
2229
|
provider,
|
|
1704
2230
|
label,
|
|
1705
2231
|
installed,
|
|
1706
2232
|
version,
|
|
2233
|
+
binary,
|
|
1707
2234
|
loggedIn: false,
|
|
1708
2235
|
billing: "unknown",
|
|
1709
2236
|
plan: null,
|
|
@@ -1755,29 +2282,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1755
2282
|
};
|
|
1756
2283
|
}
|
|
1757
2284
|
async function createCodingSessionsEngine(options = {}) {
|
|
1758
|
-
const dataDir = options.dataDir ??
|
|
1759
|
-
const stateDir =
|
|
2285
|
+
const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
|
|
2286
|
+
const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
|
|
1760
2287
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1761
2288
|
const index = new RunIndex(dataDir);
|
|
1762
2289
|
await index.load();
|
|
1763
2290
|
const preferences = new PreferenceStore(dataDir);
|
|
1764
2291
|
await preferences.load();
|
|
1765
|
-
await
|
|
2292
|
+
await import_promises8.default.mkdir(stateDir, { recursive: true });
|
|
1766
2293
|
const identity = await index.identity();
|
|
2294
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1767
2295
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1768
2296
|
sink: options.rawBackup,
|
|
1769
2297
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1770
2298
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1771
2299
|
}) : null;
|
|
1772
2300
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1773
|
-
const healthFile =
|
|
2301
|
+
const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
|
|
1774
2302
|
let health = {};
|
|
1775
2303
|
try {
|
|
1776
|
-
health = JSON.parse(await
|
|
2304
|
+
health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
|
|
1777
2305
|
} catch {
|
|
1778
2306
|
health = {};
|
|
1779
2307
|
}
|
|
1780
|
-
const saveHealth = () =>
|
|
2308
|
+
const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1781
2309
|
function noteRunOutcome(provider, status, texts) {
|
|
1782
2310
|
if (status === "completed") {
|
|
1783
2311
|
if (health[provider]) {
|
|
@@ -1805,7 +2333,24 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1805
2333
|
// Marks the forked process as a driver worker (see worker.ts).
|
|
1806
2334
|
MATRX_CODING_SESSIONS_WORKER: "1"
|
|
1807
2335
|
});
|
|
1808
|
-
const
|
|
2336
|
+
const binaryHome = options.hostEnv?.HOME ?? import_node_os5.default.homedir();
|
|
2337
|
+
const binaryCache = /* @__PURE__ */ new Map();
|
|
2338
|
+
function binaryFor(provider, fresh = false) {
|
|
2339
|
+
const cached = binaryCache.get(provider);
|
|
2340
|
+
if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
|
|
2341
|
+
const forced = options.commands?.[provider];
|
|
2342
|
+
const value = resolveBinary(provider, {
|
|
2343
|
+
env: workerEnv,
|
|
2344
|
+
home: binaryHome,
|
|
2345
|
+
forced,
|
|
2346
|
+
// A forced command needs no discovery to run; the status check still
|
|
2347
|
+
// looks, so it can say when a newer copy exists.
|
|
2348
|
+
discover: !forced || fresh
|
|
2349
|
+
}).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
|
|
2350
|
+
if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
|
|
2351
|
+
return value;
|
|
2352
|
+
}
|
|
2353
|
+
const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
|
|
1809
2354
|
const workerPath = resolveWorkerPath();
|
|
1810
2355
|
const shimPath = await installShim(dataDir);
|
|
1811
2356
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1855,7 +2400,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1855
2400
|
}
|
|
1856
2401
|
}
|
|
1857
2402
|
function forkWorker() {
|
|
1858
|
-
return (0,
|
|
2403
|
+
return (0, import_node_child_process3.fork)(workerPath, [], {
|
|
1859
2404
|
env: workerEnv,
|
|
1860
2405
|
cwd: dataDir,
|
|
1861
2406
|
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
@@ -1876,8 +2421,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1876
2421
|
}
|
|
1877
2422
|
async function startRun(session, prompt) {
|
|
1878
2423
|
const provider = session.provider;
|
|
1879
|
-
const realCommand = commandFor(provider);
|
|
1880
|
-
const runId = (0,
|
|
2424
|
+
const realCommand = await commandFor(provider);
|
|
2425
|
+
const runId = (0, import_node_crypto5.randomUUID)();
|
|
1881
2426
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
1882
2427
|
const run = {
|
|
1883
2428
|
runId,
|
|
@@ -1915,7 +2460,26 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1915
2460
|
let fileOffset = null;
|
|
1916
2461
|
if (session.providerSessionId) {
|
|
1917
2462
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
1918
|
-
if (file) fileOffset = (await
|
|
2463
|
+
if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
|
|
2464
|
+
}
|
|
2465
|
+
let agentArgs = [];
|
|
2466
|
+
if (session.agent) {
|
|
2467
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2468
|
+
if (!bundle) {
|
|
2469
|
+
const failed = saveRun({
|
|
2470
|
+
...run,
|
|
2471
|
+
status: "failed",
|
|
2472
|
+
endedAt: nowIso(),
|
|
2473
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2474
|
+
});
|
|
2475
|
+
const current = index.session(session.id);
|
|
2476
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2477
|
+
return failed;
|
|
2478
|
+
}
|
|
2479
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2480
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2481
|
+
});
|
|
2482
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
1919
2483
|
}
|
|
1920
2484
|
const child = forkWorker();
|
|
1921
2485
|
const liveRun = {
|
|
@@ -2089,7 +2653,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2089
2653
|
resumeSessionId: session.providerSessionId,
|
|
2090
2654
|
shimPath,
|
|
2091
2655
|
realCommand,
|
|
2092
|
-
identity
|
|
2656
|
+
identity,
|
|
2657
|
+
agentArgs
|
|
2093
2658
|
});
|
|
2094
2659
|
return run;
|
|
2095
2660
|
}
|
|
@@ -2108,7 +2673,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2108
2673
|
}, 3e3);
|
|
2109
2674
|
}
|
|
2110
2675
|
async function pruneDriverScratch() {
|
|
2111
|
-
const runsDir =
|
|
2676
|
+
const runsDir = import_node_path9.default.join(
|
|
2112
2677
|
stateDir,
|
|
2113
2678
|
"instances",
|
|
2114
2679
|
"default",
|
|
@@ -2119,12 +2684,13 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2119
2684
|
"claude-runtime",
|
|
2120
2685
|
"runs"
|
|
2121
2686
|
);
|
|
2122
|
-
const entries = await
|
|
2687
|
+
const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
|
|
2123
2688
|
await Promise.all(
|
|
2124
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2689
|
+
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))
|
|
2125
2690
|
);
|
|
2126
2691
|
}
|
|
2127
2692
|
async function probe() {
|
|
2693
|
+
const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
|
|
2128
2694
|
return new Promise((resolve) => {
|
|
2129
2695
|
const child = forkWorker();
|
|
2130
2696
|
let answered = false;
|
|
@@ -2140,8 +2706,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2140
2706
|
answered = true;
|
|
2141
2707
|
clearTimeout(timer);
|
|
2142
2708
|
resolve({
|
|
2143
|
-
claude: message.claude ?? {},
|
|
2144
|
-
codex: message.codex ?? {}
|
|
2709
|
+
claude: { ...message.claude ?? {}, binary: claudeBinary },
|
|
2710
|
+
codex: { ...message.codex ?? {}, binary: codexBinary }
|
|
2145
2711
|
});
|
|
2146
2712
|
}
|
|
2147
2713
|
});
|
|
@@ -2153,7 +2719,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2153
2719
|
});
|
|
2154
2720
|
child.send({
|
|
2155
2721
|
kind: "probe",
|
|
2156
|
-
commands: { claude:
|
|
2722
|
+
commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
|
|
2157
2723
|
quota: options.quota !== false
|
|
2158
2724
|
});
|
|
2159
2725
|
});
|
|
@@ -2161,7 +2727,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2161
2727
|
function newSession(input) {
|
|
2162
2728
|
const at = nowIso();
|
|
2163
2729
|
return {
|
|
2164
|
-
id: (0,
|
|
2730
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
2165
2731
|
provider: input.provider,
|
|
2166
2732
|
providerSessionId: input.providerSessionId,
|
|
2167
2733
|
cwd: input.cwd,
|
|
@@ -2179,17 +2745,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2179
2745
|
function resolveFolder(raw) {
|
|
2180
2746
|
const trimmed = raw.trim();
|
|
2181
2747
|
if (!trimmed) return null;
|
|
2182
|
-
return
|
|
2748
|
+
return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2183
2749
|
}
|
|
2184
2750
|
async function checkFolder(raw) {
|
|
2185
2751
|
const target = resolveFolder(raw);
|
|
2186
2752
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2187
|
-
const stat = await
|
|
2753
|
+
const stat = await import_promises8.default.stat(target).catch(() => null);
|
|
2188
2754
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2189
2755
|
}
|
|
2190
2756
|
async function assertFolder(cwd) {
|
|
2191
|
-
const resolved =
|
|
2192
|
-
const stat = await
|
|
2757
|
+
const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2758
|
+
const stat = await import_promises8.default.stat(resolved).catch(() => null);
|
|
2193
2759
|
if (!stat?.isDirectory()) {
|
|
2194
2760
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2195
2761
|
}
|
|
@@ -2251,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2251
2817
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2252
2818
|
path: folder,
|
|
2253
2819
|
lastUsedAt,
|
|
2254
|
-
exists: (await
|
|
2820
|
+
exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2255
2821
|
}))
|
|
2256
2822
|
);
|
|
2257
2823
|
},
|
|
@@ -2296,7 +2862,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2296
2862
|
async createFolder(raw) {
|
|
2297
2863
|
const target = resolveFolder(raw);
|
|
2298
2864
|
if (!target) throw new Error("Type a folder path first.");
|
|
2299
|
-
await
|
|
2865
|
+
await import_promises8.default.mkdir(target, { recursive: true });
|
|
2300
2866
|
return checkFolder(target);
|
|
2301
2867
|
},
|
|
2302
2868
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2334,31 +2900,35 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2334
2900
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2335
2901
|
};
|
|
2336
2902
|
}
|
|
2337
|
-
const stat = await
|
|
2903
|
+
const stat = await import_promises8.default.stat(file);
|
|
2338
2904
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2339
2905
|
return {
|
|
2340
2906
|
available: false,
|
|
2341
2907
|
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}.`
|
|
2342
2908
|
};
|
|
2343
2909
|
}
|
|
2344
|
-
const content = await
|
|
2345
|
-
return { available: true, fileName:
|
|
2910
|
+
const content = await import_promises8.default.readFile(file, "utf8");
|
|
2911
|
+
return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
|
|
2346
2912
|
},
|
|
2347
2913
|
async start(input) {
|
|
2348
2914
|
const prompt = input.prompt.trim();
|
|
2349
2915
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2350
2916
|
const cwd = await assertFolder(input.cwd);
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
)
|
|
2917
|
+
const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
|
|
2918
|
+
const draft = newSession({
|
|
2919
|
+
provider: input.provider,
|
|
2920
|
+
cwd,
|
|
2921
|
+
title: titleFrom(prompt),
|
|
2922
|
+
model: input.model?.trim() || null,
|
|
2923
|
+
effort: input.effort?.trim() || null,
|
|
2924
|
+
origin: "started",
|
|
2925
|
+
providerSessionId: null
|
|
2926
|
+
});
|
|
2927
|
+
if (bundle) {
|
|
2928
|
+
await agents.saveSessionBundle(draft.id, bundle);
|
|
2929
|
+
draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
|
|
2930
|
+
}
|
|
2931
|
+
const session = saveSession(draft);
|
|
2362
2932
|
const run = await startRun(session, prompt);
|
|
2363
2933
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2364
2934
|
},
|
|
@@ -2427,6 +2997,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2427
2997
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2428
2998
|
return saveSession(next);
|
|
2429
2999
|
},
|
|
3000
|
+
async agentAccess() {
|
|
3001
|
+
return agents.access();
|
|
3002
|
+
},
|
|
3003
|
+
async agentCatalogRpc(request) {
|
|
3004
|
+
return agents.catalogRpc(request);
|
|
3005
|
+
},
|
|
3006
|
+
async agentDetails(agentId) {
|
|
3007
|
+
return agents.details(String(agentId ?? ""));
|
|
3008
|
+
},
|
|
3009
|
+
setMcpBaseUrl(baseUrl) {
|
|
3010
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
3011
|
+
},
|
|
3012
|
+
async handleMcp(secret, body) {
|
|
3013
|
+
return agents.handleMcp(secret, body);
|
|
3014
|
+
},
|
|
2430
3015
|
async dispose() {
|
|
2431
3016
|
for (const liveRun of live.values()) {
|
|
2432
3017
|
liveRun.stopRequested = true;
|
|
@@ -2463,7 +3048,7 @@ function openFolderIn(folder, target) {
|
|
|
2463
3048
|
args = ["-a", app.app, folder];
|
|
2464
3049
|
}
|
|
2465
3050
|
return new Promise((resolve, reject) => {
|
|
2466
|
-
(0,
|
|
3051
|
+
(0, import_node_child_process3.execFile)(command, args, { timeout: 1e4 }, (error) => {
|
|
2467
3052
|
if (!error) return resolve();
|
|
2468
3053
|
const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
|
|
2469
3054
|
reject(
|