quoxcli 0.2.0 → 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 +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/admin.js +26 -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/chat.js +7 -0
- package/lib/api/endpoints/contactsq.js +2 -0
- package/lib/api/endpoints/conversations.js +14 -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 +30 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +14 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +6 -0
- package/lib/api/endpoints/objectives.js +6 -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 +6 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -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 +86 -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 +61 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +163 -27
- package/lib/commands/agentAttach.js +94 -6
- package/lib/commands/approvals.js +73 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -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/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +57 -2
- package/lib/commands/claude.js +34 -8
- package/lib/commands/contacts.js +49 -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 +17 -6
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +311 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +178 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/licence.js +14 -155
- 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 +11 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +11 -1
- 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 +159 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +38 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +64 -9
- package/lib/commands/server.js +292 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/skills.js +48 -5
- 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/core/actingAs.js +2 -2
- package/lib/core/activeSummary.js +186 -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 +144 -35
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/config.js +142 -24
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/global-flags.js +105 -5
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +4 -4
- 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/sessionsOverview.js +64 -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 +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 +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/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +504 -25
- package/lib/quoxon/app.js +3000 -230
- 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 +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +363 -38
- 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 +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +44 -3
- 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 +2 -2
- package/lib/quoxon/events.js +244 -25
- 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/indicator.js +6 -2
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +49 -6
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +92 -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 +211 -0
- package/lib/quoxon/noai.js +57 -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 +24 -3
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/peek.js +17 -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/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/view.js +76 -19
- 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 +9 -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 +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +58 -37
- package/lib/quoxon/render.js +638 -119
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +95 -9
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +711 -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/session.js +172 -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 +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -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/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 +21 -7
- 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/restart.js +71 -13
- 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/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 +59 -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/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 +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/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +4 -1
- package/lib/core/websiteSession.js +0 -148
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-FW3: a message typed while the server is restarting is kept, not dropped (pure helpers).
|
|
3
|
+
* A send is only retried when the connection failed BEFORE any event arrived (so no turn started and
|
|
4
|
+
* nothing can run twice); the caller enforces that.
|
|
5
|
+
*/
|
|
6
|
+
export const SERVER_RESTARTING = "Quox server is restarting. I'll send this when it is back. Esc cancels.";
|
|
7
|
+
export const MAX_WAIT_MS = 180000;
|
|
8
|
+
|
|
9
|
+
/** True for "could not reach the server at all" (refused / fetch failed), never for an HTTP answer. */
|
|
10
|
+
export function isServerDown(e) {
|
|
11
|
+
if (!e || e.status) return false;
|
|
12
|
+
const code = e.cause?.code || e.code || '';
|
|
13
|
+
const msg = String(e.message || '');
|
|
14
|
+
return /ECONNREFUSED|ECONNRESET|ENOTFOUND|EAI_AGAIN|UND_ERR_SOCKET/.test(String(code)) || /Could not connect to|fetch failed|ECONNREFUSED/i.test(msg);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** gh #1157: resume during a collector restart. Connection-class failures (refused, fetch failed, hang up, 502/503/504) are worth retrying; 401/404 are answers. */
|
|
18
|
+
export function isResumeRetryable(e) {
|
|
19
|
+
if (!e) return false;
|
|
20
|
+
if (e.status) return [502, 503, 504].includes(Number(e.status));
|
|
21
|
+
return isServerDown(e) || /socket hang up|ECONNRESET|other side closed|terminated/i.test(String(e.message || ''));
|
|
22
|
+
}
|
|
23
|
+
/** 2, 4, 8, 15, 30 s, then 30 s steps: about 2 minutes in all before giving up. */
|
|
24
|
+
export const RESUME_RETRY_DELAYS = [2000, 4000, 8000, 15000, 30000, 30000, 30000];
|
|
25
|
+
export const RESUME_RECONNECTING = 'Quox server restarting, reconnecting to your conversation…';
|
|
26
|
+
|
|
27
|
+
/** 1s, 2s, 4s, 8s, then 10s (capped). */
|
|
28
|
+
export const backoffMs = (i) => Math.min(10000, 1000 * 2 ** i);
|
|
29
|
+
|
|
30
|
+
const sleepFor = (ms, signal) => new Promise((r) => {
|
|
31
|
+
if (signal?.aborted) return r();
|
|
32
|
+
const t = setTimeout(done, ms);
|
|
33
|
+
function done() { clearTimeout(t); signal?.removeEventListener?.('abort', done); r(); }
|
|
34
|
+
signal?.addEventListener?.('abort', done, { once: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Poll health with backoff until it answers 2xx. -> 'up' | 'cancelled' | 'timeout'.
|
|
39
|
+
* delays: optional array (tests); else backoffMs. Total waiting is bounded by maxMs of scheduled sleep.
|
|
40
|
+
*/
|
|
41
|
+
export async function waitForServer({ health, signal, delays = null, maxMs = MAX_WAIT_MS }) {
|
|
42
|
+
let waited = 0;
|
|
43
|
+
for (let i = 0; ; i++) {
|
|
44
|
+
if (signal?.aborted) return 'cancelled';
|
|
45
|
+
const d = delays ? delays[Math.min(i, delays.length - 1)] : backoffMs(i);
|
|
46
|
+
if (waited >= maxMs || (delays && i >= delays.length * 4)) return 'timeout';
|
|
47
|
+
await sleepFor(d, signal);
|
|
48
|
+
waited += d;
|
|
49
|
+
if (signal?.aborted) return 'cancelled';
|
|
50
|
+
let r = null;
|
|
51
|
+
try { r = await health(3000); } catch { r = null; }
|
|
52
|
+
if (r?.ok) return 'up';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* QUOXON-FW4: a stream that died mid-turn ("terminated" is undici's wording) gets a cause and a way
|
|
58
|
+
* forward instead of a bare word. Returns null when the error is not a transport/timeout failure.
|
|
59
|
+
*/
|
|
60
|
+
export function explainTermination(e) {
|
|
61
|
+
if (!e || e.status) return null;
|
|
62
|
+
const msg = String(e.message || '');
|
|
63
|
+
const code = String(e.cause?.code || e.code || '');
|
|
64
|
+
if (e.name === 'TimeoutError' || /timed? ?out|ETIMEDOUT|UND_ERR_(HEADERS|BODY)_TIMEOUT/i.test(`${msg} ${code}`)) return `The turn timed out before the server finished.`;
|
|
65
|
+
if (/^terminated$/i.test(msg.trim()) || /ECONNRESET|UND_ERR_SOCKET|other side closed|premature close|aborted/i.test(`${msg} ${code}`)) {
|
|
66
|
+
return `The connection to the server was cut mid-turn (likely a server restart, proxy timeout or provider error).`;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The second line after a cut turn: the message is kept, and what to do next. */
|
|
72
|
+
export const RETRY_HINT = 'Your message is kept: press Up to recall it, then Enter to resend. Anything it started may or may not have finished; check before repeating it.';
|
package/lib/quoxon/roster.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { clip } from './theme.js';
|
|
8
8
|
|
|
9
9
|
export const GLYPH = { running: '◐', waiting: '⏸', done: '✔', failed: '✖' };
|
|
10
|
+
/** A row that ended without finishing says HOW: failed is the only error; the rest are neutral (no error colour). */
|
|
11
|
+
export const OUTCOME_GLYPH = { failed: '✖', cancelled: '○', expired: '○', rejected: '⊘' };
|
|
10
12
|
export const FRESH_MS = 10 * 60 * 1000; // finished rows linger this long
|
|
11
13
|
export const USER_DONE_MS = 20 * 1000; // a user-started completion vanishes quickly: rows come and go
|
|
12
14
|
export const PENDING_FRESH_MS = 24 * 3600 * 1000; // an older "pending" is stale, not waiting
|
|
@@ -36,7 +38,18 @@ export function elapsed(ms) {
|
|
|
36
38
|
|
|
37
39
|
const ACTIVE_RE = /^(running|planning|decompos|executing|in_progress|active)/;
|
|
38
40
|
const WAITING_RE = /^(paused|awaiting|blocked|plan_review|pending_approval|waiting)/;
|
|
39
|
-
const FAILED = new Set(['failed', 'error', 'halted', 'cancelled', 'canceled', 'timed_out', 'timeout']);
|
|
41
|
+
const FAILED = new Set(['failed', 'error', 'halted', 'cancelled', 'canceled', 'timed_out', 'timeout', 'expired', 'rejected']);
|
|
42
|
+
/** The word for how an ended row ended (state stays 'failed' for the ended-badly group; this says which). */
|
|
43
|
+
export function outcomeOf(status) {
|
|
44
|
+
const st = String(status || '').toLowerCase();
|
|
45
|
+
if (st === 'cancelled' || st === 'canceled') return 'cancelled';
|
|
46
|
+
if (st === 'expired') return 'expired';
|
|
47
|
+
if (st === 'rejected') return 'rejected';
|
|
48
|
+
return 'failed';
|
|
49
|
+
}
|
|
50
|
+
/** Word, glyph and whether it is an error, for a row. Non-failed states return their own state. */
|
|
51
|
+
export function endWord(r) { return r.state === 'failed' ? outcomeOf(r.status) : r.state; }
|
|
52
|
+
export const glyphOf = (r) => (r.state === 'failed' ? OUTCOME_GLYPH[endWord(r)] : GLYPH[r.state]);
|
|
40
53
|
|
|
41
54
|
/** How long a finished row lingers before it leaves the roster (null while running/waiting). */
|
|
42
55
|
const expiryFor = (state, doneMs) => state === 'done' ? doneMs : state === 'failed' ? FRESH_MS : null;
|
|
@@ -59,6 +72,8 @@ function stateOf(status, { startedAt, createdAt, finishedAt, now, doneMs = FRESH
|
|
|
59
72
|
return null;
|
|
60
73
|
}
|
|
61
74
|
|
|
75
|
+
const latest = (...ts) => { const v = ts.filter((t) => t != null); return v.length ? Math.max(...v) : null; };
|
|
76
|
+
|
|
62
77
|
export function fromObjective(o, now = Date.now()) {
|
|
63
78
|
const startedAt = parseTime(o.started_at);
|
|
64
79
|
const createdAt = parseTime(o.created_at);
|
|
@@ -78,6 +93,7 @@ export function fromObjective(o, now = Date.now()) {
|
|
|
78
93
|
name: o.title || o.id, activity, status: o.status,
|
|
79
94
|
startedAt: from, finishedAt, ageMs: from != null ? (state === 'done' || state === 'failed' ? (finishedAt ?? now) - from : now - from) : null,
|
|
80
95
|
expiryMs: expiryFor(state, USER_DONE_MS),
|
|
96
|
+
progressAt: latest(parseTime(o.updated_at), parseTime(o.last_progress_at), parseTime(o.last_heartbeat_at)),
|
|
81
97
|
detail: { description: o.description, error: o.error_message, tokens: o.tokens_used, toolCalls: o.tool_calls_used }
|
|
82
98
|
};
|
|
83
99
|
}
|
|
@@ -95,7 +111,8 @@ const BACKGROUND_BY = /^(schedule|cron|interval|webhook|event|system)\b/i;
|
|
|
95
111
|
export function isBackgroundExecution(e, userId = null) {
|
|
96
112
|
const by = e?.triggeredBy == null ? '' : String(e.triggeredBy);
|
|
97
113
|
if (BACKGROUND_BY.test(by)) return true;
|
|
98
|
-
|
|
114
|
+
// quoxflow records a person-started run as user:<id> (RunActor); a bare id is an older row
|
|
115
|
+
if (userId && by && (by === String(userId) || by === `user:${userId}`)) return false;
|
|
99
116
|
return !USER_TRIGGERS.has(String(e?.triggerData?.trigger || '').toLowerCase());
|
|
100
117
|
}
|
|
101
118
|
|
|
@@ -114,6 +131,7 @@ export function fromExecution(e, now = Date.now(), { userId = null } = {}) {
|
|
|
114
131
|
name: e.workflowName || e.workflowId || e.id, activity, status: e.status,
|
|
115
132
|
startedAt, finishedAt, ageMs: startedAt != null ? (state === 'done' || state === 'failed' ? (finishedAt ?? now) - startedAt : now - startedAt) : null,
|
|
116
133
|
expiryMs: expiryFor(state, background ? FRESH_MS : USER_DONE_MS),
|
|
134
|
+
progressAt: latest(parseTime(e.updatedAt), parseTime(e.lastHeartbeatAt)),
|
|
117
135
|
detail: { workflowId: e.workflowId, correlationId: e.correlationId, trigger: e.triggerData?.trigger, error: e.error }
|
|
118
136
|
};
|
|
119
137
|
}
|
|
@@ -169,22 +187,83 @@ export function toneName(r, pos, now = Date.now()) {
|
|
|
169
187
|
|
|
170
188
|
/** Live age text: a clock for running/waiting rows, recency ("done 3s ago") for finished ones. */
|
|
171
189
|
export function ageText(r, now = Date.now()) {
|
|
172
|
-
if (r.state === 'done' || r.state === 'failed') return r.finishedAt != null ? `${r
|
|
190
|
+
if (r.state === 'done' || r.state === 'failed') return r.finishedAt != null ? `${endWord(r)} ${elapsed(now - r.finishedAt)} ago` : '';
|
|
173
191
|
return r.startedAt != null ? elapsed(now - r.startedAt) : '';
|
|
174
192
|
}
|
|
175
193
|
|
|
194
|
+
/* ---- pulse: is it moving? ---------------------------------------------------------------- */
|
|
195
|
+
|
|
196
|
+
export const PULSE_MOVING_MS = 2 * 60 * 1000; // progress this recently = moving
|
|
197
|
+
export const PULSE_STALL_MS = 15 * 60 * 1000; // running with no progress this long = stalled
|
|
198
|
+
export const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
199
|
+
export const SPIN_MS = 120;
|
|
200
|
+
|
|
201
|
+
const sigOf = (r) => `${r.state}|${r.status}|${r.activity}`;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remember what each row looked like at the last poll so "state changed / iteration advanced" is noticed.
|
|
205
|
+
* Sets r.progressAt (newest of the API's own timestamp and the change we saw; null = nothing seen yet) and
|
|
206
|
+
* r.watchSince (when we first saw the row). Mutates rows and memo (key -> { sig, watchSince, changedAt }).
|
|
207
|
+
*/
|
|
208
|
+
export function applyProgress(rows, memo, now = Date.now()) {
|
|
209
|
+
const keep = new Set();
|
|
210
|
+
for (const r of rows) {
|
|
211
|
+
keep.add(r.key);
|
|
212
|
+
const sig = sigOf(r);
|
|
213
|
+
let m = memo.get(r.key);
|
|
214
|
+
if (!m) { m = { sig, watchSince: now, changedAt: null }; memo.set(r.key, m); }
|
|
215
|
+
else if (m.sig !== sig) { m.sig = sig; m.changedAt = now; }
|
|
216
|
+
r.progressAt = latest(r.progressAt, m.changedAt);
|
|
217
|
+
r.watchSince = m.watchSince;
|
|
218
|
+
}
|
|
219
|
+
for (const k of [...memo.keys()]) if (!keep.has(k)) memo.delete(k);
|
|
220
|
+
return rows;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** -> { kind: 'moving' | 'idle' | 'stalled' | 'none', glyph, role }. motion=false gives static glyphs. */
|
|
224
|
+
export function pulseOf(r, now = Date.now(), { motion = false } = {}) {
|
|
225
|
+
if (r.kind === 'approval' || r.state === 'done' || r.state === 'failed') return { kind: 'none', glyph: ' ', role: 'dim' };
|
|
226
|
+
if (r.state !== 'running') return { kind: 'idle', glyph: '·', role: 'dim' };
|
|
227
|
+
const last = latest(r.progressAt, r.startedAt != null && now - r.startedAt < PULSE_MOVING_MS ? r.startedAt : null);
|
|
228
|
+
if (last != null && now - last < PULSE_MOVING_MS) return { kind: 'moving', glyph: motion ? SPIN[Math.floor(now / SPIN_MS) % SPIN.length] : '●', role: 'sky' };
|
|
229
|
+
const ref = last ?? r.watchSince;
|
|
230
|
+
if (ref != null && now - ref > PULSE_STALL_MS) return { kind: 'stalled', glyph: '!', role: 'warn' };
|
|
231
|
+
return { kind: 'idle', glyph: '·', role: 'dim' };
|
|
232
|
+
}
|
|
233
|
+
|
|
176
234
|
export function countsLine(rows) {
|
|
177
235
|
const n = (s) => rows.filter(r => r.state === s).length;
|
|
178
|
-
|
|
179
|
-
|
|
236
|
+
const o = (w) => rows.filter(r => r.state === 'failed' && endWord(r) === w).length;
|
|
237
|
+
return [['running', 'running', n('running'), GLYPH.running], ['waiting', 'waiting', n('waiting'), GLYPH.waiting],
|
|
238
|
+
['failed', 'failed', o('failed'), OUTCOME_GLYPH.failed], ['cancelled', 'cancelled', o('cancelled'), OUTCOME_GLYPH.cancelled],
|
|
239
|
+
['expired', 'expired', o('expired'), OUTCOME_GLYPH.expired], ['rejected', 'rejected', o('rejected'), OUTCOME_GLYPH.rejected], ['done', 'finished', n('done'), GLYPH.done]]
|
|
240
|
+
.filter(([, , k]) => k).map(([, w, k, g]) => `${g} ${k} ${w}`).join(' · ');
|
|
180
241
|
}
|
|
181
242
|
|
|
182
243
|
/**
|
|
183
244
|
* View model. mode: 'hidden' (off or nothing running) | 'summary' (narrow or short terminal) | 'full'.
|
|
184
245
|
* lines: [{ state, text, selected }] where text is already clipped to cols.
|
|
185
246
|
*/
|
|
186
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Two or more waiting approvals are ONE row ("⏸ 4 approvals waiting · oldest 41m · newest: <title>… · /approvals"), not
|
|
249
|
+
* one row each plus a summary line (owner, live 05:29): the bottom area must never grow with the inbox. A single approval
|
|
250
|
+
* keeps its own row. Focus (the user is walking the roster) shows the real rows so selection stays truthful.
|
|
251
|
+
*/
|
|
252
|
+
export function collapseApprovals(rows, now = Date.now()) {
|
|
253
|
+
const aps = rows.filter(r => r.kind === 'approval' && r.state === 'waiting');
|
|
254
|
+
if (aps.length < 2) return rows;
|
|
255
|
+
const started = aps.map(r => r.startedAt).filter(x => x != null);
|
|
256
|
+
const newest = aps.reduce((a, b) => ((b.startedAt ?? -Infinity) > (a.startedAt ?? -Infinity) ? b : a));
|
|
257
|
+
const one = { key: 'approval:*', id: '*', source: 'approvals', kind: 'approval', state: 'waiting', background: false, collapsed: true, count: aps.length,
|
|
258
|
+
name: `${aps.length} approvals waiting`, activity: '/approvals', status: 'pending', startedAt: started.length ? Math.min(...started) : null, finishedAt: null,
|
|
259
|
+
oldestMs: started.length ? now - Math.min(...started) : null, newestTitle: String(newest.detail?.description || newest.name || ''), detail: {} };
|
|
260
|
+
let placed = false;
|
|
261
|
+
return rows.flatMap(r => (aps.includes(r) ? (placed ? [] : (placed = true, [one])) : [r]));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function rosterView({ rows, cols, termRows = 40, max = MAX_ROWS, on = true, sel = 0, focus = false, now = Date.now(), motion = false }) {
|
|
187
265
|
if (!on || !rows.length) return { mode: 'hidden', lines: [], more: 0 };
|
|
266
|
+
if (!focus) rows = collapseApprovals(rows, now);
|
|
188
267
|
if (cols < MIN_FULL_COLS || termRows < MIN_FULL_ROWS) {
|
|
189
268
|
return { mode: 'summary', lines: [], more: 0, summary: clip(' ' + countsLine(rows) + ' · /roster', cols - 1) };
|
|
190
269
|
}
|
|
@@ -197,12 +276,19 @@ export function rosterView({ rows, cols, termRows = 40, max = MAX_ROWS, on = tru
|
|
|
197
276
|
const tone = toneName(r, pos, now); if (r.kind !== 'approval') pos++;
|
|
198
277
|
const actW = Math.min(18, Math.max(0, ...shown.map(x => [...(x.activity || '')].length)));
|
|
199
278
|
const act = clip(r.activity || '', actW).padEnd(actW);
|
|
200
|
-
const fixed = 1 + 1 + 1 + kindW + 1 + 1 + actW + 1 + ageW; // marker glyph kind name act elapsed
|
|
279
|
+
const fixed = 1 + 1 + 1 + kindW + 1 + 1 + actW + 1 + ageW + 2; // pulse column (space + glyph); marker glyph kind name act elapsed
|
|
201
280
|
const nameW = Math.max(8, cols - 2 - fixed - 1);
|
|
202
281
|
const name = clip(r.name, nameW).padEnd(nameW);
|
|
203
282
|
const marker = focus && i === sel ? '›' : ' ';
|
|
283
|
+
if (r.collapsed) { // one summary row: count, oldest age, newest title, where to go; clipped to the line
|
|
284
|
+
const head = `${r.count} approvals waiting · oldest ${elapsed(r.oldestMs)} · newest: `, tail = ' · /approvals';
|
|
285
|
+
const room = cols - 5 - [...head].length - [...tail].length;
|
|
286
|
+
const text = room >= 12 ? `${head}${clip(r.newestTitle, room)}${tail}` : clip(`${r.count} approvals waiting · oldest ${elapsed(r.oldestMs)}${tail}`, cols - 4); // too narrow for a title
|
|
287
|
+
return { key: r.key, kind: 'approval', state: r.state, tone: toneName(r, pos, now), selected: false, marker: ' ', glyph: GLYPH[r.state], rest: ' ' + text, pulse: '', pulseRole: 'dim', pulseKind: 'none', text: ` ${GLYPH[r.state]} ${text}` };
|
|
288
|
+
}
|
|
204
289
|
const rest = `${r.kind.padEnd(kindW)} ${name} ${act} ${el}`;
|
|
205
|
-
|
|
290
|
+
const p = pulseOf(r, now, { motion });
|
|
291
|
+
return { key: r.key, kind: r.kind, state: r.state, tone, selected: focus && i === sel, marker, glyph: glyphOf(r), glyphRole: r.state === 'failed' && endWord(r) !== 'failed' ? 'dim' : null, rest: ' ' + rest, pulse: ' ' + p.glyph, pulseRole: p.role, pulseKind: p.kind, text: `${marker}${glyphOf(r)} ${rest} ${p.glyph}` };
|
|
206
292
|
});
|
|
207
293
|
return { mode: 'full', lines, more: Math.max(0, rows.length - shown.length) };
|
|
208
294
|
}
|
|
@@ -210,7 +296,7 @@ export function rosterView({ rows, cols, termRows = 40, max = MAX_ROWS, on = tru
|
|
|
210
296
|
export function peekLines(row, now = Date.now()) {
|
|
211
297
|
if (!row) return [];
|
|
212
298
|
const age = ageText(row, now);
|
|
213
|
-
const out = [`${row.kind} ${row.id}`, `status ${row.status} · ${row
|
|
299
|
+
const out = [`${row.kind} ${row.id}`, `status ${row.status} · ${endWord(row)}${age ? ` · ${age}` : ''}`, `name ${row.name}`];
|
|
214
300
|
if (row.activity) out.push(`activity ${row.activity}`);
|
|
215
301
|
const d = row.detail || {};
|
|
216
302
|
if (d.description) out.push(`goal ${d.description}`);
|
package/lib/quoxon/route.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const VALUE_FLAGS = new Set(['-p', '--profile', '-n', '--name']);
|
|
7
7
|
const LEVELS = new Set(['off', 'on', 'full']); // `--verbose` alone means on; it only takes a value that is a level
|
|
8
|
-
const PRINT_VALUE_FLAGS = new Set(['--output', '--conversation', '--turn-id', '--timeout', '--provider', '--model']);
|
|
9
|
-
const BOOL_FLAGS = new Set(['--demo', '--demo-onboarding', '--no-clear', '--color', '--service-key']);
|
|
10
|
-
const ALL_FLAGS = new Set([...VALUE_FLAGS, ...PRINT_VALUE_FLAGS, ...BOOL_FLAGS, '--print', '--resume', '-h', '--help', '--']);
|
|
8
|
+
const PRINT_VALUE_FLAGS = new Set(['--output', '--conversation', '--turn-id', '--timeout', '--provider', '--model', '--agent', '--prompt-profile']);
|
|
9
|
+
const BOOL_FLAGS = new Set(['--demo', '--autoplay', '--demo-onboarding', '--no-clear', '--color', '--service-key']);
|
|
10
|
+
const ALL_FLAGS = new Set([...VALUE_FLAGS, ...PRINT_VALUE_FLAGS, ...BOOL_FLAGS, '--print', '--resume', '--autoplay-exit', '-h', '--help', '--']);
|
|
11
11
|
|
|
12
12
|
export function routesToQuoxon(argv, isTTY) {
|
|
13
13
|
let print = false;
|
|
@@ -18,17 +18,18 @@ export function routesToQuoxon(argv, isTTY) {
|
|
|
18
18
|
for (let i = 0; i < argv.length; i++) {
|
|
19
19
|
const a = argv[i];
|
|
20
20
|
if (a === '--') { return print; } // everything after -- is the prompt
|
|
21
|
-
if (a === '--print') { print = true; const n = argv[i + 1]; if (n !== undefined && !ALL_FLAGS.has(n) && !/^--(profile|verbose|output|conversation|turn-id|timeout|print|provider|model)=/.test(n)) i++; }
|
|
21
|
+
if (a === '--print') { print = true; const n = argv[i + 1]; if (n !== undefined && !ALL_FLAGS.has(n) && !/^--(profile|verbose|output|conversation|turn-id|timeout|print|provider|model|agent|prompt-profile)=/.test(n)) i++; }
|
|
22
22
|
else if (a.startsWith('--print=')) print = true;
|
|
23
23
|
else if (a === '-h' || a === '--help') help = true;
|
|
24
24
|
else if (a === '--verbose') { if (LEVELS.has(argv[i + 1])) i++; }
|
|
25
25
|
else if (a === '--resume') { const n = argv[i + 1]; if (n !== undefined && !n.startsWith('-')) i++; } // optional id or name
|
|
26
|
+
else if (a === '--autoplay-exit') { if (/^\d+(\.\d+)?$/.test(argv[i + 1] || '')) i++; } // optional seconds
|
|
26
27
|
else if (a.startsWith('--resume=')) { /* inline value */ }
|
|
27
28
|
else if (VALUE_FLAGS.has(a)) i++;
|
|
28
29
|
else if (/^--(profile|verbose|name)=/.test(a)) { /* inline value */ }
|
|
29
30
|
else if (BOOL_FLAGS.has(a)) { /* no value */ }
|
|
30
31
|
else if (PRINT_VALUE_FLAGS.has(a)) i++;
|
|
31
|
-
else if (/^--(output|conversation|turn-id|timeout|provider|model)=/.test(a)) { /* inline value */ }
|
|
32
|
+
else if (/^--(output|conversation|turn-id|timeout|provider|model|agent|prompt-profile)=/.test(a)) { /* inline value */ }
|
|
32
33
|
else if (hasPrint && !a.startsWith('-')) { print = true; } // the prompt, wherever it sits among the flags
|
|
33
34
|
else return false; // subcommand, positional, --help, --version, unknown flag
|
|
34
35
|
}
|