@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.87

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 (402) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +2 -2
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +12 -12
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +293 -7
  94. package/dist/core/edits/format-matrix.js +26 -0
  95. package/dist/core/edits/fuzzy-ladder.js +650 -0
  96. package/dist/core/edits/index.js +3 -1
  97. package/dist/core/edits/journal.js +199 -0
  98. package/dist/core/edits/layer-a-apply.js +15 -15
  99. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  100. package/dist/core/edits/layer-b-apply.js +9 -9
  101. package/dist/core/edits/layer-c-apply.js +6 -6
  102. package/dist/core/edits/layer-d-ast.js +557 -14
  103. package/dist/core/edits/marker-parser.js +12 -12
  104. package/dist/core/edits/security-gate.js +27 -27
  105. package/dist/core/edits/verify-hook.js +273 -0
  106. package/dist/core/edits/worktree.js +322 -0
  107. package/dist/core/engine/anvil-client.js +140 -26
  108. package/dist/core/engine/auto-compact.js +179 -0
  109. package/dist/core/engine/budgets.js +186 -0
  110. package/dist/core/engine/context-prefix.js +155 -0
  111. package/dist/core/engine/index.js +1 -1
  112. package/dist/core/engine/intensity.js +158 -0
  113. package/dist/core/engine/intent.js +260 -0
  114. package/dist/core/engine/native-pugi.js +1295 -227
  115. package/dist/core/engine/prompts.js +134 -16
  116. package/dist/core/engine/strip-internal-fields.js +124 -0
  117. package/dist/core/engine/tool-bridge.js +1295 -59
  118. package/dist/core/evaluation/golden-dataset.js +293 -0
  119. package/dist/core/feedback/queue.js +177 -0
  120. package/dist/core/feedback/submitter.js +145 -0
  121. package/dist/core/file-cache.js +113 -1
  122. package/dist/core/flatten/flatten-repo.js +439 -0
  123. package/dist/core/format/osc8-link.js +28 -0
  124. package/dist/core/hook-chains.js +392 -0
  125. package/dist/core/hooks/citation-verify-hook.js +138 -0
  126. package/dist/core/hooks/citation-verify.js +112 -0
  127. package/dist/core/hooks/events.js +44 -0
  128. package/dist/core/hooks/index.js +15 -0
  129. package/dist/core/hooks/registry.js +213 -0
  130. package/dist/core/hooks/runner.js +236 -0
  131. package/dist/core/hooks/v2/event-emitter.js +115 -0
  132. package/dist/core/hooks/v2/executor.js +282 -0
  133. package/dist/core/hooks/v2/index.js +25 -0
  134. package/dist/core/hooks/v2/lifecycle.js +104 -0
  135. package/dist/core/hooks/v2/loader.js +216 -0
  136. package/dist/core/hooks/v2/matcher.js +125 -0
  137. package/dist/core/hooks/v2/trust.js +143 -0
  138. package/dist/core/hooks/v2/types.js +86 -0
  139. package/dist/core/image/renderer.js +71 -0
  140. package/dist/core/init/detector.js +582 -0
  141. package/dist/core/init/template-renderer.js +242 -0
  142. package/dist/core/jobs/registry.js +18 -18
  143. package/dist/core/ledger/results-tsv.js +142 -0
  144. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  145. package/dist/core/lsp/cache.js +105 -0
  146. package/dist/core/lsp/client.js +776 -0
  147. package/dist/core/lsp/language-detect.js +66 -0
  148. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  149. package/dist/core/lsp/symbol-tools.js +372 -0
  150. package/dist/core/mcp/client.js +97 -28
  151. package/dist/core/mcp/http-server.js +553 -0
  152. package/dist/core/mcp/orchestrator-tools.js +662 -0
  153. package/dist/core/mcp/permission.js +190 -0
  154. package/dist/core/mcp/registry.js +39 -17
  155. package/dist/core/mcp/server-tools.js +219 -0
  156. package/dist/core/mcp/server.js +397 -0
  157. package/dist/core/mcp/trust.js +10 -10
  158. package/dist/core/memory/dual-write.js +416 -0
  159. package/dist/core/memory/passive-extract.js +130 -0
  160. package/dist/core/memory/phase1-kinds.js +20 -0
  161. package/dist/core/memory/secret-scanner.js +304 -0
  162. package/dist/core/memory-sync/queue.js +170 -0
  163. package/dist/core/metrics/extract.js +113 -0
  164. package/dist/core/modes/roo-modes.js +68 -0
  165. package/dist/core/onboarding/ensure-initialized.js +133 -0
  166. package/dist/core/onboarding/marker.js +111 -0
  167. package/dist/core/onboarding/telemetry-state.js +108 -0
  168. package/dist/core/output-style/presets.js +176 -0
  169. package/dist/core/output-style/state.js +185 -0
  170. package/dist/core/path-security.js +287 -5
  171. package/dist/core/permission.js +82 -22
  172. package/dist/core/permissions/auto-classifier.js +124 -0
  173. package/dist/core/permissions/bash-parser.js +371 -0
  174. package/dist/core/permissions/circuit-breaker.js +83 -0
  175. package/dist/core/permissions/constrained-edit.js +91 -0
  176. package/dist/core/permissions/gate.js +278 -0
  177. package/dist/core/permissions/index.js +20 -0
  178. package/dist/core/permissions/mode.js +174 -0
  179. package/dist/core/permissions/network-egress.js +137 -0
  180. package/dist/core/permissions/state.js +241 -0
  181. package/dist/core/permissions/tool-class.js +93 -0
  182. package/dist/core/plan-mode/ui-state.js +51 -0
  183. package/dist/core/plans/plan-artifact.js +721 -0
  184. package/dist/core/policy-limits/etag-store.js +122 -0
  185. package/dist/core/prd-check/parser.js +215 -0
  186. package/dist/core/prd-check/reporter.js +127 -0
  187. package/dist/core/prd-check/session-review.js +557 -0
  188. package/dist/core/prd-check/verifiers.js +223 -0
  189. package/dist/core/prompt-cache/client-cache.js +99 -0
  190. package/dist/core/prompts/assembly.js +29 -0
  191. package/dist/core/prompts/registry.js +364 -0
  192. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  193. package/dist/core/pugi-md/context-injector.js +76 -0
  194. package/dist/core/pugi-md/walk-up.js +207 -0
  195. package/dist/core/python/uv-installer.js +270 -0
  196. package/dist/core/python/uv-resolver.js +83 -0
  197. package/dist/core/rate-limit/narrator.js +146 -0
  198. package/dist/core/recipes/cli-types.js +20 -0
  199. package/dist/core/recipes/loader.js +103 -0
  200. package/dist/core/recipes/runner.js +345 -0
  201. package/dist/core/recipes/schema.js +587 -0
  202. package/dist/core/release-notes/parser.js +241 -0
  203. package/dist/core/release-notes/state.js +116 -0
  204. package/dist/core/repl/ask.js +37 -37
  205. package/dist/core/repl/cancellation.js +26 -26
  206. package/dist/core/repl/cap-warning.js +4 -4
  207. package/dist/core/repl/clipboard-read.js +11 -11
  208. package/dist/core/repl/dispatch-fsm.js +12 -12
  209. package/dist/core/repl/history-search.js +15 -15
  210. package/dist/core/repl/history.js +28 -18
  211. package/dist/core/repl/kill-ring.js +5 -5
  212. package/dist/core/repl/model-pricing.js +135 -0
  213. package/dist/core/repl/privacy-banner.js +22 -22
  214. package/dist/core/repl/session.js +2157 -214
  215. package/dist/core/repl/slash-commands.js +533 -40
  216. package/dist/core/repl/store/index.js +1 -1
  217. package/dist/core/repl/store/jsonl-log.js +22 -22
  218. package/dist/core/repl/store/lockfile.js +10 -10
  219. package/dist/core/repl/store/session-store.js +136 -107
  220. package/dist/core/repl/store/types.js +15 -15
  221. package/dist/core/repl/store/uuid-v7.js +12 -12
  222. package/dist/core/repl/workspace-context.js +43 -21
  223. package/dist/core/repo-map/build.js +125 -0
  224. package/dist/core/repo-map/cache.js +185 -0
  225. package/dist/core/repo-map/extractor.js +254 -0
  226. package/dist/core/repo-map/formatter.js +145 -0
  227. package/dist/core/repo-map/page-rank.js +105 -0
  228. package/dist/core/repo-map/scanner.js +211 -0
  229. package/dist/core/retry-budget/budget.js +284 -0
  230. package/dist/core/retry-budget/index.js +5 -0
  231. package/dist/core/retry-budget/retry-cap.js +74 -0
  232. package/dist/core/routing/lead-worker.js +43 -0
  233. package/dist/core/routing/pre-flight-estimator.js +108 -0
  234. package/dist/core/runs/run-tree.js +103 -0
  235. package/dist/core/security/injection-scanner.js +367 -0
  236. package/dist/core/security/output-filter.js +418 -0
  237. package/dist/core/session/env-file.js +105 -0
  238. package/dist/core/session/section-budgets.js +140 -0
  239. package/dist/core/session.js +92 -0
  240. package/dist/core/settings.js +286 -5
  241. package/dist/core/share/formatter.js +271 -0
  242. package/dist/core/share/redactor.js +221 -0
  243. package/dist/core/share/uploader.js +267 -0
  244. package/dist/core/skills/defaults.js +457 -0
  245. package/dist/core/skills/loader.js +22 -22
  246. package/dist/core/skills/sources.js +27 -27
  247. package/dist/core/smoke/headless-driver.js +174 -0
  248. package/dist/core/smoke/orchestrator.js +194 -0
  249. package/dist/core/smoke/runner.js +238 -0
  250. package/dist/core/smoke/scenario-parser.js +316 -0
  251. package/dist/core/statusline.js +99 -0
  252. package/dist/core/subagents/dispatcher-real.js +600 -0
  253. package/dist/core/subagents/dispatcher.js +132 -43
  254. package/dist/core/subagents/index.js +19 -6
  255. package/dist/core/subagents/isolation-matrix.js +213 -0
  256. package/dist/core/subagents/spawn.js +19 -4
  257. package/dist/core/telemetry/emitter.js +229 -0
  258. package/dist/core/telemetry/queue.js +251 -0
  259. package/dist/core/theme/context.js +91 -0
  260. package/dist/core/theme/presets.js +228 -0
  261. package/dist/core/theme/state.js +181 -0
  262. package/dist/core/todos/invariant.js +10 -0
  263. package/dist/core/todos/state.js +177 -0
  264. package/dist/core/tool-schema/compressor.js +89 -0
  265. package/dist/core/transport/version-interceptor.js +166 -0
  266. package/dist/core/trust.js +2 -2
  267. package/dist/core/tui/thinking-block.js +64 -0
  268. package/dist/core/vim/keymap.js +288 -0
  269. package/dist/core/vim/state.js +92 -0
  270. package/dist/core/watch-markers/marker-watcher.js +133 -0
  271. package/dist/core/worktree-manager/cleanup.js +123 -0
  272. package/dist/core/worktree-manager/manager.js +303 -0
  273. package/dist/index.js +28 -0
  274. package/dist/runtime/bootstrap.js +190 -0
  275. package/dist/runtime/cli.js +4151 -489
  276. package/dist/runtime/commands/agents.js +30 -30
  277. package/dist/runtime/commands/budget.js +5 -5
  278. package/dist/runtime/commands/cancel.js +231 -0
  279. package/dist/runtime/commands/chain.js +489 -0
  280. package/dist/runtime/commands/codegraph-status.js +227 -0
  281. package/dist/runtime/commands/compact.js +297 -0
  282. package/dist/runtime/commands/config.js +32 -32
  283. package/dist/runtime/commands/cost.js +199 -0
  284. package/dist/runtime/commands/delegate.js +244 -13
  285. package/dist/runtime/commands/dispatch.js +126 -0
  286. package/dist/runtime/commands/doctor.js +579 -0
  287. package/dist/runtime/commands/feedback.js +184 -0
  288. package/dist/runtime/commands/hooks.js +184 -0
  289. package/dist/runtime/commands/init.js +254 -0
  290. package/dist/runtime/commands/lsp.js +368 -0
  291. package/dist/runtime/commands/mcp.js +879 -0
  292. package/dist/runtime/commands/memory.js +582 -0
  293. package/dist/runtime/commands/model.js +237 -0
  294. package/dist/runtime/commands/onboarding.js +275 -0
  295. package/dist/runtime/commands/patch.js +128 -0
  296. package/dist/runtime/commands/permissions.js +112 -0
  297. package/dist/runtime/commands/plan.js +143 -0
  298. package/dist/runtime/commands/prd-check.js +285 -0
  299. package/dist/runtime/commands/privacy.js +17 -17
  300. package/dist/runtime/commands/recipe.js +325 -0
  301. package/dist/runtime/commands/redo-blob-store.js +92 -0
  302. package/dist/runtime/commands/redo.js +361 -0
  303. package/dist/runtime/commands/release-notes.js +229 -0
  304. package/dist/runtime/commands/repo-map.js +95 -0
  305. package/dist/runtime/commands/report.js +299 -0
  306. package/dist/runtime/commands/resume.js +118 -0
  307. package/dist/runtime/commands/review-consensus.js +68 -53
  308. package/dist/runtime/commands/rewind.js +333 -0
  309. package/dist/runtime/commands/roster.js +14 -14
  310. package/dist/runtime/commands/sessions.js +163 -0
  311. package/dist/runtime/commands/share.js +316 -0
  312. package/dist/runtime/commands/skills.js +31 -31
  313. package/dist/runtime/commands/status.js +186 -0
  314. package/dist/runtime/commands/stickers.js +82 -0
  315. package/dist/runtime/commands/style.js +194 -0
  316. package/dist/runtime/commands/theme.js +196 -0
  317. package/dist/runtime/commands/undo.js +54 -22
  318. package/dist/runtime/commands/update.js +289 -0
  319. package/dist/runtime/commands/vim.js +140 -0
  320. package/dist/runtime/commands/worktree.js +177 -0
  321. package/dist/runtime/commands/worktrees.js +155 -0
  322. package/dist/runtime/headless-repl.js +195 -0
  323. package/dist/runtime/headless.js +543 -0
  324. package/dist/runtime/load-hooks-or-exit.js +71 -0
  325. package/dist/runtime/plan-decompose.js +531 -0
  326. package/dist/runtime/update-check.js +28 -28
  327. package/dist/runtime/version.js +65 -0
  328. package/dist/skills/bundled/batch.js +617 -0
  329. package/dist/skills/bundled/index.js +45 -0
  330. package/dist/skills/bundled/loop.js +358 -0
  331. package/dist/skills/bundled/remember.js +383 -0
  332. package/dist/skills/bundled/simplify.js +289 -0
  333. package/dist/skills/bundled/skillify.js +373 -0
  334. package/dist/skills/bundled/stuck.js +558 -0
  335. package/dist/skills/bundled/verify.js +439 -0
  336. package/dist/testing/vcr.js +486 -0
  337. package/dist/tools/agent-tool.js +229 -0
  338. package/dist/tools/apply-patch.js +556 -0
  339. package/dist/tools/ask-user-question.js +222 -0
  340. package/dist/tools/ask-user.js +115 -0
  341. package/dist/tools/bash.js +623 -45
  342. package/dist/tools/brief.js +224 -0
  343. package/dist/tools/enter-worktree.js +250 -0
  344. package/dist/tools/exit-worktree.js +147 -0
  345. package/dist/tools/file-tools.js +161 -44
  346. package/dist/tools/lsp-tools.js +189 -0
  347. package/dist/tools/mcp-tool.js +260 -0
  348. package/dist/tools/multi-edit.js +361 -0
  349. package/dist/tools/powershell.js +268 -0
  350. package/dist/tools/registry.js +85 -0
  351. package/dist/tools/skill-tool.js +96 -0
  352. package/dist/tools/sleep.js +99 -0
  353. package/dist/tools/synthetic-output.js +133 -0
  354. package/dist/tools/tasks.js +208 -0
  355. package/dist/tools/todo-write.js +184 -0
  356. package/dist/tools/verify-plan-execution.js +295 -0
  357. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  358. package/dist/tools/web-fetch.js +195 -10
  359. package/dist/tools/web-search.js +458 -0
  360. package/dist/tui/agent-progress-card.js +111 -0
  361. package/dist/tui/agent-tree.js +11 -1
  362. package/dist/tui/ask-modal.js +14 -14
  363. package/dist/tui/ask-user-question-prompt.js +203 -0
  364. package/dist/tui/compact-banner.js +81 -0
  365. package/dist/tui/conversation-pane.js +85 -11
  366. package/dist/tui/cost-table.js +111 -0
  367. package/dist/tui/device-flow.js +2 -2
  368. package/dist/tui/doctor-table.js +46 -0
  369. package/dist/tui/feedback-prompt.js +156 -0
  370. package/dist/tui/input-box.js +247 -32
  371. package/dist/tui/login-picker.js +3 -3
  372. package/dist/tui/markdown-render.js +6 -6
  373. package/dist/tui/onboarding-wizard.js +240 -0
  374. package/dist/tui/permissions-picker.js +86 -0
  375. package/dist/tui/render.js +35 -0
  376. package/dist/tui/repl-render.js +332 -54
  377. package/dist/tui/repl-splash-art.js +16 -16
  378. package/dist/tui/repl-splash-mascot.js +48 -24
  379. package/dist/tui/repl-splash.js +22 -22
  380. package/dist/tui/repl.js +124 -44
  381. package/dist/tui/slash-palette.js +6 -6
  382. package/dist/tui/splash.js +2 -2
  383. package/dist/tui/status-bar.js +109 -31
  384. package/dist/tui/status-table.js +7 -0
  385. package/dist/tui/stickers-art.js +136 -0
  386. package/dist/tui/style-table.js +28 -0
  387. package/dist/tui/theme-table.js +29 -0
  388. package/dist/tui/thinking-spinner.js +123 -0
  389. package/dist/tui/tool-stream-pane.js +53 -4
  390. package/dist/tui/update-banner.js +27 -2
  391. package/dist/tui/vim-input.js +267 -0
  392. package/dist/tui/welcome-banner.js +107 -0
  393. package/dist/tui/welcome-data.js +293 -0
  394. package/dist/tui/workspace-context.js +2 -2
  395. package/docs/examples/codegraph.mcp.json +10 -0
  396. package/package.json +23 -6
  397. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  398. package/test/scenarios/compact-force.scenario.txt +11 -0
  399. package/test/scenarios/identity.scenario.txt +11 -0
  400. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  401. package/test/scenarios/walkback.scenario.txt +12 -0
  402. package/dist/core/engine/compaction-hook.js +0 -154
@@ -1,29 +1,35 @@
1
1
  /**
2
- * Production REPL mount + transport - Sprint α5.7.
2
+ * Production REPL mount + transport - Sprint .
3
3
  *
4
4
  * Owns the Ink mount lifecycle for `<Repl />` and wires the real
5
5
  * fetch + SSE transport. The CLI dispatcher in `runtime/cli.ts` calls
6
6
  * `renderRepl` on the bare-`pugi` path when stdin / stdout are TTYs.
7
7
  *
8
8
  * The transport speaks to admin-api:
9
- * POST /api/pugi/sessions → { sessionId }
10
- * POST /api/pugi/sessions/:id/brief → { dispatchId }
11
- * POST /api/pugi/sessions/:id/stop → { stopped }
12
- * GET /api/pugi/sessions/:id/stream → text/event-stream
9
+ * POST /api/pugi/sessions → { sessionId }
10
+ * POST /api/pugi/sessions/:id/brief → { dispatchId }
11
+ * POST /api/pugi/sessions/:id/stop → { stopped }
12
+ * GET /api/pugi/sessions/:id/stream → text/event-stream
13
13
  *
14
14
  * SSE is parsed client-side from a streaming fetch response - Node 22
15
15
  * native fetch returns a WHATWG `ReadableStream` which we feed through
16
16
  * a tiny `event:`/`data:`/`id:` parser. This keeps the dependency
17
17
  * graph at zero new packages.
18
18
  */
19
+ import { existsSync } from 'node:fs';
20
+ import { resolve } from 'node:path';
19
21
  import React from 'react';
20
22
  import { render } from 'ink';
21
23
  import { Repl } from './repl.js';
22
24
  import { printPugMascotPreInk } from './repl-splash-mascot.js';
25
+ import { collectWelcomeData } from './welcome-data.js';
26
+ import { ThemeProvider } from '../core/theme/context.js';
27
+ import { resolveTheme } from '../core/theme/state.js';
23
28
  import { ReplSession, } from '../core/repl/session.js';
24
29
  import { resolveWorkspaceContext } from '../core/repl/workspace-context.js';
25
30
  import { SqliteSessionStore } from '../core/repl/store/index.js';
26
31
  import { slugForCwd } from '../core/repl/history.js';
32
+ import { loadSettings } from '../core/settings.js';
27
33
  import { WorkingSet, buildRepoSkeleton, loadPugiIgnore, PugiWatcher, } from '../core/context/index.js';
28
34
  /**
29
35
  * Mount the REPL and resolve when the user exits via Ctrl+C × 2 or
@@ -31,13 +37,86 @@ import { WorkingSet, buildRepoSkeleton, loadPugiIgnore, PugiWatcher, } from '../
31
37
  * `pugi resume <sessionId>` once that command exists).
32
38
  */
33
39
  export async function renderRepl(options) {
40
+ // beta.9 CEO dogfood: claim stdin raw mode + alt-screen
41
+ // BEFORE any async bootstrap step so keystrokes typed during the
42
+ // [launch -> Ink mount] window cannot echo into the terminal in
43
+ // cooked mode. Previously openLocalStore (SQLite open) +
44
+ // bootstrapContext (chokidar start) could take hundreds of ms to
45
+ // multiple seconds on a fresh install / large repo; during that
46
+ // window stdin stayed in cooked mode and the terminal echoed
47
+ // every typed character literally onto the screen below the
48
+ // pre-printed mascot/header. The visible result was the operator's
49
+ // "ssssss" landing on the rendered status-bar bottom row (CEO
50
+ // screenshot: beta.8 REPL bug 2).
51
+ //
52
+ // The claim is idempotent with Ink's own raw-mode enable: Ink
53
+ // ref-counts setRawMode calls, and Node's stdin.setRawMode is
54
+ // safe to call twice with the same value. The pre-Ink claim acts
55
+ // as a "raw-mode floor" - whatever Ink does after mount layers on
56
+ // top, and our finally{} restore drops the floor only after Ink
57
+ // has cleanly torn down (or never mounted on a bootstrap crash).
58
+ const bootstrap = claimTerminalForRepl();
59
+ // beta.13 auto-init wire (CEO dogfood): scaffold the
60
+ // `.pugi/` workspace silently on REPL boot so launching `pugi` in a
61
+ // fresh cwd no longer demands an explicit `pugi init` round-trip.
62
+ // Idempotent — every helper inside scaffoldPugiWorkspace is a
63
+ // `*_IfMissing` write, so re-running over an existing workspace is
64
+ // a no-op. Fail-safe: any FS / perms error never blocks REPL launch.
65
+ // Operator escape hatch: PUGI_NO_AUTO_INIT=1.
66
+ //
67
+ // Beta.13 P2 fix: gate the scaffold on project-root markers
68
+ // so launching `pugi` from `$HOME` / `/tmp` / arbitrary dirs does NOT
69
+ // sprinkle `.pugi/` directories all over the filesystem. The gate
70
+ // mirrors `isBoundWorkspace` from workspace-context.ts but also
71
+ // accepts non-JS roots (Cargo / pyproject / go.mod) because the CLI
72
+ // is language-agnostic and an operator working in a Rust repo deserves
73
+ // the same auto-init UX as a Node operator. Already-bound `.pugi/`
74
+ // dirs also opt back in so the scaffold can fill any missing
75
+ // sub-artifacts the operator deleted.
76
+ // `--bare` (PUGI_BARE=1) ALSO suppresses the
77
+ // auto-init scaffold. Bare mode is the deterministic "fresh install
78
+ // anywhere" path — no `.pugi/` writes, no PUGI.md scaffold, no
79
+ // settings.json seed. The pre-existing PUGI_NO_AUTO_INIT escape
80
+ // hatch stays — bare mode just unions with it.
81
+ const { isBareMode } = await import('../core/bare-mode/index.js');
82
+ if (process.env.PUGI_NO_AUTO_INIT !== '1' &&
83
+ !isBareMode() &&
84
+ isProjectRoot(process.cwd())) {
85
+ try {
86
+ const { scaffoldPugiWorkspace } = await import('../runtime/cli.js');
87
+ await scaffoldPugiWorkspace({
88
+ cwd: process.cwd(),
89
+ noDefaults: true,
90
+ log: () => {
91
+ /* silent — never leak scaffold progress into the REPL alt-screen */
92
+ },
93
+ });
94
+ }
95
+ catch (err) {
96
+ // Fail-safe: read-only FS or perms error never blocks REPL launch.
97
+ // Beta.13 P2 fix: bare-catch swallowed the diagnostic;
98
+ // surface it on stderr under PUGI_DEBUG=1 so operator-triage on
99
+ // "why isn't .pugi/ being created?" has a starting point without
100
+ // having to re-instrument the bootstrap.
101
+ if (process.env.PUGI_DEBUG === '1') {
102
+ const msg = err instanceof Error ? err.message : String(err);
103
+ process.stderr.write(`[pugi-debug] auto-init failed: ${msg}\n`);
104
+ }
105
+ }
106
+ }
34
107
  const transport = createProductionTransport();
35
- // Auto-bind the workspace context from process.cwd() so Mira knows
108
+ // Auto-bind the workspace context from process.cwd() so Pugi knows
36
109
  // which repo the operator launched the CLI in. The resolver is
37
110
  // best-effort — any FS error falls back to a basename-only summary,
38
- // never blocks REPL launch. Wave 4 fix 2026-05-25.
111
+ // never blocks REPL launch. fix.
39
112
  const workspace = options.workspace ?? resolveWorkspaceContext(process.cwd());
40
- // α6.4: open the local SessionStore for `/resume` persistence. The
113
+ // Beta.13 P1 fix: read `ui.cyberZoo` from
114
+ // `.pugi/settings.json` so the operator's splash posture flows to
115
+ // admin-api on session open. Without this, the renderer's `cyberZoo`
116
+ // parameter (added beta.13) was always defaulted to 'on' regardless
117
+ // of the operator's actual setting.
118
+ const cyberZoo = readCyberZooSetting(process.cwd());
119
+ // : open the local SessionStore for `/resume` persistence. The
41
120
  // store lives under `~/.pugi/projects/<slug>/`; failure is fail-safe
42
121
  // — we log a one-line warning to stderr and continue with the REPL
43
122
  // in memory-only mode. Lock-busy errors get the friendliest message
@@ -49,7 +128,7 @@ export async function renderRepl(options) {
49
128
  workspaceRoot: process.cwd(),
50
129
  resumeLocalSessionId: options.resumeLocalSessionId,
51
130
  });
52
- // α6.5 three-tier context bootstrap. The skeleton + working set
131
+ // three-tier context bootstrap. The skeleton + working set
53
132
  // + watcher are local-first and best-effort: every step is wrapped
54
133
  // in try/catch so an unreadable workspace never blocks REPL launch.
55
134
  // Opt-out via PUGI_DISABLE_CONTEXT=1 for hermetic test runs.
@@ -64,6 +143,7 @@ export async function renderRepl(options) {
64
143
  cliVersion: options.cliVersion,
65
144
  transport,
66
145
  workspace,
146
+ cyberZoo,
67
147
  store,
68
148
  localSessionId: openedSessionId,
69
149
  repoSkeleton: skeleton,
@@ -86,22 +166,15 @@ export async function renderRepl(options) {
86
166
  // Kick off the connect; the Repl renders the connecting state until
87
167
  // the session pushes `connection: 'on_watch'` from the SSE onOpen.
88
168
  void session.start();
89
- // α6.14.4 CEO dogfood 2026-05-25 (parity with Claude Code): enter
90
- // the terminal's alternate screen buffer so the REPL renders on a
91
- // fresh "screen" the operator cannot scroll above. On exit, leave
92
- // restores the previous terminal contents - the conversation does
93
- // not pollute the operator's shell history. Skipped under --no-tty
94
- // and when stdout is not a TTY (pipe/CI), where the escapes would
95
- // appear as literal characters.
96
- // ORDER MATTERS (beta.2 follow-up): alt-screen enter MUST happen
97
- // BEFORE the chafa mascot pre-print. Reversed, the alt-screen clear
98
- // wiped the freshly-painted pug and the operator saw nothing.
99
- const supportsAltScreen = process.stdout.isTTY === true;
100
- if (supportsAltScreen) {
101
- process.stdout.write('\x1b[?1049h');
102
- process.stdout.write('\x1b[H');
103
- }
104
- // α6.14.2 wave 5: paint the chafa-baked brand-pug ANSI render to
169
+ // beta.9: drain any keystrokes that landed in stdin between the
170
+ // pre-Ink raw-mode claim and now. Without this, the queued bytes
171
+ // would feed Ink's first useInput tick as a flood of "stale"
172
+ // characters once the InputBox mounts - the operator would see
173
+ // their pre-typed input materialise in the prompt as if they had
174
+ // typed it after the REPL became interactive. Idempotent: no-op
175
+ // when stdin is not a TTY or no bytes were buffered.
176
+ drainBufferedStdin(process.stdin);
177
+ // wave 5: paint the chafa-baked brand-pug ANSI render to
105
178
  // stdout BEFORE Ink mounts (but AFTER alt-screen enter). Ink's
106
179
  // layout engine would mis-measure the truecolor escape sequences,
107
180
  // so the pug must land verbatim. The flag is passed into <Repl />
@@ -111,33 +184,55 @@ export async function renderRepl(options) {
111
184
  // (operator opted out via --no-splash), we suppress the pre-print
112
185
  // too so the boot stays silent.
113
186
  const mascotPrePrinted = options.skipSplash === true ? false : printPugMascotPreInk(process.stdout);
114
- const instance = render(React.createElement(Repl, {
187
+ // resolve the active theme ONCE at mount
188
+ // and wrap `<Repl />` in `<ThemeProvider>` so every Ink consumer
189
+ // (`<Header>`, `<DoctorTable>`, `<StyleTable>`, `<ThemeTable>`,
190
+ // …) picks up the same color tokens. The provider is stable for
191
+ // the lifetime of the REPL — operator `/theme <name>` writes to
192
+ // disk + appends a system line, and the next `pugi` launch re-
193
+ // mounts with the new slug. Re-mounting mid-session would race
194
+ // against Ink's raw-mode handler so we deliberately keep the
195
+ // session-lifetime contract instead of polling the config file.
196
+ const resolvedTheme = resolveTheme({
197
+ workspaceRoot: process.cwd(),
198
+ env: process.env,
199
+ });
200
+ // CEO P0 #2 : collect welcome banner data BEFORE Ink
201
+ // mounts so the banner paints on the first frame instead of swapping
202
+ // in mid-render. The collector swallows every IO error so а missing
203
+ // CHANGELOG / unreadable credential / malformed settings never
204
+ // blocks the boot.
205
+ let welcomeData;
206
+ if (options.skipSplash !== true) {
207
+ try {
208
+ welcomeData = collectWelcomeData({
209
+ cliVersion: options.cliVersion,
210
+ cwd: process.cwd(),
211
+ });
212
+ }
213
+ catch {
214
+ welcomeData = undefined;
215
+ }
216
+ }
217
+ const instance = render(React.createElement(ThemeProvider, { slug: resolvedTheme.slug }, React.createElement(Repl, {
115
218
  session,
116
219
  updateBanner: options.updateBanner ?? null,
117
220
  skipSplash: options.skipSplash === true,
118
221
  hideToolStream: options.hideToolStream === true,
119
222
  mascotPrePrinted,
120
- }));
121
- const restoreAltScreen = () => {
122
- if (supportsAltScreen) {
123
- try {
124
- process.stdout.write('\x1b[?1049l');
125
- }
126
- catch {
127
- /* shutdown race — terminal already detached */
128
- }
129
- }
130
- };
223
+ welcomeData,
224
+ autoInitStatus: options.autoInitStatus ?? null,
225
+ })));
131
226
  // Make sure we leave the alt screen on abrupt exits too. Without
132
227
  // this the operator's shell stays "frozen" on the Pugi splash.
133
- process.once('exit', restoreAltScreen);
134
- process.once('SIGINT', restoreAltScreen);
135
- process.once('SIGTERM', restoreAltScreen);
228
+ process.once('exit', bootstrap.restore);
229
+ process.once('SIGINT', bootstrap.restore);
230
+ process.once('SIGTERM', bootstrap.restore);
136
231
  try {
137
232
  await instance.waitUntilExit();
138
233
  }
139
234
  finally {
140
- restoreAltScreen();
235
+ bootstrap.restore();
141
236
  session.close();
142
237
  if (store) {
143
238
  try {
@@ -157,6 +252,138 @@ export async function renderRepl(options) {
157
252
  }
158
253
  }
159
254
  }
255
+ export function claimTerminalForRepl(stdin = process.stdin, stdout = process.stdout) {
256
+ const isStdoutTty = stdout.isTTY === true;
257
+ const isStdinTty = stdin.isTTY === true && typeof stdin.setRawMode === 'function';
258
+ let altScreenEntered = false;
259
+ if (isStdoutTty) {
260
+ try {
261
+ stdout.write('\x1b[?1049h');
262
+ stdout.write('\x1b[H');
263
+ altScreenEntered = true;
264
+ }
265
+ catch {
266
+ /* terminal already detached */
267
+ }
268
+ }
269
+ let rawModeClaimed = false;
270
+ if (isStdinTty) {
271
+ try {
272
+ stdin.setEncoding('utf8');
273
+ stdin.setRawMode(true);
274
+ // Resume so the kernel actually delivers bytes to Node's event
275
+ // loop. Without resume, raw mode is set but data does not flow
276
+ // until something else (e.g. Ink) attaches a 'data' listener.
277
+ stdin.resume();
278
+ rawModeClaimed = true;
279
+ }
280
+ catch {
281
+ /* raw mode unsupported - the operator's shell still works */
282
+ }
283
+ }
284
+ let restored = false;
285
+ const restore = () => {
286
+ if (restored)
287
+ return;
288
+ restored = true;
289
+ if (rawModeClaimed && isStdinTty) {
290
+ try {
291
+ stdin.setRawMode(false);
292
+ }
293
+ catch {
294
+ /* terminal already detached */
295
+ }
296
+ }
297
+ if (altScreenEntered) {
298
+ try {
299
+ stdout.write('\x1b[?1049l');
300
+ }
301
+ catch {
302
+ /* shutdown race - terminal already detached */
303
+ }
304
+ }
305
+ };
306
+ return { altScreenEntered, rawModeClaimed, restore };
307
+ }
308
+ /**
309
+ * Read and discard any bytes buffered in stdin between
310
+ * `claimTerminalForRepl()` and the Ink mount. Returns the number of
311
+ * bytes drained so tests can assert the behaviour without intercepting
312
+ * the side effect.
313
+ *
314
+ * `stdin.read()` is a no-op when no data is buffered, so this is safe
315
+ * to call whether or not the operator actually typed during bootstrap.
316
+ * Wrapped in try/catch because a closed / piped stdin will throw on
317
+ * read in some Node versions.
318
+ */
319
+ export function drainBufferedStdin(stdin = process.stdin) {
320
+ if (stdin.isTTY !== true)
321
+ return 0;
322
+ try {
323
+ let bytesDrained = 0;
324
+ // Loop until read() returns null - readable streams may chunk
325
+ // buffered bytes across multiple read() calls when the operator
326
+ // typed faster than the kernel could deliver to Node's loop.
327
+ for (;;) {
328
+ const chunk = stdin.read();
329
+ if (chunk === null)
330
+ return bytesDrained;
331
+ bytesDrained += typeof chunk === 'string' ? chunk.length : chunk.byteLength;
332
+ }
333
+ }
334
+ catch {
335
+ return 0;
336
+ }
337
+ }
338
+ /**
339
+ * Project-root probe — beta.13 P2 fix.
340
+ *
341
+ * Beta.13 auto-init was unconditional and silently created `.pugi/` in
342
+ * every cwd the REPL was launched from, including `$HOME` and `/tmp`.
343
+ * Operators who ran `pugi` to ask a quick question outside of any
344
+ * project ended up with stray `.pugi/` directories polluting their
345
+ * filesystem. The gate looks for any of six project-root markers
346
+ * before scaffolding:
347
+ *
348
+ * - `package.json` — JS / TS workspaces
349
+ * - `.git` — any cloned repo regardless of language
350
+ * - `.pugi` — already-bound Pugi workspace (re-scaffold
351
+ * fills any missing artifacts the operator
352
+ * deleted, idempotent over existing files)
353
+ * - `Cargo.toml` — Rust crates
354
+ * - `pyproject.toml` — Python projects (PEP 518)
355
+ * - `go.mod` — Go modules
356
+ *
357
+ * The probe is six cheap `existsSync` calls; the cost is negligible
358
+ * compared with the alt-screen + Ink mount that follows. Exported so a
359
+ * future unit spec can lock the contract.
360
+ */
361
+ export function isProjectRoot(cwd) {
362
+ // ESM static imports — `require()` is not defined in a `"type": "module"`
363
+ // bundle and would throw `ReferenceError: require is not defined` the
364
+ // moment the REPL bootstrap calls this gate. Beta.16 P0 fix.
365
+ return (existsSync(resolve(cwd, 'package.json')) ||
366
+ existsSync(resolve(cwd, '.git')) ||
367
+ existsSync(resolve(cwd, '.pugi')) ||
368
+ existsSync(resolve(cwd, 'Cargo.toml')) ||
369
+ existsSync(resolve(cwd, 'pyproject.toml')) ||
370
+ existsSync(resolve(cwd, 'go.mod')));
371
+ }
372
+ /**
373
+ * Read the operator's cyber-zoo posture from `.pugi/settings.json`.
374
+ * Best-effort: when the file is missing / malformed, fall through to
375
+ * the historical 'on' default so the REPL never refuses to launch on
376
+ * a settings error. Beta.13 P1 fix.
377
+ */
378
+ function readCyberZooSetting(cwd) {
379
+ try {
380
+ const settings = loadSettings(cwd);
381
+ return settings.ui?.cyberZoo ?? 'on';
382
+ }
383
+ catch {
384
+ return 'on';
385
+ }
386
+ }
160
387
  /**
161
388
  * Open the local SessionStore for the REPL bootstrap. Returns
162
389
  * `{ store: null, openedSessionId: undefined }` on any error so the
@@ -194,11 +421,11 @@ async function openLocalStore(input) {
194
421
  }
195
422
  }
196
423
  /**
197
- * Bootstrap the α6.5 three-tier context primitives:
424
+ * Bootstrap the three-tier context primitives:
198
425
  *
199
- * - Tier 0: `RepoSkeleton` (~5KB ASCII tree + meta) for prompt injection.
200
- * - Tier 1: `WorkingSet` LRU bounded at 50 entries.
201
- * - Filewatch: chokidar started against cwd, ignore-filtered.
426
+ * - Tier 0: `RepoSkeleton` (~5KB ASCII tree + meta) for prompt injection.
427
+ * - Tier 1: `WorkingSet` LRU bounded at 50 entries.
428
+ * - Filewatch: chokidar started against cwd, ignore-filtered.
202
429
  *
203
430
  * The bootstrap is fail-safe: every primitive is wrapped so the REPL
204
431
  * still launches when (e.g.) chokidar refuses to start on a
@@ -246,15 +473,22 @@ async function bootstrapContext(input) {
246
473
  return { skeleton, workingSet, watcher };
247
474
  }
248
475
  /* ------------------------------------------------------------------ */
249
- /* Production transport */
476
+ /* Production transport */
250
477
  /* ------------------------------------------------------------------ */
251
- function createProductionTransport() {
478
+ export function createProductionTransport() {
252
479
  return {
253
- async createSession({ apiUrl, apiKey, workspace }) {
480
+ async createSession({ apiUrl, apiKey, workspace, cyberZoo }) {
254
481
  // Forward the workspace bundle in the POST body so admin-api can
255
- // surface `<workspace-context>` in Mira's prompt. Older admin-api
482
+ // surface `<workspace-context>` in Pugi's prompt. Older admin-api
256
483
  // builds ignore unknown fields, so this stays forward-compatible.
257
- // Wave 4 fix 2026-05-25.
484
+ // fix.
485
+ //
486
+ // Beta.13 P1 fix: also forward `cyberZoo` so admin-api
487
+ // can render Pugi's `<cyber-zoo>` marker matching the operator's
488
+ // `.pugi/settings.json::ui.cyberZoo` toggle instead of the
489
+ // historical 'on' default. Only included on the wire when set
490
+ // explicitly so a missing setting still survives older admin-api
491
+ // builds that do not declare the DTO field.
258
492
  const body = {};
259
493
  if (workspace?.workspaceCwd)
260
494
  body.workspaceCwd = workspace.workspaceCwd;
@@ -262,6 +496,8 @@ function createProductionTransport() {
262
496
  body.workspaceSlug = workspace.workspaceSlug;
263
497
  if (workspace?.workspaceSummary)
264
498
  body.workspaceSummary = workspace.workspaceSummary;
499
+ if (cyberZoo === 'on' || cyberZoo === 'off')
500
+ body.cyberZoo = cyberZoo;
265
501
  const response = await fetch(joinUrl(apiUrl, '/api/pugi/sessions'), {
266
502
  method: 'POST',
267
503
  headers: jsonHeaders(apiKey),
@@ -307,6 +543,31 @@ function createProductionTransport() {
307
543
  if (lastEventId) {
308
544
  headers['Last-Event-ID'] = lastEventId;
309
545
  }
546
+ // beta.9 CEO dogfood: hard timeout on the SSE
547
+ // handshake so a CDN/proxy that buffers the response (or an
548
+ // admin-api that accepted the route but never flushed headers)
549
+ // cannot freeze the REPL in `connecting` forever. The 5s budget
550
+ // is generous - admin-api routinely responds in <500ms when
551
+ // healthy - but tight enough that an operator who launched
552
+ // `pugi` and is staring at the screen will see the status flip
553
+ // to `reconnecting` instead of an indefinite hang. The
554
+ // AbortController bound to the fetch aborts the in-flight
555
+ // request when the timer fires, which surfaces as an
556
+ // `AbortError` and routes through the existing onError handler
557
+ // (which calls scheduleReconnect via the session). The timer
558
+ // is cleared the moment onOpen fires so a slow-but-eventually-
559
+ // successful handshake still works.
560
+ const handshakeDeadlineMs = 5_000;
561
+ const handshakeTimer = setTimeout(() => {
562
+ controller.abort();
563
+ // onError is called from the catch block below (the abort
564
+ // synthesises an AbortError that consumeSseStream / fetch
565
+ // will throw). No explicit onError call here - we let the
566
+ // catch path normalise the error message so the operator
567
+ // sees the consistent "SSE handshake timed out (5s)" prose
568
+ // through the same plumbing that surfaces every other
569
+ // transport failure.
570
+ }, handshakeDeadlineMs);
310
571
  void (async () => {
311
572
  try {
312
573
  const response = await fetch(url, {
@@ -320,6 +581,9 @@ function createProductionTransport() {
320
581
  if (!response.body) {
321
582
  throw new Error('SSE response has no body');
322
583
  }
584
+ // Handshake survived; cancel the deadline so a slow
585
+ // first-event stream does not get aborted later.
586
+ clearTimeout(handshakeTimer);
323
587
  onOpen();
324
588
  await consumeSseStream(response.body, onEvent);
325
589
  // Server closed the stream cleanly. Treat as an error so
@@ -329,19 +593,33 @@ function createProductionTransport() {
329
593
  onError(new Error('SSE stream ended'));
330
594
  }
331
595
  catch (error) {
332
- if (controller.signal.aborted)
596
+ clearTimeout(handshakeTimer);
597
+ if (controller.signal.aborted) {
598
+ // Distinguish operator-driven close (session.close())
599
+ // from the handshake-deadline abort. The session sets a
600
+ // `closed` flag before calling controller.abort(); the
601
+ // handshake-deadline abort fires while the session is
602
+ // still expecting onOpen. We cannot read session state
603
+ // from here, so we surface a single error class with a
604
+ // clear message - the session-side onError handler
605
+ // already short-circuits when `closed=true`.
606
+ onError(new Error(`SSE handshake timed out after ${handshakeDeadlineMs}ms`));
333
607
  return;
608
+ }
334
609
  onError(error instanceof Error ? error : new Error(String(error)));
335
610
  }
336
611
  })();
337
612
  return {
338
- close: () => controller.abort(),
613
+ close: () => {
614
+ clearTimeout(handshakeTimer);
615
+ controller.abort();
616
+ },
339
617
  };
340
618
  },
341
619
  };
342
620
  }
343
621
  /* ------------------------------------------------------------------ */
344
- /* SSE parser */
622
+ /* SSE parser */
345
623
  /* ------------------------------------------------------------------ */
346
624
  /**
347
625
  * Minimal SSE parser. Reads a UTF-8 stream of `id:` / `event:` / `data:`
@@ -405,7 +683,7 @@ async function consumeSseStream(body, onEvent) {
405
683
  }
406
684
  }
407
685
  /* ------------------------------------------------------------------ */
408
- /* Small helpers */
686
+ /* Small helpers */
409
687
  /* ------------------------------------------------------------------ */
410
688
  function jsonHeaders(apiKey) {
411
689
  return {
@@ -1,9 +1,9 @@
1
1
  /**
2
- * ASCII pug mascot for the REPL boot splash (α6.14 wave 3).
2
+ * ASCII pug mascot for the REPL boot splash .
3
3
  *
4
4
  * Hand-crafted at 9 rows × 20 columns to read as a pug at a single
5
5
  * glance — references the cyber-zoo hero glyph in
6
- * `apps/clawhost-web/public/brand/hero-pug.png`: blocky pug face with
6
+ * `apps/console-web/public/brand/hero-pug.png`: blocky pug face with
7
7
  * angular ear flaps on either side of the head, forehead crease,
8
8
  * angular cyan eyes (`◉`), smushed snout, undershot jaw, and a small
9
9
  * cyan circuit chip (`▐■▌`) on the lower-right cheek.
@@ -15,30 +15,30 @@
15
15
  * columns cyan (#3DA9FC, brandbook §05).
16
16
  *
17
17
  * Convention:
18
- * - PUG_MASCOT[i] = one row of the silhouette
19
- * - PUG_MASCOT_CYAN_MASK[i] = parallel boolean array, true => that
20
- * column renders cyan instead of gray
18
+ * - PUG_MASCOT[i] = one row of the silhouette
19
+ * - PUG_MASCOT_CYAN_MASK[i] = parallel boolean array, true => that
20
+ * column renders cyan instead of gray
21
21
  *
22
22
  * Both arrays MUST stay the same length and each mask row MUST be the
23
23
  * same length as the corresponding art row. A unit test enforces this.
24
24
  */
25
25
  /* eslint-disable no-irregular-whitespace */
26
26
  export const PUG_MASCOT = [
27
- ' ▄▀▀▀▄▄▄▀▀▀▄ ',
28
- ' █▄▄ ▄▄█ ',
29
- ' ▀▄▄▄▄▄▀ ',
30
- ' ',
31
- ' ▀▄ ▀█▀ ▄▀ ',
32
- ' █▀▀▀▀▀█ ',
33
- ' █▒▒▒▒▒█ ▐■▌ ',
34
- ' ▀▄▄▄▀ ',
35
- ' ',
27
+ ' ▄▀▀▀▄▄▄▀▀▀▄ ',
28
+ ' █▄▄ ▄▄█ ',
29
+ ' ▀▄▄▄▄▄▀ ',
30
+ ' ',
31
+ ' ▀▄ ▀█▀ ▄▀ ',
32
+ ' █▀▀▀▀▀█ ',
33
+ ' █▒▒▒▒▒█ ▐■▌ ',
34
+ ' ▀▄▄▄▀ ',
35
+ ' ',
36
36
  ];
37
37
  /**
38
38
  * Cyan accents are derived from the source characters so the art file
39
39
  * stays the single source of truth. Two glyph classes get colored:
40
- * - `◉` -> the two cyan eyes on row 3
41
- * - `▐■▌` -> the cyan chip cluster on row 6 (right cheek)
40
+ * - `◉` -> the two cyan eyes on row 3
41
+ * - `▐■▌` -> the cyan chip cluster on row 6 (right cheek)
42
42
  *
43
43
  * Everything else renders gray. The derivation runs at module load,
44
44
  * which keeps the mask trivially auditable from the source array.