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
@@ -2,41 +2,150 @@
2
2
  * Agent commands: CRUD, activate/deactivate, tool assignments
3
3
  */
4
4
 
5
+ import { readFileSync } from 'node:fs';
5
6
  import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
6
7
  import { resolveOrgId } from './helpers.js';
7
8
  import { resolveEndpoint } from '../api/endpoints.js';
9
+ import { QuoxApiError } from '../core/errors.js';
10
+ import { loadBundledAssistantsRoster } from '../core/agentAlias.js';
11
+ import { AGENT_FAMILY_IDS } from '../core/agentFamilies.js';
8
12
  import { registerAgentAttachCommands } from './agentAttach.js';
9
13
 
14
+ // quox#1169: the prompt, domains and parent a user gives their own agent. Auth reads
15
+ // system_prompt_additions, domains[] and parent_agent_id; it has never read manifest_id.
16
+ function agentPromptFields(options) {
17
+ if (options.prompt !== undefined && options.promptFile !== undefined) {
18
+ throw new Error('Give either --prompt or --prompt-file, not both. Nothing was sent.');
19
+ }
20
+ if (options.manifestId !== undefined) {
21
+ throw new Error('--manifest-id is not supported by the server (it was always ignored). Nothing was sent.');
22
+ }
23
+ const out = {};
24
+ if (options.prompt !== undefined) out.system_prompt_additions = String(options.prompt);
25
+ if (options.promptFile !== undefined) {
26
+ try {
27
+ out.system_prompt_additions = readFileSync(options.promptFile, 'utf8');
28
+ } catch (err) {
29
+ throw new Error(`cannot read --prompt-file ${options.promptFile}: ${err.code || err.message}. Nothing was sent.`);
30
+ }
31
+ }
32
+ if (options.domain !== undefined) out.domains = String(options.domain).split(',').map((d) => d.trim()).filter(Boolean);
33
+ if (options.parent !== undefined) out.parent_agent_id = options.parent;
34
+ return out;
35
+ }
36
+
10
37
  export function registerAgentCommands(program, ctx) {
11
38
  const { client, auth } = ctx;
12
39
 
40
+ // Registered specialists (no org row): resolved from the collector registry, quox#927.
41
+ async function registryAgent(agentId) {
42
+ try {
43
+ const ep = resolveEndpoint('v1.agents.registry');
44
+ const data = await client.get(ep.path, { service: ep.service });
45
+ return (data?.agents || []).find((r) => r.agent_id === agentId) || null;
46
+ } catch { return null; }
47
+ }
48
+ async function registryGrants(agentId) {
49
+ try {
50
+ const ep = resolveEndpoint('v1.tools.agents.list');
51
+ const data = await client.get(ep.path, { service: ep.service });
52
+ const list = data?.agentTools?.[agentId];
53
+ return Array.isArray(list) ? list : null;
54
+ } catch { return null; }
55
+ }
56
+
13
57
  const agent = program
14
58
  .command('agent')
15
59
  .description('Manage agents');
16
60
 
17
61
  agent
18
62
  .command('list')
19
- .description('List agents')
20
- .option('--status <status>', 'filter by status')
63
+ .description('List registered specialist agents and the org\'s own agents')
64
+ .option('--status <status>', 'filter by status (org agents only)')
21
65
  .option('--level <level>', 'filter by level (0-4)')
66
+ .option('--source <source>', 'registry | org | all', 'all')
67
+ .option('--plugin <id>', 'only agents that require this plugin')
68
+ .option('--family <id>', `only agents in this family (${AGENT_FAMILY_IDS.join(', ')})`)
69
+ .option('--search <text>', 'substring match on id, name or description')
22
70
  .action(async (options) => {
23
71
  auth.requireAuth();
24
- const orgId = resolveOrgId(auth, program);
25
- const query = {};
26
- if (options.status) query.status = options.status;
27
- if (options.level) query.level = options.level;
28
- // fleet #392: tool_count is only computed when includeTools=true, which
29
- // was never sent; and rows carry a `domains` ARRAY, not a `domain`.
30
- query.includeTools = 'true';
31
- const data = await client.get(resolveEndpoint('agents.list', { orgId }).path, { service: resolveEndpoint('agents.list', { orgId }).service, query });
72
+ const source = String(options.source || 'all').toLowerCase();
73
+ if (!['registry', 'org', 'all'].includes(source)) {
74
+ throw new Error(`--source must be registry, org or all (got "${options.source}")`);
75
+ }
76
+ if (options.family !== undefined && !AGENT_FAMILY_IDS.includes(options.family)) {
77
+ throw new Error(`unknown family "${options.family}". Valid families: ${AGENT_FAMILY_IDS.join(', ')}`);
78
+ }
79
+ const notes = [];
80
+
81
+ // Registered specialists (server roster; bundled roster if the server lacks the route).
82
+ let registry = [];
83
+ if (source !== 'org') {
84
+ try {
85
+ const ep = resolveEndpoint('v1.agents.registry');
86
+ const data = await client.get(ep.path, { service: ep.service });
87
+ if (!Array.isArray(data?.agents)) throw new Error('unexpected response');
88
+ registry = data.agents.map(r => ({ ...r, source: 'registry' }));
89
+ } catch (err) {
90
+ notes.push(`note: server registry unavailable (${err.message}); showing the bundled roster, which may be out of date.`);
91
+ registry = loadBundledAssistantsRoster().map(r => ({
92
+ agent_id: r.id, name: r.name || r.id, level: null, requires_plugin: null, family: r.family || null, tool_count: null, source: 'registry',
93
+ }));
94
+ }
95
+ }
96
+
97
+ // The org's own agent rows.
98
+ let orgRows = [];
99
+ if (source !== 'registry') {
100
+ try {
101
+ const orgId = resolveOrgId(auth, program);
102
+ const query = { includeTools: 'true' };
103
+ if (options.status) query.status = options.status;
104
+ if (options.level) query.level = options.level;
105
+ const ep = resolveEndpoint('agents.list', { orgId });
106
+ const data = await client.get(ep.path, { service: ep.service, query });
107
+ orgRows = (data.agents || data || []).map(r => ({ ...r, source: 'org' }));
108
+ } catch (err) {
109
+ if (source === 'org') throw err;
110
+ notes.push(`note: org agents unavailable (${err.message}); showing registered specialists only.`);
111
+ }
112
+ }
113
+
114
+ // Merge by agent_id: org row wins for status, registry supplies plugin/description.
115
+ const merged = new Map();
116
+ for (const r of registry) merged.set(r.agent_id, r);
117
+ for (const o of orgRows) {
118
+ const key = o.agent_id || o.id;
119
+ const reg = merged.get(key);
120
+ merged.set(key, reg
121
+ ? { ...reg, ...o, requires_plugin: reg.requires_plugin, family: reg.family ?? null, description: reg.description ?? o.description, tool_count: o.tool_count ?? reg.tool_count, source: 'org+registry' }
122
+ : o);
123
+ }
124
+
125
+ let all = [...merged.values()];
126
+ if (options.level !== undefined) all = all.filter(r => String(r.level) === String(options.level));
127
+ if (options.status) all = all.filter(r => r.status === options.status);
128
+ if (options.plugin) all = all.filter(r => r.requires_plugin === options.plugin);
129
+ if (options.family) all = all.filter(r => r.family === options.family);
130
+ if (options.search) {
131
+ const q = options.search.toLowerCase();
132
+ all = all.filter(r => [r.agent_id, r.name, r.description].some(v => String(v ?? '').toLowerCase().includes(q)));
133
+ }
134
+ all.sort((x, y) => String(x.agent_id).localeCompare(String(y.agent_id)));
135
+
136
+ for (const n of notes) console.error(n);
137
+ const dash = (v) => (v === null || v === undefined || v === '' ? '-' : v);
32
138
  formatOutput({
33
- columns: ['id', 'agent_id', 'name', 'level', 'status', 'domains', 'tool_count'],
34
- headers: ['ID', 'Agent ID', 'Name', 'Level', 'Status', 'Domains', 'Tools'],
35
- rows: (data.agents || data || []).map(a => ({
36
- ...a,
37
- domains: Array.isArray(a.domains) ? a.domains.join(', ') : (a.domains ?? '-'),
139
+ columns: ['agent_id', 'name', 'family', 'source', 'level', 'state', 'tool_count'],
140
+ headers: ['Agent ID', 'Name', 'Family', 'Source', 'Level', 'Status/Plugin', 'Tools'],
141
+ rows: all.map(r => ({
142
+ ...r,
143
+ family: dash(r.family),
144
+ level: dash(r.level),
145
+ state: r.status ? r.status : (r.requires_plugin ? `plugin:${r.requires_plugin}` : '-'),
146
+ tool_count: dash(r.tool_count),
38
147
  })),
39
- }, { format: program.opts().output });
148
+ }, { format: program.opts().output, raw: all, emptyMessage: 'No agents match.' });
40
149
  });
41
150
 
42
151
  agent
@@ -46,7 +155,16 @@ export function registerAgentCommands(program, ctx) {
46
155
  .action(async (agentId) => {
47
156
  auth.requireAuth();
48
157
  const orgId = resolveOrgId(auth, program);
49
- const data = await client.get(resolveEndpoint('agents.get', { orgId, agentId }).path, { service: resolveEndpoint('agents.get', { orgId, agentId }).service });
158
+ let data;
159
+ try {
160
+ data = await client.get(resolveEndpoint('agents.get', { orgId, agentId }).path, { service: resolveEndpoint('agents.get', { orgId, agentId }).service });
161
+ } catch (err) {
162
+ // quox#927: `agent list` also shows registered specialists that have no org row;
163
+ // resolve those from the registry instead of answering 404 for an id list printed.
164
+ const reg = err?.status === 404 ? await registryAgent(agentId) : null;
165
+ if (!reg) throw err;
166
+ data = { agent: { ...reg, source: 'registry' } };
167
+ }
50
168
  formatDetail(data.agent || data, {
51
169
  format: program.opts().output,
52
170
  title: data.agent?.name || data.name,
@@ -62,19 +180,22 @@ export function registerAgentCommands(program, ctx) {
62
180
  .option('--domain <domain>', 'agent domain')
63
181
  .option('--description <desc>', 'description')
64
182
  .option('--parent <parentId>', 'parent agent ID')
65
- .option('--manifest-id <id>', 'manifest ID')
183
+ .option('--manifest-id <id>', 'not supported: refused (the server never read it)')
184
+ .option('--prompt <text>', "the agent's instructions (its system prompt)")
185
+ .option('--prompt-file <path>', "read the agent's instructions from a file")
66
186
  .action(async (options) => {
67
187
  auth.requireAuth();
68
188
  const orgId = resolveOrgId(auth, program);
189
+ const fields = agentPromptFields(options);
69
190
  const body = {
70
191
  name: options.name,
71
192
  level: parseInt(options.level, 10),
193
+ ...fields,
72
194
  };
73
195
  if (options.agentId) body.agent_id = options.agentId;
196
+ // legacy key kept so older servers and tests see it; auth reads domains[] (set above)
74
197
  if (options.domain) body.domain = options.domain;
75
198
  if (options.description) body.description = options.description;
76
- if (options.parent) body.parent_id = options.parent;
77
- if (options.manifestId) body.manifest_id = options.manifestId;
78
199
  await client.post(resolveEndpoint('agents.create', { orgId }).path, { service: resolveEndpoint('agents.create', { orgId }).service, body });
79
200
  printSuccess(`Agent "${options.name}" created.`);
80
201
  });
@@ -87,10 +208,14 @@ export function registerAgentCommands(program, ctx) {
87
208
  .option('--description <desc>', 'new description')
88
209
  .option('--level <level>', 'new level')
89
210
  .option('--status <status>', 'new status')
211
+ .option('--prompt <text>', "new instructions (system prompt)")
212
+ .option('--prompt-file <path>', 'read new instructions from a file')
213
+ .option('--domain <domains>', 'comma-separated domains')
214
+ .option('--parent <parentId>', 'parent agent ID')
90
215
  .action(async (agentId, options) => {
91
216
  auth.requireAuth();
92
217
  const orgId = resolveOrgId(auth, program);
93
- const body = {};
218
+ const body = { ...agentPromptFields(options) };
94
219
  if (options.name) body.name = options.name;
95
220
  if (options.description) body.description = options.description;
96
221
  if (options.level) body.level = parseInt(options.level, 10);
@@ -133,7 +258,7 @@ export function registerAgentCommands(program, ctx) {
133
258
  const orgId = resolveOrgId(auth, program);
134
259
  await client.patch(resolveEndpoint('agents.update', { orgId, agentId }).path, {
135
260
  service: resolveEndpoint('agents.update', { orgId, agentId }).service,
136
- body: { status: 'inactive' },
261
+ body: { status: 'paused' }, // #985: auth accepts draft|active|paused|retired; 'inactive' 400'd
137
262
  });
138
263
  printSuccess('Agent deactivated.');
139
264
  });
@@ -150,7 +275,15 @@ export function registerAgentCommands(program, ctx) {
150
275
  .action(async (agentId) => {
151
276
  auth.requireAuth();
152
277
  const orgId = resolveOrgId(auth, program);
153
- const data = await client.get(resolveEndpoint('agents.tools.list', { orgId, agentId }).path, { service: resolveEndpoint('agents.tools.list', { orgId, agentId }).service });
278
+ let data;
279
+ try {
280
+ data = await client.get(resolveEndpoint('agents.tools.list', { orgId, agentId }).path, { service: resolveEndpoint('agents.tools.list', { orgId, agentId }).service });
281
+ } catch (err) {
282
+ // quox#927: a registered specialist's tools are its registry grants, not org assignments.
283
+ const grants = err?.status === 404 ? await registryGrants(agentId) : null;
284
+ if (!grants) throw err;
285
+ data = { tools: grants.map((name) => ({ tool_id: name, tool_name: name, category: '-', access_mode: 'registry grant', enabled: true })) };
286
+ }
154
287
  formatOutput({
155
288
  // fleet #392: assignment rows carry tool_name/category/enabled.
156
289
  columns: ['tool_id', 'tool_name', 'category', 'access_mode', 'enabled'],
@@ -168,10 +301,20 @@ export function registerAgentCommands(program, ctx) {
168
301
  .action(async (agentId, options) => {
169
302
  auth.requireAuth();
170
303
  const orgId = resolveOrgId(auth, program);
171
- await client.post(resolveEndpoint('agents.tools.create', { orgId, agentId }).path, {
172
- service: resolveEndpoint('agents.tools.create', { orgId, agentId }).service,
173
- body: { tool_id: options.tool, access_mode: options.accessMode },
174
- });
304
+ try {
305
+ await client.post(resolveEndpoint('agents.tools.create', { orgId, agentId }).path, {
306
+ service: resolveEndpoint('agents.tools.create', { orgId, agentId }).service,
307
+ body: { tool_id: options.tool, access_mode: options.accessMode },
308
+ });
309
+ } catch (err) {
310
+ // quox#1072: a built-in agent's tools come from its registry grant and
311
+ // hat. The server says so in prose; surface that sentence as the error
312
+ // with a stable code, so it does not read as a generic conflict.
313
+ if (err?.status === 409 && err.body?.code === 'BUILTIN_AGENT_TOOLS_FROM_GRANT') {
314
+ throw new QuoxApiError(409, err.body.code, err.body.message || err.body.error, undefined, err.body);
315
+ }
316
+ throw err;
317
+ }
175
318
  printSuccess(`Tool assigned to agent.`);
176
319
  });
177
320
 
@@ -435,6 +435,38 @@ export function execResultFailed(data) {
435
435
  return negative(data.success) || negative(data.ok);
436
436
  }
437
437
 
438
+ /**
439
+ * A governed tool that was held for a human returns { success:false, pending:true, approvalId }. That is not a
440
+ * failure: the approval is filed and waiting. Returns the approval id, or null when the result is not pending.
441
+ */
442
+ export function execResultPending(data) {
443
+ if (!data || typeof data !== 'object') return null;
444
+ const src = data.pending === true ? data : (data.result && typeof data.result === 'object' && data.result.pending === true ? data.result : null);
445
+ if (!src) return null;
446
+ return src.approvalId || src.approval_id || (src._approval && src._approval.id) || 'unknown';
447
+ }
448
+
449
+ /**
450
+ * A governed tool held by the policy layer answers HTTP 403 with {pending:true, approvalId}: the approval IS filed.
451
+ * Returns that body when an API error carries one (so the caller renders awaiting approval), else null.
452
+ */
453
+ export function pendingBodyOfError(err) {
454
+ const body = err && err.body && typeof err.body === 'object' ? err.body : null;
455
+ return body && execResultPending(body) ? body : null;
456
+ }
457
+
458
+ /**
459
+ * Is this 401/403 really about the attach token? 401 is an unauthenticated bearer. A 403 counts only when the server
460
+ * says so (attach/lease/token in its code or message); any other 403 is the server's own refusal and is shown verbatim.
461
+ */
462
+ export function isAttachTokenError(err) {
463
+ const status = err && err.status;
464
+ if (status === 401) return true;
465
+ if (status !== 403) return false;
466
+ const text = [err && err.code, err && err.body && err.body.error, err && err.body && err.body.error_code, err && err.message].filter(Boolean).join(' ');
467
+ return /attach|lease|token/i.test(text);
468
+ }
469
+
438
470
  /** The first human-readable error string a failed tool result carries, or a default. */
439
471
  function execResultError(data) {
440
472
  return (data && (data.error || data.error_code || data.reason || data.message)) || 'tool reported failure';
@@ -699,6 +731,36 @@ export async function heartbeatAttachLease(client, agentId, { getOrgId } = {}) {
699
731
  return { leaseId, expires: newExpiry || 'unchanged' };
700
732
  }
701
733
 
734
+ /**
735
+ * #893: re-lease an attach that LAPSED (expired while heartbeats could not get
736
+ * through, e.g. an auth stall longer than the TTL). Never takes over: a 409
737
+ * here means another session holds the agent, and the keeper then exits as
738
+ * before. Rewrites only the token, lease id and expiry; the installed kit
739
+ * (prompt, tools, memory) is unchanged.
740
+ */
741
+ const leaseMetaPath = (agentId) => join(dirname(tokenPath(agentId)), 'lease-meta.json');
742
+
743
+ export async function reacquireAttachLease(client, agentId) {
744
+ const stored = readStoredKit(agentId);
745
+ if (!stored?.lease?.id) throw new QuoxApiError(404, 'no_local_lease', `No local attach state for "${agentId}"`);
746
+ let meta = {};
747
+ try { meta = JSON.parse(readFileSync(leaseMetaPath(agentId), 'utf-8')); } catch { /* defaults */ }
748
+ const ep = resolveEndpoint('agent-attach.lease.create', { agentId });
749
+ const data = await client.post(ep.path, {
750
+ service: ep.service,
751
+ body: {
752
+ session_label: meta.session_label || agentId,
753
+ ttl_seconds: meta.ttl_seconds || 900,
754
+ takeover: false,
755
+ },
756
+ });
757
+ if (!data?.token || !data?.lease?.id) throw new Error('re-lease returned no token or lease id');
758
+ writeFile0600(tokenPath(agentId), `${data.token}\n`);
759
+ stored.lease = { ...stored.lease, id: data.lease.id, expires_at: data.lease.expires_at || data.expires_at || null };
760
+ writeFile0600(kitPath(agentId), `${JSON.stringify(stored, null, 2)}\n`);
761
+ return { leaseId: data.lease.id };
762
+ }
763
+
702
764
  /**
703
765
  * P10: keep the bound slot (or this session's membership of it) alive with the
704
766
  * ATTACH token. Failures are logged, never fatal to the lease heartbeat.
@@ -897,15 +959,36 @@ export function registerAgentAttachCommands(agent, ctx, program) {
897
959
  });
898
960
  } catch (err) {
899
961
  const status = err instanceof QuoxApiError ? err.status : 'network error';
962
+ // quox#1026 (b): the kit cannot resolve this agent at all, so no retry can succeed. Release the lease
963
+ // just granted (never leave it held) and say plainly which agents are attachable.
964
+ if (err instanceof QuoxApiError && err.status === 404 && err.code === 'unknown_agent' && lease.id) {
965
+ let released = false;
966
+ try {
967
+ const relEp = resolveEndpoint('agent-attach.lease.release', { leaseId: lease.id });
968
+ await client.post(relEp.path, { service: relEp.service, body: {} });
969
+ released = true;
970
+ } catch { /* reported below */ }
971
+ throw new Error(
972
+ `"${agentId}" is not attachable: only core registry agents and custom assistants can be attached `
973
+ + '(an agent made with `quox agent create` has no attach kit yet, quox#1026). '
974
+ + (released ? `The lease ${lease.id} was released.` : `Releasing lease ${lease.id} failed: run \`quox agent detach ${agentId} --lease ${lease.id}\`.`)
975
+ );
976
+ }
900
977
  throw new Error(
901
978
  `GET ${kitEp.path} failed (${status}) after the lease was granted (lease ${lease.id || 'unknown'}). `
902
- + `The lease is still active: retry the kit fetch, or run \`quox agent detach ${agentId}\` to release it. `
979
+ + `The lease is still active: retry the kit fetch, or run \`quox agent detach ${agentId}${lease.id ? ` --lease ${lease.id}` : ''}\` to release it. `
903
980
  + `Original error: ${err.message}`
904
981
  );
905
982
  }
906
983
 
907
984
  const splinter = splinterInfoFrom(leaseData?.splinter);
908
985
  const paths = installAttachKit(agentId, { kit, token, splinter });
986
+ // #893: remember the label and TTL (beside the token, not in kit.json,
987
+ // which stays exactly what the server sent) so the keeper can re-lease a
988
+ // lapsed attach under the same identity.
989
+ try {
990
+ writeFile0600(leaseMetaPath(agentId), `${JSON.stringify({ session_label: attachLabel, ttl_seconds: options.ttl })}\n`);
991
+ } catch { /* the attach stands; a re-lease falls back to defaults */ }
909
992
  // ONE-BRAIN CX4: agents whose resolved tool list carries brain_context get
910
993
  // a primer from the brain. Best effort, bounded, never fails the attach.
911
994
  let primer = '';
@@ -1074,6 +1157,7 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1074
1157
  .option('--handoff <file>', 'path to a file with a handoff note (state, open threads, next moves) to store before detaching')
1075
1158
  .option('--handoff-text <text>', 'handoff note text, provided inline instead of a file')
1076
1159
  .option('--force-detach', 'release the lease even if storing the handoff note fails')
1160
+ .option('--lease <leaseId>', 'release this lease when there is no local attach kit (an attach that failed after its lease was granted); the server allows only its holder or an org admin')
1077
1161
  .option('--no-brain-note', 'do not also save the handoff to the knowledge brain as a private note')
1078
1162
  .action(async (agentId, options) => {
1079
1163
  auth.requireAuth();
@@ -1081,7 +1165,20 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1081
1165
  agentId = await resolveAgentId(client, agentId);
1082
1166
  const stored = readStoredKit(agentId);
1083
1167
  const leaseId = stored?.lease?.id;
1084
- if (!leaseId) throw noLocalLeaseError(agentId, 'attach-status');
1168
+ // #1003: no local kit (attach failed after the lease was granted). Release exactly the lease the caller
1169
+ // names, never a guessed one: a lease held by another session must not be released by inference.
1170
+ if (!leaseId && options.lease) {
1171
+ const relEp = resolveEndpoint('agent-attach.lease.release', { leaseId: options.lease });
1172
+ await client.post(relEp.path, { service: relEp.service, body: {} });
1173
+ removeAttachDir(agentId);
1174
+ printEvent(events.released('quox', `attach ${agentId}`, `lease ${String(options.lease).slice(0, 8)} (no local kit)`));
1175
+ return;
1176
+ }
1177
+ if (!leaseId) {
1178
+ throw new Error(`${noLocalLeaseError(agentId, 'attach-status').message} `
1179
+ + `If an attach failed after its lease was granted, release that lease with \`quox agent detach ${agentId} --lease <leaseId>\` `
1180
+ + `(the lease id is in the attach error and in \`quox agent attach-status ${agentId}\`).`);
1181
+ }
1085
1182
 
1086
1183
  if (options.handoff && options.handoffText) {
1087
1184
  throw new QuoxError('Pass only one of --handoff or --handoff-text.', 2);
@@ -1173,6 +1270,7 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1173
1270
  sleep: ctx.sleep,
1174
1271
  dir: ctx.keeperDir,
1175
1272
  heartbeat: () => heartbeatAttachLease(client, agentId, { getOrgId: () => resolveOrgId(auth, program) }),
1273
+ reacquire: () => reacquireAttachLease(client, agentId),
1176
1274
  release: async () => {
1177
1275
  const leaseId = readStoredKit(agentId)?.lease?.id;
1178
1276
  if (!leaseId) return;
@@ -1213,10 +1311,10 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1213
1311
  // the route deployed) degrades to silence, not an error.
1214
1312
  try {
1215
1313
  const bindingEp = resolveEndpoint('agent-attach.slot-binding.get', { agentId });
1216
- const binding = await client.get(bindingEp.path, { service: bindingEp.service });
1217
- if (binding?.name) {
1218
- printInfo(` slot: ${binding.name}${binding.holder_session ? ` (held by ${binding.holder_session})` : ' (free)'}`);
1219
- }
1314
+ // quox#1308: the route answers { binding: { slot_name, ... } }; this read .name off the body and never printed.
1315
+ const res = await client.get(bindingEp.path, { service: bindingEp.service });
1316
+ const slotName = res?.binding?.slot_name;
1317
+ if (slotName) printInfo(` slot: ${slotName}`);
1220
1318
  } catch {
1221
1319
  // No binding, or the route isn't deployed yet: not a status-command failure.
1222
1320
  }
@@ -1277,13 +1375,17 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1277
1375
  });
1278
1376
  } catch (err) {
1279
1377
  const status = err instanceof QuoxApiError ? err.status : 'network error';
1280
- if (err instanceof QuoxApiError && (err.status === 401 || err.status === 403)) {
1378
+ const held = err instanceof QuoxApiError ? pendingBodyOfError(err) : null;
1379
+ if (held) {
1380
+ data = held; // the policy layer filed an approval: that is a result, not an error
1381
+ } else if (err instanceof QuoxApiError && isAttachTokenError(err)) {
1281
1382
  throw new Error(
1282
1383
  `POST ${ep.path} failed (${status}): the attach token for "${agentId}" is no longer valid `
1283
1384
  + `(expired or revoked lease). Re-attach with \`quox agent attach ${agentId}\`.`
1284
1385
  );
1386
+ } else {
1387
+ throw new Error(`POST ${ep.path} failed (${status}): ${err.message}`);
1285
1388
  }
1286
- throw new Error(`POST ${ep.path} failed (${status}): ${err.message}`);
1287
1389
  }
1288
1390
 
1289
1391
  // finding (autonomous loop): the glyph must reflect the TOOL result, not
@@ -1292,14 +1394,17 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1292
1394
  // agent read failures as passes ([error-body-is-not-data] at the CLI
1293
1395
  // surface). Set a non-zero exit code too so a scripted / feedback-loop
1294
1396
  // caller can't mistake it for a pass.
1295
- const toolOk = !execResultFailed(data);
1397
+ const pendingId = execResultPending(data);
1398
+ const toolOk = pendingId ? true : !execResultFailed(data);
1296
1399
  // Skip the announce line under --json/-o json: a scripted caller parses
1297
1400
  // stdout as a single JSON document, and the glyph line would break that.
1298
1401
  if (program.opts().output !== 'json') {
1299
- printEvent(line('quox', `exec ${options.tool}`, {
1300
- result: toolOk ? agentId : `${agentId} · ${execResultError(data)}`,
1301
- status: toolOk ? 'ok' : 'fail',
1302
- }));
1402
+ printEvent(line('quox', `exec ${options.tool}`, pendingId
1403
+ ? { result: `${agentId} · awaiting approval ${pendingId}`, status: 'busy' }
1404
+ : {
1405
+ result: toolOk ? agentId : `${agentId} · ${execResultError(data)}`,
1406
+ status: toolOk ? 'ok' : 'fail',
1407
+ }));
1303
1408
  }
1304
1409
  formatDetail(data, { format: program.opts().output, title: `Tool result: ${options.tool}` });
1305
1410
  if (!toolOk) process.exitCode = 1;
@@ -1328,6 +1433,28 @@ export function registerAgentAttachCommands(agent, ctx, program) {
1328
1433
  if (program.opts().output === 'json') formatDetail(data, { format: 'json' });
1329
1434
  });
1330
1435
 
1436
+ // quox#1308: bind-slot was write-only. Reading an address is open to any org member (the GET route has no
1437
+ // admin gate). No binding is an answer, not a failure: exit 0, and --json prints { binding: null }.
1438
+ agent
1439
+ .command('slot')
1440
+ .description("Show which slot an agent is bound to (the only slot a user-made agent may post to)")
1441
+ .argument('<agentId>', 'registry agent id')
1442
+ .action(async (agentId) => {
1443
+ auth.requireAuth();
1444
+ const ep = resolveEndpoint('agent-attach.slot-binding.get', { agentId });
1445
+ let binding = null;
1446
+ try {
1447
+ const res = await client.get(ep.path, { service: ep.service });
1448
+ binding = res?.binding || null;
1449
+ } catch (err) {
1450
+ const status = err instanceof QuoxApiError ? err.status : 'network error';
1451
+ if (!(status === 404 && err.code === 'no_binding')) throw new Error(`GET ${ep.path} failed (${status}): ${err.message}`);
1452
+ }
1453
+ if (program.opts().output === 'json') { formatDetail({ binding }, { format: 'json' }); return; }
1454
+ if (!binding) { printInfo(`${agentId} has no slot binding. An org admin can bind one: quox agent bind-slot ${agentId} <slot>`); return; }
1455
+ printInfo(`${agentId} is bound to slot ${binding.slot_name}${binding.created_at ? ` (since ${binding.created_at})` : ''}`);
1456
+ });
1457
+
1331
1458
  agent
1332
1459
  .command('unbind-slot')
1333
1460
  .description("Remove an agent identity's slot binding (org admin only)")
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Approvals commands: result (quox#791)
2
+ * Approvals commands: result (quox#791), grants list|create|revoke (fleet standing grants, 2026-10-13)
3
3
  *
4
4
  * After a human approves a held tool call (e.g. a gated ssh_exec) the collector
5
5
  * runs it and stores the outcome. `quox approvals result <id>` reads it back.
@@ -7,12 +7,15 @@
7
7
 
8
8
  import { resolveEndpoint } from '../api/endpoints.js';
9
9
 
10
+ import { attachWalkthrough } from './walkthrough.js';
11
+
10
12
  export function registerApprovalsCommands(program, ctx) {
11
13
  const { client, auth } = ctx;
12
14
 
13
15
  const approvals = program
14
16
  .command('approvals')
15
17
  .description('Tool approvals (held actions awaiting or past a human decision)');
18
+ attachWalkthrough(approvals, ctx, program);
16
19
 
17
20
  approvals
18
21
  .command('result <approvalId>')
@@ -31,6 +34,11 @@ export function registerApprovalsCommands(program, ctx) {
31
34
  }
32
35
  console.log(`Approval ${data.approval_id} tool: ${data.tool} status: ${data.status}`
33
36
  + (data.executed_at ? ` executed: ${data.executed_at}` : ''));
37
+ if (data.decided_by) {
38
+ const who = data.decided_by.name || data.decided_by.id;
39
+ const via = data.decided_via && data.decided_via.channel ? ` via ${data.decided_via.channel}` : '';
40
+ console.log(`decided by ${who} (${data.decided_by.actor_type || 'user'})${via} at ${data.decided_at || 'unknown time'}`);
41
+ }
34
42
  if (data.result === null || data.result === undefined) {
35
43
  if (data.status === 'pending') console.log('Still pending: a human has not decided yet.');
36
44
  else if (data.status === 'resuming') console.log('Approved, executing. Re-run shortly.');
@@ -44,4 +52,73 @@ export function registerApprovalsCommands(program, ctx) {
44
52
  if (out !== undefined) console.log(typeof out === 'string' ? out : JSON.stringify(out, null, 2));
45
53
  else console.log(JSON.stringify(r, null, 2));
46
54
  });
55
+
56
+ // Fleet standing grants: an org admin pre-approves ONE host + an anchored command
57
+ // shape + a VMID range until an expiry, for ssh_exec. Every use still lands in
58
+ // approvals history with an AEE envelope; anything outside the grant files HITL.
59
+ const grants = approvals
60
+ .command('grants')
61
+ .description('Standing grants: org-admin pre-approved fleet command lanes (ssh_exec)');
62
+
63
+ grants
64
+ .command('list')
65
+ .description('List active standing grants (--all includes revoked and expired)')
66
+ .option('--all', 'include revoked and expired grants')
67
+ .action(async (options) => {
68
+ auth.requireAuth();
69
+ const ep = resolveEndpoint('v1.approvals.grants.list');
70
+ const data = await client.get(ep.path, { service: ep.service, ...(options.all ? { query: { all: '1' } } : {}) });
71
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
72
+ const rows = data.grants || [];
73
+ if (!rows.length) { console.log('No standing grants.'); return; }
74
+ for (const g of rows) {
75
+ const state = g.revoked_at ? `revoked ${g.revoked_at}` : `expires ${g.expires_at}`;
76
+ const range = g.vmid_min != null ? ` vmids ${g.vmid_min}-${g.vmid_max}` : '';
77
+ console.log(`${g.grant_id} ${g.tool}@${g.host}${range} ${g.preset || `${g.patterns.length} patterns`} uses ${g.use_count} ${state}`);
78
+ console.log(` reason: ${g.reason} (by ${g.created_by})`);
79
+ }
80
+ });
81
+
82
+ grants
83
+ .command('create')
84
+ .description('Create a standing grant (human org admin only)')
85
+ .requiredOption('--host <hostId>', 'the ONE host the grant covers')
86
+ .requiredOption('--reason <text>', 'why (shown in history and evidence)')
87
+ .option('--preset <name>', 'proxmox-test-vm-lane (clone/power/destroy/guest-exec in a VMID range)')
88
+ .option('--template <vmid>', 'template VMID the lane clones from (preset)')
89
+ .option('--vmid-range <min-max>', 'VMID range, e.g. 9100-9199')
90
+ .option('--name-prefix <prefix>', 'VM name prefix the lane may create (preset)', 'z2h')
91
+ .option('--pattern <regex>', 'custom anchored command regex (repeatable; instead of --preset)', (v, a) => (a || []).concat([v]))
92
+ .option('--days <n>', 'expiry in days (max 30)', '14')
93
+ .action(async (options) => {
94
+ auth.requireAuth();
95
+ const body = { tool: 'ssh_exec', host: options.host, reason: options.reason, expires_in_days: Number(options.days) };
96
+ if (options.vmidRange) {
97
+ const m = /^(\d+)-(\d+)$/.exec(options.vmidRange);
98
+ if (!m) { console.error('--vmid-range must look like 9100-9199'); process.exitCode = 1; return; }
99
+ body.vmid_min = Number(m[1]); body.vmid_max = Number(m[2]);
100
+ }
101
+ if (options.preset) {
102
+ body.preset = options.preset; body.template_vmid = Number(options.template); body.name_prefix = options.namePrefix;
103
+ } else {
104
+ body.patterns = options.pattern || [];
105
+ }
106
+ const ep = resolveEndpoint('v1.approvals.grants.create');
107
+ const data = await client.post(ep.path, { service: ep.service, body });
108
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
109
+ const g = data.grant || {};
110
+ console.log(`Created ${g.grant_id}: ${g.tool}@${g.host}${g.vmid_min != null ? ` vmids ${g.vmid_min}-${g.vmid_max}` : ''}, expires ${g.expires_at}`);
111
+ console.log(`Revoke any time: quox approvals grants revoke ${g.grant_id}`);
112
+ });
113
+
114
+ grants
115
+ .command('revoke <grantId>')
116
+ .description('Revoke a standing grant now (human org admin only)')
117
+ .action(async (grantId) => {
118
+ auth.requireAuth();
119
+ const ep = resolveEndpoint('v1.approvals.grants.revoke', { id: grantId });
120
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
121
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
122
+ console.log(`Revoked ${data.grant?.grant_id || grantId} at ${data.grant?.revoked_at || 'now'}.`);
123
+ });
47
124
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * quox aside "<question>"
3
+ *
4
+ * A read-only side question: the model gets no tools and no conversation, so nothing it says can change
5
+ * anything or land in a conversation's history. The TUI's /aside is the same call with the recent
6
+ * exchanges passed as read-only context.
7
+ */
8
+ import { askAside } from '../core/aside.js';
9
+
10
+ export function registerAsideCommand(program, ctx) {
11
+ const { client, auth } = ctx;
12
+ program
13
+ .command('aside')
14
+ .description('Ask a read-only side question: no tools, not saved to any conversation')
15
+ .argument('<question...>', 'the question')
16
+ .option('--agent <id>', 'who answers (default quox)')
17
+ .action(async (words, opts) => {
18
+ auth.requireAuth();
19
+ const r = await askAside(client, { question: words.join(' '), agent: opts.agent });
20
+ if (program.opts().output === 'json') { console.log(JSON.stringify({ answer: r.text, model: r.model, provider: r.provider, tools_used: r.tools, read_only: true }, null, 2)); return; }
21
+ console.log(r.text || '(no answer)');
22
+ console.log(`· aside, read only: no tools, not saved${r.model ? ` · ${r.provider ? `${r.provider}/` : ''}${r.model}` : ''}`);
23
+ });
24
+ }