@agentproto/runtime 3.6.1 → 4.1.0

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.
Files changed (39) hide show
  1. package/README.md +8 -3
  2. package/dist/app-ui-build.d.ts +96 -0
  3. package/dist/app-ui-build.mjs +250 -0
  4. package/dist/app-ui-build.mjs.map +1 -0
  5. package/dist/app-ui-delivery.d.ts +75 -0
  6. package/dist/app-ui-delivery.mjs +144 -0
  7. package/dist/app-ui-delivery.mjs.map +1 -0
  8. package/dist/app-ui-placeholder.d.ts +73 -0
  9. package/dist/app-ui-placeholder.mjs +89 -0
  10. package/dist/app-ui-placeholder.mjs.map +1 -0
  11. package/dist/catalog-models.d.ts +5 -3
  12. package/dist/catalog-models.mjs.map +1 -1
  13. package/dist/config-schema.d.ts +134 -0
  14. package/dist/config-schema.mjs +3697 -0
  15. package/dist/config-schema.mjs.map +1 -0
  16. package/dist/config.d.ts +22 -2
  17. package/dist/config.mjs +3615 -4
  18. package/dist/config.mjs.map +1 -1
  19. package/dist/{context-continuity-ib9_bVYM.d.ts → context-continuity-Bn99UtVC.d.ts} +40 -1
  20. package/dist/{heartbeat-COGpMrJS.d.ts → heartbeat-BJtxbDb9.d.ts} +13 -1
  21. package/dist/heartbeat.d.ts +1 -1
  22. package/dist/index.d.ts +3057 -151
  23. package/dist/index.mjs +17669 -7188
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/mcp-imports.d.ts +2 -1
  26. package/dist/resume-strategies.mjs +27 -1
  27. package/dist/resume-strategies.mjs.map +1 -1
  28. package/dist/{session-config-DbWP9RRj.d.ts → session-config-ByfVeEQJ.d.ts} +1 -1
  29. package/dist/session-presence.mjs +3613 -6
  30. package/dist/session-presence.mjs.map +1 -1
  31. package/dist/session-story.d.ts +10 -0
  32. package/dist/{spawn-defaults-CwI9Puwy.d.ts → spawn-defaults-CZj39Znq.d.ts} +146 -2
  33. package/dist/user-presets.d.ts +8 -2
  34. package/dist/user-presets.mjs +2 -1
  35. package/dist/user-presets.mjs.map +1 -1
  36. package/docs/TRANSMITTER.md +356 -0
  37. package/docs/mcp-tools/agent_start.md +476 -0
  38. package/docs/mcp-tools/app_run.md +47 -0
  39. package/package.json +47 -23
@@ -1,13 +1,3590 @@
1
- import { promises } from 'fs';
1
+ import { createHash } from 'crypto';
2
+ import { join, resolve } from 'path';
3
+ import '@agentproto/model-catalog/llm';
4
+ import { createReadStream, promises } from 'fs';
2
5
  import { homedir } from 'os';
3
- import { join } from 'path';
6
+ import { createInterface } from 'readline';
7
+ import 'fs/promises';
8
+ import * as providers_store_star from '@agentproto/providers-store';
9
+ import '@agentproto/model-catalog/route-identity';
10
+ import '@agentproto/provider-presets';
11
+ import '@agentproto/secrets/provision/recipe';
12
+ import { z } from 'zod';
13
+ import '@agentproto/auth';
14
+ import 'child_process';
15
+ import '@agentproto/command-sandbox';
16
+ import '@agentproto/plugin-local-browser';
17
+ import '@modelcontextprotocol/sdk/types.js';
18
+ import '@agentproto/provider-kit';
19
+ import '@modelcontextprotocol/sdk/client/index.js';
20
+ import '@modelcontextprotocol/sdk/client/streamableHttp.js';
21
+ import 'net';
22
+ import { SandboxSpecSchema } from '@agentproto/sandbox';
23
+ import { CatalogProviderSchema } from '@agentproto/model-catalog';
24
+ import '@agentproto/app-client/runner-select';
25
+ import '@agentproto/app-client/display-mode';
26
+ import '@agentproto/tool';
27
+ import '@agentproto/mcp-server';
28
+ import '@agentproto/apps';
29
+ import 'events';
30
+ import '@agentproto/driver-agent-cli';
31
+ import { resolvePairPageUrl, DEFAULT_PAIR_PAGE } from '@agentproto/secrets/pairing/browser';
4
32
 
5
33
  /**
6
34
  * @agentproto/runtime v0.1.0-alpha
7
35
  * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
8
36
  */
37
+ var __defProp = Object.defineProperty;
38
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
39
+ var __getOwnPropNames = Object.getOwnPropertyNames;
40
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
41
+ var __esm = (fn, res) => function __init() {
42
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
43
+ };
44
+ var __export = (target, all) => {
45
+ for (var name in all)
46
+ __defProp(target, name, { get: all[name], enumerable: true });
47
+ };
48
+ var __copyProps = (to, from, except, desc) => {
49
+ if (from && typeof from === "object" || typeof from === "function") {
50
+ for (let key of __getOwnPropNames(from))
51
+ if (!__hasOwnProp.call(to, key) && key !== except)
52
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
53
+ }
54
+ return to;
55
+ };
56
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget);
57
+
58
+ // src/worktree-isolation.ts
59
+ var WORKTREE_ISOLATION_ENV, DEFAULT_WORKTREE_ISOLATION;
60
+ var init_worktree_isolation = __esm({
61
+ "src/worktree-isolation.ts"() {
62
+ init_config();
63
+ WORKTREE_ISOLATION_ENV = "AGENTPROTO_WORKTREES_ISOLATION";
64
+ DEFAULT_WORKTREE_ISOLATION = "on-request";
65
+ }
66
+ });
67
+
68
+ // src/spawn-attach.ts
69
+ var SPAWN_ATTACH_ENV, DEFAULT_SPAWN_ATTACH;
70
+ var init_spawn_attach = __esm({
71
+ "src/spawn-attach.ts"() {
72
+ init_config();
73
+ SPAWN_ATTACH_ENV = "AGENTPROTO_SPAWN_ATTACH";
74
+ DEFAULT_SPAWN_ATTACH = "always";
75
+ }
76
+ });
77
+ var SPAWN_DEDUPE_ENV, DEFAULT_SPAWN_DEDUPE;
78
+ var init_spawn_dedupe = __esm({
79
+ "src/spawn-dedupe.ts"() {
80
+ init_config();
81
+ SPAWN_DEDUPE_ENV = "AGENTPROTO_SPAWN_DEDUPE";
82
+ DEFAULT_SPAWN_DEDUPE = "always";
83
+ }
84
+ });
85
+ var init_pr_provenance = __esm({
86
+ "src/pr-provenance.ts"() {
87
+ }
88
+ });
89
+
90
+ // src/tool-presenter.ts
91
+ function isRecord(value) {
92
+ return typeof value === "object" && value !== null && !Array.isArray(value);
93
+ }
94
+ function truncate(value, max) {
95
+ const oneLine = value.replace(/\s+/g, " ").trim();
96
+ return oneLine.length > max ? `${oneLine.slice(0, max - 1)}\u2026` : oneLine;
97
+ }
98
+ function formatArgValue(value) {
99
+ if (typeof value === "string") return value;
100
+ if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? "" : "s"}`;
101
+ if (isRecord(value)) return JSON.stringify(value);
102
+ return String(value);
103
+ }
104
+ function pickSalientArg(args) {
105
+ for (const key of SALIENT_ARG_KEYS) {
106
+ const value = args[key];
107
+ if (value !== void 0 && value !== null && value !== "") {
108
+ return formatArgValue(value);
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function subagentSummary(args) {
114
+ const description = typeof args.description === "string" ? args.description : typeof args.prompt === "string" ? args.prompt : "subagent";
115
+ return `\u21B3 subagent: ${truncate(description, MAX_CALL_LENGTH)}`;
116
+ }
117
+ function formatToolCall(toolName, args) {
118
+ const name = toolName || "tool";
119
+ const argsRecord = isRecord(args) ? args : {};
120
+ const bespoke = BESPOKE_TOOLS[name.toLowerCase()];
121
+ if (bespoke) return bespoke(argsRecord);
122
+ const salient = pickSalientArg(argsRecord);
123
+ if (salient !== null) {
124
+ if (name.toLowerCase().includes(salient.toLowerCase())) {
125
+ return truncate(name, MAX_CALL_LENGTH);
126
+ }
127
+ return truncate(`${name} ${salient}`, MAX_CALL_LENGTH);
128
+ }
129
+ if (Object.keys(argsRecord).length === 0) return name;
130
+ return truncate(`${name} ${JSON.stringify(args)}`, MAX_CALL_LENGTH);
131
+ }
132
+ var SALIENT_ARG_KEYS, MAX_CALL_LENGTH, BESPOKE_TOOLS;
133
+ var init_tool_presenter = __esm({
134
+ "src/tool-presenter.ts"() {
135
+ SALIENT_ARG_KEYS = [
136
+ "file_path",
137
+ "path",
138
+ "filePath",
139
+ "file",
140
+ "command",
141
+ "pattern",
142
+ "query",
143
+ "q",
144
+ "url",
145
+ "todos",
146
+ "description",
147
+ "prompt"
148
+ ];
149
+ MAX_CALL_LENGTH = 120;
150
+ BESPOKE_TOOLS = {
151
+ schedulewakeup: (args) => {
152
+ const delay = args.delaySeconds ?? args.delay ?? "?";
153
+ const reason = typeof args.reason === "string" ? args.reason : "";
154
+ return reason ? `\u23F0 wake in ${delay}s \u2014 ${reason}` : `\u23F0 wake in ${delay}s`;
155
+ },
156
+ task: subagentSummary,
157
+ agent: subagentSummary,
158
+ todowrite: (args) => {
159
+ const todos = Array.isArray(args.todos) ? args.todos : [];
160
+ return `\u2611 todos (${todos.length})`;
161
+ },
162
+ exitplanmode: () => "\u{1F4CB} plan ready"
163
+ };
164
+ }
165
+ });
166
+
167
+ // src/tool-call-record.ts
168
+ var init_tool_call_record = __esm({
169
+ "src/tool-call-record.ts"() {
170
+ }
171
+ });
172
+ var init_usage = __esm({
173
+ "src/usage.ts"() {
174
+ }
175
+ });
176
+ function defaultTranscriptBaseDir() {
177
+ return join(homedir(), ".agentproto", "sessions");
178
+ }
179
+ function sessionTranscriptDir(sessionId, baseDir) {
180
+ return join(defaultTranscriptBaseDir(), sessionId);
181
+ }
182
+ function sessionEventsPath(sessionId, baseDir) {
183
+ return join(sessionTranscriptDir(sessionId), "events.jsonl");
184
+ }
185
+ var init_transcript_writer = __esm({
186
+ "src/transcript-writer.ts"() {
187
+ init_tool_call_record();
188
+ init_pr_provenance();
189
+ init_usage();
190
+ }
191
+ });
192
+
193
+ // src/transcript-export.ts
194
+ var transcript_export_exports = {};
195
+ __export(transcript_export_exports, {
196
+ crossValidateHermesExport: () => crossValidateHermesExport,
197
+ discoverCodexSessions: () => discoverCodexSessions,
198
+ discoverHermesSessions: () => discoverHermesSessions,
199
+ discoverMastracodeInprocessSessions: () => discoverMastracodeInprocessSessions,
200
+ discoverOpenCodeSessions: () => discoverOpenCodeSessions,
201
+ discoverPiSessions: () => discoverPiSessions,
202
+ exportAgentSession: () => exportAgentSession,
203
+ exportClaudeCodeSession: () => exportClaudeCodeSession,
204
+ exportCodexSession: () => exportCodexSession,
205
+ exportDaemonEventsSession: () => exportDaemonEventsSession,
206
+ exportHermesSession: () => exportHermesSession,
207
+ exportMastracodeInprocessSession: () => exportMastracodeInprocessSession,
208
+ exportOpenCodeSession: () => exportOpenCodeSession,
209
+ exportPiSession: () => exportPiSession,
210
+ renderJson: () => renderJson,
211
+ renderMarkdown: () => renderMarkdown
212
+ });
213
+ function trunc(text, n) {
214
+ if (text.length <= n) return text;
215
+ return text.slice(0, n) + `
216
+ \u2026 [${text.length - n} chars truncated]`;
217
+ }
218
+ function renderMarkdown(session, opts = {}) {
219
+ const { meta, messages } = session;
220
+ const maxToolChars = opts.maxToolChars ?? 1200;
221
+ const out = [];
222
+ out.push(`# ${meta.title ?? "(untitled)"}`);
223
+ out.push("");
224
+ const sourceNote = meta.source ? ` \xB7 source \`${meta.source}\`` : "";
225
+ const originNote = meta.origin ? ` \xB7 origin \`${meta.origin}\`` : "";
226
+ const callerNote = meta.callerSessionId ? ` \xB7 caller \`${meta.callerSessionId}\`` : "";
227
+ out.push(`> Session${sourceNote}${originNote}${callerNote}`);
228
+ out.push("");
229
+ out.push("| | |");
230
+ out.push("|---|---|");
231
+ if (meta.model) out.push(`| Model | \`${meta.model}\` |`);
232
+ if (meta.startedAt || meta.endedAt) {
233
+ out.push(`| Start \u2192 end | ${meta.startedAt ?? "?"} \u2192 ${meta.endedAt ?? "?"} |`);
234
+ }
235
+ if (meta.messageCount !== void 0 || meta.toolCallCount !== void 0) {
236
+ const parts = [];
237
+ if (meta.messageCount !== void 0) parts.push(`${meta.messageCount} messages`);
238
+ if (meta.toolCallCount !== void 0) parts.push(`${meta.toolCallCount} tool calls`);
239
+ out.push(`| Count | ${parts.join(" \xB7 ")} |`);
240
+ }
241
+ if (meta.tokens && Object.keys(meta.tokens).length) {
242
+ const t = meta.tokens;
243
+ const parts = [];
244
+ if (t.input !== void 0) parts.push(`in ${t.input.toLocaleString("en-US")}`);
245
+ if (t.output !== void 0) parts.push(`out ${t.output.toLocaleString("en-US")}`);
246
+ if (t.cacheRead !== void 0 || t.cacheWrite !== void 0) {
247
+ parts.push(
248
+ `cache r/w ${(t.cacheRead ?? 0).toLocaleString("en-US")}/${(t.cacheWrite ?? 0).toLocaleString("en-US")}`
249
+ );
250
+ }
251
+ if (t.reasoning !== void 0) parts.push(`reason. ${t.reasoning.toLocaleString("en-US")}`);
252
+ if (parts.length) out.push(`| Tokens | ${parts.join(" \xB7 ")} |`);
253
+ }
254
+ if (meta.costUsd !== void 0) {
255
+ out.push(`| Cost | $${meta.costUsd.toFixed(4)} |`);
256
+ }
257
+ out.push("");
258
+ out.push("---");
259
+ out.push("");
260
+ for (const m of messages) {
261
+ const icon = ROLE_ICON[m.role] ?? m.role;
262
+ const nameSuffix = m.toolName ? ` \xB7 \`${m.toolName}\`` : m.from ? ` \xB7 from ${m.from.relation} \`${m.from.label ?? m.from.sessionId ?? ""}\` (${m.from.kind})` : "";
263
+ out.push(`### ${icon}${nameSuffix}`);
264
+ if (m.reasoning?.trim()) {
265
+ out.push("");
266
+ out.push("<details><summary>\u{1F4AD} reasoning</summary>");
267
+ out.push("");
268
+ out.push(trunc(m.reasoning, 4e3));
269
+ out.push("");
270
+ out.push("</details>");
271
+ }
272
+ if (m.text?.trim()) {
273
+ out.push("");
274
+ if (m.role === "tool") {
275
+ out.push("```");
276
+ out.push(trunc(m.text, maxToolChars));
277
+ out.push("```");
278
+ } else {
279
+ out.push(m.text.trim());
280
+ }
281
+ }
282
+ if (m.toolCalls?.length) {
283
+ out.push("");
284
+ for (const tc of m.toolCalls) {
285
+ let parsedArgs;
286
+ try {
287
+ parsedArgs = JSON.parse(tc.args);
288
+ } catch {
289
+ parsedArgs = tc.args;
290
+ }
291
+ out.push(`> \u{1F4DE} ${formatToolCall(tc.name, parsedArgs)}`);
292
+ }
293
+ }
294
+ out.push("");
295
+ }
296
+ return out.join("\n");
297
+ }
298
+ function renderJson(session) {
299
+ return JSON.stringify(session, null, 2);
300
+ }
301
+ async function exportClaudeCodeSession(adapterSessionId, cwd, configDir) {
302
+ if (!cwd) {
303
+ throw new Error(
304
+ "claude-code exporter: cwd is required to locate the JSONL file.\nPass cwd explicitly or use a session id that is in the registry."
305
+ );
306
+ }
307
+ const filePath = join(claudeCodeProjectDir(cwd, configDir), `${adapterSessionId}.jsonl`);
308
+ let stream;
309
+ try {
310
+ stream = createReadStream(filePath, { encoding: "utf8" });
311
+ await new Promise((resolve16, reject) => {
312
+ stream.once("error", reject);
313
+ stream.once("open", resolve16);
314
+ });
315
+ } catch (err) {
316
+ const code = err.code;
317
+ if (code === "ENOENT") {
318
+ throw new Error(
319
+ `claude-code: JSONL file not found: ${filePath}
320
+ Verify cwd="${cwd}" and adapterSessionId="${adapterSessionId}".`
321
+ );
322
+ }
323
+ throw err;
324
+ }
325
+ const messages = [];
326
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
327
+ for await (const line of rl) {
328
+ const trimmed = line.trim();
329
+ if (!trimmed) continue;
330
+ let entry;
331
+ try {
332
+ entry = JSON.parse(trimmed);
333
+ } catch {
334
+ continue;
335
+ }
336
+ if (IGNORED_CLAUDE_TYPES.has(entry.type)) continue;
337
+ if (entry.type !== "user" && entry.type !== "assistant") continue;
338
+ const msg = entry.message;
339
+ if (!msg) continue;
340
+ const role = msg.role === "assistant" ? "assistant" : "user";
341
+ const content = msg.content;
342
+ if (typeof content === "string") {
343
+ if (content.trim()) messages.push({ role, text: content.trim() });
344
+ continue;
345
+ }
346
+ if (!Array.isArray(content)) continue;
347
+ let textAcc = "";
348
+ let reasoningAcc = "";
349
+ const toolCalls = [];
350
+ const toolResults = [];
351
+ for (const block of content) {
352
+ switch (block.type) {
353
+ case "text": {
354
+ textAcc += block.text;
355
+ break;
356
+ }
357
+ case "thinking": {
358
+ reasoningAcc += block.thinking;
359
+ break;
360
+ }
361
+ case "tool_use": {
362
+ const tb = block;
363
+ const args = typeof tb.input === "string" ? tb.input : JSON.stringify(tb.input ?? {});
364
+ toolCalls.push({ name: tb.name, args });
365
+ break;
366
+ }
367
+ case "tool_result": {
368
+ const tb = block;
369
+ let resultText = "";
370
+ if (typeof tb.content === "string") {
371
+ resultText = tb.content;
372
+ } else if (Array.isArray(tb.content)) {
373
+ for (const c of tb.content) {
374
+ if (c.type === "text") resultText += c.text;
375
+ }
376
+ }
377
+ toolResults.push({ text: resultText });
378
+ break;
379
+ }
380
+ }
381
+ }
382
+ if (textAcc.trim() || toolCalls.length || reasoningAcc) {
383
+ const m = { role };
384
+ if (textAcc.trim()) m.text = textAcc.trim();
385
+ if (reasoningAcc) m.reasoning = reasoningAcc;
386
+ if (toolCalls.length) m.toolCalls = toolCalls;
387
+ messages.push(m);
388
+ }
389
+ for (const tr of toolResults) {
390
+ messages.push({ role: "tool", text: tr.text });
391
+ }
392
+ }
393
+ return { meta: { source: "claude-code" }, messages };
394
+ }
395
+ async function importNodeSqlite() {
396
+ return import(
397
+ /* @vite-ignore */
398
+ NODE_SQLITE
399
+ );
400
+ }
401
+ async function openHermesDb(dbPath) {
402
+ let DatabaseSync;
403
+ try {
404
+ const sqlite = await importNodeSqlite();
405
+ DatabaseSync = sqlite.DatabaseSync;
406
+ } catch {
407
+ throw new Error("hermes: node:sqlite unavailable. Requires Node.js \u226522.5.0.");
408
+ }
409
+ try {
410
+ return new DatabaseSync(dbPath, { readOnly: true });
411
+ } catch (err) {
412
+ const msg = String(err);
413
+ if (err.code === "ENOENT" || msg.includes("unable to open database file")) {
414
+ throw new Error(
415
+ `hermes: state.db not found at ${dbPath}. Has hermes been run at least once?`
416
+ );
417
+ }
418
+ if (msg.includes("SQLITE_BUSY") || msg.includes("database is locked")) {
419
+ throw new Error(
420
+ `hermes: database is locked (SQLITE_BUSY). Hermes may be writing. Try again in a moment.`
421
+ );
422
+ }
423
+ throw err;
424
+ }
425
+ }
426
+ function withRetryOnBusy(fn) {
427
+ try {
428
+ return fn();
429
+ } catch (err) {
430
+ const msg = String(err);
431
+ if (msg.includes("SQLITE_BUSY") || msg.includes("database is locked")) {
432
+ try {
433
+ return fn();
434
+ } catch {
435
+ throw new Error(
436
+ `hermes: database is locked. Hermes may be actively writing. Try again.`
437
+ );
438
+ }
439
+ }
440
+ throw err;
441
+ }
442
+ }
443
+ async function openReadonlySqlite(dbPath, label) {
444
+ let DatabaseSync;
445
+ try {
446
+ const sqlite = await importNodeSqlite();
447
+ DatabaseSync = sqlite.DatabaseSync;
448
+ } catch {
449
+ throw new Error(`${label}: node:sqlite unavailable. Requires Node.js \u226522.5.0.`);
450
+ }
451
+ try {
452
+ return new DatabaseSync(dbPath, { readOnly: true });
453
+ } catch (err) {
454
+ const msg = String(err);
455
+ if (err.code === "ENOENT" || msg.includes("unable to open database file")) {
456
+ throw new Error(`${label}: database not found at ${dbPath}. Has ${label} been run at least once?`);
457
+ }
458
+ if (msg.includes("SQLITE_BUSY") || msg.includes("database is locked")) {
459
+ throw new Error(`${label}: database is locked (SQLITE_BUSY). It may be writing. Try again in a moment.`);
460
+ }
461
+ throw err;
462
+ }
463
+ }
464
+ async function exportHermesSession(adapterSessionId) {
465
+ const dbPath = join(homedir(), ".hermes", "state.db");
466
+ const db = await openHermesDb(dbPath);
467
+ const session = withRetryOnBusy(
468
+ () => db.prepare("SELECT * FROM sessions WHERE id = ?").get(adapterSessionId)
469
+ );
470
+ if (!session) {
471
+ db.close();
472
+ throw new Error(
473
+ `hermes: session "${adapterSessionId}" not found in ${dbPath}. ACP sessions are indexed by the agentproto UUID passed as resumeSessionId.`
474
+ );
475
+ }
476
+ const rows = withRetryOnBusy(
477
+ () => db.prepare(
478
+ "SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC"
479
+ ).all(adapterSessionId)
480
+ );
481
+ db.close();
482
+ const cost = session.actual_cost_usd != null ? session.actual_cost_usd : session.estimated_cost_usd;
483
+ const startedAt = session.started_at ? new Date(session.started_at * 1e3).toISOString().replace("T", " ").slice(0, 19) : void 0;
484
+ const endedAt = session.ended_at ? new Date(session.ended_at * 1e3).toISOString().replace("T", " ").slice(0, 19) : void 0;
485
+ const meta = {
486
+ ...session.title ? { title: session.title } : {},
487
+ ...session.model ? { model: session.model } : {},
488
+ ...startedAt ? { startedAt } : {},
489
+ ...endedAt ? { endedAt } : {},
490
+ ...session.message_count !== void 0 ? { messageCount: session.message_count } : {},
491
+ ...session.tool_call_count !== void 0 ? { toolCallCount: session.tool_call_count } : {},
492
+ ...cost != null ? { costUsd: Number(cost) } : {},
493
+ ...session.source ? { source: session.source } : {}
494
+ };
495
+ const tokensInput = session.input_tokens;
496
+ const tokensOutput = session.output_tokens;
497
+ const tokensCacheRead = session.cache_read_tokens;
498
+ const tokensCacheWrite = session.cache_write_tokens;
499
+ const tokensReasoning = session.reasoning_tokens;
500
+ if (tokensInput !== void 0 || tokensOutput !== void 0 || tokensCacheRead !== void 0 || tokensCacheWrite !== void 0 || tokensReasoning !== void 0) {
501
+ meta.tokens = {
502
+ ...tokensInput !== void 0 ? { input: tokensInput } : {},
503
+ ...tokensOutput !== void 0 ? { output: tokensOutput } : {},
504
+ ...tokensCacheRead !== void 0 ? { cacheRead: tokensCacheRead } : {},
505
+ ...tokensCacheWrite !== void 0 ? { cacheWrite: tokensCacheWrite } : {},
506
+ ...tokensReasoning !== void 0 ? { reasoning: tokensReasoning } : {}
507
+ };
508
+ }
509
+ const messages = rows.map((row) => {
510
+ const validRoles = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system"]);
511
+ const role = validRoles.has(row.role) ? row.role : "user";
512
+ const reasoning = row.reasoning ?? row.reasoning_content;
513
+ let toolCalls;
514
+ if (row.tool_calls) {
515
+ try {
516
+ const tc = JSON.parse(row.tool_calls);
517
+ if (Array.isArray(tc) && tc.length) {
518
+ toolCalls = tc.map((c) => {
519
+ const entry = c;
520
+ const fn = entry.function != null ? entry.function : entry;
521
+ const name = String(fn.name ?? entry.name ?? "tool");
522
+ const args = typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {});
523
+ return { name, args };
524
+ });
525
+ }
526
+ } catch {
527
+ }
528
+ }
529
+ const m = { role };
530
+ if (row.content?.trim()) m.text = row.content.trim();
531
+ if (reasoning && String(reasoning).trim()) m.reasoning = String(reasoning);
532
+ if (row.tool_name) m.toolName = row.tool_name;
533
+ if (toolCalls) m.toolCalls = toolCalls;
534
+ if (row.timestamp) m.ts = row.timestamp;
535
+ return m;
536
+ });
537
+ void crossValidateHermesExport(adapterSessionId, messages.length).catch(() => {
538
+ });
539
+ return { meta, messages };
540
+ }
541
+ function hermesRowToCandidate(row) {
542
+ const startedAtIso = row.started_at !== void 0 ? new Date(row.started_at * 1e3).toISOString() : void 0;
543
+ const lastActivitySec = row.ended_at ?? row.started_at;
544
+ const lastActivityIso = lastActivitySec !== void 0 ? new Date(lastActivitySec * 1e3).toISOString() : void 0;
545
+ return {
546
+ conversationId: row.id,
547
+ ...startedAtIso ? { startedAt: startedAtIso } : {},
548
+ ...lastActivityIso ? { lastActivityAt: lastActivityIso } : {},
549
+ ...row.message_count !== void 0 ? { messageCount: row.message_count } : {},
550
+ ...row.title ? { preview: row.title } : {},
551
+ ...row.source ? { lastWriter: row.source } : {}
552
+ };
553
+ }
554
+ async function discoverHermesSessions(cwd, since, expectedId) {
555
+ const dbPath = join(homedir(), ".hermes", "state.db");
556
+ let db;
557
+ try {
558
+ db = await openHermesDb(dbPath);
559
+ } catch (err) {
560
+ const msg = err instanceof Error ? err.message : String(err);
561
+ if (msg.includes("state.db not found")) return [];
562
+ throw err;
563
+ }
564
+ try {
565
+ if (expectedId) {
566
+ const row = withRetryOnBusy(
567
+ () => db.prepare("SELECT * FROM sessions WHERE id = ?").get(expectedId)
568
+ );
569
+ return row ? [hermesRowToCandidate(row)] : [];
570
+ }
571
+ const rows = withRetryOnBusy(
572
+ () => db.prepare("SELECT * FROM sessions WHERE cwd = ?").all(cwd)
573
+ );
574
+ const sinceMs = since ? Date.parse(since) : NaN;
575
+ return rows.filter((row) => {
576
+ if (!Number.isFinite(sinceMs)) return true;
577
+ const lastActivitySec = row.ended_at ?? row.started_at;
578
+ if (lastActivitySec === void 0) return true;
579
+ return lastActivitySec * 1e3 >= sinceMs - 1e3;
580
+ }).map(hermesRowToCandidate);
581
+ } finally {
582
+ db.close();
583
+ }
584
+ }
585
+ async function defaultHermesRunner(adapterSessionId) {
586
+ const { spawn: spawn5 } = await import('child_process');
587
+ return new Promise((resolve16, reject) => {
588
+ const chunks = [];
589
+ const errChunks = [];
590
+ const proc = spawn5(
591
+ "hermes",
592
+ ["sessions", "export", "--session-id", adapterSessionId, "-"],
593
+ { stdio: ["ignore", "pipe", "pipe"] }
594
+ );
595
+ proc.stdout.on("data", (chunk) => chunks.push(chunk));
596
+ proc.stderr.on("data", (chunk) => errChunks.push(chunk));
597
+ proc.on("error", reject);
598
+ proc.on("close", (code) => {
599
+ if (code !== 0) {
600
+ reject(
601
+ new Error(
602
+ `hermes exited with code ${code}: ${Buffer.concat(errChunks).toString()}`
603
+ )
604
+ );
605
+ } else {
606
+ resolve16(Buffer.concat(chunks).toString("utf8"));
607
+ }
608
+ });
609
+ });
610
+ }
611
+ async function crossValidateHermesExport(adapterSessionId, sqliteCount, _runner = defaultHermesRunner) {
612
+ let stdout;
613
+ try {
614
+ stdout = await _runner(adapterSessionId);
615
+ } catch (err) {
616
+ const msg = String(err);
617
+ if (err.code === "ENOENT" || msg.includes("ENOENT") || msg.includes("not found")) {
618
+ return { sqliteCount, binaryCount: -1, matched: true, binaryUnavailable: true };
619
+ }
620
+ return null;
621
+ }
622
+ const binaryCount = stdout.split("\n").filter((l) => l.trim()).filter((l) => {
623
+ try {
624
+ const obj = JSON.parse(l);
625
+ return typeof obj.role === "string";
626
+ } catch {
627
+ return false;
628
+ }
629
+ }).length;
630
+ const matched = binaryCount === sqliteCount;
631
+ if (!matched) {
632
+ console.warn(
633
+ `[hermes export] cross-validation mismatch for session ${adapterSessionId}: SQLite=${sqliteCount} messages, binary=${binaryCount} messages. The state.db schema may have changed \u2014 check the SQLite reader in transcript-export.ts.`
634
+ );
635
+ }
636
+ return { sqliteCount, binaryCount, matched };
637
+ }
638
+ async function exportDaemonEventsSession(sessionId, desc) {
639
+ const filePath = sessionEventsPath(sessionId);
640
+ let stream;
641
+ try {
642
+ stream = createReadStream(filePath, { encoding: "utf8" });
643
+ await new Promise((resolve16, reject) => {
644
+ stream.once("error", reject);
645
+ stream.once("open", resolve16);
646
+ });
647
+ } catch (err) {
648
+ const code = err.code;
649
+ if (code === "ENOENT") {
650
+ throw new Error(
651
+ `daemon-events: no events.jsonl for session "${sessionId}" (${filePath}).
652
+ Either the session never drove an agent-cli turn, or it predates this feature.`
653
+ );
654
+ }
655
+ throw err;
656
+ }
657
+ const messages = [];
658
+ const toolNameById = /* @__PURE__ */ new Map();
659
+ let toolCallCount = 0;
660
+ let lastUsage;
661
+ let asmText = "";
662
+ let asmReasoning = "";
663
+ let asmToolCalls = [];
664
+ let asmTs;
665
+ const flushAssistant = () => {
666
+ if (!asmText.trim() && !asmReasoning.trim() && asmToolCalls.length === 0) return;
667
+ const m = { role: "assistant" };
668
+ if (asmText.trim()) m.text = asmText.trim();
669
+ if (asmReasoning.trim()) m.reasoning = asmReasoning.trim();
670
+ if (asmToolCalls.length) m.toolCalls = asmToolCalls;
671
+ if (asmTs !== void 0) m.ts = asmTs;
672
+ messages.push(m);
673
+ asmText = "";
674
+ asmReasoning = "";
675
+ asmToolCalls = [];
676
+ asmTs = void 0;
677
+ };
678
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
679
+ for await (const line of rl) {
680
+ const trimmed = line.trim();
681
+ if (!trimmed) continue;
682
+ let rec;
683
+ try {
684
+ rec = JSON.parse(trimmed);
685
+ } catch {
686
+ continue;
687
+ }
688
+ const ts = Date.parse(rec.ts);
689
+ const tsOrUndefined = Number.isNaN(ts) ? void 0 : ts;
690
+ if (rec.kind === void 0 && typeof rec.command === "string" && typeof rec.exitCode === "number") {
691
+ flushAssistant();
692
+ const toolName = "command_execute";
693
+ const argsJson = JSON.stringify({
694
+ command: rec.command,
695
+ args: Array.isArray(rec.args) ? rec.args : [],
696
+ ...rec.cwd !== void 0 ? { cwd: rec.cwd } : {}
697
+ });
698
+ messages.push({
699
+ role: "assistant",
700
+ toolCalls: [{ name: toolName, args: argsJson }],
701
+ ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
702
+ });
703
+ const header = `exit code ${rec.exitCode}` + (rec.signal ? `, signal ${rec.signal}` : "") + (typeof rec.durationMs === "number" ? `, ${rec.durationMs}ms` : "");
704
+ const sections = [header];
705
+ if (rec.stdout) sections.push(`stdout:
706
+ ${rec.stdout}`);
707
+ if (rec.stderr) sections.push(`stderr:
708
+ ${rec.stderr}`);
709
+ if (rec.truncated) sections.push("(output truncated)");
710
+ if (rec.timedOut) sections.push("(timed out)");
711
+ const text = sections.join("\n");
712
+ messages.push({
713
+ role: "tool",
714
+ text: rec.exitCode !== 0 ? `[error] ${text}` : text,
715
+ toolName,
716
+ ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
717
+ });
718
+ toolCallCount += 1;
719
+ continue;
720
+ }
721
+ switch (rec.kind) {
722
+ case "user-prompt":
723
+ flushAssistant();
724
+ messages.push({ role: "user", text: rec.text ?? "", ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {} });
725
+ break;
726
+ case "session-message": {
727
+ flushAssistant();
728
+ const m = rec.message;
729
+ if (!m) break;
730
+ const relation = m.from?.relation ?? "system";
731
+ messages.push({
732
+ role: "user",
733
+ text: m.text ?? "",
734
+ from: {
735
+ ...m.from?.sessionId ? { sessionId: m.from.sessionId } : {},
736
+ ...m.from?.label ? { label: m.from.label } : {},
737
+ relation,
738
+ kind: m.kind ?? "report",
739
+ messageId: m.id
740
+ },
741
+ ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
742
+ });
743
+ break;
744
+ }
745
+ case "system-prompt":
746
+ flushAssistant();
747
+ messages.push({ role: "system", text: rec.text ?? "", ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {} });
748
+ break;
749
+ case "text-delta":
750
+ if (asmTs === void 0) asmTs = tsOrUndefined;
751
+ asmText += rec.text ?? "";
752
+ break;
753
+ case "thought":
754
+ if (asmTs === void 0) asmTs = tsOrUndefined;
755
+ asmReasoning += rec.text ?? "";
756
+ break;
757
+ case "tool-call": {
758
+ if (asmTs === void 0) asmTs = tsOrUndefined;
759
+ const name = rec.toolName ?? "tool";
760
+ if (rec.toolCallId) toolNameById.set(rec.toolCallId, name);
761
+ const args = typeof rec.arguments === "string" ? rec.arguments : JSON.stringify(rec.arguments ?? {});
762
+ asmToolCalls.push({ name, args });
763
+ toolCallCount += 1;
764
+ break;
765
+ }
766
+ case "tool-result": {
767
+ flushAssistant();
768
+ const name = rec.toolCallId ? toolNameById.get(rec.toolCallId) : void 0;
769
+ const text = typeof rec.result === "string" ? rec.result : JSON.stringify(rec.result ?? "");
770
+ messages.push({
771
+ role: "tool",
772
+ text: rec.isError ? `[error] ${text}` : text,
773
+ ...name ? { toolName: name } : {},
774
+ ...tsOrUndefined !== void 0 ? { ts: tsOrUndefined } : {}
775
+ });
776
+ break;
777
+ }
778
+ case "agent-prompt":
779
+ flushAssistant();
780
+ messages.push({ role: "system", text: "[awaiting input] agent requested a decision" });
781
+ break;
782
+ case "plan": {
783
+ flushAssistant();
784
+ const entries = rec.entries ?? [];
785
+ const done = entries.filter((e) => e.status === "completed").length;
786
+ const list = entries.map((e) => e.content).join("; ");
787
+ messages.push({ role: "system", text: `[plan] ${done}/${entries.length} ${list}`.trim() });
788
+ break;
789
+ }
790
+ case "usage_update":
791
+ lastUsage = { size: rec.size, used: rec.used, cost: rec.cost };
792
+ break;
793
+ case "error":
794
+ flushAssistant();
795
+ messages.push({ role: "system", text: `[error] ${rec.error?.message ?? "unknown"}` });
796
+ break;
797
+ case "turn-end":
798
+ flushAssistant();
799
+ break;
800
+ case "notice":
801
+ flushAssistant();
802
+ messages.push({ role: "system", text: rec.text ?? "" });
803
+ break;
804
+ }
805
+ }
806
+ flushAssistant();
807
+ const meta = { source: "daemon-events" };
808
+ if (desc?.label) meta.title = desc.label;
809
+ else if (desc?.command) {
810
+ meta.title = desc.command.length > 120 ? `${desc.command.slice(0, 120)}\u2026` : desc.command;
811
+ }
812
+ if (desc?.model) meta.model = desc.model;
813
+ if (desc?.startedAt) meta.startedAt = desc.startedAt;
814
+ if (desc?.endedAt) meta.endedAt = desc.endedAt;
815
+ if (desc?.origin) meta.origin = desc.origin;
816
+ if (desc?.callerSessionId) meta.callerSessionId = desc.callerSessionId;
817
+ meta.messageCount = messages.length;
818
+ meta.toolCallCount = toolCallCount;
819
+ if (desc?.costUsd !== void 0) meta.costUsd = desc.costUsd;
820
+ else if (lastUsage?.cost) meta.costUsd = lastUsage.cost.amount;
821
+ if (desc?.tokensIn !== void 0 || desc?.tokensOut !== void 0) {
822
+ meta.tokens = {
823
+ ...desc?.tokensIn !== void 0 ? { input: desc.tokensIn } : {},
824
+ ...desc?.tokensOut !== void 0 ? { output: desc.tokensOut } : {}
825
+ };
826
+ }
827
+ return { meta, messages };
828
+ }
829
+ async function readFirstJsonLine(path) {
830
+ const stream = createReadStream(path, { encoding: "utf8" });
831
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
832
+ try {
833
+ for await (const line of rl) {
834
+ const t = line.trim();
835
+ if (t) return t;
836
+ }
837
+ return void 0;
838
+ } finally {
839
+ rl.close();
840
+ stream.destroy();
841
+ }
842
+ }
843
+ function codexHome() {
844
+ return process.env.CODEX_HOME ?? join(homedir(), ".codex");
845
+ }
846
+ function codexSessionsDir() {
847
+ return join(codexHome(), "sessions");
848
+ }
849
+ async function walkCodexRollouts(dir) {
850
+ let entries;
851
+ try {
852
+ entries = await promises.readdir(dir, { withFileTypes: true });
853
+ } catch {
854
+ return [];
855
+ }
856
+ const out = [];
857
+ for (const e of entries) {
858
+ const p = join(dir, e.name);
859
+ if (e.isDirectory()) {
860
+ out.push(...await walkCodexRollouts(p));
861
+ } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
862
+ out.push(p);
863
+ }
864
+ }
865
+ return out;
866
+ }
867
+ function codexBlocksText(content) {
868
+ if (typeof content === "string") return content;
869
+ if (!Array.isArray(content)) return "";
870
+ let acc = "";
871
+ for (const c of content) {
872
+ if (c && typeof c === "object") {
873
+ const b = c;
874
+ if (typeof b.text === "string" && (b.type === "input_text" || b.type === "output_text" || b.type === "text")) {
875
+ acc += b.text;
876
+ }
877
+ }
878
+ }
879
+ return acc;
880
+ }
881
+ async function findCodexRolloutFile(conversationId) {
882
+ const files = await walkCodexRollouts(codexSessionsDir());
883
+ const byName = files.find((f) => f.endsWith(`-${conversationId}.jsonl`));
884
+ if (byName) return byName;
885
+ for (const f of files) {
886
+ const first = await readFirstJsonLine(f);
887
+ if (!first) continue;
888
+ let meta;
889
+ try {
890
+ meta = JSON.parse(first);
891
+ } catch {
892
+ continue;
893
+ }
894
+ const p = meta.payload;
895
+ if (p && (p.id === conversationId || p.session_id === conversationId)) return f;
896
+ }
897
+ return void 0;
898
+ }
899
+ async function exportCodexSession(conversationId) {
900
+ const file = await findCodexRolloutFile(conversationId);
901
+ if (!file) {
902
+ throw new Error(
903
+ `codex: no rollout file for conversation "${conversationId}" under ${codexSessionsDir()}.
904
+ The session may predate persistence, or CODEX_HOME points elsewhere.`
905
+ );
906
+ }
907
+ const stream = createReadStream(file, { encoding: "utf8" });
908
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
909
+ const messages = [];
910
+ const toolNameByCall = /* @__PURE__ */ new Map();
911
+ let toolCallCount = 0;
912
+ let startedAt;
913
+ let model;
914
+ for await (const line of rl) {
915
+ const trimmed = line.trim();
916
+ if (!trimmed) continue;
917
+ let entry;
918
+ try {
919
+ entry = JSON.parse(trimmed);
920
+ } catch {
921
+ continue;
922
+ }
923
+ const p = entry.payload ?? {};
924
+ if (entry.type === "session_meta") {
925
+ startedAt = p.timestamp ?? entry.timestamp;
926
+ if (typeof p.model === "string") model = p.model;
927
+ continue;
928
+ }
929
+ if (entry.type === "turn_context") {
930
+ if (!model && typeof p.model === "string") model = p.model;
931
+ continue;
932
+ }
933
+ if (entry.type !== "response_item") continue;
934
+ switch (p.type) {
935
+ case "message": {
936
+ const text = codexBlocksText(p.content).trim();
937
+ if (!text) break;
938
+ const role = p.role === "assistant" ? "assistant" : p.role === "user" ? "user" : "system";
939
+ messages.push({ role, text });
940
+ break;
941
+ }
942
+ case "reasoning": {
943
+ const r = (p.summary ?? []).map((s) => s.text ?? "").join("\n").trim();
944
+ if (r) messages.push({ role: "assistant", reasoning: r });
945
+ break;
946
+ }
947
+ case "function_call":
948
+ case "custom_tool_call": {
949
+ const name = p.name ?? "tool";
950
+ if (typeof p.call_id === "string") toolNameByCall.set(p.call_id, name);
951
+ const raw = p.type === "custom_tool_call" ? p.input : p.arguments;
952
+ const args = typeof raw === "string" ? raw : JSON.stringify(raw ?? {});
953
+ messages.push({ role: "assistant", toolCalls: [{ name, args }] });
954
+ toolCallCount += 1;
955
+ break;
956
+ }
957
+ case "function_call_output":
958
+ case "custom_tool_call_output": {
959
+ const name = typeof p.call_id === "string" ? toolNameByCall.get(p.call_id) : void 0;
960
+ const out = codexBlocksText(p.output) || (typeof p.output === "string" ? p.output : "");
961
+ messages.push({ role: "tool", text: out, ...name ? { toolName: name } : {} });
962
+ break;
963
+ }
964
+ }
965
+ }
966
+ const meta = { source: "codex" };
967
+ if (startedAt) meta.startedAt = startedAt;
968
+ if (model) meta.model = model;
969
+ meta.messageCount = messages.length;
970
+ meta.toolCallCount = toolCallCount;
971
+ return { meta, messages };
972
+ }
973
+ async function discoverCodexSessions(cwd, since, until, expectedId) {
974
+ const files = await walkCodexRollouts(codexSessionsDir());
975
+ const sinceMs = since ? Date.parse(since) : NaN;
976
+ const untilMs = until ? Date.parse(until) : NaN;
977
+ const scored = [];
978
+ for (const f of files) {
979
+ let mtimeMs;
980
+ try {
981
+ mtimeMs = (await promises.stat(f)).mtimeMs;
982
+ } catch {
983
+ continue;
984
+ }
985
+ if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
986
+ const first = await readFirstJsonLine(f);
987
+ if (!first) continue;
988
+ let meta;
989
+ try {
990
+ meta = JSON.parse(first);
991
+ } catch {
992
+ continue;
993
+ }
994
+ if (meta.type !== "session_meta") continue;
995
+ const p = meta.payload ?? {};
996
+ const id = p.id ?? p.session_id;
997
+ if (!id) continue;
998
+ if (expectedId) {
999
+ if (id !== expectedId) continue;
1000
+ } else if (p.cwd !== cwd) {
1001
+ continue;
1002
+ }
1003
+ const startedAt = p.timestamp;
1004
+ if (Number.isFinite(untilMs) && startedAt) {
1005
+ const startedMs = Date.parse(startedAt);
1006
+ if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
1007
+ }
1008
+ scored.push({
1009
+ candidate: {
1010
+ conversationId: id,
1011
+ ...startedAt ? { startedAt } : {},
1012
+ lastActivityAt: new Date(mtimeMs).toISOString()
1013
+ },
1014
+ mtimeMs
1015
+ });
1016
+ }
1017
+ scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
1018
+ return scored.map((s) => s.candidate);
1019
+ }
1020
+ function openCodeDbPath() {
1021
+ const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
1022
+ return join(dataHome, "opencode", "opencode.db");
1023
+ }
1024
+ function openCodeModelLabel(raw) {
1025
+ if (!raw) return void 0;
1026
+ try {
1027
+ const m = JSON.parse(raw);
1028
+ const id = m.modelID ?? m.id;
1029
+ if (!id) return void 0;
1030
+ return m.providerID ? `${m.providerID}/${id}` : id;
1031
+ } catch {
1032
+ return void 0;
1033
+ }
1034
+ }
1035
+ async function exportOpenCodeSession(conversationId) {
1036
+ const dbPath = openCodeDbPath();
1037
+ const db = await openReadonlySqlite(dbPath, "opencode");
1038
+ try {
1039
+ const session = withRetryOnBusy(
1040
+ () => db.prepare("SELECT * FROM session WHERE id = ?").get(conversationId)
1041
+ );
1042
+ if (!session) {
1043
+ throw new Error(
1044
+ `opencode: session "${conversationId}" not found in ${dbPath}. Sessions are keyed by the ACP session id (ses_\u2026) recorded as adapterSessionId.`
1045
+ );
1046
+ }
1047
+ const msgRows = withRetryOnBusy(
1048
+ () => db.prepare("SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(conversationId)
1049
+ );
1050
+ const partRows = withRetryOnBusy(
1051
+ () => db.prepare("SELECT message_id, data, time_created FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(conversationId)
1052
+ );
1053
+ const partsByMsg = /* @__PURE__ */ new Map();
1054
+ for (const r of partRows) {
1055
+ const list = partsByMsg.get(r.message_id) ?? [];
1056
+ list.push(r);
1057
+ partsByMsg.set(r.message_id, list);
1058
+ }
1059
+ const messages = [];
1060
+ let toolCallCount = 0;
1061
+ for (const m of msgRows) {
1062
+ let mdata;
1063
+ try {
1064
+ mdata = JSON.parse(m.data);
1065
+ } catch {
1066
+ continue;
1067
+ }
1068
+ const role = mdata.role === "assistant" ? "assistant" : "user";
1069
+ let text = "";
1070
+ let reasoning = "";
1071
+ const toolCalls = [];
1072
+ const toolResults = [];
1073
+ for (const pr of partsByMsg.get(m.id) ?? []) {
1074
+ let part;
1075
+ try {
1076
+ part = JSON.parse(pr.data);
1077
+ } catch {
1078
+ continue;
1079
+ }
1080
+ switch (part.type) {
1081
+ case "text":
1082
+ if (part.text) text += part.text;
1083
+ break;
1084
+ case "reasoning":
1085
+ if (part.text) reasoning += part.text;
1086
+ break;
1087
+ case "tool": {
1088
+ const name = part.tool ?? "tool";
1089
+ toolCalls.push({ name, args: JSON.stringify(part.state?.input ?? {}) });
1090
+ toolCallCount += 1;
1091
+ const output = part.state?.output;
1092
+ if (output !== void 0 && output !== null && output !== "") {
1093
+ toolResults.push({ name, text: typeof output === "string" ? output : JSON.stringify(output) });
1094
+ }
1095
+ break;
1096
+ }
1097
+ }
1098
+ }
1099
+ if (text.trim() || reasoning.trim() || toolCalls.length) {
1100
+ const em = { role };
1101
+ if (text.trim()) em.text = text.trim();
1102
+ if (reasoning.trim()) em.reasoning = reasoning.trim();
1103
+ if (toolCalls.length) em.toolCalls = toolCalls;
1104
+ em.ts = m.time_created;
1105
+ messages.push(em);
1106
+ }
1107
+ for (const tr of toolResults) {
1108
+ messages.push({ role: "tool", text: tr.text, ...tr.name ? { toolName: tr.name } : {} });
1109
+ }
1110
+ }
1111
+ const meta = { source: "opencode" };
1112
+ if (session.title) meta.title = session.title;
1113
+ const model = openCodeModelLabel(session.model);
1114
+ if (model) meta.model = model;
1115
+ if (session.time_created) meta.startedAt = new Date(session.time_created).toISOString();
1116
+ if (session.time_updated) meta.endedAt = new Date(session.time_updated).toISOString();
1117
+ meta.messageCount = messages.length;
1118
+ meta.toolCallCount = toolCallCount;
1119
+ if (session.cost != null) meta.costUsd = Number(session.cost);
1120
+ const tk = {
1121
+ ...session.tokens_input != null ? { input: session.tokens_input } : {},
1122
+ ...session.tokens_output != null ? { output: session.tokens_output } : {},
1123
+ ...session.tokens_cache_read != null ? { cacheRead: session.tokens_cache_read } : {},
1124
+ ...session.tokens_cache_write != null ? { cacheWrite: session.tokens_cache_write } : {},
1125
+ ...session.tokens_reasoning != null ? { reasoning: session.tokens_reasoning } : {}
1126
+ };
1127
+ if (Object.keys(tk).length) meta.tokens = tk;
1128
+ return { meta, messages };
1129
+ } finally {
1130
+ db.close();
1131
+ }
1132
+ }
1133
+ async function discoverOpenCodeSessions(cwd, since, expectedId) {
1134
+ const dbPath = openCodeDbPath();
1135
+ let db;
1136
+ try {
1137
+ db = await openReadonlySqlite(dbPath, "opencode");
1138
+ } catch (err) {
1139
+ const msg = err instanceof Error ? err.message : String(err);
1140
+ if (msg.includes("database not found")) return [];
1141
+ throw err;
1142
+ }
1143
+ try {
1144
+ if (expectedId) {
1145
+ const row = withRetryOnBusy(
1146
+ () => db.prepare("SELECT * FROM session WHERE id = ?").get(expectedId)
1147
+ );
1148
+ return row ? [openCodeRowToCandidate(row)] : [];
1149
+ }
1150
+ const rows = withRetryOnBusy(
1151
+ () => db.prepare("SELECT * FROM session WHERE directory = ?").all(cwd)
1152
+ );
1153
+ const sinceMs = since ? Date.parse(since) : NaN;
1154
+ return rows.filter((r) => {
1155
+ if (!Number.isFinite(sinceMs)) return true;
1156
+ if (r.time_created === void 0) return false;
1157
+ return r.time_created >= sinceMs - 1e3;
1158
+ }).map(openCodeRowToCandidate);
1159
+ } finally {
1160
+ db.close();
1161
+ }
1162
+ }
1163
+ function openCodeRowToCandidate(row) {
1164
+ return {
1165
+ conversationId: row.id,
1166
+ ...row.time_created ? { startedAt: new Date(row.time_created).toISOString() } : {},
1167
+ ...row.time_updated ? { lastActivityAt: new Date(row.time_updated).toISOString() } : {},
1168
+ ...row.title ? { preview: row.title } : {}
1169
+ };
1170
+ }
1171
+ function mastracodeInprocessDbPath() {
1172
+ const home = process.env.AGENTPROTO_HOME ?? join(homedir(), ".agentproto");
1173
+ return join(home, "mastracode-inprocess", "storage.db");
1174
+ }
1175
+ function mastraThreadId(conversationId) {
1176
+ const idx = conversationId.indexOf(":");
1177
+ return idx > 0 ? conversationId.slice(idx + 1) : conversationId;
1178
+ }
1179
+ function mastraContentToMessage(role, content) {
1180
+ const toolResults = [];
1181
+ let parsed;
1182
+ try {
1183
+ parsed = JSON.parse(content);
1184
+ } catch {
1185
+ return { message: void 0, toolResults, toolCalls: 0 };
1186
+ }
1187
+ let text = "";
1188
+ let reasoning = "";
1189
+ const toolCalls = [];
1190
+ for (const raw of parsed.parts ?? []) {
1191
+ if (!raw || typeof raw !== "object") continue;
1192
+ const part = raw;
1193
+ if (part.type === "text" && part.text) text += part.text;
1194
+ else if (part.type === "reasoning" && part.reasoning) reasoning += part.reasoning;
1195
+ else if (part.type === "tool-invocation" && part.toolInvocation) {
1196
+ const inv = part.toolInvocation;
1197
+ const name = inv.toolName ?? "tool";
1198
+ toolCalls.push({ name, args: JSON.stringify(inv.args ?? {}) });
1199
+ if (inv.result !== void 0) {
1200
+ const r = inv.result;
1201
+ const rtext = Array.isArray(r?.content) ? r.content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join("") : typeof inv.result === "string" ? inv.result : JSON.stringify(inv.result);
1202
+ toolResults.push({ name, text: rtext });
1203
+ }
1204
+ }
1205
+ }
1206
+ let message;
1207
+ if (text.trim() || reasoning.trim() || toolCalls.length) {
1208
+ message = { role };
1209
+ if (text.trim()) message.text = text.trim();
1210
+ if (reasoning.trim()) message.reasoning = reasoning.trim();
1211
+ if (toolCalls.length) message.toolCalls = toolCalls;
1212
+ }
1213
+ return { message, toolResults, toolCalls: toolCalls.length };
1214
+ }
1215
+ async function exportMastracodeInprocessSession(conversationId) {
1216
+ const threadId = mastraThreadId(conversationId);
1217
+ const dbPath = mastracodeInprocessDbPath();
1218
+ const db = await openReadonlySqlite(dbPath, "mastracode-inprocess");
1219
+ try {
1220
+ const thread = withRetryOnBusy(
1221
+ () => db.prepare("SELECT * FROM mastra_threads WHERE id = ?").get(threadId)
1222
+ );
1223
+ if (!thread) {
1224
+ throw new Error(
1225
+ `mastracode-inprocess: thread "${threadId}" not found in ${dbPath}. The sessionId is "<resourceId>:<threadId>"; only the threadId is looked up.`
1226
+ );
1227
+ }
1228
+ const rows = withRetryOnBusy(
1229
+ () => db.prepare('SELECT role, content, createdAt FROM mastra_messages WHERE thread_id = ? ORDER BY "createdAt" ASC').all(threadId)
1230
+ );
1231
+ const messages = [];
1232
+ let toolCallCount = 0;
1233
+ for (const r of rows) {
1234
+ const role = r.role === "assistant" ? "assistant" : "user";
1235
+ const { message, toolResults, toolCalls } = mastraContentToMessage(role, r.content);
1236
+ if (message) messages.push(message);
1237
+ toolCallCount += toolCalls;
1238
+ for (const tr of toolResults) {
1239
+ messages.push({ role: "tool", text: tr.text, ...tr.name ? { toolName: tr.name } : {} });
1240
+ }
1241
+ }
1242
+ const meta = { source: "mastracode-inprocess" };
1243
+ if (thread.title) meta.title = thread.title;
1244
+ if (thread.createdAt) meta.startedAt = thread.createdAt;
1245
+ if (thread.updatedAt) meta.endedAt = thread.updatedAt;
1246
+ meta.messageCount = messages.length;
1247
+ meta.toolCallCount = toolCallCount;
1248
+ return { meta, messages };
1249
+ } finally {
1250
+ db.close();
1251
+ }
1252
+ }
1253
+ async function discoverMastracodeInprocessSessions(_cwd, since, expectedId) {
1254
+ const dbPath = mastracodeInprocessDbPath();
1255
+ let db;
1256
+ try {
1257
+ db = await openReadonlySqlite(dbPath, "mastracode-inprocess");
1258
+ } catch (err) {
1259
+ const msg = err instanceof Error ? err.message : String(err);
1260
+ if (msg.includes("database not found")) return [];
1261
+ throw err;
1262
+ }
1263
+ try {
1264
+ if (!expectedId) return [];
1265
+ const threadId = mastraThreadId(expectedId);
1266
+ const thread = withRetryOnBusy(
1267
+ () => db.prepare("SELECT * FROM mastra_threads WHERE id = ?").get(threadId)
1268
+ );
1269
+ if (!thread) return [];
1270
+ const sinceMs = since ? Date.parse(since) : NaN;
1271
+ if (Number.isFinite(sinceMs) && thread.updatedAt) {
1272
+ const upd = Date.parse(thread.updatedAt);
1273
+ if (Number.isFinite(upd) && upd < sinceMs - 1e3) return [];
1274
+ }
1275
+ return [
1276
+ {
1277
+ // Echo back the full composite id so callers/read() round-trip it.
1278
+ conversationId: expectedId,
1279
+ ...thread.createdAt ? { startedAt: thread.createdAt } : {},
1280
+ ...thread.updatedAt ? { lastActivityAt: thread.updatedAt } : {},
1281
+ ...thread.title ? { preview: thread.title } : {}
1282
+ }
1283
+ ];
1284
+ } finally {
1285
+ db.close();
1286
+ }
1287
+ }
1288
+ function piSessionsDir() {
1289
+ return join(homedir(), ".pi", "agent", "sessions");
1290
+ }
1291
+ function piBlocksText(content) {
1292
+ if (typeof content === "string") return content;
1293
+ if (!Array.isArray(content)) return "";
1294
+ let acc = "";
1295
+ for (const c of content) {
1296
+ if (c && typeof c === "object") {
1297
+ const b = c;
1298
+ if (b.type === "text" && typeof b.text === "string") acc += b.text;
1299
+ }
1300
+ }
1301
+ return acc;
1302
+ }
1303
+ async function walkPiSessionFiles(dir) {
1304
+ let entries;
1305
+ try {
1306
+ entries = await promises.readdir(dir, { withFileTypes: true });
1307
+ } catch {
1308
+ return [];
1309
+ }
1310
+ const out = [];
1311
+ for (const e of entries) {
1312
+ const p = join(dir, e.name);
1313
+ if (e.isDirectory()) out.push(...await walkPiSessionFiles(p));
1314
+ else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(p);
1315
+ }
1316
+ return out;
1317
+ }
1318
+ async function findPiSessionFile(conversationId) {
1319
+ const files = await walkPiSessionFiles(piSessionsDir());
1320
+ const byName = files.find((f) => f.endsWith(`_${conversationId}.jsonl`));
1321
+ if (byName) return byName;
1322
+ for (const f of files) {
1323
+ const first = await readFirstJsonLine(f);
1324
+ if (!first) continue;
1325
+ try {
1326
+ const meta = JSON.parse(first);
1327
+ if (meta.type === "session" && meta.id === conversationId) return f;
1328
+ } catch {
1329
+ }
1330
+ }
1331
+ return void 0;
1332
+ }
1333
+ async function exportPiSession(conversationId) {
1334
+ const file = await findPiSessionFile(conversationId);
1335
+ if (!file) {
1336
+ throw new Error(
1337
+ `pi: no session file for conversation "${conversationId}" under ${piSessionsDir()}.`
1338
+ );
1339
+ }
1340
+ const stream = createReadStream(file, { encoding: "utf8" });
1341
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
1342
+ const messages = [];
1343
+ let toolCallCount = 0;
1344
+ let startedAt;
1345
+ let model;
1346
+ for await (const line of rl) {
1347
+ const trimmed = line.trim();
1348
+ if (!trimmed) continue;
1349
+ let entry;
1350
+ try {
1351
+ entry = JSON.parse(trimmed);
1352
+ } catch {
1353
+ continue;
1354
+ }
1355
+ if (entry.type === "session") {
1356
+ startedAt = entry.timestamp;
1357
+ continue;
1358
+ }
1359
+ if (entry.type === "model_change") {
1360
+ model = entry.provider ? `${entry.provider}/${entry.modelId ?? ""}` : entry.modelId;
1361
+ continue;
1362
+ }
1363
+ if (entry.type !== "message" || !entry.message) continue;
1364
+ const m = entry.message;
1365
+ const content = m.content;
1366
+ if (m.role === "toolResult") {
1367
+ const text2 = piBlocksText(content);
1368
+ messages.push({
1369
+ role: "tool",
1370
+ text: m.isError ? `[error] ${text2}` : text2,
1371
+ ...m.toolName ? { toolName: m.toolName } : {}
1372
+ });
1373
+ continue;
1374
+ }
1375
+ const role = m.role === "assistant" ? "assistant" : "user";
1376
+ let text = "";
1377
+ let reasoning = "";
1378
+ const toolCalls = [];
1379
+ if (typeof content === "string") {
1380
+ text = content;
1381
+ } else if (Array.isArray(content)) {
1382
+ for (const raw of content) {
1383
+ if (!raw || typeof raw !== "object") continue;
1384
+ const part = raw;
1385
+ if (part.type === "text" && part.text) text += part.text;
1386
+ else if (part.type === "thinking" && part.thinking) reasoning += part.thinking;
1387
+ else if (part.type === "toolCall") {
1388
+ toolCalls.push({ name: part.name ?? "tool", args: JSON.stringify(part.arguments ?? {}) });
1389
+ toolCallCount += 1;
1390
+ }
1391
+ }
1392
+ }
1393
+ if (text.trim() || reasoning.trim() || toolCalls.length) {
1394
+ const em = { role };
1395
+ if (text.trim()) em.text = text.trim();
1396
+ if (reasoning.trim()) em.reasoning = reasoning.trim();
1397
+ if (toolCalls.length) em.toolCalls = toolCalls;
1398
+ messages.push(em);
1399
+ }
1400
+ }
1401
+ const meta = { source: "pi" };
1402
+ if (startedAt) meta.startedAt = startedAt;
1403
+ if (model) meta.model = model;
1404
+ meta.messageCount = messages.length;
1405
+ meta.toolCallCount = toolCallCount;
1406
+ return { meta, messages };
1407
+ }
1408
+ async function discoverPiSessions(cwd, since, until, expectedId) {
1409
+ const files = await walkPiSessionFiles(piSessionsDir());
1410
+ const sinceMs = since ? Date.parse(since) : NaN;
1411
+ const untilMs = until ? Date.parse(until) : NaN;
1412
+ const scored = [];
1413
+ for (const f of files) {
1414
+ let mtimeMs;
1415
+ try {
1416
+ mtimeMs = (await promises.stat(f)).mtimeMs;
1417
+ } catch {
1418
+ continue;
1419
+ }
1420
+ if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
1421
+ const first = await readFirstJsonLine(f);
1422
+ if (!first) continue;
1423
+ let meta;
1424
+ try {
1425
+ meta = JSON.parse(first);
1426
+ } catch {
1427
+ continue;
1428
+ }
1429
+ if (meta.type !== "session" || !meta.id) continue;
1430
+ if (expectedId) {
1431
+ if (meta.id !== expectedId) continue;
1432
+ } else if (meta.cwd !== cwd) {
1433
+ continue;
1434
+ }
1435
+ const startedAt = meta.timestamp;
1436
+ if (Number.isFinite(untilMs) && startedAt) {
1437
+ const s = Date.parse(startedAt);
1438
+ if (Number.isFinite(s) && s > untilMs) continue;
1439
+ }
1440
+ scored.push({
1441
+ candidate: {
1442
+ conversationId: meta.id,
1443
+ ...startedAt ? { startedAt } : {},
1444
+ lastActivityAt: new Date(mtimeMs).toISOString()
1445
+ },
1446
+ mtimeMs
1447
+ });
1448
+ }
1449
+ scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
1450
+ return scored.map((s) => s.candidate);
1451
+ }
1452
+ async function exportAgentSession(input) {
1453
+ const { sessionId, registry, format = "markdown", maxToolChars = 1200 } = input;
1454
+ const source = input.source ?? "auto";
1455
+ let adapterSlug = input.adapter;
1456
+ let cwd = input.cwd;
1457
+ let adapterSessionId = sessionId;
1458
+ let configDir;
1459
+ const err = (msg) => ({
1460
+ sessionId,
1461
+ adapter: adapterSlug ?? "unknown",
1462
+ format,
1463
+ meta: {},
1464
+ content: `Error: ${msg}`
1465
+ });
1466
+ const desc = registry.findByIdOrName(sessionId);
1467
+ const daemonSessionId = desc?.id ?? sessionId;
1468
+ if (desc) {
1469
+ adapterSlug = adapterSlug ?? desc.adapterSlug;
1470
+ cwd = cwd ?? desc.cwd;
1471
+ configDir = desc.adapterConfigDir;
1472
+ if (desc.adapterSessionId) adapterSessionId = desc.adapterSessionId;
1473
+ }
1474
+ const tryNative = async () => {
1475
+ if (!adapterSlug) {
1476
+ throw new Error(
1477
+ `session "${sessionId}" not found in registry and no adapter override supplied.
1478
+ Pass adapter explicitly or use a known session id (sess_xxx or name).`
1479
+ );
1480
+ }
1481
+ const exporter = EXPORT_STRATEGIES[adapterSlug];
1482
+ if (!exporter) {
1483
+ const supported = Object.keys(EXPORT_STRATEGIES).join(", ");
1484
+ throw new Error(
1485
+ `no exporter for adapter "${adapterSlug}". Supported: ${supported}.
1486
+ Only sessions spawned via claude-code or hermes can be exported.`
1487
+ );
1488
+ }
1489
+ return exporter.exportSession(adapterSessionId, cwd, configDir);
1490
+ };
1491
+ const tryDaemon = () => exportDaemonEventsSession(daemonSessionId, desc);
1492
+ let session;
1493
+ try {
1494
+ if (source === "native") {
1495
+ session = await tryNative();
1496
+ } else if (source === "daemon") {
1497
+ session = await tryDaemon();
1498
+ } else {
1499
+ try {
1500
+ session = await tryNative();
1501
+ } catch (nativeErr) {
1502
+ try {
1503
+ session = await tryDaemon();
1504
+ } catch (daemonErr) {
1505
+ const nativeMsg = nativeErr instanceof Error ? nativeErr.message : String(nativeErr);
1506
+ const daemonMsg = daemonErr instanceof Error ? daemonErr.message : String(daemonErr);
1507
+ return err(`${nativeMsg}
1508
+ (daemon-events fallback also failed: ${daemonMsg})`);
1509
+ }
1510
+ }
1511
+ }
1512
+ } catch (e) {
1513
+ return err(e instanceof Error ? e.message : String(e));
1514
+ }
1515
+ const content = format === "json" ? renderJson(session) : renderMarkdown(session, { maxToolChars });
1516
+ return {
1517
+ sessionId,
1518
+ adapter: adapterSlug ?? (session.meta.source === "daemon-events" ? "daemon" : "unknown"),
1519
+ format,
1520
+ meta: session.meta,
1521
+ content
1522
+ };
1523
+ }
1524
+ var ROLE_ICON, IGNORED_CLAUDE_TYPES, NODE_SQLITE, EXPORT_STRATEGIES;
1525
+ var init_transcript_export = __esm({
1526
+ "src/transcript-export.ts"() {
1527
+ init_tool_presenter();
1528
+ init_transcript_writer();
1529
+ init_conversation_store();
1530
+ ROLE_ICON = {
1531
+ user: "\u{1F9D1} User",
1532
+ assistant: "\u{1F916} Assistant",
1533
+ tool: "\u{1F527} Tool",
1534
+ system: "\u2699\uFE0F System"
1535
+ };
1536
+ IGNORED_CLAUDE_TYPES = /* @__PURE__ */ new Set([
1537
+ "queue-operation",
1538
+ "attachment",
1539
+ "file-history-snapshot",
1540
+ "ai-title",
1541
+ "last-prompt"
1542
+ ]);
1543
+ NODE_SQLITE = "node:sqlite";
1544
+ EXPORT_STRATEGIES = {
1545
+ "claude-code": {
1546
+ exportSession: exportClaudeCodeSession
1547
+ },
1548
+ hermes: {
1549
+ exportSession: (id) => exportHermesSession(id)
1550
+ },
1551
+ codex: {
1552
+ exportSession: (id) => exportCodexSession(id)
1553
+ },
1554
+ opencode: {
1555
+ exportSession: (id) => exportOpenCodeSession(id)
1556
+ },
1557
+ "mastracode-inprocess": {
1558
+ exportSession: (id) => exportMastracodeInprocessSession(id)
1559
+ },
1560
+ pi: {
1561
+ exportSession: (id) => exportPiSession(id)
1562
+ }
1563
+ };
1564
+ }
1565
+ });
1566
+ function claudeProjectSlug(cwd) {
1567
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
1568
+ }
1569
+ function claudeCodeProjectDir(cwd, configDir) {
1570
+ const base = configDir ?? resolve(homedir(), ".claude");
1571
+ return resolve(base, "projects", claudeProjectSlug(cwd));
1572
+ }
1573
+ function extractFirstText(content) {
1574
+ if (typeof content === "string") {
1575
+ const t = content.trim();
1576
+ return t || void 0;
1577
+ }
1578
+ if (Array.isArray(content)) {
1579
+ for (const block of content) {
1580
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
1581
+ const t = block.text.trim();
1582
+ if (t) return t;
1583
+ }
1584
+ }
1585
+ }
1586
+ return void 0;
1587
+ }
1588
+ function isClaudeControlText(text) {
1589
+ return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(text);
1590
+ }
1591
+ async function scanClaudeJsonl(filePath) {
1592
+ const stream = createReadStream(filePath, { encoding: "utf8" });
1593
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
1594
+ let startedAt;
1595
+ let lastActivityAt;
1596
+ let messageCount = 0;
1597
+ let preview;
1598
+ let lastWriter;
1599
+ for await (const line of rl) {
1600
+ const trimmed = line.trim();
1601
+ if (!trimmed) continue;
1602
+ let entry;
1603
+ try {
1604
+ entry = JSON.parse(trimmed);
1605
+ } catch {
1606
+ continue;
1607
+ }
1608
+ if (typeof entry.timestamp === "string") {
1609
+ if (!startedAt) startedAt = entry.timestamp;
1610
+ lastActivityAt = entry.timestamp;
1611
+ }
1612
+ if (typeof entry.entrypoint === "string") {
1613
+ lastWriter = entry.entrypoint;
1614
+ }
1615
+ if (entry.type === "user" || entry.type === "assistant") {
1616
+ messageCount += 1;
1617
+ if (preview === void 0 && entry.type === "user") {
1618
+ const text = extractFirstText(entry.message?.content);
1619
+ if (text !== void 0 && !isClaudeControlText(text)) {
1620
+ preview = text.length > 120 ? text.slice(0, 120) : text;
1621
+ }
1622
+ }
1623
+ }
1624
+ }
1625
+ return { startedAt, lastActivityAt, messageCount, preview, lastWriter };
1626
+ }
1627
+ async function buildClaudeCandidate(filePath, conversationId) {
1628
+ const scanned = await scanClaudeJsonl(filePath);
1629
+ return { conversationId, ...scanned };
1630
+ }
1631
+ function claudeEntrypointFor(mode) {
1632
+ return mode === "native" ? "cli" : "sdk-ts";
1633
+ }
1634
+ async function discoverClaudeCode(input) {
1635
+ const { cwd, since, until, attachmentMode, configDir, expectedId } = input;
1636
+ const dir = claudeCodeProjectDir(cwd, configDir);
1637
+ if (expectedId) {
1638
+ const filePath = join(dir, `${expectedId}.jsonl`);
1639
+ try {
1640
+ await promises.stat(filePath);
1641
+ } catch {
1642
+ return [];
1643
+ }
1644
+ return [await buildClaudeCandidate(filePath, expectedId)];
1645
+ }
1646
+ let entries;
1647
+ try {
1648
+ entries = await promises.readdir(dir);
1649
+ } catch {
1650
+ return [];
1651
+ }
1652
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
1653
+ if (jsonlFiles.length === 0) return [];
1654
+ const sinceMs = since ? Date.parse(since) : NaN;
1655
+ const untilMs = until ? Date.parse(until) : NaN;
1656
+ const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : void 0;
1657
+ const scored = [];
1658
+ for (const f of jsonlFiles) {
1659
+ const filePath = join(dir, f);
1660
+ let mtimeMs;
1661
+ try {
1662
+ mtimeMs = (await promises.stat(filePath)).mtimeMs;
1663
+ } catch {
1664
+ continue;
1665
+ }
1666
+ if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
1667
+ const conversationId = f.replace(/\.jsonl$/, "");
1668
+ const candidate = await buildClaudeCandidate(filePath, conversationId);
1669
+ if (Number.isFinite(untilMs) && candidate.startedAt !== void 0) {
1670
+ const startedMs = Date.parse(candidate.startedAt);
1671
+ if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
1672
+ }
1673
+ if (wantEntrypoint !== void 0 && candidate.lastWriter !== void 0 && candidate.lastWriter !== wantEntrypoint) {
1674
+ continue;
1675
+ }
1676
+ scored.push({ candidate, mtimeMs });
1677
+ }
1678
+ scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
1679
+ return scored.map((s) => s.candidate);
1680
+ }
1681
+ async function readClaudeCode(conversationId, cwd, configDir) {
1682
+ const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1683
+ return exportClaudeCodeSession2(conversationId, cwd, configDir);
1684
+ }
1685
+ async function discoverHermes(input) {
1686
+ const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1687
+ return discoverHermesSessions2(input.cwd, input.since, input.expectedId);
1688
+ }
1689
+ async function readHermes(conversationId) {
1690
+ const { exportHermesSession: exportHermesSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1691
+ return exportHermesSession2(conversationId);
1692
+ }
1693
+ async function discoverCodex(input) {
1694
+ const { discoverCodexSessions: discoverCodexSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1695
+ return discoverCodexSessions2(input.cwd, input.since, input.until, input.expectedId);
1696
+ }
1697
+ async function readCodex(conversationId) {
1698
+ const { exportCodexSession: exportCodexSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1699
+ return exportCodexSession2(conversationId);
1700
+ }
1701
+ async function discoverOpenCode(input) {
1702
+ const { discoverOpenCodeSessions: discoverOpenCodeSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1703
+ return discoverOpenCodeSessions2(input.cwd, input.since, input.expectedId);
1704
+ }
1705
+ async function readOpenCode(conversationId) {
1706
+ const { exportOpenCodeSession: exportOpenCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1707
+ return exportOpenCodeSession2(conversationId);
1708
+ }
1709
+ async function discoverMastracodeInprocess(input) {
1710
+ const { discoverMastracodeInprocessSessions: discoverMastracodeInprocessSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1711
+ return discoverMastracodeInprocessSessions2(input.cwd, input.since, input.expectedId);
1712
+ }
1713
+ async function readMastracodeInprocess(conversationId) {
1714
+ const { exportMastracodeInprocessSession: exportMastracodeInprocessSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1715
+ return exportMastracodeInprocessSession2(conversationId);
1716
+ }
1717
+ async function discoverPi(input) {
1718
+ const { discoverPiSessions: discoverPiSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1719
+ return discoverPiSessions2(input.cwd, input.since, input.until, input.expectedId);
1720
+ }
1721
+ async function readPi(conversationId) {
1722
+ const { exportPiSession: exportPiSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
1723
+ return exportPiSession2(conversationId);
1724
+ }
1725
+ var CONVERSATION_STORES;
1726
+ var init_conversation_store = __esm({
1727
+ "src/conversation-store.ts"() {
1728
+ CONVERSATION_STORES = {
1729
+ "claude-code": {
1730
+ storeAs: "claudeResumeId",
1731
+ // Printed by claude on graceful exit when session persistence is on
1732
+ // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
1733
+ outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
1734
+ attachArgv: (conversationId) => ["claude", "--resume", conversationId],
1735
+ transcriptPath: (cwd, conversationId, configDir) => join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),
1736
+ configDirEnvVar: "CLAUDE_CONFIG_DIR",
1737
+ discover: discoverClaudeCode,
1738
+ read: readClaudeCode
1739
+ },
1740
+ hermes: {
1741
+ storeAs: "hermesResumeId",
1742
+ // `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);
1743
+ // `--resume SESSION --tui` is the native TUI resume path — same binary,
1744
+ // different flags, unlike claude-code where the two arms are different
1745
+ // binaries. Verified via `hermes --help`: `--resume SESSION, -r` =
1746
+ // "Resume a previous session by ID or title", `--tui` = the real TUI.
1747
+ attachArgv: (conversationId) => ["hermes", "--resume", conversationId, "--tui"],
1748
+ discover: discoverHermes,
1749
+ read: readHermes
1750
+ },
1751
+ // codex-acp writes no resume flag we sniff, and there's no native PTY
1752
+ // reattach argv to declare — export/read only, so no outputHint/attachArgv.
1753
+ codex: {
1754
+ storeAs: "codexResumeId",
1755
+ discover: discoverCodex,
1756
+ read: readCodex
1757
+ },
1758
+ opencode: {
1759
+ storeAs: "openCodeResumeId",
1760
+ // Match the adapter's portable npx installation: a direct `opencode`
1761
+ // binary is not guaranteed to be on the daemon's PATH on every host.
1762
+ attachArgv: (conversationId) => ["npx", "-y", "opencode-ai", "-s", conversationId],
1763
+ discover: discoverOpenCode,
1764
+ read: readOpenCode
1765
+ },
1766
+ "mastracode-inprocess": {
1767
+ storeAs: "mastracodeInprocessResumeId",
1768
+ discover: discoverMastracodeInprocess,
1769
+ read: readMastracodeInprocess
1770
+ },
1771
+ pi: {
1772
+ storeAs: "piResumeId",
1773
+ discover: discoverPi,
1774
+ read: readPi
1775
+ }
1776
+ };
1777
+ new Set(
1778
+ Object.values(CONVERSATION_STORES).map((s) => s.storeAs)
1779
+ );
1780
+ }
1781
+ });
1782
+ var init_resume_strategies = __esm({
1783
+ "src/resume-strategies.ts"() {
1784
+ init_conversation_store();
1785
+ Object.fromEntries(
1786
+ Object.entries(CONVERSATION_STORES).filter(([, store]) => typeof store.attachArgv === "function").map(([slug, store]) => {
1787
+ const s = store;
1788
+ return [
1789
+ slug,
1790
+ {
1791
+ outputHint: store.outputHint,
1792
+ storeAs: store.storeAs,
1793
+ ...store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {},
1794
+ ...store.transcriptPath ? { transcriptPath: store.transcriptPath } : {},
1795
+ fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
1796
+ const candidates = await s.discover({
1797
+ cwd,
1798
+ since: prevStartedAt,
1799
+ expectedId,
1800
+ configDir
1801
+ });
1802
+ return candidates[0]?.conversationId ?? null;
1803
+ },
1804
+ spawnArgs: s.attachArgv
1805
+ }
1806
+ ];
1807
+ })
1808
+ );
1809
+ }
1810
+ });
1811
+ var init_command_log = __esm({
1812
+ "src/command-log.ts"() {
1813
+ init_transcript_writer();
1814
+ }
1815
+ });
1816
+ var init_sandbox_ledger = __esm({
1817
+ "src/sandbox-ledger.ts"() {
1818
+ }
1819
+ });
1820
+ var init_tool_call_log = __esm({
1821
+ "src/tool-call-log.ts"() {
1822
+ init_transcript_writer();
1823
+ }
1824
+ });
1825
+ var init_usage_snapshot_log = __esm({
1826
+ "src/usage-snapshot-log.ts"() {
1827
+ init_transcript_writer();
1828
+ }
1829
+ });
1830
+ var init_hooks_config = __esm({
1831
+ "src/hooks-config.ts"() {
1832
+ }
1833
+ });
1834
+
1835
+ // src/providers-store.ts
1836
+ var providers_store_exports = {};
1837
+ var init_providers_store = __esm({
1838
+ "src/providers-store.ts"() {
1839
+ __reExport(providers_store_exports, providers_store_star);
1840
+ }
1841
+ });
1842
+ var init_spawn_defaults = __esm({
1843
+ "src/spawn-defaults.ts"() {
1844
+ init_providers_store();
1845
+ }
1846
+ });
1847
+ var init_claude_code_oauth_source = __esm({
1848
+ "src/claude-code-oauth-source.ts"() {
1849
+ init_spawn_defaults();
1850
+ }
1851
+ });
1852
+ var remainingQuotaSchema, storedProfileQuotaSchema;
1853
+ var init_remaining_quota_store = __esm({
1854
+ "src/remaining-quota-store.ts"() {
1855
+ remainingQuotaSchema = z.object({
1856
+ window: z.string(),
1857
+ remaining: z.number(),
1858
+ resetsAt: z.string(),
1859
+ basis: z.literal("provider")
1860
+ });
1861
+ storedProfileQuotaSchema = z.object({
1862
+ profileRef: z.string(),
1863
+ windows: z.object({
1864
+ "5h": remainingQuotaSchema.optional(),
1865
+ "7d": remainingQuotaSchema.optional()
1866
+ }),
1867
+ fetchedAt: z.string()
1868
+ });
1869
+ z.object({
1870
+ version: z.literal(1),
1871
+ profiles: z.record(z.string(), storedProfileQuotaSchema)
1872
+ });
1873
+ }
1874
+ });
1875
+
1876
+ // src/remaining-quota.ts
1877
+ var init_remaining_quota = __esm({
1878
+ "src/remaining-quota.ts"() {
1879
+ init_claude_code_oauth_source();
1880
+ init_remaining_quota_store();
1881
+ }
1882
+ });
1883
+ var init_account_credits = __esm({
1884
+ "src/account-credits.ts"() {
1885
+ z.object({
1886
+ data: z.object({ limit_remaining: z.number().nullable().optional() }).passthrough()
1887
+ }).passthrough();
1888
+ z.object({
1889
+ data: z.object({ available_balance: z.number() }).passthrough()
1890
+ }).passthrough();
1891
+ }
1892
+ });
1893
+ var init_usage_rollup_service = __esm({
1894
+ "src/usage-rollup-service.ts"() {
1895
+ init_remaining_quota();
1896
+ init_account_credits();
1897
+ }
1898
+ });
1899
+
1900
+ // src/usage-rollup.ts
1901
+ var init_usage_rollup = __esm({
1902
+ "src/usage-rollup.ts"() {
1903
+ }
1904
+ });
1905
+
1906
+ // src/cost-budget.ts
1907
+ var init_cost_budget = __esm({
1908
+ "src/cost-budget.ts"() {
1909
+ }
1910
+ });
1911
+ var init_pr_provenance_stamp = __esm({
1912
+ "src/pr-provenance-stamp.ts"() {
1913
+ init_pr_provenance();
1914
+ }
1915
+ });
1916
+ var init_command_allowlist = __esm({
1917
+ "src/command-allowlist.ts"() {
1918
+ }
1919
+ });
1920
+ var init_command_tools = __esm({
1921
+ "src/command-tools.ts"() {
1922
+ init_sessions();
1923
+ init_pr_provenance_stamp();
1924
+ init_command_allowlist();
1925
+ init_command_allowlist();
1926
+ }
1927
+ });
1928
+ var init_workspaces_config = __esm({
1929
+ "src/workspaces-config.ts"() {
1930
+ }
1931
+ });
1932
+ var init_worktree_identity = __esm({
1933
+ "src/worktree-identity.ts"() {
1934
+ z.object({ worktreeId: z.string() });
1935
+ }
1936
+ });
1937
+ var init_catalog_models = __esm({
1938
+ "src/catalog-models.ts"() {
1939
+ init_spawn_defaults();
1940
+ }
1941
+ });
1942
+ var init_model_wire = __esm({
1943
+ "src/model-wire.ts"() {
1944
+ init_catalog_models();
1945
+ }
1946
+ });
1947
+
1948
+ // src/launch-config.ts
1949
+ var init_launch_config = __esm({
1950
+ "src/launch-config.ts"() {
1951
+ init_model_wire();
1952
+ init_spawn_defaults();
1953
+ }
1954
+ });
1955
+
1956
+ // src/context-continuity.ts
1957
+ var init_context_continuity = __esm({
1958
+ "src/context-continuity.ts"() {
1959
+ }
1960
+ });
1961
+
1962
+ // src/canonical-posture.ts
1963
+ function normalizeModeId(id) {
1964
+ return id.toLowerCase().replace(/[^a-z0-9]/g, "");
1965
+ }
1966
+ var POSTURE_NATIVE_ALIASES, CANONICAL_POSTURES;
1967
+ var init_canonical_posture = __esm({
1968
+ "src/canonical-posture.ts"() {
1969
+ POSTURE_NATIVE_ALIASES = {
1970
+ default: ["default", "build", "normal", "standard"],
1971
+ plan: ["plan", "planning", "plan-mode"],
1972
+ "accept-edits": ["accept-edits", "auto-accept", "auto-edit"],
1973
+ bypass: ["bypass", "bypass-permissions", "full-access", "yolo", "dangerously-skip-permissions"],
1974
+ "read-only": ["read-only", "chat", "ask"]
1975
+ };
1976
+ CANONICAL_POSTURES = Object.keys(
1977
+ POSTURE_NATIVE_ALIASES
1978
+ );
1979
+ new Set(CANONICAL_POSTURES);
1980
+ (() => {
1981
+ const index = /* @__PURE__ */ new Map();
1982
+ for (const [posture, aliases] of Object.entries(POSTURE_NATIVE_ALIASES)) {
1983
+ for (const alias of aliases) index.set(normalizeModeId(alias), posture);
1984
+ }
1985
+ return index;
1986
+ })();
1987
+ }
1988
+ });
1989
+ var harnessPresetSchema;
1990
+ var init_harness_preset_store = __esm({
1991
+ "src/harness-preset-store.ts"() {
1992
+ harnessPresetSchema = z.object({
1993
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
1994
+ harnessSlug: z.string().min(1),
1995
+ name: z.string().min(1),
1996
+ profileRef: z.string().min(1),
1997
+ defaultModel: z.string().min(1),
1998
+ isDefault: z.boolean()
1999
+ });
2000
+ z.object({
2001
+ version: z.literal(1),
2002
+ presets: z.array(harnessPresetSchema)
2003
+ });
2004
+ }
2005
+ });
2006
+ var init_browser_mount = __esm({
2007
+ "src/browser-mount.ts"() {
2008
+ }
2009
+ });
2010
+
2011
+ // src/role.ts
2012
+ var EXECUTOR_ROLE, SUPERVISOR_ROLE, DEFAULT_ROLE_DEPTH_CUTOFF;
2013
+ var init_role = __esm({
2014
+ "src/role.ts"() {
2015
+ EXECUTOR_ROLE = {
2016
+ name: "executor",
2017
+ disposition: "You are the executor. Do the task yourself, using only the tools provided \u2014 never spawn or delegate to another agent, even if it looks convenient. This includes any subagent/Task tool your own CLI ships natively: it is not routed through agentproto and this daemon cannot strip it out, so the rule holds regardless of which tools appear available to you.",
2018
+ toolPolicy: { delegation: "deny" },
2019
+ skills: [],
2020
+ // The floor of the lattice — in practice spawns nothing, since
2021
+ // `toolPolicy.delegation: "deny"` already strips `agent_start` before
2022
+ // the level comparison is ever reached. NOTE: that strip only covers
2023
+ // the daemon's own MCP gateway (agent_start/agent_prompt) — a native
2024
+ // subagent/Task tool bundled with the CLI itself (e.g. claude-code's
2025
+ // Task tool) isn't an MCP tool and can't be gated here at all, which
2026
+ // is why the disposition above spells it out explicitly.
2027
+ level: 0,
2028
+ // Executors are the primary consumer of the deferred-tools surface: they
2029
+ // never delegate (agent_start/agent_prompt are already stripped above),
2030
+ // so the bulk of the ~190-tool daemon gateway is dead weight at turn 0.
2031
+ // Default ON regardless of the daemon's own global
2032
+ // `defaults.mcp.deferredTools` setting — an operator who hasn't opted the
2033
+ // whole daemon in still gets a lean executor by default.
2034
+ deferredTools: true
2035
+ };
2036
+ SUPERVISOR_ROLE = {
2037
+ name: "supervisor",
2038
+ disposition: "You are the supervisor. Decompose the work and delegate the parts that genuinely benefit from a separate agent, then verify each result before relying on it. Delegate through the `agent_start` MCP tool (on the `agentproto` MCP server), not your CLI's native subagent/Task tool \u2014 a native subagent is invisible to this daemon (no session id, no tracking, no kill switch), whereas agent_start gives you a session you can observe and supervise. Prefer doing small work inline.",
2039
+ toolPolicy: { delegation: "allow" },
2040
+ level: 100
2041
+ };
2042
+ ({
2043
+ [EXECUTOR_ROLE.name]: EXECUTOR_ROLE,
2044
+ [SUPERVISOR_ROLE.name]: SUPERVISOR_ROLE
2045
+ });
2046
+ DEFAULT_ROLE_DEPTH_CUTOFF = 1;
2047
+ }
2048
+ });
2049
+ var init_deferred_tools = __esm({
2050
+ "src/deferred-tools.ts"() {
2051
+ }
2052
+ });
2053
+
2054
+ // src/role-pack.ts
2055
+ var init_role_pack = __esm({
2056
+ "src/role-pack.ts"() {
2057
+ init_browser_mount();
2058
+ }
2059
+ });
2060
+ var init_role_registry = __esm({
2061
+ "src/role-registry.ts"() {
2062
+ init_config();
2063
+ init_role_pack();
2064
+ }
2065
+ });
2066
+ var DEFAULT_AGENTS_MD_INLINE_MAX_KB;
2067
+ var init_agents_md = __esm({
2068
+ "src/agents-md.ts"() {
2069
+ init_config();
2070
+ DEFAULT_AGENTS_MD_INLINE_MAX_KB = 8;
2071
+ }
2072
+ });
2073
+ var init_workspace_buckets = __esm({
2074
+ "src/workspace-buckets.ts"() {
2075
+ init_workspaces_config();
2076
+ }
2077
+ });
2078
+ var init_workspace_rules = __esm({
2079
+ "src/workspace-rules.ts"() {
2080
+ init_workspace_buckets();
2081
+ }
2082
+ });
2083
+
2084
+ // src/session-title.ts
2085
+ var init_session_title = __esm({
2086
+ "src/session-title.ts"() {
2087
+ }
2088
+ });
2089
+
2090
+ // src/mcp-credential-deps.ts
2091
+ var init_mcp_credential_deps = __esm({
2092
+ "src/mcp-credential-deps.ts"() {
2093
+ }
2094
+ });
2095
+
2096
+ // src/sandbox-agent-session-proxy.ts
2097
+ var init_sandbox_agent_session_proxy = __esm({
2098
+ "src/sandbox-agent-session-proxy.ts"() {
2099
+ init_sandbox_ledger();
2100
+ }
2101
+ });
2102
+ var init_sandbox_app_serve = __esm({
2103
+ "src/sandbox-app-serve.ts"() {
2104
+ }
2105
+ });
2106
+ var init_local = __esm({
2107
+ "src/sandbox-providers/local.ts"() {
2108
+ }
2109
+ });
9
2110
 
10
- var CONFIG_FILE_PATH = () => join(homedir(), ".agentproto", "config.json");
2111
+ // src/sandbox-providers/registry.ts
2112
+ var CAPABILITIES_TODAY;
2113
+ var init_registry = __esm({
2114
+ "src/sandbox-providers/registry.ts"() {
2115
+ init_local();
2116
+ CAPABILITIES_TODAY = {
2117
+ networkEgress: true,
2118
+ mounts: false,
2119
+ lifecyclePause: false,
2120
+ readOnly: false
2121
+ };
2122
+ ({
2123
+ ...CAPABILITIES_TODAY,
2124
+ lifecyclePause: true
2125
+ });
2126
+ ({
2127
+ ...CAPABILITIES_TODAY,
2128
+ lifecyclePause: true
2129
+ });
2130
+ }
2131
+ });
2132
+ var init_sandbox_adapters = __esm({
2133
+ "src/sandbox-adapters.ts"() {
2134
+ init_registry();
2135
+ }
2136
+ });
2137
+ var init_session_spawn = __esm({
2138
+ "src/session-spawn.ts"() {
2139
+ init_sessions();
2140
+ init_workspaces_config();
2141
+ init_config();
2142
+ init_worktree_identity();
2143
+ init_spawn_defaults();
2144
+ init_launch_config();
2145
+ init_claude_code_oauth_source();
2146
+ init_providers_store();
2147
+ init_catalog_models();
2148
+ init_context_continuity();
2149
+ init_canonical_posture();
2150
+ init_harness_preset_store();
2151
+ init_browser_mount();
2152
+ init_role();
2153
+ init_deferred_tools();
2154
+ init_role_registry();
2155
+ init_agents_md();
2156
+ init_workspace_rules();
2157
+ init_session_title();
2158
+ init_mcp_credential_deps();
2159
+ init_sandbox_agent_session_proxy();
2160
+ init_sandbox_ledger();
2161
+ init_sandbox_app_serve();
2162
+ init_sandbox_adapters();
2163
+ init_worktree_isolation();
2164
+ init_spawn_attach();
2165
+ init_spawn_dedupe();
2166
+ init_gh_provenance_shim();
2167
+ }
2168
+ });
2169
+ function tryParseJson(value) {
2170
+ try {
2171
+ return JSON.parse(value);
2172
+ } catch {
2173
+ return value;
2174
+ }
2175
+ }
2176
+ function jsonTolerant(schema) {
2177
+ return z.preprocess(
2178
+ (v) => typeof v === "string" ? tryParseJson(v) : v,
2179
+ schema
2180
+ );
2181
+ }
2182
+ var init_json_tolerant = __esm({
2183
+ "src/json-tolerant.ts"() {
2184
+ }
2185
+ });
2186
+ var sandboxSpecWithReuseSchema;
2187
+ var init_sandbox_spec_schema = __esm({
2188
+ "src/sandbox-spec-schema.ts"() {
2189
+ sandboxSpecWithReuseSchema = z.object({
2190
+ ...SandboxSpecSchema.shape,
2191
+ reuse: z.string().min(1).optional().describe(
2192
+ "Reconnect to an existing box instead of booting a new one. Accepts a prior session's exact `sandboxId`, a ledger `label` (exact match), or an unambiguous prefix of a ledger sandboxId \u2014 resolved against the local sandbox ledger (`~/.agentproto/sandboxes.json`, see `agentproto sandbox list`). A prefix matching more than one ledger entry fails with `sandbox_reuse_ambiguous` listing the candidates. Requires the provider to support reconnect (e.g. e2b); omit to boot fresh (default)."
2193
+ )
2194
+ }).strict();
2195
+ }
2196
+ });
2197
+ var commandSandboxSchema, attachFieldSchema, contentBlockSchema, promptInputSchema, contextContinuityInputSchema;
2198
+ var init_spawn_field_schemas = __esm({
2199
+ "src/spawn-field-schemas.ts"() {
2200
+ commandSandboxSchema = z.enum(["off", "workspace", "strict"]);
2201
+ attachFieldSchema = z.union([
2202
+ z.boolean(),
2203
+ z.object({ parent: z.string().min(1).optional() })
2204
+ ]);
2205
+ contentBlockSchema = z.record(z.string(), z.unknown());
2206
+ promptInputSchema = z.union([
2207
+ z.string().min(1),
2208
+ contentBlockSchema,
2209
+ z.array(contentBlockSchema).min(1)
2210
+ ]);
2211
+ contextContinuityInputSchema = z.object({
2212
+ mode: z.enum(["manual", "ask", "auto"]).optional(),
2213
+ warnAtPct: z.number().int().min(0).max(100).optional(),
2214
+ compactAtPct: z.number().int().min(0).max(100).optional(),
2215
+ continueFreshAtPct: z.number().int().min(0).max(100).optional(),
2216
+ hardStopAtPct: z.number().int().min(0).max(100).optional(),
2217
+ goal: z.boolean().optional(),
2218
+ plan: z.boolean().optional(),
2219
+ decisions: z.boolean().optional(),
2220
+ changedFiles: z.boolean().optional(),
2221
+ gitStatus: z.boolean().optional(),
2222
+ tests: z.boolean().optional(),
2223
+ errors: z.boolean().optional(),
2224
+ risks: z.boolean().optional(),
2225
+ nextStep: z.boolean().optional(),
2226
+ config: z.boolean().optional(),
2227
+ label: z.string().optional()
2228
+ });
2229
+ }
2230
+ });
2231
+ var mcpBool, mcpPositiveNumber, help, agentStartInputShape, agentStartInputSchema;
2232
+ var init_agent_start_schema = __esm({
2233
+ "src/agent-start-schema.ts"() {
2234
+ init_json_tolerant();
2235
+ init_sandbox_spec_schema();
2236
+ init_spawn_field_schemas();
2237
+ mcpBool = z.preprocess(
2238
+ (v) => v === "true" ? true : v === "false" ? false : v,
2239
+ z.boolean()
2240
+ );
2241
+ mcpPositiveNumber = z.preprocess(
2242
+ (v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v,
2243
+ z.number().positive()
2244
+ );
2245
+ help = (field) => `Details: tool_help {name:"agent_start", topic:"${field}"}`;
2246
+ agentStartInputShape = {
2247
+ adapter: z.string().min(1).optional().describe(
2248
+ `Adapter slug (e.g. 'claude-code', 'hermes', 'aider'). Omit only when \`presetId\` supplies one. ${help("adapter")}`
2249
+ ),
2250
+ harness: z.string().min(1).optional().describe(`Alias for \`adapter\` \u2014 same values, whichever your caller produces.`),
2251
+ presetId: z.string().min(1).optional().describe(
2252
+ "Saved spawn preset id from `agentproto preset list`. Explicit fields on this call override its values."
2253
+ ),
2254
+ workspaceSlug: z.string().optional().describe(
2255
+ "Workspace slug from `agentproto workspace list`, resolved to a path. Omit to use `cwd` or the active workspace."
2256
+ ),
2257
+ cwd: z.string().optional().describe("Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."),
2258
+ prompt: promptInputSchema.optional().describe(
2259
+ 'Optional initial prompt \u2014 spawns and dispatches it in one call (spawn + `agent_prompt` combined). Omit to spawn idle. A plain string is composed with the role/AGENTS.md disposition preamble as usual; a content block or block array (e.g. a pasted image, `{type:"image", data, mimeType}`) SKIPS that composition and is sent to the adapter verbatim \u2014 the adapter negotiates its own multimodal support. ' + help("prompt")
2260
+ ),
2261
+ label: z.string().optional().describe("Free-text label shown in `agent_sessions_list` and the UI."),
2262
+ mode: z.string().optional().describe(
2263
+ `Manifest-declared mode id (AIP-45 \`modes\`), applied before the child starts (e.g. claude-code's 'plan'/'accept-edits'/'bypass-permissions'). Adapters with no declared \`modes\` REJECT any value here. ${help("mode")}`
2264
+ ),
2265
+ origin: z.string().min(1).optional().describe(
2266
+ "Source label for this spawn (codex, cowork, vscode, cron, \u2026) \u2014 descriptor-only, groups the session under a source node in the tree."
2267
+ ),
2268
+ parentSessionId: z.string().min(1).optional().describe(
2269
+ `Parent-lineage hint: nests this spawn under that session in the tree instead of a depth-0 root. Ignored through the scoped orchestrator gateway (its own token wins). ${help("parentSessionId")}`
2270
+ ),
2271
+ attach: jsonTolerant(attachFieldSchema).optional().describe(
2272
+ `Parent-attach override, mirrors \`worktree\`. Default attaches under the calling session; \`false\` = independent root; \`true\` forces attach; \`{ parent: "<id>" }\` pins an explicit parent. ${help("attach")}`
2273
+ ),
2274
+ boardId: z.string().min(1).optional().describe(
2275
+ `Task-board pin for the child (\`meta.boardId\`), resolved before parent-lineage. An explicit board on a task verb still wins. ${help("boardId")}`
2276
+ ),
2277
+ idempotencyKey: z.string().min(1).optional().describe(
2278
+ `Caller-declared dedup token: a retried call with the SAME key for the same \`adapter\`+\`cwd\` within ~10min of a successful spawn returns that SAME session (\`deduped: true\`) instead of forking a new one. Always wins over the implicit key \`dedupe\` derives. ${help("idempotencyKey")}`
2279
+ ),
2280
+ dedupe: mcpBool.optional().describe(
2281
+ `Override the implicit dedup-by-\`label\` default (on when no \`idempotencyKey\` is given): \`false\` opts this spawn out, \`true\` forces it even under an "on-request" daemon policy. ${help("dedupe")}`
2282
+ ),
2283
+ permissionHold: mcpBool.optional().describe(
2284
+ "Hold every ACP permission request in the cross-session inbox (`permissions_list`/`permissions_respond`) instead of auto-answering. ACP adapters only; default false."
2285
+ ),
2286
+ notifyParentOnCrash: mcpBool.optional().describe(
2287
+ `Notify this session's parent directly (in-band) if it later crashes. Independent of the global \`notifyUrl\` webhook, which fires either way. ${help("notifyParentOnCrash")}`
2288
+ ),
2289
+ allowSharedCwd: mcpBool.optional().describe(
2290
+ "Acknowledge running a nested spawn in the parent's dirty working tree, silencing the shared-dirty-cwd warning. Only relevant with no `worktree`/`sandbox`; default false = warn."
2291
+ ),
2292
+ keepAlive: mcpBool.optional().describe(
2293
+ "Exempt this session from the idle-reaper \u2014 never auto-retired for sitting idle. Default false. Toggle later with `session_set_keepalive`."
2294
+ ),
2295
+ options: jsonTolerant(
2296
+ z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]))
2297
+ ).optional().describe(
2298
+ "Manifest-declared option id \u2192 value map (AIP-45 `options`), validated against each option's declared type/enum/min/max. Unknown ids reject."
2299
+ ),
2300
+ skills: jsonTolerant(z.array(z.string())).optional().describe(
2301
+ `Normalized skill ids for this session (e.g. ['agentproto']). REPLACES (not unions) config defaults when set. Adapters with no \`skills\` option (e.g. claude-code) ignore it. ${help("skills")}`
2302
+ ),
2303
+ model: z.string().optional().describe(
2304
+ "Model identifier to pass to the adapter. For claude-code, applied via session/set_config_option \u2014 NOT a CLI flag."
2305
+ ),
2306
+ effort: z.string().optional().describe(
2307
+ "Reasoning effort label ('low'/'medium'/'high'/'xhigh'/'max'/'ultracode'). Calibrated PER MODEL \u2014 the same label means different compute budgets across models. Omit to keep the model's own default."
2308
+ ),
2309
+ route: jsonTolerant(
2310
+ z.object({ gateway: z.string().min(1), baseUrl: z.string().url().optional() })
2311
+ ).optional().describe("Billing route/gateway. Independent of `model` and `access`."),
2312
+ access: jsonTolerant(z.object({ profileRef: z.string().min(1).optional() })).optional().describe("Named auth profile to bill at initial spawn; resolved from the local keychain."),
2313
+ posture: z.string().optional().describe("Canonical agent posture (plan, bypass, accept-edits, read-only) or native harness mode."),
2314
+ contextProfile: z.string().optional().describe("Context intake profile (for example full or lean)."),
2315
+ auth: jsonTolerant(
2316
+ z.object({
2317
+ mode: z.enum(["subscription", "api-key"]).optional(),
2318
+ token: z.string().optional(),
2319
+ source: z.string().optional(),
2320
+ apiKey: z.string().optional()
2321
+ })
2322
+ ).optional().describe(
2323
+ `Explicit billing-auth mode + credential for adapters that declare it (today: claude-code) \u2014 'subscription' (default) bills the Max/Pro plan, 'api-key' bills API credits. FAILS FAST with no fallback if the resolved mode has no credential configured anywhere. ${help("auth")}`
2324
+ ),
2325
+ mcpServers: jsonTolerant(
2326
+ z.array(
2327
+ z.object({
2328
+ name: z.string(),
2329
+ transport: z.enum(["stdio", "http", "sse"]),
2330
+ ref: z.string().optional(),
2331
+ headers: z.record(z.string(), z.string()).optional().describe("Static HTTP headers for `http`/`sse` transports. Ignored for `stdio`."),
2332
+ credentialRef: z.string().optional().describe(
2333
+ "Brokered credential resolved into `headers` at spawn time (wins on collision with `headers`); the secret never lives in env/config."
2334
+ ),
2335
+ args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command."),
2336
+ env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server.")
2337
+ })
2338
+ )
2339
+ ).optional().describe(
2340
+ "MCP servers to mount into the spawned agent's session at spawn time. Adapters that don't model MCP mounting ignore it."
2341
+ ),
2342
+ orchestrator: jsonTolerant(
2343
+ z.union([
2344
+ z.boolean(),
2345
+ z.object({
2346
+ tools: z.array(z.string()).optional().describe("Explicit allowlist \u2286 the default subset \u2014 a child can never widen its own scope."),
2347
+ maxDepth: z.number().int().min(1).max(8).optional().describe("Max recursion depth reachable through this child (default 3, ceiling 8, lower-only on recursion)."),
2348
+ maxChildren: z.number().int().min(1).optional().describe("Max concurrently-alive sub-agents this child may spawn (default 8, lower-only on recursion).")
2349
+ })
2350
+ ])
2351
+ ).optional().describe(
2352
+ `Make this child a SCOPED orchestrator \u2014 auto-mounts the daemon's own spawn/supervise tools, scoped to its own sub-agents. \`true\` = default subset; \`{ tools }\` narrows it. Shell/fs/remote tools are NEVER exposed. ${help("orchestrator")}`
2353
+ ),
2354
+ notifyUrl: z.string().url().optional().describe(
2355
+ "Optional per-session webhook URL. POSTed (fire-and-forget) on this session's turn-end / awaiting-input / exited events, in addition to any global notify URL."
2356
+ ),
2357
+ wait: mcpBool.optional().describe(
2358
+ `Block until the spawned session's first turn completes (~40-90s+) and include its output. Default false = return immediately. Batched \`wait:true\` calls do NOT run in parallel \u2014 see \`tool_help {name:"agent_start", topic:"wait"}\` for the fan-out pattern.`
2359
+ ),
2360
+ maxCostUsd: mcpPositiveNumber.optional().describe(
2361
+ "Hard ceiling on cumulative session cost (USD). The session is stopped at a turn-end once exceeded."
2362
+ ),
2363
+ costBudget: jsonTolerant(
2364
+ z.object({
2365
+ maxCostUsd: z.number().positive().describe("Windowed spend ceiling in USD."),
2366
+ window: z.string().min(1).describe('Rolling window spec ("5h"/"7d"/"P7D").'),
2367
+ scope: z.enum(["session", "profile"]).describe("Spend surface: this session, or every session on its auth profile.")
2368
+ })
2369
+ ).optional().describe(
2370
+ "Windowed cost-budget cap (DISTINCT from `maxCostUsd`) \u2014 trips `policy:failed` on overage, never kills the session."
2371
+ ),
2372
+ restartPolicy: jsonTolerant(
2373
+ z.object({
2374
+ on: z.array(z.enum(["crashed", "error"])).min(1).describe("Which automatic death reasons trigger a restart. Never a clean exit, kill, idle-reap, or cost-budget kill."),
2375
+ maxRetries: z.number().int().positive().describe("Rolling-window crash-loop cap: give up after this many restarts within `windowMs`."),
2376
+ windowMs: z.number().int().positive().describe("Rolling window (ms) `maxRetries` counts restarts over."),
2377
+ baseDelayMs: z.number().int().positive().describe("First restart's backoff delay (ms), before `factor` compounds it."),
2378
+ factor: z.number().positive().describe("Exponential backoff multiplier applied per consecutive restart."),
2379
+ maxDelayMs: z.number().int().positive().describe("Backoff ceiling (ms) \u2014 the computed delay never grows past this."),
2380
+ resume: z.boolean().optional().describe("Reserved for a future resume-vs-fresh-spawn toggle; this always revives in place today.")
2381
+ })
2382
+ ).optional().describe(
2383
+ `Opt-in auto-restart policy: an unexpected death (\`crashed\`/\`error\`, per \`on\`) is revived IN PLACE after exponential backoff, up to a rolling-window crash-loop cap. Omit for today's stay-dead behaviour. ${help("restartPolicy")}`
2384
+ ),
2385
+ contextContinuity: jsonTolerant(contextContinuityInputSchema).optional().describe(
2386
+ "Context-continuity policy for this session \u2014 controls warning, opportunistic compaction, fresh-continuation, and hard-stop thresholds. Resolved from global \u2192 per-adapter \u2192 explicit override."
2387
+ ),
2388
+ role: z.string().optional().describe(
2389
+ `Spawn-time delegation gate. 'executor' = leaf, cannot delegate (\`agent_start\`/\`agent_prompt\` stripped). 'supervisor' = may delegate (default). Custom role packs resolve the same way. Omit to derive from spawn depth. ${help("role")}`
2390
+ ),
2391
+ promptAppend: z.string().optional().describe(
2392
+ "One-off runtime text layered onto the resolved role's disposition and prepended to `prompt` \u2014 specializes it, cannot reopen a denied tool gate."
2393
+ ),
2394
+ deferredTools: z.boolean().optional().describe(
2395
+ `Override lazy MCP tool loading for this spawn: \`true\` hides most tools from \`tools/list\` (still callable via \`tool_search\`), \`false\` keeps the full eager surface. Omit to use the role/daemon default. ${help("deferredTools")}`
2396
+ ),
2397
+ browser: z.preprocess(
2398
+ // `true` is sugar for the only mode; stringified booleans tolerated.
2399
+ (v) => v === true || v === "true" ? "headless" : v === "false" ? false : v,
2400
+ z.union([z.literal("headless"), z.literal(false)])
2401
+ ).optional().describe(
2402
+ `\`"headless"\` gives the spawned agent its own isolated headless Chrome as a per-session \`browser\` MCP server, torn down with the session. \`false\` = none (default). Not supported with \`sandbox\`. ${help("browser")}`
2403
+ ),
2404
+ trace: z.boolean().optional().describe(
2405
+ "Emit Langfuse observability traces for this session (prompt/completion + tool spans + tokens/cost). Off by default; requires langfuse eval-reporter creds configured."
2406
+ ),
2407
+ sandbox: jsonTolerant(
2408
+ z.union([
2409
+ z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
2410
+ sandboxSpecWithReuseSchema.describe(
2411
+ "Inline AIP-36 SandboxDefinition \u2014 boots this exact spec instead of a catalog slug. Set `reuse` to reconnect to an existing sandbox instead of booting fresh."
2412
+ )
2413
+ ])
2414
+ ).optional().describe(
2415
+ `Run this session inside a sandbox instead of on the host (provider slug or inline AIP-36 spec) \u2014 the daemon boots the box, spawns \`adapter\` on its own agentproto daemon, and proxies the conversation back here. DISTINCT from \`commandSandbox\` (whole separate machine vs. confining this host's own process). Omit to run locally. ${help("sandbox")}`
2416
+ ),
2417
+ appServe: jsonTolerant(
2418
+ z.object({
2419
+ dir: z.string().min(1).describe("Absolute path to the app's directory INSIDE the sandbox box (e.g. '/home/user/apps/<slug>')."),
2420
+ port: z.number().int().min(1).max(65535).optional().describe("Port the UI binds inside the box (default 3210). Exposed by the sandbox provider as a public URL.")
2421
+ }).strict()
2422
+ ).optional().describe(
2423
+ `Serve an agentproto app's UI from INSIDE the sandbox box and return its public URL. Requires \`sandbox\` (rejected otherwise). ${help("appServe")}`
2424
+ ),
2425
+ commandSandbox: commandSandboxSchema.optional().describe(
2426
+ `OS-level confinement (macOS Seatbelt / Linux bubblewrap) of the adapter's OWN process on THIS host \u2014 NOT \`sandbox\` above, which boots a separate machine. \`"off"\` (default) = unconfined; \`"workspace"\` = deny reads/writes outside cwd; \`"strict"\` = + deny network. Fails the spawn if the OS backend isn't installed. ${help("commandSandbox")}`
2427
+ ),
2428
+ worktree: jsonTolerant(
2429
+ z.union([
2430
+ mcpBool,
2431
+ z.object({
2432
+ slug: z.string().regex(
2433
+ /^[a-z0-9][a-z0-9-]*$/,
2434
+ "slug must be lowercase kebab-case (letters, digits, hyphens)"
2435
+ ).optional().describe("Pin the worktree's slug (branch `wt/<slug>`). Omit to auto-mint one from the label."),
2436
+ base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'."),
2437
+ async: z.boolean().optional().describe(
2438
+ `Return a registered session immediately (status "starting") instead of blocking on provisioning; incompatible with \`wait\`. Defaults true whenever a worktree is provisioned. ${help("worktree")}`
2439
+ )
2440
+ }).strict()
2441
+ ])
2442
+ ).optional().describe(
2443
+ `Isolate this session in its OWN git worktree instead of \`cwd\`. \`true\` provisions one on a fresh \`wt/<slug>\` branch; \`{ slug, base, async }\` pins details. ROOT spawns only (rejected on a nested spawn \u2014 use \`sandbox\` there); ignored when \`cwd\` isn't a git repo. NOT auto-removed on session close \u2014 tear down with \`agentproto worktree rm\`. ${help("worktree")}`
2444
+ )
2445
+ };
2446
+ agentStartInputSchema = z.object(agentStartInputShape);
2447
+ agentStartInputSchema.omit({ wait: true });
2448
+ }
2449
+ });
2450
+ var effortSchema, postureSchema, routeSchema, userPresetSchema;
2451
+ var init_user_presets = __esm({
2452
+ "src/user-presets.ts"() {
2453
+ effortSchema = z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]);
2454
+ postureSchema = z.union([
2455
+ z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
2456
+ z.object({ harnessModeId: z.string().min(1) })
2457
+ ]);
2458
+ routeSchema = z.object({
2459
+ gateway: z.string().min(1),
2460
+ baseUrl: z.string().url().optional()
2461
+ });
2462
+ userPresetSchema = z.object({
2463
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
2464
+ label: z.string().min(1),
2465
+ adapter: z.string().min(1).optional(),
2466
+ harness: z.string().min(1).optional(),
2467
+ model: z.string().min(1).optional(),
2468
+ route: routeSchema.optional(),
2469
+ access: z.object({ profileRef: z.string().min(1).optional() }).optional(),
2470
+ posture: postureSchema.optional(),
2471
+ effort: effortSchema.optional(),
2472
+ contextProfile: z.string().min(1).optional(),
2473
+ cwd: z.string().min(1).optional(),
2474
+ skills: z.array(z.string().min(1)).optional(),
2475
+ browser: z.union([z.literal("headless"), z.literal(false)]).optional()
2476
+ });
2477
+ z.object({
2478
+ version: z.literal(1),
2479
+ presets: z.array(userPresetSchema)
2480
+ });
2481
+ }
2482
+ });
2483
+ var init_catalog_provider_models = __esm({
2484
+ "src/catalog-provider-models.ts"() {
2485
+ }
2486
+ });
2487
+ var init_app_ui_build = __esm({
2488
+ "src/app-ui-build.ts"() {
2489
+ }
2490
+ });
2491
+
2492
+ // src/app-ui-placeholder.ts
2493
+ var init_app_ui_placeholder = __esm({
2494
+ "src/app-ui-placeholder.ts"() {
2495
+ }
2496
+ });
2497
+ var init_app_ui_apps = __esm({
2498
+ "src/app-ui-apps.ts"() {
2499
+ init_app_ui_build();
2500
+ init_app_ui_placeholder();
2501
+ }
2502
+ });
2503
+ var MESSAGE_KINDS, MESSAGE_URGENCIES;
2504
+ var init_session_message = __esm({
2505
+ "src/session-message.ts"() {
2506
+ MESSAGE_KINDS = ["report", "question", "blocker", "done", "notice"];
2507
+ MESSAGE_URGENCIES = ["fyi", "next-turn", "steer", "interrupt"];
2508
+ }
2509
+ });
2510
+ var init_message_tools = __esm({
2511
+ "src/message-tools.ts"() {
2512
+ init_session_message();
2513
+ z.enum(MESSAGE_KINDS);
2514
+ z.enum(MESSAGE_URGENCIES);
2515
+ z.unknown().optional().describe("Not settable \u2014 leave it out: the daemon attests the sender from your session identity.");
2516
+ }
2517
+ });
2518
+ var init_agent_tools = __esm({
2519
+ "src/agent-tools.ts"() {
2520
+ init_transcript_export();
2521
+ init_agent_start_schema();
2522
+ init_spawn_field_schemas();
2523
+ init_session_spawn();
2524
+ init_canonical_posture();
2525
+ init_user_presets();
2526
+ init_role();
2527
+ init_role_registry();
2528
+ init_catalog_provider_models();
2529
+ init_app_ui_apps();
2530
+ init_session_message();
2531
+ init_message_tools();
2532
+ z.string().optional().describe(
2533
+ "Session id (as returned by agent_start). Accepts either `sessionId` or its alias `id` \u2014 pass whichever you have."
2534
+ );
2535
+ z.string().optional().describe("Alias for `sessionId` \u2014 the `id` field returned by agent_start.");
2536
+ }
2537
+ });
2538
+ var init_supervisor = __esm({
2539
+ "src/supervisor.ts"() {
2540
+ init_sessions();
2541
+ init_usage_rollup_service();
2542
+ init_usage_rollup();
2543
+ init_cost_budget();
2544
+ init_command_tools();
2545
+ init_session_spawn();
2546
+ init_agent_tools();
2547
+ }
2548
+ });
2549
+
2550
+ // src/session-activity.ts
2551
+ var init_session_activity = __esm({
2552
+ "src/session-activity.ts"() {
2553
+ }
2554
+ });
2555
+ var DEFAULT_BG_TASK_WAKE_GRACE_MS;
2556
+ var init_background_task_wake = __esm({
2557
+ "src/background-task-wake.ts"() {
2558
+ DEFAULT_BG_TASK_WAKE_GRACE_MS = 2e4;
2559
+ }
2560
+ });
2561
+
2562
+ // src/session-observer.ts
2563
+ var init_session_observer = __esm({
2564
+ "src/session-observer.ts"() {
2565
+ }
2566
+ });
2567
+ var DEFAULT_TITLER_MODEL;
2568
+ var init_session_titler = __esm({
2569
+ "src/session-titler.ts"() {
2570
+ init_config();
2571
+ init_transcript_writer();
2572
+ DEFAULT_TITLER_MODEL = "z-ai/glm-5.2@openrouter";
2573
+ }
2574
+ });
2575
+
2576
+ // src/resume-context-digest.ts
2577
+ var init_resume_context_digest = __esm({
2578
+ "src/resume-context-digest.ts"() {
2579
+ init_transcript_export();
2580
+ }
2581
+ });
2582
+ var init_conversation_index = __esm({
2583
+ "src/conversation-index.ts"() {
2584
+ init_conversation_store();
2585
+ init_workspace_buckets();
2586
+ }
2587
+ });
2588
+ var init_terminal_transcript_writer = __esm({
2589
+ "src/terminal-transcript-writer.ts"() {
2590
+ init_transcript_writer();
2591
+ }
2592
+ });
2593
+ var init_context_window = __esm({
2594
+ "src/context-window.ts"() {
2595
+ }
2596
+ });
2597
+ var init_context_checkpoint = __esm({
2598
+ "src/context-checkpoint.ts"() {
2599
+ init_transcript_export();
2600
+ init_transcript_writer();
2601
+ }
2602
+ });
2603
+
2604
+ // src/session-continue-fresh.ts
2605
+ var init_session_continue_fresh = __esm({
2606
+ "src/session-continue-fresh.ts"() {
2607
+ init_context_checkpoint();
2608
+ init_context_continuity();
2609
+ init_session_spawn();
2610
+ }
2611
+ });
2612
+ var init_session_outcome = __esm({
2613
+ "src/session-outcome.ts"() {
2614
+ }
2615
+ });
2616
+
2617
+ // src/session-end-reason.ts
2618
+ var init_session_end_reason = __esm({
2619
+ "src/session-end-reason.ts"() {
2620
+ }
2621
+ });
2622
+ var init_sessions = __esm({
2623
+ "src/sessions.ts"() {
2624
+ init_resume_strategies();
2625
+ init_conversation_store();
2626
+ init_command_log();
2627
+ init_sandbox_ledger();
2628
+ init_tool_call_log();
2629
+ init_usage_snapshot_log();
2630
+ init_tool_call_record();
2631
+ init_hooks_config();
2632
+ init_supervisor();
2633
+ init_session_title();
2634
+ init_session_activity();
2635
+ init_canonical_posture();
2636
+ init_background_task_wake();
2637
+ init_catalog_models();
2638
+ init_model_wire();
2639
+ init_session_observer();
2640
+ init_tool_presenter();
2641
+ init_transcript_writer();
2642
+ init_session_titler();
2643
+ init_resume_context_digest();
2644
+ init_conversation_index();
2645
+ init_workspace_buckets();
2646
+ init_terminal_transcript_writer();
2647
+ init_usage();
2648
+ init_context_window();
2649
+ init_worktree_identity();
2650
+ init_context_continuity();
2651
+ init_session_continue_fresh();
2652
+ init_session_outcome();
2653
+ init_session_end_reason();
2654
+ init_session_message();
2655
+ }
2656
+ });
2657
+ var PROVENANCE_WRAP_GH_ENV, DEFAULT_WRAP_GH;
2658
+ var init_gh_provenance_shim = __esm({
2659
+ "src/gh-provenance-shim.ts"() {
2660
+ init_pr_provenance();
2661
+ init_sessions();
2662
+ PROVENANCE_WRAP_GH_ENV = "AGENTPROTO_PROVENANCE_WRAP_GH";
2663
+ DEFAULT_WRAP_GH = false;
2664
+ }
2665
+ });
2666
+ function validateConfig(raw) {
2667
+ const result = agentprotoConfigSchema.safeParse(raw);
2668
+ if (result.success) return { ok: true, issues: [] };
2669
+ const issues = result.error.issues.map((issue) => {
2670
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
2671
+ return `${path}: ${issue.message}`;
2672
+ });
2673
+ return { ok: false, issues };
2674
+ }
2675
+ var contextContinuityPolicySchema, deferredToolsConfigSchema, spawnBrowserModeSchema, optionsMapSchema, defaultsAdapterAuthConfigSchema, defaultsAdapterConfigSchema, mcpDefaultsConfigSchema, spawnDefaultsConfigSchema, daemonConfigSchema, titlerConfigSchema, tunnelConfigSchema, featuresConfigSchema, worktreesConfigSchema, spawnConfigSchema, provenanceConfigSchema, sessionsConfigSchema, agentsMdConfigSchema, pairPageSchema, pairingConfigSchema, profileConfigSchema, acpAgentConfigEntrySchema, terminalPresetSchema, agentprotoConfigSchema, bool, str, num, strArray;
2676
+ var init_config_schema = __esm({
2677
+ "src/config-schema.ts"() {
2678
+ init_worktree_isolation();
2679
+ init_spawn_attach();
2680
+ init_spawn_dedupe();
2681
+ init_session_presence();
2682
+ init_gh_provenance_shim();
2683
+ init_agents_md();
2684
+ init_session_titler();
2685
+ init_background_task_wake();
2686
+ init_role();
2687
+ contextContinuityPolicySchema = z.object({
2688
+ mode: z.enum(["manual", "ask", "auto"]).optional(),
2689
+ warnAtPct: z.number().optional(),
2690
+ compactAtPct: z.number().optional(),
2691
+ continueFreshAtPct: z.number().optional(),
2692
+ hardStopAtPct: z.number().optional(),
2693
+ goal: z.boolean().optional(),
2694
+ plan: z.boolean().optional(),
2695
+ decisions: z.boolean().optional(),
2696
+ changedFiles: z.boolean().optional(),
2697
+ gitStatus: z.boolean().optional(),
2698
+ tests: z.boolean().optional(),
2699
+ errors: z.boolean().optional(),
2700
+ risks: z.boolean().optional(),
2701
+ nextStep: z.boolean().optional(),
2702
+ config: z.boolean().optional(),
2703
+ label: z.string().optional()
2704
+ }).passthrough();
2705
+ deferredToolsConfigSchema = z.union([
2706
+ z.boolean(),
2707
+ z.object({ alwaysOn: z.array(z.string()) }).passthrough()
2708
+ ]);
2709
+ spawnBrowserModeSchema = z.union([
2710
+ z.literal("headless"),
2711
+ z.literal(false)
2712
+ ]);
2713
+ optionsMapSchema = z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]));
2714
+ defaultsAdapterAuthConfigSchema = z.object({
2715
+ mode: z.enum(["subscription", "api-key"]).optional(),
2716
+ token: z.string().optional(),
2717
+ source: z.string().optional(),
2718
+ apiKey: z.string().optional(),
2719
+ provider: CatalogProviderSchema.optional()
2720
+ }).passthrough();
2721
+ defaultsAdapterConfigSchema = z.object({
2722
+ skills: z.array(z.string()).optional(),
2723
+ options: optionsMapSchema.optional(),
2724
+ auth: defaultsAdapterAuthConfigSchema.optional(),
2725
+ contextContinuity: contextContinuityPolicySchema.optional()
2726
+ }).passthrough();
2727
+ mcpDefaultsConfigSchema = z.object({
2728
+ deferredTools: deferredToolsConfigSchema.optional()
2729
+ }).passthrough();
2730
+ spawnDefaultsConfigSchema = z.object({
2731
+ skills: z.array(z.string()).optional(),
2732
+ options: optionsMapSchema.optional(),
2733
+ adapters: z.record(z.string(), defaultsAdapterConfigSchema).optional(),
2734
+ contextContinuity: contextContinuityPolicySchema.optional(),
2735
+ defaultRoleDepthCutoff: z.number().optional(),
2736
+ maxGrantableDelegation: z.number().optional(),
2737
+ langfuseTracing: z.boolean().optional(),
2738
+ backgroundTaskWake: z.object({ enabled: z.boolean().optional(), graceMs: z.number().optional() }).passthrough().optional(),
2739
+ traceRedactor: z.string().optional(),
2740
+ agentPromptInterrupt: z.boolean().optional(),
2741
+ messaging: z.object({
2742
+ allowSiblings: z.boolean().optional(),
2743
+ agentInterrupt: z.enum(["allow", "deny"]).optional()
2744
+ }).passthrough().optional(),
2745
+ mcp: mcpDefaultsConfigSchema.optional(),
2746
+ spawn: z.object({ browser: spawnBrowserModeSchema.optional() }).passthrough().optional()
2747
+ }).passthrough();
2748
+ daemonConfigSchema = z.object({
2749
+ workspace: z.string().optional(),
2750
+ port: z.number().optional(),
2751
+ bind: z.string().optional(),
2752
+ allowedOrigins: z.array(z.string()).optional(),
2753
+ strictOrigins: z.boolean().optional(),
2754
+ label: z.string().optional(),
2755
+ authToken: z.string().optional(),
2756
+ resumeSessionsOnBoot: z.boolean().optional(),
2757
+ continueInterruptedOnBoot: z.boolean().optional(),
2758
+ idleReapAfterMs: z.number().optional(),
2759
+ crashDetectIntervalMs: z.number().optional(),
2760
+ restartSweepIntervalMs: z.number().optional(),
2761
+ turnStallAfterMs: z.number().optional()
2762
+ }).passthrough();
2763
+ titlerConfigSchema = z.object({
2764
+ enabled: z.boolean().optional(),
2765
+ model: z.string().optional()
2766
+ }).passthrough();
2767
+ tunnelConfigSchema = z.object({
2768
+ host: z.string().optional(),
2769
+ token: z.string().optional(),
2770
+ autoconnect: z.boolean().optional(),
2771
+ e2e: z.boolean().optional()
2772
+ }).passthrough();
2773
+ featuresConfigSchema = z.object({
2774
+ pty: z.boolean().optional(),
2775
+ llmEndpoint: z.boolean().optional()
2776
+ }).passthrough();
2777
+ worktreesConfigSchema = z.object({
2778
+ root: z.string().optional(),
2779
+ isolation: z.enum(["always", "on-request", "never"]).optional()
2780
+ }).passthrough();
2781
+ spawnConfigSchema = z.object({
2782
+ attach: z.enum(["always", "on-request"]).optional(),
2783
+ dedupe: z.enum(["always", "on-request"]).optional()
2784
+ }).passthrough();
2785
+ provenanceConfigSchema = z.object({ wrapGh: z.boolean().optional() }).passthrough();
2786
+ sessionsConfigSchema = z.object({
2787
+ attentionDelaySec: z.number().optional(),
2788
+ eventsDir: z.string().optional()
2789
+ }).passthrough();
2790
+ agentsMdConfigSchema = z.object({ inlineMaxKb: z.number().optional() }).passthrough();
2791
+ pairPageSchema = z.string().superRefine((value, ctx) => {
2792
+ try {
2793
+ resolvePairPageUrl(value, "0123456789abcdef");
2794
+ } catch (err) {
2795
+ ctx.addIssue({ code: "custom", message: err instanceof Error ? err.message : String(err) });
2796
+ }
2797
+ });
2798
+ pairingConfigSchema = z.object({
2799
+ rendezvous: z.string().optional(),
2800
+ autoconnect: z.boolean().optional(),
2801
+ pairPage: pairPageSchema.optional()
2802
+ }).passthrough();
2803
+ profileConfigSchema = z.object({
2804
+ daemon: daemonConfigSchema.optional(),
2805
+ tunnel: tunnelConfigSchema.optional(),
2806
+ features: featuresConfigSchema.optional()
2807
+ }).passthrough();
2808
+ acpAgentConfigEntrySchema = z.object({
2809
+ name: z.string().optional(),
2810
+ description: z.string().optional(),
2811
+ bin: z.string(),
2812
+ bin_args: z.array(z.string()).optional(),
2813
+ env: z.record(z.string(), z.string()).optional(),
2814
+ cwd_flag: z.string().optional(),
2815
+ resumable: z.boolean().optional(),
2816
+ models: z.object({
2817
+ default: z.string().optional(),
2818
+ allowed: z.array(z.string()).optional()
2819
+ }).passthrough().optional(),
2820
+ provider: z.string().optional(),
2821
+ install_hint: z.string().optional()
2822
+ }).passthrough();
2823
+ terminalPresetSchema = z.object({
2824
+ argv: z.array(z.string()).optional(),
2825
+ env: z.record(z.string(), z.string()).optional(),
2826
+ cwd: z.string().optional(),
2827
+ workspace: z.string().optional(),
2828
+ name: z.string().optional(),
2829
+ label: z.string().optional()
2830
+ }).passthrough();
2831
+ agentprotoConfigSchema = z.object({
2832
+ version: z.number().optional(),
2833
+ daemon: daemonConfigSchema.optional(),
2834
+ tunnel: tunnelConfigSchema.optional(),
2835
+ features: featuresConfigSchema.optional(),
2836
+ pairing: pairingConfigSchema.optional(),
2837
+ worktrees: worktreesConfigSchema.optional(),
2838
+ spawn: spawnConfigSchema.optional(),
2839
+ sessions: sessionsConfigSchema.optional(),
2840
+ provenance: provenanceConfigSchema.optional(),
2841
+ agentsMd: agentsMdConfigSchema.optional(),
2842
+ titler: titlerConfigSchema.optional(),
2843
+ profiles: z.record(z.string(), profileConfigSchema).optional(),
2844
+ activeProfile: z.string().optional(),
2845
+ defaults: spawnDefaultsConfigSchema.optional(),
2846
+ acpAgents: z.record(z.string(), acpAgentConfigEntrySchema).optional(),
2847
+ terminalPresets: z.record(z.string(), terminalPresetSchema).optional()
2848
+ }).passthrough();
2849
+ bool = z.boolean();
2850
+ str = z.string();
2851
+ num = z.number();
2852
+ strArray = z.array(z.string());
2853
+ [
2854
+ // ── daemon ──
2855
+ {
2856
+ path: "daemon.workspace",
2857
+ schema: str,
2858
+ apply: "restart",
2859
+ writable: true,
2860
+ section: "daemon",
2861
+ label: "Workspace",
2862
+ help: "Absolute path the daemon binds to at boot."
2863
+ },
2864
+ {
2865
+ path: "daemon.port",
2866
+ schema: num,
2867
+ apply: "restart",
2868
+ writable: false,
2869
+ section: "daemon",
2870
+ label: "Port",
2871
+ help: "Local HTTP port. Changing this from a UI served BY the daemon can cut the UI off; hand-edit only.",
2872
+ default: 18790
2873
+ },
2874
+ {
2875
+ path: "daemon.bind",
2876
+ schema: str,
2877
+ apply: "restart",
2878
+ writable: false,
2879
+ section: "daemon",
2880
+ label: "Bind address",
2881
+ help: "Bind address. Lockout: a bad value here can make the daemon unreachable.",
2882
+ default: "127.0.0.1"
2883
+ },
2884
+ {
2885
+ path: "daemon.allowedOrigins",
2886
+ schema: strArray,
2887
+ apply: "restart",
2888
+ writable: false,
2889
+ section: "daemon",
2890
+ label: "Allowed origins",
2891
+ help: "Trusted browser origins for mutating /sessions/* routes, in addition to the hardcoded localhost defaults. Lockout: a bad value can lock a browser UI out."
2892
+ },
2893
+ {
2894
+ path: "daemon.strictOrigins",
2895
+ schema: bool,
2896
+ apply: "restart",
2897
+ writable: false,
2898
+ section: "daemon",
2899
+ label: "Strict origins",
2900
+ help: "When true, only origins in allowedOrigins are trusted (no auto-trust of localhost-on-any-port). Lockout: pairs with allowedOrigins.",
2901
+ default: false
2902
+ },
2903
+ {
2904
+ path: "daemon.label",
2905
+ schema: str,
2906
+ apply: "restart",
2907
+ writable: true,
2908
+ section: "daemon",
2909
+ label: "Server label",
2910
+ help: "Label sent in tunnel hello frames."
2911
+ },
2912
+ {
2913
+ path: "daemon.authToken",
2914
+ schema: str,
2915
+ apply: "restart",
2916
+ secret: true,
2917
+ writable: false,
2918
+ section: "daemon",
2919
+ label: "Auth token",
2920
+ help: 'Bearer token gating the gateway at boot. Unset \u21D2 mode "none" (open on loopback). Set via the CLI, never through config_set.'
2921
+ },
2922
+ {
2923
+ path: "daemon.resumeSessionsOnBoot",
2924
+ schema: bool,
2925
+ apply: "restart",
2926
+ writable: true,
2927
+ section: "daemon",
2928
+ label: "Resume sessions on boot",
2929
+ help: "Eagerly re-spawn eligible agent-cli sessions that died from a daemon restart.",
2930
+ default: false
2931
+ },
2932
+ {
2933
+ path: "daemon.continueInterruptedOnBoot",
2934
+ schema: bool,
2935
+ apply: "restart",
2936
+ writable: true,
2937
+ section: "daemon",
2938
+ label: "Continue interrupted sessions on boot",
2939
+ help: 'After the eager resume pass, send a one-shot "continue" prompt to sessions the last restart cut off mid-turn.',
2940
+ default: false
2941
+ },
2942
+ {
2943
+ path: "daemon.idleReapAfterMs",
2944
+ schema: num,
2945
+ apply: "restart",
2946
+ env: "AGENTPROTO_IDLE_REAP_AFTER_MS",
2947
+ writable: true,
2948
+ section: "daemon",
2949
+ label: "Idle reap after (ms)",
2950
+ help: "Retire idle agent-cli sessions after this many ms. Unset/0/negative \u21D2 the reaper never runs.",
2951
+ default: 0
2952
+ },
2953
+ {
2954
+ path: "daemon.crashDetectIntervalMs",
2955
+ schema: num,
2956
+ apply: "restart",
2957
+ env: "AGENTPROTO_CRASH_DETECT_INTERVAL_MS",
2958
+ writable: true,
2959
+ section: "daemon",
2960
+ label: "Crash-detect interval (ms)",
2961
+ help: "Crash-detect sweep interval. Default-on (non-destructive observability); a non-positive value disables it.",
2962
+ default: 3e4
2963
+ },
2964
+ {
2965
+ path: "daemon.restartSweepIntervalMs",
2966
+ schema: num,
2967
+ apply: "restart",
2968
+ env: "AGENTPROTO_RESTART_SWEEP_INTERVAL_MS",
2969
+ writable: true,
2970
+ section: "daemon",
2971
+ label: "Restart-sweep interval (ms)",
2972
+ help: "Sweep interval that executes already-scheduled session restarts. Off by default.",
2973
+ default: 0
2974
+ },
2975
+ {
2976
+ path: "daemon.turnStallAfterMs",
2977
+ schema: num,
2978
+ apply: "restart",
2979
+ env: "AGENTPROTO_TURN_STALL_AFTER_MS",
2980
+ writable: true,
2981
+ section: "daemon",
2982
+ label: "Turn-stall threshold (ms)",
2983
+ help: "Turn-liveness watchdog threshold. Default-on (non-destructive observability); a non-positive value disables it.",
2984
+ default: 3e5
2985
+ },
2986
+ {
2987
+ path: "sessions.eventsDir",
2988
+ schema: str,
2989
+ apply: "restart",
2990
+ writable: false,
2991
+ section: "daemon",
2992
+ label: "Sessions events dir",
2993
+ help: "Root directory for per-session transcripts. Lockout: existing transcripts do not move when this changes, and a bad path can break every session reader.",
2994
+ default: "~/.agentproto/sessions"
2995
+ },
2996
+ {
2997
+ path: "profiles",
2998
+ schema: z.record(str, profileConfigSchema),
2999
+ apply: "restart",
3000
+ writable: false,
3001
+ section: "daemon",
3002
+ label: "Connection profiles",
3003
+ help: "Named daemon/tunnel/features overrides selected via --profile. Lockout in v1: edit the file by hand."
3004
+ },
3005
+ {
3006
+ path: "activeProfile",
3007
+ schema: str,
3008
+ apply: "restart",
3009
+ writable: false,
3010
+ section: "daemon",
3011
+ label: "Active profile",
3012
+ help: "Profile used when --profile isn't passed. Lockout in v1: edit the file by hand."
3013
+ },
3014
+ // ── remote (tunnel + pairing) ──
3015
+ {
3016
+ path: "tunnel.host",
3017
+ schema: str,
3018
+ apply: "restart",
3019
+ writable: true,
3020
+ section: "remote",
3021
+ label: "Tunnel host",
3022
+ help: "Cloud WS URL. With autoconnect=true, `serve` bootstraps with --connect <host>."
3023
+ },
3024
+ {
3025
+ path: "tunnel.token",
3026
+ schema: str,
3027
+ apply: "restart",
3028
+ env: "AGENTPROTO_TOKEN",
3029
+ secret: true,
3030
+ writable: false,
3031
+ section: "remote",
3032
+ label: "Tunnel token",
3033
+ help: "Daemon token presented at the tunnel upgrade. Set via the CLI, never through config_set."
3034
+ },
3035
+ {
3036
+ path: "tunnel.autoconnect",
3037
+ schema: bool,
3038
+ apply: "restart",
3039
+ writable: true,
3040
+ section: "remote",
3041
+ label: "Tunnel autoconnect",
3042
+ help: "Whether `daemon start` connects the tunnel by default.",
3043
+ default: false
3044
+ },
3045
+ {
3046
+ path: "tunnel.e2e",
3047
+ schema: bool,
3048
+ apply: "restart",
3049
+ writable: true,
3050
+ section: "remote",
3051
+ label: "Tunnel end-to-end encryption",
3052
+ help: "Opt into E2E encryption of the outbound tunnel. Requires tunnel.token.",
3053
+ default: false
3054
+ },
3055
+ {
3056
+ path: "pairing.rendezvous",
3057
+ schema: str,
3058
+ apply: "restart",
3059
+ writable: true,
3060
+ section: "remote",
3061
+ label: "Pairing rendezvous URL",
3062
+ help: "Rendezvous broker WS URL used by pair_offer and autoconnect on boot."
3063
+ },
3064
+ {
3065
+ path: "pairing.autoconnect",
3066
+ schema: bool,
3067
+ apply: "restart",
3068
+ writable: true,
3069
+ section: "remote",
3070
+ label: "Pairing autoconnect",
3071
+ help: "Open standing rendezvous connections for every persisted pairing on boot.",
3072
+ default: true
3073
+ },
3074
+ {
3075
+ path: "pairing.pairPage",
3076
+ schema: pairPageSchema,
3077
+ // Read by `pair offer --qr` on every run, not at daemon boot.
3078
+ apply: "hot",
3079
+ writable: true,
3080
+ section: "remote",
3081
+ label: "Phone pair page",
3082
+ help: "Web page the `pair offer --qr` link opens (the offer rides in its URL fragment). A template with {fp} in the hostname gives one browser origin per daemon (the default, https://{fp}.agentproto.cloud/pair); a plain http(s) URL is one shared origin. `--pair-page` overrides it.",
3083
+ default: DEFAULT_PAIR_PAGE
3084
+ },
3085
+ // ── models ──
3086
+ {
3087
+ path: "titler.model",
3088
+ schema: str,
3089
+ apply: "hot",
3090
+ writable: true,
3091
+ section: "models",
3092
+ label: "Titler model",
3093
+ help: "OpenRouter model id used to generate session titles. Requires OPENROUTER_API_KEY.",
3094
+ default: DEFAULT_TITLER_MODEL
3095
+ },
3096
+ // ── defaults: spawn ──
3097
+ {
3098
+ path: "defaults.skills",
3099
+ schema: strArray,
3100
+ apply: "hot",
3101
+ writable: true,
3102
+ section: "defaults",
3103
+ label: "Default skills",
3104
+ help: "Skills auto-applied to every agent_start spawn (unioned with per-adapter skills)."
3105
+ },
3106
+ {
3107
+ path: "defaults.options",
3108
+ schema: optionsMapSchema,
3109
+ apply: "hot",
3110
+ writable: true,
3111
+ section: "defaults",
3112
+ label: "Default options",
3113
+ help: "AIP-45 options auto-applied to every agent_start spawn."
3114
+ },
3115
+ {
3116
+ path: "defaults.defaultRoleDepthCutoff",
3117
+ schema: num,
3118
+ apply: "hot",
3119
+ writable: true,
3120
+ section: "defaults",
3121
+ label: "Default role depth cutoff",
3122
+ help: "Spawn depth at/after which an agent_start with no explicit role defaults to executor rather than supervisor.",
3123
+ default: DEFAULT_ROLE_DEPTH_CUTOFF
3124
+ },
3125
+ {
3126
+ path: "defaults.maxGrantableDelegation",
3127
+ schema: num,
3128
+ apply: "hot",
3129
+ writable: true,
3130
+ section: "defaults",
3131
+ label: "Max grantable delegation",
3132
+ help: "Caps the delegation level a role pack may self-grant. Unset \u21D2 no cap."
3133
+ },
3134
+ {
3135
+ path: "defaults.spawn.browser",
3136
+ schema: spawnBrowserModeSchema,
3137
+ apply: "hot",
3138
+ writable: true,
3139
+ section: "defaults",
3140
+ label: "Default spawn browser mode",
3141
+ help: "Default agent_start.browser when neither the call, its role, nor its preset says otherwise.",
3142
+ default: false
3143
+ },
3144
+ {
3145
+ path: "worktrees.root",
3146
+ schema: str,
3147
+ apply: "hot",
3148
+ env: "AGENTPROTO_WORKTREES_ROOT",
3149
+ writable: true,
3150
+ section: "defaults",
3151
+ label: "Worktrees root",
3152
+ help: "Absolute path new `worktree new` worktrees are created under.",
3153
+ default: "~/.agentproto/worktrees"
3154
+ },
3155
+ {
3156
+ path: "worktrees.isolation",
3157
+ schema: z.enum(["always", "on-request", "never"]),
3158
+ apply: "hot",
3159
+ env: WORKTREE_ISOLATION_ENV,
3160
+ writable: true,
3161
+ section: "defaults",
3162
+ label: "Worktree isolation policy",
3163
+ help: "Whether a freshly-spawned agent_start session is isolated into its own git worktree.",
3164
+ default: DEFAULT_WORKTREE_ISOLATION
3165
+ },
3166
+ {
3167
+ path: "spawn.attach",
3168
+ schema: z.enum(["always", "on-request"]),
3169
+ apply: "hot",
3170
+ env: SPAWN_ATTACH_ENV,
3171
+ writable: true,
3172
+ section: "defaults",
3173
+ label: "Spawn attach policy",
3174
+ help: "Whether a spawn auto-attaches to its calling session as parent lineage.",
3175
+ default: DEFAULT_SPAWN_ATTACH
3176
+ },
3177
+ {
3178
+ path: "spawn.dedupe",
3179
+ schema: z.enum(["always", "on-request"]),
3180
+ apply: "hot",
3181
+ env: SPAWN_DEDUPE_ENV,
3182
+ writable: true,
3183
+ section: "defaults",
3184
+ label: "Spawn dedupe policy",
3185
+ help: "Whether agent_start derives an implicit idempotency key when the caller passes none.",
3186
+ default: DEFAULT_SPAWN_DEDUPE
3187
+ },
3188
+ {
3189
+ path: "provenance.wrapGh",
3190
+ schema: bool,
3191
+ apply: "hot",
3192
+ env: PROVENANCE_WRAP_GH_ENV,
3193
+ writable: true,
3194
+ section: "defaults",
3195
+ label: "gh provenance shim",
3196
+ help: "Opt-in gh PATH shim that stamps PR bodies with agentproto provenance.",
3197
+ default: DEFAULT_WRAP_GH
3198
+ },
3199
+ {
3200
+ path: "agentsMd.inlineMaxKb",
3201
+ schema: num,
3202
+ apply: "hot",
3203
+ writable: true,
3204
+ section: "defaults",
3205
+ label: "AGENTS.md inline threshold (KiB)",
3206
+ help: "A resolved AGENTS.md strictly under this size is inlined into the spawn prompt; at/above it, a pointer is injected instead.",
3207
+ default: DEFAULT_AGENTS_MD_INLINE_MAX_KB
3208
+ },
3209
+ // ── defaults: context continuity ──
3210
+ {
3211
+ path: "defaults.contextContinuity",
3212
+ schema: contextContinuityPolicySchema,
3213
+ apply: "hot",
3214
+ writable: true,
3215
+ section: "defaults",
3216
+ label: "Default context-continuity policy",
3217
+ help: "Global context-continuity policy fragment (mode + thresholds + checkpoint sections). Validated as one cross-field unit."
3218
+ },
3219
+ // ── defaults: observability ──
3220
+ {
3221
+ path: "titler.enabled",
3222
+ schema: bool,
3223
+ apply: "hot",
3224
+ writable: true,
3225
+ section: "defaults",
3226
+ label: "Session titler enabled",
3227
+ help: "Generate a short title from the first completed turn of eligible sessions. Default off.",
3228
+ default: false
3229
+ },
3230
+ {
3231
+ path: "defaults.langfuseTracing",
3232
+ schema: bool,
3233
+ apply: "restart",
3234
+ writable: true,
3235
+ section: "defaults",
3236
+ label: "Langfuse tracing default",
3237
+ help: "Default per-session Langfuse tracing opt-in when agent_start omits `trace`.",
3238
+ default: false
3239
+ },
3240
+ {
3241
+ path: "defaults.traceRedactor",
3242
+ schema: str,
3243
+ apply: "restart",
3244
+ writable: true,
3245
+ section: "defaults",
3246
+ label: "Trace redactor",
3247
+ help: "Redactor slug applied to traced session content before it reaches Langfuse.",
3248
+ default: "secrets"
3249
+ },
3250
+ {
3251
+ path: "sessions.attentionDelaySec",
3252
+ schema: num,
3253
+ apply: "hot",
3254
+ env: ATTENTION_DELAY_ENV,
3255
+ writable: true,
3256
+ section: "defaults",
3257
+ label: "Attention delay (s)",
3258
+ help: 'How long a session stays shown as "running" after its last turn ends before settling to attention/quiet.',
3259
+ default: DEFAULT_ATTENTION_DELAY_SEC
3260
+ },
3261
+ {
3262
+ path: "defaults.mcp.deferredTools",
3263
+ schema: deferredToolsConfigSchema,
3264
+ apply: "restart",
3265
+ writable: true,
3266
+ section: "defaults",
3267
+ label: "Deferred MCP tools",
3268
+ help: "Gateway-wide lazy tools/list loading. false/absent \u21D2 eager (today's behaviour).",
3269
+ default: false
3270
+ },
3271
+ {
3272
+ path: "defaults.backgroundTaskWake.enabled",
3273
+ schema: bool,
3274
+ apply: "restart",
3275
+ writable: true,
3276
+ section: "defaults",
3277
+ label: "Background-task wake enabled",
3278
+ help: "Wake an idle agent session when one of its background tasks settles.",
3279
+ default: true
3280
+ },
3281
+ {
3282
+ path: "defaults.backgroundTaskWake.graceMs",
3283
+ schema: num,
3284
+ apply: "restart",
3285
+ writable: true,
3286
+ section: "defaults",
3287
+ label: "Background-task wake grace (ms)",
3288
+ help: "How long the daemon waits for the agent to wake itself before prompting it.",
3289
+ default: DEFAULT_BG_TASK_WAKE_GRACE_MS
3290
+ },
3291
+ // ── defaults: messaging ──
3292
+ {
3293
+ path: "defaults.agentPromptInterrupt",
3294
+ schema: bool,
3295
+ apply: "hot",
3296
+ writable: true,
3297
+ section: "defaults",
3298
+ label: "Default agent_prompt interrupt",
3299
+ help: "Default `interrupt` applied when agent_prompt/message_parent leaves it unset. Takes effect on the next call.",
3300
+ default: false
3301
+ },
3302
+ {
3303
+ path: "defaults.messaging.allowSiblings",
3304
+ schema: bool,
3305
+ apply: "hot",
3306
+ writable: true,
3307
+ section: "defaults",
3308
+ label: "Allow sibling messaging",
3309
+ help: "Let message_send/message_reply reach a sibling session (same parent). Takes effect on the next call.",
3310
+ default: false
3311
+ },
3312
+ {
3313
+ path: "defaults.messaging.agentInterrupt",
3314
+ schema: z.enum(["allow", "deny"]),
3315
+ apply: "hot",
3316
+ writable: true,
3317
+ section: "defaults",
3318
+ label: "Agent-sender interrupt urgency",
3319
+ help: 'Whether a SESSION sender may use urgency "interrupt" (vs. downgraded to "steer"). Takes effect on the next call.',
3320
+ default: "deny"
3321
+ },
3322
+ // ── harnesses: per-adapter defaults ──
3323
+ {
3324
+ path: "defaults.adapters.*.skills",
3325
+ schema: strArray,
3326
+ apply: "hot",
3327
+ writable: true,
3328
+ section: "harnesses",
3329
+ label: "Adapter default skills",
3330
+ help: "Skills auto-applied to spawns of this adapter (unioned with the global default)."
3331
+ },
3332
+ {
3333
+ path: "defaults.adapters.*.options",
3334
+ schema: optionsMapSchema,
3335
+ apply: "hot",
3336
+ writable: true,
3337
+ section: "harnesses",
3338
+ label: "Adapter default options",
3339
+ help: "AIP-45 options auto-applied to spawns of this adapter."
3340
+ },
3341
+ {
3342
+ path: "defaults.adapters.*.contextContinuity",
3343
+ schema: contextContinuityPolicySchema,
3344
+ apply: "hot",
3345
+ writable: true,
3346
+ section: "harnesses",
3347
+ label: "Adapter context-continuity policy",
3348
+ help: "Per-adapter context-continuity policy fragment, overriding the global default. Validated as one cross-field unit."
3349
+ },
3350
+ {
3351
+ path: "defaults.adapters.*.auth.mode",
3352
+ schema: z.enum(["subscription", "api-key"]),
3353
+ apply: "hot",
3354
+ writable: true,
3355
+ section: "harnesses",
3356
+ label: "Adapter auth mode",
3357
+ help: '"subscription" or "api-key". Omitted \u21D2 resolver picks by ordered preference.'
3358
+ },
3359
+ {
3360
+ path: "defaults.adapters.*.auth.token",
3361
+ schema: str,
3362
+ apply: "hot",
3363
+ secret: true,
3364
+ writable: false,
3365
+ section: "harnesses",
3366
+ label: "Adapter subscription token",
3367
+ help: "Subscription bearer token. Wallet secrets belong in auth profiles; this field is not writable through config_set."
3368
+ },
3369
+ {
3370
+ path: "defaults.adapters.*.auth.source",
3371
+ schema: str,
3372
+ apply: "hot",
3373
+ writable: true,
3374
+ section: "harnesses",
3375
+ label: "Adapter subscription source",
3376
+ help: 'Opt-in self-refreshing subscription source, e.g. "claude-code-oauth".'
3377
+ },
3378
+ {
3379
+ path: "defaults.adapters.*.auth.apiKey",
3380
+ schema: str,
3381
+ apply: "hot",
3382
+ secret: true,
3383
+ writable: false,
3384
+ section: "harnesses",
3385
+ label: "Adapter API key",
3386
+ help: 'Explicit API key for "api-key" mode. Wallet secrets belong in auth profiles; this field is not writable through config_set.'
3387
+ },
3388
+ {
3389
+ path: "defaults.adapters.*.auth.provider",
3390
+ schema: CatalogProviderSchema,
3391
+ apply: "hot",
3392
+ writable: true,
3393
+ section: "harnesses",
3394
+ label: "Adapter auth provider pin",
3395
+ help: "Per-adapter provider pin, overriding the adapter's fixed/model-derived provider."
3396
+ },
3397
+ // ── harnesses: user ACP agents ──
3398
+ //
3399
+ // Every leaf here is `writable: false`: these fields name a BINARY, argv,
3400
+ // and environment that the daemon executes on the host, the same reasoning
3401
+ // that keeps `adapter_install` off the app surface (PLAN.md Q6). The write
3402
+ // path in v1 is the CLI (`agentproto acp add/rm`, which is exempt from this
3403
+ // flag entirely) or a hand edit, never a future config_set.
3404
+ {
3405
+ path: "acpAgents.*.name",
3406
+ schema: str,
3407
+ apply: "hot",
3408
+ writable: false,
3409
+ section: "harnesses",
3410
+ label: "ACP agent display name",
3411
+ help: "Display name. Defaults to the slug when omitted. Set via `agentproto acp add`."
3412
+ },
3413
+ {
3414
+ path: "acpAgents.*.description",
3415
+ schema: str,
3416
+ apply: "hot",
3417
+ writable: false,
3418
+ section: "harnesses",
3419
+ label: "ACP agent description",
3420
+ help: "One-line description surfaced in `acp ls`. Set via `agentproto acp add`."
3421
+ },
3422
+ {
3423
+ path: "acpAgents.*.bin",
3424
+ schema: str,
3425
+ apply: "hot",
3426
+ writable: false,
3427
+ section: "harnesses",
3428
+ label: "ACP agent executable",
3429
+ help: "Executable the daemon spawns on the host. Set via `agentproto acp add`; not writable through config_set."
3430
+ },
3431
+ {
3432
+ path: "acpAgents.*.bin_args",
3433
+ schema: strArray,
3434
+ apply: "hot",
3435
+ writable: false,
3436
+ section: "harnesses",
3437
+ label: "ACP agent argv",
3438
+ help: "Extra argv appended after `bin`. Set via `agentproto acp add`; not writable through config_set."
3439
+ },
3440
+ {
3441
+ path: "acpAgents.*.env",
3442
+ schema: z.record(str, str),
3443
+ apply: "hot",
3444
+ secret: true,
3445
+ writable: false,
3446
+ section: "harnesses",
3447
+ label: "ACP agent environment",
3448
+ help: "Extra environment variables for the spawned process; values may hold secrets, so a reader redacts them. Set via `agentproto acp add`; not writable through config_set."
3449
+ },
3450
+ {
3451
+ path: "acpAgents.*.cwd_flag",
3452
+ schema: str,
3453
+ apply: "hot",
3454
+ writable: false,
3455
+ section: "harnesses",
3456
+ label: "ACP agent cwd flag",
3457
+ help: "Flag the CLI uses to receive the working directory, if it needs one passed explicitly. Set via `agentproto acp add`."
3458
+ },
3459
+ {
3460
+ path: "acpAgents.*.resumable",
3461
+ schema: bool,
3462
+ apply: "hot",
3463
+ writable: false,
3464
+ section: "harnesses",
3465
+ label: "ACP agent resumable",
3466
+ help: "Advertise resumable + native-resume continuation. Set via `agentproto acp add`."
3467
+ },
3468
+ {
3469
+ path: "acpAgents.*.models.default",
3470
+ schema: str,
3471
+ apply: "hot",
3472
+ writable: false,
3473
+ section: "harnesses",
3474
+ label: "ACP agent default model",
3475
+ help: "Known default model id (informational + validation hint). Set via `agentproto acp add`."
3476
+ },
3477
+ {
3478
+ path: "acpAgents.*.models.allowed",
3479
+ schema: strArray,
3480
+ apply: "hot",
3481
+ writable: false,
3482
+ section: "harnesses",
3483
+ label: "ACP agent allowed models",
3484
+ help: "Known allowed model ids (informational + validation hint). Set via `agentproto acp add`."
3485
+ },
3486
+ {
3487
+ path: "acpAgents.*.provider",
3488
+ schema: str,
3489
+ apply: "hot",
3490
+ writable: false,
3491
+ section: "harnesses",
3492
+ label: "ACP agent billing provider",
3493
+ help: "Billing endpoint this CLI's own auth bills, if a single known one applies. Set via `agentproto acp add`."
3494
+ },
3495
+ {
3496
+ path: "acpAgents.*.install_hint",
3497
+ schema: str,
3498
+ apply: "hot",
3499
+ writable: false,
3500
+ section: "harnesses",
3501
+ label: "ACP agent install hint",
3502
+ help: "Shown when `bin` is missing from PATH. Set via `agentproto acp add`."
3503
+ },
3504
+ // ── advanced: terminal/TUI presets ──
3505
+ //
3506
+ // Same reasoning as the ACP agents above: `writable: false` on every leaf,
3507
+ // since these name a command, argv, and environment run on the host. The
3508
+ // write path in v1 is `agentproto config set` (exempt from this flag) or a
3509
+ // hand edit, never a future config_set.
3510
+ {
3511
+ path: "terminalPresets.*.argv",
3512
+ schema: strArray,
3513
+ apply: "hot",
3514
+ writable: false,
3515
+ section: "advanced",
3516
+ label: "Terminal preset argv",
3517
+ help: "Command + args to spawn. Not writable through config_set; set via `agentproto config set` or a hand edit."
3518
+ },
3519
+ {
3520
+ path: "terminalPresets.*.env",
3521
+ schema: z.record(str, str),
3522
+ apply: "hot",
3523
+ secret: true,
3524
+ writable: false,
3525
+ section: "advanced",
3526
+ label: "Terminal preset environment",
3527
+ help: "Extra environment variables layered on the daemon's inherited env; values may hold secrets, so a reader redacts them. Not writable through config_set."
3528
+ },
3529
+ {
3530
+ path: "terminalPresets.*.cwd",
3531
+ schema: str,
3532
+ apply: "hot",
3533
+ writable: false,
3534
+ section: "advanced",
3535
+ label: "Terminal preset cwd",
3536
+ help: "Working directory for the PTY session. Not writable through config_set."
3537
+ },
3538
+ {
3539
+ path: "terminalPresets.*.workspace",
3540
+ schema: str,
3541
+ apply: "hot",
3542
+ writable: false,
3543
+ section: "advanced",
3544
+ label: "Terminal preset workspace",
3545
+ help: "Workspace slug used for cwd fallback when `cwd` is omitted. Not writable through config_set."
3546
+ },
3547
+ {
3548
+ path: "terminalPresets.*.name",
3549
+ schema: str,
3550
+ apply: "hot",
3551
+ writable: false,
3552
+ section: "advanced",
3553
+ label: "Terminal preset session name",
3554
+ help: "Stable session name passed to the registry. Not writable through config_set."
3555
+ },
3556
+ {
3557
+ path: "terminalPresets.*.label",
3558
+ schema: str,
3559
+ apply: "hot",
3560
+ writable: false,
3561
+ section: "advanced",
3562
+ label: "Terminal preset label",
3563
+ help: "Human-readable label surfaced in session listings. Not writable through config_set."
3564
+ },
3565
+ // ── daemon-adjacent (no dedicated UI section yet — features toggles) ──
3566
+ {
3567
+ path: "features.pty",
3568
+ schema: bool,
3569
+ apply: "restart",
3570
+ writable: true,
3571
+ section: "daemon",
3572
+ label: "PTY feature hint",
3573
+ help: "Informational hint that PTY is desired; the daemon still detects node-pty's presence at runtime."
3574
+ },
3575
+ {
3576
+ path: "features.llmEndpoint",
3577
+ schema: bool,
3578
+ apply: "restart",
3579
+ writable: true,
3580
+ section: "daemon",
3581
+ label: "LLM Endpoint feature",
3582
+ help: "Enable the local LLM Endpoint proxy sidecar (route + MCP tools + child-process lifecycle).",
3583
+ default: false
3584
+ }
3585
+ ];
3586
+ }
3587
+ });
11
3588
  function sanitizeAcpAgents(raw, target) {
12
3589
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
13
3590
  console.warn(
@@ -27,6 +3604,9 @@ function sanitizeAcpAgents(raw, target) {
27
3604
  }
28
3605
  return Object.keys(out).length > 0 ? out : void 0;
29
3606
  }
3607
+ function hashConfigContent(raw) {
3608
+ return createHash("sha256").update(raw, "utf8").digest("hex");
3609
+ }
30
3610
  async function loadConfig(path) {
31
3611
  const target = path ?? CONFIG_FILE_PATH();
32
3612
  try {
@@ -37,6 +3617,18 @@ async function loadConfig(path) {
37
3617
  if (cfg.acpAgents !== void 0) {
38
3618
  cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target);
39
3619
  }
3620
+ const validation = validateConfig(cfg);
3621
+ if (!validation.ok) {
3622
+ const contentHash = hashConfigContent(raw);
3623
+ if (warnedInvalidConfigHashes.get(target) !== contentHash) {
3624
+ warnedInvalidConfigHashes.set(target, contentHash);
3625
+ const shown = validation.issues.slice(0, 5);
3626
+ const more = validation.issues.length > shown.length ? ` (+${validation.issues.length - shown.length} more)` : "";
3627
+ console.warn(
3628
+ `[runtime/config] ${target}: ${validation.issues.length} schema issue(s): ${shown.join("; ")}${more}`
3629
+ );
3630
+ }
3631
+ }
40
3632
  return cfg;
41
3633
  }
42
3634
  console.warn(
@@ -53,11 +3645,16 @@ async function loadConfig(path) {
53
3645
  return {};
54
3646
  }
55
3647
  }
3648
+ var CONFIG_FILE_PATH, warnedInvalidConfigHashes;
3649
+ var init_config = __esm({
3650
+ "src/config.ts"() {
3651
+ init_config_schema();
3652
+ CONFIG_FILE_PATH = () => join(homedir(), ".agentproto", "config.json");
3653
+ warnedInvalidConfigHashes = /* @__PURE__ */ new Map();
3654
+ }
3655
+ });
56
3656
 
57
3657
  // src/session-presence.ts
58
- var ATTENTION_DELAY_ENV = "AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC";
59
- var DEFAULT_ATTENTION_DELAY_SEC = 60;
60
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["exited", "killed", "error"]);
61
3658
  function inAttentionGrace(s, opts) {
62
3659
  const delaySec = opts?.attentionDelaySec ?? DEFAULT_ATTENTION_DELAY_SEC;
63
3660
  if (delaySec <= 0) return false;
@@ -97,6 +3694,16 @@ async function resolveAttentionDelaySec(loadCfg = loadConfig) {
97
3694
  }
98
3695
  return DEFAULT_ATTENTION_DELAY_SEC;
99
3696
  }
3697
+ var ATTENTION_DELAY_ENV, DEFAULT_ATTENTION_DELAY_SEC, TERMINAL_STATUSES;
3698
+ var init_session_presence = __esm({
3699
+ "src/session-presence.ts"() {
3700
+ init_config();
3701
+ ATTENTION_DELAY_ENV = "AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC";
3702
+ DEFAULT_ATTENTION_DELAY_SEC = 60;
3703
+ TERMINAL_STATUSES = /* @__PURE__ */ new Set(["exited", "killed", "error"]);
3704
+ }
3705
+ });
3706
+ init_session_presence();
100
3707
 
101
3708
  export { ATTENTION_DELAY_ENV, DEFAULT_ATTENTION_DELAY_SEC, inAttentionGrace, presenceFor, resolveAttentionDelaySec };
102
3709
  //# sourceMappingURL=session-presence.mjs.map