@fitchmultz/pi-subagents 0.0.0-stage → 0.44.5

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 (347) hide show
  1. package/CHANGELOG.md +1654 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1533 -2
  4. package/agents/context-builder.md +97 -0
  5. package/agents/debugger.md +51 -0
  6. package/agents/delegate.md +13 -0
  7. package/agents/fixer.md +37 -0
  8. package/agents/oracle.md +71 -0
  9. package/agents/planner.md +72 -0
  10. package/agents/researcher.md +51 -0
  11. package/agents/reviewer-claude.md +69 -0
  12. package/agents/reviewer-gpt.md +68 -0
  13. package/agents/reviewer-ponytail.md +82 -0
  14. package/agents/reviewer-security.md +81 -0
  15. package/agents/reviewer.md +69 -0
  16. package/agents/scout.md +66 -0
  17. package/agents/ui-designer.md +51 -0
  18. package/agents/watcher.md +26 -0
  19. package/agents/worker.md +46 -0
  20. package/agents/writer.md +36 -0
  21. package/dist/agents/agent-management.js +923 -0
  22. package/dist/agents/agent-scope.js +7 -0
  23. package/dist/agents/agent-selection.js +20 -0
  24. package/dist/agents/agent-serializer.js +100 -0
  25. package/dist/agents/agents.js +752 -0
  26. package/dist/agents/chain-serializer.js +367 -0
  27. package/dist/agents/frontmatter.js +21 -0
  28. package/dist/agents/identity.js +29 -0
  29. package/dist/agents/skills.js +570 -0
  30. package/dist/extension/build-info.js +1 -0
  31. package/dist/extension/compact-tools.js +112 -0
  32. package/dist/extension/config.js +22 -0
  33. package/dist/extension/control-notices.js +29 -0
  34. package/dist/extension/doctor.js +143 -0
  35. package/dist/extension/fanout-child.js +326 -0
  36. package/dist/extension/index.js +577 -0
  37. package/dist/extension/schemas.js +416 -0
  38. package/dist/extension/tool-input.js +20 -0
  39. package/dist/extension/tool-result.js +14 -0
  40. package/dist/history/canonical-result.js +81 -0
  41. package/dist/history/database.js +72 -0
  42. package/dist/history/index.js +199 -0
  43. package/dist/history/ingest.js +468 -0
  44. package/dist/history/queries.js +350 -0
  45. package/dist/history/store.js +216 -0
  46. package/dist/history/types.js +4 -0
  47. package/dist/history/worker.js +466 -0
  48. package/dist/intercom/intercom-bridge.js +112 -0
  49. package/dist/intercom/live-intercom.js +94 -0
  50. package/dist/intercom/result-intercom.js +373 -0
  51. package/dist/pi-intercom/agent-dir.js +10 -0
  52. package/dist/pi-intercom/bridge.js +750 -0
  53. package/dist/pi-intercom/broker/broker.js +426 -0
  54. package/dist/pi-intercom/broker/client.js +492 -0
  55. package/dist/pi-intercom/broker/framing.js +65 -0
  56. package/dist/pi-intercom/broker/paths.js +42 -0
  57. package/dist/pi-intercom/broker/pid.js +66 -0
  58. package/dist/pi-intercom/broker/spawn.js +221 -0
  59. package/dist/pi-intercom/config.js +102 -0
  60. package/dist/pi-intercom/index.js +2942 -0
  61. package/dist/pi-intercom/reply-tracker.js +182 -0
  62. package/dist/pi-intercom/session-targets.js +128 -0
  63. package/dist/pi-intercom/subagent-live-events.js +132 -0
  64. package/dist/pi-intercom/topics.js +157 -0
  65. package/dist/pi-intercom/types.js +163 -0
  66. package/dist/pi-intercom/ui/compose.js +269 -0
  67. package/dist/pi-intercom/ui/inline-message.js +80 -0
  68. package/dist/pi-intercom/ui/session-list.js +124 -0
  69. package/dist/runs/background/async-control.js +95 -0
  70. package/dist/runs/background/async-execution.js +676 -0
  71. package/dist/runs/background/async-job-tracker.js +383 -0
  72. package/dist/runs/background/async-result-file.js +58 -0
  73. package/dist/runs/background/async-resume.js +305 -0
  74. package/dist/runs/background/async-status.js +442 -0
  75. package/dist/runs/background/completion-dedupe.js +69 -0
  76. package/dist/runs/background/completion-delivery.js +245 -0
  77. package/dist/runs/background/notify.js +71 -0
  78. package/dist/runs/background/parallel-groups.js +55 -0
  79. package/dist/runs/background/result-watcher.js +426 -0
  80. package/dist/runs/background/run-id-resolver.js +64 -0
  81. package/dist/runs/background/run-status.js +382 -0
  82. package/dist/runs/background/stale-run-reconciler.js +335 -0
  83. package/dist/runs/background/subagent-runner-launcher.js +55 -0
  84. package/dist/runs/background/subagent-runner.js +1872 -0
  85. package/dist/runs/background/top-level-async.js +5 -0
  86. package/dist/runs/foreground/chain-clarify.js +1152 -0
  87. package/dist/runs/foreground/clarify-invocation.js +72 -0
  88. package/dist/runs/foreground/execution-input.js +423 -0
  89. package/dist/runs/foreground/foreground-control.js +817 -0
  90. package/dist/runs/foreground/question-control.js +88 -0
  91. package/dist/runs/foreground/run-async-path.js +205 -0
  92. package/dist/runs/foreground/subagent-executor.js +442 -0
  93. package/dist/runs/foreground/subagent-params.js +165 -0
  94. package/dist/runs/foreground/wait-run.js +141 -0
  95. package/dist/runs/shared/acceptance-contract.js +253 -0
  96. package/dist/runs/shared/acceptance-evaluation.js +256 -0
  97. package/dist/runs/shared/acceptance-finalization.js +280 -0
  98. package/dist/runs/shared/acceptance-reports.js +147 -0
  99. package/dist/runs/shared/acceptance.js +4 -0
  100. package/dist/runs/shared/chain-outputs.js +110 -0
  101. package/dist/runs/shared/child-attempt.js +693 -0
  102. package/dist/runs/shared/child-execution-cwd.js +96 -0
  103. package/dist/runs/shared/child-observations.js +114 -0
  104. package/dist/runs/shared/claude-code.js +199 -0
  105. package/dist/runs/shared/completion-guard.js +31 -0
  106. package/dist/runs/shared/dynamic-fanout.js +274 -0
  107. package/dist/runs/shared/history-index.js +72 -0
  108. package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
  109. package/dist/runs/shared/model-fallback.js +185 -0
  110. package/dist/runs/shared/mutating-tool-guard.js +170 -0
  111. package/dist/runs/shared/native-finalization.js +101 -0
  112. package/dist/runs/shared/native-usage.js +153 -0
  113. package/dist/runs/shared/nested-events.js +700 -0
  114. package/dist/runs/shared/nested-path.js +48 -0
  115. package/dist/runs/shared/nested-render.js +108 -0
  116. package/dist/runs/shared/parallel-utils.js +61 -0
  117. package/dist/runs/shared/parent-receipts.js +68 -0
  118. package/dist/runs/shared/parent-usage.js +121 -0
  119. package/dist/runs/shared/pi-args.js +310 -0
  120. package/dist/runs/shared/pi-spawn.js +52 -0
  121. package/dist/runs/shared/run-history-queries.js +45 -0
  122. package/dist/runs/shared/run-history.js +110 -0
  123. package/dist/runs/shared/run-id-ambiguity.js +4 -0
  124. package/dist/runs/shared/run-records.js +679 -0
  125. package/dist/runs/shared/session-cwd-preload.js +33 -0
  126. package/dist/runs/shared/single-output.js +222 -0
  127. package/dist/runs/shared/streaming-text.js +15 -0
  128. package/dist/runs/shared/structured-output.js +55 -0
  129. package/dist/runs/shared/subagent-control.js +179 -0
  130. package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
  131. package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
  132. package/dist/runs/shared/supervisor-questions.js +350 -0
  133. package/dist/runs/shared/workflow-graph.js +214 -0
  134. package/dist/runs/shared/workflow-policy.js +84 -0
  135. package/dist/runs/shared/worktree.js +525 -0
  136. package/dist/shared/agent-context-policy.js +126 -0
  137. package/dist/shared/artifacts.js +96 -0
  138. package/dist/shared/atomic-json.js +13 -0
  139. package/dist/shared/cooperative.js +20 -0
  140. package/dist/shared/execution-cwd.js +14 -0
  141. package/dist/shared/file-coalescer.js +21 -0
  142. package/dist/shared/fork-context.js +49 -0
  143. package/dist/shared/formatters.js +85 -0
  144. package/dist/shared/journal-reader.js +407 -0
  145. package/dist/shared/lazy-tools.js +32 -0
  146. package/dist/shared/model-info.js +68 -0
  147. package/dist/shared/native-session.js +15 -0
  148. package/dist/shared/native-tui.js +18 -0
  149. package/dist/shared/native-typebox.js +20 -0
  150. package/dist/shared/post-exit-stdio-guard.js +197 -0
  151. package/dist/shared/prompt-sections.js +8 -0
  152. package/dist/shared/session-entries.js +52 -0
  153. package/dist/shared/session-identity.js +14 -0
  154. package/dist/shared/settings.js +254 -0
  155. package/dist/shared/status-format.js +111 -0
  156. package/dist/shared/temp-root.js +114 -0
  157. package/dist/shared/types.js +207 -0
  158. package/dist/shared/ui-mode.js +3 -0
  159. package/dist/shared/utils.js +334 -0
  160. package/dist/slash/prompt-template-bridge.js +298 -0
  161. package/dist/slash/slash-bridge.js +116 -0
  162. package/dist/slash/slash-commands.js +607 -0
  163. package/dist/slash/slash-live-state.js +246 -0
  164. package/dist/tui/action-hints.js +75 -0
  165. package/dist/tui/agent-history.js +197 -0
  166. package/dist/tui/agent-view.js +1656 -0
  167. package/dist/tui/render.js +1515 -0
  168. package/docs/intercom-bridge.md +212 -0
  169. package/docs/intercom.md +581 -0
  170. package/docs/performance.md +121 -0
  171. package/package.json +102 -4
  172. package/prompts/gather-context-and-clarify.md +14 -0
  173. package/prompts/parallel-cleanup.md +62 -0
  174. package/prompts/parallel-context-build.md +56 -0
  175. package/prompts/parallel-handoff-plan.md +62 -0
  176. package/prompts/parallel-research.md +51 -0
  177. package/prompts/parallel-review.md +59 -0
  178. package/prompts/review-loop.md +42 -0
  179. package/scripts/build.mjs +134 -0
  180. package/scripts/check-compat.mjs +48 -0
  181. package/scripts/compat-host.mjs +19 -0
  182. package/scripts/intercom-bridge-credentials.mjs +143 -0
  183. package/scripts/intercom-remote.mjs +103 -0
  184. package/scripts/linux-smoke.sh +51 -0
  185. package/scripts/local-install-smoke.mjs +66 -0
  186. package/scripts/native-package-smoke.mjs +82 -0
  187. package/scripts/package-smoke.mjs +209 -0
  188. package/scripts/prepare.mjs +67 -0
  189. package/scripts/real-pi-smoke.mjs +415 -0
  190. package/scripts/run-tests.mjs +149 -0
  191. package/skills/pi-intercom/SKILL.md +113 -0
  192. package/skills/pi-intercom/evals/evals.json +29 -0
  193. package/skills/pi-intercom/evals/trigger-evals.json +47 -0
  194. package/skills/pi-intercom/references/peer-sessions.md +69 -0
  195. package/skills/pi-subagents/SKILL.md +76 -0
  196. package/skills/pi-subagents/evals/evals.json +29 -0
  197. package/skills/pi-subagents/evals/trigger-evals.json +42 -0
  198. package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
  199. package/src/agents/agent-management.ts +813 -0
  200. package/src/agents/agent-scope.ts +7 -0
  201. package/src/agents/agent-selection.ts +23 -0
  202. package/src/agents/agent-serializer.ts +99 -0
  203. package/src/agents/agents.ts +916 -0
  204. package/src/agents/chain-serializer.ts +329 -0
  205. package/src/agents/frontmatter.ts +21 -0
  206. package/src/agents/identity.ts +30 -0
  207. package/src/agents/skills.ts +637 -0
  208. package/src/extension/build-info.ts +2 -0
  209. package/src/extension/compact-tools.ts +114 -0
  210. package/src/extension/config.ts +23 -0
  211. package/src/extension/control-notices.ts +47 -0
  212. package/src/extension/doctor.ts +171 -0
  213. package/src/extension/fanout-child.ts +312 -0
  214. package/src/extension/index.ts +649 -0
  215. package/src/extension/schemas.ts +444 -0
  216. package/src/extension/tool-input.ts +19 -0
  217. package/src/extension/tool-result.ts +16 -0
  218. package/src/history/canonical-result.ts +62 -0
  219. package/src/history/database.ts +59 -0
  220. package/src/history/index.ts +156 -0
  221. package/src/history/ingest.ts +349 -0
  222. package/src/history/queries.ts +241 -0
  223. package/src/history/store.ts +203 -0
  224. package/src/history/types.ts +166 -0
  225. package/src/history/worker.ts +321 -0
  226. package/src/intercom/intercom-bridge.ts +123 -0
  227. package/src/intercom/live-intercom.ts +104 -0
  228. package/src/intercom/result-intercom.ts +429 -0
  229. package/src/pi-intercom/agent-dir.ts +9 -0
  230. package/src/pi-intercom/bridge.ts +668 -0
  231. package/src/pi-intercom/broker/broker.ts +463 -0
  232. package/src/pi-intercom/broker/client.ts +558 -0
  233. package/src/pi-intercom/broker/framing.ts +77 -0
  234. package/src/pi-intercom/broker/paths.ts +47 -0
  235. package/src/pi-intercom/broker/pid.ts +59 -0
  236. package/src/pi-intercom/broker/spawn.ts +248 -0
  237. package/src/pi-intercom/config.ts +142 -0
  238. package/src/pi-intercom/index.ts +2929 -0
  239. package/src/pi-intercom/reply-tracker.ts +211 -0
  240. package/src/pi-intercom/session-targets.ts +172 -0
  241. package/src/pi-intercom/subagent-live-events.ts +133 -0
  242. package/src/pi-intercom/topics.ts +125 -0
  243. package/src/pi-intercom/types.ts +271 -0
  244. package/src/pi-intercom/ui/compose.ts +289 -0
  245. package/src/pi-intercom/ui/inline-message.ts +92 -0
  246. package/src/pi-intercom/ui/session-list.ts +139 -0
  247. package/src/runs/background/async-control.ts +89 -0
  248. package/src/runs/background/async-execution.ts +850 -0
  249. package/src/runs/background/async-job-tracker.ts +385 -0
  250. package/src/runs/background/async-result-file.ts +62 -0
  251. package/src/runs/background/async-resume.ts +333 -0
  252. package/src/runs/background/async-status.ts +481 -0
  253. package/src/runs/background/completion-dedupe.ts +79 -0
  254. package/src/runs/background/completion-delivery.ts +195 -0
  255. package/src/runs/background/notify.ts +123 -0
  256. package/src/runs/background/parallel-groups.ts +53 -0
  257. package/src/runs/background/result-watcher.ts +423 -0
  258. package/src/runs/background/run-id-resolver.ts +69 -0
  259. package/src/runs/background/run-status.ts +384 -0
  260. package/src/runs/background/stale-run-reconciler.ts +369 -0
  261. package/src/runs/background/subagent-runner-launcher.ts +56 -0
  262. package/src/runs/background/subagent-runner.ts +2053 -0
  263. package/src/runs/background/top-level-async.ts +13 -0
  264. package/src/runs/foreground/chain-clarify.ts +1340 -0
  265. package/src/runs/foreground/clarify-invocation.ts +77 -0
  266. package/src/runs/foreground/execution-input.ts +467 -0
  267. package/src/runs/foreground/foreground-control.ts +821 -0
  268. package/src/runs/foreground/question-control.ts +82 -0
  269. package/src/runs/foreground/run-async-path.ts +243 -0
  270. package/src/runs/foreground/subagent-executor.ts +501 -0
  271. package/src/runs/foreground/subagent-params.ts +320 -0
  272. package/src/runs/foreground/wait-run.ts +124 -0
  273. package/src/runs/shared/acceptance-contract.ts +257 -0
  274. package/src/runs/shared/acceptance-evaluation.ts +275 -0
  275. package/src/runs/shared/acceptance-finalization.ts +353 -0
  276. package/src/runs/shared/acceptance-reports.ts +152 -0
  277. package/src/runs/shared/acceptance.ts +33 -0
  278. package/src/runs/shared/chain-outputs.ts +114 -0
  279. package/src/runs/shared/child-attempt.ts +623 -0
  280. package/src/runs/shared/child-execution-cwd.ts +91 -0
  281. package/src/runs/shared/child-observations.ts +99 -0
  282. package/src/runs/shared/claude-code.ts +243 -0
  283. package/src/runs/shared/completion-guard.ts +35 -0
  284. package/src/runs/shared/dynamic-fanout.ts +295 -0
  285. package/src/runs/shared/history-index.ts +69 -0
  286. package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
  287. package/src/runs/shared/model-fallback.ts +219 -0
  288. package/src/runs/shared/mutating-tool-guard.ts +194 -0
  289. package/src/runs/shared/native-finalization.ts +128 -0
  290. package/src/runs/shared/native-usage.ts +113 -0
  291. package/src/runs/shared/nested-events.ts +752 -0
  292. package/src/runs/shared/nested-path.ts +52 -0
  293. package/src/runs/shared/nested-render.ts +119 -0
  294. package/src/runs/shared/parallel-utils.ts +153 -0
  295. package/src/runs/shared/parent-receipts.ts +59 -0
  296. package/src/runs/shared/parent-usage.ts +121 -0
  297. package/src/runs/shared/pi-args.ts +350 -0
  298. package/src/runs/shared/pi-spawn.ts +52 -0
  299. package/src/runs/shared/run-history-queries.ts +44 -0
  300. package/src/runs/shared/run-history.ts +100 -0
  301. package/src/runs/shared/run-id-ambiguity.ts +4 -0
  302. package/src/runs/shared/run-records.ts +607 -0
  303. package/src/runs/shared/session-cwd-preload.ts +27 -0
  304. package/src/runs/shared/single-output.ts +267 -0
  305. package/src/runs/shared/streaming-text.ts +18 -0
  306. package/src/runs/shared/structured-output.ts +68 -0
  307. package/src/runs/shared/subagent-control.ts +219 -0
  308. package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
  309. package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
  310. package/src/runs/shared/supervisor-questions.ts +415 -0
  311. package/src/runs/shared/workflow-graph.ts +220 -0
  312. package/src/runs/shared/workflow-policy.ts +125 -0
  313. package/src/runs/shared/worktree.ts +635 -0
  314. package/src/shared/agent-context-policy.ts +184 -0
  315. package/src/shared/artifacts.ts +95 -0
  316. package/src/shared/atomic-json.ts +16 -0
  317. package/src/shared/cooperative.ts +21 -0
  318. package/src/shared/execution-cwd.ts +14 -0
  319. package/src/shared/file-coalescer.ts +29 -0
  320. package/src/shared/fork-context.ts +68 -0
  321. package/src/shared/formatters.ts +84 -0
  322. package/src/shared/journal-reader.ts +333 -0
  323. package/src/shared/lazy-tools.ts +36 -0
  324. package/src/shared/model-info.ts +81 -0
  325. package/src/shared/native-session.ts +8 -0
  326. package/src/shared/native-tui.ts +10 -0
  327. package/src/shared/native-typebox.ts +14 -0
  328. package/src/shared/post-exit-stdio-guard.ts +204 -0
  329. package/src/shared/prompt-sections.ts +10 -0
  330. package/src/shared/session-entries.ts +49 -0
  331. package/src/shared/session-identity.ts +18 -0
  332. package/src/shared/settings.ts +384 -0
  333. package/src/shared/status-format.ts +123 -0
  334. package/src/shared/temp-root.ts +107 -0
  335. package/src/shared/types.ts +1260 -0
  336. package/src/shared/ui-mode.ts +5 -0
  337. package/src/shared/utils.ts +342 -0
  338. package/src/slash/prompt-template-bridge.ts +408 -0
  339. package/src/slash/slash-bridge.ts +178 -0
  340. package/src/slash/slash-commands.ts +617 -0
  341. package/src/slash/slash-live-state.ts +292 -0
  342. package/src/tui/action-hints.ts +69 -0
  343. package/src/tui/agent-history.ts +210 -0
  344. package/src/tui/agent-view.ts +1323 -0
  345. package/src/tui/render.ts +1550 -0
  346. package/tsconfig.build.json +12 -0
  347. package/tsconfig.json +19 -0
@@ -0,0 +1,163 @@
1
+ export function isTopicSubscription(value) {
2
+ if (!value || typeof value !== "object")
3
+ return false;
4
+ const item = value;
5
+ return typeof item.topic === "string" && safeLabel(item.topic.trim()) && (item.awaitRelease === undefined || typeof item.awaitRelease === "boolean");
6
+ }
7
+ export function isTopicUpdate(value) {
8
+ if (!value || typeof value !== "object")
9
+ return false;
10
+ const item = value;
11
+ return typeof item.topic === "string" && safeLabel(item.topic.trim()) && typeof item.text === "string" && safeText(item.text)
12
+ && ["update", "blocker", "decision", "release"].includes(String(item.event))
13
+ && typeof item.revision === "number" && Number.isSafeInteger(item.revision) && item.revision > 0
14
+ && typeof item.updatedAt === "number" && Number.isFinite(item.updatedAt)
15
+ && (item.ownership !== "released" || item.event === "release")
16
+ && (item.resource === undefined || typeof item.resource === "string" && safeLabel(item.resource))
17
+ && (item.ownership === undefined || typeof item.resource === "string" && (item.ownership === "held" || item.ownership === "released"));
18
+ }
19
+ export function isHumanMessageOrigin(value) {
20
+ if (!value || typeof value !== "object")
21
+ return false;
22
+ const origin = value;
23
+ return typeof origin.ownerSessionId === "string" && /^[a-zA-Z0-9_-]+$/.test(origin.ownerSessionId)
24
+ && typeof origin.runId === "string" && /^[a-zA-Z0-9_-]+$/.test(origin.runId)
25
+ && typeof origin.index === "number" && Number.isSafeInteger(origin.index) && origin.index >= 0;
26
+ }
27
+ const UNSAFE_TEXT_CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/;
28
+ const UNSAFE_LABEL_CONTROLS = /[\u0000-\u001f\u007f-\u009f]/;
29
+ function safeText(value) {
30
+ return !UNSAFE_TEXT_CONTROLS.test(value);
31
+ }
32
+ function safeLabel(value) {
33
+ return value.length > 0 && !UNSAFE_LABEL_CONTROLS.test(value);
34
+ }
35
+ export function isAttachment(value) {
36
+ if (typeof value !== "object" || value === null) {
37
+ return false;
38
+ }
39
+ const attachment = value;
40
+ if (attachment.type !== "file"
41
+ && attachment.type !== "snippet"
42
+ && attachment.type !== "context") {
43
+ return false;
44
+ }
45
+ if (typeof attachment.name !== "string" || !safeLabel(attachment.name) || typeof attachment.content !== "string" || !safeText(attachment.content))
46
+ return false;
47
+ return attachment.language === undefined || (typeof attachment.language === "string" && safeLabel(attachment.language));
48
+ }
49
+ export function normalizeSessionInfo(value) {
50
+ if (typeof value !== "object" || value === null || typeof value.id !== "string")
51
+ return null;
52
+ if (isSessionRegistration(value))
53
+ return value;
54
+ if (!("projectId" in value))
55
+ return null;
56
+ const { projectId: _projectId, ...withoutProjectId } = value;
57
+ return isSessionRegistration(withoutProjectId) ? withoutProjectId : null;
58
+ }
59
+ export function isMessage(value) {
60
+ if (typeof value !== "object" || value === null) {
61
+ return false;
62
+ }
63
+ const message = value;
64
+ if (typeof message.id !== "string" || !safeLabel(message.id) || typeof message.timestamp !== "number" || !Number.isFinite(message.timestamp)) {
65
+ return false;
66
+ }
67
+ if (message.replyTo !== undefined && (typeof message.replyTo !== "string" || !safeLabel(message.replyTo))) {
68
+ return false;
69
+ }
70
+ if (message.human !== undefined && !isHumanMessageOrigin(message.human))
71
+ return false;
72
+ if (message.topic !== undefined && (!isTopicUpdate(message.topic) || message.human !== undefined || message.expectsReply || message.replyTo))
73
+ return false;
74
+ if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") {
75
+ return false;
76
+ }
77
+ if (message.passive !== undefined && typeof message.passive !== "boolean") {
78
+ return false;
79
+ }
80
+ if (message.delivery !== undefined
81
+ && message.delivery !== "queue"
82
+ && message.delivery !== "steer"
83
+ && message.delivery !== "passive") {
84
+ return false;
85
+ }
86
+ if (message.queueMode !== undefined
87
+ && message.queueMode !== "stack"
88
+ && message.queueMode !== "replace") {
89
+ return false;
90
+ }
91
+ if (message.threadId !== undefined && (typeof message.threadId !== "string" || !safeLabel(message.threadId.trim()))) {
92
+ return false;
93
+ }
94
+ if (message.passive === true && message.delivery !== undefined && message.delivery !== "passive") {
95
+ return false;
96
+ }
97
+ if ((message.passive === true || message.delivery === "passive") && message.expectsReply === true) {
98
+ return false;
99
+ }
100
+ if (message.queueMode !== undefined && message.delivery !== "queue") {
101
+ return false;
102
+ }
103
+ if (message.queueMode === "replace" && typeof message.threadId !== "string") {
104
+ return false;
105
+ }
106
+ if (message.threadId !== undefined && message.queueMode !== "replace") {
107
+ return false;
108
+ }
109
+ if (typeof message.content !== "object" || message.content === null) {
110
+ return false;
111
+ }
112
+ const content = message.content;
113
+ if (typeof content.text !== "string" || !safeText(content.text)) {
114
+ return false;
115
+ }
116
+ return content.attachments === undefined
117
+ || (Array.isArray(content.attachments) && content.attachments.every(isAttachment));
118
+ }
119
+ export function isSessionRegistration(value) {
120
+ if (typeof value !== "object" || value === null || Array.isArray(value))
121
+ return false;
122
+ const session = value;
123
+ if (typeof session.cwd !== "string" || !safeLabel(session.cwd) || typeof session.model !== "string" || !safeLabel(session.model))
124
+ return false;
125
+ if (session.name !== undefined && (typeof session.name !== "string" || !safeLabel(session.name)))
126
+ return false;
127
+ if (session.projectId !== undefined && (typeof session.projectId !== "string" || !/^[a-f0-9]{64}$/.test(session.projectId)))
128
+ return false;
129
+ if (session.status !== undefined && (typeof session.status !== "string" || !safeLabel(session.status)))
130
+ return false;
131
+ if (session.lastSeen !== undefined && (typeof session.lastSeen !== "number" || !Number.isFinite(session.lastSeen)))
132
+ return false;
133
+ if (session.lastIntercomActivity !== undefined && (typeof session.lastIntercomActivity !== "number" || !Number.isFinite(session.lastIntercomActivity)))
134
+ return false;
135
+ if (session.pendingAsks !== undefined && (typeof session.pendingAsks !== "number" || !Number.isInteger(session.pendingAsks) || session.pendingAsks < 0))
136
+ return false;
137
+ if (session.subscriptions !== undefined && (!Array.isArray(session.subscriptions) || !session.subscriptions.every(isTopicSubscription)))
138
+ return false;
139
+ if (session.topics !== undefined && (!Array.isArray(session.topics) || !session.topics.every(isTopicUpdate)))
140
+ return false;
141
+ return session.acceptsAsks === undefined || typeof session.acceptsAsks === "boolean";
142
+ }
143
+ /** Topic text and the standard coalescing key need only one copy on the wire. */
144
+ export function compactTopicMessage(message) {
145
+ if (!message.topic)
146
+ return message;
147
+ return { ...message,
148
+ topic: message.topic.text === message.content.text ? { ...message.topic, text: undefined } : message.topic,
149
+ threadId: message.queueMode === "replace" && message.threadId === `topic:${message.topic.topic}` ? undefined : message.threadId,
150
+ };
151
+ }
152
+ export function normalizeMessage(value) {
153
+ if (isMessage(value))
154
+ return value;
155
+ if (!value || typeof value !== "object")
156
+ return null;
157
+ const message = value;
158
+ if (!message.topic || typeof message.topic !== "object" || !message.content)
159
+ return null;
160
+ const restored = { ...message, topic: { ...message.topic, text: message.topic.text === undefined ? message.content.text : message.topic.text },
161
+ ...(message.queueMode === "replace" && message.threadId === undefined ? { threadId: `topic:${message.topic.topic}` } : {}) };
162
+ return isMessage(restored) ? restored : null;
163
+ }
@@ -0,0 +1,269 @@
1
+ import { Box, Container, Text, sliceByColumn, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { actionHints } from "../../tui/action-hints.js";
3
+ const BRACKETED_PASTE_START = "\x1b[200~";
4
+ const BRACKETED_PASTE_END = "\x1b[201~";
5
+ const MAX_PASTE_CHARS = 1_000_000;
6
+ const INCOMPLETE_PASTE_IDLE_MS = 200;
7
+ const PASTE_RENDER_TAIL_CHARS = 8192;
8
+ export class ComposeOverlay extends Container {
9
+ tui;
10
+ theme;
11
+ keybindings;
12
+ target;
13
+ targetLabel;
14
+ client;
15
+ done;
16
+ inputBuffer = "";
17
+ mode = "send";
18
+ completed = false;
19
+ sending = false;
20
+ error = null;
21
+ pasteBuffer = null;
22
+ pasteStartPrefix = "";
23
+ pasteIdleTimer = null;
24
+ constructor(tui, theme, keybindings, target, targetLabel, client, done) {
25
+ super();
26
+ this.tui = tui;
27
+ this.theme = theme;
28
+ this.keybindings = keybindings;
29
+ this.target = target;
30
+ this.targetLabel = targetLabel;
31
+ this.client = client;
32
+ this.done = done;
33
+ }
34
+ dispose() {
35
+ this.completed = true;
36
+ if (this.pasteIdleTimer)
37
+ clearTimeout(this.pasteIdleTimer);
38
+ this.pasteIdleTimer = null;
39
+ }
40
+ handleMouse(event) {
41
+ const width = Math.min(event.width, 72);
42
+ if (this.sending || this.completed || this.pasteBuffer !== null || this.pasteStartPrefix || event.x >= width)
43
+ return;
44
+ return super.handleMouse({ ...event, width });
45
+ }
46
+ act(action) {
47
+ if (this.sending || this.completed || this.pasteBuffer !== null || this.pasteStartPrefix)
48
+ return;
49
+ if (action === "close")
50
+ this.finish({ sent: false });
51
+ else if (action === "mode") {
52
+ this.mode = this.mode === "send" ? "ask" : "send";
53
+ this.error = null;
54
+ }
55
+ else if (this.inputBuffer.trim())
56
+ void this.sendMessage();
57
+ this.tui.requestRender();
58
+ }
59
+ finish(result) {
60
+ if (this.completed)
61
+ return;
62
+ this.dispose();
63
+ this.done(result);
64
+ }
65
+ scheduleIncompletePasteFlush() {
66
+ if (this.pasteIdleTimer)
67
+ clearTimeout(this.pasteIdleTimer);
68
+ this.pasteIdleTimer = setTimeout(() => {
69
+ this.pasteIdleTimer = null;
70
+ if (this.pasteBuffer === null || this.completed)
71
+ return;
72
+ const data = this.pasteBuffer.replace(/\r\n?/g, "\n");
73
+ this.pasteBuffer = null;
74
+ const printable = [...data].filter((character) => character >= " " || character === "\n" || character === "\t").join("");
75
+ if (printable)
76
+ this.inputBuffer += printable;
77
+ this.error = null;
78
+ this.tui.requestRender();
79
+ }, INCOMPLETE_PASTE_IDLE_MS);
80
+ this.pasteIdleTimer.unref?.();
81
+ }
82
+ handleInput(data) {
83
+ if (this.sending || this.completed || !data)
84
+ return;
85
+ let pasted = false;
86
+ if (this.pasteBuffer !== null) {
87
+ this.pasteBuffer += data;
88
+ const end = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);
89
+ if (end === -1 && this.pasteBuffer.length <= MAX_PASTE_CHARS) {
90
+ this.scheduleIncompletePasteFlush();
91
+ this.tui.requestRender();
92
+ return;
93
+ }
94
+ data = end === -1
95
+ ? this.pasteBuffer
96
+ : this.pasteBuffer.slice(0, end) + this.pasteBuffer.slice(end + BRACKETED_PASTE_END.length);
97
+ this.pasteBuffer = null;
98
+ if (this.pasteIdleTimer)
99
+ clearTimeout(this.pasteIdleTimer);
100
+ this.pasteIdleTimer = null;
101
+ data = data.replace(/\r\n?/g, "\n");
102
+ pasted = true;
103
+ }
104
+ else {
105
+ if (this.pasteStartPrefix) {
106
+ if (this.pasteIdleTimer)
107
+ clearTimeout(this.pasteIdleTimer);
108
+ this.pasteIdleTimer = null;
109
+ data = this.pasteStartPrefix + data;
110
+ this.pasteStartPrefix = "";
111
+ }
112
+ if (data !== BRACKETED_PASTE_START && BRACKETED_PASTE_START.startsWith(data)) {
113
+ this.pasteStartPrefix = data;
114
+ this.pasteIdleTimer = setTimeout(() => {
115
+ this.pasteIdleTimer = null;
116
+ const pendingPrefix = this.pasteStartPrefix;
117
+ this.pasteStartPrefix = "";
118
+ if (pendingPrefix === "\x1b" && this.keybindings.matches(pendingPrefix, "tui.select.cancel"))
119
+ this.finish({ sent: false });
120
+ }, INCOMPLETE_PASTE_IDLE_MS);
121
+ this.pasteIdleTimer.unref?.();
122
+ return;
123
+ }
124
+ }
125
+ if (!pasted && data.startsWith(BRACKETED_PASTE_START)) {
126
+ const body = data.slice(BRACKETED_PASTE_START.length);
127
+ const end = body.indexOf(BRACKETED_PASTE_END);
128
+ if (end === -1 && body.length <= MAX_PASTE_CHARS) {
129
+ this.pasteBuffer = body;
130
+ this.scheduleIncompletePasteFlush();
131
+ this.tui.requestRender();
132
+ return;
133
+ }
134
+ data = end === -1 ? body : body.slice(0, end) + body.slice(end + BRACKETED_PASTE_END.length);
135
+ data = data.replace(/\r\n?/g, "\n");
136
+ pasted = true;
137
+ }
138
+ if (!pasted && data.includes(BRACKETED_PASTE_END))
139
+ data = data.replaceAll(BRACKETED_PASTE_END, "");
140
+ if (!data)
141
+ return;
142
+ if (!pasted && this.keybindings.matches(data, "tui.select.cancel")) {
143
+ this.act("close");
144
+ return;
145
+ }
146
+ if (!pasted && data === "\t") {
147
+ this.act("mode");
148
+ return;
149
+ }
150
+ if (!pasted && this.keybindings.matches(data, "tui.select.confirm")) {
151
+ this.act("send");
152
+ return;
153
+ }
154
+ if (!pasted && data.startsWith("\x1b")) {
155
+ return;
156
+ }
157
+ if (!pasted && this.keybindings.matches(data, "tui.editor.deleteCharBackward")) {
158
+ this.inputBuffer = [...this.inputBuffer].slice(0, -1).join("");
159
+ this.error = null;
160
+ this.tui.requestRender();
161
+ return;
162
+ }
163
+ const printable = [...data].filter(c => c >= " " || c === "\n" || c === "\t").join("");
164
+ if (printable) {
165
+ this.inputBuffer += printable;
166
+ this.error = null;
167
+ this.tui.requestRender();
168
+ }
169
+ }
170
+ async sendMessage() {
171
+ this.sending = true;
172
+ this.error = null;
173
+ this.tui.requestRender();
174
+ try {
175
+ const expectsReply = this.mode === "ask";
176
+ const text = this.inputBuffer;
177
+ const result = await this.client.send(this.target.id, {
178
+ text,
179
+ expectsReply,
180
+ });
181
+ if (!result.accepted) {
182
+ this.error = result.reason ?? "Message not delivered. Session may not exist or has disconnected.";
183
+ this.sending = false;
184
+ this.tui.requestRender();
185
+ return;
186
+ }
187
+ this.finish({
188
+ sent: true,
189
+ messageId: result.id,
190
+ text,
191
+ expectsReply,
192
+ });
193
+ }
194
+ catch (error) {
195
+ this.error = error instanceof Error ? error.message : String(error);
196
+ this.sending = false;
197
+ this.tui.requestRender();
198
+ }
199
+ }
200
+ renderInputLines(row, lines, contentWidth) {
201
+ const pendingPaste = this.pasteBuffer === null
202
+ ? ""
203
+ : [...this.pasteBuffer.slice(-PASTE_RENDER_TAIL_CHARS)].filter((character) => character >= " " || character === "\n" || character === "\t").join("");
204
+ const rawLines = `${this.inputBuffer.slice(-PASTE_RENDER_TAIL_CHARS)}${pendingPaste}`.split("\n");
205
+ const visibleLines = rawLines.slice(-8);
206
+ visibleLines.forEach((line, index) => {
207
+ const isLast = index === visibleLines.length - 1;
208
+ const prefix = index === 0 ? " > " : " ";
209
+ if (isLast) {
210
+ const graphemes = [...line];
211
+ const budget = Math.max(1, contentWidth - prefix.length - 1);
212
+ let used = 0;
213
+ let start = graphemes.length;
214
+ while (start > 0) {
215
+ const width = visibleWidth(graphemes[start - 1]);
216
+ if (used + width > budget)
217
+ break;
218
+ used += width;
219
+ start--;
220
+ }
221
+ line = graphemes.slice(start).join("");
222
+ }
223
+ lines.push(row(`${prefix}${line}${isLast ? "█" : ""}`));
224
+ });
225
+ }
226
+ render(width) {
227
+ this.clear();
228
+ if (width < 3)
229
+ return [truncateToWidth("Intercom", width)];
230
+ const innerWidth = Math.min(width, 72);
231
+ const contentWidth = Math.max(1, innerWidth - 2);
232
+ const send = [this.keybindings.getKeys("tui.select.confirm").join("/"), this.mode === "ask" ? "Request reply" : "Send"].filter(Boolean).join(": ");
233
+ const mode = `Tab: ${this.mode === "ask" ? "Send mode" : "Request-reply mode"}`;
234
+ const close = [this.keybindings.getKeys("tui.select.cancel").join("/"), "Close"].filter(Boolean).join(": ");
235
+ const footer = this.sending ? ["Sending…"] : this.pasteBuffer !== null || this.pasteStartPrefix ? ["Pasting…"] : [
236
+ { text: send, run: () => this.act("send") }, " • ", { text: mode, run: () => this.act("mode") }, " • ", { text: close, run: () => this.act("close") },
237
+ ];
238
+ const border = (text) => this.theme.fg("accent", text);
239
+ const row = (text = "") => {
240
+ const clipped = truncateToWidth(text, contentWidth, "…", true);
241
+ return `${border("│")}${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))}${border("│")}`;
242
+ };
243
+ const lines = [];
244
+ lines.push(border(`╭${"─".repeat(contentWidth)}╮`));
245
+ lines.push(row(this.theme.bold(` ${this.mode === "ask" ? "Request reply" : "Send"} to: ${this.targetLabel}`)));
246
+ lines.push(row(this.theme.fg("dim", ` Native session cwd: ${this.target.cwd} • ${this.target.model}`)));
247
+ lines.push(border(`├${"─".repeat(contentWidth)}┤`));
248
+ lines.push(row());
249
+ if (this.sending) {
250
+ lines.push(row(this.theme.fg("dim", " Sending...")));
251
+ }
252
+ else if (this.error) {
253
+ lines.push(row(this.theme.fg("error", ` Error: ${this.error}`)));
254
+ lines.push(row());
255
+ this.renderInputLines(row, lines, contentWidth);
256
+ }
257
+ else {
258
+ this.renderInputLines(row, lines, contentWidth);
259
+ }
260
+ lines.push(row());
261
+ lines.push(border(`├${"─".repeat(contentWidth)}┤`));
262
+ this.addChild(new Text(lines.join("\n"), 0, 0));
263
+ const controls = new Box(1, 0, (line) => row(sliceByColumn(line, 1, contentWidth)));
264
+ controls.addChild(actionHints([" ", ...footer], (text) => this.theme.fg("dim", text), "…"));
265
+ this.addChild(controls);
266
+ this.addChild(new Text(border(`╰${"─".repeat(contentWidth)}╯`), 0, 0));
267
+ return super.render(innerWidth);
268
+ }
269
+ }
@@ -0,0 +1,80 @@
1
+ import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
+ import { keyText } from "@earendil-works/pi-coding-agent";
3
+ export class InlineMessageComponent {
4
+ from;
5
+ message;
6
+ theme;
7
+ replyCommand;
8
+ bodyText;
9
+ expanded;
10
+ cachedWidth;
11
+ cachedLines;
12
+ constructor(from, message, theme, replyCommand, bodyText, expanded = true) {
13
+ this.from = from;
14
+ this.message = message;
15
+ this.theme = theme;
16
+ this.replyCommand = replyCommand;
17
+ this.bodyText = bodyText;
18
+ this.expanded = expanded;
19
+ }
20
+ invalidate() {
21
+ this.cachedWidth = undefined;
22
+ this.cachedLines = undefined;
23
+ }
24
+ render(width) {
25
+ if (this.cachedLines && this.cachedWidth === width)
26
+ return this.cachedLines;
27
+ const lines = [];
28
+ const borderChar = "─";
29
+ if (width < 3) {
30
+ this.cachedWidth = width;
31
+ this.cachedLines = [truncateToWidth(`From ${this.from.name || this.from.id.slice(0, 8)}`, width)];
32
+ return this.cachedLines;
33
+ }
34
+ const bodyWidth = Math.max(1, width - 2);
35
+ const senderName = this.from.name || this.from.id.slice(0, 8);
36
+ const header = ` 📨 From: ${senderName} (Native session cwd: ${this.from.cwd}) `;
37
+ const headerText = truncateToWidth(header, bodyWidth, "");
38
+ const headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));
39
+ lines.push(this.theme.fg("accent", `╭${headerText}${borderChar.repeat(headerPadding)}╮`));
40
+ const body = this.bodyText || this.message.content.text;
41
+ // Grouped subagent results start with run, mode, status and child counts after the title.
42
+ const contentLines = this.expanded ? wrapTextWithAnsi(body, bodyWidth) : body.split("\n").slice(2, 6);
43
+ if (!this.expanded)
44
+ contentLines.push(this.theme.fg("dim", `${keyText("app.tools.expand")} full response`));
45
+ for (const line of contentLines) {
46
+ const text = truncateToWidth(line, bodyWidth, "");
47
+ const padding = Math.max(0, bodyWidth - visibleWidth(text));
48
+ lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
49
+ }
50
+ if (this.replyCommand) {
51
+ lines.push(this.theme.fg("accent", `│${" ".repeat(bodyWidth)}│`));
52
+ const replyLines = wrapTextWithAnsi(this.theme.fg("dim", ` ↩ To reply: ${this.replyCommand}`), bodyWidth);
53
+ for (const line of replyLines) {
54
+ const text = truncateToWidth(line, bodyWidth, "");
55
+ const padding = Math.max(0, bodyWidth - visibleWidth(text));
56
+ lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
57
+ }
58
+ }
59
+ if (this.message.content.attachments?.length && !this.bodyText) {
60
+ lines.push(this.theme.fg("accent", `│${" ".repeat(bodyWidth)}│`));
61
+ for (const att of this.message.content.attachments) {
62
+ const label = this.theme.fg("dim", ` 📎 ${att.name}`);
63
+ const text = truncateToWidth(label, bodyWidth, "");
64
+ const padding = Math.max(0, bodyWidth - visibleWidth(text));
65
+ lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
66
+ }
67
+ }
68
+ if (this.message.replyTo && !this.message.expectsReply) {
69
+ lines.push(this.theme.fg("accent", `│${" ".repeat(bodyWidth)}│`));
70
+ const reply = this.theme.fg("dim", ` ↳ Reply to ${this.message.replyTo.slice(0, 8)}`);
71
+ const text = truncateToWidth(reply, bodyWidth, "");
72
+ const padding = Math.max(0, bodyWidth - visibleWidth(text));
73
+ lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`));
74
+ }
75
+ lines.push(this.theme.fg("accent", `╰${borderChar.repeat(bodyWidth)}╯`));
76
+ this.cachedWidth = width;
77
+ this.cachedLines = lines;
78
+ return lines;
79
+ }
80
+ }
@@ -0,0 +1,124 @@
1
+ import { Box, Container, SelectList, Text, sliceByColumn, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { actionHints } from "../../tui/action-hints.js";
3
+ import { formatSessionTarget } from "../session-targets.js";
4
+ function sessionTitle(session, allSessions, suffix) {
5
+ return `${session.name || "Unnamed session"} (${formatSessionTarget(session, allSessions)})${suffix ? ` [${suffix}]` : ""}`;
6
+ }
7
+ export class SessionListOverlay extends Container {
8
+ completed = false;
9
+ selectList;
10
+ allSessions;
11
+ tui;
12
+ theme;
13
+ keybindings;
14
+ currentSession;
15
+ sessions;
16
+ done;
17
+ hiddenSessionCount;
18
+ constructor(tui, theme, keybindings, currentSession, sessions, done, hiddenSessionCount = 0, allSessions = [currentSession, ...sessions]) {
19
+ super();
20
+ this.tui = tui;
21
+ this.theme = theme;
22
+ this.keybindings = keybindings;
23
+ this.currentSession = currentSession;
24
+ this.sessions = sessions;
25
+ this.done = done;
26
+ this.hiddenSessionCount = hiddenSessionCount;
27
+ this.allSessions = allSessions;
28
+ const items = sessions.map((session) => ({
29
+ value: session.id,
30
+ label: sessionTitle(session, this.allSessions, session.cwd === currentSession.cwd ? "same native session cwd" : undefined),
31
+ description: `${session.topics?.filter((topic) => topic.resource && topic.ownership === "held").map((topic) => `Using ${topic.resource}`).join(" · ") || `Native session cwd: ${session.cwd}`} • ${session.model}`,
32
+ }));
33
+ this.selectList = new SelectList(items, 8, {
34
+ selectedPrefix: (text) => theme.fg("accent", text),
35
+ selectedText: (text) => theme.fg("accent", text),
36
+ description: (text) => theme.fg("dim", text),
37
+ scrollInfo: (text) => theme.fg("dim", text),
38
+ noMatch: (text) => theme.fg("dim", text),
39
+ });
40
+ this.selectList.onSelect = (item) => this.finish(sessions.find((session) => session.id === item.value));
41
+ this.selectList.onCancel = () => this.finish();
42
+ }
43
+ finish(result) {
44
+ if (this.completed)
45
+ return;
46
+ this.completed = true;
47
+ this.done(result);
48
+ }
49
+ dispose() { this.completed = true; }
50
+ handleMouse(event) {
51
+ const width = Math.min(event.width, 88);
52
+ if (this.completed || event.x >= width)
53
+ return;
54
+ return super.handleMouse({ ...event, width });
55
+ }
56
+ handleInput(data) {
57
+ if (this.completed)
58
+ return;
59
+ if (this.sessions.length === 0) {
60
+ if (this.keybindings.matches(data, "tui.select.cancel"))
61
+ this.finish();
62
+ }
63
+ else {
64
+ this.selectList.handleInput(data);
65
+ }
66
+ this.tui.requestRender();
67
+ }
68
+ render(width) {
69
+ this.clear();
70
+ if (width < 3)
71
+ return [truncateToWidth("Intercom", width)];
72
+ const innerWidth = Math.min(width, 88);
73
+ const contentWidth = Math.max(1, innerWidth - 2);
74
+ const message = [this.keybindings.getKeys("tui.select.confirm").join("/"), "Message"].filter(Boolean).join(": ");
75
+ const close = [this.keybindings.getKeys("tui.select.cancel").join("/"), "Close"].filter(Boolean).join(": ");
76
+ const border = (text) => this.theme.fg("accent", text);
77
+ const row = (text = "") => {
78
+ const clipped = truncateToWidth(text, contentWidth, "", true);
79
+ return `${border("│")}${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))}${border("│")}`;
80
+ };
81
+ const framed = (component) => {
82
+ const box = new Box(1, 0, (line) => row(sliceByColumn(line, 1, contentWidth)));
83
+ box.addChild(component);
84
+ return box;
85
+ };
86
+ const lines = [
87
+ border(`╭${"─".repeat(contentWidth)}╮`),
88
+ row(this.theme.bold(" Current Session")),
89
+ border(`├${"─".repeat(contentWidth)}┤`),
90
+ row(` ${this.theme.fg("dim", sessionTitle(this.currentSession, this.allSessions, "self"))}`),
91
+ row(` ${this.theme.fg("dim", `Native session cwd: ${this.currentSession.cwd} • ${this.currentSession.model}`)}`),
92
+ border(`├${"─".repeat(contentWidth)}┤`),
93
+ row(this.theme.bold(" Other Sessions")),
94
+ ];
95
+ this.addChild(new Text(lines.join("\n"), 0, 0));
96
+ if (this.sessions.length === 0) {
97
+ this.addChild(new Text((this.hiddenSessionCount > 0
98
+ ? [
99
+ row(this.theme.fg("dim", " No other sessions in this project")),
100
+ row(this.theme.fg("dim", ` ${this.hiddenSessionCount} in other project${this.hiddenSessionCount === 1 ? "" : "s"} hidden`)),
101
+ row(this.theme.fg("dim", " Run /intercom all to show every connected session")),
102
+ ]
103
+ : [
104
+ row(this.theme.fg("dim", " No other intercom-connected sessions")),
105
+ row(this.theme.fg("dim", " Start another session with: pi --name worker")),
106
+ row(this.theme.fg("dim", " Then run intercom({ action: \"list\" }) again")),
107
+ ]).join("\n"), 0, 0));
108
+ }
109
+ else {
110
+ this.addChild(framed(this.selectList));
111
+ if (this.hiddenSessionCount > 0) {
112
+ this.addChild(new Text(row(this.theme.fg("dim", ` ${this.hiddenSessionCount} other-project session${this.hiddenSessionCount === 1 ? "" : "s"} hidden · /intercom all`)), 0, 0));
113
+ }
114
+ }
115
+ this.addChild(new Text(border(`├${"─".repeat(contentWidth)}┤`), 0, 0));
116
+ this.addChild(framed(actionHints([
117
+ " ", ...(this.sessions.length ? [{ text: message, run: () => { const item = this.selectList.getSelectedItem(); if (item)
118
+ this.selectList.onSelect?.(item); } }, " • "] : []),
119
+ { text: close, run: () => this.finish() },
120
+ ], (text) => this.theme.fg("dim", text), "")));
121
+ this.addChild(new Text(border(`╰${"─".repeat(contentWidth)}╯`), 0, 0));
122
+ return super.render(innerWidth);
123
+ }
124
+ }