@oh-my-pi/pi-coding-agent 6.9.0 → 6.9.69
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 +41 -0
- package/package.json +6 -5
- package/src/cli/stats-cli.ts +191 -0
- package/src/core/agent-session.ts +103 -1
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/runner.ts +31 -0
- package/src/core/extensions/types.ts +24 -0
- package/src/core/messages.ts +48 -0
- package/src/core/session-manager.ts +10 -1
- package/src/core/tools/bash.ts +5 -7
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/patch/applicator.ts +115 -17
- package/src/core/tools/patch/index.ts +1 -1
- package/src/core/tools/patch/normalize.ts +185 -10
- package/src/core/tools/python.ts +444 -86
- package/src/core/tools/task/executor.ts +2 -6
- package/src/core/tools/task/index.ts +30 -12
- package/src/core/tools/task/render.ts +163 -30
- package/src/core/tools/task/template.ts +37 -0
- package/src/core/tools/task/types.ts +6 -2
- package/src/core/tools/task/worker.ts +1 -1
- package/src/index.ts +2 -0
- package/src/main.ts +12 -0
- package/src/modes/interactive/components/python-execution.ts +180 -0
- package/src/modes/interactive/components/welcome.ts +1 -0
- package/src/modes/interactive/controllers/command-controller.ts +46 -0
- package/src/modes/interactive/controllers/input-controller.ts +28 -1
- package/src/modes/interactive/interactive-mode.ts +10 -0
- package/src/modes/interactive/theme/dark.json +2 -9
- package/src/modes/interactive/theme/defaults/alabaster.json +2 -8
- package/src/modes/interactive/theme/defaults/amethyst.json +2 -9
- package/src/modes/interactive/theme/defaults/anthracite.json +2 -9
- package/src/modes/interactive/theme/defaults/basalt.json +89 -88
- package/src/modes/interactive/theme/defaults/birch.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-abyss.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-arctic.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-aurora.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-cavern.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-copper.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-cosmos.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-dracula.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-eclipse.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-ember.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-equinox.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-forest.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-github.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-lavender.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-lunar.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-midnight.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-monokai.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-nebula.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-nord.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-ocean.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-one.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-rainforest.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-reef.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-retro.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-sakura.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-slate.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-solarized.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-solstice.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-starfall.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-sunset.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-swamp.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-taiga.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-terminal.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-tundra.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-twilight.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-volcanic.json +2 -8
- package/src/modes/interactive/theme/defaults/graphite.json +2 -9
- package/src/modes/interactive/theme/defaults/light-arctic.json +2 -1
- package/src/modes/interactive/theme/defaults/light-aurora-day.json +2 -8
- package/src/modes/interactive/theme/defaults/light-canyon.json +2 -8
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +2 -1
- package/src/modes/interactive/theme/defaults/light-cirrus.json +2 -8
- package/src/modes/interactive/theme/defaults/light-coral.json +3 -2
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +2 -9
- package/src/modes/interactive/theme/defaults/light-dawn.json +2 -8
- package/src/modes/interactive/theme/defaults/light-dunes.json +2 -8
- package/src/modes/interactive/theme/defaults/light-eucalyptus.json +3 -2
- package/src/modes/interactive/theme/defaults/light-forest.json +2 -9
- package/src/modes/interactive/theme/defaults/light-frost.json +3 -2
- package/src/modes/interactive/theme/defaults/light-github.json +2 -1
- package/src/modes/interactive/theme/defaults/light-glacier.json +2 -8
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +2 -9
- package/src/modes/interactive/theme/defaults/light-haze.json +2 -8
- package/src/modes/interactive/theme/defaults/light-honeycomb.json +3 -2
- package/src/modes/interactive/theme/defaults/light-lagoon.json +2 -8
- package/src/modes/interactive/theme/defaults/light-lavender.json +3 -2
- package/src/modes/interactive/theme/defaults/light-meadow.json +2 -8
- package/src/modes/interactive/theme/defaults/light-mint.json +3 -2
- package/src/modes/interactive/theme/defaults/light-monochrome.json +2 -1
- package/src/modes/interactive/theme/defaults/light-ocean.json +2 -9
- package/src/modes/interactive/theme/defaults/light-one.json +2 -8
- package/src/modes/interactive/theme/defaults/light-opal.json +2 -8
- package/src/modes/interactive/theme/defaults/light-orchard.json +2 -8
- package/src/modes/interactive/theme/defaults/light-paper.json +3 -2
- package/src/modes/interactive/theme/defaults/light-prism.json +2 -8
- package/src/modes/interactive/theme/defaults/light-retro.json +2 -9
- package/src/modes/interactive/theme/defaults/light-sand.json +3 -2
- package/src/modes/interactive/theme/defaults/light-savanna.json +2 -8
- package/src/modes/interactive/theme/defaults/light-solarized.json +2 -1
- package/src/modes/interactive/theme/defaults/light-soleil.json +2 -8
- package/src/modes/interactive/theme/defaults/light-sunset.json +2 -9
- package/src/modes/interactive/theme/defaults/light-synthwave.json +2 -9
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +2 -9
- package/src/modes/interactive/theme/defaults/light-wetland.json +2 -8
- package/src/modes/interactive/theme/defaults/light-zenith.json +2 -8
- package/src/modes/interactive/theme/defaults/limestone.json +2 -8
- package/src/modes/interactive/theme/defaults/mahogany.json +2 -9
- package/src/modes/interactive/theme/defaults/marble.json +2 -8
- package/src/modes/interactive/theme/defaults/obsidian.json +89 -88
- package/src/modes/interactive/theme/defaults/onyx.json +89 -88
- package/src/modes/interactive/theme/defaults/pearl.json +2 -8
- package/src/modes/interactive/theme/defaults/porcelain.json +89 -88
- package/src/modes/interactive/theme/defaults/quartz.json +2 -8
- package/src/modes/interactive/theme/defaults/sandstone.json +2 -8
- package/src/modes/interactive/theme/defaults/titanium.json +88 -87
- package/src/modes/interactive/theme/light.json +2 -8
- package/src/modes/interactive/theme/theme-schema.json +5 -0
- package/src/modes/interactive/theme/theme.ts +7 -0
- package/src/modes/interactive/types.ts +5 -0
- package/src/modes/interactive/utils/ui-helpers.ts +20 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/python.md +40 -2
- package/src/prompts/tools/task.md +8 -13
|
@@ -28,12 +28,14 @@ import { discoverAgents, getAgent } from "./discovery";
|
|
|
28
28
|
import { runSubprocess } from "./executor";
|
|
29
29
|
import { mapWithConcurrencyLimit } from "./parallel";
|
|
30
30
|
import { renderCall, renderResult } from "./render";
|
|
31
|
+
import { renderTemplate, validateTaskTemplate } from "./template";
|
|
31
32
|
import {
|
|
32
33
|
type AgentProgress,
|
|
33
34
|
MAX_AGENTS_IN_DESCRIPTION,
|
|
34
35
|
MAX_CONCURRENCY,
|
|
35
36
|
MAX_PARALLEL_TASKS,
|
|
36
37
|
type SingleResult,
|
|
38
|
+
type TaskParams,
|
|
37
39
|
type TaskToolDetails,
|
|
38
40
|
taskSchema,
|
|
39
41
|
} from "./types";
|
|
@@ -112,14 +114,6 @@ async function buildDescription(cwd: string): Promise<string> {
|
|
|
112
114
|
// Tool Class
|
|
113
115
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
114
116
|
|
|
115
|
-
type TaskParams = {
|
|
116
|
-
agent: string;
|
|
117
|
-
context?: string;
|
|
118
|
-
model?: string;
|
|
119
|
-
output?: unknown;
|
|
120
|
-
tasks: Array<{ id: string; task: string; description: string }>;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
117
|
/**
|
|
124
118
|
* Task tool - Delegate tasks to specialized agents.
|
|
125
119
|
*
|
|
@@ -201,7 +195,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
201
195
|
content: [
|
|
202
196
|
{
|
|
203
197
|
type: "text",
|
|
204
|
-
text: `No tasks provided. Use: { agent, context, tasks: [{id,
|
|
198
|
+
text: `No tasks provided. Use: { agent, context, tasks: [{id, description, vars}, ...] }`,
|
|
205
199
|
},
|
|
206
200
|
],
|
|
207
201
|
details: {
|
|
@@ -277,6 +271,18 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
277
271
|
};
|
|
278
272
|
}
|
|
279
273
|
|
|
274
|
+
const templateError = validateTaskTemplate(context, tasks);
|
|
275
|
+
if (templateError) {
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: "text", text: templateError }],
|
|
278
|
+
details: {
|
|
279
|
+
projectAgentsDir,
|
|
280
|
+
results: [],
|
|
281
|
+
totalDurationMs: 0,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
280
286
|
// Derive artifacts directory
|
|
281
287
|
const sessionFile = this.session.getSessionFile();
|
|
282
288
|
const artifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
@@ -341,10 +347,12 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
// Build full prompts with context prepended
|
|
350
|
+
const contextTemplate = context ?? "";
|
|
344
351
|
const tasksWithContext = tasks.map((t) => ({
|
|
345
|
-
task:
|
|
352
|
+
task: renderTemplate(contextTemplate, t.vars),
|
|
346
353
|
description: t.description,
|
|
347
354
|
taskId: t.id,
|
|
355
|
+
vars: t.vars,
|
|
348
356
|
}));
|
|
349
357
|
|
|
350
358
|
// Initialize progress for all tasks
|
|
@@ -357,6 +365,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
357
365
|
agentSource: agent.source,
|
|
358
366
|
status: "pending",
|
|
359
367
|
task: t.task,
|
|
368
|
+
vars: t.vars,
|
|
360
369
|
recentTools: [],
|
|
361
370
|
recentOutput: [],
|
|
362
371
|
toolCount: 0,
|
|
@@ -391,7 +400,10 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
391
400
|
signal,
|
|
392
401
|
eventBus: undefined,
|
|
393
402
|
onProgress: (progress) => {
|
|
394
|
-
progressMap.set(index,
|
|
403
|
+
progressMap.set(index, {
|
|
404
|
+
...structuredClone(progress),
|
|
405
|
+
vars: tasksWithContext[index]?.vars,
|
|
406
|
+
});
|
|
395
407
|
emitProgress();
|
|
396
408
|
},
|
|
397
409
|
authStorage: this.session.authStorage,
|
|
@@ -405,7 +417,12 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
405
417
|
|
|
406
418
|
// Fill in skipped tasks (undefined entries from abort) with placeholder results
|
|
407
419
|
const results: SingleResult[] = partialResults.map((result, index) => {
|
|
408
|
-
if (result !== undefined)
|
|
420
|
+
if (result !== undefined) {
|
|
421
|
+
return {
|
|
422
|
+
...result,
|
|
423
|
+
vars: tasksWithContext[index]?.vars,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
409
426
|
const task = tasksWithContext[index];
|
|
410
427
|
return {
|
|
411
428
|
index,
|
|
@@ -413,6 +430,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
413
430
|
agent: agentName,
|
|
414
431
|
agentSource: agent.source,
|
|
415
432
|
task: task.task,
|
|
433
|
+
vars: task.vars,
|
|
416
434
|
description: task.description,
|
|
417
435
|
exitCode: 1,
|
|
418
436
|
output: "",
|
|
@@ -67,9 +67,12 @@ function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): s
|
|
|
67
67
|
return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function formatJsonScalar(value: unknown): string {
|
|
70
|
+
function formatJsonScalar(value: unknown, theme: Theme): string {
|
|
71
71
|
if (value === null) return "null";
|
|
72
|
-
if (typeof value === "string")
|
|
72
|
+
if (typeof value === "string") {
|
|
73
|
+
const trimmed = truncate(value, 70, theme.format.ellipsis);
|
|
74
|
+
return `"${trimmed}"`;
|
|
75
|
+
}
|
|
73
76
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
74
77
|
return "";
|
|
75
78
|
}
|
|
@@ -108,7 +111,7 @@ function renderJsonTreeLines(
|
|
|
108
111
|
|
|
109
112
|
const connector = isLast ? theme.tree.last : theme.tree.branch;
|
|
110
113
|
const prefix = `${buildTreePrefix(ancestors, theme)}${theme.fg("dim", connector)} `;
|
|
111
|
-
const scalar = formatJsonScalar(val);
|
|
114
|
+
const scalar = formatJsonScalar(val, theme);
|
|
112
115
|
|
|
113
116
|
if (scalar) {
|
|
114
117
|
const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
|
|
@@ -186,7 +189,33 @@ function renderJsonTreeLines(
|
|
|
186
189
|
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", String(val))}`);
|
|
187
190
|
};
|
|
188
191
|
|
|
189
|
-
|
|
192
|
+
const renderRoot = (val: unknown) => {
|
|
193
|
+
if (Array.isArray(val)) {
|
|
194
|
+
for (let i = 0; i < val.length; i++) {
|
|
195
|
+
renderNode(val[i], `[${i}]`, [], i === val.length - 1, 1);
|
|
196
|
+
if (lines.length >= maxLines) {
|
|
197
|
+
truncated = true;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (val && typeof val === "object") {
|
|
204
|
+
const entries = Object.entries(val as Record<string, unknown>);
|
|
205
|
+
for (let i = 0; i < entries.length; i++) {
|
|
206
|
+
const [childKey, child] = entries[i];
|
|
207
|
+
renderNode(child, childKey, [], i === entries.length - 1, 1);
|
|
208
|
+
if (lines.length >= maxLines) {
|
|
209
|
+
truncated = true;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
renderNode(val, undefined, [], true, 0);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
renderRoot(value);
|
|
190
219
|
|
|
191
220
|
return { lines, truncated };
|
|
192
221
|
}
|
|
@@ -203,11 +232,18 @@ function renderOutputSection(
|
|
|
203
232
|
const trimmedOutput = output.trim();
|
|
204
233
|
if (!trimmedOutput) return lines;
|
|
205
234
|
|
|
206
|
-
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
207
|
-
|
|
208
235
|
if (trimmedOutput.startsWith("{") || trimmedOutput.startsWith("[")) {
|
|
209
236
|
try {
|
|
210
237
|
const parsed = JSON.parse(trimmedOutput);
|
|
238
|
+
|
|
239
|
+
// Collapsed: inline format like Vars
|
|
240
|
+
if (!expanded) {
|
|
241
|
+
lines.push(`${continuePrefix}${theme.fg("dim", formatOutputInline(parsed, theme))}`);
|
|
242
|
+
return lines;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Expanded: tree format
|
|
246
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
211
247
|
const tree = renderJsonTreeLines(parsed, theme, expanded ? 6 : 2, expanded ? 24 : 6);
|
|
212
248
|
if (tree.lines.length > 0) {
|
|
213
249
|
for (const line of tree.lines) {
|
|
@@ -223,6 +259,8 @@ function renderOutputSection(
|
|
|
223
259
|
}
|
|
224
260
|
}
|
|
225
261
|
|
|
262
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
263
|
+
|
|
226
264
|
const outputLines = output.split("\n").filter((line) => line.trim());
|
|
227
265
|
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
228
266
|
for (const line of outputLines.slice(0, previewCount)) {
|
|
@@ -238,6 +276,92 @@ function renderOutputSection(
|
|
|
238
276
|
return lines;
|
|
239
277
|
}
|
|
240
278
|
|
|
279
|
+
function formatVarsInline(vars: Record<string, string>, theme: Theme): string {
|
|
280
|
+
const entries = Object.entries(vars);
|
|
281
|
+
if (entries.length === 0) return "Vars: none";
|
|
282
|
+
const pairs = entries.map(([key, value]) => `${key}=${truncate(value, 24, theme.format.ellipsis)}`);
|
|
283
|
+
return `Vars: ${pairs.join(", ")}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function formatScalarInline(value: unknown, maxLen: number, theme: Theme): string {
|
|
287
|
+
if (value === null) return "null";
|
|
288
|
+
if (value === undefined) return "undefined";
|
|
289
|
+
if (typeof value === "boolean") return String(value);
|
|
290
|
+
if (typeof value === "number") return String(value);
|
|
291
|
+
if (typeof value === "string") return `"${truncate(value, maxLen, theme.format.ellipsis)}"`;
|
|
292
|
+
if (Array.isArray(value)) return `[${value.length} items]`;
|
|
293
|
+
if (typeof value === "object") {
|
|
294
|
+
const keys = Object.keys(value);
|
|
295
|
+
return `{${keys.length} keys}`;
|
|
296
|
+
}
|
|
297
|
+
return String(value);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string {
|
|
301
|
+
if (data === null || data === undefined) return "Output: none";
|
|
302
|
+
|
|
303
|
+
// For scalars, show directly
|
|
304
|
+
if (typeof data !== "object") {
|
|
305
|
+
return `Output: ${formatScalarInline(data, 60, theme)}`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// For arrays, show count and first element preview
|
|
309
|
+
if (Array.isArray(data)) {
|
|
310
|
+
if (data.length === 0) return "Output: []";
|
|
311
|
+
const preview = formatScalarInline(data[0], 40, theme);
|
|
312
|
+
return `Output: [${data.length} items] ${preview}${data.length > 1 ? theme.format.ellipsis : ""}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// For objects, show key=value pairs inline
|
|
316
|
+
const entries = Object.entries(data as Record<string, unknown>);
|
|
317
|
+
if (entries.length === 0) return "Output: {}";
|
|
318
|
+
|
|
319
|
+
const pairs: string[] = [];
|
|
320
|
+
let totalLen = "Output: ".length;
|
|
321
|
+
|
|
322
|
+
for (const [key, value] of entries) {
|
|
323
|
+
const valueStr = formatScalarInline(value, 24, theme);
|
|
324
|
+
const pairStr = `${key}=${valueStr}`;
|
|
325
|
+
const addLen = pairs.length > 0 ? pairStr.length + 2 : pairStr.length; // +2 for ", "
|
|
326
|
+
|
|
327
|
+
if (totalLen + addLen > maxWidth && pairs.length > 0) {
|
|
328
|
+
pairs.push(theme.format.ellipsis);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
pairs.push(pairStr);
|
|
333
|
+
totalLen += addLen;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return `Output: ${pairs.join(", ")}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function renderVarsSection(
|
|
340
|
+
vars: Record<string, string> | undefined,
|
|
341
|
+
continuePrefix: string,
|
|
342
|
+
expanded: boolean,
|
|
343
|
+
theme: Theme,
|
|
344
|
+
): string[] {
|
|
345
|
+
if (!vars || Object.keys(vars).length === 0) return [];
|
|
346
|
+
const lines: string[] = [];
|
|
347
|
+
|
|
348
|
+
if (!expanded) {
|
|
349
|
+
lines.push(`${continuePrefix}${theme.fg("dim", formatVarsInline(vars, theme))}`);
|
|
350
|
+
return lines;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Vars")}`);
|
|
354
|
+
const tree = renderJsonTreeLines(vars, theme, 4, 16);
|
|
355
|
+
for (const line of tree.lines) {
|
|
356
|
+
lines.push(`${continuePrefix} ${line}`);
|
|
357
|
+
}
|
|
358
|
+
if (tree.truncated) {
|
|
359
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", theme.format.ellipsis)}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return lines;
|
|
363
|
+
}
|
|
364
|
+
|
|
241
365
|
/**
|
|
242
366
|
* Render the tool call arguments.
|
|
243
367
|
*/
|
|
@@ -247,24 +371,29 @@ export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
|
247
371
|
theme.fg("dim", `${theme.format.bracketLeft}${args.agent}${theme.format.bracketRight}`),
|
|
248
372
|
);
|
|
249
373
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
374
|
+
const lines: string[] = [];
|
|
375
|
+
lines.push(`${label} ${agentTag}`);
|
|
376
|
+
|
|
377
|
+
const contextTemplate = args.context ?? "";
|
|
378
|
+
const context = contextTemplate.trim();
|
|
379
|
+
const hasContext = context.length > 0;
|
|
380
|
+
const branch = theme.fg("dim", theme.tree.branch);
|
|
381
|
+
const last = theme.fg("dim", theme.tree.last);
|
|
382
|
+
const vertical = theme.fg("dim", theme.tree.vertical);
|
|
383
|
+
|
|
384
|
+
if (hasContext) {
|
|
385
|
+
lines.push(` ${branch} ${theme.fg("dim", "Context")}`);
|
|
386
|
+
for (const line of context.split("\n")) {
|
|
387
|
+
const content = line ? theme.fg("muted", line) : "";
|
|
388
|
+
lines.push(` ${vertical} ${content}`);
|
|
389
|
+
}
|
|
390
|
+
lines.push(` ${last} ${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks.length} agents`)}`);
|
|
391
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
lines.push(`${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks.length} agents`)}`);
|
|
395
|
+
|
|
396
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
268
397
|
}
|
|
269
398
|
|
|
270
399
|
/**
|
|
@@ -278,8 +407,8 @@ function renderAgentProgress(
|
|
|
278
407
|
spinnerFrame?: number,
|
|
279
408
|
): string[] {
|
|
280
409
|
const lines: string[] = [];
|
|
281
|
-
const prefix = isLast ? theme.tree.last : theme.tree.branch;
|
|
282
|
-
const continuePrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
410
|
+
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
411
|
+
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
283
412
|
|
|
284
413
|
const icon = getStatusIcon(progress.status, theme, spinnerFrame);
|
|
285
414
|
const iconColor =
|
|
@@ -316,6 +445,8 @@ function renderAgentProgress(
|
|
|
316
445
|
|
|
317
446
|
lines.push(statusLine);
|
|
318
447
|
|
|
448
|
+
lines.push(...renderVarsSection(progress.vars, continuePrefix, expanded, theme));
|
|
449
|
+
|
|
319
450
|
// Current tool (if running) or most recent completed tool
|
|
320
451
|
if (progress.status === "running") {
|
|
321
452
|
if (progress.currentTool) {
|
|
@@ -498,8 +629,8 @@ function renderFindings(
|
|
|
498
629
|
*/
|
|
499
630
|
function renderAgentResult(result: SingleResult, isLast: boolean, expanded: boolean, theme: Theme): string[] {
|
|
500
631
|
const lines: string[] = [];
|
|
501
|
-
const prefix = isLast ? theme.tree.last : theme.tree.branch;
|
|
502
|
-
const continuePrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
632
|
+
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
633
|
+
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
503
634
|
|
|
504
635
|
const aborted = result.aborted ?? false;
|
|
505
636
|
const success = !aborted && result.exitCode === 0;
|
|
@@ -525,6 +656,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
525
656
|
}
|
|
526
657
|
|
|
527
658
|
lines.push(statusLine);
|
|
659
|
+
lines.push(...renderVarsSection(result.vars, continuePrefix, expanded, theme));
|
|
528
660
|
|
|
529
661
|
// Check for review result (complete with review schema + report_finding)
|
|
530
662
|
const completeData = result.extractedToolData?.complete as Array<{ data: unknown }> | undefined;
|
|
@@ -634,7 +766,7 @@ export function renderResult(
|
|
|
634
766
|
const abortedCount = details.results.filter((r) => r.aborted).length;
|
|
635
767
|
const successCount = details.results.filter((r) => !r.aborted && r.exitCode === 0).length;
|
|
636
768
|
const failCount = details.results.length - successCount - abortedCount;
|
|
637
|
-
let summary =
|
|
769
|
+
let summary = `${theme.fg("dim", "Total:")} `;
|
|
638
770
|
if (abortedCount > 0) {
|
|
639
771
|
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
640
772
|
if (successCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
@@ -656,7 +788,8 @@ export function renderResult(
|
|
|
656
788
|
return new Text(theme.fg("dim", "No results"), 0, 0);
|
|
657
789
|
}
|
|
658
790
|
|
|
659
|
-
|
|
791
|
+
const indented = lines.map((line) => (line.trim() ? ` ${line}` : ""));
|
|
792
|
+
return new Text(indented.join("\n"), 0, 0);
|
|
660
793
|
}
|
|
661
794
|
|
|
662
795
|
export const taskToolRenderer = {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function renderTemplate(template: string, vars: Record<string, string>): string {
|
|
2
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => vars[key] ?? `{{${key}}}`);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function extractPlaceholders(template: string): string[] {
|
|
6
|
+
return [...template.matchAll(/\{\{(\w+)\}\}/g)].map((match) => match[1]);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function validateTaskTemplate(
|
|
10
|
+
context: string | undefined,
|
|
11
|
+
tasks: Array<{ id: string; vars: Record<string, string> }>,
|
|
12
|
+
): string | null {
|
|
13
|
+
const template = context ?? "";
|
|
14
|
+
const placeholders = extractPlaceholders(template);
|
|
15
|
+
|
|
16
|
+
if (tasks.length > 1 && placeholders.length === 0) {
|
|
17
|
+
return "Multi-task invocations require {{placeholders}} in context";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (placeholders.length > 0) {
|
|
21
|
+
for (const task of tasks) {
|
|
22
|
+
const missing = placeholders.filter((placeholder) => !(placeholder in task.vars));
|
|
23
|
+
if (missing.length > 0) {
|
|
24
|
+
return `Task "${task.id}" missing vars: ${missing.join(", ")}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (tasks.length > 1 && placeholders.length > 0) {
|
|
30
|
+
const withoutPlaceholders = template.replace(/\{\{\w+\}\}/g, "").trim();
|
|
31
|
+
if (withoutPlaceholders.length < 50) {
|
|
32
|
+
return "Context must contain instructions (50+ chars) around {{placeholders}}";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
@@ -46,8 +46,10 @@ export const taskItemSchema = Type.Object({
|
|
|
46
46
|
description: "Short task identifier for display (max 32 chars, CamelCase, e.g. 'SessionStore', 'WebFetchFix')",
|
|
47
47
|
maxLength: 32,
|
|
48
48
|
}),
|
|
49
|
-
task: Type.String({ description: "Task description for the agent" }),
|
|
50
49
|
description: Type.String({ description: "Short description for UI display" }),
|
|
50
|
+
vars: Type.Record(Type.String(), Type.String(), {
|
|
51
|
+
description: "Template variables to fill {{placeholders}} in context",
|
|
52
|
+
}),
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
export type TaskItem = Static<typeof taskItemSchema>;
|
|
@@ -55,7 +57,7 @@ export type TaskItem = Static<typeof taskItemSchema>;
|
|
|
55
57
|
/** Task tool parameters */
|
|
56
58
|
export const taskSchema = Type.Object({
|
|
57
59
|
agent: Type.String({ description: "Agent type to use for all tasks" }),
|
|
58
|
-
context: Type.String({ description: "
|
|
60
|
+
context: Type.String({ description: "Template with {{placeholders}} filled by task vars" }),
|
|
59
61
|
model: Type.Optional(
|
|
60
62
|
Type.String({
|
|
61
63
|
description: "Model override for all tasks (fuzzy matching, e.g. 'sonnet', 'opus')",
|
|
@@ -120,6 +122,7 @@ export interface AgentProgress {
|
|
|
120
122
|
agentSource: AgentSource;
|
|
121
123
|
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
122
124
|
task: string;
|
|
125
|
+
vars?: Record<string, string>;
|
|
123
126
|
description?: string;
|
|
124
127
|
currentTool?: string;
|
|
125
128
|
currentToolArgs?: string;
|
|
@@ -141,6 +144,7 @@ export interface SingleResult {
|
|
|
141
144
|
agent: string;
|
|
142
145
|
agentSource: AgentSource;
|
|
143
146
|
task: string;
|
|
147
|
+
vars?: Record<string, string>;
|
|
144
148
|
description?: string;
|
|
145
149
|
exitCode: number;
|
|
146
150
|
output: string;
|
|
@@ -363,7 +363,7 @@ function createMCPProxyTool(metadata: MCPToolMetadata): CustomTool<TSchema> {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
function getPythonCallTimeoutMs(params: PythonToolParams): number | undefined {
|
|
366
|
-
const timeout = params.
|
|
366
|
+
const timeout = params.timeoutMs;
|
|
367
367
|
if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0) {
|
|
368
368
|
return Math.max(1000, Math.round(timeout * 1000) + 1000);
|
|
369
369
|
}
|
package/src/index.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { listModels } from "./cli/list-models";
|
|
|
18
18
|
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
|
|
19
19
|
import { selectSession } from "./cli/session-picker";
|
|
20
20
|
import { parseSetupArgs, printSetupHelp, runSetupCommand } from "./cli/setup-cli";
|
|
21
|
+
import { parseStatsArgs, printStatsHelp, runStatsCommand } from "./cli/stats-cli";
|
|
21
22
|
import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
|
|
22
23
|
import { findConfigFile, getModelsPath, VERSION } from "./config";
|
|
23
24
|
import type { AgentSession } from "./core/agent-session";
|
|
@@ -520,6 +521,17 @@ export async function main(args: string[]) {
|
|
|
520
521
|
return;
|
|
521
522
|
}
|
|
522
523
|
|
|
524
|
+
// Handle stats subcommand
|
|
525
|
+
const statsCmd = parseStatsArgs(args);
|
|
526
|
+
if (statsCmd) {
|
|
527
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
528
|
+
printStatsHelp();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
await runStatsCommand(statsCmd);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
523
535
|
const parsed = parseArgs(args);
|
|
524
536
|
time("parseArgs");
|
|
525
537
|
await maybeAutoChdir(parsed);
|