@intentic/sandbox-contract 1.239.0 → 1.242.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 (310) hide show
  1. package/README.md +52 -16
  2. package/dist/agent-catalog.d.ts +5 -28
  3. package/dist/agent-catalog.d.ts.map +1 -1
  4. package/dist/agent-catalog.js +21 -157
  5. package/dist/agent-catalog.js.map +1 -1
  6. package/dist/agent-run-model.d.ts +3 -2
  7. package/dist/agent-run-model.d.ts.map +1 -1
  8. package/dist/agent-run-model.js +9 -6
  9. package/dist/agent-run-model.js.map +1 -1
  10. package/dist/agent-runtimes.d.ts +27 -0
  11. package/dist/agent-runtimes.d.ts.map +1 -0
  12. package/dist/agent-runtimes.js +113 -0
  13. package/dist/agent-runtimes.js.map +1 -0
  14. package/dist/approvals-execution.d.ts +11 -0
  15. package/dist/approvals-execution.d.ts.map +1 -0
  16. package/dist/approvals-execution.js +44 -0
  17. package/dist/approvals-execution.js.map +1 -0
  18. package/dist/arrival.d.ts +151 -0
  19. package/dist/arrival.d.ts.map +1 -0
  20. package/dist/arrival.js +56 -0
  21. package/dist/arrival.js.map +1 -0
  22. package/dist/card-status.d.ts +5 -0
  23. package/dist/card-status.d.ts.map +1 -0
  24. package/dist/card-status.js +74 -0
  25. package/dist/card-status.js.map +1 -0
  26. package/dist/chores/chores.d.ts.map +1 -1
  27. package/dist/chores/chores.js +1 -1
  28. package/dist/chores/chores.js.map +1 -1
  29. package/dist/command-classes.d.ts +0 -1
  30. package/dist/command-classes.d.ts.map +1 -1
  31. package/dist/command-classes.js +9 -2
  32. package/dist/command-classes.js.map +1 -1
  33. package/dist/command-run.d.ts +10 -0
  34. package/dist/command-run.d.ts.map +1 -0
  35. package/dist/command-run.js +46 -0
  36. package/dist/command-run.js.map +1 -0
  37. package/dist/contracts/agent.contract.d.ts +991 -771
  38. package/dist/contracts/agent.contract.d.ts.map +1 -1
  39. package/dist/contracts/agents.contract.d.ts +483 -1
  40. package/dist/contracts/agents.contract.d.ts.map +1 -1
  41. package/dist/contracts/agents.contract.js +20 -2
  42. package/dist/contracts/agents.contract.js.map +1 -1
  43. package/dist/contracts/approvals.contract.d.ts +99 -0
  44. package/dist/contracts/approvals.contract.d.ts.map +1 -0
  45. package/dist/contracts/approvals.contract.js +32 -0
  46. package/dist/contracts/approvals.contract.js.map +1 -0
  47. package/dist/contracts/ci.contract.d.ts +1 -4
  48. package/dist/contracts/ci.contract.d.ts.map +1 -1
  49. package/dist/contracts/ci.contract.js +1 -9
  50. package/dist/contracts/ci.contract.js.map +1 -1
  51. package/dist/contracts/claude.contract.d.ts +9 -0
  52. package/dist/contracts/claude.contract.d.ts.map +1 -1
  53. package/dist/contracts/cursor.contract.d.ts +6 -0
  54. package/dist/contracts/cursor.contract.d.ts.map +1 -1
  55. package/dist/contracts/git.contract.d.ts +63 -1
  56. package/dist/contracts/git.contract.d.ts.map +1 -1
  57. package/dist/contracts/git.contract.js +22 -4
  58. package/dist/contracts/git.contract.js.map +1 -1
  59. package/dist/contracts/grok.contract.d.ts +3 -0
  60. package/dist/contracts/grok.contract.d.ts.map +1 -1
  61. package/dist/contracts/keys.contract.d.ts +81 -0
  62. package/dist/contracts/keys.contract.d.ts.map +1 -0
  63. package/dist/contracts/keys.contract.js +51 -0
  64. package/dist/contracts/keys.contract.js.map +1 -0
  65. package/dist/contracts/panels.contract.d.ts +7 -0
  66. package/dist/contracts/panels.contract.d.ts.map +1 -1
  67. package/dist/contracts/prepush.contract.js +2 -2
  68. package/dist/contracts/providers.contract.d.ts +2 -0
  69. package/dist/contracts/providers.contract.d.ts.map +1 -1
  70. package/dist/contracts/runner.contract.d.ts +99 -101
  71. package/dist/contracts/runner.contract.d.ts.map +1 -1
  72. package/dist/contracts/safety.contract.d.ts +35 -0
  73. package/dist/contracts/safety.contract.d.ts.map +1 -0
  74. package/dist/contracts/safety.contract.js +32 -0
  75. package/dist/contracts/safety.contract.js.map +1 -0
  76. package/dist/contracts/sessions.contract.d.ts +211 -1
  77. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  78. package/dist/contracts/settings.contract.d.ts +30 -32
  79. package/dist/contracts/settings.contract.d.ts.map +1 -1
  80. package/dist/contracts/system.contract.d.ts +277 -1
  81. package/dist/contracts/system.contract.d.ts.map +1 -1
  82. package/dist/contracts/translator.contract.d.ts +31 -3
  83. package/dist/contracts/translator.contract.d.ts.map +1 -1
  84. package/dist/contracts/usage.contract.d.ts +29 -20
  85. package/dist/contracts/usage.contract.d.ts.map +1 -1
  86. package/dist/contracts/usage.contract.js +13 -0
  87. package/dist/contracts/usage.contract.js.map +1 -1
  88. package/dist/contracts/workspace.contract.d.ts +8 -0
  89. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  90. package/dist/conversation-ids.d.ts +2 -0
  91. package/dist/conversation-ids.d.ts.map +1 -1
  92. package/dist/conversation-ids.js +8 -0
  93. package/dist/conversation-ids.js.map +1 -1
  94. package/dist/credential-material.d.ts +1 -0
  95. package/dist/credential-material.d.ts.map +1 -1
  96. package/dist/credential-material.js +24 -2
  97. package/dist/credential-material.js.map +1 -1
  98. package/dist/definition.d.ts +51 -93
  99. package/dist/definition.d.ts.map +1 -1
  100. package/dist/definition.js +6 -28
  101. package/dist/definition.js.map +1 -1
  102. package/dist/events.d.ts +2947 -991
  103. package/dist/events.d.ts.map +1 -1
  104. package/dist/events.js +233 -82
  105. package/dist/events.js.map +1 -1
  106. package/dist/history-state.d.ts.map +1 -1
  107. package/dist/history-state.js +1 -0
  108. package/dist/history-state.js.map +1 -1
  109. package/dist/index.d.ts +2532 -1054
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +20 -5
  112. package/dist/index.js.map +1 -1
  113. package/dist/ingress-protocol.d.ts +13 -1
  114. package/dist/ingress-protocol.d.ts.map +1 -1
  115. package/dist/ingress-protocol.js +19 -0
  116. package/dist/ingress-protocol.js.map +1 -1
  117. package/dist/mentions.d.ts +3 -0
  118. package/dist/mentions.d.ts.map +1 -0
  119. package/dist/mentions.js +13 -0
  120. package/dist/mentions.js.map +1 -0
  121. package/dist/needs-action.d.ts +7 -0
  122. package/dist/needs-action.d.ts.map +1 -0
  123. package/dist/needs-action.js +3 -0
  124. package/dist/needs-action.js.map +1 -0
  125. package/dist/overlay-lint.d.ts +7 -0
  126. package/dist/overlay-lint.d.ts.map +1 -0
  127. package/dist/overlay-lint.js +68 -0
  128. package/dist/overlay-lint.js.map +1 -0
  129. package/dist/owner-ticket.d.ts +18 -0
  130. package/dist/owner-ticket.d.ts.map +1 -0
  131. package/dist/owner-ticket.js +51 -0
  132. package/dist/owner-ticket.js.map +1 -0
  133. package/dist/plan-pools.d.ts +12 -0
  134. package/dist/plan-pools.d.ts.map +1 -0
  135. package/dist/plan-pools.js +30 -0
  136. package/dist/plan-pools.js.map +1 -0
  137. package/dist/provider-specs.d.ts +228 -0
  138. package/dist/provider-specs.d.ts.map +1 -0
  139. package/dist/provider-specs.js +124 -0
  140. package/dist/provider-specs.js.map +1 -0
  141. package/dist/quick-model.d.ts.map +1 -1
  142. package/dist/quick-model.js +2 -1
  143. package/dist/quick-model.js.map +1 -1
  144. package/dist/runner-protocol.js +2 -2
  145. package/dist/runner-protocol.js.map +1 -1
  146. package/dist/runtime-state.d.ts +2 -2
  147. package/dist/runtime-state.d.ts.map +1 -1
  148. package/dist/runtime-state.js +1 -1
  149. package/dist/runtime-state.js.map +1 -1
  150. package/dist/safety-policy.d.ts +49 -0
  151. package/dist/safety-policy.d.ts.map +1 -0
  152. package/dist/safety-policy.js +52 -0
  153. package/dist/safety-policy.js.map +1 -0
  154. package/dist/schemas/agent.d.ts +21 -3
  155. package/dist/schemas/agent.d.ts.map +1 -1
  156. package/dist/schemas/agent.js +18 -8
  157. package/dist/schemas/agent.js.map +1 -1
  158. package/dist/schemas/agents.d.ts +19 -0
  159. package/dist/schemas/agents.d.ts.map +1 -1
  160. package/dist/schemas/agents.js +15 -0
  161. package/dist/schemas/agents.js.map +1 -1
  162. package/dist/schemas/approvals.d.ts +241 -0
  163. package/dist/schemas/approvals.d.ts.map +1 -0
  164. package/dist/schemas/approvals.js +63 -0
  165. package/dist/schemas/approvals.js.map +1 -0
  166. package/dist/schemas/automations.d.ts +5 -0
  167. package/dist/schemas/automations.d.ts.map +1 -1
  168. package/dist/schemas/ci.d.ts +5 -9
  169. package/dist/schemas/ci.d.ts.map +1 -1
  170. package/dist/schemas/ci.js +3 -10
  171. package/dist/schemas/ci.js.map +1 -1
  172. package/dist/schemas/computers.d.ts +31 -5
  173. package/dist/schemas/computers.d.ts.map +1 -1
  174. package/dist/schemas/computers.js +7 -5
  175. package/dist/schemas/computers.js.map +1 -1
  176. package/dist/schemas/environment.d.ts +11 -131
  177. package/dist/schemas/environment.d.ts.map +1 -1
  178. package/dist/schemas/environment.js +5 -42
  179. package/dist/schemas/environment.js.map +1 -1
  180. package/dist/schemas/git-history.d.ts +4 -0
  181. package/dist/schemas/git-history.d.ts.map +1 -1
  182. package/dist/schemas/git.d.ts +179 -0
  183. package/dist/schemas/git.d.ts.map +1 -1
  184. package/dist/schemas/git.js +39 -1
  185. package/dist/schemas/git.js.map +1 -1
  186. package/dist/schemas/panels.d.ts +21 -0
  187. package/dist/schemas/panels.d.ts.map +1 -1
  188. package/dist/schemas/panels.js +5 -0
  189. package/dist/schemas/panels.js.map +1 -1
  190. package/dist/schemas/plan-limits.d.ts +50 -3
  191. package/dist/schemas/plan-limits.d.ts.map +1 -1
  192. package/dist/schemas/plan-limits.js +11 -6
  193. package/dist/schemas/plan-limits.js.map +1 -1
  194. package/dist/schemas/provider-oauth.d.ts +6 -0
  195. package/dist/schemas/provider-oauth.d.ts.map +1 -1
  196. package/dist/schemas/provider-subscriptions.d.ts +2 -1
  197. package/dist/schemas/provider-subscriptions.d.ts.map +1 -1
  198. package/dist/schemas/provider-subscriptions.js +2 -1
  199. package/dist/schemas/provider-subscriptions.js.map +1 -1
  200. package/dist/schemas/settings.d.ts +29 -16
  201. package/dist/schemas/settings.d.ts.map +1 -1
  202. package/dist/schemas/settings.js +13 -20
  203. package/dist/schemas/settings.js.map +1 -1
  204. package/dist/schemas/system.d.ts +3 -0
  205. package/dist/schemas/system.d.ts.map +1 -1
  206. package/dist/schemas/system.js +5 -1
  207. package/dist/schemas/system.js.map +1 -1
  208. package/dist/schemas/workspace-repos.d.ts +14 -0
  209. package/dist/schemas/workspace-repos.d.ts.map +1 -1
  210. package/dist/schemas/workspace-repos.js +5 -0
  211. package/dist/schemas/workspace-repos.js.map +1 -1
  212. package/dist/schemas/workspace-tree.d.ts +1 -0
  213. package/dist/schemas/workspace-tree.d.ts.map +1 -1
  214. package/dist/schemas/workspace-tree.js +3 -0
  215. package/dist/schemas/workspace-tree.js.map +1 -1
  216. package/dist/transcript-fold.d.ts +31 -0
  217. package/dist/transcript-fold.d.ts.map +1 -0
  218. package/dist/transcript-fold.js +457 -0
  219. package/dist/transcript-fold.js.map +1 -0
  220. package/dist/workspace-state.d.ts +20 -3
  221. package/dist/workspace-state.d.ts.map +1 -1
  222. package/dist/workspace-state.js +22 -2
  223. package/dist/workspace-state.js.map +1 -1
  224. package/package.json +27 -5
  225. package/src/agent-catalog.test.ts +1 -1
  226. package/src/agent-catalog.ts +76 -555
  227. package/src/agent-run-model.test.ts +31 -15
  228. package/src/agent-run-model.ts +26 -12
  229. package/src/agent-runtimes.ts +419 -0
  230. package/src/approvals-execution.ts +96 -0
  231. package/src/arrival.ts +160 -0
  232. package/src/capability-ledger.test.ts +6 -4
  233. package/src/card-status.ts +93 -0
  234. package/src/chores/chores.ts +6 -3
  235. package/src/command-classes.test.ts +25 -0
  236. package/src/command-classes.ts +47 -5
  237. package/src/command-run.ts +78 -0
  238. package/src/contract-lock.test.ts +1 -1
  239. package/src/contract-lock.ts +2 -2
  240. package/src/contracts/agents.contract.ts +35 -2
  241. package/src/contracts/approvals.contract.ts +37 -0
  242. package/src/contracts/ci.contract.ts +0 -13
  243. package/src/contracts/git.contract.ts +28 -3
  244. package/src/contracts/keys.contract.ts +79 -0
  245. package/src/contracts/prepush.contract.ts +3 -3
  246. package/src/contracts/safety.contract.ts +48 -0
  247. package/src/contracts/usage.contract.ts +22 -0
  248. package/src/conversation-ids.test.ts +31 -1
  249. package/src/conversation-ids.ts +37 -3
  250. package/src/credential-material.test.ts +69 -1
  251. package/src/credential-material.ts +86 -5
  252. package/src/definition.ts +25 -53
  253. package/src/events.ts +508 -198
  254. package/src/history-state.ts +6 -0
  255. package/src/index.ts +24 -5
  256. package/src/ingress-protocol.ts +52 -1
  257. package/src/mentions.test.ts +22 -0
  258. package/src/mentions.ts +25 -0
  259. package/src/needs-action.ts +14 -0
  260. package/src/overlay-lint.test.ts +89 -0
  261. package/src/overlay-lint.ts +116 -0
  262. package/src/owner-ticket.test.ts +54 -0
  263. package/src/owner-ticket.ts +102 -0
  264. package/src/plan-pools.test.ts +71 -0
  265. package/src/plan-pools.ts +82 -0
  266. package/src/provider-specs.test.ts +130 -0
  267. package/src/provider-specs.ts +352 -0
  268. package/src/quick-model.ts +9 -5
  269. package/src/runner-protocol.ts +2 -2
  270. package/src/runtime-state.ts +6 -6
  271. package/src/safety-policy.ts +165 -0
  272. package/src/schemas/agent.ts +62 -17
  273. package/src/schemas/agents.ts +71 -0
  274. package/src/schemas/approvals.ts +174 -0
  275. package/src/schemas/ci.ts +19 -26
  276. package/src/schemas/computers.ts +52 -11
  277. package/src/schemas/environment.ts +21 -100
  278. package/src/schemas/git.ts +121 -6
  279. package/src/schemas/issues.ts +1 -1
  280. package/src/schemas/panels.ts +20 -0
  281. package/src/schemas/plan-limits.ts +58 -11
  282. package/src/schemas/provider-subscriptions.ts +7 -2
  283. package/src/schemas/settings.ts +67 -93
  284. package/src/schemas/system.ts +12 -1
  285. package/src/schemas/workspace-repos.ts +9 -0
  286. package/src/schemas/workspace-tree.ts +11 -0
  287. package/src/transcript-fold.test.ts +493 -0
  288. package/src/transcript-fold.ts +655 -0
  289. package/src/workspace-state.test.ts +77 -29
  290. package/src/workspace-state.ts +99 -31
  291. package/dist/contracts/drafts.contract.d.ts +0 -57
  292. package/dist/contracts/drafts.contract.d.ts.map +0 -1
  293. package/dist/contracts/drafts.contract.js +0 -32
  294. package/dist/contracts/drafts.contract.js.map +0 -1
  295. package/dist/definition-action.d.ts +0 -7
  296. package/dist/definition-action.d.ts.map +0 -1
  297. package/dist/definition-action.js +0 -3
  298. package/dist/definition-action.js.map +0 -1
  299. package/dist/publish-drafts.d.ts +0 -7
  300. package/dist/publish-drafts.d.ts.map +0 -1
  301. package/dist/publish-drafts.js +0 -20
  302. package/dist/publish-drafts.js.map +0 -1
  303. package/dist/schemas/drafts.d.ts +0 -84
  304. package/dist/schemas/drafts.d.ts.map +0 -1
  305. package/dist/schemas/drafts.js +0 -44
  306. package/dist/schemas/drafts.js.map +0 -1
  307. package/src/contracts/drafts.contract.ts +0 -35
  308. package/src/definition-action.ts +0 -8
  309. package/src/publish-drafts.ts +0 -59
  310. package/src/schemas/drafts.ts +0 -108
@@ -0,0 +1,174 @@
1
+ // approvals: things the agent prepared and may not do until the owner says yes (.intentic/config/approvals/<id>.json)
2
+ import { z } from "zod";
3
+ import { entryId } from "./internal.js";
4
+
5
+ /* ONE QUEUE FOR EVERYTHING THAT WAITS ON A YES, and the test for what belongs in it: the agent prepared an exact
6
+ * thing, the owner's click releases it, a machine then carries out precisely that thing, and the outcome is
7
+ * written back. A post is the first such thing and was the whole of this file (it was `drafts`); a hotel
8
+ * booking, a payment, a message sent under the owner's name are the same shape with a different payload, and
9
+ * each of them arriving as its own inbox is how a product grows five tiles that all say "approve".
10
+ *
11
+ * So the record is an ENVELOPE plus a KIND. The envelope is what every approval shares and what the queue, the
12
+ * badge and the daemon act on: who it acts as, when it is due, where it stands, what happened. The kind is what
13
+ * the row draws and the daemon dispatches on, a discriminated union so a new kind is one variant here, one
14
+ * body component in the extension and one executor in the daemon, and nothing else moves.
15
+ *
16
+ * What does NOT belong here, and why the line is drawn where it is: an inbox of FACTS (the issues queue, where
17
+ * a crash arrived and the owner's verbs are resolve / ignore / investigate) has no prepared thing to release,
18
+ * and a hold a RUNNING turn is blocked on (a permission card, a spend offer) lives in the conversation, because
19
+ * a queue is the wrong latency for a click somebody is spinning on.
20
+ *
21
+ * One JSON file per approval. The AGENT creates them with its normal file tools, it can't call daemon routes,
22
+ * the same split as the environment proposal, while the daemon edits/deletes them on the owner's behalf, so
23
+ * the two writers never share a file. The id IS the filename (entryId charset ⇒ path-safe); the body never
24
+ * carries it. */
25
+
26
+ export const ApprovalKindSchema = z.enum(["post", "action"]);
27
+ export type ApprovalKind = z.infer<typeof ApprovalKindSchema>;
28
+
29
+ /* proposed (agent) → approved (owner) → running (executor, set BEFORE acting so a dead turn can't do it twice)
30
+ * → done | failed. Reject = delete the file; retry = re-approve a failed one. The names are deliberately not a
31
+ * post's ("posting", "posted"): they are read by every kind, and a booking is not "posted". */
32
+ export const ApprovalStatusSchema = z.enum(["proposed", "approved", "running", "done", "failed"]);
33
+ export type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;
34
+
35
+ /* THE ENVELOPE, spread into every variant below (zod's discriminated union wants flat objects, not an extended
36
+ * base), which is also why a variant's own fields sit beside these rather than under a `payload` key: the file
37
+ * an agent writes stays one level deep, and "which fields are mine" is answered by the variant's schema. */
38
+ const envelope = {
39
+ /* WHOSE NAME THIS ACTS UNDER, a PersonaSchema id, handed to the executing turn as AgentTurnSchema.actsAs.
40
+ * Required in practice for every post outside DIRECT_PUBLISH_PLATFORMS, and the reason is the whole shape
41
+ * of turnPersona: acting through a browser needs a logged-in account, and an UNATTENDED turn that names no
42
+ * persona is denied every account there is. Without this field the executor could only wake such a turn,
43
+ * one structurally unable to reach the login the post needs, which read from inside the turn as "this
44
+ * account is not connected" and cost two approved posts before anyone traced it back here.
45
+ *
46
+ * A PERSONA RATHER THAN AN ACCOUNT ID, because that is the vocabulary the rest of the system already speaks:
47
+ * `actsAs` is the only pin turnPersona honours, and a card carries the workspace scope the turn also needs to
48
+ * write this file's own status back. Naming the account directly would invent a second way to say the same
49
+ * thing, and the two would disagree the first time a card's accounts changed.
50
+ *
51
+ * The daemon never guesses it. One site can be connected several times over, five Reddit logins here, and
52
+ * picking for the owner means picking wrong in public, with no undo. A post that needs a turn and names
53
+ * nobody is failed with that sentence instead of sent. An ACTION that names nobody runs with no accounts,
54
+ * which is a legitimate choice for work that needs none. */
55
+ actsAs: entryId
56
+ .optional()
57
+ .describe(
58
+ "Whose name it acts under. Needed for anything that requires being logged in, because an unwatched turn naming nobody is allowed no account at all. Never guessed: one site can be connected five times over, and picking for you means picking wrong in public with no undo.",
59
+ ),
60
+ // Suggested time (epoch ms, the at/nextRun convention). Optional, the agent may propose without a date and
61
+ // the owner sets one at approval; an approved item with no date is dated one hold ahead by the daemon.
62
+ scheduledAt: z
63
+ .number()
64
+ .optional()
65
+ .describe(
66
+ "When it should happen, in milliseconds. An agent may propose without one and you set it when approving; an approved item with no time goes after a short countdown you can still stop.",
67
+ ),
68
+ // Agent-written files only need the kind's own fields; status defaults, the rest are optional, so a
69
+ // well-formed proposal never lands in `invalid` just for omitting bookkeeping fields.
70
+ status: ApprovalStatusSchema.default("proposed").describe(
71
+ "Where it is: proposed by the agent, approved by you, being carried out, done, or failed. Rejecting is deleting it; retrying is approving a failed one again.",
72
+ ),
73
+ createdAt: z.number().optional().describe("When it was written, in milliseconds."),
74
+ // When execution STARTED, stamped with status "running". The executor needs it to tell a run that is under
75
+ // way from one whose turn died mid-flight, and those two are indistinguishable from the due time: a post
76
+ // scheduled for last week is not a post that has been sending since last week.
77
+ startedAt: z
78
+ .number()
79
+ .optional()
80
+ .describe(
81
+ "When it started being carried out, in milliseconds. Needed to tell a run that is under way from one whose turn died mid-flight, which the scheduled time cannot.",
82
+ ),
83
+ finishedAt: z.number().optional().describe("When it was done, in milliseconds."),
84
+ // What came back, when something did: a post's own address, a booking's confirmation. The one thing a done
85
+ // row can offer that reading the proposal cannot: the result itself, to go and look at. A URL is drawn as
86
+ // a link; anything else as the sentence it is.
87
+ result: z
88
+ .string()
89
+ .optional()
90
+ .describe(
91
+ "What came back, when something did: the post's own address, a confirmation number. The one thing a finished item can offer that reading it cannot.",
92
+ ),
93
+ // Why it failed; set with status "failed". Written for the owner to read in the queue, so it is a sentence
94
+ // rather than a code.
95
+ error: z.string().optional().describe("Why it failed, written as a sentence for a person to read rather than as a code."),
96
+ };
97
+
98
+ /* A POST, the first kind: words that go out in public under the owner's name and cannot be recalled. */
99
+ export const PostApprovalSchema = z.object({
100
+ kind: z.literal("post").describe("A post to publish somewhere."),
101
+ // Which skill posts it: "x" | "reddit" | "youtube" | "discord" | …, a bare string so new platforms need
102
+ // no contract change; an unknown platform simply fails at posting time.
103
+ platform: z
104
+ .string()
105
+ .min(1)
106
+ .describe("Where it should go. A plain name, so a new site needs no change here; an unknown one simply fails when it tries to post."),
107
+ content: z.string().min(1).describe("The post itself."),
108
+ // Reddit posts / YouTube uploads need one.
109
+ title: z.string().optional().describe("A title, where the site wants one."),
110
+ /* Where on the platform: subreddit / Discord channel id / community. OR the URL of the thing this post
111
+ * replies to. A URL target means the post is a reply, and on reddit the difference between a thread's
112
+ * address and one comment's permalink is the difference between talking to the room and answering the
113
+ * person: the publisher opens exactly this and replies where it lands. */
114
+ target: z
115
+ .string()
116
+ .optional()
117
+ .describe(
118
+ "Where on the site: a community, a channel. Or the address of the thing this replies to, in which case it is a reply, and on some sites the difference between a thread's address and one comment's is the difference between talking to the room and answering the person.",
119
+ ),
120
+ // Workspace-relative attachment paths, e.g. ".intentic/config/approvals/media/chart.png".
121
+ media: z.array(z.string()).optional().describe("Anything to attach, as workspace paths."),
122
+ ...envelope,
123
+ });
124
+ export type PostApproval = z.infer<typeof PostApprovalSchema>;
125
+
126
+ /* AN ACTION, the general kind: anything else the agent is about to do that it should not do unasked. A
127
+ * booking, a purchase, a message to a person, a deletion that cannot be undone. The agent writes what it will
128
+ * do in words the owner reads (`summary`, `details`), and what it will tell ITSELF to do once released
129
+ * (`instructions`), because the turn that carries it out is a fresh one, hours later, with none of the
130
+ * conversation that led here. Executed by an agent turn, always: there is no typed door for "whatever it is". */
131
+ export const ActionApprovalSchema = z.object({
132
+ kind: z.literal("action").describe("Something the agent will do once you say so."),
133
+ summary: z.string().min(1).max(200).describe("What will happen, in one line: the row's headline and the confirm dialog's item."),
134
+ // Markdown. The specifics a yes is being asked for: the hotel and the dates, the amount and the account,
135
+ // the exact message and its recipient. Everything the owner has to see to be able to say no.
136
+ details: z.string().optional().describe("The specifics, as Markdown: everything you would want to see before saying yes."),
137
+ // What the executing turn is told. Written by the agent for its later self, so it names files, ids and
138
+ // steps rather than saying "do what we discussed".
139
+ instructions: z
140
+ .string()
141
+ .min(1)
142
+ .describe(
143
+ "What to do once approved, written for the fresh turn that will do it: names, ids and steps, since it has none of this conversation.",
144
+ ),
145
+ ...envelope,
146
+ });
147
+ export type ActionApproval = z.infer<typeof ActionApprovalSchema>;
148
+
149
+ // The on-disk file body, whichever kind it is.
150
+ export const ApprovalSchema = z.discriminatedUnion("kind", [PostApprovalSchema, ActionApprovalSchema]);
151
+ export type Approval = z.infer<typeof ApprovalSchema>;
152
+
153
+ // The list row / upsert input: the file body plus its filename id.
154
+ const withId = { id: entryId.describe("The approval's id.") };
155
+ export const PostApprovalSummarySchema = PostApprovalSchema.extend(withId);
156
+ export type PostApprovalSummary = z.infer<typeof PostApprovalSummarySchema>;
157
+ export const ActionApprovalSummarySchema = ActionApprovalSchema.extend(withId);
158
+ export type ActionApprovalSummary = z.infer<typeof ActionApprovalSummarySchema>;
159
+ export const ApprovalSummarySchema = z.discriminatedUnion("kind", [PostApprovalSummarySchema, ActionApprovalSummarySchema]);
160
+ export type ApprovalSummary = z.infer<typeof ApprovalSummarySchema>;
161
+
162
+ // `invalid` = filenames that failed to parse. Agent-written files are a trust boundary, without this a typo'd
163
+ // approval (or one of a kind this daemon does not know) would silently never run.
164
+ export const ApprovalsListSchema = z.object({
165
+ approvals: z.array(ApprovalSummarySchema).describe("The queue."),
166
+ invalid: z
167
+ .array(z.string())
168
+ .describe(
169
+ "Files that could not be read at all, or name a kind this daemon does not know. Listed rather than skipped, because an agent writes these files directly and a malformed one would otherwise never run and never say why.",
170
+ ),
171
+ });
172
+ export type ApprovalsList = z.infer<typeof ApprovalsListSchema>;
173
+ // entryId, not a bare string: the id becomes a filename under .intentic/config/approvals/.
174
+ export const ApprovalIdParamSchema = z.object({ id: entryId.describe("Which approval.") });
package/src/schemas/ci.ts CHANGED
@@ -124,22 +124,8 @@ export const CiRunsResponseSchema = z.object({
124
124
  repos: z.array(CiRepoSchema).describe("Which workspace repositories are wired to a forge, and how each one's notifications are set up."),
125
125
  // Newest first, across all mapped repos.
126
126
  runs: z.array(PipelineRunSchema).describe("Runs across all of them, newest first."),
127
- // When the owner last opened the pipelines view. Rides the runs response so the rail can decide what is
128
- // NEW without a second call, a breakage older than this has already been seen and must not badge again.
129
- // Absent ⇒ never opened, so everything counts as unseen.
130
- seenAt: z
131
- .number()
132
- .optional()
133
- .describe(
134
- "When this was last looked at, in milliseconds, so a badge can tell new breakages from ones already read without a second call. Absent means never, so everything counts as new.",
135
- ),
136
127
  });
137
128
  export type CiRunsResponse = z.infer<typeof CiRunsResponseSchema>;
138
- // Stamping the view as read hands back the timestamp it wrote, so the client updates without a refetch.
139
- export const CiSeenResponseSchema = z.object({
140
- seenAt: z.number().describe("The timestamp that was written, handed back so a caller can update without asking again."),
141
- });
142
- export type CiSeenResponse = z.infer<typeof CiSeenResponseSchema>;
143
129
  // rerun/cancel/fix address a run by repo + vendor id; the daemon re-resolves repo → project + token per call,
144
130
  // so a stale card can't act on a project the workspace no longer maps to.
145
131
  export const CiRunParamSchema = z.object({
@@ -181,21 +167,28 @@ export type CiFixResponse = z.infer<typeof CiFixResponseSchema>;
181
167
  * dialog that started it, and is gone. Nothing survives a daemon restart because nothing needs to: the next
182
168
  * push asks again. */
183
169
 
184
- /* Where a run is.
170
+ /* A COMMAND RUN ON A CLICK, in a visible terminal, with a verdict and a quotable tail: the ONE shape for
171
+ * everything the daemon runs because the owner pressed a button and then has to be told how it went. The
172
+ * pre-push check is one (above); the push itself is another (PushRunSchema, schemas/git.ts), because a push
173
+ * runs the repository's own pre-push hook, which is a suite in disguise. One shape rather than one per
174
+ * moment, so a field the browser learns to read for the check (the terminal to open, the tail to quote, the
175
+ * kill to distinguish from the timeout) is read for the push by the same code, and cannot drift.
176
+ *
177
+ * Where a run is.
185
178
  *
186
179
  * idle , nothing has run in this daemon's life, or the last run was cleared.
187
- * running , the check is live. Its output is the terminal's (`session`), not this object's.
188
- * passed , exited 0. The push goes.
189
- * failed , exited non-zero, or was killed by prepushTimeoutMs (`timedOut`). The state a fix answers.
190
- * error , the check could not run at all: the command was not spawnable. NOT a fix-able failure, because
191
- * there is nothing wrong with the code, the command is misconfigured, and saying "tests failed"
192
- * would send an agent hunting a bug that isn't there.
180
+ * running , the command is live. Its output is the terminal's (`session`), not this object's.
181
+ * passed , exited 0. The push goes (or went).
182
+ * failed , exited non-zero, or was killed by its ceiling (`timedOut`). The state a fix answers.
183
+ * error , the command could not run at all: it was not spawnable. NOT a fix-able failure, because there
184
+ * is nothing wrong with the code, the command is misconfigured, and saying "tests failed" would
185
+ * send an agent hunting a bug that isn't there.
193
186
  * cancelled, the user stopped the run.
194
187
  */
195
- export const PrepushStatusSchema = z.enum(["idle", "running", "passed", "failed", "error", "cancelled"]);
196
- export type PrepushStatus = z.infer<typeof PrepushStatusSchema>;
197
- export const PrepushRunSchema = z.object({
198
- status: PrepushStatusSchema.describe(
188
+ export const CommandRunStatusSchema = z.enum(["idle", "running", "passed", "failed", "error", "cancelled"]);
189
+ export type CommandRunStatus = z.infer<typeof CommandRunStatusSchema>;
190
+ export const CommandRunSchema = z.object({
191
+ status: CommandRunStatusSchema.describe(
199
192
  "Where the run is. Failed and error are deliberately different: failed means the code is wrong, error means the command could not be run at all, and calling the second one a test failure would send an agent hunting a bug that is not there.",
200
193
  ),
201
194
  // The command this run executed, echoed rather than read back from settings: a result read after the
@@ -232,4 +225,4 @@ export const PrepushRunSchema = z.object({
232
225
  "The end of what it printed, as plain text with the colour codes and redrawn progress lines resolved away. The end rather than the beginning, because a suite's verdict is at the end. Empty while it runs, and for one that was killed.",
233
226
  ),
234
227
  });
235
- export type PrepushRun = z.infer<typeof PrepushRunSchema>;
228
+ export type CommandRun = z.infer<typeof CommandRunSchema>;
@@ -133,7 +133,19 @@ export type MachineFlowLine = z.infer<typeof MachineFlowLineSchema>;
133
133
  *
134
134
  * The machine still enforces its own switches. "Run commands" being off comes back as its own refusal, in its
135
135
  * own words, naming the control to flip — exactly as it does for the sandbox ops. */
136
- export const MachineCommandSchema = z.enum(["mirror-off", "mirror-on"]);
136
+ /* THE SET, and why the file-sync half of it is here beside the mirroring half.
137
+ *
138
+ * Both are the same gesture to the person clicking: something this computer is doing for this sandbox, turned
139
+ * off or on from the row that describes it. They were split for a while by nothing but which one had been built
140
+ * — mirroring had a button and pausing a file sync had a paragraph telling you to go and find a terminal — and
141
+ * that is exactly the gap this door exists to close.
142
+ *
143
+ * `sync-unpair` is the one that DESTROYS something, and it is deliberately the machine's `sync uninstall
144
+ * --sandbox`, not this side's idea of unpairing: the agent terminates both Mutagen sessions, drops the local
145
+ * pairing and self-revokes its enrollment on the way out, so the machine cleans up after itself rather than
146
+ * leaving a sandbox to guess what it managed to do. Revoking from the SANDBOX side (an unreachable machine, a
147
+ * laptop that is never coming back) is a different act and a different route, see the enrollment revoke. */
148
+ export const MachineCommandSchema = z.enum(["mirror-off", "mirror-on", "sync-pause", "sync-resume", "sync-unpair"]);
137
149
  export type MachineCommand = z.infer<typeof MachineCommandSchema>;
138
150
  /* Which paired sandbox the command acts on: the machine's own id for it, as it appears in that machine's report,
139
151
  * so nothing here has to re-derive the sanitizing the agent applied. Absent means every sandbox that machine
@@ -195,7 +207,7 @@ export const MachinePairingSchema = z.object({
195
207
  /* The SECOND session's word, the one-way mirror carrying the sandbox's state dir down (sync's backupSpec).
196
208
  * Reported separately rather than folded into the status above, because the two fail independently and mean
197
209
  * different things: the first going quiet stops the owner's edits moving, the second going quiet stops their
198
- * personas, skills, automations, drafts and transcripts from surviving the sandbox. A backup that is not
210
+ * personas, skills, automations, approvals and transcripts from surviving the sandbox. A backup that is not
199
211
  * running is only dangerous while nobody knows, so it gets its own word on the line. */
200
212
  backupStatus: z.string().optional(),
201
213
  });
@@ -294,13 +306,39 @@ export type ComputerGap = z.infer<typeof ComputerGapSchema>;
294
306
  * enrolled ssh key's comment vs. the capability id the user typed, so the two are reconciled on the `hostname`
295
307
  * their reports agree on, and left as separate rows when there is nothing to reconcile them by. Guessing that two
296
308
  * differently-named machines are the same one would merge two people's laptops on a shared sandbox. */
309
+ /* THE DESKTOP-SYNC ENROLLMENT BEHIND A ROW, which used to be a boolean and could not be.
310
+ *
311
+ * `syncEnrolled: true` answered "is this machine paired" and nothing a reader standing in front of the row
312
+ * actually asks next: WHICH half of desktop sync it holds (files and ports, or ports alone), whether it has
313
+ * ever used the enrollment, and how to name it when they want it gone. Those three lived on /system/sync
314
+ * instead, as one machine's worth of `syncingFrom` plus a list of `mirroredBy` names, which is the sandbox-level
315
+ * shape this view exists to stop being: one card claiming a sandbox has A desktop sync, over a list of the
316
+ * several computers that actually do.
317
+ *
318
+ * `machine` is the enrollment's own name for the box (the ssh key's comment). It is what the reports are filed
319
+ * under, and it is the id the revoke route takes — the same string, so a row can revoke exactly the enrollment
320
+ * it is drawn from. Two machines that present the same comment share one enrollment identity throughout the
321
+ * daemon (reports included); that is a pre-existing property of naming machines by their key comment, and this
322
+ * field inherits it rather than inventing a second identity that would disagree with the first. */
323
+ export const ComputerSyncSchema = z.object({
324
+ machine: z.string(),
325
+ /* Which half. "sync" is files AND ports and is SINGLE-HOLDER for the sandbox; "mirror" is ports only and any
326
+ * number of machines may hold one. The row says which, because "your laptop is paired" is read as the first
327
+ * by somebody who has the second, and then their files are not where they expect them. */
328
+ mode: z.enum(["sync", "mirror"]),
329
+ // When this machine last USED its enrollment (its watcher's own polls stamp it). Absent on one that never
330
+ // has, which is exactly what a setup that did not finish leaves behind, and must not read as healthy.
331
+ seenAt: z.number().optional(),
332
+ });
333
+ export type ComputerSync = z.infer<typeof ComputerSyncSchema>;
297
334
  export const ComputerSchema = z.object({
298
335
  // Stable row key: the reported hostname when either door produced one, else the name that door knows it by.
299
336
  key: z.string(),
300
337
  // What to call it on screen, the user's own name for the machine wherever one exists.
301
338
  label: z.string(),
302
- // Whether a desktop-sync enrollment exists for this machine (it syncs files and/or mirrors ports).
303
- syncEnrolled: z.boolean(),
339
+ // The desktop-sync enrollment this machine holds with this sandbox, absent when it has none (a computer
340
+ // reached only through its `host` capability).
341
+ sync: ComputerSyncSchema.optional(),
304
342
  // The host capability's id, when this machine is also a connected computer. Absent otherwise.
305
343
  hostId: z.string().optional(),
306
344
  // Host-capability liveness. Absent when there is no host capability, which is NOT the same as offline.
@@ -327,9 +365,16 @@ export const ComputerSchema = z.object({
327
365
  });
328
366
  export type Computer = z.infer<typeof ComputerSchema>;
329
367
  export const ComputersListSchema = z.object({ computers: z.array(ComputerSchema) });
330
- // GET /system/sync, the enrollment state the Desktop sync card is built on, plus what each enrolled machine has
331
- // said about itself. `machines` is optional because a daemon predating machine reports omits it, and an SPA is
332
- // routinely newer than the daemon it is pointed at during a rolling update.
368
+ /* GET /system/sync: what desktop sync is doing for this sandbox, WITHOUT naming any one machine as the answer.
369
+ *
370
+ * It used to carry `syncingFrom` + `syncSeenAt` + `mirroredBy`, which is the enrollment list flattened into one
371
+ * holder and a list of everybody else — the shape a card that believed a sandbox has A desktop sync needed, and
372
+ * the reason that card kept restating facts the Computers list beside it already had per machine. Every one of
373
+ * those now rides on the machine's own row (ComputerSync), where a reader can act on it.
374
+ *
375
+ * What is left is what is genuinely about the SANDBOX rather than about any computer: whether sync is possible
376
+ * here at all, whether anything at all is enrolled, and the raw reports, which is the cheap ambient read the
377
+ * rail's badge lives on (it must never fan out to somebody's laptop just to decide whether to draw a chip). */
333
378
  export const SyncStatusSchema = z.object({
334
379
  enrolled: z.boolean(),
335
380
  /* Whether this sandbox can do desktop sync at all. It used to be the SSH hostname the laptop would dial, and
@@ -338,10 +383,6 @@ export const SyncStatusSchema = z.object({
338
383
  * surface now, so a sandbox that can answer this read can also sync. Kept as a field rather than assumed,
339
384
  * because the card branches on it and a daemon too old to say is one that should not be offered sync. */
340
385
  available: z.boolean().optional(),
341
- // The single machine holding file sync, and when its heartbeat last landed.
342
- syncingFrom: z.string().optional(),
343
- syncSeenAt: z.number().optional(),
344
- mirroredBy: z.array(z.string()).optional(),
345
386
  machines: z.array(MachineReportSchema).optional(),
346
387
  });
347
388
  export type SyncStatus = z.infer<typeof SyncStatusSchema>;
@@ -70,8 +70,22 @@ export const EnvironmentRecurringSchema = z.object({
70
70
  live: z.boolean(),
71
71
  drafted: z.boolean().optional(),
72
72
  declined: z.boolean().optional(),
73
+ /* The Dockerfile step that would bake this tool, when its ecosystem has one that follows from the name
74
+ * alone (auto-drafts.ts `stepFor`). Sent rather than re-derived in the browser because it is the SAME string
75
+ * the owner would approve, and because its presence is the honest answer to "can this be fixed by pressing a
76
+ * button?": absent means the fix is a judgement call (which pip package, replaying which installer) and the
77
+ * card offers an agent instead. */
78
+ step: z.string().optional(),
73
79
  });
74
80
  export type EnvironmentRecurring = z.infer<typeof EnvironmentRecurringSchema>;
81
+ /* The owner's answer to ONE line of that list. `adopt` writes the tool's overlay draft now, without waiting for
82
+ * the sweep to re-earn it; `dismiss` tombstones it so nothing proposes it again; `restore` undoes a dismissal.
83
+ * One route rather than three because it is one decision surface about one ledger entry. */
84
+ export const EnvironmentRuntimeDecisionSchema = z.object({
85
+ tool: z.string().min(1),
86
+ decision: z.enum(["adopt", "dismiss", "restore"]),
87
+ });
88
+ export type EnvironmentRuntimeDecision = z.infer<typeof EnvironmentRuntimeDecisionSchema>;
75
89
  export const EnvironmentSchema = z.object({
76
90
  proposal: environmentFileSchema.optional(),
77
91
  // The owner-approved agent-written custom section (.intentic/config/environment.custom.Dockerfile).
@@ -142,116 +156,23 @@ export const EnvironmentItemSchema = z.object({
142
156
  export type EnvironmentItem = z.infer<typeof EnvironmentItemSchema>;
143
157
  export const EnvironmentContentsSchema = z.object({ items: z.array(EnvironmentItemSchema) });
144
158
  export type EnvironmentContents = z.infer<typeof EnvironmentContentsSchema>;
145
- /* ---- portability: exporting a sandbox's environment and restoring it into a fresh one ----
159
+ /* ---- portability: exporting a sandbox's environment ----
146
160
  *
147
161
  * A sandbox is four stores, not one: `/work` (the workspace and the daemon's manifests), `/history` (every
148
162
  * repo's real git dir, the fleet registry, the ledgers), the CONTAINER (the built overlay image plus the env
149
163
  * the run contract replays) and the AI-provider credential root. A bundle carries the first two, declared entry
150
164
  * by entry in WORKSPACE_STATE_FILES / HISTORY_STATE_FILES. It cannot carry the other two, and the honest
151
- * consequence is that an import ends in a REPORT rather than a claim of equivalence, the container has no
165
+ * consequence is that taking one in ends in a REPORT rather than a claim of equivalence, the container has no
152
166
  * docker socket, so only the host can rebuild the image the overlay describes.
153
167
  *
154
168
  * The bundle's manifest (BundleManifestSchema) lives in definition.ts beside the sandbox DEFINITION it embeds:
155
169
  * a bundle is definition + state, and keeping the two schemas together is what keeps the two export doors from
156
- * drifting into different answers about what an environment is. */
157
-
158
- // What a restore actually did. `needsAction` is the part that matters: the environment rebuild command, the
159
- // credentials to re-enter, the logins to redo, each one a thing the target cannot do for itself.
160
- export const ImportReportSchema = z.object({
161
- restored: z.object({ workspaceFiles: z.number(), historyFiles: z.number(), repos: z.array(z.string()), bytes: z.number() }),
162
- // Entries the bundle carried that this daemon refused to write (an identity file, an escaping path), empty
163
- // for any bundle a matching exporter produced, and a tamper signal when it is not.
164
- refused: z.array(z.string()),
165
- needsAction: z.array(z.object({ subject: z.string(), detail: z.string() })),
166
- });
167
- export type ImportReport = z.infer<typeof ImportReportSchema>;
168
- /* ---- migrations: importing a FOREIGN assistant's setup (Hermes, OpenClaw) ----
169
- *
170
- * A different crossing than a bundle restore, and deliberately a different surface: a bundle is our own format,
171
- * re-derived entry by entry against the state manifests, while a migration reads a directory some OTHER
172
- * program laid out (`~/.hermes`) and TRANSLATES it into native things, skills, automations, capabilities,
173
- * merged memory. Nothing foreign is executed or copied verbatim into daemon state; every item lands through the
174
- * same write paths the settings/skills/automations/capabilities surfaces use, which is what keeps an imported
175
- * setup editable and deletable in the ordinary UI the day after (docs/assistant-import-design.md).
170
+ * drifting into different answers about what an environment is.
176
171
  *
177
- * The flow is PREVIEW-FIRST, mirroring what these tools' own `migrate` commands taught their users to expect:
178
- * `plan` parses the uploaded archive into an itemized checklist and holds the upload in memory under a token;
179
- * `apply` names the ticked item ids and the token. The plan is RE-DERIVED from the held archive at apply, the
180
- * wire plan is a rendering for the owner, never the input the write trusts (restore.ts's rule, kept). */
181
- export const MigrationSourceSchema = z.enum(["hermes", "openclaw"]);
182
- export type MigrationSource = z.infer<typeof MigrationSourceSchema>;
183
- // What an item becomes here, not what it was there, the apply loop dispatches on this, and the checklist
184
- // groups by it so the owner reads "3 skills, 2 automations" rather than a foreign directory listing.
185
- export const MigrationTargetSchema = z.enum(["memory", "skill", "automation", "capability", "secret", "file"]);
186
- export type MigrationTarget = z.infer<typeof MigrationTargetSchema>;
187
- export const MigrationItemSchema = z.object({
188
- // Deterministic (derived from the source artifact, e.g. `skill:weather`), so the ids the owner ticked name
189
- // the same items when the plan is re-derived at apply.
190
- id: z.string(),
191
- target: MigrationTargetSchema,
192
- // The checklist line, plain words: "Skill, weather", "Nightly digest (9:00 every day)".
193
- label: z.string(),
194
- detail: z.string().optional(),
195
- /* The default tick. False marks the items the owner should read before taking, a server URL that points at
196
- * localhost on the OLD machine, an .env key that looks like tuning rather than a credential. They still
197
- * import fine when ticked; the flag is the adapter's judgment, not a gate. */
198
- recommended: z.boolean(),
199
- // Names of the secrets this item would store (never values, values stay in the held archive until apply,
200
- // and only move when the apply says includeSecrets). Empty for items that carry none.
201
- secrets: z.array(z.string()),
202
- });
203
- export type MigrationItem = z.infer<typeof MigrationItemSchema>;
204
- export const MigrationNeedsActionSchema = z.object({ subject: z.string(), detail: z.string() });
205
- export const MigrationPlanSchema = z.object({
206
- source: MigrationSourceSchema,
207
- // Names the held upload for the apply call. Minted per plan; a new upload replaces the held one.
208
- token: z.string(),
209
- items: z.array(MigrationItemSchema),
210
- // What the adapter saw and will not move, sessions, logs, pairing state, listed rather than silent.
211
- refused: z.array(z.string()),
212
- // What is already known not to move mechanically (channels to reconnect, a model to pick), the same
213
- // honesty ImportReportSchema carries, surfaced at PREVIEW time so the owner ticks with open eyes.
214
- needsAction: z.array(MigrationNeedsActionSchema),
215
- });
216
- export type MigrationPlan = z.infer<typeof MigrationPlanSchema>;
217
- /* One of the owner's own computers, as an import SOURCE, the answer to "where is my setup" that needs no
218
- * packing at all. Read on the card's first render for every enrolled machine, so the offer appears before the
219
- * owner has read a single instruction.
220
- *
221
- * `found` absent means "connected, and nothing to import here", which is a real answer worth rendering
222
- * quietly, not an error: the machine may simply be a different one from the machine the assistant runs on. */
223
- export const MigrationHostSchema = z.object({
224
- id: z.string(),
225
- online: z.boolean(),
226
- found: MigrationSourceSchema.optional(),
227
- // Why this machine cannot be read right now, when it cannot, offline, or its own refusal, in its words.
228
- detail: z.string().optional(),
229
- });
230
- export const MigrationHostsSchema = z.object({ hosts: z.array(MigrationHostSchema) });
231
- export type MigrationHost = z.infer<typeof MigrationHostSchema>;
232
- // Read the setup off a connected computer instead of an upload. Answers with a plan, exactly as the upload
233
- // route does, everything after this point is identical whichever door the setup came through.
234
- export const MigrationScanSchema = z.object({ host: z.string().min(1) });
235
- export const MigrationApplySchema = z.object({
236
- token: z.string(),
237
- // The ticked item ids. Ids the re-derived plan does not contain are ignored rather than erroring, the
238
- // archive is the truth, and a stale checklist must not block the items that still exist.
239
- items: z.array(z.string()),
240
- // The owner's explicit consent to move credential VALUES (mirrors the bundle export's `?secrets=`, and the
241
- // `--include-secrets` these tools' own migrate commands require). Off: secret items are skipped and
242
- // capability configs land without their keys.
243
- includeSecrets: z.boolean(),
244
- });
245
- export type MigrationApply = z.infer<typeof MigrationApplySchema>;
246
- export const MigrationReportSchema = z.object({
247
- applied: z.array(z.object({ id: z.string(), target: MigrationTargetSchema, label: z.string() })),
248
- // Items that were ticked and did not land, each with the reason, a full disk, an env store that needs
249
- // DevOps active. Distinct from `refused`, which is the class of things never attempted.
250
- failed: z.array(z.object({ id: z.string(), label: z.string(), error: z.string() })),
251
- refused: z.array(z.string()),
252
- needsAction: z.array(MigrationNeedsActionSchema),
253
- });
254
- export type MigrationReport = z.infer<typeof MigrationReportSchema>;
172
+ * ONLY THE OUTBOUND HALF IS HERE. Taking a bundle IN is not a surface of its own any more: it is one of the
173
+ * four sources the arrival pipeline reads (arrival.ts), beside a definition and the two foreign assistants,
174
+ * because all four answer the same question and used to answer it three different ways. */
175
+
255
176
  /* One export sitting in the daemon's export directory, the ARTIFACT a bundle is, rather than the request that
256
177
  * produced it. Packing takes minutes over a real workspace, so tying it to a response made it a property of one
257
178
  * browser tab: a refresh abandoned the work and left nothing to come back to. It is a file now, and every field