@selesai/code 0.5.19 → 0.5.21
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/dist/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +2 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
3
5
|
import * as path from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { describe, it } from "node:test";
|
|
8
|
+
import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/subagent-wait.ts";
|
|
6
9
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../../src/runs/shared/pi-args.ts";
|
|
7
10
|
|
|
8
11
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -11,13 +14,14 @@ function parentToolEnv(): NodeJS.ProcessEnv {
|
|
|
11
14
|
const env = { ...process.env };
|
|
12
15
|
delete env[SUBAGENT_CHILD_ENV];
|
|
13
16
|
delete env[SUBAGENT_FANOUT_CHILD_ENV];
|
|
17
|
+
delete env[WAIT_TOOL_ENABLED_ENV];
|
|
14
18
|
return env;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
describe("subagent extension child mode", () => {
|
|
18
22
|
it("collapses tool detail before direct subagent tool execution", () => {
|
|
19
23
|
const script = String.raw`
|
|
20
|
-
import registerSubagentExtension from "./
|
|
24
|
+
import registerSubagentExtension from "./index.ts";
|
|
21
25
|
const events = { on() { return () => {}; }, emit() {} };
|
|
22
26
|
let registeredTool;
|
|
23
27
|
const fakePi = new Proxy({
|
|
@@ -69,7 +73,7 @@ describe("subagent extension child mode", () => {
|
|
|
69
73
|
|
|
70
74
|
it("does not show async badge for explicit foreground clarify chain calls", () => {
|
|
71
75
|
const script = String.raw`
|
|
72
|
-
import registerSubagentExtension from "./
|
|
76
|
+
import registerSubagentExtension from "./index.ts";
|
|
73
77
|
const events = { on() { return () => {}; }, emit() {} };
|
|
74
78
|
let registeredTool;
|
|
75
79
|
const fakePi = new Proxy({
|
|
@@ -111,9 +115,363 @@ describe("subagent extension child mode", () => {
|
|
|
111
115
|
);
|
|
112
116
|
});
|
|
113
117
|
|
|
118
|
+
it("does not animate foreground results on a timer", () => {
|
|
119
|
+
const script = String.raw`
|
|
120
|
+
import registerSubagentExtension from "./index.ts";
|
|
121
|
+
const events = { on() { return () => {}; }, emit() {} };
|
|
122
|
+
let registeredTool;
|
|
123
|
+
const fakePi = new Proxy({
|
|
124
|
+
events,
|
|
125
|
+
registerTool(tool) { if (tool.name === "subagent") registeredTool = tool; },
|
|
126
|
+
registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
|
|
127
|
+
sendMessage() {}, getSessionName() { return undefined; },
|
|
128
|
+
}, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
|
|
129
|
+
registerSubagentExtension(fakePi);
|
|
130
|
+
if (!registeredTool) throw new Error("tool not registered");
|
|
131
|
+
let invalidations = 0;
|
|
132
|
+
let legacyTicks = 0;
|
|
133
|
+
const context = {
|
|
134
|
+
state: { subagentResultAnimationTimer: setInterval(() => { legacyTicks += 1; }, 10) },
|
|
135
|
+
invalidate() { invalidations += 1; },
|
|
136
|
+
};
|
|
137
|
+
registeredTool.renderResult({
|
|
138
|
+
content: [{ type: "text", text: "running" }],
|
|
139
|
+
details: {
|
|
140
|
+
mode: "single",
|
|
141
|
+
results: [{
|
|
142
|
+
agent: "worker", task: "quiet", exitCode: 0, messages: [],
|
|
143
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
144
|
+
progress: { status: "running", index: 0, agent: "worker", toolCount: 0, tokens: 0, durationMs: 0 },
|
|
145
|
+
}],
|
|
146
|
+
},
|
|
147
|
+
}, { expanded: false }, { fg(_name, text) { return text; }, bold(text) { return text; } }, context);
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
149
|
+
if (context.state.subagentResultAnimationTimer) clearInterval(context.state.subagentResultAnimationTimer);
|
|
150
|
+
if (context.state.subagentResultAnimationTimer !== undefined) throw new Error("legacy timer was not cleared");
|
|
151
|
+
if (legacyTicks !== 0) throw new Error("legacy timer ticked " + legacyTicks + " times");
|
|
152
|
+
if (invalidations !== 0) throw new Error("foreground result invalidated " + invalidations + " times");
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
execFileSync(
|
|
156
|
+
process.execPath,
|
|
157
|
+
[
|
|
158
|
+
"--experimental-transform-types",
|
|
159
|
+
"--import",
|
|
160
|
+
"./test/support/register-loader.mjs",
|
|
161
|
+
"--input-type=module",
|
|
162
|
+
"--eval",
|
|
163
|
+
script,
|
|
164
|
+
],
|
|
165
|
+
{ cwd: projectRoot, env: parentToolEnv(), stdio: "pipe" },
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("registers only subagent_wait and honors waitTool disabled config", () => {
|
|
170
|
+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-wait-tool-config-"));
|
|
171
|
+
try {
|
|
172
|
+
const configDir = path.join(agentDir, "extensions", "subagent");
|
|
173
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
174
|
+
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ waitTool: { enabled: false } }), "utf-8");
|
|
175
|
+
|
|
176
|
+
const script = String.raw`
|
|
177
|
+
import registerSubagentExtension from "./index.ts";
|
|
178
|
+
const events = { on() { return () => {}; }, emit() {} };
|
|
179
|
+
let subagentWaitTool;
|
|
180
|
+
let legacyWaitRegistered = false;
|
|
181
|
+
const fakePi = new Proxy({
|
|
182
|
+
events,
|
|
183
|
+
registerTool(tool) {
|
|
184
|
+
if (tool.name === "subagent_wait") subagentWaitTool = tool;
|
|
185
|
+
if (tool.name === "wait") legacyWaitRegistered = true;
|
|
186
|
+
},
|
|
187
|
+
registerCommand() {},
|
|
188
|
+
registerShortcut() {},
|
|
189
|
+
registerMessageRenderer() {},
|
|
190
|
+
sendMessage() {},
|
|
191
|
+
getSessionName() { return undefined; },
|
|
192
|
+
}, {
|
|
193
|
+
get(target, prop) {
|
|
194
|
+
if (prop in target) return target[prop];
|
|
195
|
+
return () => undefined;
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
registerSubagentExtension(fakePi);
|
|
199
|
+
if (!subagentWaitTool) throw new Error("subagent_wait tool not registered");
|
|
200
|
+
if (legacyWaitRegistered) throw new Error("legacy wait tool must not be registered");
|
|
201
|
+
const result = await subagentWaitTool.execute("subagent-wait-disabled", {}, new AbortController().signal, undefined, {});
|
|
202
|
+
process.stdout.write(JSON.stringify(result.content[0].text));
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
const env = parentToolEnv();
|
|
206
|
+
env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
207
|
+
const output = execFileSync(
|
|
208
|
+
process.execPath,
|
|
209
|
+
[
|
|
210
|
+
"--experimental-transform-types",
|
|
211
|
+
"--import",
|
|
212
|
+
"./test/support/register-loader.mjs",
|
|
213
|
+
"--input-type=module",
|
|
214
|
+
"--eval",
|
|
215
|
+
script,
|
|
216
|
+
],
|
|
217
|
+
{ cwd: projectRoot, env, encoding: "utf-8" },
|
|
218
|
+
);
|
|
219
|
+
assert.match(JSON.parse(output) as string, /disabled/i);
|
|
220
|
+
} finally {
|
|
221
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("does not restore the async widget from tool results when asyncWidget is disabled", () => {
|
|
226
|
+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-async-widget-config-"));
|
|
227
|
+
try {
|
|
228
|
+
const configDir = path.join(agentDir, "extensions", "subagent");
|
|
229
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
230
|
+
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ asyncWidget: false }), "utf-8");
|
|
231
|
+
const script = String.raw`
|
|
232
|
+
import registerSubagentExtension from "./index.ts";
|
|
233
|
+
const eventHandlers = new Map();
|
|
234
|
+
const handlers = new Map();
|
|
235
|
+
const events = { on(channel, handler) { eventHandlers.set(channel, handler); return () => {}; }, emit() {} };
|
|
236
|
+
const fakePi = new Proxy({
|
|
237
|
+
events,
|
|
238
|
+
on(channel, handler) { handlers.set(channel, handler); },
|
|
239
|
+
registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
|
|
240
|
+
sendMessage() {}, getSessionName() { return undefined; },
|
|
241
|
+
}, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
|
|
242
|
+
const widgets = [];
|
|
243
|
+
const ctx = {
|
|
244
|
+
cwd: process.cwd(), hasUI: true,
|
|
245
|
+
ui: { setWidget(key, value) { widgets.push({ key, value }); }, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
|
|
246
|
+
sessionManager: { getSessionId() { return "session-widget"; }, getSessionFile() { return null; }, getEntries() { return []; } },
|
|
247
|
+
modelRegistry: { getAvailable() { return []; } },
|
|
248
|
+
};
|
|
249
|
+
registerSubagentExtension(fakePi);
|
|
250
|
+
handlers.get("session_start")({}, ctx);
|
|
251
|
+
widgets.length = 0;
|
|
252
|
+
eventHandlers.get("subagent:async-started")({ id: "widget-run", pid: 1, sessionId: "session-widget", mode: "single", agent: "worker", asyncDir: "/tmp/widget-run" });
|
|
253
|
+
handlers.get("tool_result")({ toolName: "subagent" }, ctx);
|
|
254
|
+
const asyncWidgets = widgets.filter((entry) => entry.key === "subagent-async");
|
|
255
|
+
if (asyncWidgets.length < 2 || asyncWidgets.some((entry) => entry.value !== undefined)) throw new Error("async widget rendered despite disabled config: " + JSON.stringify(asyncWidgets));
|
|
256
|
+
handlers.get("session_shutdown")();
|
|
257
|
+
`;
|
|
258
|
+
const env = parentToolEnv();
|
|
259
|
+
env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
260
|
+
execFileSync(process.execPath, ["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script], { cwd: projectRoot, env, stdio: "pipe" });
|
|
261
|
+
} finally {
|
|
262
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("disposes pending completion notifications on session shutdown", () => {
|
|
267
|
+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-notify-shutdown-"));
|
|
268
|
+
const configDir = path.join(agentDir, "extensions", "subagent");
|
|
269
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
270
|
+
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ completionBatch: { enabled: true, debounceMs: 150 } }), "utf-8");
|
|
271
|
+
const script = String.raw`
|
|
272
|
+
import registerSubagentExtension from "./index.ts";
|
|
273
|
+
const pendingTimers = new Map();
|
|
274
|
+
const realSetTimeout = globalThis.setTimeout;
|
|
275
|
+
const realClearTimeout = globalThis.clearTimeout;
|
|
276
|
+
globalThis.setTimeout = (handler) => {
|
|
277
|
+
const token = {};
|
|
278
|
+
pendingTimers.set(token, handler);
|
|
279
|
+
return token;
|
|
280
|
+
};
|
|
281
|
+
globalThis.clearTimeout = (token) => pendingTimers.delete(token);
|
|
282
|
+
const eventListeners = new Map();
|
|
283
|
+
const events = {
|
|
284
|
+
on(channel, handler) {
|
|
285
|
+
let listeners = eventListeners.get(channel);
|
|
286
|
+
if (!listeners) eventListeners.set(channel, listeners = new Set());
|
|
287
|
+
listeners.add(handler);
|
|
288
|
+
return () => listeners.delete(handler);
|
|
289
|
+
},
|
|
290
|
+
emit(channel, payload) {
|
|
291
|
+
for (const handler of [...(eventListeners.get(channel) ?? [])]) handler(payload);
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
const handlers = new Map();
|
|
295
|
+
const sent = [];
|
|
296
|
+
const fakePi = new Proxy({
|
|
297
|
+
events,
|
|
298
|
+
on(channel, handler) { handlers.set(channel, handler); },
|
|
299
|
+
registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
|
|
300
|
+
sendMessage(message) { sent.push(message); }, getSessionName() { return undefined; },
|
|
301
|
+
}, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
|
|
302
|
+
const ctx = {
|
|
303
|
+
cwd: process.cwd(), hasUI: false,
|
|
304
|
+
ui: { setWidget() {}, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
|
|
305
|
+
sessionManager: { getSessionId() { return "notify-shutdown-session"; }, getSessionFile() { return null; }, getEntries() { return []; } },
|
|
306
|
+
modelRegistry: { getAvailable() { return []; } },
|
|
307
|
+
};
|
|
308
|
+
registerSubagentExtension(fakePi);
|
|
309
|
+
handlers.get("session_start")({}, ctx);
|
|
310
|
+
sent.length = 0;
|
|
311
|
+
events.emit("subagent:async-complete", {
|
|
312
|
+
id: "shutdown-held-completion", agent: "worker", success: true, summary: "Done",
|
|
313
|
+
exitCode: 0, timestamp: Date.now(), sessionId: "notify-shutdown-session",
|
|
314
|
+
});
|
|
315
|
+
if (sent.length !== 0) throw new Error("completion was not queued before shutdown");
|
|
316
|
+
const heldTimers = [...pendingTimers.values()];
|
|
317
|
+
if (heldTimers.length === 0) throw new Error("completion did not schedule a timer");
|
|
318
|
+
handlers.get("session_shutdown")();
|
|
319
|
+
if (pendingTimers.size !== 0) throw new Error("shutdown left completion timers pending");
|
|
320
|
+
for (const handler of heldTimers) handler();
|
|
321
|
+
if (sent.length !== 0) throw new Error("stale completion sent after shutdown");
|
|
322
|
+
globalThis.setTimeout = realSetTimeout;
|
|
323
|
+
globalThis.clearTimeout = realClearTimeout;
|
|
324
|
+
`;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const env = parentToolEnv();
|
|
328
|
+
env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
329
|
+
execFileSync(
|
|
330
|
+
process.execPath,
|
|
331
|
+
["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
|
|
332
|
+
{ cwd: projectRoot, env, stdio: "pipe" },
|
|
333
|
+
);
|
|
334
|
+
} finally {
|
|
335
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("disposes pending completion notifications during runtime reload cleanup", () => {
|
|
340
|
+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-notify-reload-"));
|
|
341
|
+
const configDir = path.join(agentDir, "extensions", "subagent");
|
|
342
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
343
|
+
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ completionBatch: { enabled: true, debounceMs: 150 } }), "utf-8");
|
|
344
|
+
const script = String.raw`
|
|
345
|
+
import registerSubagentExtension from "./index.ts";
|
|
346
|
+
const pendingTimers = new Map();
|
|
347
|
+
const realSetTimeout = globalThis.setTimeout;
|
|
348
|
+
const realClearTimeout = globalThis.clearTimeout;
|
|
349
|
+
globalThis.setTimeout = (handler) => {
|
|
350
|
+
const token = {};
|
|
351
|
+
pendingTimers.set(token, handler);
|
|
352
|
+
return token;
|
|
353
|
+
};
|
|
354
|
+
globalThis.clearTimeout = (token) => pendingTimers.delete(token);
|
|
355
|
+
function createRuntime(sessionId) {
|
|
356
|
+
const eventListeners = new Map();
|
|
357
|
+
const handlers = new Map();
|
|
358
|
+
const sent = [];
|
|
359
|
+
const events = {
|
|
360
|
+
on(channel, handler) {
|
|
361
|
+
let listeners = eventListeners.get(channel);
|
|
362
|
+
if (!listeners) eventListeners.set(channel, listeners = new Set());
|
|
363
|
+
listeners.add(handler);
|
|
364
|
+
return () => listeners.delete(handler);
|
|
365
|
+
},
|
|
366
|
+
emit(channel, payload) {
|
|
367
|
+
for (const handler of [...(eventListeners.get(channel) ?? [])]) handler(payload);
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
const pi = new Proxy({
|
|
371
|
+
events,
|
|
372
|
+
on(channel, handler) { handlers.set(channel, handler); },
|
|
373
|
+
registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
|
|
374
|
+
sendMessage(message) { sent.push(message); }, getSessionName() { return undefined; },
|
|
375
|
+
}, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
|
|
376
|
+
const ctx = {
|
|
377
|
+
cwd: process.cwd(), hasUI: false,
|
|
378
|
+
ui: { setWidget() {}, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
|
|
379
|
+
sessionManager: { getSessionId() { return sessionId; }, getSessionFile() { return null; }, getEntries() { return []; } },
|
|
380
|
+
modelRegistry: { getAvailable() { return []; } },
|
|
381
|
+
};
|
|
382
|
+
return { pi, events, handlers, sent, ctx };
|
|
383
|
+
}
|
|
384
|
+
const oldRuntime = createRuntime("notify-reload-old");
|
|
385
|
+
registerSubagentExtension(oldRuntime.pi);
|
|
386
|
+
oldRuntime.handlers.get("session_start")({}, oldRuntime.ctx);
|
|
387
|
+
oldRuntime.sent.length = 0;
|
|
388
|
+
const timersBeforeOldCompletion = new Set(pendingTimers.keys());
|
|
389
|
+
oldRuntime.events.emit("subagent:async-complete", {
|
|
390
|
+
id: "reload-held-completion", agent: "worker", success: true, summary: "Old",
|
|
391
|
+
exitCode: 0, timestamp: Date.now(), sessionId: "notify-reload-old",
|
|
392
|
+
});
|
|
393
|
+
if (oldRuntime.sent.length !== 0) throw new Error("old completion was not queued before reload");
|
|
394
|
+
const oldCompletionTimers = [...pendingTimers.entries()].filter(([token]) => !timersBeforeOldCompletion.has(token));
|
|
395
|
+
if (oldCompletionTimers.length === 0) throw new Error("old completion did not schedule a timer");
|
|
396
|
+
|
|
397
|
+
const newRuntime = createRuntime("notify-reload-new");
|
|
398
|
+
registerSubagentExtension(newRuntime.pi);
|
|
399
|
+
newRuntime.handlers.get("session_start")({}, newRuntime.ctx);
|
|
400
|
+
for (const [, handler] of oldCompletionTimers) handler();
|
|
401
|
+
if (oldRuntime.sent.length !== 0) throw new Error("stale completion sent after runtime cleanup");
|
|
402
|
+
|
|
403
|
+
const timersBeforeNewCompletion = new Set(pendingTimers.keys());
|
|
404
|
+
newRuntime.events.emit("subagent:async-complete", {
|
|
405
|
+
id: "reload-new-completion", agent: "reviewer", success: true, summary: "New",
|
|
406
|
+
exitCode: 0, timestamp: Date.now(), sessionId: "notify-reload-new",
|
|
407
|
+
});
|
|
408
|
+
const newCompletionTimers = [...pendingTimers.entries()].filter(([token]) => !timersBeforeNewCompletion.has(token));
|
|
409
|
+
if (newCompletionTimers.length === 0) throw new Error("new completion did not schedule a timer");
|
|
410
|
+
for (const [token, handler] of newCompletionTimers) {
|
|
411
|
+
pendingTimers.delete(token);
|
|
412
|
+
handler();
|
|
413
|
+
}
|
|
414
|
+
if (newRuntime.sent.length !== 1) throw new Error("new notifier was not active after reload cleanup");
|
|
415
|
+
newRuntime.handlers.get("session_shutdown")();
|
|
416
|
+
globalThis.setTimeout = realSetTimeout;
|
|
417
|
+
globalThis.clearTimeout = realClearTimeout;
|
|
418
|
+
`;
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const env = parentToolEnv();
|
|
422
|
+
env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
423
|
+
execFileSync(
|
|
424
|
+
process.execPath,
|
|
425
|
+
["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
|
|
426
|
+
{ cwd: projectRoot, env, stdio: "pipe" },
|
|
427
|
+
);
|
|
428
|
+
} finally {
|
|
429
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("registers the main watchdog command and renderer in parent mode", () => {
|
|
434
|
+
const script = String.raw`
|
|
435
|
+
import registerSubagentExtension from "./index.ts";
|
|
436
|
+
const events = { on() { return () => {}; }, emit() {} };
|
|
437
|
+
const commands = [];
|
|
438
|
+
const renderers = [];
|
|
439
|
+
const fakePi = new Proxy({
|
|
440
|
+
events,
|
|
441
|
+
registerTool() {},
|
|
442
|
+
registerCommand(name) { commands.push(name); },
|
|
443
|
+
registerShortcut() {},
|
|
444
|
+
registerMessageRenderer(type) { renderers.push(type); },
|
|
445
|
+
sendMessage() {},
|
|
446
|
+
getSessionName() { return undefined; },
|
|
447
|
+
}, {
|
|
448
|
+
get(target, prop) {
|
|
449
|
+
if (prop in target) return target[prop];
|
|
450
|
+
return () => undefined;
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
registerSubagentExtension(fakePi);
|
|
454
|
+
if (!commands.includes("subagents-watchdog")) throw new Error("watchdog command not registered: " + commands.join(", "));
|
|
455
|
+
if (!renderers.includes("subagent_watchdog_warning")) throw new Error("watchdog renderer not registered: " + renderers.join(", "));
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
execFileSync(
|
|
459
|
+
process.execPath,
|
|
460
|
+
[
|
|
461
|
+
"--experimental-transform-types",
|
|
462
|
+
"--import",
|
|
463
|
+
"./test/support/register-loader.mjs",
|
|
464
|
+
"--input-type=module",
|
|
465
|
+
"--eval",
|
|
466
|
+
script,
|
|
467
|
+
],
|
|
468
|
+
{ cwd: projectRoot, env: parentToolEnv(), stdio: "pipe" },
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
114
472
|
it("returns before registering anything for non-fanout children", () => {
|
|
115
473
|
const script = String.raw`
|
|
116
|
-
import registerSubagentExtension from "./
|
|
474
|
+
import registerSubagentExtension from "./index.ts";
|
|
117
475
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
|
|
118
476
|
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
119
477
|
process.env[SUBAGENT_FANOUT_CHILD_ENV] = "0";
|
|
@@ -148,7 +506,7 @@ describe("subagent extension child mode", () => {
|
|
|
148
506
|
|
|
149
507
|
it("returns before registering anything for fanout children", () => {
|
|
150
508
|
const script = String.raw`
|
|
151
|
-
import registerSubagentExtension from "./
|
|
509
|
+
import registerSubagentExtension from "./index.ts";
|
|
152
510
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
|
|
153
511
|
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
154
512
|
process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
|
|
@@ -184,7 +542,7 @@ describe("subagent extension child mode", () => {
|
|
|
184
542
|
|
|
185
543
|
it("does not double-register the child-safe subagent tool when index and fanout-child both load", () => {
|
|
186
544
|
const script = String.raw`
|
|
187
|
-
import registerSubagentExtension from "./
|
|
545
|
+
import registerSubagentExtension from "./index.ts";
|
|
188
546
|
import registerFanoutChildSubagentExtension from "./src/extension/fanout-child.ts";
|
|
189
547
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
|
|
190
548
|
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
@@ -253,6 +611,10 @@ describe("subagent extension child mode", () => {
|
|
|
253
611
|
if (!create.isError) throw new Error("create should be blocked");
|
|
254
612
|
const text = create.content?.[0]?.text ?? "";
|
|
255
613
|
if (!text.includes("not available from child-safe subagent fanout mode")) throw new Error("unexpected create error: " + text);
|
|
614
|
+
const grant = await registeredTool.execute("grant-check", { action: "grant-spawn-budget", additional: 1 }, new AbortController().signal, undefined, { ...ctx, hasUI: true });
|
|
615
|
+
if (!grant.isError) throw new Error("grant-spawn-budget should be blocked");
|
|
616
|
+
const grantText = grant.content?.[0]?.text ?? "";
|
|
617
|
+
if (!grantText.includes("root interactive parent session")) throw new Error("unexpected grant error: " + grantText);
|
|
256
618
|
`;
|
|
257
619
|
|
|
258
620
|
execFileSync(
|
|
@@ -170,4 +170,15 @@ describe("applyIntercomBridgeToAgent", () => {
|
|
|
170
170
|
assert.deepEqual(updated.tools, ["read", "intercom", "contact_supervisor"]);
|
|
171
171
|
assert.match(updated.systemPrompt, /contact_supervisor/);
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
it("does not widen explicit empty or MCP-only builtin allowlists", () => {
|
|
175
|
+
const emptyTools = applyIntercomBridgeToAgent(makeAgent({ tools: [] }), activeBridge);
|
|
176
|
+
assert.deepEqual(emptyTools.tools, []);
|
|
177
|
+
assert.match(emptyTools.systemPrompt, /contact_supervisor/);
|
|
178
|
+
|
|
179
|
+
const mcpOnly = applyIntercomBridgeToAgent(makeAgent({ tools: [], mcpDirectTools: ["github/search_repositories"] }), activeBridge);
|
|
180
|
+
assert.deepEqual(mcpOnly.tools, []);
|
|
181
|
+
assert.deepEqual(mcpOnly.mcpDirectTools, ["github/search_repositories"]);
|
|
182
|
+
assert.match(mcpOnly.systemPrompt, /contact_supervisor/);
|
|
183
|
+
});
|
|
173
184
|
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
fuzzyResolveModel,
|
|
6
6
|
isRetryableModelFailure,
|
|
7
7
|
normalizeModelSegment,
|
|
8
|
+
resolveEffectiveSubagentModel,
|
|
8
9
|
resolveModelCandidate,
|
|
9
10
|
resolveSubagentModelOverride,
|
|
10
11
|
} from "../../src/runs/shared/model-fallback.ts";
|
|
@@ -71,6 +72,8 @@ describe("model fallback helpers", () => {
|
|
|
71
72
|
assert.equal(isRetryableModelFailure("authentication failed"), true);
|
|
72
73
|
assert.equal(isRetryableModelFailure("Subagent produced no output (possible model cold-start or empty response)."), true);
|
|
73
74
|
assert.equal(isRetryableModelFailure("model load failed"), true);
|
|
75
|
+
assert.equal(isRetryableModelFailure("Stream ended without finish_reason"), true);
|
|
76
|
+
assert.equal(isRetryableModelFailure("Request timed out."), true);
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
it("does not treat ordinary task/tool failures as retryable model failures", () => {
|
|
@@ -78,6 +81,16 @@ describe("model fallback helpers", () => {
|
|
|
78
81
|
assert.equal(isRetryableModelFailure("read failed (exit 1): no such file or directory"), false);
|
|
79
82
|
assert.equal(isRetryableModelFailure(undefined), false);
|
|
80
83
|
});
|
|
84
|
+
|
|
85
|
+
it("does not treat network-flavored tool failures as retryable model failures", () => {
|
|
86
|
+
assert.equal(isRetryableModelFailure("bash failed (exit 1): requests.exceptions.ConnectionError: Connection error."), false);
|
|
87
|
+
assert.equal(isRetryableModelFailure("bash failed (exit 1): urllib.error.URLError: request timed out"), false);
|
|
88
|
+
assert.equal(isRetryableModelFailure("fetch_content failed with exit code 1"), false);
|
|
89
|
+
assert.equal(isRetryableModelFailure("mcp.server/write failed (exit 1): request timed out"), false);
|
|
90
|
+
assert.equal(isRetryableModelFailure("mcp:tools.search failed with exit code 1"), false);
|
|
91
|
+
assert.equal(isRetryableModelFailure("Provider error: bash failed (exit 1): request timed out"), true);
|
|
92
|
+
assert.equal(isRetryableModelFailure("bash failed (exit unknown): request timed out"), true);
|
|
93
|
+
});
|
|
81
94
|
});
|
|
82
95
|
|
|
83
96
|
describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", () => {
|
|
@@ -151,6 +164,29 @@ describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", ()
|
|
|
151
164
|
});
|
|
152
165
|
});
|
|
153
166
|
|
|
167
|
+
describe("resolveEffectiveSubagentModel", () => {
|
|
168
|
+
const availableModels = [
|
|
169
|
+
{ provider: "openai", id: "gpt-5-mini", fullId: "openai/gpt-5-mini" },
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
it("falls back to the agent model when inheritance has no parent", () => {
|
|
173
|
+
assert.equal(resolveEffectiveSubagentModel("", "openai/gpt-5-mini", undefined, availableModels), "openai/gpt-5-mini");
|
|
174
|
+
assert.equal(resolveEffectiveSubagentModel("inherit", "openai/gpt-5-mini", undefined, availableModels), "openai/gpt-5-mini");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("keeps agent models inherited for scope enforcement", () => {
|
|
178
|
+
const warnings: string[] = [];
|
|
179
|
+
assert.equal(
|
|
180
|
+
resolveEffectiveSubagentModel(undefined, "openai/gpt-5-mini", undefined, availableModels, undefined, {
|
|
181
|
+
scope: { enforce: true, allow: ["anthropic/*"] },
|
|
182
|
+
onWarn: (violation) => warnings.push(violation.message),
|
|
183
|
+
}),
|
|
184
|
+
"openai/gpt-5-mini",
|
|
185
|
+
);
|
|
186
|
+
assert.equal(warnings.length, 1);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
154
190
|
describe("fuzzyResolveModel / normalizeModelSegment", () => {
|
|
155
191
|
const registry = [
|
|
156
192
|
{ provider: "anthropic", id: "claude-sonnet-4", fullId: "anthropic/claude-sonnet-4" },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import { findModelInfo, getSupportedThinkingLevels, type ModelInfo } from "../../src/shared/model-info.ts";
|
|
3
|
+
import { findModelInfo, getSupportedThinkingLevels, splitKnownThinkingSuffix, toModelInfo, type ModelInfo } from "../../src/shared/model-info.ts";
|
|
4
4
|
|
|
5
5
|
describe("model info helpers", () => {
|
|
6
6
|
const ambiguousModels: ModelInfo[] = [
|
|
@@ -20,14 +20,19 @@ describe("model info helpers", () => {
|
|
|
20
20
|
assert.equal(findModelInfo("openai/gpt-5-mini:high", ambiguousModels, "github-copilot")?.fullId, "openai/gpt-5-mini");
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it("
|
|
23
|
+
it("preserves registry API metadata", () => {
|
|
24
|
+
assert.equal(toModelInfo({ provider: "gateway", id: "model", api: "anthropic-messages" }).api, "anthropic-messages");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps the legacy thinking list for models without per-level metadata", () => {
|
|
24
28
|
assert.deepEqual(
|
|
25
29
|
getSupportedThinkingLevels({ provider: "openai", id: "gpt-5", fullId: "openai/gpt-5", reasoning: true }),
|
|
26
30
|
["off", "minimal", "low", "medium", "high", "xhigh"],
|
|
27
31
|
);
|
|
32
|
+
assert.deepEqual(getSupportedThinkingLevels(undefined), ["off", "minimal", "low", "medium", "high", "xhigh"]);
|
|
28
33
|
});
|
|
29
34
|
|
|
30
|
-
it("keeps the legacy
|
|
35
|
+
it("keeps the legacy thinking list when older model metadata omits reasoning", () => {
|
|
31
36
|
assert.deepEqual(
|
|
32
37
|
getSupportedThinkingLevels({ provider: "openai", id: "gpt-5", fullId: "openai/gpt-5" }),
|
|
33
38
|
["off", "minimal", "low", "medium", "high", "xhigh"],
|
|
@@ -59,4 +64,21 @@ describe("model info helpers", () => {
|
|
|
59
64
|
["high"],
|
|
60
65
|
);
|
|
61
66
|
});
|
|
67
|
+
|
|
68
|
+
it("honors an explicit max mapping and recognizes max suffixes", () => {
|
|
69
|
+
assert.deepEqual(
|
|
70
|
+
getSupportedThinkingLevels({
|
|
71
|
+
provider: "openai",
|
|
72
|
+
id: "gpt-5",
|
|
73
|
+
fullId: "openai/gpt-5",
|
|
74
|
+
reasoning: true,
|
|
75
|
+
thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: "max" },
|
|
76
|
+
}),
|
|
77
|
+
["max"],
|
|
78
|
+
);
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
splitKnownThinkingSuffix("openai/gpt-5:max"),
|
|
81
|
+
{ baseModel: "openai/gpt-5", thinkingSuffix: ":max" },
|
|
82
|
+
);
|
|
83
|
+
});
|
|
62
84
|
});
|
|
@@ -97,7 +97,7 @@ describe("checkModelScope", () => {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
describe("parseModelScopeConfig", () => {
|
|
100
|
-
const meta = { filePath: "~/.
|
|
100
|
+
const meta = { filePath: "~/.selesai/agent/settings.json" };
|
|
101
101
|
|
|
102
102
|
it("returns undefined when the field is absent", () => {
|
|
103
103
|
assert.equal(parseModelScopeConfig(undefined, meta), undefined);
|