@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,303 @@
1
+ /**
2
+ * Pi Agent Board — extension entry point.
3
+ *
4
+ * Registers the `/agent-board` dashboard command, resolves how to
5
+ * launch background workers and the detached runner, and keeps a small footer status with
6
+ * the count of sessions needing attention.
7
+ */
8
+ import { chmodSync, mkdirSync } from "node:fs";
9
+ import "./core/load-config.mjs";
10
+ import { fileURLToPath } from "node:url";
11
+ import { createConnection } from "node:net";
12
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
13
+ import { resolvePiInvocation } from "./core/invocation.mjs";
14
+ import { defaultRoot, resolveControlEndpoint } from "./core/paths.mjs";
15
+ import { listRows } from "./core/store.mjs";
16
+ import { createService, envInt } from "./runtime/service.mjs";
17
+ import { attachWarmHostSweeper } from "./core/warm-host-sweeper.mjs";
18
+ import { openDashboard, registerAgentBoardCommand } from "./commands/agent-board.js";
19
+ import { registerBgCommand } from "./commands/bg.js";
20
+ import { registerBoardModelCommand } from "./commands/board-model.ts";
21
+ import { announceBoardClosedOn } from "./ui/chrome.ts";
22
+ import { watchDoubleLeft } from "./ui/double-left.ts";
23
+ import { watchWaiting } from "./ui/waiting.ts";
24
+
25
+ const RUNNER_SCRIPT = fileURLToPath(new URL("../runner/job-runner.mjs", import.meta.url));
26
+ const PTY_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/pty-runner.mjs", import.meta.url));
27
+ const TITLE_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/title-runner.mjs", import.meta.url));
28
+ const AUTO_STATE_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/state-runner.mjs", import.meta.url));
29
+
30
+ let hostedEditorReporter: { start(): void; stop(): void } | null = null;
31
+ /**
32
+ * Issue #222 (23b1, FW-03/FW-04): hosted-child frame-witness reporter. It is
33
+ * capability-gated by the runner (`frame_witness_enable`) and only exists for hosted
34
+ * children with a view id and a TUI; it publishes NO attach readiness or proof — the
35
+ * runner only consumes the markers for its own bookkeeping until 23b2.
36
+ */
37
+ let hostedFrameWitness: { stop(): void } | null = null;
38
+ // F4: truthful session epoch source. A hosted runner child currently hosts exactly ONE
39
+ // interactive session, so the first session_start is epoch 1; any additional actual
40
+ // session_start increments it (and the runner-side intent lifecycle invalidates the old
41
+ // candidates). No other topology is used here.
42
+ let hostedSessionEpoch = 0;
43
+
44
+ /** Transcripts and screen logs live under here, and can hold whatever a session printed. */
45
+ function keepRootPrivate(root: string): void {
46
+ try {
47
+ mkdirSync(root, { recursive: true, mode: 0o700 });
48
+ chmodSync(root, 0o700);
49
+ } catch {
50
+ /* The board still works, as it did before it had this. */
51
+ }
52
+ }
53
+
54
+ export default function piAgentBoard(pi: ExtensionAPI): void {
55
+ announceBoardClosedOn(pi.events);
56
+ let stopDoubleLeft: (() => void) | null = null;
57
+ let stopWaiting: (() => void) | null = null;
58
+ const root = defaultRoot();
59
+ keepRootPrivate(root);
60
+ const { piCommand, piArgsPrefix } = resolvePiInvocation();
61
+
62
+ const isHostedChild = process.env.AGENT_BOARD_CHILD === "1" || process.env.AGENT_VIEW_CHILD === "1";
63
+ const hostedViewId = process.env.AGENT_BOARD_VIEW_ID ?? process.env.AGENT_VIEW_VIEW_ID;
64
+
65
+ const commandOpts = {
66
+ root,
67
+ runnerScript: RUNNER_SCRIPT,
68
+ titleRunnerScript: TITLE_RUNNER_SCRIPT,
69
+ autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT,
70
+ ptyRunnerScript: PTY_RUNNER_SCRIPT,
71
+ piCommand,
72
+ piArgsPrefix,
73
+ getThinkingLevel: () => pi.getThinkingLevel(),
74
+ };
75
+
76
+ // Warm-host reclaim (issue #75): idle PTY hosts must not leak forever after
77
+ // detach. Sweep once now (reclaims hosts orphaned by a previous host pi that
78
+ // exited without cleanup), then periodically; sweep again on shutdown.
79
+ // Child pi processes skip entirely (they share the board root and would
80
+ // terminate their own runner). serviceForContext is defined before the attach
81
+ // call because the sweeper sweeps synchronously on attach and the sweep
82
+ // closure references it (no TDZ).
83
+ const serviceForContext = () =>
84
+ createService({ root, runnerScript: RUNNER_SCRIPT, ptyRunnerScript: PTY_RUNNER_SCRIPT, titleRunnerScript: TITLE_RUNNER_SCRIPT, autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT, piCommand, piArgsPrefix, defaultCwd: process.cwd() });
85
+ let sweepService: ReturnType<typeof createService> | null = null;
86
+ attachWarmHostSweeper(pi, {
87
+ isHostedChild,
88
+ sweep: () => {
89
+ try {
90
+ // Reuse one service instance per extension lifetime: createService
91
+ // schedules a screen-log GC on construction, so per-tick creation
92
+ // would turn an event-driven cost into a timed one.
93
+ sweepService ??= serviceForContext();
94
+ sweepService.pruneWarmHosts();
95
+ } catch {
96
+ /* best-effort: never break the session over a sweep */
97
+ }
98
+ },
99
+ intervalMs: envInt("AGENT_BOARD_SWEEP_INTERVAL_MS", 60_000, 0, 24 * 60 * 60 * 1000, "AGENT_VIEW_SWEEP_INTERVAL_MS"),
100
+ });
101
+
102
+ registerAgentBoardCommand(pi, commandOpts);
103
+ registerBgCommand(pi, commandOpts);
104
+ registerBoardModelCommand(pi);
105
+ pi.registerFlag("agent-board", {
106
+ description: "Open the agent-board dashboard on startup",
107
+ type: "boolean",
108
+ default: false,
109
+ });
110
+
111
+ // Footer status: reconcile stale rows and surface how many need attention.
112
+ // Live model list for the stale-defaultModel launch guard (issue #90); the
113
+ // service calls it per validation, and undefined conservatively skips.
114
+ const availableModelsFor = (ctx: ExtensionContext) => () => {
115
+ try {
116
+ return ctx.modelRegistry.getAvailable();
117
+ } catch {
118
+ return undefined;
119
+ }
120
+ };
121
+ const serviceFor = (ctx: ExtensionContext) =>
122
+ createService({ root, runnerScript: RUNNER_SCRIPT, ptyRunnerScript: PTY_RUNNER_SCRIPT, titleRunnerScript: TITLE_RUNNER_SCRIPT, autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT, piCommand, piArgsPrefix, defaultCwd: ctx.cwd, availableModels: availableModelsFor(ctx) });
123
+
124
+ const updateStatus = (ctx: ExtensionContext) => {
125
+ try {
126
+ void serviceFor(ctx).reconcile().catch(() => {});
127
+ const rows = listRows(root);
128
+ const needs = rows.filter((r) => r.state?.semanticState === "needs_input").length;
129
+ const working = rows.filter((r) => r.alive).length;
130
+ const queued = rows.reduce((sum, r) => sum + (r.state?.followUps?.queuedCount ?? 0), 0);
131
+ if (isHostedChild) return;
132
+ if (rows.length === 0) {
133
+ ctx.ui.setStatus("agent-board", undefined);
134
+ return;
135
+ }
136
+ const parts: string[] = [];
137
+ if (working > 0) parts.push(ctx.ui.theme.fg("accent", `●${working}`));
138
+ if (needs > 0) parts.push(ctx.ui.theme.fg("warning", `◆${needs}`));
139
+ if (queued > 0) parts.push(ctx.ui.theme.fg("muted", `q${queued}`));
140
+ ctx.ui.setStatus("agent-board", parts.length ? `${ctx.ui.theme.fg("muted", "agents")} ${parts.join(" ")}` : undefined);
141
+ } catch {
142
+ /* never break the session over a status update */
143
+ }
144
+ };
145
+
146
+ async function startHostedFrameWitness(ctx: ExtensionContext): Promise<void> {
147
+ if (hostedFrameWitness !== null || !hostedViewId || !ctx.hasUI || !ctx.ui) return;
148
+ const { AttachFrameReporter } = await import("./core/attach-frame-reporter.mjs");
149
+ const socket = createConnection(
150
+ resolveControlEndpoint({
151
+ envSocketPath: process.env.AGENT_BOARD_CONTROL_SOCKET,
152
+ platform: process.platform as "win32" | "linux" | "darwin",
153
+ root,
154
+ viewId: hostedViewId,
155
+ }),
156
+ );
157
+ const reporter = new AttachFrameReporter({
158
+ ui: ctx.ui as never,
159
+ mode: "fullscreen", // F6: the sentinel factory rebinds from the ACTUAL public tui.mode
160
+ sessionEpoch: hostedSessionEpoch,
161
+ send: (message: unknown) => {
162
+ try {
163
+ socket.write(`${JSON.stringify(message)}\n`);
164
+ } catch {
165
+ /* socket errors surface through the error handler */
166
+ }
167
+ },
168
+ });
169
+ let installed = false;
170
+ let buffer = "";
171
+ socket.on("connect", () => reporter.announce());
172
+ socket.on("data", (chunk: Buffer) => {
173
+ buffer += chunk.toString("utf8");
174
+ let newline = buffer.indexOf("\n");
175
+ while (newline >= 0) {
176
+ const line = buffer.slice(0, newline);
177
+ buffer = buffer.slice(newline + 1);
178
+ try {
179
+ const message = JSON.parse(line) as { type?: string };
180
+ if (message.type === "frame_witness_enable") {
181
+ const negotiation = reporter.handleMessage(message);
182
+ if (negotiation.status === "enabled" && !installed) installed = reporter.install();
183
+ } else if (message.type === "frame_witness_intent") {
184
+ reporter.applyIntent(message);
185
+ }
186
+ } catch {
187
+ /* malformed runner message: ignore, the capability stays as it is */
188
+ }
189
+ newline = buffer.indexOf("\n");
190
+ }
191
+ });
192
+ let cleaned = false;
193
+ const cleanup = (reason: string) => {
194
+ if (cleaned) return;
195
+ cleaned = true;
196
+ try {
197
+ reporter.dispose(reason);
198
+ } catch {
199
+ /* never break the session over the frame witness */
200
+ }
201
+ try {
202
+ socket.destroy();
203
+ } catch {
204
+ /* already closed */
205
+ }
206
+ hostedFrameWitness = null;
207
+ };
208
+ socket.on("error", () => cleanup("socket-error"));
209
+ socket.on("close", () => cleanup("socket-close"));
210
+ hostedFrameWitness = { stop: () => cleanup("session-shutdown") };
211
+ }
212
+
213
+ // After this the context they hold is stale, and using one takes Pi down.
214
+ pi.on("session_shutdown", () => {
215
+ stopDoubleLeft?.();
216
+ stopDoubleLeft = null;
217
+ stopWaiting?.();
218
+ stopWaiting = null;
219
+ });
220
+
221
+ pi.on("session_start", async (event, ctx) => {
222
+ if (!isHostedChild && ctx.mode === "tui") {
223
+ stopDoubleLeft?.();
224
+ stopDoubleLeft = watchDoubleLeft(ctx);
225
+ }
226
+ if (ctx.mode === "tui") {
227
+ stopWaiting?.();
228
+ stopWaiting = watchWaiting(ctx, (waiting) => syncForeground({ type: "agent_board_waiting", waiting }, ctx));
229
+ }
230
+ if (isHostedChild && !hostedEditorReporter && typeof ctx.ui?.getEditorText === "function" && hostedViewId) {
231
+ const { createEditorStateReporter } = await import("./core/editor-state-reporter.mjs");
232
+ hostedEditorReporter = createEditorStateReporter({
233
+ getEditorText: () => ctx.ui.getEditorText(),
234
+ connect: () => createConnection(resolveControlEndpoint({
235
+ // The runner injects the endpoint it actually bound (issue #103);
236
+ // falling back to the stable per-view address keeps older runners
237
+ // and legacy hosts working.
238
+ envSocketPath: process.env.AGENT_BOARD_CONTROL_SOCKET,
239
+ platform: process.platform as "win32" | "linux" | "darwin",
240
+ root,
241
+ viewId: hostedViewId,
242
+ })),
243
+ });
244
+ hostedEditorReporter.start();
245
+ }
246
+ if (isHostedChild) {
247
+ hostedSessionEpoch += 1;
248
+ try {
249
+ // P2: an actual additional session_start invalidates the old witness (its
250
+ // epoch is stale) and starts a new one with the new epoch.
251
+ if (hostedFrameWitness !== null) {
252
+ hostedFrameWitness.stop();
253
+ hostedFrameWitness = null;
254
+ }
255
+ await startHostedFrameWitness(ctx);
256
+ } catch {
257
+ /* the frame witness must never break a hosted session */
258
+ }
259
+ }
260
+ updateStatus(ctx);
261
+ if (event.reason === "startup" && !isHostedChild && pi.getFlag("agent-board") === true && ctx.hasUI) {
262
+ const service = createService({ root, runnerScript: RUNNER_SCRIPT, ptyRunnerScript: PTY_RUNNER_SCRIPT, titleRunnerScript: TITLE_RUNNER_SCRIPT, autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT, piCommand, piArgsPrefix, defaultCwd: ctx.cwd, availableModels: availableModelsFor(ctx) });
263
+ void service.reconcile().catch(() => {});
264
+ ctx.ui.setWorkingVisible(false);
265
+ ctx.ui.setHeader(() => ({ render: () => [], invalidate() {} }));
266
+ ctx.ui.setFooter(() => ({ render: () => [], invalidate() {} }));
267
+ ctx.ui.setTitle("agent board");
268
+ const result = await openDashboard(ctx, service, { currentThinkingLevel: pi.getThinkingLevel() });
269
+ if (result.action === "attach") {
270
+ ctx.ui.notify("Attach requires the /agent-board command path; launch from a normal Pi session for now.", "warning");
271
+ } else {
272
+ ctx.shutdown();
273
+ }
274
+ }
275
+ });
276
+ const syncForeground = (event: unknown, ctx: ExtensionContext) => {
277
+ try {
278
+ const service = serviceFor(ctx);
279
+ if (hostedViewId) service.syncHostedEvent(hostedViewId, event);
280
+ else service.syncForegroundEvent(ctx.sessionManager.getSessionFile(), event);
281
+ updateStatus(ctx);
282
+ } catch {
283
+ /* never break the session over dashboard mirroring */
284
+ }
285
+ };
286
+
287
+ pi.on("session_shutdown", () => {
288
+ try {
289
+ hostedFrameWitness?.stop();
290
+ } catch {
291
+ /* best effort */
292
+ }
293
+ hostedFrameWitness = null;
294
+ });
295
+ pi.on("input", async (event, ctx) => syncForeground(event, ctx));
296
+ pi.on("before_agent_start", async (event, ctx) => syncForeground(event, ctx));
297
+ pi.on("agent_start", async (event, ctx) => syncForeground(event, ctx));
298
+ pi.on("tool_execution_start", async (event, ctx) => syncForeground(event, ctx));
299
+ pi.on("tool_execution_end", async (event, ctx) => syncForeground(event, ctx));
300
+ pi.on("message_start", async (event, ctx) => syncForeground(event, ctx));
301
+ pi.on("message_end", async (event, ctx) => syncForeground(event, ctx));
302
+ pi.on("agent_end", async (event, ctx) => syncForeground(event, ctx));
303
+ }