muonroi-cli 1.8.5 → 1.9.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/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +20 -4
- package/dist/packages/agent-harness-core/src/event-tee.js +32 -5
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +14 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +86 -10
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +24 -2
- package/dist/src/agent-harness/mock-model.d.ts +10 -0
- package/dist/src/agent-harness/mock-model.js +6 -2
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/keys.d.ts +8 -45
- package/dist/src/cli/keys.js +18 -324
- package/dist/src/council/clarifier.d.ts +4 -2
- package/dist/src/council/clarifier.js +158 -36
- package/dist/src/council/debate-planner.js +3 -2
- package/dist/src/council/debate.js +59 -5
- package/dist/src/council/index.d.ts +23 -0
- package/dist/src/council/index.js +37 -2
- package/dist/src/council/llm.d.ts +62 -0
- package/dist/src/council/llm.js +123 -23
- package/dist/src/council/panel-select.js +13 -3
- package/dist/src/council/planner.js +17 -0
- package/dist/src/council/preflight.d.ts +10 -0
- package/dist/src/council/preflight.js +36 -0
- package/dist/src/council/prompts.d.ts +9 -1
- package/dist/src/council/prompts.js +23 -6
- package/dist/src/council/types.d.ts +10 -0
- package/dist/src/ee/auth.d.ts +19 -0
- package/dist/src/ee/auth.js +39 -0
- package/dist/src/ee/client.js +28 -3
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/flow/compaction/compress.d.ts +2 -2
- package/dist/src/flow/compaction/compress.js +21 -8
- package/dist/src/flow/compaction/extract.d.ts +3 -3
- package/dist/src/flow/compaction/extract.js +6 -6
- package/dist/src/flow/compaction/index.d.ts +2 -1
- package/dist/src/flow/compaction/index.js +29 -3
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/flags.d.ts +11 -0
- package/dist/src/gsd/flags.js +19 -0
- package/dist/src/gsd/plan-council.js +104 -72
- package/dist/src/gsd/verdict-schema.d.ts +1 -1
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/index.js +129 -260
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/mcp/client-pool.js +43 -15
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog.json +19 -19
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/compaction.d.ts +2 -3
- package/dist/src/orchestrator/compaction.js +8 -8
- package/dist/src/orchestrator/council-manager.js +9 -8
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +26 -8
- package/dist/src/orchestrator/orchestrator.d.ts +25 -0
- package/dist/src/orchestrator/orchestrator.js +204 -50
- package/dist/src/orchestrator/preprocessor.js +2 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +5 -0
- package/dist/src/orchestrator/safety-intercept.js +7 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +8 -1
- package/dist/src/orchestrator/stall-watchdog.js +24 -3
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +54 -21
- package/dist/src/orchestrator/tool-engine.d.ts +19 -0
- package/dist/src/orchestrator/tool-engine.js +241 -25
- package/dist/src/orchestrator/turn-watchdog.d.ts +7 -0
- package/dist/src/orchestrator/turn-watchdog.js +38 -9
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +2 -2
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/llm-classify.d.ts +78 -3
- package/dist/src/pil/llm-classify.js +351 -111
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +7 -0
- package/dist/src/pil/pipeline.js +2 -0
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +37 -18
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +14 -1
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/index.js +7 -0
- package/dist/src/product-loop/loop-driver.js +21 -8
- package/dist/src/product-loop/phase-plan.d.ts +16 -0
- package/dist/src/product-loop/phase-plan.js +42 -4
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +30 -11
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +62 -0
- package/dist/src/product-loop/sprint-runner.js +309 -8
- package/dist/src/product-loop/types.d.ts +25 -0
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +14 -0
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -70
- package/dist/src/providers/auth/types.d.ts +8 -0
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +21 -17
- package/dist/src/providers/keychain.js +124 -135
- package/dist/src/providers/runtime.d.ts +24 -9
- package/dist/src/providers/runtime.js +48 -37
- package/dist/src/providers/strategies/thinking-mode.js +9 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/self-qa/agentic-loop.js +3 -2
- package/dist/src/storage/transcript.js +56 -2
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +76 -1
- package/dist/src/tools/registry.d.ts +17 -0
- package/dist/src/tools/registry.js +116 -1
- package/dist/src/types/index.d.ts +29 -1
- package/dist/src/ui/app.js +67 -4
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/council-phase-timeline.js +17 -2
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +2 -1
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/menu-items.js +11 -2
- package/dist/src/ui/use-app-logic.js +354 -224
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/settings.d.ts +27 -0
- package/dist/src/utils/settings.js +38 -2
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/verify/entrypoint.js +51 -16
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/package.json +1 -2
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -194
|
@@ -4,17 +4,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
|
4
4
|
*
|
|
5
5
|
* Claude-Code-style "tool streak" panel — replaces the previous one-line-per-
|
|
6
6
|
* tool render. Lifecycle:
|
|
7
|
-
* - active → header "●
|
|
8
|
-
* - done → collapsed "●
|
|
7
|
+
* - active → header "● Reading 2 files… (N tools · Ms)" + indented item list
|
|
8
|
+
* - done → collapsed "● Read 2 files, ran 1 shell command ctrl+e to expand"
|
|
9
9
|
* - failed → always-expanded header + items, error item flagged red
|
|
10
10
|
*
|
|
11
|
+
* The header names the work per CATEGORY (see utils/tool-summary.ts) rather
|
|
12
|
+
* than saying "Done": once the group collapses that line is all the user has,
|
|
13
|
+
* so it has to carry the meaning. Items add the detail a glance needs — the
|
|
14
|
+
* full shell command for bash, "Wrote N lines to <path>" before a write diff.
|
|
15
|
+
*
|
|
11
16
|
* The active group lives at the tail of the message stream as a regular
|
|
12
17
|
* ChatEntry of type "tool_group"; closing the streak (assistant text arrives
|
|
13
18
|
* or stream ends) flips its state to done/failed in place.
|
|
14
19
|
*/
|
|
15
20
|
import { Semantic } from "@muonroi/agent-harness-opentui";
|
|
16
21
|
import { trunc } from "../utils/text.js";
|
|
17
|
-
import {
|
|
22
|
+
import { activeToolGroupHeader, doneToolGroupSummary } from "../utils/tool-summary.js";
|
|
23
|
+
import { toolLabel } from "../utils/tools.js";
|
|
18
24
|
import { DiffView } from "./diff-view.js";
|
|
19
25
|
// Tools whose result carries a FileDiff worth rendering inline under the item
|
|
20
26
|
// line. Mirrors the per-tool branch in message-view.tsx so grouped edits show
|
|
@@ -33,6 +39,42 @@ function fmtElapsed(ms) {
|
|
|
33
39
|
const remS = Math.floor(s % 60);
|
|
34
40
|
return `${m}m ${remS.toString().padStart(2, "0")}s`;
|
|
35
41
|
}
|
|
42
|
+
// Max command lines rendered inline for a bash item; the rest are elided so a
|
|
43
|
+
// 40-line heredoc can't take over the transcript.
|
|
44
|
+
const BASH_INLINE_LINES = 8;
|
|
45
|
+
/** The command a bash item ran, split for multi-line display. Null when absent. */
|
|
46
|
+
export function bashCommandLines(tc) {
|
|
47
|
+
let cmd;
|
|
48
|
+
try {
|
|
49
|
+
cmd = JSON.parse(tc.function.arguments)?.command;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// Arguments stream in as partial JSON while the call is still assembling —
|
|
53
|
+
// an unparseable value here is expected, not an error worth surfacing.
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (typeof cmd !== "string" || !cmd.trim())
|
|
57
|
+
return null;
|
|
58
|
+
const lines = cmd.replace(/\r\n/g, "\n").split("\n");
|
|
59
|
+
if (lines.length <= BASH_INLINE_LINES)
|
|
60
|
+
return lines;
|
|
61
|
+
const hidden = lines.length - BASH_INLINE_LINES;
|
|
62
|
+
return [...lines.slice(0, BASH_INLINE_LINES), `… +${hidden} more line${hidden > 1 ? "s" : ""}`];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One-line prose outcome for a write/edit, e.g. "Wrote 161 lines to a.ts" or
|
|
66
|
+
* "Edited a.ts (+12 -3)". Null when the tool isn't a file mutation.
|
|
67
|
+
*/
|
|
68
|
+
export function writeOutcomeLine(toolName, diff) {
|
|
69
|
+
if (toolName === "write_file") {
|
|
70
|
+
const n = diff.additions;
|
|
71
|
+
return `Wrote ${n} line${n === 1 ? "" : "s"} to ${diff.filePath}`;
|
|
72
|
+
}
|
|
73
|
+
if (toolName === "edit_file") {
|
|
74
|
+
return `Edited ${diff.filePath} (+${diff.additions} -${diff.removals})`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
36
78
|
function itemGlyph(item) {
|
|
37
79
|
if (item.failed)
|
|
38
80
|
return "✗";
|
|
@@ -51,21 +93,23 @@ export function ToolGroupView({ entry, t, expanded, modeColor }) {
|
|
|
51
93
|
const g = entry.toolGroup;
|
|
52
94
|
if (!g)
|
|
53
95
|
return null;
|
|
54
|
-
const
|
|
96
|
+
const toolNames = g.items.map((it) => it.toolCall.function.name);
|
|
55
97
|
const elapsed = (g.finishedAt ?? Date.now()) - g.startedAt;
|
|
56
98
|
const errorCount = g.items.filter((it) => it.failed).length;
|
|
57
99
|
const total = g.items.length;
|
|
58
100
|
// Decide whether the items list is visible. Active and failed always show
|
|
59
101
|
// items; done collapses unless user expanded with ctrl+e.
|
|
60
102
|
const showItems = g.state === "active" || g.state === "failed" || expanded;
|
|
61
|
-
// Header
|
|
62
|
-
// active: "Reading…"
|
|
63
|
-
// done: "
|
|
103
|
+
// Header names the actual work, counted per category:
|
|
104
|
+
// active: "Reading 2 files…"
|
|
105
|
+
// done: "Read 2 files, ran 1 shell command"
|
|
64
106
|
// failed: "Failed (k errors)"
|
|
107
|
+
// A bare "Done" told the user nothing about what just happened; the recap is
|
|
108
|
+
// the only line left once the group collapses, so it carries the meaning.
|
|
65
109
|
let headerVerb;
|
|
66
110
|
let headerColor;
|
|
67
111
|
if (g.state === "active") {
|
|
68
|
-
headerVerb =
|
|
112
|
+
headerVerb = activeToolGroupHeader(toolNames);
|
|
69
113
|
headerColor = entry.modeColor || modeColor || t.accent;
|
|
70
114
|
}
|
|
71
115
|
else if (g.state === "failed") {
|
|
@@ -73,10 +117,14 @@ export function ToolGroupView({ entry, t, expanded, modeColor }) {
|
|
|
73
117
|
headerColor = t.diffRemovedFg;
|
|
74
118
|
}
|
|
75
119
|
else {
|
|
76
|
-
headerVerb =
|
|
120
|
+
headerVerb = doneToolGroupSummary(toolNames);
|
|
77
121
|
headerColor = t.diffAddedFg;
|
|
78
122
|
}
|
|
79
|
-
|
|
123
|
+
// A restored group's timestamps are write-time, not a measured span — print
|
|
124
|
+
// the count only rather than a fabricated duration.
|
|
125
|
+
const stats = g.restored
|
|
126
|
+
? `${total} tool${total !== 1 ? "s" : ""}`
|
|
127
|
+
: `${total} tool${total !== 1 ? "s" : ""} · ${fmtElapsed(elapsed)}`;
|
|
80
128
|
// Items to render: active capped at ACTIVE_INLINE_LIMIT (overflow line);
|
|
81
129
|
// expanded done/failed shows everything.
|
|
82
130
|
const overflow = g.state === "active" && total > ACTIVE_INLINE_LIMIT ? total - ACTIVE_INLINE_LIMIT : 0;
|
|
@@ -85,7 +133,17 @@ export function ToolGroupView({ entry, t, expanded, modeColor }) {
|
|
|
85
133
|
const label = trunc(toolLabel(it.toolCall), 90);
|
|
86
134
|
const errSuffix = it.failed && it.result?.error ? ` — ${trunc(it.result.error.replace(/\s+/g, " "), 60)}` : "";
|
|
87
135
|
const diff = !it.failed && DIFF_TOOLS.has(it.toolCall.function.name) ? it.result?.diff : undefined;
|
|
88
|
-
|
|
136
|
+
// A bash call's whole point is WHICH command ran; truncating it to
|
|
137
|
+
// one elided line hides that. Show every line, indented under the
|
|
138
|
+
// item, the way a shell transcript reads.
|
|
139
|
+
const bashLines = it.toolCall.function.name === "bash" ? bashCommandLines(it.toolCall) : null;
|
|
140
|
+
// Shell transcript shape: "$ <first line>" on the item line, any
|
|
141
|
+
// continuation lines aligned under it.
|
|
142
|
+
const headLine = bashLines ? `$ ${bashLines[0]}` : label;
|
|
143
|
+
const restLines = bashLines ? bashLines.slice(1) : [];
|
|
144
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { style: { fg: itemColor(it, t) }, children: `${itemGlyph(it)} ` }), _jsx("span", { style: { fg: t.textMuted }, children: trunc(headLine, 100) }), errSuffix && _jsx("span", { style: { fg: t.diffRemovedFg }, children: errSuffix })] }), restLines.map((line, li) => (
|
|
145
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: command lines are positional and immutable
|
|
146
|
+
_jsx("box", { paddingLeft: 2, children: _jsx("text", { fg: t.textMuted, children: trunc(line, 100) }) }, `${it.toolCall.id}-cmd-${li}`))), diff && writeOutcomeLine(it.toolCall.function.name, diff) && (_jsx("box", { paddingLeft: 2, children: _jsx("text", { fg: t.textMuted, children: writeOutcomeLine(it.toolCall.function.name, diff) }) })), diff && _jsx(DiffView, { t: t, diff: diff })] }, it.toolCall.id));
|
|
89
147
|
})] })), g.state === "done" && !expanded && total > 0 && (_jsx("box", { paddingLeft: 2, children: _jsx("text", { fg: t.textDim, children: "ctrl+e to expand" }) }))] }) }));
|
|
90
148
|
}
|
|
91
149
|
//# sourceMappingURL=tool-group.js.map
|
|
@@ -12,7 +12,7 @@ import { UpdateModal } from "../modals/update-modal.js";
|
|
|
12
12
|
import { WalletPickerModal } from "../modals/wallet-picker-modal.js";
|
|
13
13
|
import { ScheduleBrowserModal } from "../schedule-modal.js";
|
|
14
14
|
export function ModalsLayer(props) {
|
|
15
|
-
const { agentRows, agentsEditorDraft, agentsEditorError, agentsEditorField, agentsEditorModelIndex, agentsEditorSyncKey, agentsModalIndex, agentsSearchQuery, apiKeyError, apiKeyInputRef, apiKeyPrompt,
|
|
16
|
-
return (_jsxs(_Fragment, { children: [showApiKeyModal && (_jsx(ApiKeyModal, { t: t, width: width, height: height, inputRef: apiKeyInputRef, error: apiKeyError, onSubmit: submitApiKey })), showUpdateModal && updateInfo && (_jsx(UpdateModal, { t: t, width: width, height: height, currentVersion: startupConfig.version, latestVersion: updateInfo.latestVersion })), showMcpModal && !showMcpEditor && (_jsx(McpBrowserModal, { t: t, width: width, height: height, selectedIndex: mcpModalIndex, searchQuery: mcpSearchQuery, rows: mcpRows })), showMcpEditor && (_jsx(McpEditorModal, { t: t, width: width, height: height, draft: mcpEditorDraft, focusedField: mcpEditorField, syncKey: mcpEditorSyncKey, error: mcpEditorError, title: editingMcpId ? "Edit MCP Server" : "Add MCP Server", labelRef: mcpLabelRef, urlRef: mcpUrlRef, headersRef: mcpHeadersRef, commandRef: mcpCommandRef, argsRef: mcpArgsRef, cwdRef: mcpCwdRef, envRef: mcpEnvRef, onSubmit: submitMcpEditor })), showScheduleModal && (_jsx(ScheduleBrowserModal, { t: t, width: width, height: height, selectedIndex: scheduleModalIndex, searchQuery: scheduleSearchQuery, rows: scheduleRows })), showAgentsModal && !showAgentsEditor && (_jsx(SubagentsBrowserModal, { t: t, width: width, height: height, selectedIndex: agentsModalIndex, searchQuery: agentsSearchQuery, rows: agentRows })), showAgentsEditor && (_jsx(SubagentEditorModal, { t: t, width: width, height: height, draft: agentsEditorDraft, focusedField: agentsEditorField, modelIndex: agentsEditorModelIndex, error: agentsEditorError, title: editingSubagent ? `Edit sub-agent: ${formatSubagentName(editingSubagent.name)}` : "Add sub-agent", nameRef: subagentNameRef, instructionRef: subagentInstructionRef, onSubmit: submitSubagentEditor, showRemoveHint: !!editingSubagent }, `subagent-editor-${agentsEditorSyncKey}`)), showModelPicker && (_jsx(ModelPickerModal, { t: t, currentModel: model, selectedIndex: modelPickerIndex, width: width, height: height, searchQuery: modelSearchQuery, filteredModels: filteredModels, reasoningEffortByModel: reasoningEffortByModel, configuredProviders: configuredProviders, disabledProviders: disabledProviders, disabledModels: disabledModels, defaultProvider: defaultProvider, focus: modelPickerFocus, providerChipIndex: providerChipIndex, providersWithKey: providersWithKey, apiKeyPrompt: apiKeyPrompt,
|
|
15
|
+
const { agentRows, agentsEditorDraft, agentsEditorError, agentsEditorField, agentsEditorModelIndex, agentsEditorSyncKey, agentsModalIndex, agentsSearchQuery, apiKeyError, apiKeyInputRef, apiKeyPrompt, oauthProviders, oauthLogin, configuredProviders, connectModalIndex, defaultProvider, disabledModels, disabledProviders, editingMcpId, editingSubagent, filteredModels, height, mcpArgsRef, mcpCommandRef, mcpCwdRef, mcpEditorDraft, mcpEditorError, mcpEditorField, mcpEditorSyncKey, mcpEnvRef, mcpHeadersRef, mcpLabelRef, mcpModalIndex, mcpRows, mcpSearchQuery, mcpUrlRef, model, modelPickerFocus, modelPickerIndex, modelSearchQuery, providerChipIndex, providersWithKey, reasoningEffortByModel, sandboxMode, sandboxSettings, sandboxSettingsEditBuffer, sandboxSettingsEditing, sandboxSettingsFocusIndex, scheduleModalIndex, scheduleRows, scheduleSearchQuery, sessionPickerIndex, sessionPickerList, showAgentsEditor, showAgentsModal, showApiKeyModal, showConnectModal, showMcpEditor, showMcpModal, showModelPicker, showSandboxPicker, showScheduleModal, showSessionPicker, showTelegramPairModal, showTelegramTokenModal, showUpdateModal, showWalletPicker, startupConfig, subagentInstructionRef, subagentNameRef, submitApiKey, submitMcpEditor, submitSubagentEditor, submitTelegramPair, submitTelegramToken, t, telegramPairError, telegramPairInputRef, telegramTokenError, telegramTokenInputRef, updateInfo, walletDisplayInfo, walletFocusIndex, walletSettings, width, } = props;
|
|
16
|
+
return (_jsxs(_Fragment, { children: [showApiKeyModal && (_jsx(ApiKeyModal, { t: t, width: width, height: height, inputRef: apiKeyInputRef, error: apiKeyError, onSubmit: submitApiKey })), showUpdateModal && updateInfo && (_jsx(UpdateModal, { t: t, width: width, height: height, currentVersion: startupConfig.version, latestVersion: updateInfo.latestVersion })), showMcpModal && !showMcpEditor && (_jsx(McpBrowserModal, { t: t, width: width, height: height, selectedIndex: mcpModalIndex, searchQuery: mcpSearchQuery, rows: mcpRows })), showMcpEditor && (_jsx(McpEditorModal, { t: t, width: width, height: height, draft: mcpEditorDraft, focusedField: mcpEditorField, syncKey: mcpEditorSyncKey, error: mcpEditorError, title: editingMcpId ? "Edit MCP Server" : "Add MCP Server", labelRef: mcpLabelRef, urlRef: mcpUrlRef, headersRef: mcpHeadersRef, commandRef: mcpCommandRef, argsRef: mcpArgsRef, cwdRef: mcpCwdRef, envRef: mcpEnvRef, onSubmit: submitMcpEditor })), showScheduleModal && (_jsx(ScheduleBrowserModal, { t: t, width: width, height: height, selectedIndex: scheduleModalIndex, searchQuery: scheduleSearchQuery, rows: scheduleRows })), showAgentsModal && !showAgentsEditor && (_jsx(SubagentsBrowserModal, { t: t, width: width, height: height, selectedIndex: agentsModalIndex, searchQuery: agentsSearchQuery, rows: agentRows })), showAgentsEditor && (_jsx(SubagentEditorModal, { t: t, width: width, height: height, draft: agentsEditorDraft, focusedField: agentsEditorField, modelIndex: agentsEditorModelIndex, error: agentsEditorError, title: editingSubagent ? `Edit sub-agent: ${formatSubagentName(editingSubagent.name)}` : "Add sub-agent", nameRef: subagentNameRef, instructionRef: subagentInstructionRef, onSubmit: submitSubagentEditor, showRemoveHint: !!editingSubagent }, `subagent-editor-${agentsEditorSyncKey}`)), showModelPicker && (_jsx(ModelPickerModal, { t: t, currentModel: model, selectedIndex: modelPickerIndex, width: width, height: height, searchQuery: modelSearchQuery, filteredModels: filteredModels, reasoningEffortByModel: reasoningEffortByModel, configuredProviders: configuredProviders, disabledProviders: disabledProviders, disabledModels: disabledModels, defaultProvider: defaultProvider, focus: modelPickerFocus, providerChipIndex: providerChipIndex, providersWithKey: providersWithKey, apiKeyPrompt: apiKeyPrompt, oauthProviders: oauthProviders, oauthLogin: oauthLogin })), showSessionPicker && (_jsx(SessionPickerModal, { t: t, sessions: sessionPickerList, focusIndex: sessionPickerIndex, width: width, height: height })), showWalletPicker && (_jsx(WalletPickerModal, { t: t, settings: walletSettings, walletInfo: walletDisplayInfo, focusIndex: walletFocusIndex, width: width, height: height })), showSandboxPicker && (_jsx(SandboxPickerModal, { t: t, currentMode: sandboxMode, settings: sandboxSettings, focusIndex: sandboxSettingsFocusIndex, editing: sandboxSettingsEditing, editBuffer: sandboxSettingsEditBuffer, width: width, height: height })), showConnectModal && (_jsx(ConnectModal, { t: t, width: width, height: height, selectedIndex: connectModalIndex, channels: CONNECT_CHANNELS })), showTelegramTokenModal && (_jsx(TelegramTokenModal, { t: t, width: width, height: height, inputRef: telegramTokenInputRef, error: telegramTokenError, onSubmit: submitTelegramToken })), showTelegramPairModal && (_jsx(TelegramPairModal, { t: t, width: width, height: height, inputRef: telegramPairInputRef, error: telegramPairError, onSubmit: () => void submitTelegramPair() }))] }));
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=modals-layer.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/ui/council-harness-event.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure mapping from a council `CouncilStatusData` chunk to the harness
|
|
5
|
+
* `council-speaker` LiveEvent payload.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists. The long research phase (`llm.research`, up to ~10 min /
|
|
8
|
+
* 15 tool steps) runs as a single internal `generateText` — the MAIN stream is
|
|
9
|
+
* quiet the whole time, so `tui_wait_for(idle)` reports a FALSE idle and an
|
|
10
|
+
* agent driving the TUI misjudges the run as hung. The only signal during that
|
|
11
|
+
* window is the 1s `council_status` heartbeat (`state:"tick"`, advancing
|
|
12
|
+
* `elapsedMs`) emitted by `tracedAsync`. Previously the emit site collapsed
|
|
13
|
+
* every non-start state to `status:"done"` and DROPPED `elapsedMs`, so a poller
|
|
14
|
+
* reading `tui_last_event("council-speaker")` saw a frozen "done" event and
|
|
15
|
+
* concluded the speaker had finished — the exact false-stall this fixes.
|
|
16
|
+
*
|
|
17
|
+
* Carrying `state:"tick"` + `elapsedMs` through lets a harness monitor
|
|
18
|
+
* distinguish ALIVE (elapsedMs advancing across polls) from HUNG (elapsedMs
|
|
19
|
+
* frozen past a threshold) with a single `tui_last_event` call.
|
|
20
|
+
*/
|
|
21
|
+
import type { CouncilStatusState } from "../types/index.js";
|
|
22
|
+
/** Harness `council-speaker` status: adds "tick" progress heartbeat to start/done. */
|
|
23
|
+
export type CouncilSpeakerStatus = "start" | "tick" | "done";
|
|
24
|
+
export interface CouncilStatusLike {
|
|
25
|
+
state: CouncilStatusState;
|
|
26
|
+
statusId: string;
|
|
27
|
+
role?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
elapsedMs?: number;
|
|
30
|
+
streamedChars?: number;
|
|
31
|
+
lastDeltaAgeMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CouncilSpeakerEvent {
|
|
34
|
+
t: "event";
|
|
35
|
+
kind: "council-speaker";
|
|
36
|
+
role: string;
|
|
37
|
+
status: CouncilSpeakerStatus;
|
|
38
|
+
correlationId: string;
|
|
39
|
+
elapsedMs?: number;
|
|
40
|
+
/** Chars (text + reasoning deltas) streamed since this phase began. */
|
|
41
|
+
streamedChars?: number;
|
|
42
|
+
/** Age of the most recent stream delta, in ms. */
|
|
43
|
+
lastDeltaAgeMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Map a council status chunk to the `council-speaker` harness event payload.
|
|
47
|
+
* `start`→"start", `tick`→"tick" (progress heartbeat), `done`/`error`→"done".
|
|
48
|
+
* `elapsedMs` is passed through unchanged so a poller can measure progress.
|
|
49
|
+
*
|
|
50
|
+
* `streamedChars` / `lastDeltaAgeMs` ride along when the council emitted them.
|
|
51
|
+
* `elapsedMs` can freeze even on a healthy call — the tick generator only
|
|
52
|
+
* advances when its consumer pulls, and a round awaiting pairs via Promise.all
|
|
53
|
+
* does not pull. These two are pushed from the token stream itself, so they
|
|
54
|
+
* separate SLOW-BUT-ALIVE (chars growing, small delta age) from STUCK (chars
|
|
55
|
+
* static, delta age growing) without depending on generator pumping.
|
|
56
|
+
*/
|
|
57
|
+
export declare function mapCouncilStatusToSpeakerEvent(cs: CouncilStatusLike): CouncilSpeakerEvent;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/ui/council-harness-event.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure mapping from a council `CouncilStatusData` chunk to the harness
|
|
5
|
+
* `council-speaker` LiveEvent payload.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists. The long research phase (`llm.research`, up to ~10 min /
|
|
8
|
+
* 15 tool steps) runs as a single internal `generateText` — the MAIN stream is
|
|
9
|
+
* quiet the whole time, so `tui_wait_for(idle)` reports a FALSE idle and an
|
|
10
|
+
* agent driving the TUI misjudges the run as hung. The only signal during that
|
|
11
|
+
* window is the 1s `council_status` heartbeat (`state:"tick"`, advancing
|
|
12
|
+
* `elapsedMs`) emitted by `tracedAsync`. Previously the emit site collapsed
|
|
13
|
+
* every non-start state to `status:"done"` and DROPPED `elapsedMs`, so a poller
|
|
14
|
+
* reading `tui_last_event("council-speaker")` saw a frozen "done" event and
|
|
15
|
+
* concluded the speaker had finished — the exact false-stall this fixes.
|
|
16
|
+
*
|
|
17
|
+
* Carrying `state:"tick"` + `elapsedMs` through lets a harness monitor
|
|
18
|
+
* distinguish ALIVE (elapsedMs advancing across polls) from HUNG (elapsedMs
|
|
19
|
+
* frozen past a threshold) with a single `tui_last_event` call.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Map a council status chunk to the `council-speaker` harness event payload.
|
|
23
|
+
* `start`→"start", `tick`→"tick" (progress heartbeat), `done`/`error`→"done".
|
|
24
|
+
* `elapsedMs` is passed through unchanged so a poller can measure progress.
|
|
25
|
+
*
|
|
26
|
+
* `streamedChars` / `lastDeltaAgeMs` ride along when the council emitted them.
|
|
27
|
+
* `elapsedMs` can freeze even on a healthy call — the tick generator only
|
|
28
|
+
* advances when its consumer pulls, and a round awaiting pairs via Promise.all
|
|
29
|
+
* does not pull. These two are pushed from the token stream itself, so they
|
|
30
|
+
* separate SLOW-BUT-ALIVE (chars growing, small delta age) from STUCK (chars
|
|
31
|
+
* static, delta age growing) without depending on generator pumping.
|
|
32
|
+
*/
|
|
33
|
+
export function mapCouncilStatusToSpeakerEvent(cs) {
|
|
34
|
+
const status = cs.state === "start" ? "start" : cs.state === "tick" ? "tick" : "done";
|
|
35
|
+
return {
|
|
36
|
+
t: "event",
|
|
37
|
+
kind: "council-speaker",
|
|
38
|
+
role: cs.role ?? cs.label ?? "unknown",
|
|
39
|
+
status,
|
|
40
|
+
correlationId: cs.statusId,
|
|
41
|
+
...(typeof cs.elapsedMs === "number" ? { elapsedMs: cs.elapsedMs } : {}),
|
|
42
|
+
...(typeof cs.streamedChars === "number" ? { streamedChars: cs.streamedChars } : {}),
|
|
43
|
+
...(typeof cs.lastDeltaAgeMs === "number" ? { lastDeltaAgeMs: cs.lastDeltaAgeMs } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=council-harness-event.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/ui/heartbeat-debug.ts
|
|
3
|
+
*
|
|
4
|
+
* Env-gated instrumentation to pinpoint the council rail "frozen elapsed / static
|
|
5
|
+
* spinner" during long phases. Live evidence (harness 60Hz dedup frames identical
|
|
6
|
+
* for 110s + braille spinner not rotating) shows a real in-phase render/paint
|
|
7
|
+
* freeze, but NOT which link starves: (T) the setInterval TIMER not firing, (R)
|
|
8
|
+
* the timer fires but React does not re-render/commit, or (P) React commits but
|
|
9
|
+
* OpenTUI does not repaint.
|
|
10
|
+
*
|
|
11
|
+
* When `MUONROI_HEARTBEAT_DEBUG` is set to a file path, we append one JSONL line
|
|
12
|
+
* per event so a single instrumented council run answers T/R/P:
|
|
13
|
+
* - kind:"timer" → a setInterval callback actually fired (rules in/out T)
|
|
14
|
+
* - kind:"render" → a component's render body ran (rules in/out R)
|
|
15
|
+
* Compare timestamps against the sidechannel council-step timeline: if `timer`
|
|
16
|
+
* lines keep landing every ~100/1000ms through a phase but the harness frame is
|
|
17
|
+
* frozen, the starve is at paint (P); if `timer` lines STOP during the phase, the
|
|
18
|
+
* event loop / timer is starved (T).
|
|
19
|
+
*
|
|
20
|
+
* Unset → a single boolean check per call, zero file I/O, zero behaviour change.
|
|
21
|
+
*/
|
|
22
|
+
export declare function heartbeatDebugEnabled(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Append one instrumentation record. No-op unless MUONROI_HEARTBEAT_DEBUG points
|
|
25
|
+
* at a file. `kind` is "timer" (a setInterval callback fired) or "render" (a
|
|
26
|
+
* component render body executed). `extra` carries component-specific detail
|
|
27
|
+
* (frame index, computed elapsedMs, phase label).
|
|
28
|
+
*/
|
|
29
|
+
export declare function heartbeatDebug(component: string, kind: "timer" | "render", extra?: Record<string, unknown>): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/ui/heartbeat-debug.ts
|
|
3
|
+
*
|
|
4
|
+
* Env-gated instrumentation to pinpoint the council rail "frozen elapsed / static
|
|
5
|
+
* spinner" during long phases. Live evidence (harness 60Hz dedup frames identical
|
|
6
|
+
* for 110s + braille spinner not rotating) shows a real in-phase render/paint
|
|
7
|
+
* freeze, but NOT which link starves: (T) the setInterval TIMER not firing, (R)
|
|
8
|
+
* the timer fires but React does not re-render/commit, or (P) React commits but
|
|
9
|
+
* OpenTUI does not repaint.
|
|
10
|
+
*
|
|
11
|
+
* When `MUONROI_HEARTBEAT_DEBUG` is set to a file path, we append one JSONL line
|
|
12
|
+
* per event so a single instrumented council run answers T/R/P:
|
|
13
|
+
* - kind:"timer" → a setInterval callback actually fired (rules in/out T)
|
|
14
|
+
* - kind:"render" → a component's render body ran (rules in/out R)
|
|
15
|
+
* Compare timestamps against the sidechannel council-step timeline: if `timer`
|
|
16
|
+
* lines keep landing every ~100/1000ms through a phase but the harness frame is
|
|
17
|
+
* frozen, the starve is at paint (P); if `timer` lines STOP during the phase, the
|
|
18
|
+
* event loop / timer is starved (T).
|
|
19
|
+
*
|
|
20
|
+
* Unset → a single boolean check per call, zero file I/O, zero behaviour change.
|
|
21
|
+
*/
|
|
22
|
+
import { appendFileSync } from "node:fs";
|
|
23
|
+
const SINK = process.env.MUONROI_HEARTBEAT_DEBUG?.trim() || null;
|
|
24
|
+
export function heartbeatDebugEnabled() {
|
|
25
|
+
return SINK !== null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Append one instrumentation record. No-op unless MUONROI_HEARTBEAT_DEBUG points
|
|
29
|
+
* at a file. `kind` is "timer" (a setInterval callback fired) or "render" (a
|
|
30
|
+
* component render body executed). `extra` carries component-specific detail
|
|
31
|
+
* (frame index, computed elapsedMs, phase label).
|
|
32
|
+
*/
|
|
33
|
+
export function heartbeatDebug(component, kind, extra) {
|
|
34
|
+
if (SINK === null)
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
const rec = { ts: Date.now(), component, kind, ...(extra ?? {}) };
|
|
38
|
+
appendFileSync(SINK, `${JSON.stringify(rec)}\n`);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
// Instrumentation must never break the UI; surface once at debug level.
|
|
42
|
+
console.error(`[heartbeat-debug] append failed: ${err?.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=heartbeat-debug.js.map
|
|
@@ -10,6 +10,6 @@ export function ApiKeyModal({ t, width, height, inputRef, error, onSubmit, }) {
|
|
|
10
10
|
const panelWidth = Math.min(68, width - 6);
|
|
11
11
|
const panelHeight = 13;
|
|
12
12
|
const top = bottomAlignedModalTop(height, panelHeight);
|
|
13
|
-
return (_jsx(Semantic, { id: "api-key-modal", role: "dialog", name: "API Key", isModal: true, children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: top, backgroundColor: overlayBg, children: _jsxs("box", { width: panelWidth, height: panelHeight, backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, flexDirection: "column", children: [_jsxs("box", { flexShrink: 0, flexDirection: "row", justifyContent: "space-between", paddingLeft: 2, paddingRight: 2, children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: "Add API key" }) }), _jsx("text", { fg: t.textMuted, children: "esc" })] }), _jsx("box", { paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.text, children: "Paste
|
|
13
|
+
return (_jsx(Semantic, { id: "api-key-modal", role: "dialog", name: "API Key", isModal: true, children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: top, backgroundColor: overlayBg, children: _jsxs("box", { width: panelWidth, height: panelHeight, backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, flexDirection: "column", children: [_jsxs("box", { flexShrink: 0, flexDirection: "row", justifyContent: "space-between", paddingLeft: 2, paddingRight: 2, children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: "Add API key" }) }), _jsx("text", { fg: t.textMuted, children: "esc" })] }), _jsx("box", { paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.text, children: "Paste a provider API key, or press Esc and use /providers to sign in (OAuth)." }) }), _jsx("box", { paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("box", { backgroundColor: t.backgroundElement, paddingLeft: 1, paddingRight: 1, width: "100%", children: _jsx(Semantic, { id: "api-key-input", role: "textbox", children: _jsx("textarea", { ref: inputRef, focused: true, placeholder: "sk-...", textColor: t.text, backgroundColor: t.backgroundElement, placeholderColor: t.textMuted, minHeight: 1, maxHeight: 3, wrapMode: "word", keyBindings: TEXTAREA_KEYBINDINGS, onSubmit: onSubmit }) }) }) }), _jsx("box", { flexGrow: 1, minHeight: 0 }), _jsx("box", { paddingLeft: 2, paddingRight: 2, paddingTop: 2, paddingBottom: 1, children: error ? (_jsx("text", { fg: t.diffRemovedFg, children: error })) : (_jsxs("text", { children: [_jsx("span", { style: { fg: t.primary }, children: "enter " }), _jsx("span", { style: { fg: t.textMuted }, children: "save key · " }), _jsx("span", { style: { fg: t.primary }, children: "esc " }), _jsx("span", { style: { fg: t.textMuted }, children: "hide" })] })) })] }) }) }));
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=api-key-modal.js.map
|
|
@@ -14,24 +14,12 @@ export interface ApiKeyPromptState {
|
|
|
14
14
|
/** When true the field shows the raw key instead of mask dots (Ctrl+R). */
|
|
15
15
|
reveal?: boolean;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
error: string | null;
|
|
21
|
-
loading: boolean;
|
|
22
|
-
} | {
|
|
23
|
-
phase: "picker";
|
|
24
|
-
session: string;
|
|
25
|
-
items: Array<{
|
|
26
|
-
provider: ProviderId;
|
|
27
|
-
key: string;
|
|
28
|
-
}>;
|
|
29
|
-
selected: Set<ProviderId>;
|
|
30
|
-
focusIndex: number;
|
|
31
|
-
loading: boolean;
|
|
17
|
+
/** In-progress OAuth login overlay state (browser-based subscription sign-in). */
|
|
18
|
+
export interface OAuthLoginState {
|
|
19
|
+
provider: ProviderId;
|
|
32
20
|
error: string | null;
|
|
33
|
-
}
|
|
34
|
-
export declare function ModelPickerModal({ t, width, height, configuredProviders, disabledProviders, defaultProvider, providerChipIndex, providersWithKey, apiKeyPrompt,
|
|
21
|
+
}
|
|
22
|
+
export declare function ModelPickerModal({ t, width, height, configuredProviders, disabledProviders, defaultProvider, providerChipIndex, providersWithKey, apiKeyPrompt, oauthProviders, oauthLogin, }: {
|
|
35
23
|
t: Theme;
|
|
36
24
|
currentModel?: string;
|
|
37
25
|
selectedIndex?: number;
|
|
@@ -48,5 +36,7 @@ export declare function ModelPickerModal({ t, width, height, configuredProviders
|
|
|
48
36
|
providerChipIndex: number;
|
|
49
37
|
providersWithKey?: ReadonlySet<ProviderId>;
|
|
50
38
|
apiKeyPrompt?: ApiKeyPromptState | null;
|
|
51
|
-
|
|
39
|
+
/** Providers that support OAuth subscription login (openai, xai). */
|
|
40
|
+
oauthProviders?: ReadonlySet<ProviderId>;
|
|
41
|
+
oauthLogin?: OAuthLoginState | null;
|
|
52
42
|
}): import("react").ReactNode;
|
|
@@ -21,9 +21,10 @@ export function groupModelsByTier(models) {
|
|
|
21
21
|
const order = ["premium", "balanced", "fast", "other"];
|
|
22
22
|
return order.filter((t) => map.has(t)).map((t) => ({ tier: t, items: map.get(t) }));
|
|
23
23
|
}
|
|
24
|
-
export function ModelPickerModal({ t, width, height, configuredProviders, disabledProviders, defaultProvider, providerChipIndex, providersWithKey, apiKeyPrompt,
|
|
24
|
+
export function ModelPickerModal({ t, width, height, configuredProviders, disabledProviders, defaultProvider, providerChipIndex, providersWithKey, apiKeyPrompt, oauthProviders, oauthLogin, }) {
|
|
25
25
|
const disabledSet = new Set(disabledProviders);
|
|
26
26
|
const keyedSet = providersWithKey ?? new Set();
|
|
27
|
+
const oauthSet = oauthProviders ?? new Set();
|
|
27
28
|
const panelWidth = Math.min(64, width - 6);
|
|
28
29
|
const rowCount = Math.max(configuredProviders.length, 1);
|
|
29
30
|
const contentHeight = rowCount + 7;
|
|
@@ -32,13 +33,9 @@ export function ModelPickerModal({ t, width, height, configuredProviders, disabl
|
|
|
32
33
|
const top = bottomAlignedModalTop(height, panelHeight);
|
|
33
34
|
const overlayBg = "#000000cc";
|
|
34
35
|
// Sub-modal: API key prompt overlay (rendered on top of the picker).
|
|
35
|
-
const subModal = apiKeyPrompt ? (_jsx(Semantic, { id: "provider-key-prompt", role: "dialog", isModal: true, name: `Set ${apiKeyPrompt.provider} key`, children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: Math.floor(height / 3), backgroundColor: "#000000dd", children: _jsxs("box", { width: Math.min(60, width - 8), backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, paddingLeft: 2, paddingRight: 2, flexDirection: "column", children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: `Set API key — ${apiKeyPrompt.provider}` }) }), _jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "Key is
|
|
36
|
-
// Sub-modal:
|
|
37
|
-
const
|
|
38
|
-
const focused = i === bwSync.focusIndex;
|
|
39
|
-
const checked = bwSync.selected.has(item.provider);
|
|
40
|
-
return (_jsx("box", { paddingLeft: 1, paddingRight: 1, backgroundColor: focused ? t.selectedBg : undefined, children: _jsx("text", { fg: focused ? t.accent : t.text, children: `${focused ? "›" : " "} [${checked ? "x" : " "}] ${item.provider}` }) }, item.provider));
|
|
41
|
-
}) }), bwSync.error ? (_jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.initFormError, children: bwSync.error }) })) : null, _jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: bwSync.loading ? "importing..." : "↑↓ nav Space toggle Enter import Esc cancel" }) })] })) }) }) })) : null;
|
|
36
|
+
const subModal = apiKeyPrompt ? (_jsx(Semantic, { id: "provider-key-prompt", role: "dialog", isModal: true, name: `Set ${apiKeyPrompt.provider} key`, children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: Math.floor(height / 3), backgroundColor: "#000000dd", children: _jsxs("box", { width: Math.min(60, width - 8), backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, paddingLeft: 2, paddingRight: 2, flexDirection: "column", children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: `Set API key — ${apiKeyPrompt.provider}` }) }), _jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "Key is stored as an environment variable (~/.muonroi-cli/.env, mirrored to your OS env)." }) }), _jsx(Semantic, { id: "provider-key-input", role: "textbox", name: `${apiKeyPrompt.provider} API key`, focus: true, value: apiKeyPrompt.value, children: _jsxs("box", { paddingTop: 1, backgroundColor: t.backgroundElement, paddingLeft: 1, paddingRight: 1, flexDirection: "row", justifyContent: "space-between", children: [apiKeyPrompt.value.length > 0 ? (_jsx("text", { fg: t.text, children: `${apiKeyPrompt.reveal ? apiKeyPrompt.value.slice(0, 40) : "•".repeat(Math.min(apiKeyPrompt.value.length, 40))}▏` })) : (_jsx("text", { fg: t.textDim, children: "▏(type or paste key)" })), apiKeyPrompt.value.length > 0 ? (_jsx("text", { fg: t.textMuted, children: `${apiKeyPrompt.reveal ? "shown" : "hidden"} · ${apiKeyPrompt.value.length} chars` })) : null] }) }), apiKeyPrompt.error ? (_jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.initFormError, children: apiKeyPrompt.error }) })) : null, _jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "Enter save · Esc cancel · Ctrl+R reveal · paste works" }) })] }) }) })) : null;
|
|
37
|
+
// Sub-modal: OAuth subscription login (browser-based) for openai / xai.
|
|
38
|
+
const oauthModal = oauthLogin ? (_jsx(Semantic, { id: "provider-oauth-login", role: "dialog", isModal: true, name: `Sign in to ${oauthLogin.provider}`, children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: Math.floor(height / 3), backgroundColor: "#000000dd", children: _jsxs("box", { width: Math.min(60, width - 8), backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, paddingLeft: 2, paddingRight: 2, flexDirection: "column", children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: `Sign in to ${oauthLogin.provider} (OAuth)` }) }), _jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "A browser window is opening. Complete the sign-in there; this returns automatically." }) }), oauthLogin.error ? (_jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.initFormError, children: oauthLogin.error }) })) : (_jsx("box", { paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "Waiting for authorization… Esc cancel" }) }))] }) }) })) : null;
|
|
42
39
|
return (_jsxs(_Fragment, { children: [_jsx(Semantic, { id: "model-picker", role: "dialog", isModal: true, name: "Providers", children: _jsx("box", { position: "absolute", left: 0, top: 0, width: width, height: height, alignItems: "center", paddingTop: top, backgroundColor: overlayBg, children: _jsxs("box", { width: panelWidth, height: panelHeight, backgroundColor: t.backgroundPanel, paddingTop: 1, paddingBottom: 1, flexDirection: "column", children: [_jsxs("box", { flexShrink: 0, flexDirection: "row", justifyContent: "space-between", paddingLeft: 2, paddingRight: 2, children: [_jsx("text", { fg: t.primary, children: _jsx("b", { children: "Providers" }) }), _jsx("text", { fg: t.textMuted, children: "esc" })] }), _jsx("box", { flexShrink: 0, paddingLeft: 2, paddingRight: 2, paddingTop: 1, flexDirection: "column", children: configuredProviders.length === 0 ? (_jsx("text", { fg: t.textMuted, children: "No providers configured" })) : (configuredProviders.map((p, i) => {
|
|
43
40
|
const hasKey = keyedSet.has(p);
|
|
44
41
|
const enabled = hasKey && !disabledSet.has(p);
|
|
@@ -48,8 +45,9 @@ export function ModelPickerModal({ t, width, height, configuredProviders, disabl
|
|
|
48
45
|
const star = isDefault ? "★" : " ";
|
|
49
46
|
const fg = focused ? t.accent : enabled ? t.text : t.textMuted;
|
|
50
47
|
const starFg = isDefault ? t.primary : t.textDim;
|
|
51
|
-
const
|
|
48
|
+
const canOAuth = oauthSet.has(p);
|
|
49
|
+
const suffix = !hasKey ? (canOAuth ? " (Enter sign in · K key)" : " (no key — press K)") : "";
|
|
52
50
|
return (_jsx(Semantic, { id: `provider-chip-${p}`, role: "button", selected: enabled ? true : undefined, disabled: !hasKey ? true : undefined, name: p, children: _jsxs("box", { paddingLeft: 1, paddingRight: 1, backgroundColor: focused ? t.selectedBg : undefined, width: "100%", flexDirection: "row", children: [_jsx("text", { fg: fg, children: `${focused ? "›" : " "} ${mark} ` }), _jsx("text", { fg: starFg, children: `${star} ` }), _jsx("text", { fg: fg, children: p }), suffix ? _jsx("text", { fg: t.textMuted, children: suffix }) : null] }) }, p));
|
|
53
|
-
})) }), _jsx("box", { flexGrow: 1, minHeight: 0 }), _jsx("box", { flexShrink: 0, paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "↑↓ nav Space toggle D default K set key
|
|
51
|
+
})) }), _jsx("box", { flexGrow: 1, minHeight: 0 }), _jsx("box", { flexShrink: 0, paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "↑↓ nav Space toggle D default O sign in K set key Esc close" }) })] }) }) }), subModal, oauthModal] }));
|
|
54
52
|
}
|
|
55
53
|
//# sourceMappingURL=model-picker-modal.js.map
|
package/dist/src/ui/slash/ee.js
CHANGED
|
@@ -154,6 +154,8 @@ const HELP = [
|
|
|
154
154
|
"**Experience Engine (/ee)**",
|
|
155
155
|
"",
|
|
156
156
|
"Usage:",
|
|
157
|
+
" /ee config — Show EE connection settings",
|
|
158
|
+
" /ee config <url> [token] — Connect to an EE server (writes ~/.experience/config.json)",
|
|
157
159
|
" /ee stats [7d|30d|all] — Knowledge base statistics",
|
|
158
160
|
" /ee timeline <topic> — Chronological principle evolution",
|
|
159
161
|
" /ee graph <id> — Principle relationship graph",
|
|
@@ -167,10 +169,89 @@ const HELP = [
|
|
|
167
169
|
" /ee mode — Show detected client mode (thin/fat/disabled)",
|
|
168
170
|
].join("\n");
|
|
169
171
|
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
172
|
+
/** Never echo a token back in full — the transcript is persisted to SQLite. */
|
|
173
|
+
function maskToken(token) {
|
|
174
|
+
if (!token)
|
|
175
|
+
return "(none)";
|
|
176
|
+
return token.length <= 4 ? "set (…)" : `set (…${token.slice(-4)})`;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Show or set the EE connection. Until this existed, EE was configurable only
|
|
180
|
+
* by the one-shot first-run wizard — and index.ts marks eeSetupPrompted even
|
|
181
|
+
* when the user SKIPS it, so skipping once left hand-editing
|
|
182
|
+
* ~/.experience/config.json as the only way back in.
|
|
183
|
+
*/
|
|
184
|
+
async function handleConfig(args) {
|
|
185
|
+
const { getCachedServerBaseUrl, getCachedAuthToken, getExperienceConfigPath, probeEEHealth, refreshAuthToken } = await import("../../ee/auth.js");
|
|
186
|
+
const url = args[0];
|
|
187
|
+
if (!url) {
|
|
188
|
+
const baseUrl = getCachedServerBaseUrl();
|
|
189
|
+
const lines = [
|
|
190
|
+
"**EE Config**",
|
|
191
|
+
"",
|
|
192
|
+
` serverBaseUrl: ${baseUrl ?? "(not configured)"}`,
|
|
193
|
+
` serverAuthToken: ${maskToken(getCachedAuthToken())}`,
|
|
194
|
+
` config file: ${getExperienceConfigPath()}`,
|
|
195
|
+
];
|
|
196
|
+
if (process.env.MUONROI_EE_BASE_URL) {
|
|
197
|
+
lines.push("", ` ⚠ MUONROI_EE_BASE_URL=${process.env.MUONROI_EE_BASE_URL} overrides the config file.`);
|
|
198
|
+
}
|
|
199
|
+
if (baseUrl) {
|
|
200
|
+
const health = await probeEEHealth(baseUrl, getCachedAuthToken() ?? undefined);
|
|
201
|
+
lines.push("", health.ok ? ` ✓ reachable (${health.detail})` : ` ⚠ not reachable — ${health.detail}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
lines.push("", "Connect one with: `/ee config <url> [token]`");
|
|
205
|
+
}
|
|
206
|
+
return lines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
let normalized;
|
|
209
|
+
try {
|
|
210
|
+
normalized = new URL(url).toString().replace(/\/$/, "");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return `"${url}" is not a valid URL. Example: \`/ee config https://experience.example.com <token>\``;
|
|
214
|
+
}
|
|
215
|
+
const token = args[1];
|
|
216
|
+
try {
|
|
217
|
+
const { writeExperienceConfig } = await import("../../ee/auth.js");
|
|
218
|
+
await writeExperienceConfig({
|
|
219
|
+
serverBaseUrl: normalized,
|
|
220
|
+
...(token ? { serverAuthToken: token } : {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const message = err?.message ?? String(err);
|
|
225
|
+
console.error(`[slash/ee] writing ${getExperienceConfigPath()} failed: ${message}`);
|
|
226
|
+
return `Could not write ${getExperienceConfigPath()}: ${message}`;
|
|
227
|
+
}
|
|
228
|
+
// Re-read so THIS session picks the change up; without it the new server is
|
|
229
|
+
// only live after a restart.
|
|
230
|
+
await refreshAuthToken();
|
|
231
|
+
const { detectEEClientMode, describeMode } = await import("../../ee/client-mode.js");
|
|
232
|
+
const info = await detectEEClientMode({ force: true });
|
|
233
|
+
const health = await probeEEHealth(normalized, token || (getCachedAuthToken() ?? undefined));
|
|
234
|
+
return [
|
|
235
|
+
"**EE Config saved**",
|
|
236
|
+
"",
|
|
237
|
+
` serverBaseUrl: ${normalized}`,
|
|
238
|
+
` serverAuthToken: ${maskToken(token ?? getCachedAuthToken())}`,
|
|
239
|
+
` config file: ${getExperienceConfigPath()}`,
|
|
240
|
+
` client mode: ${info.mode} — ${describeMode(info)}`,
|
|
241
|
+
"",
|
|
242
|
+
health.ok
|
|
243
|
+
? ` ✓ reachable (${health.detail}) — recall is live for this session.`
|
|
244
|
+
: ` ⚠ saved, but not reachable — ${health.detail}. EE calls degrade gracefully until it responds.`,
|
|
245
|
+
].join("\n");
|
|
246
|
+
}
|
|
170
247
|
export const handleEESlash = async (args, _ctx) => {
|
|
171
248
|
const sub = args[0]?.toLowerCase();
|
|
172
249
|
if (!sub)
|
|
173
250
|
return HELP;
|
|
251
|
+
// `config` must not construct the EE client: it is the command you reach for
|
|
252
|
+
// precisely when there is no server configured yet.
|
|
253
|
+
if (sub === "config" || sub === "setup")
|
|
254
|
+
return await handleConfig(args.slice(1));
|
|
174
255
|
const client = getDefaultEEClient();
|
|
175
256
|
switch (sub) {
|
|
176
257
|
case "stats": {
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
export const SLASH_MENU_ITEMS = [
|
|
29
29
|
// ── Primary surface ────────────────────────────────────────────────────
|
|
30
30
|
{ id: "exit", label: "exit", description: "Quit the CLI" },
|
|
31
|
-
{
|
|
31
|
+
{
|
|
32
|
+
id: "providers",
|
|
33
|
+
label: "providers",
|
|
34
|
+
description: "Providers, keys and sign-in (K adds a key, Enter signs in with OAuth)",
|
|
35
|
+
},
|
|
32
36
|
{ id: "compact", label: "compact", description: "Compact conversation context" },
|
|
33
37
|
{ id: "clear", label: "clear", description: "Clear conversation and start fresh" },
|
|
34
38
|
{ id: "ideal", label: "ideal", description: "Product Ideal Loop — from idea to shipped product, autonomously" },
|
|
@@ -48,7 +52,12 @@ export const SLASH_MENU_ITEMS = [
|
|
|
48
52
|
{ id: "verify", label: "verify", description: "Run local verification", hidden: true },
|
|
49
53
|
{ id: "update", label: "update", description: "Update muonroi-cli to the latest version", hidden: true },
|
|
50
54
|
{ id: "cost", label: "cost", description: "Show session cost breakdown", hidden: true },
|
|
51
|
-
{
|
|
55
|
+
{
|
|
56
|
+
id: "ee",
|
|
57
|
+
label: "ee",
|
|
58
|
+
description: "Experience Engine — connect a brain (/ee config <url> [token]), stats, search",
|
|
59
|
+
hidden: true,
|
|
60
|
+
},
|
|
52
61
|
{ id: "route", label: "route", description: "Show current model routing info", hidden: true },
|
|
53
62
|
{ id: "plan", label: "plan", description: "Show active GSD plan", hidden: true },
|
|
54
63
|
{ id: "execute", label: "execute", description: "Execute active GSD plan", hidden: true },
|