@asterxsk/kiln 0.1.0 → 0.2.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 +21 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
declare module "promise.try" {
|
|
2
|
-
function promiseTry<T>(callback: () => T | PromiseLike<T>): Promise<T>;
|
|
3
|
-
namespace promiseTry {
|
|
4
|
-
function shim(): void;
|
|
5
|
-
}
|
|
6
|
-
export default promiseTry;
|
|
7
|
-
}
|
|
1
|
+
declare module "promise.try" {
|
|
2
|
+
function promiseTry<T>(callback: () => T | PromiseLike<T>): Promise<T>;
|
|
3
|
+
namespace promiseTry {
|
|
4
|
+
function shim(): void;
|
|
5
|
+
}
|
|
6
|
+
export default promiseTry;
|
|
7
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { complete, type Api, type Model, type ProviderHeaders } from "@earendil-works/pi-ai/compat";
|
|
2
|
-
type SummaryGenerationContext = Pick<import("@earendil-works/pi-coding-agent").ExtensionContext, "model" | "modelRegistry" | "cwd" | "isProjectTrusted">;
|
|
3
|
-
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
|
|
4
|
-
|
|
5
|
-
async function resolveFirstAvailableModel(
|
|
6
|
-
ctx: SummaryGenerationContext,
|
|
7
|
-
candidates: Array<{ provider: string; id: string }>,
|
|
8
|
-
): Promise<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }> {
|
|
9
|
-
const enabledModelPatterns = loadEnabledModelPatterns(ctx);
|
|
10
|
-
for (const { provider, id } of candidates) {
|
|
11
|
-
const model = findModelWithProviderRouting(ctx.modelRegistry, provider, id);
|
|
12
|
-
if (!model || !modelMatchesEnabledPatterns(model, enabledModelPatterns)) continue;
|
|
13
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
14
|
-
if (auth.ok && auth.apiKey) return { model, apiKey: auth.apiKey, headers: auth.headers };
|
|
15
|
-
}
|
|
16
|
-
throw new Error(`No enabled model available: ${candidates.map(candidate => `${candidate.provider}/${candidate.id}`).join(", ")}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function rewriteSearchQuery(
|
|
20
|
-
query: string,
|
|
21
|
-
ctx: SummaryGenerationContext,
|
|
22
|
-
signal: AbortSignal,
|
|
23
|
-
): Promise<string> {
|
|
24
|
-
const { model, apiKey, headers } = await resolveFirstAvailableModel(ctx, [
|
|
25
|
-
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
26
|
-
{ provider: "google", id: "gemini-3.6-flash" },
|
|
27
|
-
{ provider: "openai", id: "gpt-5-mini" },
|
|
28
|
-
]);
|
|
29
|
-
const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
|
|
30
|
-
const usesRegistryComplete = typeof registry.complete === "function";
|
|
31
|
-
const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : complete;
|
|
32
|
-
const response = await completeFn(
|
|
33
|
-
model,
|
|
34
|
-
{
|
|
35
|
-
messages: [{
|
|
36
|
-
role: "user",
|
|
37
|
-
content: [{ type: "text", text: `Rewrite this web search query to get better, more specific results. Add relevant year qualifiers, precise technical terms, and specificity. Return ONLY the improved query text, nothing else.\n\nQuery: ${query}` }],
|
|
38
|
-
timestamp: Date.now(),
|
|
39
|
-
}],
|
|
40
|
-
},
|
|
41
|
-
usesRegistryComplete ? { signal } : { apiKey, headers, signal },
|
|
42
|
-
);
|
|
43
|
-
if (response.stopReason === "aborted") throw new Error("Aborted");
|
|
44
|
-
const contentParts = Array.isArray(response.content) ? response.content : [];
|
|
45
|
-
const text = contentParts
|
|
46
|
-
.map((part: unknown) => part && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { text?: unknown }).text === "string" ? (part as { text: string }).text : "")
|
|
47
|
-
.join("")
|
|
48
|
-
.trim();
|
|
49
|
-
if (!text) throw new Error("Rewrite returned empty response");
|
|
50
|
-
return text;
|
|
51
|
-
}
|
|
1
|
+
import { complete, type Api, type Model, type ProviderHeaders } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
type SummaryGenerationContext = Pick<import("@earendil-works/pi-coding-agent").ExtensionContext, "model" | "modelRegistry" | "cwd" | "isProjectTrusted">;
|
|
3
|
+
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
|
|
4
|
+
|
|
5
|
+
async function resolveFirstAvailableModel(
|
|
6
|
+
ctx: SummaryGenerationContext,
|
|
7
|
+
candidates: Array<{ provider: string; id: string }>,
|
|
8
|
+
): Promise<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }> {
|
|
9
|
+
const enabledModelPatterns = loadEnabledModelPatterns(ctx);
|
|
10
|
+
for (const { provider, id } of candidates) {
|
|
11
|
+
const model = findModelWithProviderRouting(ctx.modelRegistry, provider, id);
|
|
12
|
+
if (!model || !modelMatchesEnabledPatterns(model, enabledModelPatterns)) continue;
|
|
13
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
14
|
+
if (auth.ok && auth.apiKey) return { model, apiKey: auth.apiKey, headers: auth.headers };
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`No enabled model available: ${candidates.map(candidate => `${candidate.provider}/${candidate.id}`).join(", ")}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function rewriteSearchQuery(
|
|
20
|
+
query: string,
|
|
21
|
+
ctx: SummaryGenerationContext,
|
|
22
|
+
signal: AbortSignal,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
const { model, apiKey, headers } = await resolveFirstAvailableModel(ctx, [
|
|
25
|
+
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
26
|
+
{ provider: "google", id: "gemini-3.6-flash" },
|
|
27
|
+
{ provider: "openai", id: "gpt-5-mini" },
|
|
28
|
+
]);
|
|
29
|
+
const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
|
|
30
|
+
const usesRegistryComplete = typeof registry.complete === "function";
|
|
31
|
+
const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : complete;
|
|
32
|
+
const response = await completeFn(
|
|
33
|
+
model,
|
|
34
|
+
{
|
|
35
|
+
messages: [{
|
|
36
|
+
role: "user",
|
|
37
|
+
content: [{ type: "text", text: `Rewrite this web search query to get better, more specific results. Add relevant year qualifiers, precise technical terms, and specificity. Return ONLY the improved query text, nothing else.\n\nQuery: ${query}` }],
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
}],
|
|
40
|
+
},
|
|
41
|
+
usesRegistryComplete ? { signal } : { apiKey, headers, signal },
|
|
42
|
+
);
|
|
43
|
+
if (response.stopReason === "aborted") throw new Error("Aborted");
|
|
44
|
+
const contentParts = Array.isArray(response.content) ? response.content : [];
|
|
45
|
+
const text = contentParts
|
|
46
|
+
.map((part: unknown) => part && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { text?: unknown }).text === "string" ? (part as { text: string }).text : "")
|
|
47
|
+
.join("")
|
|
48
|
+
.trim();
|
|
49
|
+
if (!text) throw new Error("Rewrite returned empty response");
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free renderer for web_search error / cancel results.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS EXISTS: the upstream web_search `renderResult` (index.ts) early-returns
|
|
5
|
-
* a SINGLE line on the error/cancel path, before the collapsed/expanded branch.
|
|
6
|
-
* That makes Ctrl+O (app.tools.expand) flip `expanded` with zero visible effect —
|
|
7
|
-
* a dead-end with no diagnostics. It also discards the partial search results
|
|
8
|
-
* gathered before the cancel, so even an expandable view would have nothing to show.
|
|
9
|
-
*
|
|
10
|
-
* This module is the single source of truth for the error/cancel render PLAN
|
|
11
|
-
* (plain strings, no theme/ANSI), so it is unit-testable without pi's runtime
|
|
12
|
-
* (@mariozechner/pi-tui Text/Box). index.ts.renderResult delegates to it and only
|
|
13
|
-
* applies theme colors + creates Text/Box components.
|
|
14
|
-
*
|
|
15
|
-
* Contract for callers:
|
|
16
|
-
* const plan = buildSearchErrorPlan(details);
|
|
17
|
-
* if (plan === null) ... // not an error/cancel result; use the normal renderer
|
|
18
|
-
* // collapsed: [statusLine, ...plan.collapsed, plan.expandHint].filter(Boolean)
|
|
19
|
-
* // expanded: plan.expanded
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export interface CancelledQueryDetail {
|
|
23
|
-
query: string;
|
|
24
|
-
provider: string | null;
|
|
25
|
-
error: string | null; // null = completed ok; string = per-query error
|
|
26
|
-
resultCount: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface SearchErrorDetails {
|
|
30
|
-
/** The headline error/cancel message, e.g. "Search cancelled (stale)." */
|
|
31
|
-
error?: string;
|
|
32
|
-
cancelled?: boolean;
|
|
33
|
-
cancelReason?: string;
|
|
34
|
-
/** Did the search ever establish a connection / make progress? */
|
|
35
|
-
browserConnected?: boolean;
|
|
36
|
-
/** Age (ms) of the last progress heartbeat at cancel time, if known. */
|
|
37
|
-
lastHeartbeatAgeMs?: number | null;
|
|
38
|
-
/** Total queries the user requested. */
|
|
39
|
-
queryCount?: number;
|
|
40
|
-
/** Partial per-query results gathered before the cancel/error. */
|
|
41
|
-
cancelledQueries?: CancelledQueryDetail[];
|
|
42
|
-
/** Arbitrary extra diagnostic lines (e.g. URLs, response id) for non-cancel errors
|
|
43
|
-
* like fetch_content / get_search_content. Shown in the expanded view. */
|
|
44
|
-
extraLines?: string[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface SearchErrorPlan {
|
|
48
|
-
/** Full diagnostic block, shown when expanded (Ctrl+O). */
|
|
49
|
-
expanded: string[];
|
|
50
|
-
/** Short preview lines, shown under the headline when collapsed. */
|
|
51
|
-
collapsed: string[];
|
|
52
|
-
/** The "... (N more lines, ctrl+o to expand)" hint, or null if nothing is hidden. */
|
|
53
|
-
expandHint: string | null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function truncate(text: string, max: number): string {
|
|
57
|
-
return text.length > max ? text.slice(0, max - 1) + "\u2026" : text;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Build the error/cancel render plan. Returns null when `details` carries no
|
|
62
|
-
* error/cancel signal (so the caller falls through to the normal success renderer).
|
|
63
|
-
*/
|
|
64
|
-
export function buildSearchErrorPlan(details: SearchErrorDetails | undefined | null): SearchErrorPlan | null {
|
|
65
|
-
if (!details || (!details.error && !details.cancelled)) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const headline = details.error ?? "Search cancelled.";
|
|
70
|
-
const queries = details.cancelledQueries ?? [];
|
|
71
|
-
const queryCount = typeof details.queryCount === "number" && details.queryCount > 0
|
|
72
|
-
? details.queryCount
|
|
73
|
-
: queries.length;
|
|
74
|
-
const done = queries.length;
|
|
75
|
-
const errored = queries.filter(q => q.error).length;
|
|
76
|
-
|
|
77
|
-
// Rich diagnostics only make sense when there is something to diagnose: a
|
|
78
|
-
// cancelled result with partial data, OR a non-cancel error that carries
|
|
79
|
-
// extra detail (urls/response-id for fetch_content, the failed query for
|
|
80
|
-
// get_search_content). A bare argument error (e.g. "No URL
|
|
81
|
-
// provided") stays a clean single line -- no noise.
|
|
82
|
-
const extras = details.extraLines ?? [];
|
|
83
|
-
const rich = details.cancelled === true || queries.length > 0 || extras.length > 0;
|
|
84
|
-
if (!rich) {
|
|
85
|
-
return { expanded: [headline], collapsed: [], expandHint: null };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// --- diagnostics block (expanded): cancel-specific only. For non-cancel
|
|
89
|
-
// errors (fetch_content/get_search_content) there is no browser or
|
|
90
|
-
// query state to report beyond the queries themselves, so we skip this block and show only Details. ---
|
|
91
|
-
const expanded: string[] = [headline, ""];
|
|
92
|
-
|
|
93
|
-
if (details.cancelled === true || queries.length > 0) {
|
|
94
|
-
const diag: string[] = [];
|
|
95
|
-
if (details.cancelled) {
|
|
96
|
-
diag.push(`cancel reason : ${details.cancelReason ?? "unknown"}`);
|
|
97
|
-
}
|
|
98
|
-
// Browser connection state — the most common stale cause (page never opened).
|
|
99
|
-
const browserLabel = details.browserConnected === undefined
|
|
100
|
-
? "unknown"
|
|
101
|
-
: details.browserConnected
|
|
102
|
-
? "connected"
|
|
103
|
-
: "never connected";
|
|
104
|
-
diag.push(`browser : ${browserLabel}`);
|
|
105
|
-
if (typeof details.lastHeartbeatAgeMs === "number" && Number.isFinite(details.lastHeartbeatAgeMs)) {
|
|
106
|
-
diag.push(`last heartbeat : ${Math.round(details.lastHeartbeatAgeMs / 1000)}s ago`);
|
|
107
|
-
}
|
|
108
|
-
if (queryCount > 0) {
|
|
109
|
-
diag.push(`queries started : ${queryCount}`);
|
|
110
|
-
diag.push(`queries done : ${done}`);
|
|
111
|
-
if (errored > 0) diag.push(`queries errored : ${errored}`);
|
|
112
|
-
}
|
|
113
|
-
expanded.push("Diagnostics:");
|
|
114
|
-
for (const line of diag) expanded.push(` ${line}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// --- per-query results gathered before cancel ---
|
|
118
|
-
if (queries.length > 0) {
|
|
119
|
-
expanded.push("");
|
|
120
|
-
expanded.push("Per-query results (gathered before cancel):");
|
|
121
|
-
for (const q of queries) {
|
|
122
|
-
const dq = truncate(q.query, 52);
|
|
123
|
-
const tag = q.error ? "[err] " : "[ok] ";
|
|
124
|
-
const provider = q.provider ? ` (${q.provider})` : "";
|
|
125
|
-
const tail = q.error
|
|
126
|
-
? `\u2014 ${truncate(q.error, 60)}`
|
|
127
|
-
: `\u2014 ${q.resultCount} source${q.resultCount === 1 ? "" : "s"}`;
|
|
128
|
-
expanded.push(` ${tag}"${dq}"${provider} ${tail}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// --- arbitrary extra detail (non-cancel errors) ---
|
|
133
|
-
if (extras.length > 0) {
|
|
134
|
-
expanded.push("");
|
|
135
|
-
expanded.push("Details:");
|
|
136
|
-
for (const e of extras) expanded.push(` ${e}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// --- collapsed preview ---
|
|
140
|
-
const collapsed: string[] = [];
|
|
141
|
-
const parts: string[] = [];
|
|
142
|
-
if (queryCount > 0) {
|
|
143
|
-
parts.push(`${done}/${queryCount} queries completed`);
|
|
144
|
-
}
|
|
145
|
-
if (errored > 0) {
|
|
146
|
-
parts.push(`${errored} errored`);
|
|
147
|
-
}
|
|
148
|
-
if (details.browserConnected === false) {
|
|
149
|
-
parts.push("browser never connected");
|
|
150
|
-
} else if (details.cancelReason) {
|
|
151
|
-
parts.push(`reason: ${details.cancelReason}`);
|
|
152
|
-
}
|
|
153
|
-
if (parts.length > 0) {
|
|
154
|
-
collapsed.push(parts.join("; ") + ".");
|
|
155
|
-
}
|
|
156
|
-
// For non-cancel errors with extra detail, preview the first detail line.
|
|
157
|
-
if (collapsed.length === 0 && extras.length > 0) {
|
|
158
|
-
for (const e of extras.slice(0, 2)) {
|
|
159
|
-
collapsed.push(truncate(e, 100));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// --- expand hint ---
|
|
164
|
-
const hiddenLines = Math.max(0, expanded.length - (1 + collapsed.length)); // headline + preview shown when collapsed
|
|
165
|
-
const expandHint = hiddenLines > 0
|
|
166
|
-
? `... (${hiddenLines} more lines, ${expanded.length} total, ctrl+o to expand)`
|
|
167
|
-
: null;
|
|
168
|
-
|
|
169
|
-
return { expanded, collapsed, expandHint };
|
|
170
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Pure, dependency-free renderer for web_search error / cancel results.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS: the upstream web_search `renderResult` (index.ts) early-returns
|
|
5
|
+
* a SINGLE line on the error/cancel path, before the collapsed/expanded branch.
|
|
6
|
+
* That makes Ctrl+O (app.tools.expand) flip `expanded` with zero visible effect —
|
|
7
|
+
* a dead-end with no diagnostics. It also discards the partial search results
|
|
8
|
+
* gathered before the cancel, so even an expandable view would have nothing to show.
|
|
9
|
+
*
|
|
10
|
+
* This module is the single source of truth for the error/cancel render PLAN
|
|
11
|
+
* (plain strings, no theme/ANSI), so it is unit-testable without pi's runtime
|
|
12
|
+
* (@mariozechner/pi-tui Text/Box). index.ts.renderResult delegates to it and only
|
|
13
|
+
* applies theme colors + creates Text/Box components.
|
|
14
|
+
*
|
|
15
|
+
* Contract for callers:
|
|
16
|
+
* const plan = buildSearchErrorPlan(details);
|
|
17
|
+
* if (plan === null) ... // not an error/cancel result; use the normal renderer
|
|
18
|
+
* // collapsed: [statusLine, ...plan.collapsed, plan.expandHint].filter(Boolean)
|
|
19
|
+
* // expanded: plan.expanded
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface CancelledQueryDetail {
|
|
23
|
+
query: string;
|
|
24
|
+
provider: string | null;
|
|
25
|
+
error: string | null; // null = completed ok; string = per-query error
|
|
26
|
+
resultCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SearchErrorDetails {
|
|
30
|
+
/** The headline error/cancel message, e.g. "Search cancelled (stale)." */
|
|
31
|
+
error?: string;
|
|
32
|
+
cancelled?: boolean;
|
|
33
|
+
cancelReason?: string;
|
|
34
|
+
/** Did the search ever establish a connection / make progress? */
|
|
35
|
+
browserConnected?: boolean;
|
|
36
|
+
/** Age (ms) of the last progress heartbeat at cancel time, if known. */
|
|
37
|
+
lastHeartbeatAgeMs?: number | null;
|
|
38
|
+
/** Total queries the user requested. */
|
|
39
|
+
queryCount?: number;
|
|
40
|
+
/** Partial per-query results gathered before the cancel/error. */
|
|
41
|
+
cancelledQueries?: CancelledQueryDetail[];
|
|
42
|
+
/** Arbitrary extra diagnostic lines (e.g. URLs, response id) for non-cancel errors
|
|
43
|
+
* like fetch_content / get_search_content. Shown in the expanded view. */
|
|
44
|
+
extraLines?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SearchErrorPlan {
|
|
48
|
+
/** Full diagnostic block, shown when expanded (Ctrl+O). */
|
|
49
|
+
expanded: string[];
|
|
50
|
+
/** Short preview lines, shown under the headline when collapsed. */
|
|
51
|
+
collapsed: string[];
|
|
52
|
+
/** The "... (N more lines, ctrl+o to expand)" hint, or null if nothing is hidden. */
|
|
53
|
+
expandHint: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function truncate(text: string, max: number): string {
|
|
57
|
+
return text.length > max ? text.slice(0, max - 1) + "\u2026" : text;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the error/cancel render plan. Returns null when `details` carries no
|
|
62
|
+
* error/cancel signal (so the caller falls through to the normal success renderer).
|
|
63
|
+
*/
|
|
64
|
+
export function buildSearchErrorPlan(details: SearchErrorDetails | undefined | null): SearchErrorPlan | null {
|
|
65
|
+
if (!details || (!details.error && !details.cancelled)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const headline = details.error ?? "Search cancelled.";
|
|
70
|
+
const queries = details.cancelledQueries ?? [];
|
|
71
|
+
const queryCount = typeof details.queryCount === "number" && details.queryCount > 0
|
|
72
|
+
? details.queryCount
|
|
73
|
+
: queries.length;
|
|
74
|
+
const done = queries.length;
|
|
75
|
+
const errored = queries.filter(q => q.error).length;
|
|
76
|
+
|
|
77
|
+
// Rich diagnostics only make sense when there is something to diagnose: a
|
|
78
|
+
// cancelled result with partial data, OR a non-cancel error that carries
|
|
79
|
+
// extra detail (urls/response-id for fetch_content, the failed query for
|
|
80
|
+
// get_search_content). A bare argument error (e.g. "No URL
|
|
81
|
+
// provided") stays a clean single line -- no noise.
|
|
82
|
+
const extras = details.extraLines ?? [];
|
|
83
|
+
const rich = details.cancelled === true || queries.length > 0 || extras.length > 0;
|
|
84
|
+
if (!rich) {
|
|
85
|
+
return { expanded: [headline], collapsed: [], expandHint: null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- diagnostics block (expanded): cancel-specific only. For non-cancel
|
|
89
|
+
// errors (fetch_content/get_search_content) there is no browser or
|
|
90
|
+
// query state to report beyond the queries themselves, so we skip this block and show only Details. ---
|
|
91
|
+
const expanded: string[] = [headline, ""];
|
|
92
|
+
|
|
93
|
+
if (details.cancelled === true || queries.length > 0) {
|
|
94
|
+
const diag: string[] = [];
|
|
95
|
+
if (details.cancelled) {
|
|
96
|
+
diag.push(`cancel reason : ${details.cancelReason ?? "unknown"}`);
|
|
97
|
+
}
|
|
98
|
+
// Browser connection state — the most common stale cause (page never opened).
|
|
99
|
+
const browserLabel = details.browserConnected === undefined
|
|
100
|
+
? "unknown"
|
|
101
|
+
: details.browserConnected
|
|
102
|
+
? "connected"
|
|
103
|
+
: "never connected";
|
|
104
|
+
diag.push(`browser : ${browserLabel}`);
|
|
105
|
+
if (typeof details.lastHeartbeatAgeMs === "number" && Number.isFinite(details.lastHeartbeatAgeMs)) {
|
|
106
|
+
diag.push(`last heartbeat : ${Math.round(details.lastHeartbeatAgeMs / 1000)}s ago`);
|
|
107
|
+
}
|
|
108
|
+
if (queryCount > 0) {
|
|
109
|
+
diag.push(`queries started : ${queryCount}`);
|
|
110
|
+
diag.push(`queries done : ${done}`);
|
|
111
|
+
if (errored > 0) diag.push(`queries errored : ${errored}`);
|
|
112
|
+
}
|
|
113
|
+
expanded.push("Diagnostics:");
|
|
114
|
+
for (const line of diag) expanded.push(` ${line}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- per-query results gathered before cancel ---
|
|
118
|
+
if (queries.length > 0) {
|
|
119
|
+
expanded.push("");
|
|
120
|
+
expanded.push("Per-query results (gathered before cancel):");
|
|
121
|
+
for (const q of queries) {
|
|
122
|
+
const dq = truncate(q.query, 52);
|
|
123
|
+
const tag = q.error ? "[err] " : "[ok] ";
|
|
124
|
+
const provider = q.provider ? ` (${q.provider})` : "";
|
|
125
|
+
const tail = q.error
|
|
126
|
+
? `\u2014 ${truncate(q.error, 60)}`
|
|
127
|
+
: `\u2014 ${q.resultCount} source${q.resultCount === 1 ? "" : "s"}`;
|
|
128
|
+
expanded.push(` ${tag}"${dq}"${provider} ${tail}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- arbitrary extra detail (non-cancel errors) ---
|
|
133
|
+
if (extras.length > 0) {
|
|
134
|
+
expanded.push("");
|
|
135
|
+
expanded.push("Details:");
|
|
136
|
+
for (const e of extras) expanded.push(` ${e}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- collapsed preview ---
|
|
140
|
+
const collapsed: string[] = [];
|
|
141
|
+
const parts: string[] = [];
|
|
142
|
+
if (queryCount > 0) {
|
|
143
|
+
parts.push(`${done}/${queryCount} queries completed`);
|
|
144
|
+
}
|
|
145
|
+
if (errored > 0) {
|
|
146
|
+
parts.push(`${errored} errored`);
|
|
147
|
+
}
|
|
148
|
+
if (details.browserConnected === false) {
|
|
149
|
+
parts.push("browser never connected");
|
|
150
|
+
} else if (details.cancelReason) {
|
|
151
|
+
parts.push(`reason: ${details.cancelReason}`);
|
|
152
|
+
}
|
|
153
|
+
if (parts.length > 0) {
|
|
154
|
+
collapsed.push(parts.join("; ") + ".");
|
|
155
|
+
}
|
|
156
|
+
// For non-cancel errors with extra detail, preview the first detail line.
|
|
157
|
+
if (collapsed.length === 0 && extras.length > 0) {
|
|
158
|
+
for (const e of extras.slice(0, 2)) {
|
|
159
|
+
collapsed.push(truncate(e, 100));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- expand hint ---
|
|
164
|
+
const hiddenLines = Math.max(0, expanded.length - (1 + collapsed.length)); // headline + preview shown when collapsed
|
|
165
|
+
const expandHint = hiddenLines > 0
|
|
166
|
+
? `... (${hiddenLines} more lines, ${expanded.length} total, ctrl+o to expand)`
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
return { expanded, collapsed, expandHint };
|
|
170
|
+
}
|