@intentic/sandbox-contract 1.342.0 → 1.344.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 +4 -0
- package/dist/contracts/device.contract.d.ts +17 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +3 -2
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +4 -2
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +52 -5
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +1 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +1 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/index.d.ts +56 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +1 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +17 -0
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/schemas/data-protection.d.ts +4 -1
- package/dist/schemas/data-protection.d.ts.map +1 -1
- package/dist/schemas/data-protection.js +17 -1
- package/dist/schemas/data-protection.js.map +1 -1
- package/dist/schemas/device-folder.d.ts +61 -0
- package/dist/schemas/device-folder.d.ts.map +1 -0
- package/dist/schemas/device-folder.js +24 -0
- package/dist/schemas/device-folder.js.map +1 -0
- package/dist/schemas/devices.d.ts +108 -38
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +7 -10
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/metrics.d.ts +11 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +17 -0
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +1 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +15 -0
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +4 -2
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +4 -8
- package/dist/schemas/settings.js.map +1 -1
- package/dist/state/definition.d.ts +8 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/events/agent-events.ts +2 -0
- package/src/policy/turned-away.ts +1 -0
- package/src/schemas/data-protection.ts +35 -9
- package/src/schemas/device-folder.ts +59 -0
- package/src/schemas/devices.ts +13 -38
- package/src/schemas/metrics.ts +28 -3
- package/src/schemas/settings-history.ts +20 -0
- package/src/schemas/settings.test.ts +38 -0
- package/src/schemas/settings.ts +12 -12
package/src/schemas/devices.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// What one of the user's own machines is running.
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { DeviceBridgeHoldSchema, DeviceConflictSchema, DeviceLowDiskSchema } from "./device-folder.js";
|
|
3
4
|
import { parseHostConnection, type DeviceFacts, DeviceFactsSchema, MachineIdSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js";
|
|
4
5
|
import { RollbackTargetSchema, UpdateOutcomeSchema } from "./updates.js";
|
|
5
6
|
import { DEV_VERSION } from "../state/versions.js";
|
|
@@ -316,38 +317,8 @@ export const DeviceCommandResultSchema = z.object({
|
|
|
316
317
|
});
|
|
317
318
|
export type DeviceCommandResult = z.infer<typeof DeviceCommandResultSchema>;
|
|
318
319
|
|
|
319
|
-
// What
|
|
320
|
-
|
|
321
|
-
// — and it is the whole difference between a conflict somebody must settle and one a device can clear by itself. Absent
|
|
322
|
-
// means Mutagen did not say, not that a side is untouched.
|
|
323
|
-
export const DeviceConflictChangeSchema = z.enum(["created", "modified", "deleted", "untracked"]);
|
|
324
|
-
export type DeviceConflictChange = z.infer<typeof DeviceConflictChangeSchema>;
|
|
325
|
-
|
|
326
|
-
// Which of two unlike things this conflict is. `both-edited` is the one the mode exists for: two real copies, one
|
|
327
|
-
// choice, a person's. `derived-leftover` is not a disagreement at all — one side deleted a directory, the other still
|
|
328
|
-
// holds build output inside it, and Mutagen will not delete a directory whose contents it never carried. Nothing at
|
|
329
|
-
// stake, nobody's edit, and no judgement to make.
|
|
330
|
-
export const DeviceConflictNatureSchema = z.enum(["derived-leftover", "both-edited"]);
|
|
331
|
-
export type DeviceConflictNature = z.infer<typeof DeviceConflictNatureSchema>;
|
|
332
|
-
|
|
333
|
-
export const DeviceConflictSchema = z.object({
|
|
334
|
-
/** Relative to the synced folder (matches both `localDir` and /work); empty means the root itself. */
|
|
335
|
-
path: z.string(),
|
|
336
|
-
/** What happened on the device (Mutagen's alpha). */
|
|
337
|
-
local: DeviceConflictChangeSchema.optional(),
|
|
338
|
-
/** And in the sandbox's /work (its beta). */
|
|
339
|
-
sandbox: DeviceConflictChangeSchema.optional(),
|
|
340
|
-
// Absent from an agent older than the classification, which is why no reader may treat absence as `both-edited`:
|
|
341
|
-
// `clearableOnDevice` below tests for the derived shape rather than against the other one.
|
|
342
|
-
nature: DeviceConflictNatureSchema.optional(),
|
|
343
|
-
});
|
|
344
|
-
export type DeviceConflict = z.infer<typeof DeviceConflictSchema>;
|
|
345
|
-
|
|
346
|
-
// Whether this one is the device's own to clear: build output on the device, inside a directory the sandbox deleted.
|
|
347
|
-
// The rule lives here because two sides read it — the machine agent deciding what to remove, and the browser deciding
|
|
348
|
-
// whether to offer a button instead of an agent — and they must never disagree about which conflicts need a person.
|
|
349
|
-
export const clearableOnDevice = (conflict: DeviceConflict): boolean =>
|
|
350
|
-
conflict.nature === "derived-leftover" && conflict.local === "untracked" && conflict.sandbox === "deleted";
|
|
320
|
+
// What a synced folder reports beyond Mutagen's word, its conflicts and what the device holds back (device-folder.ts).
|
|
321
|
+
export * from "./device-folder.js";
|
|
351
322
|
|
|
352
323
|
// One paired sandbox as the local agent holds it; `localDir` is which folder on that device holds this sandbox's /work.
|
|
353
324
|
export const DevicePairingSchema = z.object({
|
|
@@ -378,6 +349,10 @@ export const DevicePairingSchema = z.object({
|
|
|
378
349
|
paused: z.boolean().optional(),
|
|
379
350
|
// Second session's status (the state-dir backup mirror), reported separately: the two fail independently.
|
|
380
351
|
backupStatus: z.string().optional(),
|
|
352
|
+
// (2026-10-18) While the drive holding `localDir` is nearly full, the reading that paused its sync and git bridge; and the
|
|
353
|
+
// repos whose git history stopped following for a while (device-folder.ts). Each absent while it does not hold.
|
|
354
|
+
lowDisk: DeviceLowDiskSchema.optional(),
|
|
355
|
+
bridgeHolds: z.array(DeviceBridgeHoldSchema).optional(),
|
|
381
356
|
});
|
|
382
357
|
export type DevicePairing = z.infer<typeof DevicePairingSchema>;
|
|
383
358
|
// One sandbox port and what became of it on this machine's localhost; carries a state rather than just live forwards,
|
|
@@ -574,14 +549,14 @@ export const DeviceSchema = z.object({
|
|
|
574
549
|
gap: DeviceGapSchema.optional(),
|
|
575
550
|
});
|
|
576
551
|
export type Device = z.infer<typeof DeviceSchema>;
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
// connection key); never withdrawn by the daemon itself, since the manifest the cards live in is a workspace file a land
|
|
580
|
-
// can rewrite for a moment, and withdrawing on that would unpair real devices.
|
|
552
|
+
// Device keys no machine is drawn for, each list absent when empty: enrollments no card holds, which only the owner withdraws (a land can rewrite
|
|
553
|
+
// the card manifest for a moment), and connections live at once that report one machine id, kept rather than deduped (hosts/host-dedupe.ts).
|
|
581
554
|
export const CardlessHostSchema = z.object({ id: z.string(), card: z.string(), environment: z.string() });
|
|
582
555
|
export type CardlessHost = z.infer<typeof CardlessHostSchema>;
|
|
583
|
-
|
|
584
|
-
export
|
|
556
|
+
export const SharedIdentitySchema = z.object({ machineId: z.string(), connections: z.array(CardlessHostSchema) });
|
|
557
|
+
export type SharedIdentity = z.infer<typeof SharedIdentitySchema>;
|
|
558
|
+
const deviceKeyNotes = { cardlessHosts: z.array(CardlessHostSchema).optional(), sharedIdentities: z.array(SharedIdentitySchema).optional() };
|
|
559
|
+
export const DevicesListSchema = z.object({ devices: z.array(DeviceSchema), ...deviceKeyNotes });
|
|
585
560
|
export type DevicesList = z.infer<typeof DevicesListSchema>;
|
|
586
561
|
|
|
587
562
|
// The physical computer a device is an environment of. Windows and every WSL distro on it are one machine with one
|
package/src/schemas/metrics.ts
CHANGED
|
@@ -97,7 +97,9 @@ export const SandboxUsageSchema = z.object({
|
|
|
97
97
|
countedFileCacheBytes: z
|
|
98
98
|
.number()
|
|
99
99
|
.optional()
|
|
100
|
-
.describe(
|
|
100
|
+
.describe(
|
|
101
|
+
"File cache counted in resident usage (memory.stat file less inactive_file), in bytes. Absent unless both counters are valid.",
|
|
102
|
+
),
|
|
101
103
|
kernelBytes: z
|
|
102
104
|
.number()
|
|
103
105
|
.optional()
|
|
@@ -163,7 +165,9 @@ export const SandboxMetricsSchema = z.object({
|
|
|
163
165
|
.optional()
|
|
164
166
|
.describe("How long the CPU figures were measured over, in milliseconds. Absent on a first reading, and then so is every CPU figure."),
|
|
165
167
|
sandbox: SandboxUsageSchema.describe("The sandbox as a whole, including the daemon."),
|
|
166
|
-
daemon: DaemonUsageSchema.describe(
|
|
168
|
+
daemon: DaemonUsageSchema.describe(
|
|
169
|
+
"The daemon's own usage, included in the sandbox total and process count but excluded from roles and session sums.",
|
|
170
|
+
),
|
|
167
171
|
sessions: z
|
|
168
172
|
.record(z.string(), SessionMetricsSchema)
|
|
169
173
|
.describe(
|
|
@@ -273,8 +277,29 @@ export const StorageScanSchema = z.object({
|
|
|
273
277
|
export type StorageScan = z.infer<typeof StorageScanSchema>;
|
|
274
278
|
|
|
275
279
|
export const StorageReportSchema = z.object({
|
|
276
|
-
scan: StorageScanSchema.optional().describe(
|
|
280
|
+
scan: StorageScanSchema.optional().describe(
|
|
281
|
+
"The last scan that finished, kept across restarts; its `finishedAt` says how old it is. Absent until one has.",
|
|
282
|
+
),
|
|
277
283
|
scanning: z.boolean().describe("Whether a scan is running now."),
|
|
284
|
+
historyDisk: z
|
|
285
|
+
.object({
|
|
286
|
+
freeBytes: z.number().describe("Space available to the sandbox on the history volume, measured now."),
|
|
287
|
+
totalBytes: z.number().describe("The history volume's total size."),
|
|
288
|
+
floorBytes: z.number().describe("Space kept free before a new checkout: 2% of this volume clamped to 5–20 GiB."),
|
|
289
|
+
low: z.boolean().describe("Whether available history space is below the checkout floor."),
|
|
290
|
+
})
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("The filesystem holding conversation checkouts, independent of the workspace volume. Absent if unreadable."),
|
|
293
|
+
repositoryCheckouts: z
|
|
294
|
+
.array(
|
|
295
|
+
z.object({
|
|
296
|
+
repo: z.string().describe("The live repository's workspace-relative id, or root for the workspace repository."),
|
|
297
|
+
head: z.string().describe("The commit whose tracked blobs were measured."),
|
|
298
|
+
bytes: z.number().describe("The sum of blob sizes at this HEAD: the estimated bytes for one full checkout."),
|
|
299
|
+
}),
|
|
300
|
+
)
|
|
301
|
+
.optional()
|
|
302
|
+
.describe("Live repositories' checkout costs, cached per repository and commit. Repositories without a readable HEAD are omitted."),
|
|
278
303
|
});
|
|
279
304
|
export type StorageReport = z.infer<typeof StorageReportSchema>;
|
|
280
305
|
|
|
@@ -3,6 +3,7 @@ import { at, drop, dropAll, fold, isJsonObject, type JsonObject, mapValue, retyp
|
|
|
3
3
|
import { type ModelRole, ModelRoleSchema } from "../models/model-roles.js";
|
|
4
4
|
import { setupsFromRoleLists } from "../models/model-setups.js";
|
|
5
5
|
import { ModelPinSchema, type ModelPin } from "./agent.js";
|
|
6
|
+
import { RESTORE_POINT_KEEP_DAYS_DEFAULT } from "./data-protection.js";
|
|
6
7
|
import type { KeepWarmSettings, RuleSchema } from "./settings.js";
|
|
7
8
|
|
|
8
9
|
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
@@ -101,6 +102,21 @@ const roleListsIntoSetups = transform("folds modelRoles and modelDefault into se
|
|
|
101
102
|
return { ...rest, modelSetups, roleSetups };
|
|
102
103
|
});
|
|
103
104
|
|
|
105
|
+
const restorePointsKept = fold("carries restorePointDays into restorePointKeepDays, a stored zero taking the 30-day default", {
|
|
106
|
+
from: ["restorePointDays"],
|
|
107
|
+
into: "restorePointKeepDays",
|
|
108
|
+
applies: (retention) => Object.hasOwn(retention, "restorePointDays"),
|
|
109
|
+
convert: ({ restorePointDays }, retention) => {
|
|
110
|
+
// A file that already names the new key (a definition written for both) keeps it.
|
|
111
|
+
const kept = FlatNumber.safeParse(retention["restorePointKeepDays"]);
|
|
112
|
+
if (kept.success) {
|
|
113
|
+
return kept.data;
|
|
114
|
+
}
|
|
115
|
+
const chosen = FlatNumber.safeParse(restorePointDays);
|
|
116
|
+
return chosen.success && chosen.data > 0 ? chosen.data : RESTORE_POINT_KEEP_DAYS_DEFAULT;
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
104
120
|
export const SETTINGS_HISTORY = [
|
|
105
121
|
// Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
|
|
106
122
|
// retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
|
|
@@ -154,4 +170,8 @@ export const SETTINGS_HISTORY = [
|
|
|
154
170
|
...dropAll(["toolResultClearing", "toolResultClearingHoldout"]),
|
|
155
171
|
at("modelRoles", refactorsFromChores),
|
|
156
172
|
roleListsIntoSetups,
|
|
173
|
+
// Restore points kept 30 days by default (2026-10-18). `restorePointDays` held zero both where the owner chose to keep
|
|
174
|
+
// every point and where the settings page had saved the old default, which no reading could tell apart: the key is
|
|
175
|
+
// retired, a positive choice carries over, and a stored zero takes the new default.
|
|
176
|
+
at("retention", restorePointsKept),
|
|
157
177
|
] as const;
|
|
@@ -207,3 +207,41 @@ describe(`per-job model lists`, () => {
|
|
|
207
207
|
expect(convertDocument(SETTINGS_HISTORY, `object`, { modelSetups: [{ id: `low`, pins: [haiku] }], roleSetups: {} }, true).changes).toEqual([]);
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
test("disk retention defaults bound checkouts and restore points while preserving explicit zero", () => {
|
|
212
|
+
const defaults = SandboxSettingsSchema.parse({});
|
|
213
|
+
expect(defaults.agentRetentionDays).toBe(3);
|
|
214
|
+
expect(defaults.heldCheckoutDays).toBe(14);
|
|
215
|
+
expect(defaults.retention.restorePointKeepDays).toBe(30);
|
|
216
|
+
const existing = SandboxSettingsSchema.parse({ heldCheckoutDays: 0, retention: { restorePointKeepDays: 0 } });
|
|
217
|
+
expect(existing.heldCheckoutDays).toBe(0);
|
|
218
|
+
expect(existing.retention.restorePointKeepDays).toBe(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe(`restore points' retention, renamed so a stored zero is the owner's own (2026-10-18)`, () => {
|
|
222
|
+
const read = (retention: Record<string, unknown>) => {
|
|
223
|
+
const converted = convertDocument(SETTINGS_HISTORY, `object`, { retention: { transcriptDays: 7, ...retention } }, true);
|
|
224
|
+
return { retention: SandboxSettingsSchema.parse(converted.value).retention, changes: converted.changes };
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
test(`an earlier release's stored zero, which the settings page wrote for the old default, takes the 30-day default`, () => {
|
|
228
|
+
const { retention, changes } = read({ restorePointDays: 0 });
|
|
229
|
+
expect(retention.restorePointKeepDays).toBe(30);
|
|
230
|
+
expect(retention.transcriptDays).toBe(7);
|
|
231
|
+
expect(changes.map((change) => change.at)).toEqual([`retention`]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test(`a positive choice carries over`, () => {
|
|
235
|
+
expect(read({ restorePointDays: 90 }).retention.restorePointKeepDays).toBe(90);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test(`a zero stored under the new key is kept, and read again changes nothing`, () => {
|
|
239
|
+
const { retention, changes } = read({ restorePointKeepDays: 0 });
|
|
240
|
+
expect(retention.restorePointKeepDays).toBe(0);
|
|
241
|
+
expect(changes).toEqual([]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test(`a file naming both keeps the new one`, () => {
|
|
245
|
+
expect(read({ restorePointDays: 0, restorePointKeepDays: 5 }).retention.restorePointKeepDays).toBe(5);
|
|
246
|
+
});
|
|
247
|
+
});
|
package/src/schemas/settings.ts
CHANGED
|
@@ -13,7 +13,13 @@ import { SessionsPlacementSchema } from "./offload.js";
|
|
|
13
13
|
import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema } from "./turn-break.js";
|
|
14
14
|
import { ZoneSchema } from "../time/zone.js";
|
|
15
15
|
import { VoiceSettingsSchema } from "./voice.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
AiNoticeSchema,
|
|
18
|
+
AuditSettingsSchema,
|
|
19
|
+
RetentionSettingsSchema,
|
|
20
|
+
HeldCheckoutDaysSchema,
|
|
21
|
+
AgentRetentionDaysSchema,
|
|
22
|
+
} from "./data-protection.js";
|
|
17
23
|
// Which prompt base the agent runs before this turn composes anything on top: Intentic's own (default), Claude Code's
|
|
18
24
|
// preset, or the owner's text. Declared out here since both the daemon and the browser branch on it.
|
|
19
25
|
// Kept in the daemon's auth root, never in SandboxSettings: an agent cannot edit its execution boundary.
|
|
@@ -398,14 +404,8 @@ export const SandboxSettingsSchema = z.object({
|
|
|
398
404
|
.describe(
|
|
399
405
|
"Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.",
|
|
400
406
|
),
|
|
401
|
-
agentRetentionDays:
|
|
402
|
-
|
|
403
|
-
.min(0)
|
|
404
|
-
.max(365)
|
|
405
|
-
.default(3)
|
|
406
|
-
.describe(
|
|
407
|
-
"How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.",
|
|
408
|
-
),
|
|
407
|
+
agentRetentionDays: AgentRetentionDaysSchema,
|
|
408
|
+
heldCheckoutDays: HeldCheckoutDaysSchema,
|
|
409
409
|
// One answer per ending, never a set of switches over the same event: the chat's own question and these rows are
|
|
410
410
|
// the same question at two scopes. Every ending defaults to `wait`, because a re-run spends the reader's allowance
|
|
411
411
|
// on a turn they sent once.
|
|
@@ -543,10 +543,10 @@ export const SandboxSettingsSchema = z.object({
|
|
|
543
543
|
voice: VoiceSettingsSchema.prefault({}).describe(
|
|
544
544
|
"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.",
|
|
545
545
|
),
|
|
546
|
-
// What a data protection officer asks about (schemas/data-protection.ts).
|
|
547
|
-
//
|
|
546
|
+
// What a data protection officer asks about (schemas/data-protection.ts). Restore points default to thirty days;
|
|
547
|
+
// the other policies preserve the earlier behaviour until the owner changes them or applies the public-office preset.
|
|
548
548
|
retention: RetentionSettingsSchema.prefault({}).describe(
|
|
549
|
-
"How long this sandbox keeps each kind of copy it makes of what people and agents wrote: transcripts, restore points, the privacy shield's memory, converted documents and raw command output.
|
|
549
|
+
"How long this sandbox keeps each kind of copy it makes of what people and agents wrote: transcripts, restore points, the privacy shield's memory, converted documents and raw command output. Restore points default to 30 days; the other copies default to forever.",
|
|
550
550
|
),
|
|
551
551
|
audit: AuditSettingsSchema.prefault({}).describe(
|
|
552
552
|
"The audit trail: who changed a policy, a member, a gate or a setting, and optionally who read what. How long it is kept, and where a copy is sent.",
|