@oh-my-pi/pi-coding-agent 14.9.2 → 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 +45 -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/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 +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/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/session/agent-session.ts +49 -17
- package/src/system-prompt.ts +8 -9
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +38 -31
- package/src/tools/bash.ts +15 -41
- package/src/tools/eval.ts +13 -36
- package/src/tools/path-utils.ts +21 -1
- package/src/tools/read.ts +69 -27
- package/src/tools/search.ts +13 -1
- package/src/utils/file-display-mode.ts +11 -5
- package/src/task/template.ts +0 -47
- package/src/tools/bash-normalize.ts +0 -107
|
@@ -570,6 +570,7 @@ export class AgentSession {
|
|
|
570
570
|
#agentId: string | undefined;
|
|
571
571
|
#agentRegistry: AgentRegistry | undefined;
|
|
572
572
|
#providerSessionId: string | undefined;
|
|
573
|
+
#isDisposed = false;
|
|
573
574
|
// Extension system
|
|
574
575
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
575
576
|
#turnIndex = 0;
|
|
@@ -646,23 +647,32 @@ export class AgentSession {
|
|
|
646
647
|
#hindsightSessionState: HindsightSessionState | undefined = undefined;
|
|
647
648
|
readonly rawSseDebugBuffer: RawSseDebugBuffer;
|
|
648
649
|
|
|
649
|
-
#
|
|
650
|
-
if (process.platform !== "darwin")
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
#acquirePowerAssertion(): void {
|
|
651
|
+
if (process.platform !== "darwin") return;
|
|
652
|
+
if (this.#powerAssertion) return;
|
|
653
|
+
const idle = this.settings.get("power.preventIdleSleep");
|
|
654
|
+
const system = this.settings.get("power.preventSystemSleep");
|
|
655
|
+
const user = this.settings.get("power.declareUserActive");
|
|
656
|
+
const display = this.settings.get("power.preventDisplaySleep");
|
|
657
|
+
// All four off → user opted out; do nothing.
|
|
658
|
+
if (!idle && !system && !user && !display) return;
|
|
653
659
|
try {
|
|
654
|
-
this.#powerAssertion = MacOSPowerAssertion.start({
|
|
660
|
+
this.#powerAssertion = MacOSPowerAssertion.start({
|
|
661
|
+
reason: "Oh My Pi agent session",
|
|
662
|
+
idle,
|
|
663
|
+
system,
|
|
664
|
+
user,
|
|
665
|
+
display,
|
|
666
|
+
});
|
|
655
667
|
} catch (error) {
|
|
656
668
|
logger.warn("Failed to acquire macOS power assertion", { error: String(error) });
|
|
657
669
|
}
|
|
658
670
|
}
|
|
659
671
|
|
|
660
|
-
#
|
|
672
|
+
#releasePowerAssertion(): void {
|
|
661
673
|
const assertion = this.#powerAssertion;
|
|
662
674
|
this.#powerAssertion = undefined;
|
|
663
|
-
if (!assertion)
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
675
|
+
if (!assertion) return;
|
|
666
676
|
try {
|
|
667
677
|
assertion.stop();
|
|
668
678
|
} catch (error) {
|
|
@@ -670,11 +680,30 @@ export class AgentSession {
|
|
|
670
680
|
}
|
|
671
681
|
}
|
|
672
682
|
|
|
683
|
+
#beginInFlight(): void {
|
|
684
|
+
this.#promptInFlightCount++;
|
|
685
|
+
if (this.#promptInFlightCount === 1) {
|
|
686
|
+
this.#acquirePowerAssertion();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
#endInFlight(): void {
|
|
691
|
+
this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
|
|
692
|
+
if (this.#promptInFlightCount === 0) {
|
|
693
|
+
this.#releasePowerAssertion();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
#resetInFlight(): void {
|
|
698
|
+
this.#promptInFlightCount = 0;
|
|
699
|
+
this.#releasePowerAssertion();
|
|
700
|
+
}
|
|
701
|
+
|
|
673
702
|
constructor(config: AgentSessionConfig) {
|
|
674
703
|
this.agent = config.agent;
|
|
675
704
|
this.sessionManager = config.sessionManager;
|
|
676
705
|
this.settings = config.settings;
|
|
677
|
-
|
|
706
|
+
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
678
707
|
this.#asyncJobManager = config.asyncJobManager;
|
|
679
708
|
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
680
709
|
this.#scopedModels = config.scopedModels ?? [];
|
|
@@ -2108,6 +2137,8 @@ export class AgentSession {
|
|
|
2108
2137
|
* Call this when completely done with the session.
|
|
2109
2138
|
*/
|
|
2110
2139
|
async dispose(): Promise<void> {
|
|
2140
|
+
this.#isDisposed = true;
|
|
2141
|
+
this.#pendingBackgroundExchanges = [];
|
|
2111
2142
|
this.#evalExecutionDisposing = true;
|
|
2112
2143
|
try {
|
|
2113
2144
|
if (this.#extensionRunner?.hasHandlers("session_shutdown")) {
|
|
@@ -2130,7 +2161,7 @@ export class AgentSession {
|
|
|
2130
2161
|
);
|
|
2131
2162
|
}
|
|
2132
2163
|
await disposeKernelSessionsByOwner(this.#evalKernelOwnerId);
|
|
2133
|
-
this.#
|
|
2164
|
+
this.#releasePowerAssertion();
|
|
2134
2165
|
await this.sessionManager.close();
|
|
2135
2166
|
this.#closeAllProviderSessions("dispose");
|
|
2136
2167
|
const hindsightState = this.setHindsightSessionState(undefined);
|
|
@@ -3171,7 +3202,7 @@ export class AgentSession {
|
|
|
3171
3202
|
skipPostPromptRecoveryWait?: boolean;
|
|
3172
3203
|
},
|
|
3173
3204
|
): Promise<void> {
|
|
3174
|
-
this.#
|
|
3205
|
+
this.#beginInFlight();
|
|
3175
3206
|
const generation = this.#promptGeneration;
|
|
3176
3207
|
try {
|
|
3177
3208
|
// Flush any pending bash messages before the new prompt
|
|
@@ -3291,7 +3322,7 @@ export class AgentSession {
|
|
|
3291
3322
|
await this.#waitForPostPromptRecovery();
|
|
3292
3323
|
}
|
|
3293
3324
|
} finally {
|
|
3294
|
-
this.#
|
|
3325
|
+
this.#endInFlight();
|
|
3295
3326
|
}
|
|
3296
3327
|
}
|
|
3297
3328
|
|
|
@@ -3877,7 +3908,7 @@ export class AgentSession {
|
|
|
3877
3908
|
// Clear prompt-in-flight state: waitForIdle resolves when the agent loop's finally
|
|
3878
3909
|
// block runs, but nested prompt setup/finalizers may still be unwinding. Without this,
|
|
3879
3910
|
// a subsequent prompt() can incorrectly observe the session as busy after an abort.
|
|
3880
|
-
this.#
|
|
3911
|
+
this.#resetInFlight();
|
|
3881
3912
|
// Safety net: if the agent loop aborted without producing an assistant
|
|
3882
3913
|
// message (e.g. failed before the first stream), the in-flight yield was
|
|
3883
3914
|
// never resolved or rejected by the normal message_end path. Reject it now
|
|
@@ -4699,7 +4730,7 @@ export class AgentSession {
|
|
|
4699
4730
|
if (handoffSignal.aborted) {
|
|
4700
4731
|
throw new Error("Handoff cancelled");
|
|
4701
4732
|
}
|
|
4702
|
-
this.#
|
|
4733
|
+
this.#beginInFlight();
|
|
4703
4734
|
try {
|
|
4704
4735
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
4705
4736
|
await this.#promptAgentWithIdleRetry([
|
|
@@ -4711,7 +4742,7 @@ export class AgentSession {
|
|
|
4711
4742
|
},
|
|
4712
4743
|
]);
|
|
4713
4744
|
} finally {
|
|
4714
|
-
this.#
|
|
4745
|
+
this.#endInFlight();
|
|
4715
4746
|
}
|
|
4716
4747
|
await completionPromise;
|
|
4717
4748
|
|
|
@@ -6853,7 +6884,8 @@ export class AgentSession {
|
|
|
6853
6884
|
if (this.#scheduledBackgroundExchangeFlush) return;
|
|
6854
6885
|
this.#scheduledBackgroundExchangeFlush = true;
|
|
6855
6886
|
const attempt = (): void => {
|
|
6856
|
-
if (this.#pendingBackgroundExchanges.length === 0) {
|
|
6887
|
+
if (this.#pendingBackgroundExchanges.length === 0 || this.#isDisposed) {
|
|
6888
|
+
this.#pendingBackgroundExchanges = [];
|
|
6857
6889
|
this.#scheduledBackgroundExchangeFlush = false;
|
|
6858
6890
|
return;
|
|
6859
6891
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -12,8 +12,10 @@ import type { SkillsSettings } from "./config/settings";
|
|
|
12
12
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
13
13
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
14
14
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
15
|
+
import nowPromptTemplate from "./prompts/system/now-prompt.md" with { type: "text" };
|
|
15
16
|
import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
|
|
16
17
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
18
|
+
import { shortenPath } from "./tools/render-utils";
|
|
17
19
|
import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
18
20
|
|
|
19
21
|
interface AlwaysApplyRule {
|
|
@@ -503,7 +505,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
503
505
|
|
|
504
506
|
const date = new Date().toISOString().slice(0, 10);
|
|
505
507
|
const dateTime = date;
|
|
506
|
-
const promptCwd = resolvedCwd.replace(/\\/g, "/");
|
|
508
|
+
const promptCwd = shortenPath(resolvedCwd.replace(/\\/g, "/"));
|
|
507
509
|
|
|
508
510
|
// Build tool metadata for system prompt rendering
|
|
509
511
|
// Priority: explicit list > tools map > defaults
|
|
@@ -541,7 +543,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
541
543
|
const injectedAlwaysApplyRules = dedupeAlwaysApplyRules(alwaysApplyRules, promptSources);
|
|
542
544
|
|
|
543
545
|
const environment = await logger.time("getEnvironmentInfo", getEnvironmentInfo);
|
|
544
|
-
const reportToolIssueToolName = toolPromptNames.get("report_tool_issue") ?? "report_tool_issue";
|
|
545
546
|
const data = {
|
|
546
547
|
systemPromptCustomization: effectiveSystemPromptCustomization,
|
|
547
548
|
customPrompt: resolvedCustomPrompt,
|
|
@@ -568,18 +569,16 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
568
569
|
eagerTasks,
|
|
569
570
|
secretsEnabled,
|
|
570
571
|
};
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
// When autoqa is active the report_tool_issue tool is in the tool set — nudge the agent.
|
|
574
|
-
if (toolNames.includes("report_tool_issue")) {
|
|
575
|
-
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>`;
|
|
576
|
-
}
|
|
577
|
-
|
|
572
|
+
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
|
578
573
|
const systemPrompt = [rendered];
|
|
579
574
|
const projectPrompt = resolvedCustomPrompt ? "" : prompt.render(projectPromptTemplate, data).trim();
|
|
580
575
|
if (projectPrompt) {
|
|
581
576
|
systemPrompt.push(projectPrompt);
|
|
582
577
|
}
|
|
578
|
+
const nowPrompt = prompt.render(nowPromptTemplate, data).trim();
|
|
579
|
+
if (nowPrompt) {
|
|
580
|
+
systemPrompt.push(nowPrompt);
|
|
581
|
+
}
|
|
583
582
|
|
|
584
583
|
return { systemPrompt };
|
|
585
584
|
}
|
package/src/task/executor.ts
CHANGED
|
@@ -139,6 +139,7 @@ export interface ExecutorOptions {
|
|
|
139
139
|
agent: AgentDefinition;
|
|
140
140
|
task: string;
|
|
141
141
|
assignment?: string;
|
|
142
|
+
context?: string;
|
|
142
143
|
description?: string;
|
|
143
144
|
index: number;
|
|
144
145
|
id: string;
|
|
@@ -994,17 +995,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
994
995
|
skills: options.skills,
|
|
995
996
|
promptTemplates: options.promptTemplates,
|
|
996
997
|
workspaceTree: options.workspaceTree,
|
|
997
|
-
systemPrompt: defaultPrompt =>
|
|
998
|
-
prompt.render(subagentSystemPromptTemplate, {
|
|
999
|
-
base: defaultPrompt.join("\n\n"),
|
|
998
|
+
systemPrompt: defaultPrompt => {
|
|
999
|
+
const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
|
|
1000
1000
|
agent: agent.systemPrompt,
|
|
1001
|
+
context: options.context?.trim() ?? "",
|
|
1001
1002
|
worktree: worktree ?? "",
|
|
1002
1003
|
outputSchema: normalizedOutputSchema,
|
|
1003
1004
|
contextFile: options.contextFile,
|
|
1004
1005
|
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
1005
1006
|
ircSelfId: ircEnabled ? id : "",
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1007
|
+
});
|
|
1008
|
+
return defaultPrompt.length === 0
|
|
1009
|
+
? [subagentPrompt]
|
|
1010
|
+
: [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
|
|
1011
|
+
},
|
|
1008
1012
|
sessionManager,
|
|
1009
1013
|
hasUI: false,
|
|
1010
1014
|
spawns: spawnsEnv,
|
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,
|
|
@@ -894,8 +898,9 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
894
898
|
cwd: this.session.cwd,
|
|
895
899
|
worktree: isolationDir,
|
|
896
900
|
agent,
|
|
897
|
-
task: task.
|
|
898
|
-
assignment: task.assignment,
|
|
901
|
+
task: renderSubagentUserPrompt(task.assignment, simpleMode),
|
|
902
|
+
assignment: task.assignment.trim(),
|
|
903
|
+
context: sharedContext,
|
|
899
904
|
description: task.description,
|
|
900
905
|
index,
|
|
901
906
|
id: task.id,
|
|
@@ -979,13 +984,14 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
979
984
|
return result;
|
|
980
985
|
} catch (err) {
|
|
981
986
|
const message = err instanceof Error ? err.message : String(err);
|
|
987
|
+
const assignment = task.assignment.trim();
|
|
982
988
|
return {
|
|
983
989
|
index,
|
|
984
990
|
id: task.id,
|
|
985
991
|
agent: agent.name,
|
|
986
992
|
agentSource: agent.source,
|
|
987
|
-
task:
|
|
988
|
-
assignment
|
|
993
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
994
|
+
assignment,
|
|
989
995
|
description: task.description,
|
|
990
996
|
exitCode: 1,
|
|
991
997
|
output: "",
|
|
@@ -1011,7 +1017,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1011
1017
|
|
|
1012
1018
|
// Execute in parallel with concurrency limit
|
|
1013
1019
|
const { results: partialResults, aborted } = await mapWithConcurrencyLimit(
|
|
1014
|
-
|
|
1020
|
+
tasksWithUniqueIds,
|
|
1015
1021
|
maxConcurrency,
|
|
1016
1022
|
runTask,
|
|
1017
1023
|
signal,
|
|
@@ -1022,14 +1028,15 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
1022
1028
|
if (result !== undefined) {
|
|
1023
1029
|
return result;
|
|
1024
1030
|
}
|
|
1025
|
-
const task =
|
|
1031
|
+
const task = tasksWithUniqueIds[index];
|
|
1032
|
+
const assignment = task.assignment.trim();
|
|
1026
1033
|
return {
|
|
1027
1034
|
index,
|
|
1028
1035
|
id: task.id,
|
|
1029
1036
|
agent: agentName,
|
|
1030
1037
|
agentSource: agent.source,
|
|
1031
|
-
task:
|
|
1032
|
-
assignment
|
|
1038
|
+
task: renderSubagentUserPrompt(assignment, simpleMode),
|
|
1039
|
+
assignment,
|
|
1033
1040
|
description: task.description,
|
|
1034
1041
|
exitCode: 1,
|
|
1035
1042
|
output: "",
|
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 = {
|