@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
package/src/schemas/hosts.ts
CHANGED
|
@@ -9,9 +9,22 @@ import { z } from "zod";
|
|
|
9
9
|
export const WslEnvironmentSchema = z.object({ distro: z.string() });
|
|
10
10
|
export type WslEnvironment = z.infer<typeof WslEnvironmentSchema>;
|
|
11
11
|
|
|
12
|
+
// WHICH PHYSICAL COMPUTER A DOOR IS ON. The machine agent mints this once, at install, and keeps it in its own state
|
|
13
|
+
// (`~/.intentic/machine/machine-id`); a WSL distro's agent is handed its Windows side's, since a distro is an
|
|
14
|
+
// environment of the PC, not a second computer. It is what an enrollment, a sync enrollment and a device row are
|
|
15
|
+
// joined on — never a hostname, which WSL shares between a PC and its distros and a user can rename.
|
|
16
|
+
export const MachineIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/);
|
|
17
|
+
// An id the daemon stands in for a machine that has not said its own yet (an enrollment made before agents reported
|
|
18
|
+
// one): named after the card, so every environment of that card shares it until each reports the real id. Never
|
|
19
|
+
// sent by an agent, and never the join key of two different cards.
|
|
20
|
+
export const derivedMachineId = (card: string): string => `card:${card}`;
|
|
21
|
+
export const isDerivedMachineId = (machineId: string): boolean => machineId.startsWith("card:");
|
|
22
|
+
|
|
12
23
|
// What a machine reports once at connect (`host.describe`), cached until it reconnects: the skill pack says how to
|
|
13
24
|
// drive Windows, this says which Windows it is.
|
|
14
25
|
export const DeviceFactsSchema = z.object({
|
|
26
|
+
// The computer this environment is on (MachineIdSchema). Absent from an agent older than the field.
|
|
27
|
+
machineId: MachineIdSchema.optional(),
|
|
15
28
|
// The OS's own name for itself, e.g. "Windows 11 Pro 24H2".
|
|
16
29
|
os: z.string(),
|
|
17
30
|
arch: z.string(),
|
|
@@ -24,8 +37,7 @@ export const DeviceFactsSchema = z.object({
|
|
|
24
37
|
// Docker engine's size (WSL guest on Windows, Desktop VM on macOS, host on Linux); the ceiling a sandbox's share is
|
|
25
38
|
// bounded by.
|
|
26
39
|
engine: z.object({ memoryBytes: z.number(), cpus: z.number() }).optional(),
|
|
27
|
-
// OS hostname
|
|
28
|
-
// `wsl`, so a hostname with no `wsl` beside it is a native install; absent from an agent older than both.
|
|
40
|
+
// OS hostname, for the owner to read. Not a join key: `machineId` is.
|
|
29
41
|
hostname: z.string().optional(),
|
|
30
42
|
// Present only inside a WSL distro.
|
|
31
43
|
wsl: WslEnvironmentSchema.optional(),
|
|
@@ -35,16 +47,38 @@ export const DeviceFactsSchema = z.object({
|
|
|
35
47
|
// the rest are other sandboxes' addresses, and a sandbox has no business learning its siblings'. Absent from an
|
|
36
48
|
// agent older than this field, which is why "none unreachable" is not the same value as "did not say".
|
|
37
49
|
links: z.object({ total: z.number(), unreachable: z.number(), unreachableSince: z.number().optional() }).optional(),
|
|
38
|
-
//
|
|
39
|
-
//
|
|
50
|
+
// Which of the optional ops this agent AND the `ic` it drives implement (DeviceFeatureSchema), for a page to offer
|
|
51
|
+
// only what works and for the daemon to refuse an op before sending it. Strings on the wire, so a newer agent's
|
|
52
|
+
// feature never fails an older daemon's read; `deviceFeatures` keeps the ones this build knows. Nothing is
|
|
53
|
+
// detected by a field's presence any more: the device RPC inputs are strict, and an agent rejects a field it does
|
|
54
|
+
// not know rather than dropping it.
|
|
40
55
|
features: z.array(z.string()).optional(),
|
|
56
|
+
// Why the `ic` this agent drives is older than the agent, as one sentence, when fetching the current one failed:
|
|
57
|
+
// what explains `features` missing what this agent's release has (a stale ic has no logs verb and no `--set`).
|
|
58
|
+
// Absent when ic is current, and from an agent older than the field.
|
|
59
|
+
icOutOfDate: z.string().optional(),
|
|
41
60
|
});
|
|
42
61
|
export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
|
|
43
62
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
// The optional device ops, by what enables them:
|
|
64
|
+
// - `reshape-later`: the old `reshape` op with `later`, saving the change for the next restart. An agent from before it
|
|
65
|
+
// strips `later` and reshapes NOW, which is why a later-reshape is never sent to one. Redundant with `set-shape`'s
|
|
66
|
+
// `when` for every current page; kept only for pages and daemons from before `set-shape` (v1.312.0 and older),
|
|
67
|
+
// which check it. REMOVE IN v1.314.0, with the old `reshape` op it gates.
|
|
68
|
+
// - `set-shape`: the `set-shape`/`forget-shape` ops, and `start`/`restart` applying a saved shape, all through an `ic`
|
|
69
|
+
// whose `ic sandbox shape` takes the contract's own shape (`--set`); advertised only when the agent's `ic` does.
|
|
70
|
+
// Both features ride that verb, so an agent advertises both or neither.
|
|
71
|
+
export const DeviceFeatureSchema = z.enum(["reshape-later", "set-shape"]);
|
|
72
|
+
export type DeviceFeature = z.infer<typeof DeviceFeatureSchema>;
|
|
73
|
+
export const DEVICE_FEATURE_RESHAPE_LATER: DeviceFeature = "reshape-later";
|
|
74
|
+
export const DEVICE_FEATURE_SET_SHAPE: DeviceFeature = "set-shape";
|
|
75
|
+
// The features an agent advertised that this build knows; an unknown one is a newer agent's and means nothing here.
|
|
76
|
+
export const deviceFeatures = (facts: Pick<DeviceFacts, "features"> | undefined): DeviceFeature[] =>
|
|
77
|
+
(facts?.features ?? []).flatMap((feature) => {
|
|
78
|
+
const known = DeviceFeatureSchema.safeParse(feature);
|
|
79
|
+
return known.success ? [known.data] : [];
|
|
80
|
+
});
|
|
81
|
+
export const deviceSupports = (facts: Pick<DeviceFacts, "features"> | undefined, feature: DeviceFeature): boolean => deviceFeatures(facts).includes(feature);
|
|
48
82
|
|
|
49
83
|
// Docker Desktop's own distros: `wsl -l -q` lists them like any other, and none ever runs an agent or holds a checkout.
|
|
50
84
|
export const WSL_SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
|
|
@@ -64,11 +98,7 @@ export const windowsPathOf = (distro: string, linuxPath: string): string => `\\\
|
|
|
64
98
|
// ONE CARD IS ONE COMPUTER, AND EACH OS INSTALL ON IT IS A CONNECTION OF THAT CARD. A PC's Windows side and every WSL
|
|
65
99
|
// distro on it hold their own agent — mutagen has to watch the filesystem it syncs, and a distro's login shell is its
|
|
66
100
|
// own — but the owner connected a computer, not a shell, so the grant, the row and the tools are the machine's.
|
|
67
|
-
// The native environment's connection key IS the card id, by definition rather than as a fallback: a machine with one
|
|
68
|
-
// OS install has one connection named after itself, and a sibling hangs off it as `<card>::wsl:<distro>`. `::` cannot
|
|
69
|
-
// collide with the single colon in an environment key.
|
|
70
101
|
export const HOST_NATIVE_ENVIRONMENT = "native";
|
|
71
|
-
const ENVIRONMENT_SEPARATOR = "::";
|
|
72
102
|
|
|
73
103
|
// Which environment a machine is describing when it connects: the distro by the name WSL registered (what `wsl -l -q`
|
|
74
104
|
// prints and `in: "wsl:<name>"` takes), or the metal. Facts arrive at connect and no scope withholds them, so this is
|
|
@@ -76,14 +106,27 @@ const ENVIRONMENT_SEPARATOR = "::";
|
|
|
76
106
|
export const environmentKeyOf = (facts: Pick<DeviceFacts, "wsl">): string =>
|
|
77
107
|
facts.wsl === undefined ? HOST_NATIVE_ENVIRONMENT : `wsl:${facts.wsl.distro}`;
|
|
78
108
|
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
// A CONNECTION'S KEY, spelled and read in exactly one place. The key is what the hub, a device RPC and a page's row
|
|
110
|
+
// address a connection by: the native environment's key IS the card id, and a sibling hangs off it as
|
|
111
|
+
// `<card>::wsl:<distro>` (`::` cannot collide with the single colon in an environment key). What a connection IS —
|
|
112
|
+
// its card, its environment, its computer — is the enrollment's own record (a host enrollment says all three, and a
|
|
113
|
+
// device row carries `card`), so the parser is only for a key with nothing beside it: the key the owner asks to pair,
|
|
114
|
+
// an enrollment written before records said, a row from a daemon older than `card`. A rename relabels the record and
|
|
115
|
+
// the key follows from the label; the live connection is held under the new key without being cut (PeerHub.rekey).
|
|
116
|
+
export interface HostConnection {
|
|
117
|
+
readonly card: string;
|
|
118
|
+
readonly environment: string;
|
|
119
|
+
}
|
|
120
|
+
const ENVIRONMENT_SEPARATOR = "::";
|
|
81
121
|
|
|
82
|
-
export const
|
|
122
|
+
export const hostConnectionKey = (card: string, environment: string): string =>
|
|
123
|
+
environment === HOST_NATIVE_ENVIRONMENT ? card : `${card}${ENVIRONMENT_SEPARATOR}${environment}`;
|
|
83
124
|
|
|
84
|
-
export const
|
|
85
|
-
const at =
|
|
86
|
-
return at === -1
|
|
125
|
+
export const parseHostConnection = (key: string): HostConnection => {
|
|
126
|
+
const at = key.indexOf(ENVIRONMENT_SEPARATOR);
|
|
127
|
+
return at === -1
|
|
128
|
+
? { card: key, environment: HOST_NATIVE_ENVIRONMENT }
|
|
129
|
+
: { card: key.slice(0, at), environment: key.slice(at + ENVIRONMENT_SEPARATOR.length) };
|
|
87
130
|
};
|
|
88
131
|
|
|
89
132
|
// One OS install of a machine, as its card knows it. `key` is the environment (`native`, `wsl:archlinux`); everything
|
|
@@ -91,6 +134,9 @@ export const hostEnvironmentOf = (connection: string): string => {
|
|
|
91
134
|
// version — one side can be asleep, or running a build behind.
|
|
92
135
|
export const HostEnvironmentSchema = z.object({
|
|
93
136
|
key: z.string().min(1),
|
|
137
|
+
// The computer this environment's enrollment is on, once its agent has said (never a derived id): what joins it to
|
|
138
|
+
// a sync enrollment and to the other environments of that computer, whether or not it is connected right now.
|
|
139
|
+
machineId: MachineIdSchema.optional(),
|
|
94
140
|
online: z.boolean(),
|
|
95
141
|
version: z.string().optional(),
|
|
96
142
|
lastSeen: z.number().optional(),
|
|
@@ -99,8 +145,11 @@ export const HostEnvironmentSchema = z.object({
|
|
|
99
145
|
export type HostEnvironment = z.infer<typeof HostEnvironmentSchema>;
|
|
100
146
|
|
|
101
147
|
export const HostSummarySchema = z.object({
|
|
102
|
-
// The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command).
|
|
148
|
+
// The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command). On a per-connection
|
|
149
|
+
// row (`hostConnections`) it is that connection's key instead, and `card` names the machine.
|
|
103
150
|
id: z.string(),
|
|
151
|
+
// The card a per-connection row is a connection of: a label on the row, read rather than parsed out of `id`.
|
|
152
|
+
card: z.string().optional(),
|
|
104
153
|
platform: z.string().min(1),
|
|
105
154
|
// Every environment of this machine, native first: what the page draws a row per, and what a command picks from.
|
|
106
155
|
// Never empty — a card that has never connected still has its native environment, offline.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DeviceSandboxFlowSchema, icForgetShapeArgs, icPowerArgs, icShapeArgs, SandboxShapeSchema } from "./devices.js";
|
|
2
|
+
|
|
3
|
+
// The one spelling of ic's shape and power verbs outside ic: the machine agent runs these and the Devices view prints them for a
|
|
4
|
+
// person to type, so a drift here is a command that fails on the machine, or one that does something else there.
|
|
5
|
+
|
|
6
|
+
test("a whole shape is one `--set` per field, in the contract's own names and values", () => {
|
|
7
|
+
expect(icShapeArgs("work", { memoryGib: 20, cpus: null, privileged: false, gpu: true }, "nextRestart")).toEqual([
|
|
8
|
+
"sandbox",
|
|
9
|
+
"shape",
|
|
10
|
+
"work",
|
|
11
|
+
"--set",
|
|
12
|
+
"memoryGib=20",
|
|
13
|
+
"--set",
|
|
14
|
+
"cpus=null",
|
|
15
|
+
"--set",
|
|
16
|
+
"privileged=false",
|
|
17
|
+
"--set",
|
|
18
|
+
"gpu=true",
|
|
19
|
+
"--when",
|
|
20
|
+
"nextRestart",
|
|
21
|
+
]);
|
|
22
|
+
expect(icShapeArgs("work", { memoryGib: null, cpus: 4, privileged: true, gpu: false }, "now").slice(3)).toEqual([
|
|
23
|
+
"--set",
|
|
24
|
+
"memoryGib=null",
|
|
25
|
+
"--set",
|
|
26
|
+
"cpus=4",
|
|
27
|
+
"--set",
|
|
28
|
+
"privileged=true",
|
|
29
|
+
"--set",
|
|
30
|
+
"gpu=false",
|
|
31
|
+
"--when",
|
|
32
|
+
"now",
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a field left out is left to ic, which keeps what it has for it", () => {
|
|
37
|
+
expect(icShapeArgs("work", { memoryGib: 12, gpu: undefined }, "now")).toEqual(["sandbox", "shape", "work", "--set", "memoryGib=12", "--when", "now"]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("forgetting and power name only the sandbox", () => {
|
|
41
|
+
expect(icForgetShapeArgs("work")).toEqual(["sandbox", "shape", "work", "--forget"]);
|
|
42
|
+
expect(icPowerArgs("restart", "work")).toEqual(["sandbox", "restart", "work"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// A shape is whole: a delta would put the merge back into every reader, which is what this schema exists to end.
|
|
46
|
+
test("a shape missing a field is not a shape", () => {
|
|
47
|
+
expect(SandboxShapeSchema.safeParse({ memoryGib: 20, cpus: null, privileged: false }).success).toBe(false);
|
|
48
|
+
expect(SandboxShapeSchema.safeParse({ memoryGib: 0, cpus: null, privileged: false, gpu: false }).success).toBe(false);
|
|
49
|
+
expect(SandboxShapeSchema.safeParse({ memoryGib: null, cpus: null, privileged: false, gpu: false }).success).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Every device input is strict, the shape nested in one included: an agent that dropped a field it did not know would
|
|
53
|
+
// carry out a different order than the one sent.
|
|
54
|
+
test("a device order refuses a shape or delta field it does not know", () => {
|
|
55
|
+
const whole = { memoryGib: 20, cpus: null, privileged: false, gpu: false };
|
|
56
|
+
// As the order arrives off the wire, where the nested object is only ever JSON.
|
|
57
|
+
const accepted = (fields: object): boolean =>
|
|
58
|
+
DeviceSandboxFlowSchema.safeParse(JSON.parse(`{"op":"set-shape","slug":"work","when":"now","shape":${JSON.stringify(fields)}}`)).success;
|
|
59
|
+
expect(accepted(whole)).toBe(true);
|
|
60
|
+
expect(accepted({ ...whole, swapGib: 4 })).toBe(false);
|
|
61
|
+
expect(DeviceSandboxFlowSchema.safeParse({ op: "reshape", slug: "work", resources: { memoryGib: 20, swapGib: 4 } }).success).toBe(false);
|
|
62
|
+
// A report is read leniently: a newer ic's extra field must not fail an older reader.
|
|
63
|
+
expect(SandboxShapeSchema.safeParse({ ...whole, swapGib: 4 }).success).toBe(true);
|
|
64
|
+
});
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
keepWarmDueAt,
|
|
6
6
|
keepWarmHorizon,
|
|
7
7
|
keepWarmLeadMs,
|
|
8
|
-
keepWarmMaxRefreshes,
|
|
9
8
|
keepWarmRefreshes,
|
|
10
9
|
keptWarmLine,
|
|
11
10
|
} from "./keep-warm.js";
|
|
@@ -13,20 +12,13 @@ import {
|
|
|
13
12
|
const MINUTE = 60_000;
|
|
14
13
|
const HOUR = 60 * MINUTE;
|
|
15
14
|
|
|
16
|
-
// The
|
|
15
|
+
// The clock both the daemon and every press read, so the refreshes shown before a press are the ones the daemon sends.
|
|
17
16
|
describe("keep-warm economics", () => {
|
|
18
17
|
test("refreshes a fifth of an entry's life early, never under a minute or over ten", () => {
|
|
19
18
|
expect(keepWarmLeadMs(HOUR)).toBe(10 * MINUTE);
|
|
20
19
|
expect(keepWarmLeadMs(5 * MINUTE)).toBe(MINUTE);
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
test("spends at most half of what a cold resume would rewrite", () => {
|
|
24
|
-
// A 1h rewrite costs 2x base input and a read 0.1x: nineteen reads break even, so nine.
|
|
25
|
-
expect(keepWarmMaxRefreshes(HOUR)).toBe(9);
|
|
26
|
-
// A 5m rewrite costs 1.25x: eleven reads break even, so five.
|
|
27
|
-
expect(keepWarmMaxRefreshes(5 * MINUTE)).toBe(5);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
22
|
test("counts the refreshes that keep an entry alive through the deadline", () => {
|
|
31
23
|
const cache = { at: 0, ttlMs: HOUR };
|
|
32
24
|
expect(keepWarmRefreshes(cache, HOUR)).toBe(0);
|
|
@@ -34,11 +26,12 @@ describe("keep-warm economics", () => {
|
|
|
34
26
|
expect(keepWarmDueAt(cache)).toBe(50 * MINUTE);
|
|
35
27
|
});
|
|
36
28
|
|
|
37
|
-
test("reaches no further than the
|
|
29
|
+
test("reaches no further than the refreshes left carry it, or the date change when that comes first", () => {
|
|
38
30
|
const cache = { at: 0, ttlMs: HOUR };
|
|
39
|
-
expect(keepWarmHorizon(cache)).toBe(HOUR + 9 * 50 * MINUTE);
|
|
40
|
-
expect(
|
|
41
|
-
expect(keepWarmCap(
|
|
31
|
+
expect(keepWarmHorizon(cache, 9)).toBe(HOUR + 9 * 50 * MINUTE);
|
|
32
|
+
expect(keepWarmHorizon(cache, -2)).toBe(HOUR);
|
|
33
|
+
expect(keepWarmCap(cache, 3 * HOUR, 9)).toBe(3 * HOUR);
|
|
34
|
+
expect(keepWarmCap({ at: 0, ttlMs: 5 * MINUTE }, undefined, 5)).toBe(5 * MINUTE + 5 * 4 * MINUTE);
|
|
42
35
|
});
|
|
43
36
|
|
|
44
37
|
test("names the parts two fingerprints disagree on", () => {
|
package/src/schemas/keep-warm.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
// keep-warm: holding an idle conversation's prompt cache open by re-reading it shortly before it expires.
|
|
1
|
+
// keep-warm: holding an idle conversation's prompt cache open by re-reading it shortly before it expires. Only the
|
|
2
|
+
// clock lives here; how many refreshes a hold may spend is priced by the provider that serves it (runtimes/claude).
|
|
2
3
|
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
5
|
-
const HOUR_MS = 3_600_000;
|
|
6
|
-
|
|
7
|
-
// Anthropic's price multipliers on base input: a read costs 0.1x, a write 2x under the 1h TTL and 1.25x under 5m.
|
|
8
|
-
const READ_COST = 0.1;
|
|
9
|
-
const writeCost = (ttlMs: number): number => (ttlMs >= HOUR_MS ? 2 : 1.25);
|
|
10
|
-
|
|
11
6
|
// A cache clock as the daemon publishes it: the last request that touched the entry, and the entry's lifetime.
|
|
12
7
|
export interface CacheClock {
|
|
13
8
|
readonly at: number;
|
|
@@ -17,28 +12,26 @@ export interface CacheClock {
|
|
|
17
12
|
/** How long before expiry a refresh starts: a fifth of the entry's life, never under a minute or over ten. */
|
|
18
13
|
export const keepWarmLeadMs = (ttlMs: number): number => Math.min(10 * 60_000, Math.max(60_000, Math.round(ttlMs / 5)));
|
|
19
14
|
|
|
20
|
-
/** Refreshes one hold may spend: half of what a cold resume rewrites, the other half left for each refresh's own tail. */
|
|
21
|
-
export const keepWarmMaxRefreshes = (ttlMs: number): number => Math.floor((writeCost(ttlMs) - READ_COST) / READ_COST / 2);
|
|
22
|
-
|
|
23
15
|
/** How many refreshes keep an entry alive through `until`. */
|
|
24
16
|
export const keepWarmRefreshes = (cache: CacheClock, until: number): number => {
|
|
25
17
|
const beyond = until - (cache.at + cache.ttlMs);
|
|
26
18
|
return beyond <= 0 ? 0 : Math.ceil(beyond / (cache.ttlMs - keepWarmLeadMs(cache.ttlMs)));
|
|
27
19
|
};
|
|
28
20
|
|
|
29
|
-
/** The latest instant
|
|
30
|
-
export const keepWarmHorizon = (cache: CacheClock,
|
|
31
|
-
cache.at + cache.ttlMs + Math.max(0,
|
|
21
|
+
/** The latest instant `left` more refreshes can carry an entry to: past it, refreshing costs more than the cold resume it saves. */
|
|
22
|
+
export const keepWarmHorizon = (cache: CacheClock, left: number): number =>
|
|
23
|
+
cache.at + cache.ttlMs + Math.max(0, left) * (cache.ttlMs - keepWarmLeadMs(cache.ttlMs));
|
|
32
24
|
|
|
33
25
|
/** When the next refresh is due for an entry last touched at `cache.at`. */
|
|
34
26
|
export const keepWarmDueAt = (cache: CacheClock): number => cache.at + cache.ttlMs - keepWarmLeadMs(cache.ttlMs);
|
|
35
27
|
|
|
36
28
|
/** The furthest `until` a hold can honestly promise: the economic horizon, or the date change, whichever comes first. */
|
|
37
|
-
export const keepWarmCap = (cache: CacheClock, rollsAt: number | undefined,
|
|
38
|
-
rollsAt === undefined ? keepWarmHorizon(cache,
|
|
29
|
+
export const keepWarmCap = (cache: CacheClock, rollsAt: number | undefined, left: number): number =>
|
|
30
|
+
rollsAt === undefined ? keepWarmHorizon(cache, left) : Math.min(keepWarmHorizon(cache, left), rollsAt);
|
|
39
31
|
|
|
40
|
-
// Why a hold ended before anyone picked the conversation up
|
|
41
|
-
|
|
32
|
+
// Why a hold ended before anyone picked the conversation up, one per thing a person would do about it; `detail` carries
|
|
33
|
+
// the specifics. `elapsed` is the one ending that is not a problem.
|
|
34
|
+
export const KeepWarmEndSchema = z.enum(["elapsed", "allowance", "changed", "cold", "failed"]);
|
|
42
35
|
export type KeepWarmEnd = z.infer<typeof KeepWarmEndSchema>;
|
|
43
36
|
|
|
44
37
|
export const KeepWarmSchema = z.object({
|
|
@@ -48,7 +41,6 @@ export const KeepWarmSchema = z.object({
|
|
|
48
41
|
.describe(
|
|
49
42
|
"When it stops by itself, in milliseconds: the time asked for, shortened to what the sandbox can honestly keep, which is never past the point where refreshing costs more than re-reading, nor past the date change that rewrites the prompt.",
|
|
50
43
|
),
|
|
51
|
-
auto: z.boolean().optional().describe("Started by the sandbox-wide setting after a turn, rather than by a press on this conversation."),
|
|
52
44
|
refreshes: z.number().int().min(0).describe("Refreshes sent so far."),
|
|
53
45
|
readTokens: z
|
|
54
46
|
.number()
|
|
@@ -58,9 +50,12 @@ export const KeepWarmSchema = z.object({
|
|
|
58
50
|
.object({
|
|
59
51
|
at: z.number().describe("When it stopped, in milliseconds."),
|
|
60
52
|
reason: KeepWarmEndSchema.describe(
|
|
61
|
-
"Why: `elapsed` the time asked for ran out; `allowance` the account reached the reserve kept for real work
|
|
53
|
+
"Why: `elapsed` the time asked for ran out; `allowance` the account reached the reserve kept for real work, or the provider refused a refresh for its limit; `changed` what the next turn would send no longer matches the cache (a part of the prompt, the date in it, or the conversation's session or account); `cold` the cache was gone, expired before a refresh could run or found missing by one; `failed` a refresh failed.",
|
|
62
54
|
),
|
|
63
|
-
detail: z
|
|
55
|
+
detail: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("The specifics, when there are any: which parts of the prompt changed, how full the account was, or the failure's own words."),
|
|
64
59
|
})
|
|
65
60
|
.optional()
|
|
66
61
|
.describe("Why keeping it warm stopped before anyone picked the conversation up. Absent while it is still being kept."),
|
package/src/schemas/metrics.ts
CHANGED
|
@@ -53,20 +53,49 @@ export const PressureMetricsSchema = z.object({
|
|
|
53
53
|
});
|
|
54
54
|
export type PressureMetrics = z.infer<typeof PressureMetricsSchema>;
|
|
55
55
|
|
|
56
|
+
// The memory figures the daemon admits work by (its resource budget), so a gauge built on them warns exactly when a turn
|
|
57
|
+
// would be held.
|
|
58
|
+
export const MemoryRoomSchema = z.object({
|
|
59
|
+
freeBytes: z
|
|
60
|
+
.number()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("The limit less what is used and what work admitted in the last minute and a half still holds. Absent where nothing measures it."),
|
|
63
|
+
reservedBytes: z.number().describe("What work admitted in the last minute and a half holds before it shows in `memoryBytes`."),
|
|
64
|
+
personNeedBytes: z.number().describe("What a person's turn needs free to start without a warning: below it, the sandbox is short."),
|
|
65
|
+
stallPercent: z.number().describe("Percent of the last ten seconds in which everything in the sandbox waited on memory (pressure `full`)."),
|
|
66
|
+
stallLimitPercent: z.number().describe("The stall at or past which the sandbox counts as short of memory, whatever `freeBytes` says."),
|
|
67
|
+
});
|
|
68
|
+
export type MemoryRoom = z.infer<typeof MemoryRoomSchema>;
|
|
69
|
+
|
|
56
70
|
export const SandboxUsageSchema = z.object({
|
|
57
71
|
cpuPercent: z
|
|
58
72
|
.number()
|
|
59
73
|
.optional()
|
|
60
74
|
.describe("CPU the whole sandbox used over the window, as a percentage of all it may use (`cores`). Absent on a first reading."),
|
|
61
75
|
cores: z.number().describe("How many cores the sandbox may use: its CPU quota, or every core it is allowed to run on when it has none."),
|
|
62
|
-
memoryBytes: z
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
memoryBytes: z
|
|
77
|
+
.number()
|
|
78
|
+
.describe(
|
|
79
|
+
"Memory in use as the daemon admits work by it: resident memory less the file cache the kernel takes back on demand, plus what was pushed to swap.",
|
|
80
|
+
),
|
|
81
|
+
memoryLimitBytes: z
|
|
82
|
+
.number()
|
|
83
|
+
.describe(
|
|
84
|
+
"The memory limit work is admitted against: where the kernel starts throttling the container (memory.high), else its hard limit, else the machine's memory.",
|
|
85
|
+
),
|
|
86
|
+
swapBytes: z.number().optional().describe("Of `memoryBytes`, what was pushed out to swap. Absent where the sandbox cannot see its own memory."),
|
|
87
|
+
memoryRoom: MemoryRoomSchema.optional().describe("What admission reads off the same reading. Absent from a daemon that predates it."),
|
|
65
88
|
diskBytes: z.number().optional().describe("Space used on the volume the workspace lives on. Absent when the volume would not say."),
|
|
66
89
|
diskTotalBytes: z.number().optional().describe("That volume's size. Absent when the volume would not say."),
|
|
67
90
|
loadAverage: z
|
|
68
91
|
.tuple([z.number(), z.number(), z.number()])
|
|
69
92
|
.describe("The load average over 1, 5 and 15 minutes. It is the machine's, so other sandboxes on it count too."),
|
|
93
|
+
machineCores: z
|
|
94
|
+
.number()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe(
|
|
97
|
+
"Cores the machine's load average reads against: every core the sandbox may be scheduled on, before any quota. Absent from a daemon that predates it.",
|
|
98
|
+
),
|
|
70
99
|
processes: z.number().describe("How many processes are running in the sandbox."),
|
|
71
100
|
pressure: PressureMetricsSchema.optional().describe(
|
|
72
101
|
"How much work waited on CPU, memory or disk lately. Absent where the kernel does not report it.",
|
package/src/schemas/personas.ts
CHANGED
|
@@ -42,6 +42,21 @@ export const PersonaPowersSchema = z.object({
|
|
|
42
42
|
connectors: z.array(entryId).max(100).optional(),
|
|
43
43
|
devices: z.array(entryId).max(50).optional(),
|
|
44
44
|
mcp: z.array(entryId).max(50).optional(),
|
|
45
|
+
// By extension id (`publisher.name`, or a git install's card id), the same tri-state. A card an extension serves
|
|
46
|
+
// rides `connectors`; this is what the extension brings of its own.
|
|
47
|
+
extensions: z
|
|
48
|
+
.array(
|
|
49
|
+
z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(121)
|
|
53
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/),
|
|
54
|
+
)
|
|
55
|
+
.max(100)
|
|
56
|
+
.optional()
|
|
57
|
+
.describe(
|
|
58
|
+
"Which extensions' own agent tools and agent plugin (skills, commands, subagents) it gets, by extension id. Absent means every enabled one; empty means none. The tools an extension serves for a connected card follow the connectors list instead.",
|
|
59
|
+
),
|
|
45
60
|
});
|
|
46
61
|
export type PersonaPowers = z.infer<typeof PersonaPowersSchema>;
|
|
47
62
|
// `folders` only refuses file-tool calls outside it; it stops a misread instruction, not a shell. The container is the
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ChildRunSchema } from "../../events/requests.js";
|
|
2
3
|
import { TRANSLATOR_PROVIDERS, type TranslatorProvider } from "../../models/provider-specs.js";
|
|
3
4
|
import { AgentHarnessSchema, AgentProviderSchema, EditorContextSchema } from "../agent.js";
|
|
4
5
|
import { MENTION_LIMIT } from "../../text/mentions.js";
|
|
@@ -44,6 +45,38 @@ export const AccountUsageSchema = z.object({
|
|
|
44
45
|
),
|
|
45
46
|
});
|
|
46
47
|
export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
48
|
+
// Who has to act for a blocked account, which is the only thing a surface chooses its instruction by: the reason is the
|
|
49
|
+
// words for a person, never a key.
|
|
50
|
+
export const AccountFixSchema = z.enum(["reconnect", "admin", "wait"]);
|
|
51
|
+
export type AccountFix = z.infer<typeof AccountFixSchema>;
|
|
52
|
+
// Whether an account can serve a turn, decided once by the daemon (models/plan-pools.ts `serviceState`) from every fact
|
|
53
|
+
// it holds about it: a revoked sign-in, a lost seat, a translator bench, a refusal still standing, and its plan limits.
|
|
54
|
+
// Every picker (an unnamed turn, a limit move, a keep-warm refresh) and every surface reads this one verdict.
|
|
55
|
+
export const AccountStateSchema = z.discriminatedUnion("kind", [
|
|
56
|
+
z.object({
|
|
57
|
+
kind: z.literal("ready"),
|
|
58
|
+
room: z
|
|
59
|
+
.number()
|
|
60
|
+
.describe("How much of the fullest pool that gates the turn is left, in percent (above 0, up to 100). Pickers take the most room."),
|
|
61
|
+
}),
|
|
62
|
+
z.object({
|
|
63
|
+
kind: z.literal("spent"),
|
|
64
|
+
reopensAt: z
|
|
65
|
+
.number()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("When every full pool has reopened, in epoch seconds, where the plan publishes it. Absent means unknown, never now."),
|
|
68
|
+
}),
|
|
69
|
+
z.object({
|
|
70
|
+
kind: z.literal("blocked"),
|
|
71
|
+
fix: AccountFixSchema.describe(
|
|
72
|
+
"Who can make it serve again: `reconnect` (sign in again on this sandbox), `admin` (an organisation admin hands the seat back), or `wait` (it lifts by itself, at `until` where known).",
|
|
73
|
+
),
|
|
74
|
+
reason: z.string().describe("Why, in words a person can act on: the provider's own sentence where it gave one."),
|
|
75
|
+
until: z.number().optional().describe("When waiting lifts it, in epoch seconds, for `wait` only."),
|
|
76
|
+
}),
|
|
77
|
+
z.object({ kind: z.literal("unknown").describe("Nothing blocks it and nothing has been measured: usable, never read as room.") }),
|
|
78
|
+
]);
|
|
79
|
+
export type AccountState = z.infer<typeof AccountStateSchema>;
|
|
47
80
|
// Anthropic's once-a-week reset of the session window only; the weekly allowance is untouched. The answer is entirely
|
|
48
81
|
// the provider's — never infer `available` from a 100% window. `reason` is its own word, carried verbatim.
|
|
49
82
|
export const LimitResetStatusSchema = z.object({
|
|
@@ -120,6 +153,9 @@ export const TranslatorAccountSchema = z.object({
|
|
|
120
153
|
reason: z.string().optional(),
|
|
121
154
|
})
|
|
122
155
|
.optional(),
|
|
156
|
+
state: AccountStateSchema.optional().describe(
|
|
157
|
+
"Whether it can serve a turn now, judged from everything above plus the provider's last refusal. Absent from a daemon older than this field.",
|
|
158
|
+
),
|
|
123
159
|
});
|
|
124
160
|
export type TranslatorAccount = z.infer<typeof TranslatorAccountSchema>;
|
|
125
161
|
// A list per provider, not a flag: CLIProxyAPI holds several auth files per provider and balances across them. Built
|
|
@@ -167,6 +203,10 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
|
|
|
167
203
|
.enum(["once", "always", "deny"])
|
|
168
204
|
.describe("Once allows this call alone; always allows that whole tool for the rest of the conversation; no blocks it."),
|
|
169
205
|
feedback: z.string().optional().describe("Why not, which goes back to the model as the reason."),
|
|
206
|
+
// Whole, not a patch: effort or an account named for one model means nothing on another.
|
|
207
|
+
child: ChildRunSchema.optional().describe(
|
|
208
|
+
"For a request to start a child agent: what to start it on instead of what the agent asked for. It replaces the whole run (model, account, effort and the rest), not only the fields it names. Ignored with a no, and on any other request.",
|
|
209
|
+
),
|
|
170
210
|
}),
|
|
171
211
|
z.object({
|
|
172
212
|
kind: z
|
|
@@ -221,6 +261,11 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
|
|
|
221
261
|
}),
|
|
222
262
|
]);
|
|
223
263
|
export type AgentReply = z.infer<typeof AgentReplySchema>;
|
|
264
|
+
// Every card a turn can park on its person with, one per reply kind, so a new card reaches every park reader at once.
|
|
265
|
+
export type ParkKind = AgentReply["kind"];
|
|
266
|
+
export const PARK_KINDS: readonly ParkKind[] = AgentReplySchema.options.map((option) => option.shape.kind.value);
|
|
267
|
+
const PARKING = new Set<string>(PARK_KINDS);
|
|
268
|
+
export const isParkKind = (kind: string): kind is ParkKind => PARKING.has(kind);
|
|
224
269
|
// A message injected into a running turn between tool calls; NOT_FOUND when none is steerable, and the client queues it
|
|
225
270
|
// as the next turn instead. Carries everything a fresh prompt can (files, editor context).
|
|
226
271
|
export const SteerSchema = z
|
|
@@ -295,7 +340,12 @@ export type StopResult = z.infer<typeof StopResultSchema>;
|
|
|
295
340
|
export const ResumeRoutingSchema = z.object({
|
|
296
341
|
agent: AgentProviderSchema.describe("Which provider serves the re-run."),
|
|
297
342
|
harness: AgentHarnessSchema.describe("Which agentic loop runs it."),
|
|
298
|
-
account: z
|
|
343
|
+
account: z
|
|
344
|
+
.string()
|
|
345
|
+
.optional()
|
|
346
|
+
.describe(
|
|
347
|
+
"Which of that provider's accounts pays for it. Leave it out to keep the account the conversation runs on, or, on another provider, to take whichever of its accounts can serve with the most room. Moving to another account of the same provider is `switchAccount`'s job; naming one here still works.",
|
|
348
|
+
),
|
|
299
349
|
// A client whose catalog hasn't loaded has nothing to send; the turn's own model is the better fallback.
|
|
300
350
|
model: z.string().optional().describe("Which model. Leave it out to keep the one the refused turn named."),
|
|
301
351
|
// A session is a file the daemon keeps; a credential is an env var. That's what lets a same-provider account switch
|
|
@@ -339,3 +389,23 @@ export const QueueResumeSchema = z.object({
|
|
|
339
389
|
),
|
|
340
390
|
});
|
|
341
391
|
export type QueueResume = z.infer<typeof QueueResumeSchema>;
|
|
392
|
+
// Moves a conversation to another account of the provider it runs on: the one command that changes who pays, so the
|
|
393
|
+
// account a conversation runs on is always the daemon's record and never a window's guess.
|
|
394
|
+
export const SwitchAccountSchema = z.object({
|
|
395
|
+
conversationId: z.string().min(1).describe("Which conversation to move."),
|
|
396
|
+
account: z.string().min(1).describe("Which of the conversation's provider's connected accounts pays for its turns from now on."),
|
|
397
|
+
carry: z
|
|
398
|
+
.boolean()
|
|
399
|
+
.optional()
|
|
400
|
+
.describe(
|
|
401
|
+
"Keep the provider session across the move (the model keeps everything, and re-reads all of it once on the other account) rather than opening a fresh one seeded from the record.",
|
|
402
|
+
),
|
|
403
|
+
});
|
|
404
|
+
export type SwitchAccount = z.infer<typeof SwitchAccountSchema>;
|
|
405
|
+
export const AccountSwitchedSchema = z.object({
|
|
406
|
+
run: z
|
|
407
|
+
.string()
|
|
408
|
+
.optional()
|
|
409
|
+
.describe("The run that re-ran a held turn on the new account, when one was waiting: attach to it. Absent when nothing was held."),
|
|
410
|
+
});
|
|
411
|
+
export type AccountSwitched = z.infer<typeof AccountSwitchedSchema>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { AccountUsageSchema } from "./plan-limits.js";
|
|
2
|
+
import { AccountStateSchema, AccountUsageSchema } from "./plan-limits.js";
|
|
3
3
|
import { KeyedProviderSchema } from "./provider-subscriptions.js";
|
|
4
4
|
// Claude uses PKCE paste-back (start → exchange); Codex uses OpenAI's device-code flow (start → poll). A sandbox can
|
|
5
5
|
// hold several accounts per provider; `id` is the store key, `label` the display name. Tokens never ride this shape —
|
|
@@ -15,6 +15,12 @@ export const OauthAccountSchema = z.object({
|
|
|
15
15
|
"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.",
|
|
16
16
|
),
|
|
17
17
|
organization: z.string().optional().describe("Which organisation it belongs to, where the provider says."),
|
|
18
|
+
variant: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe(
|
|
22
|
+
"Which of the provider's estates it signs in to, for a provider selling more than one (Z.ai's international or mainland plan). With the email and organisation, it is who the account is: a sign-in matching all three lands on this account again, same id.",
|
|
23
|
+
),
|
|
18
24
|
scope: z.string().optional().describe("What the credential is permitted to do, in the provider's terms."),
|
|
19
25
|
connectedAt: z.number().describe("When it was connected, in milliseconds."),
|
|
20
26
|
needsReauth: z
|
|
@@ -31,6 +37,9 @@ export const OauthAccountSchema = z.object({
|
|
|
31
37
|
usage: AccountUsageSchema.optional().describe(
|
|
32
38
|
"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.",
|
|
33
39
|
),
|
|
40
|
+
state: AccountStateSchema.optional().describe(
|
|
41
|
+
"Whether it can serve a turn now, judged once from the sign-in, the seat, the provider's last refusal and the plan limits: the verdict every picker in the sandbox uses. Read this rather than the fields it was judged from. Absent from a daemon older than this field.",
|
|
42
|
+
),
|
|
34
43
|
});
|
|
35
44
|
export type OauthAccount = z.infer<typeof OauthAccountSchema>;
|
|
36
45
|
export const OauthAccountListSchema = z.object({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { dropAll, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
3
|
-
import type { RuleSchema } from "./settings.js";
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
3
|
+
import type { KeepWarmSettings, RuleSchema } from "./settings.js";
|
|
4
4
|
|
|
5
5
|
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
6
6
|
// SandboxSettingsSchema. Two documents carry this shape, the daemon's settings.json and a sandbox.toml's [settings]
|
|
@@ -23,6 +23,46 @@ const isRetiredRule = (rule: unknown): boolean => {
|
|
|
23
23
|
|
|
24
24
|
const holdsRetiredRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isRetiredRule);
|
|
25
25
|
|
|
26
|
+
// A rule at the `turn.ending` moment reads and runs nothing since nothing checks inside a turn (its moment and the
|
|
27
|
+
// `verify-ui-edits` built-in stay in the read schema so a file written before reads); dropped, so it stops showing as a
|
|
28
|
+
// rule the owner has and stops travelling with the settings.
|
|
29
|
+
const isInertRule = (rule: unknown): boolean =>
|
|
30
|
+
isJsonObject(rule) &&
|
|
31
|
+
(rule["moment"] === "turn.ending" || (isJsonObject(rule["action"]) && rule["action"]["kind"] === "builtin" && rule["action"]["name"] === "verify-ui-edits"));
|
|
32
|
+
|
|
33
|
+
const holdsInertRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isInertRule);
|
|
34
|
+
|
|
35
|
+
// keep-warm's four top-level settings (2026-09-26), folded into the one `keepWarm` object; a value of the wrong type is
|
|
36
|
+
// left out, for the schema's own default to fill.
|
|
37
|
+
const KEEP_WARM_FLAT = ["keepWarm", "keepWarmHours", "keepWarmMinTokens", "keepWarmReserve"] as const;
|
|
38
|
+
const FlatSwitch = z.boolean();
|
|
39
|
+
const FlatNumber = z.number();
|
|
40
|
+
const keepWarmObject = fold("folds keepWarm, keepWarmHours, keepWarmMinTokens and keepWarmReserve into one keepWarm object", {
|
|
41
|
+
from: KEEP_WARM_FLAT,
|
|
42
|
+
into: "keepWarm",
|
|
43
|
+
applies: (settings) => FlatSwitch.safeParse(settings["keepWarm"]).success || KEEP_WARM_FLAT.slice(1).some((key) => Object.hasOwn(settings, key)),
|
|
44
|
+
convert: ({ keepWarm, keepWarmHours, keepWarmMinTokens, keepWarmReserve }): Partial<KeepWarmSettings> => {
|
|
45
|
+
const folded: Partial<KeepWarmSettings> = {};
|
|
46
|
+
const auto = FlatSwitch.safeParse(keepWarm);
|
|
47
|
+
const hours = FlatNumber.safeParse(keepWarmHours);
|
|
48
|
+
const minTokens = FlatNumber.safeParse(keepWarmMinTokens);
|
|
49
|
+
const reserve = FlatNumber.safeParse(keepWarmReserve);
|
|
50
|
+
if (auto.success) {
|
|
51
|
+
folded.auto = auto.data;
|
|
52
|
+
}
|
|
53
|
+
if (hours.success) {
|
|
54
|
+
folded.hours = hours.data;
|
|
55
|
+
}
|
|
56
|
+
if (minTokens.success) {
|
|
57
|
+
folded.minTokens = minTokens.data;
|
|
58
|
+
}
|
|
59
|
+
if (reserve.success) {
|
|
60
|
+
folded.reserve = reserve.data;
|
|
61
|
+
}
|
|
62
|
+
return folded;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
26
66
|
export const SETTINGS_HISTORY = [
|
|
27
67
|
// Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
|
|
28
68
|
// retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
|
|
@@ -58,4 +98,11 @@ export const SETTINGS_HISTORY = [
|
|
|
58
98
|
),
|
|
59
99
|
// suggest, the old default, becomes the new default: routing on.
|
|
60
100
|
mapValue("personaRouting", { off: false, suggest: true, auto: true }),
|
|
101
|
+
retype(
|
|
102
|
+
"rules",
|
|
103
|
+
holdsInertRule,
|
|
104
|
+
(rules) => rules.filter((rule) => !isInertRule(rule)) as z.input<typeof RuleSchema>[],
|
|
105
|
+
"drops rules at the retired turn.ending moment (and the verify-ui-edits built-in), which run nothing",
|
|
106
|
+
),
|
|
107
|
+
keepWarmObject,
|
|
61
108
|
] as const;
|