gentle-pi 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +157 -18
  2. package/assets/agents/gentle-ai-explore.md +11 -4
  3. package/assets/agents/gentle-ai-verify.md +23 -4
  4. package/assets/agents/gentle-ai-worker.md +6 -6
  5. package/assets/install-wizard/index.html +47 -0
  6. package/assets/install-wizard/wizard.css +1279 -0
  7. package/assets/install-wizard/wizard.js +951 -0
  8. package/assets/orchestrator-delegation.md +70 -163
  9. package/assets/orchestrator-memory.md +11 -4
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-tracking.md +53 -0
  12. package/assets/orchestrator-verification.md +28 -0
  13. package/assets/orchestrator-writer.md +38 -0
  14. package/assets/orchestrator.md +29 -25
  15. package/assets/sounds/LICENSE.md +52 -0
  16. package/assets/sounds/attention.wav +0 -0
  17. package/assets/sounds/error.wav +0 -0
  18. package/assets/sounds/success.wav +0 -0
  19. package/bin/engram-guard +9 -0
  20. package/bin/gentle-shell-install.mjs +302 -0
  21. package/bin/gentle-shell.mjs +708 -22
  22. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  23. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  24. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  25. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  27. package/docs/auto-permission-answers.md +25 -0
  28. package/docs/auto-permission-evidence.md +39 -0
  29. package/docs/bundled-install.md +399 -0
  30. package/docs/delegated-verification.md +3 -5
  31. package/docs/gentle-agents-activity.md +513 -0
  32. package/docs/gentle-shell-uninstall.md +136 -0
  33. package/docs/gentle-shell.md +90 -33
  34. package/docs/install-wizard.md +2040 -0
  35. package/docs/native-authority-architecture.md +1 -1
  36. package/docs/permission-modes.md +127 -0
  37. package/docs/prompt-history.md +1 -1
  38. package/docs/readme-reference.md +310 -47
  39. package/docs/render-cache-performance.md +28 -0
  40. package/docs/session-profile-format.md +39 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/yolo-mode.md +11 -10
  44. package/extensions/ask-user-choice.ts +4 -0
  45. package/extensions/ask-user-question.ts +6 -1
  46. package/extensions/child-context.ts +11 -2
  47. package/extensions/child-safety.ts +4 -2
  48. package/extensions/codegraph-tools.ts +18 -5
  49. package/extensions/gentle-agents.ts +718 -87
  50. package/extensions/gentle-ai.ts +991 -263
  51. package/extensions/gentle-herdr-activity.ts +81 -0
  52. package/extensions/gentle-notifications.ts +104 -0
  53. package/extensions/gentle-shell.ts +271 -80
  54. package/extensions/gentle-todo.ts +123 -18
  55. package/extensions/history/index.ts +11 -6
  56. package/extensions/pi-pretty.ts +39 -3
  57. package/extensions/quiet-tools.ts +91 -21
  58. package/extensions/skill-registry.ts +175 -46
  59. package/extensions/startup-banner.ts +11 -2
  60. package/lib/agent-home.ts +10 -1
  61. package/lib/agent-model-resolution.ts +80 -0
  62. package/lib/agent-profiles.ts +13 -3
  63. package/lib/agents-completion-delivery.ts +11 -3
  64. package/lib/agents-messaging.ts +1 -1
  65. package/lib/agents-protocol.ts +40 -1
  66. package/lib/agents-runner.ts +90 -15
  67. package/lib/auto-permission-answers.ts +133 -0
  68. package/lib/auto-permission-classifier.ts +459 -0
  69. package/lib/auto-permission-evidence.ts +88 -0
  70. package/lib/auto-permission-memory.ts +69 -0
  71. package/lib/auto-permission-runtime.ts +484 -0
  72. package/lib/auto-permission-tool-context.ts +61 -0
  73. package/lib/background-jobs-tools.ts +202 -0
  74. package/lib/background-jobs.ts +253 -0
  75. package/lib/background-monitor.ts +154 -0
  76. package/lib/bounded-writer-admission.ts +83 -10
  77. package/lib/builtin-codemode-optout.ts +166 -0
  78. package/lib/card-content-policy.ts +85 -0
  79. package/lib/child-package-injection.ts +62 -0
  80. package/lib/codemode-renderer.ts +14 -4
  81. package/lib/command-palette-catalog.ts +3 -2
  82. package/lib/context-threshold.ts +26 -0
  83. package/lib/destructive-command-guard.ts +104 -28
  84. package/lib/gentle-ai-binary.ts +109 -4
  85. package/lib/gentle-shell-bundled.ts +535 -0
  86. package/lib/gentle-shell-launcher.ts +75 -24
  87. package/lib/gentle-shell-startup-flow.ts +54 -0
  88. package/lib/gentle-shell-startup-update.ts +92 -0
  89. package/lib/gentle-shell-uninstall.ts +840 -0
  90. package/lib/gentle-shell-update-panel.ts +231 -0
  91. package/lib/herdr-activity.ts +183 -0
  92. package/lib/inprocess-reviewer.ts +3 -0
  93. package/lib/jobs-sidebar-state.ts +20 -0
  94. package/lib/jobs-view.ts +161 -0
  95. package/lib/model-price-ratio.ts +83 -0
  96. package/lib/model-routing-authority.ts +6 -1
  97. package/lib/nan-provider.ts +33 -11
  98. package/lib/native-review-cli.ts +194 -73
  99. package/lib/notification-audio-native.ts +162 -0
  100. package/lib/notification-audio-windows.ts +399 -0
  101. package/lib/notification-audio.ts +332 -0
  102. package/lib/notification-customize.ts +241 -0
  103. package/lib/notification-events.ts +113 -0
  104. package/lib/notification-policy.ts +148 -0
  105. package/lib/notification-pulse-client.ts +367 -0
  106. package/lib/notification-pulse-protocol.ts +305 -0
  107. package/lib/notification-pulse-stream.ts +213 -0
  108. package/lib/notification-pulse-worker.ts +66 -0
  109. package/lib/notification-scheduler.ts +173 -0
  110. package/lib/notification-service.ts +202 -0
  111. package/lib/notification-ui.ts +87 -0
  112. package/lib/odd-phase.ts +1 -1
  113. package/lib/orchestrator-alias.ts +20 -0
  114. package/lib/orchestrator-catalog.ts +79 -0
  115. package/lib/orchestrator-consultation.ts +61 -0
  116. package/lib/orchestrator-discovery.ts +49 -0
  117. package/lib/orchestrator-helper-consent.ts +122 -0
  118. package/lib/orchestrator-helper.ts +167 -0
  119. package/lib/orchestrator-message-card.ts +105 -0
  120. package/lib/orchestrator-presence.ts +115 -14
  121. package/lib/orchestrator-scope.ts +80 -0
  122. package/lib/orchestrator-state.ts +107 -0
  123. package/lib/orchestrator-tool-card.ts +280 -0
  124. package/lib/orchestrator-work-search.ts +218 -0
  125. package/lib/orchestrator-work.ts +79 -0
  126. package/lib/permission-mode-policy.ts +59 -0
  127. package/lib/review-candidate-view-owner.ts +8 -6
  128. package/lib/review-candidate-view.ts +3 -2
  129. package/lib/review-compact-contract.ts +2 -2
  130. package/lib/review-host-relay.ts +7 -2
  131. package/lib/review-integration-v2.ts +100 -22
  132. package/lib/review-publication-gate.ts +1 -1
  133. package/lib/review-risk-assessment.ts +77 -51
  134. package/lib/review-sidebar-state.ts +2 -1
  135. package/lib/review-transaction.ts +72 -3
  136. package/lib/runtime-metrics-children.ts +1 -1
  137. package/lib/runtime-metrics-native.ts +3 -2
  138. package/lib/selection-engine.ts +22 -7
  139. package/lib/session-change-capture.ts +5 -4
  140. package/lib/session-changes.ts +27 -22
  141. package/lib/session-profile-binding.ts +83 -0
  142. package/lib/session-profile-freeze.ts +261 -0
  143. package/lib/session-profile-persistence.ts +187 -0
  144. package/lib/session-worktree-registry.ts +74 -8
  145. package/lib/shell-bar.ts +41 -11
  146. package/lib/shell-changes-view.ts +40 -13
  147. package/lib/shell-changes.ts +88 -71
  148. package/lib/shell-prompt.ts +6 -4
  149. package/lib/shell-sidebar-layout.ts +5 -1
  150. package/lib/shell-todo.ts +81 -26
  151. package/lib/shell-usage.ts +4 -3
  152. package/lib/status-timing-diagnostics.ts +201 -0
  153. package/lib/telemetry-trigger.ts +3 -1
  154. package/lib/vim-editor-adapter.ts +27 -5
  155. package/lib/visual-customization-policy.ts +20 -9
  156. package/lib/visual-customize-view.ts +171 -20
  157. package/lib/writer-surfaces.ts +162 -0
  158. package/lib/yolo-session-policy.ts +198 -121
  159. package/package.json +5 -5
  160. package/runtime/child-package-injection.mjs +63 -0
  161. package/runtime/gentle-ai-binary.mjs +109 -4
  162. package/runtime/gentle-shell-bundled.mjs +536 -0
  163. package/runtime/gentle-shell-launcher.mjs +74 -23
  164. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  165. package/runtime/gentle-shell-startup-update.mjs +93 -0
  166. package/runtime/gentle-shell-uninstall.mjs +841 -0
  167. package/runtime/gentle-shell-update-panel.mjs +232 -0
  168. package/runtime/native-review-cli.mjs +191 -70
  169. package/runtime/review-integration-v2.mjs +100 -22
  170. package/runtime/review-risk-assessment.mjs +77 -51
  171. package/runtime/status-timing-diagnostics.mjs +202 -0
  172. package/runtime/telemetry-trigger.mjs +3 -1
  173. package/scripts/bootstrap.cmd +255 -0
  174. package/scripts/bootstrap.sh +168 -0
  175. package/scripts/build-distribution.mjs +157 -0
  176. package/scripts/build-installer-bundles.mjs +136 -0
  177. package/scripts/build-runtime-modules.mjs +7 -0
  178. package/scripts/bundled-install.mjs +1308 -0
  179. package/scripts/bundled-wizard.mjs +399 -0
  180. package/scripts/check-types.mjs +4 -6
  181. package/scripts/gentle-ai-installer.mjs +79 -32
  182. package/scripts/install-gentle-ai.mjs +48 -1
  183. package/scripts/install-tui-mode-setting.mjs +10 -1
  184. package/scripts/install-wizard-preview.mjs +154 -0
  185. package/scripts/installer-downloads.mjs +648 -0
  186. package/scripts/installer-preflight.mjs +387 -0
  187. package/scripts/installer-probes.mjs +564 -0
  188. package/scripts/installer-runner.mjs +1102 -0
  189. package/scripts/installer-server.mjs +881 -0
  190. package/scripts/installer-windows-artifacts.json +15 -0
  191. package/scripts/installer-windows.mjs +752 -0
  192. package/scripts/main-channel.mjs +452 -0
  193. package/scripts/npm-pack-result.mjs +31 -0
  194. package/scripts/pi-update.mjs +223 -0
  195. package/scripts/render-cache-performance-worker.mjs +205 -0
  196. package/scripts/render-cache-performance.mjs +115 -0
  197. package/scripts/run-test-suite.mjs +22 -8
  198. package/scripts/test-installer-acceptance.sh +161 -0
  199. package/scripts/test-packed-runner.mjs +23 -5
  200. package/scripts/types-baseline.json +2 -92
  201. package/scripts/verify-package-files.mjs +82 -18
  202. package/skills/gentle-ai/SKILL.md +12 -11
  203. package/tests/agent-home.test.ts +56 -3
  204. package/tests/agent-model-resolution.test.ts +113 -0
  205. package/tests/agent-profiles.test.ts +12 -0
  206. package/tests/agent-risk-escalation.test.ts +123 -0
  207. package/tests/agents-completion-delivery.test.ts +17 -0
  208. package/tests/agents-protocol.test.ts +57 -0
  209. package/tests/agents-queries.test.ts +9 -9
  210. package/tests/agents-runner-process.test.ts +31 -0
  211. package/tests/agents-runner.test.ts +89 -4
  212. package/tests/append-system-prompt-route.test.ts +18 -1
  213. package/tests/auto-mode-human-answers.test.ts +68 -0
  214. package/tests/auto-mode-invariants.test.ts +276 -0
  215. package/tests/auto-mode-sdk.test.ts +84 -0
  216. package/tests/auto-mode.test.ts +49 -0
  217. package/tests/auto-permission-answers.test.ts +170 -0
  218. package/tests/auto-permission-classifier.test.ts +634 -0
  219. package/tests/auto-permission-compound.test.ts +24 -0
  220. package/tests/auto-permission-evidence.test.ts +88 -0
  221. package/tests/auto-permission-memory.test.ts +46 -0
  222. package/tests/auto-permission-public-regressions.test.ts +111 -0
  223. package/tests/auto-permission-runtime.test.ts +454 -0
  224. package/tests/autonomous-guard.test.ts +86 -16
  225. package/tests/background-cache-warming.test.ts +2 -1
  226. package/tests/background-jobs.test.ts +227 -0
  227. package/tests/background-monitor.test.ts +195 -0
  228. package/tests/background-subagents-default-mode.test.ts +28 -5
  229. package/tests/bounded-stop-contract.test.ts +85 -0
  230. package/tests/bounded-writer-admission.test.ts +52 -1
  231. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  232. package/tests/bridge-wake-rendering.test.ts +56 -0
  233. package/tests/build-distribution.test.ts +269 -0
  234. package/tests/builtin-codemode-optout.test.ts +242 -0
  235. package/tests/bundled-install.test.ts +1319 -0
  236. package/tests/bundled-wizard.test.ts +505 -0
  237. package/tests/card-content-policy.test.ts +68 -0
  238. package/tests/card-style-policy.test.ts +34 -2
  239. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  240. package/tests/child-package-entrypoints.test.ts +65 -0
  241. package/tests/child-package-injection.test.ts +74 -0
  242. package/tests/child-safety.test.ts +12 -0
  243. package/tests/child-web-package.test.ts +224 -0
  244. package/tests/codegraph-tools.test.ts +35 -0
  245. package/tests/codemode-rendering.test.ts +107 -2
  246. package/tests/command-palette.test.ts +5 -5
  247. package/tests/context-threshold.test.ts +31 -0
  248. package/tests/delegate-for-reason-contract.test.ts +327 -0
  249. package/tests/delegated-key-learnings-contract.test.ts +3 -2
  250. package/tests/devbinary/native-review-parity.devtest.ts +1 -1
  251. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  252. package/tests/engram-safety.test.ts +316 -0
  253. package/tests/generic-agent-tools.test.ts +16 -1
  254. package/tests/gentle-agents.test.ts +1895 -76
  255. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  256. package/tests/gentle-ai-binary.test.ts +19 -2
  257. package/tests/gentle-ai-child-guards.test.ts +202 -0
  258. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  259. package/tests/gentle-ai-installer.test.ts +548 -58
  260. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  261. package/tests/gentle-ai-renderer.test.ts +6 -6
  262. package/tests/gentle-ai.test.ts +999 -31
  263. package/tests/gentle-herdr-activity.test.ts +100 -0
  264. package/tests/gentle-notifications.test.ts +427 -0
  265. package/tests/gentle-shell-bin.test.ts +263 -19
  266. package/tests/gentle-shell-bundled.test.ts +1036 -0
  267. package/tests/gentle-shell-launcher.test.ts +251 -25
  268. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  269. package/tests/gentle-shell-startup-update.test.ts +85 -0
  270. package/tests/gentle-shell-uninstall.test.ts +978 -0
  271. package/tests/gentle-shell-update-panel.test.ts +158 -0
  272. package/tests/gentle-shell.test.ts +611 -100
  273. package/tests/gentle-todo.test.ts +317 -14
  274. package/tests/herdr-activity.test.ts +243 -0
  275. package/tests/history-agent-home.test.ts +81 -0
  276. package/tests/history-child-guard.test.ts +70 -0
  277. package/tests/inprocess-reviewer.test.ts +20 -0
  278. package/tests/install-gentle-ai-failure.test.ts +69 -0
  279. package/tests/install-tui-mode-setting.test.ts +15 -0
  280. package/tests/install-wizard.test.ts +1223 -0
  281. package/tests/installer-bundles.test.ts +122 -0
  282. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  283. package/tests/installer-preflight.test.ts +750 -0
  284. package/tests/installer-probes.test.ts +1106 -0
  285. package/tests/installer-runner.test.ts +2159 -0
  286. package/tests/installer-server.test.ts +1567 -0
  287. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  288. package/tests/jobs-view.test.ts +160 -0
  289. package/tests/main-channel-upgrade.test.ts +395 -0
  290. package/tests/main-channel.test.ts +344 -0
  291. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  292. package/tests/model-price-ratio.test.ts +116 -0
  293. package/tests/model-profiles-preservation.test.ts +153 -0
  294. package/tests/model-routing-package.test.ts +10 -0
  295. package/tests/nan-provider-runtime.test.ts +156 -0
  296. package/tests/nan-provider.test.ts +28 -18
  297. package/tests/native-binary-gate.test.ts +2 -0
  298. package/tests/native-review-capability-contract.test.ts +7 -1
  299. package/tests/native-review-cli.test.ts +236 -1
  300. package/tests/native-review-consent.test.ts +2 -2
  301. package/tests/native-review-parity-runtime.test.ts +1 -1
  302. package/tests/native-review-parity.test.ts +12 -48
  303. package/tests/native-risk-evidence-parity.test.ts +21 -0
  304. package/tests/no-self-pause-contract.test.ts +87 -0
  305. package/tests/notification-audio-native.test.ts +281 -0
  306. package/tests/notification-audio-windows.test.ts +581 -0
  307. package/tests/notification-audio.test.ts +548 -0
  308. package/tests/notification-customize.test.ts +655 -0
  309. package/tests/notification-events.test.ts +115 -0
  310. package/tests/notification-policy.test.ts +201 -0
  311. package/tests/notification-pulse-client.test.ts +356 -0
  312. package/tests/notification-pulse-protocol.test.ts +213 -0
  313. package/tests/notification-pulse-stream.test.ts +320 -0
  314. package/tests/notification-scheduler.test.ts +258 -0
  315. package/tests/notification-ui.test.ts +157 -0
  316. package/tests/npm-pack-result.test.ts +41 -0
  317. package/tests/odd-integration.test.ts +14 -5
  318. package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
  319. package/tests/odd-routing-contract.test.ts +80 -23
  320. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  321. package/tests/orchestrator-budget.test.ts +29 -6
  322. package/tests/orchestrator-catalog.test.ts +146 -0
  323. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  324. package/tests/orchestrator-consultation.test.ts +111 -0
  325. package/tests/orchestrator-discovery.test.ts +241 -0
  326. package/tests/orchestrator-helper-consent.test.ts +171 -0
  327. package/tests/orchestrator-helper.test.ts +297 -0
  328. package/tests/orchestrator-message-card.test.ts +190 -0
  329. package/tests/orchestrator-presence.test.ts +23 -0
  330. package/tests/orchestrator-rdd-ownership.test.ts +6 -5
  331. package/tests/orchestrator-scope.test.ts +107 -0
  332. package/tests/orchestrator-state.test.ts +114 -0
  333. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  334. package/tests/orchestrator-tool-card.test.ts +398 -0
  335. package/tests/orchestrator-work-search.test.ts +318 -0
  336. package/tests/orchestrator-work.test.ts +147 -0
  337. package/tests/package-manifest.test.ts +105 -21
  338. package/tests/packed-runner-owned-path.test.ts +35 -1
  339. package/tests/parallel-reason-contract.test.ts +21 -0
  340. package/tests/parallel-writer-admission.test.ts +210 -0
  341. package/tests/permission-mode-policy.test.ts +194 -0
  342. package/tests/persona-single-channel.test.ts +4 -12
  343. package/tests/pi-pretty.test.ts +56 -0
  344. package/tests/pi-update-launcher.test.ts +87 -0
  345. package/tests/pi-update.test.ts +186 -0
  346. package/tests/provider-defect-handoff.test.ts +4 -3
  347. package/tests/quiet-tool-rendering.test.ts +160 -3
  348. package/tests/rdd-aware-verification-contract.test.ts +29 -41
  349. package/tests/rdd-status-line.test.ts +52 -0
  350. package/tests/render-cache-performance.test.ts +48 -0
  351. package/tests/review-agent-end-preflight.test.ts +62 -10
  352. package/tests/review-candidate-view.test.ts +101 -12
  353. package/tests/review-contract-prompt.test.ts +33 -4
  354. package/tests/review-controller-native-recovery.test.ts +327 -17
  355. package/tests/review-controller-native-routing.test.ts +255 -1
  356. package/tests/review-controller-workspace-root.test.ts +43 -2
  357. package/tests/review-controller.test.ts +2 -2
  358. package/tests/review-correction-lifecycle.test.ts +2 -1
  359. package/tests/review-gate.test.ts +7 -4
  360. package/tests/review-graph-schema.test.ts +2 -2
  361. package/tests/review-host-relay-routing.test.ts +5 -3
  362. package/tests/review-host-relay.test.ts +49 -2
  363. package/tests/review-integration-v2-forward.test.ts +21 -1
  364. package/tests/review-json-arguments.test.ts +167 -0
  365. package/tests/review-last-event-closure.test.ts +157 -8
  366. package/tests/review-ledger-contract.test.ts +5 -4
  367. package/tests/review-policy-judgment-day.test.ts +1 -1
  368. package/tests/review-policy-ordinary.test.ts +2 -2
  369. package/tests/review-relay-transport-agent.test.ts +66 -1
  370. package/tests/review-risk-assessment.test.ts +109 -33
  371. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  372. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  373. package/tests/review-test-fixtures.ts +7 -1
  374. package/tests/review-transaction.test.ts +18 -1
  375. package/tests/run-test-suite.test.ts +83 -5
  376. package/tests/runtime-harness.mjs +42 -15
  377. package/tests/runtime-metrics-extension.test.ts +1 -1
  378. package/tests/runtime-metrics-native.test.ts +31 -1
  379. package/tests/selection-engine.test.ts +65 -5
  380. package/tests/session-change-capture.test.ts +10 -10
  381. package/tests/session-changes-shell.test.ts +242 -48
  382. package/tests/session-changes.test.ts +17 -24
  383. package/tests/session-profile-binding.test.ts +163 -0
  384. package/tests/session-profile-freeze.test.ts +394 -0
  385. package/tests/session-profile-persistence.test.ts +334 -0
  386. package/tests/session-subject-routing-contract.test.ts +34 -0
  387. package/tests/session-worktree-registry.test.ts +57 -2
  388. package/tests/shell-bar.test.ts +40 -0
  389. package/tests/shell-changes-view.test.ts +85 -34
  390. package/tests/shell-changes.test.ts +79 -62
  391. package/tests/shell-render-cache-runtime.test.ts +69 -0
  392. package/tests/shell-render-cache.test.ts +129 -0
  393. package/tests/shell-sidebar-layout.test.ts +27 -2
  394. package/tests/shell-todo.test.ts +132 -11
  395. package/tests/shell-usage-view.test.ts +1 -1
  396. package/tests/shell-usage.test.ts +35 -0
  397. package/tests/short-check-routing-contract.test.ts +49 -0
  398. package/tests/skill-registry-watcher.test.ts +119 -0
  399. package/tests/skill-registry.test.ts +311 -5
  400. package/tests/startup-banner.test.ts +117 -3
  401. package/tests/startup-home-repo.test.ts +170 -0
  402. package/tests/status-timing-diagnostics.test.ts +283 -0
  403. package/tests/support/orchestrator-modules.ts +19 -0
  404. package/tests/task-size-routing-contract.test.ts +241 -0
  405. package/tests/telemetry-trigger.test.ts +27 -1
  406. package/tests/test-strength-contract.test.ts +124 -0
  407. package/tests/verify-agent-contract.test.ts +90 -0
  408. package/tests/verify-first-launch-contract.test.ts +139 -0
  409. package/tests/verify-package-files.test.ts +56 -0
  410. package/tests/vim-editor-adapter.test.ts +47 -21
  411. package/tests/visual-customization-policy.test.ts +32 -3
  412. package/tests/visual-customize-view.test.ts +288 -10
  413. package/tests/writer-edit-surface-scope.test.ts +59 -2
  414. package/tests/writer-surfaces.test.ts +199 -0
  415. package/tests/yolo-customize.test.ts +18 -3
  416. package/tests/yolo-mode-runtime.test.ts +37 -10
  417. package/tests/yolo-mode.test.ts +38 -5
  418. package/tests/yolo-session-policy.test.ts +1 -1
  419. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  420. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  421. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  422. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  423. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  424. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  425. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,398 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { visibleWidth } from "@earendil-works/pi-tui";
4
+ import { orchestratorToolRenderers } from "../lib/orchestrator-tool-card.ts";
5
+ import { CARD_STYLE, cardStyle, setCardStyle } from "../lib/shell-card.ts";
6
+ import { stripAnsi } from "../lib/terminal-theme.ts";
7
+
8
+ const theme = { fg: (_role: string, text: string) => text, bg: (_role: string, text: string) => `\x1b[48;2;20;20;20m${text}\x1b[49m` };
9
+ interface Example {
10
+ kind: Parameters<typeof orchestratorToolRenderers>[0];
11
+ args: Record<string, unknown>;
12
+ result: { content: Array<{ type: string; text: string }>; details?: unknown };
13
+ summary: RegExp;
14
+ }
15
+ const cases: Example[] = [
16
+ { kind: "session" as const, args: { subject: "Inspect Windows", state: { objective: "Read-only VM inspection" } }, result: { content: [{ type: "text", text: "Active session ID: stable-routing-id\nCurrent alias: Recover Alan changes" }], details: { gentleAgents: { senderSessionId: "stable-routing-id", alias: "Recover Alan changes" } } }, summary: /Current alias: Recover Alan changes/ },
17
+ { kind: "consult" as const, args: { recipient_session_id: "peer-id" }, result: { content: [{ type: "text", text: '{"status":"available","authority":"none"}' }], details: { gentleAgents: { receipt: { status: "available", authority: "none" } } } }, summary: /available.*not an owner reply/i },
18
+ { kind: "list" as const, args: {}, result: { content: [{ type: "text", text: "Advertised sessions (reachability is unknown):\n- peer-id" }], details: { gentleAgents: { candidates: [{ sessionId: "peer-id" }] } } }, summary: /1 advertised session.*reachability unknown/i },
19
+ ];
20
+
21
+ function card(entry: Example, expanded: boolean, partial = false) {
22
+ const renderer = orchestratorToolRenderers(entry.kind, (open) => open ? "collapse" : "expand");
23
+ const context = { args: entry.args, state: {}, expanded, isPartial: partial };
24
+ const call = renderer.renderCall(entry.args, theme, context);
25
+ const result = renderer.renderResult(entry.result, { expanded, isPartial: partial }, theme, context);
26
+ return { render: (width: number) => [...call.render(width), ...result.render(width)], invalidate() { call.invalidate(); result.invalidate(); } };
27
+ }
28
+
29
+ test("orchestrator cards replace JSON calls with semantic headings and useful summaries", () => {
30
+ for (const entry of cases) {
31
+ const compact = stripAnsi(card(entry, false).render(140).join("\n"));
32
+ assert.match(compact, entry.summary);
33
+ assert.doesNotMatch(compact, /\{"|stable-routing-id|peer-id|Read-only VM inspection/);
34
+ assert.match(compact, /expand/);
35
+ const expanded = stripAnsi(card(entry, true).render(140).join("\n"));
36
+ assert.notEqual(compact, expanded);
37
+ assert.doesNotMatch(expanded, /Arguments:|Result details:|\{"|^\s*[{}]\s*$/m);
38
+ assert.ok(expanded.includes(entry.kind === "session" ? "Routing ID:" : entry.kind === "consult" ? "Status:" : "without recent metadata"));
39
+ assert.match(expanded, /collapse/);
40
+ }
41
+ const identity = stripAnsi(card(cases[0], true).render(140).join("\n"));
42
+ assert.match(identity, /Requested subject: Inspect Windows/);
43
+ assert.match(identity, /Existing alias preserved/);
44
+ assert.match(identity, /stable-routing-id/);
45
+ assert.match(identity, /Read-only VM inspection/);
46
+ });
47
+
48
+ test("discovery overview uses one row per recorded session and groups unknown sessions", () => {
49
+ const entry: Example = {
50
+ ...cases[2],
51
+ result: {
52
+ content: [{ type: "text", text: "Advertised sessions: raw-output-must-not-repeat" }],
53
+ details: { gentleAgents: { candidates: [
54
+ { sessionId: "known-id", label: "API team", freshness: "recent", reachability: "unknown", workspace: "/work/api", tasks: [{ id: "task-1", label: "Check login", status: "running", workspace: "/work/task" }], scope: { host: { root: "/repo/api" }, complete: false, omittedTasks: 2 } },
55
+ { sessionId: "unknown-id", freshness: "unknown", reachability: "unknown" },
56
+ { sessionId: "stale-id", freshness: "stale", reachability: "unknown", label: "Old alias must stay hidden", workspace: "/stale-workspace" },
57
+ ] } },
58
+ },
59
+ };
60
+ const before = structuredClone(entry);
61
+ const output = stripAnsi(card(entry, true).render(140).join("\n"));
62
+ assert.match(output, /API team\s+known-id\s+api\s+1/);
63
+ assert.match(output, /2 without recent metadata/);
64
+ assert.match(output, /Detail: consult\(full ID\)/);
65
+ assert.doesNotMatch(output, /Session ID:|Check login|Complete:|Omitted tasks:|\/work\/api|unknown-id|stale-id|Arguments:|raw-output-must-not-repeat|recorded catalog: unknown|Old alias|stale-workspace|[{}]/);
66
+ assert.deepEqual(entry, before);
67
+ });
68
+
69
+ test("consultation, classified work, and missing-detail JSON become labelled rows with safety limits", () => {
70
+ const examples: Example[] = [
71
+ { ...cases[1], result: { content: [{ type: "text", text: '{"status":"available","authority":"none"}' }], details: { gentleAgents: { receipt: { status: "available", ownerReply: false, authority: "none", snapshot: { label: "API team", state: { state: { objective: "Verify login" } } }, unknowns: ["reachability"], omissions: ["private-context"], digest: "technical-digest" } } } } },
72
+ { ...cases[2], args: { filter: { area: "Auth" } }, result: { content: [{ type: "text", text: '{"matches":[]}' }], details: { gentleAgents: { workSearch: { matches: [{ sessionId: "peer-id", work: { area: "Auth", tags: ["Review"] } }], coverage: { exhaustive: false, omittedMatches: 2 }, ownerReply: false, authority: "none" } } } } },
73
+ { ...cases[1], result: { content: [{ type: "text", text: '{"status":"unavailable","code":"not-ready"}' }] } },
74
+ ];
75
+ const outputs = examples.map(entry => stripAnsi(card(entry, true).render(140).join("\n")));
76
+ for (const output of outputs) assert.doesNotMatch(output, /[{}]|"status"|"matches"|Arguments:|Result details:/);
77
+ assert.match(outputs[0], /Objective: Verify login/);
78
+ assert.match(outputs[0], /Owner reply: no/);
79
+ assert.match(outputs[0], /Authority: none/);
80
+ assert.match(outputs[0], /Unknowns:/);
81
+ assert.match(outputs[0], /private-context/);
82
+ assert.doesNotMatch(outputs[0], /technical-digest/);
83
+ assert.match(outputs[1], /Auth/);
84
+ assert.match(outputs[1], /non-exhaustive/);
85
+ assert.match(outputs[1], /2 matches omitted/);
86
+ assert.match(outputs[1], /No authority/);
87
+ assert.match(outputs[2], /Code: not-ready/);
88
+ });
89
+
90
+ test("targeted discovery retains full identity and concise unknown repository limits", () => {
91
+ const peers = [
92
+ { sessionId: "no-scope", label: "Workspace only", freshness: "recent", workspace: "/work" },
93
+ { sessionId: "no-identity", label: "Non-Git workspace", freshness: "recent", scope: { host: { root: null, cloneHash: null, resolvedAt: 1, source: "recorded-workspace/git" }, complete: true } },
94
+ ];
95
+ for (const peer of peers) {
96
+ const entry: Example = { ...cases[2], args: { recipient_session_id: peer.sessionId }, result: { content: [], details: { gentleAgents: { candidates: [peer] } } } };
97
+ const output = stripAnsi(card(entry, true).render(140).join("\n"));
98
+ assert.ok(output.includes(`Session ID: ${peer.sessionId}`));
99
+ assert.match(output, peer.sessionId === "no-scope" ? /Repository scope: unknown/ : /Repository identity: unknown/);
100
+ assert.doesNotMatch(output, /Root: null|Clone hash: null|[{}]/);
101
+ }
102
+ });
103
+
104
+ test("expanded discovery has a fixed height even with dozens of rich records and narrow terminals", () => {
105
+ const previous = cardStyle();
106
+ const peers = Array.from({ length: 40 }, (_, index) => ({
107
+ sessionId: `01a10f0e-b0aa-72f1-951e-${String(index).padStart(12, "0")}`,
108
+ label: `API 团队 ${index} ${"long label ".repeat(8)}`, freshness: index < 9 ? "unknown" : "recent", reachability: "unknown",
109
+ workspace: `/home/devel/projects/${"long-parent/".repeat(20)}worktree-${index}`,
110
+ tasks: Array.from({ length: 8 }, (_, task) => ({ id: `task-${task}`, label: "Verbose task must not be dumped", workspace: "/hidden/task/workspace", status: "running" })), omitted: 2,
111
+ catalog: { tasks: [{ label: "Duplicated catalog must not appear" }], registered: Array(8).fill("/very/long/registered/worktree/path") },
112
+ }));
113
+ const entry: Example = { ...cases[2], result: { content: [], details: { gentleAgents: { candidates: peers } } } };
114
+ const before = structuredClone(entry);
115
+ try {
116
+ for (const style of Object.values(CARD_STYLE)) {
117
+ setCardStyle(style);
118
+ for (const width of [140, 80, 48, 24, 8, 1, 0, 140]) {
119
+ const view = card(entry, true);
120
+ const rows = view.render(width);
121
+ assert.ok(rows.length <= 16, `${style}/${width}: ${rows.length} physical rows`);
122
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
123
+ if (style === CARD_STYLE.FLOAT && width === 140) assert.ok(rows.some(row => stripAnsi(row).trimStart().startsWith("▎")), "exercise real float chrome, not its no-background fallback");
124
+ if (width === 140) {
125
+ const output = stripAnsi(rows.join("\n"));
126
+ assert.match(output, /9 without recent metadata/);
127
+ assert.match(output, /25 more recorded sessions/);
128
+ assert.match(output, /worktree-9\s+8\+/);
129
+ assert.doesNotMatch(output, /Verbose task|Duplicated catalog|registered\/worktree|long-parent|Session ID:|ownership guarantee/);
130
+ }
131
+ }
132
+ }
133
+ } finally { setCardStyle(previous); }
134
+ assert.deepEqual(entry, before);
135
+ });
136
+
137
+ test("classified-work overviews are also bounded and do not dump descriptors or coverage trees", () => {
138
+ const matches = Array.from({ length: 30 }, (_, index) => ({ sessionId: `peer-${index}`, label: `Auth task ${index}`, taskId: `task-${index}`, work: { area: "Auth", topic: "Login", tags: Array(8).fill("Verbose tag must stay hidden") } }));
139
+ const entry: Example = { ...cases[2], args: { filter: { area: "Auth" } }, result: { content: [], details: { gentleAgents: { workSearch: { matches, coverage: { exhaustive: false, omittedMatches: 2, unexaminedPeers: 3 }, ownerReply: false, authority: "none" } } } } };
140
+ for (const width of [140, 80, 48, 24, 1, 0]) {
141
+ const rows = card(entry, true).render(width);
142
+ assert.ok(rows.length <= 16, `width ${width}: ${rows.length} rows`);
143
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
144
+ if (width === 140) {
145
+ const output = stripAnsi(rows.join("\n"));
146
+ assert.match(output, /Auth\/Login/);
147
+ assert.match(output, /24 more work matches/);
148
+ assert.match(output, /2 matches omitted/);
149
+ assert.match(output, /3 peers not scanned/);
150
+ assert.doesNotMatch(output, /Verbose tag|Coverage:|Work search:|Owner reply:/);
151
+ }
152
+ }
153
+ });
154
+
155
+ test("overview colors create hierarchy without changing text, column widths, or height", () => {
156
+ const colors: Record<string, number> = { accent: 34, toolTitle: 35, dim: 90, muted: 37, syntaxNumber: 36, warning: 33 };
157
+ const calls: Array<{ role: string; value: string }> = [];
158
+ const colored = {
159
+ ...theme,
160
+ fg: (role: string, value: string) => { calls.push({ role, value }); return `\x1b[${colors[role] ?? 39}m${value}\x1b[39m`; },
161
+ bold: (value: string) => `\x1b[1m${value}\x1b[22m`,
162
+ };
163
+ const entry: Example = { ...cases[2], result: { content: [], details: { gentleAgents: { candidates: [
164
+ { sessionId: "peer-1", label: "API 团队", workspace: "/work/api", freshness: "recent", tasks: [{}, {}, {}] },
165
+ { sessionId: "peer-2", label: "No tasks", freshness: "recent", tasks: [] },
166
+ { sessionId: "peer-3", label: "Unknown count", freshness: "recent" },
167
+ { sessionId: "hidden-peer", freshness: "unknown" },
168
+ ] } } } };
169
+ const renderer = orchestratorToolRenderers("list", open => open ? "collapse" : "expand");
170
+ const context = { args: entry.args, state: {}, expanded: true, isPartial: false };
171
+ const call = renderer.renderCall(entry.args, colored, context);
172
+ const result = renderer.renderResult(entry.result, { expanded: true, isPartial: false }, colored, context);
173
+ const previous = cardStyle();
174
+ try {
175
+ for (const style of Object.values(CARD_STYLE)) {
176
+ setCardStyle(style);
177
+ for (const width of [140, 80, 48, 24, 8, 1, 0, 140]) {
178
+ calls.length = 0;
179
+ const rows = [...call.render(width), ...result.render(width)];
180
+ assert.deepEqual(rows.map(stripAnsi), card(entry, true).render(width).map(stripAnsi));
181
+ assert.ok(rows.length <= 16);
182
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
183
+ if (width === 140) {
184
+ const output = rows.join("\n");
185
+ assert.ok(output.includes("\x1b[34mAPI 团队\x1b[39m"));
186
+ assert.ok(output.includes("\x1b[90mpeer-1\x1b[39m"));
187
+ assert.ok(calls.some(call => call.role === "toolTitle" && call.value === "Alias"));
188
+ assert.ok(calls.some(call => call.role === "syntaxNumber" && call.value === "3"));
189
+ assert.ok(calls.some(call => call.role === "muted" && call.value === "0"));
190
+ assert.ok(calls.some(call => call.role === "warning" && call.value === "?"));
191
+ assert.ok(calls.some(call => call.role === "muted" && call.value.includes("without recent metadata")));
192
+ assert.ok(!calls.some(call => call.role === "success"), "colors must not suggest confirmed live reachability");
193
+ }
194
+ }
195
+ }
196
+ } finally { setCardStyle(previous); }
197
+ });
198
+
199
+ test("session identity highlights alias and subject without changing content or wrapping", () => {
200
+ let accent = 34;
201
+ const colors: Record<string, number> = { muted: 37, dim: 90 };
202
+ const colored = { ...theme, fg: (role: string, value: string) => `\x1b[${role === "accent" ? accent : colors[role] ?? 39}m${value}\x1b[39m` };
203
+ const entry: Example = { ...cases[0], args: { subject: "Inspeccionar Windows — demo 团队" }, result: { content: [], details: { gentleAgents: { alias: "Prueba visual", senderSessionId: "01a11f31-f3a7-71e9-ba8a-4649a0cf8c11" } } } };
204
+ const before = structuredClone(entry);
205
+ const previous = cardStyle();
206
+ try {
207
+ for (const style of Object.values(CARD_STYLE)) {
208
+ setCardStyle(style);
209
+ for (const expanded of [false, true]) {
210
+ const renderer = orchestratorToolRenderers("session", open => open ? "collapse" : "expand");
211
+ const context = { args: entry.args, state: {}, expanded, isPartial: false };
212
+ const call = renderer.renderCall(entry.args, colored, context);
213
+ const result = renderer.renderResult(entry.result, { expanded, isPartial: false }, colored, context);
214
+ for (const width of [140, 80, 48, 24, 8, 1, 0, 140]) {
215
+ const rows = [...call.render(width), ...result.render(width)];
216
+ assert.deepEqual(rows.map(stripAnsi), card(entry, expanded).render(width).map(stripAnsi));
217
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
218
+ if (width === 140) {
219
+ const output = rows.join("\n");
220
+ assert.ok(output.includes("\x1b[37mCurrent alias: \x1b[39m\x1b[34mPrueba visual\x1b[39m"));
221
+ assert.ok(output.includes("\x1b[37mExisting alias preserved; subject does not rename it.\x1b[39m"));
222
+ if (expanded) {
223
+ assert.ok(output.includes("\x1b[90m01a11f31-f3a7-71e9-ba8a-4649a0cf8c11\x1b[39m"));
224
+ assert.ok(output.includes("\x1b[34mInspeccionar Windows — demo 团队\x1b[39m"));
225
+ }
226
+ }
227
+ }
228
+ accent = 35;
229
+ result.invalidate();
230
+ assert.ok(result.render(140).join("\n").includes("\x1b[35mPrueba visual\x1b[39m"));
231
+ accent = 34;
232
+ }
233
+ }
234
+ } finally { setCardStyle(previous); }
235
+ assert.deepEqual(entry, before);
236
+ });
237
+
238
+ test("session identity preserves main's separate task aliases and human session name", () => {
239
+ const entry: Example = { ...cases[0], args: { subject: "Inspect project" }, result: { content: [], details: { gentleAgents: { alias: "Demo session", sessionName: "Demo session", currentAlias: "Inspect project", initialAlias: "First task", senderSessionId: "stable-routing-id" } } } };
240
+ const compact = stripAnsi(card(entry, false).render(140).join("\n"));
241
+ assert.match(compact, /Current alias: Inspect project/);
242
+ assert.match(compact, /Session name: Demo session/);
243
+ assert.doesNotMatch(compact, /Current alias: Demo session|Existing alias preserved/);
244
+ const expanded = stripAnsi(card(entry, true).render(140).join("\n"));
245
+ assert.match(expanded, /Initial alias: First task/);
246
+ assert.match(expanded, /Session name preserved; subject updates task aliases/);
247
+ assert.match(expanded, /Routing ID: stable-routing-id/);
248
+ assert.doesNotMatch(expanded, /Requested subject: Inspect project/);
249
+ for (const expanded of [false, true]) {
250
+ const unknown: Example = { ...entry, result: { content: [], details: { gentleAgents: { alias: "Demo session", sessionName: "Demo session", currentAlias: null, initialAlias: null } } } };
251
+ const output = stripAnsi(card(unknown, expanded).render(140).join("\n"));
252
+ assert.match(output, /Current alias: unknown/);
253
+ assert.doesNotMatch(output, /Current alias: Demo session|Current alias: Inspect project/);
254
+ if (expanded) assert.match(output, /Initial alias: unknown/);
255
+ }
256
+ });
257
+
258
+ test("classified-work limits use warning rather than success colors", () => {
259
+ const calls: Array<{ role: string; value: string }> = [];
260
+ const colors = { ...theme, fg: (role: string, value: string) => { calls.push({ role, value }); return value; } };
261
+ const result = { content: [], details: { gentleAgents: { workSearch: { matches: [{ sessionId: "peer-1", label: "Auth task", work: { area: "Auth" } }], coverage: { omittedMatches: 2 }, source: { status: "unavailable", reason: "source-unclassified" } } } } };
262
+ const component = orchestratorToolRenderers("list", () => "collapse").renderResult(result, { expanded: true, isPartial: false }, colors, { args: { filter: {} }, state: {} });
263
+ component.render(140);
264
+ assert.ok(calls.some(call => call.role === "warning" && call.value === "2 matches omitted"));
265
+ assert.ok(calls.some(call => call.role === "warning" && call.value.includes("Source unavailable")));
266
+ assert.ok(calls.some(call => call.role === "accent" && call.value === "Auth task"));
267
+ assert.ok(!calls.some(call => call.role === "success"));
268
+ });
269
+
270
+ test("overview styles are recomputed on render after a theme changes", () => {
271
+ let accent = 34;
272
+ const changing = { ...theme, fg: (role: string, value: string) => role === "accent" ? `\x1b[${accent}m${value}\x1b[39m` : value };
273
+ const entry: Example = { ...cases[2], result: { content: [], details: { gentleAgents: { candidates: [{ sessionId: "peer-1", label: "API team", freshness: "recent", tasks: [] }] } } } };
274
+ const context = { args: {}, state: {}, expanded: true, isPartial: false };
275
+ const component = orchestratorToolRenderers("list", () => "collapse").renderResult(entry.result, { expanded: true, isPartial: false }, changing, context);
276
+ const before = component.render(140);
277
+ assert.ok(before.join("\n").includes("\x1b[34mAPI team\x1b[39m"));
278
+ accent = 31;
279
+ component.invalidate();
280
+ const after = component.render(140);
281
+ assert.ok(after.join("\n").includes("\x1b[31mAPI team\x1b[39m"));
282
+ assert.deepEqual(before.map(stripAnsi), after.map(stripAnsi));
283
+ });
284
+
285
+ test("legacy JSON keys that match object prototypes render as data, never as label functions", () => {
286
+ const entry: Example = { ...cases[1], result: { content: [{ type: "text", text: '{"status":"unavailable","constructor":"Visible constructor","__proto__":{"prototype":"Visible prototype"}}' }] } };
287
+ const output = stripAnsi(card(entry, true).render(140).join("\n"));
288
+ assert.match(output, /Constructor: Visible constructor/);
289
+ assert.match(output, /Prototype: Visible prototype/);
290
+ assert.doesNotMatch(output, /[{}]/);
291
+ });
292
+
293
+ test("nested readable data remains sanitized and width-safe with Unicode", () => {
294
+ const unsafe = "\x1b]52;c;clipboard\x07\x1b[31mAPI 团队 e\u0301\x1b[0m\r\u0000";
295
+ const entry: Example = { ...cases[1], result: { content: [{ type: "text", text: "ignored serialization" }], details: { gentleAgents: { receipt: { status: "available", snapshot: { [unsafe]: unsafe }, unknowns: [unsafe] } } } } };
296
+ const previous = cardStyle();
297
+ try {
298
+ for (const style of Object.values(CARD_STYLE)) {
299
+ setCardStyle(style);
300
+ const view = card(entry, true);
301
+ for (const width of [140, 60, 24, 8, 1, 0, 140]) {
302
+ view.invalidate();
303
+ const rows = view.render(width);
304
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
305
+ const output = stripAnsi(rows.join("\n"));
306
+ assert.doesNotMatch(output, /clipboard|\x1b|\r|\u0000/);
307
+ if (width === 140) assert.match(output, /API 团队 e\u0301/);
308
+ }
309
+ }
310
+ } finally { setCardStyle(previous); }
311
+ });
312
+
313
+ test("pending requests and withdrawn state are readable without empty argument blocks", () => {
314
+ for (const args of [{}, { subject: "Inspect Windows", state: null }]) {
315
+ const renderer = orchestratorToolRenderers("session", () => "collapse");
316
+ const output = stripAnsi(renderer.renderCall(args, theme, { args, state: {}, expanded: true, isPartial: true }).render(140).join("\n"));
317
+ assert.doesNotMatch(output, /Arguments:|[{}]/);
318
+ if ("state" in args) assert.match(output, /State: withdrawn/);
319
+ }
320
+ });
321
+
322
+ test("the same completed row expands and collapses without stale detail or headings", () => {
323
+ for (const entry of cases) {
324
+ const renderer = orchestratorToolRenderers(entry.kind, open => open ? "collapse" : "expand");
325
+ const context = { args: entry.args, state: {}, expanded: false, isPartial: false };
326
+ const snapshots: string[] = [];
327
+ for (const expanded of [false, true, false]) {
328
+ context.expanded = expanded;
329
+ const call = renderer.renderCall(entry.args, theme, context);
330
+ const result = renderer.renderResult(entry.result, { expanded, isPartial: false }, theme, context);
331
+ call.invalidate(); result.invalidate();
332
+ snapshots.push(stripAnsi([...call.render(140), ...result.render(140)].join("\n")));
333
+ }
334
+ assert.equal(snapshots[0], snapshots[2]);
335
+ assert.notEqual(snapshots[0], snapshots[1]);
336
+ assert.doesNotMatch(snapshots[2], /Arguments:|Result details:/);
337
+ }
338
+ });
339
+
340
+ test("identity without a requested subject or with a matching alias does not claim a preserved mismatch", () => {
341
+ for (const args of [{}, { subject: "Recover Alan changes" }, { state: null }]) {
342
+ const output = stripAnsi(card({ ...cases[0], args }, true).render(140).join("\n"));
343
+ assert.doesNotMatch(output, /Existing alias preserved/);
344
+ }
345
+ });
346
+
347
+ test("error and partial cards do not claim successful consultation or discovery", () => {
348
+ for (const entry of cases) {
349
+ const renderer = orchestratorToolRenderers(entry.kind, () => "expand");
350
+ const context = { args: entry.args, state: {}, isPartial: true, expanded: false };
351
+ const call = renderer.renderCall(entry.args, theme, context);
352
+ assert.match(stripAnsi(call.render(120).join("\n")), /running/);
353
+ const result = renderer.renderResult({ content: [{ type: "text", text: "Error: not ready" }], details: { error: "not ready" } }, { expanded: false, isPartial: false }, theme, context);
354
+ const failed = stripAnsi([...call.render(120), ...result.render(120)].join("\n"));
355
+ assert.match(failed, /Error: not ready/);
356
+ assert.doesNotMatch(failed, /running|Current alias:|advertised sessions|available ·/);
357
+ }
358
+ });
359
+
360
+ test("successful-looking partial results never display a final status or count", () => {
361
+ for (const entry of cases.slice(1)) {
362
+ const output = stripAnsi(card(entry, false, true).render(140).join("\n"));
363
+ assert.match(output, /Receiving partial result/);
364
+ assert.doesNotMatch(output, entry.summary);
365
+ }
366
+ });
367
+
368
+ test("work search and detail-less stored results remain useful", () => {
369
+ const renderer = orchestratorToolRenderers("list", () => "expand");
370
+ const context = { args: { filter: {} }, state: {}, expanded: false, isPartial: false };
371
+ const result = renderer.renderResult({ content: [{ type: "text", text: '{"matches":[]}' }], details: { gentleAgents: { workSearch: { matches: [] } } } }, { expanded: false, isPartial: false }, theme, context);
372
+ assert.match(stripAnsi(result.render(140).join("\n")), /0 work matches.*non-exhaustive/i);
373
+ const legacy = renderer.renderResult({ content: [{ type: "text", text: "No other sessions are currently advertised." }] }, { expanded: true, isPartial: false }, theme, context);
374
+ assert.match(stripAnsi(legacy.render(140).join("\n")), /No other sessions/);
375
+ });
376
+
377
+ test("expanded cards preserve payloads, sanitize terminal text, and resize in both styles", () => {
378
+ const previous = cardStyle();
379
+ try {
380
+ for (const style of Object.values(CARD_STYLE)) {
381
+ setCardStyle(style);
382
+ for (const entry of cases) for (const expanded of [false, true]) {
383
+ const view = card(entry, expanded);
384
+ for (const width of [140, 80, 24, 8, 2, 1, 0, 140]) {
385
+ view.invalidate();
386
+ const rows = view.render(width);
387
+ for (const row of rows) assert.ok(visibleWidth(row) <= width);
388
+ if (!width) assert.deepEqual(rows, []);
389
+ }
390
+ }
391
+ }
392
+ const unsafe = "\x1b]52;c;clipboard\x07Visible\r\u0000";
393
+ const view = card({ ...cases[0], result: { content: [{ type: "text", text: unsafe }], details: { gentleAgents: { alias: unsafe, senderSessionId: "stable-routing-id" } } } }, true);
394
+ const text = stripAnsi(view.render(140).join("\n"));
395
+ assert.match(text, /Visible/);
396
+ assert.doesNotMatch(text, /\x1b|\r|\u0000/);
397
+ } finally { setCardStyle(previous); }
398
+ });