@ai-matrx/coding-sessions 0.1.83 → 0.1.86
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 +18 -0
- package/dist/engine/index.cjs +656 -126
- 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 +654 -126
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +7 -5
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-DQ0teQvw.d.cts → index-aavbdzSo.d.cts} +161 -1
- package/dist/{index-DQ0teQvw.d.ts → index-aavbdzSo.d.ts} +161 -1
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -1
- package/dist/index.d.ts +112 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +821 -412
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +113 -8
- package/dist/react/index.d.ts +113 -8
- package/dist/react/index.js +851 -418
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +720 -141
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +5 -1
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.js +719 -140
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/index-XIcCZr8A.js +71 -0
- package/dist/ui/assets/index-eqe0sxR4.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +3 -1
- package/dist/ui/assets/index-C-_1qjy9.css +0 -2
- package/dist/ui/assets/index-CmFgFvLa.js +0 -69
package/dist/server/index.cjs
CHANGED
|
@@ -43,17 +43,17 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
43
43
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
44
44
|
|
|
45
45
|
// src/server/index.ts
|
|
46
|
-
var
|
|
46
|
+
var import_node_crypto6 = require("crypto");
|
|
47
47
|
var import_node_fs5 = require("fs");
|
|
48
|
-
var
|
|
48
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
49
49
|
var import_node_http = __toESM(require("http"), 1);
|
|
50
|
-
var
|
|
50
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
51
51
|
var import_node_url2 = require("url");
|
|
52
52
|
var import_ws = require("ws");
|
|
53
53
|
|
|
54
54
|
// src/engine/index.ts
|
|
55
55
|
var import_node_child_process3 = require("child_process");
|
|
56
|
-
var
|
|
56
|
+
var import_node_crypto5 = require("crypto");
|
|
57
57
|
var import_node_fs4 = require("fs");
|
|
58
58
|
var import_format = require("@ai-matrx/kit/format");
|
|
59
59
|
|
|
@@ -114,10 +114,448 @@ 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
|
+
|
|
121
|
+
// src/normalize/agent-block.ts
|
|
122
|
+
var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
|
|
123
|
+
function stripAgentBlock(text) {
|
|
124
|
+
const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
|
|
125
|
+
if (removed) return text.slice(removed[0].length);
|
|
126
|
+
if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
|
|
127
|
+
const end = text.indexOf("</ai_matrx_agent>");
|
|
128
|
+
if (end < 0) return text;
|
|
129
|
+
let rest = text.slice(end + "</ai_matrx_agent>".length);
|
|
130
|
+
const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
|
|
131
|
+
if (skills) rest = rest.slice(skills[0].length);
|
|
132
|
+
return rest.replace(/^\s+/, "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/engine/agent-attach.ts
|
|
136
|
+
var AGENT_MCP_SERVER_NAME = "matrx-agent";
|
|
137
|
+
var AGENT_PLUGIN_NAME = "matrx-agent";
|
|
138
|
+
function escapeAttr(value) {
|
|
139
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
140
|
+
}
|
|
141
|
+
function versionWords(bundle) {
|
|
142
|
+
return bundle.version_number !== null ? String(bundle.version_number) : "latest";
|
|
143
|
+
}
|
|
144
|
+
function renderAgentInstructions(bundle, options = {}) {
|
|
145
|
+
const included = bundle.skills.filter((skill) => skill.tier === "included");
|
|
146
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
147
|
+
const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
|
|
148
|
+
if (included.length > 0) {
|
|
149
|
+
parts.push(
|
|
150
|
+
"## Skills (always in effect)",
|
|
151
|
+
...included.map((skill) => `### ${skill.label}
|
|
152
|
+
|
|
153
|
+
${skill.body.trim()}`)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (options.claudeSkills && listed2.length > 0) {
|
|
157
|
+
parts.push(
|
|
158
|
+
[
|
|
159
|
+
"## Your skills",
|
|
160
|
+
"When a request matches one of these, load it with your Skill tool and follow it before answering:",
|
|
161
|
+
...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
|
|
162
|
+
].join("\n")
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
|
|
166
|
+
parts.push(bundle.instructions.trim());
|
|
167
|
+
parts.push("</ai_matrx_agent>");
|
|
168
|
+
return `${parts.filter(Boolean).join("\n\n")}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
function renderCodexSkillsList(bundle, skillsRoot) {
|
|
172
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
173
|
+
if (listed2.length === 0) return "";
|
|
174
|
+
const lines = [
|
|
175
|
+
"<skills_instructions>",
|
|
176
|
+
"## AI Matrx agent skills",
|
|
177
|
+
"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.",
|
|
178
|
+
"### Available skills",
|
|
179
|
+
...listed2.map(
|
|
180
|
+
(skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
181
|
+
),
|
|
182
|
+
"</skills_instructions>"
|
|
183
|
+
];
|
|
184
|
+
return lines.join("\n");
|
|
185
|
+
}
|
|
186
|
+
function renderFallbackBlock(bundle, skillsRoot) {
|
|
187
|
+
const skills = renderCodexSkillsList(bundle, skillsRoot);
|
|
188
|
+
const body = renderAgentInstructions(bundle).trim().replace(/^(<ai_matrx_agent [^>]*>)/, "$1\n\nThe instructions below are your AI Matrx agent for this chat from now on; they replace any AI Matrx agent instructions given earlier.");
|
|
189
|
+
return skills ? `${body}
|
|
190
|
+
|
|
191
|
+
${skills}
|
|
192
|
+
|
|
193
|
+
` : `${body}
|
|
194
|
+
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function tomlString(value) {
|
|
198
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
199
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
200
|
+
}
|
|
201
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
202
|
+
const pluginDir = import_node_path.default.join(dir, "plugin");
|
|
203
|
+
const skillsRoot = import_node_path.default.join(dir, "skills");
|
|
204
|
+
await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
205
|
+
await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
206
|
+
const instructions = renderAgentInstructions(bundle);
|
|
207
|
+
const instructionsFile = import_node_path.default.join(dir, "instructions.md");
|
|
208
|
+
await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
209
|
+
await import_promises2.default.writeFile(
|
|
210
|
+
import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
211
|
+
`${JSON.stringify(
|
|
212
|
+
{
|
|
213
|
+
name: AGENT_PLUGIN_NAME,
|
|
214
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
215
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
216
|
+
},
|
|
217
|
+
null,
|
|
218
|
+
2
|
|
219
|
+
)}
|
|
220
|
+
`,
|
|
221
|
+
{ mode: 384 }
|
|
222
|
+
);
|
|
223
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
224
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
225
|
+
for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
|
|
226
|
+
await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
|
|
227
|
+
for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
|
|
228
|
+
if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
for (const skill of listed2) {
|
|
231
|
+
const skillDir = import_node_path.default.join(root, skill.slug);
|
|
232
|
+
await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
233
|
+
await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let mcpConfigFile = null;
|
|
237
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
238
|
+
const mcpPath = import_node_path.default.join(dir, "mcp.json");
|
|
239
|
+
if (mcpUrl) {
|
|
240
|
+
mcpConfigFile = mcpPath;
|
|
241
|
+
await import_promises2.default.writeFile(
|
|
242
|
+
mcpPath,
|
|
243
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
244
|
+
`,
|
|
245
|
+
{ mode: 384 }
|
|
246
|
+
);
|
|
247
|
+
} else {
|
|
248
|
+
await import_promises2.default.rm(mcpPath, { force: true });
|
|
249
|
+
}
|
|
250
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
251
|
+
return {
|
|
252
|
+
dir,
|
|
253
|
+
instructionsFile,
|
|
254
|
+
pluginDir,
|
|
255
|
+
mcpConfigFile,
|
|
256
|
+
mcpUrl,
|
|
257
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
258
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
259
|
+
|
|
260
|
+
${instructions}` : instructions
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function agentArgsFor(provider, attachment, options) {
|
|
264
|
+
if (provider === "claude") {
|
|
265
|
+
const args2 = [
|
|
266
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
267
|
+
"--append-system-prompt-file",
|
|
268
|
+
attachment.instructionsFile,
|
|
269
|
+
"--system-prompt-snapshot",
|
|
270
|
+
"off",
|
|
271
|
+
"--plugin-dir",
|
|
272
|
+
attachment.pluginDir
|
|
273
|
+
];
|
|
274
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
275
|
+
return args2;
|
|
276
|
+
}
|
|
277
|
+
const args = [];
|
|
278
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
279
|
+
if (attachment.mcpUrl) {
|
|
280
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
281
|
+
args.push(
|
|
282
|
+
"-c",
|
|
283
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
284
|
+
"-c",
|
|
285
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return args;
|
|
289
|
+
}
|
|
290
|
+
function sessionAgentRecord(bundle, options) {
|
|
291
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
292
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
293
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
294
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
295
|
+
for (const tool of bundle.tools) {
|
|
296
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
300
|
+
const loaded = [
|
|
301
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
302
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
303
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
304
|
+
];
|
|
305
|
+
return {
|
|
306
|
+
agentId: bundle.agent_id,
|
|
307
|
+
versionId: bundle.version_id,
|
|
308
|
+
versionNumber: bundle.version_number,
|
|
309
|
+
name: bundle.name,
|
|
310
|
+
definitionHash: bundle.definition_hash,
|
|
311
|
+
loaded,
|
|
312
|
+
unavailable,
|
|
313
|
+
skills: bundle.skills.map((skill) => skill.label),
|
|
314
|
+
tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
|
|
315
|
+
...options.variables ? { variables: options.variables } : {}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
var AGENT_REMOVED_BLOCK = "<ai_matrx_agent_removed>The AI Matrx agent instructions given earlier in this chat no longer apply.</ai_matrx_agent_removed>\n\n";
|
|
319
|
+
async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
|
|
320
|
+
const text = await import_promises2.default.readFile(rolloutFile, "utf8").catch(() => "");
|
|
321
|
+
if (!text.includes('"type":"compacted"')) return false;
|
|
322
|
+
let latest = null;
|
|
323
|
+
for (const line of text.split("\n")) {
|
|
324
|
+
if (!line.includes('"type":"compacted"')) continue;
|
|
325
|
+
try {
|
|
326
|
+
const entry = JSON.parse(line);
|
|
327
|
+
if (entry.type === "compacted") latest = entry;
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!latest) return false;
|
|
332
|
+
if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
|
|
333
|
+
return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/engine/agents.ts
|
|
337
|
+
var import_node_crypto2 = require("crypto");
|
|
338
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
339
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
340
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
341
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
342
|
+
var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
|
|
343
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
344
|
+
"agx_get_list_full",
|
|
345
|
+
"agx_search",
|
|
346
|
+
"agx_resolve_agent_address",
|
|
347
|
+
"ues_get_bulk",
|
|
348
|
+
"ues_list",
|
|
349
|
+
"ues_set",
|
|
350
|
+
"ues_touch"
|
|
351
|
+
]);
|
|
352
|
+
var agentsClient = async () => {
|
|
353
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
354
|
+
import("@ai-matrx/agents/matrx"),
|
|
355
|
+
import("@ai-matrx/agents/portable"),
|
|
356
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
357
|
+
]);
|
|
358
|
+
return { ...matrx, ...portable, ...mcp };
|
|
359
|
+
};
|
|
360
|
+
function sortedValues(values) {
|
|
361
|
+
return Object.keys(values).sort().map((key) => [key, values[key]]);
|
|
362
|
+
}
|
|
363
|
+
var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
|
|
364
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
365
|
+
function userIdOf(credential) {
|
|
366
|
+
if (!credential || credential.kind !== "user") return null;
|
|
367
|
+
const payload = credential.accessToken.split(".")[1];
|
|
368
|
+
if (!payload) return null;
|
|
369
|
+
try {
|
|
370
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
371
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
372
|
+
} catch {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function createAgentConnection(options, dataDir) {
|
|
377
|
+
const credentials = options?.credentials ?? null;
|
|
378
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
379
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
380
|
+
const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
|
|
381
|
+
const root = import_node_path2.default.join(dataDir, "agents");
|
|
382
|
+
let mcpBaseUrl = null;
|
|
383
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
384
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
385
|
+
const organizationId = async () => {
|
|
386
|
+
const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
|
|
387
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
388
|
+
};
|
|
389
|
+
const transport = async () => {
|
|
390
|
+
const org = await organizationId();
|
|
391
|
+
return (await agentsClient()).createMatrxTransport({
|
|
392
|
+
baseUrl: serverUrl,
|
|
393
|
+
...credentials ? { credentials } : {},
|
|
394
|
+
...org ? { organizationId: org } : {},
|
|
395
|
+
source: "codingSessionsAgents"
|
|
396
|
+
});
|
|
397
|
+
};
|
|
398
|
+
const cacheDir = import_node_path2.default.join(dataDir, "agents", ".bundles");
|
|
399
|
+
const cacheKey = (definitionHash, selection, surface) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
|
|
400
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
401
|
+
const requireSignedIn = async () => {
|
|
402
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
403
|
+
};
|
|
404
|
+
const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
405
|
+
async function meta(sessionId) {
|
|
406
|
+
const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
|
|
407
|
+
const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
408
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
409
|
+
const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
|
|
410
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
411
|
+
await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
412
|
+
return created;
|
|
413
|
+
}
|
|
414
|
+
async function findSessionBySecret(secret) {
|
|
415
|
+
for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
|
|
416
|
+
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);
|
|
417
|
+
if (found) {
|
|
418
|
+
secretToSession.set(secret, name);
|
|
419
|
+
return name;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
async function readSessionBundle(sessionId) {
|
|
425
|
+
return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
async access() {
|
|
429
|
+
const current = await credential().catch(() => null);
|
|
430
|
+
const userId = userIdOf(current);
|
|
431
|
+
return {
|
|
432
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
433
|
+
signedIn: userId !== null,
|
|
434
|
+
userId,
|
|
435
|
+
organizationId: await organizationId().catch(() => null)
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
async catalogRpc(request) {
|
|
439
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
440
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
441
|
+
}
|
|
442
|
+
const current = await credential();
|
|
443
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
444
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
445
|
+
const query = new URLSearchParams();
|
|
446
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
447
|
+
if (request.range) {
|
|
448
|
+
query.set("offset", String(request.range[0]));
|
|
449
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
450
|
+
}
|
|
451
|
+
const headers = {
|
|
452
|
+
apikey: supabaseKey,
|
|
453
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
456
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
457
|
+
"Content-Profile": "public",
|
|
458
|
+
"Accept-Profile": "public"
|
|
459
|
+
};
|
|
460
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
461
|
+
const qs = query.toString();
|
|
462
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
463
|
+
method: "POST",
|
|
464
|
+
headers,
|
|
465
|
+
body: JSON.stringify(request.args ?? {})
|
|
466
|
+
});
|
|
467
|
+
const text = await response.text();
|
|
468
|
+
const body = text ? JSON.parse(text) : null;
|
|
469
|
+
if (!response.ok) {
|
|
470
|
+
const err = body ?? {};
|
|
471
|
+
return {
|
|
472
|
+
data: null,
|
|
473
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const range = response.headers.get("content-range");
|
|
477
|
+
const total = range?.split("/")[1];
|
|
478
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
479
|
+
},
|
|
480
|
+
async details(agentId) {
|
|
481
|
+
await requireSignedIn();
|
|
482
|
+
const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
|
|
483
|
+
return {
|
|
484
|
+
agentId: summary.agent_id,
|
|
485
|
+
name: summary.name,
|
|
486
|
+
variables: summary.variables,
|
|
487
|
+
versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
|
|
488
|
+
latestVersionNumber: summary.version_number,
|
|
489
|
+
latestVersionId: summary.version_id
|
|
490
|
+
};
|
|
491
|
+
},
|
|
492
|
+
async loadBundle(selection) {
|
|
493
|
+
await requireSignedIn();
|
|
494
|
+
const client = await agentsClient();
|
|
495
|
+
const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
|
|
496
|
+
const versionId = selection.versionId ?? summary.version_id;
|
|
497
|
+
const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
|
|
498
|
+
const surface = "matrx-user/code-editor";
|
|
499
|
+
const file = definitionHash ? import_node_path2.default.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
|
|
500
|
+
if (file) {
|
|
501
|
+
const cached = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
502
|
+
const fresh = await import_promises3.default.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
|
|
503
|
+
if (cached && fresh && cached.definition_hash === definitionHash) return cached;
|
|
504
|
+
}
|
|
505
|
+
const bundle = await client.fetchPortableAgent(
|
|
506
|
+
await transport(),
|
|
507
|
+
versionId ? { versionId } : { agentId: selection.agentId },
|
|
508
|
+
{ variables: selection.variables ?? {}, surface }
|
|
509
|
+
);
|
|
510
|
+
if (file && bundle.definition_hash === definitionHash) {
|
|
511
|
+
await import_promises3.default.mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
512
|
+
await import_promises3.default.writeFile(file, JSON.stringify(bundle), { mode: 384 });
|
|
513
|
+
}
|
|
514
|
+
return bundle;
|
|
515
|
+
},
|
|
516
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
517
|
+
await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
518
|
+
await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
519
|
+
await meta(sessionId);
|
|
520
|
+
},
|
|
521
|
+
readSessionBundle,
|
|
522
|
+
sessionDir,
|
|
523
|
+
async mcpUrlFor(sessionId) {
|
|
524
|
+
if (!mcpBaseUrl) return null;
|
|
525
|
+
const { mcpSecret } = await meta(sessionId);
|
|
526
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
527
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
528
|
+
},
|
|
529
|
+
setMcpBaseUrl(baseUrl) {
|
|
530
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
531
|
+
},
|
|
532
|
+
async handleMcp(secret, body) {
|
|
533
|
+
let handler = handlers.get(secret);
|
|
534
|
+
if (!handler) {
|
|
535
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
536
|
+
if (!sessionId) return null;
|
|
537
|
+
const bundle = await readSessionBundle(sessionId);
|
|
538
|
+
if (!bundle) return null;
|
|
539
|
+
const { conversationId } = await meta(sessionId);
|
|
540
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
541
|
+
handler = createAgentToolsMcpHandler({
|
|
542
|
+
bundle,
|
|
543
|
+
credentials,
|
|
544
|
+
organizationId: () => organizationId(),
|
|
545
|
+
baseUrl: serverUrl,
|
|
546
|
+
conversationId
|
|
547
|
+
});
|
|
548
|
+
handlers.set(secret, handler);
|
|
549
|
+
}
|
|
550
|
+
return handler.handle(body);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
117
555
|
// src/engine/index.ts
|
|
118
|
-
var
|
|
556
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
119
557
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
120
|
-
var
|
|
558
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
121
559
|
var import_node_url = require("url");
|
|
122
560
|
|
|
123
561
|
// src/normalize/claude.ts
|
|
@@ -488,7 +926,7 @@ function rolloutItem(item, fallbackId, at) {
|
|
|
488
926
|
const id = str2(item.id) ?? fallbackId;
|
|
489
927
|
switch (item.type) {
|
|
490
928
|
case "UserMessage": {
|
|
491
|
-
const text = stripCodexInjectedContext(itemText(item.content));
|
|
929
|
+
const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
|
|
492
930
|
return text.trim() ? [{ kind: "user", id, text, at }] : [];
|
|
493
931
|
}
|
|
494
932
|
case "AgentMessage": {
|
|
@@ -571,7 +1009,7 @@ function createCodexRolloutNormalizer() {
|
|
|
571
1009
|
return rolloutItem(payload.item, fallbackId, at);
|
|
572
1010
|
}
|
|
573
1011
|
if (payload.type === "user_message" && !appItems) {
|
|
574
|
-
const text = stripCodexInjectedContext(str2(payload.message) ?? "");
|
|
1012
|
+
const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
|
|
575
1013
|
return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
|
|
576
1014
|
}
|
|
577
1015
|
return [];
|
|
@@ -787,7 +1225,7 @@ function describeProblem(provider, problem) {
|
|
|
787
1225
|
var import_node_child_process = require("child_process");
|
|
788
1226
|
var import_node_fs = require("fs");
|
|
789
1227
|
var import_node_os = __toESM(require("os"), 1);
|
|
790
|
-
var
|
|
1228
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
791
1229
|
var SOURCE_LABEL = {
|
|
792
1230
|
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
793
1231
|
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
@@ -836,10 +1274,10 @@ function exeName(name, platform) {
|
|
|
836
1274
|
}
|
|
837
1275
|
function allOnPath(name, pathValue, platform = process.platform) {
|
|
838
1276
|
const found = [];
|
|
839
|
-
for (const dir of pathValue.split(
|
|
1277
|
+
for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
|
|
840
1278
|
if (!dir) continue;
|
|
841
1279
|
for (const file of exeName(name, platform)) {
|
|
842
|
-
const candidate =
|
|
1280
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
843
1281
|
if (executable(candidate)) {
|
|
844
1282
|
found.push(candidate);
|
|
845
1283
|
break;
|
|
@@ -852,17 +1290,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
|
|
|
852
1290
|
if (platform !== "darwin") return [];
|
|
853
1291
|
if (provider === "codex") {
|
|
854
1292
|
return applications.map(
|
|
855
|
-
(apps) =>
|
|
1293
|
+
(apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
856
1294
|
);
|
|
857
1295
|
}
|
|
858
|
-
const root =
|
|
1296
|
+
const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
859
1297
|
let versions = [];
|
|
860
1298
|
try {
|
|
861
1299
|
versions = (0, import_node_fs.readdirSync)(root);
|
|
862
1300
|
} catch {
|
|
863
1301
|
return [];
|
|
864
1302
|
}
|
|
865
|
-
return versions.map((v) =>
|
|
1303
|
+
return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
866
1304
|
}
|
|
867
1305
|
function npmGlobalPrefix(env) {
|
|
868
1306
|
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
@@ -880,13 +1318,13 @@ function declaredVersion(provider, source, file) {
|
|
|
880
1318
|
if (source !== "desktop-app") return null;
|
|
881
1319
|
if (provider === "codex") {
|
|
882
1320
|
try {
|
|
883
|
-
const pkg = JSON.parse((0, import_node_fs.readFileSync)(
|
|
1321
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.dirname(file)), "codex-package.json"), "utf8"));
|
|
884
1322
|
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
885
1323
|
} catch {
|
|
886
1324
|
return null;
|
|
887
1325
|
}
|
|
888
1326
|
}
|
|
889
|
-
return parseVersion(
|
|
1327
|
+
return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
|
|
890
1328
|
}
|
|
891
1329
|
function runVersion(file, env) {
|
|
892
1330
|
return new Promise((resolve) => {
|
|
@@ -900,14 +1338,14 @@ async function discoverBinaries(provider, options) {
|
|
|
900
1338
|
const platform = options.platform ?? process.platform;
|
|
901
1339
|
const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
|
|
902
1340
|
const listed2 = [
|
|
903
|
-
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications",
|
|
1341
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
904
1342
|
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
905
1343
|
];
|
|
906
1344
|
const prefix = await npmGlobalPrefix(options.env);
|
|
907
1345
|
if (prefix) {
|
|
908
|
-
const dir = platform === "win32" ? prefix :
|
|
1346
|
+
const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
|
|
909
1347
|
for (const file of exeName(provider, platform)) {
|
|
910
|
-
const candidate =
|
|
1348
|
+
const candidate = import_node_path3.default.join(dir, file);
|
|
911
1349
|
if (executable(candidate)) {
|
|
912
1350
|
listed2.push({ path: candidate, source: "npm-global" });
|
|
913
1351
|
break;
|
|
@@ -953,7 +1391,7 @@ async function resolveBinary(provider, options) {
|
|
|
953
1391
|
|
|
954
1392
|
// src/engine/clean-env.ts
|
|
955
1393
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
956
|
-
var
|
|
1394
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
957
1395
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
958
1396
|
"HOME",
|
|
959
1397
|
"USER",
|
|
@@ -975,11 +1413,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
975
1413
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
976
1414
|
function wellKnownBinDirs(home) {
|
|
977
1415
|
return [
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1416
|
+
import_node_path4.default.join(home, ".local", "bin"),
|
|
1417
|
+
import_node_path4.default.join(home, ".claude", "local"),
|
|
1418
|
+
import_node_path4.default.join(home, ".npm-global", "bin"),
|
|
1419
|
+
import_node_path4.default.join(home, ".bun", "bin"),
|
|
1420
|
+
import_node_path4.default.join(home, ".volta", "bin"),
|
|
983
1421
|
"/opt/homebrew/bin",
|
|
984
1422
|
"/usr/local/bin",
|
|
985
1423
|
"/usr/bin",
|
|
@@ -999,21 +1437,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
999
1437
|
}
|
|
1000
1438
|
const home = out.HOME ?? import_node_os2.default.homedir();
|
|
1001
1439
|
out.HOME = home;
|
|
1002
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1440
|
+
const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
|
|
1003
1441
|
const merged = [];
|
|
1004
1442
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
1005
1443
|
if (!merged.includes(dir)) merged.push(dir);
|
|
1006
1444
|
}
|
|
1007
|
-
out.PATH = merged.join(
|
|
1445
|
+
out.PATH = merged.join(import_node_path4.default.delimiter);
|
|
1008
1446
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
1009
1447
|
return out;
|
|
1010
1448
|
}
|
|
1011
1449
|
|
|
1012
1450
|
// src/engine/files.ts
|
|
1013
1451
|
var import_node_fs2 = require("fs");
|
|
1014
|
-
var
|
|
1452
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1015
1453
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
1016
|
-
var
|
|
1454
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1017
1455
|
|
|
1018
1456
|
// src/engine/activity.ts
|
|
1019
1457
|
var import_node_child_process2 = require("child_process");
|
|
@@ -1082,15 +1520,15 @@ function describeActivity(input) {
|
|
|
1082
1520
|
// src/engine/files.ts
|
|
1083
1521
|
function defaultProviderHomes(home = import_node_os3.default.homedir()) {
|
|
1084
1522
|
return {
|
|
1085
|
-
claudeProjects:
|
|
1086
|
-
codexSessions:
|
|
1087
|
-
codexIndex:
|
|
1523
|
+
claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
|
|
1524
|
+
codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
|
|
1525
|
+
codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
|
|
1088
1526
|
};
|
|
1089
1527
|
}
|
|
1090
1528
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
1091
1529
|
async function safeReaddir(dir) {
|
|
1092
1530
|
try {
|
|
1093
|
-
return await
|
|
1531
|
+
return await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
1094
1532
|
} catch {
|
|
1095
1533
|
return [];
|
|
1096
1534
|
}
|
|
@@ -1099,7 +1537,7 @@ async function statCandidate(provider, file) {
|
|
|
1099
1537
|
const match = UUIDISH.exec(file);
|
|
1100
1538
|
if (!match?.[1]) return null;
|
|
1101
1539
|
try {
|
|
1102
|
-
const stat = await
|
|
1540
|
+
const stat = await import_promises4.default.stat(file);
|
|
1103
1541
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1104
1542
|
} catch {
|
|
1105
1543
|
return null;
|
|
@@ -1109,10 +1547,10 @@ async function claudeCandidates(root) {
|
|
|
1109
1547
|
const out = [];
|
|
1110
1548
|
for (const project of await safeReaddir(root)) {
|
|
1111
1549
|
if (!project.isDirectory()) continue;
|
|
1112
|
-
const dir =
|
|
1550
|
+
const dir = import_node_path5.default.join(root, project.name);
|
|
1113
1551
|
const entries = await safeReaddir(dir);
|
|
1114
1552
|
const stats = await Promise.all(
|
|
1115
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1553
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
|
|
1116
1554
|
);
|
|
1117
1555
|
for (const s of stats) if (s) out.push(s);
|
|
1118
1556
|
}
|
|
@@ -1122,14 +1560,14 @@ async function codexCandidates(root) {
|
|
|
1122
1560
|
const out = [];
|
|
1123
1561
|
for (const year of await safeReaddir(root)) {
|
|
1124
1562
|
if (!year.isDirectory()) continue;
|
|
1125
|
-
for (const month of await safeReaddir(
|
|
1563
|
+
for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
|
|
1126
1564
|
if (!month.isDirectory()) continue;
|
|
1127
|
-
for (const day of await safeReaddir(
|
|
1565
|
+
for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
|
|
1128
1566
|
if (!day.isDirectory()) continue;
|
|
1129
|
-
const dir =
|
|
1567
|
+
const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
|
|
1130
1568
|
const entries = await safeReaddir(dir);
|
|
1131
1569
|
const stats = await Promise.all(
|
|
1132
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1570
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
|
|
1133
1571
|
);
|
|
1134
1572
|
for (const s of stats) if (s) out.push(s);
|
|
1135
1573
|
}
|
|
@@ -1138,7 +1576,7 @@ async function codexCandidates(root) {
|
|
|
1138
1576
|
return out;
|
|
1139
1577
|
}
|
|
1140
1578
|
async function readSlice(file, start, length) {
|
|
1141
|
-
const handle = await
|
|
1579
|
+
const handle = await import_promises4.default.open(file, "r");
|
|
1142
1580
|
try {
|
|
1143
1581
|
const buffer = Buffer.alloc(length);
|
|
1144
1582
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1211,9 +1649,9 @@ async function readCodexIndex(file) {
|
|
|
1211
1649
|
const names = /* @__PURE__ */ new Map();
|
|
1212
1650
|
let text;
|
|
1213
1651
|
try {
|
|
1214
|
-
const stat = await
|
|
1652
|
+
const stat = await import_promises4.default.stat(file);
|
|
1215
1653
|
const budget = 4 * 1024 * 1024;
|
|
1216
|
-
text = stat.size <= budget ? await
|
|
1654
|
+
text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1217
1655
|
} catch {
|
|
1218
1656
|
return names;
|
|
1219
1657
|
}
|
|
@@ -1334,7 +1772,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1334
1772
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1335
1773
|
let size;
|
|
1336
1774
|
try {
|
|
1337
|
-
size = (await
|
|
1775
|
+
size = (await import_promises4.default.stat(file)).size;
|
|
1338
1776
|
} catch {
|
|
1339
1777
|
return {
|
|
1340
1778
|
items: [],
|
|
@@ -1388,9 +1826,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1388
1826
|
}
|
|
1389
1827
|
|
|
1390
1828
|
// src/engine/preferences.ts
|
|
1391
|
-
var
|
|
1392
|
-
var
|
|
1393
|
-
var
|
|
1829
|
+
var import_node_crypto3 = require("crypto");
|
|
1830
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
1831
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1394
1832
|
var PreferenceStore = class {
|
|
1395
1833
|
constructor(dir) {
|
|
1396
1834
|
this.dir = dir;
|
|
@@ -1399,11 +1837,11 @@ var PreferenceStore = class {
|
|
|
1399
1837
|
data = {};
|
|
1400
1838
|
writing = Promise.resolve();
|
|
1401
1839
|
get file() {
|
|
1402
|
-
return
|
|
1840
|
+
return import_node_path6.default.join(this.dir, "preferences.json");
|
|
1403
1841
|
}
|
|
1404
1842
|
async load() {
|
|
1405
1843
|
try {
|
|
1406
|
-
const parsed = JSON.parse(await
|
|
1844
|
+
const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
|
|
1407
1845
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1408
1846
|
} catch {
|
|
1409
1847
|
this.data = {};
|
|
@@ -1441,10 +1879,10 @@ var PreferenceStore = class {
|
|
|
1441
1879
|
persist() {
|
|
1442
1880
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1443
1881
|
this.writing = this.writing.then(async () => {
|
|
1444
|
-
await
|
|
1445
|
-
const tmp = `${this.file}.${(0,
|
|
1446
|
-
await
|
|
1447
|
-
await
|
|
1882
|
+
await import_promises5.default.mkdir(this.dir, { recursive: true });
|
|
1883
|
+
const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
|
|
1884
|
+
await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
1885
|
+
await import_promises5.default.rename(tmp, this.file);
|
|
1448
1886
|
}).catch(() => {
|
|
1449
1887
|
});
|
|
1450
1888
|
}
|
|
@@ -1601,9 +2039,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1601
2039
|
}
|
|
1602
2040
|
|
|
1603
2041
|
// src/engine/store.ts
|
|
1604
|
-
var
|
|
1605
|
-
var
|
|
1606
|
-
var
|
|
2042
|
+
var import_node_crypto4 = require("crypto");
|
|
2043
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
2044
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1607
2045
|
var MAX_RUNS = 2e3;
|
|
1608
2046
|
var RunIndex = class {
|
|
1609
2047
|
constructor(dir) {
|
|
@@ -1613,12 +2051,12 @@ var RunIndex = class {
|
|
|
1613
2051
|
data = { version: 1, sessions: [], runs: [] };
|
|
1614
2052
|
writing = Promise.resolve();
|
|
1615
2053
|
get file() {
|
|
1616
|
-
return
|
|
2054
|
+
return import_node_path7.default.join(this.dir, "index.json");
|
|
1617
2055
|
}
|
|
1618
2056
|
async load() {
|
|
1619
|
-
await
|
|
2057
|
+
await import_promises6.default.mkdir(this.dir, { recursive: true });
|
|
1620
2058
|
try {
|
|
1621
|
-
const parsed = JSON.parse(await
|
|
2059
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
|
|
1622
2060
|
this.data = {
|
|
1623
2061
|
version: 1,
|
|
1624
2062
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1660,9 +2098,9 @@ var RunIndex = class {
|
|
|
1660
2098
|
persist() {
|
|
1661
2099
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1662
2100
|
this.writing = this.writing.then(async () => {
|
|
1663
|
-
const tmp = `${this.file}.${(0,
|
|
1664
|
-
await
|
|
1665
|
-
await
|
|
2101
|
+
const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
2102
|
+
await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
|
|
2103
|
+
await import_promises6.default.rename(tmp, this.file);
|
|
1666
2104
|
}).catch(() => {
|
|
1667
2105
|
});
|
|
1668
2106
|
}
|
|
@@ -1672,27 +2110,27 @@ var RunIndex = class {
|
|
|
1672
2110
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1673
2111
|
* driver's scratch state is reused rather than growing per run. */
|
|
1674
2112
|
async identity() {
|
|
1675
|
-
const file =
|
|
2113
|
+
const file = import_node_path7.default.join(this.dir, "identity.json");
|
|
1676
2114
|
try {
|
|
1677
|
-
const parsed = JSON.parse(await
|
|
2115
|
+
const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
|
|
1678
2116
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1679
2117
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1680
2118
|
}
|
|
1681
2119
|
} catch {
|
|
1682
2120
|
}
|
|
1683
|
-
const identity = { companyId: (0,
|
|
1684
|
-
await
|
|
2121
|
+
const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
|
|
2122
|
+
await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1685
2123
|
return identity;
|
|
1686
2124
|
}
|
|
1687
2125
|
};
|
|
1688
2126
|
|
|
1689
2127
|
// src/engine/user-defaults.ts
|
|
1690
|
-
var
|
|
2128
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
1691
2129
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
1692
|
-
var
|
|
2130
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1693
2131
|
async function readJson(file) {
|
|
1694
2132
|
try {
|
|
1695
|
-
const parsed = JSON.parse(await
|
|
2133
|
+
const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
|
|
1696
2134
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1697
2135
|
} catch {
|
|
1698
2136
|
return null;
|
|
@@ -1703,8 +2141,8 @@ function str3(value) {
|
|
|
1703
2141
|
}
|
|
1704
2142
|
async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
1705
2143
|
const files = [
|
|
1706
|
-
...cwd ? [
|
|
1707
|
-
|
|
2144
|
+
...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
|
|
2145
|
+
import_node_path8.default.join(home, ".claude", "settings.json")
|
|
1708
2146
|
];
|
|
1709
2147
|
let model = null;
|
|
1710
2148
|
let effort = null;
|
|
@@ -1720,7 +2158,7 @@ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
|
|
|
1720
2158
|
async function codexDefaults(home = import_node_os4.default.homedir()) {
|
|
1721
2159
|
let text;
|
|
1722
2160
|
try {
|
|
1723
|
-
text = await
|
|
2161
|
+
text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
|
|
1724
2162
|
} catch {
|
|
1725
2163
|
return { model: null, effort: null };
|
|
1726
2164
|
}
|
|
@@ -1744,31 +2182,31 @@ var EFFORT_LEVELS = {
|
|
|
1744
2182
|
// src/engine/index.ts
|
|
1745
2183
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1746
2184
|
function moduleDir() {
|
|
1747
|
-
return
|
|
2185
|
+
return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1748
2186
|
}
|
|
1749
2187
|
function resolveWorkerPath() {
|
|
1750
2188
|
const dir = moduleDir();
|
|
1751
2189
|
for (const candidate of [
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2190
|
+
import_node_path9.default.join(dir, "worker.js"),
|
|
2191
|
+
import_node_path9.default.join(dir, "worker.ts"),
|
|
2192
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.js"),
|
|
2193
|
+
import_node_path9.default.join(dir, "..", "engine", "worker.ts")
|
|
1756
2194
|
]) {
|
|
1757
2195
|
if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
|
|
1758
2196
|
}
|
|
1759
2197
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1760
2198
|
}
|
|
1761
2199
|
async function installShim(dataDir) {
|
|
1762
|
-
const source =
|
|
2200
|
+
const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
1763
2201
|
if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
1764
|
-
const body = await
|
|
1765
|
-
const target =
|
|
1766
|
-
const current = await
|
|
2202
|
+
const body = await import_promises8.default.readFile(source, "utf8");
|
|
2203
|
+
const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
|
|
2204
|
+
const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
|
|
1767
2205
|
if (current !== body) {
|
|
1768
|
-
await
|
|
1769
|
-
await
|
|
2206
|
+
await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
|
|
2207
|
+
await import_promises8.default.writeFile(target, body, { mode: 493 });
|
|
1770
2208
|
}
|
|
1771
|
-
await
|
|
2209
|
+
await import_promises8.default.chmod(target, 493);
|
|
1772
2210
|
return target;
|
|
1773
2211
|
}
|
|
1774
2212
|
function nowIso() {
|
|
@@ -1868,29 +2306,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1868
2306
|
};
|
|
1869
2307
|
}
|
|
1870
2308
|
async function createCodingSessionsEngine(options = {}) {
|
|
1871
|
-
const dataDir = options.dataDir ??
|
|
1872
|
-
const stateDir =
|
|
2309
|
+
const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
|
|
2310
|
+
const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
|
|
1873
2311
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1874
2312
|
const index = new RunIndex(dataDir);
|
|
1875
2313
|
await index.load();
|
|
1876
2314
|
const preferences = new PreferenceStore(dataDir);
|
|
1877
2315
|
await preferences.load();
|
|
1878
|
-
await
|
|
2316
|
+
await import_promises8.default.mkdir(stateDir, { recursive: true });
|
|
1879
2317
|
const identity = await index.identity();
|
|
2318
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1880
2319
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1881
2320
|
sink: options.rawBackup,
|
|
1882
2321
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1883
2322
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1884
2323
|
}) : null;
|
|
1885
2324
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1886
|
-
const healthFile =
|
|
2325
|
+
const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
|
|
1887
2326
|
let health = {};
|
|
1888
2327
|
try {
|
|
1889
|
-
health = JSON.parse(await
|
|
2328
|
+
health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
|
|
1890
2329
|
} catch {
|
|
1891
2330
|
health = {};
|
|
1892
2331
|
}
|
|
1893
|
-
const saveHealth = () =>
|
|
2332
|
+
const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1894
2333
|
function noteRunOutcome(provider, status, texts) {
|
|
1895
2334
|
if (status === "completed") {
|
|
1896
2335
|
if (health[provider]) {
|
|
@@ -2007,7 +2446,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2007
2446
|
async function startRun(session, prompt) {
|
|
2008
2447
|
const provider = session.provider;
|
|
2009
2448
|
const realCommand = await commandFor(provider);
|
|
2010
|
-
const runId = (0,
|
|
2449
|
+
const runId = (0, import_node_crypto5.randomUUID)();
|
|
2011
2450
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
2012
2451
|
const run = {
|
|
2013
2452
|
runId,
|
|
@@ -2045,7 +2484,36 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2045
2484
|
let fileOffset = null;
|
|
2046
2485
|
if (session.providerSessionId) {
|
|
2047
2486
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
2048
|
-
if (file) fileOffset = (await
|
|
2487
|
+
if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
|
|
2488
|
+
}
|
|
2489
|
+
let agentArgs = [];
|
|
2490
|
+
let cliPrompt = prompt;
|
|
2491
|
+
let delivered = null;
|
|
2492
|
+
if (!session.agent && session.agentRemovedDue) {
|
|
2493
|
+
if (provider === "codex") cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
|
|
2494
|
+
delivered = { agentRemovedDue: false };
|
|
2495
|
+
}
|
|
2496
|
+
if (session.agent) {
|
|
2497
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2498
|
+
if (!bundle) {
|
|
2499
|
+
const failed = saveRun({
|
|
2500
|
+
...run,
|
|
2501
|
+
status: "failed",
|
|
2502
|
+
endedAt: nowIso(),
|
|
2503
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2504
|
+
});
|
|
2505
|
+
const current = index.session(session.id);
|
|
2506
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2507
|
+
return failed;
|
|
2508
|
+
}
|
|
2509
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2510
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2511
|
+
});
|
|
2512
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
2513
|
+
if (provider === "codex" && session.agent.blockDue) {
|
|
2514
|
+
cliPrompt = `${renderFallbackBlock(bundle, import_node_path9.default.join(attachment.dir, "skills"))}${prompt}`;
|
|
2515
|
+
}
|
|
2516
|
+
delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
|
|
2049
2517
|
}
|
|
2050
2518
|
const child = forkWorker();
|
|
2051
2519
|
const liveRun = {
|
|
@@ -2061,6 +2529,10 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2061
2529
|
};
|
|
2062
2530
|
live.set(runId, liveRun);
|
|
2063
2531
|
emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
|
|
2532
|
+
if (delivered) {
|
|
2533
|
+
const current = index.session(session.id);
|
|
2534
|
+
if (current) saveSession({ ...current, ...delivered });
|
|
2535
|
+
}
|
|
2064
2536
|
const splitter = createLineSplitter();
|
|
2065
2537
|
const claude = provider === "claude" ? createClaudeNormalizer() : null;
|
|
2066
2538
|
const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
|
|
@@ -2099,6 +2571,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2099
2571
|
}
|
|
2100
2572
|
}
|
|
2101
2573
|
void pruneDriverScratch();
|
|
2574
|
+
if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
|
|
2102
2575
|
};
|
|
2103
2576
|
let settled = false;
|
|
2104
2577
|
child.on("message", (raw) => {
|
|
@@ -2213,16 +2686,39 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2213
2686
|
provider,
|
|
2214
2687
|
runId,
|
|
2215
2688
|
cwd: session.cwd,
|
|
2216
|
-
prompt,
|
|
2689
|
+
prompt: cliPrompt,
|
|
2217
2690
|
model,
|
|
2218
2691
|
effort: session.effort ?? null,
|
|
2219
2692
|
resumeSessionId: session.providerSessionId,
|
|
2220
2693
|
shimPath,
|
|
2221
2694
|
realCommand,
|
|
2222
|
-
identity
|
|
2695
|
+
identity,
|
|
2696
|
+
agentArgs
|
|
2223
2697
|
});
|
|
2224
2698
|
return run;
|
|
2225
2699
|
}
|
|
2700
|
+
async function checkCompaction(sessionId) {
|
|
2701
|
+
const session = index.session(sessionId);
|
|
2702
|
+
if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
|
|
2703
|
+
const file = await findSessionFile("codex", session.providerSessionId, homes);
|
|
2704
|
+
if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
|
|
2705
|
+
const current = index.session(sessionId);
|
|
2706
|
+
if (current?.agent) {
|
|
2707
|
+
saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
async function attachAgent(sessionId, selection, options2) {
|
|
2711
|
+
const bundle = await agents.loadBundle(selection);
|
|
2712
|
+
await agents.saveSessionBundle(sessionId, bundle);
|
|
2713
|
+
return {
|
|
2714
|
+
...sessionAgentRecord(bundle, {
|
|
2715
|
+
toolsServed: await agents.mcpUrlFor(sessionId) !== null,
|
|
2716
|
+
variables: selection.variables ?? {}
|
|
2717
|
+
}),
|
|
2718
|
+
blockDue: options2.blockDue,
|
|
2719
|
+
deliveredAt: null
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2226
2722
|
function killGroup(liveRun) {
|
|
2227
2723
|
const target = liveRun.pgid;
|
|
2228
2724
|
if (target === null) return;
|
|
@@ -2238,7 +2734,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2238
2734
|
}, 3e3);
|
|
2239
2735
|
}
|
|
2240
2736
|
async function pruneDriverScratch() {
|
|
2241
|
-
const runsDir =
|
|
2737
|
+
const runsDir = import_node_path9.default.join(
|
|
2242
2738
|
stateDir,
|
|
2243
2739
|
"instances",
|
|
2244
2740
|
"default",
|
|
@@ -2249,9 +2745,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2249
2745
|
"claude-runtime",
|
|
2250
2746
|
"runs"
|
|
2251
2747
|
);
|
|
2252
|
-
const entries = await
|
|
2748
|
+
const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
|
|
2253
2749
|
await Promise.all(
|
|
2254
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2750
|
+
entries.filter((name) => !live.has(name)).map((name) => import_promises8.default.rm(import_node_path9.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
|
|
2255
2751
|
);
|
|
2256
2752
|
}
|
|
2257
2753
|
async function probe() {
|
|
@@ -2292,7 +2788,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2292
2788
|
function newSession(input) {
|
|
2293
2789
|
const at = nowIso();
|
|
2294
2790
|
return {
|
|
2295
|
-
id: (0,
|
|
2791
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
2296
2792
|
provider: input.provider,
|
|
2297
2793
|
providerSessionId: input.providerSessionId,
|
|
2298
2794
|
cwd: input.cwd,
|
|
@@ -2310,17 +2806,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2310
2806
|
function resolveFolder(raw) {
|
|
2311
2807
|
const trimmed = raw.trim();
|
|
2312
2808
|
if (!trimmed) return null;
|
|
2313
|
-
return
|
|
2809
|
+
return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2314
2810
|
}
|
|
2315
2811
|
async function checkFolder(raw) {
|
|
2316
2812
|
const target = resolveFolder(raw);
|
|
2317
2813
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2318
|
-
const stat = await
|
|
2814
|
+
const stat = await import_promises8.default.stat(target).catch(() => null);
|
|
2319
2815
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2320
2816
|
}
|
|
2321
2817
|
async function assertFolder(cwd) {
|
|
2322
|
-
const resolved =
|
|
2323
|
-
const stat = await
|
|
2818
|
+
const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
|
|
2819
|
+
const stat = await import_promises8.default.stat(resolved).catch(() => null);
|
|
2324
2820
|
if (!stat?.isDirectory()) {
|
|
2325
2821
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2326
2822
|
}
|
|
@@ -2382,7 +2878,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2382
2878
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2383
2879
|
path: folder,
|
|
2384
2880
|
lastUsedAt,
|
|
2385
|
-
exists: (await
|
|
2881
|
+
exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2386
2882
|
}))
|
|
2387
2883
|
);
|
|
2388
2884
|
},
|
|
@@ -2427,7 +2923,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2427
2923
|
async createFolder(raw) {
|
|
2428
2924
|
const target = resolveFolder(raw);
|
|
2429
2925
|
if (!target) throw new Error("Type a folder path first.");
|
|
2430
|
-
await
|
|
2926
|
+
await import_promises8.default.mkdir(target, { recursive: true });
|
|
2431
2927
|
return checkFolder(target);
|
|
2432
2928
|
},
|
|
2433
2929
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2465,48 +2961,63 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2465
2961
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2466
2962
|
};
|
|
2467
2963
|
}
|
|
2468
|
-
const stat = await
|
|
2964
|
+
const stat = await import_promises8.default.stat(file);
|
|
2469
2965
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2470
2966
|
return {
|
|
2471
2967
|
available: false,
|
|
2472
2968
|
reason: `This transcript is ${(0, import_format.formatFileSize)(stat.size)}, larger than the ${(0, import_format.formatFileSize)(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
|
|
2473
2969
|
};
|
|
2474
2970
|
}
|
|
2475
|
-
const content = await
|
|
2476
|
-
return { available: true, fileName:
|
|
2971
|
+
const content = await import_promises8.default.readFile(file, "utf8");
|
|
2972
|
+
return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
|
|
2477
2973
|
},
|
|
2478
2974
|
async start(input) {
|
|
2479
2975
|
const prompt = input.prompt.trim();
|
|
2480
2976
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2481
2977
|
const cwd = await assertFolder(input.cwd);
|
|
2482
|
-
const
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
);
|
|
2978
|
+
const draft = newSession({
|
|
2979
|
+
provider: input.provider,
|
|
2980
|
+
cwd,
|
|
2981
|
+
title: titleFrom(prompt),
|
|
2982
|
+
model: input.model?.trim() || null,
|
|
2983
|
+
effort: input.effort?.trim() || null,
|
|
2984
|
+
origin: "started",
|
|
2985
|
+
providerSessionId: null
|
|
2986
|
+
});
|
|
2987
|
+
if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
|
|
2988
|
+
const session = saveSession(draft);
|
|
2493
2989
|
const run = await startRun(session, prompt);
|
|
2494
2990
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2495
2991
|
},
|
|
2496
2992
|
async takeover(input) {
|
|
2497
2993
|
const existing = index.session(input.providerSessionId);
|
|
2498
|
-
if (existing)
|
|
2994
|
+
if (existing) {
|
|
2995
|
+
if (!input.agent) return existing;
|
|
2996
|
+
return engine.setAgent(existing.id, input.agent);
|
|
2997
|
+
}
|
|
2499
2998
|
const cwd = await assertFolder(input.cwd);
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
);
|
|
2999
|
+
const draft = newSession({
|
|
3000
|
+
provider: input.provider,
|
|
3001
|
+
cwd,
|
|
3002
|
+
title: input.title?.trim() || "Taken-over chat",
|
|
3003
|
+
model: null,
|
|
3004
|
+
origin: "takeover",
|
|
3005
|
+
providerSessionId: input.providerSessionId
|
|
3006
|
+
});
|
|
3007
|
+
if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
|
|
3008
|
+
return saveSession(draft);
|
|
3009
|
+
},
|
|
3010
|
+
async setAgent(sessionId, selection) {
|
|
3011
|
+
const session = index.session(String(sessionId ?? ""));
|
|
3012
|
+
if (!session) throw new Error("This session is not in the list anymore.");
|
|
3013
|
+
if (!selection) {
|
|
3014
|
+
const told = session.provider === "codex" && session.agent?.deliveredAt != null;
|
|
3015
|
+
return saveSession({ ...session, agent: null, agentRemovedDue: told });
|
|
3016
|
+
}
|
|
3017
|
+
const agent = await attachAgent(session.id, selection, {
|
|
3018
|
+
blockDue: session.provider === "codex" && session.providerSessionId !== null
|
|
3019
|
+
});
|
|
3020
|
+
return saveSession({ ...session, agent, agentRemovedDue: false });
|
|
2510
3021
|
},
|
|
2511
3022
|
async send(sessionId, rawPrompt) {
|
|
2512
3023
|
const prompt = rawPrompt.trim();
|
|
@@ -2558,6 +3069,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2558
3069
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2559
3070
|
return saveSession(next);
|
|
2560
3071
|
},
|
|
3072
|
+
async agentAccess() {
|
|
3073
|
+
return agents.access();
|
|
3074
|
+
},
|
|
3075
|
+
async agentCatalogRpc(request) {
|
|
3076
|
+
return agents.catalogRpc(request);
|
|
3077
|
+
},
|
|
3078
|
+
async agentDetails(agentId) {
|
|
3079
|
+
return agents.details(String(agentId ?? ""));
|
|
3080
|
+
},
|
|
3081
|
+
setMcpBaseUrl(baseUrl) {
|
|
3082
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
3083
|
+
},
|
|
3084
|
+
async handleMcp(secret, body) {
|
|
3085
|
+
return agents.handleMcp(secret, body);
|
|
3086
|
+
},
|
|
2561
3087
|
async dispose() {
|
|
2562
3088
|
for (const liveRun of live.values()) {
|
|
2563
3089
|
liveRun.stopRequested = true;
|
|
@@ -2627,7 +3153,11 @@ var BRIDGE_METHODS = [
|
|
|
2627
3153
|
"setChatPreference",
|
|
2628
3154
|
"searchChats",
|
|
2629
3155
|
"openFolder",
|
|
2630
|
-
"setSessionOptions"
|
|
3156
|
+
"setSessionOptions",
|
|
3157
|
+
"agentAccess",
|
|
3158
|
+
"agentCatalogRpc",
|
|
3159
|
+
"agentDetails",
|
|
3160
|
+
"setAgent"
|
|
2631
3161
|
];
|
|
2632
3162
|
var BridgeError = class extends Error {
|
|
2633
3163
|
constructor(message, code) {
|
|
@@ -2671,14 +3201,14 @@ var MIME = {
|
|
|
2671
3201
|
".map": "application/json"
|
|
2672
3202
|
};
|
|
2673
3203
|
function moduleDir2() {
|
|
2674
|
-
return
|
|
3204
|
+
return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
2675
3205
|
}
|
|
2676
3206
|
function defaultUiDir() {
|
|
2677
3207
|
for (const candidate of [
|
|
2678
|
-
|
|
2679
|
-
|
|
3208
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
|
|
3209
|
+
import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
|
|
2680
3210
|
]) {
|
|
2681
|
-
if ((0, import_node_fs5.existsSync)(
|
|
3211
|
+
if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
|
|
2682
3212
|
}
|
|
2683
3213
|
return null;
|
|
2684
3214
|
}
|
|
@@ -2686,7 +3216,7 @@ function tokensMatch(expected, given) {
|
|
|
2686
3216
|
if (!given) return false;
|
|
2687
3217
|
const a = Buffer.from(expected);
|
|
2688
3218
|
const b = Buffer.from(given);
|
|
2689
|
-
return a.length === b.length && (0,
|
|
3219
|
+
return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
|
|
2690
3220
|
}
|
|
2691
3221
|
var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
|
|
2692
3222
|
<body style="font:15px system-ui;padding:48px;max-width:560px">
|
|
@@ -2701,19 +3231,19 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2701
3231
|
return;
|
|
2702
3232
|
}
|
|
2703
3233
|
const requested = decodeURIComponent(url.pathname);
|
|
2704
|
-
let file =
|
|
3234
|
+
let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
|
|
2705
3235
|
if (!file.startsWith(uiDir)) {
|
|
2706
3236
|
res.writeHead(403).end();
|
|
2707
3237
|
return;
|
|
2708
3238
|
}
|
|
2709
|
-
const stat = await
|
|
2710
|
-
if (!stat || stat.isDirectory()) file =
|
|
2711
|
-
const body = await
|
|
3239
|
+
const stat = await import_promises9.default.stat(file).catch(() => null);
|
|
3240
|
+
if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
|
|
3241
|
+
const body = await import_promises9.default.readFile(file).catch(() => null);
|
|
2712
3242
|
if (!body) {
|
|
2713
3243
|
res.writeHead(404).end();
|
|
2714
3244
|
return;
|
|
2715
3245
|
}
|
|
2716
|
-
const ext =
|
|
3246
|
+
const ext = import_node_path10.default.extname(file);
|
|
2717
3247
|
res.writeHead(200, {
|
|
2718
3248
|
"content-type": MIME[ext] ?? "application/octet-stream",
|
|
2719
3249
|
"cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
|
|
@@ -2722,6 +3252,46 @@ async function serveStatic(uiDir, req, res) {
|
|
|
2722
3252
|
});
|
|
2723
3253
|
res.end(body);
|
|
2724
3254
|
}
|
|
3255
|
+
var MAX_MCP_BODY = 4 * 1024 * 1024;
|
|
3256
|
+
async function handleMcp(engine, secret, req, res, allowedOrigin) {
|
|
3257
|
+
if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
|
|
3258
|
+
res.writeHead(403).end();
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
if (req.method !== "POST") {
|
|
3262
|
+
res.writeHead(405, { allow: "POST" }).end();
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
const chunks = [];
|
|
3266
|
+
let size = 0;
|
|
3267
|
+
for await (const chunk of req) {
|
|
3268
|
+
size += chunk.length;
|
|
3269
|
+
if (size > MAX_MCP_BODY) {
|
|
3270
|
+
res.writeHead(413).end();
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
chunks.push(chunk);
|
|
3274
|
+
}
|
|
3275
|
+
let body;
|
|
3276
|
+
try {
|
|
3277
|
+
body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
3278
|
+
} catch {
|
|
3279
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3280
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
|
|
3284
|
+
if (!reply) {
|
|
3285
|
+
res.writeHead(404).end();
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
if (reply.body === void 0) {
|
|
3289
|
+
res.writeHead(reply.status).end();
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
res.writeHead(reply.status, { "content-type": "application/json" });
|
|
3293
|
+
res.end(JSON.stringify(reply.body));
|
|
3294
|
+
}
|
|
2725
3295
|
function attachSocket(socket, bridge) {
|
|
2726
3296
|
const unsubscribe = bridge.subscribe((event) => {
|
|
2727
3297
|
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
|
|
@@ -2752,7 +3322,7 @@ function attachSocket(socket, bridge) {
|
|
|
2752
3322
|
socket.on("close", unsubscribe);
|
|
2753
3323
|
}
|
|
2754
3324
|
async function startCodingSessionsHost(options = {}) {
|
|
2755
|
-
const token = options.token ?? (0,
|
|
3325
|
+
const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
|
|
2756
3326
|
const engine = options.engine ?? await createCodingSessionsEngine(options);
|
|
2757
3327
|
const bridge = createHostBridge(engine);
|
|
2758
3328
|
const uiDir = options.uiDir ?? defaultUiDir();
|
|
@@ -2763,6 +3333,13 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2763
3333
|
};
|
|
2764
3334
|
const server = import_node_http.default.createServer((req, res) => {
|
|
2765
3335
|
const url2 = new URL(req.url ?? "/", `http://${HOST}`);
|
|
3336
|
+
if (url2.pathname.startsWith("/mcp/")) {
|
|
3337
|
+
handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
|
|
3338
|
+
if (!res.headersSent) res.writeHead(500);
|
|
3339
|
+
res.end();
|
|
3340
|
+
});
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
2766
3343
|
if (url2.pathname === "/health") {
|
|
2767
3344
|
res.writeHead(200, { "content-type": "application/json" });
|
|
2768
3345
|
res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
|
|
@@ -2793,6 +3370,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2793
3370
|
server.off("error", reject);
|
|
2794
3371
|
const address = server.address();
|
|
2795
3372
|
if (address && typeof address === "object") port = address.port;
|
|
3373
|
+
engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
|
|
2796
3374
|
resolve();
|
|
2797
3375
|
});
|
|
2798
3376
|
});
|
|
@@ -2803,6 +3381,7 @@ async function startCodingSessionsHost(options = {}) {
|
|
|
2803
3381
|
token,
|
|
2804
3382
|
engine,
|
|
2805
3383
|
async close() {
|
|
3384
|
+
engine.setMcpBaseUrl?.(null);
|
|
2806
3385
|
for (const client of wss.clients) client.terminate();
|
|
2807
3386
|
await new Promise((resolve) => wss.close(() => resolve()));
|
|
2808
3387
|
await new Promise((resolve) => server.close(() => resolve()));
|