@intentic/sandbox-contract 1.312.0 → 1.314.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 +26 -3
- 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/contracts/accounts.contract.d.ts +57 -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 +123 -96
- package/dist/contracts/agents.contract.d.ts.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 +11 -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/personas.contract.d.ts +2 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +138 -0
- package/dist/contracts/runner.contract.d.ts.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 +12 -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 +141 -19
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +72 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +85 -14
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/documents/index.d.ts +1 -0
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +1 -0
- package/dist/documents/index.js.map +1 -1
- 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/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 +8 -12
- package/dist/events/system-events.d.ts.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/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 +36 -30
- package/dist/front/generated/wire.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +1 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +2 -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 +1038 -168
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- 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/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 -1
- 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 +18 -40
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +5 -16
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts +20 -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/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/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 +17 -18
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +17 -4
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +4 -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 +22 -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 +3 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/hosts.d.ts +35 -5
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +21 -7
- 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 +24 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +22 -3
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/personas.d.ts +3 -0
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +9 -0
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +140 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +47 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +57 -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 +28 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +35 -1
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +184 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +31 -23
- 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 +48 -8
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +10 -14
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace/mainline.d.ts +206 -64
- package/dist/schemas/workspace/mainline.d.ts.map +1 -1
- package/dist/schemas/workspace/mainline.js +73 -25
- package/dist/schemas/workspace/mainline.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 +24 -16
- package/dist/state/definition.d.ts.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 +2 -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/workspace-state.d.ts +1 -0
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +6 -2
- 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.map +1 -1
- package/dist/text/transcript-fold.js +14 -7
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +28 -29
- package/src/chores/index.ts +0 -1
- package/src/contracts/agent.contract.ts +14 -0
- package/src/contracts/capabilities.contract.ts +2 -2
- package/src/contracts/device.contract.ts +5 -3
- package/src/contracts/secrets.contract.ts +2 -0
- package/src/contracts/settings.contract.ts +2 -1
- package/src/documents/conversions.ts +3 -3
- package/src/documents/index.ts +3 -1
- 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/requests.ts +40 -1
- package/src/events/transcript.ts +8 -0
- 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 +21 -24
- package/src/front/wire-manifests.test.ts +61 -0
- package/src/ids/conversation-ids.ts +6 -5
- package/src/ids/session-names.ts +7 -0
- package/src/index.ts +8 -1
- 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/edge-verdict.ts +13 -11
- package/src/protocol/ingress-contract.fixture.json +13 -0
- package/src/protocol/ingress-contract.ts +9 -5
- package/src/protocol/raw-routes.ts +10 -21
- package/src/protocol/route-meta.ts +36 -1
- 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/agent.ts +28 -3
- package/src/schemas/agents.ts +33 -7
- package/src/schemas/automations.ts +4 -2
- package/src/schemas/devices.ts +107 -39
- package/src/schemas/extension-updates.ts +6 -0
- package/src/schemas/hosts.ts +68 -19
- 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 +32 -3
- package/src/schemas/personas.ts +15 -0
- package/src/schemas/providers/plan-limits.ts +71 -1
- package/src/schemas/providers/provider-oauth.ts +10 -1
- package/src/schemas/settings-history.ts +50 -3
- package/src/schemas/settings.test.ts +54 -1
- package/src/schemas/settings.ts +49 -31
- 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 +20 -16
- package/src/schemas/workspace/mainline.test.ts +28 -0
- package/src/schemas/workspace/mainline.ts +111 -44
- package/src/state/contract-lock.test.ts +65 -4
- package/src/state/contract-lock.ts +70 -2
- package/src/state/fix-stance.test.ts +8 -0
- package/src/state/fix-stance.ts +3 -3
- package/src/state/history-state.ts +5 -1
- 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/workspace-state.test.ts +7 -1
- package/src/state/workspace-state.ts +10 -2
- package/src/text/shell-regions.test.ts +38 -0
- package/src/text/shell-regions.ts +3 -3
- package/src/text/transcript-fold.test.ts +13 -0
- package/src/text/transcript-fold.ts +17 -8
- 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 -19
- package/dist/chores/fix-deps.js.map +0 -1
- package/dist/front/terminal-frames.d.ts +0 -17
- package/dist/front/terminal-frames.d.ts.map +0 -1
- package/dist/front/terminal-frames.js +0 -79
- package/dist/front/terminal-frames.js.map +0 -1
- package/dist/protocol/tunnel-lanes.d.ts +0 -10
- package/dist/protocol/tunnel-lanes.d.ts.map +0 -1
- package/dist/protocol/tunnel-lanes.js +0 -16
- package/dist/protocol/tunnel-lanes.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 -23
- package/src/front/terminal-frames.fixture.json +0 -55
- package/src/front/terminal-frames.test.ts +0 -63
- package/src/front/terminal-frames.ts +0 -102
- package/src/protocol/tunnel-lanes.json +0 -136
- package/src/protocol/tunnel-lanes.test.ts +0 -30
- package/src/protocol/tunnel-lanes.ts +0 -32
- package/src/protocol/upgrade-relay.ts +0 -28
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"close": {
|
|
3
|
+
"away": 1001,
|
|
4
|
+
"displaced": 4001,
|
|
5
|
+
"refused": 4003
|
|
6
|
+
},
|
|
7
|
+
"headers": {
|
|
8
|
+
"bulk": "x-intentic-bulk",
|
|
9
|
+
"grant": "x-intentic-grant",
|
|
10
|
+
"lane": "x-intentic-lane",
|
|
11
|
+
"transports": "x-intentic-transports"
|
|
12
|
+
},
|
|
13
|
+
"lanes": [
|
|
14
|
+
"interactive",
|
|
15
|
+
"bulk"
|
|
16
|
+
],
|
|
17
|
+
"liveness": {
|
|
18
|
+
"deadAfterMs": 45000,
|
|
19
|
+
"pingEveryMs": 15000
|
|
20
|
+
},
|
|
21
|
+
"mux": "yamux",
|
|
22
|
+
"path": "/tunnel/v2",
|
|
23
|
+
"quic": {
|
|
24
|
+
"alpn": "intentic-tunnel/1",
|
|
25
|
+
"hello": {
|
|
26
|
+
"gone": 2,
|
|
27
|
+
"held": 0,
|
|
28
|
+
"refused": 1
|
|
29
|
+
},
|
|
30
|
+
"yieldAfterBytes": 1048576
|
|
31
|
+
},
|
|
32
|
+
"transports": [
|
|
33
|
+
"quic",
|
|
34
|
+
"h3",
|
|
35
|
+
"webtransport"
|
|
36
|
+
],
|
|
37
|
+
"upgrade": "websocket"
|
|
38
|
+
}
|
|
@@ -16,7 +16,9 @@ export type Endpoint = { host: string, port: number, };
|
|
|
16
16
|
/**
|
|
17
17
|
* Everything Node sends the front.
|
|
18
18
|
*/
|
|
19
|
-
export type FromNode = { "kind": "hello", build: string, pid: number, } | { "kind": "listen", config: ListenConfig, } | { "kind": "certificate", certificate?: Certificate, } | { "kind": "tunnel", tunnel?: TunnelConfig, } | { "kind": "
|
|
19
|
+
export type FromNode = { "kind": "hello", build: string, pid: number, } | { "kind": "listen", config: ListenConfig, } | { "kind": "certificate", certificate?: Certificate, } | { "kind": "tunnel", tunnel?: TunnelConfig, } | { "kind": "ask", id: number, question: FrontQuestion, } | { "kind": "answer", id: number, answer: Answer, } | { "kind": "refused", id: number, message: string, } | { "kind": "revoke", member?: string, } | { "kind": "watch", checkout: WatchedCheckout, } | { "kind": "unwatch", dir: string, };
|
|
20
|
+
|
|
21
|
+
export type FrontAnswer = { "answer": "sync", generations: Array<number | null>, };
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Request headers only the front sets on what it forwards to Node; it strips them from everything arriving outside.
|
|
@@ -24,6 +26,11 @@ export type FromNode = { "kind": "hello", build: string, pid: number, } | { "kin
|
|
|
24
26
|
*/
|
|
25
27
|
export type FrontHeader = "x-intentic-preview" | "x-intentic-preview-unreachable";
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A question Node asks the front.
|
|
31
|
+
*/
|
|
32
|
+
export type FrontQuestion = { "question": "sync", dirs: Array<string>, };
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Every port the front binds, as Node's config names them; an absent one is not bound.
|
|
29
36
|
*/
|
|
@@ -42,47 +49,37 @@ frameAncestors: Array<string>,
|
|
|
42
49
|
previewProbePath: string, };
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
export type PreviewRoute = { "to": "upstream", upstream: Upstream, } | { "to": "node" };
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A question the front asks Node; the answer carries the same id.
|
|
52
|
+
* An answer Node renders whole when asked, for the front to write as it stands: a refusal page, or the probe's report.
|
|
51
53
|
*/
|
|
52
|
-
export type
|
|
53
|
-
|
|
54
|
-
export type Scheme = "http" | "https";
|
|
54
|
+
export type Page = { status: number, headers: { [key in string]: string }, body: string, };
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* What
|
|
57
|
+
* What becomes of a preview host's request, decided by Node once, when asked.
|
|
58
58
|
*/
|
|
59
|
-
export type
|
|
59
|
+
export type PreviewRoute = { "to": "upstream", upstream: Upstream, } | { "to": "page", page: Page, } | { "to": "outbox" };
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* A question the front asks Node.
|
|
63
63
|
*/
|
|
64
|
-
export type
|
|
64
|
+
export type Question = { "question": "preview", host: string, probe: boolean, } | { "question": "terminal", query: string, };
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
* What a terminal socket sends as JSON text; the pane's own bytes travel as binary frames.
|
|
68
|
-
*/
|
|
69
|
-
export type TerminalServerMessage = { "type": "exit", code: number, reason?: string, } | { "type": "pong" };
|
|
66
|
+
export type Scheme = "http" | "https";
|
|
70
67
|
|
|
71
68
|
/**
|
|
72
|
-
*
|
|
73
|
-
* as frames (`FrameKind`). Exported as a literal type, as `FrontHeader` is.
|
|
69
|
+
* What a terminal socket opens onto, as Node decided from its ticket, session name and working directory.
|
|
74
70
|
*/
|
|
75
|
-
export type
|
|
71
|
+
export type TerminalPlan = { "plan": "session", name: string, createIn?: string, } | { "plan": "tail", path: string, } | { "plan": "exit", code: number, reason: string, } | { "plan": "refused", code: number, reason: string, };
|
|
76
72
|
|
|
77
73
|
/**
|
|
78
74
|
* Everything the front sends Node.
|
|
79
75
|
*/
|
|
80
|
-
export type ToNode = { "kind": "ask", id: number, question: Question, } | { "kind": "
|
|
76
|
+
export type ToNode = { "kind": "ask", id: number, question: Question, } | { "kind": "answer", id: number, answer: FrontAnswer, } | { "kind": "refused", id: number, message: string, } | { "kind": "tunnel", connected: boolean, };
|
|
81
77
|
|
|
82
78
|
/**
|
|
83
|
-
* The ingress tunnel's door
|
|
79
|
+
* The ingress tunnel's door, the reachability grant it presents there, and the daemon's transfer routes it announces
|
|
80
|
+
* (`METHOD /path` each, as the daemon names them), which the edge sends down the bulk socket.
|
|
84
81
|
*/
|
|
85
|
-
export type TunnelConfig = { url: string, grant: string, };
|
|
82
|
+
export type TunnelConfig = { url: string, grant: string, bulk: Array<string>, };
|
|
86
83
|
|
|
87
84
|
/**
|
|
88
85
|
* A local upstream a preview host relays to.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { EDGE_VERDICT_HEADER, edgeVerdictOf } from "../protocol/edge-verdict.js";
|
|
3
|
+
import { INGRESS_GRANT_HEADER, INGRESS_TUNNEL_PATH } from "../protocol/ingress-contract.js";
|
|
4
|
+
import { EDGE_TRANSPORTS, TERMINAL_PATH, WEBTRANSPORT_PATH } from "./browser-wire.js";
|
|
5
|
+
import { ASK_PATIENCE_MS, FRAME_LENGTH_BYTES, FRONT_SOCKET_ENV, NODE_SOCKET_ENV } from "./front-wire.js";
|
|
6
|
+
|
|
7
|
+
// The manifests `cargo test` writes from the Rust crates that define this wire: every value TypeScript restates is read
|
|
8
|
+
// back against them, so the two languages cannot drift and contract.lock.json pins what both agree on.
|
|
9
|
+
const manifest = (name: string): Record<string, unknown> =>
|
|
10
|
+
JSON.parse(readFileSync(new URL(`./generated/${name}.json`, import.meta.url), "utf8")) as Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
const tunnel = manifest("tunnel") as {
|
|
13
|
+
path: string;
|
|
14
|
+
headers: { grant: string; transports: string };
|
|
15
|
+
transports: string[];
|
|
16
|
+
};
|
|
17
|
+
// SAFETY: front-wire's own test writes this manifest with exactly these keys, and the lock pins them.
|
|
18
|
+
const socket = manifest("front-wire") as {
|
|
19
|
+
env: { frontSocket: string; nodeSocket: string };
|
|
20
|
+
askPatienceMs: number;
|
|
21
|
+
frame: { lengthBytes: number };
|
|
22
|
+
};
|
|
23
|
+
const browser = manifest("browser-wire") as {
|
|
24
|
+
edge: { verdictHeader: string; verdicts: { oneOf: { const: string }[] } };
|
|
25
|
+
terminal: { path: string; upgrade: string };
|
|
26
|
+
webTransport: { path: string };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("the wire outside oRPC, as the Rust crates define it", () => {
|
|
30
|
+
it("names the tunnel door and its headers as the tunnel crate does", () => {
|
|
31
|
+
expect({ path: INGRESS_TUNNEL_PATH, grant: INGRESS_GRANT_HEADER }).toEqual({
|
|
32
|
+
path: tunnel.path,
|
|
33
|
+
grant: tunnel.headers.grant,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("names the control socket's env vars, its patience and its framing as front-wire does", () => {
|
|
38
|
+
expect({ front: FRONT_SOCKET_ENV, node: NODE_SOCKET_ENV, patience: ASK_PATIENCE_MS, length: FRAME_LENGTH_BYTES }).toEqual({
|
|
39
|
+
front: socket.env.frontSocket,
|
|
40
|
+
node: socket.env.nodeSocket,
|
|
41
|
+
patience: socket.askPatienceMs,
|
|
42
|
+
length: socket.frame.lengthBytes,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("declares exactly the transports an edge may", () => {
|
|
47
|
+
expect<readonly string[]>(EDGE_TRANSPORTS).toEqual(tunnel.transports);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("reads the edge's verdicts and the browser's paths as browser-wire writes them", () => {
|
|
51
|
+
const verdicts = browser.edge.verdicts.oneOf.map((verdict) => verdict.const);
|
|
52
|
+
expect(verdicts).toEqual(["no-tunnel", "unknown-sandbox", "dropped"]);
|
|
53
|
+
expect<readonly (string | undefined)[]>(verdicts.map((verdict) => edgeVerdictOf(verdict))).toEqual(verdicts);
|
|
54
|
+
expect({ header: EDGE_VERDICT_HEADER, terminal: TERMINAL_PATH, session: WEBTRANSPORT_PATH, upgrade: "websocket" }).toEqual({
|
|
55
|
+
header: browser.edge.verdictHeader,
|
|
56
|
+
terminal: browser.terminal.path,
|
|
57
|
+
session: browser.webTransport.path,
|
|
58
|
+
upgrade: browser.terminal.upgrade,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -160,15 +160,16 @@ const repoSlug = (repo: string): string =>
|
|
|
160
160
|
.replace(/^-+|-+$/g, "") || "repo";
|
|
161
161
|
export const ciFixConversationId = (repo: string, runId: number): string => `${CI_FIX_PREFIX}${repoSlug(repo)}-${runId}`;
|
|
162
162
|
|
|
163
|
-
// Keyed by
|
|
164
|
-
//
|
|
163
|
+
// Keyed by when the project's push red began (pushFixBase, `red:<since>`), not a run id (a push has none): a refused push
|
|
164
|
+
// is owed by that red too (push-checks-store.ts). Hashed rather than spelled into the branch name; the fix agent's first
|
|
165
|
+
// message names what it is for.
|
|
165
166
|
export const PUSH_FIX_PREFIX = "push-fix-";
|
|
166
167
|
|
|
167
168
|
// FNV-1a, not a cryptographic hash: nothing here is secret, the only requirement is the same failure yields the same
|
|
168
|
-
// seven characters in every browser and in node.
|
|
169
|
+
// seven characters in every browser and in node. The one spelling: a push finding's id (push-checks-store.ts) is this too.
|
|
169
170
|
const FNV_OFFSET = 0x811c_9dc5;
|
|
170
171
|
const FNV_PRIME = 0x0100_0193;
|
|
171
|
-
const
|
|
172
|
+
export const fnvDigest = (text: string): string => {
|
|
172
173
|
let hash = FNV_OFFSET;
|
|
173
174
|
for (let index = 0; index < text.length; index += 1) {
|
|
174
175
|
hash = Math.imul(hash ^ text.charCodeAt(index), FNV_PRIME);
|
|
@@ -176,7 +177,7 @@ const digest = (text: string): string => {
|
|
|
176
177
|
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
177
178
|
};
|
|
178
179
|
|
|
179
|
-
export const pushFixConversationId = (scope: string, signature: string): string => `${PUSH_FIX_PREFIX}${repoSlug(scope)}-${
|
|
180
|
+
export const pushFixConversationId = (scope: string, signature: string): string => `${PUSH_FIX_PREFIX}${repoSlug(scope)}-${fnvDigest(signature)}`;
|
|
180
181
|
|
|
181
182
|
// A red main-line check that no conversation still holding the work could take: keyed by the project and the moment its
|
|
182
183
|
// red streak began, so every attempt at one streak shares a base and a later streak in the same project starts over.
|
package/src/ids/session-names.ts
CHANGED
|
@@ -6,6 +6,8 @@ export const WEB_SESSION_PREFIX = "web-";
|
|
|
6
6
|
export const AGENT_SESSION_PREFIX = "agent-";
|
|
7
7
|
export const JOB_SESSION_PREFIX = "job-";
|
|
8
8
|
export const BROWSER_SESSION_PREFIX = "browser-";
|
|
9
|
+
// A panel's session, one per managed process; the browser string-builds these names too, so never rename it.
|
|
10
|
+
export const PANEL_SESSION_PREFIX = "panel-";
|
|
9
11
|
|
|
10
12
|
// Eight characters of the SDK session UUID, sanitized to the name-guard charset; groups a turn's work, including its
|
|
11
13
|
// subagents', under one name. Undefined when it sanitizes to empty.
|
|
@@ -21,6 +23,11 @@ export const agentSessionName = (sessionId: string): string | undefined => {
|
|
|
21
23
|
return suffix === undefined ? undefined : `${AGENT_SESSION_PREFIX}${suffix}`;
|
|
22
24
|
};
|
|
23
25
|
|
|
26
|
+
// The session a panel keyed `key` runs in, and back; undefined for a name that isn't a panel session at all.
|
|
27
|
+
export const panelSession = (key: string): string => `${PANEL_SESSION_PREFIX}${key}`;
|
|
28
|
+
export const panelKeyOf = (session: string | undefined): string | undefined =>
|
|
29
|
+
session?.startsWith(PANEL_SESSION_PREFIX) === true ? session.slice(PANEL_SESSION_PREFIX.length) : undefined;
|
|
30
|
+
|
|
24
31
|
// The browser session one SDK session drives. Shares the suffix with agentSessionName on purpose: a conversation's
|
|
25
32
|
// shell and its browser read as the pair they are.
|
|
26
33
|
export const browserSessionName = (sessionId: string): string | undefined => {
|
package/src/index.ts
CHANGED
|
@@ -114,14 +114,17 @@ export * from "./events/transcript.js";
|
|
|
114
114
|
export * from "./events/land-breakage.js";
|
|
115
115
|
export * from "./events/land-conflict.js";
|
|
116
116
|
export * from "./events/verify-nudge.js";
|
|
117
|
+
export * from "./events/errands.js";
|
|
117
118
|
export * from "./events/watch-wake.js";
|
|
118
119
|
export * from "./policy/request-status.js";
|
|
120
|
+
export * from "./events/child-run.js";
|
|
119
121
|
export * from "./policy/turned-away.js";
|
|
120
122
|
export * from "./text/mentions.js";
|
|
121
123
|
export * from "./protocol/sse.js";
|
|
122
124
|
export * from "./protocol/routes.js";
|
|
123
|
-
export type
|
|
125
|
+
export { type ControlReach, defaultFloor, type RouteAccess, type RouteMeta, routeAccess } from "./protocol/route-meta.js";
|
|
124
126
|
export { RAW_ROUTE_LIST, RAW_ROUTES, type RawRouteKey, rawRoutePath } from "./protocol/raw-routes.js";
|
|
127
|
+
export { tunnelBulkRoutes } from "./protocol/tunnel-bulk.js";
|
|
125
128
|
export type { SandboxCallInput, SandboxGroup, SandboxHandlerInput, SandboxHandlerOutput, SandboxProcedure } from "./protocol/procedure-types.js";
|
|
126
129
|
export * from "./policy/control-scopes.js";
|
|
127
130
|
// The container's directory layout, re-exported since extensions can't import @intentic/constants directly.
|
|
@@ -133,6 +136,7 @@ export * from "./state/history-state.js";
|
|
|
133
136
|
export * from "./state/fix-stance.js";
|
|
134
137
|
export * from "./state/landed-commit.js";
|
|
135
138
|
export * from "./state/fix-attempt-plan.js";
|
|
139
|
+
export * from "./state/red-streak.js";
|
|
136
140
|
// Provider vocabulary bottom-up: agent-runtimes, provider-specs, agent-catalog; three modules since the dependency
|
|
137
141
|
// points one way.
|
|
138
142
|
export * from "./models/agent-runtimes.js";
|
|
@@ -173,6 +177,7 @@ export * from "./schemas/areas.js";
|
|
|
173
177
|
export * from "./policy/fence-paths.js";
|
|
174
178
|
export * from "./policy/persona-home.js";
|
|
175
179
|
export * from "./schemas/agents.js";
|
|
180
|
+
export * from "./schemas/speaker.js";
|
|
176
181
|
export * from "./schemas/approvals.js";
|
|
177
182
|
export * from "./schemas/automations.js";
|
|
178
183
|
export * from "./schemas/capabilities.js";
|
|
@@ -218,6 +223,7 @@ export * from "./schemas/share.js";
|
|
|
218
223
|
export * from "./schemas/shared.js";
|
|
219
224
|
export * from "./schemas/system-prompt.js";
|
|
220
225
|
export * from "./schemas/system.js";
|
|
226
|
+
export * from "./schemas/state-plan.js";
|
|
221
227
|
export * from "./schemas/terminal.js";
|
|
222
228
|
export * from "./schemas/turn-break.js";
|
|
223
229
|
export * from "./schemas/keep-warm.js";
|
|
@@ -238,6 +244,7 @@ export * from "./state/starter.js";
|
|
|
238
244
|
export * from "./text/title.js";
|
|
239
245
|
export * from "./state/versions.js";
|
|
240
246
|
export * from "./text/model-answer.js";
|
|
247
|
+
export * from "./text/shell-regions.js";
|
|
241
248
|
export * from "./text/whisper.js";
|
|
242
249
|
export * from "./text/workflow-faults.js";
|
|
243
250
|
export * from "./text/emoji.js";
|
|
@@ -31,7 +31,7 @@ describe("every provider/harness pair declares what it can do", () => {
|
|
|
31
31
|
|
|
32
32
|
expect(["claude-code", "codex", "cursor", "opencode", "opencode-gemini", "acp", "pi"]).toContain(capabilities.runtime);
|
|
33
33
|
expect(["modes", "plan"]).toContain(capabilities.permissions);
|
|
34
|
-
expect(["full", "tools", "
|
|
34
|
+
expect(["full", "tools", "http", "none"]).toContain(capabilities.mcp);
|
|
35
35
|
expect(capabilities.execution).toContain("shell");
|
|
36
36
|
for (const backend of capabilities.execution) {
|
|
37
37
|
expect(["shell", "js"]).toContain(backend);
|
|
@@ -135,8 +135,8 @@ test("the ceiling has nothing to disclose; a floor names what it lacks", () => {
|
|
|
135
135
|
expect(acpCaps.terminals).toBe(true);
|
|
136
136
|
|
|
137
137
|
const codexCaps = capabilitiesOf("codex", "native");
|
|
138
|
-
expect(codexCaps.mcp).toBe("
|
|
139
|
-
expect(limitationsOf(codexCaps).
|
|
138
|
+
expect(codexCaps.mcp).toBe("http");
|
|
139
|
+
expect(limitationsOf(codexCaps)).toContain("MCP servers only, none of the daemon's own tools, no plugins");
|
|
140
140
|
expect(limitationsOf(codexCaps).length).toBeLessThan(limitationsOf({ ...grokCaps, mcp: "none" }).length + 5);
|
|
141
141
|
});
|
|
142
142
|
|
|
@@ -154,13 +154,15 @@ test("every axis a record can lack has words for it", () => {
|
|
|
154
154
|
commands: false,
|
|
155
155
|
terminals: false,
|
|
156
156
|
recovery: false,
|
|
157
|
+
warm: false,
|
|
157
158
|
instructions: "none",
|
|
158
159
|
skillDiscovery: "prompt",
|
|
159
160
|
rulebook: "none",
|
|
160
161
|
secrets: "none",
|
|
161
162
|
};
|
|
162
163
|
|
|
163
|
-
// Thirteen disclosable axes, thirteen sentences; `fastMode` is
|
|
164
|
+
// Thirteen disclosable axes, thirteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
|
|
165
|
+
// default) is no limit on the turn itself.
|
|
164
166
|
expect(limitationsOf(nothing)).toHaveLength(13);
|
|
165
167
|
expect(limitationsOf(nothing).join(" ")).not.toContain("fast");
|
|
166
168
|
});
|
|
@@ -116,12 +116,10 @@ export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
|
|
|
116
116
|
...(capabilities.mcp === "none"
|
|
117
117
|
? ["no MCP tools or plugins"]
|
|
118
118
|
: capabilities.mcp === "http"
|
|
119
|
-
? ["MCP
|
|
120
|
-
: capabilities.mcp === "
|
|
121
|
-
? ["
|
|
122
|
-
:
|
|
123
|
-
? ["no plugins"]
|
|
124
|
-
: []),
|
|
119
|
+
? ["MCP servers only, none of the daemon's own tools, no plugins"]
|
|
120
|
+
: capabilities.mcp === "tools"
|
|
121
|
+
? ["no plugins"]
|
|
122
|
+
: []),
|
|
125
123
|
...(capabilities.execution.includes("js") ? [] : ["no code runs, shell only"]),
|
|
126
124
|
...(capabilities.effort ? [] : ["no effort control"]),
|
|
127
125
|
...(capabilities.commands ? [] : ["no slash commands"]),
|
|
@@ -15,8 +15,13 @@ export interface AgentCapabilities {
|
|
|
15
15
|
readonly permissions: "modes" | "plan";
|
|
16
16
|
// Can stop mid-turn and ask the user a multiple-choice question (`question` frames).
|
|
17
17
|
readonly questions: boolean;
|
|
18
|
-
// Which tools reach the agent, full down to none; never rounded up or down from what a runtime has.
|
|
19
|
-
|
|
18
|
+
// Which tools reach the agent, full down to none; never rounded up or down from what a runtime has. Every runtime
|
|
19
|
+
// but `none` takes the turn's whole remote list (TurnTools.remote: the daemon's MCP door's mounts, browsers
|
|
20
|
+
// included, and the mcp-kind cards), so no granted server reaches one runtime and misses another:
|
|
21
|
+
// full - that list, the daemon's own in-process servers (ui, secrets, watch, …), and plugin checkouts.
|
|
22
|
+
// tools - that list, and the daemon's own tools through the runtime's host callbacks; no plugins.
|
|
23
|
+
// http - that list alone, as http MCP servers.
|
|
24
|
+
readonly mcp: "full" | "tools" | "http" | "none";
|
|
20
25
|
// Which ways of executing the daemon stands behind here; distinct from `mcp`, which names tools, not execution.
|
|
21
26
|
readonly execution: readonly ExecutionBackend[];
|
|
22
27
|
// Reasoning-effort selection is forwarded to the model.
|
|
@@ -31,6 +36,9 @@ export interface AgentCapabilities {
|
|
|
31
36
|
readonly terminals: boolean;
|
|
32
37
|
// Fails with the coded frames auto-resume keys off, so a turn the provider killed is re-run once it's back.
|
|
33
38
|
readonly recovery: boolean;
|
|
39
|
+
// Can hold an idle conversation's prompt cache warm: its provider replays the last turn's prefix as one forked,
|
|
40
|
+
// unsaved request with every tool refused (keep-warm). Only where the provider module also implements `warm`.
|
|
41
|
+
readonly warm: boolean;
|
|
34
42
|
// How much of the owner's instructions this runtime takes: replace the base, append to it, or no seam at all.
|
|
35
43
|
readonly instructions: "replace" | "append" | "none";
|
|
36
44
|
// How the runtime discovers loaded skills: scans its own filesystem, or the daemon names them in the opener.
|
|
@@ -61,6 +69,7 @@ export const CLAUDE_CODE: AgentCapabilities = {
|
|
|
61
69
|
commands: true,
|
|
62
70
|
terminals: true,
|
|
63
71
|
recovery: true,
|
|
72
|
+
warm: true,
|
|
64
73
|
instructions: "replace",
|
|
65
74
|
skillDiscovery: "native",
|
|
66
75
|
// The only runtime with its own pre-execution hook, so the only one where a hold can park instead of refusing.
|
|
@@ -68,14 +77,15 @@ export const CLAUDE_CODE: AgentCapabilities = {
|
|
|
68
77
|
secrets: "masked",
|
|
69
78
|
};
|
|
70
79
|
|
|
71
|
-
// Codex app-server: item-level events,
|
|
72
|
-
// the shared mount namespace).
|
|
80
|
+
// Codex app-server: item-level events, the turn's remote MCP servers over http, and four seams (steer, a question
|
|
81
|
+
// request, the skills list, the shared mount namespace). The daemon's in-process servers, plugins and server approvals
|
|
82
|
+
// stay unwired.
|
|
73
83
|
export const CODEX: AgentCapabilities = {
|
|
74
84
|
runtime: "codex",
|
|
75
85
|
steering: true,
|
|
76
86
|
permissions: "plan",
|
|
77
87
|
questions: true,
|
|
78
|
-
mcp: "
|
|
88
|
+
mcp: "http",
|
|
79
89
|
execution: ["shell"],
|
|
80
90
|
effort: true,
|
|
81
91
|
fastMode: false,
|
|
@@ -83,6 +93,7 @@ export const CODEX: AgentCapabilities = {
|
|
|
83
93
|
commands: true,
|
|
84
94
|
terminals: false,
|
|
85
95
|
recovery: false,
|
|
96
|
+
warm: false,
|
|
86
97
|
// Both ride the per-thread config: one replaces Codex's base prompt, the other adds a developer message.
|
|
87
98
|
instructions: "replace",
|
|
88
99
|
skillDiscovery: "native",
|
|
@@ -106,6 +117,7 @@ export const OPENCODE: AgentCapabilities = {
|
|
|
106
117
|
commands: false,
|
|
107
118
|
terminals: false,
|
|
108
119
|
recovery: false,
|
|
120
|
+
warm: false,
|
|
109
121
|
// `system` on the prompt body, per message; adds to OpenCode's own prompt, with no seam to replace it.
|
|
110
122
|
instructions: "append",
|
|
111
123
|
skillDiscovery: "prompt",
|
|
@@ -122,7 +134,7 @@ export const OPENCODE_GEMINI: AgentCapabilities = {
|
|
|
122
134
|
};
|
|
123
135
|
|
|
124
136
|
// Any agent speaking the Agent Client Protocol: a documented floor, not the native ceiling. Publishes commands and
|
|
125
|
-
// terminals, takes
|
|
137
|
+
// terminals, takes the turn's remote MCP servers when it advertises http MCP, but owns its own model, effort and permission posture.
|
|
126
138
|
export const ACP: AgentCapabilities = {
|
|
127
139
|
runtime: "acp",
|
|
128
140
|
steering: false,
|
|
@@ -136,6 +148,7 @@ export const ACP: AgentCapabilities = {
|
|
|
136
148
|
commands: true,
|
|
137
149
|
terminals: true,
|
|
138
150
|
recovery: false,
|
|
151
|
+
warm: false,
|
|
139
152
|
// ACP's calls carry no system field: the agent owns its instructions, the persona note rides the message.
|
|
140
153
|
instructions: "none",
|
|
141
154
|
skillDiscovery: "prompt",
|
|
@@ -159,6 +172,7 @@ export const PI: AgentCapabilities = {
|
|
|
159
172
|
commands: true,
|
|
160
173
|
terminals: false,
|
|
161
174
|
recovery: false,
|
|
175
|
+
warm: false,
|
|
162
176
|
// Pi's RPC sets no standing instructions; like ACP, it hears the persona note through the user message.
|
|
163
177
|
instructions: "none",
|
|
164
178
|
skillDiscovery: "prompt",
|
|
@@ -177,7 +191,7 @@ export const CURSOR: AgentCapabilities = {
|
|
|
177
191
|
permissions: "plan",
|
|
178
192
|
// True since the daemon supplies its own ask tool; Cursor's own can fabricate an answer, so it's disallowed.
|
|
179
193
|
questions: true,
|
|
180
|
-
//
|
|
194
|
+
// The turn's remote MCP servers plus host callbacks; everything but a Claude Code plugin checkout.
|
|
181
195
|
mcp: "tools",
|
|
182
196
|
// The JS backend rides Cursor's custom-tool seam rather than an MCP server, and consults the rulebook in its
|
|
183
197
|
// handler, since the hook file only covers the shell.
|
|
@@ -193,6 +207,7 @@ export const CURSOR: AgentCapabilities = {
|
|
|
193
207
|
terminals: false,
|
|
194
208
|
// The SDK throws typed errors instead of dissolving a refusal into prose, so the adapter files coded frames.
|
|
195
209
|
recovery: true,
|
|
210
|
+
warm: false,
|
|
196
211
|
// append, reached differently: `beforeSubmitPrompt`'s reply folds the daemon's text onto Cursor's base. The SDK can
|
|
197
212
|
// replace that base outright, but only for an entitled account, so the seam every turn can reach is this one.
|
|
198
213
|
instructions: "append",
|
|
@@ -32,6 +32,8 @@ const LEDGER: Record<keyof AgentCapabilities, Backing> = {
|
|
|
32
32
|
// - refuse-only: a hold refuses instead of parking
|
|
33
33
|
// - other values: park on a card
|
|
34
34
|
rulebook: "enforced",
|
|
35
|
+
// agent/run/turn/cache-keepwarm.ts files nothing to keep warm for a turn whose runtime lacks it.
|
|
36
|
+
warm: "enforced",
|
|
35
37
|
|
|
36
38
|
// Descriptive: true of the runtime but nothing consults these; wiring one up moves it to enforced.
|
|
37
39
|
questions: "descriptive",
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
bindingWindow,
|
|
3
|
+
gatesModel,
|
|
4
|
+
gatingWindows,
|
|
5
|
+
headroomState,
|
|
6
|
+
preferredAccount,
|
|
7
|
+
refusalVerdict,
|
|
8
|
+
roomiestAccount,
|
|
9
|
+
scopedWindow,
|
|
10
|
+
serviceState,
|
|
11
|
+
serviceStates,
|
|
12
|
+
windowLive,
|
|
13
|
+
windowPeriod,
|
|
14
|
+
} from "./plan-pools.js";
|
|
15
|
+
import type { AccountState, AccountUsage, ProviderRefusal, UsageWindow } from "../schemas/providers/plan-limits.js";
|
|
3
16
|
|
|
4
17
|
// One rule for which pool blocks a given model, shared by the daemon and the browser: a plan can meter models
|
|
5
18
|
// separately, so the account's fullest pool need not be the model's own.
|
|
@@ -102,3 +115,109 @@ test("a reading is live until its reset, or, with none published, for one window
|
|
|
102
115
|
// And a pool whose length nothing names has only its reset instant; without one it stands.
|
|
103
116
|
expect(windowLive(window({ kind: "claude:tangelo" }), NOW - 400 * HOUR, NOW)).toBe(true);
|
|
104
117
|
});
|
|
118
|
+
|
|
119
|
+
// Serviceability: the one verdict on whether an account can serve a turn.
|
|
120
|
+
|
|
121
|
+
const at = (measuredAt: number, ...windows: UsageWindow[]): AccountUsage => ({ windows, measuredAt });
|
|
122
|
+
const refusal = (over: Partial<ProviderRefusal> & Pick<ProviderRefusal, "kind">): ProviderRefusal => ({ at: 1_000, message: "no.", ...over });
|
|
123
|
+
|
|
124
|
+
test("spent is the contract's one line: 99.5% still has room, 100% is spent until its last full pool reopens", () => {
|
|
125
|
+
expect(headroomState(usage(window({ kind: "five_hour", utilization: 99.5 })))).toEqual({ kind: "ready", room: 0.5 });
|
|
126
|
+
expect(
|
|
127
|
+
headroomState(usage(window({ kind: "five_hour", utilization: 100, resetsAt: 50 }), window({ kind: "seven_day", utilization: 100, resetsAt: 900 }))),
|
|
128
|
+
).toEqual({ kind: "spent", reopensAt: 900 });
|
|
129
|
+
expect(headroomState(usage(window({ kind: "five_hour", utilization: 100 })))).toEqual({ kind: "spent" });
|
|
130
|
+
expect(headroomState(undefined)).toEqual({ kind: "unknown" });
|
|
131
|
+
expect(headroomState(CLAUDE, { id: "claude-haiku-4-5" })).toEqual({ kind: "ready", room: 70 });
|
|
132
|
+
expect(headroomState(CLAUDE, { id: "claude-opus-4-6" })).toEqual({ kind: "spent" });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("with no model named, an account is spent only when no model can run on it", () => {
|
|
136
|
+
// A full Opus slice leaves every other model the room of the fullest pool still open.
|
|
137
|
+
expect(headroomState(CLAUDE)).toEqual({ kind: "ready", room: 70 });
|
|
138
|
+
expect(headroomState(GOOGLE)).toEqual({ kind: "ready", room: 73 });
|
|
139
|
+
// A pool gating every model, full: nothing runs, and it reopens with that pool.
|
|
140
|
+
expect(headroomState(usage(window({ kind: "seven_day", utilization: 100, resetsAt: 900 }), window({ kind: "model:Opus", utilization: 10, gates: { models: ["Opus"] } })))).toEqual({
|
|
141
|
+
kind: "spent",
|
|
142
|
+
reopensAt: 900,
|
|
143
|
+
});
|
|
144
|
+
// Every slice full: the first to reopen lets some model run again.
|
|
145
|
+
expect(
|
|
146
|
+
headroomState(
|
|
147
|
+
usage(
|
|
148
|
+
window({ kind: "g", utilization: 100, resetsAt: 500, gates: { models: ["gemini"] } }),
|
|
149
|
+
window({ kind: "c", utilization: 100, resetsAt: 800, gates: { models: ["claude"] } }),
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
).toEqual({ kind: "spent", reopensAt: 500 });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a revoked sign-in outranks a lost seat, which outranks a bench, whatever the meters say", () => {
|
|
156
|
+
const facts = { account: "a", usage: usage(window({ kind: "five_hour", utilization: 0 })) };
|
|
157
|
+
expect(serviceState({ ...facts, needsReauth: true, seatRefusal: "no seat" })).toEqual({ kind: "blocked", fix: "reconnect", reason: "sign-in expired" });
|
|
158
|
+
expect(serviceState({ ...facts, seatRefusal: "Your org disabled Claude Code." })).toEqual({
|
|
159
|
+
kind: "blocked",
|
|
160
|
+
fix: "admin",
|
|
161
|
+
reason: "Your org disabled Claude Code.",
|
|
162
|
+
});
|
|
163
|
+
expect(serviceState({ ...facts, cooling: { reason: "no project" } })).toEqual({ kind: "blocked", fix: "reconnect", reason: "no project" });
|
|
164
|
+
expect(serviceState({ ...facts, cooling: { until: 2_000 } }, undefined, undefined, 1_000_000)).toEqual({
|
|
165
|
+
kind: "blocked",
|
|
166
|
+
fix: "wait",
|
|
167
|
+
reason: "cooling down",
|
|
168
|
+
until: 2_000,
|
|
169
|
+
});
|
|
170
|
+
// A bench whose instant has passed is over.
|
|
171
|
+
expect(serviceState({ ...facts, cooling: { until: 999 } }, undefined, undefined, 1_000_000)).toEqual({ kind: "ready", room: 100 });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a standing refusal blocks by its kind, and a limit refusal pins only the pool its model spends", () => {
|
|
175
|
+
const facts = { account: "a", usage: CLAUDE };
|
|
176
|
+
expect(serviceState(facts, refusal({ kind: "auth" }))).toEqual({ kind: "blocked", fix: "reconnect", reason: "no." });
|
|
177
|
+
expect(serviceState(facts, refusal({ kind: "entitlement" }))).toEqual({ kind: "blocked", fix: "admin", reason: "no." });
|
|
178
|
+
// Refused on Haiku: the 5-hour/weekly binding pool (seven_day, 30%) reads full, so Haiku is spent too.
|
|
179
|
+
expect(serviceState(facts, refusal({ kind: "limit", model: "claude-haiku-4-5" }), { id: "claude-haiku-4-5" })).toEqual({ kind: "spent" });
|
|
180
|
+
// Refused on Opus: its own slice was already full; Haiku keeps the room its pools show.
|
|
181
|
+
expect(serviceState(facts, refusal({ kind: "limit", model: "claude-opus-4-6" }), { id: "claude-haiku-4-5" })).toEqual({ kind: "ready", room: 70 });
|
|
182
|
+
// No reading at all: the refusal is the evidence, unless it was about another model.
|
|
183
|
+
expect(serviceState({ account: "a" }, refusal({ kind: "limit" }))).toEqual({ kind: "spent" });
|
|
184
|
+
expect(serviceState({ account: "a" }, refusal({ kind: "limit", model: "opus" }), { id: "haiku" })).toEqual({ kind: "unknown" });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("a refusal stands until a later reading contradicts it, and never past its account's disconnection", () => {
|
|
188
|
+
const room = at(2_000, window({ kind: "five_hour", utilization: 40 }));
|
|
189
|
+
const full = at(2_000, window({ kind: "five_hour", utilization: 100 }));
|
|
190
|
+
const before = at(500, window({ kind: "five_hour", utilization: 40 }));
|
|
191
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: room }])).toBe("answered");
|
|
192
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: full }])).toBe("standing");
|
|
193
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: before }])).toBe("standing");
|
|
194
|
+
// Another account's room proves nothing about the one refused.
|
|
195
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: before }, { account: "b", usage: room }])).toBe("standing");
|
|
196
|
+
// A refusal naming no account is answered by any of them.
|
|
197
|
+
expect(refusalVerdict(refusal({ kind: "limit" }), [{ account: "a", usage: before }, { account: "b", usage: room }])).toBe("answered");
|
|
198
|
+
expect(refusalVerdict(refusal({ kind: "auth", account: "a" }), [{ account: "a", usage: full }])).toBe("answered");
|
|
199
|
+
expect(refusalVerdict(refusal({ kind: "auth", account: "a" }), [{ account: "a", usage: full, needsReauth: true }])).toBe("standing");
|
|
200
|
+
expect(refusalVerdict(refusal({ kind: "entitlement", account: "a" }), [{ account: "a", usage: room }])).toBe("standing");
|
|
201
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "gone" }), [{ account: "a", usage: room }])).toBe("gone");
|
|
202
|
+
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [])).toBe("standing");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("a provider's accounts are judged together: a standing refusal lands only on the account it names", () => {
|
|
206
|
+
const room = at(500, window({ kind: "five_hour", utilization: 40 }));
|
|
207
|
+
const states = serviceStates([{ account: "a", usage: room }, { account: "b", usage: room }], refusal({ kind: "entitlement", account: "a" }));
|
|
208
|
+
expect(Object.fromEntries(states)).toEqual({ a: { kind: "blocked", fix: "admin", reason: "no." }, b: { kind: "ready", room: 60 } });
|
|
209
|
+
const answered = serviceStates([{ account: "a", usage: at(2_000, window({ kind: "five_hour", utilization: 40 })) }], refusal({ kind: "limit", account: "a" }));
|
|
210
|
+
expect(answered.get("a")).toEqual({ kind: "ready", room: 60 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("an unnamed turn takes the most room, then an unmeasured account, then a spent one, and a blocked one only when that is all", () => {
|
|
214
|
+
const entry = (id: string, state: AccountState) => ({ id, state });
|
|
215
|
+
const blocked: AccountState = { kind: "blocked", fix: "admin", reason: "no seat" };
|
|
216
|
+
expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" }), entry("low", { kind: "ready", room: 5 }), entry("high", { kind: "ready", room: 60 })])?.id).toBe("high");
|
|
217
|
+
expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" }), entry("unread", { kind: "unknown" })])?.id).toBe("unread");
|
|
218
|
+
expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" })])?.id).toBe("spent");
|
|
219
|
+
expect(preferredAccount([entry("seatless", blocked), entry("other", blocked)])?.id).toBe("seatless");
|
|
220
|
+
expect(preferredAccount([entry("first", { kind: "ready", room: 60 }), entry("second", { kind: "ready", room: 60 })])?.id).toBe("first");
|
|
221
|
+
expect(roomiestAccount([entry("seatless", blocked), entry("unread", { kind: "unknown" })])).toBeUndefined();
|
|
222
|
+
expect(roomiestAccount([entry("low", { kind: "ready", room: 5 }), entry("high", { kind: "ready", room: 60 })])?.id).toBe("high");
|
|
223
|
+
});
|