@huseynsnmz/pi-suite 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +88 -2
  4. package/SECURITY.md +5 -0
  5. package/package.json +112 -4
  6. package/packages/board/CHANGELOG.md +340 -0
  7. package/packages/board/CONTRIBUTING.md +7 -0
  8. package/packages/board/LICENSE +21 -0
  9. package/packages/board/README.md +88 -0
  10. package/packages/board/SECURITY.md +19 -0
  11. package/packages/board/docs/how-it-works.md +31 -0
  12. package/packages/board/docs/reference.md +278 -0
  13. package/packages/board/package.json +81 -0
  14. package/packages/board/runner/job-runner-legacy.mjs +68 -0
  15. package/packages/board/runner/job-runner.mjs +834 -0
  16. package/packages/board/runner/pty-runner-legacy.mjs +50 -0
  17. package/packages/board/runner/pty-runner.mjs +2175 -0
  18. package/packages/board/runner/state-coordinator.mjs +673 -0
  19. package/packages/board/runner/state-runner.mjs +203 -0
  20. package/packages/board/runner/title-runner.mjs +80 -0
  21. package/packages/board/src/commands/agent-board.ts +394 -0
  22. package/packages/board/src/commands/attach-decision.mjs +66 -0
  23. package/packages/board/src/commands/attach-flow.ts +251 -0
  24. package/packages/board/src/commands/bg.ts +103 -0
  25. package/packages/board/src/commands/board-model.ts +21 -0
  26. package/packages/board/src/core/atomic.mjs +184 -0
  27. package/packages/board/src/core/attach-delete.mjs +24 -0
  28. package/packages/board/src/core/attach-frame-proof.mjs +870 -0
  29. package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
  30. package/packages/board/src/core/attach-frame-stream.mjs +266 -0
  31. package/packages/board/src/core/attach-view-proof.mjs +709 -0
  32. package/packages/board/src/core/auto-state.mjs +377 -0
  33. package/packages/board/src/core/background-runs.mjs +273 -0
  34. package/packages/board/src/core/code-refs-store.mjs +315 -0
  35. package/packages/board/src/core/code-refs.mjs +913 -0
  36. package/packages/board/src/core/config.mjs +77 -0
  37. package/packages/board/src/core/control-protocol.mjs +482 -0
  38. package/packages/board/src/core/coordinator-client.mjs +633 -0
  39. package/packages/board/src/core/coordinator-journal.mjs +282 -0
  40. package/packages/board/src/core/coordinator-protocol.mjs +14 -0
  41. package/packages/board/src/core/cwd-stats.mjs +114 -0
  42. package/packages/board/src/core/dashboard-render.mjs +10 -0
  43. package/packages/board/src/core/derive.mjs +117 -0
  44. package/packages/board/src/core/diagnostics.mjs +133 -0
  45. package/packages/board/src/core/double-press.mjs +26 -0
  46. package/packages/board/src/core/editor-state-reporter.mjs +112 -0
  47. package/packages/board/src/core/events.mjs +286 -0
  48. package/packages/board/src/core/evidence.mjs +245 -0
  49. package/packages/board/src/core/follow-up-queue.mjs +211 -0
  50. package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
  51. package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
  52. package/packages/board/src/core/frame-witness-update.mjs +35 -0
  53. package/packages/board/src/core/heuristics.mjs +313 -0
  54. package/packages/board/src/core/hold-input.mjs +27 -0
  55. package/packages/board/src/core/host-coordination.mjs +300 -0
  56. package/packages/board/src/core/host-crash.mjs +79 -0
  57. package/packages/board/src/core/host-probe.mjs +207 -0
  58. package/packages/board/src/core/host-protocol.mjs +73 -0
  59. package/packages/board/src/core/ids.mjs +35 -0
  60. package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
  61. package/packages/board/src/core/invocation.mjs +43 -0
  62. package/packages/board/src/core/launch-options.mjs +418 -0
  63. package/packages/board/src/core/launch.mjs +157 -0
  64. package/packages/board/src/core/load-config.mjs +4 -0
  65. package/packages/board/src/core/locks.mjs +530 -0
  66. package/packages/board/src/core/paths.mjs +183 -0
  67. package/packages/board/src/core/pid.mjs +75 -0
  68. package/packages/board/src/core/prewarm-schedule.mjs +41 -0
  69. package/packages/board/src/core/process-identity-probe.mjs +228 -0
  70. package/packages/board/src/core/prompt-transport.mjs +13 -0
  71. package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
  72. package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
  73. package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
  74. package/packages/board/src/core/pty-attach-render.mjs +229 -0
  75. package/packages/board/src/core/pty-links.mjs +81 -0
  76. package/packages/board/src/core/pty-scroll.mjs +522 -0
  77. package/packages/board/src/core/pty-support.mjs +327 -0
  78. package/packages/board/src/core/repo.mjs +103 -0
  79. package/packages/board/src/core/rows.mjs +414 -0
  80. package/packages/board/src/core/screen-log-gc.mjs +198 -0
  81. package/packages/board/src/core/screen-log.mjs +160 -0
  82. package/packages/board/src/core/session-view.mjs +174 -0
  83. package/packages/board/src/core/slash-commands.mjs +52 -0
  84. package/packages/board/src/core/state-commands.mjs +757 -0
  85. package/packages/board/src/core/status-consistency.mjs +98 -0
  86. package/packages/board/src/core/steering-prompts.mjs +34 -0
  87. package/packages/board/src/core/steering.mjs +133 -0
  88. package/packages/board/src/core/stop-latch.mjs +41 -0
  89. package/packages/board/src/core/store.mjs +523 -0
  90. package/packages/board/src/core/suspend.mjs +36 -0
  91. package/packages/board/src/core/terminal-attach-client.mjs +981 -0
  92. package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
  93. package/packages/board/src/core/terminal-model.mjs +222 -0
  94. package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
  95. package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
  96. package/packages/board/src/core/terminal-snapshot.mjs +440 -0
  97. package/packages/board/src/core/test-knobs.mjs +28 -0
  98. package/packages/board/src/core/title.mjs +43 -0
  99. package/packages/board/src/core/types.mjs +414 -0
  100. package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
  101. package/packages/board/src/core/worktree.mjs +65 -0
  102. package/packages/board/src/index.ts +303 -0
  103. package/packages/board/src/runtime/service.mjs +3222 -0
  104. package/packages/board/src/ui/chrome.ts +20 -0
  105. package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
  106. package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
  107. package/packages/board/src/ui/dashboard.ts +2237 -0
  108. package/packages/board/src/ui/double-left.ts +30 -0
  109. package/packages/board/src/ui/external-editor.ts +25 -0
  110. package/packages/board/src/ui/focus.ts +32 -0
  111. package/packages/board/src/ui/pty-attach.ts +3184 -0
  112. package/packages/board/src/ui/waiting.ts +30 -0
  113. package/packages/commands/LICENSE +21 -0
  114. package/packages/commands/README.md +19 -0
  115. package/packages/commands/package.json +22 -0
  116. package/packages/commands/src/index.ts +43 -0
  117. package/packages/commands/src/security-review.ts +109 -0
  118. package/packages/frame/CHANGELOG.md +113 -0
  119. package/packages/frame/CONTRIBUTING.md +7 -0
  120. package/packages/frame/LICENSE +22 -0
  121. package/packages/frame/README.md +105 -0
  122. package/packages/frame/SECURITY.md +16 -0
  123. package/packages/frame/docs/how-it-works.md +53 -0
  124. package/packages/frame/docs/reference.md +78 -0
  125. package/packages/frame/package.json +72 -0
  126. package/packages/frame/src/data/git.ts +90 -0
  127. package/packages/frame/src/data/github.ts +140 -0
  128. package/packages/frame/src/data/gitlab.ts +63 -0
  129. package/packages/frame/src/data/mode.ts +49 -0
  130. package/packages/frame/src/data/past-prompts.ts +57 -0
  131. package/packages/frame/src/data/runner.ts +32 -0
  132. package/packages/frame/src/data/snapshot.ts +223 -0
  133. package/packages/frame/src/data/start-notes.ts +104 -0
  134. package/packages/frame/src/data/telemetry.ts +337 -0
  135. package/packages/frame/src/data/todos.ts +162 -0
  136. package/packages/frame/src/data/totals.ts +94 -0
  137. package/packages/frame/src/data/visits.ts +45 -0
  138. package/packages/frame/src/index.ts +696 -0
  139. package/packages/frame/src/kit/LICENSE +21 -0
  140. package/packages/frame/src/kit/app/attribution.ts +30 -0
  141. package/packages/frame/src/kit/app/builder.ts +186 -0
  142. package/packages/frame/src/kit/app/claim.ts +27 -0
  143. package/packages/frame/src/kit/app/feature.ts +91 -0
  144. package/packages/frame/src/kit/app/host.ts +214 -0
  145. package/packages/frame/src/kit/app/renderers.ts +62 -0
  146. package/packages/frame/src/kit/app/scope.ts +125 -0
  147. package/packages/frame/src/kit/board/model.ts +337 -0
  148. package/packages/frame/src/kit/contract.ts +79 -0
  149. package/packages/frame/src/kit/contracts/accounts.ts +33 -0
  150. package/packages/frame/src/kit/contracts/ask.ts +120 -0
  151. package/packages/frame/src/kit/contracts/board.ts +6 -0
  152. package/packages/frame/src/kit/contracts/by.ts +22 -0
  153. package/packages/frame/src/kit/contracts/calls.ts +46 -0
  154. package/packages/frame/src/kit/contracts/events.ts +89 -0
  155. package/packages/frame/src/kit/contracts/mode.ts +26 -0
  156. package/packages/frame/src/kit/contracts/names.ts +12 -0
  157. package/packages/frame/src/kit/contracts/peers.ts +77 -0
  158. package/packages/frame/src/kit/contracts/prompts.ts +8 -0
  159. package/packages/frame/src/kit/contracts/screen.ts +75 -0
  160. package/packages/frame/src/kit/contracts/skills.ts +81 -0
  161. package/packages/frame/src/kit/contracts/sources.ts +81 -0
  162. package/packages/frame/src/kit/contracts/views.ts +42 -0
  163. package/packages/frame/src/kit/control.ts +59 -0
  164. package/packages/frame/src/kit/dark-theme.ts +41 -0
  165. package/packages/frame/src/kit/decode.ts +210 -0
  166. package/packages/frame/src/kit/events/log.ts +313 -0
  167. package/packages/frame/src/kit/format.ts +25 -0
  168. package/packages/frame/src/kit/host.ts +4 -0
  169. package/packages/frame/src/kit/index.ts +25 -0
  170. package/packages/frame/src/kit/legacy-tasks.ts +39 -0
  171. package/packages/frame/src/kit/overlay-images.ts +36 -0
  172. package/packages/frame/src/kit/paths.ts +16 -0
  173. package/packages/frame/src/kit/screen/client.ts +74 -0
  174. package/packages/frame/src/kit/screen/feature.ts +64 -0
  175. package/packages/frame/src/kit/screen/model.ts +182 -0
  176. package/packages/frame/src/kit/screen/view.ts +838 -0
  177. package/packages/frame/src/kit/settings/files.ts +90 -0
  178. package/packages/frame/src/kit/settings/setting.ts +57 -0
  179. package/packages/frame/src/kit/settings/store.ts +192 -0
  180. package/packages/frame/src/kit/sources/file.ts +129 -0
  181. package/packages/frame/src/kit/sources/follow.ts +155 -0
  182. package/packages/frame/src/kit/testing.ts +260 -0
  183. package/packages/frame/src/kit/totals.ts +72 -0
  184. package/packages/frame/src/kit/ui/entry.ts +75 -0
  185. package/packages/frame/src/kit/ui/frame.ts +212 -0
  186. package/packages/frame/src/kit/ui/glyphs.ts +132 -0
  187. package/packages/frame/src/kit/ui/scroll.ts +14 -0
  188. package/packages/frame/src/render/diff.ts +157 -0
  189. package/packages/frame/src/render/fit.ts +86 -0
  190. package/packages/frame/src/render/format.ts +108 -0
  191. package/packages/frame/src/render/frame.ts +106 -0
  192. package/packages/frame/src/render/glyphs.ts +189 -0
  193. package/packages/frame/src/render/keys.ts +43 -0
  194. package/packages/frame/src/render/lead.ts +37 -0
  195. package/packages/frame/src/render/paint.ts +135 -0
  196. package/packages/frame/src/render/safe.ts +19 -0
  197. package/packages/frame/src/render/segments.ts +735 -0
  198. package/packages/frame/src/render/streaming.ts +223 -0
  199. package/packages/frame/src/render/timing.ts +106 -0
  200. package/packages/frame/src/render/tools.ts +167 -0
  201. package/packages/frame/src/settings.ts +105 -0
  202. package/packages/frame/src/ui/editor.ts +272 -0
  203. package/packages/frame/src/ui/effort.ts +36 -0
  204. package/packages/frame/src/ui/footer.ts +165 -0
  205. package/packages/frame/src/ui/header.ts +362 -0
  206. package/packages/frame/src/ui/history-search.ts +23 -0
  207. package/packages/frame/src/ui/interrupt.ts +21 -0
  208. package/packages/frame/src/ui/outputs.ts +170 -0
  209. package/packages/frame/src/ui/pi-entries.ts +298 -0
  210. package/packages/frame/src/ui/previews.ts +72 -0
  211. package/packages/frame/src/ui/queue.ts +75 -0
  212. package/packages/frame/src/ui/screen.ts +59 -0
  213. package/packages/frame/src/ui/timings.ts +48 -0
  214. package/packages/frame/src/ui/todos.ts +205 -0
  215. package/packages/frame/src/ui/tool-frame.ts +595 -0
  216. package/packages/frame/src/ui/working.ts +25 -0
  217. package/packages/frame/src/view/state.ts +97 -0
  218. package/packages/frame/src/view/transcript.ts +175 -0
  219. package/packages/frame/src/view/view.ts +162 -0
  220. package/packages/memory/CONTRIBUTING.md +7 -0
  221. package/packages/memory/LICENSE +21 -0
  222. package/packages/memory/README.md +44 -0
  223. package/packages/memory/SECURITY.md +20 -0
  224. package/packages/memory/package.json +55 -0
  225. package/packages/memory/skills/review-memory/SKILL.md +48 -0
  226. package/packages/memory/src/index.ts +163 -0
  227. package/packages/memory/src/prompt.ts +25 -0
  228. package/packages/memory/src/review.ts +150 -0
  229. package/packages/memory/src/secrets.ts +22 -0
  230. package/packages/memory/src/store.ts +173 -0
  231. package/packages/modes/CHANGELOG.md +104 -0
  232. package/packages/modes/CONTRIBUTING.md +7 -0
  233. package/packages/modes/LICENSE +21 -0
  234. package/packages/modes/README.md +174 -0
  235. package/packages/modes/SECURITY.md +17 -0
  236. package/packages/modes/package.json +55 -0
  237. package/packages/modes/skills/review-permissions/SKILL.md +54 -0
  238. package/packages/modes/src/bash.ts +218 -0
  239. package/packages/modes/src/deny-off.ts +62 -0
  240. package/packages/modes/src/git-safety.ts +72 -0
  241. package/packages/modes/src/index.ts +338 -0
  242. package/packages/modes/src/interview.ts +206 -0
  243. package/packages/modes/src/judge-gate.ts +113 -0
  244. package/packages/modes/src/modes.ts +121 -0
  245. package/packages/modes/src/review/apply.mjs +156 -0
  246. package/packages/modes/src/review/collect.mjs +208 -0
  247. package/packages/modes/src/review/tools.ts +71 -0
  248. package/packages/modes/src/sandbox.ts +37 -0
  249. package/packages/router/CHANGELOG.md +8 -0
  250. package/packages/router/LICENSE +21 -0
  251. package/packages/router/README.md +90 -0
  252. package/packages/router/package.json +51 -0
  253. package/packages/router/src/config.ts +65 -0
  254. package/packages/router/src/index.ts +172 -0
  255. package/packages/router/src/judge.ts +51 -0
  256. package/packages/router/src/route.ts +67 -0
  257. package/packages/web/CHANGELOG.md +36 -0
  258. package/packages/web/LICENSE +21 -0
  259. package/packages/web/README.md +87 -0
  260. package/packages/web/SECURITY.md +13 -0
  261. package/packages/web/package.json +59 -0
  262. package/packages/web/src/config.ts +109 -0
  263. package/packages/web/src/dnsfilter.ts +97 -0
  264. package/packages/web/src/extract.ts +65 -0
  265. package/packages/web/src/index.ts +217 -0
  266. package/packages/web/src/markdown.ts +122 -0
  267. package/packages/web/src/net.ts +109 -0
  268. package/packages/web/src/reader.ts +73 -0
  269. package/packages/web/src/sanitize.ts +50 -0
  270. package/packages/web/src/search.ts +76 -0
  271. package/packages/web/src/secrets.ts +22 -0
  272. package/packages/web/src/validate.ts +186 -0
  273. package/src/index.ts +156 -0
  274. package/src/settings.ts +79 -0
@@ -0,0 +1,160 @@
1
+ import {
2
+ appendFileSync,
3
+ closeSync,
4
+ existsSync,
5
+ fstatSync,
6
+ fsyncSync,
7
+ openSync,
8
+ readSync,
9
+ renameSync,
10
+ statSync,
11
+ unlinkSync,
12
+ writeSync,
13
+ } from "node:fs";
14
+
15
+ export const SCREEN_LOG_REPLAY_BYTES = 100_000;
16
+ export const SCREEN_LOG_MAX_BYTES = 5_000_000;
17
+
18
+ export const defaultScreenLogFs = Object.freeze({
19
+ appendFileSync,
20
+ closeSync,
21
+ existsSync,
22
+ fstatSync,
23
+ fsyncSync,
24
+ openSync,
25
+ readSync,
26
+ renameSync,
27
+ statSync,
28
+ unlinkSync,
29
+ writeSync,
30
+ });
31
+
32
+ let tempSequence = 0;
33
+
34
+ /** @param {string} file @param {number} maxBytes @param {typeof defaultScreenLogFs} fs */
35
+ export function readScreenLogTailBytes(file, maxBytes = SCREEN_LOG_REPLAY_BYTES, fs = defaultScreenLogFs) {
36
+ return readTailResult(file, maxBytes, fs).data;
37
+ }
38
+
39
+ /** @param {string} file @param {number} maxBytes */
40
+ export function readScreenLogTail(file, maxBytes = SCREEN_LOG_REPLAY_BYTES) {
41
+ return readScreenLogTailBytes(file, maxBytes).toString("utf8");
42
+ }
43
+
44
+ /**
45
+ * Compact an existing screen log when it exceeds maxBytes.
46
+ * @param {string} file
47
+ * @param {{ maxBytes?: number, retainBytes?: number, fs?: typeof defaultScreenLogFs }} [opts]
48
+ */
49
+ export function reconcileScreenLog(file, opts = {}) {
50
+ const fs = opts.fs ?? defaultScreenLogFs;
51
+ const { maxBytes, retainBytes } = limits(opts);
52
+ const size = fileSize(file, 0, fs);
53
+ if (size <= maxBytes) return size;
54
+ const tail = readTailResult(file, retainBytes, fs);
55
+ if (!tail.ok || !replaceScreenLog(file, tail.data, fs)) return fileSize(file, size, fs);
56
+ return fileSize(file, tail.data.length, fs);
57
+ }
58
+
59
+ /**
60
+ * Append PTY output while bounding the persisted replay log.
61
+ * @param {string} file
62
+ * @param {string|Buffer} data
63
+ * @param {number} currentBytes
64
+ * @param {{ maxBytes?: number, retainBytes?: number, fs?: typeof defaultScreenLogFs }} [opts]
65
+ */
66
+ export function appendBoundedScreenLog(file, data, currentBytes, opts = {}) {
67
+ const fs = opts.fs ?? defaultScreenLogFs;
68
+ const { maxBytes, retainBytes } = limits(opts);
69
+ const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
70
+ let size = Number.isFinite(currentBytes) && currentBytes >= 0 ? currentBytes : fileSize(file, 0, fs);
71
+
72
+ if (size > maxBytes) {
73
+ size = reconcileScreenLog(file, { maxBytes, retainBytes, fs });
74
+ if (size > maxBytes) return size;
75
+ }
76
+ if (payload.length === 0) return size;
77
+ if (payload.length >= maxBytes) {
78
+ const tail = payload.subarray(Math.max(0, payload.length - retainBytes));
79
+ if (!replaceScreenLog(file, tail, fs)) return fileSize(file, size, fs);
80
+ return fileSize(file, tail.length, fs);
81
+ }
82
+
83
+ try {
84
+ fs.appendFileSync(file, payload);
85
+ } catch {
86
+ return fileSize(file, size, fs);
87
+ }
88
+ size += payload.length;
89
+ if (size <= maxBytes) return size;
90
+ return reconcileScreenLog(file, { maxBytes, retainBytes, fs });
91
+ }
92
+
93
+ /** @param {string} file @param {number} maxBytes @param {typeof defaultScreenLogFs} fs */
94
+ function readTailResult(file, maxBytes, fs) {
95
+ if (!file || maxBytes <= 0 || !fs.existsSync(file)) return { ok: true, data: Buffer.alloc(0) };
96
+ let fd;
97
+ try {
98
+ fd = fs.openSync(file, "r");
99
+ const size = fs.fstatSync(fd).size;
100
+ const length = Math.min(size, Math.floor(maxBytes));
101
+ if (length <= 0) return { ok: true, data: Buffer.alloc(0) };
102
+ const output = Buffer.allocUnsafe(length);
103
+ const start = size - length;
104
+ let offset = 0;
105
+ while (offset < length) {
106
+ const read = fs.readSync(fd, output, offset, length - offset, start + offset);
107
+ if (read === 0) break;
108
+ offset += read;
109
+ }
110
+ return { ok: true, data: offset === length ? output : output.subarray(0, offset) };
111
+ } catch {
112
+ return { ok: false, data: Buffer.alloc(0) };
113
+ } finally {
114
+ if (fd !== undefined) {
115
+ try { fs.closeSync(fd); } catch {}
116
+ }
117
+ }
118
+ }
119
+
120
+ /** @param {{ maxBytes?: number, retainBytes?: number }} opts */
121
+ function limits(opts) {
122
+ const maxBytes = positiveInt(opts.maxBytes, SCREEN_LOG_MAX_BYTES);
123
+ return { maxBytes, retainBytes: Math.min(positiveInt(opts.retainBytes, SCREEN_LOG_REPLAY_BYTES), maxBytes) };
124
+ }
125
+
126
+ /** @param {string} file @param {Buffer} data @param {typeof defaultScreenLogFs} fs */
127
+ function replaceScreenLog(file, data, fs) {
128
+ const temp = `${file}.${process.pid}.${tempSequence++}.tmp`;
129
+ let fd;
130
+ try {
131
+ fd = fs.openSync(temp, "w");
132
+ let offset = 0;
133
+ while (offset < data.length) offset += fs.writeSync(fd, data, offset, data.length - offset, offset);
134
+ fs.fsyncSync(fd);
135
+ fs.closeSync(fd);
136
+ fd = undefined;
137
+ fs.renameSync(temp, file);
138
+ return true;
139
+ } catch {
140
+ if (fd !== undefined) {
141
+ try { fs.closeSync(fd); } catch {}
142
+ }
143
+ try { fs.unlinkSync(temp); } catch {}
144
+ return false;
145
+ }
146
+ }
147
+
148
+ /** @param {string} file @param {number} fallback @param {typeof defaultScreenLogFs} fs */
149
+ function fileSize(file, fallback, fs) {
150
+ try {
151
+ return fs.statSync(file).size;
152
+ } catch {
153
+ return fallback;
154
+ }
155
+ }
156
+
157
+ function positiveInt(value, fallback) {
158
+ const number = Number(value);
159
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
160
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Read and project a managed Pi session file into a simple display model for the
3
+ * dashboard's non-interrupting session view.
4
+ *
5
+ * We show the current active branch only: starting from the last appended entry
6
+ * (the active leaf in normal Pi append semantics), walk parentId links to root,
7
+ * then render messages / visible custom messages / summaries on that branch.
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+
11
+ /**
12
+ * @typedef {Object} SessionViewItem
13
+ * @property {string} id
14
+ * @property {"user"|"assistant"|"custom"|"note"} role
15
+ * @property {string} label
16
+ * @property {string} text
17
+ * @property {string} timestamp
18
+ * @property {string} entryType
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} SessionViewData
23
+ * @property {{ id:string, cwd:string }|null} header
24
+ * @property {SessionViewItem[]} items
25
+ * @property {string|null} error
26
+ */
27
+
28
+ /**
29
+ * Load and parse one session file.
30
+ * @param {string} sessionFile
31
+ * @returns {SessionViewData}
32
+ */
33
+ export function loadSessionView(sessionFile) {
34
+ if (!sessionFile || !existsSync(sessionFile)) {
35
+ return { header: null, items: [], error: "Session file not created yet." };
36
+ }
37
+ try {
38
+ return parseSessionText(readFileSync(sessionFile, "utf8"));
39
+ } catch (err) {
40
+ return {
41
+ header: null,
42
+ items: [],
43
+ error: `Couldn't read session: ${err instanceof Error ? err.message : String(err)}`,
44
+ };
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Parse session JSONL into a displayable active branch.
50
+ * @param {string} text
51
+ * @returns {SessionViewData}
52
+ */
53
+ export function parseSessionText(text) {
54
+ /** @type {{ id:string, cwd:string }|null} */
55
+ let header = null;
56
+ /** @type {any[]} */
57
+ const entries = [];
58
+ /** @type {Map<string, any>} */
59
+ const byId = new Map();
60
+
61
+ for (const rawLine of String(text || "").split(/\r?\n/)) {
62
+ const line = rawLine.trim();
63
+ if (!line) continue;
64
+ let entry;
65
+ try {
66
+ entry = JSON.parse(line);
67
+ } catch {
68
+ continue;
69
+ }
70
+ if (entry?.type === "session") {
71
+ header = { id: String(entry.id || ""), cwd: String(entry.cwd || "") };
72
+ continue;
73
+ }
74
+ if (!entry || typeof entry !== "object" || typeof entry.id !== "string") continue;
75
+ entries.push(entry);
76
+ byId.set(entry.id, entry);
77
+ }
78
+
79
+ if (entries.length === 0) return { header, items: [], error: null };
80
+
81
+ const leaf = entries[entries.length - 1];
82
+ const branch = activeBranch(leaf, byId);
83
+ const items = branch.flatMap(displayItemsForEntry);
84
+ return { header, items, error: null };
85
+ }
86
+
87
+ /**
88
+ * Walk from `leaf` to root following parentId pointers.
89
+ * @param {any} leaf
90
+ * @param {Map<string, any>} byId
91
+ * @returns {any[]}
92
+ */
93
+ function activeBranch(leaf, byId) {
94
+ /** @type {any[]} */
95
+ const out = [];
96
+ const seen = new Set();
97
+ let cur = leaf;
98
+ while (cur && typeof cur.id === "string" && !seen.has(cur.id)) {
99
+ out.push(cur);
100
+ seen.add(cur.id);
101
+ cur = cur.parentId ? byId.get(cur.parentId) ?? null : null;
102
+ }
103
+ out.reverse();
104
+ return out;
105
+ }
106
+
107
+ /**
108
+ * @param {any} entry
109
+ * @returns {SessionViewItem[]}
110
+ */
111
+ function displayItemsForEntry(entry) {
112
+ switch (entry?.type) {
113
+ case "message": {
114
+ const role = entry.message?.role;
115
+ const text = contentText(entry.message?.content);
116
+ if (!text) return [];
117
+ if (role === "user") {
118
+ return [{ id: entry.id, role: "user", label: "you", text, timestamp: String(entry.timestamp || ""), entryType: entry.type }];
119
+ }
120
+ if (role === "assistant") {
121
+ return [{ id: entry.id, role: "assistant", label: "agent", text, timestamp: String(entry.timestamp || ""), entryType: entry.type }];
122
+ }
123
+ return [{ id: entry.id, role: "note", label: String(role || "message"), text, timestamp: String(entry.timestamp || ""), entryType: entry.type }];
124
+ }
125
+ case "custom_message": {
126
+ if (entry.display === false) return [];
127
+ const text = contentText(entry.content);
128
+ if (!text) return [];
129
+ return [{
130
+ id: entry.id,
131
+ role: "custom",
132
+ label: String(entry.customType || "context"),
133
+ text,
134
+ timestamp: String(entry.timestamp || ""),
135
+ entryType: entry.type,
136
+ }];
137
+ }
138
+ case "branch_summary":
139
+ return [{
140
+ id: entry.id,
141
+ role: "note",
142
+ label: "branch summary",
143
+ text: String(entry.summary || "").trim(),
144
+ timestamp: String(entry.timestamp || ""),
145
+ entryType: entry.type,
146
+ }].filter((x) => x.text);
147
+ case "compaction":
148
+ return [{
149
+ id: entry.id,
150
+ role: "note",
151
+ label: "compaction",
152
+ text: String(entry.summary || "").trim(),
153
+ timestamp: String(entry.timestamp || ""),
154
+ entryType: entry.type,
155
+ }].filter((x) => x.text);
156
+ default:
157
+ return [];
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Convert Pi message/content blocks to plain text.
163
+ * @param {any} content
164
+ * @returns {string}
165
+ */
166
+ function contentText(content) {
167
+ if (typeof content === "string") return content.trim();
168
+ if (!Array.isArray(content)) return "";
169
+ return content
170
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
171
+ .map((b) => b.text)
172
+ .join("\n")
173
+ .trim();
174
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pi's own commands. It doesn't export this list, and pi.getCommands() leaves them out.
3
+ * From pi-coding-agent 1.5.0, dist/core/slash-commands.js.
4
+ */
5
+ export const BUILTIN_COMMANDS = [
6
+ ["settings", "Open settings menu"],
7
+ ["model", "Select model (opens selector UI)"],
8
+ ["tree", "Navigate session tree (switch branches)"],
9
+ ["thinking", "Set thinking level"],
10
+ ["scoped-models", "Enable/disable models for Ctrl+P cycling"],
11
+ ["export", "Export session (HTML default, or specify path: .html/.jsonl)"],
12
+ ["import", "Import a JSONL session, preserving its ID and working directory"],
13
+ ["share", "Share session via a Radius artifact or a GitHub gist"],
14
+ ["bug", "Report a bug to the Pi developers"],
15
+ ["copy", "Copy last agent message to clipboard"],
16
+ ["name", "Set session display name"],
17
+ ["session", "Show session info and stats"],
18
+ ["changelog", "Show changelog entries"],
19
+ ["hotkeys", "Show all keyboard shortcuts"],
20
+ ["fork", "Create a new fork from a previous user message"],
21
+ ["clone", "Duplicate the current session at the current position"],
22
+ ["trust", "Save project trust decision for future sessions"],
23
+ ["login", "Configure provider authentication"],
24
+ ["logout", "Remove provider authentication"],
25
+ ["new", "Start a new session"],
26
+ ["compact", "Manually compact the session context"],
27
+ ["resume", "Resume another session"],
28
+ ["reload", "Reload keybindings, extensions, skills, prompts, themes, and context files"],
29
+ ["quit", "Quit Pi"],
30
+ ].map(([name, description]) => ({ name, description, source: "builtin" }));
31
+
32
+ /**
33
+ * What the board offers after `/`: Pi's commands, then what extensions, templates and skills add.
34
+ * @param {{ name: string, description?: string, source: string }[]} added from pi.getCommands()
35
+ */
36
+ export function boardCommands(added) {
37
+ const seen = new Set(BUILTIN_COMMANDS.map((command) => command.name));
38
+ return [...BUILTIN_COMMANDS, ...added.filter((command) => !seen.has(command.name))];
39
+ }
40
+
41
+ /**
42
+ * Commands act on the Pi the board is open in. Templates and skills are prompts, so they
43
+ * start a session like any task, and so does anything that isn't a command.
44
+ * @param {string} text
45
+ * @param {{ name: string, source: string }[]} commands
46
+ */
47
+ export function runsInPi(text, commands) {
48
+ if (!text.startsWith("/")) return false;
49
+ const name = text.slice(1).split(/\s/, 1)[0];
50
+ const command = commands.find((candidate) => candidate.name === name);
51
+ return command !== undefined && command.source !== "prompt" && command.source !== "skill";
52
+ }