@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.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/CHANGELOG.md +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
package/src/web/search/render.ts
CHANGED
|
@@ -15,23 +15,16 @@ import {
|
|
|
15
15
|
formatMoreItems,
|
|
16
16
|
formatStatusIcon,
|
|
17
17
|
getDomain,
|
|
18
|
-
getPreviewLines,
|
|
19
18
|
PREVIEW_LIMITS,
|
|
20
|
-
|
|
19
|
+
replaceTabs,
|
|
21
20
|
truncateToWidth,
|
|
22
21
|
} from "../../tools/render-utils";
|
|
23
|
-
import { renderStatusLine, renderTreeList } from "../../tui";
|
|
24
|
-
import { CachedOutputBlock } from "../../tui/output-block";
|
|
22
|
+
import { renderStatusLine, renderTreeList, urlHyperlink } from "../../tui";
|
|
23
|
+
import { CachedOutputBlock, markFramedBlockComponent } from "../../tui/output-block";
|
|
25
24
|
import { getSearchProviderLabel } from "./provider";
|
|
26
25
|
import type { SearchResponse } from "./types";
|
|
27
26
|
|
|
28
|
-
const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
|
|
29
|
-
const MAX_SNIPPET_LINES = 2;
|
|
30
|
-
const MAX_SNIPPET_LINE_LEN = TRUNCATE_LENGTHS.LINE;
|
|
31
27
|
const MAX_COLLAPSED_ITEMS = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
32
|
-
const MAX_QUERY_PREVIEW = 2;
|
|
33
|
-
const MAX_QUERY_LEN = 90;
|
|
34
|
-
const MAX_REQUEST_ID_LEN = 36;
|
|
35
28
|
|
|
36
29
|
function renderFallbackText(contentText: string, expanded: boolean, theme: Theme): Component {
|
|
37
30
|
const lines = contentText.split("\n").filter(line => line.trim());
|
|
@@ -66,6 +59,21 @@ export interface SearchRenderDetails {
|
|
|
66
59
|
error?: string;
|
|
67
60
|
}
|
|
68
61
|
|
|
62
|
+
/** Render a web search failure as a framed error panel, matching the success layout. */
|
|
63
|
+
function renderSearchErrorPanel(message: string, providerLabel: string | undefined, theme: Theme): Component {
|
|
64
|
+
const header = renderStatusLine({ icon: "error", title: "Web Search", description: providerLabel }, theme);
|
|
65
|
+
const body = theme.fg("error", `Error: ${replaceTabs(message)}`);
|
|
66
|
+
const outputBlock = new CachedOutputBlock();
|
|
67
|
+
return markFramedBlockComponent({
|
|
68
|
+
render(width: number): string[] {
|
|
69
|
+
return outputBlock.render({ header, state: "error", sections: [{ lines: [body] }], width }, theme);
|
|
70
|
+
},
|
|
71
|
+
invalidate() {
|
|
72
|
+
outputBlock.invalidate();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
/** Render web search result with tree-based layout */
|
|
70
78
|
export function renderSearchResult(
|
|
71
79
|
result: { content: Array<{ type: string; text?: string }>; details?: SearchRenderDetails },
|
|
@@ -78,9 +86,12 @@ export function renderSearchResult(
|
|
|
78
86
|
): Component {
|
|
79
87
|
const details = result.details;
|
|
80
88
|
|
|
81
|
-
// Handle error case
|
|
89
|
+
// Handle error case as a framed panel, matching the success layout.
|
|
82
90
|
if (details?.error) {
|
|
83
|
-
|
|
91
|
+
const errorProvider = details.response?.provider;
|
|
92
|
+
const errorProviderLabel =
|
|
93
|
+
errorProvider && errorProvider !== "none" ? getSearchProviderLabel(errorProvider) : undefined;
|
|
94
|
+
return renderSearchErrorPanel(details.error, errorProviderLabel, theme);
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
const rawText = result.content?.find(block => block.type === "text")?.text?.trim() ?? "";
|
|
@@ -91,8 +102,6 @@ export function renderSearchResult(
|
|
|
91
102
|
|
|
92
103
|
const sources = Array.isArray(response.sources) ? response.sources : [];
|
|
93
104
|
const sourceCount = sources.length;
|
|
94
|
-
const citations = Array.isArray(response.citations) ? response.citations : [];
|
|
95
|
-
const citationCount = citations.length;
|
|
96
105
|
const searchQueries = Array.isArray(response.searchQueries)
|
|
97
106
|
? response.searchQueries.filter(item => typeof item === "string")
|
|
98
107
|
: [];
|
|
@@ -118,16 +127,11 @@ export function renderSearchResult(
|
|
|
118
127
|
theme,
|
|
119
128
|
);
|
|
120
129
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
127
|
-
if (response.model) metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
|
|
128
|
-
metaLines.push(`${theme.fg("muted", "Sources:")} ${theme.fg("text", String(sourceCount))}`);
|
|
129
|
-
if (citationCount > 0)
|
|
130
|
-
metaLines.push(`${theme.fg("muted", "Citations:")} ${theme.fg("text", String(citationCount))}`);
|
|
130
|
+
const authShort =
|
|
131
|
+
response.authMode === "oauth" ? "OAuth" : response.authMode === "api_key" ? "API" : response.authMode;
|
|
132
|
+
let providerInfo = response.model ? `${response.model} @ ${providerLabel}` : providerLabel;
|
|
133
|
+
if (authShort) providerInfo += ` (${authShort})`;
|
|
134
|
+
const metaLines: string[] = [`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerInfo)}`];
|
|
131
135
|
if (response.usage) {
|
|
132
136
|
const usageParts: string[] = [];
|
|
133
137
|
if (response.usage.inputTokens !== undefined) usageParts.push(`in ${response.usage.inputTokens}`);
|
|
@@ -137,22 +141,11 @@ export function renderSearchResult(
|
|
|
137
141
|
if (usageParts.length > 0)
|
|
138
142
|
metaLines.push(`${theme.fg("muted", "Usage:")} ${theme.fg("text", usageParts.join(theme.sep.dot))}`);
|
|
139
143
|
}
|
|
140
|
-
if (response.requestId) {
|
|
141
|
-
metaLines.push(
|
|
142
|
-
`${theme.fg("muted", "Request:")} ${theme.fg("text", truncateToWidth(response.requestId, MAX_REQUEST_ID_LEN))}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
if (searchQueries.length > 0) {
|
|
146
|
-
const queriesPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
|
|
147
|
-
const queryList = queriesPreview.map(q => truncateToWidth(q, MAX_QUERY_LEN));
|
|
148
|
-
const suffix = searchQueries.length > queriesPreview.length ? "…" : "";
|
|
149
|
-
metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
|
|
150
|
-
}
|
|
151
144
|
|
|
152
145
|
const answerMarkdown = contentText ? new Markdown(contentText, 0, 0, getMarkdownTheme()) : undefined;
|
|
153
146
|
const outputBlock = new CachedOutputBlock();
|
|
154
147
|
|
|
155
|
-
return {
|
|
148
|
+
return markFramedBlockComponent({
|
|
156
149
|
render(width: number): string[] {
|
|
157
150
|
// Read mutable state at render time
|
|
158
151
|
const { expanded } = options;
|
|
@@ -163,15 +156,15 @@ export function renderSearchResult(
|
|
|
163
156
|
let answerLines: string[];
|
|
164
157
|
if (renderedAnswer.length === 0) {
|
|
165
158
|
answerLines = [theme.fg("muted", "No answer text returned")];
|
|
166
|
-
} else if (expanded) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const collapsedCap = args?.maxAnswerLines ?? MAX_COLLAPSED_ANSWER_LINES;
|
|
170
|
-
answerLines = renderedAnswer.slice(0, collapsedCap);
|
|
159
|
+
} else if (args?.maxAnswerLines !== undefined && !expanded) {
|
|
160
|
+
// CLI compact mode (`omp q`) caps the answer; the TUI passes no cap and shows it in full.
|
|
161
|
+
answerLines = renderedAnswer.slice(0, args.maxAnswerLines);
|
|
171
162
|
const remaining = renderedAnswer.length - answerLines.length;
|
|
172
163
|
if (remaining > 0) {
|
|
173
164
|
answerLines.push(theme.fg("muted", formatMoreItems(remaining, "line")));
|
|
174
165
|
}
|
|
166
|
+
} else {
|
|
167
|
+
answerLines = renderedAnswer;
|
|
175
168
|
}
|
|
176
169
|
|
|
177
170
|
const sourceTree = renderTreeList(
|
|
@@ -187,30 +180,22 @@ export function renderSearchResult(
|
|
|
187
180
|
: typeof src.url === "string" && src.url.trim()
|
|
188
181
|
? src.url
|
|
189
182
|
: "Untitled";
|
|
190
|
-
const title = truncateToWidth(titleText, MAX_SNIPPET_LINE_LEN);
|
|
191
183
|
const url = typeof src.url === "string" ? src.url : "";
|
|
192
184
|
const domain = url ? getDomain(url) : "";
|
|
193
185
|
const age =
|
|
194
186
|
formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
|
|
195
187
|
const metaParts: string[] = [];
|
|
196
188
|
if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
|
|
197
|
-
if (typeof src.author === "string" && src.author.trim())
|
|
198
|
-
metaParts.push(theme.fg("muted", truncateToWidth(src.author.trim(), 40)));
|
|
199
189
|
if (age) metaParts.push(theme.fg("muted", age));
|
|
200
190
|
const metaSep = theme.fg("dim", theme.sep.dot);
|
|
201
191
|
const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
srcLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (url) srcLines.push(theme.fg("mdLinkUrl", truncateToWidth(url, MAX_SNIPPET_LINE_LEN)));
|
|
213
|
-
return srcLines;
|
|
192
|
+
// One line per source: the title links to its URL, followed by domain · age.
|
|
193
|
+
// Reserve room for the box borders, the tree branch, and the meta suffix.
|
|
194
|
+
const lineBudget = Math.max(24, width - 6);
|
|
195
|
+
const titleBudget = Math.max(12, lineBudget - Bun.stringWidth(metaSuffix));
|
|
196
|
+
const title = theme.fg("accent", truncateToWidth(titleText, titleBudget));
|
|
197
|
+
const linkedTitle = url ? urlHyperlink(url, title) : title;
|
|
198
|
+
return [`${linkedTitle}${metaSuffix}`];
|
|
214
199
|
},
|
|
215
200
|
},
|
|
216
201
|
theme,
|
|
@@ -246,7 +231,7 @@ export function renderSearchResult(
|
|
|
246
231
|
invalidate() {
|
|
247
232
|
outputBlock.invalidate();
|
|
248
233
|
},
|
|
249
|
-
};
|
|
234
|
+
});
|
|
250
235
|
}
|
|
251
236
|
|
|
252
237
|
/** Render web search call (query preview) */
|
package/src/web/search/types.ts
CHANGED
|
@@ -33,7 +33,11 @@ export const SEARCH_PROVIDER_OPTIONS = [
|
|
|
33
33
|
description: "Automatically uses the first configured web-search provider",
|
|
34
34
|
},
|
|
35
35
|
{ value: "tavily", label: "Tavily", description: "Requires TAVILY_API_KEY" },
|
|
36
|
-
{
|
|
36
|
+
{
|
|
37
|
+
value: "perplexity",
|
|
38
|
+
label: "Perplexity",
|
|
39
|
+
description: "Uses auth when configured; explicit selection falls back to anonymous search",
|
|
40
|
+
},
|
|
37
41
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
38
42
|
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
39
43
|
{ value: "kimi", label: "Kimi", description: "Requires MOONSHOT_SEARCH_API_KEY or MOONSHOT_API_KEY" },
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keepalive for in-flight host-side eval bridge calls.
|
|
3
|
-
*
|
|
4
|
-
* The eval watchdog ({@link ../tools/eval IdleTimeout}) caps a cell's `timeout`
|
|
5
|
-
* as a wall-clock budget on the cell's *own* work, but pauses that budget while
|
|
6
|
-
* a host-side `agent()`/`parallel()` (via `runSubprocess`) or `llm()` (a single
|
|
7
|
-
* completion) call is in flight. Those calls are the only thing that re-arms the
|
|
8
|
-
* watchdog — and they can run for long stretches with **no** status of their own
|
|
9
|
-
* (a subagent's time-to-first-token on a reasoning model, a long quiet nested
|
|
10
|
-
* tool, or the entire body of a oneshot `llm()` call). Without a keepalive the
|
|
11
|
-
* watchdog would mistake that delegated work for the cell stalling and abort it
|
|
12
|
-
* mid-flight, killing the subagent.
|
|
13
|
-
*
|
|
14
|
-
* {@link withBridgeHeartbeat} bridges that gap by emitting a synthetic
|
|
15
|
-
* {@link EVAL_HEARTBEAT_OP} status event immediately when the call begins and
|
|
16
|
-
* then on a fixed cadence until it settles. The event rides the same
|
|
17
|
-
* `emitStatus → onStatus` channel both runtimes already forward, so it re-arms
|
|
18
|
-
* the watchdog without any new plumbing. The heartbeat is the *sole* signal that
|
|
19
|
-
* extends the budget: consumers MUST treat it as a pure keepalive — bump the
|
|
20
|
-
* watchdog and drop it (never persist or render it) — see the executor display
|
|
21
|
-
* sinks and the eval tool's `onStatus` handler. Every other status event
|
|
22
|
-
* (compute helpers, `log()`/`phase()`, tool results) counts against the budget.
|
|
23
|
-
*/
|
|
24
|
-
import type { JsStatusEvent } from "./js/shared/types";
|
|
25
|
-
/**
|
|
26
|
-
* Synthetic status op emitted purely to keep the eval idle watchdog alive while
|
|
27
|
-
* a host-side bridge call is in flight. Carries no payload.
|
|
28
|
-
*/
|
|
29
|
-
export declare const EVAL_HEARTBEAT_OP = "heartbeat";
|
|
30
|
-
/**
|
|
31
|
-
* Test seam: override the heartbeat cadence so integration tests can exercise
|
|
32
|
-
* the keepalive within a sub-second idle budget. Pass no value to restore the
|
|
33
|
-
* production default.
|
|
34
|
-
*/
|
|
35
|
-
export declare function setBridgeHeartbeatIntervalMs(ms?: number): void;
|
|
36
|
-
/**
|
|
37
|
-
* Run {@link operation}, pumping {@link EVAL_HEARTBEAT_OP} status events through
|
|
38
|
-
* {@link emitStatus} — one immediately, then on a fixed cadence — until it
|
|
39
|
-
* settles. The immediate beat pauses the watchdog the instant the call begins,
|
|
40
|
-
* so a bridge call that starts close to the budget edge (after the cell already
|
|
41
|
-
* spent most of it computing) is not aborted before the first interval tick. A
|
|
42
|
-
* no-op wrapper when no `emitStatus` sink is wired (the heartbeat would reach
|
|
43
|
-
* nobody).
|
|
44
|
-
*/
|
|
45
|
-
export declare function withBridgeHeartbeat<T>(emitStatus: ((event: JsStatusEvent) => void) | undefined, operation: () => Promise<T>): Promise<T>;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { EVAL_HEARTBEAT_OP, setBridgeHeartbeatIntervalMs, withBridgeHeartbeat } from "../heartbeat";
|
|
3
|
-
import type { JsStatusEvent } from "../js/shared/types";
|
|
4
|
-
|
|
5
|
-
describe("withBridgeHeartbeat", () => {
|
|
6
|
-
afterEach(() => {
|
|
7
|
-
setBridgeHeartbeatIntervalMs();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("pumps heartbeat events on cadence while the operation is pending, then stops", async () => {
|
|
11
|
-
setBridgeHeartbeatIntervalMs(20);
|
|
12
|
-
const events: JsStatusEvent[] = [];
|
|
13
|
-
|
|
14
|
-
const value = await withBridgeHeartbeat(
|
|
15
|
-
event => events.push(event),
|
|
16
|
-
async () => {
|
|
17
|
-
await Bun.sleep(130);
|
|
18
|
-
return "done";
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
expect(value).toBe("done");
|
|
23
|
-
// ~6 ticks fit in 130ms at a 20ms cadence; assert it ticked repeatedly
|
|
24
|
-
// without pinning the exact count (scheduler jitter).
|
|
25
|
-
expect(events.length).toBeGreaterThanOrEqual(3);
|
|
26
|
-
expect(events.every(event => event.op === EVAL_HEARTBEAT_OP)).toBe(true);
|
|
27
|
-
|
|
28
|
-
// The interval is cleared once the operation settles: no further ticks.
|
|
29
|
-
const settledCount = events.length;
|
|
30
|
-
await Bun.sleep(80);
|
|
31
|
-
expect(events.length).toBe(settledCount);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("emits a heartbeat immediately so a bridge call extends the budget at once", async () => {
|
|
35
|
-
// Interval far longer than the operation: the only beat that can fire is
|
|
36
|
-
// the immediate one at call start. It must still reach the sink.
|
|
37
|
-
setBridgeHeartbeatIntervalMs(10_000);
|
|
38
|
-
const events: JsStatusEvent[] = [];
|
|
39
|
-
|
|
40
|
-
await withBridgeHeartbeat(
|
|
41
|
-
event => events.push(event),
|
|
42
|
-
async () => {
|
|
43
|
-
await Bun.sleep(30);
|
|
44
|
-
return "done";
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
expect(events.length).toBe(1);
|
|
49
|
-
expect(events[0]?.op).toBe(EVAL_HEARTBEAT_OP);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("runs the operation without emitting when no status sink is wired", async () => {
|
|
53
|
-
setBridgeHeartbeatIntervalMs(5);
|
|
54
|
-
let ran = 0;
|
|
55
|
-
|
|
56
|
-
const value = await withBridgeHeartbeat(undefined, async () => {
|
|
57
|
-
ran++;
|
|
58
|
-
await Bun.sleep(40);
|
|
59
|
-
return 42;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(value).toBe(42);
|
|
63
|
-
expect(ran).toBe(1);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("clears the heartbeat even when the operation throws", async () => {
|
|
67
|
-
setBridgeHeartbeatIntervalMs(15);
|
|
68
|
-
const events: JsStatusEvent[] = [];
|
|
69
|
-
|
|
70
|
-
await expect(
|
|
71
|
-
withBridgeHeartbeat(
|
|
72
|
-
event => events.push(event),
|
|
73
|
-
async () => {
|
|
74
|
-
await Bun.sleep(60);
|
|
75
|
-
throw new Error("boom");
|
|
76
|
-
},
|
|
77
|
-
),
|
|
78
|
-
).rejects.toThrow("boom");
|
|
79
|
-
|
|
80
|
-
const afterThrow = events.length;
|
|
81
|
-
await Bun.sleep(60);
|
|
82
|
-
expect(events.length).toBe(afterThrow);
|
|
83
|
-
});
|
|
84
|
-
});
|