@oh-my-pi/pi-coding-agent 0.1.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 +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subprocess execution for subagents.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `omp` in JSON mode to execute tasks with isolated context.
|
|
5
|
+
* Parses JSON events for progress tracking.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import * as readline from "node:readline";
|
|
13
|
+
import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
|
|
14
|
+
import { resolveModelPattern } from "./model-resolver";
|
|
15
|
+
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
16
|
+
import {
|
|
17
|
+
type AgentDefinition,
|
|
18
|
+
type AgentProgress,
|
|
19
|
+
MAX_OUTPUT_BYTES,
|
|
20
|
+
MAX_OUTPUT_LINES,
|
|
21
|
+
OMP_BLOCKED_AGENT_ENV,
|
|
22
|
+
OMP_SPAWNS_ENV,
|
|
23
|
+
type SingleResult,
|
|
24
|
+
} from "./types";
|
|
25
|
+
|
|
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
|
+
/** Options for subprocess execution */
|
|
33
|
+
export interface ExecutorOptions {
|
|
34
|
+
cwd: string;
|
|
35
|
+
agent: AgentDefinition;
|
|
36
|
+
task: string;
|
|
37
|
+
index: number;
|
|
38
|
+
context?: string;
|
|
39
|
+
modelOverride?: string;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
onProgress?: (progress: AgentProgress) => void;
|
|
42
|
+
sessionFile?: string | null;
|
|
43
|
+
persistArtifacts?: boolean;
|
|
44
|
+
artifactsDir?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Truncate output to byte and line limits.
|
|
49
|
+
*/
|
|
50
|
+
function truncateOutput(output: string): { text: string; truncated: boolean } {
|
|
51
|
+
let truncated = false;
|
|
52
|
+
let byteBudget = MAX_OUTPUT_BYTES;
|
|
53
|
+
let lineBudget = MAX_OUTPUT_LINES;
|
|
54
|
+
|
|
55
|
+
let i = 0;
|
|
56
|
+
let lastNewlineIndex = -1;
|
|
57
|
+
while (i < output.length && byteBudget > 0) {
|
|
58
|
+
const ch = output.charCodeAt(i);
|
|
59
|
+
byteBudget--;
|
|
60
|
+
|
|
61
|
+
if (ch === 10 /* \n */) {
|
|
62
|
+
lineBudget--;
|
|
63
|
+
lastNewlineIndex = i;
|
|
64
|
+
if (lineBudget <= 0) {
|
|
65
|
+
truncated = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (i < output.length) {
|
|
74
|
+
truncated = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (truncated && lineBudget <= 0 && lastNewlineIndex >= 0) {
|
|
78
|
+
output = output.slice(0, lastNewlineIndex);
|
|
79
|
+
} else {
|
|
80
|
+
output = output.slice(0, i);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { text: output, truncated };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extract a short preview from tool args for display.
|
|
88
|
+
*/
|
|
89
|
+
function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
90
|
+
// Priority order for preview
|
|
91
|
+
const previewKeys = ["command", "file_path", "path", "pattern", "query", "url", "task", "prompt"];
|
|
92
|
+
|
|
93
|
+
for (const key of previewKeys) {
|
|
94
|
+
if (args[key] && typeof args[key] === "string") {
|
|
95
|
+
const value = args[key] as string;
|
|
96
|
+
return value.length > 60 ? `${value.slice(0, 57)}...` : value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getNumberField(record: Record<string, unknown>, key: string): number | undefined {
|
|
104
|
+
if (!Object.hasOwn(record, key)) return undefined;
|
|
105
|
+
const value = record[key];
|
|
106
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function firstNumberField(record: Record<string, unknown>, keys: string[]): number | undefined {
|
|
110
|
+
for (const key of keys) {
|
|
111
|
+
const value = getNumberField(record, key);
|
|
112
|
+
if (value !== undefined) return value;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Normalize usage objects from different event formats.
|
|
119
|
+
*/
|
|
120
|
+
function getUsageTokens(usage: unknown): number {
|
|
121
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
122
|
+
const record = usage as Record<string, unknown>;
|
|
123
|
+
|
|
124
|
+
const totalTokens = firstNumberField(record, ["totalTokens", "total_tokens"]);
|
|
125
|
+
if (totalTokens !== undefined && totalTokens > 0) return totalTokens;
|
|
126
|
+
|
|
127
|
+
const input = firstNumberField(record, ["input", "input_tokens", "inputTokens"]) ?? 0;
|
|
128
|
+
const output = firstNumberField(record, ["output", "output_tokens", "outputTokens"]) ?? 0;
|
|
129
|
+
const cacheRead = firstNumberField(record, ["cacheRead", "cache_read", "cacheReadTokens"]) ?? 0;
|
|
130
|
+
const cacheWrite = firstNumberField(record, ["cacheWrite", "cache_write", "cacheWriteTokens"]) ?? 0;
|
|
131
|
+
|
|
132
|
+
return input + output + cacheRead + cacheWrite;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Run a single agent as a subprocess.
|
|
137
|
+
*/
|
|
138
|
+
export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
|
|
139
|
+
const { cwd, agent, task, index, context, modelOverride, signal, onProgress } = options;
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
|
|
142
|
+
// Initialize progress
|
|
143
|
+
const progress: AgentProgress = {
|
|
144
|
+
index,
|
|
145
|
+
agent: agent.name,
|
|
146
|
+
agentSource: agent.source,
|
|
147
|
+
status: "running",
|
|
148
|
+
task,
|
|
149
|
+
recentTools: [],
|
|
150
|
+
recentOutput: [],
|
|
151
|
+
toolCount: 0,
|
|
152
|
+
tokens: 0,
|
|
153
|
+
durationMs: 0,
|
|
154
|
+
modelOverride,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Check if already aborted
|
|
158
|
+
if (signal?.aborted) {
|
|
159
|
+
return {
|
|
160
|
+
index,
|
|
161
|
+
agent: agent.name,
|
|
162
|
+
agentSource: agent.source,
|
|
163
|
+
task,
|
|
164
|
+
exitCode: 1,
|
|
165
|
+
output: "",
|
|
166
|
+
stderr: "Aborted before start",
|
|
167
|
+
truncated: false,
|
|
168
|
+
durationMs: 0,
|
|
169
|
+
tokens: 0,
|
|
170
|
+
modelOverride,
|
|
171
|
+
error: "Aborted",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Write system prompt to temp file
|
|
176
|
+
const tempDir = os.tmpdir();
|
|
177
|
+
const promptFile = path.join(
|
|
178
|
+
tempDir,
|
|
179
|
+
`omp-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
fs.writeFileSync(promptFile, agent.systemPrompt, "utf-8");
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return {
|
|
186
|
+
index,
|
|
187
|
+
agent: agent.name,
|
|
188
|
+
agentSource: agent.source,
|
|
189
|
+
task,
|
|
190
|
+
exitCode: 1,
|
|
191
|
+
output: "",
|
|
192
|
+
stderr: `Failed to write prompt file: ${err}`,
|
|
193
|
+
truncated: false,
|
|
194
|
+
durationMs: Date.now() - startTime,
|
|
195
|
+
tokens: 0,
|
|
196
|
+
modelOverride,
|
|
197
|
+
error: `Failed to write prompt file: ${err}`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Build full task with context
|
|
202
|
+
const fullTask = context ? `${context}\n\n${task}` : task;
|
|
203
|
+
|
|
204
|
+
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
205
|
+
let artifactPaths: { inputPath: string; outputPath: string; jsonlPath: string } | undefined;
|
|
206
|
+
let subtaskSessionFile: string | undefined;
|
|
207
|
+
|
|
208
|
+
if (options.artifactsDir) {
|
|
209
|
+
ensureArtifactsDir(options.artifactsDir);
|
|
210
|
+
artifactPaths = getArtifactPaths(options.artifactsDir, agent.name, index);
|
|
211
|
+
subtaskSessionFile = artifactPaths.jsonlPath;
|
|
212
|
+
|
|
213
|
+
// Write input file immediately (real-time visibility)
|
|
214
|
+
try {
|
|
215
|
+
fs.writeFileSync(artifactPaths.inputPath, fullTask, "utf-8");
|
|
216
|
+
} catch {
|
|
217
|
+
// Non-fatal, continue without input artifact
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Build args
|
|
222
|
+
const args: string[] = ["--mode", "json", "--non-interactive"];
|
|
223
|
+
|
|
224
|
+
// Add system prompt
|
|
225
|
+
args.push("--append-system-prompt", promptFile);
|
|
226
|
+
|
|
227
|
+
// Add tools if specified
|
|
228
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
229
|
+
let toolList = agent.tools;
|
|
230
|
+
// Auto-include task tool if spawns defined but task not in tools
|
|
231
|
+
if (agent.spawns !== undefined && !toolList.includes("task")) {
|
|
232
|
+
toolList = [...toolList, "task"];
|
|
233
|
+
}
|
|
234
|
+
args.push("--tools", toolList.join(","));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Resolve and add model
|
|
238
|
+
const resolvedModel = resolveModelPattern(modelOverride || agent.model);
|
|
239
|
+
if (resolvedModel) {
|
|
240
|
+
args.push("--model", resolvedModel);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Add session options - use subtask-specific session file for real-time streaming
|
|
244
|
+
if (subtaskSessionFile) {
|
|
245
|
+
args.push("--session", subtaskSessionFile);
|
|
246
|
+
} else if (options.sessionFile) {
|
|
247
|
+
args.push("--session", options.sessionFile);
|
|
248
|
+
} else {
|
|
249
|
+
args.push("--no-session");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Add task as prompt
|
|
253
|
+
args.push("--prompt", fullTask);
|
|
254
|
+
|
|
255
|
+
// Set up environment - block same-agent recursion unless explicitly recursive
|
|
256
|
+
const env = { ...process.env };
|
|
257
|
+
if (!agent.recursive) {
|
|
258
|
+
env[OMP_BLOCKED_AGENT_ENV] = agent.name;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Propagate spawn restrictions to subprocess
|
|
262
|
+
if (agent.spawns === undefined) {
|
|
263
|
+
env[OMP_SPAWNS_ENV] = ""; // No spawns = deny all
|
|
264
|
+
} else if (agent.spawns === "*") {
|
|
265
|
+
env[OMP_SPAWNS_ENV] = "*";
|
|
266
|
+
} else {
|
|
267
|
+
env[OMP_SPAWNS_ENV] = agent.spawns.join(",");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Spawn subprocess
|
|
271
|
+
const proc = spawn(OMP_CMD, args, {
|
|
272
|
+
cwd,
|
|
273
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
274
|
+
shell: OMP_SHELL_OPT,
|
|
275
|
+
env,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
let output = "";
|
|
279
|
+
let stderr = "";
|
|
280
|
+
let finalOutput = "";
|
|
281
|
+
let resolved = false;
|
|
282
|
+
let pendingTermination = false; // Set when shouldTerminate fires, wait for message_end
|
|
283
|
+
const jsonlEvents: string[] = [];
|
|
284
|
+
|
|
285
|
+
// Handle abort signal
|
|
286
|
+
const onAbort = () => {
|
|
287
|
+
if (!resolved) {
|
|
288
|
+
proc.kill("SIGTERM");
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
if (signal) {
|
|
292
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Parse JSON events from stdout
|
|
296
|
+
const rl = readline.createInterface({ input: proc.stdout! });
|
|
297
|
+
|
|
298
|
+
rl.on("line", (line) => {
|
|
299
|
+
if (resolved) return;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const event = JSON.parse(line);
|
|
303
|
+
jsonlEvents.push(line);
|
|
304
|
+
const now = Date.now();
|
|
305
|
+
|
|
306
|
+
switch (event.type) {
|
|
307
|
+
case "tool_execution_start":
|
|
308
|
+
progress.toolCount++;
|
|
309
|
+
progress.currentTool = event.toolName;
|
|
310
|
+
progress.currentToolArgs = extractToolArgsPreview(event.toolArgs || event.args || {});
|
|
311
|
+
progress.currentToolStartMs = now;
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case "tool_execution_end": {
|
|
315
|
+
if (progress.currentTool) {
|
|
316
|
+
progress.recentTools.unshift({
|
|
317
|
+
tool: progress.currentTool,
|
|
318
|
+
args: progress.currentToolArgs || "",
|
|
319
|
+
endMs: now,
|
|
320
|
+
});
|
|
321
|
+
// Keep only last 5
|
|
322
|
+
if (progress.recentTools.length > 5) {
|
|
323
|
+
progress.recentTools.pop();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
progress.currentTool = undefined;
|
|
327
|
+
progress.currentToolArgs = undefined;
|
|
328
|
+
progress.currentToolStartMs = undefined;
|
|
329
|
+
|
|
330
|
+
// Check for registered subprocess tool handler
|
|
331
|
+
const handler = subprocessToolRegistry.getHandler(event.toolName);
|
|
332
|
+
if (handler) {
|
|
333
|
+
// Extract data using handler
|
|
334
|
+
if (handler.extractData) {
|
|
335
|
+
const data = handler.extractData({
|
|
336
|
+
toolName: event.toolName,
|
|
337
|
+
toolCallId: event.toolCallId,
|
|
338
|
+
args: event.args,
|
|
339
|
+
result: event.result,
|
|
340
|
+
isError: event.isError,
|
|
341
|
+
});
|
|
342
|
+
if (data !== undefined) {
|
|
343
|
+
progress.extractedToolData = progress.extractedToolData || {};
|
|
344
|
+
progress.extractedToolData[event.toolName] = progress.extractedToolData[event.toolName] || [];
|
|
345
|
+
progress.extractedToolData[event.toolName].push(data);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check if handler wants to terminate subprocess
|
|
350
|
+
if (
|
|
351
|
+
handler.shouldTerminate?.({
|
|
352
|
+
toolName: event.toolName,
|
|
353
|
+
toolCallId: event.toolCallId,
|
|
354
|
+
args: event.args,
|
|
355
|
+
result: event.result,
|
|
356
|
+
isError: event.isError,
|
|
357
|
+
})
|
|
358
|
+
) {
|
|
359
|
+
// Don't kill immediately - wait for message_end to get token counts
|
|
360
|
+
pendingTermination = true;
|
|
361
|
+
// Safety timeout in case message_end never arrives
|
|
362
|
+
setTimeout(() => {
|
|
363
|
+
if (!resolved) {
|
|
364
|
+
resolved = true;
|
|
365
|
+
proc.kill("SIGTERM");
|
|
366
|
+
}
|
|
367
|
+
}, 2000);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case "message_update": {
|
|
374
|
+
// Extract text for progress display only (replace, don't accumulate)
|
|
375
|
+
const updateContent = event.message?.content || event.content;
|
|
376
|
+
if (updateContent && Array.isArray(updateContent)) {
|
|
377
|
+
const allText: string[] = [];
|
|
378
|
+
for (const block of updateContent) {
|
|
379
|
+
if (block.type === "text" && block.text) {
|
|
380
|
+
const lines = block.text.split("\n").filter((l: string) => l.trim());
|
|
381
|
+
allText.push(...lines);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Show last 8 lines from current state (not accumulated)
|
|
385
|
+
progress.recentOutput = allText.slice(-8).reverse();
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
case "message_end": {
|
|
391
|
+
// Extract final text content from completed message
|
|
392
|
+
const messageContent = event.message?.content || event.content;
|
|
393
|
+
if (messageContent && Array.isArray(messageContent)) {
|
|
394
|
+
for (const block of messageContent) {
|
|
395
|
+
if (block.type === "text" && block.text) {
|
|
396
|
+
output += block.text;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Extract usage (prefer message.usage, fallback to event.usage)
|
|
401
|
+
const messageUsage = event.message?.usage || event.usage;
|
|
402
|
+
if (messageUsage) {
|
|
403
|
+
// Accumulate tokens across messages (not overwrite)
|
|
404
|
+
progress.tokens += getUsageTokens(messageUsage);
|
|
405
|
+
}
|
|
406
|
+
// If pending termination, now we have tokens - terminate
|
|
407
|
+
if (pendingTermination && !resolved) {
|
|
408
|
+
resolved = true;
|
|
409
|
+
proc.kill("SIGTERM");
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case "agent_end":
|
|
415
|
+
// Extract final content from messages array
|
|
416
|
+
if (event.messages && Array.isArray(event.messages)) {
|
|
417
|
+
for (const msg of event.messages) {
|
|
418
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
419
|
+
for (const block of msg.content) {
|
|
420
|
+
if (block.type === "text" && block.text) {
|
|
421
|
+
finalOutput += block.text;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
progress.durationMs = now - startTime;
|
|
431
|
+
// Clone progress object before passing to callback to prevent mutation during render
|
|
432
|
+
onProgress?.({ ...progress });
|
|
433
|
+
} catch {
|
|
434
|
+
// Ignore non-JSON lines
|
|
435
|
+
}
|
|
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
|
+
});
|
|
443
|
+
|
|
444
|
+
// Wait for readline to finish BEFORE resolving
|
|
445
|
+
const exitCode = await new Promise<number>((resolve) => {
|
|
446
|
+
let code: number | null = null;
|
|
447
|
+
let rlClosed = false;
|
|
448
|
+
let procClosed = false;
|
|
449
|
+
|
|
450
|
+
const maybeResolve = () => {
|
|
451
|
+
if (rlClosed && procClosed) {
|
|
452
|
+
resolved = true;
|
|
453
|
+
resolve(code ?? 1);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
rl.on("close", () => {
|
|
458
|
+
rlClosed = true;
|
|
459
|
+
maybeResolve();
|
|
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
|
+
});
|
|
475
|
+
|
|
476
|
+
// Cleanup
|
|
477
|
+
if (signal) {
|
|
478
|
+
signal.removeEventListener("abort", onAbort);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
fs.unlinkSync(promptFile);
|
|
483
|
+
} catch {
|
|
484
|
+
// Ignore cleanup errors
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Use final output if available, otherwise accumulated output
|
|
488
|
+
const rawOutput = finalOutput || output;
|
|
489
|
+
const { text: truncatedOutput, truncated } = truncateOutput(rawOutput);
|
|
490
|
+
|
|
491
|
+
// Write output artifact (input and jsonl already written in real-time)
|
|
492
|
+
// Compute output metadata for Output tool integration
|
|
493
|
+
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
494
|
+
if (artifactPaths) {
|
|
495
|
+
try {
|
|
496
|
+
fs.writeFileSync(artifactPaths.outputPath, rawOutput, "utf-8");
|
|
497
|
+
outputMeta = {
|
|
498
|
+
lineCount: rawOutput.split("\n").length,
|
|
499
|
+
charCount: rawOutput.length,
|
|
500
|
+
};
|
|
501
|
+
} catch {
|
|
502
|
+
// Non-fatal
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Update final progress
|
|
507
|
+
const wasAborted = signal?.aborted ?? false;
|
|
508
|
+
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
509
|
+
progress.durationMs = Date.now() - startTime;
|
|
510
|
+
onProgress?.(progress);
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
index,
|
|
514
|
+
agent: agent.name,
|
|
515
|
+
agentSource: agent.source,
|
|
516
|
+
task,
|
|
517
|
+
exitCode,
|
|
518
|
+
output: truncatedOutput,
|
|
519
|
+
stderr,
|
|
520
|
+
truncated,
|
|
521
|
+
durationMs: Date.now() - startTime,
|
|
522
|
+
tokens: progress.tokens,
|
|
523
|
+
modelOverride,
|
|
524
|
+
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
525
|
+
aborted: wasAborted,
|
|
526
|
+
jsonlEvents,
|
|
527
|
+
artifactPaths,
|
|
528
|
+
extractedToolData: progress.extractedToolData,
|
|
529
|
+
outputMeta,
|
|
530
|
+
};
|
|
531
|
+
}
|