@intentic/sandbox-contract 1.223.0 → 1.224.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 (86) hide show
  1. package/dist/chores/chores.js +1 -1
  2. package/dist/chores/chores.js.map +1 -1
  3. package/dist/chores/prompt.d.ts.map +1 -1
  4. package/dist/chores/prompt.js +3 -3
  5. package/dist/chores/prompt.js.map +1 -1
  6. package/dist/contracts/ports.contract.d.ts +11 -0
  7. package/dist/contracts/ports.contract.d.ts.map +1 -1
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/schemas.d.ts +22 -0
  11. package/dist/schemas.d.ts.map +1 -1
  12. package/dist/schemas.js +3 -0
  13. package/dist/schemas.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/agent-catalog.ts +78 -78
  16. package/src/agent-run-model.ts +8 -8
  17. package/src/capability-env.ts +3 -3
  18. package/src/capability-secrets.ts +5 -5
  19. package/src/chores/chores.ts +67 -67
  20. package/src/chores/digest.ts +3 -3
  21. package/src/chores/extension-update.ts +3 -3
  22. package/src/chores/fix-deps.ts +3 -3
  23. package/src/chores/probes.ts +16 -16
  24. package/src/chores/prompt.ts +9 -9
  25. package/src/chores/stack.ts +17 -17
  26. package/src/chores/verdict.ts +23 -23
  27. package/src/contract-lock.ts +7 -7
  28. package/src/contracts/activity.contract.ts +1 -1
  29. package/src/contracts/agent.contract.ts +2 -2
  30. package/src/contracts/agents.contract.ts +11 -11
  31. package/src/contracts/automations.contract.ts +5 -5
  32. package/src/contracts/capabilities.contract.ts +7 -7
  33. package/src/contracts/chores.contract.ts +3 -3
  34. package/src/contracts/ci.contract.ts +2 -2
  35. package/src/contracts/claude.contract.ts +3 -3
  36. package/src/contracts/drafts.contract.ts +1 -1
  37. package/src/contracts/endpoints.contract.ts +2 -2
  38. package/src/contracts/extensions.contract.ts +8 -8
  39. package/src/contracts/git.contract.ts +6 -6
  40. package/src/contracts/grok.contract.ts +4 -4
  41. package/src/contracts/host.contract.ts +5 -5
  42. package/src/contracts/intentic.contract.ts +2 -2
  43. package/src/contracts/logs.contract.ts +1 -1
  44. package/src/contracts/loops.contract.ts +8 -8
  45. package/src/contracts/personas.contract.ts +8 -8
  46. package/src/contracts/ports.contract.ts +1 -1
  47. package/src/contracts/prepush.contract.ts +2 -2
  48. package/src/contracts/providers.contract.ts +4 -4
  49. package/src/contracts/public.contract.ts +1 -1
  50. package/src/contracts/push.contract.ts +2 -2
  51. package/src/contracts/secrets.contract.ts +2 -2
  52. package/src/contracts/sessions.contract.ts +2 -2
  53. package/src/contracts/settings.contract.ts +3 -3
  54. package/src/contracts/share.contract.ts +1 -1
  55. package/src/contracts/skills.contract.ts +2 -2
  56. package/src/contracts/system.contract.ts +9 -9
  57. package/src/contracts/usage.contract.ts +1 -1
  58. package/src/contracts/vpn.contract.ts +5 -5
  59. package/src/contracts/workflows.contract.ts +12 -12
  60. package/src/contracts/workspace.contract.ts +13 -13
  61. package/src/conversation-ids.ts +7 -7
  62. package/src/events.ts +136 -136
  63. package/src/history-state.ts +12 -12
  64. package/src/host-protocol.ts +7 -7
  65. package/src/hostnames.ts +15 -15
  66. package/src/index.ts +8 -8
  67. package/src/listener-protocol.ts +12 -12
  68. package/src/model-order.ts +30 -30
  69. package/src/output-fields.ts +9 -9
  70. package/src/path-refs.ts +6 -6
  71. package/src/publish-drafts.ts +6 -6
  72. package/src/quick-model.ts +15 -15
  73. package/src/routes.ts +19 -19
  74. package/src/runtime-state.ts +22 -22
  75. package/src/schemas.ts +930 -918
  76. package/src/search-globs.ts +6 -6
  77. package/src/session-names.ts +5 -5
  78. package/src/share-paths.ts +7 -7
  79. package/src/sse.ts +1 -1
  80. package/src/state-portability.ts +8 -8
  81. package/src/terminal-protocol.ts +3 -3
  82. package/src/title.ts +20 -20
  83. package/src/tunnel-ids.ts +7 -7
  84. package/src/versions.ts +5 -5
  85. package/src/workflow-faults.ts +11 -11
  86. package/src/workspace-state.ts +119 -119
@@ -11,19 +11,19 @@ import {
11
11
  TAILWIND_PACKAGES,
12
12
  } from "./stack.js";
13
13
 
14
- /* THE PROBES the measurements that cost a subprocess, declared once so the daemon that runs them and the panel
14
+ /* THE PROBES, the measurements that cost a subprocess, declared once so the daemon that runs them and the panel
15
15
  * that explains them cannot disagree about what "outdated" meant.
16
16
  *
17
17
  * A spec is a shell command and a parser, deliberately in that order of trust: the command is whatever the tool's
18
18
  * own maintainers publish as its machine-readable output, and the parser is written to be DISAPPOINTED. Every one
19
19
  * of these tools has changed its JSON shape at least once, they are run against whatever version the repo pinned,
20
20
  * and a probe that throws on an unexpected field would take the whole maintenance surface down with it. So each
21
- * parser walks the structure defensively and returns `undefined` when it cannot recognise what it got which the
21
+ * parser walks the structure defensively and returns `undefined` when it cannot recognise what it got, which the
22
22
  * runner records as a failed probe with the output attached, rather than as a clean repository.
23
23
  *
24
24
  * TIERS ARE ABOUT COST, and the cost is what sets the cadence. Tier 1 reads metadata that already exists (a
25
25
  * lockfile, a registry's version list) and finishes in seconds, so the background runner refreshes it daily. Tier
26
- * 2 reads the whole tree knip type-checks it, jscpd tokenizes every file and can run for minutes on a large
26
+ * 2 reads the whole tree, knip type-checks it, jscpd tokenizes every file, and can run for minutes on a large
27
27
  * repo, so it refreshes weekly and says how long it took, because a reader deciding whether to force a refresh
28
28
  * deserves to know what they are asking for.
29
29
  *
@@ -48,7 +48,7 @@ export interface ProbeSpec {
48
48
  // Exit 0 ⇒ this repo can be measured. Runs in the repo's own directory, like the command.
49
49
  readonly available: string;
50
50
  /* What is MISSING when `available` says no, named here rather than derived from the title. The obvious
51
- * derivation "this repository has no security advisories to measure" states the one thing an unmeasured
51
+ * derivation, "this repository has no security advisories to measure", states the one thing an unmeasured
52
52
  * probe must never claim, that there are none, and it is the same conflation the block above exists to
53
53
  * prevent. Phrased as a bare clause ("no lockfile"), because the panel groups these under its own lead-in. */
54
54
  readonly unavailable: string;
@@ -97,7 +97,7 @@ const semverKind = (current: string, latest: string): OutdatedPackage["kind"] =>
97
97
  /* `pnpm outdated --json` prints a map of package name → { current, latest, dependencyType }. In a workspace the
98
98
  * recursive form merges every package's entries into the same map and adds `dependentPackages`, which is why this
99
99
  * reads the map rather than expecting a list: one shape covers both, and a field we don't use appearing is not a
100
- * parse failure. Entries missing `current` or `latest` are skipped that is how pnpm reports a package it could
100
+ * parse failure. Entries missing `current` or `latest` are skipped, that is how pnpm reports a package it could
101
101
  * not resolve against the registry, and it is not evidence of anything. */
102
102
  const parseOutdated = (stdout: string): ProbeFacts | undefined => {
103
103
  const root = asObject(stdout);
@@ -123,8 +123,8 @@ const parseOutdated = (stdout: string): ProbeFacts | undefined => {
123
123
  const SEVERITIES = new Set([`critical`, `high`, `moderate`, `low`, `info`]);
124
124
 
125
125
  /* `pnpm audit --json` prints `{ advisories: { <id>: {...} }, metadata: {...} }`. The metadata's counts are
126
- * deliberately ignored: they are a tally, and this surface needs the advisories themselves which package, and
127
- * whether a patched range exists because "there is a fix that is a version bump" and "there is no patch yet"
126
+ * deliberately ignored: they are a tally, and this surface needs the advisories themselves, which package, and
127
+ * whether a patched range exists, because "there is a fix that is a version bump" and "there is no patch yet"
128
128
  * lead to completely different turns, and a count cannot tell them apart.
129
129
  *
130
130
  * `dev` comes off the findings' own flag rather than being inferred. A build-time-only advisory is real but it is
@@ -135,7 +135,7 @@ const parseAudit = (stdout: string): ProbeFacts | undefined => {
135
135
  return undefined;
136
136
  }
137
137
  const raw = root[`advisories`];
138
- // No `advisories` key at all is pnpm's clean report an empty list, not an unrecognisable one.
138
+ // No `advisories` key at all is pnpm's clean report, an empty list, not an unrecognisable one.
139
139
  if (raw === undefined) {
140
140
  return { id: `audit`, advisories: [] };
141
141
  }
@@ -168,7 +168,7 @@ const parseAudit = (stdout: string): ProbeFacts | undefined => {
168
168
  return { id: `audit`, advisories };
169
169
  };
170
170
 
171
- /* knip's JSON reporter prints `{ issues: [...] }` one row per file that has findings, carrying a per-kind array
171
+ /* knip's JSON reporter prints `{ issues: [...] }`, one row per file that has findings, carrying a per-kind array
172
172
  * of what it found there. A wholly unreferenced file is a row whose own `files` array names it, which is why that
173
173
  * count is a sum like every other kind rather than a list of its own. Counts plus a sample of the file paths, not
174
174
  * the full list: the agent re-runs knip itself against the live tree (a list from a probe hours old would send it
@@ -233,16 +233,16 @@ const parseJscpd = (stdout: string): ProbeFacts | undefined => {
233
233
  * Everything it emits is a labelled, tab-separated line, and the first line is always the bare marker `UI`. That
234
234
  * marker is the whole reason this parser can tell "the sweep ran and this repository is clean" from "the sweep
235
235
  * never ran": every other line is optional, so without it an empty stdout and a spotless codebase are the same
236
- * string and reporting the second when it was the first is the one thing probes.ts exists to prevent. */
236
+ * string, and reporting the second when it was the first is the one thing probes.ts exists to prevent. */
237
237
  const UI_MARKER = `UI`;
238
- // Caps, applied after `sort` so truncation is alphabetical and therefore identical between runs an unsorted
238
+ // Caps, applied after `sort` so truncation is alphabetical and therefore identical between runs, an unsorted
239
239
  // truncation would mint a new digest on every sweep and badge forever. What is dropped is genuinely dropped: a
240
240
  // component past the cap cannot join a family, and the chore says so rather than implying it saw everything.
241
241
  const COMPONENT_LIMIT = 2000;
242
242
  const RULE_FILE_LIMIT = 500;
243
243
 
244
244
  /* THE `.` IS LOAD-BEARING, and leaving it off cost this probe every finding it will ever have. Given no path,
245
- * ripgrep searches the tree only when stdin is a TTY otherwise it reads STDIN, which is exactly how the runner
245
+ * ripgrep searches the tree only when stdin is a TTY, otherwise it reads STDIN, which is exactly how the runner
246
246
  * spawns a probe. The sweep therefore ran, exited 0, printed its marker and matched nothing, in every repository,
247
247
  * forever: the precise failure the marker line was introduced to make impossible, arriving through the one door it
248
248
  * does not cover. It reproduces from Node and not from an interactive shell, which is why it survived being read.
@@ -367,7 +367,7 @@ const parseBundle = (stdout: string): ProbeFacts | undefined => {
367
367
  };
368
368
 
369
369
  // Where the tier-2 tools leave their reports. Under /tmp because they are inputs to a parse that happens
370
- // immediately after, never something to keep the cached ProbeResult is the artefact that survives. The same
370
+ // immediately after, never something to keep, the cached ProbeResult is the artefact that survives. The same
371
371
  // path the scheduled form of this chore uses (chores.ts), so a workspace running both keeps one copy.
372
372
  const JSCPD_DIR = `/tmp/intentic-chore-jscpd`;
373
373
 
@@ -423,7 +423,7 @@ export const PROBES: readonly ProbeSpec[] = [
423
423
  timeoutMs: 20 * 60_000,
424
424
  available: `test -f package.json`,
425
425
  unavailable: `no package.json`,
426
- // `--threshold 100` so jscpd never fails the command on its own opinion of what is too much duplication
426
+ // `--threshold 100` so jscpd never fails the command on its own opinion of what is too much duplication,
427
427
  // that judgement is the chore's, made from the percentage, not the tool's exit code.
428
428
  command:
429
429
  `pnpm dlx jscpd --reporters json --output ${JSCPD_DIR} --min-lines 12 --threshold 100 . >/dev/null 2>&1; ` +
@@ -437,11 +437,11 @@ export const PROBES: readonly ProbeSpec[] = [
437
437
  /* Tier 1 despite reading the whole tree, and the placement is a judgement rather than an oversight. The
438
438
  * tier is about COST: this is a dozen ripgrep walks, seconds on a large monorepo, against knip
439
439
  * type-checking the tree and jscpd tokenizing every file for minutes. A weekly TTL would also make it the
440
- * wrong shape its findings move whenever someone writes a component, which is daily. */
440
+ * wrong shape, its findings move whenever someone writes a component, which is daily. */
441
441
  tier: 1,
442
442
  ttlMs: DAY_MS,
443
443
  timeoutMs: 5 * 60_000,
444
- // Any manifest in the repo declaring a UI framework or Tailwind, not just the root's a monorepo keeps
444
+ // Any manifest in the repo declaring a UI framework or Tailwind, not just the root's, a monorepo keeps
445
445
  // React in the app package and the root manifest is a handful of build tools.
446
446
  available:
447
447
  `rg -l --no-messages -g '**/package.json' -g '!**/node_modules/**' ` +
@@ -1,18 +1,18 @@
1
- /* HOW WE ASK. Every prompt this workspace generates from a measurement a hotspot's refactor, a chore's sweep
1
+ /* HOW WE ASK. Every prompt this workspace generates from a measurement, a hotspot's refactor, a chore's sweep,
2
2
  * has the same four parts, in the same order, for the same reasons:
3
3
  *
4
4
  * subject the one line that says what is being worked on. First, because a model that reads the rationale
5
5
  * before the target starts planning against a subject it has not been told yet.
6
6
  * why the NUMBERS, quoted exactly as the panel shows them, then what they mean. Exact so the agent and
7
7
  * the person are arguing about one set of facts; the agent can and should recount them.
8
- * goal what shape to move towards never a design. Whoever generated this prompt has not read the code,
8
+ * goal what shape to move towards, never a design. Whoever generated this prompt has not read the code,
9
9
  * so a prescribed solution from out here is a guess wearing an instruction's clothes.
10
10
  * done falsifiable, and checkable by the agent itself. The same resident engine that produced the
11
11
  * measurement answers `iq` in the agent's own worktree, so "run it again and see" is available and
12
12
  * "I have finished" is not something it has to be taken at its word on.
13
13
  *
14
14
  * The invariants sit between goal and done because they are the constraints on HOW, and they are stated in full
15
- * every time rather than assumed. Each one is a specific way the turn fails without it they are here because
15
+ * every time rather than assumed. Each one is a specific way the turn fails without it, they are here because
16
16
  * they were each learned from a diff nobody could review. */
17
17
 
18
18
  export interface Ask {
@@ -30,16 +30,16 @@ export const composeAsk = ({ subject, why, diagnosis, goal, invariants, done }:
30
30
  /* Said to every turn a TOOL woke, and the reason the maintenance surface can point agents at tool output at all.
31
31
  * A tool reporting N findings is not reporting N problems: knip is confidently wrong about anything reachable
32
32
  * from outside the repo, jscpd counts generated files, an advisory in a build-time dependency is not the same
33
- * risk as one in a running service. A chore that mechanically actions the whole list is worse than no chore
33
+ * risk as one in a running service. A chore that mechanically actions the whole list is worse than no chore,
34
34
  * it makes noisy, confident, wrong changes at three in the morning, and the next person has to review a diff
35
35
  * whose author had no opinion about it. */
36
36
  export const TRIAGE_NOTE =
37
37
  `The measurement woke you; it did not decide anything. Read the repository before you touch it, and treat every ` +
38
38
  `finding as a claim to verify rather than a task to execute. If a finding is wrong, say why in one line and leave ` +
39
- `it a run that verifies ten and fixes two is a good run.`;
39
+ `it. A run that verifies ten and fixes two is a good run.`;
40
40
 
41
41
  /* The invariants for a turn that CHANGES things. Whatever it does lands as uncommitted work in the owner's
42
- * workspace, so it is reviewed as one diff by someone who did not watch it happen which is what every clause
42
+ * workspace, so it is reviewed as one diff by someone who did not watch it happen, which is what every clause
43
43
  * here is protecting.
44
44
  *
45
45
  * "Separately explainable" is doing the most work: a chore that fixes its findings AND tidies what it passed on
@@ -57,8 +57,8 @@ export const REPORT_INVARIANTS =
57
57
  `can act on or dismiss. Where you would propose an edit, describe it and where it would go instead of making it.`;
58
58
 
59
59
  /* The invariants for a turn refactoring ONE FILE, as the codebase-health panel's rows ask for. Distinct from the
60
- * chore ones because the blast radius is the thing at stake: named as a radius rather than "only this file",
60
+ * chore ones because the scope of changes is the thing at stake: named as a scope rather than "only this file",
61
61
  * since half those archetypes ask for new files and must not read as forbidding them. */
62
62
  export const REFACTOR_INVARIANTS =
63
- `Read it first. Behaviour stays identical, and the blast radius is this file, whatever it splits into, and the ` +
64
- `importers that must follow no re-export shims left behind.`;
63
+ `Read it first. Behaviour stays identical. Changes affect only this file, whatever it splits into, and the ` +
64
+ `importers that must follow. Leave no re-export shims behind.`;
@@ -1,4 +1,4 @@
1
- /* WHAT THIS REPOSITORY IS BUILT WITH, and the patterns that follow from it the table the UI chores are written
1
+ /* WHAT THIS REPOSITORY IS BUILT WITH, and the patterns that follow from it, the table the UI chores are written
2
2
  * against, kept apart from both the probe that runs it and the chores that read it.
3
3
  *
4
4
  * It sits in the middle on purpose. probes.ts composes ONE ripgrep sweep out of the rules below, and chores.ts
@@ -13,14 +13,14 @@
13
13
  * THE PATTERNS ARE RIPGREP'S DIALECT, and they carry two constraints that are not obvious from reading them:
14
14
  *
15
15
  * No literal apostrophe, ever. The scan command wraps each pattern in shell single quotes, so a `'` inside one
16
- * would end the quoting and hand the rest of the regex to the shell. Match quotes as `[\x22\x27]` instead
16
+ * would end the quoting and hand the rest of the regex to the shell. Match quotes as `[\x22\x27]` instead,
17
17
  * Rust's regex crate reads those escapes, and the shell never sees a quote character at all. stack.test.ts
18
18
  * enforces this, because the failure is a probe that dies at three in the morning in someone else's workspace
19
19
  * rather than anything a reader would notice here.
20
20
  *
21
21
  * No lookaround. Rust's regex crate has none, and reaching for ripgrep's PCRE2 mode to get it would make the
22
22
  * sweep depend on how the box's ripgrep was compiled. A rule that seems to need it is usually asking a question
23
- * about the FILE rather than about a line see `absent` below, which is what that question actually is. */
23
+ * about the FILE rather than about a line, see `absent` below, which is what that question actually is. */
24
24
 
25
25
  export interface UiFramework {
26
26
  readonly id: string;
@@ -38,7 +38,7 @@ export const UI_FRAMEWORKS: readonly UiFramework[] = [
38
38
  { id: `angular`, label: `Angular`, packages: [`@angular/core`] },
39
39
  ];
40
40
 
41
- // Tailwind is not in the table above because it is not a UI framework and does not own any idiom rules it is a
41
+ // Tailwind is not in the table above because it is not a UI framework and does not own any idiom rules, it is a
42
42
  // styling system that any of the three can be wearing, and it gates exactly one chore.
43
43
  export const TAILWIND_PACKAGES: readonly string[] = [`tailwindcss`];
44
44
 
@@ -64,20 +64,20 @@ export const SCAN_IGNORES: readonly string[] = [
64
64
  `!**/*.{test,spec,stories}.*`,
65
65
  ];
66
66
 
67
- // What counts as a component file, across all three frameworks at once. The sweep cannot vary by repository a
68
- // probe's command is a fixed string so it asks for all of them and a Vue-only repo simply has no `.tsx` files.
67
+ // What counts as a component file, across all three frameworks at once. The sweep cannot vary by repository, a
68
+ // probe's command is a fixed string, so it asks for all of them and a Vue-only repo simply has no `.tsx` files.
69
69
  export const COMPONENT_GLOBS: readonly string[] = [`*.vue`, `*.tsx`, `*.jsx`, `*.component.ts`];
70
70
 
71
71
  // Where a Tailwind class can appear. Wider than COMPONENT_GLOBS because a class list lives in markup as often as
72
- // in a component an Angular template and a plain .html page both style with the same utilities.
72
+ // in a component, an Angular template and a plain .html page both style with the same utilities.
73
73
  export const MARKUP_GLOBS: readonly string[] = [`*.vue`, `*.tsx`, `*.jsx`, `*.html`, `*.svelte`, `*.astro`];
74
74
 
75
- /* THE DESIGN SYSTEM BYPASS. Not "any arbitrary value" `grid-cols-[1fr_auto]` and `w-[calc(100%-2rem)]` are
75
+ /* THE DESIGN SYSTEM BYPASS. Not "any arbitrary value", `grid-cols-[1fr_auto]` and `w-[calc(100%-2rem)]` are
76
76
  * Tailwind working as designed, and a chore that counted them would be objecting to the feature rather than to
77
77
  * anything wrong. What this matches is the two arbitrary values that route around a decision the theme already
78
78
  * made: a colour that is not in the palette, and a pixel size that is not on the spacing or type scale.
79
79
  *
80
- * The leading `-` is load-bearing. It anchors the match to a utility prefix (`bg-`, `text-`, `w-`), so a bare
80
+ * The leading `-` is required. It anchors the match to a utility prefix (`bg-`, `text-`, `w-`), so a bare
81
81
  * `[...]` in ordinary prose or an array index cannot be mistaken for a class. */
82
82
  export const BYPASS_PATTERN = `-\\[(#[0-9a-fA-F]{3,8}|(rgb|hsl)a?\\(|[0-9]+(\\.[0-9]+)?px)`;
83
83
 
@@ -87,12 +87,12 @@ export interface IdiomRule {
87
87
  readonly framework: string;
88
88
  // What the repository still has, named as the reader would name it.
89
89
  readonly label: string;
90
- // What replaced it. Carried so the prompt can say where to go rather than only what to leave an agent told
90
+ // What replaced it. Carried so the prompt can say where to go rather than only what to leave, an agent told
91
91
  // "you still use NgModule" and nothing else will pick a destination, and it may not pick this one.
92
92
  readonly replacement: string;
93
93
  readonly pattern: string;
94
94
  readonly globs: readonly string[];
95
- /* THE IDIOM IS THE PATTERN BEING MISSING, not present `pattern` names the NEW way, and the file is on the
95
+ /* THE IDIOM IS THE PATTERN BEING MISSING, not present, `pattern` names the NEW way, and the file is on the
96
96
  * old one precisely because the new one does not appear in it anywhere. The sweep spells this
97
97
  * `--files-without-match`.
98
98
  *
@@ -100,7 +100,7 @@ export interface IdiomRule {
100
100
  * <script setup>" was first written as a lookahead over `<script`, which matches per LINE: a migrated
101
101
  * component with a second plain `<script>` block for defineOptions, or one that merely mentions `<script` in
102
102
  * a comment, both read as un-migrated. It reported five files in an application whose 167 SFCs are every one
103
- * of them migrated. The question was never "is there a line like this" it is "does this file contain the
103
+ * of them migrated. The question was never "is there a line like this", it is "does this file contain the
104
104
  * new idiom at all", which is one flag rather than a cleverer regex, and it costs no PCRE2.
105
105
  *
106
106
  * The globs carry more weight on an absent rule than on a normal one, and narrowly is the only safe way to
@@ -110,7 +110,7 @@ export interface IdiomRule {
110
110
  }
111
111
 
112
112
  /* THE IDIOMS THEIR OWN MAINTAINERS HAVE MOVED ON FROM. Every rule here names something the framework's own
113
- * documentation now steers people away from, and every one of them still works which is exactly why they
113
+ * documentation now steers people away from, and every one of them still works, which is exactly why they
114
114
  * accumulate, and why no editor and no linter will bring them up unprompted.
115
115
  *
116
116
  * High confidence over coverage. Each pattern is one a reader can check by eye against a file, and the ones that
@@ -156,7 +156,7 @@ export const IDIOM_RULES: readonly IdiomRule[] = [
156
156
  framework: `vue`,
157
157
  label: `the Options API`,
158
158
  replacement: `<script setup> with the Composition API`,
159
- // The new idiom, inverted by `absent` below an SFC that never opens a `<script setup>` tag is still on
159
+ // The new idiom, inverted by `absent` below, an SFC that never opens a `<script setup>` tag is still on
160
160
  // the old one. A file with no script block at all is swept up too, and that is the honest reading: it has
161
161
  // not been migrated because there was nothing there to migrate.
162
162
  pattern: `<script[^>]*\\bsetup\\b`,
@@ -213,14 +213,14 @@ export const idiomRule = (id: string): IdiomRule | undefined => IDIOM_RULES.find
213
213
  * side had two extra characters. */
214
214
  export const normalizePath = (path: string): string => path.replace(/^\.\//, ``);
215
215
 
216
- // Below this a stem is too short to have survived the stripping above with its meaning intact `H1` and `H2`
216
+ // Below this a stem is too short to have survived the stripping above with its meaning intact, `H1` and `H2`
217
217
  // would both reduce to `h` and read as one family of heading components that are not duplicates of anything.
218
218
  const MIN_STEM = 3;
219
219
 
220
220
  const QUALIFIER_PREFIX = /^(base|the)/;
221
221
  const QUALIFIER_SUFFIX = /(v[0-9]+|new|old|legacy|copy|component|[0-9]+)$/;
222
222
 
223
- /* THE NAME TWO COMPONENTS SHARE WHEN THEY ARE THE SAME COMPONENT TWICE or `undefined` when the file has no
223
+ /* THE NAME TWO COMPONENTS SHARE WHEN THEY ARE THE SAME COMPONENT TWICE, or `undefined` when the file has no
224
224
  * name worth comparing.
225
225
  *
226
226
  * This is a normaliser, not a similarity score, and that is the point: it answers a question the reader can check
@@ -232,7 +232,7 @@ const QUALIFIER_SUFFIX = /(v[0-9]+|new|old|legacy|copy|component|[0-9]+)$/;
232
232
  * forty index files is a finding about the naming convention rather than about any duplication. */
233
233
  export const componentStem = (path: string): string | undefined => {
234
234
  const file = normalizePath(path).split(`/`).pop() ?? ``;
235
- // `.component.ts` loses both suffixes, `.vue` loses one taking everything before the first dot handles both
235
+ // `.component.ts` loses both suffixes, `.vue` loses one, taking everything before the first dot handles both
236
236
  // without a table, since a component's name is never the part after a dot.
237
237
  const base = (file.split(`.`)[0] ?? ``).toLowerCase().replace(/[^a-z0-9]/g, ``);
238
238
  if (base === `` || base === `index`) {
@@ -2,18 +2,18 @@ import type { ChoreLedgerEntry, ChoresReport, ProbeId, ProbeResult } from "../sc
2
2
  import { type Chore, type ChoreContext, type ChoreFinding, CHORES, chorePrompt } from "./chores.js";
3
3
  import { probeSpec } from "./probes.js";
4
4
 
5
- /* FROM EVIDENCE TO A VERDICT the one place that decides whether a chore is due, and the only place that is
5
+ /* FROM EVIDENCE TO A VERDICT, the one place that decides whether a chore is due, and the only place that is
6
6
  * allowed to. Both the Maintenance panel and its rail badge run this function over the same report, so the number
7
7
  * on the tile and the reason in the panel are the same computation and cannot drift apart.
8
8
  *
9
9
  * Six states, and the distinctions between them are the whole design:
10
10
  *
11
- * not-applicable this chore is not a QUESTION worth asking of this repository there is no Dockerfile to
11
+ * not-applicable this chore is not a QUESTION worth asking of this repository, there is no Dockerfile to
12
12
  * slim, no pipeline to tighten, no documentation to re-read. Dropped from the panel entirely
13
13
  * rather than shown as clear, because "clear" claims we checked, and there was nothing to check.
14
14
  * The reason survives in the scope strip, so "why is there no Docker chore here?" has an answer.
15
15
  * unavailable we have not measured this. knip is not a devDependency; there is no lockfile to audit. Rendered
16
- * greyed, never badged, and never collapsed into `clear` a maintenance surface reporting a green
16
+ * greyed, never badged, and never collapsed into `clear`, a maintenance surface reporting a green
17
17
  * repository it has never actually measured is worse than one that says nothing.
18
18
  * clear we measured, and there is nothing to do. This is the common state, and it has to be visibly
19
19
  * reachable or the panel is just a list of complaints.
@@ -23,23 +23,23 @@ import { probeSpec } from "./probes.js";
23
23
  * due there is something to do.
24
24
  *
25
25
  * The first three are all ways of saying "no", and keeping them apart is what makes the surface trustworthy: they
26
- * mean we cannot ask, we did not measure, and we measured and found nothing three different claims, and only
26
+ * mean we cannot ask, we did not measure, and we measured and found nothing, three different claims, and only
27
27
  * the last one is reassurance.
28
28
  *
29
29
  * And one flag that is not a state: `settled`. A due chore that has been RE-MEASURED since a turn was spent on
30
- * it, and whose evidence did not move, stays due because it is but must never light the rail again. This is
30
+ * it, and whose evidence did not move, stays due, because it is, but must never light the rail again. This is
31
31
  * what stops the surface repeating itself while a fix sits in review, and it is why the ledger stores a digest
32
32
  * rather than a timestamp: "ran 3 days ago" cannot tell you whether it ran against THIS.
33
33
  *
34
34
  * `stale` is the other half of that sentence, and it exists because the digest alone cannot tell the two apart.
35
35
  * A probe that never re-ran produces an unchanged digest for free, so "the fix did not move the numbers" and "we
36
- * have not looked since the fix" arrived at this function looking identical and the panel showed the second as
36
+ * have not looked since the fix" arrived at this function looking identical, and the panel showed the second as
37
37
  * the first, quoting a week-old count an hour after the work that invalidated it. Comparing the run's time to the
38
38
  * MEASUREMENT's time is what separates them, and it is a comparison of two numbers the report already carries.
39
39
  *
40
40
  * Nothing here can hide a problem. Snoozing and settling change whether the rail SPEAKS; the panel still shows
41
41
  * the chore, its evidence and its state. The one thing that removes a row entirely is `not-applicable`, and that
42
- * is not hiding it is the absence of a subject, counted in the panel's scope strip and expandable to the reason.
42
+ * is not hiding, it is the absence of a subject, counted in the panel's scope strip and expandable to the reason.
43
43
  * A maintenance surface you can quietly bury findings in is a maintenance surface nobody trusts. */
44
44
 
45
45
  export type ChoreState = "due" | "clear" | "snoozed" | "stale" | "unavailable" | "not-applicable";
@@ -49,16 +49,16 @@ export interface ChoreVerdict {
49
49
  readonly repo: string;
50
50
  readonly state: ChoreState;
51
51
  readonly severity: ChoreFinding["severity"];
52
- // Always present, in every state "nothing to do" and "not measured" are answers a reader deserves in words.
52
+ // Always present, in every state, "nothing to do" and "not measured" are answers a reader deserves in words.
53
53
  readonly headline: string;
54
54
  readonly detail: readonly string[];
55
55
  // The evidence identity. Empty for `unavailable`, where there is no evidence to identify.
56
56
  readonly digest: string;
57
- /* WHEN THE EVIDENCE WAS TAKEN the fact every row shows beside its numbers, and the one whose absence let a
57
+ /* WHEN THE EVIDENCE WAS TAKEN, the fact every row shows beside its numbers, and the one whose absence let a
58
58
  * measurement from last Tuesday read as this morning's. Undefined when the verdict rests on no measurement at
59
59
  * all: a survey is decided by the calendar, and an unavailable chore has nothing to be out of date. */
60
60
  readonly measuredAt: number | undefined;
61
- // The turn. Present only when there is something to do a "start an agent" button on a clear chore is an
61
+ // The turn. Present only when there is something to do, a "start an agent" button on a clear chore is an
62
62
  // invitation to spend money proving that nothing is wrong.
63
63
  readonly prompt: string | undefined;
64
64
  readonly lastRun: ChoreLedgerEntry | undefined;
@@ -68,7 +68,7 @@ export interface ChoreVerdict {
68
68
  }
69
69
 
70
70
  /* HOW OLD THE EVIDENCE IS: the OLDEST of the measurements a verdict rests on, because a claim is only as current
71
- * as the least current thing it was computed from. Undefined when it rests on none a survey has no measurement,
71
+ * as the least current thing it was computed from. Undefined when it rests on none, a survey has no measurement,
72
72
  * and an unavailable chore's probe did not produce one. */
73
73
  const measurementAge = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId, ProbeResult>): number | undefined => {
74
74
  const taken = needs.flatMap((id) => {
@@ -79,7 +79,7 @@ const measurementAge = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId,
79
79
  };
80
80
 
81
81
  // A survey that is clear is clear because it was READ recently, and saying so is the only way its row means
82
- // anything "nothing to do" under a chore that has no measurement would be a claim about the code rather than
82
+ // anything, "nothing to do" under a chore that has no measurement would be a claim about the code rather than
83
83
  // about the calendar.
84
84
  const clearHeadline = (chore: Chore, lastRun: ChoreLedgerEntry | undefined, nowMs: number): string =>
85
85
  chore.survey === true && lastRun !== undefined ? `Surveyed ${Math.round((nowMs - lastRun.ranAt) / 86_400_000)} days ago` : `Nothing to do`;
@@ -109,9 +109,9 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
109
109
  const base = { chore, repo: context.repo, lastRun: ledger, settled: false, prompt: undefined } as const;
110
110
 
111
111
  /* APPLICABILITY FIRST, before anything is measured or any evidence is read. A chore that does not apply is
112
- * not "clear" and not "unmeasured" the question does not arise here, and every subsequent branch of this
113
- * function would be answering it anyway. The cause is carried as the headline, because the scope strip which
114
- * groups these rows BY it is the only place it will ever be read. */
112
+ * not "clear" and not "unmeasured", the question does not arise here, and every subsequent branch of this
113
+ * function would be answering it anyway. The cause is carried as the headline, because the scope strip, which
114
+ * groups these rows BY it, is the only place it will ever be read. */
115
115
  const inapplicable = chore.applies?.(context.signals);
116
116
  if (inapplicable !== undefined) {
117
117
  return { ...base, state: `not-applicable`, severity: `info`, headline: inapplicable, detail: [], digest: ``, measuredAt: undefined };
@@ -138,7 +138,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
138
138
  };
139
139
  }
140
140
 
141
- /* Has the last run's settlement lapsed? A cadence of 0 means "this is decided by evidence alone" an advisory
141
+ /* Has the last run's settlement lapsed? A cadence of 0 means "this is decided by evidence alone", an advisory
142
142
  * does not become worth looking at again because ninety days passed, it becomes worth looking at again when
143
143
  * the advisory set changes. Anything with a cadence expires its own settlement, so "we looked and chose not to
144
144
  * act" cannot silence a chore for good. */
@@ -176,7 +176,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
176
176
  };
177
177
  }
178
178
 
179
- /* The agent looked at exactly this evidence and reported that there was nothing in it knip's findings were
179
+ /* The agent looked at exactly this evidence and reported that there was nothing in it, knip's findings were
180
180
  * all public entry points, the clones were all generated files. That verdict has to stick, or the next poll
181
181
  * starts the same turn again and the surface has taught the owner that its rows are wrong. It stops sticking
182
182
  * when the evidence changes (a different digest) or the cadence lapses. */
@@ -193,10 +193,10 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
193
193
  }
194
194
 
195
195
  /* THE MEASUREMENT IS OLDER THAN THE WORK. A turn landed after the last time we looked, so the evidence below
196
- * describes a tree that no longer exists an hour after a run deleted the dead code, the row was still
196
+ * describes a tree that no longer exists, an hour after a run deleted the dead code, the row was still
197
197
  * quoting the count from six days before it. `sameEvidence` cannot catch this: an unchanged digest is exactly
198
198
  * what a probe that never re-ran produces, so the flag says "settled" at its most confident when it knows
199
- * least. The chore steps down instead evidence stays on the row, the CLAIM comes off it and carries no
199
+ * least. The chore steps down instead, evidence stays on the row, the CLAIM comes off it, and carries no
200
200
  * prompt, because the honest next move is to measure again rather than to spend a second turn on a finding
201
201
  * nobody has re-checked. It cannot badge either, which is what stops the tile lighting for work already done. */
202
202
  if (ledger !== undefined && measuredAt !== undefined && ledger.ranAt > measuredAt) {
@@ -220,8 +220,8 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
220
220
  // two independent questions with two independent answers.
221
221
  export const ledgerKey = (repo: string, chore: string): string => `${repo}|${chore}`;
222
222
 
223
- /* Every chore in every repo, from one report. This is what both surfaces call the panel groups the result, the
224
- * badge filters it so there is exactly one traversal of the book in the codebase and adding a chore to CHORES
223
+ /* Every chore in every repo, from one report. This is what both surfaces call, the panel groups the result, the
224
+ * badge filters it, so there is exactly one traversal of the book in the codebase and adding a chore to CHORES
225
225
  * reaches both surfaces without touching either. */
226
226
  export const assessReport = (report: ChoresReport, nowMs: number): ChoreVerdict[] => {
227
227
  const ledger = new Map(report.ledger.map((entry) => [ledgerKey(entry.repo, entry.chore), entry]));
@@ -232,10 +232,10 @@ export const assessReport = (report: ChoresReport, nowMs: number): ChoreVerdict[
232
232
  };
233
233
 
234
234
  /* WHAT THE RAIL IS ALLOWED TO SAY. A badge must mean "something happened here that you don't already know about",
235
- * never "here is a statistic" the extension API states that bar and this is the function that holds this
235
+ * never "here is a statistic", the extension API states that bar and this is the function that holds this
236
236
  * surface to it. Three filters, and every one of them removes a case that would otherwise light the tile forever:
237
237
  *
238
- * state === due the obvious one and it is also what keeps `stale` silent, since a measurement taken before
238
+ * state === due the obvious one, and it is also what keeps `stale` silent, since a measurement taken before
239
239
  * the last turn is not a fact anyone should be interrupted about.
240
240
  * !settled a turn has been spent on this chore and the re-measured evidence did not move.
241
241
  * unseen digest the owner has already LOOKED at this evidence in the panel. Acknowledgement is per digest
@@ -1,24 +1,24 @@
1
- /* THE WIRE SURFACE, AS ONE COMPARABLE DOCUMENT every zod schema this package exports, serialized to JSON
1
+ /* THE WIRE SURFACE, AS ONE COMPARABLE DOCUMENT, every zod schema this package exports, serialized to JSON
2
2
  * Schema and sorted into a canonical shape. `contract.lock.json` at the package root is this function's
3
3
  * committed output, and the pair is what makes "did the contract change?" a question git can answer.
4
4
  *
5
5
  * Two readers depend on that answer. contract-lock.test.ts fails whenever the committed file is out of step
6
- * with the code, so a contract change always lands WITH its lock diff visible in review, attributable to a
6
+ * with the code, so a contract change always lands WITH its lock diff, visible in review, attributable to a
7
7
  * commit. And prepass.mjs (invariant 6) compares the committed lock against its merge-base to insist that a
8
- * SHRUNK surface a schema or field removed, a type changed arrives as a `!` commit carrying a
8
+ * SHRUNK surface, a schema or field removed, a type changed, arrives as a `!` commit carrying a
9
9
  * `Breaking-Note:` trailer, which is what feeds the release's "Breaking changes" section and the update card's
10
10
  * warning. Additions pass freely; every persisted-manifest reader parses loosely, so growth breaks nobody.
11
11
  *
12
12
  * Serialized from the package's EXPORTS rather than a hand-kept list, on the repo's own rule (AGENTS.md:
13
13
  * "guard invariants by discovery, not enumeration"): a schema added tomorrow is in the lock tomorrow, and a
14
14
  * schema that stops being exported is a removal the lock shows. `unrepresentable: "any"` keeps the rare
15
- * function-valued corner from throwing it serializes as `{}`, which still diffs when it moves. */
15
+ * function-valued corner from throwing, it serializes as `{}`, which still diffs when it moves. */
16
16
 
17
17
  import { z } from "zod";
18
18
  import * as contract from "./index.js";
19
19
 
20
20
  // Canonical ordering, so two runs of the same code are byte-identical and a lock diff is a contract diff
21
- // rather than a key-order shuffle. Arrays keep their order for `required` and `enum` lists the order zod
21
+ // rather than a key-order shuffle. Arrays keep their order, for `required` and `enum` lists the order zod
22
22
  // emits is stable, and sorting them would hide a reorder that is genuinely no change at all anyway.
23
23
  const sorted = (value: unknown): unknown => {
24
24
  if (Array.isArray(value)) {
@@ -43,7 +43,7 @@ export const currentLock = (): Record<string, unknown> => {
43
43
  }
44
44
  try {
45
45
  const schema = sorted(z.toJSONSchema(value, { unrepresentable: "any" })) as Record<string, unknown>;
46
- // The dialect banner is identical on all ~500 entries pure weight, no surface.
46
+ // The dialect banner is identical on all ~500 entries, pure weight, no surface.
47
47
  delete schema["$schema"];
48
48
  lock[name] = schema;
49
49
  } catch {
@@ -55,7 +55,7 @@ export const currentLock = (): Record<string, unknown> => {
55
55
  return lock;
56
56
  };
57
57
 
58
- /* ONE LINE PER SCHEMA, on purpose not JSON.stringify(lock, null, 4). Pretty-printed, the lock is a 35k-line
58
+ /* ONE LINE PER SCHEMA, on purpose, not JSON.stringify(lock, null, 4). Pretty-printed, the lock is a 35k-line
59
59
  * wall nobody scrolls; a line per export keeps it ~500 lines and makes `git diff` read as the list of WHICH
60
60
  * surfaces moved, which is the level a reviewer reviews at. The path-level detail lives in the tooling: the
61
61
  * lock test diffs parsed objects and prepass invariant 6 names the exact removed paths, so nothing is lost by
@@ -1,7 +1,7 @@
1
1
  import { oc } from "@orpc/contract";
2
2
  import { ActivityListSchema, ActivityQuerySchema, ActivityStatusSchema } from "../schemas.js";
3
3
 
4
- // The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design
4
+ // The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design,
5
5
  // events are appended daemon-side only, so the log stays a trustworthy record of what the agent did.
6
6
  export const activityContract = {
7
7
  list: oc.route({ method: "GET", path: "/activity" }).input(ActivityQuerySchema).output(ActivityListSchema),
@@ -14,7 +14,7 @@ import {
14
14
  } from "../schemas.js";
15
15
 
16
16
  // A turn EXECUTES as a detached daemon-side run: `run` starts it and acks with the run id; any number of
17
- // clients render it via `attach` (replay from a seq cursor, then live) the initiating window holds no
17
+ // clients render it via `attach` (replay from a seq cursor, then live), the initiating window holds no
18
18
  // special stream, so a reload, a second window, or another device attaches identically. `reply` un-parks a
19
19
  // turn waiting on any interactive card (plan approval, clarifying questions, a per-tool permission prompt);
20
20
  // steer injects a user message into the running turn; stop hard-cancels it daemon-side.
@@ -25,7 +25,7 @@ export const agentContract = {
25
25
  steer: oc.route({ method: "POST", path: "/agent/steer" }).input(SteerSchema).output(OkSchema),
26
26
  stop: oc.route({ method: "POST", path: "/agent/stop" }).input(StopTurnSchema).output(OkSchema),
27
27
  // Go back to a message: restore the workspace to that turn's checkpoint, drop the messages after it, and
28
- // forget the provider session. CONFLICT while a turn is running a restore cannot overwrite files an
28
+ // forget the provider session. CONFLICT while a turn is running, a restore cannot overwrite files an
29
29
  // agent is editing. NOT_FOUND when that message has no checkpoint to go back to.
30
30
  rewind: oc.route({ method: "POST", path: "/agent/rewind" }).input(RewindTurnSchema).output(RewindResultSchema),
31
31
  // The provider's slash commands as last published by one of its turns, so a conversation's `/` popover is
@@ -24,14 +24,14 @@ import {
24
24
 
25
25
  // The fleet: every registered conversation-agent (see AgentSummarySchema). Registry-level actions (read,
26
26
  // rename, seen, archive) apply uniformly. For branch-backed conversations, `diff` is the worktree's CUMULATIVE
27
- // multi-repo delta vs its recorded per-repo bases one flat change set per repo
27
+ // multi-repo delta vs its recorded per-repo bases, one flat change set per repo
28
28
  // (AgentChanges), each file flagged `landed` or not, deliberately not the working tree's staged/unstaged
29
29
  // shape: a worktree the user never checks out has no index they could stage into. `land` merges the worktree
30
30
  // branches into the main tree (per-repo, conflicts reported, nothing lost on failure); `discard` removes
31
31
  // worktrees + branches + registry entry. Those branch actions reject workspace conversations explicitly. An
32
32
  // unknown {id} is NOT_FOUND; land/discard while the turn runs is CONFLICT.
33
- // `rename` sets the user-chosen display title legal mid-turn (it touches no worktree state).
34
- // `seen`/`seenAll` stamp the read marker behind the cards' unread badge (AgentSummarySchema.seenAt) also
33
+ // `rename` sets the user-chosen display title, legal mid-turn (it touches no worktree state).
34
+ // `seen`/`seenAll` stamp the read marker behind the cards' unread badge (AgentSummarySchema.seenAt), also
35
35
  // legal mid-turn, and like `rename` they never bump `updatedAt` (reading is not activity).
36
36
  //
37
37
  // ARCHIVE is the non-destructive counterpart to discard, and the one the board leans on: for an isolated
@@ -41,7 +41,7 @@ import {
41
41
  // surviving branch. Archiving a running agent is CONFLICT, same as land/discard.
42
42
  //
43
43
  // PURGE empties the archive, and it is `discard` applied to every agent already in there: worktree remnants,
44
- // branches and entries all go. Deliberately the whole archive and not a list of ids the archive is the pile
44
+ // branches and entries all go. Deliberately the whole archive and not a list of ids, the archive is the pile
45
45
  // of agents the user has already decided are over, so "clean it up" is one act with one confirmation, and a
46
46
  // per-id purge would be `discard`, which already exists. Never touches a running agent (a turn un-archives its
47
47
  // own agent, so there should be none) and answers with what it actually deleted: a teardown that fails on one
@@ -49,31 +49,31 @@ import {
49
49
  export const agentsContract = {
50
50
  list: oc.route({ method: "GET", path: "/agents" }).output(AgentsListSchema),
51
51
  archived: oc.route({ method: "GET", path: "/agents/archived" }).output(AgentsListSchema),
52
- // The board's filter. Answers over BOTH halves of the fleet the live roster and the archive because
52
+ // The board's filter. Answers over BOTH halves of the fleet, the live roster and the archive, because
53
53
  // the board hides by design (the Finished lane windows to a handful, archived agents are off the roster
54
54
  // entirely), and a filter that reports "no matches" while the agent sits one click away is a lie. The
55
55
  // never-carded conversations that are neither are `sessions.list`'s query, which matches by the same rule.
56
56
  search: oc.route({ method: "GET", path: "/agents/search" }).input(AgentSearchQuerySchema).output(AgentSearchResultSchema),
57
57
  get: oc.route({ method: "GET", path: "/agents/{id}" }).input(AgentIdSchema).output(AgentSummarySchema),
58
58
  transcript: oc.route({ method: "GET", path: "/agents/{id}/transcript" }).input(AgentIdSchema).output(AgentTranscriptSchema),
59
- /* SPEAK AS THE AGENT append the user's words to the conversation's record as an assistant row, with no
59
+ /* SPEAK AS THE AGENT, append the user's words to the conversation's record as an assistant row, with no
60
60
  * turn behind them and no reply. The row is marked `placed` for human readers (RestoredMessageSchema); the
61
61
  * provider session is FORGOTTEN in the same breath, rewind-style, so the next real turn opens a fresh
62
- * runtime session seeded from the record where the placed line reads as the agent's own words, because
62
+ * runtime session seeded from the record, where the placed line reads as the agent's own words, because
63
63
  * the handoff renders every assistant row identically. A running turn is CONFLICT: the illusion can only be
64
64
  * established between turns, and a concurrent turn would resume the very session this exists to retire. */
65
65
  place: oc.route({ method: "POST", path: "/agents/{id}/place" }).input(AgentPlaceSchema).output(OkSchema),
66
66
  rename: oc.route({ method: "POST", path: "/agents/{id}/rename" }).input(AgentRenameSchema).output(AgentSummarySchema),
67
- // This agent's own land-at-completion posture an override of the sandbox-wide `autoLand` setting; null
67
+ // This agent's own land-at-completion posture, an override of the sandbox-wide `autoLand` setting; null
68
68
  // clears it back to "inherit". Legal mid-turn on purpose: the setting is read at turn COMPLETION, so
69
69
  // flipping it while the agent works is exactly "hold THIS turn's work for review", the press that matters.
70
70
  autoLand: oc.route({ method: "POST", path: "/agents/{id}/auto-land" }).input(AgentAutoLandSchema).output(AgentSummarySchema),
71
- /* THIS conversation's answer to a provider outage an override of the sandbox-wide `resumeAfterOutage`
71
+ /* THIS conversation's answer to a provider outage, an override of the sandbox-wide `resumeAfterOutage`
72
72
  * setting; null clears it back to "inherit". The chat's offer at the moment a turn dies writes this and
73
73
  * never the global: the press happens inside one conversation and means "finish this piece of work", so
74
- * its honest blast radius is that conversation. Sandbox Agent owns the default for everything else.
74
+ * its honest scope is that conversation. Sandbox > Agent owns the default for everything else.
75
75
  *
76
- * Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too an outage kills a
76
+ * Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too, an outage kills a
77
77
  * main-tree chat exactly as readily as an isolated one, and there is no branch involved either way. */
78
78
  resumeAfterOutage: oc
79
79
  .route({ method: "POST", path: "/agents/{id}/resume-after-outage" })