quoxcli 0.4.0 → 0.5.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/CHANGELOG.md +61 -0
- package/README.md +2 -2
- package/bin/quox.js +19 -1
- package/bin/quoxon.js +6 -1
- package/lib/api/endpoints/admin.js +7 -0
- package/lib/api/endpoints/agent-attach.js +7 -0
- package/lib/api/endpoints/compliance.js +25 -0
- package/lib/api/endpoints/inbox.js +6 -0
- package/lib/api/endpoints/inboxq.js +19 -0
- package/lib/api/endpoints/index.js +2 -0
- package/lib/api/endpoints/mon.js +122 -0
- package/lib/api/endpoints/tools.js +13 -0
- package/lib/api/endpoints/v1.js +13 -0
- package/lib/commands/admin.js +35 -0
- package/lib/commands/agentAttach.js +15 -0
- package/lib/commands/approvals.js +193 -3
- package/lib/commands/ask.js +1 -1
- package/lib/commands/auth.js +64 -11
- package/lib/commands/backup.js +11 -4
- package/lib/commands/capabilities.js +3 -2
- package/lib/commands/chat.js +15 -0
- package/lib/commands/claude.js +4 -1
- package/lib/commands/compliance.js +109 -0
- package/lib/commands/connect.js +69 -0
- package/lib/commands/dev.js +6 -8
- package/lib/commands/doctor.js +22 -3
- package/lib/commands/evidence.js +2 -1
- package/lib/commands/gripe.js +24 -13
- package/lib/commands/inbox.js +37 -3
- package/lib/commands/inboxq.js +147 -0
- package/lib/commands/kuma.js +78 -5
- package/lib/commands/licenceLinkView.js +311 -0
- package/lib/commands/linkedin.js +19 -0
- package/lib/commands/loop.js +30 -3
- package/lib/commands/lucid.js +1 -0
- package/lib/commands/mail.js +161 -20
- package/lib/commands/memory.js +68 -4
- package/lib/commands/memorySync.js +158 -0
- package/lib/commands/mon.js +55 -144
- package/lib/commands/notes.js +29 -7
- package/lib/commands/notesSeal.js +259 -0
- package/lib/commands/objective.js +5 -2
- package/lib/commands/offload.js +2 -3
- package/lib/commands/plan.js +17 -0
- package/lib/commands/provider.js +29 -8
- package/lib/commands/qlarity.js +32 -1
- package/lib/commands/run.js +2 -1
- package/lib/commands/server.js +37 -0
- package/lib/commands/setup.js +127 -75
- package/lib/commands/skills.js +4 -1
- package/lib/commands/slot.js +49 -9
- package/lib/commands/status.js +92 -44
- package/lib/commands/tool.js +30 -0
- package/lib/commands/vault.js +6 -3
- package/lib/commands/walkthrough.js +14 -2
- package/lib/commands/watch.js +11 -2
- package/lib/commands/wazuh.js +824 -0
- package/lib/commands/wazuhIntake.js +317 -0
- package/lib/commands/wazuhReport.js +192 -0
- package/lib/commands/workflow.js +47 -4
- package/lib/commands/x.js +50 -0
- package/lib/core/announce.js +86 -0
- package/lib/core/auth.js +20 -2
- package/lib/core/callMe.js +118 -0
- package/lib/core/client.js +27 -1
- package/lib/core/commandRegistrars.js +8 -0
- package/lib/core/config.js +28 -2
- package/lib/core/confirm.js +68 -7
- package/lib/core/crossOrgApprovals.js +69 -0
- package/lib/core/errors.js +52 -1
- package/lib/core/governedSession.js +135 -5
- package/lib/core/inboxResolution.js +3 -2
- package/lib/core/loginGuard.js +73 -0
- package/lib/core/offload.js +37 -0
- package/lib/core/org-override.js +5 -1
- package/lib/core/output.js +1 -1
- package/lib/core/profileTrust.js +182 -0
- package/lib/core/quoxglyph.js +22 -1
- package/lib/core/router.js +14 -0
- package/lib/core/stepUpPrompt.js +99 -0
- package/lib/core/version.js +1 -1
- package/lib/core/walkthrough.js +9 -5
- package/lib/dragon/select.js +2 -2
- package/lib/quoxon/EXTENSIONS.md +142 -19
- package/lib/quoxon/api.js +185 -34
- package/lib/quoxon/app.js +1032 -152
- package/lib/quoxon/approvalBatch.js +117 -0
- package/lib/quoxon/approvalFacts.js +56 -2
- package/lib/quoxon/approvalSentence.js +8 -4
- package/lib/quoxon/approvalsView.js +69 -11
- package/lib/quoxon/autoOpen.js +94 -0
- package/lib/quoxon/backgroundWork.js +59 -0
- package/lib/quoxon/bang.js +1 -1
- package/lib/quoxon/capabilities.js +7 -2
- package/lib/quoxon/commandShort.js +7 -0
- package/lib/quoxon/commands.js +40 -10
- package/lib/quoxon/connect.js +5 -3
- package/lib/quoxon/deckLazy.js +58 -0
- package/lib/quoxon/destructive.js +35 -0
- package/lib/quoxon/dnd.js +71 -0
- package/lib/quoxon/dragonCompanion.js +37 -7
- package/lib/quoxon/dragonMoments.js +78 -0
- package/lib/quoxon/echo.js +1 -0
- package/lib/quoxon/events.js +43 -7
- package/lib/quoxon/extensions.js +398 -38
- package/lib/quoxon/gripeQueue.js +135 -0
- package/lib/quoxon/inboxView.js +125 -22
- package/lib/quoxon/keys.js +2 -2
- package/lib/quoxon/loopCard.js +5 -1
- package/lib/quoxon/loopCards.js +17 -8
- package/lib/quoxon/loopStream.js +20 -9
- package/lib/quoxon/mailView.js +71 -0
- package/lib/quoxon/main.js +20 -5
- package/lib/quoxon/markdown.js +1 -1
- package/lib/quoxon/model.js +15 -0
- package/lib/quoxon/neverAnswered.js +20 -0
- package/lib/quoxon/objects/away.js +43 -5
- package/lib/quoxon/onboarding/connectServer.js +162 -0
- package/lib/quoxon/onboarding/copy.js +6 -0
- package/lib/quoxon/onboarding/index.js +5 -43
- package/lib/quoxon/onboarding/probe.js +20 -1
- package/lib/quoxon/onboarding/view.js +16 -3
- package/lib/quoxon/orgSwitch.js +22 -0
- package/lib/quoxon/print.js +2 -0
- package/lib/quoxon/profileSwitch.js +7 -0
- package/lib/quoxon/promptHistory.js +3 -1
- package/lib/quoxon/render.js +63 -14
- package/lib/quoxon/roster.js +10 -5
- package/lib/quoxon/rosterSessions.js +44 -0
- package/lib/quoxon/screenContext.js +1 -1
- package/lib/quoxon/secretShape.js +86 -0
- package/lib/quoxon/slash/agents.js +2 -1
- package/lib/quoxon/slash/argSources.js +9 -2
- package/lib/quoxon/slash/args.js +4 -2
- package/lib/quoxon/slash/broadcast.js +18 -5
- package/lib/quoxon/slash/broadcastRun.js +13 -2
- package/lib/quoxon/slash/cliLeaves.json +63 -1
- package/lib/quoxon/slash/hatWalk.js +1 -1
- package/lib/quoxon/slash/palette.js +22 -1
- package/lib/quoxon/slash/paletteController.js +10 -4
- package/lib/quoxon/slash/restart.js +7 -4
- package/lib/quoxon/slash/stepUp.js +5 -5
- package/lib/quoxon/slash/stepUpCmds.js +74 -1
- package/lib/quoxon/slashSpinner.js +35 -0
- package/lib/quoxon/slotAttach.js +8 -0
- package/lib/quoxon/slotChat.js +17 -0
- package/lib/quoxon/slotFormat.js +1 -1
- package/lib/quoxon/slotOutbox.js +113 -0
- package/lib/quoxon/suggestCommand.js +1 -1
- package/lib/quoxon/term.js +25 -1
- package/lib/quoxon/transcript.js +2 -1
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +1 -1
- package/lib/quoxon/whatsnew.jsonl +36 -0
- package/lib/server/installFlow.js +168 -0
- package/lib/server/status.js +15 -1
- package/lib/toolOutputShape.js +55 -0
- package/package.json +3 -2
- package/vendor/dragonlab/SOURCE +3 -3
- package/vendor/dragonlab/design/quoxperticus/v2/quoxperticus-master.svg +59 -0
- package/vendor/dragonlab/design/quoxperticus/v3/character.mjs +43 -0
- package/vendor/dragonlab/design/quoxperticus/v4/character.mjs +57 -0
- package/vendor/dragonlab/design/quoxperticus/v4/review.mjs +35 -0
- package/vendor/dragonlab/design/quoxperticus/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/vulcan/v1/character.mjs +34 -0
- package/vendor/dragonlab/design/vulcan/v1/vulcan-master.svg +57 -0
- package/vendor/dragonlab/design/vulcan/v2/character.mjs +39 -0
- package/vendor/dragonlab/design/vulcan/v3/character.mjs +41 -0
- package/vendor/dragonlab/design/vulcan/v3/review.mjs +35 -0
- package/vendor/dragonlab/design/vulcan/v3/watch.mjs +56 -0
- package/vendor/dragonlab/factory/svg-rig-contour.mjs +64 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +20 -0
- package/vendor/dragonlab/integration/companions-v1/quoxperticus.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +1 -1
- package/vendor/dragonlab/integration/companions-v1/vulcan.mjs +2 -0
package/lib/quoxon/events.js
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* turn : null | { id, status: running|interrupting, startedAt, stage, activity }
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { eventLine } from '../core/announce.js';
|
|
15
|
+
import { isDestructive, isDestructiveTool, destructiveObject } from './destructive.js';
|
|
16
|
+
import { heldReason } from './bulkRisk.js';
|
|
14
17
|
import { approvalSentence } from './approvalSentence.js';
|
|
15
18
|
import { dividerStep } from './dividerAocl.js';
|
|
16
19
|
import { approvalFacts } from './approvalFacts.js';
|
|
@@ -80,6 +83,15 @@ export function textGate(st, ev) {
|
|
|
80
83
|
|
|
81
84
|
const tms = (v) => { const t = typeof v === 'number' ? v : Date.parse(v); return Number.isFinite(t) ? t : null; };
|
|
82
85
|
|
|
86
|
+
/** A machine source key (`devsuite_pack_install:<host>:<pack>:<ver>`) is never shown as a requester: it names a human-readable origin instead. */
|
|
87
|
+
const SOURCE_KEY = /^[A-Za-z][\w.-]*(:[^:\s]+){2,}$/;
|
|
88
|
+
const SOURCE_NAMES = [[/^devsuite_/i, 'DevSuite (skill-pack sync)']];
|
|
89
|
+
export function humanRequester(v) {
|
|
90
|
+
const t = String(v ?? '').trim(); if (!t || !SOURCE_KEY.test(t)) return t || null;
|
|
91
|
+
const hit = SOURCE_NAMES.find(([re]) => re.test(t)); if (hit) return hit[1];
|
|
92
|
+
const head = t.split(':')[0].replace(/[_-]+/g, ' ').trim();
|
|
93
|
+
return head ? head.charAt(0).toUpperCase() + head.slice(1) : 'plugin or workflow';
|
|
94
|
+
}
|
|
83
95
|
const short = (v, n = 120) => { const t = typeof v === 'string' ? v : (v == null ? '' : JSON.stringify(v)); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
|
|
84
96
|
const argText = (a) => a && typeof a === 'object' && !Array.isArray(a) && (a.argv || a.command) ? argText(a.argv ?? a.command) : Array.isArray(a) ? a.join(' ') : (a && typeof a === 'object' ? Object.entries(a).filter(([, v]) => v != null && typeof v !== 'object').slice(0, 4).map(([k, v]) => `${k}=${v}`).join(' ') : (a == null ? '' : String(a)));
|
|
85
97
|
|
|
@@ -97,7 +109,7 @@ export function describeInboxItem(it) {
|
|
|
97
109
|
const argv = m.argv ?? m.args ?? c.argv ?? c.args ?? p.argv ?? p.args ?? m.command ?? c.command ?? p.command ?? null;
|
|
98
110
|
const cmd = short([m.leaf || c.leaf || p.leaf, argText(argv)].filter(Boolean).join(' ').trim() || null);
|
|
99
111
|
const target = w.target || m.target || c.target || p.target || cmd || w.target_none || c.target_org || c.org_name || p.org_name || c.org_id || p.org_id || null;
|
|
100
|
-
const by = ob.agent || ob.agent_id || ob.session_name || ob.session || c.requested_by?.agent_label || c.source_label || p.source || it?.source || null;
|
|
112
|
+
const by = humanRequester(ob.agent || ob.agent_id || ob.session_name || ob.session || c.requested_by?.agent_label || c.source_label || p.source || it?.source || null);
|
|
101
113
|
const what = w.summary ? String(w.summary) : (it?.title ? short(String(it.title), 160) : null);
|
|
102
114
|
return { tool, target: target ? short(target, 200) : null, title: what, by: by ? String(by) : null, at: it?.created_at ?? it?.createdAt ?? null };
|
|
103
115
|
}
|
|
@@ -129,6 +141,9 @@ export function cardFromInboxItem(it, { since = 0, userId = null, conversationId
|
|
|
129
141
|
tool, target: d.target, what: d.title, askedBy: d.by, askedAt: d.at, scope: null, risk: c.risk || null,
|
|
130
142
|
url: it.dashboard_url || null, shownAt: now, undescribable: !d.tool && !d.target && !it._plan,
|
|
131
143
|
plan: it._plan ? { lines: planLines(it._plan.steps), loaded: it._plan.loaded !== false, orgId: it._plan.orgId || null, orgName: it._plan.orgName || null } : null, // the plan's steps + the item's own org
|
|
144
|
+
destructive: isDestructive(it) ? destructiveObject(it) : null, // typed-name confirm before a y approves it
|
|
145
|
+
disruptive: [it, it.payload, c, m, it.payload?.what].some(o => o && o.disruptive === true), // server-marked: never batched
|
|
146
|
+
held: heldReason(it), // a risky ask is never part of a batch card
|
|
132
147
|
facts: approvalFacts(it), // APPROVAL-WWWH: the exact command, hosts, chain, reason, time and read-only class
|
|
133
148
|
// only an item that names THIS conversation is ours; anything else came from another session and must not take the keyboard
|
|
134
149
|
foreign: !here,
|
|
@@ -140,7 +155,7 @@ export function cardFromInboxItem(it, { since = 0, userId = null, conversationId
|
|
|
140
155
|
/** Where an inbox approval came from: its source label, else the payload source, else its type. */
|
|
141
156
|
export function approvalSourceLabel(it) {
|
|
142
157
|
const c = it?.payload?.approval_context || {};
|
|
143
|
-
return
|
|
158
|
+
return humanRequester(c.source_label || it?.payload?.source || it?.source || (c.requested_by && c.requested_by.agent_label) || it?.payload?.type || 'plugin or workflow');
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
/**
|
|
@@ -377,7 +392,7 @@ function reduceInner(s, a) {
|
|
|
377
392
|
case 'help':
|
|
378
393
|
return { ...s, history: [...s.history, { id: nid(), kind: 'help', model: a.model }] };
|
|
379
394
|
case 'object': // inline object view (objects/): pure data, rebuilt at render width
|
|
380
|
-
return { ...s, history: [...s.history, { id: nid(), kind: 'object', obj: a.obj }] };
|
|
395
|
+
return { ...s, notify: scopeNotify(s.notify, a.obj), history: [...s.history, { id: nid(), kind: 'object', obj: a.obj }] };
|
|
381
396
|
case 'error':
|
|
382
397
|
return flush(addBlock(s, { kind: 'error', text: a.text }), false);
|
|
383
398
|
case 'verbose':
|
|
@@ -435,11 +450,11 @@ function reduceInner(s, a) {
|
|
|
435
450
|
const card = cardFromInboxItem(a.item, { since: s.turn?.startedAt ?? a.since ?? 0, userId: a.userId, conversationId: s.ctx.conversationId, now: a.now || 0 });
|
|
436
451
|
if (card && card.approvalId) { // an event card raised without an inbox id learns it from the poll (else y answers a null id: 404)
|
|
437
452
|
const j = s.approvals.findIndex(x => !x.inboxItemId && x.approvalId === card.approvalId);
|
|
438
|
-
if (j >= 0) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], inboxItemId: card.inboxItemId, url: approvals[j].url || card.url, facts: approvals[j].facts || card.facts }; return { ...s, approvals, seenInbox: { ...s.seenInbox, [card.inboxItemId]: true } }; }
|
|
453
|
+
if (j >= 0) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], inboxItemId: card.inboxItemId, url: approvals[j].url || card.url, facts: approvals[j].facts || card.facts, destructive: approvals[j].destructive || card.destructive }; return { ...s, approvals, seenInbox: { ...s.seenInbox, [card.inboxItemId]: true } }; }
|
|
439
454
|
}
|
|
440
455
|
if (card && card.approvalId) { // an event stub that could not be described learns its description from the inbox item
|
|
441
456
|
const j = s.approvals.findIndex(x => x.undescribable && (x.inboxItemId === card.inboxItemId || x.approvalId === card.approvalId));
|
|
442
|
-
if (j >= 0 && !card.undescribable) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], tool: card.tool, target: card.target, what: card.what, askedBy: card.askedBy, askedAt: card.askedAt, facts: card.facts, undescribable: false }; return { ...s, approvals }; }
|
|
457
|
+
if (j >= 0 && !card.undescribable) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], tool: card.tool, target: card.target, what: card.what, askedBy: card.askedBy, askedAt: card.askedAt, facts: card.facts, destructive: approvals[j].destructive || card.destructive, undescribable: false }; return { ...s, approvals }; }
|
|
443
458
|
}
|
|
444
459
|
if (!card || s.seenInbox[card.inboxItemId] || s.approvals.some(x => x.inboxItemId === card.inboxItemId || (card.approvalId && x.approvalId === card.approvalId))) return s;
|
|
445
460
|
if (card.foreign) { // another conversation's card: a compact notice, never the focused card
|
|
@@ -461,6 +476,10 @@ function reduceInner(s, a) {
|
|
|
461
476
|
if (a.batch) return rest; // /approve N prints its own one-line summary
|
|
462
477
|
if (a.quiet && a.action === 'approve') return rest;
|
|
463
478
|
if (a.action !== 'approve') return note(rest, approvalSentence(a, false), 'warn');
|
|
479
|
+
if (a.foreign && a.approvalId) { // tracked: the poll adds one follow-up line with the outcome
|
|
480
|
+
const run = { callId: a.callId, approvalId: a.approvalId, tool: a.tool, target: a.target };
|
|
481
|
+
return note({ ...rest, running: [...rest.running.filter(x => x.callId !== a.callId), run] }, approvalSentence(a, true), 'ok');
|
|
482
|
+
}
|
|
464
483
|
if (a.foreign) return note(rest, approvalSentence(a, true), 'ok');
|
|
465
484
|
if (a.tool === 'hat.switch') { // applied server-side by the inbox approve; nothing to wait for
|
|
466
485
|
const to = String(a.target || '').split('\u2192').pop().replace(/\(privileged\)/, '').trim() || 'the new hat';
|
|
@@ -475,7 +494,7 @@ function reduceInner(s, a) {
|
|
|
475
494
|
const n = { ...s, running: s.running.filter(x => x.callId !== a.callId) };
|
|
476
495
|
const what = run ? `${run.tool || 'approval'}${run.target ? ` on ${run.target}` : ''}` : 'approval';
|
|
477
496
|
if (a.outcome === 'done') return note(n, `result: ${what}: ${a.text || 'completed'}`, 'ok');
|
|
478
|
-
if (a.outcome === 'failed') return note(n, `failed: ${what}: ${a.text || 'error'}`, 'warn');
|
|
497
|
+
if (a.outcome === 'failed') return note(n, `${Number.isInteger(a.exit) && a.exit >= 1 ? `ran, exit ${a.exit}` : 'failed'}: ${what}: ${a.text || 'error'}`, 'warn');
|
|
479
498
|
if (a.outcome === 'denied') return note(n, `denied: ${what}`, 'warn');
|
|
480
499
|
// FW4: never silent and never a guess. Say what is unknown and where the authoritative answer is.
|
|
481
500
|
const check = run && run.approvalId ? `quox approvals result ${run.approvalId} (or /approvals)` : '/approvals (no approval id was returned, so it cannot be tracked from here)';
|
|
@@ -495,6 +514,15 @@ function reduceInner(s, a) {
|
|
|
495
514
|
}
|
|
496
515
|
|
|
497
516
|
/** The first card is armed from the moment it is actually SHOWN, not from when it was received. */
|
|
517
|
+
/** An away/sitrep card that lists older items or a source it cannot see: the approvals bar must not say all-clear next to it. */
|
|
518
|
+
function scopeNotify(n, obj) {
|
|
519
|
+
const d = obj?.data;
|
|
520
|
+
if (!d || (obj.type !== 'away' && obj.type !== 'sitrep')) return n;
|
|
521
|
+
const outstanding = obj.type === 'away' ? (d.standing?.count || 0) : (d.needs || []).length;
|
|
522
|
+
const blind = Array.isArray(d.blind) ? d.blind.length : 0;
|
|
523
|
+
return { ...n, outstanding, blind };
|
|
524
|
+
}
|
|
525
|
+
|
|
498
526
|
export function reduce(s, a) {
|
|
499
527
|
let n = reduceInner(s, a);
|
|
500
528
|
// TUI-DIVIDER: the bottom-rule AOCL strip, moved only by real turn events (dividerAocl.js).
|
|
@@ -593,7 +621,8 @@ function reduceEvent(s, ev, now) {
|
|
|
593
621
|
target: ev.target, scope: ev.scope, risk: ev.risk, url: ev.dashboard_url, shownAt: now,
|
|
594
622
|
undescribable: ev.describable === false || !ev.target,
|
|
595
623
|
turnId: s.turn?.id ?? ev.turn_id ?? null, // the turn this card arrived in (a stream event is always this conversation)
|
|
596
|
-
privileged: isPrivileged({ tool: ev.tool, risk: ev.risk, flag: ev.privileged === true || ev.elevated === true })
|
|
624
|
+
privileged: isPrivileged({ tool: ev.tool, risk: ev.risk, flag: ev.privileged === true || ev.elevated === true }),
|
|
625
|
+
destructive: ev.destructive === true || ev.irreversible === true || isDestructiveTool(ev.tool) ? (() => { const v = String(ev.object_name || ev.target || ev.inbox_item_id || ev.tool || 'item').slice(0, 80); return { label: v, name: v }; })() : null
|
|
597
626
|
};
|
|
598
627
|
if (s.approvals.some(x => x.callId === card.callId)) return s;
|
|
599
628
|
const dup = s.approvals.findIndex(x => (card.inboxItemId && x.inboxItemId === card.inboxItemId) || (card.approvalId && x.approvalId === card.approvalId));
|
|
@@ -611,6 +640,13 @@ function reduceEvent(s, ev, now) {
|
|
|
611
640
|
const n = { ...s, approvals: s.approvals.filter(x => x.callId !== ev.call_id) };
|
|
612
641
|
return card || ev.decision ? note(n, `${ev.decision === 'approved' ? '✔ approved by you' : `approval ${ev.decision || 'resolved'}`}${card ? `: ${card.tool} on ${card.target}` : ''}`, ev.decision === 'approved' ? 'ok' : 'warn') : n;
|
|
613
642
|
}
|
|
643
|
+
// QUOX-ANNOUNCE A2: a Quox effect of this turn (memory saved, approval held, delegation ...), one line, same renderer as the CLI verbs.
|
|
644
|
+
case 'quox_event': {
|
|
645
|
+
const text = eventLine(ev);
|
|
646
|
+
if (!text) return s;
|
|
647
|
+
if (ev.status === 'fail') return addBlock(s, { kind: 'error', text });
|
|
648
|
+
return addBlock(s, { kind: 'note', text, tone: ev.status === 'ok' ? 'ok' : ev.status === 'busy' ? 'accent' : 'warn' });
|
|
649
|
+
}
|
|
614
650
|
case 'steer.accepted': {
|
|
615
651
|
const i = s.steering.findIndex(x => x.text === ev.text);
|
|
616
652
|
const steering = i >= 0 ? s.steering.filter((_, k) => k !== i) : s.steering.slice(1);
|