@detergent-software/conductor 3.0.0-dev.9 → 3.1.0-dev.1

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 (212) hide show
  1. package/CHANGELOG.md +486 -0
  2. package/README.md +272 -126
  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 +41 -14
  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 +226 -64
  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 +242 -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/attach-clients.js +56 -0
  40. package/build/host/attach-clients.js.map +1 -0
  41. package/build/host/credential-manager.js +109 -0
  42. package/build/host/credential-manager.js.map +1 -0
  43. package/build/host/keychain.js +44 -0
  44. package/build/host/keychain.js.map +1 -0
  45. package/build/host/launchd.js +118 -0
  46. package/build/host/launchd.js.map +1 -0
  47. package/build/host/libsecret.js +40 -0
  48. package/build/host/libsecret.js.map +1 -0
  49. package/build/host/macos-terminal.js +63 -0
  50. package/build/host/macos-terminal.js.map +1 -0
  51. package/build/host/platform.js +4 -0
  52. package/build/host/platform.js.map +1 -0
  53. package/build/host/secrets.js +46 -0
  54. package/build/host/secrets.js.map +1 -0
  55. package/build/host/shell.js +26 -0
  56. package/build/host/shell.js.map +1 -0
  57. package/build/host/startup.js +26 -0
  58. package/build/host/startup.js.map +1 -0
  59. package/build/host/systemd.js +91 -0
  60. package/build/host/systemd.js.map +1 -0
  61. package/build/host/task-scheduler.js +78 -0
  62. package/build/host/task-scheduler.js.map +1 -0
  63. package/build/host/terminal.js +47 -0
  64. package/build/host/terminal.js.map +1 -0
  65. package/build/host/tmux.js +39 -0
  66. package/build/host/tmux.js.map +1 -0
  67. package/build/host/windows-terminal.js +46 -0
  68. package/build/host/windows-terminal.js.map +1 -0
  69. package/build/in-process.js +5 -4
  70. package/build/in-process.js.map +1 -1
  71. package/build/inbox.js +47 -35
  72. package/build/inbox.js.map +1 -1
  73. package/build/init.js +509 -0
  74. package/build/init.js.map +1 -0
  75. package/build/landing.js +107 -10
  76. package/build/landing.js.map +1 -1
  77. package/build/lib/providers/ado/code-host.js +5 -1
  78. package/build/lib/providers/ado/code-host.js.map +1 -1
  79. package/build/lib/providers/ado/pipelines.js +138 -26
  80. package/build/lib/providers/ado/pipelines.js.map +1 -1
  81. package/build/lib/providers/ado/trigger.js +5 -0
  82. package/build/lib/providers/ado/trigger.js.map +1 -1
  83. package/build/lib/providers/ado/writes.js +17 -2
  84. package/build/lib/providers/ado/writes.js.map +1 -1
  85. package/build/lib/providers/github/actions.js +90 -3
  86. package/build/lib/providers/github/actions.js.map +1 -1
  87. package/build/lib/providers/github/code-host.js +2 -0
  88. package/build/lib/providers/github/code-host.js.map +1 -1
  89. package/build/lib/providers/github/writes.js +25 -2
  90. package/build/lib/providers/github/writes.js.map +1 -1
  91. package/build/lib/providers/index.js +3 -2
  92. package/build/lib/providers/index.js.map +1 -1
  93. package/build/lib/providers/npm/stage.js +14 -1
  94. package/build/lib/providers/npm/stage.js.map +1 -1
  95. package/build/lib/providers/timing.js +10 -0
  96. package/build/lib/providers/timing.js.map +1 -0
  97. package/build/lib/providers/verdicts/pr.js +47 -6
  98. package/build/lib/providers/verdicts/pr.js.map +1 -1
  99. package/build/lib/providers/verdicts/runs.js +12 -2
  100. package/build/lib/providers/verdicts/runs.js.map +1 -1
  101. package/build/logins.js +13 -6
  102. package/build/logins.js.map +1 -1
  103. package/build/manager.js +200 -55
  104. package/build/manager.js.map +1 -1
  105. package/build/note.js +5 -15
  106. package/build/note.js.map +1 -1
  107. package/build/notes.js +130 -23
  108. package/build/notes.js.map +1 -1
  109. package/build/notify.js +104 -42
  110. package/build/notify.js.map +1 -1
  111. package/build/npm-reads.js +151 -22
  112. package/build/npm-reads.js.map +1 -1
  113. package/build/ownership.js +2 -2
  114. package/build/paths.js +44 -1
  115. package/build/paths.js.map +1 -1
  116. package/build/plugin.js +4 -2
  117. package/build/plugin.js.map +1 -1
  118. package/build/profile-fields.js +153 -0
  119. package/build/profile-fields.js.map +1 -0
  120. package/build/profile-layers.js +264 -0
  121. package/build/profile-layers.js.map +1 -0
  122. package/build/profile.js +69 -49
  123. package/build/profile.js.map +1 -1
  124. package/build/project-check-cli.js +17 -19
  125. package/build/project-check-cli.js.map +1 -1
  126. package/build/project-check.js +166 -104
  127. package/build/project-check.js.map +1 -1
  128. package/build/protocol.js +5 -21
  129. package/build/protocol.js.map +1 -1
  130. package/build/pushover.js +5 -5
  131. package/build/pushover.js.map +1 -1
  132. package/build/queue.js +4 -4
  133. package/build/queue.js.map +1 -1
  134. package/build/registry.js +195 -36
  135. package/build/registry.js.map +1 -1
  136. package/build/release-kinds.js +34 -0
  137. package/build/release-kinds.js.map +1 -0
  138. package/build/rule-actions.js +8 -6
  139. package/build/rule-actions.js.map +1 -1
  140. package/build/rules-service.js +83 -44
  141. package/build/rules-service.js.map +1 -1
  142. package/build/rules.js +23 -23
  143. package/build/rules.js.map +1 -1
  144. package/build/run.js +20 -7
  145. package/build/run.js.map +1 -1
  146. package/build/runs.js +218 -68
  147. package/build/runs.js.map +1 -1
  148. package/build/scheduler.js +78 -12
  149. package/build/scheduler.js.map +1 -1
  150. package/build/search.js +2 -1
  151. package/build/search.js.map +1 -1
  152. package/build/server.js +51 -14
  153. package/build/server.js.map +1 -1
  154. package/build/spend.js +1 -1
  155. package/build/spend.js.map +1 -1
  156. package/build/store.js +79 -20
  157. package/build/store.js.map +1 -1
  158. package/build/supervise.js +1 -1
  159. package/build/supervise.js.map +1 -1
  160. package/build/timeline.js +104 -0
  161. package/build/timeline.js.map +1 -0
  162. package/build/tracker-reads-cli.js +4 -3
  163. package/build/tracker-reads-cli.js.map +1 -1
  164. package/build/trackers.js +56 -10
  165. package/build/trackers.js.map +1 -1
  166. package/build/train-cli.js +6 -5
  167. package/build/train-cli.js.map +1 -1
  168. package/build/train.js +2 -2
  169. package/build/train.js.map +1 -1
  170. package/build/upgrade.js +8 -9
  171. package/build/upgrade.js.map +1 -1
  172. package/build/wait.js +10 -10
  173. package/build/wait.js.map +1 -1
  174. package/build/watchers/ado.js +42 -19
  175. package/build/watchers/ado.js.map +1 -1
  176. package/build/watchers/github.js +18 -4
  177. package/build/watchers/github.js.map +1 -1
  178. package/build/watchers/index.js +2 -2
  179. package/build/watchers/index.js.map +1 -1
  180. package/build/watchers/linear-queue.js +47 -14
  181. package/build/watchers/linear-queue.js.map +1 -1
  182. package/build/watchers/linear-writes.js +26 -12
  183. package/build/watchers/linear-writes.js.map +1 -1
  184. package/build/watchers/linear.js +2 -2
  185. package/build/watchers/linear.js.map +1 -1
  186. package/build/watchers/source.js +5 -1
  187. package/build/watchers/source.js.map +1 -1
  188. package/build/webhook.js +80 -0
  189. package/build/webhook.js.map +1 -0
  190. package/build/why.js +1 -1
  191. package/build/why.js.map +1 -1
  192. package/build/worktrees.js +56 -93
  193. package/build/worktrees.js.map +1 -1
  194. package/package.json +61 -61
  195. package/plugin/.claude-plugin/plugin.json +1 -1
  196. package/plugin/hooks/model.ts +55 -6
  197. package/plugin/hooks/register.tsx +43 -4
  198. package/plugin/skills/conductor-console/SKILL.md +26 -27
  199. package/plugin/skills/work-item/SKILL.md +58 -54
  200. package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
  201. package/plugin/skills/work-item/trackers/github-issues.md +1 -1
  202. package/plugin/skills/work-item/trackers/linear.md +2 -2
  203. package/plugin/types/index.d.ts +1 -1
  204. package/protocol/PROTOCOL.md +3 -9
  205. package/build/checkout-sync.js +0 -249
  206. package/build/checkout-sync.js.map +0 -1
  207. package/build/credential.js +0 -66
  208. package/build/credential.js.map +0 -1
  209. package/build/startup.js +0 -92
  210. package/build/startup.js.map +0 -1
  211. package/build/tab.js +0 -32
  212. package/build/tab.js.map +0 -1
@@ -0,0 +1,91 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { ConductorError } from "../errors.js";
4
+ import { outputOf } from "../run.js";
5
+ export const SYSTEMD_UNIT = 'conductor.service';
6
+ // A systemd user unit that runs `conduct supervise` when the user's service manager starts: at
7
+ // login, or at boot with lingering on. supervise restarts the daemon and conduct stop must stick,
8
+ // so no Restart=; and KillMode=process, or the unit ending (a stop, an upgrade) would kill its whole
9
+ // cgroup, the workers and tmux the daemon started included. Its output goes to the journal
10
+ // (journalctl --user -u conductor).
11
+ export function systemd({ env, home, run, user, }) {
12
+ // systemd reads user units from $XDG_CONFIG_HOME/systemd/user, ~/.config without it.
13
+ const unit = join(env.XDG_CONFIG_HOME || join(home, '.config'), 'systemd', 'user', SYSTEMD_UNIT);
14
+ const systemctl = async (what, args) => {
15
+ const result = await run('systemctl', ['--user', ...args], { timeoutMs: 60_000 });
16
+ if (result.code !== 0)
17
+ throw systemctlFailed(what, result);
18
+ };
19
+ const text = () => (existsSync(unit) ? readFileSync(unit, 'utf8') : undefined);
20
+ return {
21
+ install: async (spec) => {
22
+ mkdirSync(dirname(unit), { recursive: true });
23
+ writeFileSync(unit, systemdUnit(spec));
24
+ await systemctl('could not load the systemd unit', ['daemon-reload']);
25
+ await systemctl('could not enable the systemd unit', ['enable', SYSTEMD_UNIT]);
26
+ // A running one keeps the command it started with until it restarts.
27
+ await systemctl('could not start the systemd unit', ['restart', SYSTEMD_UNIT]);
28
+ const linger = await run('loginctl', ['show-user', user, '--property=Linger', '--value'], { timeoutMs: 30_000 });
29
+ return [
30
+ 'systemd started it now',
31
+ ...(linger.stdout.trim() === 'yes'
32
+ ? []
33
+ : [
34
+ `it starts at your login; to start it at boot on a machine nobody logs in to, run \`loginctl enable-linger ${user || '$USER'}\``,
35
+ ]),
36
+ ];
37
+ },
38
+ name: `the systemd user unit ${SYSTEMD_UNIT}`,
39
+ // Disabled and gone, but not stopped: a daemon it started keeps running, as on Windows.
40
+ remove: async () => {
41
+ if (!existsSync(unit))
42
+ return false;
43
+ await systemctl('could not disable the systemd unit', ['disable', SYSTEMD_UNIT]);
44
+ rmSync(unit);
45
+ await run('systemctl', ['--user', 'daemon-reload'], { timeoutMs: 60_000 });
46
+ return true;
47
+ },
48
+ run: () => systemctl('could not start the systemd unit', ['start', SYSTEMD_UNIT]),
49
+ runsCli: async (cliPath) => execStart(text())?.includes(` ${unitWord(cliPath)} `) ?? false,
50
+ supervises: async () => / supervise$/.test(execStart(text()) ?? ''),
51
+ };
52
+ }
53
+ // The unit file.
54
+ export function systemdUnit({ cliPath, nodePath, path }) {
55
+ return [
56
+ '[Unit]',
57
+ 'Description=The conductor daemon (conduct supervise)',
58
+ '',
59
+ '[Service]',
60
+ `ExecStart=${[nodePath, cliPath].map((word) => unitWord(word)).join(' ')} supervise`,
61
+ `Environment=${unitWord(`PATH=${path}`, false)}`,
62
+ 'KillMode=process',
63
+ '',
64
+ '[Install]',
65
+ 'WantedBy=default.target',
66
+ '',
67
+ ].join('\n');
68
+ }
69
+ function execStart(unit) {
70
+ return /^ExecStart=(.*)$/m.exec(unit ?? '')?.[1];
71
+ }
72
+ function systemctlFailed(what, result) {
73
+ if (/ENOENT/.test(result.error ?? '')) {
74
+ return new ConductorError(`${what}: no systemctl`, ['start `conduct supervise` from your own login items']);
75
+ }
76
+ return new ConductorError(what, [
77
+ outputOf(result, 1000),
78
+ "systemctl --user needs your login session's service manager: run it in a session you logged in to, not after su",
79
+ ]);
80
+ }
81
+ // A word in quotes, as ExecStart= and Environment= read it: `%` starts a specifier, and in
82
+ // ExecStart= `$` an environment variable.
83
+ function unitWord(text, command = true) {
84
+ const escaped = text
85
+ .replaceAll('\\', '\\\\')
86
+ .replaceAll('"', '\\"')
87
+ .replaceAll('%', '%%')
88
+ .replaceAll('$', () => (command ? '$$' : '$'));
89
+ return `"${escaped}"`;
90
+ }
91
+ //# sourceMappingURL=systemd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/host/systemd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { ConductorError } from "../errors.js";
2
+ import { outputOf } from "../run.js";
3
+ export const TASK_NAME = 'Conductor';
4
+ // The task's <Arguments> quote the cli.js path (install); XML escapes it, and Windows paths compare
5
+ // without case.
6
+ export function taskRunsCli(xml, cliPath) {
7
+ const args = taskArguments(xml);
8
+ return args !== undefined && args.toLowerCase().includes(`"${cliPath.toLowerCase()}"`);
9
+ }
10
+ // A per-user Task Scheduler task that runs `conduct supervise` at logon, which keeps `conduct serve`
11
+ // running (supervise.ts). No elevation is needed for a task that runs as the current user.
12
+ // conhost --headless keeps a console window (or a Windows Terminal tab) from opening, and the zero
13
+ // time limit stops Task Scheduler from ending the daemon after its default 72 hours. A second start
14
+ // while one runs exits on the pid lock, and its supervisor with it. Installing doesn't start it.
15
+ export function taskScheduler(run) {
16
+ const schtasks = (args) => run('schtasks', args, { timeoutMs: 60_000 });
17
+ const taskXml = async () => {
18
+ const result = await schtasks(['/Query', '/TN', TASK_NAME, '/XML']);
19
+ return result.code === 0 ? result.stdout : undefined;
20
+ };
21
+ const installed = async () => (await schtasks(['/Query', '/TN', TASK_NAME])).code === 0;
22
+ return {
23
+ install: async ({ cliPath, nodePath }) => {
24
+ const script = [
25
+ "$ErrorActionPreference = 'Stop'",
26
+ '$user = "$env:USERDOMAIN\\$env:USERNAME"',
27
+ '$action = New-ScheduledTaskAction -Execute "$env:SystemRoot\\System32\\conhost.exe" -Argument $env:CONDUCTOR_TASK_ARGS',
28
+ '$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user',
29
+ '$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit ([TimeSpan]::Zero)',
30
+ '$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited',
31
+ `Register-ScheduledTask -TaskName '${TASK_NAME}' -Description 'Starts the conductor daemon (conduct supervise) at logon.' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null`,
32
+ ].join('\n');
33
+ const result = await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
34
+ env: { ...process.env, CONDUCTOR_TASK_ARGS: `--headless "${nodePath}" "${cliPath}" supervise` },
35
+ timeoutMs: 60_000,
36
+ });
37
+ if (result.code !== 0)
38
+ throw new ConductorError('could not register the logon task', [outputOf(result, 1000)]);
39
+ return [];
40
+ },
41
+ name: `the "${TASK_NAME}" logon task`,
42
+ remove: async () => {
43
+ if (!(await installed()))
44
+ return false;
45
+ const result = await schtasks(['/Delete', '/TN', TASK_NAME, '/F']);
46
+ if (result.code !== 0)
47
+ throw new ConductorError('could not remove the logon task', [outputOf(result, 1000)]);
48
+ return true;
49
+ },
50
+ run: async () => {
51
+ const result = await schtasks(['/Run', '/TN', TASK_NAME]);
52
+ if (result.code !== 0)
53
+ throw new ConductorError(`could not run the "${TASK_NAME}" logon task`, [outputOf(result, 1000)]);
54
+ },
55
+ runsCli: async (cliPath) => {
56
+ const xml = await taskXml();
57
+ return xml !== undefined && taskRunsCli(xml, cliPath);
58
+ },
59
+ supervises: async () => {
60
+ const xml = await taskXml();
61
+ return xml !== undefined && taskSupervises(xml);
62
+ },
63
+ };
64
+ }
65
+ export function taskSupervises(xml) {
66
+ return /\ssupervise\s*$/.test(taskArguments(xml) ?? '');
67
+ }
68
+ // The task action's <Arguments>, XML unescaped.
69
+ function taskArguments(xml) {
70
+ return /<Arguments>([^<]*)<\/Arguments>/
71
+ .exec(xml)?.[1]
72
+ ?.replaceAll('&quot;', '"')
73
+ .replaceAll('&apos;', "'")
74
+ .replaceAll('&lt;', '<')
75
+ .replaceAll('&gt;', '>')
76
+ .replaceAll('&amp;', '&');
77
+ }
78
+ //# sourceMappingURL=task-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-scheduler.js","sourceRoot":"","sources":["../../src/host/task-scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
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, clients) {
33
+ return {
34
+ ...(clients ? { clients } : {}),
35
+ hint: (window) => where.attachHint(window),
36
+ onLaunch,
37
+ open: (tab) => where.open({
38
+ command: ['claude', 'attach', tab.handle],
39
+ cwd: tab.cwd,
40
+ env: {},
41
+ hold: true,
42
+ title: tab.title,
43
+ window: tab.window,
44
+ }),
45
+ };
46
+ }
47
+ //# 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;AAKjC,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;AA+C5F,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,EAAE,OAAuB;IACpF,OAAO;QACL,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,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, profileProblem, 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
19
  catch (error) {
19
- return { refusal: `${why}, and ${checkout} has no readable ${PROFILE_FILE}: ${profileProblem(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,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACpH,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,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,QAAQ,oBAAoB,YAAY,KAAK,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAC1G,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,7 +3,8 @@ 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";
8
9
  import { refusalProblems } from "./rules-service.js";
9
10
  import { formatTokens } from "./spend.js";
@@ -34,10 +35,10 @@ export const UNWATCHED_HANDOFFS = new Set([
34
35
  'scope',
35
36
  ]);
36
37
  // The phases that close a gate besides its own condition. A merge waits for the PR, so a Progress
37
- // 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.
38
39
  const GATE_PHASES = {
40
+ ...Object.fromEntries(RELEASE_GATES.map((gate) => [gate, ['Resumed', 'Landed', 'Done']])),
39
41
  merge: ['Landed', 'Done'],
40
- 'npm-otp': ['Resumed', 'Landed', 'Done'],
41
42
  plan: ['Plan approved'],
42
43
  };
43
44
  // How a merge item resolves while its PR's checks are red; green checks raise it again.
@@ -76,6 +77,8 @@ const loginData = z.looseObject({
76
77
  command: z.string(),
77
78
  error: z.string().nullable().optional(),
78
79
  label: z.string(),
80
+ // The ADO organization a machine token's spell is kept to (ATK-536).
81
+ organization: z.string().optional(),
79
82
  provider: z.string(),
80
83
  });
81
84
  const ignoredData = z.looseObject({ author: z.string(), kind: z.enum(['approve', 'revise']) });
@@ -101,9 +104,15 @@ const checksData = z.looseObject({
101
104
  });
102
105
  const workerData = z.looseObject({
103
106
  error: z.string().optional(),
104
- // 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.
105
109
  kept: z
106
- .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
+ })
107
116
  .optional()
108
117
  .catch(undefined),
109
118
  kind: z.string().optional(),
@@ -140,10 +149,10 @@ export function inboxLines(items, now) {
140
149
  ...i.links.map((l) => ` ${l.label}: ${l.href}`),
141
150
  ]);
142
151
  }
143
- // 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,
144
153
  // waits and session events, each with its exact commands and links. Items resolve themselves when
145
154
  // their condition is seen (the approval comment, the PR merged, the version on its tag, a later
146
- // note), or when Jason answers one (`conduct approve`, `revise`, `done`).
155
+ // note), or when the operator answers one (`conduct approve`, `revise`, `done`).
147
156
  export function inboxService({ onWaitResolved, store, tracker }) {
148
157
  // Items whose approve or revise comment is being posted.
149
158
  const answering = new Set();
@@ -267,6 +276,8 @@ export function inboxService({ onWaitResolved, store, tracker }) {
267
276
  // The worker waits on something now, so it isn't idle. A park that keeps failing still is.
268
277
  resolveAll(openOf(worker, ['stall']).filter((i) => !isParkStall(i)), `waiting on ${text}`);
269
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;
270
281
  if (condition?.kind === 'approval') {
271
282
  gate(event.project, condition.item, 'plan', text, worker, () => ({
272
283
  commands: planCommands(condition.item),
@@ -274,15 +285,11 @@ export function inboxService({ onWaitResolved, store, tracker }) {
274
285
  text: planText(condition.item),
275
286
  }));
276
287
  }
277
- else if (condition?.kind === 'npm' && event.subject !== null) {
278
- gate(event.project, event.subject, 'npm-otp', text, worker, () => ({
279
- commands: [],
280
- links: [],
281
- text: `approve the npm stage of ${condition.package}@${condition.version} (${condition.tag}); the npm stage approve line is on ${event.subject}`,
282
- }));
288
+ else if (asked !== null) {
289
+ gate(event.project, asked.item, asked.gate, text, worker, () => ({ commands: [], links: [], text: asked.text }));
283
290
  }
284
291
  else if (condition?.kind === 'pr' && condition.on === 'merged') {
285
- // 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.
286
293
  for (const open of openOn(event.project, event.subject, ['merge'])) {
287
294
  if (open.condition === null)
288
295
  store.setInboxCondition(open.id, text);
@@ -294,7 +301,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
294
301
  const gate = (project, item, kind, condition, worker, fresh) => {
295
302
  const open = openOn(project, item, [kind]);
296
303
  if (open.length === 0) {
297
- // 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
298
305
  // watcher resolves this wait on its next pass. A new Plan note raises a new item anyway.
299
306
  const latest = store.latestInbox(project, item, kind);
300
307
  if (kind === 'plan' && latest?.resolvedBy?.startsWith('approve (conduct approve'))
@@ -310,9 +317,10 @@ export function inboxService({ onWaitResolved, store, tracker }) {
310
317
  raise({ ...fresh(), condition, item, kind, project, worker });
311
318
  return;
312
319
  }
313
- // 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);
314
322
  for (const o of open)
315
- if (o.condition === null || kind === 'npm-otp')
323
+ if (o.condition === null || release)
316
324
  store.setInboxCondition(o.id, condition);
317
325
  };
318
326
  // The item's open merge items for a PR, and its open checks failures on it.
@@ -334,11 +342,12 @@ export function inboxService({ onWaitResolved, store, tracker }) {
334
342
  return;
335
343
  const { checks, condition: text, resolution = text, worker } = data.data;
336
344
  const condition = conditionOf(text);
345
+ const release = condition ? releaseGate(condition.kind) : null;
337
346
  if (condition?.kind === 'approval') {
338
347
  resolveAll(openOn(event.project, condition.item, ['plan']), resolution);
339
348
  }
340
- else if (condition?.kind === 'npm') {
341
- 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);
342
351
  }
343
352
  else if (condition?.kind === 'pr' && /^pr \S+ (merged|abandoned|closed unmerged)\b/.test(resolution)) {
344
353
  // Right after the PR, so a check named "… merged …" in a checks line isn't the PR merging.
@@ -357,7 +366,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
357
366
  }
358
367
  };
359
368
  // A PR's required checks failed (watchers/index.ts): a failure with each run's link, and the
360
- // 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.
361
370
  const onChecksFailed = (event) => {
362
371
  const data = checksData.safeParse(event.data);
363
372
  if (!data.success || event.subject === null)
@@ -418,16 +427,16 @@ export function inboxService({ onWaitResolved, store, tracker }) {
418
427
  worker,
419
428
  });
420
429
  };
421
- // A lost provider login (logins.ts): one item per provider and account, however many projects
422
- // 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
423
432
  // resolves the items (start) and a login still lost raises its item again.
424
433
  const logins = new Map();
425
434
  const onLogin = (event) => {
426
435
  const data = loginData.safeParse(event.data);
427
436
  if (!data.success)
428
437
  return;
429
- const { account, command, error = null, label, provider } = data.data;
430
- 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}`;
431
440
  const open = logins.get(key);
432
441
  const item = open === undefined ? null : store.getInbox(open);
433
442
  if (event.kind === 'provider.login-restored') {
@@ -445,22 +454,26 @@ export function inboxService({ onWaitResolved, store, tracker }) {
445
454
  kind: 'login',
446
455
  links: [],
447
456
  project: event.project,
448
- text: `${loginText({ label, provider }, event.project)}${error ? `: ${error.slice(0, 120)}` : ''}`,
457
+ text: `${loginText({ label, provider }, event.project, organization)}${error ? `: ${error.slice(0, 120)}` : ''}`,
449
458
  worker: null,
450
459
  });
451
460
  logins.set(key, raised.id);
452
461
  };
453
- // 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
454
463
  // (a project's whole last good profile, when it has one) stay in force meanwhile. One item per
455
464
  // file, found by its link.
456
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');
457
469
  const onRules = (event) => {
458
470
  const data = rulesData.safeParse(event.data);
459
471
  if (!data.success)
460
472
  return;
461
473
  const { file, problems = [], upgrade } = data.data;
462
474
  if (event.kind === 'rules.loaded') {
463
- 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');
464
477
  return;
465
478
  }
466
479
  resolveAll(openRules(file), 'refused again');
@@ -491,7 +504,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
491
504
  const earlierFailures = () => openOn(event.project, event.subject, ['failed']).filter((i) => i.worker !== null && i.worker !== worker && store.getWorker(i.worker)?.name === name);
492
505
  switch (event.kind) {
493
506
  case 'worker.compact-failed':
494
- // 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
495
508
  // to compact or retire. A rule's own `inbox context` item is another matter.
496
509
  if (compactFailures(worker).length > 0)
497
510
  return;
@@ -559,7 +572,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
559
572
  // behind on Windows.
560
573
  const { kept } = data.data;
561
574
  if (kept) {
562
- 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;
563
576
  raise({
564
577
  commands: root
565
578
  ? [
@@ -636,7 +649,7 @@ export function inboxService({ onWaitResolved, store, tracker }) {
636
649
  answering.add(id);
637
650
  let comment;
638
651
  try {
639
- // 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.
640
653
  comment = await tracker.comment(key, body, profile);
641
654
  }
642
655
  finally {
@@ -653,8 +666,8 @@ export function inboxService({ onWaitResolved, store, tracker }) {
653
666
  const profile = profileOf(project);
654
667
  let comment;
655
668
  try {
656
- // As Jason, as `conduct approve` posts it: the worker's approval wait reads it, and the
657
- // 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.
658
671
  comment = await tracker.comment(key, `approve: auto-approved by the conductor: rule "${rule}", at once`, profile);
659
672
  }
660
673
  catch (error) {
@@ -755,10 +768,9 @@ function mergeIsFor(item, pr) {
755
768
  return named.size === 0 || named.has(Number(/#(\d+)$/.exec(pr)?.[1]));
756
769
  }
757
770
  // The item a note's Handoff kind raises; undefined for `conductor` and for a kind protocol 2 doesn't
758
- // have. A reader stays lenient: a protocol 1 note a worker posted itself (`jason`) raises what its
759
- // kind is called now; `conduct note` refuses a new one.
771
+ // have.
760
772
  function noteKindOf(kind) {
761
- const parsed = handoffKindSchema.safeParse(renamedTo(kind) ?? kind);
773
+ const parsed = handoffKindSchema.safeParse(kind);
762
774
  return parsed.success && parsed.data !== 'conductor' ? NOTE_KINDS[parsed.data] : undefined;
763
775
  }
764
776
  function planCommands(item) {