@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/engine/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/engine/index.ts
|
|
2
2
|
import { execFile as execFile3, fork } from "child_process";
|
|
3
|
-
import { randomUUID as
|
|
3
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { formatFileSize } from "@ai-matrx/kit/format";
|
|
6
6
|
|
|
@@ -98,10 +98,448 @@ function createAiMatrxRawBackupSink(options) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// src/engine/agent-attach.ts
|
|
102
|
+
import fs2 from "fs/promises";
|
|
103
|
+
import path from "path";
|
|
104
|
+
|
|
105
|
+
// src/normalize/agent-block.ts
|
|
106
|
+
var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
|
|
107
|
+
function stripAgentBlock(text) {
|
|
108
|
+
const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
|
|
109
|
+
if (removed) return text.slice(removed[0].length);
|
|
110
|
+
if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
|
|
111
|
+
const end = text.indexOf("</ai_matrx_agent>");
|
|
112
|
+
if (end < 0) return text;
|
|
113
|
+
let rest = text.slice(end + "</ai_matrx_agent>".length);
|
|
114
|
+
const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
|
|
115
|
+
if (skills) rest = rest.slice(skills[0].length);
|
|
116
|
+
return rest.replace(/^\s+/, "");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/engine/agent-attach.ts
|
|
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: ${path.join(skillsRoot, skill.slug, "SKILL.md")})`
|
|
165
|
+
),
|
|
166
|
+
"</skills_instructions>"
|
|
167
|
+
];
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
function renderFallbackBlock(bundle, skillsRoot) {
|
|
171
|
+
const skills = renderCodexSkillsList(bundle, skillsRoot);
|
|
172
|
+
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.");
|
|
173
|
+
return skills ? `${body}
|
|
174
|
+
|
|
175
|
+
${skills}
|
|
176
|
+
|
|
177
|
+
` : `${body}
|
|
178
|
+
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
function tomlString(value) {
|
|
182
|
+
const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
183
|
+
return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
|
|
184
|
+
}
|
|
185
|
+
async function writeAgentAttachment(dir, bundle, options) {
|
|
186
|
+
const pluginDir = path.join(dir, "plugin");
|
|
187
|
+
const skillsRoot = path.join(dir, "skills");
|
|
188
|
+
await fs2.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
|
|
189
|
+
await fs2.mkdir(skillsRoot, { recursive: true, mode: 448 });
|
|
190
|
+
const instructions = renderAgentInstructions(bundle);
|
|
191
|
+
const instructionsFile = path.join(dir, "instructions.md");
|
|
192
|
+
await fs2.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
|
|
193
|
+
await fs2.writeFile(
|
|
194
|
+
path.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
195
|
+
`${JSON.stringify(
|
|
196
|
+
{
|
|
197
|
+
name: AGENT_PLUGIN_NAME,
|
|
198
|
+
description: `Skills of the AI Matrx agent "${bundle.name}"`,
|
|
199
|
+
version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
|
|
200
|
+
},
|
|
201
|
+
null,
|
|
202
|
+
2
|
|
203
|
+
)}
|
|
204
|
+
`,
|
|
205
|
+
{ mode: 384 }
|
|
206
|
+
);
|
|
207
|
+
const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
|
|
208
|
+
const wanted = new Set(listed2.map((skill) => skill.slug));
|
|
209
|
+
for (const root of [path.join(pluginDir, "skills"), skillsRoot]) {
|
|
210
|
+
await fs2.mkdir(root, { recursive: true, mode: 448 });
|
|
211
|
+
for (const existing of await fs2.readdir(root).catch(() => [])) {
|
|
212
|
+
if (!wanted.has(existing)) await fs2.rm(path.join(root, existing), { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
for (const skill of listed2) {
|
|
215
|
+
const skillDir = path.join(root, skill.slug);
|
|
216
|
+
await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
|
|
217
|
+
await fs2.writeFile(path.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
let mcpConfigFile = null;
|
|
221
|
+
const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
|
|
222
|
+
const mcpPath = path.join(dir, "mcp.json");
|
|
223
|
+
if (mcpUrl) {
|
|
224
|
+
mcpConfigFile = mcpPath;
|
|
225
|
+
await fs2.writeFile(
|
|
226
|
+
mcpPath,
|
|
227
|
+
`${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
|
|
228
|
+
`,
|
|
229
|
+
{ mode: 384 }
|
|
230
|
+
);
|
|
231
|
+
} else {
|
|
232
|
+
await fs2.rm(mcpPath, { force: true });
|
|
233
|
+
}
|
|
234
|
+
const skillsList = renderCodexSkillsList(bundle, skillsRoot);
|
|
235
|
+
return {
|
|
236
|
+
dir,
|
|
237
|
+
instructionsFile,
|
|
238
|
+
pluginDir,
|
|
239
|
+
mcpConfigFile,
|
|
240
|
+
mcpUrl,
|
|
241
|
+
// Same order as Claude's: the skills list first, the agent's words last.
|
|
242
|
+
codexInstructions: skillsList ? `${skillsList}
|
|
243
|
+
|
|
244
|
+
${instructions}` : instructions
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function agentArgsFor(provider, attachment, options) {
|
|
248
|
+
if (provider === "claude") {
|
|
249
|
+
const args2 = [
|
|
250
|
+
// Every turn: without snapshot off, a resumed Claude drops appended instructions.
|
|
251
|
+
"--append-system-prompt-file",
|
|
252
|
+
attachment.instructionsFile,
|
|
253
|
+
"--system-prompt-snapshot",
|
|
254
|
+
"off",
|
|
255
|
+
"--plugin-dir",
|
|
256
|
+
attachment.pluginDir
|
|
257
|
+
];
|
|
258
|
+
if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
|
|
259
|
+
return args2;
|
|
260
|
+
}
|
|
261
|
+
const args = [];
|
|
262
|
+
if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
|
|
263
|
+
if (attachment.mcpUrl) {
|
|
264
|
+
const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
|
|
265
|
+
args.push(
|
|
266
|
+
"-c",
|
|
267
|
+
`${key}.url=${tomlString(attachment.mcpUrl)}`,
|
|
268
|
+
"-c",
|
|
269
|
+
`${key}.default_tools_approval_mode="approve"`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return args;
|
|
273
|
+
}
|
|
274
|
+
function sessionAgentRecord(bundle, options) {
|
|
275
|
+
const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
|
|
276
|
+
const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
|
|
277
|
+
const missingTools = unavailable.filter((u) => u.kind === "tool").length;
|
|
278
|
+
if (!options.toolsServed && bundle.tools.length > 0) {
|
|
279
|
+
for (const tool of bundle.tools) {
|
|
280
|
+
unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
|
|
284
|
+
const loaded = [
|
|
285
|
+
{ part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
|
|
286
|
+
{ part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
|
|
287
|
+
{ part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
|
|
288
|
+
];
|
|
289
|
+
return {
|
|
290
|
+
agentId: bundle.agent_id,
|
|
291
|
+
versionId: bundle.version_id,
|
|
292
|
+
versionNumber: bundle.version_number,
|
|
293
|
+
name: bundle.name,
|
|
294
|
+
definitionHash: bundle.definition_hash,
|
|
295
|
+
loaded,
|
|
296
|
+
unavailable,
|
|
297
|
+
skills: bundle.skills.map((skill) => skill.label),
|
|
298
|
+
tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
|
|
299
|
+
...options.variables ? { variables: options.variables } : {}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
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";
|
|
303
|
+
async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
|
|
304
|
+
const text = await fs2.readFile(rolloutFile, "utf8").catch(() => "");
|
|
305
|
+
if (!text.includes('"type":"compacted"')) return false;
|
|
306
|
+
let latest = null;
|
|
307
|
+
for (const line of text.split("\n")) {
|
|
308
|
+
if (!line.includes('"type":"compacted"')) continue;
|
|
309
|
+
try {
|
|
310
|
+
const entry = JSON.parse(line);
|
|
311
|
+
if (entry.type === "compacted") latest = entry;
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (!latest) return false;
|
|
316
|
+
if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
|
|
317
|
+
return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/engine/agents.ts
|
|
321
|
+
import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
|
|
322
|
+
import fs3 from "fs/promises";
|
|
323
|
+
import path2 from "path";
|
|
324
|
+
var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
325
|
+
var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
326
|
+
var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
|
|
327
|
+
var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
|
|
328
|
+
"agx_get_list_full",
|
|
329
|
+
"agx_search",
|
|
330
|
+
"agx_resolve_agent_address",
|
|
331
|
+
"ues_get_bulk",
|
|
332
|
+
"ues_list",
|
|
333
|
+
"ues_set",
|
|
334
|
+
"ues_touch"
|
|
335
|
+
]);
|
|
336
|
+
var agentsClient = async () => {
|
|
337
|
+
const [matrx, portable, mcp] = await Promise.all([
|
|
338
|
+
import("@ai-matrx/agents/matrx"),
|
|
339
|
+
import("@ai-matrx/agents/portable"),
|
|
340
|
+
import("@ai-matrx/agents/portable/mcp")
|
|
341
|
+
]);
|
|
342
|
+
return { ...matrx, ...portable, ...mcp };
|
|
343
|
+
};
|
|
344
|
+
function sortedValues(values) {
|
|
345
|
+
return Object.keys(values).sort().map((key) => [key, values[key]]);
|
|
346
|
+
}
|
|
347
|
+
var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
|
|
348
|
+
var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
|
|
349
|
+
function userIdOf(credential) {
|
|
350
|
+
if (!credential || credential.kind !== "user") return null;
|
|
351
|
+
const payload = credential.accessToken.split(".")[1];
|
|
352
|
+
if (!payload) return null;
|
|
353
|
+
try {
|
|
354
|
+
const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
355
|
+
return typeof claims.sub === "string" ? claims.sub : null;
|
|
356
|
+
} catch {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function createAgentConnection(options, dataDir) {
|
|
361
|
+
const credentials = options?.credentials ?? null;
|
|
362
|
+
const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
363
|
+
const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
|
|
364
|
+
const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
|
|
365
|
+
const root = path2.join(dataDir, "agents");
|
|
366
|
+
let mcpBaseUrl = null;
|
|
367
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
368
|
+
const secretToSession = /* @__PURE__ */ new Map();
|
|
369
|
+
const organizationId = async () => {
|
|
370
|
+
const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
|
|
371
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
372
|
+
};
|
|
373
|
+
const transport = async () => {
|
|
374
|
+
const org = await organizationId();
|
|
375
|
+
return (await agentsClient()).createMatrxTransport({
|
|
376
|
+
baseUrl: serverUrl,
|
|
377
|
+
...credentials ? { credentials } : {},
|
|
378
|
+
...org ? { organizationId: org } : {},
|
|
379
|
+
source: "codingSessionsAgents"
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
const cacheDir = path2.join(dataDir, "agents", ".bundles");
|
|
383
|
+
const cacheKey = (definitionHash, selection, surface) => createHash2("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
|
|
384
|
+
const credential = async () => credentials ? credentials.get() : null;
|
|
385
|
+
const requireSignedIn = async () => {
|
|
386
|
+
if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
|
|
387
|
+
};
|
|
388
|
+
const sessionDir = (sessionId) => path2.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
389
|
+
async function meta(sessionId) {
|
|
390
|
+
const file = path2.join(sessionDir(sessionId), "session.json");
|
|
391
|
+
const existing = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
392
|
+
if (existing?.mcpSecret && existing.conversationId) return existing;
|
|
393
|
+
const created = { mcpSecret: randomBytes(24).toString("base64url"), conversationId: randomUUID() };
|
|
394
|
+
await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
395
|
+
await fs3.writeFile(file, JSON.stringify(created), { mode: 384 });
|
|
396
|
+
return created;
|
|
397
|
+
}
|
|
398
|
+
async function findSessionBySecret(secret) {
|
|
399
|
+
for (const name of await fs3.readdir(root).catch(() => [])) {
|
|
400
|
+
const found = await fs3.readFile(path2.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
|
|
401
|
+
if (found) {
|
|
402
|
+
secretToSession.set(secret, name);
|
|
403
|
+
return name;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
async function readSessionBundle(sessionId) {
|
|
409
|
+
return fs3.readFile(path2.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
async access() {
|
|
413
|
+
const current = await credential().catch(() => null);
|
|
414
|
+
const userId = userIdOf(current);
|
|
415
|
+
return {
|
|
416
|
+
configured: credentials !== null && supabaseKey !== null,
|
|
417
|
+
signedIn: userId !== null,
|
|
418
|
+
userId,
|
|
419
|
+
organizationId: await organizationId().catch(() => null)
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
async catalogRpc(request) {
|
|
423
|
+
if (!AGENT_CATALOG_RPCS.has(request.fn)) {
|
|
424
|
+
return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
|
|
425
|
+
}
|
|
426
|
+
const current = await credential();
|
|
427
|
+
if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
|
|
428
|
+
if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
|
|
429
|
+
const query = new URLSearchParams();
|
|
430
|
+
if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
|
|
431
|
+
if (request.range) {
|
|
432
|
+
query.set("offset", String(request.range[0]));
|
|
433
|
+
query.set("limit", String(request.range[1] - request.range[0] + 1));
|
|
434
|
+
}
|
|
435
|
+
const headers = {
|
|
436
|
+
apikey: supabaseKey,
|
|
437
|
+
Authorization: `Bearer ${current.accessToken}`,
|
|
438
|
+
"Content-Type": "application/json",
|
|
439
|
+
// Every catalogue RPC lives in `public`; the database's default
|
|
440
|
+
// exposed schema is not `public`, so name it (supabase-js does).
|
|
441
|
+
"Content-Profile": "public",
|
|
442
|
+
"Accept-Profile": "public"
|
|
443
|
+
};
|
|
444
|
+
if (request.count) headers.Prefer = `count=${request.count}`;
|
|
445
|
+
const qs = query.toString();
|
|
446
|
+
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers,
|
|
449
|
+
body: JSON.stringify(request.args ?? {})
|
|
450
|
+
});
|
|
451
|
+
const text = await response.text();
|
|
452
|
+
const body = text ? JSON.parse(text) : null;
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
const err = body ?? {};
|
|
455
|
+
return {
|
|
456
|
+
data: null,
|
|
457
|
+
error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const range = response.headers.get("content-range");
|
|
461
|
+
const total = range?.split("/")[1];
|
|
462
|
+
return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
|
|
463
|
+
},
|
|
464
|
+
async details(agentId) {
|
|
465
|
+
await requireSignedIn();
|
|
466
|
+
const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
|
|
467
|
+
return {
|
|
468
|
+
agentId: summary.agent_id,
|
|
469
|
+
name: summary.name,
|
|
470
|
+
variables: summary.variables,
|
|
471
|
+
versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
|
|
472
|
+
latestVersionNumber: summary.version_number,
|
|
473
|
+
latestVersionId: summary.version_id
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
async loadBundle(selection) {
|
|
477
|
+
await requireSignedIn();
|
|
478
|
+
const client = await agentsClient();
|
|
479
|
+
const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
|
|
480
|
+
const versionId = selection.versionId ?? summary.version_id;
|
|
481
|
+
const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
|
|
482
|
+
const surface = "matrx-user/code-editor";
|
|
483
|
+
const file = definitionHash ? path2.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
|
|
484
|
+
if (file) {
|
|
485
|
+
const cached = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
486
|
+
const fresh = await fs3.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
|
|
487
|
+
if (cached && fresh && cached.definition_hash === definitionHash) return cached;
|
|
488
|
+
}
|
|
489
|
+
const bundle = await client.fetchPortableAgent(
|
|
490
|
+
await transport(),
|
|
491
|
+
versionId ? { versionId } : { agentId: selection.agentId },
|
|
492
|
+
{ variables: selection.variables ?? {}, surface }
|
|
493
|
+
);
|
|
494
|
+
if (file && bundle.definition_hash === definitionHash) {
|
|
495
|
+
await fs3.mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
496
|
+
await fs3.writeFile(file, JSON.stringify(bundle), { mode: 384 });
|
|
497
|
+
}
|
|
498
|
+
return bundle;
|
|
499
|
+
},
|
|
500
|
+
async saveSessionBundle(sessionId, bundle) {
|
|
501
|
+
await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
|
|
502
|
+
await fs3.writeFile(path2.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
|
|
503
|
+
await meta(sessionId);
|
|
504
|
+
},
|
|
505
|
+
readSessionBundle,
|
|
506
|
+
sessionDir,
|
|
507
|
+
async mcpUrlFor(sessionId) {
|
|
508
|
+
if (!mcpBaseUrl) return null;
|
|
509
|
+
const { mcpSecret } = await meta(sessionId);
|
|
510
|
+
secretToSession.set(mcpSecret, sessionId);
|
|
511
|
+
return `${mcpBaseUrl}/mcp/${mcpSecret}`;
|
|
512
|
+
},
|
|
513
|
+
setMcpBaseUrl(baseUrl) {
|
|
514
|
+
mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
|
|
515
|
+
},
|
|
516
|
+
async handleMcp(secret, body) {
|
|
517
|
+
let handler = handlers.get(secret);
|
|
518
|
+
if (!handler) {
|
|
519
|
+
const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
|
|
520
|
+
if (!sessionId) return null;
|
|
521
|
+
const bundle = await readSessionBundle(sessionId);
|
|
522
|
+
if (!bundle) return null;
|
|
523
|
+
const { conversationId } = await meta(sessionId);
|
|
524
|
+
const { createAgentToolsMcpHandler } = await agentsClient();
|
|
525
|
+
handler = createAgentToolsMcpHandler({
|
|
526
|
+
bundle,
|
|
527
|
+
credentials,
|
|
528
|
+
organizationId: () => organizationId(),
|
|
529
|
+
baseUrl: serverUrl,
|
|
530
|
+
conversationId
|
|
531
|
+
});
|
|
532
|
+
handlers.set(secret, handler);
|
|
533
|
+
}
|
|
534
|
+
return handler.handle(body);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
101
539
|
// src/engine/index.ts
|
|
102
|
-
import
|
|
540
|
+
import fs8 from "fs/promises";
|
|
103
541
|
import os5 from "os";
|
|
104
|
-
import
|
|
542
|
+
import path9 from "path";
|
|
105
543
|
import { fileURLToPath } from "url";
|
|
106
544
|
|
|
107
545
|
// src/normalize/claude.ts
|
|
@@ -472,7 +910,7 @@ function rolloutItem(item, fallbackId, at) {
|
|
|
472
910
|
const id = str2(item.id) ?? fallbackId;
|
|
473
911
|
switch (item.type) {
|
|
474
912
|
case "UserMessage": {
|
|
475
|
-
const text = stripCodexInjectedContext(itemText(item.content));
|
|
913
|
+
const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
|
|
476
914
|
return text.trim() ? [{ kind: "user", id, text, at }] : [];
|
|
477
915
|
}
|
|
478
916
|
case "AgentMessage": {
|
|
@@ -555,7 +993,7 @@ function createCodexRolloutNormalizer() {
|
|
|
555
993
|
return rolloutItem(payload.item, fallbackId, at);
|
|
556
994
|
}
|
|
557
995
|
if (payload.type === "user_message" && !appItems) {
|
|
558
|
-
const text = stripCodexInjectedContext(str2(payload.message) ?? "");
|
|
996
|
+
const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
|
|
559
997
|
return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
|
|
560
998
|
}
|
|
561
999
|
return [];
|
|
@@ -771,7 +1209,7 @@ function describeProblem(provider, problem) {
|
|
|
771
1209
|
import { execFile } from "child_process";
|
|
772
1210
|
import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
|
|
773
1211
|
import os from "os";
|
|
774
|
-
import
|
|
1212
|
+
import path3 from "path";
|
|
775
1213
|
var SOURCE_LABEL = {
|
|
776
1214
|
codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
|
|
777
1215
|
claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
|
|
@@ -820,10 +1258,10 @@ function exeName(name, platform) {
|
|
|
820
1258
|
}
|
|
821
1259
|
function allOnPath(name, pathValue, platform = process.platform) {
|
|
822
1260
|
const found = [];
|
|
823
|
-
for (const dir of pathValue.split(
|
|
1261
|
+
for (const dir of pathValue.split(path3.delimiter)) {
|
|
824
1262
|
if (!dir) continue;
|
|
825
1263
|
for (const file of exeName(name, platform)) {
|
|
826
|
-
const candidate =
|
|
1264
|
+
const candidate = path3.join(dir, file);
|
|
827
1265
|
if (executable(candidate)) {
|
|
828
1266
|
found.push(candidate);
|
|
829
1267
|
break;
|
|
@@ -836,17 +1274,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
|
|
|
836
1274
|
if (platform !== "darwin") return [];
|
|
837
1275
|
if (provider === "codex") {
|
|
838
1276
|
return applications.map(
|
|
839
|
-
(apps) =>
|
|
1277
|
+
(apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
|
|
840
1278
|
);
|
|
841
1279
|
}
|
|
842
|
-
const root =
|
|
1280
|
+
const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
843
1281
|
let versions = [];
|
|
844
1282
|
try {
|
|
845
1283
|
versions = readdirSync(root);
|
|
846
1284
|
} catch {
|
|
847
1285
|
return [];
|
|
848
1286
|
}
|
|
849
|
-
return versions.map((v) =>
|
|
1287
|
+
return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
|
|
850
1288
|
}
|
|
851
1289
|
function npmGlobalPrefix(env) {
|
|
852
1290
|
const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
@@ -864,13 +1302,13 @@ function declaredVersion(provider, source, file) {
|
|
|
864
1302
|
if (source !== "desktop-app") return null;
|
|
865
1303
|
if (provider === "codex") {
|
|
866
1304
|
try {
|
|
867
|
-
const pkg = JSON.parse(readFileSync(
|
|
1305
|
+
const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
|
|
868
1306
|
return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
|
|
869
1307
|
} catch {
|
|
870
1308
|
return null;
|
|
871
1309
|
}
|
|
872
1310
|
}
|
|
873
|
-
return parseVersion(
|
|
1311
|
+
return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
|
|
874
1312
|
}
|
|
875
1313
|
function runVersion(file, env) {
|
|
876
1314
|
return new Promise((resolve) => {
|
|
@@ -884,14 +1322,14 @@ async function discoverBinaries(provider, options) {
|
|
|
884
1322
|
const platform = options.platform ?? process.platform;
|
|
885
1323
|
const home = options.home ?? options.env.HOME ?? os.homedir();
|
|
886
1324
|
const listed2 = [
|
|
887
|
-
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications",
|
|
1325
|
+
...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
|
|
888
1326
|
...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
|
|
889
1327
|
];
|
|
890
1328
|
const prefix = await npmGlobalPrefix(options.env);
|
|
891
1329
|
if (prefix) {
|
|
892
|
-
const dir = platform === "win32" ? prefix :
|
|
1330
|
+
const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
|
|
893
1331
|
for (const file of exeName(provider, platform)) {
|
|
894
|
-
const candidate =
|
|
1332
|
+
const candidate = path3.join(dir, file);
|
|
895
1333
|
if (executable(candidate)) {
|
|
896
1334
|
listed2.push({ path: candidate, source: "npm-global" });
|
|
897
1335
|
break;
|
|
@@ -937,7 +1375,7 @@ async function resolveBinary(provider, options) {
|
|
|
937
1375
|
|
|
938
1376
|
// src/engine/clean-env.ts
|
|
939
1377
|
import os2 from "os";
|
|
940
|
-
import
|
|
1378
|
+
import path4 from "path";
|
|
941
1379
|
var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
942
1380
|
"HOME",
|
|
943
1381
|
"USER",
|
|
@@ -959,11 +1397,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
|
|
|
959
1397
|
var ALLOWED_PREFIXES = ["LC_"];
|
|
960
1398
|
function wellKnownBinDirs(home) {
|
|
961
1399
|
return [
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1400
|
+
path4.join(home, ".local", "bin"),
|
|
1401
|
+
path4.join(home, ".claude", "local"),
|
|
1402
|
+
path4.join(home, ".npm-global", "bin"),
|
|
1403
|
+
path4.join(home, ".bun", "bin"),
|
|
1404
|
+
path4.join(home, ".volta", "bin"),
|
|
967
1405
|
"/opt/homebrew/bin",
|
|
968
1406
|
"/usr/local/bin",
|
|
969
1407
|
"/usr/bin",
|
|
@@ -983,21 +1421,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
|
|
|
983
1421
|
}
|
|
984
1422
|
const home = out.HOME ?? os2.homedir();
|
|
985
1423
|
out.HOME = home;
|
|
986
|
-
const hostPath = (hostEnv.PATH ?? "").split(
|
|
1424
|
+
const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
|
|
987
1425
|
const merged = [];
|
|
988
1426
|
for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
|
|
989
1427
|
if (!merged.includes(dir)) merged.push(dir);
|
|
990
1428
|
}
|
|
991
|
-
out.PATH = merged.join(
|
|
1429
|
+
out.PATH = merged.join(path4.delimiter);
|
|
992
1430
|
for (const [name, value] of Object.entries(extra)) out[name] = value;
|
|
993
1431
|
return out;
|
|
994
1432
|
}
|
|
995
1433
|
|
|
996
1434
|
// src/engine/files.ts
|
|
997
1435
|
import { createReadStream } from "fs";
|
|
998
|
-
import
|
|
1436
|
+
import fs4 from "fs/promises";
|
|
999
1437
|
import os3 from "os";
|
|
1000
|
-
import
|
|
1438
|
+
import path5 from "path";
|
|
1001
1439
|
|
|
1002
1440
|
// src/engine/activity.ts
|
|
1003
1441
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -1066,15 +1504,15 @@ function describeActivity(input) {
|
|
|
1066
1504
|
// src/engine/files.ts
|
|
1067
1505
|
function defaultProviderHomes(home = os3.homedir()) {
|
|
1068
1506
|
return {
|
|
1069
|
-
claudeProjects:
|
|
1070
|
-
codexSessions:
|
|
1071
|
-
codexIndex:
|
|
1507
|
+
claudeProjects: path5.join(home, ".claude", "projects"),
|
|
1508
|
+
codexSessions: path5.join(home, ".codex", "sessions"),
|
|
1509
|
+
codexIndex: path5.join(home, ".codex", "session_index.jsonl")
|
|
1072
1510
|
};
|
|
1073
1511
|
}
|
|
1074
1512
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
1075
1513
|
async function safeReaddir(dir) {
|
|
1076
1514
|
try {
|
|
1077
|
-
return await
|
|
1515
|
+
return await fs4.readdir(dir, { withFileTypes: true });
|
|
1078
1516
|
} catch {
|
|
1079
1517
|
return [];
|
|
1080
1518
|
}
|
|
@@ -1083,7 +1521,7 @@ async function statCandidate(provider, file) {
|
|
|
1083
1521
|
const match = UUIDISH.exec(file);
|
|
1084
1522
|
if (!match?.[1]) return null;
|
|
1085
1523
|
try {
|
|
1086
|
-
const stat = await
|
|
1524
|
+
const stat = await fs4.stat(file);
|
|
1087
1525
|
return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1088
1526
|
} catch {
|
|
1089
1527
|
return null;
|
|
@@ -1093,10 +1531,10 @@ async function claudeCandidates(root) {
|
|
|
1093
1531
|
const out = [];
|
|
1094
1532
|
for (const project of await safeReaddir(root)) {
|
|
1095
1533
|
if (!project.isDirectory()) continue;
|
|
1096
|
-
const dir =
|
|
1534
|
+
const dir = path5.join(root, project.name);
|
|
1097
1535
|
const entries = await safeReaddir(dir);
|
|
1098
1536
|
const stats = await Promise.all(
|
|
1099
|
-
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude",
|
|
1537
|
+
entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path5.join(dir, e.name)))
|
|
1100
1538
|
);
|
|
1101
1539
|
for (const s of stats) if (s) out.push(s);
|
|
1102
1540
|
}
|
|
@@ -1106,14 +1544,14 @@ async function codexCandidates(root) {
|
|
|
1106
1544
|
const out = [];
|
|
1107
1545
|
for (const year of await safeReaddir(root)) {
|
|
1108
1546
|
if (!year.isDirectory()) continue;
|
|
1109
|
-
for (const month of await safeReaddir(
|
|
1547
|
+
for (const month of await safeReaddir(path5.join(root, year.name))) {
|
|
1110
1548
|
if (!month.isDirectory()) continue;
|
|
1111
|
-
for (const day of await safeReaddir(
|
|
1549
|
+
for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
|
|
1112
1550
|
if (!day.isDirectory()) continue;
|
|
1113
|
-
const dir =
|
|
1551
|
+
const dir = path5.join(root, year.name, month.name, day.name);
|
|
1114
1552
|
const entries = await safeReaddir(dir);
|
|
1115
1553
|
const stats = await Promise.all(
|
|
1116
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex",
|
|
1554
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
|
|
1117
1555
|
);
|
|
1118
1556
|
for (const s of stats) if (s) out.push(s);
|
|
1119
1557
|
}
|
|
@@ -1122,7 +1560,7 @@ async function codexCandidates(root) {
|
|
|
1122
1560
|
return out;
|
|
1123
1561
|
}
|
|
1124
1562
|
async function readSlice(file, start, length) {
|
|
1125
|
-
const handle = await
|
|
1563
|
+
const handle = await fs4.open(file, "r");
|
|
1126
1564
|
try {
|
|
1127
1565
|
const buffer = Buffer.alloc(length);
|
|
1128
1566
|
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
@@ -1195,9 +1633,9 @@ async function readCodexIndex(file) {
|
|
|
1195
1633
|
const names = /* @__PURE__ */ new Map();
|
|
1196
1634
|
let text;
|
|
1197
1635
|
try {
|
|
1198
|
-
const stat = await
|
|
1636
|
+
const stat = await fs4.stat(file);
|
|
1199
1637
|
const budget = 4 * 1024 * 1024;
|
|
1200
|
-
text = stat.size <= budget ? await
|
|
1638
|
+
text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
|
|
1201
1639
|
} catch {
|
|
1202
1640
|
return names;
|
|
1203
1641
|
}
|
|
@@ -1318,7 +1756,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
|
1318
1756
|
async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
|
|
1319
1757
|
let size;
|
|
1320
1758
|
try {
|
|
1321
|
-
size = (await
|
|
1759
|
+
size = (await fs4.stat(file)).size;
|
|
1322
1760
|
} catch {
|
|
1323
1761
|
return {
|
|
1324
1762
|
items: [],
|
|
@@ -1372,9 +1810,9 @@ async function describeSessionFile(candidate, codexNames) {
|
|
|
1372
1810
|
}
|
|
1373
1811
|
|
|
1374
1812
|
// src/engine/preferences.ts
|
|
1375
|
-
import { randomUUID } from "crypto";
|
|
1376
|
-
import
|
|
1377
|
-
import
|
|
1813
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1814
|
+
import fs5 from "fs/promises";
|
|
1815
|
+
import path6 from "path";
|
|
1378
1816
|
var PreferenceStore = class {
|
|
1379
1817
|
constructor(dir) {
|
|
1380
1818
|
this.dir = dir;
|
|
@@ -1383,11 +1821,11 @@ var PreferenceStore = class {
|
|
|
1383
1821
|
data = {};
|
|
1384
1822
|
writing = Promise.resolve();
|
|
1385
1823
|
get file() {
|
|
1386
|
-
return
|
|
1824
|
+
return path6.join(this.dir, "preferences.json");
|
|
1387
1825
|
}
|
|
1388
1826
|
async load() {
|
|
1389
1827
|
try {
|
|
1390
|
-
const parsed = JSON.parse(await
|
|
1828
|
+
const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
|
|
1391
1829
|
this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1392
1830
|
} catch {
|
|
1393
1831
|
this.data = {};
|
|
@@ -1425,10 +1863,10 @@ var PreferenceStore = class {
|
|
|
1425
1863
|
persist() {
|
|
1426
1864
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1427
1865
|
this.writing = this.writing.then(async () => {
|
|
1428
|
-
await
|
|
1429
|
-
const tmp = `${this.file}.${
|
|
1430
|
-
await
|
|
1431
|
-
await
|
|
1866
|
+
await fs5.mkdir(this.dir, { recursive: true });
|
|
1867
|
+
const tmp = `${this.file}.${randomUUID2()}.tmp`;
|
|
1868
|
+
await fs5.writeFile(tmp, snapshot, { mode: 384 });
|
|
1869
|
+
await fs5.rename(tmp, this.file);
|
|
1432
1870
|
}).catch(() => {
|
|
1433
1871
|
});
|
|
1434
1872
|
}
|
|
@@ -1585,9 +2023,9 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1585
2023
|
}
|
|
1586
2024
|
|
|
1587
2025
|
// src/engine/store.ts
|
|
1588
|
-
import { randomUUID as
|
|
1589
|
-
import
|
|
1590
|
-
import
|
|
2026
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2027
|
+
import fs6 from "fs/promises";
|
|
2028
|
+
import path7 from "path";
|
|
1591
2029
|
var MAX_RUNS = 2e3;
|
|
1592
2030
|
var RunIndex = class {
|
|
1593
2031
|
constructor(dir) {
|
|
@@ -1597,12 +2035,12 @@ var RunIndex = class {
|
|
|
1597
2035
|
data = { version: 1, sessions: [], runs: [] };
|
|
1598
2036
|
writing = Promise.resolve();
|
|
1599
2037
|
get file() {
|
|
1600
|
-
return
|
|
2038
|
+
return path7.join(this.dir, "index.json");
|
|
1601
2039
|
}
|
|
1602
2040
|
async load() {
|
|
1603
|
-
await
|
|
2041
|
+
await fs6.mkdir(this.dir, { recursive: true });
|
|
1604
2042
|
try {
|
|
1605
|
-
const parsed = JSON.parse(await
|
|
2043
|
+
const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
|
|
1606
2044
|
this.data = {
|
|
1607
2045
|
version: 1,
|
|
1608
2046
|
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
@@ -1644,9 +2082,9 @@ var RunIndex = class {
|
|
|
1644
2082
|
persist() {
|
|
1645
2083
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1646
2084
|
this.writing = this.writing.then(async () => {
|
|
1647
|
-
const tmp = `${this.file}.${
|
|
1648
|
-
await
|
|
1649
|
-
await
|
|
2085
|
+
const tmp = `${this.file}.${randomUUID3()}.tmp`;
|
|
2086
|
+
await fs6.writeFile(tmp, snapshot, { mode: 384 });
|
|
2087
|
+
await fs6.rename(tmp, this.file);
|
|
1650
2088
|
}).catch(() => {
|
|
1651
2089
|
});
|
|
1652
2090
|
}
|
|
@@ -1656,27 +2094,27 @@ var RunIndex = class {
|
|
|
1656
2094
|
/** A stable Paperclip agent/company identity for this install, so the
|
|
1657
2095
|
* driver's scratch state is reused rather than growing per run. */
|
|
1658
2096
|
async identity() {
|
|
1659
|
-
const file =
|
|
2097
|
+
const file = path7.join(this.dir, "identity.json");
|
|
1660
2098
|
try {
|
|
1661
|
-
const parsed = JSON.parse(await
|
|
2099
|
+
const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1662
2100
|
if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
|
|
1663
2101
|
return { companyId: parsed.companyId, agentId: parsed.agentId };
|
|
1664
2102
|
}
|
|
1665
2103
|
} catch {
|
|
1666
2104
|
}
|
|
1667
|
-
const identity = { companyId:
|
|
1668
|
-
await
|
|
2105
|
+
const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
|
|
2106
|
+
await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
|
|
1669
2107
|
return identity;
|
|
1670
2108
|
}
|
|
1671
2109
|
};
|
|
1672
2110
|
|
|
1673
2111
|
// src/engine/user-defaults.ts
|
|
1674
|
-
import
|
|
2112
|
+
import fs7 from "fs/promises";
|
|
1675
2113
|
import os4 from "os";
|
|
1676
|
-
import
|
|
2114
|
+
import path8 from "path";
|
|
1677
2115
|
async function readJson(file) {
|
|
1678
2116
|
try {
|
|
1679
|
-
const parsed = JSON.parse(await
|
|
2117
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
1680
2118
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1681
2119
|
} catch {
|
|
1682
2120
|
return null;
|
|
@@ -1687,8 +2125,8 @@ function str3(value) {
|
|
|
1687
2125
|
}
|
|
1688
2126
|
async function claudeDefaults(cwd, home = os4.homedir()) {
|
|
1689
2127
|
const files = [
|
|
1690
|
-
...cwd ? [
|
|
1691
|
-
|
|
2128
|
+
...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
|
|
2129
|
+
path8.join(home, ".claude", "settings.json")
|
|
1692
2130
|
];
|
|
1693
2131
|
let model = null;
|
|
1694
2132
|
let effort = null;
|
|
@@ -1704,7 +2142,7 @@ async function claudeDefaults(cwd, home = os4.homedir()) {
|
|
|
1704
2142
|
async function codexDefaults(home = os4.homedir()) {
|
|
1705
2143
|
let text;
|
|
1706
2144
|
try {
|
|
1707
|
-
text = await
|
|
2145
|
+
text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
|
|
1708
2146
|
} catch {
|
|
1709
2147
|
return { model: null, effort: null };
|
|
1710
2148
|
}
|
|
@@ -1728,31 +2166,31 @@ var EFFORT_LEVELS = {
|
|
|
1728
2166
|
// src/engine/index.ts
|
|
1729
2167
|
var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
|
|
1730
2168
|
function moduleDir() {
|
|
1731
|
-
return
|
|
2169
|
+
return path9.dirname(fileURLToPath(import.meta.url));
|
|
1732
2170
|
}
|
|
1733
2171
|
function resolveWorkerPath() {
|
|
1734
2172
|
const dir = moduleDir();
|
|
1735
2173
|
for (const candidate of [
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2174
|
+
path9.join(dir, "worker.js"),
|
|
2175
|
+
path9.join(dir, "worker.ts"),
|
|
2176
|
+
path9.join(dir, "..", "engine", "worker.js"),
|
|
2177
|
+
path9.join(dir, "..", "engine", "worker.ts")
|
|
1740
2178
|
]) {
|
|
1741
2179
|
if (existsSync(candidate)) return candidate;
|
|
1742
2180
|
}
|
|
1743
2181
|
throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
|
|
1744
2182
|
}
|
|
1745
2183
|
async function installShim(dataDir) {
|
|
1746
|
-
const source =
|
|
2184
|
+
const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
|
|
1747
2185
|
if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
|
|
1748
|
-
const body = await
|
|
1749
|
-
const target =
|
|
1750
|
-
const current = await
|
|
2186
|
+
const body = await fs8.readFile(source, "utf8");
|
|
2187
|
+
const target = path9.join(dataDir, "bin", "cli-shim.sh");
|
|
2188
|
+
const current = await fs8.readFile(target, "utf8").catch(() => null);
|
|
1751
2189
|
if (current !== body) {
|
|
1752
|
-
await
|
|
1753
|
-
await
|
|
2190
|
+
await fs8.mkdir(path9.dirname(target), { recursive: true });
|
|
2191
|
+
await fs8.writeFile(target, body, { mode: 493 });
|
|
1754
2192
|
}
|
|
1755
|
-
await
|
|
2193
|
+
await fs8.chmod(target, 493);
|
|
1756
2194
|
return target;
|
|
1757
2195
|
}
|
|
1758
2196
|
function nowIso() {
|
|
@@ -1852,29 +2290,30 @@ function describeAuthStatus(provider, probe) {
|
|
|
1852
2290
|
};
|
|
1853
2291
|
}
|
|
1854
2292
|
async function createCodingSessionsEngine(options = {}) {
|
|
1855
|
-
const dataDir = options.dataDir ??
|
|
1856
|
-
const stateDir =
|
|
2293
|
+
const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
|
|
2294
|
+
const stateDir = path9.join(dataDir, "paperclip-state");
|
|
1857
2295
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1858
2296
|
const index = new RunIndex(dataDir);
|
|
1859
2297
|
await index.load();
|
|
1860
2298
|
const preferences = new PreferenceStore(dataDir);
|
|
1861
2299
|
await preferences.load();
|
|
1862
|
-
await
|
|
2300
|
+
await fs8.mkdir(stateDir, { recursive: true });
|
|
1863
2301
|
const identity = await index.identity();
|
|
2302
|
+
const agents = createAgentConnection(options.agents, dataDir);
|
|
1864
2303
|
const backups = options.rawBackup ? createRawBackupScheduler({
|
|
1865
2304
|
sink: options.rawBackup,
|
|
1866
2305
|
findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
|
|
1867
2306
|
onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
|
|
1868
2307
|
}) : null;
|
|
1869
2308
|
const discoveredSeen = /* @__PURE__ */ new Map();
|
|
1870
|
-
const healthFile =
|
|
2309
|
+
const healthFile = path9.join(dataDir, "provider-health.json");
|
|
1871
2310
|
let health = {};
|
|
1872
2311
|
try {
|
|
1873
|
-
health = JSON.parse(await
|
|
2312
|
+
health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
|
|
1874
2313
|
} catch {
|
|
1875
2314
|
health = {};
|
|
1876
2315
|
}
|
|
1877
|
-
const saveHealth = () =>
|
|
2316
|
+
const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
|
|
1878
2317
|
function noteRunOutcome(provider, status, texts) {
|
|
1879
2318
|
if (status === "completed") {
|
|
1880
2319
|
if (health[provider]) {
|
|
@@ -1991,7 +2430,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
1991
2430
|
async function startRun(session, prompt) {
|
|
1992
2431
|
const provider = session.provider;
|
|
1993
2432
|
const realCommand = await commandFor(provider);
|
|
1994
|
-
const runId =
|
|
2433
|
+
const runId = randomUUID4();
|
|
1995
2434
|
const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
|
|
1996
2435
|
const run = {
|
|
1997
2436
|
runId,
|
|
@@ -2029,7 +2468,36 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2029
2468
|
let fileOffset = null;
|
|
2030
2469
|
if (session.providerSessionId) {
|
|
2031
2470
|
const file = await findSessionFile(provider, session.providerSessionId, homes);
|
|
2032
|
-
if (file) fileOffset = (await
|
|
2471
|
+
if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
|
|
2472
|
+
}
|
|
2473
|
+
let agentArgs = [];
|
|
2474
|
+
let cliPrompt = prompt;
|
|
2475
|
+
let delivered = null;
|
|
2476
|
+
if (!session.agent && session.agentRemovedDue) {
|
|
2477
|
+
if (provider === "codex") cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
|
|
2478
|
+
delivered = { agentRemovedDue: false };
|
|
2479
|
+
}
|
|
2480
|
+
if (session.agent) {
|
|
2481
|
+
const bundle = await agents.readSessionBundle(session.id);
|
|
2482
|
+
if (!bundle) {
|
|
2483
|
+
const failed = saveRun({
|
|
2484
|
+
...run,
|
|
2485
|
+
status: "failed",
|
|
2486
|
+
endedAt: nowIso(),
|
|
2487
|
+
error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
|
|
2488
|
+
});
|
|
2489
|
+
const current = index.session(session.id);
|
|
2490
|
+
if (current) saveSession({ ...current, lastRunStatus: "failed" });
|
|
2491
|
+
return failed;
|
|
2492
|
+
}
|
|
2493
|
+
const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
|
|
2494
|
+
mcpUrl: await agents.mcpUrlFor(session.id)
|
|
2495
|
+
});
|
|
2496
|
+
agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
|
|
2497
|
+
if (provider === "codex" && session.agent.blockDue) {
|
|
2498
|
+
cliPrompt = `${renderFallbackBlock(bundle, path9.join(attachment.dir, "skills"))}${prompt}`;
|
|
2499
|
+
}
|
|
2500
|
+
delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
|
|
2033
2501
|
}
|
|
2034
2502
|
const child = forkWorker();
|
|
2035
2503
|
const liveRun = {
|
|
@@ -2045,6 +2513,10 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2045
2513
|
};
|
|
2046
2514
|
live.set(runId, liveRun);
|
|
2047
2515
|
emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
|
|
2516
|
+
if (delivered) {
|
|
2517
|
+
const current = index.session(session.id);
|
|
2518
|
+
if (current) saveSession({ ...current, ...delivered });
|
|
2519
|
+
}
|
|
2048
2520
|
const splitter = createLineSplitter();
|
|
2049
2521
|
const claude = provider === "claude" ? createClaudeNormalizer() : null;
|
|
2050
2522
|
const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
|
|
@@ -2083,6 +2555,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2083
2555
|
}
|
|
2084
2556
|
}
|
|
2085
2557
|
void pruneDriverScratch();
|
|
2558
|
+
if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
|
|
2086
2559
|
};
|
|
2087
2560
|
let settled = false;
|
|
2088
2561
|
child.on("message", (raw) => {
|
|
@@ -2197,16 +2670,39 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2197
2670
|
provider,
|
|
2198
2671
|
runId,
|
|
2199
2672
|
cwd: session.cwd,
|
|
2200
|
-
prompt,
|
|
2673
|
+
prompt: cliPrompt,
|
|
2201
2674
|
model,
|
|
2202
2675
|
effort: session.effort ?? null,
|
|
2203
2676
|
resumeSessionId: session.providerSessionId,
|
|
2204
2677
|
shimPath,
|
|
2205
2678
|
realCommand,
|
|
2206
|
-
identity
|
|
2679
|
+
identity,
|
|
2680
|
+
agentArgs
|
|
2207
2681
|
});
|
|
2208
2682
|
return run;
|
|
2209
2683
|
}
|
|
2684
|
+
async function checkCompaction(sessionId) {
|
|
2685
|
+
const session = index.session(sessionId);
|
|
2686
|
+
if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
|
|
2687
|
+
const file = await findSessionFile("codex", session.providerSessionId, homes);
|
|
2688
|
+
if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
|
|
2689
|
+
const current = index.session(sessionId);
|
|
2690
|
+
if (current?.agent) {
|
|
2691
|
+
saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
async function attachAgent(sessionId, selection, options2) {
|
|
2695
|
+
const bundle = await agents.loadBundle(selection);
|
|
2696
|
+
await agents.saveSessionBundle(sessionId, bundle);
|
|
2697
|
+
return {
|
|
2698
|
+
...sessionAgentRecord(bundle, {
|
|
2699
|
+
toolsServed: await agents.mcpUrlFor(sessionId) !== null,
|
|
2700
|
+
variables: selection.variables ?? {}
|
|
2701
|
+
}),
|
|
2702
|
+
blockDue: options2.blockDue,
|
|
2703
|
+
deliveredAt: null
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2210
2706
|
function killGroup(liveRun) {
|
|
2211
2707
|
const target = liveRun.pgid;
|
|
2212
2708
|
if (target === null) return;
|
|
@@ -2222,7 +2718,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2222
2718
|
}, 3e3);
|
|
2223
2719
|
}
|
|
2224
2720
|
async function pruneDriverScratch() {
|
|
2225
|
-
const runsDir =
|
|
2721
|
+
const runsDir = path9.join(
|
|
2226
2722
|
stateDir,
|
|
2227
2723
|
"instances",
|
|
2228
2724
|
"default",
|
|
@@ -2233,9 +2729,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2233
2729
|
"claude-runtime",
|
|
2234
2730
|
"runs"
|
|
2235
2731
|
);
|
|
2236
|
-
const entries = await
|
|
2732
|
+
const entries = await fs8.readdir(runsDir).catch(() => []);
|
|
2237
2733
|
await Promise.all(
|
|
2238
|
-
entries.filter((name) => !live.has(name)).map((name) =>
|
|
2734
|
+
entries.filter((name) => !live.has(name)).map((name) => fs8.rm(path9.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
|
|
2239
2735
|
);
|
|
2240
2736
|
}
|
|
2241
2737
|
async function probe() {
|
|
@@ -2276,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2276
2772
|
function newSession(input) {
|
|
2277
2773
|
const at = nowIso();
|
|
2278
2774
|
return {
|
|
2279
|
-
id:
|
|
2775
|
+
id: randomUUID4(),
|
|
2280
2776
|
provider: input.provider,
|
|
2281
2777
|
providerSessionId: input.providerSessionId,
|
|
2282
2778
|
cwd: input.cwd,
|
|
@@ -2294,17 +2790,17 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2294
2790
|
function resolveFolder(raw) {
|
|
2295
2791
|
const trimmed = raw.trim();
|
|
2296
2792
|
if (!trimmed) return null;
|
|
2297
|
-
return
|
|
2793
|
+
return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2298
2794
|
}
|
|
2299
2795
|
async function checkFolder(raw) {
|
|
2300
2796
|
const target = resolveFolder(raw);
|
|
2301
2797
|
if (!target) return { path: "", exists: false, isDirectory: false };
|
|
2302
|
-
const stat = await
|
|
2798
|
+
const stat = await fs8.stat(target).catch(() => null);
|
|
2303
2799
|
return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
|
|
2304
2800
|
}
|
|
2305
2801
|
async function assertFolder(cwd) {
|
|
2306
|
-
const resolved =
|
|
2307
|
-
const stat = await
|
|
2802
|
+
const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
|
|
2803
|
+
const stat = await fs8.stat(resolved).catch(() => null);
|
|
2308
2804
|
if (!stat?.isDirectory()) {
|
|
2309
2805
|
throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
|
|
2310
2806
|
}
|
|
@@ -2366,7 +2862,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2366
2862
|
folders.map(async ([folder, lastUsedAt]) => ({
|
|
2367
2863
|
path: folder,
|
|
2368
2864
|
lastUsedAt,
|
|
2369
|
-
exists: (await
|
|
2865
|
+
exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
|
|
2370
2866
|
}))
|
|
2371
2867
|
);
|
|
2372
2868
|
},
|
|
@@ -2411,7 +2907,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2411
2907
|
async createFolder(raw) {
|
|
2412
2908
|
const target = resolveFolder(raw);
|
|
2413
2909
|
if (!target) throw new Error("Type a folder path first.");
|
|
2414
|
-
await
|
|
2910
|
+
await fs8.mkdir(target, { recursive: true });
|
|
2415
2911
|
return checkFolder(target);
|
|
2416
2912
|
},
|
|
2417
2913
|
async sessionActivity(provider, providerSessionId) {
|
|
@@ -2449,48 +2945,63 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2449
2945
|
reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
|
|
2450
2946
|
};
|
|
2451
2947
|
}
|
|
2452
|
-
const stat = await
|
|
2948
|
+
const stat = await fs8.stat(file);
|
|
2453
2949
|
if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
|
|
2454
2950
|
return {
|
|
2455
2951
|
available: false,
|
|
2456
2952
|
reason: `This transcript is ${formatFileSize(stat.size)}, larger than the ${formatFileSize(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
|
|
2457
2953
|
};
|
|
2458
2954
|
}
|
|
2459
|
-
const content = await
|
|
2460
|
-
return { available: true, fileName:
|
|
2955
|
+
const content = await fs8.readFile(file, "utf8");
|
|
2956
|
+
return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
|
|
2461
2957
|
},
|
|
2462
2958
|
async start(input) {
|
|
2463
2959
|
const prompt = input.prompt.trim();
|
|
2464
2960
|
if (!prompt) throw new Error("Type what you want done before starting.");
|
|
2465
2961
|
const cwd = await assertFolder(input.cwd);
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
);
|
|
2962
|
+
const draft = newSession({
|
|
2963
|
+
provider: input.provider,
|
|
2964
|
+
cwd,
|
|
2965
|
+
title: titleFrom(prompt),
|
|
2966
|
+
model: input.model?.trim() || null,
|
|
2967
|
+
effort: input.effort?.trim() || null,
|
|
2968
|
+
origin: "started",
|
|
2969
|
+
providerSessionId: null
|
|
2970
|
+
});
|
|
2971
|
+
if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
|
|
2972
|
+
const session = saveSession(draft);
|
|
2477
2973
|
const run = await startRun(session, prompt);
|
|
2478
2974
|
return { session: index.session(session.id) ?? session, run, queued: false };
|
|
2479
2975
|
},
|
|
2480
2976
|
async takeover(input) {
|
|
2481
2977
|
const existing = index.session(input.providerSessionId);
|
|
2482
|
-
if (existing)
|
|
2978
|
+
if (existing) {
|
|
2979
|
+
if (!input.agent) return existing;
|
|
2980
|
+
return engine.setAgent(existing.id, input.agent);
|
|
2981
|
+
}
|
|
2483
2982
|
const cwd = await assertFolder(input.cwd);
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
);
|
|
2983
|
+
const draft = newSession({
|
|
2984
|
+
provider: input.provider,
|
|
2985
|
+
cwd,
|
|
2986
|
+
title: input.title?.trim() || "Taken-over chat",
|
|
2987
|
+
model: null,
|
|
2988
|
+
origin: "takeover",
|
|
2989
|
+
providerSessionId: input.providerSessionId
|
|
2990
|
+
});
|
|
2991
|
+
if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
|
|
2992
|
+
return saveSession(draft);
|
|
2993
|
+
},
|
|
2994
|
+
async setAgent(sessionId, selection) {
|
|
2995
|
+
const session = index.session(String(sessionId ?? ""));
|
|
2996
|
+
if (!session) throw new Error("This session is not in the list anymore.");
|
|
2997
|
+
if (!selection) {
|
|
2998
|
+
const told = session.provider === "codex" && session.agent?.deliveredAt != null;
|
|
2999
|
+
return saveSession({ ...session, agent: null, agentRemovedDue: told });
|
|
3000
|
+
}
|
|
3001
|
+
const agent = await attachAgent(session.id, selection, {
|
|
3002
|
+
blockDue: session.provider === "codex" && session.providerSessionId !== null
|
|
3003
|
+
});
|
|
3004
|
+
return saveSession({ ...session, agent, agentRemovedDue: false });
|
|
2494
3005
|
},
|
|
2495
3006
|
async send(sessionId, rawPrompt) {
|
|
2496
3007
|
const prompt = rawPrompt.trim();
|
|
@@ -2542,6 +3053,21 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2542
3053
|
if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
|
|
2543
3054
|
return saveSession(next);
|
|
2544
3055
|
},
|
|
3056
|
+
async agentAccess() {
|
|
3057
|
+
return agents.access();
|
|
3058
|
+
},
|
|
3059
|
+
async agentCatalogRpc(request) {
|
|
3060
|
+
return agents.catalogRpc(request);
|
|
3061
|
+
},
|
|
3062
|
+
async agentDetails(agentId) {
|
|
3063
|
+
return agents.details(String(agentId ?? ""));
|
|
3064
|
+
},
|
|
3065
|
+
setMcpBaseUrl(baseUrl) {
|
|
3066
|
+
agents.setMcpBaseUrl(baseUrl);
|
|
3067
|
+
},
|
|
3068
|
+
async handleMcp(secret, body) {
|
|
3069
|
+
return agents.handleMcp(secret, body);
|
|
3070
|
+
},
|
|
2545
3071
|
async dispose() {
|
|
2546
3072
|
for (const liveRun of live.values()) {
|
|
2547
3073
|
liveRun.stopRequested = true;
|
|
@@ -2590,6 +3116,8 @@ function openFolderIn(folder, target) {
|
|
|
2590
3116
|
});
|
|
2591
3117
|
}
|
|
2592
3118
|
export {
|
|
3119
|
+
DEFAULT_SERVER_URL,
|
|
3120
|
+
DEFAULT_SUPABASE_URL,
|
|
2593
3121
|
EFFORT_LEVELS,
|
|
2594
3122
|
PreferenceStore,
|
|
2595
3123
|
RAW_TRANSCRIPT_MAX_BYTES,
|