@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +1 -1
- package/dist/cli.js +4685 -4614
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +374 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +48 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +35 -0
- package/dist/types/live/visualizer.d.ts +30 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +321 -0
- package/dist/types/session/agent-session.d.ts +135 -753
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +51 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +24 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +158 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/streaming-output.d.ts +8 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +7 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +10 -29
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli/usage-cli.ts +10 -1
- package/src/cli.ts +8 -0
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +16 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +270 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +90 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +214 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +28 -112
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +255 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +80 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +176 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +215 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +343 -0
- package/src/session/agent-session.ts +1940 -12218
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +217 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +266 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +980 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +257 -20
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +45 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +116 -86
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +9 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +121 -29
- package/src/tts/streaming-player.ts +82 -283
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
package/src/task/index.ts
CHANGED
|
@@ -187,7 +187,6 @@ function renderDescription(
|
|
|
187
187
|
agents: renderedAgents,
|
|
188
188
|
spawningDisabled,
|
|
189
189
|
defaultAgent: spawnPolicy.defaultAgent,
|
|
190
|
-
allowedAgentsText: spawnPolicy.allowedPromptText,
|
|
191
190
|
isolationEnabled,
|
|
192
191
|
applyIsolatedChanges,
|
|
193
192
|
batchEnabled,
|
|
@@ -531,19 +530,35 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
531
530
|
if (typeof params.context === "string" && params.context.trim()) {
|
|
532
531
|
lines.push(`Context:\n${truncateForPrompt(params.context)}`);
|
|
533
532
|
}
|
|
534
|
-
const tasks = Array.isArray(params.tasks) ? params.tasks : [];
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
533
|
+
const tasks: unknown[] = Array.isArray(params.tasks) ? params.tasks : [];
|
|
534
|
+
if (tasks.length > 0) {
|
|
535
|
+
const defaultAgent = resolveSpawnPolicy(this.session.getSessionSpawns()).defaultAgent;
|
|
536
|
+
const effectiveAgent = (item: unknown): string => {
|
|
537
|
+
if (item && typeof item === "object" && "agent" in item) {
|
|
538
|
+
const agent = item.agent;
|
|
539
|
+
if (typeof agent === "string" && agent.trim()) return agent.trim();
|
|
540
|
+
}
|
|
541
|
+
return defaultAgent;
|
|
542
|
+
};
|
|
543
|
+
const agentCounts = new Map<string, number>();
|
|
544
|
+
for (const item of tasks) {
|
|
545
|
+
const agent = effectiveAgent(item);
|
|
546
|
+
agentCounts.set(agent, (agentCounts.get(agent) ?? 0) + 1);
|
|
542
547
|
}
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
548
|
+
const agentSummary = [...agentCounts].map(([agent, count]) => `${agent} ×${count}`).join(", ");
|
|
549
|
+
lines.push(`Batch agents: ${truncateForPrompt(agentSummary)}`);
|
|
550
|
+
|
|
551
|
+
const firstTask = tasks[0];
|
|
552
|
+
if (firstTask && typeof firstTask === "object") {
|
|
553
|
+
if ("name" in firstTask && typeof firstTask.name === "string" && firstTask.name.trim()) {
|
|
554
|
+
lines.push(`Name: ${truncateForPrompt(firstTask.name)}`);
|
|
555
|
+
}
|
|
556
|
+
lines.push(`Agent: ${truncateForPrompt(effectiveAgent(firstTask))}`);
|
|
557
|
+
const itemModel = formatModelForApproval("model" in firstTask ? firstTask.model : undefined);
|
|
558
|
+
if (itemModel) lines.push(`Model: ${itemModel}`);
|
|
559
|
+
if ("task" in firstTask && typeof firstTask.task === "string") {
|
|
560
|
+
lines.push(`Task:\n${truncateForPrompt(firstTask.task)}`);
|
|
561
|
+
}
|
|
547
562
|
}
|
|
548
563
|
if (tasks.length > 1) {
|
|
549
564
|
lines.push(`+${tasks.length - 1} more task${tasks.length === 2 ? "" : "s"}`);
|
|
@@ -687,27 +702,53 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
687
702
|
const spawnItems = resolveSpawnItems(params);
|
|
688
703
|
const normalizedSpawnParams = spawnItems.map(item => spawnParamsFor(params, item, defaultAgent));
|
|
689
704
|
const resolvedAgents = normalizedSpawnParams.map(spawn => spawn.agent ?? defaultAgent);
|
|
705
|
+
// Resolve every item before choosing an execution path. No executor or
|
|
706
|
+
// job manager may observe a batch unless every effective policy is valid.
|
|
707
|
+
const preflights = await Promise.all(
|
|
708
|
+
normalizedSpawnParams.map(async spawn => {
|
|
709
|
+
try {
|
|
710
|
+
return { policy: await this.#resolveSpawnPreflight(spawn) };
|
|
711
|
+
} catch (error) {
|
|
712
|
+
return { error: error instanceof StructuredSubagentError ? error.message : String(error) };
|
|
713
|
+
}
|
|
714
|
+
}),
|
|
715
|
+
);
|
|
716
|
+
const preflightFailures = preflights
|
|
717
|
+
.map((preflight, index) => ("error" in preflight ? { index, error: preflight.error } : undefined))
|
|
718
|
+
.filter((failure): failure is { index: number; error: string } => failure !== undefined);
|
|
719
|
+
if (preflightFailures.length > 0) {
|
|
720
|
+
if (!batchEnabled) {
|
|
721
|
+
return createTaskModeError(`Task execution failed: ${preflightFailures[0]!.error}`);
|
|
722
|
+
}
|
|
723
|
+
return createTaskModeError(
|
|
724
|
+
preflightFailures
|
|
725
|
+
.map(({ index, error }) => {
|
|
726
|
+
const item = spawnItems[index]!;
|
|
727
|
+
return `Task ${item.name?.trim() || `#${index + 1}`} failed preflight: ${error}`;
|
|
728
|
+
})
|
|
729
|
+
.join("\n"),
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
const policies = preflights.map(preflight => preflight.policy!);
|
|
733
|
+
const itemBlocking = policies.map(policy => policy.effectiveAgent.blocking === true);
|
|
734
|
+
|
|
690
735
|
// Execution mode is per item: an item whose agent type declares
|
|
691
736
|
// `blocking: true` runs inline on this turn (the parent waits on its
|
|
692
737
|
// result); every other item becomes a background job when async
|
|
693
738
|
// execution is available.
|
|
694
|
-
const provisionalBlocking = resolvedAgents.map(
|
|
695
|
-
name => this.#discoveredAgents.find(agent => agent.name === name)?.blocking === true,
|
|
696
|
-
);
|
|
697
739
|
const asyncEnabled = this.session.settings.get("async.enabled");
|
|
698
740
|
const manager = asyncEnabled ? this.session.asyncJobManager : undefined;
|
|
699
|
-
const
|
|
741
|
+
const asyncItems = manager ? spawnItems.filter((_, index) => !itemBlocking[index]) : [];
|
|
700
742
|
const depthCapacity = canSpawnAtDepth(
|
|
701
743
|
this.session.settings.get("task.maxRecursionDepth") ?? 2,
|
|
702
744
|
this.session.taskDepth ?? 0,
|
|
703
745
|
);
|
|
704
746
|
const ircEnabled = isIrcEnabled(this.session.settings, this.session.taskDepth ?? 0);
|
|
705
747
|
|
|
706
|
-
if (!manager ||
|
|
748
|
+
if (!manager || asyncItems.length === 0) {
|
|
707
749
|
// Sync fallback: async execution disabled, orphaned host that never
|
|
708
750
|
// wired a job manager, or every item's agent type declares
|
|
709
|
-
// `blocking: true`.
|
|
710
|
-
// preflight before reserving an id in these inline paths.
|
|
751
|
+
// `blocking: true`.
|
|
711
752
|
if (asyncEnabled && !this.session.asyncJobManager) {
|
|
712
753
|
logger.warn("task: no AsyncJobManager registered; falling back to sync execution");
|
|
713
754
|
}
|
|
@@ -715,7 +756,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
715
756
|
? undefined
|
|
716
757
|
: composeSpawnAdvisory({
|
|
717
758
|
agents: resolvedAgents,
|
|
718
|
-
items:
|
|
759
|
+
items: asyncItems,
|
|
719
760
|
depthCapacity,
|
|
720
761
|
ircEnabled,
|
|
721
762
|
willRunAsync: false,
|
|
@@ -741,43 +782,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
741
782
|
return { ...result, content };
|
|
742
783
|
}
|
|
743
784
|
|
|
744
|
-
// Async jobs are otherwise registered before their body can reach
|
|
745
|
-
// `runStructuredSubagent`. Resolve the shared policy first so policy
|
|
746
|
-
// failures remain synchronous and cannot leave a queued invalid job.
|
|
747
|
-
const preflights = await Promise.all(
|
|
748
|
-
normalizedSpawnParams.map(async spawn => {
|
|
749
|
-
try {
|
|
750
|
-
return { policy: await this.#resolveSpawnPreflight(spawn) };
|
|
751
|
-
} catch (error) {
|
|
752
|
-
return { error: error instanceof StructuredSubagentError ? error.message : String(error) };
|
|
753
|
-
}
|
|
754
|
-
}),
|
|
755
|
-
);
|
|
756
|
-
const preflightFailures = preflights
|
|
757
|
-
.map((preflight, index) => ("error" in preflight ? { index, error: preflight.error } : undefined))
|
|
758
|
-
.filter((failure): failure is { index: number; error: string } => failure !== undefined);
|
|
759
|
-
const renderPreflightFailures = () =>
|
|
760
|
-
preflightFailures
|
|
761
|
-
.map(({ index, error }) => {
|
|
762
|
-
const item = spawnItems[index]!;
|
|
763
|
-
return `Task ${item.name?.trim() || `#${index + 1}`} failed preflight: ${error}`;
|
|
764
|
-
})
|
|
765
|
-
.join("\n");
|
|
766
|
-
if (preflightFailures.length === spawnItems.length) {
|
|
767
|
-
return createTaskModeError(renderPreflightFailures());
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const validIndices = preflights.flatMap((preflight, index) => (preflight.policy ? [index] : []));
|
|
771
|
-
const validSpawns = validIndices.map(index => ({ item: spawnItems[index]!, index }));
|
|
772
|
-
const itemBlocking = preflights.map(preflight => preflight.policy?.effectiveAgent.blocking === true);
|
|
773
|
-
const asyncItems = validIndices.filter(index => !itemBlocking[index]).map(index => spawnItems[index]!);
|
|
774
785
|
// Coordination only makes sense for spawns that keep running after this
|
|
775
786
|
// call returns (the async subset). Blocking items have already completed
|
|
776
787
|
// by then, so a "coordinate while they run" hint would misfire.
|
|
777
788
|
const advisory = this.session.suppressSpawnAdvisory
|
|
778
789
|
? undefined
|
|
779
790
|
: composeSpawnAdvisory({
|
|
780
|
-
agents:
|
|
791
|
+
agents: resolvedAgents,
|
|
781
792
|
items: asyncItems,
|
|
782
793
|
depthCapacity,
|
|
783
794
|
ircEnabled,
|
|
@@ -799,24 +810,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
799
810
|
if (!appended) content.push({ type: "text", text: advisory });
|
|
800
811
|
return { ...result, content };
|
|
801
812
|
};
|
|
802
|
-
const withPreflightFailures = (result: AgentToolResult<TaskToolDetails>): AgentToolResult<TaskToolDetails> => {
|
|
803
|
-
if (preflightFailures.length === 0) return result;
|
|
804
|
-
const failures = renderPreflightFailures();
|
|
805
|
-
let prepended = false;
|
|
806
|
-
const content = result.content.map(part => {
|
|
807
|
-
if (!prepended && part.type === "text" && typeof part.text === "string") {
|
|
808
|
-
prepended = true;
|
|
809
|
-
return { ...part, text: `${failures}\n\n${part.text}` };
|
|
810
|
-
}
|
|
811
|
-
return part;
|
|
812
|
-
});
|
|
813
|
-
if (!prepended) content.unshift({ type: "text", text: failures });
|
|
814
|
-
return { ...result, content };
|
|
815
|
-
};
|
|
816
813
|
if (asyncItems.length === 0) {
|
|
817
|
-
return
|
|
818
|
-
|
|
819
|
-
|
|
814
|
+
return withAdvisory(
|
|
815
|
+
await this.#executeSyncFanout(
|
|
816
|
+
toolCallId,
|
|
817
|
+
params,
|
|
818
|
+
spawnItems.map((item, index) => ({ item, index })),
|
|
819
|
+
defaultAgent,
|
|
820
|
+
signal,
|
|
821
|
+
onUpdate,
|
|
820
822
|
),
|
|
821
823
|
);
|
|
822
824
|
}
|
|
@@ -836,12 +838,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
836
838
|
blocking: boolean;
|
|
837
839
|
progress: AgentProgress;
|
|
838
840
|
}> = [];
|
|
839
|
-
for (const index of
|
|
840
|
-
const item = spawnItems[index]!;
|
|
841
|
+
for (const [index, item] of spawnItems.entries()) {
|
|
841
842
|
const agentType = resolvedAgents[index]!;
|
|
842
|
-
const
|
|
843
|
-
const policy = preflight.policy;
|
|
844
|
-
if (!policy) continue;
|
|
843
|
+
const policy = policies[index]!;
|
|
845
844
|
const agentSource = policy.agent.source;
|
|
846
845
|
const agentId = await outputManager.allocate(item.name?.trim() || generateTaskName());
|
|
847
846
|
const assignment = (item.task ?? "").trim();
|
|
@@ -878,7 +877,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
878
877
|
// failed. Blocking spawns run inline below and land in `results` before
|
|
879
878
|
// the call returns, so post-return job updates never drop them.
|
|
880
879
|
let settledCount = 0;
|
|
881
|
-
let failedCount =
|
|
880
|
+
let failedCount = 0;
|
|
882
881
|
let primaryJobId = asyncSpawns[0].agentId;
|
|
883
882
|
const syncResults: SingleResult[] = [];
|
|
884
883
|
let syncUsage: Usage | undefined;
|
|
@@ -928,7 +927,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
928
927
|
}
|
|
929
928
|
|
|
930
929
|
if (started.length === 0 && syncSpawns.length === 0) {
|
|
931
|
-
return
|
|
930
|
+
return {
|
|
932
931
|
content: [
|
|
933
932
|
{
|
|
934
933
|
type: "text",
|
|
@@ -936,7 +935,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
936
935
|
},
|
|
937
936
|
],
|
|
938
937
|
details: { projectAgentsDir: null, results: [], totalDurationMs: 0 },
|
|
939
|
-
}
|
|
938
|
+
};
|
|
940
939
|
}
|
|
941
940
|
|
|
942
941
|
const scheduleFailureSummary =
|
|
@@ -961,34 +960,30 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
961
960
|
content: [{ type: "text", text: `Spawned agent \`${agentId}\`...` }],
|
|
962
961
|
details: buildAsyncDetails(),
|
|
963
962
|
});
|
|
964
|
-
return
|
|
965
|
-
withAdvisory({
|
|
966
|
-
content: [
|
|
967
|
-
{
|
|
968
|
-
type: "text",
|
|
969
|
-
text: `Spawned agent \`${agentId}\` (job \`${jobId}\`). Its result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first. ${coordinationHint}`,
|
|
970
|
-
},
|
|
971
|
-
],
|
|
972
|
-
details: buildAsyncDetails(),
|
|
973
|
-
}),
|
|
974
|
-
);
|
|
975
|
-
}
|
|
976
|
-
const startedListing = started.map(({ agentId, jobId }) => `- \`${agentId}\` (job \`${jobId}\`)`).join("\n");
|
|
977
|
-
onUpdate?.({
|
|
978
|
-
content: [{ type: "text", text: `Spawned ${started.length} agents...` }],
|
|
979
|
-
details: buildAsyncDetails(),
|
|
980
|
-
});
|
|
981
|
-
return withPreflightFailures(
|
|
982
|
-
withAdvisory({
|
|
963
|
+
return withAdvisory({
|
|
983
964
|
content: [
|
|
984
965
|
{
|
|
985
966
|
type: "text",
|
|
986
|
-
text: `Spawned
|
|
967
|
+
text: `Spawned agent \`${agentId}\` (job \`${jobId}\`). Its result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first. ${coordinationHint}`,
|
|
987
968
|
},
|
|
988
969
|
],
|
|
989
970
|
details: buildAsyncDetails(),
|
|
990
|
-
})
|
|
991
|
-
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
const startedListing = started.map(({ agentId, jobId }) => `- \`${agentId}\` (job \`${jobId}\`)`).join("\n");
|
|
974
|
+
onUpdate?.({
|
|
975
|
+
content: [{ type: "text", text: `Spawned ${started.length} agents...` }],
|
|
976
|
+
details: buildAsyncDetails(),
|
|
977
|
+
});
|
|
978
|
+
return withAdvisory({
|
|
979
|
+
content: [
|
|
980
|
+
{
|
|
981
|
+
type: "text",
|
|
982
|
+
text: `Spawned ${started.length} background agents using ${agentLabel}.${scheduleFailureSummary} Each result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first.\n${startedListing}\n${coordinationHint}`,
|
|
983
|
+
},
|
|
984
|
+
],
|
|
985
|
+
details: buildAsyncDetails(),
|
|
986
|
+
});
|
|
992
987
|
}
|
|
993
988
|
|
|
994
989
|
// Mixed call: the async jobs above already run detached; the blocking
|
|
@@ -1053,12 +1048,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1053
1048
|
const text = [merged.contentParts.join("\n\n"), spawnedSummary]
|
|
1054
1049
|
.filter(section => section.trim().length > 0)
|
|
1055
1050
|
.join("\n\n");
|
|
1056
|
-
return
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}),
|
|
1061
|
-
);
|
|
1051
|
+
return withAdvisory({
|
|
1052
|
+
content: [{ type: "text", text: text.length > 0 ? text : "No results." }],
|
|
1053
|
+
details: buildAsyncDetails(),
|
|
1054
|
+
});
|
|
1062
1055
|
}
|
|
1063
1056
|
|
|
1064
1057
|
/**
|
|
@@ -18,10 +18,11 @@ import { ADVISOR_TRANSCRIPT_STEM } from "../advisor/transcript-recorder";
|
|
|
18
18
|
*
|
|
19
19
|
* The first allocation of a given name keeps the name as-is; subsequent
|
|
20
20
|
* allocations of the same name get a `-2`, `-3`, … suffix. On resume, scans
|
|
21
|
-
* existing output files so
|
|
21
|
+
* existing output and child-session files so prior state is never overwritten.
|
|
22
22
|
*/
|
|
23
23
|
export class AgentOutputManager {
|
|
24
24
|
#initialized = false;
|
|
25
|
+
#initializing: Promise<void> | undefined;
|
|
25
26
|
/** Final ids already handed out, relative to this manager's scope. */
|
|
26
27
|
readonly #taken = new Set<string>();
|
|
27
28
|
readonly #getArtifactsDir: () => string | null;
|
|
@@ -42,8 +43,12 @@ export class AgentOutputManager {
|
|
|
42
43
|
*/
|
|
43
44
|
async #ensureInitialized(): Promise<void> {
|
|
44
45
|
if (this.#initialized) return;
|
|
46
|
+
this.#initializing ??= this.#seedFromDisk();
|
|
47
|
+
await this.#initializing;
|
|
45
48
|
this.#initialized = true;
|
|
49
|
+
}
|
|
46
50
|
|
|
51
|
+
async #seedFromDisk(): Promise<void> {
|
|
47
52
|
const dir = this.#getArtifactsDir();
|
|
48
53
|
if (!dir) return;
|
|
49
54
|
|
|
@@ -56,8 +61,9 @@ export class AgentOutputManager {
|
|
|
56
61
|
|
|
57
62
|
const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
|
|
58
63
|
for (const file of files) {
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const extension = file.endsWith(".jsonl") ? ".jsonl" : file.endsWith(".md") ? ".md" : undefined;
|
|
65
|
+
if (!extension) continue;
|
|
66
|
+
let rest = file.slice(0, -extension.length);
|
|
61
67
|
if (prefix) {
|
|
62
68
|
if (!rest.startsWith(prefix)) continue;
|
|
63
69
|
rest = rest.slice(prefix.length);
|
|
@@ -80,6 +86,22 @@ export class AgentOutputManager {
|
|
|
80
86
|
return this.#parentPrefix ? `${this.#parentPrefix}.${candidate}` : candidate;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
/** Reserve final IDs discovered outside the output directory scan. */
|
|
90
|
+
async reserve(ids: Iterable<string>): Promise<void> {
|
|
91
|
+
await this.#ensureInitialized();
|
|
92
|
+
const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
|
|
93
|
+
for (const id of ids) {
|
|
94
|
+
let rest = id;
|
|
95
|
+
if (prefix) {
|
|
96
|
+
if (!rest.startsWith(prefix)) continue;
|
|
97
|
+
rest = rest.slice(prefix.length);
|
|
98
|
+
}
|
|
99
|
+
const dot = rest.indexOf(".");
|
|
100
|
+
const segment = dot === -1 ? rest : rest.slice(0, dot);
|
|
101
|
+
if (segment) this.#taken.add(segment);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
/**
|
|
84
106
|
* Allocate a unique ID.
|
|
85
107
|
*
|
|
@@ -71,7 +71,7 @@ export function createPersistedSubagentReviverFactory(
|
|
|
71
71
|
taskDepth++;
|
|
72
72
|
parentId = registry.get(parentId)?.parentId;
|
|
73
73
|
}
|
|
74
|
-
return async
|
|
74
|
+
return async expectedRef => {
|
|
75
75
|
// Re-open fresh on every revive: park closes the writer, so this takes
|
|
76
76
|
// the single-writer lock cleanly and restores the full message history.
|
|
77
77
|
const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
|
|
@@ -97,6 +97,7 @@ export function createPersistedSubagentReviverFactory(
|
|
|
97
97
|
agentDisplayName: ref.displayName,
|
|
98
98
|
parentTaskPrefix: ref.id,
|
|
99
99
|
parentAgentId: ref.parentId,
|
|
100
|
+
expectedAgentRef: expectedRef,
|
|
100
101
|
taskDepth,
|
|
101
102
|
toolNames: init.tools,
|
|
102
103
|
outputSchema: init.outputSchema,
|
|
@@ -131,8 +132,8 @@ export function createPersistedSubagentReviverFactory(
|
|
|
131
132
|
// Without it the idle-TTL timer never clears on a turn and the lifecycle
|
|
132
133
|
// could park the agent mid-run.
|
|
133
134
|
session.subscribe(event => {
|
|
134
|
-
if (event.type === "agent_start") registry.setStatus(ref.id, "running");
|
|
135
|
-
else if (event.type === "agent_end") registry.setStatus(ref.id, "idle");
|
|
135
|
+
if (event.type === "agent_start") registry.setStatus(ref.id, "running", session);
|
|
136
|
+
else if (event.type === "agent_end") registry.setStatus(ref.id, "idle", session);
|
|
136
137
|
});
|
|
137
138
|
return session;
|
|
138
139
|
};
|
|
@@ -373,6 +373,7 @@ function buildExecutorOptions(
|
|
|
373
373
|
const enableMCP = !policy.planMode && (session.enableMCP ?? true);
|
|
374
374
|
return {
|
|
375
375
|
cwd: session.cwd,
|
|
376
|
+
additionalDirectories: session.additionalDirectories,
|
|
376
377
|
agent: policy.effectiveAgent,
|
|
377
378
|
task: renderSubagentPrompt(request.assignment),
|
|
378
379
|
assignment: request.assignment.trim(),
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -196,6 +196,28 @@ export class TinyTitleClient {
|
|
|
196
196
|
return () => this.#progressListeners.delete(listener);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Spawn the tiny-model worker ahead of first use without loading any model.
|
|
201
|
+
* Called from idle TUI startup so the first {@link generate} reuses a live,
|
|
202
|
+
* unref'd subprocess instead of paying subprocess-spawn latency on the submit
|
|
203
|
+
* hot path (issue #6462). No-ops for online / non-local keys and for models
|
|
204
|
+
* already marked failed. A no-op `ping` round-trips the transport to fault in
|
|
205
|
+
* the worker's module graph; no pending request is registered, so
|
|
206
|
+
* {@link #syncWorkerRef} leaves the worker unref'd and idle sessions still exit.
|
|
207
|
+
*/
|
|
208
|
+
prewarm(modelKey: string): void {
|
|
209
|
+
if (!isTinyTitleLocalModelKey(modelKey) || this.#failedModels.has(modelKey)) return;
|
|
210
|
+
try {
|
|
211
|
+
const worker = this.#ensureWorker();
|
|
212
|
+
worker.send({ type: "ping", id: String(++this.#nextRequestId) });
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.debug("tiny-title: prewarm failed", {
|
|
215
|
+
modelKey,
|
|
216
|
+
error: error instanceof Error ? error.message : String(error),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
199
221
|
async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null>;
|
|
200
222
|
async generate(modelKey: string, message: string, options?: TinyTitleGenerateOptions): Promise<string | null>;
|
|
201
223
|
async generate(
|
package/src/tools/approval.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface ResolvedApproval {
|
|
|
20
20
|
tier: ToolTier;
|
|
21
21
|
reason?: string;
|
|
22
22
|
override: boolean;
|
|
23
|
+
source?: "tool" | "user" | "mode";
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const POLICY_VALUES: ReadonlySet<ApprovalPolicy> = new Set(["allow", "deny", "prompt"]);
|
|
@@ -50,7 +51,7 @@ function isToolTier(value: unknown): value is ToolTier {
|
|
|
50
51
|
return typeof value === "string" && TIER_VALUES.has(value as ToolTier);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> {
|
|
54
|
+
function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> & { policy?: ApprovalPolicy } {
|
|
54
55
|
if (isToolTier(value)) {
|
|
55
56
|
return { tier: value, override: false };
|
|
56
57
|
}
|
|
@@ -59,9 +60,11 @@ function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> {
|
|
|
59
60
|
const record = value as Record<string, unknown>;
|
|
60
61
|
const tier = isToolTier(record.tier) ? record.tier : "exec";
|
|
61
62
|
const reason = typeof record.reason === "string" && record.reason.length > 0 ? record.reason : undefined;
|
|
63
|
+
const policy = normalizePolicy(record.policy);
|
|
62
64
|
return {
|
|
63
65
|
tier,
|
|
64
66
|
override: record.override === true,
|
|
67
|
+
...(policy ? { policy } : {}),
|
|
65
68
|
...(reason ? { reason } : {}),
|
|
66
69
|
};
|
|
67
70
|
}
|
|
@@ -69,7 +72,10 @@ function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> {
|
|
|
69
72
|
return { tier: "exec", override: false };
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
function getToolDecision(
|
|
75
|
+
function getToolDecision(
|
|
76
|
+
tool: ApprovalSubject,
|
|
77
|
+
args: unknown,
|
|
78
|
+
): Omit<ResolvedApproval, "policy"> & { policy?: ApprovalPolicy } {
|
|
73
79
|
const approval = tool.approval;
|
|
74
80
|
const decision: ToolApprovalDecision | undefined = typeof approval === "function" ? approval(args) : approval;
|
|
75
81
|
return normalizeDecision(decision);
|
|
@@ -110,34 +116,70 @@ export function resolveApproval(
|
|
|
110
116
|
const decision = getToolDecision(tool, args);
|
|
111
117
|
const userPolicy = Object.hasOwn(userConfig, tool.name) ? normalizePolicy(userConfig[tool.name]) : undefined;
|
|
112
118
|
|
|
119
|
+
if (decision.policy === "deny") {
|
|
120
|
+
return {
|
|
121
|
+
policy: "deny",
|
|
122
|
+
tier: decision.tier,
|
|
123
|
+
override: decision.override,
|
|
124
|
+
source: "tool",
|
|
125
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (userPolicy === "deny") {
|
|
129
|
+
return { policy: "deny", tier: decision.tier, override: decision.override, source: "user" };
|
|
130
|
+
}
|
|
131
|
+
|
|
113
132
|
if (mode === "yolo") {
|
|
114
|
-
|
|
133
|
+
if (decision.policy) {
|
|
134
|
+
return {
|
|
135
|
+
policy: decision.policy,
|
|
136
|
+
tier: decision.tier,
|
|
137
|
+
override: false,
|
|
138
|
+
source: "tool",
|
|
139
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
policy: userPolicy ?? "allow",
|
|
144
|
+
tier: decision.tier,
|
|
145
|
+
override: false,
|
|
146
|
+
source: userPolicy ? "user" : "mode",
|
|
147
|
+
};
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
if (decision.override) {
|
|
118
|
-
if (userPolicy === "deny") {
|
|
119
|
-
return { policy: "deny", tier: decision.tier, override: true };
|
|
120
|
-
}
|
|
121
151
|
return {
|
|
122
|
-
policy: "prompt",
|
|
152
|
+
policy: decision.policy === "allow" ? "allow" : "prompt",
|
|
123
153
|
tier: decision.tier,
|
|
124
154
|
override: true,
|
|
155
|
+
source: "tool",
|
|
156
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (decision.policy === "allow" || decision.policy === "prompt") {
|
|
161
|
+
return {
|
|
162
|
+
policy: decision.policy,
|
|
163
|
+
tier: decision.tier,
|
|
164
|
+
override: false,
|
|
165
|
+
source: "tool",
|
|
125
166
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
126
167
|
};
|
|
127
168
|
}
|
|
128
169
|
|
|
129
170
|
if (userPolicy) {
|
|
130
|
-
return { policy: userPolicy, tier: decision.tier, override: false };
|
|
171
|
+
return { policy: userPolicy, tier: decision.tier, override: false, source: "user" };
|
|
131
172
|
}
|
|
132
173
|
|
|
133
174
|
if (modeApprovesTier(mode, decision.tier)) {
|
|
134
|
-
return { policy: "allow", tier: decision.tier, override: false };
|
|
175
|
+
return { policy: "allow", tier: decision.tier, override: false, source: "mode" };
|
|
135
176
|
}
|
|
136
177
|
|
|
137
178
|
return {
|
|
138
179
|
policy: "prompt",
|
|
139
180
|
tier: decision.tier,
|
|
140
181
|
override: false,
|
|
182
|
+
source: "mode",
|
|
141
183
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
142
184
|
};
|
|
143
185
|
}
|
|
@@ -154,9 +196,12 @@ export function requiresApproval(
|
|
|
154
196
|
mode: ApprovalMode,
|
|
155
197
|
userConfig: Record<string, unknown> = {},
|
|
156
198
|
): { required: boolean; reason?: string } {
|
|
157
|
-
const { policy, reason } = resolveApproval(tool, args, mode, userConfig);
|
|
199
|
+
const { policy, reason, source } = resolveApproval(tool, args, mode, userConfig);
|
|
158
200
|
|
|
159
201
|
if (policy === "deny") {
|
|
202
|
+
if (source === "tool") {
|
|
203
|
+
throw new Error(`Tool "${tool.name}" is blocked by tool policy.${reason ? `\nReason: ${reason}` : ""}`);
|
|
204
|
+
}
|
|
160
205
|
throw new Error(
|
|
161
206
|
`Tool "${tool.name}" is blocked by user policy.\n` +
|
|
162
207
|
`To allow: remove "tools.approval.${tool.name}: deny" from config.`,
|