@intentic/sandbox-contract 1.244.0 → 1.246.1
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 +17 -1
- package/dist/batch-runs.d.ts +32 -0
- package/dist/batch-runs.d.ts.map +1 -0
- package/dist/batch-runs.js +43 -0
- package/dist/batch-runs.js.map +1 -0
- package/dist/command-classes.d.ts +6 -3
- package/dist/command-classes.d.ts.map +1 -1
- package/dist/command-classes.js +43 -18
- package/dist/command-classes.js.map +1 -1
- package/dist/contracts/{cursor.contract.d.ts → accounts.contract.d.ts} +102 -3
- package/dist/contracts/accounts.contract.d.ts.map +1 -0
- package/dist/contracts/accounts.contract.js +61 -0
- package/dist/contracts/accounts.contract.js.map +1 -0
- package/dist/contracts/agents.contract.d.ts +121 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +4 -4
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +2 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.d.ts +35 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/personas.contract.d.ts +4 -2
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +2 -2
- package/dist/contracts/settings.contract.d.ts +2 -51
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +52 -2
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +26 -17
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/definition.d.ts +4 -8
- package/dist/definition.d.ts.map +1 -1
- package/dist/documents.d.ts +0 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1 -2
- package/dist/documents.js.map +1 -1
- package/dist/embed.d.ts +23 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +84 -0
- package/dist/embed.js.map +1 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +5 -2
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +2 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +354 -335
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -13
- package/dist/index.js.map +1 -1
- package/dist/peer-dial.d.ts +33 -0
- package/dist/peer-dial.d.ts.map +1 -0
- package/dist/peer-dial.js +79 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/peer-mcp-server.d.ts +36 -0
- package/dist/peer-mcp-server.d.ts.map +1 -0
- package/dist/peer-mcp-server.js +71 -0
- package/dist/peer-mcp-server.js.map +1 -0
- package/dist/provider-specs.d.ts +38 -20
- package/dist/provider-specs.d.ts.map +1 -1
- package/dist/provider-specs.js +39 -13
- package/dist/provider-specs.js.map +1 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +2 -1
- package/dist/quick-model.js.map +1 -1
- package/dist/runtime-state.d.ts +1 -1
- package/dist/runtime-state.js +1 -1
- package/dist/runtime-state.js.map +1 -1
- package/dist/safety-policy.d.ts +12 -3
- package/dist/safety-policy.d.ts.map +1 -1
- package/dist/safety-policy.js +30 -5
- package/dist/safety-policy.js.map +1 -1
- package/dist/schemas/agent.d.ts +6 -6
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +6 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +42 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +25 -4
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +11 -2
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js +1 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/ci.d.ts +6 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +3 -2
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/context.d.ts +30 -0
- package/dist/schemas/context.d.ts.map +1 -0
- package/dist/schemas/context.js +34 -0
- package/dist/schemas/context.js.map +1 -0
- package/dist/schemas/{computers.d.ts → devices.d.ts} +155 -61
- package/dist/schemas/devices.d.ts.map +1 -0
- package/dist/schemas/devices.js +157 -0
- package/dist/schemas/devices.js.map +1 -0
- package/dist/schemas/hosts.d.ts +12 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +1 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/issues.d.ts +0 -5
- package/dist/schemas/issues.d.ts.map +1 -1
- package/dist/schemas/issues.js +0 -1
- package/dist/schemas/issues.js.map +1 -1
- package/dist/schemas/personas.d.ts +5 -3
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +3 -2
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/provider-oauth.d.ts +48 -16
- package/dist/schemas/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/provider-oauth.js +22 -20
- package/dist/schemas/provider-oauth.js.map +1 -1
- package/dist/schemas/settings.d.ts +1 -52
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +8 -11
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/terminal.js +9 -9
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/usage.d.ts +0 -2
- package/dist/schemas/usage.d.ts.map +1 -1
- package/dist/schemas/usage.js +0 -2
- package/dist/schemas/usage.js.map +1 -1
- package/dist/shell-regions.d.ts +4 -0
- package/dist/shell-regions.d.ts.map +1 -0
- package/dist/shell-regions.js +156 -0
- package/dist/shell-regions.js.map +1 -0
- package/dist/workspace-state.d.ts +15 -0
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +21 -5
- package/dist/workspace-state.js.map +1 -1
- package/package.json +48 -4
- package/src/agent-catalog.ts +1 -1
- package/src/arrival.ts +3 -3
- package/src/batch-runs.test.ts +167 -0
- package/src/batch-runs.ts +181 -0
- package/src/command-classes.test.ts +195 -71
- package/src/command-classes.ts +148 -46
- package/src/contracts/accounts.contract.ts +94 -0
- package/src/contracts/agents.contract.ts +4 -3
- package/src/contracts/exit.contract.ts +2 -2
- package/src/contracts/host.contract.ts +17 -5
- package/src/contracts/settings.contract.ts +1 -1
- package/src/contracts/system.contract.ts +43 -24
- package/src/contracts/vpn.contract.ts +2 -2
- package/src/documents.test.ts +2 -1
- package/src/documents.ts +7 -11
- package/src/embed.test.ts +68 -0
- package/src/embed.ts +164 -0
- package/src/events.ts +30 -3
- package/src/history-state.ts +12 -3
- package/src/host-protocol.ts +2 -2
- package/src/index.ts +6 -14
- package/src/model-order.ts +1 -1
- package/src/peer-dial.test.ts +203 -0
- package/src/peer-dial.ts +163 -0
- package/src/peer-mcp-server.test.ts +104 -0
- package/src/peer-mcp-server.ts +144 -0
- package/src/provider-specs.test.ts +44 -17
- package/src/provider-specs.ts +147 -67
- package/src/quick-model.ts +10 -3
- package/src/routes.test.ts +6 -3
- package/src/runner-protocol.ts +1 -1
- package/src/runtime-state.ts +2 -2
- package/src/safety-policy.test.ts +88 -0
- package/src/safety-policy.ts +84 -14
- package/src/schemas/agent.ts +41 -13
- package/src/schemas/agents.ts +65 -4
- package/src/schemas/automations.ts +6 -4
- package/src/schemas/capabilities.ts +4 -4
- package/src/schemas/ci.ts +23 -6
- package/src/schemas/context.ts +87 -0
- package/src/schemas/{computers.ts → devices.ts} +201 -109
- package/src/schemas/hosts.ts +5 -1
- package/src/schemas/issues.ts +0 -4
- package/src/schemas/personas.ts +8 -3
- package/src/schemas/provider-oauth.ts +49 -52
- package/src/schemas/settings.ts +30 -54
- package/src/schemas/terminal.ts +12 -12
- package/src/schemas/usage.ts +3 -27
- package/src/schemas/version-seam.test.ts +0 -1
- package/src/shell-regions.ts +289 -0
- package/src/versions.ts +2 -2
- package/src/webext-links.ts +2 -2
- package/src/webext-protocol.ts +2 -2
- package/src/workspace-state.test.ts +55 -1
- package/src/workspace-state.ts +76 -11
- package/dist/contracts/claude.contract.d.ts +0 -91
- package/dist/contracts/claude.contract.d.ts.map +0 -1
- package/dist/contracts/claude.contract.js +0 -50
- package/dist/contracts/claude.contract.js.map +0 -1
- package/dist/contracts/cursor.contract.d.ts.map +0 -1
- package/dist/contracts/cursor.contract.js +0 -50
- package/dist/contracts/cursor.contract.js.map +0 -1
- package/dist/contracts/grok.contract.d.ts +0 -36
- package/dist/contracts/grok.contract.d.ts.map +0 -1
- package/dist/contracts/grok.contract.js +0 -31
- package/dist/contracts/grok.contract.js.map +0 -1
- package/dist/contracts/keys.contract.d.ts +0 -81
- package/dist/contracts/keys.contract.d.ts.map +0 -1
- package/dist/contracts/keys.contract.js +0 -51
- package/dist/contracts/keys.contract.js.map +0 -1
- package/dist/schemas/computers.d.ts.map +0 -1
- package/dist/schemas/computers.js +0 -134
- package/dist/schemas/computers.js.map +0 -1
- package/src/contracts/claude.contract.ts +0 -71
- package/src/contracts/cursor.contract.ts +0 -74
- package/src/contracts/grok.contract.ts +0 -41
- package/src/contracts/keys.contract.ts +0 -79
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
//
|
|
1
|
+
// devices: what ONE of the user's own machines is running
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { HostFactsSchema } from "./hosts.js";
|
|
4
|
+
// The one sentinel every non-release build carries, so a locally compiled agent is never told it is behind.
|
|
5
|
+
import { DEV_VERSION } from "../versions.js";
|
|
4
6
|
/* The other end of desktop sync, stated as a fact instead of a claim.
|
|
5
7
|
*
|
|
6
8
|
* Everything here already existed, as the machine agent's printed status on a terminal nobody running the desktop app
|
|
@@ -24,8 +26,47 @@ import { HostFactsSchema } from "./hosts.js";
|
|
|
24
26
|
* `mirror` enrollment, a collaborator's own laptop, drops `localDir` with it. So a member who mirrors one
|
|
25
27
|
* dev-server port does not hand the sandbox's owner a map of their machine. */
|
|
26
28
|
|
|
29
|
+
/* ONE SANDBOX'S SHARE OF ITS MACHINE, as docker enforces it right now: read off the container's HostConfig
|
|
30
|
+
* and env by the machine agent, never asked of the sandbox (which cannot see its own cgroup ceiling as a
|
|
31
|
+
* docker flag, only as a number in /sys/fs/cgroup).
|
|
32
|
+
*
|
|
33
|
+
* The two `*Runtime` lists are the same directive vocabulary the run contract allowlists
|
|
34
|
+
* (@intentic/sandbox-run RUNTIME_DIRECTIVES), split by WHO asked: `overlayRuntime` is what the approved
|
|
35
|
+
* environment demands (a view draws those locked — "required by the Docker capability"), `hostRuntime` is
|
|
36
|
+
* what the owner asked for on top and may withdraw. `privileged` and `gpu` are the docker-side truth of the
|
|
37
|
+
* union: what the container actually got, after a host without the nvidia runtime dropped the GPU. */
|
|
38
|
+
export const SandboxResourcesSchema = z.object({
|
|
39
|
+
// The cgroup memory ceiling in bytes; absent when docker imposes none (the hosted shape).
|
|
40
|
+
memoryBytes: z.number().optional(),
|
|
41
|
+
// The CFS quota as whole cores; absent when the container may use every core (the default).
|
|
42
|
+
cpus: z.number().optional(),
|
|
43
|
+
privileged: z.boolean(),
|
|
44
|
+
gpu: z.boolean(),
|
|
45
|
+
hostRuntime: z.array(z.string()),
|
|
46
|
+
overlayRuntime: z.array(z.string()),
|
|
47
|
+
});
|
|
48
|
+
export type SandboxResources = z.infer<typeof SandboxResourcesSchema>;
|
|
49
|
+
|
|
50
|
+
/* WHAT A RESHAPE ASKS FOR: the Resources dialog's answer, the `reshape` op's payload, and the shape the
|
|
51
|
+
* machine turns into `ic sandbox reshape` flags. Every key is "leave it" when absent; the two caps take
|
|
52
|
+
* `null` for "back to the default" (the machine-derived memory share; every core), which `ic` spells as
|
|
53
|
+
* `default` and the run contract as an empty seed. At least one key, because a reshape with nothing to change
|
|
54
|
+
* is a restart for nothing, and the machine refuses it before anything is spawned. */
|
|
55
|
+
export const SandboxResourcesAskFieldsSchema = z.object({
|
|
56
|
+
memoryGib: z.int().positive().nullable().optional(),
|
|
57
|
+
cpus: z.int().positive().nullable().optional(),
|
|
58
|
+
privileged: z.boolean().optional(),
|
|
59
|
+
gpu: z.boolean().optional(),
|
|
60
|
+
});
|
|
61
|
+
// The fields alone are exported too, for a caller that composes them into a wider object (the machine's MCP
|
|
62
|
+
// tool adds the slug beside them) and applies the at-least-one rule itself.
|
|
63
|
+
export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.refine((ask) => Object.values(ask).some((value) => value !== undefined), {
|
|
64
|
+
message: "a reshape must change at least one thing",
|
|
65
|
+
});
|
|
66
|
+
export type SandboxResourcesAsk = z.infer<typeof SandboxResourcesAskSchema>;
|
|
67
|
+
|
|
27
68
|
// One sandbox container on the machine, the docker half, filled in by the reader, never by the sync agent.
|
|
28
|
-
export const
|
|
69
|
+
export const DeviceSandboxSchema = z.object({
|
|
29
70
|
slug: z.string(),
|
|
30
71
|
container: z.string(),
|
|
31
72
|
// The display name, when the machine has one recorded. Docker knows only the container name.
|
|
@@ -35,9 +76,12 @@ export const MachineSandboxSchema = z.object({
|
|
|
35
76
|
// Absent when the sandbox has no cloudflared sidecar AT ALL (reached over the user's own proxy), which is
|
|
36
77
|
// not the same fact as a sidecar that is down, and must not render as one.
|
|
37
78
|
tunnelRunning: z.boolean().optional(),
|
|
79
|
+
// Its share of the machine (above). Absent from a reader that did not inspect the container: the cheap
|
|
80
|
+
// `docker ps` listing the machine's own fleet reads use carries none of it.
|
|
81
|
+
resources: SandboxResourcesSchema.optional(),
|
|
38
82
|
});
|
|
39
|
-
export type
|
|
40
|
-
/* ONE OPERATION ON ONE SANDBOX ON ONE MACHINE, the
|
|
83
|
+
export type DeviceSandbox = z.infer<typeof DeviceSandboxSchema>;
|
|
84
|
+
/* ONE OPERATION ON ONE SANDBOX ON ONE MACHINE, the Devices view's buttons, and the only thing that changes a
|
|
41
85
|
* machine's fleet from a browser.
|
|
42
86
|
*
|
|
43
87
|
* All nine ops travel one route because they are one decision to the person clicking, however differently they
|
|
@@ -54,6 +98,11 @@ export type MachineSandbox = z.infer<typeof MachineSandboxSchema>;
|
|
|
54
98
|
* same row as the others, on a container that may be too broken to answer any other way, and the stream shape
|
|
55
99
|
* already carries "many lines, then an outcome" exactly as a log tail wants to arrive.
|
|
56
100
|
*
|
|
101
|
+
* `reshape` is the one that changes the CONTAINER without changing its image: its share of the machine (memory
|
|
102
|
+
* and CPU caps) and its privileges (privileged, the host's GPU), recreated onto the same image so the values
|
|
103
|
+
* live on the container and outlive every later swap. It carries `resources`, the only op with a payload of
|
|
104
|
+
* its own besides `rebuild`'s hash.
|
|
105
|
+
*
|
|
57
106
|
* The machine enforces which of them it will do: `sandboxes` covers everything but removal, which takes its own
|
|
58
107
|
* switch, and a refusal comes back as the machine's own sentence naming the control to flip. */
|
|
59
108
|
/* `runner-up` / `runner-remove` are the same door for a container that belongs to THIS SANDBOX rather than to
|
|
@@ -62,7 +111,7 @@ export type MachineSandbox = z.infer<typeof MachineSandboxSchema>;
|
|
|
62
111
|
* clicking they are the same row of buttons. Both take the `sandboxes` switch and neither takes the removal
|
|
63
112
|
* one: a runner holds no workspace of its own, only a mirror of the parent's git, so removing it destroys
|
|
64
113
|
* nothing the parent does not still have. */
|
|
65
|
-
export const
|
|
114
|
+
export const DeviceSandboxOpSchema = z.enum([
|
|
66
115
|
"start",
|
|
67
116
|
"stop",
|
|
68
117
|
"restart",
|
|
@@ -70,20 +119,23 @@ export const MachineSandboxOpSchema = z.enum([
|
|
|
70
119
|
"update",
|
|
71
120
|
"rebuild",
|
|
72
121
|
"rollback",
|
|
122
|
+
"reshape",
|
|
73
123
|
"remove",
|
|
74
124
|
"logs",
|
|
75
125
|
"runner-up",
|
|
76
126
|
"runner-remove",
|
|
77
127
|
]);
|
|
78
|
-
export type
|
|
79
|
-
export const
|
|
80
|
-
op:
|
|
128
|
+
export type DeviceSandboxOp = z.infer<typeof DeviceSandboxOpSchema>;
|
|
129
|
+
export const DeviceSandboxFlowSchema = z.object({
|
|
130
|
+
op: DeviceSandboxOpSchema,
|
|
81
131
|
// Which sandbox, or, for the two runner ops, which RUNNER: the name it is known by at both ends, the
|
|
82
132
|
// parent's `/system/runners` list and the machine's `ic runner list`.
|
|
83
133
|
slug: z.string().min(1),
|
|
84
134
|
// The approved overlay's sha256, required by `rebuild` and meaningless to the rest. It is the trust anchor:
|
|
85
135
|
// only content that still hashes to what the owner reviewed is ever built.
|
|
86
136
|
hash: z.string().optional(),
|
|
137
|
+
// What `reshape` should change, required by it and meaningless to the rest.
|
|
138
|
+
resources: SandboxResourcesAskSchema.optional(),
|
|
87
139
|
/* `runner-up` only, and both are filled in by the DAEMON, never by the caller: where the runner dials
|
|
88
140
|
* (this sandbox's public URL) and the single-use pairing it redeems there. The browser asks for a runner
|
|
89
141
|
* on a machine; it never holds the credential that makes one, which is what keeps a pairing out of every
|
|
@@ -103,29 +155,53 @@ export const MachineSandboxFlowSchema = z.object({
|
|
|
103
155
|
overlay: z.string().optional(),
|
|
104
156
|
overlayHash: z.string().optional(),
|
|
105
157
|
});
|
|
106
|
-
export type
|
|
158
|
+
export type DeviceSandboxFlow = z.infer<typeof DeviceSandboxFlowSchema>;
|
|
107
159
|
// The same input plus which machine it is for, the browser's half, since the daemon reaches the machine by id.
|
|
108
|
-
export const
|
|
109
|
-
export type
|
|
160
|
+
export const DeviceSandboxFlowInputSchema = DeviceSandboxFlowSchema.extend({ id: z.string().min(1) });
|
|
161
|
+
export type DeviceSandboxFlowInput = z.infer<typeof DeviceSandboxFlowInputSchema>;
|
|
110
162
|
/* What a running operation says, in the one line shape every streamed flow in this product already uses
|
|
111
163
|
* (IntenticLineSchema, which the browser's reader parses): `line` as the machine prints it, then exactly one
|
|
112
164
|
* terminal frame, `result` when it worked, `error` when it did not, carrying the machine's own words either
|
|
113
165
|
* way rather than a code this side invented. */
|
|
114
|
-
export const
|
|
166
|
+
export const DeviceFlowLineSchema = z.discriminatedUnion("kind", [
|
|
115
167
|
z.object({ kind: z.literal("line"), text: z.string() }),
|
|
116
168
|
z.object({ kind: z.literal("result"), message: z.string() }),
|
|
117
169
|
z.object({ kind: z.literal("error"), message: z.string() }),
|
|
118
170
|
]);
|
|
119
|
-
export type
|
|
120
|
-
/*
|
|
171
|
+
export type DeviceFlowLine = z.infer<typeof DeviceFlowLineSchema>;
|
|
172
|
+
/* UPDATING OR RESTARTING THE AGENT ITSELF, from the row that reports its version.
|
|
173
|
+
*
|
|
174
|
+
* THE OPERATION THAT KILLS ITS OWN TRANSPORT, and that is the whole reason it is a flow of its own rather than
|
|
175
|
+
* another entry in the DeviceCommand set below. `run_command` runs as a child of the resident process, and both
|
|
176
|
+
* of these stop that process: `upgrade` calls stopResident() before it swaps the binary, and `run` stops before
|
|
177
|
+
* it starts. So the socket carrying the request dies mid-command, and the child's stdout pipe dies with it —
|
|
178
|
+
* an EPIPE on `process.stdout` is an uncaught exception in Node. An upgrade killed between
|
|
179
|
+
* `swap(agentPath, previous)` and `swap(staged, agentPath)` leaves a device with NO `intentic-machine` binary
|
|
180
|
+
* and a login entry pointing at a missing file, which is the one outcome worse than being out of date.
|
|
181
|
+
*
|
|
182
|
+
* The agent therefore runs the work DETACHED (spawnDetached, exactly as it launches its own loop) and streams
|
|
183
|
+
* its log while it can. The stream ending with no terminal frame is the NORMAL case here, not a failure, and
|
|
184
|
+
* confirmation is the version moving on the view's next poll — which is also the only honest confirmation: the
|
|
185
|
+
* CLI's own `loop-behind` outcome exists because "a process came up" does not mean the new build is serving.
|
|
121
186
|
*
|
|
122
|
-
*
|
|
187
|
+
* `restart` needs no new CLI verb: bare `intentic-machine run` is a restart (reconcileResidency stops before it
|
|
188
|
+
* starts), and it is the remedy for both a stalled loop and one serving an older build than the file beside it. */
|
|
189
|
+
export const DeviceAgentOpSchema = z.enum(["upgrade", "restart"]);
|
|
190
|
+
export type DeviceAgentOp = z.infer<typeof DeviceAgentOpSchema>;
|
|
191
|
+
export const DeviceAgentFlowSchema = z.object({ op: DeviceAgentOpSchema });
|
|
192
|
+
export type DeviceAgentFlow = z.infer<typeof DeviceAgentFlowSchema>;
|
|
193
|
+
// The same input plus which device it is for, the browser's half.
|
|
194
|
+
export const DeviceAgentFlowInputSchema = DeviceAgentFlowSchema.extend({ id: z.string().min(1) });
|
|
195
|
+
export type DeviceAgentFlowInput = z.infer<typeof DeviceAgentFlowInputSchema>;
|
|
196
|
+
/* RUNNING ONE OF THIS PRODUCT'S OWN CLIs ON A CONNECTED DEVICE, FROM A BUTTON, with no agent in the loop.
|
|
197
|
+
*
|
|
198
|
+
* A machine that is connected as a device can already be told things: the ops above drive its docker, and an
|
|
123
199
|
* agent with the `host` capability can run whatever it likes through `run_command`. What had no door was the
|
|
124
200
|
* ordinary case in between — the user wants the thing the CLI on their machine already does, and the sandbox is
|
|
125
201
|
* where they are looking. Their alternatives were to go and find a terminal, or to ask an agent to do it, which
|
|
126
202
|
* spends a turn and a model's judgement on a decision that has none in it.
|
|
127
203
|
*
|
|
128
|
-
* So: a CLOSED SET OF NAMES, and the argv is built on the daemon from the name alone (hosts/
|
|
204
|
+
* So: a CLOSED SET OF NAMES, and the argv is built on the daemon from the name alone (hosts/device-commands.ts).
|
|
129
205
|
* The browser sends `mirror-off`, never a command line. That is the whole security property, and it is the
|
|
130
206
|
* reason this is an enum rather than a string: the same socket carries `run_command`, so a route that forwarded
|
|
131
207
|
* caller-supplied text would hand every browser session a shell on the user's laptop, which is a grant the
|
|
@@ -135,7 +211,7 @@ export type MachineFlowLine = z.infer<typeof MachineFlowLineSchema>;
|
|
|
135
211
|
* own words, naming the control to flip — exactly as it does for the sandbox ops. */
|
|
136
212
|
/* THE SET, and why the file-sync half of it is here beside the mirroring half.
|
|
137
213
|
*
|
|
138
|
-
* Both are the same gesture to the person clicking: something this
|
|
214
|
+
* Both are the same gesture to the person clicking: something this device is doing for this sandbox, turned
|
|
139
215
|
* off or on from the row that describes it. They were split for a while by nothing but which one had been built
|
|
140
216
|
* — mirroring had a button and pausing a file sync had a paragraph telling you to go and find a terminal — and
|
|
141
217
|
* that is exactly the gap this door exists to close.
|
|
@@ -145,8 +221,8 @@ export type MachineFlowLine = z.infer<typeof MachineFlowLineSchema>;
|
|
|
145
221
|
* pairing and self-revokes its enrollment on the way out, so the machine cleans up after itself rather than
|
|
146
222
|
* leaving a sandbox to guess what it managed to do. Revoking from the SANDBOX side (an unreachable machine, a
|
|
147
223
|
* laptop that is never coming back) is a different act and a different route, see the enrollment revoke. */
|
|
148
|
-
export const
|
|
149
|
-
export type
|
|
224
|
+
export const DeviceCommandSchema = z.enum(["mirror-off", "mirror-on", "sync-pause", "sync-resume", "sync-unpair"]);
|
|
225
|
+
export type DeviceCommand = z.infer<typeof DeviceCommandSchema>;
|
|
150
226
|
/* Which paired sandbox the command acts on: the machine's own id for it, as it appears in that machine's report,
|
|
151
227
|
* so nothing here has to re-derive the sanitizing the agent applied. Absent means every sandbox that machine
|
|
152
228
|
* pairs, which is what the CLI does when it is run bare.
|
|
@@ -154,42 +230,42 @@ export type MachineCommand = z.infer<typeof MachineCommandSchema>;
|
|
|
154
230
|
* Pattern-bound because it becomes an argv token. It must start with an alphanumeric, not merely consist of id
|
|
155
231
|
* characters: a value like `--takeover` is made only of legal id characters and is a FLAG by the time the CLI on
|
|
156
232
|
* the machine parses it. Real ids are `sandbox-<hex>-<zone>`-shaped, so nothing legitimate leads with a dash. */
|
|
157
|
-
export const
|
|
233
|
+
export const DeviceSandboxIdSchema = z
|
|
158
234
|
.string()
|
|
159
235
|
.max(200)
|
|
160
236
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
|
|
161
|
-
export const
|
|
237
|
+
export const DeviceCommandInputSchema = z.object({
|
|
162
238
|
id: z.string().min(1),
|
|
163
|
-
command:
|
|
164
|
-
sandboxId:
|
|
239
|
+
command: DeviceCommandSchema,
|
|
240
|
+
sandboxId: DeviceSandboxIdSchema.optional(),
|
|
165
241
|
});
|
|
166
|
-
export type
|
|
242
|
+
export type DeviceCommandInput = z.infer<typeof DeviceCommandInputSchema>;
|
|
167
243
|
/* What came back. `ok` is the command's own exit status, not this route's: a machine that refused the call, or a
|
|
168
244
|
* CLI that exited non-zero, is a real answer to show the person who clicked, not an exception to convert into
|
|
169
245
|
* one. Only an unreachable machine throws, because then there is nothing to report at all.
|
|
170
246
|
*
|
|
171
247
|
* `output` is what the command printed, kept because the CLI's own sentences ("Port mirroring OFF for: …") are
|
|
172
248
|
* better than anything this side would write over them. */
|
|
173
|
-
export const
|
|
249
|
+
export const DeviceCommandResultSchema = z.object({
|
|
174
250
|
ok: z.boolean(),
|
|
175
251
|
message: z.string(),
|
|
176
252
|
output: z.string().optional(),
|
|
177
253
|
});
|
|
178
|
-
export type
|
|
254
|
+
export type DeviceCommandResult = z.infer<typeof DeviceCommandResultSchema>;
|
|
179
255
|
// One paired sandbox as the local agent holds it. `localDir` is the answer to the question the Desktop sync card
|
|
180
|
-
// has never been able to answer: which folder on that
|
|
181
|
-
export const
|
|
256
|
+
// has never been able to answer: which folder on that device this sandbox's /work actually is.
|
|
257
|
+
export const DevicePairingSchema = z.object({
|
|
182
258
|
sandboxId: z.string(),
|
|
183
259
|
mode: z.enum(["sync", "mirror"]),
|
|
184
260
|
// Set only for mode "sync", and only for the sandbox being reported to, see the redaction note above.
|
|
185
261
|
localDir: z.string().optional(),
|
|
186
|
-
/* Whether that
|
|
262
|
+
/* Whether that device is putting this sandbox's ports on its own localhost, which is a switch its owner
|
|
187
263
|
* holds and not a state this sandbox can read off anything else. An empty port list means two opposite
|
|
188
264
|
* things — nothing is listening in the sandbox, or the machine was told to keep them off — and only the
|
|
189
265
|
* second is worth a word on screen or a button to undo.
|
|
190
266
|
*
|
|
191
267
|
* The MACHINE owns the flag (the agent's `sync mirror off`), because the localhost being written to is
|
|
192
|
-
* there: a
|
|
268
|
+
* there: a device told to keep ports off must keep them off while this sandbox is asleep, unreachable, or
|
|
193
269
|
* arguing. A browser asks for it by running that same command over the machine's `host` capability, so the
|
|
194
270
|
* button and the CLI are one gesture rather than two mechanisms that can disagree.
|
|
195
271
|
*
|
|
@@ -211,117 +287,127 @@ export const MachinePairingSchema = z.object({
|
|
|
211
287
|
* running is only dangerous while nobody knows, so it gets its own word on the line. */
|
|
212
288
|
backupStatus: z.string().optional(),
|
|
213
289
|
});
|
|
214
|
-
export type
|
|
290
|
+
export type DevicePairing = z.infer<typeof DevicePairingSchema>;
|
|
215
291
|
/* One sandbox port and what became of it on this machine's localhost. The rows that did NOT make it are the
|
|
216
|
-
* reason this carries a state rather than being a list of live forwards: two sandboxes on one
|
|
292
|
+
* reason this carries a state rather than being a list of live forwards: two sandboxes on one device routinely
|
|
217
293
|
* serve the same dev-server port and only one can own localhost:6480, so the loser's port is simply missing from
|
|
218
294
|
* localhost with nothing anywhere saying why. Today that fact exists only as a line in mirror.log. */
|
|
219
|
-
export const
|
|
295
|
+
export const DevicePortStateSchema = z.enum([
|
|
220
296
|
// Forwarded: the sandbox's listener answers on this machine's localhost at the same number.
|
|
221
297
|
"mirrored",
|
|
222
298
|
// Another PAIRED SANDBOX got there first (first paired wins), `heldBy` names it, because "busy on this
|
|
223
299
|
// machine" sends people hunting for a process that does not exist.
|
|
224
300
|
"held-by-sandbox",
|
|
225
|
-
// Something else on this
|
|
301
|
+
// Something else on this device already binds the port, a local dev server, another tool. Not ours to
|
|
226
302
|
// name, and not ours to take.
|
|
227
303
|
"busy",
|
|
228
304
|
]);
|
|
229
|
-
export const
|
|
305
|
+
export const DevicePortSchema = z.object({
|
|
230
306
|
port: z.number().int().min(1).max(65535),
|
|
231
307
|
host: z.enum(["127.0.0.1", "::1"]),
|
|
232
308
|
// The sandbox serving the port, whose /ports listed it, not whoever ended up holding the local bind.
|
|
233
309
|
sandboxId: z.string(),
|
|
234
|
-
state:
|
|
310
|
+
state: DevicePortStateSchema,
|
|
235
311
|
// Set only for "held-by-sandbox": the sandbox id that owns the local bind instead.
|
|
236
312
|
heldBy: z.string().optional(),
|
|
237
313
|
// What is listening on the sandbox side ("node …/vite"), for a row the user has to recognise to act on.
|
|
238
314
|
command: z.string().optional(),
|
|
239
315
|
});
|
|
240
|
-
export type
|
|
241
|
-
/*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
|
|
316
|
+
export type DevicePort = z.infer<typeof DevicePortSchema>;
|
|
317
|
+
/* THE AGENT ON THIS DEVICE, AS ONE BLOCK, because it is one binary and one process.
|
|
318
|
+
*
|
|
319
|
+
* It used to be two: `agents: { sync, host }` up in the report, and a `watcher` beside it. That split was a
|
|
320
|
+
* fiction two merges out of date — `resident.ts` runs a SINGLE process serving the outbound socket per linked
|
|
321
|
+
* sandbox and the mirror watcher together, from a single `intentic-machine` on disk — and it cost the Devices
|
|
322
|
+
* view its version chip. The chip read `agents.sync` and labelled it with the ENROLLMENT MODE ("desktop sync
|
|
323
|
+
* 1.243.0", or the same number as "ports only 1.243.0" on a mirror), so one binary's version wore two product
|
|
324
|
+
* names and appeared on no row that had never been paired for sync. `agents.host` was never filled by the
|
|
325
|
+
* report at all: the daemon fills `Device.agentVersion` from the hello frame it already holds.
|
|
326
|
+
*
|
|
327
|
+
* So: one block, and every version question about a device is answered inside it.
|
|
328
|
+
*
|
|
329
|
+
* `running` also decides whether everything ELSE in the report is still true — a healthy session list under a
|
|
330
|
+
* dead loop means new dev-server ports stop appearing on localhost and commits stop arriving in the local
|
|
331
|
+
* clones, while every other row reads exactly as it did the moment before. */
|
|
332
|
+
export const DeviceAgentSchema = z.object({
|
|
245
333
|
running: z.boolean(),
|
|
246
334
|
pid: z.number().int().optional(),
|
|
247
|
-
/*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
|
|
251
|
-
|
|
335
|
+
/* THE BUILD ON DISK: the file at `~/.intentic/bin/intentic-machine`, asked what it is rather than assumed
|
|
336
|
+
* (the agent's own installed.ts). Absent on a device with no installed agent at all — a dev run, an `npx`
|
|
337
|
+
* one, a binary somebody put elsewhere on their PATH — which is "not known", never a version. */
|
|
338
|
+
installed: z.string().optional(),
|
|
339
|
+
/* THE BUILD ACTUALLY SERVING, stamped into the pidfile by the loop that claimed it, which is the only place
|
|
340
|
+
* the fact exists: replacing the binary does not touch the running process, so a device can hold a current
|
|
341
|
+
* agent and go on serving a months-old one indefinitely. The two differing is a restart somebody is owed
|
|
342
|
+
* (see agentBuildSkew). Absent when no loop is running, and when the one running predates the stamp. */
|
|
252
343
|
build: z.string().optional(),
|
|
253
|
-
/* When the
|
|
344
|
+
/* When the loop last FINISHED a pass, the field that makes `running` mean something. The agent holds its
|
|
254
345
|
* SSH transport listeners on its own event loop, so a failure that escapes the loop leaves a process that is
|
|
255
346
|
* alive and a loop that is gone: pid present, unit "active", mirroring and the git bridge stopped. Absent
|
|
256
347
|
* means the agent has not reported one (too old to stamp, or its first pass has not landed), which is not
|
|
257
348
|
* the same as stalled, and readers must not treat it as either state. */
|
|
258
349
|
lastTickAt: z.number().optional(),
|
|
259
350
|
});
|
|
260
|
-
export type
|
|
261
|
-
/* How long
|
|
262
|
-
*
|
|
263
|
-
* several passes of slack, the same yardstick the
|
|
351
|
+
export type DeviceAgent = z.infer<typeof DeviceAgentSchema>;
|
|
352
|
+
/* How long the loop may go without finishing a pass before "running" stops being the honest word for it. It
|
|
353
|
+
* polls every 5s and its slowest step is bounded by two 10s network timeouts per pairing, so a minute is
|
|
354
|
+
* several passes of slack, the same yardstick the Devices view already ages a whole report by.
|
|
264
355
|
*
|
|
265
356
|
* The rule lives HERE, next to the field, because the terminal and the browser both answer this question and a
|
|
266
|
-
*
|
|
267
|
-
export const
|
|
268
|
-
export const
|
|
269
|
-
|
|
270
|
-
export const
|
|
357
|
+
* device that is "running" in one and "stalled" in the other is worse than either answer alone. */
|
|
358
|
+
export const AGENT_STALL_AFTER_MS = 60_000;
|
|
359
|
+
export const agentStalled = (agent: DeviceAgent, now: number): boolean =>
|
|
360
|
+
agent.running && agent.lastTickAt !== undefined && now - agent.lastTickAt > AGENT_STALL_AFTER_MS;
|
|
361
|
+
export const DeviceReportSchema = z.object({
|
|
271
362
|
/* The OS hostname, and the JOIN KEY. A machine can arrive here two ways at once, volunteered by its sync
|
|
272
363
|
* agent, and read through its `host` capability, and those two know it by different names (the enrolled
|
|
273
364
|
* key's comment vs. the capability id the user typed). The hostname is the one thing both can state about
|
|
274
365
|
* the same box, so it is what dedupes them into a single row. */
|
|
275
366
|
hostname: z.string(),
|
|
276
367
|
os: z.string(),
|
|
277
|
-
/* Which agents this machine has, and at what version, so one on an old build is visible rather than
|
|
278
|
-
* mysteriously lacking a field. Same argument as HostSummary.version.
|
|
279
|
-
*
|
|
280
|
-
* `sync` is the agent INSTALLED here — the file on disk — and `watcher.build` beside it is the loop running
|
|
281
|
-
* from that file. It used to be neither: whichever process happened to build the report stamped its own
|
|
282
|
-
* version here, so the same machine answered its running build to a sandbox its loop posted to and its
|
|
283
|
-
* installed build to one that ran `status --json` over a host capability. One field, two meanings, and the
|
|
284
|
-
* gap between them — a machine updated but never restarted — invisible in both.
|
|
285
|
-
*
|
|
286
|
-
* `host` is what the live socket announced (so: what is running), filled by the daemon at merge time from
|
|
287
|
-
* the hello frame it already holds, never by the sync agent, which would have to go reading another agent's
|
|
288
|
-
* config to guess at it. */
|
|
289
|
-
agents: z.object({ sync: z.string().optional(), host: z.string().optional() }),
|
|
290
368
|
// Filled by the READER, never the agent (see above). Empty is the resting state: no Docker on the machine,
|
|
291
369
|
// or nothing has looked. Neither is an error, and neither means "no sandboxes exist".
|
|
292
|
-
sandboxes: z.array(
|
|
293
|
-
pairings: z.array(
|
|
294
|
-
ports: z.array(
|
|
295
|
-
|
|
370
|
+
sandboxes: z.array(DeviceSandboxSchema),
|
|
371
|
+
pairings: z.array(DevicePairingSchema),
|
|
372
|
+
ports: z.array(DevicePortSchema),
|
|
373
|
+
// The one agent this device runs, on disk and in flight, in one block (see DeviceAgentSchema for why it is
|
|
374
|
+
// one and not the `agents` map plus a `watcher` it replaced).
|
|
375
|
+
agent: DeviceAgentSchema,
|
|
296
376
|
// When the machine took this reading. NOT when the daemon received it. A report is a snapshot from a box
|
|
297
377
|
// that may since have gone to sleep, and the UI ages it against this rather than presenting it as now.
|
|
298
378
|
capturedAt: z.number(),
|
|
299
379
|
});
|
|
300
|
-
export type
|
|
380
|
+
export type DeviceReport = z.infer<typeof DeviceReportSchema>;
|
|
301
381
|
|
|
302
382
|
/* THE AGENT THIS MACHINE INSTALLED AND THE ONE IT IS RUNNING, when they are not the same build — the whole of
|
|
303
383
|
* "you updated the agent and nothing changed", as a value.
|
|
304
384
|
*
|
|
305
|
-
* It is one comparison, and it lives HERE for the same reason
|
|
306
|
-
* status`) and the browser (the
|
|
385
|
+
* It is one comparison, and it lives HERE for the same reason agentStalled does: the terminal (`intentic-machine
|
|
386
|
+
* status`) and the browser (the Devices row) both answer this question, and a machine that is behind in one and
|
|
307
387
|
* fine in the other is worse than either answer alone. The remedy is the same in both: restart the loop.
|
|
308
388
|
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
389
|
+
* AN UNSTAMPED LOOP IS THE LOUDEST CASE, not a missing one, and reading it as "nothing to say" is what let this
|
|
390
|
+
* whole check miss the machines it was written for. `agent.build` is stamped into the pidfile by the loop that
|
|
391
|
+
* claimed it, so a loop old enough to predate the stamp reports none — and it is running, and something newer is
|
|
392
|
+
* installed beside it, which is a skew by definition and a wider one than any it could have named. Every surface
|
|
393
|
+
* therefore went quiet on precisely the machines furthest behind: upgrade, see the new number everywhere, watch
|
|
394
|
+
* nothing change, and have no screen anywhere say why.
|
|
395
|
+
*
|
|
396
|
+
* So the running build is OPTIONAL in the answer and the question is asked of the installed one. Still silent
|
|
397
|
+
* whenever the honest answer is "no idea": a loop that is stopped (nothing is serving, and every surface already
|
|
398
|
+
* says so in louder words), a device with no installed agent to compare against, and a working-tree build, which
|
|
399
|
+
* is not a version and must not be told it is behind. */
|
|
400
|
+
export const agentBuildSkew = (agent: DeviceAgent): { readonly running: string | undefined; readonly installed: string } | undefined => {
|
|
401
|
+
const { build: running, installed } = agent;
|
|
402
|
+
if (!agent.running || installed === undefined || installed === DEV_VERSION || running === installed) {
|
|
317
403
|
return undefined;
|
|
318
404
|
}
|
|
319
405
|
return { running, installed };
|
|
320
406
|
};
|
|
321
407
|
|
|
322
|
-
// Why a
|
|
408
|
+
// Why a device that is plainly THERE has no report to show. Each is a different errand for the reader, which is
|
|
323
409
|
// the whole reason they are not collapsed into one "unavailable".
|
|
324
|
-
export const
|
|
410
|
+
export const DeviceGapSchema = z.enum([
|
|
325
411
|
// A host capability that is enrolled but has no socket right now. Laptops sleep; this is not a fault.
|
|
326
412
|
"offline",
|
|
327
413
|
// Connected, but "Run commands" is switched off on its capability card, so the daemon may not ask it
|
|
@@ -333,8 +419,8 @@ export const ComputerGapSchema = z.enum([
|
|
|
333
419
|
// machine reports. Distinct from "no-agent" because the agent IS there and the folders ARE syncing.
|
|
334
420
|
"unreported",
|
|
335
421
|
]);
|
|
336
|
-
export type
|
|
337
|
-
/* ONE
|
|
422
|
+
export type DeviceGap = z.infer<typeof DeviceGapSchema>;
|
|
423
|
+
/* ONE DEVICE, however the sandbox happens to be able to see it, and it may be both ways at once.
|
|
338
424
|
*
|
|
339
425
|
* A machine reaches a sandbox through two independent doors: a desktop-sync enrollment (which volunteers its own
|
|
340
426
|
* report) and a `host` capability (which the daemon can ask). They know the same box by different names, the
|
|
@@ -348,14 +434,14 @@ export type ComputerGap = z.infer<typeof ComputerGapSchema>;
|
|
|
348
434
|
* ever used the enrollment, and how to name it when they want it gone. Those three lived on /system/sync
|
|
349
435
|
* instead, as one machine's worth of `syncingFrom` plus a list of `mirroredBy` names, which is the sandbox-level
|
|
350
436
|
* shape this view exists to stop being: one card claiming a sandbox has A desktop sync, over a list of the
|
|
351
|
-
* several
|
|
437
|
+
* several devices that actually do.
|
|
352
438
|
*
|
|
353
439
|
* `machine` is the enrollment's own name for the box (the ssh key's comment). It is what the reports are filed
|
|
354
440
|
* under, and it is the id the revoke route takes — the same string, so a row can revoke exactly the enrollment
|
|
355
441
|
* it is drawn from. Two machines that present the same comment share one enrollment identity throughout the
|
|
356
442
|
* daemon (reports included); that is a pre-existing property of naming machines by their key comment, and this
|
|
357
443
|
* field inherits it rather than inventing a second identity that would disagree with the first. */
|
|
358
|
-
export const
|
|
444
|
+
export const DeviceSyncSchema = z.object({
|
|
359
445
|
machine: z.string(),
|
|
360
446
|
/* Which half. "sync" is files AND ports and is SINGLE-HOLDER for the sandbox; "mirror" is ports only and any
|
|
361
447
|
* number of machines may hold one. The row says which, because "your laptop is paired" is read as the first
|
|
@@ -365,24 +451,24 @@ export const ComputerSyncSchema = z.object({
|
|
|
365
451
|
// has, which is exactly what a setup that did not finish leaves behind, and must not read as healthy.
|
|
366
452
|
seenAt: z.number().optional(),
|
|
367
453
|
});
|
|
368
|
-
export type
|
|
369
|
-
export const
|
|
454
|
+
export type DeviceSync = z.infer<typeof DeviceSyncSchema>;
|
|
455
|
+
export const DeviceSchema = z.object({
|
|
370
456
|
// Stable row key: the reported hostname when either door produced one, else the name that door knows it by.
|
|
371
457
|
key: z.string(),
|
|
372
458
|
// What to call it on screen, the user's own name for the machine wherever one exists.
|
|
373
459
|
label: z.string(),
|
|
374
|
-
// The desktop-sync enrollment this machine holds with this sandbox, absent when it has none (a
|
|
460
|
+
// The desktop-sync enrollment this machine holds with this sandbox, absent when it has none (a device
|
|
375
461
|
// reached only through its `host` capability).
|
|
376
|
-
sync:
|
|
377
|
-
// The host capability's id, when this machine is also a connected
|
|
462
|
+
sync: DeviceSyncSchema.optional(),
|
|
463
|
+
// The host capability's id, when this machine is also a connected device. Absent otherwise.
|
|
378
464
|
hostId: z.string().optional(),
|
|
379
465
|
// Host-capability liveness. Absent when there is no host capability, which is NOT the same as offline.
|
|
380
466
|
online: z.boolean().optional(),
|
|
381
|
-
/* WHAT THE
|
|
467
|
+
/* WHAT THE DEVICE IS, as distinct from how it is reachable, the half a row used to leave out entirely,
|
|
382
468
|
* so a Windows laptop and a Linux desktop were two identical lines of text with different names on them.
|
|
383
469
|
*
|
|
384
470
|
* It is carried BESIDE the report rather than inside it because the rows that need it most are the ones with
|
|
385
|
-
* no report: a connected
|
|
471
|
+
* no report: a connected device with no sync agent, or one that is asleep, still knows its own OS. Nothing
|
|
386
472
|
* here depends on an agent being installed, and the daemon has held all of it since the machine connected.
|
|
387
473
|
*
|
|
388
474
|
* `platform` is the slug this side classifies the machine by, the host capability's own card ("windows",
|
|
@@ -390,24 +476,30 @@ export const ComputerSchema = z.object({
|
|
|
390
476
|
* machine's connect-time description of ITSELF, which is what says which Windows and which shell. */
|
|
391
477
|
platform: z.string().optional(),
|
|
392
478
|
facts: HostFactsSchema.optional(),
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
479
|
+
/* The agent version the socket ANNOUNCED at connect, and when the device last held one: how a connected
|
|
480
|
+
* device AGES. An old agent explains a row that lacks something newer devices have, and "last seen" is the
|
|
481
|
+
* one honest thing an offline row can still say about itself.
|
|
482
|
+
*
|
|
483
|
+
* It is the same number as `report.agent.build` whenever both are known — one resident process stamps its
|
|
484
|
+
* `MACHINE_VERSION` into the pidfile and sends it in the hello frame — and it is kept because it is the
|
|
485
|
+
* ONLY version a row with no report has: a device whose "Run commands" switch is off, or which has no
|
|
486
|
+
* agent to answer `status --json`, still told us what it was when it dialled. That is what stops the
|
|
487
|
+
* Devices view's agent chip going blank on exactly the rows that need explaining. */
|
|
488
|
+
agentVersion: z.string().optional(),
|
|
397
489
|
lastSeen: z.number().optional(),
|
|
398
|
-
report:
|
|
399
|
-
gap:
|
|
490
|
+
report: DeviceReportSchema.optional(),
|
|
491
|
+
gap: DeviceGapSchema.optional(),
|
|
400
492
|
});
|
|
401
|
-
export type
|
|
402
|
-
export const
|
|
493
|
+
export type Device = z.infer<typeof DeviceSchema>;
|
|
494
|
+
export const DevicesListSchema = z.object({ devices: z.array(DeviceSchema) });
|
|
403
495
|
/* GET /system/sync: what desktop sync is doing for this sandbox, WITHOUT naming any one machine as the answer.
|
|
404
496
|
*
|
|
405
497
|
* It used to carry `syncingFrom` + `syncSeenAt` + `mirroredBy`, which is the enrollment list flattened into one
|
|
406
498
|
* holder and a list of everybody else — the shape a card that believed a sandbox has A desktop sync needed, and
|
|
407
|
-
* the reason that card kept restating facts the
|
|
408
|
-
* those now rides on the machine's own row (
|
|
499
|
+
* the reason that card kept restating facts the Devices list beside it already had per machine. Every one of
|
|
500
|
+
* those now rides on the machine's own row (DeviceSync), where a reader can act on it.
|
|
409
501
|
*
|
|
410
|
-
* What is left is what is genuinely about the SANDBOX rather than about any
|
|
502
|
+
* What is left is what is genuinely about the SANDBOX rather than about any device: whether sync is possible
|
|
411
503
|
* here at all, whether anything at all is enrolled, and the raw reports, which is the cheap ambient read the
|
|
412
504
|
* rail's badge lives on (it must never fan out to somebody's laptop just to decide whether to draw a chip). */
|
|
413
505
|
export const SyncStatusSchema = z.object({
|
|
@@ -418,6 +510,6 @@ export const SyncStatusSchema = z.object({
|
|
|
418
510
|
* surface now, so a sandbox that can answer this read can also sync. Kept as a field rather than assumed,
|
|
419
511
|
* because the card branches on it and a daemon too old to say is one that should not be offered sync. */
|
|
420
512
|
available: z.boolean().optional(),
|
|
421
|
-
machines: z.array(
|
|
513
|
+
machines: z.array(DeviceReportSchema).optional(),
|
|
422
514
|
});
|
|
423
515
|
export type SyncStatus = z.infer<typeof SyncStatusSchema>;
|
package/src/schemas/hosts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// hosts: the user's own connected
|
|
1
|
+
// hosts: the user's own connected devices (the `host` capability's live half)
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
// The manifest says which machines the user INTENDS to have connected; this says which are actually holding a
|
|
4
4
|
// socket right now. Nothing here is remembered across a daemon restart except the enrollment itself: a machine
|
|
@@ -18,6 +18,10 @@ export const HostFactsSchema = z.object({
|
|
|
18
18
|
home: z.string(),
|
|
19
19
|
// Roots in force right now (the capability's `roots`, or [home]), the agent sees its own boundary.
|
|
20
20
|
roots: z.array(z.string()),
|
|
21
|
+
/* The Docker ENGINE's size — the WSL guest on Windows, the Desktop VM on macOS, the host on Linux — which
|
|
22
|
+
* is the ceiling a sandbox's share is bounded by, and the number a Resources dialog draws its limits from.
|
|
23
|
+
* Absent when the machine has no docker to ask, which is a machine that runs no sandboxes anyway. */
|
|
24
|
+
engine: z.object({ memoryBytes: z.number(), cpus: z.number() }).optional(),
|
|
21
25
|
});
|
|
22
26
|
export type HostFacts = z.infer<typeof HostFactsSchema>;
|
|
23
27
|
export const HostSummarySchema = z.object({
|
package/src/schemas/issues.ts
CHANGED
|
@@ -234,10 +234,6 @@ export const IssuePublicConfigSchema = z.object({
|
|
|
234
234
|
});
|
|
235
235
|
export type IssuePublicConfig = z.infer<typeof IssuePublicConfigSchema>;
|
|
236
236
|
|
|
237
|
-
// The proof-of-work challenge, the same shape and the same solver as the Front Desk's.
|
|
238
|
-
export const IssueChallengeSchema = z.object({ salt: z.string(), difficulty: z.number().int().positive() });
|
|
239
|
-
export type IssueChallenge = z.infer<typeof IssueChallengeSchema>;
|
|
240
|
-
|
|
241
237
|
/* What the intake answers with. A short reference the reporter can be shown ("we filed this as 4f3a…"), and
|
|
242
238
|
* nothing else: whether this crash is new, how often it has happened and whether it woke anybody are the
|
|
243
239
|
* owner's facts, and the caller is a stranger's browser. */
|