muonroi-cli 1.6.5 → 1.7.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/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/orchestrator/message-processor.js +1 -1
- package/dist/src/orchestrator/prompts.js +16 -2
- package/dist/src/orchestrator/stream-runner.js +50 -3
- package/dist/src/orchestrator/subagent-compactor.d.ts +1 -1
- package/dist/src/orchestrator/subagent-compactor.js +1 -1
- package/dist/src/pil/__tests__/layer4-gsd.test.js +40 -23
- package/dist/src/pil/__tests__/llm-classify.test.js +40 -3
- package/dist/src/pil/layer1-intent.js +10 -1
- package/dist/src/pil/layer1-intent.test.js +18 -0
- package/dist/src/pil/layer4-gsd.js +43 -19
- package/dist/src/pil/llm-classify.d.ts +36 -0
- package/dist/src/pil/llm-classify.js +84 -18
- package/dist/src/pil/types.d.ts +27 -2
- package/dist/src/{gsd → playbook}/__tests__/directives.test.js +34 -58
- package/dist/src/playbook/complexity.d.ts +17 -0
- package/dist/src/playbook/complexity.js +18 -0
- package/dist/src/{gsd → playbook}/directives.d.ts +20 -13
- package/dist/src/playbook/directives.js +149 -0
- package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +70 -1
- package/dist/src/providers/strategies/deepseek.strategy.js +5 -22
- package/dist/src/providers/strategies/siliconflow.strategy.js +5 -0
- package/dist/src/providers/strategies/thinking-mode.d.ts +35 -0
- package/dist/src/providers/strategies/thinking-mode.js +73 -0
- package/dist/src/tools/registry.js +47 -47
- package/dist/src/ui/app.js +91 -24
- 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.js +83 -0
- package/package.json +1 -1
- package/dist/src/gsd/__tests__/complexity.test.js +0 -0
- package/dist/src/gsd/complexity.d.ts +0 -28
- package/dist/src/gsd/complexity.js +0 -103
- package/dist/src/gsd/directives.js +0 -154
- /package/dist/src/{gsd → playbook}/__tests__/directives.test.d.ts +0 -0
- /package/dist/src/{gsd/__tests__/complexity.test.d.ts → ui/utils/relaunch.test.d.ts} +0 -0
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
21
21
|
import { streamText } from "ai";
|
|
22
|
-
import { describe, expect, it } from "vitest";
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
23
|
+
import { transformThinkingModeBody } from "../strategies/thinking-mode.js";
|
|
23
24
|
function makeStubProvider(name, capture) {
|
|
24
25
|
return createOpenAICompatible({
|
|
25
26
|
name,
|
|
@@ -113,6 +114,11 @@ describe("reasoning_content round-trip — AI SDK 2.0.42 wire shape", () => {
|
|
|
113
114
|
expect(Array.isArray(assistantMsg.tool_calls)).toBe(true);
|
|
114
115
|
expect(assistantMsg.tool_calls[0]?.id).toBe("c1");
|
|
115
116
|
});
|
|
117
|
+
// NOTE: the bare provider correctly omits reasoning_content when a turn has
|
|
118
|
+
// no reasoning part — but that is exactly the shape SiliconFlow's
|
|
119
|
+
// thinking-mode validator rejects (code 20015) in a mixed history. The
|
|
120
|
+
// strategy's transformRequestBody backfills it; see the dedicated describe
|
|
121
|
+
// block below ("transformThinkingModeBody — backfill / disable").
|
|
116
122
|
it("emits no reasoning_content key when there are no reasoning parts (no false positives)", async () => {
|
|
117
123
|
const capture = { current: null };
|
|
118
124
|
const provider = makeStubProvider("siliconflow", capture);
|
|
@@ -132,4 +138,67 @@ describe("reasoning_content round-trip — AI SDK 2.0.42 wire shape", () => {
|
|
|
132
138
|
expect(assistantMsg.reasoning_content).toBeUndefined();
|
|
133
139
|
});
|
|
134
140
|
});
|
|
141
|
+
describe("transformThinkingModeBody — backfill / disable (code 20015 fix)", () => {
|
|
142
|
+
const ENV = "MUONROI_DEEPSEEK_DISABLE_THINKING";
|
|
143
|
+
let saved;
|
|
144
|
+
beforeEach(() => {
|
|
145
|
+
saved = process.env[ENV];
|
|
146
|
+
delete process.env[ENV];
|
|
147
|
+
});
|
|
148
|
+
afterEach(() => {
|
|
149
|
+
if (saved === undefined)
|
|
150
|
+
delete process.env[ENV];
|
|
151
|
+
else
|
|
152
|
+
process.env[ENV] = saved;
|
|
153
|
+
});
|
|
154
|
+
it("A (default): backfills reasoning_content on a tool-call turn that lacks it", () => {
|
|
155
|
+
const body = {
|
|
156
|
+
messages: [
|
|
157
|
+
{ role: "user", content: "go" },
|
|
158
|
+
// tool-call turn with NO reasoning (the real bug shape)
|
|
159
|
+
{ role: "assistant", content: null, tool_calls: [{ id: "t1", type: "function" }] },
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
const out = transformThinkingModeBody(body);
|
|
163
|
+
const asst = out.messages.find((m) => m.role === "assistant");
|
|
164
|
+
expect(asst.reasoning_content).toBe("");
|
|
165
|
+
expect(Array.isArray(asst.tool_calls)).toBe(true); // tool_calls preserved
|
|
166
|
+
expect("thinking" in out).toBe(false); // thinking still ON
|
|
167
|
+
});
|
|
168
|
+
it("A (default): leaves a real reasoning_content untouched and patches only the gap", () => {
|
|
169
|
+
const body = {
|
|
170
|
+
messages: [
|
|
171
|
+
{ role: "user", content: "go" },
|
|
172
|
+
{ role: "assistant", content: null, reasoning_content: "real thought", tool_calls: [{ id: "a" }] },
|
|
173
|
+
{ role: "tool", content: "result" },
|
|
174
|
+
{ role: "assistant", content: null, tool_calls: [{ id: "b" }] }, // gap
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
const out = transformThinkingModeBody(body);
|
|
178
|
+
const asst = out.messages.filter((m) => m.role === "assistant");
|
|
179
|
+
expect(asst[0].reasoning_content).toBe("real thought"); // untouched
|
|
180
|
+
expect(asst[1].reasoning_content).toBe(""); // backfilled
|
|
181
|
+
});
|
|
182
|
+
it("A (default): does not touch non-assistant messages", () => {
|
|
183
|
+
const body = {
|
|
184
|
+
messages: [
|
|
185
|
+
{ role: "user", content: "hi" },
|
|
186
|
+
{ role: "tool", content: "r" },
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
const out = transformThinkingModeBody(body);
|
|
190
|
+
expect("reasoning_content" in out.messages[0]).toBe(false);
|
|
191
|
+
expect("reasoning_content" in out.messages[1]).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
it("B (env=1): disables thinking and does NOT backfill reasoning_content", () => {
|
|
194
|
+
process.env[ENV] = "1";
|
|
195
|
+
const body = {
|
|
196
|
+
messages: [{ role: "assistant", content: null, tool_calls: [{ id: "t1" }] }],
|
|
197
|
+
};
|
|
198
|
+
const out = transformThinkingModeBody(body);
|
|
199
|
+
expect(out.thinking).toEqual({ type: "disabled" });
|
|
200
|
+
const asst = out.messages.find((m) => m.role === "assistant");
|
|
201
|
+
expect("reasoning_content" in asst).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
135
204
|
//# sourceMappingURL=reasoning-roundtrip.test.js.map
|
|
@@ -7,19 +7,7 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
|
7
7
|
import { getProviderCapabilities } from "../capabilities.js";
|
|
8
8
|
import { OPENAI_COMPATIBLE_BASE_URLS } from "../endpoints.js";
|
|
9
9
|
import { BaseProviderStrategy } from "./base.strategy.js";
|
|
10
|
-
|
|
11
|
-
* If MUONROI_DEEPSEEK_DISABLE_THINKING=1 (default for self-qa), inject
|
|
12
|
-
* `extra_body.thinking.type="disabled"` into every DeepSeek request per
|
|
13
|
-
* https://api-docs.deepseek.com/guides/thinking_mode . Cuts response time
|
|
14
|
-
* 30-50% and prevents reasoning prose from leaking into JSON outputs.
|
|
15
|
-
*
|
|
16
|
-
* Set MUONROI_DEEPSEEK_DISABLE_THINKING=0 to keep thinking mode on for
|
|
17
|
-
* chat sessions that actually benefit from reasoning.
|
|
18
|
-
*/
|
|
19
|
-
function shouldDisableThinking() {
|
|
20
|
-
const v = process.env["MUONROI_DEEPSEEK_DISABLE_THINKING"];
|
|
21
|
-
return v === undefined ? false : v === "1" || v.toLowerCase() === "true";
|
|
22
|
-
}
|
|
10
|
+
import { transformThinkingModeBody } from "./thinking-mode.js";
|
|
23
11
|
export class DeepSeekStrategy extends BaseProviderStrategy {
|
|
24
12
|
id = "deepseek";
|
|
25
13
|
capabilities = getProviderCapabilities("deepseek");
|
|
@@ -34,15 +22,10 @@ export class DeepSeekStrategy extends BaseProviderStrategy {
|
|
|
34
22
|
// json_object form for generateObject calls, matching DeepSeek docs:
|
|
35
23
|
// https://api-docs.deepseek.com/guides/json_mode .
|
|
36
24
|
supportsStructuredOutputs: false,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
thinking: { type: "disabled" },
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return body;
|
|
45
|
-
},
|
|
25
|
+
// Thinking-mode round-trip fix: backfill reasoning_content (default) or
|
|
26
|
+
// disable thinking (MUONROI_DEEPSEEK_DISABLE_THINKING=1). See
|
|
27
|
+
// thinking-mode.ts for the full rationale (code 20015 rejection).
|
|
28
|
+
transformRequestBody: (body) => transformThinkingModeBody(body),
|
|
46
29
|
});
|
|
47
30
|
return (modelId) => p(modelId);
|
|
48
31
|
}
|
|
@@ -8,6 +8,7 @@ import { getProviderCapabilities } from "../capabilities.js";
|
|
|
8
8
|
import { OPENAI_COMPATIBLE_BASE_URLS } from "../endpoints.js";
|
|
9
9
|
import { createSiliconflowRepairFetch } from "../siliconflow-sse-repair.js";
|
|
10
10
|
import { BaseProviderStrategy } from "./base.strategy.js";
|
|
11
|
+
import { transformThinkingModeBody } from "./thinking-mode.js";
|
|
11
12
|
export class SiliconflowStrategy extends BaseProviderStrategy {
|
|
12
13
|
id = "siliconflow";
|
|
13
14
|
capabilities = getProviderCapabilities("siliconflow");
|
|
@@ -17,6 +18,10 @@ export class SiliconflowStrategy extends BaseProviderStrategy {
|
|
|
17
18
|
baseURL: opts.baseURL ?? OPENAI_COMPATIBLE_BASE_URLS.siliconflow,
|
|
18
19
|
apiKey: opts.apiKey,
|
|
19
20
|
fetch: createSiliconflowRepairFetch(),
|
|
21
|
+
// Thinking-mode round-trip fix (code 20015): backfill reasoning_content
|
|
22
|
+
// on every assistant turn, or disable thinking when
|
|
23
|
+
// MUONROI_DEEPSEEK_DISABLE_THINKING=1. See thinking-mode.ts.
|
|
24
|
+
transformRequestBody: (body) => transformThinkingModeBody(body),
|
|
20
25
|
});
|
|
21
26
|
return (modelId) => p(modelId);
|
|
22
27
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/strategies/thinking-mode.ts
|
|
3
|
+
*
|
|
4
|
+
* Shared `transformRequestBody` logic for DeepSeek-family providers
|
|
5
|
+
* (deepseek + siliconflow) that run a `thinking`/reasoning mode.
|
|
6
|
+
*
|
|
7
|
+
* THE BUG (verified on a live SiliconFlow wire body): DeepSeek-V4-Flash in
|
|
8
|
+
* thinking mode rejects the WHOLE request with HTTP 400 / code 20015
|
|
9
|
+
* ("The reasoning_content in the thinking mode must be passed back to the
|
|
10
|
+
* API") whenever the history contains an assistant message that lacks a
|
|
11
|
+
* `reasoning_content` field. During multi-step tool loops some assistant
|
|
12
|
+
* turns make a tool call WITHOUT emitting a reasoning segment (e.g. a quick
|
|
13
|
+
* `todo_write`), so `@ai-sdk/openai-compatible` serializes them as
|
|
14
|
+
* `{content:null, tool_calls:[...]}` with no `reasoning_content` key — and
|
|
15
|
+
* the next request blows up. The earlier "reasoning round-trips natively"
|
|
16
|
+
* conclusion only held for histories where EVERY assistant turn had reasoning.
|
|
17
|
+
*
|
|
18
|
+
* Two mitigations, selected by `MUONROI_DEEPSEEK_DISABLE_THINKING`:
|
|
19
|
+
*
|
|
20
|
+
* - Default (A): keep thinking ON, but backfill `reasoning_content: ""` onto
|
|
21
|
+
* every assistant message in the wire body that is missing it, so the
|
|
22
|
+
* thinking-mode validator always sees the field.
|
|
23
|
+
* - Fallback (B, env=1): disable thinking entirely via
|
|
24
|
+
* `thinking: { type: "disabled" }` (per the DeepSeek thinking_mode guide).
|
|
25
|
+
* Sidesteps the whole class of bug, cuts latency 30-50%, and stops
|
|
26
|
+
* reasoning prose from leaking into JSON outputs — at the cost of reasoning.
|
|
27
|
+
*
|
|
28
|
+
* https://api-docs.deepseek.com/guides/thinking_mode
|
|
29
|
+
*/
|
|
30
|
+
export declare function shouldDisableThinking(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The shared `transformRequestBody` for deepseek + siliconflow. Runs on the
|
|
33
|
+
* fully-serialized wire body right before fetch.
|
|
34
|
+
*/
|
|
35
|
+
export declare function transformThinkingModeBody<T extends Record<string, unknown>>(body: T): T;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/strategies/thinking-mode.ts
|
|
3
|
+
*
|
|
4
|
+
* Shared `transformRequestBody` logic for DeepSeek-family providers
|
|
5
|
+
* (deepseek + siliconflow) that run a `thinking`/reasoning mode.
|
|
6
|
+
*
|
|
7
|
+
* THE BUG (verified on a live SiliconFlow wire body): DeepSeek-V4-Flash in
|
|
8
|
+
* thinking mode rejects the WHOLE request with HTTP 400 / code 20015
|
|
9
|
+
* ("The reasoning_content in the thinking mode must be passed back to the
|
|
10
|
+
* API") whenever the history contains an assistant message that lacks a
|
|
11
|
+
* `reasoning_content` field. During multi-step tool loops some assistant
|
|
12
|
+
* turns make a tool call WITHOUT emitting a reasoning segment (e.g. a quick
|
|
13
|
+
* `todo_write`), so `@ai-sdk/openai-compatible` serializes them as
|
|
14
|
+
* `{content:null, tool_calls:[...]}` with no `reasoning_content` key — and
|
|
15
|
+
* the next request blows up. The earlier "reasoning round-trips natively"
|
|
16
|
+
* conclusion only held for histories where EVERY assistant turn had reasoning.
|
|
17
|
+
*
|
|
18
|
+
* Two mitigations, selected by `MUONROI_DEEPSEEK_DISABLE_THINKING`:
|
|
19
|
+
*
|
|
20
|
+
* - Default (A): keep thinking ON, but backfill `reasoning_content: ""` onto
|
|
21
|
+
* every assistant message in the wire body that is missing it, so the
|
|
22
|
+
* thinking-mode validator always sees the field.
|
|
23
|
+
* - Fallback (B, env=1): disable thinking entirely via
|
|
24
|
+
* `thinking: { type: "disabled" }` (per the DeepSeek thinking_mode guide).
|
|
25
|
+
* Sidesteps the whole class of bug, cuts latency 30-50%, and stops
|
|
26
|
+
* reasoning prose from leaking into JSON outputs — at the cost of reasoning.
|
|
27
|
+
*
|
|
28
|
+
* https://api-docs.deepseek.com/guides/thinking_mode
|
|
29
|
+
*/
|
|
30
|
+
export function shouldDisableThinking() {
|
|
31
|
+
const v = process.env["MUONROI_DEEPSEEK_DISABLE_THINKING"];
|
|
32
|
+
return v === undefined ? false : v === "1" || v.toLowerCase() === "true";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Backfill `reasoning_content: ""` onto any assistant message that lacks a
|
|
36
|
+
* (non-empty/present) one, so SiliconFlow's thinking-mode validator never
|
|
37
|
+
* sees a reasoning-less assistant turn. Assistant turns that already carry a
|
|
38
|
+
* real `reasoning_content` are left untouched.
|
|
39
|
+
*/
|
|
40
|
+
function backfillReasoningContent(messages) {
|
|
41
|
+
let mutated = false;
|
|
42
|
+
const next = messages.map((m) => {
|
|
43
|
+
if (m?.role !== "assistant")
|
|
44
|
+
return m;
|
|
45
|
+
const rc = m.reasoning_content;
|
|
46
|
+
if (typeof rc === "string")
|
|
47
|
+
return m; // already present (incl. "")
|
|
48
|
+
mutated = true;
|
|
49
|
+
return { ...m, reasoning_content: "" };
|
|
50
|
+
});
|
|
51
|
+
return mutated ? next : messages;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The shared `transformRequestBody` for deepseek + siliconflow. Runs on the
|
|
55
|
+
* fully-serialized wire body right before fetch.
|
|
56
|
+
*/
|
|
57
|
+
export function transformThinkingModeBody(body) {
|
|
58
|
+
if (shouldDisableThinking()) {
|
|
59
|
+
// Fallback B: turn thinking off. No reasoning is produced, so there is
|
|
60
|
+
// nothing to backfill.
|
|
61
|
+
return { ...body, thinking: { type: "disabled" } };
|
|
62
|
+
}
|
|
63
|
+
// Default A: keep thinking on, but guarantee every assistant message carries
|
|
64
|
+
// a reasoning_content field so the validator is satisfied.
|
|
65
|
+
const messages = body["messages"];
|
|
66
|
+
if (!Array.isArray(messages))
|
|
67
|
+
return body;
|
|
68
|
+
const patched = backfillReasoningContent(messages);
|
|
69
|
+
if (patched === messages)
|
|
70
|
+
return body;
|
|
71
|
+
return { ...body, messages: patched };
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=thinking-mode.js.map
|
|
@@ -593,58 +593,58 @@ export function createBuiltinTools(bash, mode, opts) {
|
|
|
593
593
|
.join("\n");
|
|
594
594
|
},
|
|
595
595
|
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
596
|
+
}
|
|
597
|
+
// todo_write — Claude-Code-style task list. Each call REPLACES the agent's
|
|
598
|
+
// current todo snapshot; the orchestrator post-processes this tool's args
|
|
599
|
+
// into a task_list_update StreamChunk that the UI renders as a sticky
|
|
600
|
+
// checklist panel. Status flow: pending → in_progress → completed; only
|
|
601
|
+
// ONE item should be in_progress at a time. Use this when the user asks
|
|
602
|
+
// for a multi-step task (≥3 distinct steps) so progress is visible.
|
|
603
|
+
tools.todo_write = dynamicTool({
|
|
604
|
+
description: "Write the full current todo list. Replaces the previous list entirely on every call (no partial updates). Use when a user request resolves into ≥3 discrete steps so the UI can show progress. Mark exactly one item as in_progress at a time. Always emit the FULL list, not just the changed items.",
|
|
605
|
+
inputSchema: jsonSchema({
|
|
606
|
+
type: "object",
|
|
607
|
+
properties: {
|
|
608
|
+
todos: {
|
|
609
|
+
type: "array",
|
|
610
|
+
description: "The full ordered list of todo items. Replaces any prior list. Keep order stable across updates so the UI doesn't reshuffle on every call.",
|
|
611
|
+
items: {
|
|
612
|
+
type: "object",
|
|
613
|
+
properties: {
|
|
614
|
+
id: { type: "string", description: "Stable id across updates (e.g. '1','2', or a slug)." },
|
|
615
|
+
subject: { type: "string", description: "Short imperative title shown in the list." },
|
|
616
|
+
activeForm: {
|
|
617
|
+
type: "string",
|
|
618
|
+
description: "Present-continuous form shown while in_progress (e.g. 'Reading files'). Falls back to subject when absent.",
|
|
619
|
+
},
|
|
620
|
+
status: {
|
|
621
|
+
type: "string",
|
|
622
|
+
enum: ["pending", "in_progress", "completed"],
|
|
623
|
+
description: "Item status. Only ONE item should be in_progress at any time.",
|
|
624
624
|
},
|
|
625
|
-
required: ["id", "subject", "status"],
|
|
626
625
|
},
|
|
626
|
+
required: ["id", "subject", "status"],
|
|
627
627
|
},
|
|
628
628
|
},
|
|
629
|
-
required: ["todos"],
|
|
630
|
-
}),
|
|
631
|
-
execute: async (input) => {
|
|
632
|
-
const todos = Array.isArray(input?.todos)
|
|
633
|
-
? input.todos
|
|
634
|
-
: [];
|
|
635
|
-
const counts = { completed: 0, inProgress: 0, pending: 0, total: todos.length };
|
|
636
|
-
for (const t of todos) {
|
|
637
|
-
if (t.status === "completed")
|
|
638
|
-
counts.completed++;
|
|
639
|
-
else if (t.status === "in_progress")
|
|
640
|
-
counts.inProgress++;
|
|
641
|
-
else
|
|
642
|
-
counts.pending++;
|
|
643
|
-
}
|
|
644
|
-
return `Tracking ${counts.total} todo${counts.total !== 1 ? "s" : ""}: ${counts.completed} done · ${counts.inProgress} in progress · ${counts.pending} queued.`;
|
|
645
629
|
},
|
|
646
|
-
|
|
647
|
-
|
|
630
|
+
required: ["todos"],
|
|
631
|
+
}),
|
|
632
|
+
execute: async (input) => {
|
|
633
|
+
const todos = Array.isArray(input?.todos)
|
|
634
|
+
? input.todos
|
|
635
|
+
: [];
|
|
636
|
+
const counts = { completed: 0, inProgress: 0, pending: 0, total: todos.length };
|
|
637
|
+
for (const t of todos) {
|
|
638
|
+
if (t.status === "completed")
|
|
639
|
+
counts.completed++;
|
|
640
|
+
else if (t.status === "in_progress")
|
|
641
|
+
counts.inProgress++;
|
|
642
|
+
else
|
|
643
|
+
counts.pending++;
|
|
644
|
+
}
|
|
645
|
+
return `Tracking ${counts.total} todo${counts.total !== 1 ? "s" : ""}: ${counts.completed} done · ${counts.inProgress} in progress · ${counts.pending} queued.`;
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
648
|
// Vision-tool gate: drop the 3 vision-proxy tools on turns with no plausible
|
|
649
649
|
// image involvement. Built then deleted (closures are cheap) to avoid
|
|
650
650
|
// re-indenting the tool definitions above. todo_write + core tools untouched.
|
package/dist/src/ui/app.js
CHANGED
|
@@ -58,6 +58,7 @@ import { usePairQuoteBuffer } from "./components/use-pair-quote-buffer.js";
|
|
|
58
58
|
import { useAgentEditor } from "./hooks/use-agent-editor.js";
|
|
59
59
|
import { useMcpEditor } from "./hooks/use-mcp-editor.js";
|
|
60
60
|
import { useModelPicker } from "./hooks/use-model-picker.js";
|
|
61
|
+
import { useSessionPicker } from "./hooks/use-session-picker.js";
|
|
61
62
|
import { useTypeahead } from "./hooks/useTypeahead.js";
|
|
62
63
|
import { Markdown } from "./markdown.js";
|
|
63
64
|
import { buildMcpBrowseRows, McpBrowserModal, McpEditorModal } from "./mcp-modal.js";
|
|
@@ -66,6 +67,7 @@ import { ApiKeyModal } from "./modals/api-key-modal.js";
|
|
|
66
67
|
import { ConnectModal, TelegramPairModal, TelegramTokenModal } from "./modals/connect-modal.js";
|
|
67
68
|
import { ModelPickerModal } from "./modals/model-picker-modal.js";
|
|
68
69
|
import { SandboxPickerModal } from "./modals/sandbox-picker-modal.js";
|
|
70
|
+
import { SessionPickerModal } from "./modals/session-picker-modal.js";
|
|
69
71
|
import { UpdateModal } from "./modals/update-modal.js";
|
|
70
72
|
import { PaymentApprovalPanel, WalletPickerModal } from "./modals/wallet-picker-modal.js";
|
|
71
73
|
import { resolvePickerProviders } from "./picker-providers.js";
|
|
@@ -77,6 +79,7 @@ import { StatusBar } from "./status-bar/index.js";
|
|
|
77
79
|
import { statusBarStore, wireStatusBar } from "./status-bar/store.js";
|
|
78
80
|
import { getCompactTuiSelectionText } from "./terminal-selection-text.js";
|
|
79
81
|
import { dark } from "./theme.js";
|
|
82
|
+
import { relaunchWithSession } from "./utils/relaunch.js";
|
|
80
83
|
import "./slash/route.js";
|
|
81
84
|
import "./slash/optimize.js";
|
|
82
85
|
import "./slash/discuss.js";
|
|
@@ -565,6 +568,7 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
565
568
|
const dismissToast = useCallback(() => setActiveToast(null), []);
|
|
566
569
|
// ─── /Phase 21 toast subscriber ────────────────────────────────────────────
|
|
567
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();
|
|
568
572
|
const modelRef = useRef(model);
|
|
569
573
|
const [providersWithKey, setProvidersWithKey] = useState(() => new Set());
|
|
570
574
|
const refreshProvidersWithKey = useCallback(async () => {
|
|
@@ -3051,6 +3055,27 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3051
3055
|
openSandboxPicker();
|
|
3052
3056
|
return true;
|
|
3053
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
|
+
}
|
|
3054
3079
|
if (c === "/wallet") {
|
|
3055
3080
|
openWalletPicker();
|
|
3056
3081
|
return true;
|
|
@@ -3747,6 +3772,9 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3747
3772
|
model,
|
|
3748
3773
|
messages.length,
|
|
3749
3774
|
messages,
|
|
3775
|
+
setSessionPickerList,
|
|
3776
|
+
setSessionPickerIndex,
|
|
3777
|
+
setShowSessionPicker,
|
|
3750
3778
|
]);
|
|
3751
3779
|
const handleSlashMenuSelect = useCallback((item) => {
|
|
3752
3780
|
setShowSlashMenuSync(false);
|
|
@@ -3921,34 +3949,27 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
3921
3949
|
]);
|
|
3922
3950
|
break;
|
|
3923
3951
|
case "sessions": {
|
|
3924
|
-
//
|
|
3925
|
-
//
|
|
3926
|
-
|
|
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.
|
|
3927
3955
|
try {
|
|
3928
3956
|
const { SessionStore } = require("../storage/sessions.js");
|
|
3929
|
-
const
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
const ts = new Date(s.updatedAt).toLocaleString();
|
|
3934
|
-
const title = s.title?.trim() || "(untitled)";
|
|
3935
|
-
const truncTitle = title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
3936
|
-
return `${String(idx + 1).padStart(2)}. [${s.id}] ${ts} ${s.model}\n ${truncTitle}`;
|
|
3937
|
-
});
|
|
3938
|
-
body = [
|
|
3939
|
-
"Recent sessions in this workspace:",
|
|
3940
|
-
"",
|
|
3941
|
-
...lines,
|
|
3942
|
-
"",
|
|
3943
|
-
"Resume on next launch: muonroi-cli --session <id>",
|
|
3944
|
-
"Or: muonroi-cli --session latest",
|
|
3945
|
-
].join("\n");
|
|
3946
|
-
}
|
|
3957
|
+
const list = new SessionStore(agent.getCwd()).listRecentSessions(20);
|
|
3958
|
+
setSessionPickerList(list);
|
|
3959
|
+
setSessionPickerIndex(0);
|
|
3960
|
+
setShowSessionPicker(true);
|
|
3947
3961
|
}
|
|
3948
3962
|
catch (err) {
|
|
3949
|
-
|
|
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
|
+
]);
|
|
3950
3972
|
}
|
|
3951
|
-
setMessages((p) => [...p, { type: "assistant", content: body, timestamp: new Date() }]);
|
|
3952
3973
|
break;
|
|
3953
3974
|
}
|
|
3954
3975
|
default: {
|
|
@@ -4076,12 +4097,16 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
4076
4097
|
setModelPickerIndex,
|
|
4077
4098
|
setModelSearchQuery,
|
|
4078
4099
|
setShowModelPicker,
|
|
4100
|
+
setSessionPickerList,
|
|
4101
|
+
setSessionPickerIndex,
|
|
4102
|
+
setShowSessionPicker,
|
|
4079
4103
|
]);
|
|
4080
4104
|
const blockPrompt = showConnectModal ||
|
|
4081
4105
|
showTelegramTokenModal ||
|
|
4082
4106
|
showTelegramPairModal ||
|
|
4083
4107
|
showMcpModal ||
|
|
4084
4108
|
showSandboxPicker ||
|
|
4109
|
+
showSessionPicker ||
|
|
4085
4110
|
showWalletPicker ||
|
|
4086
4111
|
!!pendingPaymentApproval ||
|
|
4087
4112
|
showScheduleModal ||
|
|
@@ -5182,6 +5207,43 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
5182
5207
|
}
|
|
5183
5208
|
return;
|
|
5184
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
|
+
}
|
|
5185
5247
|
if (showModelPicker) {
|
|
5186
5248
|
// Sub-modal: BW sync (password + provider picker phases).
|
|
5187
5249
|
if (bwSync) {
|
|
@@ -5806,6 +5868,11 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
5806
5868
|
setShowModelPicker,
|
|
5807
5869
|
setModelPickerIndex,
|
|
5808
5870
|
setModel,
|
|
5871
|
+
showSessionPicker,
|
|
5872
|
+
sessionPickerList,
|
|
5873
|
+
sessionPickerIndex,
|
|
5874
|
+
setShowSessionPicker,
|
|
5875
|
+
setSessionPickerIndex,
|
|
5809
5876
|
]);
|
|
5810
5877
|
useKeyboard(handleKey);
|
|
5811
5878
|
const handlePaste = useCallback((event) => {
|
|
@@ -6040,7 +6107,7 @@ export function App({ agent, startupConfig, initialMessage, onExit }) {
|
|
|
6040
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" &&
|
|
6041
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 })] })] })) : (
|
|
6042
6109
|
/* ── Home ───────────────────────────────────────── */
|
|
6043
|
-
_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() }))] }) }));
|
|
6044
6111
|
}
|
|
6045
6112
|
export { computeMcpRunInfo } from "./components/message-view.js";
|
|
6046
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
|