sisyphi 1.2.1 → 1.2.11

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 (87) hide show
  1. package/README.md +20 -20
  2. package/dist/cli.js +12461 -11237
  3. package/dist/cli.js.map +1 -1
  4. package/dist/daemon.js +1112 -564
  5. package/dist/daemon.js.map +1 -1
  6. package/dist/templates/agent-plugin/agents/CLAUDE.md +2 -2
  7. package/dist/templates/agent-plugin/agents/implementor.md +3 -2
  8. package/dist/templates/agent-plugin/agents/operator.md +3 -4
  9. package/dist/templates/agent-plugin/agents/plan.md +1 -1
  10. package/dist/templates/agent-plugin/agents/problem.md +20 -20
  11. package/dist/templates/agent-plugin/agents/research-lead.md +1 -1
  12. package/dist/templates/agent-plugin/agents/spec/engineer.md +9 -7
  13. package/dist/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
  14. package/dist/templates/agent-plugin/agents/spec.md +31 -25
  15. package/dist/templates/agent-plugin/hooks/CLAUDE.md +0 -1
  16. package/dist/templates/agent-plugin/hooks/ask-background-guard.sh +11 -11
  17. package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
  18. package/dist/templates/agent-plugin/hooks/operator-user-prompt.sh +2 -2
  19. package/dist/templates/agent-plugin/hooks/plan-validate.sh +3 -3
  20. package/dist/templates/agent-plugin/hooks/require-submit.sh +1 -1
  21. package/dist/templates/agent-plugin/skills/operator/SKILL.md +1 -1
  22. package/dist/templates/agent-suffix.md +4 -18
  23. package/dist/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
  24. package/dist/templates/dashboard-claude.md +15 -13
  25. package/dist/templates/orchestrator-base.md +44 -78
  26. package/dist/templates/orchestrator-completion.md +9 -11
  27. package/dist/templates/orchestrator-discovery.md +8 -8
  28. package/dist/templates/orchestrator-impl.md +6 -7
  29. package/dist/templates/orchestrator-planning.md +2 -2
  30. package/dist/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
  31. package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
  32. package/dist/templates/orchestrator-validation.md +1 -3
  33. package/dist/templates/termrender-haiku-system.md +5 -3
  34. package/dist/tui.js +1817 -1400
  35. package/dist/tui.js.map +1 -1
  36. package/native/build-notify.sh +2 -2
  37. package/package.json +3 -3
  38. package/templates/agent-plugin/agents/CLAUDE.md +2 -2
  39. package/templates/agent-plugin/agents/implementor.md +3 -2
  40. package/templates/agent-plugin/agents/operator.md +3 -4
  41. package/templates/agent-plugin/agents/plan.md +1 -1
  42. package/templates/agent-plugin/agents/problem.md +20 -20
  43. package/templates/agent-plugin/agents/research-lead.md +1 -1
  44. package/templates/agent-plugin/agents/spec/engineer.md +9 -7
  45. package/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
  46. package/templates/agent-plugin/agents/spec.md +31 -25
  47. package/templates/agent-plugin/hooks/CLAUDE.md +0 -1
  48. package/templates/agent-plugin/hooks/ask-background-guard.sh +11 -11
  49. package/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
  50. package/templates/agent-plugin/hooks/operator-user-prompt.sh +2 -2
  51. package/templates/agent-plugin/hooks/plan-validate.sh +3 -3
  52. package/templates/agent-plugin/hooks/require-submit.sh +1 -1
  53. package/templates/agent-plugin/skills/operator/SKILL.md +1 -1
  54. package/templates/agent-suffix.md +4 -18
  55. package/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
  56. package/templates/dashboard-claude.md +15 -13
  57. package/templates/orchestrator-base.md +44 -78
  58. package/templates/orchestrator-completion.md +9 -11
  59. package/templates/orchestrator-discovery.md +8 -8
  60. package/templates/orchestrator-impl.md +6 -7
  61. package/templates/orchestrator-planning.md +2 -2
  62. package/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
  63. package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
  64. package/templates/orchestrator-validation.md +1 -3
  65. package/templates/termrender-haiku-system.md +5 -3
  66. package/dist/templates/agent-plugin/skills/humanloop/SKILL.md +0 -148
  67. package/dist/templates/agent-plugin/skills/operator-memory/SKILL.md +0 -64
  68. package/dist/templates/agent-plugin/skills/perspective-fanout/SKILL.md +0 -115
  69. package/dist/templates/agent-plugin/skills/problem-document/SKILL.md +0 -105
  70. package/dist/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +0 -83
  71. package/dist/templates/orchestrator-plugin/skills/humanloop/SKILL.md +0 -150
  72. package/dist/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +0 -1
  73. package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +0 -29
  74. package/dist/templates/orchestrator-plugin/skills/orchestration/strategy.md +0 -160
  75. package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +0 -266
  76. package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +0 -428
  77. package/templates/agent-plugin/skills/humanloop/SKILL.md +0 -148
  78. package/templates/agent-plugin/skills/operator-memory/SKILL.md +0 -64
  79. package/templates/agent-plugin/skills/perspective-fanout/SKILL.md +0 -115
  80. package/templates/agent-plugin/skills/problem-document/SKILL.md +0 -105
  81. package/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +0 -83
  82. package/templates/orchestrator-plugin/skills/humanloop/SKILL.md +0 -150
  83. package/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +0 -1
  84. package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +0 -29
  85. package/templates/orchestrator-plugin/skills/orchestration/strategy.md +0 -160
  86. package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +0 -266
  87. package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +0 -428
package/dist/tui.js CHANGED
@@ -220,7 +220,7 @@ var init_terminal = __esm({
220
220
 
221
221
  // src/shared/paths.ts
222
222
  import { homedir } from "os";
223
- import { basename, join } from "path";
223
+ import { basename as basename2, join } from "path";
224
224
  function globalDir() {
225
225
  return join(homedir(), ".sisyphus");
226
226
  }
@@ -276,16 +276,22 @@ function askMetaPath(cwd2, sessionId2, askId2) {
276
276
  return join(askEntryDir(cwd2, sessionId2, askId2), "meta.json");
277
277
  }
278
278
  function askDecisionsPath(cwd2, sessionId2, askId2) {
279
- return join(askEntryDir(cwd2, sessionId2, askId2), "decisions.json");
279
+ return join(askEntryDir(cwd2, sessionId2, askId2), "deck.json");
280
280
  }
281
281
  function askOutputPath(cwd2, sessionId2, askId2) {
282
- return join(askEntryDir(cwd2, sessionId2, askId2), "output.json");
282
+ return join(askEntryDir(cwd2, sessionId2, askId2), "response.json");
283
283
  }
284
284
  function askProgressPath(cwd2, sessionId2, askId2) {
285
285
  return join(askEntryDir(cwd2, sessionId2, askId2), "progress.json");
286
286
  }
287
+ function askReviewPath(cwd2, sessionId2, askId2) {
288
+ return join(askEntryDir(cwd2, sessionId2, askId2), "review.json");
289
+ }
290
+ function askReviewDraftPath(cwd2, sessionId2, askId2) {
291
+ return join(askEntryDir(cwd2, sessionId2, askId2), "draft.json");
292
+ }
287
293
  function tmuxSessionName(cwd2, sessionLabel) {
288
- return `ssyph_${basename(cwd2)}_${sessionLabel}`;
294
+ return `ssyph_${basename2(cwd2)}_${sessionLabel}`;
289
295
  }
290
296
  function companionPath() {
291
297
  return join(globalDir(), "companion.json");
@@ -676,11 +682,21 @@ var init_render = __esm({
676
682
  }
677
683
  });
678
684
 
685
+ // src/shared/shell.ts
686
+ function shellQuote(s) {
687
+ return `'${s.replace(/'/g, "'\\''")}'`;
688
+ }
689
+ var init_shell = __esm({
690
+ "src/shared/shell.ts"() {
691
+ "use strict";
692
+ }
693
+ });
694
+
679
695
  // src/shared/config.ts
680
- import { readFileSync as readFileSync5 } from "fs";
696
+ import { readFileSync as readFileSync9 } from "fs";
681
697
  function readJsonFile(filePath) {
682
698
  try {
683
- const content = readFileSync5(filePath, "utf-8");
699
+ const content = readFileSync9(filePath, "utf-8");
684
700
  return JSON.parse(content);
685
701
  } catch {
686
702
  return {};
@@ -723,6 +739,7 @@ var init_config = __esm({
723
739
  DEFAULT_CONFIG = {
724
740
  model: "claude-opus-4-7[1m]",
725
741
  pollIntervalMs: 5e3,
742
+ statusBarRenderTicks: 4,
726
743
  orchestratorEffort: "xhigh",
727
744
  agentEffort: "medium",
728
745
  notifications: {
@@ -732,15 +749,22 @@ var init_config = __esm({
732
749
  companionPopup: true,
733
750
  requiredPlugins: [
734
751
  { name: "devcore", marketplace: "crouton-kit", owner: "crouton-labs" }
752
+ ],
753
+ requiredCrtrPlugins: [
754
+ {
755
+ marketplace: "sisyphus",
756
+ plugin: "sisyphus",
757
+ gitUrl: "https://github.com/crouton-labs/sisyphus"
758
+ }
735
759
  ]
736
760
  };
737
761
  }
738
762
  });
739
763
 
740
764
  // src/daemon/history.ts
741
- import { appendFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, renameSync as renameSync3, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync2, statSync } from "fs";
765
+ import { appendFileSync, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, renameSync as renameSync3, readdirSync as readdirSync4, readFileSync as readFileSync10, rmSync as rmSync3, statSync as statSync2 } from "fs";
742
766
  import { randomUUID as randomUUID3 } from "crypto";
743
- import { dirname as dirname3, join as join6 } from "path";
767
+ import { dirname as dirname4, join as join8 } from "path";
744
768
  var init_history = __esm({
745
769
  "src/daemon/history.ts"() {
746
770
  "use strict";
@@ -750,12 +774,12 @@ var init_history = __esm({
750
774
 
751
775
  // src/daemon/lib/atomic.ts
752
776
  import { randomUUID as randomUUID4 } from "crypto";
753
- import { dirname as dirname4, join as join7 } from "path";
754
- import { renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
777
+ import { dirname as dirname5, join as join9 } from "path";
778
+ import { renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
755
779
  function atomicWrite(filePath, data) {
756
- const dir = dirname4(filePath);
757
- const tmpPath = join7(dir, `.atomic.${randomUUID4()}.tmp`);
758
- writeFileSync5(tmpPath, data, "utf-8");
780
+ const dir = dirname5(filePath);
781
+ const tmpPath = join9(dir, `.atomic.${randomUUID4()}.tmp`);
782
+ writeFileSync6(tmpPath, data, "utf-8");
759
783
  renameSync4(tmpPath, filePath);
760
784
  }
761
785
  async function withLock(key, fn) {
@@ -784,8 +808,8 @@ var init_atomic = __esm({
784
808
  });
785
809
 
786
810
  // src/shared/gitignore.ts
787
- import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
788
- import { join as join8 } from "path";
811
+ import { existsSync as existsSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
812
+ import { join as join10 } from "path";
789
813
  var init_gitignore = __esm({
790
814
  "src/shared/gitignore.ts"() {
791
815
  "use strict";
@@ -800,8 +824,8 @@ var init_types = __esm({
800
824
  });
801
825
 
802
826
  // src/daemon/state.ts
803
- import { copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
804
- import { join as join9 } from "path";
827
+ import { copyFileSync, cpSync as cpSync2, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync5, rmSync as rmSync4, statSync as statSync3, watch as fsWatch, writeFileSync as writeFileSync8 } from "fs";
828
+ import { join as join11 } from "path";
805
829
  var init_state = __esm({
806
830
  "src/daemon/state.ts"() {
807
831
  "use strict";
@@ -812,20 +836,10 @@ var init_state = __esm({
812
836
  }
813
837
  });
814
838
 
815
- // src/shared/shell.ts
816
- function shellQuote(s) {
817
- return `'${s.replace(/'/g, "'\\''")}'`;
818
- }
819
- var init_shell = __esm({
820
- "src/shared/shell.ts"() {
821
- "use strict";
822
- }
823
- });
824
-
825
839
  // src/daemon/notify.ts
826
840
  import { spawn, execFile as execFile2 } from "child_process";
827
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync7 } from "fs";
828
- import { join as join10 } from "path";
841
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync8 } from "fs";
842
+ import { join as join12 } from "path";
829
843
  import { homedir as homedir3 } from "os";
830
844
  var TMUX_SOCKET, SWITCH_SCRIPT;
831
845
  var init_notify = __esm({
@@ -878,11 +892,11 @@ var init_notify = __esm({
878
892
  });
879
893
 
880
894
  // src/daemon/ask-store.ts
881
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync9, readdirSync as readdirSync4 } from "fs";
895
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
882
896
  function readDecisions(cwd2, sessionId2, askId2) {
883
897
  const p = askDecisionsPath(cwd2, sessionId2, askId2);
884
898
  try {
885
- return JSON.parse(readFileSync9(p, { encoding: "utf-8" }));
899
+ return JSON.parse(readFileSync13(p, { encoding: "utf-8" }));
886
900
  } catch (_e) {
887
901
  return null;
888
902
  }
@@ -890,7 +904,7 @@ function readDecisions(cwd2, sessionId2, askId2) {
890
904
  function readProgress(cwd2, sessionId2, askId2) {
891
905
  const p = askProgressPath(cwd2, sessionId2, askId2);
892
906
  try {
893
- const data = JSON.parse(readFileSync9(p, { encoding: "utf-8" }));
907
+ const data = JSON.parse(readFileSync13(p, { encoding: "utf-8" }));
894
908
  if (!Array.isArray(data["responses"])) return null;
895
909
  return { responses: data["responses"], savedAt: data["savedAt"] };
896
910
  } catch (_e) {
@@ -905,20 +919,45 @@ function writeOutput(cwd2, sessionId2, askId2, responses, completedAt) {
905
919
  }
906
920
  function readMeta(cwd2, sessionId2, askId2) {
907
921
  const p = askMetaPath(cwd2, sessionId2, askId2);
908
- if (!existsSync8(p)) {
922
+ if (!existsSync9(p)) {
909
923
  return null;
910
924
  }
911
- return JSON.parse(readFileSync9(p, "utf-8"));
925
+ return JSON.parse(readFileSync13(p, "utf-8"));
912
926
  }
913
927
  async function updateMeta(cwd2, sessionId2, askId2, patch) {
914
- return withLock(askId2, () => {
928
+ const next = await withLock(askId2, () => {
915
929
  const cur = readMeta(cwd2, sessionId2, askId2);
916
930
  if (!cur) {
917
931
  throw new Error(`updateMeta: askId ${askId2} not found`);
918
932
  }
919
- const next = { ...cur, ...patch };
920
- atomicWrite(askMetaPath(cwd2, sessionId2, askId2), JSON.stringify(next, null, 2));
921
- return next;
933
+ const updated = { ...cur, ...patch };
934
+ atomicWrite(askMetaPath(cwd2, sessionId2, askId2), JSON.stringify(updated, null, 2));
935
+ return updated;
936
+ });
937
+ if (patch.status === "answered" && next.heartbeatAskId) {
938
+ const hbAskId = next.heartbeatAskId;
939
+ cascadeResolveHeartbeatAsk(cwd2, sessionId2, hbAskId).catch((err) => {
940
+ console.warn(
941
+ `[sisyphus] heartbeat cascade-resolve failed for ${hbAskId}:`,
942
+ err instanceof Error ? err.message : err
943
+ );
944
+ });
945
+ }
946
+ return next;
947
+ }
948
+ async function cascadeResolveHeartbeatAsk(cwd2, sessionId2, heartbeatAskId) {
949
+ const hbMeta = readMeta(cwd2, sessionId2, heartbeatAskId);
950
+ if (!hbMeta) return;
951
+ if (hbMeta.status === "answered") return;
952
+ if (existsSync9(askOutputPath(cwd2, sessionId2, heartbeatAskId))) return;
953
+ writeOutput(cwd2, sessionId2, heartbeatAskId, [{
954
+ id: "heartbeat",
955
+ selectedOptionId: "ack",
956
+ freetext: "auto-resolved: original ask was answered"
957
+ }]);
958
+ await updateMeta(cwd2, sessionId2, heartbeatAskId, {
959
+ status: "answered",
960
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
922
961
  });
923
962
  }
924
963
  var init_ask_store = __esm({
@@ -939,7 +978,7 @@ var single_ask_exports = {};
939
978
  __export(single_ask_exports, {
940
979
  runSingleAsk: () => runSingleAsk
941
980
  });
942
- import { existsSync as existsSync12, watchFile, unwatchFile } from "fs";
981
+ import { existsSync as existsSync12, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
943
982
  import { mountPanel as mountPanel2 } from "@crouton-kit/humanloop";
944
983
  async function runSingleAsk(opts) {
945
984
  const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = opts;
@@ -956,6 +995,8 @@ async function runSingleAsk(opts) {
956
995
  let stopKeypress = null;
957
996
  let stopResize = null;
958
997
  const outputPath = askOutputPath(cwd2, sessionId2, askId2);
998
+ const decisionsPath = askDecisionsPath(cwd2, sessionId2, askId2);
999
+ let lastDeckJson = JSON.stringify(deck);
959
1000
  const exit = (code) => {
960
1001
  if (exiting) return;
961
1002
  exiting = true;
@@ -972,7 +1013,11 @@ async function runSingleAsk(opts) {
972
1013
  } catch {
973
1014
  }
974
1015
  try {
975
- unwatchFile(outputPath, onExternalChange);
1016
+ unwatchFile2(outputPath, onExternalChange);
1017
+ } catch {
1018
+ }
1019
+ try {
1020
+ unwatchFile2(decisionsPath, onDeckChange);
976
1021
  } catch {
977
1022
  }
978
1023
  cleanupTerminal();
@@ -988,6 +1033,16 @@ async function runSingleAsk(opts) {
988
1033
  if (!existsSync12(outputPath)) return;
989
1034
  exit(0);
990
1035
  };
1036
+ const onDeckChange = () => {
1037
+ if (exiting || !panel) return;
1038
+ const next = readDecisions(cwd2, sessionId2, askId2);
1039
+ if (!next) return;
1040
+ const nextJson = JSON.stringify(next);
1041
+ if (nextJson === lastDeckJson) return;
1042
+ lastDeckJson = nextJson;
1043
+ panel.loadDeck(next, { progressPath: askProgressPath(cwd2, sessionId2, askId2) });
1044
+ flushHost(panel.render());
1045
+ };
991
1046
  let lastResponses = [];
992
1047
  const submit = (responses) => {
993
1048
  if (exiting) return;
@@ -1038,7 +1093,8 @@ async function runSingleAsk(opts) {
1038
1093
  prevFrame2 = [];
1039
1094
  flushHost(panel.render());
1040
1095
  });
1041
- watchFile(outputPath, { interval: 250 }, onExternalChange);
1096
+ watchFile2(outputPath, { interval: 250 }, onExternalChange);
1097
+ watchFile2(decisionsPath, { interval: 500 }, onDeckChange);
1042
1098
  if (existsSync12(outputPath)) {
1043
1099
  exit(0);
1044
1100
  return;
@@ -1201,7 +1257,9 @@ function createAppState(cwd2) {
1201
1257
  flowExpanded: false,
1202
1258
  resolutionActive: false,
1203
1259
  resolutionHandle: null,
1260
+ inlineDeck: null,
1204
1261
  visuals: /* @__PURE__ */ new Map(),
1262
+ reviewPanel: null,
1205
1263
  cwd: cwd2
1206
1264
  };
1207
1265
  }
@@ -1265,12 +1323,28 @@ function autoExpandCycle(state2) {
1265
1323
  }
1266
1324
 
1267
1325
  // src/tui/app.ts
1268
- import { readFileSync as readFileSync16, existsSync as existsSync11, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
1326
+ import { readFileSync as readFileSync17, existsSync as existsSync11, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
1269
1327
  import { join as join15 } from "path";
1270
1328
 
1329
+ // src/shared/inbox-types.ts
1330
+ import { basename, dirname } from "path";
1331
+ function coerceKind(k) {
1332
+ if (k !== void 0) return k;
1333
+ return "validation";
1334
+ }
1335
+ function sessionIdFromDir(dir) {
1336
+ return basename(dirname(dirname(dirname(dir))));
1337
+ }
1338
+ function cwdFromDir(dir) {
1339
+ return dirname(dirname(dirname(dirname(dirname(dirname(dir))))));
1340
+ }
1341
+ function askIdFromDir(dir) {
1342
+ return basename(dir);
1343
+ }
1344
+
1271
1345
  // src/tui/input.ts
1272
- import { readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
1273
- import { join as join11 } from "path";
1346
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync } from "fs";
1347
+ import { join as join6 } from "path";
1274
1348
 
1275
1349
  // src/shared/session-export.ts
1276
1350
  init_paths();
@@ -1530,9 +1604,14 @@ function statusColor(status) {
1530
1604
  return "white";
1531
1605
  }
1532
1606
  }
1533
- var COLOR_ENABLED = process.env["FORCE_COLOR"] === "1" || process.stdout.isTTY === true && process.env["NO_COLOR"] === void 0 && process.env["TERM"] !== "dumb";
1607
+ function colorEnabled() {
1608
+ if (process.env["FORCE_COLOR"] === "1") return true;
1609
+ if (process.env["NO_COLOR"] !== void 0) return false;
1610
+ if (process.env["TERM"] === "dumb") return false;
1611
+ return process.stdout.isTTY === true;
1612
+ }
1534
1613
  function wrap(open, close = "\x1B[0m") {
1535
- return (s) => COLOR_ENABLED ? `${open}${s}${close}` : s;
1614
+ return (s) => colorEnabled() ? `${open}${s}${close}` : s;
1536
1615
  }
1537
1616
  var bold = wrap("\x1B[1m");
1538
1617
  var dim = wrap("\x1B[2m");
@@ -1647,6 +1726,28 @@ function agentTypeColor(agentType) {
1647
1726
  if (t.includes("plan")) return "yellow";
1648
1727
  return void 0;
1649
1728
  }
1729
+ var KIND_ICON = {
1730
+ notify: "\u2709",
1731
+ validation: "\u2713",
1732
+ decision: "\u25C6",
1733
+ context: "\u270E",
1734
+ error: "\u26A0",
1735
+ review: "\u25C8"
1736
+ };
1737
+ var KIND_COLOR = {
1738
+ notify: "gray",
1739
+ validation: "cyan",
1740
+ decision: "cyan",
1741
+ context: "cyan",
1742
+ error: "red",
1743
+ review: "magenta"
1744
+ };
1745
+ function kindIcon(kind) {
1746
+ return kind && kind in KIND_ICON ? KIND_ICON[kind] : "\xB7";
1747
+ }
1748
+ function kindColor(kind) {
1749
+ return kind && kind in KIND_COLOR ? KIND_COLOR[kind] : "cyan";
1750
+ }
1650
1751
  function divider(width, char = "\u2500") {
1651
1752
  return char.repeat(Math.max(0, width));
1652
1753
  }
@@ -1765,9 +1866,10 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
1765
1866
  const nodes = [];
1766
1867
  const inboxBySession = /* @__PURE__ */ new Map();
1767
1868
  for (const item of aggregateInbox) {
1768
- const arr = inboxBySession.get(item.sessionId) ?? [];
1869
+ const sessionId2 = sessionIdFromDir(item.dir);
1870
+ const arr = inboxBySession.get(sessionId2) ?? [];
1769
1871
  arr.push(item);
1770
- inboxBySession.set(item.sessionId, arr);
1872
+ inboxBySession.set(sessionId2, arr);
1771
1873
  }
1772
1874
  const needsYou = [];
1773
1875
  const running = [];
@@ -2300,7 +2402,7 @@ function applyColor(result, fields, facePart, mood, opts) {
2300
2402
  init_paths();
2301
2403
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
2302
2404
  import { randomUUID as randomUUID2 } from "crypto";
2303
- import { dirname as dirname2, join as join5 } from "path";
2405
+ import { dirname as dirname3, join as join5 } from "path";
2304
2406
 
2305
2407
  // src/shared/companion-normalize.ts
2306
2408
  function emptyStats() {
@@ -2425,7 +2527,7 @@ var ACHIEVEMENTS = [
2425
2527
  // src/daemon/companion-memory.ts
2426
2528
  init_paths();
2427
2529
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
2428
- import { dirname, join as join4 } from "path";
2530
+ import { dirname as dirname2, join as join4 } from "path";
2429
2531
  import { randomUUID } from "crypto";
2430
2532
  import { z } from "zod";
2431
2533
 
@@ -2469,10 +2571,12 @@ function augmentedPath() {
2469
2571
  return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
2470
2572
  }
2471
2573
  function execEnv() {
2472
- return {
2574
+ const env = {
2473
2575
  ...process.env,
2474
2576
  PATH: augmentedPath()
2475
2577
  };
2578
+ if (!env["LC_ALL"] && !env["LANG"]) env["LANG"] = "en_US.UTF-8";
2579
+ return env;
2476
2580
  }
2477
2581
 
2478
2582
  // src/daemon/haiku.ts
@@ -3740,433 +3844,151 @@ function renderCompanionDebugOverlay(buf, rows, cols, companion) {
3740
3844
  }
3741
3845
  }
3742
3846
 
3743
- // src/tui/panels/mounted-humanloop.ts
3744
- init_ask_store();
3745
- init_paths();
3746
- import { readFileSync as readFileSync10 } from "fs";
3747
- import { mountPanel } from "@crouton-kit/humanloop";
3847
+ // src/shared/keymap.ts
3848
+ var MENU_FOR_MODE = {
3849
+ "leader": "topLevel",
3850
+ "copy-menu": "copy",
3851
+ "open-menu": "open",
3852
+ "agent-menu": "agent",
3853
+ "session-menu": "session",
3854
+ "go-menu": "go",
3855
+ "companion-menu": "companion"
3856
+ };
3857
+ var KEYMAP = {
3858
+ version: 1,
3859
+ topLevel: {
3860
+ title: " Sisyphus ",
3861
+ items: [
3862
+ { key: "s", label: " Cycle session", action: { type: "script", name: "sisyphus-cycle" } },
3863
+ { key: "h", label: " Home / dashboard", action: { type: "script", name: "sisyphus-home" } },
3864
+ { key: "n", label: " New session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
3865
+ { key: "m", label: " Message orchestrator", action: { type: "popup", name: "sisyphus-msg", popup: { w: "80%", h: "60%", cwd: "current" } } },
3866
+ { key: "t", label: " Status (where am I?)", action: { type: "popup", name: "sisyphus-status-popup", popup: { w: "90%", h: "90%", cwd: "current" } } },
3867
+ { key: "l", label: " Session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3868
+ { key: "z", label: " Zoom pane", action: { type: "tmux", cmd: "resize-pane -Z" } },
3869
+ { key: "x", label: " Kill pane (smart)", action: { type: "script", name: "sisyphus-kill-pane" } },
3870
+ { key: "?", label: " Full help reference", action: { type: "popup", name: "sisyphus-help", popup: { w: "80", h: "32", title: " Keybindings " } } },
3871
+ { key: "/", label: " Search / filter", action: { type: "script", name: "sisyphus-search-reports" }, tuiAction: "search" },
3872
+ { key: " ", label: " Open popup explicitly", action: { type: "tui", action: "show-leader" } },
3873
+ { key: "y", label: " Yank \u203A", action: { type: "submenu", ref: "copy" } },
3874
+ { key: "c", label: " Side claude pane", action: { type: "script", name: "sisyphus-companion-pane" }, tuiAction: "companion-pane" },
3875
+ { key: "C", label: " Companion (gamification) \u203A", action: { type: "submenu", ref: "companion" } },
3876
+ { key: "o", label: " Open \u203A", action: { type: "submenu", ref: "open" } },
3877
+ { key: "a", label: " Agent \u203A", action: { type: "submenu", ref: "agent" } },
3878
+ { key: "S", label: " Session \u203A", action: { type: "submenu", ref: "session" } },
3879
+ { key: "g", label: " Go \u203A", action: { type: "submenu", ref: "go" } }
3880
+ ]
3881
+ },
3882
+ submenus: {
3883
+ companion: {
3884
+ title: " Companion ",
3885
+ items: [
3886
+ { key: "p", label: " profile (overlay)", action: { type: "tui", action: "companion-overlay" } },
3887
+ { key: "d", label: " debug (mood signals)", action: { type: "tui", action: "companion-debug" } }
3888
+ ]
3889
+ },
3890
+ copy: {
3891
+ title: " Copy ",
3892
+ items: [
3893
+ { key: "p", label: " session dir path", action: { type: "script", name: "sisyphus-copy-path" } },
3894
+ { key: "i", label: " session UUID", action: { type: "script", name: "sisyphus-copy-id" } },
3895
+ { key: "c", label: " full session context XML", action: { type: "script", name: "sisyphus-copy-context" } },
3896
+ { key: "l", label: " logs (last 200 lines)", action: { type: "script", name: "sisyphus-copy-logs" } },
3897
+ { key: "r", label: " latest report content", action: { type: "script", name: "sisyphus-copy-latest-report" } },
3898
+ { key: "a", label: " agent ID (picker)", action: { type: "script", name: "sisyphus-copy-agent-id" } }
3899
+ ]
3900
+ },
3901
+ open: {
3902
+ title: " Open ",
3903
+ items: [
3904
+ { key: "g", label: " goal.md", action: { type: "popup", name: "sisyphus-open-goal", popup: { w: "95%", h: "95%", cwd: "current" } } },
3905
+ { key: "r", label: " roadmap.md", action: { type: "popup", name: "sisyphus-open-roadmap", popup: { w: "95%", h: "95%", cwd: "current" } } },
3906
+ { key: "s", label: " strategy.md", action: { type: "popup", name: "sisyphus-open-strategy", popup: { w: "95%", h: "95%", cwd: "current" } } },
3907
+ { key: "l", label: " logs popup (tail)", action: { type: "popup", name: "sisyphus-open-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
3908
+ { key: "d", label: " session dir in file mgr", action: { type: "script", name: "sisyphus-open-dir" } },
3909
+ { key: "R", label: " latest report file", action: { type: "popup", name: "sisyphus-open-latest-report", popup: { w: "95%", h: "95%", cwd: "current" } } },
3910
+ { key: "c", label: " scratch", action: { type: "popup", name: "sisyphus-open-scratch", popup: { w: "95%", h: "95%", cwd: "current" } } },
3911
+ { key: "e", label: " edit context file", action: { type: "popup", name: "sisyphus-edit-context-file", popup: { w: "95%", h: "95%", cwd: "current" } }, tuiAction: "edit-context-file" }
3912
+ ]
3913
+ },
3914
+ agent: {
3915
+ title: " Agent ",
3916
+ items: [
3917
+ { key: "s", label: " spawn agent", action: { type: "popup", name: "sisyphus-spawn-agent", popup: { w: "80%", h: "70%", cwd: "current" } } },
3918
+ { key: "m", label: " message agent (picker)", action: { type: "popup", name: "sisyphus-msg-agent", popup: { w: "80%", h: "60%", cwd: "current" } } },
3919
+ { key: "r", label: " restart agent (picker)", action: { type: "popup", name: "sisyphus-restart-agent-popup", popup: { w: "70%", h: "50%", cwd: "current" } } },
3920
+ { key: "R", label: " re-run agent (picker)", action: { type: "popup", name: "sisyphus-rerun-agent", popup: { w: "70%", h: "50%", cwd: "current" } } },
3921
+ { key: "j", label: " jump to agent's pane", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
3922
+ { key: "o", label: " open claude --resume", action: { type: "popup", name: "sisyphus-open-claude-agent", popup: { w: "60%", h: "60%", cwd: "current" } } },
3923
+ { key: "t", label: " tail agent logs (picker)", action: { type: "popup", name: "sisyphus-tail-agent-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
3924
+ { key: "k", label: " kill agent (picker)", action: { type: "popup", name: "sisyphus-kill-agent", popup: { w: "60%", h: "40%", cwd: "current" } } },
3925
+ { key: "e", label: " quick-spawn Explore", action: { type: "script", name: "sisyphus-quick-spawn-explore" } },
3926
+ { key: "d", label: " quick-spawn Debug", action: { type: "script", name: "sisyphus-quick-spawn-debug" } }
3927
+ ]
3928
+ },
3929
+ session: {
3930
+ title: " Session ",
3931
+ items: [
3932
+ { key: "n", label: " new session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
3933
+ { key: "r", label: " resume", action: { type: "popup", name: "sisyphus-resume-session", popup: { w: "80%", h: "60%", cwd: "current" } } },
3934
+ { key: "c", label: " continue", action: { type: "popup", name: "sisyphus-continue-session", popup: { w: "50", h: "5", borderStyle: "fg=yellow", title: " Continue Session ", cwd: "current" } } },
3935
+ { key: "b", label: " rollback (prompts cycle)", action: { type: "popup", name: "sisyphus-rollback-session", popup: { w: "50", h: "5", title: " Rollback ", cwd: "current" } } },
3936
+ { key: "k", label: " kill", action: { type: "popup", name: "sisyphus-kill-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Kill Session ", cwd: "current" } } },
3937
+ { key: "d", label: " delete (confirms)", action: { type: "popup", name: "sisyphus-delete-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Delete Session ", cwd: "current" } } },
3938
+ { key: "e", label: " export to ~/Downloads", action: { type: "popup", name: "sisyphus-export-session", popup: { w: "60", h: "8", title: " Export Session ", cwd: "current" } } },
3939
+ { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
3940
+ { key: "C", label: " clone (sisyphus session clone)", action: { type: "popup", name: "sisyphus-clone-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3941
+ { key: "i", label: " history", action: { type: "popup", name: "sisyphus-history", popup: { w: "95%", h: "95%", cwd: "current" } } }
3942
+ ]
3943
+ },
3944
+ go: {
3945
+ title: " Go ",
3946
+ items: [
3947
+ { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
3948
+ { key: "p", label: " jump to pane (picker)", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
3949
+ { key: "s", label: " session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3950
+ { key: "n", label: " next session", action: { type: "script", name: "sisyphus-cycle" } },
3951
+ { key: "r", label: " reconnect", action: { type: "popup", name: "sisyphus-reconnect", popup: { w: "80%", h: "40%", cwd: "current" } } }
3952
+ ]
3953
+ }
3954
+ }
3955
+ };
3748
3956
 
3749
- // src/shared/client.ts
3750
- init_paths();
3751
- import { connect } from "net";
3752
- function rawSend(request, timeoutMs = 1e4) {
3753
- const sock = socketPath();
3754
- return new Promise((resolve2, reject) => {
3755
- const socket = connect(sock);
3756
- let data = "";
3757
- const timeout = setTimeout(() => {
3758
- socket.destroy();
3759
- reject(new Error(`Request timed out after ${(timeoutMs / 1e3).toFixed(0)}s. The daemon may be overloaded.
3760
- Check: sis admin doctor
3761
- Logs: tail -20 ~/.sisyphus/daemon.log`));
3762
- }, timeoutMs);
3763
- socket.on("connect", () => {
3764
- socket.write(JSON.stringify(request) + "\n");
3765
- });
3766
- socket.on("data", (chunk) => {
3767
- data += chunk.toString();
3768
- const newlineIdx = data.indexOf("\n");
3769
- if (newlineIdx !== -1) {
3770
- clearTimeout(timeout);
3771
- const line = data.slice(0, newlineIdx);
3772
- socket.destroy();
3957
+ // src/tui/input.ts
3958
+ function dispatchComposeAction(action, content, state2, actions) {
3959
+ switch (action.kind) {
3960
+ case "new-session":
3961
+ actions.sendAndNotify(
3962
+ { type: "start", task: content, cwd: state2.cwd },
3963
+ "Session created"
3964
+ );
3965
+ break;
3966
+ case "message-orchestrator":
3967
+ actions.sendAndNotify(
3968
+ { type: "message", sessionId: action.sessionId, content },
3969
+ "Message queued"
3970
+ );
3971
+ break;
3972
+ case "resume":
3973
+ actions.sendAndNotify(
3974
+ { type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
3975
+ "Session resumed"
3976
+ );
3977
+ break;
3978
+ case "continue":
3979
+ void (async () => {
3773
3980
  try {
3774
- resolve2(JSON.parse(line));
3775
- } catch {
3776
- reject(new Error(`Invalid JSON response from daemon: ${line}`));
3777
- }
3778
- }
3779
- });
3780
- socket.on("error", (err) => {
3781
- clearTimeout(timeout);
3782
- reject(err);
3783
- });
3784
- });
3785
- }
3786
-
3787
- // src/tui/panels/mounted-humanloop.ts
3788
- async function dispatchOrphanResolution(orphanTarget, selectedOptionId, deps) {
3789
- if (selectedOptionId === "takeover" && orphanTarget.kind === "agent") {
3790
- await deps.onOrphanTakeover?.({
3791
- sessionId: deps.sessionId,
3792
- agentId: orphanTarget.agentId,
3793
- paneId: orphanTarget.paneId
3794
- });
3795
- } else if (selectedOptionId === "restart" && orphanTarget.kind === "agent") {
3796
- await deps.daemonSend({ type: "restart-agent", sessionId: deps.sessionId, agentId: orphanTarget.agentId });
3797
- } else if (selectedOptionId === "resume" && orphanTarget.kind === "orchestrator") {
3798
- await deps.daemonSend({ type: "resume", sessionId: deps.sessionId, cwd: deps.cwd });
3799
- } else if (selectedOptionId === "dismiss" && orphanTarget.kind === "orchestrator") {
3800
- await deps.daemonSend({ type: "clear-orphan", sessionId: deps.sessionId, cwd: deps.cwd });
3801
- }
3802
- }
3803
- function mountResolutionPanel(opts, state2) {
3804
- let queue = [...opts.aggregateInbox];
3805
- let currentIndex = opts.startIndex;
3806
- let bodyCols = opts.cols;
3807
- const item = () => queue[currentIndex];
3808
- function buildDeck(idx) {
3809
- const it = queue[idx];
3810
- if (!it) return null;
3811
- const deck = readDecisions(it.cwd, it.sessionId, it.askId);
3812
- if (!deck) return null;
3813
- deck.source = {
3814
- sessionName: it.sessionName,
3815
- askedBy: it.askedBy,
3816
- blockedSince: it.blockedSince
3817
- };
3818
- return deck;
3819
- }
3820
- const initialDeck = buildDeck(currentIndex);
3821
- if (!initialDeck) return null;
3822
- let currentDeck = initialDeck;
3823
- const initialProgress = readProgress(item().cwd, item().sessionId, item().askId);
3824
- let answeredCount = initialProgress?.responses.length ?? 0;
3825
- function getCurrentQid() {
3826
- return currentDeck.interactions[answeredCount]?.id ?? currentDeck.interactions[0]?.id;
3827
- }
3828
- function fireVisualGen(force) {
3829
- const qid = getCurrentQid();
3830
- if (!qid) return;
3831
- const it = item();
3832
- state2.visuals.set(qid, { status: "loading", content: "", visible: true });
3833
- requestRender();
3834
- void (async () => {
3835
- const res = await rawSend({
3836
- type: "ask-generate-visual",
3837
- sessionId: it.sessionId,
3838
- askId: it.askId,
3839
- qid,
3840
- cols: bodyCols,
3841
- force
3842
- }, 6e4);
3843
- if (res.ok) {
3844
- const ansiPath = res.data.ansiPath;
3845
- const ansi = readFileSync10(ansiPath, "utf-8");
3846
- state2.visuals.set(qid, { status: "ready", content: ansi, visible: true });
3847
- } else {
3848
- state2.visuals.set(qid, { status: "error", content: "", visible: true, error: res.error });
3849
- }
3850
- requestRender();
3851
- })();
3852
- }
3853
- let lastResponses = [];
3854
- const submitResponses = (responses) => {
3855
- void (async () => {
3856
- const it = item();
3857
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3858
- const meta = readMeta(it.cwd, it.sessionId, it.askId);
3859
- if (meta?.orphanTarget && responses.length > 0) {
3860
- const sel = responses[0].selectedOptionId;
3861
- if (sel) {
3862
- await dispatchOrphanResolution(meta.orphanTarget, sel, {
3863
- daemonSend: opts.daemonSend,
3864
- onOrphanTakeover: opts.onOrphanTakeover,
3865
- sessionId: it.sessionId,
3866
- cwd: it.cwd
3867
- });
3868
- }
3869
- }
3870
- writeOutput(it.cwd, it.sessionId, it.askId, responses, completedAt);
3871
- await updateMeta(it.cwd, it.sessionId, it.askId, { status: "answered", completedAt });
3872
- const refreshRes = await opts.daemonSend({ type: "inbox-list" });
3873
- const newQueue = refreshRes.ok ? refreshRes.data?.items ?? [] : [];
3874
- if (newQueue.length === 0) {
3875
- opts.onUnmount();
3876
- return;
3877
- }
3878
- queue = newQueue;
3879
- currentIndex = 0;
3880
- const nextItem = queue[0];
3881
- const nextDeck = buildDeck(0);
3882
- if (!nextDeck) {
3883
- opts.onUnmount();
3884
- return;
3885
- }
3886
- currentDeck = nextDeck;
3887
- const nextProgress = readProgress(nextItem.cwd, nextItem.sessionId, nextItem.askId);
3888
- answeredCount = nextProgress?.responses.length ?? 0;
3889
- lastResponses = [];
3890
- state2.visuals.clear();
3891
- panel.loadDeck(nextDeck, {
3892
- progressPath: askProgressPath(nextItem.cwd, nextItem.sessionId, nextItem.askId)
3893
- });
3894
- requestRender();
3895
- })();
3896
- };
3897
- const panel = mountPanel({
3898
- deck: initialDeck,
3899
- cols: opts.cols,
3900
- rows: opts.rows,
3901
- progressPath: askProgressPath(item().cwd, item().sessionId, item().askId),
3902
- onProgress: (responses) => {
3903
- answeredCount = responses.length;
3904
- lastResponses = responses;
3905
- requestRender();
3906
- const it = item();
3907
- const cur = readMeta(it.cwd, it.sessionId, it.askId);
3908
- if (cur?.status === "pending") {
3909
- void updateMeta(it.cwd, it.sessionId, it.askId, { status: "in-progress", startedAt: (/* @__PURE__ */ new Date()).toISOString() });
3910
- }
3911
- },
3912
- onComplete: (responses) => {
3913
- submitResponses(responses);
3914
- },
3915
- // Final-phase Enter on an incomplete deck routes here (humanloop only fires
3916
- // onComplete when responses === interactions). The 'final' UI prompts
3917
- // "enter submit", so honor that by submitting whatever was answered.
3918
- onExit: () => {
3919
- submitResponses(lastResponses);
3920
- }
3921
- });
3922
- return {
3923
- handleKey(input, key) {
3924
- panel.handleKey(input, key);
3925
- },
3926
- render() {
3927
- return panel.render();
3928
- },
3929
- handleResize(cols, rows) {
3930
- bodyCols = cols;
3931
- panel.handleResize(cols, rows);
3932
- },
3933
- unmount() {
3934
- panel.unmount();
3935
- opts.onUnmount();
3936
- },
3937
- canAcceptHostKeys() {
3938
- return panel.canAcceptHostKeys();
3939
- },
3940
- advanceQueue(delta) {
3941
- const newIndex = Math.max(0, Math.min(queue.length - 1, currentIndex + delta));
3942
- if (newIndex === currentIndex) return;
3943
- currentIndex = newIndex;
3944
- const nextDeck = buildDeck(currentIndex);
3945
- if (!nextDeck) return;
3946
- currentDeck = nextDeck;
3947
- const it = item();
3948
- const progress = readProgress(it.cwd, it.sessionId, it.askId);
3949
- answeredCount = progress?.responses.length ?? 0;
3950
- panel.loadDeck(nextDeck, {
3951
- progressPath: askProgressPath(it.cwd, it.sessionId, it.askId)
3952
- });
3953
- requestRender();
3954
- },
3955
- spaceVisualToggle() {
3956
- const qid = getCurrentQid();
3957
- if (!qid) return;
3958
- const entry = state2.visuals.get(qid);
3959
- if (!entry) {
3960
- fireVisualGen(false);
3961
- } else if (entry.status === "ready") {
3962
- state2.visuals.set(qid, { ...entry, visible: !entry.visible });
3963
- requestRender();
3964
- } else if (entry.status === "loading") {
3965
- } else {
3966
- fireVisualGen(false);
3967
- }
3968
- },
3969
- regenerateVisual() {
3970
- const qid = getCurrentQid();
3971
- if (!qid) return;
3972
- state2.visuals.set(qid, { status: "loading", content: "", visible: true });
3973
- requestRender();
3974
- fireVisualGen(true);
3975
- },
3976
- getHeaderInfo() {
3977
- const it = item();
3978
- const askTitle = currentDeck.title ?? currentDeck.interactions[0]?.title;
3979
- return {
3980
- currentIndex,
3981
- queueLength: queue.length,
3982
- sessionName: it.sessionName,
3983
- askTitle: askTitle ? askTitle.slice(0, 32) : void 0,
3984
- blockedSince: it.blockedSince,
3985
- kind: it.kind
3986
- };
3987
- },
3988
- getCurrentQid
3989
- };
3990
- }
3991
- function enterResolutionMode(state2, askId2, daemonSend, onOrphanTakeover) {
3992
- const queue = state2.aggregateInbox;
3993
- const startIdx = queue.findIndex((item) => item.askId === askId2);
3994
- if (startIdx < 0) {
3995
- if (queue.length === 0) return;
3996
- enterResolutionMode(state2, queue[0].askId, daemonSend, onOrphanTakeover);
3997
- return;
3998
- }
3999
- const handle = mountResolutionPanel(
4000
- {
4001
- aggregateInbox: queue,
4002
- startIndex: startIdx,
4003
- cols: state2.cols,
4004
- rows: state2.rows - 1,
4005
- daemonSend,
4006
- onUnmount: () => {
4007
- state2.resolutionActive = false;
4008
- state2.resolutionHandle = null;
4009
- state2.visuals.clear();
4010
- requestRender();
4011
- },
4012
- onOrphanTakeover
4013
- },
4014
- state2
4015
- );
4016
- if (!handle) {
4017
- requestRender();
4018
- return;
4019
- }
4020
- state2.resolutionHandle = handle;
4021
- state2.resolutionActive = true;
4022
- requestRender();
4023
- }
4024
-
4025
- // src/shared/keymap.ts
4026
- var MENU_FOR_MODE = {
4027
- "leader": "topLevel",
4028
- "copy-menu": "copy",
4029
- "open-menu": "open",
4030
- "agent-menu": "agent",
4031
- "session-menu": "session",
4032
- "go-menu": "go",
4033
- "companion-menu": "companion"
4034
- };
4035
- var KEYMAP = {
4036
- version: 1,
4037
- topLevel: {
4038
- title: " Sisyphus ",
4039
- items: [
4040
- { key: "s", label: " Cycle session", action: { type: "script", name: "sisyphus-cycle" } },
4041
- { key: "h", label: " Home / dashboard", action: { type: "script", name: "sisyphus-home" } },
4042
- { key: "n", label: " New session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
4043
- { key: "m", label: " Message orchestrator", action: { type: "popup", name: "sisyphus-msg", popup: { w: "80%", h: "60%", cwd: "current" } } },
4044
- { key: "t", label: " Status (where am I?)", action: { type: "popup", name: "sisyphus-status-popup", popup: { w: "90%", h: "90%", cwd: "current" } } },
4045
- { key: "l", label: " Session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
4046
- { key: "z", label: " Zoom pane", action: { type: "tmux", cmd: "resize-pane -Z" } },
4047
- { key: "x", label: " Kill pane (smart)", action: { type: "script", name: "sisyphus-kill-pane" } },
4048
- { key: "?", label: " Full help reference", action: { type: "popup", name: "sisyphus-help", popup: { w: "80", h: "32", title: " Keybindings " } } },
4049
- { key: "/", label: " Search / filter", action: { type: "script", name: "sisyphus-search-reports" }, tuiAction: "search" },
4050
- { key: " ", label: " Open popup explicitly", action: { type: "tui", action: "show-leader" } },
4051
- { key: "y", label: " Yank \u203A", action: { type: "submenu", ref: "copy" } },
4052
- { key: "c", label: " Side claude pane", action: { type: "script", name: "sisyphus-companion-pane" }, tuiAction: "companion-pane" },
4053
- { key: "C", label: " Companion (gamification) \u203A", action: { type: "submenu", ref: "companion" } },
4054
- { key: "o", label: " Open \u203A", action: { type: "submenu", ref: "open" } },
4055
- { key: "a", label: " Agent \u203A", action: { type: "submenu", ref: "agent" } },
4056
- { key: "S", label: " Session \u203A", action: { type: "submenu", ref: "session" } },
4057
- { key: "g", label: " Go \u203A", action: { type: "submenu", ref: "go" } }
4058
- ]
4059
- },
4060
- submenus: {
4061
- companion: {
4062
- title: " Companion ",
4063
- items: [
4064
- { key: "p", label: " profile (overlay)", action: { type: "tui", action: "companion-overlay" } },
4065
- { key: "d", label: " debug (mood signals)", action: { type: "tui", action: "companion-debug" } }
4066
- ]
4067
- },
4068
- copy: {
4069
- title: " Copy ",
4070
- items: [
4071
- { key: "p", label: " session dir path", action: { type: "script", name: "sisyphus-copy-path" } },
4072
- { key: "i", label: " session UUID", action: { type: "script", name: "sisyphus-copy-id" } },
4073
- { key: "c", label: " full session context XML", action: { type: "script", name: "sisyphus-copy-context" } },
4074
- { key: "l", label: " logs (last 200 lines)", action: { type: "script", name: "sisyphus-copy-logs" } },
4075
- { key: "r", label: " latest report content", action: { type: "script", name: "sisyphus-copy-latest-report" } },
4076
- { key: "a", label: " agent ID (picker)", action: { type: "script", name: "sisyphus-copy-agent-id" } }
4077
- ]
4078
- },
4079
- open: {
4080
- title: " Open ",
4081
- items: [
4082
- { key: "g", label: " goal.md", action: { type: "popup", name: "sisyphus-open-goal", popup: { w: "95%", h: "95%", cwd: "current" } } },
4083
- { key: "r", label: " roadmap.md", action: { type: "popup", name: "sisyphus-open-roadmap", popup: { w: "95%", h: "95%", cwd: "current" } } },
4084
- { key: "s", label: " strategy.md", action: { type: "popup", name: "sisyphus-open-strategy", popup: { w: "95%", h: "95%", cwd: "current" } } },
4085
- { key: "l", label: " logs popup (tail)", action: { type: "popup", name: "sisyphus-open-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
4086
- { key: "d", label: " session dir in file mgr", action: { type: "script", name: "sisyphus-open-dir" } },
4087
- { key: "R", label: " latest report file", action: { type: "popup", name: "sisyphus-open-latest-report", popup: { w: "95%", h: "95%", cwd: "current" } } },
4088
- { key: "c", label: " scratch", action: { type: "popup", name: "sisyphus-open-scratch", popup: { w: "95%", h: "95%", cwd: "current" } } },
4089
- { key: "e", label: " edit context file", action: { type: "popup", name: "sisyphus-edit-context-file", popup: { w: "95%", h: "95%", cwd: "current" } }, tuiAction: "edit-context-file" }
4090
- ]
4091
- },
4092
- agent: {
4093
- title: " Agent ",
4094
- items: [
4095
- { key: "s", label: " spawn agent", action: { type: "popup", name: "sisyphus-spawn-agent", popup: { w: "80%", h: "70%", cwd: "current" } } },
4096
- { key: "m", label: " message agent (picker)", action: { type: "popup", name: "sisyphus-msg-agent", popup: { w: "80%", h: "60%", cwd: "current" } } },
4097
- { key: "r", label: " restart agent (picker)", action: { type: "popup", name: "sisyphus-restart-agent-popup", popup: { w: "70%", h: "50%", cwd: "current" } } },
4098
- { key: "R", label: " re-run agent (picker)", action: { type: "popup", name: "sisyphus-rerun-agent", popup: { w: "70%", h: "50%", cwd: "current" } } },
4099
- { key: "j", label: " jump to agent's pane", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
4100
- { key: "o", label: " open claude --resume", action: { type: "popup", name: "sisyphus-open-claude-agent", popup: { w: "60%", h: "60%", cwd: "current" } } },
4101
- { key: "t", label: " tail agent logs (picker)", action: { type: "popup", name: "sisyphus-tail-agent-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
4102
- { key: "k", label: " kill agent (picker)", action: { type: "popup", name: "sisyphus-kill-agent", popup: { w: "60%", h: "40%", cwd: "current" } } },
4103
- { key: "e", label: " quick-spawn Explore", action: { type: "script", name: "sisyphus-quick-spawn-explore" } },
4104
- { key: "d", label: " quick-spawn Debug", action: { type: "script", name: "sisyphus-quick-spawn-debug" } }
4105
- ]
4106
- },
4107
- session: {
4108
- title: " Session ",
4109
- items: [
4110
- { key: "n", label: " new session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
4111
- { key: "r", label: " resume", action: { type: "popup", name: "sisyphus-resume-session", popup: { w: "80%", h: "60%", cwd: "current" } } },
4112
- { key: "c", label: " continue", action: { type: "popup", name: "sisyphus-continue-session", popup: { w: "50", h: "5", borderStyle: "fg=yellow", title: " Continue Session ", cwd: "current" } } },
4113
- { key: "b", label: " rollback (prompts cycle)", action: { type: "popup", name: "sisyphus-rollback-session", popup: { w: "50", h: "5", title: " Rollback ", cwd: "current" } } },
4114
- { key: "k", label: " kill", action: { type: "popup", name: "sisyphus-kill-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Kill Session ", cwd: "current" } } },
4115
- { key: "d", label: " delete (confirms)", action: { type: "popup", name: "sisyphus-delete-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Delete Session ", cwd: "current" } } },
4116
- { key: "e", label: " export to ~/Downloads", action: { type: "popup", name: "sisyphus-export-session", popup: { w: "60", h: "8", title: " Export Session ", cwd: "current" } } },
4117
- { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
4118
- { key: "C", label: " clone (sisyphus session clone)", action: { type: "popup", name: "sisyphus-clone-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
4119
- { key: "i", label: " history", action: { type: "popup", name: "sisyphus-history", popup: { w: "95%", h: "95%", cwd: "current" } } }
4120
- ]
4121
- },
4122
- go: {
4123
- title: " Go ",
4124
- items: [
4125
- { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
4126
- { key: "p", label: " jump to pane (picker)", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
4127
- { key: "s", label: " session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
4128
- { key: "n", label: " next session", action: { type: "script", name: "sisyphus-cycle" } },
4129
- { key: "r", label: " reconnect", action: { type: "popup", name: "sisyphus-reconnect", popup: { w: "80%", h: "40%", cwd: "current" } } }
4130
- ]
4131
- }
4132
- }
4133
- };
4134
-
4135
- // src/tui/input.ts
4136
- function dispatchComposeAction(action, content, state2, actions) {
4137
- switch (action.kind) {
4138
- case "new-session":
4139
- actions.sendAndNotify(
4140
- { type: "start", task: content, cwd: state2.cwd },
4141
- "Session created"
4142
- );
4143
- break;
4144
- case "message-orchestrator":
4145
- actions.sendAndNotify(
4146
- { type: "message", sessionId: action.sessionId, content },
4147
- "Message queued"
4148
- );
4149
- break;
4150
- case "resume":
4151
- actions.sendAndNotify(
4152
- { type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
4153
- "Session resumed"
4154
- );
4155
- break;
4156
- case "continue":
4157
- void (async () => {
4158
- try {
4159
- const contRes = await actions.send({ type: "continue", sessionId: action.sessionId });
4160
- if (!contRes.ok) {
4161
- notify(state2, `Error: ${contRes.error}`);
4162
- return;
4163
- }
4164
- actions.sendAndNotify(
4165
- { type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
4166
- "Session continued"
4167
- );
4168
- } catch (err) {
4169
- notify(state2, `Error: ${err.message}`);
3981
+ const contRes = await actions.send({ type: "continue", sessionId: action.sessionId });
3982
+ if (!contRes.ok) {
3983
+ notify(state2, `Error: ${contRes.error}`);
3984
+ return;
3985
+ }
3986
+ actions.sendAndNotify(
3987
+ { type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
3988
+ "Session continued"
3989
+ );
3990
+ } catch (err) {
3991
+ notify(state2, `Error: ${err.message}`);
4170
3992
  }
4171
3993
  })();
4172
3994
  break;
@@ -4252,18 +4074,18 @@ var TUI_HANDLERS = {
4252
4074
  function findLatestReport(cwd2, sessionId2) {
4253
4075
  const dir = reportsDir(cwd2, sessionId2);
4254
4076
  try {
4255
- const files = readdirSync5(dir);
4077
+ const files = readdirSync2(dir);
4256
4078
  if (files.length === 0) return null;
4257
4079
  let latestFile = files[0];
4258
- let latestMtime = statSync3(join11(dir, latestFile)).mtimeMs;
4080
+ let latestMtime = statSync(join6(dir, latestFile)).mtimeMs;
4259
4081
  for (let i = 1; i < files.length; i++) {
4260
- const m = statSync3(join11(dir, files[i])).mtimeMs;
4082
+ const m = statSync(join6(dir, files[i])).mtimeMs;
4261
4083
  if (m > latestMtime) {
4262
4084
  latestMtime = m;
4263
4085
  latestFile = files[i];
4264
4086
  }
4265
4087
  }
4266
- return join11(dir, latestFile);
4088
+ return join6(dir, latestFile);
4267
4089
  } catch {
4268
4090
  return null;
4269
4091
  }
@@ -4629,7 +4451,7 @@ function handleLeaderAction(action, state2, actions) {
4629
4451
  break;
4630
4452
  }
4631
4453
  try {
4632
- const content = readFileSync11(latest, "utf-8");
4454
+ const content = readFileSync5(latest, "utf-8");
4633
4455
  actions.copyToClipboard(content);
4634
4456
  notify(state2, `Copied latest report (${content.length} chars)`);
4635
4457
  } catch {
@@ -4689,7 +4511,7 @@ function handleLeaderAction(action, state2, actions) {
4689
4511
  }
4690
4512
  const editor = actions.resolveEditor();
4691
4513
  try {
4692
- actions.openEditorPopup(state2.cwd, editor, join11(sessionDir(state2.cwd, selectedSessionId), "scratch.md"));
4514
+ actions.openEditorPopup(state2.cwd, editor, join6(sessionDir(state2.cwd, selectedSessionId), "scratch.md"));
4693
4515
  } catch {
4694
4516
  notify(state2, "Failed to open scratch");
4695
4517
  }
@@ -4896,7 +4718,7 @@ function handleLeaderAction(action, state2, actions) {
4896
4718
  break;
4897
4719
  }
4898
4720
  try {
4899
- actions.openShellPopup(state2.cwd, `sis session clone ${selectedSessionId}`);
4721
+ actions.openShellPopup(state2.cwd, `sis session recover clone ${selectedSessionId}`);
4900
4722
  } catch {
4901
4723
  notify(state2, "Failed to open shell");
4902
4724
  }
@@ -4904,7 +4726,7 @@ function handleLeaderAction(action, state2, actions) {
4904
4726
  }
4905
4727
  case "history": {
4906
4728
  try {
4907
- actions.openShellPopup(state2.cwd, "sis admin history");
4729
+ actions.openShellPopup(state2.cwd, "sis session inspect history");
4908
4730
  } catch {
4909
4731
  notify(state2, "Failed to open shell");
4910
4732
  }
@@ -4928,7 +4750,7 @@ function handleLeaderAction(action, state2, actions) {
4928
4750
  }
4929
4751
  case "reconnect": {
4930
4752
  try {
4931
- actions.openShellPopup(state2.cwd, "sis session reconnect");
4753
+ actions.openShellPopup(state2.cwd, "sis session recover reconnect");
4932
4754
  } catch {
4933
4755
  notify(state2, "Failed to open shell");
4934
4756
  }
@@ -4944,7 +4766,7 @@ function handleLeaderAction(action, state2, actions) {
4944
4766
  }
4945
4767
  case "show-status": {
4946
4768
  try {
4947
- actions.openShellPopup(state2.cwd, `sis status${selectedSessionId ? ` ${selectedSessionId}` : ""}`);
4769
+ actions.openShellPopup(state2.cwd, `sis session inspect status${selectedSessionId ? ` ${selectedSessionId}` : ""}`);
4948
4770
  } catch {
4949
4771
  notify(state2, "Failed to open status");
4950
4772
  }
@@ -5047,6 +4869,11 @@ function handleResolutionKey(input, key, state2, actions) {
5047
4869
  const handle = state2.resolutionHandle;
5048
4870
  if (!handle) return;
5049
4871
  if (key.escape) {
4872
+ if (!handle.atDeckTop()) {
4873
+ handle.handleKey(input, key);
4874
+ requestRender();
4875
+ return;
4876
+ }
5050
4877
  handle.unmount();
5051
4878
  return;
5052
4879
  }
@@ -5069,6 +4896,48 @@ function handleResolutionKey(input, key, state2, actions) {
5069
4896
  handle.handleKey(input, key);
5070
4897
  requestRender();
5071
4898
  }
4899
+ function handleInlineDeckKey(input, key, state2, actions) {
4900
+ const handle = state2.inlineDeck;
4901
+ if (!handle) return;
4902
+ if (key.escape) {
4903
+ if (!handle.atDeckTop()) {
4904
+ handle.handleKey(input, key);
4905
+ requestRender();
4906
+ return;
4907
+ }
4908
+ handle.unmount();
4909
+ const nodes = actions.getNodes();
4910
+ const i = nodes.findIndex((n) => n.id === "needs-you-virtual");
4911
+ const prev = i > 0 ? nodes[i - 1] : nodes.find((n) => n.type === "session") ?? nodes[0];
4912
+ state2.cursorNodeId = prev?.id ?? null;
4913
+ state2.focusPane = "tree";
4914
+ requestRender();
4915
+ return;
4916
+ }
4917
+ if (key.tab && key.shift) {
4918
+ state2.focusPane = "tree";
4919
+ requestRender();
4920
+ return;
4921
+ }
4922
+ if (input === "J" && handle.canAcceptHostKeys()) {
4923
+ handle.advanceQueue(1);
4924
+ return;
4925
+ }
4926
+ if (input === "K" && handle.canAcceptHostKeys()) {
4927
+ handle.advanceQueue(-1);
4928
+ return;
4929
+ }
4930
+ if (input === " " && handle.canAcceptHostKeys()) {
4931
+ handle.spaceVisualToggle();
4932
+ return;
4933
+ }
4934
+ if (input === "R" && handle.canAcceptHostKeys()) {
4935
+ handle.regenerateVisual();
4936
+ return;
4937
+ }
4938
+ handle.handleKey(input, key);
4939
+ requestRender();
4940
+ }
5072
4941
  function focusCycle(state2) {
5073
4942
  const stops = [{ pane: "tree" }];
5074
4943
  if (state2.useStackedDetail && state2.detailMode === "gsr") {
@@ -5170,6 +5039,11 @@ function handleNavigateKey(input, key, state2, actions) {
5170
5039
  if (key.rightArrow || input === "l") {
5171
5040
  const node = nodes[state2.cursorIndex];
5172
5041
  if (!node) return;
5042
+ if (node.type === "needs-you-virtual" && state2.focusPane === "tree") {
5043
+ state2.focusPane = "detail";
5044
+ requestRender();
5045
+ return;
5046
+ }
5173
5047
  if (state2.useStackedDetail && state2.focusPane === "tree" && node.type === "session" && node.expanded) {
5174
5048
  state2.detailMode = state2.detailMode === "gsr" ? "cycle-log" : "gsr";
5175
5049
  state2.cachedStackedLines = null;
@@ -5207,29 +5081,7 @@ function handleNavigateKey(input, key, state2, actions) {
5207
5081
  if (key.return) {
5208
5082
  const node = nodes[state2.cursorIndex];
5209
5083
  if (!node) return;
5210
- if (node.type === "needs-you-virtual") {
5211
- const firstItem = state2.aggregateInbox[0];
5212
- if (firstItem) {
5213
- enterResolutionMode(state2, firstItem.askId, actions.send, async ({ sessionId: sessionId2, agentId, paneId }) => {
5214
- const res = await actions.send({ type: "status", sessionId: sessionId2 });
5215
- const sess = res.ok ? res.data?.session : void 0;
5216
- if (!sess) {
5217
- notify(state2, "Session not found");
5218
- return;
5219
- }
5220
- if (paneId && actions.paneExists(paneId)) {
5221
- if (sess.tmuxSessionName) actions.switchToSession(sess.tmuxSessionName);
5222
- if (sess.tmuxWindowId) actions.selectWindow(sess.tmuxWindowId);
5223
- actions.selectPane(paneId);
5224
- return;
5225
- }
5226
- if (sess.tmuxSessionName) actions.switchToSession(sess.tmuxSessionName);
5227
- notify(state2, `Pane ${paneId ? paneId : "?"} is gone \u2014 agent ${agentId} cannot be taken over.`);
5228
- });
5229
- } else {
5230
- notify(state2, "No pending asks");
5231
- }
5232
- } else if (node.expandable && !node.expanded) {
5084
+ if (node.expandable && !node.expanded) {
5233
5085
  state2.expanded.add(node.id);
5234
5086
  expandSessionLatestCycle(state2, node);
5235
5087
  requestRender();
@@ -5510,6 +5362,28 @@ function handleKeypress(input, key, state2, actions) {
5510
5362
  handleResolutionKey(input, key, state2, actions);
5511
5363
  return;
5512
5364
  }
5365
+ if (state2.reviewPanel && state2.focusPane === "detail" && actions.getCursorNode()?.type === "needs-you-virtual") {
5366
+ if (key.escape) {
5367
+ state2.reviewPanel = null;
5368
+ state2.focusPane = "tree";
5369
+ requestRender();
5370
+ return;
5371
+ }
5372
+ if (key.tab && key.shift) {
5373
+ state2.focusPane = "tree";
5374
+ requestRender();
5375
+ return;
5376
+ }
5377
+ const consumed = state2.reviewPanel.handleKey(input);
5378
+ if (consumed) {
5379
+ requestRender();
5380
+ return;
5381
+ }
5382
+ }
5383
+ if (state2.inlineDeck && state2.focusPane === "detail" && actions.getCursorNode()?.type === "needs-you-virtual") {
5384
+ handleInlineDeckKey(input, key, state2, actions);
5385
+ return;
5386
+ }
5513
5387
  if (state2.mode === "search") {
5514
5388
  handleSearchKey(input, key, state2);
5515
5389
  } else if (state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "open-menu" || state2.mode === "agent-menu" || state2.mode === "session-menu" || state2.mode === "go-menu" || state2.mode === "companion-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
@@ -5596,10 +5470,10 @@ function precomputePrefixes(nodes) {
5596
5470
  }
5597
5471
 
5598
5472
  // src/tui/lib/reports.ts
5599
- import { readFileSync as readFileSync12 } from "fs";
5473
+ import { readFileSync as readFileSync6 } from "fs";
5600
5474
  function loadReportContent(report) {
5601
5475
  try {
5602
- return readFileSync12(report.filePath, "utf-8");
5476
+ return readFileSync6(report.filePath, "utf-8");
5603
5477
  } catch {
5604
5478
  return report.summary;
5605
5479
  }
@@ -5613,6 +5487,44 @@ function resolveReports(reports) {
5613
5487
  }));
5614
5488
  }
5615
5489
 
5490
+ // src/shared/client.ts
5491
+ init_paths();
5492
+ import { connect } from "net";
5493
+ function rawSend(request, timeoutMs = 1e4) {
5494
+ const sock = socketPath();
5495
+ return new Promise((resolve2, reject) => {
5496
+ const socket = connect(sock);
5497
+ let data = "";
5498
+ const timeout = setTimeout(() => {
5499
+ socket.destroy();
5500
+ reject(new Error(`Request timed out after ${(timeoutMs / 1e3).toFixed(0)}s. The daemon may be overloaded.
5501
+ Check: sis admin check doctor
5502
+ Logs: tail -20 ~/.sisyphus/daemon.log`));
5503
+ }, timeoutMs);
5504
+ socket.on("connect", () => {
5505
+ socket.write(JSON.stringify(request) + "\n");
5506
+ });
5507
+ socket.on("data", (chunk) => {
5508
+ data += chunk.toString();
5509
+ const newlineIdx = data.indexOf("\n");
5510
+ if (newlineIdx !== -1) {
5511
+ clearTimeout(timeout);
5512
+ const line = data.slice(0, newlineIdx);
5513
+ socket.destroy();
5514
+ try {
5515
+ resolve2(JSON.parse(line));
5516
+ } catch {
5517
+ reject(new Error(`Invalid JSON response from daemon: ${line}`));
5518
+ }
5519
+ }
5520
+ });
5521
+ socket.on("error", (err) => {
5522
+ clearTimeout(timeout);
5523
+ reject(err);
5524
+ });
5525
+ });
5526
+ }
5527
+
5616
5528
  // src/tui/lib/client.ts
5617
5529
  function send(request) {
5618
5530
  return rawSend(request, 8e3);
@@ -5626,8 +5538,8 @@ async function inboxList() {
5626
5538
  // src/tui/lib/tmux.ts
5627
5539
  init_paths();
5628
5540
  import { execSync as execSync3 } from "child_process";
5629
- import { join as join12 } from "path";
5630
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdtempSync, rmSync as rmSync4, cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync8 } from "fs";
5541
+ import { join as join7 } from "path";
5542
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdtempSync, rmSync as rmSync2, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
5631
5543
  import { tmpdir } from "os";
5632
5544
  init_shell();
5633
5545
 
@@ -5693,10 +5605,10 @@ function registerDashboardWindow(cwd2) {
5693
5605
  }
5694
5606
  }
5695
5607
  function setupCompanionPlugin() {
5696
- const srcDir = join12(import.meta.dirname, "templates", "companion-plugin");
5697
- const destDir = join12(globalDir(), "companion-plugin");
5698
- if (!existsSync9(destDir)) mkdirSync8(destDir, { recursive: true });
5699
- cpSync2(srcDir, destDir, { recursive: true });
5608
+ const srcDir = join7(import.meta.dirname, "templates", "companion-plugin");
5609
+ const destDir = join7(globalDir(), "companion-plugin");
5610
+ if (!existsSync5(destDir)) mkdirSync4(destDir, { recursive: true });
5611
+ cpSync(srcDir, destDir, { recursive: true });
5700
5612
  return destDir;
5701
5613
  }
5702
5614
  function paneExists(paneId) {
@@ -5722,18 +5634,18 @@ function openCompanionPane(cwd2) {
5722
5634
  return;
5723
5635
  }
5724
5636
  const pluginDir = setupCompanionPlugin();
5725
- const templatePath = join12(import.meta.dirname, "templates", "dashboard-claude.md");
5637
+ const templatePath = join7(import.meta.dirname, "templates", "dashboard-claude.md");
5726
5638
  let template;
5727
5639
  try {
5728
- template = readFileSync13(templatePath, "utf-8");
5640
+ template = readFileSync7(templatePath, "utf-8");
5729
5641
  } catch {
5730
5642
  template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
5731
5643
  Project: ${cwd2}
5732
- Run \`sis list\` and \`sis status\` to see current state.`;
5644
+ Run \`sis session inspect list\` and \`sis session inspect status\` to see current state.`;
5733
5645
  }
5734
5646
  const rendered = template.replace(/\{\{CWD\}\}/g, cwd2);
5735
- const promptPath = join12(globalDir(), "dashboard-companion-prompt.md");
5736
- writeFileSync9(promptPath, rendered, "utf-8");
5647
+ const promptPath = join7(globalDir(), "dashboard-companion-prompt.md");
5648
+ writeFileSync4(promptPath, rendered, "utf-8");
5737
5649
  const pathEnv = augmentedPath();
5738
5650
  const claudeCmd = `SISYPHUS_COMPANION_CWD=${shellQuote(cwd2)} PATH=${shellQuote(pathEnv)} claude --dangerously-skip-permissions --plugin-dir ${shellQuote(pluginDir)} --append-system-prompt "$(cat ${shellQuote(promptPath)})"`;
5739
5651
  const result = exec(
@@ -5749,32 +5661,32 @@ function switchToSession(sessionName) {
5749
5661
  execSafe(`tmux switch-client -t ${shellQuote(sessionName)}`);
5750
5662
  }
5751
5663
  function editInPopup(cwd2, editor, opts) {
5752
- const tmpDir = mkdtempSync(join12(tmpdir(), "sisyphus-"));
5753
- const filePath = join12(tmpDir, "input.md");
5664
+ const tmpDir = mkdtempSync(join7(tmpdir(), "sisyphus-"));
5665
+ const filePath = join7(tmpDir, "input.md");
5754
5666
  try {
5755
- writeFileSync9(filePath, opts?.content ? opts.content : "", "utf-8");
5667
+ writeFileSync4(filePath, opts?.content ? opts.content : "", "utf-8");
5756
5668
  openEditorPopup(cwd2, editor, filePath, opts?.size);
5757
- const result = readFileSync13(filePath, "utf-8").trim();
5669
+ const result = readFileSync7(filePath, "utf-8").trim();
5758
5670
  return result || null;
5759
5671
  } finally {
5760
- rmSync4(tmpDir, { recursive: true, force: true });
5672
+ rmSync2(tmpDir, { recursive: true, force: true });
5761
5673
  }
5762
5674
  }
5763
5675
  function promptInPopup(prompt, opts) {
5764
5676
  const { w = "50%", h = "3" } = opts ?? {};
5765
- const tmpDir = mkdtempSync(join12(tmpdir(), "sisyphus-"));
5766
- const outFile = join12(tmpDir, "result");
5677
+ const tmpDir = mkdtempSync(join7(tmpdir(), "sisyphus-"));
5678
+ const outFile = join7(tmpDir, "result");
5767
5679
  try {
5768
5680
  const script = `printf ${shellQuote(prompt + " ")} && read -r line && printf '%s' "$line" > ${shellQuote(outFile)}`;
5769
5681
  execSync3(
5770
5682
  `tmux display-popup -E -w ${w} -h ${h} ${shellQuote(`bash -c ${shellQuote(script)}`)}`,
5771
5683
  { stdio: "inherit", env: EXEC_ENV }
5772
5684
  );
5773
- if (!existsSync9(outFile)) return null;
5774
- const result = readFileSync13(outFile, "utf-8").trim();
5685
+ if (!existsSync5(outFile)) return null;
5686
+ const result = readFileSync7(outFile, "utf-8").trim();
5775
5687
  return result || null;
5776
5688
  } finally {
5777
- rmSync4(tmpDir, { recursive: true, force: true });
5689
+ rmSync2(tmpDir, { recursive: true, force: true });
5778
5690
  }
5779
5691
  }
5780
5692
  function openLogPopup() {
@@ -5868,10 +5780,10 @@ function openEditorPopup(cwd2, editor, filePath, size) {
5868
5780
 
5869
5781
  // src/tui/lib/context.ts
5870
5782
  init_paths();
5871
- import { readFileSync as readFileSync14, readdirSync as readdirSync6 } from "fs";
5783
+ import { readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
5872
5784
  function readFileSafe(filePath) {
5873
5785
  try {
5874
- return readFileSync14(filePath, "utf-8");
5786
+ return readFileSync8(filePath, "utf-8");
5875
5787
  } catch {
5876
5788
  return null;
5877
5789
  }
@@ -6765,12 +6677,6 @@ function buildCycleFlowLines(session, width, expanded) {
6765
6677
  return lines;
6766
6678
  }
6767
6679
 
6768
- // src/shared/inbox-types.ts
6769
- function coerceKind(k) {
6770
- if (k !== void 0) return k;
6771
- return "validation";
6772
- }
6773
-
6774
6680
  // src/tui/panels/detail.ts
6775
6681
  function buildPlanLines(content, maxLines, width) {
6776
6682
  const clean = stripFrontmatter(content);
@@ -7418,7 +7324,7 @@ function formatTimestampShort(iso) {
7418
7324
  function renderFleetRollup(rect, state2, focused) {
7419
7325
  const items = state2.aggregateInbox;
7420
7326
  const sessions = state2.sessions;
7421
- const cacheKey = `rollup:${items.length}:${sessions.length}:${items.map((i) => `${i.askId}:${i.status}`).join(",")}:${rect.w}`;
7327
+ const cacheKey = `rollup:${items.length}:${sessions.length}:${items.map((i) => `${askIdFromDir(i.dir)}:${i.blockedSince}`).join(",")}:${rect.w}`;
7422
7328
  let lines;
7423
7329
  if (cacheKey === state2.digestCacheKey && state2.cachedDigestLines !== null) {
7424
7330
  lines = state2.cachedDigestLines;
@@ -7432,7 +7338,7 @@ function renderFleetRollup(rect, state2, focused) {
7432
7338
  for (const s of sessions) {
7433
7339
  byStatus.set(s.status, (byStatus.get(s.status) ?? 0) + 1);
7434
7340
  }
7435
- const uniqueSessions = new Set(items.map((i) => i.sessionId)).size;
7341
+ const uniqueSessions = new Set(items.map((i) => sessionIdFromDir(i.dir))).size;
7436
7342
  lines = [];
7437
7343
  lines.push([seg(" Fleet Inbox", { color: "red", bold: true })]);
7438
7344
  lines.push(singleLine(` ${items.length} pending across ${uniqueSessions} sessions`, { dim: true }));
@@ -7571,836 +7477,1318 @@ var HEADING_STYLES = [
7571
7477
  function stripDisplayHazards(s) {
7572
7478
  return s.replace(/✅/g, "\u2713").replace(/❌/g, "\u2717").replace(new RegExp("\\p{Emoji_Presentation}", "gu"), "");
7573
7479
  }
7574
- var INLINE_RE = /\*\*([^*\n]+)\*\*|__([^_\n]+)__|\*([^*\n]+)\*|_([^_\n]+)_|`([^`\n]+)`|~~([^~\n]+)~~|\[([^\]\n]+)\]\(([^)\n]+)\)/g;
7575
- function tokenizeInline(line, baseFg, baseStyle) {
7480
+ var INLINE_RE = /\*\*([^*\n]+)\*\*|__([^_\n]+)__|\*([^*\n]+)\*|_([^_\n]+)_|`([^`\n]+)`|~~([^~\n]+)~~|\[([^\]\n]+)\]\(([^)\n]+)\)/g;
7481
+ function tokenizeInline(line, baseFg, baseStyle) {
7482
+ const out = [];
7483
+ const baseSeg = (text) => ({ text, fg: baseFg, ...baseStyle });
7484
+ let cursor = 0;
7485
+ for (const m of line.matchAll(INLINE_RE)) {
7486
+ const idx = m.index;
7487
+ if (idx > cursor) out.push(baseSeg(line.slice(cursor, idx)));
7488
+ if (m[1] !== void 0) {
7489
+ out.push({ text: m[1], fg: GLOAM.fg0, bold: true, bg: baseStyle?.bg });
7490
+ } else if (m[2] !== void 0) {
7491
+ out.push({ text: m[2], fg: GLOAM.fg0, bold: true, bg: baseStyle?.bg });
7492
+ } else if (m[3] !== void 0) {
7493
+ out.push({ text: m[3], fg: GLOAM.fg0, italic: true, bg: baseStyle?.bg });
7494
+ } else if (m[4] !== void 0) {
7495
+ out.push({ text: m[4], fg: GLOAM.fg0, italic: true, bg: baseStyle?.bg });
7496
+ } else if (m[5] !== void 0) {
7497
+ out.push({
7498
+ text: m[5],
7499
+ fg: GLOAM.aqua,
7500
+ bg: baseStyle?.bg ?? GLOAM.bg_bg1
7501
+ });
7502
+ } else if (m[6] !== void 0) {
7503
+ out.push({ text: m[6], fg: GLOAM.fg3, strikethrough: true, bg: baseStyle?.bg });
7504
+ } else if (m[7] !== void 0 && m[8] !== void 0) {
7505
+ out.push({ text: m[7], fg: GLOAM.blue, bold: true, bg: baseStyle?.bg });
7506
+ }
7507
+ cursor = idx + m[0].length;
7508
+ }
7509
+ if (cursor < line.length) out.push(baseSeg(line.slice(cursor)));
7510
+ if (out.length === 0) out.push(baseSeg(""));
7511
+ return out;
7512
+ }
7513
+ function segsDisplayWidth(segs) {
7514
+ let w = 0;
7515
+ for (const s of segs) w += stringWidth6(s.text);
7516
+ return w;
7517
+ }
7518
+ function segsToAtoms(segs) {
7519
+ const atoms = [];
7520
+ for (const s of segs) {
7521
+ if (!s.text) continue;
7522
+ const { text, ...style } = s;
7523
+ const re = /(\s+|\S+)/g;
7524
+ for (const m of text.matchAll(re)) {
7525
+ const piece = m[0];
7526
+ atoms.push({
7527
+ text: piece,
7528
+ width: stringWidth6(piece),
7529
+ style,
7530
+ space: /^\s+$/.test(piece)
7531
+ });
7532
+ }
7533
+ }
7534
+ return atoms;
7535
+ }
7536
+ function wrapSegs(segs, width, contIndent) {
7537
+ if (width <= 0) return [segs];
7538
+ const atoms = segsToAtoms(segs);
7539
+ if (atoms.length === 0) return [[{ text: "" }]];
7540
+ const lines = [];
7541
+ let current = [];
7542
+ let currentWidth = 0;
7543
+ const pushAtom = (a) => {
7544
+ current.push({ ...a.style, text: a.text });
7545
+ currentWidth += a.width;
7546
+ };
7547
+ const flushLine = () => {
7548
+ while (current.length > 0) {
7549
+ const last = current[current.length - 1];
7550
+ if (/^\s+$/.test(last.text) && !last.bg) {
7551
+ currentWidth -= stringWidth6(last.text);
7552
+ current.pop();
7553
+ } else break;
7554
+ }
7555
+ lines.push(current.length > 0 ? current : [{ text: "" }]);
7556
+ current = [];
7557
+ currentWidth = 0;
7558
+ };
7559
+ for (let i = 0; i < atoms.length; i++) {
7560
+ const atom = atoms[i];
7561
+ if (atom.space) {
7562
+ if (currentWidth + atom.width <= width) pushAtom(atom);
7563
+ continue;
7564
+ }
7565
+ if (atom.width > width) {
7566
+ let remaining = atom.text;
7567
+ while (remaining.length > 0) {
7568
+ const spaceLeft = width - currentWidth;
7569
+ if (spaceLeft <= 0) {
7570
+ flushLine();
7571
+ if (contIndent) {
7572
+ current.push({ text: contIndent });
7573
+ currentWidth = stringWidth6(contIndent);
7574
+ }
7575
+ continue;
7576
+ }
7577
+ let cut = 0;
7578
+ let cutW = 0;
7579
+ for (let k = 0; k < remaining.length; k++) {
7580
+ const cw = stringWidth6(remaining[k]);
7581
+ if (cutW + cw > spaceLeft) break;
7582
+ cutW += cw;
7583
+ cut = k + 1;
7584
+ }
7585
+ if (cut === 0) {
7586
+ flushLine();
7587
+ if (contIndent) {
7588
+ current.push({ text: contIndent });
7589
+ currentWidth = stringWidth6(contIndent);
7590
+ }
7591
+ continue;
7592
+ }
7593
+ current.push({ ...atom.style, text: remaining.slice(0, cut) });
7594
+ currentWidth += cutW;
7595
+ remaining = remaining.slice(cut);
7596
+ if (remaining.length > 0) {
7597
+ flushLine();
7598
+ if (contIndent) {
7599
+ current.push({ text: contIndent });
7600
+ currentWidth = stringWidth6(contIndent);
7601
+ }
7602
+ }
7603
+ }
7604
+ continue;
7605
+ }
7606
+ if (currentWidth + atom.width > width) {
7607
+ flushLine();
7608
+ if (contIndent) {
7609
+ current.push({ text: contIndent });
7610
+ currentWidth = stringWidth6(contIndent);
7611
+ }
7612
+ }
7613
+ pushAtom(atom);
7614
+ }
7615
+ flushLine();
7616
+ return lines.length > 0 ? lines : [[{ text: "" }]];
7617
+ }
7618
+ function buildHeadingLine(level, rawText, innerW) {
7619
+ const style = HEADING_STYLES[Math.min(level - 1, HEADING_STYLES.length - 1)];
7620
+ const cleanedText = stripDisplayHazards(rawText).trim();
7621
+ const marker = "#".repeat(level);
7622
+ const prefix = " ";
7623
+ const sep = " ";
7624
+ const headerSegs = [
7625
+ { text: prefix, bg: style.bg },
7626
+ { text: marker, fg: style.markerFg, bg: style.bg, bold: true },
7627
+ { text: sep, bg: style.bg },
7628
+ { text: cleanedText, fg: style.textFg, bg: style.bg, bold: true }
7629
+ ];
7630
+ const used = segsDisplayWidth(headerSegs);
7631
+ const padW = Math.max(0, innerW - used);
7632
+ if (padW > 0) {
7633
+ headerSegs.push({ text: " ".repeat(padW), bg: style.bg });
7634
+ }
7635
+ return headerSegs;
7636
+ }
7637
+ function buildListLine(marker, body, innerW, prefixIndent, markerFg) {
7638
+ const indent = `${prefixIndent} `;
7639
+ const head = [
7640
+ { text: prefixIndent, fg: GLOAM.fg2 },
7641
+ { text: marker, fg: markerFg, bold: true },
7642
+ { text: " ", fg: GLOAM.fg2 }
7643
+ ];
7644
+ const headW = segsDisplayWidth(head);
7645
+ const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg1);
7646
+ const wrapped = wrapSegs([...head, ...bodySegs], innerW, indent);
7647
+ return wrapped;
7648
+ }
7649
+ function buildCheckboxLine(checked, body, innerW) {
7650
+ const icon = checked ? "\u2611" : "\u2610";
7651
+ const iconFg = checked ? GLOAM.green : GLOAM.fg4;
7652
+ const head = [
7653
+ { text: " ", fg: GLOAM.fg2 },
7654
+ { text: icon, fg: iconFg, bold: true },
7655
+ { text: " ", fg: GLOAM.fg2 }
7656
+ ];
7657
+ const bodyFg = checked ? GLOAM.fg3 : GLOAM.fg1;
7658
+ const bodyStyle = checked ? { strikethrough: true } : {};
7659
+ const bodySegs = tokenizeInline(stripDisplayHazards(body), bodyFg, bodyStyle);
7660
+ return wrapSegs([...head, ...bodySegs], innerW, " ");
7661
+ }
7662
+ function buildQuoteLine(body, innerW) {
7663
+ const head = [
7664
+ { text: " ", fg: GLOAM.fg2 },
7665
+ { text: "\u258E ", fg: GLOAM.fg3 }
7666
+ ];
7667
+ const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg2, { italic: true });
7668
+ return wrapSegs([...head, ...bodySegs], innerW, " ");
7669
+ }
7670
+ function buildHrLine(innerW) {
7671
+ const w = Math.max(2, innerW - 4);
7672
+ return [
7673
+ { text: " ", fg: GLOAM.fg4 },
7674
+ { text: "\u2500".repeat(w), fg: GLOAM.fg4 }
7675
+ ];
7676
+ }
7677
+ function buildCodeFenceLine(fence, innerW) {
7678
+ return [
7679
+ { text: " ", fg: GLOAM.fg4 },
7680
+ {
7681
+ text: fence + " ".repeat(Math.max(0, innerW - 2 - stringWidth6(fence))),
7682
+ fg: GLOAM.fg4,
7683
+ bg: GLOAM.bg_bg1
7684
+ }
7685
+ ];
7686
+ }
7687
+ function buildCodeLine(content, innerW) {
7688
+ const cleaned = stripDisplayHazards(content);
7689
+ const cw = stringWidth6(cleaned);
7690
+ const padW = Math.max(0, innerW - 2 - cw);
7691
+ return [
7692
+ { text: " ", bg: GLOAM.bg_bg1 },
7693
+ { text: cleaned, fg: GLOAM.aqua, bg: GLOAM.bg_bg1 },
7694
+ ...padW > 0 ? [{ text: " ".repeat(padW), bg: GLOAM.bg_bg1 }] : []
7695
+ ];
7696
+ }
7697
+ function buildParagraphLines(body, innerW) {
7698
+ const head = [{ text: " ", fg: GLOAM.fg1 }];
7699
+ const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg1);
7700
+ return wrapSegs([...head, ...bodySegs], innerW, " ");
7701
+ }
7702
+ function parseTableCells(line) {
7703
+ let s = line.trim();
7704
+ if (s.startsWith("|")) s = s.slice(1);
7705
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
7706
+ const cells = [];
7707
+ let cur = "";
7708
+ for (let i = 0; i < s.length; i++) {
7709
+ const ch = s[i];
7710
+ if (ch === "\\" && s[i + 1] === "|") {
7711
+ cur += "|";
7712
+ i++;
7713
+ } else if (ch === "|") {
7714
+ cells.push(cur.trim());
7715
+ cur = "";
7716
+ } else {
7717
+ cur += ch;
7718
+ }
7719
+ }
7720
+ cells.push(cur.trim());
7721
+ return cells;
7722
+ }
7723
+ function parseTableSeparator(line) {
7724
+ if (!line.includes("|") && !/^[\s:|+-]+$/.test(line)) return null;
7725
+ const cells = parseTableCells(line);
7726
+ if (cells.length === 0) return null;
7727
+ const aligns = [];
7728
+ for (const c of cells) {
7729
+ const m = c.match(/^(:?)\s*-{2,}\s*(:?)$/);
7730
+ if (!m) return null;
7731
+ if (m[1] === ":" && m[2] === ":") aligns.push("center");
7732
+ else if (m[2] === ":") aligns.push("right");
7733
+ else aligns.push("left");
7734
+ }
7735
+ return aligns;
7736
+ }
7737
+ function padCell(text, width, align) {
7738
+ const w = stringWidth6(text);
7739
+ const pad = Math.max(0, width - w);
7740
+ let left = 0;
7741
+ let right = 0;
7742
+ if (align === "right") left = pad;
7743
+ else if (align === "center") {
7744
+ left = Math.floor(pad / 2);
7745
+ right = pad - left;
7746
+ } else right = pad;
7747
+ return " ".repeat(left) + text + " ".repeat(right);
7748
+ }
7749
+ function wrapCell(text, width, align) {
7750
+ if (width <= 0) return [""];
7751
+ const cleaned = cleanMarkdown(text);
7752
+ if (cleaned === "") return [padCell("", width, align)];
7576
7753
  const out = [];
7577
- const baseSeg = (text) => ({ text, fg: baseFg, ...baseStyle });
7578
- let cursor = 0;
7579
- for (const m of line.matchAll(INLINE_RE)) {
7580
- const idx = m.index;
7581
- if (idx > cursor) out.push(baseSeg(line.slice(cursor, idx)));
7582
- if (m[1] !== void 0) {
7583
- out.push({ text: m[1], fg: GLOAM.fg0, bold: true, bg: baseStyle?.bg });
7584
- } else if (m[2] !== void 0) {
7585
- out.push({ text: m[2], fg: GLOAM.fg0, bold: true, bg: baseStyle?.bg });
7586
- } else if (m[3] !== void 0) {
7587
- out.push({ text: m[3], fg: GLOAM.fg0, italic: true, bg: baseStyle?.bg });
7588
- } else if (m[4] !== void 0) {
7589
- out.push({ text: m[4], fg: GLOAM.fg0, italic: true, bg: baseStyle?.bg });
7590
- } else if (m[5] !== void 0) {
7591
- out.push({
7592
- text: m[5],
7593
- fg: GLOAM.aqua,
7594
- bg: baseStyle?.bg ?? GLOAM.bg_bg1
7595
- });
7596
- } else if (m[6] !== void 0) {
7597
- out.push({ text: m[6], fg: GLOAM.fg3, strikethrough: true, bg: baseStyle?.bg });
7598
- } else if (m[7] !== void 0 && m[8] !== void 0) {
7599
- out.push({ text: m[7], fg: GLOAM.blue, bold: true, bg: baseStyle?.bg });
7754
+ let cur = "";
7755
+ let curW = 0;
7756
+ const flush = () => {
7757
+ out.push(padCell(cur.replace(/\s+$/, ""), width, align));
7758
+ cur = "";
7759
+ curW = 0;
7760
+ };
7761
+ for (const piece of cleaned.match(/\s+|\S+/g) ?? []) {
7762
+ const isSpace = /^\s+$/.test(piece);
7763
+ const pw = stringWidth6(piece);
7764
+ if (isSpace) {
7765
+ if (curW === 0) continue;
7766
+ if (curW + pw > width) flush();
7767
+ else {
7768
+ cur += piece;
7769
+ curW += pw;
7770
+ }
7771
+ continue;
7600
7772
  }
7601
- cursor = idx + m[0].length;
7773
+ if (curW + pw <= width) {
7774
+ cur += piece;
7775
+ curW += pw;
7776
+ continue;
7777
+ }
7778
+ if (curW > 0) flush();
7779
+ if (pw > width) {
7780
+ let rem = piece;
7781
+ while (rem.length > 0) {
7782
+ let cut = 0;
7783
+ let cutW = 0;
7784
+ for (let k = 0; k < rem.length; k++) {
7785
+ const cw = stringWidth6(rem[k]);
7786
+ if (cutW + cw > width) break;
7787
+ cutW += cw;
7788
+ cut = k + 1;
7789
+ }
7790
+ if (cut === 0) cut = 1;
7791
+ const slice = rem.slice(0, cut);
7792
+ if (cut === rem.length) {
7793
+ cur = slice;
7794
+ curW = stringWidth6(slice);
7795
+ } else {
7796
+ out.push(padCell(slice, width, align));
7797
+ }
7798
+ rem = rem.slice(cut);
7799
+ }
7800
+ continue;
7801
+ }
7802
+ cur = piece;
7803
+ curW = pw;
7602
7804
  }
7603
- if (cursor < line.length) out.push(baseSeg(line.slice(cursor)));
7604
- if (out.length === 0) out.push(baseSeg(""));
7805
+ if (curW > 0 || out.length === 0) flush();
7605
7806
  return out;
7606
7807
  }
7607
- function segsDisplayWidth(segs) {
7608
- let w = 0;
7609
- for (const s of segs) w += stringWidth6(s.text);
7610
- return w;
7611
- }
7612
- function segsToAtoms(segs) {
7613
- const atoms = [];
7614
- for (const s of segs) {
7615
- if (!s.text) continue;
7616
- const { text, ...style } = s;
7617
- const re = /(\s+|\S+)/g;
7618
- for (const m of text.matchAll(re)) {
7619
- const piece = m[0];
7620
- atoms.push({
7621
- text: piece,
7622
- width: stringWidth6(piece),
7623
- style,
7624
- space: /^\s+$/.test(piece)
7625
- });
7808
+ function buildTableLines(headers, alignsIn, rowsIn, innerW) {
7809
+ const ncols = headers.length;
7810
+ if (ncols === 0) return [];
7811
+ const aligns = [];
7812
+ for (let i = 0; i < ncols; i++) {
7813
+ const a = alignsIn[i];
7814
+ aligns.push(a === void 0 ? "left" : a);
7815
+ }
7816
+ const rows = rowsIn.map((r) => {
7817
+ const out2 = [];
7818
+ for (let i = 0; i < ncols; i++) {
7819
+ const c = r[i];
7820
+ out2.push(c === void 0 ? "" : c);
7821
+ }
7822
+ return out2;
7823
+ });
7824
+ const naturalW = new Array(ncols).fill(0);
7825
+ const measure = (cells) => {
7826
+ for (let i = 0; i < ncols; i++) {
7827
+ const w = stringWidth6(cleanMarkdown(cells[i]));
7828
+ if (w > naturalW[i]) naturalW[i] = w;
7829
+ }
7830
+ };
7831
+ measure(headers);
7832
+ for (const r of rows) measure(r);
7833
+ const margin = 2;
7834
+ const overhead = 1 + ncols * 3;
7835
+ const available = innerW - margin - overhead;
7836
+ const minColW = 3;
7837
+ if (available < ncols * minColW) {
7838
+ return [[{ text: " (table too narrow to render)", fg: GLOAM.fg4, italic: true }]];
7839
+ }
7840
+ const colW = [...naturalW];
7841
+ for (let i = 0; i < ncols; i++) if (colW[i] < minColW) colW[i] = minColW;
7842
+ let total = colW.reduce((a, b) => a + b, 0);
7843
+ if (total > available) {
7844
+ while (total > available) {
7845
+ let widest = 0;
7846
+ for (let i = 1; i < ncols; i++) if (colW[i] > colW[widest]) widest = i;
7847
+ if (colW[widest] <= minColW) break;
7848
+ colW[widest]--;
7849
+ total--;
7850
+ }
7851
+ } else if (total < available) {
7852
+ while (total < available) {
7853
+ let widest = 0;
7854
+ for (let i = 1; i < ncols; i++) if (colW[i] > colW[widest]) widest = i;
7855
+ colW[widest]++;
7856
+ total++;
7626
7857
  }
7627
7858
  }
7628
- return atoms;
7859
+ const borderFg = GLOAM.fg3;
7860
+ const headerFg = GLOAM.fg0;
7861
+ const headerBg = GLOAM.bg_bg1;
7862
+ const cellFg = GLOAM.fg1;
7863
+ const marginText = " ";
7864
+ const buildBorder = (left, mid, right) => {
7865
+ let s = left;
7866
+ for (let i = 0; i < ncols; i++) {
7867
+ s += "\u2500".repeat(colW[i] + 2);
7868
+ s += i === ncols - 1 ? right : mid;
7869
+ }
7870
+ return [{ text: marginText }, { text: s, fg: borderFg }];
7871
+ };
7872
+ const buildDataRow = (cells, header) => {
7873
+ const wrapped = [];
7874
+ for (let i = 0; i < ncols; i++) {
7875
+ const cell = cells[i];
7876
+ wrapped.push(wrapCell(cell === void 0 ? "" : cell, colW[i], aligns[i]));
7877
+ }
7878
+ let height = 1;
7879
+ for (const w of wrapped) if (w.length > height) height = w.length;
7880
+ for (let i = 0; i < ncols; i++) {
7881
+ const blank = " ".repeat(colW[i]);
7882
+ while (wrapped[i].length < height) wrapped[i].push(blank);
7883
+ }
7884
+ const out2 = [];
7885
+ for (let row = 0; row < height; row++) {
7886
+ const segs = [
7887
+ { text: marginText },
7888
+ { text: "\u2502", fg: borderFg }
7889
+ ];
7890
+ for (let i = 0; i < ncols; i++) {
7891
+ const padded = " " + wrapped[i][row] + " ";
7892
+ segs.push(
7893
+ header ? { text: padded, fg: headerFg, bg: headerBg, bold: true } : { text: padded, fg: cellFg }
7894
+ );
7895
+ segs.push({ text: "\u2502", fg: borderFg });
7896
+ }
7897
+ out2.push(segs);
7898
+ }
7899
+ return out2;
7900
+ };
7901
+ const out = [];
7902
+ out.push(buildBorder("\u250C", "\u252C", "\u2510"));
7903
+ for (const dl of buildDataRow(headers, true)) out.push(dl);
7904
+ out.push(buildBorder("\u251C", "\u253C", "\u2524"));
7905
+ for (const r of rows) for (const dl of buildDataRow(r, false)) out.push(dl);
7906
+ out.push(buildBorder("\u2514", "\u2534", "\u2518"));
7907
+ return out;
7629
7908
  }
7630
- function wrapSegs(segs, width, contIndent) {
7631
- if (width <= 0) return [segs];
7632
- const atoms = segsToAtoms(segs);
7633
- if (atoms.length === 0) return [[{ text: "" }]];
7909
+ function buildHighlightedMarkdownLines(content, innerW) {
7634
7910
  const lines = [];
7635
- let current = [];
7636
- let currentWidth = 0;
7637
- const pushAtom = (a) => {
7638
- current.push({ ...a.style, text: a.text });
7639
- currentWidth += a.width;
7640
- };
7641
- const flushLine = () => {
7642
- while (current.length > 0) {
7643
- const last = current[current.length - 1];
7644
- if (/^\s+$/.test(last.text) && !last.bg) {
7645
- currentWidth -= stringWidth6(last.text);
7646
- current.pop();
7647
- } else break;
7911
+ const clean = stripFrontmatter(content);
7912
+ if (!clean.trim()) {
7913
+ lines.push([{ text: " (empty)", fg: GLOAM.fg4, italic: true }]);
7914
+ return lines;
7915
+ }
7916
+ const rawLines = clean.split("\n");
7917
+ let inCodeBlock = false;
7918
+ for (let li = 0; li < rawLines.length; li++) {
7919
+ const raw = rawLines[li];
7920
+ const trimmed = raw.trim();
7921
+ if (/^```/.test(trimmed)) {
7922
+ inCodeBlock = !inCodeBlock;
7923
+ lines.push(buildCodeFenceLine(trimmed, innerW));
7924
+ continue;
7648
7925
  }
7649
- lines.push(current.length > 0 ? current : [{ text: "" }]);
7650
- current = [];
7651
- currentWidth = 0;
7652
- };
7653
- for (let i = 0; i < atoms.length; i++) {
7654
- const atom = atoms[i];
7655
- if (atom.space) {
7656
- if (currentWidth + atom.width <= width) pushAtom(atom);
7926
+ if (inCodeBlock) {
7927
+ lines.push(buildCodeLine(raw.replace(/\t/g, " "), innerW));
7657
7928
  continue;
7658
7929
  }
7659
- if (atom.width > width) {
7660
- let remaining = atom.text;
7661
- while (remaining.length > 0) {
7662
- const spaceLeft = width - currentWidth;
7663
- if (spaceLeft <= 0) {
7664
- flushLine();
7665
- if (contIndent) {
7666
- current.push({ text: contIndent });
7667
- currentWidth = stringWidth6(contIndent);
7668
- }
7669
- continue;
7670
- }
7671
- let cut = 0;
7672
- let cutW = 0;
7673
- for (let k = 0; k < remaining.length; k++) {
7674
- const cw = stringWidth6(remaining[k]);
7675
- if (cutW + cw > spaceLeft) break;
7676
- cutW += cw;
7677
- cut = k + 1;
7678
- }
7679
- if (cut === 0) {
7680
- flushLine();
7681
- if (contIndent) {
7682
- current.push({ text: contIndent });
7683
- currentWidth = stringWidth6(contIndent);
7684
- }
7685
- continue;
7686
- }
7687
- current.push({ ...atom.style, text: remaining.slice(0, cut) });
7688
- currentWidth += cutW;
7689
- remaining = remaining.slice(cut);
7690
- if (remaining.length > 0) {
7691
- flushLine();
7692
- if (contIndent) {
7693
- current.push({ text: contIndent });
7694
- currentWidth = stringWidth6(contIndent);
7695
- }
7930
+ if (trimmed.includes("|") && li + 1 < rawLines.length) {
7931
+ const sepLine = rawLines[li + 1];
7932
+ const sepAligns = parseTableSeparator(sepLine);
7933
+ if (sepAligns) {
7934
+ const headers = parseTableCells(raw);
7935
+ const tRows = [];
7936
+ let j = li + 2;
7937
+ while (j < rawLines.length) {
7938
+ const next = rawLines[j];
7939
+ if (next.trim() === "") break;
7940
+ if (!next.includes("|")) break;
7941
+ if (/^```/.test(next.trim())) break;
7942
+ tRows.push(parseTableCells(next));
7943
+ j++;
7696
7944
  }
7945
+ for (const tl of buildTableLines(headers, sepAligns, tRows, innerW)) lines.push(tl);
7946
+ li = j - 1;
7947
+ continue;
7697
7948
  }
7949
+ }
7950
+ if (trimmed === "") {
7951
+ const last = lines[lines.length - 1];
7952
+ if (last && last.length === 1 && last[0].text === "") continue;
7953
+ lines.push([{ text: "" }]);
7698
7954
  continue;
7699
7955
  }
7700
- if (currentWidth + atom.width > width) {
7701
- flushLine();
7702
- if (contIndent) {
7703
- current.push({ text: contIndent });
7704
- currentWidth = stringWidth6(contIndent);
7956
+ if (trimmed === "---") {
7957
+ lines.push(buildHrLine(innerW));
7958
+ continue;
7959
+ }
7960
+ const headMatch = raw.match(/^(\s*)(#{1,6})\s+(.+?)\s*#*\s*$/);
7961
+ if (headMatch) {
7962
+ const level = headMatch[2].length;
7963
+ lines.push(buildHeadingLine(level, headMatch[3], innerW));
7964
+ continue;
7965
+ }
7966
+ if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(raw)) {
7967
+ lines.push(buildHrLine(innerW));
7968
+ continue;
7969
+ }
7970
+ const cbMatch = raw.match(/^\s*[-*+]\s+\[( |x|X)\]\s+(.+)$/);
7971
+ if (cbMatch) {
7972
+ const checked = cbMatch[1] !== " ";
7973
+ for (const wl of buildCheckboxLine(checked, cbMatch[2], innerW)) lines.push(wl);
7974
+ continue;
7975
+ }
7976
+ const numMatch = raw.match(/^(\s*)(\d+)([.)])\s+(.+)$/);
7977
+ if (numMatch) {
7978
+ const indent = numMatch[1].length > 0 ? " " : " ";
7979
+ const marker = `${numMatch[2]}${numMatch[3]}`;
7980
+ for (const wl of buildListLine(marker, numMatch[4], innerW, indent, GLOAM.purple)) {
7981
+ lines.push(wl);
7705
7982
  }
7983
+ continue;
7706
7984
  }
7707
- pushAtom(atom);
7985
+ const bulMatch = raw.match(/^(\s*)([-*+])\s+(.+)$/);
7986
+ if (bulMatch) {
7987
+ const depth = Math.floor(bulMatch[1].length / 2);
7988
+ const indent = " " + " ".repeat(Math.min(depth, 4));
7989
+ const bullet = depth === 0 ? "\xB7" : "\u25E6";
7990
+ for (const wl of buildListLine(bullet, bulMatch[3], innerW, indent, GLOAM.orange)) {
7991
+ lines.push(wl);
7992
+ }
7993
+ continue;
7994
+ }
7995
+ const qMatch = raw.match(/^\s*>\s?(.*)$/);
7996
+ if (qMatch) {
7997
+ for (const wl of buildQuoteLine(qMatch[1], innerW)) lines.push(wl);
7998
+ continue;
7999
+ }
8000
+ for (const wl of buildParagraphLines(trimmed, innerW)) lines.push(wl);
7708
8001
  }
7709
- flushLine();
7710
- return lines.length > 0 ? lines : [[{ text: "" }]];
7711
- }
7712
- function buildHeadingLine(level, rawText, innerW) {
7713
- const style = HEADING_STYLES[Math.min(level - 1, HEADING_STYLES.length - 1)];
7714
- const cleanedText = stripDisplayHazards(rawText).trim();
7715
- const marker = "#".repeat(level);
7716
- const prefix = " ";
7717
- const sep = " ";
7718
- const headerSegs = [
7719
- { text: prefix, bg: style.bg },
7720
- { text: marker, fg: style.markerFg, bg: style.bg, bold: true },
7721
- { text: sep, bg: style.bg },
7722
- { text: cleanedText, fg: style.textFg, bg: style.bg, bold: true }
7723
- ];
7724
- const used = segsDisplayWidth(headerSegs);
7725
- const padW = Math.max(0, innerW - used);
7726
- if (padW > 0) {
7727
- headerSegs.push({ text: " ".repeat(padW), bg: style.bg });
8002
+ while (lines.length > 0) {
8003
+ const last = lines[lines.length - 1];
8004
+ if (last.length === 1 && last[0].text === "") lines.pop();
8005
+ else break;
7728
8006
  }
7729
- return headerSegs;
7730
- }
7731
- function buildListLine(marker, body, innerW, prefixIndent, markerFg) {
7732
- const indent = `${prefixIndent} `;
7733
- const head = [
7734
- { text: prefixIndent, fg: GLOAM.fg2 },
7735
- { text: marker, fg: markerFg, bold: true },
7736
- { text: " ", fg: GLOAM.fg2 }
7737
- ];
7738
- const headW = segsDisplayWidth(head);
7739
- const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg1);
7740
- const wrapped = wrapSegs([...head, ...bodySegs], innerW, indent);
7741
- return wrapped;
8007
+ return lines;
7742
8008
  }
7743
- function buildCheckboxLine(checked, body, innerW) {
7744
- const icon = checked ? "\u2611" : "\u2610";
7745
- const iconFg = checked ? GLOAM.green : GLOAM.fg4;
7746
- const head = [
7747
- { text: " ", fg: GLOAM.fg2 },
7748
- { text: icon, fg: iconFg, bold: true },
7749
- { text: " ", fg: GLOAM.fg2 }
7750
- ];
7751
- const bodyFg = checked ? GLOAM.fg3 : GLOAM.fg1;
7752
- const bodyStyle = checked ? { strikethrough: true } : {};
7753
- const bodySegs = tokenizeInline(stripDisplayHazards(body), bodyFg, bodyStyle);
7754
- return wrapSegs([...head, ...bodySegs], innerW, " ");
8009
+
8010
+ // src/tui/panels/stacked-detail.ts
8011
+ var HEADERS_ACTIVE = {
8012
+ top: "GOAL",
8013
+ middle: "STRATEGY",
8014
+ bottom: "ROADMAP"
8015
+ };
8016
+ var HEADERS_DONE = {
8017
+ top: "GOAL",
8018
+ middle: "COMPLETION",
8019
+ bottom: "SUMMARY"
8020
+ };
8021
+ function renderStackedDetailRows(rect, state2, detailCtx) {
8022
+ const focused = state2.focusPane === "detail";
8023
+ const { w, h } = rect;
8024
+ const innerW = w - 4;
8025
+ const cursorNode = detailCtx.nodes[state2.cursorIndex];
8026
+ if (!cursorNode || !state2.selectedSession || cursorNode.sessionId !== state2.selectedSession.id) {
8027
+ return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mSelect a session\x1B[0m");
8028
+ }
8029
+ if (state2.detailMode === "cycle-log") {
8030
+ return renderCycleLogMode(rect, state2, focused);
8031
+ }
8032
+ const session = state2.selectedSession;
8033
+ const isDone = session.status === "completed";
8034
+ const headers = isDone ? HEADERS_DONE : HEADERS_ACTIVE;
8035
+ const middleContent = isDone ? buildCompletionContent(session) : state2.strategyContent;
8036
+ const bottomContent = isDone ? pickSummaryContent(state2) : state2.planContent;
8037
+ const cacheKey = [
8038
+ cursorNode.sessionId,
8039
+ rect.w,
8040
+ isDone ? "done" : "active",
8041
+ state2.goalContent.length,
8042
+ middleContent.length,
8043
+ bottomContent.length
8044
+ ].join(":");
8045
+ let lines = state2.cachedStackedLines;
8046
+ if (cacheKey !== state2.stackedCacheKey || lines === null) {
8047
+ lines = {
8048
+ goal: buildSectionLines(state2.goalContent, innerW),
8049
+ strategy: buildSectionLines(middleContent, innerW),
8050
+ roadmap: buildSectionLines(bottomContent, innerW),
8051
+ cycleLog: []
8052
+ };
8053
+ state2.cachedStackedLines = lines;
8054
+ state2.stackedCacheKey = cacheKey;
8055
+ }
8056
+ const heights = allocateStripHeights(h, lines.goal.length, lines.strategy.length, lines.roadmap.length);
8057
+ const rows = new Array(h);
8058
+ const focusColor = focused ? "cyan" : "gray";
8059
+ const sgr = `\x1B[${colorToSGR(focusColor)}m`;
8060
+ const reset = "\x1B[0m";
8061
+ rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
8062
+ rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
8063
+ let cursor = 1;
8064
+ cursor = paintStrip(
8065
+ rows,
8066
+ cursor,
8067
+ w,
8068
+ sgr,
8069
+ reset,
8070
+ headers.top,
8071
+ lines.goal,
8072
+ state2.goalScroll,
8073
+ heights.goalHeight,
8074
+ state2.focusedStrip === "goal" && focused
8075
+ );
8076
+ rows[cursor++] = sgr + "\u251C" + "\u2500".repeat(w - 2) + "\u2524" + reset;
8077
+ cursor = paintStrip(
8078
+ rows,
8079
+ cursor,
8080
+ w,
8081
+ sgr,
8082
+ reset,
8083
+ headers.middle,
8084
+ lines.strategy,
8085
+ state2.strategyScroll,
8086
+ heights.stratHeight,
8087
+ state2.focusedStrip === "strategy" && focused
8088
+ );
8089
+ rows[cursor++] = sgr + "\u251C" + "\u2500".repeat(w - 2) + "\u2524" + reset;
8090
+ paintStrip(
8091
+ rows,
8092
+ cursor,
8093
+ w,
8094
+ sgr,
8095
+ reset,
8096
+ headers.bottom,
8097
+ lines.roadmap,
8098
+ state2.roadmapScroll,
8099
+ heights.roadHeight,
8100
+ state2.focusedStrip === "roadmap" && focused
8101
+ );
8102
+ return rows;
7755
8103
  }
7756
- function buildQuoteLine(body, innerW) {
7757
- const head = [
7758
- { text: " ", fg: GLOAM.fg2 },
7759
- { text: "\u258E ", fg: GLOAM.fg3 }
7760
- ];
7761
- const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg2, { italic: true });
7762
- return wrapSegs([...head, ...bodySegs], innerW, " ");
8104
+ function buildCompletionContent(session) {
8105
+ const parts = [];
8106
+ if (session.completedAt) {
8107
+ parts.push(`*completed ${formatTimestamp(session.completedAt)}*`);
8108
+ parts.push("");
8109
+ }
8110
+ if (session.completionReport && session.completionReport.trim()) {
8111
+ parts.push(session.completionReport.trim());
8112
+ } else {
8113
+ parts.push("_No completion report written._");
8114
+ }
8115
+ return parts.join("\n");
7763
8116
  }
7764
- function buildHrLine(innerW) {
7765
- const w = Math.max(2, innerW - 4);
7766
- return [
7767
- { text: " ", fg: GLOAM.fg4 },
7768
- { text: "\u2500".repeat(w), fg: GLOAM.fg4 }
7769
- ];
8117
+ function pickSummaryContent(state2) {
8118
+ if (state2.completionSummaryContent.trim()) return state2.completionSummaryContent;
8119
+ if (state2.logsCycles.length > 0) {
8120
+ const last = state2.logsCycles[state2.logsCycles.length - 1];
8121
+ return `# Cycle ${last.cycle} log
8122
+
8123
+ ${last.content}`;
8124
+ }
8125
+ if (state2.strategyContent.trim()) return state2.strategyContent;
8126
+ return "_No completion artifacts. Try expanding context/ in the tree for raw files._";
7770
8127
  }
7771
- function buildCodeFenceLine(fence, innerW) {
7772
- return [
7773
- { text: " ", fg: GLOAM.fg4 },
7774
- {
7775
- text: fence + " ".repeat(Math.max(0, innerW - 2 - stringWidth6(fence))),
7776
- fg: GLOAM.fg4,
7777
- bg: GLOAM.bg_bg1
7778
- }
7779
- ];
8128
+ function formatTimestamp(iso) {
8129
+ try {
8130
+ const d = new Date(iso);
8131
+ if (isNaN(d.getTime())) return iso;
8132
+ return d.toLocaleString();
8133
+ } catch {
8134
+ return iso;
8135
+ }
7780
8136
  }
7781
- function buildCodeLine(content, innerW) {
7782
- const cleaned = stripDisplayHazards(content);
7783
- const cw = stringWidth6(cleaned);
7784
- const padW = Math.max(0, innerW - 2 - cw);
7785
- return [
7786
- { text: " ", bg: GLOAM.bg_bg1 },
7787
- { text: cleaned, fg: GLOAM.aqua, bg: GLOAM.bg_bg1 },
7788
- ...padW > 0 ? [{ text: " ".repeat(padW), bg: GLOAM.bg_bg1 }] : []
7789
- ];
8137
+ function buildSectionLines(content, innerW) {
8138
+ return buildHighlightedMarkdownLines(content, innerW);
7790
8139
  }
7791
- function buildParagraphLines(body, innerW) {
7792
- const head = [{ text: " ", fg: GLOAM.fg1 }];
7793
- const bodySegs = tokenizeInline(stripDisplayHazards(body), GLOAM.fg1);
7794
- return wrapSegs([...head, ...bodySegs], innerW, " ");
8140
+ function allocateStripHeights(rectH, gN, sN, _rN) {
8141
+ const innerH = rectH - 2;
8142
+ const stripsAvail = innerH - 2;
8143
+ const goalCap = Math.floor(stripsAvail * 0.2);
8144
+ const stratCap = Math.floor(stripsAvail * 0.4);
8145
+ const roadCap = stripsAvail - goalCap - stratCap;
8146
+ const goalNeed = Math.min(gN + 1, goalCap);
8147
+ const stratNeed = Math.min(sN + 1, stratCap);
8148
+ const slack = goalCap - goalNeed + (stratCap - stratNeed);
8149
+ return {
8150
+ goalHeight: Math.max(2, goalNeed),
8151
+ stratHeight: Math.max(2, stratNeed),
8152
+ roadHeight: Math.max(2, roadCap + slack)
8153
+ };
7795
8154
  }
7796
- function parseTableCells(line) {
7797
- let s = line.trim();
7798
- if (s.startsWith("|")) s = s.slice(1);
7799
- if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
7800
- const cells = [];
7801
- let cur = "";
7802
- for (let i = 0; i < s.length; i++) {
7803
- const ch = s[i];
7804
- if (ch === "\\" && s[i + 1] === "|") {
7805
- cur += "|";
7806
- i++;
7807
- } else if (ch === "|") {
7808
- cells.push(cur.trim());
7809
- cur = "";
7810
- } else {
7811
- cur += ch;
7812
- }
8155
+ function paintStrip(rows, startRow, w, sgr, reset, label, lines, scroll, height, focused) {
8156
+ const innerW = w - 4;
8157
+ const borderL = sgr + "\u2502" + reset + " ";
8158
+ const borderR = " " + sgr + "\u2502" + reset;
8159
+ const headerSeg = `\x1B[${colorToSGR("yellow")};1m \u258E ${label}\x1B[0m`;
8160
+ const headerClipped = clipAnsi(headerSeg + (focused ? " \u25C0" : ""), innerW);
8161
+ rows[startRow] = borderL + headerClipped + borderR;
8162
+ const contentH = height - 1;
8163
+ const hasOverflow = lines.length > contentH;
8164
+ const viewableH = hasOverflow ? contentH - 1 : contentH;
8165
+ const maxScroll = Math.max(0, lines.length - viewableH);
8166
+ scroll.setMax(maxScroll);
8167
+ const effOffset = scroll.offset;
8168
+ for (let i = 0; i < viewableH; i++) {
8169
+ const li = effOffset + i;
8170
+ const ansi = li < lines.length ? renderLine(lines[li]) : "";
8171
+ rows[startRow + 1 + i] = borderL + clipAnsi(ansi, innerW) + borderR;
7813
8172
  }
7814
- cells.push(cur.trim());
7815
- return cells;
7816
- }
7817
- function parseTableSeparator(line) {
7818
- if (!line.includes("|") && !/^[\s:|+-]+$/.test(line)) return null;
7819
- const cells = parseTableCells(line);
7820
- if (cells.length === 0) return null;
7821
- const aligns = [];
7822
- for (const c of cells) {
7823
- const m = c.match(/^(:?)\s*-{2,}\s*(:?)$/);
7824
- if (!m) return null;
7825
- if (m[1] === ":" && m[2] === ":") aligns.push("center");
7826
- else if (m[2] === ":") aligns.push("right");
7827
- else aligns.push("left");
8173
+ if (hasOverflow) {
8174
+ const pct = maxScroll > 0 ? Math.round(effOffset / maxScroll * 100) : 100;
8175
+ const indicator = `\x1B[2m \u2195 ${pct}% \xB7 ${lines.length} lines\x1B[0m`;
8176
+ rows[startRow + 1 + viewableH] = borderL + clipAnsi(indicator, innerW) + borderR;
7828
8177
  }
7829
- return aligns;
8178
+ return startRow + height;
7830
8179
  }
7831
- function padCell(text, width, align) {
7832
- const w = stringWidth6(text);
7833
- const pad = Math.max(0, width - w);
7834
- let left = 0;
7835
- let right = 0;
7836
- if (align === "right") left = pad;
7837
- else if (align === "center") {
7838
- left = Math.floor(pad / 2);
7839
- right = pad - left;
7840
- } else right = pad;
7841
- return " ".repeat(left) + text + " ".repeat(right);
8180
+ function renderCycleLogMode(rect, state2, focused) {
8181
+ if (state2.logsCycles.length === 0) {
8182
+ return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mNo cycle logs yet\x1B[0m");
8183
+ }
8184
+ const cacheKey = `cycleLog:${state2.logsCycles.length}:${rect.w}`;
8185
+ let lines;
8186
+ if (cacheKey === state2.stackedCacheKey && state2.cachedStackedLines !== null) {
8187
+ lines = state2.cachedStackedLines.cycleLog;
8188
+ } else {
8189
+ lines = buildLogsLines(state2.logsCycles, rect.w);
8190
+ const existing = state2.cachedStackedLines ?? { goal: [], strategy: [], roadmap: [], cycleLog: [] };
8191
+ state2.cachedStackedLines = { ...existing, cycleLog: lines };
8192
+ state2.stackedCacheKey = cacheKey;
8193
+ }
8194
+ return buildPanelRows(rect, lines, state2.detailScroll, focused, "cyan", state2.stackedRenderedCache);
7842
8195
  }
7843
- function wrapCell(text, width, align) {
7844
- if (width <= 0) return [""];
7845
- const cleaned = cleanMarkdown(text);
7846
- if (cleaned === "") return [padCell("", width, align)];
7847
- const out = [];
7848
- let cur = "";
7849
- let curW = 0;
7850
- const flush = () => {
7851
- out.push(padCell(cur.replace(/\s+$/, ""), width, align));
7852
- cur = "";
7853
- curW = 0;
7854
- };
7855
- for (const piece of cleaned.match(/\s+|\S+/g) ?? []) {
7856
- const isSpace = /^\s+$/.test(piece);
7857
- const pw = stringWidth6(piece);
7858
- if (isSpace) {
7859
- if (curW === 0) continue;
7860
- if (curW + pw > width) flush();
7861
- else {
7862
- cur += piece;
7863
- curW += pw;
7864
- }
7865
- continue;
7866
- }
7867
- if (curW + pw <= width) {
7868
- cur += piece;
7869
- curW += pw;
7870
- continue;
7871
- }
7872
- if (curW > 0) flush();
7873
- if (pw > width) {
7874
- let rem = piece;
7875
- while (rem.length > 0) {
7876
- let cut = 0;
7877
- let cutW = 0;
7878
- for (let k = 0; k < rem.length; k++) {
7879
- const cw = stringWidth6(rem[k]);
7880
- if (cutW + cw > width) break;
7881
- cutW += cw;
7882
- cut = k + 1;
7883
- }
7884
- if (cut === 0) cut = 1;
7885
- const slice = rem.slice(0, cut);
7886
- if (cut === rem.length) {
7887
- cur = slice;
7888
- curW = stringWidth6(slice);
7889
- } else {
7890
- out.push(padCell(slice, width, align));
7891
- }
7892
- rem = rem.slice(cut);
7893
- }
7894
- continue;
8196
+
8197
+ // src/tui/panels/bottom.ts
8198
+ init_render();
8199
+ var B = ansiBold;
8200
+ var D = ansiDim;
8201
+ var SEP = D("\u2502 ");
8202
+ var DANGER_BADGE = "\x1B[1;41;97m DANGEROUS \x1B[0m";
8203
+ function renderStatusLine(buf, y, state2, cursorNodeType) {
8204
+ const { mode, focusPane, notification, error } = state2;
8205
+ if (mode === "report-detail") return;
8206
+ let content;
8207
+ if (notification !== null) {
8208
+ const icon = /error|failed/i.test(notification) ? "\u2715" : /success|created|killed|sent|copied|deleted/i.test(notification) ? "\u2713" : "\u2139";
8209
+ content = `\x1B[1;33m${icon} ${notification}\x1B[0m`;
8210
+ } else if (error !== null) {
8211
+ content = `\x1B[31m\u26A0 ${error}\x1B[0m`;
8212
+ } else if (mode === "search") {
8213
+ const cursor = `\x1B[7m \x1B[0m`;
8214
+ content = `\x1B[1;34m/\x1B[0m${state2.searchText}${cursor}` + D(" enter to apply \xB7 esc to clear");
8215
+ } else if (mode === "leader") {
8216
+ content = `\x1B[1;35mLEADER\x1B[0m` + D(" [c]opy [o]pen [a]gent [S]ession [g]o or [s]cycle [h]ome [n]ew [m]sg [t]status [l]picker [x]kill [/]search [?]help [esc] cancel");
8217
+ } else if (mode === "copy-menu") {
8218
+ content = `\x1B[1;36mCOPY\x1B[0m` + D(" [p]ath [i]d [c] context [l]ogs [r]eport [a]gent ID [esc] cancel");
8219
+ } else if (mode === "open-menu") {
8220
+ content = `\x1B[1;32mOPEN\x1B[0m` + D(" [g]oal [r]oadmap [s]trategy [l]ogs [d]ir [R]eport [c]scratch [e]dit context [esc] cancel");
8221
+ } else if (mode === "agent-menu") {
8222
+ content = `\x1B[1;34mAGENT\x1B[0m` + D(" [s]pawn [m]sg [r]estart [R]erun [j]ump [o]pen-claude [t]ail [k]ill [e]xplore [d]ebug [esc] cancel");
8223
+ } else if (mode === "session-menu") {
8224
+ content = `\x1B[1;31mSESSION\x1B[0m` + D(" [n]ew [r]esume [c]ontinue [b]ollback [k]ill [d]elete [e]xport [w]indow [C]lone [i]history [esc] cancel");
8225
+ } else if (mode === "go-menu") {
8226
+ content = `\x1B[1;33mGO\x1B[0m` + D(" [w]indow [p]ane [s]ession [n]ext [r]econnect [esc] cancel");
8227
+ } else if (mode === "help") {
8228
+ content = `\x1B[1;33mHELP\x1B[0m` + D(" [esc] or [?] to dismiss");
8229
+ } else if (focusPane === "logs" || focusPane === "detail") {
8230
+ content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle view ") + B("[F]") + D("low \xB1 ") + 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");
8231
+ } else if (cursorNodeType === "needs-you-virtual") {
8232
+ content = B("[enter]") + D(" open ask ") + B("[esc]") + D(" back ") + SEP + B("[q]") + D("uit");
8233
+ } else {
8234
+ let contextFilePart = "";
8235
+ if (cursorNodeType === "context-file") {
8236
+ contextFilePart = B("[e]") + D("dit ") + B("[\u23CE]") + D(" open ");
7895
8237
  }
7896
- cur = piece;
7897
- curW = pw;
8238
+ content = contextFilePart + B("[enter]") + D(" select ") + B("[m]") + D("essage ") + B("[n]") + D("ew ") + B("[w]") + D(" tmux ") + SEP + B("[q]") + D("uit");
7898
8239
  }
7899
- if (curW > 0 || out.length === 0) flush();
7900
- return out;
8240
+ if (state2.selectedSession?.dangerousMode === true) {
8241
+ content = `${DANGER_BADGE} ${content}`;
8242
+ }
8243
+ writeClipped(buf, 1, y, content, buf.width - 2);
7901
8244
  }
7902
- function buildTableLines(headers, alignsIn, rowsIn, innerW) {
7903
- const ncols = headers.length;
7904
- if (ncols === 0) return [];
7905
- const aligns = [];
7906
- for (let i = 0; i < ncols; i++) {
7907
- const a = alignsIn[i];
7908
- aligns.push(a === void 0 ? "left" : a);
8245
+
8246
+ // src/tui/panels/inbox-deck.ts
8247
+ init_render();
8248
+ import stringWidth7 from "string-width";
8249
+ import { readFileSync as readFileSync15 } from "fs";
8250
+
8251
+ // src/tui/panels/mounted-humanloop.ts
8252
+ init_ask_store();
8253
+ init_paths();
8254
+ import { readFileSync as readFileSync14, watchFile, unwatchFile } from "fs";
8255
+ import { mountPanel } from "@crouton-kit/humanloop";
8256
+ async function dispatchOrphanResolution(orphanTarget, selectedOptionId, deps) {
8257
+ if (selectedOptionId === "takeover" && orphanTarget.kind === "agent") {
8258
+ await deps.onOrphanTakeover?.({
8259
+ sessionId: deps.sessionId,
8260
+ agentId: orphanTarget.agentId,
8261
+ paneId: orphanTarget.paneId
8262
+ });
8263
+ } else if (selectedOptionId === "restart" && orphanTarget.kind === "agent") {
8264
+ await deps.daemonSend({ type: "restart-agent", sessionId: deps.sessionId, agentId: orphanTarget.agentId });
8265
+ } else if (selectedOptionId === "resume" && orphanTarget.kind === "orchestrator") {
8266
+ await deps.daemonSend({ type: "resume", sessionId: deps.sessionId, cwd: deps.cwd });
8267
+ } else if (selectedOptionId === "dismiss" && orphanTarget.kind === "orchestrator") {
8268
+ await deps.daemonSend({ type: "clear-orphan", sessionId: deps.sessionId, cwd: deps.cwd });
7909
8269
  }
7910
- const rows = rowsIn.map((r) => {
7911
- const out2 = [];
7912
- for (let i = 0; i < ncols; i++) {
7913
- const c = r[i];
7914
- out2.push(c === void 0 ? "" : c);
8270
+ }
8271
+ function makeOrphanTakeover(state2, ops) {
8272
+ return async ({ sessionId: sessionId2, agentId, paneId }) => {
8273
+ const res = await ops.send({ type: "status", sessionId: sessionId2 });
8274
+ const sess = res.ok ? res.data?.session : void 0;
8275
+ if (!sess) {
8276
+ notify(state2, "Session not found");
8277
+ return;
7915
8278
  }
7916
- return out2;
7917
- });
7918
- const naturalW = new Array(ncols).fill(0);
7919
- const measure = (cells) => {
7920
- for (let i = 0; i < ncols; i++) {
7921
- const w = stringWidth6(cleanMarkdown(cells[i]));
7922
- if (w > naturalW[i]) naturalW[i] = w;
8279
+ if (paneId && ops.paneExists(paneId)) {
8280
+ if (sess.tmuxSessionName) ops.switchToSession(sess.tmuxSessionName);
8281
+ if (sess.tmuxWindowId) ops.selectWindow(sess.tmuxWindowId);
8282
+ ops.selectPane(paneId);
8283
+ return;
7923
8284
  }
8285
+ if (sess.tmuxSessionName) ops.switchToSession(sess.tmuxSessionName);
8286
+ notify(state2, `Pane ${paneId ? paneId : "?"} is gone \u2014 agent ${agentId} cannot be taken over.`);
7924
8287
  };
7925
- measure(headers);
7926
- for (const r of rows) measure(r);
7927
- const margin = 2;
7928
- const overhead = 1 + ncols * 3;
7929
- const available = innerW - margin - overhead;
7930
- const minColW = 3;
7931
- if (available < ncols * minColW) {
7932
- return [[{ text: " (table too narrow to render)", fg: GLOAM.fg4, italic: true }]];
8288
+ }
8289
+ function mountResolutionPanel(opts, state2) {
8290
+ let queue = [...opts.aggregateInbox];
8291
+ let currentIndex = opts.startIndex;
8292
+ let bodyCols = opts.cols;
8293
+ const item = () => queue[currentIndex];
8294
+ function itemCoords(it) {
8295
+ return {
8296
+ cwd: cwdFromDir(it.dir),
8297
+ sessionId: sessionIdFromDir(it.dir),
8298
+ askId: askIdFromDir(it.dir)
8299
+ };
7933
8300
  }
7934
- const colW = [...naturalW];
7935
- for (let i = 0; i < ncols; i++) if (colW[i] < minColW) colW[i] = minColW;
7936
- let total = colW.reduce((a, b) => a + b, 0);
7937
- if (total > available) {
7938
- while (total > available) {
7939
- let widest = 0;
7940
- for (let i = 1; i < ncols; i++) if (colW[i] > colW[widest]) widest = i;
7941
- if (colW[widest] <= minColW) break;
7942
- colW[widest]--;
7943
- total--;
7944
- }
7945
- } else if (total < available) {
7946
- while (total < available) {
7947
- let widest = 0;
7948
- for (let i = 1; i < ncols; i++) if (colW[i] > colW[widest]) widest = i;
7949
- colW[widest]++;
7950
- total++;
7951
- }
8301
+ function buildDeck(idx) {
8302
+ const it = queue[idx];
8303
+ if (!it) return null;
8304
+ const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = itemCoords(it);
8305
+ const deck = readDecisions(cwd2, sessionId2, askId2);
8306
+ if (!deck) return null;
8307
+ deck.source = {
8308
+ sessionName: it.sessionName ?? it.source?.sessionName,
8309
+ askedBy: it.source?.askedBy,
8310
+ blockedSince: it.blockedSince
8311
+ };
8312
+ return deck;
7952
8313
  }
7953
- const borderFg = GLOAM.fg3;
7954
- const headerFg = GLOAM.fg0;
7955
- const headerBg = GLOAM.bg_bg1;
7956
- const cellFg = GLOAM.fg1;
7957
- const marginText = " ";
7958
- const buildBorder = (left, mid, right) => {
7959
- let s = left;
7960
- for (let i = 0; i < ncols; i++) {
7961
- s += "\u2500".repeat(colW[i] + 2);
7962
- s += i === ncols - 1 ? right : mid;
7963
- }
7964
- return [{ text: marginText }, { text: s, fg: borderFg }];
7965
- };
7966
- const buildDataRow = (cells, header) => {
7967
- const wrapped = [];
7968
- for (let i = 0; i < ncols; i++) {
7969
- const cell = cells[i];
7970
- wrapped.push(wrapCell(cell === void 0 ? "" : cell, colW[i], aligns[i]));
7971
- }
7972
- let height = 1;
7973
- for (const w of wrapped) if (w.length > height) height = w.length;
7974
- for (let i = 0; i < ncols; i++) {
7975
- const blank = " ".repeat(colW[i]);
7976
- while (wrapped[i].length < height) wrapped[i].push(blank);
7977
- }
7978
- const out2 = [];
7979
- for (let row = 0; row < height; row++) {
7980
- const segs = [
7981
- { text: marginText },
7982
- { text: "\u2502", fg: borderFg }
7983
- ];
7984
- for (let i = 0; i < ncols; i++) {
7985
- const padded = " " + wrapped[i][row] + " ";
7986
- segs.push(
7987
- header ? { text: padded, fg: headerFg, bg: headerBg, bold: true } : { text: padded, fg: cellFg }
7988
- );
7989
- segs.push({ text: "\u2502", fg: borderFg });
7990
- }
7991
- out2.push(segs);
7992
- }
7993
- return out2;
7994
- };
7995
- const out = [];
7996
- out.push(buildBorder("\u250C", "\u252C", "\u2510"));
7997
- for (const dl of buildDataRow(headers, true)) out.push(dl);
7998
- out.push(buildBorder("\u251C", "\u253C", "\u2524"));
7999
- for (const r of rows) for (const dl of buildDataRow(r, false)) out.push(dl);
8000
- out.push(buildBorder("\u2514", "\u2534", "\u2518"));
8001
- return out;
8002
- }
8003
- function buildHighlightedMarkdownLines(content, innerW) {
8004
- const lines = [];
8005
- const clean = stripFrontmatter(content);
8006
- if (!clean.trim()) {
8007
- lines.push([{ text: " (empty)", fg: GLOAM.fg4, italic: true }]);
8008
- return lines;
8314
+ const { cwd: initCwd, sessionId: initSessionId, askId: initAskId } = itemCoords(item());
8315
+ const initialDeck = buildDeck(currentIndex);
8316
+ if (!initialDeck) return null;
8317
+ let currentDeck = initialDeck;
8318
+ const initialProgress = readProgress(initCwd, initSessionId, initAskId);
8319
+ let answeredCount = initialProgress?.responses.length ?? 0;
8320
+ function getCurrentQid() {
8321
+ return currentDeck.interactions[answeredCount]?.id ?? currentDeck.interactions[0]?.id;
8009
8322
  }
8010
- const rawLines = clean.split("\n");
8011
- let inCodeBlock = false;
8012
- for (let li = 0; li < rawLines.length; li++) {
8013
- const raw = rawLines[li];
8014
- const trimmed = raw.trim();
8015
- if (/^```/.test(trimmed)) {
8016
- inCodeBlock = !inCodeBlock;
8017
- lines.push(buildCodeFenceLine(trimmed, innerW));
8018
- continue;
8019
- }
8020
- if (inCodeBlock) {
8021
- lines.push(buildCodeLine(raw.replace(/\t/g, " "), innerW));
8022
- continue;
8023
- }
8024
- if (trimmed.includes("|") && li + 1 < rawLines.length) {
8025
- const sepLine = rawLines[li + 1];
8026
- const sepAligns = parseTableSeparator(sepLine);
8027
- if (sepAligns) {
8028
- const headers = parseTableCells(raw);
8029
- const tRows = [];
8030
- let j = li + 2;
8031
- while (j < rawLines.length) {
8032
- const next = rawLines[j];
8033
- if (next.trim() === "") break;
8034
- if (!next.includes("|")) break;
8035
- if (/^```/.test(next.trim())) break;
8036
- tRows.push(parseTableCells(next));
8037
- j++;
8038
- }
8039
- for (const tl of buildTableLines(headers, sepAligns, tRows, innerW)) lines.push(tl);
8040
- li = j - 1;
8041
- continue;
8323
+ function fireVisualGen(force) {
8324
+ const qid = getCurrentQid();
8325
+ if (!qid) return;
8326
+ const it = item();
8327
+ const { sessionId: itSessionId, askId: itAskId } = itemCoords(it);
8328
+ state2.visuals.set(qid, { status: "loading", content: "", visible: true });
8329
+ requestRender();
8330
+ void (async () => {
8331
+ const res = await rawSend({
8332
+ type: "ask-generate-visual",
8333
+ sessionId: itSessionId,
8334
+ askId: itAskId,
8335
+ qid,
8336
+ cols: bodyCols,
8337
+ force
8338
+ }, 6e4);
8339
+ if (res.ok) {
8340
+ const ansiPath = res.data.ansiPath;
8341
+ const ansi = readFileSync14(ansiPath, "utf-8");
8342
+ state2.visuals.set(qid, { status: "ready", content: ansi, visible: true });
8343
+ } else {
8344
+ const errMsg = typeof res.error === "string" ? res.error : res.error?.message;
8345
+ state2.visuals.set(qid, { status: "error", content: "", visible: true, error: errMsg });
8042
8346
  }
8043
- }
8044
- if (trimmed === "") {
8045
- const last = lines[lines.length - 1];
8046
- if (last && last.length === 1 && last[0].text === "") continue;
8047
- lines.push([{ text: "" }]);
8048
- continue;
8049
- }
8050
- if (trimmed === "---") {
8051
- lines.push(buildHrLine(innerW));
8052
- continue;
8053
- }
8054
- const headMatch = raw.match(/^(\s*)(#{1,6})\s+(.+?)\s*#*\s*$/);
8055
- if (headMatch) {
8056
- const level = headMatch[2].length;
8057
- lines.push(buildHeadingLine(level, headMatch[3], innerW));
8058
- continue;
8059
- }
8060
- if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(raw)) {
8061
- lines.push(buildHrLine(innerW));
8062
- continue;
8063
- }
8064
- const cbMatch = raw.match(/^\s*[-*+]\s+\[( |x|X)\]\s+(.+)$/);
8065
- if (cbMatch) {
8066
- const checked = cbMatch[1] !== " ";
8067
- for (const wl of buildCheckboxLine(checked, cbMatch[2], innerW)) lines.push(wl);
8068
- continue;
8069
- }
8070
- const numMatch = raw.match(/^(\s*)(\d+)([.)])\s+(.+)$/);
8071
- if (numMatch) {
8072
- const indent = numMatch[1].length > 0 ? " " : " ";
8073
- const marker = `${numMatch[2]}${numMatch[3]}`;
8074
- for (const wl of buildListLine(marker, numMatch[4], innerW, indent, GLOAM.purple)) {
8075
- lines.push(wl);
8347
+ requestRender();
8348
+ })();
8349
+ }
8350
+ let lastResponses = [];
8351
+ const submitResponses = (responses) => {
8352
+ void (async () => {
8353
+ const it = item();
8354
+ const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = itemCoords(it);
8355
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
8356
+ const meta = readMeta(cwd2, sessionId2, askId2);
8357
+ if (meta?.orphanTarget && responses.length > 0) {
8358
+ const sel = responses[0].selectedOptionId;
8359
+ if (sel) {
8360
+ await dispatchOrphanResolution(meta.orphanTarget, sel, {
8361
+ daemonSend: opts.daemonSend,
8362
+ onOrphanTakeover: opts.onOrphanTakeover,
8363
+ sessionId: sessionId2,
8364
+ cwd: cwd2
8365
+ });
8366
+ }
8367
+ }
8368
+ writeOutput(cwd2, sessionId2, askId2, responses, completedAt);
8369
+ await updateMeta(cwd2, sessionId2, askId2, { status: "answered", completedAt });
8370
+ state2.aggregateInbox = state2.aggregateInbox.filter((i) => i.dir !== it.dir);
8371
+ const idx = queue.findIndex((i) => i.dir === it.dir);
8372
+ if (idx >= 0) queue.splice(idx, 1);
8373
+ if (queue.length === 0) {
8374
+ teardown();
8375
+ return;
8376
+ }
8377
+ currentIndex = Math.min(currentIndex, queue.length - 1);
8378
+ const nextItem = queue[currentIndex];
8379
+ const nextCoords = itemCoords(nextItem);
8380
+ const nextDeck = buildDeck(currentIndex);
8381
+ if (!nextDeck) {
8382
+ teardown();
8383
+ return;
8384
+ }
8385
+ currentDeck = nextDeck;
8386
+ const nextProgress = readProgress(nextCoords.cwd, nextCoords.sessionId, nextCoords.askId);
8387
+ answeredCount = nextProgress?.responses.length ?? 0;
8388
+ lastResponses = [];
8389
+ state2.visuals.clear();
8390
+ panel.loadDeck(nextDeck, {
8391
+ progressPath: askProgressPath(nextCoords.cwd, nextCoords.sessionId, nextCoords.askId)
8392
+ });
8393
+ setDeckWatch(nextCoords, nextDeck);
8394
+ requestRender();
8395
+ })();
8396
+ };
8397
+ let watchedDeckPath = null;
8398
+ let lastWatchedDeckJson = "";
8399
+ const onWatchedDeckChange = () => {
8400
+ const nextDeck = buildDeck(currentIndex);
8401
+ if (!nextDeck) return;
8402
+ const nextJson = JSON.stringify(nextDeck);
8403
+ if (nextJson === lastWatchedDeckJson) return;
8404
+ lastWatchedDeckJson = nextJson;
8405
+ currentDeck = nextDeck;
8406
+ const { cwd: rcwd, sessionId: rsid, askId: raid } = itemCoords(item());
8407
+ const progress = readProgress(rcwd, rsid, raid);
8408
+ answeredCount = progress?.responses.length ?? 0;
8409
+ state2.visuals.clear();
8410
+ panel.loadDeck(nextDeck, { progressPath: askProgressPath(rcwd, rsid, raid) });
8411
+ requestRender();
8412
+ };
8413
+ function setDeckWatch(coords, baselineDeck) {
8414
+ const p = askDecisionsPath(coords.cwd, coords.sessionId, coords.askId);
8415
+ lastWatchedDeckJson = JSON.stringify(baselineDeck);
8416
+ if (p === watchedDeckPath) return;
8417
+ if (watchedDeckPath !== null) {
8418
+ try {
8419
+ unwatchFile(watchedDeckPath, onWatchedDeckChange);
8420
+ } catch {
8076
8421
  }
8077
- continue;
8078
8422
  }
8079
- const bulMatch = raw.match(/^(\s*)([-*+])\s+(.+)$/);
8080
- if (bulMatch) {
8081
- const depth = Math.floor(bulMatch[1].length / 2);
8082
- const indent = " " + " ".repeat(Math.min(depth, 4));
8083
- const bullet = depth === 0 ? "\xB7" : "\u25E6";
8084
- for (const wl of buildListLine(bullet, bulMatch[3], innerW, indent, GLOAM.orange)) {
8085
- lines.push(wl);
8423
+ watchedDeckPath = p;
8424
+ watchFile(p, { interval: 500 }, onWatchedDeckChange);
8425
+ }
8426
+ const panel = mountPanel({
8427
+ deck: initialDeck,
8428
+ cols: opts.cols,
8429
+ rows: opts.rows,
8430
+ progressPath: askProgressPath(initCwd, initSessionId, initAskId),
8431
+ onProgress: (responses) => {
8432
+ answeredCount = responses.length;
8433
+ lastResponses = responses;
8434
+ requestRender();
8435
+ const it = item();
8436
+ const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = itemCoords(it);
8437
+ const cur = readMeta(cwd2, sessionId2, askId2);
8438
+ if (cur?.status === "pending") {
8439
+ void updateMeta(cwd2, sessionId2, askId2, { status: "in-progress", startedAt: (/* @__PURE__ */ new Date()).toISOString() });
8086
8440
  }
8087
- continue;
8441
+ },
8442
+ onComplete: (responses) => {
8443
+ submitResponses(responses);
8444
+ },
8445
+ // Final-phase Enter on an incomplete deck routes here (humanloop only fires
8446
+ // onComplete when responses === interactions). The 'final' UI prompts
8447
+ // "enter submit", so honor that by submitting whatever was answered.
8448
+ onExit: () => {
8449
+ submitResponses(lastResponses);
8088
8450
  }
8089
- const qMatch = raw.match(/^\s*>\s?(.*)$/);
8090
- if (qMatch) {
8091
- for (const wl of buildQuoteLine(qMatch[1], innerW)) lines.push(wl);
8092
- continue;
8451
+ });
8452
+ setDeckWatch({ cwd: initCwd, sessionId: initSessionId, askId: initAskId }, initialDeck);
8453
+ function teardown() {
8454
+ if (watchedDeckPath !== null) {
8455
+ try {
8456
+ unwatchFile(watchedDeckPath, onWatchedDeckChange);
8457
+ } catch {
8458
+ }
8459
+ watchedDeckPath = null;
8093
8460
  }
8094
- for (const wl of buildParagraphLines(trimmed, innerW)) lines.push(wl);
8095
- }
8096
- while (lines.length > 0) {
8097
- const last = lines[lines.length - 1];
8098
- if (last.length === 1 && last[0].text === "") lines.pop();
8099
- else break;
8461
+ panel.unmount();
8462
+ opts.onUnmount();
8100
8463
  }
8101
- return lines;
8464
+ return {
8465
+ handleKey(input, key) {
8466
+ panel.handleKey(input, key);
8467
+ },
8468
+ render() {
8469
+ return panel.render();
8470
+ },
8471
+ handleResize(cols, rows) {
8472
+ bodyCols = cols;
8473
+ panel.handleResize(cols, rows);
8474
+ },
8475
+ unmount() {
8476
+ teardown();
8477
+ },
8478
+ canAcceptHostKeys() {
8479
+ return panel.canAcceptHostKeys();
8480
+ },
8481
+ atDeckTop() {
8482
+ return panel.atDeckTop();
8483
+ },
8484
+ advanceQueue(delta) {
8485
+ const newIndex = Math.max(0, Math.min(queue.length - 1, currentIndex + delta));
8486
+ if (newIndex === currentIndex) return;
8487
+ currentIndex = newIndex;
8488
+ const nextDeck = buildDeck(currentIndex);
8489
+ if (!nextDeck) return;
8490
+ currentDeck = nextDeck;
8491
+ const it = item();
8492
+ const { cwd: advCwd, sessionId: advSid, askId: advAid } = itemCoords(it);
8493
+ const progress = readProgress(advCwd, advSid, advAid);
8494
+ answeredCount = progress?.responses.length ?? 0;
8495
+ panel.loadDeck(nextDeck, {
8496
+ progressPath: askProgressPath(advCwd, advSid, advAid)
8497
+ });
8498
+ setDeckWatch({ cwd: advCwd, sessionId: advSid, askId: advAid }, nextDeck);
8499
+ requestRender();
8500
+ },
8501
+ spaceVisualToggle() {
8502
+ const qid = getCurrentQid();
8503
+ if (!qid) return;
8504
+ const entry = state2.visuals.get(qid);
8505
+ if (!entry) {
8506
+ fireVisualGen(false);
8507
+ } else if (entry.status === "ready") {
8508
+ state2.visuals.set(qid, { ...entry, visible: !entry.visible });
8509
+ requestRender();
8510
+ } else if (entry.status === "loading") {
8511
+ } else {
8512
+ fireVisualGen(false);
8513
+ }
8514
+ },
8515
+ regenerateVisual() {
8516
+ const qid = getCurrentQid();
8517
+ if (!qid) return;
8518
+ state2.visuals.set(qid, { status: "loading", content: "", visible: true });
8519
+ requestRender();
8520
+ fireVisualGen(true);
8521
+ },
8522
+ getHeaderInfo() {
8523
+ const it = item();
8524
+ const askTitle = currentDeck.title ?? currentDeck.interactions[0]?.title;
8525
+ const itWithName = it;
8526
+ return {
8527
+ currentIndex,
8528
+ queueLength: queue.length,
8529
+ sessionName: itWithName.sessionName ?? it.source?.sessionName,
8530
+ askTitle: askTitle ?? void 0,
8531
+ askedBy: it.source?.askedBy,
8532
+ subtitle: it.subtitle,
8533
+ blockedSince: it.blockedSince,
8534
+ kind: it.kind
8535
+ };
8536
+ },
8537
+ getCurrentQid
8538
+ };
8102
8539
  }
8103
8540
 
8104
- // src/tui/panels/stacked-detail.ts
8105
- var HEADERS_ACTIVE = {
8106
- top: "GOAL",
8107
- middle: "STRATEGY",
8108
- bottom: "ROADMAP"
8109
- };
8110
- var HEADERS_DONE = {
8111
- top: "GOAL",
8112
- middle: "COMPLETION",
8113
- bottom: "SUMMARY"
8114
- };
8115
- function renderStackedDetailRows(rect, state2, detailCtx) {
8116
- const focused = state2.focusPane === "detail";
8117
- const { w, h } = rect;
8118
- const innerW = w - 4;
8119
- const cursorNode = detailCtx.nodes[state2.cursorIndex];
8120
- if (!cursorNode || !state2.selectedSession || cursorNode.sessionId !== state2.selectedSession.id) {
8121
- return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mSelect a session\x1B[0m");
8122
- }
8123
- if (state2.detailMode === "cycle-log") {
8124
- return renderCycleLogMode(rect, state2, focused);
8125
- }
8126
- const session = state2.selectedSession;
8127
- const isDone = session.status === "completed";
8128
- const headers = isDone ? HEADERS_DONE : HEADERS_ACTIVE;
8129
- const middleContent = isDone ? buildCompletionContent(session) : state2.strategyContent;
8130
- const bottomContent = isDone ? pickSummaryContent(state2) : state2.planContent;
8131
- const cacheKey = [
8132
- cursorNode.sessionId,
8133
- rect.w,
8134
- isDone ? "done" : "active",
8135
- state2.goalContent.length,
8136
- middleContent.length,
8137
- bottomContent.length
8138
- ].join(":");
8139
- let lines = state2.cachedStackedLines;
8140
- if (cacheKey !== state2.stackedCacheKey || lines === null) {
8141
- lines = {
8142
- goal: buildSectionLines(state2.goalContent, innerW),
8143
- strategy: buildSectionLines(middleContent, innerW),
8144
- roadmap: buildSectionLines(bottomContent, innerW),
8145
- cycleLog: []
8146
- };
8147
- state2.cachedStackedLines = lines;
8148
- state2.stackedCacheKey = cacheKey;
8541
+ // src/tui/panels/inbox-deck.ts
8542
+ init_paths();
8543
+
8544
+ // src/tui/panels/review-action.ts
8545
+ init_shell();
8546
+ import { execSync as execSync4 } from "child_process";
8547
+ import { basename as basename3, dirname as dirname6 } from "path";
8548
+ function mountReviewActionPanel(opts) {
8549
+ let selectedAction = "open";
8550
+ function render(cols, _rows) {
8551
+ const rows = [];
8552
+ const fileBase = basename3(opts.reviewFile);
8553
+ const fileDir = dirname6(opts.reviewFile);
8554
+ const maxPathLen = Math.max(8, cols - 4);
8555
+ const pathDisplay = truncate(`${fileDir}/${fileBase}`, maxPathLen);
8556
+ rows.push(` ${ansiColor("\u25C8", "magenta")} ${pathDisplay}`);
8557
+ const age = opts.blockedSince ? formatTimeAgo(opts.blockedSince) : "unknown";
8558
+ const commentText = opts.draftCommentCount === 1 ? "1 comment in draft" : `${opts.draftCommentCount} comments in draft`;
8559
+ rows.push(` ${ansiDim(age)} \xB7 ${ansiDim(commentText)}`);
8560
+ rows.push("");
8561
+ const openChevron = selectedAction === "open" ? ">" : " ";
8562
+ const openLabel = selectedAction === "open" ? `${openChevron} ${ansiColor("[Open editor]", "magenta", true)}` : `${openChevron} [Open editor]`;
8563
+ rows.push(` ${openLabel}`);
8564
+ const submitChevron = selectedAction === "submit" ? ">" : " ";
8565
+ const submitLabel = selectedAction === "submit" ? `${submitChevron} ${ansiColor("[Submit]", "magenta", true)}` : `${submitChevron} [Submit]`;
8566
+ rows.push(` ${submitLabel}`);
8567
+ rows.push("");
8568
+ rows.push(` ${ansiDim("j/k toggle \xB7 Enter invoke \xB7 Esc unmount")}`);
8569
+ return rows;
8570
+ }
8571
+ function invokeOpen() {
8572
+ const sisBin = process.argv[1];
8573
+ const cmd = `${shellQuote(sisBin)} ask review open ${shellQuote(opts.askId)} --session ${shellQuote(opts.sessionId)}`;
8574
+ execSync4(
8575
+ `tmux display-popup -E -w 90% -h 90% -d ${shellQuote(opts.cwd)} ${shellQuote(cmd)}`,
8576
+ { stdio: "inherit", env: EXEC_ENV }
8577
+ );
8578
+ opts.onAfterAction();
8149
8579
  }
8150
- const heights = allocateStripHeights(h, lines.goal.length, lines.strategy.length, lines.roadmap.length);
8151
- const rows = new Array(h);
8152
- const focusColor = focused ? "cyan" : "gray";
8153
- const sgr = `\x1B[${colorToSGR(focusColor)}m`;
8154
- const reset = "\x1B[0m";
8155
- rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
8156
- rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
8157
- let cursor = 1;
8158
- cursor = paintStrip(
8159
- rows,
8160
- cursor,
8161
- w,
8162
- sgr,
8163
- reset,
8164
- headers.top,
8165
- lines.goal,
8166
- state2.goalScroll,
8167
- heights.goalHeight,
8168
- state2.focusedStrip === "goal" && focused
8169
- );
8170
- rows[cursor++] = sgr + "\u251C" + "\u2500".repeat(w - 2) + "\u2524" + reset;
8171
- cursor = paintStrip(
8172
- rows,
8173
- cursor,
8174
- w,
8175
- sgr,
8176
- reset,
8177
- headers.middle,
8178
- lines.strategy,
8179
- state2.strategyScroll,
8180
- heights.stratHeight,
8181
- state2.focusedStrip === "strategy" && focused
8182
- );
8183
- rows[cursor++] = sgr + "\u251C" + "\u2500".repeat(w - 2) + "\u2524" + reset;
8184
- paintStrip(
8185
- rows,
8186
- cursor,
8187
- w,
8188
- sgr,
8189
- reset,
8190
- headers.bottom,
8191
- lines.roadmap,
8192
- state2.roadmapScroll,
8193
- heights.roadHeight,
8194
- state2.focusedStrip === "roadmap" && focused
8195
- );
8196
- return rows;
8197
- }
8198
- function buildCompletionContent(session) {
8199
- const parts = [];
8200
- if (session.completedAt) {
8201
- parts.push(`*completed ${formatTimestamp(session.completedAt)}*`);
8202
- parts.push("");
8580
+ function invokeSubmit() {
8581
+ const sisBin = process.argv[1];
8582
+ execSync4(
8583
+ `${shellQuote(sisBin)} ask review complete ${shellQuote(opts.askId)} --session ${shellQuote(opts.sessionId)}`,
8584
+ { stdio: "inherit", cwd: opts.cwd, env: EXEC_ENV }
8585
+ );
8586
+ opts.onAfterAction();
8203
8587
  }
8204
- if (session.completionReport && session.completionReport.trim()) {
8205
- parts.push(session.completionReport.trim());
8206
- } else {
8207
- parts.push("_No completion report written._");
8588
+ function handleKey(input) {
8589
+ if (input === "j" || input === "\x1B[B") {
8590
+ selectedAction = "submit";
8591
+ return true;
8592
+ }
8593
+ if (input === "k" || input === "\x1B[A") {
8594
+ selectedAction = "open";
8595
+ return true;
8596
+ }
8597
+ if (input === "\r" || input === "\n") {
8598
+ if (selectedAction === "open") {
8599
+ invokeOpen();
8600
+ } else {
8601
+ invokeSubmit();
8602
+ }
8603
+ return true;
8604
+ }
8605
+ return false;
8208
8606
  }
8209
- return parts.join("\n");
8607
+ return {
8608
+ render,
8609
+ handleKey,
8610
+ get selectedAction() {
8611
+ return selectedAction;
8612
+ }
8613
+ };
8210
8614
  }
8211
- function pickSummaryContent(state2) {
8212
- if (state2.completionSummaryContent.trim()) return state2.completionSummaryContent;
8213
- if (state2.logsCycles.length > 0) {
8214
- const last = state2.logsCycles[state2.logsCycles.length - 1];
8215
- return `# Cycle ${last.cycle} log
8216
8615
 
8217
- ${last.content}`;
8218
- }
8219
- if (state2.strategyContent.trim()) return state2.strategyContent;
8220
- return "_No completion artifacts. Try expanding context/ in the tree for raw files._";
8616
+ // src/tui/panels/inbox-deck.ts
8617
+ var lastMountDims = null;
8618
+ function mountInlineDeck(state2, cols, rows) {
8619
+ return mountResolutionPanel(
8620
+ {
8621
+ aggregateInbox: state2.aggregateInbox,
8622
+ startIndex: 0,
8623
+ cols,
8624
+ rows,
8625
+ daemonSend: send,
8626
+ onUnmount: () => {
8627
+ state2.inlineDeck = null;
8628
+ state2.visuals.clear();
8629
+ state2.focusPane = "tree";
8630
+ requestRender();
8631
+ },
8632
+ onOrphanTakeover: makeOrphanTakeover(state2, {
8633
+ send,
8634
+ paneExists,
8635
+ switchToSession,
8636
+ selectWindow,
8637
+ selectPane
8638
+ })
8639
+ },
8640
+ state2
8641
+ );
8221
8642
  }
8222
- function formatTimestamp(iso) {
8643
+ function readDraftCommentCount(cwd2, sessionId2, askId2) {
8223
8644
  try {
8224
- const d = new Date(iso);
8225
- if (isNaN(d.getTime())) return iso;
8226
- return d.toLocaleString();
8645
+ const raw = readFileSync15(askReviewDraftPath(cwd2, sessionId2, askId2), "utf-8");
8646
+ const data = JSON.parse(raw);
8647
+ if (Array.isArray(data["comments"])) return data["comments"].length;
8648
+ return 0;
8227
8649
  } catch {
8228
- return iso;
8650
+ return 0;
8229
8651
  }
8230
8652
  }
8231
- function buildSectionLines(content, innerW) {
8232
- return buildHighlightedMarkdownLines(content, innerW);
8233
- }
8234
- function allocateStripHeights(rectH, gN, sN, _rN) {
8235
- const innerH = rectH - 2;
8236
- const stripsAvail = innerH - 2;
8237
- const goalCap = Math.floor(stripsAvail * 0.2);
8238
- const stratCap = Math.floor(stripsAvail * 0.4);
8239
- const roadCap = stripsAvail - goalCap - stratCap;
8240
- const goalNeed = Math.min(gN + 1, goalCap);
8241
- const stratNeed = Math.min(sN + 1, stratCap);
8242
- const slack = goalCap - goalNeed + (stratCap - stratNeed);
8243
- return {
8244
- goalHeight: Math.max(2, goalNeed),
8245
- stratHeight: Math.max(2, stratNeed),
8246
- roadHeight: Math.max(2, roadCap + slack)
8247
- };
8248
- }
8249
- function paintStrip(rows, startRow, w, sgr, reset, label, lines, scroll, height, focused) {
8250
- const innerW = w - 4;
8251
- const borderL = sgr + "\u2502" + reset + " ";
8252
- const borderR = " " + sgr + "\u2502" + reset;
8253
- const headerSeg = `\x1B[${colorToSGR("yellow")};1m \u258E ${label}\x1B[0m`;
8254
- const headerClipped = clipAnsi(headerSeg + (focused ? " \u25C0" : ""), innerW);
8255
- rows[startRow] = borderL + headerClipped + borderR;
8256
- const contentH = height - 1;
8257
- const hasOverflow = lines.length > contentH;
8258
- const viewableH = hasOverflow ? contentH - 1 : contentH;
8259
- const maxScroll = Math.max(0, lines.length - viewableH);
8260
- scroll.setMax(maxScroll);
8261
- const effOffset = scroll.offset;
8262
- for (let i = 0; i < viewableH; i++) {
8263
- const li = effOffset + i;
8264
- const ansi = li < lines.length ? renderLine(lines[li]) : "";
8265
- rows[startRow + 1 + i] = borderL + clipAnsi(ansi, innerW) + borderR;
8266
- }
8267
- if (hasOverflow) {
8268
- const pct = maxScroll > 0 ? Math.round(effOffset / maxScroll * 100) : 100;
8269
- const indicator = `\x1B[2m \u2195 ${pct}% \xB7 ${lines.length} lines\x1B[0m`;
8270
- rows[startRow + 1 + viewableH] = borderL + clipAnsi(indicator, innerW) + borderR;
8653
+ function readReviewFile(cwd2, sessionId2, askId2) {
8654
+ try {
8655
+ const raw = readFileSync15(askReviewPath(cwd2, sessionId2, askId2), "utf-8");
8656
+ const data = JSON.parse(raw);
8657
+ if (typeof data["file"] === "string") return data["file"];
8658
+ return null;
8659
+ } catch {
8660
+ return null;
8271
8661
  }
8272
- return startRow + height;
8273
8662
  }
8274
- function renderCycleLogMode(rect, state2, focused) {
8275
- if (state2.logsCycles.length === 0) {
8276
- return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mNo cycle logs yet\x1B[0m");
8663
+ function renderReviewPanel(rect, state2, item) {
8664
+ const blank = clipAnsi("", rect.w);
8665
+ const cwd2 = cwdFromDir(item.dir);
8666
+ const sessionId2 = sessionIdFromDir(item.dir);
8667
+ const askId2 = askIdFromDir(item.dir);
8668
+ const reviewFile = readReviewFile(cwd2, sessionId2, askId2);
8669
+ const panelAskId = state2.reviewPanel?._askId;
8670
+ if (state2.reviewPanel && panelAskId !== askId2) {
8671
+ state2.reviewPanel = null;
8672
+ }
8673
+ if (!state2.reviewPanel) {
8674
+ if (!reviewFile) {
8675
+ const rows = [];
8676
+ const mid = Math.floor(rect.h / 2);
8677
+ for (let i = 0; i < rect.h; i++) {
8678
+ rows.push(i === mid ? clipAnsi(ansiColor("[review.json not found \u2014 Esc to dismiss]", "red"), rect.w) : blank);
8679
+ }
8680
+ return rows;
8681
+ }
8682
+ const draftCommentCount = readDraftCommentCount(cwd2, sessionId2, askId2);
8683
+ const panel = mountReviewActionPanel({
8684
+ cwd: cwd2,
8685
+ sessionId: sessionId2,
8686
+ askId: askId2,
8687
+ reviewFile,
8688
+ blockedSince: item.blockedSince,
8689
+ draftCommentCount,
8690
+ onAfterAction: () => {
8691
+ state2.reviewPanel = null;
8692
+ state2.focusPane = "tree";
8693
+ requestRender();
8694
+ }
8695
+ });
8696
+ panel["_askId"] = askId2;
8697
+ state2.reviewPanel = panel;
8277
8698
  }
8278
- const cacheKey = `cycleLog:${state2.logsCycles.length}:${rect.w}`;
8279
- let lines;
8280
- if (cacheKey === state2.stackedCacheKey && state2.cachedStackedLines !== null) {
8281
- lines = state2.cachedStackedLines.cycleLog;
8282
- } else {
8283
- lines = buildLogsLines(state2.logsCycles, rect.w);
8284
- const existing = state2.cachedStackedLines ?? { goal: [], strategy: [], roadmap: [], cycleLog: [] };
8285
- state2.cachedStackedLines = { ...existing, cycleLog: lines };
8286
- state2.stackedCacheKey = cacheKey;
8699
+ const panelRows = state2.reviewPanel.render(rect.w, rect.h);
8700
+ const result = [];
8701
+ for (let i = 0; i < rect.h; i++) {
8702
+ const line = panelRows[i];
8703
+ result.push(clipAnsi(line !== void 0 ? line : "", rect.w));
8287
8704
  }
8288
- return buildPanelRows(rect, lines, state2.detailScroll, focused, "cyan", state2.stackedRenderedCache);
8705
+ return result;
8289
8706
  }
8290
-
8291
- // src/tui/panels/bottom.ts
8292
- init_render();
8293
- var B = ansiBold;
8294
- var D = ansiDim;
8295
- var SEP = D("\u2502 ");
8296
- var DANGER_BADGE = "\x1B[1;41;97m DANGEROUS \x1B[0m";
8297
- function renderStatusLine(buf, y, state2, cursorNodeType) {
8298
- const { mode, focusPane, notification, error } = state2;
8299
- if (mode === "report-detail") return;
8300
- let content;
8301
- if (notification !== null) {
8302
- const icon = /error|failed/i.test(notification) ? "\u2715" : /success|created|killed|sent|copied|deleted/i.test(notification) ? "\u2713" : "\u2139";
8303
- content = `\x1B[1;33m${icon} ${notification}\x1B[0m`;
8304
- } else if (error !== null) {
8305
- content = `\x1B[31m\u26A0 ${error}\x1B[0m`;
8306
- } else if (mode === "search") {
8307
- const cursor = `\x1B[7m \x1B[0m`;
8308
- content = `\x1B[1;34m/\x1B[0m${state2.searchText}${cursor}` + D(" enter to apply \xB7 esc to clear");
8309
- } else if (mode === "leader") {
8310
- content = `\x1B[1;35mLEADER\x1B[0m` + D(" [c]opy [o]pen [a]gent [S]ession [g]o or [s]cycle [h]ome [n]ew [m]sg [t]status [l]picker [x]kill [/]search [?]help [esc] cancel");
8311
- } else if (mode === "copy-menu") {
8312
- content = `\x1B[1;36mCOPY\x1B[0m` + D(" [p]ath [i]d [c] context [l]ogs [r]eport [a]gent ID [esc] cancel");
8313
- } else if (mode === "open-menu") {
8314
- content = `\x1B[1;32mOPEN\x1B[0m` + D(" [g]oal [r]oadmap [s]trategy [l]ogs [d]ir [R]eport [c]scratch [e]dit context [esc] cancel");
8315
- } else if (mode === "agent-menu") {
8316
- content = `\x1B[1;34mAGENT\x1B[0m` + D(" [s]pawn [m]sg [r]estart [R]erun [j]ump [o]pen-claude [t]ail [k]ill [e]xplore [d]ebug [esc] cancel");
8317
- } else if (mode === "session-menu") {
8318
- content = `\x1B[1;31mSESSION\x1B[0m` + D(" [n]ew [r]esume [c]ontinue [b]ollback [k]ill [d]elete [e]xport [w]indow [C]lone [i]history [esc] cancel");
8319
- } else if (mode === "go-menu") {
8320
- content = `\x1B[1;33mGO\x1B[0m` + D(" [w]indow [p]ane [s]ession [n]ext [r]econnect [esc] cancel");
8321
- } else if (mode === "help") {
8322
- content = `\x1B[1;33mHELP\x1B[0m` + D(" [esc] or [?] to dismiss");
8323
- } else if (focusPane === "logs" || focusPane === "detail") {
8324
- content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle view ") + B("[F]") + D("low \xB1 ") + 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");
8325
- } else if (cursorNodeType === "needs-you-virtual") {
8326
- content = B("[enter]") + D(" open ask ") + B("[esc]") + D(" back ") + SEP + B("[q]") + D("uit");
8327
- } else {
8328
- let contextFilePart = "";
8329
- if (cursorNodeType === "context-file") {
8330
- contextFilePart = B("[e]") + D("dit ") + B("[\u23CE]") + D(" open ");
8707
+ function renderInboxDeckRows(rect, state2) {
8708
+ if (rect.w <= 0 || rect.h <= 0) {
8709
+ return Array.from({ length: Math.max(0, rect.h) }, () => clipAnsi("", rect.w));
8710
+ }
8711
+ if (!state2.inlineDeck) lastMountDims = null;
8712
+ const blank = clipAnsi("", rect.w);
8713
+ const emptyState = () => {
8714
+ const rows2 = [];
8715
+ const mid = Math.floor(rect.h / 2);
8716
+ const msg = "No pending asks";
8717
+ const pad = Math.max(0, Math.floor((rect.w - msg.length) / 2));
8718
+ for (let i = 0; i < rect.h; i++) {
8719
+ rows2.push(i === mid ? clipAnsi(" ".repeat(pad) + ansiDim(msg), rect.w) : blank);
8720
+ }
8721
+ return rows2;
8722
+ };
8723
+ if (state2.aggregateInbox.length === 0) {
8724
+ return emptyState();
8725
+ }
8726
+ const firstItem = state2.aggregateInbox[0];
8727
+ if (firstItem.kind === "review") {
8728
+ return renderReviewPanel(rect, state2, firstItem);
8729
+ }
8730
+ const HEADER = 3;
8731
+ const bodyRows = Math.max(1, rect.h - HEADER);
8732
+ if (!state2.inlineDeck) {
8733
+ const handle2 = mountInlineDeck(state2, rect.w, bodyRows);
8734
+ state2.inlineDeck = handle2;
8735
+ lastMountDims = handle2 ? { cols: rect.w, rows: bodyRows } : null;
8736
+ if (!state2.inlineDeck) return emptyState();
8737
+ } else if (!lastMountDims || lastMountDims.cols !== rect.w || lastMountDims.rows !== bodyRows) {
8738
+ state2.inlineDeck.handleResize(rect.w, bodyRows);
8739
+ lastMountDims = { cols: rect.w, rows: bodyRows };
8740
+ }
8741
+ const handle = state2.inlineDeck;
8742
+ const info = handle.getHeaderInfo();
8743
+ const icon = kindIcon(info.kind);
8744
+ const iconColor = kindColor(info.kind);
8745
+ let prefixPlain = ` ${icon} Ask ${info.currentIndex + 1}/${info.queueLength} \xB7 ${info.sessionName ?? "?"}`;
8746
+ if (info.askedBy) prefixPlain += ` \xB7 ${info.askedBy}`;
8747
+ prefixPlain += ` \xB7 ${formatTimeAgo(info.blockedSince)} \u2014 `;
8748
+ const prefixDisplayWidth = stringWidth7(prefixPlain);
8749
+ const T = Math.max(8, rect.w - prefixDisplayWidth - 3);
8750
+ const title = truncate(info.askTitle ?? "", T);
8751
+ let line1 = ` ${ansiColor(icon, iconColor)} Ask ${info.currentIndex + 1}/${info.queueLength} \xB7 ${info.sessionName ?? "?"}`;
8752
+ if (info.askedBy) line1 += ` \xB7 ${info.askedBy}`;
8753
+ line1 += ` \xB7 ${formatTimeAgo(info.blockedSince)} \u2014 ${title}`;
8754
+ const row0 = clipAnsi(line1, rect.w);
8755
+ const row1 = clipAnsi(info.subtitle ? " " + ansiDim(info.subtitle) : "", rect.w);
8756
+ const row2 = blank;
8757
+ const qid = handle.getCurrentQid();
8758
+ const visualEntry = qid ? state2.visuals.get(qid) : void 0;
8759
+ const body = [];
8760
+ if (visualEntry?.visible && visualEntry.status === "ready") {
8761
+ const visualLines = visualEntry.content.split("\n");
8762
+ for (let i = 0; i < bodyRows; i++) {
8763
+ body.push(clipAnsi(i < visualLines.length ? visualLines[i] : "", rect.w));
8331
8764
  }
8332
- content = contextFilePart + B("[enter]") + D(" select ") + B("[m]") + D("essage ") + B("[n]") + D("ew ") + B("[w]") + D(" tmux ") + SEP + B("[q]") + D("uit");
8333
- }
8334
- if (state2.selectedSession?.dangerousMode === true) {
8335
- content = `${DANGER_BADGE} ${content}`;
8336
- }
8337
- writeClipped(buf, 1, y, content, buf.width - 2);
8338
- }
8339
-
8340
- // src/tui/panels/cross-session-inbox.ts
8341
- init_render();
8342
- var KIND_ICON = {
8343
- notify: "\u2709",
8344
- validation: "\u2713",
8345
- decision: "\u25C6",
8346
- context: "\u270E",
8347
- error: "\u26A0"
8348
- };
8349
- var KIND_COLOR = {
8350
- notify: "gray",
8351
- validation: "cyan",
8352
- decision: "cyan",
8353
- context: "cyan",
8354
- error: "red"
8355
- };
8356
- function buildInboxLines(items, width) {
8357
- const lines = [];
8358
- if (items.length === 0) {
8359
- lines.push(singleLine(" No pending asks across the fleet", { dim: true, italic: true }));
8360
- return lines;
8361
- }
8362
- lines.push(singleLine(` ${items.length} pending`, { bold: true }));
8363
- lines.push(singleLine(" "));
8364
- const contentWidth = width - 4;
8365
- for (const item of items) {
8366
- const kindKey = coerceKind(item.kind);
8367
- const icon = kindKey in KIND_ICON ? KIND_ICON[kindKey] : "\xB7";
8368
- const iconColor = kindKey in KIND_COLOR ? KIND_COLOR[kindKey] : "cyan";
8369
- const source = item.sessionName ? item.sessionName : item.sessionId.slice(0, 8);
8370
- const titleText = item.title ? item.title : `(${item.askId.slice(0, 8)})`;
8371
- const blocked = formatTimeAgo(item.blockedSince);
8372
- const maxTitle = Math.max(10, contentWidth - source.length - blocked.length - 8);
8373
- lines.push([
8374
- seg(" "),
8375
- seg(icon, { color: iconColor }),
8376
- seg(` ${source}`, { color: "yellow" }),
8377
- seg(" \xB7 ", { dim: true }),
8378
- seg(truncate(titleText, maxTitle), { bold: true }),
8379
- seg(` ${blocked}`, { dim: true })
8380
- ]);
8381
- if (item.subtitle) {
8382
- lines.push(singleLine(` ${truncate(item.subtitle, contentWidth - 6)}`, { dim: true }));
8765
+ } else {
8766
+ const humanloopLines = handle.render();
8767
+ const midRow = Math.floor(bodyRows / 2);
8768
+ for (let i = 0; i < bodyRows; i++) {
8769
+ if (visualEntry?.visible && visualEntry.status === "loading" && i === midRow) {
8770
+ const placeholderText = "[generating visual\u2026 ~30s]";
8771
+ const padL = Math.max(0, Math.floor((rect.w - placeholderText.length) / 2));
8772
+ body.push(clipAnsi(" ".repeat(padL) + ansiDim(placeholderText), rect.w));
8773
+ } else if (visualEntry?.visible && visualEntry.status === "error" && i === midRow) {
8774
+ const errText = visualEntry.error ? visualEntry.error : "unknown";
8775
+ body.push(
8776
+ clipAnsi(
8777
+ ansiColor(`[visual error: ${errText}]`, "red") + ansiDim(" [R] retry"),
8778
+ rect.w
8779
+ )
8780
+ );
8781
+ } else {
8782
+ body.push(clipAnsi(i < humanloopLines.length ? humanloopLines[i] : "", rect.w));
8783
+ }
8383
8784
  }
8384
8785
  }
8385
- return lines;
8386
- }
8387
- function renderCrossSessionInboxRows(rect, state2) {
8388
- if (rect.w <= 0 || rect.h <= 0) {
8389
- return buildEmptyPanelRows(rect, state2.focusPane === "detail", "red", "");
8390
- }
8391
- const focused = state2.focusPane === "detail";
8392
- const items = state2.aggregateInbox;
8393
- const fingerprint = items.map((i) => `${i.askId}:${i.status}`).join(",");
8394
- const cacheKey = `${items.length}:${fingerprint}:${rect.w}`;
8395
- let lines;
8396
- if (cacheKey === state2.inboxCacheKey && state2.cachedInboxLines !== null) {
8397
- lines = state2.cachedInboxLines;
8398
- } else {
8399
- lines = buildInboxLines(items, rect.w);
8400
- state2.cachedInboxLines = lines;
8401
- state2.inboxCacheKey = cacheKey;
8786
+ const rows = [row0, row1, row2];
8787
+ for (let i = 0; i < bodyRows && rows.length < rect.h; i++) {
8788
+ rows.push(body[i]);
8402
8789
  }
8403
- return buildPanelRows(rect, lines, state2.crossSessionInboxScroll, focused, "red", state2.inboxRenderedCache);
8790
+ while (rows.length < rect.h) rows.push(blank);
8791
+ return rows.slice(0, rect.h);
8404
8792
  }
8405
8793
 
8406
8794
  // src/tui/app.ts
@@ -8408,8 +8796,8 @@ init_paths();
8408
8796
 
8409
8797
  // src/tui/lib/popup-compose.ts
8410
8798
  init_shell();
8411
- import { execSync as execSync4 } from "child_process";
8412
- import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync10, readFileSync as readFileSync15, rmSync as rmSync5 } from "fs";
8799
+ import { execSync as execSync5 } from "child_process";
8800
+ import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync10, readFileSync as readFileSync16, rmSync as rmSync5 } from "fs";
8413
8801
  import { join as join14 } from "path";
8414
8802
  import { tmpdir as tmpdir2 } from "os";
8415
8803
 
@@ -8439,13 +8827,13 @@ function composeViaPopup(action, state2, actions) {
8439
8827
  try {
8440
8828
  writeFileSync10(tempFile, "", "utf-8");
8441
8829
  const cmd = `NVIM_APPNAME=sisyphus nvim ${shellQuote(tempFile)}`;
8442
- execSync4(
8830
+ execSync5(
8443
8831
  `tmux display-popup -E -w 90% -h 90% -d ${shellQuote(state2.cwd)} ${shellQuote(cmd)}`,
8444
8832
  { stdio: "inherit", env: EXEC_ENV }
8445
8833
  );
8446
8834
  let rawContent = "";
8447
8835
  try {
8448
- rawContent = readFileSync15(tempFile, "utf-8");
8836
+ rawContent = readFileSync16(tempFile, "utf-8");
8449
8837
  } catch {
8450
8838
  }
8451
8839
  const required = !OPTIONAL_COMPOSE.has(action.kind);
@@ -8470,7 +8858,7 @@ function getCompanion() {
8470
8858
  const { mtimeMs } = statSync4(companionPath());
8471
8859
  if (_cachedCompanion && mtimeMs === _companionMtime) return _cachedCompanion;
8472
8860
  _companionMtime = mtimeMs;
8473
- _cachedCompanion = normalizeCompanion(JSON.parse(readFileSync16(companionPath(), "utf-8")));
8861
+ _cachedCompanion = normalizeCompanion(JSON.parse(readFileSync17(companionPath(), "utf-8")));
8474
8862
  return _cachedCompanion;
8475
8863
  } catch {
8476
8864
  return _cachedCompanion;
@@ -8481,6 +8869,7 @@ var cachedContextFilePath = null;
8481
8869
  var cachedContextFileContent = null;
8482
8870
  var prevFrame = [];
8483
8871
  var prevResolutionActive = false;
8872
+ var prevCursorOnNeedsYou = false;
8484
8873
  var prevTreeInputs = "";
8485
8874
  var prevBottomInputs = "";
8486
8875
  function computeTreeWidth(cols) {
@@ -8490,6 +8879,8 @@ var prevOverlayMode = "";
8490
8879
  var cachedTreeRows = [];
8491
8880
  var cachedLogSessionId = null;
8492
8881
  var cachedLogFiles = /* @__PURE__ */ new Map();
8882
+ var detailPassToken = 0;
8883
+ var lastPolledDetailSessionId = void 0;
8493
8884
  function renderResolutionFrame(buf, state2) {
8494
8885
  const handle = state2.resolutionHandle;
8495
8886
  const info = handle.getHeaderInfo();
@@ -8561,13 +8952,6 @@ function startApp(state2, cleanup2) {
8561
8952
  async function poll() {
8562
8953
  try {
8563
8954
  let selectedSession = null;
8564
- let planContent = "";
8565
- let strategyContent = "";
8566
- let goalContent = "";
8567
- let completionSummaryContent = "";
8568
- let logsContent = "";
8569
- let logsCycles = [];
8570
- let digestData = null;
8571
8955
  let paneAlive = true;
8572
8956
  let contextFiles = [];
8573
8957
  const listPromise = send({ type: "list", cwd: state2.cwd });
@@ -8595,65 +8979,6 @@ function startApp(state2, cleanup2) {
8595
8979
  const cached2 = sessions.find((s) => s.id === state2.selectedSessionId);
8596
8980
  paneAlive = cached2?.windowAlive ?? false;
8597
8981
  }
8598
- try {
8599
- const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
8600
- if (existsSync11(pp)) {
8601
- planContent = readFileSync16(pp, "utf-8");
8602
- }
8603
- } catch {
8604
- }
8605
- try {
8606
- const gp = goalPath(state2.cwd, state2.selectedSessionId);
8607
- if (existsSync11(gp)) {
8608
- goalContent = readFileSync16(gp, "utf-8");
8609
- }
8610
- } catch {
8611
- }
8612
- try {
8613
- const sp = strategyPath(state2.cwd, state2.selectedSessionId);
8614
- if (existsSync11(sp)) {
8615
- strategyContent = readFileSync16(sp, "utf-8");
8616
- }
8617
- } catch {
8618
- }
8619
- try {
8620
- const cp = join15(contextDir(state2.cwd, state2.selectedSessionId), "completion-summary.md");
8621
- if (existsSync11(cp)) {
8622
- completionSummaryContent = readFileSync16(cp, "utf-8");
8623
- }
8624
- } catch {
8625
- }
8626
- try {
8627
- const ld = logsDir(state2.cwd, state2.selectedSessionId);
8628
- if (existsSync11(ld)) {
8629
- if (state2.selectedSessionId !== cachedLogSessionId) {
8630
- cachedLogFiles = /* @__PURE__ */ new Map();
8631
- cachedLogSessionId = state2.selectedSessionId;
8632
- }
8633
- const files = readdirSync7(ld).filter((f) => f.startsWith("cycle-")).sort();
8634
- const fileSet = new Set(files);
8635
- for (const key of cachedLogFiles.keys()) {
8636
- if (!fileSet.has(key)) cachedLogFiles.delete(key);
8637
- }
8638
- for (const f of files) {
8639
- const filePath = join15(ld, f);
8640
- const mtime = statSync4(filePath).mtimeMs;
8641
- const cached2 = cachedLogFiles.get(f);
8642
- if (!cached2 || cached2.mtime !== mtime) {
8643
- const match = f.match(/cycle-(\d+)\.md$/);
8644
- const cycle = match ? parseInt(match[1], 10) : 0;
8645
- const content = readFileSync16(filePath, "utf-8");
8646
- cachedLogFiles.set(f, { mtime, cycle, content });
8647
- }
8648
- }
8649
- logsCycles = files.map((f) => {
8650
- const entry = cachedLogFiles.get(f);
8651
- return { cycle: entry.cycle, content: entry.content };
8652
- });
8653
- logsContent = logsCycles.map((c) => c.content).join("\n");
8654
- }
8655
- } catch {
8656
- }
8657
8982
  try {
8658
8983
  const cd = contextDir(state2.cwd, state2.selectedSessionId);
8659
8984
  if (existsSync11(cd)) {
@@ -8674,37 +8999,29 @@ function startApp(state2, cleanup2) {
8674
8999
  }
8675
9000
  } catch {
8676
9001
  }
8677
- try {
8678
- const dp = digestPath(state2.cwd, state2.selectedSessionId);
8679
- if (existsSync11(dp)) {
8680
- const raw = JSON.parse(readFileSync16(dp, "utf-8"));
8681
- if (raw && typeof raw.recentWork === "string" && typeof raw.currentActivity === "string" && typeof raw.whatsNext === "string" && Array.isArray(raw.unusualEvents)) {
8682
- digestData = raw;
8683
- }
8684
- }
8685
- } catch {
8686
- }
8687
- }
8688
- state2.cachedReportBlocks.clear();
8689
- if (selectedSession) {
8690
- for (const agent of selectedSession.agents) {
8691
- state2.cachedReportBlocks.set(agent.id, resolveReports(agent.reports));
8692
- }
8693
9002
  }
8694
9003
  }
8695
9004
  state2.sessions = sessions;
8696
9005
  state2.selectedSession = selectedSession;
8697
- state2.planContent = planContent;
8698
- state2.strategyContent = strategyContent;
8699
- state2.goalContent = goalContent;
8700
- state2.completionSummaryContent = completionSummaryContent;
8701
- state2.logsContent = logsContent;
8702
- state2.logsCycles = logsCycles;
8703
- state2.digestData = digestData;
8704
9006
  state2.paneAlive = paneAlive;
8705
9007
  state2.contextFiles = contextFiles;
8706
9008
  state2.error = null;
9009
+ const detailSelectionChanged = state2.selectedSessionId !== lastPolledDetailSessionId;
9010
+ lastPolledDetailSessionId = state2.selectedSessionId;
9011
+ if (detailSelectionChanged) {
9012
+ state2.planContent = "";
9013
+ state2.strategyContent = "";
9014
+ state2.goalContent = "";
9015
+ state2.completionSummaryContent = "";
9016
+ state2.logsContent = "";
9017
+ state2.logsCycles = [];
9018
+ state2.digestData = null;
9019
+ state2.cachedReportBlocks.clear();
9020
+ }
8707
9021
  requestRender();
9022
+ const myToken = ++detailPassToken;
9023
+ const mySession = state2.selectedSessionId;
9024
+ setImmediate(() => scheduleDetailReads(myToken, mySession));
8708
9025
  } catch (err) {
8709
9026
  const wasError = state2.error !== null;
8710
9027
  state2.error = err.message;
@@ -8712,6 +9029,103 @@ function startApp(state2, cleanup2) {
8712
9029
  requestRender();
8713
9030
  }
8714
9031
  }
9032
+ function scheduleDetailReads(myToken, mySession) {
9033
+ if (myToken !== detailPassToken || state2.selectedSessionId !== mySession || state2.resolutionActive) return;
9034
+ let planContent = "";
9035
+ let strategyContent = "";
9036
+ let goalContent = "";
9037
+ let completionSummaryContent = "";
9038
+ let logsContent = "";
9039
+ let logsCycles = [];
9040
+ let digestData = null;
9041
+ if (mySession) {
9042
+ try {
9043
+ const pp = roadmapPath(state2.cwd, mySession);
9044
+ if (existsSync11(pp)) {
9045
+ planContent = readFileSync17(pp, "utf-8");
9046
+ }
9047
+ } catch {
9048
+ }
9049
+ try {
9050
+ const gp = goalPath(state2.cwd, mySession);
9051
+ if (existsSync11(gp)) {
9052
+ goalContent = readFileSync17(gp, "utf-8");
9053
+ }
9054
+ } catch {
9055
+ }
9056
+ try {
9057
+ const sp = strategyPath(state2.cwd, mySession);
9058
+ if (existsSync11(sp)) {
9059
+ strategyContent = readFileSync17(sp, "utf-8");
9060
+ }
9061
+ } catch {
9062
+ }
9063
+ try {
9064
+ const cp = join15(contextDir(state2.cwd, mySession), "completion-summary.md");
9065
+ if (existsSync11(cp)) {
9066
+ completionSummaryContent = readFileSync17(cp, "utf-8");
9067
+ }
9068
+ } catch {
9069
+ }
9070
+ try {
9071
+ const ld = logsDir(state2.cwd, mySession);
9072
+ if (existsSync11(ld)) {
9073
+ if (mySession !== cachedLogSessionId) {
9074
+ cachedLogFiles = /* @__PURE__ */ new Map();
9075
+ cachedLogSessionId = mySession;
9076
+ }
9077
+ const files = readdirSync7(ld).filter((f) => f.startsWith("cycle-")).sort();
9078
+ const fileSet = new Set(files);
9079
+ for (const key of cachedLogFiles.keys()) {
9080
+ if (!fileSet.has(key)) cachedLogFiles.delete(key);
9081
+ }
9082
+ for (const f of files) {
9083
+ const filePath = join15(ld, f);
9084
+ const mtime = statSync4(filePath).mtimeMs;
9085
+ const cached2 = cachedLogFiles.get(f);
9086
+ if (!cached2 || cached2.mtime !== mtime) {
9087
+ const match = f.match(/cycle-(\d+)\.md$/);
9088
+ const cycle = match ? parseInt(match[1], 10) : 0;
9089
+ const content = readFileSync17(filePath, "utf-8");
9090
+ cachedLogFiles.set(f, { mtime, cycle, content });
9091
+ }
9092
+ }
9093
+ logsCycles = files.map((f) => {
9094
+ const entry = cachedLogFiles.get(f);
9095
+ return { cycle: entry.cycle, content: entry.content };
9096
+ });
9097
+ logsContent = logsCycles.map((c) => c.content).join("\n");
9098
+ }
9099
+ } catch {
9100
+ }
9101
+ try {
9102
+ const dp = digestPath(state2.cwd, mySession);
9103
+ if (existsSync11(dp)) {
9104
+ const raw = JSON.parse(readFileSync17(dp, "utf-8"));
9105
+ if (raw && typeof raw.recentWork === "string" && typeof raw.currentActivity === "string" && typeof raw.whatsNext === "string" && Array.isArray(raw.unusualEvents)) {
9106
+ digestData = raw;
9107
+ }
9108
+ }
9109
+ } catch {
9110
+ }
9111
+ }
9112
+ if (myToken !== detailPassToken || state2.selectedSessionId !== mySession || state2.resolutionActive) return;
9113
+ state2.planContent = planContent;
9114
+ state2.strategyContent = strategyContent;
9115
+ state2.goalContent = goalContent;
9116
+ state2.completionSummaryContent = completionSummaryContent;
9117
+ state2.logsContent = logsContent;
9118
+ state2.logsCycles = logsCycles;
9119
+ state2.digestData = digestData;
9120
+ state2.cachedReportBlocks.clear();
9121
+ const sel = state2.selectedSession;
9122
+ if (sel) {
9123
+ for (const agent of sel.agents) {
9124
+ state2.cachedReportBlocks.set(agent.id, resolveReports(agent.reports));
9125
+ }
9126
+ }
9127
+ requestRender();
9128
+ }
8715
9129
  function render() {
8716
9130
  const stdoutRows = process.stdout.rows;
8717
9131
  const stdoutCols = process.stdout.columns;
@@ -8750,7 +9164,7 @@ function startApp(state2, cleanup2) {
8750
9164
  return s.task.toLowerCase().includes(q) || s.id.toLowerCase().includes(q);
8751
9165
  }) : state2.sessions;
8752
9166
  const statusFP = filteredSessions.map((s) => `${s.status}:${s.windowAlive}:${s.runningAgentCount}:${s.orphaned ?? false}`).join(",");
8753
- const inboxFP = `${state2.aggregateInbox.length}:${state2.aggregateInbox.map((i) => i.askId).join(",")}`;
9167
+ const inboxFP = `${state2.aggregateInbox.length}:${state2.aggregateInbox.map((i) => askIdFromDir(i.dir)).join(",")}`;
8754
9168
  const cacheKey = `${state2.expanded.size}:${filteredSessions.length}:${state2.selectedSession?.id}:${state2.contextFiles.length}:${state2.searchFilter}:${statusFP}:${inboxFP}`;
8755
9169
  let nodes;
8756
9170
  if (cacheKey === state2.treeCacheKey && state2.cachedTreeNodes !== null) {
@@ -8772,6 +9186,9 @@ function startApp(state2, cleanup2) {
8772
9186
  latestNodes = nodes;
8773
9187
  const cursorNode = nodes[state2.cursorIndex];
8774
9188
  if (cursorNode) state2.cursorNodeId = cursorNode.id;
9189
+ const onNeedsYou = cursorNode?.type === "needs-you-virtual";
9190
+ if (prevCursorOnNeedsYou && !onNeedsYou && state2.inlineDeck) state2.inlineDeck.unmount();
9191
+ prevCursorOnNeedsYou = onNeedsYou;
8775
9192
  const rawSessionId = cursorNode?.sessionId;
8776
9193
  const newSessionId = rawSessionId ? rawSessionId : null;
8777
9194
  if (newSessionId !== state2.selectedSessionId) {
@@ -8821,7 +9238,7 @@ function startApp(state2, cleanup2) {
8821
9238
  cachedContextFilePath = cursorNode.filePath;
8822
9239
  try {
8823
9240
  if (existsSync11(cursorNode.filePath)) {
8824
- cachedContextFileContent = readFileSync16(cursorNode.filePath, "utf-8");
9241
+ cachedContextFileContent = readFileSync17(cursorNode.filePath, "utf-8");
8825
9242
  } else {
8826
9243
  cachedContextFileContent = null;
8827
9244
  }
@@ -8885,7 +9302,7 @@ function startApp(state2, cleanup2) {
8885
9302
  };
8886
9303
  let detailRows;
8887
9304
  if (cursorNode?.type === "needs-you-virtual") {
8888
- detailRows = renderCrossSessionInboxRows(detailRect, state2);
9305
+ detailRows = renderInboxDeckRows(detailRect, state2);
8889
9306
  } else if (state2.useStackedDetail) {
8890
9307
  detailRows = renderStackedDetailRows(detailRect, state2, detailCtx);
8891
9308
  } else {