@detergent-software/conductor 3.0.0-dev.8 → 3.0.0

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 (210) hide show
  1. package/CHANGELOG.md +482 -0
  2. package/README.md +275 -125
  3. package/build/brief-cli.js +5 -12
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/brief.js +4 -3
  6. package/build/brief.js.map +1 -1
  7. package/build/broker-cli.js +42 -15
  8. package/build/broker-cli.js.map +1 -1
  9. package/build/broker.js +1 -1
  10. package/build/broker.js.map +1 -1
  11. package/build/cli.js +221 -63
  12. package/build/cli.js.map +1 -1
  13. package/build/clones.js +92 -0
  14. package/build/clones.js.map +1 -0
  15. package/build/code-host-cli.js +172 -40
  16. package/build/code-host-cli.js.map +1 -1
  17. package/build/code-host-service.js +35 -2
  18. package/build/code-host-service.js.map +1 -1
  19. package/build/code-host.js +138 -16
  20. package/build/code-host.js.map +1 -1
  21. package/build/conditions.js +33 -16
  22. package/build/conditions.js.map +1 -1
  23. package/build/config.js +58 -28
  24. package/build/config.js.map +1 -1
  25. package/build/console.js +17 -27
  26. package/build/console.js.map +1 -1
  27. package/build/credentials.js +86 -54
  28. package/build/credentials.js.map +1 -1
  29. package/build/daemon.js +54 -65
  30. package/build/daemon.js.map +1 -1
  31. package/build/file-lock.js +155 -40
  32. package/build/file-lock.js.map +1 -1
  33. package/build/follow-ups.js +2 -2
  34. package/build/follow-ups.js.map +1 -1
  35. package/build/help.js +241 -83
  36. package/build/help.js.map +1 -1
  37. package/build/history.js +10 -4
  38. package/build/history.js.map +1 -1
  39. package/build/host/credential-manager.js +109 -0
  40. package/build/host/credential-manager.js.map +1 -0
  41. package/build/host/keychain.js +44 -0
  42. package/build/host/keychain.js.map +1 -0
  43. package/build/host/launchd.js +118 -0
  44. package/build/host/launchd.js.map +1 -0
  45. package/build/host/libsecret.js +40 -0
  46. package/build/host/libsecret.js.map +1 -0
  47. package/build/host/macos-terminal.js +63 -0
  48. package/build/host/macos-terminal.js.map +1 -0
  49. package/build/host/platform.js +4 -0
  50. package/build/host/platform.js.map +1 -0
  51. package/build/host/secrets.js +46 -0
  52. package/build/host/secrets.js.map +1 -0
  53. package/build/host/shell.js +26 -0
  54. package/build/host/shell.js.map +1 -0
  55. package/build/host/startup.js +26 -0
  56. package/build/host/startup.js.map +1 -0
  57. package/build/host/systemd.js +91 -0
  58. package/build/host/systemd.js.map +1 -0
  59. package/build/host/task-scheduler.js +78 -0
  60. package/build/host/task-scheduler.js.map +1 -0
  61. package/build/host/terminal.js +46 -0
  62. package/build/host/terminal.js.map +1 -0
  63. package/build/host/tmux.js +39 -0
  64. package/build/host/tmux.js.map +1 -0
  65. package/build/host/windows-terminal.js +46 -0
  66. package/build/host/windows-terminal.js.map +1 -0
  67. package/build/in-process.js +6 -5
  68. package/build/in-process.js.map +1 -1
  69. package/build/inbox.js +57 -39
  70. package/build/inbox.js.map +1 -1
  71. package/build/init.js +509 -0
  72. package/build/init.js.map +1 -0
  73. package/build/landing.js +107 -10
  74. package/build/landing.js.map +1 -1
  75. package/build/lib/providers/ado/code-host.js +5 -1
  76. package/build/lib/providers/ado/code-host.js.map +1 -1
  77. package/build/lib/providers/ado/pipelines.js +138 -26
  78. package/build/lib/providers/ado/pipelines.js.map +1 -1
  79. package/build/lib/providers/ado/trigger.js +5 -0
  80. package/build/lib/providers/ado/trigger.js.map +1 -1
  81. package/build/lib/providers/ado/writes.js +17 -2
  82. package/build/lib/providers/ado/writes.js.map +1 -1
  83. package/build/lib/providers/github/actions.js +90 -3
  84. package/build/lib/providers/github/actions.js.map +1 -1
  85. package/build/lib/providers/github/code-host.js +2 -0
  86. package/build/lib/providers/github/code-host.js.map +1 -1
  87. package/build/lib/providers/github/writes.js +25 -2
  88. package/build/lib/providers/github/writes.js.map +1 -1
  89. package/build/lib/providers/index.js +3 -2
  90. package/build/lib/providers/index.js.map +1 -1
  91. package/build/lib/providers/npm/stage.js +14 -1
  92. package/build/lib/providers/npm/stage.js.map +1 -1
  93. package/build/lib/providers/timing.js +10 -0
  94. package/build/lib/providers/timing.js.map +1 -0
  95. package/build/lib/providers/verdicts/pr.js +47 -6
  96. package/build/lib/providers/verdicts/pr.js.map +1 -1
  97. package/build/lib/providers/verdicts/runs.js +12 -2
  98. package/build/lib/providers/verdicts/runs.js.map +1 -1
  99. package/build/logins.js +13 -6
  100. package/build/logins.js.map +1 -1
  101. package/build/manager.js +121 -55
  102. package/build/manager.js.map +1 -1
  103. package/build/note.js +5 -15
  104. package/build/note.js.map +1 -1
  105. package/build/notes.js +130 -23
  106. package/build/notes.js.map +1 -1
  107. package/build/notify.js +104 -42
  108. package/build/notify.js.map +1 -1
  109. package/build/npm-reads.js +151 -22
  110. package/build/npm-reads.js.map +1 -1
  111. package/build/ownership.js +2 -2
  112. package/build/paths.js +44 -1
  113. package/build/paths.js.map +1 -1
  114. package/build/plugin.js +4 -2
  115. package/build/plugin.js.map +1 -1
  116. package/build/profile-fields.js +153 -0
  117. package/build/profile-fields.js.map +1 -0
  118. package/build/profile-layers.js +264 -0
  119. package/build/profile-layers.js.map +1 -0
  120. package/build/profile.js +107 -51
  121. package/build/profile.js.map +1 -1
  122. package/build/project-check-cli.js +17 -19
  123. package/build/project-check-cli.js.map +1 -1
  124. package/build/project-check.js +166 -104
  125. package/build/project-check.js.map +1 -1
  126. package/build/protocol.js +5 -21
  127. package/build/protocol.js.map +1 -1
  128. package/build/pushover.js +5 -5
  129. package/build/pushover.js.map +1 -1
  130. package/build/queue.js +4 -4
  131. package/build/queue.js.map +1 -1
  132. package/build/registry.js +195 -36
  133. package/build/registry.js.map +1 -1
  134. package/build/release-kinds.js +34 -0
  135. package/build/release-kinds.js.map +1 -0
  136. package/build/rule-actions.js +8 -6
  137. package/build/rule-actions.js.map +1 -1
  138. package/build/rules-service.js +104 -47
  139. package/build/rules-service.js.map +1 -1
  140. package/build/rules.js +23 -23
  141. package/build/rules.js.map +1 -1
  142. package/build/run.js +20 -7
  143. package/build/run.js.map +1 -1
  144. package/build/runs.js +218 -68
  145. package/build/runs.js.map +1 -1
  146. package/build/scheduler.js +89 -13
  147. package/build/scheduler.js.map +1 -1
  148. package/build/search.js +2 -1
  149. package/build/search.js.map +1 -1
  150. package/build/server.js +51 -14
  151. package/build/server.js.map +1 -1
  152. package/build/spend.js +1 -1
  153. package/build/spend.js.map +1 -1
  154. package/build/store.js +79 -20
  155. package/build/store.js.map +1 -1
  156. package/build/supervise.js +1 -1
  157. package/build/supervise.js.map +1 -1
  158. package/build/timeline.js +104 -0
  159. package/build/timeline.js.map +1 -0
  160. package/build/tracker-reads-cli.js +4 -5
  161. package/build/tracker-reads-cli.js.map +1 -1
  162. package/build/trackers.js +56 -10
  163. package/build/trackers.js.map +1 -1
  164. package/build/train-cli.js +6 -5
  165. package/build/train-cli.js.map +1 -1
  166. package/build/train.js +2 -2
  167. package/build/train.js.map +1 -1
  168. package/build/upgrade.js +8 -9
  169. package/build/upgrade.js.map +1 -1
  170. package/build/wait.js +10 -10
  171. package/build/wait.js.map +1 -1
  172. package/build/watchers/ado.js +42 -19
  173. package/build/watchers/ado.js.map +1 -1
  174. package/build/watchers/github.js +18 -4
  175. package/build/watchers/github.js.map +1 -1
  176. package/build/watchers/index.js +2 -2
  177. package/build/watchers/index.js.map +1 -1
  178. package/build/watchers/linear-queue.js +47 -14
  179. package/build/watchers/linear-queue.js.map +1 -1
  180. package/build/watchers/linear-writes.js +26 -12
  181. package/build/watchers/linear-writes.js.map +1 -1
  182. package/build/watchers/linear.js +2 -2
  183. package/build/watchers/linear.js.map +1 -1
  184. package/build/watchers/source.js +5 -1
  185. package/build/watchers/source.js.map +1 -1
  186. package/build/webhook.js +80 -0
  187. package/build/webhook.js.map +1 -0
  188. package/build/why.js +1 -1
  189. package/build/why.js.map +1 -1
  190. package/build/worktrees.js +56 -93
  191. package/build/worktrees.js.map +1 -1
  192. package/package.json +61 -61
  193. package/plugin/.claude-plugin/plugin.json +1 -1
  194. package/plugin/hooks/model.ts +46 -6
  195. package/plugin/hooks/register.tsx +41 -2
  196. package/plugin/skills/conductor-console/SKILL.md +26 -27
  197. package/plugin/skills/work-item/SKILL.md +58 -54
  198. package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
  199. package/plugin/skills/work-item/trackers/github-issues.md +1 -1
  200. package/plugin/skills/work-item/trackers/linear.md +2 -2
  201. package/plugin/types/index.d.ts +1 -1
  202. package/protocol/PROTOCOL.md +3 -9
  203. package/build/checkout-sync.js +0 -249
  204. package/build/checkout-sync.js.map +0 -1
  205. package/build/credential.js +0 -66
  206. package/build/credential.js.map +0 -1
  207. package/build/startup.js +0 -92
  208. package/build/startup.js.map +0 -1
  209. package/build/tab.js +0 -32
  210. package/build/tab.js.map +0 -1
@@ -0,0 +1,46 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { run as runCommand } from "../run.js";
5
+ import { appleTerminal, iterm } from "./macos-terminal.js";
6
+ import { hostPlatform } from "./platform.js";
7
+ import { tmux } from "./tmux.js";
8
+ import { windowsTerminal } from "./windows-terminal.js";
9
+ // config.json's workers.terminal: auto is Windows Terminal on Windows, iTerm on macOS where it is
10
+ // installed and Terminal where it isn't, and tmux elsewhere.
11
+ export const TERMINALS = ['auto', 'windows-terminal', 'terminal', 'iterm', 'tmux'];
12
+ const ITERM_APPS = ['/Applications/iTerm.app', join(homedir(), 'Applications', 'iTerm.app')];
13
+ // This platform's terminal, or the one config.json names.
14
+ export function terminal({ choice = 'auto', installed = existsSync, platform = hostPlatform(), run = runCommand, } = {}) {
15
+ const picked = choice !== 'auto'
16
+ ? choice
17
+ : platform === 'win32'
18
+ ? 'windows-terminal'
19
+ : platform === 'darwin'
20
+ ? ITERM_APPS.some((app) => installed(app))
21
+ ? 'iterm'
22
+ : 'terminal'
23
+ : 'tmux';
24
+ const terminals = {
25
+ iterm: () => iterm(run),
26
+ terminal: () => appleTerminal(run),
27
+ tmux: () => tmux(run),
28
+ 'windows-terminal': () => windowsTerminal(run),
29
+ };
30
+ return terminals[picked]();
31
+ }
32
+ export function workerTabs(where, onLaunch) {
33
+ return {
34
+ hint: (window) => where.attachHint(window),
35
+ onLaunch,
36
+ open: (tab) => where.open({
37
+ command: ['claude', 'attach', tab.handle],
38
+ cwd: tab.cwd,
39
+ env: {},
40
+ hold: true,
41
+ title: tab.title,
42
+ window: tab.window,
43
+ }),
44
+ };
45
+ }
46
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/host/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAqB,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,kGAAkG;AAClG,6DAA6D;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AA4C5F,MAAM,UAAU,GAAG,CAAC,yBAAyB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;AAE7F,0DAA0D;AAC1D,MAAM,UAAU,QAAQ,CAAC,EACvB,MAAM,GAAG,MAAM,EACf,SAAS,GAAG,UAAU,EACtB,QAAQ,GAAG,YAAY,EAAE,EACzB,GAAG,GAAG,UAAU,MAOd,EAAE;IACJ,MAAM,MAAM,GACV,MAAM,KAAK,MAAM;QACf,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,QAAQ,KAAK,OAAO;YACpB,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,QAAQ,KAAK,QAAQ;gBACrB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACxC,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,UAAU;gBACd,CAAC,CAAC,MAAM,CAAC;IACjB,MAAM,SAAS,GAA0C;QACvD,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;QACvB,QAAQ,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;QAClC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;QACrB,kBAAkB,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;KAC/C,CAAC;IACF,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAe,EAAE,QAAiB;IAC3D,OAAO;QACL,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAC1C,QAAQ;QACR,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CACZ,KAAK,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC;YACzC,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,EAAE;YACP,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC;KACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { outputOf } from "../run.js";
2
+ import { shLine, viaSh } from "./shell.js";
3
+ // tmux: a detached session per window, made by its first tab, and a tmux window per tab. It runs
4
+ // where there is no desktop (a server over ssh), so a person reaches it with `tmux attach`. tmux
5
+ // starts a tab in the tmux server's environment, so `env` goes on the command line, and runs it in
6
+ // its default-shell, so the line goes through sh.
7
+ export function tmux(run) {
8
+ return {
9
+ attachHint: (window) => `tmux attach -t ${tmuxSession(window)}`,
10
+ name: 'tmux',
11
+ open: async (tab) => {
12
+ const session = tmuxSession(tab.window);
13
+ // `=` matches the name exactly, not as a prefix.
14
+ const has = await run('tmux', ['has-session', '-t', `=${session}`], { timeoutMs: 30_000 });
15
+ if (has.error !== null && /ENOENT/.test(has.error))
16
+ return 'no tmux to open it in';
17
+ // tmux expands formats in -n and -c, and `#(…)` in one runs a command: `##` is a plain `#`.
18
+ const literal = (text) => text.replace(/#/g, '##');
19
+ const title = literal(tab.title.replace(/\s+/g, ' ').trim().slice(0, 80));
20
+ const open = (where) => run('tmux', [...where, '-n', title, '-c', literal(tab.cwd), viaSh(shLine(tab))], { timeoutMs: 30_000 });
21
+ const inSession = ['new-window', '-t', `=${session}:`];
22
+ let result = await open(has.code === 0 ? inSession : ['new-session', '-d', '-s', session]);
23
+ // Another tab made the session since has-session asked.
24
+ if (has.code !== 0 && /duplicate session/.test(result.stderr))
25
+ result = await open(inSession);
26
+ return result.code === 0 ? null : outputOf(result, 500);
27
+ },
28
+ };
29
+ }
30
+ // The tmux session a window's tabs open in: its name in lower case, with a dash for anything tmux
31
+ // reads in a target (`.` and `:`) and anything a person would have to quote. "Conductor · web"
32
+ // becomes conductor-web.
33
+ export function tmuxSession(window) {
34
+ return (window
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9_-]+/g, '-')
37
+ .replace(/^-+|-+$/g, '') || 'conductor');
38
+ }
39
+ //# sourceMappingURL=tmux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../src/host/tmux.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE3C,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,kDAAkD;AAClD,MAAM,UAAU,IAAI,CAAC,GAAQ;IAC3B,OAAO;QACL,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,WAAW,CAAC,MAAM,CAAC,EAAE;QAC/D,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxC,iDAAiD;YACjD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3F,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,uBAAuB,CAAC;YACnF,4FAA4F;YAC5F,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAG,CAAC,KAAwB,EAAE,EAAE,CACxC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YAC1G,MAAM,SAAS,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,OAAO,GAAG,CAAC,CAAC;YACvD,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YAC3F,wDAAwD;YACxD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;gBAAE,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9F,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,+FAA+F;AAC/F,yBAAyB;AACzB,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,CACL,MAAM;SACH,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;SAC7B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,WAAW,CAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { outputOf } from "../run.js";
2
+ import { failedText } from "./shell.js";
3
+ // Windows Terminal (wt.exe). Not hidden: when the named window doesn't exist yet, Windows Terminal
4
+ // creates it with the spawn's SW_HIDE, and the tab runs where nobody sees it (ATK-469, ATK-336).
5
+ export function windowsTerminal(run) {
6
+ return {
7
+ attachHint: () => null,
8
+ name: 'Windows Terminal',
9
+ open: async (tab) => {
10
+ const args = windowsTerminalArgs(tab);
11
+ if (typeof args === 'string')
12
+ return args;
13
+ const result = await run('wt.exe', args, { cwd: tab.cwd, timeoutMs: 60_000, windowsHide: false });
14
+ if (result.code === 0)
15
+ return null;
16
+ return result.error !== null && /ENOENT/.test(result.error)
17
+ ? 'no Windows Terminal (wt.exe) to open it in'
18
+ : outputOf(result, 500);
19
+ },
20
+ };
21
+ }
22
+ // wt.exe's arguments for a tab in the named window. wt.exe splits its own arguments on `;`, so the
23
+ // window and title lose it, a folder with one is left out (the tab starts in its profile's, as
24
+ // tabs did before ATK-529), and a command with one can't open (a string says why). A held command
25
+ // runs in pwsh and chains its prompt with `||`. The tab gets wt.exe's environment, which is the
26
+ // caller's even in a window already open, so `env` needs no setting.
27
+ export function windowsTerminalArgs(tab) {
28
+ const split = tab.command.find((part) => part.includes(';'));
29
+ if (split !== undefined)
30
+ return `a Windows Terminal tab can't take ";": ${split}`;
31
+ const clean = (text) => text.replace(/;/g, ',').replace(/\s+/g, ' ').trim();
32
+ const command = tab.hold
33
+ ? ['pwsh', '-Command', `${psLine(tab.command)} || Read-Host ${psQuote(failedText(tab.command))}`]
34
+ : tab.command;
35
+ const folder = tab.cwd.includes(';') ? [] : ['-d', tab.cwd];
36
+ return ['-w', clean(tab.window), 'new-tab', '--title', clean(tab.title).slice(0, 80), ...folder, ...command];
37
+ }
38
+ // A command as pwsh runs it: words bare where they can be, and a quoted program called with `&`.
39
+ function psLine(command) {
40
+ const words = command.map((word) => (/^[\w.:/\\-]+$/.test(word) ? word : psQuote(word)));
41
+ return words[0]?.startsWith("'") ? `& ${words.join(' ')}` : words.join(' ');
42
+ }
43
+ function psQuote(text) {
44
+ return `'${text.replace(/'/g, "''")}'`;
45
+ }
46
+ //# sourceMappingURL=windows-terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows-terminal.js","sourceRoot":"","sources":["../../src/host/windows-terminal.ts"],"names":[],"mappings":"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"}
@@ -1,22 +1,23 @@
1
1
  import { loadConfig } from "./config.js";
2
2
  import { providerCredentials } from "./credentials.js";
3
- import { adoOrganization, PROFILE_FILE, readProfile, trackerKey } from "./profile.js";
3
+ import { profileHere } from "./profile-layers.js";
4
+ import { adoOrganization, profileProblem, trackerKey } from "./profile.js";
4
5
  import { gitToplevel } from "./wait.js";
5
6
  // What a tracker verb run in-process (no daemon) needs: the project of the checkout it runs in, and
6
7
  // the machine's accounts. `conduct note` and `conduct brief` share it.
7
8
  // The profile of the checkout around `cwd`, when `key` (an item key, checked already) is one of its
8
9
  // items; else why not, after `why` (why it runs here), for the verb's refusal. `purpose` ends the
9
10
  // sentence: "post it from", "read it from".
10
- export async function checkoutProject(cwd, key, why, purpose) {
11
+ export async function checkoutProject(paths, cwd, key, why, purpose) {
11
12
  const checkout = await gitToplevel(cwd);
12
13
  if (checkout === null)
13
14
  return { refusal: `${why}, and this isn't a project checkout to ${purpose}` };
14
15
  let profile;
15
16
  try {
16
- profile = readProfile(checkout).profile;
17
+ profile = profileHere(checkout, paths);
17
18
  }
18
- catch {
19
- return { refusal: `${why}, and ${checkout} has no readable ${PROFILE_FILE}` };
19
+ catch (error) {
20
+ return { refusal: `${why}, and ${checkout} has no readable profile: ${profileProblem(error)}` };
20
21
  }
21
22
  if (key.split('-')[0] !== trackerKey(profile).toUpperCase())
22
23
  return { refusal: `${why}, and ${key} isn't an item of ${profile.name}, this checkout's project` };
@@ -1 +1 @@
1
- {"version":3,"file":"in-process.js","sourceRoot":"","sources":["../src/in-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAgB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,oGAAoG;AACpG,uEAAuE;AAEvE,oGAAoG;AACpG,kGAAkG;AAClG,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,GAAW,EACX,GAAW,EACX,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,0CAA0C,OAAO,EAAE,EAAE,CAAC;IACrG,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,QAAQ,oBAAoB,YAAY,EAAE,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE;QACzD,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,GAAG,qBAAqB,OAAO,CAAC,IAAI,2BAA2B,EAAE,CAAC;IACrG,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,8FAA8F;AAC9F,kGAAkG;AAClG,4BAA4B;AAC5B,MAAM,UAAU,gBAAgB,CAAC,KAAqB,EAAE,KAAmC;IACzF,OAAO,mBAAmB,CAAC;QACzB,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9E,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB,CAAC,KAAkC;IACnE,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QACtF,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"in-process.js","sourceRoot":"","sources":["../src/in-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAgB,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,oGAAoG;AACpG,uEAAuE;AAEvE,oGAAoG;AACpG,kGAAkG;AAClG,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAuC,EACvC,GAAW,EACX,GAAW,EACX,GAAW,EACX,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,0CAA0C,OAAO,EAAE,EAAE,CAAC;IACrG,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,QAAQ,6BAA6B,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE;QACzD,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,GAAG,qBAAqB,OAAO,CAAC,IAAI,2BAA2B,EAAE,CAAC;IACrG,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,8FAA8F;AAC9F,kGAAkG;AAClG,4BAA4B;AAC5B,MAAM,UAAU,gBAAgB,CAAC,KAAqB,EAAE,KAAmC;IACzF,OAAO,mBAAmB,CAAC;QACzB,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9E,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB,CAAC,KAAkC;IACnE,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QACtF,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC"}
package/build/inbox.js CHANGED
@@ -3,8 +3,10 @@ import { formatCondition, parseCondition } from "./conditions.js";
3
3
  import { loginText } from "./credentials.js";
4
4
  import { ConductorError } from "./errors.js";
5
5
  import { inboxLinkSchema, link, uniqueLinks } from "./links.js";
6
- import { handoffKindSchema, renamedTo } from "./protocol.js";
6
+ import { handoffKindSchema } from "./protocol.js";
7
+ import { RELEASE_GATES, releaseAsk, releaseGate } from "./release-kinds.js";
7
8
  import { resolveOpenWait } from "./resolve.js";
9
+ import { refusalProblems } from "./rules-service.js";
8
10
  import { formatTokens } from "./spend.js";
9
11
  import {} from "./store.js";
10
12
  import { describePending, pendingCallSchema } from "./transcript.js";
@@ -33,10 +35,10 @@ export const UNWATCHED_HANDOFFS = new Set([
33
35
  'scope',
34
36
  ]);
35
37
  // The phases that close a gate besides its own condition. A merge waits for the PR, so a Progress
36
- // note while it waits leaves it open.
38
+ // note while it waits leaves it open; a release's gate (npm-otp) closes once the worker goes on.
37
39
  const GATE_PHASES = {
40
+ ...Object.fromEntries(RELEASE_GATES.map((gate) => [gate, ['Resumed', 'Landed', 'Done']])),
38
41
  merge: ['Landed', 'Done'],
39
- 'npm-otp': ['Resumed', 'Landed', 'Done'],
40
42
  plan: ['Plan approved'],
41
43
  };
42
44
  // How a merge item resolves while its PR's checks are red; green checks raise it again.
@@ -75,10 +77,17 @@ const loginData = z.looseObject({
75
77
  command: z.string(),
76
78
  error: z.string().nullable().optional(),
77
79
  label: z.string(),
80
+ // The ADO organization a machine token's spell is kept to (ATK-536).
81
+ organization: z.string().optional(),
78
82
  provider: z.string(),
79
83
  });
80
84
  const ignoredData = z.looseObject({ author: z.string(), kind: z.enum(['approve', 'revise']) });
81
- const rulesData = z.looseObject({ file: z.string(), problems: z.array(z.string()).optional() });
85
+ // `upgrade`: the command a profile written for a newer conductor needs (ProfileTooNewError).
86
+ const rulesData = z.looseObject({
87
+ file: z.string(),
88
+ problems: z.array(z.string()).optional(),
89
+ upgrade: z.string().optional(),
90
+ });
82
91
  const waitData = z.looseObject({
83
92
  // A PR's settled checks, on a checks wait (watchers/index.ts).
84
93
  checks: z.string().optional(),
@@ -95,9 +104,15 @@ const checksData = z.looseObject({
95
104
  });
96
105
  const workerData = z.looseObject({
97
106
  error: z.string().optional(),
98
- // The worktree a retire kept, because its PR hadn't merged (worker.retired).
107
+ // The worktree a retire kept, because its PR hadn't merged (worker.retired), and the repository it
108
+ // belongs to: the project's clone, or for one made before clones the person's checkout.
99
109
  kept: z
100
- .looseObject({ branch: z.string().nullable(), reason: z.string(), worktree: z.string() })
110
+ .looseObject({
111
+ branch: z.string().nullable(),
112
+ reason: z.string(),
113
+ repo: z.string().nullable().optional(),
114
+ worktree: z.string(),
115
+ })
101
116
  .optional()
102
117
  .catch(undefined),
103
118
  kind: z.string().optional(),
@@ -134,10 +149,10 @@ export function inboxLines(items, now) {
134
149
  ...i.links.map((l) => ` ${l.label}: ${l.href}`),
135
150
  ]);
136
151
  }
137
- // The inbox (design note, section 7): everything that waits on Jason, raised from phase notes,
152
+ // The inbox (design note, section 7): everything that waits on the operator, raised from phase notes,
138
153
  // waits and session events, each with its exact commands and links. Items resolve themselves when
139
154
  // their condition is seen (the approval comment, the PR merged, the version on its tag, a later
140
- // note), or when Jason answers one (`conduct approve`, `revise`, `done`).
155
+ // note), or when the operator answers one (`conduct approve`, `revise`, `done`).
141
156
  export function inboxService({ onWaitResolved, store, tracker }) {
142
157
  // Items whose approve or revise comment is being posted.
143
158
  const answering = new Set();
@@ -261,6 +276,8 @@ export function inboxService({ onWaitResolved, store, tracker }) {
261
276
  // The worker waits on something now, so it isn't idle. A park that keeps failing still is.
262
277
  resolveAll(openOf(worker, ['stall']).filter((i) => !isParkStall(i)), `waiting on ${text}`);
263
278
  const condition = conditionOf(text);
279
+ // A wait on a release (an npm version on its dist-tag) holds its kind's gate (release-kinds.ts).
280
+ const asked = condition && event.subject !== null ? releaseAsk(condition, event.subject) : null;
264
281
  if (condition?.kind === 'approval') {
265
282
  gate(event.project, condition.item, 'plan', text, worker, () => ({
266
283
  commands: planCommands(condition.item),
@@ -268,15 +285,11 @@ export function inboxService({ onWaitResolved, store, tracker }) {
268
285
  text: planText(condition.item),
269
286
  }));
270
287
  }
271
- else if (condition?.kind === 'npm' && event.subject !== null) {
272
- gate(event.project, event.subject, 'npm-otp', text, worker, () => ({
273
- commands: [],
274
- links: [],
275
- text: `approve the npm stage of ${condition.package}@${condition.version} (${condition.tag}); the npm stage approve line is on ${event.subject}`,
276
- }));
288
+ else if (asked !== null) {
289
+ gate(event.project, asked.item, asked.gate, text, worker, () => ({ commands: [], links: [], text: asked.text }));
277
290
  }
278
291
  else if (condition?.kind === 'pr' && condition.on === 'merged') {
279
- // A merge is Jason's only when a note said so; the wait just names its PR.
292
+ // A merge is the operator's only when a note said so; the wait just names its PR.
280
293
  for (const open of openOn(event.project, event.subject, ['merge'])) {
281
294
  if (open.condition === null)
282
295
  store.setInboxCondition(open.id, text);
@@ -288,7 +301,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
288
301
  const gate = (project, item, kind, condition, worker, fresh) => {
289
302
  const open = openOn(project, item, [kind]);
290
303
  if (open.length === 0) {
291
- // Jason approved the plan before its worker waited on it: the comment is there, and the
304
+ // The operator approved the plan before its worker waited on it: the comment is there, and the
292
305
  // watcher resolves this wait on its next pass. A new Plan note raises a new item anyway.
293
306
  const latest = store.latestInbox(project, item, kind);
294
307
  if (kind === 'plan' && latest?.resolvedBy?.startsWith('approve (conduct approve'))
@@ -304,9 +317,10 @@ export function inboxService({ onWaitResolved, store, tracker }) {
304
317
  raise({ ...fresh(), condition, item, kind, project, worker });
305
318
  return;
306
319
  }
307
- // The newest npm wait is the version that waits: a stage that failed is replaced by the next.
320
+ // A release's newest wait is the version that waits: a stage that failed is replaced by the next.
321
+ const release = RELEASE_GATES.includes(kind);
308
322
  for (const o of open)
309
- if (o.condition === null || kind === 'npm-otp')
323
+ if (o.condition === null || release)
310
324
  store.setInboxCondition(o.id, condition);
311
325
  };
312
326
  // The item's open merge items for a PR, and its open checks failures on it.
@@ -328,11 +342,12 @@ export function inboxService({ onWaitResolved, store, tracker }) {
328
342
  return;
329
343
  const { checks, condition: text, resolution = text, worker } = data.data;
330
344
  const condition = conditionOf(text);
345
+ const release = condition ? releaseGate(condition.kind) : null;
331
346
  if (condition?.kind === 'approval') {
332
347
  resolveAll(openOn(event.project, condition.item, ['plan']), resolution);
333
348
  }
334
- else if (condition?.kind === 'npm') {
335
- resolveAll(openOn(event.project, event.subject, ['npm-otp']).filter((i) => i.condition === null || i.condition === text), resolution);
349
+ else if (release !== null) {
350
+ resolveAll(openOn(event.project, event.subject, [release]).filter((i) => i.condition === null || i.condition === text), resolution);
336
351
  }
337
352
  else if (condition?.kind === 'pr' && /^pr \S+ (merged|abandoned|closed unmerged)\b/.test(resolution)) {
338
353
  // Right after the PR, so a check named "… merged …" in a checks line isn't the PR merging.
@@ -351,7 +366,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
351
366
  }
352
367
  };
353
368
  // A PR's required checks failed (watchers/index.ts): a failure with each run's link, and the
354
- // PR's merge held, so nothing asks Jason to merge it while it is red. One failure per PR.
369
+ // PR's merge held, so nothing asks the operator to merge it while it is red. One failure per PR.
355
370
  const onChecksFailed = (event) => {
356
371
  const data = checksData.safeParse(event.data);
357
372
  if (!data.success || event.subject === null)
@@ -412,16 +427,16 @@ export function inboxService({ onWaitResolved, store, tracker }) {
412
427
  worker,
413
428
  });
414
429
  };
415
- // A lost provider login (logins.ts): one item per provider and account, however many projects
416
- // and calls find it, resolved when the login works again. Spells live in memory, so a restart
430
+ // A lost provider login (logins.ts): one item per provider and account (and organization, for a
431
+ // machine ADO token), however many projects and calls find it, resolved when the login works again. Spells live in memory, so a restart
417
432
  // resolves the items (start) and a login still lost raises its item again.
418
433
  const logins = new Map();
419
434
  const onLogin = (event) => {
420
435
  const data = loginData.safeParse(event.data);
421
436
  if (!data.success)
422
437
  return;
423
- const { account, command, error = null, label, provider } = data.data;
424
- const key = `${provider}:${account}`;
438
+ const { account, command, error = null, label, organization = null, provider } = data.data;
439
+ const key = organization === null ? `${provider}:${account}` : `${provider}:${account} ${organization}`;
425
440
  const open = logins.get(key);
426
441
  const item = open === undefined ? null : store.getInbox(open);
427
442
  if (event.kind === 'provider.login-restored') {
@@ -439,33 +454,37 @@ export function inboxService({ onWaitResolved, store, tracker }) {
439
454
  kind: 'login',
440
455
  links: [],
441
456
  project: event.project,
442
- text: `${loginText({ label, provider }, event.project)}${error ? `: ${error.slice(0, 120)}` : ''}`,
457
+ text: `${loginText({ label, provider }, event.project, organization)}${error ? `: ${error.slice(0, 120)}` : ''}`,
443
458
  worker: null,
444
459
  });
445
460
  logins.set(key, raised.id);
446
461
  };
447
- // A rules file the conductor refused waits on Jason until it loads again; the last good rules
462
+ // A rules file the conductor refused waits on the operator until it loads again; the last good rules
448
463
  // (a project's whole last good profile, when it has one) stay in force meanwhile. One item per
449
464
  // file, found by its link.
450
465
  const openRules = (file) => store.openInbox().filter((i) => isRulesItem(i) && i.links[0]?.href === file);
466
+ // A project's profile loading clears every refusal of it, whichever of its files that one named
467
+ // (its machine profile, its repo layer, or the checkout it was registered by before clones).
468
+ const openProfile = (project) => store.openInbox().filter((i) => isRulesItem(i) && i.project === project && i.links[0]?.label === 'profile');
451
469
  const onRules = (event) => {
452
470
  const data = rulesData.safeParse(event.data);
453
471
  if (!data.success)
454
472
  return;
455
- const { file, problems = [] } = data.data;
473
+ const { file, problems = [], upgrade } = data.data;
456
474
  if (event.kind === 'rules.loaded') {
457
- resolveAll(openRules(file), 'the rules loaded');
475
+ const loaded = [...openRules(file), ...(event.project === null ? [] : openProfile(event.project))];
476
+ resolveAll([...new Map(loaded.map((i) => [i.id, i])).values()], 'the rules loaded');
458
477
  return;
459
478
  }
460
479
  resolveAll(openRules(file), 'refused again');
461
480
  raise({
462
- commands: [RULES_COMMAND],
481
+ commands: [RULES_COMMAND, ...(upgrade === undefined ? [] : [upgrade])],
463
482
  condition: null,
464
483
  item: null,
465
484
  kind: 'failed',
466
485
  links: [link(file, event.project === null ? 'rules' : 'profile')],
467
486
  project: event.project,
468
- text: `${file} was refused, so ${event.project === null ? 'its last good rules stay in force' : 'the project stays on its last good profile, if it has one'}: ${problems.slice(1).join('; ') || (problems[0] ?? 'see conduct rules')}`,
487
+ text: `${file} was refused, so ${event.project === null ? 'its last good rules stay in force' : 'the project stays on its last good profile, if it has one, and launches nothing until it loads'}: ${refusalProblems(problems).join('; ') || 'see conduct rules'}`,
469
488
  worker: null,
470
489
  });
471
490
  };
@@ -485,7 +504,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
485
504
  const earlierFailures = () => openOn(event.project, event.subject, ['failed']).filter((i) => i.worker !== null && i.worker !== worker && store.getWorker(i.worker)?.name === name);
486
505
  switch (event.kind) {
487
506
  case 'worker.compact-failed':
488
- // The manager tries a worker once a run (ATK-497): a session it couldn't compact is Jason's
507
+ // The manager tries a worker once a run (ATK-497): a session it couldn't compact is the operator's
489
508
  // to compact or retire. A rule's own `inbox context` item is another matter.
490
509
  if (compactFailures(worker).length > 0)
491
510
  return;
@@ -553,7 +572,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
553
572
  // behind on Windows.
554
573
  const { kept } = data.data;
555
574
  if (kept) {
556
- const root = store.listProjects().find((p) => p.name === event.project)?.root;
575
+ const root = kept.repo ?? store.listProjects().find((p) => p.name === event.project)?.clone;
557
576
  raise({
558
577
  commands: root
559
578
  ? [
@@ -630,7 +649,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
630
649
  answering.add(id);
631
650
  let comment;
632
651
  try {
633
- // As Jason: the comment carries the key owner's name, and the worker reads it on the tracker.
652
+ // As the operator: the comment carries the key owner's name, and the worker reads it on the tracker.
634
653
  comment = await tracker.comment(key, body, profile);
635
654
  }
636
655
  finally {
@@ -647,8 +666,8 @@ export function inboxService({ onWaitResolved, store, tracker }) {
647
666
  const profile = profileOf(project);
648
667
  let comment;
649
668
  try {
650
- // As Jason, as `conduct approve` posts it: the worker's approval wait reads it, and the
651
- // work skill records it as the conductor's, not his.
669
+ // As the operator, as `conduct approve` posts it: the worker's approval wait reads it, and the
670
+ // work skill records it as the conductor's, not theirs.
652
671
  comment = await tracker.comment(key, `approve: auto-approved by the conductor: rule "${rule}", at once`, profile);
653
672
  }
654
673
  catch (error) {
@@ -749,10 +768,9 @@ function mergeIsFor(item, pr) {
749
768
  return named.size === 0 || named.has(Number(/#(\d+)$/.exec(pr)?.[1]));
750
769
  }
751
770
  // The item a note's Handoff kind raises; undefined for `conductor` and for a kind protocol 2 doesn't
752
- // have. A reader stays lenient: a protocol 1 note a worker posted itself (`jason`) raises what its
753
- // kind is called now; `conduct note` refuses a new one.
771
+ // have.
754
772
  function noteKindOf(kind) {
755
- const parsed = handoffKindSchema.safeParse(renamedTo(kind) ?? kind);
773
+ const parsed = handoffKindSchema.safeParse(kind);
756
774
  return parsed.success && parsed.data !== 'conductor' ? NOTE_KINDS[parsed.data] : undefined;
757
775
  }
758
776
  function planCommands(item) {