taskplane 0.23.16 → 0.24.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.
@@ -15,9 +15,9 @@ import { ORCH_MESSAGES, computeIntegrateCleanupResult } from "./messages.ts";
15
15
  import type { IntegrateCleanupRepoFindings } from "./messages.ts";
16
16
  import { computeWaveAssignments } from "./waves.ts";
17
17
  import { createOrchWidget, formatDependencyGraph, formatWavePlan } from "./formatting.ts";
18
- import { deleteBatchState, loadBatchState, saveBatchState, detectOrphanSessions, parseOrchSessionNames } from "./persistence.ts";
18
+ import { deleteBatchState, loadBatchState, saveBatchState, detectOrphanSessions } from "./persistence.ts";
19
19
  import { deleteStaleBranches, listWorktrees, resolveWorktreeBasePath, formatPreflightResults, runPreflight } from "./worktree.ts";
20
- import { computeTransitiveDependents, executeLane, resolveCanonicalTaskPaths, tmuxHasSession } from "./execution.ts";
20
+ import { computeTransitiveDependents, resolveCanonicalTaskPaths } from "./execution.ts";
21
21
  import { executeOrchBatch } from "./engine.ts";
22
22
  import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
23
23
  import { formatOrchSessions, listOrchSessions } from "./sessions.ts";
@@ -29,6 +29,7 @@ import { buildExecutionContext } from "./workspace.ts";
29
29
  import { openSettingsTui } from "./settings-tui.ts";
30
30
  import { loadProjectConfig } from "./config-loader.ts";
31
31
  import { runMigrations } from "./migrations.ts";
32
+ import { executeAbort } from "./abort.ts";
32
33
  import { serializeWorkspaceConfig, applySerializedState, deserializeWorkspaceConfig } from "./engine-worker.ts";
33
34
  import type { EngineWorkerData, WorkerToMainMessage } from "./engine-worker.ts";
34
35
  import { cleanupPostIntegrate, formatPostIntegrateCleanup, sweepStaleArtifacts, formatPreflightSweep, rotateSupervisorLogs, formatLogRotation } from "./cleanup.ts";
@@ -1481,7 +1482,7 @@ export default function (pi: ExtensionAPI) {
1481
1482
  function updateOrchWidget() {
1482
1483
  if (!orchWidgetCtx) return;
1483
1484
  const ctx = orchWidgetCtx;
1484
- const prefix = orchConfig.orchestrator.tmux_prefix;
1485
+ const prefix = orchConfig.orchestrator.sessionPrefix;
1485
1486
 
1486
1487
  ctx.ui.setWidget(
1487
1488
  "task-orchestrator",
@@ -1641,6 +1642,7 @@ export default function (pi: ExtensionAPI) {
1641
1642
  }
1642
1643
 
1643
1644
  // ── Section 1: Preflight ─────────────────────────────────
1645
+ ctx.ui.notify("ℹ️ Runtime V2 is the default backend (subprocess-only).", "info");
1644
1646
  const preflight = runPreflight(orchConfig, execCtx!.repoRoot);
1645
1647
  ctx.ui.notify(formatPreflightResults(preflight), preflight.passed ? "info" : "error");
1646
1648
  if (!preflight.passed) return;
@@ -1783,7 +1785,7 @@ export default function (pi: ExtensionAPI) {
1783
1785
 
1784
1786
  // Orphan detection
1785
1787
  const orphanResult = detectOrphanSessions(
1786
- orchConfig.orchestrator.tmux_prefix,
1788
+ orchConfig.orchestrator.sessionPrefix,
1787
1789
  repoRoot,
1788
1790
  );
1789
1791
 
@@ -2215,9 +2217,9 @@ export default function (pi: ExtensionAPI) {
2215
2217
  * Core logic for orch-abort. Returns accumulated status messages.
2216
2218
  * Works even without execCtx (safety-critical).
2217
2219
  */
2218
- function doOrchAbort(hard: boolean, ctx: ExtensionContext): string {
2220
+ async function doOrchAbort(hard: boolean, ctx: ExtensionContext): Promise<string> {
2219
2221
  const mode: AbortMode = hard ? "hard" : "graceful";
2220
- const prefix = orchConfig.orchestrator.tmux_prefix;
2222
+ const prefix = orchConfig.orchestrator.sessionPrefix;
2221
2223
 
2222
2224
  const stateRoot = execCtx?.repoRoot ?? ctx.cwd;
2223
2225
  const messages: string[] = [`🛑 Abort requested (${mode} mode, prefix: ${prefix})...`];
@@ -2249,7 +2251,6 @@ export default function (pi: ExtensionAPI) {
2249
2251
  }
2250
2252
  }
2251
2253
 
2252
- // Step 3: Check what we're aborting
2253
2254
  const hasActiveBatch = orchBatchState.phase !== "idle" &&
2254
2255
  orchBatchState.phase !== "completed" &&
2255
2256
  orchBatchState.phase !== "failed" &&
@@ -2267,67 +2268,25 @@ export default function (pi: ExtensionAPI) {
2267
2268
  `persisted=${persistedState ? persistedState.batchId : "none"}`,
2268
2269
  );
2269
2270
 
2270
- // If no batch AND no sessions, nothing to abort
2271
2271
  if (!hasActiveBatch && !persistedState) {
2272
- // Still check for sessions below, but short-circuit if none
2273
- let allSessionNames: string[] = [];
2274
- try {
2275
- const tmuxOutput = execSync('tmux list-sessions -F "#{session_name}"', {
2276
- encoding: "utf-8",
2277
- timeout: 5000,
2278
- }).trim();
2279
- const all = tmuxOutput ? tmuxOutput.split("\n").map(s => s.trim()).filter(Boolean) : [];
2280
- allSessionNames = all.filter(name => name.startsWith(`${prefix}-`));
2281
- } catch {
2282
- // tmux not available
2283
- }
2284
- if (allSessionNames.length === 0) {
2285
- try { unlinkSync(abortSignalFile); } catch {}
2286
- return ORCH_MESSAGES.abortNoBatch();
2287
- }
2272
+ try { unlinkSync(abortSignalFile); } catch {}
2273
+ return ORCH_MESSAGES.abortNoBatch();
2288
2274
  }
2289
2275
 
2290
2276
  const batchId = orchBatchState.batchId || persistedState?.batchId || "unknown";
2277
+ const gracePeriodMs = Math.max(0, orchConfig.failure.abort_grace_period * 1000);
2278
+ const pollIntervalMs = Math.max(250, orchConfig.monitoring.poll_interval * 1000);
2291
2279
 
2292
- // Step 5: Kill sessions
2293
- let allSessionNames: string[] = [];
2294
- try {
2295
- const tmuxOutput = execSync('tmux list-sessions -F "#{session_name}"', {
2296
- encoding: "utf-8",
2297
- timeout: 5000,
2298
- }).trim();
2299
- const all = tmuxOutput ? tmuxOutput.split("\n").map(s => s.trim()).filter(Boolean) : [];
2300
- allSessionNames = all.filter(name => name.startsWith(`${prefix}-`));
2301
- messages.push(` Found ${allSessionNames.length} session(s) matching prefix "${prefix}-"`);
2302
- } catch {
2303
- messages.push(" ⚠ Could not list tmux sessions (tmux not available?)");
2304
- }
2305
-
2306
- if (allSessionNames.length > 0) {
2307
- messages.push(` Killing ${allSessionNames.length} tmux session(s)...`);
2308
- let killed = 0;
2309
- for (const name of allSessionNames) {
2310
- try {
2311
- execSync(`tmux kill-session -t "${name}-worker" 2>/dev/null`, { timeout: 3000 }).toString();
2312
- } catch {}
2313
- try {
2314
- execSync(`tmux kill-session -t "${name}-reviewer" 2>/dev/null`, { timeout: 3000 }).toString();
2315
- } catch {}
2316
- try {
2317
- execSync(`tmux kill-session -t "${name}" 2>/dev/null`, { timeout: 3000 }).toString();
2318
- killed++;
2319
- messages.push(` ✓ Killed: ${name}`);
2320
- } catch {
2321
- messages.push(` · ${name} (already exited)`);
2322
- killed++;
2323
- }
2324
- }
2325
- messages.push(` ✓ ${killed}/${allSessionNames.length} session(s) terminated`);
2326
- } else {
2327
- messages.push(" No tmux sessions to kill");
2328
- }
2280
+ const abortResult = await executeAbort(
2281
+ mode,
2282
+ prefix,
2283
+ stateRoot,
2284
+ orchBatchState,
2285
+ persistedState,
2286
+ gracePeriodMs,
2287
+ pollIntervalMs,
2288
+ );
2329
2289
 
2330
- // Step 6: Clean up batch state
2331
2290
  deactivateSupervisor(pi, supervisorState);
2332
2291
 
2333
2292
  try {
@@ -2339,19 +2298,30 @@ export default function (pi: ExtensionAPI) {
2339
2298
  messages.push(` ⚠ Failed to update in-memory state: ${err instanceof Error ? err.message : String(err)}`);
2340
2299
  }
2341
2300
 
2342
- try {
2343
- deleteBatchState(stateRoot);
2344
- messages.push(" ✓ Batch state file deleted (.pi/batch-state.json)");
2345
- } catch (err) {
2346
- messages.push(` ⚠ Failed to delete batch state file: ${err instanceof Error ? err.message : String(err)}`);
2301
+ messages.push(` Found ${abortResult.sessionsFound} session target(s) matching prefix "${prefix}-"`);
2302
+ if (mode === "graceful") {
2303
+ const forceKilled = Math.max(0, abortResult.sessionsKilled - abortResult.gracefulExits);
2304
+ messages.push(ORCH_MESSAGES.abortGracefulComplete(batchId, abortResult.gracefulExits, forceKilled, Math.round(abortResult.durationMs / 1000)));
2305
+ } else {
2306
+ messages.push(ORCH_MESSAGES.abortHardComplete(batchId, abortResult.sessionsKilled, Math.round(abortResult.durationMs / 1000)));
2307
+ }
2308
+
2309
+ if (!abortResult.stateDeleted) {
2310
+ messages.push(" ⚠ Batch state file was not deleted cleanly");
2311
+ }
2312
+ if (abortResult.errors.length > 0) {
2313
+ messages.push(ORCH_MESSAGES.abortPartialFailure(abortResult.errors.length));
2314
+ for (const err of abortResult.errors) {
2315
+ messages.push(` - ${err.code}: ${err.message}`);
2316
+ }
2347
2317
  }
2348
2318
 
2349
2319
  // Step 7: Clean up abort signal file
2350
2320
  try { unlinkSync(abortSignalFile); } catch {}
2351
2321
 
2352
2322
  messages.push(
2353
- `✅ Abort complete for batch ${batchId}. Sessions killed, state cleaned up.\n` +
2354
- ` Worktrees and branches are preserved for inspection.`,
2323
+ `🏁 Abort (${mode}) complete for batch ${batchId}. ` +
2324
+ `Worktrees and branches are preserved for inspection.`,
2355
2325
  );
2356
2326
 
2357
2327
  return messages.join("\n");
@@ -3091,15 +3061,13 @@ export default function (pi: ExtensionAPI) {
3091
3061
  handler: async (args, ctx) => {
3092
3062
  try {
3093
3063
  const hard = args?.trim() === "--hard";
3094
- const result = doOrchAbort(hard, ctx);
3064
+ const result = await doOrchAbort(hard, ctx);
3095
3065
  ctx.ui.notify(result, "info");
3096
3066
  } catch (err) {
3097
3067
  // Top-level catch: ensure the user ALWAYS sees something
3098
3068
  ctx.ui.notify(
3099
3069
  `❌ Abort failed with error: ${err instanceof Error ? err.message : String(err)}\n` +
3100
- ` Stack: ${err instanceof Error ? err.stack : "N/A"}\n\n` +
3101
- ` Manual cleanup: tmux kill-server (kills ALL tmux sessions)\n` +
3102
- ` Or: tmux kill-session -t <session-name> for each session`,
3070
+ ` Stack: ${err instanceof Error ? err.stack : "N/A"}`,
3103
3071
  "error",
3104
3072
  );
3105
3073
  }
@@ -3185,9 +3153,9 @@ export default function (pi: ExtensionAPI) {
3185
3153
  });
3186
3154
 
3187
3155
  pi.registerCommand("orch-sessions", {
3188
- description: "List active orchestrator TMUX sessions",
3156
+ description: "List active orchestrator sessions",
3189
3157
  handler: async (_args, ctx) => {
3190
- const sessions = listOrchSessions(orchConfig.orchestrator.tmux_prefix, orchBatchState);
3158
+ const sessions = listOrchSessions(orchConfig.orchestrator.sessionPrefix, orchBatchState);
3191
3159
  ctx.ui.notify(formatOrchSessions(sessions), "info");
3192
3160
  },
3193
3161
  });
@@ -3445,13 +3413,13 @@ export default function (pi: ExtensionAPI) {
3445
3413
  name: "orch_abort",
3446
3414
  label: "Abort Batch",
3447
3415
  description:
3448
- "Abort the running batch. Kills tmux sessions, cleans up state. " +
3416
+ "Abort the running batch. Stops Runtime V2 lane + merge agents and cleans up state. " +
3449
3417
  "Use hard=true for immediate kill (no grace period). " +
3450
3418
  "Works even without execution context (safety-critical).",
3451
3419
  promptSnippet: "orch_abort(hard?) — abort the running batch",
3452
3420
  promptGuidelines: [
3453
3421
  "Call orch_abort to stop a running batch.",
3454
- "Default (hard=false) is graceful abort — writes signal file and kills sessions.",
3422
+ "Default (hard=false) is graceful abort — writes signal file and waits for checkpoint exit before forced cleanup.",
3455
3423
  "Set hard=true for immediate termination without grace period.",
3456
3424
  "Use this when a batch is stuck, failing repeatedly, or the operator requests it.",
3457
3425
  "Worktrees and branches are preserved for inspection after abort.",
@@ -3463,7 +3431,7 @@ export default function (pi: ExtensionAPI) {
3463
3431
  }),
3464
3432
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
3465
3433
  try {
3466
- const result = doOrchAbort(params.hard ?? false, ctx);
3434
+ const result = await doOrchAbort(params.hard ?? false, ctx);
3467
3435
  return { content: [{ type: "text" as const, text: result }], details: undefined };
3468
3436
  } catch (err) {
3469
3437
  return {
@@ -3722,12 +3690,12 @@ export default function (pi: ExtensionAPI) {
3722
3690
  // Legacy fallback from lane naming when registry is absent/empty.
3723
3691
  if (ids.size === 0) {
3724
3692
  const orchConfig = execCtx?.orchestratorConfig;
3725
- const tmuxPrefix = orchConfig?.orchestrator?.tmux_prefix ?? "orch";
3693
+ const sessionPrefix = orchConfig?.orchestrator?.sessionPrefix ?? "orch";
3726
3694
  const opId = orchConfig ? resolveOperatorId(orchConfig) : "op";
3727
3695
  for (const lane of state.lanes) {
3728
- ids.add(`${lane.tmuxSessionName}-worker`);
3729
- ids.add(`${lane.tmuxSessionName}-reviewer`);
3730
- ids.add(`${tmuxPrefix}-${opId}-merge-${lane.laneNumber}`);
3696
+ ids.add(`${lane.laneSessionId}-worker`);
3697
+ ids.add(`${lane.laneSessionId}-reviewer`);
3698
+ ids.add(`${sessionPrefix}-${opId}-merge-${lane.laneNumber}`);
3731
3699
  }
3732
3700
  }
3733
3701
 
@@ -3776,21 +3744,16 @@ export default function (pi: ExtensionAPI) {
3776
3744
  return `❌ Unknown session "${to}" in batch ${state.batchId}.\nValid targets: ${examples}${validSessions.size > 5 ? ` (${validSessions.size} total)` : ""}`;
3777
3745
  }
3778
3746
 
3779
- // Guard: ensure the target agent is currently alive.
3780
- // Check process registry first (Runtime V2), fall back to TMUX (legacy).
3747
+ // Guard: ensure the target agent is currently alive via Runtime V2 registry.
3781
3748
  let agentAlive = false;
3782
3749
  try {
3783
3750
  const registry = readRegistrySnapshot(stateRoot, state.batchId);
3784
3751
  if (registry && registry.agents[to]) {
3785
3752
  const manifest = registry.agents[to];
3786
3753
  agentAlive = !isTerminalStatus(manifest.status) && registryIsProcessAlive(manifest.pid);
3787
- } else {
3788
- // No registry entry — fall back to TMUX for legacy batches
3789
- agentAlive = tmuxHasSession(to);
3790
3754
  }
3791
3755
  } catch {
3792
- // Registry read failedfall back to TMUX
3793
- agentAlive = tmuxHasSession(to);
3756
+ // Registry read failuretreat as not alive.
3794
3757
  }
3795
3758
  if (!agentAlive) {
3796
3759
  return `❌ Agent "${to}" is not currently running. Use orch_status() or orch_resume() before sending messages.`;
@@ -4098,7 +4061,7 @@ export default function (pi: ExtensionAPI) {
4098
4061
  const runningTask = laneTasks.find(t => t.status === "running");
4099
4062
  const currentTask = runningTask || laneTasks[laneTasks.length - 1];
4100
4063
 
4101
- lines.push(`### Lane ${laneRec.laneNumber} — ${laneRec.tmuxSessionName}`);
4064
+ lines.push(`### Lane ${laneRec.laneNumber} — ${laneRec.laneSessionId}`);
4102
4065
  lines.push(`**Branch:** ${laneRec.branch}`);
4103
4066
 
4104
4067
  if (currentTask) {
@@ -4142,7 +4105,7 @@ export default function (pi: ExtensionAPI) {
4142
4105
 
4143
4106
  // Read lane-state sidecar
4144
4107
  try {
4145
- const lsPath = join(stateRoot, ".pi", `lane-state-${laneRec.tmuxSessionName}.json`);
4108
+ const lsPath = join(stateRoot, ".pi", `lane-state-${laneRec.laneSessionId}.json`);
4146
4109
  if (existsSync(lsPath)) {
4147
4110
  const ls = JSON.parse(readFileSync(lsPath, "utf-8"));
4148
4111
  const parts: string[] = [];
@@ -4378,8 +4341,8 @@ export default function (pi: ExtensionAPI) {
4378
4341
  name: "list_active_agents",
4379
4342
  label: "List Active Agents",
4380
4343
  description:
4381
- "List all tmux sessions with their role, lane, task, context %, and elapsed time.",
4382
- promptSnippet: "list_active_agents() — show all tmux sessions with role, lane, task, context %, elapsed",
4344
+ "List all active Runtime V2 agents with their role, lane, task, status, and elapsed time.",
4345
+ promptSnippet: "list_active_agents() — show active Runtime V2 agents with role, lane, task, status, elapsed",
4383
4346
  promptGuidelines: [
4384
4347
  "Call list_active_agents to see all running agent sessions.",
4385
4348
  "Shows: session name, role (worker/reviewer/merger/supervisor), lane, task, context %, elapsed.",
@@ -4399,7 +4362,7 @@ export default function (pi: ExtensionAPI) {
4399
4362
  });
4400
4363
 
4401
4364
  /**
4402
- * List all active tmux sessions with agent metadata.
4365
+ * List all active Runtime V2 agents using the persisted registry.
4403
4366
  * @since TP-096
4404
4367
  */
4405
4368
  function doListActiveAgents(ctx: ExtensionContext): string {
@@ -4414,99 +4377,7 @@ export default function (pi: ExtensionAPI) {
4414
4377
  }
4415
4378
  }
4416
4379
 
4417
- // Fall back to TMUX-based discovery for legacy batches
4418
- let sessions: string[] = [];
4419
- try {
4420
- const output = execSync('tmux list-sessions -F "#{session_name}"', {
4421
- encoding: "utf-8",
4422
- timeout: 5000,
4423
- stdio: ["ignore", "pipe", "ignore"],
4424
- }).trim();
4425
- sessions = output ? output.split("\n").map(s => s.trim()).filter(Boolean) : [];
4426
- } catch {
4427
- return "❌ No active agents found (no registry and no tmux sessions).";
4428
- }
4429
-
4430
- if (sessions.length === 0) return "❌ No active agents found.";
4431
-
4432
- // state already loaded above for registry check (may be null)
4433
-
4434
- // Build a map of session name → lane-state data
4435
- const laneStates: Record<string, any> = {};
4436
- try {
4437
- const piDir = join(stateRoot, ".pi");
4438
- if (existsSync(piDir)) {
4439
- const files = readdirSync(piDir).filter(f => f.startsWith("lane-state-") && f.endsWith(".json"));
4440
- for (const file of files) {
4441
- try {
4442
- const data = JSON.parse(readFileSync(join(piDir, file), "utf-8"));
4443
- if (data.prefix) laneStates[data.prefix] = data;
4444
- } catch { continue; }
4445
- }
4446
- }
4447
- } catch { /* .pi dir missing */ }
4448
-
4449
- const lines: string[] = [];
4450
- lines.push(`👥 **Active Agents** (${sessions.length} sessions)\n`);
4451
-
4452
- // Parse each session name to extract role, lane, etc.
4453
- for (const sess of sessions) {
4454
- let role = "unknown";
4455
- let laneNum = "";
4456
- let taskId = "";
4457
- let contextPct = "";
4458
- let elapsed = "";
4459
- let costStr = "";
4460
-
4461
- // Parse session name pattern:
4462
- // Workers/reviewers: orch-{opId}-lane-{N} (or -worker/-reviewer suffix)
4463
- // Mergers: orch-{opId}-merge-{N}
4464
- // Supervisor: pi-supervisor-{...}
4465
- const laneMatch = sess.match(/-lane-(\d+)/);
4466
- const mergeMatch = sess.match(/-merge-(\d+)/);
4467
-
4468
- if (mergeMatch) {
4469
- role = "merger";
4470
- laneNum = mergeMatch[1];
4471
- } else if (laneMatch) {
4472
- if (sess.includes("-reviewer")) {
4473
- role = "reviewer";
4474
- } else {
4475
- role = "worker";
4476
- }
4477
- laneNum = laneMatch[1];
4478
- } else if (sess.includes("supervisor")) {
4479
- role = "supervisor";
4480
- }
4481
-
4482
- // Find matching task and lane-state
4483
- if (state && laneNum) {
4484
- const ln = parseInt(laneNum);
4485
- const task = state.tasks.find(t => t.laneNumber === ln && t.status === "running");
4486
- if (task) taskId = task.taskId;
4487
-
4488
- // Find lane-state prefix (may be the session name or a prefix of it)
4489
- const laneRec = state.lanes.find(l => l.laneNumber === ln);
4490
- const prefix = laneRec?.tmuxSessionName || sess;
4491
- const ls = laneStates[prefix];
4492
- if (ls) {
4493
- if (ls.workerContextPct) contextPct = `${Math.round(ls.workerContextPct)}%`;
4494
- if (ls.workerElapsed) elapsed = `${Math.round(ls.workerElapsed / 1000)}s`;
4495
- if (ls.workerCostUsd) costStr = `$${ls.workerCostUsd.toFixed(3)}`;
4496
- }
4497
- }
4498
-
4499
- const parts: string[] = [`**${sess}**`];
4500
- parts.push(`role: ${role}`);
4501
- if (laneNum) parts.push(`lane: ${laneNum}`);
4502
- if (taskId) parts.push(`task: ${taskId}`);
4503
- if (contextPct) parts.push(`ctx: ${contextPct}`);
4504
- if (elapsed) parts.push(`elapsed: ${elapsed}`);
4505
- if (costStr) parts.push(`cost: ${costStr}`);
4506
- lines.push(`- ${parts.join(" · ")}`);
4507
- }
4508
-
4509
- return lines.join("\n");
4380
+ return "❌ No active agents found (Runtime V2 registry is empty).";
4510
4381
  }
4511
4382
 
4512
4383
 
@@ -4744,14 +4615,14 @@ export default function (pi: ExtensionAPI) {
4744
4615
  ctx.ui.notify(
4745
4616
  "Task Orchestrator ready\n\n" +
4746
4617
  `Mode: ${modeLabel}\n` +
4618
+ `Runtime: V2 default (configured spawn_mode: ${orchConfig.orchestrator.spawn_mode})\n` +
4747
4619
  `Config: ${orchConfig.orchestrator.max_lanes} lanes, ` +
4748
- `${orchConfig.orchestrator.spawn_mode} mode, ` +
4749
4620
  `${orchConfig.dependencies.source} deps\n` +
4750
4621
  `Areas: ${areaCount} registered\n\n` +
4751
4622
  "/orch <areas|all> Start batch execution\n" +
4752
4623
  "/orch-plan <areas|all> Preview execution plan\n" +
4753
4624
  "/orch-deps <areas|all> Show dependency graph\n" +
4754
- "/orch-sessions List TMUX sessions\n" +
4625
+ "/orch-sessions List orchestrator sessions\n" +
4755
4626
  "/orch-takeover Force supervisor takeover\n" +
4756
4627
  "/orch-integrate Integrate orch branch into working branch",
4757
4628
  "info",
@@ -401,7 +401,7 @@ export function buildDashboardViewModel(
401
401
  laneCards.push({
402
402
  laneNumber: lane.laneNumber,
403
403
  laneId: lane.laneId,
404
- sessionName: lane.tmuxSessionName,
404
+ sessionName: lane.laneSessionId,
405
405
  sessionAlive: true, // assumed alive during allocation
406
406
  currentTaskId: lane.tasks.length > 0 ? lane.tasks[0].taskId : null,
407
407
  currentStepName: null,
@@ -419,9 +419,9 @@ export function buildDashboardViewModel(
419
419
  let attachHint = "";
420
420
  const aliveLane = laneCards.find(l => l.sessionAlive && l.status === "running");
421
421
  if (aliveLane) {
422
- attachHint = `tmux attach -t ${aliveLane.sessionName}`;
422
+ attachHint = `Use /orch-sessions to inspect active lane sessions (${aliveLane.sessionName})`;
423
423
  } else if (laneCards.length > 0) {
424
- attachHint = "/orch-sessions for session list";
424
+ attachHint = "Use /orch-sessions for active lane session list";
425
425
  }
426
426
 
427
427
  // Determine failure policy if batch was stopped
@@ -545,12 +545,12 @@ export function renderLaneCard(card: OrchLaneCardData, colWidth: number, theme:
545
545
  *
546
546
  * @param getBatchState - Getter for current batch state
547
547
  * @param getMonitorState - Getter for current monitor state (may be null)
548
- * @param tmuxPrefix - TMUX session prefix for attach hints
548
+ * @param sessionPrefix - Session prefix for lane identification
549
549
  */
550
550
  export function createOrchWidget(
551
551
  getBatchState: () => OrchBatchRuntimeState,
552
552
  getMonitorState: () => MonitorState | null,
553
- tmuxPrefix: string,
553
+ sessionPrefix: string,
554
554
  ): (_tui: any, theme: any) => { render(width: number): string[]; invalidate(): void } {
555
555
  return (_tui: any, theme: any) => {
556
556
  return {