@selesai/code 0.13.16 → 0.13.19

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 (479) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/agent-browser.ts +1 -2
  65. package/dist/extensions/package.json +0 -1
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/README.md +6 -5
  372. package/dist/extensions/pi-web-agent/src/backends/config.ts +12 -6
  373. package/dist/extensions/pi-web-agent/src/backends/factory.ts +2 -2
  374. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +4 -5
  375. package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +8 -8
  376. package/dist/extensions/pi-web-agent/src/extension.ts +3 -3
  377. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +2 -2
  378. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +14 -3
  379. package/dist/extensions/pi-zentui/README.md +17 -0
  380. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  381. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  382. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  384. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  385. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  386. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  387. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  388. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  389. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  390. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  391. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  392. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  393. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  394. package/dist/extensions/pi-zentui/package.json +7 -4
  395. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  396. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  397. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  398. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  399. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  400. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  401. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  402. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  403. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  404. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  405. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  406. package/dist/index.d.ts +1 -1
  407. package/dist/main.js +4 -1
  408. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  409. package/dist/modes/interactive/chat-viewport.js +27 -0
  410. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  411. package/dist/modes/interactive/components/assistant-message.js +30 -13
  412. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  413. package/dist/modes/interactive/components/custom-editor.js +39 -1
  414. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  415. package/dist/modes/interactive/components/index.d.ts +1 -1
  416. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  417. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  418. package/dist/modes/interactive/components/model-selector.js +19 -17
  419. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  420. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  421. package/dist/modes/interactive/components/settings-selector.js +128 -20
  422. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  423. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  424. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  425. package/dist/modes/interactive/components/status-indicator.js +10 -3
  426. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  427. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  428. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  429. package/dist/modes/interactive/components/tool-execution.js +35 -33
  430. package/dist/modes/interactive/components/trust-selector.js +2 -2
  431. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  432. package/dist/modes/interactive/interactive-mode.js +144 -80
  433. package/dist/modes/interactive/session-share.d.ts +15 -0
  434. package/dist/modes/interactive/session-share.js +183 -0
  435. package/dist/modes/interactive/theme/dark.json +2 -1
  436. package/dist/modes/interactive/theme/light.json +2 -1
  437. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  438. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  439. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  440. package/dist/modes/interactive/theme/theme-json.js +129 -0
  441. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  442. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  443. package/dist/modes/interactive/theme/theme.js +18 -119
  444. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  445. package/dist/modes/interactive/tui-renderer.js +65 -0
  446. package/dist/utils/exif-orientation.js +2 -3
  447. package/dist/utils/management-http.d.ts +24 -0
  448. package/dist/utils/management-http.js +53 -0
  449. package/dist/utils/text.d.ts +7 -0
  450. package/dist/utils/text.js +8 -0
  451. package/dist/utils/tools-manager.d.ts +1 -0
  452. package/dist/utils/tools-manager.js +43 -21
  453. package/docs/docs.json +0 -4
  454. package/package.json +6 -6
  455. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  456. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  457. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  458. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  459. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  460. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  461. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  462. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  463. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  464. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  465. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  466. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  467. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  468. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  469. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  470. package/dist/extensions/web-agent-onboarding.test.ts +0 -415
  471. package/dist/extensions/web-agent-onboarding.ts +0 -222
  472. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  473. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  474. package/dist/skills/implanger/SKILL.md +0 -70
  475. package/dist/skills/output/SKILL.md +0 -49
  476. package/dist/skills/planger/SKILL.md +0 -167
  477. package/dist/skills/workflow/SKILL.md +0 -95
  478. package/docs/workflows.md +0 -18
  479. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -16,18 +16,18 @@ One public tool, `web_explore`, that does bounded web research for Pi: search, f
16
16
 
17
17
  - **One tool.** `web_explore` handles direct links, discovery, HTTP reads, targeted headless rendering, source ranking, source-quality checks, and caveats internally.
18
18
  - **Reads the real content behind links.** Paste a GitHub, PDF, or YouTube URL and it pulls the actual thing (GitHub files/issues/PRs from the API, PDF text, YouTube transcripts), keyless. So "summarize this PDF" or "what does this repo do" works off the source, not the page shell.
19
- - **Six search backends.** DuckDuckGo (keyless default), SearXNG, Brave, You.com, Exa, Tavily.
19
+ - **Seven search backends.** TokenIn (default with an active account), DuckDuckGo (keyless otherwise), SearXNG, Brave, You.com, Exa, Tavily.
20
20
  - **Optional search fanout.** Query several backends at once, dedupe, and rank pages that more than one provider agreed on to the top. Off by default; flip it to `on` or `auto`.
21
21
  - **Honest by default.** Weak, narrow, blocked, or cautionary evidence gets flagged instead of dressed up as confidence.
22
22
  - **Bounded output.** `compact` / `preview` / `verbose` transcript modes.
23
- - **Zero-config to start.** Runs keyless out of the box (DuckDuckGo search, local browser, the built-in readers). Opt into hosted backends, fallback, search fanout, and per-tool output modes through config when you want more control.
23
+ - **Zero-config to start.** Without an active TokenIn account, runs keyless out of the box (DuckDuckGo search, local browser, the built-in readers). Opt into hosted backends, fallback, search fanout, and per-tool output modes through config when you want more control.
24
24
 
25
25
  ## Why pi-web-agent
26
26
 
27
27
  Compared to other web tooling for agents:
28
28
 
29
29
  - **Hands-off.** No curator, no browser windows to approve, no step that pops you out of your session. Ask `web_explore` once and the answer comes back with caveats. Nothing to babysit.
30
- - **Keyless by default.** Search, page reads, and the GitHub/PDF/YouTube readers all work with no API keys. Add hosted providers only when you want them.
30
+ - **Keyless without TokenIn.** Search, page reads, and the GitHub/PDF/YouTube readers all work with no API keys when TokenIn is not configured. Add hosted providers only when you want them.
31
31
  - **Bounded and honest.** Compact output by default, and it says when a read was not good enough instead of returning fake confidence.
32
32
 
33
33
  ## Install
@@ -63,11 +63,12 @@ If a pass comes back thin, call `web_explore` again with a narrower query.
63
63
 
64
64
  ## Backends
65
65
 
66
- Defaults are DuckDuckGo search, plain HTTP fetch, and local-browser headless. Switch providers from `/web-agent settings → Backends`. API keys stay in environment variables, never in config files.
66
+ Defaults are TokenIn search when an active TokenIn account exists, otherwise DuckDuckGo search; fetch remains plain HTTP and headless remains local-browser. Switch providers from `/web-agent settings → Backends`. API keys stay in environment variables, never in config files.
67
67
 
68
68
  | Backend | Role | Enable with |
69
69
  | --- | --- | --- |
70
- | DuckDuckGo | search (default) | nothing, keyless |
70
+ | TokenIn | search (default with an active account) | `/tokenin add` |
71
+ | DuckDuckGo | search (default without TokenIn) | nothing, keyless |
71
72
  | SearXNG | search (self-hosted) | base URL |
72
73
  | Brave | search (hosted) | `PI_WEB_AGENT_BRAVE_API_KEY` |
73
74
  | You.com | search (hosted) | `YDC_API_KEY` |
@@ -54,11 +54,17 @@ export type BackendConfigFile = {
54
54
  };
55
55
  };
56
56
 
57
- export const DEFAULT_BACKEND_CONFIG: BackendConfig = {
58
- search: { provider: 'tokenin', fallback: 'duckduckgo' },
59
- fetch: { provider: 'http' },
60
- headless: { provider: 'local-browser' }
61
- };
57
+ export function getDefaultBackendConfig(
58
+ hasActiveAccount: () => boolean = hasActiveTokenInAccount
59
+ ): BackendConfig {
60
+ return {
61
+ search: hasActiveAccount()
62
+ ? { provider: 'tokenin', fallback: 'duckduckgo' }
63
+ : { provider: 'duckduckgo' },
64
+ fetch: { provider: 'http' },
65
+ headless: { provider: 'local-browser' }
66
+ };
67
+ }
62
68
 
63
69
  function extractStringArray(value: unknown): string[] | undefined {
64
70
  if (!Array.isArray(value)) return undefined;
@@ -260,6 +266,6 @@ export function mergeBackendConfigLayers(
260
266
  fetch: mergeFetchConfig(merged.fetch, layer?.fetch),
261
267
  headless: { ...merged.headless, ...layer?.headless }
262
268
  }),
263
- DEFAULT_BACKEND_CONFIG
269
+ getDefaultBackendConfig()
264
270
  );
265
271
  }
@@ -13,7 +13,7 @@ import { createWebFetchTool } from '../tools/web-fetch.js';
13
13
  import { createWebSearchTool } from '../tools/web-search.js';
14
14
  import { readBraveKeyFromSettings } from './settings-reader.js';
15
15
  import type { SearchProviderName, WebFetchHeadlessResponse, WebFetchResponse, WebSearchResponse } from '../types.js';
16
- import { DEFAULT_BACKEND_CONFIG, type BackendConfig, usableSearchProviders } from './config.js';
16
+ import { getDefaultBackendConfig, type BackendConfig, usableSearchProviders } from './config.js';
17
17
  import { createSpecialContentResolver } from '../readers/resolver.js';
18
18
  import { createGithubReader } from '../readers/github-reader.js';
19
19
  import { createPdfReader } from '../readers/pdf-reader.js';
@@ -117,7 +117,7 @@ function withFetchFallback(
117
117
  }
118
118
 
119
119
  export function createBackendSet(
120
- config: BackendConfig = DEFAULT_BACKEND_CONFIG,
120
+ config: BackendConfig = getDefaultBackendConfig(),
121
121
  deps: BackendFactoryDeps = {}
122
122
  ): BackendSet {
123
123
  const createDuckDuckGoSearch = deps.createDuckDuckGoSearch ?? createWebSearchTool;
@@ -1,8 +1,7 @@
1
- // ponytail: selesai stores the Brave API key in settings.json (webAgent.braveApiKey),
2
- // set by the bundled web-agent-onboarding extension. Upstream pi-web-agent reads the
3
- // key only from PI_WEB_AGENT_BRAVE_API_KEY env var. This helper bridges that gap with a
4
- // minimal settings.json read. Ceiling: if selesai moves web search config elsewhere, this
5
- // helper needs updating; upgrade path = upstream pi-web-agent adopting a settings hook.
1
+ // ponytail: preserve existing manually configured Brave keys in settings.json
2
+ // (webAgent.braveApiKey). Upstream pi-web-agent reads only PI_WEB_AGENT_BRAVE_API_KEY.
3
+ // Ceiling: if selesai moves web search config elsewhere, update this helper; upgrade path =
4
+ // upstream pi-web-agent adopting a settings hook.
6
5
  import { existsSync, readFileSync } from 'node:fs';
7
6
  import { join } from 'node:path';
8
7
  import { getAgentDir, getSettingsPath } from '@selesai/code';
@@ -1,5 +1,5 @@
1
1
  import {
2
- DEFAULT_BACKEND_CONFIG,
2
+ getDefaultBackendConfig,
3
3
  mergeBackendConfigLayers,
4
4
  validateBackendConfig,
5
5
  usableSearchProviders,
@@ -150,7 +150,7 @@ function formatFetchOptions(config: BackendConfig['fetch']) {
150
150
  ].filter(Boolean).join(' ');
151
151
  }
152
152
 
153
- function formatBackendSummary(config: BackendConfig = DEFAULT_BACKEND_CONFIG) {
153
+ function formatBackendSummary(config: BackendConfig = getDefaultBackendConfig()) {
154
154
  const searchSuffix = formatSearchOptions(config.search);
155
155
  const fetchSuffix = formatFetchOptions(config.fetch);
156
156
  const searchBase = config.search.baseUrl
@@ -394,10 +394,10 @@ export function getInheritedBackendsForScope(
394
394
  scope: PresentationScope
395
395
  ): BackendConfig {
396
396
  if (scope === 'global') {
397
- return DEFAULT_BACKEND_CONFIG;
397
+ return getDefaultBackendConfig();
398
398
  }
399
399
 
400
- return mergeBackendConfigLayers(DEFAULT_BACKEND_CONFIG, loaded.global.rawBackends);
400
+ return mergeBackendConfigLayers(getDefaultBackendConfig(), loaded.global.rawBackends);
401
401
  }
402
402
 
403
403
  export function getScopeDisplayBackends(
@@ -405,11 +405,11 @@ export function getScopeDisplayBackends(
405
405
  scope: PresentationScope
406
406
  ): BackendConfig {
407
407
  if (scope === 'global') {
408
- return mergeBackendConfigLayers(DEFAULT_BACKEND_CONFIG, loaded.global.rawBackends);
408
+ return mergeBackendConfigLayers(getDefaultBackendConfig(), loaded.global.rawBackends);
409
409
  }
410
410
 
411
411
  return mergeBackendConfigLayers(
412
- DEFAULT_BACKEND_CONFIG,
412
+ getDefaultBackendConfig(),
413
413
  loaded.global.rawBackends,
414
414
  loaded.project.rawBackends
415
415
  );
@@ -942,7 +942,7 @@ export function registerWebAgentConfigCommands(pi: ExtensionAPI, deps: CommandDe
942
942
 
943
943
  if (action === 'doctor') {
944
944
  const [typeboxOk, browser, loaded] = await Promise.all([checkTypebox(), resolveBrowser(), load()]);
945
- const backendConfig = loaded.effectiveBackends ?? DEFAULT_BACKEND_CONFIG;
945
+ const backendConfig = loaded.effectiveBackends ?? getDefaultBackendConfig();
946
946
  const backendIssues = validateBackendConfig(backendConfig);
947
947
  const backendHealth = await checkBackends(backendConfig);
948
948
  const lines = [
@@ -971,7 +971,7 @@ export function registerWebAgentConfigCommands(pi: ExtensionAPI, deps: CommandDe
971
971
  ctx.ui.notify(
972
972
  [
973
973
  formatConfigSummary(loaded.effectiveConfig),
974
- formatBackendSummary(loaded.effectiveBackends ?? DEFAULT_BACKEND_CONFIG),
974
+ formatBackendSummary(loaded.effectiveBackends ?? getDefaultBackendConfig()),
975
975
  `global: ${loaded.global.path}${loaded.global.exists ? '' : ' (missing)'}`,
976
976
  `project: ${loaded.project.path}${loaded.project.exists ? '' : ' (missing)'}`
977
977
  ].join('\n'),
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from '@selesai/code';
2
- import { DEFAULT_BACKEND_CONFIG, type BackendConfig } from './backends/config.js';
2
+ import { getDefaultBackendConfig, type BackendConfig } from './backends/config.js';
3
3
  import { Type } from 'typebox';
4
4
  import { registerWebAgentConfigCommands } from './commands/web-agent-config.js';
5
5
  import { DEFAULT_PRESENTATION_CONFIG, resolvePresentationMode } from './presentation/config.js';
@@ -60,9 +60,9 @@ async function getEffectivePresentationConfig(pi: ExtensionAPI): Promise<Present
60
60
  async function getEffectiveBackendConfig(pi: ExtensionAPI): Promise<BackendConfig> {
61
61
  try {
62
62
  const loaded = await loadWebAgentConfig(pi);
63
- return loaded.effectiveBackends ?? DEFAULT_BACKEND_CONFIG;
63
+ return loaded.effectiveBackends ?? getDefaultBackendConfig();
64
64
  } catch {
65
- return DEFAULT_BACKEND_CONFIG;
65
+ return getDefaultBackendConfig();
66
66
  }
67
67
  }
68
68
 
@@ -2,7 +2,7 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { CONFIG_DIR_NAME, getAgentDir } from '@selesai/code';
4
4
  import {
5
- DEFAULT_BACKEND_CONFIG,
5
+ getDefaultBackendConfig,
6
6
  extractBackendConfigOverride,
7
7
  mergeBackendConfigLayers,
8
8
  type BackendConfig,
@@ -162,7 +162,7 @@ export async function loadPresentationConfigLayers(
162
162
  project.rawConfig
163
163
  ),
164
164
  effectiveBackends: mergeBackendConfigLayers(
165
- DEFAULT_BACKEND_CONFIG,
165
+ getDefaultBackendConfig(),
166
166
  global.rawBackends,
167
167
  project.rawBackends
168
168
  )
@@ -6,6 +6,7 @@ import type { SearchResult, WebSearchResponse } from '../types.js';
6
6
 
7
7
  export const TOKENIN_SEARCH_TOOL_NAME = 'firecrawl';
8
8
  export const TOKENIN_DEFAULT_BASE_URL = 'https://lite.andlet.me/v1';
9
+ const TOKENIN_SEARCH_TIMEOUT_MS = 10_000;
9
10
 
10
11
  type TokenInAccount = { id: string; label: string; apiKey: string; baseUrl?: string };
11
12
  type TokenInAuth = { accounts: TokenInAccount[]; activeId: string | null };
@@ -54,10 +55,12 @@ function normalizeResults(response: LiteLLMSearchResponse): SearchResult[] {
54
55
 
55
56
  export function createTokenInSearchTool({
56
57
  readAccount = readActiveTokenInAccount,
57
- fetchImpl = fetch
58
+ fetchImpl = fetch,
59
+ timeoutMs = TOKENIN_SEARCH_TIMEOUT_MS
58
60
  }: {
59
61
  readAccount?: () => TokenInAccount | undefined;
60
62
  fetchImpl?: typeof fetch;
63
+ timeoutMs?: number;
61
64
  } = {}) {
62
65
  return async function tokenInSearch({ query }: { query: string }): Promise<WebSearchResponse> {
63
66
  const normalizedQuery = query.trim();
@@ -89,6 +92,9 @@ export function createTokenInSearchTool({
89
92
  .replace(/\/v1$/, '');
90
93
  const url = `${rootUrl}/v1/search/${TOKENIN_SEARCH_TOOL_NAME}`;
91
94
 
95
+ const controller = new AbortController();
96
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
97
+
92
98
  try {
93
99
  const response = await fetchImpl(url, {
94
100
  method: 'POST',
@@ -97,7 +103,8 @@ export function createTokenInSearchTool({
97
103
  'Content-Type': 'application/json',
98
104
  Authorization: `Bearer ${account.apiKey}`
99
105
  },
100
- body: JSON.stringify({ query: normalizedQuery, max_results: 10 })
106
+ body: JSON.stringify({ query: normalizedQuery, max_results: 10 }),
107
+ signal: controller.signal
101
108
  });
102
109
 
103
110
  if (!response.ok) {
@@ -122,13 +129,17 @@ export function createTokenInSearchTool({
122
129
  metadata: { backend: 'tokenin', cacheHit: false }
123
130
  });
124
131
  } catch (error) {
125
- const rawMessage = error instanceof Error ? error.message : String(error);
132
+ const rawMessage = controller.signal.aborted
133
+ ? `Token-In search timed out after ${timeoutMs}ms.`
134
+ : error instanceof Error ? error.message : String(error);
126
135
  return resultWithPresentation({
127
136
  status: 'error',
128
137
  results: [],
129
138
  metadata: { backend: 'tokenin', cacheHit: false },
130
139
  error: { code: 'FETCH_FAILED', message: `Token-In search request failed: ${rawMessage}` }
131
140
  });
141
+ } finally {
142
+ clearTimeout(timeout);
132
143
  }
133
144
  };
134
145
  }
@@ -37,6 +37,8 @@ Editor, User messages, Thinking (Experimental), Working line, and selector borde
37
37
 
38
38
  The Starship Footer shows directory, Git, runtime, context, tokens, and cost. Optional segments include model/provider, package version, session duration, `user@host`, time, OS, Git commit, Git metrics, and third-party extension statuses. The layout is segment-driven by default and supports a complete Starship-style format template.
39
39
 
40
+ When the Working line is enabled, third-party extensions can publish keyed text segments through Pi's shared event bus. Zentui composes those segments into the owned row so Classic and KITT animate across them with the built-in content. See the [Working-line extension integration](https://github.com/lmilojevicc/pi-zentui/blob/main/docs/configuration.md#working-line-extension-integration) reference.
41
+
40
42
  Zentui detects a broad set of runtime and language modules, preserves Nerd Font icons with an ASCII mode, and can source colors from the active Pi theme or directly from the terminal palette.
41
43
 
42
44
  ## Screenshots
@@ -95,6 +97,21 @@ Run `/zentui` inside Pi to configure Appearance, Editor, User messages, Thinking
95
97
  ~/.pi/agent/zentui.json
96
98
  ```
97
99
 
100
+ ### Component presets
101
+
102
+ Choose **Appearance → Preset** in `/zentui`, or run `/zentui preset <id>`:
103
+
104
+ | ID | Editor (enabled) | Footer | User messages |
105
+ | --- | --- | --- | --- |
106
+ | `opencode` | Opencode | Starship | Framed |
107
+ | `opencode-copy-friendly` | Opencode (copy-friendly) | Starship | Framed (copy-friendly) |
108
+ | `rail` | Accent Rail | Starship | Compact (accent rail) |
109
+ | `minimalist` | Minimalist | Hidden (zero rows) | Zentui styling disabled |
110
+
111
+ Presets apply once, saving only these component selections. Colors, color sources, style options, icons, Footer segments/formats, selector borders, Working line, and Thinking remain unchanged. Minimalist preserves the dormant message style and leaves Pi's native message presentation alone. Hidden suppresses the Footer; it is not Native.
112
+
113
+ The displayed preset is derived from your current selections: individual changes may show **Custom**, and returning to a matching combination restores its name. No `preset` config key is saved or reapplied at startup. Defaults are unchanged and match Opencode. Selecting a preset keeps settings open for further adjustments. Editor installation waits until the panel closes; if editor ownership prevents application, the saved choice may require reloading Pi.
114
+
98
115
  A small starter config:
99
116
 
100
117
  ```json
@@ -8,7 +8,7 @@ Zentui reads optional user configuration from `~/.pi/agent/zentui.json`. Missing
8
8
 
9
9
  The interactive `/zentui` menu is split into nine component-oriented sections. Use `Tab` and `Shift+Tab` to switch sections:
10
10
 
11
- 1. **Appearance** — selector-border enablement, style, and colors; icon mode.
11
+ 1. **Appearance** — component Preset; selector-border enablement, style, and colors; icon mode.
12
12
  2. **Editor** — enablement, style, colors, model label, border behavior, viewport indicators, settings for the selected editor style, and a static synthetic preview.
13
13
  3. **User messages** — enablement, style, colors, and a static synthetic Markdown preview.
14
14
  4. **Thinking (Experimental)** — private Rail, Tree, or Streaming rendering; active Streaming can switch live to Rail or Tree, Rail and Tree can switch live between each other, and the private renderer may break after Pi updates.
@@ -45,6 +45,44 @@ Useful slash-command shortcuts:
45
45
 
46
46
  `footer`, `statusline`, `status`, and `status line` are aliases. Enable selects Starship, disable selects Native, and toggle selects Native only from Starship; Native or Hidden toggle to Starship.
47
47
 
48
+ ### Component presets
49
+
50
+ Use the first **Appearance → Preset** row, or one of these exact commands:
51
+
52
+ ```text
53
+ /zentui preset opencode
54
+ /zentui preset opencode-copy-friendly
55
+ /zentui preset rail
56
+ /zentui preset minimalist
57
+ ```
58
+
59
+ | Preset ID | `components.editor` | `components.footer.style` | `components.userMessages` |
60
+ | --- | --- | --- | --- |
61
+ | `opencode` | `enabled: true`, `style: "opencode"` | `"starship"` | `enabled: true`, `style: "framed"` |
62
+ | `opencode-copy-friendly` | `enabled: true`, `style: "opencode-copy-friendly"` | `"starship"` | `enabled: true`, `style: "framed-copy-friendly"` |
63
+ | `rail` | `enabled: true`, `style: "accent-rail"` | `"starship"` | `enabled: true`, `style: "compact"` |
64
+ | `minimalist` | `enabled: true`, `style: "minimalist"` | `"hidden"` | `enabled: false` (style preserved) |
65
+
66
+ These are one-time, atomic selection patches, not ongoing profiles. Only the listed leaves are written, with the existing obsolete copy-friendly/Footer-enabled migration flags removed when their styles are explicitly selected. Colors and color sources, per-style options, icons, Footer segments/templates/path settings, all other components, and unknown fields remain untouched. Unsupported future style IDs are preserved unless the preset explicitly replaces that style; Minimalist even preserves an unsupported dormant message style. Existing legacy option resolution continues to work.
67
+
68
+ **Custom** is a derived display state, not a selectable preset or a saved key. Matching checks only the listed selection leaves, not colors/options or current runtime ownership; Minimalist ignores dormant message style. Unsupported active selected styles do not match. Hand-editing or individually changing a selection can show Custom; returning to a matching combination restores the preset label. No `preset` config key is used, and nothing is automatically reapplied on startup. New-install defaults remain unchanged and match Opencode.
69
+
70
+ The existing Minimalist editor stays enabled with its saved metadata/options. Disabled User messages releases only Zentui styling, leaving native or predecessor rendering intact. Hidden installs an owned zero-row Footer, whereas Native releases Zentui's Footer to Pi or a predecessor; `/zentui statusline disable` still selects Native, not Hidden.
71
+
72
+ Selecting presets keeps the settings panel open and preserves its focus. Config, Footer, and User messages update immediately; editor installation waits until the panel closes and Pi restores the draft. Only the latest editor settings are reconciled on exit, and shutdown cancels pending installation. With public editor-text APIs available, opening settings expands nonempty drafts before Pi snapshots them, preserving collapsed paste contents; this can move the cursor to the end and add an undo step. Empty drafts are untouched. Older hosts without these APIs retain Pi's existing draft-restoration behavior. Saves fail without changing active settings or overwriting corrupt/unreadable JSON. Live application reconciles only Editor, User messages, Footer, and dependent timers. Editor ownership restrictions are reported as saved-but-not-applied/reload-required; Footer host failures retain existing fail-open behavior. Direct preset commands also save in non-TUI modes without installing TUI components. Unknown IDs, missing IDs, and extra arguments do not change settings.
73
+
74
+ ### Extension-status hyperlinks
75
+
76
+ An extension's **Original** color mode preserves SGR styling and HTTP/HTTPS
77
+ OSC 8 hyperlinks supplied by that extension. For example, set
78
+ `components.footer.styles.starship.extensionStatuses.colorModes.github-pr` to
79
+ `"original"` to retain a PR link from a GitHub status extension. Open it with
80
+ your terminal's link-opening gesture. Other URL schemes and unrelated terminal
81
+ controls (including clipboard, title, and cursor commands) are removed.
82
+
83
+ Zentui color mode continues to show plain status text. Zentui does not infer a
84
+ URL when an extension supplies only a label.
85
+
48
86
  ## Complete default configuration
49
87
 
50
88
  Copy this example and change only the values you need. Optional editor source-aware overrides such as `editorRail`, `editorGitBranch`, and `editorThinkingMax` are intentionally omitted.
@@ -428,7 +466,38 @@ Classic and KITT move color across message and segments. **Animate spinner color
428
466
 
429
467
  Both speeds accept `30..1000` ms. Classic/KITT combine both cadences through one Pi Loader interval; exact cycles are used within 1024-frame/512-KiB limits. Pathological custom pairs use a bounded evenly distributed schedule with at most half a spinner-cycle and half a text-step rounding. Legacy `intervalMs` is accepted only as migration input for `spinnerIntervalMs` when the canonical field is absent.
430
468
 
431
- Content reserves the complete Tokens label first, then Message, Thought, Elapsed, and Tool allocation, while preserving visual order **Message · Tool · Elapsed · Thought · Tokens** within the 80-column Loader-row contract. Active thought starts as `thinking 0s`; completed positive thought becomes `thought for Ns`. Rebuilds preserve spinner and visible color phase. Pi's working-row APIs are global and unkeyed, so another extension may win by writing last.
469
+ Content reserves the complete Tokens label and active extension segments first, then Message, Thought, Elapsed, and Tool allocation, while preserving visual order **Message · Tool · Elapsed · Thought · Tokens · Extensions** within the 80-column Loader-row contract. Active thought starts as `thinking 0s`; completed positive thought becomes `thought for Ns`. Rebuilds preserve spinner and visible color phase. Pi's working-row APIs are global and unkeyed, so another extension may win by writing last.
470
+
471
+ ### Working-line extension integration
472
+
473
+ Third-party extensions can add dynamic text to Zentui's owned Working line through Pi's shared event bus. Protocol version 1 uses keyed segments that are sanitized, ordered by key, width-bounded, and included in the same Classic/KITT animation frames as Zentui's built-in content.
474
+
475
+ Probe the capability when an interaction starts so an extension can fall back to Pi's public `setWorkingMessage()` slot when Zentui's Working line is unavailable:
476
+
477
+ ```typescript
478
+ const capability = { supported: false, active: false };
479
+ pi.events.emit("zentui:working-line-segment-capability", capability);
480
+
481
+ if (capability.active) {
482
+ pi.events.emit("zentui:working-line-segment", {
483
+ key: "@scope/my-extension:throughput",
484
+ text: "24.3 tok/s · TTFT 820ms",
485
+ });
486
+ }
487
+ ```
488
+
489
+ Update a segment by emitting the same key with new text. Remove it when the interaction settles or the publishing extension shuts down:
490
+
491
+ ```typescript
492
+ pi.events.emit("zentui:working-line-segment", {
493
+ key: "@scope/my-extension:throughput",
494
+ text: undefined,
495
+ });
496
+ ```
497
+
498
+ All publishers share one global key namespace. Collisions are last-update-wins, and removal by either publisher removes the value for that key. Publishers must therefore use stable, package-qualified keys such as `@scope/package:segment`; each publisher owns removal and lifecycle cleanup for its keys. `text: ""` also removes a segment. Published state is scoped to the current session and Working-row ownership: Zentui discards it on a new session, disable, shutdown, or ownership release. Positive updates while capability is inactive are ignored rather than retained, so publishers must probe again and republish their current value after capability becomes active.
499
+
500
+ Zentui accepts at most 16 unique keys, keys up to 64 code units, and values up to 256 code units. Extra segments are omitted or truncated when the complete row reaches its fixed width. `supported` reports whether this Zentui version understands the protocol. Zentui also adds `version: 1` to the mutable capability response; probes that initialize only `supported` and `active`, as above, remain compatible. `active` additionally requires an enabled Working line in an active TUI session where Zentui successfully installed and still claims both required Pi working-row surfaces. Pi's unkeyed, last-writer-wins APIs provide no way to prove that another extension has not overwritten a surface after installation, so publishers should probe at each interaction and retain their normal fallback.
432
501
 
433
502
  ## Git status icons
434
503
 
@@ -24,6 +24,7 @@ import {
24
24
  type ResolvedIcons,
25
25
  resolveConfiguredIcons,
26
26
  } from "./icons";
27
+ import type { ComponentPreset } from "./presets";
27
28
  import { isSupportedColorSpec } from "./style";
28
29
  import { normalizeWorkingLineMessages } from "./working-line";
29
30
  import { PI_WORKING_LINE_MESSAGES } from "./working-line-messages";
@@ -1651,6 +1652,16 @@ function deleteLegacyMessageCopyFriendly(record: ConfigRecord): void {
1651
1652
  if (isRecord(framed)) delete framed.copyFriendly;
1652
1653
  }
1653
1654
 
1655
+ /** A preset is a one-time, selection-only transaction; do not normalize unrelated leaves. */
1656
+ export function saveComponentPreset(preset: ComponentPreset, path = configPath): PolishedTuiConfig {
1657
+ return mutateConfig(path, (record) => {
1658
+ record.components = overlayKnown(record.components, preset.components);
1659
+ deleteLegacyEditorCopyFriendly(record);
1660
+ deleteLegacyFooterEnabled(record);
1661
+ if (preset.components.userMessages.style !== undefined) deleteLegacyMessageCopyFriendly(record);
1662
+ });
1663
+ }
1664
+
1654
1665
  function applyEditorComponentPatch(
1655
1666
  component: EditorComponentConfig,
1656
1667
  patch: Partial<
@@ -19,6 +19,20 @@ export type EditorTransferResult =
19
19
  | { ok: true }
20
20
  | { ok: false; reason: EditorTransferFailureReason };
21
21
 
22
+ /**
23
+ * Pi custom UI saves raw editor text and restores it with setText(), which clears
24
+ * collapsed paste storage. Expand nonempty drafts before that snapshot is taken.
25
+ * Hosts without public text APIs retain their existing custom-UI behavior.
26
+ */
27
+ export function prepareEditorTextForCustomUi(
28
+ ui: Pick<EditorTransferUi, "getEditorText" | "setEditorText">,
29
+ ): void {
30
+ if (typeof ui.getEditorText !== "function" || typeof ui.setEditorText !== "function") return;
31
+ const text = ui.getEditorText();
32
+ if (typeof text !== "string") throw new Error("expanded editor text could not be read safely");
33
+ if (text) ui.setEditorText(text);
34
+ }
35
+
22
36
  /**
23
37
  * Prepare the active editor's public expanded text before Pi replaces its factory.
24
38
  * This intentionally transfers prompt contents only; editor-private state is never inspected.
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { stripVTControlCharacters } from "node:util";
2
3
  import type { ExtensionStatusColorMode, ExtensionStatusPlacement, ZentuiConfig } from "./config";
3
4
  import {
@@ -19,9 +20,6 @@ export type ExtensionStatusSegmentsByPlacement = {
19
20
  right: ExtensionStatusSegment[];
20
21
  };
21
22
 
22
- const safeSgrPattern = /\x1b\[[0-9;:]*m/g;
23
- const sgrPlaceholderPattern = /__ZENTUI_SGR_(\d+)__/g;
24
-
25
23
  function compareKeys(a: ExtensionStatusSegment, b: ExtensionStatusSegment): number {
26
24
  return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
27
25
  }
@@ -43,18 +41,36 @@ function hasVisibleStatusText(value: string): boolean {
43
41
  }
44
42
 
45
43
  export function sanitizeExtensionStatusOriginalText(value: string): string {
46
- const safeSequences: string[] = [];
47
- const protectedValue = value.replace(safeSgrPattern, (sequence) => {
48
- const index = safeSequences.push(sequence) - 1;
49
- return `__ZENTUI_SGR_${index}__`;
50
- });
44
+ // Preserve only SGR and HTTP(S) OSC 8 links. Never pass title/clipboard/cursor controls.
45
+ const marker = `__ZENTUI_${randomUUID()}_`;
46
+ const sequences: Array<{ sequence: string; url: string | undefined }> = [];
47
+ const protectedValue = value.replace(
48
+ /\x1b\[[0-9;:]*m|\x1b\]8;[^;\x07\x1b]*;([^\x07\x1b]*)(?:\x07|\x1b\\)/g,
49
+ (sequence, url: string | undefined) => `${marker}${sequences.push({ sequence, url }) - 1}__`,
50
+ );
51
51
  const cleaned = normalizeStatusWhitespace(stripVTControlCharacters(protectedValue));
52
- const restored = cleaned.replace(sgrPlaceholderPattern, (_match, indexText: string) => {
53
- const index = Number.parseInt(indexText, 10);
54
- return safeSequences[index] ?? "";
52
+ // Other controls can swallow placeholders. Track only sequences that survive
53
+ // stripping, including invalid targets that must end any surviving link.
54
+ let activeLink = false;
55
+ const restored = cleaned.replace(new RegExp(`${marker}(\\d+)__`, "g"), (_match, index) => {
56
+ const entry = sequences[Number(index)];
57
+ if (!entry) return "";
58
+ const { sequence, url } = entry;
59
+ if (url === undefined) return sequence;
60
+ const close = activeLink ? "\x1b]8;;\x07" : "";
61
+ activeLink = false;
62
+ if (!url || /[\s\x00-\x1f\x7f-\x9f]/.test(url)) return close;
63
+ try {
64
+ const parsed = new URL(url);
65
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return close;
66
+ activeLink = true;
67
+ return `${close}\x1b]8;;${parsed.href}\x07`;
68
+ } catch {
69
+ return close;
70
+ }
55
71
  });
56
-
57
- return hasVisibleStatusText(restored) ? restored : "";
72
+ const result = restored + (activeLink ? "\x1b]8;;\x07" : "");
73
+ return hasVisibleStatusText(result) ? result : "";
58
74
  }
59
75
 
60
76
  export function collectExtensionStatusSegments(
@@ -1,6 +1,7 @@
1
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
1
+ import { visibleWidth } from "@earendil-works/pi-tui";
2
2
  import type { CompactFooterMaxLines } from "./config";
3
3
  import type { CompactBoundaryKind } from "./footer-format";
4
+ import { truncateFooterText } from "./footer-text";
4
5
 
5
6
  export type FooterZones = {
6
7
  left: string;
@@ -59,7 +60,7 @@ function fitChunk(chunk: string, innerWidth: number): PackedRow {
59
60
  return { text: chunk, endsWithRendererEllipsis: false };
60
61
  }
61
62
  return {
62
- text: truncateToWidth(chunk, innerWidth, "…"),
63
+ text: truncateFooterText(chunk, innerWidth, "…"),
63
64
  endsWithRendererEllipsis: true,
64
65
  };
65
66
  }
@@ -68,7 +69,7 @@ function appendOmissionMarker(row: PackedRow, innerWidth: number): PackedRow {
68
69
  if (row.endsWithRendererEllipsis) return row;
69
70
  if (innerWidth <= 1) return { text: "…", endsWithRendererEllipsis: true };
70
71
  return {
71
- text: `${truncateToWidth(row.text, innerWidth - 1, "")}…`,
72
+ text: `${truncateFooterText(row.text, innerWidth - 1, "")}…`,
72
73
  endsWithRendererEllipsis: true,
73
74
  };
74
75
  }
@@ -118,5 +119,5 @@ export function packCompactChunks(
118
119
  }
119
120
 
120
121
  if (current) rows.push(omitted ? appendOmissionMarker(current, innerWidth) : current);
121
- return rows.map((row) => truncateToWidth(row.text, innerWidth, ""));
122
+ return rows.map((row) => truncateFooterText(row.text, innerWidth, ""));
122
123
  }
@@ -0,0 +1,11 @@
1
+ import { truncateToWidth } from "@earendil-works/pi-tui";
2
+
3
+ /** Close retained links before joining/padding footer fragments, even on Pi < 0.84.0. */
4
+ export function truncateFooterText(text: string, width: number, ellipsis: string): string {
5
+ const truncated = truncateToWidth(text, width, ellipsis);
6
+ let activeLink = false;
7
+ for (const match of truncated.matchAll(/\x1b\]8;[^;\x07\x1b]*;([^\x07\x1b]*)(?:\x07|\x1b\\)/g)) {
8
+ activeLink = Boolean(match[1]);
9
+ }
10
+ return truncated + (activeLink ? "\x1b]8;;\x07" : "");
11
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { visibleWidth } from "@earendil-works/pi-tui";
3
3
  import type { SeparatorStyle, ZentuiConfig } from "./config";
4
4
  import { FOOTER_FORMAT_ALIASES } from "./config";
5
5
  import { sanitizeEditorMetadataText } from "./editor-metadata-format";
@@ -22,6 +22,7 @@ import {
22
22
  packCompactChunks,
23
23
  reflowFullFooter,
24
24
  } from "./footer-layout";
25
+ import { truncateFooterText } from "./footer-text";
25
26
  import {
26
27
  buildContextDisplayLabel,
27
28
  buildSessionDurationLabel,
@@ -77,7 +78,7 @@ function fitStatusTexts(statusTexts: string[], maxWidth: number, separator: stri
77
78
  }
78
79
 
79
80
  if (fitted.length === 0) {
80
- return maxWidth > 1 ? truncateToWidth(text, maxWidth, "…") : "";
81
+ return maxWidth > 1 ? truncateFooterText(text, maxWidth, "…") : "";
81
82
  }
82
83
  break;
83
84
  }
@@ -101,10 +102,10 @@ function composeBuiltInFooterContent(left: string, right: string, innerWidth: nu
101
102
  const leftWidth = visibleWidth(left);
102
103
  const rightWidth = visibleWidth(right);
103
104
  return leftWidth >= innerWidth
104
- ? truncateToWidth(left, innerWidth, "")
105
+ ? truncateFooterText(left, innerWidth, "")
105
106
  : leftWidth + 1 + rightWidth <= innerWidth
106
107
  ? `${left}${" ".repeat(innerWidth - leftWidth - rightWidth)}${right}`
107
- : truncateToWidth(left, innerWidth, "");
108
+ : truncateFooterText(left, innerWidth, "");
108
109
  }
109
110
 
110
111
  function composeFooterContent(
@@ -700,8 +701,8 @@ export function installFooter(
700
701
  );
701
702
  const frameRows = (rows: string[]) =>
702
703
  rows.map((row) => {
703
- const framed = width > 2 ? ` ${truncateToWidth(row, width - 2, "")} ` : row;
704
- return truncateToWidth(framed, width, "");
704
+ const framed = width > 2 ? ` ${truncateFooterText(row, width - 2, "")} ` : row;
705
+ return truncateFooterText(framed, width, "");
705
706
  });
706
707
 
707
708
  if (!config.components.footer.styles.starship.responsive)
@@ -732,8 +733,8 @@ export function installFooter(
732
733
  if (reflowed) return frameRows(reflowed);
733
734
 
734
735
  const chunkBudget = compactChunkBudget(innerWidth);
735
- const compactCwdLabel = truncateToWidth(cwdLabel, chunkBudget, "…");
736
- const compactSessionNameLabel = truncateToWidth(
736
+ const compactCwdLabel = truncateFooterText(cwdLabel, chunkBudget, "…");
737
+ const compactSessionNameLabel = truncateFooterText(
737
738
  sessionNameLabel,
738
739
  Math.max(1, chunkBudget - visibleWidth("in ")),
739
740
  "…",
@@ -742,7 +743,7 @@ export function installFooter(
742
743
  1,
743
744
  chunkBudget - visibleWidth("on ") - (statusBlock ? visibleWidth(statusBlock) + 1 : 0),
744
745
  );
745
- const compactBranchLabel = truncateToWidth(
746
+ const compactBranchLabel = truncateFooterText(
746
747
  renderVariable("git_branch"),
747
748
  compactBranchBudget,
748
749
  "…",
@@ -784,7 +785,7 @@ export function installFooter(
784
785
  );
785
786
  const references = collectFooterFormatReferences(chunk.tokens, FOOTER_FORMAT_ALIASES);
786
787
  if (["cwd", "session_name", "git_branch"].some((name) => references.has(name))) {
787
- rendered = truncateToWidth(rendered, chunkBudget, "…");
788
+ rendered = truncateFooterText(rendered, chunkBudget, "…");
788
789
  }
789
790
  if (rendered) compactChunks.push({ text: rendered, boundary: chunk.boundary });
790
791
  }