@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,303 @@
|
|
|
1
|
+
// usage: the durable spend ledger
|
|
2
|
+
// usage: per-account token/cost totals
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// One row per attributed turn, appended at turn end and NEVER pruned. This exists because the activity log
|
|
5
|
+
// can't answer a money question: it prunes to its most recent entries, so a month's spend is unanswerable and
|
|
6
|
+
//, worse for a cost readout, the totals SHRINK as newer turns evict older ones. The ledger keeps the raw
|
|
7
|
+
// per-turn facts and the rollup projects them on read, so a new grouping (by day, by model, by conversation)
|
|
8
|
+
// needs no new storage and no migration.
|
|
9
|
+
export const UsageTurnSchema = z.object({
|
|
10
|
+
// Epoch ms at turn end. Kept alongside `day` so a future timezone-aware rollup is a pure change over data
|
|
11
|
+
// already on disk.
|
|
12
|
+
at: z.number().describe("When the turn ended, in milliseconds."),
|
|
13
|
+
// The UTC calendar day (YYYY-MM-DD) `at` fell in, precomputed so a rollup never re-derives a timezone.
|
|
14
|
+
day: z.string().describe("The day it fell in, as YYYY-MM-DD in UTC, worked out once so nothing downstream has to do timezone arithmetic."),
|
|
15
|
+
provider: z.string().describe("Which model provider served it."),
|
|
16
|
+
// Absent on an env-token turn, which has no account to attribute to (same rule as the activity log).
|
|
17
|
+
account: z.string().optional().describe("Which account paid. Absent for a turn run on a plain key, which belongs to no account."),
|
|
18
|
+
// The model the turn ACTUALLY ran, resolved past the client's pick and every provider default. Absent only
|
|
19
|
+
// when the provider's own subscription default served it without the daemon naming one.
|
|
20
|
+
model: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(
|
|
24
|
+
"The model that actually ran, past whatever was asked for and every default. Absent only when the provider's own default served it without being named.",
|
|
25
|
+
),
|
|
26
|
+
/* WHAT THE CLIENT ASKED FOR, beside `model` above, which is what ran. The pair is the point: a routing
|
|
27
|
+
* surprise is then a diff on one row rather than an investigation through the routing code.
|
|
28
|
+
*
|
|
29
|
+
* The gap between the two is real and was unreadable. A pick is resolved past the tier judge's downgrade,
|
|
30
|
+
* a provider's own subscription default, a catalog validity check that silently substitutes (Grok rejects
|
|
31
|
+
* a retired models.dev id, so an invalid pin becomes the catalog default), and CLIProxyAPI's own choice on
|
|
32
|
+
* a routed turn. Every one of those is a legitimate substitution and none of them was recorded, so "I
|
|
33
|
+
* chose one model and got another's error" could only be answered by reading four resolution paths and
|
|
34
|
+
* guessing which had fired.
|
|
35
|
+
*
|
|
36
|
+
* Absent ⇒ the client named nothing and asked for the default, which is not the same as asking for what it
|
|
37
|
+
* got. Equal to `model` on the overwhelming majority of turns; the rows where they differ are the whole
|
|
38
|
+
* reason this is here. */
|
|
39
|
+
modelRequested: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("The model that was asked for, when one was named. Differs from `model` when something resolved it."),
|
|
43
|
+
harness: z.string().describe("Which agentic loop it ran on."),
|
|
44
|
+
/* HOW THE TURN ENDED. The field that turns the ledger from an accounting record into a diagnostic one.
|
|
45
|
+
*
|
|
46
|
+
* Without it a turn that died is byte-for-byte indistinguishable from one that succeeded, except that it
|
|
47
|
+
* cost less, so "four sessions all broke a minute ago" had no record to read and had to be answered by
|
|
48
|
+
* re-running the destructive act in a live sandbox. The failure was never nowhere: it was in the activity
|
|
49
|
+
* log, which prunes to its most recent entries, so an incident survives only until the feed rolls past it.
|
|
50
|
+
* This log is never pruned, which is the entire difference.
|
|
51
|
+
*
|
|
52
|
+
* "cancelled" is a user pressing Stop, which is not a failure and must never be read as one, the registry
|
|
53
|
+
* learned that lesson already (see the abort branch in streamAgent). "error" is a turn the provider or the
|
|
54
|
+
* request killed. Both still carry whatever they spent before they ended.
|
|
55
|
+
*
|
|
56
|
+
* Absent ⇒ the row predates this being recorded, NOT a turn that succeeded. Readers that count failures
|
|
57
|
+
* must treat absent as unknown, and the experiment readers do exactly that. */
|
|
58
|
+
outcome: z.enum(["ok", "error", "cancelled"]).optional().describe("How it ended: finished, failed, or was stopped by the user."),
|
|
59
|
+
// The failing frame's code, when it carried one, e.g. `rate_limit`, `provider-outage`, `claude-not-entitled`.
|
|
60
|
+
// Present only alongside outcome "error", and absent even then for a failure that named no code, which is
|
|
61
|
+
// itself the interesting case: an unclassified failure is one nothing downstream knows how to handle.
|
|
62
|
+
errorCode: z.string().optional().describe("The failure's code, when it had one."),
|
|
63
|
+
// The failing frame's own sentence, capped at ERROR_MESSAGE_CHARS. Capped rather than omitted because the
|
|
64
|
+
// provider's wording is routinely the only thing that distinguishes two failures sharing one code, and
|
|
65
|
+
// uncapped it would let one bad provider message dominate a file that must stay cheap to read whole.
|
|
66
|
+
errorMessage: z.string().optional().describe("What the failure said, trimmed."),
|
|
67
|
+
// The conversation this turn belonged to, so spend can join to a fleet agent. Absent only for an internal
|
|
68
|
+
// one-shot turn that has no conversation identity.
|
|
69
|
+
conversationId: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe(
|
|
73
|
+
"Which conversation it belonged to, so spending can be traced to a card. Absent only for an internal one-off with no conversation at all.",
|
|
74
|
+
),
|
|
75
|
+
// The provider's own turn count for the request (a Claude "turn" can be several under the hood), so turns
|
|
76
|
+
// and cost stay comparable across providers. 1 when the provider reported none.
|
|
77
|
+
turns: z
|
|
78
|
+
.number()
|
|
79
|
+
.describe("The provider's own count for the request, since one exchange can be several under the hood. One when it reported none."),
|
|
80
|
+
inputTokens: z.number().describe("Tokens sent."),
|
|
81
|
+
outputTokens: z.number().describe("Tokens received."),
|
|
82
|
+
cacheReadTokens: z.number().describe("Tokens served from cache, which cost less."),
|
|
83
|
+
cacheCreationTokens: z.number().describe("Tokens written to cache, which cost more up front and less afterwards."),
|
|
84
|
+
costUsd: z.number().describe("What it cost, in dollars."),
|
|
85
|
+
durationMs: z.number().describe("How long it took, in milliseconds."),
|
|
86
|
+
/* Which arm of the terse experiment this turn ran on (settings.terseHoldout), the only record of it, and
|
|
87
|
+
* the reason the savings report can say what the steer is worth instead of guessing.
|
|
88
|
+
*
|
|
89
|
+
* ABSENT means "not part of the experiment", not "off": a turn under a custom system prompt drops the
|
|
90
|
+
* steer along with everything else the daemon appends, and a turn run with the experiment switched off has
|
|
91
|
+
* no control to be compared against. Pooling those into the off-arm would compare steered turns against a
|
|
92
|
+
* population selected by something other than the coin flip, which is not a control at all. */
|
|
93
|
+
terse: z.boolean().optional(),
|
|
94
|
+
/* Which arm of the iq SEARCH-TEACHING experiment this conversation runs on
|
|
95
|
+
* (settings.iqSearchHoldout). Stable for every turn in one conversation: the treatment is instruction
|
|
96
|
+
* loaded into a provider session, so flipping it per turn would call a remembered treatment a control.
|
|
97
|
+
* Absent ⇒ measurement is off; true/false ⇒ taught/cold. */
|
|
98
|
+
iqSearchArm: z.boolean().optional(),
|
|
99
|
+
// Hash of the plugin nudge + skill body used for this arm. Control turns carry it too, so a report can keep
|
|
100
|
+
// both sides of one treatment revision together and exclude older wording after an upgrade.
|
|
101
|
+
iqSearchCohort: z.string().optional(),
|
|
102
|
+
/* Characters of the model's own PROSE this turn, the `delta` frames only, so no tool-call arguments and no
|
|
103
|
+
* thinking. What the terse steer is judged on, and the reason it can be judged at all.
|
|
104
|
+
*
|
|
105
|
+
* `outputTokens` cannot serve: measured over a day of real turns it is 91.6% tool-call arguments (an Edit's
|
|
106
|
+
* old_string and new_string, a Write's whole file body) and 7.8% prose. The steer moves prose. So a fifth
|
|
107
|
+
* off the model's narration moves the total by 1.6%, against a margin of ±35 points, which is to say the
|
|
108
|
+
* experiment was structurally unable to see its own treatment, and the number it printed instead was
|
|
109
|
+
* whichever arm happened to draw the bigger tasks.
|
|
110
|
+
*
|
|
111
|
+
* CHARACTERS, not tokens, because the provider bills a total and never breaks it down, a token figure here
|
|
112
|
+
* would be chars÷4 wearing a unit it had not earned. For a comparison of two arms the constant cancels
|
|
113
|
+
* anyway, and the honest unit is the one actually counted.
|
|
114
|
+
*
|
|
115
|
+
* Absent ⇒ the turn predates this being measured; `armOf` drops it from the population rather than reading
|
|
116
|
+
* it as a silent turn. */
|
|
117
|
+
proseChars: z.number().optional(),
|
|
118
|
+
/* SEARCHES THIS TURN RAN, every tool call that went looking for code, the dedicated search tools and the
|
|
119
|
+
* CLI searches alike (isSearchCall owns the rule; `iq q` is Bash and would otherwise not be counted at all).
|
|
120
|
+
* What the search teaching is judged on, and the same correction `proseChars` is to the terse steer.
|
|
121
|
+
*
|
|
122
|
+
* COST PER TURN CANNOT SERVE: cost is a whole turn's worth of work, a search mechanism touches one part of
|
|
123
|
+
* it, and the part lives inside the noise of the rest, exactly the shape that made output tokens unable to
|
|
124
|
+
* see the steer. Nine days of a since-removed retrieval experiment proved it with an interval from −2.9% to
|
|
125
|
+
* +56.9%, driven entirely by which arm had drawn the bigger jobs.
|
|
126
|
+
*
|
|
127
|
+
* Searches are what the mechanism acts on directly. Turns that never search stay in the population at zero
|
|
128
|
+
* rather than being filtered out, they dilute both arms equally, while selecting on "did it search" would
|
|
129
|
+
* select on the treatment itself.
|
|
130
|
+
*
|
|
131
|
+
* Absent ⇒ the turn predates this being measured; `armOf` drops it rather than reading it as a turn that
|
|
132
|
+
* searched nothing. */
|
|
133
|
+
searchCalls: z.number().optional(),
|
|
134
|
+
/* …and how many of them came BEFORE the turn first opened or changed a file, the orientation burst. A turn
|
|
135
|
+
* that already knows where to look starts working; one that doesn't goes hunting first.
|
|
136
|
+
*
|
|
137
|
+
* The narrower of the two readings and the less confounded: `searchCalls` still grows with the size of the
|
|
138
|
+
* job, while the walk up to the first file is roughly the same act whatever the job turns out to be.
|
|
139
|
+
*
|
|
140
|
+
* A turn that never reads or edits counts all of its searches here, it never arrived, so all of it was
|
|
141
|
+
* orientation. Dropping those instead would select the population by an OUTCOME the treatment moves, which
|
|
142
|
+
* is the one bias an arm-based reading cannot absorb.
|
|
143
|
+
*
|
|
144
|
+
* Absent ⇒ as for `searchCalls`. */
|
|
145
|
+
openingSearches: z.number().optional(),
|
|
146
|
+
/* DID THIS TURN FINISH, OR DID IT STOP TALKING. The fields that tell the two apart, and the reason
|
|
147
|
+
* `outcome` alone could never.
|
|
148
|
+
*
|
|
149
|
+
* A turn ends at least five different ways: its stop condition was met, the model ran out of things to say,
|
|
150
|
+
* the loop hit a cap, the budget ran out, or the model asserted it was done and nothing checked the claim.
|
|
151
|
+
* `outcome` collapses four of those into "ok", so a ledger that already recorded every failure perfectly
|
|
152
|
+
* still wrote the same word over a turn that proved its work and a turn that went quiet halfway through its
|
|
153
|
+
* own checklist. The daemon was computing the difference and discarding it at turn end.
|
|
154
|
+
*
|
|
155
|
+
* NO SINGLE STOP-REASON WORD IS STORED, deliberately: these are the facts, and which of the five modes they
|
|
156
|
+
* add up to is a rule that will get better. A word written down now would freeze today's rule into rows
|
|
157
|
+
* that outlive it and cannot be re-read under the next one, the same reason `tierRouted` is recorded beside
|
|
158
|
+
* `tierScore` instead of being derived from it once and forgotten.
|
|
159
|
+
*
|
|
160
|
+
* `verification` is the four-state VerificationState the child roster already speaks (agent-verification.ts):
|
|
161
|
+
* "verified" = a check passed AFTER the last code edit, "failing" = the last one after it did not,
|
|
162
|
+
* "unproven" = nothing ran, "no-code" = nothing a check could speak to was edited. Folded from this turn's
|
|
163
|
+
* own tool-call frames, subagents' included, so a Codex turn is judged exactly as a Claude one is. Absent ⇒
|
|
164
|
+
* the row predates this, NOT a turn nothing was known about.
|
|
165
|
+
*
|
|
166
|
+
* `check` is the command that SPOKE, the one that cleared the work or the one that broke, capped. It is
|
|
167
|
+
* what keeps "verified" auditable: a passing `vitest run one.test.ts` is evidence about one file and must
|
|
168
|
+
* never read as the repo being green, and only the command itself shows which was which.
|
|
169
|
+
*
|
|
170
|
+
* `filesEdited` counts every file written, prose included, where `verification` speaks only about code. The
|
|
171
|
+
* pair is the point: `{ verification: "no-code", filesEdited: 3 }` is a documentation turn, and
|
|
172
|
+
* `{ verification: "unproven", filesEdited: 0 }` cannot happen. */
|
|
173
|
+
verification: z.enum(["verified", "unproven", "failing", "no-code"]).optional(),
|
|
174
|
+
check: z.string().optional(),
|
|
175
|
+
filesEdited: z.number().optional(),
|
|
176
|
+
/* THE AGENT'S OWN CHECKLIST WHEN THE TURN ENDED, reconstructed from the Task tool family and reported by
|
|
177
|
+
* every runtime that keeps one (`todos` frames). `checklistOpen` is pending plus in-progress.
|
|
178
|
+
*
|
|
179
|
+
* This is the honest reading of "the model stopped emitting": a turn that ended `ok` with items still open
|
|
180
|
+
* abandoned a plan it wrote itself, which is a different event from a turn that finished one, and the two
|
|
181
|
+
* were indistinguishable in every record this daemon kept. Absent ⇒ the turn kept no checklist at all,
|
|
182
|
+
* which is most short turns and is not the same as an empty one. */
|
|
183
|
+
checklistTotal: z.number().optional(),
|
|
184
|
+
checklistOpen: z.number().optional(),
|
|
185
|
+
/* HOW MANY TIMES THE CONTEXT WAS COMPACTED under this turn, and how full the window was when it ended.
|
|
186
|
+
*
|
|
187
|
+
* Compaction is the least observable thing a harness does to a turn: a poorly timed one discards the
|
|
188
|
+
* partial result the turn still needed, and the only trace was a frame streamed to whoever was watching.
|
|
189
|
+
* Recorded here, "did compaction hurt" becomes a question this ledger can answer, by joining these against
|
|
190
|
+
* `verification` and `outcome` over months of real turns, which is a number nobody currently has.
|
|
191
|
+
*
|
|
192
|
+
* `contextTokens`/`contextWindow` are the last `context_usage` frame, absent for a runtime that reports
|
|
193
|
+
* none. Both, rather than a percentage: the window is a per-model constant that moves between model
|
|
194
|
+
* versions, and a fraction computed today cannot be recomputed tomorrow from what it threw away.
|
|
195
|
+
*
|
|
196
|
+
* Together they carry ~90 bytes onto a ~250-byte row. A heavy day is ~100 turns, so a year of hard use goes
|
|
197
|
+
* from ~9 MB to ~12 MB, which is the price of a log that can answer why a turn ended rather than only what
|
|
198
|
+
* it cost. */
|
|
199
|
+
compactions: z.number().optional(),
|
|
200
|
+
contextTokens: z.number().optional(),
|
|
201
|
+
contextWindow: z.number().optional(),
|
|
202
|
+
/* WHAT THE COMPLEXITY JUDGE SAID ABOUT THIS TURN, and whether anything was done about it. The three fields
|
|
203
|
+
* automatic tier selection is calibrated from, and the reason it can ship in shadow at all.
|
|
204
|
+
*
|
|
205
|
+
* They live on the SPEND ledger rather than in a log of their own because the question they exist to answer
|
|
206
|
+
* is a question about money: what did the turns we would have downgraded actually cost, and what did the
|
|
207
|
+
* ones we did downgrade cost instead. A separate log would have to be joined back to this one on every
|
|
208
|
+
* read, and the join key (a turn) is already the row.
|
|
209
|
+
*
|
|
210
|
+
* `tierScore` is 0..1 from judgeComplexity, comparable against FAST_CEILING, which is the cutoff it was
|
|
211
|
+
* judged against at the time. Absent ⇒ the judge did not run (settings.autoTier "off", or a row written
|
|
212
|
+
* before this existed), which is NOT the same as a turn that scored zero.
|
|
213
|
+
*
|
|
214
|
+
* `tierRules` is which named features fired, and it is the half that makes the ledger analysable rather
|
|
215
|
+
* than merely tallyable: a score says a threshold was crossed, the rules say which feature is doing the
|
|
216
|
+
* work, and re-fitting the weights needs the second. Bounded by construction, there are ~19 of them.
|
|
217
|
+
*
|
|
218
|
+
* `tierRouted` is whether the turn ACTUALLY ran on the cheap rung. It is not implied by the score: a turn
|
|
219
|
+
* judged fast still runs standard in shadow mode, and still runs standard in `on` mode when the provider
|
|
220
|
+
* publishes nothing cheaper than the user's pick. Reading the score as the decision would report savings
|
|
221
|
+
* that were never made. */
|
|
222
|
+
tierScore: z.number().optional(),
|
|
223
|
+
tierRules: z.array(z.string()).optional(),
|
|
224
|
+
tierRouted: z.boolean().optional(),
|
|
225
|
+
/* THE VERDICT ITSELF, and the cutoff it was reached against.
|
|
226
|
+
*
|
|
227
|
+
* `tierScore` stopped being able to answer "was this called simple" the moment the cutoff became the owner's
|
|
228
|
+
* to choose (settings.autoTierEagerness): 0.35 is standard on the middle stop and fast on the eager one, and
|
|
229
|
+
* a fast verdict also requires a positively-easy signal that no score can express. So the answer is written
|
|
230
|
+
* down rather than re-derived, and the ceiling goes with it because a refit reading a column of bare scores
|
|
231
|
+
* could not otherwise tell two rows apart.
|
|
232
|
+
*
|
|
233
|
+
* Absent on rows written before the knob existed, which were all judged at the `balanced` cutoff — which is
|
|
234
|
+
* exactly what a reader falls back to (FAST_CEILING), so old and new rows stay one population. */
|
|
235
|
+
tierFast: z.boolean().optional(),
|
|
236
|
+
tierCeiling: z.number().optional(),
|
|
237
|
+
/* THE USER SAID NO: the turn carried AgentTurn.tierHold, so a fast verdict moved nothing. Recorded rather
|
|
238
|
+
* than folded into `tierRouted: false` because it is the strongest calibration label this ledger ever gets,
|
|
239
|
+
* a person looking at this very conversation deciding the cheap rung was not to be trusted with it, and the
|
|
240
|
+
* refit (docs/model-routing-design.md §4) needs it kept apart from "nothing cheaper was published". Absent ⇒
|
|
241
|
+
* no veto, which is every row written before the control existed and most rows after. */
|
|
242
|
+
tierDenied: z.boolean().optional(),
|
|
243
|
+
});
|
|
244
|
+
export type UsageTurn = z.infer<typeof UsageTurnSchema>;
|
|
245
|
+
// The ledger grouped by day × provider × account × model × harness × conversation, the finest grouping any
|
|
246
|
+
// dashboard panel needs, and a handful of rows per active day instead of one per turn, so a year of history is
|
|
247
|
+
// well under a MB over the tunnel. Every panel (spend per day, cost by model, cost by agent, cache hit rate) is
|
|
248
|
+
// a projection of these.
|
|
249
|
+
// The conversation is in the KEY, not merely along for the ride, because cost-by-agent has to answer within the
|
|
250
|
+
// same window as every other panel on the screen. The fleet registry also carries a per-agent total, but only a
|
|
251
|
+
// cumulative, all-time one, reading it beside a "last 7 days" filter would print an all-time number under a
|
|
252
|
+
// windowed heading, which is the shrinking-totals bug wearing a different hat.
|
|
253
|
+
export const UsageRollupRowSchema = z.object({
|
|
254
|
+
day: z.string().describe("The day, as YYYY-MM-DD in UTC."),
|
|
255
|
+
provider: z.string().describe("Which model provider."),
|
|
256
|
+
account: z.string().optional().describe("Which account. Absent for work run on a plain key."),
|
|
257
|
+
model: z.string().optional().describe("Which model."),
|
|
258
|
+
harness: z.string().describe("Which agentic loop."),
|
|
259
|
+
conversationId: z.string().optional().describe("Which conversation."),
|
|
260
|
+
turns: z.number().describe("Turns in this group."),
|
|
261
|
+
inputTokens: z.number().describe("Tokens sent."),
|
|
262
|
+
outputTokens: z.number().describe("Tokens received."),
|
|
263
|
+
cacheReadTokens: z.number().describe("Tokens served from cache."),
|
|
264
|
+
cacheCreationTokens: z.number().describe("Tokens written to cache."),
|
|
265
|
+
costUsd: z.number().describe("What the group cost, in dollars."),
|
|
266
|
+
durationMs: z.number().describe("Time spent, in milliseconds."),
|
|
267
|
+
});
|
|
268
|
+
export type UsageRollupRow = z.infer<typeof UsageRollupRowSchema>;
|
|
269
|
+
// Inclusive UTC day bounds (YYYY-MM-DD). Both absent ⇒ the whole ledger. Shared by every windowed read of a
|
|
270
|
+
// daemon ledger (spend, savings): one window shape, so a screen that filters two ledgers at once filters them
|
|
271
|
+
// with the same calendar.
|
|
272
|
+
export const DayWindowQuerySchema = z.object({
|
|
273
|
+
from: z.string().optional().describe("First day to include, as YYYY-MM-DD in UTC. Leave it out for everything up to the end day."),
|
|
274
|
+
to: z
|
|
275
|
+
.string()
|
|
276
|
+
.optional()
|
|
277
|
+
.describe(
|
|
278
|
+
"Last day to include, as YYYY-MM-DD in UTC, and it is included rather than excluded. Leave it out for everything from the start day onwards.",
|
|
279
|
+
),
|
|
280
|
+
});
|
|
281
|
+
export type DayWindowQuery = z.infer<typeof DayWindowQuerySchema>;
|
|
282
|
+
export const UsageRollupSchema = z.object({
|
|
283
|
+
rows: z
|
|
284
|
+
.array(UsageRollupRowSchema)
|
|
285
|
+
.describe(
|
|
286
|
+
"Spending grouped by day, provider, account, model and conversation. Everything a cost screen shows is a rearrangement of these rows, which is why there is no second call for any of it.",
|
|
287
|
+
),
|
|
288
|
+
});
|
|
289
|
+
// The account picker's headroom readout, folded from the ledger above (all-time, not a log window), grouped by
|
|
290
|
+
// provider+account. `account` is the attribution key, so env-token turns are excluded rather than pooled under
|
|
291
|
+
// a blank id, an unattributed turn belongs to no account's total.
|
|
292
|
+
export const UsageAccountSchema = z.object({
|
|
293
|
+
provider: z.string(),
|
|
294
|
+
account: z.string(),
|
|
295
|
+
turns: z.number(),
|
|
296
|
+
inputTokens: z.number(),
|
|
297
|
+
outputTokens: z.number(),
|
|
298
|
+
cacheReadTokens: z.number(),
|
|
299
|
+
cacheCreationTokens: z.number(),
|
|
300
|
+
costUsd: z.number(),
|
|
301
|
+
});
|
|
302
|
+
export type UsageAccount = z.infer<typeof UsageAccountSchema>;
|
|
303
|
+
export const UsageSummarySchema = z.object({ accounts: z.array(UsageAccountSchema) });
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import { CapabilitiesListSchema
|
|
2
|
+
import { CapabilitiesListSchema } from "./capabilities.js";
|
|
3
|
+
import { SandboxSettingsSchema } from "./settings.js";
|
|
4
|
+
import { IpsecVpnConfigSchema } from "./vpn.js";
|
|
3
5
|
|
|
4
6
|
/* The settings shape spans a version seam that really moves: the browser ships with the platform, the daemon
|
|
5
7
|
* ships inside the user's sandbox image, so a web build routinely parses a payload from an OLDER daemon. These
|
|
@@ -29,7 +31,7 @@ test("a payload from a build that predates a toggle parses, with the new toggle
|
|
|
29
31
|
* time only when the user changes something, so until then the daemon parses `{}`: one field without a
|
|
30
32
|
* `.default()` turns that into a throw at boot, and the version tolerance above is built on the same property.
|
|
31
33
|
*
|
|
32
|
-
* Asserted by shape rather than by value: what each default IS belongs next to the field in
|
|
34
|
+
* Asserted by shape rather than by value: what each default IS belongs next to the field in settings.ts, where
|
|
33
35
|
* the reason it holds is written down. A second copy here proved nothing the schema didn't already say and
|
|
34
36
|
* failed on every field the product added. */
|
|
35
37
|
test("no field is required: a workspace that has never written settings parses", () => {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// vpn: live tunnel state + connect/disconnect
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// A VPN the agent's traffic rides. One capability = one tunnel, discriminated by `provider` so a new protocol
|
|
4
|
+
// is a new arm (plus a driver in the daemon's vpn/), never a reinterpretation of an existing field:
|
|
5
|
+
// wireguard, a pasted .conf, brought up with wg-quick.
|
|
6
|
+
// fortinet , a FortiGate SSL-VPN (what FortiClient's <sslvpn> connections speak), dialled with openconnect
|
|
7
|
+
// --protocol=fortinet. openconnect is the client rather than openfortivpn because it routes over
|
|
8
|
+
// tun instead of pppd: it needs exactly the tun + NET_ADMIN grant this kind already carries, and
|
|
9
|
+
// no /dev/ppp device (which the runtime allowlist does not, and should not, include).
|
|
10
|
+
// ipsec , an IKEv1/IKEv2 tunnel with a pre-shared key and optional XAuth (FortiClient's <ipsecvpn>
|
|
11
|
+
// connections), run by strongSwan. `aggressive` mirrors FortiClient's dial-up default.
|
|
12
|
+
// Connecting is NOT a config field: connect/disconnect are live operations (see vpn.contract.ts) that both the
|
|
13
|
+
// user and the agent drive, so a stored tunnel's up/down state is read from the OS, never from the manifest.
|
|
14
|
+
// `autoConnect` is the only persisted intent, whether the daemon dials this tunnel again on boot.
|
|
15
|
+
export const VpnProviderSchema = z.enum(["wireguard", "fortinet", "ipsec"]);
|
|
16
|
+
export type VpnProvider = z.infer<typeof VpnProviderSchema>;
|
|
17
|
+
const autoConnect = z.enum(["on", "off"]).default("on");
|
|
18
|
+
// FortiClient wraps every stored credential in its own "EncX <hex>" (older builds: "Enc <hex>") encryption,
|
|
19
|
+
// keyed to the machine that exported the config, it is NOT recoverable from the file. Pasting one is an easy
|
|
20
|
+
// mistake to make, because in the XML it sits exactly where the credential belongs, and the failure it causes
|
|
21
|
+
// is unreadable: phase 1 negotiates fine and IKE then reports "calculated HASH does not match HASH payload",
|
|
22
|
+
// which says nothing about where the bad value came from. Rejecting it here turns that into a sentence at the
|
|
23
|
+
// point of entry. (The FortiClient importer already drops these, this catches a hand-paste.)
|
|
24
|
+
// Exported so the add form can flag it inline on blur instead of only on a rejected round-trip, one
|
|
25
|
+
// definition of what "this is ciphertext, not a credential" means, shared by the browser and the daemon.
|
|
26
|
+
export const isForticlientCiphertext = (value: string): boolean => /^Enc[X]?\s+[0-9A-Fa-f]{8,}$/.test(value.trim());
|
|
27
|
+
const notForticlientCiphertext = <T extends z.ZodType<string>>(field: T, label: string): T =>
|
|
28
|
+
field.refine((value) => !isForticlientCiphertext(value), {
|
|
29
|
+
message: `That looks like a value copied straight out of a FortiClient config, FortiClient encrypts it with a key tied to the machine that exported it, so it can't be used here. Enter the actual ${label} (ask whoever administers the gateway).`,
|
|
30
|
+
}) as unknown as T;
|
|
31
|
+
export const WireguardVpnConfigSchema = z.object({
|
|
32
|
+
provider: z.literal("wireguard"),
|
|
33
|
+
// The pasted .conf ([Interface] + [Peer]), it holds the private key, so it's this arm's secret field.
|
|
34
|
+
config: z.string().min(1),
|
|
35
|
+
autoConnect,
|
|
36
|
+
});
|
|
37
|
+
export const FortinetVpnConfigSchema = z.object({
|
|
38
|
+
provider: z.literal("fortinet"),
|
|
39
|
+
// Gateway host only; the port is its own field so a pasted "host:port" can be split on import.
|
|
40
|
+
server: z.string().min(1),
|
|
41
|
+
port: z.coerce.number().int().min(1).max(65535).default(443),
|
|
42
|
+
username: z.string().min(1),
|
|
43
|
+
password: notForticlientCiphertext(z.string().min(1), "password"),
|
|
44
|
+
// A FortiGate on a self-signed/private-CA certificate: openconnect pins this digest
|
|
45
|
+
// ("sha256:…", copied from its own refusal message) instead of trusting a CA. Absent ⇒ normal CA validation.
|
|
46
|
+
trustedCert: z.string().min(1).optional(),
|
|
47
|
+
// Some gateways scope a login to a realm/group (openconnect --usergroup, FortiClient's tunnel realm).
|
|
48
|
+
realm: z.string().min(1).optional(),
|
|
49
|
+
autoConnect,
|
|
50
|
+
});
|
|
51
|
+
export const IpsecVpnConfigSchema = z.object({
|
|
52
|
+
provider: z.literal("ipsec"),
|
|
53
|
+
server: z.string().min(1),
|
|
54
|
+
presharedKey: notForticlientCiphertext(z.string().min(1), "pre-shared key"),
|
|
55
|
+
// The local IKE identity (FortiClient's <localid>), dial-up FortiGates key their phase-1 selection off it.
|
|
56
|
+
localId: z.string().min(1).optional(),
|
|
57
|
+
remoteId: z.string().min(1).optional(),
|
|
58
|
+
// XAuth (FortiClient's <xauth>), absent for PSK-only tunnels.
|
|
59
|
+
username: z.string().min(1).optional(),
|
|
60
|
+
password: notForticlientCiphertext(z.string().min(1), "XAuth password").optional(),
|
|
61
|
+
ikeVersion: z.enum(["1", "2"]).default("1"),
|
|
62
|
+
// Perfect Forward Secrecy for phase 2. Must match the gateway EXACTLY: it decides whether a KE payload is
|
|
63
|
+
// sent in quick mode, and a mismatch fails with NO_PROPOSAL_CHOSEN only after phase 1 and XAuth have
|
|
64
|
+
// succeeded, which reads like anything but a phase 2 problem. FortiClient stores it as <pfs> under
|
|
65
|
+
// <ipsec_settings> and defaults it on, so that is the default here too.
|
|
66
|
+
pfs: z.enum(["on", "off"]).default("on"),
|
|
67
|
+
// The Diffie-Hellman group, as FortiClient numbers them. ONE field for both phases on purpose: in IKEv1
|
|
68
|
+
// strongSwan sends a single KE payload in quick mode and the phase-2 group ends up following phase 1, so
|
|
69
|
+
// offering a phase-1 list that starts with a different group than the gateway wants for phase 2 fails with
|
|
70
|
+
// NO_PROPOSAL_CHOSEN no matter what the esp= line says. 14 (modp2048) is FortiClient's phase-2 default;
|
|
71
|
+
// it is <dhgroup> under <ipsec_settings> in an export.
|
|
72
|
+
dhGroup: z.enum(["2", "5", "14", "15", "16", "19", "20"]).default("14"),
|
|
73
|
+
// IKEv1 aggressive mode: insecure by construction, and exactly what FortiGate dial-up with a group PSK
|
|
74
|
+
// requires, hence opt-in per connection rather than a global strongSwan setting.
|
|
75
|
+
aggressive: z.enum(["on", "off"]).default("on"),
|
|
76
|
+
// WHICH networks ride the tunnel, strongSwan's rightsubnet, the traffic selector this client offers in
|
|
77
|
+
// quick mode. The single most consequential setting on an ipsec tunnel, and the one with no visible symptom
|
|
78
|
+
// until it is wrong: 0.0.0.0/0 offers the gateway EVERYTHING the sandbox sends, including the sandbox's own
|
|
79
|
+
// outbound connection to the model endpoint. A gateway that routes only its own networks accepts that
|
|
80
|
+
// selector, assigns a virtual IP, and then black-holes the rest, so the agent goes silent mid-turn, which
|
|
81
|
+
// reads as the agent breaking rather than as a VPN setting. Narrowing this to the networks actually behind
|
|
82
|
+
// the gateway (10.0.0.0/8,192.168.0.0/16) fixes it with nothing lost: the gateway is asked for less, not for
|
|
83
|
+
// something different, and it needs no change of its own to accept that.
|
|
84
|
+
// Comma-separated because strongSwan takes a list; under IKEv1 each entry is its own CHILD_SA, which not
|
|
85
|
+
// every gateway will negotiate, a list that dials as one entry is a gateway limit, not a config error.
|
|
86
|
+
// The DEFAULT STAYS 0.0.0.0/0: narrowing it for everyone would cut existing tunnels off from networks they
|
|
87
|
+
// reach today, and a full tunnel is right whenever the gateway does route the internet.
|
|
88
|
+
routedNetworks: z
|
|
89
|
+
.string()
|
|
90
|
+
.default("0.0.0.0/0")
|
|
91
|
+
.refine(
|
|
92
|
+
(value) =>
|
|
93
|
+
value
|
|
94
|
+
.split(",")
|
|
95
|
+
.map((entry) => entry.trim())
|
|
96
|
+
.every((entry) => z.cidrv4().safeParse(entry).success || z.cidrv6().safeParse(entry).success),
|
|
97
|
+
{
|
|
98
|
+
message:
|
|
99
|
+
"Routed networks is a comma-separated list of CIDRs, like 10.0.0.0/8,192.168.0.0/16. A single host needs its prefix too (192.168.0.168/32). Leave it at 0.0.0.0/0 to send everything through the gateway.",
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
autoConnect,
|
|
103
|
+
});
|
|
104
|
+
export const VpnConfigSchema = z.discriminatedUnion("provider", [WireguardVpnConfigSchema, FortinetVpnConfigSchema, IpsecVpnConfigSchema]);
|
|
105
|
+
export type WireguardVpnConfig = z.infer<typeof WireguardVpnConfigSchema>;
|
|
106
|
+
export type FortinetVpnConfig = z.infer<typeof FortinetVpnConfigSchema>;
|
|
107
|
+
export type IpsecVpnConfig = z.infer<typeof IpsecVpnConfigSchema>;
|
|
108
|
+
export type VpnConfig = z.infer<typeof VpnConfigSchema>;
|
|
109
|
+
// The manifest says which VPNs EXIST; this says which are UP right now. Every field is read back from the OS
|
|
110
|
+
// (wg show / ip / openconnect's pidfile / swanctl), never remembered by the daemon, so a tunnel the agent
|
|
111
|
+
// dropped from a shell and one the UI dropped read identically, and a daemon restart loses nothing.
|
|
112
|
+
|
|
113
|
+
export const VpnStateSchema = z.enum([
|
|
114
|
+
// The tunnel is up and carrying traffic.
|
|
115
|
+
"connected",
|
|
116
|
+
// Dialling: openconnect authenticated but the interface has no address yet, or strongSwan is negotiating.
|
|
117
|
+
"connecting",
|
|
118
|
+
// Configured and idle, the normal resting state for a tunnel nobody asked for.
|
|
119
|
+
"disconnected",
|
|
120
|
+
// The tunnel's client isn't installed yet: the capability's image fragment needs an owner-run rebuild.
|
|
121
|
+
"unavailable",
|
|
122
|
+
// The last dial failed; `detail` carries the client's own message.
|
|
123
|
+
"failed",
|
|
124
|
+
]);
|
|
125
|
+
export type VpnState = z.infer<typeof VpnStateSchema>;
|
|
126
|
+
export const VpnLinkSchema = z.object({
|
|
127
|
+
id: z.string().describe("Which tunnel."),
|
|
128
|
+
provider: VpnProviderSchema.describe("What kind of tunnel it is."),
|
|
129
|
+
state: VpnStateSchema.describe(
|
|
130
|
+
"Whether it is up, dialling, resting, failed, or not installable yet because its client needs a rebuild to arrive.",
|
|
131
|
+
),
|
|
132
|
+
// The gateway this tunnel dials, host:port for fortinet, the [Peer] endpoint for wireguard, the IKE peer
|
|
133
|
+
// for ipsec. Display only; never a secret.
|
|
134
|
+
gateway: z.string().optional().describe("What it dials. For display only, and never a credential."),
|
|
135
|
+
// The tun/wg interface carrying the tunnel, once it exists.
|
|
136
|
+
interface: z.string().optional().describe("The network interface carrying it, once one exists."),
|
|
137
|
+
// The address the gateway assigned this sandbox, the single most useful "am I on the VPN?" fact.
|
|
138
|
+
address: z
|
|
139
|
+
.string()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("The address the far end gave this sandbox, which is the single most useful answer to whether you are on the VPN."),
|
|
142
|
+
// The CIDRs routed into the tunnel ("0.0.0.0/0" = full tunnel). Empty until the link is up.
|
|
143
|
+
routes: z
|
|
144
|
+
.array(z.string())
|
|
145
|
+
.default([])
|
|
146
|
+
.describe("What goes through it. Everything, when the range covers the whole internet. Empty until it is up."),
|
|
147
|
+
// DNS servers the tunnel pushed, when it pushed any.
|
|
148
|
+
dns: z.array(z.string()).default([]).describe("Name servers it pushed, when it pushed any."),
|
|
149
|
+
// Epoch ms the link came up, the UI renders "connected 14m ago". Absent unless connected.
|
|
150
|
+
since: z.number().optional().describe("When it came up, in milliseconds. Absent unless it is."),
|
|
151
|
+
// Whether the daemon re-dials this tunnel on boot (the manifest's autoConnect).
|
|
152
|
+
autoConnect: z.boolean().describe("Whether it dials itself when the sandbox starts."),
|
|
153
|
+
// Why it is failed/unavailable, or an extra note on a healthy link. Never carries credentials.
|
|
154
|
+
detail: z.string().optional().describe("Why it failed, or a note about a healthy one. Never a credential."),
|
|
155
|
+
});
|
|
156
|
+
export type VpnLink = z.infer<typeof VpnLinkSchema>;
|
|
157
|
+
export const VpnListSchema = z.object({
|
|
158
|
+
links: z
|
|
159
|
+
.array(VpnLinkSchema)
|
|
160
|
+
.describe("Every configured tunnel with its live state, read back from the operating system each time rather than remembered."),
|
|
161
|
+
});
|
|
162
|
+
// POST /vpn/{id}/connect body. `otp` is a one-time 2FA code, supplied per dial and NEVER stored, a FortiGate
|
|
163
|
+
// with token auth rejects the dial without it, and the daemon surfaces that as a retry-with-a-code error.
|
|
164
|
+
export const VpnConnectInputSchema = z.object({
|
|
165
|
+
id: z.string().describe("Which tunnel to dial."),
|
|
166
|
+
otp: z
|
|
167
|
+
.string()
|
|
168
|
+
.min(1)
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("A one-time code, where the gateway wants one. Supplied per dial and never stored; without it such a gateway refuses and says so."),
|
|
171
|
+
});
|
|
172
|
+
export const VpnIdParamSchema = z.object({ id: z.string().describe("Which tunnel.") });
|
|
173
|
+
// POST /vpn/import-forticlient: parse an exported FortiClient configuration (the XML FortiClient writes from
|
|
174
|
+
// File → Settings → Backup) into addable connections. Credentials in that file are wrapped in FortiClient's
|
|
175
|
+
// proprietary "EncX …" encryption, which is NOT reversible here, so a parsed connection carries the endpoint
|
|
176
|
+
// and, when it was stored in the clear, the username; the password is always typed by the user afterwards.
|
|
177
|
+
export const ForticlientImportInputSchema = z.object({
|
|
178
|
+
xml: z.string().min(1).describe("The exported configuration file, whole. Nothing is stored: it is read and thrown away."),
|
|
179
|
+
});
|
|
180
|
+
export const ForticlientConnectionSchema = z.object({
|
|
181
|
+
// FortiClient's connection name, slugged into a legal capability id.
|
|
182
|
+
id: z.string().describe("The id it would be added under."),
|
|
183
|
+
// The original <name>, shown so the user recognises the connection they picked.
|
|
184
|
+
label: z.string().describe("Its name as the file has it, so somebody recognises the connection they are picking."),
|
|
185
|
+
provider: VpnProviderSchema.describe("What kind of tunnel it is."),
|
|
186
|
+
server: z.string().describe("Where it dials."),
|
|
187
|
+
port: z.number().describe("On which port."),
|
|
188
|
+
// Present only when FortiClient stored it unencrypted; an EncX-wrapped username is dropped, not guessed.
|
|
189
|
+
username: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("The username, but only when the file stored it in the clear. An encrypted one is dropped rather than guessed at."),
|
|
193
|
+
description: z.string().optional().describe("Whatever the file said about it."),
|
|
194
|
+
// ipsec-only, and only when the file stored them in the clear.
|
|
195
|
+
localId: z.string().optional().describe("An identity some tunnel types need, when the file stored it readably."),
|
|
196
|
+
aggressive: z.boolean().optional().describe("Which negotiation mode it used."),
|
|
197
|
+
// Phase-2 settings, read from <ipsec_settings>, the pair that decides whether quick mode can succeed.
|
|
198
|
+
pfs: z.boolean().optional().describe("Whether it asked for forward secrecy."),
|
|
199
|
+
dhGroup: z
|
|
200
|
+
.string()
|
|
201
|
+
.optional()
|
|
202
|
+
.describe(
|
|
203
|
+
"Which key-exchange group it used. Together with the setting above, this is what decides whether the connection can complete at all.",
|
|
204
|
+
),
|
|
205
|
+
// What the user still has to supply for this connection to dial (always at least the password).
|
|
206
|
+
needs: z
|
|
207
|
+
.array(z.string())
|
|
208
|
+
.describe(
|
|
209
|
+
"What you still have to type in before it can dial. Always at least the password, because the export wraps credentials in encryption that cannot be undone here.",
|
|
210
|
+
),
|
|
211
|
+
});
|
|
212
|
+
export type ForticlientConnection = z.infer<typeof ForticlientConnectionSchema>;
|
|
213
|
+
export const ForticlientImportSchema = z.object({
|
|
214
|
+
connections: z.array(ForticlientConnectionSchema).describe("The connections found in the file, ready to be added one at a time."),
|
|
215
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// webext: the user's own browser (the `webext` capability's live half)
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/* ONE SITE THE PERSON ALLOWED, as the browser itself understands it. `origin` is Chrome's own match pattern
|
|
4
|
+
* for that grant ("https://github.com/*"), because that is the string the extension asked `chrome.permissions`
|
|
5
|
+
* for and the string the browser will show in its own settings — inventing a prettier spelling here would mean
|
|
6
|
+
* a card naming something the browser's permission list does not.
|
|
7
|
+
*
|
|
8
|
+
* `mode` is this extension's own narrowing on top of Chrome's grant: a site may be readable without being
|
|
9
|
+
* driveable. Chrome has no concept for that, so it is enforced in the extension and shown here so the card can
|
|
10
|
+
* say which is which. */
|
|
11
|
+
export const WebExtGrantSchema = z.object({
|
|
12
|
+
origin: z.string(),
|
|
13
|
+
mode: z.enum(["read", "act"]),
|
|
14
|
+
});
|
|
15
|
+
export type WebExtGrant = z.infer<typeof WebExtGrantSchema>;
|
|
16
|
+
// What a connected browser reports about itself. The parallel of HostFacts, and the same job: the SKILL pack
|
|
17
|
+
// teaches the agent how to drive a browser, this tells it which browser, how many tabs are open, and — the
|
|
18
|
+
// part with no equivalent on a machine — exactly which sites it may touch right now.
|
|
19
|
+
export const WebExtFactsSchema = z.object({
|
|
20
|
+
// How a person would name it: "Chrome 141 on Windows".
|
|
21
|
+
browser: z.string(),
|
|
22
|
+
// Tabs open right now. A count rather than a list: the list is a tool call away and changes every minute,
|
|
23
|
+
// and a card that renders someone's open tabs is a card nobody wants to screenshot.
|
|
24
|
+
tabs: z.number(),
|
|
25
|
+
/* The sites the person has allowed, live from the browser's own permission store rather than from anything
|
|
26
|
+
* this daemon remembers. It is the answer to the question every reader of this card actually has, and the
|
|
27
|
+
* only honest source for it is the browser: a grant can be revoked in Chrome's UI without the sandbox
|
|
28
|
+
* being told. */
|
|
29
|
+
grants: z.array(WebExtGrantSchema),
|
|
30
|
+
// The extension's own kill switch, flipped in its popup. True ⇒ every tool refuses, and says so.
|
|
31
|
+
paused: z.boolean(),
|
|
32
|
+
});
|
|
33
|
+
export type WebExtFacts = z.infer<typeof WebExtFactsSchema>;
|
|
34
|
+
export const WebExtSummarySchema = z.object({
|
|
35
|
+
// The capability id, the browser's name, and the prefix of its tools (mcp__<id>__click).
|
|
36
|
+
id: z.string(),
|
|
37
|
+
platform: z.string().min(1),
|
|
38
|
+
online: z.boolean(),
|
|
39
|
+
// The extension build, so a browser running an old one is visible rather than mysteriously lacking a tool.
|
|
40
|
+
version: z.string().optional(),
|
|
41
|
+
// Epoch ms of the last time this browser held a socket. Absent ⇒ it has not connected since this daemon
|
|
42
|
+
// booted: liveness is a fact about a socket, so a restart forgets it rather than claiming stale uptime.
|
|
43
|
+
lastSeen: z.number().optional(),
|
|
44
|
+
facts: WebExtFactsSchema.optional(),
|
|
45
|
+
});
|
|
46
|
+
export type WebExtSummary = z.infer<typeof WebExtSummarySchema>;
|
|
47
|
+
export const WebExtsListSchema = z.object({ browsers: z.array(WebExtSummarySchema) });
|
|
48
|
+
/* ---- handing a site's session to the sandbox: what the extension POSTs, and what comes back ----
|
|
49
|
+
*
|
|
50
|
+
* THE PAYLOAD IS A CREDENTIAL, the whole of one. It travels on its own HTTPS door (webext-protocol.ts's
|
|
51
|
+
* webextSessionUrl) rather than as an answer on the socket, because socket answers are MCP results and MCP
|
|
52
|
+
* results land in the model's context. Nothing here is ever logged, echoed in an error, or written anywhere
|
|
53
|
+
* but the target profile's cookie store. */
|
|
54
|
+
export const WebExtCookieSchema = z.object({
|
|
55
|
+
name: z.string(),
|
|
56
|
+
value: z.string(),
|
|
57
|
+
// Chrome's own spelling, leading dot and all (".github.com"): it is what the browser stored, and rewriting
|
|
58
|
+
// it here is how a session arrives that the target site does not recognise.
|
|
59
|
+
domain: z.string(),
|
|
60
|
+
path: z.string(),
|
|
61
|
+
// Epoch SECONDS, Chrome's unit. Absent ⇒ a session cookie, which dies with the browser that receives it.
|
|
62
|
+
expires: z.number().optional(),
|
|
63
|
+
httpOnly: z.boolean(),
|
|
64
|
+
secure: z.boolean(),
|
|
65
|
+
sameSite: z.enum(["Strict", "Lax", "None"]),
|
|
66
|
+
});
|
|
67
|
+
export type WebExtCookie = z.infer<typeof WebExtCookieSchema>;
|
|
68
|
+
export const WebExtSessionImportSchema = z.object({
|
|
69
|
+
// The `browser`-kind capability whose profile receives this. Named by the agent from the roster it can
|
|
70
|
+
// already read, so a session can only ever land in an account the owner had already created.
|
|
71
|
+
account: z.string().min(1),
|
|
72
|
+
// The site this came from, for the message the owner reads afterwards. Never used to place the cookies:
|
|
73
|
+
// each cookie carries its own domain.
|
|
74
|
+
origin: z.string().min(1),
|
|
75
|
+
// A ceiling rather than a guess: a big site's cookie jar for one registrable domain runs to dozens, and
|
|
76
|
+
// anything past this is a caller that misunderstood the tool, not a login.
|
|
77
|
+
cookies: z.array(WebExtCookieSchema).min(1).max(300),
|
|
78
|
+
});
|
|
79
|
+
export type WebExtSessionImport = z.infer<typeof WebExtSessionImportSchema>;
|