quoxcli 0.0.0-stage → 0.3.1
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 +35 -0
- package/README.md +144 -2
- package/bin/quox.js +265 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +151 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +63 -0
- package/lib/api/endpoints/assistants.js +62 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +61 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +68 -0
- package/lib/api/endpoints/conversations.js +46 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +45 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +40 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +66 -0
- package/lib/api/endpoints/inbox.js +52 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +242 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +94 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +184 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +68 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +152 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +121 -0
- package/lib/api/endpoints/quoxseo.js +114 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +53 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +200 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +96 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +585 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +47 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +169 -0
- package/lib/api/endpoints/x.js +32 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +375 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +337 -0
- package/lib/commands/agentAttach.js +1528 -0
- package/lib/commands/approvals.js +119 -0
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +169 -0
- package/lib/commands/auth.js +464 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +377 -0
- package/lib/commands/brain.js +1085 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +170 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +295 -0
- package/lib/commands/claude.js +380 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +1016 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +105 -0
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +96 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +326 -0
- package/lib/commands/devSupervisor.js +1062 -0
- package/lib/commands/discord.js +132 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +347 -0
- package/lib/commands/engagement.js +597 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +165 -0
- package/lib/commands/fleet.js +1189 -0
- package/lib/commands/governance.js +647 -0
- package/lib/commands/gripe.js +355 -0
- package/lib/commands/hat.js +374 -0
- package/lib/commands/helpers.js +96 -0
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +173 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +139 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +597 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +361 -0
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +581 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +123 -0
- package/lib/commands/objective.js +240 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +329 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +276 -0
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +944 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +649 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +706 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +275 -0
- package/lib/commands/quoxseo.js +378 -0
- package/lib/commands/reach.js +159 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +187 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/server.js +292 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +569 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +687 -0
- package/lib/commands/slot.js +1451 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +142 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +304 -0
- package/lib/commands/trace.js +530 -0
- package/lib/commands/trigger.js +223 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +1447 -0
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +213 -0
- package/lib/commands/volt.js +570 -0
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +167 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +660 -0
- package/lib/commands/x.js +448 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +501 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +693 -0
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +669 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/customHats.js +168 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +227 -0
- package/lib/core/errors.js +441 -0
- package/lib/core/global-flags.js +190 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +122 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +239 -0
- package/lib/core/peek.js +126 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +106 -0
- 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 +192 -0
- package/lib/core/sessionsOverview.js +224 -0
- package/lib/core/slotKeeper.js +527 -0
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +933 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +92 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +116 -0
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +952 -0
- package/lib/quoxon/app.js +4032 -0
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -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 +82 -0
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +479 -0
- package/lib/quoxon/connect.js +654 -0
- package/lib/quoxon/connectCard.js +45 -0
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +34 -0
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +76 -0
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -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 +36 -0
- package/lib/quoxon/events.js +677 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +237 -0
- 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/indicator.js +197 -0
- package/lib/quoxon/intent.js +52 -0
- package/lib/quoxon/keys.js +164 -0
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +176 -0
- package/lib/quoxon/markdown.js +341 -0
- 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 +311 -0
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +70 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +182 -0
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +106 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +110 -0
- 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/skill.js +105 -0
- package/lib/quoxon/objects/view.js +87 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +263 -0
- package/lib/quoxon/onboarding/credential.js +108 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +134 -0
- package/lib/quoxon/onboarding/index.js +94 -0
- package/lib/quoxon/onboarding/installRunner.js +284 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +156 -0
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +682 -0
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +98 -0
- 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 +348 -0
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +132 -0
- package/lib/quoxon/render.js +1167 -0
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +307 -0
- package/lib/quoxon/route.js +39 -0
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +238 -0
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/skills.js +30 -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 +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +90 -0
- 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/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 +88 -0
- package/lib/quoxon/slash/listView.js +30 -0
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +118 -0
- package/lib/quoxon/slash/paletteController.js +169 -0
- package/lib/quoxon/slash/panels.js +116 -0
- package/lib/quoxon/slash/picker.js +56 -0
- package/lib/quoxon/slash/restart.js +202 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +60 -0
- 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/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/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +120 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +141 -0
- package/lib/quoxon/textBuffer.js +416 -0
- package/lib/quoxon/theme.js +286 -0
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +188 -0
- package/lib/quoxon/web/qface.js +89 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -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/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +719 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +75 -4
|
@@ -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/aee.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AEE envelope emitter for CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Emits lightweight event envelopes so CLI actions appear in the
|
|
5
|
+
* AEE audit trail. Fire-and-forget — failures are silently ignored
|
|
6
|
+
* so they never block the command itself.
|
|
7
|
+
*
|
|
8
|
+
* CLI-3 EVIDENCED dimension (quoxai/quox#290)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
12
|
+
|
|
13
|
+
const PROTOCOL_VERSION = '1';
|
|
14
|
+
|
|
15
|
+
function ulid() {
|
|
16
|
+
const t = Date.now().toString(36).padStart(10, '0');
|
|
17
|
+
const r = Array.from({ length: 16 }, () => Math.random().toString(36)[2]).join('');
|
|
18
|
+
return (t + r).toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Emit an AEE event envelope for a CLI command invocation.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} client - The CLI HTTP client (ctx.client)
|
|
25
|
+
* @param {Object} params
|
|
26
|
+
* @param {string} params.command - CLI command name (e.g. 'status', 'doctor')
|
|
27
|
+
* @param {string} params.intent - AEE intent string (e.g. 'quox.cli.status')
|
|
28
|
+
* @param {Object} [params.payload] - Command-specific result data
|
|
29
|
+
* @param {string} [params.status] - 'success' | 'error'
|
|
30
|
+
*/
|
|
31
|
+
export async function emitCliEvent(client, { command, intent, payload = {}, status = 'success' }) {
|
|
32
|
+
try {
|
|
33
|
+
const envelope = {
|
|
34
|
+
v: PROTOCOL_VERSION,
|
|
35
|
+
id: ulid(),
|
|
36
|
+
ts: new Date().toISOString(),
|
|
37
|
+
type: 'event',
|
|
38
|
+
from: 'cli.quox',
|
|
39
|
+
to: 'system.audit',
|
|
40
|
+
intent,
|
|
41
|
+
corr: null,
|
|
42
|
+
reply_to: null,
|
|
43
|
+
payload: {
|
|
44
|
+
command,
|
|
45
|
+
status,
|
|
46
|
+
...payload,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
await client.post(resolveEndpoint('aee.store').path, { service: resolveEndpoint('aee.store').service, body: envelope, timeout: 3000 });
|
|
50
|
+
} catch {
|
|
51
|
+
// Fire-and-forget — never block CLI output
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent display-name alias resolution — DEVSUITE-GRIPE-P7 / quox#567 (CLI
|
|
3
|
+
* half only; server-side alias resolution stays open in #567).
|
|
4
|
+
*
|
|
5
|
+
* `quox agent attach vulcan` failed because the registry id is `forge` —
|
|
6
|
+
* "VULCAN" is only the agent's display name (identity.name in
|
|
7
|
+
* src/config/entities/assistants/forge.json), never sent to any API. This
|
|
8
|
+
* module resolves a non-matching agent id case-insensitively against the
|
|
9
|
+
* core assistants roster BEFORE the attach/detach/attach-status/
|
|
10
|
+
* attach-heartbeat/exec/grant-attach commands call the real API, so the
|
|
11
|
+
* canonical registry id is always what goes over the wire.
|
|
12
|
+
*
|
|
13
|
+
* Two sources, combined:
|
|
14
|
+
* - the collector's no-auth-gate `/api/v1/tools/agents` route, which is
|
|
15
|
+
* the live, authoritative list of agent ids this deployment currently
|
|
16
|
+
* recognises (but carries NO display names — it's a tool-permission
|
|
17
|
+
* map keyed by id);
|
|
18
|
+
* - the bundled assistants roster (src/config/entities/assistants/*.json,
|
|
19
|
+
* shipped inside this same repo checkout) for identity.name /
|
|
20
|
+
* identity.shortName, the only source of display names.
|
|
21
|
+
* A live-roster failure (network down, route not deployed yet) degrades to
|
|
22
|
+
* the bundled roster's own id set rather than blocking alias resolution
|
|
23
|
+
* entirely.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
30
|
+
|
|
31
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
|
|
33
|
+
// cli/lib/core/agentAlias.js -> cli/lib -> cli -> repo root -> src/config/...
|
|
34
|
+
const DEFAULT_ASSISTANTS_DIR = path.resolve(__dirname, '../../../src/config/entities/assistants');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read the bundled assistants roster. Tolerant of a missing directory (a
|
|
38
|
+
* standalone CLI install without the dashboard source tree alongside it)
|
|
39
|
+
* and of individual malformed files — one bad entry must never break alias
|
|
40
|
+
* resolution for every other agent.
|
|
41
|
+
*
|
|
42
|
+
* @returns {Array<{id: string, name: string|null, shortName: string|null}>}
|
|
43
|
+
*/
|
|
44
|
+
export function loadBundledAssistantsRoster(dir = DEFAULT_ASSISTANTS_DIR) {
|
|
45
|
+
let files;
|
|
46
|
+
try {
|
|
47
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const roster = [];
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
56
|
+
if (raw?.id) {
|
|
57
|
+
roster.push({
|
|
58
|
+
id: raw.id,
|
|
59
|
+
name: raw.identity?.name || null,
|
|
60
|
+
shortName: raw.identity?.shortName || null,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Skip an unreadable/malformed file rather than failing the whole load.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return roster;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pure resolver, exported for unit tests: no filesystem or network access.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} requestedId - what the user typed
|
|
74
|
+
* @param {Set<string>} validIds - agent ids this deployment currently recognises
|
|
75
|
+
* @param {Array<{id: string, name: string|null, shortName: string|null}>} roster
|
|
76
|
+
* @returns {{id: string, aliasLabel: string|null}} aliasLabel is the display
|
|
77
|
+
* name to print (`alias: <aliasLabel> -> <id>`), or null when the input
|
|
78
|
+
* was already a valid id (no alias happened).
|
|
79
|
+
*/
|
|
80
|
+
export function resolveAgentAliasPure(requestedId, validIds, roster) {
|
|
81
|
+
if (!requestedId || typeof requestedId !== 'string') {
|
|
82
|
+
throw new Error('Agent id is required.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Exact, case-sensitive match against a known id: pass through untouched.
|
|
86
|
+
// This is the common case and must never be affected by alias lookup.
|
|
87
|
+
if (validIds.has(requestedId)) {
|
|
88
|
+
return { id: requestedId, aliasLabel: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lower = requestedId.toLowerCase();
|
|
92
|
+
for (const entry of roster) {
|
|
93
|
+
// Never alias to an id this deployment doesn't currently recognise.
|
|
94
|
+
if (!validIds.has(entry.id)) continue;
|
|
95
|
+
const name = entry.name?.toLowerCase();
|
|
96
|
+
const shortName = entry.shortName?.toLowerCase();
|
|
97
|
+
if (name === lower || shortName === lower) {
|
|
98
|
+
return { id: entry.id, aliasLabel: entry.name || entry.shortName || requestedId.toUpperCase() };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Unknown agent "${requestedId}": not a registry id, and no matching display name in the assistants roster. `
|
|
104
|
+
+ 'Run `quox agent list` to see valid ids.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a possibly-display-name agent id against the live collector
|
|
110
|
+
* roster plus the bundled assistants roster, printing exactly one
|
|
111
|
+
* `alias: <NAME> -> <id>` line on a hit. Callers must use the RETURNED
|
|
112
|
+
* canonical id for every subsequent call — the display name is never sent
|
|
113
|
+
* to the API.
|
|
114
|
+
*
|
|
115
|
+
* Printed unconditionally (never suppressed under -o json/quiet, unlike
|
|
116
|
+
* printInfo): a silent semantic remap of what agent id you are talking to
|
|
117
|
+
* is exactly the kind of thing a scripted/JSON caller most needs to see.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} client - the CLI's HTTP client (ctx.client)
|
|
120
|
+
* @param {string} requestedId
|
|
121
|
+
* @param {{print?: (line: string) => void, rosterDir?: string}} [opts]
|
|
122
|
+
* @returns {Promise<string>} the canonical agent id
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveAgentId(client, requestedId, opts = {}) {
|
|
125
|
+
const print = opts.print || ((line) => console.log(line));
|
|
126
|
+
const roster = loadBundledAssistantsRoster(opts.rosterDir);
|
|
127
|
+
|
|
128
|
+
// CORTEX-BENCH: an exact core id resolves to itself whatever the live list
|
|
129
|
+
// says (absent from it, it passes through as a custom id and the server
|
|
130
|
+
// decides), so skip the round trip that cost every exec 60ms-1.4s.
|
|
131
|
+
if (roster.some((r) => r.id === requestedId)) return requestedId;
|
|
132
|
+
|
|
133
|
+
let validIds = null;
|
|
134
|
+
try {
|
|
135
|
+
const ep = resolveEndpoint('v1.tools.agents.list');
|
|
136
|
+
const data = await client.get(ep.path, { service: ep.service, noAuth: true });
|
|
137
|
+
const ids = Object.keys(data?.agentTools || {});
|
|
138
|
+
if (ids.length > 0) validIds = new Set(ids);
|
|
139
|
+
} catch {
|
|
140
|
+
// Live lookup is an optimisation, not a hard requirement -- fall
|
|
141
|
+
// through to the bundled roster's own id set below.
|
|
142
|
+
}
|
|
143
|
+
if (!validIds) validIds = new Set(roster.map((r) => r.id));
|
|
144
|
+
|
|
145
|
+
let resolved;
|
|
146
|
+
try {
|
|
147
|
+
resolved = resolveAgentAliasPure(requestedId, validIds, roster);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// A non-empty id that matches neither a core-registry id nor a display
|
|
150
|
+
// name may be a user/org-created custom assistant (auth `assistants`
|
|
151
|
+
// table) — the core roster this resolver knows never lists those.
|
|
152
|
+
// QUOXIDENTITY custom-attach (CLI half): the client must not gate an id
|
|
153
|
+
// the server can now resolve. Pass it through and let the server be the
|
|
154
|
+
// authority — the attach-kit endpoint resolves a custom assistant
|
|
155
|
+
// org-scoped, or returns a clean 404 for a genuine typo. (An empty id
|
|
156
|
+
// still throws the original 'Agent id is required.' error.)
|
|
157
|
+
if (!requestedId || typeof requestedId !== 'string') throw err;
|
|
158
|
+
print(`note: "${requestedId}" is not a core registry agent, trying it as a custom/org assistant (server resolves or rejects).`);
|
|
159
|
+
return requestedId;
|
|
160
|
+
}
|
|
161
|
+
if (resolved.aliasLabel) print(`alias: ${resolved.aliasLabel} -> ${resolved.id}`);
|
|
162
|
+
return resolved.id;
|
|
163
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITL-CONTEXT (owner 2026-10-07): render an approval item's
|
|
3
|
+
* payload.approval_context (built server-side by the collector at request
|
|
4
|
+
* time) for `quox inbox list` / `quox inbox show`. Pure, so it is testable.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function clip(text, n) {
|
|
8
|
+
const s = String(text == null ? '' : text).replace(/\s+/g, ' ').trim();
|
|
9
|
+
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getApprovalContext(item) {
|
|
13
|
+
const c = item && item.payload && item.payload.approval_context;
|
|
14
|
+
return c && typeof c === 'object' ? c : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Ordered [label, value] pairs: Source, On behalf of, Reason, Method, Command, Why approval, Risk, Expires. */
|
|
18
|
+
export function approvalContextFields(c) {
|
|
19
|
+
if (!c) return [];
|
|
20
|
+
const rows = [];
|
|
21
|
+
const rb = c.requested_by || {};
|
|
22
|
+
let source = c.source_label || rb.agent_label || 'unknown';
|
|
23
|
+
if (rb.profile) source += ` · profile ${rb.profile}`;
|
|
24
|
+
rows.push(['Source', source]);
|
|
25
|
+
if (c.on_behalf_of) {
|
|
26
|
+
const ob = c.on_behalf_of;
|
|
27
|
+
let v = ob.user_name || ob.user_id || 'unknown user';
|
|
28
|
+
if (ob.request_excerpt) v += `, asked: "${ob.request_excerpt}"`;
|
|
29
|
+
if (ob.conversation_id) v += ` (conversation ${ob.conversation_id})`;
|
|
30
|
+
rows.push(['On behalf of', v]);
|
|
31
|
+
}
|
|
32
|
+
const r = c.reason || {};
|
|
33
|
+
rows.push(['Reason', r.text ? `${r.text}${r.source === 'inferred' ? ' (inferred, not stated by the caller)' : ''}` : 'none given']);
|
|
34
|
+
const m = c.method || {};
|
|
35
|
+
rows.push(['Method', `${m.tool || '?'} via ${m.transport || '?'}${m.target ? ` on ${m.target}` : ''}`]);
|
|
36
|
+
if (m.command) rows.push(['Command', m.command]);
|
|
37
|
+
else if (m.args) rows.push(['Args', m.args]);
|
|
38
|
+
if (c.why_approval && c.why_approval.label) rows.push(['Why approval', c.why_approval.label]);
|
|
39
|
+
if (c.risk) rows.push(['Risk', c.risk]);
|
|
40
|
+
if (c.expires_at) rows.push(['Expires', c.expires_at]);
|
|
41
|
+
return rows;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Plain-text block for `quox inbox show`. */
|
|
45
|
+
export function formatApprovalContext(c) {
|
|
46
|
+
const rows = approvalContextFields(c);
|
|
47
|
+
if (rows.length === 0) return '';
|
|
48
|
+
const w = Math.max(...rows.map(([k]) => k.length));
|
|
49
|
+
return ['Approval context', ...rows.map(([k, v]) => ` ${k.padEnd(w)} ${v}`)].join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Short columns for `quox inbox list`. */
|
|
53
|
+
export function approvalListColumns(item) {
|
|
54
|
+
const c = getApprovalContext(item);
|
|
55
|
+
if (!c) return { source: '', reason: '' };
|
|
56
|
+
const r = c.reason || {};
|
|
57
|
+
return {
|
|
58
|
+
source: clip(c.source_label || '', 40),
|
|
59
|
+
reason: r.text ? clip(`${r.text}${r.source === 'inferred' ? ' (inferred)' : ''}`, 60) : '',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -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
|
+
}
|