@oh-my-pi/pi-coding-agent 14.9.1 → 14.9.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 +60 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +3 -3
- package/src/config/prompt-templates.ts +0 -5
- package/src/config/settings-schema.ts +38 -0
- package/src/eval/eval.lark +10 -31
- package/src/eval/index.ts +1 -0
- package/src/eval/parse.ts +156 -255
- package/src/eval/sniff.ts +28 -0
- package/src/export/html/template.css +38 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +209 -15
- package/src/extensibility/extensions/runner.ts +173 -177
- package/src/hashline/apply.ts +8 -24
- package/src/hashline/constants.ts +20 -0
- package/src/hashline/execute.ts +0 -1
- package/src/hashline/grammar.lark +16 -27
- package/src/hashline/hash.ts +4 -34
- package/src/hashline/input.ts +16 -2
- package/src/hashline/parser.ts +12 -40
- package/src/hashline/types.ts +1 -2
- package/src/internal-urls/agent-protocol.ts +1 -0
- package/src/internal-urls/artifact-protocol.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +2 -1
- package/src/internal-urls/jobs-protocol.ts +1 -0
- package/src/internal-urls/local-protocol.ts +1 -0
- package/src/internal-urls/mcp-protocol.ts +1 -0
- package/src/internal-urls/memory-protocol.ts +1 -0
- package/src/internal-urls/pi-protocol.ts +1 -0
- package/src/internal-urls/router.ts +2 -1
- package/src/internal-urls/rule-protocol.ts +1 -0
- package/src/internal-urls/skill-protocol.ts +1 -0
- package/src/internal-urls/types.ts +18 -2
- package/src/mcp/transports/http.ts +49 -47
- package/src/prompts/system/custom-system-prompt.md +0 -2
- package/src/prompts/system/now-prompt.md +7 -0
- package/src/prompts/system/project-prompt.md +2 -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 +154 -233
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/eval.md +26 -13
- package/src/prompts/tools/hashline.md +1 -4
- package/src/sdk.ts +12 -22
- package/src/session/agent-session.ts +49 -17
- package/src/system-prompt.ts +38 -104
- package/src/task/executor.ts +15 -9
- package/src/task/index.ts +38 -33
- package/src/task/render.ts +4 -2
- package/src/tools/bash.ts +15 -41
- package/src/tools/eval.ts +13 -36
- package/src/tools/index.ts +0 -3
- package/src/tools/path-utils.ts +21 -1
- package/src/tools/read.ts +71 -49
- package/src/tools/search.ts +13 -1
- package/src/utils/file-display-mode.ts +11 -5
- package/src/workspace-tree.ts +210 -410
- package/src/task/template.ts +0 -47
- package/src/tools/bash-normalize.ts +0 -107
package/src/task/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type { ToolSession } from "..";
|
|
|
23
23
|
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
24
24
|
import type { Theme } from "../modes/theme/theme";
|
|
25
25
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
26
|
+
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
26
27
|
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
|
27
28
|
import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
|
|
28
29
|
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
@@ -38,7 +39,6 @@ import { AgentOutputManager } from "./output-manager";
|
|
|
38
39
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
39
40
|
import { renderResult, renderCall as renderTaskCall } from "./render";
|
|
40
41
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
41
|
-
import { renderTemplate } from "./template";
|
|
42
42
|
import {
|
|
43
43
|
type AgentDefinition,
|
|
44
44
|
type AgentProgress,
|
|
@@ -65,6 +65,12 @@ import {
|
|
|
65
65
|
type WorktreeBaseline,
|
|
66
66
|
} from "./worktree";
|
|
67
67
|
|
|
68
|
+
function renderSubagentUserPrompt(assignment: string, simpleMode: TaskSimpleMode): string {
|
|
69
|
+
return prompt.render(subagentUserPromptTemplate, {
|
|
70
|
+
assignment: assignment.trim(),
|
|
71
|
+
independentMode: simpleMode === "independent",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
68
74
|
function createUsageTotals(): Usage {
|
|
69
75
|
return {
|
|
70
76
|
input: 0,
|
|
@@ -282,21 +288,19 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
282
288
|
const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id));
|
|
283
289
|
const fallbackAgentSource =
|
|
284
290
|
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
291
|
const progressByTaskId = new Map<string, AgentProgress>();
|
|
289
|
-
for (let index = 0; index <
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
+
for (let index = 0; index < taskItems.length; index++) {
|
|
293
|
+
const taskItem = taskItems[index];
|
|
294
|
+
const assignment = taskItem.assignment.trim();
|
|
295
|
+
progressByTaskId.set(taskItem.id, {
|
|
292
296
|
index,
|
|
293
|
-
id:
|
|
297
|
+
id: taskItem.id,
|
|
294
298
|
agent: params.agent,
|
|
295
299
|
agentSource: fallbackAgentSource,
|
|
296
300
|
status: "pending",
|
|
297
|
-
task:
|
|
298
|
-
assignment
|
|
299
|
-
description:
|
|
301
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
302
|
+
assignment,
|
|
303
|
+
description: taskItem.description,
|
|
300
304
|
recentTools: [],
|
|
301
305
|
recentOutput: [],
|
|
302
306
|
toolCount: 0,
|
|
@@ -506,7 +510,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
506
510
|
const { agent: agentName, context, schema: outputSchema } = params;
|
|
507
511
|
const simpleMode = this.#getTaskSimpleMode();
|
|
508
512
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
509
|
-
const sharedContext = contextEnabled ? context : undefined;
|
|
513
|
+
const sharedContext = contextEnabled ? context?.trim() : undefined;
|
|
510
514
|
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
511
515
|
const isolationRequested = "isolated" in params ? params.isolated === true : false;
|
|
512
516
|
const isIsolated = isolationMode !== "none" && isolationRequested;
|
|
@@ -802,8 +806,6 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
802
806
|
}
|
|
803
807
|
const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: uniqueIds[i] }));
|
|
804
808
|
|
|
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
809
|
const availableSkills = [...(this.session.skills ?? [])];
|
|
808
810
|
const contextFiles = this.session.contextFiles?.filter(
|
|
809
811
|
file => path.basename(file.path).toLowerCase() !== "agents.md",
|
|
@@ -811,34 +813,36 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
811
813
|
const promptTemplates = this.session.promptTemplates;
|
|
812
814
|
|
|
813
815
|
// Initialize progress for all tasks
|
|
814
|
-
for (let i = 0; i <
|
|
815
|
-
const
|
|
816
|
+
for (let i = 0; i < tasksWithUniqueIds.length; i++) {
|
|
817
|
+
const taskItem = tasksWithUniqueIds[i];
|
|
818
|
+
const assignment = taskItem.assignment.trim();
|
|
816
819
|
progressMap.set(i, {
|
|
817
820
|
index: i,
|
|
818
|
-
id:
|
|
821
|
+
id: taskItem.id,
|
|
819
822
|
agent: agentName,
|
|
820
823
|
agentSource: agent.source,
|
|
821
824
|
status: "pending",
|
|
822
|
-
task:
|
|
823
|
-
assignment
|
|
825
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
826
|
+
assignment,
|
|
824
827
|
recentTools: [],
|
|
825
828
|
recentOutput: [],
|
|
826
829
|
toolCount: 0,
|
|
827
830
|
tokens: 0,
|
|
828
831
|
durationMs: 0,
|
|
829
832
|
modelOverride,
|
|
830
|
-
description:
|
|
833
|
+
description: taskItem.description,
|
|
831
834
|
});
|
|
832
835
|
}
|
|
833
836
|
emitProgress();
|
|
834
837
|
|
|
835
|
-
const runTask = async (task: (typeof
|
|
838
|
+
const runTask = async (task: (typeof tasksWithUniqueIds)[number], index: number) => {
|
|
836
839
|
if (!isIsolated) {
|
|
837
840
|
return runSubprocess({
|
|
838
841
|
cwd: this.session.cwd,
|
|
839
842
|
agent,
|
|
840
|
-
task: task.
|
|
841
|
-
assignment: task.assignment,
|
|
843
|
+
task: renderSubagentUserPrompt(task.assignment, simpleMode),
|
|
844
|
+
assignment: task.assignment.trim(),
|
|
845
|
+
context: sharedContext,
|
|
842
846
|
description: task.description,
|
|
843
847
|
index,
|
|
844
848
|
id: task.id,
|
|
@@ -866,7 +870,6 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
866
870
|
mcpManager: this.session.mcpManager,
|
|
867
871
|
contextFiles,
|
|
868
872
|
skills: availableSkills,
|
|
869
|
-
agentsMdSearch: this.session.agentsMdSearch,
|
|
870
873
|
workspaceTree: this.session.workspaceTree,
|
|
871
874
|
promptTemplates,
|
|
872
875
|
localProtocolOptions,
|
|
@@ -895,8 +898,9 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
895
898
|
cwd: this.session.cwd,
|
|
896
899
|
worktree: isolationDir,
|
|
897
900
|
agent,
|
|
898
|
-
task: task.
|
|
899
|
-
assignment: task.assignment,
|
|
901
|
+
task: renderSubagentUserPrompt(task.assignment, simpleMode),
|
|
902
|
+
assignment: task.assignment.trim(),
|
|
903
|
+
context: sharedContext,
|
|
900
904
|
description: task.description,
|
|
901
905
|
index,
|
|
902
906
|
id: task.id,
|
|
@@ -924,7 +928,6 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
924
928
|
mcpManager: this.session.mcpManager,
|
|
925
929
|
contextFiles,
|
|
926
930
|
skills: availableSkills,
|
|
927
|
-
agentsMdSearch: this.session.agentsMdSearch,
|
|
928
931
|
workspaceTree: this.session.workspaceTree,
|
|
929
932
|
promptTemplates,
|
|
930
933
|
localProtocolOptions,
|
|
@@ -981,13 +984,14 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
981
984
|
return result;
|
|
982
985
|
} catch (err) {
|
|
983
986
|
const message = err instanceof Error ? err.message : String(err);
|
|
987
|
+
const assignment = task.assignment.trim();
|
|
984
988
|
return {
|
|
985
989
|
index,
|
|
986
990
|
id: task.id,
|
|
987
991
|
agent: agent.name,
|
|
988
992
|
agentSource: agent.source,
|
|
989
|
-
task:
|
|
990
|
-
assignment
|
|
993
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
994
|
+
assignment,
|
|
991
995
|
description: task.description,
|
|
992
996
|
exitCode: 1,
|
|
993
997
|
output: "",
|
|
@@ -1013,7 +1017,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1013
1017
|
|
|
1014
1018
|
// Execute in parallel with concurrency limit
|
|
1015
1019
|
const { results: partialResults, aborted } = await mapWithConcurrencyLimit(
|
|
1016
|
-
|
|
1020
|
+
tasksWithUniqueIds,
|
|
1017
1021
|
maxConcurrency,
|
|
1018
1022
|
runTask,
|
|
1019
1023
|
signal,
|
|
@@ -1024,14 +1028,15 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1024
1028
|
if (result !== undefined) {
|
|
1025
1029
|
return result;
|
|
1026
1030
|
}
|
|
1027
|
-
const task =
|
|
1031
|
+
const task = tasksWithUniqueIds[index];
|
|
1032
|
+
const assignment = task.assignment.trim();
|
|
1028
1033
|
return {
|
|
1029
1034
|
index,
|
|
1030
1035
|
id: task.id,
|
|
1031
1036
|
agent: agentName,
|
|
1032
1037
|
agentSource: agent.source,
|
|
1033
|
-
task:
|
|
1034
|
-
assignment
|
|
1038
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
1039
|
+
assignment,
|
|
1035
1040
|
description: task.description,
|
|
1036
1041
|
exitCode: 1,
|
|
1037
1042
|
output: "",
|
package/src/task/render.ts
CHANGED
|
@@ -470,14 +470,16 @@ export function renderCall(args: TaskParams, _options: RenderResultOptions, them
|
|
|
470
470
|
lines.push(` ${vertical} ${content}`);
|
|
471
471
|
}
|
|
472
472
|
const taskPrefix = showIsolated ? branch : last;
|
|
473
|
-
lines.push(
|
|
473
|
+
lines.push(
|
|
474
|
+
` ${taskPrefix} ${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks?.length ?? 0} agents`)}`,
|
|
475
|
+
);
|
|
474
476
|
if (showIsolated) {
|
|
475
477
|
lines.push(` ${last} ${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
|
|
476
478
|
}
|
|
477
479
|
return new Text(lines.join("\n"), 0, 0);
|
|
478
480
|
}
|
|
479
481
|
|
|
480
|
-
lines.push(`${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks
|
|
482
|
+
lines.push(`${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks?.length ?? 0} agents`)}`);
|
|
481
483
|
if (showIsolated) {
|
|
482
484
|
lines.push(`${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
|
|
483
485
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { getSixelLineMask } from "../utils/sixel";
|
|
|
16
16
|
import type { ToolSession } from ".";
|
|
17
17
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
18
18
|
import { checkBashInterception } from "./bash-interceptor";
|
|
19
|
-
import { applyHeadTail } from "./bash-normalize";
|
|
20
19
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
21
20
|
import { formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
22
21
|
import { resolveToCwd } from "./path-utils";
|
|
@@ -50,8 +49,7 @@ const bashSchemaBase = Type.Object({
|
|
|
50
49
|
),
|
|
51
50
|
timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 300 })),
|
|
52
51
|
cwd: Type.Optional(Type.String({ description: "working directory", examples: ["src/", "/tmp"] })),
|
|
53
|
-
|
|
54
|
-
tail: Type.Optional(Type.Number({ description: "last n lines of output" })),
|
|
52
|
+
|
|
55
53
|
pty: Type.Optional(
|
|
56
54
|
Type.Boolean({
|
|
57
55
|
description: "run in pty mode",
|
|
@@ -75,8 +73,7 @@ export interface BashToolInput {
|
|
|
75
73
|
env?: Record<string, string>;
|
|
76
74
|
timeout?: number;
|
|
77
75
|
cwd?: string;
|
|
78
|
-
|
|
79
|
-
tail?: number;
|
|
76
|
+
|
|
80
77
|
async?: boolean;
|
|
81
78
|
pty?: boolean;
|
|
82
79
|
}
|
|
@@ -266,16 +263,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
266
263
|
});
|
|
267
264
|
}
|
|
268
265
|
|
|
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;
|
|
266
|
+
#formatResultOutput(result: BashResult | BashInteractiveResult): string {
|
|
267
|
+
const outputText = normalizeResultOutput(result);
|
|
268
|
+
return outputText || "(no output)";
|
|
279
269
|
}
|
|
280
270
|
|
|
281
271
|
#buildResultText(result: BashResult | BashInteractiveResult, timeoutSec: number, outputText: string): string {
|
|
@@ -297,11 +287,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
297
287
|
#buildCompletedResult(
|
|
298
288
|
result: BashResult | BashInteractiveResult,
|
|
299
289
|
timeoutSec: number,
|
|
300
|
-
headLines?: number,
|
|
301
|
-
tailLines?: number,
|
|
302
290
|
options: { requestedTimeoutSec?: number; notices?: string[] } = {},
|
|
303
291
|
): AgentToolResult<BashToolDetails> {
|
|
304
|
-
const outputLines = [this.#formatResultOutput(result
|
|
292
|
+
const outputLines = [this.#formatResultOutput(result)];
|
|
305
293
|
const notices = options.notices?.filter(Boolean) ?? [];
|
|
306
294
|
if (notices.length > 0) outputLines.push("", ...notices);
|
|
307
295
|
const outputText = outputLines.join("\n");
|
|
@@ -356,8 +344,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
356
344
|
timeoutSec: number;
|
|
357
345
|
requestedTimeoutSec?: number;
|
|
358
346
|
timeoutClampNotice?: string;
|
|
359
|
-
|
|
360
|
-
tailLines?: number;
|
|
347
|
+
|
|
361
348
|
resolvedEnv?: Record<string, string>;
|
|
362
349
|
onUpdate?: AgentToolUpdateCallback<BashToolDetails>;
|
|
363
350
|
startBackgrounded: boolean;
|
|
@@ -394,16 +381,10 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
394
381
|
},
|
|
395
382
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
396
383
|
});
|
|
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
|
-
);
|
|
384
|
+
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
385
|
+
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
386
|
+
notices: [options.timeoutClampNotice].filter((notice): notice is string => Boolean(notice)),
|
|
387
|
+
});
|
|
407
388
|
const finalText = this.#extractTextResult(finalResult);
|
|
408
389
|
latestText = finalText;
|
|
409
390
|
completion.resolve({ kind: "completed", result: finalResult });
|
|
@@ -481,8 +462,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
481
462
|
env: rawEnv,
|
|
482
463
|
timeout: rawTimeout = 300,
|
|
483
464
|
cwd,
|
|
484
|
-
|
|
485
|
-
tail,
|
|
465
|
+
|
|
486
466
|
async: asyncRequested = false,
|
|
487
467
|
pty = false,
|
|
488
468
|
}: BashToolInput,
|
|
@@ -505,10 +485,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
505
485
|
throw new ToolError("Async bash execution is disabled. Enable async.enabled to use async mode.");
|
|
506
486
|
}
|
|
507
487
|
|
|
508
|
-
// Only apply explicit head/tail params from tool input.
|
|
509
|
-
const headLines = head;
|
|
510
|
-
const tailLines = tail;
|
|
511
|
-
|
|
512
488
|
// Check both the original command and the cwd-normalized command so
|
|
513
489
|
// leading `cd ... &&` wrappers do not hide either shell-navigation rules
|
|
514
490
|
// or the dedicated-tool command that follows the directory change.
|
|
@@ -583,8 +559,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
583
559
|
timeoutSec,
|
|
584
560
|
requestedTimeoutSec,
|
|
585
561
|
timeoutClampNotice,
|
|
586
|
-
|
|
587
|
-
tailLines,
|
|
562
|
+
|
|
588
563
|
resolvedEnv,
|
|
589
564
|
onUpdate,
|
|
590
565
|
startBackgrounded: true,
|
|
@@ -605,8 +580,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
605
580
|
timeoutSec,
|
|
606
581
|
requestedTimeoutSec,
|
|
607
582
|
timeoutClampNotice,
|
|
608
|
-
|
|
609
|
-
tailLines,
|
|
583
|
+
|
|
610
584
|
resolvedEnv,
|
|
611
585
|
onUpdate,
|
|
612
586
|
startBackgrounded,
|
|
@@ -675,7 +649,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
675
649
|
if (isInteractiveResult(result) && result.timedOut) {
|
|
676
650
|
throw new ToolError(normalizeResultOutput(result) || `Command timed out after ${timeoutSec} seconds`);
|
|
677
651
|
}
|
|
678
|
-
return this.#buildCompletedResult(result, timeoutSec,
|
|
652
|
+
return this.#buildCompletedResult(result, timeoutSec, {
|
|
679
653
|
requestedTimeoutSec,
|
|
680
654
|
notices: [timeoutClampNotice].filter((notice): notice is string => Boolean(notice)),
|
|
681
655
|
});
|
package/src/tools/eval.ts
CHANGED
|
@@ -4,10 +4,10 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
4
4
|
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { type Static, Type } from "@sinclair/typebox";
|
|
7
|
-
import { jsBackend, parseEvalInput, pythonBackend } from "../eval";
|
|
7
|
+
import { jsBackend, parseEvalInput, pythonBackend, sniffEvalLanguage } from "../eval";
|
|
8
8
|
import type { ExecutorBackend } from "../eval/backend";
|
|
9
9
|
import evalGrammar from "../eval/eval.lark" with { type: "text" };
|
|
10
|
-
import type
|
|
10
|
+
import { ABORT_WARNING, type ParsedEvalCell } from "../eval/parse";
|
|
11
11
|
import type { EvalCellResult, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
@@ -26,7 +26,7 @@ export const EVAL_DEFAULT_PREVIEW_LINES = 10;
|
|
|
26
26
|
|
|
27
27
|
export const evalSchema = Type.Object({
|
|
28
28
|
input: Type.String({
|
|
29
|
-
description: "eval input as a sequence of
|
|
29
|
+
description: "eval input as a sequence of `*** Begin <LANG>` cell headers followed by code",
|
|
30
30
|
}),
|
|
31
31
|
});
|
|
32
32
|
export type EvalToolParams = Static<typeof evalSchema>;
|
|
@@ -131,33 +131,6 @@ function timeoutSecondsFromMs(timeoutMs: number): number {
|
|
|
131
131
|
return clampTimeout("eval", timeoutMs / 1000);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
/**
|
|
135
|
-
* Best-effort language sniff for cells with no explicit `language`.
|
|
136
|
-
*
|
|
137
|
-
* Order:
|
|
138
|
-
* 1. Shebang on first line (`#!/usr/bin/env python`, `#!/usr/bin/env node`, etc.)
|
|
139
|
-
* 2. Strong syntactic markers unique to one language. We bias false negatives over
|
|
140
|
-
* false positives — anything ambiguous returns `undefined` and the caller falls
|
|
141
|
-
* back to the default-backend rules.
|
|
142
|
-
*/
|
|
143
|
-
function sniffLanguage(code: string): EvalLanguage | undefined {
|
|
144
|
-
const stripped = code.replace(/^\s+/, "");
|
|
145
|
-
if (stripped.startsWith("#!")) {
|
|
146
|
-
const firstLine = stripped.split("\n", 1)[0]!.toLowerCase();
|
|
147
|
-
if (/(\bpython\d?\b|\bipython\b)/.test(firstLine)) return "python";
|
|
148
|
-
if (/(\bnode\b|\bbun\b|\bdeno\b|\bjavascript\b|\bjs\b)/.test(firstLine)) return "js";
|
|
149
|
-
}
|
|
150
|
-
const jsMarkers =
|
|
151
|
-
/(^|\n)\s*(const|let|var|async\s+function|function\s*\*?\s*[\w$]*\s*\(|import\s+[^\n]+\sfrom\s|export\s+(default|const|let|function|class|async)|require\s*\(|console\.\w+\s*\(|=>|;\s*$)/m;
|
|
152
|
-
const pyMarkers =
|
|
153
|
-
/(^|\n)\s*(def\s+\w+\s*\(|from\s+[\w.]+\s+import|import\s+\w+(\s+as\s+\w+)?\s*$|class\s+\w+\s*[(:]|print\s*\(|elif\s+[^\n]*:|with\s+[^\n]+:\s*$|@[\w.]+\s*$)/m;
|
|
154
|
-
const hasJs = jsMarkers.test(code);
|
|
155
|
-
const hasPy = pyMarkers.test(code);
|
|
156
|
-
if (hasJs && !hasPy) return "js";
|
|
157
|
-
if (hasPy && !hasJs) return "python";
|
|
158
|
-
return undefined;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
134
|
async function resolveBackend(
|
|
162
135
|
session: ToolSession,
|
|
163
136
|
requested: EvalLanguage | undefined,
|
|
@@ -180,7 +153,7 @@ async function resolveBackend(
|
|
|
180
153
|
return { backend: jsBackend, fallback: false };
|
|
181
154
|
}
|
|
182
155
|
// Auto-detect.
|
|
183
|
-
const sniffed =
|
|
156
|
+
const sniffed = sniffEvalLanguage(code);
|
|
184
157
|
if (sniffed === "python" && allowPy && (await pythonBackend.isAvailable(session))) {
|
|
185
158
|
return { backend: pythonBackend, fallback: false };
|
|
186
159
|
}
|
|
@@ -446,10 +419,11 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
446
419
|
pushUpdate();
|
|
447
420
|
const errorMsg = result.output || "Command aborted";
|
|
448
421
|
const combinedOutput = cellOutputs.join("\n\n");
|
|
422
|
+
const abortSuffix = parsedInput.aborted ? `\n\n${ABORT_WARNING}` : "";
|
|
449
423
|
const outputText =
|
|
450
|
-
cells.length > 1
|
|
424
|
+
(cells.length > 1
|
|
451
425
|
? `${combinedOutput}\n\nCell ${i + 1} aborted: ${errorMsg}`
|
|
452
|
-
: combinedOutput || errorMsg;
|
|
426
|
+
: combinedOutput || errorMsg) + abortSuffix;
|
|
453
427
|
|
|
454
428
|
const summaryForMeta = await summarizeFinal(combinedOutput, finalizeOutput);
|
|
455
429
|
const details: EvalToolDetails = {
|
|
@@ -473,12 +447,13 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
473
447
|
cellResult.status = "error";
|
|
474
448
|
pushUpdate();
|
|
475
449
|
const combinedOutput = cellOutputs.join("\n\n");
|
|
450
|
+
const abortSuffix = parsedInput.aborted ? `\n\n${ABORT_WARNING}` : "";
|
|
476
451
|
const outputText =
|
|
477
|
-
cells.length > 1
|
|
452
|
+
(cells.length > 1
|
|
478
453
|
? `${combinedOutput}\n\nCell ${i + 1} failed (exit code ${result.exitCode}). Earlier cells succeeded—their state persists. Fix only cell ${i + 1}.`
|
|
479
454
|
: combinedOutput
|
|
480
455
|
? `${combinedOutput}\n\nCommand exited with code ${result.exitCode}`
|
|
481
|
-
: `Command exited with code ${result.exitCode}
|
|
456
|
+
: `Command exited with code ${result.exitCode}`) + abortSuffix;
|
|
482
457
|
|
|
483
458
|
const summaryForMeta = await summarizeFinal(combinedOutput, finalizeOutput);
|
|
484
459
|
const details: EvalToolDetails = {
|
|
@@ -503,8 +478,10 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
503
478
|
}
|
|
504
479
|
|
|
505
480
|
const combinedOutput = cellOutputs.join("\n\n");
|
|
481
|
+
const abortSuffix = parsedInput.aborted ? `\n\n${ABORT_WARNING}` : "";
|
|
506
482
|
const outputText =
|
|
507
|
-
combinedOutput || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)")
|
|
483
|
+
(combinedOutput || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)")) +
|
|
484
|
+
abortSuffix;
|
|
508
485
|
const summaryForMeta = await summarizeFinal(combinedOutput, finalizeOutput);
|
|
509
486
|
|
|
510
487
|
const details: EvalToolDetails = {
|
package/src/tools/index.ts
CHANGED
|
@@ -14,7 +14,6 @@ import type { PlanModeState } from "../plan-mode/state";
|
|
|
14
14
|
import type { AgentRegistry } from "../registry/agent-registry";
|
|
15
15
|
import type { CustomMessage } from "../session/messages";
|
|
16
16
|
import type { ToolChoiceQueue } from "../session/tool-choice-queue";
|
|
17
|
-
import type { AgentsMdSearch } from "../system-prompt";
|
|
18
17
|
import { TaskTool } from "../task";
|
|
19
18
|
import type { AgentOutputManager } from "../task/output-manager";
|
|
20
19
|
import type { DiscoverableTool, DiscoverableToolSearchIndex } from "../tool-discovery/tool-index";
|
|
@@ -122,8 +121,6 @@ export interface ToolSession {
|
|
|
122
121
|
skipPythonPreflight?: boolean;
|
|
123
122
|
/** Pre-loaded context files (AGENTS.md, etc) */
|
|
124
123
|
contextFiles?: ContextFileEntry[];
|
|
125
|
-
/** Pre-loaded AGENTS.md search (forwarded to subagents to skip re-scanning) */
|
|
126
|
-
agentsMdSearch?: AgentsMdSearch;
|
|
127
124
|
/** Pre-loaded workspace tree (forwarded to subagents to skip re-scanning) */
|
|
128
125
|
workspaceTree?: WorkspaceTree;
|
|
129
126
|
/** Pre-loaded skills */
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
|
6
6
|
|
|
7
7
|
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
8
8
|
const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+)?|raw)$/i;
|
|
9
|
+
const FILE_LINE_RANGE_ONLY_RE = /^L?\d+(?:[-+]L?\d+)?$/i;
|
|
10
|
+
const FILE_RAW_ONLY_RE = /^raw$/i;
|
|
9
11
|
const NARROW_NO_BREAK_SPACE = "\u202F";
|
|
10
12
|
const TOP_LEVEL_INTERNAL_URL_PREFIXES = [
|
|
11
13
|
"agent://",
|
|
@@ -110,7 +112,25 @@ export function splitPathAndSel(rawPath: string): { path: string; sel?: string }
|
|
|
110
112
|
const candidate = rawPath.slice(colon + 1);
|
|
111
113
|
if (!FILE_LINE_RANGE_RE.test(candidate)) return { path: rawPath };
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
let basePath = rawPath.slice(0, colon);
|
|
116
|
+
let sel = candidate;
|
|
117
|
+
|
|
118
|
+
// Allow a compound trailing selector: `path:1-50:raw` or `path:raw:1-50`.
|
|
119
|
+
// The two chunks must be one line-range plus one `raw`, in either order.
|
|
120
|
+
const innerColon = basePath.lastIndexOf(":");
|
|
121
|
+
if (innerColon > 0) {
|
|
122
|
+
const innerCandidate = basePath.slice(innerColon + 1);
|
|
123
|
+
const innerIsRaw = FILE_RAW_ONLY_RE.test(innerCandidate);
|
|
124
|
+
const outerIsRaw = FILE_RAW_ONLY_RE.test(candidate);
|
|
125
|
+
const innerIsRange = FILE_LINE_RANGE_ONLY_RE.test(innerCandidate);
|
|
126
|
+
const outerIsRange = FILE_LINE_RANGE_ONLY_RE.test(candidate);
|
|
127
|
+
if ((innerIsRaw && outerIsRange) || (innerIsRange && outerIsRaw)) {
|
|
128
|
+
sel = `${innerCandidate}:${candidate}`;
|
|
129
|
+
basePath = basePath.slice(0, innerColon);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { path: basePath, sel };
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
function assertNotInternalUrl(expanded: string, original: string): void {
|