@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11
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 +117 -0
- package/dist/cli.js +23087 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/async/job-manager.d.ts +18 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +1 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
- package/dist/types/cli/usage-cli.d.ts +72 -0
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/model-discovery.d.ts +55 -0
- package/dist/types/config/model-registry.d.ts +20 -219
- package/dist/types/config/model-resolver.d.ts +16 -10
- package/dist/types/config/model-roles.d.ts +28 -0
- package/dist/types/config/models-config-schema.d.ts +523 -42
- package/dist/types/config/models-config.d.ts +385 -0
- package/dist/types/config/settings-schema.d.ts +12 -16
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/debug/terminal-info.d.ts +0 -1
- package/dist/types/eval/backend.d.ts +0 -2
- package/dist/types/eval/idle-timeout.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -3
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/index.d.ts +2 -2
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +3 -0
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/mnemopi/state.d.ts +11 -1
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +31 -26
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +19 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/github-cache.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +3 -0
- package/dist/types/tools/todo.d.ts +2 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +1 -1
- package/examples/extensions/tools.ts +5 -4
- package/package.json +14 -11
- package/scripts/build-binary.ts +18 -23
- package/scripts/bundle-dist.ts +81 -0
- package/scripts/{dev-launch → omp} +1 -1
- package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
- package/src/async/job-manager.ts +57 -3
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/capability/fs.ts +10 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -3
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +1 -1
- package/src/cli/gallery-fixtures/types.ts +5 -1
- package/src/cli/list-models.ts +7 -12
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +69 -5
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commands/read.ts +6 -3
- package/src/commands/usage.ts +35 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/model-selection.ts +1 -1
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +308 -1025
- package/src/config/model-resolver.ts +113 -156
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +57 -8
- package/src/config/models-config.ts +129 -0
- package/src/config/settings-schema.ts +18 -14
- package/src/config/settings.ts +37 -1
- package/src/dap/client.ts +124 -37
- package/src/dap/session.ts +259 -158
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/raw-sse.ts +1 -1
- package/src/debug/terminal-info.ts +0 -3
- package/src/edit/diff.ts +95 -18
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +8 -2
- package/src/edit/index.ts +16 -1
- package/src/edit/modes/patch.ts +52 -0
- package/src/edit/modes/replace.ts +56 -22
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +36 -10
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/backend.ts +0 -2
- package/src/eval/completion-bridge.ts +2 -1
- package/src/eval/idle-timeout.ts +2 -9
- package/src/eval/js/context-manager.ts +6 -8
- package/src/eval/js/executor.ts +6 -2
- package/src/eval/js/index.ts +0 -2
- package/src/eval/js/shared/helpers.ts +5 -6
- package/src/eval/js/shared/local-module-loader.ts +1 -1
- package/src/eval/js/shared/prelude.txt +62 -1
- package/src/eval/js/shared/rewrite-imports.ts +49 -23
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +0 -2
- package/src/eval/py/kernel.ts +19 -0
- package/src/eval/py/runner.py +107 -3
- package/src/exec/bash-executor.ts +3 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/types.ts +3 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +6 -1
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/issue-pr-protocol.ts +12 -5
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/lib/xai-http.ts +1 -1
- package/src/lsp/client.ts +118 -38
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/index.ts +31 -22
- package/src/lsp/render.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/lsp/utils.ts +28 -10
- package/src/main.ts +165 -17
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +2 -1
- package/src/mnemopi/backend.ts +25 -3
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +19 -13
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/copy-selector.ts +1 -1
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +1 -1
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-selector.ts +66 -54
- package/src/modes/components/plan-review-overlay.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-selector.ts +5 -1
- package/src/modes/components/status-line/component.ts +1 -1
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +373 -141
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +17 -5
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +108 -26
- package/src/modes/controllers/command-controller.ts +10 -3
- package/src/modes/controllers/event-controller.ts +73 -49
- package/src/modes/controllers/input-controller.ts +5 -5
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -5
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/interactive-mode.ts +5 -19
- package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
- package/src/modes/setup-wizard/scenes/providers.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +1 -1
- package/src/modes/setup-wizard/scenes/types.ts +1 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +2 -1
- package/src/prompts/agents/explore.md +2 -2
- package/src/prompts/agents/librarian.md +1 -2
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +5 -5
- package/src/prompts/agents/task.md +5 -5
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/background-tan-dispatch.md +1 -1
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +2 -2
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/system/manual-continue.md +1 -1
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +9 -9
- package/src/prompts/system/plan-mode-active.md +4 -4
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +15 -26
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +8 -10
- package/src/prompts/tools/browser.md +7 -7
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +15 -15
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +3 -4
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task.md +2 -3
- package/src/prompts/tools/todo.md +6 -2
- package/src/sdk.ts +23 -10
- package/src/session/agent-session.ts +44 -10
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +23 -2
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/ssh/connection-manager.ts +27 -0
- package/src/task/commands.ts +2 -1
- package/src/task/discovery.ts +17 -24
- package/src/task/executor.ts +61 -53
- package/src/task/index.ts +137 -60
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +2 -2
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +2 -1
- package/src/tiny/title-client.ts +32 -14
- package/src/tools/archive-reader.ts +30 -2
- package/src/tools/ask.ts +104 -21
- package/src/tools/ast-edit.ts +25 -5
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +27 -7
- package/src/tools/bash.ts +54 -13
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +4 -1
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +55 -16
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +5 -5
- package/src/tools/eval.ts +0 -2
- package/src/tools/fetch.ts +33 -10
- package/src/tools/gh-cache-invalidation.ts +63 -8
- package/src/tools/gh-renderer.ts +1 -1
- package/src/tools/gh.ts +172 -29
- package/src/tools/github-cache.ts +70 -6
- package/src/tools/image-gen.ts +3 -9
- package/src/tools/irc.ts +5 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/read.ts +202 -61
- package/src/tools/render-utils.ts +3 -3
- package/src/tools/resolve.ts +1 -1
- package/src/tools/search.ts +92 -29
- package/src/tools/sqlite-reader.ts +17 -5
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +51 -12
- package/src/tools/write.ts +118 -18
- package/src/tui/output-block.ts +4 -4
- package/src/utils/changelog.ts +27 -1
- package/src/utils/file-mentions.ts +2 -1
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +6 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/providers/anthropic.ts +8 -2
- package/src/web/search/providers/codex.ts +2 -1
- package/src/web/search/providers/gemini.ts +2 -3
- package/src/web/search/render.ts +8 -6
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-id-affixes.d.ts +0 -12
- package/dist/types/config/model-provider-priority.d.ts +0 -1
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/src/config/model-equivalence.ts +0 -875
- package/src/config/model-id-affixes.ts +0 -81
- package/src/config/model-provider-priority.ts +0 -56
- package/src/exec/idle-timeout-watchdog.ts +0 -126
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as stats from "@oh-my-pi/omp-stats";
|
|
2
|
+
import * as openUtils from "../../utils/open";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_STATS_DASHBOARD_PORT = 3847;
|
|
5
|
+
|
|
6
|
+
interface StatsDashboardServer {
|
|
7
|
+
port: number;
|
|
8
|
+
stop: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StatsDashboardArgs {
|
|
12
|
+
port: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface StatsDashboardLaunchResult {
|
|
16
|
+
url: string;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let activeStatsServer: StatsDashboardServer | undefined;
|
|
21
|
+
|
|
22
|
+
const STATS_DASHBOARD_USAGE = "Usage: /stats [--port <port>]";
|
|
23
|
+
|
|
24
|
+
function parsePort(value: string | undefined): number | string {
|
|
25
|
+
if (!value) return `Missing port. ${STATS_DASHBOARD_USAGE}`;
|
|
26
|
+
if (!/^\d+$/.test(value)) return `Invalid port: ${value}`;
|
|
27
|
+
const port = Number(value);
|
|
28
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) return `Invalid port: ${value}`;
|
|
29
|
+
return port;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parseStatsDashboardArgs(args: string): StatsDashboardArgs | { error: string } {
|
|
33
|
+
const tokens = args.split(/\s+/).filter(Boolean);
|
|
34
|
+
let port = DEFAULT_STATS_DASHBOARD_PORT;
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
37
|
+
const token = tokens[i];
|
|
38
|
+
if (token === "--port" || token === "-p") {
|
|
39
|
+
const parsed = parsePort(tokens[++i]);
|
|
40
|
+
if (typeof parsed === "string") return { error: parsed };
|
|
41
|
+
port = parsed;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (token.startsWith("--port=")) {
|
|
45
|
+
const parsed = parsePort(token.slice("--port=".length));
|
|
46
|
+
if (typeof parsed === "string") return { error: parsed };
|
|
47
|
+
port = parsed;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
return { error: `Unknown option: ${token}. ${STATS_DASHBOARD_USAGE}` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { port };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function launchStatsDashboard(args: StatsDashboardArgs): Promise<StatsDashboardLaunchResult> {
|
|
57
|
+
const { processed, files } = await stats.syncAllSessions();
|
|
58
|
+
const total = await stats.getTotalMessageCount();
|
|
59
|
+
let requestedPortIgnored = false;
|
|
60
|
+
|
|
61
|
+
if (!activeStatsServer) {
|
|
62
|
+
activeStatsServer = await stats.startServer(args.port);
|
|
63
|
+
} else if (args.port !== activeStatsServer.port) {
|
|
64
|
+
requestedPortIgnored = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const url = `http://localhost:${activeStatsServer.port}`;
|
|
68
|
+
openUtils.openPath(url);
|
|
69
|
+
|
|
70
|
+
const serverLine = requestedPortIgnored
|
|
71
|
+
? `Dashboard already running at: ${url} (requested port ${args.port} ignored)`
|
|
72
|
+
: `Dashboard available at: ${url}`;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
url,
|
|
76
|
+
message: `Synced ${processed} new entries from ${files} files (${total} total)\n${serverLine}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function stopStatsDashboard(): void {
|
|
81
|
+
if (!activeStatsServer) return;
|
|
82
|
+
activeStatsServer.stop();
|
|
83
|
+
activeStatsServer = undefined;
|
|
84
|
+
stats.closeDb();
|
|
85
|
+
}
|
|
@@ -355,6 +355,33 @@ export async function getHostInfoForHost(host: SSHConnectionTarget): Promise<SSH
|
|
|
355
355
|
return await loadHostInfoFromDisk(host);
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Synchronous, probe-free host info lookup for startup paths.
|
|
360
|
+
*
|
|
361
|
+
* Checks the in-memory cache, then falls back to a synchronous read of the
|
|
362
|
+
* persisted host-info cache file. Never opens a connection or probes the
|
|
363
|
+
* remote host — callers get `undefined` when nothing is cached yet.
|
|
364
|
+
*/
|
|
365
|
+
export function getCachedHostInfoSync(host: SSHConnectionTarget): SSHHostInfo | undefined {
|
|
366
|
+
const cached = hostInfoCache.get(host.name);
|
|
367
|
+
if (cached) {
|
|
368
|
+
const resolved = applyCompatOverride(host, cached);
|
|
369
|
+
if (resolved !== cached) hostInfoCache.set(host.name, resolved);
|
|
370
|
+
return resolved;
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
const parsed = parseHostInfo(JSON.parse(fs.readFileSync(getHostInfoPath(host.name), "utf-8")));
|
|
374
|
+
if (!parsed) return undefined;
|
|
375
|
+
const resolved = applyCompatOverride(host, parsed);
|
|
376
|
+
hostInfoCache.set(host.name, resolved);
|
|
377
|
+
return resolved;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (isEnoent(err)) return undefined;
|
|
380
|
+
logger.warn("Failed to load SSH host info", { host: host.name, error: String(err) });
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
358
385
|
export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
|
|
359
386
|
const cached = hostInfoCache.get(host.name);
|
|
360
387
|
if (cached) {
|
package/src/task/commands.ts
CHANGED
|
@@ -120,7 +120,8 @@ export function getCommand(commands: WorkflowCommand[], name: string): WorkflowC
|
|
|
120
120
|
* Replaces $@ with the provided input.
|
|
121
121
|
*/
|
|
122
122
|
export function expandCommand(command: WorkflowCommand, input: string): string {
|
|
123
|
-
|
|
123
|
+
// Function replacement so `$`-patterns in user input ($$, $&, ...) stay literal.
|
|
124
|
+
return command.instructions.replace(/\$@/g, () => input);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
package/src/task/discovery.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent discovery from filesystem.
|
|
3
3
|
*
|
|
4
|
-
* Discovers agent definitions from:
|
|
5
|
-
* - ~/.omp/agent/agents/*.md (user-level
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Discovers agent definitions from OMP-native task-agent roots:
|
|
5
|
+
* - ~/.omp/agent/agents/*.md (user-level)
|
|
6
|
+
* - .omp/agents/*.md (project-level)
|
|
7
|
+
*
|
|
8
|
+
* Claude Code marketplace plugin agents are discovered separately via the
|
|
9
|
+
* claude-plugins provider. Direct cross-harness roots such as .claude/agents
|
|
10
|
+
* are intentionally skipped because their frontmatter schema is not the OMP
|
|
11
|
+
* task-agent contract.
|
|
11
12
|
*
|
|
12
13
|
* Agent files use markdown with YAML frontmatter.
|
|
13
14
|
*/
|
|
@@ -21,6 +22,8 @@ import { listClaudePluginRoots } from "../discovery/helpers";
|
|
|
21
22
|
import { loadBundledAgents, parseAgent } from "./agents";
|
|
22
23
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
23
24
|
|
|
25
|
+
const TASK_AGENT_CONFIG_SOURCE = ".omp";
|
|
26
|
+
|
|
24
27
|
/** Result of agent discovery */
|
|
25
28
|
export interface DiscoveryResult {
|
|
26
29
|
agents: AgentDefinition[];
|
|
@@ -52,41 +55,31 @@ async function loadAgentsFromDir(dir: string, source: AgentSource): Promise<Agen
|
|
|
52
55
|
/**
|
|
53
56
|
* Discover agents from filesystem and merge with bundled agents.
|
|
54
57
|
*
|
|
55
|
-
* Precedence (highest wins): .omp
|
|
56
|
-
*
|
|
58
|
+
* Precedence (highest wins): project .omp, user .omp, Claude plugin agents, then bundled
|
|
57
59
|
* @param cwd - Current working directory for project agent discovery
|
|
58
60
|
*/
|
|
59
61
|
export async function discoverAgents(cwd: string, home: string = os.homedir()): Promise<DiscoveryResult> {
|
|
60
62
|
const resolvedCwd = path.resolve(cwd);
|
|
61
|
-
const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map(entry => entry.source)));
|
|
62
63
|
|
|
63
|
-
// Get user directories (priority order: .omp, .pi, .claude, ...)
|
|
64
64
|
const userDirs = getConfigDirs("agents", { project: false })
|
|
65
|
-
.filter(entry =>
|
|
65
|
+
.filter(entry => entry.source === TASK_AGENT_CONFIG_SOURCE)
|
|
66
66
|
.map(entry => ({
|
|
67
67
|
...entry,
|
|
68
68
|
path: path.resolve(entry.path),
|
|
69
69
|
}));
|
|
70
70
|
|
|
71
|
-
// Get project directories by walking up from cwd (priority order)
|
|
72
71
|
const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
|
|
73
|
-
.filter(entry =>
|
|
72
|
+
.filter(entry => entry.source === TASK_AGENT_CONFIG_SOURCE)
|
|
74
73
|
.map(entry => ({
|
|
75
74
|
...entry,
|
|
76
75
|
path: path.resolve(entry.path),
|
|
77
76
|
}));
|
|
78
77
|
|
|
79
|
-
const orderedSources = agentSources.filter(
|
|
80
|
-
source => userDirs.some(entry => entry.source === source) || projectDirs.some(entry => entry.source === source),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
78
|
const orderedDirs: Array<{ dir: string; source: AgentSource }> = [];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
|
89
|
-
}
|
|
79
|
+
const project = projectDirs[0];
|
|
80
|
+
if (project) orderedDirs.push({ dir: project.path, source: "project" });
|
|
81
|
+
const user = userDirs[0];
|
|
82
|
+
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
|
90
83
|
|
|
91
84
|
// Load agents from Claude Code marketplace plugins (respects disabledProviders)
|
|
92
85
|
const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
|
package/src/task/executor.ts
CHANGED
|
@@ -1285,59 +1285,67 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1285
1285
|
|
|
1286
1286
|
const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
|
|
1287
1287
|
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1288
|
+
const sessionPromise = createAgentSession({
|
|
1289
|
+
cwd: worktree ?? cwd,
|
|
1290
|
+
authStorage,
|
|
1291
|
+
modelRegistry,
|
|
1292
|
+
settings: subagentSettings,
|
|
1293
|
+
model,
|
|
1294
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
1295
|
+
toolNames,
|
|
1296
|
+
outputSchema,
|
|
1297
|
+
requireYieldTool: true,
|
|
1298
|
+
contextFiles: options.contextFiles,
|
|
1299
|
+
skills: options.skills,
|
|
1300
|
+
promptTemplates: options.promptTemplates,
|
|
1301
|
+
workspaceTree: options.workspaceTree,
|
|
1302
|
+
rules: options.rules,
|
|
1303
|
+
preloadedExtensionPaths: options.preloadedExtensionPaths,
|
|
1304
|
+
preloadedCustomToolPaths: options.preloadedCustomToolPaths,
|
|
1305
|
+
systemPrompt: defaultPrompt => {
|
|
1306
|
+
const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
|
|
1307
|
+
agent: agent.systemPrompt,
|
|
1308
|
+
context: options.context?.trim() ?? "",
|
|
1309
|
+
planReference: options.planReference?.content ?? "",
|
|
1310
|
+
planReferencePath: options.planReference?.path ?? "",
|
|
1311
|
+
worktree: worktree ?? "",
|
|
1312
|
+
outputSchema: normalizedOutputSchema,
|
|
1313
|
+
contextFile: contextFileForPrompt,
|
|
1314
|
+
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
1315
|
+
ircSelfId: ircEnabled ? id : "",
|
|
1316
|
+
});
|
|
1317
|
+
return defaultPrompt.length === 0
|
|
1318
|
+
? [subagentPrompt]
|
|
1319
|
+
: [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
|
|
1320
|
+
},
|
|
1321
|
+
sessionManager,
|
|
1322
|
+
hasUI: false,
|
|
1323
|
+
spawns: spawnsEnv,
|
|
1324
|
+
taskDepth: childDepth,
|
|
1325
|
+
parentHindsightSessionState: options.parentHindsightSessionState,
|
|
1326
|
+
parentMnemopiSessionState: options.parentMnemopiSessionState,
|
|
1327
|
+
parentTaskPrefix: id,
|
|
1328
|
+
agentId: id,
|
|
1329
|
+
agentDisplayName: agent.name,
|
|
1330
|
+
enableLsp: lspEnabled,
|
|
1331
|
+
skipPythonPreflight,
|
|
1332
|
+
enableMCP,
|
|
1333
|
+
mcpManager: options.mcpManager,
|
|
1334
|
+
customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
|
|
1335
|
+
localProtocolOptions: options.localProtocolOptions,
|
|
1336
|
+
telemetry: subagentTelemetry,
|
|
1337
|
+
parentEvalSessionId: options.parentEvalSessionId,
|
|
1338
|
+
});
|
|
1339
|
+
let session: AgentSession;
|
|
1340
|
+
try {
|
|
1341
|
+
({ session } = await awaitAbortable(sessionPromise));
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
// Abort raced session startup. The session may still resolve later
|
|
1344
|
+
// holding live LSP/MCP child processes — dispose it when it does so
|
|
1345
|
+
// a cancelled subagent cannot leak them.
|
|
1346
|
+
void sessionPromise.then(created => created.session.dispose()).catch(() => {});
|
|
1347
|
+
throw err;
|
|
1348
|
+
}
|
|
1341
1349
|
|
|
1342
1350
|
activeSession = session;
|
|
1343
1351
|
|
package/src/task/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ import type { LocalProtocolOptions } from "../internal-urls";
|
|
|
43
43
|
import { loadOverallPlanReference } from "../plan-mode/plan-handoff";
|
|
44
44
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
45
45
|
import * as git from "../utils/git";
|
|
46
|
-
import { discoverAgents, getAgent } from "./discovery";
|
|
46
|
+
import { type DiscoveryResult, discoverAgents, getAgent } from "./discovery";
|
|
47
47
|
import { runSubprocess } from "./executor";
|
|
48
48
|
import { AgentOutputManager } from "./output-manager";
|
|
49
49
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
@@ -242,6 +242,88 @@ function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams):
|
|
|
242
242
|
return "task.simple is set to independent, so the task tool does not accept `context` or `schema`. Put all required background and output expectations inside each task assignment or the selected agent definition.";
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/** Sentinel for async jobs whose subagent finished with a failing result; batch counters are already updated. */
|
|
246
|
+
class TaskJobError extends Error {}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validate task ids: every task needs a non-empty id and ids must be unique
|
|
250
|
+
* (case-insensitive). Returns a problem description, or undefined when valid.
|
|
251
|
+
*/
|
|
252
|
+
function validateTaskIds(tasks: TaskParams["tasks"]): string | undefined {
|
|
253
|
+
const missingTaskIndexes: number[] = [];
|
|
254
|
+
const idIndexes = new Map<string, number[]>();
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
257
|
+
const id = tasks[i]?.id;
|
|
258
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
259
|
+
missingTaskIndexes.push(i);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const normalizedId = id.toLowerCase();
|
|
263
|
+
const indexes = idIndexes.get(normalizedId);
|
|
264
|
+
if (indexes) {
|
|
265
|
+
indexes.push(i);
|
|
266
|
+
} else {
|
|
267
|
+
idIndexes.set(normalizedId, [i]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
|
|
272
|
+
for (const [normalizedId, indexes] of idIndexes.entries()) {
|
|
273
|
+
if (indexes.length > 1) {
|
|
274
|
+
duplicateIds.push({
|
|
275
|
+
id: tasks[indexes[0]]?.id ?? normalizedId,
|
|
276
|
+
indexes,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (missingTaskIndexes.length === 0 && duplicateIds.length === 0) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const problems: string[] = [];
|
|
286
|
+
if (missingTaskIndexes.length > 0) {
|
|
287
|
+
problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
|
|
288
|
+
}
|
|
289
|
+
if (duplicateIds.length > 0) {
|
|
290
|
+
const details = duplicateIds.map(entry => `${entry.id} (indexes ${entry.indexes.join(", ")})`).join("; ");
|
|
291
|
+
problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
|
|
292
|
+
}
|
|
293
|
+
return `Invalid tasks: ${problems.join(". ")}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Process-level memo for create-time agent discovery, keyed by resolved cwd.
|
|
298
|
+
*
|
|
299
|
+
* `TaskTool.create` runs for every (sub)agent session in this process and the
|
|
300
|
+
* walk-up + plugin-registry scan in `discoverAgents` is identical for a given
|
|
301
|
+
* cwd, so repeat creations reuse the first scan. Execution-time discovery
|
|
302
|
+
* (`#executeSync`) intentionally stays fresh. The memo also tracks the live
|
|
303
|
+
* `discoverAgents` binding: test spies swap that binding, which invalidates
|
|
304
|
+
* the memo automatically.
|
|
305
|
+
*/
|
|
306
|
+
const discoveryMemo = new Map<string, Promise<DiscoveryResult>>();
|
|
307
|
+
let discoveryMemoFn: typeof discoverAgents | undefined;
|
|
308
|
+
|
|
309
|
+
function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
|
|
310
|
+
const fn = discoverAgents;
|
|
311
|
+
if (discoveryMemoFn !== fn) {
|
|
312
|
+
discoveryMemoFn = fn;
|
|
313
|
+
discoveryMemo.clear();
|
|
314
|
+
}
|
|
315
|
+
const key = path.resolve(cwd);
|
|
316
|
+
let pending = discoveryMemo.get(key);
|
|
317
|
+
if (!pending) {
|
|
318
|
+
pending = fn(cwd);
|
|
319
|
+
discoveryMemo.set(key, pending);
|
|
320
|
+
pending.catch(() => {
|
|
321
|
+
if (discoveryMemo.get(key) === pending) discoveryMemo.delete(key);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return pending;
|
|
325
|
+
}
|
|
326
|
+
|
|
245
327
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
246
328
|
// Tool Class
|
|
247
329
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -325,7 +407,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
325
407
|
* Create a TaskTool instance with async agent discovery.
|
|
326
408
|
*/
|
|
327
409
|
static async create(session: ToolSession): Promise<TaskTool> {
|
|
328
|
-
const { agents } = await
|
|
410
|
+
const { agents } = await discoverAgentsForCreate(session.cwd);
|
|
329
411
|
return new TaskTool(session, agents);
|
|
330
412
|
}
|
|
331
413
|
|
|
@@ -363,6 +445,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
363
445
|
return this.#executeSync(_toolCallId, params, signal, onUpdate);
|
|
364
446
|
}
|
|
365
447
|
|
|
448
|
+
const taskIdProblem = validateTaskIds(taskItems);
|
|
449
|
+
if (taskIdProblem) {
|
|
450
|
+
return createTaskModeError(taskIdProblem);
|
|
451
|
+
}
|
|
452
|
+
|
|
366
453
|
const outputManager =
|
|
367
454
|
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
368
455
|
const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id));
|
|
@@ -396,9 +483,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
396
483
|
let failedJobs = 0;
|
|
397
484
|
|
|
398
485
|
const getProgressSnapshot = (): AgentProgress[] => {
|
|
486
|
+
// Shallow copies: top-level fields are reassigned (never mutated in
|
|
487
|
+
// place) and the large nested payloads (extractedToolData) are
|
|
488
|
+
// immutable once attached — structuredClone here cost O(batch × payload)
|
|
489
|
+
// per progress event.
|
|
399
490
|
return Array.from(progressByTaskId.values())
|
|
400
491
|
.sort((a, b) => a.index - b.index)
|
|
401
|
-
.map(progress =>
|
|
492
|
+
.map(progress => ({ ...progress }));
|
|
402
493
|
};
|
|
403
494
|
|
|
404
495
|
const buildAsyncDetails = (state: "running" | "completed" | "failed", jobId: string): TaskToolDetails => ({
|
|
@@ -424,6 +515,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
424
515
|
const taskItem = taskItems[i];
|
|
425
516
|
if (signal?.aborted) {
|
|
426
517
|
failedSchedules.push(`${taskItem.id}: cancelled before scheduling`);
|
|
518
|
+
completedJobs += 1;
|
|
427
519
|
const progress = progressByTaskId.get(taskItem.id);
|
|
428
520
|
if (progress) {
|
|
429
521
|
progress.status = "aborted";
|
|
@@ -438,7 +530,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
438
530
|
const jobId = manager.register(
|
|
439
531
|
"task",
|
|
440
532
|
label,
|
|
441
|
-
async ({ signal: runSignal, reportProgress }) => {
|
|
533
|
+
async ({ signal: runSignal, reportProgress, markRunning }) => {
|
|
442
534
|
const startedAt = Date.now();
|
|
443
535
|
const progress = progressByTaskId.get(taskItem.id);
|
|
444
536
|
await semaphore.acquire();
|
|
@@ -447,8 +539,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
447
539
|
if (progress) {
|
|
448
540
|
progress.status = "aborted";
|
|
449
541
|
}
|
|
542
|
+
completedJobs += 1;
|
|
543
|
+
failedJobs += 1;
|
|
450
544
|
throw new Error("Aborted before execution");
|
|
451
545
|
}
|
|
546
|
+
markRunning();
|
|
452
547
|
if (progress) {
|
|
453
548
|
progress.status = "running";
|
|
454
549
|
}
|
|
@@ -462,12 +557,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
462
557
|
]);
|
|
463
558
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
464
559
|
const singleResult = result.details?.results[0];
|
|
560
|
+
// A missing per-task result means #executeSync failed at the
|
|
561
|
+
// tool level (results: []) — treat it as a failure, not success.
|
|
562
|
+
const resultFailed =
|
|
563
|
+
!singleResult || (singleResult.aborted ?? false) || singleResult.exitCode !== 0;
|
|
465
564
|
if (progress) {
|
|
466
|
-
progress.status = singleResult?.aborted
|
|
467
|
-
? "aborted"
|
|
468
|
-
: (singleResult?.exitCode ?? 0) === 0
|
|
469
|
-
? "completed"
|
|
470
|
-
: "failed";
|
|
565
|
+
progress.status = singleResult?.aborted ? "aborted" : resultFailed ? "failed" : "completed";
|
|
471
566
|
progress.durationMs = singleResult?.durationMs ?? Math.max(0, Date.now() - startedAt);
|
|
472
567
|
progress.tokens = singleResult?.tokens ?? 0;
|
|
473
568
|
progress.contextTokens = singleResult?.contextTokens;
|
|
@@ -478,7 +573,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
478
573
|
progress.retryState = undefined;
|
|
479
574
|
}
|
|
480
575
|
completedJobs += 1;
|
|
481
|
-
if (
|
|
576
|
+
if (resultFailed) {
|
|
482
577
|
failedJobs += 1;
|
|
483
578
|
}
|
|
484
579
|
const remaining = taskItems.length - completedJobs;
|
|
@@ -498,8 +593,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
498
593
|
`Background task batch complete: ${completedJobs}/${taskItems.length} finished.`,
|
|
499
594
|
);
|
|
500
595
|
}
|
|
596
|
+
if (resultFailed) {
|
|
597
|
+
// Mark the job itself failed; counters above are already updated.
|
|
598
|
+
throw new TaskJobError(finalText);
|
|
599
|
+
}
|
|
501
600
|
return finalText;
|
|
502
601
|
} catch (error) {
|
|
602
|
+
if (error instanceof TaskJobError) {
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
503
605
|
if (progress) {
|
|
504
606
|
progress.status = "failed";
|
|
505
607
|
progress.durationMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -530,6 +632,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
530
632
|
},
|
|
531
633
|
{
|
|
532
634
|
id: label,
|
|
635
|
+
queued: true,
|
|
533
636
|
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
534
637
|
onProgress: (text, details) => {
|
|
535
638
|
const progressDetails =
|
|
@@ -543,6 +646,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
543
646
|
} catch (error) {
|
|
544
647
|
const message = error instanceof Error ? error.message : String(error);
|
|
545
648
|
failedSchedules.push(`${taskItem.id}: ${message}`);
|
|
649
|
+
completedJobs += 1;
|
|
546
650
|
const progress = progressByTaskId.get(taskItem.id);
|
|
547
651
|
if (progress) {
|
|
548
652
|
progress.status = "failed";
|
|
@@ -734,45 +838,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
734
838
|
}
|
|
735
839
|
|
|
736
840
|
const tasks = params.tasks;
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
741
|
-
const id = tasks[i]?.id;
|
|
742
|
-
if (typeof id !== "string" || id.trim() === "") {
|
|
743
|
-
missingTaskIndexes.push(i);
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
const normalizedId = id.toLowerCase();
|
|
747
|
-
const indexes = idIndexes.get(normalizedId);
|
|
748
|
-
if (indexes) {
|
|
749
|
-
indexes.push(i);
|
|
750
|
-
} else {
|
|
751
|
-
idIndexes.set(normalizedId, [i]);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
|
|
756
|
-
for (const [normalizedId, indexes] of idIndexes.entries()) {
|
|
757
|
-
if (indexes.length > 1) {
|
|
758
|
-
duplicateIds.push({
|
|
759
|
-
id: tasks[indexes[0]]?.id ?? normalizedId,
|
|
760
|
-
indexes,
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
if (missingTaskIndexes.length > 0 || duplicateIds.length > 0) {
|
|
766
|
-
const problems: string[] = [];
|
|
767
|
-
if (missingTaskIndexes.length > 0) {
|
|
768
|
-
problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
|
|
769
|
-
}
|
|
770
|
-
if (duplicateIds.length > 0) {
|
|
771
|
-
const details = duplicateIds.map(entry => `${entry.id} (indexes ${entry.indexes.join(", ")})`).join("; ");
|
|
772
|
-
problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
|
|
773
|
-
}
|
|
841
|
+
const taskIdProblem = validateTaskIds(tasks);
|
|
842
|
+
if (taskIdProblem) {
|
|
774
843
|
return {
|
|
775
|
-
content: [{ type: "text", text:
|
|
844
|
+
content: [{ type: "text", text: taskIdProblem }],
|
|
776
845
|
details: {
|
|
777
846
|
projectAgentsDir,
|
|
778
847
|
results: [],
|
|
@@ -951,7 +1020,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
951
1020
|
}
|
|
952
1021
|
emitProgress();
|
|
953
1022
|
|
|
954
|
-
const runTask = async (
|
|
1023
|
+
const runTask = async (
|
|
1024
|
+
task: (typeof tasksWithUniqueIds)[number],
|
|
1025
|
+
index: number,
|
|
1026
|
+
workerSignal?: AbortSignal,
|
|
1027
|
+
) => {
|
|
955
1028
|
if (!isIsolated) {
|
|
956
1029
|
return runSubprocess({
|
|
957
1030
|
cwd: this.session.cwd,
|
|
@@ -973,12 +1046,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
973
1046
|
artifactsDir: effectiveArtifactsDir,
|
|
974
1047
|
contextFile: contextFilePath,
|
|
975
1048
|
enableLsp: subagentLspEnabled,
|
|
976
|
-
signal,
|
|
1049
|
+
signal: workerSignal ?? signal,
|
|
977
1050
|
eventBus: this.session.eventBus,
|
|
978
1051
|
onProgress: progress => {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1052
|
+
// Shallow snapshot; recentTools is mutated in place by the
|
|
1053
|
+
// executor, the rest is reassigned or immutable. A deep clone
|
|
1054
|
+
// here cost O(extractedToolData) per progress event.
|
|
1055
|
+
progressMap.set(index, { ...progress, recentTools: progress.recentTools.slice() });
|
|
982
1056
|
emitProgress();
|
|
983
1057
|
},
|
|
984
1058
|
authStorage: this.session.authStorage,
|
|
@@ -1034,12 +1108,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1034
1108
|
artifactsDir: effectiveArtifactsDir,
|
|
1035
1109
|
contextFile: contextFilePath,
|
|
1036
1110
|
enableLsp: subagentLspEnabled,
|
|
1037
|
-
signal,
|
|
1111
|
+
signal: workerSignal ?? signal,
|
|
1038
1112
|
eventBus: this.session.eventBus,
|
|
1039
1113
|
onProgress: progress => {
|
|
1040
|
-
progressMap.set(index, {
|
|
1041
|
-
...structuredClone(progress),
|
|
1042
|
-
});
|
|
1114
|
+
progressMap.set(index, { ...progress, recentTools: progress.recentTools.slice() });
|
|
1043
1115
|
emitProgress();
|
|
1044
1116
|
},
|
|
1045
1117
|
authStorage: this.session.authStorage,
|
|
@@ -1226,6 +1298,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1226
1298
|
const conflictPart = mergeResult.conflict ? `\nConflict: ${mergeResult.conflict}` : "";
|
|
1227
1299
|
mergeSummary = `\n\n<system-notification>Branch merge failed. ${mergedPart}${failedPart}${conflictPart}\nUnmerged branches remain for manual resolution.</system-notification>`;
|
|
1228
1300
|
}
|
|
1301
|
+
if (mergeResult.stashConflict) {
|
|
1302
|
+
mergeSummary += `\n\n<system-notification>${mergeResult.stashConflict}</system-notification>`;
|
|
1303
|
+
}
|
|
1229
1304
|
}
|
|
1230
1305
|
|
|
1231
1306
|
// Clean up merged branches (keep failed ones for manual resolution)
|
|
@@ -1234,9 +1309,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1234
1309
|
await cleanupTaskBranches(repoRoot, allBranches);
|
|
1235
1310
|
}
|
|
1236
1311
|
} else {
|
|
1237
|
-
// Patch mode:
|
|
1238
|
-
|
|
1239
|
-
const
|
|
1312
|
+
// Patch mode: apply patches from successful tasks. Failed or
|
|
1313
|
+
// aborted siblings must not block completed work from landing.
|
|
1314
|
+
const successfulResults = results.filter(r => r.exitCode === 0 && !r.error && !r.aborted);
|
|
1315
|
+
const patchesInOrder = successfulResults.map(result => result.patchPath).filter(Boolean) as string[];
|
|
1316
|
+
const missingPatch = successfulResults.some(result => !result.patchPath);
|
|
1240
1317
|
if (missingPatch) {
|
|
1241
1318
|
changesApplied = false;
|
|
1242
1319
|
hadAnyChanges = false;
|