@ai-matrx/coding-sessions 0.1.83 → 0.1.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/engine/index.cjs +521 -112
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -1
- package/dist/engine/index.d.ts +4 -1
- package/dist/engine/index.js +521 -112
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +2 -2
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-DQ0teQvw.d.cts → index-DEPwyr2j.d.cts} +130 -0
- package/dist/{index-DQ0teQvw.d.ts → index-DEPwyr2j.d.ts} +130 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +666 -408
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +87 -7
- package/dist/react/index.d.ts +87 -7
- package/dist/react/index.js +674 -403
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +588 -127
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +4 -1
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.js +587 -126
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/index-DSWoryQZ.js +72 -0
- package/dist/ui/assets/index-Gp2bMDtx.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +4 -2
- package/dist/ui/assets/index-C-_1qjy9.css +0 -2
- package/dist/ui/assets/index-CmFgFvLa.js +0 -69
package/dist/engine/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/engine/index.ts
|
|
2
2
|
import { execFile as execFile3, fork } from "child_process";
|
|
3
|
-
import { randomUUID as
|
|
3
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { formatFileSize } from "@ai-matrx/kit/format";
|
|
6
6
|
|
|
@@ -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
|
|
471
|
+
import fs8 from "fs/promises";
|
|
103
472
|
import os5 from "os";
|
|
104
|
-
import
|
|
473
|
+
import path9 from "path";
|
|
105
474
|
import { fileURLToPath } from "url";
|
|
106
475
|
|
|
107
476
|
// src/normalize/claude.ts
|
|
@@ -771,7 +1140,7 @@ function describeProblem(provider, problem) {
|
|
|
771
1140
|
import { execFile } from "child_process";
|
|
772
1141
|
import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
|
|
773
1142
|
import os from "os";
|
|
774
|
-
import
|
|
1143
|
+
import path3 from "path";
|
|
775
1144
|
var SOURCE_LABEL = {
|
|
776
1145
|
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
777
1146
|
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
@@ -820,10 +1189,10 @@ function exeName(name, platform) {
|
|
|
820
1189
|
}
|
|
821
1190
|
function allOnPath(name, pathValue, platform = process.platform) {
|
|
822
1191
|
const found = [];
|
|
823
|
-
for (const dir of pathValue.split(
|
|
1192
|
+
for (const dir of pathValue.split(path3.delimiter)) {
|
|
824
1193
|
if (!dir) continue;
|
|
825
1194
|
for (const file of exeName(name, platform)) {
|
|
826
|
-
const candidate =
|
|
1195
|
+
const candidate = path3.join(dir, file);
|
|
827
1196
|
if (executable(candidate)) {
|
|
828
1197
|
found.push(candidate);
|
|
829
1198
|
break;
|
|
@@ -836,17 +1205,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
|
|
|
836
1205
|
if (platform !== "darwin") return [];
|
|
837
1206
|
if (provider === "codex") {
|
|
838
1207
|
return applications.map(
|
|
839
|
-
(apps) =>
|
|
1208
|
+
(apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
840
1209
|
);
|
|
841
1210
|
}
|
|
842
|
-
const root =
|
|
1211
|
+
const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
843
1212
|
let versions = [];
|
|
844
1213
|
try {
|
|
845
1214
|
versions = readdirSync(root);
|
|
846
1215
|
} catch {
|
|
847
1216
|
return [];
|
|
848
1217
|
}
|
|
849
|
-
return versions.map((v) =>
|
|
1218
|
+
return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
850
1219
|
}
|
|
851
1220
|
function npmGlobalPrefix(env) {
|
|
852
1221
|
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
@@ -864,13 +1233,13 @@ function declaredVersion(provider, source, file) {
|
|
|
864
1233
|
if (source !== "desktop-app") return null;
|
|
865
1234
|
if (provider === "codex") {
|
|
866
1235
|
try {
|
|
867
|
-
const pkg = JSON.parse(readFileSync(
|
|
1236
|
+
const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
|
|
868
1237
|
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
869
1238
|
} catch {
|
|
870
1239
|
return null;
|
|
871
1240
|
}
|
|
872
1241
|
}
|
|
873
|
-
return parseVersion(
|
|
1242
|
+
return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
|
|
874
1243
|
}
|
|
875
1244
|
function runVersion(file, env) {
|
|
876
1245
|
return new Promise((resolve) => {
|
|
@@ -884,14 +1253,14 @@ async function discoverBinaries(provider, options) {
|
|
|
884
1253
|
const platform = options.platform ?? process.platform;
|
|
885
1254
|
const home = options.home ?? options.env.HOME ?? os.homedir();
|
|
886
1255
|
const listed2 = [
|
|
887
|
-
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications",
|
|
1256
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
888
1257
|
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
889
1258
|
];
|
|
890
1259
|
const prefix = await npmGlobalPrefix(options.env);
|
|
891
1260
|
if (prefix) {
|
|
892
|
-
const dir = platform === "win32" ? prefix :
|
|
1261
|
+
const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
|
|
893
1262
|
for (const file of exeName(provider, platform)) {
|
|
894
|
-
const candidate =
|
|
1263
|
+
const candidate = path3.join(dir, file);
|
|
895
1264
|
if (executable(candidate)) {
|
|
896
1265
|
listed2.push({ path: candidate, source: "npm-global" });
|
|
897
1266
|
break;
|
|
@@ -937,7 +1306,7 @@ async function resolveBinary(provider, options) {
|
|
|
937
1306
|
|
|
938
1307
|
// src/engine/clean-env.ts
|
|
939
1308
|
import os2 from "os";
|
|
940
|
-
import
|
|
1309
|
+
import path4 from "path";
|
|
941
1310
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
942
1311
|
"HOME",
|
|
943
1312
|
"USER",
|
|
@@ -959,11 +1328,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
959
1328
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
960
1329
|
function wellKnownBinDirs(home) {
|
|
961
1330
|
return [
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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"),
|
|
967
1336
|
"/opt/homebrew/bin",
|
|
968
1337
|
"/usr/local/bin",
|
|
969
1338
|
"/usr/bin",
|
|
@@ -983,21 +1352,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
983
1352
|
}
|
|
984
1353
|
const home = out.HOME ?? os2.homedir();
|
|
985
1354
|
out.HOME = home;
|
|
986
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1355
|
+
const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
|
|
987
1356
|
const merged = [];
|
|
988
1357
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
989
1358
|
if (!merged.includes(dir)) merged.push(dir);
|
|
990
1359
|
}
|
|
991
|
-
out.PATH = merged.join(
|
|
1360
|
+
out.PATH = merged.join(path4.delimiter);
|
|
992
1361
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
993
1362
|
return out;
|
|
994
1363
|
}
|
|
995
1364
|
|
|
996
1365
|
// src/engine/files.ts
|
|
997
1366
|
import { createReadStream } from "fs";
|
|
998
|
-
import
|
|
1367
|
+
import fs4 from "fs/promises";
|
|
999
1368
|
import os3 from "os";
|
|
1000
|
-
import
|
|
1369
|
+
import path5 from "path";
|
|
1001
1370
|
|
|
1002
1371
|
// src/engine/activity.ts
|
|
1003
1372
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -1066,15 +1435,15 @@ function describeActivity(input) {
|
|
|
1066
1435
|
// src/engine/files.ts
|
|
1067
1436
|
function defaultProviderHomes(home = os3.homedir()) {
|
|
1068
1437
|
return {
|
|
1069
|
-
claudeProjects:
|
|
1070
|
-
codexSessions:
|
|
1071
|
-
codexIndex:
|
|
1438
|
+
claudeProjects: path5.join(home, ".claude", "projects"),
|
|
1439
|
+
codexSessions: path5.join(home, ".codex", "sessions"),
|
|
1440
|
+
codexIndex: path5.join(home, ".codex", "session_index.jsonl")
|
|
1072
1441
|
};
|
|
1073
1442
|
}
|
|
1074
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;
|
|
1075
1444
|
async function safeReaddir(dir) {
|
|
1076
1445
|
try {
|
|
1077
|
-
return await
|
|
1446
|
+
return await fs4.readdir(dir, { withFileTypes: true });
|
|
1078
1447
|
} catch {
|
|
1079
1448
|
return [];
|
|
1080
1449
|
}
|
|
@@ -1083,7 +1452,7 @@ async function statCandidate(provider, file) {
|
|
|
1083
1452
|
const match = UUIDISH.exec(file);
|
|
1084
1453
|
if (!match?.[1]) return null;
|
|
1085
1454
|
try {
|
|
1086
|
-
const stat = await
|
|
1455
|
+
const stat = await fs4.stat(file);
|
|
1087
1456
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1088
1457
|
} catch {
|
|
1089
1458
|
return null;
|
|
@@ -1093,10 +1462,10 @@ async function claudeCandidates(root) {
|
|
|
1093
1462
|
const out = [];
|
|
1094
1463
|
for (const project of await safeReaddir(root)) {
|
|
1095
1464
|
if (!project.isDirectory()) continue;
|
|
1096
|
-
const dir =
|
|
1465
|
+
const dir = path5.join(root, project.name);
|
|
1097
1466
|
const entries = await safeReaddir(dir);
|
|
1098
1467
|
const stats = await Promise.all(
|
|
1099
|
-
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)))
|
|
1100
1469
|
);
|
|
1101
1470
|
for (const s of stats) if (s) out.push(s);
|
|
1102
1471
|
}
|
|
@@ -1106,14 +1475,14 @@ async function codexCandidates(root) {
|
|
|
1106
1475
|
const out = [];
|
|
1107
1476
|
for (const year of await safeReaddir(root)) {
|
|
1108
1477
|
if (!year.isDirectory()) continue;
|
|
1109
|
-
for (const month of await safeReaddir(
|
|
1478
|
+
for (const month of await safeReaddir(path5.join(root, year.name))) {
|
|
1110
1479
|
if (!month.isDirectory()) continue;
|
|
1111
|
-
for (const day of await safeReaddir(
|
|
1480
|
+
for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
|
|
1112
1481
|
if (!day.isDirectory()) continue;
|
|
1113
|
-
const dir =
|
|
1482
|
+
const dir = path5.join(root, year.name, month.name, day.name);
|
|
1114
1483
|
const entries = await safeReaddir(dir);
|
|
1115
1484
|
const stats = await Promise.all(
|
|
1116
|
-
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)))
|
|
1117
1486
|
);
|
|
1118
1487
|
for (const s of stats) if (s) out.push(s);
|
|
1119
1488
|
}
|
|
@@ -1122,7 +1491,7 @@ async function codexCandidates(root) {
|
|
|
1122
1491
|
return out;
|
|
1123
1492
|
}
|
|
1124
1493
|
async function readSlice(file, start, length) {
|
|
1125
|
-
const handle = await
|
|
1494
|
+
const handle = await fs4.open(file, "r");
|
|
1126
1495
|
try {
|
|
1127
1496
|
const buffer = Buffer.alloc(length);
|
|
1128
1497
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1195,9 +1564,9 @@ async function readCodexIndex(file) {
|
|
|
1195
1564
|
const names = /* @__PURE__ */ new Map();
|
|
1196
1565
|
let text;
|
|
1197
1566
|
try {
|
|
1198
|
-
const stat = await
|
|
1567
|
+
const stat = await fs4.stat(file);
|
|
1199
1568
|
const budget = 4 * 1024 * 1024;
|
|
1200
|
-
text = stat.size <= budget ? await
|
|
1569
|
+
text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1201
1570
|
} catch {
|
|
1202
1571
|
return names;
|
|
1203
1572
|
}
|
|
@@ -1318,7 +1687,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1318
1687
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1319
1688
|
let size;
|
|
1320
1689
|
try {
|
|
1321
|
-
size = (await
|
|
1690
|
+
size = (await fs4.stat(file)).size;
|
|
1322
1691
|
} catch {
|
|
1323
1692
|
return {
|
|
1324
1693
|
items: [],
|
|
@@ -1372,9 +1741,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1372
1741
|
}
|
|
1373
1742
|
|
|
1374
1743
|
// src/engine/preferences.ts
|
|
1375
|
-
import { randomUUID } from "crypto";
|
|
1376
|
-
import
|
|
1377
|
-
import
|
|
1744
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1745
|
+
import fs5 from "fs/promises";
|
|
1746
|
+
import path6 from "path";
|
|
1378
1747
|
var PreferenceStore = class {
|
|
1379
1748
|
constructor(dir) {
|
|
1380
1749
|
this.dir = dir;
|
|
@@ -1383,11 +1752,11 @@ var PreferenceStore = class {
|
|
|
1383
1752
|
data = {};
|
|
1384
1753
|
writing = Promise.resolve();
|
|
1385
1754
|
get file() {
|
|
1386
|
-
return
|
|
1755
|
+
return path6.join(this.dir, "preferences.json");
|
|
1387
1756
|
}
|
|
1388
1757
|
async load() {
|
|
1389
1758
|
try {
|
|
1390
|
-
const parsed = JSON.parse(await
|
|
1759
|
+
const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
|
|
1391
1760
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1392
1761
|
} catch {
|
|
1393
1762
|
this.data = {};
|
|
@@ -1425,10 +1794,10 @@ var PreferenceStore = class {
|
|
|
1425
1794
|
persist() {
|
|
1426
1795
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1427
1796
|
this.writing = this.writing.then(async () => {
|
|
1428
|
-
await
|
|
1429
|
-
const tmp = `${this.file}.${
|
|
1430
|
-
await
|
|
1431
|
-
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);
|
|
1432
1801
|
}).catch(() => {
|
|
1433
1802
|
});
|
|
1434
1803
|
}
|
|
@@ -1585,9 +1954,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1585
1954
|
}
|
|
1586
1955
|
|
|
1587
1956
|
// src/engine/store.ts
|
|
1588
|
-
import { randomUUID as
|
|
1589
|
-
import
|
|
1590
|
-
import
|
|
1957
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1958
|
+
import fs6 from "fs/promises";
|
|
1959
|
+
import path7 from "path";
|
|
1591
1960
|
var MAX_RUNS = 2e3;
|
|
1592
1961
|
var RunIndex = class {
|
|
1593
1962
|
constructor(dir) {
|
|
@@ -1597,12 +1966,12 @@ var RunIndex = class {
|
|
|
1597
1966
|
data = { version: 1, sessions: [], runs: [] };
|
|
1598
1967
|
writing = Promise.resolve();
|
|
1599
1968
|
get file() {
|
|
1600
|
-
return
|
|
1969
|
+
return path7.join(this.dir, "index.json");
|
|
1601
1970
|
}
|
|
1602
1971
|
async load() {
|
|
1603
|
-
await
|
|
1972
|
+
await fs6.mkdir(this.dir, { recursive: true });
|
|
1604
1973
|
try {
|
|
1605
|
-
const parsed = JSON.parse(await
|
|
1974
|
+
const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
|
|
1606
1975
|
this.data = {
|
|
1607
1976
|
version: 1,
|
|
1608
1977
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1644,9 +2013,9 @@ var RunIndex = class {
|
|
|
1644
2013
|
persist() {
|
|
1645
2014
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1646
2015
|
this.writing = this.writing.then(async () => {
|
|
1647
|
-
const tmp = `${this.file}.${
|
|
1648
|
-
await
|
|
1649
|
-
await
|
|
2016
|
+
const tmp = `${this.file}.${randomUUID3()}.tmp`;
|
|
2017
|
+
await fs6.writeFile(tmp, snapshot, { mode: 384 });
|
|
2018
|
+
await fs6.rename(tmp, this.file);
|
|
1650
2019
|
}).catch(() => {
|
|
1651
2020
|
});
|
|
1652
2021
|
}
|
|
@@ -1656,27 +2025,27 @@ var RunIndex = class {
|
|
|
1656
2025
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1657
2026
|
* driver's scratch state is reused rather than growing per run. */
|
|
1658
2027
|
async identity() {
|
|
1659
|
-
const file =
|
|
2028
|
+
const file = path7.join(this.dir, "identity.json");
|
|
1660
2029
|
try {
|
|
1661
|
-
const parsed = JSON.parse(await
|
|
2030
|
+
const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1662
2031
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1663
2032
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1664
2033
|
}
|
|
1665
2034
|
} catch {
|
|
1666
2035
|
}
|
|
1667
|
-
const identity = { companyId:
|
|
1668
|
-
await
|
|
2036
|
+
const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
|
|
2037
|
+
await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1669
2038
|
return identity;
|
|
1670
2039
|
}
|
|
1671
2040
|
};
|
|
1672
2041
|
|
|
1673
2042
|
// src/engine/user-defaults.ts
|
|
1674
|
-
import
|
|
2043
|
+
import fs7 from "fs/promises";
|
|
1675
2044
|
import os4 from "os";
|
|
1676
|
-
import
|
|
2045
|
+
import path8 from "path";
|
|
1677
2046
|
async function readJson(file) {
|
|
1678
2047
|
try {
|
|
1679
|
-
const parsed = JSON.parse(await
|
|
2048
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
1680
2049
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1681
2050
|
} catch {
|
|
1682
2051
|
return null;
|
|
@@ -1687,8 +2056,8 @@ function str3(value) {
|
|
|
1687
2056
|
}
|
|
1688
2057
|
async function claudeDefaults(cwd, home = os4.homedir()) {
|
|
1689
2058
|
const files = [
|
|
1690
|
-
...cwd ? [
|
|
1691
|
-
|
|
2059
|
+
...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
|
|
2060
|
+
path8.join(home, ".claude", "settings.json")
|
|
1692
2061
|
];
|
|
1693
2062
|
let model = null;
|
|
1694
2063
|
let effort = null;
|
|
@@ -1704,7 +2073,7 @@ async function claudeDefaults(cwd, home = os4.homedir()) {
|
|
|
1704
2073
|
async function codexDefaults(home = os4.homedir()) {
|
|
1705
2074
|
let text;
|
|
1706
2075
|
try {
|
|
1707
|
-
text = await
|
|
2076
|
+
text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
|
|
1708
2077
|
} catch {
|
|
1709
2078
|
return { model: null, effort: null };
|
|
1710
2079
|
}
|
|
@@ -1728,31 +2097,31 @@ var EFFORT_LEVELS = {
|
|
|
1728
2097
|
// src/engine/index.ts
|
|
1729
2098
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1730
2099
|
function moduleDir() {
|
|
1731
|
-
return
|
|
2100
|
+
return path9.dirname(fileURLToPath(import.meta.url));
|
|
1732
2101
|
}
|
|
1733
2102
|
function resolveWorkerPath() {
|
|
1734
2103
|
const dir = moduleDir();
|
|
1735
2104
|
for (const candidate of [
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
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")
|
|
1740
2109
|
]) {
|
|
1741
2110
|
if (existsSync(candidate)) return candidate;
|
|
1742
2111
|
}
|
|
1743
2112
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1744
2113
|
}
|
|
1745
2114
|
async function installShim(dataDir) {
|
|
1746
|
-
const source =
|
|
2115
|
+
const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
1747
2116
|
if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
1748
|
-
const body = await
|
|
1749
|
-
const target =
|
|
1750
|
-
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);
|
|
1751
2120
|
if (current !== body) {
|
|
1752
|
-
await
|
|
1753
|
-
await
|
|
2121
|
+
await fs8.mkdir(path9.dirname(target), { recursive: true });
|
|
2122
|
+
await fs8.writeFile(target, body, { mode: 493 });
|
|
1754
2123
|
}
|
|
1755
|
-
await
|
|
2124
|
+
await fs8.chmod(target, 493);
|
|
1756
2125
|
return target;
|
|
1757
2126
|
}
|
|
1758
2127
|
function nowIso() {
|
|
@@ -1852,29 +2221,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1852
2221
|
};
|
|
1853
2222
|
}
|
|
1854
2223
|
async function createCodingSessionsEngine(options = {}) {
|
|
1855
|
-
const dataDir = options.dataDir ??
|
|
1856
|
-
const stateDir =
|
|
2224
|
+
const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
|
|
2225
|
+
const stateDir = path9.join(dataDir, "paperclip-state");
|
|
1857
2226
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1858
2227
|
const index = new RunIndex(dataDir);
|
|
1859
2228
|
await index.load();
|
|
1860
2229
|
const preferences = new PreferenceStore(dataDir);
|
|
1861
2230
|
await preferences.load();
|
|
1862
|
-
await
|
|
2231
|
+
await fs8.mkdir(stateDir, { recursive: true });
|
|
1863
2232
|
const identity = await index.identity();
|
|
2233
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1864
2234
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1865
2235
|
sink: options.rawBackup,
|
|
1866
2236
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1867
2237
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1868
2238
|
}) : null;
|
|
1869
2239
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1870
|
-
const healthFile =
|
|
2240
|
+
const healthFile = path9.join(dataDir, "provider-health.json");
|
|
1871
2241
|
let health = {};
|
|
1872
2242
|
try {
|
|
1873
|
-
health = JSON.parse(await
|
|
2243
|
+
health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
|
|
1874
2244
|
} catch {
|
|
1875
2245
|
health = {};
|
|
1876
2246
|
}
|
|
1877
|
-
const saveHealth = () =>
|
|
2247
|
+
const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1878
2248
|
function noteRunOutcome(provider, status, texts) {
|
|
1879
2249
|
if (status === "completed") {
|
|
1880
2250
|
if (health[provider]) {
|
|
@@ -1991,7 +2361,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1991
2361
|
async function startRun(session, prompt) {
|
|
1992
2362
|
const provider = session.provider;
|
|
1993
2363
|
const realCommand = await commandFor(provider);
|
|
1994
|
-
const runId =
|
|
2364
|
+
const runId = randomUUID4();
|
|
1995
2365
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
1996
2366
|
const run = {
|
|
1997
2367
|
runId,
|
|
@@ -2029,7 +2399,26 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2029
2399
|
let fileOffset = null;
|
|
2030
2400
|
if (session.providerSessionId) {
|
|
2031
2401
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
2032
|
-
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 });
|
|
2033
2422
|
}
|
|
2034
2423
|
const child = forkWorker();
|
|
2035
2424
|
const liveRun = {
|
|
@@ -2203,7 +2592,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2203
2592
|
resumeSessionId: session.providerSessionId,
|
|
2204
2593
|
shimPath,
|
|
2205
2594
|
realCommand,
|
|
2206
|
-
identity
|
|
2595
|
+
identity,
|
|
2596
|
+
agentArgs
|
|
2207
2597
|
});
|
|
2208
2598
|
return run;
|
|
2209
2599
|
}
|
|
@@ -2222,7 +2612,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2222
2612
|
}, 3e3);
|
|
2223
2613
|
}
|
|
2224
2614
|
async function pruneDriverScratch() {
|
|
2225
|
-
const runsDir =
|
|
2615
|
+
const runsDir = path9.join(
|
|
2226
2616
|
stateDir,
|
|
2227
2617
|
"instances",
|
|
2228
2618
|
"default",
|
|
@@ -2233,9 +2623,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2233
2623
|
"claude-runtime",
|
|
2234
2624
|
"runs"
|
|
2235
2625
|
);
|
|
2236
|
-
const entries = await
|
|
2626
|
+
const entries = await fs8.readdir(runsDir).catch(() => []);
|
|
2237
2627
|
await Promise.all(
|
|
2238
|
-
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))
|
|
2239
2629
|
);
|
|
2240
2630
|
}
|
|
2241
2631
|
async function probe() {
|
|
@@ -2276,7 +2666,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2276
2666
|
function newSession(input) {
|
|
2277
2667
|
const at = nowIso();
|
|
2278
2668
|
return {
|
|
2279
|
-
id:
|
|
2669
|
+
id: randomUUID4(),
|
|
2280
2670
|
provider: input.provider,
|
|
2281
2671
|
providerSessionId: input.providerSessionId,
|
|
2282
2672
|
cwd: input.cwd,
|
|
@@ -2294,17 +2684,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2294
2684
|
function resolveFolder(raw) {
|
|
2295
2685
|
const trimmed = raw.trim();
|
|
2296
2686
|
if (!trimmed) return null;
|
|
2297
|
-
return
|
|
2687
|
+
return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2298
2688
|
}
|
|
2299
2689
|
async function checkFolder(raw) {
|
|
2300
2690
|
const target = resolveFolder(raw);
|
|
2301
2691
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2302
|
-
const stat = await
|
|
2692
|
+
const stat = await fs8.stat(target).catch(() => null);
|
|
2303
2693
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2304
2694
|
}
|
|
2305
2695
|
async function assertFolder(cwd) {
|
|
2306
|
-
const resolved =
|
|
2307
|
-
const stat = await
|
|
2696
|
+
const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2697
|
+
const stat = await fs8.stat(resolved).catch(() => null);
|
|
2308
2698
|
if (!stat?.isDirectory()) {
|
|
2309
2699
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2310
2700
|
}
|
|
@@ -2366,7 +2756,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2366
2756
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2367
2757
|
path: folder,
|
|
2368
2758
|
lastUsedAt,
|
|
2369
|
-
exists: (await
|
|
2759
|
+
exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2370
2760
|
}))
|
|
2371
2761
|
);
|
|
2372
2762
|
},
|
|
@@ -2411,7 +2801,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2411
2801
|
async createFolder(raw) {
|
|
2412
2802
|
const target = resolveFolder(raw);
|
|
2413
2803
|
if (!target) throw new Error("Type a folder path first.");
|
|
2414
|
-
await
|
|
2804
|
+
await fs8.mkdir(target, { recursive: true });
|
|
2415
2805
|
return checkFolder(target);
|
|
2416
2806
|
},
|
|
2417
2807
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2449,31 +2839,35 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2449
2839
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2450
2840
|
};
|
|
2451
2841
|
}
|
|
2452
|
-
const stat = await
|
|
2842
|
+
const stat = await fs8.stat(file);
|
|
2453
2843
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2454
2844
|
return {
|
|
2455
2845
|
available: false,
|
|
2456
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}.`
|
|
2457
2847
|
};
|
|
2458
2848
|
}
|
|
2459
|
-
const content = await
|
|
2460
|
-
return { available: true, fileName:
|
|
2849
|
+
const content = await fs8.readFile(file, "utf8");
|
|
2850
|
+
return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
|
|
2461
2851
|
},
|
|
2462
2852
|
async start(input) {
|
|
2463
2853
|
const prompt = input.prompt.trim();
|
|
2464
2854
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2465
2855
|
const cwd = await assertFolder(input.cwd);
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
)
|
|
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);
|
|
2477
2871
|
const run = await startRun(session, prompt);
|
|
2478
2872
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2479
2873
|
},
|
|
@@ -2542,6 +2936,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2542
2936
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2543
2937
|
return saveSession(next);
|
|
2544
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
|
+
},
|
|
2545
2954
|
async dispose() {
|
|
2546
2955
|
for (const liveRun of live.values()) {
|
|
2547
2956
|
liveRun.stopRequested = true;
|