muonroi-cli 1.6.4 → 1.6.6
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 +1 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -2
- package/dist/packages/agent-harness-core/src/protocol.d.ts +8 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/__tests__/directives.test.js +37 -0
- package/dist/src/gsd/directives.d.ts +18 -0
- package/dist/src/gsd/directives.js +23 -2
- package/dist/src/orchestrator/message-processor.d.ts +8 -0
- package/dist/src/orchestrator/message-processor.js +56 -7
- package/dist/src/orchestrator/orchestrator.d.ts +10 -0
- package/dist/src/orchestrator/orchestrator.js +11 -0
- package/dist/src/orchestrator/stall-rescue.d.ts +1 -0
- package/dist/src/orchestrator/stall-rescue.js +20 -1
- package/dist/src/orchestrator/stall-rescue.test.js +30 -1
- package/dist/src/orchestrator/steer-inbox.d.ts +32 -0
- package/dist/src/orchestrator/steer-inbox.js +20 -0
- package/dist/src/orchestrator/steer-inbox.test.d.ts +1 -0
- package/dist/src/orchestrator/steer-inbox.test.js +33 -0
- package/dist/src/orchestrator/tool-loop-askcard.d.ts +59 -0
- package/dist/src/orchestrator/tool-loop-askcard.js +86 -0
- package/dist/src/orchestrator/tool-loop-askcard.test.d.ts +1 -0
- package/dist/src/orchestrator/tool-loop-askcard.test.js +71 -0
- package/dist/src/pil/layer4-gsd.js +5 -1
- package/dist/src/ui/app.js +142 -59
- package/dist/src/ui/hooks/use-session-picker.d.ts +14 -0
- package/dist/src/ui/hooks/use-session-picker.js +20 -0
- package/dist/src/ui/modals/session-picker-modal.d.ts +14 -0
- package/dist/src/ui/modals/session-picker-modal.js +39 -0
- package/dist/src/ui/utils/relaunch.d.ts +41 -0
- package/dist/src/ui/utils/relaunch.js +71 -0
- package/dist/src/ui/utils/relaunch.test.d.ts +1 -0
- package/dist/src/ui/utils/relaunch.test.js +83 -0
- package/dist/src/utils/settings.d.ts +10 -0
- package/dist/src/utils/settings.js +12 -0
- package/dist/src/utils/settings.test.js +21 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/orchestrator/tool-loop-askcard.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure helper that computes the tool-loop-cap askcard tier (label set + default
|
|
5
|
+
* action) from the current step number and the resolved natural ceiling for
|
|
6
|
+
* the (taskType, size) matrix.
|
|
7
|
+
*
|
|
8
|
+
* Four tiers (open intervals — boundaries belong to the higher tier):
|
|
9
|
+
* - early : step < 0.5 × ceiling — a transient fixation. Default Continue.
|
|
10
|
+
* - normal : 0.5× ≤ step ≤ 2× ceiling — used cheap budget; Default Stop.
|
|
11
|
+
* - overBudget : 2× < step ≤ 5× ceiling — Continue still available but the
|
|
12
|
+
* label carries the overage multiplier so the cost of
|
|
13
|
+
* continuing is visible at decision time. Default Stop.
|
|
14
|
+
* - extreme : step > 5× ceiling — Stop is moved FIRST in the option
|
|
15
|
+
* array (Enter = Stop) and Continue is labelled "expensive".
|
|
16
|
+
* Default Stop (now at index 0).
|
|
17
|
+
*
|
|
18
|
+
* Live miss this tier set fixes (session 1f29e238, step 77/6 = 12.8×): extreme
|
|
19
|
+
* tier put Stop first with a warning — good. But the storyflow_ui session
|
|
20
|
+
* 22661c8de9f2 ran step 29/12 = 2.4× — the OLD code had no middle warning, so
|
|
21
|
+
* the askcard showed a plain "Continue (let agent try)" with no signal that
|
|
22
|
+
* continuing costs more. User chose Continue, the model stalled 4 tool-calls
|
|
23
|
+
* later, and forced-finalize had to rescue a degraded answer.
|
|
24
|
+
*
|
|
25
|
+
* Pure — no React, no DOM, no side effects. Unit-testable in isolation.
|
|
26
|
+
*/
|
|
27
|
+
const NORMAL_LABELS = ["Continue (let agent try)", "Stop and answer"];
|
|
28
|
+
const NORMAL_VALUES = ["continue", "stop"];
|
|
29
|
+
/**
|
|
30
|
+
* Decide the askcard layout for a tool-loop-cap pattern hit. Pure.
|
|
31
|
+
*/
|
|
32
|
+
export function planLoopCapAskcard(opts) {
|
|
33
|
+
const { stepNumber, naturalCeiling } = opts;
|
|
34
|
+
// No ceiling → cannot compute multipliers. Fall back to a static threshold:
|
|
35
|
+
// step ≤ 15 looks "early" enough to default Continue, else default Stop.
|
|
36
|
+
if (!naturalCeiling || naturalCeiling <= 0) {
|
|
37
|
+
const tier = stepNumber > 0 && stepNumber <= 15 ? "early" : "normal";
|
|
38
|
+
return {
|
|
39
|
+
tier,
|
|
40
|
+
defaultIndex: tier === "early" ? 0 : 1,
|
|
41
|
+
optionLabels: NORMAL_LABELS,
|
|
42
|
+
optionValues: NORMAL_VALUES,
|
|
43
|
+
overageMultiplier: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const ratio = stepNumber / naturalCeiling;
|
|
47
|
+
const multiplier = ratio.toFixed(1);
|
|
48
|
+
if (ratio > 5) {
|
|
49
|
+
return {
|
|
50
|
+
tier: "extreme",
|
|
51
|
+
defaultIndex: 0,
|
|
52
|
+
optionLabels: ["Stop and answer (recommended)", `Continue anyway (⚠ ${multiplier}× over budget — expensive)`],
|
|
53
|
+
optionValues: ["stop", "continue"],
|
|
54
|
+
overageMultiplier: multiplier,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (ratio > 2) {
|
|
58
|
+
return {
|
|
59
|
+
tier: "overBudget",
|
|
60
|
+
defaultIndex: 1,
|
|
61
|
+
optionLabels: [
|
|
62
|
+
`Continue (⚠ ${multiplier}× past natural budget — quality may degrade)`,
|
|
63
|
+
"Stop and answer (recommended)",
|
|
64
|
+
],
|
|
65
|
+
optionValues: NORMAL_VALUES,
|
|
66
|
+
overageMultiplier: multiplier,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (ratio < 0.5) {
|
|
70
|
+
return {
|
|
71
|
+
tier: "early",
|
|
72
|
+
defaultIndex: 0,
|
|
73
|
+
optionLabels: NORMAL_LABELS,
|
|
74
|
+
optionValues: NORMAL_VALUES,
|
|
75
|
+
overageMultiplier: null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
tier: "normal",
|
|
80
|
+
defaultIndex: 1,
|
|
81
|
+
optionLabels: NORMAL_LABELS,
|
|
82
|
+
optionValues: NORMAL_VALUES,
|
|
83
|
+
overageMultiplier: null,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=tool-loop-askcard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { planLoopCapAskcard } from "./tool-loop-askcard.js";
|
|
3
|
+
describe("planLoopCapAskcard", () => {
|
|
4
|
+
it("early tier (< 0.5× ceiling): default Continue, no warning", () => {
|
|
5
|
+
const r = planLoopCapAskcard({ stepNumber: 5, naturalCeiling: 12 });
|
|
6
|
+
expect(r.tier).toBe("early");
|
|
7
|
+
expect(r.defaultIndex).toBe(0);
|
|
8
|
+
expect(r.optionLabels[0]).toMatch(/Continue/);
|
|
9
|
+
expect(r.optionValues[0]).toBe("continue");
|
|
10
|
+
expect(r.overageMultiplier).toBeNull();
|
|
11
|
+
// no warning emoji on the Continue label
|
|
12
|
+
expect(r.optionLabels[0]).not.toMatch(/⚠/);
|
|
13
|
+
});
|
|
14
|
+
it("normal tier (0.5×–2× ceiling): default Stop, no warning, Continue first", () => {
|
|
15
|
+
const r = planLoopCapAskcard({ stepNumber: 18, naturalCeiling: 12 });
|
|
16
|
+
expect(r.tier).toBe("normal");
|
|
17
|
+
expect(r.defaultIndex).toBe(1);
|
|
18
|
+
expect(r.optionLabels[0]).toBe("Continue (let agent try)");
|
|
19
|
+
expect(r.optionLabels[1]).toBe("Stop and answer");
|
|
20
|
+
expect(r.overageMultiplier).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it("overBudget tier (2×–5× ceiling): Continue carries the overage multiplier, default Stop", () => {
|
|
23
|
+
// The storyflow_ui case: step 29 / ceiling 12 = 2.4×
|
|
24
|
+
const r = planLoopCapAskcard({ stepNumber: 29, naturalCeiling: 12 });
|
|
25
|
+
expect(r.tier).toBe("overBudget");
|
|
26
|
+
expect(r.defaultIndex).toBe(1);
|
|
27
|
+
expect(r.optionLabels[0]).toMatch(/⚠ 2\.4× past natural budget/);
|
|
28
|
+
expect(r.optionLabels[1]).toMatch(/Stop and answer \(recommended\)/);
|
|
29
|
+
expect(r.overageMultiplier).toBe("2.4");
|
|
30
|
+
// order preserved: Continue at 0, Stop at 1
|
|
31
|
+
expect(r.optionValues).toEqual(["continue", "stop"]);
|
|
32
|
+
});
|
|
33
|
+
it("extreme tier (> 5× ceiling): Stop FIRST in the array, Continue labelled expensive", () => {
|
|
34
|
+
// session 1f29e238 — step 77 / ceiling 6 = 12.8×
|
|
35
|
+
const r = planLoopCapAskcard({ stepNumber: 77, naturalCeiling: 6 });
|
|
36
|
+
expect(r.tier).toBe("extreme");
|
|
37
|
+
expect(r.defaultIndex).toBe(0);
|
|
38
|
+
expect(r.optionLabels[0]).toMatch(/Stop and answer \(recommended\)/);
|
|
39
|
+
expect(r.optionLabels[1]).toMatch(/⚠ 12\.8× over budget — expensive/);
|
|
40
|
+
expect(r.optionValues).toEqual(["stop", "continue"]); // ORDER REVERSED at extreme
|
|
41
|
+
expect(r.overageMultiplier).toBe("12.8");
|
|
42
|
+
});
|
|
43
|
+
it("tier boundaries are open-on-the-lower-side (ratio==2 → normal; ratio==5 → overBudget; ratio==0.5 → normal)", () => {
|
|
44
|
+
// ratio === 2.0 exactly → still normal (the > 2 gate excludes 2.0)
|
|
45
|
+
expect(planLoopCapAskcard({ stepNumber: 24, naturalCeiling: 12 }).tier).toBe("normal");
|
|
46
|
+
// ratio === 5.0 exactly → still overBudget (the > 5 gate excludes 5.0)
|
|
47
|
+
expect(planLoopCapAskcard({ stepNumber: 60, naturalCeiling: 12 }).tier).toBe("overBudget");
|
|
48
|
+
// ratio === 0.5 exactly → normal (the < 0.5 gate excludes 0.5)
|
|
49
|
+
expect(planLoopCapAskcard({ stepNumber: 6, naturalCeiling: 12 }).tier).toBe("normal");
|
|
50
|
+
});
|
|
51
|
+
it("falls back to step-threshold heuristic when naturalCeiling is missing", () => {
|
|
52
|
+
const early = planLoopCapAskcard({ stepNumber: 8 });
|
|
53
|
+
expect(early.tier).toBe("early");
|
|
54
|
+
expect(early.defaultIndex).toBe(0);
|
|
55
|
+
const normal = planLoopCapAskcard({ stepNumber: 22 });
|
|
56
|
+
expect(normal.tier).toBe("normal");
|
|
57
|
+
expect(normal.defaultIndex).toBe(1);
|
|
58
|
+
// boundary: step === 15 → still early
|
|
59
|
+
expect(planLoopCapAskcard({ stepNumber: 15 }).tier).toBe("early");
|
|
60
|
+
// step === 16 → normal
|
|
61
|
+
expect(planLoopCapAskcard({ stepNumber: 16 }).tier).toBe("normal");
|
|
62
|
+
// step === 0 → normal (no early credit for nothing)
|
|
63
|
+
expect(planLoopCapAskcard({ stepNumber: 0 }).tier).toBe("normal");
|
|
64
|
+
});
|
|
65
|
+
it("treats naturalCeiling=0 the same as undefined (no multiplier possible)", () => {
|
|
66
|
+
const r = planLoopCapAskcard({ stepNumber: 30, naturalCeiling: 0 });
|
|
67
|
+
expect(r.overageMultiplier).toBeNull();
|
|
68
|
+
expect(r.tier).toBe("normal");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=tool-loop-askcard.test.js.map
|
|
@@ -102,7 +102,11 @@ export async function layer4Gsd(ctx) {
|
|
|
102
102
|
(ctx.taskType === "general" && ctx.intentKind === "task") ||
|
|
103
103
|
(isQuestionLike(ctx.raw) && !isImplementationIntent(ctx.raw));
|
|
104
104
|
const ecosystem = mentionsEcosystemScope(ctx.raw);
|
|
105
|
-
|
|
105
|
+
// Heuristic: VN diacritics → user wrote Vietnamese → re-anchor language rule
|
|
106
|
+
// inside the directive (storyflow_ui session 22661c8de9f2 — base rule
|
|
107
|
+
// crowded out by brevity/FIX-FIRST directives).
|
|
108
|
+
const replyLanguage = /[à-ỹÀ-Ỹ]/.test(ctx.raw) ? "Vietnamese" : undefined;
|
|
109
|
+
const directive = buildDirective({ complexity, phase, grayAreas, informational, ecosystem, replyLanguage });
|
|
106
110
|
const budgetChars = Math.floor(ctx.tokenBudget * DIRECTIVE_BUDGET_FRACTION);
|
|
107
111
|
const trimmed = truncateToBudget(directive.text, budgetChars);
|
|
108
112
|
return {
|
package/dist/src/ui/app.js
CHANGED
|
@@ -13,6 +13,7 @@ import { POPULAR_MCP_CATALOG } from "../mcp/catalog.js";
|
|
|
13
13
|
import { parseEnvLines, parseHeaderLines } from "../mcp/parse-headers.js";
|
|
14
14
|
import { toMcpServerId, validateMcpServerConfig } from "../mcp/validate.js";
|
|
15
15
|
import { Agent } from "../orchestrator/orchestrator.js";
|
|
16
|
+
import { planLoopCapAskcard } from "../orchestrator/tool-loop-askcard.js";
|
|
16
17
|
import { getConfiguredProviders, setKeyForProvider } from "../providers/keychain.js";
|
|
17
18
|
import { buildIdealContinuationPrompt } from "../scaffold/continuation-prompt.js";
|
|
18
19
|
import { continueAsCouncil } from "../scaffold/continue-as-council.js";
|
|
@@ -24,7 +25,7 @@ import { processAtMentions } from "../utils/at-mentions.js";
|
|
|
24
25
|
import { readClipboardImage } from "../utils/clipboard-image.js";
|
|
25
26
|
import { FileIndex } from "../utils/file-index.js";
|
|
26
27
|
import { copyTextToHostClipboard, readTextFromHostClipboard } from "../utils/host-clipboard.js";
|
|
27
|
-
import { getApiKey, getCurrentModel, getTelegramBotToken, isModelDisabled, isReservedSubagentName, loadMcpServers, loadPaymentSettings, loadUserSettings, loadValidSubAgents, saveApprovedTelegramUserId, saveMcpServers, savePaymentSettings, saveProjectSettings, saveUserSettings, setDefaultProvider, setModelDisabled, setProviderDisabled, } from "../utils/settings.js";
|
|
28
|
+
import { getApiKey, getCurrentModel, getSteerInjectionEnabled, getTelegramBotToken, isModelDisabled, isReservedSubagentName, loadMcpServers, loadPaymentSettings, loadUserSettings, loadValidSubAgents, saveApprovedTelegramUserId, saveMcpServers, savePaymentSettings, saveProjectSettings, saveUserSettings, setDefaultProvider, setModelDisabled, setProviderDisabled, } from "../utils/settings.js";
|
|
28
29
|
import { discoverSkills, formatSkillsForChat } from "../utils/skills.js";
|
|
29
30
|
import { formatSubagentName } from "../utils/subagent-display.js";
|
|
30
31
|
import { checkForUpdate, runUpdate } from "../utils/update-checker.js";
|
|
@@ -57,6 +58,7 @@ import { usePairQuoteBuffer } from "./components/use-pair-quote-buffer.js";
|
|
|
57
58
|
import { useAgentEditor } from "./hooks/use-agent-editor.js";
|
|
58
59
|
import { useMcpEditor } from "./hooks/use-mcp-editor.js";
|
|
59
60
|
import { useModelPicker } from "./hooks/use-model-picker.js";
|
|
61
|
+
import { useSessionPicker } from "./hooks/use-session-picker.js";
|
|
60
62
|
import { useTypeahead } from "./hooks/useTypeahead.js";
|
|
61
63
|
import { Markdown } from "./markdown.js";
|
|
62
64
|
import { buildMcpBrowseRows, McpBrowserModal, McpEditorModal } from "./mcp-modal.js";
|
|
@@ -65,6 +67,7 @@ import { ApiKeyModal } from "./modals/api-key-modal.js";
|
|
|
65
67
|
import { ConnectModal, TelegramPairModal, TelegramTokenModal } from "./modals/connect-modal.js";
|
|
66
68
|
import { ModelPickerModal } from "./modals/model-picker-modal.js";
|
|
67
69
|
import { SandboxPickerModal } from "./modals/sandbox-picker-modal.js";
|
|
70
|
+
import { SessionPickerModal } from "./modals/session-picker-modal.js";
|
|
68
71
|
import { UpdateModal } from "./modals/update-modal.js";
|
|
69
72
|
import { PaymentApprovalPanel, WalletPickerModal } from "./modals/wallet-picker-modal.js";
|
|
70
73
|
import { resolvePickerProviders } from "./picker-providers.js";
|
|
@@ -76,6 +79,7 @@ import { StatusBar } from "./status-bar/index.js";
|
|
|
76
79
|
import { statusBarStore, wireStatusBar } from "./status-bar/store.js";
|
|
77
80
|
import { getCompactTuiSelectionText } from "./terminal-selection-text.js";
|
|
78
81
|
import { dark } from "./theme.js";
|
|
82
|
+
import { relaunchWithSession } from "./utils/relaunch.js";
|
|
79
83
|
import "./slash/route.js";
|
|
80
84
|
import "./slash/optimize.js";
|
|
81
85
|
import "./slash/discuss.js";
|
|
@@ -483,6 +487,11 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
483
487
|
pushToast(lvl, text);
|
|
484
488
|
return;
|
|
485
489
|
}
|
|
490
|
+
if (e.kind === "steer-inject") {
|
|
491
|
+
const count = typeof e.count === "number" ? e.count : 1;
|
|
492
|
+
pushToast("info", `↳ steering applied (${count} message${count === 1 ? "" : "s"})`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
486
495
|
if (e.kind === "ee-timeout" || e.kind === "ee-error") {
|
|
487
496
|
const source = typeof e.source === "string" ? e.source : "unknown";
|
|
488
497
|
const kind = e.kind === "ee-timeout" ? "timeout" : "error";
|
|
@@ -539,9 +548,27 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
539
548
|
}
|
|
540
549
|
return undefined;
|
|
541
550
|
}, [handleHarnessEvent]);
|
|
551
|
+
// Live-queue steering: expose the mid-turn queue to the running turn so
|
|
552
|
+
// prepareStep can inject typed-while-busy messages at the next step boundary
|
|
553
|
+
// instead of deferring them to a new turn. Disabled → callback not wired, so
|
|
554
|
+
// finishTurnProcessing drains the queue post-turn exactly as before.
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
if (!getSteerInjectionEnabled())
|
|
557
|
+
return;
|
|
558
|
+
agent.setSteerDrain(() => {
|
|
559
|
+
if (queuedMessagesRef.current.length === 0)
|
|
560
|
+
return [];
|
|
561
|
+
const drained = queuedMessagesRef.current.map((m) => ({ text: m.text }));
|
|
562
|
+
queuedMessagesRef.current = [];
|
|
563
|
+
setQueuedMessages([]);
|
|
564
|
+
return drained;
|
|
565
|
+
});
|
|
566
|
+
return () => agent.setSteerDrain(null);
|
|
567
|
+
}, [agent]);
|
|
542
568
|
const dismissToast = useCallback(() => setActiveToast(null), []);
|
|
543
569
|
// ─── /Phase 21 toast subscriber ────────────────────────────────────────────
|
|
544
570
|
const { model, setModel, showModelPicker, setShowModelPicker, modelPickerIndex, setModelPickerIndex, modelSearchQuery, setModelSearchQuery, configuredProviders, setConfiguredProviders, disabledProviders, setDisabledProvidersState, defaultProvider, setDefaultProviderState, disabledModels, setDisabledModelsState, modelPickerFocus, setModelPickerFocus, providerChipIndex, setProviderChipIndex, reasoningEffortByModel, setReasoningEffortByModel, } = useModelPicker(agent.getModel());
|
|
571
|
+
const { showSessionPicker, setShowSessionPicker, sessionPickerIndex, setSessionPickerIndex, sessions: sessionPickerList, setSessions: setSessionPickerList, } = useSessionPicker();
|
|
545
572
|
const modelRef = useRef(model);
|
|
546
573
|
const [providersWithKey, setProvidersWithKey] = useState(() => new Set());
|
|
547
574
|
const refreshProvidersWithKey = useCallback(async () => {
|
|
@@ -1779,50 +1806,43 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
1779
1806
|
const isPattern = info.kind === "pattern";
|
|
1780
1807
|
const qid = isPattern ? `tool-pattern-loop-${Date.now()}` : `tool-loop-cap-${info.stepNumber}-${Date.now()}`;
|
|
1781
1808
|
toolLoopCapResolversRef.current.set(qid, resolve);
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
1784
|
-
//
|
|
1785
|
-
//
|
|
1786
|
-
//
|
|
1787
|
-
//
|
|
1788
|
-
//
|
|
1789
|
-
//
|
|
1809
|
+
// Tier-aware askcard layout (planLoopCapAskcard) — 4 tiers:
|
|
1810
|
+
// early (< 0.5× ceiling) → Default Continue, no warning
|
|
1811
|
+
// normal (0.5×–2× ceiling) → Default Stop, no warning
|
|
1812
|
+
// overBudget (2×–5× ceiling) → Default Stop, Continue label carries
|
|
1813
|
+
// the overage multiplier so cost is
|
|
1814
|
+
// visible (storyflow_ui 22661c8de9f2:
|
|
1815
|
+
// 2.4× hit had no warning before)
|
|
1816
|
+
// extreme (> 5× ceiling) → Stop FIRST in the array (Enter=Stop),
|
|
1817
|
+
// Continue labelled "expensive"
|
|
1818
|
+
// (session 1f29e238: 12.8× past ceiling)
|
|
1790
1819
|
const patternStep = isPattern ? info.stepNumber : 0;
|
|
1791
1820
|
const patternCeiling = isPattern ? info.naturalCeiling : undefined;
|
|
1792
|
-
const
|
|
1793
|
-
? patternStep
|
|
1794
|
-
:
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
const patternExtreme = patternCeiling !== undefined && patternCeiling > 0 && patternStep > patternCeiling * 5;
|
|
1802
|
-
const overageMultiplier = patternExtreme && patternCeiling ? (patternStep / patternCeiling).toFixed(1) : null;
|
|
1803
|
-
const patternDefaultIdx = patternEarly ? 0 : patternExtreme ? 0 : 1;
|
|
1804
|
-
const patternOptions = patternExtreme
|
|
1821
|
+
const layout = isPattern
|
|
1822
|
+
? planLoopCapAskcard({ stepNumber: patternStep, naturalCeiling: patternCeiling })
|
|
1823
|
+
: null;
|
|
1824
|
+
const patternEarly = layout?.tier === "early";
|
|
1825
|
+
const patternOverBudget = layout?.tier === "overBudget";
|
|
1826
|
+
const patternExtreme = layout?.tier === "extreme";
|
|
1827
|
+
const overageMultiplier = layout?.overageMultiplier ?? null;
|
|
1828
|
+
const patternDefaultIdx = layout?.defaultIndex ?? 0;
|
|
1829
|
+
const patternOptions = layout
|
|
1805
1830
|
? [
|
|
1806
|
-
{ label:
|
|
1807
|
-
{
|
|
1808
|
-
label: `Continue anyway (⚠ ${overageMultiplier}× over budget — expensive)`,
|
|
1809
|
-
value: "continue",
|
|
1810
|
-
kind: "choice",
|
|
1811
|
-
},
|
|
1831
|
+
{ label: layout.optionLabels[0], value: layout.optionValues[0], kind: "choice" },
|
|
1832
|
+
{ label: layout.optionLabels[1], value: layout.optionValues[1], kind: "choice" },
|
|
1812
1833
|
]
|
|
1813
|
-
: [
|
|
1814
|
-
{ label: "Continue (let agent try)", value: "continue", kind: "choice" },
|
|
1815
|
-
{ label: "Stop and answer", value: "stop", kind: "choice" },
|
|
1816
|
-
];
|
|
1834
|
+
: [];
|
|
1817
1835
|
const question = isPattern
|
|
1818
1836
|
? {
|
|
1819
1837
|
questionId: qid,
|
|
1820
1838
|
question: `Tool \`${info.toolName}\` đã chạy ${info.count}/${info.windowSize} lần với args gần giống (step ${info.stepNumber}${patternCeiling ? `/${patternCeiling}` : ""}) — có thể đang loop. Tiếp tục?`,
|
|
1821
1839
|
context: patternExtreme
|
|
1822
1840
|
? `EXTREME OVERAGE — ${overageMultiplier}× past natural budget. Continuing has historically not converged in this regime (see session 1f29e238: 8× over budget, still failed). Stop returns the agent's best answer with current context.`
|
|
1823
|
-
:
|
|
1824
|
-
?
|
|
1825
|
-
:
|
|
1841
|
+
: patternOverBudget
|
|
1842
|
+
? `Past natural budget — ${overageMultiplier}× the typical step count for this task type. Continuing may still converge but quality often degrades (longer compaction, stale tool results, forced-finalize on stall). Stop returns the agent's best answer with current context.`
|
|
1843
|
+
: patternEarly
|
|
1844
|
+
? "Continue lets the agent keep trying — likely the right call this early in the run. Stop returns the agent's best answer with current context."
|
|
1845
|
+
: "You're past the natural budget for this task type. Stop usually recovers a clean answer; Continue keeps spending tokens.",
|
|
1826
1846
|
isRequired: true,
|
|
1827
1847
|
phase: "tool-loop-cap",
|
|
1828
1848
|
options: patternOptions,
|
|
@@ -3035,6 +3055,27 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3035
3055
|
openSandboxPicker();
|
|
3036
3056
|
return true;
|
|
3037
3057
|
}
|
|
3058
|
+
if (c === "/sessions" || c === "/session") {
|
|
3059
|
+
try {
|
|
3060
|
+
const { SessionStore } = require("../storage/sessions.js");
|
|
3061
|
+
const list = new SessionStore(agent.getCwd()).listRecentSessions(20);
|
|
3062
|
+
setSessionPickerList(list);
|
|
3063
|
+
setSessionPickerIndex(0);
|
|
3064
|
+
setShowSessionPicker(true);
|
|
3065
|
+
}
|
|
3066
|
+
catch (err) {
|
|
3067
|
+
console.error(`[session-picker] list failed: ${err?.message ?? err}`);
|
|
3068
|
+
setMessages((p) => [
|
|
3069
|
+
...p,
|
|
3070
|
+
{
|
|
3071
|
+
type: "assistant",
|
|
3072
|
+
content: `Failed to list sessions: ${err instanceof Error ? err.message : String(err)}`,
|
|
3073
|
+
timestamp: new Date(),
|
|
3074
|
+
},
|
|
3075
|
+
]);
|
|
3076
|
+
}
|
|
3077
|
+
return true;
|
|
3078
|
+
}
|
|
3038
3079
|
if (c === "/wallet") {
|
|
3039
3080
|
openWalletPicker();
|
|
3040
3081
|
return true;
|
|
@@ -3731,6 +3772,9 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3731
3772
|
model,
|
|
3732
3773
|
messages.length,
|
|
3733
3774
|
messages,
|
|
3775
|
+
setSessionPickerList,
|
|
3776
|
+
setSessionPickerIndex,
|
|
3777
|
+
setShowSessionPicker,
|
|
3734
3778
|
]);
|
|
3735
3779
|
const handleSlashMenuSelect = useCallback((item) => {
|
|
3736
3780
|
setShowSlashMenuSync(false);
|
|
@@ -3905,34 +3949,27 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3905
3949
|
]);
|
|
3906
3950
|
break;
|
|
3907
3951
|
case "sessions": {
|
|
3908
|
-
//
|
|
3909
|
-
//
|
|
3910
|
-
|
|
3952
|
+
// Open the picker (delegates to the same path as typing `/sessions`)
|
|
3953
|
+
// so the user can pick a session and resume it directly instead of
|
|
3954
|
+
// having to remember the id + relaunch by hand.
|
|
3911
3955
|
try {
|
|
3912
3956
|
const { SessionStore } = require("../storage/sessions.js");
|
|
3913
|
-
const
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
const ts = new Date(s.updatedAt).toLocaleString();
|
|
3918
|
-
const title = s.title?.trim() || "(untitled)";
|
|
3919
|
-
const truncTitle = title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
3920
|
-
return `${String(idx + 1).padStart(2)}. [${s.id}] ${ts} ${s.model}\n ${truncTitle}`;
|
|
3921
|
-
});
|
|
3922
|
-
body = [
|
|
3923
|
-
"Recent sessions in this workspace:",
|
|
3924
|
-
"",
|
|
3925
|
-
...lines,
|
|
3926
|
-
"",
|
|
3927
|
-
"Resume on next launch: muonroi-cli --session <id>",
|
|
3928
|
-
"Or: muonroi-cli --session latest",
|
|
3929
|
-
].join("\n");
|
|
3930
|
-
}
|
|
3957
|
+
const list = new SessionStore(agent.getCwd()).listRecentSessions(20);
|
|
3958
|
+
setSessionPickerList(list);
|
|
3959
|
+
setSessionPickerIndex(0);
|
|
3960
|
+
setShowSessionPicker(true);
|
|
3931
3961
|
}
|
|
3932
3962
|
catch (err) {
|
|
3933
|
-
|
|
3963
|
+
console.error(`[session-picker] list failed: ${err?.message ?? err}`);
|
|
3964
|
+
setMessages((p) => [
|
|
3965
|
+
...p,
|
|
3966
|
+
{
|
|
3967
|
+
type: "assistant",
|
|
3968
|
+
content: `Failed to list sessions: ${err instanceof Error ? err.message : String(err)}`,
|
|
3969
|
+
timestamp: new Date(),
|
|
3970
|
+
},
|
|
3971
|
+
]);
|
|
3934
3972
|
}
|
|
3935
|
-
setMessages((p) => [...p, { type: "assistant", content: body, timestamp: new Date() }]);
|
|
3936
3973
|
break;
|
|
3937
3974
|
}
|
|
3938
3975
|
default: {
|
|
@@ -4060,12 +4097,16 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
4060
4097
|
setModelPickerIndex,
|
|
4061
4098
|
setModelSearchQuery,
|
|
4062
4099
|
setShowModelPicker,
|
|
4100
|
+
setSessionPickerList,
|
|
4101
|
+
setSessionPickerIndex,
|
|
4102
|
+
setShowSessionPicker,
|
|
4063
4103
|
]);
|
|
4064
4104
|
const blockPrompt = showConnectModal ||
|
|
4065
4105
|
showTelegramTokenModal ||
|
|
4066
4106
|
showTelegramPairModal ||
|
|
4067
4107
|
showMcpModal ||
|
|
4068
4108
|
showSandboxPicker ||
|
|
4109
|
+
showSessionPicker ||
|
|
4069
4110
|
showWalletPicker ||
|
|
4070
4111
|
!!pendingPaymentApproval ||
|
|
4071
4112
|
showScheduleModal ||
|
|
@@ -5166,6 +5207,43 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
5166
5207
|
}
|
|
5167
5208
|
return;
|
|
5168
5209
|
}
|
|
5210
|
+
if (showSessionPicker) {
|
|
5211
|
+
if (isEscapeKey(key)) {
|
|
5212
|
+
setShowSessionPicker(false);
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
if (key.name === "up") {
|
|
5216
|
+
setSessionPickerIndex((i) => Math.max(0, i - 1));
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
5219
|
+
if (key.name === "down") {
|
|
5220
|
+
setSessionPickerIndex((i) => Math.min(Math.max(0, sessionPickerList.length - 1), i + 1));
|
|
5221
|
+
return;
|
|
5222
|
+
}
|
|
5223
|
+
if (key.name === "return") {
|
|
5224
|
+
const picked = sessionPickerList[sessionPickerIndex];
|
|
5225
|
+
if (!picked) {
|
|
5226
|
+
setShowSessionPicker(false);
|
|
5227
|
+
return;
|
|
5228
|
+
}
|
|
5229
|
+
// Close the modal first so the toast renders before the spawn.
|
|
5230
|
+
setShowSessionPicker(false);
|
|
5231
|
+
pushToast("info", `Resuming session ${picked.id.slice(-8)}… restarting CLI`);
|
|
5232
|
+
// Defer to the next tick so OpenTUI flushes the toast frame; then
|
|
5233
|
+
// spawn the child (which inherits the TTY) and exit this process.
|
|
5234
|
+
setTimeout(() => {
|
|
5235
|
+
try {
|
|
5236
|
+
relaunchWithSession(picked.id);
|
|
5237
|
+
}
|
|
5238
|
+
catch (err) {
|
|
5239
|
+
console.error(`[session-picker] relaunch failed: ${err?.message ?? err}`);
|
|
5240
|
+
pushToast("error", `Resume failed: ${err?.message ?? err}`);
|
|
5241
|
+
}
|
|
5242
|
+
}, 50);
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5169
5247
|
if (showModelPicker) {
|
|
5170
5248
|
// Sub-modal: BW sync (password + provider picker phases).
|
|
5171
5249
|
if (bwSync) {
|
|
@@ -5790,6 +5868,11 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
5790
5868
|
setShowModelPicker,
|
|
5791
5869
|
setModelPickerIndex,
|
|
5792
5870
|
setModel,
|
|
5871
|
+
showSessionPicker,
|
|
5872
|
+
sessionPickerList,
|
|
5873
|
+
sessionPickerIndex,
|
|
5874
|
+
setShowSessionPicker,
|
|
5875
|
+
setSessionPickerIndex,
|
|
5793
5876
|
]);
|
|
5794
5877
|
useKeyboard(handleKey);
|
|
5795
5878
|
const handlePaste = useCallback((event) => {
|
|
@@ -6024,7 +6107,7 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
6024
6107
|
: `💭 Thought for ${(lastReasoningElapsedMs / 1000).toFixed(1)}s` }) })), streamContent && (_jsx("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, children: _jsx(Markdown, { content: streamContent, t: t }) })), isProcessing && !streamContent && activeToolCalls.length === 0 && (_jsx(ShimmerText, { t: t, text: "Planning next moves" })), showPlanPanel && _jsx(PlanQuestionsPanel, { t: t, questions: planQuestions, state: pqs }), pendingPaymentApproval && _jsx(PaymentApprovalPanel, { t: t, payment: pendingPaymentApproval }), activeHaltCard && (_jsx(HaltRecoveryCard, { halt: activeHaltCard, selectedIndex: haltSelectedIndex, terminalCols: width, theme: t })), initNewForm && _jsx(InitNewFormCard, { state: initNewForm, terminalCols: width, theme: t }), pointToExistingForm && (_jsx(PointToExistingFormCard, { state: pointToExistingForm, terminalCols: width, theme: t })), councilProgress && (_jsx(Semantic, { id: "continue-as-council-progress", role: "log", name: "Council brainstorm", children: _jsx("box", { flexDirection: "column", borderStyle: "single", borderColor: councilProgress.status === "error" ? t.initFormError : t.text, padding: 1, marginTop: 1, children: _jsxs("text", { fg: t.text, children: [councilProgress.status === "running" && "Council brainstorming — writing spec.md...", councilProgress.status === "done" &&
|
|
6025
6108
|
`Council brainstorm complete: ${councilProgress.specPath}${councilProgress.hasContent ? "" : " (no content — production council wiring deferred)"}`, councilProgress.status === "error" && `Council brainstorm failed: ${councilProgress.error}`] }) }) }))] }) }), btwState && _jsx(BtwOverlay, { state: btwState, theme: t }), _jsx("box", { flexShrink: 0, children: _jsx(PromptBox, { t: t, inputRef: inputRef, isProcessing: isProcessing, showModelPicker: showModelPicker, showSandboxPicker: showSandboxPicker, showWalletPicker: showWalletPicker, showSlashMenu: showSlashMenu, showPlanQuestions: showPlanPanel, showApiKeyModal: showApiKeyModal, blockPrompt: blockPrompt, onSubmit: handleSubmit, onPaste: handlePaste, pasteBlocks: pasteBlocks, modeInfo: modeInfo, model: model, modelInfo: modelInfo, contextStats: contextStats, queuedCount: queuedMessages.length, queuedMessages: queuedMessages, typeahead: typeahead, slashItems: filteredSlashItems, slashSelectedIndex: slashMenuIndex, slashInputIsMatched: slashInputIsMatched, composerValue: showSlashMenu ? `/${slashSearchQuery}` : undefined }) })] }), _jsx("box", { paddingLeft: 2, paddingRight: 2, flexShrink: 0, children: _jsx(StatusBar, {}) }), _jsxs("box", { paddingLeft: 2, paddingRight: 2, paddingBottom: 1, flexDirection: "row", flexShrink: 0, children: [_jsx("text", { fg: t.textDim, children: agent.getCwd().replace(os.homedir(), "~") }), sandboxMode === "shuru" ? _jsx("text", { fg: "#f97316", children: " · sandbox" }) : null, _jsx("box", { flexGrow: 1 })] })] })) : (
|
|
6026
6109
|
/* ── Home ───────────────────────────────────────── */
|
|
6027
|
-
_jsxs(_Fragment, { children: [_jsxs("box", { flexGrow: 1, alignItems: "center", paddingLeft: 2, paddingRight: 2, children: [_jsx("box", { flexGrow: 1, minHeight: 0 }), _jsx("box", { flexShrink: 0, alignItems: "center", children: _jsx(HeroLogo, { t: t }) }), _jsx("box", { height: 1, minHeight: 0, flexShrink: 1 }), _jsx("box", { width: "100%", maxWidth: 75, flexShrink: 0, children: _jsx(PromptBox, { t: t, inputRef: inputRef, isProcessing: isProcessing, showModelPicker: showModelPicker, showSandboxPicker: showSandboxPicker, showWalletPicker: showWalletPicker, showSlashMenu: showSlashMenu, showPlanQuestions: showPlanPanel, showApiKeyModal: showApiKeyModal, blockPrompt: blockPrompt, onSubmit: handleSubmit, onPaste: handlePaste, pasteBlocks: pasteBlocks, modeInfo: modeInfo, model: model, modelInfo: modelInfo, contextStats: contextStats, placeholder: "What are we building?", typeahead: typeahead, slashItems: filteredSlashItems, slashSelectedIndex: slashMenuIndex, slashInputIsMatched: slashInputIsMatched, composerValue: showSlashMenu ? `/${slashSearchQuery}` : undefined }) }), _jsx("box", { height: 2, minHeight: 0, flexShrink: 1 }), _jsx("box", { flexGrow: 1, minHeight: 0 })] }), updateInfo?.hasUpdate && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsxs("text", { fg: "#f59e0b", children: ["┃ Update available: v", startupConfig.version, " → v", updateInfo.latestVersion, " — run /update to install"] }) })), isUpdating && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsx("text", { fg: "#f59e0b", children: "┃ Updating..." }) })), updateOutput && !isUpdating && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsxs("text", { fg: updateOutput.startsWith("Update complete") ? "#22c55e" : "#ef4444", children: ["┃ ", updateOutput] }) })), _jsx("box", { paddingLeft: 2, paddingRight: 2, flexShrink: 0, children: _jsx(StatusBar, {}) }), _jsxs("box", { paddingLeft: 2, paddingRight: 2, paddingBottom: 1, flexDirection: "row", flexShrink: 0, children: [_jsx("text", { fg: t.textDim, children: agent.getCwd().replace(os.homedir(), "~") }), sandboxMode === "shuru" ? _jsx("text", { fg: "#f97316", children: " · sandbox" }) : null, _jsx("box", { flexGrow: 1 }), _jsx("text", { fg: t.textDim, children: `v${startupConfig.version}` })] })] })), 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, bwSync: bwSync })), 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() }))] }) }));
|
|
6110
|
+
_jsxs(_Fragment, { children: [_jsxs("box", { flexGrow: 1, alignItems: "center", paddingLeft: 2, paddingRight: 2, children: [_jsx("box", { flexGrow: 1, minHeight: 0 }), _jsx("box", { flexShrink: 0, alignItems: "center", children: _jsx(HeroLogo, { t: t }) }), _jsx("box", { height: 1, minHeight: 0, flexShrink: 1 }), _jsx("box", { width: "100%", maxWidth: 75, flexShrink: 0, children: _jsx(PromptBox, { t: t, inputRef: inputRef, isProcessing: isProcessing, showModelPicker: showModelPicker, showSandboxPicker: showSandboxPicker, showWalletPicker: showWalletPicker, showSlashMenu: showSlashMenu, showPlanQuestions: showPlanPanel, showApiKeyModal: showApiKeyModal, blockPrompt: blockPrompt, onSubmit: handleSubmit, onPaste: handlePaste, pasteBlocks: pasteBlocks, modeInfo: modeInfo, model: model, modelInfo: modelInfo, contextStats: contextStats, placeholder: "What are we building?", typeahead: typeahead, slashItems: filteredSlashItems, slashSelectedIndex: slashMenuIndex, slashInputIsMatched: slashInputIsMatched, composerValue: showSlashMenu ? `/${slashSearchQuery}` : undefined }) }), _jsx("box", { height: 2, minHeight: 0, flexShrink: 1 }), _jsx("box", { flexGrow: 1, minHeight: 0 })] }), updateInfo?.hasUpdate && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsxs("text", { fg: "#f59e0b", children: ["┃ Update available: v", startupConfig.version, " → v", updateInfo.latestVersion, " — run /update to install"] }) })), isUpdating && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsx("text", { fg: "#f59e0b", children: "┃ Updating..." }) })), updateOutput && !isUpdating && (_jsx("box", { paddingLeft: 2, paddingRight: 2, flexDirection: "row", flexShrink: 0, children: _jsxs("text", { fg: updateOutput.startsWith("Update complete") ? "#22c55e" : "#ef4444", children: ["┃ ", updateOutput] }) })), _jsx("box", { paddingLeft: 2, paddingRight: 2, flexShrink: 0, children: _jsx(StatusBar, {}) }), _jsxs("box", { paddingLeft: 2, paddingRight: 2, paddingBottom: 1, flexDirection: "row", flexShrink: 0, children: [_jsx("text", { fg: t.textDim, children: agent.getCwd().replace(os.homedir(), "~") }), sandboxMode === "shuru" ? _jsx("text", { fg: "#f97316", children: " · sandbox" }) : null, _jsx("box", { flexGrow: 1 }), _jsx("text", { fg: t.textDim, children: `v${startupConfig.version}` })] })] })), 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, bwSync: bwSync })), 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() }))] }) }));
|
|
6028
6111
|
}
|
|
6029
6112
|
export { computeMcpRunInfo } from "./components/message-view.js";
|
|
6030
6113
|
/* ── Slash Menu ──────────────────────────────────────────────── */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SessionInfo } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* State for the /sessions picker modal. Sessions are loaded lazily when the
|
|
4
|
+
* picker is opened (the SQLite query is cheap — ORDER BY updated_at LIMIT 20
|
|
5
|
+
* on an indexed column) so we do not pay for it on cold boot.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useSessionPicker(): {
|
|
8
|
+
showSessionPicker: boolean;
|
|
9
|
+
setShowSessionPicker: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
10
|
+
sessionPickerIndex: number;
|
|
11
|
+
setSessionPickerIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
12
|
+
sessions: SessionInfo[];
|
|
13
|
+
setSessions: import("react").Dispatch<import("react").SetStateAction<SessionInfo[]>>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* State for the /sessions picker modal. Sessions are loaded lazily when the
|
|
4
|
+
* picker is opened (the SQLite query is cheap — ORDER BY updated_at LIMIT 20
|
|
5
|
+
* on an indexed column) so we do not pay for it on cold boot.
|
|
6
|
+
*/
|
|
7
|
+
export function useSessionPicker() {
|
|
8
|
+
const [showSessionPicker, setShowSessionPicker] = useState(false);
|
|
9
|
+
const [sessionPickerIndex, setSessionPickerIndex] = useState(0);
|
|
10
|
+
const [sessions, setSessions] = useState([]);
|
|
11
|
+
return {
|
|
12
|
+
showSessionPicker,
|
|
13
|
+
setShowSessionPicker,
|
|
14
|
+
sessionPickerIndex,
|
|
15
|
+
setSessionPickerIndex,
|
|
16
|
+
sessions,
|
|
17
|
+
setSessions,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=use-session-picker.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SessionInfo } from "../../types/index.js";
|
|
2
|
+
import type { Theme } from "../theme.js";
|
|
3
|
+
/**
|
|
4
|
+
* Recent-sessions picker. Opened by `/sessions` or `/session`. Selecting a
|
|
5
|
+
* row relaunches the CLI with `--session <id>` (see ui/utils/relaunch.ts) so
|
|
6
|
+
* the user does not need to remember the id or restart by hand.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SessionPickerModal({ t, sessions, focusIndex, width, height, }: {
|
|
9
|
+
t: Theme;
|
|
10
|
+
sessions: SessionInfo[];
|
|
11
|
+
focusIndex: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
}): import("react").ReactNode;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { bottomAlignedModalTop } from "../utils/modal.js";
|
|
3
|
+
/**
|
|
4
|
+
* Recent-sessions picker. Opened by `/sessions` or `/session`. Selecting a
|
|
5
|
+
* row relaunches the CLI with `--session <id>` (see ui/utils/relaunch.ts) so
|
|
6
|
+
* the user does not need to remember the id or restart by hand.
|
|
7
|
+
*/
|
|
8
|
+
export function SessionPickerModal({ t, sessions, focusIndex, width, height, }) {
|
|
9
|
+
const panelWidth = Math.min(80, width - 6);
|
|
10
|
+
const rowCount = Math.max(sessions.length, 1);
|
|
11
|
+
// 4 chrome lines (title row + spacer + footer + paddings) + the rows
|
|
12
|
+
const contentHeight = rowCount + 4;
|
|
13
|
+
const maxH = Math.floor(height * 0.7);
|
|
14
|
+
const panelHeight = Math.min(contentHeight, maxH);
|
|
15
|
+
const top = bottomAlignedModalTop(height, panelHeight);
|
|
16
|
+
const overlayBg = "#000000cc";
|
|
17
|
+
return (_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: "Resume session" }) }), _jsx("text", { fg: t.textMuted, children: "esc" })] }), _jsx("scrollbox", { flexGrow: 1, minHeight: 0, children: sessions.length === 0 ? (_jsx("box", { paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "No prior sessions in this workspace." }) })) : (sessions.map((s, idx) => {
|
|
18
|
+
const focused = idx === focusIndex;
|
|
19
|
+
const ts = formatTimestamp(s.updatedAt);
|
|
20
|
+
const titleRaw = s.title?.trim() || "(untitled)";
|
|
21
|
+
const titleMax = Math.max(8, panelWidth - 38);
|
|
22
|
+
const title = titleRaw.length > titleMax ? `${titleRaw.slice(0, titleMax - 1)}…` : titleRaw;
|
|
23
|
+
const idShort = s.id.slice(-8);
|
|
24
|
+
return (_jsxs("box", { backgroundColor: focused ? t.selectedBg : undefined, paddingLeft: 2, paddingRight: 2, width: "100%", flexDirection: "row", justifyContent: "space-between", children: [_jsx("text", { fg: focused ? t.selected : t.text, children: `${ts} ${title}` }), _jsx("text", { fg: focused ? t.primary : t.textMuted, children: `${s.model} ${idShort}` })] }, s.id));
|
|
25
|
+
})) }), _jsx("box", { flexShrink: 0, paddingLeft: 2, paddingRight: 2, paddingTop: 1, children: _jsx("text", { fg: t.textMuted, children: "↑↓ navigate · enter resume (restarts CLI) · esc cancel" }) })] }) }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compact MM-DD HH:MM timestamp for the picker rows. Trades the year for
|
|
29
|
+
* space — the picker is workspace-scoped + lists the latest 20 sessions, so
|
|
30
|
+
* a year boundary is rare and an obvious context.
|
|
31
|
+
*/
|
|
32
|
+
function formatTimestamp(d) {
|
|
33
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
34
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
35
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
36
|
+
const min = String(d.getMinutes()).padStart(2, "0");
|
|
37
|
+
return `${mm}-${dd} ${hh}:${min}`;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=session-picker-modal.js.map
|