@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/server/index.cjs
CHANGED
|
@@ -43,18 +43,18 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
43
43
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
44
44
|
|
|
45
45
|
// src/server/index.ts
|
|
46
|
-
var
|
|
47
|
-
var
|
|
48
|
-
var
|
|
46
|
+
var import_node_crypto6 = require("crypto");
|
|
47
|
+
var import_node_fs5 = require("fs");
|
|
48
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
49
49
|
var import_node_http = __toESM(require("http"), 1);
|
|
50
|
-
var
|
|
50
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
51
51
|
var import_node_url2 = require("url");
|
|
52
52
|
var import_ws = require("ws");
|
|
53
53
|
|
|
54
54
|
// src/engine/index.ts
|
|
55
|
-
var
|
|
56
|
-
var
|
|
57
|
-
var
|
|
55
|
+
var import_node_child_process3 = require("child_process");
|
|
56
|
+
var import_node_crypto5 = require("crypto");
|
|
57
|
+
var import_node_fs4 = require("fs");
|
|
58
58
|
var import_format = require("@ai-matrx/kit/format");
|
|
59
59
|
|
|
60
60
|
// src/engine/raw-backup.ts
|
|
@@ -114,10 +114,379 @@ function createRawBackupScheduler(options) {
|
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// src/engine/agent-attach.ts
|
|
118
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
119
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
120
|
+
var AGENT_MCP_SERVER_NAME = "matrx-agent";
|
|
121
|
+
var AGENT_PLUGIN_NAME = "matrx-agent";
|
|
122
|
+
function escapeAttr(value) {
|
|
123
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
124
|
+
}
|
|
125
|
+
function versionWords(bundle) {
|
|
126
|
+
return bundle.version_number !== null ? String(bundle.version_number) : "latest";
|
|
127
|
+
}
|
|
128
|
+
function renderAgentInstructions(bundle, options = {}) {
|
|
129
|
+
const included = bundle.skills.filter((skill) => skill.tier === "included");
|
|
130
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
131
|
+
const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
|
|
132
|
+
if (included.length > 0) {
|
|
133
|
+
parts.push(
|
|
134
|
+
"## Skills (always in effect)",
|
|
135
|
+
...included.map((skill) => `### ${skill.label}
|
|
136
|
+
|
|
137
|
+
${skill.body.trim()}`)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (options.claudeSkills && listed2.length > 0) {
|
|
141
|
+
parts.push(
|
|
142
|
+
[
|
|
143
|
+
"## Your skills",
|
|
144
|
+
"When a request matches one of these, load it with your Skill tool and follow it before answering:",
|
|
145
|
+
...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
|
|
146
|
+
].join("\n")
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
|
|
150
|
+
parts.push(bundle.instructions.trim());
|
|
151
|
+
parts.push("</ai_matrx_agent>");
|
|
152
|
+
return `${parts.filter(Boolean).join("\n\n")}
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
function renderCodexSkillsList(bundle, skillsRoot) {
|
|
156
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
157
|
+
if (listed2.length === 0) return "";
|
|
158
|
+
const lines = [
|
|
159
|
+
"<skills_instructions>",
|
|
160
|
+
"## AI Matrx agent skills",
|
|
161
|
+
"A skill is a set of instructions stored in a `SKILL.md` file. Each entry has a name, a description and the absolute path of its file. When a task matches a skill's description, read that file and follow it.",
|
|
162
|
+
"### Available skills",
|
|
163
|
+
...listed2.map(
|
|
164
|
+
(skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
165
|
+
),
|
|
166
|
+
"</skills_instructions>"
|
|
167
|
+
];
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
function tomlString(value) {
|
|
171
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
172
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
173
|
+
}
|
|
174
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
175
|
+
const pluginDir = import_node_path.default.join(dir, "plugin");
|
|
176
|
+
const skillsRoot = import_node_path.default.join(dir, "skills");
|
|
177
|
+
await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
178
|
+
await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
179
|
+
const instructions = renderAgentInstructions(bundle);
|
|
180
|
+
const instructionsFile = import_node_path.default.join(dir, "instructions.md");
|
|
181
|
+
await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
182
|
+
await import_promises2.default.writeFile(
|
|
183
|
+
import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
184
|
+
`${JSON.stringify(
|
|
185
|
+
{
|
|
186
|
+
name: AGENT_PLUGIN_NAME,
|
|
187
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
188
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
189
|
+
},
|
|
190
|
+
null,
|
|
191
|
+
2
|
|
192
|
+
)}
|
|
193
|
+
`,
|
|
194
|
+
{ mode: 384 }
|
|
195
|
+
);
|
|
196
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
197
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
198
|
+
for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
|
|
199
|
+
await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
|
|
200
|
+
for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
|
|
201
|
+
if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
for (const skill of listed2) {
|
|
204
|
+
const skillDir = import_node_path.default.join(root, skill.slug);
|
|
205
|
+
await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
206
|
+
await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
let mcpConfigFile = null;
|
|
210
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
211
|
+
const mcpPath = import_node_path.default.join(dir, "mcp.json");
|
|
212
|
+
if (mcpUrl) {
|
|
213
|
+
mcpConfigFile = mcpPath;
|
|
214
|
+
await import_promises2.default.writeFile(
|
|
215
|
+
mcpPath,
|
|
216
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
217
|
+
`,
|
|
218
|
+
{ mode: 384 }
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
await import_promises2.default.rm(mcpPath, { force: true });
|
|
222
|
+
}
|
|
223
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
224
|
+
return {
|
|
225
|
+
dir,
|
|
226
|
+
instructionsFile,
|
|
227
|
+
pluginDir,
|
|
228
|
+
mcpConfigFile,
|
|
229
|
+
mcpUrl,
|
|
230
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
231
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
232
|
+
|
|
233
|
+
${instructions}` : instructions
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function agentArgsFor(provider, attachment, options) {
|
|
237
|
+
if (provider === "claude") {
|
|
238
|
+
const args2 = [
|
|
239
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
240
|
+
"--append-system-prompt-file",
|
|
241
|
+
attachment.instructionsFile,
|
|
242
|
+
"--system-prompt-snapshot",
|
|
243
|
+
"off",
|
|
244
|
+
"--plugin-dir",
|
|
245
|
+
attachment.pluginDir
|
|
246
|
+
];
|
|
247
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
248
|
+
return args2;
|
|
249
|
+
}
|
|
250
|
+
const args = [];
|
|
251
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
252
|
+
if (attachment.mcpUrl) {
|
|
253
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
254
|
+
args.push(
|
|
255
|
+
"-c",
|
|
256
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
257
|
+
"-c",
|
|
258
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return args;
|
|
262
|
+
}
|
|
263
|
+
function sessionAgentRecord(bundle, options) {
|
|
264
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
265
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
266
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
267
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
268
|
+
for (const tool of bundle.tools) {
|
|
269
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
273
|
+
const loaded = [
|
|
274
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
275
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
276
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
277
|
+
];
|
|
278
|
+
return {
|
|
279
|
+
agentId: bundle.agent_id,
|
|
280
|
+
versionId: bundle.version_id,
|
|
281
|
+
versionNumber: bundle.version_number,
|
|
282
|
+
name: bundle.name,
|
|
283
|
+
definitionHash: bundle.definition_hash,
|
|
284
|
+
loaded,
|
|
285
|
+
unavailable
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/engine/agents.ts
|
|
290
|
+
var import_node_crypto2 = require("crypto");
|
|
291
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
292
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
293
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
294
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
295
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
296
|
+
"agx_get_list_full",
|
|
297
|
+
"agx_search",
|
|
298
|
+
"agx_resolve_agent_address",
|
|
299
|
+
"ues_get_bulk",
|
|
300
|
+
"ues_list",
|
|
301
|
+
"ues_set",
|
|
302
|
+
"ues_touch"
|
|
303
|
+
]);
|
|
304
|
+
var agentsClient = async () => {
|
|
305
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
306
|
+
import("@ai-matrx/agents/matrx"),
|
|
307
|
+
import("@ai-matrx/agents/portable"),
|
|
308
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
309
|
+
]);
|
|
310
|
+
return { ...matrx, ...portable, ...mcp };
|
|
311
|
+
};
|
|
312
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
313
|
+
function userIdOf(credential) {
|
|
314
|
+
if (!credential || credential.kind !== "user") return null;
|
|
315
|
+
const payload = credential.accessToken.split(".")[1];
|
|
316
|
+
if (!payload) return null;
|
|
317
|
+
try {
|
|
318
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
319
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function createAgentConnection(options, dataDir) {
|
|
325
|
+
const credentials = options?.credentials ?? null;
|
|
326
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
327
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
328
|
+
const supabaseKey = options?.supabaseKey ?? null;
|
|
329
|
+
const root = import_node_path2.default.join(dataDir, "agents");
|
|
330
|
+
let mcpBaseUrl = null;
|
|
331
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
332
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
333
|
+
const organizationId = () => {
|
|
334
|
+
const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
|
|
335
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
336
|
+
};
|
|
337
|
+
const transport = async () => (await agentsClient()).createMatrxTransport({
|
|
338
|
+
baseUrl: serverUrl,
|
|
339
|
+
...credentials ? { credentials } : {},
|
|
340
|
+
...organizationId() ? { organizationId: () => organizationId() } : {},
|
|
341
|
+
source: "codingSessionsAgents"
|
|
342
|
+
});
|
|
343
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
344
|
+
const requireSignedIn = async () => {
|
|
345
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
346
|
+
};
|
|
347
|
+
const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
348
|
+
async function meta(sessionId) {
|
|
349
|
+
const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
|
|
350
|
+
const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
351
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
352
|
+
const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
|
|
353
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
354
|
+
await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
355
|
+
return created;
|
|
356
|
+
}
|
|
357
|
+
async function findSessionBySecret(secret) {
|
|
358
|
+
for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
|
|
359
|
+
const found = await import_promises3.default.readFile(import_node_path2.default.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
|
|
360
|
+
if (found) {
|
|
361
|
+
secretToSession.set(secret, name);
|
|
362
|
+
return name;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
async function readSessionBundle(sessionId) {
|
|
368
|
+
return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
async access() {
|
|
372
|
+
const current = await credential().catch(() => null);
|
|
373
|
+
const userId = userIdOf(current);
|
|
374
|
+
return {
|
|
375
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
376
|
+
signedIn: userId !== null,
|
|
377
|
+
userId,
|
|
378
|
+
organizationId: organizationId()
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
async catalogRpc(request) {
|
|
382
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
383
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
384
|
+
}
|
|
385
|
+
const current = await credential();
|
|
386
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
387
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
388
|
+
const query = new URLSearchParams();
|
|
389
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
390
|
+
if (request.range) {
|
|
391
|
+
query.set("offset", String(request.range[0]));
|
|
392
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
393
|
+
}
|
|
394
|
+
const headers = {
|
|
395
|
+
apikey: supabaseKey,
|
|
396
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
397
|
+
"Content-Type": "application/json",
|
|
398
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
399
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
400
|
+
"Content-Profile": "public",
|
|
401
|
+
"Accept-Profile": "public"
|
|
402
|
+
};
|
|
403
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
404
|
+
const qs = query.toString();
|
|
405
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
406
|
+
method: "POST",
|
|
407
|
+
headers,
|
|
408
|
+
body: JSON.stringify(request.args ?? {})
|
|
409
|
+
});
|
|
410
|
+
const text = await response.text();
|
|
411
|
+
const body = text ? JSON.parse(text) : null;
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
const err = body ?? {};
|
|
414
|
+
return {
|
|
415
|
+
data: null,
|
|
416
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const range = response.headers.get("content-range");
|
|
420
|
+
const total = range?.split("/")[1];
|
|
421
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
422
|
+
},
|
|
423
|
+
async details(agentId) {
|
|
424
|
+
await requireSignedIn();
|
|
425
|
+
const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
|
|
426
|
+
const [bundle, versions] = await Promise.all([
|
|
427
|
+
fetchPortableAgent(await transport(), { agentId }),
|
|
428
|
+
fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
|
|
429
|
+
]);
|
|
430
|
+
return {
|
|
431
|
+
agentId: bundle.agent_id,
|
|
432
|
+
name: bundle.name,
|
|
433
|
+
variables: bundle.variables,
|
|
434
|
+
versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
|
|
435
|
+
latestVersionNumber: bundle.version_number
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
async loadBundle(selection) {
|
|
439
|
+
await requireSignedIn();
|
|
440
|
+
const { fetchPortableAgent } = await agentsClient();
|
|
441
|
+
return fetchPortableAgent(
|
|
442
|
+
await transport(),
|
|
443
|
+
selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
|
|
444
|
+
{ variables: selection.variables ?? {} }
|
|
445
|
+
);
|
|
446
|
+
},
|
|
447
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
448
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
449
|
+
await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
450
|
+
await meta(sessionId);
|
|
451
|
+
},
|
|
452
|
+
readSessionBundle,
|
|
453
|
+
sessionDir,
|
|
454
|
+
async mcpUrlFor(sessionId) {
|
|
455
|
+
if (!mcpBaseUrl) return null;
|
|
456
|
+
const { mcpSecret } = await meta(sessionId);
|
|
457
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
458
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
459
|
+
},
|
|
460
|
+
setMcpBaseUrl(baseUrl) {
|
|
461
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
462
|
+
},
|
|
463
|
+
async handleMcp(secret, body) {
|
|
464
|
+
let handler = handlers.get(secret);
|
|
465
|
+
if (!handler) {
|
|
466
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
467
|
+
if (!sessionId) return null;
|
|
468
|
+
const bundle = await readSessionBundle(sessionId);
|
|
469
|
+
if (!bundle) return null;
|
|
470
|
+
const { conversationId } = await meta(sessionId);
|
|
471
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
472
|
+
handler = createAgentToolsMcpHandler({
|
|
473
|
+
bundle,
|
|
474
|
+
credentials,
|
|
475
|
+
organizationId: () => organizationId(),
|
|
476
|
+
baseUrl: serverUrl,
|
|
477
|
+
conversationId
|
|
478
|
+
});
|
|
479
|
+
handlers.set(secret, handler);
|
|
480
|
+
}
|
|
481
|
+
return handler.handle(body);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
117
486
|
// src/engine/index.ts
|
|
118
|
-
var
|
|
119
|
-
var
|
|
120
|
-
var
|
|
487
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
488
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
489
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
121
490
|
var import_node_url = require("url");
|
|
122
491
|
|
|
123
492
|
// src/normalize/claude.ts
|
|
@@ -783,9 +1152,177 @@ function describeProblem(provider, problem) {
|
|
|
783
1152
|
};
|
|
784
1153
|
}
|
|
785
1154
|
|
|
786
|
-
// src/engine/
|
|
1155
|
+
// src/engine/binaries.ts
|
|
1156
|
+
var import_node_child_process = require("child_process");
|
|
1157
|
+
var import_node_fs = require("fs");
|
|
787
1158
|
var import_node_os = __toESM(require("os"), 1);
|
|
788
|
-
var
|
|
1159
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
1160
|
+
var SOURCE_LABEL = {
|
|
1161
|
+
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
1162
|
+
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
1163
|
+
};
|
|
1164
|
+
function sourceLabel(provider, source) {
|
|
1165
|
+
return SOURCE_LABEL[provider][source];
|
|
1166
|
+
}
|
|
1167
|
+
function parseVersion(text) {
|
|
1168
|
+
const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
|
|
1169
|
+
return match ? match[0] : null;
|
|
1170
|
+
}
|
|
1171
|
+
function compareVersions(a, b) {
|
|
1172
|
+
const [aCore = "", aPre] = a.split("-", 2);
|
|
1173
|
+
const [bCore = "", bPre] = b.split("-", 2);
|
|
1174
|
+
const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1175
|
+
const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1176
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1177
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1178
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1179
|
+
}
|
|
1180
|
+
if (aPre && !bPre) return -1;
|
|
1181
|
+
if (!aPre && bPre) return 1;
|
|
1182
|
+
return 0;
|
|
1183
|
+
}
|
|
1184
|
+
function pickBinary(candidates) {
|
|
1185
|
+
let best = null;
|
|
1186
|
+
for (const c of candidates) {
|
|
1187
|
+
if (!best) {
|
|
1188
|
+
best = c;
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
|
|
1192
|
+
}
|
|
1193
|
+
return best;
|
|
1194
|
+
}
|
|
1195
|
+
function executable(file) {
|
|
1196
|
+
try {
|
|
1197
|
+
(0, import_node_fs.accessSync)(file, import_node_fs.constants.X_OK);
|
|
1198
|
+
return true;
|
|
1199
|
+
} catch {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function exeName(name, platform) {
|
|
1204
|
+
return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
|
|
1205
|
+
}
|
|
1206
|
+
function allOnPath(name, pathValue, platform = process.platform) {
|
|
1207
|
+
const found = [];
|
|
1208
|
+
for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
|
|
1209
|
+
if (!dir) continue;
|
|
1210
|
+
for (const file of exeName(name, platform)) {
|
|
1211
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
1212
|
+
if (executable(candidate)) {
|
|
1213
|
+
found.push(candidate);
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return found;
|
|
1219
|
+
}
|
|
1220
|
+
function desktopAppCandidates(provider, platform, home, applications) {
|
|
1221
|
+
if (platform !== "darwin") return [];
|
|
1222
|
+
if (provider === "codex") {
|
|
1223
|
+
return applications.map(
|
|
1224
|
+
(apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1228
|
+
let versions = [];
|
|
1229
|
+
try {
|
|
1230
|
+
versions = (0, import_node_fs.readdirSync)(root);
|
|
1231
|
+
} catch {
|
|
1232
|
+
return [];
|
|
1233
|
+
}
|
|
1234
|
+
return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
1235
|
+
}
|
|
1236
|
+
function npmGlobalPrefix(env) {
|
|
1237
|
+
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
1238
|
+
if (fromEnv) return Promise.resolve(fromEnv);
|
|
1239
|
+
const npm = allOnPath("npm", env.PATH ?? "")[0];
|
|
1240
|
+
if (!npm) return Promise.resolve(null);
|
|
1241
|
+
return new Promise((resolve) => {
|
|
1242
|
+
const child = (0, import_node_child_process.execFile)(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
|
|
1243
|
+
resolve(error ? null : String(stdout).trim() || null);
|
|
1244
|
+
});
|
|
1245
|
+
child.stdin?.end();
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
function declaredVersion(provider, source, file) {
|
|
1249
|
+
if (source !== "desktop-app") return null;
|
|
1250
|
+
if (provider === "codex") {
|
|
1251
|
+
try {
|
|
1252
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.dirname(file)), "codex-package.json"), "utf8"));
|
|
1253
|
+
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
1254
|
+
} catch {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
|
|
1259
|
+
}
|
|
1260
|
+
function runVersion(file, env) {
|
|
1261
|
+
return new Promise((resolve) => {
|
|
1262
|
+
const child = (0, import_node_child_process.execFile)(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
|
|
1263
|
+
resolve(error ? null : parseVersion(String(stdout)));
|
|
1264
|
+
});
|
|
1265
|
+
child.stdin?.end();
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
async function discoverBinaries(provider, options) {
|
|
1269
|
+
const platform = options.platform ?? process.platform;
|
|
1270
|
+
const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
|
|
1271
|
+
const listed2 = [
|
|
1272
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
1273
|
+
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
1274
|
+
];
|
|
1275
|
+
const prefix = await npmGlobalPrefix(options.env);
|
|
1276
|
+
if (prefix) {
|
|
1277
|
+
const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
|
|
1278
|
+
for (const file of exeName(provider, platform)) {
|
|
1279
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
1280
|
+
if (executable(candidate)) {
|
|
1281
|
+
listed2.push({ path: candidate, source: "npm-global" });
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1287
|
+
const unique = listed2.filter((c) => {
|
|
1288
|
+
let real = c.path;
|
|
1289
|
+
try {
|
|
1290
|
+
real = (0, import_node_fs.realpathSync)(c.path);
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
if (seen.has(real)) return false;
|
|
1294
|
+
seen.add(real);
|
|
1295
|
+
return true;
|
|
1296
|
+
});
|
|
1297
|
+
return Promise.all(
|
|
1298
|
+
unique.map(async (c) => ({
|
|
1299
|
+
...c,
|
|
1300
|
+
version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
|
|
1301
|
+
}))
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
async function resolveBinary(provider, options) {
|
|
1305
|
+
const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
|
|
1306
|
+
const best = pickBinary(candidates);
|
|
1307
|
+
const inUse = options.forced ? {
|
|
1308
|
+
path: options.forced,
|
|
1309
|
+
source: "configured",
|
|
1310
|
+
version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
|
|
1311
|
+
} : best;
|
|
1312
|
+
if (!inUse) return null;
|
|
1313
|
+
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;
|
|
1314
|
+
return {
|
|
1315
|
+
path: inUse.path,
|
|
1316
|
+
version: inUse.version,
|
|
1317
|
+
source: inUse.source,
|
|
1318
|
+
sourceLabel: sourceLabel(provider, inUse.source),
|
|
1319
|
+
newer
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/engine/clean-env.ts
|
|
1324
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
1325
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
789
1326
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
790
1327
|
"HOME",
|
|
791
1328
|
"USER",
|
|
@@ -807,11 +1344,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
807
1344
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
808
1345
|
function wellKnownBinDirs(home) {
|
|
809
1346
|
return [
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1347
|
+
import_node_path4.default.join(home, ".local", "bin"),
|
|
1348
|
+
import_node_path4.default.join(home, ".claude", "local"),
|
|
1349
|
+
import_node_path4.default.join(home, ".npm-global", "bin"),
|
|
1350
|
+
import_node_path4.default.join(home, ".bun", "bin"),
|
|
1351
|
+
import_node_path4.default.join(home, ".volta", "bin"),
|
|
815
1352
|
"/opt/homebrew/bin",
|
|
816
1353
|
"/usr/local/bin",
|
|
817
1354
|
"/usr/bin",
|
|
@@ -829,31 +1366,31 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
829
1366
|
if (typeof value !== "string") continue;
|
|
830
1367
|
if (isAllowedEnvName(name)) out[name] = value;
|
|
831
1368
|
}
|
|
832
|
-
const home = out.HOME ??
|
|
1369
|
+
const home = out.HOME ?? import_node_os2.default.homedir();
|
|
833
1370
|
out.HOME = home;
|
|
834
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1371
|
+
const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
|
|
835
1372
|
const merged = [];
|
|
836
1373
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
837
1374
|
if (!merged.includes(dir)) merged.push(dir);
|
|
838
1375
|
}
|
|
839
|
-
out.PATH = merged.join(
|
|
1376
|
+
out.PATH = merged.join(import_node_path4.default.delimiter);
|
|
840
1377
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
841
1378
|
return out;
|
|
842
1379
|
}
|
|
843
1380
|
|
|
844
1381
|
// src/engine/files.ts
|
|
845
|
-
var
|
|
846
|
-
var
|
|
847
|
-
var
|
|
848
|
-
var
|
|
1382
|
+
var import_node_fs2 = require("fs");
|
|
1383
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1384
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
1385
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
849
1386
|
|
|
850
1387
|
// src/engine/activity.ts
|
|
851
|
-
var
|
|
1388
|
+
var import_node_child_process2 = require("child_process");
|
|
852
1389
|
var RECENT_WRITE_MS = 2 * 60 * 1e3;
|
|
853
1390
|
var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
|
|
854
1391
|
function listProcesses() {
|
|
855
1392
|
return new Promise((resolve) => {
|
|
856
|
-
(0,
|
|
1393
|
+
(0, import_node_child_process2.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
|
|
857
1394
|
if (error) {
|
|
858
1395
|
resolve([]);
|
|
859
1396
|
return;
|
|
@@ -912,17 +1449,17 @@ function describeActivity(input) {
|
|
|
912
1449
|
}
|
|
913
1450
|
|
|
914
1451
|
// src/engine/files.ts
|
|
915
|
-
function defaultProviderHomes(home =
|
|
1452
|
+
function defaultProviderHomes(home = import_node_os3.default.homedir()) {
|
|
916
1453
|
return {
|
|
917
|
-
claudeProjects:
|
|
918
|
-
codexSessions:
|
|
919
|
-
codexIndex:
|
|
1454
|
+
claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
|
|
1455
|
+
codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
|
|
1456
|
+
codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
|
|
920
1457
|
};
|
|
921
1458
|
}
|
|
922
1459
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
923
1460
|
async function safeReaddir(dir) {
|
|
924
1461
|
try {
|
|
925
|
-
return await
|
|
1462
|
+
return await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
926
1463
|
} catch {
|
|
927
1464
|
return [];
|
|
928
1465
|
}
|
|
@@ -931,7 +1468,7 @@ async function statCandidate(provider, file) {
|
|
|
931
1468
|
const match = UUIDISH.exec(file);
|
|
932
1469
|
if (!match?.[1]) return null;
|
|
933
1470
|
try {
|
|
934
|
-
const stat = await
|
|
1471
|
+
const stat = await import_promises4.default.stat(file);
|
|
935
1472
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
936
1473
|
} catch {
|
|
937
1474
|
return null;
|
|
@@ -941,10 +1478,10 @@ async function claudeCandidates(root) {
|
|
|
941
1478
|
const out = [];
|
|
942
1479
|
for (const project of await safeReaddir(root)) {
|
|
943
1480
|
if (!project.isDirectory()) continue;
|
|
944
|
-
const dir =
|
|
1481
|
+
const dir = import_node_path5.default.join(root, project.name);
|
|
945
1482
|
const entries = await safeReaddir(dir);
|
|
946
1483
|
const stats = await Promise.all(
|
|
947
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1484
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
|
|
948
1485
|
);
|
|
949
1486
|
for (const s of stats) if (s) out.push(s);
|
|
950
1487
|
}
|
|
@@ -954,14 +1491,14 @@ async function codexCandidates(root) {
|
|
|
954
1491
|
const out = [];
|
|
955
1492
|
for (const year of await safeReaddir(root)) {
|
|
956
1493
|
if (!year.isDirectory()) continue;
|
|
957
|
-
for (const month of await safeReaddir(
|
|
1494
|
+
for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
|
|
958
1495
|
if (!month.isDirectory()) continue;
|
|
959
|
-
for (const day of await safeReaddir(
|
|
1496
|
+
for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
|
|
960
1497
|
if (!day.isDirectory()) continue;
|
|
961
|
-
const dir =
|
|
1498
|
+
const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
|
|
962
1499
|
const entries = await safeReaddir(dir);
|
|
963
1500
|
const stats = await Promise.all(
|
|
964
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1501
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
|
|
965
1502
|
);
|
|
966
1503
|
for (const s of stats) if (s) out.push(s);
|
|
967
1504
|
}
|
|
@@ -970,7 +1507,7 @@ async function codexCandidates(root) {
|
|
|
970
1507
|
return out;
|
|
971
1508
|
}
|
|
972
1509
|
async function readSlice(file, start, length) {
|
|
973
|
-
const handle = await
|
|
1510
|
+
const handle = await import_promises4.default.open(file, "r");
|
|
974
1511
|
try {
|
|
975
1512
|
const buffer = Buffer.alloc(length);
|
|
976
1513
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1043,9 +1580,9 @@ async function readCodexIndex(file) {
|
|
|
1043
1580
|
const names = /* @__PURE__ */ new Map();
|
|
1044
1581
|
let text;
|
|
1045
1582
|
try {
|
|
1046
|
-
const stat = await
|
|
1583
|
+
const stat = await import_promises4.default.stat(file);
|
|
1047
1584
|
const budget = 4 * 1024 * 1024;
|
|
1048
|
-
text = stat.size <= budget ? await
|
|
1585
|
+
text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1049
1586
|
} catch {
|
|
1050
1587
|
return names;
|
|
1051
1588
|
}
|
|
@@ -1166,7 +1703,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1166
1703
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1167
1704
|
let size;
|
|
1168
1705
|
try {
|
|
1169
|
-
size = (await
|
|
1706
|
+
size = (await import_promises4.default.stat(file)).size;
|
|
1170
1707
|
} catch {
|
|
1171
1708
|
return {
|
|
1172
1709
|
items: [],
|
|
@@ -1182,7 +1719,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
|
|
|
1182
1719
|
let droppedFirst = start > 0;
|
|
1183
1720
|
let carry = "";
|
|
1184
1721
|
await new Promise((resolve, reject) => {
|
|
1185
|
-
const stream = (0,
|
|
1722
|
+
const stream = (0, import_node_fs2.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
|
|
1186
1723
|
stream.on("data", (chunk) => {
|
|
1187
1724
|
const text = carry + String(chunk);
|
|
1188
1725
|
const lines = text.split("\n");
|
|
@@ -1220,9 +1757,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1220
1757
|
}
|
|
1221
1758
|
|
|
1222
1759
|
// src/engine/preferences.ts
|
|
1223
|
-
var
|
|
1224
|
-
var
|
|
1225
|
-
var
|
|
1760
|
+
var import_node_crypto3 = require("crypto");
|
|
1761
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
1762
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1226
1763
|
var PreferenceStore = class {
|
|
1227
1764
|
constructor(dir) {
|
|
1228
1765
|
this.dir = dir;
|
|
@@ -1231,11 +1768,11 @@ var PreferenceStore = class {
|
|
|
1231
1768
|
data = {};
|
|
1232
1769
|
writing = Promise.resolve();
|
|
1233
1770
|
get file() {
|
|
1234
|
-
return
|
|
1771
|
+
return import_node_path6.default.join(this.dir, "preferences.json");
|
|
1235
1772
|
}
|
|
1236
1773
|
async load() {
|
|
1237
1774
|
try {
|
|
1238
|
-
const parsed = JSON.parse(await
|
|
1775
|
+
const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
|
|
1239
1776
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1240
1777
|
} catch {
|
|
1241
1778
|
this.data = {};
|
|
@@ -1273,10 +1810,10 @@ var PreferenceStore = class {
|
|
|
1273
1810
|
persist() {
|
|
1274
1811
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1275
1812
|
this.writing = this.writing.then(async () => {
|
|
1276
|
-
await
|
|
1277
|
-
const tmp = `${this.file}.${(0,
|
|
1278
|
-
await
|
|
1279
|
-
await
|
|
1813
|
+
await import_promises5.default.mkdir(this.dir, { recursive: true });
|
|
1814
|
+
const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
|
|
1815
|
+
await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
1816
|
+
await import_promises5.default.rename(tmp, this.file);
|
|
1280
1817
|
}).catch(() => {
|
|
1281
1818
|
});
|
|
1282
1819
|
}
|
|
@@ -1286,7 +1823,7 @@ var PreferenceStore = class {
|
|
|
1286
1823
|
};
|
|
1287
1824
|
|
|
1288
1825
|
// src/engine/search.ts
|
|
1289
|
-
var
|
|
1826
|
+
var import_node_fs3 = require("fs");
|
|
1290
1827
|
function findInLine(provider, value, needle) {
|
|
1291
1828
|
const items = provider === "claude" ? createClaudeNormalizer().push(value) : createCodexRolloutNormalizer().push(value);
|
|
1292
1829
|
for (const item of items) {
|
|
@@ -1327,7 +1864,7 @@ async function scanFile(provider, file, needle, raw, deadline) {
|
|
|
1327
1864
|
return new Promise((resolve) => {
|
|
1328
1865
|
let carry = Buffer.alloc(0);
|
|
1329
1866
|
let done = false;
|
|
1330
|
-
const stream = (0,
|
|
1867
|
+
const stream = (0, import_node_fs3.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
|
|
1331
1868
|
const finish = (value) => {
|
|
1332
1869
|
if (done) return;
|
|
1333
1870
|
done = true;
|
|
@@ -1433,9 +1970,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1433
1970
|
}
|
|
1434
1971
|
|
|
1435
1972
|
// src/engine/store.ts
|
|
1436
|
-
var
|
|
1437
|
-
var
|
|
1438
|
-
var
|
|
1973
|
+
var import_node_crypto4 = require("crypto");
|
|
1974
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
1975
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1439
1976
|
var MAX_RUNS = 2e3;
|
|
1440
1977
|
var RunIndex = class {
|
|
1441
1978
|
constructor(dir) {
|
|
@@ -1445,12 +1982,12 @@ var RunIndex = class {
|
|
|
1445
1982
|
data = { version: 1, sessions: [], runs: [] };
|
|
1446
1983
|
writing = Promise.resolve();
|
|
1447
1984
|
get file() {
|
|
1448
|
-
return
|
|
1985
|
+
return import_node_path7.default.join(this.dir, "index.json");
|
|
1449
1986
|
}
|
|
1450
1987
|
async load() {
|
|
1451
|
-
await
|
|
1988
|
+
await import_promises6.default.mkdir(this.dir, { recursive: true });
|
|
1452
1989
|
try {
|
|
1453
|
-
const parsed = JSON.parse(await
|
|
1990
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
|
|
1454
1991
|
this.data = {
|
|
1455
1992
|
version: 1,
|
|
1456
1993
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1492,9 +2029,9 @@ var RunIndex = class {
|
|
|
1492
2029
|
persist() {
|
|
1493
2030
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1494
2031
|
this.writing = this.writing.then(async () => {
|
|
1495
|
-
const tmp = `${this.file}.${(0,
|
|
1496
|
-
await
|
|
1497
|
-
await
|
|
2032
|
+
const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
2033
|
+
await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
2034
|
+
await import_promises6.default.rename(tmp, this.file);
|
|
1498
2035
|
}).catch(() => {
|
|
1499
2036
|
});
|
|
1500
2037
|
}
|
|
@@ -1504,27 +2041,27 @@ var RunIndex = class {
|
|
|
1504
2041
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1505
2042
|
* driver's scratch state is reused rather than growing per run. */
|
|
1506
2043
|
async identity() {
|
|
1507
|
-
const file =
|
|
2044
|
+
const file = import_node_path7.default.join(this.dir, "identity.json");
|
|
1508
2045
|
try {
|
|
1509
|
-
const parsed = JSON.parse(await
|
|
2046
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
|
|
1510
2047
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1511
2048
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1512
2049
|
}
|
|
1513
2050
|
} catch {
|
|
1514
2051
|
}
|
|
1515
|
-
const identity = { companyId: (0,
|
|
1516
|
-
await
|
|
2052
|
+
const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
|
|
2053
|
+
await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1517
2054
|
return identity;
|
|
1518
2055
|
}
|
|
1519
2056
|
};
|
|
1520
2057
|
|
|
1521
2058
|
// src/engine/user-defaults.ts
|
|
1522
|
-
var
|
|
1523
|
-
var
|
|
1524
|
-
var
|
|
2059
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
2060
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
2061
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1525
2062
|
async function readJson(file) {
|
|
1526
2063
|
try {
|
|
1527
|
-
const parsed = JSON.parse(await
|
|
2064
|
+
const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
|
|
1528
2065
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1529
2066
|
} catch {
|
|
1530
2067
|
return null;
|
|
@@ -1533,10 +2070,10 @@ async function readJson(file) {
|
|
|
1533
2070
|
function str3(value) {
|
|
1534
2071
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1535
2072
|
}
|
|
1536
|
-
async function claudeDefaults(cwd, home =
|
|
2073
|
+
async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
1537
2074
|
const files = [
|
|
1538
|
-
...cwd ? [
|
|
1539
|
-
|
|
2075
|
+
...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
|
|
2076
|
+
import_node_path8.default.join(home, ".claude", "settings.json")
|
|
1540
2077
|
];
|
|
1541
2078
|
let model = null;
|
|
1542
2079
|
let effort = null;
|
|
@@ -1549,10 +2086,10 @@ async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
|
|
|
1549
2086
|
}
|
|
1550
2087
|
return { model, effort };
|
|
1551
2088
|
}
|
|
1552
|
-
async function codexDefaults(home =
|
|
2089
|
+
async function codexDefaults(home = import_node_os4.default.homedir()) {
|
|
1553
2090
|
let text;
|
|
1554
2091
|
try {
|
|
1555
|
-
text = await
|
|
2092
|
+
text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
|
|
1556
2093
|
} catch {
|
|
1557
2094
|
return { model: null, effort: null };
|
|
1558
2095
|
}
|
|
@@ -1576,45 +2113,33 @@ var EFFORT_LEVELS = {
|
|
|
1576
2113
|
// src/engine/index.ts
|
|
1577
2114
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1578
2115
|
function moduleDir() {
|
|
1579
|
-
return
|
|
2116
|
+
return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1580
2117
|
}
|
|
1581
2118
|
function resolveWorkerPath() {
|
|
1582
2119
|
const dir = moduleDir();
|
|
1583
2120
|
for (const candidate of [
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
2121
|
+
import_node_path9.default.join(dir, "worker.js"),
|
|
2122
|
+
import_node_path9.default.join(dir, "worker.ts"),
|
|
2123
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.js"),
|
|
2124
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.ts")
|
|
1588
2125
|
]) {
|
|
1589
|
-
if ((0,
|
|
2126
|
+
if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
|
|
1590
2127
|
}
|
|
1591
2128
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1592
2129
|
}
|
|
1593
2130
|
async function installShim(dataDir) {
|
|
1594
|
-
const source =
|
|
1595
|
-
if (!(0,
|
|
1596
|
-
const body = await
|
|
1597
|
-
const target =
|
|
1598
|
-
const current = await
|
|
2131
|
+
const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
2132
|
+
if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
2133
|
+
const body = await import_promises8.default.readFile(source, "utf8");
|
|
2134
|
+
const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
|
|
2135
|
+
const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
|
|
1599
2136
|
if (current !== body) {
|
|
1600
|
-
await
|
|
1601
|
-
await
|
|
2137
|
+
await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
|
|
2138
|
+
await import_promises8.default.writeFile(target, body, { mode: 493 });
|
|
1602
2139
|
}
|
|
1603
|
-
await
|
|
2140
|
+
await import_promises8.default.chmod(target, 493);
|
|
1604
2141
|
return target;
|
|
1605
2142
|
}
|
|
1606
|
-
function whichOnPath(name, pathValue) {
|
|
1607
|
-
for (const dir of pathValue.split(import_node_path6.default.delimiter)) {
|
|
1608
|
-
if (!dir) continue;
|
|
1609
|
-
const candidate = import_node_path6.default.join(dir, name);
|
|
1610
|
-
try {
|
|
1611
|
-
(0, import_node_fs3.accessSync)(candidate, import_node_fs3.constants.X_OK);
|
|
1612
|
-
return candidate;
|
|
1613
|
-
} catch {
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
return null;
|
|
1617
|
-
}
|
|
1618
2143
|
function nowIso() {
|
|
1619
2144
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1620
2145
|
}
|
|
@@ -1653,12 +2178,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
|
|
|
1653
2178
|
function describeAuthStatus(provider, probe) {
|
|
1654
2179
|
const label = PROVIDER_LABEL[provider];
|
|
1655
2180
|
const installed = probe?.installed === true;
|
|
1656
|
-
const
|
|
2181
|
+
const binary = probe?.binary ?? null;
|
|
2182
|
+
const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
|
|
1657
2183
|
const base = {
|
|
1658
2184
|
provider,
|
|
1659
2185
|
label,
|
|
1660
2186
|
installed,
|
|
1661
2187
|
version,
|
|
2188
|
+
binary,
|
|
1662
2189
|
loggedIn: false,
|
|
1663
2190
|
billing: "unknown",
|
|
1664
2191
|
plan: null,
|
|
@@ -1710,29 +2237,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1710
2237
|
};
|
|
1711
2238
|
}
|
|
1712
2239
|
async function createCodingSessionsEngine(options = {}) {
|
|
1713
|
-
const dataDir = options.dataDir ??
|
|
1714
|
-
const stateDir =
|
|
2240
|
+
const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
|
|
2241
|
+
const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
|
|
1715
2242
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1716
2243
|
const index = new RunIndex(dataDir);
|
|
1717
2244
|
await index.load();
|
|
1718
2245
|
const preferences = new PreferenceStore(dataDir);
|
|
1719
2246
|
await preferences.load();
|
|
1720
|
-
await
|
|
2247
|
+
await import_promises8.default.mkdir(stateDir, { recursive: true });
|
|
1721
2248
|
const identity = await index.identity();
|
|
2249
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1722
2250
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1723
2251
|
sink: options.rawBackup,
|
|
1724
2252
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1725
2253
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1726
2254
|
}) : null;
|
|
1727
2255
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1728
|
-
const healthFile =
|
|
2256
|
+
const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
|
|
1729
2257
|
let health = {};
|
|
1730
2258
|
try {
|
|
1731
|
-
health = JSON.parse(await
|
|
2259
|
+
health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
|
|
1732
2260
|
} catch {
|
|
1733
2261
|
health = {};
|
|
1734
2262
|
}
|
|
1735
|
-
const saveHealth = () =>
|
|
2263
|
+
const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1736
2264
|
function noteRunOutcome(provider, status, texts) {
|
|
1737
2265
|
if (status === "completed") {
|
|
1738
2266
|
if (health[provider]) {
|
|
@@ -1760,7 +2288,24 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1760
2288
|
// Marks the forked process as a driver worker (see worker.ts).
|
|
1761
2289
|
MATRX_CODING_SESSIONS_WORKER: "1"
|
|
1762
2290
|
});
|
|
1763
|
-
const
|
|
2291
|
+
const binaryHome = options.hostEnv?.HOME ?? import_node_os5.default.homedir();
|
|
2292
|
+
const binaryCache = /* @__PURE__ */ new Map();
|
|
2293
|
+
function binaryFor(provider, fresh = false) {
|
|
2294
|
+
const cached = binaryCache.get(provider);
|
|
2295
|
+
if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
|
|
2296
|
+
const forced = options.commands?.[provider];
|
|
2297
|
+
const value = resolveBinary(provider, {
|
|
2298
|
+
env: workerEnv,
|
|
2299
|
+
home: binaryHome,
|
|
2300
|
+
forced,
|
|
2301
|
+
// A forced command needs no discovery to run; the status check still
|
|
2302
|
+
// looks, so it can say when a newer copy exists.
|
|
2303
|
+
discover: !forced || fresh
|
|
2304
|
+
}).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
|
|
2305
|
+
if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
|
|
2306
|
+
return value;
|
|
2307
|
+
}
|
|
2308
|
+
const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
|
|
1764
2309
|
const workerPath = resolveWorkerPath();
|
|
1765
2310
|
const shimPath = await installShim(dataDir);
|
|
1766
2311
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1810,7 +2355,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1810
2355
|
}
|
|
1811
2356
|
}
|
|
1812
2357
|
function forkWorker() {
|
|
1813
|
-
return (0,
|
|
2358
|
+
return (0, import_node_child_process3.fork)(workerPath, [], {
|
|
1814
2359
|
env: workerEnv,
|
|
1815
2360
|
cwd: dataDir,
|
|
1816
2361
|
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
@@ -1831,8 +2376,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1831
2376
|
}
|
|
1832
2377
|
async function startRun(session, prompt) {
|
|
1833
2378
|
const provider = session.provider;
|
|
1834
|
-
const realCommand = commandFor(provider);
|
|
1835
|
-
const runId = (0,
|
|
2379
|
+
const realCommand = await commandFor(provider);
|
|
2380
|
+
const runId = (0, import_node_crypto5.randomUUID)();
|
|
1836
2381
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
1837
2382
|
const run = {
|
|
1838
2383
|
runId,
|
|
@@ -1870,7 +2415,26 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1870
2415
|
let fileOffset = null;
|
|
1871
2416
|
if (session.providerSessionId) {
|
|
1872
2417
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
1873
|
-
if (file) fileOffset = (await
|
|
2418
|
+
if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
|
|
2419
|
+
}
|
|
2420
|
+
let agentArgs = [];
|
|
2421
|
+
if (session.agent) {
|
|
2422
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2423
|
+
if (!bundle) {
|
|
2424
|
+
const failed = saveRun({
|
|
2425
|
+
...run,
|
|
2426
|
+
status: "failed",
|
|
2427
|
+
endedAt: nowIso(),
|
|
2428
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2429
|
+
});
|
|
2430
|
+
const current = index.session(session.id);
|
|
2431
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2432
|
+
return failed;
|
|
2433
|
+
}
|
|
2434
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2435
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2436
|
+
});
|
|
2437
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
1874
2438
|
}
|
|
1875
2439
|
const child = forkWorker();
|
|
1876
2440
|
const liveRun = {
|
|
@@ -2044,7 +2608,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2044
2608
|
resumeSessionId: session.providerSessionId,
|
|
2045
2609
|
shimPath,
|
|
2046
2610
|
realCommand,
|
|
2047
|
-
identity
|
|
2611
|
+
identity,
|
|
2612
|
+
agentArgs
|
|
2048
2613
|
});
|
|
2049
2614
|
return run;
|
|
2050
2615
|
}
|
|
@@ -2063,7 +2628,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2063
2628
|
}, 3e3);
|
|
2064
2629
|
}
|
|
2065
2630
|
async function pruneDriverScratch() {
|
|
2066
|
-
const runsDir =
|
|
2631
|
+
const runsDir = import_node_path9.default.join(
|
|
2067
2632
|
stateDir,
|
|
2068
2633
|
"instances",
|
|
2069
2634
|
"default",
|
|
@@ -2074,12 +2639,13 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2074
2639
|
"claude-runtime",
|
|
2075
2640
|
"runs"
|
|
2076
2641
|
);
|
|
2077
|
-
const entries = await
|
|
2642
|
+
const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
|
|
2078
2643
|
await Promise.all(
|
|
2079
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2644
|
+
entries.filter((name) => !live.has(name)).map((name) => import_promises8.default.rm(import_node_path9.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
|
|
2080
2645
|
);
|
|
2081
2646
|
}
|
|
2082
2647
|
async function probe() {
|
|
2648
|
+
const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
|
|
2083
2649
|
return new Promise((resolve) => {
|
|
2084
2650
|
const child = forkWorker();
|
|
2085
2651
|
let answered = false;
|
|
@@ -2095,8 +2661,8 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2095
2661
|
answered = true;
|
|
2096
2662
|
clearTimeout(timer);
|
|
2097
2663
|
resolve({
|
|
2098
|
-
claude: message.claude ?? {},
|
|
2099
|
-
codex: message.codex ?? {}
|
|
2664
|
+
claude: { ...message.claude ?? {}, binary: claudeBinary },
|
|
2665
|
+
codex: { ...message.codex ?? {}, binary: codexBinary }
|
|
2100
2666
|
});
|
|
2101
2667
|
}
|
|
2102
2668
|
});
|
|
@@ -2108,7 +2674,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2108
2674
|
});
|
|
2109
2675
|
child.send({
|
|
2110
2676
|
kind: "probe",
|
|
2111
|
-
commands: { claude:
|
|
2677
|
+
commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
|
|
2112
2678
|
quota: options.quota !== false
|
|
2113
2679
|
});
|
|
2114
2680
|
});
|
|
@@ -2116,7 +2682,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2116
2682
|
function newSession(input) {
|
|
2117
2683
|
const at = nowIso();
|
|
2118
2684
|
return {
|
|
2119
|
-
id: (0,
|
|
2685
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
2120
2686
|
provider: input.provider,
|
|
2121
2687
|
providerSessionId: input.providerSessionId,
|
|
2122
2688
|
cwd: input.cwd,
|
|
@@ -2134,17 +2700,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2134
2700
|
function resolveFolder(raw) {
|
|
2135
2701
|
const trimmed = raw.trim();
|
|
2136
2702
|
if (!trimmed) return null;
|
|
2137
|
-
return
|
|
2703
|
+
return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2138
2704
|
}
|
|
2139
2705
|
async function checkFolder(raw) {
|
|
2140
2706
|
const target = resolveFolder(raw);
|
|
2141
2707
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2142
|
-
const stat = await
|
|
2708
|
+
const stat = await import_promises8.default.stat(target).catch(() => null);
|
|
2143
2709
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2144
2710
|
}
|
|
2145
2711
|
async function assertFolder(cwd) {
|
|
2146
|
-
const resolved =
|
|
2147
|
-
const stat = await
|
|
2712
|
+
const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2713
|
+
const stat = await import_promises8.default.stat(resolved).catch(() => null);
|
|
2148
2714
|
if (!stat?.isDirectory()) {
|
|
2149
2715
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2150
2716
|
}
|
|
@@ -2206,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2206
2772
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2207
2773
|
path: folder,
|
|
2208
2774
|
lastUsedAt,
|
|
2209
|
-
exists: (await
|
|
2775
|
+
exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2210
2776
|
}))
|
|
2211
2777
|
);
|
|
2212
2778
|
},
|
|
@@ -2251,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2251
2817
|
async createFolder(raw) {
|
|
2252
2818
|
const target = resolveFolder(raw);
|
|
2253
2819
|
if (!target) throw new Error("Type a folder path first.");
|
|
2254
|
-
await
|
|
2820
|
+
await import_promises8.default.mkdir(target, { recursive: true });
|
|
2255
2821
|
return checkFolder(target);
|
|
2256
2822
|
},
|
|
2257
2823
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2289,31 +2855,35 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2289
2855
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2290
2856
|
};
|
|
2291
2857
|
}
|
|
2292
|
-
const stat = await
|
|
2858
|
+
const stat = await import_promises8.default.stat(file);
|
|
2293
2859
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2294
2860
|
return {
|
|
2295
2861
|
available: false,
|
|
2296
2862
|
reason: `This transcript is ${(0, import_format.formatFileSize)(stat.size)}, larger than the ${(0, import_format.formatFileSize)(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
|
|
2297
2863
|
};
|
|
2298
2864
|
}
|
|
2299
|
-
const content = await
|
|
2300
|
-
return { available: true, fileName:
|
|
2865
|
+
const content = await import_promises8.default.readFile(file, "utf8");
|
|
2866
|
+
return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
|
|
2301
2867
|
},
|
|
2302
2868
|
async start(input) {
|
|
2303
2869
|
const prompt = input.prompt.trim();
|
|
2304
2870
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2305
2871
|
const cwd = await assertFolder(input.cwd);
|
|
2306
|
-
const
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
)
|
|
2872
|
+
const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
|
|
2873
|
+
const draft = newSession({
|
|
2874
|
+
provider: input.provider,
|
|
2875
|
+
cwd,
|
|
2876
|
+
title: titleFrom(prompt),
|
|
2877
|
+
model: input.model?.trim() || null,
|
|
2878
|
+
effort: input.effort?.trim() || null,
|
|
2879
|
+
origin: "started",
|
|
2880
|
+
providerSessionId: null
|
|
2881
|
+
});
|
|
2882
|
+
if (bundle) {
|
|
2883
|
+
await agents.saveSessionBundle(draft.id, bundle);
|
|
2884
|
+
draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
|
|
2885
|
+
}
|
|
2886
|
+
const session = saveSession(draft);
|
|
2317
2887
|
const run = await startRun(session, prompt);
|
|
2318
2888
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2319
2889
|
},
|
|
@@ -2382,6 +2952,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2382
2952
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2383
2953
|
return saveSession(next);
|
|
2384
2954
|
},
|
|
2955
|
+
async agentAccess() {
|
|
2956
|
+
return agents.access();
|
|
2957
|
+
},
|
|
2958
|
+
async agentCatalogRpc(request) {
|
|
2959
|
+
return agents.catalogRpc(request);
|
|
2960
|
+
},
|
|
2961
|
+
async agentDetails(agentId) {
|
|
2962
|
+
return agents.details(String(agentId ?? ""));
|
|
2963
|
+
},
|
|
2964
|
+
setMcpBaseUrl(baseUrl) {
|
|
2965
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
2966
|
+
},
|
|
2967
|
+
async handleMcp(secret, body) {
|
|
2968
|
+
return agents.handleMcp(secret, body);
|
|
2969
|
+
},
|
|
2385
2970
|
async dispose() {
|
|
2386
2971
|
for (const liveRun of live.values()) {
|
|
2387
2972
|
liveRun.stopRequested = true;
|
|
@@ -2418,7 +3003,7 @@ function openFolderIn(folder, target) {
|
|
|
2418
3003
|
args = ["-a", app.app, folder];
|
|
2419
3004
|
}
|
|
2420
3005
|
return new Promise((resolve, reject) => {
|
|
2421
|
-
(0,
|
|
3006
|
+
(0, import_node_child_process3.execFile)(command, args, { timeout: 1e4 }, (error) => {
|
|
2422
3007
|
if (!error) return resolve();
|
|
2423
3008
|
const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
|
|
2424
3009
|
reject(
|
|
@@ -2451,7 +3036,10 @@ var BRIDGE_METHODS = [
|
|
|
2451
3036
|
"setChatPreference",
|
|
2452
3037
|
"searchChats",
|
|
2453
3038
|
"openFolder",
|
|
2454
|
-
"setSessionOptions"
|
|
3039
|
+
"setSessionOptions",
|
|
3040
|
+
"agentAccess",
|
|
3041
|
+
"agentCatalogRpc",
|
|
3042
|
+
"agentDetails"
|
|
2455
3043
|
];
|
|
2456
3044
|
var BridgeError = class extends Error {
|
|
2457
3045
|
constructor(message, code) {
|
|
@@ -2495,14 +3083,14 @@ var MIME = {
|
|
|
2495
3083
|
".map": "application/json"
|
|
2496
3084
|
};
|
|
2497
3085
|
function moduleDir2() {
|
|
2498
|
-
return
|
|
3086
|
+
return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
2499
3087
|
}
|
|
2500
3088
|
function defaultUiDir() {
|
|
2501
3089
|
for (const candidate of [
|
|
2502
|
-
|
|
2503
|
-
|
|
3090
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
|
|
3091
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
|
|
2504
3092
|
]) {
|
|
2505
|
-
if ((0,
|
|
3093
|
+
if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
|
|
2506
3094
|
}
|
|
2507
3095
|
return null;
|
|
2508
3096
|
}
|
|
@@ -2510,7 +3098,7 @@ function tokensMatch(expected, given) {
|
|
|
2510
3098
|
if (!given) return false;
|
|
2511
3099
|
const a = Buffer.from(expected);
|
|
2512
3100
|
const b = Buffer.from(given);
|
|
2513
|
-
return a.length === b.length && (0,
|
|
3101
|
+
return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
|
|
2514
3102
|
}
|
|
2515
3103
|
var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
|
|
2516
3104
|
<body style="font:15px system-ui;padding:48px;max-width:560px">
|
|
@@ -2525,19 +3113,19 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2525
3113
|
return;
|
|
2526
3114
|
}
|
|
2527
3115
|
const requested = decodeURIComponent(url.pathname);
|
|
2528
|
-
let file =
|
|
3116
|
+
let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
|
|
2529
3117
|
if (!file.startsWith(uiDir)) {
|
|
2530
3118
|
res.writeHead(403).end();
|
|
2531
3119
|
return;
|
|
2532
3120
|
}
|
|
2533
|
-
const stat = await
|
|
2534
|
-
if (!stat || stat.isDirectory()) file =
|
|
2535
|
-
const body = await
|
|
3121
|
+
const stat = await import_promises9.default.stat(file).catch(() => null);
|
|
3122
|
+
if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
|
|
3123
|
+
const body = await import_promises9.default.readFile(file).catch(() => null);
|
|
2536
3124
|
if (!body) {
|
|
2537
3125
|
res.writeHead(404).end();
|
|
2538
3126
|
return;
|
|
2539
3127
|
}
|
|
2540
|
-
const ext =
|
|
3128
|
+
const ext = import_node_path10.default.extname(file);
|
|
2541
3129
|
res.writeHead(200, {
|
|
2542
3130
|
"content-type": MIME[ext] ?? "application/octet-stream",
|
|
2543
3131
|
"cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
|
|
@@ -2546,6 +3134,46 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2546
3134
|
});
|
|
2547
3135
|
res.end(body);
|
|
2548
3136
|
}
|
|
3137
|
+
var MAX_MCP_BODY = 4 * 1024 * 1024;
|
|
3138
|
+
async function handleMcp(engine, secret, req, res, allowedOrigin) {
|
|
3139
|
+
if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
|
|
3140
|
+
res.writeHead(403).end();
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
if (req.method !== "POST") {
|
|
3144
|
+
res.writeHead(405, { allow: "POST" }).end();
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
const chunks = [];
|
|
3148
|
+
let size = 0;
|
|
3149
|
+
for await (const chunk of req) {
|
|
3150
|
+
size += chunk.length;
|
|
3151
|
+
if (size > MAX_MCP_BODY) {
|
|
3152
|
+
res.writeHead(413).end();
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
chunks.push(chunk);
|
|
3156
|
+
}
|
|
3157
|
+
let body;
|
|
3158
|
+
try {
|
|
3159
|
+
body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
3160
|
+
} catch {
|
|
3161
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3162
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
|
|
3166
|
+
if (!reply) {
|
|
3167
|
+
res.writeHead(404).end();
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
if (reply.body === void 0) {
|
|
3171
|
+
res.writeHead(reply.status).end();
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
res.writeHead(reply.status, { "content-type": "application/json" });
|
|
3175
|
+
res.end(JSON.stringify(reply.body));
|
|
3176
|
+
}
|
|
2549
3177
|
function attachSocket(socket, bridge) {
|
|
2550
3178
|
const unsubscribe = bridge.subscribe((event) => {
|
|
2551
3179
|
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
|
|
@@ -2576,7 +3204,7 @@ function attachSocket(socket, bridge) {
|
|
|
2576
3204
|
socket.on("close", unsubscribe);
|
|
2577
3205
|
}
|
|
2578
3206
|
async function startCodingSessionsHost(options = {}) {
|
|
2579
|
-
const token = options.token ?? (0,
|
|
3207
|
+
const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
|
|
2580
3208
|
const engine = options.engine ?? await createCodingSessionsEngine(options);
|
|
2581
3209
|
const bridge = createHostBridge(engine);
|
|
2582
3210
|
const uiDir = options.uiDir ?? defaultUiDir();
|
|
@@ -2587,6 +3215,13 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2587
3215
|
};
|
|
2588
3216
|
const server = import_node_http.default.createServer((req, res) => {
|
|
2589
3217
|
const url2 = new URL(req.url ?? "/", `http://${HOST}`);
|
|
3218
|
+
if (url2.pathname.startsWith("/mcp/")) {
|
|
3219
|
+
handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
|
|
3220
|
+
if (!res.headersSent) res.writeHead(500);
|
|
3221
|
+
res.end();
|
|
3222
|
+
});
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
2590
3225
|
if (url2.pathname === "/health") {
|
|
2591
3226
|
res.writeHead(200, { "content-type": "application/json" });
|
|
2592
3227
|
res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
|
|
@@ -2617,6 +3252,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2617
3252
|
server.off("error", reject);
|
|
2618
3253
|
const address = server.address();
|
|
2619
3254
|
if (address && typeof address === "object") port = address.port;
|
|
3255
|
+
engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
|
|
2620
3256
|
resolve();
|
|
2621
3257
|
});
|
|
2622
3258
|
});
|
|
@@ -2627,6 +3263,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2627
3263
|
token,
|
|
2628
3264
|
engine,
|
|
2629
3265
|
async close() {
|
|
3266
|
+
engine.setMcpBaseUrl?.(null);
|
|
2630
3267
|
for (const client of wss.clients) client.terminate();
|
|
2631
3268
|
await new Promise((resolve) => wss.close(() => resolve()));
|
|
2632
3269
|
await new Promise((resolve) => server.close(() => resolve()));
|