@code-yeongyu/senpi 2026.5.21-2 → 2026.5.23-2

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 (258) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +1 -1
  3. package/dist/cli/file-processor.d.ts.map +1 -1
  4. package/dist/cli/file-processor.js +2 -3
  5. package/dist/cli/file-processor.js.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +3 -10
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  10. package/dist/core/agent-session-runtime.js +2 -1
  11. package/dist/core/agent-session-runtime.js.map +1 -1
  12. package/dist/core/agent-session-services.d.ts.map +1 -1
  13. package/dist/core/agent-session-services.js +3 -2
  14. package/dist/core/agent-session-services.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +2 -0
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +28 -4
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/auth-storage.d.ts.map +1 -1
  20. package/dist/core/auth-storage.js +2 -1
  21. package/dist/core/auth-storage.js.map +1 -1
  22. package/dist/core/export-html/index.d.ts.map +1 -1
  23. package/dist/core/export-html/index.js +8 -7
  24. package/dist/core/export-html/index.js.map +1 -1
  25. package/dist/core/export-html/template.js +6 -3
  26. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/compaction/index.js +9 -0
  28. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  29. package/dist/core/extensions/builtin/history-search/filter.d.ts +3 -0
  30. package/dist/core/extensions/builtin/history-search/filter.d.ts.map +1 -0
  31. package/dist/core/extensions/builtin/history-search/filter.js +22 -0
  32. package/dist/core/extensions/builtin/history-search/filter.js.map +1 -0
  33. package/dist/core/extensions/builtin/history-search/index.d.ts +7 -0
  34. package/dist/core/extensions/builtin/history-search/index.d.ts.map +1 -0
  35. package/dist/core/extensions/builtin/history-search/index.js +45 -0
  36. package/dist/core/extensions/builtin/history-search/index.js.map +1 -0
  37. package/dist/core/extensions/builtin/history-search/indexer.d.ts +3 -0
  38. package/dist/core/extensions/builtin/history-search/indexer.d.ts.map +1 -0
  39. package/dist/core/extensions/builtin/history-search/indexer.js +161 -0
  40. package/dist/core/extensions/builtin/history-search/indexer.js.map +1 -0
  41. package/dist/core/extensions/builtin/history-search/overlay.d.ts +30 -0
  42. package/dist/core/extensions/builtin/history-search/overlay.d.ts.map +1 -0
  43. package/dist/core/extensions/builtin/history-search/overlay.js +115 -0
  44. package/dist/core/extensions/builtin/history-search/overlay.js.map +1 -0
  45. package/dist/core/extensions/builtin/history-search/types.d.ts +8 -0
  46. package/dist/core/extensions/builtin/history-search/types.d.ts.map +1 -0
  47. package/dist/core/extensions/builtin/history-search/types.js +2 -0
  48. package/dist/core/extensions/builtin/history-search/types.js.map +1 -0
  49. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/index.js +4 -0
  51. package/dist/core/extensions/builtin/index.js.map +1 -1
  52. package/dist/core/extensions/builtin/session-observer/index.d.ts +5 -0
  53. package/dist/core/extensions/builtin/session-observer/index.d.ts.map +1 -0
  54. package/dist/core/extensions/builtin/session-observer/index.js +36 -0
  55. package/dist/core/extensions/builtin/session-observer/index.js.map +1 -0
  56. package/dist/core/extensions/builtin/session-observer/loader.d.ts +3 -0
  57. package/dist/core/extensions/builtin/session-observer/loader.d.ts.map +1 -0
  58. package/dist/core/extensions/builtin/session-observer/loader.js +20 -0
  59. package/dist/core/extensions/builtin/session-observer/loader.js.map +1 -0
  60. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts +7 -0
  61. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts.map +1 -0
  62. package/dist/core/extensions/builtin/session-observer/overlay-format.js +30 -0
  63. package/dist/core/extensions/builtin/session-observer/overlay-format.js.map +1 -0
  64. package/dist/core/extensions/builtin/session-observer/overlay.d.ts +51 -0
  65. package/dist/core/extensions/builtin/session-observer/overlay.d.ts.map +1 -0
  66. package/dist/core/extensions/builtin/session-observer/overlay.js +239 -0
  67. package/dist/core/extensions/builtin/session-observer/overlay.js.map +1 -0
  68. package/dist/core/extensions/builtin/session-observer/scanner.d.ts +10 -0
  69. package/dist/core/extensions/builtin/session-observer/scanner.d.ts.map +1 -0
  70. package/dist/core/extensions/builtin/session-observer/scanner.js +140 -0
  71. package/dist/core/extensions/builtin/session-observer/scanner.js.map +1 -0
  72. package/dist/core/extensions/builtin/session-observer/text.d.ts +7 -0
  73. package/dist/core/extensions/builtin/session-observer/text.d.ts.map +1 -0
  74. package/dist/core/extensions/builtin/session-observer/text.js +37 -0
  75. package/dist/core/extensions/builtin/session-observer/text.js.map +1 -0
  76. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts +7 -0
  77. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts.map +1 -0
  78. package/dist/core/extensions/builtin/session-observer/transcript-entries.js +71 -0
  79. package/dist/core/extensions/builtin/session-observer/transcript-entries.js.map +1 -0
  80. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts +11 -0
  81. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts.map +1 -0
  82. package/dist/core/extensions/builtin/session-observer/transcript-format.js +65 -0
  83. package/dist/core/extensions/builtin/session-observer/transcript-format.js.map +1 -0
  84. package/dist/core/extensions/builtin/session-observer/transcript.d.ts +4 -0
  85. package/dist/core/extensions/builtin/session-observer/transcript.d.ts.map +1 -0
  86. package/dist/core/extensions/builtin/session-observer/transcript.js +81 -0
  87. package/dist/core/extensions/builtin/session-observer/transcript.js.map +1 -0
  88. package/dist/core/extensions/builtin/session-observer/types.d.ts +33 -0
  89. package/dist/core/extensions/builtin/session-observer/types.d.ts.map +1 -0
  90. package/dist/core/extensions/builtin/session-observer/types.js +2 -0
  91. package/dist/core/extensions/builtin/session-observer/types.js.map +1 -0
  92. package/dist/core/extensions/loader.d.ts.map +1 -1
  93. package/dist/core/extensions/loader.js +13 -30
  94. package/dist/core/extensions/loader.js.map +1 -1
  95. package/dist/core/extensions/runner.d.ts.map +1 -1
  96. package/dist/core/extensions/runner.js +1 -0
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/keybindings.d.ts +10 -0
  99. package/dist/core/keybindings.d.ts.map +1 -1
  100. package/dist/core/keybindings.js +3 -0
  101. package/dist/core/keybindings.js.map +1 -1
  102. package/dist/core/model-registry.d.ts.map +1 -1
  103. package/dist/core/model-registry.js +5 -1
  104. package/dist/core/model-registry.js.map +1 -1
  105. package/dist/core/package-manager.d.ts +1 -0
  106. package/dist/core/package-manager.d.ts.map +1 -1
  107. package/dist/core/package-manager.js +47 -32
  108. package/dist/core/package-manager.js.map +1 -1
  109. package/dist/core/prompt-templates.d.ts.map +1 -1
  110. package/dist/core/prompt-templates.js +6 -20
  111. package/dist/core/prompt-templates.js.map +1 -1
  112. package/dist/core/resource-loader.d.ts.map +1 -1
  113. package/dist/core/resource-loader.js +38 -31
  114. package/dist/core/resource-loader.js.map +1 -1
  115. package/dist/core/sdk.d.ts.map +1 -1
  116. package/dist/core/sdk.js +9 -4
  117. package/dist/core/sdk.js.map +1 -1
  118. package/dist/core/session-manager.d.ts.map +1 -1
  119. package/dist/core/session-manager.js +32 -24
  120. package/dist/core/session-manager.js.map +1 -1
  121. package/dist/core/settings-manager.d.ts.map +1 -1
  122. package/dist/core/settings-manager.js +6 -13
  123. package/dist/core/settings-manager.js.map +1 -1
  124. package/dist/core/skills.d.ts.map +1 -1
  125. package/dist/core/skills.js +8 -22
  126. package/dist/core/skills.js.map +1 -1
  127. package/dist/core/tools/bash.d.ts.map +1 -1
  128. package/dist/core/tools/bash.js +54 -53
  129. package/dist/core/tools/bash.js.map +1 -1
  130. package/dist/core/tools/edit-diff.d.ts +3 -1
  131. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  132. package/dist/core/tools/edit-diff.js +8 -1
  133. package/dist/core/tools/edit-diff.js.map +1 -1
  134. package/dist/core/tools/edit.d.ts +3 -1
  135. package/dist/core/tools/edit.d.ts.map +1 -1
  136. package/dist/core/tools/edit.js +44 -81
  137. package/dist/core/tools/edit.js.map +1 -1
  138. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  139. package/dist/core/tools/file-mutation-queue.js +27 -12
  140. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  141. package/dist/core/tools/find.d.ts.map +1 -1
  142. package/dist/core/tools/find.js +2 -3
  143. package/dist/core/tools/find.js.map +1 -1
  144. package/dist/core/tools/grep.d.ts.map +1 -1
  145. package/dist/core/tools/grep.js +3 -3
  146. package/dist/core/tools/grep.js.map +1 -1
  147. package/dist/core/tools/ls.d.ts.map +1 -1
  148. package/dist/core/tools/ls.js +5 -5
  149. package/dist/core/tools/ls.js.map +1 -1
  150. package/dist/core/tools/output-accumulator.d.ts +2 -0
  151. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  152. package/dist/core/tools/output-accumulator.js +9 -3
  153. package/dist/core/tools/output-accumulator.js.map +1 -1
  154. package/dist/core/tools/path-utils.d.ts +2 -0
  155. package/dist/core/tools/path-utils.d.ts.map +1 -1
  156. package/dist/core/tools/path-utils.js +39 -21
  157. package/dist/core/tools/path-utils.js.map +1 -1
  158. package/dist/core/tools/read.d.ts.map +1 -1
  159. package/dist/core/tools/read.js +9 -8
  160. package/dist/core/tools/read.js.map +1 -1
  161. package/dist/core/tools/truncate.d.ts.map +1 -1
  162. package/dist/core/tools/truncate.js +12 -2
  163. package/dist/core/tools/truncate.js.map +1 -1
  164. package/dist/core/tools/write.d.ts.map +1 -1
  165. package/dist/core/tools/write.js +20 -35
  166. package/dist/core/tools/write.js.map +1 -1
  167. package/dist/main.d.ts.map +1 -1
  168. package/dist/main.js +5 -6
  169. package/dist/main.js.map +1 -1
  170. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/config-selector.js +1 -1
  172. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  173. package/dist/modes/interactive/components/footer.d.ts +1 -0
  174. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  175. package/dist/modes/interactive/components/footer.js +87 -67
  176. package/dist/modes/interactive/components/footer.js.map +1 -1
  177. package/dist/modes/interactive/components/login-dialog.d.ts +9 -1
  178. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/login-dialog.js +29 -4
  180. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  181. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  182. package/dist/modes/interactive/interactive-mode.js +22 -4
  183. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  184. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  185. package/dist/modes/interactive/theme/theme.js +37 -28
  186. package/dist/modes/interactive/theme/theme.js.map +1 -1
  187. package/dist/utils/clipboard-native.d.ts +3 -1
  188. package/dist/utils/clipboard-native.d.ts.map +1 -1
  189. package/dist/utils/clipboard-native.js +14 -8
  190. package/dist/utils/clipboard-native.js.map +1 -1
  191. package/dist/utils/image-resize-core.d.ts +30 -0
  192. package/dist/utils/image-resize-core.d.ts.map +1 -0
  193. package/dist/utils/image-resize-core.js +124 -0
  194. package/dist/utils/image-resize-core.js.map +1 -0
  195. package/dist/utils/image-resize-worker.d.ts +2 -0
  196. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  197. package/dist/utils/image-resize-worker.js +31 -0
  198. package/dist/utils/image-resize-worker.js.map +1 -0
  199. package/dist/utils/image-resize.d.ts +7 -27
  200. package/dist/utils/image-resize.d.ts.map +1 -1
  201. package/dist/utils/image-resize.js +75 -115
  202. package/dist/utils/image-resize.js.map +1 -1
  203. package/dist/utils/paths.d.ts +16 -1
  204. package/dist/utils/paths.d.ts.map +1 -1
  205. package/dist/utils/paths.js +41 -7
  206. package/dist/utils/paths.js.map +1 -1
  207. package/docs/custom-provider.md +44 -12
  208. package/docs/models.md +8 -2
  209. package/docs/packages.md +5 -4
  210. package/docs/sdk.md +2 -0
  211. package/docs/usage.md +2 -2
  212. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  213. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  214. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  215. package/examples/extensions/sandbox/package-lock.json +2 -2
  216. package/examples/extensions/sandbox/package.json +1 -1
  217. package/examples/extensions/with-deps/package-lock.json +2 -2
  218. package/examples/extensions/with-deps/package.json +1 -1
  219. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  220. package/node_modules/@earendil-works/pi-ai/dist/cli.d.ts.map +1 -1
  221. package/node_modules/@earendil-works/pi-ai/dist/cli.js +14 -0
  222. package/node_modules/@earendil-works/pi-ai/dist/cli.js.map +1 -1
  223. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -1
  224. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  226. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +145 -225
  227. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  228. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +134 -225
  229. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  230. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  231. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +2 -1
  232. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts +27 -8
  234. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +35 -22
  236. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  237. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +10 -0
  238. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  239. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  240. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +19 -0
  241. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -0
  242. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +55 -0
  243. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -0
  244. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts +3 -3
  245. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +45 -69
  247. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts +1 -0
  249. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -0
  251. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js.map +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.d.ts +8 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.d.ts.map +1 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.js.map +1 -1
  255. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  256. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  257. package/npm-shrinkwrap.json +13 -13
  258. package/package.json +5 -5
@@ -0,0 +1,239 @@
1
+ import { Container, getKeybindings, SelectList, Spacer, Text, TruncatedText, } from "@earendil-works/pi-tui";
2
+ import { DynamicBorder } from "../../../../modes/interactive/components/dynamic-border.js";
3
+ import { keyHint } from "../../../../modes/interactive/components/keybinding-hints.js";
4
+ import { getMarkdownTheme, theme } from "../../../../modes/interactive/theme/theme.js";
5
+ import { shortenPath } from "../../../../utils/paths.js";
6
+ import { loadTranscriptSnapshot } from "./loader.js";
7
+ import { describeSession, pickerLabel, renderLine, sessionAge, viewerFooter } from "./overlay-format.js";
8
+ import { sanitizeLine } from "./text.js";
9
+ import { renderTranscript } from "./transcript.js";
10
+ const MAX_VISIBLE_SESSIONS = 12;
11
+ const PICKER_ACTIONS = [
12
+ "tui.select.up",
13
+ "tui.select.down",
14
+ "tui.select.confirm",
15
+ "tui.select.cancel",
16
+ ];
17
+ export class SessionHudOverlay extends Container {
18
+ options;
19
+ sessionsByValue = new Map();
20
+ topBorder = new DynamicBorder((text) => theme.fg("accent", text));
21
+ middleBorder = new DynamicBorder((text) => theme.fg("accent", text));
22
+ bottomBorder = new DynamicBorder((text) => theme.fg("accent", text));
23
+ list;
24
+ mode = "picker";
25
+ selectedSession;
26
+ snapshot;
27
+ renderedLines = [];
28
+ ranges = [];
29
+ selectedEntryIndex = -1;
30
+ shouldSelectLastOnLoad = false;
31
+ expandedEntries = new Set();
32
+ scrollOffset = 0;
33
+ viewportHeight = 12;
34
+ loadingText;
35
+ _focused = false;
36
+ constructor(options) {
37
+ super();
38
+ this.options = options;
39
+ this.rebuildPicker();
40
+ }
41
+ get focused() {
42
+ return this._focused;
43
+ }
44
+ set focused(value) {
45
+ this._focused = value;
46
+ }
47
+ handleInput(input) {
48
+ if (this.mode === "picker") {
49
+ this.handlePickerInput(input);
50
+ return;
51
+ }
52
+ this.handleViewerInput(input);
53
+ }
54
+ render(width) {
55
+ if (this.mode === "picker")
56
+ return super.render(width);
57
+ return this.renderViewer(width);
58
+ }
59
+ getMode() {
60
+ return this.mode;
61
+ }
62
+ getSelectedEntryIndex() {
63
+ return this.selectedEntryIndex;
64
+ }
65
+ getExpandedEntryCount() {
66
+ return this.expandedEntries.size;
67
+ }
68
+ handlePickerInput(input) {
69
+ const keybindings = getKeybindings();
70
+ if (PICKER_ACTIONS.some((action) => keybindings.matches(input, action))) {
71
+ this.list?.handleInput(input);
72
+ }
73
+ }
74
+ rebuildPicker() {
75
+ this.sessionsByValue.clear();
76
+ const items = this.options.sessions.map((session, index) => this.toPickerItem(session, index));
77
+ const list = new SelectList(items, Math.min(MAX_VISIBLE_SESSIONS, Math.max(1, items.length)), {
78
+ selectedPrefix: (text) => theme.fg("accent", text),
79
+ selectedText: (text) => text,
80
+ description: (text) => theme.fg("muted", text),
81
+ scrollInfo: (text) => theme.fg("dim", text),
82
+ noMatch: (text) => theme.fg("warning", text.replace("commands", "sessions")),
83
+ });
84
+ list.onSelect = (item) => {
85
+ const session = this.sessionsByValue.get(item.value);
86
+ if (session)
87
+ void this.openSession(session);
88
+ };
89
+ list.onCancel = () => this.options.done();
90
+ this.list = list;
91
+ this.clear();
92
+ this.addChild(new Spacer(1));
93
+ this.addChild(this.topBorder);
94
+ this.addChild(new Spacer(1));
95
+ this.addChild(new Text(`${theme.bold(theme.fg("accent", " Sessions"))}${theme.fg("dim", ` ${this.options.sessions.length} sessions`)}`, 0, 0));
96
+ this.addChild(new Spacer(1));
97
+ this.addChild(list);
98
+ this.addChild(new Spacer(1));
99
+ this.addChild(new TruncatedText(`${keyHint("tui.select.confirm", "view")} ${keyHint("tui.select.cancel", "close")}`, 0, 0));
100
+ this.addChild(new Spacer(1));
101
+ this.addChild(this.bottomBorder);
102
+ }
103
+ toPickerItem(session, index) {
104
+ const value = String(index);
105
+ this.sessionsByValue.set(value, session);
106
+ return { value, label: pickerLabel(session), description: describeSession(session) };
107
+ }
108
+ async openSession(session) {
109
+ this.mode = "viewer";
110
+ this.selectedSession = session;
111
+ this.snapshot = undefined;
112
+ this.loadingText = "Loading session transcript...";
113
+ this.resetViewerState();
114
+ this.options.requestRender();
115
+ try {
116
+ this.snapshot = await loadTranscriptSnapshot(session.path);
117
+ this.loadingText = undefined;
118
+ }
119
+ catch (error) {
120
+ this.loadingText = `Failed to read session: ${error instanceof Error ? error.message : String(error)}`;
121
+ }
122
+ this.rebuildTranscript(process.stdout.columns || 80);
123
+ this.options.requestRender();
124
+ }
125
+ resetViewerState() {
126
+ this.expandedEntries = new Set();
127
+ this.scrollOffset = 0;
128
+ this.selectedEntryIndex = -1;
129
+ this.shouldSelectLastOnLoad = true;
130
+ this.renderedLines = [];
131
+ this.ranges = [];
132
+ }
133
+ rebuildTranscript(width) {
134
+ if (!this.snapshot)
135
+ return;
136
+ const rendered = renderTranscript(this.snapshot.entries, {
137
+ width,
138
+ selectedIndex: this.selectedEntryIndex,
139
+ expandedEntries: this.expandedEntries,
140
+ markdownTheme: getMarkdownTheme(),
141
+ });
142
+ this.renderedLines = rendered.lines;
143
+ this.ranges = rendered.ranges;
144
+ if (this.ranges.length > 0 && this.shouldSelectLastOnLoad) {
145
+ this.shouldSelectLastOnLoad = false;
146
+ this.selectedEntryIndex = this.ranges.length - 1;
147
+ this.rebuildTranscript(width);
148
+ }
149
+ this.scrollToSelected();
150
+ }
151
+ renderViewer(width) {
152
+ this.viewportHeight = Math.max(5, (process.stdout.rows || 32) - 8);
153
+ this.rebuildTranscript(width);
154
+ const session = this.selectedSession;
155
+ const title = session ? `Sessions > ${shortenPath(session.cwd) || "unknown"} · ${session.shortId}` : "Sessions";
156
+ const status = session
157
+ ? `${session.messageCount} messages · ${sessionAge(session)}${this.snapshot?.model ? ` · ${this.snapshot.model}` : ""}`
158
+ : "";
159
+ const maxScroll = Math.max(0, this.renderedLines.length - this.viewportHeight);
160
+ this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));
161
+ const content = this.loadingText ? [theme.fg("dim", this.loadingText)] : this.renderedLines;
162
+ const visible = content.slice(this.scrollOffset, this.scrollOffset + this.viewportHeight);
163
+ const lines = [];
164
+ lines.push(...this.topBorder.render(width));
165
+ lines.push(renderLine(` ${theme.bold(theme.fg("accent", title))}`, width));
166
+ if (status)
167
+ lines.push(renderLine(` ${theme.fg("dim", status)}`, width));
168
+ lines.push(...this.middleBorder.render(width));
169
+ for (const line of visible)
170
+ lines.push(` ${sanitizeLine(line, width - 2)}`);
171
+ for (let index = visible.length; index < this.viewportHeight; index += 1)
172
+ lines.push("");
173
+ const scroll = content.length > this.viewportHeight
174
+ ? ` [${this.scrollOffset + 1}-${Math.min(this.scrollOffset + this.viewportHeight, content.length)}/${content.length}]`
175
+ : "";
176
+ lines.push(renderLine(` ${viewerFooter(scroll)}`, width));
177
+ lines.push(...this.bottomBorder.render(width));
178
+ return lines;
179
+ }
180
+ handleViewerInput(input) {
181
+ const keybindings = getKeybindings();
182
+ if (keybindings.matches(input, "app.sessions.observe")) {
183
+ this.options.done();
184
+ return;
185
+ }
186
+ else if (keybindings.matches(input, "tui.select.cancel"))
187
+ this.backToPicker();
188
+ else if (input === "j" || keybindings.matches(input, "tui.select.down"))
189
+ this.moveSelection(1);
190
+ else if (input === "k" || keybindings.matches(input, "tui.select.up"))
191
+ this.moveSelection(-1);
192
+ else if (keybindings.matches(input, "tui.select.pageDown"))
193
+ this.moveSelection(5);
194
+ else if (keybindings.matches(input, "tui.select.pageUp"))
195
+ this.moveSelection(-5);
196
+ else if (input === "g")
197
+ this.jumpTo(0);
198
+ else if (input === "G")
199
+ this.jumpTo(this.ranges.length - 1);
200
+ else if (keybindings.matches(input, "tui.select.confirm"))
201
+ this.toggleExpanded();
202
+ this.options.requestRender();
203
+ }
204
+ backToPicker() {
205
+ this.mode = "picker";
206
+ this.rebuildPicker();
207
+ }
208
+ moveSelection(delta) {
209
+ if (this.ranges.length === 0)
210
+ return;
211
+ this.selectedEntryIndex = Math.max(0, Math.min(this.selectedEntryIndex + delta, this.ranges.length - 1));
212
+ this.scrollToSelected();
213
+ }
214
+ jumpTo(index) {
215
+ if (this.ranges.length === 0)
216
+ return;
217
+ this.selectedEntryIndex = Math.max(0, Math.min(index, this.ranges.length - 1));
218
+ this.scrollToSelected();
219
+ }
220
+ toggleExpanded() {
221
+ if (this.ranges.length === 0)
222
+ return;
223
+ if (this.expandedEntries.has(this.selectedEntryIndex))
224
+ this.expandedEntries.delete(this.selectedEntryIndex);
225
+ else
226
+ this.expandedEntries.add(this.selectedEntryIndex);
227
+ }
228
+ scrollToSelected() {
229
+ const selected = this.ranges[this.selectedEntryIndex];
230
+ if (!selected)
231
+ return;
232
+ const bottom = selected.lineStart + selected.lineCount;
233
+ if (selected.lineStart < this.scrollOffset)
234
+ this.scrollOffset = Math.max(0, selected.lineStart - 1);
235
+ if (bottom > this.scrollOffset + this.viewportHeight)
236
+ this.scrollOffset = Math.max(0, bottom - this.viewportHeight + 1);
237
+ }
238
+ }
239
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/overlay.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tContainer,\n\ttype Focusable,\n\tgetKeybindings,\n\ttype SelectItem,\n\tSelectList,\n\tSpacer,\n\tText,\n\tTruncatedText,\n} from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { shortenPath } from \"../../../../utils/paths.ts\";\nimport type {} from \"../../../keybindings.ts\";\nimport { loadTranscriptSnapshot } from \"./loader.ts\";\nimport { describeSession, pickerLabel, renderLine, sessionAge, viewerFooter } from \"./overlay-format.ts\";\nimport { sanitizeLine } from \"./text.ts\";\nimport { renderTranscript } from \"./transcript.ts\";\nimport type { SessionHudEntry, TranscriptSnapshot, ViewerEntryRange } from \"./types.ts\";\n\nconst MAX_VISIBLE_SESSIONS = 12;\n\ntype Mode = \"picker\" | \"viewer\";\ntype PickerAction = \"tui.select.up\" | \"tui.select.down\" | \"tui.select.confirm\" | \"tui.select.cancel\";\n\nconst PICKER_ACTIONS: readonly PickerAction[] = [\n\t\"tui.select.up\",\n\t\"tui.select.down\",\n\t\"tui.select.confirm\",\n\t\"tui.select.cancel\",\n];\n\ninterface SessionHudOverlayOptions {\n\treadonly sessions: readonly SessionHudEntry[];\n\treadonly done: () => void;\n\treadonly requestRender: () => void;\n}\n\nexport class SessionHudOverlay extends Container implements Focusable {\n\tprivate readonly options: SessionHudOverlayOptions;\n\tprivate readonly sessionsByValue = new Map<string, SessionHudEntry>();\n\tprivate readonly topBorder = new DynamicBorder((text) => theme.fg(\"accent\", text));\n\tprivate readonly middleBorder = new DynamicBorder((text) => theme.fg(\"accent\", text));\n\tprivate readonly bottomBorder = new DynamicBorder((text) => theme.fg(\"accent\", text));\n\tprivate list: SelectList | undefined;\n\tprivate mode: Mode = \"picker\";\n\tprivate selectedSession: SessionHudEntry | undefined;\n\tprivate snapshot: TranscriptSnapshot | undefined;\n\tprivate renderedLines: readonly string[] = [];\n\tprivate ranges: readonly ViewerEntryRange[] = [];\n\tprivate selectedEntryIndex = -1;\n\tprivate shouldSelectLastOnLoad = false;\n\tprivate expandedEntries = new Set<number>();\n\tprivate scrollOffset = 0;\n\tprivate viewportHeight = 12;\n\tprivate loadingText: string | undefined;\n\tprivate _focused = false;\n\n\tconstructor(options: SessionHudOverlayOptions) {\n\t\tsuper();\n\t\tthis.options = options;\n\t\tthis.rebuildPicker();\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t}\n\n\thandleInput(input: string): void {\n\t\tif (this.mode === \"picker\") {\n\t\t\tthis.handlePickerInput(input);\n\t\t\treturn;\n\t\t}\n\t\tthis.handleViewerInput(input);\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (this.mode === \"picker\") return super.render(width);\n\t\treturn this.renderViewer(width);\n\t}\n\n\tgetMode(): Mode {\n\t\treturn this.mode;\n\t}\n\n\tgetSelectedEntryIndex(): number {\n\t\treturn this.selectedEntryIndex;\n\t}\n\n\tgetExpandedEntryCount(): number {\n\t\treturn this.expandedEntries.size;\n\t}\n\n\tprivate handlePickerInput(input: string): void {\n\t\tconst keybindings = getKeybindings();\n\t\tif (PICKER_ACTIONS.some((action) => keybindings.matches(input, action))) {\n\t\t\tthis.list?.handleInput(input);\n\t\t}\n\t}\n\n\tprivate rebuildPicker(): void {\n\t\tthis.sessionsByValue.clear();\n\t\tconst items = this.options.sessions.map((session, index) => this.toPickerItem(session, index));\n\t\tconst list = new SelectList(items, Math.min(MAX_VISIBLE_SESSIONS, Math.max(1, items.length)), {\n\t\t\tselectedPrefix: (text) => theme.fg(\"accent\", text),\n\t\t\tselectedText: (text) => text,\n\t\t\tdescription: (text) => theme.fg(\"muted\", text),\n\t\t\tscrollInfo: (text) => theme.fg(\"dim\", text),\n\t\t\tnoMatch: (text) => theme.fg(\"warning\", text.replace(\"commands\", \"sessions\")),\n\t\t});\n\t\tlist.onSelect = (item) => {\n\t\t\tconst session = this.sessionsByValue.get(item.value);\n\t\t\tif (session) void this.openSession(session);\n\t\t};\n\t\tlist.onCancel = () => this.options.done();\n\t\tthis.list = list;\n\t\tthis.clear();\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.topBorder);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\t`${theme.bold(theme.fg(\"accent\", \" Sessions\"))}${theme.fg(\"dim\", ` ${this.options.sessions.length} sessions`)}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(list);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew TruncatedText(`${keyHint(\"tui.select.confirm\", \"view\")} ${keyHint(\"tui.select.cancel\", \"close\")}`, 0, 0),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.bottomBorder);\n\t}\n\n\tprivate toPickerItem(session: SessionHudEntry, index: number): SelectItem {\n\t\tconst value = String(index);\n\t\tthis.sessionsByValue.set(value, session);\n\t\treturn { value, label: pickerLabel(session), description: describeSession(session) };\n\t}\n\n\tprivate async openSession(session: SessionHudEntry): Promise<void> {\n\t\tthis.mode = \"viewer\";\n\t\tthis.selectedSession = session;\n\t\tthis.snapshot = undefined;\n\t\tthis.loadingText = \"Loading session transcript...\";\n\t\tthis.resetViewerState();\n\t\tthis.options.requestRender();\n\t\ttry {\n\t\t\tthis.snapshot = await loadTranscriptSnapshot(session.path);\n\t\t\tthis.loadingText = undefined;\n\t\t} catch (error) {\n\t\t\tthis.loadingText = `Failed to read session: ${error instanceof Error ? error.message : String(error)}`;\n\t\t}\n\t\tthis.rebuildTranscript(process.stdout.columns || 80);\n\t\tthis.options.requestRender();\n\t}\n\n\tprivate resetViewerState(): void {\n\t\tthis.expandedEntries = new Set<number>();\n\t\tthis.scrollOffset = 0;\n\t\tthis.selectedEntryIndex = -1;\n\t\tthis.shouldSelectLastOnLoad = true;\n\t\tthis.renderedLines = [];\n\t\tthis.ranges = [];\n\t}\n\n\tprivate rebuildTranscript(width: number): void {\n\t\tif (!this.snapshot) return;\n\t\tconst rendered = renderTranscript(this.snapshot.entries, {\n\t\t\twidth,\n\t\t\tselectedIndex: this.selectedEntryIndex,\n\t\t\texpandedEntries: this.expandedEntries,\n\t\t\tmarkdownTheme: getMarkdownTheme(),\n\t\t});\n\t\tthis.renderedLines = rendered.lines;\n\t\tthis.ranges = rendered.ranges;\n\t\tif (this.ranges.length > 0 && this.shouldSelectLastOnLoad) {\n\t\t\tthis.shouldSelectLastOnLoad = false;\n\t\t\tthis.selectedEntryIndex = this.ranges.length - 1;\n\t\t\tthis.rebuildTranscript(width);\n\t\t}\n\t\tthis.scrollToSelected();\n\t}\n\n\tprivate renderViewer(width: number): string[] {\n\t\tthis.viewportHeight = Math.max(5, (process.stdout.rows || 32) - 8);\n\t\tthis.rebuildTranscript(width);\n\t\tconst session = this.selectedSession;\n\t\tconst title = session ? `Sessions > ${shortenPath(session.cwd) || \"unknown\"} · ${session.shortId}` : \"Sessions\";\n\t\tconst status = session\n\t\t\t? `${session.messageCount} messages · ${sessionAge(session)}${this.snapshot?.model ? ` · ${this.snapshot.model}` : \"\"}`\n\t\t\t: \"\";\n\t\tconst maxScroll = Math.max(0, this.renderedLines.length - this.viewportHeight);\n\t\tthis.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));\n\t\tconst content = this.loadingText ? [theme.fg(\"dim\", this.loadingText)] : this.renderedLines;\n\t\tconst visible = content.slice(this.scrollOffset, this.scrollOffset + this.viewportHeight);\n\t\tconst lines: string[] = [];\n\t\tlines.push(...this.topBorder.render(width));\n\t\tlines.push(renderLine(` ${theme.bold(theme.fg(\"accent\", title))}`, width));\n\t\tif (status) lines.push(renderLine(` ${theme.fg(\"dim\", status)}`, width));\n\t\tlines.push(...this.middleBorder.render(width));\n\t\tfor (const line of visible) lines.push(` ${sanitizeLine(line, width - 2)}`);\n\t\tfor (let index = visible.length; index < this.viewportHeight; index += 1) lines.push(\"\");\n\t\tconst scroll =\n\t\t\tcontent.length > this.viewportHeight\n\t\t\t\t? ` [${this.scrollOffset + 1}-${Math.min(this.scrollOffset + this.viewportHeight, content.length)}/${content.length}]`\n\t\t\t\t: \"\";\n\t\tlines.push(renderLine(` ${viewerFooter(scroll)}`, width));\n\t\tlines.push(...this.bottomBorder.render(width));\n\t\treturn lines;\n\t}\n\n\tprivate handleViewerInput(input: string): void {\n\t\tconst keybindings = getKeybindings();\n\t\tif (keybindings.matches(input, \"app.sessions.observe\")) {\n\t\t\tthis.options.done();\n\t\t\treturn;\n\t\t} else if (keybindings.matches(input, \"tui.select.cancel\")) this.backToPicker();\n\t\telse if (input === \"j\" || keybindings.matches(input, \"tui.select.down\")) this.moveSelection(1);\n\t\telse if (input === \"k\" || keybindings.matches(input, \"tui.select.up\")) this.moveSelection(-1);\n\t\telse if (keybindings.matches(input, \"tui.select.pageDown\")) this.moveSelection(5);\n\t\telse if (keybindings.matches(input, \"tui.select.pageUp\")) this.moveSelection(-5);\n\t\telse if (input === \"g\") this.jumpTo(0);\n\t\telse if (input === \"G\") this.jumpTo(this.ranges.length - 1);\n\t\telse if (keybindings.matches(input, \"tui.select.confirm\")) this.toggleExpanded();\n\t\tthis.options.requestRender();\n\t}\n\n\tprivate backToPicker(): void {\n\t\tthis.mode = \"picker\";\n\t\tthis.rebuildPicker();\n\t}\n\n\tprivate moveSelection(delta: number): void {\n\t\tif (this.ranges.length === 0) return;\n\t\tthis.selectedEntryIndex = Math.max(0, Math.min(this.selectedEntryIndex + delta, this.ranges.length - 1));\n\t\tthis.scrollToSelected();\n\t}\n\n\tprivate jumpTo(index: number): void {\n\t\tif (this.ranges.length === 0) return;\n\t\tthis.selectedEntryIndex = Math.max(0, Math.min(index, this.ranges.length - 1));\n\t\tthis.scrollToSelected();\n\t}\n\n\tprivate toggleExpanded(): void {\n\t\tif (this.ranges.length === 0) return;\n\t\tif (this.expandedEntries.has(this.selectedEntryIndex)) this.expandedEntries.delete(this.selectedEntryIndex);\n\t\telse this.expandedEntries.add(this.selectedEntryIndex);\n\t}\n\n\tprivate scrollToSelected(): void {\n\t\tconst selected = this.ranges[this.selectedEntryIndex];\n\t\tif (!selected) return;\n\t\tconst bottom = selected.lineStart + selected.lineCount;\n\t\tif (selected.lineStart < this.scrollOffset) this.scrollOffset = Math.max(0, selected.lineStart - 1);\n\t\tif (bottom > this.scrollOffset + this.viewportHeight)\n\t\t\tthis.scrollOffset = Math.max(0, bottom - this.viewportHeight + 1);\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { SessionHudEntry } from "./types.ts";
2
+ interface PathLike {
3
+ resolve(path: string): string;
4
+ relative(from: string, to: string): string;
5
+ isAbsolute(path: string): boolean;
6
+ }
7
+ export declare function resolveSessionHudRoot(currentSessionDir: string, defaultSessionsRoot?: string, pathImpl?: PathLike): string;
8
+ export declare function scanSessionHudEntries(root: string, currentSessionFile?: string): Promise<readonly SessionHudEntry[]>;
9
+ export {};
10
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/scanner.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,UAAU,QAAQ;IACjB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC;AAED,wBAAgB,qBAAqB,CACpC,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,GAAE,MAAyB,EAC9C,QAAQ,GAAE,QAA4C,GACpD,MAAM,CAQR;AAsGD,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,MAAM,EACZ,kBAAkB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAarC","sourcesContent":["import type { Stats } from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getSessionsDir } from \"../../../../config.ts\";\nimport type { FileEntry, SessionHeader } from \"../../../session-manager.ts\";\nimport { parseSessionEntries } from \"../../../session-manager.ts\";\nimport { compactWhitespace, getTextContent } from \"./text.ts\";\nimport type { SessionHudEntry } from \"./types.ts\";\n\ninterface PathLike {\n\tresolve(path: string): string;\n\trelative(from: string, to: string): string;\n\tisAbsolute(path: string): boolean;\n}\n\nexport function resolveSessionHudRoot(\n\tcurrentSessionDir: string,\n\tdefaultSessionsRoot: string = getSessionsDir(),\n\tpathImpl: PathLike = { resolve, relative, isAbsolute },\n): string {\n\tconst defaultRoot = pathImpl.resolve(defaultSessionsRoot);\n\tif (!currentSessionDir) return defaultRoot;\n\tconst current = pathImpl.resolve(currentSessionDir);\n\tif (current === defaultRoot) return defaultRoot;\n\tconst rel = pathImpl.relative(defaultRoot, current);\n\tif (rel && !rel.startsWith(\"..\") && !pathImpl.isAbsolute(rel)) return defaultRoot;\n\treturn current;\n}\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function readDirIfExists(path: string): Promise<readonly string[]> {\n\ttry {\n\t\treturn await readdir(path);\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return [];\n\t\tthrow error;\n\t}\n}\n\nasync function statIfFile(path: string): Promise<Stats | undefined> {\n\ttry {\n\t\tconst fileStat = await stat(path);\n\t\treturn fileStat.isFile() ? fileStat : undefined;\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\n}\n\nasync function collectFilesInDir(dir: string): Promise<readonly string[]> {\n\tconst names = await readDirIfExists(dir);\n\tconst files: string[] = [];\n\tfor (const name of names) {\n\t\tif (!name.endsWith(\".jsonl\")) continue;\n\t\tconst file = join(dir, name);\n\t\tif (await statIfFile(file)) files.push(file);\n\t}\n\treturn files;\n}\n\nasync function discoverSessionFiles(root: string): Promise<readonly string[]> {\n\tconst names = await readDirIfExists(root);\n\tconst files: string[] = [...(await collectFilesInDir(root))];\n\tfor (const name of names) {\n\t\tif (name.endsWith(\".jsonl\")) continue;\n\t\tconst dir = join(root, name);\n\t\ttry {\n\t\t\tconst dirStat = await stat(dir);\n\t\t\tif (dirStat.isDirectory()) files.push(...(await collectFilesInDir(dir)));\n\t\t} catch (error) {\n\t\t\tif (!hasErrorCode(error, \"ENOENT\")) throw error;\n\t\t}\n\t}\n\treturn files;\n}\n\nfunction firstHeader(entries: readonly FileEntry[], filePath: string): SessionHeader | undefined {\n\tconst header = entries[0];\n\tif (header?.type === \"session\") return header;\n\treturn { type: \"session\", id: basename(filePath, \".jsonl\"), timestamp: new Date(0).toISOString(), cwd: \"\" };\n}\n\nfunction lastUserText(entries: readonly FileEntry[]): string {\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (entry?.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\") continue;\n\t\tconst text = compactWhitespace(getTextContent(message.content));\n\t\tif (text) return text;\n\t}\n\treturn \"(no user prompt)\";\n}\n\nfunction latestMessageTimestamp(entries: readonly FileEntry[], fallback: number): number {\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (!entry || entry.type === \"session\") continue;\n\t\tconst timestamp = Date.parse(entry.timestamp);\n\t\tif (Number.isFinite(timestamp)) return timestamp;\n\t}\n\treturn fallback;\n}\n\nasync function summarizeSession(\n\tfilePath: string,\n\tcurrentSessionFile: string | undefined,\n): Promise<SessionHudEntry | undefined> {\n\tconst [content, fileStat] = await Promise.all([readFile(filePath, \"utf-8\"), stat(filePath)]);\n\tconst entries = parseSessionEntries(content);\n\tconst header = firstHeader(entries, filePath);\n\tif (!header) return undefined;\n\tconst messageCount = entries.filter((entry) => entry.type === \"message\").length;\n\treturn {\n\t\tid: header.id,\n\t\tshortId: header.id.length <= 8 ? header.id : header.id.slice(0, 8),\n\t\tpath: filePath,\n\t\tcwd: header.cwd,\n\t\tcreatedAt: Date.parse(header.timestamp),\n\t\tmodifiedAt: latestMessageTimestamp(entries, fileStat.mtime.getTime()),\n\t\tmessageCount,\n\t\tlastUserText: lastUserText(entries),\n\t\tisCurrent: currentSessionFile === filePath,\n\t};\n}\n\nexport async function scanSessionHudEntries(\n\troot: string,\n\tcurrentSessionFile?: string,\n): Promise<readonly SessionHudEntry[]> {\n\tconst files = await discoverSessionFiles(root);\n\tconst sessions: SessionHudEntry[] = [];\n\tfor (const file of files) {\n\t\ttry {\n\t\t\tconst session = await summarizeSession(file, currentSessionFile);\n\t\t\tif (session) sessions.push(session);\n\t\t} catch (error) {\n\t\t\tif (!hasErrorCode(error, \"ENOENT\")) throw error;\n\t\t}\n\t}\n\tsessions.sort((left, right) => right.modifiedAt - left.modifiedAt);\n\treturn sessions;\n}\n"]}
@@ -0,0 +1,140 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { basename, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { getSessionsDir } from "../../../../config.js";
4
+ import { parseSessionEntries } from "../../../session-manager.js";
5
+ import { compactWhitespace, getTextContent } from "./text.js";
6
+ export function resolveSessionHudRoot(currentSessionDir, defaultSessionsRoot = getSessionsDir(), pathImpl = { resolve, relative, isAbsolute }) {
7
+ const defaultRoot = pathImpl.resolve(defaultSessionsRoot);
8
+ if (!currentSessionDir)
9
+ return defaultRoot;
10
+ const current = pathImpl.resolve(currentSessionDir);
11
+ if (current === defaultRoot)
12
+ return defaultRoot;
13
+ const rel = pathImpl.relative(defaultRoot, current);
14
+ if (rel && !rel.startsWith("..") && !pathImpl.isAbsolute(rel))
15
+ return defaultRoot;
16
+ return current;
17
+ }
18
+ function hasErrorCode(error, code) {
19
+ return error instanceof Error && "code" in error && error.code === code;
20
+ }
21
+ async function readDirIfExists(path) {
22
+ try {
23
+ return await readdir(path);
24
+ }
25
+ catch (error) {
26
+ if (hasErrorCode(error, "ENOENT"))
27
+ return [];
28
+ throw error;
29
+ }
30
+ }
31
+ async function statIfFile(path) {
32
+ try {
33
+ const fileStat = await stat(path);
34
+ return fileStat.isFile() ? fileStat : undefined;
35
+ }
36
+ catch (error) {
37
+ if (hasErrorCode(error, "ENOENT"))
38
+ return undefined;
39
+ throw error;
40
+ }
41
+ }
42
+ async function collectFilesInDir(dir) {
43
+ const names = await readDirIfExists(dir);
44
+ const files = [];
45
+ for (const name of names) {
46
+ if (!name.endsWith(".jsonl"))
47
+ continue;
48
+ const file = join(dir, name);
49
+ if (await statIfFile(file))
50
+ files.push(file);
51
+ }
52
+ return files;
53
+ }
54
+ async function discoverSessionFiles(root) {
55
+ const names = await readDirIfExists(root);
56
+ const files = [...(await collectFilesInDir(root))];
57
+ for (const name of names) {
58
+ if (name.endsWith(".jsonl"))
59
+ continue;
60
+ const dir = join(root, name);
61
+ try {
62
+ const dirStat = await stat(dir);
63
+ if (dirStat.isDirectory())
64
+ files.push(...(await collectFilesInDir(dir)));
65
+ }
66
+ catch (error) {
67
+ if (!hasErrorCode(error, "ENOENT"))
68
+ throw error;
69
+ }
70
+ }
71
+ return files;
72
+ }
73
+ function firstHeader(entries, filePath) {
74
+ const header = entries[0];
75
+ if (header?.type === "session")
76
+ return header;
77
+ return { type: "session", id: basename(filePath, ".jsonl"), timestamp: new Date(0).toISOString(), cwd: "" };
78
+ }
79
+ function lastUserText(entries) {
80
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
81
+ const entry = entries[index];
82
+ if (entry?.type !== "message")
83
+ continue;
84
+ const message = entry.message;
85
+ if (message.role !== "user")
86
+ continue;
87
+ const text = compactWhitespace(getTextContent(message.content));
88
+ if (text)
89
+ return text;
90
+ }
91
+ return "(no user prompt)";
92
+ }
93
+ function latestMessageTimestamp(entries, fallback) {
94
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
95
+ const entry = entries[index];
96
+ if (!entry || entry.type === "session")
97
+ continue;
98
+ const timestamp = Date.parse(entry.timestamp);
99
+ if (Number.isFinite(timestamp))
100
+ return timestamp;
101
+ }
102
+ return fallback;
103
+ }
104
+ async function summarizeSession(filePath, currentSessionFile) {
105
+ const [content, fileStat] = await Promise.all([readFile(filePath, "utf-8"), stat(filePath)]);
106
+ const entries = parseSessionEntries(content);
107
+ const header = firstHeader(entries, filePath);
108
+ if (!header)
109
+ return undefined;
110
+ const messageCount = entries.filter((entry) => entry.type === "message").length;
111
+ return {
112
+ id: header.id,
113
+ shortId: header.id.length <= 8 ? header.id : header.id.slice(0, 8),
114
+ path: filePath,
115
+ cwd: header.cwd,
116
+ createdAt: Date.parse(header.timestamp),
117
+ modifiedAt: latestMessageTimestamp(entries, fileStat.mtime.getTime()),
118
+ messageCount,
119
+ lastUserText: lastUserText(entries),
120
+ isCurrent: currentSessionFile === filePath,
121
+ };
122
+ }
123
+ export async function scanSessionHudEntries(root, currentSessionFile) {
124
+ const files = await discoverSessionFiles(root);
125
+ const sessions = [];
126
+ for (const file of files) {
127
+ try {
128
+ const session = await summarizeSession(file, currentSessionFile);
129
+ if (session)
130
+ sessions.push(session);
131
+ }
132
+ catch (error) {
133
+ if (!hasErrorCode(error, "ENOENT"))
134
+ throw error;
135
+ }
136
+ }
137
+ sessions.sort((left, right) => right.modifiedAt - left.modifiedAt);
138
+ return sessions;
139
+ }
140
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/scanner.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Stats } from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getSessionsDir } from \"../../../../config.ts\";\nimport type { FileEntry, SessionHeader } from \"../../../session-manager.ts\";\nimport { parseSessionEntries } from \"../../../session-manager.ts\";\nimport { compactWhitespace, getTextContent } from \"./text.ts\";\nimport type { SessionHudEntry } from \"./types.ts\";\n\ninterface PathLike {\n\tresolve(path: string): string;\n\trelative(from: string, to: string): string;\n\tisAbsolute(path: string): boolean;\n}\n\nexport function resolveSessionHudRoot(\n\tcurrentSessionDir: string,\n\tdefaultSessionsRoot: string = getSessionsDir(),\n\tpathImpl: PathLike = { resolve, relative, isAbsolute },\n): string {\n\tconst defaultRoot = pathImpl.resolve(defaultSessionsRoot);\n\tif (!currentSessionDir) return defaultRoot;\n\tconst current = pathImpl.resolve(currentSessionDir);\n\tif (current === defaultRoot) return defaultRoot;\n\tconst rel = pathImpl.relative(defaultRoot, current);\n\tif (rel && !rel.startsWith(\"..\") && !pathImpl.isAbsolute(rel)) return defaultRoot;\n\treturn current;\n}\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function readDirIfExists(path: string): Promise<readonly string[]> {\n\ttry {\n\t\treturn await readdir(path);\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return [];\n\t\tthrow error;\n\t}\n}\n\nasync function statIfFile(path: string): Promise<Stats | undefined> {\n\ttry {\n\t\tconst fileStat = await stat(path);\n\t\treturn fileStat.isFile() ? fileStat : undefined;\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\n}\n\nasync function collectFilesInDir(dir: string): Promise<readonly string[]> {\n\tconst names = await readDirIfExists(dir);\n\tconst files: string[] = [];\n\tfor (const name of names) {\n\t\tif (!name.endsWith(\".jsonl\")) continue;\n\t\tconst file = join(dir, name);\n\t\tif (await statIfFile(file)) files.push(file);\n\t}\n\treturn files;\n}\n\nasync function discoverSessionFiles(root: string): Promise<readonly string[]> {\n\tconst names = await readDirIfExists(root);\n\tconst files: string[] = [...(await collectFilesInDir(root))];\n\tfor (const name of names) {\n\t\tif (name.endsWith(\".jsonl\")) continue;\n\t\tconst dir = join(root, name);\n\t\ttry {\n\t\t\tconst dirStat = await stat(dir);\n\t\t\tif (dirStat.isDirectory()) files.push(...(await collectFilesInDir(dir)));\n\t\t} catch (error) {\n\t\t\tif (!hasErrorCode(error, \"ENOENT\")) throw error;\n\t\t}\n\t}\n\treturn files;\n}\n\nfunction firstHeader(entries: readonly FileEntry[], filePath: string): SessionHeader | undefined {\n\tconst header = entries[0];\n\tif (header?.type === \"session\") return header;\n\treturn { type: \"session\", id: basename(filePath, \".jsonl\"), timestamp: new Date(0).toISOString(), cwd: \"\" };\n}\n\nfunction lastUserText(entries: readonly FileEntry[]): string {\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (entry?.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\") continue;\n\t\tconst text = compactWhitespace(getTextContent(message.content));\n\t\tif (text) return text;\n\t}\n\treturn \"(no user prompt)\";\n}\n\nfunction latestMessageTimestamp(entries: readonly FileEntry[], fallback: number): number {\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (!entry || entry.type === \"session\") continue;\n\t\tconst timestamp = Date.parse(entry.timestamp);\n\t\tif (Number.isFinite(timestamp)) return timestamp;\n\t}\n\treturn fallback;\n}\n\nasync function summarizeSession(\n\tfilePath: string,\n\tcurrentSessionFile: string | undefined,\n): Promise<SessionHudEntry | undefined> {\n\tconst [content, fileStat] = await Promise.all([readFile(filePath, \"utf-8\"), stat(filePath)]);\n\tconst entries = parseSessionEntries(content);\n\tconst header = firstHeader(entries, filePath);\n\tif (!header) return undefined;\n\tconst messageCount = entries.filter((entry) => entry.type === \"message\").length;\n\treturn {\n\t\tid: header.id,\n\t\tshortId: header.id.length <= 8 ? header.id : header.id.slice(0, 8),\n\t\tpath: filePath,\n\t\tcwd: header.cwd,\n\t\tcreatedAt: Date.parse(header.timestamp),\n\t\tmodifiedAt: latestMessageTimestamp(entries, fileStat.mtime.getTime()),\n\t\tmessageCount,\n\t\tlastUserText: lastUserText(entries),\n\t\tisCurrent: currentSessionFile === filePath,\n\t};\n}\n\nexport async function scanSessionHudEntries(\n\troot: string,\n\tcurrentSessionFile?: string,\n): Promise<readonly SessionHudEntry[]> {\n\tconst files = await discoverSessionFiles(root);\n\tconst sessions: SessionHudEntry[] = [];\n\tfor (const file of files) {\n\t\ttry {\n\t\t\tconst session = await summarizeSession(file, currentSessionFile);\n\t\t\tif (session) sessions.push(session);\n\t\t} catch (error) {\n\t\t\tif (!hasErrorCode(error, \"ENOENT\")) throw error;\n\t\t}\n\t}\n\tsessions.sort((left, right) => right.modifiedAt - left.modifiedAt);\n\treturn sessions;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
2
+ export declare const INDENT = " ";
3
+ export declare function sanitizeLine(text: string, width: number): string;
4
+ export declare function compactWhitespace(text: string): string;
5
+ export declare function getTextContent(content: string | readonly (TextContent | ImageContent)[]): string;
6
+ export declare function formatSessionDate(date: Date): string;
7
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGvE,eAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,GAAG,MAAM,CAMhG;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAcpD","sourcesContent":["import type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { truncateToWidth } from \"@earendil-works/pi-tui\";\n\nexport const INDENT = \" \";\n\nexport function sanitizeLine(text: string, width: number): string {\n\treturn truncateToWidth(text.replace(/\\t/g, \" \").replace(/\\r/g, \"\"), Math.max(1, width), \"…\");\n}\n\nexport function compactWhitespace(text: string): string {\n\treturn text.replace(/[\\r\\n\\t ]+/g, \" \").trim();\n}\n\nexport function getTextContent(content: string | readonly (TextContent | ImageContent)[]): string {\n\tif (typeof content === \"string\") return content;\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nexport function formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60_000);\n\tconst diffHours = Math.floor(diffMs / 3_600_000);\n\tconst diffDays = Math.floor(diffMs / 86_400_000);\n\n\tif (diffMins < 1) return \"now\";\n\tif (diffMins < 60) return `${diffMins}m`;\n\tif (diffHours < 24) return `${diffHours}h`;\n\tif (diffDays < 7) return `${diffDays}d`;\n\tif (diffDays < 30) return `${Math.floor(diffDays / 7)}w`;\n\tif (diffDays < 365) return `${Math.floor(diffDays / 30)}mo`;\n\treturn `${Math.floor(diffDays / 365)}y`;\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import { truncateToWidth } from "@earendil-works/pi-tui";
2
+ export const INDENT = " ";
3
+ export function sanitizeLine(text, width) {
4
+ return truncateToWidth(text.replace(/\t/g, " ").replace(/\r/g, ""), Math.max(1, width), "…");
5
+ }
6
+ export function compactWhitespace(text) {
7
+ return text.replace(/[\r\n\t ]+/g, " ").trim();
8
+ }
9
+ export function getTextContent(content) {
10
+ if (typeof content === "string")
11
+ return content;
12
+ return content
13
+ .filter((block) => block.type === "text")
14
+ .map((block) => block.text)
15
+ .join("\n");
16
+ }
17
+ export function formatSessionDate(date) {
18
+ const now = new Date();
19
+ const diffMs = now.getTime() - date.getTime();
20
+ const diffMins = Math.floor(diffMs / 60_000);
21
+ const diffHours = Math.floor(diffMs / 3_600_000);
22
+ const diffDays = Math.floor(diffMs / 86_400_000);
23
+ if (diffMins < 1)
24
+ return "now";
25
+ if (diffMins < 60)
26
+ return `${diffMins}m`;
27
+ if (diffHours < 24)
28
+ return `${diffHours}h`;
29
+ if (diffDays < 7)
30
+ return `${diffDays}d`;
31
+ if (diffDays < 30)
32
+ return `${Math.floor(diffDays / 7)}w`;
33
+ if (diffDays < 365)
34
+ return `${Math.floor(diffDays / 30)}mo`;
35
+ return `${Math.floor(diffDays / 365)}y`;
36
+ }
37
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/text.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC;AAE7B,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAa,EAAU;IACjE,OAAO,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,KAAG,CAAC,CAAC;AAAA,CAC/F;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAU;IACvD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAAA,CAC/C;AAED,MAAM,UAAU,cAAc,CAAC,OAAyD,EAAU;IACjG,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,OAAO,OAAO;SACZ,MAAM,CAAC,CAAC,KAAK,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACb;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAU,EAAU;IACrD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAEjD,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,QAAQ,GAAG,EAAE;QAAE,OAAO,GAAG,QAAQ,GAAG,CAAC;IACzC,IAAI,SAAS,GAAG,EAAE;QAAE,OAAO,GAAG,SAAS,GAAG,CAAC;IAC3C,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,GAAG,QAAQ,GAAG,CAAC;IACxC,IAAI,QAAQ,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC;IACzD,IAAI,QAAQ,GAAG,GAAG;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,IAAI,CAAC;IAC5D,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC;AAAA,CACxC","sourcesContent":["import type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { truncateToWidth } from \"@earendil-works/pi-tui\";\n\nexport const INDENT = \" \";\n\nexport function sanitizeLine(text: string, width: number): string {\n\treturn truncateToWidth(text.replace(/\\t/g, \" \").replace(/\\r/g, \"\"), Math.max(1, width), \"…\");\n}\n\nexport function compactWhitespace(text: string): string {\n\treturn text.replace(/[\\r\\n\\t ]+/g, \" \").trim();\n}\n\nexport function getTextContent(content: string | readonly (TextContent | ImageContent)[]): string {\n\tif (typeof content === \"string\") return content;\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nexport function formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60_000);\n\tconst diffHours = Math.floor(diffMs / 3_600_000);\n\tconst diffDays = Math.floor(diffMs / 86_400_000);\n\n\tif (diffMins < 1) return \"now\";\n\tif (diffMins < 60) return `${diffMins}m`;\n\tif (diffHours < 24) return `${diffHours}h`;\n\tif (diffDays < 7) return `${diffDays}d`;\n\tif (diffDays < 30) return `${Math.floor(diffDays / 7)}w`;\n\tif (diffDays < 365) return `${Math.floor(diffDays / 30)}mo`;\n\treturn `${Math.floor(diffDays / 365)}y`;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ToolCall, ToolResultMessage } from "@earendil-works/pi-ai";
2
+ import type { TranscriptRenderOptions } from "./types.ts";
3
+ export declare function renderThinkingEntry(lines: string[], text: string, expanded: boolean, selected: boolean, options: TranscriptRenderOptions): void;
4
+ export declare function renderTextEntry(lines: string[], label: string, text: string, expanded: boolean, selected: boolean, options: TranscriptRenderOptions): void;
5
+ export declare function renderUserEntry(lines: string[], label: string, text: string, expanded: boolean, selected: boolean, options: TranscriptRenderOptions): void;
6
+ export declare function renderToolEntry(lines: string[], call: ToolCall, result: ToolResultMessage<unknown> | undefined, expanded: boolean, selected: boolean, options: TranscriptRenderOptions): void;
7
+ //# sourceMappingURL=transcript-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-entries.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/transcript-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAYzE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAiBN;AAED,wBAAgB,eAAe,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAUN;AAED,wBAAgB,eAAe,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAaN;AA4BD,wBAAgB,eAAe,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,SAAS,EAC9C,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAON","sourcesContent":["import type { ToolCall, ToolResultMessage } from \"@earendil-works/pi-ai\";\nimport { getMarkdownTheme, theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { compactWhitespace, INDENT, sanitizeLine } from \"./text.ts\";\nimport {\n\tcontentWidth,\n\tformatToolArgs,\n\tMAX_EXPANDED_LINES,\n\tMAX_THINKING_COLLAPSED,\n\trenderMarkdown,\n\trenderPreview,\n\ttoolResultText,\n} from \"./transcript-format.ts\";\nimport type { TranscriptRenderOptions } from \"./types.ts\";\n\nexport function renderThinkingEntry(\n\tlines: string[],\n\ttext: string,\n\texpanded: boolean,\n\tselected: boolean,\n\toptions: TranscriptRenderOptions,\n): void {\n\tconst cursor = selected ? theme.fg(\"accent\", \"▶\") : \" \";\n\tlines.push(\"\");\n\tlines.push(\n\t\t`${cursor} ${theme.fg(\"dim\", \"💭 Thinking\")}${!expanded && text.length > MAX_THINKING_COLLAPSED ? theme.fg(\"dim\", \" ↵\") : \"\"}`,\n\t);\n\tconst displayText =\n\t\texpanded || text.length <= MAX_THINKING_COLLAPSED ? text : `${text.slice(0, MAX_THINKING_COLLAPSED)}...`;\n\tif (!expanded) {\n\t\trenderPreview(lines, displayText, options.width, \"thinkingText\");\n\t\treturn;\n\t}\n\tconst rendered = renderMarkdown(displayText, options.width, options.markdownTheme ?? getMarkdownTheme());\n\tfor (const line of rendered.slice(0, MAX_EXPANDED_LINES)) lines.push(line);\n\tif (rendered.length > MAX_EXPANDED_LINES) {\n\t\tlines.push(`${INDENT}${theme.fg(\"dim\", `... ${rendered.length - MAX_EXPANDED_LINES} more lines`)}`);\n\t}\n}\n\nexport function renderTextEntry(\n\tlines: string[],\n\tlabel: string,\n\ttext: string,\n\texpanded: boolean,\n\tselected: boolean,\n\toptions: TranscriptRenderOptions,\n): void {\n\tconst cursor = selected ? theme.fg(\"accent\", \"▶\") : \" \";\n\tlines.push(\"\");\n\tlines.push(`${cursor} ${theme.fg(\"muted\", label)}`);\n\tif (!expanded) {\n\t\trenderPreview(lines, text, options.width, \"dim\");\n\t\treturn;\n\t}\n\tfor (const line of renderMarkdown(text, options.width, options.markdownTheme ?? getMarkdownTheme()))\n\t\tlines.push(line);\n}\n\nexport function renderUserEntry(\n\tlines: string[],\n\tlabel: string,\n\ttext: string,\n\texpanded: boolean,\n\tselected: boolean,\n\toptions: TranscriptRenderOptions,\n): void {\n\tconst cursor = selected ? theme.fg(\"accent\", \"▶\") : \" \";\n\tlines.push(\"\");\n\tif (expanded) {\n\t\tlines.push(`${cursor} ${theme.fg(\"dim\", `[${label}]`)}`);\n\t\tfor (const line of renderMarkdown(text, options.width, options.markdownTheme ?? getMarkdownTheme()))\n\t\t\tlines.push(line);\n\t\treturn;\n\t}\n\tconst normalized = compactWhitespace(text);\n\tlines.push(\n\t\t`${cursor} ${theme.fg(\"dim\", `[${label}]`)} ${theme.fg(\"muted\", sanitizeLine(normalized, contentWidth(options.width)))}`,\n\t);\n}\n\nfunction renderToolResult(\n\tlines: string[],\n\tresult: ToolResultMessage<unknown> | undefined,\n\texpanded: boolean,\n\toptions: TranscriptRenderOptions,\n): void {\n\tif (!result) return;\n\tconst text = toolResultText(result);\n\tif (!text) {\n\t\tlines.push(`${INDENT}${theme.fg(\"success\", \"✓ done\")}`);\n\t\treturn;\n\t}\n\tconst resultLines = text.split(\"\\n\");\n\tconst maxLines = expanded ? 20 : 3;\n\tconst color = result.isError ? \"error\" : \"dim\";\n\tconst marker = result.isError ? \"✗\" : \"✓\";\n\tlines.push(\n\t\t`${INDENT}${theme.fg(result.isError ? \"error\" : \"success\", marker)} ${theme.fg(color, sanitizeLine(resultLines[0] ?? \"\", contentWidth(options.width)))}`,\n\t);\n\tfor (const line of resultLines.slice(1, maxLines)) {\n\t\tlines.push(`${INDENT} ${theme.fg(color, sanitizeLine(line, contentWidth(options.width)))}`);\n\t}\n\tif (resultLines.length > maxLines)\n\t\tlines.push(`${INDENT} ${theme.fg(\"dim\", `... ${resultLines.length - maxLines} more`)}`);\n}\n\nexport function renderToolEntry(\n\tlines: string[],\n\tcall: ToolCall,\n\tresult: ToolResultMessage<unknown> | undefined,\n\texpanded: boolean,\n\tselected: boolean,\n\toptions: TranscriptRenderOptions,\n): void {\n\tconst cursor = selected ? theme.fg(\"accent\", \"▶\") : \" \";\n\tlines.push(\"\");\n\tlines.push(`${cursor} ${theme.fg(\"accent\", \"▸\")} ${theme.bold(theme.fg(\"muted\", call.name))}`);\n\tconst args = formatToolArgs(call);\n\tif (args) lines.push(`${INDENT}${theme.fg(\"dim\", sanitizeLine(args, contentWidth(options.width)))}`);\n\trenderToolResult(lines, result, expanded, options);\n}\n"]}
@@ -0,0 +1,71 @@
1
+ import { getMarkdownTheme, theme } from "../../../../modes/interactive/theme/theme.js";
2
+ import { compactWhitespace, INDENT, sanitizeLine } from "./text.js";
3
+ import { contentWidth, formatToolArgs, MAX_EXPANDED_LINES, MAX_THINKING_COLLAPSED, renderMarkdown, renderPreview, toolResultText, } from "./transcript-format.js";
4
+ export function renderThinkingEntry(lines, text, expanded, selected, options) {
5
+ const cursor = selected ? theme.fg("accent", "▶") : " ";
6
+ lines.push("");
7
+ lines.push(`${cursor} ${theme.fg("dim", "💭 Thinking")}${!expanded && text.length > MAX_THINKING_COLLAPSED ? theme.fg("dim", " ↵") : ""}`);
8
+ const displayText = expanded || text.length <= MAX_THINKING_COLLAPSED ? text : `${text.slice(0, MAX_THINKING_COLLAPSED)}...`;
9
+ if (!expanded) {
10
+ renderPreview(lines, displayText, options.width, "thinkingText");
11
+ return;
12
+ }
13
+ const rendered = renderMarkdown(displayText, options.width, options.markdownTheme ?? getMarkdownTheme());
14
+ for (const line of rendered.slice(0, MAX_EXPANDED_LINES))
15
+ lines.push(line);
16
+ if (rendered.length > MAX_EXPANDED_LINES) {
17
+ lines.push(`${INDENT}${theme.fg("dim", `... ${rendered.length - MAX_EXPANDED_LINES} more lines`)}`);
18
+ }
19
+ }
20
+ export function renderTextEntry(lines, label, text, expanded, selected, options) {
21
+ const cursor = selected ? theme.fg("accent", "▶") : " ";
22
+ lines.push("");
23
+ lines.push(`${cursor} ${theme.fg("muted", label)}`);
24
+ if (!expanded) {
25
+ renderPreview(lines, text, options.width, "dim");
26
+ return;
27
+ }
28
+ for (const line of renderMarkdown(text, options.width, options.markdownTheme ?? getMarkdownTheme()))
29
+ lines.push(line);
30
+ }
31
+ export function renderUserEntry(lines, label, text, expanded, selected, options) {
32
+ const cursor = selected ? theme.fg("accent", "▶") : " ";
33
+ lines.push("");
34
+ if (expanded) {
35
+ lines.push(`${cursor} ${theme.fg("dim", `[${label}]`)}`);
36
+ for (const line of renderMarkdown(text, options.width, options.markdownTheme ?? getMarkdownTheme()))
37
+ lines.push(line);
38
+ return;
39
+ }
40
+ const normalized = compactWhitespace(text);
41
+ lines.push(`${cursor} ${theme.fg("dim", `[${label}]`)} ${theme.fg("muted", sanitizeLine(normalized, contentWidth(options.width)))}`);
42
+ }
43
+ function renderToolResult(lines, result, expanded, options) {
44
+ if (!result)
45
+ return;
46
+ const text = toolResultText(result);
47
+ if (!text) {
48
+ lines.push(`${INDENT}${theme.fg("success", "✓ done")}`);
49
+ return;
50
+ }
51
+ const resultLines = text.split("\n");
52
+ const maxLines = expanded ? 20 : 3;
53
+ const color = result.isError ? "error" : "dim";
54
+ const marker = result.isError ? "✗" : "✓";
55
+ lines.push(`${INDENT}${theme.fg(result.isError ? "error" : "success", marker)} ${theme.fg(color, sanitizeLine(resultLines[0] ?? "", contentWidth(options.width)))}`);
56
+ for (const line of resultLines.slice(1, maxLines)) {
57
+ lines.push(`${INDENT} ${theme.fg(color, sanitizeLine(line, contentWidth(options.width)))}`);
58
+ }
59
+ if (resultLines.length > maxLines)
60
+ lines.push(`${INDENT} ${theme.fg("dim", `... ${resultLines.length - maxLines} more`)}`);
61
+ }
62
+ export function renderToolEntry(lines, call, result, expanded, selected, options) {
63
+ const cursor = selected ? theme.fg("accent", "▶") : " ";
64
+ lines.push("");
65
+ lines.push(`${cursor} ${theme.fg("accent", "▸")} ${theme.bold(theme.fg("muted", call.name))}`);
66
+ const args = formatToolArgs(call);
67
+ if (args)
68
+ lines.push(`${INDENT}${theme.fg("dim", sanitizeLine(args, contentWidth(options.width)))}`);
69
+ renderToolResult(lines, result, expanded, options);
70
+ }
71
+ //# sourceMappingURL=transcript-entries.js.map