@intentic/sandbox-contract 1.168.0 → 1.169.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/src/schemas.ts CHANGED
@@ -781,34 +781,26 @@ export const SandboxSettingsSchema = z.object({
781
781
  * OFF still records the interruption: the fleet card reads `interrupted` (see AgentStatusSchema) and an
782
782
  * automation's row shows an `interrupted` run — nothing is re-run, but nothing is silently lost either. */
783
783
  autoResumeOnRestart: z.boolean().default(true),
784
- /* THE LANDING GATE — the check command run over the COMPOSITE of landed work after a landing debounce.
785
- * Empty ⇒ no gate at all, which is the default: only the owner knows what verifies this workspace, and a
786
- * guessed command that fails on a fresh clone would read as the gate finding a bug on its first run.
784
+ /* THE PRE-PUSH CHECK — the command run when the user pushes, before anything leaves the machine.
785
+ * Empty ⇒ no check at all, which is the default: only the owner knows what verifies this workspace, and a
786
+ * guessed command that fails on a fresh clone would read as the check finding a bug on its first run.
787
787
  *
788
788
  * Configuring it is the opt-in, which is why there is no separate enable flag to disagree with it. The
789
- * command runs in the workspace root through `sh -c`, exactly as a terminal would run it (see gate/gate.ts
790
- * for why this is NOT an automation guard: a suite outlives GUARD_TIMEOUT_MS, and a timed-out guard reads
791
- * as "skipped" a silent green over a suite that never finished). */
792
- gateCommand: z.string().max(500).default(""),
793
- /* How long after a land the gate waits before running. A landing burst is the case this exists for: five
794
- * agents finishing within a minute of each other are five lands, and a gate that ran per land would spend
795
- * five suites to answer about four trees nobody will ever push. Every land re-arms the timer, so the run
796
- * happens once, on the tree the user is about to review.
789
+ * command runs in the workspace root through `sh -c`, exactly as a terminal would run it. */
790
+ prepushCommand: z.string().max(500).default(""),
791
+ // Ceiling on one run, after which the child's whole process group is killed and the result is `failed` with
792
+ // `timedOut`. Never a pass: a suite that did not finish has said nothing about the tree, and the one thing
793
+ // this check exists to prevent is a green light nobody earned.
794
+ prepushTimeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
795
+ /* WHAT THE SUGGESTED FIX SESSION RUNS ON when the check fails `${provider}:${model}` (quickModelKey), and
796
+ * the reasoning effort beside it. Both empty ⇒ whatever the chat composer would have started with, which is
797
+ * the right default precisely because it is the model the user already chose to work with.
797
798
  *
798
- * It counts from the last LAND and nothing else explicitly not "until the fleet is idle". Agents here run
799
- * for hours, so a fleet of twenty with one long runner would never present a quiet moment, and a gate that
800
- * waited for one would only ever fire when clicked (gate/gate.ts). */
801
- gateQuietMs: z.number().min(0).max(600_000).default(20_000),
802
- // Ceiling on one gate run, after which the child is killed and the verdict is `failed` with `timedOut`.
803
- // Never a pass: a suite that did not finish has not said anything about the tree, and the one thing this
804
- // gate exists to prevent is a green light nobody earned.
805
- gateTimeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
806
- /* Wake a fixer automatically when the gate goes red, instead of only lighting the badge. ON with a
807
- * configured command, and the difference from the unattended spend a usage limit refuses is
808
- * that the spend here is the POINT: a red gate whose fix waits for the user to notice has moved the CI
809
- * round-trip into the workspace without removing it from the user's day. One attempt per verdict, so a
810
- * command that fails for a reason no agent can fix costs one turn, not a loop (gate/gate.ts). */
811
- gateAutoFix: z.boolean().default(true),
799
+ * Pinned here rather than resolved cheapest-first like `quickModel`: this is not a one-click helper, it is a
800
+ * turn that has to read a failing suite and repair it, so the choice is the user's and worth stating. The
801
+ * suggestion dialog seeds from these and stays editable the setting decides where the dialog OPENS. */
802
+ prepushFixModel: z.string().default(""),
803
+ prepushFixEffort: z.string().default(""),
812
804
  });
813
805
  export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
814
806
 
@@ -2243,95 +2235,51 @@ export type CiRunParam = z.infer<typeof CiRunParamSchema>;
2243
2235
  export const CiFixResponseSchema = z.object({ conversationId: z.string() });
2244
2236
  export type CiFixResponse = z.infer<typeof CiFixResponseSchema>;
2245
2237
 
2246
- /* ---- the landing gate: the workspace's own verdict on the composite of landed work ----
2238
+ /* ---- the pre-push check: the workspace's own answer to "would this push go red" ----
2247
2239
  *
2248
- * WHERE THIS SITS, and why it is not one of the four other places it could:
2240
+ * WHERE THIS SITS. A fleet of 5-20 agents lands work into the main tree, the user reviews and commits it by
2241
+ * parts, pushes, and CI answers minutes later. The check front-runs that answer at the push itself — the last
2242
+ * moment before the work leaves the machine, and the first moment at which what will be pushed is finally
2243
+ * settled.
2249
2244
  *
2250
- * A fleet of 5-20 agents lands work into the main tree as UNCOMMITTED changes (agents/land.ts), the user
2251
- * reviews and commits it by parts, pushes, and CI answers minutes later. This gate front-runs that answer by
2252
- * asking the same question of the same artifact, before the push.
2245
+ * WHY THE PUSH AND NOT THE LAND, which is where this used to run. A post-land verdict is about a tree that
2246
+ * keeps moving: the user commits by parts, another agent lands, an edit arrives so the verdict spent its life
2247
+ * either stale or being recomputed, and needed a content fingerprint, a staleness rule and a badge to say which.
2248
+ * All of that machinery existed to answer a question the push asks for free, because at the push there is
2249
+ * exactly one artifact and the user is standing in front of it waiting.
2253
2250
  *
2254
- * NOT inside an agent's turn. An isolated worktree's `node_modules` reads as the MAIN checkout's, so a
2255
- * monorepo's workspace links resolve cross-package imports to /work's sources rather than the worktree's edited
2256
- * ones (agents/worktrees.ts). A suite run in a worktree therefore tests the agent's edits against everyone
2257
- * else's UNEDITED siblings: it invents failures that don't exist and passes changes that break on the
2258
- * composite, and two agents editing one contract each go green alone and red together. The composite is the
2259
- * only honest artifact, and it exists in exactly one place — the main working tree.
2260
- *
2261
- * NOT at commit. The user commits BY PARTS, and a suite reads the worktree, not the index — so a verdict taken
2262
- * at a partial commit describes a tree that never gets pushed as such. Commit is where a verdict is DISPLAYED
2263
- * (ReviewPanel's badge), computed earlier.
2264
- *
2265
- * NOT at push. By then HEAD has moved, per-path attribution has expired (agents/origins.ts), and the agents may
2266
- * be archived with their worktrees reclaimed — so the fix starts cold, in the same position `/ci/fix` is in.
2267
- * That saves the CI round-trip and none of the context switch.
2268
- *
2269
- * So: after the land, before the staging — the one window where the artifact is what CI will see, attribution
2270
- * is still live, and nobody is waiting on it. */
2251
+ * SO THERE IS NO STORED VERDICT AND NOTHING IS POLLED AT REST. A run exists while it runs, reports to the
2252
+ * dialog that started it, and is gone. Nothing survives a daemon restart because nothing needs to: the next
2253
+ * push asks again. */
2271
2254
 
2272
- /* What the gate has to say about the tree right now.
2255
+ /* Where a run is.
2273
2256
  *
2274
- * idle — no command configured, or nothing has run yet.
2275
- * armed — work landed; the quiet period is counting down (see gateQuietMs).
2257
+ * idle — nothing has run in this daemon's life, or the last run was cleared.
2276
2258
  * running — the check is live. `output` grows as it streams.
2277
- * passed — exited 0 over `fingerprint`.
2278
- * failed — exited non-zero, or was killed by gateTimeoutMs (`timedOut`). The state a fix answers.
2279
- * error — the gate itself could not run: the command was not spawnable. NOT a fix-able failure, because
2280
- * there is nothing wrong with the code — the gate is misconfigured, and saying "tests failed"
2259
+ * passed — exited 0. The push goes.
2260
+ * failed — exited non-zero, or was killed by prepushTimeoutMs (`timedOut`). The state a fix answers.
2261
+ * error — the check could not run at all: the command was not spawnable. NOT a fix-able failure, because
2262
+ * there is nothing wrong with the code — the command is misconfigured, and saying "tests failed"
2281
2263
  * would send an agent hunting a bug that isn't there.
2282
- * cancelled — the user stopped the run, or the tree moved under it.
2264
+ * cancelled — the user stopped the run.
2283
2265
  */
2284
- export const GateStatusSchema = z.enum(["idle", "armed", "running", "passed", "failed", "error", "cancelled"]);
2285
- export type GateStatus = z.infer<typeof GateStatusSchema>;
2286
-
2287
- // An agent whose landed work the failure implicates. `paths` are its attributed files that the check's own
2288
- // output NAMED — empty when the output named none of them, which is the honest shape for a failure that could
2289
- // not be pinpointed (an integration break between two deltas, a suite that prints no paths at all): the agent
2290
- // is listed because its work is in the tree under test, not because anything accused it.
2291
- export const GateAgentSchema = z.object({
2292
- agentId: z.string(),
2293
- title: z.string().optional(),
2294
- provider: AgentProviderSchema.optional(),
2295
- paths: z.array(z.string()),
2296
- });
2297
- export type GateAgent = z.infer<typeof GateAgentSchema>;
2266
+ export const PrepushStatusSchema = z.enum(["idle", "running", "passed", "failed", "error", "cancelled"]);
2267
+ export type PrepushStatus = z.infer<typeof PrepushStatusSchema>;
2298
2268
 
2299
- // The fix turn one red verdict got. It is a workspace conversation because the composite it must reproduce
2300
- // lives as uncommitted content in the main working tree; it still has the same stable registry identity as any
2301
- // other conversation.
2302
- export const GateFixSchema = z.object({
2303
- startedAt: z.number(),
2304
- conversationId: z.string(),
2305
- // `running` while the turn streams; `done` when it ended cleanly, whatever the re-check then said;
2306
- // `error` when the turn itself failed (a provider outage, no credential), which is worth distinguishing
2307
- // because it is the one case where re-running the fix could still help.
2308
- outcome: z.enum(["running", "done", "error"]),
2309
- detail: z.string().optional(),
2310
- });
2311
- export type GateFix = z.infer<typeof GateFixSchema>;
2312
-
2313
- export const GateVerdictSchema = z.object({
2314
- status: GateStatusSchema,
2315
- // The command this verdict ran, echoed rather than read back from settings: a verdict read after the
2269
+ export const PrepushRunSchema = z.object({
2270
+ status: PrepushStatusSchema,
2271
+ // The command this run executed, echoed rather than read back from settings: a result read after the
2316
2272
  // setting changed still has to say what produced it.
2317
2273
  command: z.string(),
2318
2274
  startedAt: z.number().optional(),
2319
2275
  finishedAt: z.number().optional(),
2320
2276
  exitCode: z.number().optional(),
2321
2277
  timedOut: z.boolean().optional(),
2322
- // The check's own output, tail-capped (GATE_OUTPUT_BYTES). The tail, not the head: a suite's verdict and
2278
+ // The check's own output, tail-capped (PREPUSH_OUTPUT_BYTES). The TAIL, not the head: a suite's verdict and
2323
2279
  // its failure summary are at the end, and a head-capped buffer of a chatty build is all progress lines.
2324
2280
  output: z.string(),
2325
- /* WHICH TREE this verdict is about — HEAD plus the shape of every repo's uncommitted content. Recomputed
2326
- * on read: when it no longer matches, the verdict is `stale` and the badge says so instead of asserting a
2327
- * green light over a tree that has since moved. This is what keeps a passed verdict from outliving its
2328
- * subject when the user edits, discards, or commits half of it. */
2329
- fingerprint: z.string(),
2330
- stale: z.boolean(),
2331
- implicated: z.array(GateAgentSchema),
2332
- fix: GateFixSchema.optional(),
2333
- });
2334
- export type GateVerdict = z.infer<typeof GateVerdictSchema>;
2281
+ });
2282
+ export type PrepushRun = z.infer<typeof PrepushRunSchema>;
2335
2283
 
2336
2284
  // ---- drafts: agent-proposed posts awaiting owner approval (.intentic/drafts/<id>.json) ----
2337
2285
  // One JSON file per draft. The AGENT creates drafts with its normal file tools — it can't call daemon routes,
@@ -89,16 +89,6 @@ export const WORKSPACE_STATE_FILES: readonly WorkspaceStateFile[] = [
89
89
  },
90
90
 
91
91
  // ---- daemon-owned, nothing derives from watching them ----
92
- {
93
- path: ".intentic/gate.json",
94
- invalidates: [],
95
- why: "The landing gate's verdict is POLLED on purpose (web's useGate). Its fingerprint pass rewrites this file every couple of seconds while a check runs, and pushing that back would refetch the review set — the daemon's most expensive read — on every poll.",
96
- },
97
- {
98
- path: ".intentic/gate-index/",
99
- invalidates: [],
100
- why: "The gate's per-repo git index. Machine state, rewritten continuously by the fingerprint pass.",
101
- },
102
92
  /* Agent session transcripts, rewritten on every streamed token.
103
93
  *
104
94
  * The memory notes under it (`projects/<slug>/memory/**`) ARE user-facing and the /memory view polls them
@@ -1,47 +0,0 @@
1
- export declare const gateContract: {
2
- verdict: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
3
- status: import("zod").ZodEnum<{
4
- armed: "armed";
5
- cancelled: "cancelled";
6
- error: "error";
7
- failed: "failed";
8
- idle: "idle";
9
- passed: "passed";
10
- running: "running";
11
- }>;
12
- command: import("zod").ZodString;
13
- startedAt: import("zod").ZodOptional<import("zod").ZodNumber>;
14
- finishedAt: import("zod").ZodOptional<import("zod").ZodNumber>;
15
- exitCode: import("zod").ZodOptional<import("zod").ZodNumber>;
16
- timedOut: import("zod").ZodOptional<import("zod").ZodBoolean>;
17
- output: import("zod").ZodString;
18
- fingerprint: import("zod").ZodString;
19
- stale: import("zod").ZodBoolean;
20
- implicated: import("zod").ZodArray<import("zod").ZodObject<{
21
- agentId: import("zod").ZodString;
22
- title: import("zod").ZodOptional<import("zod").ZodString>;
23
- provider: import("zod").ZodOptional<import("zod").ZodString>;
24
- paths: import("zod").ZodArray<import("zod").ZodString>;
25
- }, import("zod/v4/core").$strip>>;
26
- fix: import("zod").ZodOptional<import("zod").ZodObject<{
27
- startedAt: import("zod").ZodNumber;
28
- conversationId: import("zod").ZodString;
29
- outcome: import("zod").ZodEnum<{
30
- done: "done";
31
- error: "error";
32
- running: "running";
33
- }>;
34
- detail: import("zod").ZodOptional<import("zod").ZodString>;
35
- }, import("zod/v4/core").$strip>>;
36
- }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
37
- run: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
38
- ok: import("zod").ZodLiteral<true>;
39
- }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
40
- cancel: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
41
- ok: import("zod").ZodLiteral<true>;
42
- }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
43
- fix: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
44
- ok: import("zod").ZodLiteral<true>;
45
- }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
46
- };
47
- //# sourceMappingURL=gate.contract.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gate.contract.d.ts","sourceRoot":"","sources":["../../src/contracts/gate.contract.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,YAAY;IACrB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IACP,GAAG;;;IACH,MAAM;;;IACN,GAAG;;;CACN,CAAC"}
@@ -1,9 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import { GateVerdictSchema, OkSchema } from "../schemas.js";
3
- export const gateContract = {
4
- verdict: oc.route({ method: "GET", path: "/gate/verdict" }).output(GateVerdictSchema),
5
- run: oc.route({ method: "POST", path: "/gate/run" }).output(OkSchema),
6
- cancel: oc.route({ method: "POST", path: "/gate/cancel" }).output(OkSchema),
7
- fix: oc.route({ method: "POST", path: "/gate/fix" }).output(OkSchema),
8
- };
9
- //# sourceMappingURL=gate.contract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gate.contract.js","sourceRoot":"","sources":["../../src/contracts/gate.contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAY5D,MAAM,CAAC,MAAM,YAAY,GAAG;IACxB,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC;IACrF,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC3E,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;CACxE,CAAC"}
@@ -1,19 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import { GateVerdictSchema, OkSchema } from "../schemas.js";
3
-
4
- // The landing gate — the check command run over the composite of landed work once the fleet goes quiet (see
5
- // GateVerdictSchema for where this sits and why). Four verbs, all about ONE verdict: the gate answers about the
6
- // main working tree, of which there is exactly one, so nothing here is addressed by id.
7
- //
8
- // `verdict` is the one read, and the panel polls it; it recomputes staleness per call, so a passed verdict stops
9
- // claiming a green light the moment the tree moves under it. The three writes answer `ok` and nothing else —
10
- // each starts work that outlives the request, so there is no result to return and the poll is what reports.
11
- // `run` arms nothing and waits for nothing: it starts the check now, the user's own "I'm about to commit, check
12
- // this". `fix` opens a seeded workspace conversation for a red verdict; it has the same registry lifecycle as
13
- // /ci/fix, minus the isolated worktree it must not have.
14
- export const gateContract = {
15
- verdict: oc.route({ method: "GET", path: "/gate/verdict" }).output(GateVerdictSchema),
16
- run: oc.route({ method: "POST", path: "/gate/run" }).output(OkSchema),
17
- cancel: oc.route({ method: "POST", path: "/gate/cancel" }).output(OkSchema),
18
- fix: oc.route({ method: "POST", path: "/gate/fix" }).output(OkSchema),
19
- };