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
|
@@ -65,7 +65,7 @@ export function compactError(e) {
|
|
|
65
65
|
if (code === 'transcripts_disabled') return msg || 'Transcript storage is off for this org, so no summary can be stored.';
|
|
66
66
|
return msg || 'The server refused to compact right now (409).';
|
|
67
67
|
}
|
|
68
|
-
if (e?.status === 403 && code === 'ai_consent_required') return 'Compacting sends this conversation to your AI provider, and
|
|
68
|
+
if (e?.status === 403 && code === 'ai_consent_required') return 'Compacting sends this conversation to your AI provider, and you have not given AI processing consent. /consent grant gives it (Settings > Privacy in the dashboard does too).';
|
|
69
69
|
if (e?.status === 403) return 'Not allowed to compact this conversation (403).';
|
|
70
70
|
if (e?.status === 404) return 'The server has no stored turns for this conversation (expired after 7 days, or nothing sent yet).';
|
|
71
71
|
if (e?.status === 502) return 'The model call failed; nothing was changed. Try /compact again.';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One preview card, one y/n. Shared by /watch, /automate and /crystallize: a direct human ask is already the
|
|
3
|
+
* authorization, so the preview is the only confirmation. The answer is the next composer line (same pattern as the
|
|
4
|
+
* /approve "y/N" grant); a slash command typed instead drops the preview and runs normally.
|
|
5
|
+
*/
|
|
6
|
+
import { askText } from '../ask.js';
|
|
7
|
+
export function createConfirm({ say, ttlMs = 120000, now = () => Date.now() }) {
|
|
8
|
+
let p = null;
|
|
9
|
+
return {
|
|
10
|
+
pending: () => Boolean(p),
|
|
11
|
+
/** y/N/c: a chat about the ask keeps it open with a fresh window. */
|
|
12
|
+
touch() { if (p) p.at = now(); },
|
|
13
|
+
ask(lines, onYes, { prompt = 'Go ahead?' } = {}) {
|
|
14
|
+
p = { onYes, at: now() };
|
|
15
|
+
for (const l of lines) (Array.isArray(l) ? say(l[0], l[1]) : say(l));
|
|
16
|
+
say(askText(String(prompt).replace(/\s*y\/n$/i, '')), 'ask'); // QUOXON-ASK: one shape
|
|
17
|
+
},
|
|
18
|
+
/** -> true when this line was the answer (consumed); false when the caller should treat it as an ordinary line. */
|
|
19
|
+
answer(text) {
|
|
20
|
+
if (!p) return false;
|
|
21
|
+
const q = p; p = null;
|
|
22
|
+
const t = String(text || '').trim();
|
|
23
|
+
if (t.startsWith('/')) return false;
|
|
24
|
+
if (now() - q.at > ttlMs) { say('That preview expired, so nothing was created. Run the command again.', 'dim'); return true; }
|
|
25
|
+
if (/^y(es)?$/i.test(t)) { Promise.resolve(q.onYes()).catch((e) => say(`Failed: ${e?.message || e}`, 'warn')); return true; }
|
|
26
|
+
say('Cancelled; nothing was created.', 'dim');
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /crystallize [what]: turn what was just accomplished into a permanent reusable skill (rules, tools, tests).
|
|
3
|
+
* Real: one drafting model call (draft.js), a dry-run through the skill firewall for the preview, and creation in
|
|
4
|
+
* the QSKL store (POST /api/quox-skills). /crystal (is a workflow eligible to become a Crystal) is a different check.
|
|
5
|
+
*/
|
|
6
|
+
import { draftJson, transcriptText } from './draft.js';
|
|
7
|
+
|
|
8
|
+
const why = (e) => `${e?.status ? `HTTP ${e.status}: ` : ''}${e?.message || 'failed'}`;
|
|
9
|
+
const NAME_RE = /^[a-z0-9][a-z0-9-]{1,60}$/;
|
|
10
|
+
|
|
11
|
+
export const crystallizePrompt = (context) => [
|
|
12
|
+
'Turn what was just accomplished in the conversation below into a reusable skill. Reply with ONE JSON object and nothing else. Use no tools.',
|
|
13
|
+
'Shape: {"name":"kebab-case-name","category":str,"description":"one sentence","rules":[str,...],"tools":[tool name,...],',
|
|
14
|
+
' "tests":[{"name":str,"input":str,"expect":str}]}.',
|
|
15
|
+
'Rules are the steps and guardrails to repeat next time. Never include secrets, keys, hostnames or personal data. Tools are only ones actually used above.',
|
|
16
|
+
'', '--- conversation ---', context, '--- end ---'
|
|
17
|
+
].join('\n');
|
|
18
|
+
|
|
19
|
+
/** -> { body (POST /api/quox-skills), card[] } or { error } */
|
|
20
|
+
export function buildSkill(d, { orgId, author }) {
|
|
21
|
+
const name = String(d?.name || '').trim().toLowerCase();
|
|
22
|
+
if (!NAME_RE.test(name)) return { error: 'the draft has no valid kebab-case name' };
|
|
23
|
+
const rules = (Array.isArray(d.rules) ? d.rules : []).map((r) => String(r).trim()).filter(Boolean);
|
|
24
|
+
if (!rules.length) return { error: 'the draft has no rules' };
|
|
25
|
+
const description = String(d.description || '').trim();
|
|
26
|
+
if (!description) return { error: 'the draft has no description' };
|
|
27
|
+
const tools = (Array.isArray(d.tools) ? d.tools : []).map((t) => String(t).trim()).filter(Boolean);
|
|
28
|
+
const tests = {};
|
|
29
|
+
for (const [i, t] of (Array.isArray(d.tests) ? d.tests : []).entries()) {
|
|
30
|
+
if (t && (t.input || t.expect)) tests[String(t.name || `case-${i + 1}`)] = { input: String(t.input || ''), expect: String(t.expect || '') };
|
|
31
|
+
}
|
|
32
|
+
const bodyMd = [`# ${name}`, '', description, '', '## Rules', ...rules.map((r, i) => `${i + 1}. ${r}`),
|
|
33
|
+
...(tools.length ? ['', '## Tools', ...tools.map((t) => `- ${t}`)] : [])].join('\n');
|
|
34
|
+
const body = { orgId, scopeId: null, name, category: String(d.category || 'general'), description, bodyMd, author: author || 'quoxon', requiredTools: tools, tests, tags: ['crystallized'] };
|
|
35
|
+
const card = [
|
|
36
|
+
['Skill draft (nothing is saved until you answer):', 'accent'],
|
|
37
|
+
` Name ${name}`,
|
|
38
|
+
` About ${description}`,
|
|
39
|
+
` Rules ${rules.length} first: ${rules[0]}`,
|
|
40
|
+
` Tools ${tools.length ? tools.join(', ') : 'none'}`,
|
|
41
|
+
` Tests ${Object.keys(tests).length}${Object.keys(tests).length ? ' ' + Object.keys(tests).join(', ') : ''}`
|
|
42
|
+
];
|
|
43
|
+
return { body, card, name };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createCrystallizeCmd({ api, say, confirm, getState, getFields }) {
|
|
47
|
+
return {
|
|
48
|
+
async handle(arg) {
|
|
49
|
+
if (!api.skillCreate) return say('Skills are not available in this mode.', 'warn');
|
|
50
|
+
const ctx = [transcriptText(getState()), String(arg || '').trim() ? `Focus: ${String(arg).trim()}` : ''].filter(Boolean).join('\n');
|
|
51
|
+
if (!ctx) return say('Nothing to crystallize yet. Do the task with Quox first, or say what: /crystallize how we rotate the API key.');
|
|
52
|
+
say('Drafting the skill…', 'dim');
|
|
53
|
+
let d;
|
|
54
|
+
try { d = await draftJson({ api, prompt: crystallizePrompt(ctx), fields: getFields() }); }
|
|
55
|
+
catch (e) { return say(`Could not draft a skill: ${e.message}. Nothing was saved.`, 'warn'); }
|
|
56
|
+
const b = buildSkill(d, { orgId: api.orgId?.(), author: api.userId?.() });
|
|
57
|
+
if (b.error) return say(`Could not draft a usable skill: ${b.error}. Nothing was saved.`, 'warn');
|
|
58
|
+
try { await api.skillCreate(b.body, { dryRun: true }); } // the server's skill firewall, before the card
|
|
59
|
+
catch (e) { return say(`The skill was not saved: ${why(e)}`, 'warn'); }
|
|
60
|
+
b.card.push(' Firewall passed the server dry run');
|
|
61
|
+
confirm.ask(b.card, async () => {
|
|
62
|
+
try { await api.skillCreate(b.body); } catch (e) { return say(`Could not save the skill: ${why(e)}`, 'warn'); }
|
|
63
|
+
say(`✔ Saved skill ${b.name}.`, 'ok');
|
|
64
|
+
say(` See it: /skills ${b.name}`);
|
|
65
|
+
}, { prompt: 'Save this skill?' });
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /dashboard [page] and `quox dashboard [page]`: a one-time sign-in link to the web dashboard.
|
|
3
|
+
*
|
|
4
|
+
* The link comes from POST /api/auth/login-link (10 minutes, single use, bound to this user and org) and is
|
|
5
|
+
* joined to the active profile's dashboard URL. It is a credential: callers print it on the visible line only,
|
|
6
|
+
* never into the dev transcript or any history. Opened in the default browser when running locally; never over
|
|
7
|
+
* SSH (the browser would open on the wrong machine), where it is only printed.
|
|
8
|
+
*
|
|
9
|
+
* `next` asks the consume route to land on the page after sign-in. A server that does not know `next` lands
|
|
10
|
+
* on the dashboard home instead; nothing breaks.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
/** Page name -> real route in src/config/routeRegistry.js (approvals = the HITL /inbox, inbox = InboxQ mail). */
|
|
15
|
+
export const PAGES = {
|
|
16
|
+
sessions: '/sessions',
|
|
17
|
+
approvals: '/inbox',
|
|
18
|
+
agents: '/agents',
|
|
19
|
+
lucid: '/lucid',
|
|
20
|
+
inbox: '/inboxq',
|
|
21
|
+
seo: '/quoxseo',
|
|
22
|
+
settings: '/settings'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function resolvePage(arg) {
|
|
26
|
+
const name = String(arg ?? '').trim().toLowerCase();
|
|
27
|
+
if (!name) return { ok: true, page: null, path: '/' };
|
|
28
|
+
if (Object.hasOwn(PAGES, name)) return { ok: true, page: name, path: PAGES[name] };
|
|
29
|
+
return { ok: false, error: `Unknown page "${name}". Pages: ${Object.keys(PAGES).join(', ')}.` };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const isRemoteSession = (env = process.env) => Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY);
|
|
33
|
+
|
|
34
|
+
export function openerFor(platform = process.platform) {
|
|
35
|
+
if (platform === 'linux') return { cmd: 'xdg-open', args: [] };
|
|
36
|
+
if (platform === 'darwin') return { cmd: 'open', args: [] };
|
|
37
|
+
if (platform === 'win32') return { cmd: 'rundll32', args: ['url.dll,FileProtocolHandler'] }; // no shell, so & in the URL is safe
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** -> { opened: true } | { opened: false, reason: 'ssh' | 'no-opener' | 'failed' }. Never throws, never uses a shell. */
|
|
42
|
+
export function openInBrowser(url, { env = process.env, platform = process.platform, spawn = nodeSpawn } = {}) {
|
|
43
|
+
if (isRemoteSession(env)) return Promise.resolve({ opened: false, reason: 'ssh' });
|
|
44
|
+
const op = openerFor(platform);
|
|
45
|
+
if (!op) return Promise.resolve({ opened: false, reason: 'no-opener' });
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
let done = false;
|
|
48
|
+
const fin = (r) => { if (!done) { done = true; resolve(r); } };
|
|
49
|
+
try {
|
|
50
|
+
const child = spawn(op.cmd, [...op.args, url], { stdio: 'ignore', detached: true });
|
|
51
|
+
child.on('error', () => fin({ opened: false, reason: 'failed' }));
|
|
52
|
+
child.unref?.();
|
|
53
|
+
setTimeout(() => fin({ opened: true }), 150); // an error (opener missing) arrives well inside this
|
|
54
|
+
} catch { fin({ opened: false, reason: 'failed' }); }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const missing = (e) => e?.status === 404 || e?.status === 405 || e?.status === 501;
|
|
59
|
+
const join = (base, path) => new URL(path, base).href;
|
|
60
|
+
const isLoopbackUrl = (u) => /^(localhost|127\.\d+\.\d+\.\d+|\[::1\]|0\.0\.0\.0)$/i.test(new URL(u).hostname);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* mint: async () -> { path, expires_at, ttl_seconds } (POST /api/auth/login-link).
|
|
64
|
+
* -> { kind: 'link' | 'plain' | 'error', url, plainUrl, page, remote, opened, openFailed, ttlSeconds, expiresAt, error }
|
|
65
|
+
*/
|
|
66
|
+
export async function runDashboard({ page, mint, baseUrl, env = process.env, open = (u) => openInBrowser(u, { env }) } = {}) {
|
|
67
|
+
const p = resolvePage(page);
|
|
68
|
+
if (!p.ok) return { kind: 'error', error: p.error };
|
|
69
|
+
const remote = isRemoteSession(env);
|
|
70
|
+
let plainUrl = null;
|
|
71
|
+
try { plainUrl = join(baseUrl, p.path); } catch { /* no profile URL: the server may still give one */ }
|
|
72
|
+
const base = { page: p.page, plainUrl, remote };
|
|
73
|
+
const noUrl = 'This profile has no dashboard URL. Set one with: quox config set url <https://host:3000>';
|
|
74
|
+
let r;
|
|
75
|
+
try { r = await mint(); } catch (e) {
|
|
76
|
+
if (missing(e)) return plainUrl ? { ...base, kind: 'plain', url: plainUrl } : { kind: 'error', error: noUrl };
|
|
77
|
+
const why = e?.status ? `${e.message || 'refused'} (HTTP ${e.status})` : (e?.message || 'failed');
|
|
78
|
+
return { ...base, kind: 'error', url: plainUrl, error: `Could not create a sign-in link: ${why}` };
|
|
79
|
+
}
|
|
80
|
+
if (!r?.path || !/^\/api\/auth\/login-link\/consume\?t=[\w-]+$/.test(r.path)) {
|
|
81
|
+
return { ...base, kind: 'error', url: plainUrl, error: 'Could not create a sign-in link: unexpected answer from the auth service.' };
|
|
82
|
+
}
|
|
83
|
+
// The server knows the public address of the instance; the profile URL may be localhost on this box.
|
|
84
|
+
let origin = null;
|
|
85
|
+
if (typeof r.url === 'string') {
|
|
86
|
+
try {
|
|
87
|
+
const u = new URL(r.url);
|
|
88
|
+
if (/^https?:$/.test(u.protocol) && r.url === u.origin + r.path) origin = u.origin;
|
|
89
|
+
} catch { /* ignore a malformed server url */ }
|
|
90
|
+
}
|
|
91
|
+
let linkBase = origin || baseUrl;
|
|
92
|
+
let local = false;
|
|
93
|
+
if (!origin) {
|
|
94
|
+
try { local = isLoopbackUrl(join(baseUrl, '/')); } catch { /* handled below */ }
|
|
95
|
+
}
|
|
96
|
+
let link;
|
|
97
|
+
try { link = join(linkBase, r.path); } catch { return { kind: 'error', error: noUrl }; }
|
|
98
|
+
const url = link + (p.page ? `&next=${encodeURIComponent(p.path)}` : '');
|
|
99
|
+
if (origin) base.plainUrl = join(origin, p.path);
|
|
100
|
+
const out = { ...base, kind: 'link', url, localOnly: local, ttlSeconds: r.ttl_seconds ?? 600, expiresAt: r.expires_at, opened: false };
|
|
101
|
+
if (!remote) {
|
|
102
|
+
const o = await open(url).catch(() => ({ opened: false, reason: 'failed' }));
|
|
103
|
+
out.opened = Boolean(o?.opened);
|
|
104
|
+
out.openFailed = !o?.opened;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const osc8 = (url, text) => `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
110
|
+
|
|
111
|
+
/** Result -> [{ text, tone }] for the visible lines. The link line is the only place the credential is printed. */
|
|
112
|
+
export function dashboardLines(r, { hyperlinks = false } = {}) {
|
|
113
|
+
const where = r.page ? `the ${r.page} page` : 'the dashboard';
|
|
114
|
+
if (r.kind === 'error') {
|
|
115
|
+
return [
|
|
116
|
+
{ text: r.error, tone: 'warn' },
|
|
117
|
+
...(r.url ? [{ text: `The dashboard is at ${r.url} (you will need to sign in).`, tone: 'dim' }] : [])
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
if (r.kind === 'plain') {
|
|
121
|
+
return [
|
|
122
|
+
{ text: `This server does not offer one-time sign-in links (an older server). Open ${where} here and sign in:`, tone: 'warn' },
|
|
123
|
+
{ text: r.url }
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
const mins = Math.round((r.ttlSeconds || 600) / 60);
|
|
127
|
+
return [
|
|
128
|
+
{ text: r.opened ? `Opened ${where} in your browser, already signed in.` : `Sign-in link for ${where}:` },
|
|
129
|
+
...(hyperlinks ? [{ text: osc8(r.url, `Open ${where} (signed in)`) }] : []),
|
|
130
|
+
{ text: r.url },
|
|
131
|
+
{ text: `Valid for ${mins} minutes, works once, tied to your account. Anyone holding it can sign in as you until then.`, tone: 'dim' },
|
|
132
|
+
...(r.localOnly ? [{ text: 'This address only works on this machine; set the app URL with `quox server config app-url <url>` or in Settings > General > App URL.', tone: 'dim' }] : []),
|
|
133
|
+
...(r.remote ? [{ text: 'Over SSH, so it was not opened here: open it in a browser on your own machine.', tone: 'dim' }] : []),
|
|
134
|
+
...(r.openFailed ? [{ text: 'Could not open a browser from here: use the link above.', tone: 'warn' }] : [])
|
|
135
|
+
];
|
|
136
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One drafting model call for /automate and /crystallize, through the same governed turn path as chat (api.stream)
|
|
3
|
+
* but in its own throwaway conversation, so it never lands in the visible transcript. The model is told to answer
|
|
4
|
+
* with JSON only and use no tools; an approval request or error aborts the draft. The JSON is validated by the
|
|
5
|
+
* caller before any card is shown.
|
|
6
|
+
*/
|
|
7
|
+
import { textGate } from '../events.js';
|
|
8
|
+
|
|
9
|
+
const newId = () => `QXN_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
10
|
+
|
|
11
|
+
/** The visible conversation as plain text for the drafting prompt (last `max` chars). */
|
|
12
|
+
export function transcriptText(state, max = 12000) {
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const it of [...(state?.history || []), ...(state?.blocks || [])]) {
|
|
15
|
+
if (it.kind === 'user' && !it.echo && it.text && !it.text.startsWith('/')) out.push(`User: ${it.text}`);
|
|
16
|
+
else if (it.kind === 'text' && it.text) out.push(`Quox: ${it.text}`);
|
|
17
|
+
else if (it.kind === 'tool' && it.tool) out.push(`Tool used: ${[it.tool, it.target].filter(Boolean).join(' ')}`);
|
|
18
|
+
}
|
|
19
|
+
const t = out.join('\n');
|
|
20
|
+
return t.length > max ? t.slice(t.length - max) : t;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** First JSON object in a model reply (plain, fenced, or surrounded by prose), or null. */
|
|
24
|
+
export function extractJson(text) {
|
|
25
|
+
const s = String(text || '');
|
|
26
|
+
const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
27
|
+
const cand = fence ? fence[1] : s;
|
|
28
|
+
const a = cand.indexOf('{'), b = cand.lastIndexOf('}');
|
|
29
|
+
if (a < 0 || b <= a) return null;
|
|
30
|
+
try { const v = JSON.parse(cand.slice(a, b + 1)); return v && typeof v === 'object' && !Array.isArray(v) ? v : null; } catch { return null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** -> parsed object, or throws Error(reason). `fields` = streamFields(model choice). */
|
|
34
|
+
export async function draftJson({ api, prompt, fields = {}, timeoutMs = 90000 }) {
|
|
35
|
+
if (!api.stream) throw new Error('no model connection in this mode');
|
|
36
|
+
const ctrl = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
38
|
+
let text = '', gate = null;
|
|
39
|
+
try {
|
|
40
|
+
for await (const ev of api.stream({ turnId: newId(), message: prompt, agent: 'quox', ...fields }, ctrl.signal)) {
|
|
41
|
+
if (ev.type === 'error') throw new Error(String(ev.error || 'the model call failed'));
|
|
42
|
+
if (ev.type === 'approval.requested') throw new Error('the model tried to use a tool instead of drafting');
|
|
43
|
+
const g = textGate(gate, ev); gate = g.st;
|
|
44
|
+
if (g.append) text += ev.content;
|
|
45
|
+
if (ev.type === 'turn.finished' || ev.type === 'done') break;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (ctrl.signal.aborted) throw new Error('the model took too long');
|
|
49
|
+
throw e;
|
|
50
|
+
} finally { clearTimeout(timer); }
|
|
51
|
+
const j = extractJson(text);
|
|
52
|
+
if (!j) throw new Error('the model did not return a usable draft');
|
|
53
|
+
return j;
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /evidence: export this conversation's signed evidence bundle (the same call as `quox evidence bundle`) to
|
|
3
|
+
* ~/quox-evidence/<conversation>-<timestamp>.zip and say what is in it. /ward lists the WARD receipts the export found
|
|
4
|
+
* (newest ten, then N more). /envelope, /envelopes and /aee are pointers to /proof (the envelope ids); /volt is the
|
|
5
|
+
* VOLT run, which travels inside the bundle. Pure formatting plus one api call; the lines are { text, tone }.
|
|
6
|
+
*/
|
|
7
|
+
import { wrapHang } from '../objects/model.js';
|
|
8
|
+
export const EVIDENCE_ALIASES = ['envelope', 'envelopes', 'aee', 'volt', 'ward'];
|
|
9
|
+
const SHOWN = 10;
|
|
10
|
+
|
|
11
|
+
export async function runEvidenceCommand({ cmd, api, conversationId, cols = Infinity }) {
|
|
12
|
+
const W = Number.isFinite(cols) ? Math.max(20, cols - 4) : 1e9;
|
|
13
|
+
const wrapT = (l) => wrapHang(l.text, W, 4).map(text => ({ ...l, text }));
|
|
14
|
+
if (!api || typeof api.evidenceExport !== 'function') return [{ text: 'Evidence export is not available in this mode. In a terminal: quox evidence bundle', tone: 'warn' }].flatMap(wrapT);
|
|
15
|
+
if (!conversationId) return [{ text: 'No conversation to export yet: send a message first, or in a terminal run quox evidence bundle <conversation id>.', tone: 'warn' }].flatMap(wrapT);
|
|
16
|
+
let r;
|
|
17
|
+
try { r = await api.evidenceExport(conversationId); } catch (e) {
|
|
18
|
+
return [{ text: `Evidence export failed: ${e?.message || 'failed'}. Nothing was written.`, tone: 'warn' }].flatMap(wrapT);
|
|
19
|
+
}
|
|
20
|
+
const out = [];
|
|
21
|
+
const ok = r.verify && r.verify.ok;
|
|
22
|
+
out.push({ text: `${ok ? '✔' : '✖'} evidence bundle ${ok ? 'written and verified' : 'written but FAILED verification'}: ${r.path}`, tone: ok ? 'ok' : 'warn' });
|
|
23
|
+
out.push({ text: ` ${r.envelopes} envelopes, ${r.receipts} WARD receipts, conversation ${r.corr}`, tone: 'dim' });
|
|
24
|
+
if (cmd === '/ward') {
|
|
25
|
+
out.push({ text: ' WARD receipts for this conversation (the same bundle /evidence writes):', tone: 'dim' });
|
|
26
|
+
const rows = [...(r.receiptRows || [])].sort((a, b) => (b.seq || 0) - (a.seq || 0));
|
|
27
|
+
for (const x of rows.slice(0, SHOWN)) out.push({ text: ` WARD seq ${x.seq} envelope ${x.envelope_id}`, tone: 'dim' });
|
|
28
|
+
if (rows.length > SHOWN) out.push({ text: ` ${rows.length - SHOWN} more in the bundle`, tone: 'dim' });
|
|
29
|
+
if (!rows.length) out.push({ text: ' No WARD receipt was found for this conversation\'s envelopes yet (witnessing can lag a few seconds).', tone: 'warn' });
|
|
30
|
+
}
|
|
31
|
+
if (cmd === '/volt') out.push({ text: ' The VOLT run (if this conversation has one) is volt.json inside the bundle.', tone: 'dim' });
|
|
32
|
+
out.push({ text: ` check it offline: quox evidence verify ${r.path}`, tone: 'dim' });
|
|
33
|
+
return out.flatMap(wrapT);
|
|
34
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CUSTOM-HATS (owner 2026-10-17): /hat new | edit <hat> | delete <hat> in the quox window. One small step machine:
|
|
3
|
+
* every question is either a y/N/c card (askText, the same shape as every other ask) or a one-line text capture
|
|
4
|
+
* (a name, a pick by number or name, a list of agents). Esc, or any other slash command, ends the walk and
|
|
5
|
+
* nothing is sent. Nothing reaches the server until the final summary card is answered y.
|
|
6
|
+
* The definition maths is shared with `quox hat create|edit` (core/customHats.js); this file has no I/O of its own
|
|
7
|
+
* beyond the api it is given.
|
|
8
|
+
*/
|
|
9
|
+
import { buildCustomDefinition, applyHatEdits } from '../../core/customHats.js';
|
|
10
|
+
import { matchHat, hatChoices } from '../hatSwitch.js';
|
|
11
|
+
import { askText } from '../ask.js';
|
|
12
|
+
|
|
13
|
+
export const slugify = (name) => String(name ?? '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
14
|
+
const isCustom = (hat) => hat?.template_key == null;
|
|
15
|
+
const nameOf = (hat) => hat?.name || hat?.key || 'hat';
|
|
16
|
+
const joinNice = (xs) => xs.join(', ');
|
|
17
|
+
|
|
18
|
+
/** "1, 3 sec" -> picks resolved against rows [{ id }] -> { ids, unknown } (numbers are 1-based positions). */
|
|
19
|
+
export function pickAgents(line, rows) {
|
|
20
|
+
const ids = []; const unknown = [];
|
|
21
|
+
for (const raw of String(line ?? '').split(/[\s,]+/).map(s => s.trim()).filter(Boolean)) {
|
|
22
|
+
let id = null;
|
|
23
|
+
if (/^\d+$/.test(raw)) id = rows[Number(raw) - 1]?.id ?? null;
|
|
24
|
+
else id = rows.find(r => r.id.toLowerCase() === raw.toLowerCase())?.id ?? null;
|
|
25
|
+
if (!id) unknown.push(raw); else if (!ids.includes(id)) ids.push(id);
|
|
26
|
+
}
|
|
27
|
+
return { ids, unknown };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createHatWalk({ api, say, bump = () => {} }) {
|
|
31
|
+
let W = null; // the walk in progress, or null
|
|
32
|
+
|
|
33
|
+
const ask = (question, reason = '') => { const st = { at: Date.now() }; W.card = st; W.wait = { ...W.wait, kind: 'yn' }; say(askText(question, { reason }), 'ask'); return st; };
|
|
34
|
+
const yn = (question, on, reason) => { W.wait = { kind: 'yn', on }; ask(question, reason); bump(); };
|
|
35
|
+
const text = (prompt, on) => { W.wait = { kind: 'text', on }; W.card = null; say(prompt); bump(); };
|
|
36
|
+
const end = () => { W = null; bump(); };
|
|
37
|
+
const fail = (what, e) => { say(`${what}: ${e?.message || 'the server refused'}.`, 'warn'); end(); };
|
|
38
|
+
|
|
39
|
+
async function loadAgents() {
|
|
40
|
+
if (W.agents) return W.agents;
|
|
41
|
+
try {
|
|
42
|
+
const rows = await api.agents();
|
|
43
|
+
W.agents = (Array.isArray(rows) ? rows : []).map(a => ({ id: String(a.agent_id ?? a.id ?? ''), name: a.name || a.agent_id || a.id })).filter(a => a.id);
|
|
44
|
+
} catch { W.agents = null; }
|
|
45
|
+
return W.agents;
|
|
46
|
+
}
|
|
47
|
+
const agentLabel = (id) => { const a = W.agents?.find(x => x.id === id); return a && a.name && a.name !== id ? `${id} (${a.name})` : id; };
|
|
48
|
+
|
|
49
|
+
// Shared tail: keep cards for each current specialist, then "Add agents?". done(kept, added).
|
|
50
|
+
async function agentSteps(current, done) {
|
|
51
|
+
await loadAgents();
|
|
52
|
+
const kept = []; const queue = [...current];
|
|
53
|
+
const nextKeep = () => {
|
|
54
|
+
if (!queue.length) return askAdd();
|
|
55
|
+
const id = queue.shift();
|
|
56
|
+
yn(`Keep ${agentLabel(id)}?`, (yes) => { if (yes) kept.push(id); nextKeep(); }, 'This hat lists it as a specialist');
|
|
57
|
+
};
|
|
58
|
+
const askAdd = () => yn('Add agents?', async (yes) => {
|
|
59
|
+
if (!yes) return done(kept, []);
|
|
60
|
+
if (!W.agents?.length) { say('Could not list the org agents, so none were added.', 'warn'); return done(kept, []); }
|
|
61
|
+
const rows = W.agents.filter(a => !kept.includes(a.id));
|
|
62
|
+
if (!rows.length) { say('Every agent in this org is already in the hat.'); return done(kept, []); }
|
|
63
|
+
rows.forEach((a, i) => say(` ${i + 1}. ${a.id}${a.name && a.name !== a.id ? ` ${a.name}` : ''}`));
|
|
64
|
+
const pick = () => text('Which agents? Numbers and/or ids, separated by commas or spaces.', (line) => {
|
|
65
|
+
const { ids, unknown } = pickAgents(line, rows);
|
|
66
|
+
if (unknown.length) { say(`Not in the list above: ${unknown.join(', ')}.`, 'warn'); return pick(); }
|
|
67
|
+
if (!ids.length) { say('No agent picked.', 'warn'); return pick(); }
|
|
68
|
+
done(kept, ids);
|
|
69
|
+
});
|
|
70
|
+
pick();
|
|
71
|
+
});
|
|
72
|
+
nextKeep();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function startNew() {
|
|
76
|
+
let hats;
|
|
77
|
+
try { hats = await api.hats(); } catch (e) { return fail("Couldn't list hats", e); }
|
|
78
|
+
W = { mode: 'new', hats };
|
|
79
|
+
const askName = () => text('What should the new hat be called?', (line) => {
|
|
80
|
+
const name = line.trim(); const key = slugify(name);
|
|
81
|
+
if (key.length < 2 || key.length > 41) { say('A hat name needs 2 to 41 letters or digits. Try another.', 'warn'); return askName(); }
|
|
82
|
+
if (hats.some(h => h.key === key)) { say(`A hat with the key "${key}" already exists. Try another name.`, 'warn'); return askName(); }
|
|
83
|
+
W.name = name; W.key = key; askBase();
|
|
84
|
+
});
|
|
85
|
+
const askBase = () => yn('Start from an empty hat?', (yes) => {
|
|
86
|
+
if (yes) { W.base = null; return agents(); }
|
|
87
|
+
hats.forEach((h, i) => say(` ${i + 1}. ${nameOf(h)} ${h.key}`));
|
|
88
|
+
const pick = () => text('Which hat should it start from? A number or a name.', (line) => {
|
|
89
|
+
const t = line.trim();
|
|
90
|
+
const m = /^\d+$/.test(t) ? { hat: hats[Number(t) - 1] } : matchHat(t, hats);
|
|
91
|
+
if (m.ambiguous) { say(`"${t}" matches more than one hat: ${hatChoices(m.ambiguous)}. Be more specific.`, 'warn'); return pick(); }
|
|
92
|
+
if (!m.hat) { say(`No hat matches "${t}".`, 'warn'); return pick(); }
|
|
93
|
+
W.base = m.hat; agents();
|
|
94
|
+
});
|
|
95
|
+
pick();
|
|
96
|
+
});
|
|
97
|
+
const agents = () => agentSteps(W.base?.definition?.agents?.specialists || [], (kept, added) => {
|
|
98
|
+
const list = [...kept, ...added];
|
|
99
|
+
let built;
|
|
100
|
+
try { built = buildCustomDefinition({ base: W.base?.definition || null, name: W.name, agents: list }); } catch (e) { return fail('Could not build the hat', e); }
|
|
101
|
+
yn(`Create the ${W.name} hat with ${list.length ? joinNice(list) : 'no agents'}? It offers only its own tools and agents.`, async (yes) => {
|
|
102
|
+
if (!yes) { say('Nothing created.', 'dim'); return end(); }
|
|
103
|
+
const { key, name } = W;
|
|
104
|
+
try {
|
|
105
|
+
const r = await api.createHat({ key, name, definition: built.definition, visibility: 'org' });
|
|
106
|
+
say(`Hat ${name} created (rev ${r?.hat?.current_revision ?? 1}). It is in your hat list now, ready to wear.`, 'ok');
|
|
107
|
+
for (const w of Array.isArray(r?.warnings) ? r.warnings : []) say(`Note: ${typeof w === 'string' ? w : w?.message || JSON.stringify(w)}`, 'warn');
|
|
108
|
+
} catch (e) { say(`Hat not created: ${e?.message || 'the server refused'}.`, 'warn'); }
|
|
109
|
+
end();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
askName();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function findCustom(arg, verb) {
|
|
116
|
+
let hats;
|
|
117
|
+
try { hats = await api.hats(); } catch (e) { say(`Couldn't list hats: ${e?.message || 'failed'}.`, 'warn'); return null; }
|
|
118
|
+
const mine = hats.filter(isCustom);
|
|
119
|
+
if (!arg) { say(`Which hat? ${mine.length ? `Your own hats: ${hatChoices(mine)}.` : 'You have no hats of your own yet. /hat new makes one.'}`, 'warn'); return null; }
|
|
120
|
+
const m = matchHat(arg, hats);
|
|
121
|
+
if (m.ambiguous) { say(`"${arg}" matches more than one hat: ${hatChoices(m.ambiguous)}. Be more specific.`, 'warn'); return null; }
|
|
122
|
+
if (!m.hat) { say(`No hat matches "${arg}". Your own hats: ${hatChoices(mine) || 'none yet'}.`, 'warn'); return null; }
|
|
123
|
+
if (!isCustom(m.hat)) {
|
|
124
|
+
say(verb === 'edit'
|
|
125
|
+
? `${nameOf(m.hat)} is a built-in hat, so it is not edited here. It can be reset, or used as a starting point with /hat new.`
|
|
126
|
+
: `${nameOf(m.hat)} is a built-in hat and cannot be deleted. It can be reset, or used as a starting point with /hat new.`, 'warn');
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return m.hat;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function startEdit(arg) {
|
|
133
|
+
const hat = await findCustom(arg, 'edit'); if (!hat) return;
|
|
134
|
+
W = { mode: 'edit', hat };
|
|
135
|
+
const def = hat.definition || {};
|
|
136
|
+
agentSteps(def.agents?.specialists || [], (kept, added) => {
|
|
137
|
+
const current = def.agents?.specialists || [];
|
|
138
|
+
const removeAgents = current.filter(a => !kept.includes(a));
|
|
139
|
+
const rename = () => yn('Rename it?', (yes) => {
|
|
140
|
+
if (!yes) return summary(null);
|
|
141
|
+
const ask2 = () => text('What is the new name?', (line) => { const n = line.trim(); if (!n) { say('A name cannot be empty.', 'warn'); return ask2(); } summary(n); });
|
|
142
|
+
ask2();
|
|
143
|
+
});
|
|
144
|
+
const summary = (newName) => {
|
|
145
|
+
let ed;
|
|
146
|
+
try { ed = applyHatEdits(def, { addAgents: added, removeAgents }); } catch (e) { return fail('Could not apply the edit', e); }
|
|
147
|
+
const changes = [...ed.changes];
|
|
148
|
+
if (newName && newName !== hat.name) changes.push(`name ${newName}`); else newName = null;
|
|
149
|
+
if (!changes.length) { say(`Nothing changed on the ${nameOf(hat)} hat. Nothing was sent.`); return end(); }
|
|
150
|
+
yn(`Update the ${nameOf(hat)} hat: ${changes.join('; ')}?`, async (yes) => {
|
|
151
|
+
if (!yes) { say('Nothing changed.', 'dim'); return end(); }
|
|
152
|
+
try {
|
|
153
|
+
const r = await api.updateHat(hat.id, { definition: ed.definition, ...(newName ? { name: newName } : {}), change_reason: 'Edited from the quox window' });
|
|
154
|
+
say(`Hat ${newName || nameOf(hat)} updated (rev ${r?.hat?.current_revision ?? 'next'}).`, 'ok');
|
|
155
|
+
for (const w of Array.isArray(r?.warnings) ? r.warnings : []) say(`Note: ${typeof w === 'string' ? w : w?.message || JSON.stringify(w)}`, 'warn');
|
|
156
|
+
} catch (e) { say(`Hat not updated: ${e?.message || 'the server refused'}.`, 'warn'); }
|
|
157
|
+
end();
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
rename();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function startDelete(arg) {
|
|
165
|
+
const hat = await findCustom(arg, 'delete'); if (!hat) return;
|
|
166
|
+
W = { mode: 'delete', hat };
|
|
167
|
+
yn(`Delete the ${nameOf(hat)} hat? Conversations wearing it go back to the default hat.`, async (yes) => {
|
|
168
|
+
if (!yes) { say('Nothing deleted.', 'dim'); return end(); }
|
|
169
|
+
try {
|
|
170
|
+
const r = await api.deleteHat(hat.id);
|
|
171
|
+
const d = r?.deleted || {};
|
|
172
|
+
say(`Hat ${nameOf(hat)} deleted. Conversations moved off it: ${d.bindings_cleared ?? 0}. Personal choices cleared: ${d.user_prefs_cleared ?? 0}.`, 'ok');
|
|
173
|
+
} catch (e) { say(`Hat not deleted: ${e?.message || 'the server refused'}.`, 'warn'); }
|
|
174
|
+
end();
|
|
175
|
+
}, 'It cannot be undone');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
active: () => Boolean(W),
|
|
180
|
+
/** the y/N/c card currently on screen (for the ask machinery), or null */
|
|
181
|
+
card: () => (W?.wait?.kind === 'yn' ? W.card : null),
|
|
182
|
+
start(kind, arg = '') {
|
|
183
|
+
if (W) { say('A hat walk is already open. Esc cancels it.', 'warn'); return; }
|
|
184
|
+
if (kind === 'new') return startNew();
|
|
185
|
+
if (kind === 'edit') return startEdit(arg);
|
|
186
|
+
return startDelete(arg);
|
|
187
|
+
},
|
|
188
|
+
/** One typed line. true = consumed. A slash command ends the walk and is left to run. */
|
|
189
|
+
answer(line) {
|
|
190
|
+
if (!W?.wait) return false;
|
|
191
|
+
const t = String(line ?? '').trim();
|
|
192
|
+
if (t.startsWith('/')) { W = null; say('Hat walk ended. Nothing was sent.', 'dim'); bump(); return false; }
|
|
193
|
+
const w = W.wait;
|
|
194
|
+
if (w.kind === 'yn') {
|
|
195
|
+
const yes = /^y(es)?$/i.test(t);
|
|
196
|
+
if (!yes && t && !/^no?$/i.test(t)) return false; // c or free text: the card's normal chat behaviour
|
|
197
|
+
W.wait = null; Promise.resolve(w.on(yes)).catch((e) => fail('Hat walk failed', e));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (!t) return true;
|
|
201
|
+
W.wait = null; Promise.resolve(w.on(t)).catch((e) => fail('Hat walk failed', e));
|
|
202
|
+
return true;
|
|
203
|
+
},
|
|
204
|
+
cancel() { if (!W) return false; W = null; say('Hat walk cancelled. Nothing was sent.', 'dim'); bump(); return true; },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /undo: which tool calls count as state changes, and the small registry of KNOWN inverses.
|
|
3
|
+
*
|
|
4
|
+
* Receipts are content-free on purpose (the AEE envelope commits to the action by digest, never plaintext
|
|
5
|
+
* args) and the stream summary only echoes a few safe keys. So an inverse is never built from "what the
|
|
6
|
+
* receipt said the args were". Each entry binds its target from something that is really there: a safe key
|
|
7
|
+
* in the call's own summary (slot name), or the server's own record of what the call produced, matched by
|
|
8
|
+
* time window and accepted only when EXACTLY ONE record fits. Zero or several = not bound = nothing runs,
|
|
9
|
+
* and the user gets the by-hand route. Nothing here guesses an inverse.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const READ_ONLY = /(^|_)(list|get|read|search|status|view|history|health|query|check|show|stats|find|lookup|related|suggest|research|describe|info|accounts|folders|pending|proposals|blocked|report|transcript|doctor|ping|whoami|verify)(_|$)/;
|
|
13
|
+
|
|
14
|
+
/** A settled, successful, receipt-backed call that is not a read. */
|
|
15
|
+
export function isStateChanging(b) {
|
|
16
|
+
return Boolean(b) && b.kind === 'tool' && b.status === 'ok' && Boolean(b.envelopeId) && Boolean(b.tool) && !READ_ONLY.test(String(b.tool));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function lastStateChange(items) {
|
|
20
|
+
const all = (items || []).filter(isStateChanging);
|
|
21
|
+
return all.length ? all[all.length - 1] : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const WIN_BEFORE = 10_000;
|
|
25
|
+
const WIN_AFTER = 20_000;
|
|
26
|
+
const toMs = (v) => (typeof v === 'number' ? v : Number.isFinite(Date.parse(v)) ? Date.parse(v) : null);
|
|
27
|
+
function inWindow(call, ts) {
|
|
28
|
+
const t = toMs(ts);
|
|
29
|
+
if (t == null || !Number.isFinite(call.startedAt)) return false;
|
|
30
|
+
return t >= call.startedAt - WIN_BEFORE && t <= call.startedAt + (call.durationMs || 0) + WIN_AFTER;
|
|
31
|
+
}
|
|
32
|
+
const summaryKey = (call, key) => new RegExp(`(?:^|\\s)${key}=(\\S+)`).exec(String(call.summary || ''))?.[1] || null;
|
|
33
|
+
|
|
34
|
+
/** Each bind -> { ok: true, label, run(ops) -> { receipt } } | { ok: false, why, byHand } */
|
|
35
|
+
export const INVERSES = {
|
|
36
|
+
slot_checkout: {
|
|
37
|
+
did: 'checked a slot out for this conversation',
|
|
38
|
+
async bind(call) {
|
|
39
|
+
const name = summaryKey(call, 'name');
|
|
40
|
+
if (!name) return { ok: false, why: 'the call summary does not name the slot', byHand: 'quox slot list, then quox slot release <name>' };
|
|
41
|
+
return { ok: true, reverse: `release slot ${name} (the hold ends; the slot itself stays)`, run: (ops) => ops.releaseSlot(name) };
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
inbox_move: {
|
|
45
|
+
did: 'moved mail between folders',
|
|
46
|
+
async bind(call, ops) {
|
|
47
|
+
const moves = (await ops.recentMoves()).filter(m => inWindow(call, m.movedAt ?? m.moved_at));
|
|
48
|
+
if (moves.length === 1) {
|
|
49
|
+
const m = moves[0];
|
|
50
|
+
return { ok: true, reverse: `move the message back from ${m.toFolder || m.to_folder || 'its new folder'} to ${m.fromFolder || m.from_folder || 'its source folder'} (move ${m.id})`, run: (o) => o.undoMove(m.id) };
|
|
51
|
+
}
|
|
52
|
+
return { ok: false, why: moves.length ? `more than one move fits that call (${moves.map(m => m.id).join(', ')}), so Quox can't tell which one it made` : 'no recorded move fits that call', byHand: 'quox mail moves, then quox mail undo <moveId>' };
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
notesq_create: {
|
|
56
|
+
did: 'created a note',
|
|
57
|
+
async bind(call, ops) {
|
|
58
|
+
const notes = (await ops.listNotes()).filter(n => inWindow(call, n.createdAt ?? n.created_at));
|
|
59
|
+
if (notes.length === 1) {
|
|
60
|
+
const n = notes[0];
|
|
61
|
+
return { ok: true, reverse: `delete the note "${n.title || n.id}" (${n.id})`, run: (o) => o.deleteNote(n.id) };
|
|
62
|
+
}
|
|
63
|
+
return { ok: false, why: notes.length ? `more than one note fits that call (${notes.map(n => n.id).join(', ')})` : 'no recorded note fits that call', byHand: 'quox notes list, then delete the one you mean from NotesQ' };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Tools that change state but have no automatic inverse here: the by-hand route, stated plainly. */
|
|
69
|
+
export const BY_HAND = {
|
|
70
|
+
activate_workflow: 'quox workflow list, then deactivate it from the dashboard or ask Quox to deactivate_workflow with the id',
|
|
71
|
+
deactivate_workflow: 'reactivate it with activate_workflow, or from the dashboard',
|
|
72
|
+
skill_manage: 'a created skill sits in the review queue: reject it there, or leave it unapproved',
|
|
73
|
+
notesq_delete: 'ask Quox to notesq_restore the note, or use quox notes restore <id>',
|
|
74
|
+
email_send: 'a sent email cannot be recalled',
|
|
75
|
+
email: 'a sent email cannot be recalled',
|
|
76
|
+
slot_say: 'a message already posted in a slot room stays there'
|
|
77
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /watch, /automate, /crystallize: the shared factory app.js wires (switch cases + one answer hook).
|
|
3
|
+
* Each command makes ONE preview card; the next line y/n creates through an existing backend path.
|
|
4
|
+
*/
|
|
5
|
+
import { createConfirm } from './confirm.js';
|
|
6
|
+
import { createWatchCmd } from './watch.js';
|
|
7
|
+
import { createAutomateCmd } from './automate.js';
|
|
8
|
+
import { createCrystallizeCmd } from './crystallize.js';
|
|
9
|
+
|
|
10
|
+
export function createKillerCmds({ api, say, getState, getFields }) {
|
|
11
|
+
const confirm = createConfirm({ say });
|
|
12
|
+
const watch = createWatchCmd({ api, say, confirm });
|
|
13
|
+
const automate = createAutomateCmd({ api, say, confirm, getState, getFields });
|
|
14
|
+
const crystallize = createCrystallizeCmd({ api, say, confirm, getState, getFields });
|
|
15
|
+
return {
|
|
16
|
+
crystallize: (arg) => crystallize.handle(arg),
|
|
17
|
+
automate: (arg) => automate.handle(arg),
|
|
18
|
+
watch: (arg) => watch.handle(arg),
|
|
19
|
+
/** The composer line after a preview card: true when consumed as the y/n answer. */
|
|
20
|
+
answer: (text) => confirm.answer(text),
|
|
21
|
+
pending: () => confirm.pending(),
|
|
22
|
+
touch: () => confirm.touch()
|
|
23
|
+
};
|
|
24
|
+
}
|