@detergent-software/conductor 3.0.0-dev.2 → 3.0.0-dev.21

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 (137) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +158 -45
  3. package/build/brief-cli.js +1 -1
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/broker-cli.js +24 -25
  6. package/build/broker-cli.js.map +1 -1
  7. package/build/broker.js +66 -92
  8. package/build/broker.js.map +1 -1
  9. package/build/claude.js +50 -7
  10. package/build/claude.js.map +1 -1
  11. package/build/cli.js +119 -54
  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 +101 -26
  16. package/build/code-host-cli.js.map +1 -1
  17. package/build/code-host.js +13 -8
  18. package/build/code-host.js.map +1 -1
  19. package/build/config.js +31 -12
  20. package/build/config.js.map +1 -1
  21. package/build/console.js +40 -24
  22. package/build/console.js.map +1 -1
  23. package/build/credentials.js +26 -24
  24. package/build/credentials.js.map +1 -1
  25. package/build/daemon.js +35 -21
  26. package/build/daemon.js.map +1 -1
  27. package/build/help.js +141 -30
  28. package/build/help.js.map +1 -1
  29. package/build/host/credential-manager.js +109 -0
  30. package/build/host/credential-manager.js.map +1 -0
  31. package/build/host/keychain.js +44 -0
  32. package/build/host/keychain.js.map +1 -0
  33. package/build/host/libsecret.js +40 -0
  34. package/build/host/libsecret.js.map +1 -0
  35. package/build/host/macos-terminal.js +63 -0
  36. package/build/host/macos-terminal.js.map +1 -0
  37. package/build/host/platform.js +4 -0
  38. package/build/host/platform.js.map +1 -0
  39. package/build/host/secrets.js +46 -0
  40. package/build/host/secrets.js.map +1 -0
  41. package/build/host/shell.js +26 -0
  42. package/build/host/shell.js.map +1 -0
  43. package/build/host/terminal.js +46 -0
  44. package/build/host/terminal.js.map +1 -0
  45. package/build/host/tmux.js +39 -0
  46. package/build/host/tmux.js.map +1 -0
  47. package/build/host/windows-terminal.js +46 -0
  48. package/build/host/windows-terminal.js.map +1 -0
  49. package/build/in-process.js +6 -5
  50. package/build/in-process.js.map +1 -1
  51. package/build/inbox.js +50 -5
  52. package/build/inbox.js.map +1 -1
  53. package/build/init.js +508 -0
  54. package/build/init.js.map +1 -0
  55. package/build/lib/providers/ado/pipelines.js +102 -17
  56. package/build/lib/providers/ado/pipelines.js.map +1 -1
  57. package/build/lib/providers/ado/trigger.js +5 -0
  58. package/build/lib/providers/ado/trigger.js.map +1 -1
  59. package/build/lib/providers/github/actions.js +85 -2
  60. package/build/lib/providers/github/actions.js.map +1 -1
  61. package/build/lib/providers/index.js +3 -2
  62. package/build/lib/providers/index.js.map +1 -1
  63. package/build/lib/providers/npm/stage.js +14 -4
  64. package/build/lib/providers/npm/stage.js.map +1 -1
  65. package/build/lib/providers/timing.js +10 -0
  66. package/build/lib/providers/timing.js.map +1 -0
  67. package/build/lib/providers/verdicts/runs.js +12 -2
  68. package/build/lib/providers/verdicts/runs.js.map +1 -1
  69. package/build/local-files.js +95 -0
  70. package/build/local-files.js.map +1 -0
  71. package/build/manager.js +171 -47
  72. package/build/manager.js.map +1 -1
  73. package/build/note.js +1 -1
  74. package/build/note.js.map +1 -1
  75. package/build/npm-reads.js +323 -0
  76. package/build/npm-reads.js.map +1 -0
  77. package/build/paths.js +44 -1
  78. package/build/paths.js.map +1 -1
  79. package/build/plugin.js +10 -0
  80. package/build/plugin.js.map +1 -1
  81. package/build/profile-fields.js +149 -0
  82. package/build/profile-fields.js.map +1 -0
  83. package/build/profile-layers.js +264 -0
  84. package/build/profile-layers.js.map +1 -0
  85. package/build/profile.js +60 -38
  86. package/build/profile.js.map +1 -1
  87. package/build/project-check-cli.js +18 -10
  88. package/build/project-check-cli.js.map +1 -1
  89. package/build/project-check.js +179 -97
  90. package/build/project-check.js.map +1 -1
  91. package/build/pushover.js +5 -5
  92. package/build/pushover.js.map +1 -1
  93. package/build/registry.js +195 -36
  94. package/build/registry.js.map +1 -1
  95. package/build/rule-actions.js +3 -1
  96. package/build/rule-actions.js.map +1 -1
  97. package/build/rules-service.js +101 -44
  98. package/build/rules-service.js.map +1 -1
  99. package/build/rules.js +11 -2
  100. package/build/rules.js.map +1 -1
  101. package/build/runs.js +88 -14
  102. package/build/runs.js.map +1 -1
  103. package/build/scheduler.js +11 -1
  104. package/build/scheduler.js.map +1 -1
  105. package/build/server.js +4 -25
  106. package/build/server.js.map +1 -1
  107. package/build/spend.js +4 -0
  108. package/build/spend.js.map +1 -1
  109. package/build/store.js +82 -25
  110. package/build/store.js.map +1 -1
  111. package/build/timeline.js +104 -0
  112. package/build/timeline.js.map +1 -0
  113. package/build/tracker-reads-cli.js +4 -5
  114. package/build/tracker-reads-cli.js.map +1 -1
  115. package/build/train-cli.js +6 -5
  116. package/build/train-cli.js.map +1 -1
  117. package/build/upgrade.js +16 -1
  118. package/build/upgrade.js.map +1 -1
  119. package/build/wait.js +6 -6
  120. package/build/wait.js.map +1 -1
  121. package/build/watchers/ado.js +3 -2
  122. package/build/watchers/ado.js.map +1 -1
  123. package/build/watchers/github.js +3 -2
  124. package/build/watchers/github.js.map +1 -1
  125. package/build/watchers/npm.js +6 -4
  126. package/build/watchers/npm.js.map +1 -1
  127. package/build/worktrees.js +199 -92
  128. package/build/worktrees.js.map +1 -1
  129. package/package.json +61 -61
  130. package/plugin/skills/conductor-console/SKILL.md +3 -3
  131. package/plugin/skills/work-item/SKILL.md +8 -6
  132. package/build/checkout-sync.js +0 -249
  133. package/build/checkout-sync.js.map +0 -1
  134. package/build/credential.js +0 -66
  135. package/build/credential.js.map +0 -1
  136. package/build/tab.js +0 -32
  137. package/build/tab.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../../../src/lib/providers/verdicts/runs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../../../src/lib/providers/verdicts/runs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { cp } from 'node:fs/promises';
3
+ import { dirname, join, matchesGlob, relative } from 'node:path';
4
+ import { ConductorError } from "./errors.js";
5
+ import { outputOf } from "./run.js";
6
+ export const INCLUDE_FILE = '.worktreeinclude';
7
+ // Copies the source checkout's included files into `target`; returns their paths, relative and with
8
+ // forward slashes. Git lists ignored paths with whole ignored directories collapsed, which keeps
9
+ // node_modules out of the walk. A pattern without a slash (".env*") matches at any depth but not
10
+ // inside an ignored directory; an anchored one ("apps/api/secrets/*.json") also reaches into one.
11
+ export async function copyLocalFiles(git, source, target) {
12
+ if (!existsSync(join(source, '.git')))
13
+ throw new ConductorError(`localFilesFrom ${source} is not a git checkout`, [
14
+ "set the profile's localFilesFrom to a checkout of the project you keep",
15
+ ]);
16
+ const patterns = includePatterns(join(source, INCLUDE_FILE));
17
+ if (patterns.length === 0)
18
+ return [];
19
+ const listed = await git(['ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z'], source);
20
+ if (listed.code !== 0)
21
+ throw new ConductorError(`git couldn't list the ignored files in ${source}`, [outputOf(listed, 500)]);
22
+ const copied = [];
23
+ // Asynchronous, so an included directory of any size doesn't hold up the daemon.
24
+ const copy = async (rel) => {
25
+ const to = join(target, rel);
26
+ mkdirSync(dirname(to), { recursive: true });
27
+ await cp(join(source, rel), to, { recursive: true });
28
+ copied.push(rel);
29
+ };
30
+ // Git can list a directory and also entries inside it (apps/, apps/api/, ...), parents first, so
31
+ // anything under a directory already copied or walked is skipped.
32
+ const handled = ['.claude/worktrees'];
33
+ for (const entry of listed.stdout.split('\0').filter(Boolean)) {
34
+ const isDir = entry.endsWith('/');
35
+ const rel = isDir ? entry.slice(0, -1) : entry;
36
+ if (handled.some((dir) => rel === dir || rel.startsWith(`${dir}/`)))
37
+ continue;
38
+ if (isIncluded(patterns, rel, isDir))
39
+ await copy(rel);
40
+ else if (isDir)
41
+ for (const file of walkAnchored(patterns, source, rel))
42
+ await copy(file);
43
+ if (isDir)
44
+ handled.push(rel);
45
+ }
46
+ return copied;
47
+ }
48
+ export function includePatterns(file) {
49
+ if (!existsSync(file))
50
+ return [];
51
+ return readFileSync(file, 'utf8')
52
+ .split(/\r?\n/)
53
+ .map((line) => line.trim())
54
+ .filter((line) => line && !line.startsWith('#'))
55
+ .map((line) => {
56
+ const negated = line.startsWith('!');
57
+ let pattern = negated ? line.slice(1) : line;
58
+ const dirOnly = pattern.endsWith('/');
59
+ if (dirOnly)
60
+ pattern = pattern.slice(0, -1);
61
+ const anchored = pattern.includes('/');
62
+ if (pattern.startsWith('/'))
63
+ pattern = pattern.slice(1);
64
+ return { anchored, dirOnly, glob: anchored ? pattern : `**/${pattern}`, negated };
65
+ });
66
+ }
67
+ export function isIncluded(patterns, rel, isDir) {
68
+ const parts = rel.split('/');
69
+ for (let i = 1; i < parts.length; i++)
70
+ if (matchesLast(patterns, parts.slice(0, i).join('/'), true))
71
+ return true;
72
+ return matchesLast(patterns, rel, isDir);
73
+ }
74
+ function matchesLast(patterns, rel, isDir) {
75
+ let result = false;
76
+ for (const p of patterns) {
77
+ if (p.dirOnly && !isDir)
78
+ continue;
79
+ if (matchesGlob(rel, p.glob))
80
+ result = !p.negated;
81
+ }
82
+ return result;
83
+ }
84
+ function* walkAnchored(patterns, source, dir) {
85
+ if (!patterns.some((p) => p.anchored && !p.negated && p.glob.startsWith(`${dir}/`)))
86
+ return;
87
+ for (const entry of readdirSync(join(source, dir), { recursive: true, withFileTypes: true })) {
88
+ if (!entry.isFile())
89
+ continue;
90
+ const rel = relative(source, join(entry.parentPath, entry.name)).split('\\').join('/');
91
+ if (isIncluded(patterns, rel, false))
92
+ yield rel;
93
+ }
94
+ }
95
+ //# sourceMappingURL=local-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-files.js","sourceRoot":"","sources":["../src/local-files.ts"],"names":[],"mappings":"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"}
package/build/manager.js CHANGED
@@ -1,18 +1,22 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { freePorts, releaseLocks } from "./broker.js";
5
- import { isLive, isStarting, permissionPrompt, SessionGone, turnEnded, } from "./claude.js";
4
+ import { freePorts, portEnv, releaseLocks } from "./broker.js";
5
+ import { isLive, isStarting, permissionPrompt, SessionGone, trustStep, turnEnded, WorkspaceUntrusted, } from "./claude.js";
6
6
  import { formatCondition, parseCondition, prRef, uncheckable } from "./conditions.js";
7
7
  import { ConductorError } from "./errors.js";
8
8
  import { UNWATCHED_HANDOFFS } from "./inbox.js";
9
9
  import { processAlive as defaultProcessAlive } from "./lock.js";
10
+ import { projectPaths } from "./paths.js";
10
11
  import { missingPluginSkills, projectSkills } from "./plugin.js";
11
12
  import { WAKE_PREFIX } from "./protocol.js";
13
+ import { registeredByCheckout } from "./registry.js";
12
14
  import { resolveOpenWait } from "./resolve.js";
13
15
  import { findTranscript, formatTokens, lastTurnAt, localDay, projectSlug, readUsage, transcriptFiles, } from "./spend.js";
14
16
  import { ACTIVE_STATUSES, openStore, waitSchema, workerSchema, } from "./store.js";
17
+ import { linkedPr } from "./tracker-writes.js";
15
18
  import { pendingCall } from "./transcript.js";
19
+ import { BRANCH_PREFIX } from "./worktrees.js";
16
20
  // A worker as GET /fleet shows it.
17
21
  export const fleetWorkerSchema = workerSchema.extend({
18
22
  // launching, starting, working, idle, waiting on <x>, parked, stopped, no session
@@ -94,6 +98,9 @@ const ITEM = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
94
98
  const STOP_WAIT_MS = 30_000;
95
99
  // A park that failed (the process outlived `claude stop`) is tried again this much later.
96
100
  const PARK_RETRY_MS = 5 * 60_000;
101
+ // How long a new session id has to stay listed before the worker follows it. A resuming session is
102
+ // listed for under a second under the id its process started with, before it loads its own (ATK-574).
103
+ const SESSION_ID_SETTLE_MS = 5_000;
97
104
  const TRANSCRIPT_SCAN_MS = 60_000;
98
105
  // A compaction with no boundary in the transcript this long after it started, its turn over, failed.
99
106
  const COMPACT_MINUTES = 10;
@@ -116,7 +123,7 @@ export function eventText(waits) {
116
123
  return `${WAKE_PREFIX}${[...facts].map(([fact, at]) => `${fact} ${clock(new Date(at))}`).join('; ')}`;
117
124
  }
118
125
  export function sessionManager(deps) {
119
- const { claude, config, log, openTab, pluginDir, store, worktrees } = deps;
126
+ const { broker, claude, config, log, pluginDir, prs, store, tabs, worktrees } = deps;
120
127
  const now = deps.now ?? (() => new Date());
121
128
  const alive = deps.processAlive ?? defaultProcessAlive;
122
129
  const sleep = deps.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
@@ -126,6 +133,8 @@ export function sessionManager(deps) {
126
133
  const stalls = new Map();
127
134
  const transcripts = new Map();
128
135
  const transcriptScans = new Map();
136
+ // worker id → a session id the listing shows for it that it hasn't followed yet, since when
137
+ const newSessionIds = new Map();
129
138
  let sessions = [];
130
139
  // Until the first sweep, nothing is known about any session.
131
140
  let listed = false;
@@ -226,10 +235,15 @@ export function sessionManager(deps) {
226
235
  throw new ConductorError(`project "${found.name}" has a problem: ${found.problem ?? 'no profile'}`);
227
236
  if (!found.enabled)
228
237
  throw new ConductorError(`project "${found.name}" is disabled`);
238
+ if (registeredByCheckout(deps.paths, found)) {
239
+ throw new ConductorError(`project "${found.name}" is registered by the checkout ${found.clone}`, [
240
+ `the conductor works only in a clone of its own: run \`conduct project add "${found.clone}"\` first`,
241
+ ]);
242
+ }
229
243
  return { ...found, profile: found.profile };
230
244
  };
231
- // Where a worker's commands run: its project's main checkout, where conduct-atk starts them too.
232
- const rootOf = (worker) => store.listProjects().find((p) => p.name === worker.project)?.root ?? worker.worktree ?? process.cwd();
245
+ // Where a worker's commands run when it has no worktree: its project's clone.
246
+ const cloneOf = (worker) => projectPaths(deps.paths, worker.project).clone;
233
247
  const sessionOf = (worker) => sessions.find((s) => (worker.sessionId !== null && s.sessionId === worker.sessionId) ||
234
248
  (worker.handle !== null && s.id === worker.handle));
235
249
  const minutesSince = (iso) => (now().getTime() - Date.parse(iso)) / 60_000;
@@ -266,27 +280,58 @@ export function sessionManager(deps) {
266
280
  }
267
281
  };
268
282
  // ---------- launch ----------
269
- const launchWorker = async (worker, root, profile, prompt) => {
283
+ // The worktree first (design note v2, section 2): its ports from the broker, then `git worktree add`
284
+ // in the project's clone from origin/<base>, the local files and the profile's setup. The session
285
+ // starts in it.
286
+ const launchWorker = async (worker, profile, prompt) => {
270
287
  let current = worker;
271
- let stage = 'create worktree';
288
+ let stage = 'assign ports';
289
+ const { clone, worktrees: dir } = projectPaths(deps.paths, worker.project);
290
+ const path = join(dir, worker.name);
291
+ // A worktree there already (a kept one) isn't this launch's to remove when it fails.
292
+ const reused = existsSync(join(path, '.git'));
272
293
  try {
273
- const worktree = await worktrees.create(root, worker.name);
294
+ // Recorded before it exists: the broker keeps an in-flight worker's ports while its directory
295
+ // isn't there yet, and a create that fails part way is cleaned up.
296
+ current = update(current, { worktree: path });
297
+ const { ports } = await broker.assignPorts(path, worker.project, profile.ports);
298
+ stage = 'create worktree';
299
+ const env = portEnv(ports);
300
+ const worktree = await worktrees.create({
301
+ base: profile.baseBranch,
302
+ clone,
303
+ env: { ...env, CONDUCTOR_ITEM: worker.item, CONDUCTOR_PROJECT: worker.project, CONDUCTOR_WORKTREE: path },
304
+ ...(profile.localFilesFrom === undefined ? {} : { localFilesFrom: profile.localFilesFrom }),
305
+ name: worker.name,
306
+ path,
307
+ sessionEnv: env,
308
+ setup: profile.setup,
309
+ });
274
310
  current = update(current, { branch: await worktrees.branchOf(worktree), worktree });
275
311
  stage = 'check skills';
276
312
  const skills = [profile.skills.work, profile.skills.verify];
277
- const problem = (await worktrees.checkSkills(worktree, projectSkills(skills))) ?? missingPluginSkills(pluginDir, skills);
313
+ const problem = worktrees.missingSkills(worktree, projectSkills(skills)) ?? missingPluginSkills(pluginDir, skills);
278
314
  if (problem)
279
315
  throw new ConductorError(problem);
280
316
  stage = 'start session';
281
317
  const started = now().getTime();
282
- const handle = await claude.launch({
283
- cwd: root,
318
+ const handle = await claude
319
+ .launch({
320
+ cwd: worktree,
284
321
  disallowedTools: DISALLOWED_TOOLS,
285
322
  name: worker.name,
286
323
  permissionMode: PERMISSION_MODE,
287
324
  pluginDir,
288
325
  prompt,
289
- worktree: worker.name,
326
+ })
327
+ .catch((error) => {
328
+ // Claude Code's own message names the worktree, which this failed launch removes.
329
+ if (!(error instanceof WorkspaceUntrusted))
330
+ throw error;
331
+ throw new ConductorError(`Claude Code doesn't trust ${clone}`, [
332
+ `${trustStep(clone)}, then \`conduct project check ${worker.project}\``,
333
+ ...error.details,
334
+ ]);
290
335
  });
291
336
  try {
292
337
  sessions = await claude.agents();
@@ -299,9 +344,9 @@ export function sessionManager(deps) {
299
344
  current = update(current, { handle, sessionId: session?.sessionId ?? null, status: 'running' });
300
345
  event(current, 'worker.launched', { handle, pluginDir, sessionId: current.sessionId, worktree });
301
346
  expectPlugin(current, started);
302
- if (profile.workers.tab) {
303
- const tab = await openTab({
304
- cwd: root,
347
+ if (tabs.onLaunch) {
348
+ const tab = await tabs.open({
349
+ cwd: worktree,
305
350
  handle,
306
351
  title: worker.name,
307
352
  window: `${worker.probe ? 'probe' : 'Conductor'} · ${worker.project}`,
@@ -312,24 +357,62 @@ export function sessionManager(deps) {
312
357
  }
313
358
  catch (error) {
314
359
  // A worktree with no session would read as in flight; one with work in it is kept.
315
- const cleanup = current.worktree ? await removeIfClean(root, current.worktree) : {};
360
+ const cleanup = reused
361
+ ? { kept: path, keptBecause: 'it was there before the launch' }
362
+ : current.worktree && existsSync(current.worktree)
363
+ ? await removeIfClean(clone, current.worktree)
364
+ : {};
365
+ freePorts(store, path, 'launch failed');
316
366
  current = update(current, { status: 'failed' });
317
367
  event(current, 'worker.launch-failed', { error: messageOf(error), step: stage, ...cleanup });
318
368
  log.log('warn', 'launch failed', { error: messageOf(error), step: stage, worker: worker.id });
319
369
  }
320
370
  };
321
- const removeIfClean = async (root, worktree) => {
371
+ // A failed launch's worktree, unless it holds work. Its branch goes with it while it still has the
372
+ // name the launch gave it; a renamed one was the worker's.
373
+ const removeIfClean = async (clone, worktree) => {
322
374
  try {
323
375
  const work = await worktrees.unfinishedWork(worktree);
324
376
  if (work)
325
377
  return { kept: worktree, ...work };
326
- await worktrees.remove(root, worktree);
378
+ const branch = await worktrees.branchOf(worktree);
379
+ await worktrees.remove(worktree, { deleteBranch: branch?.startsWith(BRANCH_PREFIX) === true, repo: clone });
327
380
  return { removed: worktree };
328
381
  }
329
382
  catch (error) {
330
383
  return { removeError: messageOf(error) };
331
384
  }
332
385
  };
386
+ // Whether retiring the worker removes its worktree and branch: once its PR has merged, and for a
387
+ // probe or a forced retire. `unread`: its PR couldn't be read, so retire waits.
388
+ const worktreeFate = async (worker, force) => {
389
+ const decided = (remove, reason) => ({ reason, remove, unread: null });
390
+ if (worker.worktree === null || !existsSync(worker.worktree))
391
+ return decided(true, 'no worktree');
392
+ if (worker.probe)
393
+ return decided(true, 'a probe');
394
+ if (force)
395
+ return decided(true, 'retire --force');
396
+ // With no PR and nothing uncommitted or unpushed, nothing in it is lost (a canceled item, a worker
397
+ // retired before its first commit): it goes, with no inbox item.
398
+ if (worker.pr === null)
399
+ return (await worktrees.unfinishedWork(worker.worktree)) === null
400
+ ? decided(true, 'no PR and nothing unpushed')
401
+ : decided(false, 'it has no PR');
402
+ const profile = store.listProjects().find((p) => p.name === worker.project)?.profile;
403
+ if (!profile)
404
+ return { reason: '', remove: false, unread: `project ${worker.project} has no readable profile` };
405
+ // No read would ever answer for it, so waiting would refuse the retire for good.
406
+ if (linkedPr(worker.pr, profile) === null)
407
+ return decided(false, `its PR ${worker.pr} isn't one ${worker.project}'s code host reads`);
408
+ try {
409
+ const state = await prs(profile, worker.pr);
410
+ return decided(state === 'merged', `its PR ${worker.pr} is ${state}`);
411
+ }
412
+ catch (error) {
413
+ return { reason: '', remove: false, unread: messageOf(error) };
414
+ }
415
+ };
333
416
  // ---------- the sweep ----------
334
417
  // The clock: the sweep is its timer. The other kinds are the watchers'.
335
418
  const resolveTimeWaits = () => {
@@ -383,7 +466,7 @@ export function sessionManager(deps) {
383
466
  const text = eventText(resolved);
384
467
  const started = now().getTime();
385
468
  const { copy } = await claude.resume({
386
- cwd: session?.cwd ?? worker.worktree ?? rootOf(worker),
469
+ cwd: session?.cwd ?? worker.worktree ?? cloneOf(worker),
387
470
  sessionId: worker.sessionId,
388
471
  text,
389
472
  });
@@ -432,7 +515,7 @@ export function sessionManager(deps) {
432
515
  let copy;
433
516
  try {
434
517
  ({ copy } = await claude.resume({
435
- cwd: session?.cwd ?? worker.worktree ?? rootOf(worker),
518
+ cwd: session?.cwd ?? worker.worktree ?? cloneOf(worker),
436
519
  sessionId: worker.sessionId,
437
520
  text: COMPACT_TEXT,
438
521
  }));
@@ -578,14 +661,29 @@ export function sessionManager(deps) {
578
661
  let current = worker;
579
662
  const session = sessionOf(current);
580
663
  // The listing is the truth for ids: fill them in after a launch, and follow a changed short id.
581
- if (session && (current.sessionId !== session.sessionId || current.handle !== session.id)) {
582
- // A new session id (a /clear) writes a new transcript.
583
- if (current.sessionId !== session.sessionId) {
664
+ // A changed session id is followed once it has stayed listed SESSION_ID_SETTLE_MS.
665
+ if (session) {
666
+ let sessionId = current.sessionId;
667
+ const pending = newSessionIds.get(current.id);
668
+ if (sessionId === null || session.sessionId === sessionId)
669
+ newSessionIds.delete(current.id);
670
+ else if (pending?.sessionId !== session.sessionId) {
671
+ newSessionIds.set(current.id, { sessionId: session.sessionId, since: now().getTime() });
672
+ }
673
+ else if (now().getTime() - pending.since >= SESSION_ID_SETTLE_MS) {
674
+ newSessionIds.delete(current.id);
675
+ // A new session id (a /clear) writes a new transcript.
584
676
  transcripts.delete(current.id);
585
677
  transcriptScans.delete(current.id);
678
+ sessionId = session.sessionId;
679
+ }
680
+ sessionId ??= session.sessionId;
681
+ if (sessionId !== current.sessionId || session.id !== current.handle) {
682
+ current = update(current, { handle: session.id, sessionId });
586
683
  }
587
- current = update(current, { handle: session.id, sessionId: session.sessionId });
588
684
  }
685
+ else
686
+ newSessionIds.delete(current.id);
589
687
  const waits = store.waitsOf(current.id);
590
688
  const resolved = waits.filter((w) => w.resolvedAt !== null);
591
689
  // A listed pid can outlive its process for a moment after a stop.
@@ -773,9 +871,17 @@ export function sessionManager(deps) {
773
871
  });
774
872
  continue;
775
873
  }
776
- const root = rootOf(worker);
777
- const worktree = worker.worktree ?? join(root, '.claude', 'worktrees', worker.name);
778
- const cleanup = listing !== null && existsSync(worktree) ? await removeIfClean(root, worktree) : {};
874
+ const root = cloneOf(worker);
875
+ const worktree = worker.worktree ?? join(projectPaths(deps.paths, worker.project).worktrees, worker.name);
876
+ const reused = store
877
+ .workerEvents(worker.id, 1000)
878
+ .some((e) => e.kind === 'worker.launching' && e.data.reused === true);
879
+ const cleanup = reused
880
+ ? { kept: worktree, keptBecause: 'it was there before the launch' }
881
+ : listing !== null && existsSync(worktree)
882
+ ? await removeIfClean(root, worktree)
883
+ : {};
884
+ freePorts(store, worktree, 'launch failed');
779
885
  store.closeWaits(worker.id, 'launch failed');
780
886
  event(update(worker, { status: 'failed' }), 'worker.launch-failed', {
781
887
  error: 'the daemon stopped during the launch',
@@ -805,7 +911,7 @@ export function sessionManager(deps) {
805
911
  const problem = uncheckable(parsed, project?.profile ?? null);
806
912
  if (problem) {
807
913
  throw new ConductorError(problem, [
808
- `the conductor takes ${worker.project}'s profile from ${project?.profilePath ?? 'its .claude/conductor.json'} when it starts or the file is saved; \`conduct rules\` lists one it refused`,
914
+ `the conductor takes ${worker.project}'s profile again when it starts, when ${project?.profilePath ?? 'its machine profile'} is saved, and after each fetch of its clone; \`conduct profile ${worker.project}\` shows it, and \`conduct rules\` lists one it refused`,
809
915
  ]);
810
916
  }
811
917
  // Stored in its normal full form, so the watchers and `conduct status` read one spelling. A
@@ -847,7 +953,7 @@ export function sessionManager(deps) {
847
953
  // No event text: the session wakes idle and keeps running when the tab closes.
848
954
  started = now().getTime();
849
955
  const { copy } = await claude.resume({
850
- cwd: sessionOf(current)?.cwd ?? current.worktree ?? rootOf(current),
956
+ cwd: sessionOf(current)?.cwd ?? current.worktree ?? cloneOf(current),
851
957
  sessionId,
852
958
  text: null,
853
959
  });
@@ -859,16 +965,12 @@ export function sessionManager(deps) {
859
965
  }
860
966
  current = update(current, { attachedAt: now().toISOString(), status: 'running', stoppedAt: null });
861
967
  forgetAttach(current.id);
862
- const tab = await openTab({
863
- cwd: rootOf(current),
864
- handle,
865
- title: current.name,
866
- window: `${current.probe ? 'probe' : 'Conductor'} · ${current.project}`,
867
- });
968
+ const window = `${current.probe ? 'probe' : 'Conductor'} · ${current.project}`;
969
+ const tab = await tabs.open({ cwd: current.worktree ?? cloneOf(current), handle, title: current.name, window });
868
970
  event(current, 'worker.attached', { resumed, ...(tab ? { tabError: tab } : {}) });
869
971
  if (started !== null)
870
972
  expectPlugin(current, started);
871
- return { resumed, tab, worker: current };
973
+ return { hint: tab === null ? tabs.hint(window) : null, resumed, tab, worker: current };
872
974
  }
873
975
  finally {
874
976
  busy.delete(worker.id);
@@ -896,17 +998,23 @@ export function sessionManager(deps) {
896
998
  probe: request.probe === true,
897
999
  project: target.name,
898
1000
  });
1001
+ // A worktree there already isn't the launch's to remove if it fails, after a restart too.
1002
+ const reused = existsSync(join(projectPaths(deps.paths, target.name).worktrees, name, '.git'));
899
1003
  event(worker, 'worker.launching', {
900
1004
  probe: worker.probe,
1005
+ ...(reused ? { reused } : {}),
901
1006
  ...(request.cause === undefined ? {} : { cause: request.cause }),
902
1007
  });
903
1008
  busy.add(worker.id);
904
- void track(launchWorker(worker, target.root, target.profile, prompt).finally(() => busy.delete(worker.id)));
1009
+ void track(launchWorker(worker, target.profile, prompt).finally(() => busy.delete(worker.id)));
905
1010
  return worker;
906
1011
  },
907
1012
  pluginSeen: (sessionId) => {
908
1013
  pluginReports.set(sessionId, now().getTime());
909
- return store.listWorkers(ACTIVE_STATUSES).find((w) => w.sessionId === sessionId) ?? null;
1014
+ // A /clear's session reports before the worker follows its new id.
1015
+ return (store
1016
+ .listWorkers(ACTIVE_STATUSES)
1017
+ .find((w) => w.sessionId === sessionId || newSessionIds.get(w.id)?.sessionId === sessionId) ?? null);
910
1018
  },
911
1019
  retire: async (workerId, { cause, force = false } = {}) => {
912
1020
  const worker = activeWorker(workerId);
@@ -938,10 +1046,15 @@ export function sessionManager(deps) {
938
1046
  if (session && isLive(session) && !turnEnded(session) && !force) {
939
1047
  return refuse('its turn is still running: retire once it ends, or retire --force', true);
940
1048
  }
941
- const work = worker.worktree ? await worktrees.unfinishedWork(worker.worktree) : null;
1049
+ // The worktree and its branch go once the PR has merged; a probe's and a forced retire's go
1050
+ // anyway. Otherwise both are kept, so nothing the PR didn't land is lost (design note v2,
1051
+ // section 2), and the inbox says where they are.
1052
+ const fate = await worktreeFate(worker, force);
1053
+ if (fate.unread)
1054
+ return refuse(`its PR couldn't be read: ${fate.unread}`, true);
1055
+ const work = fate.remove && worker.worktree ? await worktrees.unfinishedWork(worker.worktree) : null;
942
1056
  if (work && !force)
943
1057
  return refuse('unfinished work: commit and push it, or retire --force to throw it away', false, work);
944
- const root = store.listProjects().find((p) => p.name === worker.project)?.root;
945
1058
  const steps = [];
946
1059
  const handle = worker.handle;
947
1060
  if (handle !== null && session?.pid !== undefined) {
@@ -959,14 +1072,24 @@ export function sessionManager(deps) {
959
1072
  return { outcome: 'partial', steps, worker };
960
1073
  }
961
1074
  }
1075
+ let kept = null;
962
1076
  if (worker.worktree !== null) {
963
1077
  const worktree = worker.worktree;
964
- await step(steps, 'remove worktree', async () => {
965
- if (!root)
966
- throw new Error(`project ${worker.project} is not registered`);
967
- await worktrees.remove(root, worktree);
968
- freePorts(store, worktree, 'worker retired');
969
- });
1078
+ if (fate.remove) {
1079
+ // The repository is the worktree's own: one launched before clones is in a person's
1080
+ // checkout, and retires from there.
1081
+ await step(steps, 'remove worktree', () => worktrees.remove(worktree, { deleteBranch: true, repo: cloneOf(worker) }));
1082
+ }
1083
+ else if (existsSync(worktree)) {
1084
+ kept = {
1085
+ branch: await worktrees.branchOf(worktree),
1086
+ reason: fate.reason,
1087
+ repo: await worktrees.repoOf(worktree),
1088
+ worktree,
1089
+ };
1090
+ }
1091
+ // No session serves on them any more, kept worktree or not.
1092
+ freePorts(store, worktree, 'worker retired');
970
1093
  }
971
1094
  try {
972
1095
  readSpend(worker); // the last of it, before the session goes
@@ -988,6 +1111,7 @@ export function sessionManager(deps) {
988
1111
  event(current, ok ? 'worker.retired' : 'worker.retire-partial', {
989
1112
  forced: force && work !== null,
990
1113
  steps,
1114
+ ...(kept === null ? {} : { kept }),
991
1115
  ...(cause === undefined ? {} : { cause }),
992
1116
  });
993
1117
  return { outcome: ok ? 'retired' : 'partial', steps, worker: current };
@@ -1008,7 +1132,7 @@ export function sessionManager(deps) {
1008
1132
  try {
1009
1133
  const started = now().getTime();
1010
1134
  const { copy } = await claude.resume({
1011
- cwd: worker.worktree ?? rootOf(worker),
1135
+ cwd: worker.worktree ?? cloneOf(worker),
1012
1136
  sessionId: worker.sessionId,
1013
1137
  text,
1014
1138
  });