quoxcli 0.2.0 → 0.4.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 (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -11,9 +11,16 @@
11
11
  * turn : null | { id, status: running|interrupting, startedAt, stage, activity }
12
12
  */
13
13
 
14
- import { splitSettled } from './markdown.js';
14
+ import { approvalSentence } from './approvalSentence.js';
15
+ import { dividerStep } from './dividerAocl.js';
16
+ import { approvalFacts } from './approvalFacts.js';
17
+ import { planLines } from './planSteps.js';
18
+ import { isConsentError, consentErrorText } from './consent.js';
19
+ import { splitSettled } from './markdownSettle.js'; // not markdown.js: that loads React + Ink (QUOXON-LATENCY)
15
20
  import { nextActivity, actionActivity } from './activity.js';
16
21
  import { normUsage, turnTokens, fmtTokens, fmtCost } from './indicator.js';
22
+ import { isLayerTrace, normaliseSummary, summaryFromTraces, verdictNote } from './aocl.js';
23
+ import { toolResultText } from './toolError.js';
17
24
 
18
25
  export const TOOL_STATUSES = ['ok', 'failed', 'denied', 'effect_unknown'];
19
26
  export const APPROVAL_ARM_MS = 300;
@@ -23,10 +30,14 @@ export const HELD_KEY_MS = 1500;
23
30
  export const initialState = (over = {}) => ({
24
31
  history: [],
25
32
  blocks: [],
33
+ asks: [], // ASK-SETTLE: open y/N/c ask cards (live, never scrollback) until answered/expired/closed
26
34
  turn: null,
27
35
  ctx: { agent: null, model: null, provider: null, endpoint: null, hat: null, conversationId: null },
28
36
  pending: [], // client-side queue (Tab)
29
37
  steering: [], // steer messages not yet accepted
38
+ lastTurnId: null, // the newest turn begun here: a card from an older turn is stale
39
+ lastAocl: null, // QUOXON-AOCL: the last turn's normalised aocl.summary (for /aocl)
40
+ waiting: [], // approvals raised by ANOTHER conversation/session: a notice only, never a focused card (answered via /approvals)
30
41
  approvals: [], // approval cards waiting for a human, first is shown; survive the turn that raised them
31
42
  running: [], // approved from this terminal, result arrives asynchronously (polled)
32
43
  inbox: [], // last /approvals listing
@@ -46,21 +57,51 @@ export const initialState = (over = {}) => ({
46
57
  * streams keep using `chunk`. Returns { append, st } where st is { v1, lastChunk }.
47
58
  */
48
59
  export function textGate(st, ev) {
49
- const g = st || { v1: false, lastChunk: null };
60
+ const g = st || { v1: false, lastChunk: null, acc: '' };
61
+ const acc = g.acc || '';
62
+ const add = (c) => (acc + c).slice(-200000);
50
63
  if (ev.type === 'turn.started') return { append: false, st: { ...g, v1: true } };
51
64
  if (ev.type === 'chunk') {
52
- if (g.v1) return { append: false, st: g };
53
- return { append: Boolean(ev.content), st: { ...g, lastChunk: ev.content ?? null } };
65
+ const c = ev.content;
66
+ if (!c) return { append: false, st: g };
67
+ // RESTORE-TUI: on a v1 stream a chunk is skipped only when it mirrors text already shown (the full text so far,
68
+ // or the delta it repeats). A chunk carrying NEW text is still answer text (it was dropped since dae720283).
69
+ if (g.v1 && acc.endsWith(c)) return { append: false, st: g };
70
+ return { append: true, st: { ...g, lastChunk: c, acc: add(c) } };
54
71
  }
55
72
  if (ev.type === 'text.delta') {
56
- const mirrored = !g.v1 && g.lastChunk != null && g.lastChunk === ev.content;
57
- return { append: Boolean(ev.content) && !mirrored, st: { v1: true, lastChunk: null } };
73
+ const c = ev.content;
74
+ const mirrored = g.lastChunk != null && g.lastChunk === c;
75
+ const append = Boolean(c) && !mirrored;
76
+ return { append, st: { v1: true, lastChunk: null, acc: append ? add(c) : acc } };
58
77
  }
59
78
  return { append: false, st: g };
60
79
  }
61
80
 
62
81
  const tms = (v) => { const t = typeof v === 'number' ? v : Date.parse(v); return Number.isFinite(t) ? t : null; };
63
82
 
83
+ const short = (v, n = 120) => { const t = typeof v === 'string' ? v : (v == null ? '' : JSON.stringify(v)); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
84
+ const argText = (a) => a && typeof a === 'object' && !Array.isArray(a) && (a.argv || a.command) ? argText(a.argv ?? a.command) : Array.isArray(a) ? a.join(' ') : (a && typeof a === 'object' ? Object.entries(a).filter(([, v]) => v != null && typeof v !== 'object').slice(0, 4).map(([k, v]) => `${k}=${v}`).join(' ') : (a == null ? '' : String(a)));
85
+
86
+ /**
87
+ * Describe ANY inbox approval from what the item carries: the tool or leaf, the command or key args, the target
88
+ * (explicit target, else the org it acts on), and who asked and when. Never invents; empty fields are null.
89
+ */
90
+ export function describeInboxItem(it) {
91
+ const p = it?.payload || {};
92
+ const c = p.approval_context || {};
93
+ const m = c.method || {};
94
+ const ob = c.on_behalf_of || {};
95
+ const w = p.what && typeof p.what === 'object' ? p.what : {}; // APPROVAL-WHAT: the server's own action/target/summary wins
96
+ const tool = w.action || m.tool || c.tool || c.leaf || p.tool || p.leaf || null;
97
+ const argv = m.argv ?? m.args ?? c.argv ?? c.args ?? p.argv ?? p.args ?? m.command ?? c.command ?? p.command ?? null;
98
+ const cmd = short([m.leaf || c.leaf || p.leaf, argText(argv)].filter(Boolean).join(' ').trim() || null);
99
+ const target = w.target || m.target || c.target || p.target || cmd || w.target_none || c.target_org || c.org_name || p.org_name || c.org_id || p.org_id || null;
100
+ const by = ob.agent || ob.agent_id || ob.session_name || ob.session || c.requested_by?.agent_label || c.source_label || p.source || it?.source || null;
101
+ const what = w.summary ? String(w.summary) : (it?.title ? short(String(it.title), 160) : null);
102
+ return { tool, target: target ? short(target, 200) : null, title: what, by: by ? String(by) : null, at: it?.created_at ?? it?.createdAt ?? null };
103
+ }
104
+
64
105
  /**
65
106
  * A pending inbox approval found by polling -> approval card, or null when it does not qualify.
66
107
  * Qualifies when created after `since` (the turn start, else app start), or raised on behalf of this
@@ -68,19 +109,80 @@ const tms = (v) => { const t = typeof v === 'number' ? v : Date.parse(v); return
68
109
  */
69
110
  export function cardFromInboxItem(it, { since = 0, userId = null, conversationId = null, now = 0 } = {}) {
70
111
  if (!it || !it.id) return null;
112
+ if (it.payload?.type === 'decision') return null; // quox ask: answered with /answer N key from /approvals, never a y/n key card
71
113
  const c = it.payload?.approval_context || {};
72
114
  const m = c.method || {};
73
115
  const created = tms(it.created_at ?? it.createdAt);
74
116
  const mine = Boolean(userId && c.on_behalf_of?.user_id && String(c.on_behalf_of.user_id) === String(userId));
75
117
  const here = Boolean(conversationId && c.on_behalf_of?.conversation_id === conversationId);
76
118
  if (!(created != null && created >= since) && !mine && !here) return null;
119
+ // QUOXON-REVIEW: collector tool approvals mint the inbox item with aee_correlation_id = the approval id;
120
+ // without it the card had no id, polling was skipped and every terminal approve read "timeout".
121
+ const toolApproval = it.payload?.type === 'native_tool_approval';
122
+ const approvalId = it.payload?.approval_id || it.approval_id || it.approvalId
123
+ || (toolApproval ? (it.aee_correlation_id || it.aeeCorrelationId || null) : null) || null;
124
+ const ob = c.on_behalf_of || {};
125
+ const d = describeInboxItem(it);
126
+ const tool = d.tool || String(it.title || it.id);
77
127
  return {
78
- callId: `inbox:${it.id}`, inboxItemId: it.id, approvalId: it.payload?.approval_id || it.approval_id || it.approvalId || null,
79
- tool: m.tool || String(it.title || it.id), target: m.target || null, scope: null, risk: c.risk || null,
80
- url: it.dashboard_url || null, shownAt: now, undescribable: !m.tool || !m.target
128
+ callId: `inbox:${it.id}`, inboxItemId: it.id, approvalId,
129
+ tool, target: d.target, what: d.title, askedBy: d.by, askedAt: d.at, scope: null, risk: c.risk || null,
130
+ url: it.dashboard_url || null, shownAt: now, undescribable: !d.tool && !d.target && !it._plan,
131
+ plan: it._plan ? { lines: planLines(it._plan.steps), loaded: it._plan.loaded !== false, orgId: it._plan.orgId || null, orgName: it._plan.orgName || null } : null, // the plan's steps + the item's own org
132
+ facts: approvalFacts(it), // APPROVAL-WWWH: the exact command, hosts, chain, reason, time and read-only class
133
+ // only an item that names THIS conversation is ours; anything else came from another session and must not take the keyboard
134
+ foreign: !here,
135
+ origin: String(ob.agent || ob.agent_id || ob.session_name || ob.session || (ob.conversation_id ? `conversation ${String(ob.conversation_id).slice(0, 8)}` : approvalSourceLabel(it))),
136
+ privileged: isPrivileged({ tool, risk: c.risk, flag: c.privileged === true || c.elevated === true || it.payload?.privileged === true, title: it.title })
81
137
  };
82
138
  }
83
139
 
140
+ /** Where an inbox approval came from: its source label, else the payload source, else its type. */
141
+ export function approvalSourceLabel(it) {
142
+ const c = it?.payload?.approval_context || {};
143
+ return String(c.source_label || it?.payload?.source || it?.source || (c.requested_by && c.requested_by.agent_label) || it?.payload?.type || 'plugin or workflow');
144
+ }
145
+
146
+ /**
147
+ * QUOXON-FW7: every open inbox approval, grouped for /approvals. 'this conversation' (names this conversation,
148
+ * or already a card here), 'other sessions' (raised on behalf of another conversation or agent), and
149
+ * 'plugins and workflows' (everything else, labelled by source). Empty groups are dropped.
150
+ */
151
+ export function groupApprovals(items, { conversationId = null, cardIds = [] } = {}) {
152
+ const g = { 'decisions asked of you': [], 'this conversation': [], 'other sessions': [], 'plugins and workflows': [] };
153
+ for (const item of items || []) {
154
+ if (item?.payload?.type === 'decision') { g['decisions asked of you'].push({ item, label: String(item.payload.session || 'session') }); continue; } // quox ask
155
+ const ob = item?.payload?.approval_context?.on_behalf_of || {};
156
+ const label = approvalSourceLabel(item);
157
+ // A card that names a conversation belongs here only when it is THIS one (a card shown in this window for another
158
+ // conversation, e.g. a privileged hat.switch, is still "other sessions"). No conversation named: a card here counts.
159
+ const mine = ob.conversation_id ? Boolean(conversationId) && ob.conversation_id === conversationId : cardIds.includes(item.id);
160
+ if (mine) g['this conversation'].push({ item, label });
161
+ else if (ob.conversation_id || ob.agent || ob.agent_id || ob.session_name || ob.session) g['other sessions'].push({ item, label: String(ob.session_name || ob.session || ob.agent || ob.agent_id || `conversation ${String(ob.conversation_id).slice(0, 8)}`) });
162
+ else g['plugins and workflows'].push({ item, label });
163
+ }
164
+ return Object.entries(g).filter(([, v]) => v.length).map(([group, v]) => ({ group, items: v }));
165
+ }
166
+
167
+ /**
168
+ * QLOOP-LIVE: which approval card the keyboard goes to first. Cards about a loop THIS window is watching lead, then the rest of
169
+ * this conversation, then other sessions' items (stable inside each rank). The loop id appears in the card's target / description.
170
+ */
171
+ export function orderApprovals(cards, mineIds = []) {
172
+ const ids = (mineIds || []).filter(Boolean).map(String);
173
+ const rank = (c) => (ids.length && ids.some((id) => [c.target, c.what, c.facts?.where].some((t) => t != null && String(t).includes(id))) ? 0 : c.foreign ? 2 : 1);
174
+ return cards.map((c, i) => ({ c, i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).map((x) => x.c);
175
+ }
176
+ /** Cards raised by other sessions (the "other sessions: N" tail). */
177
+ export const otherSessionsCount = (cards) => (cards || []).filter((c) => c.foreign).length;
178
+
179
+ /** Privileged/elevated grants (hat switches and anything the server marks privileged or elevated) need an explicit confirm. */
180
+ export function isPrivileged({ tool = '', risk = null, flag = false, title = '' } = {}) {
181
+ if (flag === true) return true;
182
+ if (String(tool) === 'hat.switch' || /^hat[ .]switch/i.test(String(title))) return true;
183
+ return /privileged|elevated|critical/i.test(String(risk || ''));
184
+ }
185
+
84
186
  let uid = 0;
85
187
  const nid = () => ++uid;
86
188
 
@@ -101,6 +203,11 @@ function flush(s, force = false) {
101
203
  return out.length || blocks.length !== s.blocks.length ? { ...s, history: [...s.history, ...out], blocks } : s;
102
204
  }
103
205
 
206
+ /** Fold the turn's VOLT tool count and list into its AOCL summary (footer count, /aocl list). */
207
+ function withTools(sum, turn) {
208
+ const t = turn?.toolsOffer;
209
+ return t && t.offered != null ? { ...sum, toolsOffered: t.offered, toolsTotal: t.total, toolsList: t.list } : sum;
210
+ }
104
211
  function note(s, text, tone = 'dim') {
105
212
  return { ...s, history: [...s.history, { id: nid(), kind: 'note', text, tone }] };
106
213
  }
@@ -133,6 +240,14 @@ function appendText(s00, content) {
133
240
  return addBlock(s, { kind: 'text', text: content });
134
241
  }
135
242
 
243
+ /** QUOXON-FW3: who a delegation targets. Server fields first; older servers only put agent_id=<id> in the summary. */
244
+ export function delegateInfo(ev) {
245
+ if (ev.tool !== 'delegate_to_agent') return {};
246
+ const id = ev.agent_id || (String(ev.summary || '').match(/\bagent_id=(\S+)/) || [])[1] || null;
247
+ if (!id) return {};
248
+ return { agentId: id, agentName: ev.agent_name || id, ...(ev.task_gist ? { taskGist: ev.task_gist } : {}) };
249
+ }
250
+
136
251
  function upsertTool(s, ev, patch) {
137
252
  const i = s.blocks.findIndex(b => b.kind === 'tool' && b.callId === ev.call_id);
138
253
  if (i >= 0) {
@@ -172,7 +287,8 @@ export function summarizeTurn(s, status, now) {
172
287
  // Real server usage when it arrived; otherwise the estimate from streamed text; nothing when neither exists.
173
288
  const tk = turnTokens(t);
174
289
  if (tk > 0) parts.push(`${fmtTokens(tk)} tokens`);
175
- const cost = fmtCost(t?.usage?.cost ?? normUsage(null, t?.doneCost).cost);
290
+ // A labelled estimate (cost_source, e.g. the claude CLI's own figure) is never shown as this turn's price; /cost explains it.
291
+ const cost = t?.usage?.costSource || t?.viaCli ? null : fmtCost(t?.usage?.cost ?? normUsage(null, t?.doneCost).cost);
176
292
  if (cost) parts.push(cost);
177
293
  if (status === 'interrupted' || status === 'failed') {
178
294
  if (t?.tools) parts.push(`${plural(finished, 'tool')} finished`);
@@ -215,15 +331,49 @@ function endTurn(s, status, now) {
215
331
  function reduceInner(s, a) {
216
332
  switch (a.type) {
217
333
  case 'user': // a submitted prompt
218
- return { ...s, history: [...s.history, { id: nid(), kind: 'user', text: a.text, queued: !!a.queued }] };
334
+ return { ...s, history: [...s.history, { id: nid(), kind: 'user', text: a.text, queued: !!a.queued, ...(a.echo ? { echo: true } : {}) }] };
219
335
  case 'turn.begin':
220
- return { ...s, turn: { id: a.turnId, status: 'running', startedAt: a.now, stage: null, activity: null, tools: 0, finishedTools: 0, approvals: 0, receipts: [], chars: 0, usage: null }, blocks: [], autoDrain: false };
336
+ return { ...s, lastTurnId: a.turnId, turn: { id: a.turnId, status: 'running', startedAt: a.now, stage: null, activity: null, tools: 0, finishedTools: 0, approvals: 0, receipts: [], chars: 0, usage: null }, blocks: [], autoDrain: false };
221
337
  case 'note':
222
338
  return note(s, a.text, a.tone);
339
+ // ASK-SETTLE (owner 2026-10-17): an open ask is a live card; settling it removes the card and leaves one line.
340
+ case 'ask.open': { // the same ask shown again (after a chat about it) replaces its card, never a second one
341
+ const asks = (s.asks || []).filter(x => x.text !== a.text);
342
+ return { ...s, asks: [...asks, { id: a.id, text: a.text, at: a.at ?? null }] };
343
+ }
344
+ case 'ask.settle': {
345
+ const asks = s.asks || [];
346
+ if (!asks.some(x => x.id === a.id)) return s;
347
+ if (a.silent) return { ...s, asks: asks.filter(x => x.id !== a.id) }; // a walkthrough card that ended normally: no leftover line
348
+ return note({ ...s, asks: asks.filter(x => x.id !== a.id) }, a.text, a.tone);
349
+ }
350
+ case 'slotmsg.settle': { // SLOT-RUN: an answered/expired run request collapses to one line in place
351
+ let hit = false;
352
+ const history = s.history.map(it => {
353
+ if (it.kind !== 'slotmsg' || !it.line?.run || it.line.run.askId !== a.askId || it.line.run.settled) return it;
354
+ hit = true;
355
+ return { ...it, line: { ...it.line, run: { ...it.line.run, settled: a.text, settledTone: a.tone } } };
356
+ });
357
+ return hit ? { ...s, history } : s;
358
+ }
359
+ case 'bang.out': // QUOXON-FW8: command output, shown as is (not note-painted); the transcript records only the action name
360
+ return { ...s, history: [...s.history, { id: nid(), kind: 'bang', text: a.text }] };
361
+ case 'slotmsg': // QUOXON-MESH: a slot chat line (slotChat.js slotLine), incoming or sent
362
+ return { ...s, history: [...s.history, { id: nid(), kind: 'slotmsg', line: a.line }] };
363
+ case 'loopcard': // QLOOP-LIVE: a live loop card; the card object is mutated in place by loopCards.js
364
+ return { ...s, history: [...s.history, { id: nid(), kind: 'loopcard', card: a.card }] };
365
+ case 'tip': // a "did you know" tip (tips.js); text + the command to try
366
+ return { ...s, history: [...s.history, { id: nid(), kind: 'tip', text: a.text, cmd: a.cmd }] };
367
+ case 'bcard': // QUOXON-BROADCAST: the sender's framed delivery card (slash/broadcast.js broadcastCard), pure data
368
+ return { ...s, history: [...s.history, { id: nid(), kind: 'bcard', card: a.card }] };
369
+ case 'whatsnew': // the startup "What's new" box (whatsnew.js): [{ title, cmd }] + how many more
370
+ return { ...s, history: [...s.history, { id: nid(), kind: 'whatsnew', items: a.items, more: a.more, head: a.head, pad: a.pad }] };
223
371
  case 'notify':
224
372
  return { ...s, notify: { ...s.notify, ...a.patch } };
225
373
  case 'welcome':
226
374
  return { ...s, history: [...s.history, { id: nid(), kind: 'welcome', version: a.version }] };
375
+ case 'introShow': // /welcome: the first screen again (Q art + the greeting text), settled in history
376
+ return { ...s, history: [...s.history, { id: nid(), kind: 'intro', text: a.text, cols: a.cols }] };
227
377
  case 'help':
228
378
  return { ...s, history: [...s.history, { id: nid(), kind: 'help', model: a.model }] };
229
379
  case 'object': // inline object view (objects/): pure data, rebuilt at render width
@@ -232,6 +382,8 @@ function reduceInner(s, a) {
232
382
  return flush(addBlock(s, { kind: 'error', text: a.text }), false);
233
383
  case 'verbose':
234
384
  return { ...s, verbose: a.mode };
385
+ case 'aocl.table': // /aocl: the last turn's per-layer table, on demand
386
+ return { ...s, history: [...s.history, { id: nid(), kind: 'aocl', table: true, sum: s.lastAocl || null }] };
235
387
  case 'history.add': // restored turns (/resume): ordinary history items, plus their answers for /copy
236
388
  return { ...s, history: [...s.history, ...(a.items || []).map(i => ({ id: nid(), ...i }))], answers: [...(s.answers || []), ...(a.answers || [])].slice(-20) };
237
389
  case 'history.reset': // /clear: a fresh screen; nothing on the server is touched
@@ -273,20 +425,47 @@ function reduceInner(s, a) {
273
425
  }
274
426
  case 'interrupt.requested':
275
427
  return s.turn ? { ...s, turn: { ...s.turn, status: 'interrupting' } } : s;
428
+ case 'turn.deferred': // QUOXON-FW3: the send never reached the server; end the turn quietly (no failure summary, nothing ran)
429
+ return { ...s, turn: null, blocks: [], autoDrain: false };
276
430
  case 'stopped.local': { // SSE aborted by us because the server has no interrupt route
277
431
  let n = note(s, 'Stopped locally; work already sent may still finish. Verify before retrying.', 'warn');
278
432
  return endTurn(n, 'interrupted', a.now);
279
433
  }
280
434
  case 'approval.found': { // pending approval seen by polling (covers provider paths that emit no approval.requested)
281
435
  const card = cardFromInboxItem(a.item, { since: s.turn?.startedAt ?? a.since ?? 0, userId: a.userId, conversationId: s.ctx.conversationId, now: a.now || 0 });
436
+ if (card && card.approvalId) { // an event card raised without an inbox id learns it from the poll (else y answers a null id: 404)
437
+ const j = s.approvals.findIndex(x => !x.inboxItemId && x.approvalId === card.approvalId);
438
+ if (j >= 0) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], inboxItemId: card.inboxItemId, url: approvals[j].url || card.url, facts: approvals[j].facts || card.facts }; return { ...s, approvals, seenInbox: { ...s.seenInbox, [card.inboxItemId]: true } }; }
439
+ }
440
+ if (card && card.approvalId) { // an event stub that could not be described learns its description from the inbox item
441
+ const j = s.approvals.findIndex(x => x.undescribable && (x.inboxItemId === card.inboxItemId || x.approvalId === card.approvalId));
442
+ if (j >= 0 && !card.undescribable) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], tool: card.tool, target: card.target, what: card.what, askedBy: card.askedBy, askedAt: card.askedAt, facts: card.facts, undescribable: false }; return { ...s, approvals }; }
443
+ }
282
444
  if (!card || s.seenInbox[card.inboxItemId] || s.approvals.some(x => x.inboxItemId === card.inboxItemId || (card.approvalId && x.approvalId === card.approvalId))) return s;
283
- return { ...s, approvals: [...s.approvals, card], seenInbox: { ...s.seenInbox, [card.inboxItemId]: true }, turn: s.turn ? { ...s.turn, approvals: s.turn.approvals + 1 } : s.turn };
445
+ if (card.foreign) { // another conversation's card: a compact notice, never the focused card
446
+ if (s.waiting.some(x => x.inboxItemId === card.inboxItemId)) return s;
447
+ return { ...s, waiting: [...s.waiting, card] };
448
+ }
449
+ return { ...s, approvals: [...s.approvals, { ...card, turnId: s.turn?.id ?? null }], seenInbox: { ...s.seenInbox, [card.inboxItemId]: true }, turn: s.turn ? { ...s.turn, approvals: s.turn.approvals + 1 } : s.turn };
450
+ }
451
+ case 'approval.gone': { // the server says it no longer knows this approval (404): someone already handled it
452
+ const hit = x => x.callId === a.callId || (a.inboxItemId && x.inboxItemId === a.inboxItemId);
453
+ const had = s.approvals.some(hit) || s.waiting.some(hit);
454
+ const n = { ...s, inbox: (s.inbox || []).filter(x => x.id !== a.inboxItemId), approvals: s.approvals.filter(x => !hit(x)), waiting: s.waiting.filter(x => !hit(x)), seenInbox: a.inboxItemId ? { ...s.seenInbox, [a.inboxItemId]: true } : s.seenInbox };
455
+ return had ? note(n, 'approval already handled', 'accent') : n;
284
456
  }
285
457
  case 'approval.dismiss':
286
458
  return { ...s, approvals: s.approvals.filter(x => x.callId !== a.callId) };
287
459
  case 'approval.sent': { // the human answered from this terminal
288
- const rest = { ...s, approvals: s.approvals.filter(x => x.callId !== a.callId) };
289
- if (a.action !== 'approve') return note(rest, `denied: ${a.tool || 'approval'}${a.target ? ` on ${a.target}` : ''}`, 'warn');
460
+ const rest = { ...s, inbox: (s.inbox || []).filter(x => x.id !== a.inboxItemId), seenInbox: a.inboxItemId ? { ...s.seenInbox, [a.inboxItemId]: true } : s.seenInbox, approvals: s.approvals.filter(x => x.callId !== a.callId && !(a.inboxItemId && x.inboxItemId === a.inboxItemId)), waiting: s.waiting.filter(x => x.callId !== a.callId && x.inboxItemId !== a.callId) };
461
+ if (a.batch) return rest; // /approve N prints its own one-line summary
462
+ if (a.quiet && a.action === 'approve') return rest;
463
+ if (a.action !== 'approve') return note(rest, approvalSentence(a, false), 'warn');
464
+ if (a.foreign) return note(rest, approvalSentence(a, true), 'ok');
465
+ if (a.tool === 'hat.switch') { // applied server-side by the inbox approve; nothing to wait for
466
+ const to = String(a.target || '').split('\u2192').pop().replace(/\(privileged\)/, '').trim() || 'the new hat';
467
+ return note(rest, `Hat switched to ${to}. Ask again and I'll carry on.`, 'ok');
468
+ }
290
469
  const run = { callId: a.callId, approvalId: a.approvalId || null, tool: a.tool, target: a.target };
291
470
  return note({ ...rest, running: [...rest.running.filter(x => x.callId !== a.callId), run] },
292
471
  `approved; running (result arrives asynchronously)${a.tool ? `: ${a.tool}${a.target ? ` on ${a.target}` : ''}` : ''}`, 'ok');
@@ -298,12 +477,15 @@ function reduceInner(s, a) {
298
477
  if (a.outcome === 'done') return note(n, `result: ${what}: ${a.text || 'completed'}`, 'ok');
299
478
  if (a.outcome === 'failed') return note(n, `failed: ${what}: ${a.text || 'error'}`, 'warn');
300
479
  if (a.outcome === 'denied') return note(n, `denied: ${what}`, 'warn');
301
- return note(n, `result not known yet: ${what}; check /approvals`, 'warn');
480
+ // FW4: never silent and never a guess. Say what is unknown and where the authoritative answer is.
481
+ const check = run && run.approvalId ? `quox approvals result ${run.approvalId} (or /approvals)` : '/approvals (no approval id was returned, so it cannot be tracked from here)';
482
+ return note(n, `result not known yet: ${what}; approved, but the server has not said whether it ran. It may still be running or may have run. Check: ${check}`, 'warn');
302
483
  }
303
484
  case 'inbox.sync': { // open approval ids from a successful inbox refresh
304
485
  const ids = new Set(a.ids);
305
486
  const approvals = s.approvals.filter(x => !x.inboxItemId || ids.has(x.inboxItemId) || (a.now || 0) - x.shownAt < 5000);
306
- return approvals.length === s.approvals.length ? s : { ...s, approvals };
487
+ const waiting = s.waiting.filter(x => ids.has(x.inboxItemId));
488
+ return approvals.length === s.approvals.length && waiting.length === s.waiting.length ? s : { ...s, approvals, waiting };
307
489
  }
308
490
  case 'event':
309
491
  return reduceEvent(s, a.ev, a.now || 0);
@@ -315,6 +497,14 @@ function reduceInner(s, a) {
315
497
  /** The first card is armed from the moment it is actually SHOWN, not from when it was received. */
316
498
  export function reduce(s, a) {
317
499
  let n = reduceInner(s, a);
500
+ // TUI-DIVIDER: the bottom-rule AOCL strip, moved only by real turn events (dividerAocl.js).
501
+ if (a.type === 'turn.begin' || a.type === 'event') {
502
+ const dv = dividerStep(s.divider ?? null, a, a.now || 0);
503
+ if (dv !== (n.divider ?? null)) n = { ...n, divider: dv };
504
+ }
505
+ // A turn that ends WITHOUT a turn.finished event (stopped.local, turn.deferred, a /clear cancel) must not leave the
506
+ // strip open: endedAt stayed null, dividerLive stayed true, and the dot train + scrambled label looped on a dead turn.
507
+ if (n.divider && n.divider.endedAt == null && !n.turn) n = { ...n, divider: null };
318
508
  // The honest activity word: only real events move it (see activity.js).
319
509
  if (n.turn) {
320
510
  const act = a.type === 'event' ? nextActivity(s.turn?.act, a.ev, a.now || 0, { hasText: Boolean(n.turn.chars) })
@@ -331,12 +521,15 @@ export function reduce(s, a) {
331
521
  function reduceEvent(s, ev, now) {
332
522
  switch (ev.type) {
333
523
  case 'connected': return s;
334
- // TODO(contract v2): background.status is not in events v1; wire here once the server defines it.
524
+ // background.status is not in events v1 yet; handled so the line works the moment the server sends it (RESTORE-TUI: 4fbd3178c dropped it).
525
+ case 'background.status': return { ...s, background: Math.max(0, Number(ev.pending) || 0) };
335
526
  case 'turn.started': {
336
527
  const ctx = {
337
528
  agent: ev.agent ?? s.ctx.agent, model: ev.model ?? s.ctx.model, provider: ev.provider ?? s.ctx.provider,
338
529
  // B3: the host a self-hosted provider ran at; cleared when the server names none.
339
530
  endpoint: ev.provider ? (ev.endpoint ?? null) : (s.ctx.endpoint ?? null),
531
+ // QUOXON-PARITY: which Claude path ran (subscription CLI vs API key); cleared like endpoint.
532
+ auth: ev.provider ? (ev.auth ?? null) : (s.ctx.auth ?? null),
340
533
  hat: ev.hat ?? s.ctx.hat, conversationId: ev.conversation_id ?? s.ctx.conversationId
341
534
  };
342
535
  // The model THIS turn actually used, as the server reported it (never the profile's default).
@@ -378,13 +571,15 @@ function reduceEvent(s, ev, now) {
378
571
  case 'replay.gap':
379
572
  return addBlock(s, { kind: 'note', text: `Events ${ev.from} to ${ev.to} of this turn were not kept on the server (size cap); the rest follows.`, tone: 'warn' });
380
573
  case 'tool.started':
381
- return upsertTool(s, ev, { status: 'running', summary: ev.summary || ev.tool, startedAt: now });
574
+ return upsertTool(s, ev, { status: 'running', summary: ev.summary || ev.tool, startedAt: now, ...delegateInfo(ev) });
382
575
  case 'tool.finished': {
383
576
  const status = TOOL_STATUSES.includes(ev.status) ? ev.status : 'unknown'; // never green for a status we do not know
384
577
  const first = !s.doneCalls[ev.call_id] && ['ok', 'failed', 'denied'].includes(status);
385
578
  let withTool = upsertTool(s, ev, {
386
- status, resultSummary: ev.result_summary, durationMs: ev.duration_ms,
387
- envelopeId: ev.envelope_id, receipt: ev.receipt || null
579
+ status, resultSummary: toolResultText(ev.result_summary, s.verbose), rawSummary: ev.result_summary, durationMs: ev.duration_ms,
580
+ envelopeId: ev.envelope_id, receipt: ev.receipt || null,
581
+ ...(ev.card && typeof ev.card === 'object' && Array.isArray(ev.card.rows) ? { card: ev.card } : {}), // HITL-INBOX-BAR: a list answer's rows (toolCards.js)
582
+ ...(Number.isFinite(ev.tools_used) ? { toolsUsed: ev.tools_used } : {})
388
583
  });
389
584
  if (first && withTool.turn) withTool = { ...withTool, turn: { ...withTool.turn, finishedTools: withTool.turn.finishedTools + 1 } };
390
585
  if (ev.receipt && ev.receipt.verified === true && withTool.turn && ev.receipt.ward_seq != null) {
@@ -396,7 +591,9 @@ function reduceEvent(s, ev, now) {
396
591
  const card = {
397
592
  callId: ev.call_id, inboxItemId: ev.inbox_item_id, approvalId: ev.approval_id, tool: ev.tool,
398
593
  target: ev.target, scope: ev.scope, risk: ev.risk, url: ev.dashboard_url, shownAt: now,
399
- undescribable: ev.describable === false || !ev.target
594
+ undescribable: ev.describable === false || !ev.target,
595
+ turnId: s.turn?.id ?? ev.turn_id ?? null, // the turn this card arrived in (a stream event is always this conversation)
596
+ privileged: isPrivileged({ tool: ev.tool, risk: ev.risk, flag: ev.privileged === true || ev.elevated === true })
400
597
  };
401
598
  if (s.approvals.some(x => x.callId === card.callId)) return s;
402
599
  const dup = s.approvals.findIndex(x => (card.inboxItemId && x.inboxItemId === card.inboxItemId) || (card.approvalId && x.approvalId === card.approvalId));
@@ -404,13 +601,15 @@ function reduceEvent(s, ev, now) {
404
601
  const approvals = s.approvals.slice(); approvals[dup] = { ...approvals[dup], callId: card.callId };
405
602
  return { ...s, approvals };
406
603
  }
604
+ const wi = card.inboxItemId ? s.waiting.findIndex(x => x.inboxItemId === card.inboxItemId) : -1;
605
+ if (wi >= 0) s = { ...s, waiting: s.waiting.filter((_, i) => i !== wi) }; // the stream says it is ours after all
407
606
  if (card.inboxItemId && s.seenInbox[card.inboxItemId]) return s; // already shown and answered
408
607
  return { ...s, seenInbox: card.inboxItemId ? { ...s.seenInbox, [card.inboxItemId]: true } : s.seenInbox, approvals: [...s.approvals, card], turn: s.turn ? { ...s.turn, approvals: s.turn.approvals + 1 } : s.turn };
409
608
  }
410
609
  case 'approval.resolved': {
411
610
  const card = s.approvals.find(x => x.callId === ev.call_id);
412
611
  const n = { ...s, approvals: s.approvals.filter(x => x.callId !== ev.call_id) };
413
- return card || ev.decision ? note(n, `approval ${ev.decision || 'resolved'}${card ? `: ${card.tool} on ${card.target}` : ''}`, ev.decision === 'approved' ? 'ok' : 'warn') : n;
612
+ return card || ev.decision ? note(n, `${ev.decision === 'approved' ? '✔ approved by you' : `approval ${ev.decision || 'resolved'}`}${card ? `: ${card.tool} on ${card.target}` : ''}`, ev.decision === 'approved' ? 'ok' : 'warn') : n;
414
613
  }
415
614
  case 'steer.accepted': {
416
615
  const i = s.steering.findIndex(x => x.text === ev.text);
@@ -418,10 +617,27 @@ function reduceEvent(s, ev, now) {
418
617
  return addBlock({ ...s, steering }, { kind: 'note', text: `steered: ${ev.text}`, tone: 'accent' });
419
618
  }
420
619
  case 'trace':
620
+ // QUOXON-AOCL: per-layer AOCL rows fold into ONE line per turn (aocl.summary); /aocl shows the table.
621
+ if (ev.protocol === 'volt' && /tools offered/i.test(ev.label || '')) { // kept for the footer count and /aocl, shown only in verbose
622
+ const m = String(ev.label).match(/(\d+)\s+of\s+(\d+)/);
623
+ const list = String(ev.detail || '');
624
+ const toolsOffered = m ? Number(m[1]) : (list ? list.split(/,\s*/).filter(Boolean).length : null);
625
+ if (s.turn) s = { ...s, turn: { ...s.turn, toolsOffer: { offered: toolsOffered, total: m ? Number(m[2]) : null, list } } };
626
+ }
627
+ if (isLayerTrace(ev)) return s.turn ? { ...s, turn: { ...s.turn, aoclTraces: [...(s.turn.aoclTraces || []), { label: ev.label, detail: ev.detail }] } } : s;
421
628
  if (s.verbose === 'off') return s;
422
629
  return addBlock(s, { kind: 'trace', protocol: ev.protocol, label: ev.label, ref: ev.ref, detail: ev.detail });
630
+ case 'aocl.summary': {
631
+ const sum = withTools(normaliseSummary(ev), s.turn);
632
+ const n = { ...s, lastAocl: sum, turn: s.turn ? { ...s.turn, aoclSummary: true } : s.turn };
633
+ return addBlock(n, { kind: 'aocl', sum }); // the one-line footer shows at every verbosity
634
+ }
635
+ case 'aocl.verdict': {
636
+ const v = verdictNote(ev);
637
+ return v.tone === 'error' ? addBlock(s, { kind: 'error', text: v.text }) : addBlock(s, { kind: 'note', text: v.text, tone: v.tone });
638
+ }
423
639
  case 'error': {
424
- const n = addBlock(s, { kind: 'error', text: String(ev.error || 'stream error') });
640
+ const n = addBlock(s, { kind: 'error', text: isConsentError(ev) ? consentErrorText(ev) : String(ev.error || 'stream error') });
425
641
  return n;
426
642
  }
427
643
  case 'turn.finished': case 'done': {
@@ -435,8 +651,16 @@ function reduceEvent(s, ev, now) {
435
651
  // `done` only carries text/session; turn.finished (sent just after it) ends the turn. A legacy
436
652
  // server never sends turn.finished: the app synthesises one when the stream closes.
437
653
  // usage: `done` may carry cost_usd; turn.finished carries `usage` (input/output tokens, maybe cost_usd).
438
- if (ev.type === 'done') return { ...n, turn: { ...n.turn, doneCost: ev.cost_usd } };
439
- n = { ...n, turn: { ...n.turn, usage: normUsage(ev.usage, n.turn.doneCost) } };
654
+ // cli_session_id on `done` = the claude CLI path: its cost is that CLI's own estimate even from a server that
655
+ // does not label it yet (cost_source 'claude-cli', R3 D5).
656
+ if (ev.type === 'done') return { ...n, turn: { ...n.turn, doneCost: ev.cost_usd, viaCli: Boolean(ev.cli_session_id) } };
657
+ // An older server sends per-layer AOCL traces and no aocl.summary: fold them into the one line here.
658
+ if (!n.turn.aoclSummary && n.turn.aoclTraces?.length) {
659
+ const sum = withTools(summaryFromTraces(n.turn.aoclTraces), n.turn);
660
+ n = { ...n, lastAocl: sum };
661
+ n = addBlock(n, { kind: 'aocl', sum });
662
+ }
663
+ n = { ...n, turn: { ...n.turn, usage: normUsage(n.turn.viaCli && ev.usage && !ev.usage.cost_source ? { ...ev.usage, cost_source: 'claude-cli' } : ev.usage, n.turn.doneCost) } };
440
664
  return endTurn(n, ev.status || 'completed', now);
441
665
  }
442
666
  default:
@@ -456,3 +680,19 @@ export function approvalKeyAllowed({ now, shownAt, lastKeyAt, heldKey = null, ch
456
680
  if (heldKey != null && ch === heldKey && (lastKeyAt == null || now - lastKeyAt < HELD_KEY_MS)) return false;
457
681
  return true;
458
682
  }
683
+
684
+ /**
685
+ * What an approval is for, without repeating who asked: titles often start "<who> wants to: ..." (the source label
686
+ * already says who), and a generic "Approval required" title falls back to the item's summary or description.
687
+ */
688
+ export function approvalWhat(it) {
689
+ const w = it?.payload?.what;
690
+ if (w && typeof w === 'object' && typeof w.summary === 'string' && w.summary.trim()) { // APPROVAL-WHAT: the server says what will happen
691
+ const s = w.summary.trim(); const tg = typeof w.target === 'string' ? w.target.trim() : '';
692
+ return tg && !s.includes(tg) ? `${s} (target: ${tg})` : (!tg && w.target_none ? `${s} (${String(w.target_none).trim()})` : s);
693
+ }
694
+ let t = String(it?.title || it?.id || '').replace(/^[^:]{0,80}\bwants to:\s*/i, '').trim();
695
+ const more = it?.summary || it?.description || it?.detail || '';
696
+ if ((!t || /^approval (?:required|needed)\b/i.test(t)) && more) t = String(more).trim();
697
+ return t || String(it?.id || 'approval');
698
+ }