@intentic/sandbox-contract 1.332.0 → 1.333.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 +8 -1
- package/dist/contracts/agent.contract.d.ts +1 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.d.ts +1 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +29 -1
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.js +2 -2
- package/dist/contracts/needs.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +2 -2
- package/dist/contracts/runner.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +34 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/index.d.ts +114 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/models/model-roles.d.ts +8 -0
- package/dist/models/model-roles.d.ts.map +1 -1
- package/dist/models/model-roles.js +8 -0
- package/dist/models/model-roles.js.map +1 -1
- package/dist/policy/control-scopes.d.ts +47 -0
- package/dist/policy/control-scopes.d.ts.map +1 -1
- package/dist/policy/control-scopes.js +16 -0
- package/dist/policy/control-scopes.js.map +1 -1
- package/dist/policy/device-programs.d.ts +28 -0
- package/dist/policy/device-programs.d.ts.map +1 -0
- package/dist/policy/device-programs.js +79 -0
- package/dist/policy/device-programs.js.map +1 -0
- package/dist/policy/identity-assertion.d.ts +27 -0
- package/dist/policy/identity-assertion.d.ts.map +1 -0
- package/dist/policy/identity-assertion.js +77 -0
- package/dist/policy/identity-assertion.js.map +1 -0
- package/dist/policy/sandbox-power.d.ts +34 -0
- package/dist/policy/sandbox-power.d.ts.map +1 -0
- package/dist/policy/sandbox-power.js +146 -0
- package/dist/policy/sandbox-power.js.map +1 -0
- package/dist/protocol/doors/runner-protocol.d.ts +36 -0
- package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
- package/dist/protocol/doors/runner-protocol.js +2 -0
- package/dist/protocol/doors/runner-protocol.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +839 -1
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.js +60 -2
- package/dist/protocol/raw/raw-json-routes.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +9 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +3 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +3 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/automations.d.ts +1 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js +8 -2
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/device-swaps.d.ts +19 -0
- package/dist/schemas/device-swaps.d.ts.map +1 -0
- package/dist/schemas/device-swaps.js +11 -0
- package/dist/schemas/device-swaps.js.map +1 -0
- package/dist/schemas/environment.d.ts +7 -0
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js +2 -0
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/members.d.ts +4 -0
- package/dist/schemas/members.d.ts.map +1 -1
- package/dist/schemas/members.js +5 -0
- package/dist/schemas/members.js.map +1 -1
- package/dist/schemas/needs.d.ts +78 -1
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +36 -1
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/restart-intent.d.ts +47 -0
- package/dist/schemas/restart-intent.d.ts.map +1 -0
- package/dist/schemas/restart-intent.js +19 -0
- package/dist/schemas/restart-intent.js.map +1 -0
- package/dist/schemas/settings.d.ts +34 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +2 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/short-codes.d.ts +16 -0
- package/dist/schemas/short-codes.d.ts.map +1 -1
- package/dist/schemas/short-codes.js +2 -0
- package/dist/schemas/short-codes.js.map +1 -1
- package/dist/schemas/speech.d.ts +4 -0
- package/dist/schemas/speech.d.ts.map +1 -1
- package/dist/schemas/speech.js +1 -0
- package/dist/schemas/speech.js.map +1 -1
- package/dist/schemas/system.d.ts +46 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +18 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/voice.d.ts +171 -0
- package/dist/schemas/voice.d.ts.map +1 -0
- package/dist/schemas/voice.js +99 -0
- package/dist/schemas/voice.js.map +1 -0
- package/dist/schemas/workspace/workspace-upload.d.ts +14 -0
- package/dist/schemas/workspace/workspace-upload.d.ts.map +1 -0
- package/dist/schemas/workspace/workspace-upload.js +6 -0
- package/dist/schemas/workspace/workspace-upload.js.map +1 -0
- package/dist/state/arrival.d.ts +3 -0
- package/dist/state/arrival.d.ts.map +1 -1
- package/dist/state/arrival.js +1 -0
- package/dist/state/arrival.js.map +1 -1
- package/dist/state/definition.d.ts +52 -4
- 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 +2 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +2 -2
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +2 -2
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/update-probation.d.ts +50 -0
- package/dist/state/update-probation.d.ts.map +1 -0
- package/dist/state/update-probation.js +81 -0
- package/dist/state/update-probation.js.map +1 -0
- package/dist/state/workspace-state.d.ts +1 -1
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +1 -1
- package/dist/state/workspace-state.js.map +1 -1
- package/package.json +16 -5
- package/src/contracts/needs.contract.ts +2 -2
- package/src/contracts/runner.contract.ts +10 -2
- package/src/ids/tunnel-ids.test.ts +30 -0
- package/src/index.ts +7 -0
- package/src/models/model-roles.ts +10 -0
- package/src/policy/control-scopes.ts +27 -0
- package/src/policy/device-programs.ts +137 -0
- package/src/policy/identity-assertion.ts +130 -0
- package/src/policy/sandbox-power.ts +247 -0
- package/src/policy/tests/device-programs.test.ts +107 -0
- package/src/policy/tests/identity-assertion.test.ts +79 -0
- package/src/policy/tests/sandbox-power.test.ts +181 -0
- package/src/protocol/doors/runner-protocol.ts +7 -0
- package/src/protocol/raw/raw-json-routes.test.ts +87 -0
- package/src/protocol/raw/raw-json-routes.ts +125 -6
- package/src/protocol/raw/raw-routes.ts +6 -0
- package/src/schemas/automations.ts +10 -3
- package/src/schemas/device-swaps.test.ts +39 -0
- package/src/schemas/device-swaps.ts +26 -0
- package/src/schemas/environment.ts +4 -0
- package/src/schemas/members.ts +8 -0
- package/src/schemas/needs.ts +51 -1
- package/src/schemas/restart-intent.test.ts +45 -0
- package/src/schemas/restart-intent.ts +47 -0
- package/src/schemas/settings.ts +6 -0
- package/src/schemas/short-codes.ts +11 -0
- package/src/schemas/speech.ts +4 -0
- package/src/schemas/system.ts +43 -4
- package/src/schemas/voice.ts +161 -0
- package/src/schemas/workspace/workspace-upload.ts +11 -0
- package/src/state/arrival.ts +3 -0
- package/src/state/history-state.ts +5 -0
- package/src/state/runtime-state.test.ts +9 -0
- package/src/state/runtime-state.ts +7 -5
- package/src/state/update-probation.test.ts +45 -0
- package/src/state/update-probation.ts +169 -0
- package/src/state/workspace-state.ts +4 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
+
import { ControlTokenMintedSchema, ControlTokenMintSchema, ControlTokensListSchema } from "../../policy/control-scopes.js";
|
|
2
3
|
import { EngineChannelInputSchema, EngineRevertInputSchema, EnginesViewSchema, EngineUpdateInputSchema, EngineWriteResultSchema } from "../../schemas/engines.js";
|
|
3
4
|
import {
|
|
5
|
+
BundleExportsSchema,
|
|
6
|
+
BundleStartedSchema,
|
|
7
|
+
BundleTicketSchema,
|
|
4
8
|
EnvironmentApproveSchema,
|
|
5
9
|
EnvironmentContentsSchema,
|
|
6
10
|
EnvironmentRebuildWhenIdleSchema,
|
|
@@ -8,23 +12,62 @@ import {
|
|
|
8
12
|
EnvironmentRuntimeDecisionSchema,
|
|
9
13
|
EnvironmentSchema,
|
|
10
14
|
} from "../../schemas/environment.js";
|
|
15
|
+
import { SyncStatusSchema } from "../../schemas/devices.js";
|
|
16
|
+
import { HostsListSchema } from "../../schemas/hosts.js";
|
|
17
|
+
import { VoicesViewSchema } from "../../schemas/voice.js";
|
|
11
18
|
import { MemberEmailSchema, MemberGrantSchema, MembersListSchema, MembersRosterSchema } from "../../schemas/members.js";
|
|
19
|
+
import { PhonesListSchema, PhoneWakeRegistrationSchema } from "../../schemas/phone.js";
|
|
12
20
|
import { OkSchema } from "../../schemas/shared.js";
|
|
13
|
-
import { EnrollCodeAnswerSchema, EnrollCodeRequestSchema } from "../../schemas/short-codes.js";
|
|
21
|
+
import { EnrollCodeAnswerSchema, EnrollCodeRequestSchema, PeerPairingAnswerSchema, SyncPairAnswerSchema, SyncPairRequestSchema } from "../../schemas/short-codes.js";
|
|
22
|
+
import { SpeechStatusSchema, SpeechTranscriptSchema } from "../../schemas/speech.js";
|
|
23
|
+
import {
|
|
24
|
+
AuthenticationOptionsSchema,
|
|
25
|
+
DaemonSessionSchema,
|
|
26
|
+
PasskeyAssertRequestSchema,
|
|
27
|
+
PasskeyPolicyAnswerSchema,
|
|
28
|
+
PasskeyPolicySchema,
|
|
29
|
+
PasskeyRecoverRequestSchema,
|
|
30
|
+
PasskeyRegisteredSchema,
|
|
31
|
+
PasskeyRegisterRequestSchema,
|
|
32
|
+
PasskeysListSchema,
|
|
33
|
+
RecoveryCodesSchema,
|
|
34
|
+
RegistrationOptionsSchema,
|
|
35
|
+
SessionRecoveredSchema,
|
|
36
|
+
} from "../../schemas/system.js";
|
|
37
|
+
import { WebExtsListSchema } from "../../schemas/webext.js";
|
|
38
|
+
import { UploadManifestSchema, UploadSkipSchema } from "../../schemas/workspace/workspace-upload.js";
|
|
39
|
+
import {
|
|
40
|
+
ArrivalAbandonedSchema,
|
|
41
|
+
ArrivalApplySchema,
|
|
42
|
+
ArrivalHostsSchema,
|
|
43
|
+
ArrivalPlanSchema,
|
|
44
|
+
ArrivalReportSchema,
|
|
45
|
+
ArrivalScanSchema,
|
|
46
|
+
ProviderKeysAppliedSchema,
|
|
47
|
+
ProviderKeysApplySchema,
|
|
48
|
+
ProviderKeysSchema,
|
|
49
|
+
} from "../../state/arrival.js";
|
|
14
50
|
import { DefinitionDiffSchema, DefinitionExportSchema, WorkspacePublishResultSchema, WorkspacePublishSchema, WorkspaceRemoteSchema } from "../../state/definition.js";
|
|
51
|
+
import { RunnerDefinitionAppliedSchema, RunnersListSchema } from "../doors/runner-protocol.js";
|
|
15
52
|
import type { RawRouteKey } from "./raw-routes.js";
|
|
16
53
|
|
|
17
54
|
// What a raw route that answers plain JSON takes and answers, under its RAW_ROUTES key. `input` is the JSON body a write
|
|
18
|
-
// sends; `text` marks one whose body is a document rather than JSON (a sandbox.toml to diff)
|
|
19
|
-
//
|
|
20
|
-
// A
|
|
55
|
+
// sends; `text` marks one whose body is a document rather than JSON (a sandbox.toml to diff), and `bytes` one whose body
|
|
56
|
+
// is the thing itself (a file's bytes, a WAV, a tar), which the caller hands over as a Blob, an ArrayBuffer or a string.
|
|
57
|
+
// A caller reaches these by key through a typed helper that parses the answer with `output`, so a hand-written route is
|
|
58
|
+
// as typed as a procedure. What a route takes in its query string (`?id=`, `?path=`) and in a header is the helper's
|
|
59
|
+
// `query` and `headers`, since the daemon reads them by name and there is nothing to parse. A refusal is the daemon's
|
|
60
|
+
// `{ "error": … }` with a 4xx status on every one of them, outside these schemas.
|
|
21
61
|
export interface RawJsonRoute {
|
|
22
62
|
readonly input?: z.ZodType;
|
|
23
63
|
readonly text?: true;
|
|
64
|
+
readonly bytes?: true;
|
|
24
65
|
readonly output: z.ZodType;
|
|
25
66
|
}
|
|
26
67
|
|
|
27
68
|
export const RAW_JSON_ROUTES = {
|
|
69
|
+
// The voices a sandbox speaks with, for the Voice settings (schemas/voice.ts).
|
|
70
|
+
"GET /speech/voices": { output: VoicesViewSchema },
|
|
28
71
|
// A server-connect code for the owner's command (short-codes.ts).
|
|
29
72
|
"POST /system/enroll/code": { input: EnrollCodeRequestSchema, output: EnrollCodeAnswerSchema },
|
|
30
73
|
// The roster is ownership's; every write answers the roster after it.
|
|
@@ -49,6 +92,70 @@ export const RAW_JSON_ROUTES = {
|
|
|
49
92
|
"POST /definition/diff": { text: true, output: DefinitionDiffSchema },
|
|
50
93
|
"GET /definition/workspace": { output: WorkspaceRemoteSchema },
|
|
51
94
|
"POST /definition/workspace/publish": { input: WorkspacePublishSchema, output: WorkspacePublishResultSchema },
|
|
95
|
+
// Sandboxes moving in: a plan read off an archive (`bytes`) or a connected device, applied by its token, and the
|
|
96
|
+
// model keys the owner's devices hold.
|
|
97
|
+
"GET /arrivals/hosts": { output: ArrivalHostsSchema },
|
|
98
|
+
"POST /arrivals/scan": { input: ArrivalScanSchema, output: ArrivalPlanSchema },
|
|
99
|
+
"POST /arrivals/plan": { bytes: true, output: ArrivalPlanSchema },
|
|
100
|
+
"POST /arrivals/apply": { input: ArrivalApplySchema, output: ArrivalReportSchema },
|
|
101
|
+
"GET /arrivals/keys": { output: ProviderKeysSchema },
|
|
102
|
+
"POST /arrivals/keys/apply": { input: ProviderKeysApplySchema, output: ProviderKeysAppliedSchema },
|
|
103
|
+
"DELETE /arrivals": { output: ArrivalAbandonedSchema },
|
|
104
|
+
// Exports: `?secrets=1` on the start, `?name=` on the delete and the ticket.
|
|
105
|
+
"GET /bundles": { output: BundleExportsSchema },
|
|
106
|
+
"POST /bundles": { output: BundleStartedSchema },
|
|
107
|
+
"DELETE /bundles": { output: OkSchema },
|
|
108
|
+
"POST /bundles/ticket": { output: BundleTicketSchema },
|
|
109
|
+
// Paired machines: each door's roster, the pairing minted for one id (`?id=`), and dropping one's enrollment. The
|
|
110
|
+
// devices' answer is `hosts`, the browsers' `browsers`, the phones' `phones`, the runners' `runners`.
|
|
111
|
+
"GET /system/hosts": { output: HostsListSchema },
|
|
112
|
+
"POST /system/hosts/pair": { output: PeerPairingAnswerSchema },
|
|
113
|
+
"DELETE /system/hosts/{id}": { output: OkSchema },
|
|
114
|
+
"GET /system/webext": { output: WebExtsListSchema },
|
|
115
|
+
"POST /system/webext/pair": { output: PeerPairingAnswerSchema },
|
|
116
|
+
"DELETE /system/webext/{id}": { output: OkSchema },
|
|
117
|
+
"GET /system/phones": { output: PhonesListSchema },
|
|
118
|
+
"POST /system/phones/pair": { output: PeerPairingAnswerSchema },
|
|
119
|
+
"DELETE /system/phones/{id}": { output: OkSchema },
|
|
120
|
+
"POST /system/phones/{id}/wake": { input: PhoneWakeRegistrationSchema, output: OkSchema },
|
|
121
|
+
"GET /system/runners": { output: RunnersListSchema },
|
|
122
|
+
"DELETE /system/runners/{id}": { output: OkSchema },
|
|
123
|
+
"POST /system/runners/{id}/definition/sync": { output: RunnerDefinitionAppliedSchema },
|
|
124
|
+
// Desktop sync: the sandbox-level state, the pairing (`?mode=mirror` asks for the narrower one), one machine's key.
|
|
125
|
+
"GET /system/sync": { output: SyncStatusSchema },
|
|
126
|
+
"POST /system/sync/pair": { input: SyncPairRequestSchema, output: SyncPairAnswerSchema },
|
|
127
|
+
"DELETE /system/authorized-key/{machine}": { output: OkSchema },
|
|
128
|
+
// Every browser session revoked, the owner's own included.
|
|
129
|
+
"POST /system/sessions/revoke": { output: OkSchema },
|
|
130
|
+
"GET /system/control/tokens": { output: ControlTokensListSchema },
|
|
131
|
+
"POST /system/control/tokens": { input: ControlTokenMintSchema, output: ControlTokenMintedSchema },
|
|
132
|
+
"DELETE /system/control/tokens/{id}": { output: OkSchema },
|
|
133
|
+
// One's own passkeys, the require rule and its recovery codes. The two registration calls carry the WebAuthn ceremony's
|
|
134
|
+
// options out and its response back, the shapes the browser's own parser reads and writes.
|
|
135
|
+
"GET /system/passkeys": { output: PasskeysListSchema },
|
|
136
|
+
"POST /system/passkeys/register/options": { output: RegistrationOptionsSchema },
|
|
137
|
+
"POST /system/passkeys/register": { input: PasskeyRegisterRequestSchema, output: PasskeyRegisteredSchema },
|
|
138
|
+
"POST /system/passkeys/policy": { input: PasskeyPolicySchema, output: PasskeyPolicyAnswerSchema },
|
|
139
|
+
"POST /system/passkeys/recovery": { output: RecoveryCodesSchema },
|
|
140
|
+
"DELETE /system/passkeys/{id}": { output: OkSchema },
|
|
141
|
+
// The sign-in's own calls, before any session exists: the assertion is how one is minted, and recovery is the owner's
|
|
142
|
+
// way back in with the code a policy handed out. Their credential is the door's (a challenge, a recovery code under the
|
|
143
|
+
// caller's own proof), so the editor sends them outside its session client (client/session/passkeySignIn.ts) and reads
|
|
144
|
+
// their answers with these schemas.
|
|
145
|
+
"POST /system/passkeys/assert/options": { output: AuthenticationOptionsSchema },
|
|
146
|
+
"POST /system/passkeys/assert": { input: PasskeyAssertRequestSchema, output: DaemonSessionSchema },
|
|
147
|
+
"POST /system/session/recover": { input: PasskeyRecoverRequestSchema, output: SessionRecoveredSchema },
|
|
148
|
+
// Dictation over HTTP, for a daemon without the phrase stream (`?lang=`): the model's readiness, arming it, and one
|
|
149
|
+
// phrase as WAV.
|
|
150
|
+
"GET /speech/status": { output: SpeechStatusSchema },
|
|
151
|
+
"POST /speech/prepare": { output: SpeechStatusSchema },
|
|
152
|
+
"POST /speech/transcribe": { bytes: true, output: SpeechTranscriptSchema },
|
|
153
|
+
// A file written into the tree (`?path=`, the body its bytes, `x-intentic-base-hash` refusing a stale save), and the
|
|
154
|
+
// pre-drop check of which dropped files are already there.
|
|
155
|
+
"POST /workspace/upload": { bytes: true, output: OkSchema },
|
|
156
|
+
// A dropped tree as one tar, extracted entry by entry.
|
|
157
|
+
"POST /workspace/upload-archive": { bytes: true, output: OkSchema },
|
|
158
|
+
"POST /workspace/upload-diff": { input: UploadManifestSchema, output: UploadSkipSchema },
|
|
52
159
|
} as const satisfies Partial<Record<RawRouteKey, RawJsonRoute>>;
|
|
53
160
|
|
|
54
161
|
export type RawJsonRouteKey = keyof typeof RAW_JSON_ROUTES;
|
|
@@ -56,9 +163,21 @@ export type RawJsonRouteKey = keyof typeof RAW_JSON_ROUTES;
|
|
|
56
163
|
// What a JSON raw route answers, parsed.
|
|
57
164
|
export type RawJsonOutput<Key extends RawJsonRouteKey> = z.output<(typeof RAW_JSON_ROUTES)[Key]["output"]>;
|
|
58
165
|
|
|
59
|
-
//
|
|
166
|
+
// The routes that take their body as bytes: a file, a WAV, a tar. Some of these also go up in parts with progress.
|
|
167
|
+
export type RawBytesRouteKey = {
|
|
168
|
+
[Key in RawJsonRouteKey]: (typeof RAW_JSON_ROUTES)[Key] extends { readonly bytes: true } ? Key : never;
|
|
169
|
+
}[RawJsonRouteKey];
|
|
170
|
+
|
|
171
|
+
// The body a `bytes` route takes: a Blob or typed array (a file, a tar), an ArrayBuffer (an encoded WAV) or a string
|
|
172
|
+
// (a saved file's text).
|
|
173
|
+
export type RawBytes = Blob | ArrayBuffer | ArrayBufferView | string;
|
|
174
|
+
|
|
175
|
+
// What a JSON raw route's body is: its input's shape, a string for a document body, the bytes for a `bytes` route,
|
|
176
|
+
// undefined for one that takes none.
|
|
60
177
|
export type RawJsonInput<Key extends RawJsonRouteKey> = (typeof RAW_JSON_ROUTES)[Key] extends { readonly input: infer Input extends z.ZodType }
|
|
61
178
|
? z.input<Input>
|
|
62
179
|
: (typeof RAW_JSON_ROUTES)[Key] extends { readonly text: true }
|
|
63
180
|
? string
|
|
64
|
-
:
|
|
181
|
+
: (typeof RAW_JSON_ROUTES)[Key] extends { readonly bytes: true }
|
|
182
|
+
? RawBytes
|
|
183
|
+
: undefined;
|
|
@@ -21,6 +21,12 @@ export const RAW_ROUTES = {
|
|
|
21
21
|
// The live phrase stream (schemas/speech.ts): a WebSocket, so the query ticket, floored at the collaborator in the
|
|
22
22
|
// handler like the transcribe it streams.
|
|
23
23
|
"GET /speech/stream": { auth: "door" },
|
|
24
|
+
// Speaking (schemas/voice.ts): the voices to choose from is a read any tier makes; speech costs this box's CPU or
|
|
25
|
+
// the owner's provider credits, so saying anything is the collaborator's, like dictating. The WebSocket floors its
|
|
26
|
+
// query ticket in the handler; the HTTP door is Discord's calls and anything without a socket.
|
|
27
|
+
"GET /speech/voices": { guest: true },
|
|
28
|
+
"POST /speech/say": { floor: "collaborator" },
|
|
29
|
+
"GET /speech/say": { auth: "door" },
|
|
24
30
|
// The bytes behind the workspace reads; each applies the caller's fence itself.
|
|
25
31
|
"GET /workspace/raw": { guest: true, lane: "bulk" },
|
|
26
32
|
// No door, unlike media: the page fetches a thumbnail itself, so the request carries the header.
|
|
@@ -435,7 +435,7 @@ export const AutomationSchema = z.object({
|
|
|
435
435
|
enabled: z.boolean().describe("Whether it fires at all."),
|
|
436
436
|
});
|
|
437
437
|
export type Automation = z.infer<typeof AutomationSchema>;
|
|
438
|
-
// A wake held for owner approval (
|
|
438
|
+
// A wake held for owner approval (automation-holds/<id>.json on the history volume); snapshots the trigger payload so an approved
|
|
439
439
|
// run replays exactly what fired, even across a daemon restart.
|
|
440
440
|
export const AutomationApprovalSchema = z.object({
|
|
441
441
|
id: entryId.describe("This waiting item's own id, which approving and rejecting take."),
|
|
@@ -476,7 +476,7 @@ export const AutomationApprovalSchema = z.object({
|
|
|
476
476
|
.number()
|
|
477
477
|
.optional()
|
|
478
478
|
.describe(
|
|
479
|
-
"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person.",
|
|
479
|
+
"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person, and for one the sandbox did not itself queue, which only a person releases.",
|
|
480
480
|
),
|
|
481
481
|
});
|
|
482
482
|
export type AutomationApproval = z.infer<typeof AutomationApprovalSchema>;
|
|
@@ -484,7 +484,14 @@ export type AutomationApproval = z.infer<typeof AutomationApprovalSchema>;
|
|
|
484
484
|
// Whether a held wake waits on a person. A countdown hold (`autoRunAt`) releases itself, so counting it would ask the
|
|
485
485
|
// owner about something already about to happen on its own.
|
|
486
486
|
export const awaitsOwner = (wake: Pick<AutomationApproval, "autoRunAt">): boolean => wake.autoRunAt === undefined;
|
|
487
|
-
export const AutomationApprovalsListSchema = z.object({
|
|
487
|
+
export const AutomationApprovalsListSchema = z.object({
|
|
488
|
+
approvals: z.array(AutomationApprovalSchema).describe("Everything waiting for a yes."),
|
|
489
|
+
// Reported, never dropped: a file in the queue the sandbox cannot read would otherwise be a hold that never runs and never says why.
|
|
490
|
+
unreadable: z
|
|
491
|
+
.array(z.string())
|
|
492
|
+
.default([])
|
|
493
|
+
.describe("Files in the queue this sandbox could not read, by name. Each is a held wake that cannot be released until it is fixed or removed."),
|
|
494
|
+
});
|
|
488
495
|
export const AutomationApprovalIdParamSchema = z.object({ id: z.string().describe("Which waiting item.") });
|
|
489
496
|
export const AutomationRunSchema = z.object({
|
|
490
497
|
at: z.number(),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { packageRoot } from "@intentic/constants/node";
|
|
4
|
+
import { DeviceSwapsSchema } from "./device-swaps.js";
|
|
5
|
+
|
|
6
|
+
// The rows `ic sandbox swaps --json` answers, as examples: golden/swap-verdicts.json holds each of ic's records and the
|
|
7
|
+
// row it gives (the Rust test, _sandbox/ic/src/sandbox/swaps.rs, builds every one; the machine agent's fallback is held
|
|
8
|
+
// to the same rows). This is the TS half: the rows ic gives are the rows the contract reads, to the field.
|
|
9
|
+
|
|
10
|
+
interface VerdictCase {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly record: string;
|
|
13
|
+
readonly now: number;
|
|
14
|
+
readonly swap: Record<string, unknown> | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// SAFETY: the golden is this package's own file; the assertions below fail on any field it lacks.
|
|
18
|
+
const GOLDEN = JSON.parse(readFileSync(join(packageRoot(import.meta.url), "golden", "swap-verdicts.json"), "utf8")) as {
|
|
19
|
+
readonly slug: string;
|
|
20
|
+
readonly cases: readonly VerdictCase[];
|
|
21
|
+
};
|
|
22
|
+
const rows: unknown[] = GOLDEN.cases.flatMap((one) => (one.swap === null ? [] : [one.swap]));
|
|
23
|
+
|
|
24
|
+
test("every row ic gives for the golden records is a row the contract reads, field for field", () => {
|
|
25
|
+
expect(rows.length).toBeGreaterThan(8);
|
|
26
|
+
const read: unknown = DeviceSwapsSchema.parse(rows);
|
|
27
|
+
expect(read).toEqual(rows);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("a row from a newer ic keeps the swap going on: an unknown phase and an extra field still read", () => {
|
|
31
|
+
const [row] = DeviceSwapsSchema.parse([{ slug: "work", phase: "staging", at: 5, cutoverInProgress: false, next: "later" }]);
|
|
32
|
+
expect(row).toMatchObject({ slug: "work", phase: "staging", at: 5, cutoverInProgress: false });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a field of the wrong type is a field that is not there, but a row without its verdict is no row", () => {
|
|
36
|
+
const [row] = DeviceSwapsSchema.parse([{ slug: "work", phase: "cutover", at: 5, alive: "soon", cutoverInProgress: true }]);
|
|
37
|
+
expect(row?.alive).toBeUndefined();
|
|
38
|
+
expect(DeviceSwapsSchema.safeParse([{ slug: "work", phase: "cutover", at: 5 }]).success).toBe(false);
|
|
39
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// WHAT `ic sandbox swaps --json` ANSWERS, one row per sandbox whose channel record names a swap in flight or on
|
|
4
|
+
// probation. `ic` is the one reader of its own swap records: `cutoverInProgress` is its verdict (a live ic run's
|
|
5
|
+
// heartbeat, `swap_alive`, is fresh), and every other process asks for it here instead of judging the record again. The
|
|
6
|
+
// command reads ic's home and not Docker, so it answers while Docker is down. golden/swap-verdicts.json holds each
|
|
7
|
+
// record's row, and ic's Rust test and the machine agent's tests both read it.
|
|
8
|
+
|
|
9
|
+
// Read loosely: a `phase` a newer ic adds is still a swap going on, and a field of an unexpected shape is a field that
|
|
10
|
+
// is not there. A row that lacks its identity or its verdict is no row at all (the caller falls back to the records).
|
|
11
|
+
export const DeviceSwapSchema = z.looseObject({
|
|
12
|
+
slug: z.string().min(1),
|
|
13
|
+
// ic's word: `cutover` (the old container is parked, the new one not yet proven) or `probation` (it is being watched).
|
|
14
|
+
phase: z.string().min(1),
|
|
15
|
+
// When the cutover began, epoch milliseconds.
|
|
16
|
+
at: z.number().int().nonnegative(),
|
|
17
|
+
// The last heartbeat of the ic run doing the cutover.
|
|
18
|
+
alive: z.number().int().nonnegative().optional().catch(undefined),
|
|
19
|
+
// When a probation ends and the parked container is let go.
|
|
20
|
+
probationUntil: z.number().int().nonnegative().optional().catch(undefined),
|
|
21
|
+
// ic's verdict at the moment it answered: this cutover is being done right now by a live ic run.
|
|
22
|
+
cutoverInProgress: z.boolean(),
|
|
23
|
+
});
|
|
24
|
+
export type DeviceSwap = z.infer<typeof DeviceSwapSchema>;
|
|
25
|
+
|
|
26
|
+
export const DeviceSwapsSchema = z.array(DeviceSwapSchema);
|
|
@@ -216,3 +216,7 @@ export const BundleExportSchema = z.object({
|
|
|
216
216
|
});
|
|
217
217
|
export type BundleExport = z.infer<typeof BundleExportSchema>;
|
|
218
218
|
export const BundleExportsSchema = z.object({ exports: z.array(BundleExportSchema) });
|
|
219
|
+
// POST /bundles names the export it started, which fills in as it packs.
|
|
220
|
+
export const BundleStartedSchema = z.object({ name: z.string() });
|
|
221
|
+
// POST /bundles/ticket: the short-lived credential a download navigation carries, since it cannot send a bearer header.
|
|
222
|
+
export const BundleTicketSchema = z.object({ ticket: z.string(), expiresAt: z.number() });
|
package/src/schemas/members.ts
CHANGED
|
@@ -8,6 +8,10 @@ export const MemberSchema = z.object({
|
|
|
8
8
|
email: z.string().describe("Who, lowercased."),
|
|
9
9
|
role: GrantedRoleSchema.describe("The tier they were granted."),
|
|
10
10
|
areas: z.array(z.string()).optional().describe("The areas they are fenced to. Absent means the whole workspace."),
|
|
11
|
+
emailSignIn: z
|
|
12
|
+
.boolean()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Whether they may sign in with an email code the platform confirmed, rather than only with Google or a passkey. Absent means no."),
|
|
11
15
|
});
|
|
12
16
|
export type Member = z.infer<typeof MemberSchema>;
|
|
13
17
|
|
|
@@ -22,10 +26,14 @@ export type MembersRoster = z.infer<typeof MembersRosterSchema>;
|
|
|
22
26
|
|
|
23
27
|
// POST /members: a grant is a role decision, so the role is required. Areas are optional, their absence being the whole
|
|
24
28
|
// workspace; a writer and a guest are refused without them, since for those two tiers the fence is the tier.
|
|
29
|
+
// `emailSignIn` lets the platform's identity assertion open this sandbox for them (policy/identity-assertion.ts): true
|
|
30
|
+
// allows it, false takes it back, absent keeps what the row already says, so a re-grade from an editor that predates
|
|
31
|
+
// the field never drops it.
|
|
25
32
|
export const MemberGrantSchema = z.object({
|
|
26
33
|
email: z.string(),
|
|
27
34
|
role: GrantedRoleSchema,
|
|
28
35
|
areas: z.array(z.string().min(1)).max(20).optional(),
|
|
36
|
+
emailSignIn: z.boolean().optional(),
|
|
29
37
|
});
|
|
30
38
|
export type MemberGrant = z.infer<typeof MemberGrantSchema>;
|
|
31
39
|
|
package/src/schemas/needs.ts
CHANGED
|
@@ -333,6 +333,38 @@ export const ConversationGrantSchema = z.object({
|
|
|
333
333
|
});
|
|
334
334
|
export type ConversationGrant = z.infer<typeof ConversationGrantSchema>;
|
|
335
335
|
|
|
336
|
+
// A yes that names no conversation: it applies to the whole sandbox, so it is listed beside the per-conversation ones and
|
|
337
|
+
// taken back from the same place (docs/architecture/needs.md). Each is kept off the workspace, where no agent writes.
|
|
338
|
+
export const OWNER_GRANT_KINDS = ["command-policy", "hook-set", "extension"] as const;
|
|
339
|
+
export const OwnerGrantKindSchema = z.enum(OWNER_GRANT_KINDS);
|
|
340
|
+
export type OwnerGrantKind = z.infer<typeof OwnerGrantKindSchema>;
|
|
341
|
+
|
|
342
|
+
export const OwnerGrantSchema = z.discriminatedUnion("kind", [
|
|
343
|
+
// A permission card's "Always: <line>" on a command: the line joins the owner's policy for the safety judge, in every
|
|
344
|
+
// conversation, until somebody takes it back.
|
|
345
|
+
z.object({
|
|
346
|
+
kind: z.literal("command-policy"),
|
|
347
|
+
line: z.string().describe("The policy line the judge reads, exactly as it was accepted on the card."),
|
|
348
|
+
at: z.number().describe("When it was allowed, in milliseconds."),
|
|
349
|
+
by: z.string().optional().describe("Who allowed it, off the verified reply."),
|
|
350
|
+
}),
|
|
351
|
+
// A set of Claude Code hooks approved by its digest (guard/hook-approvals.ts): turns run its hooks until it is taken back.
|
|
352
|
+
z.object({
|
|
353
|
+
kind: z.literal("hook-set"),
|
|
354
|
+
digest: z.string().describe("The set's fingerprint, what taking it back names."),
|
|
355
|
+
hooks: z.array(z.string()).describe("Each hook in the set in the daemon's words: when it runs and what it runs."),
|
|
356
|
+
at: z.number().describe("When it was approved, in milliseconds."),
|
|
357
|
+
}),
|
|
358
|
+
// A workspace extension approved for the powers it declared then (extensions/extension-approvals.ts).
|
|
359
|
+
z.object({
|
|
360
|
+
kind: z.literal("extension"),
|
|
361
|
+
id: z.string().describe("The extension, by its publisher.name id."),
|
|
362
|
+
powers: z.array(z.string()).describe("What it was approved to do, one power per entry."),
|
|
363
|
+
at: z.number().describe("When it was approved, in milliseconds."),
|
|
364
|
+
}),
|
|
365
|
+
]);
|
|
366
|
+
export type OwnerGrant = z.infer<typeof OwnerGrantSchema>;
|
|
367
|
+
|
|
336
368
|
// Every yes still standing, by conversation: what its grants widened past its persona or area, and which gated
|
|
337
369
|
// credentials a named person released to it. Reviewed and taken back from Needs you.
|
|
338
370
|
export const StandingGrantsSchema = z.object({
|
|
@@ -353,10 +385,18 @@ export const StandingGrantsSchema = z.object({
|
|
|
353
385
|
.describe("Gated credentials released to it, until somebody takes one back."),
|
|
354
386
|
}),
|
|
355
387
|
),
|
|
388
|
+
ownerWide: z
|
|
389
|
+
.array(OwnerGrantSchema)
|
|
390
|
+
.default([])
|
|
391
|
+
.describe("Yeses that name no conversation: command policy lines allowed with Always, approved hook sets, approved workspace extensions."),
|
|
392
|
+
unreadable: z
|
|
393
|
+
.array(OwnerGrantKindSchema)
|
|
394
|
+
.default([])
|
|
395
|
+
.describe("Kinds of owner-wide yes whose record this build could not read: none of that kind stands, and none is listed, until the file is fixed."),
|
|
356
396
|
});
|
|
357
397
|
export type StandingGrants = z.infer<typeof StandingGrantsSchema>;
|
|
358
398
|
|
|
359
|
-
export const
|
|
399
|
+
export const ConversationGrantRevokeSchema = z.object({
|
|
360
400
|
conversationId: z.string(),
|
|
361
401
|
kind: z
|
|
362
402
|
.enum(["capability", "folder", "shelf", "release", "install", "secret", "everything", "rule"])
|
|
@@ -368,6 +408,16 @@ export const GrantRevokeSchema = z.object({
|
|
|
368
408
|
.describe("The capability id, folder, shelf, released credential or secret it named, or the rule's identity; empty for installs and everything."),
|
|
369
409
|
capability: z.string().optional().describe("For a rule, the capability whose rule it is; a rule's identity alone does not say."),
|
|
370
410
|
});
|
|
411
|
+
export type ConversationGrantRevoke = z.infer<typeof ConversationGrantRevokeSchema>;
|
|
412
|
+
|
|
413
|
+
// Taking back a yes that names no conversation: the kind says which list, `what` which entry of it.
|
|
414
|
+
export const OwnerGrantRevokeSchema = z.object({
|
|
415
|
+
kind: OwnerGrantKindSchema.describe("Which kind of owner-wide yes: a command policy line, an approved hook set, or an approved extension."),
|
|
416
|
+
what: z.string().min(1).describe("The policy line, the hook set's digest, or the extension's id."),
|
|
417
|
+
});
|
|
418
|
+
export type OwnerGrantRevoke = z.infer<typeof OwnerGrantRevokeSchema>;
|
|
419
|
+
|
|
420
|
+
export const GrantRevokeSchema = z.union([ConversationGrantRevokeSchema, OwnerGrantRevokeSchema]);
|
|
371
421
|
export type GrantRevoke = z.infer<typeof GrantRevokeSchema>;
|
|
372
422
|
|
|
373
423
|
// The file the needs store keeps: every open need and the recent closed ones, by id.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReadRestartIntentSchema, RESTART_ACTIONS, RESTART_INITIATORS, RESTART_SIDES, type RestartIntent, RestartIntentSchema } from "./restart-intent.js";
|
|
2
|
+
|
|
3
|
+
// The one schema every writer of /history/logs/restart-intent.json is held to; the daemon's golden/host-files.json carries its
|
|
4
|
+
// enumerations and an example for ic's Rust and the keeper's test to read back (host-files.test.ts).
|
|
5
|
+
|
|
6
|
+
const intent: RestartIntent = {
|
|
7
|
+
at: "2026-10-05T22:45:30.000Z",
|
|
8
|
+
by: "keeper",
|
|
9
|
+
host: "rog",
|
|
10
|
+
side: "wsl",
|
|
11
|
+
action: "restart",
|
|
12
|
+
reason: "the sandbox's /health stopped answering",
|
|
13
|
+
checks: ["health"],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
test("an intent a writer may leave reads back, to the field", () => {
|
|
17
|
+
expect(RestartIntentSchema.parse(intent)).toEqual(intent);
|
|
18
|
+
expect(RestartIntentSchema.parse({ at: intent.at, by: "daemon", action: "stop", reason: "idle-stop" })).toEqual({ at: intent.at, by: "daemon", action: "stop", reason: "idle-stop" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test.each([
|
|
22
|
+
["an initiator nobody is", { ...intent, by: "netd" }],
|
|
23
|
+
["an action nothing does", { ...intent, action: "kill" }],
|
|
24
|
+
["a side no computer has", { ...intent, side: "macos" }],
|
|
25
|
+
["a moment that is not one", { ...intent, at: "yesterday" }],
|
|
26
|
+
["no reason", { at: intent.at, by: "ic", action: "restart" }],
|
|
27
|
+
])("a writer cannot leave %s", (_what, body) => {
|
|
28
|
+
expect(RestartIntentSchema.safeParse(body).success).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// A writer newer than the daemon reading it: still an intent, in the words it used.
|
|
32
|
+
test("the daemon reads what a newer writer names, and only an unreadable moment or a missing reason is no intent", () => {
|
|
33
|
+
const newer = { at: intent.at, by: "netd", side: "macos", action: "kill", reason: "stuck" };
|
|
34
|
+
expect(ReadRestartIntentSchema.parse(newer)).toEqual(newer);
|
|
35
|
+
expect(ReadRestartIntentSchema.safeParse({ ...newer, at: "yesterday" }).success).toBe(false);
|
|
36
|
+
expect(ReadRestartIntentSchema.safeParse({ at: intent.at, by: "", action: "kill", reason: "x" }).success).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("the enumerations are the words every writer and the reader share", () => {
|
|
40
|
+
expect([RESTART_INITIATORS, RESTART_ACTIONS, RESTART_SIDES]).toEqual([
|
|
41
|
+
["owner", "daemon", "ic", "keeper", "desktop"],
|
|
42
|
+
["restart", "stop", "update", "swap", "reshape", "recreate", "rebuild"],
|
|
43
|
+
["wsl", "windows", "linux", "mac"],
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// WHY THE SANDBOX IS ABOUT TO GO DOWN, as the file whoever restarts it leaves on its volume (/history/logs/restart-intent.json).
|
|
4
|
+
//
|
|
5
|
+
// Four writers, three languages: the daemon itself (an idle-stop, an update when quiet, an owner's restart from the Devices
|
|
6
|
+
// card), `ic` (Rust: the verb it is about to run), the machine agent's keeper (TypeScript: the restart a repair makes), and the
|
|
7
|
+
// desktop app. The daemon reads it at its next boot and files the previous run's end against it (boot/restarts/restart-record.ts).
|
|
8
|
+
// One schema, so a writer cannot name an initiator, an action or a side the reader's words do not have: `ic`'s Rust and the
|
|
9
|
+
// keeper's TypeScript are held to it by the daemon's golden/host-files.json (host-files.test.ts writes the enumerations and an
|
|
10
|
+
// example from here; ic's host_files.rs and the keeper's test read them back).
|
|
11
|
+
|
|
12
|
+
// Who acted. `owner` is a person at the editor, `daemon` the sandbox itself, `ic` a person running ic or the desktop app's ic,
|
|
13
|
+
// `keeper` the machine agent's unattended round, `desktop` the desktop app's own restart.
|
|
14
|
+
export const RESTART_INITIATORS = ["owner", "daemon", "ic", "keeper", "desktop"] as const;
|
|
15
|
+
// What is about to happen to the container: `swap` is onto another version that is not the channel's newest (a rollback, or
|
|
16
|
+
// going back from a failed update).
|
|
17
|
+
export const RESTART_ACTIONS = ["restart", "stop", "update", "swap", "reshape", "recreate", "rebuild"] as const;
|
|
18
|
+
// The side of a computer the initiator runs on.
|
|
19
|
+
export const RESTART_SIDES = ["wsl", "windows", "linux", "mac"] as const;
|
|
20
|
+
|
|
21
|
+
// What a writer puts in the file.
|
|
22
|
+
export const RestartIntentSchema = z.object({
|
|
23
|
+
// ISO time it was written, right before the stop it announces.
|
|
24
|
+
at: z.string().refine((at) => Number.isFinite(Date.parse(at)), "an ISO moment"),
|
|
25
|
+
by: z.enum(RESTART_INITIATORS),
|
|
26
|
+
// The machine the initiator runs on, and which side of it.
|
|
27
|
+
host: z.string().optional(),
|
|
28
|
+
side: z.enum(RESTART_SIDES).optional(),
|
|
29
|
+
action: z.enum(RESTART_ACTIONS),
|
|
30
|
+
reason: z.string(),
|
|
31
|
+
// For a keeper's repair: the health checks that failed and made it act.
|
|
32
|
+
checks: z.array(z.string()).optional(),
|
|
33
|
+
});
|
|
34
|
+
export type RestartIntent = z.infer<typeof RestartIntentSchema>;
|
|
35
|
+
export type RestartInitiator = RestartIntent["by"];
|
|
36
|
+
export type RestartAction = RestartIntent["action"];
|
|
37
|
+
export type RestartSide = NonNullable<RestartIntent["side"]>;
|
|
38
|
+
|
|
39
|
+
// What the daemon makes of a file it reads. Lenient where a writer newer than this daemon may be ahead of it: an initiator, an
|
|
40
|
+
// action or a side it does not know still says more than no intent at all, so those three read as the words they were. `at` is
|
|
41
|
+
// held to its meaning, since freshness is decided on it.
|
|
42
|
+
export const ReadRestartIntentSchema = RestartIntentSchema.extend({
|
|
43
|
+
by: z.string().min(1),
|
|
44
|
+
side: z.string().optional(),
|
|
45
|
+
action: z.string().min(1),
|
|
46
|
+
});
|
|
47
|
+
export type ReadRestartIntent = z.infer<typeof ReadRestartIntentSchema>;
|
package/src/schemas/settings.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { LimitHandoffSchema } from "./providers/handoff.js";
|
|
|
10
10
|
import { SessionsPlacementSchema } from "./offload.js";
|
|
11
11
|
import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema } from "./turn-break.js";
|
|
12
12
|
import { ZoneSchema } from "../time/zone.js";
|
|
13
|
+
import { VoiceSettingsSchema } from "./voice.js";
|
|
13
14
|
// Which prompt base the agent runs before this turn composes anything on top: Intentic's own (default), Claude Code's
|
|
14
15
|
// preset, or the owner's text. Declared out here since both the daemon and the browser branch on it.
|
|
15
16
|
// Kept in the daemon's auth root, never in SandboxSettings: an agent cannot edit its execution boundary.
|
|
@@ -560,6 +561,11 @@ export const SandboxSettingsSchema = z.object({
|
|
|
560
561
|
.describe(
|
|
561
562
|
"How many failures in a row before an automation switches itself off. Zero means never, which is the default, because the failure is not always the automation's fault and a job disabled at three in the morning is one nobody re-enables. Only real errors count: a guard deciding there was nothing to do, or the sandbox dying mid-run, say nothing about the automation.",
|
|
562
563
|
),
|
|
564
|
+
// Speaking replies aloud (schemas/voice.ts): which voice speaks each language, and whether a hands-free turn is
|
|
565
|
+
// answered out loud. Read by the daemon's voice engine on every utterance, so a change is heard on the next one.
|
|
566
|
+
voice: VoiceSettingsSchema.prefault({}).describe(
|
|
567
|
+
"How the agent's replies are spoken aloud: whether a hands-free conversation is answered out loud, which voice speaks each language, and a speech provider of your own if you connected one.",
|
|
568
|
+
),
|
|
563
569
|
// Defaults all-allow, so a fresh sandbox behaves as it did before this floor existed.
|
|
564
570
|
admission: AdmissionPolicySchema.prefault({}).describe(
|
|
565
571
|
"Whether work started from outside may run, per kind of trigger: let it, hold it for approval, or refuse it. Composes with each automation's own setting, and the stricter of the two wins, so holding every visitor's message needs no edit to each automation.",
|
|
@@ -28,6 +28,17 @@ export const PairingAnswerSchema = z.object({
|
|
|
28
28
|
});
|
|
29
29
|
export type PairingAnswer = z.infer<typeof PairingAnswerSchema>;
|
|
30
30
|
|
|
31
|
+
// What POST /system/<door>/pair answers on any peer door (device, browser, phone, runner): the token, and the short code
|
|
32
|
+
// only the device door mints beside it. `expiresIn` is optional on read since a reader meets daemons older than the
|
|
33
|
+
// field; the editor then shows the token without a countdown.
|
|
34
|
+
export const PeerPairingAnswerSchema = PairingAnswerSchema.partial({ code: true, expiresIn: true });
|
|
35
|
+
export type PeerPairingAnswer = z.infer<typeof PeerPairingAnswerSchema>;
|
|
36
|
+
|
|
37
|
+
// POST /system/sync/pair: the pairing with the mode the daemon granted, which may be less than the one asked for (a
|
|
38
|
+
// member's "sync" comes back "mirror"), and the code that stands for it.
|
|
39
|
+
export const SyncPairAnswerSchema = PeerPairingAnswerSchema.extend({ mode: z.enum(["sync", "mirror"]).optional() });
|
|
40
|
+
export type SyncPairAnswer = z.infer<typeof SyncPairAnswerSchema>;
|
|
41
|
+
|
|
31
42
|
// POST /system/sync/pair's optional body: the person's chosen local folder and takeover, handed out with the code.
|
|
32
43
|
// It goes to their own sandbox, never the platform.
|
|
33
44
|
export const SyncPairRequestSchema = z.object({
|
package/src/schemas/speech.ts
CHANGED
|
@@ -66,6 +66,10 @@ export const SpeechStatusSchema = z.object({
|
|
|
66
66
|
});
|
|
67
67
|
export type SpeechStatus = z.infer<typeof SpeechStatusSchema>;
|
|
68
68
|
|
|
69
|
+
// POST /speech/transcribe: one phrase as WAV in, its words out; empty when it held none.
|
|
70
|
+
export const SpeechTranscriptSchema = z.object({ text: z.string() });
|
|
71
|
+
export type SpeechTranscript = z.infer<typeof SpeechTranscriptSchema>;
|
|
72
|
+
|
|
69
73
|
// GET /speech/stream, a WebSocket: the composer cuts the microphone into phrases itself (its silence segmenter) and
|
|
70
74
|
// streams each one as it is spoken. A text frame `begin` opens phrase `id`; binary frames that follow are its audio,
|
|
71
75
|
// 16 kHz mono s16le; `end` closes it, keeping its first `keep` samples (the trailing pause trimmed off); `drop`
|