@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.2
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 +110 -0
- package/dist/types/cli/file-processor.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +45 -3
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +2 -0
- package/dist/types/edit/file-read-cache.d.ts +15 -4
- package/dist/types/edit/index.d.ts +3 -8
- package/dist/types/edit/renderer.d.ts +1 -2
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
- package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
- package/dist/types/eval/js/shared/runtime.d.ts +14 -8
- package/dist/types/eval/py/executor.d.ts +1 -2
- package/dist/types/eval/py/kernel.d.ts +6 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -5
- package/dist/types/eval/session-id.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +1 -3
- package/dist/types/hashline/anchors.d.ts +15 -9
- package/dist/types/hashline/constants.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +1 -2
- package/dist/types/hashline/executor.d.ts +52 -0
- package/dist/types/hashline/hash.d.ts +44 -93
- package/dist/types/hashline/index.d.ts +2 -1
- package/dist/types/hashline/input.d.ts +2 -9
- package/dist/types/hashline/recovery.d.ts +3 -9
- package/dist/types/hashline/tokenizer.d.ts +91 -0
- package/dist/types/hashline/types.d.ts +5 -7
- package/dist/types/modes/components/extensions/types.d.ts +0 -4
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -15
- package/dist/types/session/agent-storage.d.ts +11 -10
- package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
- package/dist/types/slash-commands/types.d.ts +0 -5
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tool-discovery/tool-index.d.ts +0 -50
- package/dist/types/tools/index.d.ts +2 -8
- package/dist/types/tools/match-line-format.d.ts +4 -4
- package/dist/types/tools/output-schema-validator.d.ts +64 -0
- package/dist/types/tools/review.d.ts +13 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +4 -3
- package/dist/types/utils/edit-mode.d.ts +1 -1
- package/dist/types/web/kagi.d.ts +4 -2
- package/dist/types/web/parallel.d.ts +4 -3
- package/dist/types/web/scrapers/types.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +12 -4
- package/dist/types/web/search/provider.d.ts +2 -1
- package/dist/types/web/search/providers/anthropic.d.ts +9 -4
- package/dist/types/web/search/providers/base.d.ts +34 -2
- package/dist/types/web/search/providers/brave.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +13 -9
- package/dist/types/web/search/providers/exa.d.ts +10 -1
- package/dist/types/web/search/providers/gemini.d.ts +20 -23
- package/dist/types/web/search/providers/jina.d.ts +2 -1
- package/dist/types/web/search/providers/kagi.d.ts +4 -1
- package/dist/types/web/search/providers/kimi.d.ts +10 -1
- package/dist/types/web/search/providers/parallel.d.ts +3 -2
- package/dist/types/web/search/providers/perplexity.d.ts +5 -2
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +5 -8
- package/dist/types/web/search/providers/tavily.d.ts +11 -4
- package/dist/types/web/search/providers/utils.d.ts +8 -6
- package/dist/types/web/search/providers/zai.d.ts +12 -3
- package/package.json +7 -7
- package/src/cli/file-processor.ts +12 -2
- package/src/cli.ts +0 -8
- package/src/commands/commit.ts +8 -8
- package/src/config/prompt-templates.ts +6 -6
- package/src/config/settings-schema.ts +47 -3
- package/src/config/settings.ts +5 -5
- package/src/debug/raw-sse.ts +68 -3
- package/src/edit/file-read-cache.ts +68 -25
- package/src/edit/index.ts +6 -37
- package/src/edit/renderer.ts +9 -47
- package/src/edit/streaming.ts +43 -56
- package/src/eval/__tests__/shared-executors.test.ts +520 -0
- package/src/eval/js/context-manager.ts +64 -53
- package/src/eval/js/shared/local-module-loader.ts +265 -0
- package/src/eval/js/shared/prelude.txt +4 -0
- package/src/eval/js/shared/rewrite-imports.ts +85 -0
- package/src/eval/js/shared/runtime.ts +129 -86
- package/src/eval/js/worker-core.ts +23 -38
- package/src/eval/py/executor.ts +155 -84
- package/src/eval/py/kernel.ts +10 -1
- package/src/eval/py/prelude.py +22 -24
- package/src/eval/py/runner.py +203 -85
- package/src/eval/py/tool-bridge.ts +17 -10
- package/src/eval/session-id.ts +8 -0
- package/src/exec/bash-executor.ts +27 -16
- package/src/extensibility/extensions/runner.ts +0 -1
- package/src/extensibility/extensions/types.ts +1 -3
- package/src/hashline/anchors.ts +56 -65
- package/src/hashline/apply.ts +29 -31
- package/src/hashline/constants.ts +0 -3
- package/src/hashline/diff-preview.ts +4 -5
- package/src/hashline/diff.ts +30 -4
- package/src/hashline/execute.ts +91 -26
- package/src/hashline/executor.ts +239 -0
- package/src/hashline/grammar.lark +12 -10
- package/src/hashline/hash.ts +69 -114
- package/src/hashline/index.ts +2 -1
- package/src/hashline/input.ts +48 -41
- package/src/hashline/prefixes.ts +21 -11
- package/src/hashline/recovery.ts +63 -71
- package/src/hashline/stream.ts +2 -2
- package/src/hashline/tokenizer.ts +467 -0
- package/src/hashline/types.ts +6 -8
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/modes/components/extensions/types.ts +0 -5
- package/src/modes/components/session-observer-overlay.ts +11 -2
- package/src/modes/components/settings-selector.ts +10 -1
- package/src/modes/components/tree-selector.ts +10 -2
- package/src/modes/controllers/command-controller.ts +1 -3
- package/src/modes/controllers/extension-ui-controller.ts +10 -11
- package/src/modes/controllers/selector-controller.ts +5 -5
- package/src/modes/theme/theme.ts +4 -2
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +4 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/eval.md +1 -1
- package/src/prompts/tools/hashline.md +73 -94
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/search.md +3 -3
- package/src/sdk.ts +33 -26
- package/src/session/agent-session.ts +59 -66
- package/src/session/agent-storage.ts +13 -14
- package/src/slash-commands/acp-builtins.ts +3 -3
- package/src/slash-commands/types.ts +0 -6
- package/src/task/executor.ts +26 -57
- package/src/task/index.ts +8 -4
- package/src/tool-discovery/tool-index.ts +0 -134
- package/src/tools/ast-edit.ts +36 -13
- package/src/tools/ast-grep.ts +45 -4
- package/src/tools/browser/tab-worker.ts +3 -2
- package/src/tools/eval.ts +2 -1
- package/src/tools/fetch.ts +23 -14
- package/src/tools/index.ts +2 -8
- package/src/tools/irc.ts +59 -5
- package/src/tools/match-line-format.ts +5 -7
- package/src/tools/output-schema-validator.ts +132 -0
- package/src/tools/read.ts +142 -31
- package/src/tools/review.ts +23 -0
- package/src/tools/search-tool-bm25.ts +3 -30
- package/src/tools/search.ts +48 -16
- package/src/tools/write.ts +3 -3
- package/src/tools/yield.ts +32 -41
- package/src/utils/edit-mode.ts +1 -2
- package/src/utils/file-mentions.ts +2 -2
- package/src/web/kagi.ts +15 -6
- package/src/web/parallel.ts +9 -6
- package/src/web/scrapers/types.ts +7 -1
- package/src/web/scrapers/youtube.ts +13 -7
- package/src/web/search/index.ts +37 -11
- package/src/web/search/provider.ts +5 -3
- package/src/web/search/providers/anthropic.ts +30 -21
- package/src/web/search/providers/base.ts +35 -2
- package/src/web/search/providers/brave.ts +4 -4
- package/src/web/search/providers/codex.ts +118 -89
- package/src/web/search/providers/exa.ts +3 -2
- package/src/web/search/providers/gemini.ts +58 -155
- package/src/web/search/providers/jina.ts +4 -4
- package/src/web/search/providers/kagi.ts +17 -11
- package/src/web/search/providers/kimi.ts +29 -13
- package/src/web/search/providers/parallel.ts +171 -23
- package/src/web/search/providers/perplexity.ts +38 -37
- package/src/web/search/providers/searxng.ts +3 -1
- package/src/web/search/providers/synthetic.ts +16 -19
- package/src/web/search/providers/tavily.ts +23 -18
- package/src/web/search/providers/utils.ts +11 -17
- package/src/web/search/providers/zai.ts +16 -8
- package/dist/types/hashline/parser.d.ts +0 -7
- package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
- package/dist/types/tools/vim.d.ts +0 -58
- package/dist/types/vim/buffer.d.ts +0 -41
- package/dist/types/vim/commands.d.ts +0 -6
- package/dist/types/vim/engine.d.ts +0 -47
- package/dist/types/vim/parser.d.ts +0 -3
- package/dist/types/vim/render.d.ts +0 -25
- package/dist/types/vim/types.d.ts +0 -182
- package/src/hashline/parser.ts +0 -246
- package/src/mcp/discoverable-tool-metadata.ts +0 -24
- package/src/prompts/tools/vim.md +0 -98
- package/src/tools/vim.ts +0 -949
- package/src/vim/buffer.ts +0 -309
- package/src/vim/commands.ts +0 -382
- package/src/vim/engine.ts +0 -2409
- package/src/vim/parser.ts +0 -134
- package/src/vim/render.ts +0 -252
- package/src/vim/types.ts +0 -197
package/src/tools/fetch.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { Settings } from "../config/settings";
|
|
|
10
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
11
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
12
12
|
import type { ToolSession } from "../sdk";
|
|
13
|
+
import type { AgentStorage } from "../session/agent-storage";
|
|
13
14
|
import { DEFAULT_MAX_BYTES, truncateHead } from "../session/streaming-output";
|
|
14
15
|
import { renderStatusLine } from "../tui";
|
|
15
16
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
@@ -542,7 +543,8 @@ async function renderHtmlToText(
|
|
|
542
543
|
html: string,
|
|
543
544
|
timeout: number,
|
|
544
545
|
settings: Settings,
|
|
545
|
-
userSignal
|
|
546
|
+
userSignal: AbortSignal | undefined,
|
|
547
|
+
storage: AgentStorage | null,
|
|
546
548
|
): Promise<{ content: string; ok: boolean; method: string }> {
|
|
547
549
|
const signal = ptree.combineSignals(userSignal, timeout * 1000);
|
|
548
550
|
const execOptions = {
|
|
@@ -554,14 +556,18 @@ async function renderHtmlToText(
|
|
|
554
556
|
};
|
|
555
557
|
|
|
556
558
|
// Try Parallel extract first when credentials are configured
|
|
557
|
-
if (settings.get("providers.parallelFetch") &&
|
|
559
|
+
if (settings.get("providers.parallelFetch") && findParallelApiKey(storage)) {
|
|
558
560
|
try {
|
|
559
|
-
const parallelResult = await extractWithParallel(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
561
|
+
const parallelResult = await extractWithParallel(
|
|
562
|
+
[url],
|
|
563
|
+
{
|
|
564
|
+
objective: "Extract the main content",
|
|
565
|
+
excerpts: true,
|
|
566
|
+
fullContent: false,
|
|
567
|
+
signal,
|
|
568
|
+
},
|
|
569
|
+
storage,
|
|
570
|
+
);
|
|
565
571
|
const firstDocument = parallelResult.results[0];
|
|
566
572
|
if (firstDocument) {
|
|
567
573
|
const content = getParallelExtractContent(firstDocument);
|
|
@@ -682,13 +688,14 @@ type FetchRenderResult = RenderResult & {
|
|
|
682
688
|
async function handleSpecialUrls(
|
|
683
689
|
url: string,
|
|
684
690
|
timeout: number,
|
|
685
|
-
signal
|
|
691
|
+
signal: AbortSignal | undefined,
|
|
692
|
+
storage: AgentStorage | null,
|
|
686
693
|
): Promise<FetchRenderResult | null> {
|
|
687
694
|
for (const handler of specialHandlers) {
|
|
688
695
|
if (signal?.aborted) {
|
|
689
696
|
throw new ToolAbortError();
|
|
690
697
|
}
|
|
691
|
-
const result = await handler(url, timeout, signal);
|
|
698
|
+
const result = await handler(url, timeout, signal, storage);
|
|
692
699
|
if (result) return result;
|
|
693
700
|
}
|
|
694
701
|
return null;
|
|
@@ -706,7 +713,8 @@ async function renderUrl(
|
|
|
706
713
|
timeout: number,
|
|
707
714
|
raw: boolean,
|
|
708
715
|
settings: Settings,
|
|
709
|
-
signal
|
|
716
|
+
signal: AbortSignal | undefined,
|
|
717
|
+
storage: AgentStorage | null,
|
|
710
718
|
): Promise<FetchRenderResult> {
|
|
711
719
|
const notes: string[] = [];
|
|
712
720
|
const fetchedAt = new Date().toISOString();
|
|
@@ -733,7 +741,7 @@ async function renderUrl(
|
|
|
733
741
|
|
|
734
742
|
// Step 1: Try special handlers for known sites (unless raw mode)
|
|
735
743
|
if (!raw) {
|
|
736
|
-
const specialResult = await handleSpecialUrls(url, timeout, signal);
|
|
744
|
+
const specialResult = await handleSpecialUrls(url, timeout, signal, storage);
|
|
737
745
|
if (specialResult) return specialResult;
|
|
738
746
|
}
|
|
739
747
|
|
|
@@ -1051,7 +1059,7 @@ async function renderUrl(
|
|
|
1051
1059
|
}
|
|
1052
1060
|
|
|
1053
1061
|
// 5E: Render HTML with lynx or html2text
|
|
1054
|
-
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, settings, signal);
|
|
1062
|
+
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, settings, signal, storage);
|
|
1055
1063
|
if (!htmlResult.ok) {
|
|
1056
1064
|
notes.push("html rendering failed (lynx/html2text unavailable)");
|
|
1057
1065
|
const output = finalizeOutput(rawContent);
|
|
@@ -1233,7 +1241,8 @@ async function buildReadUrlCacheEntry(
|
|
|
1233
1241
|
throw new ToolAbortError();
|
|
1234
1242
|
}
|
|
1235
1243
|
|
|
1236
|
-
const
|
|
1244
|
+
const storage = session.settings.getStorage();
|
|
1245
|
+
const result = await renderUrl(url, effectiveTimeout, raw, session.settings, signal, storage);
|
|
1237
1246
|
const output = buildUrlReadOutput(result, result.content);
|
|
1238
1247
|
const artifactId = options?.ensureArtifact ? await persistReadUrlArtifact(session, output) : undefined;
|
|
1239
1248
|
|
package/src/tools/index.ts
CHANGED
|
@@ -91,7 +91,6 @@ export * from "./search";
|
|
|
91
91
|
export * from "./search-tool-bm25";
|
|
92
92
|
export * from "./ssh";
|
|
93
93
|
export * from "./todo-write";
|
|
94
|
-
export * from "./vim";
|
|
95
94
|
export * from "./write";
|
|
96
95
|
export * from "./yield";
|
|
97
96
|
|
|
@@ -104,7 +103,6 @@ export type ContextFileEntry = {
|
|
|
104
103
|
depth?: number;
|
|
105
104
|
};
|
|
106
105
|
|
|
107
|
-
export type { DiscoverableMCPTool } from "../mcp/discoverable-tool-metadata";
|
|
108
106
|
export type {
|
|
109
107
|
DiscoverableTool,
|
|
110
108
|
DiscoverableToolSearchIndex,
|
|
@@ -140,6 +138,8 @@ export interface ToolSession {
|
|
|
140
138
|
requireYieldTool?: boolean;
|
|
141
139
|
/** Task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
142
140
|
taskDepth?: number;
|
|
141
|
+
/** Get shared eval executor session ID. Subagents inherit this to share JS/Python state. */
|
|
142
|
+
getEvalSessionId?: () => string | null;
|
|
143
143
|
/** Get session file */
|
|
144
144
|
getSessionFile: () => string | null;
|
|
145
145
|
/** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
|
|
@@ -194,12 +194,6 @@ export interface ToolSession {
|
|
|
194
194
|
setTodoPhases?: (phases: TodoPhase[]) => void;
|
|
195
195
|
/** Whether MCP tool discovery is active for this session. */
|
|
196
196
|
isMCPDiscoveryEnabled?: () => boolean;
|
|
197
|
-
/** Get hidden-but-discoverable MCP tools for search_tool_bm25 prompts and fallbacks.
|
|
198
|
-
* @deprecated Use getDiscoverableTools with source filter instead. */
|
|
199
|
-
getDiscoverableMCPTools?: () => import("../mcp/discoverable-tool-metadata").DiscoverableMCPTool[];
|
|
200
|
-
/** Get the cached discoverable MCP search index for search_tool_bm25 execution.
|
|
201
|
-
* @deprecated Use getDiscoverableToolSearchIndex instead. */
|
|
202
|
-
getDiscoverableMCPSearchIndex?: () => import("../tool-discovery/tool-index").DiscoverableMCPSearchIndex;
|
|
203
197
|
/** Get MCP tools activated by prior search_tool_bm25 calls. */
|
|
204
198
|
getSelectedMCPToolNames?: () => string[];
|
|
205
199
|
/** Merge MCP tool selections into the active session tool set. */
|
package/src/tools/irc.ts
CHANGED
|
@@ -25,6 +25,7 @@ import ircDescription from "../prompts/tools/irc.md" with { type: "text" };
|
|
|
25
25
|
import type { AgentRef, AgentRegistry } from "../registry/agent-registry";
|
|
26
26
|
import type { ToolSession } from ".";
|
|
27
27
|
|
|
28
|
+
const DEFAULT_IRC_TIMEOUT_MS = 120_000;
|
|
28
29
|
const ircSchema = z.object({
|
|
29
30
|
op: z.enum(["send", "list"]).describe("irc operation"),
|
|
30
31
|
to: z.string().optional().describe('recipient agent id or "all"'),
|
|
@@ -159,6 +160,7 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
|
159
160
|
|
|
160
161
|
const awaitReply = params.awaitReply ?? !isBroadcast;
|
|
161
162
|
|
|
163
|
+
const timeoutMs = normalizeIrcTimeoutMs(this.session.settings.get("irc.timeoutMs"));
|
|
162
164
|
const delivered: string[] = [];
|
|
163
165
|
const replies: IrcReply[] = [];
|
|
164
166
|
const failed: Array<{ id: string; error: string }> = [];
|
|
@@ -174,12 +176,18 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
|
174
176
|
return;
|
|
175
177
|
}
|
|
176
178
|
try {
|
|
177
|
-
const result = await
|
|
178
|
-
|
|
179
|
-
message,
|
|
180
|
-
awaitReply,
|
|
179
|
+
const result = await runIrcDispatchWithTimeout(
|
|
180
|
+
timeoutMs,
|
|
181
181
|
signal,
|
|
182
|
-
|
|
182
|
+
timeoutSignal =>
|
|
183
|
+
targetSession.respondAsBackground({
|
|
184
|
+
from: senderId,
|
|
185
|
+
message,
|
|
186
|
+
awaitReply,
|
|
187
|
+
signal: timeoutSignal,
|
|
188
|
+
}),
|
|
189
|
+
target.id,
|
|
190
|
+
);
|
|
183
191
|
delivered.push(target.id);
|
|
184
192
|
if (awaitReply && result.replyText) {
|
|
185
193
|
replies.push({ from: target.id, text: result.replyText });
|
|
@@ -237,3 +245,49 @@ function errorResult(text: string, details: IrcDetails): AgentToolResult<IrcDeta
|
|
|
237
245
|
details,
|
|
238
246
|
};
|
|
239
247
|
}
|
|
248
|
+
|
|
249
|
+
function normalizeIrcTimeoutMs(value: number): number {
|
|
250
|
+
if (!Number.isFinite(value) || value === 0) return value === 0 ? 0 : DEFAULT_IRC_TIMEOUT_MS;
|
|
251
|
+
return Math.max(1, Math.trunc(value));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function runIrcDispatchWithTimeout<T>(
|
|
255
|
+
timeoutMs: number,
|
|
256
|
+
parentSignal: AbortSignal | undefined,
|
|
257
|
+
run: (signal?: AbortSignal) => Promise<T>,
|
|
258
|
+
targetId: string,
|
|
259
|
+
): Promise<T> {
|
|
260
|
+
if (timeoutMs <= 0) {
|
|
261
|
+
return await run(parentSignal);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const timeoutError = new Error(`IRC timed out waiting for ${targetId} after ${timeoutMs} ms`);
|
|
266
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
267
|
+
let parentAbortListener: (() => void) | undefined;
|
|
268
|
+
|
|
269
|
+
const timeoutDeferred = Promise.withResolvers<never>();
|
|
270
|
+
if (parentSignal) {
|
|
271
|
+
if (parentSignal.aborted) {
|
|
272
|
+
throw parentSignal.reason instanceof Error ? parentSignal.reason : new Error("IRC aborted");
|
|
273
|
+
}
|
|
274
|
+
parentAbortListener = () => {
|
|
275
|
+
controller.abort(parentSignal.reason);
|
|
276
|
+
timeoutDeferred.reject(parentSignal.reason instanceof Error ? parentSignal.reason : new Error("IRC aborted"));
|
|
277
|
+
};
|
|
278
|
+
parentSignal.addEventListener("abort", parentAbortListener, { once: true });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
timeout = setTimeout(() => {
|
|
282
|
+
controller.abort(timeoutError);
|
|
283
|
+
timeoutDeferred.reject(timeoutError);
|
|
284
|
+
}, timeoutMs);
|
|
285
|
+
timeout.unref?.();
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
return await Promise.race([run(controller.signal), timeoutDeferred.promise]);
|
|
289
|
+
} finally {
|
|
290
|
+
if (timeout) clearTimeout(timeout);
|
|
291
|
+
if (parentSignal && parentAbortListener) parentSignal.removeEventListener("abort", parentAbortListener);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { computeLineHash } from "../hashline/hash";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Format a single line of match output for grep/ast-grep style results.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Matched lines are prefixed with `*`; context lines are prefixed with a single
|
|
5
|
+
* space so line numbers align in column. In hashline mode the line uses the
|
|
6
|
+
* editable `LINE:content` shape under a file-hash header; in plain mode it keeps
|
|
7
|
+
* the legacy `LINE|content` display-only shape. Line numbers are never padded.
|
|
10
8
|
*/
|
|
11
9
|
export function formatMatchLine(
|
|
12
10
|
lineNumber: number,
|
|
@@ -16,7 +14,7 @@ export function formatMatchLine(
|
|
|
16
14
|
): string {
|
|
17
15
|
const marker = isMatch ? "*" : " ";
|
|
18
16
|
if (options.useHashLines) {
|
|
19
|
-
return `${marker}${lineNumber}
|
|
17
|
+
return `${marker}${lineNumber}:${line}`;
|
|
20
18
|
}
|
|
21
19
|
return `${marker}${lineNumber}|${line}`;
|
|
22
20
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared output-schema validation for subagent yield + executor finalization.
|
|
3
|
+
*
|
|
4
|
+
* Both the in-process `yield` tool (subagent side) and the executor's post-mortem
|
|
5
|
+
* finalize path (parent side) need to validate yield payloads against the agent's
|
|
6
|
+
* declared output schema. This module is the single source of truth for that
|
|
7
|
+
* pipeline — keeping the two callsites in lockstep so a schema accepted in-tool
|
|
8
|
+
* cannot be rejected post-mortem (or vice versa).
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
isValidJsonSchema,
|
|
12
|
+
type JsonSchemaValidationIssue,
|
|
13
|
+
type JsonSchemaValidationResult,
|
|
14
|
+
validateJsonSchemaValue,
|
|
15
|
+
} from "@oh-my-pi/pi-ai/utils/schema";
|
|
16
|
+
import { jtdToJsonSchema, normalizeSchema } from "./jtd-to-json-schema";
|
|
17
|
+
|
|
18
|
+
/** A validator bound to a specific output schema. */
|
|
19
|
+
export interface OutputValidator {
|
|
20
|
+
/** Run JSON Schema validation; returns the raw `success`/`issues` shape so callers may inspect every failure. */
|
|
21
|
+
validate(value: unknown): JsonSchemaValidationResult;
|
|
22
|
+
/** Top-level required property names. Empty if the schema has no `required` array at root. */
|
|
23
|
+
readonly requiredFields: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BuildOutputValidatorResult {
|
|
27
|
+
/** Present when the schema produced a usable validator (i.e. constraining schemas). Absent for missing/unconstrained schemas. */
|
|
28
|
+
validator?: OutputValidator;
|
|
29
|
+
/** Raw JSON Schema produced by `jtdToJsonSchema`. Available alongside the validator so callers can derive related artifacts (strict-mode probe, dereference, hint text). */
|
|
30
|
+
jsonSchema?: Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Normalized schema (post-`normalizeSchema`). Surfaced so callers can distinguish
|
|
33
|
+
* "no schema provided" (`undefined`) from "intentionally unconstrained" (`true`)
|
|
34
|
+
* when both produce no validator.
|
|
35
|
+
*/
|
|
36
|
+
normalized?: unknown;
|
|
37
|
+
/** Set when the schema cannot be used. Callers should treat this as a "no validation" case (loose acceptance) and surface the message in diagnostics. */
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the canonical validator for a JTD-or-JSON-Schema output declaration.
|
|
43
|
+
*
|
|
44
|
+
* Returns:
|
|
45
|
+
* - `{ validator, jsonSchema, normalized }` for constraining schemas — both callers use this path.
|
|
46
|
+
* - `{ normalized: true }` for an intentionally unconstrained schema (the JSON Schema literal `true`).
|
|
47
|
+
* No validator, but distinguishable from "no schema provided".
|
|
48
|
+
* - `{}` for an absent schema (`undefined`).
|
|
49
|
+
* - `{ error, normalized? }` when the schema cannot be honored (invalid syntax, `false`, malformed JTD).
|
|
50
|
+
*/
|
|
51
|
+
export function buildOutputValidator(schema: unknown): BuildOutputValidatorResult {
|
|
52
|
+
const { normalized, error: normalizeError } = normalizeSchema(schema);
|
|
53
|
+
if (normalizeError) return { error: normalizeError, normalized };
|
|
54
|
+
if (normalized === undefined) return {};
|
|
55
|
+
if (normalized === false) return { error: "boolean false schema rejects all outputs", normalized };
|
|
56
|
+
if (normalized === true) return { normalized };
|
|
57
|
+
|
|
58
|
+
const jsonSchema = jtdToJsonSchema(normalized);
|
|
59
|
+
if (jsonSchema === undefined) return { normalized };
|
|
60
|
+
if (jsonSchema === false) return { error: "boolean false schema rejects all outputs", normalized };
|
|
61
|
+
if (jsonSchema === true) return { normalized };
|
|
62
|
+
if (typeof jsonSchema !== "object" || Array.isArray(jsonSchema)) {
|
|
63
|
+
return { error: "invalid JSON schema", normalized };
|
|
64
|
+
}
|
|
65
|
+
if (!isValidJsonSchema(jsonSchema)) return { error: "invalid JSON schema", normalized };
|
|
66
|
+
|
|
67
|
+
const jsonSchemaRecord = jsonSchema as Record<string, unknown>;
|
|
68
|
+
const required = extractRequiredFields(jsonSchemaRecord);
|
|
69
|
+
return {
|
|
70
|
+
normalized,
|
|
71
|
+
jsonSchema: jsonSchemaRecord,
|
|
72
|
+
validator: {
|
|
73
|
+
requiredFields: required,
|
|
74
|
+
validate: value => validateJsonSchemaValue(jsonSchemaRecord, value),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Produce the executor's headline+missing-required summary from a failed validation. */
|
|
80
|
+
export function summarizeValidationFailure(
|
|
81
|
+
result: JsonSchemaValidationResult,
|
|
82
|
+
value: unknown,
|
|
83
|
+
requiredFields: readonly string[],
|
|
84
|
+
): { message: string; missingRequired: string[] } {
|
|
85
|
+
if (result.success) return { message: "", missingRequired: [] };
|
|
86
|
+
const missing = computeMissingRequired(requiredFields, value);
|
|
87
|
+
const message = formatValidationIssueHeadline(result.issues[0]) ?? "schema validation failed";
|
|
88
|
+
return { message, missingRequired: missing };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function extractRequiredFields(jsonSchema: unknown): string[] {
|
|
92
|
+
if (!jsonSchema || typeof jsonSchema !== "object") return [];
|
|
93
|
+
const required = (jsonSchema as { required?: unknown }).required;
|
|
94
|
+
return Array.isArray(required) ? required.filter((k): k is string => typeof k === "string") : [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function computeMissingRequired(required: readonly string[], value: unknown): string[] {
|
|
98
|
+
if (required.length === 0) return [];
|
|
99
|
+
if (value === null || value === undefined) return [...required];
|
|
100
|
+
if (typeof value !== "object" || Array.isArray(value)) return [];
|
|
101
|
+
const record = value as Record<string, unknown>;
|
|
102
|
+
return required.filter(key => !(key in record) || record[key] === undefined);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Format a single validation issue as `path.with.dots: message`.
|
|
107
|
+
*
|
|
108
|
+
* Used by the executor's post-mortem `schema_violation` headline — one line, dot-separated path,
|
|
109
|
+
* since the executor's error format already lists missing-required fields separately.
|
|
110
|
+
*/
|
|
111
|
+
export function formatValidationIssueHeadline(issue: JsonSchemaValidationIssue | undefined): string | undefined {
|
|
112
|
+
if (!issue) return undefined;
|
|
113
|
+
const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
|
|
114
|
+
return `${path}: ${issue.message}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format every validation issue as `path/with/slashes: message; ...`.
|
|
119
|
+
*
|
|
120
|
+
* Used by the yield tool's model-facing retry feedback — the model gets every problem at once so it
|
|
121
|
+
* can fix the entire output in one retry instead of iterating issue-by-issue. The slash separator
|
|
122
|
+
* mirrors JSON Pointer convention and disambiguates against fields whose names contain dots.
|
|
123
|
+
*/
|
|
124
|
+
export function formatAllValidationIssues(issues: ReadonlyArray<JsonSchemaValidationIssue> | undefined): string {
|
|
125
|
+
if (!issues || issues.length === 0) return "Unknown schema validation error.";
|
|
126
|
+
return issues
|
|
127
|
+
.map(issue => {
|
|
128
|
+
const path = issue.path.length === 0 ? "" : `${issue.path.map(seg => String(seg)).join("/")}: `;
|
|
129
|
+
return `${path}${issue.message}`;
|
|
130
|
+
})
|
|
131
|
+
.join("; ");
|
|
132
|
+
}
|