@selesai/code 0.13.16 → 0.13.18
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 +30 -0
- package/README.md +2 -14
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
- package/dist/bun/sandbox-env-setup.d.ts +1 -0
- package/dist/bun/sandbox-env-setup.js +3 -0
- package/dist/cli/auth-command.d.ts +23 -0
- package/dist/cli/auth-command.js +102 -0
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +11 -0
- package/dist/cli.js +2 -14
- package/dist/config.d.ts +3 -2
- package/dist/config.js +9 -4
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session.d.ts +2 -6
- package/dist/core/agent-session.js +15 -4
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/types.d.ts +16 -2
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/keybindings.d.ts +5 -0
- package/dist/core/keybindings.js +4 -0
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/model-config.d.ts +15 -0
- package/dist/core/model-config.js +3 -0
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.js +5 -2
- package/dist/core/provider-composer.js +8 -2
- package/dist/core/session-export.d.ts +3 -0
- package/dist/core/session-export.js +31 -0
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.js +40 -13
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/system-prompt.js +10 -10
- package/dist/core/tools/bash.js +3 -117
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.js +5 -163
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.js +10 -122
- package/dist/core/tools/renderers/bash.d.ts +11 -0
- package/dist/core/tools/renderers/bash.js +129 -0
- package/dist/core/tools/renderers/edit.d.ts +22 -0
- package/dist/core/tools/renderers/edit.js +166 -0
- package/dist/core/tools/renderers/find.d.ts +9 -0
- package/dist/core/tools/renderers/find.js +63 -0
- package/dist/core/tools/renderers/grep.d.ts +9 -0
- package/dist/core/tools/renderers/grep.js +68 -0
- package/dist/core/tools/renderers/index.d.ts +27 -0
- package/dist/core/tools/renderers/index.js +46 -0
- package/dist/core/tools/renderers/ls.d.ts +9 -0
- package/dist/core/tools/renderers/ls.js +57 -0
- package/dist/core/tools/renderers/read.d.ts +10 -0
- package/dist/core/tools/renderers/read.js +128 -0
- package/dist/core/tools/renderers/write.d.ts +9 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/write.js +5 -146
- package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
- package/dist/extensions/pi-hermes-memory/README.md +16 -12
- package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
- package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
- package/dist/extensions/pi-hermes-memory/package.json +4 -4
- package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
- package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
- package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
- package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
- package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
- package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
- package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
- package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
- package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
- package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
- package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
- package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
- package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
- package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
- package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
- package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
- package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
- package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +16 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
- package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
- package/dist/extensions/pi-intercom/index.ts +1 -1
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +1 -6
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
- package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
- package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
- package/dist/extensions/pi-subagents/README.md +3 -3
- package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/docs/agents.md +32 -16
- package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
- package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
- package/dist/extensions/pi-subagents/docs/missions.md +2 -0
- package/dist/extensions/pi-subagents/docs/models.md +58 -1
- package/dist/extensions/pi-subagents/docs/observability.md +50 -12
- package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
- package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
- package/dist/extensions/pi-subagents/package-lock.json +747 -177
- package/dist/extensions/pi-subagents/package.json +6 -4
- package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
- package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
- package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
- package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
- package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
- package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
- package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
- package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
- package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
- package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
- package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
- package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
- package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
- package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
- package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
- package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
- package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
- package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
- package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
- package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
- package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
- package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
- package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
- package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
- package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
- package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
- package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
- package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
- package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
- package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
- package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
- package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
- package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
- package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
- package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
- package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
- package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
- package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
- package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
- package/dist/extensions/pi-zentui/README.md +17 -0
- package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
- package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
- package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
- package/dist/extensions/pi-zentui/package-lock.json +277 -517
- package/dist/extensions/pi-zentui/package.json +7 -4
- package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
- package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
- package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
- package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
- package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
- package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -1
- package/dist/modes/interactive/chat-viewport.d.ts +19 -0
- package/dist/modes/interactive/chat-viewport.js +27 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
- package/dist/modes/interactive/components/assistant-message.js +30 -13
- package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
- package/dist/modes/interactive/components/custom-editor.js +39 -1
- package/dist/modes/interactive/components/first-time-setup.js +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
- package/dist/modes/interactive/components/markdown-transform.js +18 -0
- package/dist/modes/interactive/components/model-selector.js +19 -17
- package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
- package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -20
- package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
- package/dist/modes/interactive/components/settings-submenu.js +164 -0
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.js +75 -18
- package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
- package/dist/modes/interactive/components/tool-execution.js +35 -33
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/interactive-mode.d.ts +13 -15
- package/dist/modes/interactive/interactive-mode.js +144 -80
- package/dist/modes/interactive/session-share.d.ts +15 -0
- package/dist/modes/interactive/session-share.js +183 -0
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
- package/dist/modes/interactive/theme/theme-json.js +129 -0
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/tui-renderer.d.ts +20 -0
- package/dist/modes/interactive/tui-renderer.js +65 -0
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/management-http.d.ts +24 -0
- package/dist/utils/management-http.js +53 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.js +8 -0
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.js +43 -21
- package/docs/docs.json +0 -4
- package/package.json +4 -4
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
- package/dist/skills/implanger/SKILL.md +0 -70
- package/dist/skills/output/SKILL.md +0 -49
- package/dist/skills/planger/SKILL.md +0 -167
- package/dist/skills/workflow/SKILL.md +0 -95
- package/docs/workflows.md +0 -18
- /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AsyncStatus,
|
|
3
|
-
|
|
3
|
+
HostStepNode,
|
|
4
4
|
HostStepState,
|
|
5
5
|
HostStepVerdict,
|
|
6
6
|
WorkflowGraphNode,
|
|
@@ -57,7 +57,7 @@ function assertTimestamp(value: unknown, field: string, source: string, required
|
|
|
57
57
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid host step '${source}': ${field} must be a non-negative safe integer.`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function assertFreshness(value: unknown, source: string): asserts value is
|
|
60
|
+
function assertFreshness(value: unknown, source: string): asserts value is HostStepNode["freshness"] {
|
|
61
61
|
if (!isRecord(value)) throw new Error(`Invalid host step '${source}': freshness must be an object.`);
|
|
62
62
|
const unknownFields = Object.keys(value).filter((field) => !FRESHNESS_FIELDS.has(field));
|
|
63
63
|
if (unknownFields.length > 0) throw new Error(`Invalid host step '${source}': freshness has unsupported fields: ${unknownFields.join(", ")}.`);
|
|
@@ -70,7 +70,7 @@ function assertFreshness(value: unknown, source: string): asserts value is HostS
|
|
|
70
70
|
* Validate the persisted host-step contract. This deliberately rejects unknown
|
|
71
71
|
* fields and unbounded values so status and receipt loaders fail closed.
|
|
72
72
|
*/
|
|
73
|
-
export function assertHostStepNode(value: unknown, source = "status"): asserts value is
|
|
73
|
+
export function assertHostStepNode(value: unknown, source = "status"): asserts value is HostStepNode {
|
|
74
74
|
if (!isRecord(value)) throw new Error(`Invalid host step '${source}': expected an object.`);
|
|
75
75
|
const unknownFields = Object.keys(value).filter((field) => !HOST_STEP_FIELDS.has(field));
|
|
76
76
|
if (unknownFields.length > 0) throw new Error(`Invalid host step '${source}': unsupported fields: ${unknownFields.join(", ")}.`);
|
|
@@ -105,7 +105,7 @@ export function assertHostStepNode(value: unknown, source = "status"): asserts v
|
|
|
105
105
|
assertTimestamp(value.deadlineAt, "deadlineAt", source);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export function parseHostStepNode(value: unknown, source = "status"):
|
|
108
|
+
export function parseHostStepNode(value: unknown, source = "status"): HostStepNode {
|
|
109
109
|
assertHostStepNode(value, source);
|
|
110
110
|
return {
|
|
111
111
|
...value,
|
|
@@ -113,7 +113,7 @@ export function parseHostStepNode(value: unknown, source = "status"): HostStepNo
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export function assertUniqueHostStepIds(hostSteps: readonly
|
|
116
|
+
export function assertUniqueHostStepIds(hostSteps: readonly HostStepNode[], source = "status"): void {
|
|
117
117
|
const ids = new Set<string>();
|
|
118
118
|
for (const hostStep of hostSteps) {
|
|
119
119
|
if (ids.has(hostStep.id)) throw new Error(`Invalid host step '${source}': duplicate host step id '${hostStep.id}'.`);
|
|
@@ -122,11 +122,11 @@ export function assertUniqueHostStepIds(hostSteps: readonly HostStepNodeV1[], so
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/** Return only valid host nodes so an untrusted in-memory projection fails closed. */
|
|
125
|
-
export function validHostStepNodes(graph: WorkflowGraphSnapshot | undefined):
|
|
125
|
+
export function validHostStepNodes(graph: WorkflowGraphSnapshot | undefined): HostStepNode[] {
|
|
126
126
|
const nodes = graph?.nodes ?? [];
|
|
127
127
|
const nodeIdCounts = new Map<string, number>();
|
|
128
128
|
for (const node of nodes) nodeIdCounts.set(node.id, (nodeIdCounts.get(node.id) ?? 0) + 1);
|
|
129
|
-
const hostSteps:
|
|
129
|
+
const hostSteps: HostStepNode[] = [];
|
|
130
130
|
for (const [index, node] of nodes.entries()) {
|
|
131
131
|
if (node.kind !== "host-step") continue;
|
|
132
132
|
try {
|
|
@@ -147,7 +147,7 @@ export function assertWorkflowGraphHostSteps(graph: WorkflowGraphSnapshot | unde
|
|
|
147
147
|
if (!Array.isArray(graph.nodes)) throw new Error(`Invalid host step '${source}.workflowGraph': nodes must be an array.`);
|
|
148
148
|
const hostStepCount = graph.nodes.filter((node) => node.kind === "host-step").length;
|
|
149
149
|
if (hostStepCount > HOST_STEP_MAX_COUNT) throw new Error(`Invalid host step '${source}': workflowGraph contains more than ${HOST_STEP_MAX_COUNT} host steps.`);
|
|
150
|
-
const hostSteps:
|
|
150
|
+
const hostSteps: HostStepNode[] = [];
|
|
151
151
|
for (const [index, node] of graph.nodes.entries()) {
|
|
152
152
|
if (node.kind !== "host-step") continue;
|
|
153
153
|
const hostStep = parseHostStepNode(node.hostStep, `${source}.workflowGraph.nodes[${index}].hostStep`);
|
|
@@ -161,7 +161,7 @@ export function assertWorkflowGraphHostSteps(graph: WorkflowGraphSnapshot | unde
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export function hostStepWorkflowNode(hostStep:
|
|
164
|
+
export function hostStepWorkflowNode(hostStep: HostStepNode): WorkflowGraphNode {
|
|
165
165
|
assertHostStepNode(hostStep, hostStep.id);
|
|
166
166
|
return {
|
|
167
167
|
id: hostStep.id,
|
|
@@ -172,7 +172,7 @@ export function hostStepWorkflowNode(hostStep: HostStepNodeV1): WorkflowGraphNod
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function workflowNodeStatus(hostStep:
|
|
175
|
+
function workflowNodeStatus(hostStep: HostStepNode): WorkflowNodeStatus {
|
|
176
176
|
if (hostStep.state === "pending") return "pending";
|
|
177
177
|
if (hostStep.state === "running") return "running";
|
|
178
178
|
if (hostStep.state === "cancelled") return "stopped";
|
|
@@ -187,7 +187,7 @@ function workflowNodeStatus(hostStep: HostStepNodeV1): WorkflowNodeStatus {
|
|
|
187
187
|
*/
|
|
188
188
|
export function upsertHostStep(input: {
|
|
189
189
|
status: AsyncStatus;
|
|
190
|
-
hostStep:
|
|
190
|
+
hostStep: HostStepNode;
|
|
191
191
|
persist: (status: AsyncStatus) => void;
|
|
192
192
|
}): AsyncStatus {
|
|
193
193
|
const hostStep = parseHostStepNode(input.hostStep, input.hostStep.id);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { Agent,
|
|
3
|
-
import {
|
|
4
|
-
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import type { Agent, AgentTool, StreamFn } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ExtensionContext } from "@selesai/code";
|
|
5
4
|
import type { ProviderHeaders } from "@earendil-works/pi-ai";
|
|
6
5
|
import { Type, type Static } from "typebox";
|
|
7
6
|
import { agentStreamOptions } from "../../shared/agent-stream-options.ts";
|
|
@@ -52,7 +51,11 @@ export function mapArbiterDecision(
|
|
|
52
51
|
|
|
53
52
|
interface ArbiterRuntime {
|
|
54
53
|
model: NonNullable<RegistryModel>;
|
|
55
|
-
|
|
54
|
+
/** Explicit override; it always wins over a registered provider stream. */
|
|
55
|
+
explicitStreamFn?: StreamFn;
|
|
56
|
+
/** Registered provider stream, usable only when its api matches the model. */
|
|
57
|
+
registeredStreamFn?: StreamFn;
|
|
58
|
+
registeredApi?: string;
|
|
56
59
|
timeoutMs: number;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -73,9 +76,10 @@ export interface TaskMutationArbiterOptions {
|
|
|
73
76
|
const DEFAULT_ARBITER_TIMEOUT_MS = 10_000;
|
|
74
77
|
|
|
75
78
|
type RegistryModel = ReturnType<NonNullable<ExtensionContext["modelRegistry"]["find"]>>;
|
|
79
|
+
type ArbiterModelContext = Pick<ExtensionContext, "model" | "modelRegistry">;
|
|
76
80
|
|
|
77
81
|
function resolveArbiterModel(
|
|
78
|
-
ctx:
|
|
82
|
+
ctx: ArbiterModelContext,
|
|
79
83
|
options?: TaskMutationArbiterOptions,
|
|
80
84
|
): NonNullable<RegistryModel> | null {
|
|
81
85
|
const registry = ctx.modelRegistry as {
|
|
@@ -95,7 +99,7 @@ function resolveArbiterModel(
|
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
function resolveArbiterRuntime(
|
|
98
|
-
ctx:
|
|
102
|
+
ctx: ArbiterModelContext,
|
|
99
103
|
options?: TaskMutationArbiterOptions,
|
|
100
104
|
): ArbiterRuntime | null {
|
|
101
105
|
const model = resolveArbiterModel(ctx, options);
|
|
@@ -103,23 +107,20 @@ function resolveArbiterRuntime(
|
|
|
103
107
|
const registry = ctx.modelRegistry as {
|
|
104
108
|
getRegisteredProviderConfig?: (provider: string) => { api?: string; streamSimple?: StreamFn } | undefined;
|
|
105
109
|
};
|
|
106
|
-
const modelApi = (model as { api?: string }).api;
|
|
107
110
|
const registered = registry.getRegisteredProviderConfig?.(model.provider);
|
|
108
|
-
const baseStreamFn = options?.streamFn
|
|
109
|
-
?? (registered?.streamSimple && registered.api === modelApi
|
|
110
|
-
? registered.streamSimple
|
|
111
|
-
: streamSimple);
|
|
112
111
|
return {
|
|
113
112
|
model,
|
|
114
|
-
|
|
113
|
+
explicitStreamFn: options?.streamFn,
|
|
114
|
+
registeredStreamFn: registered?.streamSimple,
|
|
115
|
+
registeredApi: registered?.api,
|
|
115
116
|
timeoutMs: options?.timeoutMs ?? DEFAULT_ARBITER_TIMEOUT_MS,
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
async function resolveArbiterAuth(
|
|
120
|
-
ctx:
|
|
121
|
+
ctx: ArbiterModelContext,
|
|
121
122
|
model: RegistryModel,
|
|
122
|
-
): Promise<ArbiterAuth> {
|
|
123
|
+
): Promise<ArbiterAuth | undefined> {
|
|
123
124
|
const registry = ctx.modelRegistry as {
|
|
124
125
|
getApiKeyAndHeaders?: (m: RegistryModel) => Promise<{
|
|
125
126
|
ok: boolean;
|
|
@@ -135,14 +136,14 @@ async function resolveArbiterAuth(
|
|
|
135
136
|
if (!registry.getApiKeyAndHeaders) return {};
|
|
136
137
|
try {
|
|
137
138
|
const auth = await registry.getApiKeyAndHeaders(model);
|
|
138
|
-
if (auth.ok === false) return
|
|
139
|
+
if (auth.ok === false) return undefined;
|
|
139
140
|
return {
|
|
140
141
|
...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
|
|
141
142
|
...(auth.headers ? { headers: auth.headers } : {}),
|
|
142
143
|
...(auth.env ? { env: auth.env } : {}),
|
|
143
144
|
};
|
|
144
145
|
} catch {
|
|
145
|
-
return
|
|
146
|
+
return undefined;
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -164,6 +165,15 @@ async function runArbitration(
|
|
|
164
165
|
auth: ArbiterAuth,
|
|
165
166
|
task: string,
|
|
166
167
|
): Promise<TaskMutationVerdict> {
|
|
168
|
+
// Keep optional Pi peers out of the detached runner's static import graph.
|
|
169
|
+
const [{ Agent }, { convertToLlm }, { streamSimple }] = await Promise.all([
|
|
170
|
+
import("@earendil-works/pi-agent-core"),
|
|
171
|
+
import("@selesai/code"),
|
|
172
|
+
import("@earendil-works/pi-ai/compat"),
|
|
173
|
+
]);
|
|
174
|
+
const streamFn: StreamFn = runtime.explicitStreamFn
|
|
175
|
+
?? (runtime.registeredApi !== undefined && runtime.registeredApi === runtime.model.api ? runtime.registeredStreamFn : undefined)
|
|
176
|
+
?? streamSimple;
|
|
167
177
|
let decision: DecisionParams | undefined;
|
|
168
178
|
const tool: AgentTool<typeof DecisionParams, { recorded: boolean }> = {
|
|
169
179
|
name: "task_mutation_decision",
|
|
@@ -190,7 +200,7 @@ async function runArbitration(
|
|
|
190
200
|
tools: [tool],
|
|
191
201
|
},
|
|
192
202
|
convertToLlm,
|
|
193
|
-
...agentStreamOptions(authWrappedStreamFn(
|
|
203
|
+
...agentStreamOptions(authWrappedStreamFn(streamFn, auth)),
|
|
194
204
|
getApiKey: (providerName) =>
|
|
195
205
|
providerName === runtime.model.provider ? auth.apiKey : undefined,
|
|
196
206
|
beforeToolCall: async ({ toolCall }) =>
|
|
@@ -221,9 +231,9 @@ async function runArbitration(
|
|
|
221
231
|
}
|
|
222
232
|
}
|
|
223
233
|
|
|
224
|
-
/** Create a memoized arbiter bound to the
|
|
234
|
+
/** Create a memoized arbiter bound to the supplied model services, or undefined when disabled/unavailable. */
|
|
225
235
|
export function createTaskMutationArbiter(
|
|
226
|
-
ctx:
|
|
236
|
+
ctx: ArbiterModelContext,
|
|
227
237
|
options?: TaskMutationArbiterOptions,
|
|
228
238
|
): TaskMutationArbiter | undefined {
|
|
229
239
|
if (process.env.SELESAI_SUBAGENTS_LLM_INTENT_ARBITER === "0") return undefined;
|
|
@@ -235,7 +245,7 @@ export function createTaskMutationArbiter(
|
|
|
235
245
|
const cached = cache.get(key);
|
|
236
246
|
if (cached) return cached;
|
|
237
247
|
const auth = await resolveArbiterAuth(ctx, runtime.model);
|
|
238
|
-
const verdict = await runArbitration(runtime, auth, task);
|
|
248
|
+
const verdict = auth ? await runArbitration(runtime, auth, task) : "unavailable";
|
|
239
249
|
if (cache.size > 200) cache.clear();
|
|
240
250
|
cache.set(key, verdict);
|
|
241
251
|
return verdict;
|
|
@@ -115,8 +115,11 @@ export function resolveMcpDirectToolResolution(
|
|
|
115
115
|
? mergeConfigs(config, { mcpServers: runtimeServers })
|
|
116
116
|
: config;
|
|
117
117
|
validateSelectedServerDefinitions(resolvedConfig, selectors);
|
|
118
|
+
const runtime = Object.keys(runtimeServers).length > 0
|
|
119
|
+
? { mcpConfig: resolvedConfig, runtimeServerNames: Object.keys(runtimeServers) }
|
|
120
|
+
: {};
|
|
118
121
|
const cache = loadMetadataCache();
|
|
119
|
-
if (!cache) return { selections: [], unresolvedSelectors: selectors };
|
|
122
|
+
if (!cache) return { selections: [], unresolvedSelectors: selectors, ...runtime };
|
|
120
123
|
const validMetadata: Record<string, ServerCacheEntry> = {};
|
|
121
124
|
for (const [serverName, definition] of Object.entries(resolvedConfig.mcpServers)) {
|
|
122
125
|
const serverCache = cache.servers[serverName];
|
|
@@ -131,9 +134,7 @@ export function resolveMcpDirectToolResolution(
|
|
|
131
134
|
return {
|
|
132
135
|
selections: grant.selections,
|
|
133
136
|
unresolvedSelectors: grant.unresolvedSelectors,
|
|
134
|
-
...
|
|
135
|
-
? { mcpConfig: resolvedConfig, runtimeServerNames: Object.keys(runtimeServers) }
|
|
136
|
-
: {}),
|
|
137
|
+
...runtime,
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { splitKnownThinkingSuffix } from "../../shared/model-info.ts";
|
|
4
4
|
import { TEMP_ROOT_DIR } from "../../shared/types.ts";
|
|
5
|
+
import { getAgentDir } from "../../shared/utils.ts";
|
|
5
6
|
|
|
6
7
|
export const EXCLUSIONS_PATH_ENV = "PI_MODEL_EXCLUSIONS_PATH";
|
|
7
8
|
|
|
@@ -30,6 +31,43 @@ let loadedTTLCeilingMs: number | undefined;
|
|
|
30
31
|
let persistTimer: ReturnType<typeof setTimeout> | null = null;
|
|
31
32
|
let persistSeq = 0;
|
|
32
33
|
|
|
34
|
+
const AUTH_FAILURE_PATTERNS = [
|
|
35
|
+
/auth(?:entication)?/i,
|
|
36
|
+
/unauthori[sz]ed/i,
|
|
37
|
+
/forbidden/i,
|
|
38
|
+
/api key/i,
|
|
39
|
+
/token expired/i,
|
|
40
|
+
/invalid key/i,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function isAuthModelExclusion(entry: ModelExclusion): boolean {
|
|
44
|
+
const reason = entry.reason;
|
|
45
|
+
return typeof reason === "string" && AUTH_FAILURE_PATTERNS.some((pattern) => pattern.test(reason));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getAuthStoreMtimeMs(): number | undefined {
|
|
49
|
+
const authStorePath = path.join(getAgentDir(), "auth.json");
|
|
50
|
+
try {
|
|
51
|
+
const stats = fs.statSync(authStorePath);
|
|
52
|
+
return stats.isFile() && Number.isFinite(stats.mtimeMs) ? stats.mtimeMs : undefined;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
55
|
+
console.error(`[model-exclusions] Failed to stat Pi auth store at ${authStorePath}; preserving auth-related exclusions:`, error);
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function invalidateAuthExclusions(): void {
|
|
62
|
+
if (!exclusions.some(isAuthModelExclusion)) return;
|
|
63
|
+
const authStoreMtimeMs = getAuthStoreMtimeMs();
|
|
64
|
+
if (authStoreMtimeMs === undefined) return;
|
|
65
|
+
const retained = exclusions.filter((entry) => !isAuthModelExclusion(entry) || authStoreMtimeMs <= entry.recordedAt);
|
|
66
|
+
if (retained.length === exclusions.length) return;
|
|
67
|
+
exclusions = retained;
|
|
68
|
+
schedulePersist();
|
|
69
|
+
}
|
|
70
|
+
|
|
33
71
|
/**
|
|
34
72
|
* Override the default TTL applied to newly recorded model exclusions.
|
|
35
73
|
*
|
|
@@ -95,23 +133,23 @@ function ensureLoaded(): void {
|
|
|
95
133
|
const data = JSON.parse(raw);
|
|
96
134
|
if (data.version === 1) {
|
|
97
135
|
if (!Array.isArray(data.exclusions)) throw new Error("Model exclusion store version 1 must contain an exclusions array.");
|
|
98
|
-
for (let index = 0; index < data.exclusions.length; index++) {
|
|
99
|
-
const entry = data.exclusions[index];
|
|
100
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`Model exclusion store entry ${index} must be an object.`);
|
|
101
|
-
if (entry.reason !== undefined && typeof entry.reason !== "string") throw new Error(`Model exclusion store entry ${index} has an invalid reason.`);
|
|
102
|
-
for (const field of ["recordedAt", "expiresAt"] as const) {
|
|
103
|
-
const timestamp = entry[field];
|
|
104
|
-
if (typeof timestamp !== "number" || !Number.isFinite(timestamp) || timestamp <= 0 || timestamp > MAX_DATE_TIMESTAMP_MS) {
|
|
105
|
-
throw new Error(`Model exclusion store entry ${index} has an invalid ${field}.`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
136
|
const now = Date.now();
|
|
110
|
-
|
|
137
|
+
let droppedInvalid = false;
|
|
138
|
+
exclusions = data.exclusions.flatMap((entry: unknown, index: number) => {
|
|
139
|
+
const result = readPersistedExclusion(entry, index);
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
droppedInvalid = true;
|
|
142
|
+
console.warn(`[model-exclusions] Ignoring invalid exclusion: ${result.message}`);
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
return [result.exclusion];
|
|
146
|
+
}).filter((e: ModelExclusion) => e.expiresAt > now);
|
|
111
147
|
const shortened = loadedTTLCeilingMs !== undefined && shortenExclusionsToTTL(exclusions, loadedTTLCeilingMs, now);
|
|
112
148
|
exclusions = deduplicate(exclusions);
|
|
113
|
-
if (
|
|
149
|
+
if (droppedInvalid) flushPersist();
|
|
150
|
+
else if (shortened) schedulePersist();
|
|
114
151
|
}
|
|
152
|
+
invalidateAuthExclusions();
|
|
115
153
|
} catch (error) {
|
|
116
154
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
117
155
|
console.error(`[model-exclusions] Failed to load exclusions from ${getExclusionsFilePath()}:`, error);
|
|
@@ -119,6 +157,32 @@ function ensureLoaded(): void {
|
|
|
119
157
|
}
|
|
120
158
|
}
|
|
121
159
|
|
|
160
|
+
type PersistedExclusionRead =
|
|
161
|
+
| { ok: true; exclusion: ModelExclusion }
|
|
162
|
+
| { ok: false; message: string };
|
|
163
|
+
|
|
164
|
+
function invalidPersistedExclusion(index: number, message: string): PersistedExclusionRead {
|
|
165
|
+
return { ok: false, message: `Model exclusion store entry ${index} ${message}.` };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function readPersistedExclusion(entry: unknown, index: number): PersistedExclusionRead {
|
|
169
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return invalidPersistedExclusion(index, "must be an object");
|
|
170
|
+
const candidate = entry as { modelId?: unknown; provider?: unknown; reason?: unknown; recordedAt?: unknown; expiresAt?: unknown };
|
|
171
|
+
const { modelId, provider, reason } = candidate;
|
|
172
|
+
if (modelId !== undefined && (typeof modelId !== "string" || modelId.length === 0)) return invalidPersistedExclusion(index, "has an invalid modelId");
|
|
173
|
+
if (provider !== undefined && (typeof provider !== "string" || provider.length === 0)) return invalidPersistedExclusion(index, "has an invalid provider");
|
|
174
|
+
if (reason !== undefined && typeof reason !== "string") return invalidPersistedExclusion(index, "has an invalid reason");
|
|
175
|
+
const { recordedAt, expiresAt } = candidate;
|
|
176
|
+
if (typeof recordedAt !== "number" || !Number.isFinite(recordedAt) || recordedAt <= 0 || recordedAt > MAX_DATE_TIMESTAMP_MS) return invalidPersistedExclusion(index, "has an invalid recordedAt");
|
|
177
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0 || expiresAt > MAX_DATE_TIMESTAMP_MS) return invalidPersistedExclusion(index, "has an invalid expiresAt");
|
|
178
|
+
const metadata = { ...(reason === undefined ? {} : { reason }), recordedAt, expiresAt };
|
|
179
|
+
if (modelId === undefined) {
|
|
180
|
+
if (provider === undefined) return invalidPersistedExclusion(index, "must include modelId or provider");
|
|
181
|
+
return { ok: true, exclusion: { provider, ...metadata } };
|
|
182
|
+
}
|
|
183
|
+
return { ok: true, exclusion: { modelId, ...(provider === undefined ? {} : { provider }), ...metadata } };
|
|
184
|
+
}
|
|
185
|
+
|
|
122
186
|
function dedupKey(entry: ModelExclusion): string {
|
|
123
187
|
return `${entry.provider ?? ""}|${entry.modelId ?? ""}`;
|
|
124
188
|
}
|
|
@@ -165,6 +229,7 @@ export function recordModelFailure(options: RecordModelFailureOptions): void {
|
|
|
165
229
|
*/
|
|
166
230
|
export function clearExpiredExclusions(): void {
|
|
167
231
|
ensureLoaded();
|
|
232
|
+
invalidateAuthExclusions();
|
|
168
233
|
prune(exclusions, Date.now());
|
|
169
234
|
schedulePersist();
|
|
170
235
|
}
|
|
@@ -190,7 +255,7 @@ export function clearExclusions(): void {
|
|
|
190
255
|
*/
|
|
191
256
|
function entryMatches(entry: ModelExclusion, candidateModelId: string, candidateProvider: string | undefined, now: number): boolean {
|
|
192
257
|
if (entry.expiresAt <= now) return false;
|
|
193
|
-
if (entry.modelId) {
|
|
258
|
+
if (entry.modelId !== undefined) {
|
|
194
259
|
if (entry.modelId !== candidateModelId) return false;
|
|
195
260
|
return !entry.provider || !candidateProvider || entry.provider === candidateProvider;
|
|
196
261
|
}
|
|
@@ -202,6 +267,7 @@ function entryMatches(entry: ModelExclusion, candidateModelId: string, candidate
|
|
|
202
267
|
*/
|
|
203
268
|
export function isExcluded(modelId: string, provider: string): boolean {
|
|
204
269
|
ensureLoaded();
|
|
270
|
+
invalidateAuthExclusions();
|
|
205
271
|
return exclusions.some((entry) => entryMatches(entry, modelId, provider, Date.now()));
|
|
206
272
|
}
|
|
207
273
|
|
|
@@ -213,6 +279,7 @@ export function isExcluded(modelId: string, provider: string): boolean {
|
|
|
213
279
|
*/
|
|
214
280
|
export function findModelExclusion(fullId: string, now = Date.now()): Readonly<ModelExclusion> | undefined {
|
|
215
281
|
ensureLoaded();
|
|
282
|
+
invalidateAuthExclusions();
|
|
216
283
|
const { provider, modelId } = parseModelKey(fullId);
|
|
217
284
|
return exclusions.find((entry) => entryMatches(entry, modelId, provider, now));
|
|
218
285
|
}
|
|
@@ -250,15 +317,17 @@ export function parseModelKey(fullId: string): { provider?: string; modelId: str
|
|
|
250
317
|
export function filterFallbackCandidates(candidates: string[], opts?: {
|
|
251
318
|
now?: number;
|
|
252
319
|
onExcluded?: (candidate: string, exclusion: Readonly<ModelExclusion>) => void;
|
|
320
|
+
ignoreExclusion?: (candidate: string, exclusion: Readonly<ModelExclusion>) => boolean;
|
|
253
321
|
}): string[] {
|
|
254
322
|
ensureLoaded();
|
|
323
|
+
invalidateAuthExclusions();
|
|
255
324
|
const timestamp = opts?.now ?? Date.now();
|
|
256
325
|
const seen = new Set<string>();
|
|
257
326
|
const filtered: string[] = [];
|
|
258
327
|
for (const raw of candidates) {
|
|
259
328
|
if (!raw || seen.has(raw)) continue;
|
|
260
329
|
const { provider: candidateProvider, modelId: candidateModelId } = parseModelKey(raw);
|
|
261
|
-
const exclusion = exclusions.find((entry) => entryMatches(entry, candidateModelId, candidateProvider, timestamp));
|
|
330
|
+
const exclusion = exclusions.find((entry) => entryMatches(entry, candidateModelId, candidateProvider, timestamp) && opts?.ignoreExclusion?.(raw, entry) !== true);
|
|
262
331
|
if (exclusion) {
|
|
263
332
|
opts?.onExcluded?.(raw, exclusion);
|
|
264
333
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { splitKnownThinkingSuffix, type ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
|
|
1
|
+
import { splitKnownThinkingSuffix as splitThinkingSuffix, type ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
|
|
2
2
|
import type { Usage } from "../../shared/types.ts";
|
|
3
3
|
import { filterFallbackCandidates, findModelExclusion, parseModelKey, recordModelFailure } from "./model-exclusions.ts";
|
|
4
4
|
import { checkModelScope, type ModelScopeCheckRule, type ModelScopeViolation, type ModelSource } from "./model-scope.ts";
|
|
@@ -14,13 +14,19 @@ interface ModelAttemptSummary {
|
|
|
14
14
|
usage?: Usage;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
return splitKnownThinkingSuffix(model);
|
|
19
|
-
}
|
|
17
|
+
export { splitThinkingSuffix };
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
/** Aliases apply only to the resolved launch candidate (without its thinking suffix) and the exact raw response ID. */
|
|
20
|
+
export function formatSubagentModelVerificationError(
|
|
21
|
+
expectedModel: string,
|
|
22
|
+
observedModel: string,
|
|
23
|
+
availableModels: AvailableModelInfo[] | undefined,
|
|
24
|
+
modelResponseAliases?: Record<string, string[]>,
|
|
25
|
+
): string | undefined {
|
|
22
26
|
if (!availableModels || availableModels.length === 0) return undefined;
|
|
23
27
|
const expectedBase = splitThinkingSuffix(expectedModel).baseModel;
|
|
28
|
+
if (modelResponseAliases && Object.hasOwn(modelResponseAliases, expectedBase)
|
|
29
|
+
&& modelResponseAliases[expectedBase]?.includes(observedModel)) return undefined;
|
|
24
30
|
const observedBase = splitThinkingSuffix(observedModel).baseModel;
|
|
25
31
|
if (expectedBase === observedBase) return undefined;
|
|
26
32
|
const expectedEntry = availableModels.find((entry) => entry.fullId === expectedBase);
|
|
@@ -30,7 +36,7 @@ export function formatSubagentModelVerificationError(expectedModel: string, obse
|
|
|
30
36
|
const expectedFullIdLeaf = expectedEntry.fullId.slice(expectedEntry.fullId.lastIndexOf("/") + 1);
|
|
31
37
|
if (expectedIdLeaf === observedBase || expectedFullIdLeaf === observedBase) return undefined;
|
|
32
38
|
}
|
|
33
|
-
return `model_verification_failed: child reported a different model than the launch candidate. Expected '${expectedModel}' but observed '${observedModel}'.`;
|
|
39
|
+
return `model_verification_failed: native Pi child reported a different model than the launch candidate. Expected '${expectedModel}' but observed '${observedModel}'. If you have independently verified this response ID identifies the requested model, declare the exact mapping in modelResponseAliases in ~/.selesai/agent/extensions/subagent/config.json (see docs/configuration.md#modelresponsealiases). Use the resolved provider/model ID without its thinking suffix as the key. This leaves the outgoing request unchanged. Configuration changes affect new independent native runs; resumed native runs retain their launch-time declaration. External CLI adapters do not use this setting.`;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/** Sentinel model value requesting that a subagent inherit the parent session's model. */
|
|
@@ -284,6 +290,49 @@ function enforceModelScopes(
|
|
|
284
290
|
for (const violation of violations) (onWarn ?? defaultScopeWarn)(violation);
|
|
285
291
|
}
|
|
286
292
|
|
|
293
|
+
const MODEL_EXCLUSION_DIAGNOSTIC_MAX_LENGTH = 240;
|
|
294
|
+
const MODEL_EXCLUSION_DIAGNOSTIC_MAX_ENTRIES = 20;
|
|
295
|
+
|
|
296
|
+
function sanitizeModelExclusionDiagnostic(value: string | undefined, fallback: string): string {
|
|
297
|
+
const normalized = typeof value === "string"
|
|
298
|
+
? value.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, " ").trim()
|
|
299
|
+
: "";
|
|
300
|
+
return redactSecretValues(normalized || fallback).slice(0, MODEL_EXCLUSION_DIAGNOSTIC_MAX_LENGTH);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function formatModelExclusionExpiry(expiresAt: number): string {
|
|
304
|
+
if (!Number.isFinite(expiresAt)) return "unknown";
|
|
305
|
+
const date = new Date(expiresAt);
|
|
306
|
+
return Number.isNaN(date.getTime()) ? "unknown" : date.toISOString();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function formatExcludedCandidateEvidence(candidate: string, exclusion: NonNullable<ReturnType<typeof findModelExclusion>>): string {
|
|
310
|
+
const { provider, modelId } = parseModelKey(candidate);
|
|
311
|
+
const displayCandidate = sanitizeModelExclusionDiagnostic(candidate, "unknown");
|
|
312
|
+
const displayModel = sanitizeModelExclusionDiagnostic(modelId, "unknown");
|
|
313
|
+
const displayProvider = sanitizeModelExclusionDiagnostic(provider ?? exclusion.provider, "unspecified");
|
|
314
|
+
const reason = sanitizeModelExclusionDiagnostic(exclusion.reason, "runtime-failure");
|
|
315
|
+
return `${displayCandidate} — model: ${displayModel}; provider: ${displayProvider}; reason: ${reason}; expires: ${formatModelExclusionExpiry(exclusion.expiresAt)}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const MODEL_UNAVAILABLE_EXCLUSION_PATTERNS = [
|
|
319
|
+
/model.*not found/i,
|
|
320
|
+
/unknown model/i,
|
|
321
|
+
/model.*unavailable/i,
|
|
322
|
+
/model.*disabled/i,
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
function isCurrentRegistryModel(candidate: string, availableModels: AvailableModelInfo[] | undefined): boolean {
|
|
326
|
+
if (!availableModels || availableModels.length === 0) return false;
|
|
327
|
+
const { baseModel } = splitThinkingSuffix(candidate);
|
|
328
|
+
return availableModels.some((entry) => entry.fullId === baseModel);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function ignoreStaleModelUnavailableExclusion(candidate: string, exclusion: NonNullable<ReturnType<typeof findModelExclusion>>, availableModels: AvailableModelInfo[] | undefined): boolean {
|
|
332
|
+
const reason = exclusion.reason ?? "";
|
|
333
|
+
return MODEL_UNAVAILABLE_EXCLUSION_PATTERNS.some((pattern) => pattern.test(reason)) && isCurrentRegistryModel(candidate, availableModels);
|
|
334
|
+
}
|
|
335
|
+
|
|
287
336
|
function throwForExplicitModelExclusion(model: string): void {
|
|
288
337
|
const exclusion = findModelExclusion(model);
|
|
289
338
|
if (!exclusion) return;
|
|
@@ -419,9 +468,15 @@ export function buildModelCandidates(
|
|
|
419
468
|
if (!primaryModel) throwForUnresolvedEnforcedInheritScope(options?.scope, true);
|
|
420
469
|
const origin = options?.origin ?? (options?.primaryModelFromParent ? "inherited" : "configured");
|
|
421
470
|
const scopes = configuredScopes(options?.scope);
|
|
471
|
+
type ExcludedCandidate = { candidate: string; exclusion: NonNullable<ReturnType<typeof findModelExclusion>> };
|
|
472
|
+
const excludedCandidates: ExcludedCandidate[] = [];
|
|
473
|
+
let excludedCandidateCount = 0;
|
|
422
474
|
const warnCachedExclusion = (candidate: string, exclusion: NonNullable<ReturnType<typeof findModelExclusion>>) => {
|
|
423
|
-
|
|
424
|
-
|
|
475
|
+
excludedCandidateCount++;
|
|
476
|
+
if (excludedCandidates.length < MODEL_EXCLUSION_DIAGNOSTIC_MAX_ENTRIES) excludedCandidates.push({ candidate, exclusion });
|
|
477
|
+
const displayCandidate = sanitizeModelExclusionDiagnostic(candidate, "unknown");
|
|
478
|
+
const reason = sanitizeModelExclusionDiagnostic(exclusion.reason, "runtime-failure");
|
|
479
|
+
console.warn(`[pi-subagents] Skipping model '${displayCandidate}' due to a cached exclusion (reason: ${reason}; expires: ${formatModelExclusionExpiry(exclusion.expiresAt)}).`);
|
|
425
480
|
};
|
|
426
481
|
if (origin === "explicit" && primaryModel) {
|
|
427
482
|
const normalized = resolveRequiredSubagentModelCandidate(primaryModel.trim(), availableModels, preferredProvider);
|
|
@@ -433,6 +488,7 @@ export function buildModelCandidates(
|
|
|
433
488
|
const candidates: string[] = [];
|
|
434
489
|
const rawCandidates = [primaryModel, ...(fallbackModels ?? [])];
|
|
435
490
|
let skippedPrimary: string | undefined;
|
|
491
|
+
let skippedFallback: string | undefined;
|
|
436
492
|
for (let index = 0; index < rawCandidates.length; index++) {
|
|
437
493
|
const raw = rawCandidates[index];
|
|
438
494
|
if (!raw) continue;
|
|
@@ -442,7 +498,10 @@ export function buildModelCandidates(
|
|
|
442
498
|
: resolveSubagentModelCandidate(model, availableModels, preferredProvider);
|
|
443
499
|
if (!normalized) {
|
|
444
500
|
if (index === 0) skippedPrimary = model;
|
|
445
|
-
else
|
|
501
|
+
else {
|
|
502
|
+
skippedFallback ??= model;
|
|
503
|
+
console.warn(`[pi-subagents] Skipping fallback model '${model}' because it is unavailable in this environment.`);
|
|
504
|
+
}
|
|
446
505
|
continue;
|
|
447
506
|
}
|
|
448
507
|
if (seen.has(normalized)) continue;
|
|
@@ -452,10 +511,21 @@ export function buildModelCandidates(
|
|
|
452
511
|
seen.add(normalized);
|
|
453
512
|
candidates.push(normalized);
|
|
454
513
|
}
|
|
455
|
-
const resolved = filterFallbackCandidates(candidates, {
|
|
514
|
+
const resolved = filterFallbackCandidates(candidates, {
|
|
515
|
+
onExcluded: warnCachedExclusion,
|
|
516
|
+
ignoreExclusion: (candidate, exclusion) => ignoreStaleModelUnavailableExclusion(candidate, exclusion, availableModels),
|
|
517
|
+
});
|
|
456
518
|
if (resolved.length === 0) {
|
|
457
519
|
if (skippedPrimary) resolveRequiredSubagentModelCandidate(skippedPrimary, availableModels, preferredProvider);
|
|
458
|
-
if (candidates.length
|
|
520
|
+
if (candidates.length === 0 && skippedFallback) resolveRequiredSubagentModelCandidate(skippedFallback, availableModels, preferredProvider);
|
|
521
|
+
if (candidates.length > 0) {
|
|
522
|
+
const shownExclusions = excludedCandidates;
|
|
523
|
+
const omittedExclusions = excludedCandidateCount - shownExclusions.length;
|
|
524
|
+
const evidence = shownExclusions.length > 0
|
|
525
|
+
? ` (excluded: ${shownExclusions.map(({ candidate, exclusion }) => formatExcludedCandidateEvidence(candidate, exclusion)).join("; ")}${omittedExclusions > 0 ? `; ... and ${omittedExclusions} more` : ""})`
|
|
526
|
+
: "";
|
|
527
|
+
throw new Error(`${ZERO_USABLE_MODEL_CANDIDATES_ERROR}${evidence}`);
|
|
528
|
+
}
|
|
459
529
|
return resolved;
|
|
460
530
|
}
|
|
461
531
|
if (skippedPrimary) {
|
|
@@ -465,6 +535,7 @@ export function buildModelCandidates(
|
|
|
465
535
|
}
|
|
466
536
|
|
|
467
537
|
const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
538
|
+
/^REQUEST_LIMIT_EXCEEDED$/,
|
|
468
539
|
/rate\s*limit/i,
|
|
469
540
|
/usage\s*limit/i,
|
|
470
541
|
/too many requests/i,
|
|
@@ -536,8 +607,13 @@ export function isRetryableModelFailureAttempt(input: { error: string | undefine
|
|
|
536
607
|
return Boolean(error && input.messages?.some((message) => messageError(message)?.trim() === error));
|
|
537
608
|
}
|
|
538
609
|
|
|
610
|
+
// Request-shape failures can match broad fallback signals such as "upstream",
|
|
611
|
+
// but do not establish that the model is unhealthy for subsequent requests.
|
|
612
|
+
const REQUEST_SHAPE_FAILURE_PATTERN = /\b(?:bad[ _]request|invalid[ _]argument|invalid_request_error)\b/i;
|
|
613
|
+
|
|
539
614
|
export function recordRetryableModelFailure(model: string | undefined, error: string | undefined): void {
|
|
540
|
-
if (!model || !isRetryableModelFailure(error)) return;
|
|
615
|
+
if (!model || !error || !isRetryableModelFailure(error) || isContextOverflow(error)) return;
|
|
616
|
+
if (REQUEST_SHAPE_FAILURE_PATTERN.test(error)) return;
|
|
541
617
|
const { provider, modelId } = parseModelKey(model);
|
|
542
618
|
recordModelFailure({ modelId, reason: error, ...(provider ? { provider } : {}) });
|
|
543
619
|
}
|