@bastani/atomic 0.8.25 → 0.8.26-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/index-heavy.ts +1754 -0
- package/dist/builtin/intercom/index.ts +374 -1746
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/result-renderers.ts +77 -0
- package/dist/builtin/mcp/CHANGELOG.md +10 -0
- package/dist/builtin/mcp/index.ts +151 -57
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/index-heavy.ts +2060 -0
- package/dist/builtin/web-access/index.ts +182 -2274
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/web-access/result-renderers.ts +364 -0
- package/dist/builtin/workflows/CHANGELOG.md +9 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.ts +13 -3
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +53 -2
- package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +12 -3
- package/dist/builtin/workflows/src/tui/inline-form-store.ts +17 -6
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +13 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +7 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +17 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/timings.d.ts +9 -0
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +28 -1
- package/dist/core/timings.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +36 -4
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +19 -7
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@bastani/atomic";
|
|
2
|
+
import { Box, Text } from "@mariozechner/pi-tui";
|
|
3
|
+
import { formatSeconds } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
type ToolResultRenderer = NonNullable<ToolDefinition["renderResult"]>;
|
|
6
|
+
type ToolRenderResultArgs = Parameters<ToolResultRenderer>;
|
|
7
|
+
type ToolRenderResult = ReturnType<ToolResultRenderer>;
|
|
8
|
+
type RenderedResult = ToolRenderResultArgs[0];
|
|
9
|
+
type TextContentBlock = Extract<RenderedResult["content"][number], { type: "text" }>;
|
|
10
|
+
|
|
11
|
+
type QueryDetail = {
|
|
12
|
+
query: string;
|
|
13
|
+
provider: string | null;
|
|
14
|
+
answer: string | null;
|
|
15
|
+
sources: Array<{ title: string; url: string }>;
|
|
16
|
+
error: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type WebSearchResultDetails = {
|
|
20
|
+
queryCount?: number;
|
|
21
|
+
successfulQueries?: number;
|
|
22
|
+
totalResults?: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
fetchId?: string;
|
|
25
|
+
fetchUrls?: string[];
|
|
26
|
+
phase?: string;
|
|
27
|
+
progress?: number;
|
|
28
|
+
currentQuery?: string;
|
|
29
|
+
curated?: boolean;
|
|
30
|
+
curatedFrom?: number;
|
|
31
|
+
curatedQueries?: QueryDetail[];
|
|
32
|
+
cancelled?: boolean;
|
|
33
|
+
cancelReason?: string;
|
|
34
|
+
summary?: {
|
|
35
|
+
text: string;
|
|
36
|
+
workflow: "summary-review";
|
|
37
|
+
model: string | null;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
tokenEstimate: number;
|
|
40
|
+
fallbackUsed: boolean;
|
|
41
|
+
fallbackReason?: string;
|
|
42
|
+
edited?: boolean;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type CodeSearchResultDetails = {
|
|
47
|
+
query?: string;
|
|
48
|
+
maxTokens?: number;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type FetchContentResultDetails = {
|
|
53
|
+
urlCount?: number;
|
|
54
|
+
successful?: number;
|
|
55
|
+
totalChars?: number;
|
|
56
|
+
error?: string;
|
|
57
|
+
title?: string;
|
|
58
|
+
truncated?: boolean;
|
|
59
|
+
responseId?: string;
|
|
60
|
+
phase?: string;
|
|
61
|
+
progress?: number;
|
|
62
|
+
hasImage?: boolean;
|
|
63
|
+
imageCount?: number;
|
|
64
|
+
prompt?: string;
|
|
65
|
+
timestamp?: string;
|
|
66
|
+
frames?: number;
|
|
67
|
+
duration?: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type GetSearchContentResultDetails = {
|
|
71
|
+
error?: string;
|
|
72
|
+
query?: string;
|
|
73
|
+
url?: string;
|
|
74
|
+
title?: string;
|
|
75
|
+
resultCount?: number;
|
|
76
|
+
contentLength?: number;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function isTextContentBlock(block: RenderedResult["content"][number]): block is TextContentBlock {
|
|
80
|
+
return block.type === "text";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function firstTextContent(result: RenderedResult): string {
|
|
84
|
+
return result.content.find(isTextContentBlock)?.text ?? "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function progressBar(progress: number): string {
|
|
88
|
+
const filled = Math.floor(progress * 10);
|
|
89
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const renderWebSearchResult: ToolResultRenderer = (result, { expanded, isPartial }, theme) => {
|
|
93
|
+
const details = result.details as WebSearchResultDetails | undefined;
|
|
94
|
+
|
|
95
|
+
if (isPartial) {
|
|
96
|
+
if (details?.phase === "curating") {
|
|
97
|
+
return new Text(theme.fg("accent", "waiting for summary approval..."), 0, 0);
|
|
98
|
+
}
|
|
99
|
+
if (details?.phase === "searching") {
|
|
100
|
+
const progress = details?.progress ?? 0;
|
|
101
|
+
const bar = progressBar(progress);
|
|
102
|
+
const query = details?.currentQuery || "";
|
|
103
|
+
const display = query.length > 40 ? query.slice(0, 37) + "..." : query;
|
|
104
|
+
return new Text(theme.fg("accent", `[${bar}] ${display}`), 0, 0);
|
|
105
|
+
}
|
|
106
|
+
const progress = details?.progress ?? 0;
|
|
107
|
+
const bar = progressBar(progress);
|
|
108
|
+
return new Text(theme.fg("accent", `[${bar}] ${details?.phase || "searching"}`), 0, 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (details?.error) {
|
|
112
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let statusLine: string;
|
|
116
|
+
const queryInfo = details?.queryCount === 1 ? "" : `${details?.successfulQueries}/${details?.queryCount} queries, `;
|
|
117
|
+
statusLine = theme.fg("success", `${queryInfo}${details?.totalResults ?? 0} sources`);
|
|
118
|
+
if (details?.curated && details?.curatedFrom) {
|
|
119
|
+
statusLine += theme.fg("muted", ` (${details.queryCount}/${details.curatedFrom} queries curated)`);
|
|
120
|
+
}
|
|
121
|
+
if (details?.fetchId && details?.fetchUrls) {
|
|
122
|
+
statusLine += theme.fg("muted", ` (fetching ${details.fetchUrls.length} URLs)`);
|
|
123
|
+
} else if (details?.fetchId) {
|
|
124
|
+
statusLine += theme.fg("muted", " (content ready)");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Build expanded lines first so collapsed view can reference total count
|
|
128
|
+
const lines = [statusLine];
|
|
129
|
+
if (details?.summary?.text) {
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push(theme.fg("accent", `── Summary (${details.summary.workflow}) ` + "─".repeat(32)));
|
|
132
|
+
lines.push("");
|
|
133
|
+
for (const line of details.summary.text.split("\n")) {
|
|
134
|
+
lines.push(` ${line}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push("");
|
|
137
|
+
const metaParts = [
|
|
138
|
+
details.summary.model ? `model=${details.summary.model}` : "model=deterministic",
|
|
139
|
+
`duration=${details.summary.durationMs}ms`,
|
|
140
|
+
`tokens~${details.summary.tokenEstimate}`,
|
|
141
|
+
details.summary.fallbackUsed ? "fallback=true" : "fallback=false",
|
|
142
|
+
details.summary.edited ? "edited=true" : "edited=false",
|
|
143
|
+
];
|
|
144
|
+
if (details.summary.fallbackReason) {
|
|
145
|
+
metaParts.push(`reason=${details.summary.fallbackReason}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push(theme.fg("dim", " " + metaParts.join(" · ")));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const queryDetails = details?.curatedQueries;
|
|
151
|
+
if (queryDetails?.length) {
|
|
152
|
+
const kept = queryDetails.length;
|
|
153
|
+
const from = details?.curatedFrom ?? kept;
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push(theme.fg("accent", `\u2500\u2500 Curated Results (${kept} of ${from} queries kept) ` + "\u2500".repeat(24)));
|
|
156
|
+
|
|
157
|
+
for (const cq of queryDetails) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
const dq = cq.query.length > 65 ? cq.query.slice(0, 62) + "..." : cq.query;
|
|
160
|
+
const providerLabel = cq.provider ? ` (${cq.provider})` : "";
|
|
161
|
+
lines.push(theme.fg("accent", ` "${dq}"${providerLabel}`));
|
|
162
|
+
|
|
163
|
+
if (cq.error) {
|
|
164
|
+
lines.push(theme.fg("error", ` ${cq.error}`));
|
|
165
|
+
} else if (cq.answer) {
|
|
166
|
+
lines.push("");
|
|
167
|
+
for (const line of cq.answer.split("\n")) {
|
|
168
|
+
lines.push(` ${line}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (cq.sources.length > 0) {
|
|
173
|
+
lines.push("");
|
|
174
|
+
for (const s of cq.sources) {
|
|
175
|
+
const domain = s.url.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
176
|
+
const title = s.title.length > 50 ? s.title.slice(0, 47) + "..." : s.title;
|
|
177
|
+
lines.push(theme.fg("muted", ` \u25b8 ${title}`) + theme.fg("dim", ` \u00b7 ${domain}`));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
lines.push("");
|
|
182
|
+
} else {
|
|
183
|
+
const textContent = firstTextContent(result);
|
|
184
|
+
const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
|
|
185
|
+
for (const line of preview.split("\n")) {
|
|
186
|
+
lines.push(theme.fg("dim", line));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (details?.fetchUrls && details.fetchUrls.length > 0) {
|
|
191
|
+
if (details.curated) {
|
|
192
|
+
lines.push(theme.fg("muted", `Fetching ${details.fetchUrls.length} URLs in background`));
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(theme.fg("muted", "Fetching:"));
|
|
195
|
+
for (const u of details.fetchUrls.slice(0, 5)) {
|
|
196
|
+
const display = u.length > 60 ? u.slice(0, 57) + "..." : u;
|
|
197
|
+
lines.push(theme.fg("dim", " " + display));
|
|
198
|
+
}
|
|
199
|
+
if (details.fetchUrls.length > 5) {
|
|
200
|
+
lines.push(theme.fg("dim", ` ... and ${details.fetchUrls.length - 5} more`));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const totalLines = lines.length;
|
|
206
|
+
|
|
207
|
+
if (!expanded) {
|
|
208
|
+
const box = new Box(1, 0, (t) => theme.bg("toolSuccessBg", t));
|
|
209
|
+
box.addChild(new Text(statusLine, 0, 0));
|
|
210
|
+
|
|
211
|
+
let collapsedLines = 1; // statusLine
|
|
212
|
+
const summaryPreview = details?.summary?.text?.trim() || "";
|
|
213
|
+
if (summaryPreview) {
|
|
214
|
+
const preview = summaryPreview.length > 120 ? summaryPreview.slice(0, 117) + "..." : summaryPreview;
|
|
215
|
+
box.addChild(new Text(theme.fg("dim", preview), 0, 0));
|
|
216
|
+
collapsedLines++;
|
|
217
|
+
} else if (details?.curatedQueries?.length) {
|
|
218
|
+
for (const cq of details.curatedQueries.slice(0, 3)) {
|
|
219
|
+
const dq = cq.query.length > 55 ? cq.query.slice(0, 52) + "..." : cq.query;
|
|
220
|
+
const srcCount = cq.sources?.length ?? 0;
|
|
221
|
+
const suffix = cq.error ? theme.fg("error", " (error)") : theme.fg("dim", ` · ${srcCount} sources`);
|
|
222
|
+
box.addChild(new Text(theme.fg("accent", ` "${dq}"`) + suffix, 0, 0));
|
|
223
|
+
collapsedLines++;
|
|
224
|
+
}
|
|
225
|
+
if (details.curatedQueries.length > 3) {
|
|
226
|
+
box.addChild(new Text(theme.fg("dim", ` ... and ${details.curatedQueries.length - 3} more`), 0, 0));
|
|
227
|
+
collapsedLines++;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const textContent = firstTextContent(result);
|
|
231
|
+
const firstContentLine = textContent.split("\n").find(l => {
|
|
232
|
+
const t = l.trim();
|
|
233
|
+
return t && !t.startsWith("[") && !t.startsWith("#") && !t.startsWith("---");
|
|
234
|
+
});
|
|
235
|
+
const fallbackLine = (firstContentLine?.trim() || "").replace(/\*\*/g, "");
|
|
236
|
+
if (fallbackLine) {
|
|
237
|
+
const preview = fallbackLine.length > 120 ? fallbackLine.slice(0, 117) + "..." : fallbackLine;
|
|
238
|
+
box.addChild(new Text(theme.fg("dim", preview), 0, 0));
|
|
239
|
+
collapsedLines++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const moreLines = Math.max(0, totalLines - collapsedLines);
|
|
243
|
+
if (moreLines > 0) {
|
|
244
|
+
box.addChild(new Text(theme.fg("muted", `\n... (${moreLines} more lines, ${totalLines} total, CTRL+O Expand)`), 0, 0));
|
|
245
|
+
}
|
|
246
|
+
return box;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const renderCodeSearchResult: ToolResultRenderer = (result, { expanded }, theme) => {
|
|
253
|
+
const details = result.details as CodeSearchResultDetails | undefined;
|
|
254
|
+
if (details?.error) {
|
|
255
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const summary = theme.fg("success", "code context returned") +
|
|
259
|
+
theme.fg("muted", ` (${details?.maxTokens ?? 5000} tokens max)`);
|
|
260
|
+
if (!expanded) return new Text(summary, 0, 0);
|
|
261
|
+
|
|
262
|
+
const textContent = firstTextContent(result);
|
|
263
|
+
const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
|
|
264
|
+
return new Text(summary + "\n" + theme.fg("dim", preview), 0, 0);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const renderFetchContentResult: ToolResultRenderer = (result, { expanded, isPartial }, theme) => {
|
|
268
|
+
const details = result.details as FetchContentResultDetails | undefined;
|
|
269
|
+
|
|
270
|
+
if (isPartial) {
|
|
271
|
+
const progress = details?.progress ?? 0;
|
|
272
|
+
const bar = progressBar(progress);
|
|
273
|
+
return new Text(theme.fg("accent", `[${bar}] ${details?.phase || "fetching"}`), 0, 0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (details?.error) {
|
|
277
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (details?.urlCount === 1) {
|
|
281
|
+
const title = details?.title || "Untitled";
|
|
282
|
+
const imgCount = details?.imageCount ?? (details?.hasImage ? 1 : 0);
|
|
283
|
+
const imageBadge = imgCount > 1
|
|
284
|
+
? theme.fg("accent", ` [${imgCount} images]`)
|
|
285
|
+
: imgCount === 1
|
|
286
|
+
? theme.fg("accent", " [image]")
|
|
287
|
+
: "";
|
|
288
|
+
let statusLine = theme.fg("success", title) + theme.fg("muted", ` (${details?.totalChars ?? 0} chars)`) + imageBadge;
|
|
289
|
+
if (details?.truncated) {
|
|
290
|
+
statusLine += theme.fg("warning", " [truncated]");
|
|
291
|
+
}
|
|
292
|
+
if (typeof details?.duration === "number") {
|
|
293
|
+
statusLine += theme.fg("muted", ` | ${formatSeconds(Math.floor(details.duration))} total`);
|
|
294
|
+
}
|
|
295
|
+
const textContent = firstTextContent(result);
|
|
296
|
+
if (!expanded) {
|
|
297
|
+
const brief = textContent.length > 200 ? textContent.slice(0, 200) + "..." : textContent;
|
|
298
|
+
return new Text(statusLine + "\n" + theme.fg("dim", brief), 0, 0);
|
|
299
|
+
}
|
|
300
|
+
const lines = [statusLine];
|
|
301
|
+
if (details?.prompt) {
|
|
302
|
+
const display = details.prompt.length > 250 ? details.prompt.slice(0, 247) + "..." : details.prompt;
|
|
303
|
+
lines.push(theme.fg("dim", ` prompt: "${display}"`));
|
|
304
|
+
}
|
|
305
|
+
if (details?.timestamp) {
|
|
306
|
+
lines.push(theme.fg("dim", ` timestamp: ${details.timestamp}`));
|
|
307
|
+
}
|
|
308
|
+
if (typeof details?.frames === "number") {
|
|
309
|
+
lines.push(theme.fg("dim", ` frames: ${details.frames}`));
|
|
310
|
+
}
|
|
311
|
+
const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
|
|
312
|
+
lines.push(theme.fg("dim", preview));
|
|
313
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const countColor = (details?.successful ?? 0) > 0 ? "success" : "error";
|
|
317
|
+
const statusLine = theme.fg(countColor, `${details?.successful}/${details?.urlCount} URLs`) + theme.fg("muted", " (content stored)");
|
|
318
|
+
if (!expanded) {
|
|
319
|
+
return new Text(statusLine, 0, 0);
|
|
320
|
+
}
|
|
321
|
+
const textContent = firstTextContent(result);
|
|
322
|
+
const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
|
|
323
|
+
return new Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export const renderGetSearchContentResult: ToolResultRenderer = (result, { expanded }, theme) => {
|
|
327
|
+
const details = result.details as GetSearchContentResultDetails | undefined;
|
|
328
|
+
|
|
329
|
+
if (details?.error) {
|
|
330
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let statusLine: string;
|
|
334
|
+
if (details?.query) {
|
|
335
|
+
statusLine = theme.fg("success", `"${details.query}"`) + theme.fg("muted", ` (${details.resultCount} results)`);
|
|
336
|
+
} else {
|
|
337
|
+
statusLine = theme.fg("success", details?.title || "Content") + theme.fg("muted", ` (${details?.contentLength ?? 0} chars)`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!expanded) {
|
|
341
|
+
return new Text(statusLine, 0, 0);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const textContent = firstTextContent(result);
|
|
345
|
+
const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
|
|
346
|
+
return new Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export function renderWebAccessToolResult(name: string, args: ToolRenderResultArgs): ToolRenderResult {
|
|
350
|
+
switch (name) {
|
|
351
|
+
case "web_search":
|
|
352
|
+
return renderWebSearchResult(...args);
|
|
353
|
+
case "code_search":
|
|
354
|
+
return renderCodeSearchResult(...args);
|
|
355
|
+
case "fetch_content":
|
|
356
|
+
return renderFetchContentResult(...args);
|
|
357
|
+
case "get_search_content":
|
|
358
|
+
return renderGetSearchContentResult(...args);
|
|
359
|
+
default: {
|
|
360
|
+
const theme = args[2];
|
|
361
|
+
return new Text(theme.fg("error", `Result renderer not found: ${name}`), 0, 0);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.26-alpha.1] - 2026-06-05
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed the inline-form "snapshot lost" renderer and the `workflow.run.start`/`workflow.run.end` banner renderers returning bare strings, which crashed the host TUI with `child.render is not a function` when resuming a session containing persisted workflow custom messages. These renderers now return proper render components ([#1236](https://github.com/bastani-inc/atomic/issues/1236)).
|
|
14
|
+
- Fixed the workflow input form (the `/workflow <name>` argument selector) leaking into model context: spawning the picker and exiting without running the workflow no longer sends the form to the LLM. The input-form card is now emitted with `excludeFromContext` since it is transient UI, not conversation.
|
|
15
|
+
- Fixed the workflow input widget re-rendering in chat after `/resume`. Inline-form state is now cleared on `session_start`, and a rehydrated `workflows:input-form` card whose backing state is gone now renders nothing (returns `null`) instead of a stale form or "snapshot lost" placeholder.
|
|
16
|
+
- Stage sessions now emit `session_shutdown` before `dispose()` (mirroring the host `AgentSessionRuntime` teardown) so bound extensions receive a graceful shutdown signal instead of being silently invalidated. This stops disposed stage sessions from leaking child MCP servers and from triggering spurious stale-context "MCP initialization failed" errors when an extension's deferred `session_start` work races with stage disposal.
|
|
17
|
+
|
|
9
18
|
## [0.8.25] - 2026-06-04
|
|
10
19
|
|
|
11
20
|
### Changed
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
openInlineInputsForm,
|
|
55
55
|
registerInlineFormRenderer,
|
|
56
56
|
} from "../tui/inline-form-overlay.js";
|
|
57
|
+
import { clearForms } from "../tui/inline-form-store.js";
|
|
57
58
|
import {
|
|
58
59
|
registerChatSurfaceRenderer,
|
|
59
60
|
emitChatSurface,
|
|
@@ -166,7 +167,7 @@ export interface PiMessageRenderOptions {
|
|
|
166
167
|
expanded: boolean;
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
export type PiMessageRendererResult = string | PiMessageRenderComponent | undefined;
|
|
170
|
+
export type PiMessageRendererResult = string | PiMessageRenderComponent | null | undefined;
|
|
170
171
|
export type PiMessageRenderer = (
|
|
171
172
|
payload: unknown,
|
|
172
173
|
options?: PiMessageRenderOptions,
|
|
@@ -3756,11 +3757,14 @@ function factory(pi: ExtensionAPI): void {
|
|
|
3756
3757
|
// duplicating it into chat scroll just creates visual noise and pushes
|
|
3757
3758
|
// older chat content out of view every time a stage transitions.
|
|
3758
3759
|
if (typeof pi.registerMessageRenderer === "function") {
|
|
3760
|
+
// Wrap the string-producing banners in a render component: the host adds a
|
|
3761
|
+
// renderer's result directly as a TUI child, so a bare string would crash
|
|
3762
|
+
// `Container.render()` with "child.render is not a function".
|
|
3759
3763
|
pi.registerMessageRenderer("workflow.run.start", (payload) =>
|
|
3760
|
-
renderRunBanner(payload as RunStartPayload),
|
|
3764
|
+
dynamicTextRenderComponent(() => renderRunBanner(payload as RunStartPayload)),
|
|
3761
3765
|
);
|
|
3762
3766
|
pi.registerMessageRenderer("workflow.run.end", (payload) =>
|
|
3763
|
-
renderRunSummary(payload as RunEndPayload),
|
|
3767
|
+
dynamicTextRenderComponent(() => renderRunSummary(payload as RunEndPayload)),
|
|
3764
3768
|
);
|
|
3765
3769
|
// Inline workflow-input form (Option C in the design conversation):
|
|
3766
3770
|
// a sticky chat-history card driven by a custom EditorComponent. The
|
|
@@ -3834,6 +3838,12 @@ function factory(pi: ExtensionAPI): void {
|
|
|
3834
3838
|
persistence: persistenceRef.current,
|
|
3835
3839
|
});
|
|
3836
3840
|
store.clear();
|
|
3841
|
+
// Drop any inline input-form state from a previous session in this pi
|
|
3842
|
+
// process. A resumed/replaced session must not render a stale live form,
|
|
3843
|
+
// and rehydrated `workflows:input-form` cards then resolve to no backing
|
|
3844
|
+
// state so their renderer suppresses output (input widget hidden after
|
|
3845
|
+
// /resume).
|
|
3846
|
+
clearForms();
|
|
3837
3847
|
resetWorkflowLifecycleNotificationState(lifecycleNotificationState);
|
|
3838
3848
|
resetWorkflowHilAnswerNotificationState(hilAnswerNotificationState);
|
|
3839
3849
|
stageControlRegistry.clear();
|
|
@@ -285,6 +285,57 @@ function terminatingToolResultText(
|
|
|
285
285
|
return undefined;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* A stage session backed by a real Atomic `AgentSession` exposes its
|
|
290
|
+
* `extensionRunner`. When workflow wiring binds extensions to a stage session it
|
|
291
|
+
* replays the `session_start` lifecycle (see wiring.ts `bindExtensions`), so
|
|
292
|
+
* extensions such as MCP begin per-session initialization. Tearing that session
|
|
293
|
+
* down with `dispose()` alone invalidates the extension runtime WITHOUT emitting
|
|
294
|
+
* `session_shutdown`, so those extensions never receive a graceful teardown
|
|
295
|
+
* signal: MCP, for example, logs a spurious stale-context "initialization
|
|
296
|
+
* failed" error when its deferred init races with disposal, and leaves any child
|
|
297
|
+
* MCP servers running.
|
|
298
|
+
*
|
|
299
|
+
* The test stub session (createTestAgentSession) has no `extensionRunner`, so the
|
|
300
|
+
* capability is optional and feature-detected at runtime.
|
|
301
|
+
*/
|
|
302
|
+
type StageSessionExtensionRunner = {
|
|
303
|
+
hasHandlers(eventType: string): boolean;
|
|
304
|
+
emit(event: { readonly type: "session_shutdown"; readonly reason: "quit" }): Promise<unknown>;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
function stageSessionExtensionRunner(
|
|
308
|
+
current: StageSessionRuntime,
|
|
309
|
+
): StageSessionExtensionRunner | undefined {
|
|
310
|
+
const runner = (current as StageSessionRuntime & { extensionRunner?: StageSessionExtensionRunner })
|
|
311
|
+
.extensionRunner;
|
|
312
|
+
if (runner && typeof runner.hasHandlers === "function" && typeof runner.emit === "function") {
|
|
313
|
+
return runner;
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Dispose a stage session, mirroring the host `AgentSessionRuntime` teardown:
|
|
320
|
+
* emit `session_shutdown` before `dispose()` whenever the session exposes a
|
|
321
|
+
* compatible extension runner, so extensions tear down per-session resources
|
|
322
|
+
* (and bump their lifecycle generation) instead of being silently invalidated.
|
|
323
|
+
* A throwing shutdown handler must never strand the session, so disposal always
|
|
324
|
+
* runs.
|
|
325
|
+
*/
|
|
326
|
+
async function disposeStageSession(current: StageSessionRuntime | undefined): Promise<void> {
|
|
327
|
+
if (!current) return;
|
|
328
|
+
const runner = stageSessionExtensionRunner(current);
|
|
329
|
+
if (runner?.hasHandlers("session_shutdown")) {
|
|
330
|
+
try {
|
|
331
|
+
await runner.emit({ type: "session_shutdown", reason: "quit" });
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error("atomic-workflows: stage session_shutdown handler failed", error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
await current.dispose();
|
|
337
|
+
}
|
|
338
|
+
|
|
288
339
|
function asAgentSession(activeSession: StageSessionRuntime | undefined): AgentSession | undefined {
|
|
289
340
|
if (!activeSession) return undefined;
|
|
290
341
|
const candidate = activeSession as StageSessionRuntime & Partial<Pick<AgentSession, "state" | "sessionManager" | "modelRegistry" | "getContextUsage">>;
|
|
@@ -675,7 +726,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
|
|
|
675
726
|
unsubscribeTerminateWatcher?.();
|
|
676
727
|
unsubscribeTerminateWatcher = undefined;
|
|
677
728
|
terminatingToolCallIds.clear();
|
|
678
|
-
await current
|
|
729
|
+
await disposeStageSession(current);
|
|
679
730
|
}
|
|
680
731
|
|
|
681
732
|
async function promptWithPauseResume(
|
|
@@ -895,7 +946,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
|
|
|
895
946
|
unsubscribeTerminateWatcher?.();
|
|
896
947
|
unsubscribeTerminateWatcher = undefined;
|
|
897
948
|
terminatingToolCallIds.clear();
|
|
898
|
-
await session
|
|
949
|
+
await disposeStageSession(session);
|
|
899
950
|
},
|
|
900
951
|
|
|
901
952
|
__getLastAssistantText() {
|
|
@@ -79,7 +79,7 @@ interface CardComponent {
|
|
|
79
79
|
invalidate?(): void;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
type RawRenderer = (payload: unknown) =>
|
|
82
|
+
type RawRenderer = (payload: unknown) => CardComponent | null | undefined;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Wire the message renderer once per live ExtensionAPI host. pi creates a new
|
|
@@ -106,8 +106,11 @@ export function registerInlineFormRenderer(pi: ExtensionAPI, theme: GraphTheme):
|
|
|
106
106
|
if (!formId) return undefined;
|
|
107
107
|
const state = getForm(formId);
|
|
108
108
|
if (!state) {
|
|
109
|
-
//
|
|
110
|
-
|
|
109
|
+
// No backing state — the session was resumed/replaced (the store is
|
|
110
|
+
// cleared on session_start) or the map was evicted. Return null so the
|
|
111
|
+
// host renders nothing: the input widget must not reappear in chat after
|
|
112
|
+
// /resume rather than showing a stale or "snapshot lost" placeholder.
|
|
113
|
+
return null;
|
|
111
114
|
}
|
|
112
115
|
return {
|
|
113
116
|
// The card is fully reactive: read fresh state on every render call,
|
|
@@ -291,11 +294,17 @@ export async function openInlineInputsForm(
|
|
|
291
294
|
display?: boolean;
|
|
292
295
|
details?: FormMessageDetails;
|
|
293
296
|
},
|
|
297
|
+
options?: { excludeFromContext?: boolean },
|
|
294
298
|
) => void).call(pi, {
|
|
295
299
|
customType: CUSTOM_TYPE,
|
|
296
300
|
content: opts.workflowName,
|
|
297
301
|
display: true,
|
|
298
302
|
details: { formId },
|
|
303
|
+
}, {
|
|
304
|
+
// The input form is a transient UI surface, not conversation. Keep it
|
|
305
|
+
// out of LLM context so spawning the picker and exiting without
|
|
306
|
+
// running the workflow never leaks the form into the model.
|
|
307
|
+
excludeFromContext: true,
|
|
299
308
|
});
|
|
300
309
|
} catch {
|
|
301
310
|
activeEditor?.dispose?.();
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* `finalizeForm(id, "submit")` → status = "submitted", values frozen
|
|
13
13
|
* `finalizeForm(id, "cancel")` → status = "cancelled"
|
|
14
14
|
*
|
|
15
|
-
* After finalize the state stays in the map
|
|
16
|
-
* renderer reads it to display the historical card.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* After finalize the state stays in the map for the lifetime of the session.
|
|
16
|
+
* The renderer reads it to display the historical card. On a session boundary
|
|
17
|
+
* (`session_start`: new/resume/fork/reload) the store is cleared via
|
|
18
|
+
* {@link clearForms}, so a rehydrated `workflows:input-form` message has no
|
|
19
|
+
* backing state and its renderer suppresses output (returns null) — the input
|
|
20
|
+
* widget never reappears in chat after `/resume`.
|
|
20
21
|
*
|
|
21
22
|
* Why a global registry instead of closure capture: the message renderer is
|
|
22
23
|
* registered ONCE at factory time and called many times for any number of
|
|
@@ -73,7 +74,17 @@ export function finalizeForm(formId: string, outcome: "submit" | "cancel"): void
|
|
|
73
74
|
touch(s);
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Clear all inline form state. Called on `session_start` so a resumed or
|
|
79
|
+
* replaced session never renders a stale live form, and so a rehydrated
|
|
80
|
+
* `workflows:input-form` message resolves to no backing state (its renderer
|
|
81
|
+
* then returns null and the host renders nothing).
|
|
82
|
+
*/
|
|
83
|
+
export function clearForms(): void {
|
|
84
|
+
FORMS.clear();
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
/** Test helper — clear the registry between tests. */
|
|
77
88
|
export function _resetForms(): void {
|
|
78
|
-
|
|
89
|
+
clearForms();
|
|
79
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-services.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-services.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAyB,KAAK,4BAA4B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACrH,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,wBAAwB,EAAsB,MAAM,UAAU,CAAC;AAC7G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC7C,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iCAAiC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;IACpD,qBAAqB,CAAC,EAAE,IAAI,CAAC,4BAA4B,EAAE,KAAK,GAAG,UAAU,GAAG,iBAAiB,CAAC,CAAC;CACnG;AAED;;;;;GAKG;AACH,MAAM,WAAW,qCAAqC;IACrD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC3E,KAAK,CAAC,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC3C,aAAa,CAAC,EAAE,yBAAyB,CAAC,eAAe,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,yBAAyB,CAAC,SAAS,CAAC,CAAC;IAC/C,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,6BAA6B,EAAE,CAAC;CAC7C;AAkDD;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC/C,OAAO,EAAE,iCAAiC,GACxC,OAAO,CAAC,oBAAoB,CAAC,CAuC/B;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,qCAAqC,GAC5C,OAAO,CAAC,wBAAwB,CAAC,CAkBnC","sourcesContent":["import { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport type { SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { DefaultResourceLoader, type DefaultResourceLoaderOptions, type ResourceLoader } from \"./resource-loader.ts\";\nimport { type CreateAgentSessionOptions, type CreateAgentSessionResult, createAgentSession } from \"./sdk.ts\";\nimport type { SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * Non-fatal issues collected while creating services or sessions.\n *\n * Runtime creation returns diagnostics to the caller instead of printing or\n * exiting. The app layer decides whether warnings should be shown and whether\n * errors should abort startup.\n */\nexport interface AgentSessionRuntimeDiagnostic {\n\ttype: \"info\" | \"warning\" | \"error\";\n\tmessage: string;\n}\n\n/**\n * Inputs for creating cwd-bound runtime services.\n *\n * These services are recreated whenever the effective session cwd changes.\n * CLI-provided resource paths should be resolved to absolute paths before they\n * reach this function, so later cwd switches do not reinterpret them.\n */\nexport interface CreateAgentSessionServicesOptions {\n\tcwd: string;\n\tagentDir?: string;\n\tauthStorage?: AuthStorage;\n\tsettingsManager?: SettingsManager;\n\tmodelRegistry?: ModelRegistry;\n\textensionFlagValues?: Map<string, boolean | string>;\n\tresourceLoaderOptions?: Omit<DefaultResourceLoaderOptions, \"cwd\" | \"agentDir\" | \"settingsManager\">;\n}\n\n/**\n * Inputs for creating an AgentSession from already-created services.\n *\n * Use this after services exist and any cwd-bound model/tool/session options\n * have been resolved against those services.\n */\nexport interface CreateAgentSessionFromServicesOptions {\n\tservices: AgentSessionServices;\n\tsessionManager: SessionManager;\n\tsessionStartEvent?: SessionStartEvent;\n\tmodel?: Model<Api>;\n\tthinkingLevel?: ThinkingLevel;\n\tscopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\ttools?: CreateAgentSessionOptions[\"tools\"];\n\texcludedTools?: CreateAgentSessionOptions[\"excludedTools\"];\n\tnoTools?: CreateAgentSessionOptions[\"noTools\"];\n\tcustomTools?: ToolDefinition[];\n}\n\n/**\n * Coherent cwd-bound runtime services for one effective session cwd.\n *\n * This is infrastructure only. The AgentSession itself is created separately so\n * session options can be resolved against these services first.\n */\nexport interface AgentSessionServices {\n\tcwd: string;\n\tagentDir: string;\n\tauthStorage: AuthStorage;\n\tsettingsManager: SettingsManager;\n\tmodelRegistry: ModelRegistry;\n\tresourceLoader: ResourceLoader;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n}\n\nfunction applyExtensionFlagValues(\n\tresourceLoader: ResourceLoader,\n\textensionFlagValues: Map<string, boolean | string> | undefined,\n): AgentSessionRuntimeDiagnostic[] {\n\tif (!extensionFlagValues) {\n\t\treturn [];\n\t}\n\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tconst extensionsResult = resourceLoader.getExtensions();\n\tconst registeredFlags = new Map<string, { type: \"boolean\" | \"string\" }>();\n\tfor (const extension of extensionsResult.extensions) {\n\t\tfor (const [name, flag] of extension.flags) {\n\t\t\tregisteredFlags.set(name, { type: flag.type });\n\t\t}\n\t}\n\n\tconst unknownFlags: string[] = [];\n\tfor (const [name, value] of extensionFlagValues) {\n\t\tconst flag = registeredFlags.get(name);\n\t\tif (!flag) {\n\t\t\tunknownFlags.push(name);\n\t\t\tcontinue;\n\t\t}\n\t\tif (flag.type === \"boolean\") {\n\t\t\textensionsResult.runtime.flagValues.set(name, true);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof value === \"string\") {\n\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\tcontinue;\n\t\t}\n\t\tdiagnostics.push({\n\t\t\ttype: \"error\",\n\t\t\tmessage: `Extension flag \"--${name}\" requires a value`,\n\t\t});\n\t}\n\n\tif (unknownFlags.length > 0) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"error\",\n\t\t\tmessage: `Unknown option${unknownFlags.length === 1 ? \"\" : \"s\"}: ${unknownFlags.map((name) => `--${name}`).join(\", \")}`,\n\t\t});\n\t}\n\n\treturn diagnostics;\n}\n\n/**\n * Create cwd-bound runtime services.\n *\n * Returns services plus diagnostics. It does not create an AgentSession.\n */\nexport async function createAgentSessionServices(\n\toptions: CreateAgentSessionServicesOptions,\n): Promise<AgentSessionServices> {\n\tconst cwd = resolvePath(options.cwd);\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getAgentDir();\n\tconst authStorage = options.authStorage ?? AuthStorage.create(join(agentDir, \"auth.json\"));\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst modelRegistry = options.modelRegistry ?? ModelRegistry.create(authStorage, join(agentDir, \"models.json\"));\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\t...(options.resourceLoaderOptions ?? {}),\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t});\n\tawait resourceLoader.reload();\n\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tconst extensionsResult = resourceLoader.getExtensions();\n\tfor (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {\n\t\ttry {\n\t\t\tmodelRegistry.registerProvider(name, config);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\tmessage: `Extension \"${extensionPath}\" error: ${message}`,\n\t\t\t});\n\t\t}\n\t}\n\textensionsResult.runtime.pendingProviderRegistrations = [];\n\tdiagnostics.push(...applyExtensionFlagValues(resourceLoader, options.extensionFlagValues));\n\n\treturn {\n\t\tcwd,\n\t\tagentDir,\n\t\tauthStorage,\n\t\tsettingsManager,\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tdiagnostics,\n\t};\n}\n\n/**\n * Create an AgentSession from previously created services.\n *\n * This keeps session creation separate from service creation so callers can\n * resolve model, thinking, tools, and other session inputs against the target\n * cwd before constructing the session.\n */\nexport async function createAgentSessionFromServices(\n\toptions: CreateAgentSessionFromServicesOptions,\n): Promise<CreateAgentSessionResult> {\n\treturn createAgentSession({\n\t\tcwd: options.services.cwd,\n\t\tagentDir: options.services.agentDir,\n\t\tauthStorage: options.services.authStorage,\n\t\tsettingsManager: options.services.settingsManager,\n\t\tmodelRegistry: options.services.modelRegistry,\n\t\tresourceLoader: options.services.resourceLoader,\n\t\tsessionManager: options.sessionManager,\n\t\tmodel: options.model,\n\t\tthinkingLevel: options.thinkingLevel,\n\t\tscopedModels: options.scopedModels,\n\t\ttools: options.tools,\n\t\texcludedTools: options.excludedTools,\n\t\tnoTools: options.noTools,\n\t\tcustomTools: options.customTools,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session-services.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-services.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAyB,KAAK,4BAA4B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACrH,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,wBAAwB,EAAsB,MAAM,UAAU,CAAC;AAC7G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC7C,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iCAAiC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;IACpD,qBAAqB,CAAC,EAAE,IAAI,CAAC,4BAA4B,EAAE,KAAK,GAAG,UAAU,GAAG,iBAAiB,CAAC,CAAC;CACnG;AAED;;;;;GAKG;AACH,MAAM,WAAW,qCAAqC;IACrD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC3E,KAAK,CAAC,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC3C,aAAa,CAAC,EAAE,yBAAyB,CAAC,eAAe,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,yBAAyB,CAAC,SAAS,CAAC,CAAC;IAC/C,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,6BAA6B,EAAE,CAAC;CAC7C;AAkDD;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC/C,OAAO,EAAE,iCAAiC,GACxC,OAAO,CAAC,oBAAoB,CAAC,CAmD/B;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,qCAAqC,GAC5C,OAAO,CAAC,wBAAwB,CAAC,CAkBnC","sourcesContent":["import { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport type { SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { DefaultResourceLoader, type DefaultResourceLoaderOptions, type ResourceLoader } from \"./resource-loader.ts\";\nimport { type CreateAgentSessionOptions, type CreateAgentSessionResult, createAgentSession } from \"./sdk.ts\";\nimport type { SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { endTimingSpan, startTimingSpan } from \"./timings.ts\";\n\n/**\n * Non-fatal issues collected while creating services or sessions.\n *\n * Runtime creation returns diagnostics to the caller instead of printing or\n * exiting. The app layer decides whether warnings should be shown and whether\n * errors should abort startup.\n */\nexport interface AgentSessionRuntimeDiagnostic {\n\ttype: \"info\" | \"warning\" | \"error\";\n\tmessage: string;\n}\n\n/**\n * Inputs for creating cwd-bound runtime services.\n *\n * These services are recreated whenever the effective session cwd changes.\n * CLI-provided resource paths should be resolved to absolute paths before they\n * reach this function, so later cwd switches do not reinterpret them.\n */\nexport interface CreateAgentSessionServicesOptions {\n\tcwd: string;\n\tagentDir?: string;\n\tauthStorage?: AuthStorage;\n\tsettingsManager?: SettingsManager;\n\tmodelRegistry?: ModelRegistry;\n\textensionFlagValues?: Map<string, boolean | string>;\n\tresourceLoaderOptions?: Omit<DefaultResourceLoaderOptions, \"cwd\" | \"agentDir\" | \"settingsManager\">;\n}\n\n/**\n * Inputs for creating an AgentSession from already-created services.\n *\n * Use this after services exist and any cwd-bound model/tool/session options\n * have been resolved against those services.\n */\nexport interface CreateAgentSessionFromServicesOptions {\n\tservices: AgentSessionServices;\n\tsessionManager: SessionManager;\n\tsessionStartEvent?: SessionStartEvent;\n\tmodel?: Model<Api>;\n\tthinkingLevel?: ThinkingLevel;\n\tscopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\ttools?: CreateAgentSessionOptions[\"tools\"];\n\texcludedTools?: CreateAgentSessionOptions[\"excludedTools\"];\n\tnoTools?: CreateAgentSessionOptions[\"noTools\"];\n\tcustomTools?: ToolDefinition[];\n}\n\n/**\n * Coherent cwd-bound runtime services for one effective session cwd.\n *\n * This is infrastructure only. The AgentSession itself is created separately so\n * session options can be resolved against these services first.\n */\nexport interface AgentSessionServices {\n\tcwd: string;\n\tagentDir: string;\n\tauthStorage: AuthStorage;\n\tsettingsManager: SettingsManager;\n\tmodelRegistry: ModelRegistry;\n\tresourceLoader: ResourceLoader;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n}\n\nfunction applyExtensionFlagValues(\n\tresourceLoader: ResourceLoader,\n\textensionFlagValues: Map<string, boolean | string> | undefined,\n): AgentSessionRuntimeDiagnostic[] {\n\tif (!extensionFlagValues) {\n\t\treturn [];\n\t}\n\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tconst extensionsResult = resourceLoader.getExtensions();\n\tconst registeredFlags = new Map<string, { type: \"boolean\" | \"string\" }>();\n\tfor (const extension of extensionsResult.extensions) {\n\t\tfor (const [name, flag] of extension.flags) {\n\t\t\tregisteredFlags.set(name, { type: flag.type });\n\t\t}\n\t}\n\n\tconst unknownFlags: string[] = [];\n\tfor (const [name, value] of extensionFlagValues) {\n\t\tconst flag = registeredFlags.get(name);\n\t\tif (!flag) {\n\t\t\tunknownFlags.push(name);\n\t\t\tcontinue;\n\t\t}\n\t\tif (flag.type === \"boolean\") {\n\t\t\textensionsResult.runtime.flagValues.set(name, true);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof value === \"string\") {\n\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\tcontinue;\n\t\t}\n\t\tdiagnostics.push({\n\t\t\ttype: \"error\",\n\t\t\tmessage: `Extension flag \"--${name}\" requires a value`,\n\t\t});\n\t}\n\n\tif (unknownFlags.length > 0) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"error\",\n\t\t\tmessage: `Unknown option${unknownFlags.length === 1 ? \"\" : \"s\"}: ${unknownFlags.map((name) => `--${name}`).join(\", \")}`,\n\t\t});\n\t}\n\n\treturn diagnostics;\n}\n\n/**\n * Create cwd-bound runtime services.\n *\n * Returns services plus diagnostics. It does not create an AgentSession.\n */\nexport async function createAgentSessionServices(\n\toptions: CreateAgentSessionServicesOptions,\n): Promise<AgentSessionServices> {\n\tconst cwd = resolvePath(options.cwd);\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getAgentDir();\n\tconst authStorageSpan = startTimingSpan(\"createAgentSessionServices.authStorage\");\n\tconst authStorage = options.authStorage ?? AuthStorage.create(join(agentDir, \"auth.json\"));\n\tendTimingSpan(authStorageSpan);\n\tconst settingsSpan = startTimingSpan(\"createAgentSessionServices.settingsManager\");\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tendTimingSpan(settingsSpan);\n\tconst modelRegistrySpan = startTimingSpan(\"createAgentSessionServices.modelRegistry\");\n\tconst modelRegistry = options.modelRegistry ?? ModelRegistry.create(authStorage, join(agentDir, \"models.json\"));\n\tendTimingSpan(modelRegistrySpan);\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\t...(options.resourceLoaderOptions ?? {}),\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t});\n\tconst reloadSpan = startTimingSpan(\"createAgentSessionServices.resourceLoader.reload\");\n\tawait resourceLoader.reload();\n\tendTimingSpan(reloadSpan);\n\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tconst providerSpan = startTimingSpan(\"createAgentSessionServices.providerRegistrations\");\n\tconst extensionsResult = resourceLoader.getExtensions();\n\tfor (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {\n\t\ttry {\n\t\t\tmodelRegistry.registerProvider(name, config);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\tmessage: `Extension \"${extensionPath}\" error: ${message}`,\n\t\t\t});\n\t\t}\n\t}\n\textensionsResult.runtime.pendingProviderRegistrations = [];\n\tendTimingSpan(providerSpan);\n\tconst flagSpan = startTimingSpan(\"createAgentSessionServices.extensionFlagValidation\");\n\tdiagnostics.push(...applyExtensionFlagValues(resourceLoader, options.extensionFlagValues));\n\tendTimingSpan(flagSpan);\n\n\treturn {\n\t\tcwd,\n\t\tagentDir,\n\t\tauthStorage,\n\t\tsettingsManager,\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tdiagnostics,\n\t};\n}\n\n/**\n * Create an AgentSession from previously created services.\n *\n * This keeps session creation separate from service creation so callers can\n * resolve model, thinking, tools, and other session inputs against the target\n * cwd before constructing the session.\n */\nexport async function createAgentSessionFromServices(\n\toptions: CreateAgentSessionFromServicesOptions,\n): Promise<CreateAgentSessionResult> {\n\treturn createAgentSession({\n\t\tcwd: options.services.cwd,\n\t\tagentDir: options.services.agentDir,\n\t\tauthStorage: options.services.authStorage,\n\t\tsettingsManager: options.services.settingsManager,\n\t\tmodelRegistry: options.services.modelRegistry,\n\t\tresourceLoader: options.services.resourceLoader,\n\t\tsessionManager: options.sessionManager,\n\t\tmodel: options.model,\n\t\tthinkingLevel: options.thinkingLevel,\n\t\tscopedModels: options.scopedModels,\n\t\ttools: options.tools,\n\t\texcludedTools: options.excludedTools,\n\t\tnoTools: options.noTools,\n\t\tcustomTools: options.customTools,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n}\n"]}
|