@detergent-software/conductor 3.2.0-dev.1 → 3.2.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +15 -11
  3. package/build/brief-cli.js +7 -11
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/checkout.js +8 -2
  6. package/build/checkout.js.map +1 -1
  7. package/build/cli.js +18 -3
  8. package/build/cli.js.map +1 -1
  9. package/build/code-host-cli.js +110 -35
  10. package/build/code-host-cli.js.map +1 -1
  11. package/build/code-host.js +16 -6
  12. package/build/code-host.js.map +1 -1
  13. package/build/conditions.js +15 -7
  14. package/build/conditions.js.map +1 -1
  15. package/build/follow-ups.js +23 -2
  16. package/build/follow-ups.js.map +1 -1
  17. package/build/help.js +53 -15
  18. package/build/help.js.map +1 -1
  19. package/build/in-process.js +12 -1
  20. package/build/in-process.js.map +1 -1
  21. package/build/inbox.js +34 -13
  22. package/build/inbox.js.map +1 -1
  23. package/build/manager.js +40 -12
  24. package/build/manager.js.map +1 -1
  25. package/build/note.js +121 -34
  26. package/build/note.js.map +1 -1
  27. package/build/notes.js +47 -32
  28. package/build/notes.js.map +1 -1
  29. package/build/profile-fields.js +1 -0
  30. package/build/profile-fields.js.map +1 -1
  31. package/build/profile.js +3 -0
  32. package/build/profile.js.map +1 -1
  33. package/build/queue.js +5 -2
  34. package/build/queue.js.map +1 -1
  35. package/build/runs.js +10 -12
  36. package/build/runs.js.map +1 -1
  37. package/build/scheduler.js +3 -0
  38. package/build/scheduler.js.map +1 -1
  39. package/build/server.js +2 -0
  40. package/build/server.js.map +1 -1
  41. package/build/store.js +13 -0
  42. package/build/store.js.map +1 -1
  43. package/build/tracker-writes.js +8 -1
  44. package/build/tracker-writes.js.map +1 -1
  45. package/build/watchers/ado.js +44 -8
  46. package/build/watchers/ado.js.map +1 -1
  47. package/build/watchers/boards-brief.js +14 -1
  48. package/build/watchers/boards-brief.js.map +1 -1
  49. package/build/watchers/boards-writes.js +41 -7
  50. package/build/watchers/boards-writes.js.map +1 -1
  51. package/build/watchers/issues-writes.js +4 -5
  52. package/build/watchers/issues-writes.js.map +1 -1
  53. package/build/watchers/linear-writes.js +11 -7
  54. package/build/watchers/linear-writes.js.map +1 -1
  55. package/build/watchers/merge.js +2 -1
  56. package/build/watchers/merge.js.map +1 -1
  57. package/package.json +3 -3
  58. package/plugin/hooks/model.ts +32 -11
  59. package/plugin/hooks/register.tsx +3 -3
  60. package/plugin/skills/conductor-console/SKILL.md +1 -1
  61. package/plugin/skills/work-item/SKILL.md +2 -2
  62. package/plugin/types/index.d.ts +2 -0
  63. package/protocol/PROTOCOL.md +3 -3
@@ -1,3 +1,4 @@
1
+ import { isOwnRepo } from "../code-host.js";
1
2
  import { LoginNeeded } from "../credentials.js";
2
3
  import { ConductorError } from "../errors.js";
3
4
  import { mergeLine, mergeRuns, NO_GIT, releaseStages } from "../runs.js";
@@ -28,7 +29,7 @@ export async function checkMerge(check) {
28
29
  }
29
30
  if (report.outcome === 'waiting')
30
31
  return null;
31
- return mergeLine({ ...report, stages: profile ? await releaseStages(ci, profile, repo, report) : {} });
32
+ return mergeLine({ ...report, stages: profile ? await releaseStages(ci, profile, repo, report) : {} }, { other: profile !== null && !isOwnRepo(profile, repo) });
32
33
  }
33
34
  // Each wait's merge facts, read once: they don't change once the merge exists. A wait no longer
34
35
  // open forgets them.
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/watchers/merge.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAgB,SAAS,EAAoB,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB1C,kGAAkG;AAClG,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAiB;IAChD,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC/C,IAAI,MAAmB,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,SAAS,CAAC;YACvB,6FAA6F;YAC7F,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE;YAC3B,EAAE;YACF,KAAK,EAAE,MAAM,KAAK,CAAC,KAAK,EAAE;YAC1B,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM;YAChC,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8FAA8F;QAC9F,kEAAkE;QAClE,IAAI,KAAK,YAAY,cAAc,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7G,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzG,CAAC;AAED,gGAAgG;AAChG,qBAAqB;AACrB,MAAM,UAAU,UAAU;IAIxB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,OAAO;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACvB,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/watchers/merge.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAgB,SAAS,EAAoB,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB1C,kGAAkG;AAClG,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAiB;IAChD,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC/C,IAAI,MAAmB,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,SAAS,CAAC;YACvB,6FAA6F;YAC7F,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE;YAC3B,EAAE;YACF,KAAK,EAAE,MAAM,KAAK,CAAC,KAAK,EAAE;YAC1B,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM;YAChC,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8FAA8F;QAC9F,kEAAkE;QAClE,IAAI,KAAK,YAAY,cAAc,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7G,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,SAAS,CACd,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EACpF,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CACzD,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,qBAAqB;AACrB,MAAM,UAAU,UAAU;IAIxB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,OAAO;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACvB,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@detergent-software/conductor",
3
- "version": "3.2.0-dev.1",
3
+ "version": "3.2.0-dev.11",
4
4
  "description": "Conductor: the local daemon that runs conducted projects' work loops (conduct serve) and its conduct CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,8 +45,8 @@
45
45
  "semantic-release": "^25.0.3",
46
46
  "typescript": "~6.0.3",
47
47
  "vitest": "^5.0.3",
48
- "@atk/tsconfig": "0.0.0",
49
- "@atk/eslint-config": "0.0.0"
48
+ "@atk/eslint-config": "0.0.0",
49
+ "@atk/tsconfig": "0.0.0"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsc",
@@ -303,20 +303,34 @@ export function errorOf(answer: ApiAnswer): string {
303
303
  return `the conductor answered ${answer.status}`;
304
304
  }
305
305
 
306
- // The open scope, blocked or human Handoff of `ref` that /answer posts on (ATK-682), as findPlan
307
- // finds a plan: an item key, or an inbox id as #12 (any kind; the daemon refuses the rest).
306
+ // The open Handoff of `ref` that /answer posts on (ATK-682), as findPlan finds a plan: an item key,
307
+ // or an inbox id as #12 (any kind; the daemon refuses the rest).
308
308
  export function findHandoff(inbox: readonly ConductorInboxItem[], ref: string): Found<ConductorInboxItem> {
309
- return findOpen(inbox, ref, ANSWERED_KINDS, { none: 'no Handoff of', several: 'several Handoffs of' });
309
+ return findOpen(inbox, ref, takesAnswer, { none: 'no Handoff of', several: 'several Handoffs of' });
310
310
  }
311
311
 
312
312
  // The open plan of `ref` (as `conduct approve` finds it): an item key, or an inbox id as #12.
313
313
  export function findPlan(inbox: readonly ConductorInboxItem[], ref: string): Found<ConductorInboxItem> {
314
- return findOpen(inbox, ref, PLAN_KINDS, { none: 'no plan of', several: 'several plans of' });
314
+ return findOpen(inbox, ref, (i) => i.kind === 'plan', { none: 'no plan of', several: 'several plans of' });
315
+ }
316
+
317
+ // The items /answer offers on, as the daemon's inbox takes an answer (ATK-681, ATK-718): a scope,
318
+ // blocked, human or operator Handoff's, and a failed one only while its worker waits on an answer on
319
+ // its item, since the daemon raises failed items of its own (red checks, a lost session).
320
+ export function takesAnswer(item: ConductorInboxItem): boolean {
321
+ if (item.item === null) return false;
322
+ if (item.kind !== 'failed') return ANSWERED_KINDS.includes(item.kind);
323
+ // An item wait's full form (conditions.ts), on the item itself: `item:<tracker>:<item>:comment` or
324
+ // `…:unlabelled=<label>`.
325
+ const [verb, , on, field] = (item.condition ?? '').split(':');
326
+ return (
327
+ verb === 'item' &&
328
+ on?.toUpperCase() === item.item.toUpperCase() &&
329
+ (field === 'comment' || field?.startsWith('unlabelled=') === true)
330
+ );
315
331
  }
316
332
 
317
- // The inbox kinds /answer offers on: those whose worker waits on an answer on the item (ATK-681).
318
- export const ANSWERED_KINDS: readonly string[] = ['scope', 'blocked', 'human'];
319
- const PLAN_KINDS: readonly string[] = ['plan'];
333
+ const ANSWERED_KINDS: readonly string[] = ['scope', 'blocked', 'human', 'operator'];
320
334
 
321
335
  // A worker in flight by name or item; a name match wins, as `conduct attach` finds it.
322
336
  export function findWorker(workers: readonly ConductorWorker[], ref: string): Found<ConductorWorker> {
@@ -434,6 +448,7 @@ export function parseInbox(body: unknown): ConductorInboxItem[] | null {
434
448
  return [
435
449
  {
436
450
  commands: strings(i.commands),
451
+ condition: typeof i.condition === 'string' ? i.condition : null,
437
452
  id: i.id,
438
453
  item: typeof i.item === 'string' ? i.item : null,
439
454
  kind: i.kind,
@@ -539,7 +554,7 @@ export function tokens(n: number): string {
539
554
  function findOpen(
540
555
  inbox: readonly ConductorInboxItem[],
541
556
  ref: string,
542
- kinds: readonly string[],
557
+ waits: (item: ConductorInboxItem) => boolean,
543
558
  say: { none: string; several: string },
544
559
  ): Found<ConductorInboxItem> {
545
560
  const byId = /^#(\d+)$/.exec(ref.trim());
@@ -550,11 +565,11 @@ function findOpen(
550
565
  if (/^\d+$/.test(ref.trim())) {
551
566
  const n = ref.trim();
552
567
  // The waiting items whose key ends in the number, else a placeholder: the project's key isn't known here.
553
- const keys = inbox.flatMap((i) => (kinds.includes(i.kind) && i.item?.endsWith(`-${n}`) ? [i.item] : []));
568
+ const keys = inbox.flatMap((i) => (waits(i) && i.item?.endsWith(`-${n}`) ? [i.item] : []));
554
569
  const item = keys.length > 0 ? keys.join(' or ') : `<KEY>-${n}`;
555
570
  return { error: `"${ref}" is ambiguous: name the item, such as ${item}, or the inbox id as #${n}` };
556
571
  }
557
- const found = inbox.filter((i) => kinds.includes(i.kind) && i.item?.toUpperCase() === ref.trim().toUpperCase());
572
+ const found = inbox.filter((i) => waits(i) && i.item?.toUpperCase() === ref.trim().toUpperCase());
558
573
  if (found.length === 1 && found[0]) return { found: found[0] };
559
574
  if (found.length === 0) return { error: `${say.none} ${ref} waits in the inbox` };
560
575
  return { error: `${say.several} ${ref} wait: name one as ${found.map((p) => `#${p.id}`).join(' or ')}` };
@@ -579,7 +594,13 @@ export function answerText(action: 'answer' | 'approve' | 'done' | 'revise', id:
579
594
  }
580
595
  const body = isRecord(answer.body) ? answer.body : {};
581
596
  const key = isRecord(body.item) && typeof body.item.item === 'string' ? body.item.item : `#${id}`;
582
- if (action === 'done') return `done: ${key}`;
597
+ if (action === 'done') {
598
+ // A Handoff's worker parked on an answer on its item stays parked, as `conduct done` says (ATK-718).
599
+ const condition = isRecord(body.item) && typeof body.item.condition === 'string' ? body.item.condition : '';
600
+ return condition.endsWith(':comment') && key !== `#${id}`
601
+ ? `done: ${key}; its worker still waits on ${condition}: comment on ${key}, or /attach ${key}, to resume it`
602
+ : `done: ${key}`;
603
+ }
583
604
  const comment = isRecord(body.comment) ? body.comment : {};
584
605
  const where =
585
606
  typeof comment.url === 'string' ? comment.url : typeof comment.id === 'string' ? comment.id : 'comment posted';
@@ -6,7 +6,6 @@ import type { ConductorSnapshot } from '../types';
6
6
 
7
7
  import {
8
8
  age,
9
- ANSWERED_KINDS,
10
9
  answerText,
11
10
  type ApiAnswer,
12
11
  approvedPlanItem,
@@ -47,6 +46,7 @@ import {
47
46
  snapshotOf,
48
47
  spendToday,
49
48
  statusText,
49
+ takesAnswer,
50
50
  tokens,
51
51
  triageText,
52
52
  WARM_AFTER_MS,
@@ -521,7 +521,7 @@ async function registerCommands($: EngineInterface): Promise<void> {
521
521
  });
522
522
  await $.command.register({
523
523
  argumentHint: '<item | #id> <your answer>',
524
- description: 'Answer a scope, blocked or human Handoff (posts it on the item as you)',
524
+ description: 'Answer a scope, blocked, human, operator or failed Handoff (posts it on the item as you)',
525
525
  name: 'answer',
526
526
  });
527
527
  await $.command.register({
@@ -947,7 +947,7 @@ export const register: Register = (on) => {
947
947
  onPress={() => $.prompt.fill({ text: `/revise ${item.item ?? ''} ` })}
948
948
  />
949
949
  )}
950
- {ANSWERED_KINDS.includes(item.kind) && item.item !== null && (
950
+ {takesAnswer(item) && (
951
951
  <Button
952
952
  key={`answer-${item.id}`}
953
953
  label='answer'
@@ -80,7 +80,7 @@ Turn requests into commands, then show back what took effect: quote the line fro
80
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
82
  | "approve ENG-12" / "ENG-12: change X" | `conduct approve ENG-12` / `conduct revise ENG-12 <their words>` |
83
- | "tell ENG-12 to keep -d" (an answer to its `scope`, `blocked` or `human` Handoff) | `conduct done <its inbox id> --answer <their words>`: posts their words on the item, and its worker resumes once the conductor reads them. `conduct inbox` gives the id |
83
+ | "tell ENG-12 to keep -d" (an answer to its `scope`, `blocked`, `human`, `operator` or `failed` Handoff) | `conduct done <its inbox id> --answer <their words>`: posts their words on the item, and its worker resumes once the conductor reads them. `conduct inbox` gives the id |
84
84
  | "queue ENG-40" / "hold ENG-40" (a new item in triage) | `conduct triage ENG-40 --queue` / `conduct triage ENG-40 --hold` |
85
85
  | "start ENG-40 now" | `conduct launch ENG-40` |
86
86
  | "show me ENG-12" | `conduct attach ENG-12` (opens its tab) |
@@ -124,12 +124,12 @@ A conducted worker writes its notes and waits through `conduct`, so the conducto
124
124
  | The operator's merge (review mode) | the PR opened note headed `<skill> · PR opened · merge: <PR URL>`, or Handoff · merge | the same |
125
125
  | Pipelines after the merge (step 9.2) | none | `conduct runs <mergeCommit>` (with `--for`, as step 9.2 says). Act on its outcome as in step 9; on `waiting`: `conduct wait merge <mergeCommit>` with the same `--for`; on `cancelled` or `missing`, or once on a `failed` whose log shows a flake: `conduct runs <mergeCommit> --requeue <pipeline>`, then the same `conduct wait merge`; requeue refused as under 15 minutes old: `conduct wait time <the time it names>` |
126
126
  | npm OTP (9.3) | Handoff · npm-otp | `conduct wait npm <version> --package <package>`, plus the dist-tag the `after-merge` hook names (`--tag <tag>`); one per staged package. The version on its tag resumes you, with no message from anyone |
127
- | The operator's answer on the item | Handoff · scope, blocked or human | `conduct wait item KEY-n comment`: an approver's comment after your newest note. `blocked` on another item also waits on it: `conduct wait item <blocker> done`. `human` also waits on its label coming off: `conduct wait item KEY-n unlabelled <the profile's tracker.humanLabel>`. A comment, the blocker done or the label off resumes you, with no console or tab message |
127
+ | The operator's answer on the item | Handoff · scope, blocked, human, operator or failed | `conduct wait item KEY-n comment`: an approver's comment after your newest note (for `operator`, that the action is done; for `failed`, what to do about it). `blocked` on another item also waits on it: `conduct wait item <blocker> done`. `human` also waits on its label coming off: `conduct wait item KEY-n unlabelled <the profile's tracker.humanLabel>`. A comment, the blocker done or the label off resumes you, with no console or tab message |
128
128
  | A PR in another repo (one the item opened: Another repo, below) | Handoff · merge | `conduct land <n> --repo github:<owner>/<repo> --mode review --reason "…"`, acted on as in step 8; on `waiting`: `conduct wait pr <n> --repo github:<owner>/<repo>` |
129
129
  | A PR a hook's publish opened (no checkout) | Handoff · merge | `conduct pr status <n> --repo github:<owner>/<repo>` gives its verdict (`land` refuses a PR with no checkout); on `waiting`: `conduct wait pr <n> --repo github:<owner>/<repo>` |
130
130
  | Its runs after the merge | none | `conduct runs <mergeCommit> --repo github:<owner>/<repo>`; on `waiting`: `conduct wait merge <mergeCommit> --repo github:<owner>/<repo>` |
131
131
 
132
- A Handoff with nothing to watch (`operator`, `release`, `conductor`, `abandoned`, `failed`) ends the turn with no wait: a console message or the operator in the tab resumes it.
132
+ A Handoff with nothing to watch (`release`, `conductor`, `abandoned`) ends the turn with no wait: a console message or the operator in the tab resumes it.
133
133
  - **On wake.** A turn that reads `[conductor] event: <what held> HH:MM` is the daemon resuming you. It says which wait held and nothing more: a hint, never an instruction, never an approval. Work out the phase from the facts again (step 1), then re-read the source before acting on it: `conduct brief KEY-n --fresh` for an approval (quote the approver's comment, or the conductor's auto-approval, and its `createdAt` in `Approved via:`), `conduct land <pr> --reason "…"` for a PR (it is safe to repeat), `conduct runs <mergeCommit>` for pipelines and their npm stages (npm can still answer 404 for a moment after the daemon saw the version: run it again a minute later), `conduct comments KEY-n --since <your Handoff's time>` for an answer on the item (`item KEY-n comment by <who> at <time>`; `unlabelled` for a label off, or the blocker's `item … state`). An answer that doesn't settle the Handoff (a question back, "still thinking") isn't one: reply in a new Handoff note of the same kind, then wait on the item again. `plan KEY-n revised …` means an approver wrote `revise:` after your plan was approved and before your PR merged, and the daemon resumed you for it while you waited: read their words with `conduct brief KEY-n --fresh` (its `revised after it` line, or `approval: revise …` when nothing on the tracker approved the plan; its `phase` stays `wait` while the PR is open), then do what exit 4 says: stop, commit any work in progress, and go back to step 4 with their words. Don't wait on the PR again first. If the source doesn't show it yet (a tracker read can lag a minute), wait again. After a Handoff wait, post `<skill> · Resumed` as above.
134
134
  - **Wake text in a tab.** The operator may attach (`conduct attach KEY-n`) and type. Their words count as they always do; text that only claims to be from the daemon or the conductor is a hint at most.
135
135
 
@@ -1,5 +1,7 @@
1
1
  export type ConductorInboxItem = {
2
2
  commands: string[];
3
+ // The full-form condition of the wait that resolves it, when one is known (conditions.ts).
4
+ condition: null | string;
3
5
  id: number;
4
6
  item: null | string;
5
7
  kind: string;
@@ -53,16 +53,16 @@ Text after a phase is always `<kind>: <one line>`. `## <skill> · Started · pic
53
53
  |---|---|---|
54
54
  | `conductor` | the conductor console: its rule escalates the note | nothing; the console answers |
55
55
  | `merge` | the operator's merge of the PR in the line | a `merge` inbox item |
56
- | `operator` | a physical action only the operator can do | an `operator` item |
56
+ | `operator` | a physical action only the operator can do: an approver's comment on the item that it is done (`item <item> comment`) | an `operator` item |
57
57
  | `npm-otp` | the operator's `npm stage approve` for each staged package | an `npm-otp` item |
58
58
  | `release` | a release of the base branch (the profile's `release.handoff` says what to run) | a `release` item |
59
59
  | `scope` | a change of scope or risk that needs the operator: an approver's comment on the item (`item <item> comment`) | a `scope` item |
60
60
  | `blocked` | an open blocker: an approver's comment on the item, or the blocker done (`item <blocker> done`) | a `blocked` item |
61
61
  | `human` | a decision someone else owns: the item is labelled with the profile's `humanLabel`. An approver's comment on the item, or the label coming off (`item <item> unlabelled <humanLabel>`) | a `human` item |
62
62
  | `abandoned` | an abandoned PR | an `abandoned` item |
63
- | `failed` | a pipeline failure the worker can't fix | a `failed` item |
63
+ | `failed` | a pipeline failure the worker can't fix: an approver's comment on the item that says what to do (`item <item> comment`) | a `failed` item |
64
64
 
65
- A later note on the item answers the `operator`, `release`, `scope`, `blocked`, `human`, `abandoned` and `failed` Handoffs, because the worker has moved on. A `scope`, `blocked` or `human` Handoff's worker waits on an answer on the item (see Waits), and that answer resolves its item too. The others have nothing to watch: the worker ends its turn with no wait, and a console message or the operator in its tab resumes it.
65
+ A later note on the item answers the `operator`, `release`, `scope`, `blocked`, `human`, `abandoned` and `failed` Handoffs, because the worker has moved on. A `scope`, `blocked`, `human`, `operator` or `failed` Handoff's worker waits on an answer on the item (see Waits), and that answer resolves its item too. A `failed` Handoff's item stays open while its worker waits on its item, even when the worker runs again (an attach): the daemon's own failures, such as a lost session, resolve then, and a `failed` Handoff doesn't. The others have nothing to watch: the worker ends its turn with no wait, and a console message or the operator in its tab resumes it.
66
66
 
67
67
  ### `conduct note <item> <file>`
68
68