@intentic/sandbox-contract 1.315.0 → 1.317.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.
Files changed (214) hide show
  1. package/README.md +5 -5
  2. package/dist/contracts/agent.contract.d.ts +562 -0
  3. package/dist/contracts/agent.contract.d.ts.map +1 -1
  4. package/dist/contracts/agents.contract.d.ts +402 -0
  5. package/dist/contracts/agents.contract.d.ts.map +1 -1
  6. package/dist/contracts/agents.contract.js +3 -3
  7. package/dist/contracts/agents.contract.js.map +1 -1
  8. package/dist/contracts/capabilities.contract.d.ts +16 -0
  9. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  10. package/dist/contracts/capabilities.contract.js +10 -1
  11. package/dist/contracts/capabilities.contract.js.map +1 -1
  12. package/dist/contracts/ci.contract.d.ts +22 -0
  13. package/dist/contracts/ci.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.d.ts +1 -0
  15. package/dist/contracts/device.contract.d.ts.map +1 -1
  16. package/dist/contracts/needs.contract.d.ts +532 -0
  17. package/dist/contracts/needs.contract.d.ts.map +1 -0
  18. package/dist/contracts/needs.contract.js +80 -0
  19. package/dist/contracts/needs.contract.js.map +1 -0
  20. package/dist/contracts/runner.contract.d.ts +294 -122
  21. package/dist/contracts/runner.contract.d.ts.map +1 -1
  22. package/dist/contracts/secrets.contract.d.ts +16 -0
  23. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  24. package/dist/contracts/secrets.contract.js +12 -2
  25. package/dist/contracts/secrets.contract.js.map +1 -1
  26. package/dist/contracts/sessions.contract.d.ts +79 -0
  27. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  28. package/dist/contracts/settings.contract.d.ts +2 -3
  29. package/dist/contracts/settings.contract.d.ts.map +1 -1
  30. package/dist/contracts/system.contract.d.ts +163 -0
  31. package/dist/contracts/system.contract.d.ts.map +1 -1
  32. package/dist/contracts/system.contract.js +11 -0
  33. package/dist/contracts/system.contract.js.map +1 -1
  34. package/dist/contracts/workspace.contract.d.ts +15 -137
  35. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  36. package/dist/contracts/workspace.contract.js +25 -15
  37. package/dist/contracts/workspace.contract.js.map +1 -1
  38. package/dist/events/agent-events.d.ts +309 -0
  39. package/dist/events/agent-events.d.ts.map +1 -1
  40. package/dist/events/agent-events.js +2 -0
  41. package/dist/events/agent-events.js.map +1 -1
  42. package/dist/events/agent-words.d.ts.map +1 -1
  43. package/dist/events/agent-words.js +2 -1
  44. package/dist/events/agent-words.js.map +1 -1
  45. package/dist/events/land-breakage.d.ts +0 -2
  46. package/dist/events/land-breakage.d.ts.map +1 -1
  47. package/dist/events/land-breakage.js +0 -2
  48. package/dist/events/land-breakage.js.map +1 -1
  49. package/dist/events/need-wake.d.ts +17 -0
  50. package/dist/events/need-wake.d.ts.map +1 -0
  51. package/dist/events/need-wake.js +34 -0
  52. package/dist/events/need-wake.js.map +1 -0
  53. package/dist/events/system-events.d.ts +40 -0
  54. package/dist/events/system-events.d.ts.map +1 -1
  55. package/dist/events/transcript.d.ts +484 -0
  56. package/dist/events/transcript.d.ts.map +1 -1
  57. package/dist/events/transcript.js +9 -0
  58. package/dist/events/transcript.js.map +1 -1
  59. package/dist/index.d.ts +1956 -284
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +8 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/models/agent-runtimes.d.ts.map +1 -1
  64. package/dist/models/agent-runtimes.js +1 -1
  65. package/dist/models/agent-runtimes.js.map +1 -1
  66. package/dist/models/plan-pools.d.ts +2 -1
  67. package/dist/models/plan-pools.d.ts.map +1 -1
  68. package/dist/models/plan-pools.js +8 -4
  69. package/dist/models/plan-pools.js.map +1 -1
  70. package/dist/protocol/raw-routes.d.ts +9 -5
  71. package/dist/protocol/raw-routes.d.ts.map +1 -1
  72. package/dist/protocol/raw-routes.js +2 -1
  73. package/dist/protocol/raw-routes.js.map +1 -1
  74. package/dist/protocol/request-id.d.ts +0 -1
  75. package/dist/protocol/request-id.d.ts.map +1 -1
  76. package/dist/protocol/request-id.js +0 -1
  77. package/dist/protocol/request-id.js.map +1 -1
  78. package/dist/schemas/agent.js +1 -1
  79. package/dist/schemas/agent.js.map +1 -1
  80. package/dist/schemas/agents.d.ts +58 -1
  81. package/dist/schemas/agents.d.ts.map +1 -1
  82. package/dist/schemas/agents.js +11 -2
  83. package/dist/schemas/agents.js.map +1 -1
  84. package/dist/schemas/automations.d.ts +19 -0
  85. package/dist/schemas/automations.d.ts.map +1 -1
  86. package/dist/schemas/capabilities.d.ts +19 -0
  87. package/dist/schemas/capabilities.d.ts.map +1 -1
  88. package/dist/schemas/capabilities.js +16 -0
  89. package/dist/schemas/capabilities.js.map +1 -1
  90. package/dist/schemas/ci.d.ts +45 -0
  91. package/dist/schemas/ci.d.ts.map +1 -1
  92. package/dist/schemas/ci.js +18 -0
  93. package/dist/schemas/ci.js.map +1 -1
  94. package/dist/schemas/devices.d.ts +68 -1
  95. package/dist/schemas/devices.d.ts.map +1 -1
  96. package/dist/schemas/devices.js +10 -4
  97. package/dist/schemas/devices.js.map +1 -1
  98. package/dist/schemas/hosts.d.ts +2 -0
  99. package/dist/schemas/hosts.d.ts.map +1 -1
  100. package/dist/schemas/hosts.js +2 -1
  101. package/dist/schemas/hosts.js.map +1 -1
  102. package/dist/schemas/needs.d.ts +661 -0
  103. package/dist/schemas/needs.d.ts.map +1 -0
  104. package/dist/schemas/needs.js +196 -0
  105. package/dist/schemas/needs.js.map +1 -0
  106. package/dist/schemas/personas.js +2 -2
  107. package/dist/schemas/personas.js.map +1 -1
  108. package/dist/schemas/providers/plan-limits.d.ts +2 -0
  109. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  110. package/dist/schemas/providers/plan-limits.js +4 -0
  111. package/dist/schemas/providers/plan-limits.js.map +1 -1
  112. package/dist/schemas/secrets.d.ts +18 -0
  113. package/dist/schemas/secrets.d.ts.map +1 -1
  114. package/dist/schemas/secrets.js +20 -0
  115. package/dist/schemas/secrets.js.map +1 -1
  116. package/dist/schemas/settings-history.d.ts +1 -1
  117. package/dist/schemas/settings-history.d.ts.map +1 -1
  118. package/dist/schemas/settings-history.js +2 -1
  119. package/dist/schemas/settings-history.js.map +1 -1
  120. package/dist/schemas/settings.d.ts +2 -4
  121. package/dist/schemas/settings.d.ts.map +1 -1
  122. package/dist/schemas/settings.js +10 -13
  123. package/dist/schemas/settings.js.map +1 -1
  124. package/dist/schemas/state-plan.d.ts +1 -0
  125. package/dist/schemas/state-plan.d.ts.map +1 -1
  126. package/dist/schemas/state-plan.js +3 -1
  127. package/dist/schemas/state-plan.js.map +1 -1
  128. package/dist/schemas/system.d.ts +20 -0
  129. package/dist/schemas/system.d.ts.map +1 -1
  130. package/dist/schemas/system.js +7 -0
  131. package/dist/schemas/system.js.map +1 -1
  132. package/dist/schemas/updates.d.ts +39 -0
  133. package/dist/schemas/updates.d.ts.map +1 -0
  134. package/dist/schemas/updates.js +31 -0
  135. package/dist/schemas/updates.js.map +1 -0
  136. package/dist/schemas/workspace/push-checks.d.ts +222 -0
  137. package/dist/schemas/workspace/push-checks.d.ts.map +1 -0
  138. package/dist/schemas/workspace/push-checks.js +97 -0
  139. package/dist/schemas/workspace/push-checks.js.map +1 -0
  140. package/dist/schemas/workspace/workspace-tree.d.ts +9 -0
  141. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  142. package/dist/schemas/workspace/workspace-tree.js +12 -0
  143. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  144. package/dist/state/definition.d.ts +16 -4
  145. package/dist/state/definition.d.ts.map +1 -1
  146. package/dist/state/history-state.d.ts.map +1 -1
  147. package/dist/state/history-state.js +3 -0
  148. package/dist/state/history-state.js.map +1 -1
  149. package/dist/state/runtime-state.d.ts +2 -2
  150. package/dist/state/runtime-state.d.ts.map +1 -1
  151. package/dist/state/runtime-state.js +1 -1
  152. package/dist/state/runtime-state.js.map +1 -1
  153. package/dist/state/workspace-state.d.ts +5 -11
  154. package/dist/state/workspace-state.d.ts.map +1 -1
  155. package/dist/state/workspace-state.js +2 -13
  156. package/dist/state/workspace-state.js.map +1 -1
  157. package/dist/text/transcript-fold.d.ts.map +1 -1
  158. package/dist/text/transcript-fold.js +3 -0
  159. package/dist/text/transcript-fold.js.map +1 -1
  160. package/package.json +5 -5
  161. package/src/contracts/agents.contract.ts +3 -3
  162. package/src/contracts/capabilities.contract.ts +14 -1
  163. package/src/contracts/needs.contract.ts +103 -0
  164. package/src/contracts/secrets.contract.ts +17 -3
  165. package/src/contracts/system.contract.ts +14 -0
  166. package/src/contracts/workspace.contract.ts +35 -21
  167. package/src/events/agent-events.ts +4 -0
  168. package/src/events/agent-words.ts +2 -1
  169. package/src/events/errands.test.ts +4 -4
  170. package/src/events/land-breakage.ts +5 -9
  171. package/src/events/need-wake.test.ts +55 -0
  172. package/src/events/need-wake.ts +71 -0
  173. package/src/events/transcript.ts +16 -0
  174. package/src/events/verify-nudge.ts +3 -3
  175. package/src/index.ts +8 -2
  176. package/src/models/agent-runtimes.ts +5 -4
  177. package/src/models/plan-pools.test.ts +15 -0
  178. package/src/models/plan-pools.ts +20 -4
  179. package/src/protocol/ingress-contract.fixture.json +2 -0
  180. package/src/protocol/raw-routes.ts +5 -3
  181. package/src/protocol/request-id.ts +0 -3
  182. package/src/schemas/agent.ts +1 -1
  183. package/src/schemas/agents.ts +19 -8
  184. package/src/schemas/automations.ts +2 -2
  185. package/src/schemas/capabilities.ts +28 -0
  186. package/src/schemas/ci.ts +25 -0
  187. package/src/schemas/devices.ts +25 -12
  188. package/src/schemas/hosts.ts +4 -1
  189. package/src/schemas/needs.ts +292 -0
  190. package/src/schemas/personas.ts +2 -2
  191. package/src/schemas/providers/plan-limits.ts +8 -0
  192. package/src/schemas/secrets.ts +23 -0
  193. package/src/schemas/settings-history.ts +3 -1
  194. package/src/schemas/settings.test.ts +4 -3
  195. package/src/schemas/settings.ts +25 -31
  196. package/src/schemas/speaker.ts +4 -4
  197. package/src/schemas/state-plan.ts +8 -1
  198. package/src/schemas/system.ts +15 -0
  199. package/src/schemas/updates.ts +56 -0
  200. package/src/schemas/workspace/{mainline.test.ts → push-checks.test.ts} +5 -5
  201. package/src/schemas/workspace/push-checks.ts +155 -0
  202. package/src/schemas/workspace/workspace-tree.ts +15 -0
  203. package/src/state/fix-stance.test.ts +2 -1
  204. package/src/state/history-state.ts +5 -0
  205. package/src/state/landed-commit.ts +1 -1
  206. package/src/state/red-streak.ts +4 -4
  207. package/src/state/runtime-state.ts +2 -3
  208. package/src/state/workspace-state.ts +5 -13
  209. package/src/text/transcript-fold.ts +4 -0
  210. package/dist/schemas/workspace/mainline.d.ts +0 -513
  211. package/dist/schemas/workspace/mainline.d.ts.map +0 -1
  212. package/dist/schemas/workspace/mainline.js +0 -182
  213. package/dist/schemas/workspace/mainline.js.map +0 -1
  214. package/src/schemas/workspace/mainline.ts +0 -268
@@ -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: a land's
72
- // breakage sent back to it, a fresh fix-up on a red main line and its later nudges, a note to a conversation still
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",
@@ -40,8 +40,15 @@ export type StatePlan = z.infer<typeof StatePlanSchema>;
40
40
 
41
41
  // The `state` of `/health`: whether an update's state changes are still uncommitted. The host rolls an update back when
42
42
  // the journal stays open, so this rides the same probe as the boot's progress.
43
+ // `failed`: a conversion or structural step threw partway through this boot; the daemon put back every file the episode
44
+ // had touched and keeps running on read-time conversions, and a host rolls the update back as it would an open journal
45
+ // that never commits (this version could not bring the files to its shape, the one before it can read them as they are).
43
46
  export const StateStatusSchema = z.object({
44
- journal: z.enum(["open", "none"]).describe("Open from the start of a boot that changed stored files until that boot has converged."),
47
+ journal: z
48
+ .enum(["open", "none", "failed"])
49
+ .describe(
50
+ "Open from the start of a boot that changed stored files until that boot has converged. Failed when a conversion threw partway: the files were put back as they were, and a host takes that as the update not having taken.",
51
+ ),
45
52
  engine: z.number().describe("The running build's conversion count."),
46
53
  });
47
54
  export type StateStatus = z.infer<typeof StateStatusSchema>;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { StatePlanSchema } from "./state-plan.js";
3
+ import { UpdateOutcomeSchema, WithdrawnReleaseSchema } from "./updates.js";
3
4
  // version is this build's baked-in version; latest/updateAvailable come from comparing it to the published stable
4
5
  // release.
5
6
  // Whether a runtime can serve a turn, probed off the turn path. "unknown" reads as available-but-unverified, never as
@@ -82,6 +83,20 @@ export const InfoSchema = z.object({
82
83
  staged: StagedUpdateSchema.optional().describe(
83
84
  "An update already downloaded and built on the machine running this container, waiting only for the restart that applies it. That restart is seconds, where an unprepared update is minutes, which is a different decision entirely. Absent when nothing is waiting.",
84
85
  ),
86
+ // What the host last did about this sandbox's version (update-outcome.json on /history, written by ic); absent
87
+ // when no host has said, which is every hosted sandbox and every host older than the file.
88
+ lastUpdate: UpdateOutcomeSchema.optional().describe(
89
+ "What the machine running this sandbox last did about its version: an update that took, one it gave up on and why, and until when the previous version stays ready. Absent when that machine has never said.",
90
+ ),
91
+ // The running version was taken back after it shipped; absent for every version still standing.
92
+ withdrawn: WithdrawnReleaseSchema.optional().describe(
93
+ "Set when the version this sandbox runs was withdrawn after it shipped, which is the moment to go back to the one before it. Absent for every version still standing.",
94
+ ),
95
+ // The owner's "not this one" (POST /system/update/skip); updateAvailable reads false while `latest` is it.
96
+ skippedVersion: z
97
+ .string()
98
+ .optional()
99
+ .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."),
85
100
  });
86
101
  export type Info = z.infer<typeof InfoSchema>;
87
102
  // What the daemon could not read in its own `.intentic/` state files, and in the files on its volume that
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+
3
+ // WHAT THE HOST LAST DID ABOUT THIS SANDBOX'S VERSION, in the host's own words. Written by `ic` (the swap, its health
4
+ // check and the probation that follows it: _sandbox/ic/src/sandbox/outcome.rs) onto /history as `update-outcome.json`,
5
+ // the same volume whichever container runs, so the version that ends up running reads what happened to the other one.
6
+ // Advisory: the swap and its undoing are the host's; this is only how the sandbox and its owner hear about them.
7
+ export const UpdateOutcomeResultSchema = z.enum(["updated", "kept", "restored", "rolled-back"]);
8
+ export type UpdateOutcomeResult = z.infer<typeof UpdateOutcomeResultSchema>;
9
+
10
+ export const UpdateOutcomeSchema = z.object({
11
+ result: UpdateOutcomeResultSchema.describe(
12
+ "What happened. Updated: the new version passed its first health check and runs, with the previous one kept ready until keepUntil. Kept: that probation ended and the new version stays. Restored: the new version never came up, so the previous container was put back at once. Rolled back: the new version came up and then failed its probation (it kept crashing, never became ready, or lost its tunnel), so the host went back to the previous one by itself.",
13
+ ),
14
+ // What the person or program asked for. A string rather than an enum: a newer ic's verb must not make an older
15
+ // daemon drop the whole outcome.
16
+ verb: z.string().optional().describe("What was asked for: update, rollback, rebuild, dev, reshape, or the probation watch acting on its own."),
17
+ at: z.number().describe("When it happened, in milliseconds."),
18
+ from: z.string().optional().describe("The version (or, when it would not say, the image) that ran before."),
19
+ to: z.string().optional().describe("The version (or image) that was moved onto, or that was tried and given up on."),
20
+ // Plain words for the owner: why a version was given up on. Absent for an update that simply worked.
21
+ reason: z.string().optional().describe("Why the host gave up on the new version, in plain words. Absent when nothing went wrong."),
22
+ // A path on the machine that runs the sandbox, which only means something to a person on that machine.
23
+ log: z.string().optional().describe("Where the host kept the full log of the swap, as a path on the machine that runs the sandbox."),
24
+ keepUntil: z
25
+ .number()
26
+ .optional()
27
+ .describe(
28
+ "Until when the previous version stays parked and ready, in milliseconds. While it does, going back takes seconds and nothing is downloaded or rebuilt; after it, going back uses the pinned image.",
29
+ ),
30
+ });
31
+ export type UpdateOutcome = z.infer<typeof UpdateOutcomeSchema>;
32
+
33
+ // A published release that was taken back after it shipped (the release pipeline's rollback-stable.sh marks it on its
34
+ // GitHub release). A sandbox running one is told, and offered the way back.
35
+ export const WithdrawnReleaseSchema = z.object({
36
+ version: z.string().describe("The withdrawn version, which is the one this sandbox is running."),
37
+ reason: z.string().optional().describe("Why it was withdrawn, as the people who withdrew it put it."),
38
+ });
39
+ export type WithdrawnRelease = z.infer<typeof WithdrawnReleaseSchema>;
40
+
41
+ // The owner's "not this one": no update is offered while the newest release is the skipped one. `null` clears it.
42
+ export const SkipUpdateInputSchema = z.object({
43
+ version: z
44
+ .string()
45
+ .min(1)
46
+ .nullable()
47
+ .describe("The release to stop offering, or null to offer the newest release again. A newer release than the skipped one is always offered."),
48
+ });
49
+ export type SkipUpdateInput = z.infer<typeof SkipUpdateInputSchema>;
50
+
51
+ // One image a sandbox can go back to, as `ic sandbox versions` knows it: the pinned local image and what it says it is.
52
+ export const RollbackTargetSchema = z.object({
53
+ image: z.string().describe("The local image a rollback would run, pinned under a tag no other flow writes."),
54
+ version: z.string().optional().describe("What that image says it is. Absent when it would not say."),
55
+ });
56
+ export type RollbackTarget = z.infer<typeof RollbackTargetSchema>;
@@ -1,5 +1,5 @@
1
1
  import { pushFixConversationId } from "../../ids/conversation-ids.js";
2
- import { pushFixBase, pushRedOf, type Red, RedSchema } from "./mainline.js";
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 land check's red of the same folder or another project's`, () => {
11
- const land = red({ source: `land` });
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([land, other, mine], `app`)).toBe(mine);
15
- expect(pushRedOf([land, other], `app`)).toBeUndefined();
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
- expect(fixStance(agent({ status: `idle`, watches, ...(diff === undefined ? {} : { diff }) }))).toStrictEqual({
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,
@@ -78,6 +78,11 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
78
78
  { path: "engines/", portability: "derived" },
79
79
  // An update the host downloaded and built for this sandbox (`ic sandbox prepare`); about this machine only.
80
80
  { path: "update-staged.json", portability: "derived" },
81
+ // What the host last did about this sandbox's version (ic writes it), the owner's skipped release, and why the
82
+ // last boot could not start (the daemon writes it, the host's probation reads it): all rebuilt by whoever next says.
83
+ { path: "update-outcome.json", portability: "derived" },
84
+ { path: "update-skipped.json", portability: "derived" },
85
+ { path: "boot-failure.json", portability: "derived" },
81
86
 
82
87
  /* ---- credentials ---- */
83
88
 
@@ -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 check after the land and the push read them off the landed commit.
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());
@@ -1,7 +1,7 @@
1
- // ONE DEFINITION OF A RED STREAK, for every source that goes red: a land check's project (verify-store.ts), main's CI
2
- // (repair-gate.ts), a pipeline branch on the board (the pipelines extension's ciStreaks.ts) and the Main line's older
3
- // daemons (the editor's mainlineView.ts). A streak is the unbroken run of red at the head: a red extends it, anything
4
- // else ends it, and `since` is where it began, which is what names one failure across every red that continues it.
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
- // The main tree's own check after a land: queued, started, settled, or its red handed to somebody. Held in the
63
- // daemon's memory as much as in a file, so a push is the only way a watcher hears it move.
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;
@@ -132,6 +132,10 @@ const STATE_FILES = [
132
132
  // needing, not what one machine did. The drift snapshot self-expires on a move since its `bornAt` won't match the
133
133
  // new container.
134
134
  { path: ".intentic/records/runtime-installs.json", invalidates: ["environment"], portability: "carry" },
135
+ // What agents are waiting on people for (docs/architecture/needs.md): every open need and the recent closed ones.
136
+ // Holds no credential (a secret need names the secret, its value goes to the secret store), so it may `carry`: a
137
+ // question a moved sandbox still has to ask. `needs` is the chat cards', the board's and the inbox's shared read.
138
+ { path: ".intentic/records/needs.json", invalidates: ["needs"], portability: "carry" },
135
139
  // Where each agent engine's version comes from: blessed list, upstream newest, a pin, or the image
136
140
  // (schemas/engines.ts). The versions themselves are machine state on the daemon's volume (architecture-specific
137
141
  // binaries); the policy here travels. `versioned`: a human decision worth reviewing. Own key, not `environment`'s:
@@ -379,24 +383,12 @@ const STATE_FILES = [
379
383
  portability: "derived",
380
384
  note: "The target stamps its own daemon version on first boot.",
381
385
  },
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
386
  {
389
387
  path: ".intentic/records/push-checks.json",
390
388
  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/mainline, which the daemon pushes itself (the `mainline` runtime domain).",
389
+ 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
390
  portability: "carry",
393
391
  },
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
392
  // What an update converted before it booted all the way (the sandbox daemon's state-journal.ts): a copy of every
401
393
  // file the new version changed, kept until it commits and for a grace window after. `secret` whatever the source
402
394
  // was, since a vault's copy is a vault; locked below for the same reason.
@@ -16,6 +16,7 @@ import { isLandConflict } from "../events/land-conflict.js";
16
16
  import { turnedAwayCode } from "../policy/turned-away.js";
17
17
  import { mentionedPathTokens } from "./mentions.js";
18
18
  import { unspokenPromptRow } from "../events/agent-words.js";
19
+ import { needRowText } from "../events/need-wake.js";
19
20
 
20
21
  // Folds a turn's frames into rows once, live and for the settled record alike, so a reopened chat matches what was on
21
22
  // screen. `tag` selects the stream read: undefined is the main turn, a tool-call id is the subagent it spawned; other
@@ -408,6 +409,9 @@ export class TranscriptFold {
408
409
  return this.park(event.requestId, { paymentOffer: { requestId: event.requestId, offer: event.offer, status: "pending" } });
409
410
  case "credential_offer":
410
411
  return this.park(event.requestId, { credentialOffer: { requestId: event.requestId, offer: event.offer, status: "pending" } });
412
+ case "need":
413
+ // A row of its own, not a park: the turn goes on, and the card reads the need's live state by its id.
414
+ return this.pushRow({ role: "notice", text: needRowText(event.need), need: event.need });
411
415
  case "resolved":
412
416
  // Releases the card; the answering window already froze it locally, so this is a no-op there.
413
417
  return this.patchParked(event.requestId, (row) => Object.assign(row, settledRequests(row, event.reply)));