@detergent-software/conductor 3.0.0-dev.4 → 3.0.0-dev.41
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 +352 -0
- package/README.md +274 -124
- package/build/brief-cli.js +5 -12
- package/build/brief-cli.js.map +1 -1
- package/build/brief.js +4 -3
- package/build/brief.js.map +1 -1
- package/build/broker-cli.js +15 -10
- package/build/broker-cli.js.map +1 -1
- package/build/broker.js +1 -1
- package/build/broker.js.map +1 -1
- package/build/claude.js +48 -2
- package/build/claude.js.map +1 -1
- package/build/cli.js +225 -71
- package/build/cli.js.map +1 -1
- package/build/clones.js +92 -0
- package/build/clones.js.map +1 -0
- package/build/code-host-cli.js +172 -40
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host-service.js +35 -2
- package/build/code-host-service.js.map +1 -1
- package/build/code-host.js +140 -16
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +33 -16
- package/build/conditions.js.map +1 -1
- package/build/config.js +58 -28
- package/build/config.js.map +1 -1
- package/build/console.js +43 -27
- package/build/console.js.map +1 -1
- package/build/credentials.js +86 -54
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +57 -66
- package/build/daemon.js.map +1 -1
- package/build/file-lock.js +155 -40
- package/build/file-lock.js.map +1 -1
- package/build/follow-ups.js +2 -2
- package/build/follow-ups.js.map +1 -1
- package/build/help.js +237 -82
- package/build/help.js.map +1 -1
- package/build/history.js +10 -4
- package/build/history.js.map +1 -1
- package/build/host/credential-manager.js +109 -0
- package/build/host/credential-manager.js.map +1 -0
- package/build/host/keychain.js +44 -0
- package/build/host/keychain.js.map +1 -0
- package/build/host/launchd.js +118 -0
- package/build/host/launchd.js.map +1 -0
- package/build/host/libsecret.js +40 -0
- package/build/host/libsecret.js.map +1 -0
- package/build/host/macos-terminal.js +63 -0
- package/build/host/macos-terminal.js.map +1 -0
- package/build/host/platform.js +4 -0
- package/build/host/platform.js.map +1 -0
- package/build/host/secrets.js +46 -0
- package/build/host/secrets.js.map +1 -0
- package/build/host/shell.js +26 -0
- package/build/host/shell.js.map +1 -0
- package/build/host/startup.js +26 -0
- package/build/host/startup.js.map +1 -0
- package/build/host/systemd.js +91 -0
- package/build/host/systemd.js.map +1 -0
- package/build/host/task-scheduler.js +78 -0
- package/build/host/task-scheduler.js.map +1 -0
- package/build/host/terminal.js +46 -0
- package/build/host/terminal.js.map +1 -0
- package/build/host/tmux.js +39 -0
- package/build/host/tmux.js.map +1 -0
- package/build/host/windows-terminal.js +46 -0
- package/build/host/windows-terminal.js.map +1 -0
- package/build/in-process.js +6 -5
- package/build/in-process.js.map +1 -1
- package/build/inbox.js +57 -39
- package/build/inbox.js.map +1 -1
- package/build/init.js +509 -0
- package/build/init.js.map +1 -0
- package/build/landing.js +107 -10
- package/build/landing.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +5 -1
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +138 -26
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/ado/trigger.js +5 -0
- package/build/lib/providers/ado/trigger.js.map +1 -1
- package/build/lib/providers/ado/writes.js +17 -2
- package/build/lib/providers/ado/writes.js.map +1 -1
- package/build/lib/providers/github/actions.js +90 -3
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +2 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/github/writes.js +25 -2
- package/build/lib/providers/github/writes.js.map +1 -1
- package/build/lib/providers/index.js +4 -3
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +28 -5
- package/build/lib/providers/npm/stage.js.map +1 -1
- package/build/lib/providers/timing.js +10 -0
- package/build/lib/providers/timing.js.map +1 -0
- package/build/lib/providers/verdicts/pr.js +47 -6
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/lib/providers/verdicts/runs.js +12 -2
- package/build/lib/providers/verdicts/runs.js.map +1 -1
- package/build/logins.js +13 -6
- package/build/logins.js.map +1 -1
- package/build/manager.js +131 -55
- package/build/manager.js.map +1 -1
- package/build/note.js +5 -15
- package/build/note.js.map +1 -1
- package/build/notes.js +130 -23
- package/build/notes.js.map +1 -1
- package/build/notify.js +104 -42
- package/build/notify.js.map +1 -1
- package/build/npm-reads.js +323 -0
- package/build/npm-reads.js.map +1 -0
- package/build/ownership.js +2 -2
- package/build/paths.js +44 -1
- package/build/paths.js.map +1 -1
- package/build/plugin.js +4 -2
- package/build/plugin.js.map +1 -1
- package/build/profile-fields.js +153 -0
- package/build/profile-fields.js.map +1 -0
- package/build/profile-layers.js +264 -0
- package/build/profile-layers.js.map +1 -0
- package/build/profile.js +107 -51
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +19 -19
- package/build/project-check-cli.js.map +1 -1
- package/build/project-check.js +194 -108
- package/build/project-check.js.map +1 -1
- package/build/protocol.js +5 -21
- package/build/protocol.js.map +1 -1
- package/build/pushover.js +5 -5
- package/build/pushover.js.map +1 -1
- package/build/queue.js +4 -4
- package/build/queue.js.map +1 -1
- package/build/registry.js +195 -36
- package/build/registry.js.map +1 -1
- package/build/release-kinds.js +34 -0
- package/build/release-kinds.js.map +1 -0
- package/build/rule-actions.js +8 -6
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +104 -47
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +23 -23
- package/build/rules.js.map +1 -1
- package/build/run.js +20 -7
- package/build/run.js.map +1 -1
- package/build/runs.js +218 -68
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +89 -13
- package/build/scheduler.js.map +1 -1
- package/build/search.js +2 -1
- package/build/search.js.map +1 -1
- package/build/server.js +51 -14
- package/build/server.js.map +1 -1
- package/build/spend.js +1 -1
- package/build/spend.js.map +1 -1
- package/build/store.js +79 -20
- package/build/store.js.map +1 -1
- package/build/supervise.js +1 -1
- package/build/supervise.js.map +1 -1
- package/build/timeline.js +104 -0
- package/build/timeline.js.map +1 -0
- package/build/tracker-reads-cli.js +4 -5
- package/build/tracker-reads-cli.js.map +1 -1
- package/build/trackers.js +56 -10
- package/build/trackers.js.map +1 -1
- package/build/train-cli.js +6 -5
- package/build/train-cli.js.map +1 -1
- package/build/train.js +2 -2
- package/build/train.js.map +1 -1
- package/build/upgrade.js +8 -9
- package/build/upgrade.js.map +1 -1
- package/build/wait.js +10 -10
- package/build/wait.js.map +1 -1
- package/build/watchers/ado.js +44 -20
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +21 -6
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +2 -2
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/linear-queue.js +47 -14
- package/build/watchers/linear-queue.js.map +1 -1
- package/build/watchers/linear-writes.js +26 -12
- package/build/watchers/linear-writes.js.map +1 -1
- package/build/watchers/linear.js +2 -2
- package/build/watchers/linear.js.map +1 -1
- package/build/watchers/npm.js +6 -4
- package/build/watchers/npm.js.map +1 -1
- package/build/watchers/source.js +5 -1
- package/build/watchers/source.js.map +1 -1
- package/build/webhook.js +80 -0
- package/build/webhook.js.map +1 -0
- package/build/why.js +1 -1
- package/build/why.js.map +1 -1
- package/build/worktrees.js +56 -93
- package/build/worktrees.js.map +1 -1
- package/package.json +61 -61
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/model.ts +46 -6
- package/plugin/hooks/register.tsx +41 -2
- package/plugin/skills/conductor-console/SKILL.md +26 -27
- package/plugin/skills/work-item/SKILL.md +58 -54
- package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
- package/plugin/skills/work-item/trackers/github-issues.md +1 -1
- package/plugin/skills/work-item/trackers/linear.md +2 -2
- package/plugin/types/index.d.ts +1 -1
- package/protocol/PROTOCOL.md +3 -9
- package/build/checkout-sync.js +0 -249
- package/build/checkout-sync.js.map +0 -1
- package/build/credential.js +0 -66
- package/build/credential.js.map +0 -1
- package/build/startup.js +0 -92
- package/build/startup.js.map +0 -1
- package/build/tab.js +0 -32
- package/build/tab.js.map +0 -1
package/plugin/hooks/model.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type ApiAnswer = { body: unknown; status: number };
|
|
|
9
9
|
|
|
10
10
|
// What a Remote Control push came to: reached the phone (ok); not, as Remote Control is off
|
|
11
11
|
// (inactive) or pushes are off in Claude Code's config (disabled); not sent to the phone on
|
|
12
|
-
// purpose (skipped:
|
|
12
|
+
// purpose (skipped: the operator is at the terminal, or the answer says nothing about the phone); or the
|
|
13
13
|
// call failed (failed: it threw, was denied, or answered an error).
|
|
14
14
|
export type PushOutcome = { detail: string; state: 'disabled' | 'failed' | 'inactive' | 'ok' | 'skipped' };
|
|
15
15
|
|
|
@@ -211,7 +211,7 @@ export type FleetRow = {
|
|
|
211
211
|
};
|
|
212
212
|
|
|
213
213
|
// A wait's condition key (conditions.ts's formatCondition) in words, the telling part first so a
|
|
214
|
-
// truncated row keeps it: `PR #557 to merge (
|
|
214
|
+
// truncated row keeps it: `PR #557 to merge (skills-registry)`, `npm 1.2.3 on dev (cli)`.
|
|
215
215
|
// A kind this doesn't know, or a key it can't read, stays as it is.
|
|
216
216
|
export function waitLabel(condition: string, now: number): string {
|
|
217
217
|
const colon = condition.indexOf(':');
|
|
@@ -301,10 +301,13 @@ export function findPlan(inbox: readonly ConductorInboxItem[], ref: string): Fou
|
|
|
301
301
|
const item = inbox.find((i) => i.id === Number(byId[1]));
|
|
302
302
|
return item ? { found: item } : { error: `no open inbox item ${ref}` };
|
|
303
303
|
}
|
|
304
|
-
if (/^\d+$/.test(ref.trim()))
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
};
|
|
304
|
+
if (/^\d+$/.test(ref.trim())) {
|
|
305
|
+
const n = ref.trim();
|
|
306
|
+
// The waiting plans whose key ends in the number, else a placeholder: the project's key isn't known here.
|
|
307
|
+
const keys = inbox.flatMap((i) => (i.kind === 'plan' && i.item?.endsWith(`-${n}`) ? [i.item] : []));
|
|
308
|
+
const item = keys.length > 0 ? keys.join(' or ') : `<KEY>-${n}`;
|
|
309
|
+
return { error: `"${ref}" is ambiguous: name the item, such as ${item}, or the inbox id as #${n}` };
|
|
310
|
+
}
|
|
308
311
|
const plans = inbox.filter((i) => i.kind === 'plan' && i.item?.toUpperCase() === ref.trim().toUpperCase());
|
|
309
312
|
if (plans.length === 1 && plans[0]) return { found: plans[0] };
|
|
310
313
|
if (plans.length === 0) return { error: `no plan of ${ref} waits in the inbox` };
|
|
@@ -553,6 +556,14 @@ export function answerText(action: 'approve' | 'done' | 'revise', id: number, an
|
|
|
553
556
|
return `${action === 'approve' ? 'approved' : 'asked for a revision of'} ${key}: ${where}`;
|
|
554
557
|
}
|
|
555
558
|
|
|
559
|
+
// The item a /revise names when no plan of it waits in the inbox, which `conduct revise` then revises
|
|
560
|
+
// as an approved plan (ATK-664); null for an inbox id, a bare number, or an item whose plan waits.
|
|
561
|
+
export function approvedPlanItem(inbox: readonly ConductorInboxItem[], ref: string): null | string {
|
|
562
|
+
const key = ref.trim().toUpperCase();
|
|
563
|
+
if (!/^[A-Z]+-\d+$/.test(key)) return null;
|
|
564
|
+
return inbox.some((i) => i.kind === 'plan' && i.item?.toUpperCase() === key) ? null : key;
|
|
565
|
+
}
|
|
566
|
+
|
|
556
567
|
// The conductor this plugin folder belongs to: the CLI the mod runs, and the daemon's home. The
|
|
557
568
|
// daemon's copy (<CONDUCTOR_HOME>/plugin) names both in conductor.json, written on each refresh.
|
|
558
569
|
// The home matters because a `claude --bg` session doesn't see its launcher's environment: a worker
|
|
@@ -636,6 +647,35 @@ export function pushOutcome(answer: unknown): PushOutcome {
|
|
|
636
647
|
return { detail: detail || 'PushNotification said nothing about the phone', state: 'skipped' };
|
|
637
648
|
}
|
|
638
649
|
|
|
650
|
+
// POST /items/:item/revise's answer as the line to show, as `conduct revise` says it (reviseOutcome
|
|
651
|
+
// in src/cli.ts, which the mod doesn't import). A daemon from before ATK-664 has no such route (404).
|
|
652
|
+
export function reviseText(item: string, answer: ApiAnswer): string {
|
|
653
|
+
if (answer.status === 404)
|
|
654
|
+
return `not revised: no plan of ${item} waits in the inbox, and a conductor started before ATK-664 revises no approved plan: restart it for that`;
|
|
655
|
+
if (answer.status !== 200) {
|
|
656
|
+
const unknown = answer.status === 502 && isRecord(answer.body) && answer.body.posted === 'unknown';
|
|
657
|
+
return `not revised: ${errorOf(answer)}${unknown ? `; ${MAYBE_POSTED}` : ''}`;
|
|
658
|
+
}
|
|
659
|
+
const r = isRecord(answer.body) && isRecord(answer.body.revise) ? answer.body.revise : {};
|
|
660
|
+
const comment = isRecord(r.comment) ? r.comment : {};
|
|
661
|
+
const where =
|
|
662
|
+
typeof comment.url === 'string' ? comment.url : typeof comment.id === 'string' ? comment.id : 'comment posted';
|
|
663
|
+
const none = 'no worker is on it: one launched on it reads the revise in its brief';
|
|
664
|
+
const outcome =
|
|
665
|
+
typeof r.pending === 'string'
|
|
666
|
+
? `nothing recorded yet (${r.pending}): ${r.worker === true ? 'the revise sweep records it and wakes its worker within 5 minutes' : none}`
|
|
667
|
+
: r.revised !== true
|
|
668
|
+
? 'a revise of its plan was recorded already: its worker re-plans on the first one'
|
|
669
|
+
: typeof r.woken === 'number' && r.woken > 0
|
|
670
|
+
? 'woke its worker to re-plan'
|
|
671
|
+
: r.landed === true
|
|
672
|
+
? 'its PR has merged: its worker learns of it at its next note, for a follow-up PR'
|
|
673
|
+
: r.worker === true
|
|
674
|
+
? 'its worker is running: its next note is held back, and it re-plans'
|
|
675
|
+
: none;
|
|
676
|
+
return `asked for a revision of ${item}'s approved plan: ${where}; ${outcome}`;
|
|
677
|
+
}
|
|
678
|
+
|
|
639
679
|
// A `conduct` run (attach, approve, revise) as the line to show: what it printed when it succeeded
|
|
640
680
|
// (`woke …`, `approved …`), else `failed`, then its error and details without the `conduct: `
|
|
641
681
|
// prefix, and without anything node wrote before it (a warning).
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
age,
|
|
9
9
|
answerText,
|
|
10
10
|
type ApiAnswer,
|
|
11
|
+
approvedPlanItem,
|
|
11
12
|
bandEntries,
|
|
12
13
|
clearedText,
|
|
13
14
|
CONSOLE_SKILL,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
pushWarning,
|
|
40
41
|
reaction,
|
|
41
42
|
reportedState,
|
|
43
|
+
reviseText,
|
|
42
44
|
runText,
|
|
43
45
|
snapshotOf,
|
|
44
46
|
spendToday,
|
|
@@ -65,6 +67,9 @@ const ATTACH_RUN_MS = 10 * 60_000;
|
|
|
65
67
|
// An approve or a revise asks Linear twice, and `conduct approve|revise` allows that 60 s (ATK-508);
|
|
66
68
|
// this adds node's start.
|
|
67
69
|
const ANSWER_RUN_MS = 2 * 60_000;
|
|
70
|
+
// A revise of an approved plan reads the brief up to three times after it posts, and
|
|
71
|
+
// `conduct revise` allows that 3 min (ATK-664).
|
|
72
|
+
const REVISE_RUN_MS = 4 * 60_000;
|
|
68
73
|
const NOT_RUNNING = 'the conductor is not running (conduct serve)';
|
|
69
74
|
|
|
70
75
|
const snapshot = atom({ key: 'snapshot', plugin: 'conductor' } as const, null);
|
|
@@ -98,7 +103,7 @@ let isWindows = false;
|
|
|
98
103
|
|
|
99
104
|
type Endpoint = { port: number; token: string };
|
|
100
105
|
|
|
101
|
-
//
|
|
106
|
+
// The operator's answer to an inbox item, as `conduct approve|revise|done` posts it; the line to show.
|
|
102
107
|
// Approve and revise run `conduct` (ANSWER_RUN_MS), by inbox id. Without the CLI, the API, which
|
|
103
108
|
// $.http.fetch cuts off after 30 s, possibly after the comment was posted. Done doesn't ask Linear.
|
|
104
109
|
async function answer(
|
|
@@ -549,6 +554,38 @@ async function reportPlugin($: EngineInterface): Promise<void> {
|
|
|
549
554
|
}
|
|
550
555
|
}
|
|
551
556
|
|
|
557
|
+
// /revise of an item with no plan in the inbox: `conduct revise <item>` revises its approved plan
|
|
558
|
+
// (ATK-664), as `answer` runs it, or POST /items/:item/revise without the CLI.
|
|
559
|
+
async function reviseApproved($: EngineInterface, item: string, text: string): Promise<string> {
|
|
560
|
+
try {
|
|
561
|
+
const cli = await cliPath($);
|
|
562
|
+
if (await $.fs.exists(cli)) {
|
|
563
|
+
let run;
|
|
564
|
+
try {
|
|
565
|
+
run = await $.process.run(['node', cli, 'revise', item, text], {
|
|
566
|
+
...(await cliInit($)),
|
|
567
|
+
timeoutMs: REVISE_RUN_MS,
|
|
568
|
+
});
|
|
569
|
+
} catch (error) {
|
|
570
|
+
return `not revised: ${error instanceof Error ? error.message : String(error)}`;
|
|
571
|
+
}
|
|
572
|
+
refreshSoon($);
|
|
573
|
+
return runText(run, { failed: 'not revised', ok: `asked for a revision of ${item}'s approved plan` });
|
|
574
|
+
}
|
|
575
|
+
const at = await endpoint($);
|
|
576
|
+
let result: ApiAnswer;
|
|
577
|
+
try {
|
|
578
|
+
result = await callApi($, 'POST', `/items/${encodeURIComponent(item)}/revise`, { text }, at);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return `not revised: ${error instanceof Error ? error.message : String(error)}; ${MAYBE_POSTED}`;
|
|
581
|
+
}
|
|
582
|
+
refreshSoon($);
|
|
583
|
+
return reviseText(item, result);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
return error instanceof Error ? error.message : String(error);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
552
589
|
// `/notify <profile> [--until <when>]` and the pane's picker: switch the notification profile.
|
|
553
590
|
async function switchProfile($: EngineInterface, name: string, until: null | string = null): Promise<string> {
|
|
554
591
|
try {
|
|
@@ -671,7 +708,9 @@ export const register: Register = (on) => {
|
|
|
671
708
|
const snap = await refresh($);
|
|
672
709
|
if (snap.error !== null) return { text: snap.error };
|
|
673
710
|
const plan = findPlan(snap.inbox, ref);
|
|
674
|
-
return { text:
|
|
711
|
+
if (plan.found) return { text: await answer($, plan.found.id, { action: 'revise', text }) };
|
|
712
|
+
const approved = approvedPlanItem(snap.inbox, ref);
|
|
713
|
+
return { text: approved === null ? plan.error : await reviseApproved($, approved, text) };
|
|
675
714
|
});
|
|
676
715
|
|
|
677
716
|
on('command.run', { command: 'attach' }, async ($, e) => {
|
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conductor-console
|
|
3
|
-
description: Be the conductor console - the one always-open Claude session beside the conductor daemon (`conduct`). Wakes only for an escalation from the daemon's rules or for
|
|
3
|
+
description: Be the conductor console - the one always-open Claude session beside the conductor daemon (`conduct`). Wakes only for an escalation from the daemon's rules or for the operator. Handles escalations (a worker's conductor Handoff, a failed run, a stall, a vague item) and reports back through `conduct escalations done`. Answers the operator from `conduct status`, `inbox`, `queue` and `why`, and turns plain words into `conduct pause`, `focus`, `notify profile`, `approve` or `retire`. No loop, no ticks. Ships in the conductor plugin. Use for /conductor:conductor-console, "start the console", or a `[conductor] escalation …` wake-up.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# conductor-console
|
|
7
7
|
|
|
8
|
-
You are **the console**, the one Claude session that stays open beside **the daemon** (`conduct serve`, package `@detergent-software/conductor`). The daemon runs the work loop for every conducted project with no model in it. It picks items, launches and retires **workers** (one Claude session per item), parks and resumes them, raises an inbox of what waits on
|
|
8
|
+
You are **the console**, the one Claude session that stays open beside **the daemon** (`conduct serve`, package `@detergent-software/conductor`). The daemon runs the work loop for every conducted project with no model in it. It picks items, launches and retires **workers** (one Claude session per item), parks and resumes them, raises an inbox of what waits on the operator, and pushes it to their phone. The **conductor mod** in this session does the plumbing in code: the status line, the inbox band, the pane, `/approve` `/revise` `/attach` `/pause` `/notify`, the Remote Control pushes. Your model is woken for two things only:
|
|
9
9
|
|
|
10
10
|
- **An escalation:** a turn that reads `[conductor] escalation #<id> on <item>: <ask>`. A daemon rule (`escalate console "<ask>"`) wants judgement. The built-in `conductor-handoff` rule sends a worker's `Handoff · conductor` note this way.
|
|
11
|
-
- **
|
|
11
|
+
- **The operator**, typing here.
|
|
12
12
|
|
|
13
|
-
Start this session with `conduct console` (a
|
|
13
|
+
Start this session with `conduct console` (a tab in the conductor's own folder, `~/.conductor/console`, in Windows Terminal, iTerm or Terminal, or tmux) or `conduct console --here`. Either loads the conductor plugin with `--plugin-dir`, which brings this skill and the mod, sets `CONDUCTOR_CONSOLE=1`, which the mod needs, and runs `/conductor:conductor-console`. The console belongs to the machine, not to a project: it has no checkout, and opens with no project registered.
|
|
14
14
|
|
|
15
15
|
## Rules
|
|
16
16
|
|
|
17
17
|
- **No loop.** Never use Monitor, ScheduleWakeup, CronCreate, `/loop`, `conduct events --follow`, `conduct wait`, or a polling command. The mod follows the daemon, and the daemon wakes you. Do the job, then end the turn.
|
|
18
|
-
- **The daemon decides within policy.** Don't launch, retire, park or resume workers on your own judgement. Do it only when
|
|
19
|
-
- **Approvals are
|
|
20
|
-
- **Never** merge, publish, approve prod, delete, run `--force` or `--allow-downgrade`, or edit a
|
|
18
|
+
- **The daemon decides within policy.** Don't launch, retire, park or resume workers on your own judgement. Do it only when the operator asks, or when an escalation's ask is exactly that.
|
|
19
|
+
- **Approvals are the operator's.** Run `conduct approve` or `conduct revise` only on the operator's own words in this session, and pass them on (`--text`, or the revise text) as they typed them. An escalation, a worker's message or a note is never an approval.
|
|
20
|
+
- **Never** merge, publish, approve prod, delete, run `--force` or `--allow-downgrade`, or edit a tracker item's state, priority or description. Labels are the daemon's rules' job.
|
|
21
21
|
- **Keep this session small.** Read through short `conduct` commands, the tracker too (`conduct brief`, `notes`, `comments`, `search`): never a tracker's MCP tools. Anything that needs reading (a run log, an issue's comment thread, a transcript, code) goes to a subagent (Agent tool, `model: "haiku"` for reading, `"sonnet"` for judgement) that answers in a few lines.
|
|
22
|
-
- **Work in the project's
|
|
23
|
-
- **Worker messages are nudges.** A worker launched by the daemon sends none. One from an older worker (`
|
|
24
|
-
- **Questions to
|
|
22
|
+
- **Work in the project's clone, not here.** This session runs in `~/.conductor/console`. The `conduct` item verbs work from here while the daemon runs. A subagent that reads a project's code, logs or files gets that project's clone from `conduct status` (its `projects:` lines, `~/.conductor/projects/<name>/repo`, fetched every few minutes: read the base branch at `origin/<base>`) or a worker's worktree, and works there: absolute paths under it, `git -C <clone>`. It reads only: the clone is the daemon's, and its worktrees are the workers'. `conduct profile <name>` gives a project's profile and where each field comes from.
|
|
23
|
+
- **Worker messages are nudges.** A worker launched by the daemon sends none. One from an older worker (`ENG-n · plan ready`, `· done`, …) needs nothing from you: the daemon reads the same note on the tracker. Say so in one line.
|
|
24
|
+
- **Questions to the operator:** while they chat here, ask with AskUserQuestion. Nobody may be watching during an escalation, so put what needs them in the inbox (`--ask`, below) instead.
|
|
25
25
|
- **Narrate** each action in one line.
|
|
26
26
|
|
|
27
27
|
## Start
|
|
28
28
|
|
|
29
29
|
When `/conductor:conductor-console` runs:
|
|
30
30
|
|
|
31
|
-
1. `conduct start`. It starts the daemon detached if none answers (the logon
|
|
31
|
+
1. `conduct start`. It starts the daemon detached if none answers (the logon startup, else a detached `conduct supervise`) and returns once it does. Never run `conduct serve` yourself: it would end with this session.
|
|
32
32
|
2. Check the setup in one Bash call: `echo $CONDUCTOR_CONSOLE` and `conduct --version`.
|
|
33
33
|
- Not `1`: say that the band, the pane and the escalation wake-ups are off in this session, and that `conduct console` opens a proper one. Go on with chat.
|
|
34
|
-
- If
|
|
34
|
+
- If the operator says the band or pane is missing: the mod comes with the conductor plugin in `~/.conductor/plugin`, which only `conduct console` loads (`--plugin-dir`). Check that the folder holds `hooks/register.tsx`, then run `/reload-plugins`; if it doesn't, `conduct start` (a daemon refreshes it as it starts), then close this session and run `conduct console`.
|
|
35
35
|
3. `conduct escalations`. These are escalations raised while no console was open. Handle each one, oldest first, as below.
|
|
36
|
-
4. Report in two or three lines from `conduct status`, `conduct inbox` and the first line of `conduct queue`: workers running and parked, what waits on
|
|
36
|
+
4. Report in two or three lines from `conduct status`, `conduct inbox` and the first line of `conduct queue`: workers running and parked, what waits on the operator, and the scheduler's mode. If the mode is `off`, say that the daemon launches nothing until `"scheduler": { "mode": "on" }` is set in `~/.conductor/config.json` and the daemon is restarted (`conduct stop`, then `conduct start`).
|
|
37
37
|
|
|
38
38
|
Then end the turn.
|
|
39
39
|
|
|
@@ -43,25 +43,25 @@ Then end the turn.
|
|
|
43
43
|
|
|
44
44
|
1. **What fired it:** `conduct why <item>` shows the rule and its trigger. `conduct escalations --json` has the rule's ref.
|
|
45
45
|
2. **Do the job** within the rules above. Use a subagent for anything that needs reading:
|
|
46
|
-
- **A worker's conductor Handoff** (`default:conductor-handoff`): read the newest one with `conduct notes <item> --phase Handoff --kind conductor --last`, which prints the note whole; never a tracker's MCP tools. `conduct comments <item> --since <its time>` shows what was said after it. Do what it asks when a `conduct` command does it. To reach a worker: `conduct status` shows whether it is `running` or `parked`. SendMessage goes only to a running, idle worker. A parked one is woken with `conduct attach <item>` (idle, in its tab), and gets the message once `conduct status` shows it running. Anything outside your remit goes to
|
|
46
|
+
- **A worker's conductor Handoff** (`default:conductor-handoff`): read the newest one with `conduct notes <item> --phase Handoff --kind conductor --last`, which prints the note whole; never a tracker's MCP tools. `conduct comments <item> --since <its time>` shows what was said after it. Do what it asks when a `conduct` command does it. To reach a worker: `conduct status` shows whether it is `running` or `parked`. SendMessage goes only to a running, idle worker, addressed by the name and ref ListAgents prints (`ENG-12 [b316bc]`), not the bare name: another session can carry the same name. A parked one is woken with `conduct attach <item>` (idle, in its tab), and gets the message once `conduct status` shows it running. Anything outside your remit goes to the operator with `--ask`.
|
|
47
47
|
- **A failed run:** a subagent reads the run's log and says whether it is the change or the pipeline (flaky, infra), with the failing step and line.
|
|
48
48
|
- **A stall:** `conduct status` (activity, waits, phase), `conduct inbox` (its `permission`, `stall` or `failed` item) and `conduct why <item>`. Say what it waits on and who can move it.
|
|
49
|
-
- **A vague item:** a subagent reads it (`conduct brief <item>`, `conduct comments <item>`) and drafts a sharper description and a "done when". Put the draft in the `--ask` for
|
|
49
|
+
- **A vague item:** a subagent reads it (`conduct brief <item>`, `conduct comments <item>`) and drafts a sharper description and a "done when". Put the draft in the `--ask` for the operator to adopt; never edit the description.
|
|
50
50
|
- **Anything else:** do what the ask says, if the rules allow it.
|
|
51
51
|
3. **Report back, always**, also when there was nothing to do:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
conduct escalations done <id> "<what you found and what you did, one or two lines>"
|
|
55
|
-
conduct escalations done <id> --ask "<what
|
|
55
|
+
conduct escalations done <id> --ask "<what the operator must do, with the command or link>"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
`--ask` puts the finding in the inbox as a `console` item, which the daemon pushes to
|
|
58
|
+
`--ask` puts the finding in the inbox as a `console` item, which the daemon pushes to the operator's phone. Use it only when they must act. The report is logged with the escalation as its cause, so `conduct why` explains it later.
|
|
59
59
|
|
|
60
60
|
## Chat
|
|
61
61
|
|
|
62
62
|
Answer from the daemon, never from memory:
|
|
63
63
|
|
|
64
|
-
|
|
|
64
|
+
| The operator asks | Run |
|
|
65
65
|
|---|---|
|
|
66
66
|
| what's running, parked, stuck | `conduct status`, `conduct inbox` |
|
|
67
67
|
| why isn't X started; what's next | `conduct queue` (`--fresh` reads the trackers now) |
|
|
@@ -72,17 +72,17 @@ Answer from the daemon, never from memory:
|
|
|
72
72
|
|
|
73
73
|
Turn requests into commands, then show back what took effect: quote the line from `conduct rules` (its overrides) or `conduct notify` that proves it.
|
|
74
74
|
|
|
75
|
-
|
|
|
75
|
+
| The operator says | Run |
|
|
76
76
|
|---|---|
|
|
77
|
-
| "pause
|
|
77
|
+
| "pause portal until Monday" | `conduct pause portal --until monday` |
|
|
78
78
|
| "stop all launches for now" | `conduct pause` (until `conduct unpause`) |
|
|
79
|
-
| "resume
|
|
80
|
-
| "put
|
|
79
|
+
| "resume web" | `conduct unpause web` |
|
|
80
|
+
| "put web first this afternoon" | `conduct focus web --until 17:00` (`--slots <n>` or `--weight <n>` when they say so) |
|
|
81
81
|
| "I'm away until tomorrow" | `conduct notify profile away --until tomorrow` |
|
|
82
|
-
| "approve
|
|
83
|
-
| "start
|
|
84
|
-
| "show me
|
|
85
|
-
| "drop
|
|
82
|
+
| "approve ENG-12" / "ENG-12: change X" | `conduct approve ENG-12` / `conduct revise ENG-12 <their words>` |
|
|
83
|
+
| "start ENG-40 now" | `conduct launch ENG-40` |
|
|
84
|
+
| "show me ENG-12" | `conduct attach ENG-12` (opens its tab) |
|
|
85
|
+
| "drop ENG-12's worker" | `conduct retire ENG-12`. On a refusal, report the unfinished work, and never add `--force` yourself |
|
|
86
86
|
| "always do X when Y" | a rule in `~/.conductor/rules.json` (`when` / `do`, see the conductor README's Rules). Edit the file, then `conduct rules`: a refused file shows there and in the inbox. Project rules change by PR in the project's `.claude/conductor.json`, which is work for a worker |
|
|
87
87
|
|
|
88
88
|
`--until` takes `17:00` (the next one), `tomorrow`, a day such as `monday`, or a date and time. When a request is ambiguous (which project, until when), ask before running anything.
|
|
@@ -91,4 +91,3 @@ Turn requests into commands, then show back what took effect: quote the line fro
|
|
|
91
91
|
|
|
92
92
|
- **This session closed, crashed or got slow:** run `conduct console` (or `/clear`, then `/conductor:conductor-console`). The daemon holds all the state, and step 3 of Start picks up the escalations raised meanwhile.
|
|
93
93
|
- **The daemon is down** (`conduct status` says not running): `conduct start`. Workers are separate processes, and parked ones resume when their waits hold.
|
|
94
|
-
- **Back to conduct-atk** (a rollback): `"scheduler": { "mode": "observe" }` in `~/.conductor/config.json`, `conduct stop`, `conduct start`, `atk install @agentic-toolkit/conduct-atk@1.7.5 --pin`, then close this console and run `/conduct-atk` as before.
|