@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.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/engine/index.ts
|
|
2
|
-
import { execFile as
|
|
3
|
-
import { randomUUID as
|
|
4
|
-
import {
|
|
2
|
+
import { execFile as execFile3, fork } from "child_process";
|
|
3
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
5
|
import { formatFileSize } from "@ai-matrx/kit/format";
|
|
6
6
|
|
|
7
7
|
// src/engine/raw-backup.ts
|
|
@@ -98,10 +98,379 @@ function createAiMatrxRawBackupSink(options) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// src/engine/agent-attach.ts
|
|
102
|
+
import fs2 from "fs/promises";
|
|
103
|
+
import path from "path";
|
|
104
|
+
var AGENT_MCP_SERVER_NAME = "matrx-agent";
|
|
105
|
+
var AGENT_PLUGIN_NAME = "matrx-agent";
|
|
106
|
+
function escapeAttr(value) {
|
|
107
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
108
|
+
}
|
|
109
|
+
function versionWords(bundle) {
|
|
110
|
+
return bundle.version_number !== null ? String(bundle.version_number) : "latest";
|
|
111
|
+
}
|
|
112
|
+
function renderAgentInstructions(bundle, options = {}) {
|
|
113
|
+
const included = bundle.skills.filter((skill) => skill.tier === "included");
|
|
114
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
115
|
+
const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
|
|
116
|
+
if (included.length > 0) {
|
|
117
|
+
parts.push(
|
|
118
|
+
"## Skills (always in effect)",
|
|
119
|
+
...included.map((skill) => `### ${skill.label}
|
|
120
|
+
|
|
121
|
+
${skill.body.trim()}`)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (options.claudeSkills && listed2.length > 0) {
|
|
125
|
+
parts.push(
|
|
126
|
+
[
|
|
127
|
+
"## Your skills",
|
|
128
|
+
"When a request matches one of these, load it with your Skill tool and follow it before answering:",
|
|
129
|
+
...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
|
|
130
|
+
].join("\n")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
|
|
134
|
+
parts.push(bundle.instructions.trim());
|
|
135
|
+
parts.push("</ai_matrx_agent>");
|
|
136
|
+
return `${parts.filter(Boolean).join("\n\n")}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
function renderCodexSkillsList(bundle, skillsRoot) {
|
|
140
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
141
|
+
if (listed2.length === 0) return "";
|
|
142
|
+
const lines = [
|
|
143
|
+
"<skills_instructions>",
|
|
144
|
+
"## AI Matrx agent skills",
|
|
145
|
+
"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.",
|
|
146
|
+
"### Available skills",
|
|
147
|
+
...listed2.map(
|
|
148
|
+
(skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${path.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
149
|
+
),
|
|
150
|
+
"</skills_instructions>"
|
|
151
|
+
];
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
function tomlString(value) {
|
|
155
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
156
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
157
|
+
}
|
|
158
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
159
|
+
const pluginDir = path.join(dir, "plugin");
|
|
160
|
+
const skillsRoot = path.join(dir, "skills");
|
|
161
|
+
await fs2.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
162
|
+
await fs2.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
163
|
+
const instructions = renderAgentInstructions(bundle);
|
|
164
|
+
const instructionsFile = path.join(dir, "instructions.md");
|
|
165
|
+
await fs2.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
166
|
+
await fs2.writeFile(
|
|
167
|
+
path.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
168
|
+
`${JSON.stringify(
|
|
169
|
+
{
|
|
170
|
+
name: AGENT_PLUGIN_NAME,
|
|
171
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
172
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
173
|
+
},
|
|
174
|
+
null,
|
|
175
|
+
2
|
|
176
|
+
)}
|
|
177
|
+
`,
|
|
178
|
+
{ mode: 384 }
|
|
179
|
+
);
|
|
180
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
181
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
182
|
+
for (const root of [path.join(pluginDir, "skills"), skillsRoot]) {
|
|
183
|
+
await fs2.mkdir(root, { recursive: true, mode: 448 });
|
|
184
|
+
for (const existing of await fs2.readdir(root).catch(() => [])) {
|
|
185
|
+
if (!wanted.has(existing)) await fs2.rm(path.join(root, existing), { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
for (const skill of listed2) {
|
|
188
|
+
const skillDir = path.join(root, skill.slug);
|
|
189
|
+
await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
190
|
+
await fs2.writeFile(path.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
let mcpConfigFile = null;
|
|
194
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
195
|
+
const mcpPath = path.join(dir, "mcp.json");
|
|
196
|
+
if (mcpUrl) {
|
|
197
|
+
mcpConfigFile = mcpPath;
|
|
198
|
+
await fs2.writeFile(
|
|
199
|
+
mcpPath,
|
|
200
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
201
|
+
`,
|
|
202
|
+
{ mode: 384 }
|
|
203
|
+
);
|
|
204
|
+
} else {
|
|
205
|
+
await fs2.rm(mcpPath, { force: true });
|
|
206
|
+
}
|
|
207
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
208
|
+
return {
|
|
209
|
+
dir,
|
|
210
|
+
instructionsFile,
|
|
211
|
+
pluginDir,
|
|
212
|
+
mcpConfigFile,
|
|
213
|
+
mcpUrl,
|
|
214
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
215
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
216
|
+
|
|
217
|
+
${instructions}` : instructions
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function agentArgsFor(provider, attachment, options) {
|
|
221
|
+
if (provider === "claude") {
|
|
222
|
+
const args2 = [
|
|
223
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
224
|
+
"--append-system-prompt-file",
|
|
225
|
+
attachment.instructionsFile,
|
|
226
|
+
"--system-prompt-snapshot",
|
|
227
|
+
"off",
|
|
228
|
+
"--plugin-dir",
|
|
229
|
+
attachment.pluginDir
|
|
230
|
+
];
|
|
231
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
232
|
+
return args2;
|
|
233
|
+
}
|
|
234
|
+
const args = [];
|
|
235
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
236
|
+
if (attachment.mcpUrl) {
|
|
237
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
238
|
+
args.push(
|
|
239
|
+
"-c",
|
|
240
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
241
|
+
"-c",
|
|
242
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return args;
|
|
246
|
+
}
|
|
247
|
+
function sessionAgentRecord(bundle, options) {
|
|
248
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
249
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
250
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
251
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
252
|
+
for (const tool of bundle.tools) {
|
|
253
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
257
|
+
const loaded = [
|
|
258
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
259
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
260
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
261
|
+
];
|
|
262
|
+
return {
|
|
263
|
+
agentId: bundle.agent_id,
|
|
264
|
+
versionId: bundle.version_id,
|
|
265
|
+
versionNumber: bundle.version_number,
|
|
266
|
+
name: bundle.name,
|
|
267
|
+
definitionHash: bundle.definition_hash,
|
|
268
|
+
loaded,
|
|
269
|
+
unavailable
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/engine/agents.ts
|
|
274
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
275
|
+
import fs3 from "fs/promises";
|
|
276
|
+
import path2 from "path";
|
|
277
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
278
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
279
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
280
|
+
"agx_get_list_full",
|
|
281
|
+
"agx_search",
|
|
282
|
+
"agx_resolve_agent_address",
|
|
283
|
+
"ues_get_bulk",
|
|
284
|
+
"ues_list",
|
|
285
|
+
"ues_set",
|
|
286
|
+
"ues_touch"
|
|
287
|
+
]);
|
|
288
|
+
var agentsClient = async () => {
|
|
289
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
290
|
+
import("@ai-matrx/agents/matrx"),
|
|
291
|
+
import("@ai-matrx/agents/portable"),
|
|
292
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
293
|
+
]);
|
|
294
|
+
return { ...matrx, ...portable, ...mcp };
|
|
295
|
+
};
|
|
296
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
297
|
+
function userIdOf(credential) {
|
|
298
|
+
if (!credential || credential.kind !== "user") return null;
|
|
299
|
+
const payload = credential.accessToken.split(".")[1];
|
|
300
|
+
if (!payload) return null;
|
|
301
|
+
try {
|
|
302
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
303
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function createAgentConnection(options, dataDir) {
|
|
309
|
+
const credentials = options?.credentials ?? null;
|
|
310
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
311
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
312
|
+
const supabaseKey = options?.supabaseKey ?? null;
|
|
313
|
+
const root = path2.join(dataDir, "agents");
|
|
314
|
+
let mcpBaseUrl = null;
|
|
315
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
316
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
317
|
+
const organizationId = () => {
|
|
318
|
+
const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
|
|
319
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
320
|
+
};
|
|
321
|
+
const transport = async () => (await agentsClient()).createMatrxTransport({
|
|
322
|
+
baseUrl: serverUrl,
|
|
323
|
+
...credentials ? { credentials } : {},
|
|
324
|
+
...organizationId() ? { organizationId: () => organizationId() } : {},
|
|
325
|
+
source: "codingSessionsAgents"
|
|
326
|
+
});
|
|
327
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
328
|
+
const requireSignedIn = async () => {
|
|
329
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
330
|
+
};
|
|
331
|
+
const sessionDir = (sessionId) => path2.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
332
|
+
async function meta(sessionId) {
|
|
333
|
+
const file = path2.join(sessionDir(sessionId), "session.json");
|
|
334
|
+
const existing = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
335
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
336
|
+
const created = { mcpSecret: randomBytes(24).toString("base64url"), conversationId: randomUUID() };
|
|
337
|
+
await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
338
|
+
await fs3.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
339
|
+
return created;
|
|
340
|
+
}
|
|
341
|
+
async function findSessionBySecret(secret) {
|
|
342
|
+
for (const name of await fs3.readdir(root).catch(() => [])) {
|
|
343
|
+
const found = await fs3.readFile(path2.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
|
|
344
|
+
if (found) {
|
|
345
|
+
secretToSession.set(secret, name);
|
|
346
|
+
return name;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
async function readSessionBundle(sessionId) {
|
|
352
|
+
return fs3.readFile(path2.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
async access() {
|
|
356
|
+
const current = await credential().catch(() => null);
|
|
357
|
+
const userId = userIdOf(current);
|
|
358
|
+
return {
|
|
359
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
360
|
+
signedIn: userId !== null,
|
|
361
|
+
userId,
|
|
362
|
+
organizationId: organizationId()
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
async catalogRpc(request) {
|
|
366
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
367
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
368
|
+
}
|
|
369
|
+
const current = await credential();
|
|
370
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
371
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
372
|
+
const query = new URLSearchParams();
|
|
373
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
374
|
+
if (request.range) {
|
|
375
|
+
query.set("offset", String(request.range[0]));
|
|
376
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
377
|
+
}
|
|
378
|
+
const headers = {
|
|
379
|
+
apikey: supabaseKey,
|
|
380
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
381
|
+
"Content-Type": "application/json",
|
|
382
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
383
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
384
|
+
"Content-Profile": "public",
|
|
385
|
+
"Accept-Profile": "public"
|
|
386
|
+
};
|
|
387
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
388
|
+
const qs = query.toString();
|
|
389
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers,
|
|
392
|
+
body: JSON.stringify(request.args ?? {})
|
|
393
|
+
});
|
|
394
|
+
const text = await response.text();
|
|
395
|
+
const body = text ? JSON.parse(text) : null;
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
const err = body ?? {};
|
|
398
|
+
return {
|
|
399
|
+
data: null,
|
|
400
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const range = response.headers.get("content-range");
|
|
404
|
+
const total = range?.split("/")[1];
|
|
405
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
406
|
+
},
|
|
407
|
+
async details(agentId) {
|
|
408
|
+
await requireSignedIn();
|
|
409
|
+
const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
|
|
410
|
+
const [bundle, versions] = await Promise.all([
|
|
411
|
+
fetchPortableAgent(await transport(), { agentId }),
|
|
412
|
+
fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
|
|
413
|
+
]);
|
|
414
|
+
return {
|
|
415
|
+
agentId: bundle.agent_id,
|
|
416
|
+
name: bundle.name,
|
|
417
|
+
variables: bundle.variables,
|
|
418
|
+
versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
|
|
419
|
+
latestVersionNumber: bundle.version_number
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
async loadBundle(selection) {
|
|
423
|
+
await requireSignedIn();
|
|
424
|
+
const { fetchPortableAgent } = await agentsClient();
|
|
425
|
+
return fetchPortableAgent(
|
|
426
|
+
await transport(),
|
|
427
|
+
selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
|
|
428
|
+
{ variables: selection.variables ?? {} }
|
|
429
|
+
);
|
|
430
|
+
},
|
|
431
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
432
|
+
await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
433
|
+
await fs3.writeFile(path2.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
434
|
+
await meta(sessionId);
|
|
435
|
+
},
|
|
436
|
+
readSessionBundle,
|
|
437
|
+
sessionDir,
|
|
438
|
+
async mcpUrlFor(sessionId) {
|
|
439
|
+
if (!mcpBaseUrl) return null;
|
|
440
|
+
const { mcpSecret } = await meta(sessionId);
|
|
441
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
442
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
443
|
+
},
|
|
444
|
+
setMcpBaseUrl(baseUrl) {
|
|
445
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
446
|
+
},
|
|
447
|
+
async handleMcp(secret, body) {
|
|
448
|
+
let handler = handlers.get(secret);
|
|
449
|
+
if (!handler) {
|
|
450
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
451
|
+
if (!sessionId) return null;
|
|
452
|
+
const bundle = await readSessionBundle(sessionId);
|
|
453
|
+
if (!bundle) return null;
|
|
454
|
+
const { conversationId } = await meta(sessionId);
|
|
455
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
456
|
+
handler = createAgentToolsMcpHandler({
|
|
457
|
+
bundle,
|
|
458
|
+
credentials,
|
|
459
|
+
organizationId: () => organizationId(),
|
|
460
|
+
baseUrl: serverUrl,
|
|
461
|
+
conversationId
|
|
462
|
+
});
|
|
463
|
+
handlers.set(secret, handler);
|
|
464
|
+
}
|
|
465
|
+
return handler.handle(body);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
101
470
|
// src/engine/index.ts
|
|
102
|
-
import
|
|
103
|
-
import
|
|
104
|
-
import
|
|
471
|
+
import fs8 from "fs/promises";
|
|
472
|
+
import os5 from "os";
|
|
473
|
+
import path9 from "path";
|
|
105
474
|
import { fileURLToPath } from "url";
|
|
106
475
|
|
|
107
476
|
// src/normalize/claude.ts
|
|
@@ -767,9 +1136,177 @@ function describeProblem(provider, problem) {
|
|
|
767
1136
|
};
|
|
768
1137
|
}
|
|
769
1138
|
|
|
770
|
-
// src/engine/
|
|
1139
|
+
// src/engine/binaries.ts
|
|
1140
|
+
import { execFile } from "child_process";
|
|
1141
|
+
import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
|
|
771
1142
|
import os from "os";
|
|
772
|
-
import
|
|
1143
|
+
import path3 from "path";
|
|
1144
|
+
var SOURCE_LABEL = {
|
|
1145
|
+
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
1146
|
+
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
1147
|
+
};
|
|
1148
|
+
function sourceLabel(provider, source) {
|
|
1149
|
+
return SOURCE_LABEL[provider][source];
|
|
1150
|
+
}
|
|
1151
|
+
function parseVersion(text) {
|
|
1152
|
+
const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
|
|
1153
|
+
return match ? match[0] : null;
|
|
1154
|
+
}
|
|
1155
|
+
function compareVersions(a, b) {
|
|
1156
|
+
const [aCore = "", aPre] = a.split("-", 2);
|
|
1157
|
+
const [bCore = "", bPre] = b.split("-", 2);
|
|
1158
|
+
const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1159
|
+
const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1160
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1161
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1162
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1163
|
+
}
|
|
1164
|
+
if (aPre && !bPre) return -1;
|
|
1165
|
+
if (!aPre && bPre) return 1;
|
|
1166
|
+
return 0;
|
|
1167
|
+
}
|
|
1168
|
+
function pickBinary(candidates) {
|
|
1169
|
+
let best = null;
|
|
1170
|
+
for (const c of candidates) {
|
|
1171
|
+
if (!best) {
|
|
1172
|
+
best = c;
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
|
|
1176
|
+
}
|
|
1177
|
+
return best;
|
|
1178
|
+
}
|
|
1179
|
+
function executable(file) {
|
|
1180
|
+
try {
|
|
1181
|
+
accessSync(file, fsConstants.X_OK);
|
|
1182
|
+
return true;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function exeName(name, platform) {
|
|
1188
|
+
return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
|
|
1189
|
+
}
|
|
1190
|
+
function allOnPath(name, pathValue, platform = process.platform) {
|
|
1191
|
+
const found = [];
|
|
1192
|
+
for (const dir of pathValue.split(path3.delimiter)) {
|
|
1193
|
+
if (!dir) continue;
|
|
1194
|
+
for (const file of exeName(name, platform)) {
|
|
1195
|
+
const candidate = path3.join(dir, file);
|
|
1196
|
+
if (executable(candidate)) {
|
|
1197
|
+
found.push(candidate);
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return found;
|
|
1203
|
+
}
|
|
1204
|
+
function desktopAppCandidates(provider, platform, home, applications) {
|
|
1205
|
+
if (platform !== "darwin") return [];
|
|
1206
|
+
if (provider === "codex") {
|
|
1207
|
+
return applications.map(
|
|
1208
|
+
(apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1212
|
+
let versions = [];
|
|
1213
|
+
try {
|
|
1214
|
+
versions = readdirSync(root);
|
|
1215
|
+
} catch {
|
|
1216
|
+
return [];
|
|
1217
|
+
}
|
|
1218
|
+
return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
1219
|
+
}
|
|
1220
|
+
function npmGlobalPrefix(env) {
|
|
1221
|
+
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
1222
|
+
if (fromEnv) return Promise.resolve(fromEnv);
|
|
1223
|
+
const npm = allOnPath("npm", env.PATH ?? "")[0];
|
|
1224
|
+
if (!npm) return Promise.resolve(null);
|
|
1225
|
+
return new Promise((resolve) => {
|
|
1226
|
+
const child = execFile(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
|
|
1227
|
+
resolve(error ? null : String(stdout).trim() || null);
|
|
1228
|
+
});
|
|
1229
|
+
child.stdin?.end();
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function declaredVersion(provider, source, file) {
|
|
1233
|
+
if (source !== "desktop-app") return null;
|
|
1234
|
+
if (provider === "codex") {
|
|
1235
|
+
try {
|
|
1236
|
+
const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
|
|
1237
|
+
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
1238
|
+
} catch {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
|
|
1243
|
+
}
|
|
1244
|
+
function runVersion(file, env) {
|
|
1245
|
+
return new Promise((resolve) => {
|
|
1246
|
+
const child = execFile(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
|
|
1247
|
+
resolve(error ? null : parseVersion(String(stdout)));
|
|
1248
|
+
});
|
|
1249
|
+
child.stdin?.end();
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
async function discoverBinaries(provider, options) {
|
|
1253
|
+
const platform = options.platform ?? process.platform;
|
|
1254
|
+
const home = options.home ?? options.env.HOME ?? os.homedir();
|
|
1255
|
+
const listed2 = [
|
|
1256
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
1257
|
+
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
1258
|
+
];
|
|
1259
|
+
const prefix = await npmGlobalPrefix(options.env);
|
|
1260
|
+
if (prefix) {
|
|
1261
|
+
const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
|
|
1262
|
+
for (const file of exeName(provider, platform)) {
|
|
1263
|
+
const candidate = path3.join(dir, file);
|
|
1264
|
+
if (executable(candidate)) {
|
|
1265
|
+
listed2.push({ path: candidate, source: "npm-global" });
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1271
|
+
const unique = listed2.filter((c) => {
|
|
1272
|
+
let real = c.path;
|
|
1273
|
+
try {
|
|
1274
|
+
real = realpathSync(c.path);
|
|
1275
|
+
} catch {
|
|
1276
|
+
}
|
|
1277
|
+
if (seen.has(real)) return false;
|
|
1278
|
+
seen.add(real);
|
|
1279
|
+
return true;
|
|
1280
|
+
});
|
|
1281
|
+
return Promise.all(
|
|
1282
|
+
unique.map(async (c) => ({
|
|
1283
|
+
...c,
|
|
1284
|
+
version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
|
|
1285
|
+
}))
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
async function resolveBinary(provider, options) {
|
|
1289
|
+
const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
|
|
1290
|
+
const best = pickBinary(candidates);
|
|
1291
|
+
const inUse = options.forced ? {
|
|
1292
|
+
path: options.forced,
|
|
1293
|
+
source: "configured",
|
|
1294
|
+
version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
|
|
1295
|
+
} : best;
|
|
1296
|
+
if (!inUse) return null;
|
|
1297
|
+
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;
|
|
1298
|
+
return {
|
|
1299
|
+
path: inUse.path,
|
|
1300
|
+
version: inUse.version,
|
|
1301
|
+
source: inUse.source,
|
|
1302
|
+
sourceLabel: sourceLabel(provider, inUse.source),
|
|
1303
|
+
newer
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/engine/clean-env.ts
|
|
1308
|
+
import os2 from "os";
|
|
1309
|
+
import path4 from "path";
|
|
773
1310
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
774
1311
|
"HOME",
|
|
775
1312
|
"USER",
|
|
@@ -791,11 +1328,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
791
1328
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
792
1329
|
function wellKnownBinDirs(home) {
|
|
793
1330
|
return [
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1331
|
+
path4.join(home, ".local", "bin"),
|
|
1332
|
+
path4.join(home, ".claude", "local"),
|
|
1333
|
+
path4.join(home, ".npm-global", "bin"),
|
|
1334
|
+
path4.join(home, ".bun", "bin"),
|
|
1335
|
+
path4.join(home, ".volta", "bin"),
|
|
799
1336
|
"/opt/homebrew/bin",
|
|
800
1337
|
"/usr/local/bin",
|
|
801
1338
|
"/usr/bin",
|
|
@@ -813,31 +1350,31 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
813
1350
|
if (typeof value !== "string") continue;
|
|
814
1351
|
if (isAllowedEnvName(name)) out[name] = value;
|
|
815
1352
|
}
|
|
816
|
-
const home = out.HOME ??
|
|
1353
|
+
const home = out.HOME ?? os2.homedir();
|
|
817
1354
|
out.HOME = home;
|
|
818
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1355
|
+
const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
|
|
819
1356
|
const merged = [];
|
|
820
1357
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
821
1358
|
if (!merged.includes(dir)) merged.push(dir);
|
|
822
1359
|
}
|
|
823
|
-
out.PATH = merged.join(
|
|
1360
|
+
out.PATH = merged.join(path4.delimiter);
|
|
824
1361
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
825
1362
|
return out;
|
|
826
1363
|
}
|
|
827
1364
|
|
|
828
1365
|
// src/engine/files.ts
|
|
829
1366
|
import { createReadStream } from "fs";
|
|
830
|
-
import
|
|
831
|
-
import
|
|
832
|
-
import
|
|
1367
|
+
import fs4 from "fs/promises";
|
|
1368
|
+
import os3 from "os";
|
|
1369
|
+
import path5 from "path";
|
|
833
1370
|
|
|
834
1371
|
// src/engine/activity.ts
|
|
835
|
-
import { execFile } from "child_process";
|
|
1372
|
+
import { execFile as execFile2 } from "child_process";
|
|
836
1373
|
var RECENT_WRITE_MS = 2 * 60 * 1e3;
|
|
837
1374
|
var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
|
|
838
1375
|
function listProcesses() {
|
|
839
1376
|
return new Promise((resolve) => {
|
|
840
|
-
|
|
1377
|
+
execFile2("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
|
|
841
1378
|
if (error) {
|
|
842
1379
|
resolve([]);
|
|
843
1380
|
return;
|
|
@@ -896,17 +1433,17 @@ function describeActivity(input) {
|
|
|
896
1433
|
}
|
|
897
1434
|
|
|
898
1435
|
// src/engine/files.ts
|
|
899
|
-
function defaultProviderHomes(home =
|
|
1436
|
+
function defaultProviderHomes(home = os3.homedir()) {
|
|
900
1437
|
return {
|
|
901
|
-
claudeProjects:
|
|
902
|
-
codexSessions:
|
|
903
|
-
codexIndex:
|
|
1438
|
+
claudeProjects: path5.join(home, ".claude", "projects"),
|
|
1439
|
+
codexSessions: path5.join(home, ".codex", "sessions"),
|
|
1440
|
+
codexIndex: path5.join(home, ".codex", "session_index.jsonl")
|
|
904
1441
|
};
|
|
905
1442
|
}
|
|
906
1443
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
907
1444
|
async function safeReaddir(dir) {
|
|
908
1445
|
try {
|
|
909
|
-
return await
|
|
1446
|
+
return await fs4.readdir(dir, { withFileTypes: true });
|
|
910
1447
|
} catch {
|
|
911
1448
|
return [];
|
|
912
1449
|
}
|
|
@@ -915,7 +1452,7 @@ async function statCandidate(provider, file) {
|
|
|
915
1452
|
const match = UUIDISH.exec(file);
|
|
916
1453
|
if (!match?.[1]) return null;
|
|
917
1454
|
try {
|
|
918
|
-
const stat = await
|
|
1455
|
+
const stat = await fs4.stat(file);
|
|
919
1456
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
920
1457
|
} catch {
|
|
921
1458
|
return null;
|
|
@@ -925,10 +1462,10 @@ async function claudeCandidates(root) {
|
|
|
925
1462
|
const out = [];
|
|
926
1463
|
for (const project of await safeReaddir(root)) {
|
|
927
1464
|
if (!project.isDirectory()) continue;
|
|
928
|
-
const dir =
|
|
1465
|
+
const dir = path5.join(root, project.name);
|
|
929
1466
|
const entries = await safeReaddir(dir);
|
|
930
1467
|
const stats = await Promise.all(
|
|
931
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1468
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path5.join(dir, e.name)))
|
|
932
1469
|
);
|
|
933
1470
|
for (const s of stats) if (s) out.push(s);
|
|
934
1471
|
}
|
|
@@ -938,14 +1475,14 @@ async function codexCandidates(root) {
|
|
|
938
1475
|
const out = [];
|
|
939
1476
|
for (const year of await safeReaddir(root)) {
|
|
940
1477
|
if (!year.isDirectory()) continue;
|
|
941
|
-
for (const month of await safeReaddir(
|
|
1478
|
+
for (const month of await safeReaddir(path5.join(root, year.name))) {
|
|
942
1479
|
if (!month.isDirectory()) continue;
|
|
943
|
-
for (const day of await safeReaddir(
|
|
1480
|
+
for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
|
|
944
1481
|
if (!day.isDirectory()) continue;
|
|
945
|
-
const dir =
|
|
1482
|
+
const dir = path5.join(root, year.name, month.name, day.name);
|
|
946
1483
|
const entries = await safeReaddir(dir);
|
|
947
1484
|
const stats = await Promise.all(
|
|
948
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1485
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
|
|
949
1486
|
);
|
|
950
1487
|
for (const s of stats) if (s) out.push(s);
|
|
951
1488
|
}
|
|
@@ -954,7 +1491,7 @@ async function codexCandidates(root) {
|
|
|
954
1491
|
return out;
|
|
955
1492
|
}
|
|
956
1493
|
async function readSlice(file, start, length) {
|
|
957
|
-
const handle = await
|
|
1494
|
+
const handle = await fs4.open(file, "r");
|
|
958
1495
|
try {
|
|
959
1496
|
const buffer = Buffer.alloc(length);
|
|
960
1497
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1027,9 +1564,9 @@ async function readCodexIndex(file) {
|
|
|
1027
1564
|
const names = /* @__PURE__ */ new Map();
|
|
1028
1565
|
let text;
|
|
1029
1566
|
try {
|
|
1030
|
-
const stat = await
|
|
1567
|
+
const stat = await fs4.stat(file);
|
|
1031
1568
|
const budget = 4 * 1024 * 1024;
|
|
1032
|
-
text = stat.size <= budget ? await
|
|
1569
|
+
text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1033
1570
|
} catch {
|
|
1034
1571
|
return names;
|
|
1035
1572
|
}
|
|
@@ -1150,7 +1687,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1150
1687
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1151
1688
|
let size;
|
|
1152
1689
|
try {
|
|
1153
|
-
size = (await
|
|
1690
|
+
size = (await fs4.stat(file)).size;
|
|
1154
1691
|
} catch {
|
|
1155
1692
|
return {
|
|
1156
1693
|
items: [],
|
|
@@ -1204,9 +1741,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1204
1741
|
}
|
|
1205
1742
|
|
|
1206
1743
|
// src/engine/preferences.ts
|
|
1207
|
-
import { randomUUID } from "crypto";
|
|
1208
|
-
import
|
|
1209
|
-
import
|
|
1744
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1745
|
+
import fs5 from "fs/promises";
|
|
1746
|
+
import path6 from "path";
|
|
1210
1747
|
var PreferenceStore = class {
|
|
1211
1748
|
constructor(dir) {
|
|
1212
1749
|
this.dir = dir;
|
|
@@ -1215,11 +1752,11 @@ var PreferenceStore = class {
|
|
|
1215
1752
|
data = {};
|
|
1216
1753
|
writing = Promise.resolve();
|
|
1217
1754
|
get file() {
|
|
1218
|
-
return
|
|
1755
|
+
return path6.join(this.dir, "preferences.json");
|
|
1219
1756
|
}
|
|
1220
1757
|
async load() {
|
|
1221
1758
|
try {
|
|
1222
|
-
const parsed = JSON.parse(await
|
|
1759
|
+
const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
|
|
1223
1760
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1224
1761
|
} catch {
|
|
1225
1762
|
this.data = {};
|
|
@@ -1257,10 +1794,10 @@ var PreferenceStore = class {
|
|
|
1257
1794
|
persist() {
|
|
1258
1795
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1259
1796
|
this.writing = this.writing.then(async () => {
|
|
1260
|
-
await
|
|
1261
|
-
const tmp = `${this.file}.${
|
|
1262
|
-
await
|
|
1263
|
-
await
|
|
1797
|
+
await fs5.mkdir(this.dir, { recursive: true });
|
|
1798
|
+
const tmp = `${this.file}.${randomUUID2()}.tmp`;
|
|
1799
|
+
await fs5.writeFile(tmp, snapshot, { mode: 384 });
|
|
1800
|
+
await fs5.rename(tmp, this.file);
|
|
1264
1801
|
}).catch(() => {
|
|
1265
1802
|
});
|
|
1266
1803
|
}
|
|
@@ -1417,9 +1954,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1417
1954
|
}
|
|
1418
1955
|
|
|
1419
1956
|
// src/engine/store.ts
|
|
1420
|
-
import { randomUUID as
|
|
1421
|
-
import
|
|
1422
|
-
import
|
|
1957
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1958
|
+
import fs6 from "fs/promises";
|
|
1959
|
+
import path7 from "path";
|
|
1423
1960
|
var MAX_RUNS = 2e3;
|
|
1424
1961
|
var RunIndex = class {
|
|
1425
1962
|
constructor(dir) {
|
|
@@ -1429,12 +1966,12 @@ var RunIndex = class {
|
|
|
1429
1966
|
data = { version: 1, sessions: [], runs: [] };
|
|
1430
1967
|
writing = Promise.resolve();
|
|
1431
1968
|
get file() {
|
|
1432
|
-
return
|
|
1969
|
+
return path7.join(this.dir, "index.json");
|
|
1433
1970
|
}
|
|
1434
1971
|
async load() {
|
|
1435
|
-
await
|
|
1972
|
+
await fs6.mkdir(this.dir, { recursive: true });
|
|
1436
1973
|
try {
|
|
1437
|
-
const parsed = JSON.parse(await
|
|
1974
|
+
const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
|
|
1438
1975
|
this.data = {
|
|
1439
1976
|
version: 1,
|
|
1440
1977
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1476,9 +2013,9 @@ var RunIndex = class {
|
|
|
1476
2013
|
persist() {
|
|
1477
2014
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1478
2015
|
this.writing = this.writing.then(async () => {
|
|
1479
|
-
const tmp = `${this.file}.${
|
|
1480
|
-
await
|
|
1481
|
-
await
|
|
2016
|
+
const tmp = `${this.file}.${randomUUID3()}.tmp`;
|
|
2017
|
+
await fs6.writeFile(tmp, snapshot, { mode: 384 });
|
|
2018
|
+
await fs6.rename(tmp, this.file);
|
|
1482
2019
|
}).catch(() => {
|
|
1483
2020
|
});
|
|
1484
2021
|
}
|
|
@@ -1488,27 +2025,27 @@ var RunIndex = class {
|
|
|
1488
2025
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1489
2026
|
* driver's scratch state is reused rather than growing per run. */
|
|
1490
2027
|
async identity() {
|
|
1491
|
-
const file =
|
|
2028
|
+
const file = path7.join(this.dir, "identity.json");
|
|
1492
2029
|
try {
|
|
1493
|
-
const parsed = JSON.parse(await
|
|
2030
|
+
const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1494
2031
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1495
2032
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1496
2033
|
}
|
|
1497
2034
|
} catch {
|
|
1498
2035
|
}
|
|
1499
|
-
const identity = { companyId:
|
|
1500
|
-
await
|
|
2036
|
+
const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
|
|
2037
|
+
await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1501
2038
|
return identity;
|
|
1502
2039
|
}
|
|
1503
2040
|
};
|
|
1504
2041
|
|
|
1505
2042
|
// src/engine/user-defaults.ts
|
|
1506
|
-
import
|
|
1507
|
-
import
|
|
1508
|
-
import
|
|
2043
|
+
import fs7 from "fs/promises";
|
|
2044
|
+
import os4 from "os";
|
|
2045
|
+
import path8 from "path";
|
|
1509
2046
|
async function readJson(file) {
|
|
1510
2047
|
try {
|
|
1511
|
-
const parsed = JSON.parse(await
|
|
2048
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
1512
2049
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1513
2050
|
} catch {
|
|
1514
2051
|
return null;
|
|
@@ -1517,10 +2054,10 @@ async function readJson(file) {
|
|
|
1517
2054
|
function str3(value) {
|
|
1518
2055
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1519
2056
|
}
|
|
1520
|
-
async function claudeDefaults(cwd, home =
|
|
2057
|
+
async function claudeDefaults(cwd, home = os4.homedir()) {
|
|
1521
2058
|
const files = [
|
|
1522
|
-
...cwd ? [
|
|
1523
|
-
|
|
2059
|
+
...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
|
|
2060
|
+
path8.join(home, ".claude", "settings.json")
|
|
1524
2061
|
];
|
|
1525
2062
|
let model = null;
|
|
1526
2063
|
let effort = null;
|
|
@@ -1533,10 +2070,10 @@ async function claudeDefaults(cwd, home = os3.homedir()) {
|
|
|
1533
2070
|
}
|
|
1534
2071
|
return { model, effort };
|
|
1535
2072
|
}
|
|
1536
|
-
async function codexDefaults(home =
|
|
2073
|
+
async function codexDefaults(home = os4.homedir()) {
|
|
1537
2074
|
let text;
|
|
1538
2075
|
try {
|
|
1539
|
-
text = await
|
|
2076
|
+
text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
|
|
1540
2077
|
} catch {
|
|
1541
2078
|
return { model: null, effort: null };
|
|
1542
2079
|
}
|
|
@@ -1560,45 +2097,33 @@ var EFFORT_LEVELS = {
|
|
|
1560
2097
|
// src/engine/index.ts
|
|
1561
2098
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1562
2099
|
function moduleDir() {
|
|
1563
|
-
return
|
|
2100
|
+
return path9.dirname(fileURLToPath(import.meta.url));
|
|
1564
2101
|
}
|
|
1565
2102
|
function resolveWorkerPath() {
|
|
1566
2103
|
const dir = moduleDir();
|
|
1567
2104
|
for (const candidate of [
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
2105
|
+
path9.join(dir, "worker.js"),
|
|
2106
|
+
path9.join(dir, "worker.ts"),
|
|
2107
|
+
path9.join(dir, "..", "engine", "worker.js"),
|
|
2108
|
+
path9.join(dir, "..", "engine", "worker.ts")
|
|
1572
2109
|
]) {
|
|
1573
2110
|
if (existsSync(candidate)) return candidate;
|
|
1574
2111
|
}
|
|
1575
2112
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1576
2113
|
}
|
|
1577
2114
|
async function installShim(dataDir) {
|
|
1578
|
-
const source =
|
|
2115
|
+
const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
1579
2116
|
if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
1580
|
-
const body = await
|
|
1581
|
-
const target =
|
|
1582
|
-
const current = await
|
|
2117
|
+
const body = await fs8.readFile(source, "utf8");
|
|
2118
|
+
const target = path9.join(dataDir, "bin", "cli-shim.sh");
|
|
2119
|
+
const current = await fs8.readFile(target, "utf8").catch(() => null);
|
|
1583
2120
|
if (current !== body) {
|
|
1584
|
-
await
|
|
1585
|
-
await
|
|
2121
|
+
await fs8.mkdir(path9.dirname(target), { recursive: true });
|
|
2122
|
+
await fs8.writeFile(target, body, { mode: 493 });
|
|
1586
2123
|
}
|
|
1587
|
-
await
|
|
2124
|
+
await fs8.chmod(target, 493);
|
|
1588
2125
|
return target;
|
|
1589
2126
|
}
|
|
1590
|
-
function whichOnPath(name, pathValue) {
|
|
1591
|
-
for (const dir of pathValue.split(path6.delimiter)) {
|
|
1592
|
-
if (!dir) continue;
|
|
1593
|
-
const candidate = path6.join(dir, name);
|
|
1594
|
-
try {
|
|
1595
|
-
accessSync(candidate, fsConstants.X_OK);
|
|
1596
|
-
return candidate;
|
|
1597
|
-
} catch {
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
return null;
|
|
1601
|
-
}
|
|
1602
2127
|
function nowIso() {
|
|
1603
2128
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1604
2129
|
}
|
|
@@ -1637,12 +2162,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
|
|
|
1637
2162
|
function describeAuthStatus(provider, probe) {
|
|
1638
2163
|
const label = PROVIDER_LABEL[provider];
|
|
1639
2164
|
const installed = probe?.installed === true;
|
|
1640
|
-
const
|
|
2165
|
+
const binary = probe?.binary ?? null;
|
|
2166
|
+
const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
|
|
1641
2167
|
const base = {
|
|
1642
2168
|
provider,
|
|
1643
2169
|
label,
|
|
1644
2170
|
installed,
|
|
1645
2171
|
version,
|
|
2172
|
+
binary,
|
|
1646
2173
|
loggedIn: false,
|
|
1647
2174
|
billing: "unknown",
|
|
1648
2175
|
plan: null,
|
|
@@ -1694,29 +2221,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1694
2221
|
};
|
|
1695
2222
|
}
|
|
1696
2223
|
async function createCodingSessionsEngine(options = {}) {
|
|
1697
|
-
const dataDir = options.dataDir ??
|
|
1698
|
-
const stateDir =
|
|
2224
|
+
const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
|
|
2225
|
+
const stateDir = path9.join(dataDir, "paperclip-state");
|
|
1699
2226
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1700
2227
|
const index = new RunIndex(dataDir);
|
|
1701
2228
|
await index.load();
|
|
1702
2229
|
const preferences = new PreferenceStore(dataDir);
|
|
1703
2230
|
await preferences.load();
|
|
1704
|
-
await
|
|
2231
|
+
await fs8.mkdir(stateDir, { recursive: true });
|
|
1705
2232
|
const identity = await index.identity();
|
|
2233
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1706
2234
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1707
2235
|
sink: options.rawBackup,
|
|
1708
2236
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1709
2237
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1710
2238
|
}) : null;
|
|
1711
2239
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1712
|
-
const healthFile =
|
|
2240
|
+
const healthFile = path9.join(dataDir, "provider-health.json");
|
|
1713
2241
|
let health = {};
|
|
1714
2242
|
try {
|
|
1715
|
-
health = JSON.parse(await
|
|
2243
|
+
health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
|
|
1716
2244
|
} catch {
|
|
1717
2245
|
health = {};
|
|
1718
2246
|
}
|
|
1719
|
-
const saveHealth = () =>
|
|
2247
|
+
const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1720
2248
|
function noteRunOutcome(provider, status, texts) {
|
|
1721
2249
|
if (status === "completed") {
|
|
1722
2250
|
if (health[provider]) {
|
|
@@ -1744,7 +2272,24 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1744
2272
|
// Marks the forked process as a driver worker (see worker.ts).
|
|
1745
2273
|
MATRX_CODING_SESSIONS_WORKER: "1"
|
|
1746
2274
|
});
|
|
1747
|
-
const
|
|
2275
|
+
const binaryHome = options.hostEnv?.HOME ?? os5.homedir();
|
|
2276
|
+
const binaryCache = /* @__PURE__ */ new Map();
|
|
2277
|
+
function binaryFor(provider, fresh = false) {
|
|
2278
|
+
const cached = binaryCache.get(provider);
|
|
2279
|
+
if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
|
|
2280
|
+
const forced = options.commands?.[provider];
|
|
2281
|
+
const value = resolveBinary(provider, {
|
|
2282
|
+
env: workerEnv,
|
|
2283
|
+
home: binaryHome,
|
|
2284
|
+
forced,
|
|
2285
|
+
// A forced command needs no discovery to run; the status check still
|
|
2286
|
+
// looks, so it can say when a newer copy exists.
|
|
2287
|
+
discover: !forced || fresh
|
|
2288
|
+
}).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
|
|
2289
|
+
if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
|
|
2290
|
+
return value;
|
|
2291
|
+
}
|
|
2292
|
+
const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
|
|
1748
2293
|
const workerPath = resolveWorkerPath();
|
|
1749
2294
|
const shimPath = await installShim(dataDir);
|
|
1750
2295
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1815,8 +2360,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1815
2360
|
}
|
|
1816
2361
|
async function startRun(session, prompt) {
|
|
1817
2362
|
const provider = session.provider;
|
|
1818
|
-
const realCommand = commandFor(provider);
|
|
1819
|
-
const runId =
|
|
2363
|
+
const realCommand = await commandFor(provider);
|
|
2364
|
+
const runId = randomUUID4();
|
|
1820
2365
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
1821
2366
|
const run = {
|
|
1822
2367
|
runId,
|
|
@@ -1854,7 +2399,26 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1854
2399
|
let fileOffset = null;
|
|
1855
2400
|
if (session.providerSessionId) {
|
|
1856
2401
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
1857
|
-
if (file) fileOffset = (await
|
|
2402
|
+
if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
|
|
2403
|
+
}
|
|
2404
|
+
let agentArgs = [];
|
|
2405
|
+
if (session.agent) {
|
|
2406
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2407
|
+
if (!bundle) {
|
|
2408
|
+
const failed = saveRun({
|
|
2409
|
+
...run,
|
|
2410
|
+
status: "failed",
|
|
2411
|
+
endedAt: nowIso(),
|
|
2412
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2413
|
+
});
|
|
2414
|
+
const current = index.session(session.id);
|
|
2415
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2416
|
+
return failed;
|
|
2417
|
+
}
|
|
2418
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2419
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2420
|
+
});
|
|
2421
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
1858
2422
|
}
|
|
1859
2423
|
const child = forkWorker();
|
|
1860
2424
|
const liveRun = {
|
|
@@ -2028,7 +2592,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2028
2592
|
resumeSessionId: session.providerSessionId,
|
|
2029
2593
|
shimPath,
|
|
2030
2594
|
realCommand,
|
|
2031
|
-
identity
|
|
2595
|
+
identity,
|
|
2596
|
+
agentArgs
|
|
2032
2597
|
});
|
|
2033
2598
|
return run;
|
|
2034
2599
|
}
|
|
@@ -2047,7 +2612,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2047
2612
|
}, 3e3);
|
|
2048
2613
|
}
|
|
2049
2614
|
async function pruneDriverScratch() {
|
|
2050
|
-
const runsDir =
|
|
2615
|
+
const runsDir = path9.join(
|
|
2051
2616
|
stateDir,
|
|
2052
2617
|
"instances",
|
|
2053
2618
|
"default",
|
|
@@ -2058,12 +2623,13 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2058
2623
|
"claude-runtime",
|
|
2059
2624
|
"runs"
|
|
2060
2625
|
);
|
|
2061
|
-
const entries = await
|
|
2626
|
+
const entries = await fs8.readdir(runsDir).catch(() => []);
|
|
2062
2627
|
await Promise.all(
|
|
2063
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2628
|
+
entries.filter((name) => !live.has(name)).map((name) => fs8.rm(path9.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
|
|
2064
2629
|
);
|
|
2065
2630
|
}
|
|
2066
2631
|
async function probe() {
|
|
2632
|
+
const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
|
|
2067
2633
|
return new Promise((resolve) => {
|
|
2068
2634
|
const child = forkWorker();
|
|
2069
2635
|
let answered = false;
|
|
@@ -2079,8 +2645,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2079
2645
|
answered = true;
|
|
2080
2646
|
clearTimeout(timer);
|
|
2081
2647
|
resolve({
|
|
2082
|
-
claude: message.claude ?? {},
|
|
2083
|
-
codex: message.codex ?? {}
|
|
2648
|
+
claude: { ...message.claude ?? {}, binary: claudeBinary },
|
|
2649
|
+
codex: { ...message.codex ?? {}, binary: codexBinary }
|
|
2084
2650
|
});
|
|
2085
2651
|
}
|
|
2086
2652
|
});
|
|
@@ -2092,7 +2658,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2092
2658
|
});
|
|
2093
2659
|
child.send({
|
|
2094
2660
|
kind: "probe",
|
|
2095
|
-
commands: { claude:
|
|
2661
|
+
commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
|
|
2096
2662
|
quota: options.quota !== false
|
|
2097
2663
|
});
|
|
2098
2664
|
});
|
|
@@ -2100,7 +2666,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2100
2666
|
function newSession(input) {
|
|
2101
2667
|
const at = nowIso();
|
|
2102
2668
|
return {
|
|
2103
|
-
id:
|
|
2669
|
+
id: randomUUID4(),
|
|
2104
2670
|
provider: input.provider,
|
|
2105
2671
|
providerSessionId: input.providerSessionId,
|
|
2106
2672
|
cwd: input.cwd,
|
|
@@ -2118,17 +2684,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2118
2684
|
function resolveFolder(raw) {
|
|
2119
2685
|
const trimmed = raw.trim();
|
|
2120
2686
|
if (!trimmed) return null;
|
|
2121
|
-
return
|
|
2687
|
+
return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2122
2688
|
}
|
|
2123
2689
|
async function checkFolder(raw) {
|
|
2124
2690
|
const target = resolveFolder(raw);
|
|
2125
2691
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2126
|
-
const stat = await
|
|
2692
|
+
const stat = await fs8.stat(target).catch(() => null);
|
|
2127
2693
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2128
2694
|
}
|
|
2129
2695
|
async function assertFolder(cwd) {
|
|
2130
|
-
const resolved =
|
|
2131
|
-
const stat = await
|
|
2696
|
+
const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2697
|
+
const stat = await fs8.stat(resolved).catch(() => null);
|
|
2132
2698
|
if (!stat?.isDirectory()) {
|
|
2133
2699
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2134
2700
|
}
|
|
@@ -2190,7 +2756,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2190
2756
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2191
2757
|
path: folder,
|
|
2192
2758
|
lastUsedAt,
|
|
2193
|
-
exists: (await
|
|
2759
|
+
exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2194
2760
|
}))
|
|
2195
2761
|
);
|
|
2196
2762
|
},
|
|
@@ -2235,7 +2801,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2235
2801
|
async createFolder(raw) {
|
|
2236
2802
|
const target = resolveFolder(raw);
|
|
2237
2803
|
if (!target) throw new Error("Type a folder path first.");
|
|
2238
|
-
await
|
|
2804
|
+
await fs8.mkdir(target, { recursive: true });
|
|
2239
2805
|
return checkFolder(target);
|
|
2240
2806
|
},
|
|
2241
2807
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2273,31 +2839,35 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2273
2839
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2274
2840
|
};
|
|
2275
2841
|
}
|
|
2276
|
-
const stat = await
|
|
2842
|
+
const stat = await fs8.stat(file);
|
|
2277
2843
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2278
2844
|
return {
|
|
2279
2845
|
available: false,
|
|
2280
2846
|
reason: `This transcript is ${formatFileSize(stat.size)}, larger than the ${formatFileSize(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
|
|
2281
2847
|
};
|
|
2282
2848
|
}
|
|
2283
|
-
const content = await
|
|
2284
|
-
return { available: true, fileName:
|
|
2849
|
+
const content = await fs8.readFile(file, "utf8");
|
|
2850
|
+
return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
|
|
2285
2851
|
},
|
|
2286
2852
|
async start(input) {
|
|
2287
2853
|
const prompt = input.prompt.trim();
|
|
2288
2854
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2289
2855
|
const cwd = await assertFolder(input.cwd);
|
|
2290
|
-
const
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
)
|
|
2856
|
+
const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
|
|
2857
|
+
const draft = newSession({
|
|
2858
|
+
provider: input.provider,
|
|
2859
|
+
cwd,
|
|
2860
|
+
title: titleFrom(prompt),
|
|
2861
|
+
model: input.model?.trim() || null,
|
|
2862
|
+
effort: input.effort?.trim() || null,
|
|
2863
|
+
origin: "started",
|
|
2864
|
+
providerSessionId: null
|
|
2865
|
+
});
|
|
2866
|
+
if (bundle) {
|
|
2867
|
+
await agents.saveSessionBundle(draft.id, bundle);
|
|
2868
|
+
draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
|
|
2869
|
+
}
|
|
2870
|
+
const session = saveSession(draft);
|
|
2301
2871
|
const run = await startRun(session, prompt);
|
|
2302
2872
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2303
2873
|
},
|
|
@@ -2366,6 +2936,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2366
2936
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2367
2937
|
return saveSession(next);
|
|
2368
2938
|
},
|
|
2939
|
+
async agentAccess() {
|
|
2940
|
+
return agents.access();
|
|
2941
|
+
},
|
|
2942
|
+
async agentCatalogRpc(request) {
|
|
2943
|
+
return agents.catalogRpc(request);
|
|
2944
|
+
},
|
|
2945
|
+
async agentDetails(agentId) {
|
|
2946
|
+
return agents.details(String(agentId ?? ""));
|
|
2947
|
+
},
|
|
2948
|
+
setMcpBaseUrl(baseUrl) {
|
|
2949
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
2950
|
+
},
|
|
2951
|
+
async handleMcp(secret, body) {
|
|
2952
|
+
return agents.handleMcp(secret, body);
|
|
2953
|
+
},
|
|
2369
2954
|
async dispose() {
|
|
2370
2955
|
for (const liveRun of live.values()) {
|
|
2371
2956
|
liveRun.stopRequested = true;
|
|
@@ -2402,7 +2987,7 @@ function openFolderIn(folder, target) {
|
|
|
2402
2987
|
args = ["-a", app.app, folder];
|
|
2403
2988
|
}
|
|
2404
2989
|
return new Promise((resolve, reject) => {
|
|
2405
|
-
|
|
2990
|
+
execFile3(command, args, { timeout: 1e4 }, (error) => {
|
|
2406
2991
|
if (!error) return resolve();
|
|
2407
2992
|
const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
|
|
2408
2993
|
reject(
|