@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.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 +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
package/src/task/executor.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
|
9
9
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import type { TSchema } from "@sinclair/typebox";
|
|
11
11
|
import Ajv, { type ValidateFunction } from "ajv";
|
|
12
|
-
import
|
|
12
|
+
import { ModelRegistry } from "../config/model-registry";
|
|
13
13
|
import { resolveModelOverride } from "../config/model-resolver";
|
|
14
14
|
import { type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
|
15
15
|
import { Settings } from "../config/settings";
|
|
@@ -19,7 +19,7 @@ import { callTool } from "../mcp/client";
|
|
|
19
19
|
import type { MCPManager } from "../mcp/manager";
|
|
20
20
|
import submitReminderTemplate from "../prompts/system/subagent-submit-reminder.md" with { type: "text" };
|
|
21
21
|
import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
|
|
22
|
-
import { createAgentSession, discoverAuthStorage
|
|
22
|
+
import { createAgentSession, discoverAuthStorage } from "../sdk";
|
|
23
23
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
24
24
|
import type { AuthStorage } from "../session/auth-storage";
|
|
25
25
|
import { SessionManager } from "../session/session-manager";
|
|
@@ -153,7 +153,6 @@ export interface ExecutorOptions {
|
|
|
153
153
|
description?: string;
|
|
154
154
|
index: number;
|
|
155
155
|
id: string;
|
|
156
|
-
context?: string;
|
|
157
156
|
modelOverride?: string | string[];
|
|
158
157
|
thinkingLevel?: ThinkingLevel;
|
|
159
158
|
outputSchema?: unknown;
|
|
@@ -374,7 +373,6 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
374
373
|
index,
|
|
375
374
|
id,
|
|
376
375
|
worktree,
|
|
377
|
-
context,
|
|
378
376
|
modelOverride,
|
|
379
377
|
thinkingLevel,
|
|
380
378
|
outputSchema,
|
|
@@ -421,9 +419,6 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
421
419
|
};
|
|
422
420
|
}
|
|
423
421
|
|
|
424
|
-
// Build full task with context
|
|
425
|
-
const fullTask = context ? `${context}\n\n${task}` : task;
|
|
426
|
-
|
|
427
422
|
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
428
423
|
let subtaskSessionFile: string | undefined;
|
|
429
424
|
if (options.artifactsDir) {
|
|
@@ -849,7 +844,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
849
844
|
checkAbort();
|
|
850
845
|
const authStorage = options.authStorage ?? (await discoverAuthStorage());
|
|
851
846
|
checkAbort();
|
|
852
|
-
const modelRegistry = options.modelRegistry ??
|
|
847
|
+
const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
|
|
853
848
|
checkAbort();
|
|
854
849
|
|
|
855
850
|
const { model, thinkingLevel: resolvedThinkingLevel } = resolveModelOverride(
|
|
@@ -905,7 +900,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
905
900
|
|
|
906
901
|
session.sessionManager.appendSessionInit({
|
|
907
902
|
systemPrompt: session.agent.state.systemPrompt,
|
|
908
|
-
task
|
|
903
|
+
task,
|
|
909
904
|
tools: session.getAllToolNames(),
|
|
910
905
|
outputSchema,
|
|
911
906
|
});
|
|
@@ -994,7 +989,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
994
989
|
}
|
|
995
990
|
});
|
|
996
991
|
|
|
997
|
-
await session.prompt(
|
|
992
|
+
await session.prompt(task);
|
|
998
993
|
|
|
999
994
|
const reminderToolChoice = buildSubmitResultToolChoice(session.model);
|
|
1000
995
|
|
package/src/task/index.ts
CHANGED
|
@@ -35,7 +35,15 @@ import { AgentOutputManager } from "./output-manager";
|
|
|
35
35
|
import { mapWithConcurrencyLimit } from "./parallel";
|
|
36
36
|
import { renderCall, renderResult } from "./render";
|
|
37
37
|
import { renderTemplate } from "./template";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
type AgentProgress,
|
|
40
|
+
type SingleResult,
|
|
41
|
+
type TaskParams,
|
|
42
|
+
type TaskSchema,
|
|
43
|
+
type TaskToolDetails,
|
|
44
|
+
taskSchema,
|
|
45
|
+
taskSchemaNoIsolation,
|
|
46
|
+
} from "./types";
|
|
39
47
|
import {
|
|
40
48
|
applyBaseline,
|
|
41
49
|
captureBaseline,
|
|
@@ -102,12 +110,13 @@ export { taskSchema } from "./types";
|
|
|
102
110
|
/**
|
|
103
111
|
* Build dynamic tool description listing available agents.
|
|
104
112
|
*/
|
|
105
|
-
async function buildDescription(cwd: string, maxConcurrency: number): Promise<string> {
|
|
113
|
+
async function buildDescription(cwd: string, maxConcurrency: number, isolationEnabled: boolean): Promise<string> {
|
|
106
114
|
const { agents } = await discoverAgents(cwd);
|
|
107
115
|
|
|
108
116
|
return renderPromptTemplate(taskDescriptionTemplate, {
|
|
109
117
|
agents,
|
|
110
118
|
MAX_CONCURRENCY: maxConcurrency,
|
|
119
|
+
isolationEnabled,
|
|
111
120
|
});
|
|
112
121
|
}
|
|
113
122
|
|
|
@@ -121,20 +130,21 @@ async function buildDescription(cwd: string, maxConcurrency: number): Promise<st
|
|
|
121
130
|
* Requires async initialization to discover available agents.
|
|
122
131
|
* Use `TaskTool.create(session)` to instantiate.
|
|
123
132
|
*/
|
|
124
|
-
export class TaskTool implements AgentTool<
|
|
133
|
+
export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
125
134
|
public readonly name = "task";
|
|
126
135
|
public readonly label = "Task";
|
|
127
|
-
public readonly
|
|
128
|
-
public readonly parameters = taskSchema;
|
|
136
|
+
public readonly parameters: TaskSchema;
|
|
129
137
|
public readonly renderCall = renderCall;
|
|
130
138
|
public readonly renderResult = renderResult;
|
|
131
139
|
|
|
132
|
-
private readonly session: ToolSession;
|
|
133
140
|
private readonly blockedAgent: string | undefined;
|
|
134
141
|
|
|
135
|
-
private constructor(
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
private constructor(
|
|
143
|
+
private readonly session: ToolSession,
|
|
144
|
+
public readonly description: string,
|
|
145
|
+
isolationEnabled: boolean,
|
|
146
|
+
) {
|
|
147
|
+
this.parameters = isolationEnabled ? taskSchema : taskSchemaNoIsolation;
|
|
138
148
|
this.blockedAgent = $env.PI_BLOCKED_AGENT;
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -143,8 +153,9 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
143
153
|
*/
|
|
144
154
|
public static async create(session: ToolSession): Promise<TaskTool> {
|
|
145
155
|
const maxConcurrency = session.settings.get("task.maxConcurrency");
|
|
146
|
-
const
|
|
147
|
-
|
|
156
|
+
const isolationEnabled = session.settings.get("task.isolation.enabled");
|
|
157
|
+
const description = await buildDescription(session.cwd, maxConcurrency, isolationEnabled);
|
|
158
|
+
return new TaskTool(session, description, isolationEnabled);
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
public async execute(
|
|
@@ -155,11 +166,29 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
155
166
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
156
167
|
const startTime = Date.now();
|
|
157
168
|
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
158
|
-
const { agent: agentName, context, schema: outputSchema
|
|
159
|
-
const
|
|
169
|
+
const { agent: agentName, context, schema: outputSchema } = params;
|
|
170
|
+
const isolationEnabled = this.session.settings.get("task.isolation.enabled");
|
|
171
|
+
const isolationRequested = "isolated" in params ? params.isolated === true : false;
|
|
172
|
+
const isIsolated = isolationEnabled && isolationRequested;
|
|
160
173
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
161
174
|
const taskDepth = this.session.taskDepth ?? 0;
|
|
162
175
|
|
|
176
|
+
if (!isolationEnabled && "isolated" in params) {
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: "Task isolation is disabled. Remove the isolated argument to run subagents.",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
details: {
|
|
185
|
+
projectAgentsDir,
|
|
186
|
+
results: [],
|
|
187
|
+
totalDurationMs: 0,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
163
192
|
// Validate agent exists
|
|
164
193
|
const agent = getAgent(agents, agentName);
|
|
165
194
|
if (!agent) {
|
|
@@ -426,7 +455,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
426
455
|
agentSource: agent.source,
|
|
427
456
|
status: "pending",
|
|
428
457
|
task: t.task,
|
|
429
|
-
args: t.args,
|
|
430
458
|
recentTools: [],
|
|
431
459
|
recentOutput: [],
|
|
432
460
|
toolCount: 0,
|
|
@@ -447,7 +475,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
447
475
|
description: task.description,
|
|
448
476
|
index,
|
|
449
477
|
id: task.id,
|
|
450
|
-
context: undefined, // Already prepended above
|
|
451
478
|
taskDepth,
|
|
452
479
|
modelOverride,
|
|
453
480
|
thinkingLevel: thinkingLevelOverride,
|
|
@@ -462,7 +489,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
462
489
|
onProgress: progress => {
|
|
463
490
|
progressMap.set(index, {
|
|
464
491
|
...structuredClone(progress),
|
|
465
|
-
args: tasksWithSkills[index]?.args,
|
|
466
492
|
});
|
|
467
493
|
emitProgress();
|
|
468
494
|
},
|
|
@@ -493,7 +519,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
493
519
|
description: task.description,
|
|
494
520
|
index,
|
|
495
521
|
id: task.id,
|
|
496
|
-
context: undefined, // Already prepended above
|
|
497
522
|
taskDepth,
|
|
498
523
|
modelOverride,
|
|
499
524
|
thinkingLevel: thinkingLevelOverride,
|
|
@@ -508,7 +533,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
508
533
|
onProgress: progress => {
|
|
509
534
|
progressMap.set(index, {
|
|
510
535
|
...structuredClone(progress),
|
|
511
|
-
args: tasksWithSkills[index]?.args,
|
|
512
536
|
});
|
|
513
537
|
emitProgress();
|
|
514
538
|
},
|
|
@@ -564,10 +588,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
564
588
|
// Fill in skipped tasks (undefined entries from abort) with placeholder results
|
|
565
589
|
const results: SingleResult[] = partialResults.map((result, index) => {
|
|
566
590
|
if (result !== undefined) {
|
|
567
|
-
return
|
|
568
|
-
...result,
|
|
569
|
-
args: tasksWithSkills[index]?.args,
|
|
570
|
-
};
|
|
591
|
+
return result;
|
|
571
592
|
}
|
|
572
593
|
const task = tasksWithSkills[index];
|
|
573
594
|
return {
|
|
@@ -576,7 +597,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
576
597
|
agent: agentName,
|
|
577
598
|
agentSource: agent.source,
|
|
578
599
|
task: task.task,
|
|
579
|
-
args: task.args,
|
|
580
600
|
description: task.description,
|
|
581
601
|
exitCode: 1,
|
|
582
602
|
output: "",
|
package/src/task/render.ts
CHANGED
|
@@ -354,25 +354,27 @@ function renderOutputSection(
|
|
|
354
354
|
return lines;
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
function renderTaskSection(
|
|
358
|
+
task: string,
|
|
359
|
+
continuePrefix: string,
|
|
360
|
+
expanded: boolean,
|
|
361
|
+
theme: Theme,
|
|
362
|
+
maxExpanded = 20,
|
|
363
|
+
): string[] {
|
|
364
|
+
const lines: string[] = [];
|
|
365
|
+
const trimmed = task.trimEnd();
|
|
366
|
+
if (!expanded || !trimmed) return lines;
|
|
360
367
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
368
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Task")}`);
|
|
369
|
+
const taskLines = trimmed.split("\n");
|
|
370
|
+
for (const line of taskLines.slice(0, maxExpanded)) {
|
|
371
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", truncateToWidth(line, 70))}`);
|
|
372
|
+
}
|
|
373
|
+
if (taskLines.length > maxExpanded) {
|
|
374
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", formatMoreItems(taskLines.length - maxExpanded, "line"))}`);
|
|
367
375
|
}
|
|
368
376
|
|
|
369
|
-
|
|
370
|
-
return `Args: ${pairs.join(", ")}`;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/** Convert snake_case or kebab-case to Title Case */
|
|
374
|
-
function humanizeKey(key: string): string {
|
|
375
|
-
return key.replace(/[-_]/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
|
377
|
+
return lines;
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
function formatScalarInline(value: unknown, maxLen: number, _theme: Theme): string {
|
|
@@ -428,47 +430,6 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
|
|
|
428
430
|
return `Output: ${pairs.join(", ")}`;
|
|
429
431
|
}
|
|
430
432
|
|
|
431
|
-
function renderArgsSection(
|
|
432
|
-
args: Record<string, string> | undefined,
|
|
433
|
-
continuePrefix: string,
|
|
434
|
-
expanded: boolean,
|
|
435
|
-
theme: Theme,
|
|
436
|
-
): string[] {
|
|
437
|
-
if (!args) return [];
|
|
438
|
-
// Filter out auto-injected id and description
|
|
439
|
-
const filteredArgs = Object.fromEntries(
|
|
440
|
-
Object.entries(args).filter(([key]) => key !== "id" && key !== "description"),
|
|
441
|
-
);
|
|
442
|
-
if (Object.keys(filteredArgs).length === 0) return [];
|
|
443
|
-
const lines: string[] = [];
|
|
444
|
-
const entries = Object.entries(filteredArgs);
|
|
445
|
-
|
|
446
|
-
if (!expanded) {
|
|
447
|
-
lines.push(`${continuePrefix}${theme.fg("dim", formatArgsInline(filteredArgs, theme))}`);
|
|
448
|
-
return lines;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Single variable: show inline as "Key: value" without tree structure
|
|
452
|
-
if (entries.length === 1) {
|
|
453
|
-
const [key, value] = entries[0];
|
|
454
|
-
const humanKey = humanizeKey(key);
|
|
455
|
-
const displayValue = `"${truncateToWidth(value, 60)}"`;
|
|
456
|
-
lines.push(`${continuePrefix}${theme.fg("dim", `${humanKey}: ${displayValue}`)}`);
|
|
457
|
-
return lines;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
lines.push(`${continuePrefix}${theme.fg("dim", "Args")}`);
|
|
461
|
-
const tree = renderJsonTreeLines(filteredArgs, theme, 4, 16);
|
|
462
|
-
for (const line of tree.lines) {
|
|
463
|
-
lines.push(`${continuePrefix} ${line}`);
|
|
464
|
-
}
|
|
465
|
-
if (tree.truncated) {
|
|
466
|
-
lines.push(`${continuePrefix} ${theme.fg("dim", "…")}`);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return lines;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
433
|
/**
|
|
473
434
|
* Render the tool call arguments.
|
|
474
435
|
*/
|
|
@@ -482,7 +443,7 @@ export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
|
482
443
|
const branch = theme.fg("dim", theme.tree.branch);
|
|
483
444
|
const last = theme.fg("dim", theme.tree.last);
|
|
484
445
|
const vertical = theme.fg("dim", theme.tree.vertical);
|
|
485
|
-
const showIsolated = args.isolated === true;
|
|
446
|
+
const showIsolated = "isolated" in args && args.isolated === true;
|
|
486
447
|
|
|
487
448
|
if (hasContext) {
|
|
488
449
|
lines.push(` ${branch} ${theme.fg("dim", "Context")}`);
|
|
@@ -556,7 +517,7 @@ function renderAgentProgress(
|
|
|
556
517
|
|
|
557
518
|
lines.push(statusLine);
|
|
558
519
|
|
|
559
|
-
lines.push(...
|
|
520
|
+
lines.push(...renderTaskSection(progress.task, continuePrefix, expanded, theme));
|
|
560
521
|
|
|
561
522
|
// Current tool (if running) or most recent completed tool
|
|
562
523
|
if (progress.status === "running") {
|
|
@@ -778,7 +739,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
778
739
|
}
|
|
779
740
|
|
|
780
741
|
lines.push(statusLine);
|
|
781
|
-
|
|
742
|
+
|
|
743
|
+
lines.push(...renderTaskSection(result.task, continuePrefix, expanded, theme));
|
|
782
744
|
|
|
783
745
|
// Check for review result (submit_result with review schema + report_finding)
|
|
784
746
|
const completeData = result.extractedToolData?.submit_result as Array<{ data: unknown }> | undefined;
|
|
@@ -810,6 +772,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
810
772
|
|
|
811
773
|
// Check for extracted tool data with custom renderers (skip review tools)
|
|
812
774
|
let hasCustomRendering = false;
|
|
775
|
+
const deferredToolLines: string[] = [];
|
|
813
776
|
if (result.extractedToolData) {
|
|
814
777
|
for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
|
|
815
778
|
// Skip review tools - handled above
|
|
@@ -817,20 +780,24 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
817
780
|
|
|
818
781
|
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
819
782
|
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
|
820
|
-
|
|
783
|
+
const isTaskTool = toolName === "task";
|
|
821
784
|
const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
|
|
822
|
-
|
|
785
|
+
const target = isTaskTool ? deferredToolLines : lines;
|
|
786
|
+
if (!isTaskTool) {
|
|
787
|
+
hasCustomRendering = true;
|
|
788
|
+
target.push(`${continuePrefix}${theme.fg("dim", `Tool: ${toolName}`)}`);
|
|
789
|
+
}
|
|
823
790
|
if (component instanceof Text) {
|
|
824
791
|
// Prefix each line with continuePrefix
|
|
825
792
|
const text = component.getText();
|
|
826
793
|
for (const line of text.split("\n")) {
|
|
827
|
-
|
|
794
|
+
target.push(`${continuePrefix}${line}`);
|
|
828
795
|
}
|
|
829
796
|
} else if (component instanceof Container) {
|
|
830
797
|
// For containers, render each child
|
|
831
798
|
for (const child of (component as Container).children) {
|
|
832
799
|
if (child instanceof Text) {
|
|
833
|
-
|
|
800
|
+
target.push(`${continuePrefix}${child.getText()}`);
|
|
834
801
|
}
|
|
835
802
|
}
|
|
836
803
|
}
|
|
@@ -854,6 +821,10 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
854
821
|
);
|
|
855
822
|
}
|
|
856
823
|
|
|
824
|
+
if (deferredToolLines.length > 0) {
|
|
825
|
+
lines.push(...deferredToolLines);
|
|
826
|
+
}
|
|
827
|
+
|
|
857
828
|
if (result.patchPath && !aborted && result.exitCode === 0) {
|
|
858
829
|
lines.push(`${continuePrefix}${theme.fg("dim", `Patch: ${result.patchPath}`)}`);
|
|
859
830
|
}
|
|
@@ -944,6 +915,38 @@ export function renderResult(
|
|
|
944
915
|
return new Text(indented.join("\n"), 0, 0);
|
|
945
916
|
}
|
|
946
917
|
|
|
918
|
+
function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
|
919
|
+
return (
|
|
920
|
+
Boolean(value) &&
|
|
921
|
+
typeof value === "object" &&
|
|
922
|
+
"results" in (value as TaskToolDetails) &&
|
|
923
|
+
Array.isArray((value as TaskToolDetails).results)
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function renderNestedTaskResults(detailsList: TaskToolDetails[], expanded: boolean, theme: Theme): string[] {
|
|
928
|
+
const lines: string[] = [];
|
|
929
|
+
for (const details of detailsList) {
|
|
930
|
+
if (!details.results || details.results.length === 0) continue;
|
|
931
|
+
details.results.forEach((result, index) => {
|
|
932
|
+
const isLast = index === details.results.length - 1;
|
|
933
|
+
lines.push(...renderAgentResult(result, isLast, expanded, theme));
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
return lines;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
subprocessToolRegistry.register<TaskToolDetails>("task", {
|
|
940
|
+
extractData: event => {
|
|
941
|
+
const details = event.result?.details;
|
|
942
|
+
return isTaskToolDetails(details) ? details : undefined;
|
|
943
|
+
},
|
|
944
|
+
renderFinal: (allData, theme, expanded) => {
|
|
945
|
+
const lines = renderNestedTaskResults(allData, expanded, theme);
|
|
946
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
|
|
947
950
|
export const taskToolRenderer = {
|
|
948
951
|
renderCall,
|
|
949
952
|
renderResult,
|
package/src/task/template.ts
CHANGED
|
@@ -1,47 +1,30 @@
|
|
|
1
|
+
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
2
|
+
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
1
3
|
import type { TaskItem } from "./types";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
interface RenderResult {
|
|
6
|
+
/** Full task text sent to the subagent */
|
|
4
7
|
task: string;
|
|
5
|
-
args: Record<string, string>;
|
|
6
8
|
id: string;
|
|
7
9
|
description: string;
|
|
8
10
|
skills?: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function renderTemplate(template: string, task: TaskItem): RenderResult {
|
|
12
|
-
const { id, description, args, skills } = task;
|
|
13
|
-
|
|
14
|
-
let usedPlaceholder = false;
|
|
15
|
-
const unknownArguments: string[] = [];
|
|
16
|
-
let renderedTask = template.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => {
|
|
17
|
-
const value = args?.[key];
|
|
18
|
-
if (value) {
|
|
19
|
-
usedPlaceholder = true;
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
switch (key) {
|
|
23
|
-
case "id":
|
|
24
|
-
usedPlaceholder = true;
|
|
25
|
-
return id;
|
|
26
|
-
case "description":
|
|
27
|
-
usedPlaceholder = true;
|
|
28
|
-
return description;
|
|
29
|
-
default:
|
|
30
|
-
unknownArguments.push(key);
|
|
31
|
-
return `{{${key}}}`;
|
|
32
|
-
}
|
|
33
|
-
});
|
|
11
|
+
}
|
|
34
12
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Build the full task text from shared context and per-task assignment.
|
|
15
|
+
*
|
|
16
|
+
* If context is provided, it is prepended with a separator.
|
|
17
|
+
*/
|
|
18
|
+
export function renderTemplate(context: string | undefined, task: TaskItem): RenderResult {
|
|
19
|
+
let { id, description, assignment, skills } = task;
|
|
20
|
+
assignment = assignment.trim();
|
|
21
|
+
context = context?.trim();
|
|
38
22
|
|
|
39
|
-
if (!
|
|
40
|
-
|
|
23
|
+
if (!context || !assignment) {
|
|
24
|
+
return { task: assignment || context!, id, description, skills };
|
|
41
25
|
}
|
|
42
26
|
return {
|
|
43
|
-
task:
|
|
44
|
-
args: { id, description, ...args },
|
|
27
|
+
task: renderPromptTemplate(subagentUserPromptTemplate, { context, assignment }),
|
|
45
28
|
id,
|
|
46
29
|
description,
|
|
47
30
|
skills,
|
package/src/task/types.ts
CHANGED
|
@@ -33,36 +33,65 @@ export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
|
|
|
33
33
|
/** Single task item for parallel execution */
|
|
34
34
|
export const taskItemSchema = Type.Object({
|
|
35
35
|
id: Type.String({
|
|
36
|
-
description: "
|
|
36
|
+
description: "CamelCase identifier, max 32 chars",
|
|
37
37
|
maxLength: 32,
|
|
38
38
|
}),
|
|
39
|
-
description: Type.String({
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
description: Type.String({
|
|
40
|
+
description: "Short one-liner for UI display only — not seen by the subagent",
|
|
41
|
+
}),
|
|
42
|
+
assignment: Type.String({
|
|
43
|
+
description:
|
|
44
|
+
"Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure. Only include per-task deltas — shared background belongs in `context`.",
|
|
45
|
+
}),
|
|
45
46
|
skills: Type.Optional(
|
|
46
47
|
Type.Array(Type.String(), {
|
|
47
|
-
description: "Skill names to preload into the subagent
|
|
48
|
+
description: "Skill names to preload into the subagent. Use only where it changes correctness.",
|
|
48
49
|
}),
|
|
49
50
|
),
|
|
50
51
|
});
|
|
51
|
-
|
|
52
52
|
export type TaskItem = Static<typeof taskItemSchema>;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
const createTaskSchema = (options: { isolationEnabled: boolean }) => {
|
|
55
|
+
const properties = {
|
|
56
|
+
agent: Type.String({ description: "Agent type for all tasks in this batch" }),
|
|
57
|
+
context: Type.Optional(
|
|
58
|
+
Type.String({
|
|
59
|
+
description:
|
|
60
|
+
"Shared background prepended to every task's assignment. Put goal, non-goals, constraints, conventions, reference paths, API contracts, and global acceptance commands here once — instead of duplicating across assignments.",
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
schema: Type.Optional(
|
|
64
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
65
|
+
description:
|
|
66
|
+
"JTD schema defining expected response structure. Use typed properties. Output format belongs here — never in context or assignment.",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
tasks: Type.Array(taskItemSchema, {
|
|
70
|
+
description:
|
|
71
|
+
"Tasks to execute in parallel. Each must be small-scoped (3-5 files max) and self-contained given context + assignment.",
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (options.isolationEnabled) {
|
|
76
|
+
return Type.Object({
|
|
77
|
+
...properties,
|
|
78
|
+
isolated: Type.Optional(
|
|
79
|
+
Type.Boolean({
|
|
80
|
+
description: "Run in isolated git worktree; returns patches. Use when tasks edit overlapping files.",
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Type.Object(properties);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const taskSchema = createTaskSchema({ isolationEnabled: true });
|
|
90
|
+
export const taskSchemaNoIsolation = createTaskSchema({ isolationEnabled: false });
|
|
91
|
+
|
|
92
|
+
export type TaskSchema = typeof taskSchema | typeof taskSchemaNoIsolation;
|
|
64
93
|
|
|
65
|
-
export type TaskParams = Static<
|
|
94
|
+
export type TaskParams = Static<TaskSchema>;
|
|
66
95
|
|
|
67
96
|
/** A code review finding reported by the reviewer agent */
|
|
68
97
|
export interface ReviewFinding {
|
|
@@ -110,7 +139,6 @@ export interface AgentProgress {
|
|
|
110
139
|
agentSource: AgentSource;
|
|
111
140
|
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
112
141
|
task: string;
|
|
113
|
-
args?: Record<string, string>;
|
|
114
142
|
description?: string;
|
|
115
143
|
currentTool?: string;
|
|
116
144
|
currentToolArgs?: string;
|
|
@@ -132,7 +160,6 @@ export interface SingleResult {
|
|
|
132
160
|
agent: string;
|
|
133
161
|
agentSource: AgentSource;
|
|
134
162
|
task: string;
|
|
135
|
-
args?: Record<string, string>;
|
|
136
163
|
description?: string;
|
|
137
164
|
exitCode: number;
|
|
138
165
|
output: string;
|
package/src/tools/ask.ts
CHANGED
|
@@ -254,10 +254,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
254
254
|
public readonly label = "Ask";
|
|
255
255
|
public readonly description: string;
|
|
256
256
|
public readonly parameters = askSchema;
|
|
257
|
-
private readonly session: ToolSession;
|
|
258
257
|
|
|
259
|
-
constructor(session: ToolSession) {
|
|
260
|
-
this.session = session;
|
|
258
|
+
constructor(private readonly session: ToolSession) {
|
|
261
259
|
this.description = renderPromptTemplate(askDescription);
|
|
262
260
|
}
|
|
263
261
|
|
package/src/tools/bash.ts
CHANGED
|
@@ -49,10 +49,7 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
|
|
|
49
49
|
public readonly parameters = bashSchema;
|
|
50
50
|
public readonly concurrency = "exclusive";
|
|
51
51
|
|
|
52
|
-
private readonly session: ToolSession
|
|
53
|
-
|
|
54
|
-
constructor(session: ToolSession) {
|
|
55
|
-
this.session = session;
|
|
52
|
+
constructor(private readonly session: ToolSession) {
|
|
56
53
|
this.description = renderPromptTemplate(bashDescription);
|
|
57
54
|
}
|
|
58
55
|
|
package/src/tools/browser.ts
CHANGED
|
@@ -257,7 +257,6 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
257
257
|
public readonly label = "Puppeteer";
|
|
258
258
|
public readonly description: string;
|
|
259
259
|
public readonly parameters = browserSchema;
|
|
260
|
-
private readonly session: ToolSession;
|
|
261
260
|
private browser: Browser | null = null;
|
|
262
261
|
private page: Page | null = null;
|
|
263
262
|
private currentHeadless: boolean | null = null;
|
|
@@ -267,8 +266,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
267
266
|
private readonly elementCache = new Map<number, ElementHandle>();
|
|
268
267
|
private readonly patchedClients = new WeakSet<object>();
|
|
269
268
|
|
|
270
|
-
constructor(session: ToolSession) {
|
|
271
|
-
this.session = session;
|
|
269
|
+
constructor(private readonly session: ToolSession) {
|
|
272
270
|
this.description = renderPromptTemplate(browserDescription, {});
|
|
273
271
|
}
|
|
274
272
|
|
|
@@ -1033,10 +1031,10 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
1033
1031
|
const html = (await untilAborted(signal, () => page.content())) as string;
|
|
1034
1032
|
const url = page.url();
|
|
1035
1033
|
const virtualConsole = new VirtualConsole();
|
|
1036
|
-
virtualConsole.on("jsdomError",
|
|
1037
|
-
if (
|
|
1034
|
+
virtualConsole.on("jsdomError", err => {
|
|
1035
|
+
if (err?.message?.includes("Could not parse CSS stylesheet")) return;
|
|
1038
1036
|
logger.debug("JSDOM error during readable extraction", {
|
|
1039
|
-
error:
|
|
1037
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1040
1038
|
});
|
|
1041
1039
|
});
|
|
1042
1040
|
const dom = new JSDOM(html, { url, virtualConsole });
|
|
@@ -1094,7 +1092,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
1094
1092
|
}
|
|
1095
1093
|
|
|
1096
1094
|
const mimeType = imageFormat === "png" ? "image/png" : "image/jpeg";
|
|
1097
|
-
const base64 = buffer.
|
|
1095
|
+
const base64 = buffer.toBase64();
|
|
1098
1096
|
let savedPath: string | undefined;
|
|
1099
1097
|
if (params.path) {
|
|
1100
1098
|
const resolved = resolveToCwd(params.path, this.session.cwd);
|