@code-yeongyu/senpi 2026.5.23 → 2026.5.29-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 (201) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli/args.d.ts +0 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +15 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +4 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +116 -80
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +18 -24
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +4 -2
  18. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js.map +1 -1
  19. package/dist/core/extensions/builtin/history-search/filter.d.ts +3 -0
  20. package/dist/core/extensions/builtin/history-search/filter.d.ts.map +1 -0
  21. package/dist/core/extensions/builtin/history-search/filter.js +22 -0
  22. package/dist/core/extensions/builtin/history-search/filter.js.map +1 -0
  23. package/dist/core/extensions/builtin/history-search/index.d.ts +7 -0
  24. package/dist/core/extensions/builtin/history-search/index.d.ts.map +1 -0
  25. package/dist/core/extensions/builtin/history-search/index.js +45 -0
  26. package/dist/core/extensions/builtin/history-search/index.js.map +1 -0
  27. package/dist/core/extensions/builtin/history-search/indexer.d.ts +3 -0
  28. package/dist/core/extensions/builtin/history-search/indexer.d.ts.map +1 -0
  29. package/dist/core/extensions/builtin/history-search/indexer.js +161 -0
  30. package/dist/core/extensions/builtin/history-search/indexer.js.map +1 -0
  31. package/dist/core/extensions/builtin/history-search/overlay.d.ts +30 -0
  32. package/dist/core/extensions/builtin/history-search/overlay.d.ts.map +1 -0
  33. package/dist/core/extensions/builtin/history-search/overlay.js +115 -0
  34. package/dist/core/extensions/builtin/history-search/overlay.js.map +1 -0
  35. package/dist/core/extensions/builtin/history-search/types.d.ts +8 -0
  36. package/dist/core/extensions/builtin/history-search/types.d.ts.map +1 -0
  37. package/dist/core/extensions/builtin/history-search/types.js +2 -0
  38. package/dist/core/extensions/builtin/history-search/types.js.map +1 -0
  39. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/index.js +4 -0
  41. package/dist/core/extensions/builtin/index.js.map +1 -1
  42. package/dist/core/extensions/builtin/session-observer/index.d.ts +5 -0
  43. package/dist/core/extensions/builtin/session-observer/index.d.ts.map +1 -0
  44. package/dist/core/extensions/builtin/session-observer/index.js +36 -0
  45. package/dist/core/extensions/builtin/session-observer/index.js.map +1 -0
  46. package/dist/core/extensions/builtin/session-observer/loader.d.ts +3 -0
  47. package/dist/core/extensions/builtin/session-observer/loader.d.ts.map +1 -0
  48. package/dist/core/extensions/builtin/session-observer/loader.js +20 -0
  49. package/dist/core/extensions/builtin/session-observer/loader.js.map +1 -0
  50. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts +7 -0
  51. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/session-observer/overlay-format.js +30 -0
  53. package/dist/core/extensions/builtin/session-observer/overlay-format.js.map +1 -0
  54. package/dist/core/extensions/builtin/session-observer/overlay.d.ts +51 -0
  55. package/dist/core/extensions/builtin/session-observer/overlay.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/session-observer/overlay.js +234 -0
  57. package/dist/core/extensions/builtin/session-observer/overlay.js.map +1 -0
  58. package/dist/core/extensions/builtin/session-observer/scanner.d.ts +10 -0
  59. package/dist/core/extensions/builtin/session-observer/scanner.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/session-observer/scanner.js +142 -0
  61. package/dist/core/extensions/builtin/session-observer/scanner.js.map +1 -0
  62. package/dist/core/extensions/builtin/session-observer/text.d.ts +7 -0
  63. package/dist/core/extensions/builtin/session-observer/text.d.ts.map +1 -0
  64. package/dist/core/extensions/builtin/session-observer/text.js +37 -0
  65. package/dist/core/extensions/builtin/session-observer/text.js.map +1 -0
  66. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts +7 -0
  67. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts.map +1 -0
  68. package/dist/core/extensions/builtin/session-observer/transcript-entries.js +71 -0
  69. package/dist/core/extensions/builtin/session-observer/transcript-entries.js.map +1 -0
  70. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts +11 -0
  71. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts.map +1 -0
  72. package/dist/core/extensions/builtin/session-observer/transcript-format.js +65 -0
  73. package/dist/core/extensions/builtin/session-observer/transcript-format.js.map +1 -0
  74. package/dist/core/extensions/builtin/session-observer/transcript.d.ts +4 -0
  75. package/dist/core/extensions/builtin/session-observer/transcript.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/session-observer/transcript.js +81 -0
  77. package/dist/core/extensions/builtin/session-observer/transcript.js.map +1 -0
  78. package/dist/core/extensions/builtin/session-observer/types.d.ts +33 -0
  79. package/dist/core/extensions/builtin/session-observer/types.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/session-observer/types.js +2 -0
  81. package/dist/core/extensions/builtin/session-observer/types.js.map +1 -0
  82. package/dist/core/extensions/loader.d.ts.map +1 -1
  83. package/dist/core/extensions/loader.js +21 -9
  84. package/dist/core/extensions/loader.js.map +1 -1
  85. package/dist/core/extensions/runner.d.ts.map +1 -1
  86. package/dist/core/extensions/runner.js +1 -0
  87. package/dist/core/extensions/runner.js.map +1 -1
  88. package/dist/core/keybindings.d.ts +10 -0
  89. package/dist/core/keybindings.d.ts.map +1 -1
  90. package/dist/core/keybindings.js +3 -0
  91. package/dist/core/keybindings.js.map +1 -1
  92. package/dist/core/output-guard.d.ts +1 -0
  93. package/dist/core/output-guard.d.ts.map +1 -1
  94. package/dist/core/output-guard.js +52 -22
  95. package/dist/core/output-guard.js.map +1 -1
  96. package/dist/core/package-manager.d.ts.map +1 -1
  97. package/dist/core/package-manager.js +16 -4
  98. package/dist/core/package-manager.js.map +1 -1
  99. package/dist/core/session-work-barrier.d.ts +9 -0
  100. package/dist/core/session-work-barrier.d.ts.map +1 -0
  101. package/dist/core/session-work-barrier.js +50 -0
  102. package/dist/core/session-work-barrier.js.map +1 -0
  103. package/dist/main.d.ts.map +1 -1
  104. package/dist/main.js +0 -15
  105. package/dist/main.js.map +1 -1
  106. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/footer.js +74 -63
  108. package/dist/modes/interactive/components/footer.js.map +1 -1
  109. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/user-message.js +1 -1
  111. package/dist/modes/interactive/components/user-message.js.map +1 -1
  112. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  113. package/dist/modes/interactive/interactive-mode.js +24 -0
  114. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/dist/modes/rpc/rpc-client.d.ts +3 -0
  116. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  117. package/dist/modes/rpc/rpc-client.js +64 -7
  118. package/dist/modes/rpc/rpc-client.js.map +1 -1
  119. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  120. package/dist/modes/rpc/rpc-mode.js +15 -3
  121. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  122. package/dist/utils/paths.d.ts +1 -0
  123. package/dist/utils/paths.d.ts.map +1 -1
  124. package/dist/utils/paths.js +8 -0
  125. package/dist/utils/paths.js.map +1 -1
  126. package/docs/settings.md +3 -1
  127. package/docs/terminal-setup.md +6 -0
  128. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  129. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
  130. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  131. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  132. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +249 -39
  133. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +349 -144
  135. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +54 -12
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
  144. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +46 -29
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  149. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  152. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +2 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  156. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
  157. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  159. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
  160. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +2 -17
  161. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
  162. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  163. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
  164. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  165. package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
  167. package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +7 -1
  169. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  170. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
  171. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  172. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  173. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  174. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  175. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts +3 -0
  176. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts.map +1 -0
  177. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
  178. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js.map +1 -0
  179. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts +3 -0
  180. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts.map +1 -0
  181. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js +38 -0
  182. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js.map +1 -0
  183. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
  184. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
  185. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
  186. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +2 -0
  187. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  188. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +13 -1
  189. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  190. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +5 -1
  191. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  192. package/node_modules/@earendil-works/pi-tui/dist/utils.js +66 -14
  193. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  194. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  195. package/npm-shrinkwrap.json +13 -13
  196. package/package.json +6 -7
  197. package/dist/modes/neo-mode.d.ts +0 -43
  198. package/dist/modes/neo-mode.d.ts.map +0 -1
  199. package/dist/modes/neo-mode.js +0 -142
  200. package/dist/modes/neo-mode.js.map +0 -1
  201. package/dist/neo-tui-bin/senpi-neo-tui-linux-x64 +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD,KAAK,QAAQ,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC,CAAC;AAEzE,wBAAgB,iBAAiB,CAChC,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,GAAE,MAAyB,EAC9C,QAAQ,GAAE,QAAe,GACvB,MAAM,CAQR;AAED,MAAM,CAAC,OAAO,UAAU,sBAAsB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAgCrE","sourcesContent":["import * as path from \"node:path\";\nimport { getSessionsDir } from \"../../../../config.ts\";\nimport type { ExtensionAPI } from \"../../types.ts\";\nimport { indexSessions } from \"./indexer.ts\";\nimport { HistorySearchOverlay } from \"./overlay.ts\";\nimport type { HistoryEntry } from \"./types.ts\";\n\ntype PathLike = Pick<typeof path, \"resolve\" | \"relative\" | \"isAbsolute\">;\n\nexport function resolveSearchRoot(\n\tcurrentSessionDir: string,\n\tdefaultSessionsRoot: string = getSessionsDir(),\n\tpathImpl: PathLike = path,\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\nexport default function historySearchExtension(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"history\", {\n\t\tdescription: \"Search prompt history across sessions\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"No UI available\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet entries: readonly HistoryEntry[];\n\t\t\ttry {\n\t\t\t\tconst searchRoot = resolveSearchRoot(ctx.sessionManager.getSessionDir());\n\t\t\t\tentries = await indexSessions(searchRoot);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`Failed to read prompt history: ${message}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (entries.length === 0) {\n\t\t\t\tctx.ui.notify(\"No prompt history found\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst selected = await ctx.ui.custom<HistoryEntry | undefined>(\n\t\t\t\t(tui, theme, _keybindings, done) => new HistorySearchOverlay({ tui, entries, theme, done }),\n\t\t\t\t{ overlay: true, overlayOptions: { width: \"90%\", maxHeight: \"80%\", minWidth: 60, margin: 2 } },\n\t\t\t);\n\n\t\t\tif (selected) ctx.ui.setEditorText(selected.text);\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import * as path from "node:path";
2
+ import { getSessionsDir } from "../../../../config.js";
3
+ import { indexSessions } from "./indexer.js";
4
+ import { HistorySearchOverlay } from "./overlay.js";
5
+ export function resolveSearchRoot(currentSessionDir, defaultSessionsRoot = getSessionsDir(), pathImpl = path) {
6
+ const defaultRoot = pathImpl.resolve(defaultSessionsRoot);
7
+ if (!currentSessionDir)
8
+ return defaultRoot;
9
+ const current = pathImpl.resolve(currentSessionDir);
10
+ if (current === defaultRoot)
11
+ return defaultRoot;
12
+ const rel = pathImpl.relative(defaultRoot, current);
13
+ if (rel && !rel.startsWith("..") && !pathImpl.isAbsolute(rel))
14
+ return defaultRoot;
15
+ return current;
16
+ }
17
+ export default function historySearchExtension(pi) {
18
+ pi.registerCommand("history", {
19
+ description: "Search prompt history across sessions",
20
+ handler: async (_args, ctx) => {
21
+ if (!ctx.hasUI) {
22
+ ctx.ui.notify("No UI available", "info");
23
+ return;
24
+ }
25
+ let entries;
26
+ try {
27
+ const searchRoot = resolveSearchRoot(ctx.sessionManager.getSessionDir());
28
+ entries = await indexSessions(searchRoot);
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ ctx.ui.notify(`Failed to read prompt history: ${message}`, "error");
33
+ return;
34
+ }
35
+ if (entries.length === 0) {
36
+ ctx.ui.notify("No prompt history found", "info");
37
+ return;
38
+ }
39
+ const selected = await ctx.ui.custom((tui, theme, _keybindings, done) => new HistorySearchOverlay({ tui, entries, theme, done }), { overlay: true, overlayOptions: { width: "90%", maxHeight: "80%", minWidth: 60, margin: 2 } });
40
+ if (selected)
41
+ ctx.ui.setEditorText(selected.text);
42
+ },
43
+ });
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAKpD,MAAM,UAAU,iBAAiB,CAChC,iBAAyB,EACzB,mBAAmB,GAAW,cAAc,EAAE,EAC9C,QAAQ,GAAa,IAAI,EAChB;IACT,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1D,IAAI,CAAC,iBAAiB;QAAE,OAAO,WAAW,CAAC;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,OAAO,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC;IAClF,OAAO,OAAO,CAAC;AAAA,CACf;AAED,MAAM,CAAC,OAAO,UAAU,sBAAsB,CAAC,EAAgB,EAAQ;IACtE,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE;QAC7B,WAAW,EAAE,uCAAuC;QACpD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;gBACzC,OAAO;YACR,CAAC;YAED,IAAI,OAAgC,CAAC;YACrC,IAAI,CAAC;gBACJ,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC;gBACzE,OAAO,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;YAC3C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,kCAAkC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;gBACpE,OAAO;YACR,CAAC;YAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;gBACjD,OAAO;YACR,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CACnC,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,oBAAoB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAC3F,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAC9F,CAAC;YAEF,IAAI,QAAQ;gBAAE,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAAA,CAClD;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import * as path from \"node:path\";\nimport { getSessionsDir } from \"../../../../config.ts\";\nimport type { ExtensionAPI } from \"../../types.ts\";\nimport { indexSessions } from \"./indexer.ts\";\nimport { HistorySearchOverlay } from \"./overlay.ts\";\nimport type { HistoryEntry } from \"./types.ts\";\n\ntype PathLike = Pick<typeof path, \"resolve\" | \"relative\" | \"isAbsolute\">;\n\nexport function resolveSearchRoot(\n\tcurrentSessionDir: string,\n\tdefaultSessionsRoot: string = getSessionsDir(),\n\tpathImpl: PathLike = path,\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\nexport default function historySearchExtension(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"history\", {\n\t\tdescription: \"Search prompt history across sessions\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"No UI available\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet entries: readonly HistoryEntry[];\n\t\t\ttry {\n\t\t\t\tconst searchRoot = resolveSearchRoot(ctx.sessionManager.getSessionDir());\n\t\t\t\tentries = await indexSessions(searchRoot);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`Failed to read prompt history: ${message}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (entries.length === 0) {\n\t\t\t\tctx.ui.notify(\"No prompt history found\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst selected = await ctx.ui.custom<HistoryEntry | undefined>(\n\t\t\t\t(tui, theme, _keybindings, done) => new HistorySearchOverlay({ tui, entries, theme, done }),\n\t\t\t\t{ overlay: true, overlayOptions: { width: \"90%\", maxHeight: \"80%\", minWidth: 60, margin: 2 } },\n\t\t\t);\n\n\t\t\tif (selected) ctx.ui.setEditorText(selected.text);\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { HistoryEntry } from "./types.ts";
2
+ export declare function indexSessions(rootDir: string): Promise<readonly HistoryEntry[]>;
3
+ //# sourceMappingURL=indexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/indexer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA4J/C,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAQrF","sourcesContent":["import type { Stats } from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport type { HistoryEntry } from \"./types.ts\";\n\nconst MAX_HISTORY_ENTRIES = 10_000;\nconst SYSTEM_PREFIXES: readonly string[] = [\"[SYSTEM DIRECTIVE\", \"[system:\", \"[SYSTEM\"];\n\ntype SessionHeader = {\n\treadonly id: string;\n\treadonly cwd: string;\n};\n\nfunction isReadonlyArray(value: unknown): value is readonly unknown[] {\n\treturn Array.isArray(value);\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !isReadonlyArray(value);\n}\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function statIfExists(path: string): Promise<Stats | undefined> {\n\ttry {\n\t\treturn await stat(path);\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\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\nfunction parseJsonLine(line: string): unknown | undefined {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn parsed;\n\t} catch (error) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n}\n\nfunction parseHeader(line: string, sessionFile: string): SessionHeader {\n\tconst parsed = parseJsonLine(line);\n\tif (!isRecord(parsed) || parsed.type !== \"session\") {\n\t\treturn { id: basename(sessionFile, \".jsonl\"), cwd: \"\" };\n\t}\n\n\tconst id = typeof parsed.id === \"string\" ? parsed.id : basename(sessionFile, \".jsonl\");\n\tconst cwd = typeof parsed.cwd === \"string\" ? parsed.cwd : \"\";\n\treturn { id, cwd };\n}\n\nfunction getTextParts(content: readonly unknown[]): readonly string[] {\n\tconst texts: string[] = [];\n\tfor (const part of content) {\n\t\tif (!isRecord(part)) continue;\n\t\tif (part.type !== \"text\") continue;\n\t\tif (typeof part.text === \"string\") texts.push(part.text);\n\t}\n\treturn texts;\n}\n\nfunction isSystemInjectedPrompt(text: string): boolean {\n\tconst trimmedStart = text.trimStart();\n\treturn SYSTEM_PREFIXES.some((prefix) => trimmedStart.startsWith(prefix));\n}\n\nfunction extractUserText(content: unknown): string | undefined {\n\tif (typeof content === \"string\") return content;\n\tif (isReadonlyArray(content)) return getTextParts(content).join(\"\\n\");\n\treturn undefined;\n}\n\nfunction parseMessage(line: string, sessionFile: string, header: SessionHeader): HistoryEntry | undefined {\n\tconst parsed = parseJsonLine(line);\n\tif (!isRecord(parsed) || parsed.type !== \"message\") return undefined;\n\n\tconst message = parsed.message;\n\tif (!isRecord(message) || message.role !== \"user\") return undefined;\n\tconst text = extractUserText(message.content);\n\tif (text === undefined || !text.trim() || isSystemInjectedPrompt(text)) return undefined;\n\n\tconst rawTimestamp = parsed.timestamp;\n\tif (typeof rawTimestamp !== \"string\") return undefined;\n\tconst timestamp = Date.parse(rawTimestamp);\n\tif (!Number.isFinite(timestamp)) return undefined;\n\n\treturn { text, sessionId: header.id, sessionFile, cwd: header.cwd, timestamp };\n}\n\nfunction dedupeNewest(entries: readonly HistoryEntry[]): readonly HistoryEntry[] {\n\tconst newestByText = new Map<string, HistoryEntry>();\n\tfor (const entry of entries) {\n\t\tconst existing = newestByText.get(entry.text);\n\t\tif (!existing || entry.timestamp > existing.timestamp) newestByText.set(entry.text, entry);\n\t}\n\treturn [...newestByText.values()].sort((left, right) => right.timestamp - left.timestamp);\n}\n\nasync function appendSessionEntries(sessionFile: string, entries: HistoryEntry[]): Promise<void> {\n\tconst text = await readFile(sessionFile, \"utf-8\");\n\tconst lines = text.split(\"\\n\").filter((line) => line.length > 0);\n\tconst headerLine = lines[0];\n\tif (!headerLine) return;\n\n\tconst header = parseHeader(headerLine, sessionFile);\n\tfor (let index = lines.length - 1; index >= 1; index--) {\n\t\tconst line = lines[index];\n\t\tif (line === undefined) continue;\n\t\tconst entry = parseMessage(line, sessionFile, header);\n\t\tif (entry) entries.push(entry);\n\t\tif (entries.length >= MAX_HISTORY_ENTRIES) return;\n\t}\n}\n\ntype DiscoveredSessionFile = {\n\treadonly path: string;\n\treadonly basename: string;\n};\n\nasync function collectJsonlFilesInDir(dir: string): Promise<readonly DiscoveredSessionFile[]> {\n\tconst fileNames = await readDirIfExists(dir);\n\tconst files: DiscoveredSessionFile[] = [];\n\tfor (const fileName of fileNames) {\n\t\tif (!fileName.endsWith(\".jsonl\")) continue;\n\t\tconst path = join(dir, fileName);\n\t\tconst fileStat = await statIfExists(path);\n\t\tif (!fileStat?.isFile()) continue;\n\t\tfiles.push({ path, basename: fileName });\n\t}\n\treturn files;\n}\n\nasync function discoverSessionFiles(rootDir: string): Promise<readonly DiscoveredSessionFile[]> {\n\tconst topLevel = await readDirIfExists(rootDir);\n\tconst all: DiscoveredSessionFile[] = [...(await collectJsonlFilesInDir(rootDir))];\n\tfor (const name of topLevel) {\n\t\tif (name.endsWith(\".jsonl\")) continue;\n\t\tconst subDir = join(rootDir, name);\n\t\tconst subStat = await statIfExists(subDir);\n\t\tif (!subStat?.isDirectory()) continue;\n\t\tall.push(...(await collectJsonlFilesInDir(subDir)));\n\t}\n\tall.sort((left, right) => right.basename.localeCompare(left.basename));\n\treturn all;\n}\n\nexport async function indexSessions(rootDir: string): Promise<readonly HistoryEntry[]> {\n\tconst sessionFiles = await discoverSessionFiles(rootDir);\n\tconst entries: HistoryEntry[] = [];\n\tfor (const file of sessionFiles) {\n\t\tawait appendSessionEntries(file.path, entries);\n\t\tif (entries.length >= MAX_HISTORY_ENTRIES) break;\n\t}\n\treturn dedupeNewest(entries);\n}\n"]}
@@ -0,0 +1,161 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ const MAX_HISTORY_ENTRIES = 10_000;
4
+ const SYSTEM_PREFIXES = ["[SYSTEM DIRECTIVE", "[system:", "[SYSTEM"];
5
+ function isReadonlyArray(value) {
6
+ return Array.isArray(value);
7
+ }
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !isReadonlyArray(value);
10
+ }
11
+ function hasErrorCode(error, code) {
12
+ return error instanceof Error && "code" in error && error.code === code;
13
+ }
14
+ async function statIfExists(path) {
15
+ try {
16
+ return await stat(path);
17
+ }
18
+ catch (error) {
19
+ if (hasErrorCode(error, "ENOENT"))
20
+ return undefined;
21
+ throw error;
22
+ }
23
+ }
24
+ async function readDirIfExists(path) {
25
+ try {
26
+ return await readdir(path);
27
+ }
28
+ catch (error) {
29
+ if (hasErrorCode(error, "ENOENT"))
30
+ return [];
31
+ throw error;
32
+ }
33
+ }
34
+ function parseJsonLine(line) {
35
+ try {
36
+ const parsed = JSON.parse(line);
37
+ return parsed;
38
+ }
39
+ catch (error) {
40
+ if (error instanceof SyntaxError)
41
+ return undefined;
42
+ throw error;
43
+ }
44
+ }
45
+ function parseHeader(line, sessionFile) {
46
+ const parsed = parseJsonLine(line);
47
+ if (!isRecord(parsed) || parsed.type !== "session") {
48
+ return { id: basename(sessionFile, ".jsonl"), cwd: "" };
49
+ }
50
+ const id = typeof parsed.id === "string" ? parsed.id : basename(sessionFile, ".jsonl");
51
+ const cwd = typeof parsed.cwd === "string" ? parsed.cwd : "";
52
+ return { id, cwd };
53
+ }
54
+ function getTextParts(content) {
55
+ const texts = [];
56
+ for (const part of content) {
57
+ if (!isRecord(part))
58
+ continue;
59
+ if (part.type !== "text")
60
+ continue;
61
+ if (typeof part.text === "string")
62
+ texts.push(part.text);
63
+ }
64
+ return texts;
65
+ }
66
+ function isSystemInjectedPrompt(text) {
67
+ const trimmedStart = text.trimStart();
68
+ return SYSTEM_PREFIXES.some((prefix) => trimmedStart.startsWith(prefix));
69
+ }
70
+ function extractUserText(content) {
71
+ if (typeof content === "string")
72
+ return content;
73
+ if (isReadonlyArray(content))
74
+ return getTextParts(content).join("\n");
75
+ return undefined;
76
+ }
77
+ function parseMessage(line, sessionFile, header) {
78
+ const parsed = parseJsonLine(line);
79
+ if (!isRecord(parsed) || parsed.type !== "message")
80
+ return undefined;
81
+ const message = parsed.message;
82
+ if (!isRecord(message) || message.role !== "user")
83
+ return undefined;
84
+ const text = extractUserText(message.content);
85
+ if (text === undefined || !text.trim() || isSystemInjectedPrompt(text))
86
+ return undefined;
87
+ const rawTimestamp = parsed.timestamp;
88
+ if (typeof rawTimestamp !== "string")
89
+ return undefined;
90
+ const timestamp = Date.parse(rawTimestamp);
91
+ if (!Number.isFinite(timestamp))
92
+ return undefined;
93
+ return { text, sessionId: header.id, sessionFile, cwd: header.cwd, timestamp };
94
+ }
95
+ function dedupeNewest(entries) {
96
+ const newestByText = new Map();
97
+ for (const entry of entries) {
98
+ const existing = newestByText.get(entry.text);
99
+ if (!existing || entry.timestamp > existing.timestamp)
100
+ newestByText.set(entry.text, entry);
101
+ }
102
+ return [...newestByText.values()].sort((left, right) => right.timestamp - left.timestamp);
103
+ }
104
+ async function appendSessionEntries(sessionFile, entries) {
105
+ const text = await readFile(sessionFile, "utf-8");
106
+ const lines = text.split("\n").filter((line) => line.length > 0);
107
+ const headerLine = lines[0];
108
+ if (!headerLine)
109
+ return;
110
+ const header = parseHeader(headerLine, sessionFile);
111
+ for (let index = lines.length - 1; index >= 1; index--) {
112
+ const line = lines[index];
113
+ if (line === undefined)
114
+ continue;
115
+ const entry = parseMessage(line, sessionFile, header);
116
+ if (entry)
117
+ entries.push(entry);
118
+ if (entries.length >= MAX_HISTORY_ENTRIES)
119
+ return;
120
+ }
121
+ }
122
+ async function collectJsonlFilesInDir(dir) {
123
+ const fileNames = await readDirIfExists(dir);
124
+ const files = [];
125
+ for (const fileName of fileNames) {
126
+ if (!fileName.endsWith(".jsonl"))
127
+ continue;
128
+ const path = join(dir, fileName);
129
+ const fileStat = await statIfExists(path);
130
+ if (!fileStat?.isFile())
131
+ continue;
132
+ files.push({ path, basename: fileName });
133
+ }
134
+ return files;
135
+ }
136
+ async function discoverSessionFiles(rootDir) {
137
+ const topLevel = await readDirIfExists(rootDir);
138
+ const all = [...(await collectJsonlFilesInDir(rootDir))];
139
+ for (const name of topLevel) {
140
+ if (name.endsWith(".jsonl"))
141
+ continue;
142
+ const subDir = join(rootDir, name);
143
+ const subStat = await statIfExists(subDir);
144
+ if (!subStat?.isDirectory())
145
+ continue;
146
+ all.push(...(await collectJsonlFilesInDir(subDir)));
147
+ }
148
+ all.sort((left, right) => right.basename.localeCompare(left.basename));
149
+ return all;
150
+ }
151
+ export async function indexSessions(rootDir) {
152
+ const sessionFiles = await discoverSessionFiles(rootDir);
153
+ const entries = [];
154
+ for (const file of sessionFiles) {
155
+ await appendSessionEntries(file.path, entries);
156
+ if (entries.length >= MAX_HISTORY_ENTRIES)
157
+ break;
158
+ }
159
+ return dedupeNewest(entries);
160
+ }
161
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/indexer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Stats } from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport type { HistoryEntry } from \"./types.ts\";\n\nconst MAX_HISTORY_ENTRIES = 10_000;\nconst SYSTEM_PREFIXES: readonly string[] = [\"[SYSTEM DIRECTIVE\", \"[system:\", \"[SYSTEM\"];\n\ntype SessionHeader = {\n\treadonly id: string;\n\treadonly cwd: string;\n};\n\nfunction isReadonlyArray(value: unknown): value is readonly unknown[] {\n\treturn Array.isArray(value);\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !isReadonlyArray(value);\n}\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function statIfExists(path: string): Promise<Stats | undefined> {\n\ttry {\n\t\treturn await stat(path);\n\t} catch (error) {\n\t\tif (hasErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\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\nfunction parseJsonLine(line: string): unknown | undefined {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn parsed;\n\t} catch (error) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n}\n\nfunction parseHeader(line: string, sessionFile: string): SessionHeader {\n\tconst parsed = parseJsonLine(line);\n\tif (!isRecord(parsed) || parsed.type !== \"session\") {\n\t\treturn { id: basename(sessionFile, \".jsonl\"), cwd: \"\" };\n\t}\n\n\tconst id = typeof parsed.id === \"string\" ? parsed.id : basename(sessionFile, \".jsonl\");\n\tconst cwd = typeof parsed.cwd === \"string\" ? parsed.cwd : \"\";\n\treturn { id, cwd };\n}\n\nfunction getTextParts(content: readonly unknown[]): readonly string[] {\n\tconst texts: string[] = [];\n\tfor (const part of content) {\n\t\tif (!isRecord(part)) continue;\n\t\tif (part.type !== \"text\") continue;\n\t\tif (typeof part.text === \"string\") texts.push(part.text);\n\t}\n\treturn texts;\n}\n\nfunction isSystemInjectedPrompt(text: string): boolean {\n\tconst trimmedStart = text.trimStart();\n\treturn SYSTEM_PREFIXES.some((prefix) => trimmedStart.startsWith(prefix));\n}\n\nfunction extractUserText(content: unknown): string | undefined {\n\tif (typeof content === \"string\") return content;\n\tif (isReadonlyArray(content)) return getTextParts(content).join(\"\\n\");\n\treturn undefined;\n}\n\nfunction parseMessage(line: string, sessionFile: string, header: SessionHeader): HistoryEntry | undefined {\n\tconst parsed = parseJsonLine(line);\n\tif (!isRecord(parsed) || parsed.type !== \"message\") return undefined;\n\n\tconst message = parsed.message;\n\tif (!isRecord(message) || message.role !== \"user\") return undefined;\n\tconst text = extractUserText(message.content);\n\tif (text === undefined || !text.trim() || isSystemInjectedPrompt(text)) return undefined;\n\n\tconst rawTimestamp = parsed.timestamp;\n\tif (typeof rawTimestamp !== \"string\") return undefined;\n\tconst timestamp = Date.parse(rawTimestamp);\n\tif (!Number.isFinite(timestamp)) return undefined;\n\n\treturn { text, sessionId: header.id, sessionFile, cwd: header.cwd, timestamp };\n}\n\nfunction dedupeNewest(entries: readonly HistoryEntry[]): readonly HistoryEntry[] {\n\tconst newestByText = new Map<string, HistoryEntry>();\n\tfor (const entry of entries) {\n\t\tconst existing = newestByText.get(entry.text);\n\t\tif (!existing || entry.timestamp > existing.timestamp) newestByText.set(entry.text, entry);\n\t}\n\treturn [...newestByText.values()].sort((left, right) => right.timestamp - left.timestamp);\n}\n\nasync function appendSessionEntries(sessionFile: string, entries: HistoryEntry[]): Promise<void> {\n\tconst text = await readFile(sessionFile, \"utf-8\");\n\tconst lines = text.split(\"\\n\").filter((line) => line.length > 0);\n\tconst headerLine = lines[0];\n\tif (!headerLine) return;\n\n\tconst header = parseHeader(headerLine, sessionFile);\n\tfor (let index = lines.length - 1; index >= 1; index--) {\n\t\tconst line = lines[index];\n\t\tif (line === undefined) continue;\n\t\tconst entry = parseMessage(line, sessionFile, header);\n\t\tif (entry) entries.push(entry);\n\t\tif (entries.length >= MAX_HISTORY_ENTRIES) return;\n\t}\n}\n\ntype DiscoveredSessionFile = {\n\treadonly path: string;\n\treadonly basename: string;\n};\n\nasync function collectJsonlFilesInDir(dir: string): Promise<readonly DiscoveredSessionFile[]> {\n\tconst fileNames = await readDirIfExists(dir);\n\tconst files: DiscoveredSessionFile[] = [];\n\tfor (const fileName of fileNames) {\n\t\tif (!fileName.endsWith(\".jsonl\")) continue;\n\t\tconst path = join(dir, fileName);\n\t\tconst fileStat = await statIfExists(path);\n\t\tif (!fileStat?.isFile()) continue;\n\t\tfiles.push({ path, basename: fileName });\n\t}\n\treturn files;\n}\n\nasync function discoverSessionFiles(rootDir: string): Promise<readonly DiscoveredSessionFile[]> {\n\tconst topLevel = await readDirIfExists(rootDir);\n\tconst all: DiscoveredSessionFile[] = [...(await collectJsonlFilesInDir(rootDir))];\n\tfor (const name of topLevel) {\n\t\tif (name.endsWith(\".jsonl\")) continue;\n\t\tconst subDir = join(rootDir, name);\n\t\tconst subStat = await statIfExists(subDir);\n\t\tif (!subStat?.isDirectory()) continue;\n\t\tall.push(...(await collectJsonlFilesInDir(subDir)));\n\t}\n\tall.sort((left, right) => right.basename.localeCompare(left.basename));\n\treturn all;\n}\n\nexport async function indexSessions(rootDir: string): Promise<readonly HistoryEntry[]> {\n\tconst sessionFiles = await discoverSessionFiles(rootDir);\n\tconst entries: HistoryEntry[] = [];\n\tfor (const file of sessionFiles) {\n\t\tawait appendSessionEntries(file.path, entries);\n\t\tif (entries.length >= MAX_HISTORY_ENTRIES) break;\n\t}\n\treturn dedupeNewest(entries);\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { Focusable, TUI } from "@earendil-works/pi-tui";
2
+ import { Container } from "@earendil-works/pi-tui";
3
+ import type { Theme } from "../../../../modes/interactive/theme/theme.ts";
4
+ import type { HistoryEntry } from "./types.ts";
5
+ type HistorySearchTui = Pick<TUI, "requestRender">;
6
+ type HistorySearchOverlayOptions = {
7
+ readonly tui: HistorySearchTui;
8
+ readonly entries: readonly HistoryEntry[];
9
+ readonly theme: Theme;
10
+ readonly done: (entry: HistoryEntry | undefined) => void;
11
+ };
12
+ export declare class HistorySearchOverlay extends Container implements Focusable {
13
+ private readonly searchInput;
14
+ private readonly entriesByValue;
15
+ private readonly options;
16
+ private list;
17
+ private filteredEntries;
18
+ private _focused;
19
+ constructor(options: HistorySearchOverlayOptions);
20
+ get focused(): boolean;
21
+ set focused(value: boolean);
22
+ handleInput(input: string): void;
23
+ getSearchValue(): string;
24
+ getFilteredEntries(): readonly HistoryEntry[];
25
+ private rebuild;
26
+ private toSelectItem;
27
+ private renderContainer;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/overlay.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAA4D,MAAM,wBAAwB,CAAC;AAE7G,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C,KAAK,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AAEnD,KAAK,2BAA2B,GAAG;IAClC,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,KAAK,IAAI,CAAC;CACzD,CAAC;AAuBF,qBAAa,oBAAqB,SAAQ,SAAU,YAAW,SAAS;IACvE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,OAAO,EAAE,2BAA2B,EAO/C;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAa/B;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,kBAAkB,IAAI,SAAS,YAAY,EAAE,CAE5C;IAED,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,eAAe;CAavB","sourcesContent":["import { basename } from \"node:path\";\nimport type { Focusable, TUI } from \"@earendil-works/pi-tui\";\nimport { Container, getKeybindings, Input, type SelectItem, SelectList, Text } from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../../../modes/interactive/components/dynamic-border.ts\";\nimport type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { filterHistory } from \"./filter.ts\";\nimport type { HistoryEntry } from \"./types.ts\";\n\nconst MAX_VISIBLE_ROWS = 15;\nconst MAX_RENDERED_MATCHES = 250;\n\ntype SelectListAction = \"tui.select.up\" | \"tui.select.down\" | \"tui.select.confirm\" | \"tui.select.cancel\";\n\nconst SELECT_LIST_ACTIONS: readonly SelectListAction[] = [\n\t\"tui.select.up\",\n\t\"tui.select.down\",\n\t\"tui.select.confirm\",\n\t\"tui.select.cancel\",\n];\n\ntype HistorySearchTui = Pick<TUI, \"requestRender\">;\n\ntype HistorySearchOverlayOptions = {\n\treadonly tui: HistorySearchTui;\n\treadonly entries: readonly HistoryEntry[];\n\treadonly theme: Theme;\n\treadonly done: (entry: HistoryEntry | undefined) => void;\n};\n\nfunction relativeTime(timestamp: number, now = Date.now()): string {\n\tconst seconds = Math.max(0, Math.floor((now - timestamp) / 1_000));\n\tif (seconds < 60) return \"now\";\n\tconst minutes = Math.floor(seconds / 60);\n\tif (minutes < 60) return `${minutes}m ago`;\n\tconst hours = Math.floor(minutes / 60);\n\tif (hours < 24) return `${hours}h ago`;\n\tconst days = Math.floor(hours / 24);\n\tif (days < 30) return `${days}d ago`;\n\tconst months = Math.floor(days / 30);\n\tif (months < 12) return `${months}mo ago`;\n\treturn `${Math.floor(months / 12)}y ago`;\n}\n\nfunction describeEntry(entry: HistoryEntry): string {\n\tconst shortId = entry.sessionId.length <= 8 ? entry.sessionId : entry.sessionId.slice(0, 8);\n\tconst cwdName = basename(entry.cwd);\n\tconst sessionLabel = cwdName ? `${cwdName}/${shortId}` : shortId;\n\treturn `${sessionLabel} · ${relativeTime(entry.timestamp)}`;\n}\n\nexport class HistorySearchOverlay extends Container implements Focusable {\n\tprivate readonly searchInput: Input;\n\tprivate readonly entriesByValue = new Map<string, HistoryEntry>();\n\tprivate readonly options: HistorySearchOverlayOptions;\n\tprivate list: SelectList | undefined;\n\tprivate filteredEntries: readonly HistoryEntry[] = [];\n\tprivate _focused = false;\n\n\tconstructor(options: HistorySearchOverlayOptions) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onEscape = () => this.options.done(undefined);\n\t\tthis.rebuild();\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\tthis.searchInput.focused = value;\n\t}\n\n\thandleInput(input: string): void {\n\t\tconst keybindings = getKeybindings();\n\t\tif (SELECT_LIST_ACTIONS.some((action) => keybindings.matches(input, action))) {\n\t\t\tthis.list?.handleInput(input);\n\t\t\treturn;\n\t\t}\n\n\t\tconst before = this.searchInput.getValue();\n\t\tthis.searchInput.handleInput(input);\n\t\tif (before !== this.searchInput.getValue()) {\n\t\t\tthis.rebuild();\n\t\t\tthis.options.tui.requestRender();\n\t\t}\n\t}\n\n\tgetSearchValue(): string {\n\t\treturn this.searchInput.getValue();\n\t}\n\n\tgetFilteredEntries(): readonly HistoryEntry[] {\n\t\treturn this.filteredEntries;\n\t}\n\n\tprivate rebuild(): void {\n\t\tthis.entriesByValue.clear();\n\t\tthis.filteredEntries = filterHistory(this.options.entries, this.searchInput.getValue());\n\t\tconst renderedEntries = this.filteredEntries.slice(0, MAX_RENDERED_MATCHES);\n\t\tconst items = renderedEntries.map((entry, index) => this.toSelectItem(entry, index));\n\t\tconst list = new SelectList(items, Math.min(MAX_VISIBLE_ROWS, Math.max(1, items.length)), {\n\t\t\tselectedPrefix: (text) => this.options.theme.fg(\"accent\", text),\n\t\t\tselectedText: (text) => text,\n\t\t\tdescription: (text) => this.options.theme.fg(\"muted\", text),\n\t\t\tscrollInfo: (text) => this.options.theme.fg(\"dim\", text),\n\t\t\tnoMatch: (text) => this.options.theme.fg(\"warning\", text.replace(\"commands\", \"prompts\")),\n\t\t});\n\t\tlist.onSelect = (item) => this.options.done(this.entriesByValue.get(item.value));\n\t\tlist.onCancel = () => this.options.done(undefined);\n\t\tthis.list = list;\n\t\tthis.renderContainer(list, this.filteredEntries.length);\n\t}\n\n\tprivate toSelectItem(entry: HistoryEntry, index: number): SelectItem {\n\t\tconst value = String(index);\n\t\tthis.entriesByValue.set(value, entry);\n\t\treturn {\n\t\t\tvalue,\n\t\t\tlabel: entry.text.replace(/[\\r\\n]+/g, \" \").trim(),\n\t\t\tdescription: describeEntry(entry),\n\t\t};\n\t}\n\n\tprivate renderContainer(list: SelectList, matchCount: number): void {\n\t\tconst title = this.options.theme.fg(\"accent\", this.options.theme.bold(\" Search prompt history\"));\n\t\tconst count = this.options.theme.fg(\"dim\", ` ${matchCount}/${this.options.entries.length} prompts`);\n\t\tthis.clear();\n\t\tthis.addChild(new DynamicBorder((text: string) => this.options.theme.fg(\"accent\", text)));\n\t\tthis.addChild(new Text(`${title}${count}`, 0, 0));\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(list);\n\t\tthis.addChild(\n\t\t\tnew Text(this.options.theme.fg(\"dim\", \" Type to filter • ↑↓ navigate • enter select • esc close\"), 0, 0),\n\t\t);\n\t\tthis.addChild(new DynamicBorder((text: string) => this.options.theme.fg(\"accent\", text)));\n\t}\n}\n"]}
@@ -0,0 +1,115 @@
1
+ import { basename } from "node:path";
2
+ import { Container, getKeybindings, Input, SelectList, Text } from "@earendil-works/pi-tui";
3
+ import { DynamicBorder } from "../../../../modes/interactive/components/dynamic-border.js";
4
+ import { filterHistory } from "./filter.js";
5
+ const MAX_VISIBLE_ROWS = 15;
6
+ const MAX_RENDERED_MATCHES = 250;
7
+ const SELECT_LIST_ACTIONS = [
8
+ "tui.select.up",
9
+ "tui.select.down",
10
+ "tui.select.confirm",
11
+ "tui.select.cancel",
12
+ ];
13
+ function relativeTime(timestamp, now = Date.now()) {
14
+ const seconds = Math.max(0, Math.floor((now - timestamp) / 1_000));
15
+ if (seconds < 60)
16
+ return "now";
17
+ const minutes = Math.floor(seconds / 60);
18
+ if (minutes < 60)
19
+ return `${minutes}m ago`;
20
+ const hours = Math.floor(minutes / 60);
21
+ if (hours < 24)
22
+ return `${hours}h ago`;
23
+ const days = Math.floor(hours / 24);
24
+ if (days < 30)
25
+ return `${days}d ago`;
26
+ const months = Math.floor(days / 30);
27
+ if (months < 12)
28
+ return `${months}mo ago`;
29
+ return `${Math.floor(months / 12)}y ago`;
30
+ }
31
+ function describeEntry(entry) {
32
+ const shortId = entry.sessionId.length <= 8 ? entry.sessionId : entry.sessionId.slice(0, 8);
33
+ const cwdName = basename(entry.cwd);
34
+ const sessionLabel = cwdName ? `${cwdName}/${shortId}` : shortId;
35
+ return `${sessionLabel} · ${relativeTime(entry.timestamp)}`;
36
+ }
37
+ export class HistorySearchOverlay extends Container {
38
+ searchInput;
39
+ entriesByValue = new Map();
40
+ options;
41
+ list;
42
+ filteredEntries = [];
43
+ _focused = false;
44
+ constructor(options) {
45
+ super();
46
+ this.options = options;
47
+ this.searchInput = new Input();
48
+ this.searchInput.onEscape = () => this.options.done(undefined);
49
+ this.rebuild();
50
+ }
51
+ get focused() {
52
+ return this._focused;
53
+ }
54
+ set focused(value) {
55
+ this._focused = value;
56
+ this.searchInput.focused = value;
57
+ }
58
+ handleInput(input) {
59
+ const keybindings = getKeybindings();
60
+ if (SELECT_LIST_ACTIONS.some((action) => keybindings.matches(input, action))) {
61
+ this.list?.handleInput(input);
62
+ return;
63
+ }
64
+ const before = this.searchInput.getValue();
65
+ this.searchInput.handleInput(input);
66
+ if (before !== this.searchInput.getValue()) {
67
+ this.rebuild();
68
+ this.options.tui.requestRender();
69
+ }
70
+ }
71
+ getSearchValue() {
72
+ return this.searchInput.getValue();
73
+ }
74
+ getFilteredEntries() {
75
+ return this.filteredEntries;
76
+ }
77
+ rebuild() {
78
+ this.entriesByValue.clear();
79
+ this.filteredEntries = filterHistory(this.options.entries, this.searchInput.getValue());
80
+ const renderedEntries = this.filteredEntries.slice(0, MAX_RENDERED_MATCHES);
81
+ const items = renderedEntries.map((entry, index) => this.toSelectItem(entry, index));
82
+ const list = new SelectList(items, Math.min(MAX_VISIBLE_ROWS, Math.max(1, items.length)), {
83
+ selectedPrefix: (text) => this.options.theme.fg("accent", text),
84
+ selectedText: (text) => text,
85
+ description: (text) => this.options.theme.fg("muted", text),
86
+ scrollInfo: (text) => this.options.theme.fg("dim", text),
87
+ noMatch: (text) => this.options.theme.fg("warning", text.replace("commands", "prompts")),
88
+ });
89
+ list.onSelect = (item) => this.options.done(this.entriesByValue.get(item.value));
90
+ list.onCancel = () => this.options.done(undefined);
91
+ this.list = list;
92
+ this.renderContainer(list, this.filteredEntries.length);
93
+ }
94
+ toSelectItem(entry, index) {
95
+ const value = String(index);
96
+ this.entriesByValue.set(value, entry);
97
+ return {
98
+ value,
99
+ label: entry.text.replace(/[\r\n]+/g, " ").trim(),
100
+ description: describeEntry(entry),
101
+ };
102
+ }
103
+ renderContainer(list, matchCount) {
104
+ const title = this.options.theme.fg("accent", this.options.theme.bold(" Search prompt history"));
105
+ const count = this.options.theme.fg("dim", ` ${matchCount}/${this.options.entries.length} prompts`);
106
+ this.clear();
107
+ this.addChild(new DynamicBorder((text) => this.options.theme.fg("accent", text)));
108
+ this.addChild(new Text(`${title}${count}`, 0, 0));
109
+ this.addChild(this.searchInput);
110
+ this.addChild(list);
111
+ this.addChild(new Text(this.options.theme.fg("dim", " Type to filter • ↑↓ navigate • enter select • esc close"), 0, 0));
112
+ this.addChild(new DynamicBorder((text) => this.options.theme.fg("accent", text)));
113
+ }
114
+ }
115
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/overlay.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename } from \"node:path\";\nimport type { Focusable, TUI } from \"@earendil-works/pi-tui\";\nimport { Container, getKeybindings, Input, type SelectItem, SelectList, Text } from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../../../modes/interactive/components/dynamic-border.ts\";\nimport type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { filterHistory } from \"./filter.ts\";\nimport type { HistoryEntry } from \"./types.ts\";\n\nconst MAX_VISIBLE_ROWS = 15;\nconst MAX_RENDERED_MATCHES = 250;\n\ntype SelectListAction = \"tui.select.up\" | \"tui.select.down\" | \"tui.select.confirm\" | \"tui.select.cancel\";\n\nconst SELECT_LIST_ACTIONS: readonly SelectListAction[] = [\n\t\"tui.select.up\",\n\t\"tui.select.down\",\n\t\"tui.select.confirm\",\n\t\"tui.select.cancel\",\n];\n\ntype HistorySearchTui = Pick<TUI, \"requestRender\">;\n\ntype HistorySearchOverlayOptions = {\n\treadonly tui: HistorySearchTui;\n\treadonly entries: readonly HistoryEntry[];\n\treadonly theme: Theme;\n\treadonly done: (entry: HistoryEntry | undefined) => void;\n};\n\nfunction relativeTime(timestamp: number, now = Date.now()): string {\n\tconst seconds = Math.max(0, Math.floor((now - timestamp) / 1_000));\n\tif (seconds < 60) return \"now\";\n\tconst minutes = Math.floor(seconds / 60);\n\tif (minutes < 60) return `${minutes}m ago`;\n\tconst hours = Math.floor(minutes / 60);\n\tif (hours < 24) return `${hours}h ago`;\n\tconst days = Math.floor(hours / 24);\n\tif (days < 30) return `${days}d ago`;\n\tconst months = Math.floor(days / 30);\n\tif (months < 12) return `${months}mo ago`;\n\treturn `${Math.floor(months / 12)}y ago`;\n}\n\nfunction describeEntry(entry: HistoryEntry): string {\n\tconst shortId = entry.sessionId.length <= 8 ? entry.sessionId : entry.sessionId.slice(0, 8);\n\tconst cwdName = basename(entry.cwd);\n\tconst sessionLabel = cwdName ? `${cwdName}/${shortId}` : shortId;\n\treturn `${sessionLabel} · ${relativeTime(entry.timestamp)}`;\n}\n\nexport class HistorySearchOverlay extends Container implements Focusable {\n\tprivate readonly searchInput: Input;\n\tprivate readonly entriesByValue = new Map<string, HistoryEntry>();\n\tprivate readonly options: HistorySearchOverlayOptions;\n\tprivate list: SelectList | undefined;\n\tprivate filteredEntries: readonly HistoryEntry[] = [];\n\tprivate _focused = false;\n\n\tconstructor(options: HistorySearchOverlayOptions) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onEscape = () => this.options.done(undefined);\n\t\tthis.rebuild();\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\tthis.searchInput.focused = value;\n\t}\n\n\thandleInput(input: string): void {\n\t\tconst keybindings = getKeybindings();\n\t\tif (SELECT_LIST_ACTIONS.some((action) => keybindings.matches(input, action))) {\n\t\t\tthis.list?.handleInput(input);\n\t\t\treturn;\n\t\t}\n\n\t\tconst before = this.searchInput.getValue();\n\t\tthis.searchInput.handleInput(input);\n\t\tif (before !== this.searchInput.getValue()) {\n\t\t\tthis.rebuild();\n\t\t\tthis.options.tui.requestRender();\n\t\t}\n\t}\n\n\tgetSearchValue(): string {\n\t\treturn this.searchInput.getValue();\n\t}\n\n\tgetFilteredEntries(): readonly HistoryEntry[] {\n\t\treturn this.filteredEntries;\n\t}\n\n\tprivate rebuild(): void {\n\t\tthis.entriesByValue.clear();\n\t\tthis.filteredEntries = filterHistory(this.options.entries, this.searchInput.getValue());\n\t\tconst renderedEntries = this.filteredEntries.slice(0, MAX_RENDERED_MATCHES);\n\t\tconst items = renderedEntries.map((entry, index) => this.toSelectItem(entry, index));\n\t\tconst list = new SelectList(items, Math.min(MAX_VISIBLE_ROWS, Math.max(1, items.length)), {\n\t\t\tselectedPrefix: (text) => this.options.theme.fg(\"accent\", text),\n\t\t\tselectedText: (text) => text,\n\t\t\tdescription: (text) => this.options.theme.fg(\"muted\", text),\n\t\t\tscrollInfo: (text) => this.options.theme.fg(\"dim\", text),\n\t\t\tnoMatch: (text) => this.options.theme.fg(\"warning\", text.replace(\"commands\", \"prompts\")),\n\t\t});\n\t\tlist.onSelect = (item) => this.options.done(this.entriesByValue.get(item.value));\n\t\tlist.onCancel = () => this.options.done(undefined);\n\t\tthis.list = list;\n\t\tthis.renderContainer(list, this.filteredEntries.length);\n\t}\n\n\tprivate toSelectItem(entry: HistoryEntry, index: number): SelectItem {\n\t\tconst value = String(index);\n\t\tthis.entriesByValue.set(value, entry);\n\t\treturn {\n\t\t\tvalue,\n\t\t\tlabel: entry.text.replace(/[\\r\\n]+/g, \" \").trim(),\n\t\t\tdescription: describeEntry(entry),\n\t\t};\n\t}\n\n\tprivate renderContainer(list: SelectList, matchCount: number): void {\n\t\tconst title = this.options.theme.fg(\"accent\", this.options.theme.bold(\" Search prompt history\"));\n\t\tconst count = this.options.theme.fg(\"dim\", ` ${matchCount}/${this.options.entries.length} prompts`);\n\t\tthis.clear();\n\t\tthis.addChild(new DynamicBorder((text: string) => this.options.theme.fg(\"accent\", text)));\n\t\tthis.addChild(new Text(`${title}${count}`, 0, 0));\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(list);\n\t\tthis.addChild(\n\t\t\tnew Text(this.options.theme.fg(\"dim\", \" Type to filter • ↑↓ navigate • enter select • esc close\"), 0, 0),\n\t\t);\n\t\tthis.addChild(new DynamicBorder((text: string) => this.options.theme.fg(\"accent\", text)));\n\t}\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export type HistoryEntry = {
2
+ readonly text: string;
3
+ readonly sessionId: string;
4
+ readonly sessionFile: string;
5
+ readonly cwd: string;
6
+ readonly timestamp: number;
7
+ };
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC","sourcesContent":["export type HistoryEntry = {\n\treadonly text: string;\n\treadonly sessionId: string;\n\treadonly sessionFile: string;\n\treadonly cwd: string;\n\treadonly timestamp: number;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/history-search/types.ts"],"names":[],"mappings":"","sourcesContent":["export type HistoryEntry = {\n\treadonly text: string;\n\treadonly sessionId: string;\n\treadonly sessionFile: string;\n\treadonly cwd: string;\n\treadonly timestamp: number;\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKpD,OAAO,aAAa,MAAM,WAAW,CAAC;AACtC,OAAO,cAAc,MAAM,YAAY,CAAC;AAMxC,OAAO,wBAAwB,MAAM,wBAAwB,CAAC;AAK9D,OAAO,YAAY,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,gBAAgB,CAAC;CAC1B;AAED,eAAO,MAAM,yBAAyB,wDAAyD,CAAC;AAEhG,eAAO,MAAM,+BAA+B;;;;;CAKoC,CAAC;AAEjF,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,EActD,CAAC","sourcesContent":["import type { ExtensionFactory } from \"../types.ts\";\nimport anthropicBashExtension from \"./anthropic-bash/index.ts\";\nimport anthropicWebSearchExtension from \"./anthropic-web-search/index.ts\";\nimport bashTimeoutExtension from \"./bash-timeout/index.ts\";\nimport compactionExtension from \"./compaction/index.ts\";\nimport diffExtension from \"./diff.ts\";\nimport filesExtension from \"./files.ts\";\nimport gptApplyPatchExtension from \"./gpt-apply-patch/index.ts\";\nimport kimiWebSearchExtension from \"./kimi-web-search/index.ts\";\nimport openaiWebSearchExtension from \"./openai-web-search/index.ts\";\nimport permissionSystemExtension from \"./permission-system/index.ts\";\nimport promptPresetExtension from \"./prompt-preset/index.ts\";\nimport promptUrlWidgetExtension from \"./prompt-url-widget.ts\";\nimport redrawsExtension from \"./redraws.ts\";\nimport serviceTierExtension from \"./service-tier.ts\";\nimport todowriteExtension from \"./todotools/index.ts\";\nimport toolPairGuardExtension from \"./tool-pair-guard/index.ts\";\nimport tpsExtension from \"./tps.ts\";\n\nexport interface BuiltinExtensionFactory {\n\tid: string;\n\tfactory: ExtensionFactory;\n}\n\nexport const globalDefaultExtensionIds = [\"diff\", \"files\", \"prompt-url-widget\", \"tps\"] as const;\n\nexport const globalDefaultExtensionFactories = {\n\tdiff: diffExtension,\n\tfiles: filesExtension,\n\t\"prompt-url-widget\": promptUrlWidgetExtension,\n\ttps: tpsExtension,\n} satisfies Record<(typeof globalDefaultExtensionIds)[number], ExtensionFactory>;\n\nexport const builtinExtensions: BuiltinExtensionFactory[] = [\n\t{ id: \"permission-system\", factory: permissionSystemExtension },\n\t{ id: \"gpt-apply-patch\", factory: gptApplyPatchExtension },\n\t{ id: \"prompt-preset\", factory: promptPresetExtension },\n\t{ id: \"todowrite\", factory: todowriteExtension },\n\t{ id: \"redraws\", factory: redrawsExtension },\n\t{ id: \"anthropic-web-search\", factory: anthropicWebSearchExtension },\n\t{ id: \"anthropic-bash\", factory: anthropicBashExtension },\n\t{ id: \"openai-web-search\", factory: openaiWebSearchExtension },\n\t{ id: \"service-tier\", factory: serviceTierExtension },\n\t{ id: \"bash-timeout\", factory: bashTimeoutExtension },\n\t{ id: \"tool-pair-guard\", factory: toolPairGuardExtension },\n\t{ id: \"compaction\", factory: compactionExtension },\n\t{ id: \"kimi-web-search\", factory: kimiWebSearchExtension },\n];\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKpD,OAAO,aAAa,MAAM,WAAW,CAAC;AACtC,OAAO,cAAc,MAAM,YAAY,CAAC;AAOxC,OAAO,wBAAwB,MAAM,wBAAwB,CAAC;AAM9D,OAAO,YAAY,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,gBAAgB,CAAC;CAC1B;AAED,eAAO,MAAM,yBAAyB,wDAAyD,CAAC;AAEhG,eAAO,MAAM,+BAA+B;;;;;CAKoC,CAAC;AAEjF,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,EAgBtD,CAAC","sourcesContent":["import type { ExtensionFactory } from \"../types.ts\";\nimport anthropicBashExtension from \"./anthropic-bash/index.ts\";\nimport anthropicWebSearchExtension from \"./anthropic-web-search/index.ts\";\nimport bashTimeoutExtension from \"./bash-timeout/index.ts\";\nimport compactionExtension from \"./compaction/index.ts\";\nimport diffExtension from \"./diff.ts\";\nimport filesExtension from \"./files.ts\";\nimport gptApplyPatchExtension from \"./gpt-apply-patch/index.ts\";\nimport historySearchExtension from \"./history-search/index.ts\";\nimport kimiWebSearchExtension from \"./kimi-web-search/index.ts\";\nimport openaiWebSearchExtension from \"./openai-web-search/index.ts\";\nimport permissionSystemExtension from \"./permission-system/index.ts\";\nimport promptPresetExtension from \"./prompt-preset/index.ts\";\nimport promptUrlWidgetExtension from \"./prompt-url-widget.ts\";\nimport redrawsExtension from \"./redraws.ts\";\nimport serviceTierExtension from \"./service-tier.ts\";\nimport sessionObserverExtension from \"./session-observer/index.ts\";\nimport todowriteExtension from \"./todotools/index.ts\";\nimport toolPairGuardExtension from \"./tool-pair-guard/index.ts\";\nimport tpsExtension from \"./tps.ts\";\n\nexport interface BuiltinExtensionFactory {\n\tid: string;\n\tfactory: ExtensionFactory;\n}\n\nexport const globalDefaultExtensionIds = [\"diff\", \"files\", \"prompt-url-widget\", \"tps\"] as const;\n\nexport const globalDefaultExtensionFactories = {\n\tdiff: diffExtension,\n\tfiles: filesExtension,\n\t\"prompt-url-widget\": promptUrlWidgetExtension,\n\ttps: tpsExtension,\n} satisfies Record<(typeof globalDefaultExtensionIds)[number], ExtensionFactory>;\n\nexport const builtinExtensions: BuiltinExtensionFactory[] = [\n\t{ id: \"permission-system\", factory: permissionSystemExtension },\n\t{ id: \"gpt-apply-patch\", factory: gptApplyPatchExtension },\n\t{ id: \"prompt-preset\", factory: promptPresetExtension },\n\t{ id: \"todowrite\", factory: todowriteExtension },\n\t{ id: \"redraws\", factory: redrawsExtension },\n\t{ id: \"anthropic-web-search\", factory: anthropicWebSearchExtension },\n\t{ id: \"anthropic-bash\", factory: anthropicBashExtension },\n\t{ id: \"openai-web-search\", factory: openaiWebSearchExtension },\n\t{ id: \"service-tier\", factory: serviceTierExtension },\n\t{ id: \"bash-timeout\", factory: bashTimeoutExtension },\n\t{ id: \"tool-pair-guard\", factory: toolPairGuardExtension },\n\t{ id: \"compaction\", factory: compactionExtension },\n\t{ id: \"history-search\", factory: historySearchExtension },\n\t{ id: \"session-observer\", factory: sessionObserverExtension },\n\t{ id: \"kimi-web-search\", factory: kimiWebSearchExtension },\n];\n"]}
@@ -5,6 +5,7 @@ import compactionExtension from "./compaction/index.js";
5
5
  import diffExtension from "./diff.js";
6
6
  import filesExtension from "./files.js";
7
7
  import gptApplyPatchExtension from "./gpt-apply-patch/index.js";
8
+ import historySearchExtension from "./history-search/index.js";
8
9
  import kimiWebSearchExtension from "./kimi-web-search/index.js";
9
10
  import openaiWebSearchExtension from "./openai-web-search/index.js";
10
11
  import permissionSystemExtension from "./permission-system/index.js";
@@ -12,6 +13,7 @@ import promptPresetExtension from "./prompt-preset/index.js";
12
13
  import promptUrlWidgetExtension from "./prompt-url-widget.js";
13
14
  import redrawsExtension from "./redraws.js";
14
15
  import serviceTierExtension from "./service-tier.js";
16
+ import sessionObserverExtension from "./session-observer/index.js";
15
17
  import todowriteExtension from "./todotools/index.js";
16
18
  import toolPairGuardExtension from "./tool-pair-guard/index.js";
17
19
  import tpsExtension from "./tps.js";
@@ -35,6 +37,8 @@ export const builtinExtensions = [
35
37
  { id: "bash-timeout", factory: bashTimeoutExtension },
36
38
  { id: "tool-pair-guard", factory: toolPairGuardExtension },
37
39
  { id: "compaction", factory: compactionExtension },
40
+ { id: "history-search", factory: historySearchExtension },
41
+ { id: "session-observer", factory: sessionObserverExtension },
38
42
  { id: "kimi-web-search", factory: kimiWebSearchExtension },
39
43
  ];
40
44
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/index.ts"],"names":[],"mappings":"AACA,OAAO,sBAAsB,MAAM,2BAA2B,CAAC;AAC/D,OAAO,2BAA2B,MAAM,iCAAiC,CAAC;AAC1E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,aAAa,MAAM,WAAW,CAAC;AACtC,OAAO,cAAc,MAAM,YAAY,CAAC;AACxC,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,wBAAwB,MAAM,8BAA8B,CAAC;AACpE,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAC7D,OAAO,wBAAwB,MAAM,wBAAwB,CAAC;AAC9D,OAAO,gBAAgB,MAAM,cAAc,CAAC;AAC5C,OAAO,oBAAoB,MAAM,mBAAmB,CAAC;AACrD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,YAAY,MAAM,UAAU,CAAC;AAOpC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,CAAU,CAAC;AAEhG,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC9C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,cAAc;IACrB,mBAAmB,EAAE,wBAAwB;IAC7C,GAAG,EAAE,YAAY;CAC8D,CAAC;AAEjF,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC3D,EAAE,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE;IAC/D,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IAC1D,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,qBAAqB,EAAE;IACvD,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE;IAChD,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE;IAC5C,EAAE,EAAE,EAAE,sBAAsB,EAAE,OAAO,EAAE,2BAA2B,EAAE;IACpE,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IACzD,EAAE,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,wBAAwB,EAAE;IAC9D,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACrD,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACrD,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IAC1D,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE;IAClD,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;CAC1D,CAAC","sourcesContent":["import type { ExtensionFactory } from \"../types.ts\";\nimport anthropicBashExtension from \"./anthropic-bash/index.ts\";\nimport anthropicWebSearchExtension from \"./anthropic-web-search/index.ts\";\nimport bashTimeoutExtension from \"./bash-timeout/index.ts\";\nimport compactionExtension from \"./compaction/index.ts\";\nimport diffExtension from \"./diff.ts\";\nimport filesExtension from \"./files.ts\";\nimport gptApplyPatchExtension from \"./gpt-apply-patch/index.ts\";\nimport kimiWebSearchExtension from \"./kimi-web-search/index.ts\";\nimport openaiWebSearchExtension from \"./openai-web-search/index.ts\";\nimport permissionSystemExtension from \"./permission-system/index.ts\";\nimport promptPresetExtension from \"./prompt-preset/index.ts\";\nimport promptUrlWidgetExtension from \"./prompt-url-widget.ts\";\nimport redrawsExtension from \"./redraws.ts\";\nimport serviceTierExtension from \"./service-tier.ts\";\nimport todowriteExtension from \"./todotools/index.ts\";\nimport toolPairGuardExtension from \"./tool-pair-guard/index.ts\";\nimport tpsExtension from \"./tps.ts\";\n\nexport interface BuiltinExtensionFactory {\n\tid: string;\n\tfactory: ExtensionFactory;\n}\n\nexport const globalDefaultExtensionIds = [\"diff\", \"files\", \"prompt-url-widget\", \"tps\"] as const;\n\nexport const globalDefaultExtensionFactories = {\n\tdiff: diffExtension,\n\tfiles: filesExtension,\n\t\"prompt-url-widget\": promptUrlWidgetExtension,\n\ttps: tpsExtension,\n} satisfies Record<(typeof globalDefaultExtensionIds)[number], ExtensionFactory>;\n\nexport const builtinExtensions: BuiltinExtensionFactory[] = [\n\t{ id: \"permission-system\", factory: permissionSystemExtension },\n\t{ id: \"gpt-apply-patch\", factory: gptApplyPatchExtension },\n\t{ id: \"prompt-preset\", factory: promptPresetExtension },\n\t{ id: \"todowrite\", factory: todowriteExtension },\n\t{ id: \"redraws\", factory: redrawsExtension },\n\t{ id: \"anthropic-web-search\", factory: anthropicWebSearchExtension },\n\t{ id: \"anthropic-bash\", factory: anthropicBashExtension },\n\t{ id: \"openai-web-search\", factory: openaiWebSearchExtension },\n\t{ id: \"service-tier\", factory: serviceTierExtension },\n\t{ id: \"bash-timeout\", factory: bashTimeoutExtension },\n\t{ id: \"tool-pair-guard\", factory: toolPairGuardExtension },\n\t{ id: \"compaction\", factory: compactionExtension },\n\t{ id: \"kimi-web-search\", factory: kimiWebSearchExtension },\n];\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/index.ts"],"names":[],"mappings":"AACA,OAAO,sBAAsB,MAAM,2BAA2B,CAAC;AAC/D,OAAO,2BAA2B,MAAM,iCAAiC,CAAC;AAC1E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,aAAa,MAAM,WAAW,CAAC;AACtC,OAAO,cAAc,MAAM,YAAY,CAAC;AACxC,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,sBAAsB,MAAM,2BAA2B,CAAC;AAC/D,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,wBAAwB,MAAM,8BAA8B,CAAC;AACpE,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAC7D,OAAO,wBAAwB,MAAM,wBAAwB,CAAC;AAC9D,OAAO,gBAAgB,MAAM,cAAc,CAAC;AAC5C,OAAO,oBAAoB,MAAM,mBAAmB,CAAC;AACrD,OAAO,wBAAwB,MAAM,6BAA6B,CAAC;AACnE,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,YAAY,MAAM,UAAU,CAAC;AAOpC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,CAAU,CAAC;AAEhG,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC9C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,cAAc;IACrB,mBAAmB,EAAE,wBAAwB;IAC7C,GAAG,EAAE,YAAY;CAC8D,CAAC;AAEjF,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC3D,EAAE,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE;IAC/D,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IAC1D,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,qBAAqB,EAAE;IACvD,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE;IAChD,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE;IAC5C,EAAE,EAAE,EAAE,sBAAsB,EAAE,OAAO,EAAE,2BAA2B,EAAE;IACpE,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IACzD,EAAE,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,wBAAwB,EAAE;IAC9D,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACrD,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACrD,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IAC1D,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE;IAClD,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IACzD,EAAE,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE,wBAAwB,EAAE;IAC7D,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;CAC1D,CAAC","sourcesContent":["import type { ExtensionFactory } from \"../types.ts\";\nimport anthropicBashExtension from \"./anthropic-bash/index.ts\";\nimport anthropicWebSearchExtension from \"./anthropic-web-search/index.ts\";\nimport bashTimeoutExtension from \"./bash-timeout/index.ts\";\nimport compactionExtension from \"./compaction/index.ts\";\nimport diffExtension from \"./diff.ts\";\nimport filesExtension from \"./files.ts\";\nimport gptApplyPatchExtension from \"./gpt-apply-patch/index.ts\";\nimport historySearchExtension from \"./history-search/index.ts\";\nimport kimiWebSearchExtension from \"./kimi-web-search/index.ts\";\nimport openaiWebSearchExtension from \"./openai-web-search/index.ts\";\nimport permissionSystemExtension from \"./permission-system/index.ts\";\nimport promptPresetExtension from \"./prompt-preset/index.ts\";\nimport promptUrlWidgetExtension from \"./prompt-url-widget.ts\";\nimport redrawsExtension from \"./redraws.ts\";\nimport serviceTierExtension from \"./service-tier.ts\";\nimport sessionObserverExtension from \"./session-observer/index.ts\";\nimport todowriteExtension from \"./todotools/index.ts\";\nimport toolPairGuardExtension from \"./tool-pair-guard/index.ts\";\nimport tpsExtension from \"./tps.ts\";\n\nexport interface BuiltinExtensionFactory {\n\tid: string;\n\tfactory: ExtensionFactory;\n}\n\nexport const globalDefaultExtensionIds = [\"diff\", \"files\", \"prompt-url-widget\", \"tps\"] as const;\n\nexport const globalDefaultExtensionFactories = {\n\tdiff: diffExtension,\n\tfiles: filesExtension,\n\t\"prompt-url-widget\": promptUrlWidgetExtension,\n\ttps: tpsExtension,\n} satisfies Record<(typeof globalDefaultExtensionIds)[number], ExtensionFactory>;\n\nexport const builtinExtensions: BuiltinExtensionFactory[] = [\n\t{ id: \"permission-system\", factory: permissionSystemExtension },\n\t{ id: \"gpt-apply-patch\", factory: gptApplyPatchExtension },\n\t{ id: \"prompt-preset\", factory: promptPresetExtension },\n\t{ id: \"todowrite\", factory: todowriteExtension },\n\t{ id: \"redraws\", factory: redrawsExtension },\n\t{ id: \"anthropic-web-search\", factory: anthropicWebSearchExtension },\n\t{ id: \"anthropic-bash\", factory: anthropicBashExtension },\n\t{ id: \"openai-web-search\", factory: openaiWebSearchExtension },\n\t{ id: \"service-tier\", factory: serviceTierExtension },\n\t{ id: \"bash-timeout\", factory: bashTimeoutExtension },\n\t{ id: \"tool-pair-guard\", factory: toolPairGuardExtension },\n\t{ id: \"compaction\", factory: compactionExtension },\n\t{ id: \"history-search\", factory: historySearchExtension },\n\t{ id: \"session-observer\", factory: sessionObserverExtension },\n\t{ id: \"kimi-web-search\", factory: kimiWebSearchExtension },\n];\n"]}
@@ -0,0 +1,5 @@
1
+ import type { ExtensionAPI } from "../../types.ts";
2
+ export { resolveSessionHudRoot, scanSessionHudEntries } from "./scanner.ts";
3
+ export { renderTranscript } from "./transcript.ts";
4
+ export default function sessionHudExtension(pi: ExtensionAPI): void;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmClE","sourcesContent":["import { getSessionsDir } from \"../../../../config.ts\";\nimport type { ExtensionAPI } from \"../../types.ts\";\nimport { SessionHudOverlay } from \"./overlay.ts\";\nimport { resolveSessionHudRoot, scanSessionHudEntries } from \"./scanner.ts\";\nimport type { SessionHudEntry } from \"./types.ts\";\n\nexport { resolveSessionHudRoot, scanSessionHudEntries } from \"./scanner.ts\";\nexport { renderTranscript } from \"./transcript.ts\";\n\nexport default function sessionHudExtension(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"sessions\", {\n\t\tdescription: \"Peek at previous session transcripts in a HUD\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"No UI available\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet sessions: readonly SessionHudEntry[];\n\t\t\ttry {\n\t\t\t\tconst root = resolveSessionHudRoot(ctx.sessionManager.getSessionDir(), getSessionsDir());\n\t\t\t\tsessions = await scanSessionHudEntries(root, ctx.sessionManager.getSessionFile());\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`Failed to read sessions: ${message}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (sessions.length === 0) {\n\t\t\t\tctx.ui.notify(\"No sessions found\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait ctx.ui.custom<void>(\n\t\t\t\t(tui, _theme, _keybindings, done) =>\n\t\t\t\t\tnew SessionHudOverlay({\n\t\t\t\t\t\tsessions,\n\t\t\t\t\t\tdone,\n\t\t\t\t\t\trequestRender: () => tui.requestRender(),\n\t\t\t\t\t}),\n\t\t\t\t{ overlay: true, overlayOptions: { width: \"94%\", maxHeight: \"90%\", minWidth: 72, margin: 1 } },\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { getSessionsDir } from "../../../../config.js";
2
+ import { SessionHudOverlay } from "./overlay.js";
3
+ import { resolveSessionHudRoot, scanSessionHudEntries } from "./scanner.js";
4
+ export { resolveSessionHudRoot, scanSessionHudEntries } from "./scanner.js";
5
+ export { renderTranscript } from "./transcript.js";
6
+ export default function sessionHudExtension(pi) {
7
+ pi.registerCommand("sessions", {
8
+ description: "Peek at previous session transcripts in a HUD",
9
+ handler: async (_args, ctx) => {
10
+ if (!ctx.hasUI) {
11
+ ctx.ui.notify("No UI available", "info");
12
+ return;
13
+ }
14
+ let sessions;
15
+ try {
16
+ const root = resolveSessionHudRoot(ctx.sessionManager.getSessionDir(), getSessionsDir());
17
+ sessions = await scanSessionHudEntries(root, ctx.sessionManager.getSessionFile());
18
+ }
19
+ catch (error) {
20
+ const message = error instanceof Error ? error.message : String(error);
21
+ ctx.ui.notify(`Failed to read sessions: ${message}`, "error");
22
+ return;
23
+ }
24
+ if (sessions.length === 0) {
25
+ ctx.ui.notify("No sessions found", "info");
26
+ return;
27
+ }
28
+ await ctx.ui.custom((tui, _theme, _keybindings, done) => new SessionHudOverlay({
29
+ sessions,
30
+ done,
31
+ requestRender: () => tui.requestRender(),
32
+ }), { overlay: true, overlayOptions: { width: "94%", maxHeight: "90%", minWidth: 72, margin: 1 } });
33
+ },
34
+ });
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/session-observer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAgB,EAAQ;IACnE,EAAE,CAAC,eAAe,CAAC,UAAU,EAAE;QAC9B,WAAW,EAAE,+CAA+C;QAC5D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;gBACzC,OAAO;YACR,CAAC;YAED,IAAI,QAAoC,CAAC;YACzC,IAAI,CAAC;gBACJ,MAAM,IAAI,GAAG,qBAAqB,CAAC,GAAG,CAAC,cAAc,CAAC,aAAa,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;gBACzF,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC,CAAC;YACnF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,4BAA4B,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC9D,OAAO;YACR,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;gBAC3C,OAAO;YACR,CAAC;YAED,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAClB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,CACnC,IAAI,iBAAiB,CAAC;gBACrB,QAAQ;gBACR,IAAI;gBACJ,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,aAAa,EAAE;aACxC,CAAC,EACH,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAC9F,CAAC;QAAA,CACF;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { getSessionsDir } from \"../../../../config.ts\";\nimport type { ExtensionAPI } from \"../../types.ts\";\nimport { SessionHudOverlay } from \"./overlay.ts\";\nimport { resolveSessionHudRoot, scanSessionHudEntries } from \"./scanner.ts\";\nimport type { SessionHudEntry } from \"./types.ts\";\n\nexport { resolveSessionHudRoot, scanSessionHudEntries } from \"./scanner.ts\";\nexport { renderTranscript } from \"./transcript.ts\";\n\nexport default function sessionHudExtension(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"sessions\", {\n\t\tdescription: \"Peek at previous session transcripts in a HUD\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"No UI available\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet sessions: readonly SessionHudEntry[];\n\t\t\ttry {\n\t\t\t\tconst root = resolveSessionHudRoot(ctx.sessionManager.getSessionDir(), getSessionsDir());\n\t\t\t\tsessions = await scanSessionHudEntries(root, ctx.sessionManager.getSessionFile());\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`Failed to read sessions: ${message}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (sessions.length === 0) {\n\t\t\t\tctx.ui.notify(\"No sessions found\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait ctx.ui.custom<void>(\n\t\t\t\t(tui, _theme, _keybindings, done) =>\n\t\t\t\t\tnew SessionHudOverlay({\n\t\t\t\t\t\tsessions,\n\t\t\t\t\t\tdone,\n\t\t\t\t\t\trequestRender: () => tui.requestRender(),\n\t\t\t\t\t}),\n\t\t\t\t{ overlay: true, overlayOptions: { width: \"94%\", maxHeight: \"90%\", minWidth: 72, margin: 1 } },\n\t\t\t);\n\t\t},\n\t});\n}\n"]}