@detergent-software/conductor 1.0.0-dev.8 → 1.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 (106) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +277 -24
  3. package/build/brief-cli.js +68 -0
  4. package/build/brief-cli.js.map +1 -0
  5. package/build/brief.js +211 -0
  6. package/build/brief.js.map +1 -0
  7. package/build/broker-cli.js +126 -0
  8. package/build/broker-cli.js.map +1 -0
  9. package/build/broker.js +154 -0
  10. package/build/broker.js.map +1 -0
  11. package/build/cli.js +438 -132
  12. package/build/cli.js.map +1 -1
  13. package/build/clock.js +17 -0
  14. package/build/clock.js.map +1 -0
  15. package/build/conditions.js +20 -14
  16. package/build/conditions.js.map +1 -1
  17. package/build/config.js +26 -3
  18. package/build/config.js.map +1 -1
  19. package/build/console.js +176 -0
  20. package/build/console.js.map +1 -0
  21. package/build/daemon.js +136 -5
  22. package/build/daemon.js.map +1 -1
  23. package/build/events.js +2 -1
  24. package/build/events.js.map +1 -1
  25. package/build/help.js +447 -0
  26. package/build/help.js.map +1 -0
  27. package/build/history.js +637 -0
  28. package/build/history.js.map +1 -0
  29. package/build/inbox.js +117 -18
  30. package/build/inbox.js.map +1 -1
  31. package/build/manager.js +118 -43
  32. package/build/manager.js.map +1 -1
  33. package/build/notes.js +27 -43
  34. package/build/notes.js.map +1 -1
  35. package/build/notify.js +35 -14
  36. package/build/notify.js.map +1 -1
  37. package/build/overrides.js +83 -0
  38. package/build/overrides.js.map +1 -0
  39. package/build/paths.js +10 -1
  40. package/build/paths.js.map +1 -1
  41. package/build/profile.js +68 -2
  42. package/build/profile.js.map +1 -1
  43. package/build/queue.js +183 -0
  44. package/build/queue.js.map +1 -0
  45. package/build/registry.js +17 -10
  46. package/build/registry.js.map +1 -1
  47. package/build/rule-actions.js +444 -0
  48. package/build/rule-actions.js.map +1 -0
  49. package/build/rule-input.js +17 -0
  50. package/build/rule-input.js.map +1 -0
  51. package/build/rules-service.js +402 -0
  52. package/build/rules-service.js.map +1 -0
  53. package/build/rules.js +850 -0
  54. package/build/rules.js.map +1 -0
  55. package/build/scheduler.js +293 -0
  56. package/build/scheduler.js.map +1 -0
  57. package/build/serve.js +51 -0
  58. package/build/serve.js.map +1 -0
  59. package/build/server.js +230 -7
  60. package/build/server.js.map +1 -1
  61. package/build/spend.js +42 -0
  62. package/build/spend.js.map +1 -1
  63. package/build/store.js +266 -12
  64. package/build/store.js.map +1 -1
  65. package/build/trackers.js +15 -0
  66. package/build/trackers.js.map +1 -0
  67. package/build/upgrade.js +16 -14
  68. package/build/upgrade.js.map +1 -1
  69. package/build/wait.js +3 -9
  70. package/build/wait.js.map +1 -1
  71. package/build/watchers/ado-api.js +55 -0
  72. package/build/watchers/ado-api.js.map +1 -0
  73. package/build/watchers/ado.js +60 -52
  74. package/build/watchers/ado.js.map +1 -1
  75. package/build/watchers/boards-brief.js +63 -0
  76. package/build/watchers/boards-brief.js.map +1 -0
  77. package/build/watchers/boards-queue.js +120 -0
  78. package/build/watchers/boards-queue.js.map +1 -0
  79. package/build/watchers/boards-writes.js +83 -0
  80. package/build/watchers/boards-writes.js.map +1 -0
  81. package/build/watchers/boards.js +271 -0
  82. package/build/watchers/boards.js.map +1 -0
  83. package/build/watchers/conflicts.js +132 -0
  84. package/build/watchers/conflicts.js.map +1 -0
  85. package/build/watchers/github.js +5 -2
  86. package/build/watchers/github.js.map +1 -1
  87. package/build/watchers/index.js +21 -7
  88. package/build/watchers/index.js.map +1 -1
  89. package/build/watchers/linear-api.js +55 -0
  90. package/build/watchers/linear-api.js.map +1 -0
  91. package/build/watchers/linear-labels.js +43 -0
  92. package/build/watchers/linear-labels.js.map +1 -0
  93. package/build/watchers/linear-queue.js +96 -0
  94. package/build/watchers/linear-queue.js.map +1 -0
  95. package/build/watchers/linear.js +18 -16
  96. package/build/watchers/linear.js.map +1 -1
  97. package/build/watchers/source.js +3 -3
  98. package/build/watchers/source.js.map +1 -1
  99. package/build/why.js +78 -0
  100. package/build/why.js.map +1 -0
  101. package/mod/.claude-plugin/plugin.json +1 -1
  102. package/mod/README.md +2 -2
  103. package/mod/hooks/model.ts +135 -8
  104. package/mod/hooks/register.tsx +219 -19
  105. package/mod/types/index.d.ts +6 -0
  106. package/package.json +1 -1
package/build/brief.js ADDED
@@ -0,0 +1,211 @@
1
+ import { z } from 'zod';
2
+ import { ConductorError } from "./errors.js";
3
+ import { ITEM, projectFor } from "./notes.js";
4
+ import { notePrefixes } from "./profile.js";
5
+ import { linearApi, LinearRequestError } from "./watchers/linear-api.js";
6
+ import { approvalAfterPlan, noteHeading } from "./watchers/linear.js";
7
+ // `conduct brief` (design note, section 13): one item's context for a worker's orient step, read
8
+ // by the daemon from the tracker with its own key or az login, never through an MCP. The queue's
9
+ // snapshot holds no description, relations or comments, so the brief reads the item itself.
10
+ const TTL_MS = 30_000;
11
+ // A brief the tracker couldn't give: `missing` when it has no such item, `unreadable` otherwise.
12
+ export class BriefError extends ConductorError {
13
+ reason;
14
+ constructor(message, reason, details = []) {
15
+ super(message, details);
16
+ this.name = 'BriefError';
17
+ this.reason = reason;
18
+ }
19
+ }
20
+ // The brief as a reader sees it, for `conduct brief` without --json.
21
+ export function briefLines(brief) {
22
+ const links = (label, list) => list.length === 0 ? [] : [`${label}:`, ...list.map((l) => ` ${l.id} ${l.status ?? '?'} ${l.title ?? ''}`.trimEnd())];
23
+ const approval = brief.plan === null ? 'no Plan note yet'
24
+ : brief.approval === null ? `none after the Plan of ${brief.plan}`
25
+ : `${brief.approval.kind} ${brief.approval.at}: "${brief.approval.text}"`;
26
+ return [
27
+ `${brief.item} ${brief.title}`,
28
+ ...(brief.url ? [brief.url] : []),
29
+ `status: ${brief.status.name}${brief.status.type && brief.status.type !== brief.status.name.toLowerCase() ? ` (${brief.status.type})` : ''}`,
30
+ `project: ${brief.project}${brief.trackerProject ? ` · ${brief.trackerProject}` : ''}`,
31
+ `area: ${brief.area ?? 'none'}`,
32
+ `labels: ${brief.labels.length > 0 ? brief.labels.join(', ') : 'none'}`,
33
+ ...links('blocked by', brief.blockedBy),
34
+ ...links('blocks', brief.blocks),
35
+ ...links('related', brief.related),
36
+ ...(brief.notes.length === 0
37
+ ? ['notes: none']
38
+ : ['notes:', ...brief.notes.map((n) => ` ${n.at} ${n.phase}${n.kind ? ` · ${n.kind}${n.line ? `: ${n.line}` : ''}` : ''}`)]),
39
+ `approval: ${approval}`,
40
+ '',
41
+ brief.description.trim() || '(no description)',
42
+ ];
43
+ }
44
+ // The brief from what the tracker gave: the comments as phase notes, the newest Plan, and the
45
+ // approval after it.
46
+ export function briefOf(item, project, read, prefixes, at) {
47
+ const { comments, viewer, ...fields } = read;
48
+ const sorted = [...comments].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
49
+ const notes = sorted.flatMap((c) => {
50
+ const heading = noteHeading(c.body, prefixes);
51
+ return heading ? [{ at: c.createdAt, ...heading }] : [];
52
+ });
53
+ const found = approvalAfterPlan(sorted, viewer, prefixes);
54
+ // Notes carry the same author as the answer, so only a comment that isn't a note can be it.
55
+ const said = found && sorted.find((c) => c.createdAt === found.at && c.authorId === viewer && noteHeading(c.body, prefixes) === null);
56
+ return {
57
+ ...fields,
58
+ approval: found ? { ...found, text: said ? said.body.trim() : '' } : null,
59
+ item,
60
+ notes,
61
+ plan: notes.findLast((n) => n.phase === 'Plan')?.at ?? null,
62
+ project,
63
+ readAt: at.toISOString(),
64
+ };
65
+ }
66
+ export function briefService({ now = () => new Date(), reader, store, ttlMs = TTL_MS, }) {
67
+ const cache = new Map();
68
+ // Bumped by each event about an item, so a read that began before one isn't kept.
69
+ const changes = new Map();
70
+ // Whatever the daemon sees happen to an item (a note posted, an approve or revise it posted, a
71
+ // wait resolved) makes its brief stale at once.
72
+ store.onEvent((event) => {
73
+ if (!event.subject)
74
+ return;
75
+ const key = event.subject.toUpperCase();
76
+ cache.delete(key);
77
+ changes.set(key, (changes.get(key) ?? 0) + 1);
78
+ });
79
+ return {
80
+ read: async (item, { fresh = false } = {}) => {
81
+ if (!ITEM.test(item))
82
+ throw new ConductorError(`"${item}" is not a tracker item, such as ATK-311`);
83
+ const key = item.toUpperCase();
84
+ const at = now().getTime();
85
+ for (const [k, entry] of cache)
86
+ if (at - entry.at >= ttlMs)
87
+ cache.delete(k);
88
+ const hit = cache.get(key);
89
+ if (!fresh && hit)
90
+ return hit.brief;
91
+ let project;
92
+ try {
93
+ project = projectFor(store, key);
94
+ }
95
+ catch (error) {
96
+ // Its details are for a note (--worker), which a brief doesn't take.
97
+ throw error instanceof ConductorError ? new ConductorError(error.message) : error;
98
+ }
99
+ const seen = changes.get(key) ?? 0;
100
+ let read;
101
+ try {
102
+ read = await reader.read(key, project.profile);
103
+ }
104
+ catch (error) {
105
+ if (error instanceof BriefError)
106
+ throw error;
107
+ const details = error instanceof ConductorError ? error.details : [];
108
+ throw new BriefError(`couldn't read ${key}: ${error instanceof Error ? error.message : String(error)}`, 'unreadable', details);
109
+ }
110
+ const brief = briefOf(key, project.name, read, notePrefixes(project.profile), now());
111
+ if ((changes.get(key) ?? 0) === seen)
112
+ cache.set(key, { at: now().getTime(), brief });
113
+ return brief;
114
+ },
115
+ };
116
+ }
117
+ const STATE = 'state { name type }';
118
+ const LINKED = `identifier title ${STATE}`;
119
+ const COMMENTS = 'comments(first: 100, after: $after) { pageInfo { hasNextPage endCursor } nodes { body createdAt user { id } } }';
120
+ export const BRIEF_QUERY = `query Brief($id: String!, $after: String) {
121
+ viewer { id }
122
+ issue(id: $id) {
123
+ identifier title description url ${STATE}
124
+ project { name }
125
+ labels(first: 50) { nodes { name parent { name } } }
126
+ relations(first: 50) { nodes { type relatedIssue { ${LINKED} } } }
127
+ inverseRelations(first: 50) { nodes { type issue { ${LINKED} } } }
128
+ ${COMMENTS}
129
+ }
130
+ }`;
131
+ export const BRIEF_COMMENTS_QUERY = `query BriefComments($id: String!, $after: String) {
132
+ issue(id: $id) { ${COMMENTS} }
133
+ }`;
134
+ const state = z.object({ name: z.string(), type: z.string() });
135
+ const linked = z.object({ identifier: z.string(), state, title: z.string().nullable() });
136
+ const commentsPage = z.object({
137
+ nodes: z.array(z.object({ body: z.string(), createdAt: z.string(), user: z.object({ id: z.string() }).nullable() })),
138
+ pageInfo: z.object({ endCursor: z.string().nullable(), hasNextPage: z.boolean() }),
139
+ });
140
+ const briefAnswer = z.object({
141
+ issue: z
142
+ .object({
143
+ comments: commentsPage,
144
+ description: z.string().nullable(),
145
+ identifier: z.string(),
146
+ inverseRelations: z.object({ nodes: z.array(z.object({ issue: linked.nullable(), type: z.string() })) }),
147
+ labels: z.object({ nodes: z.array(z.object({ name: z.string(), parent: z.object({ name: z.string() }).nullable() })) }),
148
+ project: z.object({ name: z.string() }).nullable(),
149
+ relations: z.object({ nodes: z.array(z.object({ relatedIssue: linked.nullable(), type: z.string() })) }),
150
+ state,
151
+ title: z.string(),
152
+ url: z.string().nullable(),
153
+ })
154
+ .nullable(),
155
+ viewer: z.object({ id: z.string() }),
156
+ });
157
+ const commentsAnswer = z.object({ issue: z.object({ comments: commentsPage }).nullable() });
158
+ // Linear: the issue, its labels (the Area is the one under the profile's label group), relations
159
+ // both ways and every comment, in one request (more only for comments past the first 100).
160
+ export function linearBriefs(options = {}) {
161
+ const request = linearApi(options);
162
+ const ask = async (query, variables) => {
163
+ try {
164
+ return await request(query, variables);
165
+ }
166
+ catch (error) {
167
+ // Linear answers an identifier it doesn't have with an error, not a null issue.
168
+ if (error instanceof LinearRequestError && error.kind === 'refused' && /not found/i.test(error.message)) {
169
+ throw new BriefError(`Linear has no issue ${String(variables.id)}`, 'missing');
170
+ }
171
+ throw error;
172
+ }
173
+ };
174
+ return {
175
+ read: async (item, profile) => {
176
+ if (profile.tracker.kind !== 'linear')
177
+ throw new ConductorError(`project ${profile.name}'s tracker isn't Linear`);
178
+ const { issue, viewer } = briefAnswer.parse(await ask(BRIEF_QUERY, { after: null, id: item }));
179
+ if (!issue)
180
+ throw new BriefError(`Linear has no issue ${item}`, 'missing');
181
+ const comments = [...issue.comments.nodes];
182
+ for (let page = issue.comments.pageInfo; page.hasNextPage && page.endCursor !== null;) {
183
+ const more = commentsAnswer.parse(await ask(BRIEF_COMMENTS_QUERY, { after: page.endCursor, id: item })).issue?.comments;
184
+ if (!more)
185
+ break;
186
+ comments.push(...more.nodes);
187
+ page = more.pageInfo;
188
+ }
189
+ const link = (l) => ({ id: l.identifier, status: l.state.name, title: l.title });
190
+ // "A blocks B" is stored on A: B sees it as an inverse relation. `related` shows on either side.
191
+ const outward = issue.relations.nodes.flatMap((r) => (r.relatedIssue ? [{ issue: r.relatedIssue, type: r.type }] : []));
192
+ const inward = issue.inverseRelations.nodes.flatMap((r) => (r.issue ? [{ issue: r.issue, type: r.type }] : []));
193
+ const { areaLabelGroup } = profile.tracker;
194
+ return {
195
+ area: issue.labels.nodes.find((l) => l.parent?.name === areaLabelGroup)?.name ?? null,
196
+ blockedBy: inward.filter((r) => r.type === 'blocks').map((r) => link(r.issue)),
197
+ blocks: outward.filter((r) => r.type === 'blocks').map((r) => link(r.issue)),
198
+ comments: comments.map((c) => ({ authorId: c.user?.id ?? null, body: c.body, createdAt: c.createdAt })),
199
+ description: issue.description ?? '',
200
+ labels: issue.labels.nodes.map((l) => l.name),
201
+ related: [...outward, ...inward].filter((r) => r.type === 'related').map((r) => link(r.issue)),
202
+ status: { name: issue.state.name, type: issue.state.type },
203
+ title: issue.title,
204
+ trackerProject: issue.project?.name ?? null,
205
+ url: issue.url,
206
+ viewer: viewer.id,
207
+ };
208
+ },
209
+ };
210
+ }
211
+ //# sourceMappingURL=brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../src/brief.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { resolve } from 'node:path';
2
+ import { z } from 'zod';
3
+ import { lockVerdict } from "./broker.js";
4
+ import { callApi, NotRunningError } from "./client.js";
5
+ import { ConductorError } from "./errors.js";
6
+ import { usageError } from "./help.js";
7
+ import { lockSchema, portAssignmentSchema } from "./store.js";
8
+ import { daemonWorker, gitToplevel } from "./wait.js";
9
+ // Exit code when no daemon brokered the request: none runs, an older one has no broker, or it
10
+ // refused (a checkout in no registered project, no free slot). The caller keeps its own scheme,
11
+ // as ATK's scripts/dev-ports.mjs and the work-issue helper's file lock do.
12
+ export const NOT_BROKERED = 3;
13
+ // How often `conduct lock acquire --wait` asks again, as the work-issue helper's lock does.
14
+ const LOCK_POLL_MS = 15_000;
15
+ const errorBody = z.object({ details: z.array(z.string()).optional(), error: z.string() });
16
+ const lockAnswer = z.looseObject({ acquired: z.boolean().optional(), lock: z.string() });
17
+ // The broker's locks for `conduct status`, or null from a daemon older than the broker.
18
+ export async function heldLocks(paths, now = new Date()) {
19
+ const answer = await callApi(paths, { method: 'GET', path: '/locks' });
20
+ if (answer.status === 404)
21
+ return null;
22
+ if (answer.status !== 200) {
23
+ const refusal = errorBody.safeParse(answer.body).data;
24
+ throw new ConductorError(refusal?.error ?? `the API answered ${answer.status} for the locks`, refusal?.details ?? []);
25
+ }
26
+ return z
27
+ .object({ locks: z.array(lockSchema) })
28
+ .parse(answer.body)
29
+ .locks.map((lock) => ({ ...lock, verdict: lockVerdict(lock, null, now) === 'stale' ? 'stale' : 'held' }));
30
+ }
31
+ // `conduct lock <acquire|release|status> <name>`: the broker's named lock, such as chrome, which
32
+ // verify skills of every project take turns with. Prints the work-issue helper's JSON. The owner
33
+ // is --owner, else the item of the worker whose worktree this is.
34
+ export async function lockCommand(paths, action, name, options, io) {
35
+ if (action !== 'acquire' && action !== 'release' && action !== 'status')
36
+ throw usageError('lock', `a lock is acquired, released or its status read, not "${action}"`);
37
+ if (!/^[\w-]+$/.test(name))
38
+ throw new ConductorError(`a lock name is letters, digits, _ and -, not "${name}"`);
39
+ let owner = options.owner?.trim() || null;
40
+ if (owner === null) {
41
+ const worker = await daemonWorker(paths, await gitToplevel(io.cwd), undefined);
42
+ if (worker === 'no-daemon')
43
+ return notBrokered(io, 'the conductor is not running');
44
+ if (worker === 'unreachable')
45
+ throw new ConductorError("the conductor didn't answer");
46
+ owner = worker?.item ?? null;
47
+ if (owner === null && action !== 'status')
48
+ throw new ConductorError('no worker of the conductor works in this checkout: pass --owner <id>');
49
+ }
50
+ const until = Date.now() + (options.waitMinutes ?? 0) * 60_000;
51
+ const sleep = io.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
52
+ for (;;) {
53
+ const answer = await ask(paths, { body: { action, ...(owner === null ? {} : { owner }) }, method: 'POST', path: `/locks/${name}` });
54
+ if (!answer.ok)
55
+ return notBrokered(io, answer.why);
56
+ const body = lockAnswer.parse(answer.body);
57
+ if (action !== 'acquire' || body.acquired !== false || Date.now() >= until) {
58
+ io.out(JSON.stringify(body, null, 2));
59
+ return 0;
60
+ }
61
+ await sleep(LOCK_POLL_MS);
62
+ }
63
+ }
64
+ // One line per lock: name, holder, and its last acquire.
65
+ export function lockLine(lock) {
66
+ return ` ${lock.name} ${lock.holder} since ${new Date(lock.since).toLocaleString()}${lock.verdict === 'stale' ? ' (stale: free to take over)' : ''}`;
67
+ }
68
+ // `conduct ports [<path>]`: the checkout's dev ports from the broker, or every assignment.
69
+ export async function portsCommand(paths, path, options, io) {
70
+ if (path === undefined) {
71
+ const answer = await ask(paths, { method: 'GET', path: '/ports' });
72
+ if (!answer.ok)
73
+ return notBrokered(io, answer.why);
74
+ const assignments = z.object({ assignments: z.array(portAssignmentSchema) }).parse(answer.body).assignments;
75
+ if (options.json)
76
+ io.out(JSON.stringify(assignments, null, 2));
77
+ else if (assignments.length === 0)
78
+ io.out('no worktree has ports from the conductor');
79
+ else
80
+ assignments.forEach((a) => io.out(portsLine(a)));
81
+ return 0;
82
+ }
83
+ const answer = await ask(paths, {
84
+ body: { worktree: resolve(io.cwd, path), ...(options.prefer === undefined ? {} : { prefer: options.prefer }) },
85
+ method: 'POST',
86
+ path: '/ports',
87
+ });
88
+ if (!answer.ok)
89
+ return notBrokered(io, answer.why);
90
+ const { assignment } = z.object({ assignment: portAssignmentSchema }).parse(answer.body);
91
+ io.out(options.json ? JSON.stringify(assignment, null, 2) : portsLine(assignment));
92
+ return 0;
93
+ }
94
+ // One line per checkout: project, slot, each port, then where.
95
+ export function portsLine(a) {
96
+ const ports = Object.entries(a.ports)
97
+ .map(([name, port]) => `${name} ${port}`)
98
+ .join(' · ');
99
+ return `${a.project} slot ${a.slot} ${ports} ${a.worktree}`;
100
+ }
101
+ // The body of a 200 answer, or why nothing was brokered.
102
+ async function ask(paths, request) {
103
+ let answer;
104
+ try {
105
+ answer = await callApi(paths, request);
106
+ }
107
+ catch (error) {
108
+ if (error instanceof NotRunningError)
109
+ return { ok: false, why: 'the conductor is not running' };
110
+ throw error;
111
+ }
112
+ if (answer.status === 200)
113
+ return { body: answer.body, ok: true };
114
+ // A daemon from before the broker has no such route.
115
+ if (answer.status === 404)
116
+ return { ok: false, why: 'this conductor has no port broker (upgrade it)' };
117
+ const refusal = errorBody.safeParse(answer.body).data;
118
+ if (answer.status === 409 && refusal)
119
+ return { ok: false, why: [refusal.error, ...(refusal.details ?? [])].join('; ') };
120
+ throw new ConductorError(refusal?.error ?? `the API answered ${answer.status}`, refusal?.details ?? []);
121
+ }
122
+ function notBrokered(io, why) {
123
+ io.err(`not brokered: ${why}`);
124
+ return NOT_BROKERED;
125
+ }
126
+ //# sourceMappingURL=broker-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-cli.js","sourceRoot":"","sources":["../src/broker-cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { connect } from 'node:net';
3
+ import { ConductorError } from "./errors.js";
4
+ import { samePath } from "./paths.js";
5
+ import { basePorts } from "./profile.js";
6
+ import { checkoutOf } from "./registry.js";
7
+ import {} from "./store.js";
8
+ // The broker shares what every project on the machine uses (design note, sections 3 and 12): dev
9
+ // server ports, so two projects' worktrees never serve on the same port, and named locks such as
10
+ // chrome, so verify skills of different projects take turns with the one browser.
11
+ // A holder keeps a lock by acquiring it again; one it hasn't acquired for this long is free to take
12
+ // over. The same rule as the work-issue helper's file lock.
13
+ export const LOCK_STALE_MS = 30 * 60_000;
14
+ const MAX_PORT = 65_535;
15
+ export function broker({ exists = existsSync, listening = portListening, now = () => new Date(), store }) {
16
+ // One assignment at a time: each waits on port probes, and two at once could pick the same slot.
17
+ let assigning = Promise.resolve();
18
+ // Frees the ports of worktrees whose directory is gone, such as one /worktree-gc removed.
19
+ const sweep = () => {
20
+ for (const held of store.portAssignments())
21
+ if (!exists(held.worktree))
22
+ freePorts(store, held.worktree, 'worktree gone');
23
+ return store.portAssignments();
24
+ };
25
+ const assign = async (path, wanted) => {
26
+ const { checkout, root } = checkoutOf(path);
27
+ const projects = store.listProjects();
28
+ const project = projects.find((p) => samePath(p.root, root));
29
+ if (!project)
30
+ throw new ConductorError(`${checkout} is in no registered project`, ['`conduct project add <path>` registers one']);
31
+ const profile = profileOf(project);
32
+ const base = basePorts(profile);
33
+ if (samePath(checkout, root))
34
+ return { assignedAt: null, ports: base, project: project.name, slot: 0, worktree: checkout };
35
+ const portsOf = (slot) => Object.fromEntries(Object.entries(base).map(([name, port]) => [name, port + slot]));
36
+ let prefer = wanted;
37
+ const held = sweep().find((a) => samePath(a.worktree, checkout));
38
+ if (held) {
39
+ // Kept while the profile still gives its slot those ports; otherwise it moves, its old slot first.
40
+ if (held.slot <= profile.ports.maxSlot && samePorts(held.ports, portsOf(held.slot)))
41
+ return held;
42
+ freePorts(store, held.worktree, 'profile changed');
43
+ prefer ??= held.slot;
44
+ }
45
+ // Every main checkout's ports, and every worktree's.
46
+ const taken = new Set([
47
+ ...projects.flatMap((p) => (p.profile ? Object.values(basePorts(p.profile)) : [])),
48
+ ...store.portAssignments().flatMap((a) => Object.values(a.ports)),
49
+ ]);
50
+ const slots = Array.from({ length: profile.ports.maxSlot }, (_, i) => i + 1);
51
+ const order = prefer !== undefined && slots.includes(prefer) ? [prefer, ...slots.filter((s) => s !== prefer)] : slots;
52
+ for (const slot of order) {
53
+ const ports = portsOf(slot);
54
+ const values = Object.values(ports);
55
+ if (values.some((port) => port > MAX_PORT || taken.has(port)))
56
+ continue;
57
+ // The preferred slot is the one the checkout already serves on (its own scheme gave it), so
58
+ // its own dev servers may be what listens there.
59
+ if (slot !== prefer && (await Promise.all(values.map(listening))).some(Boolean))
60
+ continue;
61
+ const assignment = store.savePorts({ ports, project: project.name, slot, worktree: checkout });
62
+ store.appendEvent({ data: { ports, slot, worktree: checkout }, kind: 'ports.assigned', project: project.name });
63
+ return assignment;
64
+ }
65
+ throw new ConductorError(`no free slot between 1 and ${profile.ports.maxSlot} for ${checkout}`, [
66
+ '`conduct ports` lists the assignments; a removed worktree frees its slot',
67
+ ]);
68
+ };
69
+ return {
70
+ assignPorts: (path, prefer) => {
71
+ const next = assigning.then(() => assign(path, prefer));
72
+ assigning = next.catch(() => { });
73
+ return next;
74
+ },
75
+ lock: (name, action, owner) => {
76
+ const current = store.getLock(name);
77
+ const verdict = lockVerdict(current, owner, now());
78
+ if (action === 'status')
79
+ return { lock: name, verdict, ...(current ? { heldBy: current.holder, since: current.since } : {}) };
80
+ if (owner === null)
81
+ throw new ConductorError(`an owner is needed to ${action} a lock`);
82
+ if (action === 'release') {
83
+ if (verdict === 'mine') {
84
+ store.deleteLock(name);
85
+ store.appendEvent({ data: { holder: owner }, kind: 'lock.released', subject: name });
86
+ }
87
+ return { lock: name, released: verdict === 'mine', ...(current && verdict !== 'mine' ? { heldBy: current.holder } : {}) };
88
+ }
89
+ if (current && verdict === 'held')
90
+ return { acquired: false, heldBy: current.holder, lock: name, since: current.since };
91
+ store.saveLock(name, owner);
92
+ if (verdict === 'stale' && current) {
93
+ store.appendEvent({ data: { holder: owner, previous: current.holder, since: current.since }, kind: 'lock.taken-over', subject: name });
94
+ }
95
+ else if (verdict === 'free') {
96
+ store.appendEvent({ data: { holder: owner }, kind: 'lock.acquired', subject: name });
97
+ }
98
+ return { acquired: true, lock: name, owner, ...(verdict === 'mine' ? { refreshed: true } : {}) };
99
+ },
100
+ locks: () => store.listLocks(),
101
+ portAssignments: sweep,
102
+ };
103
+ }
104
+ // Frees the ports of a worktree (a retired worker's, or one whose directory is gone).
105
+ export function freePorts(store, worktree, reason) {
106
+ for (const held of store.portAssignments()) {
107
+ if (!samePath(held.worktree, worktree))
108
+ continue;
109
+ store.deletePorts(held.worktree);
110
+ store.appendEvent({ data: { ports: held.ports, reason, slot: held.slot, worktree: held.worktree }, kind: 'ports.freed', project: held.project });
111
+ }
112
+ }
113
+ export function lockVerdict(lock, owner, now) {
114
+ if (!lock)
115
+ return 'free';
116
+ if (owner !== null && lock.holder === owner)
117
+ return 'mine';
118
+ const since = Date.parse(lock.since);
119
+ return Number.isNaN(since) || now.getTime() - since > LOCK_STALE_MS ? 'stale' : 'held';
120
+ }
121
+ // True when something accepts connections on the port, on IPv4 or IPv6 loopback.
122
+ export async function portListening(port) {
123
+ const probe = (host) => new Promise((done) => {
124
+ const socket = connect({ host, port });
125
+ const finish = (open) => {
126
+ socket.destroy();
127
+ done(open);
128
+ };
129
+ socket.setTimeout(300, () => finish(false));
130
+ socket.once('connect', () => finish(true));
131
+ socket.once('error', () => finish(false));
132
+ });
133
+ return (await Promise.all(['127.0.0.1', '::1'].map(probe))).some(Boolean);
134
+ }
135
+ // Releases every lock the holder has, such as a retired worker's item.
136
+ export function releaseLocks(store, holder, reason) {
137
+ for (const lock of store.listLocks()) {
138
+ if (lock.holder !== holder)
139
+ continue;
140
+ store.deleteLock(lock.name);
141
+ store.appendEvent({ data: { holder, reason }, kind: 'lock.released', subject: lock.name });
142
+ }
143
+ }
144
+ function profileOf(project) {
145
+ if (project.profile)
146
+ return project.profile;
147
+ throw new ConductorError(`project ${project.name}'s stored profile is invalid: ${project.problem ?? 'unknown'}`, [
148
+ '`conduct project add <path>` reads it again',
149
+ ]);
150
+ }
151
+ function samePorts(a, b) {
152
+ return Object.keys(a).length === Object.keys(b).length && Object.entries(b).every(([name, port]) => a[name] === port);
153
+ }
154
+ //# sourceMappingURL=broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker.js","sourceRoot":"","sources":["../src/broker.ts"],"names":[],"mappings":"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"}