@mmerterden/multi-agent-pipeline 20.30.0 → 20.33.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.
- package/CHANGELOG.md +80 -0
- package/README.md +45 -0
- package/README.tr.md +59 -0
- package/docs/facts.json +3 -3
- package/install/_unattended-profile.mjs +61 -0
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +9 -2
- package/install/index.mjs +10 -1
- package/install/templates/claude-hooks.json +12 -1
- package/manifest.json +123 -82
- package/package.json +2 -1
- package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
- package/pipeline/commands/multi-agent/review/SKILL.md +4 -3
- package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
- package/pipeline/contract/CHANGELOG.md +46 -0
- package/pipeline/contract/README.md +22 -8
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/launch-plan.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +6 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
- package/pipeline/contract/fixtures/runs-running.json +6 -1
- package/pipeline/contract/manifest.json +153 -19
- package/pipeline/contract/types/index.d.ts +304 -6
- package/pipeline/lib/bg-launch.mjs +106 -0
- package/pipeline/lib/code-digest.mjs +49 -0
- package/pipeline/lib/credential-resolve.mjs +42 -2
- package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
- package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
- package/pipeline/multi-agent-refs/help/en.md +2 -2
- package/pipeline/multi-agent-refs/help/tr.md +2 -2
- package/pipeline/multi-agent-refs/phases/modes.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +16 -10
- package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
- package/pipeline/office/NOTICE.md +4 -1
- package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
- package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
- package/pipeline/office/assets/COUNTER_SINK.png +0 -0
- package/pipeline/office/assets/DEVICE_STAND.png +0 -0
- package/pipeline/office/assets/FILING_CABINET.png +0 -0
- package/pipeline/office/assets/FRIDGE.png +0 -0
- package/pipeline/office/assets/MAILBOX.png +0 -0
- package/pipeline/office/assets/MOP_BUCKET.png +0 -0
- package/pipeline/office/assets/PRINTER.png +0 -0
- package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
- package/pipeline/office/assets/TOILET_STALL.png +0 -0
- package/pipeline/office/assets/VENDING.png +0 -0
- package/pipeline/office/assets/WASHBASIN.png +0 -0
- package/pipeline/office/assets/WATER_COOLER.png +0 -0
- package/pipeline/office/index.html +3996 -884
- package/pipeline/office/toolkit.json +3 -0
- package/pipeline/schemas/activity.schema.json +188 -4
- package/pipeline/schemas/agent-state.schema.json +493 -115
- package/pipeline/schemas/contract-error.schema.json +12 -1
- package/pipeline/schemas/job-change.schema.json +72 -0
- package/pipeline/schemas/job-request.schema.json +38 -0
- package/pipeline/schemas/launch.json +4 -4
- package/pipeline/schemas/office-credentials.schema.json +71 -0
- package/pipeline/schemas/office-jobs.json +253 -0
- package/pipeline/schemas/office-jobs.schema.json +62 -0
- package/pipeline/schemas/office-memory.schema.json +105 -0
- package/pipeline/schemas/office-settings-request.schema.json +30 -0
- package/pipeline/schemas/office-settings.schema.json +67 -0
- package/pipeline/schemas/phases.json +10 -2
- package/pipeline/schemas/phases.schema.json +86 -19
- package/pipeline/schemas/prs.schema.json +5 -0
- package/pipeline/schemas/prune-logs-request.schema.json +26 -0
- package/pipeline/schemas/prune-logs.schema.json +52 -0
- package/pipeline/schemas/repo-change.schema.json +54 -0
- package/pipeline/schemas/repo-request.schema.json +29 -0
- package/pipeline/schemas/repos.schema.json +49 -1
- package/pipeline/schemas/runs-index.schema.json +257 -46
- package/pipeline/schemas/system-status.schema.json +111 -0
- package/pipeline/schemas/tools.schema.json +5 -0
- package/pipeline/schemas/unattended-profile.json +7 -2
- package/pipeline/scripts/_active-run.mjs +2 -1
- package/pipeline/scripts/_spawn-async.mjs +9 -5
- package/pipeline/scripts/activity-snapshot.mjs +219 -23
- package/pipeline/scripts/autopilot-runner.mjs +7 -5
- package/pipeline/scripts/contract-server.mjs +551 -25
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
- package/pipeline/scripts/job-launch.mjs +477 -0
- package/pipeline/scripts/launch-request.mjs +94 -9
- package/pipeline/scripts/office-build.mjs +148 -11
- package/pipeline/scripts/office-control.mjs +34 -7
- package/pipeline/scripts/office-credentials.mjs +106 -0
- package/pipeline/scripts/office-memory.mjs +200 -0
- package/pipeline/scripts/office-prune.mjs +125 -0
- package/pipeline/scripts/office-settings.mjs +145 -0
- package/pipeline/scripts/phase-tracker.sh +12 -5
- package/pipeline/scripts/prune-logs.sh +53 -2
- package/pipeline/scripts/runs-index.mjs +62 -2
- package/pipeline/scripts/system-status.mjs +196 -0
- package/pipeline/scripts/task-mirror.mjs +150 -0
- package/pipeline/scripts/task-mirror.sh +20 -0
- package/pipeline/scripts/toolkit-tools.mjs +33 -7
- package/pipeline/scripts/transcript-index.mjs +4 -1
- package/pipeline/scripts/work-items.mjs +191 -22
- package/pipeline/skills/.skill-manifest.json +6 -6
- package/pipeline/skills/.skills-index.json +1 -1
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
- package/pipeline/skills/skills-index.md +1 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Office routes: threat model
|
|
2
2
|
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Assets](#assets)
|
|
5
|
+
- [Attackers](#attackers)
|
|
6
|
+
- [Threats and controls](#threats-and-controls)
|
|
7
|
+
- [Residual risks](#residual-risks)
|
|
8
|
+
- [Later, with a remote carrier](#later-with-a-remote-carrier)
|
|
9
|
+
<!-- /toc -->
|
|
10
|
+
|
|
3
11
|
The office routes of `scripts/contract-server.mjs` let a client watch the
|
|
4
12
|
agents on this machine, read what they said, answer and steer runs, and change
|
|
5
13
|
what runs here (autopilot, stacks, routines). A browser panel may be served at
|
|
@@ -20,6 +28,8 @@ not mitigated here.
|
|
|
20
28
|
| Run state | `agent-state.json`, `tracker-state.json` | an answer or steer written here changes what a run does |
|
|
21
29
|
| Machine configuration | autopilot `config.json` and launchd jobs, a repo's `.claude/settings.json`, routine commands | widening any of them changes what runs unattended |
|
|
22
30
|
| PR verdicts | posted to GitHub or Bitbucket with the user's own credential | an approval in the user's name |
|
|
31
|
+
| Learning stores | `~/.claude/memory/multi-agent/<repo>/` and `~/.claude/knowledge/<repo>/` | statements a run wrote down, which can quote code or a pasted secret |
|
|
32
|
+
| Run logs | `~/.claude/logs/multi-agent/<project>/<task>/` | deleting the wrong folder loses a live run's state |
|
|
23
33
|
|
|
24
34
|
## Attackers
|
|
25
35
|
|
|
@@ -36,20 +46,28 @@ not mitigated here.
|
|
|
36
46
|
|---|---|---|---|
|
|
37
47
|
| T1 | DNS rebinding: a page on an attacker host resolves to 127.0.0.1 and calls the API under its own name | Every request, `/office` and the event stream included, must carry `Host: 127.0.0.1:<port>` or `localhost:<port>`; anything else is 403 before routing | `smoke-office-security.sh` T1, `test/office-routes.test.mjs` "403 under a rebound Host" |
|
|
38
48
|
| T2 | CSRF: a page posts to a write route | Writes (POST, DELETE) accept no `Origin` but the server's own, even an origin allowed with `--allow-origin`; a foreign preflight is 403 and no CORS header is ever sent to a foreign page; writes that widen what runs here take a second request with a single-use, route-bound confirm token | `smoke-office-security.sh` T2, `office-routes.test.mjs` "a write from a foreign page is refused" |
|
|
39
|
-
| T3 | Token theft or replay | The token is read from the `Authorization` header only (never a cookie or a query parameter), compared in constant time over digests, never written into a response, an error or a log line. It never appears in a URL: `office-on` opens the page with a one-time code in the fragment (10 seconds, one use, minted by the bearer holder at `POST /office/code` with no Origin), and the page trades it at `POST /office/exchange`, which accepts only the server's own Origin; a second use, an expired or an unknown code is 401. The code never reaches a command line, which every local user can read (`ps`): the browser is opened on a 0600 launcher page in the 0700 state directory that carries the code and forwards with `location.replace`, under a CSP that allows only its own script by hash; the launcher is removed after 15 seconds. An Origin header can be forged by a non-browser client, so the code's secrecy and its 10 second, single-use life are what protect the exchange. The page keeps the token in memory and strips the fragment.
|
|
49
|
+
| T3 | Token theft or replay | The token is read from the `Authorization` header only (never a cookie or a query parameter), compared in constant time over digests, never written into a response, an error or a log line. It never appears in a URL: `office-on` opens the page with a one-time code in the fragment (10 seconds, one use, minted by the bearer holder at `POST /office/code` with no Origin), and the page trades it at `POST /office/exchange`, which accepts only the server's own Origin; a second use, an expired or an unknown code is 401. The code never reaches a command line, which every local user can read (`ps`): the browser is opened on a 0600 launcher page in the 0700 state directory that carries the code and forwards with `location.replace`, under a CSP that allows only its own script by hash; the launcher is removed after 15 seconds. An Origin header can be forged by a non-browser client, so the code's secrecy and its 10 second, single-use life are what protect the exchange. The page keeps the token in memory and strips the fragment. A server started by `contract-server.mjs` alone exits after 8 hours however busy it is; the panel's own server (`office-on`) runs until `office-off`, because a person keeps it open beside their work. Its token lives only in the server's memory, the page's memory and a 0600 file, is never on a command line or in a URL, and is replaced each time `office-on` starts a server | `office-control.test.mjs` "the opener's argv carries no code", "the launcher page has a strict CSP", "the launcher is deleted"; `office-routes.test.mjs` "a panel code trades for the token once", "a code is refused from a foreign page", "an expired code is 401", "exits at its absolute lifetime"; `smoke-office-page.sh` "the code trades once" |
|
|
40
50
|
| T4 | Secret exposure through a response | Transcripts are read through `_transcript-tail.mjs`, which never parses api-request records and drops thinking blocks; every string passes the credential redactor and the scrubber; the finished office and chat documents are checked by the outbound gate; seat labels come from allow-listed fields (tool name, file basename, sidecar description), never a command line; credentials are read only by the service scripts and never returned | `smoke-office-privacy.sh`, `smoke-office-security.sh` T4, `test/office-activity.test.mjs` "carries no credential" |
|
|
41
51
|
| T5 | Path traversal into arbitrary files | Session, agent, run, routine, tool and evidence ids are matched against safe character sets after decoding; transcripts are looked up by name under the projects root only; a screenshot must resolve (real path) inside the run's evidence directory; an analysis document named in run state is read only when its real path is inside the run's log directory, its artifacts or a worktree git lists under the state's projectRoot, and only when it is Markdown or HTML | `smoke-office-security.sh` T5, `test/office-work.test.mjs` "nothing outside its directory", "analysis documents are read only inside the run's roots", "a worktreePath counts only when git lists it" |
|
|
42
52
|
| T6 | Oversized or malformed input | Bodies are capped at 64 KiB and validated against the route's schema; tool arguments are checked against the tool's inputSchema; launch input goes through `launch-request.mjs` validation only; chat paging is capped at 500 | `smoke-office-security.sh` T6, `office-work.test.mjs` "refuses an unknown tool, invalid arguments and an oversized body" |
|
|
43
|
-
| T7 | Resource exhaustion | At most four event streams and 64 connections; a phone request has five seconds to send its body before any signature work; at most 256 confirm tokens, the oldest dropped first; service clients, the work list and autopilot control run as child processes, never blocking the event loop, one at a time per service (429 `busy` for a second one); one snapshot is computed per poll from bounded tail reads; the session list is cached for ten seconds;
|
|
53
|
+
| T7 | Resource exhaustion | At most four event streams and 64 connections; a phone request has five seconds to send its body before any signature work; at most 256 confirm tokens, the oldest dropped first; service clients, the work list and autopilot control run as child processes, never blocking the event loop, one at a time per service (429 `busy` for a second one); one snapshot is computed per poll from bounded tail reads; the session list is cached for ten seconds; a server started on its own exits after its idle time and after 8 hours in any case, the one `office-on` starts runs until `office-off`, and both stop on SIGTERM | `smoke-office-security.sh` T7; `office-routes.test.mjs` "connections past the cap are dropped", "never sends its body", "confirm tokens are capped"; `office-work.test.mjs` "a slow service read neither blocks other routes nor runs twice at once" |
|
|
44
54
|
| T8 | XSS from hostile transcript or issue text | The panel renders every server string with `textContent` or canvas text; markdown is shown as plain text; server images only as `data:` URLs of image types; the office sprites are same-origin PNGs from `/office/assets/`, which answers only a plain file name (no separator or escape) whose real path is a PNG directly in the installed assets directory, and 404 otherwise; the page language the server stamps comes from a two-value set (`en`, `tr`), whatever the preferences file holds; the page's CSP allows only its per-response nonce for script and style, `connect-src 'self'`, no framing, no base URI, no form action | `smoke-office-page.sh`, `office-routes.test.mjs` "stamps the page language from outputLanguage", "serves a sprite and nothing else under /office/assets/", `office-build.test.mjs`, `smoke-office-runtime.sh` |
|
|
45
55
|
| T9 | Clickjacking | `frame-ancestors 'none'` on the page and on every API response | `office-routes.test.mjs` "serves the page without a token, with a nonce CSP" |
|
|
46
56
|
| T10 | An action nobody can account for | Every write leaves one line (who, route, step, status; never a body or token) in `office-actions.jsonl` | `office-routes.test.mjs` "every write leaves a line" |
|
|
47
|
-
| T11 | The panel widening the machine silently | Autopilot on, stack changes
|
|
57
|
+
| T11 | The panel widening the machine silently | Autopilot on, stack changes, routine removal and adding or removing a launch repository are tagged `local-only` and `high`, take two requests, and run the same scripts as the terminal commands where one exists (removing a launch repository has none). A repository is added only as a git checkout that is neither the home directory nor the root; a suggestion is named by an opaque id and resolved again on the server, so a page never sends or sees a path it did not type | `smoke-office-routes.sh`, `smoke-office-single-source.sh`, `office-repos.test.mjs`, `smoke-office-ui.sh` |
|
|
48
58
|
| T12 | A toolkit tool used as a reader of local files or services (a path, a `file://` or loopback URL, a shared browser page, an index of files) | Tool calls are denied by default: only a tool that declares readOnlyHint, is not destructive, is not in the `web` or `context` family and whose inputSchema takes no location (a property named for a path, URL, file, directory, output, project, root and similar, or a uri format, at any depth) is callable; everything else is 403 `tool-not-allowed` and never reaches the server. A `confirm` argument is refused, so a tool's own preview is never skipped. The toolkit child always runs with `MULTI_AGENT_UNATTENDED=1` and `MCP_TOOLKIT_URL_POLICY=strict`, applied after the registration's own environment. An `@latest` registration is pinned to a configured version or refused. The route is `local-only`, and its audit line names the tool and carries a SHA-256 of the arguments, never the arguments | `office-work.test.mjs` "refuses every tool outside the allowlist with 403 and never runs it", "refuses a confirm argument", "runs unattended with the strict URL policy", "pinned to a known version or refused", "the audit line names the tool and hashes the arguments" |
|
|
49
59
|
| T13 | A steer instruction injected into an unattended run | Steering is tagged `launch` and `high` and takes two requests: the first shows the text, the run's phase and the unread instruction it replaces, and returns a token bound to that run and exactly that text; the second carries only the token. A finished run cannot be steered | `office-routes.test.mjs` "previews first, then queues on confirm", "a steer token is single use, bound to its run and cannot carry new text" |
|
|
50
60
|
| T14 | A review verdict posted from a stale or unfinished review | A verdict is previewed only for a finished review whose every finding is accepted, rejected or deferred (409 `review-incomplete` otherwise); the confirm token is bound to the decision and a digest of the findings, and a confirmation after either changed is 409 `confirm-mismatch` | `office-work.test.mjs` "a running review or one with an untriaged finding cannot be previewed", "refused when the findings changed after the preview" |
|
|
51
61
|
| T15 | A service read pointed at another host | A Confluence URL is accepted only on the configured Confluence host, and with no host configured only a page id is accepted (409 `host-not-allowed`), before the client script runs; Crashlytics takes only a Firebase console URL | `office-work.test.mjs` "a Confluence URL on another host is refused before the client runs", "only a page id is accepted" |
|
|
52
62
|
| T16 | Machine paths leaking through errors, or `office-off` signalling a reused pid | Error bodies name no absolute path (the launchd refusal and the unreadable settings message say what failed, not where); `office-off` sends SIGTERM only when the recorded pid's token file still names it, its command is `contract-server.mjs` and its start time matches the token file's | `office-routes.test.mjs` "refused without naming its path", "reported without the plist path"; `smoke-office-control.sh` "off never signals a pid that is not its server" |
|
|
63
|
+
| T17 | A reload code minted for another page, or a stale server outliving an update | `POST /office/reload` and `GET /office/version` need the bearer token and refuse any Origin other than the panel's own (a request with no Origin, a terminal client, is accepted); the reload code is the same 10 second, single-use code `office-on` uses, so a page that already holds the token gains nothing it did not have. A server whose code changed on disk is reported stale and `office-on` replaces it instead of reusing it | `office-repos.test.mjs` "/office/version reports the installed build", "/office/reload mints a one-time code"; `smoke-office-ui.sh` |
|
|
64
|
+
| T18 | The panel starting work nobody asked for, or work wider than asked | `POST /v1/jobs` takes only a job named in `schemas/office-jobs.json`, each parameter checked by its kind (a pull request URL or `repo#N`, a Jira key, a run id the index knows, a branch name, a path that stays inside the repository, an enumerated value; a pipeline run's work item goes through the launch request rules). Two requests: the preview names the role, the repository, the exact prompt and what the job writes outside the machine (a branch and PR for a pipeline run; review and clarify jobs report locally, since agent-guard keeps GitHub writes to the runner), and the token is bound to that payload. At the confirmation the repository must still be allowed, the unattended profile installed, the workspace trusted, agent-guard.sh registered for every tool and the autopilot runner's sandbox check passing; the session runs `claude --bg` with no permission prompts under that profile, without a shell, and claude's own output stays in the server's log, not the page. The unattended profile denies reading the server's token directory, so a session it starts cannot pick up the bearer. A team job is refused while a run is live in the same repository, from the moment its session starts. Tagged `local-only`/`high`: no phone route reaches it. Every start is recorded under `logs/multi-agent/office/jobs/` and in `office-actions.jsonl` | `office-jobs.test.mjs`, `smoke-office-ui.sh` |
|
|
65
|
+
| T19 | The key cabinet leaking a credential, or the panel becoming a place to enter one | `GET /v1/credentials` runs `lib/credential-inventory.sh`, whose probe reads each credential through the credential store with its output discarded, so no value reaches the server process. The route keeps the logical name, a state and a probe verdict from fixed vocabularies and a capability line cut to 160 characters; anything else in the script's output is dropped. Tagged `local-only`: not even the state goes to a phone. There is no write route: renewing a credential stays in the terminal (`/multi-agent:setup`), so a secret never passes through the browser | `office-routes.test.mjs` "GET /v1/credentials names each credential and its verdict, never a value" |
|
|
66
|
+
| T20 | A learned statement carrying a secret or a machine path to the page | `GET /v1/memory` returns counts, the code graph's age and size, and the five newest learning statements per repository, each passed through the transcript redactor (provider credential rules, paths and addresses) and cut to 120 characters. Repository names are matched against a safe character set before any file is opened. Read only; `local-only` | `office-routes.test.mjs` "GET /v1/memory reports every shelf, redacted" |
|
|
67
|
+
| T21 | Machine details through the resource panel | `GET /v1/system` returns numbers only (cores, load, memory, free disk, process counts, log folder size) and two fixed warnings; no path, process argument, user or host name. The schema allows nothing else. The measurement runs as child processes off the event loop, concurrent requests share one measurement, and the log folder size is reused for a minute. Readable from a phone, since it names nothing | `office-routes.test.mjs` "GET /v1/system returns numbers only: no path, user or host name" |
|
|
68
|
+
| T22 | Old run logs deleted wider than asked, or a live run's log removed | `POST /v1/prune-logs` takes only an age from a fixed set (7, 30 or 90 days) and runs `prune-logs.sh`, the script `/multi-agent:prune-logs` runs: the dry run is the preview; the confirmed step lists the folders again, refuses with `changed` when they differ, and then prunes only the previewed list through `--only-from`, under a token bound to that age and list. The script never touches the audit trail, the metrics corpus or the task counters, refuses a log root that is `/`, the home folder or a git checkout, and spares a folder written in the last minutes and a run that can still be resumed. Tagged `local-only`/`high` | `office-routes.test.mjs` "POST /v1/prune-logs previews, then prunes only on the token" |
|
|
69
|
+
| T23 | Pull request listing exposing a token, or the native task mirror writing into another run | Each GitHub account gh is logged in to is asked with `gh auth token` for that host and login; the token travels in `GH_TOKEN` (and `GH_ENTERPRISE_TOKEN` for an Enterprise Server host) for the child process only, never on a command line, in a response or a log. Bitbucket Server is read over https only (a plain-http host is refused unless it is the loopback), redirects are not followed, and the token, read from the credential store with a deadline, travels in the `Authorization` header only; an unreachable host is a reason on that source, not a failure of the list. A redacted answer drops URLs and logins, numbering the accounts instead. The task-mirror hook (PostToolUse on TaskCreate and TaskUpdate) writes only into the run whose recorded session is the calling session, skips phase tiles, never prints, and exits 0 on any input it cannot read | `work-items-prs.test.mjs`, `task-mirror.test.mjs` |
|
|
70
|
+
| T24 | The panel changing a preference wider than the terminal would, or posting addresses to a phone | `POST /v1/settings` takes one of three settings and one of its two values, previews the change and runs, under a token bound to that setting and value, what `/multi-agent:language`, `/multi-agent:model` or `/multi-agent:route-on`/`route-off` runs; route on passes the saved strategy back so only the switch changes, nothing the panel sends can add rules or a free provider, and two changes never run at once. Tagged `local-only`/`high`. Report links: `channelResults` URLs must be https, travel only in the run record's `channelUrls` (local, dropped from a redacted answer) and are shown as text, never followed | `office-routes.test.mjs` "POST /v1/settings changes a switch only on the token, through its script", `runs-index.test.mjs` |
|
|
53
71
|
|
|
54
72
|
## Residual risks
|
|
55
73
|
|
|
@@ -64,9 +82,9 @@ not mitigated here.
|
|
|
64
82
|
nothing local does.
|
|
65
83
|
- **A page on the server's own origin** is trusted. Only the page this server
|
|
66
84
|
serves can be there, and its CSP keeps other code out.
|
|
67
|
-
- **Starting a session**:
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
- **Starting a session**: `POST /v1/launch` returns the launch plan for a
|
|
86
|
+
client to start. `POST /v1/jobs` starts a session itself, because the office
|
|
87
|
+
is that client; it is bounded by T18 and is never reachable from a phone.
|
|
70
88
|
- **Host permission prompts** are not forwarded: the host records none where
|
|
71
89
|
another process can read them (`GET /v1/permissions` says `available:
|
|
72
90
|
false`).
|
|
@@ -566,7 +566,7 @@ protected paths (`~/.claude/**`, `~/.copilot/**`, `~/.codex/**`,
|
|
|
566
566
|
`~/.config/gh`, `~/.config/git`, `~/.gitconfig`, `~/.npmrc`, `~/.netrc`,
|
|
567
567
|
`~/.git-credentials`, `.git/hooks/**`, `.git/config`, `**/.github/workflows/**`),
|
|
568
568
|
and the toolkit env `MCP_TOOLKIT_URL_POLICY=strict` and
|
|
569
|
-
`MCP_TOOLKIT_INDEX_DENY=~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains`.
|
|
569
|
+
`MCP_TOOLKIT_INDEX_DENY=~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains,~/.claude/logs/multi-agent/contract-server,~/.claude/logs/multi-agent/office`.
|
|
570
570
|
|
|
571
571
|
The runner hands the file to each child with `claude --settings`, so only the
|
|
572
572
|
unattended session loads it. `~/.claude/settings.json` is the attended posture
|
|
@@ -86,9 +86,9 @@ Status & Resume:
|
|
|
86
86
|
/multi-agent:autopilot-status Running, queued, awaiting an answer, today's PRs
|
|
87
87
|
/multi-agent:autopilot-off Stop picking work up (running work finishes; --now stops it)
|
|
88
88
|
/multi-agent:serve Contract server for a client: 127.0.0.1, token, stops when idle
|
|
89
|
-
/multi-agent:office-on Open the office
|
|
89
|
+
/multi-agent:office-on Open the office in your browser: live runs and agents, PRs, keys, memory, system, jobs; its orientation room explains the rest
|
|
90
90
|
/multi-agent:office-status Is the panel running, its URL (never the token), its last actions
|
|
91
|
-
/multi-agent:office-off
|
|
91
|
+
/multi-agent:office-off Stop the panel's server (it runs until you do)
|
|
92
92
|
|
|
93
93
|
Post-Hoc & Side-Channel:
|
|
94
94
|
|
|
@@ -85,9 +85,9 @@ Status & Resume:
|
|
|
85
85
|
/multi-agent:autopilot-status Koşan, sıradaki, cevap bekleyen, bugünkü PR'lar
|
|
86
86
|
/multi-agent:autopilot-off Yeni madde alma (koşan iş biter; --now onu da durdurur)
|
|
87
87
|
/multi-agent:serve İstemci için sözleşme sunucusu: 127.0.0.1, token, boşta durur
|
|
88
|
-
/multi-agent:office-on
|
|
88
|
+
/multi-agent:office-on Ofisi tarayıcıda aç: canlı koşular ve ajanlar, PR'lar, anahtarlar, hafıza, sistem, işler; gerisini oryantasyon odası anlatır
|
|
89
89
|
/multi-agent:office-status Panel çalışıyor mu, adresi (token asla), son işlemleri
|
|
90
|
-
/multi-agent:office-off
|
|
90
|
+
/multi-agent:office-off Panelin sunucusunu durdur (sen durdurana kadar çalışır)
|
|
91
91
|
|
|
92
92
|
Post-Hoc & Side-Channel:
|
|
93
93
|
|
|
@@ -66,19 +66,19 @@ Full contract: `$HOME/.claude/multi-agent-refs/phases/phase-5-report.md` (Autopi
|
|
|
66
66
|
|
|
67
67
|
## Analysis Mode (`/multi-agent:analysis`)
|
|
68
68
|
|
|
69
|
-
A different **output kind**, not a shorter pipeline. It produces the analysis document and stops; no code, no branch, no PR.
|
|
69
|
+
A different **output kind**, not a shorter pipeline. It produces the analysis document and stops; no code, no branch, no PR. It runs the same six slots under its own step names (`phases.json` `modes.analysis.names`), so the tracker and the task widget show what it is doing.
|
|
70
70
|
|
|
71
71
|
```
|
|
72
|
-
Phase 0:
|
|
72
|
+
Phase 0: Intake -> Phase 1: Evidence -> Phase 2: Synthesis -> Phase 3: Review -> Phase 4: Publish -> Phase 5: Report
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
| Phase | Analysis mode |
|
|
76
76
|
|---|---|
|
|
77
|
-
| 0
|
|
78
|
-
| 1
|
|
79
|
-
| 2
|
|
80
|
-
| 3 Review |
|
|
81
|
-
| 4
|
|
77
|
+
| 0 Intake | Same intake, but **no worktree**: nothing is isolated because nothing is built, so `state.run.repoPath` is the repo itself |
|
|
78
|
+
| 1 Evidence | Every declared source fetched in parallel, repo evidence, Code Connect index, conventions (`multi-agent-refs/analysis/evidence.md`) |
|
|
79
|
+
| 2 Synthesis | Pass A concept layer, the convention preview, Pass B per platform (`multi-agent-refs/analysis/synthesis.md`). **No task decomposition** - there is no code to plan |
|
|
80
|
+
| 3 Review | Render, the humanizer, `validate-analysis-doc.mjs`, the document review and the open questions still marked `Acik / Open` |
|
|
81
|
+
| 4 Publish | Local file / Confluence / Jira. Locked 6 still forbids `git add` and `git commit` |
|
|
82
82
|
| 5 Report | Channels, same as every mode |
|
|
83
83
|
|
|
84
84
|
**No `autopilot` variant.** Worktree isolation buys nothing when no code is written, and the intake, the Pass B convention preview and the open-question resolution are interactive by nature; a zero-interaction analysis would be a document nobody agreed to.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
### Phase 5: Report
|
|
2
2
|
|
|
3
|
-
> **TLDR** -
|
|
3
|
+
> **TLDR** - Two halves. **External delivery** goes to `/multi-agent:channels`, the single source of truth for Jira, Confluence, wiki and PR-description delivery (channel and content multi-select; always pauses in autopilot, a 30-minute timeout ends the session; contract in `commands/multi-agent/channels/SKILL.md`). **Internal capture** stays inline: `agent-log.md`, telemetry, knowledge base and memory.
|
|
4
4
|
|
|
5
5
|
<!-- progress-contract: applied -->
|
|
6
|
-
Progress
|
|
6
|
+
Progress per `$HOME/.claude/multi-agent-refs/progress-contract.md`: channels dispatch, each adapter's result, agent-log, telemetry, knowledge and memory.
|
|
7
7
|
|
|
8
8
|
## Sub-step Tracking (required)
|
|
9
9
|
|
|
@@ -96,6 +96,11 @@ Pre-ticks from `prefs.global.reportChannels` + `prefs.global.reportContent`. Gre
|
|
|
96
96
|
|
|
97
97
|
Tracker: `phase-tracker.sh sub 5 1 "Channels dispatch" completed` (or `failed` if ALL adapters failed; `skipped` if user declined all).
|
|
98
98
|
|
|
99
|
+
For the office, record each adapter's `{status, url}` (https only) under `channelResults.<pr|jira|confluence|wiki|issue>`; `write-state.mjs` merges, Step 1.5 adds `issue`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
printf '%s' '{"channelResults":{"pr":{"status":"posted","url":"<url>"},"confluence":{"status":"skipped"}}}' | node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE" >/dev/null
|
|
103
|
+
```
|
|
99
104
|
---
|
|
100
105
|
|
|
101
106
|
## Step 1.5 - GitHub Issue channel (when tracker is a GitHub issue)
|
|
@@ -112,6 +117,7 @@ bash ~/.claude/scripts/phase-tracker.sh sub 5 1.5 "Issue comment" in_progress
|
|
|
112
117
|
# unattended: skipped - the runner posts (features/unattended-security.md)
|
|
113
118
|
gh issue comment "$ISSUE_NUMBER" --repo "$ORG/$REPO" --body-file /tmp/channels-${TASK_ID}-issue.md
|
|
114
119
|
bash ~/.claude/scripts/phase-tracker.sh sub 5 1.5 "Issue comment" completed
|
|
120
|
+
printf '%s' '{"channelResults":{"issue":{"status":"posted","url":"<url>"}}}' | node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE" >/dev/null
|
|
115
121
|
|
|
116
122
|
# 2. Sync the Progress flag table from agent-state.json. Idempotent - the
|
|
117
123
|
# script no-ops if the body already matches.
|
|
@@ -150,7 +156,7 @@ Write `agent-log.md` with ALL sections:
|
|
|
150
156
|
|
|
151
157
|
## Plan Todo Rollup (when `prefs.global.planTodos.enabled`)
|
|
152
158
|
|
|
153
|
-
Rendered via `$HOME/.claude/lib/plan-todos.sh list "$TASK_ID"
|
|
159
|
+
Rendered via `$HOME/.claude/lib/plan-todos.sh list "$TASK_ID"` and inlined verbatim: one line per todo with its marker ([x] / [~] / [/] / [!]), ID, text and notes, under the one-line summary from `plan-todos.sh status` ("4/6 done, 1 skipped, 1 failed").
|
|
154
160
|
|
|
155
161
|
Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emitted, the Plan Todo block is omitted (no empty header). Phase 2 task-by-task progress still appears in the Timeline section either way.
|
|
156
162
|
|
|
@@ -183,7 +189,7 @@ Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emi
|
|
|
183
189
|
|
|
184
190
|
## Repo Profile
|
|
185
191
|
|
|
186
|
-
(from `node $HOME/.claude/scripts/repo-profile.mjs report --repo "$PROJECT_ROOT" --state "$STATE_FILE"` and `state.repoProfile`: the `action`, whether it was confirmed,
|
|
192
|
+
(from `node $HOME/.claude/scripts/repo-profile.mjs report --repo "$PROJECT_ROOT" --state "$STATE_FILE"` and `state.repoProfile`: the `action`, whether it was confirmed, `derived` fields against `confirmed` or `manual` ones, and each `ignored[]` field with confidence and reason; omitted without a `repoProfile`.)
|
|
187
193
|
|
|
188
194
|
## Channels Summary
|
|
189
195
|
|
|
@@ -243,14 +249,14 @@ This is independent of the channels-side `reportContent.costSummary` (which gate
|
|
|
243
249
|
bash $HOME/.claude/scripts/capture-flush.sh --state "$STATE_FILE"
|
|
244
250
|
```
|
|
245
251
|
|
|
246
|
-
Every phase boundary already made this call (`operations.md`), so
|
|
252
|
+
Every phase boundary already made this call (`operations.md`), so here it usually writes 0 rows: Phase 5 is the last flush, not the only one, because it is the phase a run is least likely to reach.
|
|
247
253
|
|
|
248
254
|
What it does, so this doc stays inspectable:
|
|
249
255
|
|
|
250
|
-
- **Triage ingest** (`triage-memory.mjs ingest`)
|
|
251
|
-
- **Ledger distill** (`learnings-ledger.mjs from-triage`)
|
|
256
|
+
- **Triage ingest** (`triage-memory.mjs ingest`): accepted, deferred and rejected rows into the per-repo corpus (`~/.claude/memory/multi-agent/<repo-slug>/triage-corpus.jsonl`, never cross-repo) that Phase 1 enrichment and Phase 3 prior-art recall. Idempotent. It reads the salvaged copy under `artifactsPath` first, because Phase 4 removes the worktree and a worktree-first reader silently loses the runs worth rescuing. Off when `prefs.global.priorArtEnrichment.ingestOnComplete = false`.
|
|
257
|
+
- **Ledger distill** (`learnings-ledger.mjs from-triage`): rejected findings become durable `rejected-preference` entries so reviewers stop re-raising them. Idempotent. A blocking-severity rejection is never distilled, so a wrong one cannot suppress its class for good; `learnings-ledger.mjs forget` clears a stale entry. JSONL beside the corpus, replayed into Phase 1 and Phase 3; on via `prefs.global.learningsLedger.enabled`.
|
|
252
258
|
|
|
253
|
-
No model runs in the flush
|
|
259
|
+
No model runs in the flush: it derives everything from `triage-output.json` and `agent-state.json`, which is what lets a hook call it. The parts that need a model (Step 3 knowledge capture, memory synthesis) stay here.
|
|
254
260
|
|
|
255
261
|
A durable architectural fact is a judgement call, so it stays here rather than in the flush:
|
|
256
262
|
|
|
@@ -303,13 +309,13 @@ Extract reusable knowledge from this task and **append** to `$HOME/.claude/knowl
|
|
|
303
309
|
|
|
304
310
|
**First task** (no knowledge dir): `mkdir -p`, create initial architecture.md + patterns.md from Phase 1. Log: "Knowledge base initialized for {project}"
|
|
305
311
|
|
|
306
|
-
**Code graph refresh:**
|
|
312
|
+
**Code graph refresh:** with `prefs.global.codeGraph.enabled` and `.autoRefresh` both true, rebuild the graph and `GRAPH_REPORT.md`, since the branch changed the code Phase 1 described. No API tokens, never fails the run; commands in `$HOME/.claude/multi-agent-refs/features/code-graph.md`.
|
|
307
313
|
|
|
308
314
|
**Save preferences**: Update prefs with Phase 5 selections (via channels return: `reportChannels`, `reportContent`, `confluenceUrls` per-project LRU).
|
|
309
315
|
|
|
310
316
|
**Per-repo memory synthesis (opt-in via `prefs.global.perRepoMemory`):**
|
|
311
317
|
|
|
312
|
-
After
|
|
318
|
+
After knowledge capture, a scoped synthesis subagent (sonnet, 10 turns max) reads this run's `agent-log.md` and the MEMORY.md index and returns 0-3 new entries as a JSON array of `{type: "user|feedback|project|reference", slug, title, description, body}`. The orchestrator saves them all, in order:
|
|
313
319
|
|
|
314
320
|
```bash
|
|
315
321
|
bash $HOME/.claude/scripts/memory-save.sh "$PROJECT_ROOT" --from-json memory-synthesis.json
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Standalone review: its own tracker record
|
|
2
|
+
|
|
3
|
+
A standalone review (the command seeded a `review-` id, so no pipeline run
|
|
4
|
+
owns this session) registers itself, so its progress shows wherever runs
|
|
5
|
+
are read (the office panel, `/multi-agent:status`). Inside a pipeline run the
|
|
6
|
+
run's own tracker already covers it; skip this block.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
case "$TASK_ID" in review-*)
|
|
10
|
+
S="${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/$TASK_ID"; mkdir -p "$S"
|
|
11
|
+
printf '{"taskId":"%s","mode":"review","status":"in_progress","currentPhase":0}' "$TASK_ID" \
|
|
12
|
+
| node "$HOME/.claude/scripts/write-state.mjs" "$S/agent-state.json" >/dev/null
|
|
13
|
+
export MULTI_AGENT_TASK_ID="$TASK_ID"
|
|
14
|
+
bash "$HOME/.claude/scripts/phase-tracker.sh" init "$TASK_ID"
|
|
15
|
+
for p in "0:Input" "1:Diff" "2:Reviewers" "3:Triage" "4:Report"; do
|
|
16
|
+
bash "$HOME/.claude/scripts/phase-tracker.sh" add "${p%%:*}" "${p#*:}"
|
|
17
|
+
done
|
|
18
|
+
bash "$HOME/.claude/scripts/phase-tracker.sh" update 0 in_progress ;;
|
|
19
|
+
esac
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Step 1 (1, 1a) is tracker step 0, step 2 (2-2c) step 1, steps 3-4b step 2,
|
|
23
|
+
step 5 step 3 and steps 6-7 step 4: `update <N> in_progress` on entry and `completed` on exit. Each later
|
|
24
|
+
call runs in a new shell where `$TASK_ID` is no longer set, so it carries the
|
|
25
|
+
review's id written out literally (the `review-...` value the registration
|
|
26
|
+
used) and refuses the global pointer, which another run's `init` can move:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
MULTI_AGENT_TASK_ID=review-1 MULTI_AGENT_STRICT_TASK_ID=1 \
|
|
30
|
+
bash "$HOME/.claude/scripts/phase-tracker.sh" update 2 completed
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
After step 7 a standalone review closes its record with the same `write-state.mjs` call and
|
|
34
|
+
`"status":"complete","currentPhase":4`.
|
|
35
|
+
|
|
36
|
+
Reviewer token counts (`phase-tracker.sh tokens <N> ...`) go under step 2
|
|
37
|
+
(Reviewers); inside a pipeline run they go under the run's Review phase.
|
|
@@ -93,7 +93,7 @@ Phases by mode:
|
|
|
93
93
|
| `/multi-agent` | 0,1,2,3,4,5 |
|
|
94
94
|
| answered local at Step 5b | 0,1,2,3,4,5 (the user test needs a worktree checkout and local has none, so that STEP inside Review is skipped - the phase is not) |
|
|
95
95
|
| `/multi-agent:autopilot` | 0,1,2,3,4,5 (autopilot drops the interactive user test inside Review) |
|
|
96
|
-
| `/multi-agent:analysis` | 0,1,3,4,5
|
|
96
|
+
| `/multi-agent:analysis` | 0,1,2,3,4,5 under its own names (`schemas/phases.json` `modes.analysis.names`: Intake, Evidence, Synthesis, Review, Publish, Report) |
|
|
97
97
|
|
|
98
98
|
Every entry registers its whole set in one batch: the set is a property of the command, known before the tracker boots.
|
|
99
99
|
|
|
@@ -404,7 +404,7 @@ call for call, so a reader that is not the host (`runs-index.mjs`, and every
|
|
|
404
404
|
client behind the contract server) sees the same list under the same phase:
|
|
405
405
|
|
|
406
406
|
```bash
|
|
407
|
-
bash $HOME/.claude/scripts/phase-tracker.sh task <N> <task_id> pending|in_progress|completed \
|
|
407
|
+
bash $HOME/.claude/scripts/phase-tracker.sh task <N> <task_id> pending|in_progress|completed|deleted \
|
|
408
408
|
[--subject "<subject>"] [--active-form "<activeForm>"]
|
|
409
409
|
```
|
|
410
410
|
|
|
@@ -416,10 +416,15 @@ bash $HOME/.claude/scripts/phase-tracker.sh task <N> <task_id> pending|in_progre
|
|
|
416
416
|
|
|
417
417
|
`<N>` is the phase the task belongs to: the phase in progress when it was
|
|
418
418
|
created. A new task needs `--subject`; an update may pass the status alone and
|
|
419
|
-
keeps the subject and active form it had. Phase tiles themselves are not
|
|
419
|
+
keeps the subject and active form it had. `deleted` takes the task off the list, as a native `TaskUpdate` with status `deleted` does. Phase tiles themselves are not
|
|
420
420
|
mirrored: the phase list already is the state file. The call is quiet (it
|
|
421
421
|
renders nothing), so it never replaces the host's own update; it records it.
|
|
422
422
|
|
|
423
|
+
On Claude Code the `task-mirror.sh` PostToolUse hook makes this call itself
|
|
424
|
+
for every `TaskCreate` and `TaskUpdate` of the session bound to the run, under
|
|
425
|
+
the phase in progress, and leaves phase tiles out. A manual `task` call on top
|
|
426
|
+
of it is harmless: the second write sets the same values.
|
|
427
|
+
|
|
423
428
|
## Resume behaviour
|
|
424
429
|
|
|
425
430
|
When `/multi-agent:resume <task_id>` is called:
|
|
@@ -4,7 +4,10 @@ The office page (`index.html`, built from `pipeline/office-src` by
|
|
|
4
4
|
`pipeline/scripts/office-build.mjs`) draws sprites from pixel-agents
|
|
5
5
|
(https://github.com/pixel-agents-hq/pixel-agents, commit b1bafd0): the six
|
|
6
6
|
character sheets, the floor and wall tiles and the furniture, kept unchanged
|
|
7
|
-
in `assets/`.
|
|
7
|
+
in `assets/`. The company furniture (kitchen, restroom, server room, archive,
|
|
8
|
+
reception, lab and mail room pieces) is this project's own drawing: its
|
|
9
|
+
sources are the pixel grids in `pipeline/office-art`, which the build renders
|
|
10
|
+
into `assets/` with the pixel-agents palette. pixel-agents'
|
|
8
11
|
characters are based on JIK-A-4's MetroCity free top-down character pack
|
|
9
12
|
(https://jik-a-4.itch.io/metrocity-free-topdown-character-pack), released
|
|
10
13
|
under CC0 1.0.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|