@detergent-software/conductor 3.0.0-dev.4 → 3.0.0-dev.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +352 -0
- package/README.md +274 -124
- package/build/brief-cli.js +5 -12
- package/build/brief-cli.js.map +1 -1
- package/build/brief.js +4 -3
- package/build/brief.js.map +1 -1
- package/build/broker-cli.js +15 -10
- package/build/broker-cli.js.map +1 -1
- package/build/broker.js +1 -1
- package/build/broker.js.map +1 -1
- package/build/claude.js +48 -2
- package/build/claude.js.map +1 -1
- package/build/cli.js +225 -71
- package/build/cli.js.map +1 -1
- package/build/clones.js +92 -0
- package/build/clones.js.map +1 -0
- package/build/code-host-cli.js +172 -40
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host-service.js +35 -2
- package/build/code-host-service.js.map +1 -1
- package/build/code-host.js +140 -16
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +33 -16
- package/build/conditions.js.map +1 -1
- package/build/config.js +58 -28
- package/build/config.js.map +1 -1
- package/build/console.js +43 -27
- package/build/console.js.map +1 -1
- package/build/credentials.js +86 -54
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +57 -66
- package/build/daemon.js.map +1 -1
- package/build/file-lock.js +155 -40
- package/build/file-lock.js.map +1 -1
- package/build/follow-ups.js +2 -2
- package/build/follow-ups.js.map +1 -1
- package/build/help.js +237 -82
- package/build/help.js.map +1 -1
- package/build/history.js +10 -4
- package/build/history.js.map +1 -1
- package/build/host/credential-manager.js +109 -0
- package/build/host/credential-manager.js.map +1 -0
- package/build/host/keychain.js +44 -0
- package/build/host/keychain.js.map +1 -0
- package/build/host/launchd.js +118 -0
- package/build/host/launchd.js.map +1 -0
- package/build/host/libsecret.js +40 -0
- package/build/host/libsecret.js.map +1 -0
- package/build/host/macos-terminal.js +63 -0
- package/build/host/macos-terminal.js.map +1 -0
- package/build/host/platform.js +4 -0
- package/build/host/platform.js.map +1 -0
- package/build/host/secrets.js +46 -0
- package/build/host/secrets.js.map +1 -0
- package/build/host/shell.js +26 -0
- package/build/host/shell.js.map +1 -0
- package/build/host/startup.js +26 -0
- package/build/host/startup.js.map +1 -0
- package/build/host/systemd.js +91 -0
- package/build/host/systemd.js.map +1 -0
- package/build/host/task-scheduler.js +78 -0
- package/build/host/task-scheduler.js.map +1 -0
- package/build/host/terminal.js +46 -0
- package/build/host/terminal.js.map +1 -0
- package/build/host/tmux.js +39 -0
- package/build/host/tmux.js.map +1 -0
- package/build/host/windows-terminal.js +46 -0
- package/build/host/windows-terminal.js.map +1 -0
- package/build/in-process.js +6 -5
- package/build/in-process.js.map +1 -1
- package/build/inbox.js +57 -39
- package/build/inbox.js.map +1 -1
- package/build/init.js +509 -0
- package/build/init.js.map +1 -0
- package/build/landing.js +107 -10
- package/build/landing.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +5 -1
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +138 -26
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/ado/trigger.js +5 -0
- package/build/lib/providers/ado/trigger.js.map +1 -1
- package/build/lib/providers/ado/writes.js +17 -2
- package/build/lib/providers/ado/writes.js.map +1 -1
- package/build/lib/providers/github/actions.js +90 -3
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +2 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/github/writes.js +25 -2
- package/build/lib/providers/github/writes.js.map +1 -1
- package/build/lib/providers/index.js +4 -3
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +28 -5
- package/build/lib/providers/npm/stage.js.map +1 -1
- package/build/lib/providers/timing.js +10 -0
- package/build/lib/providers/timing.js.map +1 -0
- package/build/lib/providers/verdicts/pr.js +47 -6
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/lib/providers/verdicts/runs.js +12 -2
- package/build/lib/providers/verdicts/runs.js.map +1 -1
- package/build/logins.js +13 -6
- package/build/logins.js.map +1 -1
- package/build/manager.js +131 -55
- package/build/manager.js.map +1 -1
- package/build/note.js +5 -15
- package/build/note.js.map +1 -1
- package/build/notes.js +130 -23
- package/build/notes.js.map +1 -1
- package/build/notify.js +104 -42
- package/build/notify.js.map +1 -1
- package/build/npm-reads.js +323 -0
- package/build/npm-reads.js.map +1 -0
- package/build/ownership.js +2 -2
- package/build/paths.js +44 -1
- package/build/paths.js.map +1 -1
- package/build/plugin.js +4 -2
- package/build/plugin.js.map +1 -1
- package/build/profile-fields.js +153 -0
- package/build/profile-fields.js.map +1 -0
- package/build/profile-layers.js +264 -0
- package/build/profile-layers.js.map +1 -0
- package/build/profile.js +107 -51
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +19 -19
- package/build/project-check-cli.js.map +1 -1
- package/build/project-check.js +194 -108
- package/build/project-check.js.map +1 -1
- package/build/protocol.js +5 -21
- package/build/protocol.js.map +1 -1
- package/build/pushover.js +5 -5
- package/build/pushover.js.map +1 -1
- package/build/queue.js +4 -4
- package/build/queue.js.map +1 -1
- package/build/registry.js +195 -36
- package/build/registry.js.map +1 -1
- package/build/release-kinds.js +34 -0
- package/build/release-kinds.js.map +1 -0
- package/build/rule-actions.js +8 -6
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +104 -47
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +23 -23
- package/build/rules.js.map +1 -1
- package/build/run.js +20 -7
- package/build/run.js.map +1 -1
- package/build/runs.js +218 -68
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +89 -13
- package/build/scheduler.js.map +1 -1
- package/build/search.js +2 -1
- package/build/search.js.map +1 -1
- package/build/server.js +51 -14
- package/build/server.js.map +1 -1
- package/build/spend.js +1 -1
- package/build/spend.js.map +1 -1
- package/build/store.js +79 -20
- package/build/store.js.map +1 -1
- package/build/supervise.js +1 -1
- package/build/supervise.js.map +1 -1
- package/build/timeline.js +104 -0
- package/build/timeline.js.map +1 -0
- package/build/tracker-reads-cli.js +4 -5
- package/build/tracker-reads-cli.js.map +1 -1
- package/build/trackers.js +56 -10
- package/build/trackers.js.map +1 -1
- package/build/train-cli.js +6 -5
- package/build/train-cli.js.map +1 -1
- package/build/train.js +2 -2
- package/build/train.js.map +1 -1
- package/build/upgrade.js +8 -9
- package/build/upgrade.js.map +1 -1
- package/build/wait.js +10 -10
- package/build/wait.js.map +1 -1
- package/build/watchers/ado.js +44 -20
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +21 -6
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +2 -2
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/linear-queue.js +47 -14
- package/build/watchers/linear-queue.js.map +1 -1
- package/build/watchers/linear-writes.js +26 -12
- package/build/watchers/linear-writes.js.map +1 -1
- package/build/watchers/linear.js +2 -2
- package/build/watchers/linear.js.map +1 -1
- package/build/watchers/npm.js +6 -4
- package/build/watchers/npm.js.map +1 -1
- package/build/watchers/source.js +5 -1
- package/build/watchers/source.js.map +1 -1
- package/build/webhook.js +80 -0
- package/build/webhook.js.map +1 -0
- package/build/why.js +1 -1
- package/build/why.js.map +1 -1
- package/build/worktrees.js +56 -93
- package/build/worktrees.js.map +1 -1
- package/package.json +61 -61
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/model.ts +46 -6
- package/plugin/hooks/register.tsx +41 -2
- package/plugin/skills/conductor-console/SKILL.md +26 -27
- package/plugin/skills/work-item/SKILL.md +58 -54
- package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
- package/plugin/skills/work-item/trackers/github-issues.md +1 -1
- package/plugin/skills/work-item/trackers/linear.md +2 -2
- package/plugin/types/index.d.ts +1 -1
- package/protocol/PROTOCOL.md +3 -9
- package/build/checkout-sync.js +0 -249
- package/build/checkout-sync.js.map +0 -1
- package/build/credential.js +0 -66
- package/build/credential.js.map +0 -1
- package/build/startup.js +0 -92
- package/build/startup.js.map +0 -1
- package/build/tab.js +0 -32
- package/build/tab.js.map +0 -1
|
@@ -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":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,mGAAmG;AACnG,iGAAiG;AACjG,MAAM,UAAU,eAAe,CAAC,GAAQ;IACtC,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI;QACtB,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAClG,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACnC,OAAO,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACzD,CAAC,CAAC,4CAA4C;gBAC9C,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CAAC,GAAe;IACjD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,0CAA0C,KAAK,EAAE,CAAC;IAClF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5F,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI;QACtB,CAAC,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACjG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;IAChB,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC;AAC/G,CAAC;AAED,iGAAiG;AACjG,SAAS,MAAM,CAAC,OAA0B;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AACzC,CAAC"}
|
package/build/in-process.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { loadConfig } from "./config.js";
|
|
2
2
|
import { providerCredentials } from "./credentials.js";
|
|
3
|
-
import {
|
|
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 =
|
|
17
|
+
profile = profileHere(checkout, paths);
|
|
17
18
|
}
|
|
18
|
-
catch {
|
|
19
|
-
return { refusal: `${why}, and ${checkout} has no readable ${
|
|
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` };
|
package/build/in-process.js.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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 (
|
|
272
|
-
gate(event.project,
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 ||
|
|
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 (
|
|
335
|
-
resolveAll(openOn(event.project, event.subject, [
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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)?.
|
|
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
|
|
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
|
|
651
|
-
// work skill records it as the conductor's, not
|
|
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.
|
|
753
|
-
// kind is called now; `conduct note` refuses a new one.
|
|
771
|
+
// have.
|
|
754
772
|
function noteKindOf(kind) {
|
|
755
|
-
const parsed = handoffKindSchema.safeParse(
|
|
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) {
|