@oh-my-pi/pi-coding-agent 15.13.1 → 15.13.3
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 +47 -0
- package/dist/cli.js +1057 -289
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/models-config-schema.d.ts +3 -0
- package/dist/types/config/models-config.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +97 -0
- package/dist/types/edit/hashline/block-resolver.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -0
- package/dist/types/eval/js/context-manager.d.ts +15 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +4 -4
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +2 -1
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/session-dump-format.d.ts +2 -1
- package/dist/types/session/unexpected-stop-classifier.d.ts +13 -0
- package/dist/types/stt/asr-client.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +11 -0
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +2 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +2 -0
- package/dist/types/tools/find.d.ts +2 -0
- package/dist/types/tools/inspect-image.d.ts +2 -1
- package/dist/types/tools/irc.d.ts +2 -0
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +2 -0
- package/dist/types/tts/tts-client.d.ts +1 -1
- package/dist/types/tui/tree-list.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +1 -17
- package/package.json +12 -12
- package/src/cli.ts +25 -12
- package/src/config/model-registry.ts +16 -2
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/models-config.ts +1 -0
- package/src/config/settings-schema.ts +78 -0
- package/src/edit/hashline/block-resolver.ts +1 -1
- package/src/edit/hashline/execute.ts +1 -6
- package/src/edit/index.ts +48 -0
- package/src/eval/__tests__/agent-bridge.test.ts +106 -46
- package/src/eval/__tests__/js-context-manager.test.ts +53 -3
- package/src/eval/js/context-manager.ts +132 -29
- package/src/eval/js/worker-core.ts +1 -1
- package/src/eval/js/worker-entry.ts +7 -0
- package/src/export/html/template.js +18 -22
- package/src/internal-urls/docs-index.generated.ts +12 -3
- package/src/main.ts +15 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/agent-hub.ts +31 -7
- package/src/modes/components/assistant-message.ts +24 -15
- package/src/modes/components/snapcompact-shape-preview-doc.md +2 -2
- package/src/modes/components/snapcompact-shape-preview.ts +2 -2
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/components/welcome.ts +14 -4
- package/src/modes/controllers/event-controller.ts +3 -3
- package/src/modes/controllers/input-controller.ts +28 -39
- package/src/modes/controllers/streaming-reveal.ts +4 -4
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/rpc/rpc-mode.ts +1 -0
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/types.ts +6 -0
- package/src/modes/utils/ui-helpers.ts +3 -3
- package/src/prompts/agents/oracle.md +0 -1
- package/src/prompts/agents/reviewer.md +0 -1
- package/src/prompts/system/system-prompt.md +17 -21
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/tools/ask.md +0 -8
- package/src/prompts/tools/ast-edit.md +0 -15
- package/src/prompts/tools/ast-grep.md +0 -13
- package/src/prompts/tools/browser.md +0 -21
- package/src/prompts/tools/debug.md +0 -13
- package/src/prompts/tools/eval.md +0 -9
- package/src/prompts/tools/find.md +0 -13
- package/src/prompts/tools/inspect-image.md +0 -9
- package/src/prompts/tools/irc.md +0 -15
- package/src/prompts/tools/patch.md +0 -13
- package/src/prompts/tools/ssh.md +0 -9
- package/src/prompts/tools/todo.md +1 -19
- package/src/sdk.ts +19 -0
- package/src/session/agent-session.ts +289 -29
- package/src/session/session-dump-format.ts +17 -49
- package/src/session/unexpected-stop-classifier.ts +129 -0
- package/src/stt/asr-client.ts +1 -1
- package/src/system-prompt.ts +31 -0
- package/src/tiny/title-client.ts +1 -1
- package/src/tools/ask.ts +41 -0
- package/src/tools/ast-edit.ts +46 -0
- package/src/tools/ast-grep.ts +24 -0
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser/tab-worker-entry.ts +12 -4
- package/src/tools/browser.ts +52 -0
- package/src/tools/debug.ts +17 -0
- package/src/tools/eval.ts +20 -1
- package/src/tools/find.ts +24 -0
- package/src/tools/inspect-image.ts +27 -1
- package/src/tools/irc.ts +41 -0
- package/src/tools/job.ts +1 -0
- package/src/tools/ssh.ts +16 -0
- package/src/tools/todo.ts +82 -3
- package/src/tts/tts-client.ts +1 -1
- package/src/tui/tree-list.ts +68 -19
- package/src/utils/thinking-display.ts +8 -34
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type AssistantMessage, completeSimple } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
3
|
+
|
|
4
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
5
|
+
import { resolveRoleSelection } from "../config/model-resolver";
|
|
6
|
+
import type { Settings } from "../config/settings";
|
|
7
|
+
import unexpectedStopClassifierPrompt from "../prompts/system/unexpected-stop-classifier.md" with { type: "text" };
|
|
8
|
+
import { isTinyMemoryLocalModelKey, ONLINE_MEMORY_MODEL_KEY } from "../tiny/models";
|
|
9
|
+
import { tinyModelClient } from "../tiny/title-client";
|
|
10
|
+
|
|
11
|
+
const CLASSIFIER_SYSTEM_PROMPT = prompt.render(unexpectedStopClassifierPrompt);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The answer is a single word. OpenAI-compatible endpoints reject values below
|
|
15
|
+
* 16, so 16 is the smallest portable budget for this classifier.
|
|
16
|
+
*/
|
|
17
|
+
const ANSWER_MAX_TOKENS = 16;
|
|
18
|
+
/**
|
|
19
|
+
* Reasoning backends ignore `disableReasoning` on some providers, so reserve
|
|
20
|
+
* enough output room for the keyword to still land after unavoidable thinking.
|
|
21
|
+
*/
|
|
22
|
+
const REASONING_SAFE_MAX_TOKENS = 1024;
|
|
23
|
+
|
|
24
|
+
export interface ClassifyUnexpectedStopDeps {
|
|
25
|
+
settings: Settings;
|
|
26
|
+
registry: ModelRegistry;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isUnexpectedStopCandidate(message: AssistantMessage): boolean {
|
|
33
|
+
if (message.stopReason !== "stop") return false;
|
|
34
|
+
let hasText = false;
|
|
35
|
+
for (const content of message.content) {
|
|
36
|
+
if (content.type === "toolCall") return false;
|
|
37
|
+
if (content.type === "text" && /\S/.test(content.text)) {
|
|
38
|
+
hasText = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return hasText;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function classifyUnexpectedStop(
|
|
45
|
+
text: string,
|
|
46
|
+
deps: ClassifyUnexpectedStopDeps,
|
|
47
|
+
): Promise<boolean | undefined> {
|
|
48
|
+
const backend = deps.settings.get("providers.unexpectedStopModel");
|
|
49
|
+
try {
|
|
50
|
+
if (backend === ONLINE_MEMORY_MODEL_KEY) {
|
|
51
|
+
return await classifyOnline(text, deps);
|
|
52
|
+
}
|
|
53
|
+
if (isTinyMemoryLocalModelKey(backend)) {
|
|
54
|
+
return await classifyLocal(text, backend, deps);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.debug("unexpected-stop: classification failed", {
|
|
59
|
+
error: error instanceof Error ? error.message : String(error),
|
|
60
|
+
backend,
|
|
61
|
+
});
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function classifyOnline(text: string, deps: ClassifyUnexpectedStopDeps): Promise<boolean | undefined> {
|
|
67
|
+
const resolved = resolveRoleSelection(["smol"], deps.settings, deps.registry.getAvailable(), deps.registry);
|
|
68
|
+
const model = resolved?.model;
|
|
69
|
+
if (!model) {
|
|
70
|
+
throw new Error("unexpected-stop: no smol model available for classification");
|
|
71
|
+
}
|
|
72
|
+
const apiKey = await deps.registry.getApiKey(model, deps.sessionId);
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
throw new Error(`unexpected-stop: no API key for ${model.provider}/${model.id}`);
|
|
75
|
+
}
|
|
76
|
+
const metadata = deps.metadataResolver?.(model.provider);
|
|
77
|
+
const maxTokens = model.reasoning ? Math.max(ANSWER_MAX_TOKENS, REASONING_SAFE_MAX_TOKENS) : ANSWER_MAX_TOKENS;
|
|
78
|
+
|
|
79
|
+
const response = await completeSimple(
|
|
80
|
+
model,
|
|
81
|
+
{
|
|
82
|
+
systemPrompt: [CLASSIFIER_SYSTEM_PROMPT],
|
|
83
|
+
messages: [{ role: "user", content: text, timestamp: Date.now() }],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
apiKey: deps.registry.resolver(model, deps.sessionId),
|
|
87
|
+
maxTokens,
|
|
88
|
+
disableReasoning: true,
|
|
89
|
+
metadata,
|
|
90
|
+
signal: deps.signal,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (response.stopReason === "error") {
|
|
95
|
+
throw new Error(`unexpected-stop: online classification failed: ${response.errorMessage ?? "unknown error"}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const outputText = response.content
|
|
99
|
+
.filter((part): part is { type: "text"; text: string } => part.type === "text")
|
|
100
|
+
.map(part => part.text)
|
|
101
|
+
.join("\n");
|
|
102
|
+
return parseUnexpectedStopClassification(outputText);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function classifyLocal(
|
|
106
|
+
text: string,
|
|
107
|
+
modelKey: string,
|
|
108
|
+
deps: ClassifyUnexpectedStopDeps,
|
|
109
|
+
): Promise<boolean | undefined> {
|
|
110
|
+
if (!isTinyMemoryLocalModelKey(modelKey)) {
|
|
111
|
+
throw new Error(`unexpected-stop: unsupported local classifier model: ${modelKey}`);
|
|
112
|
+
}
|
|
113
|
+
const builtPrompt = prompt.render(unexpectedStopClassifierPrompt, { message: text });
|
|
114
|
+
const output = await tinyModelClient.complete(modelKey, builtPrompt, {
|
|
115
|
+
maxTokens: ANSWER_MAX_TOKENS,
|
|
116
|
+
signal: deps.signal,
|
|
117
|
+
});
|
|
118
|
+
if (!output) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return parseUnexpectedStopClassification(output);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function parseUnexpectedStopClassification(text: string): boolean | undefined {
|
|
125
|
+
const trimmed = text.trim().toLowerCase();
|
|
126
|
+
if (trimmed.startsWith("yes")) return true;
|
|
127
|
+
if (trimmed.startsWith("no")) return false;
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
package/src/stt/asr-client.ts
CHANGED
|
@@ -72,7 +72,7 @@ const SMOKE_TEST_TIMEOUT_MS = 30_000;
|
|
|
72
72
|
* Hidden subcommand on the main CLI that boots the speech-recognition worker in
|
|
73
73
|
* the spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
74
74
|
*/
|
|
75
|
-
export const STT_WORKER_ARG = "
|
|
75
|
+
export const STT_WORKER_ARG = "__omp_worker_stt";
|
|
76
76
|
|
|
77
77
|
function readTinyModelSetting(key: "providers.tinyModelDevice" | "providers.tinyModelDtype"): string | undefined {
|
|
78
78
|
try {
|
package/src/system-prompt.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { ToolExample, TSchema } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import { renderToolInventory } from "@oh-my-pi/pi-ai/grammar";
|
|
7
9
|
import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
8
10
|
import { $ } from "bun";
|
|
9
11
|
import { contextFileCapability } from "./capability/context-file";
|
|
@@ -330,6 +332,10 @@ export interface SystemPromptToolMetadata {
|
|
|
330
332
|
description: string;
|
|
331
333
|
/** Tool name the model sees on the provider wire. Defaults to the internal tool name. */
|
|
332
334
|
wireName?: string;
|
|
335
|
+
/** Tool parameters schema (Zod or JSON Schema), fed to the verbose inventory renderer. */
|
|
336
|
+
parameters?: TSchema;
|
|
337
|
+
/** Illustrative examples rendered into the verbose inventory. */
|
|
338
|
+
examples?: readonly ToolExample[];
|
|
333
339
|
}
|
|
334
340
|
|
|
335
341
|
export function buildSystemPromptToolMetadata(
|
|
@@ -349,6 +355,8 @@ export function buildSystemPromptToolMetadata(
|
|
|
349
355
|
label: override?.label ?? (typeof toolRecord.label === "string" ? toolRecord.label : ""),
|
|
350
356
|
description:
|
|
351
357
|
override?.description ?? (typeof toolRecord.description === "string" ? toolRecord.description : ""),
|
|
358
|
+
parameters: toolRecord.parameters,
|
|
359
|
+
examples: toolRecord.examples,
|
|
352
360
|
wireName,
|
|
353
361
|
},
|
|
354
362
|
] as const;
|
|
@@ -367,6 +375,12 @@ export interface BuildSystemPromptOptions {
|
|
|
367
375
|
appendSystemPrompt?: string;
|
|
368
376
|
/** Repeat full tool descriptions in system prompt. Default: false */
|
|
369
377
|
repeatToolDescriptions?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Whether provider-native tool calling is active (no owned/in-band syntax).
|
|
380
|
+
* When true and `repeatToolDescriptions` is false, the inventory renders as a
|
|
381
|
+
* compact tool-name list; otherwise it renders full `# Tool:` sections. Default: true
|
|
382
|
+
*/
|
|
383
|
+
nativeTools?: boolean;
|
|
370
384
|
/** Skills settings for discovery. */
|
|
371
385
|
skillsSettings?: SkillsSettings;
|
|
372
386
|
/** Working directory. Default: getProjectDir() */
|
|
@@ -420,6 +434,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
420
434
|
tools,
|
|
421
435
|
appendSystemPrompt,
|
|
422
436
|
repeatToolDescriptions = false,
|
|
437
|
+
nativeTools = true,
|
|
423
438
|
skillsSettings,
|
|
424
439
|
toolNames: providedToolNames,
|
|
425
440
|
cwd,
|
|
@@ -575,6 +590,20 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
575
590
|
label: tools?.get(name)?.label ?? "",
|
|
576
591
|
description: tools?.get(name)?.description ?? "",
|
|
577
592
|
}));
|
|
593
|
+
const inventoryTools = toolNames.map(name => {
|
|
594
|
+
const meta = tools?.get(name);
|
|
595
|
+
return {
|
|
596
|
+
name: toolPromptNames.get(name) ?? name,
|
|
597
|
+
description: meta?.description ?? "",
|
|
598
|
+
parameters: meta?.parameters ?? ({ type: "object" } as TSchema),
|
|
599
|
+
examples: meta?.examples,
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
// List mode shows a compact tool-name list; it only applies when descriptions
|
|
603
|
+
// are not repeated AND native tool calling is active (the model already has the
|
|
604
|
+
// schemas). Otherwise render full `# Tool:` sections.
|
|
605
|
+
const toolListMode = !repeatToolDescriptions && nativeTools;
|
|
606
|
+
const toolInventory = toolListMode ? "" : renderToolInventory(inventoryTools, model ?? "");
|
|
578
607
|
|
|
579
608
|
// Filter skills for the rendered system prompt:
|
|
580
609
|
// - require the `read` tool so the model can actually fetch skill content;
|
|
@@ -596,7 +625,9 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
596
625
|
appendPrompt: resolvedAppendPrompt ?? "",
|
|
597
626
|
tools: toolNames,
|
|
598
627
|
toolInfo,
|
|
628
|
+
toolInventory,
|
|
599
629
|
repeatToolDescriptions,
|
|
630
|
+
toolListMode,
|
|
600
631
|
toolRefs,
|
|
601
632
|
environment,
|
|
602
633
|
contextFiles,
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -69,7 +69,7 @@ function normalizeTinyTitleGenerateOptions(
|
|
|
69
69
|
* Hidden subcommand on the main CLI that boots the tiny-model worker in the
|
|
70
70
|
* spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
71
71
|
*/
|
|
72
|
-
export const TINY_WORKER_ARG = "
|
|
72
|
+
export const TINY_WORKER_ARG = "__omp_worker_tiny_inference";
|
|
73
73
|
|
|
74
74
|
function readTinyModelSetting(path: "providers.tinyModelDevice" | "providers.tinyModelDtype"): string | undefined {
|
|
75
75
|
try {
|
package/src/tools/ask.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
19
20
|
import { type Component, Markdown, type MarkdownTheme, renderInlineMarkdown, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
20
21
|
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
21
22
|
import { z } from "zod/v4";
|
|
@@ -422,6 +423,46 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
422
423
|
readonly description: string;
|
|
423
424
|
readonly parameters = askSchema;
|
|
424
425
|
readonly strict = true;
|
|
426
|
+
|
|
427
|
+
readonly examples: readonly ToolExample<z.input<typeof askSchema>>[] = [
|
|
428
|
+
{
|
|
429
|
+
caption: "Single question",
|
|
430
|
+
call: {
|
|
431
|
+
questions: [
|
|
432
|
+
{
|
|
433
|
+
id: "auth_method",
|
|
434
|
+
question: "Which authentication method should this API use?",
|
|
435
|
+
options: [
|
|
436
|
+
{ label: "JWT", description: "Bearer tokens for stateless API clients." },
|
|
437
|
+
{ label: "OAuth2", description: "Delegated authorization with external identity providers." },
|
|
438
|
+
{
|
|
439
|
+
label: "Session cookies",
|
|
440
|
+
description: "Browser-first authentication backed by server-side sessions.",
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
recommended: 0,
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
caption: "Multiple questions",
|
|
450
|
+
call: {
|
|
451
|
+
questions: [
|
|
452
|
+
{
|
|
453
|
+
id: "storage_type",
|
|
454
|
+
question: "Which storage backend?",
|
|
455
|
+
options: [{ label: "SQLite" }, { label: "PostgreSQL" }],
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
id: "auth_method",
|
|
459
|
+
question: "Which auth method?",
|
|
460
|
+
options: [{ label: "JWT" }, { label: "Session cookies" }],
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
];
|
|
425
466
|
// Run alone in its tool batch. The interactive selector/editor is a single
|
|
426
467
|
// shared UI surface (`ExtensionUiController.showHookSelector` has no queue and
|
|
427
468
|
// overwrites `ctx.hookSelector` on each call), so two concurrent `ask` calls
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { formatHashlineHeader } from "@oh-my-pi/hashline";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { type AstReplaceChange, type AstReplaceFileChange, astEdit } from "@oh-my-pi/pi-natives";
|
|
5
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { replaceTabs, Text } from "@oh-my-pi/pi-tui";
|
|
@@ -194,6 +195,51 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
194
195
|
readonly description: string;
|
|
195
196
|
readonly parameters = astEditSchema;
|
|
196
197
|
readonly strict = true;
|
|
198
|
+
|
|
199
|
+
readonly examples: readonly ToolExample<z.input<typeof astEditSchema>>[] = [
|
|
200
|
+
{
|
|
201
|
+
caption: "Rename a call site across TypeScript files",
|
|
202
|
+
call: {
|
|
203
|
+
ops: [{ pat: "oldApi($$$ARGS)", out: "newApi($$$ARGS)" }],
|
|
204
|
+
paths: ["src/**/*.ts"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
caption: "Delete matching calls",
|
|
209
|
+
call: {
|
|
210
|
+
ops: [{ pat: "console.log($$$ARGS)", out: "" }],
|
|
211
|
+
paths: ["src/**/*.ts"],
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
caption: "Rewrite import source path",
|
|
216
|
+
call: {
|
|
217
|
+
ops: [{ pat: 'import { $$$IMPORTS } from "old-package"', out: 'import { $$$IMPORTS } from "new-package"' }],
|
|
218
|
+
paths: ["src/**/*.ts"],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
caption: "Modernize to optional chaining (same metavariable enforces identity)",
|
|
223
|
+
call: {
|
|
224
|
+
ops: [{ pat: "$A && $A()", out: "$A?.()" }],
|
|
225
|
+
paths: ["src/**/*.ts"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
caption: "Swap two arguments using captures",
|
|
230
|
+
call: {
|
|
231
|
+
ops: [{ pat: "assertEqual($A, $B)", out: "assertEqual($B, $A)" }],
|
|
232
|
+
paths: ["tests/**/*.ts"],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
caption: "Python — convert print calls to logging",
|
|
237
|
+
call: {
|
|
238
|
+
ops: [{ pat: "print($$$ARGS)", out: "logger.info($$$ARGS)" }],
|
|
239
|
+
paths: ["src/**/*.py"],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
];
|
|
197
243
|
readonly deferrable = true;
|
|
198
244
|
readonly loadMode = "discoverable";
|
|
199
245
|
constructor(private readonly session: ToolSession) {
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { formatHashlineHeader } from "@oh-my-pi/hashline";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
|
|
5
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
@@ -130,6 +131,29 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
130
131
|
readonly description: string;
|
|
131
132
|
readonly parameters = astGrepSchema;
|
|
132
133
|
readonly strict = true;
|
|
134
|
+
|
|
135
|
+
readonly examples: readonly ToolExample<z.input<typeof astGrepSchema>>[] = [
|
|
136
|
+
{
|
|
137
|
+
caption: "Search TypeScript files under src",
|
|
138
|
+
call: { pat: "console.log($$$)", paths: ["src/**/*.ts"] },
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
caption: "Named imports from a specific package",
|
|
142
|
+
call: { pat: 'import { $$$IMPORTS } from "react"', paths: ["src/**/*.ts"] },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
caption: "Arrow functions assigned to a const",
|
|
146
|
+
call: { pat: "const $NAME = ($$$ARGS) => $BODY", paths: ["src/utils/**/*.ts"] },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
caption: "Method call on any object, ignoring method name with `$_`",
|
|
150
|
+
call: { pat: "logger.$_($$$ARGS)", paths: ["src/**/*.ts"] },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
caption: "Loosest existence check for a symbol in one file",
|
|
154
|
+
call: { pat: "processItems", paths: ["src/worker.ts"] },
|
|
155
|
+
},
|
|
156
|
+
];
|
|
133
157
|
readonly loadMode = "discoverable";
|
|
134
158
|
|
|
135
159
|
constructor(private readonly session: ToolSession) {
|
|
@@ -685,7 +685,7 @@ async function spawnTabWorker(): Promise<WorkerHandle> {
|
|
|
685
685
|
try {
|
|
686
686
|
const hostEntry = workerHostEntry();
|
|
687
687
|
const worker = hostEntry
|
|
688
|
-
? new Worker(hostEntry, { type: "module", argv: ["
|
|
688
|
+
? new Worker(hostEntry, { type: "module", argv: ["__omp_worker_tab"] })
|
|
689
689
|
: new Worker(new URL("./tab-worker-entry.ts", import.meta.url).href, { type: "module" });
|
|
690
690
|
return wrapBunWorker(worker);
|
|
691
691
|
} catch (err) {
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { consumeWorkerInbox } from "@oh-my-pi/pi-utils/worker-host";
|
|
2
3
|
import type { Transport, WorkerInbound, WorkerOutbound } from "./tab-protocol";
|
|
3
4
|
import { WorkerCore } from "./tab-worker";
|
|
4
5
|
|
|
5
6
|
if (!parentPort) throw new Error("tab-worker-entry: missing parentPort");
|
|
6
7
|
|
|
8
|
+
const port = parentPort;
|
|
9
|
+
// When the CLI host pre-buffered messages (it imports this module dynamically),
|
|
10
|
+
// bind that inbox so the parent's already-delivered `init` is replayed. Loaded
|
|
11
|
+
// directly (test/SDK fallback), this module's top-level runs synchronously at
|
|
12
|
+
// worker start, so the direct `parentPort.on` below wins the flush on its own.
|
|
13
|
+
const inbox = consumeWorkerInbox();
|
|
7
14
|
const transport: Transport = {
|
|
8
15
|
send(msg, transferList) {
|
|
9
|
-
|
|
16
|
+
port.postMessage(msg, transferList ?? []);
|
|
10
17
|
},
|
|
11
18
|
onMessage(handler) {
|
|
19
|
+
if (inbox) return inbox.bind(data => handler(data as WorkerOutbound | WorkerInbound));
|
|
12
20
|
const wrap = (message: unknown): void => handler(message as WorkerOutbound | WorkerInbound);
|
|
13
|
-
|
|
14
|
-
return () =>
|
|
21
|
+
port.on("message", wrap);
|
|
22
|
+
return () => port.off("message", wrap);
|
|
15
23
|
},
|
|
16
24
|
close() {
|
|
17
|
-
|
|
25
|
+
port.close();
|
|
18
26
|
},
|
|
19
27
|
};
|
|
20
28
|
|
package/src/tools/browser.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { z } from "zod/v4";
|
|
4
5
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
@@ -118,6 +119,57 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
118
119
|
readonly parameters = browserSchema;
|
|
119
120
|
readonly strict = true;
|
|
120
121
|
|
|
122
|
+
readonly examples: readonly ToolExample<z.input<typeof browserSchema>>[] = [
|
|
123
|
+
{
|
|
124
|
+
caption: "Open a tab",
|
|
125
|
+
call: { action: "open", name: "docs", url: "https://example.com" },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
caption: "Read structured page data in the opened tab",
|
|
129
|
+
call: {
|
|
130
|
+
action: "run",
|
|
131
|
+
name: "docs",
|
|
132
|
+
code: "const obs = await tab.observe(); display(obs); return obs.elements.length;",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
caption: "Click an observed element by id",
|
|
137
|
+
call: {
|
|
138
|
+
action: "run",
|
|
139
|
+
name: "docs",
|
|
140
|
+
code: "const obs = await tab.observe(); const link = obs.elements.find(e => e.role === 'link' && e.name === 'Sign in'); assert(link, 'Sign in link missing'); await (await tab.id(link.id)).click();",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
caption: "Fill and submit a form via selectors",
|
|
145
|
+
call: {
|
|
146
|
+
action: "run",
|
|
147
|
+
name: "docs",
|
|
148
|
+
code: "await tab.fill('input[name=email]', 'me@example.com'); await tab.click('text/Continue');",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
caption: "Screenshot to look at the page — no save path",
|
|
153
|
+
call: {
|
|
154
|
+
action: "run",
|
|
155
|
+
name: "docs",
|
|
156
|
+
code: "await tab.screenshot();",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
caption: "Attach to an existing Electron app",
|
|
161
|
+
call: {
|
|
162
|
+
action: "open",
|
|
163
|
+
name: "cursor",
|
|
164
|
+
app: { path: "/Applications/Cursor.app/Contents/MacOS/Cursor" },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
caption: "Close every tab and kill spawned-app processes",
|
|
169
|
+
call: { action: "close", all: true, kill: true },
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
121
173
|
constructor(private readonly session: ToolSession) {}
|
|
122
174
|
#description?: string;
|
|
123
175
|
get description(): string {
|
package/src/tools/debug.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
RenderResultOptions,
|
|
8
8
|
ToolApprovalDecision,
|
|
9
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
10
11
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
11
12
|
import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
|
12
13
|
import { z } from "zod/v4";
|
|
@@ -659,6 +660,22 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
|
|
659
660
|
readonly description: string;
|
|
660
661
|
readonly parameters = debugSchema;
|
|
661
662
|
readonly strict = true;
|
|
663
|
+
|
|
664
|
+
readonly examples: readonly ToolExample<z.input<typeof debugSchema>>[] = [
|
|
665
|
+
{
|
|
666
|
+
caption: "Launch and inspect hang",
|
|
667
|
+
note: '1. debug(action: "launch", program: "./my_app")\n2. debug(action: "set_breakpoint", file: "src/main.c", line: 42)\n3. debug(action: "continue")\n4. If the program appears hung: debug(action: "pause")\n5. Inspect state with `threads`, `stack_trace`, `scopes`, and `variables`',
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
caption: "Launch a Python script with debugpy",
|
|
671
|
+
call: { action: "launch", adapter: "debugpy", program: "scripts/job.py", args: ["--flag"] },
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
caption: "Raw debugger command through repl",
|
|
675
|
+
call: { action: "evaluate", expression: "info registers", context: "repl" },
|
|
676
|
+
},
|
|
677
|
+
];
|
|
678
|
+
|
|
662
679
|
readonly concurrency = "exclusive";
|
|
663
680
|
readonly loadMode = "discoverable";
|
|
664
681
|
|
package/src/tools/eval.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { ImageContent, ToolExample } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
5
|
import { jsBackend, pythonBackend } from "../eval";
|
|
@@ -183,6 +183,25 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
183
183
|
const spawnsAllowed = sessionSpawns !== "" && sessionSpawns !== null;
|
|
184
184
|
return getEvalToolDescription({ py: backends.python, js: backends.js, spawns: spawnsAllowed });
|
|
185
185
|
}
|
|
186
|
+
readonly examples: readonly ToolExample<z.input<typeof evalSchema>>[] = [
|
|
187
|
+
{
|
|
188
|
+
call: {
|
|
189
|
+
cells: [
|
|
190
|
+
{
|
|
191
|
+
language: "py",
|
|
192
|
+
title: "imports",
|
|
193
|
+
timeout: 10,
|
|
194
|
+
code: "import json\nfrom pathlib import Path",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
language: "py",
|
|
198
|
+
title: "load config",
|
|
199
|
+
code: "data = json.loads(read('package.json'))\ndisplay(data)",
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
];
|
|
186
205
|
readonly parameters = evalSchema;
|
|
187
206
|
readonly concurrency = "exclusive";
|
|
188
207
|
readonly strict = true;
|
package/src/tools/find.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import * as natives from "@oh-my-pi/pi-natives";
|
|
5
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
@@ -106,6 +107,29 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
106
107
|
readonly label = "Find";
|
|
107
108
|
readonly description: string;
|
|
108
109
|
readonly parameters = findSchema;
|
|
110
|
+
|
|
111
|
+
readonly examples: readonly ToolExample<z.input<typeof findSchema>>[] = [
|
|
112
|
+
{
|
|
113
|
+
caption: "Find files",
|
|
114
|
+
call: { paths: ["src/**/*.ts"] },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
caption: "Multiple targets — separate array elements",
|
|
118
|
+
call: { paths: ["src/**/*.ts", "test/**/*.ts"] },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
caption: "Find gitignored files like .env",
|
|
122
|
+
call: { paths: [".env*"], gitignore: false },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
caption: "Find directories matching a name (returns both files and dirs; directories are suffixed with `/`)",
|
|
126
|
+
call: { paths: ["**/tests"] },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
caption: "Long-running search on a slow volume",
|
|
130
|
+
call: { paths: ["/Volumes/Storage/**/*.py"], timeout: 30 },
|
|
131
|
+
},
|
|
132
|
+
];
|
|
109
133
|
readonly strict = true;
|
|
110
134
|
|
|
111
135
|
readonly #customOps?: FindOperations;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import { type Api, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { type Api, completeSimple, type Model, type ToolExample } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { z } from "zod/v4";
|
|
6
6
|
import { extractTextContent } from "../commit/utils";
|
|
@@ -43,6 +43,32 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
43
43
|
readonly parameters = inspectImageSchema;
|
|
44
44
|
readonly strict = false;
|
|
45
45
|
|
|
46
|
+
readonly examples: readonly ToolExample<z.input<typeof inspectImageSchema>>[] = [
|
|
47
|
+
{
|
|
48
|
+
caption: "OCR with strict formatting",
|
|
49
|
+
call: {
|
|
50
|
+
path: "screenshots/error.png",
|
|
51
|
+
question: "Extract all visible text verbatim. Return as bullet list in reading order.",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
caption: "Screenshot debugging",
|
|
56
|
+
call: {
|
|
57
|
+
path: "screenshots/settings.png",
|
|
58
|
+
question:
|
|
59
|
+
"Identify the likely cause of the disabled Save button. Return: (1) observations, (2) likely cause, (3) confidence.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
caption: "Scene/object question",
|
|
64
|
+
call: {
|
|
65
|
+
path: "photos/shelf.jpg",
|
|
66
|
+
question:
|
|
67
|
+
"List all clearly visible product labels and their shelf positions (top/middle/bottom). If unreadable, say unreadable.",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
46
72
|
constructor(
|
|
47
73
|
private readonly session: ToolSession,
|
|
48
74
|
private readonly completeImageRequest: typeof completeSimple = completeSimple,
|