@detergent-software/conductor 1.0.0-dev.1 → 1.0.0-dev.11
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 +93 -0
- package/README.md +210 -9
- package/build/cli.js +456 -21
- package/build/cli.js.map +1 -1
- package/build/clock.js +17 -0
- package/build/clock.js.map +1 -0
- package/build/conditions.js +5 -6
- package/build/conditions.js.map +1 -1
- package/build/config.js +92 -1
- package/build/config.js.map +1 -1
- package/build/credential.js +52 -0
- package/build/credential.js.map +1 -0
- package/build/daemon.js +96 -2
- package/build/daemon.js.map +1 -1
- package/build/events.js +106 -0
- package/build/events.js.map +1 -0
- package/build/inbox.js +385 -0
- package/build/inbox.js.map +1 -0
- package/build/manager.js +56 -12
- package/build/manager.js.map +1 -1
- package/build/mod.js +51 -0
- package/build/mod.js.map +1 -0
- package/build/note.js +71 -0
- package/build/note.js.map +1 -0
- package/build/notes.js +188 -0
- package/build/notes.js.map +1 -0
- package/build/notify.js +393 -0
- package/build/notify.js.map +1 -0
- package/build/overrides.js +82 -0
- package/build/overrides.js.map +1 -0
- package/build/paths.js +1 -0
- package/build/paths.js.map +1 -1
- package/build/profile.js +7 -0
- package/build/profile.js.map +1 -1
- package/build/pushover.js +67 -0
- package/build/pushover.js.map +1 -0
- package/build/queue.js +178 -0
- package/build/queue.js.map +1 -0
- package/build/registry.js +3 -1
- package/build/registry.js.map +1 -1
- package/build/rule-input.js +17 -0
- package/build/rule-input.js.map +1 -0
- package/build/rules-service.js +402 -0
- package/build/rules-service.js.map +1 -0
- package/build/rules.js +842 -0
- package/build/rules.js.map +1 -0
- package/build/scheduler.js +274 -0
- package/build/scheduler.js.map +1 -0
- package/build/server.js +204 -0
- package/build/server.js.map +1 -1
- package/build/spend.js +8 -0
- package/build/spend.js.map +1 -1
- package/build/startup.js +30 -0
- package/build/startup.js.map +1 -1
- package/build/store.js +271 -0
- package/build/store.js.map +1 -1
- package/build/token.js +3 -3
- package/build/token.js.map +1 -1
- package/build/upgrade.js +105 -0
- package/build/upgrade.js.map +1 -0
- package/build/wait.js +32 -32
- package/build/wait.js.map +1 -1
- package/build/watchers/index.js +1 -0
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/linear-key.js +6 -45
- package/build/watchers/linear-key.js.map +1 -1
- package/build/watchers/linear-queue.js +124 -0
- package/build/watchers/linear-queue.js.map +1 -0
- package/build/watchers/linear.js +16 -7
- package/build/watchers/linear.js.map +1 -1
- package/build/why.js +78 -0
- package/build/why.js.map +1 -0
- package/mod/.claude-plugin/marketplace.json +6 -0
- package/mod/.claude-plugin/plugin.json +7 -0
- package/mod/README.md +17 -0
- package/mod/hooks/hooks.json +1 -0
- package/mod/hooks/model.ts +395 -0
- package/mod/hooks/register.tsx +505 -0
- package/mod/types/index.d.ts +57 -0
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,99 @@
|
|
|
2
2
|
|
|
3
3
|
Releases are tagged `conductor-v<version>` and staged on npm by `atk-conductor-release` (`pipelines/release-conductor.yml`), which writes each section below. `scripts/check-changelog.mjs` fails when a tag has no section here.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-dev.11](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.10&targetVersion=GTconductor-v1.0.0-dev.11) (2026-10-11)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **conductor:** scheduler, overrides and conduct why (ATK-315) ([ce85f79](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/ce85f793d7898f477a90ca1d51116bc41829cb46))
|
|
10
|
+
|
|
11
|
+
## [1.0.0-dev.10](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.9&targetVersion=GTconductor-v1.0.0-dev.10) (2026-10-11)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **conductor:** rules engine, settings and conduct rules (ATK-315) ([dd7110d](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/dd7110dfe48a2323ed73dac0f4a1d7334635f09d))
|
|
16
|
+
|
|
17
|
+
## [1.0.0-dev.9](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.8&targetVersion=GTconductor-v1.0.0-dev.9) (2026-10-11)
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **conductor:** read a bare HH:MM in conduct wait time (ATK-356) ([c988e8b](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c988e8b457ea8614eeb5fd078b4c033bac5ed54f))
|
|
22
|
+
|
|
23
|
+
## [1.0.0-dev.8](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.7&targetVersion=GTconductor-v1.0.0-dev.8) (2026-10-11)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **conductor:** report every Remote Control push outcome (ATK-313) ([f4d1e3c](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/f4d1e3c762f635789c6a5871eef99eddcc8d157b))
|
|
28
|
+
|
|
29
|
+
## [1.0.0-dev.7](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.6&targetVersion=GTconductor-v1.0.0-dev.7) (2026-10-11)
|
|
30
|
+
|
|
31
|
+
### Features
|
|
32
|
+
|
|
33
|
+
* **conductor:** route inbox items through a notifier (ATK-313) ([cec93e4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/cec93e4dd6a286c9a8d4a47a642eb84b28ba899f))
|
|
34
|
+
* **conductor:** switch notification profiles from the console (ATK-313) ([e011463](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/e011463f763736a1665be8ee1b7164395be331f9))
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* **conductor:** harden the notifier after review (ATK-313) ([0da9596](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/0da959630e50ccd820e9e625085943b1a245da1c))
|
|
39
|
+
* **conductor:** route reminders by the profile in force (ATK-313) ([987c2cc](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/987c2cc72bc3c6b5d0c2de73f9d7dd3e5a25ef33))
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
* **conductor:** say which day a notify rule's days match (ATK-313) ([6d175d3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6d175d3083b0f9276535758043c3b3bfeadca741))
|
|
44
|
+
|
|
45
|
+
## [1.0.0-dev.6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.5&targetVersion=GTconductor-v1.0.0-dev.6) (2026-10-11)
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* **conductor:** add conduct events, notify.requested and install-mod ([c0665ac](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c0665ac8be2854f73903605a49bf8283e6425703))
|
|
50
|
+
* **conductor:** add the conductor mod for Claude Code ([6f89cd4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6f89cd4ade58c9d10cc8882cac6890edc6d68258))
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* **conductor:** address review findings on the mod and its CLI ([c35c2da](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c35c2da943ff25bd3ff0854965a545fd17031768))
|
|
55
|
+
|
|
56
|
+
## [1.0.0-dev.5](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.4&targetVersion=GTconductor-v1.0.0-dev.5) (2026-10-11)
|
|
57
|
+
|
|
58
|
+
### Features
|
|
59
|
+
|
|
60
|
+
* **conductor:** add the inbox and conduct approve / revise (ATK-312) ([b187b8d](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b187b8d5101568ee6907bd577ef243c9aa6fdb5e))
|
|
61
|
+
|
|
62
|
+
### Bug Fixes
|
|
63
|
+
|
|
64
|
+
* **conductor:** address code review of the inbox (ATK-312) ([de75a51](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/de75a51e9d60a2873c54e169236552ff9f92e7ef))
|
|
65
|
+
* **conductor:** address review of the inbox (ATK-312) ([fa5fb30](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/fa5fb30bbfe7c3ada49b5d84b7776ce3727db0c7))
|
|
66
|
+
|
|
67
|
+
## [1.0.0-dev.4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.3&targetVersion=GTconductor-v1.0.0-dev.4) (2026-10-11)
|
|
68
|
+
|
|
69
|
+
### Features
|
|
70
|
+
|
|
71
|
+
* **conductor:** add conduct note, protocol v1's phase notes (ATK-311) ([c89342b](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c89342bb1eaaf23ae9041d529e131256a2f0cf62))
|
|
72
|
+
|
|
73
|
+
### Bug Fixes
|
|
74
|
+
|
|
75
|
+
* **conductor:** address code review of conduct note (ATK-311) ([9253da3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/9253da3bb673e1131f70fe666069e6307a40d599))
|
|
76
|
+
|
|
77
|
+
## [1.0.0-dev.3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.2&targetVersion=GTconductor-v1.0.0-dev.3) (2026-10-11)
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
* **conductor:** conduct upgrade refuses a downgrade (ATK-343) ([bb6dfdd](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/bb6dfdd2e86ad36cb3c0730407e7d3ac006abeba))
|
|
82
|
+
|
|
83
|
+
### Bug Fixes
|
|
84
|
+
|
|
85
|
+
* **conductor:** install the exact version upgrade compared (ATK-343) ([124def0](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/124def059730b2a6570fdbebbb303211c906201c))
|
|
86
|
+
|
|
87
|
+
## [1.0.0-dev.2](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.1&targetVersion=GTconductor-v1.0.0-dev.2) (2026-10-11)
|
|
88
|
+
|
|
89
|
+
### Features
|
|
90
|
+
|
|
91
|
+
* **conductor:** conduct upgrade stops, installs and restarts (ATK-340) ([76e6fa9](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/76e6fa9596e5992ddd0ef55e2b7ebbb863b8bbac))
|
|
92
|
+
|
|
93
|
+
### Bug Fixes
|
|
94
|
+
|
|
95
|
+
* **conductor:** make conduct upgrade's restart robust (ATK-340) ([eabe05f](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/eabe05fdd0a7435b09eadbd5f4bdd4c83b517a14))
|
|
96
|
+
* **conductor:** start the upgraded daemon in its home (ATK-340) ([bc0d46a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/bc0d46a516348123eff48e0856e00320649cf0fd))
|
|
97
|
+
|
|
5
98
|
## 1.0.0-dev.1 (2026-10-10)
|
|
6
99
|
|
|
7
100
|
### Features
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Conductor
|
|
2
2
|
|
|
3
|
-
`@detergent-software/conductor`: the local daemon that runs the work loop for every conducted project on this machine, and its `conduct` CLI. The design, decisions and build order are in the Linear document *Conductor: design note* (project *Conductor daemon*). This package has build step 1, the skeleton (ATK-308), step 2, the session manager (ATK-309),
|
|
3
|
+
`@detergent-software/conductor`: the local daemon that runs the work loop for every conducted project on this machine, and its `conduct` CLI. The design, decisions and build order are in the Linear document *Conductor: design note* (project *Conductor daemon*). This package has build step 1, the skeleton (ATK-308), step 2, the session manager (ATK-309), step 3, the watchers and `conduct wait` (ATK-310), the daemon's side of step 4, worker protocol v1's `conduct note` (ATK-311), step 5, the inbox with `conduct approve` / `revise` (ATK-312), step 6, the notifier (ATK-313), step 9, the conductor mod for Claude Code in `mod/` (ATK-265), and most of step 8, the rules engine and the scheduler (ATK-315; the rest of the rule actions follow).
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
@@ -14,9 +14,37 @@ conduct launch <item> start a worker: worktree, background session,
|
|
|
14
14
|
[--project <name>] [--probe] [--prompt <text>] [--conductor <name>]
|
|
15
15
|
conduct retire <worker> [--force] stop a worker, remove its worktree and session
|
|
16
16
|
conduct attach <worker> wake a parked worker (no event text) and open it in a tab
|
|
17
|
+
conduct inbox [--json] what waits on you, with its commands and links (see Inbox)
|
|
18
|
+
conduct approve <item> [--text <text>]
|
|
19
|
+
approve a plan: post "approve" on the item as you, wake its worker
|
|
20
|
+
conduct revise <item> <text> ask for another plan: post "revise: <text>", wake its worker
|
|
21
|
+
conduct done <id> mark an inbox item done (a physical action, or one you handled)
|
|
22
|
+
conduct notify [--json] the notification profile, quiet hours, each channel's state
|
|
23
|
+
conduct notify profile [<name>] [--until <when>]
|
|
24
|
+
switch where pushes go (see Notifications)
|
|
25
|
+
conduct notify test [--kind <kind>] [--channel remote-control|pushover] [--raise]
|
|
26
|
+
push a sample item now, or raise a real one with --raise
|
|
27
|
+
conduct queue [--json] [--fresh] what the scheduler picks next and why the rest wait (see Scheduler)
|
|
28
|
+
conduct pause [<project>] [--until <when>]
|
|
29
|
+
no new launches in a project, or in all of them, until a time
|
|
30
|
+
conduct unpause [<project>] launch again
|
|
31
|
+
conduct focus <project> [--slots <n>] [--weight <n>] --until <when>
|
|
32
|
+
a project's slots or ordering weight, for a while
|
|
33
|
+
conduct why <event id | item> [--json]
|
|
34
|
+
the rule behind an action, and the events that led to it
|
|
35
|
+
conduct rules [--json] the rules in force, what replaces what, any file refused (see Rules)
|
|
36
|
+
conduct rules test <file> [--json]
|
|
37
|
+
dry-run the rules on a sample event; acts on nothing
|
|
38
|
+
conduct note <item> <file> post a phase note on the item and record its phase (see Notes)
|
|
39
|
+
[--worker <worker>]
|
|
17
40
|
conduct wait <condition> wait for an outside event (see Waits)
|
|
41
|
+
conduct events [--since <id>] [--follow]
|
|
42
|
+
the event log, one JSON event per line (see API)
|
|
18
43
|
conduct install-startup start the daemon at logon (Windows Task Scheduler)
|
|
19
44
|
conduct install-startup --remove remove that logon task
|
|
45
|
+
conduct install-mod [--remove] install the conductor mod in Claude Code (see Mod)
|
|
46
|
+
conduct upgrade [--tag dev|latest] [--allow-downgrade]
|
|
47
|
+
stop the daemon, npm install the tag's newest version, start it again
|
|
20
48
|
```
|
|
21
49
|
|
|
22
50
|
A `<worker>` is its item (`ATK-309`) or its name (`probe-ATK-309`).
|
|
@@ -32,12 +60,145 @@ The session manager owns the `claude --bg` sessions it launches, and only those:
|
|
|
32
60
|
- **restarts:** a launch the daemon was stopped in adopts its session if one was started, and otherwise fails and removes its clean worktree.
|
|
33
61
|
- **attach:** a parked worker is resumed with no event text, so it wakes idle and keeps running when its tab closes. An attached worker isn't parked until it registers a new wait, or until its turn has been over `sessions.idleMinutes` (`worker.detached`). The daemon can't see the tab, so this also delivers a wait that resolved while it was attached.
|
|
34
62
|
- **park failures:** a session whose process outlives `claude stop` by 30 s logs one `worker.park-failed` per spell, and the park is tried again every 5 minutes.
|
|
35
|
-
- **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell.
|
|
36
|
-
- **spend:** tokens from the session's transcript and its subagents' (`~/.claude/projects/*/<session>.jsonl`), each message counted once, go into `spend` per worker and local day. It keeps input, output, cache read and cache write; `tokens` is their sum.
|
|
63
|
+
- **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell, and `worker.stall-cleared` when that spell ends.
|
|
64
|
+
- **spend:** tokens from the session's transcript and its subagents' (`~/.claude/projects/*/<session>.jsonl`), each message counted once, go into `spend` per worker and local day. It keeps input, output, cache read and cache write; `tokens` is their sum. A worker whose tokens in all, not counting cache reads, pass `inbox.runawayTokens` logs `worker.runaway` once per daemon run. Cache reads are left out because they are nearly all of a session's tokens (97% on ATK-312) at a tenth of the price.
|
|
37
65
|
- **probes:** `--probe` names the worktree and session `probe-<item>` and gives it a prompt that works no issue, so tests stay out of the live fleet.
|
|
38
66
|
|
|
39
67
|
Worker statuses: `launching`, `running`, `parked`, `stopped-unexpectedly`, `failed`, `exited`. Every step is an event (`worker.launched`, `worker.parked`, `worker.resumed`, …) on `GET /events`.
|
|
40
68
|
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one line>]`, such as `## work-issue · PR opened`) with `conduct note <item> <file>`. The daemon posts the file as a comment on the item in the tracker, then records the phase at once: on the worker (`phase` in `GET /fleet` and `conduct status`), on the item, and as a `note.posted` event. The worker is `--worker <worker>`, else the one whose worktree is the current checkout, else the project's live worker on that item. The project is the worker's, else the one whose tracker team is the item's key prefix.
|
|
72
|
+
|
|
73
|
+
| Exit | Means | The worker then |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| 0 | posted | goes on |
|
|
76
|
+
| 3 | nobody posted it: no daemon, one without notes, or the tracker refused it (such as a read-only key) | posts the note to the tracker itself, as before protocol v1 |
|
|
77
|
+
| 1 | refused (no heading, no project) or unclear (the daemon or Linear didn't answer the write) | reads the message; when it says the note may be there, looks before posting again |
|
|
78
|
+
|
|
79
|
+
On Linear the daemon uses the key the watchers read (below), so that key needs **Create comments** as well as Read. Comments it writes carry the key owner's name, as the Linear MCP's do.
|
|
80
|
+
|
|
81
|
+
## Inbox
|
|
82
|
+
|
|
83
|
+
The `inbox` table holds everything that waits on Jason, across projects. Each item records:
|
|
84
|
+
|
|
85
|
+
- its kind;
|
|
86
|
+
- the item and worker it is about;
|
|
87
|
+
- one line of text;
|
|
88
|
+
- the exact commands and links;
|
|
89
|
+
- when it was raised.
|
|
90
|
+
|
|
91
|
+
An item resolves itself when its condition is seen, and records what resolved it. `inbox.raised` and `inbox.resolved` events go on `GET /events`. The notifier pushes each new item to the phone (see Notifications).
|
|
92
|
+
|
|
93
|
+
| Kind | Raised by | Resolved by |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `plan` | a `Plan` note (`conduct note`), or an `approval` wait | the approval wait resolving (an `approve` or `revise:` comment), a `Plan approved` note, a newer Plan, or `conduct approve` / `revise` |
|
|
96
|
+
| `merge` | a `Handoff · merge: <PR>` note; a `pr … merged` wait attaches to it | its PR wait resolving as merged, abandoned or closed, or a `Landed` note. A PR wait without the note raises nothing, because auto landing needs nobody |
|
|
97
|
+
| `npm-otp` | a `Handoff · npm-otp` note (the `npm stage approve …` on its heading is the command), or an `npm` wait | the version on its tag, or a `Resumed` or `Landed` note |
|
|
98
|
+
| `jason`, `human`, `scope`, `blocked`, `abandoned`, `failed` | a Handoff note of that kind | any later note on the item (such as `Resumed`), or `conduct done` |
|
|
99
|
+
| `permission`, `stall` | `worker.stalled` | `worker.stall-cleared`, a park or resume, a new wait (stall only), or a daemon restart (it is raised again if it persists) |
|
|
100
|
+
| `failed` | a failed launch, or a session that stopped unexpectedly | a new launch, the worker running again, or its retirement |
|
|
101
|
+
| `runaway` | a worker passing `inbox.runawayTokens` (2M) input, output and cache-write tokens in all, once per worker | `conduct done`, or its retirement |
|
|
102
|
+
|
|
103
|
+
Nothing raises `ready-to-release`, `queue-drained` or `prod-approval` yet. The scheduler (ATK-315) and `/release-atk` will raise them. When a worker is retired, its open items resolve with it.
|
|
104
|
+
|
|
105
|
+
For an item raised from a note:
|
|
106
|
+
|
|
107
|
+
- The commands are the inline code on the note's heading and the lines of its shell-tagged fenced code blocks (`bash`, `sh`, `shell`, `powershell`, `pwsh`, `ps1`, `cmd`, `console`). An untagged block is output, not a command.
|
|
108
|
+
- The links are the comment's URL and every URL in the note.
|
|
109
|
+
|
|
110
|
+
`conduct approve ATK-312` (or the inbox id written `#12`; a bare number is refused, since it could mean an issue) posts `approve` on the item through the tracker, or `approve: <text>` with `--text`. It uses the same key as notes, so the comment carries Jason's name and counts as his answer. `conduct revise ATK-312 "<text>"` posts `revise: <text>`; every word after the item is the text, quoted or not. Either one then:
|
|
111
|
+
|
|
112
|
+
1. resolves the plan item and the worker's open `approval` wait;
|
|
113
|
+
2. has the session manager resume a parked worker with `[conductor] event: approval ATK-312: approve HH:MM`. The worker reads the comment on the tracker before it acts.
|
|
114
|
+
|
|
115
|
+
If the tracker refuses the comment, nothing is resolved. Only a plan takes approve or revise; `conduct done <id>` resolves any item.
|
|
116
|
+
|
|
117
|
+
## Notifications
|
|
118
|
+
|
|
119
|
+
The notifier routes each new inbox item to the phone, on two channels:
|
|
120
|
+
|
|
121
|
+
- **Remote Control:** the daemon appends `notify.requested` (`channel: "remote-control"`), and the conductor mod in the console pushes it with PushNotification. It reaches the phone only while the console is connected to Remote Control (see Mod).
|
|
122
|
+
- **Pushover:** sent from the daemon. The keys are `PUSHOVER_TOKEN` and `PUSHOVER_USER`, or else the Windows generic credential `atk-conductor-pushover` (`notify.pushover.credential`): its **user name is your Pushover user key** and its **password is the application's API token**. Save it with `cmdkey /generic:atk-conductor-pushover /user:<user key> /pass:<app token>` or in Credential Manager. A credential saved while the daemon runs is read on the next push. The keys are never logged or stored.
|
|
123
|
+
|
|
124
|
+
**Profiles** pick the channels. A profile is a list of rules, and the first rule that matches an item says where it goes; when none matches, the item stays in the inbox, unpushed. Three are built in, and `notify.profiles` in `config.json` replaces one by name or adds more:
|
|
125
|
+
|
|
126
|
+
```jsonc
|
|
127
|
+
{
|
|
128
|
+
"quietHours": { "from": "22:00", "to": "07:00" }, // local; omit for none
|
|
129
|
+
"notify": {
|
|
130
|
+
"profile": "default", // the profile when none is switched to
|
|
131
|
+
"profiles": {
|
|
132
|
+
"default": [{ "to": ["remote-control"] }], // built in
|
|
133
|
+
"away": [{ "to": ["pushover"] }], // built in
|
|
134
|
+
"focus": [{ "kinds": ["npm-otp", "permission", "failed"], "to": ["remote-control"] }], // built in
|
|
135
|
+
"workday": [
|
|
136
|
+
{ "projects": ["cadence"], "days": ["sat", "sun"], "to": [] }, // keep these off every channel
|
|
137
|
+
{ "kinds": ["npm-otp"], "to": ["pushover", "remote-control"], "remind": { "afterMinutes": 10, "times": 5 } },
|
|
138
|
+
{ "hours": "08:00-18:00", "to": ["remote-control"] }
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A rule matches on `kinds` (inbox kinds), `projects`, `hours` (local, `HH:MM-HH:MM`, may run past midnight) and `days` (`sun` … `sat`, the day when the item is routed, so 01:00 on Saturday is `sat` even inside a Friday `22:00-02:00`); a rule with none of them matches everything. `conduct notify profile away` switches at once, without a restart, and the profile survives one. `--until 17:00` (the next 17:00), `--until 2026-10-12` (local midnight) or `--until 2026-10-12T09:00` hands back to the profile that was in force before. Switching to config.json's own `notify.profile` without `--until` hands the choice back to config.json. Each switch is a `notify.profile` event.
|
|
146
|
+
|
|
147
|
+
- **Quiet hours** (`quietHours`): pushes are held (`notify.routed` with `outcome: "held"`). When they end, each channel gets one summary of the held items still open, such as `2 waiting since quiet hours: ATK-1 npm-otp, ATK-2 plan; 1 resolved meanwhile`, routed by the profile then in force. `conduct notify test` pushes at any hour.
|
|
148
|
+
- **Dedupe:** the same project, item, kind and text pushed within `notify.dedupeMinutes` (30) to the channels it would go to now isn't pushed again; a newer note that replaces an item is the usual case. Its reminders carry on from the first push, on the newer item only. A copy that never reached the phone (held, or a failed push) gives way to the newer one.
|
|
149
|
+
- **Reminders:** an open item is pushed again `notify.remind.afterMinutes` (30) after its last push, at most `notify.remind.times` (2) times, as `reminder 1/2: …`, on the channels the profile in force then gives it. A rule's own `remind` overrides that. A resolved item is never reminded. A first push that reached no channel (Pushover down, no credential) is tried again at the same interval without using up a reminder.
|
|
150
|
+
- **Events:** `notify.routed` (the profile, the rule, the channels, `sent`, `held`, `deduped` or `none`), `notify.requested`, `notify.sent` and `notify.failed` (Pushover), `notify.channel` (a channel's state changed), `notify.profile`.
|
|
151
|
+
|
|
152
|
+
`conduct notify` shows the profile, quiet hours and the last state of each channel: Pushover from its own answers, Remote Control from what the mod reports back from PushNotification after each push: `ok` (it reached the phone), `inactive` (Remote Control off, or mobile pushes off in Claude Code's config), `skipped` (not sent to the phone on purpose, such as while you are at the terminal: `user_present`) or `failing` (the call failed), each with its reason.
|
|
153
|
+
|
|
154
|
+
## Rules
|
|
155
|
+
|
|
156
|
+
Rules say what the conductor does when something happens (design note, section 9). Each is a `when` and a `do` in a fixed vocabulary, written in JSONC:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
// ~/.conductor/rules.json
|
|
160
|
+
[
|
|
161
|
+
{ "when": "wait.kind == merge and wait.age > 2h", "do": "remind jason" },
|
|
162
|
+
{ "when": "plan.tier == S and item.area in [docs, ops]", "do": "auto-approve after 15m" },
|
|
163
|
+
{ "id": "runaway", "when": "spend.worker > 5M", "do": "inbox runaway" }, // replaces the default
|
|
164
|
+
{ "id": "daily-cap", "do": "none" }, // switches a default off
|
|
165
|
+
{ "when": "project == web and day == saturday", "do": "pause launches" }
|
|
166
|
+
]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- **Sources, in order:** the item's project's `rules` (in its `.claude/conductor.json`, changed by PR), then `~/.conductor/rules.json`, then the built-in defaults: conduct-atk's behavior written as rules (`launch`, `retire-closed`, `park-on-wait`, `resume-on-wait`, `runaway`, `daily-cap`, `ready-to-release`, `queue-drained`). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences.
|
|
170
|
+
- **when:** `and`, `or`, `not` and parentheses over comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`, `in [a, b]`, `<label> in item.labels`) and events. An event rule fires when one of its events happens: `note`, `plan`, `wait.resolved`, `run.failed`, `worker.idle`, `worker.exited`, `worker.permission`, `worker.waiting`, `inbox.raised`, `pr.comment`, `item.unblocked`, `item.closed`, `queue.pick`, `queue.drained`, `queue.ready-to-release`. A rule without one is a state rule, checked on each scheduler tick against each wait, inbox item, worker or project, or once for the machine, by the fields it reads. Fields: `project`, `item.id/area/labels/tier/priority`, `plan.tier`, `note.phase/kind`, `wait.kind/age`, `inbox.kind/age`, `worker.retries/phase`, `spend.worker/item/project/today`, `time`, `day`, `notify.profile`. Values carry units: `15m`, `2h`, `500k`, `2M`, `22:00`, `saturday`.
|
|
171
|
+
- **do:** `launch`, `retire`, `park`, `resume worker`, `retry`, `pause launches [in <project>] [until <when> | for <duration>]`, `pause all launches …`, `unpause launches`, `focus [<project>] weight|slots <n> until <when>`, `auto-approve after <duration>`, `inbox <kind> ["<text>"]`, `remind jason`, `notify <channel> ["<text>"]`, `notify profile <name> [until <when>]`, `label add|remove human|triage|shipped-open`, `flag shipped-open`, `escalate console "<ask>"`, `none`. One string, or a list.
|
|
172
|
+
- **Guardrails:** rules act inside Area locks, slots, quiet hours and config.json's limits. A rule that would go past one is refused when it loads: `focus … slots` over `scheduler.slots`, an override with no end or one over 14 days, `retire` on anything but `item.closed`, `launch` outside `queue.pick` / `item.unblocked`. `merge`, `publish`, `approve`, `delete` and `run` aren't in the vocabulary.
|
|
173
|
+
- **Reload:** saving `rules.json` or a project's profile reloads its rules. A profile save takes only the `rules`; the rest of a profile changes with `conduct project add`. A file the conductor refuses leaves its last good rules in force, logs `rules.rejected` and raises a `failed` inbox item until it loads again (`rules.loaded`), including after a restart. `conduct project add` refuses a profile whose rules don't load, with or without the daemon.
|
|
174
|
+
- **Checking:** `conduct rules` lists the rules in force (`project.atk:#2`, `machine:runaway`, `default:launch`) and what replaces what. `conduct rules test <file>` dry-runs a sample, such as `{ "event": "pr.comment", "project": "atk", "fields": { "wait.kind": "merge", "wait.age": "3h" } }` (leave out `event` for a state rule), and shows what fires, each action's guardrail, and why every other rule of the trigger didn't. Both read the files directly when the daemon isn't running.
|
|
175
|
+
|
|
176
|
+
- **Why:** every action the conductor takes is an event that names its rule, directly (`rule`) or through the event that caused it (`data.cause`). `conduct why <event id>` follows that chain back and shows the rule as it fired; `conduct why ATK-315` does it for the item's newest actions.
|
|
177
|
+
|
|
178
|
+
In this build the scheduler's rules act: `launch`, `retire`, `park` and `resume`, and the `inbox` of `queue.drained` and `queue.ready-to-release`. State rules (`wait.age`, `spend.*`, `day`) and the other actions follow in the last part of ATK-315.
|
|
179
|
+
|
|
180
|
+
## Scheduler
|
|
181
|
+
|
|
182
|
+
The scheduler moves conduct-atk's tick into the daemon (design note, section 8). Every `scheduler.tickSeconds` (120), and 10 s after a phase note, a retire, a failed launch or a cleared inbox item, it does three things.
|
|
183
|
+
|
|
184
|
+
1. **Read.** It reads each conducted project's queue from its tracker: for Linear, the team's projects with a priority, the Area labels, every open item with its blockers, and the in-flight items. Each read is one request (plus pages), within the Linear watcher's rate budget.
|
|
185
|
+
2. **Plan.** It works out the picks across projects, with the same order and reasons as conduct-atk's `planQueue`. A replay of conduct-atk's own Linear read is in the tests.
|
|
186
|
+
- **Order:** the tracker project's priority, then the conductor project's `weight` (profile, or `conduct focus --weight`), then the item's priority, then when it was created.
|
|
187
|
+
- **Areas:** one in-flight item per Area per project. An Area is held by every worker in flight and every started item, and by the Areas it covers or is covered by.
|
|
188
|
+
- **Not picked:** items labelled with the profile's human label, and items whose blockers are still open, which wait (pending, or "not being worked" when nothing will clear them).
|
|
189
|
+
- **Slots:** `scheduler.slots` (4) across projects and the profile's `slots` (2) per project, or `conduct focus --slots`, never more than the global ones. Slots count running workers only: a parked worker keeps its Area and frees its slot.
|
|
190
|
+
- **Holds:** quiet hours and `conduct pause` turn picks into waits that say why.
|
|
191
|
+
3. **Act.** It runs the rules on what it found: `queue.pick` → `launch` (default `launch`), `item.closed` → `retire` (default `retire-closed`; a refused retire raises an inbox item once), `queue.drained` and `queue.ready-to-release` → an inbox item once a day. Each action is a `rule.fired` event, then `rule.done` or `rule.failed`. A launch that failed isn't tried again while its inbox item is open.
|
|
192
|
+
|
|
193
|
+
`scheduler.mode` decides how far it goes:
|
|
194
|
+
- **`observe`** (the default while conduct-atk's loop still launches, until ATK-314): it reads, plans and logs each action once, and does none of them.
|
|
195
|
+
- **`on`:** it acts.
|
|
196
|
+
- **`off`:** it doesn't run; `conduct queue --fresh` still works out a plan on request.
|
|
197
|
+
|
|
198
|
+
`conduct queue` shows the last plan (`--fresh` reads again): slots, holds, picks with their Area, and why each other item waits. A change of plan is a `scheduler.planned` event. A project whose read fails picks nothing that tick and logs `scheduler.read-failed` once, then `scheduler.read-recovered`. An item whose last open blocker closed is an `item.unblocked` event.
|
|
199
|
+
|
|
200
|
+
**Overrides** end on their own: `conduct pause atk --until 17:00`, `conduct pause --until tomorrow` (every project), `conduct unpause atk`, `conduct focus atk --slots 3 --until monday`. `--until` takes `17:00` (the next one), `tomorrow`, a day, or a date and time. Each is an `override.set` event, which `conduct why` attributes to the command, and they show in `conduct rules`.
|
|
201
|
+
|
|
41
202
|
## Waits
|
|
42
203
|
|
|
43
204
|
A worker waits by posting its note, running `conduct wait …` and ending the turn (worker protocol v1). The verbs take the repo, host and package from the worktree's `.claude/conductor.json`; the full form is accepted too, and is what the store keeps:
|
|
@@ -66,7 +227,8 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
|
|
|
66
227
|
|
|
67
228
|
| File | Holds |
|
|
68
229
|
|---|---|
|
|
69
|
-
| `config.json` | machine settings, JSONC (comments allowed), validated at startup; `sessions` holds `pollSeconds` (10), `settleSeconds` (5), `permissionMinutes` (5), `idleMinutes` (20); `watchers` holds `ado`, `github`, `health`, `linear` and `npm`, each `{ intervalSeconds, perMinute }` (30/60, 30/30, 30/30, 30/10, 20/30), and `statsMinutes` (10) |
|
|
230
|
+
| `config.json` | machine settings, JSONC (comments allowed), validated at startup; `inbox.runawayTokens` (2000000); `notify` and `quietHours` (see Notifications); `scheduler` holds `mode` (`observe`, `on` or `off`), `slots` (4, across projects, and the most any rule can give one project), `dailyTokens` (20000000, the `daily-cap` rule's number) and `tickSeconds` (120); `sessions` holds `pollSeconds` (10), `settleSeconds` (5), `permissionMinutes` (5), `idleMinutes` (20); `watchers` holds `ado`, `github`, `health`, `linear` and `npm`, each `{ intervalSeconds, perMinute }` (30/60, 30/30, 30/30, 30/10, 20/30), and `statsMinutes` (10) |
|
|
231
|
+
| `rules.json` | machine rules, JSONC, reloaded on save (see Rules) |
|
|
70
232
|
| `token` | the API token, readable by this user only; every request sends `Authorization: Bearer <token>` |
|
|
71
233
|
| `conductor.pid`, `port` | the single-instance lock and where the API listens (`127.0.0.1`) |
|
|
72
234
|
| `conductor.db` | SQLite state, including the append-only `events` log |
|
|
@@ -75,12 +237,28 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
|
|
|
75
237
|
## API
|
|
76
238
|
|
|
77
239
|
- `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one.
|
|
78
|
-
- `GET /events`: the event log as server-sent events. Add `?since=<id>` (or send `Last-Event-ID`) to replay from an id. Without either, the stream starts at the live end.
|
|
240
|
+
- `GET /events`: the event log as server-sent events. Add `?since=<id>` (or send `Last-Event-ID`) to replay from an id. Without either, the stream starts at the live end. `conduct events` prints it one JSON event per line: the events after `--since` (all of them without it), or with `--follow` everything from there on (live without `--since`). `--follow` reconnects with `Last-Event-ID` when the daemon goes away, so a restart loses nothing, and prints the stream's state as lines without an id: `{"kind":"conduct.connected"}` and, once per outage, `{"kind":"conduct.disconnected","error":…}`.
|
|
79
241
|
- `POST /shutdown`: a clean stop (`conduct stop`).
|
|
80
242
|
- `GET /fleet`: the workers in flight, with session activity, waits and spend (today, total).
|
|
81
243
|
- `POST /workers` with `{ "item": "ATK-309", "project"?, "probe"?, "prompt"?, "conductor"? }`: launch; answers 202 with the `launching` worker. `GET /workers/:id` shows it with its latest events.
|
|
82
244
|
- `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work when it refuses.
|
|
83
245
|
- `POST /workers/:id/attach`: wake if parked, mark attached, open the tab.
|
|
246
|
+
- `POST /items/:id/notes` with `{ "body": "## work-issue · …", "worker"?: 1 }`: post a phase note (see Notes); 201 with the note and the worker. 409 when it won't post it; 502 when the tracker didn't take it, with `posted: "no"` or `"unknown"`.
|
|
247
|
+
- `GET /inbox`: the open inbox items, oldest first.
|
|
248
|
+
- `POST /inbox/:id/resolve`: Jason's answer (see Inbox). The body is `{ "action": "approve", "text"? }`, `{ "action": "revise", "text" }` or `{ "action": "done" }`.
|
|
249
|
+
- 200 with the item, the comment and the waits it resolved.
|
|
250
|
+
- 404 when there is no such item.
|
|
251
|
+
- 409 when the item is resolved already or isn't a plan.
|
|
252
|
+
- 502 with `posted` when the tracker didn't take the comment.
|
|
253
|
+
- `GET /notify`: the profile (and until when), the profiles, quiet hours, the held count and each channel's state.
|
|
254
|
+
- `POST /notify/profile` with `{ "name": "away", "until"?: "<ISO time>" }`: switch; 409 for a profile that doesn't exist.
|
|
255
|
+
- `POST /notify/test` with `{ "kind"?: "npm-otp", "channel"?: "pushover", "raise"?: false }`: push a sample now and answer each channel's result, or with `raise` put a real item in the inbox (201).
|
|
256
|
+
- `POST /notify/report` with `{ "channel": "remote-control", "result": "<PushNotification's answer>", "state"?: "ok" | "inactive" }`: the mod's report of a push.
|
|
257
|
+
- `GET /rules`: the rules in force, each with its source, `ref`, `when`, `do` and the rules that replace it, plus any file refused and the live `overrides` (see Rules).
|
|
258
|
+
- `GET /queue`: the scheduler's last plan; `?fresh=true` reads the trackers and plans now (see Scheduler).
|
|
259
|
+
- `GET /overrides`, and `POST /overrides` with `{ "action": "pause", "project"?, "until"? }`, `{ "action": "unpause", "project"? }` or `{ "action": "focus", "project", "slots"?, "weight"?, "until" }` (and `by`, who set it): 201 with the overrides; 409 for an unknown project, a time past, or a focus over `scheduler.slots`.
|
|
260
|
+
- `GET /why/:id`: the event's chain of causes and the rule behind it; `GET /why?item=ATK-315` the same for the item's newest actions.
|
|
261
|
+
- `POST /rules/test` with `{ "event"?: "pr.comment", "project"?: "atk", "fields"?: { … } }`: a dry run; 400 for a sample it can't read.
|
|
84
262
|
- `POST /waits` with `{ "worker": 1, "condition": "pr:ado:Emergent.AgenticToolkit#1850:merged" }`: a wait in its full form (see Waits), stored in its normal form; 201 with the wait. `GET /waits/:id` shows one, with its resolution once it has one.
|
|
85
263
|
- A refusal (a duplicate launch, a bad condition, a busy worker) is a 409 with `error` and `details`.
|
|
86
264
|
|
|
@@ -96,14 +274,14 @@ conduct project add <path to the project's main checkout>
|
|
|
96
274
|
|
|
97
275
|
`conduct project add` walks up from the path it is given to the directory that holds `.claude/conductor.json`, and maps a worktree to its main checkout.
|
|
98
276
|
|
|
99
|
-
The logon task runs `node` on the `cli.js` that ran `install-startup`. A global install keeps that path across upgrades, so the task needs registering once. To upgrade
|
|
277
|
+
The logon task runs `node` on the `cli.js` that ran `install-startup`. A global install keeps that path across upgrades, so the task needs registering once. To upgrade:
|
|
100
278
|
|
|
101
279
|
```bash
|
|
102
|
-
conduct
|
|
103
|
-
npm install --global @detergent-software/conductor@dev
|
|
104
|
-
conduct serve
|
|
280
|
+
conduct upgrade
|
|
105
281
|
```
|
|
106
282
|
|
|
283
|
+
It takes the tag the installed version came from (`dev` for an `X.Y.Z-dev.N`, `latest` otherwise; `--tag` picks one), and does nothing when that tag's version is already installed. When the tag points at an older version (semver, so `1.2.0-dev.4` is older than `1.2.0`), it refuses before it stops anything and names that version; `--allow-downgrade` installs it anyway and reports `downgraded`. Otherwise it stops the daemon, runs `npm install --global @detergent-software/conductor@<the tag's version>` (the exact version it compared, in case the tag moves) and starts the daemon again: through the `Conductor` logon task when the task runs this `cli.js`, else as a detached `conduct serve`. A daemon that wasn't running stays stopped. If npm fails, the daemon still starts again on whatever npm left in place, and `upgrade` exits 1 with npm's output. A linked build (below) refuses: its `cli.js` isn't under npm's global `node_modules`.
|
|
284
|
+
|
|
107
285
|
**From a checkout instead (development).** `npm link` puts the main checkout's build on PATH, so every worktree and the logon task run that build. Run it from the main checkout, and upgrade by pulling `develop`, building, then `conduct stop` and `conduct serve`:
|
|
108
286
|
|
|
109
287
|
```bash
|
|
@@ -115,6 +293,27 @@ conduct install-startup
|
|
|
115
293
|
|
|
116
294
|
To switch from a linked build to the package: `conduct stop`, `npm rm --global @detergent-software/conductor` (removes the link), install the package as above, and run `conduct install-startup` again so the logon task points at the installed `cli.js`.
|
|
117
295
|
|
|
296
|
+
## Mod
|
|
297
|
+
|
|
298
|
+
`mod/` is the conductor mod: a Claude Code plugin (a hooks module, code that costs no tokens) that does the console's plumbing. `conduct install-mod` installs it for this user from the package's own `mod/` folder, as a folder marketplace (`claude plugin install conductor --marketplace <package>/mod --scope user`). Claude Code reads a folder marketplace's plugin in place, so `conduct upgrade` replaces the mod along with the daemon; open sessions load the new version with `/reload-plugins`. `--remove` uninstalls it.
|
|
299
|
+
|
|
300
|
+
- **Every session** shows a status line, `conductor 3▶ 2⏸ ✉2` (running, parked, inbox), from `GET /fleet` and `GET /inbox` every 30 s. It clears while no daemon answers.
|
|
301
|
+
- **The console**, the session started with `CONDUCTOR_CONSOLE=1` (`$env:CONDUCTOR_CONSOLE=1; claude -n conductor` until the console skill, ATK-314, starts it), also gets:
|
|
302
|
+
- **the band** above the prompt while the inbox has items: one hotkey per item (a plan approves after a second press, `y`, since a digit typed into an empty prompt presses a band button; an npm OTP copies its `npm stage approve` line; the rest open the pane), and `o` for the pane;
|
|
303
|
+
- **the pane** (`/conductor`, `o`, or opened at the start, docked as a sidebar on a fullscreen terminal of 144 columns or more): the notification profile (a picker; buttons on mobile) and Remote Control's state, the inbox with each item's actions (approve, revise, copy a command, open a link, done), the workers by project (a row expands to attach), and today's spend;
|
|
304
|
+
- **commands answered in code**, never by the model: `/conductor`, `/inbox`, `/approve <item | #id>`, `/revise <item | #id> <text>`, `/attach <item | worker>`, `/notify [<profile> [--until <when>]]`;
|
|
305
|
+
- **the stream**: it runs `node <package>/build/cli.js events --follow`, redraws on each event, toasts each new inbox item, and acts on three events:
|
|
306
|
+
|
|
307
|
+
| Event | Data | The mod |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `notify.requested` | `channel: "remote-control"`, `message` | pushes it to the phone (PushNotification), with no model turn, and reports every outcome to `POST /notify/report` (see Notifications). When the push comes back "Remote Control inactive" (PushNotification's `disabledReason` `no_transport`), mobile pushes are off in Claude Code's config (`config_off`), or the call fails, it toasts once, saying which; it says so again only after a push has reached the phone. A push Claude Code holds back while you are at the terminal (`user_present`) is reported as `skipped`, with no toast |
|
|
310
|
+
| `console.escalated` | `text` | wakes the console's model once with `[conductor] escalation on <item>: <text>`; nothing else wakes it |
|
|
311
|
+
| `worker.message-requested` | `to`, `message` | sends the message to that worker's session |
|
|
312
|
+
|
|
313
|
+
The notifier emits `notify.requested` (see Notifications). The rules engine (ATK-315) will emit the other two.
|
|
314
|
+
|
|
315
|
+
Its checks: `pnpm -C apps/conductor test` runs `mod/tests/model.spec.ts` (the logic, in CI). `claude plugin validate apps/conductor/mod` and `claude plugin test apps/conductor/mod` (the hooks against the engine, UI on the terminal, desktop, VS Code and mobile surfaces) need Claude Code, so they run locally. To try a change before installing it: `claude --plugin-dir apps/conductor/mod`.
|
|
316
|
+
|
|
118
317
|
## Releases
|
|
119
318
|
|
|
120
319
|
`atk-conductor-release` (`pipelines/release-conductor.yml`) runs semantic-release on each push to `develop` or `main` that touches `apps/conductor/`. It counts only commits that touch this directory, writes `CHANGELOG.md`, tags `conductor-v<version>` (`X.Y.Z-dev.N` on `develop`), and stages the version on npm under `dev` or `latest`. A maintainer publishes it with the `npm stage approve <id> --otp=<code>` line from the run summary.
|
|
@@ -127,4 +326,6 @@ pnpm -C apps/conductor typecheck
|
|
|
127
326
|
pnpm -C apps/conductor test
|
|
128
327
|
pnpm -C apps/conductor build
|
|
129
328
|
cd apps/conductor && node scripts/check-changelog.mjs
|
|
329
|
+
claude plugin validate apps/conductor/mod
|
|
330
|
+
claude plugin test apps/conductor/mod
|
|
130
331
|
```
|