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