sisyphi 1.0.14 → 1.1.7

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 (100) hide show
  1. package/dist/{chunk-Q6VQOUN3.js → chunk-M7LZ2ZHD.js} +3 -27
  2. package/dist/chunk-M7LZ2ZHD.js.map +1 -0
  3. package/dist/{chunk-YGBGKMTF.js → chunk-REUQ4B45.js} +7 -11
  4. package/dist/chunk-REUQ4B45.js.map +1 -0
  5. package/dist/{chunk-MMA43N67.js → chunk-Z32YVDMY.js} +2 -2
  6. package/dist/chunk-Z32YVDMY.js.map +1 -0
  7. package/dist/cli.js +46 -49
  8. package/dist/cli.js.map +1 -1
  9. package/dist/daemon.js +795 -796
  10. package/dist/daemon.js.map +1 -1
  11. package/dist/{paths-FYYSBD27.js → paths-IJXOAN4E.js} +4 -6
  12. package/dist/templates/CLAUDE.md +16 -14
  13. package/dist/templates/agent-plugin/agents/CLAUDE.md +17 -6
  14. package/dist/templates/agent-plugin/agents/design.md +134 -0
  15. package/dist/templates/agent-plugin/agents/explore.md +39 -0
  16. package/dist/templates/agent-plugin/agents/operator.md +24 -0
  17. package/dist/templates/agent-plugin/agents/plan.md +15 -20
  18. package/dist/templates/agent-plugin/agents/problem.md +119 -0
  19. package/dist/templates/agent-plugin/agents/requirements.md +138 -0
  20. package/dist/templates/agent-plugin/agents/review/CLAUDE.md +29 -0
  21. package/dist/templates/agent-plugin/agents/review/compliance.md +6 -6
  22. package/dist/templates/agent-plugin/agents/review-plan/code-smells.md +4 -4
  23. package/dist/templates/agent-plugin/agents/review-plan/requirements-coverage.md +62 -0
  24. package/dist/templates/agent-plugin/agents/review-plan/security.md +1 -1
  25. package/dist/templates/agent-plugin/agents/review-plan.md +9 -8
  26. package/dist/templates/agent-plugin/agents/review.md +1 -1
  27. package/dist/templates/agent-plugin/agents/test-spec.md +2 -2
  28. package/dist/templates/agent-plugin/hooks/CLAUDE.md +2 -2
  29. package/dist/templates/agent-plugin/hooks/explore-user-prompt.sh +13 -0
  30. package/dist/templates/agent-plugin/hooks/plan-user-prompt.sh +1 -1
  31. package/dist/templates/agent-plugin/hooks/require-submit.sh +69 -2
  32. package/dist/templates/agent-plugin/hooks/review-plan-user-prompt.sh +4 -4
  33. package/dist/templates/agent-plugin/hooks/review-user-prompt.sh +1 -1
  34. package/dist/templates/agent-suffix.md +0 -2
  35. package/dist/templates/orchestrator-base.md +167 -145
  36. package/dist/templates/orchestrator-impl.md +92 -57
  37. package/dist/templates/orchestrator-planning.md +46 -56
  38. package/dist/templates/orchestrator-plugin/commands/sisyphus/design.md +13 -0
  39. package/dist/templates/orchestrator-plugin/commands/sisyphus/problem.md +13 -0
  40. package/dist/templates/orchestrator-plugin/commands/sisyphus/requirements.md +13 -0
  41. package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +19 -0
  42. package/dist/templates/orchestrator-plugin/hooks/explore-gate.sh +15 -0
  43. package/dist/templates/orchestrator-plugin/hooks/hooks.json +14 -1
  44. package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +34 -27
  45. package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +56 -24
  46. package/dist/templates/orchestrator-strategy.md +233 -0
  47. package/dist/templates/orchestrator-validation.md +94 -0
  48. package/dist/tui.js +193 -120
  49. package/dist/tui.js.map +1 -1
  50. package/package.json +2 -2
  51. package/templates/CLAUDE.md +16 -14
  52. package/templates/agent-plugin/agents/CLAUDE.md +17 -6
  53. package/templates/agent-plugin/agents/design.md +134 -0
  54. package/templates/agent-plugin/agents/explore.md +39 -0
  55. package/templates/agent-plugin/agents/operator.md +24 -0
  56. package/templates/agent-plugin/agents/plan.md +15 -20
  57. package/templates/agent-plugin/agents/problem.md +119 -0
  58. package/templates/agent-plugin/agents/requirements.md +138 -0
  59. package/templates/agent-plugin/agents/review/CLAUDE.md +29 -0
  60. package/templates/agent-plugin/agents/review/compliance.md +6 -6
  61. package/templates/agent-plugin/agents/review-plan/code-smells.md +4 -4
  62. package/templates/agent-plugin/agents/review-plan/requirements-coverage.md +62 -0
  63. package/templates/agent-plugin/agents/review-plan/security.md +1 -1
  64. package/templates/agent-plugin/agents/review-plan.md +9 -8
  65. package/templates/agent-plugin/agents/review.md +1 -1
  66. package/templates/agent-plugin/agents/test-spec.md +2 -2
  67. package/templates/agent-plugin/hooks/CLAUDE.md +2 -2
  68. package/templates/agent-plugin/hooks/explore-user-prompt.sh +13 -0
  69. package/templates/agent-plugin/hooks/plan-user-prompt.sh +1 -1
  70. package/templates/agent-plugin/hooks/require-submit.sh +69 -2
  71. package/templates/agent-plugin/hooks/review-plan-user-prompt.sh +4 -4
  72. package/templates/agent-plugin/hooks/review-user-prompt.sh +1 -1
  73. package/templates/agent-suffix.md +0 -2
  74. package/templates/orchestrator-base.md +167 -145
  75. package/templates/orchestrator-impl.md +92 -57
  76. package/templates/orchestrator-planning.md +46 -56
  77. package/templates/orchestrator-plugin/commands/sisyphus/design.md +13 -0
  78. package/templates/orchestrator-plugin/commands/sisyphus/problem.md +13 -0
  79. package/templates/orchestrator-plugin/commands/sisyphus/requirements.md +13 -0
  80. package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +19 -0
  81. package/templates/orchestrator-plugin/hooks/explore-gate.sh +15 -0
  82. package/templates/orchestrator-plugin/hooks/hooks.json +14 -1
  83. package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +34 -27
  84. package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +56 -24
  85. package/templates/orchestrator-strategy.md +233 -0
  86. package/templates/orchestrator-validation.md +94 -0
  87. package/dist/chunk-MMA43N67.js.map +0 -1
  88. package/dist/chunk-Q6VQOUN3.js.map +0 -1
  89. package/dist/chunk-YGBGKMTF.js.map +0 -1
  90. package/dist/templates/agent-plugin/agents/review-plan/spec-coverage.md +0 -44
  91. package/dist/templates/agent-plugin/agents/spec-draft.md +0 -78
  92. package/dist/templates/agent-plugin/hooks/hooks.json +0 -25
  93. package/dist/templates/agent-plugin/hooks/spec-user-prompt.sh +0 -19
  94. package/dist/templates/orchestrator-plugin/skills/git-management/SKILL.md +0 -111
  95. package/templates/agent-plugin/agents/review-plan/spec-coverage.md +0 -44
  96. package/templates/agent-plugin/agents/spec-draft.md +0 -78
  97. package/templates/agent-plugin/hooks/hooks.json +0 -25
  98. package/templates/agent-plugin/hooks/spec-user-prompt.sh +0 -19
  99. package/templates/orchestrator-plugin/skills/git-management/SKILL.md +0 -111
  100. /package/dist/{paths-FYYSBD27.js.map → paths-IJXOAN4E.js.map} +0 -0
package/dist/tui.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  exec,
6
6
  execSafe,
7
7
  loadConfig
8
- } from "./chunk-MMA43N67.js";
8
+ } from "./chunk-Z32YVDMY.js";
9
9
  import {
10
10
  buildSessionContext,
11
11
  computeActiveTimeMs,
@@ -13,7 +13,7 @@ import {
13
13
  rawSend,
14
14
  resolveReports,
15
15
  statusColor
16
- } from "./chunk-Q6VQOUN3.js";
16
+ } from "./chunk-M7LZ2ZHD.js";
17
17
  import {
18
18
  shellQuote
19
19
  } from "./chunk-6G226ZK7.js";
@@ -23,8 +23,9 @@ import {
23
23
  goalPath,
24
24
  logsDir,
25
25
  roadmapPath,
26
- sessionDir
27
- } from "./chunk-YGBGKMTF.js";
26
+ sessionDir,
27
+ strategyPath
28
+ } from "./chunk-REUQ4B45.js";
28
29
 
29
30
  // src/tui/terminal.ts
30
31
  function emptyKey() {
@@ -311,6 +312,7 @@ function createAppState(cwd2) {
311
312
  notification: null,
312
313
  notificationTimer: null,
313
314
  showLogs: false,
315
+ showStrategy: false,
314
316
  inputText: "",
315
317
  inputCursorPos: 0,
316
318
  detailScroll,
@@ -318,6 +320,7 @@ function createAppState(cwd2) {
318
320
  sessions: [],
319
321
  selectedSession: null,
320
322
  planContent: "",
323
+ strategyContent: "",
321
324
  goalContent: "",
322
325
  logsContent: "",
323
326
  logsCycles: [],
@@ -393,7 +396,7 @@ function autoExpandCycle(state2) {
393
396
  }
394
397
 
395
398
  // src/tui/app.ts
396
- import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync } from "fs";
399
+ import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync, statSync } from "fs";
397
400
  import { join as join3 } from "path";
398
401
 
399
402
  // src/tui/lib/tree.ts
@@ -544,6 +547,8 @@ function abbreviateMode(mode) {
544
547
  if (!mode) return "";
545
548
  if (mode === "implementation") return "impl";
546
549
  if (mode === "planning") return "plan";
550
+ if (mode === "strategy") return "strat";
551
+ if (mode === "validation") return "valid";
547
552
  return mode;
548
553
  }
549
554
  function ansiBold(text) {
@@ -564,22 +569,10 @@ function ansiColor(text, color, bold = false) {
564
569
  function modeColor(mode) {
565
570
  if (mode === "planning") return "blue";
566
571
  if (mode === "implementation") return "green";
572
+ if (mode === "strategy") return "yellow";
573
+ if (mode === "validation") return "magenta";
567
574
  return "cyan";
568
575
  }
569
- function mergeStatusDisplay(status) {
570
- switch (status) {
571
- case "merged":
572
- return { icon: "\u2295", label: "merged", color: "green" };
573
- case "pending":
574
- return { icon: "\u25CC", label: "pending", color: "yellow" };
575
- case "no-changes":
576
- return { icon: "\u2205", label: "no changes", color: "gray" };
577
- case "conflict":
578
- return { icon: "\u26A0", label: "conflict", color: "red" };
579
- default:
580
- return null;
581
- }
582
- }
583
576
  function wrapText(text, width) {
584
577
  const cleaned = cleanMarkdown(text);
585
578
  if (width <= 0) return cleaned.split("\n");
@@ -589,30 +582,32 @@ function wrapText(text, width) {
589
582
  result.push(rawLine);
590
583
  continue;
591
584
  }
592
- let remaining = rawLine;
593
- while (stringWidth(remaining) > width) {
594
- let breakAt = -1;
595
- let estimate = Math.min(remaining.length, width);
596
- for (let i = estimate; i >= 0; i--) {
597
- if (remaining[i] === " " && stringWidth(remaining.slice(0, i)) <= width) {
598
- breakAt = i;
599
- break;
600
- }
601
- }
602
- if (breakAt <= 0) {
603
- breakAt = remaining.length;
604
- for (let i = 1; i <= remaining.length; i++) {
605
- if (stringWidth(remaining.slice(0, i)) > width) {
606
- breakAt = i - 1;
607
- break;
608
- }
585
+ let lineStart = 0;
586
+ let lastSpace = -1;
587
+ let displayWidth = 0;
588
+ for (let i = 0; i < rawLine.length; i++) {
589
+ const charWidth = stringWidth(rawLine[i]);
590
+ displayWidth += charWidth;
591
+ if (rawLine[i] === " ") lastSpace = i;
592
+ if (displayWidth > width) {
593
+ let breakAt;
594
+ if (lastSpace > lineStart) {
595
+ breakAt = lastSpace;
596
+ result.push(rawLine.slice(lineStart, breakAt));
597
+ lineStart = breakAt + 1;
598
+ while (lineStart < rawLine.length && rawLine[lineStart] === " ") lineStart++;
599
+ } else {
600
+ breakAt = Math.max(lineStart + 1, i);
601
+ result.push(rawLine.slice(lineStart, breakAt));
602
+ lineStart = breakAt;
609
603
  }
610
- if (breakAt <= 0) breakAt = 1;
604
+ displayWidth = stringWidth(rawLine.slice(lineStart, i + 1));
605
+ lastSpace = -1;
611
606
  }
612
- result.push(remaining.slice(0, breakAt));
613
- remaining = remaining.slice(breakAt).trimStart();
614
607
  }
615
- if (remaining) result.push(remaining);
608
+ if (lineStart < rawLine.length) {
609
+ result.push(rawLine.slice(lineStart));
610
+ }
616
611
  }
617
612
  return result;
618
613
  }
@@ -674,6 +669,7 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
674
669
  cycleNumber: cycle.cycle,
675
670
  timestamp: cycle.timestamp,
676
671
  completedAt: cycle.completedAt,
672
+ activeMs: cycle.activeMs,
677
673
  agentCount: allCycleAgents.length,
678
674
  mode: cycle.mode
679
675
  });
@@ -695,8 +691,8 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
695
691
  status: agent.status,
696
692
  spawnedAt: agent.spawnedAt,
697
693
  completedAt: agent.completedAt,
698
- reportCount: agent.reports.length,
699
- mergeStatus: agent.mergeStatus
694
+ activeMs: agent.activeMs,
695
+ reportCount: agent.reports.length
700
696
  });
701
697
  if (!agentExpanded || !hasReports) continue;
702
698
  for (let ri = 0; ri < agent.reports.length; ri++) {
@@ -1410,6 +1406,30 @@ function handleNavigateKey(input, key, state2, actions) {
1410
1406
  })();
1411
1407
  return;
1412
1408
  }
1409
+ if (input === "o") {
1410
+ if (!cursorNode) {
1411
+ notify(state2, "No node selected");
1412
+ return;
1413
+ }
1414
+ let claudeSessionId;
1415
+ if (cursorNode.type === "agent" || cursorNode.type === "report") {
1416
+ const agent = actions.getAgentForNode(cursorNode);
1417
+ claudeSessionId = agent?.claudeSessionId ?? void 0;
1418
+ } else if (cursorNode.type === "cycle" && session) {
1419
+ const cycle = session.orchestratorCycles.find((c) => c.cycle === cursorNode.cycleNumber);
1420
+ claudeSessionId = cycle?.claudeSessionId;
1421
+ }
1422
+ if (!claudeSessionId) {
1423
+ notify(state2, "No Claude session ID available");
1424
+ return;
1425
+ }
1426
+ try {
1427
+ actions.openClaudeResumePopup(state2.cwd, claudeSessionId);
1428
+ } catch {
1429
+ notify(state2, "Failed to open Claude session");
1430
+ }
1431
+ return;
1432
+ }
1413
1433
  if (input === "g") {
1414
1434
  if (!state2.selectedSessionId) {
1415
1435
  notify(state2, "No session selected");
@@ -1551,6 +1571,29 @@ function handleNavigateKey(input, key, state2, actions) {
1551
1571
  }
1552
1572
  return;
1553
1573
  }
1574
+ if (input === "s") {
1575
+ if (!state2.strategyContent) {
1576
+ notify(state2, "No strategy for this session");
1577
+ return;
1578
+ }
1579
+ state2.showStrategy = !state2.showStrategy;
1580
+ requestRender();
1581
+ return;
1582
+ }
1583
+ if (input === "S") {
1584
+ if (!state2.selectedSessionId) {
1585
+ notify(state2, "No session selected");
1586
+ return;
1587
+ }
1588
+ const sp = strategyPath(state2.cwd, state2.selectedSessionId);
1589
+ const editor = actions.resolveEditor();
1590
+ try {
1591
+ actions.openEditorPopup(state2.cwd, editor, sp);
1592
+ } catch {
1593
+ notify(state2, `Failed to open strategy in ${editor}`);
1594
+ }
1595
+ return;
1596
+ }
1554
1597
  if (input === "t") {
1555
1598
  if (state2.showLogs) {
1556
1599
  if (state2.focusPane === "logs") state2.focusPane = "detail";
@@ -1831,6 +1874,14 @@ function openShellPopup(cwd2, command) {
1831
1874
  function openInFileManager(path) {
1832
1875
  execSync(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
1833
1876
  }
1877
+ function openClaudeResumePopup(cwd2, claudeSessionId) {
1878
+ const pathEnv = augmentedPath();
1879
+ const cmd = `PATH=${shellQuote(pathEnv)} claude --resume ${shellQuote(claudeSessionId)}`;
1880
+ execSync(
1881
+ `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(cmd)}`,
1882
+ { stdio: "inherit", env: EXEC_ENV }
1883
+ );
1884
+ }
1834
1885
  function openEditorPopup(cwd2, editor, filePath, size) {
1835
1886
  const { w = "90%", h = "90%" } = size ?? {};
1836
1887
  const editorBin = editor.split(/\s+/)[0].split("/").pop();
@@ -1902,7 +1953,7 @@ function renderNodeContent(node, maxWidth) {
1902
1953
  }
1903
1954
  case "cycle": {
1904
1955
  const isRunning = !node.completedAt;
1905
- const dur = node.completedAt ? formatDuration(node.timestamp, node.completedAt) : "running";
1956
+ const dur = isRunning ? "running" : formatDuration(node.activeMs);
1906
1957
  const agents = `${node.agentCount} agent${node.agentCount !== 1 ? "s" : ""}`;
1907
1958
  const modeShort = abbreviateMode(node.mode);
1908
1959
  const mode = modeShort ? ` \xB7 ${modeShort}` : "";
@@ -1917,34 +1968,22 @@ function renderNodeContent(node, maxWidth) {
1917
1968
  case "agent": {
1918
1969
  const icon = agentStatusIcon(node.status);
1919
1970
  const color = statusColor(node.status);
1920
- const dur = formatDuration(node.spawnedAt, node.completedAt);
1921
- const durClr = durationColor(node.spawnedAt, node.completedAt) || void 0;
1971
+ const dur = formatDuration(node.activeMs);
1972
+ const durClr = durationColor(node.activeMs) || void 0;
1922
1973
  const dim = node.status === "completed";
1923
1974
  const displayName = agentDisplayName({
1924
1975
  name: node.name,
1925
1976
  id: node.agentId,
1926
1977
  agentType: node.agentType
1927
1978
  });
1928
- let suffix;
1929
- let suffixColor;
1930
- if (node.mergeStatus) {
1931
- const ms = node.mergeStatus === "pending" ? { icon: "\u2387", color: "yellow" } : mergeStatusDisplay(node.mergeStatus);
1932
- if (ms) {
1933
- suffix = ms.icon;
1934
- suffixColor = ms.color;
1935
- }
1936
- }
1937
- const suffixLen = suffix ? 2 : 0;
1938
- const maxLabel = Math.max(8, maxWidth - dur.length - suffixLen - 4);
1979
+ const maxLabel = Math.max(8, maxWidth - dur.length - 4);
1939
1980
  return {
1940
1981
  icon,
1941
1982
  label: truncate(displayName, maxLabel),
1942
1983
  meta: dur,
1943
1984
  color,
1944
1985
  dim,
1945
- metaColor: durClr,
1946
- suffix,
1947
- suffixColor
1986
+ metaColor: durClr
1948
1987
  };
1949
1988
  }
1950
1989
  case "report": {
@@ -2148,14 +2187,13 @@ function buildPlanLines(content, maxLines, width) {
2148
2187
  }
2149
2188
  return lines;
2150
2189
  }
2151
- function buildSessionLines(session, planContent, goalContent, width, paneAlive) {
2190
+ function buildSessionLines(session, planContent, goalContent, width, paneAlive, strategyContent = "", showStrategy = false) {
2152
2191
  const lines = [];
2153
2192
  const contentWidth = width - 4;
2154
2193
  const agents = session.agents;
2155
2194
  const cycles = session.orchestratorCycles;
2156
2195
  const messages = session.messages;
2157
2196
  const isDead = session.status === "active" && !paneAlive;
2158
- const conflicts = agents.filter((a) => a.mergeStatus === "conflict");
2159
2197
  const goalText = goalContent ? cleanMarkdown(stripFrontmatter(goalContent).trim()) : session.task;
2160
2198
  goalText.split("\n").flatMap((l) => wrapText(l, contentWidth - 2)).forEach((line, i) => {
2161
2199
  lines.push(singleLine(`${i === 0 ? " " : " "}${line}`, { bold: true }));
@@ -2189,28 +2227,34 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive)
2189
2227
  seg(" tmux window closed \u2014 [w] reopen [R] resume", { color: "red" })
2190
2228
  ]);
2191
2229
  }
2192
- if (conflicts.length > 0) {
2193
- lines.push(
2194
- singleLine(
2195
- ` \u26A0 ${conflicts.length} merge conflict${conflicts.length > 1 ? "s" : ""}`,
2196
- { color: "red", bold: true }
2197
- )
2198
- );
2199
- lines.push(
2200
- singleLine(
2201
- " resolve in worktree, then [x] restart agent",
2202
- { color: "red", dim: true }
2203
- )
2204
- );
2205
- }
2206
2230
  lines.push(singleLine(" "));
2207
- lines.push([seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true })]);
2208
- const planLines = buildPlanLines(planContent, 99999, width);
2209
- if (planLines.length === 0) {
2210
- lines.push(singleLine(" orchestrator will create one", { dim: true, italic: true }));
2231
+ if (showStrategy && strategyContent) {
2232
+ const stratHint = strategyContent ? " [s] toggle plan" : "";
2233
+ lines.push([
2234
+ seg(" \u258E \u25C8 STRATEGY", { color: "yellow", bold: true }),
2235
+ seg(stratHint, { dim: true })
2236
+ ]);
2237
+ const stratLines = buildPlanLines(strategyContent, 99999, width);
2238
+ if (stratLines.length === 0) {
2239
+ lines.push(singleLine(" (empty)", { dim: true, italic: true }));
2240
+ } else {
2241
+ for (const pl of stratLines) {
2242
+ lines.push(singleLine(pl.text, { bold: pl.bold, dim: pl.dim, color: pl.color }));
2243
+ }
2244
+ }
2211
2245
  } else {
2212
- for (const pl of planLines) {
2213
- lines.push(singleLine(pl.text, { bold: pl.bold, dim: pl.dim, color: pl.color }));
2246
+ const toggleHint = strategyContent ? " [s] toggle strategy" : "";
2247
+ lines.push([
2248
+ seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true }),
2249
+ seg(toggleHint, { dim: true })
2250
+ ]);
2251
+ const planLines = buildPlanLines(planContent, 99999, width);
2252
+ if (planLines.length === 0) {
2253
+ lines.push(singleLine(" orchestrator will create one", { dim: true, italic: true }));
2254
+ } else {
2255
+ for (const pl of planLines) {
2256
+ lines.push(singleLine(pl.text, { bold: pl.bold, dim: pl.dim, color: pl.color }));
2257
+ }
2214
2258
  }
2215
2259
  }
2216
2260
  if (session.status === "completed" && session.completionReport) {
@@ -2258,7 +2302,7 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive)
2258
2302
  const dot = isRunning ? "\u25CF" : "\u25CB";
2259
2303
  const dotColor = isRunning ? "green" : isNewest ? "white" : "gray";
2260
2304
  const rowDim = !isRunning && !isNewest && !isSecond;
2261
- const duration = isRunning ? "running" : formatDuration(cycle.timestamp, cycle.completedAt);
2305
+ const duration = isRunning ? "running" : formatDuration(cycle.activeMs);
2262
2306
  const n = cycle.agentsSpawned.length;
2263
2307
  const startTime = formatTime(cycle.timestamp);
2264
2308
  const modeLabel = abbreviateMode(cycle.mode);
@@ -2314,7 +2358,7 @@ function buildCycleLines(cycle, agents, width) {
2314
2358
  const lines = [];
2315
2359
  const contentWidth = width - 4;
2316
2360
  const isRunning = !cycle.completedAt;
2317
- const dur = cycle.completedAt ? formatDuration(cycle.timestamp, cycle.completedAt) : "running";
2361
+ const dur = isRunning ? "running" : formatDuration(cycle.activeMs);
2318
2362
  const cycleAgents = agents.filter((a) => cycle.agentsSpawned.includes(a.id));
2319
2363
  lines.push(singleLine(` Cycle ${cycle.cycle}`, { bold: true }));
2320
2364
  lines.push([
@@ -2327,6 +2371,9 @@ function buildCycleLines(cycle, agents, width) {
2327
2371
  ` ${formatTime(cycle.timestamp)}${cycle.completedAt ? ` \u2192 ${formatTime(cycle.completedAt)}` : ""}`,
2328
2372
  { dim: true }
2329
2373
  ));
2374
+ if (cycle.claudeSessionId) {
2375
+ lines.push(singleLine(` Session: ${cycle.claudeSessionId}`, { dim: true }));
2376
+ }
2330
2377
  lines.push(singleLine(" "));
2331
2378
  lines.push([seg(" \u258E \u229E AGENTS", { color: "green", bold: true })]);
2332
2379
  if (cycleAgents.length === 0) {
@@ -2337,8 +2384,8 @@ function buildCycleLines(cycle, agents, width) {
2337
2384
  const instrPreview = agent.instruction.split("\n")[0];
2338
2385
  const latestReport = agent.reports.length > 0 ? agent.reports[agent.reports.length - 1] : null;
2339
2386
  const reportSummary = latestReport !== null && agent.status === "completed" ? extractFirstSentence(latestReport.summary, contentWidth - 14) : null;
2340
- const agentDur = formatDuration(agent.spawnedAt, agent.completedAt);
2341
- const durClrRaw = durationColor(agent.spawnedAt, agent.completedAt);
2387
+ const agentDur = formatDuration(agent.activeMs);
2388
+ const durClrRaw = durationColor(agent.activeMs);
2342
2389
  const durClr = durClrRaw !== "" ? durClrRaw : void 0;
2343
2390
  const typeClrRaw = agentTypeColor(agent.agentType);
2344
2391
  const typeClr = typeClrRaw !== void 0 ? typeClrRaw : void 0;
@@ -2377,34 +2424,23 @@ function buildCycleLines(cycle, agents, width) {
2377
2424
  function buildAgentLines(agent, reportBlocks, width) {
2378
2425
  const lines = [];
2379
2426
  const contentWidth = width - 4;
2380
- const dur = formatDuration(agent.spawnedAt, agent.completedAt);
2427
+ const dur = formatDuration(agent.activeMs);
2381
2428
  const icon = agentStatusIcon(agent.status);
2382
2429
  const color = statusColor(agent.status);
2383
2430
  const nameLabel = agentDisplayName(agent);
2384
- const maxMergeLines = 3;
2385
2431
  lines.push([
2386
2432
  seg(" "),
2387
2433
  seg(icon, { color }),
2388
2434
  seg(` ${agent.id} \xB7 ${nameLabel}`, { bold: true })
2389
2435
  ]);
2390
- const merge = agent.mergeStatus ? mergeStatusDisplay(agent.mergeStatus) : null;
2391
2436
  lines.push([
2392
2437
  seg(" "),
2393
2438
  seg(agent.status, { color }),
2394
- seg(` \xB7 ${dur} \xB7 ${agent.agentType}`, { dim: true }),
2395
- ...merge ? [seg(" \xB7 ", { dim: true }), seg(`${merge.icon} ${merge.label}`, { color: merge.color })] : agent.mergeStatus ? [seg(" \xB7 ", { dim: true }), seg(agent.mergeStatus, { dim: true })] : []
2439
+ seg(` \xB7 ${dur} \xB7 ${agent.agentType}`, { dim: true })
2396
2440
  ]);
2397
2441
  if (agent.killedReason) {
2398
2442
  lines.push(singleLine(` \u26A0 ${agent.killedReason}`, { color: "red" }));
2399
2443
  }
2400
- if (agent.mergeStatus === "conflict" && agent.mergeDetails) {
2401
- for (const ml of wrapText(agent.mergeDetails, contentWidth - 6).slice(0, maxMergeLines)) {
2402
- lines.push(singleLine(` \u26A0 ${ml}`, { color: "red" }));
2403
- }
2404
- }
2405
- if (agent.mergeStatus === "conflict") {
2406
- lines.push(singleLine(" resolve conflicts in worktree dir, then restart", { color: "red", dim: true }));
2407
- }
2408
2444
  lines.push(singleLine(" "));
2409
2445
  lines.push(singleLine(" \u258E \u25B7 INSTRUCTION", { color: "white", bold: true }));
2410
2446
  for (const wl of wrapText(agent.instruction, contentWidth - 6)) {
@@ -2445,21 +2481,18 @@ function buildAgentLines(agent, reportBlocks, width) {
2445
2481
  if (agent.completedAt) {
2446
2482
  lines.push(singleLine(` Completed: ${formatTime(agent.completedAt)}`, { dim: true }));
2447
2483
  }
2484
+ if (agent.claudeSessionId) {
2485
+ lines.push(singleLine(` Session: ${agent.claudeSessionId}`, { dim: true }));
2486
+ }
2448
2487
  if (agent.paneId) {
2449
2488
  lines.push(singleLine(` Pane: ${agent.paneId}`, { dim: true }));
2450
2489
  }
2451
- if (agent.worktreePath) {
2452
- lines.push(singleLine(` Worktree: ${agent.worktreePath}`, { dim: true }));
2453
- }
2454
- if (agent.branchName) {
2455
- lines.push(singleLine(` Branch: ${agent.branchName}`, { dim: true }));
2456
- }
2457
2490
  return lines;
2458
2491
  }
2459
2492
  function buildReportViewLines(agent, reportBlocks, width) {
2460
2493
  const lines = [];
2461
2494
  const contentWidth = width - 6;
2462
- const dur = formatDuration(agent.spawnedAt, agent.completedAt);
2495
+ const dur = formatDuration(agent.activeMs);
2463
2496
  const icon = agentStatusIcon(agent.status);
2464
2497
  const color = statusColor(agent.status);
2465
2498
  const totalReports = agent.reports.length;
@@ -2552,14 +2585,14 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
2552
2585
  let borderColor = "gray";
2553
2586
  switch (cursorNode.type) {
2554
2587
  case "session": {
2555
- lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive);
2588
+ lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
2556
2589
  break;
2557
2590
  }
2558
2591
  case "cycle": {
2559
2592
  const cycleNode = cursorNode;
2560
2593
  const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
2561
2594
  if (!cycle) {
2562
- lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive);
2595
+ lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
2563
2596
  } else {
2564
2597
  lines = buildCycleLines(cycle, session.agents, rect.w);
2565
2598
  }
@@ -2569,7 +2602,7 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
2569
2602
  const agentNode = cursorNode;
2570
2603
  const agent = agents.find((a) => a.id === agentNode.agentId);
2571
2604
  if (!agent) {
2572
- lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive);
2605
+ lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
2573
2606
  } else {
2574
2607
  lines = buildAgentLines(agent, detailReportBlocks, rect.w);
2575
2608
  }
@@ -2579,7 +2612,7 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
2579
2612
  const reportNode = cursorNode;
2580
2613
  const agent = agents.find((a) => a.id === reportNode.agentId);
2581
2614
  if (!agent) {
2582
- lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive);
2615
+ lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
2583
2616
  break;
2584
2617
  }
2585
2618
  const reportIdx = reportNode.reportIndex;
@@ -2671,7 +2704,7 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
2671
2704
  break;
2672
2705
  }
2673
2706
  default: {
2674
- lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive);
2707
+ lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
2675
2708
  break;
2676
2709
  }
2677
2710
  }
@@ -2744,7 +2777,7 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
2744
2777
  } else if (mode !== "navigate") {
2745
2778
  content = D("[enter] send [esc] cancel");
2746
2779
  } else if (focusPane === "logs" || focusPane === "detail") {
2747
- content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle logs ") + SEP + B("[m]") + D("sg ") + B("[g]") + D("oal ") + B("[n]") + D("ew ") + B("[p]") + D("lan ") + B("[w]") + D("indow ") + B("[R]") + D("esume ") + B("[q]") + D("uit");
2780
+ content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle logs ") + SEP + B("[m]") + D("sg ") + B("[g]") + D("oal ") + B("[n]") + D("ew ") + B("[p]") + D("lan ") + B("[s]") + D("trat ") + B("[w]") + D("indow ") + B("[R]") + D("esume ") + B("[q]") + D("uit");
2748
2781
  } else {
2749
2782
  let contextFilePart = "";
2750
2783
  if (cursorNodeType === "context-file") {
@@ -2821,8 +2854,10 @@ function renderHelpOverlay(buf, rows, cols) {
2821
2854
  helpRow(" R resume session", " C continue session", innerWidth),
2822
2855
  helpRow(" b rollback cycle", " x restart agent", innerWidth),
2823
2856
  helpRow(" r re-run agent", " g edit goal", innerWidth),
2824
- helpRow(" p open roadmap", " w go to window", innerWidth),
2825
- helpRow(" c claude companion", " q quit", innerWidth),
2857
+ helpRow(" p open roadmap", " s toggle strategy", innerWidth),
2858
+ helpRow(" S edit strategy", " w go to window", innerWidth),
2859
+ helpRow(" o resume claude session", " c claude companion", innerWidth),
2860
+ helpRow(" q quit", "", innerWidth),
2826
2861
  " ".padEnd(innerWidth),
2827
2862
  helpRow(" space \u2192 y copy submenu", " space \u2192 d delete session", innerWidth),
2828
2863
  helpRow(" space \u2192 j jump to pane", " space \u2192 k kill", innerWidth),
@@ -2854,6 +2889,8 @@ var latestNodes = [];
2854
2889
  var cachedContextFilePath = null;
2855
2890
  var cachedContextFileContent = null;
2856
2891
  var prevFrame = [];
2892
+ var cachedLogSessionId = null;
2893
+ var cachedLogFiles = /* @__PURE__ */ new Map();
2857
2894
  function getAgentForNode(node, agents) {
2858
2895
  if (!node) return null;
2859
2896
  if (node.type === "agent" || node.type === "report") {
@@ -2868,6 +2905,7 @@ function startApp(state2, cleanup2) {
2868
2905
  try {
2869
2906
  let selectedSession = null;
2870
2907
  let planContent = "";
2908
+ let strategyContent = "";
2871
2909
  let goalContent = "";
2872
2910
  let logsContent = "";
2873
2911
  let logsCycles = [];
@@ -2911,15 +2949,39 @@ function startApp(state2, cleanup2) {
2911
2949
  }
2912
2950
  } catch {
2913
2951
  }
2952
+ try {
2953
+ const sp = strategyPath(state2.cwd, state2.selectedSessionId);
2954
+ if (existsSync2(sp)) {
2955
+ strategyContent = readFileSync2(sp, "utf-8");
2956
+ }
2957
+ } catch {
2958
+ }
2914
2959
  try {
2915
2960
  const ld = logsDir(state2.cwd, state2.selectedSessionId);
2916
2961
  if (existsSync2(ld)) {
2962
+ if (state2.selectedSessionId !== cachedLogSessionId) {
2963
+ cachedLogFiles = /* @__PURE__ */ new Map();
2964
+ cachedLogSessionId = state2.selectedSessionId;
2965
+ }
2917
2966
  const files = readdirSync(ld).filter((f) => f.startsWith("cycle-")).sort();
2967
+ const fileSet = new Set(files);
2968
+ for (const key of cachedLogFiles.keys()) {
2969
+ if (!fileSet.has(key)) cachedLogFiles.delete(key);
2970
+ }
2971
+ for (const f of files) {
2972
+ const filePath = join3(ld, f);
2973
+ const mtime = statSync(filePath).mtimeMs;
2974
+ const cached = cachedLogFiles.get(f);
2975
+ if (!cached || cached.mtime !== mtime) {
2976
+ const match = f.match(/cycle-(\d+)\.md$/);
2977
+ const cycle = match ? parseInt(match[1], 10) : 0;
2978
+ const content = readFileSync2(filePath, "utf-8");
2979
+ cachedLogFiles.set(f, { mtime, cycle, content });
2980
+ }
2981
+ }
2918
2982
  logsCycles = files.map((f) => {
2919
- const match = f.match(/cycle-(\d+)\.md$/);
2920
- const cycle = match ? parseInt(match[1], 10) : 0;
2921
- const content = readFileSync2(join3(ld, f), "utf-8");
2922
- return { cycle, content };
2983
+ const entry = cachedLogFiles.get(f);
2984
+ return { cycle: entry.cycle, content: entry.content };
2923
2985
  });
2924
2986
  logsContent = logsCycles.map((c) => c.content).join("\n");
2925
2987
  }
@@ -2942,6 +3004,7 @@ function startApp(state2, cleanup2) {
2942
3004
  state2.sessions = sessions;
2943
3005
  state2.selectedSession = selectedSession;
2944
3006
  state2.planContent = planContent;
3007
+ state2.strategyContent = strategyContent;
2945
3008
  state2.goalContent = goalContent;
2946
3009
  state2.logsContent = logsContent;
2947
3010
  state2.logsCycles = logsCycles;
@@ -3088,6 +3151,7 @@ function startApp(state2, cleanup2) {
3088
3151
  openEditorPopup,
3089
3152
  editInPopup,
3090
3153
  openCompanionPane,
3154
+ openClaudeResumePopup,
3091
3155
  selectWindow,
3092
3156
  selectPane,
3093
3157
  switchToSession,
@@ -3107,10 +3171,10 @@ function startApp(state2, cleanup2) {
3107
3171
  }
3108
3172
  };
3109
3173
  setRenderFunction(render);
3110
- startKeypressListener((input, key) => {
3174
+ const stopKeypress = startKeypressListener((input, key) => {
3111
3175
  handleKeypress(input, key, state2, inputActions);
3112
3176
  });
3113
- onResize(() => {
3177
+ const stopResize = onResize(() => {
3114
3178
  const stdoutRows = process.stdout.rows;
3115
3179
  const stdoutCols = process.stdout.columns;
3116
3180
  state2.rows = typeof stdoutRows === "number" && stdoutRows > 0 ? stdoutRows : 24;
@@ -3119,7 +3183,16 @@ function startApp(state2, cleanup2) {
3119
3183
  requestRender();
3120
3184
  });
3121
3185
  void poll();
3122
- setInterval(() => void poll(), 2500);
3186
+ const pollInterval = setInterval(() => void poll(), 2500);
3187
+ const origCleanup = inputActions.cleanup;
3188
+ inputActions.cleanup = () => {
3189
+ clearInterval(pollInterval);
3190
+ stopKeypress();
3191
+ stopResize();
3192
+ state2.detailScroll.destroy();
3193
+ state2.logsScroll.destroy();
3194
+ origCleanup();
3195
+ };
3123
3196
  requestRender();
3124
3197
  }
3125
3198