omp-conductor 0.15.2 → 0.15.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
package/src/setup-host.ts CHANGED
@@ -314,21 +314,49 @@ function tickSearchRoots(project: ProjectConfig): string[] {
314
314
  * have been a deliberate per-project choice — it *is* the collision — so it is
315
315
  * rewritten; anything else is operator intent and survives untouched.
316
316
  */
317
- function planTick(project: ProjectConfig, telegramStateDir: string): PlannedWrite<TickConfig> {
318
- let existing: { path: string; config: TickConfig } | undefined;
317
+ /**
318
+ * This project's tick config, wherever it actually lives. A config stamped for
319
+ * another project is that project's file: the search roots overlap, and
320
+ * restamping it here would hand this project's identity to the other fleet's cwd.
321
+ */
322
+ function findProjectTick(
323
+ project: ProjectConfig,
324
+ ): { root: string; path: string; config: TickConfig } | undefined {
319
325
  for (const root of tickSearchRoots(project)) {
320
326
  const result = readTickConfig(root);
321
327
  if (result.kind === "invalid") {
322
328
  throw new Error(`tick config invalid at ${result.path}: ${result.problem}; fix or remove it before setup`);
323
329
  }
324
- // A config stamped for another project is that project's file: the search
325
- // roots overlap, and restamping it here would hand this project's identity
326
- // to the other fleet's cwd.
327
330
  if (result.kind === "ok" && tickConfigMatchesProject(result.config, project.name)) {
328
- existing = { path: result.path, config: result.config };
329
- break;
331
+ return { root, path: result.path, config: result.config };
330
332
  }
331
333
  }
334
+ return undefined;
335
+ }
336
+
337
+ /**
338
+ * The directory this project's fleet pane runs in, which is **not** always
339
+ * `workspaceRoot`.
340
+ *
341
+ * A fleet that predates per-project roots keeps its `.conductor-tick.json` in the
342
+ * state dir, and that is the cwd its pane and `FLEET_CWD` already point at. Naming
343
+ * `workspaceRoot` instead sends `recover.sh` somewhere with no tick config, where
344
+ * `tick_agent_name` falls back to `fleet` and no longer matches the renamed pane —
345
+ * silently costing a live project its recovery. Falls back to `workspaceRoot`,
346
+ * which is where a brand-new project's config is written.
347
+ */
348
+ export function tickCwdForProject(project: ProjectConfig): string {
349
+ try {
350
+ return findProjectTick(project)?.root ?? project.workspaceRoot;
351
+ } catch {
352
+ // An invalid config is setup's problem to report, not the handoff's.
353
+ return project.workspaceRoot;
354
+ }
355
+ }
356
+
357
+ function planTick(project: ProjectConfig, telegramStateDir: string): PlannedWrite<TickConfig> {
358
+ const found = findProjectTick(project);
359
+ const existing = found === undefined ? undefined : { path: found.path, config: found.config };
332
360
 
333
361
  const armedFile = join(stateDir(), `armed-${project.name}`);
334
362
  const path = existing?.path ?? join(project.workspaceRoot, TICK_CONFIG_FILE);
@@ -37,6 +37,7 @@ import {
37
37
  totalConfiguredWorkers,
38
38
  runSetupSmoke,
39
39
  SYSTEMD_UNIT_DIR,
40
+ tickCwdForProject,
40
41
  writeHostRuntime,
41
42
  } from "./setup-host.ts";
42
43
  import { runGraphInstall, runHostInstall } from "./setup-install.ts";
@@ -1639,18 +1640,22 @@ export async function collectSetup(
1639
1640
  */
1640
1641
  export function formatHerdrHandoff(project: ProjectConfig, cfg: ConductorConfig): string {
1641
1642
  const session = process.env["HERDR_SESSION"] ?? "conductor";
1642
- const cwds = cfg.projects.map((p) => p.workspaceRoot).join(":");
1643
+ // Every entry is the cwd that owns that project's tick config — the directory its
1644
+ // pane runs in — not `workspaceRoot`. They differ on any fleet whose config
1645
+ // predates per-project roots, and recovery keys on the tick config.
1646
+ const paneCwd = tickCwdForProject(project);
1647
+ const cwds = cfg.projects.map((p) => tickCwdForProject(p)).join(":");
1643
1648
  return [
1644
1649
  "herdr handoff (CLI cannot do these — exact herdr argv):",
1645
- ` 1. In session "${session}", create a workspace for project "${project.name}" at its workspaceRoot:`,
1646
- ` herdr --session ${session} workspace create --cwd ${project.workspaceRoot} --label ${project.name} --no-focus`,
1650
+ ` 1. In session "${session}", create a workspace for project "${project.name}" at its fleet cwd:`,
1651
+ ` herdr --session ${session} workspace create --cwd ${paneCwd} --label ${project.name} --no-focus`,
1647
1652
  " Read root_pane.pane_id from the JSON reply (jq -r '((.result // .).root_pane.pane_id) // empty').",
1648
1653
  ` 2. Start omp in that empty pane. The agent NAME must match tick config agentName ("${project.name}"):`,
1649
1654
  ` herdr --session ${session} agent start ${project.name} --kind omp --pane <pane-id>`,
1650
1655
  " The pane must be at a shell prompt with no agent on it. Never agent-start into a live orchestrator pane.",
1651
1656
  ` 3. Point herdr-conductor recovery at every fleet cwd (colon-separated; see #320):`,
1652
1657
  ` FLEET_CWDS=${cwds}`,
1653
- ` Legacy single-fleet hosts can keep FLEET_CWD=${project.workspaceRoot} until multi-fleet recovery lands.`,
1658
+ ` Legacy single-fleet hosts can keep FLEET_CWD=${paneCwd} until multi-fleet recovery lands.`,
1654
1659
  " 4. Reload the daemon so it serves every configured project:",
1655
1660
  " omp-conductor restart --now",
1656
1661
  " (printed, not auto-run, when workers are live or a neighbour was just added)",