@detergent-software/conductor 0.0.0-stage → 1.0.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +131 -2
  3. package/build/claude.js +102 -0
  4. package/build/claude.js.map +1 -0
  5. package/build/cli.js +450 -0
  6. package/build/cli.js.map +1 -0
  7. package/build/client.js +64 -0
  8. package/build/client.js.map +1 -0
  9. package/build/conditions.js +198 -0
  10. package/build/conditions.js.map +1 -0
  11. package/build/config.js +74 -0
  12. package/build/config.js.map +1 -0
  13. package/build/daemon.js +127 -0
  14. package/build/daemon.js.map +1 -0
  15. package/build/errors.js +17 -0
  16. package/build/errors.js.map +1 -0
  17. package/build/jsonc.js +24 -0
  18. package/build/jsonc.js.map +1 -0
  19. package/build/lock.js +64 -0
  20. package/build/lock.js.map +1 -0
  21. package/build/log.js +43 -0
  22. package/build/log.js.map +1 -0
  23. package/build/manager.js +614 -0
  24. package/build/manager.js.map +1 -0
  25. package/build/paths.js +16 -0
  26. package/build/paths.js.map +1 -0
  27. package/build/profile.js +74 -0
  28. package/build/profile.js.map +1 -0
  29. package/build/registry.js +33 -0
  30. package/build/registry.js.map +1 -0
  31. package/build/resolve.js +28 -0
  32. package/build/resolve.js.map +1 -0
  33. package/build/run.js +52 -0
  34. package/build/run.js.map +1 -0
  35. package/build/server.js +227 -0
  36. package/build/server.js.map +1 -0
  37. package/build/spend.js +134 -0
  38. package/build/spend.js.map +1 -0
  39. package/build/startup.js +78 -0
  40. package/build/startup.js.map +1 -0
  41. package/build/store.js +528 -0
  42. package/build/store.js.map +1 -0
  43. package/build/tab.js +26 -0
  44. package/build/tab.js.map +1 -0
  45. package/build/token.js +58 -0
  46. package/build/token.js.map +1 -0
  47. package/build/upgrade.js +93 -0
  48. package/build/upgrade.js.map +1 -0
  49. package/build/wait.js +149 -0
  50. package/build/wait.js.map +1 -0
  51. package/build/watchers/ado.js +167 -0
  52. package/build/watchers/ado.js.map +1 -0
  53. package/build/watchers/github.js +82 -0
  54. package/build/watchers/github.js.map +1 -0
  55. package/build/watchers/health.js +42 -0
  56. package/build/watchers/health.js.map +1 -0
  57. package/build/watchers/index.js +226 -0
  58. package/build/watchers/index.js.map +1 -0
  59. package/build/watchers/linear-key.js +56 -0
  60. package/build/watchers/linear-key.js.map +1 -0
  61. package/build/watchers/linear.js +218 -0
  62. package/build/watchers/linear.js.map +1 -0
  63. package/build/watchers/npm.js +35 -0
  64. package/build/watchers/npm.js.map +1 -0
  65. package/build/watchers/source.js +94 -0
  66. package/build/watchers/source.js.map +1 -0
  67. package/build/worktrees.js +143 -0
  68. package/build/worktrees.js.map +1 -0
  69. package/package.json +56 -5
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
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
+
5
+ ## [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)
6
+
7
+ ### Features
8
+
9
+ * **conductor:** conduct upgrade stops, installs and restarts (ATK-340) ([76e6fa9](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/76e6fa9596e5992ddd0ef55e2b7ebbb863b8bbac))
10
+
11
+ ### Bug Fixes
12
+
13
+ * **conductor:** make conduct upgrade's restart robust (ATK-340) ([eabe05f](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/eabe05fdd0a7435b09eadbd5f4bdd4c83b517a14))
14
+ * **conductor:** start the upgraded daemon in its home (ATK-340) ([bc0d46a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/bc0d46a516348123eff48e0856e00320649cf0fd))
15
+
16
+ ## 1.0.0-dev.1 (2026-10-10)
17
+
18
+ ### Features
19
+
20
+ * **conductor:** add the conductor daemon skeleton (ATK-308) ([a33233b](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/a33233bd47c29416bc3f1db85d10a7904954f7de))
21
+ * **conductor:** add the session manager (ATK-309) ([aee8575](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/aee85752ece203463e8ee027459ea26d80a0c689))
22
+ * **conductor:** add the watchers and conduct wait (ATK-310) ([f803cba](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/f803cba1c8de1b31a4282d259b9439a491c28c57))
23
+
24
+ ### Bug Fixes
25
+
26
+ * **ci:** keep two npm releases from skipping or doubling (ATK-338) ([76703d3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/76703d3454ba4abc14b30d5b3b5867eb26b399ce))
27
+ * **conductor:** address code review of the session manager (ATK-309) ([724ffba](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/724ffba72e4278b6819636ccad95310921582001))
28
+ * **conductor:** address review of the watchers (ATK-310) ([05cbb88](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/05cbb88c9efc14aad83dd40a668109de77e2a74a))
29
+ * **conductor:** harden the session manager after review (ATK-309) ([9675c0d](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/9675c0d40d6373429a9f99eeaad5ba78ba54eae5))
30
+ * **conductor:** harden the skeleton after review (ATK-308) ([0930b74](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/0930b74edd9fae6dc94dfa7b62aaf4f13526b0d3))
31
+ * **conductor:** read a turn's end from status alone (ATK-309) ([a42b6d3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/a42b6d34f45c309c6da3bae7cf2c3c9d7b2d0032))
package/README.md CHANGED
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ # Conductor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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), and step 3, the watchers and `conduct wait` (ATK-310).
4
+
5
+ ## Commands
6
+
7
+ ```text
8
+ conduct serve run the daemon in this process (one per machine)
9
+ conduct stop stop the running daemon
10
+ conduct status [--json] the daemon's pid, port, projects and workers
11
+ conduct project add <path> register the project whose checkout holds .claude/conductor.json
12
+ conduct project list [--json] the registered projects
13
+ conduct launch <item> start a worker: worktree, background session, tab
14
+ [--project <name>] [--probe] [--prompt <text>] [--conductor <name>]
15
+ conduct retire <worker> [--force] stop a worker, remove its worktree and session
16
+ conduct attach <worker> wake a parked worker (no event text) and open it in a tab
17
+ conduct wait <condition> wait for an outside event (see Waits)
18
+ conduct install-startup start the daemon at logon (Windows Task Scheduler)
19
+ conduct install-startup --remove remove that logon task
20
+ conduct upgrade [--tag dev|latest]
21
+ stop the daemon, npm install the tag's newest version, start it again
22
+ ```
23
+
24
+ A `<worker>` is its item (`ATK-309`) or its name (`probe-ATK-309`).
25
+
26
+ ## Workers
27
+
28
+ The session manager owns the `claude --bg` sessions it launches, and only those:
29
+
30
+ - **launch:** the worktree comes first, through the project's `.claude/hooks/worktree-hooks.mjs`. In a repo with `.atk-lock.json`, the profile's work and verify skills must match the lockfile (`atk install` fixes them when it can). Then `claude --bg --worktree <name> -n <name> --disallowedTools EnterPlanMode ExitPlanMode --permission-mode auto -- <prompt>` runs from the main checkout. A profile with `"workers": { "tab": true }` also gets a Windows Terminal tab attached to the session.
31
+ - **park:** a worker with a wait (`POST /waits`) is stopped once `claude agents` shows its turn has ended. Its status becomes `parked`, and its tab closes.
32
+ - **resume:** when a wait resolves, the daemon lets the stop settle (`sessions.settleSeconds`). It checks the process has exited, then runs `claude --bg --resume <session> -- "[conductor] event: …"`. The session keeps its name, model, permission mode and disallowed tools. A resume of a session that still runs starts a copy; the daemon stops and removes the copy, then tries again.
33
+ - **retire:** refuses uncommitted or unpushed work, and a worker whose turn still runs, unless `--force`. It never retires a session that is still starting. Then it stops the session, removes the worktree and runs `claude rm`. If the stop fails, nothing is removed.
34
+ - **restarts:** a launch the daemon was stopped in adopts its session if one was started, and otherwise fails and removes its clean worktree.
35
+ - **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.
36
+ - **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.
37
+ - **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell.
38
+ - **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.
39
+ - **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.
40
+
41
+ Worker statuses: `launching`, `running`, `parked`, `stopped-unexpectedly`, `failed`, `exited`. Every step is an event (`worker.launched`, `worker.parked`, `worker.resumed`, …) on `GET /events`.
42
+
43
+ ## Waits
44
+
45
+ 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:
46
+
47
+ | Verb | Full form | Holds when |
48
+ |---|---|---|
49
+ | `pr 1850 merged` | `pr:ado:Emergent.AgenticToolkit#1850:merged` | the PR is completed (or abandoned: that resolves it too, as a fact) |
50
+ | `pr 1850 checks` | `…#1850:checks` | no blocking build of the PR's current commit is queued or running (passed or failed; a path-filtered build it doesn't touch counts as passed) |
51
+ | `pr 1850 comment` | `…#1850:comment` | a thread is open whose last word isn't a work-skill note |
52
+ | `pr 458 merged --repo <owner>/<repo>` | `pr:github:<owner>/<repo>#458:merged` | the same on GitHub; `comment` there is a person's comment or review after the wait was registered |
53
+ | `run atk-api-deploy 12345 done` | `run:ado:atk-api-deploy#12345:done` | the run completed, with its result |
54
+ | `npm 8.5.0-dev.2 --tag dev` | `npm:@detergent-software/atk@8.5.0-dev.2:dev` | the dist-tag points at the version |
55
+ | `health dev --version 1.2.3` / `--commit <sha>` | `health:dev:version=1.2.3` | the profile's `health.dev` URL answers with that version or commit |
56
+ | `approval` | `approval:linear:ATK-310` | an `approve` / `revise:` comment by the Linear key's owner after the item's newest `<work skill> · Plan` note |
57
+ | `item ATK-271 done` / `label human` / `note Landed` | `item:linear:ATK-271:state=done` | the state (name or type), a label, or a phase note |
58
+ | `time 2026-10-10T09:00` | `time:2026-10-10T14:00:00.000Z` | the time has passed (no zone means local time) |
59
+
60
+ - **With the daemon**, in a worker's worktree (or with `--worker <worker>`), `conduct wait` registers the wait and returns at once. The daemon parks the session when its turn ends and resumes it with `[conductor] event: <resolution> HH:MM` once the condition holds. The text is a fact, never an approval: the worker reads the source again. The daemon checks waits against the project's profile as registered (`conduct project add`), and refuses one it can't check, such as a `health` environment the profile has no URL for.
61
+ - **Without a daemon**, with `--local`, or when the daemon doesn't answer, it checks the condition itself and blocks. It prints `[conductor] event: …` and exits 0 when the condition holds, or exits 2 after `--timeout` minutes (29: Monitor ends a command at 30) so the caller can run it again. A wait that can't be checked (a PR that doesn't exist, no Linear key, `az` not signed in) exits 1. `--timeout` applies only here.
62
+
63
+ **Watchers.** One per API: Linear (GraphQL with `LINEAR_API_KEY` or the `atk-conductor-linear` credential, ported from conduct-atk), Azure DevOps (REST with the `az login` token), GitHub (`gh api`), npm (the registry's dist-tags, no cache) and `/health`. Time waits are the session manager's sweep. An API is asked nothing while no wait needs it. Otherwise it's asked once per `watchers.<api>.intervalSeconds`, within `perMinute` requests in any rolling minute shared by every project. Each pass covers all of that API's waits in as few requests as it can; Linear's is one request, filtered on `updatedAt` (with a 2-minute overlap) after the first. A pass that runs out of budget stops, and the next one starts a wait further on. A failing API waits twice as long each time (up to 10 minutes) and logs `watcher.failed` once, then `watcher.recovered`. A wait that can't be checked logs `wait.check-failed` once and stays open. Every `watchers.statsMinutes` (10) in which anything waited, a `watcher.stats` event records each API's passes, requests, budget skips, failures and waits.
64
+
65
+ ## Files
66
+
67
+ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
68
+
69
+ | File | Holds |
70
+ |---|---|
71
+ | `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) |
72
+ | `token` | the API token, readable by this user only; every request sends `Authorization: Bearer <token>` |
73
+ | `conductor.pid`, `port` | the single-instance lock and where the API listens (`127.0.0.1`) |
74
+ | `conductor.db` | SQLite state, including the append-only `events` log |
75
+ | `logs/conductor.log` | the rotating log |
76
+
77
+ ## API
78
+
79
+ - `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one.
80
+ - `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.
81
+ - `POST /shutdown`: a clean stop (`conduct stop`).
82
+ - `GET /fleet`: the workers in flight, with session activity, waits and spend (today, total).
83
+ - `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.
84
+ - `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work when it refuses.
85
+ - `POST /workers/:id/attach`: wake if parked, mark attached, open the tab.
86
+ - `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.
87
+ - A refusal (a duplicate launch, a bad condition, a busy worker) is a 409 with `error` and `details`.
88
+
89
+ ## On PATH
90
+
91
+ Install the package globally, next to `atk`. `dev` follows `develop`; `latest` follows `main`:
92
+
93
+ ```bash
94
+ npm install --global @detergent-software/conductor@dev
95
+ conduct install-startup
96
+ conduct project add <path to the project's main checkout>
97
+ ```
98
+
99
+ `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.
100
+
101
+ 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:
102
+
103
+ ```bash
104
+ conduct upgrade
105
+ ```
106
+
107
+ 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. Otherwise it stops the daemon, runs `npm install --global @detergent-software/conductor@<tag>` 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`.
108
+
109
+ **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`:
110
+
111
+ ```bash
112
+ pnpm install
113
+ pnpm -C apps/conductor build
114
+ (cd apps/conductor && npm link)
115
+ conduct install-startup
116
+ ```
117
+
118
+ 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`.
119
+
120
+ ## Releases
121
+
122
+ `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.
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ pnpm -C apps/conductor lint
128
+ pnpm -C apps/conductor typecheck
129
+ pnpm -C apps/conductor test
130
+ pnpm -C apps/conductor build
131
+ cd apps/conductor && node scripts/check-changelog.mjs
132
+ ```
@@ -0,0 +1,102 @@
1
+ import { z } from 'zod';
2
+ import { ConductorError } from "./errors.js";
3
+ import { outputOf, run } from "./run.js";
4
+ // One row of `claude agents --json --all`. Only the fields the session manager reads; the rest pass.
5
+ export const agentSessionSchema = z.looseObject({
6
+ cwd: z.string().optional(),
7
+ id: z.string(),
8
+ kind: z.string().optional(),
9
+ name: z.string().optional(),
10
+ pid: z.number().optional(),
11
+ sessionId: z.string(),
12
+ // working | done | blocked: the agent view's label for the work, from the model's last reply.
13
+ // Not whether a turn runs (that is `status`), except before a new session has its pid.
14
+ state: z.string().optional(),
15
+ // idle | busy | waiting; `waiting` comes with `waitingFor` (a permission prompt, input).
16
+ status: z.string().optional(),
17
+ waitingFor: z.string().optional(),
18
+ });
19
+ export function claudeCli() {
20
+ // Resolves to stdout, and stderr after it: the CLI prints its notes (such as a copy started) there.
21
+ const claude = async (args, cwd, timeoutMs = 120_000) => {
22
+ const result = await run('claude', args, { cwd, timeoutMs });
23
+ if (result.code !== 0)
24
+ throw new ConductorError(`claude ${args[0]} failed`, [outputOf(result, 500)]);
25
+ return { all: result.stdout + result.stderr, stdout: result.stdout };
26
+ };
27
+ return {
28
+ // Only stdout is JSON; a notice on stderr must not break the listing.
29
+ agents: async () => parseAgents((await claude(['agents', '--json', '--all'], undefined, 60_000)).stdout),
30
+ launch: async (request) => {
31
+ // The create hook (branch, pnpm install, atk install) runs first, so allow it minutes.
32
+ const output = (await claude(launchArgs(request), request.cwd, 10 * 60_000)).all;
33
+ const handle = parseHandle(output);
34
+ if (!handle)
35
+ throw new ConductorError('claude --bg printed no session id', [output.trim().slice(-500)]);
36
+ return handle;
37
+ },
38
+ resume: async ({ cwd, sessionId, text }) => {
39
+ const output = await claude(['--bg', '--resume', sessionId, ...(text === null ? [] : ['--', text])], cwd);
40
+ return { copy: parseCopy(output.all) };
41
+ },
42
+ rm: async (handle) => void (await claude(['rm', handle])),
43
+ stop: async (handle) => void (await claude(['stop', handle])),
44
+ };
45
+ }
46
+ // The session's process exists. `state: "done"` only means the turn ended.
47
+ export function isLive(session) {
48
+ return session.pid !== undefined || isStarting(session);
49
+ }
50
+ // Listed as working before it has a pid: `claude --bg` is still creating its worktree.
51
+ export function isStarting(session) {
52
+ return session.pid === undefined && session.state === 'working';
53
+ }
54
+ // The flags a worker starts with. A resume keeps them: `claude --bg --resume` wakes a session
55
+ // "with its saved options (-n, --permission-mode, --disallowedTools, --model)" (ATK-309's check).
56
+ // Workers get no plan-mode tools: an ExitPlanMode dialog passes on one stray key (ATK-269).
57
+ export function launchArgs(request) {
58
+ return [
59
+ '--bg',
60
+ '--worktree',
61
+ request.worktree,
62
+ '-n',
63
+ request.name,
64
+ '--disallowedTools',
65
+ ...request.disallowedTools,
66
+ '--permission-mode',
67
+ request.permissionMode,
68
+ '--',
69
+ request.prompt,
70
+ ];
71
+ }
72
+ // The sessions in `claude agents --json --all` that have a short id. Interactive sessions are
73
+ // listed without one; the daemon never manages those, and one odd row mustn't hide the rest.
74
+ export function parseAgents(text) {
75
+ return z
76
+ .array(z.unknown())
77
+ .parse(JSON.parse(text))
78
+ .flatMap((row) => {
79
+ const session = agentSessionSchema.safeParse(row);
80
+ return session.success ? [session.data] : [];
81
+ });
82
+ }
83
+ // A resume of a session that still runs starts a copy and says so. The copy has no name and
84
+ // doesn't show in `claude agents --json --all`, so this line is the only trace of it.
85
+ export function parseCopy(output) {
86
+ return /started a copy as ([0-9a-f]{8})\b/.exec(output)?.[1] ?? null;
87
+ }
88
+ // `claude --bg` prints `backgrounded · <id> · <name>`.
89
+ export function parseHandle(output) {
90
+ return /backgrounded · ([0-9a-f]{8})\b/.exec(output)?.[1] ?? null;
91
+ }
92
+ export function permissionPrompt(session) {
93
+ return session.status === 'waiting' && /permission/i.test(session.waitingFor ?? '') ? (session.waitingFor ?? null) : null;
94
+ }
95
+ // The turn ended and nothing holds the session: no tool running, no prompt open, no Monitor armed
96
+ // (an armed Monitor reads busy). Only `status` says so. `state` is the agent view's label for how
97
+ // the work is going, taken from the last reply: a probe that answered an event with "I'm still
98
+ // waiting" stayed `state: working` at `status: idle` for minutes (ATK-309).
99
+ export function turnEnded(session) {
100
+ return session.pid !== undefined && session.status === 'idle';
101
+ }
102
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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"}