@detergent-software/conductor 3.2.0 → 3.3.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.
@@ -254,6 +254,11 @@ export function waitLabel(condition: string, now: number): string {
254
254
  if (host) return `PR #${number} ${PR_EVENTS[on] ?? on}${host === 'github' ? ` (${last(repo)})` : ''}`;
255
255
  break;
256
256
  }
257
+ case 'pr-opened': {
258
+ const [repo = ''] = read(/^(?:ado|github):([^:]+)/) ?? [];
259
+ if (repo) return `a new PR (${last(repo)})`;
260
+ break;
261
+ }
257
262
  case 'run': {
258
263
  const [pipeline = '', id = ''] = read(/^(?:ado|github):(.+)#(\d+):done$/) ?? [];
259
264
  if (pipeline) return `run #${id} to finish (${last(pipeline).replace(/\.ya?ml$/, '')})`;
@@ -427,6 +432,7 @@ export function parseFleet(body: unknown): ConductorWorker[] | null {
427
432
  activity: typeof w.activity === 'string' ? w.activity : w.status,
428
433
  id: w.id,
429
434
  item: typeof w.item === 'string' ? w.item : w.name,
435
+ links: links(w.links),
430
436
  name: w.name,
431
437
  phase: typeof w.phase === 'string' ? w.phase : null,
432
438
  pr: typeof w.pr === 'string' ? w.pr : null,
@@ -902,7 +908,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
902
908
  return typeof value === 'object' && value !== null && !Array.isArray(value);
903
909
  }
904
910
 
905
- // An inbox item's named links; one without an href or a label is left out.
911
+ // An inbox item's or a worker's named links; one without an href or a label is left out.
906
912
  function links(value: unknown): ConductorLink[] {
907
913
  if (!Array.isArray(value)) return [];
908
914
  return value.flatMap((l: unknown) =>
@@ -2,7 +2,7 @@ import type { EngineInterface, Register, RenderElement, Timer } from 'claude-cod
2
2
 
3
3
  import { atom, read, update } from 'claude-code';
4
4
 
5
- import type { ConductorSnapshot } from '../types';
5
+ import type { ConductorLink, ConductorSnapshot } from '../types';
6
6
 
7
7
  import {
8
8
  age,
@@ -877,6 +877,14 @@ export const register: Register = (on) => {
877
877
  const now = await $.clock.now();
878
878
  const rule = (title: string): string =>
879
879
  `─ ${title} ${'─'.repeat(Math.max(0, e.props.bodyColumns - title.length - 3))}`;
880
+ // An inbox item's or a worker's [open <label>]: on the Windows terminal a Button that runs the
881
+ // browser once, else a Link (ATK-494).
882
+ const openButton = (key: string, { href, label }: ConductorLink): RenderElement =>
883
+ e.surface === 'terminal' && isWindows && isWebLink(href) ? (
884
+ <Button key={key} label={`open ${label}`} onPress={() => openLink($, href)} />
885
+ ) : (
886
+ <Link href={href} key={key} label={`open ${label}`} />
887
+ );
880
888
  if (snap === null) return <Text dimColor>Reading the conductor…</Text>;
881
889
  if (snap.error !== null) {
882
890
  return (
@@ -977,13 +985,7 @@ export const register: Register = (on) => {
977
985
  onPress={(press) => copy($, command, press.surface)}
978
986
  />
979
987
  ))}
980
- {item.links.map(({ href, label }, n) =>
981
- e.surface === 'terminal' && isWindows && isWebLink(href) ? (
982
- <Button key={`link-${item.id}-${n}`} label={`open ${label}`} onPress={() => openLink($, href)} />
983
- ) : (
984
- <Link href={href} key={`link-${item.id}-${n}`} label={`open ${label}`} />
985
- ),
986
- )}
988
+ {item.links.map((l, n) => openButton(`link-${item.id}-${n}`, l))}
987
989
  {item.kind !== 'plan' && (
988
990
  <Button
989
991
  key={`done-${item.id}`}
@@ -1038,6 +1040,7 @@ export const register: Register = (on) => {
1038
1040
  onPress={async () => $.ui.toast(await attach($, w.id, w.name))}
1039
1041
  />
1040
1042
  <Button key={`why-${w.id}`} label='why' onPress={async () => $.ui.toast(await why($, w.item))} />
1043
+ {w.links.map((l, n) => openButton(`link-worker-${w.id}-${n}`, l))}
1041
1044
  <Text dimColor>
1042
1045
  {w.status} · {tokens(w.spendToday)} tok today
1043
1046
  {w.pr === null ? '' : ` · PR ${w.pr}`}
@@ -43,6 +43,8 @@ export type ConductorWorker = {
43
43
  activity: string;
44
44
  id: number;
45
45
  item: string;
46
+ // Its PR's page and its item's, named by the daemon (ATK-742).
47
+ links: ConductorLink[];
46
48
  name: string;
47
49
  phase: null | string;
48
50
  // Its PR as the daemon spells it, such as `ado:Contoso.Web#1850`; null before it has one.
@@ -85,11 +85,12 @@ An answer by anyone else is ignored, and the plan's inbox item says whose it was
85
85
 
86
86
  ## Waits
87
87
 
88
- `conduct wait <verb …>` registers a wait for the worker whose worktree it runs in. The daemon parks the worker once its turn ends, and resumes it when the wait holds. With no daemon, it checks the wait here and blocks: exit 0 when the wait holds, exit 2 after 29 minutes, exit 1 when it can't check. Each verb has a full form, which the store keeps. `item <item> comment` holds on the first comment by an approver (as Approvals defines them) after the item's newest note of any phase, and no more than 10 minutes before the wait registered. A note never counts, and a comment by anyone else is ignored and named on the Handoff's inbox item (`comment.ignored`). `item <item> unlabelled <label>` holds once the daemon has seen the label on since the wait registered and then sees it off, so a label added after the note doesn't resolve it at once. A verb or full form that doesn't parse is refused with exit 1, and the error names the form it expects.
88
+ `conduct wait <verb …>` registers a wait for the worker whose worktree it runs in. The daemon parks the worker once its turn ends, and resumes it when the wait holds. With no daemon, it checks the wait here and blocks: exit 0 when the wait holds, exit 2 after 29 minutes, exit 1 when it can't check. Each verb has a full form, which the store keeps. `item <item> comment` holds on the first comment by an approver (as Approvals defines them) after the item's newest note of any phase, and no more than 10 minutes before the wait registered. A note never counts, and a comment by anyone else is ignored and named on the Handoff's inbox item (`comment.ignored`). `item <item> unlabelled <label>` holds once the daemon has seen the label on since the wait registered and then sees it off, so a label added after the note doesn't resolve it at once. `pr opened` holds on a PR created in the repo after the wait registered, in any state, that its filters let through; the author lists are kept lowercase and sorted, so the same filters are the same wait. A verb or full form that doesn't parse is refused with exit 1, and the error names the form it expects.
89
89
 
90
90
  | Verb | Full form |
91
91
  |---|---|
92
92
  | `pr <n> [merged\|checks\|comment …] [--repo …]` | `pr:<ado\|github>:<repo>#<n>:<verdict\|merged\|checks\|comment>` |
93
+ | `pr opened [--author a,b \| --not-author a,b] [--base b] [--repo …]` | `pr-opened:<ado\|github>:<repo>[:base=<b>][:author=a,b \| :not-author=a,b]` |
93
94
  | `merge <sha> [--for a,b] [--repo …]` | `merge:<ado\|github>:<repo>@<sha>[:for=a,b]` |
94
95
  | `run <pipeline> <id> [done]` | `run:ado:<pipeline>#<id>:done` |
95
96
  | `npm <version> [--tag t] [--package p]` | `npm:<package>@<version>:<tag>` |