@intentic/sandbox-contract 1.309.0 → 1.310.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/dist/contracts/settings.contract.d.ts +3 -15
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/index.d.ts +33 -73
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/protocol/upgrade-relay.d.ts +4 -0
- package/dist/protocol/upgrade-relay.d.ts.map +1 -0
- package/dist/protocol/upgrade-relay.js +25 -0
- package/dist/protocol/upgrade-relay.js.map +1 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/metrics.d.ts +24 -25
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +6 -31
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/settings.d.ts +9 -29
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +25 -11
- package/dist/schemas/settings.js.map +1 -1
- package/dist/state/definition.d.ts +8 -20
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/workspace-state.d.ts +0 -7
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +0 -8
- package/dist/state/workspace-state.js.map +1 -1
- package/package.json +16 -5
- package/src/chores/chores.test.ts +0 -1
- package/src/chores/digest.test.ts +0 -1
- package/src/chores/probes.test.ts +0 -1
- package/src/chores/stack.test.ts +0 -1
- package/src/chores/verdict.test.ts +0 -1
- package/src/events/agent-words.test.ts +0 -1
- package/src/events/resume.test.ts +0 -1
- package/src/events/watch-wake.test.ts +0 -1
- package/src/ids/conversation-ids.test.ts +1 -2
- package/src/ids/hostnames.test.ts +0 -1
- package/src/ids/share-paths.test.ts +0 -1
- package/src/ids/tunnel-ids.test.ts +0 -1
- package/src/index.ts +0 -3
- package/src/models/agent-catalog.test.ts +0 -1
- package/src/models/capability-ledger.test.ts +0 -1
- package/src/models/model-label.test.ts +0 -1
- package/src/models/model-offer.test.ts +0 -1
- package/src/models/model-order.test.ts +0 -1
- package/src/models/model-pins.test.ts +0 -1
- package/src/models/model-roles.test.ts +0 -1
- package/src/models/plan-pools.test.ts +0 -1
- package/src/models/provider-specs.test.ts +0 -1
- package/src/policy/batch-runs.test.ts +0 -1
- package/src/policy/command-classes.test.ts +0 -1
- package/src/policy/credential-material.test.ts +0 -1
- package/src/policy/fence-paths.test.ts +0 -1
- package/src/policy/overlay-lint.test.ts +0 -1
- package/src/policy/owner-ticket.test.ts +0 -1
- package/src/policy/persona-home.test.ts +0 -1
- package/src/policy/reserved-servers.test.ts +0 -1
- package/src/policy/safety-policy.test.ts +0 -1
- package/src/policy/search-globs.test.ts +0 -1
- package/src/protocol/container-requirements.test.ts +0 -1
- package/src/protocol/ingress-contract.test.ts +0 -1
- package/src/protocol/ingress-protocol.test.ts +0 -1
- package/src/protocol/peer-dial.test.ts +1 -2
- package/src/protocol/peer-mcp-server.test.ts +0 -1
- package/src/protocol/routes.test.ts +6 -3
- package/src/protocol/tunnel-heartbeat.test.ts +2 -3
- package/src/protocol/upgrade-relay.ts +28 -0
- package/src/schemas/agent.test.ts +0 -1
- package/src/schemas/areas.test.ts +1 -2
- package/src/schemas/ci.ts +3 -9
- package/src/schemas/context-trim.test.ts +0 -1
- package/src/schemas/dev-rebuild-log.test.ts +0 -1
- package/src/schemas/git/git.test.ts +0 -1
- package/src/schemas/metrics.ts +33 -64
- package/src/schemas/settings.test.ts +41 -7
- package/src/schemas/settings.ts +48 -44
- package/src/schemas/version-seam.test.ts +0 -1
- package/src/state/contract-lock.test.ts +0 -1
- package/src/state/fix-attempt-plan.test.ts +0 -1
- package/src/state/fix-stance.test.ts +0 -1
- package/src/state/landed-commit.test.ts +0 -1
- package/src/state/runtime-state.test.ts +0 -1
- package/src/state/versions.test.ts +0 -1
- package/src/state/workspace-state.test.ts +0 -3
- package/src/state/workspace-state.ts +0 -14
- package/src/text/archives.test.ts +0 -1
- package/src/text/documents.test.ts +0 -1
- package/src/text/embed.test.ts +2 -3
- package/src/text/emoji.test.ts +0 -1
- package/src/text/mentions.test.ts +0 -1
- package/src/text/path-refs.test.ts +0 -1
- package/src/text/title.test.ts +0 -1
- package/src/text/transcript-fold.test.ts +12 -3
- package/src/text/whisper.test.ts +0 -1
- package/src/text/workflow-faults.test.ts +0 -1
- package/src/time/zone.test.ts +0 -1
- package/dist/contracts/prepush.contract.d.ts +0 -32
- package/dist/contracts/prepush.contract.d.ts.map +0 -1
- package/dist/contracts/prepush.contract.js +0 -41
- package/dist/contracts/prepush.contract.js.map +0 -1
- package/src/contracts/prepush.contract.ts +0 -58
package/src/schemas/metrics.ts
CHANGED
|
@@ -59,9 +59,7 @@ export const SandboxUsageSchema = z.object({
|
|
|
59
59
|
.optional()
|
|
60
60
|
.describe("CPU the whole sandbox used over the window, as a percentage of all it may use (`cores`). Absent on a first reading."),
|
|
61
61
|
cores: z.number().describe("How many cores the sandbox may use: its CPU quota, or every core it is allowed to run on when it has none."),
|
|
62
|
-
memoryBytes: z
|
|
63
|
-
.number()
|
|
64
|
-
.describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
|
|
62
|
+
memoryBytes: z.number().describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
|
|
65
63
|
memoryLimitBytes: z.number().describe("The memory limit: the container's own, or the machine's memory when the container has none."),
|
|
66
64
|
swapBytes: z.number().optional().describe("Memory pushed out to swap. Absent where the sandbox cannot see its own swap."),
|
|
67
65
|
diskBytes: z.number().optional().describe("Space used on the volume the workspace lives on. Absent when the volume would not say."),
|
|
@@ -70,7 +68,9 @@ export const SandboxUsageSchema = z.object({
|
|
|
70
68
|
.tuple([z.number(), z.number(), z.number()])
|
|
71
69
|
.describe("The load average over 1, 5 and 15 minutes. It is the machine's, so other sandboxes on it count too."),
|
|
72
70
|
processes: z.number().describe("How many processes are running in the sandbox."),
|
|
73
|
-
pressure: PressureMetricsSchema.optional().describe(
|
|
71
|
+
pressure: PressureMetricsSchema.optional().describe(
|
|
72
|
+
"How much work waited on CPU, memory or disk lately. Absent where the kernel does not report it.",
|
|
73
|
+
),
|
|
74
74
|
});
|
|
75
75
|
export type SandboxUsage = z.infer<typeof SandboxUsageSchema>;
|
|
76
76
|
|
|
@@ -109,89 +109,58 @@ export type SandboxMetrics = z.infer<typeof SandboxMetricsSchema>;
|
|
|
109
109
|
// What is filling the sandbox's disk, by what it is for (GET /system/storage). Sizes are apparent bytes, and a file
|
|
110
110
|
// with several hard links counts once, toward the first place the scan met it.
|
|
111
111
|
|
|
112
|
-
// Every category a byte on the sandbox's volumes can belong to; STORAGE_CLEANABILITY below says which may be cleaned.
|
|
113
|
-
export const STORAGE_CATEGORIES = [
|
|
114
|
-
// The workspace's own files: repositories, their dependencies, anything the owner or an agent put there.
|
|
115
|
-
"workspace",
|
|
116
|
-
// Transcripts, runtime session stores, attachments and loop memory of every conversation, archived ones included.
|
|
117
|
-
"conversations",
|
|
118
|
-
// Each open conversation's own checkout of the repositories, and the dependencies its turns installed over them.
|
|
119
|
-
"checkouts",
|
|
120
|
-
// The minute-by-minute and per-turn snapshots the History timeline restores from.
|
|
121
|
-
"restorePoints",
|
|
122
|
-
// The repositories' own git data: every commit and branch, pushed or not.
|
|
123
|
-
"repositories",
|
|
124
|
-
// Installed versions of the agent runtimes: the one turns use and the one kept for Revert.
|
|
125
|
-
"engines",
|
|
126
|
-
// Search indexes and caches the sandbox keeps up to date by itself while it runs.
|
|
127
|
-
"indexes",
|
|
128
|
-
// Installed extensions and their scratch.
|
|
129
|
-
"extensions",
|
|
130
|
-
// The sandbox's own Docker: images, containers, volumes.
|
|
131
|
-
"docker",
|
|
132
|
-
// Settings, credentials, sign-ins and the ledgers of what was spent and done.
|
|
133
|
-
"state",
|
|
134
|
-
// Anything no part of the sandbox claims.
|
|
135
|
-
"other",
|
|
136
|
-
// Git data and checkouts of repositories deleted from the workspace.
|
|
137
|
-
"trash",
|
|
138
|
-
// Copies of the sandbox packed for download.
|
|
139
|
-
"exports",
|
|
140
|
-
// Files agents produced: generated images, reports, harness output.
|
|
141
|
-
"artifacts",
|
|
142
|
-
// Screenshots and page snapshots the agent's browser took.
|
|
143
|
-
"browserCaptures",
|
|
144
|
-
// The agent browser's profiles, with the sites it is signed in to.
|
|
145
|
-
"browserProfiles",
|
|
146
|
-
// Weights downloaded for local models.
|
|
147
|
-
"modelWeights",
|
|
148
|
-
// The sandbox's own logs and terminal captures.
|
|
149
|
-
"logs",
|
|
150
|
-
// Scratch space agents and checks leave behind.
|
|
151
|
-
"scratch",
|
|
152
|
-
// Package managers' content stores, which the next install refills.
|
|
153
|
-
"packageStores",
|
|
154
|
-
// Build tools' output caches.
|
|
155
|
-
"buildCaches",
|
|
156
|
-
] as const;
|
|
157
|
-
export const StorageCategoryIdSchema = z.enum(STORAGE_CATEGORIES);
|
|
158
|
-
export type StorageCategoryId = z.infer<typeof StorageCategoryIdSchema>;
|
|
159
|
-
|
|
160
112
|
// none: never removed from here; safe: comes back by itself, removed without asking; confirm: removed only once the
|
|
161
113
|
// owner has read what it costs.
|
|
162
114
|
export const StorageCleanabilitySchema = z.enum(["none", "safe", "confirm"]);
|
|
163
115
|
export type StorageCleanability = z.infer<typeof StorageCleanabilitySchema>;
|
|
164
116
|
|
|
165
|
-
//
|
|
166
|
-
// confirm by it. State, credentials and live work are `none`; what regenerates is `safe`; the owner's own data asks.
|
|
117
|
+
// Every category a byte on the volumes can belong to, and its cleanability: the cleaner refuses by it, the editor words its confirm by it.
|
|
167
118
|
export const STORAGE_CLEANABILITY = {
|
|
119
|
+
// The workspace's own files: repositories, their dependencies, anything the owner or an agent put there.
|
|
168
120
|
workspace: "none",
|
|
121
|
+
// Transcripts, runtime session stores, attachments and loop memory of every conversation, archived ones included.
|
|
169
122
|
conversations: "none",
|
|
170
|
-
//
|
|
123
|
+
// Each open conversation's own checkout of the repositories and the dependencies its turns installed; archiving the conversation frees it.
|
|
171
124
|
checkouts: "none",
|
|
172
|
-
//
|
|
125
|
+
// Per-turn and minute-by-minute snapshots History restores from; no compaction to offer: `git gc --auto` runs after each, all is reachable.
|
|
173
126
|
restorePoints: "none",
|
|
127
|
+
// The repositories' own git data: every commit and branch, pushed or not.
|
|
174
128
|
repositories: "none",
|
|
175
|
-
//
|
|
129
|
+
// Installed agent runtimes, the one turns use and the one kept for Revert; the Engines card owns which is which, so removing one strands it.
|
|
176
130
|
engines: "none",
|
|
177
|
-
//
|
|
131
|
+
// Search indexes and caches the sandbox keeps up to date while it runs, held open by it: removing a file frees nothing until it closes.
|
|
178
132
|
indexes: "none",
|
|
133
|
+
// Installed extensions and their scratch.
|
|
179
134
|
extensions: "none",
|
|
135
|
+
// The sandbox's own Docker: images, containers, volumes.
|
|
180
136
|
docker: "none",
|
|
137
|
+
// Settings, credentials, sign-ins and the ledgers of what was spent and done.
|
|
181
138
|
state: "none",
|
|
139
|
+
// Anything no part of the sandbox claims.
|
|
182
140
|
other: "none",
|
|
183
|
-
//
|
|
141
|
+
// Git data and checkouts of repositories deleted from the workspace; nothing reads it again, but it may hold commits never pushed.
|
|
184
142
|
trash: "confirm",
|
|
143
|
+
// Copies of the sandbox packed for download.
|
|
185
144
|
exports: "confirm",
|
|
145
|
+
// Files agents produced: generated images, reports, harness output.
|
|
186
146
|
artifacts: "confirm",
|
|
147
|
+
// Screenshots and page snapshots the agent's browser took.
|
|
187
148
|
browserCaptures: "confirm",
|
|
149
|
+
// The agent browser's profiles, with the sites it is signed in to.
|
|
188
150
|
browserProfiles: "confirm",
|
|
151
|
+
// Weights downloaded for local models.
|
|
189
152
|
modelWeights: "confirm",
|
|
153
|
+
// The sandbox's own logs and terminal captures.
|
|
190
154
|
logs: "safe",
|
|
155
|
+
// Scratch space agents and checks leave behind.
|
|
191
156
|
scratch: "safe",
|
|
157
|
+
// Package managers' content stores, which the next install refills.
|
|
192
158
|
packageStores: "safe",
|
|
159
|
+
// Build tools' output caches.
|
|
193
160
|
buildCaches: "safe",
|
|
194
|
-
} as const satisfies Record<
|
|
161
|
+
} as const satisfies Record<string, StorageCleanability>;
|
|
162
|
+
export type StorageCategoryId = keyof typeof STORAGE_CLEANABILITY;
|
|
163
|
+
export const StorageCategoryIdSchema = z.enum(Object.keys(STORAGE_CLEANABILITY) as StorageCategoryId[]);
|
|
195
164
|
|
|
196
165
|
export const StorageItemSchema = z.object({
|
|
197
166
|
path: z.string().describe("Where it is, as an absolute path inside the sandbox."),
|
|
@@ -245,11 +214,11 @@ export type StorageCleanInput = z.infer<typeof StorageCleanInputSchema>;
|
|
|
245
214
|
|
|
246
215
|
export const StorageCleanResultSchema = z.object({
|
|
247
216
|
category: StorageCategoryIdSchema,
|
|
248
|
-
freedBytes: z
|
|
249
|
-
removed: z.number().describe("How many items were removed."),
|
|
250
|
-
kept: z
|
|
217
|
+
freedBytes: z
|
|
251
218
|
.number()
|
|
252
|
-
.describe("
|
|
219
|
+
.describe("Space the removals gave back, in bytes. A file that is still linked elsewhere frees nothing and is not counted."),
|
|
220
|
+
removed: z.number().describe("How many items were removed."),
|
|
221
|
+
kept: z.number().describe("How many were left in place: changed too recently, in use by a running program, or no longer this category's."),
|
|
253
222
|
failed: z.number().describe("How many removals the filesystem refused."),
|
|
254
223
|
});
|
|
255
224
|
export type StorageCleanResult = z.infer<typeof StorageCleanResultSchema>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RuleSchema } from "./settings.js";
|
|
1
|
+
import { RepoChecksFileSchema, RuleSchema, SandboxSettingsSchema } from "./settings.js";
|
|
3
2
|
|
|
4
3
|
// A rule that saves cleanly and silently does nothing is the failure this schema exists to refuse: an action, and a
|
|
5
4
|
// built-in's name, are checked against the moment they are written at.
|
|
@@ -12,14 +11,49 @@ describe(`which built-in stands at which moment`, () => {
|
|
|
12
11
|
expect(rule(`turn.ending`, { kind: `builtin`, name: `version-landed` }).success).toBe(false);
|
|
13
12
|
});
|
|
14
13
|
|
|
15
|
-
test(`the
|
|
16
|
-
expect(rule(`turn.ending`, { kind: `builtin`, name: `verify-edits` }).success).toBe(true);
|
|
17
|
-
expect(rule(`agent.landed`, { kind: `builtin`, name: `verify-edits` }).success).toBe(false);
|
|
14
|
+
test(`the look stands at the turn's end and not at the landing`, () => {
|
|
15
|
+
expect(rule(`turn.ending`, { kind: `builtin`, name: `verify-ui-edits` }).success).toBe(true);
|
|
16
|
+
expect(rule(`agent.landed`, { kind: `builtin`, name: `verify-ui-edits` }).success).toBe(false);
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
test(`the landed moment takes no verdict
|
|
19
|
+
test(`the landed moment takes no verdict and no command: the work is already in the tree`, () => {
|
|
21
20
|
expect(rule(`agent.landed`, { kind: `verdict`, verdict: `allow` }).success).toBe(false);
|
|
22
21
|
expect(rule(`agent.landed`, { kind: `command`, command: `pnpm test` }).success).toBe(false);
|
|
23
|
-
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test(`there is no push moment: the repository's own pre-push hook gates a push`, () => {
|
|
25
|
+
expect(rule(`push.starting`, { kind: `command`, command: `pnpm test` }).success).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe(`where a command lives`, () => {
|
|
30
|
+
test(`settings refuse a command rule, since it belongs to the repository it checks`, () => {
|
|
31
|
+
const command = { id: `lint`, label: `lint`, moment: `file.edited`, action: { kind: `command`, command: `oxlint {file}` } };
|
|
32
|
+
expect(SandboxSettingsSchema.safeParse({ rules: [command] }).success).toBe(false);
|
|
33
|
+
const land = { id: `auto-land`, label: `land`, moment: `agent.finished`, action: { kind: `verdict`, verdict: `allow` } };
|
|
34
|
+
expect(SandboxSettingsSchema.safeParse({ rules: [land] }).success).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test(`a repository declares edit, turn and land checks, and no push check`, () => {
|
|
38
|
+
const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
|
|
39
|
+
expect(
|
|
40
|
+
file([
|
|
41
|
+
{ when: `edit`, run: `lint {file}` },
|
|
42
|
+
{ when: `turn`, run: `pnpm verify:turn` },
|
|
43
|
+
{ when: `land`, run: `pnpm verify` },
|
|
44
|
+
]),
|
|
45
|
+
).toBe(true);
|
|
46
|
+
expect(file([{ when: `push`, run: `pnpm verify:push` }])).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test(`a land check is one per repository and narrows by no path`, () => {
|
|
50
|
+
const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
|
|
51
|
+
expect(
|
|
52
|
+
file([
|
|
53
|
+
{ when: `land`, run: `pnpm verify` },
|
|
54
|
+
{ when: `land`, run: `pnpm test` },
|
|
55
|
+
]),
|
|
56
|
+
).toBe(false);
|
|
57
|
+
expect(file([{ when: `land`, run: `pnpm verify`, paths: [`src/**`] }])).toBe(false);
|
|
24
58
|
});
|
|
25
59
|
});
|
package/src/schemas/settings.ts
CHANGED
|
@@ -13,39 +13,28 @@ export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
|
|
|
13
13
|
export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
|
|
14
14
|
// Excludes "custom": there is nothing to fetch, it's whatever the owner already typed into the settings field.
|
|
15
15
|
export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
|
|
16
|
-
// Rules: "at this moment, if this is true, do this"
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
// Five places the daemon already stops to decide something; this names those decisions rather than inventing new ones.
|
|
16
|
+
// Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version) and switch the
|
|
17
|
+
// daemon's built-in reviews; every command a moment runs is a repository's own check (`<repo>/.intentic/checks.json`),
|
|
18
|
+
// compiled into this same table by the daemon, so no command lives in settings.
|
|
21
19
|
export const RuleMomentSchema = z.enum([
|
|
22
20
|
// A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
|
|
23
21
|
"file.edited",
|
|
24
22
|
// The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
|
|
25
23
|
"turn.ending",
|
|
26
|
-
// Code is about to leave the machine. A rule here gates the push on its own exit code.
|
|
27
|
-
"push.starting",
|
|
28
24
|
// An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
|
|
29
25
|
"agent.finished",
|
|
30
26
|
// An agent's delta has just reached the main tree. A rule here decides what becomes of it there.
|
|
31
27
|
"agent.landed",
|
|
32
28
|
]);
|
|
33
29
|
export type RuleMoment = z.infer<typeof RuleMomentSchema>;
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// verdict: allows or holds what is about to happen (a pass with nothing to run, told which way to go).
|
|
38
|
-
// builtin: invokes a named daemon behaviour the table has no business expressing itself.
|
|
39
|
-
// Each reads a record only the daemon keeps, which is what makes it a built-in rather than a command.
|
|
40
|
-
// verify-edits: what a turn edited, against what it ran.
|
|
41
|
-
// verify-removals: what a turn deleted, against the repo's own history (a `git log` question, not a shell one-liner).
|
|
30
|
+
// command: a repository's check; its exit code is the verdict.
|
|
31
|
+
// verdict: allows or holds what is about to happen.
|
|
32
|
+
// builtin: a daemon behaviour that reads a record only the daemon keeps.
|
|
42
33
|
// verify-ui-edits: rendered surfaces a turn changed, against whether it ever looked at one.
|
|
43
|
-
// verify-tests: whether a touched test's assertions got weaker than at HEAD, or would have passed before its own
|
|
44
|
-
// change.
|
|
45
34
|
// version-landed: commits what a land brought, in the main tree, under the subject drafted for it, and the tree's own
|
|
46
35
|
// remainder before the next isolated turn starts; the only way a person who never commits keeps every agent seeing
|
|
47
36
|
// the latest tree, since worktrees are cut from HEAD.
|
|
48
|
-
export const RuleBuiltinSchema = z.enum(["verify-
|
|
37
|
+
export const RuleBuiltinSchema = z.enum(["verify-ui-edits", "version-landed"]);
|
|
49
38
|
export type RuleBuiltin = z.infer<typeof RuleBuiltinSchema>;
|
|
50
39
|
export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
51
40
|
z.object({
|
|
@@ -54,7 +43,6 @@ export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
|
54
43
|
// Past this, the process group is killed and the run is `failed`, never a silent pass.
|
|
55
44
|
timeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
|
|
56
45
|
}),
|
|
57
|
-
z.object({ kind: z.literal("instruct"), text: z.string().min(1).max(4000) }),
|
|
58
46
|
z.object({ kind: z.literal("verdict"), verdict: z.enum(["allow", "hold"]) }),
|
|
59
47
|
z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
|
|
60
48
|
]);
|
|
@@ -63,8 +51,7 @@ export type RuleAction = z.infer<typeof RuleActionSchema>;
|
|
|
63
51
|
// defaults to HELD unless a rule explicitly allows it.
|
|
64
52
|
export const RuleOutcomeSchema = z.enum(["clean", "error", "conflict", "checks-failed"]);
|
|
65
53
|
export type RuleOutcome = z.infer<typeof RuleOutcomeSchema>;
|
|
66
|
-
//
|
|
67
|
-
// the rule always matches.
|
|
54
|
+
// Every key absent means the rule always matches.
|
|
68
55
|
export const RuleConditionSchema = z.object({
|
|
69
56
|
// A workspace repo id, or "root". Absent ⇒ any.
|
|
70
57
|
repo: z.string().min(1).optional(),
|
|
@@ -80,15 +67,13 @@ export type RuleCondition = z.infer<typeof RuleConditionSchema>;
|
|
|
80
67
|
// validated here, not left to the consumer — the alternative is a rule that saves cleanly and silently does nothing.
|
|
81
68
|
const MOMENT_ACTIONS: Record<RuleMoment, readonly RuleAction["kind"][]> = {
|
|
82
69
|
"file.edited": ["command"],
|
|
83
|
-
"turn.ending": ["builtin", "
|
|
84
|
-
"push.starting": ["command"],
|
|
70
|
+
"turn.ending": ["builtin", "command"],
|
|
85
71
|
"agent.finished": ["verdict"],
|
|
86
72
|
"agent.landed": ["builtin"],
|
|
87
73
|
};
|
|
88
|
-
// A built-in reads a record only one moment keeps, so it stands at that moment alone
|
|
89
|
-
// ledgers, the versioner reads a landing's claim.
|
|
74
|
+
// A built-in reads a record only one moment keeps, so it stands at that moment alone.
|
|
90
75
|
const MOMENT_BUILTINS: Partial<Record<RuleMoment, readonly RuleBuiltin[]>> = {
|
|
91
|
-
"turn.ending": ["verify-
|
|
76
|
+
"turn.ending": ["verify-ui-edits"],
|
|
92
77
|
"agent.landed": ["version-landed"],
|
|
93
78
|
};
|
|
94
79
|
export const RuleSchema = z
|
|
@@ -429,14 +414,17 @@ export const SandboxSettingsSchema = z.object({
|
|
|
429
414
|
.describe(
|
|
430
415
|
"Which repositories may run the checks they declare for themselves, and exactly which version of those checks you agreed to. A repository's declaration does nothing until it appears here, the same rule git keeps for hooks, which are never cloned; and a declaration that changes afterwards is held until you look at it again.",
|
|
431
416
|
),
|
|
432
|
-
//
|
|
433
|
-
//
|
|
417
|
+
// The owner's alone, since a rule can hold work. A command is refused here: it belongs to the repository it checks,
|
|
418
|
+
// in that repository's `.intentic/checks.json`, never in settings.
|
|
434
419
|
rules: z
|
|
435
420
|
.array(RuleSchema)
|
|
436
421
|
.max(50)
|
|
437
422
|
.default([])
|
|
423
|
+
.refine((rules) => rules.every((rule) => rule.action.kind !== "command"), {
|
|
424
|
+
message: "a command belongs in the repository's own .intentic/checks.json, not in settings",
|
|
425
|
+
})
|
|
438
426
|
.describe(
|
|
439
|
-
"Standing
|
|
427
|
+
"Standing decisions about the sandbox's own work: land or hold finished work, save a version of what landed, ask the assistant to look at an interface it changed. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment. A command to run is a repository's own check, declared in its .intentic/checks.json.",
|
|
440
428
|
),
|
|
441
429
|
automationFailureLimit: z
|
|
442
430
|
.number()
|
|
@@ -608,27 +596,23 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
|
608
596
|
// spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
|
|
609
597
|
export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
|
|
610
598
|
|
|
611
|
-
// Named for the occasion as a repository would say it
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
// that declares a whole-tree command here pays it per edit, so the command has to take the file.
|
|
618
|
-
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "push"]);
|
|
599
|
+
// Named for the occasion as a repository would say it: `edit` is `file.edited`, `turn` is `turn.ending`, and `land` is
|
|
600
|
+
// the daemon's run over the main tree after an install a land caused (rules/repo-checks.ts maps them). A push is gated by
|
|
601
|
+
// the repository's own git pre-push hook, which every push runs, the app's included.
|
|
602
|
+
// `edit` runs on one file (`{file}`) and its output rides back in the edit's own response, so a command declared there is
|
|
603
|
+
// paid per edit and has to take the file.
|
|
604
|
+
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
|
|
619
605
|
export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
|
|
620
606
|
|
|
621
607
|
export const RepoCheckSchema = z.object({
|
|
622
608
|
when: RepoCheckMomentSchema.describe(
|
|
623
|
-
"When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `
|
|
609
|
+
"When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `land` on the main tree after finished work lands (absent, the package's own verify or test script runs there).",
|
|
624
610
|
),
|
|
625
611
|
run: z.string().min(1).max(500).describe("The command, run in this repository's own directory, so it reads as it would in a terminal there."),
|
|
626
612
|
label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
|
|
627
|
-
// Same ceiling as a rule's own command; past it the process group is killed and the run is a failure
|
|
628
|
-
// silent pass.
|
|
613
|
+
// Same ceiling as a rule's own command; past it the process group is killed and the run is a failure.
|
|
629
614
|
timeoutMs: z.number().min(60_000).max(3_600_000).optional().describe("How long it may take before it is killed and counted as failed."),
|
|
630
|
-
// Repo-relative
|
|
631
|
-
// since a rule's globs are workspace-relative.
|
|
615
|
+
// Repo-relative; the daemon prefixes the repo id before matching, since a rule's globs are workspace-relative.
|
|
632
616
|
paths: z
|
|
633
617
|
.array(z.string().min(1))
|
|
634
618
|
.max(20)
|
|
@@ -638,7 +622,16 @@ export const RepoCheckSchema = z.object({
|
|
|
638
622
|
export type RepoCheck = z.infer<typeof RepoCheckSchema>;
|
|
639
623
|
|
|
640
624
|
// The file itself. One key, so a second concern can be added later without breaking a file anyone has written.
|
|
641
|
-
export const RepoChecksFileSchema = z.object({
|
|
625
|
+
export const RepoChecksFileSchema = z.object({
|
|
626
|
+
checks: z
|
|
627
|
+
.array(RepoCheckSchema)
|
|
628
|
+
.max(10)
|
|
629
|
+
.default([])
|
|
630
|
+
// One land check per repository: it is THE verdict on the main tree the push hook replays, not one of several.
|
|
631
|
+
.refine((checks) => checks.filter((check) => check.when === "land").length <= 1, { message: "a repository declares at most one land check" })
|
|
632
|
+
// A land checks the whole tree an install settled, not a change, so there is nothing for a glob to narrow.
|
|
633
|
+
.refine((checks) => checks.every((check) => check.when !== "land" || check.paths === undefined), { message: "a land check takes no paths" }),
|
|
634
|
+
});
|
|
642
635
|
export type RepoChecksFile = z.infer<typeof RepoChecksFileSchema>;
|
|
643
636
|
|
|
644
637
|
// One repository, as a screen reads it: what it declares, and where that stands with the owner.
|
|
@@ -646,6 +639,11 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
646
639
|
repo: z.string().describe('Which repository, by its workspace id ("root" is the workspace itself).'),
|
|
647
640
|
path: z.string().describe("Where the declaration lives, relative to the workspace, whether or not the file exists yet."),
|
|
648
641
|
checks: z.array(RepoCheckSchema).describe("What it declares, in the order the file lists them."),
|
|
642
|
+
fired: z
|
|
643
|
+
.array(z.number().nullable())
|
|
644
|
+
.describe(
|
|
645
|
+
"When each declared check last reported something, in the file's order, as epoch milliseconds; null for one that never has, or for the land check, whose verdict the activity feed records instead.",
|
|
646
|
+
),
|
|
649
647
|
adopted: z
|
|
650
648
|
.boolean()
|
|
651
649
|
.describe("Whether these are running. False means declared and inert: nothing a repository writes runs until the owner switches it on."),
|
|
@@ -655,10 +653,16 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
655
653
|
"Whether the declaration changed since it was adopted, which holds it until the owner looks again. True only for a repository that was adopted before.",
|
|
656
654
|
),
|
|
657
655
|
error: z.string().optional().describe("Why the file could not be read, when it exists but does not parse. The checks list is empty in that case."),
|
|
656
|
+
landDefault: z
|
|
657
|
+
.string()
|
|
658
|
+
.optional()
|
|
659
|
+
.describe(
|
|
660
|
+
"What runs on the main tree after a land when the file declares no `land` check: the package's own verify or test script. Absent when the repository has neither.",
|
|
661
|
+
),
|
|
658
662
|
});
|
|
659
663
|
export type RepoChecksSummary = z.infer<typeof RepoChecksSummarySchema>;
|
|
660
664
|
export const RepoChecksListSchema = z.object({
|
|
661
|
-
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks
|
|
665
|
+
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks or has a package check that runs after a land, in id order."),
|
|
662
666
|
});
|
|
663
667
|
export type RepoChecksList = z.infer<typeof RepoChecksListSchema>;
|
|
664
668
|
export const RepoChecksAdoptSchema = z.object({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { packageRoot } from "@intentic/constants/node";
|
|
4
|
-
import { test, expect } from "bun:test";
|
|
5
4
|
import { currentLock } from "./contract-lock.js";
|
|
6
5
|
|
|
7
6
|
// contract.lock.json must match the exported schemas; the git-aware half of this check lives in contract-shrink.mjs.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { STATE_DIR } from "@intentic/constants";
|
|
2
2
|
import type { FileContribution } from "@intentic/extension-manifest";
|
|
3
|
-
import { describe, it, expect } from "bun:test";
|
|
4
3
|
import {
|
|
5
4
|
BACKED_UP_STATE_PATHS,
|
|
6
5
|
isLockedWorkspacePath,
|
|
@@ -346,8 +345,6 @@ describe(`VERSIONED_STATE_PATHS`, () => {
|
|
|
346
345
|
// Which commands count as heavy, and how many may run at once; a shared-box decision worth a `git log`
|
|
347
346
|
// line.
|
|
348
347
|
`${STATE_DIR}/config/heavy-commands.json`,
|
|
349
|
-
// The exclude list carves entries out by name; an unlisted directory is invisible to git and to land.
|
|
350
|
-
`${STATE_DIR}/config/hooks/`,
|
|
351
348
|
`${STATE_DIR}/config/loop-designs.json`,
|
|
352
349
|
`${STATE_DIR}/config/personas.json`,
|
|
353
350
|
// A persona's prompt and reachable skills: the text that decides how it behaves, same reason as its card.
|
|
@@ -120,20 +120,6 @@ const STATE_FILES = [
|
|
|
120
120
|
// Bash command. `carry`: a property of the workspace, not the machine. `versioned`: raising the limit is a decision
|
|
121
121
|
// worth a diff.
|
|
122
122
|
{ path: ".intentic/config/heavy-commands.json", invalidates: ["settings"], portability: "carry", versioned: true },
|
|
123
|
-
// Scripts the rule table runs, one per reader (settings.json points at them per event, e.g. `file.edited`).
|
|
124
|
-
// `versioned`: this is code that runs against every file an agent writes, more consequential than the rule naming
|
|
125
|
-
// it. `carry`: authored text, no credential. Invalidates nothing: the settings screen renders the rules from
|
|
126
|
-
// settings.json, not these scripts.
|
|
127
|
-
{
|
|
128
|
-
path: ".intentic/config/hooks/",
|
|
129
|
-
invalidates: [],
|
|
130
|
-
why: "The settings screen renders the rules that name these scripts, out of settings.json; nothing in the browser reads the scripts themselves.",
|
|
131
|
-
portability: "carry",
|
|
132
|
-
versioned: true,
|
|
133
|
-
// The daemon only runs these scripts by path; it never builds them, so there's no `statePath` call for the
|
|
134
|
-
// coverage guard to find.
|
|
135
|
-
outsideWriter: "the owner or an agent, authoring them; the daemon only ever RUNS one, by the path a rule's command names",
|
|
136
|
-
},
|
|
137
123
|
// Last-fired stamps for the rule table. `derived`: records what happened on this machine, not carried.
|
|
138
124
|
{
|
|
139
125
|
path: ".intentic/local/rule-firings.json",
|
package/src/text/embed.test.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { webcrypto } from "node:crypto";
|
|
2
|
-
import { test, expect, beforeAll, mock } from "bun:test";
|
|
3
2
|
import { stubGlobal, unstubAllGlobals } from "@intentic/testing/bun";
|
|
4
3
|
import { EmbedError, embedFailure, embedUrl, fetchEmbedJson, solveProofOfWork } from "./embed.js";
|
|
5
4
|
|
|
@@ -40,12 +39,12 @@ test("a refusal carries the server's own sentence and status, and a bodyless one
|
|
|
40
39
|
test("a JSON fetch answers the body or throws the refusal", async () => {
|
|
41
40
|
stubGlobal(
|
|
42
41
|
"fetch",
|
|
43
|
-
|
|
42
|
+
jest.fn(async () => new Response(JSON.stringify({ ok: true }), { status: 200 })),
|
|
44
43
|
);
|
|
45
44
|
expect(await fetchEmbedJson<{ ok: boolean }>("https://x/y")).toEqual({ ok: true });
|
|
46
45
|
stubGlobal(
|
|
47
46
|
"fetch",
|
|
48
|
-
|
|
47
|
+
jest.fn(async () => new Response(JSON.stringify({ error: "rate limited" }), { status: 429 })),
|
|
49
48
|
);
|
|
50
49
|
await expect(fetchEmbedJson("https://x/y")).rejects.toMatchObject({ message: "rate limited", status: 429 });
|
|
51
50
|
unstubAllGlobals();
|
package/src/text/emoji.test.ts
CHANGED
package/src/text/title.test.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { STATE_DIR } from "@intentic/constants";
|
|
2
|
-
import { describe, it, expect } from "bun:test";
|
|
3
2
|
import type { AgentEvent } from "../events/agent-events.js";
|
|
4
3
|
import type { TranscriptPatch, TranscriptRow } from "../events/transcript.js";
|
|
5
4
|
import { watchWakePrompt } from "../events/watch-wake.js";
|
|
@@ -213,9 +212,19 @@ describe("foldTurn", () => {
|
|
|
213
212
|
});
|
|
214
213
|
// An outage nobody armed says so, instead of promising the breaker's retry.
|
|
215
214
|
const unarmed: AgentEvent[] = [
|
|
216
|
-
{
|
|
215
|
+
{
|
|
216
|
+
kind: "error",
|
|
217
|
+
code: "provider-outage",
|
|
218
|
+
message: "Anthropic is down.",
|
|
219
|
+
autoResume: "available",
|
|
220
|
+
outage: { retryAt: 1 },
|
|
221
|
+
retries: { made: 0, max: 6 },
|
|
222
|
+
},
|
|
217
223
|
];
|
|
218
|
-
expect(foldOf("hi", unarmed).at(-1)).toEqual({
|
|
224
|
+
expect(foldOf("hi", unarmed).at(-1)).toEqual({
|
|
225
|
+
role: "notice",
|
|
226
|
+
text: "Anthropic is down. Nothing is retrying it, so the turn is waiting here.",
|
|
227
|
+
});
|
|
219
228
|
});
|
|
220
229
|
|
|
221
230
|
it("writes the turn's own events down as notices", () => {
|
package/src/text/whisper.test.ts
CHANGED
package/src/time/zone.test.ts
CHANGED