@intentic/sandbox-contract 1.311.0 → 1.313.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 +32 -4
- package/dist/chores/chores.js +6 -6
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/index.d.ts +0 -1
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +0 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/chores/prompt.d.ts.map +1 -1
- package/dist/chores/prompt.js +3 -2
- package/dist/chores/prompt.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +69 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +436 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +11 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +718 -90
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +30 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.js +1 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +20 -4
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +1 -1
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +13 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +2 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/offload.contract.d.ts +107 -0
- package/dist/contracts/offload.contract.d.ts.map +1 -0
- package/dist/contracts/offload.contract.js +53 -0
- package/dist/contracts/offload.contract.js.map +1 -0
- package/dist/contracts/personas.contract.d.ts +2 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.d.ts +5 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +204 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +3 -1
- 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 +1 -0
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +59 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +20 -8
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -2
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +230 -15
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +88 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +217 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +40 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/documents/conversions.d.ts +81 -0
- package/dist/documents/conversions.d.ts.map +1 -0
- package/dist/documents/conversions.js +213 -0
- package/dist/documents/conversions.js.map +1 -0
- package/dist/documents/index.d.ts +4 -0
- package/dist/documents/index.d.ts.map +1 -0
- package/dist/documents/index.js +4 -0
- package/dist/documents/index.js.map +1 -0
- package/dist/documents/passthrough.d.ts +13 -0
- package/dist/documents/passthrough.d.ts.map +1 -0
- package/dist/documents/passthrough.js +101 -0
- package/dist/documents/passthrough.js.map +1 -0
- package/dist/documents/read.d.ts +24 -0
- package/dist/documents/read.d.ts.map +1 -0
- package/dist/documents/read.js +81 -0
- package/dist/documents/read.js.map +1 -0
- package/dist/events/agent-events.d.ts +235 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +3 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/child-run.d.ts +9 -0
- package/dist/events/child-run.d.ts.map +1 -0
- package/dist/events/child-run.js +41 -0
- package/dist/events/child-run.js.map +1 -0
- package/dist/events/errands.d.ts +5 -0
- package/dist/events/errands.d.ts.map +1 -0
- package/dist/events/errands.js +19 -0
- package/dist/events/errands.js.map +1 -0
- package/dist/events/land-breakage.d.ts +3 -0
- package/dist/events/land-breakage.d.ts.map +1 -1
- package/dist/events/land-breakage.js +3 -0
- package/dist/events/land-breakage.js.map +1 -1
- package/dist/events/land-conflict.d.ts +3 -0
- package/dist/events/land-conflict.d.ts.map +1 -0
- package/dist/events/land-conflict.js +4 -0
- package/dist/events/land-conflict.js.map +1 -0
- package/dist/events/requests.d.ts +152 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +26 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/system-events.d.ts +74 -12
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +3 -1
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +387 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +3 -0
- package/dist/events/transcript.js.map +1 -1
- package/dist/events/verify-nudge.d.ts.map +1 -1
- package/dist/events/verify-nudge.js.map +1 -1
- package/dist/front/browser-wire.d.ts +6 -0
- package/dist/front/browser-wire.d.ts.map +1 -0
- package/dist/front/browser-wire.js +4 -0
- package/dist/front/browser-wire.js.map +1 -0
- package/dist/front/front-wire.d.ts +6 -0
- package/dist/front/front-wire.d.ts.map +1 -0
- package/dist/front/front-wire.js +5 -0
- package/dist/front/front-wire.js.map +1 -0
- package/dist/front/generated/browser-wire.d.ts +19 -0
- package/dist/front/generated/browser-wire.d.ts.map +1 -0
- package/dist/front/generated/browser-wire.js +2 -0
- package/dist/front/generated/browser-wire.js.map +1 -0
- package/dist/front/generated/wire.d.ts +70 -1
- package/dist/front/generated/wire.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +3 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +4 -2
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/ids/session-names.d.ts +3 -0
- package/dist/ids/session-names.d.ts.map +1 -1
- package/dist/ids/session-names.js +3 -0
- package/dist/ids/session-names.js.map +1 -1
- package/dist/index.d.ts +2052 -190
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/models/agent-catalog.d.ts.map +1 -1
- package/dist/models/agent-catalog.js +4 -6
- package/dist/models/agent-catalog.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts +2 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +7 -1
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/plan-pools.d.ts +22 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +86 -0
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/policy/request-status.d.ts +1 -1
- package/dist/policy/request-status.d.ts.map +1 -1
- package/dist/policy/request-status.js +4 -11
- package/dist/policy/request-status.js.map +1 -1
- package/dist/policy/reserved-servers.d.ts.map +1 -1
- package/dist/policy/reserved-servers.js +2 -1
- package/dist/policy/reserved-servers.js.map +1 -1
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +1 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/container-requirements.d.ts +0 -1
- package/dist/protocol/container-requirements.d.ts.map +1 -1
- package/dist/protocol/container-requirements.js +1 -2
- package/dist/protocol/container-requirements.js.map +1 -1
- package/dist/protocol/edge-verdict.d.ts +2 -1
- package/dist/protocol/edge-verdict.d.ts.map +1 -1
- package/dist/protocol/edge-verdict.js +1 -1
- package/dist/protocol/edge-verdict.js.map +1 -1
- package/dist/protocol/ingress-contract.d.ts +1 -12
- package/dist/protocol/ingress-contract.d.ts.map +1 -1
- package/dist/protocol/ingress-contract.js +1 -1
- package/dist/protocol/ingress-contract.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +36 -26
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +17 -23
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts +22 -0
- package/dist/protocol/route-meta.d.ts.map +1 -1
- package/dist/protocol/route-meta.js +16 -0
- package/dist/protocol/route-meta.js.map +1 -1
- package/dist/protocol/runner-protocol.d.ts +34 -0
- package/dist/protocol/runner-protocol.d.ts.map +1 -1
- package/dist/protocol/runner-protocol.js +33 -0
- package/dist/protocol/runner-protocol.js.map +1 -1
- package/dist/protocol/tunnel-bulk.d.ts +2 -0
- package/dist/protocol/tunnel-bulk.d.ts.map +1 -0
- package/dist/protocol/tunnel-bulk.js +3 -0
- package/dist/protocol/tunnel-bulk.js.map +1 -0
- package/dist/protocol/x402.d.ts +17 -0
- package/dist/protocol/x402.d.ts.map +1 -0
- package/dist/protocol/x402.js +37 -0
- package/dist/protocol/x402.js.map +1 -0
- package/dist/schemas/activity.d.ts.map +1 -1
- package/dist/schemas/activity.js.map +1 -1
- package/dist/schemas/agent.d.ts +25 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +14 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +90 -18
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +49 -5
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +23 -6
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/devices.d.ts +134 -7
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +43 -18
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +26 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/extension-updates.js +4 -0
- package/dist/schemas/extension-updates.js.map +1 -1
- package/dist/schemas/git/git.d.ts +19 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +1 -0
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +35 -3
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +22 -6
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +2 -12
- package/dist/schemas/keep-warm.d.ts.map +1 -1
- package/dist/schemas/keep-warm.js +8 -10
- package/dist/schemas/keep-warm.js.map +1 -1
- package/dist/schemas/metrics.d.ts +29 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +19 -3
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/offload.d.ts +62 -0
- package/dist/schemas/offload.d.ts.map +1 -0
- package/dist/schemas/offload.js +23 -0
- package/dist/schemas/offload.js.map +1 -0
- package/dist/schemas/personas.d.ts +3 -0
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +12 -3
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/ports.d.ts +10 -0
- package/dist/schemas/ports.d.ts.map +1 -1
- package/dist/schemas/ports.js +8 -0
- package/dist/schemas/ports.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +169 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +54 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +69 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +6 -1
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +55 -0
- package/dist/schemas/settings-history.d.ts.map +1 -0
- package/dist/schemas/settings-history.js +73 -0
- package/dist/schemas/settings-history.js.map +1 -0
- package/dist/schemas/settings.d.ts +188 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +41 -26
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/speaker.d.ts +36 -0
- package/dist/schemas/speaker.d.ts.map +1 -0
- package/dist/schemas/speaker.js +62 -0
- package/dist/schemas/speaker.js.map +1 -0
- package/dist/schemas/state-plan.d.ts +46 -0
- package/dist/schemas/state-plan.d.ts.map +1 -0
- package/dist/schemas/state-plan.js +31 -0
- package/dist/schemas/state-plan.js.map +1 -0
- package/dist/schemas/system.d.ts +64 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +12 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace/mainline.d.ts +513 -0
- package/dist/schemas/workspace/mainline.d.ts.map +1 -0
- package/dist/schemas/workspace/mainline.js +182 -0
- package/dist/schemas/workspace/mainline.js.map +1 -0
- package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +34 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/contract-lock.d.ts +1 -0
- package/dist/state/contract-lock.d.ts.map +1 -1
- package/dist/state/contract-lock.js +47 -1
- package/dist/state/contract-lock.js.map +1 -1
- package/dist/state/definition.d.ts +44 -20
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/definition.js.map +1 -1
- package/dist/state/fix-stance.d.ts +1 -1
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +2 -1
- package/dist/state/fix-stance.js.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +7 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/landed-commit.d.ts +1 -0
- package/dist/state/landed-commit.d.ts.map +1 -1
- package/dist/state/landed-commit.js +2 -0
- package/dist/state/landed-commit.js.map +1 -1
- package/dist/state/red-streak.d.ts +7 -0
- package/dist/state/red-streak.d.ts.map +1 -0
- package/dist/state/red-streak.js +11 -0
- package/dist/state/red-streak.js.map +1 -0
- package/dist/state/runtime-state.d.ts +3 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +23 -1
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +33 -3
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/shell-regions.d.ts +1 -0
- package/dist/text/shell-regions.d.ts.map +1 -1
- package/dist/text/shell-regions.js +1 -1
- package/dist/text/shell-regions.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +1 -0
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +24 -8
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +42 -42
- package/src/chores/chores.ts +6 -6
- package/src/chores/index.ts +0 -1
- package/src/chores/prompt.ts +3 -2
- package/src/contracts/agent.contract.ts +14 -0
- package/src/contracts/agents.contract.ts +36 -1
- package/src/contracts/capabilities.contract.ts +2 -2
- package/src/contracts/device.contract.ts +5 -3
- package/src/contracts/offload.contract.ts +60 -0
- package/src/contracts/runner.contract.ts +7 -1
- package/src/contracts/secrets.contract.ts +2 -0
- package/src/contracts/settings.contract.ts +2 -1
- package/src/contracts/workspace.contract.ts +54 -2
- package/src/documents/conversions.ts +402 -0
- package/src/documents/index.ts +7 -0
- package/src/documents/passthrough.ts +139 -0
- package/src/documents/read.ts +114 -0
- package/src/events/agent-events.ts +5 -0
- package/src/events/child-run.test.ts +81 -0
- package/src/events/child-run.ts +73 -0
- package/src/events/errands.test.ts +22 -0
- package/src/events/errands.ts +27 -0
- package/src/events/land-breakage.ts +12 -0
- package/src/events/land-conflict.ts +12 -0
- package/src/events/requests.ts +40 -1
- package/src/events/system-events.ts +9 -3
- package/src/events/transcript.ts +8 -0
- package/src/events/verify-nudge.ts +4 -3
- package/src/front/browser-wire.ts +20 -0
- package/src/front/front-wire.ts +15 -0
- package/src/front/generated/browser-wire.json +138 -0
- package/src/front/generated/browser-wire.ts +16 -0
- package/src/front/generated/front-wire.json +793 -0
- package/src/front/generated/tunnel.json +38 -0
- package/src/front/generated/wire.ts +37 -9
- package/src/front/wire-manifests.test.ts +61 -0
- package/src/ids/conversation-ids.ts +13 -5
- package/src/ids/session-names.ts +7 -0
- package/src/index.ts +15 -2
- package/src/models/agent-catalog.test.ts +6 -4
- package/src/models/agent-catalog.ts +4 -6
- package/src/models/agent-runtimes.ts +22 -7
- package/src/models/capability-ledger.test.ts +2 -0
- package/src/models/plan-pools.test.ts +121 -2
- package/src/models/plan-pools.ts +152 -2
- package/src/policy/request-status.ts +4 -12
- package/src/policy/reserved-servers.test.ts +6 -0
- package/src/policy/reserved-servers.ts +13 -10
- package/src/policy/turned-away.ts +3 -0
- package/src/protocol/container-requirements.test.ts +2 -3
- package/src/protocol/container-requirements.ts +1 -7
- package/src/protocol/edge-verdict.ts +13 -11
- package/src/protocol/ingress-contract.fixture.json +90 -0
- package/src/protocol/ingress-contract.test.ts +18 -26
- package/src/protocol/ingress-contract.ts +10 -25
- package/src/protocol/raw-routes.ts +22 -26
- package/src/protocol/route-meta.ts +39 -0
- package/src/protocol/runner-protocol.ts +53 -0
- package/src/protocol/tunnel-bulk.test.ts +37 -0
- package/src/protocol/tunnel-bulk.ts +7 -0
- package/src/protocol/x402.test.ts +29 -0
- package/src/protocol/x402.ts +61 -0
- package/src/schemas/activity.ts +3 -1
- package/src/schemas/agent.ts +28 -3
- package/src/schemas/agents.ts +83 -8
- package/src/schemas/automations.ts +4 -2
- package/src/schemas/devices.ts +106 -39
- package/src/schemas/extension-updates.ts +6 -0
- package/src/schemas/git/git.ts +5 -0
- package/src/schemas/hosts.ts +64 -13
- package/src/schemas/ic-shape-args.test.ts +64 -0
- package/src/schemas/keep-warm.test.ts +6 -13
- package/src/schemas/keep-warm.ts +15 -20
- package/src/schemas/metrics.ts +28 -3
- package/src/schemas/offload.ts +44 -0
- package/src/schemas/personas.ts +18 -3
- package/src/schemas/ports.ts +10 -0
- package/src/schemas/providers/plan-limits.ts +84 -1
- package/src/schemas/providers/provider-oauth.ts +10 -1
- package/src/schemas/settings-history.ts +108 -0
- package/src/schemas/settings.test.ts +54 -1
- package/src/schemas/settings.ts +88 -49
- package/src/schemas/speaker.ts +89 -0
- package/src/schemas/state-plan.test.ts +69 -0
- package/src/schemas/state-plan.ts +47 -0
- package/src/schemas/system.ts +24 -3
- package/src/schemas/workspace/mainline.test.ts +28 -0
- package/src/schemas/workspace/mainline.ts +268 -0
- package/src/schemas/workspace/workspace-tree.ts +47 -0
- package/src/state/contract-lock.test.ts +65 -4
- package/src/state/contract-lock.ts +70 -2
- package/src/state/definition.ts +3 -2
- package/src/state/fix-stance.test.ts +8 -0
- package/src/state/fix-stance.ts +3 -3
- package/src/state/history-state.ts +19 -0
- package/src/state/landed-commit.test.ts +12 -1
- package/src/state/landed-commit.ts +5 -0
- package/src/state/red-streak.test.ts +28 -0
- package/src/state/red-streak.ts +28 -0
- package/src/state/runtime-state.ts +4 -0
- package/src/state/workspace-state.test.ts +7 -1
- package/src/state/workspace-state.ts +45 -3
- package/src/text/shell-regions.test.ts +38 -0
- package/src/text/shell-regions.ts +3 -3
- package/src/text/transcript-fold.test.ts +46 -0
- package/src/text/transcript-fold.ts +31 -9
- package/dist/chores/fix-deps.d.ts +0 -10
- package/dist/chores/fix-deps.d.ts.map +0 -1
- package/dist/chores/fix-deps.js +0 -18
- package/dist/chores/fix-deps.js.map +0 -1
- package/dist/protocol/ingress-protocol.d.ts +0 -30
- package/dist/protocol/ingress-protocol.d.ts.map +0 -1
- package/dist/protocol/ingress-protocol.js +0 -300
- package/dist/protocol/ingress-protocol.js.map +0 -1
- package/dist/protocol/terminal-protocol.d.ts +0 -18
- package/dist/protocol/terminal-protocol.d.ts.map +0 -1
- package/dist/protocol/terminal-protocol.js +0 -2
- package/dist/protocol/terminal-protocol.js.map +0 -1
- package/dist/protocol/tunnel-heartbeat.d.ts +0 -19
- package/dist/protocol/tunnel-heartbeat.d.ts.map +0 -1
- package/dist/protocol/tunnel-heartbeat.js +0 -41
- package/dist/protocol/tunnel-heartbeat.js.map +0 -1
- package/dist/protocol/upgrade-relay.d.ts +0 -4
- package/dist/protocol/upgrade-relay.d.ts.map +0 -1
- package/dist/protocol/upgrade-relay.js +0 -25
- package/dist/protocol/upgrade-relay.js.map +0 -1
- package/src/chores/fix-deps.ts +0 -22
- package/src/protocol/ingress-protocol.test.ts +0 -579
- package/src/protocol/ingress-protocol.ts +0 -469
- package/src/protocol/terminal-protocol.ts +0 -13
- package/src/protocol/tunnel-heartbeat.test.ts +0 -75
- package/src/protocol/tunnel-heartbeat.ts +0 -67
- package/src/protocol/upgrade-relay.ts +0 -28
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { generateKeyPairSync } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { hostOwnerId, type ReachabilityGrant, mintReachabilityGrant, verifyReachabilityGrant } from "./ingress-contract.js";
|
|
4
|
+
|
|
5
|
+
// The edge's rules as both implementations must answer them; the Rust edge reads the same file. Its grants were minted
|
|
6
|
+
// by a throwaway pair whose private half was never kept, so a new grant case takes a new pair and every token again.
|
|
7
|
+
const FIXTURE = JSON.parse(readFileSync(new URL("./ingress-contract.fixture.json", import.meta.url), "utf8")) as {
|
|
8
|
+
readonly publicKey: string;
|
|
9
|
+
readonly grants: readonly { readonly token: string; readonly claim: ReachabilityGrant | null }[];
|
|
10
|
+
readonly owners: readonly { readonly host: string; readonly owner: string | null }[];
|
|
11
|
+
};
|
|
3
12
|
|
|
4
13
|
const pemPair = (): { privateKey: string; publicKey: string } => {
|
|
5
14
|
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
@@ -31,10 +40,11 @@ describe("reachability grant", () => {
|
|
|
31
40
|
expect(verifyReachabilityGrant(keys.publicKey, `${prefix}.${forged}.${signature}`)).toBeUndefined();
|
|
32
41
|
});
|
|
33
42
|
|
|
34
|
-
it("answers
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
it("answers every grant in the shared fixture as it states, and garbage with undefined rather than a throw", () => {
|
|
44
|
+
// Both verdicts, so the loop below can never pass by holding only one kind of case.
|
|
45
|
+
expect(new Set(FIXTURE.grants.map(({ claim }) => (claim === null ? "refused" : "accepted")))).toEqual(new Set(["accepted", "refused"]));
|
|
46
|
+
for (const { token, claim } of FIXTURE.grants) {
|
|
47
|
+
expect({ token, claim: verifyReachabilityGrant(FIXTURE.publicKey, token) ?? null }).toEqual({ token, claim });
|
|
38
48
|
}
|
|
39
49
|
});
|
|
40
50
|
|
|
@@ -44,27 +54,9 @@ describe("reachability grant", () => {
|
|
|
44
54
|
});
|
|
45
55
|
|
|
46
56
|
describe("hostOwnerId", () => {
|
|
47
|
-
it("owns the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(hostOwnerId(`port-0f0f0f0f0f0f-${SANDBOX_ID}.sbx.example.dev`)).toBe(SANDBOX_ID);
|
|
51
|
-
expect(hostOwnerId(`public-1a2b3c4d5e6f-${SANDBOX_ID}.sbx.example.dev`)).toBe(SANDBOX_ID);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("ignores a port suffix", () => {
|
|
55
|
-
expect(hostOwnerId(`sandbox-${SANDBOX_ID}.sbx.example.dev:443`)).toBe(SANDBOX_ID);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("owns nothing that does not end in a 12-hex tail", () => {
|
|
59
|
-
// The ingress's own door, the zone apex, the loopback name's bare-id label, and a near-miss tail.
|
|
60
|
-
for (const host of [
|
|
61
|
-
"ingress.sbx.example.dev",
|
|
62
|
-
"sbx.example.dev",
|
|
63
|
-
`${SANDBOX_ID}.local.sbx.example.dev`,
|
|
64
|
-
"sandbox-abc123def45.sbx.example.dev",
|
|
65
|
-
"",
|
|
66
|
-
]) {
|
|
67
|
-
expect(hostOwnerId(host)).toBeUndefined();
|
|
57
|
+
it("owns every host in the shared fixture as it states, port or not", () => {
|
|
58
|
+
for (const { host, owner } of FIXTURE.owners) {
|
|
59
|
+
expect({ host, owner: hostOwnerId(host) ?? null }).toEqual({ host, owner });
|
|
68
60
|
}
|
|
69
61
|
});
|
|
70
62
|
});
|
|
@@ -63,8 +63,9 @@ export const hostOwnerId = (host: string): string | undefined => {
|
|
|
63
63
|
|
|
64
64
|
// Wire constants
|
|
65
65
|
|
|
66
|
-
// Tunnel door on the ingress; versioned so a
|
|
67
|
-
|
|
66
|
+
// Tunnel door on the ingress (the tunnel crate's TUNNEL_PATH); versioned, so a new session shape adds a path. `/tunnel/v1`
|
|
67
|
+
// is the edge's alone to answer now, for fronts that predate this one.
|
|
68
|
+
export const INGRESS_TUNNEL_PATH = "/tunnel/v2";
|
|
68
69
|
|
|
69
70
|
// Grant travels as a header on the tunnel upgrade; this connection is never a browser's.
|
|
70
71
|
export const INGRESS_GRANT_HEADER = "x-intentic-grant";
|
|
@@ -73,32 +74,16 @@ export const INGRESS_GRANT_HEADER = "x-intentic-grant";
|
|
|
73
74
|
export const ENV_INGRESS_URL = "INGRESS_URL";
|
|
74
75
|
export const ENV_SANDBOX_GRANT = "SANDBOX_GRANT";
|
|
75
76
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
// Daemon-side tunnel behavior this contract requires: dial, register, forward to the loopback listener, and reconnect
|
|
79
|
-
// forever with backoff, never giving up. `close()` is for shutdown and tests.
|
|
80
|
-
export interface IngressTunnelOptions {
|
|
81
|
-
// e.g. https://ingress.<zone>; the daemon derives the wss:// door itself via INGRESS_TUNNEL_PATH.
|
|
82
|
-
readonly url: string;
|
|
83
|
-
readonly grant: string;
|
|
84
|
-
// The daemon's own loopback listener; every h2 stream lands there as a plain HTTP/1.1 request or upgrade.
|
|
85
|
-
readonly targetPort: number;
|
|
86
|
-
readonly log: (message: string, error?: unknown) => void;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface IngressTunnelHandle {
|
|
90
|
-
readonly close: () => Promise<void>;
|
|
91
|
-
// For /health and the boot log: whether the tunnel currently holds a registered session.
|
|
92
|
-
readonly connected: () => boolean;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export type StartIngressTunnel = (options: IngressTunnelOptions) => IngressTunnelHandle;
|
|
96
|
-
|
|
97
|
-
// What the ingress side must also honor, enforced by ingress-protocol tests:
|
|
77
|
+
// What the edge (_platform/ingress, Rust) must also honor, held by its suites and by ingress-contract.fixture.json:
|
|
98
78
|
// - Register: verify the grant offline; if PLATFORM_URL is set, check the sandbox exists (GET /api/reachability/<id>)
|
|
99
79
|
// and cache the answer, failing open if the platform doesn't respond. A 404 refuses the tunnel.
|
|
100
80
|
// - Displacement: a new tunnel for an id closes the old session (code 4001) and takes the registration.
|
|
101
|
-
// -
|
|
81
|
+
// - Streams: every exchange is a stream of its own carrying HTTP/1.1 (yamux over a WebSocket, or QUIC). Over TCP a
|
|
82
|
+
// transfer rides the bulk socket: a preview's request, or a route the front announced (tunnel-bulk.ts); over QUIC a
|
|
83
|
+
// stream that sends a megabyte without pausing yields to the others.
|
|
84
|
+
// - Liveness: the front pings every 15s; a peer silent for 45s is unregistered.
|
|
102
85
|
// - Routing: host maps via hostOwnerId to a registered tunnel; no tunnel answers 502 naming the sandbox label, with
|
|
103
86
|
// the verdict header and CORS of edge-verdict.ts, and admits the preflight of the request it is about to refuse.
|
|
104
87
|
// - The tunnel door and any host without a sandbox id are served directly by the ingress, never routed.
|
|
88
|
+
// - `/health` lists the tunnel doors the build serves (`"doors":["/tunnel/v1","/tunnel/v2"]`); a sandbox image moves
|
|
89
|
+
// onto a tag sandboxes pull only once the live edge lists INGRESS_TUNNEL_PATH (_tools/scripts/image/require-edge-door.sh).
|
|
@@ -8,26 +8,26 @@ import { runnerTranslatorPath } from "./runner-protocol.js";
|
|
|
8
8
|
export const RAW_ROUTES = {
|
|
9
9
|
// The "is a daemon there" probe every flow uses; it names the sandbox and says whether it is still converging.
|
|
10
10
|
"GET /health": { auth: "door", beforeBoot: true },
|
|
11
|
-
"GET /diff/raw": {},
|
|
11
|
+
"GET /diff/raw": { lane: "bulk" },
|
|
12
12
|
// Arming the dictation model is a read any tier makes; dictating is writing a message, the collaborator's grant.
|
|
13
13
|
"GET /speech/status": { guest: true },
|
|
14
14
|
"POST /speech/transcribe": { floor: "collaborator", guest: true },
|
|
15
15
|
// The bytes behind the workspace reads; each applies the caller's fence itself.
|
|
16
|
-
"GET /workspace/raw": { guest: true },
|
|
16
|
+
"GET /workspace/raw": { guest: true, lane: "bulk" },
|
|
17
17
|
// No door, unlike media: the page fetches a thumbnail itself, so the request carries the header.
|
|
18
18
|
"GET /workspace/thumb": { guest: true },
|
|
19
19
|
// Fetched by a <video>/<audio> tag, which sends no header: its scoped ticket is checked in the handler.
|
|
20
|
-
"GET /workspace/media": { auth: "door", guest: true },
|
|
20
|
+
"GET /workspace/media": { auth: "door", guest: true, lane: "bulk" },
|
|
21
21
|
// An attachment is part of the message it rides with; any other target edits the shared tree.
|
|
22
|
-
"POST /workspace/upload": { floor: "writer", attachmentFloor: "collaborator" },
|
|
22
|
+
"POST /workspace/upload": { floor: "writer", attachmentFloor: "collaborator", lane: "bulk" },
|
|
23
23
|
"POST /workspace/upload-diff": {},
|
|
24
|
-
"POST /workspace/upload-archive": {},
|
|
24
|
+
"POST /workspace/upload-archive": { lane: "bulk" },
|
|
25
25
|
// Minting is cheap: each WebSocket upgrade floors its own redemption.
|
|
26
26
|
"POST /system/ws-ticket": { beforeBoot: true, floor: "collaborator", control: "never" },
|
|
27
27
|
// A WebSocket upgrade carries no Authorization header; the terminal and both browser wires check a query ticket.
|
|
28
|
-
"GET /system/terminal": { auth: "door", beforeBoot: true, control: "never" },
|
|
28
|
+
"GET /system/terminal": { auth: "door", beforeBoot: true, control: "never", front: true },
|
|
29
29
|
// Desktop sync's byte pipe, guarded again by sshd's own key check against the same enrollment.
|
|
30
|
-
"GET /system/sync/ssh": { sync: "pipe", control: "never" },
|
|
30
|
+
"GET /system/sync/ssh": { sync: "pipe", control: "never", lane: "bulk" },
|
|
31
31
|
"GET /system/browser-profile": { auth: "door", beforeBoot: true, control: "never" },
|
|
32
32
|
"GET /system/browser-view": { auth: "door", beforeBoot: true, control: "never" },
|
|
33
33
|
// Deploy-target enrollment, gated by the connect token.
|
|
@@ -68,7 +68,7 @@ export const RAW_ROUTES = {
|
|
|
68
68
|
"DELETE /bundles": { control: "never" },
|
|
69
69
|
"POST /bundles/ticket": { control: "never" },
|
|
70
70
|
// Navigated to by the browser, so no header: its ticket is checked in the handler.
|
|
71
|
-
"GET /bundles/download": { auth: "door", control: "never" },
|
|
71
|
+
"GET /bundles/download": { auth: "door", control: "never", lane: "bulk" },
|
|
72
72
|
"GET /definition": {},
|
|
73
73
|
"POST /definition/diff": {},
|
|
74
74
|
"GET /definition/workspace": {},
|
|
@@ -78,7 +78,7 @@ export const RAW_ROUTES = {
|
|
|
78
78
|
"POST /arrivals/scan": {},
|
|
79
79
|
"POST /arrivals/apply": {},
|
|
80
80
|
"DELETE /arrivals": {},
|
|
81
|
-
"GET /extensions/{id}/bundle": {},
|
|
81
|
+
"GET /extensions/{id}/bundle": { lane: "bulk" },
|
|
82
82
|
// An extension backend's own namespace, proxied verbatim.
|
|
83
83
|
"ALL /x/*": {},
|
|
84
84
|
// The `capabilities` CLI: discovery by name, and the ask that parks on an owner-decided card.
|
|
@@ -102,11 +102,12 @@ export const RAW_ROUTES = {
|
|
|
102
102
|
"GET /fleet": { agent: true },
|
|
103
103
|
"POST /fleet/message": { agent: true },
|
|
104
104
|
"GET /fleet/{handle}": { agent: true },
|
|
105
|
-
// An extension gateway's realtime-listener control,
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
"POST /listeners/{provider}/
|
|
109
|
-
"POST /listeners/{provider}/
|
|
105
|
+
// An extension gateway's realtime-listener control: /state hands back its connectors' stored credentials, so only the
|
|
106
|
+
// extension token of the extension declaring that `listener.provider` reaches them, checked in the handler too.
|
|
107
|
+
"GET /listeners/{provider}/state": { floor: "maintainer", panel: false, control: "never" },
|
|
108
|
+
"POST /listeners/{provider}/dispatch": { panel: false, control: "never" },
|
|
109
|
+
"POST /listeners/{provider}/failure": { panel: false, control: "never" },
|
|
110
|
+
"POST /listeners/{provider}/status": { panel: false, control: "never" },
|
|
110
111
|
// The CI webhook receiver, gated by the per-sandbox webhook secret.
|
|
111
112
|
"POST /ci/webhook/{host}": { auth: "door" },
|
|
112
113
|
// Below maintainer a pairing is capped to port-mirror, so a collaborator may mint a preview tunnel.
|
|
@@ -118,33 +119,28 @@ export const RAW_ROUTES = {
|
|
|
118
119
|
"DELETE /system/hosts/{id}": { control: "never" },
|
|
119
120
|
// A peer's socket reconnects on its own backoff, so it answers before boot.
|
|
120
121
|
"GET /system/hosts/connect": { auth: "door", beforeBoot: true, control: "never" },
|
|
121
|
-
// The MCP bridge onto a peer, carrying the per-boot bridge token its handler checks.
|
|
122
|
-
"POST /mcp/hosts/{id}": { auth: "door" },
|
|
123
|
-
"GET /mcp/hosts/{id}": { auth: "door" },
|
|
124
|
-
"DELETE /mcp/hosts/{id}": { auth: "door" },
|
|
125
122
|
"POST /system/webext/pair": { control: "never" },
|
|
126
123
|
"POST /system/webext/enroll": { auth: "door", control: "never" },
|
|
127
124
|
"GET /system/webext": { control: "never" },
|
|
128
125
|
"DELETE /system/webext/{id}": { control: "never" },
|
|
129
126
|
"GET /system/webext/connect": { auth: "door", beforeBoot: true, control: "never" },
|
|
130
|
-
"POST /mcp/webext/{id}": { auth: "door" },
|
|
131
|
-
"GET /mcp/webext/{id}": { auth: "door" },
|
|
132
|
-
"DELETE /mcp/webext/{id}": { auth: "door" },
|
|
133
127
|
"POST /system/runners/pair": { control: "never" },
|
|
134
128
|
"POST /system/runners/enroll": { auth: "door", control: "never" },
|
|
135
129
|
"GET /system/runners": { control: "never" },
|
|
136
130
|
"DELETE /system/runners/{id}": { control: "never" },
|
|
137
131
|
"GET /system/runners/connect": { auth: "door", beforeBoot: true, control: "never" },
|
|
138
|
-
//
|
|
139
|
-
|
|
132
|
+
// Every MCP server the daemon hosts for a turn (its browser routers, the machines and browsers it was granted, its
|
|
133
|
+
// extension cards' endpoints), by server name: the handler checks the conversation's mount bearer the turn's tool
|
|
134
|
+
// config carries, and that the running turn mounted that name.
|
|
135
|
+
"ALL /mcp/{mount}": { auth: "door", control: "never" },
|
|
140
136
|
// A connected browser's credential doors, on its durable token: a sign-in moves in (`session`) or out (`lend`).
|
|
141
137
|
"POST /system/webext/session": { auth: "door", control: "never" },
|
|
142
138
|
"POST /system/webext/lend": { auth: "door", control: "never" },
|
|
143
139
|
"POST /system/runners/{id}/definition/sync": { control: "never" },
|
|
144
140
|
// A runner's git door and credential doors, each on the runner's own bearer.
|
|
145
|
-
"GET /system/runners/git/{repo}/info/refs": { auth: "door", control: "never" },
|
|
146
|
-
"POST /system/runners/git/{repo}/git-upload-pack": { auth: "door", control: "never" },
|
|
147
|
-
"POST /system/runners/git/{repo}/git-receive-pack": { auth: "door", control: "never" },
|
|
141
|
+
"GET /system/runners/git/{repo}/info/refs": { auth: "door", control: "never", lane: "bulk" },
|
|
142
|
+
"POST /system/runners/git/{repo}/git-upload-pack": { auth: "door", control: "never", lane: "bulk" },
|
|
143
|
+
"POST /system/runners/git/{repo}/git-receive-pack": { auth: "door", control: "never", lane: "bulk" },
|
|
148
144
|
"POST /system/runners/credentials": { auth: "door", control: "never" },
|
|
149
145
|
"POST /system/runners/credentials/refresh": { auth: "door", control: "never" },
|
|
150
146
|
[`ALL ${runnerTranslatorPath}/*` as const]: { auth: "door", control: "never" },
|
|
@@ -33,7 +33,46 @@ export interface RouteMeta {
|
|
|
33
33
|
// Withheld from the panel token, which reaches every other route: it puts a stored credential in motion.
|
|
34
34
|
readonly panel?: false;
|
|
35
35
|
readonly control?: ControlReach;
|
|
36
|
+
// A transfer: announced to the edge through the front (tunnel-bulk.ts), which sends it down the tunnel's bulk socket,
|
|
37
|
+
// off the connection every keystroke and call waits on.
|
|
38
|
+
readonly lane?: "bulk";
|
|
39
|
+
// Served by intentic-front itself (_sandbox/front, term/): the daemon only answers the front's question about it.
|
|
40
|
+
readonly front?: true;
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
// The floor a route gets when it declares none: a read (GET, HEAD) floors at viewer, anything else (`ALL` included) at
|
|
44
|
+
// maintainer, so a route nobody classified can under-serve, never over-grant.
|
|
45
|
+
export const defaultFloor = (method: string): MemberRole => (method === "GET" || method === "HEAD" ? "viewer" : "maintainer");
|
|
46
|
+
|
|
47
|
+
// The fields that say how a request travels, not who may send it; every other RouteMeta field decides reach.
|
|
48
|
+
type TransportField = "lane" | "front";
|
|
49
|
+
|
|
50
|
+
// Every field that decides who reaches a route, with its default resolved, so an absent field and a declared one read
|
|
51
|
+
// alike and loosening either is a changed value: what contract.lock.json records per route (`access:METHOD /path`),
|
|
52
|
+
// where the lock check and the push gate see it. Keyed by the interface: a RouteMeta field added above fails this
|
|
53
|
+
// package's typecheck until it is read here or named a TransportField.
|
|
54
|
+
const ACCESS = {
|
|
55
|
+
auth: (meta) => meta.auth ?? "session",
|
|
56
|
+
embedded: (meta) => meta.embedded === true,
|
|
57
|
+
beforeBoot: (meta) => meta.beforeBoot === true,
|
|
58
|
+
stream: (meta) => meta.stream === true,
|
|
59
|
+
floor: (meta, method) => meta.floor ?? defaultFloor(method),
|
|
60
|
+
guest: (meta) => meta.guest === true,
|
|
61
|
+
attachmentFloor: (meta) => meta.attachmentFloor ?? "none",
|
|
62
|
+
enrolment: (meta) => meta.enrolment === true,
|
|
63
|
+
agent: (meta) => meta.agent === true,
|
|
64
|
+
sync: (meta) => meta.sync ?? "none",
|
|
65
|
+
panel: (meta) => meta.panel !== false,
|
|
66
|
+
// `floor`: no reach of its own, the control rungs read the route's floor alone.
|
|
67
|
+
control: (meta) => meta.control ?? "floor",
|
|
68
|
+
} as const satisfies { readonly [K in Exclude<keyof RouteMeta, TransportField>]-?: (meta: RouteMeta, method: string) => string | boolean };
|
|
69
|
+
|
|
70
|
+
export type RouteAccess = { readonly [K in keyof typeof ACCESS]: ReturnType<(typeof ACCESS)[K]> };
|
|
71
|
+
|
|
72
|
+
// One route's reach as its declaration states it, every default filled in.
|
|
73
|
+
export const routeAccess = (method: string, meta: RouteMeta): RouteAccess =>
|
|
74
|
+
// SAFETY: every entry is one of ACCESS's own keys paired with what its reader returned, which RouteAccess spells.
|
|
75
|
+
Object.fromEntries(Object.entries(ACCESS).map(([field, read]) => [field, read(meta, method)])) as RouteAccess;
|
|
76
|
+
|
|
38
77
|
// The builder every sandbox procedure starts from, so each carries a RouteMeta and whatever it leaves out defaults.
|
|
39
78
|
export const procedure = oc.$meta<RouteMeta>({});
|
|
@@ -188,6 +188,59 @@ export const RunnerTurnSchema = z.object({
|
|
|
188
188
|
});
|
|
189
189
|
export type RunnerTurn = z.infer<typeof RunnerTurnSchema>;
|
|
190
190
|
|
|
191
|
+
// OFFLOADED COMMANDS: one shell line the parent hands a runner instead of running it itself (settings `offload`). The
|
|
192
|
+
// code travels as a snapshot commit of the parent's working tree, uncommitted and untracked work included, parked under
|
|
193
|
+
// this ref in the repo's git dir for the runner to fetch through the git door, and deleted once the run ends.
|
|
194
|
+
export const OFFLOAD_REF_PREFIX = "refs/intentic-offload/";
|
|
195
|
+
export const offloadRef = (runId: string): string => `${OFFLOAD_REF_PREFIX}${runId}`;
|
|
196
|
+
const OffloadRunIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{7,63}$/u);
|
|
197
|
+
const EnvNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
|
|
198
|
+
|
|
199
|
+
export const RunnerCommandSchema = z.object({
|
|
200
|
+
runId: OffloadRunIdSchema,
|
|
201
|
+
// The repo id the git door serves (a /history/gits entry: `root` for the workspace itself).
|
|
202
|
+
repo: z.string().min(1),
|
|
203
|
+
// The snapshot commit's ref in that repo, always under OFFLOAD_REF_PREFIX.
|
|
204
|
+
ref: z.string().startsWith(OFFLOAD_REF_PREFIX),
|
|
205
|
+
// Where in the repo the line runs, repo-relative; "" is the repo root.
|
|
206
|
+
cwd: z.string().refine((path) => !path.startsWith("/") && !path.split("/").includes(".."), "must stay inside the repo"),
|
|
207
|
+
command: z.string().min(1).max(64_000),
|
|
208
|
+
// Values set for the line, beside the runner's own environment. Never a secret: a line that needs one runs here.
|
|
209
|
+
env: z.record(EnvNameSchema, z.string()).default({}),
|
|
210
|
+
// Variables the line writes a file to (a report path): the runner points each at a scratch file and hands the file
|
|
211
|
+
// back with the exit.
|
|
212
|
+
exports: z.array(EnvNameSchema).max(8).default([]),
|
|
213
|
+
// The heavy-command rule it matched here, named in the runner's own output and queue.
|
|
214
|
+
label: z.string().min(1).max(80),
|
|
215
|
+
timeoutMs: z
|
|
216
|
+
.number()
|
|
217
|
+
.int()
|
|
218
|
+
.positive()
|
|
219
|
+
.max(6 * 60 * 60_000)
|
|
220
|
+
.optional(),
|
|
221
|
+
});
|
|
222
|
+
export type RunnerCommand = z.infer<typeof RunnerCommandSchema>;
|
|
223
|
+
|
|
224
|
+
// What a running command streams back: narration of the runner's own steps (fetch, install, queue), the line's output
|
|
225
|
+
// as it comes, and last its end: the exit, every file it changed as a binary git patch against the snapshot, and the
|
|
226
|
+
// exported files, base64.
|
|
227
|
+
export const RunnerCommandFrameSchema = z.union([
|
|
228
|
+
z.object({ kind: z.literal("status"), text: z.string() }),
|
|
229
|
+
z.object({ kind: z.literal("output"), stream: z.enum(["stdout", "stderr"]), text: z.string() }),
|
|
230
|
+
z.object({
|
|
231
|
+
kind: z.literal("exit"),
|
|
232
|
+
code: z.number().int(),
|
|
233
|
+
signal: z.string().optional(),
|
|
234
|
+
// Why the run did not end on its own: stopped, timed out, or never started (a fetch or install that failed).
|
|
235
|
+
failure: z.string().optional(),
|
|
236
|
+
// False when the line never started there, which sends it back to run on the parent.
|
|
237
|
+
ran: z.boolean().default(true),
|
|
238
|
+
patchBase64: z.string().optional(),
|
|
239
|
+
files: z.record(EnvNameSchema, z.string()).default({}),
|
|
240
|
+
}),
|
|
241
|
+
]);
|
|
242
|
+
export type RunnerCommandFrame = z.infer<typeof RunnerCommandFrameSchema>;
|
|
243
|
+
|
|
191
244
|
// Where a conversation runs, fixed on its first turn like `isolated` placement: later turns follow the registry, not
|
|
192
245
|
// the sender. `runner` implies isolation since a remote conversation is branch-anchored; absent means local.
|
|
193
246
|
export const AgentPlacementSchema = z.union([z.object({ kind: z.literal("local") }), z.object({ kind: z.literal("runner"), id: z.string().min(1) })]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { servedRoute } from "./routes.js";
|
|
3
|
+
import { RAW_ROUTE_LIST } from "./raw-routes.js";
|
|
4
|
+
import { hostOwnerId } from "./ingress-contract.js";
|
|
5
|
+
import { tunnelBulkRoutes } from "./tunnel-bulk.js";
|
|
6
|
+
|
|
7
|
+
// The shared fixture the edge reads too (the tunnel crate's bulk.rs): `bulk` is what the daemon announces, and every
|
|
8
|
+
// case is the lane the daemon's own router gives the request, which the edge's reading of `bulk` must match.
|
|
9
|
+
// SAFETY: the fixture is this repository's own file, and both tests below fail on a key it lacks.
|
|
10
|
+
const FIXTURE = JSON.parse(readFileSync(new URL("./ingress-contract.fixture.json", import.meta.url), "utf8")) as {
|
|
11
|
+
readonly bulk: readonly string[];
|
|
12
|
+
readonly lanes: readonly { readonly host: string; readonly method: string; readonly path: string; readonly lane: "interactive" | "bulk" }[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// A preview's dev server rides bulk; a daemon request rides its route's lane, the route found as the router finds it.
|
|
16
|
+
const laneOf = (host: string, method: string, path: string): "interactive" | "bulk" => {
|
|
17
|
+
const id = hostOwnerId(host);
|
|
18
|
+
const label = host.split(":")[0]?.split(".")[0] ?? "";
|
|
19
|
+
if (id === undefined || label !== `sandbox-${id}`) {
|
|
20
|
+
return "bulk";
|
|
21
|
+
}
|
|
22
|
+
return servedRoute(RAW_ROUTE_LIST, [], method, path)?.meta.lane ?? "interactive";
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
test("the fixture's announcement is what the daemon announces today", () => {
|
|
26
|
+
expect(tunnelBulkRoutes()).toEqual([...FIXTURE.bulk]);
|
|
27
|
+
for (const route of tunnelBulkRoutes()) {
|
|
28
|
+
expect(route).toMatch(/^[A-Z]+ \/\S*$/);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("every request the shared fixture names rides the lane the daemon's router gives it", () => {
|
|
33
|
+
expect(FIXTURE.lanes.length).toBeGreaterThan(20);
|
|
34
|
+
for (const { host, method, path, lane } of FIXTURE.lanes) {
|
|
35
|
+
expect({ host, method, path, lane: laneOf(host, method, path) }).toEqual({ host, method, path, lane });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RAW_ROUTE_LIST } from "./raw-routes.js";
|
|
2
|
+
|
|
3
|
+
// What the daemon announces to the edge as its transfers (RouteMeta `lane`), handed to the front with the tunnel's door
|
|
4
|
+
// and carried on each of its sockets' upgrades (the tunnel crate's `x-intentic-bulk`), so the edge sends a transfer
|
|
5
|
+
// down the bulk socket and a keystroke never queues behind it on one TCP connection. The edge compiles in nothing of it.
|
|
6
|
+
export const tunnelBulkRoutes = (): string[] =>
|
|
7
|
+
RAW_ROUTE_LIST.filter(({ meta }) => meta.lane === "bulk").map(({ method, path }) => `${method} ${path}`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { atomicToUsd, USDC_NETWORKS, usdcNetworkOf, usdToAtomic } from "./x402.js";
|
|
2
|
+
|
|
3
|
+
it("converts USD and atomic units without floats, in both directions", () => {
|
|
4
|
+
expect(usdToAtomic("1.00")).toBe(1_000_000n);
|
|
5
|
+
expect(usdToAtomic("0.1")).toBe(100_000n);
|
|
6
|
+
expect(usdToAtomic("0.000001")).toBe(1n);
|
|
7
|
+
// The classic float trap: 0.1 + 0.2 in binary floating point is not 0.3, and money must not care.
|
|
8
|
+
expect(usdToAtomic("0.1") + usdToAtomic("0.2")).toBe(usdToAtomic("0.3"));
|
|
9
|
+
expect(atomicToUsd(1_000_000n)).toBe("1.00");
|
|
10
|
+
expect(atomicToUsd(100_000n)).toBe("0.10");
|
|
11
|
+
expect(atomicToUsd(1n)).toBe("0.000001");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("reads a bare whole or fraction, cuts past the sixth decimal, and writes zero as cents", () => {
|
|
15
|
+
expect(usdToAtomic("5")).toBe(5_000_000n);
|
|
16
|
+
expect(usdToAtomic(".5")).toBe(500_000n);
|
|
17
|
+
expect(usdToAtomic("0.0000019")).toBe(1n);
|
|
18
|
+
expect(atomicToUsd(0n)).toBe("0.00");
|
|
19
|
+
expect(atomicToUsd(12_345_670n)).toBe("12.34567");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("knows USDC on Base and Base Sepolia by CAIP-2 id, and nothing else", () => {
|
|
23
|
+
expect(USDC_NETWORKS.map(({ network, chainId, asset }) => [network, chainId, asset])).toEqual([
|
|
24
|
+
["eip155:8453", 8453, "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
|
|
25
|
+
["eip155:84532", 84532, "0x036CbD53842c5426634e7929541eC2318f3dCF7e"],
|
|
26
|
+
]);
|
|
27
|
+
expect(usdcNetworkOf("eip155:84532")?.v1Network).toBe("base-sepolia");
|
|
28
|
+
expect(usdcNetworkOf("base")).toBeUndefined();
|
|
29
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The money side of x402 every tier states alike: which USDC contracts a payment may move, and dollars as atomic units.
|
|
2
|
+
|
|
3
|
+
// One chain's USDC: token contract, EIP-712 domain defaults, explorer; an asset not on the list below is refused.
|
|
4
|
+
export interface UsdcNetwork {
|
|
5
|
+
// CAIP-2 ("eip155:8453"), the v2 vocabulary and the wallet config's.
|
|
6
|
+
readonly network: string;
|
|
7
|
+
// How v1 challenges spell the same chain ("base"), matched on parse, echoed on the v1 retry header.
|
|
8
|
+
readonly v1Network: string;
|
|
9
|
+
readonly chainId: number;
|
|
10
|
+
readonly asset: string;
|
|
11
|
+
// EIP-712 domain fallbacks; `extra.{name,version}` on the challenge wins when present.
|
|
12
|
+
readonly domainName: string;
|
|
13
|
+
readonly domainVersion: string;
|
|
14
|
+
readonly label: string;
|
|
15
|
+
readonly explorer: string;
|
|
16
|
+
readonly rpc: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const USDC_NETWORKS: readonly UsdcNetwork[] = [
|
|
20
|
+
{
|
|
21
|
+
network: "eip155:8453",
|
|
22
|
+
v1Network: "base",
|
|
23
|
+
chainId: 8453,
|
|
24
|
+
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
25
|
+
domainName: "USD Coin",
|
|
26
|
+
domainVersion: "2",
|
|
27
|
+
label: "Base",
|
|
28
|
+
explorer: "https://basescan.org/tx/",
|
|
29
|
+
rpc: "https://mainnet.base.org",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
network: "eip155:84532",
|
|
33
|
+
v1Network: "base-sepolia",
|
|
34
|
+
chainId: 84532,
|
|
35
|
+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
36
|
+
domainName: "USDC",
|
|
37
|
+
domainVersion: "2",
|
|
38
|
+
label: "Base Sepolia (test)",
|
|
39
|
+
explorer: "https://sepolia.basescan.org/tx/",
|
|
40
|
+
rpc: "https://sepolia.base.org",
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const usdcNetworkOf = (network: string): UsdcNetwork | undefined => USDC_NETWORKS.find((entry) => entry.network === network);
|
|
45
|
+
|
|
46
|
+
// Every amount is a bigint of atomic units, never a float; USD strings ("1.50") are the display and policy vocabulary.
|
|
47
|
+
export const USDC_DECIMALS = 6n;
|
|
48
|
+
const ATOMIC_PER_USD = 10n ** USDC_DECIMALS;
|
|
49
|
+
|
|
50
|
+
// Digits past the sixth decimal are cut, not rounded: a price is never read as more than was written.
|
|
51
|
+
export const usdToAtomic = (usd: string): bigint => {
|
|
52
|
+
const [whole, fraction = ""] = usd.split(".");
|
|
53
|
+
return BigInt(whole || "0") * ATOMIC_PER_USD + BigInt(fraction.padEnd(6, "0").slice(0, 6) || "0");
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// At least cents ("1.50"), and every non-zero atomic digit after them ("0.000001").
|
|
57
|
+
export const atomicToUsd = (atomic: bigint): string => {
|
|
58
|
+
const whole = atomic / ATOMIC_PER_USD;
|
|
59
|
+
const fraction = (atomic % ATOMIC_PER_USD).toString().padStart(6, "0").replace(/0+$/, "");
|
|
60
|
+
return fraction === "" ? `${whole}.00` : `${whole}.${fraction.padEnd(2, "0")}`;
|
|
61
|
+
};
|
package/src/schemas/activity.ts
CHANGED
|
@@ -20,7 +20,9 @@ export const ActivityEventSchema = z.object({
|
|
|
20
20
|
// in: message.received, voice_utterance.received, voice_transcript.received
|
|
21
21
|
// out: message.send, reaction.add, messages.read, api.call (unclassified endpoint)
|
|
22
22
|
// system: gateway.login_failed, dispatch.failed, voice.session_started, voice.session_ended, automation.run,
|
|
23
|
-
// turn.started, turn.plan, turn.error, turn.completed, rule.
|
|
23
|
+
// turn.started, turn.plan, turn.error, turn.completed, rule.held_work, deps.verify_green, deps.verify_red,
|
|
24
|
+
// deps.breakage_routed, deps.breakage_fixup, deps.breakage_waiting, deps.breakage_held, deps.breakage_resolved,
|
|
25
|
+
// deps.breakage_spent; rule.blocked_push and rule.continued_turn are retired and only appear in older logs
|
|
24
26
|
type: z
|
|
25
27
|
.string()
|
|
26
28
|
.describe(
|
package/src/schemas/agent.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TurnErrandSchema } from "./speaker.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { CONVERSATION_ID } from "../ids/conversation-ids.js";
|
|
3
4
|
import { ModelRoleSchema } from "../models/model-roles.js";
|
|
@@ -129,6 +130,9 @@ export type ForkedFrom = z.infer<typeof ForkedFromSchema>;
|
|
|
129
130
|
// The turn's fields before the cross-field refinements below, so a subset (TurnProfileSchema) can be picked from them.
|
|
130
131
|
const AgentTurnFieldsSchema = z.object({
|
|
131
132
|
prompt: z.string().describe("What to say to the agent. May be empty if you are only attaching files."),
|
|
133
|
+
errand: TurnErrandSchema.optional().describe(
|
|
134
|
+
"What the words are for, when the app or the sandbox composed them rather than a person typing them: a land conflict to resolve, a red main line to fix. Shown as the sandbox's words, not yours. Leave it out for your own words.",
|
|
135
|
+
),
|
|
132
136
|
// Minted by the sender so a send whose answer was lost can be sent again without being delivered twice.
|
|
133
137
|
messageId: z
|
|
134
138
|
.string()
|
|
@@ -164,8 +168,14 @@ const AgentTurnFieldsSchema = z.object({
|
|
|
164
168
|
// Which agentic loop runs the turn, absent = provider's own; "claude-code" forces the SDK loop for any
|
|
165
169
|
// provider.
|
|
166
170
|
harness: AgentHarnessSchema.optional().describe("Which agentic loop runs the turn. Leave it out to use each provider's own."),
|
|
167
|
-
//
|
|
168
|
-
|
|
171
|
+
// Intent, not a guess: absent continues on the conversation's own account, or picks by serviceability where there is
|
|
172
|
+
// none on this provider yet (agent/providers/accounts/routing.ts).
|
|
173
|
+
account: z
|
|
174
|
+
.string()
|
|
175
|
+
.optional()
|
|
176
|
+
.describe(
|
|
177
|
+
"Which of that provider's connected accounts pays for the turn. Leave it out to continue on the account the conversation runs on, or, for its first turn on this provider, to take whichever account can serve with the most room. To move a running conversation, use `switchAccount`.",
|
|
178
|
+
),
|
|
169
179
|
// Distinct from `account` (who pays): whose name the turn posts as; absent means something different for each
|
|
170
180
|
// mode.
|
|
171
181
|
actsAs: entryId.optional().describe("Which persona the turn speaks as out in the world. Not the same as which account pays for it."),
|
|
@@ -317,6 +327,19 @@ export const AgentTurnSchema = AgentTurnFieldsSchema
|
|
|
317
327
|
message: 'forkOf.files "then" requires isolated',
|
|
318
328
|
});
|
|
319
329
|
export type AgentTurn = z.infer<typeof AgentTurnSchema>;
|
|
330
|
+
// What a turn naming no provider or loop runs on, stated once. Applied where a request comes in (the daemon's turn port),
|
|
331
|
+
// so nothing downstream defaults them again: the engine takes a turn with both named (withRuntimeDefaults).
|
|
332
|
+
/** A turn with its provider and loop named, as the engine takes it past the door. */
|
|
333
|
+
export type RoutedAgentTurn = AgentTurn & { readonly agent: AgentProvider; readonly harness: AgentHarness };
|
|
334
|
+
export const DEFAULT_PROVIDER: AgentProvider = "claude";
|
|
335
|
+
export const DEFAULT_HARNESS: AgentHarness = "native";
|
|
336
|
+
export const withRuntimeDefaults = <T extends { readonly agent?: AgentProvider | undefined; readonly harness?: AgentHarness | undefined }>(
|
|
337
|
+
turn: T,
|
|
338
|
+
): T & { readonly agent: AgentProvider; readonly harness: AgentHarness } => ({
|
|
339
|
+
...turn,
|
|
340
|
+
agent: turn.agent ?? DEFAULT_PROVIDER,
|
|
341
|
+
harness: turn.harness ?? DEFAULT_HARNESS,
|
|
342
|
+
});
|
|
320
343
|
// Which turn this is, as opposed to what it says: who serves it and how (agent, harness, account, model and its knobs),
|
|
321
344
|
// as whom (actsAs), where (isolated), whether anyone watches (unattended) and for which job (runRole). A turn that
|
|
322
345
|
// continues another (a resume, a wake, a nudge, a child's follow-up) carries this whole, never a hand-picked part.
|
|
@@ -357,7 +380,9 @@ export const AgentRunPickSchema = z
|
|
|
357
380
|
account: z
|
|
358
381
|
.string()
|
|
359
382
|
.optional()
|
|
360
|
-
.describe(
|
|
383
|
+
.describe(
|
|
384
|
+
"Which connected account of that provider pays, by its daemon-minted id. Leave it out to take whichever account can serve with the most room.",
|
|
385
|
+
),
|
|
361
386
|
harness: AgentHarnessSchema.optional().describe("Which agentic loop runs it. Leave it out to use the provider's own."),
|
|
362
387
|
effort: z
|
|
363
388
|
.string()
|