@intentic/sandbox-contract 1.337.0 → 1.338.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 +36 -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 +6016 -5994
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -0
- 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/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 +20 -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 +7555 -7426
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -257
- package/dist/index.js.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/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/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 +269 -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.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 +11 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +2 -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/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/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/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 +7 -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 +5 -0
- 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/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/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/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.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 +8 -1
- package/src/schemas/git/git.ts +22 -2
- package/src/schemas/hosts.ts +14 -1
- 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/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/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 +14 -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
|
@@ -158,11 +158,11 @@ test("a far end that never answers is reported a few times, then retried quietly
|
|
|
158
158
|
try {
|
|
159
159
|
const { link, sockets, said } = dialling(60_000);
|
|
160
160
|
for (let attempt = 1; attempt <= 20; attempt += 1) {
|
|
161
|
-
//
|
|
161
|
+
// one attempt after another is the thing under test
|
|
162
162
|
await waitFor(() => expect(sockets).toHaveLength(attempt));
|
|
163
163
|
sockets.at(-1)?.errors();
|
|
164
164
|
sockets.at(-1)?.drops(1006);
|
|
165
|
-
//
|
|
165
|
+
// the ladder's own wait, serial by construction
|
|
166
166
|
await advanceTimersByTimeAsync(60_000);
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -197,10 +197,10 @@ test("a link that comes back is loud again about the outage after it", async ()
|
|
|
197
197
|
try {
|
|
198
198
|
const { link, sockets, said } = dialling();
|
|
199
199
|
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
200
|
-
//
|
|
200
|
+
// one attempt after another is the thing under test
|
|
201
201
|
await waitFor(() => expect(sockets).toHaveLength(attempt));
|
|
202
202
|
sockets.at(-1)?.drops(1006);
|
|
203
|
-
//
|
|
203
|
+
// the ladder's own wait, serial by construction
|
|
204
204
|
await advanceTimersByTimeAsync(1_000);
|
|
205
205
|
}
|
|
206
206
|
const whileQuiet = said.length;
|
|
@@ -273,10 +273,10 @@ test("a link nobody has answered in a long time rests between tries, and is neve
|
|
|
273
273
|
const { link, sockets, said } = dialling(RUNG_MS);
|
|
274
274
|
// Up to the threshold on the ordinary ladder: drop, wait its rung, get the next socket.
|
|
275
275
|
for (let attempt = 1; attempt < LONG_OUTAGE_ATTEMPTS; attempt += 1) {
|
|
276
|
-
//
|
|
276
|
+
// one drop per attempt is serial by definition
|
|
277
277
|
await waitFor(() => expect(sockets).toHaveLength(attempt));
|
|
278
278
|
sockets.at(-1)?.drops(1006);
|
|
279
|
-
//
|
|
279
|
+
// ditto
|
|
280
280
|
await advanceTimersByTimeAsync(RUNG_MS);
|
|
281
281
|
}
|
|
282
282
|
await waitFor(() => expect(sockets).toHaveLength(LONG_OUTAGE_ATTEMPTS));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorMessage } from "@intentic/base/errors";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { createMcpServer, type McpAuditEntry, textResult, tool } from "./peer-mcp-server.js";
|
|
3
4
|
|
|
@@ -32,7 +33,7 @@ const build = () => {
|
|
|
32
33
|
],
|
|
33
34
|
noSuchTool: (name) => `This peer has no tool called "${name}".`,
|
|
34
35
|
refused: (error) => error instanceof Refused,
|
|
35
|
-
errorMessage
|
|
36
|
+
errorMessage,
|
|
36
37
|
audit: (entry) => void audits.push(entry),
|
|
37
38
|
});
|
|
38
39
|
return { handle, audits };
|
|
@@ -19,7 +19,7 @@ import { MemberEmailSchema, MemberGrantSchema, MemberSelfSchema, MembersListSche
|
|
|
19
19
|
import { PhonesListSchema, PhoneWakeRegistrationSchema } from "../../schemas/phone.js";
|
|
20
20
|
import { OkSchema } from "../../schemas/shared.js";
|
|
21
21
|
import { EnrollCodeAnswerSchema, EnrollCodeRequestSchema, PeerPairingAnswerSchema, SyncPairAnswerSchema, SyncPairRequestSchema } from "../../schemas/short-codes.js";
|
|
22
|
-
import { SpeechStatusSchema, SpeechTranscriptSchema } from "../../schemas/speech.js";
|
|
22
|
+
import { SpeechStatusSchema, SpeechTranscriptSchema, SpeechWakeClaimSchema, SpeechWakeGrantSchema } from "../../schemas/speech.js";
|
|
23
23
|
import {
|
|
24
24
|
AuthenticationOptionsSchema,
|
|
25
25
|
DaemonSessionSchema,
|
|
@@ -35,6 +35,20 @@ import {
|
|
|
35
35
|
SessionRecoveredSchema,
|
|
36
36
|
} from "../../schemas/system.js";
|
|
37
37
|
import { WebExtsListSchema } from "../../schemas/webext.js";
|
|
38
|
+
import {
|
|
39
|
+
InboundLinkMintedSchema,
|
|
40
|
+
InboundLinkMintSchema,
|
|
41
|
+
InboundLinksListSchema,
|
|
42
|
+
InboundLinkSummarySchema,
|
|
43
|
+
InboundLinkTermsUpdateSchema,
|
|
44
|
+
LinkAnswerSchema,
|
|
45
|
+
LinkHelloSchema,
|
|
46
|
+
LinkMessageReceiptSchema,
|
|
47
|
+
LinkMessageSchema,
|
|
48
|
+
LinkRequestDecisionSchema,
|
|
49
|
+
LinkTaskSchema,
|
|
50
|
+
LinkTaskStatusSchema,
|
|
51
|
+
} from "../../schemas/links.js";
|
|
38
52
|
import { UploadManifestSchema, UploadSkipSchema } from "../../schemas/workspace/workspace-upload.js";
|
|
39
53
|
import {
|
|
40
54
|
ArrivalAbandonedSchema,
|
|
@@ -128,6 +142,20 @@ export const RAW_JSON_ROUTES = {
|
|
|
128
142
|
"DELETE /system/authorized-key/{machine}": { output: OkSchema },
|
|
129
143
|
// Every browser session revoked, the owner's own included.
|
|
130
144
|
"POST /system/sessions/revoke": { output: OkSchema },
|
|
145
|
+
// Inbound links: the roster, a mint (its token shown once), new terms, a revoke, and the owner's answer to a waiting task.
|
|
146
|
+
"GET /system/links": { output: InboundLinksListSchema },
|
|
147
|
+
"POST /system/links": { input: InboundLinkMintSchema, output: InboundLinkMintedSchema },
|
|
148
|
+
"POST /system/links/{id}": { input: InboundLinkTermsUpdateSchema, output: InboundLinkSummarySchema },
|
|
149
|
+
"DELETE /system/links/{id}": { output: OkSchema },
|
|
150
|
+
"POST /system/links/{id}/requests/{request}": { input: LinkRequestDecisionSchema, output: InboundLinkSummarySchema },
|
|
151
|
+
// The link doors, as the asking sandbox's daemon reads them. A status takes `?after=<version>&wait=<seconds>` to
|
|
152
|
+
// be held until it moves past that version.
|
|
153
|
+
"GET /link": { output: LinkHelloSchema },
|
|
154
|
+
"POST /link/conversations": { input: LinkTaskSchema, output: LinkTaskStatusSchema },
|
|
155
|
+
"GET /link/conversations/{id}": { output: LinkTaskStatusSchema },
|
|
156
|
+
"POST /link/conversations/{id}/messages": { input: LinkMessageSchema, output: LinkMessageReceiptSchema },
|
|
157
|
+
"POST /link/conversations/{id}/answer": { input: LinkAnswerSchema, output: OkSchema },
|
|
158
|
+
"POST /link/conversations/{id}/stop": { output: LinkTaskStatusSchema },
|
|
131
159
|
"GET /system/control/tokens": { output: ControlTokensListSchema },
|
|
132
160
|
"POST /system/control/tokens": { input: ControlTokenMintSchema, output: ControlTokenMintedSchema },
|
|
133
161
|
"DELETE /system/control/tokens/{id}": { output: OkSchema },
|
|
@@ -151,6 +179,8 @@ export const RAW_JSON_ROUTES = {
|
|
|
151
179
|
"GET /speech/status": { output: SpeechStatusSchema },
|
|
152
180
|
"POST /speech/prepare": { output: SpeechStatusSchema },
|
|
153
181
|
"POST /speech/transcribe": { bytes: true, output: SpeechTranscriptSchema },
|
|
182
|
+
// One device's claim on a wake word it heard, so two devices hearing the same "Leon" open one session between them.
|
|
183
|
+
"POST /speech/wake": { input: SpeechWakeClaimSchema, output: SpeechWakeGrantSchema },
|
|
154
184
|
// A file written into the tree (`?path=`, the body its bytes, `x-intentic-base-hash` refusing a stale save), and the
|
|
155
185
|
// pre-drop check of which dropped files are already there.
|
|
156
186
|
"POST /workspace/upload": { bytes: true, output: OkSchema },
|
|
@@ -18,6 +18,8 @@ export const RAW_ROUTES = {
|
|
|
18
18
|
"GET /speech/status": { guest: true },
|
|
19
19
|
"POST /speech/prepare": { guest: true },
|
|
20
20
|
"POST /speech/transcribe": { floor: "collaborator", guest: true },
|
|
21
|
+
// A device claiming a wake word it heard (schemas/speech.ts): what it opens is a turn, so the collaborator's.
|
|
22
|
+
"POST /speech/wake": { floor: "collaborator" },
|
|
21
23
|
// The live phrase stream (schemas/speech.ts): a WebSocket, so the query ticket, floored at the collaborator in the
|
|
22
24
|
// handler like the transcribe it streams.
|
|
23
25
|
"GET /speech/stream": { auth: "door" },
|
|
@@ -27,6 +29,9 @@ export const RAW_ROUTES = {
|
|
|
27
29
|
"GET /speech/voices": { guest: true },
|
|
28
30
|
"POST /speech/say": { floor: "collaborator" },
|
|
29
31
|
"GET /speech/say": { auth: "door" },
|
|
32
|
+
// A conversation's recorded documents as bytes, for whoever owns it, a guest with no Files included; the route
|
|
33
|
+
// serves those paths alone (the daemon's conversations/produced-files.ts).
|
|
34
|
+
"GET /agents/{id}/documents/raw": { guest: true, lane: "bulk" },
|
|
30
35
|
// The bytes behind the workspace reads; each applies the caller's fence itself.
|
|
31
36
|
"GET /workspace/raw": { guest: true, lane: "bulk" },
|
|
32
37
|
// No door, unlike media: the page fetches a thumbnail itself, so the request carries the header.
|
|
@@ -118,9 +123,10 @@ export const RAW_ROUTES = {
|
|
|
118
123
|
"ALL /x/*": {},
|
|
119
124
|
// The `capabilities` CLI's discovery by name; its ask is the needs door (contracts/needs.contract.ts).
|
|
120
125
|
"GET /capabilities/connectable": { floor: "maintainer", agent: true, control: "never" },
|
|
121
|
-
// The `sandboxes` CLI; every create parks on a card in the owner's chat first.
|
|
126
|
+
// The `sandboxes` CLI; every create and every removal parks on a card in the owner's chat first.
|
|
122
127
|
"GET /sandboxes": { agent: true },
|
|
123
128
|
"POST /sandboxes": { agent: true },
|
|
129
|
+
"POST /sandboxes/remove": { agent: true },
|
|
124
130
|
// The `wallet` CLI; spend is bounded by the owner's policy and the key never enters the container.
|
|
125
131
|
"GET /wallet/status": { agent: true, control: "never" },
|
|
126
132
|
"POST /wallet/fetch": { agent: true, control: "never" },
|
|
@@ -135,6 +141,10 @@ export const RAW_ROUTES = {
|
|
|
135
141
|
"POST /children/answer": { agent: true, control: "never" },
|
|
136
142
|
"POST /children/cancel": { agent: true, control: "never" },
|
|
137
143
|
"POST /children/merge": { agent: true, control: "never" },
|
|
144
|
+
// Handing a task to another of the owner's sandboxes over a link card (`agents delegate`), and which links this
|
|
145
|
+
// conversation's turn may hand work to (`agents links`); held to the cards the turn was granted, like the tool.
|
|
146
|
+
"POST /children/delegate": { agent: true, control: "never" },
|
|
147
|
+
"GET /children/links": { agent: true, control: "never" },
|
|
138
148
|
"GET /children": { agent: true, control: "never" },
|
|
139
149
|
// The fleet reads, and the one write: words in front of another conversation, which a person can do by typing.
|
|
140
150
|
"GET /fleet": { agent: true },
|
|
@@ -203,6 +213,12 @@ export const RAW_ROUTES = {
|
|
|
203
213
|
// A connected browser's credential doors, on its durable token: a sign-in moves in (`session`) or out (`lend`).
|
|
204
214
|
"POST /system/webext/session": { auth: "door", control: "never" },
|
|
205
215
|
"POST /system/webext/lend": { auth: "door", control: "never" },
|
|
216
|
+
// A secret the browser types for the agent (`type_secret` aimed at it): one value for one ticket the daemon minted
|
|
217
|
+
// for this browser and one host, handed back on the browser's own request rather than over the tool socket.
|
|
218
|
+
"POST /system/webext/secret": { auth: "door", control: "never" },
|
|
219
|
+
// This sandbox's keyring keys (schemas/keyring.ts), public halves only: a keyring fetches them here over TLS, from the
|
|
220
|
+
// sandbox's own address, before it shares anything with it, rather than take them from whoever relays the share.
|
|
221
|
+
"GET /keyring/keys": { auth: "door", control: "never" },
|
|
206
222
|
"POST /system/runners/{id}/definition/sync": { control: "never" },
|
|
207
223
|
// A runner's git door and credential doors, each on the runner's own bearer.
|
|
208
224
|
"GET /system/runners/git/{repo}/info/refs": { auth: "door", control: "never", lane: "bulk" },
|
|
@@ -218,6 +234,21 @@ export const RAW_ROUTES = {
|
|
|
218
234
|
// Its sibling for MCP: a hooked runtime's (Cursor's) calls to its turn's MCP servers, on a session signed the same
|
|
219
235
|
// way, naming the provider and the one server it may forward to. Held open while a server streams its answer.
|
|
220
236
|
"ALL /privacy/mcp/{session}": { auth: "door", control: "never", stream: true },
|
|
237
|
+
// Inbound links: another of the owner's sandboxes handing this one tasks (schemas/links.ts). Minted, re-termed and
|
|
238
|
+
// revoked by the owner alone, checked in the handler like a control token; a waiting task is the owner's to allow.
|
|
239
|
+
"GET /system/links": { control: "never" },
|
|
240
|
+
"POST /system/links": { control: "never" },
|
|
241
|
+
"POST /system/links/{id}": { control: "never" },
|
|
242
|
+
"DELETE /system/links/{id}": { control: "never" },
|
|
243
|
+
"POST /system/links/{id}/requests/{request}": { control: "never" },
|
|
244
|
+
// The link doors, called by the asking sandbox's daemon: each checks the link token itself (x-intentic-link) and
|
|
245
|
+
// reaches only the conversations that link opened, which is the whole of a link's reach.
|
|
246
|
+
"GET /link": { auth: "door", control: "never" },
|
|
247
|
+
"POST /link/conversations": { auth: "door", control: "never" },
|
|
248
|
+
"GET /link/conversations/{id}": { auth: "door", control: "never" },
|
|
249
|
+
"POST /link/conversations/{id}/messages": { auth: "door", control: "never" },
|
|
250
|
+
"POST /link/conversations/{id}/answer": { auth: "door", control: "never" },
|
|
251
|
+
"POST /link/conversations/{id}/stop": { auth: "door", control: "never" },
|
|
221
252
|
"POST /system/control/tokens": { control: "never" },
|
|
222
253
|
"GET /system/control/tokens": { control: "never" },
|
|
223
254
|
"DELETE /system/control/tokens/{id}": { control: "never" },
|
|
@@ -249,6 +280,9 @@ export const RAW_ROUTES = {
|
|
|
249
280
|
|
|
250
281
|
export type RawRouteKey = keyof typeof RAW_ROUTES;
|
|
251
282
|
|
|
283
|
+
// The raw routes the daemon registers itself: every one but those netd answers before a request reaches the daemon.
|
|
284
|
+
export type DaemonRawRouteKey = { [K in RawRouteKey]: (typeof RAW_ROUTES)[K] extends { readonly netd: true } ? never : K }[RawRouteKey];
|
|
285
|
+
|
|
252
286
|
// A raw route's path, for a caller that builds a URL to it rather than registering it.
|
|
253
287
|
export const rawRoutePath = (key: RawRouteKey): string => key.slice(key.indexOf(" ") + 1);
|
|
254
288
|
|
|
@@ -6,7 +6,8 @@ import type { MemberRole } from "../schemas/shared.js";
|
|
|
6
6
|
// declare nothing; the daemon's bearer, boot, timer, role-floor and grant decisions all derive from this.
|
|
7
7
|
|
|
8
8
|
// How far a control token reaches a route beyond what its floor alone gives the `read`/`drive` rungs.
|
|
9
|
-
// `never`: no rung; `editor`: the editor slice too; `read`: the read rung, though no GET
|
|
9
|
+
// `never`: no rung; `editor`: the editor slice too; `read`: the read rung and the editor slice, though no GET (watching
|
|
10
|
+
// a turn is part of driving one); `land`: the land rung too.
|
|
10
11
|
export type ControlReach = "never" | "editor" | "read" | "land";
|
|
11
12
|
|
|
12
13
|
export interface RouteMeta {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eventIterator, oc } from "@orpc/contract";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import { RAW_JSON_ROUTES } from "./raw/raw-json-routes.js";
|
|
4
|
+
import { RAW_ROUTE_NAMES, rawRouteFingerprints, SANDBOX_ROUTE_NAMES, SANDBOX_ROUTES, sandboxRouteName, sandboxRouteFingerprints } from "../index.js";
|
|
4
5
|
import { procedure } from "./route-meta.js";
|
|
5
6
|
import { contractRoutes, routeNameForRequest, routeShapes, servedRoute, streamOf } from "./routes.js";
|
|
6
7
|
|
|
@@ -273,6 +274,37 @@ describe(`the real sandbox contract`, () => {
|
|
|
273
274
|
expect(Object.keys(sandboxRouteFingerprints()).length).toBe(SANDBOX_ROUTE_NAMES.length);
|
|
274
275
|
});
|
|
275
276
|
|
|
277
|
+
// The other half of the surface. Unfingerprinted and unnamed, these were the routes no version check could see:
|
|
278
|
+
// a browser calling one an older daemon predates got a bare 404 and nothing to say about it.
|
|
279
|
+
it(`names every hand-written route beside the procedures, and keeps the two sets apart`, () => {
|
|
280
|
+
expect(RAW_ROUTE_NAMES).toContain(`GET /system/links`);
|
|
281
|
+
expect(RAW_ROUTE_NAMES.filter((name) => SANDBOX_ROUTE_NAMES.includes(name))).toEqual([]);
|
|
282
|
+
expect(new Set(RAW_ROUTE_NAMES).size).toBe(RAW_ROUTE_NAMES.length);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it(`fingerprints every hand-written route whose schemas can be expressed`, () => {
|
|
286
|
+
const prints = rawRouteFingerprints();
|
|
287
|
+
// The two exceptions are `z.custom()` (schemas/system.ts: the WebAuthn options the browser's own parser reads,
|
|
288
|
+
// checked here for a challenge and nothing else), which has no JSON Schema to hash. They are still advertised
|
|
289
|
+
// by name, so a daemon that lacks them is caught; only a change in their fields passes unnoticed. A third name
|
|
290
|
+
// here is a schema that quietly stopped being comparable, not a licence to add one.
|
|
291
|
+
expect(Object.keys(RAW_JSON_ROUTES).filter((name) => !(name in prints))).toEqual([
|
|
292
|
+
`POST /system/passkeys/register/options`,
|
|
293
|
+
`POST /system/passkeys/assert/options`,
|
|
294
|
+
]);
|
|
295
|
+
// Only those: a WebSocket upgrade or a byte stream declares nothing to compare, and an unfingerprinted route
|
|
296
|
+
// is compared on nothing rather than read as a disagreement.
|
|
297
|
+
expect(Object.keys(prints).every((name) => name in RAW_JSON_ROUTES)).toBe(true);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it(`fingerprints a route by what it carries, so a changed field is a different print`, () => {
|
|
301
|
+
const prints = rawRouteFingerprints();
|
|
302
|
+
expect(prints[`GET /system/links`]).toEqual(expect.any(String));
|
|
303
|
+
expect(prints[`GET /system/links`]).not.toBe(prints[`GET /members`]);
|
|
304
|
+
// A body of bytes has no input schema, and the answer alone decides its print.
|
|
305
|
+
expect(prints[`POST /workspace/upload`]).toBe(prints[`POST /workspace/upload-archive`]);
|
|
306
|
+
});
|
|
307
|
+
|
|
276
308
|
it(`derives a route table with no duplicate names`, () => {
|
|
277
309
|
expect(SANDBOX_ROUTE_NAMES.length).toBe(SANDBOX_ROUTES.length);
|
|
278
310
|
expect(new Set(SANDBOX_ROUTE_NAMES).size).toBe(SANDBOX_ROUTE_NAMES.length);
|
package/src/protocol/routes.ts
CHANGED
|
@@ -150,6 +150,24 @@ export const routeShapes = (contract: Record<string, unknown>): Record<string, s
|
|
|
150
150
|
return shapes;
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
+
// The same, for the hand-written routes that answer plain JSON (raw-json-routes.ts). They carry their schemas directly
|
|
154
|
+
// rather than through oRPC, so they need their own walk — and without one they were the half of the surface no version
|
|
155
|
+
// check could see: a browser calling a raw route an older daemon predates got a bare 404 with nothing to say about it.
|
|
156
|
+
// The table is passed in rather than imported, so this file stays free of the raw tables that read its types.
|
|
157
|
+
export const rawRouteFingerprintsOf = (table: Record<string, { readonly input?: unknown; readonly output?: unknown }>) => {
|
|
158
|
+
const prints: Record<string, string> = {};
|
|
159
|
+
for (const [name, route] of Object.entries(table)) {
|
|
160
|
+
try {
|
|
161
|
+
// A `text` or `bytes` route declares no input schema; its body is the document or the bytes, and the
|
|
162
|
+
// fingerprint is its answer alone.
|
|
163
|
+
prints[name] = fingerprint({ in: wireShape(route.input, "input"), out: wireShape(route.output, "output") });
|
|
164
|
+
} catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return prints;
|
|
169
|
+
};
|
|
170
|
+
|
|
153
171
|
// A template split once: a literal segment, or undefined for a `{param}`; `tail` for a raw route's trailing `/*`.
|
|
154
172
|
interface Pattern {
|
|
155
173
|
readonly route: ContractRoute;
|
package/src/protocol/sse.ts
CHANGED
|
@@ -144,7 +144,7 @@ export async function* sseEvents(body: ReadableStream<Uint8Array>, idleMs: numbe
|
|
|
144
144
|
const idle = new Promise<`idle`>((resolve) => {
|
|
145
145
|
timer = setTimeout(() => resolve(`idle`), idleMs);
|
|
146
146
|
});
|
|
147
|
-
//
|
|
147
|
+
// streaming reader; each chunk must be awaited in order as it arrives
|
|
148
148
|
const result = await Promise.race([reader.read(), idle]);
|
|
149
149
|
clearTimeout(timer);
|
|
150
150
|
if (result === `idle`) {
|
package/src/schemas/agent.ts
CHANGED
|
@@ -182,7 +182,7 @@ const AgentTurnFieldsSchema = z.object({
|
|
|
182
182
|
// (withRuntimeDefaults), rather than refusing a turn a stale composer setting named.
|
|
183
183
|
harness: AgentHarnessSchema.optional().describe("Which agentic loop runs the turn. Leave it out to use each provider's own."),
|
|
184
184
|
// Intent, not a guess: absent continues on the conversation's own account, or picks by serviceability where there is
|
|
185
|
-
// none on this provider yet (
|
|
185
|
+
// none on this provider yet (conversations/registry/routing.ts).
|
|
186
186
|
account: z
|
|
187
187
|
.string()
|
|
188
188
|
.optional()
|
package/src/schemas/agents.ts
CHANGED
|
@@ -997,6 +997,7 @@ export type AgentSpan = z.infer<typeof AgentSpanSchema>;
|
|
|
997
997
|
// the rest of the turn lands on top of it later. Doesn't apply to a turn parked on a question, which needs no override
|
|
998
998
|
// at all.
|
|
999
999
|
export const AgentLandSchema = z.object({
|
|
1000
|
+
includeDocuments: z.boolean().optional().describe("Include recorded result documents, even when ignored or classified as scratch."),
|
|
1000
1001
|
id: z.string().min(1).describe("Which conversation's work to merge."),
|
|
1001
1002
|
mode: LandModeSchema.optional().describe(
|
|
1002
1003
|
"How to apply it. The default applies every repository or none, so a refusal leaves the workspace exactly as it was. The other carries the whole composition and leaves conflicted paths with markers to resolve by hand.",
|
package/src/schemas/approvals.ts
CHANGED
|
@@ -94,6 +94,20 @@ export const ActionApprovalSchema = z.object({
|
|
|
94
94
|
.describe(
|
|
95
95
|
"What to do once approved, written for the fresh turn that will do it: names, ids and steps, since it has none of this conversation.",
|
|
96
96
|
),
|
|
97
|
+
// Written by a connection's own command when it holds an act (e-urzad's office acts), never by hand. Part of what
|
|
98
|
+
// the yes pins, like every field but the bookkeeping, so an item edited after the yes no longer matches it.
|
|
99
|
+
performer: z
|
|
100
|
+
.object({
|
|
101
|
+
extension: z.string().min(1).describe("The extension that carries it out, by its publisher.name (\"intentic.e-urzad\")."),
|
|
102
|
+
digest: z
|
|
103
|
+
.string()
|
|
104
|
+
.regex(/^[0-9a-f]{64}$/)
|
|
105
|
+
.describe("The SHA-256 of exactly what it will do, which the extension recomputes before acting and refuses on any difference."),
|
|
106
|
+
})
|
|
107
|
+
.optional()
|
|
108
|
+
.describe(
|
|
109
|
+
"Who carries this out instead of an agent turn: once you approve it, the sandbox hands it to that extension's backend, which acts only if what it holds still matches the digest. Written by the extension's own command when it holds an act; an item written by hand never needs one.",
|
|
110
|
+
),
|
|
97
111
|
...envelope,
|
|
98
112
|
});
|
|
99
113
|
export type ActionApproval = z.infer<typeof ActionApprovalSchema>;
|
|
@@ -33,6 +33,7 @@ export const CapabilityKindSchema = z.enum([
|
|
|
33
33
|
"localmodel",
|
|
34
34
|
"wallet",
|
|
35
35
|
"fleet",
|
|
36
|
+
"link",
|
|
36
37
|
]);
|
|
37
38
|
export type CapabilityKind = z.infer<typeof CapabilityKindSchema>;
|
|
38
39
|
export const CapabilityStateSchema = z.enum(["active", "pending", "error", "inactive"]);
|
|
@@ -372,6 +373,16 @@ export const FleetConfigSchema = z.object({
|
|
|
372
373
|
token: z.string().min(1).describe("A provisioning token from Settings ▸ Tokens. Stored, never echoed back."),
|
|
373
374
|
});
|
|
374
375
|
export type FleetConfig = z.infer<typeof FleetConfigSchema>;
|
|
376
|
+
// Another of the owner's sandboxes this one may hand tasks to (schemas/links.ts). The token is an inbound link that
|
|
377
|
+
// sandbox's owner minted, held by this daemon and never put in the agent's env, like the fleet's: the agent gets the
|
|
378
|
+
// `delegate` tool, not the credential. What a task may do over there is that sandbox's terms, never this card's.
|
|
379
|
+
export const LinkConfigSchema = z.object({
|
|
380
|
+
url: z.url().describe("The other sandbox's own address, the one its editor reaches it at."),
|
|
381
|
+
token: z.string().min(1).describe("The link token that sandbox minted for this one. Stored, never echoed back."),
|
|
382
|
+
name: z.string().max(100).optional().describe("What the other sandbox is called, as its owner named it."),
|
|
383
|
+
sandboxId: z.string().max(100).optional().describe("The other sandbox's id on the owner's account."),
|
|
384
|
+
});
|
|
385
|
+
export type LinkConfig = z.infer<typeof LinkConfigSchema>;
|
|
375
386
|
export type McpConfig = z.infer<typeof McpConfigSchema>;
|
|
376
387
|
export type CliConfig = z.infer<typeof CliConfigSchema>;
|
|
377
388
|
export type PluginConfig = z.infer<typeof PluginConfigSchema>;
|
|
@@ -426,6 +437,8 @@ export const CapabilitySchema = z.discriminatedUnion("kind", [
|
|
|
426
437
|
z.object({ id: entryId, kind: z.literal("wallet"), config: WalletConfigSchema }),
|
|
427
438
|
// The owner's account, one per sandbox: the box holding this may provision sandboxes on it.
|
|
428
439
|
z.object({ id: entryId, kind: z.literal("fleet"), config: FleetConfigSchema }),
|
|
440
|
+
// Another of the owner's sandboxes, one card per sandbox: the id is the agent's name for it (`delegate`'s `to`).
|
|
441
|
+
z.object({ id: entryId, kind: z.literal("link"), config: LinkConfigSchema }),
|
|
429
442
|
]);
|
|
430
443
|
export type Capability = z.infer<typeof CapabilitySchema>;
|
|
431
444
|
// A credential the owner must type elsewhere (a WhatsApp pairing code); rendered large and copyable, not buried in
|
|
@@ -505,6 +518,18 @@ export const CapabilitySecretInputSchema = z.object({
|
|
|
505
518
|
id: z.string().describe("Which connection."),
|
|
506
519
|
value: z.string().min(1).describe("The new credential. Its other settings are left alone."),
|
|
507
520
|
});
|
|
521
|
+
// POST /capabilities/{id}/oauth-token: a delegated sign-in's refresh token, stored on the card that declares
|
|
522
|
+
// `refreshToken` in its manifest. Write-only: the caller proves it holds the card's current token (empty before the
|
|
523
|
+
// first sign-in) and names the settings it signed in against, so a card edited meanwhile, or another card, is refused.
|
|
524
|
+
export const CapabilityRefreshTokenInputSchema = z.object({
|
|
525
|
+
id: z.string().describe("Which connection."),
|
|
526
|
+
binding: z
|
|
527
|
+
.record(z.string(), z.string())
|
|
528
|
+
.describe("The card's settings the sign-in was made against, by field key: those its manifest's `refreshToken.boundTo` names."),
|
|
529
|
+
previous: z.string().max(32768).describe("The token the caller holds for this card, empty before its first sign-in."),
|
|
530
|
+
refreshToken: z.string().min(1).max(32768).describe("The new refresh token. Never echoed back."),
|
|
531
|
+
login: z.boolean().optional().describe("A fresh interactive sign-in rather than a rotation: restarts the card's listener."),
|
|
532
|
+
});
|
|
508
533
|
// POST /capabilities/{id}/rename: a capability's id is the agent's handle for it (skill file, tool prefix, env suffix,
|
|
509
534
|
// ssh alias), so renaming is a migration, not a label edit. Which kinds may be renamed is the handler's own answer, not
|
|
510
535
|
// this schema's.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// data protection page, never through a changed default: most sandboxes belong to developers and individuals, and a
|
|
6
6
|
// stricter default would take away something they rely on.
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import type { AdmissionRule } from "./agent.js";
|
|
8
9
|
|
|
9
10
|
export const RETENTION_DAYS_MAX = 3650;
|
|
10
11
|
const retentionDays = () => z.number().int().min(0).max(RETENTION_DAYS_MAX).default(0);
|
|
@@ -71,6 +72,52 @@ export const AuditSettingsSchema = z.object({
|
|
|
71
72
|
});
|
|
72
73
|
export type AuditSettings = z.infer<typeof AuditSettingsSchema>;
|
|
73
74
|
|
|
75
|
+
// Every outward action the agent can take that the action rules (SandboxSettings.actionRules) gate, by the key its gate
|
|
76
|
+
// consults: the outbound sniffer's (the daemon's activity/outbound.ts: discord, slack, telegram, whatsapp) and the
|
|
77
|
+
// subagent gate's (guard/actions.ts childSpawn). Writes only: a rule on `messages.read` would hold a bot reading its own
|
|
78
|
+
// channel. Email sending is the key the IMAP extension's sender consults (_extensions/imap/src/send.ts); the three
|
|
79
|
+
// `microsoft365.*` keys are what `m365` consults before sending, changing a calendar or uploading
|
|
80
|
+
// (_extensions/microsoft-365/src/actions.ts); the `ezdrp.*` and `edoreczenia.*` keys are the office acts the e-urzad
|
|
81
|
+
// extension's guard consults (_extensions/e-urzad/src/core/guard.ts): registering mail and adding files in EZD RP, sending
|
|
82
|
+
// an e-Doręczenia delivery, and opening one, the one read here with legal effect (it can make the delivery received).
|
|
83
|
+
// One list for the public-office preset, which holds every one of them, and the editor's Guardrails page, which sets them.
|
|
84
|
+
export const OUTWARD_ACTION_KEYS = [
|
|
85
|
+
"discord.message.send",
|
|
86
|
+
"discord.reaction.add",
|
|
87
|
+
"slack.message.send",
|
|
88
|
+
"slack.message.edit",
|
|
89
|
+
"slack.reaction.add",
|
|
90
|
+
"slack.reaction.remove",
|
|
91
|
+
"telegram.message.send",
|
|
92
|
+
"telegram.message.edit",
|
|
93
|
+
"telegram.reaction.add",
|
|
94
|
+
"whatsapp.message.send",
|
|
95
|
+
"imap.message.send",
|
|
96
|
+
"microsoft365.mail.send",
|
|
97
|
+
"microsoft365.calendar.write",
|
|
98
|
+
"microsoft365.files.upload",
|
|
99
|
+
"ezdrp.document.register-incoming",
|
|
100
|
+
"ezdrp.document.register-outgoing",
|
|
101
|
+
"ezdrp.case.attach-draft",
|
|
102
|
+
"edoreczenia.delivery.send",
|
|
103
|
+
"edoreczenia.delivery.open",
|
|
104
|
+
"agents.spawn",
|
|
105
|
+
] as const;
|
|
106
|
+
export type OutwardActionKey = (typeof OUTWARD_ACTION_KEYS)[number];
|
|
107
|
+
|
|
108
|
+
// The service an action key belongs to: its first segment (`discord`, `imap`, `agents`).
|
|
109
|
+
export const outwardActionService = (key: string): string => key.split(".")[0] ?? key;
|
|
110
|
+
|
|
111
|
+
// The rule an action's gate applies today: the exact key, else the service's wildcard (guard/actions.ts outboundSend).
|
|
112
|
+
// `agents.spawn` has no wildcard of its own. Undefined is no rule, which every gate reads as allowed, except the
|
|
113
|
+
// subagent gate, which holds a spawn after outside content.
|
|
114
|
+
export const effectiveActionRule = (rules: Readonly<Record<string, AdmissionRule>>, key: string): AdmissionRule | undefined => {
|
|
115
|
+
if (rules[key] !== undefined) {
|
|
116
|
+
return rules[key];
|
|
117
|
+
}
|
|
118
|
+
return key === "agents.spawn" ? undefined : rules[`${outwardActionService(key)}.*`];
|
|
119
|
+
};
|
|
120
|
+
|
|
74
121
|
export const AI_NOTICE_TEXT_MAX = 4000;
|
|
75
122
|
|
|
76
123
|
// The AI Act's literacy duty (Art. 4) falls on whoever puts staff in front of an AI system. This is the card that tells
|
package/src/schemas/devices.ts
CHANGED
|
@@ -619,7 +619,14 @@ export const DeviceSchema = z.object({
|
|
|
619
619
|
gap: DeviceGapSchema.optional(),
|
|
620
620
|
});
|
|
621
621
|
export type Device = z.infer<typeof DeviceSchema>;
|
|
622
|
-
|
|
622
|
+
// (2026-10-15) A device enrollment no capability card holds: a key into this sandbox for which no board draws a machine,
|
|
623
|
+
// since a machine is drawn from its card. Listed so the owner can withdraw it (`DELETE /system/hosts/{id}`, `id` being the
|
|
624
|
+
// connection key); never withdrawn by the daemon itself, since the manifest the cards live in is a workspace file a land
|
|
625
|
+
// can rewrite for a moment, and withdrawing on that would unpair real devices.
|
|
626
|
+
export const CardlessHostSchema = z.object({ id: z.string(), card: z.string(), environment: z.string() });
|
|
627
|
+
export type CardlessHost = z.infer<typeof CardlessHostSchema>;
|
|
628
|
+
// `cardlessHosts` is absent when there are none, and from a daemon older than the field.
|
|
629
|
+
export const DevicesListSchema = z.object({ devices: z.array(DeviceSchema), cardlessHosts: z.array(CardlessHostSchema).optional() });
|
|
623
630
|
export type DevicesList = z.infer<typeof DevicesListSchema>;
|
|
624
631
|
|
|
625
632
|
// The physical computer a device is an environment of. Windows and every WSL distro on it are one machine with one
|
package/src/schemas/git/git.ts
CHANGED
|
@@ -276,7 +276,7 @@ export type ScratchPath = z.infer<typeof ScratchPathSchema>;
|
|
|
276
276
|
export const isScratch = (path: string, scratch: readonly Pick<ScratchPath, "path">[]): boolean =>
|
|
277
277
|
scratch.some((entry) => (entry.path.endsWith("/") ? path.startsWith(entry.path) : path === entry.path));
|
|
278
278
|
// Apart from scratch, which is left out for its shape: these are left out because the repository says so, and the
|
|
279
|
-
// owner usually wants them most (a render in `out/`, a
|
|
279
|
+
// owner usually wants them most (a render in `out/`, a report as a PDF). Build output an ignore rule hides is not one.
|
|
280
280
|
export const IgnoredOutputKindSchema = z
|
|
281
281
|
.enum(["new", "changed"])
|
|
282
282
|
.describe("Whether your tree has nothing at this path yet, or holds a different file there that copying would replace.");
|
|
@@ -464,6 +464,19 @@ export const AgentRepoChangesSchema = z.object({
|
|
|
464
464
|
export type AgentRepoChanges = z.infer<typeof AgentRepoChangesSchema>;
|
|
465
465
|
// The review, plus why the last land refused: a conflict is found when the turn ends but resolved later on this
|
|
466
466
|
// surface, so it rides here rather than only in the land response, and refreshes on every land.
|
|
467
|
+
// A workspace repo this conversation's copy does not carry, where the copy nonetheless holds files at its path: what a
|
|
468
|
+
// turn wrote into a folder that was no checkout of its own, which therefore reached no branch.
|
|
469
|
+
export const OutsideCheckoutSchema = z.object({
|
|
470
|
+
repo: z.string().describe("Which repository."),
|
|
471
|
+
reason: z
|
|
472
|
+
.enum(["unborn", "unreadable", "not-carried"])
|
|
473
|
+
.describe(
|
|
474
|
+
"Why the copy does not carry it: `unborn` has no commit yet to start from, and is taken in on the conversation's next turn once it has one; `unreadable` is a repository git cannot read; `not-carried` is one the conversation was set up without, such as a persona that names other repositories.",
|
|
475
|
+
),
|
|
476
|
+
files: z.number().describe("How many files the copy holds at its path. A floor past a thousand, which is where the count stops."),
|
|
477
|
+
});
|
|
478
|
+
export type OutsideCheckout = z.infer<typeof OutsideCheckoutSchema>;
|
|
479
|
+
|
|
467
480
|
export const AgentChangesSchema = z.object({
|
|
468
481
|
repos: z.array(AgentRepoChangesSchema).describe("One entry per repository the conversation touched."),
|
|
469
482
|
// Disambiguates two opposite reasons for an empty list: nothing written, versus everything already committed.
|
|
@@ -533,7 +546,14 @@ export const AgentChangesSchema = z.object({
|
|
|
533
546
|
)
|
|
534
547
|
.optional()
|
|
535
548
|
.describe(
|
|
536
|
-
"Files the conversation wrote in its copy
|
|
549
|
+
"Files made for a person that the conversation wrote in its copy and the repository's ignore rules exclude, such as a render in `out/` or a report as a PDF, which your tree lacks or holds differently. Only media, documents, tables, archives and installers are listed: what a build regenerates (code, maps, a Cargo `target/` or anything else tagged CACHEDIR.TAG), dependencies, hidden files, logs and dumps never are. No merge carries them and they never enter git; copying one puts it at the same path in your tree. Absent when there are none.",
|
|
550
|
+
),
|
|
551
|
+
// Read off the live copy like scratch: a retired copy has no holes to read.
|
|
552
|
+
outside: z
|
|
553
|
+
.array(OutsideCheckoutSchema)
|
|
554
|
+
.optional()
|
|
555
|
+
.describe(
|
|
556
|
+
"Repositories of your workspace this conversation's copy does not carry, where it nonetheless wrote files at their path. Nothing written there is on its branch, so no merge brings it; an `unborn` one is taken into the copy, with those files, on the conversation's next turn after its first commit. Absent when there are none.",
|
|
537
557
|
),
|
|
538
558
|
});
|
|
539
559
|
export type AgentChanges = z.infer<typeof AgentChangesSchema>;
|
package/src/schemas/hosts.ts
CHANGED
|
@@ -110,7 +110,19 @@ export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
|
|
|
110
110
|
// run under the agent's supervision (device-artifacts.ts); an agent without it is told to update rather than sent one.
|
|
111
111
|
// - `project-delivery`: the `deliverProject` procedure, landed work written into a folder attached to this computer's
|
|
112
112
|
// sandbox (project-delivery.ts); an agent without it is never sent one, and the land waits for Bring back.
|
|
113
|
-
|
|
113
|
+
// - `keyring`: the keyring* procedures, intentic-keyring on this computer reached through the machine (keyring.ts);
|
|
114
|
+
// advertised only while a keyring is installed, so a sandbox never offers a computer without one as a place to keep or
|
|
115
|
+
// type a secret.
|
|
116
|
+
export const DeviceFeatureSchema = z.enum([
|
|
117
|
+
"reshape-later",
|
|
118
|
+
"set-shape",
|
|
119
|
+
"rollback-to",
|
|
120
|
+
"loopback-catch",
|
|
121
|
+
"background-prepare",
|
|
122
|
+
"project-delivery",
|
|
123
|
+
"programs",
|
|
124
|
+
"keyring",
|
|
125
|
+
]);
|
|
114
126
|
export type DeviceFeature = z.infer<typeof DeviceFeatureSchema>;
|
|
115
127
|
export const DEVICE_FEATURE_RESHAPE_LATER: DeviceFeature = "reshape-later";
|
|
116
128
|
export const DEVICE_FEATURE_SET_SHAPE: DeviceFeature = "set-shape";
|
|
@@ -119,6 +131,7 @@ export const DEVICE_FEATURE_LOOPBACK_CATCH: DeviceFeature = "loopback-catch";
|
|
|
119
131
|
export const DEVICE_FEATURE_BACKGROUND_PREPARE: DeviceFeature = "background-prepare";
|
|
120
132
|
export const DEVICE_FEATURE_PROJECT_DELIVERY: DeviceFeature = "project-delivery";
|
|
121
133
|
export const DEVICE_FEATURE_PROGRAMS: DeviceFeature = "programs";
|
|
134
|
+
export const DEVICE_FEATURE_KEYRING: DeviceFeature = "keyring";
|
|
122
135
|
// The features an agent advertised that this build knows; an unknown one is a newer agent's and means nothing here.
|
|
123
136
|
export const deviceFeatures = (facts: Pick<DeviceFacts, "features"> | undefined): DeviceFeature[] =>
|
|
124
137
|
(facts?.features ?? []).flatMap((feature) => {
|