@intentic/sandbox-contract 1.225.0 → 1.226.1

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 (172) hide show
  1. package/dist/agent-catalog.d.ts +2 -0
  2. package/dist/agent-catalog.d.ts.map +1 -1
  3. package/dist/agent-catalog.js +16 -0
  4. package/dist/agent-catalog.js.map +1 -1
  5. package/dist/contracts/activity.contract.d.ts.map +1 -1
  6. package/dist/contracts/activity.contract.js +17 -2
  7. package/dist/contracts/activity.contract.js.map +1 -1
  8. package/dist/contracts/agent.contract.d.ts.map +1 -1
  9. package/dist/contracts/agent.contract.js +71 -8
  10. package/dist/contracts/agent.contract.js.map +1 -1
  11. package/dist/contracts/agents.contract.d.ts +1 -0
  12. package/dist/contracts/agents.contract.d.ts.map +1 -1
  13. package/dist/contracts/agents.contract.js +164 -19
  14. package/dist/contracts/agents.contract.js.map +1 -1
  15. package/dist/contracts/automations.contract.d.ts.map +1 -1
  16. package/dist/contracts/automations.contract.js +78 -9
  17. package/dist/contracts/automations.contract.js.map +1 -1
  18. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  19. package/dist/contracts/capabilities.contract.js +98 -11
  20. package/dist/contracts/capabilities.contract.js.map +1 -1
  21. package/dist/contracts/chores.contract.d.ts.map +1 -1
  22. package/dist/contracts/chores.contract.js +26 -3
  23. package/dist/contracts/chores.contract.js.map +1 -1
  24. package/dist/contracts/ci.contract.d.ts.map +1 -1
  25. package/dist/contracts/ci.contract.js +52 -6
  26. package/dist/contracts/ci.contract.js.map +1 -1
  27. package/dist/contracts/claude.contract.d.ts.map +1 -1
  28. package/dist/contracts/claude.contract.js +44 -5
  29. package/dist/contracts/claude.contract.js.map +1 -1
  30. package/dist/contracts/drafts.contract.d.ts.map +1 -1
  31. package/dist/contracts/drafts.contract.js +26 -3
  32. package/dist/contracts/drafts.contract.js.map +1 -1
  33. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  34. package/dist/contracts/endpoints.contract.js +17 -2
  35. package/dist/contracts/endpoints.contract.js.map +1 -1
  36. package/dist/contracts/exit.contract.d.ts.map +1 -1
  37. package/dist/contracts/exit.contract.js +62 -7
  38. package/dist/contracts/exit.contract.js.map +1 -1
  39. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  40. package/dist/contracts/extensions.contract.js +124 -15
  41. package/dist/contracts/extensions.contract.js.map +1 -1
  42. package/dist/contracts/git.contract.d.ts.map +1 -1
  43. package/dist/contracts/git.contract.js +384 -43
  44. package/dist/contracts/git.contract.js.map +1 -1
  45. package/dist/contracts/grok.contract.d.ts.map +1 -1
  46. package/dist/contracts/grok.contract.js +25 -3
  47. package/dist/contracts/grok.contract.js.map +1 -1
  48. package/dist/contracts/history.contract.d.ts.map +1 -1
  49. package/dist/contracts/history.contract.js +35 -4
  50. package/dist/contracts/history.contract.js.map +1 -1
  51. package/dist/contracts/intentic.contract.d.ts.map +1 -1
  52. package/dist/contracts/intentic.contract.js +25 -3
  53. package/dist/contracts/intentic.contract.js.map +1 -1
  54. package/dist/contracts/inventory.contract.d.ts.map +1 -1
  55. package/dist/contracts/inventory.contract.js +26 -3
  56. package/dist/contracts/inventory.contract.js.map +1 -1
  57. package/dist/contracts/logs.contract.d.ts.map +1 -1
  58. package/dist/contracts/logs.contract.js +17 -2
  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.map +1 -1
  76. package/dist/contracts/providers.contract.js +9 -1
  77. package/dist/contracts/providers.contract.js.map +1 -1
  78. package/dist/contracts/public.contract.d.ts.map +1 -1
  79. package/dist/contracts/public.contract.js +26 -3
  80. package/dist/contracts/public.contract.js.map +1 -1
  81. package/dist/contracts/push.contract.d.ts.map +1 -1
  82. package/dist/contracts/push.contract.js +35 -4
  83. package/dist/contracts/push.contract.js.map +1 -1
  84. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  85. package/dist/contracts/secrets.contract.js +43 -5
  86. package/dist/contracts/secrets.contract.js.map +1 -1
  87. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  88. package/dist/contracts/sessions.contract.js +15 -2
  89. package/dist/contracts/sessions.contract.js.map +1 -1
  90. package/dist/contracts/settings.contract.d.ts.map +1 -1
  91. package/dist/contracts/settings.contract.js +43 -5
  92. package/dist/contracts/settings.contract.js.map +1 -1
  93. package/dist/contracts/share.contract.d.ts.map +1 -1
  94. package/dist/contracts/share.contract.js +35 -4
  95. package/dist/contracts/share.contract.js.map +1 -1
  96. package/dist/contracts/skills.contract.d.ts.map +1 -1
  97. package/dist/contracts/skills.contract.js +35 -4
  98. package/dist/contracts/skills.contract.js.map +1 -1
  99. package/dist/contracts/system.contract.d.ts.map +1 -1
  100. package/dist/contracts/system.contract.js +107 -14
  101. package/dist/contracts/system.contract.js.map +1 -1
  102. package/dist/contracts/translator.contract.d.ts.map +1 -1
  103. package/dist/contracts/translator.contract.js +29 -4
  104. package/dist/contracts/translator.contract.js.map +1 -1
  105. package/dist/contracts/usage.contract.d.ts.map +1 -1
  106. package/dist/contracts/usage.contract.js +9 -1
  107. package/dist/contracts/usage.contract.js.map +1 -1
  108. package/dist/contracts/vpn.contract.d.ts.map +1 -1
  109. package/dist/contracts/vpn.contract.js +32 -4
  110. package/dist/contracts/vpn.contract.js.map +1 -1
  111. package/dist/contracts/workflows.contract.d.ts.map +1 -1
  112. package/dist/contracts/workflows.contract.js +70 -8
  113. package/dist/contracts/workflows.contract.js.map +1 -1
  114. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  115. package/dist/contracts/workspace.contract.js +216 -25
  116. package/dist/contracts/workspace.contract.js.map +1 -1
  117. package/dist/events.d.ts.map +1 -1
  118. package/dist/events.js +179 -88
  119. package/dist/events.js.map +1 -1
  120. package/dist/history-state.d.ts.map +1 -1
  121. package/dist/history-state.js +1 -0
  122. package/dist/history-state.js.map +1 -1
  123. package/dist/index.d.ts +1 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/schemas.d.ts +1 -0
  126. package/dist/schemas.d.ts.map +1 -1
  127. package/dist/schemas.js +2856 -1276
  128. package/dist/schemas.js.map +1 -1
  129. package/package.json +4 -4
  130. package/src/agent-catalog.test.ts +48 -3
  131. package/src/agent-catalog.ts +88 -0
  132. package/src/capability-ledger.test.ts +21 -2
  133. package/src/contracts/activity.contract.ts +18 -2
  134. package/src/contracts/agent.contract.ts +78 -8
  135. package/src/contracts/agents.contract.ts +180 -19
  136. package/src/contracts/automations.contract.ts +80 -9
  137. package/src/contracts/capabilities.contract.ts +106 -11
  138. package/src/contracts/chores.contract.ts +29 -3
  139. package/src/contracts/ci.contract.ts +55 -6
  140. package/src/contracts/claude.contract.ts +47 -5
  141. package/src/contracts/drafts.contract.ts +26 -3
  142. package/src/contracts/endpoints.contract.ts +19 -2
  143. package/src/contracts/exit.contract.ts +69 -7
  144. package/src/contracts/extensions.contract.ts +134 -15
  145. package/src/contracts/git.contract.ts +421 -43
  146. package/src/contracts/grok.contract.ts +26 -3
  147. package/src/contracts/history.contract.ts +37 -4
  148. package/src/contracts/intentic.contract.ts +28 -3
  149. package/src/contracts/inventory.contract.ts +27 -3
  150. package/src/contracts/logs.contract.ts +18 -2
  151. package/src/contracts/loops.contract.ts +58 -6
  152. package/src/contracts/panels.contract.ts +26 -3
  153. package/src/contracts/personas.contract.ts +77 -8
  154. package/src/contracts/ports.contract.ts +27 -3
  155. package/src/contracts/prepush.contract.ts +26 -3
  156. package/src/contracts/providers.contract.ts +10 -1
  157. package/src/contracts/public.contract.ts +29 -3
  158. package/src/contracts/push.contract.ts +37 -4
  159. package/src/contracts/secrets.contract.ts +46 -5
  160. package/src/contracts/sessions.contract.ts +16 -2
  161. package/src/contracts/settings.contract.ts +46 -5
  162. package/src/contracts/share.contract.ts +37 -4
  163. package/src/contracts/skills.contract.ts +39 -4
  164. package/src/contracts/system.contract.ts +119 -14
  165. package/src/contracts/translator.contract.ts +32 -4
  166. package/src/contracts/usage.contract.ts +10 -1
  167. package/src/contracts/vpn.contract.ts +35 -4
  168. package/src/contracts/workflows.contract.ts +77 -8
  169. package/src/contracts/workspace.contract.ts +231 -25
  170. package/src/events.ts +207 -88
  171. package/src/history-state.ts +6 -0
  172. package/src/schemas.ts +3506 -1260
@@ -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
  };
@@ -29,32 +29,127 @@ import {
29
29
  * anything runs, the only way to change one setting on a tunnel without re-typing its key. A marker with
30
30
  * nothing behind it is refused rather than written. */
31
31
  export const capabilitiesContract = {
32
- list: oc.route({ method: "GET", path: "/capabilities" }).output(CapabilitiesListSchema),
33
- add: oc.route({ method: "POST", path: "/capabilities" }).input(CapabilitySchema).output(eventIterator(IntenticLineSchema)),
34
- remove: oc.route({ method: "DELETE", path: "/capabilities/{id}" }).input(CapabilityIdParamSchema).output(OkSchema),
32
+ list: oc
33
+ .route({
34
+ method: "GET",
35
+ path: "/capabilities",
36
+ summary: "Everything this sandbox is connected to",
37
+ description:
38
+ "Each connection with its live state, the settings that are safe to show, and the names of the credentials it holds. The values of those credentials are never in the answer, on any route but one.",
39
+ })
40
+ .output(CapabilitiesListSchema),
41
+ add: oc
42
+ .route({
43
+ method: "POST",
44
+ path: "/capabilities",
45
+ summary: "Connect something, or change a connection",
46
+ description:
47
+ "Writes a connection and streams the work of applying it, because some kinds provision real infrastructure and take a while. Sending an id that already exists edits that connection: this is the edit as well as the create. Since a caller is never shown stored credentials, it marks the ones it is leaving alone and the daemon fills them in, which is the only way to change one setting without retyping a key.",
48
+ })
49
+ .input(CapabilitySchema)
50
+ .output(eventIterator(IntenticLineSchema)),
51
+ remove: oc
52
+ .route({
53
+ method: "DELETE",
54
+ path: "/capabilities/{id}",
55
+ summary: "Disconnect something",
56
+ description:
57
+ "Tears a connection down. The kinds that own real infrastructure refuse, because deleting those would be losing data rather than losing a connection.",
58
+ })
59
+ .input(CapabilityIdParamSchema)
60
+ .output(OkSchema),
35
61
  /* Give a connection a different name, carrying what the old one keyed: a browser profile with its logins,
36
62
  * a connected machine's enrollment, an extension's checkout. The name is the agent's handle for the thing,
37
63
  * so this is a migration, add + remove would lose exactly the state that makes the connection worth
38
64
  * keeping. A kind whose name is part of what it IS (the scaffolders, the one-per-sandbox cards) refuses. */
39
- rename: oc.route({ method: "POST", path: "/capabilities/{id}/rename" }).input(CapabilityRenameSchema).output(OkSchema),
65
+ rename: oc
66
+ .route({
67
+ method: "POST",
68
+ path: "/capabilities/{id}/rename",
69
+ summary: "Rename a connection",
70
+ description:
71
+ "Carries everything the old name keyed across with it: a browser profile and its logins, an enrolled machine, an extension's copy of its source. Removing and re-adding would lose exactly the state that made the connection worth keeping. Kinds whose name is part of what they are refuse.",
72
+ })
73
+ .input(CapabilityRenameSchema)
74
+ .output(OkSchema),
40
75
  // Replace just the secret in a capability's config (the /secrets page's edit) and re-run its apply.
41
- setSecret: oc.route({ method: "POST", path: "/capabilities/{id}/secret" }).input(CapabilitySecretInputSchema).output(OkSchema),
42
- status: oc.route({ method: "GET", path: "/capabilities/{id}/status" }).input(CapabilityIdParamSchema).output(CapabilityStatusSchema),
76
+ setSecret: oc
77
+ .route({
78
+ method: "POST",
79
+ path: "/capabilities/{id}/secret",
80
+ summary: "Replace a stored credential",
81
+ description: "Swaps one connection's key or token for a new one and re-applies it, without touching any of its other settings.",
82
+ })
83
+ .input(CapabilitySecretInputSchema)
84
+ .output(OkSchema),
85
+ status: oc
86
+ .route({
87
+ method: "GET",
88
+ path: "/capabilities/{id}/status",
89
+ summary: "Re-check one connection",
90
+ description: "Probes a single connection right now, for a screen that wants to refresh one row rather than the whole list.",
91
+ })
92
+ .input(CapabilityIdParamSchema)
93
+ .output(CapabilityStatusSchema),
43
94
  /* One capability's stored config, secrets included, how an extension BACKEND dials the service behind a
44
95
  * connected capability (ext-deployments reads its Komodo's key pair through this). Never a browser's: the
45
96
  * handler refuses any caller with a member identity, so only the daemon's header grants reach it, and an
46
97
  * extension's grant reaches it only when its manifest declares the route in `permissions.daemon`, which
47
98
  * is the install dialog saying, in one line, "this extension can read connected credentials". */
48
- connection: oc.route({ method: "GET", path: "/capabilities/{id}/connection" }).input(CapabilityIdParamSchema).output(CapabilityConnectionSchema),
49
- marketplace: oc.route({ method: "POST", path: "/capabilities/marketplace" }).input(MarketplaceRequestSchema).output(MarketplaceSchema),
99
+ connection: oc
100
+ .route({
101
+ method: "GET",
102
+ path: "/capabilities/{id}/connection",
103
+ summary: "A connection's settings, credentials included",
104
+ description:
105
+ "The one call that hands back stored secrets, so an extension's own backend can dial the service behind a connection. Never answered for a signed-in person: only a machine credential reaches it, and an extension's only if its manifest asked for this route out loud at install time.",
106
+ })
107
+ .input(CapabilityIdParamSchema)
108
+ .output(CapabilityConnectionSchema),
109
+ marketplace: oc
110
+ .route({
111
+ method: "POST",
112
+ path: "/capabilities/marketplace",
113
+ summary: "Read a plugin marketplace",
114
+ description: "Resolves a plugin marketplace source into the list of connections you could install from it.",
115
+ })
116
+ .input(MarketplaceRequestSchema)
117
+ .output(MarketplaceSchema),
50
118
  // "Not needed": stop offering this card until the workspace evidence behind it changes. Nothing is torn
51
119
  // down and nothing is remembered about the card itself, only the evidence it was declined against.
52
- dismiss: oc.route({ method: "DELETE", path: "/capabilities/recommendations/{card}" }).input(CapabilityCardParamSchema).output(OkSchema),
120
+ dismiss: oc
121
+ .route({
122
+ method: "DELETE",
123
+ path: "/capabilities/recommendations/{card}",
124
+ summary: "Stop suggesting this connection",
125
+ description:
126
+ "Not needed, for now. Nothing is torn down. The suggestion comes back if what prompted it in the workspace changes, because what is remembered is the evidence, not the refusal.",
127
+ })
128
+ .input(CapabilityCardParamSchema)
129
+ .output(OkSchema),
53
130
  // Start an agent-kind capability's interactive login (its declared loginCommand) in a visible terminal
54
131
  // session the user types into, device-code sign-in flows. Returns the session the panel attaches to.
55
- login: oc.route({ method: "POST", path: "/capabilities/{id}/login" }).input(CapabilityIdParamSchema).output(CapabilityLoginSchema),
132
+ login: oc
133
+ .route({
134
+ method: "POST",
135
+ path: "/capabilities/{id}/login",
136
+ summary: "Sign in to a connection by hand",
137
+ description:
138
+ "Opens the connection's own sign-in in a terminal a person can type into, for the flows that need a code pasted or a device confirmed. The answer names the terminal to attach to.",
139
+ })
140
+ .input(CapabilityIdParamSchema)
141
+ .output(CapabilityLoginSchema),
56
142
  // Mint one TOTP code from the capability's stored seed (a field its card marks `totp`). The one capability
57
143
  // read the agent token is admitted to (see auth/grants): a code expires within its period and never reveals
58
144
  // the seed, so the in-sandbox `otp` command can answer a 2FA prompt without the agent holding the factor.
59
- otp: oc.route({ method: "GET", path: "/capabilities/{id}/otp" }).input(CapabilityIdParamSchema).output(CapabilityOtpSchema),
145
+ otp: oc
146
+ .route({
147
+ method: "GET",
148
+ path: "/capabilities/{id}/otp",
149
+ summary: "Mint a one-time code",
150
+ description:
151
+ "Generates a single two-factor code from a stored seed. The one credential-adjacent read an agent is allowed, and it is safe because a code expires in seconds and never reveals the seed, so an agent can answer a prompt without ever holding the factor.",
152
+ })
153
+ .input(CapabilityIdParamSchema)
154
+ .output(CapabilityOtpSchema),
60
155
  };
@@ -13,11 +13,37 @@ import { ChoreLedgerWriteSchema, ChoreProbeRequestSchema, ChoresReportSchema, Ok
13
13
  export const choresContract = {
14
14
  // Every repo's standing evidence in one read: cached probe results (with their age and state), the cheap
15
15
  // resident signals, the ledger, and the daemon's node version. The rail badge polls this; so does the panel.
16
- list: oc.route({ method: "GET", path: "/chores" }).output(ChoresReportSchema),
16
+ list: oc
17
+ .route({
18
+ method: "GET",
19
+ path: "/chores",
20
+ summary: "What maintenance the repos are asking for",
21
+ description:
22
+ "Every repo's standing evidence in one read: what the last measurement found and how old it is, the cheap signals that are always current, and what has already been decided about each.",
23
+ })
24
+ .output(ChoresReportSchema),
17
25
  // Re-run one repo's probe now, ignoring its TTL, the panel's per-probe refresh. An ack: the runner works in
18
26
  // the background and the result arrives on the next `list`, because a jscpd sweep outlives any sane request.
19
- probe: oc.route({ method: "POST", path: "/chores/probe" }).input(ChoreProbeRequestSchema).output(OkSchema),
27
+ probe: oc
28
+ .route({
29
+ method: "POST",
30
+ path: "/chores/probe",
31
+ summary: "Measure one repo again now",
32
+ description:
33
+ "Re-runs a single check without waiting for it to go stale. Answers immediately: the work happens in the background and the result turns up in the next read, because some of these sweeps outlive any sane request.",
34
+ })
35
+ .input(ChoreProbeRequestSchema)
36
+ .output(OkSchema),
20
37
  // Record what a chore turn concluded, or snooze one. Upsert by repo+chore: a chore has one current verdict,
21
38
  // and a growing history of "we looked at this and it was fine" is not something any reader wants paged.
22
- record: oc.route({ method: "POST", path: "/chores/ledger" }).input(ChoreLedgerWriteSchema).output(OkSchema),
39
+ record: oc
40
+ .route({
41
+ method: "POST",
42
+ path: "/chores/ledger",
43
+ summary: "Record a verdict, or snooze one",
44
+ description:
45
+ "Writes what somebody concluded about one repo's chore, replacing the previous verdict. A chore has one current answer, not a growing pile of times it was fine.",
46
+ })
47
+ .input(ChoreLedgerWriteSchema)
48
+ .output(OkSchema),
23
49
  };
@@ -15,13 +15,62 @@ import {
15
15
  // (/ci/webhook/:host) is a plain Hono route, vendors can't do Google ID tokens, so it lives outside this
16
16
  // contract, like /automations/{id}/fire.
17
17
  export const ciContract = {
18
- runs: oc.route({ method: "GET", path: "/ci/runs" }).output(CiRunsResponseSchema),
19
- rerun: oc.route({ method: "POST", path: "/ci/runs/rerun" }).input(CiRunParamSchema).output(OkSchema),
20
- cancel: oc.route({ method: "POST", path: "/ci/runs/cancel" }).input(CiRunParamSchema).output(OkSchema),
21
- jobs: oc.route({ method: "POST", path: "/ci/runs/jobs" }).input(CiRunParamSchema).output(CiJobsResponseSchema),
22
- fix: oc.route({ method: "POST", path: "/ci/fix" }).input(CiFixParamSchema).output(CiFixResponseSchema),
18
+ runs: oc
19
+ .route({
20
+ method: "GET",
21
+ path: "/ci/runs",
22
+ summary: "Pipeline runs across the repos",
23
+ description:
24
+ "What the forges are reporting for every workspace repo that has a remote, served from a cache and filled in on demand. Repos whose notifications are not wired up say so.",
25
+ })
26
+ .output(CiRunsResponseSchema),
27
+ rerun: oc
28
+ .route({
29
+ method: "POST",
30
+ path: "/ci/runs/rerun",
31
+ summary: "Run a pipeline again",
32
+ description: "Asks the forge to re-run one pipeline. The daemon only passes the request along.",
33
+ })
34
+ .input(CiRunParamSchema)
35
+ .output(OkSchema),
36
+ cancel: oc
37
+ .route({
38
+ method: "POST",
39
+ path: "/ci/runs/cancel",
40
+ summary: "Cancel a pipeline run",
41
+ description: "Asks the forge to stop a run in progress.",
42
+ })
43
+ .input(CiRunParamSchema)
44
+ .output(OkSchema),
45
+ jobs: oc
46
+ .route({
47
+ method: "POST",
48
+ path: "/ci/runs/jobs",
49
+ summary: "The steps inside one pipeline run",
50
+ description: "Each job in a run with its outcome, which is where you look to find out what actually broke.",
51
+ })
52
+ .input(CiRunParamSchema)
53
+ .output(CiJobsResponseSchema),
54
+ fix: oc
55
+ .route({
56
+ method: "POST",
57
+ path: "/ci/fix",
58
+ summary: "Put an agent on a broken pipeline",
59
+ description:
60
+ "Opens a fresh isolated conversation already holding the failure: which job, which repo, what it said. The answer names the conversation so you can open it.",
61
+ })
62
+ .input(CiFixParamSchema)
63
+ .output(CiFixResponseSchema),
23
64
  // "I have looked at the pipelines", what silences the rail badge for breakages already read. No input:
24
65
  // the surface is read as a whole, and the daemon stamps its own clock so a skewed browser can't mark
25
66
  // future failures as already seen.
26
- seen: oc.route({ method: "POST", path: "/ci/seen" }).output(CiSeenResponseSchema),
67
+ seen: oc
68
+ .route({
69
+ method: "POST",
70
+ path: "/ci/seen",
71
+ summary: "Mark the pipelines as read",
72
+ description:
73
+ "Silences the badge for breakages already looked at. Takes nothing, because the view is read as a whole, and the daemon stamps its own clock so a browser with the wrong time cannot mark future failures as already seen.",
74
+ })
75
+ .output(CiSeenResponseSchema),
27
76
  };
@@ -19,11 +19,53 @@ import {
19
19
  // shared /providers/{provider}/models route (providers.contract.ts). What is left is what is genuinely Claude's
20
20
  //, an account handshake no other provider has.
21
21
  export const claudeContract = {
22
- start: oc.route({ method: "POST", path: "/claude/oauth/start" }).output(AuthorizeChallengeSchema),
23
- exchange: oc.route({ method: "POST", path: "/claude/oauth/exchange" }).input(OauthExchangeSchema).output(OauthAccountSchema),
22
+ start: oc
23
+ .route({
24
+ method: "POST",
25
+ path: "/claude/oauth/start",
26
+ summary: "Begin connecting a Claude account",
27
+ description:
28
+ "Hands back the address to send somebody to, and the proof this sandbox will need to finish the exchange. The sandbox holds the credential afterwards, not the browser.",
29
+ })
30
+ .output(AuthorizeChallengeSchema),
31
+ exchange: oc
32
+ .route({
33
+ method: "POST",
34
+ path: "/claude/oauth/exchange",
35
+ summary: "Finish connecting a Claude account",
36
+ description:
37
+ "Trades the code from the sign-in for stored tokens and answers with the account it just connected. A sandbox can hold several Claude accounts side by side.",
38
+ })
39
+ .input(OauthExchangeSchema)
40
+ .output(OauthAccountSchema),
24
41
  // Each account carries its plan-limit reading. `force` re-measures before answering, see
25
42
  // AccountListQuerySchema, and USAGE_WAIT_MS in claude.routes.ts for what an ordinary read waits.
26
- accounts: oc.route({ method: "GET", path: "/claude/accounts" }).input(AccountListQuerySchema).output(OauthAccountListSchema),
27
- rename: oc.route({ method: "POST", path: "/claude/account/rename" }).input(AccountRenameSchema).output(OauthAccountSchema),
28
- disconnect: oc.route({ method: "POST", path: "/claude/account/disconnect" }).input(AccountIdSchema).output(OkSchema),
43
+ accounts: oc
44
+ .route({
45
+ method: "GET",
46
+ path: "/claude/accounts",
47
+ summary: "Connected Claude accounts",
48
+ description:
49
+ "Each connected account with how full its plan limits were when last measured. Ask for a fresh measurement and it takes one before answering, which is slower.",
50
+ })
51
+ .input(AccountListQuerySchema)
52
+ .output(OauthAccountListSchema),
53
+ rename: oc
54
+ .route({
55
+ method: "POST",
56
+ path: "/claude/account/rename",
57
+ summary: "Rename a Claude account",
58
+ description: "Changes the label one account shows under, so several are tellable apart.",
59
+ })
60
+ .input(AccountRenameSchema)
61
+ .output(OauthAccountSchema),
62
+ disconnect: oc
63
+ .route({
64
+ method: "POST",
65
+ path: "/claude/account/disconnect",
66
+ summary: "Disconnect a Claude account",
67
+ description: "Clears the stored tokens for one account. The others stay connected.",
68
+ })
69
+ .input(AccountIdSchema)
70
+ .output(OkSchema),
29
71
  };
@@ -5,7 +5,30 @@ import { DraftIdParamSchema, DraftsListSchema, DraftSummarySchema, OkSchema } fr
5
5
  // directly, these routes are the OWNER's side: `list` is the approval inbox, `upsert` covers approve / edit /
6
6
  // retry (all a plain re-post with a field changed, like the automations enabled toggle), `remove` is reject.
7
7
  export const draftsContract = {
8
- list: oc.route({ method: "GET", path: "/drafts" }).output(DraftsListSchema),
9
- upsert: oc.route({ method: "POST", path: "/drafts" }).input(DraftSummarySchema).output(OkSchema),
10
- remove: oc.route({ method: "DELETE", path: "/drafts/{id}" }).input(DraftIdParamSchema).output(OkSchema),
8
+ list: oc
9
+ .route({
10
+ method: "GET",
11
+ path: "/drafts",
12
+ summary: "Posts waiting for your approval",
13
+ description: "The queue of things an agent has written and would like to publish. Nothing here has gone anywhere yet.",
14
+ })
15
+ .output(DraftsListSchema),
16
+ upsert: oc
17
+ .route({
18
+ method: "POST",
19
+ path: "/drafts",
20
+ summary: "Approve, edit or retry a draft",
21
+ description: "All three are the same act with a different field changed, so they share one call. Send the draft back as you want it.",
22
+ })
23
+ .input(DraftSummarySchema)
24
+ .output(OkSchema),
25
+ remove: oc
26
+ .route({
27
+ method: "DELETE",
28
+ path: "/drafts/{id}",
29
+ summary: "Reject a draft",
30
+ description: "Throws one away unposted.",
31
+ })
32
+ .input(DraftIdParamSchema)
33
+ .output(OkSchema),
11
34
  };
@@ -44,6 +44,23 @@ export const TrialStatusSchema = z.object({
44
44
  export type TrialStatusResponse = z.infer<typeof TrialStatusSchema>;
45
45
 
46
46
  export const endpointsContract = {
47
- models: oc.route({ method: "GET", path: "/endpoints/{id}/models" }).input(CapabilityIdParamSchema).output(ModelsSchema),
48
- trial: oc.route({ method: "GET", path: "/endpoints/trial/status" }).output(TrialStatusSchema),
47
+ models: oc
48
+ .route({
49
+ method: "GET",
50
+ path: "/endpoints/{id}/models",
51
+ summary: "Models a connected server offers",
52
+ description:
53
+ "Asks one configured model server what it serves. There is no built-in list and no fallback: what a server offers is knowable only by asking it, so an empty answer is the honest report that we could not.",
54
+ })
55
+ .input(CapabilityIdParamSchema)
56
+ .output(ModelsSchema),
57
+ trial: oc
58
+ .route({
59
+ method: "GET",
60
+ path: "/endpoints/trial/status",
61
+ summary: "What is left of the free trial",
62
+ description:
63
+ "The allowance, what has been used, when it resets, and which model actually answered the last message. Not being available is the ordinary answer rather than a failure: most sandboxes run against a platform that offers no trial at all.",
64
+ })
65
+ .output(TrialStatusSchema),
49
66
  };
@@ -18,25 +18,87 @@ export const exitContract = {
18
18
  // Every configured exit with its live state, where it was asked to come out and where it actually does.
19
19
  // Drives the Status card, the browser account picker and `geo list` (the CLI is `geo`: `exit` is a shell
20
20
  // builtin, so a binary of that name is unreachable from a command line).
21
- list: oc.route({ method: "GET", path: "/exit" }).output(ExitListSchema),
21
+ list: oc
22
+ .route({
23
+ method: "GET",
24
+ path: "/exit",
25
+ summary: "Ways to come out somewhere else",
26
+ description:
27
+ "Every configured exit with its live state, the country it was asked to appear in, and the country it actually appears in. Those last two disagreeing is the whole reason this reports both.",
28
+ })
29
+ .output(ExitListSchema),
22
30
  // What this exit can reach, ranked by how much capacity is actually there. Live from the provider when it
23
31
  // answers (Tor's directory, VPN Gate's CSV, the pasted confs), from the baked fallback when it does not,
24
32
  // and `live` says which. This is what auto-fills the country picker instead of a user hunting hostnames.
25
- countries: oc.route({ method: "GET", path: "/exit/{id}/countries" }).input(ExitIdParamSchema).output(ExitCountriesSchema),
33
+ countries: oc
34
+ .route({
35
+ method: "GET",
36
+ path: "/exit/{id}/countries",
37
+ summary: "Countries one exit can reach",
38
+ description:
39
+ "Where this exit can put you, ranked by how much capacity is really there. Asked of the provider when it answers and taken from a built-in list when it does not, and the answer says which of those you got.",
40
+ })
41
+ .input(ExitIdParamSchema)
42
+ .output(ExitCountriesSchema),
26
43
  // Bring the exit up at its stored country. Streams, because a first start pulls a catalog, brings up a
27
44
  // tunnel and then verifies the address, which is tens of seconds on the free providers and can fail at
28
45
  // each step with something the user has to read. Idempotent: starting an up exit reports it and stops.
29
- start: oc.route({ method: "POST", path: "/exit/{id}/start" }).input(ExitIdParamSchema).output(eventIterator(IntenticLineSchema)),
46
+ start: oc
47
+ .route({
48
+ method: "POST",
49
+ path: "/exit/{id}/start",
50
+ summary: "Bring an exit up",
51
+ description:
52
+ "Starts the exit in the country it was configured for. Streamed, because a first start fetches a catalogue, raises a tunnel and then checks the address, which takes tens of seconds on the free providers and can fail at each step with something worth reading. Starting one that is already up simply says so.",
53
+ })
54
+ .input(ExitIdParamSchema)
55
+ .output(eventIterator(IntenticLineSchema)),
30
56
  // Move to another country, starting the exit first if it was down. Streams for the same reason as start,
31
57
  // and THROWS when the observed country does not end up matching: a switch that silently left traffic where
32
58
  // it was is the one failure mode this whole feature exists to make impossible.
33
- use: oc.route({ method: "POST", path: "/exit/{id}/use" }).input(ExitUseInputSchema).output(eventIterator(IntenticLineSchema)),
59
+ use: oc
60
+ .route({
61
+ method: "POST",
62
+ path: "/exit/{id}/use",
63
+ summary: "Move to another country",
64
+ description:
65
+ "Switches the exit's country, starting it first if it was down. It ends by checking where the world actually sees you and fails if that does not match what you asked for. A switch that quietly left your traffic where it was is the exact failure this whole feature exists to rule out.",
66
+ })
67
+ .input(ExitUseInputSchema)
68
+ .output(eventIterator(IntenticLineSchema)),
34
69
  // A different address in the SAME country. Cheap on tor (a control-port signal), a re-dial to another
35
70
  // server on the rest. Fails when the address does not actually change, which on a small pool it can't.
36
- rotate: oc.route({ method: "POST", path: "/exit/{id}/rotate" }).input(ExitIdParamSchema).output(eventIterator(IntenticLineSchema)),
71
+ rotate: oc
72
+ .route({
73
+ method: "POST",
74
+ path: "/exit/{id}/rotate",
75
+ summary: "Take a different address, same country",
76
+ description:
77
+ "Swaps to another address in the country you are already in. Fails if the address does not actually change, which on a small pool it sometimes cannot.",
78
+ })
79
+ .input(ExitIdParamSchema)
80
+ .output(eventIterator(IntenticLineSchema)),
37
81
  // What the world sees through this exit right now. Cheap, unstreamed, and the honest answer to "am I
38
82
  // actually in Germany", which is the question every other route here is judged against.
39
- check: oc.route({ method: "POST", path: "/exit/{id}/check" }).input(ExitIdParamSchema).output(ExitObservationSchema),
83
+ check: oc
84
+ .route({
85
+ method: "POST",
86
+ path: "/exit/{id}/check",
87
+ summary: "Where the world sees you right now",
88
+ description:
89
+ "Looks up the address and country as seen through this exit. Cheap, and the honest answer to whether you are really where you meant to be, which is what every other call here is judged against.",
90
+ })
91
+ .input(ExitIdParamSchema)
92
+ .output(ExitObservationSchema),
40
93
  // Take it down. Tolerates an already-down exit: "make it not be up" is the contract, not "it was up".
41
- stop: oc.route({ method: "POST", path: "/exit/{id}/stop" }).input(ExitIdParamSchema).output(OkSchema),
94
+ stop: oc
95
+ .route({
96
+ method: "POST",
97
+ path: "/exit/{id}/stop",
98
+ summary: "Take an exit down",
99
+ description:
100
+ "Shuts the exit off. One that was already down is fine: the promise is that it is not up afterwards, not that it was up before.",
101
+ })
102
+ .input(ExitIdParamSchema)
103
+ .output(OkSchema),
42
104
  };