@agentproto/runtime 0.4.0 → 0.6.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/LICENSE +202 -21
- package/README.md +2 -1
- package/dist/config.d.ts +130 -1
- package/dist/config.mjs +24 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +944 -33
- package/dist/index.mjs +5490 -707
- package/dist/index.mjs.map +1 -1
- package/dist/providers-store.d.ts +1 -57
- package/dist/providers-store.mjs +1 -84
- package/dist/providers-store.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +18 -5
- package/dist/resume-strategies.mjs +14 -2
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-story.d.ts +113 -0
- package/dist/session-story.mjs +314 -0
- package/dist/session-story.mjs.map +1 -0
- package/dist/spawn-defaults-DAbADRd4.d.ts +256 -0
- package/dist/workspaces-config.d.ts +5 -1
- package/dist/workspaces-config.mjs +6 -1
- package/dist/workspaces-config.mjs.map +1 -1
- package/package.json +29 -8
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentproto/runtime v0.1.0-alpha
|
|
3
|
+
* Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// src/tool-presenter.ts
|
|
7
|
+
var SALIENT_ARG_KEYS = [
|
|
8
|
+
"file_path",
|
|
9
|
+
"path",
|
|
10
|
+
"filePath",
|
|
11
|
+
"file",
|
|
12
|
+
"command",
|
|
13
|
+
"pattern",
|
|
14
|
+
"query",
|
|
15
|
+
"q",
|
|
16
|
+
"url",
|
|
17
|
+
"todos",
|
|
18
|
+
"description",
|
|
19
|
+
"prompt"
|
|
20
|
+
];
|
|
21
|
+
var MAX_CALL_LENGTH = 120;
|
|
22
|
+
var MAX_RESULT_LENGTH = 160;
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function truncate(value, max) {
|
|
27
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
28
|
+
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}\u2026` : oneLine;
|
|
29
|
+
}
|
|
30
|
+
function formatArgValue(value) {
|
|
31
|
+
if (typeof value === "string") return value;
|
|
32
|
+
if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? "" : "s"}`;
|
|
33
|
+
if (isRecord(value)) return JSON.stringify(value);
|
|
34
|
+
return String(value);
|
|
35
|
+
}
|
|
36
|
+
function pickSalientArg(args) {
|
|
37
|
+
for (const key of SALIENT_ARG_KEYS) {
|
|
38
|
+
const value = args[key];
|
|
39
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
40
|
+
return formatArgValue(value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function subagentSummary(args) {
|
|
46
|
+
const description = typeof args.description === "string" ? args.description : typeof args.prompt === "string" ? args.prompt : "subagent";
|
|
47
|
+
return `\u21B3 subagent: ${truncate(description, MAX_CALL_LENGTH)}`;
|
|
48
|
+
}
|
|
49
|
+
var BESPOKE_TOOLS = {
|
|
50
|
+
schedulewakeup: (args) => {
|
|
51
|
+
const delay = args.delaySeconds ?? args.delay ?? "?";
|
|
52
|
+
const reason = typeof args.reason === "string" ? args.reason : "";
|
|
53
|
+
return reason ? `\u23F0 wake in ${delay}s \u2014 ${reason}` : `\u23F0 wake in ${delay}s`;
|
|
54
|
+
},
|
|
55
|
+
task: subagentSummary,
|
|
56
|
+
agent: subagentSummary,
|
|
57
|
+
todowrite: (args) => {
|
|
58
|
+
const todos = Array.isArray(args.todos) ? args.todos : [];
|
|
59
|
+
return `\u2611 todos (${todos.length})`;
|
|
60
|
+
},
|
|
61
|
+
exitplanmode: () => "\u{1F4CB} plan ready"
|
|
62
|
+
};
|
|
63
|
+
function formatToolCall(toolName, args) {
|
|
64
|
+
const name = toolName || "tool";
|
|
65
|
+
const argsRecord = isRecord(args) ? args : {};
|
|
66
|
+
const bespoke = BESPOKE_TOOLS[name.toLowerCase()];
|
|
67
|
+
if (bespoke) return bespoke(argsRecord);
|
|
68
|
+
const salient = pickSalientArg(argsRecord);
|
|
69
|
+
if (salient !== null) {
|
|
70
|
+
if (name.toLowerCase().includes(salient.toLowerCase())) {
|
|
71
|
+
return truncate(name, MAX_CALL_LENGTH);
|
|
72
|
+
}
|
|
73
|
+
return truncate(`${name} ${salient}`, MAX_CALL_LENGTH);
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(argsRecord).length === 0) return name;
|
|
76
|
+
return truncate(`${name} ${JSON.stringify(args)}`, MAX_CALL_LENGTH);
|
|
77
|
+
}
|
|
78
|
+
function extractText(value) {
|
|
79
|
+
if (value == null) return null;
|
|
80
|
+
if (typeof value === "string") return value;
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
const parts = value.map(extractText).filter((v) => v != null);
|
|
83
|
+
return parts.length ? parts.join("\n") : null;
|
|
84
|
+
}
|
|
85
|
+
if (isRecord(value)) {
|
|
86
|
+
if (typeof value.text === "string") return value.text;
|
|
87
|
+
if (typeof value.message === "string") return value.message;
|
|
88
|
+
if (Array.isArray(value.content)) return extractText(value.content);
|
|
89
|
+
if (typeof value.error === "string") return value.error;
|
|
90
|
+
if (isRecord(value.error) && typeof value.error.message === "string") {
|
|
91
|
+
return value.error.message;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function formatToolResult(toolName, result, isError) {
|
|
98
|
+
const text = extractText(result);
|
|
99
|
+
if (isError) {
|
|
100
|
+
const message = text ?? (result != null ? JSON.stringify(result) : "failed");
|
|
101
|
+
const firstLine = message.split(/\r?\n/)[0] ?? message;
|
|
102
|
+
return truncate(firstLine, MAX_RESULT_LENGTH);
|
|
103
|
+
}
|
|
104
|
+
if (text == null) return null;
|
|
105
|
+
const trimmed = text.trim();
|
|
106
|
+
if (!trimmed) return null;
|
|
107
|
+
const lines = trimmed.split(/\r?\n/);
|
|
108
|
+
if (lines.length > 1) {
|
|
109
|
+
const bytes = Buffer.byteLength(trimmed, "utf8");
|
|
110
|
+
return `${lines.length} lines, ${bytes}B`;
|
|
111
|
+
}
|
|
112
|
+
return truncate(lines[0], MAX_RESULT_LENGTH);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/session-story.ts
|
|
116
|
+
function formatTs(ts) {
|
|
117
|
+
if (ts === void 0 || Number.isNaN(ts)) return "";
|
|
118
|
+
return new Date(ts).toISOString().slice(11, 19);
|
|
119
|
+
}
|
|
120
|
+
function firstMeaningfulLine(text) {
|
|
121
|
+
if (!text) return void 0;
|
|
122
|
+
const line = text.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
123
|
+
return line;
|
|
124
|
+
}
|
|
125
|
+
function truncate2(text, max) {
|
|
126
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
127
|
+
}
|
|
128
|
+
function parseArgs(argsJson) {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(argsJson);
|
|
131
|
+
} catch {
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function lineCount(text) {
|
|
136
|
+
return text.split("\n").filter((l) => l.trim().length > 0).length || 1;
|
|
137
|
+
}
|
|
138
|
+
function classifyKind(toolCalls) {
|
|
139
|
+
if (!toolCalls || toolCalls.length === 0) return "text";
|
|
140
|
+
const names = toolCalls.map((t) => t.name.toLowerCase());
|
|
141
|
+
if (names.some((n) => /edit|write/.test(n))) return "edit";
|
|
142
|
+
if (names.some((n) => /bash|terminal|command/.test(n))) return "bash";
|
|
143
|
+
if (names.some((n) => /read|grep|glob/.test(n))) return "read";
|
|
144
|
+
return "text";
|
|
145
|
+
}
|
|
146
|
+
var NEW_CHAPTER_RE = /\b(aussi|autre|ensuite|nouveau|nouvelle|plut[oô]t|maintenant|apr[eè]s ça|il faudrait|peux[- ]tu|on pourrait|ajoute|g[eè]re)\b/iu;
|
|
147
|
+
function classifyRoute(text) {
|
|
148
|
+
const newt = NEW_CHAPTER_RE.test(text);
|
|
149
|
+
if (!newt) return { route: "cont" };
|
|
150
|
+
const title = text.replace(/[.?!].*$/, "").slice(0, 42);
|
|
151
|
+
return { route: "newt", title };
|
|
152
|
+
}
|
|
153
|
+
function foldToolStep(assistant, toolResults) {
|
|
154
|
+
const toolCalls = assistant.toolCalls ?? [];
|
|
155
|
+
const kind = classifyKind(toolCalls);
|
|
156
|
+
const count = toolCalls.length || 1;
|
|
157
|
+
const items = [];
|
|
158
|
+
const facts = [];
|
|
159
|
+
if (assistant.text?.trim()) items.push({ text: assistant.text.trim() });
|
|
160
|
+
toolCalls.forEach((tc, i) => {
|
|
161
|
+
const args = parseArgs(tc.args);
|
|
162
|
+
const h = formatToolCall(tc.name, args);
|
|
163
|
+
const resultMsg = toolResults[i];
|
|
164
|
+
const resultText = resultMsg?.text ?? "";
|
|
165
|
+
const isError = resultText.startsWith("[error]");
|
|
166
|
+
const r = isError ? resultText.slice("[error]".length).trim() : resultText;
|
|
167
|
+
items.push({ h, r });
|
|
168
|
+
const fact = formatToolResult(tc.name, r, isError);
|
|
169
|
+
if (fact) facts.push(fact);
|
|
170
|
+
});
|
|
171
|
+
const firstLine = firstMeaningfulLine(assistant.text);
|
|
172
|
+
const firstToolCall = toolCalls[0];
|
|
173
|
+
const sum = firstLine ?? (firstToolCall ? formatToolCall(firstToolCall.name, parseArgs(firstToolCall.args)) : "\u2026");
|
|
174
|
+
let raw1;
|
|
175
|
+
if (toolCalls.length === 0) {
|
|
176
|
+
raw1 = `assistant \xB7 ${lineCount(assistant.text ?? "")} ligne(s)`;
|
|
177
|
+
} else if (toolCalls.length === 1 && firstToolCall) {
|
|
178
|
+
raw1 = formatToolCall(firstToolCall.name, parseArgs(firstToolCall.args));
|
|
179
|
+
} else {
|
|
180
|
+
raw1 = `${firstToolCall?.name ?? "tool"} \xD7${toolCalls.length}`;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
kind,
|
|
184
|
+
ts: formatTs(assistant.ts),
|
|
185
|
+
sum,
|
|
186
|
+
raw1,
|
|
187
|
+
count,
|
|
188
|
+
facts,
|
|
189
|
+
items
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function foldUserStep(msg) {
|
|
193
|
+
const text = msg.text ?? "";
|
|
194
|
+
const sum = `\xAB ${truncate2(text, 80)} \xBB`;
|
|
195
|
+
return {
|
|
196
|
+
kind: "user",
|
|
197
|
+
ts: formatTs(msg.ts),
|
|
198
|
+
sum,
|
|
199
|
+
raw1: `user \xB7 ${lineCount(text)} ligne(s)`,
|
|
200
|
+
count: 1,
|
|
201
|
+
facts: [],
|
|
202
|
+
items: [{ text }],
|
|
203
|
+
userText: text
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function foldOrphanToolStep(msg) {
|
|
207
|
+
const text = msg.text ?? "";
|
|
208
|
+
const isError = text.startsWith("[error]");
|
|
209
|
+
const r = isError ? text.slice("[error]".length).trim() : text;
|
|
210
|
+
const name = msg.toolName ?? "tool";
|
|
211
|
+
const fact = formatToolResult(name, r, isError);
|
|
212
|
+
return {
|
|
213
|
+
kind: classifyKind([{ name }]),
|
|
214
|
+
ts: formatTs(msg.ts),
|
|
215
|
+
sum: msg.toolName ? `${msg.toolName} \xB7 r\xE9sultat` : "R\xE9sultat d'outil",
|
|
216
|
+
raw1: msg.toolName ?? "tool",
|
|
217
|
+
count: 1,
|
|
218
|
+
facts: fact ? [fact] : [],
|
|
219
|
+
items: [{ h: name, r }]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function foldSystemStep(msg) {
|
|
223
|
+
const text = msg.text ?? "";
|
|
224
|
+
return {
|
|
225
|
+
kind: "text",
|
|
226
|
+
ts: formatTs(msg.ts),
|
|
227
|
+
sum: firstMeaningfulLine(text) ?? text,
|
|
228
|
+
raw1: "system",
|
|
229
|
+
count: 1,
|
|
230
|
+
facts: [],
|
|
231
|
+
items: text ? [{ text }] : []
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function foldMessages(messages) {
|
|
235
|
+
const steps = [];
|
|
236
|
+
let i = 0;
|
|
237
|
+
while (i < messages.length) {
|
|
238
|
+
const msg = messages[i];
|
|
239
|
+
if (msg.role === "user") {
|
|
240
|
+
steps.push(foldUserStep(msg));
|
|
241
|
+
i += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (msg.role === "assistant") {
|
|
245
|
+
let j = i + 1;
|
|
246
|
+
const toolResults = [];
|
|
247
|
+
while (j < messages.length && messages[j].role === "tool") {
|
|
248
|
+
toolResults.push(messages[j]);
|
|
249
|
+
j += 1;
|
|
250
|
+
}
|
|
251
|
+
steps.push(foldToolStep(msg, toolResults));
|
|
252
|
+
i = j;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (msg.role === "tool") {
|
|
256
|
+
steps.push(foldOrphanToolStep(msg));
|
|
257
|
+
i += 1;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
steps.push(foldSystemStep(msg));
|
|
261
|
+
i += 1;
|
|
262
|
+
}
|
|
263
|
+
return steps;
|
|
264
|
+
}
|
|
265
|
+
function buildStory(messages) {
|
|
266
|
+
const folded = foldMessages(messages);
|
|
267
|
+
const chapters = [];
|
|
268
|
+
const steps = [];
|
|
269
|
+
let currentChapterId;
|
|
270
|
+
let sawFirstUser = false;
|
|
271
|
+
const closeCurrent = () => {
|
|
272
|
+
const cur = chapters.find((c) => c.id === currentChapterId);
|
|
273
|
+
if (cur) cur.status = "done";
|
|
274
|
+
};
|
|
275
|
+
const openChapter = (title) => {
|
|
276
|
+
const id = `c${chapters.length + 1}`;
|
|
277
|
+
chapters.push({ id, title, status: "cur" });
|
|
278
|
+
return id;
|
|
279
|
+
};
|
|
280
|
+
for (const step of folded) {
|
|
281
|
+
let route;
|
|
282
|
+
if (step.kind === "user" && step.userText !== void 0) {
|
|
283
|
+
if (!sawFirstUser) {
|
|
284
|
+
sawFirstUser = true;
|
|
285
|
+
currentChapterId = openChapter("Cadrage");
|
|
286
|
+
} else {
|
|
287
|
+
const verdict = classifyRoute(step.userText);
|
|
288
|
+
route = verdict.route;
|
|
289
|
+
if (verdict.route === "newt") {
|
|
290
|
+
closeCurrent();
|
|
291
|
+
currentChapterId = openChapter(verdict.title || "Nouvelle sous-t\xE2che");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else if (currentChapterId === void 0) {
|
|
295
|
+
currentChapterId = openChapter("Cadrage");
|
|
296
|
+
}
|
|
297
|
+
steps.push({
|
|
298
|
+
chap: currentChapterId,
|
|
299
|
+
kind: step.kind,
|
|
300
|
+
ts: step.ts,
|
|
301
|
+
sum: step.sum,
|
|
302
|
+
raw1: step.raw1,
|
|
303
|
+
count: step.count,
|
|
304
|
+
facts: step.facts,
|
|
305
|
+
items: step.items,
|
|
306
|
+
...route ? { route } : {}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return { chapters, steps };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export { buildStory, classifyKind, classifyRoute };
|
|
313
|
+
//# sourceMappingURL=session-story.mjs.map
|
|
314
|
+
//# sourceMappingURL=session-story.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tool-presenter.ts","../src/session-story.ts"],"names":["truncate"],"mappings":"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","file":"session-story.mjs","sourcesContent":["/**\n * Turns a raw tool-call name + args (and its eventual result) into a short,\n * human-readable line for ring-buffer / CLI / transcript rendering. Without\n * this, every tool call renders as a bare `[tool] view` with no args and no\n * outcome — this module is the one place that knows how to summarize both.\n */\n\nconst SALIENT_ARG_KEYS = [\n \"file_path\",\n \"path\",\n \"filePath\",\n \"file\",\n \"command\",\n \"pattern\",\n \"query\",\n \"q\",\n \"url\",\n \"todos\",\n \"description\",\n \"prompt\",\n] as const\n\nconst MAX_CALL_LENGTH = 120\nconst MAX_RESULT_LENGTH = 160\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction truncate(value: string, max: number): string {\n const oneLine = value.replace(/\\s+/g, \" \").trim()\n return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine\n}\n\nfunction formatArgValue(value: unknown): string {\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? \"\" : \"s\"}`\n if (isRecord(value)) return JSON.stringify(value)\n return String(value)\n}\n\nfunction pickSalientArg(args: Record<string, unknown>): string | null {\n for (const key of SALIENT_ARG_KEYS) {\n const value = args[key]\n if (value !== undefined && value !== null && value !== \"\") {\n return formatArgValue(value)\n }\n }\n return null\n}\n\ntype BespokeFormatter = (args: Record<string, unknown>) => string\n\nfunction subagentSummary(args: Record<string, unknown>): string {\n const description =\n typeof args.description === \"string\"\n ? args.description\n : typeof args.prompt === \"string\"\n ? args.prompt\n : \"subagent\"\n return `↳ subagent: ${truncate(description, MAX_CALL_LENGTH)}`\n}\n\n// Bespoke one-liners for control tools where the generic arg-sniffing\n// below would either miss the point (ScheduleWakeup's payload isn't a\n// file/command/pattern) or just repeat the tool name pointlessly\n// (TodoWrite, ExitPlanMode carry no salient single-value arg).\nconst BESPOKE_TOOLS: Record<string, BespokeFormatter> = {\n schedulewakeup: (args) => {\n const delay = args.delaySeconds ?? args.delay ?? \"?\"\n const reason = typeof args.reason === \"string\" ? args.reason : \"\"\n return reason ? `⏰ wake in ${delay}s — ${reason}` : `⏰ wake in ${delay}s`\n },\n task: subagentSummary,\n agent: subagentSummary,\n todowrite: (args) => {\n const todos = Array.isArray(args.todos) ? args.todos : []\n return `☑ todos (${todos.length})`\n },\n exitplanmode: () => \"📋 plan ready\",\n}\n\n/** A one-line human summary of a tool call, e.g. `read src/foo.ts` or `⏰ wake in 30s — checking CI`. */\nexport function formatToolCall(toolName: string, args: unknown): string {\n const name = toolName || \"tool\"\n const argsRecord = isRecord(args) ? args : {}\n\n const bespoke = BESPOKE_TOOLS[name.toLowerCase()]\n if (bespoke) return bespoke(argsRecord)\n\n const salient = pickSalientArg(argsRecord)\n if (salient !== null) {\n // Some ACP agents (e.g. claude-agent-acp) already bake the salient\n // value into a curated title — \"Read src/foo.ts\" alongside\n // arguments.path === \"src/foo.ts\". Appending it again would render\n // \"Read src/foo.ts src/foo.ts\". Only append when it adds new info.\n if (name.toLowerCase().includes(salient.toLowerCase())) {\n return truncate(name, MAX_CALL_LENGTH)\n }\n return truncate(`${name} ${salient}`, MAX_CALL_LENGTH)\n }\n\n if (Object.keys(argsRecord).length === 0) return name\n\n return truncate(`${name} ${JSON.stringify(args)}`, MAX_CALL_LENGTH)\n}\n\nfunction extractText(value: unknown): string | null {\n if (value == null) return null\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) {\n const parts = value.map(extractText).filter((v): v is string => v != null)\n return parts.length ? parts.join(\"\\n\") : null\n }\n if (isRecord(value)) {\n if (typeof value.text === \"string\") return value.text\n if (typeof value.message === \"string\") return value.message\n if (Array.isArray(value.content)) return extractText(value.content)\n if (typeof value.error === \"string\") return value.error\n if (isRecord(value.error) && typeof value.error.message === \"string\") {\n return value.error.message\n }\n return null\n }\n return null\n}\n\n/**\n * A short outcome line for a completed tool call, or `null` when there's\n * nothing useful to show (e.g. an empty/void result). `toolName` is\n * accepted for parity with `formatToolCall` and future bespoke result\n * formatting, but generic text extraction covers today's cases.\n */\nexport function formatToolResult(\n toolName: string | undefined,\n result: unknown,\n isError: boolean,\n): string | null {\n void toolName\n const text = extractText(result)\n\n if (isError) {\n const message = text ?? (result != null ? JSON.stringify(result) : \"failed\")\n const firstLine = message.split(/\\r?\\n/)[0] ?? message\n return truncate(firstLine, MAX_RESULT_LENGTH)\n }\n\n if (text == null) return null\n const trimmed = text.trim()\n if (!trimmed) return null\n\n const lines = trimmed.split(/\\r?\\n/)\n if (lines.length > 1) {\n const bytes = Buffer.byteLength(trimmed, \"utf8\")\n return `${lines.length} lines, ${bytes}B`\n }\n return truncate(lines[0]!, MAX_RESULT_LENGTH)\n}\n","/**\n * Pure heuristic module that folds an exported session's messages into a\n * \"story\": chapters (inferred sub-tasks) + steps (one per fixed-height feed\n * row). Backs the `agentproto_session_story` MCP app panel.\n *\n * v1 = pure heuristics, NO LLM. Everything here is deterministic and\n * unit-testable so a future LLM-backed chaptering/summarizing pass can\n * replace the internals behind the same `buildStory(messages) => Story`\n * interface (mirrors the summarize_session heuristic → LLM swap path\n * described in agents-overview-app.ts).\n *\n * Folding rule (mirrors transcript-export.ts's own batching): an assistant\n * message plus every immediately-following `role: \"tool\"` message collapse\n * into ONE step. A user message is always its own step, and is also a\n * candidate chapter boundary.\n */\n\nimport { formatToolCall, formatToolResult } from \"./tool-presenter.js\"\nimport type { ExportedMessage } from \"./transcript-export.js\"\n\n// Re-exported so consumers of buildStory (e.g. the CLI's `sessions story`\n// command) can type the `/sessions/:id/export?format=json` payload they\n// feed into it without a second import from transcript-export.js.\nexport type { ExportedMessage, ExportedSession, ExportedSessionMeta } from \"./transcript-export.js\"\n\nexport type StoryStepKind = \"text\" | \"edit\" | \"bash\" | \"read\" | \"user\"\nexport type ChapterStatus = \"done\" | \"cur\"\nexport type RouteVerdict = \"cont\" | \"newt\"\n\nexport interface StoryChapter {\n id: string\n title: string\n status: ChapterStatus\n}\n\nexport type StoryItem = { text: string } | { h: string; r: string }\n\nexport interface StoryStep {\n chap: string\n kind: StoryStepKind\n /** HH:MM:SS (UTC) when the source message carried a `ts`, else \"\". */\n ts: string\n sum: string\n raw1: string\n count: number\n facts: string[]\n items: StoryItem[]\n route?: RouteVerdict\n}\n\nexport interface Story {\n chapters: StoryChapter[]\n steps: StoryStep[]\n}\n\n// ── formatting helpers ───────────────────────────────────────────────────\n\nfunction formatTs(ts?: number): string {\n if (ts === undefined || Number.isNaN(ts)) return \"\"\n return new Date(ts).toISOString().slice(11, 19)\n}\n\nfunction firstMeaningfulLine(text?: string): string | undefined {\n if (!text) return undefined\n const line = text\n .split(\"\\n\")\n .map(l => l.trim())\n .find(l => l.length > 0)\n return line\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text\n}\n\nfunction parseArgs(argsJson: string): unknown {\n try {\n return JSON.parse(argsJson)\n } catch {\n return {}\n }\n}\n\nfunction lineCount(text: string): number {\n return text.split(\"\\n\").filter(l => l.trim().length > 0).length || 1\n}\n\n// ── kind classification ──────────────────────────────────────────────────\n\n/** Dominant kind for a group of tool calls: edit/write → edit, bash/terminal/\n * command → bash, read/grep/glob → read, else text (pure assistant text). */\nexport function classifyKind(toolCalls?: readonly { name: string }[]): StoryStepKind {\n if (!toolCalls || toolCalls.length === 0) return \"text\"\n const names = toolCalls.map(t => t.name.toLowerCase())\n if (names.some(n => /edit|write/.test(n))) return \"edit\"\n if (names.some(n => /bash|terminal|command/.test(n))) return \"bash\"\n if (names.some(n => /read|grep|glob/.test(n))) return \"read\"\n return \"text\"\n}\n\n// ── chapter routing (mockup's `classify()`, ported verbatim) ────────────\n\nconst NEW_CHAPTER_RE =\n /\\b(aussi|autre|ensuite|nouveau|nouvelle|plut[oô]t|maintenant|apr[eè]s ça|il faudrait|peux[- ]tu|on pourrait|ajoute|g[eè]re)\\b/iu\n\nexport function classifyRoute(text: string): { route: RouteVerdict; title?: string } {\n const newt = NEW_CHAPTER_RE.test(text)\n if (!newt) return { route: \"cont\" }\n const title = text.replace(/[.?!].*$/, \"\").slice(0, 42)\n return { route: \"newt\", title }\n}\n\n// ── folding: raw messages → steps (chapter unassigned) ───────────────────\n\ninterface FoldedStep {\n kind: StoryStepKind\n ts: string\n sum: string\n raw1: string\n count: number\n facts: string[]\n items: StoryItem[]\n /** Present only for user steps — drives chapter routing. */\n userText?: string\n}\n\nfunction foldToolStep(assistant: ExportedMessage, toolResults: ExportedMessage[]): FoldedStep {\n const toolCalls = assistant.toolCalls ?? []\n const kind = classifyKind(toolCalls)\n const count = toolCalls.length || 1\n\n const items: StoryItem[] = []\n const facts: string[] = []\n if (assistant.text?.trim()) items.push({ text: assistant.text.trim() })\n toolCalls.forEach((tc, i) => {\n const args = parseArgs(tc.args)\n const h = formatToolCall(tc.name, args)\n const resultMsg = toolResults[i]\n const resultText = resultMsg?.text ?? \"\"\n const isError = resultText.startsWith(\"[error]\")\n const r = isError ? resultText.slice(\"[error]\".length).trim() : resultText\n items.push({ h, r })\n const fact = formatToolResult(tc.name, r, isError)\n if (fact) facts.push(fact)\n })\n\n const firstLine = firstMeaningfulLine(assistant.text)\n const firstToolCall = toolCalls[0]\n const sum =\n firstLine ??\n (firstToolCall ? formatToolCall(firstToolCall.name, parseArgs(firstToolCall.args)) : \"…\")\n\n let raw1: string\n if (toolCalls.length === 0) {\n raw1 = `assistant · ${lineCount(assistant.text ?? \"\")} ligne(s)`\n } else if (toolCalls.length === 1 && firstToolCall) {\n raw1 = formatToolCall(firstToolCall.name, parseArgs(firstToolCall.args))\n } else {\n raw1 = `${firstToolCall?.name ?? \"tool\"} ×${toolCalls.length}`\n }\n\n return {\n kind,\n ts: formatTs(assistant.ts),\n sum,\n raw1,\n count,\n facts,\n items,\n }\n}\n\nfunction foldUserStep(msg: ExportedMessage): FoldedStep {\n const text = msg.text ?? \"\"\n const sum = `« ${truncate(text, 80)} »`\n return {\n kind: \"user\",\n ts: formatTs(msg.ts),\n sum,\n raw1: `user · ${lineCount(text)} ligne(s)`,\n count: 1,\n facts: [],\n items: [{ text }],\n userText: text,\n }\n}\n\nfunction foldOrphanToolStep(msg: ExportedMessage): FoldedStep {\n const text = msg.text ?? \"\"\n const isError = text.startsWith(\"[error]\")\n const r = isError ? text.slice(\"[error]\".length).trim() : text\n const name = msg.toolName ?? \"tool\"\n const fact = formatToolResult(name, r, isError)\n return {\n kind: classifyKind([{ name }]),\n ts: formatTs(msg.ts),\n sum: msg.toolName ? `${msg.toolName} · résultat` : \"Résultat d'outil\",\n raw1: msg.toolName ?? \"tool\",\n count: 1,\n facts: fact ? [fact] : [],\n items: [{ h: name, r }],\n }\n}\n\nfunction foldSystemStep(msg: ExportedMessage): FoldedStep {\n const text = msg.text ?? \"\"\n return {\n kind: \"text\",\n ts: formatTs(msg.ts),\n sum: firstMeaningfulLine(text) ?? text,\n raw1: \"system\",\n count: 1,\n facts: [],\n items: text ? [{ text }] : [],\n }\n}\n\nfunction foldMessages(messages: readonly ExportedMessage[]): FoldedStep[] {\n const steps: FoldedStep[] = []\n let i = 0\n while (i < messages.length) {\n const msg = messages[i]!\n if (msg.role === \"user\") {\n steps.push(foldUserStep(msg))\n i += 1\n continue\n }\n if (msg.role === \"assistant\") {\n let j = i + 1\n const toolResults: ExportedMessage[] = []\n while (j < messages.length && messages[j]!.role === \"tool\") {\n toolResults.push(messages[j]!)\n j += 1\n }\n steps.push(foldToolStep(msg, toolResults))\n i = j\n continue\n }\n if (msg.role === \"tool\") {\n // Orphan tool result with no preceding assistant message — rare, but\n // render it as its own read-only step rather than dropping data.\n steps.push(foldOrphanToolStep(msg))\n i += 1\n continue\n }\n // system\n steps.push(foldSystemStep(msg))\n i += 1\n }\n return steps\n}\n\n// ── chaptering: assign each folded step to a chapter ─────────────────────\n\nexport function buildStory(messages: readonly ExportedMessage[]): Story {\n const folded = foldMessages(messages)\n const chapters: StoryChapter[] = []\n const steps: StoryStep[] = []\n\n let currentChapterId: string | undefined\n let sawFirstUser = false\n\n const closeCurrent = (): void => {\n const cur = chapters.find(c => c.id === currentChapterId)\n if (cur) cur.status = \"done\"\n }\n const openChapter = (title: string): string => {\n const id = `c${chapters.length + 1}`\n chapters.push({ id, title, status: \"cur\" })\n return id\n }\n\n for (const step of folded) {\n let route: RouteVerdict | undefined\n if (step.kind === \"user\" && step.userText !== undefined) {\n if (!sawFirstUser) {\n sawFirstUser = true\n currentChapterId = openChapter(\"Cadrage\")\n } else {\n const verdict = classifyRoute(step.userText)\n route = verdict.route\n if (verdict.route === \"newt\") {\n closeCurrent()\n currentChapterId = openChapter(verdict.title || \"Nouvelle sous-tâche\")\n }\n }\n } else if (currentChapterId === undefined) {\n // Transcript opens with assistant/system output before any user\n // message (unusual, but keep every step chaptered).\n currentChapterId = openChapter(\"Cadrage\")\n }\n\n steps.push({\n chap: currentChapterId!,\n kind: step.kind,\n ts: step.ts,\n sum: step.sum,\n raw1: step.raw1,\n count: step.count,\n facts: step.facts,\n items: step.items,\n ...(route ? { route } : {}),\n })\n }\n\n return { chapters, steps }\n}\n"]}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { CatalogProvider } from '@agentproto/model-catalog';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure resolver for `~/.agentproto/config.json`'s `defaults` block —
|
|
5
|
+
* computes the effective `skills` + `options` + `auth` for an `agent_start`
|
|
6
|
+
* spawn before adapter-specific normalization. No fs, no adapter I/O, so
|
|
7
|
+
* it's unit-testable in isolation from `session-spawn.ts` (which owns the
|
|
8
|
+
* fs read + the adapter-manifest lookup).
|
|
9
|
+
*
|
|
10
|
+
* Precedence (lowest → highest): global `defaults` < `defaults.adapters.
|
|
11
|
+
* <slug>` < the explicit `agent_start` call.
|
|
12
|
+
* - `options`: shallow-merged maps, later (higher-precedence) keys win.
|
|
13
|
+
* - `skills`: global ∪ per-adapter when the caller didn't pass `skills`
|
|
14
|
+
* at all; an explicit `skills` (even `[]`) REPLACES the union rather
|
|
15
|
+
* than merging into it — a deliberate exact set, mirroring how an
|
|
16
|
+
* explicit `mcpServers: []` opts out of the hermes default in
|
|
17
|
+
* `session-spawn.ts`.
|
|
18
|
+
* - `auth`: surfaces the RAW billing-auth material (requested mode, both
|
|
19
|
+
* candidate credentials, per-spawn provider pin, and the `explicit`
|
|
20
|
+
* signal) so the descriptor-aware resolver ({@link resolveAuthSpec},
|
|
21
|
+
* which also needs the adapter's provider/subscription descriptor +
|
|
22
|
+
* providers.json) can decide the final mode, env var, scrub set, and
|
|
23
|
+
* credential source. Credentials are named in config or the provider
|
|
24
|
+
* store — never read from the ambient shell env.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Deterministic billing-auth config for one adapter slug (today, only
|
|
29
|
+
* claude-code interprets it — see `AgentCliStartOptions.auth` in
|
|
30
|
+
* `@agentproto/driver-agent-cli`). EXPLICIT credential selection, not
|
|
31
|
+
* scrub-by-absence: `token`/`apiKey` are the actual secret values, named
|
|
32
|
+
* here (or supplied per-spawn) rather than inherited from the launching
|
|
33
|
+
* shell. Never logged; only a fingerprint (see {@link credentialFingerprint})
|
|
34
|
+
* is ever surfaced back to a caller.
|
|
35
|
+
*/
|
|
36
|
+
interface DefaultsAdapterAuthConfig {
|
|
37
|
+
/** `"subscription"` or `"api-key"`. Omitted ⇒ the resolver picks by
|
|
38
|
+
* ordered preference (subscription first for adapters that support it —
|
|
39
|
+
* see {@link resolveAuthSpec}), never a hardcoded default. */
|
|
40
|
+
mode?: "subscription" | "api-key";
|
|
41
|
+
/** The subscription bearer token for `"subscription"` mode — minted via
|
|
42
|
+
* `claude setup-token` (bills the Max/Pro subscription, not API credits),
|
|
43
|
+
* SET to the adapter's `authSubscription.setEnv`. */
|
|
44
|
+
token?: string;
|
|
45
|
+
/** Explicit API key for `"api-key"` mode, SET to `providerEnvVar(provider)`.
|
|
46
|
+
* Wins over the `providers.json` store key for the same provider. */
|
|
47
|
+
apiKey?: string;
|
|
48
|
+
/** Per-spawn provider PIN — overrides the adapter's fixed provider and the
|
|
49
|
+
* model-derived provider (the sharp edge for by-model routers whose config
|
|
50
|
+
* routes a catalog-"anthropic" model elsewhere). A `CatalogProvider` id. */
|
|
51
|
+
provider?: CatalogProvider;
|
|
52
|
+
}
|
|
53
|
+
interface DefaultsAdapterConfig {
|
|
54
|
+
skills?: string[];
|
|
55
|
+
options?: Record<string, boolean | number | string>;
|
|
56
|
+
auth?: DefaultsAdapterAuthConfig;
|
|
57
|
+
}
|
|
58
|
+
/** Shape of `config.json`'s top-level `defaults` block. */
|
|
59
|
+
interface SpawnDefaultsConfig {
|
|
60
|
+
skills?: string[];
|
|
61
|
+
options?: Record<string, boolean | number | string>;
|
|
62
|
+
adapters?: Record<string, DefaultsAdapterConfig>;
|
|
63
|
+
/** Depth cutoff for the role-derived default (see `resolveRole` in
|
|
64
|
+
* `role.ts`) applied when an `agent_start` call omits `role`:
|
|
65
|
+
* `depth < cutoff` → supervisor, `depth >= cutoff` → executor.
|
|
66
|
+
* Default 1 (root spawns keep today's unrestricted behaviour; any
|
|
67
|
+
* spawn made THROUGH an orchestrator defaults to executor). Tune
|
|
68
|
+
* this up (e.g. to a large number) to restore the old permissive
|
|
69
|
+
* behaviour for existing deep spawns wholesale. */
|
|
70
|
+
defaultRoleDepthCutoff?: number;
|
|
71
|
+
/** Trust-boundary cap on pack-carried roles (see `role-registry.ts`'s
|
|
72
|
+
* `loadRoleRegistry`): a role pack whose `toolPolicy.delegation` is
|
|
73
|
+
* `"allow"` at a level ABOVE this cap has it forced to `"deny"` —
|
|
74
|
+
* the pack can still declare the intent, the daemon just refuses to
|
|
75
|
+
* grant it. Lets an operator install third-party role packs without
|
|
76
|
+
* trusting every one of them to self-grant delegation. Undefined
|
|
77
|
+
* (default) ⇒ no cap, any pack-declared level may carry
|
|
78
|
+
* `delegation: "allow"` (back-compat: #214 had no such knob). */
|
|
79
|
+
maxGrantableDelegation?: number;
|
|
80
|
+
/** Default per-session Langfuse tracing opt-in when an `agent_start` call
|
|
81
|
+
* omits `trace`. Default false — sessions trace only when they opt in or
|
|
82
|
+
* this is on. See `filterSessionObserver` / `SpawnAgentInput.trace`. */
|
|
83
|
+
langfuseTracing?: boolean;
|
|
84
|
+
/** Redactor slug applied to traced session content before it's sent to
|
|
85
|
+
* Langfuse (see `@agentproto/redaction`'s registry). Default "secrets"
|
|
86
|
+
* (deny-list by key + value-scan for secret shapes). */
|
|
87
|
+
traceRedactor?: string;
|
|
88
|
+
}
|
|
89
|
+
interface ResolveSpawnDefaultsInput {
|
|
90
|
+
/** Explicit-call `skills`. Undefined ⇒ caller expressed no preference,
|
|
91
|
+
* fall through to the config union. Provided ⇒ replaces it outright. */
|
|
92
|
+
skills?: string[];
|
|
93
|
+
/** Explicit-call AIP-45 `options` map — wins per-key over both the
|
|
94
|
+
* global and per-adapter config defaults. */
|
|
95
|
+
options?: Record<string, boolean | number | string>;
|
|
96
|
+
/** Explicit-call `agent_start.auth` override. `mode` wins over
|
|
97
|
+
* `defaults.adapters.<slug>.auth.mode`; the credential field matching the
|
|
98
|
+
* RESOLVED mode wins over the matching config field. Undefined ⇒ falls
|
|
99
|
+
* through entirely to the per-adapter config default. */
|
|
100
|
+
auth?: DefaultsAdapterAuthConfig;
|
|
101
|
+
}
|
|
102
|
+
interface ResolvedSpawnDefaults {
|
|
103
|
+
skills: string[];
|
|
104
|
+
options: Record<string, boolean | number | string>;
|
|
105
|
+
/** RAW billing-auth material (config precedence applied) — fed to the
|
|
106
|
+
* descriptor-aware {@link resolveAuthSpec}, which owns the final mode /
|
|
107
|
+
* env / scrub / credential-source decision. Both candidate credentials
|
|
108
|
+
* are surfaced (NOT collapsed to one), since the ordered-mode selection
|
|
109
|
+
* needs to know which are available before it picks the mode. */
|
|
110
|
+
auth: ResolvedSpawnAuthMaterial;
|
|
111
|
+
}
|
|
112
|
+
interface ResolvedSpawnAuthMaterial {
|
|
113
|
+
/** Operator-requested mode (per-spawn > per-adapter config), or undefined
|
|
114
|
+
* ⇒ let the resolver pick by ordered preference. */
|
|
115
|
+
requestedMode?: "subscription" | "api-key";
|
|
116
|
+
/** True when the operator explicitly configured `auth` (per-spawn OR in
|
|
117
|
+
* `defaults.adapters.<slug>.auth`). The ONLY way to tell "set mode, no
|
|
118
|
+
* key" (fail-fast) from "set nothing" (ambient) — both give no credential.
|
|
119
|
+
* DECISION 5. */
|
|
120
|
+
explicit: boolean;
|
|
121
|
+
/** Subscription bearer token (per-spawn > config), if configured. */
|
|
122
|
+
subscriptionCredential?: string;
|
|
123
|
+
/** Explicit API key (per-spawn > config), if configured — distinct from the
|
|
124
|
+
* providers.json store key the resolver fetches separately. */
|
|
125
|
+
apiKeyCredential?: string;
|
|
126
|
+
/** Per-spawn provider pin, if given. */
|
|
127
|
+
provider?: CatalogProvider;
|
|
128
|
+
}
|
|
129
|
+
declare function resolveSpawnDefaults(defaults: SpawnDefaultsConfig | undefined, adapterSlug: string, input: ResolveSpawnDefaultsInput): ResolvedSpawnDefaults;
|
|
130
|
+
/**
|
|
131
|
+
* Derive a SAFE, non-secret fingerprint for a resolved auth credential —
|
|
132
|
+
* NEVER the raw value — for recording on the session descriptor / surfacing
|
|
133
|
+
* in `agentproto sessions --watch` and `agent_sessions_list` (the
|
|
134
|
+
* "verifiability" requirement: answer "what was used" without exposing the
|
|
135
|
+
* secret). Format: `<mode> · <shape-prefix>…<last4>` when the shape is known
|
|
136
|
+
* (e.g. `subscription · sk-ant-oat…3f9c`), else `<mode> · …<last4>`.
|
|
137
|
+
*
|
|
138
|
+
* The shape marker is matched from a PUBLIC key-prefix table (longest-match),
|
|
139
|
+
* not derived from `mode` — see {@link CREDENTIAL_FINGERPRINT_PREFIXES}. Only
|
|
140
|
+
* the matched prefix + the last 4 characters are ever surfaced, never the
|
|
141
|
+
* middle (mirrors GitHub's `ghp_…abcd` style).
|
|
142
|
+
*/
|
|
143
|
+
declare function credentialFingerprint(mode: "subscription" | "api-key", credential: string): string;
|
|
144
|
+
/**
|
|
145
|
+
* The adapter's billing-auth capability, projected from its AIP-45 manifest
|
|
146
|
+
* (`provider` / `authEnforce` / `authSubscription`) by the host resolver. The
|
|
147
|
+
* runtime reads THIS, never the manifest directly — keeping the LLM-catalog
|
|
148
|
+
* coupling in the runtime and the driver mechanical.
|
|
149
|
+
*/
|
|
150
|
+
interface AdapterAuthDescriptor {
|
|
151
|
+
/** FIXED provider for a single-provider adapter; omitted for by-model
|
|
152
|
+
* routers (provider then derives from the requested model). */
|
|
153
|
+
provider?: CatalogProvider;
|
|
154
|
+
/** Enforcement policy — `"always"` engages every spawn (claude-code's
|
|
155
|
+
* #312 fail-fast); `"when-configured"` (default) only when `explicit`. */
|
|
156
|
+
authEnforce?: "always" | "when-configured";
|
|
157
|
+
/** Subscription (OAuth/bearer) support. Presence ⇒ the adapter supports
|
|
158
|
+
* `"subscription"` mode. Mirrors the driver's `AgentCliAuthSubscription`. */
|
|
159
|
+
authSubscription?: {
|
|
160
|
+
setEnv: string;
|
|
161
|
+
conflictEnv?: string[];
|
|
162
|
+
unsetEnvAdd?: string[];
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** The fully-resolved spec the driver applies mechanically. Structurally
|
|
166
|
+
* matches `@agentproto/driver-agent-cli`'s `ResolvedAuthSpec` (each package
|
|
167
|
+
* owns its own copy; the object flows across the boundary by shape). */
|
|
168
|
+
interface ResolvedAuthSpec {
|
|
169
|
+
mode: "subscription" | "api-key";
|
|
170
|
+
credential?: string;
|
|
171
|
+
setEnv: string;
|
|
172
|
+
unsetEnv: string[];
|
|
173
|
+
explicit: boolean;
|
|
174
|
+
enforce: "always" | "when-configured";
|
|
175
|
+
}
|
|
176
|
+
/** Where the resolved credential came from — the observable billing axis
|
|
177
|
+
* (DECISION 10②), never inferred. */
|
|
178
|
+
type CredentialSource = "explicit-config" | "providers-store" | "none";
|
|
179
|
+
/**
|
|
180
|
+
* The OBSERVABLE echo (DECISION 9③ / 10②) — recorded on the session
|
|
181
|
+
* descriptor so a verifier checks the RESOLUTION, never the model's
|
|
182
|
+
* self-report. Never carries the raw credential (only its fingerprint).
|
|
183
|
+
*/
|
|
184
|
+
interface AuthEcho {
|
|
185
|
+
provider: CatalogProvider;
|
|
186
|
+
authMode: "subscription" | "api-key";
|
|
187
|
+
credentialSource: CredentialSource;
|
|
188
|
+
setEnv: string;
|
|
189
|
+
fingerprint?: string;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Thrown when the operator requested a billing mode the adapter can't serve —
|
|
193
|
+
* today only `"subscription"` on an adapter with no `authSubscription`. A
|
|
194
|
+
* LOUD, distinct failure (DECISION 4②), never a silent downgrade to api-key.
|
|
195
|
+
*/
|
|
196
|
+
declare class AuthResolutionError extends Error {
|
|
197
|
+
readonly code = "unsupported_auth_mode";
|
|
198
|
+
constructor(message: string);
|
|
199
|
+
}
|
|
200
|
+
interface ResolveAuthSpecInput {
|
|
201
|
+
descriptor: AdapterAuthDescriptor;
|
|
202
|
+
/** `input.model ?? adapter default model` — for model-derived provider. */
|
|
203
|
+
model?: string;
|
|
204
|
+
/** Per-spawn provider pin (`input.auth.provider`). */
|
|
205
|
+
requestedProvider?: CatalogProvider;
|
|
206
|
+
/** Operator-requested mode; undefined ⇒ ordered preference. */
|
|
207
|
+
requestedMode?: "subscription" | "api-key";
|
|
208
|
+
/** Operator explicitly configured `auth` (DECISION 5). */
|
|
209
|
+
explicit: boolean;
|
|
210
|
+
/** Subscription bearer credential, if configured. */
|
|
211
|
+
subscriptionCredential?: string;
|
|
212
|
+
/** Explicit api-key credential from config, if configured. */
|
|
213
|
+
apiKeyConfigCredential?: string;
|
|
214
|
+
/** api-key credential from `providers.json` (fetched by the caller). */
|
|
215
|
+
apiKeyStoreCredential?: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* THE billing-auth resolver (DECISIONS 4, 6, 9, 10). Pure: given the adapter
|
|
219
|
+
* descriptor + raw config material + (caller-fetched) store key, it decides
|
|
220
|
+
* the provider, the mode (ordered — subscription over api-key when a
|
|
221
|
+
* subscription credential is present; a requested-but-unsupported mode throws
|
|
222
|
+
* `unsupported_auth_mode`), the env var to SET, the derived SCRUB set, and the
|
|
223
|
+
* credential + its source. Returns the driver `spec` + the observable `echo`,
|
|
224
|
+
* or `undefined` when no provider resolves (⇒ ambient, no injection — never
|
|
225
|
+
* guess). NEVER falls back to a default provider/model. Fail-loud on a
|
|
226
|
+
* configured-but-missing credential is deferred to the driver's mechanical
|
|
227
|
+
* apply (it engages then throws `missing_auth_credential`), so the `explicit`
|
|
228
|
+
* / `enforce` signals are carried through on the spec.
|
|
229
|
+
*/
|
|
230
|
+
declare function resolveAuthSpec(input: ResolveAuthSpecInput): {
|
|
231
|
+
spec: ResolvedAuthSpec;
|
|
232
|
+
echo: AuthEcho;
|
|
233
|
+
} | undefined;
|
|
234
|
+
/** Manifest-declared AIP-45 option id + type, the minimum an adapter
|
|
235
|
+
* resolver needs to expose for `normalizeSkillsOption` below. Mirrors
|
|
236
|
+
* `AgentCliOption`'s `id`/`type` fields without importing
|
|
237
|
+
* `@agentproto/driver-agent-cli` into the runtime package. */
|
|
238
|
+
interface DeclaredAdapterOption {
|
|
239
|
+
id: string;
|
|
240
|
+
type: "boolean" | "integer" | "string" | "enum";
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Fold the resolved `skills` list into `options.skills` using whatever
|
|
244
|
+
* shape the adapter's manifest declares for that option id (today, only
|
|
245
|
+
* `type: "string"` exists for a skills-shaped option — e.g. hermes'
|
|
246
|
+
* comma-joined `--skills a,b`). Adapters with no declared `skills` option
|
|
247
|
+
* (e.g. claude-code, which auto-discovers from `~/.claude/skills`) are a
|
|
248
|
+
* documented no-op — the effective skills list has nowhere to go, so it's
|
|
249
|
+
* dropped rather than guessing a flag the manifest didn't declare.
|
|
250
|
+
*
|
|
251
|
+
* An `options.skills` already present (from config defaults or the
|
|
252
|
+
* explicit call) is respected as-is and never overwritten here.
|
|
253
|
+
*/
|
|
254
|
+
declare function normalizeSkillsOption(skills: string[], options: Record<string, boolean | number | string>, declaredOptions: readonly DeclaredAdapterOption[] | undefined): Record<string, boolean | number | string>;
|
|
255
|
+
|
|
256
|
+
export { type AdapterAuthDescriptor as A, type CredentialSource as C, type DeclaredAdapterOption as D, type ResolvedAuthSpec as R, type SpawnDefaultsConfig as S, type AuthEcho as a, AuthResolutionError as b, type DefaultsAdapterAuthConfig as c, type DefaultsAdapterConfig as d, type ResolvedSpawnAuthMaterial as e, type ResolvedSpawnDefaults as f, credentialFingerprint as g, resolveSpawnDefaults as h, normalizeSkillsOption as n, resolveAuthSpec as r };
|
|
@@ -74,8 +74,12 @@ declare function addWorkspace(config: WorkspacesConfig, input: {
|
|
|
74
74
|
declare function removeWorkspace(config: WorkspacesConfig, slug: string): WorkspacesConfig;
|
|
75
75
|
declare function setActiveWorkspace(config: WorkspacesConfig, slug: string): WorkspacesConfig;
|
|
76
76
|
declare function findWorkspace(config: WorkspacesConfig, slug: string): WorkspaceEntry | undefined;
|
|
77
|
+
/** Find a workspace whose path is an ancestor of (or equal to) the
|
|
78
|
+
* given directory. Returns the most specific match (longest path)
|
|
79
|
+
* when multiple workspaces nest under the same root. */
|
|
80
|
+
declare function findWorkspaceByPath(config: WorkspacesConfig, dir: string): WorkspaceEntry | undefined;
|
|
77
81
|
/** The active workspace, or undefined when none is registered. Pure
|
|
78
82
|
* convenience to avoid re-implementing the lookup at every caller. */
|
|
79
83
|
declare function getActiveWorkspace(config: WorkspacesConfig): WorkspaceEntry | undefined;
|
|
80
84
|
|
|
81
|
-
export { DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_PATH, WORKSPACES_CONFIG_VERSION, type WorkspaceEntry, type WorkspacesConfig, addWorkspace, findWorkspace, getActiveWorkspace, loadWorkspacesConfig, removeWorkspace, sanitizeSlug, saveWorkspacesConfig, setActiveWorkspace };
|
|
85
|
+
export { DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_PATH, WORKSPACES_CONFIG_VERSION, type WorkspaceEntry, type WorkspacesConfig, addWorkspace, findWorkspace, findWorkspaceByPath, getActiveWorkspace, loadWorkspacesConfig, removeWorkspace, sanitizeSlug, saveWorkspacesConfig, setActiveWorkspace };
|
|
@@ -93,6 +93,11 @@ function setActiveWorkspace(config, slug) {
|
|
|
93
93
|
function findWorkspace(config, slug) {
|
|
94
94
|
return config.workspaces.find((w) => w.slug === sanitizeSlug(slug));
|
|
95
95
|
}
|
|
96
|
+
function findWorkspaceByPath(config, dir) {
|
|
97
|
+
const resolved = resolve(dir);
|
|
98
|
+
const candidates = config.workspaces.filter((w) => resolved.startsWith(resolve(w.path) + "/") || resolved === resolve(w.path)).sort((a, b) => b.path.length - a.path.length);
|
|
99
|
+
return candidates[0];
|
|
100
|
+
}
|
|
96
101
|
function getActiveWorkspace(config) {
|
|
97
102
|
if (!config.active) return config.workspaces[0];
|
|
98
103
|
return findWorkspace(config, config.active) ?? config.workspaces[0];
|
|
@@ -131,6 +136,6 @@ function normalizeConfig(parsed) {
|
|
|
131
136
|
return out;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
export { DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_PATH, WORKSPACES_CONFIG_VERSION, addWorkspace, findWorkspace, getActiveWorkspace, loadWorkspacesConfig, removeWorkspace, sanitizeSlug, saveWorkspacesConfig, setActiveWorkspace };
|
|
139
|
+
export { DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_PATH, WORKSPACES_CONFIG_VERSION, addWorkspace, findWorkspace, findWorkspaceByPath, getActiveWorkspace, loadWorkspacesConfig, removeWorkspace, sanitizeSlug, saveWorkspacesConfig, setActiveWorkspace };
|
|
135
140
|
//# sourceMappingURL=workspaces-config.mjs.map
|
|
136
141
|
//# sourceMappingURL=workspaces-config.mjs.map
|