@intentic/sandbox-contract 1.223.0 → 1.225.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 (165) hide show
  1. package/README.md +14 -14
  2. package/dist/agent-catalog.js +3 -3
  3. package/dist/agent-catalog.js.map +1 -1
  4. package/dist/chores/chores.js +43 -43
  5. package/dist/chores/chores.js.map +1 -1
  6. package/dist/chores/extension-update.js +2 -2
  7. package/dist/chores/extension-update.js.map +1 -1
  8. package/dist/chores/fix-deps.js +1 -1
  9. package/dist/chores/fix-deps.js.map +1 -1
  10. package/dist/chores/probes.js +1 -1
  11. package/dist/chores/probes.js.map +1 -1
  12. package/dist/chores/prompt.d.ts.map +1 -1
  13. package/dist/chores/prompt.js +3 -3
  14. package/dist/chores/prompt.js.map +1 -1
  15. package/dist/chores/verdict.js +2 -2
  16. package/dist/chores/verdict.js.map +1 -1
  17. package/dist/contracts/capabilities.contract.d.ts +41 -0
  18. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  19. package/dist/contracts/exit.contract.d.ts +80 -0
  20. package/dist/contracts/exit.contract.d.ts.map +1 -0
  21. package/dist/contracts/exit.contract.js +13 -0
  22. package/dist/contracts/exit.contract.js.map +1 -0
  23. package/dist/contracts/ports.contract.d.ts +11 -0
  24. package/dist/contracts/ports.contract.d.ts.map +1 -1
  25. package/dist/contracts/settings.contract.d.ts +12 -0
  26. package/dist/contracts/settings.contract.d.ts.map +1 -1
  27. package/dist/events.d.ts +4 -4
  28. package/dist/events.d.ts.map +1 -1
  29. package/dist/events.js +5 -5
  30. package/dist/events.js.map +1 -1
  31. package/dist/fast-tier.d.ts +9 -0
  32. package/dist/fast-tier.d.ts.map +1 -0
  33. package/dist/fast-tier.js +19 -0
  34. package/dist/fast-tier.js.map +1 -0
  35. package/dist/history-state.js +3 -3
  36. package/dist/history-state.js.map +1 -1
  37. package/dist/index.d.ts +214 -68
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +5 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/model-order.d.ts +1 -0
  42. package/dist/model-order.d.ts.map +1 -1
  43. package/dist/model-order.js +5 -0
  44. package/dist/model-order.js.map +1 -1
  45. package/dist/output-fields.d.ts.map +1 -1
  46. package/dist/output-fields.js +2 -2
  47. package/dist/output-fields.js.map +1 -1
  48. package/dist/prompt-complexity.d.ts +19 -0
  49. package/dist/prompt-complexity.d.ts.map +1 -0
  50. package/dist/prompt-complexity.js +83 -0
  51. package/dist/prompt-complexity.js.map +1 -0
  52. package/dist/publish-drafts.js +2 -2
  53. package/dist/publish-drafts.js.map +1 -1
  54. package/dist/schemas.d.ts +236 -0
  55. package/dist/schemas.d.ts.map +1 -1
  56. package/dist/schemas.js +116 -2
  57. package/dist/schemas.js.map +1 -1
  58. package/dist/workflow-faults.js +3 -3
  59. package/dist/workflow-faults.js.map +1 -1
  60. package/dist/workspace-state.d.ts +20 -20
  61. package/dist/workspace-state.d.ts.map +1 -1
  62. package/dist/workspace-state.js +20 -20
  63. package/dist/workspace-state.js.map +1 -1
  64. package/package.json +5 -5
  65. package/src/agent-catalog.test.ts +25 -25
  66. package/src/agent-catalog.ts +81 -81
  67. package/src/agent-run-model.test.ts +3 -3
  68. package/src/agent-run-model.ts +8 -8
  69. package/src/capability-env.ts +3 -3
  70. package/src/capability-ledger.test.ts +13 -13
  71. package/src/capability-secrets.ts +5 -5
  72. package/src/chores/chores.test.ts +1 -1
  73. package/src/chores/chores.ts +109 -109
  74. package/src/chores/digest.test.ts +1 -1
  75. package/src/chores/digest.ts +3 -3
  76. package/src/chores/extension-update.ts +5 -5
  77. package/src/chores/fix-deps.ts +4 -4
  78. package/src/chores/probes.test.ts +6 -6
  79. package/src/chores/probes.ts +17 -17
  80. package/src/chores/prompt.ts +9 -9
  81. package/src/chores/stack.test.ts +3 -3
  82. package/src/chores/stack.ts +17 -17
  83. package/src/chores/verdict.test.ts +20 -20
  84. package/src/chores/verdict.ts +25 -25
  85. package/src/contract-lock.test.ts +1 -1
  86. package/src/contract-lock.ts +7 -7
  87. package/src/contracts/activity.contract.ts +1 -1
  88. package/src/contracts/agent.contract.ts +2 -2
  89. package/src/contracts/agents.contract.ts +11 -11
  90. package/src/contracts/automations.contract.ts +5 -5
  91. package/src/contracts/capabilities.contract.ts +7 -7
  92. package/src/contracts/chores.contract.ts +3 -3
  93. package/src/contracts/ci.contract.ts +2 -2
  94. package/src/contracts/claude.contract.ts +3 -3
  95. package/src/contracts/drafts.contract.ts +1 -1
  96. package/src/contracts/endpoints.contract.ts +2 -2
  97. package/src/contracts/exit.contract.ts +42 -0
  98. package/src/contracts/extensions.contract.ts +8 -8
  99. package/src/contracts/git.contract.ts +6 -6
  100. package/src/contracts/grok.contract.ts +4 -4
  101. package/src/contracts/host.contract.ts +5 -5
  102. package/src/contracts/intentic.contract.ts +2 -2
  103. package/src/contracts/logs.contract.ts +1 -1
  104. package/src/contracts/loops.contract.ts +8 -8
  105. package/src/contracts/personas.contract.ts +8 -8
  106. package/src/contracts/ports.contract.ts +1 -1
  107. package/src/contracts/prepush.contract.ts +2 -2
  108. package/src/contracts/providers.contract.ts +4 -4
  109. package/src/contracts/public.contract.ts +1 -1
  110. package/src/contracts/push.contract.ts +2 -2
  111. package/src/contracts/secrets.contract.ts +2 -2
  112. package/src/contracts/sessions.contract.ts +2 -2
  113. package/src/contracts/settings.contract.ts +3 -3
  114. package/src/contracts/share.contract.ts +1 -1
  115. package/src/contracts/skills.contract.ts +2 -2
  116. package/src/contracts/system.contract.ts +9 -9
  117. package/src/contracts/usage.contract.ts +1 -1
  118. package/src/contracts/vpn.contract.ts +5 -5
  119. package/src/contracts/workflows.contract.ts +12 -12
  120. package/src/contracts/workspace.contract.ts +13 -13
  121. package/src/conversation-ids.ts +8 -8
  122. package/src/events.test.ts +3 -3
  123. package/src/events.ts +142 -142
  124. package/src/fast-tier.test.ts +88 -0
  125. package/src/fast-tier.ts +72 -0
  126. package/src/history-state.ts +15 -15
  127. package/src/host-protocol.ts +7 -7
  128. package/src/hostnames.test.ts +1 -1
  129. package/src/hostnames.ts +15 -15
  130. package/src/index.ts +13 -8
  131. package/src/listener-protocol.ts +12 -12
  132. package/src/model-order.test.ts +11 -11
  133. package/src/model-order.ts +52 -30
  134. package/src/output-fields.ts +11 -11
  135. package/src/path-refs.test.ts +4 -4
  136. package/src/path-refs.ts +6 -6
  137. package/src/prompt-complexity.test.ts +160 -0
  138. package/src/prompt-complexity.ts +271 -0
  139. package/src/publish-drafts.ts +8 -8
  140. package/src/quick-model.test.ts +11 -11
  141. package/src/quick-model.ts +15 -15
  142. package/src/routes.test.ts +11 -5
  143. package/src/routes.ts +19 -19
  144. package/src/runtime-state.test.ts +1 -1
  145. package/src/runtime-state.ts +22 -22
  146. package/src/schemas.test.ts +8 -8
  147. package/src/schemas.ts +1238 -921
  148. package/src/search-globs.test.ts +2 -2
  149. package/src/search-globs.ts +6 -6
  150. package/src/session-names.ts +5 -5
  151. package/src/share-paths.test.ts +1 -1
  152. package/src/share-paths.ts +7 -7
  153. package/src/sse.ts +1 -1
  154. package/src/state-portability.ts +8 -8
  155. package/src/terminal-protocol.ts +3 -3
  156. package/src/title.test.ts +9 -9
  157. package/src/title.ts +21 -21
  158. package/src/tunnel-ids.test.ts +3 -3
  159. package/src/tunnel-ids.ts +7 -7
  160. package/src/versions.test.ts +3 -3
  161. package/src/versions.ts +6 -6
  162. package/src/workflow-faults.test.ts +6 -6
  163. package/src/workflow-faults.ts +14 -14
  164. package/src/workspace-state.test.ts +32 -32
  165. package/src/workspace-state.ts +139 -139
@@ -27,11 +27,11 @@ import {
27
27
  // connection simply never joins the roster.
28
28
  export const systemContract = {
29
29
  info: oc.route({ method: "GET", path: "/info" }).output(InfoSchema),
30
- // What the daemon could not read in its own `.intentic/` manifests a file it fell back on, a key it did
30
+ // What the daemon could not read in its own `.intentic/` manifests, a file it fell back on, a key it did
31
31
  // not recognise, an entry it skipped. Its own route rather than a field on /info because it is invalidated
32
32
  // by a different thing: a manifest changing on disk, which the workspace-state table already broadcasts.
33
33
  manifestProblems: oc.route({ method: "GET", path: "/system/manifest-problems" }).output(ManifestProblemsSchema),
34
- // Exchange the request's verified bearer (a Google ID token or a still-valid session, which makes this
34
+ // Exchange the request's verified bearer (a Google ID token, or a still-valid session, which makes this
35
35
  // route sliding renewal) for a daemon-minted session, the credential every steady-state call presents.
36
36
  session: oc.route({ method: "POST", path: "/system/session" }).output(DaemonSessionSchema),
37
37
  events: oc
@@ -44,28 +44,28 @@ export const systemContract = {
44
44
  usage: oc.route({ method: "GET", path: "/system/usage" }).output(UsageSummarySchema),
45
45
  // The web-owned tmux sessions behind the terminal tabs. `terminals` enumerates them (the panel rebuilds a tab
46
46
  // per name on load/reload); `killTerminal` destroys one when its tab's close button is clicked. The live I/O
47
- // is the separate /system/terminal WebSocket these are just the control plane. Bearer-authed like the rest
47
+ // is the separate /system/terminal WebSocket, these are just the control plane. Bearer-authed like the rest
48
48
  // (browser fetch sends the header), unlike the header-less WS route which app.ts exempts.
49
49
  terminals: oc.route({ method: "GET", path: "/system/terminals" }).output(TerminalsListSchema),
50
50
  killTerminal: oc.route({ method: "DELETE", path: "/system/terminals/{name}" }).input(TerminalNameParamSchema).output(OkSchema),
51
- // One session's pane history as selectable text the answer to "scroll back and copy that" in a surface
51
+ // One session's pane history as selectable text, the answer to "scroll back and copy that" in a surface
52
52
  // whose live view is a tmux client on the alternate screen, where the scrollback is on the far side of the
53
53
  // socket and the page has nothing to select. See TerminalScrollbackSchema.
54
54
  terminalScrollback: oc
55
55
  .route({ method: "GET", path: "/system/terminals/{name}/scrollback" })
56
56
  .input(TerminalScrollbackQuerySchema)
57
57
  .output(TerminalScrollbackSchema),
58
- // The agent's live Chromiums and the pages each has open the Browsers view's roster, polled while it is on
58
+ // The agent's live Chromiums and the pages each has open, the Browsers view's roster, polled while it is on
59
59
  // screen and by the rail so its tile can appear the moment a turn starts browsing. The frames are the
60
60
  // separate /system/browser-view WebSocket; this is the control plane, exactly as `terminals` is for tmux.
61
61
  // `closeBrowser` shuts one Chromium down: the agent's next browser tool call then fails as if it had crashed,
62
62
  // which is the honest account of the owner pulling the plug.
63
63
  browsers: oc.route({ method: "GET", path: "/system/browsers" }).output(BrowsersListSchema),
64
64
  closeBrowser: oc.route({ method: "DELETE", path: "/system/browsers/{name}" }).input(BrowserNameParamSchema).output(OkSchema),
65
- // The agents this sandbox's agents started SDK subagents and delegated Codex/Grok runs alike (see
65
+ // The agents this sandbox's agents started. SDK subagents and delegated Codex/Grok runs alike (see
66
66
  // SubagentSessionSchema). Same two-route shape as the browsers above, and same division of labour: the list
67
67
  // is polled by the Subagents area while it is on screen and loosely by the rail, so its tile can appear the
68
- // moment a turn delegates. There is no third WebSocket here, because a subagent has no byte stream to watch
68
+ // moment a turn delegates. There is no third WebSocket here, because a subagent has no byte stream to watch,
69
69
  // what you watch it through is its TRANSCRIPT, which `subagentTranscript` serves in the one shape every
70
70
  // other transcript route already answers in: live from the parent turn's frame log while it runs, off the
71
71
  // provider's own store once it has finished.
@@ -74,12 +74,12 @@ export const systemContract = {
74
74
  .route({ method: "GET", path: "/system/subagents/{id}/transcript" })
75
75
  .input(SubagentIdParamSchema)
76
76
  .output(SessionTranscriptSchema),
77
- /* Start, stop, restart, update, rebuild, roll back or remove a sandbox on one of the user's own computers
77
+ /* Start, stop, restart, update, rebuild, roll back or remove a sandbox on one of the user's own computers,
78
78
  * the Computers view's buttons, relayed to the machine over the socket it holds open to us.
79
79
  *
80
80
  * Streamed because the slowest of these takes minutes, and it is the same stream whatever the op: one door
81
81
  * for one decision, so the view has one shape to render rather than one per duration. The daemon adds no
82
- * judgement the machine enforces its own switches and its refusal arrives as the terminal `error` line,
82
+ * judgement, the machine enforces its own switches and its refusal arrives as the terminal `error` line,
83
83
  * in its own words, naming the control to flip. */
84
84
  manageMachineSandbox: oc
85
85
  .route({ method: "POST", path: "/system/computers/{id}/sandboxes/{slug}" })
@@ -1,7 +1,7 @@
1
1
  import { oc } from "@orpc/contract";
2
2
  import { DayWindowQuerySchema, UsageRollupSchema } from "../schemas.js";
3
3
 
4
- // The durable spend ledger (see UsageTurnSchema). Read-only over the wire rows are appended daemon-side at
4
+ // The durable spend ledger (see UsageTurnSchema). Read-only over the wire, rows are appended daemon-side at
5
5
  // turn end, so the ledger stays a trustworthy record of what was actually spent, the same principle as the
6
6
  // activity log. `rollup` serves every cost/usage panel: it groups by day × provider × account × model, and the
7
7
  // browser re-projects from there (spend per day, cost by model, cache hit rate) without another round trip.
@@ -9,22 +9,22 @@ import {
9
9
  VpnListSchema,
10
10
  } from "../schemas.js";
11
11
 
12
- // The live VPN surface. A VPN is ADDED as a `vpn` capability (credentials, autoConnect capabilities.contract);
12
+ // The live VPN surface. A VPN is ADDED as a `vpn` capability (credentials, autoConnect, capabilities.contract);
13
13
  // it is DIALLED here. The split is deliberate: connecting is a runtime operation that both the operator (the
14
14
  // Sandbox ▸ Status card) and the agent (`vpn` on its PATH, which calls these very routes) perform many times
15
- // over one stored connection, so it cannot be a capability re-add and its result is richer than a
15
+ // over one stored connection, so it cannot be a capability re-add, and its result is richer than a
16
16
  // CapabilityStatus, which is why `list` returns VpnLinks instead of {state, detail}.
17
17
  //
18
18
  // Every route reads tunnel state back from the OS rather than from daemon memory, so the agent dropping a
19
19
  // tunnel from a shell and the UI dropping it are the same event, and a daemon restart observes the truth.
20
20
  export const vpnContract = {
21
- // Every configured VPN with its live link state the Status card, the rail indicator, and `vpn list`.
21
+ // Every configured VPN with its live link state, the Status card, the rail indicator, and `vpn list`.
22
22
  list: oc.route({ method: "GET", path: "/vpn" }).output(VpnListSchema),
23
23
  // Dial a stored VPN. Streams the client's progress (auth, then routing) because a dial takes seconds and
24
- // can fail with something the user must read a wrong password, an untrusted gateway certificate, or a
24
+ // can fail with something the user must read, a wrong password, an untrusted gateway certificate, or a
25
25
  // required 2FA code. Idempotent: connecting an already-up tunnel reports it and stops.
26
26
  connect: oc.route({ method: "POST", path: "/vpn/{id}/connect" }).input(VpnConnectInputSchema).output(eventIterator(IntenticLineSchema)),
27
- // Drop a tunnel. Tolerates an already-down one "make it not be up" is the contract, not "it was up".
27
+ // Drop a tunnel. Tolerates an already-down one, "make it not be up" is the contract, not "it was up".
28
28
  disconnect: oc.route({ method: "POST", path: "/vpn/{id}/disconnect" }).input(VpnIdParamSchema).output(OkSchema),
29
29
  // Parse an exported FortiClient configuration into addable connections, so a user with that file fills the
30
30
  // add form by picking a connection instead of re-keying host/port/protocol per tunnel.
@@ -11,11 +11,11 @@ import {
11
11
  WorkflowsListSchema,
12
12
  } from "../schemas.js";
13
13
 
14
- /* The workflow routes "run these sessions, in this order, each handing its result to the next".
14
+ /* The workflow routes, "run these sessions, in this order, each handing its result to the next".
15
15
  *
16
16
  * SPLIT LIKE AUTOMATIONS, NOT LIKE LOOPS, and the split says what a workflow is. A loop has no editor because
17
- * it is started against a conversation and then it is history; a workflow is a DESIGN a thing the user
18
- * authors once, keeps, edits, and runs repeatedly so it gets the manifest treatment: create/update, delete,
17
+ * it is started against a conversation and then it is history; a workflow is a DESIGN, a thing the user
18
+ * authors once, keeps, edits, and runs repeatedly, so it gets the manifest treatment: create/update, delete,
19
19
  * list. What it does not get is an `enabled` toggle, because nothing fires it on its own: a workflow runs when
20
20
  * somebody (or an automation's prompt) says run it.
21
21
  *
@@ -28,25 +28,25 @@ export const workflowsContract = {
28
28
  // page shows both and a workflow with no runs is the interesting case, not an error.
29
29
  list: oc.route({ method: "GET", path: "/workflows" }).output(WorkflowsListSchema),
30
30
  /* Create or replace a workflow, with the operation made explicit so an accidental id collision cannot turn
31
- * a create into replacement. Refuses a graph that cannot run a cycle, a `needs` naming a step
32
- * that is not there, a step with no way to know it is finished with the same sentences the designer shows
31
+ * a create into replacement. Refuses a graph that cannot run, a cycle, a `needs` naming a step
32
+ * that is not there, a step with no way to know it is finished, with the same sentences the designer shows
33
33
  * while you type (see workflowFaults). Validation lives in the contract precisely so those two can never
34
34
  * disagree about what is legal. */
35
35
  save: oc.route({ method: "POST", path: "/workflows" }).input(WorkflowSaveSchema).output(WorkflowSchema),
36
36
  // Deleting a workflow does NOT stop a run of it that is in flight, and does not delete its history: the run
37
37
  // snapshotted its definition when it started, so it stays readable and stays stoppable.
38
38
  remove: oc.route({ method: "DELETE", path: "/workflows/{id}" }).input(WorkflowIdParamSchema).output(OkSchema),
39
- /* Start a run, optionally pointed at a request the sentence the user typed in the composer, which every
39
+ /* Start a run, optionally pointed at a request, the sentence the user typed in the composer, which every
40
40
  * step is handed on top of its own prompt. Every step is recorded `pending` up front, so the graph is
41
41
  * complete from the first frame and a missing node never has to mean two things. Several runs of one
42
- * workflow may be in flight at once they derive different conversation ids, so nothing is shared and
42
+ * workflow may be in flight at once, they derive different conversation ids, so nothing is shared and
43
43
  * nothing can collide, which is what makes "run this design again on a different question" free. */
44
44
  run: oc.route({ method: "POST", path: "/workflows/{id}/run" }).input(WorkflowRunStartSchema).output(WorkflowRunSchema),
45
- // Every run across every workflow, newest first the history the run view opens onto, and the only place a
45
+ // Every run across every workflow, newest first, the history the run view opens onto, and the only place a
46
46
  // deleted workflow's runs are still reachable.
47
47
  runs: oc.route({ method: "GET", path: "/workflows/runs" }).output(WorkflowRunsListSchema),
48
48
  /* Stop a run: nothing that has not started will start, and the steps in flight are CUT OFF where they
49
- * are their turns aborted exactly as /agent/stop aborts one, so whatever they had written stays on
49
+ * are, their turns aborted exactly as /agent/stop aborts one, so whatever they had written stays on
50
50
  * their branches and the step settles as stopped.
51
51
  *
52
52
  * Not the graceful "finish the iteration you are on" a LOOP's stop performs, and the difference is the
@@ -54,18 +54,18 @@ export const workflowsContract = {
54
54
  * Waiting for one meant a stopped run kept working, kept spending and kept asking questions for minutes
55
55
  * after the press, which is indistinguishable from a button that does nothing.
56
56
  *
57
- * IT ALWAYS ENDS THE RUN, including one no scheduler is behind a record left `running` by a daemon that
57
+ * IT ALWAYS ENDS THE RUN, including one no scheduler is behind, a record left `running` by a daemon that
58
58
  * was replaced mid-flight. That case used to be refused, which made the stuck run permanent: a Stop that
59
59
  * could not work, a step count that would never move, and no way off the board.
60
60
  */
61
61
  stopRun: oc.route({ method: "POST", path: "/workflows/runs/{runId}/stop" }).input(WorkflowRunIdParamSchema).output(OkSchema),
62
- /* Take an ENDED run off the board the run's half of `agents.archive`, and the same bargain: nothing is
62
+ /* Take an ENDED run off the board, the run's half of `agents.archive`, and the same bargain: nothing is
63
63
  * lost, the checkouts are reclaimed, and `unarchiveRun` puts it all back. It needs an exit of its own
64
64
  * because nothing about a run transitions once it is over, so a failed one would sit in Attention until
65
65
  * fifty more had rolled it off the ledger. Refused while the run is going.
66
66
  *
67
67
  * IT ARCHIVES THE STEPS WITH THE RUN, which is what makes it an archive rather than a dismissal. A step
68
- * has no card of its own the run's row stands for it so merely dropping the record released the run's
68
+ * has no card of its own, the run's row stands for it, so merely dropping the record released the run's
69
69
  * conversations onto the board as loose cards at the exact moment the user said they were done with the
70
70
  * job. Every step that ran is archived, on the same terms as pointing `agents.archive` at those ids: that
71
71
  * route archives what the user named without re-litigating whether each one was ready to go, and a run the
@@ -36,7 +36,7 @@ import {
36
36
  WorkspaceTreeSchema,
37
37
  } from "../schemas.js";
38
38
 
39
- // The full /work view + extra-repo cloning. The binary preview (/workspace/raw) is intentionally NOT here it
39
+ // The full /work view + extra-repo cloning. The binary preview (/workspace/raw) is intentionally NOT here, it
40
40
  // stays a plain Hono route serving raw bytes with a Content-Type header (oRPC's request/response shape doesn't
41
41
  // fit a streamed binary body). External MCP tools moved to the unified capabilities manifest (mcp kind).
42
42
  export const workspaceContract = {
@@ -44,13 +44,13 @@ export const workspaceContract = {
44
44
  // tree. Every read route below takes it, so a link into a conversation's own checkout browses as one tree
45
45
  // rather than as one openable file surrounded by the shared one.
46
46
  tree: oc.route({ method: "GET", path: "/workspace/tree" }).input(WorkspaceScopeSchema).output(WorkspaceTreeSchema),
47
- // Lazy-load one directory's children the tree returns ignored dirs (node_modules, .git, …) without children,
47
+ // Lazy-load one directory's children, the tree returns ignored dirs (node_modules, .git, …) without children,
48
48
  // and the client fetches them here on expand so a giant node_modules can't blow the tree walk's entry budget.
49
49
  children: oc.route({ method: "GET", path: "/workspace/children" }).input(WorkspaceChildrenQuerySchema).output(WorkspaceChildrenSchema),
50
50
  // One WINDOW of a file's text (plus the file's total size), never the whole file: the browser reads text
51
51
  // through here, and an unbounded read on an HTTP route is a way for any open log to stall the daemon.
52
52
  file: oc.route({ method: "GET", path: "/workspace/file" }).input(WorkspaceFileReadQuerySchema).output(WorkspaceFileSchema),
53
- /* The ticket a media element presents to GET /workspace/media (a plain Hono route, like /workspace/raw
53
+ /* The ticket a media element presents to GET /workspace/media (a plain Hono route, like /workspace/raw,
54
54
  * it answers a streamed byte RANGE, which oRPC has no shape for). Minting is here rather than beside it so
55
55
  * it rides the bearer middleware and the contract's route advertisement: a browser can tell whether the
56
56
  * sandbox in front of it can stream video at all, instead of learning it from a 404 mid-playback. */
@@ -58,7 +58,7 @@ export const workspaceContract = {
58
58
  .route({ method: "POST", path: "/workspace/media-ticket" })
59
59
  .input(WorkspaceMediaTicketQuerySchema)
60
60
  .output(WorkspaceMediaTicketSchema),
61
- // Which file a NAMED reference means the lookup behind every clickable path in the UI. A path an agent
61
+ // Which file a NAMED reference means, the lookup behind every clickable path in the UI. A path an agent
62
62
  // wrote in prose is often only a suffix of the real one, so it is matched against the workspace tree rather
63
63
  // than trusted as root-relative.
64
64
  resolve: oc.route({ method: "GET", path: "/workspace/resolve" }).input(WorkspaceResolveQuerySchema).output(WorkspaceResolveSchema),
@@ -66,16 +66,16 @@ export const workspaceContract = {
66
66
  // auto-mode fusion. (Implementation detail: the daemon backs this with a resident in-process iq engine.)
67
67
  search: oc.route({ method: "GET", path: "/workspace/search" }).input(WorkspaceSearchQuerySchema).output(WorkspaceSearchResultSchema),
68
68
  // One repository's health in numbers: churn × complexity per file, index totals, and the import graph's
69
- // top modules the `hotspots` and `map` rankings the CLI prints, shaped for a panel. Repo-scoped, because
69
+ // top modules, the `hotspots` and `map` rankings the CLI prints, shaped for a panel. Repo-scoped, because
70
70
  // "the codebase" is a repo, not the whole /work drop.
71
71
  health: oc.route({ method: "GET", path: "/workspace/health" }).input(WorkspaceHealthQuerySchema).output(WorkspaceHealthSchema),
72
72
  // Deterministic, no-LLM classification of the dropped workspace into coarse buckets (repositories / documents
73
73
  // / media / archives / other). Read-only proposal: the browser renders it and applies accepted moves via the
74
- // existing /workspace/move route this route never touches the tree.
74
+ // existing /workspace/move route, this route never touches the tree.
75
75
  classify: oc.route({ method: "GET", path: "/workspace/classify" }).output(WorkspaceClassificationSchema),
76
76
  // Direct file management the browser drives against the /work tree (byte writes go through POST
77
77
  // /workspace/upload). oRPC's OpenAPI codec reads non-GET input from the JSON body, so delete sends {path}
78
- // in the body too (not the query) same as the POST routes.
78
+ // in the body too (not the query), same as the POST routes.
79
79
  mkdir: oc.route({ method: "POST", path: "/workspace/dir" }).input(WorkspaceDirSchema).output(OkSchema),
80
80
  delete: oc.route({ method: "DELETE", path: "/workspace/entry" }).input(WorkspaceFileQuerySchema).output(OkSchema),
81
81
  move: oc.route({ method: "POST", path: "/workspace/move" }).input(WorkspaceMoveSchema).output(OkSchema),
@@ -83,7 +83,7 @@ export const workspaceContract = {
83
83
  // Dependency readiness for every project under /work, and the install that fixes it. An imported project
84
84
  // arrives without node_modules/.venv (the drop omits them), so "the files landed" is not "this works":
85
85
  // until setup says ready, its type-checks and tests can only lie. The install runs as a one-shot tmux panel
86
- // like a dev server or add-app attachable, survives a reload, output kept in the terminal logs.
86
+ // like a dev server or add-app, attachable, survives a reload, output kept in the terminal logs.
87
87
  setup: oc.route({ method: "GET", path: "/workspace/setup" }).output(WorkspaceSetupSchema),
88
88
  install: oc.route({ method: "POST", path: "/workspace/setup/install" }).input(WorkspaceInstallSchema).output(WorkspaceInstallResultSchema),
89
89
  repos: oc.route({ method: "GET", path: "/workspace/repos" }).output(ReposListSchema),
@@ -91,27 +91,27 @@ export const workspaceContract = {
91
91
  // Force-fetch + guarded fast-forward every repo with a remote (mutates the tree ⇒ POST). The turn hook syncs
92
92
  // automatically each turn; this is the on-demand refresh (and how you re-sync a dirty/diverged repo after committing).
93
93
  sync: oc.route({ method: "POST", path: "/workspace/sync" }).output(WorkspaceSyncSchema),
94
- // The addable app types the configured source repo offers (its templates.json) drives the apps
94
+ // The addable app types the configured source repo offers (its templates.json), drives the apps
95
95
  // extension's Add-app picker.
96
96
  templates: oc.route({ method: "GET", path: "/workspace/templates" }).output(TemplatesListSchema),
97
97
  // Per-monorepo apps, driven by the web app's apps extension (owner-authed; {repo} is validated in the
98
98
  // handler): add one or more apps into an existing monorepo, list them with per-app preview URL + status,
99
99
  // and start/stop each app's preview dev server. `addApps` kicks off a one-shot tmux job (session
100
- // panel-<repo>--add_apps) that runs `intentic scaffold add-app` the attachable terminal is the progress/error
100
+ // panel-<repo>--add_apps) that runs `intentic scaffold add-app`, the attachable terminal is the progress/error
101
101
  // surface; the extension polls the session's `running` for completion. It returns immediately (an ack).
102
102
  addApps: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps" }).input(AddAppsSchema).output(OkSchema),
103
103
  appsList: oc.route({ method: "GET", path: "/workspace/repos/{repo}/apps" }).input(RepoAppsParamSchema).output(AppsListSchema),
104
104
  // The monorepo's workspace package dependency graph (pnpm-workspace.yaml globs + per-package package.json
105
- // workspace deps) drives the apps extension's Dependencies view.
105
+ // workspace deps), drives the apps extension's Dependencies view.
106
106
  packageGraph: oc.route({ method: "GET", path: "/workspace/repos/{repo}/graph" }).input(RepoAppsParamSchema).output(WorkspaceGraphSchema),
107
- // Every repo's modules (the dirs owning a named package.json) what the review panels group changed files
107
+ // Every repo's modules (the dirs owning a named package.json), what the review panels group changed files
108
108
  // under when the reader has asked for modules instead of paths. Whole-workspace rather than per-repo: a
109
109
  // review list spans repos, and one request per repo group would be a fan-out the panel pays on every open.
110
110
  modules: oc.route({ method: "GET", path: "/workspace/modules" }).output(WorkspaceModulesSchema),
111
111
  startApp: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps/{app}/start" }).input(AppParamSchema).output(OkSchema),
112
112
  stopApp: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps/{app}/stop" }).input(AppParamSchema).output(OkSchema),
113
113
  // Run vitest for the given repo-relative project dirs in a one-shot tmux panel session
114
- // (panel-<repo>--<session>) drives the apps extension's per-app / per-package / library Run-tests actions.
114
+ // (panel-<repo>--<session>), drives the apps extension's per-app / per-package / library Run-tests actions.
115
115
  // Mirrors addApps: it returns an ack; the attachable terminal is the result surface.
116
116
  runTests: oc.route({ method: "POST", path: "/workspace/repos/{repo}/tests" }).input(RunTestsSchema).output(OkSchema),
117
117
  };
@@ -1,37 +1,37 @@
1
1
  /* THE NAME A NEW CONVERSATION IS BORN WITH.
2
2
  *
3
- * A conversation's id is not an internal key it is the most PUBLIC string this app produces. The same value
3
+ * A conversation's id is not an internal key, it is the most PUBLIC string this app produces. The same value
4
4
  * becomes the git branch (`agent/<id>`), the worktree directory on disk, the id in the page's address, and the
5
5
  * name printed on every board card (SessionChip). So it is read far more often than it is dereferenced, and by
6
6
  * eyes rather than by code: in `git branch`, in a terminal `cd`, on a card in a lane of twenty.
7
7
  *
8
8
  * It used to be `crypto.randomUUID()`, and a UUID fails every one of those readings. Forty-two characters of
9
- * hex that no one can say, remember, or tell apart from the card next to it two agents differ in the fourth
9
+ * hex that no one can say, remember, or tell apart from the card next to it, two agents differ in the fourth
10
10
  * character and look identical at a glance, which is exactly when the board is being scanned rather than read.
11
11
  * It was also the widest line on a card that has a title to show. The app already knew this everywhere else:
12
12
  * the tmux session for the very same turn is called `agent-32b6cb04` (session-names.ts), eight characters,
13
13
  * because eight is what a human uses.
14
14
  *
15
- * So a new conversation gets a name instead: `swift-otter-k9m2`. The pair is the part a person actually uses
15
+ * So a new conversation gets a name instead: `swift-otter-k9m2`. The pair is the part a person actually uses,
16
16
  * sayable over a call, distinguishable at a glance, and memorable for the hour anyone cares about it. The
17
17
  * four-character tail is the part that makes it an id: the pairs alone would start colliding within a few
18
18
  * hundred conversations (a workspace passes that in a month), and a branch name that collides is a worktree
19
19
  * that refuses to be created. Random rather than a timestamp because a timestamp in base36 is eight characters
20
20
  * of its own and would put the length straight back.
21
21
  *
22
- * The shape satisfies ConversationIdSchema (`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`) by construction every word
22
+ * The shape satisfies ConversationIdSchema (`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`) by construction, every word
23
23
  * here is lowercase a–z, the tail is lowercase base36, and the separator is the one the regex allows. That
24
24
  * guard is the injection guard for the branch name and the path, so it is the one thing about this module that
25
25
  * is not a matter of taste; the test beside it holds the whole generated space to it.
26
26
  *
27
27
  * Conversations the SANDBOX starts keep their own minters and their own prefixes (an automation's fire is
28
- * `a-<automation>-<time>`, a CI fix is minted from the run) those names say WHERE the conversation came
28
+ * `a-<automation>-<time>`, a CI fix is minted from the run), those names say WHERE the conversation came
29
29
  * from, which beats a random pair for a card the user did not ask for. This is the default for the ones a
30
30
  * person opens.
31
31
  */
32
32
 
33
33
  /* The two halves of the name. Kept short (nothing over seven letters) because the whole point is a string that
34
- * fits on a card, and visually distinct from one another no near-rhymes and no two words sharing a first
34
+ * fits on a card, and visually distinct from one another, no near-rhymes and no two words sharing a first
35
35
  * syllable, since a name is only useful here if it can be told apart from its neighbour in the lane. */
36
36
  const ADJECTIVES = [
37
37
  "amber",
@@ -161,11 +161,11 @@ const randomBelow = (upperBound: number): number => {
161
161
  }
162
162
  };
163
163
 
164
- // Uniform over the array, drawn from the platform CSPRNG `Math.random()` is seeded per process, and two
164
+ // Uniform over the array, drawn from the platform CSPRNG, `Math.random()` is seeded per process, and two
165
165
  // browser tabs opened in the same instant are exactly the case this must not produce the same name for.
166
166
  const pick = <T>(values: readonly T[]): T => values[randomBelow(values.length)]!;
167
167
 
168
- // Lowercase base36, one character per draw 0-9a-z, all of which the id guard accepts.
168
+ // Lowercase base36, one character per draw: 0-9a-z, all of which the id guard accepts.
169
169
  const tail = (): string => Array.from({ length: TAIL_LENGTH }, () => randomBelow(36).toString(36)).join("");
170
170
 
171
171
  /* A fresh conversation id: `<adjective>-<noun>-<tail>`, e.g. `swift-otter-k9m2`. Sixteen characters or so
@@ -3,8 +3,8 @@ import { RESUME_NOTES, resumeDisclosure, withResumeNote, withoutResumeNote } fro
3
3
 
4
4
  /* The resume note is a round trip across the wire: the daemon wraps a prompt to tell the model what interrupted
5
5
  * it, and the client unwraps the SAME prompt off an attach head to tell whether it already has that bubble. A
6
- * mismatch between the two halves fails silently and cosmetically a paragraph of machine prose rendered as
7
- * something the user typed which is exactly the kind of drift that stays broken. */
6
+ * mismatch between the two halves fails silently and cosmetically: a paragraph of machine prose rendered as
7
+ * something the user typed, which is exactly the kind of drift that stays broken. */
8
8
  test("a resume note round-trips back to the user's own words", () => {
9
9
  for (const note of Object.values(RESUME_NOTES)) {
10
10
  expect(withoutResumeNote(withResumeNote("ship the parser", note))).toBe("ship the parser");
@@ -39,7 +39,7 @@ test("a re-run discloses as a notice, and the answered case as a note on the mes
39
39
  const disclosure = resumeDisclosure(withResumeNote("ship the parser", RESUME_NOTES[reason]));
40
40
  expect(disclosure?.kind).toBe("notice");
41
41
  }
42
- // The answer is new words the user really did type, so nothing is dropped the interruption rides them.
42
+ // The answer is new words the user really did type, so nothing is dropped: the interruption rides them.
43
43
  const answered = resumeDisclosure(withResumeNote("option two", RESUME_NOTES.answered));
44
44
  expect(answered).toEqual({ kind: "note", note: { title: expect.any(String), text: RESUME_NOTES.answered } });
45
45
  });