@oh-my-pi/pi-coding-agent 14.5.3 → 14.5.6
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 +49 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +7 -7
- package/src/config/prompt-templates.ts +103 -8
- package/src/config/settings-schema.ts +14 -13
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +4 -4
- package/src/edit/index.ts +111 -109
- package/src/edit/line-hash.ts +33 -3
- package/src/edit/modes/apply-patch.ts +6 -4
- package/src/edit/modes/atom.lark +27 -0
- package/src/edit/modes/atom.ts +1039 -841
- package/src/edit/modes/hashline.ts +9 -10
- package/src/edit/modes/patch.ts +23 -19
- package/src/edit/modes/replace.ts +19 -15
- package/src/edit/renderer.ts +65 -8
- package/src/edit/streaming.ts +47 -77
- package/src/extensibility/extensions/types.ts +11 -11
- package/src/extensibility/hooks/types.ts +6 -6
- package/src/lsp/edits.ts +8 -5
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +7 -7
- package/src/mcp/discoverable-tool-metadata.ts +1 -1
- package/src/mcp/manager.ts +3 -3
- package/src/mcp/tool-bridge.ts +4 -4
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-defs.ts +3 -3
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +31 -7
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/system-prompt.md +116 -60
- package/src/prompts/tools/apply-patch.md +0 -2
- package/src/prompts/tools/atom.md +81 -63
- package/src/prompts/tools/bash.md +7 -4
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/find.md +6 -1
- package/src/prompts/tools/hashline.md +10 -11
- package/src/prompts/tools/patch.md +13 -13
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/replace.md +3 -3
- package/src/prompts/tools/{grep.md → search.md} +4 -4
- package/src/sdk.ts +19 -9
- package/src/session/agent-session.ts +65 -0
- package/src/system-prompt.ts +15 -5
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +10 -1
- package/src/tools/ast-edit.ts +4 -6
- package/src/tools/ast-grep.ts +4 -6
- package/src/tools/bash.ts +1 -1
- package/src/tools/file-recorder.ts +6 -6
- package/src/tools/find.ts +11 -13
- package/src/tools/index.ts +7 -7
- package/src/tools/path-utils.ts +31 -4
- package/src/tools/read.ts +12 -6
- package/src/tools/renderers.ts +2 -2
- package/src/tools/{grep.ts → search.ts} +32 -40
- package/src/tools/write.ts +8 -4
- package/src/web/search/index.ts +1 -1
- package/src/edit/block.ts +0 -308
- package/src/edit/indent.ts +0 -150
|
@@ -50,9 +50,9 @@ Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, R
|
|
|
50
50
|
</instruction>
|
|
51
51
|
|
|
52
52
|
<critical>
|
|
53
|
-
- You **MUST** use `read` for
|
|
54
|
-
You **MUST** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if
|
|
55
|
-
- You **MUST** always include the `path` parameter
|
|
56
|
-
- For specific line ranges, use `sel
|
|
53
|
+
- You **MUST** use `read` for every file, directory, archive, and URL read. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, and `wget` are **FORBIDDEN** for inspection — any such Bash call is a bug, regardless of how short or convenient it looks.
|
|
54
|
+
- You **MUST** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if `read` fails to deliver reasonable content.
|
|
55
|
+
- You **MUST** always include the `path` parameter — never call `read` with an empty argument object `{}`.
|
|
56
|
+
- For specific line ranges, use `sel` (e.g. `sel="50-200"`, `sel="50+150"`) — do **NOT** reach for `sed -n`, `awk NR`, or `head`/`tail` pipelines.
|
|
57
57
|
- You **MAY** use `sel` with URL reads; the tool paginates cached fetched output.
|
|
58
58
|
</critical>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Performs string replacements in files with fuzzy whitespace matching.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
4
|
+
- Params **MUST** be `{ path, edits }`; `path` is required at the top level and applies to every replacement
|
|
5
|
+
- You **MUST** use the smallest `old_text` that uniquely identifies the change
|
|
6
|
+
- If `old_text` is not unique, you **MUST** expand it with more context or use `all: true` to replace all occurrences
|
|
7
7
|
- You **SHOULD** prefer editing existing files over creating new ones
|
|
8
8
|
</instruction>
|
|
9
9
|
|
|
@@ -17,8 +17,8 @@ Searches files using powerful regex matching.
|
|
|
17
17
|
</output>
|
|
18
18
|
|
|
19
19
|
<critical>
|
|
20
|
-
- You **MUST** use the built-in
|
|
21
|
-
- Bash `grep`/`rg` loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The
|
|
22
|
-
- If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the
|
|
23
|
-
- If the search is open-ended, requiring multiple rounds, you **MUST** use the Task tool with the explore subagent instead of chaining
|
|
20
|
+
- You **MUST** use the built-in `search` tool for any content search. Do **NOT** shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any other CLI search via Bash — even for a single match, even "just to check quickly", even piped through other commands.
|
|
21
|
+
- Bash `grep`/`rg` loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The `search` tool is faster, structured, and already wired into the workspace — there is no scenario where Bash search is preferable.
|
|
22
|
+
- If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the lookup through the `search` tool instead.
|
|
23
|
+
- If the search is open-ended, requiring multiple rounds, you **MUST** use the Task tool with the explore subagent instead of chaining `search` calls yourself.
|
|
24
24
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
InternalUrlRouter,
|
|
67
67
|
JobsProtocolHandler,
|
|
68
68
|
LocalProtocolHandler,
|
|
69
|
+
type LocalProtocolOptions,
|
|
69
70
|
McpProtocolHandler,
|
|
70
71
|
MemoryProtocolHandler,
|
|
71
72
|
PiProtocolHandler,
|
|
@@ -111,7 +112,6 @@ import {
|
|
|
111
112
|
discoverStartupLspServers,
|
|
112
113
|
EditTool,
|
|
113
114
|
FindTool,
|
|
114
|
-
GrepTool,
|
|
115
115
|
getSearchTools,
|
|
116
116
|
HIDDEN_TOOLS,
|
|
117
117
|
isSearchProviderPreference,
|
|
@@ -121,10 +121,12 @@ import {
|
|
|
121
121
|
ReadTool,
|
|
122
122
|
ResolveTool,
|
|
123
123
|
renderSearchToolBm25Description,
|
|
124
|
+
SearchTool,
|
|
124
125
|
setPreferredImageProvider,
|
|
125
126
|
setPreferredSearchProvider,
|
|
126
127
|
type Tool,
|
|
127
128
|
type ToolSession,
|
|
129
|
+
WebSearchTool,
|
|
128
130
|
WriteTool,
|
|
129
131
|
warmupLspServers,
|
|
130
132
|
} from "./tools";
|
|
@@ -226,6 +228,9 @@ export interface CreateAgentSessionOptions {
|
|
|
226
228
|
/** Session manager. Default: session stored under the configured agentDir sessions root */
|
|
227
229
|
sessionManager?: SessionManager;
|
|
228
230
|
|
|
231
|
+
/** Override local:// protocol options for subagent local:// sharing. Default: uses the session's own artifacts dir and session ID. */
|
|
232
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
233
|
+
|
|
229
234
|
/** Settings instance. Default: Settings.init({ cwd, agentDir }) */
|
|
230
235
|
settings?: Settings;
|
|
231
236
|
|
|
@@ -271,13 +276,14 @@ export {
|
|
|
271
276
|
createTools,
|
|
272
277
|
EditTool,
|
|
273
278
|
FindTool,
|
|
274
|
-
GrepTool,
|
|
275
279
|
HIDDEN_TOOLS,
|
|
276
280
|
loadSshTool,
|
|
277
281
|
PythonTool,
|
|
278
282
|
ReadTool,
|
|
279
283
|
ResolveTool,
|
|
284
|
+
SearchTool,
|
|
280
285
|
type ToolSession,
|
|
286
|
+
WebSearchTool,
|
|
281
287
|
WriteTool,
|
|
282
288
|
};
|
|
283
289
|
|
|
@@ -996,10 +1002,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
996
1002
|
}),
|
|
997
1003
|
);
|
|
998
1004
|
internalRouter.register(
|
|
999
|
-
new LocalProtocolHandler(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1005
|
+
new LocalProtocolHandler(
|
|
1006
|
+
options.localProtocolOptions ?? {
|
|
1007
|
+
getArtifactsDir,
|
|
1008
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
1009
|
+
},
|
|
1010
|
+
),
|
|
1003
1011
|
);
|
|
1004
1012
|
internalRouter.register(
|
|
1005
1013
|
new SkillProtocolHandler({
|
|
@@ -1386,7 +1394,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1386
1394
|
? requestedActiveToolNames
|
|
1387
1395
|
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
1388
1396
|
const explicitlyRequestedMCPToolNames = options.toolNames
|
|
1389
|
-
? requestedActiveToolNames.filter(name => name.startsWith("
|
|
1397
|
+
? requestedActiveToolNames.filter(name => name.startsWith("mcp__"))
|
|
1390
1398
|
: [];
|
|
1391
1399
|
const discoveryDefaultServers = new Set(
|
|
1392
1400
|
(settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
|
|
@@ -1412,7 +1420,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1412
1420
|
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1413
1421
|
initialToolNames = [
|
|
1414
1422
|
...new Set([
|
|
1415
|
-
...initialRequestedActiveToolNames.filter(name => !name.startsWith("
|
|
1423
|
+
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp__")),
|
|
1416
1424
|
...initialSelectedMCPToolNames,
|
|
1417
1425
|
]),
|
|
1418
1426
|
];
|
|
@@ -1424,7 +1432,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1424
1432
|
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
1425
1433
|
];
|
|
1426
1434
|
for (const name of alwaysInclude) {
|
|
1427
|
-
if (mcpDiscoveryEnabled && name.startsWith("
|
|
1435
|
+
if (mcpDiscoveryEnabled && name.startsWith("mcp__")) {
|
|
1428
1436
|
continue;
|
|
1429
1437
|
}
|
|
1430
1438
|
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
@@ -1601,6 +1609,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1601
1609
|
ttsrManager,
|
|
1602
1610
|
obfuscator,
|
|
1603
1611
|
asyncJobManager,
|
|
1612
|
+
agentId: resolvedAgentId,
|
|
1613
|
+
agentRegistry,
|
|
1604
1614
|
});
|
|
1605
1615
|
hasSession = true;
|
|
1606
1616
|
|
|
@@ -130,6 +130,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
|
|
|
130
130
|
type: "text",
|
|
131
131
|
};
|
|
132
132
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
133
|
+
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
133
134
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
134
135
|
import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
|
|
135
136
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
@@ -263,6 +264,10 @@ export interface AgentSessionConfig {
|
|
|
263
264
|
obfuscator?: SecretObfuscator;
|
|
264
265
|
/** Logical owner for retained Python kernels created by this session. */
|
|
265
266
|
pythonKernelOwnerId?: string;
|
|
267
|
+
/** Agent identity (registry id like "0-Main" or "3-Alice") used for IRC routing. */
|
|
268
|
+
agentId?: string;
|
|
269
|
+
/** Shared agent registry (for forwarding IRC observations to the main session UI). */
|
|
270
|
+
agentRegistry?: AgentRegistry;
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
/** Options for AgentSession.prompt() */
|
|
@@ -478,6 +483,9 @@ export class AgentSession {
|
|
|
478
483
|
// Drained into history (via emitExternalEvent) once the recipient becomes idle.
|
|
479
484
|
#pendingBackgroundExchanges: CustomMessage[][] = [];
|
|
480
485
|
#scheduledBackgroundExchangeFlush = false;
|
|
486
|
+
// Agent identity + registry for IRC relay forwarding to the main session UI.
|
|
487
|
+
#agentId: string | undefined;
|
|
488
|
+
#agentRegistry: AgentRegistry | undefined;
|
|
481
489
|
// Extension system
|
|
482
490
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
483
491
|
#turnIndex = 0;
|
|
@@ -611,6 +619,8 @@ export class AgentSession {
|
|
|
611
619
|
);
|
|
612
620
|
this.#ttsrManager = config.ttsrManager;
|
|
613
621
|
this.#obfuscator = config.obfuscator;
|
|
622
|
+
this.#agentId = config.agentId;
|
|
623
|
+
this.#agentRegistry = config.agentRegistry;
|
|
614
624
|
this.agent.setAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
|
|
615
625
|
const event: AgentEvent = {
|
|
616
626
|
type: "message_update",
|
|
@@ -5999,6 +6009,13 @@ export class AgentSession {
|
|
|
5999
6009
|
timestamp: incomingTimestamp,
|
|
6000
6010
|
};
|
|
6001
6011
|
void this.#emitSessionEvent({ type: "irc_message", message: incomingRecord });
|
|
6012
|
+
this.#forwardIrcRelayToMain({
|
|
6013
|
+
from: args.from,
|
|
6014
|
+
to: this.#agentId ?? "?",
|
|
6015
|
+
body: args.message,
|
|
6016
|
+
kind: "message",
|
|
6017
|
+
timestamp: incomingTimestamp,
|
|
6018
|
+
});
|
|
6002
6019
|
|
|
6003
6020
|
if (!awaitReply) {
|
|
6004
6021
|
this.#queueBackgroundExchangeInjection([incomingRecord]);
|
|
@@ -6024,11 +6041,59 @@ export class AgentSession {
|
|
|
6024
6041
|
timestamp: Date.now(),
|
|
6025
6042
|
};
|
|
6026
6043
|
void this.#emitSessionEvent({ type: "irc_message", message: replyRecord });
|
|
6044
|
+
this.#forwardIrcRelayToMain({
|
|
6045
|
+
from: this.#agentId ?? "?",
|
|
6046
|
+
to: args.from,
|
|
6047
|
+
body: replyText,
|
|
6048
|
+
kind: "reply",
|
|
6049
|
+
timestamp: replyRecord.timestamp,
|
|
6050
|
+
});
|
|
6027
6051
|
this.#queueBackgroundExchangeInjection([incomingRecord, replyRecord]);
|
|
6028
6052
|
|
|
6029
6053
|
return { replyText };
|
|
6030
6054
|
}
|
|
6031
6055
|
|
|
6056
|
+
/**
|
|
6057
|
+
* Forward an IRC exchange observation to the main agent's session UI so the
|
|
6058
|
+
* user can see every IRC conversation in the main transcript, even when the
|
|
6059
|
+
* main agent is not a direct participant. The relay record is display-only:
|
|
6060
|
+
* it is NOT injected into the main agent's persisted history.
|
|
6061
|
+
*/
|
|
6062
|
+
#forwardIrcRelayToMain(args: {
|
|
6063
|
+
from: string;
|
|
6064
|
+
to: string;
|
|
6065
|
+
body: string;
|
|
6066
|
+
kind: "message" | "reply";
|
|
6067
|
+
timestamp: number;
|
|
6068
|
+
}): void {
|
|
6069
|
+
const registry = this.#agentRegistry;
|
|
6070
|
+
if (!registry) return;
|
|
6071
|
+
// If this session is the main agent, the local emit already reached the main UI.
|
|
6072
|
+
if (this.#agentId === MAIN_AGENT_ID) return;
|
|
6073
|
+
const mainRef = registry.get(MAIN_AGENT_ID);
|
|
6074
|
+
const mainSession = mainRef?.session;
|
|
6075
|
+
if (!mainSession || mainSession === this) return;
|
|
6076
|
+
const arrow = args.kind === "reply" ? "\u2192 (auto)" : "\u2192";
|
|
6077
|
+
const relayRecord: CustomMessage = {
|
|
6078
|
+
role: "custom",
|
|
6079
|
+
customType: "irc:relay",
|
|
6080
|
+
content: `[IRC \`${args.from}\` ${arrow} \`${args.to}\`]\n\n${args.body}`,
|
|
6081
|
+
display: true,
|
|
6082
|
+
details: { from: args.from, to: args.to, body: args.body, kind: args.kind },
|
|
6083
|
+
attribution: "agent",
|
|
6084
|
+
timestamp: args.timestamp,
|
|
6085
|
+
};
|
|
6086
|
+
mainSession.emitIrcRelayObservation(relayRecord);
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
/**
|
|
6090
|
+
* Emit an IRC relay observation event on this session for UI rendering only.
|
|
6091
|
+
* Does not persist the record to history. Public so other sessions can forward.
|
|
6092
|
+
*/
|
|
6093
|
+
emitIrcRelayObservation(record: CustomMessage): void {
|
|
6094
|
+
void this.#emitSessionEvent({ type: "irc_message", message: record });
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6032
6097
|
/**
|
|
6033
6098
|
* Run a single ephemeral side-channel turn against this session's current
|
|
6034
6099
|
* model + system prompt + history. No tools are used; the side request
|
package/src/system-prompt.ts
CHANGED
|
@@ -384,6 +384,8 @@ export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {
|
|
|
384
384
|
export interface SystemPromptToolMetadata {
|
|
385
385
|
label: string;
|
|
386
386
|
description: string;
|
|
387
|
+
/** Tool name the model sees on the provider wire. Defaults to the internal tool name. */
|
|
388
|
+
wireName?: string;
|
|
387
389
|
}
|
|
388
390
|
|
|
389
391
|
export function buildSystemPromptToolMetadata(
|
|
@@ -394,12 +396,16 @@ export function buildSystemPromptToolMetadata(
|
|
|
394
396
|
Array.from(tools.entries(), ([name, tool]) => {
|
|
395
397
|
const toolRecord = tool as AgentTool & { label?: string; description?: string };
|
|
396
398
|
const override = overrides[name];
|
|
399
|
+
const wireName =
|
|
400
|
+
override?.wireName ??
|
|
401
|
+
(typeof toolRecord.customWireName === "string" ? toolRecord.customWireName : undefined);
|
|
397
402
|
return [
|
|
398
403
|
name,
|
|
399
404
|
{
|
|
400
405
|
label: override?.label ?? (typeof toolRecord.label === "string" ? toolRecord.label : ""),
|
|
401
406
|
description:
|
|
402
407
|
override?.description ?? (typeof toolRecord.description === "string" ? toolRecord.description : ""),
|
|
408
|
+
wireName,
|
|
403
409
|
},
|
|
404
410
|
] as const;
|
|
405
411
|
}),
|
|
@@ -570,14 +576,17 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
570
576
|
}
|
|
571
577
|
}
|
|
572
578
|
|
|
573
|
-
// Build tool descriptions for system prompt rendering
|
|
579
|
+
// Build tool descriptions for system prompt rendering.
|
|
580
|
+
const toolPromptNames = new Map<string, string>(toolNames.map(name => [name, tools?.get(name)?.wireName ?? name]));
|
|
581
|
+
const toolRefs = Object.fromEntries(toolPromptNames.entries());
|
|
574
582
|
const toolInfo = toolNames.map(name => ({
|
|
575
|
-
name,
|
|
583
|
+
name: toolPromptNames.get(name) ?? name,
|
|
584
|
+
internalName: name,
|
|
576
585
|
label: tools?.get(name)?.label ?? "",
|
|
577
586
|
description: tools?.get(name)?.description ?? "",
|
|
578
587
|
}));
|
|
579
588
|
|
|
580
|
-
// Filter skills to only include those with read tool
|
|
589
|
+
// Filter skills to only include those with read tool.
|
|
581
590
|
const hasRead = tools?.has("read");
|
|
582
591
|
const filteredSkills = hasRead ? skills : [];
|
|
583
592
|
|
|
@@ -589,6 +598,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
589
598
|
const injectedAlwaysApplyRules = dedupeAlwaysApplyRules(alwaysApplyRules, promptSources);
|
|
590
599
|
|
|
591
600
|
const environment = await logger.time("getEnvironmentInfo", getEnvironmentInfo);
|
|
601
|
+
const reportToolIssueToolName = toolPromptNames.get("report_tool_issue") ?? "report_tool_issue";
|
|
592
602
|
const data = {
|
|
593
603
|
systemPromptCustomization: effectiveSystemPromptCustomization,
|
|
594
604
|
customPrompt: resolvedCustomPrompt,
|
|
@@ -596,6 +606,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
596
606
|
tools: toolNames,
|
|
597
607
|
toolInfo,
|
|
598
608
|
repeatToolDescriptions,
|
|
609
|
+
toolRefs,
|
|
599
610
|
environment,
|
|
600
611
|
contextFiles,
|
|
601
612
|
agentsMdSearch,
|
|
@@ -617,8 +628,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
617
628
|
|
|
618
629
|
// When autoqa is active the report_tool_issue tool is in the tool set — nudge the agent.
|
|
619
630
|
if (toolNames.includes("report_tool_issue")) {
|
|
620
|
-
rendered +=
|
|
621
|
-
"\n\n<critical>\nThe `report_tool_issue` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call `report_tool_issue` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.\n</critical>";
|
|
631
|
+
rendered += `\n\n<critical>\nThe \`${reportToolIssueToolName}\` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call \`${reportToolIssueToolName}\` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.\n</critical>`;
|
|
622
632
|
}
|
|
623
633
|
|
|
624
634
|
return rendered;
|
package/src/task/executor.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
|
+
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
9
|
import { logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
@@ -16,6 +17,7 @@ import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
|
16
17
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
17
18
|
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
18
19
|
import type { Skill } from "../extensibility/skills";
|
|
20
|
+
import type { LocalProtocolOptions } from "../internal-urls";
|
|
19
21
|
import { callTool } from "../mcp/client";
|
|
20
22
|
import type { MCPManager } from "../mcp/manager";
|
|
21
23
|
import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
|
|
@@ -159,6 +161,8 @@ export interface ExecutorOptions {
|
|
|
159
161
|
authStorage?: AuthStorage;
|
|
160
162
|
modelRegistry?: ModelRegistry;
|
|
161
163
|
settings?: Settings;
|
|
164
|
+
/** Override local:// protocol options so subagent shares parent's local:// root */
|
|
165
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
function parseStringifiedJson(value: unknown): unknown {
|
|
@@ -987,6 +991,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
987
991
|
enableMCP,
|
|
988
992
|
mcpManager: options.mcpManager,
|
|
989
993
|
customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
|
|
994
|
+
localProtocolOptions: options.localProtocolOptions,
|
|
990
995
|
});
|
|
991
996
|
|
|
992
997
|
activeSession = session;
|
package/src/task/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type:
|
|
|
28
28
|
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
29
29
|
// Import review tools for side effects (registers subagent tool handlers)
|
|
30
30
|
import "../tools/review";
|
|
31
|
+
import type { LocalProtocolOptions } from "../internal-urls";
|
|
31
32
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
32
33
|
import * as git from "../utils/git";
|
|
33
34
|
import { discoverAgents, getAgent } from "./discovery";
|
|
@@ -567,7 +568,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
567
568
|
}
|
|
568
569
|
|
|
569
570
|
const planModeState = this.session.getPlanModeState?.();
|
|
570
|
-
const planModeTools = ["read", "
|
|
571
|
+
const planModeTools = ["read", "search", "find", "lsp", "web_search"];
|
|
571
572
|
const effectiveAgent: typeof agent = planModeState?.enabled
|
|
572
573
|
? {
|
|
573
574
|
...agent,
|
|
@@ -715,6 +716,12 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
715
716
|
const tempArtifactsDir = artifactsDir ? null : path.join(os.tmpdir(), `omp-task-${Snowflake.next()}`);
|
|
716
717
|
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
717
718
|
|
|
719
|
+
// Share the parent session's local:// root with subagents so they read/write the same scratch space
|
|
720
|
+
const localProtocolOptions: LocalProtocolOptions = {
|
|
721
|
+
getArtifactsDir: this.session.getArtifactsDir ?? (() => null),
|
|
722
|
+
getSessionId: this.session.getSessionId ?? (() => null),
|
|
723
|
+
};
|
|
724
|
+
|
|
718
725
|
// Initialize progress tracking
|
|
719
726
|
const progressMap = new Map<number, AgentProgress>();
|
|
720
727
|
|
|
@@ -856,6 +863,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
856
863
|
contextFiles,
|
|
857
864
|
skills: availableSkills,
|
|
858
865
|
promptTemplates,
|
|
866
|
+
localProtocolOptions,
|
|
859
867
|
});
|
|
860
868
|
}
|
|
861
869
|
|
|
@@ -909,6 +917,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
909
917
|
contextFiles,
|
|
910
918
|
skills: availableSkills,
|
|
911
919
|
promptTemplates,
|
|
920
|
+
localProtocolOptions,
|
|
912
921
|
});
|
|
913
922
|
if (mergeMode === "branch" && result.exitCode === 0) {
|
|
914
923
|
try {
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
3
2
|
import { type AstReplaceChange, astEdit } from "@oh-my-pi/pi-natives";
|
|
4
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -16,6 +15,7 @@ import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
|
16
15
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
17
16
|
import type { OutputMeta } from "./output-meta";
|
|
18
17
|
import {
|
|
18
|
+
formatPathRelativeToCwd,
|
|
19
19
|
hasGlobPathChars,
|
|
20
20
|
normalizePathLikeInput,
|
|
21
21
|
parseSearchPath,
|
|
@@ -106,10 +106,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
106
106
|
const normalizedRewrites = Object.fromEntries(ops);
|
|
107
107
|
const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
|
|
108
108
|
|
|
109
|
-
const formatScopePath = (targetPath: string): string =>
|
|
110
|
-
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
111
|
-
return relative.length === 0 ? "." : relative;
|
|
112
|
-
};
|
|
109
|
+
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
113
110
|
let searchPath: string | undefined;
|
|
114
111
|
let scopePath: string | undefined;
|
|
115
112
|
let globFilter: string | undefined;
|
|
@@ -164,7 +161,8 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
164
161
|
});
|
|
165
162
|
|
|
166
163
|
const dedupedParseErrors = dedupeParseErrors(result.parseErrors);
|
|
167
|
-
const formatPath = (filePath: string): string =>
|
|
164
|
+
const formatPath = (filePath: string): string =>
|
|
165
|
+
formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
|
|
168
166
|
|
|
169
167
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
170
168
|
const fileReplacementCounts = new Map<string, number>();
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
3
2
|
import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
|
|
4
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -16,6 +15,7 @@ import { formatGroupedFiles } from "./grouped-file-output";
|
|
|
16
15
|
import { formatMatchLine } from "./match-line-format";
|
|
17
16
|
import type { OutputMeta } from "./output-meta";
|
|
18
17
|
import {
|
|
18
|
+
formatPathRelativeToCwd,
|
|
19
19
|
hasGlobPathChars,
|
|
20
20
|
normalizePathLikeInput,
|
|
21
21
|
parseSearchPath,
|
|
@@ -87,10 +87,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
87
87
|
if (!Number.isFinite(skip) || skip < 0) {
|
|
88
88
|
throw new ToolError("skip must be a non-negative number");
|
|
89
89
|
}
|
|
90
|
-
const formatScopePath = (targetPath: string): string =>
|
|
91
|
-
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
92
|
-
return relative.length === 0 ? "." : relative;
|
|
93
|
-
};
|
|
90
|
+
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
94
91
|
let searchPath: string | undefined;
|
|
95
92
|
let scopePath: string | undefined;
|
|
96
93
|
let globFilter: string | undefined;
|
|
@@ -147,7 +144,8 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
147
144
|
return parseError?.[1] ?? error;
|
|
148
145
|
});
|
|
149
146
|
const dedupedParseErrors = dedupeParseErrors(normalizedParseErrors);
|
|
150
|
-
const formatPath = (filePath: string): string =>
|
|
147
|
+
const formatPath = (filePath: string): string =>
|
|
148
|
+
formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
|
|
151
149
|
|
|
152
150
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
153
151
|
const fileMatchCounts = new Map<string, number>();
|
package/src/tools/bash.ts
CHANGED
|
@@ -269,7 +269,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
269
269
|
autoBackgroundThresholdSeconds: Math.max(0, Math.floor(this.#autoBackgroundThresholdMs / 1000)),
|
|
270
270
|
hasAstGrep: this.session.settings.get("astGrep.enabled"),
|
|
271
271
|
hasAstEdit: this.session.settings.get("astEdit.enabled"),
|
|
272
|
-
|
|
272
|
+
hasSearch: this.session.settings.get("search.enabled"),
|
|
273
273
|
hasFind: this.session.settings.get("find.enabled"),
|
|
274
274
|
});
|
|
275
275
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { formatPathRelativeToCwd } from "./path-utils";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Creates a deduplicating recorder for relative file paths.
|
|
@@ -22,14 +23,13 @@ export function createFileRecorder(): {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* Strip
|
|
26
|
-
*
|
|
27
|
-
* so tool output does not leak absolute paths.
|
|
26
|
+
* Strip native virtual-root prefixes and format file paths relative to cwd when
|
|
27
|
+
* they are inside cwd. Paths outside cwd remain absolute.
|
|
28
28
|
*/
|
|
29
|
-
export function formatResultPath(filePath: string, isDirectory: boolean): string {
|
|
29
|
+
export function formatResultPath(filePath: string, isDirectory: boolean, basePath: string, cwd: string): string {
|
|
30
30
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
31
31
|
if (isDirectory) {
|
|
32
|
-
return
|
|
32
|
+
return formatPathRelativeToCwd(path.resolve(basePath, cleanPath), cwd);
|
|
33
33
|
}
|
|
34
|
-
return
|
|
34
|
+
return formatPathRelativeToCwd(basePath, cwd);
|
|
35
35
|
}
|
package/src/tools/find.ts
CHANGED
|
@@ -23,7 +23,13 @@ import {
|
|
|
23
23
|
import type { ToolSession } from ".";
|
|
24
24
|
import { applyListLimit } from "./list-limit";
|
|
25
25
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
formatPathRelativeToCwd,
|
|
28
|
+
normalizePathLikeInput,
|
|
29
|
+
parseFindPattern,
|
|
30
|
+
resolveMultiFindPattern,
|
|
31
|
+
resolveToCwd,
|
|
32
|
+
} from "./path-utils";
|
|
27
33
|
import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils";
|
|
28
34
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
29
35
|
import { toolResult } from "./tool-result";
|
|
@@ -101,10 +107,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
101
107
|
const { pattern, limit, hidden } = params;
|
|
102
108
|
|
|
103
109
|
return untilAborted(signal, async () => {
|
|
104
|
-
const formatScopePath = (targetPath: string): string =>
|
|
105
|
-
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
106
|
-
return relative.length === 0 ? "." : relative;
|
|
107
|
-
};
|
|
110
|
+
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
108
111
|
const normalizedPattern = normalizePathLikeInput(pattern).replace(/\\/g, "/");
|
|
109
112
|
if (!normalizedPattern) {
|
|
110
113
|
throw new ToolError("Pattern must not be empty");
|
|
@@ -132,14 +135,9 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
132
135
|
const formatMatchPath = (matchPath: string, fileType?: natives.FileType): string => {
|
|
133
136
|
const hadTrailingSlash = matchPath.endsWith("/") || matchPath.endsWith("\\");
|
|
134
137
|
const absolutePath = path.isAbsolute(matchPath) ? matchPath : path.resolve(searchPath, matchPath);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
if ((fileType === natives.FileType.Dir || hadTrailingSlash) && !relativePath.endsWith("/")) {
|
|
140
|
-
relativePath += "/";
|
|
141
|
-
}
|
|
142
|
-
return relativePath;
|
|
138
|
+
return formatPathRelativeToCwd(absolutePath, this.session.cwd, {
|
|
139
|
+
trailingSlash: fileType === natives.FileType.Dir || hadTrailingSlash,
|
|
140
|
+
});
|
|
143
141
|
};
|
|
144
142
|
|
|
145
143
|
const buildResult = (files: string[]): AgentToolResult<FindToolDetails> => {
|
package/src/tools/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { ToolChoiceQueue } from "../session/tool-choice-queue";
|
|
|
18
18
|
import { TaskTool } from "../task";
|
|
19
19
|
import type { AgentOutputManager } from "../task/output-manager";
|
|
20
20
|
import type { EventBus } from "../utils/event-bus";
|
|
21
|
-
import {
|
|
21
|
+
import { WebSearchTool } from "../web/search";
|
|
22
22
|
import { AskTool } from "./ask";
|
|
23
23
|
import { AstEditTool } from "./ast-edit";
|
|
24
24
|
import { AstGrepTool } from "./ast-grep";
|
|
@@ -30,7 +30,6 @@ import { DebugTool } from "./debug";
|
|
|
30
30
|
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
31
31
|
import { FindTool } from "./find";
|
|
32
32
|
import { GithubTool } from "./gh";
|
|
33
|
-
import { GrepTool } from "./grep";
|
|
34
33
|
import { InspectImageTool } from "./inspect-image";
|
|
35
34
|
import { IrcTool } from "./irc";
|
|
36
35
|
import { JobTool } from "./job";
|
|
@@ -42,6 +41,7 @@ import { RenderMermaidTool } from "./render-mermaid";
|
|
|
42
41
|
import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
|
|
43
42
|
import { ResolveTool } from "./resolve";
|
|
44
43
|
import { reportFindingTool } from "./review";
|
|
44
|
+
import { SearchTool } from "./search";
|
|
45
45
|
import { SearchToolBm25Tool } from "./search-tool-bm25";
|
|
46
46
|
import { loadSshTool } from "./ssh";
|
|
47
47
|
import { type TodoPhase, TodoWriteTool } from "./todo-write";
|
|
@@ -68,7 +68,6 @@ export * from "./debug";
|
|
|
68
68
|
export * from "./exit-plan-mode";
|
|
69
69
|
export * from "./find";
|
|
70
70
|
export * from "./gh";
|
|
71
|
-
export * from "./grep";
|
|
72
71
|
export * from "./image-gen";
|
|
73
72
|
export * from "./inspect-image";
|
|
74
73
|
export * from "./irc";
|
|
@@ -80,6 +79,7 @@ export * from "./render-mermaid";
|
|
|
80
79
|
export * from "./report-tool-issue";
|
|
81
80
|
export * from "./resolve";
|
|
82
81
|
export * from "./review";
|
|
82
|
+
export * from "./search";
|
|
83
83
|
export * from "./search-tool-bm25";
|
|
84
84
|
export * from "./ssh";
|
|
85
85
|
export * from "./todo-write";
|
|
@@ -214,7 +214,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
214
214
|
edit: s => new EditTool(s),
|
|
215
215
|
github: GithubTool.createIf,
|
|
216
216
|
find: s => new FindTool(s),
|
|
217
|
-
|
|
217
|
+
search: s => new SearchTool(s),
|
|
218
218
|
lsp: LspTool.createIf,
|
|
219
219
|
notebook: s => new NotebookTool(s),
|
|
220
220
|
read: s => new ReadTool(s),
|
|
@@ -226,7 +226,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
226
226
|
job: JobTool.createIf,
|
|
227
227
|
irc: IrcTool.createIf,
|
|
228
228
|
todo_write: s => new TodoWriteTool(s),
|
|
229
|
-
web_search: s => new
|
|
229
|
+
web_search: s => new WebSearchTool(s),
|
|
230
230
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
231
231
|
write: s => new WriteTool(s),
|
|
232
232
|
};
|
|
@@ -357,7 +357,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
357
357
|
// Auto-include AST counterparts when their text-based sibling is present
|
|
358
358
|
if (requestedTools) {
|
|
359
359
|
if (
|
|
360
|
-
requestedTools.includes("
|
|
360
|
+
requestedTools.includes("search") &&
|
|
361
361
|
!requestedTools.includes("ast_grep") &&
|
|
362
362
|
session.settings.get("astGrep.enabled")
|
|
363
363
|
) {
|
|
@@ -379,7 +379,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
379
379
|
if (name === "debug") return session.settings.get("debug.enabled");
|
|
380
380
|
if (name === "todo_write") return !includeYield && session.settings.get("todo.enabled");
|
|
381
381
|
if (name === "find") return session.settings.get("find.enabled");
|
|
382
|
-
if (name === "
|
|
382
|
+
if (name === "search") return session.settings.get("search.enabled");
|
|
383
383
|
if (name === "github") return session.settings.get("github.enabled");
|
|
384
384
|
if (name === "ast_grep") return session.settings.get("astGrep.enabled");
|
|
385
385
|
if (name === "ast_edit") return session.settings.get("astEdit.enabled");
|