darkwing: row 155 packet, rounds 1-3 and the follow-up brief draft (#1649)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
jason.woltjeandClaude Opus 5.5 committed 2026-10-18 16:19:11 -05:00
1 parent b2f57ca70d
commit 66f9e9d0c6
9 files changed
+5430

No files matched your search

+457
View File
@@ -0,0 +1,457 @@
# Row 155 (#1649): the human and the chief of staff configure the assistant
Builder: Darkwing. Brief: `docs/plans/2026-10-16_q169-assistant-config.md`, section "The human
and the chief of staff configure the assistant", blob b2aeba0364dd (rev 1717). Jason's ruling Q169.
Base: refactor 7ce156259, origin/refactor after row 125 landed (rebased 2026-10-18; built on
9098c33f7, then 99d2b49c9). Worktree: `~/darkwing-scratch/r155/wt-125`; `wt` (9098c33f7) and
`wt2` (99d2b49c9) are kept unchanged. Reviewers: Dewey (security) and Filbert.
Status: code, tests and docs written, 30 modified files plus `packages/webui/tests/assistant.test.mjs`.
Rebased twice: onto 99d2b49c9 (rows 149, 124, 129 and 164; see "Rebase onto 99d2b49c9"), then
onto row 125's landing (see "Rebase onto row 125" and "Row 125 landed"). The run on 99d2b49c9
had 27 of 28 files green; the red one, webui `shell.test.mjs`, was my miss from the first round
and is fixed. The run of all 39 files on 7ce156259 is green (see "Row 125 landed"). The first
round's runs on 9098c33f7 (sage-0720) are under "Single-file runs". The full gate has not run;
it is Sage's. Candidate: `r3/build.patch` and `r3/candidate-manifest.sha256` on 0473574b0
(see "Candidate round 3"); round 2 was `r2/` on 449cadd1f, round 1 `r1/` on 7ce156259.
## Rebase onto 99d2b49c9
Three commits since 9098c33f7 touch this row's files: 70504d23a (row 124, token usage),
03bbd2c98 (row 149, roles and harness) and 850484a7d (row 129, Console chat). I saved the old
worktree's diff against 9098c33f7 (`~/darkwing-scratch/r155/out/pre-rebase-9098c33f7.patch`),
added a new worktree at 99d2b49c9 and applied the diff with `git apply --3way`. 26 files
applied cleanly. Three webui files conflicted, and each resolved as a union of both sides:
- `src/cli.mjs`: row 129's `chat = createChat({ call })` sits beside this row's `nameWrite`
and `assistantStop`, and `startServer` gets all three.
- `src/serve.mjs`: `BUS_VERBS` ends `'messages', 'usage', 'names'`, and `startServer` takes
`chat`, `nameWrite` and `assistantStop`.
- `src/public/bus.js`: row 124's `tokenUsage` stays above this row's assistant block.
`settingsView` reads `usage` first, keeping row 124's rethrow on 403 or signed-out, then
`names`, and renders the launch form, the Assistant section, then Token usage.
`webui/tests/assistant.test.mjs` is untracked in the old worktree, so the diff-based patch
did not carry it. I copied it over unchanged (sha256 4e921ce653cc…) and marked it
intent-to-add. Every changed source passes `node --check`, `public/bus.js` parses, and
`git diff --check` is clean. The rebased diff is `out/rebased-99d2b49c9.patch` in my scratch:
30 files, +800 -104.
The rebased run (`~/darkwing-scratch/r155/run-rebased.sh`, each file singly) covers the
15 files of the first round plus the tests rows 124, 129 and 149 added or changed beside the
merged sources: webui `usage`, `usage-browser`, `chat`, `chat-browser`, `browser`, `shell`
and `views`; bus `usage`, `usage-broker`, `usage-runtime` and `workflow-roles`; business
`workflow-roles`; harness `raise-domain`. 28 files.
Rebased run (sage-0876), 2026-10-18T19:58:43Z to 19:59:52Z, behind `busy.sh`, TMPDIR in my
scratch: 27 of 28 files green, 236 pass and 1 fail summed over the logs. Per-file totals are in
`~/darkwing-scratch/r155/out/20261018T195844Z/summary.txt`. The sweep found 0 leftovers.
The failure is webui `shell.test.mjs`, the write-boundary test. It lists every POST, PUT, PATCH
and DELETE `fetch(...)` in `public/bus.js` and counts `method:`. This row's `assistantPost`
added `fetch(url, {method:'POST', ...})`, so the list had a fourth entry and the count was 4,
not 3. The assertion is the same at 9098c33f7, and row 129 did not change it. The first round
ran only the files I touched, so it never ran `shell.test.mjs`. This was my miss, not a rebase
effect.
The fix is in `shell.test.mjs` only:
- The expected list gets the `fetch(url, ...)` entry second, in source order.
- The `method:` count goes from 3 to 4.
- A new assertion pins every `assistantPost(` first argument to `url` (the definition),
`'/api/settings'`, `'/api/name'` and `'/api/assistant-stop'`. A fourth caller, or a computed
route, fails it.
`public/bus.js` is unchanged by the fix; its diff against 99d2b49c9 still has sha256
e37389fd…, the one going to Mos. The rerun of `shell.test.mjs`, with `settings-form` and
`assistant` as controls, waits for a slot. The rebased diff with the fix is
`out/rebased-99d2b49c9-r2.patch`: 31 files, +804 -106.
## Rebase onto row 125 (sage-0879)
Row 125 lands before this row and touches 11 of its files, so the run on 99d2b49c9 is not the
candidate. I dry-ran the rebase onto row 125's candidate 5289ca1d2 in
`~/darkwing-scratch/r155/wt-dry`. `git apply --3way` left 12 conflict blocks in 8 files.
`~/darkwing-scratch/r155/rebase/resolve125.py` resolves each block by index and refuses on a
count mismatch. Every resolution is a union:
- `docs/TOOLS.md`: row 125's token-limit paragraph, then this row's Assistant paragraph.
- Organization keys. There is one list, row 125's, with `assistants` appended:
`revision, limits, models, instances, reset, assistants`. It is the same in `SETTINGS_KEYS`
(`launch-settings.mjs`), both `ORGANIZATION_KEYS` (`broker.mjs`, `harness/src/tools.mjs`;
row 125's `tools.test.mjs` holds the three equal) and the Console's `writable` (`serve.mjs`).
This row's `UPDATE_KEYS` is gone; `set` refuses a patch with no key other than `revision`
by reading `SETTINGS_KEYS`.
- `cli/src/limits.mjs`: row 125's `reset PATH...` verb and `formatTokens`, and this row's
`PATCH keys:` list, now with `reset`. `cli/tests/limits.test.mjs` pins the new list.
- `broker.mjs` `#claimEnd`: row 125's signature with `by = s.run ?? s.human` and both comments.
This row's callers pass five arguments, so their `by` is unchanged.
- `harness/src/tools.mjs`: `change_organization` describes both the assistant fields and the
token-limit reset, and its schema has both `reset` and `assistants`.
- `public/bus.js` `settingsView`: reads usage, names and token limits, and renders the launch
form, Assistant, Token limits, then Token usage.
- `serve.mjs`: `BUS_VERBS` ends `'usage', 'limits', 'names'`. The refusal text names "a reset
or assistant settings".
- `shell.test.mjs`: five writes in source order (settings, row 125's reset post, `fetch(url,
...)`, project file, bus message), `method:` counted 5, and this row's call-site pin.
`node --check` passes on every changed source and `git diff --check` is clean. A static
evaluation of the shell test's regexes over the merged `bus.js` matches the expected list.
`assistant.test.mjs` came through with sha256 4e921ce653cc…. The dry diff is
`out/dry-5289ca1d2.patch` (31 files, +801 -104).
When row 125 lands, I redo the rebase on the landed commit, compare it with the dry run, and
run `~/darkwing-scratch/r155/run-125.sh` on Sage's slot. That is the 28 files above plus 11 row
125 test files on the merged sources: bus `limit-checks`, `limit-stop`, `limits-runtime` and
`process`; business `token-limit-settings`; cli `launch-limits`, `launch-limit-checks`,
`limits-tokens` and `thread-launch`; webui `limits-route` and `limits-browser`.
Sage's ruling sage-0713: no schema step (no 3i; row 124 keeps 3g to 3h). Row 134 stays its own
row, waits for row 155 to be done (rev 1722) and rebases on this landing. Until row 134, the
revision the chief of staff sends comes from the human; the bus, business, harness and cli
READMEs, `docs/TOOLS.md`, the tool description and `roles/chief-of-staff.md` say so. Row 156
also touches `broker.mjs` and the role md files; whichever lands second rebases.
## Row 125 landed (sage-0882)
Row 125 landed as aa625565f, and origin/refactor is now 7ce156259. The two commits after
row 125's packet (df4d5702f) touch only `docs/SESSIONS.md` and
`docs/plans/2026-10-14_prd-1.2-build.md`, neither in this row, so I based on 7ce156259 rather
than df4d5702f. aa625565f has the same tree as the dry run's base 5289ca1d2 (`git diff`
empty).
In `~/darkwing-scratch/r155/wt-125`, `git apply --3way` of `out/rebased-99d2b49c9-r2.patch`
left the same 8 files in conflict, and `resolve125.py` resolved the same 12 blocks with no
count mismatch. `node --check` passes on every changed source and `git diff --check` is clean.
`out/rebased-7ce156259.patch` is byte-identical to `out/dry-5289ca1d2.patch` (sha256
4dda2fc2…, 31 files, +801 -104), index lines included. `assistant.test.mjs` came through with
sha256 4e921ce653cc…, staged this time, not intent-to-add. The shell test's own regex over the
merged `bus.js` gives 5 writes equal to its expected list, 5 `method:` and the four
`assistantPost(` arguments. The `bus.js` diff for Mos is `out/busjs-7ce156259.diff`, sha256
9f9ce31b…, +53 -2.
Run (sage-0890), 2026-10-18T20:30:29Z to 20:31:58Z, `~/darkwing-scratch/r155/run-125.sh` with
`WT=~/darkwing-scratch/r155/wt-125`, behind `busy.sh`, TMPDIR in my scratch, each file singly:
39 of 39 files green. The awk sum over the 39 logs is 300 pass and 0 fail, with 0 skipped and 0
todo. `shell.test.mjs` passed 5 of 5. Per-file totals are in
`~/darkwing-scratch/r155/out/20261018T203030Z/`. The sweep found 0 leftovers.
## Candidate
`r1/build.patch` is the staged diff of `wt-125` against 7ce156259, byte-identical to
`out/rebased-7ce156259.patch`: sha256 4dda2fc2b7b2971b6daacaab57a027938bd074463ec25bfeb6314095d8503b52,
31 files, +801 -104. `r1/candidate-manifest.sha256` lists the sha256 of each of the 31 files
in `wt-125`; its own sha256 is 949873cfda99a2e3d418efee0066fa1438e1c12f69b9d2dfa9413ee089a79183.
The worktree had no unstaged change when both were written.
`busjs-7ce156259.diff` in this packet is the `public/bus.js` diff against 7ce156259 for Mos's
decision 123 go: sha256 9f9ce31bc14e6c0ed6d1d6c9f6cc282992d0631192be6416e0163866264029cd,
+53 -2. It is a copy of `out/busjs-7ce156259.diff` in my scratch.
## Candidate round 2 (sage-0900)
Row 162 landed as 7e7f8e208 and changed two of this row's 31 paths, `docs/TOOLS.md` and
`packages/cli/README.md`, so the r1 manifest no longer described the tree this row would land
on. Round 2 is confirm-only and makes no source change.
I applied `r1/build.patch` unchanged with `git apply --index` to a fresh worktree
(`~/darkwing-scratch/r155/wt-r2`) at 449cadd1f, refactor HEAD. It applied without conflict,
`git diff --cached --check` was clean, and the staged names are the same 31 files (+801 -104)
with no unstaged change. No run on this base: Sage's landing gate covers it, and
`host.test.mjs` reads the cli README.
`r2/build.patch` against BASE `449cadd1f` (sha256
`2a5a470d4c6fb53ca9c6ee8ffe4f5d8cb4c49b36b95f3f5ea1bc523224d94170`).
`r2/candidate-manifest.sha256` (sha256
`b1989f39012df6cbdd0e676cf19f75ccf88fcbf9d1c0a502eb71ab4247efad25`).
What moved from r1:
- In the manifest, only `docs/TOOLS.md` (now `e8fa08bf…`) and `packages/cli/README.md` (now
`7943058d…`). The other 29 sha256s are r1's.
- In `r2/build.patch`, only lines for those two files: their `index` lines and three hunk
headers (TOOLS.md `@@ -610` is now `@@ -636`, `@@ -627,6 +630` is now `@@ -653,6 +656`; the
cli README `@@ -789` is now `@@ -795`). Row 162 added text above these hunks, so they start
lower. With `index` and `@@` lines removed, r1 and r2 compare byte-identical.
- `public/bus.js`: its diff on 449cadd1f has the same sha256 as `busjs-7ce156259.diff`
(`9f9ce31b…`, +53 -2), index line included, since bus.js is unchanged between the two bases.
Mos's decision 123 go (mos-0816) was on those lines.
Round 1 verdicts on manifest 949873cf: Filbert approved (comment 29010, rev 1849), Dewey
approved (comment 29012, rev 1852). Each gave non-blocking follow-ups (see
`followup-brief.md`, which carries Mos's bus.js notes and Dewey's F1 on `serve.mjs` null
bodies; `body()` already refuses those, see its item 3).
## Candidate round 3 (sage-0906)
Sage's landing gate on r2 (candidate ba32dcad2 on 0473574b0, every suite run through, 20:53:10Z
to 20:59:57Z) had one failure out of 2370: `packages/bus/tests/channel-proof.test.mjs:18`,
"channel conversion preserves the four proof/transport byte baselines". Row 164 (008746b6e)
pins the sha256 of `packages/webui/src/bus.mjs` at :14, and this row's eight STATUS entries in
that file change it to `d1f3a274…e241`. My single-file runs missed it: the test reads
`bus.mjs` by path, not by import, and it isn't beside any file this row touches. That was my
miss. Two other tests read a manifest path literally (`stack-release`, `stack-copy` and webui
`service` read `packages/webui/src/cli.mjs`); they passed on Sage's gate.
Sage ruled to bump the pin: the `bus.mjs` diff is STATUS table entries only, and the freeze
guards the channel conversion, not later status codes. Round 3 changes only
`channel-proof.test.mjs:14`, the `bus.mjs` baseline, from `dbad731c…1f24` to
`d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241`, with a trailing comment
naming row 155. The other three baselines (`human.mjs`, `human-cli.mjs`, `transport.mjs`) and
the import check are unchanged, and none of those three files is in this row's manifest.
I applied `r2/build.patch` unchanged with `git apply --index` to a fresh worktree
(`~/darkwing-scratch/r155/wt-r3`) at 0473574b0, then made the one edit. Run on Sage's slot
(sage-0906), output in `~/darkwing-scratch/r155/out/r3-20261018T210058Z/`, 21:00:58Z to
21:01:13Z:
- `channel-proof.test.mjs` singly: 2 pass, 0 fail.
- Mutant, the `'name-taken': 409, …` STATUS line removed from `bus.mjs`: 1 pass, 1 fail, and
the failure names `packages/webui/src/bus.mjs`. Restored after, sha256 checked against
`d1f3a274…`.
- The whole `packages/bus` suite once (`node --test tests/*.test.mjs`): 399 pass, 0 fail, 0
skipped, 0 todo.
Totals are an awk sum over the logs. The sweep found 0.
`r3/build.patch` against BASE `0473574b0` (sha256
`9bee0e1b6546e70f06c1781beadc67082aa14990228f25571cdaf72e85f8c37e`), 32 files.
`r3/candidate-manifest.sha256` (sha256
`96cae28e095eff1632b7edcba195e1793bcabc3fb5aa93ea86d485c35e1f9016`), 32 paths.
What moved from r2: one new manifest line, `packages/bus/tests/channel-proof.test.mjs`
(`3e6d794b…`), and its one-line hunk in the patch. The other 31 manifest lines and every r2
patch line, index and `@@` lines included, are unchanged. The `public/bus.js` diff still has
sha256 `9f9ce31b…`, so Mos's go (mos-0816) applies.
## No schema step
The row needs no migration, so there is no 3i.
- `thread_claims` already has `op` (claim, release, revoke), `holder_run`, `by`, `reason` and
`decision` (`packages/bus/schema.sql`). A thread revoke is one more row there.
- `role.claim` on the thread path already refuses `run-revoked` for a run with a revoke row, so a
revoked assistant run can't claim its thread again.
- Names use row 141's `instance_names`. The new config key lives in the organization file, which
isn't the bus store.
The comment at `broker.mjs:2215` says a thread claim is never revoked and `thread_claims` has no
reason. Both stop being true here. `#claimEnd` now writes `reason` for a thread claim too, and the
comment says so.
## 1. What "configure" changes
A new organization key, `assistants`:
```json
{ "revision": "<64 hex>", "assistants": { "<instance>": { "vars": {}, "tracker": {}, "definition": "assistant" } } }
```
- Each field given replaces that field of the entry. `tracker: null` removes the tracker. A field
left out stays as it is. An empty entry is refused. An instance the file doesn't have is
`invalid-request`.
- Today `tracker: null` changes nothing in practice: the assistant definition uses no vikunja
credential, so `validateBusinessDocument` refuses any tracker on it and none exists to remove.
The field is there for a future assistant definition that has one.
- The target instance's current definition has to be the thread definition, or the write refuses
`organization-not-assistant`. A given `definition` has to be one the broker treats as the
assistant, or it refuses `organization-definition-refused`. Today that list is one name,
`assistant`, so a definition change is accepted only as a no-op. It's there so the field exists
when a second assistant definition does.
- `holder` and `credentials` aren't accepted (strict keys, `invalid-request`).
- The whole next document still goes through `validateBusinessDocument` and the unknown-model check.
A tracker on a definition with no vikunja credential is refused there, as it is for a hand edit.
No writer this row adds can give an instance a definition in `END_PROTECTED`. `assistants` only
targets instances that are already the assistant, and `instances` still sets only `vars.model`. On
top of that, `set()` now compares every instance's definition before and after and refuses
`organization-definition-refused` if any other one changed. That closes the dewey-0546 case
(an unprotected instance becoming the assistant after an arbiter resolved a revoke of it) for every
key the writer takes, not only the new one. It's defense in depth: no key today can change a
definition except `assistants`, which only accepts the same one.
*For Dewey.* `vars` replaces the whole object, including the `limits.tools`, `limits.network` and
`limits.authority` vars. Those only narrow: `resolve.mjs:64-68` filters the role file's tools and
authority by them and takes the narrower network, so a chief that writes wider ones gets nothing
the role file doesn't grant. A chief can remove a narrowing the human set, back up to the role
file. `checkVars` still checks every var.
One source for the definitions. `THREAD_DEFINITION` and `END_PROTECTED` move from `broker.mjs` into
`packages/business/src/vocabulary.mjs`, which the broker already imports for `ID_PATTERN`. The
broker and `launch-settings.mjs` read the same constants, so no equality test is needed. A test
holds `END_PROTECTED` including `THREAD_DEFINITION`, so the assistant can't lose its protection by
an edit to one list.
Files: `vocabulary.mjs`, `launch-settings.mjs` (`set` and `read`), `broker.mjs`
(`ORGANIZATION_KEYS`, the two constants), `packages/harness/src/tools.mjs` (its
`ORGANIZATION_KEYS` copy, which a test holds equal).
`read` adds `assistants`: each assistant instance's `definition`, `vars` and `tracker` as the file
has them, so the human and the Console see what they're replacing.
## 2. Who configures
- **Human, CLI.** `mosaic limits set --file PATCH` already sends the whole patch to `limits.set`,
so a patch with `assistants` works through it. `mosaic limits` prints the revision. The usage
line, the cli README and `docs/TOOLS.md` name the new key. I'm not adding a second command for
the same write.
- **Human, Console.** The Settings view gets an Assistant section per assistant instance: the
definition, the vars and the tracker as JSON fields, with the same Save, revision and receipt
handling as the limits form. `settingsSend`'s writable list adds `assistants`.
- **Chief of staff.** `change_organization` takes `assistants` as well (schema and description).
It runs `organization.set`, which checks `organization.change` before the writer and consumes it
after. Every other role is refused there, as today.
- **Revision.** Both carry the file's revision and a stale one refuses `revision-conflict` in
`organizationWriter.write`.
- **Log.** `organization.mjs` already writes the actor, `before`, `after` and `changes` to
`.history/<id>/changes.jsonl`. The actor is the human id, or `chief-of-staff.<instance>`.
**Gap: the chief can't read the revision.** No verb gives an agent the organization revision. Row
134 ("Organization revision read for a role holding organization.change", queued) owns that read.
Row 155 doesn't build it. Until row 134 lands, the chief's `change_organization` call works only
with a revision it was given (by the human, in a message), and the tests pass the revision in
directly. I've told Sage.
## 3. The seat name
- **Chief of staff.** A new typed tool, `set_name` (verb `name.set`, action `instance.name`), with
`instance` and `name`. Row 141 added the grant and the broker check; this adds the tool. With it,
`untypedActions(chief)` and the bundle's untyped list become `[]`, so `tools.test.mjs:43` and
`bundle.test.mjs:94` are re-pinned.
- **Human, Console.** A POST route, `/api/name`, with `{instance, name}` and the settings boundary
(signed-in header user, exact Origin, same-origin Fetch Metadata, JSON, strict keys, 4 KiB). It
calls `name.set` through `human-cli`, so the broker's `#human` check applies: a Console started
inside an agent run gets `human-required`. The Assistant section shows the current name, read
through a new `names` bus read. `names` is in `BUS_VERBS`, and the webui `cli.mjs` adds it beside
the shared views (`bus = { ...views({ call }), names: () => call('names') }`); `bus/src/views.mjs`
is unchanged, because the CLI doesn't need it. The route is `/api/name`; a second one,
`/api/assistant-stop`, is in section 4.
- **Human, CLI.** `mosaic name` already exists (row 141).
- Every other role is refused by `name.set`'s within-role check.
## 4. Removing the assistant: ending its runs
**role.revoke and role.end of a threaded instance.** Today the end path reads `role_claims` through
`#holder`, and the assistant claims in `thread_claims`, so the answer is `not-held`. For a threaded
target the end path now collects every thread of that instance whose latest claim is `claim`, and
writes a revoke for each. None held is still `not-held`. A role target keeps its one-claim path. The
result adds `threads: N` for a threaded target.
- A chief `role.end` with a reason passes `#protectedEnd` (row 139) and ends them all.
- A `role.revoke` with a decision the human resolved through the CLI passes `#protectedEnd` and ends
them all. An arbiter can't resolve that decision at all: `#hardWait` makes a revoke of an
`END_PROTECTED` target a hard wait, which goes to the human whoever raised it.
- The chief's own run's end stays refused (`a.role === s.role`).
**The null and undefined holder run.** The raise records `holderRun` as
`#holder(...)?.holder_run ?? null`, which is null for a threaded target. The check compared it with
`#holder(...)?.holder_run`, undefined, and answered `decision-mismatch`. Both sides now use `?? null`.
*For Dewey.* For a role target the decision binds to the holder incarnation at the raise. For a
threaded target there's no single incarnation, so it binds to nothing beyond the target, the
action, the raiser and the class. It's still single-use (`#spent`) and still human-resolved through
the CLI. If you want it bound tighter, the raise could record the set of held thread runs and the
check could require the same set. I think that refuses a legitimate revoke whenever a thread idles
out between raise and use, which is the common case.
**What the run does.** The runner polls every 2 s. Its next bus call gets `not-holder`, which is in
`SESSION_GONE`, and it exits 22. A turn already running finishes first, the same as `role.end` of a
role today. The launcher starts a new run only for a thread with pending messages or an open, so the
assistant comes back only when the human writes to it again. Removing the instance from the
organization file is out of scope.
**The human, Console.** A new human-only verb, `assistant.stop {instance}`.
- It checks `#human(s)` and that the instance is threaded.
- It revokes the current claim of every thread of that instance the human owns (REQ-USER-2: a user
reaches only their own threads), with `by` set to the human and reason `stopped by the human`.
None held is `not-held`. A known instance that isn't threaded is a new code, `not-assistant`
(webui status 400); an unknown one is `unknown-role`.
- It returns the stopped runs.
The Console route is POST `/api/assistant-stop` with `{instance}` and the same boundary as `/api/name`.
The button sits in the Assistant section. I chose per-instance over per-run because the Console
lists no threads or thread runs (`agents` reads `role_claims` only), and stopping the assistant is
what Q169's "remove" means. `mosaic stop <run>` still stops one run from the CLI.
The human can't use `role.revoke` itself: `#consumeAuthority` starts with `#agent`, which refuses a
human.
## 5. Text
- `packages/harness/src/bundle.mjs:112` and the `end_run` description (`tools.mjs:166`) say the
chief can't end the assistant's run. That's false after this row. Row 149's brief leaves the
`end_run` text in `bundle.mjs` to row 155.
- `roles/chief-of-staff.md:30-35`: the chief ends the assistant with `end_run`, configures it with
`change_organization` (`assistants`, with a revision from the human until row 134), and names
instances with `set_name`. Raising `policy.change` for those goes.
## What else changed
- `packages/cli/src/limits.mjs`: the usage line names `assistants`; `limits.test.mjs` pins it.
- `packages/webui/src/bus.mjs`: statuses for the codes the new routes and `assistants` can return
(`organization-not-assistant`, `organization-definition-refused`, `unknown-model`,
`name-is-instance`, `name-is-role` and `not-assistant` 400; `name-taken` and `not-held` 409).
- `roles/chief-of-staff.md`: the contract text names no person, because
`business/tests/chief-of-staff.test.mjs` refuses `Jason` in it.
- Re-pinned tests: `chief-of-staff.test.mjs:174`, `threads.test.mjs:396`, the bus and business
`hard-waits.test.mjs`, `tools.test.mjs`, `bundle.test.mjs`, `runner.test.mjs` (its fixture's
chief gets `instance.name`), `project-runtime.test.mjs` (the broker's import line) and webui
`shell.test.mjs` (the write-boundary list, after the rebase; see "Rebase onto 99d2b49c9").
## Tests (brief item 5)
| Brief item | Where |
|---|---|
| Human CLI and Console changes of vars, tracker and definition succeed and log the human | business `launch-settings.test.mjs`, cli `limits.test.mjs`, webui `settings-write.test.mjs` |
| Chief's change succeeds and logs the chief | bus `chief-of-staff.test.mjs` |
| Another role's change is refused | bus `chief-of-staff.test.mjs` |
| Definition change without protection refused; another instance given an END_PROTECTED definition refused | business `launch-settings.test.mjs` |
| Chief-raised revoke of the assistant is a hard wait to the human, no arbiter resolves it | bus `hard-waits.test.mjs` |
| Stale revision refused | business and bus |
| Human's and chief's name changes succeed, another role's refused | bus `chief-of-staff.test.mjs` and row 141's `names.test.mjs`, harness `tools.test.mjs` (`set_name`), webui `assistant.test.mjs` |
| Chief's end stops every assistant thread run; its own end stays refused | bus `threads.test.mjs` |
| Human-resolved revoke stops them; arbiter-resolved stays refused | bus `threads.test.mjs` |
| Console stop route ends an assistant run | bus `threads.test.mjs` (verb), webui `assistant.test.mjs` (route) |
Plus the re-pins above, a `set_name` tool test, and a browser test for the Assistant section in
`settings-form.test.mjs`.
### Single-file runs (sage-0720)
`~/darkwing-scratch/r155/run-touched.sh`: one `node --test` per file from its package directory,
behind `busy.sh`, TMPDIR `~/darkwing-scratch/r155/tmp`, logs teed. 15 files: the 13 this row
touches (12 modified, `assistant.test.mjs` new) plus business `chief-of-staff.test.mjs` and bus
`names.test.mjs`. No Docker, no full suites, no mutants.
- Run 1, 2026-10-18T15:34:50Z to 15:35:18Z: 13 green, 2 red.
- bus `threads.test.mjs`, 14 of 15. My test bound an inside-agent human session to show it can't
stop runs. `bindHuman` refuses that at the bind with human-required (pinned since
`broker.test.mjs:132`), so the session never reaches the verb. The test now asserts the bind
refusal and checks the verb with pm, chief and a. No broker change.
- webui `settings-form.test.mjs`, the Q169 test. `render` always reads `/api/settings` again,
so after a save the page took its revision from the fixture server, which only ever returned
revision a. The next post carried a where the test expected b. The page is right: a real server
returns the saved revision. The test's fetch mock now serves the saved state on reads after a
save. No `bus.js` change.
- Run 2, 15:38:33Z to 15:39:01Z: all 15 green, 167 pass, 0 fail. Logs:
`~/darkwing-scratch/r155/out/20261018T153833Z/`. Sweep found 0 leftovers.
That run covers the fixtures' `project.create` and `thread.create` calls, the chief's
`decision.raise` of a revoke with no grant, the decision-consumed check order and the browser
test's timing, which were unproven before it.
## Overlaps
- Row 149 (Dewey, in review) changes `roles/chief-of-staff.md` Authority text and the `raise_decision`
tool in `tools.mjs`. I'll rebase onto its landing before the in-review move and run every touched
test file singly.
- Row 156 (Sage, queued after 149) touches `broker.mjs` and the role md files. Whichever lands second
rebases.
- Row 124 (Rocko) does the Settings readout and schema 3h. The Settings view is shared; I'll check
its diff at rebase.
## Gate
Dewey and Filbert approve on #1649 naming the candidate manifest. Mos's decision 123 go on the
`public/` diff. Bus, business, harness, cli and webui suites and every `scripts/test-*.sh` green.
@@ -0,0 +1,74 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 64a7d6674..1e0be849d 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -621,16 +621,67 @@
}
return `<section class="panel" aria-labelledby="token-usage-title"><h2 id="token-usage-title">Token usage</h2><p>Best reading of session-editable transcripts, not proof. Unknown turns may have used more tokens. Account is unassigned until account tracking is configured.</p><div class="table-wrap"><table id="token-usage-table"><thead><tr><th scope="col">Subject</th><th scope="col">Input</th><th scope="col">Output</th><th scope="col">Cache write</th><th scope="col">Cache read</th><th scope="col">Unknown turns</th></tr></thead><tbody>${rows.join('')}</tbody></table></div></section>`;
}
+ // Row 155 (#1649, Q169): each assistant instance's settings, its seat name and a stop. A save
+ // sends only the fields that changed, with the read's revision. Drafts survive polling and
+ // refusals as in the limits form, and nothing retries.
+ let assistantSnapshot=null, assistantDrafts=Object.create(null), nameDrafts=Object.create(null), assistantBusy=false, assistantReceipts=Object.create(null);
+ const asShown=(entry,field)=>field==='definition'?entry.definition??'':entry[field]==null?'':JSON.stringify(entry[field],null,2);
+ function assistantSection(value,names){
+ if(!value||value.refused||!value.assistants||!Object.keys(value.assistants).length)return '';
+ if(!Object.keys(assistantDrafts).length)assistantSnapshot=value;
+ const v=assistantSnapshot, disabled=!canSend||out()||assistantBusy?' disabled':'';
+ const named=names?Object.fromEntries(names.map(r=>[r.instance,r.name])):null;
+ const forms=Object.entries(v.assistants).map(([instance,entry])=>{
+ const d=assistantDrafts[instance]||{}, val=f=>Object.hasOwn(d,f)?d[f]:asShown(entry,f), k=esc(instance);
+ const name=Object.hasOwn(nameDrafts,instance)?nameDrafts[instance]:named?.[instance]??'';
+ return `<form class="assistant-form" data-instance="${k}"><fieldset><legend>${txt(instance)}</legend><div class="filters"><label>Seat name <input data-assistant-field="name" value="${esc(name)}" maxlength="64"${disabled}></label> <button type="button" data-assistant-name="${k}"${disabled}>Save name</button>${named?'':' <span class="small muted">The current name could not be read.</span>'}</div><label>Definition <input data-assistant-field="definition" value="${esc(val('definition'))}" maxlength="64"${disabled}></label><label>Vars (JSON) <textarea data-assistant-field="vars" rows="4"${disabled}>${esc(val('vars'))}</textarea></label><label>Tracker (JSON, empty removes it) <textarea data-assistant-field="tracker" rows="4"${disabled}>${esc(val('tracker'))}</textarea></label><button type="submit"${disabled}>Save assistant settings</button> <button type="button" data-assistant-stop="${k}"${disabled}>Stop its runs</button><p role="status" data-assistant-receipt="${k}">${txt(assistantReceipts[instance]||'')}</p></fieldset></form>`;
+ }).join('');
+ return `<section class="panel" aria-labelledby="assistant-title"><h2 id="assistant-title">Assistant</h2><p>The definition stays an assistant one. Holder and credentials stay hand edits. Stop ends the runs of your threads; the assistant starts again when you next write to it.</p>${forms}<p class="small muted">${canSend?'Changes are checked and logged with your identity.':'Changes require signed-in send mode.'}</p></section>`;
+ }
+ function assistantPayload(instance,form){
+ const entry=assistantSnapshot.assistants[instance], patch={};
+ for(const f of ['definition','vars','tracker']){
+ const text=form.querySelector(`[data-assistant-field="${f}"]`).value, what=f==='vars'?'Vars':'The tracker';
+ if(text===asShown(entry,f))continue;
+ if(f==='definition'){patch.definition=text.trim();continue;}
+ if(f==='tracker'&&text.trim()===''){patch.tracker=null;continue;}
+ let parsed;try{parsed=JSON.parse(text);}catch{throw Error(`${what} must be JSON.`);}
+ if(!parsed||typeof parsed!=='object'||Array.isArray(parsed))throw Error(`${what} must be a JSON object.`);
+ patch[f]=parsed;
+ }
+ if(!Object.keys(patch).length)throw Error('Nothing changed.');
+ return {revision:assistantSnapshot.revision,assistants:{[instance]:patch}};
+ }
+ const unknownAssistant='Outcome unknown. Read current settings before trying again. Draft kept.';
+ async function assistantPost(url,payload,instance,ok){
+ assistantBusy=true;assistantReceipts[instance]='';render(false);
+ try{const response=await fetch(url,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload),cache:'no-store',redirect:'manual'});if(response.type==='opaqueredirect'){assistantReceipts[instance]='Sign in again. Draft kept.';signOut();}else{const result=await response.json();assistantReceipts[instance]=response.ok?ok(result):result?.sent==='unknown'?unknownAssistant:`${result?.message||'Refused.'} Draft kept.`;}}
+ catch{assistantReceipts[instance]=unknownAssistant;}finally{assistantBusy=false;if(!out())render(false);}
+ }
+ const assistantForm=el=>el?.closest?.('.assistant-form');
+ for(const kind of ['input','change'])document.addEventListener(kind,e=>{const form=assistantForm(e.target);if(!form)return;const instance=form.dataset.instance,field=e.target.dataset.assistantField;if(field==='name')nameDrafts[instance]=e.target.value;else if(field)assistantDrafts[instance]={...Object.fromEntries(['definition','vars','tracker'].map(f=>[f,form.querySelector(`[data-assistant-field="${f}"]`).value]))};});
+ document.addEventListener('submit',async e=>{
+ const form=assistantForm(e.target);if(!form||e.target!==form)return;e.preventDefault();if(!canSend||out()||assistantBusy)return;const instance=form.dataset.instance;let payload;
+ try{payload=assistantPayload(instance,form);}catch(err){assistantReceipts[instance]=err.message;render(false);return;}
+ await assistantPost('/api/settings',payload,instance,result=>{if(!/^[a-f0-9]{64}$/.test(result?.revision??''))return unknownAssistant;delete assistantDrafts[instance];assistantSnapshot=result;const prior=last.get('api:settings');if(prior)last.set('api:settings',{...prior,launch:result});return 'Assistant settings saved.';});
+ });
+ document.addEventListener('click',async e=>{
+ const form=assistantForm(e.target);if(!form||!canSend||out()||assistantBusy)return;const instance=form.dataset.instance;
+ if(e.target.dataset.assistantName!==undefined){const name=form.querySelector('[data-assistant-field="name"]').value.trim();
+ await assistantPost('/api/name',{instance,name},instance,result=>{if(typeof result?.name!=='string')return unknownAssistant;delete nameDrafts[instance];const prior=last.get('names');if(prior)last.set('names',{...prior,rows:[...prior.rows.filter(r=>r.instance!==result.instance),{instance:result.instance,name:result.name,at:result.at}]});return `Seat name set to ${result.name}.`;});}
+ else if(e.target.dataset.assistantStop!==undefined)await assistantPost('/api/assistant-stop',{instance},instance,result=>Array.isArray(result?.stopped)?`Stopped ${plural(result.stopped.length,'run')}.`:unknownAssistant);
+ });
async function settingsView(get) {
const s = await get.api('settings'), n = s.notifier || {};
- let usage;
+ let usage, names = null;
try { [usage] = await get('usage'); } catch (err) { if (err.status === 403 || err.code === 'signed-out') throw err; }
+ try { names = await get('names'); } catch { /* the section says the name could not be read */ }
let limitRows = [], limitsRefused = false;
try { limitRows = await get('limits'); } catch (err) { if (err.status === 403 || err.code === 'signed-out') throw err; limitsRefused = true; }
const notify = typeof n.binding === 'string'
? `<dl class="kv"><dt>Binding</dt><dd><code>${txt(n.binding)}</code></dd></dl><p class="small muted">The binding name only. Its channels, server and user ids stay in the binding file.</p>`
: empty('No notifier binding to show.', `${n.refused ? `<code>${txt(n.refused)}</code>: ` : ''}${txt(n.message || 'the notifier settings could not be read')}`);
- return `${head('Settings', `${readLine()} Agent limits and appearance.`)}${appearance()}${launchForm(s.launch)}${tokenLimits(limitRows, limitsRefused)}${tokenUsage(usage)}
+ return `${head('Settings', `${readLine()} Agent limits and appearance.`)}${appearance()}${launchForm(s.launch)}${assistantSection(s.launch, names)}${tokenLimits(limitRows, limitsRefused)}${tokenUsage(usage)}
<section aria-labelledby="st-notify"><h2 id="st-notify">Notifications</h2>${notify}</section>
<section aria-labelledby="st-about" class="panel"><h2 id="st-about">About</h2><dl class="kv"><dt>Address</dt><dd><code>${txt(s.address) || 'unknown'}</code></dd><dt>Release</dt><dd>${s.release ? `<code>${txt(s.release)}</code>` : '<span class="muted">unknown: no RELEASE file was read</span>'}</dd><dt>Business</dt><dd>${txt(s.business) || none}</dd><dt>Board</dt><dd><code>${txt(s.board) || 'unknown'}</code></dd></dl></section>`;
}
@@ -0,0 +1,85 @@
# Follow-up row brief: Settings page reads and snapshots after Q169 (row 155 review notes)
Draft by Darkwing for Sage (sage-0899), from Mos's two `public/bus.js` notes on row 155, which
Mos (mos-0816) and Filbert (#1649 comment 29010) ruled non-blocking, plus a third instance I
found while checking them, and Dewey's F1 (#1649 comment 29012, sage-0903; withdrawn in 29014). Line numbers are `packages/webui/src/public/bus.js` in row 155's
candidate (manifest 949873cf…, base 7ce156259). This row starts after row 155 lands and rebases
on that landing.
Suggested gate: Filbert approves the candidate manifest; Mos's decision 123 go on the
`public/` diff; webui suite and every `scripts/test-*.sh` green on Sage's gate. Suggested owner:
Darkwing, reviewer Filbert, gate owner Sage.
Neither item loses data today. (a) shows an empty name field where it should show the sign-in
state. (b) makes the next save fail with `revision-conflict`, which keeps the draft and tells
the human to reread. The fix makes the page match the other reads and stop sending a revision
it already knows is old.
## Items
1. (a) The names read rethrows like the others. `settingsView` (:674) reads `usage` and
`limits` with a catch that rethrows a 403 or `signed-out` and keeps any other failure local.
The `names` read at :678 catches everything, so a signed-out or forbidden answer becomes "the
name could not be read". Give it the same catch: rethrow 403 and `signed-out`, and keep the
empty field and its text for any other failure. Test (webui `settings-form` or
`assistant`): a fixture whose `names` read answers 403 gets the same page as a 403 on
`usage`, and a fixture whose `names` read fails another way still renders the Assistant
section with "could not be read".
2. (b) One settings snapshot. The page holds the launch settings twice: `settingsSnapshot`
(:559) for the launch form and the token-limit reset, and `assistantSnapshot` (:627) for the
Assistant section. Each save refreshes only its own:
- the launch form save (:587) sets `settingsSnapshot`, not `assistantSnapshot`;
- the assistant save (:666) sets `assistantSnapshot`, not `settingsSnapshot`;
- row 125's reset (:609) sets `settingsSnapshot` when no launch draft is open, never
`assistantSnapshot`.
So a save in one form while the other has an open draft leaves the other with the old
revision, and its next save is refused. The render paths re-snapshot only when their own
form has no draft (:563, :631), which is what keeps the stale copy alive.
Fix: one snapshot for both forms. Every successful save or reset sets it from the answer,
and drafts stay per form (`settingsInputs`, `assistantDrafts`, `nameDrafts`), so an open
draft keeps its typed values over the new snapshot. The launch form payload (:577) already
builds from the snapshot plus its inputs; the assistant payload (:642-653) does the same with
only the changed fields. The reload button (:584) clears the one snapshot. Test, one per
path, in webui `settings-form` (the launch and assistant saves) and `limits-browser` (the
reset):
- with a limits draft open, save the assistant, then submit the limits draft: its posted
revision is the one the assistant save returned, and the typed limit is still in the field;
- with an assistant draft open, save the launch form, then submit the assistant draft: its
posted revision is the one the launch save returned;
- with an assistant draft open, reset a token limit, then submit the assistant draft: its
posted revision is the one the reset returned.
Each test fails on row 155's candidate, where the second post carries the old revision.
3. (F1) Null bodies on the Console write routes. Dewey's F1: `Object.keys(value)` throws on a
JSON `null` in `seatSend` (`packages/webui/src/serve.mjs:241`, the new `/api/name` and
`/api/assistant-stop` routes), `settingsSend` (:203) and `projectFileSend` (:276), and an
async handler throw with no `unhandledRejection` handler ends the Console.
I checked it against the candidate, and that throw can't happen there. Each handler reads
its body through `body()` (:35, from ea00ec66d) inside a `try`, and `body()` parses the JSON
and throws `body must be a JSON object` for `null`, an array or any non-object (:45-46). The
`catch` answers 400 `invalid-request` before the `Object.keys` line runs. Two of the four
already have passing tests for it: `settings-write.test.mjs:17` posts `null` to
`/api/settings` and expects 400, and `project-file.test.mjs:76` ("a body that is JSON but not
an object is refused 400, not thrown on") posts `null`, `[]`, `5`, `"text"` and `true` and
expects 400 with "JSON object". The two `seatSend` routes have no such test.
Dewey withdrew F1 in #1649 comment 29014 (rev 1856) and agreed the item is tests only, with
no local check beyond `body()`. Test, one per `seatSend` route in `assistant.test.mjs`:
posting `null` to `/api/name` and to `/api/assistant-stop` gets 400 `invalid-request`, no
write call is made, and a following valid request still gets an answer. The settings and
project file routes keep their existing tests. Both new tests pass on row 155's candidate;
they pin `body()`'s check on the two routes that lack one.
The shell write-boundary test (`shell.test.mjs`) pins every POST `fetch` and every
`assistantPost(` route in `bus.js`. None of the three adds or changes a write, so its list and
count stay as row 155 leaves them; if the fix touches a `fetch(` line, that test changes with it.
## Out of scope
Server-side settings handling, the routes, and any change to what a save sends beyond the
revision.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,31 @@
685f1ffd74fb5dc09eb30f2f4e8f9d8ef524b4c11956520322df59b4cf71971f docs/TOOLS.md
00e3097588604ee3dac5159bcbb430aba5ed2380ae3eea94eb2ad4c58ac47d58 packages/bus/README.md
b4def5241ba1dc2655bfcd014d6053797c89c79f23a536d7591573e4977df6d9 packages/bus/src/broker.mjs
a898b99ef887934655ed89dd7f39f1844a390eb71c5f06f77e9cdc121ee1be69 packages/bus/tests/chief-of-staff.test.mjs
9c9ec893dfb4b4afe6e3131584a4a59b57f8704020efd51347b3d82fde46e5a3 packages/bus/tests/hard-waits.test.mjs
536194fabad0608f75f7ee594df0612513766631511197a149507df02cd615d9 packages/bus/tests/project-runtime.test.mjs
3d3006280a0cdf5a3df41e6ffcf02a716fcd9a1f98547597cacea4c4bc9c5133 packages/bus/tests/threads.test.mjs
9cb665ed7f9b2acc9ea9738e78b63bf27d69ffb731d14b363c3cd92b23a1441a packages/business/README.md
f3085de0a6f7ced053e99bc860d05714189ded1700abab962bfc7200c41fc401 packages/business/src/launch-settings.mjs
bfc317915a776cbac15c190740b74b808bb03a9d8d88b9f11e054ae4fcb0b164 packages/business/src/vocabulary.mjs
174f1745fdf0120d79b09e7834e07021a41528443e1e30098fa947681d971510 packages/business/tests/hard-waits.test.mjs
303bba96b97ba287bd9c0164451d0007ce8682bb74235a7d613292cf3db276cc packages/business/tests/launch-settings.test.mjs
353ef3302041f9d042bc75c85bf50e17a1239d14741c085fd8b11e3dc6c60dcb packages/cli/README.md
696cbfcd6646441d82cdcb46ebe797f890cef6059cf885e8a8a418489ba05398 packages/cli/src/limits.mjs
1d3d4b0c2f93930011648c2ec8bf748d68681f374ade3c3f1789531f53393e8e packages/cli/tests/limits.test.mjs
4c80fa8aea72854c2349cc54767b2b530bf0063c7cdb9869f2401266d41688c2 packages/harness/README.md
899664fc1b9980c6d2718e18fd3829bf8aa1ef49454779d2c99b24684f834132 packages/harness/src/bundle.mjs
73d1db38e677be659a5455bb25f246aca2ab4187967b81543e63c994846e7186 packages/harness/src/tools.mjs
847768146342afa5fed673d30caa7e6f421c895167e7f654941968bf0a8a8dd9 packages/harness/tests/bundle.test.mjs
16b08c3bc7cc1fd9992d02134227ff51fff79a9977989c50ddd37afe00fec900 packages/harness/tests/runner.test.mjs
1a823bb1581fc76e214e1dd772ed1ce411e0741f0391b25b5b21141d7029e9bd packages/harness/tests/tools.test.mjs
aa60183c3c897299ac40e36413b3c02ecaf660f10bc6121757278382c28ecb55 packages/webui/README.md
d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241 packages/webui/src/bus.mjs
da66a46b10d15fb7f128c4f60951e9a45ef3c82c858b6fb30b365149b0c022fa packages/webui/src/cli.mjs
51bf72804c85110bdeff2a94cf0178307dbc6ca874bfe4c4f9a53150ead9f093 packages/webui/src/public/bus.js
eedebc55889b43a14339dd299c1ea8e32e46996018a008ef0e01560140fee8c0 packages/webui/src/serve.mjs
4e921ce653cc387191778b9c6b214ba81ef79a64e37ca655371ad95f067fcfab packages/webui/tests/assistant.test.mjs
2809d81c3dac4495bc58f3fc3b905bfb5ab6908fb92b465fbccd13f276ef64a5 packages/webui/tests/settings-form.test.mjs
52413621f80273cf88742448035ae5e9602b780c7a356008f791f170f8d55891 packages/webui/tests/settings-write.test.mjs
698ef27cebd404b5390bd5298ab271245c386eb3c8de11810eb3e9dc9336cd14 packages/webui/tests/shell.test.mjs
823d540726937ed40b09e11cf92a4cd0c04689506c820dcccfbc18e69ec36e70 roles/chief-of-staff.md
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,31 @@
e8fa08bf668d83aa9d30c795b409aeaea292422ef53c8e96c9aba7504c69de86 docs/TOOLS.md
00e3097588604ee3dac5159bcbb430aba5ed2380ae3eea94eb2ad4c58ac47d58 packages/bus/README.md
b4def5241ba1dc2655bfcd014d6053797c89c79f23a536d7591573e4977df6d9 packages/bus/src/broker.mjs
a898b99ef887934655ed89dd7f39f1844a390eb71c5f06f77e9cdc121ee1be69 packages/bus/tests/chief-of-staff.test.mjs
9c9ec893dfb4b4afe6e3131584a4a59b57f8704020efd51347b3d82fde46e5a3 packages/bus/tests/hard-waits.test.mjs
536194fabad0608f75f7ee594df0612513766631511197a149507df02cd615d9 packages/bus/tests/project-runtime.test.mjs
3d3006280a0cdf5a3df41e6ffcf02a716fcd9a1f98547597cacea4c4bc9c5133 packages/bus/tests/threads.test.mjs
9cb665ed7f9b2acc9ea9738e78b63bf27d69ffb731d14b363c3cd92b23a1441a packages/business/README.md
f3085de0a6f7ced053e99bc860d05714189ded1700abab962bfc7200c41fc401 packages/business/src/launch-settings.mjs
bfc317915a776cbac15c190740b74b808bb03a9d8d88b9f11e054ae4fcb0b164 packages/business/src/vocabulary.mjs
174f1745fdf0120d79b09e7834e07021a41528443e1e30098fa947681d971510 packages/business/tests/hard-waits.test.mjs
303bba96b97ba287bd9c0164451d0007ce8682bb74235a7d613292cf3db276cc packages/business/tests/launch-settings.test.mjs
7943058d0f97cde05f921fe8fe5e2bfac2198ea7c56476ba52e36ad735d3fd0f packages/cli/README.md
696cbfcd6646441d82cdcb46ebe797f890cef6059cf885e8a8a418489ba05398 packages/cli/src/limits.mjs
1d3d4b0c2f93930011648c2ec8bf748d68681f374ade3c3f1789531f53393e8e packages/cli/tests/limits.test.mjs
4c80fa8aea72854c2349cc54767b2b530bf0063c7cdb9869f2401266d41688c2 packages/harness/README.md
899664fc1b9980c6d2718e18fd3829bf8aa1ef49454779d2c99b24684f834132 packages/harness/src/bundle.mjs
73d1db38e677be659a5455bb25f246aca2ab4187967b81543e63c994846e7186 packages/harness/src/tools.mjs
847768146342afa5fed673d30caa7e6f421c895167e7f654941968bf0a8a8dd9 packages/harness/tests/bundle.test.mjs
16b08c3bc7cc1fd9992d02134227ff51fff79a9977989c50ddd37afe00fec900 packages/harness/tests/runner.test.mjs
1a823bb1581fc76e214e1dd772ed1ce411e0741f0391b25b5b21141d7029e9bd packages/harness/tests/tools.test.mjs
aa60183c3c897299ac40e36413b3c02ecaf660f10bc6121757278382c28ecb55 packages/webui/README.md
d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241 packages/webui/src/bus.mjs
da66a46b10d15fb7f128c4f60951e9a45ef3c82c858b6fb30b365149b0c022fa packages/webui/src/cli.mjs
51bf72804c85110bdeff2a94cf0178307dbc6ca874bfe4c4f9a53150ead9f093 packages/webui/src/public/bus.js
eedebc55889b43a14339dd299c1ea8e32e46996018a008ef0e01560140fee8c0 packages/webui/src/serve.mjs
4e921ce653cc387191778b9c6b214ba81ef79a64e37ca655371ad95f067fcfab packages/webui/tests/assistant.test.mjs
2809d81c3dac4495bc58f3fc3b905bfb5ab6908fb92b465fbccd13f276ef64a5 packages/webui/tests/settings-form.test.mjs
52413621f80273cf88742448035ae5e9602b780c7a356008f791f170f8d55891 packages/webui/tests/settings-write.test.mjs
698ef27cebd404b5390bd5298ab271245c386eb3c8de11810eb3e9dc9336cd14 packages/webui/tests/shell.test.mjs
823d540726937ed40b09e11cf92a4cd0c04689506c820dcccfbc18e69ec36e70 roles/chief-of-staff.md
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,32 @@
e8fa08bf668d83aa9d30c795b409aeaea292422ef53c8e96c9aba7504c69de86 docs/TOOLS.md
00e3097588604ee3dac5159bcbb430aba5ed2380ae3eea94eb2ad4c58ac47d58 packages/bus/README.md
b4def5241ba1dc2655bfcd014d6053797c89c79f23a536d7591573e4977df6d9 packages/bus/src/broker.mjs
3e6d794b6831fc162886079ec28d275ad69d4aaf84bad80a88e28afb9aee2619 packages/bus/tests/channel-proof.test.mjs
a898b99ef887934655ed89dd7f39f1844a390eb71c5f06f77e9cdc121ee1be69 packages/bus/tests/chief-of-staff.test.mjs
9c9ec893dfb4b4afe6e3131584a4a59b57f8704020efd51347b3d82fde46e5a3 packages/bus/tests/hard-waits.test.mjs
536194fabad0608f75f7ee594df0612513766631511197a149507df02cd615d9 packages/bus/tests/project-runtime.test.mjs
3d3006280a0cdf5a3df41e6ffcf02a716fcd9a1f98547597cacea4c4bc9c5133 packages/bus/tests/threads.test.mjs
9cb665ed7f9b2acc9ea9738e78b63bf27d69ffb731d14b363c3cd92b23a1441a packages/business/README.md
f3085de0a6f7ced053e99bc860d05714189ded1700abab962bfc7200c41fc401 packages/business/src/launch-settings.mjs
bfc317915a776cbac15c190740b74b808bb03a9d8d88b9f11e054ae4fcb0b164 packages/business/src/vocabulary.mjs
174f1745fdf0120d79b09e7834e07021a41528443e1e30098fa947681d971510 packages/business/tests/hard-waits.test.mjs
303bba96b97ba287bd9c0164451d0007ce8682bb74235a7d613292cf3db276cc packages/business/tests/launch-settings.test.mjs
7943058d0f97cde05f921fe8fe5e2bfac2198ea7c56476ba52e36ad735d3fd0f packages/cli/README.md
696cbfcd6646441d82cdcb46ebe797f890cef6059cf885e8a8a418489ba05398 packages/cli/src/limits.mjs
1d3d4b0c2f93930011648c2ec8bf748d68681f374ade3c3f1789531f53393e8e packages/cli/tests/limits.test.mjs
4c80fa8aea72854c2349cc54767b2b530bf0063c7cdb9869f2401266d41688c2 packages/harness/README.md
899664fc1b9980c6d2718e18fd3829bf8aa1ef49454779d2c99b24684f834132 packages/harness/src/bundle.mjs
73d1db38e677be659a5455bb25f246aca2ab4187967b81543e63c994846e7186 packages/harness/src/tools.mjs
847768146342afa5fed673d30caa7e6f421c895167e7f654941968bf0a8a8dd9 packages/harness/tests/bundle.test.mjs
16b08c3bc7cc1fd9992d02134227ff51fff79a9977989c50ddd37afe00fec900 packages/harness/tests/runner.test.mjs
1a823bb1581fc76e214e1dd772ed1ce411e0741f0391b25b5b21141d7029e9bd packages/harness/tests/tools.test.mjs
aa60183c3c897299ac40e36413b3c02ecaf660f10bc6121757278382c28ecb55 packages/webui/README.md
d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241 packages/webui/src/bus.mjs
da66a46b10d15fb7f128c4f60951e9a45ef3c82c858b6fb30b365149b0c022fa packages/webui/src/cli.mjs
51bf72804c85110bdeff2a94cf0178307dbc6ca874bfe4c4f9a53150ead9f093 packages/webui/src/public/bus.js
eedebc55889b43a14339dd299c1ea8e32e46996018a008ef0e01560140fee8c0 packages/webui/src/serve.mjs
4e921ce653cc387191778b9c6b214ba81ef79a64e37ca655371ad95f067fcfab packages/webui/tests/assistant.test.mjs
2809d81c3dac4495bc58f3fc3b905bfb5ab6908fb92b465fbccd13f276ef64a5 packages/webui/tests/settings-form.test.mjs
52413621f80273cf88742448035ae5e9602b780c7a356008f791f170f8d55891 packages/webui/tests/settings-write.test.mjs
698ef27cebd404b5390bd5298ab271245c386eb3c8de11810eb3e9dc9336cd14 packages/webui/tests/shell.test.mjs
823d540726937ed40b09e11cf92a4cd0c04689506c820dcccfbc18e69ec36e70 roles/chief-of-staff.md