@intentic/sandbox-contract 1.232.0 → 1.233.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/agent-catalog.d.ts +2 -1
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +1 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/chores/chores.d.ts +1 -1
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/probes.d.ts +1 -1
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/stack.d.ts.map +1 -1
- package/dist/chores/stack.js +2 -1
- package/dist/chores/stack.js.map +1 -1
- package/dist/chores/verdict.d.ts +1 -1
- package/dist/chores/verdict.d.ts.map +1 -1
- package/dist/command-classes.d.ts +14 -0
- package/dist/command-classes.d.ts.map +1 -0
- package/dist/command-classes.js +168 -0
- package/dist/command-classes.js.map +1 -0
- package/dist/contracts/activity.contract.js +1 -1
- package/dist/contracts/activity.contract.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +40 -2
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +4 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +24 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +5 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.js +2 -1
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +63 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +3 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/chores.contract.js +2 -1
- package/dist/contracts/chores.contract.js.map +1 -1
- package/dist/contracts/ci.contract.js +2 -1
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.js +2 -1
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/cursor.contract.js +2 -1
- package/dist/contracts/cursor.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/drafts.contract.js +2 -1
- package/dist/contracts/drafts.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +2 -1
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/exit.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.js +2 -1
- package/dist/contracts/exit.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +42 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +4 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +4 -1
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +2 -1
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/history.contract.d.ts.map +1 -1
- package/dist/contracts/history.contract.js +2 -1
- package/dist/contracts/history.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +7 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +4 -1
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.js +2 -1
- package/dist/contracts/intentic.contract.js.map +1 -1
- package/dist/contracts/inventory.contract.js +1 -1
- package/dist/contracts/inventory.contract.js.map +1 -1
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.js +1 -1
- package/dist/contracts/logs.contract.js.map +1 -1
- package/dist/contracts/loops.contract.js +2 -1
- package/dist/contracts/loops.contract.js.map +1 -1
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/panels.contract.js +2 -1
- package/dist/contracts/panels.contract.js.map +1 -1
- package/dist/contracts/personas.contract.js +2 -1
- package/dist/contracts/personas.contract.js.map +1 -1
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.js +2 -1
- package/dist/contracts/ports.contract.js.map +1 -1
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js +2 -1
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/providers.contract.js +2 -1
- package/dist/contracts/providers.contract.js.map +1 -1
- package/dist/contracts/public.contract.d.ts.map +1 -1
- package/dist/contracts/public.contract.js +2 -1
- package/dist/contracts/public.contract.js.map +1 -1
- package/dist/contracts/push.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.js +2 -1
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +45 -2
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +10 -2
- package/dist/contracts/runner.contract.js.map +1 -1
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +2 -1
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.js +1 -1
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +20 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +3 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts.map +1 -1
- package/dist/contracts/share.contract.js +2 -1
- package/dist/contracts/share.contract.js.map +1 -1
- package/dist/contracts/skills.contract.d.ts.map +1 -1
- package/dist/contracts/skills.contract.js +2 -1
- package/dist/contracts/skills.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +31 -16
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +6 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js +4 -1
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/usage.contract.js +1 -1
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/vpn.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.js +2 -1
- package/dist/contracts/vpn.contract.js.map +1 -1
- package/dist/contracts/webext.contract.d.ts +45 -0
- package/dist/contracts/webext.contract.d.ts.map +1 -0
- package/dist/contracts/webext.contract.js +12 -0
- package/dist/contracts/webext.contract.js.map +1 -0
- package/dist/contracts/workflows.contract.js +2 -1
- package/dist/contracts/workflows.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +2 -2
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +7 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/definition-action.d.ts +7 -0
- package/dist/definition-action.d.ts.map +1 -0
- package/dist/definition-action.js +3 -0
- package/dist/definition-action.js.map +1 -0
- package/dist/definition.d.ts +124 -21
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +29 -7
- package/dist/definition.js.map +1 -1
- package/dist/events.d.ts +101 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +25 -1
- package/dist/events.js.map +1 -1
- package/dist/fast-tier.d.ts +1 -1
- package/dist/fast-tier.d.ts.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +1 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +355 -103
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/dist/listener-protocol.js +1 -1
- package/dist/listener-protocol.js.map +1 -1
- package/dist/quick-model.d.ts +1 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/runner-protocol.d.ts +18 -0
- package/dist/runner-protocol.d.ts.map +1 -1
- package/dist/runner-protocol.js +8 -4
- package/dist/runner-protocol.js.map +1 -1
- package/dist/schemas/activity.d.ts +112 -0
- package/dist/schemas/activity.d.ts.map +1 -0
- package/dist/schemas/activity.js +70 -0
- package/dist/schemas/activity.js.map +1 -0
- package/dist/schemas/agent.d.ts +189 -0
- package/dist/schemas/agent.d.ts.map +1 -0
- package/dist/schemas/agent.js +170 -0
- package/dist/schemas/agent.js.map +1 -0
- package/dist/schemas/agents.d.ts +690 -0
- package/dist/schemas/agents.d.ts.map +1 -0
- package/dist/schemas/agents.js +314 -0
- package/dist/schemas/agents.js.map +1 -0
- package/dist/schemas/automations.d.ts +736 -0
- package/dist/schemas/automations.d.ts.map +1 -0
- package/dist/schemas/automations.js +218 -0
- package/dist/schemas/automations.js.map +1 -0
- package/dist/schemas/capabilities.d.ts +753 -0
- package/dist/schemas/capabilities.d.ts.map +1 -0
- package/dist/schemas/capabilities.js +269 -0
- package/dist/schemas/capabilities.js.map +1 -0
- package/dist/schemas/ci.d.ts +179 -0
- package/dist/schemas/ci.d.ts.map +1 -0
- package/dist/schemas/ci.js +99 -0
- package/dist/schemas/ci.js.map +1 -0
- package/dist/schemas/claude-gate.d.ts +13 -0
- package/dist/schemas/claude-gate.d.ts.map +1 -0
- package/dist/schemas/claude-gate.js +8 -0
- package/dist/schemas/claude-gate.js.map +1 -0
- package/dist/schemas/codebase-health.d.ts +56 -0
- package/dist/schemas/codebase-health.d.ts.map +1 -0
- package/dist/schemas/codebase-health.js +43 -0
- package/dist/schemas/codebase-health.js.map +1 -0
- package/dist/schemas/computers.d.ts +386 -0
- package/dist/schemas/computers.d.ts.map +1 -0
- package/dist/schemas/computers.js +107 -0
- package/dist/schemas/computers.js.map +1 -0
- package/dist/schemas/drafts.d.ts +84 -0
- package/dist/schemas/drafts.d.ts.map +1 -0
- package/dist/schemas/drafts.js +44 -0
- package/dist/schemas/drafts.js.map +1 -0
- package/dist/schemas/environment.d.ts +349 -0
- package/dist/schemas/environment.d.ts.map +1 -0
- package/dist/schemas/environment.js +111 -0
- package/dist/schemas/environment.js.map +1 -0
- package/dist/schemas/exit.d.ts +151 -0
- package/dist/schemas/exit.d.ts.map +1 -0
- package/dist/schemas/exit.js +115 -0
- package/dist/schemas/exit.js.map +1 -0
- package/dist/schemas/extension-updates.d.ts +1014 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -0
- package/dist/schemas/extension-updates.js +197 -0
- package/dist/schemas/extension-updates.js.map +1 -0
- package/dist/schemas/fast-mode.d.ts +8 -0
- package/dist/schemas/fast-mode.d.ts.map +1 -0
- package/dist/schemas/fast-mode.js +3 -0
- package/dist/schemas/fast-mode.js.map +1 -0
- package/dist/schemas/git-history.d.ts +238 -0
- package/dist/schemas/git-history.d.ts.map +1 -0
- package/dist/schemas/git-history.js +152 -0
- package/dist/schemas/git-history.js.map +1 -0
- package/dist/schemas/git.d.ts +471 -0
- package/dist/schemas/git.d.ts.map +1 -0
- package/dist/schemas/git.js +204 -0
- package/dist/schemas/git.js.map +1 -0
- package/dist/schemas/history.d.ts +100 -0
- package/dist/schemas/history.d.ts.map +1 -0
- package/dist/schemas/history.js +54 -0
- package/dist/schemas/history.js.map +1 -0
- package/dist/schemas/hosts.d.ts +41 -0
- package/dist/schemas/hosts.d.ts.map +1 -0
- package/dist/schemas/hosts.js +18 -0
- package/dist/schemas/hosts.js.map +1 -0
- package/dist/schemas/intentic.d.ts +5 -0
- package/dist/schemas/intentic.d.ts.map +1 -0
- package/dist/schemas/intentic.js +3 -0
- package/dist/schemas/intentic.js.map +1 -0
- package/dist/schemas/internal.d.ts +8 -0
- package/dist/schemas/internal.d.ts.map +1 -0
- package/dist/schemas/internal.js +12 -0
- package/dist/schemas/internal.js.map +1 -0
- package/dist/schemas/inventory.d.ts +171 -0
- package/dist/schemas/inventory.d.ts.map +1 -0
- package/dist/schemas/inventory.js +51 -0
- package/dist/schemas/inventory.js.map +1 -0
- package/dist/schemas/logs.d.ts +67 -0
- package/dist/schemas/logs.d.ts.map +1 -0
- package/dist/schemas/logs.js +54 -0
- package/dist/schemas/logs.js.map +1 -0
- package/dist/schemas/loops.d.ts +415 -0
- package/dist/schemas/loops.d.ts.map +1 -0
- package/dist/schemas/loops.js +188 -0
- package/dist/schemas/loops.js.map +1 -0
- package/dist/schemas/maintenance.d.ts +557 -0
- package/dist/schemas/maintenance.d.ts.map +1 -0
- package/dist/schemas/maintenance.js +201 -0
- package/dist/schemas/maintenance.js.map +1 -0
- package/dist/schemas/marketplace.d.ts +64 -0
- package/dist/schemas/marketplace.d.ts.map +1 -0
- package/dist/schemas/marketplace.js +17 -0
- package/dist/schemas/marketplace.js.map +1 -0
- package/dist/schemas/panels.d.ts +61 -0
- package/dist/schemas/panels.d.ts.map +1 -0
- package/dist/schemas/panels.js +51 -0
- package/dist/schemas/panels.js.map +1 -0
- package/dist/schemas/personas.d.ts +122 -0
- package/dist/schemas/personas.d.ts.map +1 -0
- package/dist/schemas/personas.js +102 -0
- package/dist/schemas/personas.js.map +1 -0
- package/dist/schemas/plan-limits.d.ts +172 -0
- package/dist/schemas/plan-limits.d.ts.map +1 -0
- package/dist/schemas/plan-limits.js +126 -0
- package/dist/schemas/plan-limits.js.map +1 -0
- package/dist/schemas/ports.d.ts +71 -0
- package/dist/schemas/ports.d.ts.map +1 -0
- package/dist/schemas/ports.js +42 -0
- package/dist/schemas/ports.js.map +1 -0
- package/dist/schemas/provider-oauth.d.ts +128 -0
- package/dist/schemas/provider-oauth.d.ts.map +1 -0
- package/dist/schemas/provider-oauth.js +91 -0
- package/dist/schemas/provider-oauth.js.map +1 -0
- package/dist/schemas/provider-subscriptions.d.ts +9 -0
- package/dist/schemas/provider-subscriptions.d.ts.map +1 -0
- package/dist/schemas/provider-subscriptions.js +3 -0
- package/dist/schemas/provider-subscriptions.js.map +1 -0
- package/dist/schemas/public.d.ts +32 -0
- package/dist/schemas/public.d.ts.map +1 -0
- package/dist/schemas/public.js +29 -0
- package/dist/schemas/public.js.map +1 -0
- package/dist/schemas/push.d.ts +55 -0
- package/dist/schemas/push.d.ts.map +1 -0
- package/dist/schemas/push.js +60 -0
- package/dist/schemas/push.js.map +1 -0
- package/dist/schemas/secrets.d.ts +86 -0
- package/dist/schemas/secrets.d.ts.map +1 -0
- package/dist/schemas/secrets.js +51 -0
- package/dist/schemas/secrets.js.map +1 -0
- package/dist/schemas/sessions.d.ts +32 -0
- package/dist/schemas/sessions.d.ts.map +1 -0
- package/dist/schemas/sessions.js +11 -0
- package/dist/schemas/sessions.js.map +1 -0
- package/dist/schemas/settings.d.ts +548 -0
- package/dist/schemas/settings.d.ts.map +1 -0
- package/dist/schemas/settings.js +259 -0
- package/dist/schemas/settings.js.map +1 -0
- package/dist/schemas/share.d.ts +49 -0
- package/dist/schemas/share.d.ts.map +1 -0
- package/dist/schemas/share.js +26 -0
- package/dist/schemas/share.js.map +1 -0
- package/dist/schemas/shared.d.ts +22 -0
- package/dist/schemas/shared.d.ts.map +1 -0
- package/dist/schemas/shared.js +16 -0
- package/dist/schemas/shared.js.map +1 -0
- package/dist/schemas/system.d.ts +86 -0
- package/dist/schemas/system.d.ts.map +1 -0
- package/dist/schemas/system.js +64 -0
- package/dist/schemas/system.js.map +1 -0
- package/dist/schemas/terminal.d.ts +230 -0
- package/dist/schemas/terminal.d.ts.map +1 -0
- package/dist/schemas/terminal.js +133 -0
- package/dist/schemas/terminal.js.map +1 -0
- package/dist/schemas/usage.d.ts +113 -0
- package/dist/schemas/usage.d.ts.map +1 -0
- package/dist/schemas/usage.js +91 -0
- package/dist/schemas/usage.js.map +1 -0
- package/dist/schemas/vpn.d.ts +229 -0
- package/dist/schemas/vpn.d.ts.map +1 -0
- package/dist/schemas/vpn.js +115 -0
- package/dist/schemas/vpn.js.map +1 -0
- package/dist/schemas/webext.d.ts +98 -0
- package/dist/schemas/webext.d.ts.map +1 -0
- package/dist/schemas/webext.js +36 -0
- package/dist/schemas/webext.js.map +1 -0
- package/dist/schemas/workflows.d.ts +881 -0
- package/dist/schemas/workflows.d.ts.map +1 -0
- package/dist/schemas/workflows.js +164 -0
- package/dist/schemas/workflows.js.map +1 -0
- package/dist/schemas/workspace-repos.d.ts +143 -0
- package/dist/schemas/workspace-repos.d.ts.map +1 -0
- package/dist/schemas/workspace-repos.js +87 -0
- package/dist/schemas/workspace-repos.js.map +1 -0
- package/dist/schemas/workspace-search.d.ts +170 -0
- package/dist/schemas/workspace-search.d.ts.map +1 -0
- package/dist/schemas/workspace-search.js +85 -0
- package/dist/schemas/workspace-search.js.map +1 -0
- package/dist/schemas/workspace-setup.d.ts +48 -0
- package/dist/schemas/workspace-setup.d.ts.map +1 -0
- package/dist/schemas/workspace-setup.js +26 -0
- package/dist/schemas/workspace-setup.js.map +1 -0
- package/dist/schemas/workspace-tree.d.ts +120 -0
- package/dist/schemas/workspace-tree.d.ts.map +1 -0
- package/dist/schemas/workspace-tree.js +111 -0
- package/dist/schemas/workspace-tree.js.map +1 -0
- package/dist/webext-links.d.ts +13 -0
- package/dist/webext-links.d.ts.map +1 -0
- package/dist/webext-links.js +24 -0
- package/dist/webext-links.js.map +1 -0
- package/dist/webext-protocol.d.ts +8 -0
- package/dist/webext-protocol.d.ts.map +1 -0
- package/dist/webext-protocol.js +7 -0
- package/dist/webext-protocol.js.map +1 -0
- package/dist/workflow-faults.d.ts +1 -1
- package/dist/workflow-faults.d.ts.map +1 -1
- package/dist/workspace-state.d.ts +2 -2
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +2 -2
- package/dist/workspace-state.js.map +1 -1
- package/package.json +15 -4
- package/src/agent-catalog.test.ts +1 -1
- package/src/agent-catalog.ts +4 -11
- package/src/chores/chores.ts +1 -1
- package/src/chores/probes.ts +1 -1
- package/src/chores/stack.test.ts +19 -0
- package/src/chores/stack.ts +19 -3
- package/src/chores/verdict.test.ts +11 -1
- package/src/chores/verdict.ts +1 -1
- package/src/command-classes.test.ts +378 -0
- package/src/command-classes.ts +378 -0
- package/src/contracts/activity.contract.ts +1 -1
- package/src/contracts/agent.contract.ts +4 -13
- package/src/contracts/agents.contract.ts +6 -6
- package/src/contracts/automations.contract.ts +2 -2
- package/src/contracts/capabilities.contract.ts +3 -4
- package/src/contracts/chores.contract.ts +2 -1
- package/src/contracts/ci.contract.ts +2 -2
- package/src/contracts/claude.contract.ts +2 -2
- package/src/contracts/cursor.contract.ts +2 -2
- package/src/contracts/drafts.contract.ts +2 -1
- package/src/contracts/endpoints.contract.ts +2 -1
- package/src/contracts/exit.contract.ts +2 -1
- package/src/contracts/extensions.contract.ts +5 -5
- package/src/contracts/git.contract.ts +19 -18
- package/src/contracts/grok.contract.ts +2 -1
- package/src/contracts/history.contract.ts +2 -1
- package/src/contracts/host.contract.ts +4 -1
- package/src/contracts/intentic.contract.ts +2 -1
- package/src/contracts/inventory.contract.ts +1 -1
- package/src/contracts/logs.contract.ts +7 -1
- package/src/contracts/loops.contract.ts +2 -2
- package/src/contracts/panels.contract.ts +2 -1
- package/src/contracts/personas.contract.ts +3 -3
- package/src/contracts/ports.contract.ts +3 -2
- package/src/contracts/prepush.contract.ts +2 -1
- package/src/contracts/providers.contract.ts +2 -1
- package/src/contracts/public.contract.ts +3 -2
- package/src/contracts/push.contract.ts +2 -1
- package/src/contracts/runner.contract.ts +18 -2
- package/src/contracts/secrets.contract.ts +2 -1
- package/src/contracts/sessions.contract.ts +1 -1
- package/src/contracts/settings.contract.ts +3 -9
- package/src/contracts/share.contract.ts +3 -2
- package/src/contracts/skills.contract.ts +2 -1
- package/src/contracts/system.contract.ts +6 -9
- package/src/contracts/translator.contract.ts +4 -1
- package/src/contracts/usage.contract.ts +1 -1
- package/src/contracts/vpn.contract.ts +2 -8
- package/src/contracts/webext.contract.ts +37 -0
- package/src/contracts/workflows.contract.ts +2 -2
- package/src/contracts/workspace.contract.ts +10 -12
- package/src/conversation-ids.test.ts +1 -1
- package/src/definition-action.ts +8 -0
- package/src/definition.ts +74 -10
- package/src/events.ts +84 -22
- package/src/fast-tier.ts +1 -1
- package/src/history-state.ts +5 -0
- package/src/host-protocol.ts +1 -1
- package/src/hostnames.test.ts +2 -2
- package/src/index.ts +60 -1
- package/src/listener-protocol.ts +1 -1
- package/src/quick-model.ts +1 -1
- package/src/runner-protocol.ts +35 -5
- package/src/schemas/activity.ts +123 -0
- package/src/schemas/agent.ts +409 -0
- package/src/schemas/agents.ts +802 -0
- package/src/schemas/automations.ts +510 -0
- package/src/schemas/capabilities.ts +729 -0
- package/src/schemas/ci.ts +235 -0
- package/src/schemas/claude-gate.ts +13 -0
- package/src/schemas/codebase-health.ts +64 -0
- package/src/schemas/computers.ts +287 -0
- package/src/schemas/drafts.ts +108 -0
- package/src/schemas/environment.ts +276 -0
- package/src/schemas/exit.ts +222 -0
- package/src/schemas/extension-updates.ts +322 -0
- package/src/schemas/fast-mode.ts +8 -0
- package/src/schemas/git-history.ts +265 -0
- package/src/schemas/git.ts +430 -0
- package/src/schemas/history.ts +112 -0
- package/src/schemas/hosts.ts +37 -0
- package/src/schemas/intentic.ts +2 -0
- package/src/schemas/internal.ts +17 -0
- package/src/schemas/inventory.ts +68 -0
- package/src/schemas/logs.ts +96 -0
- package/src/schemas/loops.ts +401 -0
- package/src/schemas/maintenance.ts +421 -0
- package/src/schemas/marketplace.ts +23 -0
- package/src/schemas/panels.ts +99 -0
- package/src/schemas/personas.ts +281 -0
- package/src/schemas/plan-limits.ts +255 -0
- package/src/schemas/ports.ts +84 -0
- package/src/schemas/provider-oauth.ts +191 -0
- package/src/schemas/provider-subscriptions.ts +11 -0
- package/src/schemas/public.ts +52 -0
- package/src/schemas/push.ts +112 -0
- package/src/schemas/secrets.ts +68 -0
- package/src/schemas/sessions.ts +16 -0
- package/src/schemas/settings.ts +932 -0
- package/src/schemas/share.ts +71 -0
- package/src/schemas/shared.ts +32 -0
- package/src/schemas/system.ts +156 -0
- package/src/schemas/terminal.ts +286 -0
- package/src/schemas/usage.ts +303 -0
- package/src/{schemas.test.ts → schemas/version-seam.test.ts} +4 -2
- package/src/schemas/vpn.ts +215 -0
- package/src/schemas/webext.ts +79 -0
- package/src/schemas/workflows.ts +413 -0
- package/src/schemas/workspace-repos.ts +123 -0
- package/src/schemas/workspace-search.ts +141 -0
- package/src/schemas/workspace-setup.ts +43 -0
- package/src/schemas/workspace-tree.ts +234 -0
- package/src/title.test.ts +1 -1
- package/src/webext-links.ts +80 -0
- package/src/webext-protocol.ts +27 -0
- package/src/workflow-faults.test.ts +1 -1
- package/src/workflow-faults.ts +1 -1
- package/src/workspace-state.test.ts +3 -3
- package/src/workspace-state.ts +8 -13
- package/dist/schemas.d.ts +0 -9909
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js +0 -4288
- package/dist/schemas.js.map +0 -1
- package/src/schemas.ts +0 -10090
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
// settings: per-sandbox agent settings (.intentic/config/settings.json)
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { AdmissionPolicySchema, AdmissionRuleSchema, CommandClassSchema } from "./agent.js";
|
|
4
|
+
// Which prompt the agent is, before this turn composes anything on top. Two built-in bases and an escape
|
|
5
|
+
// hatch: Intentic's own (the default), Claude Code's preset, or the owner's text. Declared out here rather
|
|
6
|
+
// than inline in the settings object because both sides of the wire branch on it, the daemon to build the
|
|
7
|
+
// turn, the browser to decide which base it can show you.
|
|
8
|
+
export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
|
|
9
|
+
export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
|
|
10
|
+
// The two bases a user can READ and fork, "custom" is excluded because there is nothing to fetch: it is
|
|
11
|
+
// whatever they have already typed into the settings field.
|
|
12
|
+
export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
|
|
13
|
+
// How far the dependency-freshness check may go, see the settings field of the same name for why the middle
|
|
14
|
+
// state exists. Declared out here because the daemon branches on it in three places (whether to wire the hook,
|
|
15
|
+
// whether to consult the successor list, what the notice may say) and the browser branches on it to draw the
|
|
16
|
+
// row: a bare inline enum would have each of those spelling the literals for itself.
|
|
17
|
+
export const DependencyFreshnessSchema = z.enum(["off", "versions", "full"]);
|
|
18
|
+
export type DependencyFreshness = z.infer<typeof DependencyFreshnessSchema>;
|
|
19
|
+
/* ---- rules: "at this moment, if this is true, do this" ------------------------------------------------------
|
|
20
|
+
*
|
|
21
|
+
* The one table behind every standing instruction the owner gives the sandbox about its own work. It replaces
|
|
22
|
+
* three settings that were the same idea built three ways, ask for proof before a turn ends, run a command
|
|
23
|
+
* before a push, hold or release finished work, and the point of replacing them is that a FOURTH is now a row
|
|
24
|
+
* in this table rather than a release.
|
|
25
|
+
*
|
|
26
|
+
* The moments are named to sit in one family with WorkspaceEventKind (`turn.settled`, `agent.landed`), because
|
|
27
|
+
* chores already wake on those and folding them into this table later must not mean renaming what users wrote.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// WHERE a rule can stand. Three, and each is a place the daemon already stopped to make a decision, this
|
|
31
|
+
// names those decisions rather than inventing new ones.
|
|
32
|
+
export const RuleMomentSchema = z.enum([
|
|
33
|
+
// The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
|
|
34
|
+
"turn.ending",
|
|
35
|
+
// Code is about to leave the machine. A rule here gates the push on its own exit code.
|
|
36
|
+
"push.starting",
|
|
37
|
+
// An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
|
|
38
|
+
"agent.finished",
|
|
39
|
+
]);
|
|
40
|
+
export type RuleMoment = z.infer<typeof RuleMomentSchema>;
|
|
41
|
+
/* WHAT A RULE DOES. Four shapes, and the split is functional rather than tidy: the three settings this table
|
|
42
|
+
* replaces need three DIFFERENT ones, which is the evidence that a single "run this command" table would have
|
|
43
|
+
* mangled at least two of them.
|
|
44
|
+
*
|
|
45
|
+
* command, run a shell command; its exit code is the verdict. What the pre-push check always was.
|
|
46
|
+
* instruct, say something to the assistant, so it acts before it finishes.
|
|
47
|
+
* verdict, allow or hold the thing that is about to happen. The vocabulary the permission rules already
|
|
48
|
+
* speak, and the honest shape of "land finished work automatically": nothing extra RUNS at that
|
|
49
|
+
* moment, a pass that always runs is told which way to go.
|
|
50
|
+
* builtin, invoke a named daemon behaviour. The escape hatch that keeps this table from having to express
|
|
51
|
+
* machinery it has no business expressing: the proof ledger behind "verify before finishing"
|
|
52
|
+
* tracks what a turn edited against what it ran, which is not a shell command and never will be.
|
|
53
|
+
*/
|
|
54
|
+
/* The named behaviours a rule can invoke. Each reads a record only the daemon keeps, which is what makes them
|
|
55
|
+
* built-ins rather than commands: `verify-edits` weighs what a turn edited against what it ran,
|
|
56
|
+
* `verify-removals` weighs what a turn DELETED against what the repository's history says about those lines,
|
|
57
|
+
* which is a question `git log` answers and no shell one-liner an owner could type would, and
|
|
58
|
+
* `verify-ui-edits` weighs the rendered surfaces a turn changed against whether it ever looked at one, the one
|
|
59
|
+
* question a passing suite is structurally unable to answer. */
|
|
60
|
+
export const RuleBuiltinSchema = z.enum(["verify-edits", "verify-removals", "verify-ui-edits"]);
|
|
61
|
+
export type RuleBuiltin = z.infer<typeof RuleBuiltinSchema>;
|
|
62
|
+
export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
63
|
+
z.object({
|
|
64
|
+
kind: z.literal("command"),
|
|
65
|
+
command: z.string().max(500),
|
|
66
|
+
// Ceiling on one run, after which the child's whole process group is killed and the run is `failed`.
|
|
67
|
+
// Never a pass: a command that did not finish has said nothing, and a green light nobody earned is the
|
|
68
|
+
// one outcome a check exists to prevent.
|
|
69
|
+
timeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
|
|
70
|
+
}),
|
|
71
|
+
z.object({ kind: z.literal("instruct"), text: z.string().min(1).max(4000) }),
|
|
72
|
+
z.object({ kind: z.literal("verdict"), verdict: z.enum(["allow", "hold"]) }),
|
|
73
|
+
z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
|
|
74
|
+
]);
|
|
75
|
+
export type RuleAction = z.infer<typeof RuleActionSchema>;
|
|
76
|
+
// WHEN a rule narrows. Three keys, chosen because they cover the two things people reach for on day one,
|
|
77
|
+
// "only this repo" and "don't bother for a docs-only change", without opening a query language. Every key
|
|
78
|
+
// absent ⇒ the rule always matches at its moment, which is what the three replaced settings each did.
|
|
79
|
+
export const RuleConditionSchema = z.object({
|
|
80
|
+
// A workspace repo id, or "root". Absent ⇒ any.
|
|
81
|
+
repo: z.string().min(1).optional(),
|
|
82
|
+
// Globs the change has to touch for the rule to fire. Absent/empty ⇒ any.
|
|
83
|
+
paths: z.array(z.string().min(1)).max(20).optional(),
|
|
84
|
+
// How the turn ended. Absent/empty ⇒ any.
|
|
85
|
+
outcome: z.array(z.enum(["clean", "error", "conflict"])).optional(),
|
|
86
|
+
});
|
|
87
|
+
export type RuleCondition = z.infer<typeof RuleConditionSchema>;
|
|
88
|
+
/* ONE RULE. `id` is stable and owner-visible: it is what the activity feed names when the rule fires and what
|
|
89
|
+
* the last-fired store is keyed by, so it survives a relabel.
|
|
90
|
+
*
|
|
91
|
+
* WHICH ACTIONS FIT WHICH MOMENT is checked here rather than left to the consumer, because the alternative is
|
|
92
|
+
* a rule that saves cleanly and then quietly does nothing, the failure mode a settings screen can least
|
|
93
|
+
* afford. A verdict at `turn.ending` has nothing to decide; a command at `agent.finished` has no defined place
|
|
94
|
+
* in the landing pass and would be a promise this stage cannot keep. */
|
|
95
|
+
const MOMENT_ACTIONS: Record<RuleMoment, readonly RuleAction["kind"][]> = {
|
|
96
|
+
"turn.ending": ["builtin", "instruct", "command"],
|
|
97
|
+
"push.starting": ["command"],
|
|
98
|
+
"agent.finished": ["verdict"],
|
|
99
|
+
};
|
|
100
|
+
export const RuleSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
103
|
+
label: z.string().min(1).max(80),
|
|
104
|
+
moment: RuleMomentSchema,
|
|
105
|
+
when: RuleConditionSchema.optional(),
|
|
106
|
+
action: RuleActionSchema,
|
|
107
|
+
enabled: z.boolean().default(true),
|
|
108
|
+
})
|
|
109
|
+
.refine((rule) => MOMENT_ACTIONS[rule.moment].includes(rule.action.kind), {
|
|
110
|
+
message: "that action cannot stand at that moment",
|
|
111
|
+
path: ["action"],
|
|
112
|
+
});
|
|
113
|
+
export type Rule = z.infer<typeof RuleSchema>;
|
|
114
|
+
// When a rule last did something, keyed by rule id, read by the settings list so a rule nobody has seen fire
|
|
115
|
+
// in three weeks is visible as such. Kept out of the settings object on purpose: a firing is not an edit, and
|
|
116
|
+
// writing the owner's config on every push would make every run a settings save.
|
|
117
|
+
export const RuleFiringsSchema = z.record(z.string(), z.number());
|
|
118
|
+
export type RuleFirings = z.infer<typeof RuleFiringsSchema>;
|
|
119
|
+
/* WHERE A SKILL CAME FROM, the fact that decides everything else about its row.
|
|
120
|
+
*
|
|
121
|
+
* A skill is inert text the agent reads, and this sandbox grows them from seven directions at once: the daemon
|
|
122
|
+
* writes one per baked tool and one per core feature that has a cheatsheet, connecting a tool or a machine
|
|
123
|
+
* writes one for that connection, the owner writes their own, a persona carries its own in its kit, an
|
|
124
|
+
* installed extension ships some inside its checkout, and a plugin capability clones a repo full of them.
|
|
125
|
+
* Nothing used to LIST the result, which is the whole gap this vocabulary closes, "what does my agent know
|
|
126
|
+
* right now" had no answer on screen, and a skill spends the agent's attention whether or not anyone remembers
|
|
127
|
+
* adding it.
|
|
128
|
+
*
|
|
129
|
+
* builtin this image ships it, a baked tool's cheatsheet, or a core feature's
|
|
130
|
+
* own the owner wrote it (.intentic/config/skills/), and only these are editable here
|
|
131
|
+
* capability something connected brought it: a CLI tool, a machine, a browser account, a VPN
|
|
132
|
+
* extension an installed extension ships it inside its checkout
|
|
133
|
+
* plugin a plugin capability cloned a repo that holds it
|
|
134
|
+
* persona one card's own kit carries it, and only turns wearing that card ever see it
|
|
135
|
+
* dropped it is simply sitting in the loaded folder, put there by hand, or by the agent itself
|
|
136
|
+
*
|
|
137
|
+
* `persona` is the one origin that is not on for everybody, which is why it needs its own word rather than
|
|
138
|
+
* being filed under `own`: it says "the agent knows this when it is wearing that card", and a list that showed
|
|
139
|
+
* it as an ordinary skill of the owner's would be claiming it applies to every chat.
|
|
140
|
+
*
|
|
141
|
+
* `dropped` is the honest bottom of the list rather than a category anything creates on purpose: the promise
|
|
142
|
+
* this surface makes is that it shows EVERYTHING the agent knows, so a file nothing else claims has to list as
|
|
143
|
+
* the loose file it is instead of being quietly left out.
|
|
144
|
+
*
|
|
145
|
+
* Deliberately NOT a capability kind. A capability holds a credential, can be broken right now, and wants a
|
|
146
|
+
* status light; a skill either exists or it does not. See _sandbox/sandbox/src/settings/skill-inventory.ts. */
|
|
147
|
+
export const SkillOriginSchema = z.enum(["builtin", "own", "capability", "extension", "plugin", "persona", "dropped"]);
|
|
148
|
+
export type SkillOrigin = z.infer<typeof SkillOriginSchema>;
|
|
149
|
+
/* A skill's own name, the directory it lives in and the word the agent invokes it by. Same slug shape the SDK's
|
|
150
|
+
* loader accepts, checked here so a bad name is a refused save rather than a skill that silently never loads. */
|
|
151
|
+
export const SkillNameSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "a skill name is lowercase letters, digits and dashes");
|
|
152
|
+
export const SkillSummarySchema = z.object({
|
|
153
|
+
/* The handle the read/remove routes take. An `own` or `builtin` skill IS its name (they share one directory,
|
|
154
|
+
* so names there are already unique); one that belongs to something else is `<origin>:<owner>:<name>`,
|
|
155
|
+
* because two plugins may each ship a `review` and the list has to be able to tell them apart. */
|
|
156
|
+
id: z
|
|
157
|
+
.string()
|
|
158
|
+
.describe(
|
|
159
|
+
"Its handle, which reading and deleting take. A skill of your own is simply its name; one belonging to something else is qualified, because two packages may each ship a review.",
|
|
160
|
+
),
|
|
161
|
+
name: z.string().describe("Its name."),
|
|
162
|
+
// The frontmatter line the agent routes on, empty when a shipped skill declares none, which is worth
|
|
163
|
+
// showing as the blank it is rather than papering over: a skill with no description is rarely picked.
|
|
164
|
+
description: z
|
|
165
|
+
.string()
|
|
166
|
+
.describe(
|
|
167
|
+
"What it is for, which is the line the agent reads to decide whether to reach for it. Empty when the skill declares none, which is worth showing as the blank it is: a skill with no description is rarely picked.",
|
|
168
|
+
),
|
|
169
|
+
origin: SkillOriginSchema.describe("Where it came from."),
|
|
170
|
+
// Who ships it, as the row names it, an extension's title, a plugin capability's id, a setting's name.
|
|
171
|
+
owner: z.string().optional().describe("Who ships it, as the row would name them."),
|
|
172
|
+
enabled: z.boolean().describe("Whether the agent can reach it."),
|
|
173
|
+
/* Whether THIS surface can switch it. True only for the skills the settings `skills` list governs (baked
|
|
174
|
+
* tools and the owner's own): everything else is on because its extension, its plugin or another setting is,
|
|
175
|
+
* and a switch here that silently did nothing would be worse than no switch at all, the row names its
|
|
176
|
+
* owner instead. */
|
|
177
|
+
switchable: z
|
|
178
|
+
.boolean()
|
|
179
|
+
.describe(
|
|
180
|
+
"Whether this surface can switch it. Everything else is on because its extension or its plugin is, and a switch here that silently did nothing would be worse than none, so the row names its owner instead.",
|
|
181
|
+
),
|
|
182
|
+
// Whether the owner may rewrite the text here. Their own skills only, a shipped one is its author's, and
|
|
183
|
+
// editing it in place would be undone the next time the thing that ships it reconciles.
|
|
184
|
+
editable: z
|
|
185
|
+
.boolean()
|
|
186
|
+
.describe(
|
|
187
|
+
"Whether it can be rewritten here. Your own only: editing somebody else's in place would be undone the next time the thing that ships it catches up.",
|
|
188
|
+
),
|
|
189
|
+
/* Whether it can be deleted from this surface. Wider than `editable` by exactly one case: a skill someone
|
|
190
|
+
* dropped into the loaded folder is not the owner's to edit (its home is that folder, not their store) but is
|
|
191
|
+
* absolutely theirs to clear out, and with no switch and no owning extension there would otherwise be no way
|
|
192
|
+
* to get rid of it short of the file tree. */
|
|
193
|
+
removable: z.boolean(),
|
|
194
|
+
});
|
|
195
|
+
export type SkillSummary = z.infer<typeof SkillSummarySchema>;
|
|
196
|
+
export const SkillsListSchema = z.array(SkillSummarySchema);
|
|
197
|
+
// One skill's full text, for reading it on screen. Its own route rather than a field on the summary: bodies run
|
|
198
|
+
// to thousands of words and a list of twenty would cost a hundred kilobytes to draw a group of one-line rows.
|
|
199
|
+
export const SkillBodySchema = z.object({
|
|
200
|
+
id: z.string().describe("The skill's id, which can carry the owner it came from."),
|
|
201
|
+
name: z.string().describe("Its name."),
|
|
202
|
+
// Everything after the frontmatter, the instructions themselves, as written.
|
|
203
|
+
body: z.string().describe("The instructions themselves, as written."),
|
|
204
|
+
});
|
|
205
|
+
export type SkillBody = z.infer<typeof SkillBodySchema>;
|
|
206
|
+
export const SkillIdSchema = z.object({
|
|
207
|
+
id: z
|
|
208
|
+
.string()
|
|
209
|
+
.min(1)
|
|
210
|
+
.describe(
|
|
211
|
+
"Which skill. It travels in the query rather than the address, because an id can name the owner it came from and that will not fit in a path.",
|
|
212
|
+
),
|
|
213
|
+
});
|
|
214
|
+
/* A skill the owner writes. Three fields because a skill IS three things, what it is called, when to reach for
|
|
215
|
+
* it, and what to do, and the daemon assembles the frontmatter from the first two so a saved skill can never
|
|
216
|
+
* be one the loader skips over. `description` is required for the reason above: it is the only part the model
|
|
217
|
+
* reads before deciding whether to open the rest. */
|
|
218
|
+
export const SkillDraftSchema = z.object({
|
|
219
|
+
name: SkillNameSchema.describe("What to call it. Saving over an existing name rewrites it, which is also how one is renamed."),
|
|
220
|
+
description: z.string().min(1).max(1024).describe("What it is for, which is what the agent reads to decide whether to reach for it."),
|
|
221
|
+
body: z.string().min(1).describe("The skill itself."),
|
|
222
|
+
});
|
|
223
|
+
export type SkillDraft = z.infer<typeof SkillDraftSchema>;
|
|
224
|
+
export const SkillRemoveSchema = z.object({
|
|
225
|
+
name: SkillNameSchema.describe("Which skill to delete. The text and the enabled list are both updated, so nothing is left half done."),
|
|
226
|
+
});
|
|
227
|
+
// Small user-owned config the /settings routes edit and streamAgent reads, all opt-in booleans the owner
|
|
228
|
+
// toggles in the UI (so each can be A/B benchmarked):
|
|
229
|
+
// stableSystemPrompt, keeps the system prompt byte-stable across turns (the delegation note rides the user
|
|
230
|
+
// message instead of the preset `append`) so the provider prompt cache survives.
|
|
231
|
+
// skills , names of baked-tool skills to load into .agents/skills so the agent reaches for them
|
|
232
|
+
// (e.g. "lsp". TS rename + diagnostics over the language service); a name absent ⇒ its
|
|
233
|
+
// skill file isn't written, so the agent doesn't reach for it. Data-driven: a new baked
|
|
234
|
+
// tool is one daemon-side registry entry, not a new settings field.
|
|
235
|
+
// hashlineEdits , swaps the native Read/Edit/Write for hash-anchored edits on the Claude path (stale-file
|
|
236
|
+
// guard + fewer output tokens); off ⇒ the native file tools.
|
|
237
|
+
// terseOutput , appends a concise-response steer to the end of the system prompt (a stable suffix, so it
|
|
238
|
+
// composes with stableSystemPrompt) to cut the model's OWN output tokens.
|
|
239
|
+
// systemPromptMode , which base the agent's prompt is: "intentic" (default), "claude", or "custom".
|
|
240
|
+
// systemPrompt , the owner's own prompt text, used only by "custom" mode, where it is the ENTIRE system
|
|
241
|
+
// prompt and nothing the daemon would otherwise append rides with it, see its own note.
|
|
242
|
+
// iqSearch , loads the image-baked iq Claude Code plugin (skill + SessionStart nudge) so the agent
|
|
243
|
+
// prefers the iq CLI over grep/find/Glob; off ⇒ plugin not loaded, native search tools
|
|
244
|
+
// only. Opt-in (default off); the browser Search box uses iq regardless.
|
|
245
|
+
// iqSearchHoldout , conversation-level measurement control for iqSearch (UsageTurn.iqSearchArm). The arm
|
|
246
|
+
// stays fixed because teaching already loaded into a session cannot be removed next turn.
|
|
247
|
+
// workspaceMap , computes an AREA index of the project a run starts in and prepends it to the
|
|
248
|
+
// conversation's opening message, so the turn does not have to buy its own orientation
|
|
249
|
+
// with a directory listing. Generated from the filesystem every time, never stored.
|
|
250
|
+
// sidecars , the background pass converging a markdown shadow of every binary workspace file
|
|
251
|
+
// (docx/pdf/images/audio → .intentic/local/cache/derived/) the moment it lands, via
|
|
252
|
+
// the baked fileq CLI, so reasoning-time reads are pre-derived. The CLI itself is
|
|
253
|
+
// always available; this gates only the eager watcher-driven derivation.
|
|
254
|
+
// dependencyFreshness, whether a version the agent is about to pin is checked against the package's own
|
|
255
|
+
// registry before it lands, and what the check is allowed to say: "off" (no hook is
|
|
256
|
+
// wired at all), "versions" (registry-derivable facts only), "full" (also names a
|
|
257
|
+
// maintained successor, where one is known and the registry corroborates it).
|
|
258
|
+
// outputCleaners , the Bash output-cleaner spec (agent-output-filter): "off" = filter disabled,
|
|
259
|
+
// "" = all cleaners on (default), else an iq-style allow-list / default-minus
|
|
260
|
+
// spec ("git,pnpm" = only those; "-cap" = all except). Threaded to the filter via env.
|
|
261
|
+
// outputHoldout , measurement control: a fraction [0,1] of Bash commands whose output bypasses cleaning
|
|
262
|
+
// (recorded raw as `heldOut`), so the savings report compares a real cleaned-vs-raw
|
|
263
|
+
// population instead of an estimate. 0 = no holdout (default).
|
|
264
|
+
// rules , the standing "at this moment, if this is true, do this" table (RuleSchema): what proves
|
|
265
|
+
// a turn's work, what runs before a push, whether finished work lands by itself. Empty
|
|
266
|
+
// (the default) means none of those happen, which is the shape a fresh sandbox has.
|
|
267
|
+
// automationFailureLimit, consecutive `error` runs after which an automation is disabled rather than left
|
|
268
|
+
// firing forever; 0 (default) ⇒ never.
|
|
269
|
+
// subagentsAtOnce / subagentsPerTurn / subagentDepth, the harness's own ceilings on delegation, raised or
|
|
270
|
+
// lowered from one place; each defaults to what the CLI enforces on its own.
|
|
271
|
+
// The booleans default off, outputCleaners defaults "" (cleaning on) and outputHoldout 0; iqSearch stays off
|
|
272
|
+
// until the owner enables it. `skills` is the exception and defaults to the
|
|
273
|
+
// baked tools worth having on: a skill file is the ONLY thing that tells the agent a baked binary exists, and
|
|
274
|
+
// with the list empty `lsp` went used once in 866 sessions, not declined, never learned about.
|
|
275
|
+
//
|
|
276
|
+
// Every field carries that default IN THE SCHEMA, so a settings object written before a field existed still
|
|
277
|
+
// parses, the absent key reads as its default. That is not a compatibility layer, it is the seam this shape
|
|
278
|
+
// spans: the browser ships with the platform while the daemon ships inside the user's sandbox image, so a web
|
|
279
|
+
// build is routinely NEWER than the daemon answering it. Requiring the key instead makes the whole settings
|
|
280
|
+
// surface fail to parse the moment a toggle is added, which reaches the user as a page of switches that are
|
|
281
|
+
// silently dead, not as an error. It also means an older on-disk manifest keeps the owner's other picks rather
|
|
282
|
+
// than being discarded whole.
|
|
283
|
+
|
|
284
|
+
export const SandboxSettingsSchema = z.object({
|
|
285
|
+
stableSystemPrompt: z
|
|
286
|
+
.boolean()
|
|
287
|
+
.default(false)
|
|
288
|
+
.describe(
|
|
289
|
+
"Keep the instructions identical between turns so the provider can cache them, moving anything that varies into the message instead. Cheaper, at the cost of some flexibility.",
|
|
290
|
+
),
|
|
291
|
+
skills: z.array(z.string()).default(["lsp", "fileq"]).describe("Which skills are switched on."),
|
|
292
|
+
hashlineEdits: z
|
|
293
|
+
.boolean()
|
|
294
|
+
.default(false)
|
|
295
|
+
.describe(
|
|
296
|
+
"Have the agent edit files by line number rather than by quoting the text it wants replaced. Cheaper on large files, and less forgiving of a stale read.",
|
|
297
|
+
),
|
|
298
|
+
terseOutput: z.boolean().default(false).describe("Ask the agent to say less. It changes how much it narrates, not how much it does."),
|
|
299
|
+
/* Measurement control for the terse steer, at TURN level, the same trick `outputHoldout` plays over
|
|
300
|
+
* commands, one layer up. A fraction [0,1] of otherwise-eligible turns run WITHOUT the steer and record
|
|
301
|
+
* which arm they ran on (UsageTurn.terse), so the savings report can compare two real populations.
|
|
302
|
+
*
|
|
303
|
+
* It has to be an experiment: unlike a cleaned command, which yields its own raw baseline in the same
|
|
304
|
+
* event, a turn cannot be re-run to see what it would have said unsteered. 0 ⇒ no measurement (every
|
|
305
|
+
* eligible turn is steered), which is the default because the control costs the very tokens it measures. */
|
|
306
|
+
terseHoldout: z
|
|
307
|
+
.number()
|
|
308
|
+
.min(0)
|
|
309
|
+
.max(1)
|
|
310
|
+
.default(0)
|
|
311
|
+
.describe(
|
|
312
|
+
"What share of turns to run without that instruction, so the two can be compared honestly. It has to be measured this way, because a turn cannot be re-run to see what it would have said. Zero means no measurement, which is the default, since the comparison costs the very tokens it is measuring.",
|
|
313
|
+
),
|
|
314
|
+
/* WHICH SYSTEM PROMPT THE AGENT RUNS ON, the base, before anything this turn composes.
|
|
315
|
+
*
|
|
316
|
+
* intentic. Intentic's own prompt, tuned for this harness (intentic-prompt.ts). The default.
|
|
317
|
+
* claude . Claude Code's preset, as shipped in the CLI this sandbox runs. Not a copy stored here, so
|
|
318
|
+
* picking it tracks whatever the installed CLI's prompt is rather than freezing at a snapshot.
|
|
319
|
+
* custom , `systemPrompt` below, and nothing else at all.
|
|
320
|
+
*
|
|
321
|
+
* The first two are peers: both get the harness's own guidance appended (the AskUserQuestion/plan blocks
|
|
322
|
+
* the chat's cards need, the checklist guidance behind the todo panel, the browser-tool guidance), plus the
|
|
323
|
+
* delegation note and the terse steer. `custom` is the one that does not, by the owner's explicit choice,
|
|
324
|
+
* see the field below. */
|
|
325
|
+
systemPromptMode: SystemPromptModeSchema.default("intentic").describe(
|
|
326
|
+
"Which instructions the agent starts from: intentic's own, the ones the installed Claude Code carries, or your own. The first two both get this product's own guidance added on top; your own gets nothing added, which is the point of it.",
|
|
327
|
+
),
|
|
328
|
+
/* The owner's own prompt, used only when `systemPromptMode` is "custom". Then it is the ENTIRE system
|
|
329
|
+
* prompt: both built-in bases are gone and so is everything the daemon would otherwise append, the widget
|
|
330
|
+
* guidance the chat's cards are driven by, and the terse-output steer (whose toggle goes inert). That is
|
|
331
|
+
* the price of total control, and the UI states it at the moment of the edit rather than letting the
|
|
332
|
+
* widgets go quietly dark. Only the cross-provider delegation note survives, because it has a home outside
|
|
333
|
+
* the system prompt already (the user-message preamble stableSystemPrompt puts it in).
|
|
334
|
+
*
|
|
335
|
+
* Cap is roomy, the bases it stands in for are ~6.8k characters, but finite, because every turn pays it. */
|
|
336
|
+
systemPrompt: z
|
|
337
|
+
.string()
|
|
338
|
+
.max(20000)
|
|
339
|
+
.default("")
|
|
340
|
+
.describe(
|
|
341
|
+
"Your own instructions, used only when the mode above says custom. Then it is the whole of them: both built-in bases go, and so does everything this product would otherwise add, including the guidance the chat's own cards are driven by. That is the price of total control.",
|
|
342
|
+
),
|
|
343
|
+
iqSearch: z
|
|
344
|
+
.boolean()
|
|
345
|
+
.default(false)
|
|
346
|
+
.describe("Teach the agent how to use this workspace's own search tool, rather than leaving it to grep around."),
|
|
347
|
+
/* Measurement control for the iq search teaching, at CONVERSATION level. A fraction [0,1] of conversations
|
|
348
|
+
* run without the plugin/instruction and stamp that stable arm on every turn. Per-turn randomization is not
|
|
349
|
+
* a valid control here: once the teaching enters a provider session, withholding it from the next request
|
|
350
|
+
* does not make the model forget it. 0 ⇒ no measurement and every conversation receives the teaching. */
|
|
351
|
+
iqSearchHoldout: z
|
|
352
|
+
.number()
|
|
353
|
+
.min(0)
|
|
354
|
+
.max(1)
|
|
355
|
+
.default(0)
|
|
356
|
+
.describe(
|
|
357
|
+
"What share of conversations to run without that teaching, so the two can be compared. Whole conversations rather than individual turns, because once the teaching is in a session, withholding it from the next request does not make the model forget it.",
|
|
358
|
+
),
|
|
359
|
+
/* THE MAP THE TURN OPENS WITH, which areas the project a run starts in has, one derived line on what each
|
|
360
|
+
* is for, and where the run is standing among them (agent/workspace-map.ts).
|
|
361
|
+
*
|
|
362
|
+
* It answers the question every first turn has whatever it was asked, "what is this and where am I in
|
|
363
|
+
* it", which across a hundred sessions of this workspace was being bought with a directory listing in two
|
|
364
|
+
* turns out of five, and with ~5.3k tokens of tool results before the job was touched.
|
|
365
|
+
*
|
|
366
|
+
* ROOTED AT THE RUN'S STARTING FOLDER rather than at the workspace: a persona's start folder, an isolated
|
|
367
|
+
* conversation's worktree, or wherever the turn's cwd is. It maps the project containing that folder and
|
|
368
|
+
* names the rest of the workspace on one line, because a run three levels inside one project is not asking
|
|
369
|
+
* about the others.
|
|
370
|
+
*
|
|
371
|
+
* REGENERATED, NEVER STORED, which is the whole reason it is a mechanism rather than a paragraph in the
|
|
372
|
+
* system prompt or a hand-written CLAUDE.md: in the ten days that motivated it this repo's two busiest
|
|
373
|
+
* top-level directories stopped existing, and every written-down copy of the layout was wrong by the end of
|
|
374
|
+
* the window. Off by default, it spends its tokens on the opening message of every conversation. */
|
|
375
|
+
workspaceMap: z
|
|
376
|
+
.boolean()
|
|
377
|
+
.default(false)
|
|
378
|
+
.describe(
|
|
379
|
+
"Open every conversation with a map of the project it starts in: what is in it, what each part is for, and where the agent is standing. Worked out fresh each time rather than written down anywhere, because a written layout is wrong within a fortnight. Off by default, since it spends tokens on the first message of every conversation.",
|
|
380
|
+
),
|
|
381
|
+
/* THE MARKDOWN SHADOWS OF BINARY FILES, the eager half of fileq (_sandbox/fileq). The lazy half — the
|
|
382
|
+
* `fileq` CLI an agent runs mid-task — is always on PATH and gated only by its skill; this switch is
|
|
383
|
+
* about the BACKGROUND pass: the daemon watching /work and converging a sidecar under
|
|
384
|
+
* .intentic/local/cache/derived/ for every docx/xlsx/pptx/pdf/image/audio file the moment it lands or
|
|
385
|
+
* changes, so reasoning-time reads hit a shadow that already exists. Off by default like every boolean
|
|
386
|
+
* here: it spends CPU unasked, on every file that lands, which is the owner's call to make. */
|
|
387
|
+
sidecars: z
|
|
388
|
+
.boolean()
|
|
389
|
+
.default(false)
|
|
390
|
+
.describe(
|
|
391
|
+
"Keep an up-to-date markdown rendering of every document, image and audio file in the workspace, made in the background as files land, so the agent reads a pre-derived text instead of paying to parse the file mid-task. Costs background CPU on a document-heavy workspace, so it is a switch rather than a default.",
|
|
392
|
+
),
|
|
393
|
+
/* IS THE VERSION ABOUT TO BE PINNED THE ONE THE REGISTRY ACTUALLY HAS?
|
|
394
|
+
*
|
|
395
|
+
* A model writes a version from memory, and memory has a publication date. Across this workspace's own
|
|
396
|
+
* session history the failure is small, steady, and never self-correcting: of the version literals an
|
|
397
|
+
* agent typed into a manifest or an install command, more than half were already behind the newest
|
|
398
|
+
* release at the moment they were typed, and a third were written without any registry ever being asked.
|
|
399
|
+
* Nothing downstream catches it either — a stale pin installs cleanly, type-checks, and passes the suite,
|
|
400
|
+
* so every gate this sandbox already has says yes to it.
|
|
401
|
+
*
|
|
402
|
+
* THREE STATES, and the middle one is the whole feature. The two halves of "this dependency choice is
|
|
403
|
+
* behind" are different KINDS of claim:
|
|
404
|
+
* versions, a measurement. The registry publishes what the newest release is and when; comparing is
|
|
405
|
+
* arithmetic, it works for any package, and there is nothing to keep current.
|
|
406
|
+
* full , a measurement plus an opinion. Naming `date-fns` as what to reach for instead of `moment`
|
|
407
|
+
* is a judgement somebody made, and judgements rot. An owner is entitled to take the facts
|
|
408
|
+
* and decline the opinions, which is exactly what the two settings are.
|
|
409
|
+
* The successor list never asserts staleness on its own: an entry surfaces only where the registry
|
|
410
|
+
* corroborates it (deprecated, or nothing published in eighteen months), so the curation supplies the
|
|
411
|
+
* NAME of the replacement and the measurement supplies the reason. That split is what stops it decaying
|
|
412
|
+
* into a list of last year's preferences.
|
|
413
|
+
*
|
|
414
|
+
* IT INFORMS, IT NEVER BLOCKS, and that is not timidity. Matching a version the workspace already pins is
|
|
415
|
+
* the common case and it is CORRECT: a new package inside a monorepo should take the catalog's
|
|
416
|
+
* `typescript`, not the newest one on the registry. A gate that refused would fight legitimate work
|
|
417
|
+
* several times for every mistake it caught, so the fact rides back as context and the model decides.
|
|
418
|
+
*
|
|
419
|
+
* Off by default like every other flag here, and off means genuinely nothing: no hook is wired, and the
|
|
420
|
+
* sandbox makes no network call it would not otherwise have made. */
|
|
421
|
+
dependencyFreshness: DependencyFreshnessSchema.default("off")
|
|
422
|
+
.describe(
|
|
423
|
+
"Whether a version the agent is about to pin is checked against the package's own registry first. Facts only, or facts plus the name of a maintained replacement where the registry agrees the current choice has been abandoned. It tells the agent and lets it decide rather than refusing, because matching a version your project already uses is usually the right answer and a gate would fight it.",
|
|
424
|
+
),
|
|
425
|
+
outputCleaners: z
|
|
426
|
+
.string()
|
|
427
|
+
.default("")
|
|
428
|
+
.describe("Which command outputs to trim before the agent reads them, cutting the noise a build tool prints without cutting what it said."),
|
|
429
|
+
outputHoldout: z
|
|
430
|
+
.number()
|
|
431
|
+
.min(0)
|
|
432
|
+
.max(1)
|
|
433
|
+
.default(0)
|
|
434
|
+
.describe("What share of commands to leave untrimmed, so the saving can be measured against a real comparison rather than estimated."),
|
|
435
|
+
/* The models behind the small automatic jobs that are not a conversation, today the commit message
|
|
436
|
+
* written when an agent's work lands. An ORDERED list of `${provider}:${modelId}`, tried top to bottom, or
|
|
437
|
+
* EMPTY for Auto.
|
|
438
|
+
*
|
|
439
|
+
* A LIST rather than a pick, because the single interesting failure of this feature is a model that is
|
|
440
|
+
* connected and simply will not answer today: the account's allowance went on the chat, and one spent
|
|
441
|
+
* provider then takes the job down for hours while the others sit idle. Written in order, the daemon steps
|
|
442
|
+
* over the spent one and the message still gets written (agent/quick-model.ts walks it).
|
|
443
|
+
*
|
|
444
|
+
* Empty is the default and still the interesting case: Auto is resolved from whatever accounts are
|
|
445
|
+
* connected at the moment it is read (resolveQuickModels), so it can never name a provider this sandbox has
|
|
446
|
+
* no credential for, it improves by itself when one is added, and it is a ladder too, the cheapest rung of
|
|
447
|
+
* every connected provider, best first. Storing resolved ids here instead would go stale exactly like a
|
|
448
|
+
* pinned model does. */
|
|
449
|
+
quickModel: z
|
|
450
|
+
.array(z.string())
|
|
451
|
+
.max(10)
|
|
452
|
+
.default([])
|
|
453
|
+
.describe(
|
|
454
|
+
"Which models do the small automatic jobs that are not a conversation, such as writing a commit message. A list rather than one pick, tried in order, because the interesting failure is a model that is connected and simply will not answer today. Empty means work it out from whatever is connected, which improves by itself as accounts are added.",
|
|
455
|
+
),
|
|
456
|
+
/* WHICH REPOS KEEP A CHANGELOG, the repos whose commits carry a `Release-Note:` trailer, written by the
|
|
457
|
+
* same quick model that drafts the subject (git/commit-message.ts) and harvested at release time.
|
|
458
|
+
*
|
|
459
|
+
* A LIST OF REPOS RATHER THAN A FLAG, and EMPTY BY DEFAULT, because this daemon runs on the user's repos
|
|
460
|
+
* rather than on ours. The commit drafter's one standing rule is that house style is INFERRED, never
|
|
461
|
+
* prescribed, it reads the last handful of subjects and matches them, so a repo that spells its commits
|
|
462
|
+
* some other way is never argued with. A note trailer is the one thing that cannot be inferred that way: a
|
|
463
|
+
* repo which has never written one gives the model nothing to copy, so asking for it has to be somebody's
|
|
464
|
+
* explicit decision. Empty means every repo behaves exactly as it did before this existed.
|
|
465
|
+
*
|
|
466
|
+
* Named by repo id ("root", or the root-relative dir discoverRepos reports), because a workspace holds
|
|
467
|
+
* several repos and a commit can span them: the trailer is written when the commit touches a repo that
|
|
468
|
+
* asked for one, and a repo that did not ask never gets a line it has to explain to its reviewers. */
|
|
469
|
+
changelogRepos: z
|
|
470
|
+
.array(z.string())
|
|
471
|
+
.max(50)
|
|
472
|
+
.default([])
|
|
473
|
+
.describe(
|
|
474
|
+
"Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.",
|
|
475
|
+
),
|
|
476
|
+
/* WHAT AN AGENT RUN OPENS ON, the tier above quickModel, and the answer for every turn a SURFACE starts
|
|
477
|
+
* rather than a person at a composer: Fix with agent on a pipeline or a deployment, a Maintenance chore, a
|
|
478
|
+
* Documentation or Acceptance run, the fix a failed pre-push check proposes. An ORDERED list of
|
|
479
|
+
* `${provider}:${model}` (quickModelKey) plus the reasoning effort beside it; EMPTY ⇒ whatever the chat
|
|
480
|
+
* composer would have started with, which is the honest floor because it is the model the user already
|
|
481
|
+
* chose to work with.
|
|
482
|
+
*
|
|
483
|
+
* A LIST, for the reason quickModel is one: the account at the head runs out, and every surface-started run
|
|
484
|
+
* in the sandbox then fails on a credential the user cannot see from the row they pressed. Written in order,
|
|
485
|
+
* the next one down catches it (turn-resume.ts walks it).
|
|
486
|
+
*
|
|
487
|
+
* PINNED, NOT DERIVED, the deliberate difference from quickModel one line above, and the reason these are
|
|
488
|
+
* two settings rather than one. A quick helper exists to stay OFF the frontier tier, so cheapest-connected
|
|
489
|
+
* is the right automatic answer and an empty list resolves to Auto. An agent run has to read a failing
|
|
490
|
+
* suite, or a container log, or a story, and repair the thing: the tier is a judgement about how much the
|
|
491
|
+
* job is worth, nothing here can make it, and a wrong guess is billed in whole sessions rather than in
|
|
492
|
+
* tokens. So an empty list here resolves to NOTHING and the composer's own pick answers instead.
|
|
493
|
+
*
|
|
494
|
+
* The daemon applies this to any turn flagged `unattended` that names no model of its own, one rule, so a
|
|
495
|
+
* surface added tomorrow inherits it by saying what it is instead of re-deriving where models come from. A
|
|
496
|
+
* surface MAY still name one (the shared run button's caret, Acceptance's per-run pick), and that wins. */
|
|
497
|
+
agentRunModels: z
|
|
498
|
+
.array(z.string())
|
|
499
|
+
.max(10)
|
|
500
|
+
.default([])
|
|
501
|
+
.describe(
|
|
502
|
+
"Which models run the work a screen starts rather than a person: fixing a red pipeline, a maintenance chore, an acceptance run. Tried in order, so one spent account does not take every such run down. Empty falls back to whatever the chat would have used, which is the honest floor because it is the model you already chose to work with.",
|
|
503
|
+
),
|
|
504
|
+
agentRunEffort: z.string().default("").describe("How hard those runs should think."),
|
|
505
|
+
/* AUTOMATIC TIER SELECTION: may the daemon run an easy-looking turn on a cheaper rung of the provider the
|
|
506
|
+
* user is already on, instead of on the model they picked?
|
|
507
|
+
*
|
|
508
|
+
* THREE STATES RATHER THAN A TOGGLE, because the middle one is the only honest way to reach the third.
|
|
509
|
+
* Nobody, this repo included, can name a sensible cutoff for "easy enough" without traffic to fit it
|
|
510
|
+
* against, and a routing threshold guessed in advance is how a cost feature quietly becomes a quality
|
|
511
|
+
* regression. So:
|
|
512
|
+
* off — the judge never runs. Nothing is scored, nothing is recorded, turns run on the user's pick.
|
|
513
|
+
* shadow — the judge runs and its verdict is written to the spend ledger beside what the turn actually
|
|
514
|
+
* cost, and NOTHING IS ROUTED. This is the default: it spends no tokens, changes no behaviour,
|
|
515
|
+
* and is the only thing that can turn the weights in prompt-complexity.ts from a hypothesis
|
|
516
|
+
* into a measurement.
|
|
517
|
+
* on — a turn judged fast runs on the cheap rung (fast-tier.ts), when the provider publishes one.
|
|
518
|
+
*
|
|
519
|
+
* IT CAN ONLY EVER ROUTE DOWN. There is no "which model is the standard tier" setting because the standard
|
|
520
|
+
* tier is the model the user already chose, so the worst case of a wrong verdict is one turn's quality on a
|
|
521
|
+
* model they can see on the card and correct, never a bill they did not ask for. That asymmetry is why this
|
|
522
|
+
* can default to shadow rather than to off: shadow costs nothing and `on` cannot overspend. */
|
|
523
|
+
autoTier: z
|
|
524
|
+
.enum(["off", "shadow", "on"])
|
|
525
|
+
.default("shadow")
|
|
526
|
+
.describe(
|
|
527
|
+
"Whether an easy-looking turn may run on a cheaper model from the same provider. Three states rather than a switch, because the middle one is the only honest road to the third: it scores every turn and routes nothing, so the guess can become a measurement before it changes anything. It can only ever route down, so the worst case is one turn's quality rather than a bill nobody asked for.",
|
|
528
|
+
),
|
|
529
|
+
/* HOW EAGER THE JUDGE IS, the one dial this feature exposes and the answer to what the Measure mode is for:
|
|
530
|
+
* the numbers say how many turns were called simple, and this is the control that acts on them.
|
|
531
|
+
*
|
|
532
|
+
* Three named stops rather than a number, because the number means nothing to anyone who has not read the
|
|
533
|
+
* weights, while "only the unmistakable" / "the default" / "an easy question about real code too" are three
|
|
534
|
+
* sentences an owner can actually hold an opinion about (FAST_CEILINGS spells out each). It moves the
|
|
535
|
+
* cutoff and nothing else: the rule that a downgrade needs something POSITIVE to have been said holds at
|
|
536
|
+
* every stop, so no setting of this can start downgrading short vague requests.
|
|
537
|
+
*
|
|
538
|
+
* `balanced` is the default and is what every verdict recorded before this existed was judged against, so
|
|
539
|
+
* the shadow history stays comparable across the change rather than silently becoming two populations. */
|
|
540
|
+
autoTierEagerness: z
|
|
541
|
+
.enum(["cautious", "balanced", "eager"])
|
|
542
|
+
.default("balanced")
|
|
543
|
+
.describe(
|
|
544
|
+
"How readily a turn counts as simple enough for the cheaper model. It moves only the cutoff: at every setting a turn still has to say something positively easy, so nothing here can downgrade a short vague request.",
|
|
545
|
+
),
|
|
546
|
+
/* WHICH CHEAP MODEL A DOWNGRADED TURN LANDS ON, an ordered list of `${provider}:${model}` keys
|
|
547
|
+
* (quickModelKey), or EMPTY for Auto.
|
|
548
|
+
*
|
|
549
|
+
* Empty is the default and the interesting case, exactly as quickModel's is: Auto is the cheapest row the
|
|
550
|
+
* turn's own provider publishes, read through the same cheap-end order (compareCheapestFirst), so the two
|
|
551
|
+
* features can never disagree about which rung is the cheap one, and connecting an account tomorrow
|
|
552
|
+
* improves the answer by itself.
|
|
553
|
+
*
|
|
554
|
+
* A LIST, so a sandbox working across several providers can name the rung it wants on each. But unlike the
|
|
555
|
+
* two lists above this one is NOT a failure ladder: entries naming a provider other than the turn's own are
|
|
556
|
+
* dropped rather than tried, because switching provider retires the conversation's session (turnRequest.ts
|
|
557
|
+
* `resumes`), and starting the conversation over to save a fraction of a cent is not a saving. The first
|
|
558
|
+
* entry that names this provider AND is genuinely cheaper than the pick wins; if none does, Auto answers. */
|
|
559
|
+
autoFastModels: z
|
|
560
|
+
.array(z.string())
|
|
561
|
+
.max(10)
|
|
562
|
+
.default([])
|
|
563
|
+
.describe(
|
|
564
|
+
"Which cheaper model a downgraded turn lands on. A list so a sandbox spanning providers can name a rung on each, but not a fallback ladder: an entry naming a different provider than the turn is on is skipped rather than tried, because switching provider retires the conversation and starting over to save a fraction of a penny is not a saving. Empty picks the cheapest the turn's own provider publishes.",
|
|
565
|
+
),
|
|
566
|
+
// How long a finished agent stays on the board before it is archived automatically (days; 0 ⇒ never).
|
|
567
|
+
// Unlike every other flag here this one defaults ON, because the lane it governs is the board's only
|
|
568
|
+
// terminal state: without a sweep the Finished lane grows for the life of the sandbox, and each card it
|
|
569
|
+
// holds is a live worktree checkout, not just a row.
|
|
570
|
+
agentRetentionDays: z
|
|
571
|
+
.number()
|
|
572
|
+
.min(0)
|
|
573
|
+
.max(365)
|
|
574
|
+
.default(3)
|
|
575
|
+
.describe(
|
|
576
|
+
"How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.",
|
|
577
|
+
),
|
|
578
|
+
/* THE SANDBOX-WIDE DEFAULT for "when a turn dies because the MODEL PROVIDER was failing (500/502/503, a
|
|
579
|
+
* 529 at capacity, a dropped socket), re-run it on an escalating backoff until it goes through or the
|
|
580
|
+
* attempts are spent".
|
|
581
|
+
*
|
|
582
|
+
* A DEFAULT, not the whole answer: any one conversation may override it (AgentSummarySchema
|
|
583
|
+
* .resumeAfterOutage), and the chat's own offer at the moment of failure writes THAT rather than this.
|
|
584
|
+
* This toggle is the standing policy for every agent that has not said otherwise, which is why it lives in
|
|
585
|
+
* settings and is not reachable by a single press from inside one chat, flipping how the whole board
|
|
586
|
+
* behaves should be a thing somebody went to do.
|
|
587
|
+
*
|
|
588
|
+
* OFF by default, on the same reasoning that keeps a spent usage limit out of this pair entirely: a resume
|
|
589
|
+
* re-runs a turn the user sent once, on their own allowance, and only they can say whether the turn was
|
|
590
|
+
* worth paying for twice. Starting off costs nothing, because the failed turn is remembered whatever the
|
|
591
|
+
* toggle says (recordOutageFailure), the failure frame reports an "available" resume and the chat's offer
|
|
592
|
+
* arms that very turn the moment it is armed for that conversation. Worth turning ON for a sandbox whose
|
|
593
|
+
* turns mostly have nobody in the room (automation wakes, Discord, webhooks), which is the case no browser
|
|
594
|
+
* could rescue and the case a per-conversation press cannot reach. */
|
|
595
|
+
resumeAfterOutage: z
|
|
596
|
+
.boolean()
|
|
597
|
+
.default(false)
|
|
598
|
+
.describe(
|
|
599
|
+
"Whether a turn killed by the model provider failing is re-run automatically, backing off between attempts. The sandbox-wide default; any one conversation can say otherwise. Off to begin with, because a retry spends your allowance on a turn you sent once and only you can say whether it was worth paying for twice. Worth turning on for a sandbox whose work mostly happens with nobody in the room.",
|
|
600
|
+
),
|
|
601
|
+
/* When the daemon dies under a running turn, re-run that turn once it is back (agent/turn-journal.ts records
|
|
602
|
+
* every in-flight turn; the boot pass in agent/turn-resume.ts re-runs what survived). OFF by default, like
|
|
603
|
+
* the outage resume above and for the same reason: a boot that re-runs turns spends the user's allowance on
|
|
604
|
+
* work they are not watching, and edits the workspace while they are still waiting for the sandbox to come
|
|
605
|
+
* back. Worth turning on for the case it was built for, the container is recreated on every update, every
|
|
606
|
+
* environment approval and every dev-sandbox.sh swap, so approving the Dockerfile change an agent asked for
|
|
607
|
+
* otherwise costs the run that asked for it.
|
|
608
|
+
*
|
|
609
|
+
* OFF still records the interruption: the fleet card reads `interrupted` (see AgentStatusSchema) and an
|
|
610
|
+
* automation's row shows an `interrupted` run, nothing is re-run, but nothing is silently lost either. */
|
|
611
|
+
autoResumeOnRestart: z
|
|
612
|
+
.boolean()
|
|
613
|
+
.default(false)
|
|
614
|
+
.describe(
|
|
615
|
+
"Whether a turn killed by the sandbox restarting is re-run once it comes back. Off to begin with, for the same reason: it would spend your allowance on work you are not watching and edit files while you are still waiting for the sandbox to return. Either way the interruption is recorded rather than silently lost.",
|
|
616
|
+
),
|
|
617
|
+
/* THE RULE TABLE, every standing instruction the owner gives the sandbox about its own work: ask for
|
|
618
|
+
* proof before a turn ends, run a command before a push, hold or release finished work, and whatever they
|
|
619
|
+
* add next. See RuleSchema for the shape and for why the four action kinds are four.
|
|
620
|
+
*
|
|
621
|
+
* EMPTY IS THE DEFAULT, and it is exactly the behaviour a fresh sandbox had when these were three separate
|
|
622
|
+
* flags: no proof is asked for, no command runs at a push, and finished work waits on its branch. That is
|
|
623
|
+
* not a coincidence to preserve by hand, each of those defaults is what "no rule matched" means at its
|
|
624
|
+
* moment, so the empty table IS the old default rather than a reconstruction of it.
|
|
625
|
+
*
|
|
626
|
+
* Rules live here, in the owner's own settings, rather than in the workspace: a rule can hold work back and
|
|
627
|
+
* gate a push, so the first version answers to the person whose sandbox it is and to nobody else. Repo-
|
|
628
|
+
* committed and extension-contributed rules are worth having and are deliberately not here yet, they need
|
|
629
|
+
* the question of what a rule from somewhere else may WIDEN answered first. */
|
|
630
|
+
rules: z
|
|
631
|
+
.array(RuleSchema)
|
|
632
|
+
.max(50)
|
|
633
|
+
.default([])
|
|
634
|
+
.describe(
|
|
635
|
+
"Standing instructions you give the sandbox about its own work: ask for proof before a turn ends, run something before a push, hold or release finished work. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment.",
|
|
636
|
+
),
|
|
637
|
+
/* STOP AN AUTOMATION THAT ONLY EVER FAILS. After this many consecutive `error` runs the scheduler disables
|
|
638
|
+
* it and says so on the row, instead of firing a job that has proven it cannot succeed every minute until
|
|
639
|
+
* someone notices. 0 ⇒ never, which is the default.
|
|
640
|
+
*
|
|
641
|
+
* Off by default because quarantining edits the USER'S OWN configuration, and the failure it reacts to is
|
|
642
|
+
* not always the automation's fault: an hourly poll against an API having a bad afternoon is broken for
|
|
643
|
+
* three fires and fine on the fourth, and a job disabled at 3 a.m. is one nobody re-enables until they
|
|
644
|
+
* notice it stopped. So the mechanism exists for the case it is unambiguously right for, a misconfigured
|
|
645
|
+
* job burning a turn's worth of tokens on every tick, and the owner is the one who decides their
|
|
646
|
+
* automations are the kind that should be stopped rather than retried.
|
|
647
|
+
*
|
|
648
|
+
* Only `error` counts. A `skipped` run is a guard doing its job, and an `interrupted` one means the daemon
|
|
649
|
+
* died mid-fire, which says nothing about the automation, counting either would quarantine healthy jobs. */
|
|
650
|
+
automationFailureLimit: z
|
|
651
|
+
.number()
|
|
652
|
+
.min(0)
|
|
653
|
+
.max(20)
|
|
654
|
+
.default(0)
|
|
655
|
+
.describe(
|
|
656
|
+
"How many failures in a row before an automation switches itself off. Zero means never, which is the default, because the failure is not always the automation's fault and a job disabled at three in the morning is one nobody re-enables. Only real errors count: a guard deciding there was nothing to do, or the sandbox dying mid-run, say nothing about the automation.",
|
|
657
|
+
),
|
|
658
|
+
/* WHO MAY START A SESSION WITHOUT YOU, the admission floor, per wake source (see AdmissionPolicySchema).
|
|
659
|
+
* Defaults all-allow, so a fresh sandbox behaves exactly as before the floor existed and the per-automation
|
|
660
|
+
* `requireApproval` stays the way most owners meet holds. */
|
|
661
|
+
admission: AdmissionPolicySchema.prefault({}).describe(
|
|
662
|
+
"Whether work started from outside may run, per kind of trigger: let it, hold it for approval, or refuse it. Composes with each automation's own setting, and the stricter of the two wins, so holding every visitor's message needs no edit to each automation.",
|
|
663
|
+
),
|
|
664
|
+
/* THE SNIFFER'S RULEBOOK, verdicts for in-turn actions the outbound gate classifies, keyed by
|
|
665
|
+
* `<provider>.<type>` ("discord.message.send") with `<provider>.*` as the per-provider wildcard; exact key
|
|
666
|
+
* wins. An action with no rule is allowed, the empty default wires no hook at all, so an unconfigured
|
|
667
|
+
* workspace pays nothing. "hold" cannot park a running turn (nobody may be there to answer); it refuses the
|
|
668
|
+
* live call and points the agent at the drafts outbox, which IS the held form of a send.
|
|
669
|
+
*
|
|
670
|
+
* The CHILD-AGENT surface reads the same book: `agents.spawn` covers starting, steering and answering
|
|
671
|
+
* child agents on every provider, `agents.spawn.<provider>` singles one out (the specific key wins), and
|
|
672
|
+
* the daemon's own taint floor holds a spawn from a turn that has taken in outside content unless the
|
|
673
|
+
* owner wrote an explicit allow (guard/actions.ts childSpawn). "hold" refuses with the owner named, the
|
|
674
|
+
* same translation a send gets. */
|
|
675
|
+
actionRules: z
|
|
676
|
+
.record(z.string(), AdmissionRuleSchema)
|
|
677
|
+
.default({})
|
|
678
|
+
.describe("What an agent may do out in the world, per kind of action: go ahead, ask first, or never."),
|
|
679
|
+
/* THE COMMAND GATE'S RULEBOOK, a verdict per CommandClass, for shell commands the agent runs itself. This
|
|
680
|
+
* is the layer that still applies once a session is already running: the admission floor above decides who
|
|
681
|
+
* may wake the agent, and after that every command it types is inside one already-admitted session.
|
|
682
|
+
*
|
|
683
|
+
* "hold" means what it says here, unlike in actionRules: the gate raises a permission card and the command
|
|
684
|
+
* waits for a real answer, in EVERY posture, hooks fire under bypassPermissions, where the card machinery
|
|
685
|
+
* on its own never would. An UNATTENDED turn has nobody to answer, so a hold there refuses instead and says
|
|
686
|
+
* why; that is the honest form of "ask me" when there is no me.
|
|
687
|
+
*
|
|
688
|
+
* An unlisted class is allowed, WITH ONE FLOOR UNDER IT: the classes nothing brings back (FLOOR_CLASSES in
|
|
689
|
+
* command-classes.ts, `system.destructive` today) are held where the owner wrote nothing, so a workspace
|
|
690
|
+
* that has never opened this page is not one mistyped path away from a formatted disk. An explicit `allow`
|
|
691
|
+
* still wins, it is a decision about that exact class and the floor must not override the person who made
|
|
692
|
+
* it. Everything else stays as it was: unlisted is allowed and ordinary work is never asked about.
|
|
693
|
+
*
|
|
694
|
+
* Keys are the CommandClass enum, so a typo is a settings error rather than a rule that silently never
|
|
695
|
+
* matches. */
|
|
696
|
+
commandRules: z
|
|
697
|
+
.partialRecord(CommandClassSchema, AdmissionRuleSchema)
|
|
698
|
+
.default({})
|
|
699
|
+
.describe(
|
|
700
|
+
"What an agent may run inside the sandbox, for the six kinds of command that are hard to take back: rewriting git history, deleting recursively, wiping a disk or a container volume, reading credential files, publishing a package, reaching out to the network. Everything else is recoverable in a container that is itself disposable, and gating it would be friction bought with nothing. Leaving a kind unset is not the same as allowing it: wiping a disk is held for your approval until you say otherwise, because nothing here brings that back.",
|
|
701
|
+
),
|
|
702
|
+
/* WHETHER A HELD COMMAND IS TRANSLATED BEFORE YOU ANSWER FOR IT. Off by default, and the default is the
|
|
703
|
+
* argument: the card is already complete without this, and switching it on spends one quick-model call per
|
|
704
|
+
* card raised, on the owner's own connected account.
|
|
705
|
+
*
|
|
706
|
+
* It buys the case the card is worst at. A held command is regularly a hundred-plus characters of pipeline
|
|
707
|
+
* the agent assembled, and the question the card actually asks, "do you want this to happen", is not
|
|
708
|
+
* answerable by reading shell quickly. The classifier already marks WHICH fragment held it (the card paints
|
|
709
|
+
* those); a sentence is what says what the rest of it does and why the agent wanted it.
|
|
710
|
+
*
|
|
711
|
+
* NEVER THE AGENT'S OWN WORDS. The sentence comes from the quick model reading the command text, not from
|
|
712
|
+
* the model being gated: a card that let the asking agent write its own justification would be a safety
|
|
713
|
+
* prompt whose persuasive half is authored by the thing it is meant to gate.
|
|
714
|
+
*
|
|
715
|
+
* The command itself is never replaced by it, only folded behind a disclosure the sentence sits above, and
|
|
716
|
+
* the marked fragments stay on the card either way, see ChatMessageView's permission card. */
|
|
717
|
+
explainCommands: z
|
|
718
|
+
.boolean()
|
|
719
|
+
.default(false)
|
|
720
|
+
.describe(
|
|
721
|
+
"Have the quick model describe each held command in one plain sentence, above the command itself. Costs one quick-model call per card raised, on your own connected account. The command is always still there to read.",
|
|
722
|
+
),
|
|
723
|
+
/* HOW MUCH AN AGENT MAY DELEGATE, the three ceilings the Claude Code harness enforces on its own Agent
|
|
724
|
+
* tool, surfaced here because their defaults are tuned for a laptop and this is a container the owner sized.
|
|
725
|
+
*
|
|
726
|
+
* They are three settings rather than one because they stop different things, and a fan-out that clears one
|
|
727
|
+
* lands on the next: `subagentsAtOnce` is the parallel width of a single fan-out, `subagentsPerTurn` is the
|
|
728
|
+
* lifetime budget of one conversation, and `subagentDepth` is how far a child may itself delegate. Raising
|
|
729
|
+
* the width alone is what makes a wide sweep hit the lifetime cap two rounds later, which reads to the user
|
|
730
|
+
* as the same wall in a new place.
|
|
731
|
+
*
|
|
732
|
+
* Each default is what the CLI does with no env set, so a sandbox that has never opened this group behaves
|
|
733
|
+
* exactly as it always did, these are not our numbers, they are the harness's, restated so they can move.
|
|
734
|
+
* The ceilings are ours: an agent is told to stop and NOT retry when it hits one, so the cost of a number
|
|
735
|
+
* set too high is a real fleet of models running at once, and the cost of one set too low is a wall.
|
|
736
|
+
*
|
|
737
|
+
* The refusal an agent sees names the env var (`ask them to raise CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`),
|
|
738
|
+
* which is why these three exist as settings at all: without them the only answer to that ask is editing
|
|
739
|
+
* the container's environment and restarting the daemon. */
|
|
740
|
+
subagentsAtOnce: z.number().min(1).max(200).default(20).describe("How many helper agents may work at the same time."),
|
|
741
|
+
subagentsPerTurn: z.number().min(1).max(2000).default(200).describe("How many a single turn may start in total."),
|
|
742
|
+
// Depth 1 = an agent may delegate, but its children may not. The CLI's own default is 3, and it is the one
|
|
743
|
+
// of the three whose runaway case is unbounded rather than merely wide, each level multiplies the last.
|
|
744
|
+
subagentDepth: z
|
|
745
|
+
.number()
|
|
746
|
+
.min(1)
|
|
747
|
+
.max(10)
|
|
748
|
+
.default(3)
|
|
749
|
+
.describe("How many levels deep the delegation may go, since a helper can start helpers of its own."),
|
|
750
|
+
});
|
|
751
|
+
export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
|
|
752
|
+
// One of the two built-in bases, as text: Intentic's own prompt, or Claude Code's preset read out of the CLI
|
|
753
|
+
// this sandbox runs (preset-prompt.ts captures it rather than storing a transcription). What the settings page
|
|
754
|
+
// shows behind "View" and drops into the editor behind "Edit a copy".
|
|
755
|
+
//
|
|
756
|
+
// `version` is the CLI build a captured preset came from, so the UI can say WHICH text the user is looking at:
|
|
757
|
+
// a custom prompt forked from an older build is a snapshot, and the version is the only honest way to tell.
|
|
758
|
+
// Empty for Intentic's prompt, which ships with the app and has no version of its own to report.
|
|
759
|
+
export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.string() });
|
|
760
|
+
export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
|
|
761
|
+
/* ---- savings report: what each token-reduction mechanism actually saved ----
|
|
762
|
+
*
|
|
763
|
+
* TWO FAMILIES, deliberately never one list of bars. They are measured differently, and a chart that ranks
|
|
764
|
+
* them side by side claims a confidence and a denominator that only one of them has:
|
|
765
|
+
*
|
|
766
|
+
* input , shell output the cleaners trimmed before the model ever saw it. Both sides of the comparison come
|
|
767
|
+
* off the SAME command (raw in, emitted out), so the counterfactual is observed rather than
|
|
768
|
+
* estimated: exact, per command, no sample size to argue about.
|
|
769
|
+
* output, the model's own tokens under the terse steer. There is no second run of the same turn to compare
|
|
770
|
+
* against, so the only honest number is an experiment: a turn-level holdout, an n per arm, and a
|
|
771
|
+
* margin. It is absent entirely until both arms are large enough for the delta to mean anything.
|
|
772
|
+
*
|
|
773
|
+
* The two are also in different units of value, a saved tool-output token is saved again on every later
|
|
774
|
+
* request of that conversation, an output token is saved once but costs several times as much, which is the
|
|
775
|
+
* other reason they are separate sections with separate totals rather than one number.
|
|
776
|
+
*/
|
|
777
|
+
|
|
778
|
+
// One mechanism's realized saving, biggest first. `savedTokens` is what THIS stage removed from what reached
|
|
779
|
+
// it in pipeline order, sequential attribution, which is why the stages sum exactly to raw − emitted and can
|
|
780
|
+
// be drawn as one stacked bar. It is NOT "what turning this cleaner off would cost you": the cap downstream
|
|
781
|
+
// would have eaten some of the same lines. `commands` is how many commands the stage ran on. Negative for the
|
|
782
|
+
// `footer` stage, which adds the retrieval pointer back, a cost on the same ledger as what it bought.
|
|
783
|
+
export const SavingsStageSchema = z.object({ id: z.string(), commands: z.number(), savedTokens: z.number() });
|
|
784
|
+
// What the cleaners saved on the way in, aggregated from historyRoot/logs/filter-stats.jsonl, one row per
|
|
785
|
+
// agent Bash command, written by agent-output-filter. Every number here is windowed on the ledger's own
|
|
786
|
+
// calendar (the UTC day each command ran), so the reader's date range and the figures above it agree.
|
|
787
|
+
export const InputSavingsSchema = z.object({
|
|
788
|
+
// When the ledger last recorded a command (epoch ms), so the card can show its age instead of implying
|
|
789
|
+
// freshness it doesn't have. Absent when the ledger has never been written.
|
|
790
|
+
updatedAt: z.number().optional(),
|
|
791
|
+
commands: z.number(),
|
|
792
|
+
rawTokens: z.number(),
|
|
793
|
+
emittedTokens: z.number(),
|
|
794
|
+
savedPct: z.number(),
|
|
795
|
+
// Per-stage attribution, biggest first.
|
|
796
|
+
perCleaner: z.array(SavingsStageSchema),
|
|
797
|
+
// The measured control, commands the holdout left raw, against the cleaned population. A real saved-%
|
|
798
|
+
// for the pipeline as a whole rather than an estimate, and the only whole-pipeline counterfactual there is.
|
|
799
|
+
holdout: z.object({ cleaned: z.number(), heldOut: z.number(), measuredSavedPct: z.number().optional() }),
|
|
800
|
+
/* High-volume commands that matched no cleaner: where the next handler is worth writing. GROUPED by the
|
|
801
|
+
* command text, `commands` is how many times it ran and `tokens` their total, because the question this
|
|
802
|
+
* list is read for is "what is worth a handler", and a handler is worth writing for a command that costs
|
|
803
|
+
* 5k twenty times over, not for the single 60k outlier that happened to sort first. */
|
|
804
|
+
gaps: z.array(z.object({ command: z.string(), commands: z.number(), tokens: z.number() })),
|
|
805
|
+
});
|
|
806
|
+
export type InputSavings = z.infer<typeof InputSavingsSchema>;
|
|
807
|
+
// One arm of a turn-level experiment: the turns that ran with the mechanism, and the turns the holdout ran
|
|
808
|
+
// without it. A mean PER TURN, because the arms never hold the same number of turns.
|
|
809
|
+
export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() });
|
|
810
|
+
/* ONE METRIC'S READING of a turn-level experiment: the two arms, and whatever the arithmetic over them will
|
|
811
|
+
* stand behind. An experiment can carry several, see TurnExperimentSchema.
|
|
812
|
+
*
|
|
813
|
+
* `metric` says what `mean` counts and what `deltaPct` is a delta in, and choosing it is most of the work.
|
|
814
|
+
* proseChars , the terse steer: the thing it steers, and the only part of the model's output that
|
|
815
|
+
* responds to being asked to be brief (UsageTurn.proseChars has why output tokens cannot).
|
|
816
|
+
* searchCalls , the search teaching: the searches a turn ran, which the teaching directly changes.
|
|
817
|
+
* openingSearches, the same, narrower: the searches before the turn first touched a file.
|
|
818
|
+
* Search mechanisms must not be judged on COST. Cost is a whole turn's work, a search mechanism moves one part
|
|
819
|
+
* of it, and the part sits inside the noise of the rest exactly as the steer's effect once sat inside its
|
|
820
|
+
* tool-call arguments. */
|
|
821
|
+
export const TurnMetricReadingSchema = z.object({
|
|
822
|
+
metric: z.enum(["proseChars", "searchCalls", "openingSearches"]),
|
|
823
|
+
on: SavingsArmSchema,
|
|
824
|
+
off: SavingsArmSchema,
|
|
825
|
+
/* HOW MUCH LONGER, when the margin spans zero and the honest answer is "keep collecting": the additional
|
|
826
|
+
* CONTROL turns at which the resolution would reach a width worth acting on (turn-experiments.ts sets it),
|
|
827
|
+
* holding the spread where it sits today.
|
|
828
|
+
*
|
|
829
|
+
* It is aimed at a FIXED resolution rather than at today's delta on purpose. Sized against the observed
|
|
830
|
+
* effect it reported fourteen more turns for an experiment that had gone nine days without resolving, an
|
|
831
|
+
* estimate divided by noise inherits the noise and promises an answer next week indefinitely. Against a
|
|
832
|
+
* fixed target the same ledger asks for a few hundred, which is the fact the reader needs: this holdout is
|
|
833
|
+
* not close, and waiting is not the move.
|
|
834
|
+
*
|
|
835
|
+
* An order-of-magnitude figure, and it reads as one, the point is telling "a few more days" apart from
|
|
836
|
+
* "not at this holdout", which is a decision, where "measuring…" forever is not.
|
|
837
|
+
*
|
|
838
|
+
* Absent ⇒ nothing to wait for: the arms are under `minTurns`, the delta is published, or the resolution is
|
|
839
|
+
* already good enough and the effect is simply smaller than it. */
|
|
840
|
+
controlTurnsNeeded: z.number().optional(),
|
|
841
|
+
/* THE RESOLUTION, present as soon as both arms clear `minTurns`: ± percentage points at 95% (Welch,
|
|
842
|
+
* unequal variances and unequal arms). Present even when the delta below is withheld, because "whatever
|
|
843
|
+
* this mechanism does, it is smaller than ±35 points" is a true and useful thing to be told, it is the
|
|
844
|
+
* reading that says to keep collecting rather than to act. */
|
|
845
|
+
marginPct: z.number().optional(),
|
|
846
|
+
/* THE CLAIM, present only once there is one. Both together, and only when the margin does NOT span zero.
|
|
847
|
+
*
|
|
848
|
+
* A schema that can't express a half-measured experiment is how a 34%-that-becomes-8%-tomorrow never
|
|
849
|
+
* reaches the screen, and clearing `minTurns` turned out not to be enough to buy that. The terse steer
|
|
850
|
+
* crossed its thirtieth control turn and immediately reported +31.2% ± 35.1pp: a confidence interval
|
|
851
|
+
* running from −3.4% to +66.7%, which is to say no effect was measured at all, rendered as an alarming
|
|
852
|
+
* number pointing the wrong way. Thirty turns is where the normal approximation starts to hold, not where
|
|
853
|
+
* this much per-turn spread resolves an effect; requiring the interval to exclude zero is the same
|
|
854
|
+
* withhold-until-it-means-something rule applied to the thing that actually decides whether it does.
|
|
855
|
+
* deltaPct, change in the metric's mean per turn under the mechanism; negative is a saving.
|
|
856
|
+
* saved , what the delta is worth over the turns that actually ran with it, in this window, in the
|
|
857
|
+
* metric's own unit (characters, or searches). */
|
|
858
|
+
deltaPct: z.number().optional(),
|
|
859
|
+
saved: z.number().optional(),
|
|
860
|
+
});
|
|
861
|
+
export type TurnMetricReading = z.infer<typeof TurnMetricReadingSchema>;
|
|
862
|
+
/* A turn-level A/B, the one shape both of this sandbox's turn experiments report in, because they differ in
|
|
863
|
+
* nothing but which flag flips and what the turns are judged on. Only turns the mechanism was ELIGIBLE for are
|
|
864
|
+
* counted: a turn under a custom system prompt drops the terse steer along with everything else the daemon
|
|
865
|
+
* appends, so it belongs to neither arm.
|
|
866
|
+
*
|
|
867
|
+
* ONE COIN FLIP, SEVERAL READINGS. `metrics` is a list because the search teaching is judged on two, the
|
|
868
|
+
* searches a turn ran, and the ones it ran before touching a file, and they are two readings of the SAME
|
|
869
|
+
* experiment, not two experiments. Splitting them into separate entries would duplicate the arm assignment and
|
|
870
|
+
* let a screen show a turn count on one that disagrees with the other. Headline first: the screens read
|
|
871
|
+
* `metrics[0]` for the big number and the rest as supporting lines. */
|
|
872
|
+
export const TurnExperimentSchema = z.object({
|
|
873
|
+
// A head and a tail rather than a plain array, because an experiment judged on nothing is not an experiment:
|
|
874
|
+
// the screens take the first reading for their headline and stack the rest under it, and this is what makes
|
|
875
|
+
// "there is always a headline" a fact the type carries instead of a check every screen repeats. (`.nonempty()`
|
|
876
|
+
// would not do it, in zod 4 it adds a min-length rule and leaves the inferred type a plain array.)
|
|
877
|
+
metrics: z.tuple([TurnMetricReadingSchema], TurnMetricReadingSchema),
|
|
878
|
+
// Turns per arm before a delta is reported at all. Carried on the wire so the screen's "measuring…" state
|
|
879
|
+
// counts toward the daemon's real threshold instead of a number the browser guessed. Shared by every
|
|
880
|
+
// reading: they are the same turns counted differently, so they clear it together.
|
|
881
|
+
minTurns: z.number(),
|
|
882
|
+
// The randomized unit behind the arm counts. Turn mechanisms default to turns; teaching loaded into a
|
|
883
|
+
// provider session randomizes and analyzes whole conversations so repeated turns are not false replicas.
|
|
884
|
+
sampleUnit: z.enum(["turns", "conversations"]).optional(),
|
|
885
|
+
// Content-addressed treatment version. Present where mixing rows from two instruction revisions would turn
|
|
886
|
+
// one experiment into two unnamed ones; the reader filters to this (latest) cohort.
|
|
887
|
+
cohort: z.string().optional(),
|
|
888
|
+
});
|
|
889
|
+
export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
|
|
890
|
+
// `output`/`search` are absent when that experiment isn't running at all (its flag off, or no holdout set), a
|
|
891
|
+
// section that isn't there reads as "not measured", which is the truth, while zeros would read as "measured,
|
|
892
|
+
// worth nothing".
|
|
893
|
+
/* WHAT THE COMPLEXITY JUDGE HAS BEEN SAYING, read back off the spend ledger's tier fields (UsageTurn.tierScore
|
|
894
|
+
* and friends) over the requested window. The three numbers docs/model-routing-design.md §4 says the feature
|
|
895
|
+
* cannot be defended without, plus the veto count, and nothing else: no counterfactual "you would have saved
|
|
896
|
+
* $X", because the ledger holds what turns COST, not what they would have cost on a model they never ran.
|
|
897
|
+
*
|
|
898
|
+
* NOT a TurnExperiment, deliberately. The experiments compare two randomized arms of one population; this is a
|
|
899
|
+
* tally of what one mechanism observed and did. Dressing it in arms and margins would claim a control group that
|
|
900
|
+
* does not exist (routing follows the settings mode, which follows time, not a coin flip).
|
|
901
|
+
*
|
|
902
|
+
* The whole section is absent when no turn in the window was judged at all (autoTier "off" throughout), which a
|
|
903
|
+
* screen renders as absence: "not measured" is the truth, zeros would read as "measured, found nothing". */
|
|
904
|
+
export const TierReportSchema = z.object({
|
|
905
|
+
// Turns the judge ran on in the window, the denominator under everything below.
|
|
906
|
+
judged: z.number(),
|
|
907
|
+
// …of which landed at or below FAST_CEILING: the turns that looked simple. fast ÷ judged is the fast share.
|
|
908
|
+
fast: z.number(),
|
|
909
|
+
/* What the fast-judged turns that STAYED on the user's pick actually cost, the money measure mode is
|
|
910
|
+
* pointing at. An upper bound on any saving, never an estimate of one: moving those turns to the cheap rung
|
|
911
|
+
* would have cost something too, and this schema refuses to guess how much. */
|
|
912
|
+
atStakeUsd: z.number(),
|
|
913
|
+
// Turns that actually ran the cheap rung, and what they cost there. Realized, not projected.
|
|
914
|
+
routed: z.number(),
|
|
915
|
+
routedUsd: z.number(),
|
|
916
|
+
/* THE GUARDRAIL: fast-judged turns whose conversation's very next ledger row asked for a dearer model, the
|
|
917
|
+
* user reaching for the model picker right after a turn the judge called simple. The strongest negative
|
|
918
|
+
* signal the ledger can carry (§4's first calibration row). Past a few percent of `fast`, the judge is
|
|
919
|
+
* costing more in retries and trust than it saves in tokens. */
|
|
920
|
+
escalated: z.number(),
|
|
921
|
+
// Fast-judged turns the user vetoed outright (UsageTurn.tierDenied): the same signal, said even louder.
|
|
922
|
+
denied: z.number(),
|
|
923
|
+
});
|
|
924
|
+
export type TierReport = z.infer<typeof TierReportSchema>;
|
|
925
|
+
export const SavingsReportSchema = z.object({
|
|
926
|
+
input: InputSavingsSchema,
|
|
927
|
+
output: TurnExperimentSchema.optional(),
|
|
928
|
+
search: TurnExperimentSchema.optional(),
|
|
929
|
+
// Automatic tier selection's readout, see TierReportSchema. Absent ⇒ nothing was judged in the window.
|
|
930
|
+
tier: TierReportSchema.optional(),
|
|
931
|
+
});
|
|
932
|
+
export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|