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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Tracker item writes and open-item reads, shared by `quox actions
|
|
3
|
+
* add|done` and the Quox TUI /todo and /todos. One core, so chat, CLI and TUI
|
|
4
|
+
* create the same item through the same collector route
|
|
5
|
+
* (POST /api/v1/actions, PATCH /api/v1/actions/:id), as the logged-in user.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
9
|
+
import { QuoxError } from './errors.js';
|
|
10
|
+
|
|
11
|
+
export const PRIORITIES = ['low', 'medium', 'high', 'urgent'];
|
|
12
|
+
const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
13
|
+
|
|
14
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
15
|
+
const dayOf = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
16
|
+
|
|
17
|
+
/** "today", "tomorrow" or YYYY-MM-DD -> YYYY-MM-DD. Anything else is a usage error, never guessed. */
|
|
18
|
+
export function parseDue(value, now = new Date()) {
|
|
19
|
+
if (value === undefined || value === null || value === '') return null;
|
|
20
|
+
const v = String(value).trim().toLowerCase();
|
|
21
|
+
if (v === 'today') return dayOf(now);
|
|
22
|
+
if (v === 'tomorrow') return dayOf(new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1));
|
|
23
|
+
if (ISO_DAY.test(v) && !Number.isNaN(Date.parse(`${v}T00:00:00Z`))) return v;
|
|
24
|
+
throw new QuoxError(`--due must be YYYY-MM-DD, today or tomorrow (got "${value}")`, 2);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Words typed after the verb -> a title. Empty or blank is a usage error. */
|
|
28
|
+
export function titleFromWords(words) {
|
|
29
|
+
const title = (Array.isArray(words) ? words.join(' ') : String(words || '')).trim();
|
|
30
|
+
if (!title) throw new QuoxError('Usage: quox actions add <text...> [--due <date>] [--priority low|medium|high|urgent]', 2);
|
|
31
|
+
return title;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Throw unless the response carries an item with an id: an error body is never a created item. */
|
|
35
|
+
function itemOf(res, what) {
|
|
36
|
+
const item = res && res.item;
|
|
37
|
+
if (!item || !item.id || res.success === false) {
|
|
38
|
+
throw new QuoxError(`${what} failed: ${(res && (res.error || res.message)) || 'the server returned no item'}`);
|
|
39
|
+
}
|
|
40
|
+
return item;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Create one Action Tracker item (status todo) as the logged-in user. -> the item. */
|
|
44
|
+
export async function addActionItem(client, { title, due, priority, description } = {}) {
|
|
45
|
+
const t = titleFromWords(title);
|
|
46
|
+
if (priority && !PRIORITIES.includes(priority)) {
|
|
47
|
+
throw new QuoxError(`--priority must be one of ${PRIORITIES.join(', ')}`, 2);
|
|
48
|
+
}
|
|
49
|
+
const body = { title: t };
|
|
50
|
+
const dueDate = parseDue(due);
|
|
51
|
+
if (dueDate) body.due_date = dueDate;
|
|
52
|
+
if (priority) body.priority = priority;
|
|
53
|
+
if (description) body.description = description;
|
|
54
|
+
const ep = resolveEndpoint('actions.create');
|
|
55
|
+
return itemOf(await client.post(ep.path, { service: ep.service, body }), 'Adding the action item');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const STATUS_COLUMNS = ['todo', 'doing', 'done'];
|
|
59
|
+
|
|
60
|
+
/** Move one item to todo, doing or done. -> the item. */
|
|
61
|
+
export async function moveActionItem(client, id, status) {
|
|
62
|
+
const clean = String(id || '').trim();
|
|
63
|
+
if (!clean) throw new QuoxError('Usage: quox actions move <id> <todo|doing|done>', 2);
|
|
64
|
+
if (!STATUS_COLUMNS.includes(status)) throw new QuoxError(`status must be one of ${STATUS_COLUMNS.join(', ')}`, 2);
|
|
65
|
+
const ep = resolveEndpoint('actions.update', { id: clean });
|
|
66
|
+
return itemOf(await client.patch(ep.path, { service: ep.service, body: { status } }), 'Moving the action item');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Mark one item done. -> the item. */
|
|
70
|
+
export async function completeActionItem(client, id) {
|
|
71
|
+
if (!String(id || '').trim()) throw new QuoxError('Usage: quox actions done <id>', 2);
|
|
72
|
+
return moveActionItem(client, id, 'done');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Every item in one column (or all), paged to the end; never a silent cap. -> [{id,title,status,priority,due_date}]. */
|
|
76
|
+
export async function listActionItems(client, status = 'all', { pageSize = 100, maxPages = 50 } = {}) {
|
|
77
|
+
const ep = resolveEndpoint('actions.list');
|
|
78
|
+
const out = [];
|
|
79
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
80
|
+
const query = { limit: pageSize, page };
|
|
81
|
+
if (status !== 'all') query.status = status;
|
|
82
|
+
const res = await client.get(ep.path, { service: ep.service, query, retries: 0 });
|
|
83
|
+
if (!res || !Array.isArray(res.items)) {
|
|
84
|
+
throw new QuoxError(`Listing action items failed: ${(res && (res.error || res.message)) || 'unexpected response'}`);
|
|
85
|
+
}
|
|
86
|
+
out.push(...res.items);
|
|
87
|
+
if (!(res.pagination && res.pagination.has_more)) return out;
|
|
88
|
+
}
|
|
89
|
+
throw new QuoxError(`More than ${maxPages * pageSize} action items; narrow with quox actions list --status.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The board by column. -> { backlog (status todo), doing, done } each a full list. */
|
|
93
|
+
export async function listBoard(client) {
|
|
94
|
+
const backlog = await listActionItems(client, 'todo');
|
|
95
|
+
const doing = await listActionItems(client, 'doing');
|
|
96
|
+
const done = await listActionItems(client, 'done');
|
|
97
|
+
return { backlog, doing, done };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Open items (todo then doing), all of them. */
|
|
101
|
+
export async function listOpenActionItems(client) {
|
|
102
|
+
return [...(await listActionItems(client, 'todo')), ...(await listActionItems(client, 'doing'))];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Delete one item (server soft-deletes; creator or admin only). Checks the status before trusting the body. -> { id, title }. */
|
|
106
|
+
export async function deleteActionItem(client, id) {
|
|
107
|
+
const clean = String(id || '').trim();
|
|
108
|
+
if (!clean) throw new QuoxError('Usage: quox actions delete <id> --yes', 2);
|
|
109
|
+
const ep = resolveEndpoint('actions.delete', { id: clean });
|
|
110
|
+
const res = await client.delete(ep.path, { service: ep.service });
|
|
111
|
+
if (!res || res.success === false || res.deleted !== true) {
|
|
112
|
+
throw new QuoxError(`Deleting the action item failed: ${(res && (res.error || res.message)) || 'the server did not confirm the delete'}`);
|
|
113
|
+
}
|
|
114
|
+
return { id: clean, title: res.title || '' };
|
|
115
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox active` / TUI `/active` (QUOXON-1208, owner: "summary of who is doing what"). ONE simplified list over the
|
|
3
|
+
* org session index (GET /api/v1/sessions: tmux sessions, held slots, residents, work, working chats), one line per
|
|
4
|
+
* active worker, newest activity first, grouped Working / Waiting on you / Idle (+ Failing when there is any).
|
|
5
|
+
* Real data only: a worker's "doing" text is what the index carries (its state reason, slot, agent), never invented;
|
|
6
|
+
* a source the index could not read is named in one line. Pure: activeLines(index, opts) -> [{ text, tone }].
|
|
7
|
+
*/
|
|
8
|
+
import { formatAge } from './claudeLs.js';
|
|
9
|
+
|
|
10
|
+
export const ACTIVE_KINDS = ['sessions', 'slots', 'agents'];
|
|
11
|
+
export const PER_GROUP = 10;
|
|
12
|
+
export const ASLEEP_AFTER_S = 6 * 3600; // a resting worker with no activity for this long reads as asleep
|
|
13
|
+
|
|
14
|
+
const clip = (v, n) => { const a = [...String(v ?? '')]; return a.length > n ? a.slice(0, n - 1).join('') + '…' : String(v ?? ''); };
|
|
15
|
+
const t = (r) => { const x = Date.parse(r.last_active); return Number.isFinite(x) ? x : -Infinity; };
|
|
16
|
+
|
|
17
|
+
/** Which filter(s) a row belongs to. sessions = terminal sessions, slots = held slots, agents = residents or agent-attached rows. */
|
|
18
|
+
export function rowKinds(r) {
|
|
19
|
+
const k = new Set();
|
|
20
|
+
if (r.kind === 'tmux') k.add('sessions');
|
|
21
|
+
if (r.kind === 'slot') k.add('slots');
|
|
22
|
+
if (r.kind === 'resident' || r.agent) k.add('agents');
|
|
23
|
+
return k;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function doing(r) {
|
|
27
|
+
const bits = [];
|
|
28
|
+
if (r.state_reason) bits.push(String(r.state_reason).replace(/\s+/g, ' ').trim());
|
|
29
|
+
if (r.kind === 'tmux' && r.backend) bits.push(`${r.backend} session`);
|
|
30
|
+
else if (r.kind === 'slot') bits.push('holding a slot');
|
|
31
|
+
else if (r.kind === 'conversation') bits.push('chat');
|
|
32
|
+
else if (r.kind) bits.push(r.kind);
|
|
33
|
+
if (r.slot && r.kind !== 'slot') bits.push(`slot ${r.slot}`);
|
|
34
|
+
if (r.agent) bits.push(`agent ${r.agent}`);
|
|
35
|
+
return bits.length ? bits.join(', ') : 'no activity detail recorded';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stateWord(r, ageS) {
|
|
39
|
+
if (r.state === 'working') return 'working';
|
|
40
|
+
if (r.state === 'waiting') return 'waiting on you';
|
|
41
|
+
if (r.state === 'failing') return 'failing';
|
|
42
|
+
if (ageS != null && ageS >= ASLEEP_AFTER_S) return 'asleep';
|
|
43
|
+
return ageS != null ? `idle ${formatAge(ageS)}` : 'idle';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** -> { rows: [{ name, doing, state, group, since, ts, kinds }], groups: {working, waiting, failing, idle}, down: [..] } */
|
|
47
|
+
export function prepareActive(index, { filter = null, now = Date.now() } = {}) {
|
|
48
|
+
const rows = (index?.rows || (index?.groups || []).flatMap((g) => g.rows) || [])
|
|
49
|
+
.filter((r) => r.kind !== 'conversation' || r.state === 'working' || r.state === 'waiting')
|
|
50
|
+
.filter((r) => !filter || rowKinds(r).has(filter))
|
|
51
|
+
.sort((a, b) => t(b) - t(a))
|
|
52
|
+
.map((r) => {
|
|
53
|
+
const ts = t(r);
|
|
54
|
+
const ageS = Number.isFinite(ts) ? Math.max(0, (now - ts) / 1000) : null;
|
|
55
|
+
const group = r.state === 'working' ? 'working' : r.state === 'waiting' ? 'waiting' : r.state === 'failing' ? 'failing' : 'idle';
|
|
56
|
+
const name = r.kind === 'slot' ? (r.holder_session || r.name || '(unnamed slot)') : (r.name || '(unnamed)');
|
|
57
|
+
return { name, doing: doing(r), state: stateWord(r, ageS), group, since: ageS == null ? 'no activity recorded' : `${formatAge(ageS)} ago`, ts };
|
|
58
|
+
});
|
|
59
|
+
const groups = { working: [], waiting: [], failing: [], idle: [] };
|
|
60
|
+
for (const r of rows) groups[r.group].push(r);
|
|
61
|
+
const down = (index?.sources || []).filter((s) => s.state !== 'connected');
|
|
62
|
+
return { rows, groups, down };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TITLES = { working: 'Working', waiting: 'Waiting on you', failing: 'Failing', idle: 'Idle' };
|
|
66
|
+
const TONES = { working: 'ok', waiting: 'warn', failing: 'warn', idle: 'dim' };
|
|
67
|
+
|
|
68
|
+
export function activeLines(index, { filter = null, now = Date.now(), all = false } = {}) {
|
|
69
|
+
const p = prepareActive(index, { filter, now });
|
|
70
|
+
const out = [{ text: `Active${filter ? ` ${filter}` : ''}: ${p.rows.length} · ${p.groups.working.length} working · ${p.groups.waiting.length} waiting on you · ${p.groups.idle.length} idle${p.groups.failing.length ? ` · ${p.groups.failing.length} failing` : ''}`, tone: 'accent' }];
|
|
71
|
+
if (!p.rows.length) out.push({ text: p.down.some((s) => s.state !== 'unavailable') ? 'Nothing to show (see the sources below).' : `No active ${filter || 'workers'} in this org.`, tone: 'dim' });
|
|
72
|
+
for (const g of ['working', 'waiting', 'failing', 'idle']) {
|
|
73
|
+
const rs = p.groups[g];
|
|
74
|
+
if (!rs.length) continue;
|
|
75
|
+
out.push({ text: `${TITLES[g]} (${rs.length})`, tone: 'accent' });
|
|
76
|
+
for (const r of all ? rs : rs.slice(0, PER_GROUP)) out.push({ text: ` ${clip(r.name, 28).padEnd(28)} ${clip(r.doing, 44).padEnd(44)} ${r.state}, ${r.since}`, tone: TONES[g] });
|
|
77
|
+
if (!all && rs.length > PER_GROUP) out.push({ text: ` +${rs.length - PER_GROUP} more`, tone: 'dim' });
|
|
78
|
+
}
|
|
79
|
+
// 'unavailable' is a source the server declares it does not list yet (residents): an expected absence, not a failed read.
|
|
80
|
+
// Only a real fault (error, timeout) is "could not read". Residents that hold a slot already show up as rows above.
|
|
81
|
+
const failed = p.down.filter((s) => s.state !== 'unavailable');
|
|
82
|
+
const absent = p.down.filter((s) => s.state === 'unavailable');
|
|
83
|
+
if (failed.length) out.push({ text: `could not read: ${failed.map((s) => `${s.id} (${s.state}${s.note ? `: ${s.note}` : ''})`).join(' · ')}`, tone: 'warn' });
|
|
84
|
+
if (absent.length) out.push({ text: `not listed separately yet: ${absent.map((s) => s.id).join(' · ')} (shown above when they hold a slot)`, tone: 'dim' });
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- TUI /active: one clean row per session -------------------------------------------------------------
|
|
89
|
+
const GLYPH = { working: '◐', waiting: '⚠', failing: '✖', idle: '·' };
|
|
90
|
+
const RANK = { waiting: 0, failing: 1, working: 2, idle: 3 };
|
|
91
|
+
const TUI_TONES = { working: undefined, waiting: undefined, failing: undefined, idle: undefined }; // the row's own glyph carries status; a tone would prefix a second glyph on every wrapped line
|
|
92
|
+
const GROUP_TITLES = { waiting: 'Waiting on you', failing: 'Failing', working: 'Working', idle: 'Idle' };
|
|
93
|
+
|
|
94
|
+
/** "resident:hyp-01" -> { name: 'hyp-01', tag: 'resident' }: the machine prefix becomes a readable tag. */
|
|
95
|
+
function readableName(raw) {
|
|
96
|
+
const m = String(raw).match(/^(resident|agent|slot|tmux):(.+)$/i);
|
|
97
|
+
return m ? { name: m[2], tag: m[1].toLowerCase() } : { name: String(raw), tag: null };
|
|
98
|
+
}
|
|
99
|
+
const humanReason = (reason) => String(reason || '').replace(/\s+/g, ' ').trim().replace(/^unread slot messages?$/i, 'unread messages');
|
|
100
|
+
|
|
101
|
+
/** Word-wrap `rest` after `first`; continuation lines get a 4-space hanging indent. Never clips, never exceeds `width`. */
|
|
102
|
+
function wrapHanging(first, rest, width, sep = ' ') {
|
|
103
|
+
const out = [];
|
|
104
|
+
let cur = first, fresh = true; // fresh: only the label is on this line so far
|
|
105
|
+
for (const w of String(rest).split(/\s+/).filter(Boolean)) {
|
|
106
|
+
const joined = fresh ? `${cur}${sep}${w}` : `${cur} ${w}`;
|
|
107
|
+
if ([...joined].length <= width) { cur = joined; fresh = false; continue; }
|
|
108
|
+
out.push(cur); cur = ` ${w}`; fresh = false;
|
|
109
|
+
}
|
|
110
|
+
out.push(cur);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The window's /active: one row per session (deduped by readable name; the row that needs you wins), shaped
|
|
116
|
+
* ` <glyph> <name> <what it needs> · <age>`, word-wrapped under a hanging indent at `cols`, never clipped, grouped
|
|
117
|
+
* Waiting on you / Failing / Working / Idle. The `quox active` CLI keeps activeLines(); this is the TUI-only view.
|
|
118
|
+
*/
|
|
119
|
+
export function activeRows(index, { now = Date.now(), cols = 80, filter = null, unread = null } = {}) {
|
|
120
|
+
const raw = (index?.rows || (index?.groups || []).flatMap((g) => g.rows) || [])
|
|
121
|
+
.filter((r) => r.kind !== 'conversation' || r.state === 'working' || r.state === 'waiting')
|
|
122
|
+
.filter((r) => !filter || rowKinds(r).has(filter));
|
|
123
|
+
const byName = new Map();
|
|
124
|
+
for (const r of raw) {
|
|
125
|
+
const base = r.kind === 'slot' ? (r.holder_session || r.name || '(unnamed slot)') : (r.name || '(unnamed)');
|
|
126
|
+
const { name, tag } = readableName(base);
|
|
127
|
+
const ts = t(r), ageS = Number.isFinite(ts) ? Math.max(0, (now - ts) / 1000) : null;
|
|
128
|
+
const group = r.state === 'working' ? 'working' : r.state === 'waiting' ? 'waiting' : r.state === 'failing' ? 'failing' : 'idle';
|
|
129
|
+
const needs = [];
|
|
130
|
+
let why = humanReason(r.state_reason);
|
|
131
|
+
const nUnread = unread && (unread.get?.(r.kind === 'slot' ? (r.name || '') : (r.slot || '')) ?? null); // optional per-slot count from the slots list
|
|
132
|
+
if (why === 'unread messages' && nUnread > 0) why = `${nUnread} unread message${nUnread === 1 ? '' : 's'}`;
|
|
133
|
+
if (why) needs.push(why);
|
|
134
|
+
else needs.push(group === 'idle' ? (ageS != null && ageS >= ASLEEP_AFTER_S ? 'asleep' : 'idle') : group);
|
|
135
|
+
if (r.kind === 'slot') needs.push('holding a slot'); else if (r.slot) needs.push(`holding slot ${r.slot}`);
|
|
136
|
+
if (r.agent) needs.push(`agent ${r.agent}`);
|
|
137
|
+
const cand = { name, tag: tag || (r.kind === 'resident' ? 'resident' : null), group, needs, ageS, ts };
|
|
138
|
+
const key = name.toLowerCase(), old = byName.get(key);
|
|
139
|
+
if (!old) { byName.set(key, cand); continue; }
|
|
140
|
+
const better = RANK[cand.group] < RANK[old.group] || (RANK[cand.group] === RANK[old.group] && cand.ts > old.ts);
|
|
141
|
+
const win = better ? cand : old, lose = better ? old : cand;
|
|
142
|
+
const hasUnread = win.needs.some((n) => /unread/.test(n));
|
|
143
|
+
win.needs = [...new Set([...win.needs, ...lose.needs.filter((n) => !/^(working|idle|asleep|failing)$/.test(n) && !(hasUnread && /unread/.test(n)) && !(/^holding a slot$/.test(n) && win.needs.some((x) => /^holding/.test(x))))])];
|
|
144
|
+
win.tag = win.tag || lose.tag;
|
|
145
|
+
byName.set(key, win);
|
|
146
|
+
}
|
|
147
|
+
const rows = [...byName.values()].sort((a, b) => RANK[a.group] - RANK[b.group] || b.ts - a.ts);
|
|
148
|
+
const n = (g) => rows.filter((r) => r.group === g).length;
|
|
149
|
+
const width = Math.max(30, cols - 4);
|
|
150
|
+
const hdr = `${rows.length} · ${n('waiting')} need you · ${n('working')} working · ${n('idle')} idle${n('failing') ? ` · ${n('failing')} failing` : ''}`;
|
|
151
|
+
const out = wrapHanging(`Active${filter ? ` ${filter}` : ''}:`, hdr, width, ' ').map((text) => ({ text, tone: 'accent' }));
|
|
152
|
+
if (!rows.length) out.push({ text: `No active ${filter || 'workers'} in this org.`, tone: undefined });
|
|
153
|
+
let last = null;
|
|
154
|
+
for (const r of rows) {
|
|
155
|
+
if (r.group !== last) { last = r.group; out.push({ text: `${GROUP_TITLES[r.group]} (${n(r.group)})`, tone: 'accent' }); }
|
|
156
|
+
const label = `${r.name}${r.tag ? ` (${r.tag})` : ''}`;
|
|
157
|
+
const what = `${r.needs.join(' · ')}${r.ageS == null ? '' : ` · ${formatAge(r.ageS)} ago`}`;
|
|
158
|
+
for (const text of wrapHanging(` ${GLYPH[r.group]} ${label}`, what, width)) out.push({ text, tone: TUI_TONES[r.group] });
|
|
159
|
+
}
|
|
160
|
+
// a source that could not be read is never an empty org: name it ('unavailable' is an expected absence, not a fault)
|
|
161
|
+
const failed = (index?.sources || []).filter((x) => x.state !== 'connected' && x.state !== 'unavailable');
|
|
162
|
+
if (failed.length) for (const text of wrapHanging('could not read:', failed.map((x) => `${x.id} (${x.state}${x.note ? `: ${x.note}` : ''})`).join(' · '), width, ' ')) out.push({ text, tone: 'warn' });
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Bare `quox active [sessions|slots|agents] [--json] [--all]`. A failed fetch prints its status and exits 1 (never an empty list). */
|
|
167
|
+
export async function runActive({ filter = null, json = false, all = false, lines = false, cols = null } = {}, over = {}) {
|
|
168
|
+
const d = { write: (s) => process.stdout.write(s), now: () => Date.now(), ...over };
|
|
169
|
+
if (filter && !ACTIVE_KINDS.includes(filter)) { d.write(`quox active: unknown filter "${filter}" (sessions, slots or agents)\n`); return 1; }
|
|
170
|
+
try {
|
|
171
|
+
const idx = await d.fetchIndex();
|
|
172
|
+
if (json) {
|
|
173
|
+
const p = prepareActive(idx, { filter, now: d.now() });
|
|
174
|
+
d.write(JSON.stringify({ filter, counts: Object.fromEntries(Object.entries(p.groups).map(([k, v]) => [k, v.length])), workers: p.rows.map(({ ts, ...r }) => r), sources_down: p.down }, null, 2) + '\n');
|
|
175
|
+
} else if (lines) d.write(activeLines(idx, { filter, now: d.now(), all }).map((l) => l.text).join('\n') + '\n');
|
|
176
|
+
else {
|
|
177
|
+
let unread = null;
|
|
178
|
+
if (d.fetchSlots) { try { unread = new Map((await d.fetchSlots() || []).map((x) => [x.name, x.unread_count || 0])); } catch { unread = null; } } // the count is a nicety: a failed slots read never hides the list
|
|
179
|
+
d.write(activeRows(idx, { filter, now: d.now(), cols: cols || process.stdout.columns || 100, unread }).map((l) => l.text).join('\n') + '\n');
|
|
180
|
+
}
|
|
181
|
+
return 0;
|
|
182
|
+
} catch (e) {
|
|
183
|
+
d.write(`quox active: ${e && e.status ? `HTTP ${e.status}: ` : ''}${(e && e.message) || 'request failed'}\n`);
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
}
|
package/lib/core/agentAlias.js
CHANGED
|
@@ -58,6 +58,8 @@ export function loadBundledAssistantsRoster(dir = DEFAULT_ASSISTANTS_DIR) {
|
|
|
58
58
|
id: raw.id,
|
|
59
59
|
name: raw.identity?.name || null,
|
|
60
60
|
shortName: raw.identity?.shortName || null,
|
|
61
|
+
// AGENT-TAXONOMY: present only when the file declares one.
|
|
62
|
+
...(raw.family ? { family: raw.family } : {}),
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
} catch {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Agent family ids, mirrored from src/config/agentFamilies.js (the authority).
|
|
2
|
+
// The published CLI does not ship src/config, so it keeps this copy;
|
|
3
|
+
// cli/test/core/agentFamilies.test.js fails if the two drift.
|
|
4
|
+
export const AGENT_FAMILY_IDS = [
|
|
5
|
+
'command', 'devops', 'monitoring', 'secops', 'development',
|
|
6
|
+
'assistantq', 'knowledge', 'marketing', 'voice', 'governance',
|
|
7
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASIDE: a read-only side question. `quox aside "<question>"` and the TUI /aside share this.
|
|
3
|
+
*
|
|
4
|
+
* It goes to the collector's no-tools invoke (POST /api/internal/agents/invoke with no_tools: true): the model
|
|
5
|
+
* gets no tools at all, and no conversation or session id is sent, so the main conversation's history is never
|
|
6
|
+
* touched. The TUI passes the last few exchanges as plain read-only context so "what did it just say about X?"
|
|
7
|
+
* works; nothing is written back.
|
|
8
|
+
*/
|
|
9
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
10
|
+
|
|
11
|
+
export const ASIDE_MAX_CONTEXT = 6000;
|
|
12
|
+
|
|
13
|
+
const ASIDE_NOTE = 'This is a read-only side question. You have no tools: answer from what you know and from the conversation excerpt below. Do not offer to run anything. Be brief.';
|
|
14
|
+
|
|
15
|
+
/** Recent [{ role: 'user'|'assistant', text }] -> one plain excerpt, newest kept when it is too long. */
|
|
16
|
+
export function asideExcerpt(turns, max = ASIDE_MAX_CONTEXT) {
|
|
17
|
+
const lines = (Array.isArray(turns) ? turns : [])
|
|
18
|
+
.filter(t => t && typeof t.text === 'string' && t.text.trim())
|
|
19
|
+
.map(t => `${t.role === 'assistant' ? 'Quox' : 'User'}: ${t.text.trim()}`);
|
|
20
|
+
let out = '';
|
|
21
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
22
|
+
const next = out ? `${lines[i]}\n\n${out}` : lines[i];
|
|
23
|
+
if (next.length > max) { if (!out) out = lines[i].slice(-max); break; }
|
|
24
|
+
out = next;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The request body: never a session or conversation id, always no_tools. */
|
|
30
|
+
export function asideBody({ question, turns = [], agent = 'quox', maxTokens = 1024 }) {
|
|
31
|
+
const q = String(question ?? '').trim();
|
|
32
|
+
const excerpt = asideExcerpt(turns);
|
|
33
|
+
return {
|
|
34
|
+
agent_id: agent || 'quox',
|
|
35
|
+
task: q,
|
|
36
|
+
no_tools: true,
|
|
37
|
+
max_tokens: maxTokens,
|
|
38
|
+
context: excerpt ? `${ASIDE_NOTE}\n\nConversation so far (read only):\n${excerpt}` : ASIDE_NOTE,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* -> { text, model, provider, tools } . Throws the client error (with .status) on a non-2xx, and an error
|
|
44
|
+
* with code 'aside_used_tools' if the server reports any tool use (it must not happen; we say so if it does).
|
|
45
|
+
*/
|
|
46
|
+
export async function askAside(client, { question, turns, agent, timeoutMs = 120000 }) {
|
|
47
|
+
if (!String(question ?? '').trim()) { const e = new Error('Ask a question: aside <question>'); e.code = 'usage'; throw e; }
|
|
48
|
+
const ep = resolveEndpoint('agents.invoke.notools');
|
|
49
|
+
const r = await client.post(ep.path, { service: ep.service, body: asideBody({ question, turns, agent }), retries: 0, timeout: timeoutMs });
|
|
50
|
+
if (r && r.success === false) { const e = new Error(r.error || 'the server could not answer'); e.status = 502; throw e; }
|
|
51
|
+
const tools = Array.isArray(r?.tools_used) ? r.tools_used : [];
|
|
52
|
+
if (tools.length) { const e = new Error(`the server reported tool use (${tools.join(', ')}) on a no-tools aside`); e.code = 'aside_used_tools'; throw e; }
|
|
53
|
+
return { text: String(r?.result ?? '').trim(), model: r?.usage?.model || null, provider: r?.provider || r?.usage?.provider || null, tools };
|
|
54
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quox ask helpers: parse the option list and durations, and wait (bounded long-poll) for a human's answer.
|
|
3
|
+
* Pure of the CLI framework so the exit codes are unit-testable. Exit codes: 0 answered, 2 expired or denied.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const EXIT_ANSWERED = 0;
|
|
7
|
+
export const EXIT_NO_ANSWER = 2;
|
|
8
|
+
|
|
9
|
+
/** "a: Ship now|b: Wait|c: Drop" -> [{key,label}]. A bare "a|b" gives key=label. Throws with a usable message. */
|
|
10
|
+
export function parseOptions(text) {
|
|
11
|
+
const parts = String(text || '').split('|').map((s) => s.trim()).filter(Boolean);
|
|
12
|
+
if (parts.length < 2) throw new Error('--options needs at least two, e.g. "a: Ship now|b: Wait for review|c: Drop it" (or use --yn)');
|
|
13
|
+
return parts.map((p) => {
|
|
14
|
+
const m = /^([A-Za-z0-9]{1,8})\s*[:=]\s*(.*)$/.exec(p);
|
|
15
|
+
if (m) return { key: m[1].toLowerCase(), label: m[2].trim() || m[1] };
|
|
16
|
+
if (/^[A-Za-z0-9]{1,8}$/.test(p)) return { key: p.toLowerCase(), label: p };
|
|
17
|
+
throw new Error(`could not read option "${p}": write it as key: label, e.g. "a: Ship now"`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const YN_OPTIONS = [{ key: 'y', label: 'Yes' }, { key: 'n', label: 'No' }];
|
|
22
|
+
|
|
23
|
+
/** "90s" | "10m" | "2h" | "1d" | bare seconds -> seconds. */
|
|
24
|
+
export function parseDuration(text) {
|
|
25
|
+
const m = /^(\d+(?:\.\d+)?)\s*([smhd]?)$/i.exec(String(text || '').trim());
|
|
26
|
+
if (!m) throw new Error(`could not read duration "${text}": use 90s, 10m, 2h or 1d`);
|
|
27
|
+
const mult = { '': 1, s: 1, m: 60, h: 3600, d: 86400 }[m[2].toLowerCase()];
|
|
28
|
+
return Math.round(Number(m[1]) * mult);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fmtLeft = (ms) => { const m = Math.max(0, Math.round(ms / 60000)); return m >= 120 ? `${Math.round(m / 60)}h` : m >= 1 ? `${m}m` : '<1m'; };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Long-poll a decision until it is no longer pending.
|
|
35
|
+
* @param {{poll:(waitSeconds:number)=>Promise<object>, log:(s:string)=>void, sleep?:Function, now?:()=>number, maxWaitMs?:number|null, pollSeconds?:number}} o
|
|
36
|
+
* @returns {Promise<{code:number, stdout:string, decision:object|null}>} stdout = chosen option key (+ note line) only when answered.
|
|
37
|
+
*/
|
|
38
|
+
export async function waitForDecision({ poll, log, now = Date.now, maxWaitMs = null, pollSeconds = 25 }) {
|
|
39
|
+
const start = now();
|
|
40
|
+
let errors = 0;
|
|
41
|
+
for (;;) {
|
|
42
|
+
let d;
|
|
43
|
+
try { d = (await poll(pollSeconds)).decision; errors = 0; }
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (e?.status === 404 || e?.status === 403 || e?.status === 401) throw e; // not a blip: a decision we cannot see
|
|
46
|
+
if (++errors >= 5) throw e;
|
|
47
|
+
log(`could not reach the server (${e?.message || 'failed'}), retrying (${errors}/5)`);
|
|
48
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!d) throw new Error('the server returned no decision');
|
|
52
|
+
if (d.state === 'answered') return { code: EXIT_ANSWERED, stdout: `${d.answer?.option ?? ''}${d.answer?.note ? `\n${d.answer.note}` : ''}`, decision: d };
|
|
53
|
+
if (d.state === 'denied') { log(`denied${d.answer?.note ? `: ${d.answer.note}` : ''}`); return { code: EXIT_NO_ANSWER, stdout: '', decision: d }; }
|
|
54
|
+
if (d.state === 'expired' || d.state === 'cancelled') { log(`${d.state}, no answer${d.default ? ` (default was ${d.default}; not applied)` : ''}`); return { code: EXIT_NO_ANSWER, stdout: '', decision: d }; }
|
|
55
|
+
const elapsed = now() - start;
|
|
56
|
+
const left = d.expires_at ? Date.parse(d.expires_at) - now() : null;
|
|
57
|
+
log(`still waiting for a human (${d.session || 'session'}, ${fmtLeft(elapsed)} elapsed${left != null ? `, ${fmtLeft(left)} left` : ''})`);
|
|
58
|
+
if (maxWaitMs != null && elapsed >= maxWaitMs) { log('gave up waiting (--timeout); the question is still open: quox ask wait ' + d.id); return { code: EXIT_NO_ANSWER, stdout: '', decision: d }; }
|
|
59
|
+
}
|
|
60
|
+
}
|