@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
@@ -11,7 +11,7 @@ test("an empty field scopes nothing", () => {
11
11
  test("a bare name is a file as well as a folder, at any depth", () => {
12
12
  expect(includeGlobs(`package.json`).globs).toEqual([`**/package.json`, `**/package.json/**`]);
13
13
  expect(includeGlobs(`docs`).globs).toEqual([`**/docs`, `**/docs/**`]);
14
- // A trailing slash is noise the folder form is generated either way.
14
+ // A trailing slash is noise: the folder form is generated either way.
15
15
  expect(includeGlobs(`docs/`).globs).toEqual([`**/docs`, `**/docs/**`]);
16
16
  });
17
17
 
@@ -41,6 +41,6 @@ test("a leading ! excludes instead", () => {
41
41
  globs: [`**/src`, `**/src/**`],
42
42
  notGlobs: [`**/*.test.ts`, `**/*.test.ts/**`],
43
43
  });
44
- // A lone "!" excludes nothing expanded, it would have matched everything.
44
+ // A lone "!" excludes nothing: expanded, it would have matched everything.
45
45
  expect(includeGlobs(`!`)).toEqual({ globs: [], notGlobs: [] });
46
46
  });
@@ -1,17 +1,17 @@
1
- /* What the search box's second field MEANS VSCode's "files to include" grammar, read the way VSCode reads it
1
+ /* What the search box's second field MEANS. VSCode's "files to include" grammar, read the way VSCode reads it
2
2
  * (its queryBuilder's parseSearchPaths + expandGlobalGlob), and answered as the two path-glob lists the search
3
3
  * engine takes.
4
4
  *
5
- * The rules that make it feel like the editor's field, each one load-bearing:
5
+ * The rules that make it feel like the editor's field, each one required:
6
6
  *
7
- * `package.json` a bare name is a FILE as well as a folder every segment expands to BOTH `**\/p` and
7
+ * `package.json` a bare name is a FILE as well as a folder, every segment expands to BOTH `**\/p` and
8
8
  * `**\/p/**`. Reading it as a folder alone is what made a file name find nothing.
9
- * `src/db` a path is still matched at any depth, not anchored `**\/src/db`. VSCode only anchors
9
+ * `src/db` a path is still matched at any depth, not anchored, `**\/src/db`. VSCode only anchors
10
10
  * `./src/db` when the segment starts with `./` (or `/`), which is how you say "the one at the root".
11
11
  * `.ts` a leading dot is shorthand for the extension: it becomes `*.ts`.
12
12
  * `docs/` a trailing slash is noise; the folder form is generated either way.
13
13
  * `*.{ts,vue}` commas separate patterns EXCEPT inside `{…}` or `[…]`, which are one pattern's own syntax.
14
- * `!**\/*.spec.ts` a leading `!` excludes. This is ours, not VSCode's the editor spends a second box on
14
+ * `!**\/*.spec.ts` a leading `!` excludes. This is ours, not VSCode's, the editor spends a second box on
15
15
  * exclusions and the explorer's sidebar has room for one field.
16
16
  *
17
17
  * It lives in the contract package because both ends run it: the daemon turns it into engine scope, and the
@@ -60,7 +60,7 @@ const expand = (segment: string): string[] => {
60
60
  };
61
61
 
62
62
  export interface IncludeGlobs {
63
- // Files the search is limited to empty means the whole workspace. OR'ed: any pattern matching admits.
63
+ // Files the search is limited to, empty means the whole workspace. OR'ed: any pattern matching admits.
64
64
  readonly globs: readonly string[];
65
65
  // Files kept out of it, whatever the includes said.
66
66
  readonly notGlobs: readonly string[];
@@ -2,14 +2,14 @@
2
2
  *
3
3
  * A turn that runs Bash gets a tmux session; a turn that browses gets a Chromium the daemon watches over CDP.
4
4
  * Both are named off the SDK session id by the same derivation, and both are listed together by
5
- * GET /system/terminals so the name is wire vocabulary, not an implementation detail of either half.
5
+ * GET /system/terminals, so the name is wire vocabulary, not an implementation detail of either half.
6
6
  *
7
7
  * It lives in the contract because three parties derive it and none of them may guess: the daemon NAMES the
8
8
  * session, the web app OPENS it, and an extension that wants to point the user at a turn's live browser has only
9
9
  * the fleet roster's `sessionId` to work from. A second copy of this string math anywhere is a drift waiting to
10
- * happen the copy would keep resolving to a plausible name that nothing has ever listed.
10
+ * happen, the copy would keep resolving to a plausible name that nothing has ever listed.
11
11
  *
12
- * Reached as `@intentic/sandbox-contract/session-names`, NOT through the barrel the same shape tunnel-ids has,
12
+ * Reached as `@intentic/sandbox-contract/session-names`, NOT through the barrel, the same shape tunnel-ids has,
13
13
  * and for the same reason: this module has no dependencies, while the barrel pulls the whole contract (and
14
14
  * @intentic/extension-api behind it). One subpath keeps a daemon unit test that only wants a session name from
15
15
  * having to resolve the entire wire surface.
@@ -21,7 +21,7 @@ export const JOB_SESSION_PREFIX = "job-";
21
21
  export const BROWSER_SESSION_PREFIX = "browser-";
22
22
 
23
23
  /* Eight characters of the SDK session UUID, sanitized to the session-name charset. Eight is what groups a whole
24
- * turn's work including its subagents' under one name, and it clears the name guard
24
+ * turn's work, including its subagents', under one name, and it clears the name guard
25
25
  * (`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`) without further escaping. Undefined when the id sanitizes to empty, which
26
26
  * is never a valid session name. */
27
27
  export const sessionSuffix = (sessionId: string): string | undefined => {
@@ -29,7 +29,7 @@ export const sessionSuffix = (sessionId: string): string | undefined => {
29
29
  return id === "" ? undefined : id;
30
30
  };
31
31
 
32
- // The tmux session one SDK session's Bash commands run in the same derivation the Bash hook routes commands
32
+ // The tmux session one SDK session's Bash commands run in, the same derivation the Bash hook routes commands
33
33
  // through, so an emitted `terminal` frame and the live session can't drift.
34
34
  export const agentSessionName = (sessionId: string): string | undefined => {
35
35
  const suffix = sessionSuffix(sessionId);
@@ -17,7 +17,7 @@ describe("the readable half", () => {
17
17
  });
18
18
 
19
19
  // A title in a writing system this alphabet cannot carry leaves the random half standing alone, which is
20
- // still a perfectly good id and a better outcome than an address full of percent-encoding.
20
+ // still a perfectly good id, and a better outcome than an address full of percent-encoding.
21
21
  it("falls back to the random half when a title has no letters to give", () => {
22
22
  expect(shareStem("日本語のタイトル")).toBe("");
23
23
  expect(shareId("日本語のタイトル", "3f9c")).toBe("3f9c");
@@ -1,4 +1,4 @@
1
- /* WHERE A SHARED CONVERSATION LIVES INSIDE THE OUTBOX one layout, stated once, because three separate
1
+ /* WHERE A SHARED CONVERSATION LIVES INSIDE THE OUTBOX, one layout, stated once, because three separate
2
2
  * things have to agree on it and only one of them is code that runs at the same time as the others.
3
3
  *
4
4
  * public/
@@ -8,12 +8,12 @@
8
8
  * index.html the page, with its conversation baked in
9
9
  * files/ the pictures that conversation showed, copied out of the workspace
10
10
  *
11
- * The VIEWER's build has to know its own address before it is ever published (its asset URLs are absolute
12
- * `/conversations/_viewer/assets/…` so that one copy of the assets serves every share and a recipient's
11
+ * The VIEWER's build has to know its own address before it is ever published (its asset URLs are absolute,
12
+ * `/conversations/_viewer/assets/…`, so that one copy of the assets serves every share and a recipient's
13
13
  * browser caches them across links). The DAEMON has to write the tree. And the outbox's own rules have to
14
14
  * leave all of it alone: nothing here begins with a dot, which is the one shape that is never served.
15
15
  *
16
- * A directory per share rather than a bare `<id>.html`, so a share owns its pictures "stop sharing" is then
16
+ * A directory per share rather than a bare `<id>.html`, so a share owns its pictures, "stop sharing" is then
17
17
  * one directory removed, and cannot half-succeed by leaving a folder of someone's screenshots behind. */
18
18
 
19
19
  // Top-level folder inside `public/`. Reads as what it is in the address bar, which matters: this is a string
@@ -24,7 +24,7 @@ export const SHARE_DIR = "conversations";
24
24
  // machinery rather than as somebody's conversation.
25
25
  export const SHARE_VIEWER_DIR = "_viewer";
26
26
 
27
- // Where a share's pictures sit, relative to its own page and therefore also the prefix every rewritten
27
+ // Where a share's pictures sit, relative to its own page, and therefore also the prefix every rewritten
28
28
  // image path in the payload carries.
29
29
  export const SHARE_FILES_DIR = "files";
30
30
 
@@ -36,7 +36,7 @@ export const SHARE_VIEWER_BASE = `/${SHARE_DIR}/${SHARE_VIEWER_DIR}/`;
36
36
  *
37
37
  * Both halves earn their place. The stem is what makes a pasted link say what it points at ("…/conversations/
38
38
  * login-redirect-fix-3f9c…"), which is most of why anyone trusts clicking one. The tail is the security: the
39
- * outbox answers on an unguessable hostname AND requires the exact path (public-files.ts rule 4 there is no
39
+ * outbox answers on an unguessable hostname AND requires the exact path (public-files.ts rule 4, there is no
40
40
  * listing), so an address is protected by the sum of the two, and the half this module controls must not be
41
41
  * derivable from a title anyone could guess. */
42
42
  const STEM_MAX = 48;
@@ -62,7 +62,7 @@ export const shareId = (title: string, random: string): string => {
62
62
  return stem === "" ? random : `${stem}-${random}`;
63
63
  };
64
64
 
65
- // What a share's own id must look like before it is ever joined onto a path the same shape the daemon's
65
+ // What a share's own id must look like before it is ever joined onto a path, the same shape the daemon's
66
66
  // other id guards take (transcript-record.ts FILE_ID), and the reason a share id from the wire can be trusted
67
67
  // into a directory name.
68
68
  export const SHARE_ID = /^[a-z0-9][a-z0-9-]{0,95}$/;
package/src/sse.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Low-level SSE framing for the daemon's streamed routes (oRPC eventIterator over HTTP): frames separated by
2
- // a blank line, each carrying one `data: <JSON>` line. Protocol-only (no domain shapes) shared by every
2
+ // a blank line, each carrying one `data: <JSON>` line. Protocol-only (no domain shapes), shared by every
3
3
  // consumer of the wire: the web's chat/intentic streams and the ACP bridge's daemon client.
4
4
 
5
5
  // Yields each raw SSE frame (the text between blank-line separators) as it arrives, reassembling frames split
@@ -1,35 +1,35 @@
1
- /* WHETHER A PIECE OF DAEMON STATE TRAVELS the second thing every state manifest declares.
1
+ /* WHETHER A PIECE OF DAEMON STATE TRAVELS, the second thing every state manifest declares.
2
2
  *
3
3
  * A sandbox's environment is not one directory. `/work` holds the workspace and the daemon's manifests,
4
4
  * `/history` holds the real git dirs, the fleet registry and the ledgers, and the container carries the built
5
5
  * image and its env. "Export the environment" therefore means walking state that lives in two volumes and
6
- * deciding, per entry, which of four things it is and that decision has to be written down beside the entry
6
+ * deciding, per entry, which of four things it is, and that decision has to be written down beside the entry
7
7
  * itself, or the exporter becomes the hand-kept list that workspace-state.ts exists to argue against.
8
8
  *
9
9
  * So each manifest entry carries a class, and both the bundler and the restorer derive from it. Adding a store
10
10
  * without saying whether it travels is a failing coverage test, not a file that silently goes missing from
11
11
  * every bundle.
12
12
  *
13
- * The classes are exhaustive on purpose there is no "unset" because the failure mode of a default is
13
+ * The classes are exhaustive on purpose, there is no "unset", because the failure mode of a default is
14
14
  * exactly the one that matters: a credential store added later would inherit `carry` and ride out of the
15
15
  * sandbox in a file the owner thought was safe to email.
16
16
  */
17
17
  export type Portability =
18
18
  /* Ordinary state. Travels in every bundle. */
19
19
  | "carry"
20
- /* Holds a credential a provider token, a webhook secret, an ssh key. Travels ONLY when the owner opts
20
+ /* Holds a credential, a provider token, a webhook secret, an ssh key. Travels ONLY when the owner opts
21
21
  * in at export, and the bundle records that it did. Never the default. */
22
22
  | "secret"
23
23
  /* Binds this sandbox to its owner, its browsers or its host: who may drive it, the session-signing secret,
24
- * push subscriptions, enrollments, certificates. Never travels the target mints its own, and carrying
24
+ * push subscriptions, enrollments, certificates. Never travels, the target mints its own, and carrying
25
25
  * one would either hand the target's ownership to the source or leave two sandboxes claiming one identity. */
26
26
  | "identity"
27
27
  /* Regenerated by the target from state that does travel: composed files, caches, indexes, checkouts. Left
28
- * out for size, not for safety carrying it would only ship a stale copy of something the daemon rebuilds
28
+ * out for size, not for safety, carrying it would only ship a stale copy of something the daemon rebuilds
29
29
  * on its own. */
30
30
  | "derived";
31
31
 
32
- // One entry of a state manifest: the path it owns and what happens to it at export. `note` is user-facing
32
+ // One entry of a state manifest: the path it owns and what happens to it at export. `note` is user-facing,
33
33
  // the import report prints it beside a skipped entry, so it is written as an instruction to the owner ("log in
34
34
  // again"), not as a rationale for the next reader. Rationale belongs in a comment on the entry.
35
35
  export interface StateFile {
@@ -42,7 +42,7 @@ export interface StateFile {
42
42
  *
43
43
  * Manifests may deliberately nest when one subtree has a different lifecycle. First-match resolution would
44
44
  * classify the child by whichever entry happened to be listed first, so the specific entry has to win over the
45
- * general one by construction rather than by list order the same reason a .gitignore consults its deepest
45
+ * general one by construction rather than by list order, the same reason a .gitignore consults its deepest
46
46
  * matcher first.
47
47
  *
48
48
  * Undefined for a path no entry claims. Callers decide what that means: workspace content (including an
@@ -1,16 +1,16 @@
1
- // The /system/terminal WebSocket wire protocol JSON text frames both ways (xterm speaks strings), shared by
1
+ // The /system/terminal WebSocket wire protocol. JSON text frames both ways (xterm speaks strings), shared by
2
2
  // the daemon's terminal route and the browser's terminal session so the two can't drift. Plain types, not zod
3
3
  // schemas: `data` is the per-pty-chunk hot path between two mutually authenticated endpoints, and no oRPC
4
4
  // eventIterator validates these frames (which is why events.ts uses schemas).
5
5
 
6
6
  // `ping` is the client's 30s keepalive against tunnel idle-reaping; the server answers with `pong`, so a
7
- // healthy idle connection always sees a frame within the ping interval silence beyond it means half-open.
7
+ // healthy idle connection always sees a frame within the ping interval, silence beyond it means half-open.
8
8
  export type TerminalClientMessage =
9
9
  | { readonly type: "input"; readonly data: string }
10
10
  | { readonly type: "resize"; readonly cols: number; readonly rows: number }
11
11
  | { readonly type: "ping" };
12
12
 
13
13
  // `data` is raw pty output; `exit` fires when the tmux client ends (shell exited, or an attach-only session
14
- // doesn't exist) and is terminal the client never reconnects after it.
14
+ // doesn't exist) and is terminal, the client never reconnects after it.
15
15
  export type TerminalServerMessage =
16
16
  { readonly type: "data"; readonly data: string } | { readonly type: "exit"; readonly code: number } | { readonly type: "pong" };
package/src/title.test.ts CHANGED
@@ -7,13 +7,13 @@ import { deriveTitle } from "./title.js";
7
7
  * politeness, or stopped mid-syllable. */
8
8
 
9
9
  test("spends the budget on the ask rather than on the politeness in front of it", () => {
10
- // `Can you please fix the auth…` is what the naive cut produced three of its five words are ceremony.
10
+ // `Can you please fix the auth…` is what the naive cut produced: three of its five words are ceremony.
11
11
  expect(deriveTitle("Can you please fix the auth tests?")).toBe("Fix the auth tests?");
12
12
  expect(deriveTitle("Hey, can you please look at the flaky test")).toBe("Look at the flaky test");
13
13
  });
14
14
 
15
15
  test("keeps a greeting whole when peeling it off would leave a fragment", () => {
16
- // `Hi there` is not a conversation about `there`, and `So what?` is not one about `what?` a one-word
16
+ // `Hi there` is not a conversation about `there`, and `So what?` is not one about `what?`: a one-word
17
17
  // remnant means the opener was carrying the sentence, so the line stands as the user wrote it.
18
18
  expect(deriveTitle("Hi there")).toBe("Hi there");
19
19
  expect(deriveTitle("So what?")).toBe("So what?");
@@ -35,7 +35,7 @@ test("titles a pasted stack trace after the sentence the user wrote around it",
35
35
 
36
36
  test("skips a greeting line and takes the ask from the line below it", () => {
37
37
  // A line that unwinds to nothing was throat-clearing; the ask is further down. The naive rule stopped at
38
- // the greeting and named every such conversation `Hey, quick one —`.
38
+ // the greeting and named every such conversation `Hey, quick one -`.
39
39
  expect(deriveTitle("Hey, quick one —\n\nWhy does the tab title truncate mid-word?")).toBe("Why does the tab title truncate mid-word?");
40
40
  });
41
41
 
@@ -61,7 +61,7 @@ test("cuts on a word boundary instead of mid-syllable", () => {
61
61
 
62
62
  test("keeps a cut it cannot put on a word boundary inside the budget anyway", () => {
63
63
  /* The sibling of the case above: a long unbroken token straddling the cut leaves no space late enough to
64
- * back off to, so the length clamp alone ends the title and it used to end it one character OVER. That
64
+ * back off to, so the length clamp alone ends the title, and it used to end it one character OVER. That
65
65
  * character is not a cosmetic overflow, it is a 400 on the turn carrying the name, and it wedged the
66
66
  * conversation for good: the browser stores the derived title before it sends, so every retry re-sent the
67
67
  * same rejected one. */
@@ -72,7 +72,7 @@ test("keeps a cut it cannot put on a word boundary inside the budget anyway", ()
72
72
  // Asserted THROUGH the contract rather than against a repeated literal: the clamp and the cap drifting
73
73
  // apart is the entire bug, and a second copy of the number is how they drift.
74
74
  expect(AgentTurnSchema.safeParse({ prompt, title }).success).toBe(true);
75
- // Not a property of that one sentence any token wide enough to swallow the window does it.
75
+ // Not a property of that one sentence: any token wide enough to swallow the window does it.
76
76
  const wide = `Investigate ${"X".repeat(120)} please`;
77
77
  expect(AgentTurnSchema.safeParse({ prompt: wide, title: deriveTitle(wide) }).success).toBe(true);
78
78
  });
@@ -111,7 +111,7 @@ test("names a prompt that is nothing but a paste after what was pasted", () => {
111
111
  });
112
112
 
113
113
  test("never returns empty for a prompt that has any content at all", () => {
114
- // A greeting with no ask behind it, and a prompt with no letters in it both still have to name a tab.
114
+ // A greeting with no ask behind it, and a prompt with no letters in it: both still have to name a tab.
115
115
  expect(deriveTitle("Hey!")).toBe("Hey!");
116
116
  expect(deriveTitle("!!!")).toBe("!!!");
117
117
  });
@@ -121,7 +121,7 @@ test("reads past quoted material to the user's own words", () => {
121
121
  });
122
122
 
123
123
  test("skips past-work narration to the instruction behind it", () => {
124
- // `We have recently added…` is the scene, not the ask titling from it names every such conversation
124
+ // `We have recently added…` is the scene, not the ask: titling from it names every such conversation
125
125
  // after last week's work. The instruction further in is what the conversation is about.
126
126
  expect(deriveTitle("We have recently added iq map and iq deps commands. Now let's also add a health contract for the daemon.")).toBe(
127
127
  "Add a health contract for the daemon",
@@ -134,13 +134,13 @@ test("skips narration to an outright question", () => {
134
134
  });
135
135
 
136
136
  test("keeps narration when nothing behind it is unmistakably the ask", () => {
137
- // A hazy follow-up is not worth skipping for better a title about last week's work than one about `it`.
137
+ // A hazy follow-up is not worth skipping for: better a title about last week's work than one about `it`.
138
138
  expect(deriveTitle("We migrated the board to SSE last week. It feels slower since.")).toBe("We migrated the board to SSE last week");
139
139
  });
140
140
 
141
141
  test("keeps a declarative problem report even when advice follows it", () => {
142
142
  // Only NARRATION is skippable. A problem statement is the ask, and the imperative behind it is merely a
143
- // pointer `Check the broadcast path` names a step, not the conversation.
143
+ // pointer: `Check the broadcast path` names a step, not the conversation.
144
144
  expect(deriveTitle("The fleet board flickers when agents land. Check the broadcast path.")).toBe("The fleet board flickers when agents land");
145
145
  });
146
146
 
package/src/title.ts CHANGED
@@ -1,10 +1,10 @@
1
- /* HOW A CONVERSATION NAMES ITSELF from its opening prompt the title a chat tab, a fleet card and the agent
1
+ /* HOW A CONVERSATION NAMES ITSELF from its opening prompt, the title a chat tab, a fleet card and the agent
2
2
  * detail header wear from the first keystroke of a turn until something better arrives.
3
3
  *
4
4
  * The naive rule (collapse whitespace, cut at N) reads the prompt as if it were a headline, and opening
5
5
  * prompts are not headlines. They are pasted stack traces, links dropped in with two words of context,
6
6
  * politeness that eats half the budget before the ask begins, and multi-line asks whose first line is throat-
7
- * clearing. Cutting those at 40 characters yields `Error: ENOENT: no such file or directo…` a title that
7
+ * clearing. Cutting those at 40 characters yields `Error: ENOENT: no such file or directo…`, a title that
8
8
  * names the paste rather than the work, and that reads identically for every one of the day's five pastes.
9
9
  *
10
10
  * So the prompt is read as prose FIRST and cut second: quoted material and fenced code are not the ask, the
@@ -12,17 +12,17 @@
12
12
  * that still says something, cut on a sentence or a word rather than mid-syllable.
13
13
  *
14
14
  * Both sides derive: the browser names a conversation the instant it sends (conversation.ts), and the daemon
15
- * names a turn that arrived without one an automation, a Discord message, a webchat visitor (agents-
15
+ * names a turn that arrived without one, an automation, a Discord message, a webchat visitor (agents-
16
16
  * registry.ts). One rule, because two would let the same prompt open under two different names depending on
17
17
  * where it entered. Nothing here calls a model: the title has to exist before the first frame comes back.
18
18
  *
19
- * Which is exactly the ceiling on it. Cutting is not naming what comes out is the user's own sentence,
19
+ * Which is exactly the ceiling on it. Cutting is not naming, what comes out is the user's own sentence,
20
20
  * shortened, and a column of those is scannable only where the users' sentences happened to differ early. The
21
21
  * name a conversation ends up WEARING is written a second or two later by a model that reads the same prompt
22
22
  * and answers in the fleet board's own shape (the daemon's title-namer.ts). This is the title that holds the
23
23
  * tab until that arrives, and the one it keeps if nothing is connected to write a better one. */
24
24
 
25
- // The registry's title budget (agents-registry MAX_TITLE_LENGTH, the rename input's maxlength) the widest
25
+ // The registry's title budget (agents-registry MAX_TITLE_LENGTH, the rename input's maxlength), the widest
26
26
  // any surface stores. Every surface truncates in CSS to its own width, so the clamp here is a storage cap,
27
27
  // not a display rule: cutting shorter than the widest surface (a full-width fleet card) would throw away
28
28
  // signal that surface has room to show, and no narrow surface is hurt because its CSS cuts first anyway.
@@ -35,7 +35,7 @@ const MIN_SENTENCE = 12;
35
35
  const MIN_WORD_CUT = MAX_LENGTH * 0.6;
36
36
 
37
37
  // Markdown's two fence syntaxes, and the quote marker. Both open and close on the same shape, so a fence line
38
- // toggles rather than matching a pair an UNTERMINATED fence (a paste the user never closed) then swallows
38
+ // toggles rather than matching a pair, an UNTERMINATED fence (a paste the user never closed) then swallows
39
39
  // the rest of the prompt, which is what it visually does too.
40
40
  const FENCE = /^\s*(?:```|~~~)/;
41
41
  const QUOTE = /^\s*>/;
@@ -52,11 +52,11 @@ const OPENERS: readonly RegExp[] = [
52
52
  /^(?:quick\s+(?:one|question|q)|one\s+more\s+thing)\b[\s,.:;!—–-]*/i,
53
53
  ];
54
54
 
55
- // A sentence ends on terminal punctuation that is actually followed by a break which is what holds `v1.2`
55
+ // A sentence ends on terminal punctuation that is actually followed by a break, which is what holds `v1.2`
56
56
  // and `foo.ts` together while still ending `Why is it red?`.
57
57
  const SENTENCE_END = /[.!?](?=\s|$)/;
58
58
 
59
- /* A report of work already done `We have recently added…`, `I've just landed…`, `we were refactoring…`. The
59
+ /* A report of work already done, `We have recently added…`, `I've just landed…`, `we were refactoring…`. The
60
60
  * one first-sentence shape that is reliably CONTEXT rather than the ask: prompts that open on it set the scene
61
61
  * for an instruction further down, and titling from the scene names every such conversation after last week's
62
62
  * work instead of this one's.
@@ -69,7 +69,7 @@ const NARRATION =
69
69
  /^(?:(?:recently|previously|earlier|today|yesterday|lately)[\s,]+)?(?:we|i)(?:'ve|'d|'m|'re)?(?:\s+(?:have|had|am|are|was|were|been|just|recently|already|earlier|previously|also|finally|now|currently|still))*\s+(?!(?:need|feed|speed|shed|heed|breed|bleed|embed|proceed|exceed|succeed)\b)(?:\w+ed|\w{3,}ing|built|wrote|rewrote|made|found|saw|thought|began|got|kept|put|set|sent|split|ran|did|redid|went|came|gave|held|hit|cut|let|read|understood|broke|chose|became|brought|spent|meant|lost|forgot|rebuilt|told|taught|stood|drew|grew|knew|threw|took|left|felt|hid)\b/i;
70
70
 
71
71
  // The ask read as an instruction: a sentence leading with a bare verb of the kind coding asks lead with. A
72
- // curated list rather than grammar English hands out noun/verb ambiguity too freely for anything cleverer.
72
+ // curated list rather than grammar. English hands out noun/verb ambiguity too freely for anything cleverer.
73
73
  const IMPERATIVE =
74
74
  /^(?:add|fix|make|implement|create|build|write|rewrite|refactor|rename|remove|delete|drop|update|change|convert|migrate|move|extract|split|merge|rebase|revert|restore|wire|connect|integrate|support|handle|improve|clean|simplify|redesign|rework|rethink|investigate|analyze|analyse|debug|find|figure|check|verify|test|run|try|document|describe|explain|propose|design|draft|prepare|stop|prevent|ensure|allow|enable|disable|introduce|replace|swap|optimize|optimise|reduce|bump|upgrade|deploy|ship|release|adjust|tweak|polish|finish|complete|continue|extend|unify|dedupe|deduplicate|cache|persist|expose|hide|show|render|port|automate|wrap|inline|audit|review|profile|measure|instrument|validate|parse|generate|turn|look|think|come|consider|help|start|use|keep|avoid|teach|harden|tighten|localize|localise|translate)\b/i;
75
75
 
@@ -102,11 +102,11 @@ const proseLines = (prompt: string): string[] => {
102
102
 
103
103
  /* Peel the openers off a line, but only keep the peeling when something with shape is left underneath.
104
104
  *
105
- * Three outcomes, and the difference between them is the whole point. A remnant of SEVERAL words is the ask
105
+ * Three outcomes, and the difference between them is the whole point. A remnant of SEVERAL words is the ask,
106
106
  * `Can you please fix the auth tests?` was carrying `fix the auth tests?`. A remnant of NOTHING was pure
107
- * greeting (`Hey, quick one —`), and returning empty is how the caller learns to read the next line instead.
108
- * A remnant of ONE word means the opener was load-bearing `Hi there` is not a conversation about `there`,
109
- * and `So what?` is not one about `what?` so the line stands as written. */
107
+ * greeting (`Hey, quick one -`), and returning empty is how the caller learns to read the next line instead.
108
+ * A remnant of ONE word means the opener was meaningful. `Hi there` is not a conversation about `there`,
109
+ * and `So what?` is not one about `what?`, so the line stands as written. */
110
110
  const withoutOpener = (line: string): string => {
111
111
  let text = line;
112
112
  for (let pass = 0; pass < OPENERS.length; pass++) {
@@ -141,7 +141,7 @@ const shortUrl = (token: string): string => {
141
141
  return /^\d+$/.test(last) && previous !== undefined ? `${previous}/${last}` : last;
142
142
  };
143
143
 
144
- // A path is worth its basename once it is deep enough that the lead is scaffolding `src/foo.ts` already
144
+ // A path is worth its basename once it is deep enough that the lead is scaffolding, `src/foo.ts` already
145
145
  // reads as a place, `_editor/web/src/composables/chat/conversation.ts` reads as a wall.
146
146
  const shortPath = (token: string): string => {
147
147
  const segments = token.replace(/^@/, "").split("/");
@@ -149,7 +149,7 @@ const shortPath = (token: string): string => {
149
149
  };
150
150
 
151
151
  // Trailing punctuation belongs to the sentence, not to the reference inside it, so it is set aside across the
152
- // shortening and put back `see _editor/web/src/foo.ts,` collapses to `see foo.ts,`.
152
+ // shortening and put back, `see _editor/web/src/foo.ts,` collapses to `see foo.ts,`.
153
153
  const shortReference = (token: string): string => {
154
154
  const match = /^(.*?)([\s,.;:!?)\]]*)$/s.exec(token);
155
155
  const core = match?.[1] ?? token;
@@ -175,7 +175,7 @@ const firstSentence = (text: string): string => {
175
175
  return terminator === "." ? text.slice(0, end) : text.slice(0, end + 1);
176
176
  };
177
177
 
178
- // Every sentence in turn, terminators handled as in firstSentence the walk the ask-scan below reads. No
178
+ // Every sentence in turn, terminators handled as in firstSentence, the walk the ask-scan below reads. No
179
179
  // abbreviation guard here: a fragment split off at an `e.g.` never leads with an imperative, so it simply
180
180
  // loses the scan rather than corrupting it.
181
181
  const sentencesOf = (text: string): string[] => {
@@ -194,11 +194,11 @@ const sentencesOf = (text: string): string[] => {
194
194
  };
195
195
 
196
196
  /* THE ELLIPSIS IS PART OF THE BUDGET, not an addition to it. MAX_LENGTH is a STORAGE cap the wire contract
197
- * enforces (AgentTurnSchema.title), so a title one character over it is not a wide tab it is a 400 on the
197
+ * enforces (AgentTurnSchema.title), so a title one character over it is not a wide tab, it is a 400 on the
198
198
  * turn that carried it. Cutting to MAX_LENGTH and appending afterwards was exactly that off-by-one, and it
199
199
  * hid for as long as it did because the word-boundary backoff almost always absorbed it: an ordinary sentence
200
200
  * has a space late in the window, so `kept` came back short and the ellipsis fit. What has no such space is a
201
- * long unbroken token straddling the cut a SCREAMING_SNAKE env var, a hash, a base64 blob and there the
201
+ * long unbroken token straddling the cut, a SCREAMING_SNAKE env var, a hash, a base64 blob, and there the
202
202
  * fallback kept the full-width slice and made it one character too long. The conversation then wedged: the
203
203
  * browser writes the derived title into its state before it sends, so every later turn re-sent the same
204
204
  * rejected name. */
@@ -225,7 +225,7 @@ const capitalized = (text: string): string => {
225
225
  /* Split a plan's markdown into its leading heading and the remaining body.
226
226
  *
227
227
  * The heading is the one place in a turn where an agent writes a name for the WHOLE job rather than for a step
228
- * a task checklist describes `Read the failing test`, `Fix the assertion`, `Run the suite`, none of which is
228
+ *, a task checklist describes `Read the failing test`, `Fix the assertion`, `Run the suite`, none of which is
229
229
  * what the conversation is about, whereas a plan opens `## Fix the flaky auth tests`. That makes it the only
230
230
  * authored line in the stream worth promoting to a title, which is why this lives here rather than next to the
231
231
  * plan card that also renders it: the browser titles its own tabs from it and the daemon titles fleet cards. */
@@ -248,7 +248,7 @@ export const deriveTitle = (prompt: string): string => {
248
248
  const lines = prose.map(withoutOpener).filter((line) => /\p{L}/u.test(line));
249
249
  const primary = lines[0];
250
250
  if (primary !== undefined) {
251
- /* A first sentence that reports work already done is the scene, not the ask `We have recently added
251
+ /* A first sentence that reports work already done is the scene, not the ask, `We have recently added
252
252
  * iq map. Now let's expose health.` is a conversation about exposing health. Skipped only for an
253
253
  * unmistakable ask further in (an instruction or an outright question); anything hazier and the
254
254
  * narration stands, exactly as every non-narrative opening always does. */
@@ -261,7 +261,7 @@ export const deriveTitle = (prompt: string): string => {
261
261
  : undefined;
262
262
  return capitalized(clamped(firstSentence(collapseReferences(ask ?? primary))));
263
263
  }
264
- // Everything the user sent was code, a quote, or a greeting title it as what it is rather than blank.
264
+ // Everything the user sent was code, a quote, or a greeting, title it as what it is rather than blank.
265
265
  const fallback = prose[0] ?? collapse(prompt.replaceAll(/^\s*(?:```|~~~).*$/gm, ""));
266
266
  return clamped(collapseReferences(fallback === "" ? collapse(prompt) : fallback));
267
267
  };
@@ -34,11 +34,11 @@ test("port slots are a fixed-size pool of DNS-safe labels, stable per token", ()
34
34
  expect(portSlotsFromToken(TOKEN)).toEqual(slots);
35
35
  });
36
36
 
37
- /* The whole point of the salt. The sandbox id is public it is the leading label of the sandbox's own URL and
38
- * of every preview link its owner has shared so anything derived from the id ALONE is derivable by whoever
37
+ /* The whole point of the salt. The sandbox id is public: it is the leading label of the sandbox's own URL and
38
+ * of every preview link its owner has shared, so anything derived from the id ALONE is derivable by whoever
39
39
  * holds one of those links. Slots must not be: knowing a sandbox's id must not tell you where its forwarded
40
40
  * ports live. */
41
- test("slots are not derivable from the sandbox id only from the token behind it", () => {
41
+ test("slots are not derivable from the sandbox id: only from the token behind it", () => {
42
42
  expect(portSlotsFromToken(OTHER)).not.toEqual(portSlotsFromToken(TOKEN));
43
43
  // No slot leaks the id it will be paired with in `port-<slot>-<sandboxId>`.
44
44
  const id = sandboxIdFromToken(TOKEN);
package/src/tunnel-ids.ts CHANGED
@@ -22,33 +22,33 @@ export const hostSshIdFromToken = (connectToken: string, hostName: string): stri
22
22
  * and it is the hard cap on preview DNS records a sandbox can ever cost the shared intentic zone. */
23
23
  export const PORT_SLOT_COUNT = 8;
24
24
 
25
- /* THE PORT-FORWARD SLOT LABELS the `port-<slot>` half of `port-<slot>-<sandboxId>.<zone>`.
25
+ /* THE PORT-FORWARD SLOT LABELS, the `port-<slot>` half of `port-<slot>-<sandboxId>.<zone>`.
26
26
  *
27
27
  * These were the letters a…h, and that was the hole: a forwarded port's hostname was then a pure function of the
28
- * sandbox id, and the sandbox id is not a secret it is the leading label of the URL the owner uses daily and
28
+ * sandbox id, and the sandbox id is not a secret, it is the leading label of the URL the owner uses daily and
29
29
  * of every preview link they have ever shared. So anyone who had seen ONE preview link could poll eight fixed
30
30
  * names forever and catch whatever the owner forwarded, at any point in the future. The Ports view says a
31
31
  * forwarded port is public, and it is; what it could not say was that "public" meant eight guessable URLs.
32
32
  *
33
33
  * Salting with the connect token fixes that without costing anything the letters bought. Still exactly eight
34
- * records (the reason slots exist at all the intentic-provided zone mints per label, and dev servers churn
34
+ * records (the reason slots exist at all, the intentic-provided zone mints per label, and dev servers churn
35
35
  * ephemeral ports far faster than DNS should), still stable across restarts so a slot's record stays warm, and
36
36
  * still derivable with no coordination by every party that already holds the token: the daemon that forwards,
37
37
  * and the platform that mints the DNS. A party without the token has no business predicting these names.
38
38
  *
39
- * The browser is deliberately NOT one of those parties it never derives a port hostname, it reads `previewUrl`
40
- * off the daemon's response which is why this can live here, in the node-only half of the contract, next to
39
+ * The browser is deliberately NOT one of those parties, it never derives a port hostname, it reads `previewUrl`
40
+ * off the daemon's response, which is why this can live here, in the node-only half of the contract, next to
41
41
  * the digest it shares with sandboxIdFromToken. */
42
42
  export const portSlotsFromToken = (connectToken: string): readonly string[] =>
43
43
  Array.from({ length: PORT_SLOT_COUNT }, (_, index) => sha256Hex(`${connectToken}:port:${index}`).slice(0, 12));
44
44
 
45
- /* THE OUTBOX SLOT the `public-<slot>` half of `public-<slot>-<sandboxId>.<zone>`, where the daemon serves the
45
+ /* THE OUTBOX SLOT, the `public-<slot>` half of `public-<slot>-<sandboxId>.<zone>`, where the daemon serves the
46
46
  * workspace's `public/` directory (PUBLIC_DIR in @intentic/workspace-ignore).
47
47
  *
48
48
  * Salted for the reason above, and it matters more here than it does for ports: a forwarded port is a live
49
49
  * server the owner started minutes ago, whereas a published file sits there. One record per sandbox, stable
50
50
  * across restarts so a link stays good for as long as the file does, and derivable only by parties holding the
51
- * token the daemon that serves and the platform that mints. Not the browser: it reads the URL off the /public
51
+ * token, the daemon that serves and the platform that mints. Not the browser: it reads the URL off the /public
52
52
  * response, exactly as it reads previewUrl off /ports.
53
53
  *
54
54
  * The unguessable hostname is also what carries the security story, because the files under it have no auth in
@@ -25,15 +25,15 @@ test("isBehind stays quiet whenever it cannot be sure", () => {
25
25
  });
26
26
 
27
27
  /* A version with a segment that will not parse is read by its numeric prefix, and the failure is one-directional:
28
- * it can withhold a nag, never invent one. Both halves are pinned because only the second is a safety property
28
+ * it can withhold a nag, never invent one. Both halves are pinned because only the second is a safety property:
29
29
  * the first is just the prefix doing its job. */
30
30
  test("a version that isn't dotted-numeric can only ever withhold the nudge", () => {
31
31
  expect(isBehind("1.2.0-rc.1", "1.183.0")).toBe(true); // the prefix already decides it: 2 is behind 183
32
32
  expect(isBehind("1.2.0-rc.1", "1.2.0")).toBe(false); // the unparseable segment is where they differ — silence
33
33
  });
34
34
 
35
- // The version every agent shipped before the release stamp existed. It is genuinely behind it predates every
36
- // release that has one and it must read that way, because those are the installs this whole signal is for.
35
+ // The version every agent shipped before the release stamp existed. It is genuinely behind: it predates every
36
+ // release that has one, and it must read that way, because those are the installs this whole signal is for.
37
37
  test("isBehind flags the hand-written version agents used to carry", () => {
38
38
  expect(isBehind("0.1.0", "1.183.0")).toBe(true);
39
39
  });
package/src/versions.ts CHANGED
@@ -1,9 +1,9 @@
1
- /* COMPARING THE VERSIONS THIS SYSTEM STAMPS ON WHAT IT SHIPS the daemon, the sandbox image, and the two agents
1
+ /* COMPARING THE VERSIONS THIS SYSTEM STAMPS ON WHAT IT SHIPS, the daemon, the sandbox image, and the two agents
2
2
  * that run on a user's own computer. One release stamps all of them to the SAME version, so "is this one behind
3
3
  * that one" is one question with one answer, and it lives here because both ends ask it: the daemon compares its
4
4
  * own build against the latest published release, and the browser compares a computer's agent against the same.
5
5
  *
6
- * Shared rather than copied because the two copies would not disagree until the day it mattered 1.9.0 against
6
+ * Shared rather than copied because the two copies would not disagree until the day it mattered: 1.9.0 against
7
7
  * 1.10.0 is where a hand-rolled comparator goes wrong, and it goes wrong by reporting "up to date". */
8
8
 
9
9
  // Release versions are plain dotted numerics (semantic-release picks them), so there is no semver dependency to
@@ -33,14 +33,14 @@ export const DEV_VERSION = `0.0.0`;
33
33
  /* WHETHER TO TELL SOMEBODY THEIR BUILD IS OLD. Deliberately false in every uncertain case, and each one is a
34
34
  * different kind of not-knowing:
35
35
  *
36
- * • no installed version the thing does not report one, so there is nothing to be behind.
37
- * • no latest version this sandbox has not reached the registry (or is a dev build and never will).
38
- * • installed is the dev sentinel see above; a nag that cannot be satisfied is worse than silence.
36
+ * • no installed version, the thing does not report one, so there is nothing to be behind.
37
+ * • no latest version, this sandbox has not reached the registry (or is a dev build and never will).
38
+ * • installed is the dev sentinel, see above; a nag that cannot be satisfied is worse than silence.
39
39
  *
40
40
  * A version that is not dotted-numeric is compared by its numeric prefix, which is both useful and safe in the
41
41
  * only direction that matters: a segment that will not parse compares as neither greater nor less, so it stops
42
42
  * the comparison at "not newer". A malformed INSTALLED version can therefore only ever withhold a nag, never
43
- * invent one and `latest` comes from the registry, so it is well-formed by construction.
43
+ * invent one, and `latest` comes from the registry, so it is well-formed by construction.
44
44
  *
45
45
  * The asymmetry is the point. Saying "you are out of date" wrongly sends someone to reinstall a working agent;
46
46
  * saying nothing wrongly leaves them where they already were. */
@@ -3,7 +3,7 @@ import { type Workflow, WorkflowSchema, type WorkflowStep } from "./schemas.js";
3
3
  import { workflowFaults, workflowRunFaults } from "./workflow-faults.js";
4
4
 
5
5
  /* The GATE rules. The graph rules beside them are exercised by the scheduler's own integration tests, which
6
- * have a real run to check them against; these have none to check, which is the point every fault here is one
6
+ * have a real run to check them against; these have none to check, which is the point: every fault here is one
7
7
  * the workflow has to be refused for BEFORE a run, because the failure it prevents costs a full fan-out of
8
8
  * sessions and then reports nothing anybody can act on.
9
9
  */
@@ -78,7 +78,7 @@ test("a gate on a field the step does not declare is refused", () => {
78
78
  test("a gate on a list field is refused", () => {
79
79
  const steps = [judge({ output: { kind: "json", fields: [{ name: "release", type: "string[]", description: "the verdicts", required: true }] } })];
80
80
  const faults = workflowFaults(gated({ steps }));
81
- expect(faults).toEqual([`The gate reads "release", which is a list a release decision has to be one value.`]);
81
+ expect(faults).toEqual([`The gate reads "release", which is a list: a release decision has to be one value.`]);
82
82
  });
83
83
 
84
84
  // The expensive one to discover at run time: it passes every save, then blocks a release on the one commit
@@ -86,10 +86,10 @@ test("a gate on a list field is refused", () => {
86
86
  test("a gate on an optional field is refused", () => {
87
87
  const steps = [judge({ output: { kind: "json", fields: [{ name: "release", type: "string", description: "pass | fail", required: false }] } })];
88
88
  const faults = workflowFaults(gated({ steps }));
89
- expect(faults).toEqual([`The gate reads "release", which "Judge" declares optional it has to be required.`]);
89
+ expect(faults).toEqual([`The gate reads "release", which "Judge" declares optional, it has to be required.`]);
90
90
  });
91
91
 
92
- /* THE RUN-TIME RULE, kept apart from every rule above it because it is not about the graph and the graph is
92
+ /* THE RUN-TIME RULE, kept apart from every rule above it because it is not about the graph, and the graph is
93
93
  * what gets saved. A design whose steps take their goal and instruction from the request is the ordinary shape
94
94
  * and must save cleanly; it is only unrunnable on the particular run that brought no request.
95
95
  */
@@ -102,7 +102,7 @@ test("a design whose steps inherit is a perfectly good design", () => {
102
102
  });
103
103
 
104
104
  /* Refused at the door rather than discovered by the first step, because this is the one combination with
105
- * nothing to tell the model at all and by the time a step found out, the run has already opened a session per
105
+ * nothing to tell the model at all, and by the time a step found out, the run has already opened a session per
106
106
  * root and started paying for them.
107
107
  */
108
108
  test("running an inheriting design with no request is refused", () => {
@@ -120,7 +120,7 @@ test("a step that declares only one of the two still needs a request", () => {
120
120
  expect(workflowRunFaults(gated({ steps: [judge({ prompt: undefined })] }), undefined)).toHaveLength(1);
121
121
  });
122
122
 
123
- // A design that says everything itself is startable from anywhere, with no composer behind it which is what
123
+ // A design that says everything itself is startable from anywhere, with no composer behind it, which is what
124
124
  // keeps the gate's webhook and the workflows page working for the designs written that way.
125
125
  test("a design that declares everything runs with no request at all", () => {
126
126
  expect(workflowRunFaults(gated(), undefined)).toEqual([]);