@oh-my-pi/pi-coding-agent 3.15.1 → 3.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/find.ts +7 -1
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/ls.ts +9 -2
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +37 -62
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line.ts +45 -37
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +643 -113
- package/src/modes/interactive/theme/defaults/index.ts +16 -16
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
|
@@ -154,7 +154,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
|
|
|
154
154
|
*
|
|
155
155
|
* @param cwd - Current working directory for project agent discovery
|
|
156
156
|
*/
|
|
157
|
-
export function discoverAgents(cwd: string): DiscoveryResult {
|
|
157
|
+
export async function discoverAgents(cwd: string): Promise<DiscoveryResult> {
|
|
158
158
|
const resolvedCwd = path.resolve(cwd);
|
|
159
159
|
const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map((entry) => entry.source)));
|
|
160
160
|
|
|
@@ -167,7 +167,7 @@ export function discoverAgents(cwd: string): DiscoveryResult {
|
|
|
167
167
|
}));
|
|
168
168
|
|
|
169
169
|
// Get project directories by walking up from cwd (priority order)
|
|
170
|
-
const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
|
|
170
|
+
const projectDirs = (await findAllNearestProjectConfigDirs("agents", resolvedCwd))
|
|
171
171
|
.filter((entry) => agentSources.includes(entry.source))
|
|
172
172
|
.map((entry) => ({
|
|
173
173
|
...entry,
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
* Parses JSON events for progress tracking.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import * as os from "node:os";
|
|
8
|
+
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
11
10
|
import * as path from "node:path";
|
|
12
|
-
import * as readline from "node:readline";
|
|
13
11
|
import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
|
|
14
12
|
import { resolveModelPattern } from "./model-resolver";
|
|
13
|
+
import { resolveOmpCommand } from "./omp-command";
|
|
15
14
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
16
15
|
import {
|
|
17
16
|
type AgentDefinition,
|
|
@@ -23,17 +22,12 @@ import {
|
|
|
23
22
|
type SingleResult,
|
|
24
23
|
} from "./types";
|
|
25
24
|
|
|
26
|
-
/** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
|
|
27
|
-
const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
|
|
28
|
-
|
|
29
|
-
/** Windows shell option for spawn */
|
|
30
|
-
const OMP_SHELL_OPT = process.platform === "win32";
|
|
31
|
-
|
|
32
25
|
/** Options for subprocess execution */
|
|
33
26
|
export interface ExecutorOptions {
|
|
34
27
|
cwd: string;
|
|
35
28
|
agent: AgentDefinition;
|
|
36
29
|
task: string;
|
|
30
|
+
description?: string;
|
|
37
31
|
index: number;
|
|
38
32
|
context?: string;
|
|
39
33
|
modelOverride?: string;
|
|
@@ -146,6 +140,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
146
140
|
agentSource: agent.source,
|
|
147
141
|
status: "running",
|
|
148
142
|
task,
|
|
143
|
+
description: options.description,
|
|
149
144
|
recentTools: [],
|
|
150
145
|
recentOutput: [],
|
|
151
146
|
toolCount: 0,
|
|
@@ -161,6 +156,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
161
156
|
agent: agent.name,
|
|
162
157
|
agentSource: agent.source,
|
|
163
158
|
task,
|
|
159
|
+
description: options.description,
|
|
164
160
|
exitCode: 1,
|
|
165
161
|
output: "",
|
|
166
162
|
stderr: "Aborted before start",
|
|
@@ -173,20 +169,21 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
173
169
|
}
|
|
174
170
|
|
|
175
171
|
// Write system prompt to temp file
|
|
176
|
-
const tempDir =
|
|
172
|
+
const tempDir = tmpdir();
|
|
177
173
|
const promptFile = path.join(
|
|
178
174
|
tempDir,
|
|
179
175
|
`omp-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
|
|
180
176
|
);
|
|
181
177
|
|
|
182
178
|
try {
|
|
183
|
-
|
|
179
|
+
writeFileSync(promptFile, agent.systemPrompt, "utf-8");
|
|
184
180
|
} catch (err) {
|
|
185
181
|
return {
|
|
186
182
|
index,
|
|
187
183
|
agent: agent.name,
|
|
188
184
|
agentSource: agent.source,
|
|
189
185
|
task,
|
|
186
|
+
description: options.description,
|
|
190
187
|
exitCode: 1,
|
|
191
188
|
output: "",
|
|
192
189
|
stderr: `Failed to write prompt file: ${err}`,
|
|
@@ -212,7 +209,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
212
209
|
|
|
213
210
|
// Write input file immediately (real-time visibility)
|
|
214
211
|
try {
|
|
215
|
-
|
|
212
|
+
writeFileSync(artifactPaths.inputPath, fullTask, "utf-8");
|
|
216
213
|
} catch {
|
|
217
214
|
// Non-fatal, continue without input artifact
|
|
218
215
|
}
|
|
@@ -268,10 +265,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
268
265
|
}
|
|
269
266
|
|
|
270
267
|
// Spawn subprocess
|
|
271
|
-
const
|
|
268
|
+
const ompCommand = resolveOmpCommand();
|
|
269
|
+
const proc = Bun.spawn([ompCommand.cmd, ...ompCommand.args, ...args], {
|
|
272
270
|
cwd,
|
|
273
|
-
|
|
274
|
-
|
|
271
|
+
stdin: "ignore",
|
|
272
|
+
stdout: "pipe",
|
|
273
|
+
stderr: "pipe",
|
|
275
274
|
env,
|
|
276
275
|
});
|
|
277
276
|
|
|
@@ -285,7 +284,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
285
284
|
// Handle abort signal
|
|
286
285
|
const onAbort = () => {
|
|
287
286
|
if (!resolved) {
|
|
288
|
-
proc.kill(
|
|
287
|
+
proc.kill(15); // SIGTERM
|
|
289
288
|
}
|
|
290
289
|
};
|
|
291
290
|
if (signal) {
|
|
@@ -293,9 +292,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
293
292
|
}
|
|
294
293
|
|
|
295
294
|
// Parse JSON events from stdout
|
|
296
|
-
const
|
|
295
|
+
const reader = proc.stdout.getReader();
|
|
296
|
+
const decoder = new TextDecoder();
|
|
297
|
+
let buffer = "";
|
|
297
298
|
|
|
298
|
-
|
|
299
|
+
const processLine = (line: string) => {
|
|
299
300
|
if (resolved) return;
|
|
300
301
|
|
|
301
302
|
try {
|
|
@@ -362,7 +363,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
362
363
|
setTimeout(() => {
|
|
363
364
|
if (!resolved) {
|
|
364
365
|
resolved = true;
|
|
365
|
-
proc.kill(
|
|
366
|
+
proc.kill(15); // SIGTERM
|
|
366
367
|
}
|
|
367
368
|
}, 2000);
|
|
368
369
|
}
|
|
@@ -406,7 +407,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
406
407
|
// If pending termination, now we have tokens - terminate
|
|
407
408
|
if (pendingTermination && !resolved) {
|
|
408
409
|
resolved = true;
|
|
409
|
-
proc.kill(
|
|
410
|
+
proc.kill(15); // SIGTERM
|
|
410
411
|
}
|
|
411
412
|
break;
|
|
412
413
|
}
|
|
@@ -433,45 +434,49 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
433
434
|
} catch {
|
|
434
435
|
// Ignore non-JSON lines
|
|
435
436
|
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Capture stderr
|
|
439
|
-
const stderrDecoder = new TextDecoder();
|
|
440
|
-
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
441
|
-
stderr += stderrDecoder.decode(chunk, { stream: true });
|
|
442
|
-
});
|
|
437
|
+
};
|
|
443
438
|
|
|
444
|
-
//
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
439
|
+
// Read stdout asynchronously
|
|
440
|
+
const stdoutDone = (async () => {
|
|
441
|
+
try {
|
|
442
|
+
while (true) {
|
|
443
|
+
const { done, value } = await reader.read();
|
|
444
|
+
if (done) break;
|
|
445
|
+
buffer += decoder.decode(value, { stream: true });
|
|
446
|
+
const lines = buffer.split("\n");
|
|
447
|
+
buffer = lines.pop() || "";
|
|
448
|
+
for (const line of lines) {
|
|
449
|
+
processLine(line);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Process remaining buffer
|
|
453
|
+
if (buffer.trim()) {
|
|
454
|
+
processLine(buffer);
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
// Ignore read errors
|
|
458
|
+
}
|
|
459
|
+
})();
|
|
449
460
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
461
|
+
// Capture stderr - Bun.spawn returns ReadableStream, convert to text
|
|
462
|
+
const stderrDone = (async () => {
|
|
463
|
+
try {
|
|
464
|
+
const stderrReader = proc.stderr.getReader();
|
|
465
|
+
const stderrDecoder = new TextDecoder();
|
|
466
|
+
while (true) {
|
|
467
|
+
const { done, value } = await stderrReader.read();
|
|
468
|
+
if (done) break;
|
|
469
|
+
stderr += stderrDecoder.decode(value, { stream: true });
|
|
454
470
|
}
|
|
455
|
-
}
|
|
471
|
+
} catch {
|
|
472
|
+
// Ignore stderr read errors
|
|
473
|
+
}
|
|
474
|
+
})();
|
|
456
475
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
proc.on("close", (c) => {
|
|
463
|
-
code = c;
|
|
464
|
-
procClosed = true;
|
|
465
|
-
maybeResolve();
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
proc.on("error", (err) => {
|
|
469
|
-
stderr += `\nProcess error: ${err.message}`;
|
|
470
|
-
code = 1;
|
|
471
|
-
procClosed = true;
|
|
472
|
-
maybeResolve();
|
|
473
|
-
});
|
|
474
|
-
});
|
|
476
|
+
// Wait for process and stream readers to finish
|
|
477
|
+
const exitCode = await proc.exited;
|
|
478
|
+
await Promise.all([stdoutDone, stderrDone]);
|
|
479
|
+
resolved = true;
|
|
475
480
|
|
|
476
481
|
// Cleanup
|
|
477
482
|
if (signal) {
|
|
@@ -479,7 +484,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
479
484
|
}
|
|
480
485
|
|
|
481
486
|
try {
|
|
482
|
-
|
|
487
|
+
if (existsSync(promptFile)) {
|
|
488
|
+
unlinkSync(promptFile);
|
|
489
|
+
}
|
|
483
490
|
} catch {
|
|
484
491
|
// Ignore cleanup errors
|
|
485
492
|
}
|
|
@@ -493,7 +500,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
493
500
|
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
494
501
|
if (artifactPaths) {
|
|
495
502
|
try {
|
|
496
|
-
|
|
503
|
+
writeFileSync(artifactPaths.outputPath, rawOutput, "utf-8");
|
|
497
504
|
outputMeta = {
|
|
498
505
|
lineCount: rawOutput.split("\n").length,
|
|
499
506
|
charCount: rawOutput.length,
|
|
@@ -514,6 +521,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
514
521
|
agent: agent.name,
|
|
515
522
|
agentSource: agent.source,
|
|
516
523
|
task,
|
|
524
|
+
description: options.description,
|
|
517
525
|
exitCode,
|
|
518
526
|
output: truncatedOutput,
|
|
519
527
|
stderr,
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
17
17
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
18
18
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
19
|
+
import { formatDuration } from "../render-utils";
|
|
19
20
|
import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir } from "./artifacts";
|
|
20
21
|
import { discoverAgents, getAgent } from "./discovery";
|
|
21
22
|
import { runSubprocess } from "./executor";
|
|
22
23
|
import { mapWithConcurrencyLimit } from "./parallel";
|
|
23
|
-
import {
|
|
24
|
+
import { renderCall, renderResult } from "./render";
|
|
24
25
|
import {
|
|
25
26
|
type AgentProgress,
|
|
26
27
|
MAX_AGENTS_IN_DESCRIPTION,
|
|
@@ -137,8 +138,8 @@ export { taskSchema } from "./types";
|
|
|
137
138
|
/**
|
|
138
139
|
* Build dynamic tool description listing available agents.
|
|
139
140
|
*/
|
|
140
|
-
function buildDescription(cwd: string): string {
|
|
141
|
-
const { agents } = discoverAgents(cwd);
|
|
141
|
+
async function buildDescription(cwd: string): Promise<string> {
|
|
142
|
+
const { agents } = await discoverAgents(cwd);
|
|
142
143
|
|
|
143
144
|
const lines: string[] = [];
|
|
144
145
|
|
|
@@ -194,7 +195,7 @@ function buildDescription(cwd: string): string {
|
|
|
194
195
|
lines.push("");
|
|
195
196
|
lines.push("Parameters:");
|
|
196
197
|
lines.push(
|
|
197
|
-
`- tasks: Array of {agent, task, model?} - tasks to run in parallel (max ${MAX_PARALLEL_TASKS}, ${MAX_CONCURRENCY} concurrent)`,
|
|
198
|
+
`- tasks: Array of {agent, task, description?, model?} - tasks to run in parallel (max ${MAX_PARALLEL_TASKS}, ${MAX_CONCURRENCY} concurrent)`,
|
|
198
199
|
);
|
|
199
200
|
lines.push(
|
|
200
201
|
' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for omp\'s default model',
|
|
@@ -257,11 +258,11 @@ function buildDescription(cwd: string): string {
|
|
|
257
258
|
/**
|
|
258
259
|
* Create the task tool configured for a specific working directory.
|
|
259
260
|
*/
|
|
260
|
-
export function createTaskTool(
|
|
261
|
+
export async function createTaskTool(
|
|
261
262
|
cwd: string,
|
|
262
263
|
sessionContext?: SessionContext,
|
|
263
264
|
options?: TaskToolOptions,
|
|
264
|
-
): AgentTool<typeof taskSchema, TaskToolDetails, Theme
|
|
265
|
+
): Promise<AgentTool<typeof taskSchema, TaskToolDetails, Theme>> {
|
|
265
266
|
const hasOutputTool = options?.availableTools?.has("output") ?? false;
|
|
266
267
|
// Check if subagents are completely inhibited (legacy recursion prevention)
|
|
267
268
|
if (process.env[OMP_NO_SUBAGENTS_ENV]) {
|
|
@@ -287,13 +288,13 @@ export function createTaskTool(
|
|
|
287
288
|
return {
|
|
288
289
|
name: "task",
|
|
289
290
|
label: "Task",
|
|
290
|
-
description: buildDescription(cwd),
|
|
291
|
+
description: await buildDescription(cwd),
|
|
291
292
|
parameters: taskSchema,
|
|
292
293
|
renderCall,
|
|
293
294
|
renderResult,
|
|
294
295
|
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
295
296
|
const startTime = Date.now();
|
|
296
|
-
const { agents, projectAgentsDir } = discoverAgents(cwd);
|
|
297
|
+
const { agents, projectAgentsDir } = await discoverAgents(cwd);
|
|
297
298
|
const context = params.context;
|
|
298
299
|
|
|
299
300
|
// Handle empty or missing tasks
|
|
@@ -435,6 +436,7 @@ export function createTaskTool(
|
|
|
435
436
|
tokens: 0,
|
|
436
437
|
durationMs: 0,
|
|
437
438
|
modelOverride: tasks[i].model,
|
|
439
|
+
description: tasks[i].description,
|
|
438
440
|
});
|
|
439
441
|
}
|
|
440
442
|
emitProgress();
|
|
@@ -444,6 +446,7 @@ export function createTaskTool(
|
|
|
444
446
|
agent: t.agent,
|
|
445
447
|
task: context ? `${context}\n\n${t.task}` : t.task,
|
|
446
448
|
model: t.model,
|
|
449
|
+
description: t.description,
|
|
447
450
|
}));
|
|
448
451
|
|
|
449
452
|
// Execute in parallel with concurrency limit
|
|
@@ -453,6 +456,7 @@ export function createTaskTool(
|
|
|
453
456
|
cwd,
|
|
454
457
|
agent,
|
|
455
458
|
task: task.task,
|
|
459
|
+
description: task.description,
|
|
456
460
|
index,
|
|
457
461
|
context: undefined, // Already prepended above
|
|
458
462
|
modelOverride: task.model,
|
|
@@ -544,5 +548,20 @@ export function createTaskTool(
|
|
|
544
548
|
};
|
|
545
549
|
}
|
|
546
550
|
|
|
547
|
-
// Default task tool using process.cwd()
|
|
548
|
-
|
|
551
|
+
// Default task tool using process.cwd() - returns a placeholder sync tool
|
|
552
|
+
// Real implementations should use createTaskTool() which properly initializes the tool
|
|
553
|
+
export const taskTool: AgentTool<typeof taskSchema, TaskToolDetails, Theme> = {
|
|
554
|
+
name: "task",
|
|
555
|
+
label: "Task",
|
|
556
|
+
description:
|
|
557
|
+
"Launch a new agent to handle complex, multi-step tasks autonomously. (Agent discovery pending - use createTaskTool for full functionality)",
|
|
558
|
+
parameters: taskSchema,
|
|
559
|
+
execute: async () => ({
|
|
560
|
+
content: [{ type: "text", text: "Task tool not properly initialized. Use createTaskTool(cwd) instead." }],
|
|
561
|
+
details: {
|
|
562
|
+
projectAgentsDir: null,
|
|
563
|
+
results: [],
|
|
564
|
+
totalDurationMs: 0,
|
|
565
|
+
},
|
|
566
|
+
}),
|
|
567
|
+
};
|
|
@@ -11,15 +11,9 @@
|
|
|
11
11
|
* - "omp/slow" → configured slow model from settings
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { spawnSync } from "node:child_process";
|
|
15
14
|
import { type Settings, settingsCapability } from "../../../capability/settings";
|
|
16
15
|
import { loadSync } from "../../../discovery";
|
|
17
|
-
|
|
18
|
-
/** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
|
|
19
|
-
const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
|
|
20
|
-
|
|
21
|
-
/** Windows shell option for spawn/spawnSync */
|
|
22
|
-
const OMP_SHELL_OPT = process.platform === "win32";
|
|
16
|
+
import { resolveOmpCommand } from "./omp-command";
|
|
23
17
|
|
|
24
18
|
/** Cache for available models (provider/modelId format) */
|
|
25
19
|
let cachedModels: string[] | null = null;
|
|
@@ -41,20 +35,21 @@ export function getAvailableModels(): string[] {
|
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
const ompCommand = resolveOmpCommand();
|
|
39
|
+
const result = Bun.spawnSync([ompCommand.cmd, ...ompCommand.args, "--list-models"], {
|
|
40
|
+
stdin: "ignore",
|
|
41
|
+
stdout: "pipe",
|
|
42
|
+
stderr: "pipe",
|
|
48
43
|
});
|
|
49
44
|
|
|
50
|
-
if (result.
|
|
45
|
+
if (result.exitCode !== 0 || !result.stdout) {
|
|
51
46
|
cachedModels = [];
|
|
52
47
|
cacheExpiry = now + CACHE_TTL_MS;
|
|
53
48
|
return cachedModels;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
// Parse output: skip header line, extract provider/model
|
|
57
|
-
const lines = result.stdout.trim().split("\n");
|
|
52
|
+
const lines = result.stdout.toString().trim().split("\n");
|
|
58
53
|
cachedModels = lines
|
|
59
54
|
.slice(1) // Skip header
|
|
60
55
|
.map((line) => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
interface OmpCommand {
|
|
4
|
+
cmd: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
shell: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
|
|
10
|
+
const DEFAULT_SHELL = process.platform === "win32";
|
|
11
|
+
|
|
12
|
+
export function resolveOmpCommand(): OmpCommand {
|
|
13
|
+
const envCmd = process.env.OMP_SUBPROCESS_CMD;
|
|
14
|
+
if (envCmd?.trim()) {
|
|
15
|
+
return { cmd: envCmd, args: [], shell: DEFAULT_SHELL };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entry = process.argv[1];
|
|
19
|
+
if (entry && (entry.endsWith(".ts") || entry.endsWith(".js"))) {
|
|
20
|
+
return { cmd: process.execPath, args: [entry], shell: false };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { cmd: DEFAULT_CMD, args: [], shell: DEFAULT_SHELL };
|
|
24
|
+
}
|
|
@@ -10,6 +10,14 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
10
10
|
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
12
12
|
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
13
|
+
import {
|
|
14
|
+
formatBadge,
|
|
15
|
+
formatDuration,
|
|
16
|
+
formatMoreItems,
|
|
17
|
+
formatTokens,
|
|
18
|
+
getStyledStatusIcon,
|
|
19
|
+
truncate,
|
|
20
|
+
} from "../render-utils";
|
|
13
21
|
import type { ReportFindingDetails, SubmitReviewDetails } from "../review";
|
|
14
22
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
15
23
|
import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
@@ -22,63 +30,26 @@ const PRIORITY_LABELS: Record<number, string> = {
|
|
|
22
30
|
3: "P3",
|
|
23
31
|
};
|
|
24
32
|
|
|
25
|
-
/**
|
|
26
|
-
* Format token count for display (e.g., 1.5k, 25k).
|
|
27
|
-
*/
|
|
28
|
-
function formatTokens(tokens: number): string {
|
|
29
|
-
if (tokens >= 1000) {
|
|
30
|
-
return `${(tokens / 1000).toFixed(1)}k`;
|
|
31
|
-
}
|
|
32
|
-
return String(tokens);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Format duration for display.
|
|
37
|
-
*/
|
|
38
|
-
export function formatDuration(ms: number): string {
|
|
39
|
-
if (ms < 1000) return `${ms}ms`;
|
|
40
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
41
|
-
return `${(ms / 60000).toFixed(1)}m`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Truncate text to max length with ellipsis.
|
|
46
|
-
*/
|
|
47
|
-
function truncate(text: string, maxLen: number, ellipsis: string): string {
|
|
48
|
-
if (text.length <= maxLen) return text;
|
|
49
|
-
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
50
|
-
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
33
|
/**
|
|
54
34
|
* Get status icon for agent state.
|
|
55
35
|
* For running status, uses animated spinner if spinnerFrame is provided.
|
|
36
|
+
* Maps AgentProgress status to styled icon format.
|
|
56
37
|
*/
|
|
57
38
|
function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFrame?: number): string {
|
|
58
39
|
switch (status) {
|
|
59
40
|
case "pending":
|
|
60
|
-
return theme
|
|
61
|
-
case "running":
|
|
62
|
-
|
|
63
|
-
if (spinnerFrame === undefined) return theme.status.running;
|
|
64
|
-
const frames = theme.spinnerFrames;
|
|
65
|
-
return frames[spinnerFrame % frames.length];
|
|
66
|
-
}
|
|
41
|
+
return getStyledStatusIcon("pending", theme);
|
|
42
|
+
case "running":
|
|
43
|
+
return getStyledStatusIcon("running", theme, spinnerFrame);
|
|
67
44
|
case "completed":
|
|
68
|
-
return theme
|
|
45
|
+
return getStyledStatusIcon("success", theme);
|
|
69
46
|
case "failed":
|
|
70
|
-
return theme
|
|
47
|
+
return getStyledStatusIcon("error", theme);
|
|
71
48
|
case "aborted":
|
|
72
|
-
return theme
|
|
49
|
+
return getStyledStatusIcon("aborted", theme);
|
|
73
50
|
}
|
|
74
51
|
}
|
|
75
52
|
|
|
76
|
-
function formatBadge(label: string, color: "success" | "error" | "warning" | "accent" | "muted", theme: Theme): string {
|
|
77
|
-
const left = theme.format.bracketLeft;
|
|
78
|
-
const right = theme.format.bracketRight;
|
|
79
|
-
return theme.fg(color, `${left}${label}${right}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
53
|
function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): string {
|
|
83
54
|
if (findings.length === 0) return theme.fg("dim", "Findings: none");
|
|
84
55
|
|
|
@@ -119,10 +90,7 @@ function renderOutputSection(
|
|
|
119
90
|
|
|
120
91
|
if (outputLines.length > previewCount) {
|
|
121
92
|
lines.push(
|
|
122
|
-
`${continuePrefix} ${theme.fg(
|
|
123
|
-
"dim",
|
|
124
|
-
`${theme.format.ellipsis} ${outputLines.length - previewCount} more lines`,
|
|
125
|
-
)}`,
|
|
93
|
+
`${continuePrefix} ${theme.fg("dim", formatMoreItems(outputLines.length - previewCount, "line", theme))}`,
|
|
126
94
|
);
|
|
127
95
|
}
|
|
128
96
|
|
|
@@ -133,17 +101,18 @@ function renderOutputSection(
|
|
|
133
101
|
* Render the tool call arguments.
|
|
134
102
|
*/
|
|
135
103
|
export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
136
|
-
const label = theme.fg("toolTitle", theme.bold("
|
|
104
|
+
const label = theme.fg("toolTitle", theme.bold("Task"));
|
|
137
105
|
|
|
138
106
|
if (args.tasks.length === 1) {
|
|
139
107
|
// Single task - show agent and task preview
|
|
140
108
|
const task = args.tasks[0];
|
|
141
|
-
const
|
|
109
|
+
const summary = task.description?.trim() || task.task;
|
|
110
|
+
const taskPreview = truncate(summary, 60, theme.format.ellipsis);
|
|
142
111
|
return new Text(`${label} ${theme.fg("accent", task.agent)}: ${theme.fg("muted", taskPreview)}`, 0, 0);
|
|
143
112
|
}
|
|
144
113
|
|
|
145
|
-
// Multiple tasks - show count and agent names
|
|
146
|
-
const agents = args.tasks.map((t) => t.agent).join(", ");
|
|
114
|
+
// Multiple tasks - show count and descriptions (or agent names as fallback)
|
|
115
|
+
const agents = args.tasks.map((t) => t.description?.trim() || t.agent).join(", ");
|
|
147
116
|
return new Text(
|
|
148
117
|
`${label} ${theme.fg("muted", `${args.tasks.length} agents: ${truncate(agents, 50, theme.format.ellipsis)}`)}`,
|
|
149
118
|
0,
|
|
@@ -178,6 +147,10 @@ function renderAgentProgress(
|
|
|
178
147
|
// Main status line - include index for Output tool ID derivation
|
|
179
148
|
const agentId = `${progress.agent}(${progress.index})`;
|
|
180
149
|
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
|
|
150
|
+
const description = progress.description?.trim();
|
|
151
|
+
if (description) {
|
|
152
|
+
statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
|
|
153
|
+
}
|
|
181
154
|
|
|
182
155
|
// Only show badge for non-running states (spinner already indicates running)
|
|
183
156
|
if (progress.status !== "running") {
|
|
@@ -193,8 +166,10 @@ function renderAgentProgress(
|
|
|
193
166
|
}
|
|
194
167
|
|
|
195
168
|
if (progress.status === "running") {
|
|
196
|
-
|
|
197
|
-
|
|
169
|
+
if (!description) {
|
|
170
|
+
const taskPreview = truncate(progress.task, 40, theme.format.ellipsis);
|
|
171
|
+
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
172
|
+
}
|
|
198
173
|
statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
199
174
|
if (progress.tokens > 0) {
|
|
200
175
|
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatTokens(progress.tokens)} tokens`)}`;
|
|
@@ -235,9 +210,7 @@ function renderAgentProgress(
|
|
|
235
210
|
}
|
|
236
211
|
}
|
|
237
212
|
if (dataArray.length > 3) {
|
|
238
|
-
lines.push(
|
|
239
|
-
`${continuePrefix}${theme.fg("dim", `${theme.format.ellipsis} ${dataArray.length - 3} more`)}`,
|
|
240
|
-
);
|
|
213
|
+
lines.push(`${continuePrefix}${theme.fg("dim", formatMoreItems(dataArray.length - 3, "item", theme))}`);
|
|
241
214
|
}
|
|
242
215
|
}
|
|
243
216
|
}
|
|
@@ -337,9 +310,7 @@ function renderFindings(
|
|
|
337
310
|
}
|
|
338
311
|
|
|
339
312
|
if (!expanded && findings.length > 3) {
|
|
340
|
-
lines.push(
|
|
341
|
-
`${continuePrefix}${theme.fg("dim", `${theme.format.ellipsis} ${findings.length - 3} more findings`)}`,
|
|
342
|
-
);
|
|
313
|
+
lines.push(`${continuePrefix}${theme.fg("dim", formatMoreItems(findings.length - 3, "finding", theme))}`);
|
|
343
314
|
}
|
|
344
315
|
|
|
345
316
|
return lines;
|
|
@@ -364,6 +335,10 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
364
335
|
// Main status line - include index for Output tool ID derivation
|
|
365
336
|
const agentId = `${result.agent}(${result.index})`;
|
|
366
337
|
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
338
|
+
const description = result.description?.trim();
|
|
339
|
+
if (description) {
|
|
340
|
+
statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
|
|
341
|
+
}
|
|
367
342
|
if (result.tokens > 0) {
|
|
368
343
|
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatTokens(result.tokens)} tokens`)}`;
|
|
369
344
|
}
|
|
@@ -479,11 +454,11 @@ export function renderResult(
|
|
|
479
454
|
let summary = `\n${theme.fg("dim", "Total:")} `;
|
|
480
455
|
if (abortedCount > 0) {
|
|
481
456
|
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
482
|
-
if (successCount > 0 || failCount > 0) summary +=
|
|
457
|
+
if (successCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
483
458
|
}
|
|
484
459
|
if (successCount > 0) {
|
|
485
460
|
summary += theme.fg("success", `${successCount} succeeded`);
|
|
486
|
-
if (failCount > 0) summary +=
|
|
461
|
+
if (failCount > 0) summary += theme.sep.dot;
|
|
487
462
|
}
|
|
488
463
|
if (failCount > 0) {
|
|
489
464
|
summary += theme.fg("error", `${failCount} failed`);
|
|
@@ -32,6 +32,7 @@ export const OMP_SPAWNS_ENV = "OMP_SPAWNS";
|
|
|
32
32
|
export const taskItemSchema = Type.Object({
|
|
33
33
|
agent: Type.String({ description: "Agent name" }),
|
|
34
34
|
task: Type.String({ description: "Task description for the agent" }),
|
|
35
|
+
description: Type.Optional(Type.String({ description: "Short description for UI display" })),
|
|
35
36
|
model: Type.Optional(Type.String({ description: "Model override for this task" })),
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -92,6 +93,7 @@ export interface AgentProgress {
|
|
|
92
93
|
agentSource: AgentSource;
|
|
93
94
|
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
94
95
|
task: string;
|
|
96
|
+
description?: string;
|
|
95
97
|
currentTool?: string;
|
|
96
98
|
currentToolArgs?: string;
|
|
97
99
|
currentToolStartMs?: number;
|
|
@@ -111,6 +113,7 @@ export interface SingleResult {
|
|
|
111
113
|
agent: string;
|
|
112
114
|
agentSource: AgentSource;
|
|
113
115
|
task: string;
|
|
116
|
+
description?: string;
|
|
114
117
|
exitCode: number;
|
|
115
118
|
output: string;
|
|
116
119
|
stderr: string;
|