@intentic/sandbox-contract 1.337.0 → 1.339.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 +41 -0
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +4 -7
- package/dist/chores/probes.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +32 -23
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +6020 -5994
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +13 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +8 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.d.ts +3 -3
- package/dist/contracts/capabilities.contract.d.ts +28 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +11 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +225 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +10 -0
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +16 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +22 -7
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/org.contract.d.ts +35 -0
- package/dist/contracts/org.contract.d.ts.map +1 -0
- package/dist/contracts/org.contract.js +14 -0
- package/dist/contracts/org.contract.js.map +1 -0
- package/dist/contracts/phone.contract.d.ts +1 -0
- package/dist/contracts/phone.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.d.ts +115 -0
- package/dist/contracts/privacy.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.js +11 -2
- package/dist/contracts/privacy.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +11 -7
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +85 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +48 -1
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +4 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +11 -3
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +23 -2
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +17 -4
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/requests.d.ts +4 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +1 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/system-events.d.ts +4 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +2 -0
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +27 -6
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/index.d.ts +7562 -7426
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -257
- package/dist/index.js.map +1 -1
- package/dist/models/agent-lanes.d.ts +30 -0
- package/dist/models/agent-lanes.d.ts.map +1 -0
- package/dist/models/agent-lanes.js +67 -0
- package/dist/models/agent-lanes.js.map +1 -0
- package/dist/models/agent-standing.d.ts +55 -0
- package/dist/models/agent-standing.d.ts.map +1 -0
- package/dist/models/agent-standing.js +82 -0
- package/dist/models/agent-standing.js.map +1 -0
- package/dist/models/agent-starter.d.ts +10 -0
- package/dist/models/agent-starter.d.ts.map +1 -0
- package/dist/models/agent-starter.js +16 -0
- package/dist/models/agent-starter.js.map +1 -0
- package/dist/netd/generated/wire.d.ts +1 -1
- package/dist/netd/generated/wire.d.ts.map +1 -1
- package/dist/policy/identity-assertion.d.ts.map +1 -1
- package/dist/policy/identity-assertion.js +2 -8
- package/dist/policy/identity-assertion.js.map +1 -1
- package/dist/policy/keyring-seal.d.ts +48 -0
- package/dist/policy/keyring-seal.d.ts.map +1 -0
- package/dist/policy/keyring-seal.js +94 -0
- package/dist/policy/keyring-seal.js.map +1 -0
- package/dist/policy/message-limits.d.ts +2 -0
- package/dist/policy/message-limits.d.ts.map +1 -1
- package/dist/policy/message-limits.js +2 -0
- package/dist/policy/message-limits.js.map +1 -1
- package/dist/policy/org-locks.d.ts +58 -0
- package/dist/policy/org-locks.d.ts.map +1 -0
- package/dist/policy/org-locks.js +113 -0
- package/dist/policy/org-locks.js.map +1 -0
- package/dist/policy/persona-capability.d.ts +14 -0
- package/dist/policy/persona-capability.d.ts.map +1 -0
- package/dist/policy/persona-capability.js +23 -0
- package/dist/policy/persona-capability.js.map +1 -0
- package/dist/policy/reserved-servers.d.ts.map +1 -1
- package/dist/policy/reserved-servers.js +1 -1
- package/dist/policy/reserved-servers.js.map +1 -1
- package/dist/protocol/doors/phone-links.d.ts +5 -0
- package/dist/protocol/doors/phone-links.d.ts.map +1 -1
- package/dist/protocol/doors/phone-links.js +3 -1
- package/dist/protocol/doors/phone-links.js.map +1 -1
- package/dist/protocol/doors/webext-links.d.ts +3 -0
- package/dist/protocol/doors/webext-links.d.ts.map +1 -1
- package/dist/protocol/doors/webext-links.js +3 -0
- package/dist/protocol/doors/webext-links.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +270 -0
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.js +14 -1
- package/dist/protocol/raw/raw-json-routes.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +70 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +18 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts.map +1 -1
- package/dist/protocol/route-meta.js.map +1 -1
- package/dist/protocol/routes.d.ts +4 -0
- package/dist/protocol/routes.d.ts.map +1 -1
- package/dist/protocol/routes.js +12 -0
- package/dist/protocol/routes.js.map +1 -1
- package/dist/schemas/agents-list.d.ts +5 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents-list.js +8 -0
- package/dist/schemas/agents-list.js.map +1 -1
- package/dist/schemas/agents.d.ts +1 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +1 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +20 -0
- package/dist/schemas/approvals.d.ts.map +1 -1
- package/dist/schemas/approvals.js +10 -0
- package/dist/schemas/approvals.js.map +1 -1
- package/dist/schemas/capabilities.d.ts +26 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +17 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/data-protection.d.ts +5 -0
- package/dist/schemas/data-protection.d.ts.map +1 -1
- package/dist/schemas/data-protection.js +29 -0
- package/dist/schemas/data-protection.js.map +1 -1
- package/dist/schemas/devices.d.ts +20 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +5 -1
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +32 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/git/git.d.ts +19 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +12 -1
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +2 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +11 -1
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/intentic-os.d.ts +218 -0
- package/dist/schemas/intentic-os.d.ts.map +1 -1
- package/dist/schemas/intentic-os.js +166 -0
- package/dist/schemas/intentic-os.js.map +1 -1
- package/dist/schemas/keyring.d.ts +421 -0
- package/dist/schemas/keyring.d.ts.map +1 -0
- package/dist/schemas/keyring.js +176 -0
- package/dist/schemas/keyring.js.map +1 -0
- package/dist/schemas/links.d.ts +208 -0
- package/dist/schemas/links.d.ts.map +1 -0
- package/dist/schemas/links.js +116 -0
- package/dist/schemas/links.js.map +1 -0
- package/dist/schemas/maintenance.d.ts.map +1 -1
- package/dist/schemas/maintenance.js.map +1 -1
- package/dist/schemas/members.d.ts.map +1 -1
- package/dist/schemas/members.js +2 -0
- package/dist/schemas/members.js.map +1 -1
- package/dist/schemas/needs.d.ts +25 -8
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +15 -3
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/organisation.d.ts +106 -0
- package/dist/schemas/organisation.d.ts.map +1 -0
- package/dist/schemas/organisation.js +52 -0
- package/dist/schemas/organisation.js.map +1 -0
- package/dist/schemas/phone.d.ts +3 -0
- package/dist/schemas/phone.d.ts.map +1 -1
- package/dist/schemas/phone.js +1 -0
- package/dist/schemas/phone.js.map +1 -1
- package/dist/schemas/privacy.d.ts +232 -0
- package/dist/schemas/privacy.d.ts.map +1 -1
- package/dist/schemas/privacy.js +154 -4
- package/dist/schemas/privacy.js.map +1 -1
- package/dist/schemas/secrets.d.ts +151 -0
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +71 -8
- package/dist/schemas/secrets.js.map +1 -1
- package/dist/schemas/settings.d.ts +8 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +4 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/speech.d.ts +12 -0
- package/dist/schemas/speech.d.ts.map +1 -1
- package/dist/schemas/speech.js +23 -0
- package/dist/schemas/speech.js.map +1 -1
- package/dist/schemas/system.d.ts +2 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +1 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/terminal.d.ts +15 -0
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +11 -2
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/voice.d.ts +3 -0
- package/dist/schemas/voice.d.ts.map +1 -1
- package/dist/schemas/voice.js +7 -0
- package/dist/schemas/voice.js.map +1 -1
- package/dist/schemas/webext.d.ts +17 -0
- package/dist/schemas/webext.d.ts.map +1 -1
- package/dist/schemas/webext.js +7 -1
- package/dist/schemas/webext.js.map +1 -1
- package/dist/state/contract-lock.d.ts +3 -1
- package/dist/state/contract-lock.d.ts.map +1 -1
- package/dist/state/contract-lock.js +34 -9
- package/dist/state/contract-lock.js.map +1 -1
- package/dist/state/contract-modules.d.ts +4 -0
- package/dist/state/contract-modules.d.ts.map +1 -0
- package/dist/state/contract-modules.js +28 -0
- package/dist/state/contract-modules.js.map +1 -0
- package/dist/state/daemon-floor.d.ts +4 -0
- package/dist/state/daemon-floor.d.ts.map +1 -0
- package/dist/state/daemon-floor.js +8 -0
- package/dist/state/daemon-floor.js.map +1 -0
- package/dist/state/definition.d.ts +34 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +8 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/text/documents.d.ts +3 -1
- package/dist/text/documents.d.ts.map +1 -1
- package/dist/text/documents.js +30 -0
- package/dist/text/documents.js.map +1 -1
- package/dist/text/pages.d.ts.map +1 -1
- package/dist/text/pages.js +3 -7
- package/dist/text/pages.js.map +1 -1
- package/package.json +27 -5
- package/src/chores/probes.ts +4 -6
- package/src/contracts/agents.contract.ts +18 -1
- package/src/contracts/capabilities.contract.ts +14 -0
- package/src/contracts/device.contract.ts +30 -0
- package/src/contracts/org.contract.ts +19 -0
- package/src/contracts/privacy.contract.ts +13 -1
- package/src/contracts/secrets.contract.ts +64 -0
- package/src/events/requests.ts +3 -1
- package/src/events/system-events.ts +9 -0
- package/src/index.ts +41 -279
- package/src/models/agent-lanes.ts +152 -0
- package/src/models/agent-standing.ts +279 -0
- package/src/models/agent-starter.ts +26 -0
- package/src/netd/generated/netd-wire.json +2 -1
- package/src/netd/generated/wire.ts +1 -1
- package/src/policy/identity-assertion.ts +2 -8
- package/src/policy/keyring-seal.test.ts +93 -0
- package/src/policy/keyring-seal.ts +148 -0
- package/src/policy/message-limits.ts +3 -0
- package/src/policy/org-locks.ts +173 -0
- package/src/policy/persona-capability.ts +57 -0
- package/src/policy/reserved-servers.ts +3 -2
- package/src/policy/tests/org-locks.test.ts +92 -0
- package/src/policy/tests/reserved-servers.test.ts +3 -2
- package/src/protocol/doors/listener-protocol.ts +1 -1
- package/src/protocol/doors/phone-links.ts +11 -2
- package/src/protocol/doors/phone-protocol.test.ts +8 -1
- package/src/protocol/doors/webext-links.ts +13 -0
- package/src/protocol/ingress-contract.fixture.json +2 -0
- package/src/protocol/pairing-redeem.ts +1 -1
- package/src/protocol/peer-dial.test.ts +6 -6
- package/src/protocol/peer-mcp-server.test.ts +2 -1
- package/src/protocol/raw/raw-json-routes.ts +31 -1
- package/src/protocol/raw/raw-routes.ts +35 -1
- package/src/protocol/route-meta.ts +2 -1
- package/src/protocol/routes.test.ts +33 -1
- package/src/protocol/routes.ts +18 -0
- package/src/protocol/sse.ts +1 -1
- package/src/schemas/agent.ts +1 -1
- package/src/schemas/agents-list.ts +13 -0
- package/src/schemas/agents.ts +1 -0
- package/src/schemas/approvals.ts +14 -0
- package/src/schemas/capabilities.ts +25 -0
- package/src/schemas/data-protection.ts +47 -0
- package/src/schemas/devices.ts +15 -1
- package/src/schemas/git/git.ts +22 -2
- package/src/schemas/hosts.ts +14 -1
- package/src/schemas/intentic-os.test.ts +148 -0
- package/src/schemas/intentic-os.ts +259 -0
- package/src/schemas/keyring.ts +277 -0
- package/src/schemas/links.ts +180 -0
- package/src/schemas/maintenance.ts +1 -0
- package/src/schemas/members.ts +4 -0
- package/src/schemas/needs.ts +21 -5
- package/src/schemas/organisation.ts +110 -0
- package/src/schemas/phone.ts +3 -0
- package/src/schemas/privacy.test.ts +63 -0
- package/src/schemas/privacy.ts +213 -5
- package/src/schemas/secrets.ts +96 -8
- package/src/schemas/settings.ts +8 -0
- package/src/schemas/speech.ts +38 -1
- package/src/schemas/system.ts +5 -2
- package/src/schemas/terminal.ts +13 -2
- package/src/schemas/voice.ts +11 -0
- package/src/schemas/webext.ts +16 -1
- package/src/state/contract-lock.test.ts +12 -12
- package/src/state/contract-lock.ts +43 -12
- package/src/state/contract-modules.ts +41 -0
- package/src/state/daemon-floor.test.ts +81 -0
- package/src/state/daemon-floor.ts +32 -0
- package/src/state/history-state.ts +17 -0
- package/src/state/workspace-state.ts +1 -1
- package/src/text/documents.test.ts +30 -1
- package/src/text/documents.ts +43 -1
- package/src/text/pages.ts +5 -7
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// contract.lock.json is every exported schema serialized to canonical JSON Schema, so git can flag a wire-contract
|
|
2
2
|
// change. contract-shrink demands a `!` commit with a Breaking-Note when a schema, field or type shrinks; additions
|
|
3
|
-
// pass freely. Derived from
|
|
4
|
-
// manifests the Rust crates that define it write beside their TypeScript (the tunnel, what a browser
|
|
5
|
-
// and the edge, and netd's control socket with Node), under `wire:` names no export can take.
|
|
3
|
+
// pass freely. Derived from every module's exports (contract-modules.ts), not a hand-kept list, plus the wire no oRPC
|
|
4
|
+
// route carries: the manifests the Rust crates that define it write beside their TypeScript (the tunnel, what a browser
|
|
5
|
+
// sees of netd and the edge, and netd's control socket with Node), under `wire:` names no export can take.
|
|
6
6
|
//
|
|
7
7
|
// And who reaches each route: every raw route's and every procedure's RouteMeta, each reach field with its default
|
|
8
8
|
// resolved (route-meta.ts `routeAccess`), under `access:METHOD /path`. Resolved, so every field is a value on every
|
|
@@ -15,7 +15,10 @@ import { readFileSync } from "node:fs";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { packageRoot } from "@intentic/constants/node";
|
|
17
17
|
import { z } from "zod";
|
|
18
|
-
import
|
|
18
|
+
import { SANDBOX_ROUTES } from "../index.js";
|
|
19
|
+
import { RAW_ROUTE_LIST } from "../protocol/raw/raw-routes.js";
|
|
20
|
+
import { routeAccess } from "../protocol/route-meta.js";
|
|
21
|
+
import { contractModules } from "./contract-modules.js";
|
|
19
22
|
|
|
20
23
|
// Canonical key order so two runs are byte-identical and a diff is a real contract change, not key-order noise. Arrays
|
|
21
24
|
// keep zod's own (stable) order.
|
|
@@ -39,12 +42,12 @@ const ACCESS_PREFIX = "access:";
|
|
|
39
42
|
// route moving between procedures is seen.
|
|
40
43
|
const accessEntries = (): Record<string, Readonly<Record<string, string | boolean>>> => {
|
|
41
44
|
const entries = new Map<string, Readonly<Record<string, string | boolean>>>();
|
|
42
|
-
for (const route of [...
|
|
45
|
+
for (const route of [...RAW_ROUTE_LIST, ...SANDBOX_ROUTES]) {
|
|
43
46
|
const key = `${ACCESS_PREFIX}${route.method} ${route.path}`;
|
|
44
47
|
if (entries.has(key)) {
|
|
45
48
|
throw new Error(`two routes declare ${route.method} ${route.path}; the lock can record only one reach for it`);
|
|
46
49
|
}
|
|
47
|
-
const access =
|
|
50
|
+
const access = routeAccess(route.method, route.meta);
|
|
48
51
|
const fields = route.name === `${route.method} ${route.path}` ? access : { ...access, procedure: route.name };
|
|
49
52
|
entries.set(key, Object.fromEntries(Object.entries(fields).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
50
53
|
}
|
|
@@ -84,13 +87,41 @@ export const accessChanges = (baseText: string, headText: string): string[] => {
|
|
|
84
87
|
// Each written by its crate's own test: `tunnel`, `browser-wire` and `netd-wire` under _sandbox/netd/crates.
|
|
85
88
|
const WIRE_MANIFESTS = ["browser-wire", "netd-wire", "tunnel"] as const;
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
// Schemas exported where the barrel never re-exported them, so the lock never recorded them; recording one is an addition.
|
|
91
|
+
const UNRECORDED = new Map<string, readonly string[]>([
|
|
92
|
+
["contracts/usage.contract", ["RefreshPlanLimitsSchema"]],
|
|
93
|
+
["policy/identity-assertion", ["IdentityAssertionMethodSchema"]],
|
|
94
|
+
// Its ExtensionHealthSchema is not schemas/extension-updates.ts's, so it can only be recorded once one is renamed.
|
|
95
|
+
[
|
|
96
|
+
"protocol/raw/extension-protocol",
|
|
97
|
+
["ExtensionEventSchema", "ExtensionHealthSchema", "ExtensionOwnSettingsSchema", "ExtensionPermissionsSchema"],
|
|
98
|
+
],
|
|
99
|
+
["schemas/internal", ["RefNameSchema", "autoStart", "entryId"]],
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// Every schema the lock records, by its export name. A name two modules export as different schemas throws: the lock
|
|
103
|
+
// is keyed by name, and either one recorded alone would hide a change to the other.
|
|
104
|
+
export const lockedSchemas = async (): Promise<ReadonlyMap<string, z.ZodType>> => {
|
|
105
|
+
const found = new Map<string, { readonly schema: z.ZodType; readonly module: string }>();
|
|
106
|
+
for (const [module, exports] of await contractModules()) {
|
|
107
|
+
const skipped = new Set(UNRECORDED.get(module));
|
|
108
|
+
for (const [name, value] of exports) {
|
|
109
|
+
if (!(value instanceof z.ZodType) || skipped.has(name)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const held = found.get(name);
|
|
113
|
+
if (held !== undefined && held.schema !== value) {
|
|
114
|
+
throw new Error(`${held.module} and ${module} both export a schema named ${name}; the lock can record only one`);
|
|
115
|
+
}
|
|
116
|
+
found.set(name, { schema: value, module: held?.module ?? module });
|
|
93
117
|
}
|
|
118
|
+
}
|
|
119
|
+
return new Map([...found].toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([name, { schema }]) => [name, schema]));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const currentLock = async (): Promise<Record<string, unknown>> => {
|
|
123
|
+
const lock: Record<string, unknown> = {};
|
|
124
|
+
for (const [name, value] of await lockedSchemas()) {
|
|
94
125
|
try {
|
|
95
126
|
const schema = sorted(z.toJSONSchema(value, { unrepresentable: "any" })) as Record<string, unknown>;
|
|
96
127
|
// The dialect banner is identical on every entry; pure weight, no signal.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Every module of this package, loaded by walking src/ rather than through index.ts, so what reads the package's whole
|
|
2
|
+
// surface (the lock, the daemon's shape seed) sees every module whether or not the barrel re-exports it. The list comes
|
|
3
|
+
// from src/ even when this runs from dist/, since a dist/ file whose source is gone is never cleaned away.
|
|
4
|
+
|
|
5
|
+
import { readdirSync } from "node:fs";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { packageRoot } from "@intentic/constants/node";
|
|
9
|
+
|
|
10
|
+
// Package-relative to src/, forward slashes, without the extension: the specifier after `@intentic/sandbox-contract/`.
|
|
11
|
+
export type ContractModuleId = string;
|
|
12
|
+
|
|
13
|
+
const sourceModules = (dir: string): string[] =>
|
|
14
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
15
|
+
const path = join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
return sourceModules(path);
|
|
18
|
+
}
|
|
19
|
+
return entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".d.ts") ? [path] : [];
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// What one module exports, by name.
|
|
23
|
+
export type ContractModuleExports = ReadonlyMap<string, unknown>;
|
|
24
|
+
|
|
25
|
+
// Each module's exports by id, in id order; `index` is left out, being what this walk stands in for.
|
|
26
|
+
export const contractModules = async (): Promise<ReadonlyMap<ContractModuleId, ContractModuleExports>> => {
|
|
27
|
+
const root = packageRoot(import.meta.url);
|
|
28
|
+
const source = join(root, "src");
|
|
29
|
+
const fromSource = import.meta.url.endsWith(".ts");
|
|
30
|
+
const ids = sourceModules(source)
|
|
31
|
+
.map((path) => relative(source, path).replaceAll("\\", "/").replace(/\.ts$/, ""))
|
|
32
|
+
.filter((id) => id !== "index")
|
|
33
|
+
.toSorted();
|
|
34
|
+
const modules = new Map<ContractModuleId, ContractModuleExports>();
|
|
35
|
+
for (const id of ids) {
|
|
36
|
+
const file = fromSource ? join(source, `${id}.ts`) : join(root, "dist", `${id}.js`);
|
|
37
|
+
const namespace: object = await import(pathToFileURL(file).href);
|
|
38
|
+
modules.set(id, new Map(Object.entries(namespace)));
|
|
39
|
+
}
|
|
40
|
+
return modules;
|
|
41
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { HelloSchema, type SystemEvent, SystemEventSchema } from "../events/system-events.js";
|
|
2
|
+
import { daemonBelowFloor, daemonVersionOfBuild, OLDEST_SUPPORTED_DAEMON } from "./daemon-floor.js";
|
|
3
|
+
import { DEV_VERSION, parseVersion } from "./versions.js";
|
|
4
|
+
|
|
5
|
+
describe(`the oldest supported daemon`, () => {
|
|
6
|
+
it(`names a released version`, () => {
|
|
7
|
+
expect(parseVersion(OLDEST_SUPPORTED_DAEMON)?.prerelease).toEqual([]);
|
|
8
|
+
expect(OLDEST_SUPPORTED_DAEMON).not.toBe(DEV_VERSION);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it(`reads the version off the head of a hello's build id`, () => {
|
|
12
|
+
expect(daemonVersionOfBuild(`1.337.0:1760000000000`)).toBe(`1.337.0`);
|
|
13
|
+
expect(daemonVersionOfBuild(`0.0.0:1760000000000`)).toBe(`0.0.0`);
|
|
14
|
+
// A bare version, should the mtime ever go, still reads.
|
|
15
|
+
expect(daemonVersionOfBuild(`1.249.0`)).toBe(`1.249.0`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it(`reads no version where the build names none`, () => {
|
|
19
|
+
expect(daemonVersionOfBuild(undefined)).toBeUndefined();
|
|
20
|
+
expect(daemonVersionOfBuild(``)).toBeUndefined();
|
|
21
|
+
expect(daemonVersionOfBuild(`test`)).toBeUndefined();
|
|
22
|
+
expect(daemonVersionOfBuild(`:1760000000000`)).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it(`calls a release older than the floor too old, and the floor itself and anything newer supported`, () => {
|
|
26
|
+
expect(daemonBelowFloor(`1.248.0`)).toBe(true);
|
|
27
|
+
expect(daemonBelowFloor(`1.0.0`)).toBe(true);
|
|
28
|
+
expect(daemonBelowFloor(OLDEST_SUPPORTED_DAEMON)).toBe(false);
|
|
29
|
+
expect(daemonBelowFloor(`1.337.0`)).toBe(false);
|
|
30
|
+
expect(daemonBelowFloor(`2.0.0`)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// A checkout's build never checks for updates and has none to take; an unknown version is no evidence at all.
|
|
34
|
+
it(`never calls a build from a checkout, or an unknown version, too old`, () => {
|
|
35
|
+
expect(daemonBelowFloor(DEV_VERSION)).toBe(false);
|
|
36
|
+
expect(daemonBelowFloor(undefined)).toBe(false);
|
|
37
|
+
expect(daemonBelowFloor(`unknown`)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// WHAT KEEPS THE FLOOR HONEST. A daemon at the floor sends every /events frame with the fields it had then. A field
|
|
42
|
+
// made required since is one such a daemon leaves out, and the editor reads the gap as something gone wrong (a
|
|
43
|
+
// heartbeat with no `rev` re-reads the whole roster every two seconds). So the fields each frame requires are frozen
|
|
44
|
+
// here, every kind by name (the `satisfies` makes a kind left out a type error). Adding a frame kind is fine (an older
|
|
45
|
+
// daemon simply never sends it): add its required fields below. Making a field of an existing kind required is not:
|
|
46
|
+
// make it optional, or raise OLDEST_SUPPORTED_DAEMON to the first release that sends it, with a dated line saying why.
|
|
47
|
+
const REQUIRED_SINCE_FLOOR = {
|
|
48
|
+
hello: [`kind`, `workspaceId`],
|
|
49
|
+
heartbeat: [`kind`, `rev`],
|
|
50
|
+
boot: [`kind`, `ready`, `startedAt`, `steps`],
|
|
51
|
+
workspaceChanged: [`kind`, `paths`],
|
|
52
|
+
treeChanged: [`dirs`, `from`, `generation`, `kind`],
|
|
53
|
+
derivedChanged: [`kind`, `paths`],
|
|
54
|
+
reposChanged: [`kind`, `repos`],
|
|
55
|
+
refsChanged: [`kind`, `repos`],
|
|
56
|
+
runtimeChanged: [`domains`, `kind`],
|
|
57
|
+
presence: [`kind`, `users`],
|
|
58
|
+
agents: [`agents`, `kind`, `rev`],
|
|
59
|
+
accountUsage: [`account`, `kind`, `provider`],
|
|
60
|
+
providerRefusal: [`kind`, `provider`],
|
|
61
|
+
} satisfies Record<SystemEvent[`kind`], readonly string[]>;
|
|
62
|
+
|
|
63
|
+
// What a frame of this kind cannot go without: every field a frame carrying nothing but its kind is refused for.
|
|
64
|
+
const requiredOf = (kind: string): string[] => {
|
|
65
|
+
const parsed = SystemEventSchema.safeParse({ kind });
|
|
66
|
+
const missing = parsed.success ? [] : parsed.error.issues.map((issue) => String(issue.path[0]));
|
|
67
|
+
return [...new Set([`kind`, ...missing])].toSorted();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
describe(`the /events frames a daemon at the floor sends`, () => {
|
|
71
|
+
it(`require no field the floor did not send`, () => {
|
|
72
|
+
for (const [kind, fields] of Object.entries(REQUIRED_SINCE_FLOOR)) {
|
|
73
|
+
expect({ kind, required: requiredOf(kind) }).toEqual({ kind, required: [...fields] });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// What the editor reads its verdict off: the route list, the fingerprints and the build that opens with the version.
|
|
78
|
+
it(`keep the hello fields the editor gates on`, () => {
|
|
79
|
+
expect(HelloSchema.keyof().options).toEqual(expect.arrayContaining([`routes`, `shapes`, `build`]));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { compareVersions, DEV_VERSION, parseVersion } from "./versions.js";
|
|
2
|
+
|
|
3
|
+
// THE OLDEST SANDBOX DAEMON THIS EDITOR SUPPORTS. The hosted editor deploys from every green push to main, and a
|
|
4
|
+
// sandbox runs whatever release it last took, so the editor always talks to daemons older than itself. Above this
|
|
5
|
+
// floor an older daemon is met feature by feature (the route list and shape fingerprints on its /events hello, read by
|
|
6
|
+
// the editor's useDaemonRoutes). Below it the editor draws one page saying the sandbox is too old and offering the
|
|
7
|
+
// update, instead of the faults that page would otherwise be built from.
|
|
8
|
+
//
|
|
9
|
+
// Raise it deliberately, with a dated line below naming what the editor now depends on and the first release that has
|
|
10
|
+
// it (`git tag --contains <sha> | sort -V | head -1`). COMPATIBILITY.md quotes the current value.
|
|
11
|
+
//
|
|
12
|
+
// 2026-10-15: 1.249.0, the first release whose /events stream carries everything the editor reads off it. The hello's
|
|
13
|
+
// route list and its `build`, which opens with the version (1.0.0), the hello's shape fingerprints (1.187.0), and the
|
|
14
|
+
// roster revision on every heartbeat (`rev`, 1.249.0). Against a daemon without `rev`, every heartbeat reads as a
|
|
15
|
+
// missed roster snapshot, so the editor reads GET /agents again every two seconds and logs `fleet.roster-behind`
|
|
16
|
+
// each time. Since 1.249.0 every frame has only gained optional fields and new kinds, which daemon-floor.test.ts
|
|
17
|
+
// holds to.
|
|
18
|
+
export const OLDEST_SUPPORTED_DAEMON = `1.249.0`;
|
|
19
|
+
|
|
20
|
+
// The version a hello's `build` names. The daemon's build id is `<version>:<newest mtime of its compiled tree>`
|
|
21
|
+
// (_sandbox/sandbox/src/version.ts `buildId`), and has been since 1.0.0. Undefined when there is no build, or its head is
|
|
22
|
+
// not a version.
|
|
23
|
+
export const daemonVersionOfBuild = (build: string | undefined): string | undefined => {
|
|
24
|
+
const head = build?.split(`:`, 1)[0]?.trim();
|
|
25
|
+
return head === undefined || head === `` || parseVersion(head) === undefined ? undefined : head;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// True only on evidence: a released version older than the floor. A daemon that names no version, one whose version
|
|
29
|
+
// does not parse, and a build from a checkout (0.0.0, which never checks for updates and has nothing to update to) are
|
|
30
|
+
// never too old.
|
|
31
|
+
export const daemonBelowFloor = (version: string | undefined, floor: string = OLDEST_SUPPORTED_DAEMON): boolean =>
|
|
32
|
+
version !== undefined && version !== DEV_VERSION && parseVersion(version) !== undefined && compareVersions(version, floor) < 0;
|
|
@@ -22,6 +22,9 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
22
22
|
// conversation's own runtime session store (what `.intentic/records/sessions/claude/` holds for every other).
|
|
23
23
|
// Carried whole, like the conversation it is.
|
|
24
24
|
{ path: "conversations/", portability: "carry" },
|
|
25
|
+
// The tasks this sandbox's conversations handed to linked sandboxes (links/linked-children.ts), followed until they
|
|
26
|
+
// settle: part of the conversations' own record, so it goes where they go.
|
|
27
|
+
{ path: "links/linked-children.json", portability: "carry" },
|
|
25
28
|
// The long tool outputs those transcripts name rather than hold, stored once by content for every record naming
|
|
26
29
|
// them; a transcript without them keeps only each output's start.
|
|
27
30
|
{ path: "blobs/", portability: "carry" },
|
|
@@ -57,6 +60,9 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
57
60
|
// What each account has run out of, for a plan publishing no allowance to poll — the refusal is the reading.
|
|
58
61
|
// Carried like the refusals above: an allowance belongs to the account, not to the machine that spent it.
|
|
59
62
|
{ path: "observed-limits.json", portability: "carry" },
|
|
63
|
+
// When each provider's allowance last ran out and under how many agents, which the spawn listing quotes beside the
|
|
64
|
+
// figure left. Carried like the refusals: it describes the subscription's windows, not the machine that spent them.
|
|
65
|
+
{ path: "run-outs.json", portability: "carry" },
|
|
60
66
|
// First-time dependency setup requests; carried so an interrupted queue survives export, entries self-remove.
|
|
61
67
|
{ path: "dependency-requests.json", portability: "carry" },
|
|
62
68
|
// Which device cards were already offered; carry, not identity, since it holds ids, never a token.
|
|
@@ -166,6 +172,9 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
166
172
|
|
|
167
173
|
// Signs every browser session cookie; carrying it would let the holder mint sessions against the target.
|
|
168
174
|
{ path: "session-secret", portability: "identity", note: "Sign in again, the target signs its own sessions." },
|
|
175
|
+
// The platform's id for this sandbox's row, as its announce last said (system/boot/platform-row.ts): the target is
|
|
176
|
+
// its own row, and learns its own id at its first announce.
|
|
177
|
+
{ path: "platform-row.json", portability: "identity", note: "The target learns its own platform id when it first announces." },
|
|
169
178
|
// Who owns this sandbox, who else may reach it and at what tier, and the hashed control tokens programs present:
|
|
170
179
|
// the daemon's to decide, so kept off the workspace every turn writes (auth/members/identity-off-workspace.ts moved
|
|
171
180
|
// them from `.intentic/identity/` on 2026-10-06). An access list that traveled would let a source sandbox hand
|
|
@@ -177,6 +186,14 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
177
186
|
note: "Re-invite collaborators from the Access tab, a grant is the platform's record, and the target enforces its own copy.",
|
|
178
187
|
},
|
|
179
188
|
{ path: "identity/control-tokens.json", portability: "identity", note: "Mint fresh control tokens, the old ones authenticate against the source sandbox." },
|
|
189
|
+
// The links other sandboxes hand this one tasks through (links/inbound-links.ts): hashed tokens like the control
|
|
190
|
+
// tokens, each minted here, and the tasks filed under them; elsewhere they would let the paired sandboxes reach the
|
|
191
|
+
// target in this one's name.
|
|
192
|
+
{
|
|
193
|
+
path: "identity/links.json",
|
|
194
|
+
portability: "identity",
|
|
195
|
+
note: "Pair the sandboxes that hand this one tasks again from its Access tab: their links name the source sandbox.",
|
|
196
|
+
},
|
|
180
197
|
// Public keys, the owner's require-a-passkey switch and the hashes of their recovery codes, moved from
|
|
181
198
|
// `.intentic/identity/passkeys.json` the same way (the passkeys-off-workspace step), since a passkey sign-in trusts
|
|
182
199
|
// the row it finds: a copy that traveled would let a source sandbox's passkeys open the target.
|
|
@@ -331,7 +331,7 @@ const STATE_FILES = [
|
|
|
331
331
|
// Owner's per-extension update posture (notify/agent/auto, advisory opt-out). `carry`: a decision about the
|
|
332
332
|
// extension, not the machine.
|
|
333
333
|
{ path: ".intentic/config/extension-update-policy.json", invalidates: ["extensions"], portability: "carry", versioned: true },
|
|
334
|
-
// Which installed extensions run from a source checkout instead of their pinned clone (
|
|
334
|
+
// Which installed extensions run from a source checkout instead of their pinned clone (capabilities/extension-inventory/extension-dev.ts).
|
|
335
335
|
// Local and never versioned: it can name a conversation's worktree, a path that means nothing in a commit or on
|
|
336
336
|
// another machine. `extensions` because the row says where it runs from, and a rebuild reported here is what offers
|
|
337
337
|
// the reload.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TranscriptRow } from "../events/transcript.js";
|
|
2
|
+
import { deliverableKindOf, documentOf, documentTitle, isDocumentPath, isPlanDocumentPath, producedDocumentPaths } from "./documents.js";
|
|
2
3
|
import { PLAN_DOCUMENTS_DIR } from "../state/workspace-state.js";
|
|
3
4
|
|
|
4
5
|
const write = (path: string, newText: string, extra: { oldText?: string; truncated?: boolean } = {}) => [
|
|
@@ -88,3 +89,31 @@ describe("deliverableKindOf", () => {
|
|
|
88
89
|
]);
|
|
89
90
|
});
|
|
90
91
|
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
test("ODF text, sheets, slides and RTF are result documents", () => {
|
|
95
|
+
expect(["letter.ODT", "budget.ods", "briefing.odp", "letter.rtf"].map(deliverableKindOf)).toEqual(["odt", "ods", "odp", "rtf"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("producedDocumentPaths", () => {
|
|
99
|
+
const rows: TranscriptRow[] = [
|
|
100
|
+
{ role: "user", text: "make me letter.odt and notes.pdf" },
|
|
101
|
+
{
|
|
102
|
+
role: "assistant",
|
|
103
|
+
text: "Done",
|
|
104
|
+
tools: [
|
|
105
|
+
{ id: "1", name: "Write", category: "edit", status: "completed", content: write("./out/letter.odt", "") },
|
|
106
|
+
{ id: "2", name: "Write", category: "edit", status: "failed", locations: [{ path: "broken.docx" }] },
|
|
107
|
+
{ id: "3", name: "Bash", category: "execute", status: "failed", locations: [{ path: "deck.pptx" }, { path: "build.log" }] },
|
|
108
|
+
{ id: "4", name: "Bash", category: "execute", status: "completed", locations: [{ path: "draft.pdf" }] },
|
|
109
|
+
{ id: "5", name: "Bash", category: "delete", status: "completed", locations: [{ path: "draft.pdf" }] },
|
|
110
|
+
{ id: "6", name: "Read", category: "read", status: "completed", locations: [{ path: "source.xlsx" }] },
|
|
111
|
+
{ id: "7", name: "Task", category: "think", status: "completed", children: [{ id: "8", name: "Write", category: "edit", status: "completed", locations: [{ path: "budget.ods" }] }] },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
test("lists what settled calls recorded writing, never what prose or a read named, minus what was deleted", () => {
|
|
117
|
+
expect(producedDocumentPaths(rows)).toEqual(["out/letter.odt", "deck.pptx", "budget.ods"]);
|
|
118
|
+
});
|
|
119
|
+
});
|
package/src/text/documents.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RequestDocument, ToolCallContent } from "../events/requests.js";
|
|
2
|
+
import type { TranscriptRow, TranscriptTool } from "../events/transcript.js";
|
|
2
3
|
import { planParts } from "./title.js";
|
|
3
4
|
import { PLAN_DOCUMENTS_DIR } from "../state/workspace-state.js";
|
|
4
5
|
|
|
@@ -17,10 +18,14 @@ export const isDocumentPath = (path: string): boolean => {
|
|
|
17
18
|
// A deliverable is a file a turn hands a person to open rather than read as code: an office document, a PDF, a web
|
|
18
19
|
// page. The daemon names the ones a command wrote on its card (agent/tools/produced-documents.ts) and the chat lists a
|
|
19
20
|
// turn's under its answer, so the two ask this one table. Markdown stays out: the chat already draws it as a document.
|
|
20
|
-
export type DeliverableKind = "docx" | "pptx" | "xlsx" | "pdf" | "html";
|
|
21
|
+
export type DeliverableKind = "docx" | "pptx" | "xlsx" | "pdf" | "html" | "odt" | "ods" | "odp" | "rtf";
|
|
21
22
|
|
|
22
23
|
const DELIVERABLE_BY_EXTENSION = new Map<string, DeliverableKind>([
|
|
23
24
|
["docx", "docx"],
|
|
25
|
+
["odt", "odt"],
|
|
26
|
+
["ods", "ods"],
|
|
27
|
+
["odp", "odp"],
|
|
28
|
+
["rtf", "rtf"],
|
|
24
29
|
["pptx", "pptx"],
|
|
25
30
|
["xlsx", "xlsx"],
|
|
26
31
|
["pdf", "pdf"],
|
|
@@ -64,3 +69,40 @@ export const documentOf = (name: string, content: readonly ToolCallContent[] | u
|
|
|
64
69
|
...(isPlanDocumentPath(diff.path) ? { plan: true } : {}),
|
|
65
70
|
};
|
|
66
71
|
};
|
|
72
|
+
|
|
73
|
+
// The deliverables a conversation's calls recorded writing, across its whole transcript, in the order first written;
|
|
74
|
+
// one a later call deleted is gone. The rule the chat's document chips follow per turn (the editor's deliverables.ts):
|
|
75
|
+
// an edit's locations and diffs, a settled command's locations (what the daemon found it wrote), a delete's locations.
|
|
76
|
+
// Recorded writes only, never a path named in prose or a command's text, since the daemon serves these paths' bytes
|
|
77
|
+
// to the conversation's owner on the strength of this list (conversations/produced-files.ts).
|
|
78
|
+
export const producedDocumentPaths = (rows: readonly TranscriptRow[]): string[] => {
|
|
79
|
+
const paths = new Set<string>();
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
for (const { path, deleted } of row.role === "assistant" ? recordedWrites(row.tools ?? []) : []) {
|
|
82
|
+
if (deleted) {
|
|
83
|
+
paths.delete(path);
|
|
84
|
+
} else {
|
|
85
|
+
paths.add(path);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...paths];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// What one settled call wrote or deleted, by name (a leading `./` folded away); a failed edit wrote nothing, a failed
|
|
93
|
+
// command still wrote what the daemon found it wrote.
|
|
94
|
+
const writesOf = (tool: TranscriptTool): readonly { readonly path: string; readonly deleted: boolean }[] => {
|
|
95
|
+
const settled = tool.status === "completed" || (tool.category === "execute" && tool.status === "failed");
|
|
96
|
+
if (!settled || (tool.category !== "edit" && tool.category !== "execute" && tool.category !== "delete")) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const diffed = tool.category === "edit" ? (tool.content ?? []).flatMap((entry) => (entry.type === "diff" ? [entry.path] : [])) : [];
|
|
100
|
+
return [...(tool.locations ?? []).map(({ path }) => path), ...diffed].map((name) => ({
|
|
101
|
+
path: name.startsWith("./") ? name.slice(2) : name,
|
|
102
|
+
deleted: tool.category === "delete",
|
|
103
|
+
}));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Depth first, a delegation's own calls right after the card that started them; deliverables only.
|
|
107
|
+
const recordedWrites = (tools: readonly TranscriptTool[]): { readonly path: string; readonly deleted: boolean }[] =>
|
|
108
|
+
tools.flatMap((tool) => [...writesOf(tool).filter(({ path }) => deliverableKindOf(path) !== undefined), ...recordedWrites(tool.children ?? [])]);
|
package/src/text/pages.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// daemon's headless check (agent/pages/page-check.ts), which lays the page out the way the chat will, and the chat's
|
|
3
3
|
// own frame (the editor's chat/transcript/pages). A page runs its scripts in a frame with an opaque origin and no
|
|
4
4
|
// network: the policy below is the whole of what it may load, and the bootstrap is the whole of what it may say.
|
|
5
|
+
import { parseJsonOr } from "@intentic/base/json";
|
|
5
6
|
|
|
6
7
|
// Everything a sealed page may load is already inside it: its own inline scripts and styles, and pictures, fonts and
|
|
7
8
|
// media carried in as data. Nothing reaches the network, another frame, or a form target.
|
|
@@ -331,13 +332,10 @@ export const readMcpAppData = (html: string): McpAppData | undefined => {
|
|
|
331
332
|
if (match === null) {
|
|
332
333
|
return undefined;
|
|
333
334
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
// allow(silent-catch): a block that does not parse is not an app's, as the comment on this function says.
|
|
339
|
-
return undefined;
|
|
340
|
-
}
|
|
335
|
+
// A block that does not parse is not an app's, as the comment on this function says.
|
|
336
|
+
// SAFETY: server and tool are checked by type below; input and result are opaque to this reader.
|
|
337
|
+
const data = parseJsonOr(match[1] ?? ``, undefined) as Partial<McpAppData> | null | undefined;
|
|
338
|
+
return typeof data?.server === `string` && typeof data.tool === `string` ? { server: data.server, tool: data.tool, input: data.input, result: data.result } : undefined;
|
|
341
339
|
};
|
|
342
340
|
|
|
343
341
|
// A page's answer as the agent is handed it: JSON text, whatever the page sent, refused past the cap rather than cut.
|