@oh-my-pi/pi-coding-agent 13.10.0 → 13.11.0
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 +52 -0
- package/package.json +7 -7
- package/src/commit/agentic/agent.ts +3 -1
- package/src/commit/agentic/index.ts +7 -1
- package/src/commit/analysis/conventional.ts +5 -1
- package/src/commit/analysis/summary.ts +5 -1
- package/src/commit/changelog/generate.ts +5 -1
- package/src/commit/changelog/index.ts +4 -0
- package/src/commit/map-reduce/index.ts +5 -0
- package/src/commit/map-reduce/map-phase.ts +17 -2
- package/src/commit/map-reduce/reduce-phase.ts +5 -1
- package/src/commit/model-selection.ts +38 -26
- package/src/commit/pipeline.ts +22 -11
- package/src/config/settings-schema.ts +20 -0
- package/src/config.ts +10 -3
- package/src/discovery/helpers.ts +7 -3
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +81 -0
- package/src/main.ts +25 -14
- package/src/mcp/manager.ts +40 -2
- package/src/mcp/oauth-flow.ts +41 -0
- package/src/mcp/transports/http.ts +23 -0
- package/src/mcp/types.ts +6 -0
- package/src/modes/components/mcp-add-wizard.ts +12 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/todo-reminder.ts +8 -1
- package/src/modes/controllers/command-controller.ts +75 -3
- package/src/modes/controllers/input-controller.ts +2 -3
- package/src/modes/controllers/mcp-command-controller.ts +9 -1
- package/src/modes/interactive-mode.ts +11 -7
- package/src/modes/theme/theme.ts +30 -27
- package/src/modes/types.ts +2 -1
- package/src/patch/hashline.ts +3 -6
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/find.md +1 -0
- package/src/prompts/tools/grep.md +1 -0
- package/src/prompts/tools/hashline.md +23 -111
- package/src/prompts/tools/todo-write.md +11 -1
- package/src/sdk.ts +1 -1
- package/src/session/agent-session.ts +85 -7
- package/src/session/session-manager.ts +5 -9
- package/src/slash-commands/builtin-registry.ts +10 -2
- package/src/task/executor.ts +9 -18
- package/src/task/index.ts +8 -4
- package/src/task/render.ts +5 -10
- package/src/task/template.ts +4 -1
- package/src/task/types.ts +2 -0
- package/src/tools/ast-edit.ts +26 -7
- package/src/tools/ast-grep.ts +26 -9
- package/src/tools/fetch.ts +36 -5
- package/src/tools/find.ts +13 -64
- package/src/tools/grep.ts +27 -10
- package/src/tools/json-tree.ts +1 -1
- package/src/tools/output-meta.ts +2 -1
- package/src/tools/path-utils.ts +348 -0
- package/src/tools/todo-write.ts +27 -4
- package/src/utils/commit-message-generator.ts +27 -22
- package/src/utils/image-input.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/utils/title-generator.ts +36 -23
- package/src/utils/tool-choice.ts +28 -0
- package/src/web/parallel.ts +346 -0
- package/src/web/scrapers/youtube.ts +29 -0
- package/src/web/search/provider.ts +4 -1
- package/src/web/search/providers/parallel.ts +63 -0
- package/src/web/search/types.ts +1 -0
|
@@ -89,6 +89,7 @@ import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
|
89
89
|
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../patch";
|
|
90
90
|
import type { PlanModeState } from "../plan-mode/state";
|
|
91
91
|
import autoHandoffThresholdFocusPrompt from "../prompts/system/auto-handoff-threshold-focus.md" with { type: "text" };
|
|
92
|
+
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
92
93
|
import handoffDocumentPrompt from "../prompts/system/handoff-document.md" with { type: "text" };
|
|
93
94
|
import planModeActivePrompt from "../prompts/system/plan-mode-active.md" with { type: "text" };
|
|
94
95
|
import planModeReferencePrompt from "../prompts/system/plan-mode-reference.md" with { type: "text" };
|
|
@@ -106,6 +107,7 @@ import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from ".
|
|
|
106
107
|
import { parseCommandArgs } from "../utils/command-args";
|
|
107
108
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
108
109
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
110
|
+
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
109
111
|
import {
|
|
110
112
|
type CompactionResult,
|
|
111
113
|
calculateContextTokens,
|
|
@@ -350,6 +352,7 @@ export class AgentSession {
|
|
|
350
352
|
// Todo completion reminder state
|
|
351
353
|
#todoReminderCount = 0;
|
|
352
354
|
#todoPhases: TodoPhase[] = [];
|
|
355
|
+
#nextToolChoiceOverride: ToolChoice | undefined = undefined;
|
|
353
356
|
|
|
354
357
|
// Bash execution state
|
|
355
358
|
#bashAbortController: AbortController | undefined = undefined;
|
|
@@ -457,6 +460,12 @@ export class AgentSession {
|
|
|
457
460
|
return this.#modelRegistry;
|
|
458
461
|
}
|
|
459
462
|
|
|
463
|
+
consumeNextToolChoiceOverride(): ToolChoice | undefined {
|
|
464
|
+
const toolChoice = this.#nextToolChoiceOverride;
|
|
465
|
+
this.#nextToolChoiceOverride = undefined;
|
|
466
|
+
return toolChoice;
|
|
467
|
+
}
|
|
468
|
+
|
|
460
469
|
/** Provider-scoped mutable state store for transport/session caches. */
|
|
461
470
|
get providerSessionState(): Map<string, ProviderSessionState> {
|
|
462
471
|
return this.#providerSessionState;
|
|
@@ -791,7 +800,11 @@ export class AgentSession {
|
|
|
791
800
|
const compactionTask = this.#checkCompaction(msg);
|
|
792
801
|
this.#trackPostPromptTask(compactionTask);
|
|
793
802
|
await compactionTask;
|
|
794
|
-
// Check for incomplete todos
|
|
803
|
+
// Check for incomplete todos only after a final assistant stop, not intermediate tool-use turns.
|
|
804
|
+
const hasToolCalls = msg.content.some(content => content.type === "toolCall");
|
|
805
|
+
if (hasToolCalls) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
795
808
|
if (msg.stopReason !== "error" && msg.stopReason !== "aborted") {
|
|
796
809
|
if (this.#enforceRewindBeforeYield()) {
|
|
797
810
|
return;
|
|
@@ -1349,6 +1362,7 @@ export class AgentSession {
|
|
|
1349
1362
|
if (!this.#extensionRunner) return;
|
|
1350
1363
|
if (event.type === "agent_start") {
|
|
1351
1364
|
this.#turnIndex = 0;
|
|
1365
|
+
this.#nextToolChoiceOverride = undefined;
|
|
1352
1366
|
await this.#extensionRunner.emit({ type: "agent_start" });
|
|
1353
1367
|
} else if (event.type === "agent_end") {
|
|
1354
1368
|
await this.#extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
@@ -1945,6 +1959,8 @@ export class AgentSession {
|
|
|
1945
1959
|
return;
|
|
1946
1960
|
}
|
|
1947
1961
|
|
|
1962
|
+
const eagerTodoPrelude = !options?.synthetic ? this.#createEagerTodoPrelude() : undefined;
|
|
1963
|
+
|
|
1948
1964
|
const userContent: (TextContent | ImageContent)[] = [{ type: "text", text: expandedText }];
|
|
1949
1965
|
if (options?.images) {
|
|
1950
1966
|
userContent.push(...options.images);
|
|
@@ -1954,7 +1970,20 @@ export class AgentSession {
|
|
|
1954
1970
|
? { role: "developer" as const, content: userContent, attribution: "agent" as const, timestamp: Date.now() }
|
|
1955
1971
|
: { role: "user" as const, content: userContent, attribution: "user" as const, timestamp: Date.now() };
|
|
1956
1972
|
|
|
1957
|
-
|
|
1973
|
+
if (eagerTodoPrelude) {
|
|
1974
|
+
this.#nextToolChoiceOverride = eagerTodoPrelude.toolChoice;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
try {
|
|
1978
|
+
await this.#promptWithMessage(message, expandedText, {
|
|
1979
|
+
...options,
|
|
1980
|
+
prependMessages: eagerTodoPrelude ? [eagerTodoPrelude.message] : undefined,
|
|
1981
|
+
});
|
|
1982
|
+
} finally {
|
|
1983
|
+
if (eagerTodoPrelude) {
|
|
1984
|
+
this.#nextToolChoiceOverride = undefined;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1958
1987
|
if (!options?.synthetic) {
|
|
1959
1988
|
await this.#enforcePlanModeToolDecision();
|
|
1960
1989
|
}
|
|
@@ -1997,6 +2026,7 @@ export class AgentSession {
|
|
|
1997
2026
|
message: AgentMessage,
|
|
1998
2027
|
expandedText: string,
|
|
1999
2028
|
options?: Pick<PromptOptions, "toolChoice" | "images" | "skipCompactionCheck"> & {
|
|
2029
|
+
prependMessages?: AgentMessage[];
|
|
2000
2030
|
skipPostPromptRecoveryWait?: boolean;
|
|
2001
2031
|
},
|
|
2002
2032
|
): Promise<void> {
|
|
@@ -2034,7 +2064,7 @@ export class AgentSession {
|
|
|
2034
2064
|
await this.#checkCompaction(lastAssistant, false);
|
|
2035
2065
|
}
|
|
2036
2066
|
|
|
2037
|
-
// Build messages array (
|
|
2067
|
+
// Build messages array (session context, eager todo prelude, then active prompt message)
|
|
2038
2068
|
const messages: AgentMessage[] = [];
|
|
2039
2069
|
const planReferenceMessage = await this.#buildPlanReferenceMessage?.();
|
|
2040
2070
|
if (planReferenceMessage) {
|
|
@@ -2044,6 +2074,9 @@ export class AgentSession {
|
|
|
2044
2074
|
if (planModeMessage) {
|
|
2045
2075
|
messages.push(planModeMessage);
|
|
2046
2076
|
}
|
|
2077
|
+
if (options?.prependMessages) {
|
|
2078
|
+
messages.push(...options.prependMessages);
|
|
2079
|
+
}
|
|
2047
2080
|
|
|
2048
2081
|
messages.push(message);
|
|
2049
2082
|
|
|
@@ -3481,6 +3514,51 @@ export class AgentSession {
|
|
|
3481
3514
|
this.agent.setTools(previousTools);
|
|
3482
3515
|
}
|
|
3483
3516
|
}
|
|
3517
|
+
|
|
3518
|
+
#createEagerTodoPrelude(): { message: AgentMessage; toolChoice: ToolChoice } | undefined {
|
|
3519
|
+
const eagerTodosEnabled = this.settings.get("todo.eager");
|
|
3520
|
+
const todosEnabled = this.settings.get("todo.enabled");
|
|
3521
|
+
if (!eagerTodosEnabled || !todosEnabled) {
|
|
3522
|
+
return undefined;
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
if (this.#planModeState?.enabled) {
|
|
3526
|
+
return undefined;
|
|
3527
|
+
}
|
|
3528
|
+
if (this.getTodoPhases().length > 0) {
|
|
3529
|
+
return undefined;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
if (!this.#toolRegistry.has("todo_write")) {
|
|
3533
|
+
logger.warn("Eager todo enforcement skipped because todo_write is unavailable", {
|
|
3534
|
+
activeToolNames: this.agent.state.tools.map(tool => tool.name),
|
|
3535
|
+
});
|
|
3536
|
+
return undefined;
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
const todoWriteToolChoice = buildNamedToolChoice("todo_write", this.model);
|
|
3540
|
+
if (!todoWriteToolChoice) {
|
|
3541
|
+
logger.warn("Eager todo enforcement skipped because the current model does not support forcing todo_write", {
|
|
3542
|
+
modelApi: this.model?.api,
|
|
3543
|
+
modelId: this.model?.id,
|
|
3544
|
+
});
|
|
3545
|
+
return undefined;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
const eagerTodoReminder = renderPromptTemplate(eagerTodoPrompt);
|
|
3549
|
+
|
|
3550
|
+
return {
|
|
3551
|
+
message: {
|
|
3552
|
+
role: "custom",
|
|
3553
|
+
customType: "eager-todo-prelude",
|
|
3554
|
+
content: eagerTodoReminder,
|
|
3555
|
+
display: false,
|
|
3556
|
+
attribution: "agent",
|
|
3557
|
+
timestamp: Date.now(),
|
|
3558
|
+
},
|
|
3559
|
+
toolChoice: todoWriteToolChoice,
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3484
3562
|
/**
|
|
3485
3563
|
* Check if agent stopped with incomplete todos and prompt to continue.
|
|
3486
3564
|
*/
|
|
@@ -5191,8 +5269,8 @@ export class AgentSession {
|
|
|
5191
5269
|
}
|
|
5192
5270
|
|
|
5193
5271
|
for (const msg of this.messages) {
|
|
5194
|
-
if (msg.role === "user") {
|
|
5195
|
-
lines.push("## User\n");
|
|
5272
|
+
if (msg.role === "user" || msg.role === "developer") {
|
|
5273
|
+
lines.push(msg.role === "developer" ? "## Developer\n" : "## User\n");
|
|
5196
5274
|
if (typeof msg.content === "string") {
|
|
5197
5275
|
lines.push(msg.content);
|
|
5198
5276
|
} else {
|
|
@@ -5316,8 +5394,8 @@ export class AgentSession {
|
|
|
5316
5394
|
lines.push("");
|
|
5317
5395
|
|
|
5318
5396
|
for (const msg of this.messages) {
|
|
5319
|
-
if (msg.role === "user") {
|
|
5320
|
-
lines.push("## User");
|
|
5397
|
+
if (msg.role === "user" || msg.role === "developer") {
|
|
5398
|
+
lines.push(msg.role === "developer" ? "## Developer" : "## User");
|
|
5321
5399
|
lines.push("");
|
|
5322
5400
|
if (typeof msg.content === "string") {
|
|
5323
5401
|
lines.push(msg.content);
|
|
@@ -629,15 +629,11 @@ function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void {
|
|
|
629
629
|
const terminalId = getTerminalId();
|
|
630
630
|
if (!terminalId) return;
|
|
631
631
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
void Bun.write(breadcrumbFile, content);
|
|
638
|
-
} catch {
|
|
639
|
-
// Best-effort — don't break session creation if breadcrumb fails
|
|
640
|
-
}
|
|
632
|
+
const breadcrumbDir = path.join(getDefaultAgentDir(), TERMINAL_SESSIONS_DIR);
|
|
633
|
+
const breadcrumbFile = path.join(breadcrumbDir, terminalId);
|
|
634
|
+
const content = `${cwd}\n${sessionFile}\n`;
|
|
635
|
+
// Best-effort — don't break session creation if breadcrumb fails
|
|
636
|
+
Bun.write(breadcrumbFile, content).catch(() => {});
|
|
641
637
|
}
|
|
642
638
|
|
|
643
639
|
/**
|
|
@@ -214,8 +214,16 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
214
214
|
{
|
|
215
215
|
name: "copy",
|
|
216
216
|
description: "Copy last agent message to clipboard",
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
subcommands: [
|
|
218
|
+
{ name: "last", description: "Copy full last agent message" },
|
|
219
|
+
{ name: "code", description: "Copy last code block" },
|
|
220
|
+
{ name: "all", description: "Copy all code blocks from last message" },
|
|
221
|
+
{ name: "cmd", description: "Copy last bash/python command" },
|
|
222
|
+
],
|
|
223
|
+
allowArgs: true,
|
|
224
|
+
handle: async (command, runtime) => {
|
|
225
|
+
const sub = command.args.trim().toLowerCase() || undefined;
|
|
226
|
+
await runtime.ctx.handleCopyCommand(sub);
|
|
219
227
|
runtime.ctx.editor.setText("");
|
|
220
228
|
},
|
|
221
229
|
},
|
package/src/task/executor.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
9
8
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
9
|
import type { TSchema } from "@sinclair/typebox";
|
|
11
10
|
import Ajv, { type ValidateFunction } from "ajv";
|
|
@@ -28,6 +27,7 @@ import { type ContextFileEntry, truncateTail } from "../tools";
|
|
|
28
27
|
import { jtdToJsonSchema } from "../tools/jtd-to-json-schema";
|
|
29
28
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
30
29
|
import type { EventBus } from "../utils/event-bus";
|
|
30
|
+
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
31
31
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
32
32
|
import {
|
|
33
33
|
type AgentDefinition,
|
|
@@ -117,28 +117,13 @@ function getReportFindingKey(value: unknown): string | null {
|
|
|
117
117
|
return `${filePath}:${lineStart}:${lineEnd}:${priority ?? ""}:${title}`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
function buildSubmitResultToolChoice(model?: Model<Api>): ToolChoice | undefined {
|
|
121
|
-
if (!model) return undefined;
|
|
122
|
-
if (
|
|
123
|
-
model.api === "openai-codex-responses" ||
|
|
124
|
-
model.api === "openai-responses" ||
|
|
125
|
-
model.api === "openai-completions" ||
|
|
126
|
-
model.api === "azure-openai-responses"
|
|
127
|
-
) {
|
|
128
|
-
return { type: "function", name: "submit_result" };
|
|
129
|
-
}
|
|
130
|
-
if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") {
|
|
131
|
-
return { type: "tool", name: "submit_result" };
|
|
132
|
-
}
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
120
|
/** Options for subagent execution */
|
|
137
121
|
export interface ExecutorOptions {
|
|
138
122
|
cwd: string;
|
|
139
123
|
worktree?: string;
|
|
140
124
|
agent: AgentDefinition;
|
|
141
125
|
task: string;
|
|
126
|
+
assignment?: string;
|
|
142
127
|
description?: string;
|
|
143
128
|
index: number;
|
|
144
129
|
id: string;
|
|
@@ -458,6 +443,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
458
443
|
cwd,
|
|
459
444
|
agent,
|
|
460
445
|
task,
|
|
446
|
+
assignment,
|
|
461
447
|
index,
|
|
462
448
|
id,
|
|
463
449
|
worktree,
|
|
@@ -478,6 +464,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
478
464
|
agentSource: agent.source,
|
|
479
465
|
status: "running",
|
|
480
466
|
task,
|
|
467
|
+
assignment,
|
|
481
468
|
description: options.description,
|
|
482
469
|
lastIntent: undefined,
|
|
483
470
|
recentTools: [],
|
|
@@ -496,6 +483,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
496
483
|
agent: agent.name,
|
|
497
484
|
agentSource: agent.source,
|
|
498
485
|
task,
|
|
486
|
+
assignment,
|
|
499
487
|
description: options.description,
|
|
500
488
|
exitCode: 1,
|
|
501
489
|
output: "",
|
|
@@ -638,6 +626,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
638
626
|
agent: agent.name,
|
|
639
627
|
agentSource: agent.source,
|
|
640
628
|
task,
|
|
629
|
+
assignment,
|
|
641
630
|
progress: { ...progress },
|
|
642
631
|
});
|
|
643
632
|
}
|
|
@@ -727,6 +716,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
727
716
|
agent: agent.name,
|
|
728
717
|
agentSource: agent.source,
|
|
729
718
|
task,
|
|
719
|
+
assignment,
|
|
730
720
|
event,
|
|
731
721
|
});
|
|
732
722
|
}
|
|
@@ -1091,7 +1081,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1091
1081
|
await session.prompt(task);
|
|
1092
1082
|
await session.waitForIdle();
|
|
1093
1083
|
|
|
1094
|
-
const reminderToolChoice =
|
|
1084
|
+
const reminderToolChoice = buildNamedToolChoice("submit_result", session.model);
|
|
1095
1085
|
|
|
1096
1086
|
let retryCount = 0;
|
|
1097
1087
|
while (!submitResultCalled && retryCount < MAX_SUBMIT_RESULT_RETRIES && !abortSignal.aborted) {
|
|
@@ -1247,6 +1237,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1247
1237
|
agent: agent.name,
|
|
1248
1238
|
agentSource: agent.source,
|
|
1249
1239
|
task,
|
|
1240
|
+
assignment,
|
|
1250
1241
|
description: options.description,
|
|
1251
1242
|
lastIntent: progress.lastIntent,
|
|
1252
1243
|
exitCode,
|
package/src/task/index.ts
CHANGED
|
@@ -224,6 +224,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
224
224
|
agentSource: fallbackAgentSource,
|
|
225
225
|
status: "pending",
|
|
226
226
|
task: renderedTask.task,
|
|
227
|
+
assignment: renderedTask.assignment,
|
|
227
228
|
description: renderedTask.description,
|
|
228
229
|
recentTools: [],
|
|
229
230
|
recentOutput: [],
|
|
@@ -732,6 +733,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
732
733
|
agentSource: agent.source,
|
|
733
734
|
status: "pending",
|
|
734
735
|
task: t.task,
|
|
736
|
+
assignment: t.assignment,
|
|
735
737
|
recentTools: [],
|
|
736
738
|
recentOutput: [],
|
|
737
739
|
toolCount: 0,
|
|
@@ -749,6 +751,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
749
751
|
cwd: this.session.cwd,
|
|
750
752
|
agent,
|
|
751
753
|
task: task.task,
|
|
754
|
+
assignment: task.assignment,
|
|
752
755
|
description: task.description,
|
|
753
756
|
index,
|
|
754
757
|
id: task.id,
|
|
@@ -801,6 +804,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
801
804
|
worktree: isolationDir,
|
|
802
805
|
agent,
|
|
803
806
|
task: task.task,
|
|
807
|
+
assignment: task.assignment,
|
|
804
808
|
description: task.description,
|
|
805
809
|
index,
|
|
806
810
|
id: task.id,
|
|
@@ -834,11 +838,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
834
838
|
const commitMsg =
|
|
835
839
|
commitStyle === "ai" && this.session.modelRegistry
|
|
836
840
|
? async (diff: string) => {
|
|
837
|
-
const smolModel = this.session.settings.getModelRole("smol");
|
|
838
841
|
return generateCommitMessage(
|
|
839
842
|
diff,
|
|
840
843
|
this.session.modelRegistry!,
|
|
841
|
-
|
|
844
|
+
this.session.settings,
|
|
842
845
|
this.session.getSessionId?.() ?? undefined,
|
|
843
846
|
);
|
|
844
847
|
}
|
|
@@ -887,6 +890,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
887
890
|
agent: agent.name,
|
|
888
891
|
agentSource: agent.source,
|
|
889
892
|
task: task.task,
|
|
893
|
+
assignment: task.assignment,
|
|
890
894
|
description: task.description,
|
|
891
895
|
exitCode: 1,
|
|
892
896
|
output: "",
|
|
@@ -930,6 +934,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
930
934
|
agent: agentName,
|
|
931
935
|
agentSource: agent.source,
|
|
932
936
|
task: task.task,
|
|
937
|
+
assignment: task.assignment,
|
|
933
938
|
description: task.description,
|
|
934
939
|
exitCode: 1,
|
|
935
940
|
output: "",
|
|
@@ -1081,11 +1086,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
1081
1086
|
const commitMsg =
|
|
1082
1087
|
commitStyle === "ai" && this.session.modelRegistry
|
|
1083
1088
|
? async (diff: string) => {
|
|
1084
|
-
const smolModel = this.session.settings.getModelRole("smol");
|
|
1085
1089
|
return generateCommitMessage(
|
|
1086
1090
|
diff,
|
|
1087
1091
|
this.session.modelRegistry!,
|
|
1088
|
-
|
|
1092
|
+
this.session.settings,
|
|
1089
1093
|
this.session.getSessionId?.() ?? undefined,
|
|
1090
1094
|
);
|
|
1091
1095
|
}
|
package/src/task/render.ts
CHANGED
|
@@ -374,16 +374,11 @@ function renderTaskSection(
|
|
|
374
374
|
maxExpanded = 20,
|
|
375
375
|
): string[] {
|
|
376
376
|
const lines: string[] = [];
|
|
377
|
-
const trimmed = task.
|
|
377
|
+
const trimmed = task.trim();
|
|
378
378
|
if (!expanded || !trimmed) return lines;
|
|
379
379
|
|
|
380
|
-
// Strip the shared <context>...</context> block — it's the same
|
|
381
|
-
// across all tasks and just adds noise when expanded.
|
|
382
|
-
const stripped = trimmed.replace(/<context>[\s\S]*?<\/context>\s*/, "").trimStart();
|
|
383
|
-
if (!stripped) return lines;
|
|
384
|
-
|
|
385
380
|
lines.push(`${continuePrefix}${theme.fg("dim", "Task")}`);
|
|
386
|
-
const taskLines =
|
|
381
|
+
const taskLines = trimmed.split("\n");
|
|
387
382
|
for (const line of taskLines.slice(0, maxExpanded)) {
|
|
388
383
|
lines.push(`${continuePrefix} ${theme.fg("dim", truncateToWidth(replaceTabs(line), 70))}`);
|
|
389
384
|
}
|
|
@@ -526,7 +521,7 @@ function renderAgentProgress(
|
|
|
526
521
|
|
|
527
522
|
if (progress.status === "running") {
|
|
528
523
|
if (!description) {
|
|
529
|
-
const taskPreview = truncateToWidth(progress.task, 40);
|
|
524
|
+
const taskPreview = truncateToWidth(progress.assignment ?? progress.task, 40);
|
|
530
525
|
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
531
526
|
}
|
|
532
527
|
if (progress.toolCount > 0) {
|
|
@@ -546,7 +541,7 @@ function renderAgentProgress(
|
|
|
546
541
|
|
|
547
542
|
lines.push(statusLine);
|
|
548
543
|
|
|
549
|
-
lines.push(...renderTaskSection(progress.task, continuePrefix, expanded, theme));
|
|
544
|
+
lines.push(...renderTaskSection(progress.assignment ?? progress.task, continuePrefix, expanded, theme));
|
|
550
545
|
|
|
551
546
|
// Current tool (if running) or most recent completed tool
|
|
552
547
|
if (progress.status === "running") {
|
|
@@ -781,7 +776,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
781
776
|
|
|
782
777
|
lines.push(statusLine);
|
|
783
778
|
|
|
784
|
-
lines.push(...renderTaskSection(result.task, continuePrefix, expanded, theme));
|
|
779
|
+
lines.push(...renderTaskSection(result.assignment ?? result.task, continuePrefix, expanded, theme));
|
|
785
780
|
|
|
786
781
|
if (aborted && result.abortReason) {
|
|
787
782
|
lines.push(
|
package/src/task/template.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { TaskItem } from "./types";
|
|
|
5
5
|
interface RenderResult {
|
|
6
6
|
/** Full task text sent to the subagent */
|
|
7
7
|
task: string;
|
|
8
|
+
/** Raw per-task assignment text, without prompt template boilerplate */
|
|
9
|
+
assignment: string;
|
|
8
10
|
id: string;
|
|
9
11
|
description: string;
|
|
10
12
|
}
|
|
@@ -20,10 +22,11 @@ export function renderTemplate(context: string | undefined, task: TaskItem): Ren
|
|
|
20
22
|
context = context?.trim();
|
|
21
23
|
|
|
22
24
|
if (!context || !assignment) {
|
|
23
|
-
return { task: assignment || context!, id, description };
|
|
25
|
+
return { task: assignment || context!, assignment: assignment || context!, id, description };
|
|
24
26
|
}
|
|
25
27
|
return {
|
|
26
28
|
task: renderPromptTemplate(subagentUserPromptTemplate, { context, assignment }),
|
|
29
|
+
assignment,
|
|
27
30
|
id,
|
|
28
31
|
description,
|
|
29
32
|
};
|
package/src/task/types.ts
CHANGED
|
@@ -136,6 +136,7 @@ export interface AgentProgress {
|
|
|
136
136
|
agentSource: AgentSource;
|
|
137
137
|
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
138
138
|
task: string;
|
|
139
|
+
assignment?: string;
|
|
139
140
|
description?: string;
|
|
140
141
|
lastIntent?: string;
|
|
141
142
|
currentTool?: string;
|
|
@@ -158,6 +159,7 @@ export interface SingleResult {
|
|
|
158
159
|
agent: string;
|
|
159
160
|
agentSource: AgentSource;
|
|
160
161
|
task: string;
|
|
162
|
+
assignment?: string;
|
|
161
163
|
description?: string;
|
|
162
164
|
lastIntent?: string;
|
|
163
165
|
exitCode: number;
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -14,7 +14,13 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
|
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
16
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
combineSearchGlobs,
|
|
19
|
+
hasGlobPathChars,
|
|
20
|
+
parseSearchPath,
|
|
21
|
+
resolveMultiSearchPath,
|
|
22
|
+
resolveToCwd,
|
|
23
|
+
} from "./path-utils";
|
|
18
24
|
import {
|
|
19
25
|
dedupeParseErrors,
|
|
20
26
|
formatCount,
|
|
@@ -98,7 +104,12 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
98
104
|
}
|
|
99
105
|
const maxFiles = parseInt(process.env.PI_MAX_AST_FILES ?? "", 10) || 1000;
|
|
100
106
|
|
|
107
|
+
const formatScopePath = (targetPath: string): string => {
|
|
108
|
+
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
109
|
+
return relative.length === 0 ? "." : relative;
|
|
110
|
+
};
|
|
101
111
|
let searchPath: string | undefined;
|
|
112
|
+
let scopePath: string | undefined;
|
|
102
113
|
let globFilter = params.glob?.trim() || undefined;
|
|
103
114
|
const rawPath = params.path?.trim();
|
|
104
115
|
if (rawPath) {
|
|
@@ -112,21 +123,29 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
112
123
|
throw new ToolError(`Cannot rewrite internal URL without backing file: ${rawPath}`);
|
|
113
124
|
}
|
|
114
125
|
searchPath = resource.sourcePath;
|
|
126
|
+
scopePath = formatScopePath(searchPath);
|
|
115
127
|
} else {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
const multiSearchPath = await resolveMultiSearchPath(rawPath, this.session.cwd, globFilter);
|
|
129
|
+
if (multiSearchPath) {
|
|
130
|
+
searchPath = multiSearchPath.basePath;
|
|
131
|
+
globFilter = multiSearchPath.glob;
|
|
132
|
+
scopePath = multiSearchPath.scopePath;
|
|
133
|
+
} else {
|
|
134
|
+
const parsedPath = parseSearchPath(rawPath);
|
|
135
|
+
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
136
|
+
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
137
|
+
scopePath = formatScopePath(searchPath);
|
|
138
|
+
}
|
|
119
139
|
}
|
|
120
140
|
}
|
|
121
|
-
|
|
122
141
|
const resolvedSearchPath = searchPath ?? resolveToCwd(".", this.session.cwd);
|
|
123
|
-
|
|
142
|
+
scopePath = scopePath ?? formatScopePath(resolvedSearchPath);
|
|
124
143
|
let isDirectory: boolean;
|
|
125
144
|
try {
|
|
126
145
|
const stat = await Bun.file(resolvedSearchPath).stat();
|
|
127
146
|
isDirectory = stat.isDirectory();
|
|
128
147
|
} catch {
|
|
129
|
-
throw new ToolError(`Path not found: ${
|
|
148
|
+
throw new ToolError(`Path not found: ${scopePath}`);
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
const result = await astEdit({
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -14,7 +14,13 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
|
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
16
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
combineSearchGlobs,
|
|
19
|
+
hasGlobPathChars,
|
|
20
|
+
parseSearchPath,
|
|
21
|
+
resolveMultiSearchPath,
|
|
22
|
+
resolveToCwd,
|
|
23
|
+
} from "./path-utils";
|
|
18
24
|
import {
|
|
19
25
|
dedupeParseErrors,
|
|
20
26
|
formatCount,
|
|
@@ -86,7 +92,12 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
86
92
|
throw new ToolError("Context must be a non-negative number");
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
const formatScopePath = (targetPath: string): string => {
|
|
96
|
+
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
97
|
+
return relative.length === 0 ? "." : relative;
|
|
98
|
+
};
|
|
89
99
|
let searchPath: string | undefined;
|
|
100
|
+
let scopePath: string | undefined;
|
|
90
101
|
let globFilter = params.glob?.trim() || undefined;
|
|
91
102
|
const rawPath = params.path?.trim();
|
|
92
103
|
if (rawPath) {
|
|
@@ -100,24 +111,30 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
100
111
|
throw new ToolError(`Cannot search internal URL without backing file: ${rawPath}`);
|
|
101
112
|
}
|
|
102
113
|
searchPath = resource.sourcePath;
|
|
114
|
+
scopePath = formatScopePath(searchPath);
|
|
103
115
|
} else {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
const multiSearchPath = await resolveMultiSearchPath(rawPath, this.session.cwd, globFilter);
|
|
117
|
+
if (multiSearchPath) {
|
|
118
|
+
searchPath = multiSearchPath.basePath;
|
|
119
|
+
globFilter = multiSearchPath.glob;
|
|
120
|
+
scopePath = multiSearchPath.scopePath;
|
|
121
|
+
} else {
|
|
122
|
+
const parsedPath = parseSearchPath(rawPath);
|
|
123
|
+
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
124
|
+
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
125
|
+
scopePath = formatScopePath(searchPath);
|
|
126
|
+
}
|
|
107
127
|
}
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
const resolvedSearchPath = searchPath ?? resolveToCwd(".", this.session.cwd);
|
|
111
|
-
|
|
112
|
-
const relative = path.relative(this.session.cwd, resolvedSearchPath).replace(/\\/g, "/");
|
|
113
|
-
return relative.length === 0 ? "." : relative;
|
|
114
|
-
})();
|
|
131
|
+
scopePath = scopePath ?? formatScopePath(resolvedSearchPath);
|
|
115
132
|
let isDirectory: boolean;
|
|
116
133
|
try {
|
|
117
134
|
const stat = await Bun.file(resolvedSearchPath).stat();
|
|
118
135
|
isDirectory = stat.isDirectory();
|
|
119
136
|
} catch {
|
|
120
|
-
throw new ToolError(`Path not found: ${
|
|
137
|
+
throw new ToolError(`Path not found: ${scopePath}`);
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
const result = await astGrep({
|