@intentic/sandbox-contract 1.271.0 → 1.273.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.
Files changed (49) hide show
  1. package/dist/chores/chores.d.ts.map +1 -1
  2. package/dist/chores/chores.js +1 -1
  3. package/dist/chores/chores.js.map +1 -1
  4. package/dist/contracts/agents.contract.d.ts +70 -0
  5. package/dist/contracts/agents.contract.d.ts.map +1 -1
  6. package/dist/contracts/capabilities.contract.d.ts +15 -0
  7. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  8. package/dist/contracts/capabilities.contract.js +10 -0
  9. package/dist/contracts/capabilities.contract.js.map +1 -1
  10. package/dist/contracts/host.contract.d.ts +5 -0
  11. package/dist/contracts/host.contract.d.ts.map +1 -1
  12. package/dist/contracts/system.contract.d.ts +37 -30
  13. package/dist/contracts/system.contract.d.ts.map +1 -1
  14. package/dist/events/system-events.d.ts +10 -0
  15. package/dist/events/system-events.d.ts.map +1 -1
  16. package/dist/index.d.ts +93 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/policy/command-run.d.ts.map +1 -1
  21. package/dist/policy/command-run.js +2 -10
  22. package/dist/policy/command-run.js.map +1 -1
  23. package/dist/schemas/agents.d.ts +21 -6
  24. package/dist/schemas/agents.d.ts.map +1 -1
  25. package/dist/schemas/agents.js +5 -1
  26. package/dist/schemas/agents.js.map +1 -1
  27. package/dist/schemas/automations.d.ts +5 -0
  28. package/dist/schemas/automations.d.ts.map +1 -1
  29. package/dist/schemas/devices.d.ts +22 -2
  30. package/dist/schemas/devices.d.ts.map +1 -1
  31. package/dist/schemas/devices.js +41 -7
  32. package/dist/schemas/devices.js.map +1 -1
  33. package/dist/schemas/hosts.d.ts +21 -0
  34. package/dist/schemas/hosts.d.ts.map +1 -1
  35. package/dist/schemas/hosts.js +9 -0
  36. package/dist/schemas/hosts.js.map +1 -1
  37. package/dist/schemas/remote-refs.d.ts +28 -0
  38. package/dist/schemas/remote-refs.d.ts.map +1 -0
  39. package/dist/schemas/remote-refs.js +30 -0
  40. package/dist/schemas/remote-refs.js.map +1 -0
  41. package/package.json +5 -4
  42. package/src/chores/chores.ts +1 -2
  43. package/src/contracts/capabilities.contract.ts +12 -0
  44. package/src/index.ts +1 -0
  45. package/src/policy/command-run.ts +2 -12
  46. package/src/schemas/agents.ts +16 -6
  47. package/src/schemas/devices.ts +69 -18
  48. package/src/schemas/hosts.ts +21 -0
  49. package/src/schemas/remote-refs.ts +39 -0
@@ -1,6 +1,6 @@
1
1
  // What one of the user's own machines is running.
2
2
  import { z } from "zod";
3
- import { HostFactsSchema } from "./hosts.js";
3
+ import { type HostFacts, HostFactsSchema, WslEnvironmentSchema } from "./hosts.js";
4
4
  import { DEV_VERSION } from "../state/versions.js";
5
5
  // Desktop-sync report shape shared by the agent, daemon and browser, produced only by `intentic-machine status --json`.
6
6
  // The agent never reports `sandboxes`; the docker half is filled in by whoever reads the report, scoped to the reader's
@@ -303,10 +303,8 @@ export const DeviceReportSchema = z.object({
303
303
  // own: a WSL distro inherits the Windows machine's name, so `wsl` below is what tells those apart.
304
304
  hostname: z.string(),
305
305
  os: z.string(),
306
- // Present only inside a WSL distro. `distro` is that distro's own name ("Arch", "Ubuntu-22.04"), empty when the
307
- // machine won't say. Windows, and every distro it hosts, all answer `hostname` with the same string while being
308
- // separate filesystems running separate agents, so this is the only thing that keeps them apart.
309
- wsl: z.object({ distro: z.string() }).optional(),
306
+ // Present only inside a WSL distro; the same fact rides the connect-time facts, which no scope can withhold.
307
+ wsl: WslEnvironmentSchema.optional(),
310
308
  pairings: z.array(DevicePairingSchema),
311
309
  ports: z.array(DevicePortSchema),
312
310
  // The one agent this device runs, on disk and in flight, in one block.
@@ -321,21 +319,23 @@ export type DeviceReport = z.infer<typeof DeviceReportSchema>;
321
319
  export const REPORT_QUIET_AFTER_MS = 60_000;
322
320
  export const reportQuiet = (report: DeviceReport, receivedAt: number): boolean => receivedAt - report.capturedAt > REPORT_QUIET_AFTER_MS;
323
321
 
324
- // The environment a reading came from, as opposed to the machine hosting it: a Windows install and every WSL distro
325
- // on it are separate filesystems running separate agents, and all of them answer `hostname` with the same string.
326
- // Undefined when nothing has reported — an absence of evidence, never read as agreement.
327
- export const environmentOf = (report: DeviceReport | undefined): string | undefined =>
328
- report === undefined ? undefined : report.wsl === undefined ? report.os : `wsl:${report.wsl.distro}`;
329
-
330
- // Whether two readings positively disagree about which environment they describe. False whenever either side has not
331
- // said, so this only ever blocks a fold it holds evidence against, and an agent too old to report `wsl` keeps the
332
- // behaviour it had before the field existed.
333
- export const differentEnvironment = (left: DeviceReport | undefined, right: DeviceReport | undefined): boolean => {
334
- const a = environmentOf(left);
335
- const b = environmentOf(right);
336
- return a !== undefined && b !== undefined && a !== b;
322
+ // The environment a device's evidence describes, as opposed to the machine hosting it: `wsl:<distro>` inside a
323
+ // distro, `native` for an install on the metal. Connect-time facts are read first, since no scope can withhold them;
324
+ // facts from an agent too old to carry `hostname` say nothing, and a report without `wsl` is native, as it always
325
+ // was. Undefined is an absence of evidence, never read as agreement.
326
+ export const environmentOf = (facts: Pick<HostFacts, "hostname" | "wsl"> | undefined, report: DeviceReport | undefined): string | undefined => {
327
+ const wsl = facts?.wsl ?? report?.wsl;
328
+ if (wsl !== undefined) {
329
+ return `wsl:${wsl.distro}`;
330
+ }
331
+ return facts?.hostname !== undefined || report !== undefined ? "native" : undefined;
337
332
  };
338
333
 
334
+ // Whether two devices positively disagree about which environment they are. False whenever either side has not said,
335
+ // so this only ever blocks a fold it holds evidence against.
336
+ export const differentEnvironment = (left: string | undefined, right: string | undefined): boolean =>
337
+ left !== undefined && right !== undefined && left !== right;
338
+
339
339
  // Compares running build against installed; silent when the loop is stopped, nothing installed, or installed is a dev
340
340
  // build. An unstamped `running` still counts as skew.
341
341
  export const agentBuildSkew = (agent: DeviceAgent): { readonly running: string | undefined; readonly installed: string } | undefined => {
@@ -402,6 +402,57 @@ export const DeviceSchema = z.object({
402
402
  export type Device = z.infer<typeof DeviceSchema>;
403
403
  export const DevicesListSchema = z.object({ devices: z.array(DeviceSchema) });
404
404
 
405
+ // The physical computer a device is an environment of. Windows and every WSL distro on it are one machine with one
406
+ // Docker engine, one screen and one set of disks, each environment holding its own agent and its own door.
407
+ export interface Machine {
408
+ /** A lone device's own key, so its address does not change; a folded machine's is the hostname its doors share. */
409
+ readonly key: string;
410
+ readonly label: string;
411
+ /** Windows first, then distros by label: the side that owns the screen leads. */
412
+ readonly environments: readonly Device[];
413
+ }
414
+
415
+ export const deviceHostname = (device: Device): string | undefined => device.facts?.hostname ?? device.report?.hostname;
416
+ export const deviceEnvironment = (device: Device): string | undefined => environmentOf(device.facts, device.report);
417
+ export const isWslDevice = (device: Device): boolean => deviceEnvironment(device)?.startsWith("wsl:") === true;
418
+
419
+ // Two native installs that merely share a name stay two machines; only a distro joins the machine whose hostname it
420
+ // carries, which is the one fact that makes the name safe to join on.
421
+ const byEnvironment = (a: Device, b: Device): number => Number(isWslDevice(a)) - Number(isWslDevice(b)) || a.label.localeCompare(b.label);
422
+
423
+ const hostnameKey = (device: Device): string | undefined => deviceHostname(device)?.toLowerCase();
424
+
425
+ const siblingsOf = (device: Device, devices: readonly Device[]): Device[] => {
426
+ const key = hostnameKey(device);
427
+ const shared = key === undefined ? [device] : devices.filter((candidate) => hostnameKey(candidate) === key);
428
+ return shared.length > 1 && shared.some(isWslDevice) ? shared.toSorted(byEnvironment) : [device];
429
+ };
430
+
431
+ const machineOf = (device: Device, environments: readonly Device[]): Machine => {
432
+ if (environments.length === 1) {
433
+ return { key: device.key, label: device.label, environments };
434
+ }
435
+ // Folding needs a hostname, so it is present here; the fallback only keeps the type honest.
436
+ const hostname = deviceHostname(device) ?? device.key;
437
+ return { key: hostname, label: hostname, environments };
438
+ };
439
+
440
+ export const machinesOf = (devices: readonly Device[]): Machine[] => {
441
+ const folded = new Set<Device>();
442
+ const machines: Machine[] = [];
443
+ for (const device of devices) {
444
+ if (folded.has(device)) {
445
+ continue;
446
+ }
447
+ const environments = siblingsOf(device, devices);
448
+ for (const environment of environments) {
449
+ folded.add(environment);
450
+ }
451
+ machines.push(machineOf(device, environments));
452
+ }
453
+ return machines;
454
+ };
455
+
405
456
  // The connected, online device whose docker reports a given sandbox slug: the machine that sandbox RUNS ON, as opposed
406
457
  // to any machine merely paired with it. Answers the question every "run it there instead of asking the owner to type
407
458
  // it" path starts from, and is `undefined` for a sync-only agent, which reports no containers at all.
@@ -3,6 +3,12 @@ import { z } from "zod";
3
3
  // Manifest says which machines are intended; this says which actually hold a socket now. Nothing persists across a
4
4
  // daemon restart except the enrollment itself, so a closed laptop reads offline within a heartbeat.
5
5
 
6
+ // Which WSL distro a Linux agent runs inside. Windows and every distro it hosts answer `hostname` with the same string
7
+ // while being separate filesystems running separate agents, so this is what keeps a distro apart from the install
8
+ // hosting it. `distro` is WSL's own name for it ("Arch", "Ubuntu-22.04"), empty when the distro won't say.
9
+ export const WslEnvironmentSchema = z.object({ distro: z.string() });
10
+ export type WslEnvironment = z.infer<typeof WslEnvironmentSchema>;
11
+
6
12
  // What a machine reports once at connect (`host.describe`), cached until it reconnects: the skill pack says how to
7
13
  // drive Windows, this says which Windows it is.
8
14
  export const HostFactsSchema = z.object({
@@ -18,8 +24,23 @@ export const HostFactsSchema = z.object({
18
24
  // Docker engine's size (WSL guest on Windows, Desktop VM on macOS, host on Linux); the ceiling a sandbox's share is
19
25
  // bounded by.
20
26
  engine: z.object({ memoryBytes: z.number(), cpus: z.number() }).optional(),
27
+ // OS hostname: the key that joins the doors of one physical machine. Reported by every agent that also reports
28
+ // `wsl`, so a hostname with no `wsl` beside it is a native install; absent from an agent older than both.
29
+ hostname: z.string().optional(),
30
+ // Present only inside a WSL distro.
31
+ wsl: WslEnvironmentSchema.optional(),
32
+ // Windows only: the distros `wsl -l -q` lists, the names `run_command`'s `in: "wsl:<name>"` accepts.
33
+ wslDistros: z.array(z.string()).optional(),
21
34
  });
22
35
  export type HostFacts = z.infer<typeof HostFactsSchema>;
36
+
37
+ // One folder, two names. Windows sees a distro's files under a UNC share and a distro sees the Windows drives under
38
+ // /mnt, so a path handed across the boundary is translated here rather than by hand at every call site.
39
+ export const wslPathOf = (windowsPath: string): string | undefined => {
40
+ const drive = /^([A-Za-z]):[\\/](.*)$/.exec(windowsPath);
41
+ return drive === null ? undefined : `/mnt/${drive[1]?.toLowerCase()}/${(drive[2] ?? "").replaceAll("\\", "/")}`.replace(/\/$/, "");
42
+ };
43
+ export const windowsPathOf = (distro: string, linuxPath: string): string => `\\\\wsl.localhost\\${distro}${linuxPath.replaceAll("/", "\\")}`;
23
44
  export const HostSummarySchema = z.object({
24
45
  // The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command).
25
46
  id: z.string(),
@@ -0,0 +1,39 @@
1
+ // What a git remote advertises right now: the versions an install form can pin to without anyone reading a sha off a
2
+ // web page. Answered from `git ls-remote`, so nothing is cloned and nothing is written.
3
+ import { z } from "zod";
4
+
5
+ // POST so a private repository's token never rides a URL or an access log, the same reasoning as the marketplace read.
6
+ export const RemoteRefsRequestSchema = z.object({
7
+ url: z.url().describe("The repository to ask. http(s) only: an ssh remote would stop on a host-key prompt nobody can answer."),
8
+ token: z
9
+ .string()
10
+ .min(1)
11
+ .optional()
12
+ .describe(
13
+ "A credential for a private one. Sent as a body rather than in the address, so it never lands in a log. A form editing a live connection has never been shown its token: it sends the VAULTED marker here and names the connection in `keeping`, so a private repository still answers without anyone retyping a key.",
14
+ ),
15
+ keeping: z
16
+ .string()
17
+ .min(1)
18
+ .optional()
19
+ .describe("Which connection a VAULTED token belongs to. Ignored when a real token is sent."),
20
+ });
21
+
22
+ export const RemoteRefSchema = z.object({
23
+ name: z.string().describe("The branch or tag as a person names it: `main`, `v1.4.0`."),
24
+ kind: z.enum(["branch", "tag"]),
25
+ sha: z
26
+ .string()
27
+ .regex(/^[0-9a-f]{40}$/)
28
+ .describe("The commit it points at. An annotated tag is peeled here, so this is always a commit, never a tag object."),
29
+ });
30
+ export type RemoteRef = z.infer<typeof RemoteRefSchema>;
31
+
32
+ export const RemoteRefsSchema = z.object({
33
+ defaultBranch: z
34
+ .string()
35
+ .optional()
36
+ .describe("The branch the remote advertises as HEAD, the one to offer first. Absent when the remote advertises no symref."),
37
+ refs: z.array(RemoteRefSchema).describe("Every branch the remote advertises, then every tag. Which to offer first is the reader's question, not this one's."),
38
+ });
39
+ export type RemoteRefs = z.infer<typeof RemoteRefsSchema>;