@intentic/sandbox-contract 1.322.0 → 1.324.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 +7 -0
- package/dist/contracts/agent.contract.d.ts +109 -6
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +13 -3
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +124 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +31 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -1
- package/dist/contracts/approvals.contract.js +2 -2
- package/dist/contracts/approvals.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +2 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/loops.contract.d.ts +2 -0
- package/dist/contracts/loops.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +4 -0
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.d.ts +187 -0
- package/dist/contracts/privacy.contract.d.ts.map +1 -0
- package/dist/contracts/privacy.contract.js +76 -0
- package/dist/contracts/privacy.contract.js.map +1 -0
- package/dist/contracts/runner.contract.d.ts +167 -122
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +11 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +74 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +90 -32
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +11 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/usage.contract.d.ts +1 -0
- package/dist/contracts/usage.contract.d.ts.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +3 -0
- package/dist/contracts/workflows.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +53 -1
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +6 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-ui.d.ts +33 -0
- package/dist/events/agent-ui.d.ts.map +1 -0
- package/dist/events/agent-ui.js +24 -0
- package/dist/events/agent-ui.js.map +1 -0
- package/dist/events/requests.d.ts +3 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +4 -0
- package/dist/events/requests.js.map +1 -1
- package/dist/events/resume.d.ts +1 -0
- package/dist/events/resume.d.ts.map +1 -1
- package/dist/events/resume.js +7 -1
- package/dist/events/resume.js.map +1 -1
- package/dist/events/sandbox-notice.d.ts +2 -0
- package/dist/events/sandbox-notice.d.ts.map +1 -1
- package/dist/events/sandbox-notice.js +1 -1
- package/dist/events/sandbox-notice.js.map +1 -1
- package/dist/events/system-events.d.ts +15 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +1 -0
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +70 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +44 -14
- package/dist/events/transcript.js.map +1 -1
- package/dist/front/generated/browser-wire.d.ts +4 -0
- package/dist/front/generated/browser-wire.d.ts.map +1 -1
- package/dist/index.d.ts +640 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts +1 -0
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +6 -0
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/policy/request-status.d.ts.map +1 -1
- package/dist/policy/request-status.js +16 -7
- package/dist/policy/request-status.js.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-routes.d.ts +12 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +3 -0
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +97 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +20 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +18 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +9 -2
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +102 -0
- package/dist/schemas/approvals.d.ts.map +1 -1
- package/dist/schemas/approvals.js +45 -4
- package/dist/schemas/approvals.js.map +1 -1
- package/dist/schemas/automations.d.ts +6 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +2 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +10 -1
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/logs.d.ts +1 -0
- package/dist/schemas/logs.d.ts.map +1 -1
- package/dist/schemas/logs.js +4 -0
- package/dist/schemas/logs.js.map +1 -1
- package/dist/schemas/loops.d.ts +3 -0
- package/dist/schemas/loops.d.ts.map +1 -1
- package/dist/schemas/loops.js +1 -1
- package/dist/schemas/loops.js.map +1 -1
- package/dist/schemas/needs.d.ts +6 -0
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +13 -3
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/platform-link.d.ts +50 -0
- package/dist/schemas/platform-link.d.ts.map +1 -0
- package/dist/schemas/platform-link.js +19 -0
- package/dist/schemas/platform-link.js.map +1 -0
- package/dist/schemas/privacy.d.ts +277 -0
- package/dist/schemas/privacy.d.ts.map +1 -0
- package/dist/schemas/privacy.js +127 -0
- package/dist/schemas/privacy.js.map +1 -0
- package/dist/schemas/providers/plan-limits.d.ts +8 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +25 -4
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +4 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +5 -2
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/push.d.ts +1 -0
- package/dist/schemas/push.d.ts.map +1 -1
- package/dist/schemas/push.js +4 -0
- package/dist/schemas/push.js.map +1 -1
- package/dist/schemas/settings.d.ts +80 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +14 -3
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/system.d.ts +17 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +2 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/updates.d.ts +30 -0
- package/dist/schemas/updates.d.ts.map +1 -1
- package/dist/schemas/updates.js +37 -0
- package/dist/schemas/updates.js.map +1 -1
- package/dist/schemas/workflows.d.ts +5 -0
- package/dist/schemas/workflows.d.ts.map +1 -1
- package/dist/state/definition.d.ts +10 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +1 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +3 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +17 -1
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +20 -1
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +9 -0
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agent.contract.ts +16 -3
- package/src/contracts/approvals.contract.ts +2 -2
- package/src/contracts/privacy.contract.ts +95 -0
- package/src/contracts/system.contract.ts +14 -1
- package/src/events/agent-events.ts +11 -0
- package/src/events/agent-ui.ts +40 -0
- package/src/events/requests.ts +6 -0
- package/src/events/resume.test.ts +13 -0
- package/src/events/resume.ts +11 -2
- package/src/events/sandbox-notice.ts +1 -1
- package/src/events/system-events.ts +2 -0
- package/src/events/transcript.ts +84 -20
- package/src/front/generated/browser-wire.json +27 -0
- package/src/front/generated/browser-wire.ts +1 -1
- package/src/index.ts +6 -0
- package/src/models/agent-catalog.test.ts +1 -0
- package/src/models/agent-runtimes.ts +17 -0
- package/src/models/capability-ledger.test.ts +3 -0
- package/src/policy/request-status.ts +19 -9
- package/src/policy/turned-away.ts +2 -0
- package/src/protocol/raw-routes.ts +9 -0
- package/src/schemas/agent.ts +36 -4
- package/src/schemas/agents.ts +17 -4
- package/src/schemas/approvals.ts +59 -4
- package/src/schemas/automations.ts +4 -4
- package/src/schemas/capabilities.ts +15 -2
- package/src/schemas/ci.ts +1 -1
- package/src/schemas/git/git.ts +1 -0
- package/src/schemas/logs.ts +6 -0
- package/src/schemas/loops.ts +2 -1
- package/src/schemas/needs.ts +18 -3
- package/src/schemas/platform-link.ts +42 -0
- package/src/schemas/privacy.ts +204 -0
- package/src/schemas/providers/plan-limits.ts +38 -5
- package/src/schemas/providers/usage.ts +13 -2
- package/src/schemas/push.ts +7 -0
- package/src/schemas/settings-history.ts +3 -3
- package/src/schemas/settings.test.ts +5 -5
- package/src/schemas/settings.ts +47 -19
- package/src/schemas/system.ts +6 -1
- package/src/schemas/updates.ts +71 -0
- package/src/state/history-state.ts +3 -0
- package/src/state/runtime-state.ts +4 -0
- package/src/state/workspace-state.test.ts +8 -2
- package/src/state/workspace-state.ts +32 -2
- package/src/text/transcript-fold.test.ts +15 -0
- package/src/text/transcript-fold.ts +10 -0
|
@@ -48,7 +48,7 @@ describe(`where a command lives`, () => {
|
|
|
48
48
|
expect(file([{ when: `push`, run: `pnpm verify:push` }])).toBe(false);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// `land` is retired
|
|
51
|
+
// `land` is retired: it runs nothing, so a file written while it ran reads however it spelled one.
|
|
52
52
|
test(`a retired land check still reads, however many a file names and whatever paths it gives`, () => {
|
|
53
53
|
const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
|
|
54
54
|
expect(
|
|
@@ -61,9 +61,9 @@ describe(`where a command lives`, () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
// save may stand a new one there.
|
|
66
|
-
describe(`the
|
|
64
|
+
// Only the checks a repository declares run when a turn ends, and the daemon adds them to a turn's rules without saving
|
|
65
|
+
// them. An owner's rule there from before still reads, its conversion drops it, and no save may stand a new one there.
|
|
66
|
+
describe(`an owner's rule at the turn.ending moment`, () => {
|
|
67
67
|
const look = { id: `verify-ui-edits`, label: `Look at what changed`, moment: `turn.ending`, action: { kind: `builtin`, name: `verify-ui-edits` } };
|
|
68
68
|
const land = { id: `auto-land`, label: `land`, moment: `agent.finished`, action: { kind: `verdict`, verdict: `allow` } };
|
|
69
69
|
|
|
@@ -74,7 +74,7 @@ describe(`the retired turn.ending moment`, () => {
|
|
|
74
74
|
test(`is refused on a save`, () => {
|
|
75
75
|
const saved = SandboxSettingsWriteSchema.safeParse(SandboxSettingsSchema.parse({ rules: [look, land] }));
|
|
76
76
|
expect(saved.error?.issues.map(({ message, path }) => ({ message, path }))).toEqual([
|
|
77
|
-
{ message: `turn.ending
|
|
77
|
+
{ message: `turn.ending runs only the checks a repository declares in its own .intentic/checks.json, so a rule cannot stand there`, path: [`rules`] },
|
|
78
78
|
]);
|
|
79
79
|
expect(SandboxSettingsWriteSchema.safeParse(SandboxSettingsSchema.parse({ rules: [land] })).success).toBe(true);
|
|
80
80
|
});
|
package/src/schemas/settings.ts
CHANGED
|
@@ -20,13 +20,15 @@ export const SYSTEM_PROMPT_MAX = 20_000;
|
|
|
20
20
|
export const AUTO_MODEL_GUIDANCE_MAX = 2000;
|
|
21
21
|
// Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version); every command a
|
|
22
22
|
// moment runs is a repository's own check (`<repo>/.intentic/checks.json`), compiled into this same table by the daemon,
|
|
23
|
-
// so no command lives in settings. Nothing verifies
|
|
24
|
-
// the
|
|
23
|
+
// so no command lives in settings. Nothing verifies after a land or on the way to a push, and nothing here holds
|
|
24
|
+
// anything: what a check finds inside a turn is said back to the model, and CI checks what the owner pushes
|
|
25
|
+
// (schemas/ci.ts).
|
|
25
26
|
export const RuleMomentSchema = z.enum([
|
|
26
27
|
// A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
|
|
27
28
|
"file.edited",
|
|
28
|
-
//
|
|
29
|
-
//
|
|
29
|
+
// An isolated turn about to stop: a repository's own `turn` check runs here once, on what the turn changed, and what
|
|
30
|
+
// it finds is said back to the model, which may fix it or say why not. It holds and refuses nothing. Only what a
|
|
31
|
+
// repository declares stands here; an owner's rule left here from before is inert, and no save stands a new one.
|
|
30
32
|
"turn.ending",
|
|
31
33
|
// An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
|
|
32
34
|
"agent.finished",
|
|
@@ -55,8 +57,8 @@ export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
|
55
57
|
z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
|
|
56
58
|
]);
|
|
57
59
|
export type RuleAction = z.infer<typeof RuleActionSchema>;
|
|
58
|
-
// `checks-failed` was a clean turn whose `turn.ending` check failed. Nothing produces it now
|
|
59
|
-
//
|
|
60
|
+
// `checks-failed` was a clean turn whose `turn.ending` check failed. Nothing produces it now: a `turn` check's findings
|
|
61
|
+
// are said back to the model and never decide whether its work lands. It stays so older rules naming it still read.
|
|
60
62
|
export const RuleOutcomeSchema = z.enum(["clean", "error", "conflict", "checks-failed"]);
|
|
61
63
|
export type RuleOutcome = z.infer<typeof RuleOutcomeSchema>;
|
|
62
64
|
// Every key absent means the rule always matches.
|
|
@@ -68,7 +70,8 @@ export const RuleConditionSchema = z.object({
|
|
|
68
70
|
// How the turn ended. Absent/empty ⇒ any.
|
|
69
71
|
outcome: z.array(RuleOutcomeSchema).optional(),
|
|
70
72
|
// The fraction of occasions a rule fires on, at a moment that draws one; absent ⇒ every occasion. No moment draws
|
|
71
|
-
//
|
|
73
|
+
// one any more (`turn.ending` did, before it held only the checks a repository declares), so a sampled rule fires
|
|
74
|
+
// every time.
|
|
72
75
|
sample: z.number().gt(0).lt(1).optional(),
|
|
73
76
|
});
|
|
74
77
|
export type RuleCondition = z.infer<typeof RuleConditionSchema>;
|
|
@@ -103,7 +106,8 @@ export const RuleSchema = z
|
|
|
103
106
|
path: ["action"],
|
|
104
107
|
});
|
|
105
108
|
export type Rule = z.infer<typeof RuleSchema>;
|
|
106
|
-
// A rule
|
|
109
|
+
// A rule the owner's settings no longer take: one at `turn.ending`, where only a repository's own checks stand, or one
|
|
110
|
+
// with the retired `verify-ui-edits` built-in. Read, so an older file parses, but never written again.
|
|
107
111
|
export const isRetiredMomentRule = (rule: Pick<Rule, "moment" | "action">): boolean =>
|
|
108
112
|
rule.moment === "turn.ending" || (rule.action.kind === "builtin" && rule.action.name === "verify-ui-edits");
|
|
109
113
|
// Kept out of the settings object on purpose: a firing is not an edit, and writing config on every push would make
|
|
@@ -314,6 +318,23 @@ export const SandboxSettingsSchema = z.object({
|
|
|
314
318
|
.describe(
|
|
315
319
|
"What share of conversations to keep on the long form, so the two can be compared. Whole conversations rather than individual turns, because the guidance sits in the prompt for the whole session.",
|
|
316
320
|
),
|
|
321
|
+
// Measured by the `clearing` experiment: the gateway in front of a native Claude turn replaces old tool results with
|
|
322
|
+
// a placeholder, a chunk at a time, so the cached prompt breaks once per chunk rather than on every call
|
|
323
|
+
// (privacy/gateway/tool-result-clearing.ts). Off by default until that experiment says it pays.
|
|
324
|
+
toolResultClearing: z
|
|
325
|
+
.boolean()
|
|
326
|
+
.default(false)
|
|
327
|
+
.describe(
|
|
328
|
+
"Replace old tool results from a Claude conversation once it grows long, a large chunk at a time, keeping the most recent ones whole. The agent sees a short placeholder where each one was and can run the tool again. Smaller prompts cost less and answer sooner, at the risk of the agent re-reading what it already saw.",
|
|
329
|
+
),
|
|
330
|
+
toolResultClearingHoldout: z
|
|
331
|
+
.number()
|
|
332
|
+
.min(0)
|
|
333
|
+
.max(1)
|
|
334
|
+
.default(0.5)
|
|
335
|
+
.describe(
|
|
336
|
+
"What share of conversations keep every tool result, so the two can be compared. Whole conversations rather than individual turns, because what was dropped stays dropped for the rest of the session.",
|
|
337
|
+
),
|
|
317
338
|
iqSearch: z
|
|
318
339
|
.boolean()
|
|
319
340
|
.default(false)
|
|
@@ -557,10 +578,11 @@ export const SandboxSettingsSchema = z.object({
|
|
|
557
578
|
.describe("How many levels deep the delegation may go, since a subagent can start subagents of its own."),
|
|
558
579
|
});
|
|
559
580
|
export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
|
|
560
|
-
// What a save takes: the settings as read, less what is only read. `turn.ending`
|
|
561
|
-
//
|
|
581
|
+
// What a save takes: the settings as read, less what is only read. `turn.ending` holds only the checks a repository
|
|
582
|
+
// declares, which the daemon adds to a turn's rules and never saves, and the `verify-ui-edits` built-in is retired; a
|
|
583
|
+
// file written before still parses (and its conversion drops such rules), but no save may stand a new one.
|
|
562
584
|
export const SandboxSettingsWriteSchema = SandboxSettingsSchema.refine((settings) => !settings.rules.some(isRetiredMomentRule), {
|
|
563
|
-
message: "turn.ending
|
|
585
|
+
message: "turn.ending runs only the checks a repository declares in its own .intentic/checks.json, so a rule cannot stand there",
|
|
564
586
|
path: ["rules"],
|
|
565
587
|
});
|
|
566
588
|
|
|
@@ -632,9 +654,13 @@ export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() }
|
|
|
632
654
|
// openingListings: directory listings a turn ran to orient itself (the project map).
|
|
633
655
|
// callsBeforeTarget: how far a turn walked before touching a file it went on to edit.
|
|
634
656
|
// failedCalls: tool calls that ended in error (the field notes, whose largest section is a failure taxonomy).
|
|
635
|
-
//
|
|
657
|
+
// roundTrips: model calls a turn took (the guidance form, whose batching and context-reuse paragraphs exist to save them).
|
|
658
|
+
// contextPerCall: prompt tokens per model call, cached or not (tool-result clearing, which exists to shrink it).
|
|
659
|
+
// Never cost: each mechanism moves one small part of a turn's work, inside the noise of the rest. Round trips are the
|
|
660
|
+
// nearest a reading comes to it, and still a count of calls, not of tokens or money; the prompt a call carries is the
|
|
661
|
+
// one exception, for the one mechanism that changes nothing else.
|
|
636
662
|
export const TurnMetricReadingSchema = z.object({
|
|
637
|
-
metric: z.enum(["searchCalls", "openingSearches", "openingListings", "callsBeforeTarget", "failedCalls"]),
|
|
663
|
+
metric: z.enum(["searchCalls", "openingSearches", "openingListings", "callsBeforeTarget", "failedCalls", "roundTrips", "contextPerCall"]),
|
|
638
664
|
on: SavingsArmSchema,
|
|
639
665
|
off: SavingsArmSchema,
|
|
640
666
|
// Additional control turns to reach a fixed target resolution, not today's delta (which inherits noise and always
|
|
@@ -698,6 +724,7 @@ export const SavingsReportSchema = z.object({
|
|
|
698
724
|
map: TurnExperimentSchema.optional(),
|
|
699
725
|
notes: TurnExperimentSchema.optional(),
|
|
700
726
|
guidance: TurnExperimentSchema.optional(),
|
|
727
|
+
clearing: TurnExperimentSchema.optional(),
|
|
701
728
|
});
|
|
702
729
|
export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
703
730
|
|
|
@@ -712,17 +739,18 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
|
712
739
|
// spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
|
|
713
740
|
export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
|
|
714
741
|
|
|
715
|
-
// Named for the occasion as a repository would say it: `edit` is `file.edited`
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
//
|
|
742
|
+
// Named for the occasion as a repository would say it: `edit` is `file.edited` and `turn` is `turn.ending`
|
|
743
|
+
// (rules/repo-checks.ts maps them). `edit` runs on one file (`{file}`) and its output rides back in the edit's own
|
|
744
|
+
// response, so a command declared there is paid per edit and has to take the file. `turn` runs once when an isolated
|
|
745
|
+
// turn is about to stop, on the whole tree, and what it prints when it fails is said back to the model once, which may
|
|
746
|
+
// fix it or say why not. `land` is retired: nothing runs after work lands any more (CI checks what the owner pushes), and
|
|
747
|
+
// a declaration naming it still reads but runs nothing. No check refuses a land, a commit or a push.
|
|
720
748
|
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
|
|
721
749
|
export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
|
|
722
750
|
|
|
723
751
|
export const RepoCheckSchema = z.object({
|
|
724
752
|
when: RepoCheckMomentSchema.describe(
|
|
725
|
-
"When to run it: `edit` on each file as it is written (`{file}` is its path)
|
|
753
|
+
"When to run it: `edit` on each file as it is written (`{file}` is its path); `turn` once when an isolated turn is about to stop, for at most three minutes, with what it prints on failure said back to the model once. Neither holds or refuses anything. `land` is retired and runs nothing, since CI checks what is pushed.",
|
|
726
754
|
),
|
|
727
755
|
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."),
|
|
728
756
|
label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
|
package/src/schemas/system.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { StatePlanSchema } from "./state-plan.js";
|
|
3
|
-
import { UpdateOutcomeSchema, WithdrawnReleaseSchema } from "./updates.js";
|
|
3
|
+
import { AutoUpdateSchema, UpdateOutcomeSchema, WithdrawnReleaseSchema } from "./updates.js";
|
|
4
4
|
// version is this build's baked-in version; latest/updateAvailable come from comparing it to the published stable
|
|
5
5
|
// release.
|
|
6
6
|
// Whether a runtime can serve a turn, probed off the turn path. "unknown" reads as available-but-unverified, never as
|
|
@@ -122,6 +122,11 @@ export const InfoSchema = z.object({
|
|
|
122
122
|
.string()
|
|
123
123
|
.optional()
|
|
124
124
|
.describe("A release the owner chose to skip. While it is the newest, no update is offered; a newer one is. Absent when nothing is skipped."),
|
|
125
|
+
// Taking a downloaded update by itself at a quiet moment (system/updates/auto-update.ts); absent where the daemon
|
|
126
|
+
// cannot (a hosted machine, a checkout-built base, a daemon outside a container) and from a daemon older than it.
|
|
127
|
+
autoUpdate: AutoUpdateSchema.optional().describe(
|
|
128
|
+
"Whether and when this sandbox takes a downloaded update by itself, and what it is waiting on. Absent where it cannot: a hosted sandbox, one built from a checkout, or one older than automatic updates.",
|
|
129
|
+
),
|
|
125
130
|
});
|
|
126
131
|
export type Info = z.infer<typeof InfoSchema>;
|
|
127
132
|
// What the daemon could not read in its own `.intentic/` state files, and in the files on its volume that
|
package/src/schemas/updates.ts
CHANGED
|
@@ -54,3 +54,74 @@ export const RollbackTargetSchema = z.object({
|
|
|
54
54
|
version: z.string().optional().describe("What that image says it is. Absent when it would not say."),
|
|
55
55
|
});
|
|
56
56
|
export type RollbackTarget = z.infer<typeof RollbackTargetSchema>;
|
|
57
|
+
|
|
58
|
+
// UPDATING BY ITSELF. Everything an update costs nobody is done unasked: the machine downloads and builds the next
|
|
59
|
+
// release in the background (`ic sandbox prepare --auto`) and checks its state conversions against this sandbox's files.
|
|
60
|
+
// What is left is a restart of about half a minute, and THAT is what this decides the moment of: the daemon holds a staged
|
|
61
|
+
// update until nothing it can see would feel the restart (no agent mid-turn, nobody at the editor, terminals quiet,
|
|
62
|
+
// nothing due), says so to anyone still connected, and then hands the machine the same `update` the button sends, asking
|
|
63
|
+
// the next boot to pick up any turn the restart cut. Only a STAGED update is taken this way: one that still has to be
|
|
64
|
+
// downloaded is minutes of downtime, which stays a person's decision. `system/updates/auto-update.ts` in the daemon.
|
|
65
|
+
|
|
66
|
+
// One thing keeping an update waiting. `kind` is a string rather than an enum, as UpdateOutcome's `verb` is: a newer
|
|
67
|
+
// daemon's reason must not make an older page drop the whole answer. The ones this build names:
|
|
68
|
+
// - agents: an agent is mid-turn, or a subagent, a workflow or a land is still running (`names` says which).
|
|
69
|
+
// - people: somebody has the editor on screen and is using it (`names` says who).
|
|
70
|
+
// - terminal: a terminal printed something in the last few minutes, the trace of a person or a build still at work.
|
|
71
|
+
// - schedule: an automation or a scheduled message is due within minutes, and the restart would land on it (`until`).
|
|
72
|
+
// - paused: the owner asked for no automatic update until `until`.
|
|
73
|
+
// - machine: the machine that runs this sandbox is not connected, so nothing here can ask it to restart anything.
|
|
74
|
+
// - retry: the last try did not take; it is tried again at `until`.
|
|
75
|
+
// - consent: the release changes something developers build on (its breaking notes), so it waits for a person to take
|
|
76
|
+
// it, however quiet the sandbox is. Never lifts by itself.
|
|
77
|
+
export const AutoUpdateHoldSchema = z.object({
|
|
78
|
+
kind: z
|
|
79
|
+
.string()
|
|
80
|
+
.describe(
|
|
81
|
+
"What it waits on: agents (an agent is mid-turn, or a subagent, workflow or land is running), people (somebody is using the editor), terminal (a terminal was busy in the last few minutes), schedule (an automation or a scheduled message is due shortly), paused (the owner asked it to wait), machine (the machine that runs this sandbox is not connected), retry (the last try did not take), or consent (the release changes something developers build on, so a person takes it). A newer sandbox may name others.",
|
|
82
|
+
),
|
|
83
|
+
names: z.array(z.string()).optional().describe("Who or what, by name: the agents still working, the people at the editor. Absent when there is nobody to name."),
|
|
84
|
+
until: z
|
|
85
|
+
.number()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("When this lifts by itself, in milliseconds, for a hold with a moment of its own: a pause, a retry, an automation's next run."),
|
|
88
|
+
});
|
|
89
|
+
export type AutoUpdateHold = z.infer<typeof AutoUpdateHoldSchema>;
|
|
90
|
+
|
|
91
|
+
export const AutoUpdateSchema = z.object({
|
|
92
|
+
enabled: z.boolean().describe("Whether this sandbox takes downloaded updates by itself. On unless the owner turned it off."),
|
|
93
|
+
// A string for the same reason as a hold's kind.
|
|
94
|
+
phase: z
|
|
95
|
+
.string()
|
|
96
|
+
.describe(
|
|
97
|
+
"Where it stands: idle (nothing downloaded to take, or turned off), waiting (an update is downloaded and waits for the holds to clear), countdown (it restarts at startsAt unless somebody stops it), or updating (the machine is restarting this sandbox onto it now).",
|
|
98
|
+
),
|
|
99
|
+
version: z.string().optional().describe("The downloaded version it will take. Absent while idle, or when the download did not say."),
|
|
100
|
+
holds: z.array(AutoUpdateHoldSchema).describe("Everything keeping it waiting right now, the one most likely to last first. Empty unless waiting."),
|
|
101
|
+
startsAt: z.number().optional().describe("During a countdown, when the restart begins, in milliseconds."),
|
|
102
|
+
pausedUntil: z.number().optional().describe("The owner's pause: no automatic update before this moment, in milliseconds. Absent when not paused."),
|
|
103
|
+
failure: z.string().optional().describe("Why the last automatic try did not take, in the machine's own words, while that is still the news."),
|
|
104
|
+
// The update this daemon itself started, read back by the version it moved onto: what lets the card say the
|
|
105
|
+
// restart happened by itself, while nobody was looking.
|
|
106
|
+
lastApplied: z
|
|
107
|
+
.object({ at: z.number(), to: z.string().optional() })
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("The last update this sandbox took by itself: when it began and which version it moved onto."),
|
|
110
|
+
});
|
|
111
|
+
export type AutoUpdate = z.infer<typeof AutoUpdateSchema>;
|
|
112
|
+
|
|
113
|
+
// The owner's say over it. Every field is optional and only what is named changes: the switch, a pause (null lifts it),
|
|
114
|
+
// or "now", which skips the wait and the countdown for the update already downloaded.
|
|
115
|
+
export const AutoUpdateInputSchema = z.object({
|
|
116
|
+
enabled: z.boolean().optional().describe("Turn taking downloaded updates by itself on or off."),
|
|
117
|
+
pausedUntil: z
|
|
118
|
+
.number()
|
|
119
|
+
.nullable()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("Hold automatic updates until this moment, in milliseconds; null lifts a pause. A moment already past lifts it too."),
|
|
122
|
+
applyNow: z
|
|
123
|
+
.literal(true)
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Take the downloaded update now, without waiting for a quiet moment or counting down. Refused when nothing is downloaded."),
|
|
126
|
+
});
|
|
127
|
+
export type AutoUpdateInput = z.infer<typeof AutoUpdateInputSchema>;
|
|
@@ -88,6 +88,9 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
88
88
|
// last boot could not start (the daemon writes it, the host's probation reads it): all rebuilt by whoever next says.
|
|
89
89
|
{ path: "update-outcome.json", portability: "derived" },
|
|
90
90
|
{ path: "update-skipped.json", portability: "derived" },
|
|
91
|
+
// Whether this sandbox takes downloaded updates by itself, a pause, and the update it last started unasked
|
|
92
|
+
// (system/updates/update-policy.ts): about this machine's restarts, so a moved sandbox starts on the default.
|
|
93
|
+
{ path: "update-policy.json", portability: "derived" },
|
|
91
94
|
{ path: "boot-failure.json", portability: "derived" },
|
|
92
95
|
// A rebuild's ask that the next boot resume the turns it cuts (restart-resume.ts): about this machine's next boot only,
|
|
93
96
|
// read once by it, so a moved sandbox starts without one.
|
|
@@ -58,6 +58,10 @@ const RUNTIME_DOMAINS = [
|
|
|
58
58
|
|
|
59
59
|
// The audit log grew or a gateway's status moved or aged out; the log lives outside /work, so no file push carries it.
|
|
60
60
|
{ domain: "activity", invalidates: [["activity"], ["activity-status"]] },
|
|
61
|
+
|
|
62
|
+
// An automatic update started waiting, changed what it waits on, began counting down or handed itself to the
|
|
63
|
+
// machine: what every page needs to hear before the sandbox goes quiet under it, wherever the reader is.
|
|
64
|
+
{ domain: "update", invalidates: [["update"]] },
|
|
61
65
|
] as const satisfies readonly RuntimeDomainBinding[];
|
|
62
66
|
|
|
63
67
|
export const RUNTIME_DOMAIN_BINDINGS: readonly RuntimeDomainBinding[] = RUNTIME_DOMAINS;
|
|
@@ -29,7 +29,7 @@ const AUTOMATIONS: readonly FileContribution[] = [
|
|
|
29
29
|
|
|
30
30
|
describe(`staleQueryKeys`, () => {
|
|
31
31
|
it(`maps a manifest write to the queries it makes stale`, () => {
|
|
32
|
-
expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([`capabilities`, `environment`, `panels`, `manifests`]);
|
|
32
|
+
expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([`capabilities`, `extensions`, `environment`, `panels`, `manifests`]);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
it(`refreshes the unreadable-manifest notice for the four files a person hand-edits`, () => {
|
|
@@ -72,6 +72,7 @@ describe(`staleQueryKeys`, () => {
|
|
|
72
72
|
// A capability add recomposes the overlay, so both paths claim environment; one entry, not two.
|
|
73
73
|
expect(staleQueryKeys([`.intentic/config/capabilities.json`, `.intentic/config/environment.Dockerfile`], [])).toEqual([
|
|
74
74
|
`capabilities`,
|
|
75
|
+
`extensions`,
|
|
75
76
|
`environment`,
|
|
76
77
|
`panels`,
|
|
77
78
|
`manifests`,
|
|
@@ -160,7 +161,9 @@ describe(`WORKSPACE_STATE_FILES`, () => {
|
|
|
160
161
|
for (const file of WORKSPACE_STATE_FILES.filter((entry) => entry.invalidates.length > 0)) {
|
|
161
162
|
const isFamilyPrefix = file.path.endsWith(`.`);
|
|
162
163
|
// .md is the safety policy: the one state file a model reads as prose rather than a parser reading JSON.
|
|
163
|
-
|
|
164
|
+
// .toon is the field-notes brief, read by rank into every turn.
|
|
165
|
+
const isFile =
|
|
166
|
+
file.path.endsWith(`.json`) || file.path.endsWith(`.Dockerfile`) || file.path.endsWith(`.md`) || file.path.endsWith(`.toon`);
|
|
164
167
|
expect(isFile || isFamilyPrefix || file.path.endsWith(`/`), file.path).toBe(true);
|
|
165
168
|
}
|
|
166
169
|
});
|
|
@@ -348,6 +351,9 @@ describe(`VERSIONED_STATE_PATHS`, () => {
|
|
|
348
351
|
`${STATE_DIR}/config/extension-settings.json`,
|
|
349
352
|
// Per-extension update posture (notify/agent/auto): a standing decision on what may run unattended.
|
|
350
353
|
`${STATE_DIR}/config/extension-update-policy.json`,
|
|
354
|
+
// The field-notes brief every turn opens with. Tracked so the automation that rewrites it, which always
|
|
355
|
+
// runs isolated, can land its rewrite at all; and a new revision is worth a reviewable diff.
|
|
356
|
+
`${STATE_DIR}/config/field-notes.toon`,
|
|
351
357
|
// Which commands count as heavy, and how many may run at once; a shared-box decision worth a `git log`
|
|
352
358
|
// line.
|
|
353
359
|
`${STATE_DIR}/config/heavy-commands.json`,
|
|
@@ -42,10 +42,12 @@ const STATE_FILES = [
|
|
|
42
42
|
// Holds no credential: values are vaulted (capabilities-store.ts's withSecretVault) and read back rehydrated; the
|
|
43
43
|
// vaulted keys are the complement of what `echo` exposes (capabilities/credentials/secret-fields.ts). `carry` because
|
|
44
44
|
// composeEnvironment reads Dockerfile fragments from here; `versioned` because connecting a capability is a
|
|
45
|
-
// consequential change worth reviewing.
|
|
45
|
+
// consequential change worth reviewing. `extensions` because an installed extension is a capability entry: the
|
|
46
|
+
// daemon writes this file once the checkout is in place, so an install an agent or another tab made reaches an open
|
|
47
|
+
// Extensions view without a reload.
|
|
46
48
|
{
|
|
47
49
|
path: ".intentic/config/capabilities.json",
|
|
48
|
-
invalidates: ["capabilities", "environment", "panels", "manifests"],
|
|
50
|
+
invalidates: ["capabilities", "extensions", "environment", "panels", "manifests"],
|
|
49
51
|
portability: "carry",
|
|
50
52
|
versioned: true,
|
|
51
53
|
},
|
|
@@ -107,6 +109,14 @@ const STATE_FILES = [
|
|
|
107
109
|
portability: "derived",
|
|
108
110
|
note: "The target starts its own record of what it decided.",
|
|
109
111
|
},
|
|
112
|
+
// What the privacy shield's gateway did with each model request: kinds and counts, never a value. `derived`: evidence
|
|
113
|
+
// about this machine, self-trimming, not carried.
|
|
114
|
+
{
|
|
115
|
+
path: ".intentic/local/privacy-log.json",
|
|
116
|
+
invalidates: ["privacy-log"],
|
|
117
|
+
portability: "derived",
|
|
118
|
+
note: "The target starts its own record of what its shield did.",
|
|
119
|
+
},
|
|
110
120
|
// Apps the daemon starts on every boot (scaffold/autostart.ts). `carry`: an exported workspace should start the
|
|
111
121
|
// same things. Invalidates nothing: the browser reads what's running off `/panels`, not this file.
|
|
112
122
|
{
|
|
@@ -120,6 +130,17 @@ const STATE_FILES = [
|
|
|
120
130
|
// Bash command. `carry`: a property of the workspace, not the machine. `versioned`: raising the limit is a decision
|
|
121
131
|
// worth a diff.
|
|
122
132
|
{ path: ".intentic/config/heavy-commands.json", invalidates: ["settings"], portability: "carry", versioned: true },
|
|
133
|
+
// The field-notes brief composed into every turn (agent-context field-notes.ts), rewritten by the monthly
|
|
134
|
+
// `field-notes` automation. `versioned` is load-bearing, not just reviewability: automation wakes always run
|
|
135
|
+
// isolated, and an unversioned path is excluded from the root repo, so the rewrite would never leave its worktree.
|
|
136
|
+
// Feeds the agent-settings row (`settings.fieldNotes`).
|
|
137
|
+
{
|
|
138
|
+
path: ".intentic/config/field-notes.toon",
|
|
139
|
+
invalidates: ["settings"],
|
|
140
|
+
portability: "carry",
|
|
141
|
+
versioned: true,
|
|
142
|
+
outsideWriter: "the field-notes automation's agent turn (the daemon only reads it, through FIELD_NOTES_FILE)",
|
|
143
|
+
},
|
|
123
144
|
// Last-fired stamps for the rule table. `derived`: records what happened on this machine, not carried.
|
|
124
145
|
{
|
|
125
146
|
path: ".intentic/local/rule-firings.json",
|
|
@@ -163,6 +184,15 @@ const STATE_FILES = [
|
|
|
163
184
|
why: "Declared by the intentic.automations extension's contributes.files, `automations` is its query key, not core's.",
|
|
164
185
|
portability: "carry",
|
|
165
186
|
},
|
|
187
|
+
// How far the scheduler has accounted for each schedule's clock, so a boot fires the moments it slept through
|
|
188
|
+
// (automations/schedule-coverage.ts). `carry`: it travels with the automations it measures, or a restored sandbox
|
|
189
|
+
// would read every one as never armed.
|
|
190
|
+
{
|
|
191
|
+
path: ".intentic/records/automation-schedule.json",
|
|
192
|
+
invalidates: [],
|
|
193
|
+
why: "Scheduler bookkeeping nothing in the browser renders: what a catch-up fire produces reaches the run ledger, which carries the automations key.",
|
|
194
|
+
portability: "carry",
|
|
195
|
+
},
|
|
166
196
|
{
|
|
167
197
|
path: ".intentic/records/approvals/",
|
|
168
198
|
invalidates: [],
|
|
@@ -408,6 +408,21 @@ describe("foldTurn", () => {
|
|
|
408
408
|
});
|
|
409
409
|
});
|
|
410
410
|
|
|
411
|
+
// What a runtime's extension said is a notice row a reopened chat still shows; its status line is live state only.
|
|
412
|
+
it("folds an extension's notice into a notice row and keeps its status entries out of the rows", () => {
|
|
413
|
+
const rows = foldOf("go", [
|
|
414
|
+
{ kind: "agent_status", key: "lint", text: "linting…", source: "lint-ext" },
|
|
415
|
+
{ kind: "agent_notice", level: "warning", text: "Command blocked by user", source: "guard-ext" },
|
|
416
|
+
{ kind: "agent_notice", level: "info", text: "Compacting soon" },
|
|
417
|
+
{ kind: "agent_notice", level: "error", text: " " },
|
|
418
|
+
]);
|
|
419
|
+
expect(rows.slice(1)).toEqual([
|
|
420
|
+
{ role: "notice", text: "Command blocked by user", agentNotice: { level: "warning", source: "guard-ext" } },
|
|
421
|
+
{ role: "notice", text: "Compacting soon", agentNotice: { level: "info" } },
|
|
422
|
+
]);
|
|
423
|
+
expect(rows.slice(1).map((row) => TranscriptRowSchema.parse(row))).toEqual(rows.slice(1));
|
|
424
|
+
});
|
|
425
|
+
|
|
411
426
|
// An agent's own install, let through the command gate, says where it writes before its output arrives.
|
|
412
427
|
it("says where an agent's own install writes, naming its projects", () => {
|
|
413
428
|
expect(foldOf("go", [{ kind: "install", reach: "own-copy", projects: ["video"] }]).at(-1)).toEqual({
|
|
@@ -459,6 +459,15 @@ export class TranscriptFold {
|
|
|
459
459
|
return this.pushRow(compactedRow());
|
|
460
460
|
case "install":
|
|
461
461
|
return this.pushRow(installRow(event));
|
|
462
|
+
case "agent_notice":
|
|
463
|
+
// A runtime's own extension or loop said it: a notice row, so a reopened conversation still says it.
|
|
464
|
+
return event.text.trim() === ""
|
|
465
|
+
? []
|
|
466
|
+
: this.pushRow({
|
|
467
|
+
role: "notice",
|
|
468
|
+
text: event.text,
|
|
469
|
+
agentNotice: { level: event.level, ...(event.source === undefined ? {} : { source: event.source }) },
|
|
470
|
+
});
|
|
462
471
|
case "error":
|
|
463
472
|
// Keeps a refusal (no prose from the provider) from reading as a session that ended mid-question.
|
|
464
473
|
// A refusal that ran nothing takes its message back out ahead of the notice standing in for it.
|
|
@@ -554,6 +563,7 @@ export class TranscriptFold {
|
|
|
554
563
|
case "account_usage":
|
|
555
564
|
case "context_usage":
|
|
556
565
|
case "mode":
|
|
566
|
+
case "agent_status":
|
|
557
567
|
case "done":
|
|
558
568
|
return [];
|
|
559
569
|
}
|