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.
- package/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
package/lib/commands/agent.js
CHANGED
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
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: ['
|
|
34
|
-
headers: ['ID', '
|
|
35
|
-
rows:
|
|
36
|
-
...
|
|
37
|
-
|
|
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
|
-
|
|
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>', '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1301
|
-
|
|
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
|
+
}
|