@intentic/sandbox-contract 1.226.0 → 1.227.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 (194) hide show
  1. package/dist/contracts/activity.contract.d.ts.map +1 -1
  2. package/dist/contracts/activity.contract.js +17 -2
  3. package/dist/contracts/activity.contract.js.map +1 -1
  4. package/dist/contracts/agent.contract.d.ts +19 -2
  5. package/dist/contracts/agent.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.js +71 -8
  7. package/dist/contracts/agent.contract.js.map +1 -1
  8. package/dist/contracts/agents.contract.d.ts +59 -0
  9. package/dist/contracts/agents.contract.d.ts.map +1 -1
  10. package/dist/contracts/agents.contract.js +164 -19
  11. package/dist/contracts/agents.contract.js.map +1 -1
  12. package/dist/contracts/automations.contract.d.ts.map +1 -1
  13. package/dist/contracts/automations.contract.js +78 -9
  14. package/dist/contracts/automations.contract.js.map +1 -1
  15. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  16. package/dist/contracts/capabilities.contract.js +98 -11
  17. package/dist/contracts/capabilities.contract.js.map +1 -1
  18. package/dist/contracts/chores.contract.d.ts.map +1 -1
  19. package/dist/contracts/chores.contract.js +26 -3
  20. package/dist/contracts/chores.contract.js.map +1 -1
  21. package/dist/contracts/ci.contract.d.ts.map +1 -1
  22. package/dist/contracts/ci.contract.js +52 -6
  23. package/dist/contracts/ci.contract.js.map +1 -1
  24. package/dist/contracts/claude.contract.d.ts.map +1 -1
  25. package/dist/contracts/claude.contract.js +44 -5
  26. package/dist/contracts/claude.contract.js.map +1 -1
  27. package/dist/contracts/drafts.contract.d.ts.map +1 -1
  28. package/dist/contracts/drafts.contract.js +26 -3
  29. package/dist/contracts/drafts.contract.js.map +1 -1
  30. package/dist/contracts/endpoints.contract.d.ts +1 -0
  31. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  32. package/dist/contracts/endpoints.contract.js +17 -2
  33. package/dist/contracts/endpoints.contract.js.map +1 -1
  34. package/dist/contracts/exit.contract.d.ts.map +1 -1
  35. package/dist/contracts/exit.contract.js +62 -7
  36. package/dist/contracts/exit.contract.js.map +1 -1
  37. package/dist/contracts/extensions.contract.d.ts +1 -0
  38. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  39. package/dist/contracts/extensions.contract.js +124 -15
  40. package/dist/contracts/extensions.contract.js.map +1 -1
  41. package/dist/contracts/git.contract.d.ts.map +1 -1
  42. package/dist/contracts/git.contract.js +384 -43
  43. package/dist/contracts/git.contract.js.map +1 -1
  44. package/dist/contracts/grok.contract.d.ts.map +1 -1
  45. package/dist/contracts/grok.contract.js +25 -3
  46. package/dist/contracts/grok.contract.js.map +1 -1
  47. package/dist/contracts/history.contract.d.ts.map +1 -1
  48. package/dist/contracts/history.contract.js +35 -4
  49. package/dist/contracts/history.contract.js.map +1 -1
  50. package/dist/contracts/intentic.contract.d.ts.map +1 -1
  51. package/dist/contracts/intentic.contract.js +25 -3
  52. package/dist/contracts/intentic.contract.js.map +1 -1
  53. package/dist/contracts/inventory.contract.d.ts.map +1 -1
  54. package/dist/contracts/inventory.contract.js +26 -3
  55. package/dist/contracts/inventory.contract.js.map +1 -1
  56. package/dist/contracts/logs.contract.d.ts +17 -0
  57. package/dist/contracts/logs.contract.d.ts.map +1 -1
  58. package/dist/contracts/logs.contract.js +27 -3
  59. package/dist/contracts/logs.contract.js.map +1 -1
  60. package/dist/contracts/loops.contract.d.ts.map +1 -1
  61. package/dist/contracts/loops.contract.js +52 -6
  62. package/dist/contracts/loops.contract.js.map +1 -1
  63. package/dist/contracts/panels.contract.d.ts.map +1 -1
  64. package/dist/contracts/panels.contract.js +26 -3
  65. package/dist/contracts/panels.contract.js.map +1 -1
  66. package/dist/contracts/personas.contract.d.ts.map +1 -1
  67. package/dist/contracts/personas.contract.js +71 -8
  68. package/dist/contracts/personas.contract.js.map +1 -1
  69. package/dist/contracts/ports.contract.d.ts.map +1 -1
  70. package/dist/contracts/ports.contract.js +26 -3
  71. package/dist/contracts/ports.contract.js.map +1 -1
  72. package/dist/contracts/prepush.contract.d.ts.map +1 -1
  73. package/dist/contracts/prepush.contract.js +24 -3
  74. package/dist/contracts/prepush.contract.js.map +1 -1
  75. package/dist/contracts/providers.contract.d.ts +1 -0
  76. package/dist/contracts/providers.contract.d.ts.map +1 -1
  77. package/dist/contracts/providers.contract.js +9 -1
  78. package/dist/contracts/providers.contract.js.map +1 -1
  79. package/dist/contracts/public.contract.d.ts.map +1 -1
  80. package/dist/contracts/public.contract.js +26 -3
  81. package/dist/contracts/public.contract.js.map +1 -1
  82. package/dist/contracts/push.contract.d.ts.map +1 -1
  83. package/dist/contracts/push.contract.js +35 -4
  84. package/dist/contracts/push.contract.js.map +1 -1
  85. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  86. package/dist/contracts/secrets.contract.js +43 -5
  87. package/dist/contracts/secrets.contract.js.map +1 -1
  88. package/dist/contracts/sessions.contract.d.ts +3 -0
  89. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  90. package/dist/contracts/sessions.contract.js +15 -2
  91. package/dist/contracts/sessions.contract.js.map +1 -1
  92. package/dist/contracts/settings.contract.d.ts +19 -0
  93. package/dist/contracts/settings.contract.d.ts.map +1 -1
  94. package/dist/contracts/settings.contract.js +43 -5
  95. package/dist/contracts/settings.contract.js.map +1 -1
  96. package/dist/contracts/share.contract.d.ts.map +1 -1
  97. package/dist/contracts/share.contract.js +35 -4
  98. package/dist/contracts/share.contract.js.map +1 -1
  99. package/dist/contracts/skills.contract.d.ts.map +1 -1
  100. package/dist/contracts/skills.contract.js +35 -4
  101. package/dist/contracts/skills.contract.js.map +1 -1
  102. package/dist/contracts/system.contract.d.ts +7 -0
  103. package/dist/contracts/system.contract.d.ts.map +1 -1
  104. package/dist/contracts/system.contract.js +107 -14
  105. package/dist/contracts/system.contract.js.map +1 -1
  106. package/dist/contracts/translator.contract.d.ts.map +1 -1
  107. package/dist/contracts/translator.contract.js +29 -4
  108. package/dist/contracts/translator.contract.js.map +1 -1
  109. package/dist/contracts/usage.contract.d.ts.map +1 -1
  110. package/dist/contracts/usage.contract.js +9 -1
  111. package/dist/contracts/usage.contract.js.map +1 -1
  112. package/dist/contracts/vpn.contract.d.ts.map +1 -1
  113. package/dist/contracts/vpn.contract.js +32 -4
  114. package/dist/contracts/vpn.contract.js.map +1 -1
  115. package/dist/contracts/workflows.contract.d.ts.map +1 -1
  116. package/dist/contracts/workflows.contract.js +70 -8
  117. package/dist/contracts/workflows.contract.js.map +1 -1
  118. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  119. package/dist/contracts/workspace.contract.js +216 -25
  120. package/dist/contracts/workspace.contract.js.map +1 -1
  121. package/dist/events.d.ts +46 -0
  122. package/dist/events.d.ts.map +1 -1
  123. package/dist/events.js +193 -88
  124. package/dist/events.js.map +1 -1
  125. package/dist/history-state.d.ts.map +1 -1
  126. package/dist/history-state.js +1 -0
  127. package/dist/history-state.js.map +1 -1
  128. package/dist/index.d.ts +197 -70
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/prompt-complexity.d.ts +9 -1
  133. package/dist/prompt-complexity.d.ts.map +1 -1
  134. package/dist/prompt-complexity.js +13 -5
  135. package/dist/prompt-complexity.js.map +1 -1
  136. package/dist/request-id.d.ts +3 -0
  137. package/dist/request-id.d.ts.map +1 -0
  138. package/dist/request-id.js +3 -0
  139. package/dist/request-id.js.map +1 -0
  140. package/dist/schemas.d.ts +89 -0
  141. package/dist/schemas.d.ts.map +1 -1
  142. package/dist/schemas.js +2912 -1276
  143. package/dist/schemas.js.map +1 -1
  144. package/dist/starter.d.ts +3 -0
  145. package/dist/starter.d.ts.map +1 -0
  146. package/dist/starter.js +3 -0
  147. package/dist/starter.js.map +1 -0
  148. package/package.json +5 -4
  149. package/src/contract-lock.test.ts +8 -2
  150. package/src/contracts/activity.contract.ts +18 -2
  151. package/src/contracts/agent.contract.ts +78 -8
  152. package/src/contracts/agents.contract.ts +180 -19
  153. package/src/contracts/automations.contract.ts +80 -9
  154. package/src/contracts/capabilities.contract.ts +106 -11
  155. package/src/contracts/chores.contract.ts +29 -3
  156. package/src/contracts/ci.contract.ts +55 -6
  157. package/src/contracts/claude.contract.ts +47 -5
  158. package/src/contracts/drafts.contract.ts +26 -3
  159. package/src/contracts/endpoints.contract.ts +19 -2
  160. package/src/contracts/exit.contract.ts +69 -7
  161. package/src/contracts/extensions.contract.ts +134 -15
  162. package/src/contracts/git.contract.ts +421 -43
  163. package/src/contracts/grok.contract.ts +26 -3
  164. package/src/contracts/history.contract.ts +37 -4
  165. package/src/contracts/intentic.contract.ts +28 -3
  166. package/src/contracts/inventory.contract.ts +27 -3
  167. package/src/contracts/logs.contract.ts +41 -4
  168. package/src/contracts/loops.contract.ts +58 -6
  169. package/src/contracts/panels.contract.ts +26 -3
  170. package/src/contracts/personas.contract.ts +77 -8
  171. package/src/contracts/ports.contract.ts +27 -3
  172. package/src/contracts/prepush.contract.ts +26 -3
  173. package/src/contracts/providers.contract.ts +10 -1
  174. package/src/contracts/public.contract.ts +29 -3
  175. package/src/contracts/push.contract.ts +37 -4
  176. package/src/contracts/secrets.contract.ts +46 -5
  177. package/src/contracts/sessions.contract.ts +16 -2
  178. package/src/contracts/settings.contract.ts +46 -5
  179. package/src/contracts/share.contract.ts +37 -4
  180. package/src/contracts/skills.contract.ts +39 -4
  181. package/src/contracts/system.contract.ts +119 -14
  182. package/src/contracts/translator.contract.ts +32 -4
  183. package/src/contracts/usage.contract.ts +10 -1
  184. package/src/contracts/vpn.contract.ts +35 -4
  185. package/src/contracts/workflows.contract.ts +77 -8
  186. package/src/contracts/workspace.contract.ts +231 -25
  187. package/src/events.ts +259 -89
  188. package/src/history-state.ts +6 -0
  189. package/src/index.ts +2 -0
  190. package/src/prompt-complexity.test.ts +69 -0
  191. package/src/prompt-complexity.ts +102 -39
  192. package/src/request-id.ts +41 -0
  193. package/src/schemas.ts +3700 -1260
  194. package/src/starter.ts +13 -0
@@ -0,0 +1,3 @@
1
+ export declare const STARTER_REPO = "site";
2
+ export declare const STARTER_APP = "landing";
3
+ //# sourceMappingURL=starter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter.d.ts","sourceRoot":"","sources":["../src/starter.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,YAAY,SAAS,CAAC;AACnC,eAAO,MAAM,WAAW,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const STARTER_REPO = "site";
2
+ export const STARTER_APP = "landing";
3
+ //# sourceMappingURL=starter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter.js","sourceRoot":"","sources":["../src/starter.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentic/sandbox-contract",
3
- "version": "1.226.0",
3
+ "version": "1.227.0",
4
4
  "description": "oRPC wire contract for the intentic sandbox daemon, shared by the daemon and its browser client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -89,14 +89,15 @@
89
89
  "@orpc/contract": "1.14.13",
90
90
  "tslib": "2.8.1",
91
91
  "zod": "4.4.3",
92
- "@intentic/constants": "1.226.0",
93
- "@intentic/registry": "1.226.0",
94
- "@intentic/extension-manifest": "1.226.0"
92
+ "@intentic/constants": "1.227.0",
93
+ "@intentic/registry": "1.227.0",
94
+ "@intentic/extension-manifest": "1.227.0"
95
95
  },
96
96
  "devDependencies": {
97
97
  "@types/node": "24.13.2",
98
98
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
99
99
  "vitest": "4.1.10",
100
+ "@intentic/testing": "0.0.0",
100
101
  "@intentic/tsconfig": "0.0.0"
101
102
  },
102
103
  "scripts": {
@@ -5,8 +5,14 @@ import { currentLock } from "./contract-lock.js";
5
5
  /* The committed lock and the code must say the same thing: see contract-lock.ts for what the pair buys.
6
6
  *
7
7
  * This is the HALF that runs everywhere the tests run; the other half (a shrunk lock needs a declared break)
8
- * lives in prepass.mjs, which has git and this suite does not. */
9
- test("contract.lock.json matches the schemas this package exports", () => {
8
+ * lives in prepass.mjs, which has git and this suite does not.
9
+ *
10
+ * ITS OWN BUDGET, because the default one is a HANG DETECTOR and this test does real work: it serializes every
11
+ * schema this package exports, ~500 of them, to JSON Schema. That is ~100ms with the machine to itself and it
12
+ * measured 8.8s on a runner running every package's suite at once, so vitest's 5s default failed it as a hang
13
+ * over a contract that had not moved: green on a box, red on a busy runner, the trap _tools/testing/src/vitest
14
+ * .ts is written against. 30s is well clear of the work and still reports a genuine hang inside half a minute. */
15
+ test("contract.lock.json matches the schemas this package exports", { timeout: 30_000 }, () => {
10
16
  const committed: unknown = JSON.parse(readFileSync(new URL("../contract.lock.json", import.meta.url), "utf8"));
11
17
  expect(
12
18
  currentLock(),
@@ -4,6 +4,22 @@ import { ActivityListSchema, ActivityQuerySchema, ActivityStatusSchema } from ".
4
4
  // The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design,
5
5
  // events are appended daemon-side only, so the log stays a trustworthy record of what the agent did.
6
6
  export const activityContract = {
7
- list: oc.route({ method: "GET", path: "/activity" }).input(ActivityQuerySchema).output(ActivityListSchema),
8
- status: oc.route({ method: "GET", path: "/activity/status" }).output(ActivityStatusSchema),
7
+ list: oc
8
+ .route({
9
+ method: "GET",
10
+ path: "/activity",
11
+ summary: "What the agent has done out in the world",
12
+ description:
13
+ "The audit trail of actions taken on outside services. Read-only on purpose: entries are written by the sandbox alone, which is what makes it a record worth trusting.",
14
+ })
15
+ .input(ActivityQuerySchema)
16
+ .output(ActivityListSchema),
17
+ status: oc
18
+ .route({
19
+ method: "GET",
20
+ path: "/activity/status",
21
+ summary: "Whether the audit trail is being kept",
22
+ description: "Which sources are feeding the record and whether each is working.",
23
+ })
24
+ .output(ActivityStatusSchema),
9
25
  };
@@ -19,20 +19,90 @@ import {
19
19
  // turn waiting on any interactive card (plan approval, clarifying questions, a per-tool permission prompt);
20
20
  // steer injects a user message into the running turn; stop hard-cancels it daemon-side.
21
21
  export const agentContract = {
22
- run: oc.route({ method: "POST", path: "/agent" }).input(AgentTurnSchema).output(StartedTurnSchema),
23
- attach: oc.route({ method: "POST", path: "/agent/attach" }).input(AttachTurnSchema).output(eventIterator(AttachFrameSchema)),
24
- reply: oc.route({ method: "POST", path: "/agent/reply" }).input(AgentReplySchema).output(OkSchema),
25
- steer: oc.route({ method: "POST", path: "/agent/steer" }).input(SteerSchema).output(OkSchema),
26
- stop: oc.route({ method: "POST", path: "/agent/stop" }).input(StopTurnSchema).output(OkSchema),
22
+ run: oc
23
+ .route({
24
+ method: "POST",
25
+ path: "/agent",
26
+ summary: "Say something to an agent",
27
+ description:
28
+ "Starts a turn and answers immediately with its id; the work runs inside the sandbox whether or not anybody stays connected. Watch it by attaching. Naming a conversation that does not exist yet opens it.",
29
+ })
30
+ .input(AgentTurnSchema)
31
+ .output(StartedTurnSchema),
32
+ attach: oc
33
+ .route({
34
+ method: "POST",
35
+ path: "/agent/attach",
36
+ summary: "Watch a turn happen",
37
+ description:
38
+ "Streams everything the agent does: its words, the tools it reaches for, and the answers it gets. Give it the point you have already seen and it replays from there before going live, so a reload loses nothing. The window that started the turn holds no special claim, and any number of watchers on any number of devices see the same thing.",
39
+ })
40
+ .input(AttachTurnSchema)
41
+ .output(eventIterator(AttachFrameSchema)),
42
+ reply: oc
43
+ .route({
44
+ method: "POST",
45
+ path: "/agent/reply",
46
+ summary: "Answer a question the agent asked",
47
+ description:
48
+ "Un-parks a turn that is waiting on you: approving a plan, choosing between options, or permitting a tool. The turn picks up where it stopped.",
49
+ })
50
+ .input(AgentReplySchema)
51
+ .output(OkSchema),
52
+ steer: oc
53
+ .route({
54
+ method: "POST",
55
+ path: "/agent/steer",
56
+ summary: "Interrupt a running turn",
57
+ description:
58
+ "Slips a message into a turn already under way, without stopping it. This is how you redirect an agent mid-thought rather than waiting for it to finish being wrong.",
59
+ })
60
+ .input(SteerSchema)
61
+ .output(OkSchema),
62
+ stop: oc
63
+ .route({
64
+ method: "POST",
65
+ path: "/agent/stop",
66
+ summary: "Stop a turn now",
67
+ description: "Cancels the running turn inside the sandbox. Whatever it had already written to disk stays written.",
68
+ })
69
+ .input(StopTurnSchema)
70
+ .output(OkSchema),
27
71
  // Go back to a message: restore the workspace to that turn's checkpoint, drop the messages after it, and
28
72
  // forget the provider session. CONFLICT while a turn is running, a restore cannot overwrite files an
29
73
  // agent is editing. NOT_FOUND when that message has no checkpoint to go back to.
30
- rewind: oc.route({ method: "POST", path: "/agent/rewind" }).input(RewindTurnSchema).output(RewindResultSchema),
74
+ rewind: oc
75
+ .route({
76
+ method: "POST",
77
+ path: "/agent/rewind",
78
+ summary: "Go back to an earlier message",
79
+ description:
80
+ "Puts the files back as they stood at that point, drops every message after it, and forgets what the model remembered, so the next thing you say starts from there cleanly. Refused while a turn is running, because a restore cannot overwrite files an agent is editing, and refused for a message with no saved state to return to.",
81
+ })
82
+ .input(RewindTurnSchema)
83
+ .output(RewindResultSchema),
31
84
  // The provider's slash commands as last published by one of its turns, so a conversation's `/` popover is
32
85
  // populated before it has run one. The live `commands` frame stays authoritative for a running turn.
33
- commands: oc.route({ method: "GET", path: "/agent/commands" }).input(AgentCommandsQuerySchema).output(AgentCommandsSchema),
86
+ commands: oc
87
+ .route({
88
+ method: "GET",
89
+ path: "/agent/commands",
90
+ summary: "Shortcut commands the agent knows",
91
+ description:
92
+ "The commands a provider published the last time one of its turns ran, so a composer can offer them before this conversation has run anything. A running turn's own list wins over this one.",
93
+ })
94
+ .input(AgentCommandsQuerySchema)
95
+ .output(AgentCommandsSchema),
34
96
  // The last refusal per provider, as reported by whichever turn was refused (ProviderRefusalSchema). Read
35
97
  // alongside the account listings by the surfaces that draw plan limits: the snapshot on an account row says
36
98
  // how full its pools were when last polled, and this says whether one of them has since said no.
37
- refusals: oc.route({ method: "GET", path: "/agent/refusals" }).output(ProviderRefusalsSchema),
99
+ refusals: oc
100
+ .route({
101
+ method: "GET",
102
+ path: "/agent/refusals",
103
+ summary: "The last time each provider said no",
104
+ description:
105
+ "What each model provider most recently refused and why. Read this alongside an account's usage: the usage says how full it was when last checked, this says whether it has since started turning work away.",
106
+ })
107
+ .output(ProviderRefusalsSchema),
38
108
  };
@@ -47,27 +47,96 @@ import {
47
47
  // own agent, so there should be none) and answers with what it actually deleted: a teardown that fails on one
48
48
  // agent's repo leaves that one behind rather than taking the batch down with it.
49
49
  export const agentsContract = {
50
- list: oc.route({ method: "GET", path: "/agents" }).output(AgentsListSchema),
51
- archived: oc.route({ method: "GET", path: "/agents/archived" }).output(AgentsListSchema),
50
+ list: oc
51
+ .route({
52
+ method: "GET",
53
+ path: "/agents",
54
+ summary: "Every live conversation",
55
+ description:
56
+ "The fleet as the board draws it: each conversation with its title, what it is doing, when it last moved and whether anybody has read it since. Archived conversations are not in here.",
57
+ })
58
+ .output(AgentsListSchema),
59
+ archived: oc
60
+ .route({
61
+ method: "GET",
62
+ path: "/agents/archived",
63
+ summary: "Conversations put away",
64
+ description:
65
+ "The same shape as the live fleet, for the conversations somebody has decided are finished. Their work is kept, and any one of them can be brought back.",
66
+ })
67
+ .output(AgentsListSchema),
52
68
  // The board's filter. Answers over BOTH halves of the fleet, the live roster and the archive, because
53
69
  // the board hides by design (the Finished lane windows to a handful, archived agents are off the roster
54
70
  // entirely), and a filter that reports "no matches" while the agent sits one click away is a lie. The
55
71
  // never-carded conversations that are neither are `sessions.list`'s query, which matches by the same rule.
56
- search: oc.route({ method: "GET", path: "/agents/search" }).input(AgentSearchQuerySchema).output(AgentSearchResultSchema),
57
- get: oc.route({ method: "GET", path: "/agents/{id}" }).input(AgentIdSchema).output(AgentSummarySchema),
58
- transcript: oc.route({ method: "GET", path: "/agents/{id}/transcript" }).input(AgentIdSchema).output(AgentTranscriptSchema),
72
+ search: oc
73
+ .route({
74
+ method: "GET",
75
+ path: "/agents/search",
76
+ summary: "Find a conversation",
77
+ description:
78
+ "Searches the live fleet and the archive together. Both halves on purpose: the board hides finished work by design, and a filter that says it found nothing while the answer sits one click away is simply wrong.",
79
+ })
80
+ .input(AgentSearchQuerySchema)
81
+ .output(AgentSearchResultSchema),
82
+ get: oc
83
+ .route({
84
+ method: "GET",
85
+ path: "/agents/{id}",
86
+ summary: "One conversation's card",
87
+ description: "Everything the board shows for a single conversation: its title, state, working branch, unread marker and timestamps.",
88
+ })
89
+ .input(AgentIdSchema)
90
+ .output(AgentSummarySchema),
91
+ transcript: oc
92
+ .route({
93
+ method: "GET",
94
+ path: "/agents/{id}/transcript",
95
+ summary: "The full conversation record",
96
+ description:
97
+ "Every message in one conversation, in order, including the tool calls and their results. This is the record the chat replays and the next turn is seeded from.",
98
+ })
99
+ .input(AgentIdSchema)
100
+ .output(AgentTranscriptSchema),
59
101
  /* SPEAK AS THE AGENT, append the user's words to the conversation's record as an assistant row, with no
60
102
  * turn behind them and no reply. The row is marked `placed` for human readers (RestoredMessageSchema); the
61
103
  * provider session is FORGOTTEN in the same breath, rewind-style, so the next real turn opens a fresh
62
104
  * runtime session seeded from the record, where the placed line reads as the agent's own words, because
63
105
  * the handoff renders every assistant row identically. A running turn is CONFLICT: the illusion can only be
64
106
  * established between turns, and a concurrent turn would resume the very session this exists to retire. */
65
- place: oc.route({ method: "POST", path: "/agents/{id}/place" }).input(AgentPlaceSchema).output(OkSchema),
66
- rename: oc.route({ method: "POST", path: "/agents/{id}/rename" }).input(AgentRenameSchema).output(AgentSummarySchema),
107
+ place: oc
108
+ .route({
109
+ method: "POST",
110
+ path: "/agents/{id}/place",
111
+ summary: "Put words in the agent's mouth",
112
+ description:
113
+ "Writes a line into the record as though the agent had said it, with no turn behind it and no reply. Human readers see it marked as placed. The next real turn starts fresh from the record, where the line reads as the agent's own. Refused while a turn is running.",
114
+ })
115
+ .input(AgentPlaceSchema)
116
+ .output(OkSchema),
117
+ rename: oc
118
+ .route({
119
+ method: "POST",
120
+ path: "/agents/{id}/rename",
121
+ summary: "Retitle a conversation",
122
+ description:
123
+ "Sets the title a person chose, replacing the one that was generated. Allowed while the conversation is working, and it does not count as activity.",
124
+ })
125
+ .input(AgentRenameSchema)
126
+ .output(AgentSummarySchema),
67
127
  // This agent's own land-at-completion posture, an override of the sandbox-wide `autoLand` setting; null
68
128
  // clears it back to "inherit". Legal mid-turn on purpose: the setting is read at turn COMPLETION, so
69
129
  // flipping it while the agent works is exactly "hold THIS turn's work for review", the press that matters.
70
- autoLand: oc.route({ method: "POST", path: "/agents/{id}/auto-land" }).input(AgentAutoLandSchema).output(AgentSummarySchema),
130
+ autoLand: oc
131
+ .route({
132
+ method: "POST",
133
+ path: "/agents/{id}/auto-land",
134
+ summary: "Whether this conversation merges its work automatically",
135
+ description:
136
+ "Overrides the sandbox-wide setting for one conversation; clear it to go back to following the default. Deliberately allowed mid-turn, because the setting is read when the turn finishes, so flipping it while the agent works means exactly hold this piece of work for review.",
137
+ })
138
+ .input(AgentAutoLandSchema)
139
+ .output(AgentSummarySchema),
71
140
  /* THIS conversation's answer to a provider outage, an override of the sandbox-wide `resumeAfterOutage`
72
141
  * setting; null clears it back to "inherit". The chat's offer at the moment a turn dies writes this and
73
142
  * never the global: the press happens inside one conversation and means "finish this piece of work", so
@@ -76,20 +145,112 @@ export const agentsContract = {
76
145
  * Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too, an outage kills a
77
146
  * main-tree chat exactly as readily as an isolated one, and there is no branch involved either way. */
78
147
  resumeAfterOutage: oc
79
- .route({ method: "POST", path: "/agents/{id}/resume-after-outage" })
148
+ .route({
149
+ method: "POST",
150
+ path: "/agents/{id}/resume-after-outage",
151
+ summary: "Whether this conversation retries after a provider outage",
152
+ description:
153
+ "Overrides the sandbox-wide setting for one conversation; clear it to follow the default again. This is what the offer shown when a turn dies writes, because the press happens inside one conversation and honestly means finish this piece of work.",
154
+ })
80
155
  .input(AgentResumeAfterOutageSchema)
81
156
  .output(AgentSummarySchema),
82
- seen: oc.route({ method: "POST", path: "/agents/{id}/seen" }).input(AgentIdSchema).output(AgentSummarySchema),
83
- seenAll: oc.route({ method: "POST", path: "/agents/seen" }).output(AgentsListSchema),
84
- diff: oc.route({ method: "GET", path: "/agents/{id}/diff" }).input(AgentIdSchema).output(AgentChangesSchema),
85
- fileDiff: oc.route({ method: "GET", path: "/agents/{id}/{repo}/file-diff" }).input(AgentFileDiffQuerySchema).output(FileDiffSchema),
86
- land: oc.route({ method: "POST", path: "/agents/{id}/land" }).input(AgentLandSchema).output(LandResultSchema),
157
+ seen: oc
158
+ .route({
159
+ method: "POST",
160
+ path: "/agents/{id}/seen",
161
+ summary: "Mark a conversation read",
162
+ description:
163
+ "Stamps the read marker behind the unread badge on one card. Allowed while the conversation is working, and reading never counts as activity.",
164
+ })
165
+ .input(AgentIdSchema)
166
+ .output(AgentSummarySchema),
167
+ seenAll: oc
168
+ .route({
169
+ method: "POST",
170
+ path: "/agents/seen",
171
+ summary: "Mark every conversation read",
172
+ description: "Clears the unread badge across the whole fleet at once, and hands the refreshed list back.",
173
+ })
174
+ .output(AgentsListSchema),
175
+ diff: oc
176
+ .route({
177
+ method: "GET",
178
+ path: "/agents/{id}/diff",
179
+ summary: "Everything a conversation has changed",
180
+ description:
181
+ "One flat set of changed files per repo, measured against where each repo stood when the conversation started, with every file flagged as already merged or not. Not the staged-and-unstaged shape a working copy has, because nobody ever checks this branch out to stage into it.",
182
+ })
183
+ .input(AgentIdSchema)
184
+ .output(AgentChangesSchema),
185
+ fileDiff: oc
186
+ .route({
187
+ method: "GET",
188
+ path: "/agents/{id}/{repo}/file-diff",
189
+ summary: "One file's before and after in a conversation's work",
190
+ description: "Both sides of a single file: what it held when the conversation started and what it holds on its branch now.",
191
+ })
192
+ .input(AgentFileDiffQuerySchema)
193
+ .output(FileDiffSchema),
194
+ land: oc
195
+ .route({
196
+ method: "POST",
197
+ path: "/agents/{id}/land",
198
+ summary: "Merge a conversation's work into the workspace",
199
+ description:
200
+ "Brings the conversation's branches into the main tree, one repo at a time. A conflict is reported rather than raised and nothing is lost when it fails. Refused while a turn is running, and refused for a conversation that works directly in the shared tree, which has nothing to merge.",
201
+ })
202
+ .input(AgentLandSchema)
203
+ .output(LandResultSchema),
87
204
  // A collaborator's ask for the land they may not perform themselves (role floors put `land`/`discard` at
88
205
  // maintainer). Stamps AgentSummarySchema.landRequested with the caller's identity and re-frames the fleet,
89
206
  // so every maintainer's board carries the request; the land or discard that answers it clears the stamp.
90
- requestLand: oc.route({ method: "POST", path: "/agents/{id}/request-land" }).input(AgentIdSchema).output(AgentSummarySchema),
91
- discard: oc.route({ method: "POST", path: "/agents/{id}/discard" }).input(AgentIdSchema).output(OkSchema),
92
- archive: oc.route({ method: "POST", path: "/agents/archive" }).input(AgentArchiveSchema).output(AgentsMovedSchema),
93
- unarchive: oc.route({ method: "POST", path: "/agents/unarchive" }).input(AgentIdsSchema).output(AgentsMovedSchema),
94
- purge: oc.route({ method: "POST", path: "/agents/purge" }).output(AgentsRemovedSchema),
207
+ requestLand: oc
208
+ .route({
209
+ method: "POST",
210
+ path: "/agents/{id}/request-land",
211
+ summary: "Ask a maintainer to merge this work",
212
+ description:
213
+ "For a collaborator who is not allowed to merge: marks the conversation as waiting for review, with who asked. The request shows on every maintainer's board and clears when somebody merges or discards it.",
214
+ })
215
+ .input(AgentIdSchema)
216
+ .output(AgentSummarySchema),
217
+ discard: oc
218
+ .route({
219
+ method: "POST",
220
+ path: "/agents/{id}/discard",
221
+ summary: "Throw a conversation's work away",
222
+ description:
223
+ "Deletes the conversation's working copies, its branches and its entry. Nothing is kept. Refused while a turn is running, and refused for a conversation working in the shared tree.",
224
+ })
225
+ .input(AgentIdSchema)
226
+ .output(OkSchema),
227
+ archive: oc
228
+ .route({
229
+ method: "POST",
230
+ path: "/agents/archive",
231
+ summary: "Put conversations away",
232
+ description:
233
+ "The gentle counterpart to discarding. Commits whatever the conversation still has in progress onto its own branch, releases its working copy, and keeps the entry and the record. It leaves the live fleet and joins the archive. Refused for a conversation that is running.",
234
+ })
235
+ .input(AgentArchiveSchema)
236
+ .output(AgentsMovedSchema),
237
+ unarchive: oc
238
+ .route({
239
+ method: "POST",
240
+ path: "/agents/unarchive",
241
+ summary: "Bring conversations back",
242
+ description:
243
+ "Returns archived conversations to the live fleet. The next turn picks up a fresh working copy from the branch that was kept.",
244
+ })
245
+ .input(AgentIdsSchema)
246
+ .output(AgentsMovedSchema),
247
+ purge: oc
248
+ .route({
249
+ method: "POST",
250
+ path: "/agents/purge",
251
+ summary: "Empty the archive for good",
252
+ description:
253
+ "Discards every conversation already in the archive: working copies, branches and entries. The whole archive rather than a chosen few, because the archive is the pile somebody has already decided is over. A teardown that fails on one conversation leaves that one behind instead of taking the rest down with it.",
254
+ })
255
+ .output(AgentsRemovedSchema),
95
256
  };
@@ -17,15 +17,54 @@ import {
17
17
  // The `pending*` routes are the owner's approval queue: a `requireApproval` automation holds each fire here
18
18
  // instead of waking; `approve` runs the held wake, `reject` drops it.
19
19
  export const automationsContract = {
20
- list: oc.route({ method: "GET", path: "/automations" }).output(AutomationsListSchema),
20
+ list: oc
21
+ .route({
22
+ method: "GET",
23
+ path: "/automations",
24
+ summary: "Things that wake an agent on their own",
25
+ description: "Every automation with its recent runs and when it fires next.",
26
+ })
27
+ .output(AutomationsListSchema),
21
28
  /* WHAT CAN WAKE AN AGENT HERE, and what to start from, the daemon's own sources and templates merged with
22
29
  * every installed extension's. The composer's entire vocabulary, so that adding a trigger to an area is a
23
30
  * change to that area and to nothing else. `upsert` below validates against the same merge, which is what
24
31
  * keeps the surface and the daemon from disagreeing about what is allowed. */
25
- catalog: oc.route({ method: "GET", path: "/automations/catalog" }).output(AutomationCatalogSchema),
26
- upsert: oc.route({ method: "POST", path: "/automations" }).input(AutomationSchema).output(OkSchema),
27
- setEnabled: oc.route({ method: "POST", path: "/automations/{id}/enabled" }).input(AutomationEnabledInputSchema).output(OkSchema),
28
- remove: oc.route({ method: "DELETE", path: "/automations/{id}" }).input(AutomationIdParamSchema).output(OkSchema),
32
+ catalog: oc
33
+ .route({
34
+ method: "GET",
35
+ path: "/automations/catalog",
36
+ summary: "What can trigger an automation here",
37
+ description:
38
+ "Every trigger this sandbox understands and every template worth starting from, the daemon's own merged with each installed extension's. Writing an automation is checked against this same list, so a screen and the daemon can never disagree about what is allowed.",
39
+ })
40
+ .output(AutomationCatalogSchema),
41
+ upsert: oc
42
+ .route({
43
+ method: "POST",
44
+ path: "/automations",
45
+ summary: "Create or edit an automation",
46
+ description: "Writes an automation by id. Nothing needs provisioning: the scheduler picks it up on its next sweep.",
47
+ })
48
+ .input(AutomationSchema)
49
+ .output(OkSchema),
50
+ setEnabled: oc
51
+ .route({
52
+ method: "POST",
53
+ path: "/automations/{id}/enabled",
54
+ summary: "Turn an automation on or off",
55
+ description: "Flips only the switch, so a row in a list can be toggled without rebuilding the whole record.",
56
+ })
57
+ .input(AutomationEnabledInputSchema)
58
+ .output(OkSchema),
59
+ remove: oc
60
+ .route({
61
+ method: "DELETE",
62
+ path: "/automations/{id}",
63
+ summary: "Delete an automation",
64
+ description: "Removes it, so nothing fires from it again.",
65
+ })
66
+ .input(AutomationIdParamSchema)
67
+ .output(OkSchema),
29
68
  /* Fire one automation NOW, by hand, the answer to "I wrote a 3 a.m. cron and I have no way to try it".
30
69
  * It runs the SAME path the real trigger runs: a schedule stays a headless main-tree wake, because a
31
70
  * test-fire that proves an isolated worktree works proves nothing about the fire it is standing in for. The
@@ -45,8 +84,40 @@ export const automationsContract = {
45
84
  *
46
85
  * Acks immediately with the turn detached, like /fire and `approve`: the guard alone may take a minute, and
47
86
  * the run history (with the session that makes it openable) is where the outcome lands. */
48
- run: oc.route({ method: "POST", path: "/automations/{id}/run" }).input(AutomationIdParamSchema).output(OkSchema),
49
- pendingList: oc.route({ method: "GET", path: "/automations/pending" }).output(AutomationApprovalsListSchema),
50
- approve: oc.route({ method: "POST", path: "/automations/pending/{id}/approve" }).input(AutomationApprovalIdParamSchema).output(OkSchema),
51
- reject: oc.route({ method: "POST", path: "/automations/pending/{id}/reject" }).input(AutomationApprovalIdParamSchema).output(OkSchema),
87
+ run: oc
88
+ .route({
89
+ method: "POST",
90
+ path: "/automations/{id}/run",
91
+ summary: "Fire an automation by hand",
92
+ description:
93
+ "The answer to writing something that runs at three in the morning and having no way to try it. It takes exactly the path the real trigger takes, including the check that decides whether there was anything to do, since skipped by the guard is the most useful thing this can tell you. A switched-off automation fires too, because trying it before switching it on is the main reason to press this. Not available for the trigger that listens for incoming messages, where a hand-fire would produce an agent asked to handle events and handed none; send the bot a message instead. Answers straight away and runs detached.",
94
+ })
95
+ .input(AutomationIdParamSchema)
96
+ .output(OkSchema),
97
+ pendingList: oc
98
+ .route({
99
+ method: "GET",
100
+ path: "/automations/pending",
101
+ summary: "Automations waiting for a yes",
102
+ description: "The queue an automation set to ask first lands in each time it would have fired.",
103
+ })
104
+ .output(AutomationApprovalsListSchema),
105
+ approve: oc
106
+ .route({
107
+ method: "POST",
108
+ path: "/automations/pending/{id}/approve",
109
+ summary: "Let a held automation run",
110
+ description: "Releases one waiting automation and runs the wake it was holding. Answers straight away and runs detached.",
111
+ })
112
+ .input(AutomationApprovalIdParamSchema)
113
+ .output(OkSchema),
114
+ reject: oc
115
+ .route({
116
+ method: "POST",
117
+ path: "/automations/pending/{id}/reject",
118
+ summary: "Drop a held automation",
119
+ description: "Throws one waiting fire away. The automation stays on, and the next trigger queues as usual.",
120
+ })
121
+ .input(AutomationApprovalIdParamSchema)
122
+ .output(OkSchema),
52
123
  };