@oh-my-pi/pi-coding-agent 14.9.2 → 14.9.5
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 +89 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +3 -3
- package/src/async/job-manager.ts +66 -9
- package/src/capability/rule.ts +20 -0
- package/src/config/model-registry.ts +13 -0
- package/src/config/model-resolver.ts +8 -2
- package/src/config/prompt-templates.ts +0 -5
- package/src/config/settings-schema.ts +39 -1
- package/src/edit/index.ts +8 -0
- package/src/edit/renderer.ts +6 -1
- package/src/edit/streaming.ts +53 -2
- package/src/eval/eval.lark +10 -31
- package/src/eval/index.ts +1 -0
- package/src/eval/js/context-manager.ts +1 -38
- package/src/eval/js/prelude.txt +0 -2
- package/src/eval/parse.ts +156 -255
- package/src/eval/py/executor.ts +24 -8
- package/src/eval/py/index.ts +1 -0
- package/src/eval/py/prelude.py +11 -80
- package/src/eval/sniff.ts +28 -0
- package/src/export/html/template.css +50 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +229 -17
- package/src/extensibility/plugins/loader.ts +31 -6
- package/src/extensibility/skills.ts +20 -0
- package/src/hashline/constants.ts +20 -0
- package/src/hashline/grammar.lark +16 -23
- package/src/hashline/hash.ts +4 -34
- package/src/hashline/input.ts +16 -2
- package/src/hashline/parser.ts +12 -1
- package/src/internal-urls/agent-protocol.ts +64 -52
- package/src/internal-urls/artifact-protocol.ts +52 -51
- package/src/internal-urls/docs-index.generated.ts +34 -1
- package/src/internal-urls/index.ts +6 -19
- package/src/internal-urls/local-protocol.ts +50 -7
- package/src/internal-urls/mcp-protocol.ts +3 -8
- package/src/internal-urls/memory-protocol.ts +90 -59
- package/src/internal-urls/pi-protocol.ts +1 -0
- package/src/internal-urls/router.ts +40 -23
- package/src/internal-urls/rule-protocol.ts +3 -20
- package/src/internal-urls/skill-protocol.ts +5 -27
- package/src/internal-urls/types.ts +18 -2
- package/src/main.ts +1 -1
- package/src/mcp/manager.ts +17 -0
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/tool-execution.ts +6 -0
- package/src/modes/components/tree-selector.ts +4 -0
- package/src/modes/controllers/event-controller.ts +23 -2
- package/src/modes/controllers/mcp-command-controller.ts +7 -10
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/theme/theme.ts +27 -27
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +14 -9
- package/src/prompts/commands/orchestrate.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +0 -2
- package/src/prompts/system/project-prompt.md +10 -0
- package/src/prompts/system/subagent-system-prompt.md +18 -9
- package/src/prompts/system/subagent-user-prompt.md +1 -10
- package/src/prompts/system/system-prompt.md +159 -232
- package/src/prompts/tools/ask.md +0 -1
- package/src/prompts/tools/bash.md +0 -34
- package/src/prompts/tools/eval.md +27 -16
- package/src/prompts/tools/github.md +6 -5
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/job.md +14 -6
- package/src/prompts/tools/task.md +20 -3
- package/src/registry/agent-registry.ts +2 -1
- package/src/sdk.ts +87 -89
- package/src/session/agent-session.ts +107 -37
- package/src/session/artifacts.ts +7 -4
- package/src/session/session-manager.ts +30 -1
- package/src/ssh/connection-manager.ts +32 -16
- package/src/ssh/sshfs-mount.ts +10 -7
- package/src/system-prompt.ts +3 -9
- package/src/task/executor.ts +23 -7
- package/src/task/index.ts +57 -36
- package/src/tool-discovery/tool-index.ts +21 -8
- package/src/tools/ast-edit.ts +3 -2
- package/src/tools/ast-grep.ts +3 -2
- package/src/tools/bash.ts +30 -50
- package/src/tools/browser/tab-supervisor.ts +12 -2
- package/src/tools/eval.ts +59 -44
- package/src/tools/fetch.ts +1 -1
- package/src/tools/gh.ts +140 -4
- package/src/tools/index.ts +12 -11
- package/src/tools/job.ts +48 -12
- package/src/tools/path-utils.ts +21 -1
- package/src/tools/read.ts +74 -31
- package/src/tools/search.ts +16 -3
- package/src/tools/todo-write.ts +1 -1
- package/src/utils/file-display-mode.ts +11 -5
- package/src/web/scrapers/mastodon.ts +1 -1
- package/src/web/scrapers/repology.ts +7 -7
- package/src/internal-urls/jobs-protocol.ts +0 -119
- package/src/task/template.ts +0 -47
- package/src/tools/bash-normalize.ts +0 -107
package/src/task/executor.ts
CHANGED
|
@@ -26,9 +26,11 @@ import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md
|
|
|
26
26
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
27
27
|
import { createAgentSession, discoverAuthStorage } from "../sdk";
|
|
28
28
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
29
|
+
import type { ArtifactManager } from "../session/artifacts";
|
|
29
30
|
import type { AuthStorage } from "../session/auth-storage";
|
|
30
31
|
import { SessionManager } from "../session/session-manager";
|
|
31
|
-
import {
|
|
32
|
+
import { truncateTail } from "../session/streaming-output";
|
|
33
|
+
import type { ContextFileEntry } from "../tools";
|
|
32
34
|
import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
|
|
33
35
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
34
36
|
import type { EventBus } from "../utils/event-bus";
|
|
@@ -139,6 +141,7 @@ export interface ExecutorOptions {
|
|
|
139
141
|
agent: AgentDefinition;
|
|
140
142
|
task: string;
|
|
141
143
|
assignment?: string;
|
|
144
|
+
context?: string;
|
|
142
145
|
description?: string;
|
|
143
146
|
index: number;
|
|
144
147
|
id: string;
|
|
@@ -171,6 +174,12 @@ export interface ExecutorOptions {
|
|
|
171
174
|
settings?: Settings;
|
|
172
175
|
/** Override local:// protocol options so subagent shares parent's local:// root */
|
|
173
176
|
localProtocolOptions?: LocalProtocolOptions;
|
|
177
|
+
/**
|
|
178
|
+
* Parent session's ArtifactManager. Subagent adopts it so artifact IDs are
|
|
179
|
+
* unique across the whole agent tree and all artifacts land in the parent's
|
|
180
|
+
* artifacts directory (no per-subagent subdir).
|
|
181
|
+
*/
|
|
182
|
+
parentArtifactManager?: ArtifactManager;
|
|
174
183
|
parentHindsightSessionState?: HindsightSessionState;
|
|
175
184
|
}
|
|
176
185
|
|
|
@@ -562,6 +571,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
562
571
|
|
|
563
572
|
const lspEnabled = enableLsp ?? true;
|
|
564
573
|
const ircEnabled = subagentSettings.get("irc.enabled") === true;
|
|
574
|
+
const contextFileForPrompt = ircEnabled ? undefined : options.contextFile;
|
|
565
575
|
const skipPythonPreflight = Array.isArray(toolNames) && !toolNames.includes("eval");
|
|
566
576
|
|
|
567
577
|
const outputChunks: string[] = [];
|
|
@@ -974,6 +984,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
974
984
|
const sessionManager = sessionFile
|
|
975
985
|
? await SessionManager.open(sessionFile)
|
|
976
986
|
: SessionManager.inMemory(worktree ?? cwd);
|
|
987
|
+
if (options.parentArtifactManager) {
|
|
988
|
+
sessionManager.adoptArtifactManager(options.parentArtifactManager);
|
|
989
|
+
}
|
|
977
990
|
|
|
978
991
|
const mcpProxyTools = options.mcpManager ? createMCPProxyTools(options.mcpManager) : [];
|
|
979
992
|
const enableMCP = !options.mcpManager;
|
|
@@ -994,17 +1007,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
994
1007
|
skills: options.skills,
|
|
995
1008
|
promptTemplates: options.promptTemplates,
|
|
996
1009
|
workspaceTree: options.workspaceTree,
|
|
997
|
-
systemPrompt: defaultPrompt =>
|
|
998
|
-
prompt.render(subagentSystemPromptTemplate, {
|
|
999
|
-
base: defaultPrompt.join("\n\n"),
|
|
1010
|
+
systemPrompt: defaultPrompt => {
|
|
1011
|
+
const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
|
|
1000
1012
|
agent: agent.systemPrompt,
|
|
1013
|
+
context: options.context?.trim() ?? "",
|
|
1001
1014
|
worktree: worktree ?? "",
|
|
1002
1015
|
outputSchema: normalizedOutputSchema,
|
|
1003
|
-
contextFile:
|
|
1016
|
+
contextFile: contextFileForPrompt,
|
|
1004
1017
|
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
1005
1018
|
ircSelfId: ircEnabled ? id : "",
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1019
|
+
});
|
|
1020
|
+
return defaultPrompt.length === 0
|
|
1021
|
+
? [subagentPrompt]
|
|
1022
|
+
: [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
|
|
1023
|
+
},
|
|
1008
1024
|
sessionManager,
|
|
1009
1025
|
hasUI: false,
|
|
1010
1026
|
spawns: spawnsEnv,
|
package/src/task/index.ts
CHANGED
|
@@ -20,9 +20,12 @@ import type { Usage } from "@oh-my-pi/pi-ai";
|
|
|
20
20
|
import { $env, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
21
21
|
import type { TSchema } from "@sinclair/typebox";
|
|
22
22
|
import type { ToolSession } from "..";
|
|
23
|
+
import { AsyncJobManager } from "../async";
|
|
23
24
|
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
25
|
+
import { MCPManager } from "../mcp/manager";
|
|
24
26
|
import type { Theme } from "../modes/theme/theme";
|
|
25
27
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
28
|
+
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
26
29
|
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
|
27
30
|
import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
|
|
28
31
|
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
@@ -38,7 +41,6 @@ import { AgentOutputManager } from "./output-manager";
|
|
|
38
41
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
39
42
|
import { renderResult, renderCall as renderTaskCall } from "./render";
|
|
40
43
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
41
|
-
import { renderTemplate } from "./template";
|
|
42
44
|
import {
|
|
43
45
|
type AgentDefinition,
|
|
44
46
|
type AgentProgress,
|
|
@@ -65,6 +67,12 @@ import {
|
|
|
65
67
|
type WorktreeBaseline,
|
|
66
68
|
} from "./worktree";
|
|
67
69
|
|
|
70
|
+
function renderSubagentUserPrompt(assignment: string, simpleMode: TaskSimpleMode): string {
|
|
71
|
+
return prompt.render(subagentUserPromptTemplate, {
|
|
72
|
+
assignment: assignment.trim(),
|
|
73
|
+
independentMode: simpleMode === "independent",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
68
76
|
function createUsageTotals(): Usage {
|
|
69
77
|
return {
|
|
70
78
|
input: 0,
|
|
@@ -135,6 +143,7 @@ function renderDescription(
|
|
|
135
143
|
asyncEnabled: boolean,
|
|
136
144
|
disabledAgents: string[],
|
|
137
145
|
simpleMode: TaskSimpleMode,
|
|
146
|
+
ircEnabled: boolean,
|
|
138
147
|
): string {
|
|
139
148
|
const filteredAgents = disabledAgents.length > 0 ? agents.filter(a => !disabledAgents.includes(a.name)) : agents;
|
|
140
149
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
@@ -145,6 +154,7 @@ function renderDescription(
|
|
|
145
154
|
asyncEnabled,
|
|
146
155
|
contextEnabled,
|
|
147
156
|
customSchemaEnabled,
|
|
157
|
+
ircEnabled,
|
|
148
158
|
defaultMode: simpleMode === "default",
|
|
149
159
|
schemaFreeMode: simpleMode === "schema-free",
|
|
150
160
|
independentMode: simpleMode === "independent",
|
|
@@ -223,6 +233,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
223
233
|
this.session.settings.get("async.enabled"),
|
|
224
234
|
disabledAgents,
|
|
225
235
|
this.#getTaskSimpleMode(),
|
|
236
|
+
this.session.settings.get("irc.enabled") === true,
|
|
226
237
|
);
|
|
227
238
|
}
|
|
228
239
|
private constructor(
|
|
@@ -264,7 +275,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
264
275
|
return this.#executeSync(_toolCallId, params, signal, onUpdate);
|
|
265
276
|
}
|
|
266
277
|
|
|
267
|
-
const manager =
|
|
278
|
+
const manager = AsyncJobManager.instance();
|
|
268
279
|
if (!manager) {
|
|
269
280
|
return {
|
|
270
281
|
content: [{ type: "text", text: "Async execution is enabled but no async job manager is available." }],
|
|
@@ -282,21 +293,19 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
282
293
|
const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id));
|
|
283
294
|
const fallbackAgentSource =
|
|
284
295
|
this.#discoveredAgents.find(agent => agent.name === params.agent)?.source ?? "bundled";
|
|
285
|
-
const { contextEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
286
|
-
const sharedContext = contextEnabled ? params.context : undefined;
|
|
287
|
-
const renderedTasks = taskItems.map(taskItem => renderTemplate(sharedContext, taskItem, simpleMode));
|
|
288
296
|
const progressByTaskId = new Map<string, AgentProgress>();
|
|
289
|
-
for (let index = 0; index <
|
|
290
|
-
const
|
|
291
|
-
|
|
297
|
+
for (let index = 0; index < taskItems.length; index++) {
|
|
298
|
+
const taskItem = taskItems[index];
|
|
299
|
+
const assignment = taskItem.assignment.trim();
|
|
300
|
+
progressByTaskId.set(taskItem.id, {
|
|
292
301
|
index,
|
|
293
|
-
id:
|
|
302
|
+
id: taskItem.id,
|
|
294
303
|
agent: params.agent,
|
|
295
304
|
agentSource: fallbackAgentSource,
|
|
296
305
|
status: "pending",
|
|
297
|
-
task:
|
|
298
|
-
assignment
|
|
299
|
-
description:
|
|
306
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
307
|
+
assignment,
|
|
308
|
+
description: taskItem.description,
|
|
300
309
|
recentTools: [],
|
|
301
310
|
recentOutput: [],
|
|
302
311
|
toolCount: 0,
|
|
@@ -440,6 +449,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
440
449
|
},
|
|
441
450
|
{
|
|
442
451
|
id: label,
|
|
452
|
+
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
443
453
|
onProgress: (text, details) => {
|
|
444
454
|
const progressDetails =
|
|
445
455
|
(details as TaskToolDetails | undefined) ??
|
|
@@ -506,7 +516,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
506
516
|
const { agent: agentName, context, schema: outputSchema } = params;
|
|
507
517
|
const simpleMode = this.#getTaskSimpleMode();
|
|
508
518
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
509
|
-
const sharedContext = contextEnabled ? context : undefined;
|
|
519
|
+
const sharedContext = contextEnabled ? context?.trim() : undefined;
|
|
510
520
|
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
511
521
|
const isolationRequested = "isolated" in params ? params.isolated === true : false;
|
|
512
522
|
const isIsolated = isolationMode !== "none" && isolationRequested;
|
|
@@ -725,6 +735,10 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
725
735
|
getSessionId: this.session.getSessionId ?? (() => null),
|
|
726
736
|
};
|
|
727
737
|
|
|
738
|
+
// Subagents adopt the parent's ArtifactManager so artifact IDs are unique
|
|
739
|
+
// across the whole tree and outputs land flat in the parent's dir.
|
|
740
|
+
const parentArtifactManager = this.session.getArtifactManager?.() ?? undefined;
|
|
741
|
+
|
|
728
742
|
// Initialize progress tracking
|
|
729
743
|
const progressMap = new Map<number, AgentProgress>();
|
|
730
744
|
|
|
@@ -781,9 +795,11 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
781
795
|
};
|
|
782
796
|
}
|
|
783
797
|
|
|
784
|
-
// Write parent conversation context for subagents
|
|
798
|
+
// Write parent conversation context for subagents. When IRC is available,
|
|
799
|
+
// subagents should ask live peers instead of reading a stale markdown dump.
|
|
785
800
|
await fs.mkdir(effectiveArtifactsDir, { recursive: true });
|
|
786
|
-
const
|
|
801
|
+
const shouldWriteConversationContext = this.session.settings.get("irc.enabled") !== true;
|
|
802
|
+
const compactContext = shouldWriteConversationContext ? this.session.getCompactContext?.() : undefined;
|
|
787
803
|
let contextFilePath: string | undefined;
|
|
788
804
|
if (compactContext) {
|
|
789
805
|
contextFilePath = path.join(effectiveArtifactsDir, "context.md");
|
|
@@ -802,8 +818,6 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
802
818
|
}
|
|
803
819
|
const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: uniqueIds[i] }));
|
|
804
820
|
|
|
805
|
-
// Build full prompts using shared context only when the current task mode allows it.
|
|
806
|
-
const tasksWithContext = tasksWithUniqueIds.map(t => renderTemplate(sharedContext, t, simpleMode));
|
|
807
821
|
const availableSkills = [...(this.session.skills ?? [])];
|
|
808
822
|
const contextFiles = this.session.contextFiles?.filter(
|
|
809
823
|
file => path.basename(file.path).toLowerCase() !== "agents.md",
|
|
@@ -811,34 +825,36 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
811
825
|
const promptTemplates = this.session.promptTemplates;
|
|
812
826
|
|
|
813
827
|
// Initialize progress for all tasks
|
|
814
|
-
for (let i = 0; i <
|
|
815
|
-
const
|
|
828
|
+
for (let i = 0; i < tasksWithUniqueIds.length; i++) {
|
|
829
|
+
const taskItem = tasksWithUniqueIds[i];
|
|
830
|
+
const assignment = taskItem.assignment.trim();
|
|
816
831
|
progressMap.set(i, {
|
|
817
832
|
index: i,
|
|
818
|
-
id:
|
|
833
|
+
id: taskItem.id,
|
|
819
834
|
agent: agentName,
|
|
820
835
|
agentSource: agent.source,
|
|
821
836
|
status: "pending",
|
|
822
|
-
task:
|
|
823
|
-
assignment
|
|
837
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
838
|
+
assignment,
|
|
824
839
|
recentTools: [],
|
|
825
840
|
recentOutput: [],
|
|
826
841
|
toolCount: 0,
|
|
827
842
|
tokens: 0,
|
|
828
843
|
durationMs: 0,
|
|
829
844
|
modelOverride,
|
|
830
|
-
description:
|
|
845
|
+
description: taskItem.description,
|
|
831
846
|
});
|
|
832
847
|
}
|
|
833
848
|
emitProgress();
|
|
834
849
|
|
|
835
|
-
const runTask = async (task: (typeof
|
|
850
|
+
const runTask = async (task: (typeof tasksWithUniqueIds)[number], index: number) => {
|
|
836
851
|
if (!isIsolated) {
|
|
837
852
|
return runSubprocess({
|
|
838
853
|
cwd: this.session.cwd,
|
|
839
854
|
agent,
|
|
840
|
-
task: task.
|
|
841
|
-
assignment: task.assignment,
|
|
855
|
+
task: renderSubagentUserPrompt(task.assignment, simpleMode),
|
|
856
|
+
assignment: task.assignment.trim(),
|
|
857
|
+
context: sharedContext,
|
|
842
858
|
description: task.description,
|
|
843
859
|
index,
|
|
844
860
|
id: task.id,
|
|
@@ -863,12 +879,13 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
863
879
|
authStorage: this.session.authStorage,
|
|
864
880
|
modelRegistry: this.session.modelRegistry,
|
|
865
881
|
settings: this.session.settings,
|
|
866
|
-
mcpManager:
|
|
882
|
+
mcpManager: MCPManager.instance(),
|
|
867
883
|
contextFiles,
|
|
868
884
|
skills: availableSkills,
|
|
869
885
|
workspaceTree: this.session.workspaceTree,
|
|
870
886
|
promptTemplates,
|
|
871
887
|
localProtocolOptions,
|
|
888
|
+
parentArtifactManager,
|
|
872
889
|
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
873
890
|
});
|
|
874
891
|
}
|
|
@@ -894,8 +911,9 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
894
911
|
cwd: this.session.cwd,
|
|
895
912
|
worktree: isolationDir,
|
|
896
913
|
agent,
|
|
897
|
-
task: task.
|
|
898
|
-
assignment: task.assignment,
|
|
914
|
+
task: renderSubagentUserPrompt(task.assignment, simpleMode),
|
|
915
|
+
assignment: task.assignment.trim(),
|
|
916
|
+
context: sharedContext,
|
|
899
917
|
description: task.description,
|
|
900
918
|
index,
|
|
901
919
|
id: task.id,
|
|
@@ -920,12 +938,13 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
920
938
|
authStorage: this.session.authStorage,
|
|
921
939
|
modelRegistry: this.session.modelRegistry,
|
|
922
940
|
settings: this.session.settings,
|
|
923
|
-
mcpManager:
|
|
941
|
+
mcpManager: MCPManager.instance(),
|
|
924
942
|
contextFiles,
|
|
925
943
|
skills: availableSkills,
|
|
926
944
|
workspaceTree: this.session.workspaceTree,
|
|
927
945
|
promptTemplates,
|
|
928
946
|
localProtocolOptions,
|
|
947
|
+
parentArtifactManager,
|
|
929
948
|
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
930
949
|
});
|
|
931
950
|
if (mergeMode === "branch" && result.exitCode === 0) {
|
|
@@ -979,13 +998,14 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
979
998
|
return result;
|
|
980
999
|
} catch (err) {
|
|
981
1000
|
const message = err instanceof Error ? err.message : String(err);
|
|
1001
|
+
const assignment = task.assignment.trim();
|
|
982
1002
|
return {
|
|
983
1003
|
index,
|
|
984
1004
|
id: task.id,
|
|
985
1005
|
agent: agent.name,
|
|
986
1006
|
agentSource: agent.source,
|
|
987
|
-
task:
|
|
988
|
-
assignment
|
|
1007
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
1008
|
+
assignment,
|
|
989
1009
|
description: task.description,
|
|
990
1010
|
exitCode: 1,
|
|
991
1011
|
output: "",
|
|
@@ -1011,7 +1031,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1011
1031
|
|
|
1012
1032
|
// Execute in parallel with concurrency limit
|
|
1013
1033
|
const { results: partialResults, aborted } = await mapWithConcurrencyLimit(
|
|
1014
|
-
|
|
1034
|
+
tasksWithUniqueIds,
|
|
1015
1035
|
maxConcurrency,
|
|
1016
1036
|
runTask,
|
|
1017
1037
|
signal,
|
|
@@ -1022,14 +1042,15 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1022
1042
|
if (result !== undefined) {
|
|
1023
1043
|
return result;
|
|
1024
1044
|
}
|
|
1025
|
-
const task =
|
|
1045
|
+
const task = tasksWithUniqueIds[index];
|
|
1046
|
+
const assignment = task.assignment.trim();
|
|
1026
1047
|
return {
|
|
1027
1048
|
index,
|
|
1028
1049
|
id: task.id,
|
|
1029
1050
|
agent: agentName,
|
|
1030
1051
|
agentSource: agent.source,
|
|
1031
|
-
task:
|
|
1032
|
-
assignment
|
|
1052
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
1053
|
+
assignment,
|
|
1033
1054
|
description: task.description,
|
|
1034
1055
|
exitCode: 1,
|
|
1035
1056
|
output: "",
|
|
@@ -89,6 +89,7 @@ export interface DiscoverableMCPSearchResult {
|
|
|
89
89
|
|
|
90
90
|
const BM25_K1 = 1.2;
|
|
91
91
|
const BM25_B = 0.75;
|
|
92
|
+
const BM25_DELTA = 1.0;
|
|
92
93
|
const FIELD_WEIGHTS = {
|
|
93
94
|
name: 6,
|
|
94
95
|
label: 4,
|
|
@@ -112,13 +113,24 @@ function getSchemaPropertyKeys(parameters: unknown): string[] {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
function tokenize(value: string): string[] {
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
return (
|
|
117
|
+
value
|
|
118
|
+
.normalize("NFKD")
|
|
119
|
+
// Drop combining marks (accents) so "café" → "cafe".
|
|
120
|
+
.replace(/\p{M}+/gu, "")
|
|
121
|
+
// Split ACRONYMBoundary: "MCPTool" → "MCP Tool".
|
|
122
|
+
.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, "$1 $2")
|
|
123
|
+
// Split camelCase / digit→letter: "fooBar" → "foo Bar", "v2Beta" → "v2 Beta".
|
|
124
|
+
.replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, "$1 $2")
|
|
125
|
+
// Everything that isn't a letter or digit becomes a separator. This subsumes markdown
|
|
126
|
+
// punctuation (`|*_`#-~>[]()`), box-drawing glyphs (─│┌), em/en dashes, smart quotes,
|
|
127
|
+
// zero-width spaces, NBSPs, etc.
|
|
128
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.trim()
|
|
131
|
+
.split(/\s+/)
|
|
132
|
+
.filter(token => token.length > 0)
|
|
133
|
+
);
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
function addWeightedTokens(termFrequencies: Map<string, number>, value: string | undefined, weight: number): void {
|
|
@@ -274,7 +286,8 @@ export function searchDiscoverableTools(
|
|
|
274
286
|
const documentFrequency = index.documentFrequencies.get(token) ?? 0;
|
|
275
287
|
const idf = Math.log(1 + (index.documents.length - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
276
288
|
const normalization = BM25_K1 * (1 - BM25_B + BM25_B * (document.length / index.averageLength));
|
|
277
|
-
score +=
|
|
289
|
+
score +=
|
|
290
|
+
queryTermCount * idf * ((termFrequency * (BM25_K1 + 1)) / (termFrequency + normalization) + BM25_DELTA);
|
|
278
291
|
}
|
|
279
292
|
return { tool: document.tool, score };
|
|
280
293
|
})
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { computeLineHash, HL_BODY_SEP } from "../hashline/hash";
|
|
10
|
+
import { InternalUrlRouter } from "../internal-urls";
|
|
10
11
|
import type { Theme } from "../modes/theme/theme";
|
|
11
12
|
import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text" };
|
|
12
13
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
@@ -213,10 +214,10 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
213
214
|
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
214
215
|
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
215
216
|
}
|
|
216
|
-
const internalRouter =
|
|
217
|
+
const internalRouter = InternalUrlRouter.instance();
|
|
217
218
|
const resolvedPathInputs: string[] = [];
|
|
218
219
|
for (const rawPath of rawPaths) {
|
|
219
|
-
if (!internalRouter
|
|
220
|
+
if (!internalRouter.canHandle(rawPath)) {
|
|
220
221
|
resolvedPathInputs.push(rawPath);
|
|
221
222
|
continue;
|
|
222
223
|
}
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Text } from "@oh-my-pi/pi-tui";
|
|
|
6
6
|
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
|
+
import { InternalUrlRouter } from "../internal-urls";
|
|
9
10
|
import type { Theme } from "../modes/theme/theme";
|
|
10
11
|
import astGrepDescription from "../prompts/tools/ast-grep.md" with { type: "text" };
|
|
11
12
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
@@ -158,10 +159,10 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
158
159
|
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
159
160
|
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
160
161
|
}
|
|
161
|
-
const internalRouter =
|
|
162
|
+
const internalRouter = InternalUrlRouter.instance();
|
|
162
163
|
const resolvedPathInputs: string[] = [];
|
|
163
164
|
for (const rawPath of rawPaths) {
|
|
164
|
-
if (!internalRouter
|
|
165
|
+
if (!internalRouter.canHandle(rawPath)) {
|
|
165
166
|
resolvedPathInputs.push(rawPath);
|
|
166
167
|
continue;
|
|
167
168
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -4,8 +4,10 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
4
4
|
import { ImageProtocol, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { $env, getProjectDir, isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
|
+
import { AsyncJobManager } from "../async";
|
|
7
8
|
import { type BashResult, executeBash } from "../exec/bash-executor";
|
|
8
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
|
+
import { InternalUrlRouter } from "../internal-urls";
|
|
9
11
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
10
12
|
import type { Theme } from "../modes/theme/theme";
|
|
11
13
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
@@ -16,7 +18,6 @@ import { getSixelLineMask } from "../utils/sixel";
|
|
|
16
18
|
import type { ToolSession } from ".";
|
|
17
19
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
18
20
|
import { checkBashInterception } from "./bash-interceptor";
|
|
19
|
-
import { applyHeadTail } from "./bash-normalize";
|
|
20
21
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
21
22
|
import { formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
22
23
|
import { resolveToCwd } from "./path-utils";
|
|
@@ -50,8 +51,7 @@ const bashSchemaBase = Type.Object({
|
|
|
50
51
|
),
|
|
51
52
|
timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 300 })),
|
|
52
53
|
cwd: Type.Optional(Type.String({ description: "working directory", examples: ["src/", "/tmp"] })),
|
|
53
|
-
|
|
54
|
-
tail: Type.Optional(Type.Number({ description: "last n lines of output" })),
|
|
54
|
+
|
|
55
55
|
pty: Type.Optional(
|
|
56
56
|
Type.Boolean({
|
|
57
57
|
description: "run in pty mode",
|
|
@@ -75,8 +75,7 @@ export interface BashToolInput {
|
|
|
75
75
|
env?: Record<string, string>;
|
|
76
76
|
timeout?: number;
|
|
77
77
|
cwd?: string;
|
|
78
|
-
|
|
79
|
-
tail?: number;
|
|
78
|
+
|
|
80
79
|
async?: boolean;
|
|
81
80
|
pty?: boolean;
|
|
82
81
|
}
|
|
@@ -266,16 +265,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
266
265
|
});
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
#formatResultOutput(result: BashResult | BashInteractiveResult
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (headTailResult.applied) {
|
|
273
|
-
outputText = headTailResult.text;
|
|
274
|
-
}
|
|
275
|
-
if (!outputText) {
|
|
276
|
-
outputText = "(no output)";
|
|
277
|
-
}
|
|
278
|
-
return outputText;
|
|
268
|
+
#formatResultOutput(result: BashResult | BashInteractiveResult): string {
|
|
269
|
+
const outputText = normalizeResultOutput(result);
|
|
270
|
+
return outputText || "(no output)";
|
|
279
271
|
}
|
|
280
272
|
|
|
281
273
|
#buildResultText(result: BashResult | BashInteractiveResult, timeoutSec: number, outputText: string): string {
|
|
@@ -297,11 +289,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
297
289
|
#buildCompletedResult(
|
|
298
290
|
result: BashResult | BashInteractiveResult,
|
|
299
291
|
timeoutSec: number,
|
|
300
|
-
headLines?: number,
|
|
301
|
-
tailLines?: number,
|
|
302
292
|
options: { requestedTimeoutSec?: number; notices?: string[] } = {},
|
|
303
293
|
): AgentToolResult<BashToolDetails> {
|
|
304
|
-
const outputLines = [this.#formatResultOutput(result
|
|
294
|
+
const outputLines = [this.#formatResultOutput(result)];
|
|
305
295
|
const notices = options.notices?.filter(Boolean) ?? [];
|
|
306
296
|
if (notices.length > 0) outputLines.push("", ...notices);
|
|
307
297
|
const outputText = outputLines.join("\n");
|
|
@@ -338,7 +328,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
338
328
|
}
|
|
339
329
|
lines.push(`Background job ${jobId} started: ${label}`);
|
|
340
330
|
lines.push("Result will be delivered automatically when complete.");
|
|
341
|
-
lines.push(
|
|
331
|
+
lines.push(
|
|
332
|
+
`You can use \`job\` to poll until complete, but prefer to continue with another task in the meanwhile if it's not blocking.`,
|
|
333
|
+
);
|
|
342
334
|
return {
|
|
343
335
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
344
336
|
details,
|
|
@@ -356,13 +348,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
356
348
|
timeoutSec: number;
|
|
357
349
|
requestedTimeoutSec?: number;
|
|
358
350
|
timeoutClampNotice?: string;
|
|
359
|
-
|
|
360
|
-
tailLines?: number;
|
|
351
|
+
|
|
361
352
|
resolvedEnv?: Record<string, string>;
|
|
362
353
|
onUpdate?: AgentToolUpdateCallback<BashToolDetails>;
|
|
363
354
|
startBackgrounded: boolean;
|
|
364
355
|
}): ManagedBashJobHandle {
|
|
365
|
-
const manager =
|
|
356
|
+
const manager = AsyncJobManager.instance();
|
|
366
357
|
if (!manager) {
|
|
367
358
|
throw new ToolError("Background job manager unavailable for this session.");
|
|
368
359
|
}
|
|
@@ -394,16 +385,10 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
394
385
|
},
|
|
395
386
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
396
387
|
});
|
|
397
|
-
const finalResult = this.#buildCompletedResult(
|
|
398
|
-
|
|
399
|
-
options.
|
|
400
|
-
|
|
401
|
-
options.tailLines,
|
|
402
|
-
{
|
|
403
|
-
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
404
|
-
notices: [options.timeoutClampNotice].filter((notice): notice is string => Boolean(notice)),
|
|
405
|
-
},
|
|
406
|
-
);
|
|
388
|
+
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
389
|
+
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
390
|
+
notices: [options.timeoutClampNotice].filter((notice): notice is string => Boolean(notice)),
|
|
391
|
+
});
|
|
407
392
|
const finalText = this.#extractTextResult(finalResult);
|
|
408
393
|
latestText = finalText;
|
|
409
394
|
completion.resolve({ kind: "completed", result: finalResult });
|
|
@@ -418,6 +403,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
418
403
|
}
|
|
419
404
|
},
|
|
420
405
|
{
|
|
406
|
+
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
421
407
|
onProgress: async (text, details) => {
|
|
422
408
|
latestText = text;
|
|
423
409
|
await options.onUpdate?.({
|
|
@@ -481,8 +467,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
481
467
|
env: rawEnv,
|
|
482
468
|
timeout: rawTimeout = 300,
|
|
483
469
|
cwd,
|
|
484
|
-
|
|
485
|
-
tail,
|
|
470
|
+
|
|
486
471
|
async: asyncRequested = false,
|
|
487
472
|
pty = false,
|
|
488
473
|
}: BashToolInput,
|
|
@@ -505,10 +490,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
505
490
|
throw new ToolError("Async bash execution is disabled. Enable async.enabled to use async mode.");
|
|
506
491
|
}
|
|
507
492
|
|
|
508
|
-
// Only apply explicit head/tail params from tool input.
|
|
509
|
-
const headLines = head;
|
|
510
|
-
const tailLines = tail;
|
|
511
|
-
|
|
512
493
|
// Check both the original command and the cwd-normalized command so
|
|
513
494
|
// leading `cd ... &&` wrappers do not hide either shell-navigation rules
|
|
514
495
|
// or the dedicated-tool command that follows the directory change.
|
|
@@ -525,7 +506,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
525
506
|
|
|
526
507
|
const internalUrlOptions: InternalUrlExpansionOptions = {
|
|
527
508
|
skills: this.session.skills ?? [],
|
|
528
|
-
internalRouter:
|
|
509
|
+
internalRouter: InternalUrlRouter.instance(),
|
|
529
510
|
localOptions: {
|
|
530
511
|
getArtifactsDir: this.session.getArtifactsDir,
|
|
531
512
|
getSessionId: this.session.getSessionId,
|
|
@@ -573,7 +554,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
573
554
|
const timeoutClampNotice = formatTimeoutClampNotice(requestedTimeoutSec, timeoutSec);
|
|
574
555
|
|
|
575
556
|
if (asyncRequested) {
|
|
576
|
-
if (!
|
|
557
|
+
if (!AsyncJobManager.instance()) {
|
|
577
558
|
throw new ToolError("Async job manager unavailable for this session.");
|
|
578
559
|
}
|
|
579
560
|
const job = this.#startManagedBashJob({
|
|
@@ -583,8 +564,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
583
564
|
timeoutSec,
|
|
584
565
|
requestedTimeoutSec,
|
|
585
566
|
timeoutClampNotice,
|
|
586
|
-
|
|
587
|
-
tailLines,
|
|
567
|
+
|
|
588
568
|
resolvedEnv,
|
|
589
569
|
onUpdate,
|
|
590
570
|
startBackgrounded: true,
|
|
@@ -595,7 +575,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
595
575
|
});
|
|
596
576
|
}
|
|
597
577
|
|
|
598
|
-
|
|
578
|
+
const autoBgManager = AsyncJobManager.instance();
|
|
579
|
+
if (this.#autoBackgroundEnabled && !pty && autoBgManager) {
|
|
599
580
|
const autoBackgroundWaitMs = this.#resolveAutoBackgroundWaitMs(timeoutMs);
|
|
600
581
|
const startBackgrounded = autoBackgroundWaitMs === 0;
|
|
601
582
|
const job = this.#startManagedBashJob({
|
|
@@ -605,8 +586,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
605
586
|
timeoutSec,
|
|
606
587
|
requestedTimeoutSec,
|
|
607
588
|
timeoutClampNotice,
|
|
608
|
-
|
|
609
|
-
tailLines,
|
|
589
|
+
|
|
610
590
|
resolvedEnv,
|
|
611
591
|
onUpdate,
|
|
612
592
|
startBackgrounded,
|
|
@@ -619,16 +599,16 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
619
599
|
}
|
|
620
600
|
const waitResult = await this.#waitForManagedBashJob(job, autoBackgroundWaitMs, signal);
|
|
621
601
|
if (waitResult.kind === "completed") {
|
|
622
|
-
|
|
602
|
+
autoBgManager.acknowledgeDeliveries([job.jobId]);
|
|
623
603
|
return waitResult.result;
|
|
624
604
|
}
|
|
625
605
|
if (waitResult.kind === "failed") {
|
|
626
|
-
|
|
606
|
+
autoBgManager.acknowledgeDeliveries([job.jobId]);
|
|
627
607
|
throw waitResult.error;
|
|
628
608
|
}
|
|
629
609
|
if (waitResult.kind === "aborted") {
|
|
630
|
-
|
|
631
|
-
|
|
610
|
+
autoBgManager.cancel(job.jobId);
|
|
611
|
+
autoBgManager.acknowledgeDeliveries([job.jobId]);
|
|
632
612
|
throw new ToolAbortError(job.getLatestText() || "Command aborted");
|
|
633
613
|
}
|
|
634
614
|
job.setBackgrounded(true);
|
|
@@ -675,7 +655,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
675
655
|
if (isInteractiveResult(result) && result.timedOut) {
|
|
676
656
|
throw new ToolError(normalizeResultOutput(result) || `Command timed out after ${timeoutSec} seconds`);
|
|
677
657
|
}
|
|
678
|
-
return this.#buildCompletedResult(result, timeoutSec,
|
|
658
|
+
return this.#buildCompletedResult(result, timeoutSec, {
|
|
679
659
|
requestedTimeoutSec,
|
|
680
660
|
notices: [timeoutClampNotice].filter((notice): notice is string => Boolean(notice)),
|
|
681
661
|
});
|