@intentic/sandbox-contract 1.315.0 → 1.316.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 +5 -5
- package/dist/contracts/agent.contract.d.ts +1 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -3
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +22 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +0 -3
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +3 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +15 -137
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +25 -15
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/land-breakage.d.ts +0 -2
- package/dist/events/land-breakage.d.ts.map +1 -1
- package/dist/events/land-breakage.js +0 -2
- package/dist/events/land-breakage.js.map +1 -1
- package/dist/events/system-events.d.ts +2 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/index.d.ts +43 -142
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +1 -1
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/plan-pools.d.ts +2 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +8 -4
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +9 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +2 -0
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/request-id.d.ts +0 -1
- package/dist/protocol/request-id.d.ts.map +1 -1
- package/dist/protocol/request-id.js +0 -1
- package/dist/protocol/request-id.js.map +1 -1
- package/dist/schemas/agent.js +1 -1
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +0 -1
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +2 -2
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/ci.d.ts +45 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +18 -0
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/devices.d.ts +0 -1
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +3 -4
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/personas.js +2 -2
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +2 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +4 -0
- package/dist/schemas/providers/plan-limits.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 +2 -1
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +0 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +6 -13
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/workspace/push-checks.d.ts +222 -0
- package/dist/schemas/workspace/push-checks.d.ts.map +1 -0
- package/dist/schemas/workspace/push-checks.js +97 -0
- package/dist/schemas/workspace/push-checks.js.map +1 -0
- package/dist/schemas/workspace/workspace-tree.d.ts +9 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +12 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +0 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/runtime-state.d.ts +2 -2
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -1
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +1 -11
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +1 -13
- package/dist/state/workspace-state.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agents.contract.ts +3 -3
- package/src/contracts/workspace.contract.ts +35 -21
- package/src/events/errands.test.ts +4 -4
- package/src/events/land-breakage.ts +5 -9
- package/src/events/verify-nudge.ts +3 -3
- package/src/index.ts +2 -2
- package/src/models/agent-runtimes.ts +5 -4
- package/src/models/plan-pools.test.ts +15 -0
- package/src/models/plan-pools.ts +20 -4
- package/src/protocol/ingress-contract.fixture.json +2 -0
- package/src/protocol/raw-routes.ts +4 -1
- package/src/protocol/request-id.ts +0 -3
- package/src/schemas/agent.ts +1 -1
- package/src/schemas/agents.ts +5 -7
- package/src/schemas/automations.ts +2 -2
- package/src/schemas/ci.ts +25 -0
- package/src/schemas/devices.ts +8 -12
- package/src/schemas/personas.ts +2 -2
- package/src/schemas/providers/plan-limits.ts +8 -0
- package/src/schemas/settings-history.ts +3 -1
- package/src/schemas/settings.test.ts +4 -3
- package/src/schemas/settings.ts +17 -31
- package/src/schemas/speaker.ts +4 -4
- package/src/schemas/workspace/{mainline.test.ts → push-checks.test.ts} +5 -5
- package/src/schemas/workspace/push-checks.ts +155 -0
- package/src/schemas/workspace/workspace-tree.ts +15 -0
- package/src/state/fix-stance.test.ts +2 -1
- package/src/state/landed-commit.ts +1 -1
- package/src/state/red-streak.ts +4 -4
- package/src/state/runtime-state.ts +2 -3
- package/src/state/workspace-state.ts +1 -13
- package/dist/schemas/workspace/mainline.d.ts +0 -513
- package/dist/schemas/workspace/mainline.d.ts.map +0 -1
- package/dist/schemas/workspace/mainline.js +0 -182
- package/dist/schemas/workspace/mainline.js.map +0 -1
- package/src/schemas/workspace/mainline.ts +0 -268
package/src/schemas/personas.ts
CHANGED
|
@@ -141,8 +141,8 @@ export const TURN_BRIEFING_NOTES: readonly TurnBriefingNote[] = [
|
|
|
141
141
|
{
|
|
142
142
|
id: "checks",
|
|
143
143
|
label: "Checks after landing",
|
|
144
|
-
when: "First message and after a compaction
|
|
145
|
-
cost: "The agent does not know
|
|
144
|
+
when: "First message and after a compaction: that nothing checks its work when it finishes or after it lands, which checks are its to run, and that CI checks what the owner pushes.",
|
|
145
|
+
cost: "The agent does not know what checks its work, so it may run the whole repository's suite on a shared machine, or chase a failure main already has.",
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
id: "dependencies",
|
|
@@ -123,6 +123,14 @@ export const ProviderRefusalSchema = z.object({
|
|
|
123
123
|
// Lets a `limit` refusal be read against the pool that model spends (`UsageWindow.gates`), not the account's
|
|
124
124
|
// fullest pool.
|
|
125
125
|
model: z.string().optional().describe("Which model the refused turn was on, where that is known."),
|
|
126
|
+
// The provider's own "try again at …", which a polled reading cannot contradict: a usage endpoint can keep showing
|
|
127
|
+
// room while the plan refuses every turn.
|
|
128
|
+
resetsAt: z
|
|
129
|
+
.number()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe(
|
|
132
|
+
"When the provider said to try again, in epoch seconds, for a spent allowance where it named one. Until then the refusal stands whatever a reading says; after it, it is over.",
|
|
133
|
+
),
|
|
126
134
|
});
|
|
127
135
|
export type ProviderRefusal = z.infer<typeof ProviderRefusalSchema>;
|
|
128
136
|
export const ProviderRefusalsSchema = z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
2
|
+
import { at, drop, dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
3
3
|
import type { KeepWarmSettings, RuleSchema } from "./settings.js";
|
|
4
4
|
|
|
5
5
|
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
@@ -105,4 +105,6 @@ export const SETTINGS_HISTORY = [
|
|
|
105
105
|
"drops rules at the retired turn.ending moment (and the verify-ui-edits built-in), which run nothing",
|
|
106
106
|
),
|
|
107
107
|
keepWarmObject,
|
|
108
|
+
// Where the check after landing ran (2026-09-27): nothing checks work after it lands any more, CI does.
|
|
109
|
+
at("offload", drop("landCheck")),
|
|
108
110
|
] as const;
|
|
@@ -48,15 +48,16 @@ describe(`where a command lives`, () => {
|
|
|
48
48
|
expect(file([{ when: `push`, run: `pnpm verify:push` }])).toBe(false);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// `land` is retired like `turn`: it runs nothing, so a file written while it ran reads however it spelled one.
|
|
52
|
+
test(`a retired land check still reads, however many a file names and whatever paths it gives`, () => {
|
|
52
53
|
const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
|
|
53
54
|
expect(
|
|
54
55
|
file([
|
|
55
56
|
{ when: `land`, run: `pnpm verify` },
|
|
56
57
|
{ when: `land`, run: `pnpm test` },
|
|
57
58
|
]),
|
|
58
|
-
).toBe(
|
|
59
|
-
expect(file([{ when: `land`, run: `pnpm verify`, paths: [`src/**`] }])).toBe(
|
|
59
|
+
).toBe(true);
|
|
60
|
+
expect(file([{ when: `land`, run: `pnpm verify`, paths: [`src/**`] }])).toBe(true);
|
|
60
61
|
});
|
|
61
62
|
});
|
|
62
63
|
|
package/src/schemas/settings.ts
CHANGED
|
@@ -15,8 +15,8 @@ export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
|
|
|
15
15
|
export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
|
|
16
16
|
// Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version); every command a
|
|
17
17
|
// moment runs is a repository's own check (`<repo>/.intentic/checks.json`), compiled into this same table by the daemon,
|
|
18
|
-
// so no command lives in settings. Nothing verifies inside a turn:
|
|
19
|
-
// holds anything (schemas/workspace/
|
|
18
|
+
// so no command lives in settings. Nothing verifies inside a turn or after a land: CI checks what the owner pushes, and
|
|
19
|
+
// nothing here holds anything (schemas/workspace/push-checks.ts).
|
|
20
20
|
export const RuleMomentSchema = z.enum([
|
|
21
21
|
// A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
|
|
22
22
|
"file.edited",
|
|
@@ -436,30 +436,26 @@ export const SandboxSettingsSchema = z.object({
|
|
|
436
436
|
),
|
|
437
437
|
// Worth it since the container is recreated on every update or environment approval — otherwise approving a
|
|
438
438
|
// Dockerfile change costs the run that asked for it.
|
|
439
|
-
// What happens
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
// cold; main's CI staying red on one failure gets a fix agent once pushes go quiet, with a fleet failure re-run instead.
|
|
443
|
-
// Off, all of it is only reported.
|
|
439
|
+
// What happens when main's CI goes red (ci/main-fixer.ts): its first failed job starts one fix agent, which is sent
|
|
440
|
+
// every later failure on main until main is green, within a few turns, and a failure on the CI fleet itself is re-run
|
|
441
|
+
// once instead. Off, it is only reported.
|
|
444
442
|
autoRepair: z
|
|
445
443
|
.boolean()
|
|
446
444
|
.default(true)
|
|
447
445
|
.describe(
|
|
448
|
-
"Whether
|
|
446
|
+
"Whether main's CI going red is repaired without asking. The first job that fails on main starts one fix agent, without waiting for the rest of the run, and every later failure on main goes to that same agent until a run passes. It gets a few turns; when they are spent, or it finishes without changing anything (a failure that is not in the code), the red waits for you. A failure on the CI fleet itself is re-run once instead. Off, all of it is only reported.",
|
|
449
447
|
),
|
|
450
448
|
// Where heavy work runs: a runner (the same image, on one of the owner's machines) instead of this sandbox. Keyed by
|
|
451
|
-
// the heavy-command rule an agent's command matched (system/resources/heavy-commands.ts),
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
// work on again.
|
|
449
|
+
// the heavy-command rule an agent's command matched (system/resources/heavy-commands.ts), each naming a runner id. A
|
|
450
|
+
// runner that is offline, outdated or full hands the work back here, which the command's output says. Never pushed
|
|
451
|
+
// to a runner itself (portability/definition.ts), which must not pass work on again.
|
|
455
452
|
offload: z
|
|
456
453
|
.object({
|
|
457
454
|
commands: z.record(z.string().min(1), z.string().min(1)).default({}),
|
|
458
|
-
landCheck: z.string().min(1).optional(),
|
|
459
455
|
})
|
|
460
456
|
.default({ commands: {} })
|
|
461
457
|
.describe(
|
|
462
|
-
"Which heavy work runs on a runner on one of your machines instead of this sandbox: agents' commands by the kind the heavy-command rules sort them into (tests, typechecks, verify…)
|
|
458
|
+
"Which heavy work runs on a runner on one of your machines instead of this sandbox: agents' commands by the kind the heavy-command rules sort them into (tests, typechecks, verify…). The code travels as it stands, uncommitted work included; the output streams back, and any file the command changed comes back with it. A machine that is offline, outdated or busy hands the work back to this sandbox, and the output says so.",
|
|
463
459
|
),
|
|
464
460
|
autoResumeOnRestart: z
|
|
465
461
|
.boolean()
|
|
@@ -665,9 +661,9 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
|
665
661
|
// spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
|
|
666
662
|
export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
|
|
667
663
|
|
|
668
|
-
// Named for the occasion as a repository would say it: `edit` is `file.edited
|
|
669
|
-
//
|
|
670
|
-
// and a declaration naming
|
|
664
|
+
// Named for the occasion as a repository would say it: `edit` is `file.edited` (rules/repo-checks.ts maps it). `turn`
|
|
665
|
+
// and `land` are retired: nothing runs when a turn ends or after work lands any more (CI checks what the owner pushes),
|
|
666
|
+
// and a declaration naming either still reads but runs nothing. No check refuses a land, a commit or a push.
|
|
671
667
|
// `edit` runs on one file (`{file}`) and its output rides back in the edit's own response, so a command declared there is
|
|
672
668
|
// paid per edit and has to take the file.
|
|
673
669
|
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
|
|
@@ -675,7 +671,7 @@ export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
|
|
|
675
671
|
|
|
676
672
|
export const RepoCheckSchema = z.object({
|
|
677
673
|
when: RepoCheckMomentSchema.describe(
|
|
678
|
-
"When to run it: `edit` on each file as it is written (`{file}` is its path)
|
|
674
|
+
"When to run it: `edit` on each file as it is written (`{file}` is its path). `turn` and `land` are retired and run nothing: checks no longer run while a conversation works or after its work lands, since CI checks what is pushed.",
|
|
679
675
|
),
|
|
680
676
|
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."),
|
|
681
677
|
label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
|
|
@@ -695,11 +691,7 @@ export const RepoChecksFileSchema = z.object({
|
|
|
695
691
|
checks: z
|
|
696
692
|
.array(RepoCheckSchema)
|
|
697
693
|
.max(10)
|
|
698
|
-
.default([])
|
|
699
|
-
// One land check per repository: it is THE verdict on the main tree the push hook replays, not one of several.
|
|
700
|
-
.refine((checks) => checks.filter((check) => check.when === "land").length <= 1, { message: "a repository declares at most one land check" })
|
|
701
|
-
// A land checks the whole tree an install settled, not a change, so there is nothing for a glob to narrow.
|
|
702
|
-
.refine((checks) => checks.every((check) => check.when !== "land" || check.paths === undefined), { message: "a land check takes no paths" }),
|
|
694
|
+
.default([]),
|
|
703
695
|
});
|
|
704
696
|
export type RepoChecksFile = z.infer<typeof RepoChecksFileSchema>;
|
|
705
697
|
|
|
@@ -711,7 +703,7 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
711
703
|
fired: z
|
|
712
704
|
.array(z.number().nullable())
|
|
713
705
|
.describe(
|
|
714
|
-
"When each declared check last reported something, in the file's order, as epoch milliseconds; null for one that never has, or for
|
|
706
|
+
"When each declared check last reported something, in the file's order, as epoch milliseconds; null for one that never has, or for a retired one, which runs nothing.",
|
|
715
707
|
),
|
|
716
708
|
adopted: z
|
|
717
709
|
.boolean()
|
|
@@ -722,16 +714,10 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
722
714
|
"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.",
|
|
723
715
|
),
|
|
724
716
|
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."),
|
|
725
|
-
landDefault: z
|
|
726
|
-
.string()
|
|
727
|
-
.optional()
|
|
728
|
-
.describe(
|
|
729
|
-
"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.",
|
|
730
|
-
),
|
|
731
717
|
});
|
|
732
718
|
export type RepoChecksSummary = z.infer<typeof RepoChecksSummarySchema>;
|
|
733
719
|
export const RepoChecksListSchema = z.object({
|
|
734
|
-
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks
|
|
720
|
+
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks, in id order."),
|
|
735
721
|
});
|
|
736
722
|
export type RepoChecksList = z.infer<typeof RepoChecksListSchema>;
|
|
737
723
|
export const RepoChecksAdoptSchema = z.object({
|
package/src/schemas/speaker.ts
CHANGED
|
@@ -68,12 +68,12 @@ export const speakerOwner = (speaker: TurnSpeaker): Pick<SessionOwner, "email" |
|
|
|
68
68
|
/** A person, verified, at the keyboard: never a program holding a token a person minted. */
|
|
69
69
|
export const spokenByPerson = (speaker: TurnSpeaker | undefined): boolean => speaker?.kind === "person";
|
|
70
70
|
|
|
71
|
-
// WHAT A COMPOSED PROMPT IS FOR, when the sandbox or the app wrote it rather than whoever is named as speaking:
|
|
72
|
-
//
|
|
73
|
-
// working on what failed, the brief a person's press hands an agent (what a push left, main's red CI), a land conflict to
|
|
71
|
+
// WHAT A COMPOSED PROMPT IS FOR, when the sandbox or the app wrote it rather than whoever is named as speaking: the brief
|
|
72
|
+
// a fix agent is handed (what a push left, main's red CI, each later failure on main) and its nudges, a land conflict to
|
|
74
73
|
// resolve. Carried on the turn and the row it opens, so a reader shows it as the sandbox's words, not the owner's, and
|
|
75
74
|
// never has to recognise the prompt by its opening; the four openings older rows are recognised by stay what they are
|
|
76
|
-
// (events/errands.ts, errandOfPrompt).
|
|
75
|
+
// (events/errands.ts, errandOfPrompt). `verify-nudge` and the `land-*` errands are retired with the checks that sent them
|
|
76
|
+
// and stay only because records hold them.
|
|
77
77
|
export const TurnErrandSchema = z.enum([
|
|
78
78
|
"land-conflict",
|
|
79
79
|
"verify-nudge",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pushFixConversationId } from "../../ids/conversation-ids.js";
|
|
2
|
-
import { pushFixBase, pushRedOf, type Red, RedSchema } from "./
|
|
2
|
+
import { pushFixBase, pushRedOf, type Red, RedSchema } from "./push-checks.js";
|
|
3
3
|
|
|
4
4
|
// What a push left is a Red like any other (source `push`, scope the project), and the hand-over's id is named by when
|
|
5
5
|
// that red began, so the editor and the daemon find the same attempt whichever of them asks.
|
|
@@ -7,12 +7,12 @@ import { pushFixBase, pushRedOf, type Red, RedSchema } from "./mainline.js";
|
|
|
7
7
|
const red = (over: Partial<Red>): Red => RedSchema.parse({ source: `push`, scope: `app`, since: 100, ...over });
|
|
8
8
|
|
|
9
9
|
describe(`a project's push red`, () => {
|
|
10
|
-
test(`is the push red of that project, never a
|
|
11
|
-
const
|
|
10
|
+
test(`is the push red of that project, never a CI red of the same name or another project's`, () => {
|
|
11
|
+
const ci = red({ source: `ci` });
|
|
12
12
|
const other = red({ scope: `web` });
|
|
13
13
|
const mine = red({});
|
|
14
|
-
expect(pushRedOf([
|
|
15
|
-
expect(pushRedOf([
|
|
14
|
+
expect(pushRedOf([ci, other, mine], `app`)).toBe(mine);
|
|
15
|
+
expect(pushRedOf([ci, other], `app`)).toBeUndefined();
|
|
16
16
|
expect(pushRedOf(undefined, `app`)).toBeUndefined();
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// what the pre-push hook let through, and the one record every red keeps, whatever went red
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { pushFixConversationId } from "../../ids/conversation-ids.js";
|
|
4
|
+
import { AgentRunPickSchema } from "../agent.js";
|
|
5
|
+
|
|
6
|
+
// Nothing checks work inside a conversation or after it lands: CI checks what the owner pushes (ci/main-fixer.ts puts
|
|
7
|
+
// one fix agent on main's red), and the pre-push hook measures the push on the way out without ever refusing it
|
|
8
|
+
// (verify-push.mjs, `--advisory`). What the hook found used to vanish with the terminal it printed to. It now leaves a
|
|
9
|
+
// report in the repository's git dir, the daemon files it once the push has actually reached the remote, and what it
|
|
10
|
+
// found is owed, as the project's push Red, until a later measurement no longer prints it or somebody dismisses it.
|
|
11
|
+
// Nobody is sent after it: acting on it is the owner's call.
|
|
12
|
+
|
|
13
|
+
// ONE FINDING, whatever measured it: a check run's line as the repository's own tooling printed it, named by what
|
|
14
|
+
// printed it (`source`, the repository's word: a check's id, `lint`, a hook), with the command that shows it again and
|
|
15
|
+
// whether a later measurement can say it is gone. A push's findings are these; a CI run's failed jobs become them too.
|
|
16
|
+
export const FindingSchema = z.object({
|
|
17
|
+
id: z.string().describe("Stable across measurements: the same problem found again is the same id."),
|
|
18
|
+
source: z.string().describe("What measured it, in the repository's own words: a check's id, `lint`, a hook."),
|
|
19
|
+
text: z.string().describe("The finding as it was printed."),
|
|
20
|
+
path: z.string().optional().describe("The repository path it is about, when it names one."),
|
|
21
|
+
command: z.string().optional().describe("The command that shows it again."),
|
|
22
|
+
recheckable: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.describe("Whether a later measurement can find it gone. False for one about commits already made, which ends only when dismissed."),
|
|
25
|
+
gate: z
|
|
26
|
+
.enum(["code", "tidy"])
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"For a check's finding: `code` means the tree fails the check whoever caused it; `tidy` means the change measured added this line.",
|
|
30
|
+
),
|
|
31
|
+
commit: z
|
|
32
|
+
.object({ sha: z.string(), subject: z.string() })
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("The newest commit of the change measured that touched the path it names, when it names one."),
|
|
35
|
+
});
|
|
36
|
+
export type Finding = z.infer<typeof FindingSchema>;
|
|
37
|
+
|
|
38
|
+
// What was decided about a red, in the order it was decided.
|
|
39
|
+
export const RedDecisionKindSchema = z.enum([
|
|
40
|
+
// A fix agent was put on it: pressed for by a person, or started by the sandbox on main's red CI.
|
|
41
|
+
"fix-up",
|
|
42
|
+
// Nobody was sent: repairs are switched off, or nothing could take it.
|
|
43
|
+
"reported",
|
|
44
|
+
// A later measurement no longer found it (`findings` names which).
|
|
45
|
+
"resolved",
|
|
46
|
+
// The fix agent had its turns, or stopped without a fix: it waits for a person.
|
|
47
|
+
"spent",
|
|
48
|
+
// A person set findings aside as not to be fixed (`findings` names which).
|
|
49
|
+
"dismissed",
|
|
50
|
+
]);
|
|
51
|
+
export type RedDecisionKind = z.infer<typeof RedDecisionKindSchema>;
|
|
52
|
+
|
|
53
|
+
export const RedDecisionSchema = z.object({
|
|
54
|
+
kind: RedDecisionKindSchema.describe("What was decided."),
|
|
55
|
+
conversationId: z.string().optional().describe("The conversation working on it, when one is."),
|
|
56
|
+
at: z.number().describe("When that was decided, in milliseconds."),
|
|
57
|
+
detail: z.string().optional().describe("One sentence on why, in the sandbox's words."),
|
|
58
|
+
findings: z
|
|
59
|
+
.array(z.string())
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("The findings it was about, by id, when it was about some of a red's findings rather than the whole red."),
|
|
62
|
+
});
|
|
63
|
+
export type RedDecision = z.infer<typeof RedDecisionSchema>;
|
|
64
|
+
|
|
65
|
+
// ONE RED, whatever went red: what a push left in a project, main's CI on a branch. What it owes (its findings) and every
|
|
66
|
+
// decision about it, oldest first. A push's red waits for the owner's press (conversations/fix/push-fix.ts); main's CI
|
|
67
|
+
// red gets one fix agent by itself (ci/main-fixer.ts). The record is the same.
|
|
68
|
+
export const RedSourceSchema = z.enum(["push", "ci"]);
|
|
69
|
+
export type RedSource = z.infer<typeof RedSourceSchema>;
|
|
70
|
+
|
|
71
|
+
export const RedSchema = z.object({
|
|
72
|
+
source: RedSourceSchema.describe("What went red."),
|
|
73
|
+
scope: z.string().describe("Where: a project's folder, or a repository and branch."),
|
|
74
|
+
since: z.number().describe("When the red streak began (state/red-streak.ts)."),
|
|
75
|
+
findings: z.array(FindingSchema).default([]).describe("What it owes, as the red's own measurement named it."),
|
|
76
|
+
decisions: z.array(RedDecisionSchema).default([]).describe("What was decided about it, oldest first."),
|
|
77
|
+
});
|
|
78
|
+
export type Red = z.infer<typeof RedSchema>;
|
|
79
|
+
|
|
80
|
+
// ONE PUSH THE HOOK MEASURED, as the record lists it: where it went and what it found that it brought in. What of that is
|
|
81
|
+
// still owed is the project's push Red's to say (`findings`), never the push's.
|
|
82
|
+
export const PushCheckSchema = z.object({
|
|
83
|
+
project: z.string().describe("Which project, by folder relative to the workspace. Empty is the workspace root."),
|
|
84
|
+
id: z.string().describe("The report's own id."),
|
|
85
|
+
at: z.number().describe("When the push check ran, in milliseconds."),
|
|
86
|
+
remote: z.string().optional().describe("The remote it was pushed to."),
|
|
87
|
+
branch: z.string().optional().describe("The branch it was pushed to."),
|
|
88
|
+
base: z.string().optional().describe("The commit the pushed range starts from; absent when the remote had nothing to compare with."),
|
|
89
|
+
head: z.string().describe("The commit that was pushed."),
|
|
90
|
+
commits: z.number().describe("How many commits the push carried."),
|
|
91
|
+
findings: z
|
|
92
|
+
.array(FindingSchema)
|
|
93
|
+
.describe("What it found that this push brought in, less what an earlier push had already left open. Empty for a clean push."),
|
|
94
|
+
refused: z
|
|
95
|
+
.boolean()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe(
|
|
98
|
+
"True when the repository's own pre-push hook refused it, so nothing reached the remote and its one finding is what the hook said.",
|
|
99
|
+
),
|
|
100
|
+
});
|
|
101
|
+
export type PushCheck = z.infer<typeof PushCheckSchema>;
|
|
102
|
+
|
|
103
|
+
export const PushChecksSchema = z.object({
|
|
104
|
+
pushed: z.array(PushCheckSchema).describe("The latest pushes the hook measured across every project, newest first, with what each brought in."),
|
|
105
|
+
reds: z.array(RedSchema).describe("What pushes left in each project and is still owed, one push Red per project, with every decision about it."),
|
|
106
|
+
});
|
|
107
|
+
export type PushChecks = z.infer<typeof PushChecksSchema>;
|
|
108
|
+
|
|
109
|
+
export const PushDismissSchema = z.object({
|
|
110
|
+
project: z.string().describe("Which project, by folder relative to the workspace."),
|
|
111
|
+
ids: z.array(z.string()).optional().describe("Which findings. Leave it out for every open one in the project."),
|
|
112
|
+
restore: z.boolean().optional().describe("Undo: open the named dismissed findings again."),
|
|
113
|
+
});
|
|
114
|
+
export type PushDismiss = z.infer<typeof PushDismissSchema>;
|
|
115
|
+
|
|
116
|
+
export const PushDismissResultSchema = z.object({
|
|
117
|
+
changed: z.number().describe("How many findings changed state."),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const PushRecheckSchema = z.object({
|
|
121
|
+
project: z.string().describe("Which project, by folder relative to the workspace."),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const PushRecheckResultSchema = z.object({
|
|
125
|
+
measured: z.boolean().describe("Whether the project could be measured at all; false leaves every finding as it was."),
|
|
126
|
+
resolved: z.number().describe("How many findings the measurement no longer saw."),
|
|
127
|
+
open: z.number().describe("How many are still open."),
|
|
128
|
+
});
|
|
129
|
+
export type PushRecheckResult = z.infer<typeof PushRecheckResultSchema>;
|
|
130
|
+
|
|
131
|
+
export const PushFixSchema = z.object({
|
|
132
|
+
project: z.string().describe("Which project's open push findings to hand over, by folder relative to the workspace."),
|
|
133
|
+
pick: AgentRunPickSchema.describe("Which model to open the conversation on, when somebody chose one. Leave it out for the sandbox's own choice."),
|
|
134
|
+
mode: z
|
|
135
|
+
.enum(["continue", "start-over"])
|
|
136
|
+
.optional()
|
|
137
|
+
.describe(
|
|
138
|
+
"What to do about an attempt already made at these findings: `continue` carries on in it, `start-over` files it away and opens the next attempt. Leave it out for the plain press.",
|
|
139
|
+
),
|
|
140
|
+
});
|
|
141
|
+
export type PushFix = z.infer<typeof PushFixSchema>;
|
|
142
|
+
|
|
143
|
+
export const PushFixResultSchema = z.object({
|
|
144
|
+
conversationId: z.string().describe("The conversation holding the findings. Open it to watch."),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// What pushes left in `project`, as the reds name it; undefined while nothing is owed.
|
|
148
|
+
export const pushRedOf = (reds: readonly Red[] | undefined, project: string): Red | undefined =>
|
|
149
|
+
reds?.find((red) => red.source === "push" && red.scope === project);
|
|
150
|
+
|
|
151
|
+
// The conversation id a hand-over of a push red wears (attempt 1), derived from when the red began, so the editor and the
|
|
152
|
+
// daemon agree on whether an agent is already on it: pressing again while anything is owed continues the same attempt,
|
|
153
|
+
// and a red that begins after everything was handled starts a fresh one. Undefined when nothing is owed.
|
|
154
|
+
export const pushFixBase = (red: Pick<Red, "scope" | "since"> | undefined): string | undefined =>
|
|
155
|
+
red === undefined ? undefined : pushFixConversationId(red.scope === "" ? "workspace" : red.scope, `red:${red.since}`);
|
|
@@ -132,6 +132,21 @@ export const WorkspaceMediaTicketSchema = z.object({
|
|
|
132
132
|
ticket: z.string().describe("Hand this to the streaming route in the query string. It buys exactly the one file it was minted for."),
|
|
133
133
|
expiresAt: z.number().describe("When it stops working, in milliseconds, so a player can tell a dead ticket from a dead file."),
|
|
134
134
|
});
|
|
135
|
+
// Credential for GET /workspace/download, which the browser navigates to so its own download manager streams the archive
|
|
136
|
+
// to disk. The ticket carries the whole selection, resolved when minted, so however many entries it names the URL stays
|
|
137
|
+
// short.
|
|
138
|
+
export const WorkspaceDownloadTicketQuerySchema = WorkspaceScopeSchema.extend({
|
|
139
|
+
paths: z
|
|
140
|
+
.array(z.string().min(1))
|
|
141
|
+
.min(1)
|
|
142
|
+
.max(10_000)
|
|
143
|
+
.describe("The files and folders to download together, as workspace paths. A folder brings everything inside it."),
|
|
144
|
+
});
|
|
145
|
+
export const WorkspaceDownloadTicketSchema = z.object({
|
|
146
|
+
ticket: z.string().describe("Hand this to the download route in the query string. It buys exactly the selection it was minted for, once resolved."),
|
|
147
|
+
expiresAt: z.number().describe("When it stops working, in milliseconds. It is meant to be used at once."),
|
|
148
|
+
filename: z.string().describe("What the archive is saved as, so a caller can say what is on its way."),
|
|
149
|
+
});
|
|
135
150
|
// A read of a window: offset negative reads that many bytes from the end (for following a growing log); limit is
|
|
136
151
|
// clamped to the daemon's own cap. Coerced from query strings.
|
|
137
152
|
export const WorkspaceFileReadQuerySchema = WorkspaceScopeSchema.extend({
|
|
@@ -50,7 +50,8 @@ test.each([
|
|
|
50
50
|
["holding nothing yet", undefined],
|
|
51
51
|
] as const)("a turn that ended waiting on its own watch, %s, is still working", (_case, diff) => {
|
|
52
52
|
const watches = [{ id: `watch-a1b2`, note: `CI on the pushed branch`, intervalSeconds: 60, deadlineAt: 9_000 }];
|
|
53
|
-
|
|
53
|
+
// The daemon reads the armed watch as a wake to come (awaitingWake); the stance reads that, never the watch list.
|
|
54
|
+
expect(fixStance(agent({ status: `idle`, watches, awaitingWake: true, ...(diff === undefined ? {} : { diff }) }))).toStrictEqual({
|
|
54
55
|
kind: `working`,
|
|
55
56
|
ongoing: true,
|
|
56
57
|
retry: false,
|
|
@@ -18,5 +18,5 @@ export const landedCommitMessage = (landed: LandedMessage): string => {
|
|
|
18
18
|
export const declaredTestNote = (said: string): string | undefined => /^Test-Note:[ \t]*(\S.*)$/m.exec(said)?.[1]?.trim();
|
|
19
19
|
|
|
20
20
|
// The `Allow: <check> — <reason>` lines a conversation ended its last message with, each an exception it declared for
|
|
21
|
-
// its change on purpose; the
|
|
21
|
+
// its change on purpose; the push reads them off the landed commit.
|
|
22
22
|
export const declaredAllows = (said: string): string[] => [...said.matchAll(/^Allow:[ \t]*(\S.*)$/gm)].map((match) => match[1]!.trim());
|
package/src/state/red-streak.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// ONE DEFINITION OF A RED STREAK, for every source that goes red: a
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// ONE DEFINITION OF A RED STREAK, for every source that goes red: what a project's pushes left (push-checks-store.ts)
|
|
2
|
+
// and a pipeline branch on the board (the pipelines extension's ciStreaks.ts). A streak is the unbroken run of red at
|
|
3
|
+
// the head: a red extends it, anything else ends it, and `since` is where it began, which is what names one failure
|
|
4
|
+
// across every red that continues it.
|
|
5
5
|
|
|
6
6
|
export interface Streak {
|
|
7
7
|
// When (or at which run) the streak began: the first red of the unbroken run.
|
|
@@ -59,9 +59,8 @@ const RUNTIME_DOMAINS = [
|
|
|
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
61
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
{ domain: "mainline", invalidates: [["mainline"]] },
|
|
62
|
+
// What the pre-push hook left behind: a push filed, a finding measured gone, dismissed or handed to an agent.
|
|
63
|
+
{ domain: "pushes", invalidates: [["push-checks"]] },
|
|
65
64
|
] as const satisfies readonly RuntimeDomainBinding[];
|
|
66
65
|
|
|
67
66
|
export const RUNTIME_DOMAIN_BINDINGS: readonly RuntimeDomainBinding[] = RUNTIME_DOMAINS;
|
|
@@ -379,24 +379,12 @@ const STATE_FILES = [
|
|
|
379
379
|
portability: "derived",
|
|
380
380
|
note: "The target stamps its own daemon version on first boot.",
|
|
381
381
|
},
|
|
382
|
-
{
|
|
383
|
-
path: ".intentic/records/verify.json",
|
|
384
|
-
invalidates: [],
|
|
385
|
-
why: "The main-line check's memory: each project's last verdict and red streak, and the latest runs with what became of each red one. Read through GET /workspace/mainline, which the daemon pushes itself (the `mainline` runtime domain), so no file watch is needed.",
|
|
386
|
-
portability: "carry",
|
|
387
|
-
},
|
|
388
382
|
{
|
|
389
383
|
path: ".intentic/records/push-checks.json",
|
|
390
384
|
invalidates: [],
|
|
391
|
-
why: "What each push check found and let through, per project, and what became of every finding: still open, measured gone, or dismissed. Filed from the report the pre-push hook leaves in the repository's git dir; read through GET /workspace/
|
|
385
|
+
why: "What each push check found and let through, per project, and what became of every finding: still open, measured gone, or dismissed. Filed from the report the pre-push hook leaves in the repository's git dir; read through GET /workspace/push-checks, which the daemon pushes itself (the `pushes` runtime domain).",
|
|
392
386
|
portability: "carry",
|
|
393
387
|
},
|
|
394
|
-
{
|
|
395
|
-
path: ".intentic/local/verify/",
|
|
396
|
-
invalidates: [],
|
|
397
|
-
why: "A running check's wrapper artifacts (log + exit status), read once by the daemon when the panel finishes.",
|
|
398
|
-
portability: "derived",
|
|
399
|
-
},
|
|
400
388
|
// What an update converted before it booted all the way (the sandbox daemon's state-journal.ts): a copy of every
|
|
401
389
|
// file the new version changed, kept until it commits and for a grace window after. `secret` whatever the source
|
|
402
390
|
// was, since a vault's copy is a vault; locked below for the same reason.
|