@qxbyte/muse 0.1.1 → 0.1.3
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/README.md +34 -37
- package/dist/cli.js +2728 -363
- package/dist/cli.js.map +1 -1
- package/dist/index.js +657 -90
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,17 +5,177 @@ import { Command } from "commander";
|
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
|
|
7
7
|
// src/app.tsx
|
|
8
|
-
import { useCallback, useEffect, useMemo as useMemo2, useReducer, useRef, useState as
|
|
9
|
-
import { Box as
|
|
10
|
-
import TextInput from "ink-text-input";
|
|
11
|
-
import { mkdir as mkdir2, readFile as readFile4, writeFile } from "fs/promises";
|
|
12
|
-
import { existsSync as existsSync4 } from "fs";
|
|
13
|
-
import { homedir as homedir7 } from "os";
|
|
14
|
-
import { dirname as dirname3, join as join5 } from "path";
|
|
8
|
+
import { useCallback, useEffect as useEffect5, useMemo as useMemo2, useReducer, useRef, useState as useState8 } from "react";
|
|
9
|
+
import { Box as Box13, Text as Text15, useApp, useInput as useInput5, useStdout as useStdout2 } from "ink";
|
|
15
10
|
|
|
16
|
-
// src/components/
|
|
17
|
-
import {
|
|
11
|
+
// src/components/BgTextInput.tsx
|
|
12
|
+
import { useState, useEffect } from "react";
|
|
13
|
+
import { Text, useInput } from "ink";
|
|
18
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var BLINK_MS = 530;
|
|
16
|
+
var PASTE_CHAR_THRESHOLD = 200;
|
|
17
|
+
function looksLikePaste(input) {
|
|
18
|
+
return input.includes("\n") || input.includes("\r") || input.length > PASTE_CHAR_THRESHOLD;
|
|
19
|
+
}
|
|
20
|
+
function stripBracketedPaste(s) {
|
|
21
|
+
return s.replace(/\x1b\[20[01]~/g, "");
|
|
22
|
+
}
|
|
23
|
+
function normalizeLineEndings(s) {
|
|
24
|
+
return s.replace(/\r\n?/g, "\n");
|
|
25
|
+
}
|
|
26
|
+
function BgTextInput({
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
onSubmit,
|
|
30
|
+
width,
|
|
31
|
+
backgroundColor,
|
|
32
|
+
color,
|
|
33
|
+
isActive = true,
|
|
34
|
+
onPaste,
|
|
35
|
+
placeholder
|
|
36
|
+
}) {
|
|
37
|
+
const [cursor, setCursor] = useState(value.length);
|
|
38
|
+
const [blinkOn, setBlinkOn] = useState(true);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setCursor((c) => Math.min(c, value.length));
|
|
41
|
+
}, [value]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!isActive) return;
|
|
44
|
+
setBlinkOn(true);
|
|
45
|
+
const id = setInterval(() => setBlinkOn((b) => !b), BLINK_MS);
|
|
46
|
+
return () => clearInterval(id);
|
|
47
|
+
}, [isActive, cursor, value]);
|
|
48
|
+
useInput(
|
|
49
|
+
(input, key) => {
|
|
50
|
+
if (key.return) {
|
|
51
|
+
onSubmit?.(value);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (key.backspace || key.delete) {
|
|
55
|
+
if (cursor === 0) return;
|
|
56
|
+
const next = value.slice(0, cursor - 1) + value.slice(cursor);
|
|
57
|
+
onChange(next);
|
|
58
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (key.leftArrow) {
|
|
62
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (key.rightArrow) {
|
|
66
|
+
setCursor((c) => Math.min(value.length, c + 1));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (key.ctrl && input === "a") {
|
|
70
|
+
setCursor(0);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (key.ctrl && input === "e") {
|
|
74
|
+
setCursor(value.length);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (key.ctrl || key.shift || key.tab || key.escape || key.upArrow || key.downArrow || key.meta) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (input && !key.return) {
|
|
81
|
+
const cleaned = normalizeLineEndings(stripBracketedPaste(input));
|
|
82
|
+
if (!cleaned) return;
|
|
83
|
+
const insertion = onPaste && looksLikePaste(cleaned) ? onPaste(cleaned) : cleaned;
|
|
84
|
+
const next = value.slice(0, cursor) + insertion + value.slice(cursor);
|
|
85
|
+
onChange(next);
|
|
86
|
+
setCursor((c) => c + insertion.length);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{ isActive }
|
|
90
|
+
);
|
|
91
|
+
const showCursor = isActive && blinkOn;
|
|
92
|
+
if (value.length === 0 && placeholder) {
|
|
93
|
+
const maxW = Math.max(0, width - 1);
|
|
94
|
+
let truncated = placeholder;
|
|
95
|
+
while (stringWidth(truncated) > maxW && truncated.length > 0) {
|
|
96
|
+
truncated = truncated.slice(0, -1);
|
|
97
|
+
}
|
|
98
|
+
const usedW = 1 + stringWidth(truncated);
|
|
99
|
+
const padLen2 = Math.max(0, width - usedW);
|
|
100
|
+
return /* @__PURE__ */ jsxs(Text, { backgroundColor, color, children: [
|
|
101
|
+
showCursor ? /* @__PURE__ */ jsx(Text, { backgroundColor: "blue", color, dimColor: true, children: " " }) : /* @__PURE__ */ jsx(Text, { backgroundColor, color, children: " " }),
|
|
102
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, dimColor: true, children: truncated }),
|
|
103
|
+
" ".repeat(padLen2)
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
const displayValue = value.replace(/[\n\r]/g, "\u21B5");
|
|
107
|
+
const view = computeViewport(displayValue, cursor, width);
|
|
108
|
+
const at = view.atChar;
|
|
109
|
+
const padLen = Math.max(0, width - view.consumedWidth);
|
|
110
|
+
return /* @__PURE__ */ jsxs(Text, { backgroundColor, color, children: [
|
|
111
|
+
view.before,
|
|
112
|
+
showCursor ? /* @__PURE__ */ jsx(Text, { backgroundColor: "blue", color, dimColor: true, children: at }) : /* @__PURE__ */ jsx(Text, { backgroundColor, color, children: at }),
|
|
113
|
+
view.after,
|
|
114
|
+
" ".repeat(padLen)
|
|
115
|
+
] });
|
|
116
|
+
}
|
|
117
|
+
function charWidth(ch) {
|
|
118
|
+
const cp = ch.codePointAt(0);
|
|
119
|
+
if (cp === void 0) return 0;
|
|
120
|
+
if (cp < 32 || cp === 127) return 0;
|
|
121
|
+
if (cp >= 4352 && cp <= 4447 || // Hangul Jamo
|
|
122
|
+
cp >= 11904 && cp <= 12350 || // CJK Radicals
|
|
123
|
+
cp >= 12353 && cp <= 13311 || // Hiragana / Katakana / CJK Symbols
|
|
124
|
+
cp >= 13312 && cp <= 19903 || // CJK Ext A
|
|
125
|
+
cp >= 19968 && cp <= 40959 || // CJK Unified
|
|
126
|
+
cp >= 40960 && cp <= 42191 || // Yi
|
|
127
|
+
cp >= 44032 && cp <= 55203 || // Hangul Syllables
|
|
128
|
+
cp >= 63744 && cp <= 64255 || // CJK Compat
|
|
129
|
+
cp >= 65072 && cp <= 65103 || // CJK Compat Forms
|
|
130
|
+
cp >= 65280 && cp <= 65376 || // Fullwidth ASCII / Punctuation
|
|
131
|
+
cp >= 65504 && cp <= 65510 || // Fullwidth Sign
|
|
132
|
+
cp >= 131072 && cp <= 196605) {
|
|
133
|
+
return 2;
|
|
134
|
+
}
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
function stringWidth(s) {
|
|
138
|
+
let w = 0;
|
|
139
|
+
for (const ch of s) w += charWidth(ch);
|
|
140
|
+
return w;
|
|
141
|
+
}
|
|
142
|
+
function computeViewport(value, cursor, width) {
|
|
143
|
+
const cursorAtEnd = cursor >= value.length;
|
|
144
|
+
const atChar = cursorAtEnd ? " " : value[cursor] ?? " ";
|
|
145
|
+
const cursorCellW = charWidth(atChar);
|
|
146
|
+
let beforeStart = 0;
|
|
147
|
+
while (true) {
|
|
148
|
+
const before = value.slice(beforeStart, cursor);
|
|
149
|
+
const after = cursorAtEnd ? "" : value.slice(cursor + 1);
|
|
150
|
+
const total = stringWidth(before) + cursorCellW + stringWidth(after);
|
|
151
|
+
if (total <= width) {
|
|
152
|
+
return { before, atChar, after, consumedWidth: total };
|
|
153
|
+
}
|
|
154
|
+
if (beforeStart >= cursor) {
|
|
155
|
+
let after2 = cursorAtEnd ? "" : value.slice(cursor + 1);
|
|
156
|
+
while (after2.length > 0 && stringWidth("") + cursorCellW + stringWidth(after2) > width) {
|
|
157
|
+
after2 = after2.slice(0, -1);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
before: "",
|
|
161
|
+
atChar,
|
|
162
|
+
after: after2,
|
|
163
|
+
consumedWidth: cursorCellW + stringWidth(after2)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
beforeStart++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/app.tsx
|
|
171
|
+
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
172
|
+
import { existsSync as existsSync5 } from "fs";
|
|
173
|
+
import { homedir as homedir8 } from "os";
|
|
174
|
+
import { basename, dirname as dirname3, join as join6 } from "path";
|
|
175
|
+
|
|
176
|
+
// src/components/StartupBanner.tsx
|
|
177
|
+
import { Box, Text as Text2 } from "ink";
|
|
178
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
19
179
|
var LETTERS = {
|
|
20
180
|
M: ["\u2588 \u2588", "\u2588\u2588 \u2588\u2588", "\u2588 \u2588 \u2588", "\u2588 \u2588", "\u2588 \u2588"],
|
|
21
181
|
U: ["\u2588 \u2588", "\u2588 \u2588", "\u2588 \u2588", "\u2588 \u2588", " \u2588\u2588\u2588 "],
|
|
@@ -35,104 +195,610 @@ var LETTER_GAP = 3;
|
|
|
35
195
|
var LOGO_WIDTH = 5 * 4 + LETTER_GAP * 3;
|
|
36
196
|
var GAP_WIDTH = 6;
|
|
37
197
|
function LogoLine({ row }) {
|
|
38
|
-
return /* @__PURE__ */
|
|
39
|
-
/* @__PURE__ */
|
|
40
|
-
/* @__PURE__ */
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
/* @__PURE__ */
|
|
198
|
+
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", children: [
|
|
199
|
+
/* @__PURE__ */ jsx2(Box, { children: /* @__PURE__ */ jsx2(Text2, { color: COLORS.M, children: LETTERS.M[row] }) }),
|
|
200
|
+
/* @__PURE__ */ jsx2(Box, { marginLeft: LETTER_GAP, children: /* @__PURE__ */ jsx2(Text2, { color: COLORS.U, children: LETTERS.U[row] }) }),
|
|
201
|
+
/* @__PURE__ */ jsx2(Box, { marginLeft: LETTER_GAP, children: /* @__PURE__ */ jsx2(Text2, { color: COLORS.S, children: LETTERS.S[row] }) }),
|
|
202
|
+
/* @__PURE__ */ jsx2(Box, { marginLeft: LETTER_GAP, children: /* @__PURE__ */ jsx2(Text2, { color: COLORS.E, children: LETTERS.E[row] }) })
|
|
43
203
|
] });
|
|
44
204
|
}
|
|
45
205
|
function BannerLine({ row, children }) {
|
|
46
|
-
return /* @__PURE__ */
|
|
47
|
-
/* @__PURE__ */
|
|
48
|
-
/* @__PURE__ */
|
|
206
|
+
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", children: [
|
|
207
|
+
/* @__PURE__ */ jsx2(Box, { minWidth: LOGO_WIDTH, children: /* @__PURE__ */ jsx2(LogoLine, { row }) }),
|
|
208
|
+
/* @__PURE__ */ jsx2(Box, { width: GAP_WIDTH }),
|
|
49
209
|
children ?? null
|
|
50
210
|
] });
|
|
51
211
|
}
|
|
52
212
|
function StartupBanner({ version, model, cwd }) {
|
|
53
|
-
return /* @__PURE__ */
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
-
/* @__PURE__ */
|
|
56
|
-
/* @__PURE__ */
|
|
57
|
-
/* @__PURE__ */
|
|
58
|
-
/* @__PURE__ */
|
|
213
|
+
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", paddingY: 0, children: [
|
|
214
|
+
/* @__PURE__ */ jsx2(BannerLine, { row: 0 }),
|
|
215
|
+
/* @__PURE__ */ jsx2(BannerLine, { row: 1, children: /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", children: [
|
|
216
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.asterisk, children: "\u273B" }),
|
|
217
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.text, children: " Welcome to Muse " }),
|
|
218
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.versionAccent, children: [
|
|
59
219
|
"v",
|
|
60
220
|
version
|
|
61
221
|
] })
|
|
62
222
|
] }) }),
|
|
63
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ jsx2(BannerLine, { row: 2, children: /* @__PURE__ */ jsxs2(Text2, { color: COLORS.text, children: [
|
|
64
224
|
"model: ",
|
|
65
225
|
model
|
|
66
226
|
] }) }),
|
|
67
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ jsx2(BannerLine, { row: 3, children: /* @__PURE__ */ jsxs2(Text2, { color: COLORS.text, children: [
|
|
68
228
|
"cwd: ",
|
|
69
229
|
cwd
|
|
70
230
|
] }) }),
|
|
71
|
-
/* @__PURE__ */
|
|
231
|
+
/* @__PURE__ */ jsx2(BannerLine, { row: 4 })
|
|
72
232
|
] });
|
|
73
233
|
}
|
|
74
234
|
function CompactBanner({ version, model, cwd }) {
|
|
75
|
-
return /* @__PURE__ */
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
235
|
+
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", paddingY: 0, children: [
|
|
236
|
+
/* @__PURE__ */ jsxs2(Box, { flexDirection: "row", children: [
|
|
237
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.asterisk, children: "\u273B" }),
|
|
238
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.text, children: " Welcome to Muse " }),
|
|
239
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.versionAccent, children: [
|
|
80
240
|
"v",
|
|
81
241
|
version
|
|
82
242
|
] })
|
|
83
243
|
] }),
|
|
84
|
-
/* @__PURE__ */
|
|
244
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.text, children: [
|
|
85
245
|
"model: ",
|
|
86
246
|
model
|
|
87
247
|
] }),
|
|
88
|
-
/* @__PURE__ */
|
|
248
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.text, children: [
|
|
89
249
|
"cwd: ",
|
|
90
250
|
cwd
|
|
91
251
|
] })
|
|
92
252
|
] });
|
|
93
253
|
}
|
|
94
254
|
function SingleLineBanner({ version, model }) {
|
|
95
|
-
return /* @__PURE__ */
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
/* @__PURE__ */
|
|
255
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
256
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.text, children: "Muse " }),
|
|
257
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.versionAccent, children: [
|
|
98
258
|
"v",
|
|
99
259
|
version
|
|
100
260
|
] }),
|
|
101
|
-
/* @__PURE__ */
|
|
261
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.text, children: [
|
|
102
262
|
" \xB7 ",
|
|
103
263
|
model
|
|
104
264
|
] })
|
|
105
265
|
] });
|
|
106
266
|
}
|
|
107
267
|
function pickBanner(width, props) {
|
|
108
|
-
if (width >= 60) return /* @__PURE__ */
|
|
109
|
-
if (width >= 40) return /* @__PURE__ */
|
|
110
|
-
return /* @__PURE__ */
|
|
268
|
+
if (width >= 60) return /* @__PURE__ */ jsx2(StartupBanner, { ...props });
|
|
269
|
+
if (width >= 40) return /* @__PURE__ */ jsx2(CompactBanner, { ...props });
|
|
270
|
+
return /* @__PURE__ */ jsx2(SingleLineBanner, { version: props.version, model: props.model });
|
|
111
271
|
}
|
|
112
272
|
|
|
113
273
|
// src/components/MessageView.tsx
|
|
114
274
|
import { useMemo } from "react";
|
|
115
|
-
import { Box as Box2, Text as
|
|
275
|
+
import { Box as Box2, Text as Text3, useStdout } from "ink";
|
|
276
|
+
|
|
277
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
278
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
279
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
280
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
281
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
282
|
+
var styles = {
|
|
283
|
+
modifier: {
|
|
284
|
+
reset: [0, 0],
|
|
285
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
286
|
+
bold: [1, 22],
|
|
287
|
+
dim: [2, 22],
|
|
288
|
+
italic: [3, 23],
|
|
289
|
+
underline: [4, 24],
|
|
290
|
+
overline: [53, 55],
|
|
291
|
+
inverse: [7, 27],
|
|
292
|
+
hidden: [8, 28],
|
|
293
|
+
strikethrough: [9, 29]
|
|
294
|
+
},
|
|
295
|
+
color: {
|
|
296
|
+
black: [30, 39],
|
|
297
|
+
red: [31, 39],
|
|
298
|
+
green: [32, 39],
|
|
299
|
+
yellow: [33, 39],
|
|
300
|
+
blue: [34, 39],
|
|
301
|
+
magenta: [35, 39],
|
|
302
|
+
cyan: [36, 39],
|
|
303
|
+
white: [37, 39],
|
|
304
|
+
// Bright color
|
|
305
|
+
blackBright: [90, 39],
|
|
306
|
+
gray: [90, 39],
|
|
307
|
+
// Alias of `blackBright`
|
|
308
|
+
grey: [90, 39],
|
|
309
|
+
// Alias of `blackBright`
|
|
310
|
+
redBright: [91, 39],
|
|
311
|
+
greenBright: [92, 39],
|
|
312
|
+
yellowBright: [93, 39],
|
|
313
|
+
blueBright: [94, 39],
|
|
314
|
+
magentaBright: [95, 39],
|
|
315
|
+
cyanBright: [96, 39],
|
|
316
|
+
whiteBright: [97, 39]
|
|
317
|
+
},
|
|
318
|
+
bgColor: {
|
|
319
|
+
bgBlack: [40, 49],
|
|
320
|
+
bgRed: [41, 49],
|
|
321
|
+
bgGreen: [42, 49],
|
|
322
|
+
bgYellow: [43, 49],
|
|
323
|
+
bgBlue: [44, 49],
|
|
324
|
+
bgMagenta: [45, 49],
|
|
325
|
+
bgCyan: [46, 49],
|
|
326
|
+
bgWhite: [47, 49],
|
|
327
|
+
// Bright color
|
|
328
|
+
bgBlackBright: [100, 49],
|
|
329
|
+
bgGray: [100, 49],
|
|
330
|
+
// Alias of `bgBlackBright`
|
|
331
|
+
bgGrey: [100, 49],
|
|
332
|
+
// Alias of `bgBlackBright`
|
|
333
|
+
bgRedBright: [101, 49],
|
|
334
|
+
bgGreenBright: [102, 49],
|
|
335
|
+
bgYellowBright: [103, 49],
|
|
336
|
+
bgBlueBright: [104, 49],
|
|
337
|
+
bgMagentaBright: [105, 49],
|
|
338
|
+
bgCyanBright: [106, 49],
|
|
339
|
+
bgWhiteBright: [107, 49]
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
343
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
344
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
345
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
346
|
+
function assembleStyles() {
|
|
347
|
+
const codes = /* @__PURE__ */ new Map();
|
|
348
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
349
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
350
|
+
styles[styleName] = {
|
|
351
|
+
open: `\x1B[${style[0]}m`,
|
|
352
|
+
close: `\x1B[${style[1]}m`
|
|
353
|
+
};
|
|
354
|
+
group[styleName] = styles[styleName];
|
|
355
|
+
codes.set(style[0], style[1]);
|
|
356
|
+
}
|
|
357
|
+
Object.defineProperty(styles, groupName, {
|
|
358
|
+
value: group,
|
|
359
|
+
enumerable: false
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
Object.defineProperty(styles, "codes", {
|
|
363
|
+
value: codes,
|
|
364
|
+
enumerable: false
|
|
365
|
+
});
|
|
366
|
+
styles.color.close = "\x1B[39m";
|
|
367
|
+
styles.bgColor.close = "\x1B[49m";
|
|
368
|
+
styles.color.ansi = wrapAnsi16();
|
|
369
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
370
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
371
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
372
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
373
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
374
|
+
Object.defineProperties(styles, {
|
|
375
|
+
rgbToAnsi256: {
|
|
376
|
+
value(red, green, blue) {
|
|
377
|
+
if (red === green && green === blue) {
|
|
378
|
+
if (red < 8) {
|
|
379
|
+
return 16;
|
|
380
|
+
}
|
|
381
|
+
if (red > 248) {
|
|
382
|
+
return 231;
|
|
383
|
+
}
|
|
384
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
385
|
+
}
|
|
386
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
387
|
+
},
|
|
388
|
+
enumerable: false
|
|
389
|
+
},
|
|
390
|
+
hexToRgb: {
|
|
391
|
+
value(hex) {
|
|
392
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
393
|
+
if (!matches) {
|
|
394
|
+
return [0, 0, 0];
|
|
395
|
+
}
|
|
396
|
+
let [colorString] = matches;
|
|
397
|
+
if (colorString.length === 3) {
|
|
398
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
399
|
+
}
|
|
400
|
+
const integer = Number.parseInt(colorString, 16);
|
|
401
|
+
return [
|
|
402
|
+
/* eslint-disable no-bitwise */
|
|
403
|
+
integer >> 16 & 255,
|
|
404
|
+
integer >> 8 & 255,
|
|
405
|
+
integer & 255
|
|
406
|
+
/* eslint-enable no-bitwise */
|
|
407
|
+
];
|
|
408
|
+
},
|
|
409
|
+
enumerable: false
|
|
410
|
+
},
|
|
411
|
+
hexToAnsi256: {
|
|
412
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
413
|
+
enumerable: false
|
|
414
|
+
},
|
|
415
|
+
ansi256ToAnsi: {
|
|
416
|
+
value(code) {
|
|
417
|
+
if (code < 8) {
|
|
418
|
+
return 30 + code;
|
|
419
|
+
}
|
|
420
|
+
if (code < 16) {
|
|
421
|
+
return 90 + (code - 8);
|
|
422
|
+
}
|
|
423
|
+
let red;
|
|
424
|
+
let green;
|
|
425
|
+
let blue;
|
|
426
|
+
if (code >= 232) {
|
|
427
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
428
|
+
green = red;
|
|
429
|
+
blue = red;
|
|
430
|
+
} else {
|
|
431
|
+
code -= 16;
|
|
432
|
+
const remainder = code % 36;
|
|
433
|
+
red = Math.floor(code / 36) / 5;
|
|
434
|
+
green = Math.floor(remainder / 6) / 5;
|
|
435
|
+
blue = remainder % 6 / 5;
|
|
436
|
+
}
|
|
437
|
+
const value = Math.max(red, green, blue) * 2;
|
|
438
|
+
if (value === 0) {
|
|
439
|
+
return 30;
|
|
440
|
+
}
|
|
441
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
442
|
+
if (value === 2) {
|
|
443
|
+
result += 60;
|
|
444
|
+
}
|
|
445
|
+
return result;
|
|
446
|
+
},
|
|
447
|
+
enumerable: false
|
|
448
|
+
},
|
|
449
|
+
rgbToAnsi: {
|
|
450
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
451
|
+
enumerable: false
|
|
452
|
+
},
|
|
453
|
+
hexToAnsi: {
|
|
454
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
455
|
+
enumerable: false
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
return styles;
|
|
459
|
+
}
|
|
460
|
+
var ansiStyles = assembleStyles();
|
|
461
|
+
var ansi_styles_default = ansiStyles;
|
|
462
|
+
|
|
463
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
464
|
+
import process2 from "process";
|
|
465
|
+
import os from "os";
|
|
466
|
+
import tty from "tty";
|
|
467
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
468
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
469
|
+
const position = argv.indexOf(prefix + flag);
|
|
470
|
+
const terminatorPosition = argv.indexOf("--");
|
|
471
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
472
|
+
}
|
|
473
|
+
var { env } = process2;
|
|
474
|
+
var flagForceColor;
|
|
475
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
476
|
+
flagForceColor = 0;
|
|
477
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
478
|
+
flagForceColor = 1;
|
|
479
|
+
}
|
|
480
|
+
function envForceColor() {
|
|
481
|
+
if ("FORCE_COLOR" in env) {
|
|
482
|
+
if (env.FORCE_COLOR === "true") {
|
|
483
|
+
return 1;
|
|
484
|
+
}
|
|
485
|
+
if (env.FORCE_COLOR === "false") {
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function translateLevel(level) {
|
|
492
|
+
if (level === 0) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
level,
|
|
497
|
+
hasBasic: true,
|
|
498
|
+
has256: level >= 2,
|
|
499
|
+
has16m: level >= 3
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
503
|
+
const noFlagForceColor = envForceColor();
|
|
504
|
+
if (noFlagForceColor !== void 0) {
|
|
505
|
+
flagForceColor = noFlagForceColor;
|
|
506
|
+
}
|
|
507
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
508
|
+
if (forceColor === 0) {
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
511
|
+
if (sniffFlags) {
|
|
512
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
513
|
+
return 3;
|
|
514
|
+
}
|
|
515
|
+
if (hasFlag("color=256")) {
|
|
516
|
+
return 2;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
522
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
const min = forceColor || 0;
|
|
526
|
+
if (env.TERM === "dumb") {
|
|
527
|
+
return min;
|
|
528
|
+
}
|
|
529
|
+
if (process2.platform === "win32") {
|
|
530
|
+
const osRelease = os.release().split(".");
|
|
531
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
532
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
533
|
+
}
|
|
534
|
+
return 1;
|
|
535
|
+
}
|
|
536
|
+
if ("CI" in env) {
|
|
537
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
538
|
+
return 3;
|
|
539
|
+
}
|
|
540
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
541
|
+
return 1;
|
|
542
|
+
}
|
|
543
|
+
return min;
|
|
544
|
+
}
|
|
545
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
546
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
547
|
+
}
|
|
548
|
+
if (env.COLORTERM === "truecolor") {
|
|
549
|
+
return 3;
|
|
550
|
+
}
|
|
551
|
+
if (env.TERM === "xterm-kitty") {
|
|
552
|
+
return 3;
|
|
553
|
+
}
|
|
554
|
+
if (env.TERM === "xterm-ghostty") {
|
|
555
|
+
return 3;
|
|
556
|
+
}
|
|
557
|
+
if (env.TERM === "wezterm") {
|
|
558
|
+
return 3;
|
|
559
|
+
}
|
|
560
|
+
if ("TERM_PROGRAM" in env) {
|
|
561
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
562
|
+
switch (env.TERM_PROGRAM) {
|
|
563
|
+
case "iTerm.app": {
|
|
564
|
+
return version >= 3 ? 3 : 2;
|
|
565
|
+
}
|
|
566
|
+
case "Apple_Terminal": {
|
|
567
|
+
return 2;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
572
|
+
return 2;
|
|
573
|
+
}
|
|
574
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
575
|
+
return 1;
|
|
576
|
+
}
|
|
577
|
+
if ("COLORTERM" in env) {
|
|
578
|
+
return 1;
|
|
579
|
+
}
|
|
580
|
+
return min;
|
|
581
|
+
}
|
|
582
|
+
function createSupportsColor(stream, options = {}) {
|
|
583
|
+
const level = _supportsColor(stream, {
|
|
584
|
+
streamIsTTY: stream && stream.isTTY,
|
|
585
|
+
...options
|
|
586
|
+
});
|
|
587
|
+
return translateLevel(level);
|
|
588
|
+
}
|
|
589
|
+
var supportsColor = {
|
|
590
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
591
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
592
|
+
};
|
|
593
|
+
var supports_color_default = supportsColor;
|
|
594
|
+
|
|
595
|
+
// node_modules/chalk/source/utilities.js
|
|
596
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
597
|
+
let index = string.indexOf(substring);
|
|
598
|
+
if (index === -1) {
|
|
599
|
+
return string;
|
|
600
|
+
}
|
|
601
|
+
const substringLength = substring.length;
|
|
602
|
+
let endIndex = 0;
|
|
603
|
+
let returnValue = "";
|
|
604
|
+
do {
|
|
605
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
606
|
+
endIndex = index + substringLength;
|
|
607
|
+
index = string.indexOf(substring, endIndex);
|
|
608
|
+
} while (index !== -1);
|
|
609
|
+
returnValue += string.slice(endIndex);
|
|
610
|
+
return returnValue;
|
|
611
|
+
}
|
|
612
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
613
|
+
let endIndex = 0;
|
|
614
|
+
let returnValue = "";
|
|
615
|
+
do {
|
|
616
|
+
const gotCR = string[index - 1] === "\r";
|
|
617
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
618
|
+
endIndex = index + 1;
|
|
619
|
+
index = string.indexOf("\n", endIndex);
|
|
620
|
+
} while (index !== -1);
|
|
621
|
+
returnValue += string.slice(endIndex);
|
|
622
|
+
return returnValue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// node_modules/chalk/source/index.js
|
|
626
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
627
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
628
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
629
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
630
|
+
var levelMapping = [
|
|
631
|
+
"ansi",
|
|
632
|
+
"ansi",
|
|
633
|
+
"ansi256",
|
|
634
|
+
"ansi16m"
|
|
635
|
+
];
|
|
636
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
637
|
+
var applyOptions = (object, options = {}) => {
|
|
638
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
639
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
640
|
+
}
|
|
641
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
642
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
643
|
+
};
|
|
644
|
+
var chalkFactory = (options) => {
|
|
645
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
646
|
+
applyOptions(chalk2, options);
|
|
647
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
648
|
+
return chalk2;
|
|
649
|
+
};
|
|
650
|
+
function createChalk(options) {
|
|
651
|
+
return chalkFactory(options);
|
|
652
|
+
}
|
|
653
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
654
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
655
|
+
styles2[styleName] = {
|
|
656
|
+
get() {
|
|
657
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
658
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
659
|
+
return builder;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
styles2.visible = {
|
|
664
|
+
get() {
|
|
665
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
666
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
667
|
+
return builder;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
671
|
+
if (model === "rgb") {
|
|
672
|
+
if (level === "ansi16m") {
|
|
673
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
674
|
+
}
|
|
675
|
+
if (level === "ansi256") {
|
|
676
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
677
|
+
}
|
|
678
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
679
|
+
}
|
|
680
|
+
if (model === "hex") {
|
|
681
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
682
|
+
}
|
|
683
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
684
|
+
};
|
|
685
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
686
|
+
for (const model of usedModels) {
|
|
687
|
+
styles2[model] = {
|
|
688
|
+
get() {
|
|
689
|
+
const { level } = this;
|
|
690
|
+
return function(...arguments_) {
|
|
691
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
692
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
697
|
+
styles2[bgModel] = {
|
|
698
|
+
get() {
|
|
699
|
+
const { level } = this;
|
|
700
|
+
return function(...arguments_) {
|
|
701
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
702
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
var proto = Object.defineProperties(() => {
|
|
708
|
+
}, {
|
|
709
|
+
...styles2,
|
|
710
|
+
level: {
|
|
711
|
+
enumerable: true,
|
|
712
|
+
get() {
|
|
713
|
+
return this[GENERATOR].level;
|
|
714
|
+
},
|
|
715
|
+
set(level) {
|
|
716
|
+
this[GENERATOR].level = level;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
var createStyler = (open, close, parent) => {
|
|
721
|
+
let openAll;
|
|
722
|
+
let closeAll;
|
|
723
|
+
if (parent === void 0) {
|
|
724
|
+
openAll = open;
|
|
725
|
+
closeAll = close;
|
|
726
|
+
} else {
|
|
727
|
+
openAll = parent.openAll + open;
|
|
728
|
+
closeAll = close + parent.closeAll;
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
open,
|
|
732
|
+
close,
|
|
733
|
+
openAll,
|
|
734
|
+
closeAll,
|
|
735
|
+
parent
|
|
736
|
+
};
|
|
737
|
+
};
|
|
738
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
739
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
740
|
+
Object.setPrototypeOf(builder, proto);
|
|
741
|
+
builder[GENERATOR] = self;
|
|
742
|
+
builder[STYLER] = _styler;
|
|
743
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
744
|
+
return builder;
|
|
745
|
+
};
|
|
746
|
+
var applyStyle = (self, string) => {
|
|
747
|
+
if (self.level <= 0 || !string) {
|
|
748
|
+
return self[IS_EMPTY] ? "" : string;
|
|
749
|
+
}
|
|
750
|
+
let styler = self[STYLER];
|
|
751
|
+
if (styler === void 0) {
|
|
752
|
+
return string;
|
|
753
|
+
}
|
|
754
|
+
const { openAll, closeAll } = styler;
|
|
755
|
+
if (string.includes("\x1B")) {
|
|
756
|
+
while (styler !== void 0) {
|
|
757
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
758
|
+
styler = styler.parent;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const lfIndex = string.indexOf("\n");
|
|
762
|
+
if (lfIndex !== -1) {
|
|
763
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
764
|
+
}
|
|
765
|
+
return openAll + string + closeAll;
|
|
766
|
+
};
|
|
767
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
768
|
+
var chalk = createChalk();
|
|
769
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
770
|
+
var source_default = chalk;
|
|
771
|
+
|
|
772
|
+
// src/components/MessageView.tsx
|
|
116
773
|
import { marked } from "marked";
|
|
117
774
|
import { markedTerminal } from "marked-terminal";
|
|
118
|
-
import { jsx as
|
|
775
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
776
|
+
if (source_default.level === 0) source_default.level = 3;
|
|
119
777
|
marked.use(markedTerminal());
|
|
120
778
|
function renderMarkdown(text) {
|
|
121
779
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
780
|
+
let out = marked.parse(text);
|
|
781
|
+
out = out.replace(/\n+$/, "");
|
|
782
|
+
out = out.replace(/\*\*([^\n*]+?)\*\*/g, (_, body) => `\x1B[1m${body}\x1B[22m`);
|
|
783
|
+
out = out.replace(/(?<![*\\\x1b])\*([^\n*]+?)\*(?!\*)/g, (_, body) => `\x1B[3m${body}\x1B[23m`);
|
|
784
|
+
out = out.replace(/\x1b\[0m/g, "");
|
|
785
|
+
return out;
|
|
124
786
|
} catch {
|
|
125
787
|
return text;
|
|
126
788
|
}
|
|
127
789
|
}
|
|
128
|
-
function MessageView({
|
|
790
|
+
function MessageView({
|
|
791
|
+
message,
|
|
792
|
+
resultsByCallId
|
|
793
|
+
}) {
|
|
129
794
|
switch (message.role) {
|
|
130
795
|
case "user":
|
|
131
|
-
return /* @__PURE__ */
|
|
796
|
+
return /* @__PURE__ */ jsx3(UserMessage, { content: typeof message.content === "string" ? message.content : flattenText(message.content) });
|
|
132
797
|
case "assistant":
|
|
133
|
-
return /* @__PURE__ */
|
|
798
|
+
return /* @__PURE__ */ jsx3(AssistantMessage, { content: message.content, resultsByCallId });
|
|
134
799
|
case "tool":
|
|
135
|
-
|
|
800
|
+
if (message.toolName === "TodoWrite") return null;
|
|
801
|
+
return /* @__PURE__ */ jsx3(ToolResultTree, { result: message, standalone: true });
|
|
136
802
|
case "system":
|
|
137
803
|
return null;
|
|
138
804
|
}
|
|
@@ -140,47 +806,174 @@ function MessageView({ message }) {
|
|
|
140
806
|
function flattenText(parts) {
|
|
141
807
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
142
808
|
}
|
|
809
|
+
var DOT = "\u23FA";
|
|
810
|
+
var USER_BG = "#262626";
|
|
143
811
|
function UserMessage({ content }) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
812
|
+
const { stdout } = useStdout();
|
|
813
|
+
const termWidth = stdout?.columns ?? 80;
|
|
814
|
+
const bandWidth = Math.max(1, termWidth - 1);
|
|
815
|
+
const PREFIX = " \u203A ";
|
|
816
|
+
const PREFIX_W = 3;
|
|
817
|
+
const rendered = useMemo(() => renderMarkdown(content), [content]);
|
|
818
|
+
const lines = rendered.split("\n");
|
|
819
|
+
const bg = source_default.bgHex(USER_BG);
|
|
820
|
+
const prefixStyle = source_default.gray.bold;
|
|
821
|
+
const padRow = bg(" ".repeat(bandWidth));
|
|
822
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
823
|
+
/* @__PURE__ */ jsx3(Text3, { children: padRow }),
|
|
824
|
+
lines.map((line, i) => {
|
|
825
|
+
const visible = stringWidth(stripAnsi(line));
|
|
826
|
+
const padLen = Math.max(0, bandWidth - PREFIX_W - visible);
|
|
827
|
+
const prefix = i === 0 ? PREFIX : " ";
|
|
828
|
+
const fullLine = bg(prefixStyle(prefix) + line + " ".repeat(padLen));
|
|
829
|
+
return /* @__PURE__ */ jsx3(Text3, { children: fullLine }, i);
|
|
830
|
+
}),
|
|
831
|
+
/* @__PURE__ */ jsx3(Text3, { children: padRow })
|
|
147
832
|
] });
|
|
148
833
|
}
|
|
149
|
-
|
|
150
|
-
|
|
834
|
+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
835
|
+
function stripAnsi(s) {
|
|
836
|
+
return s.replace(ANSI_RE, "");
|
|
837
|
+
}
|
|
838
|
+
function AssistantMessage({
|
|
839
|
+
content,
|
|
840
|
+
resultsByCallId
|
|
841
|
+
}) {
|
|
842
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginTop: 1, children: content.map((part, i) => {
|
|
151
843
|
if (part.type === "text") {
|
|
152
|
-
return /* @__PURE__ */
|
|
844
|
+
return /* @__PURE__ */ jsx3(AssistantTextPart, { text: part.text }, i);
|
|
153
845
|
}
|
|
154
846
|
if (part.type === "tool_use") {
|
|
155
|
-
|
|
847
|
+
if (part.name === "TodoWrite") {
|
|
848
|
+
return /* @__PURE__ */ jsx3(TodoList, { todos: extractTodos(part.args) }, i);
|
|
849
|
+
}
|
|
850
|
+
const result = resultsByCallId?.get(part.id);
|
|
851
|
+
return /* @__PURE__ */ jsx3(ToolCallBlock, { name: part.name, args: part.args, result }, i);
|
|
156
852
|
}
|
|
157
853
|
return null;
|
|
158
854
|
}) });
|
|
159
855
|
}
|
|
160
856
|
function AssistantTextPart({ text }) {
|
|
161
857
|
const rendered = useMemo(() => renderMarkdown(text), [text]);
|
|
162
|
-
return /* @__PURE__ */
|
|
858
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", children: [
|
|
859
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
860
|
+
DOT,
|
|
861
|
+
" "
|
|
862
|
+
] }),
|
|
863
|
+
/* @__PURE__ */ jsx3(Box2, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx3(Text3, { children: rendered }) })
|
|
864
|
+
] });
|
|
163
865
|
}
|
|
164
866
|
function ToolCallLine({ name, args }) {
|
|
165
867
|
const argSummary = formatArgs(args);
|
|
166
|
-
return /* @__PURE__ */
|
|
167
|
-
/* @__PURE__ */
|
|
168
|
-
|
|
169
|
-
|
|
868
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", children: [
|
|
869
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
|
|
870
|
+
DOT,
|
|
871
|
+
" "
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ jsx3(Text3, { color: "yellow", bold: true, children: name }),
|
|
874
|
+
/* @__PURE__ */ jsx3(Box2, { flexGrow: 1, minWidth: 0, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, wrap: "truncate-end", children: [
|
|
170
875
|
"(",
|
|
171
876
|
argSummary,
|
|
172
877
|
")"
|
|
173
|
-
] })
|
|
878
|
+
] }) })
|
|
879
|
+
] });
|
|
880
|
+
}
|
|
881
|
+
function extractTodos(args) {
|
|
882
|
+
if (typeof args !== "object" || args === null) return [];
|
|
883
|
+
const todos = args.todos;
|
|
884
|
+
return Array.isArray(todos) ? todos : [];
|
|
885
|
+
}
|
|
886
|
+
function TodoList({ todos }) {
|
|
887
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
888
|
+
/* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", children: [
|
|
889
|
+
/* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u2192 " }),
|
|
890
|
+
/* @__PURE__ */ jsx3(Text3, { color: "yellow", bold: true, children: "Todos" })
|
|
891
|
+
] }),
|
|
892
|
+
todos.map((todo, i) => /* @__PURE__ */ jsx3(TodoRow, { todo }, i))
|
|
893
|
+
] });
|
|
894
|
+
}
|
|
895
|
+
function TodoRow({ todo }) {
|
|
896
|
+
const label = todo.status === "in_progress" && todo.activeForm ? todo.activeForm : todo.content;
|
|
897
|
+
switch (todo.status) {
|
|
898
|
+
case "completed":
|
|
899
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", marginLeft: 2, children: [
|
|
900
|
+
/* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2612 " }),
|
|
901
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, strikethrough: true, children: label })
|
|
902
|
+
] });
|
|
903
|
+
case "in_progress":
|
|
904
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", marginLeft: 2, children: [
|
|
905
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "\u2610 " }),
|
|
906
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: label })
|
|
907
|
+
] });
|
|
908
|
+
default:
|
|
909
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", marginLeft: 2, children: [
|
|
910
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2610 " }),
|
|
911
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: label })
|
|
912
|
+
] });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function ToolCallBlock({
|
|
916
|
+
name,
|
|
917
|
+
args,
|
|
918
|
+
result
|
|
919
|
+
}) {
|
|
920
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
|
|
921
|
+
/* @__PURE__ */ jsx3(ToolCallLine, { name, args }),
|
|
922
|
+
result && /* @__PURE__ */ jsx3(ToolResultTree, { result })
|
|
174
923
|
] });
|
|
175
924
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
925
|
+
var MAX_RESULT_LINES = 5;
|
|
926
|
+
function ToolResultTree({ result, standalone = false }) {
|
|
927
|
+
const isError = result.isError ?? false;
|
|
928
|
+
const effective = result.kind ?? (isError ? "error" : "success");
|
|
929
|
+
const dotColor = effective === "error" ? "red" : effective === "warn" ? "yellowBright" : "green";
|
|
930
|
+
const cleaned = stripWrapperTags(result.content);
|
|
931
|
+
const rawLines = cleaned.split("\n");
|
|
932
|
+
while (rawLines.length > 0 && rawLines[rawLines.length - 1].trim() === "") rawLines.pop();
|
|
933
|
+
while (rawLines.length > 0 && rawLines[0].trim() === "") rawLines.shift();
|
|
934
|
+
let displayLines;
|
|
935
|
+
let omitted = 0;
|
|
936
|
+
if (result.summary) {
|
|
937
|
+
const extra = rawLines.length > 1 ? ` (+${rawLines.length - 1} lines)` : "";
|
|
938
|
+
displayLines = [result.summary + extra];
|
|
939
|
+
} else if (rawLines.length === 0) {
|
|
940
|
+
displayLines = ["(no output)"];
|
|
941
|
+
} else if (rawLines.length <= MAX_RESULT_LINES) {
|
|
942
|
+
displayLines = rawLines;
|
|
943
|
+
} else {
|
|
944
|
+
displayLines = rawLines.slice(0, MAX_RESULT_LINES);
|
|
945
|
+
omitted = rawLines.length - MAX_RESULT_LINES;
|
|
946
|
+
}
|
|
947
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", marginLeft: 2, marginTop: standalone ? 1 : 0, children: [
|
|
948
|
+
displayLines.map((line, i) => /* @__PURE__ */ jsxs3(Box2, { flexDirection: "row", children: [
|
|
949
|
+
/* @__PURE__ */ jsx3(Text3, { color: i === 0 ? dotColor : void 0, children: i === 0 ? "\u2514 " : " " }),
|
|
950
|
+
/* @__PURE__ */ jsx3(Box2, { flexGrow: 1, minWidth: 0, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: line || " " }) })
|
|
951
|
+
] }, i)),
|
|
952
|
+
omitted > 0 && /* @__PURE__ */ jsx3(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `(+${omitted} more lines)` }) }),
|
|
953
|
+
result.diff && /* @__PURE__ */ jsx3(DiffBlock, { diff: result.diff })
|
|
182
954
|
] });
|
|
183
955
|
}
|
|
956
|
+
function stripWrapperTags(content) {
|
|
957
|
+
return content.split("\n").filter((l) => !/^<\/?(stdout|stderr|timeout|exit_code)>\s*$/.test(l.trim())).join("\n");
|
|
958
|
+
}
|
|
959
|
+
function DiffBlock({ diff }) {
|
|
960
|
+
const lines = diff.split("\n");
|
|
961
|
+
const start = lines.findIndex((l) => l.startsWith("@@"));
|
|
962
|
+
const rendered = start >= 0 ? lines.slice(start) : lines;
|
|
963
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: rendered.map((line, i) => {
|
|
964
|
+
let color;
|
|
965
|
+
let dim = false;
|
|
966
|
+
if (line.startsWith("+")) color = "green";
|
|
967
|
+
else if (line.startsWith("-")) color = "red";
|
|
968
|
+
else if (line.startsWith("@@")) {
|
|
969
|
+
color = "cyan";
|
|
970
|
+
dim = true;
|
|
971
|
+
} else {
|
|
972
|
+
dim = true;
|
|
973
|
+
}
|
|
974
|
+
return /* @__PURE__ */ jsx3(Text3, { color, dimColor: dim, children: line || " " }, i);
|
|
975
|
+
}) });
|
|
976
|
+
}
|
|
184
977
|
function formatArgs(args) {
|
|
185
978
|
if (typeof args !== "object" || args === null) return String(args);
|
|
186
979
|
const entries = Object.entries(args);
|
|
@@ -198,34 +991,86 @@ function formatArgs(args) {
|
|
|
198
991
|
}
|
|
199
992
|
|
|
200
993
|
// src/components/PermissionPrompt.tsx
|
|
201
|
-
import {
|
|
202
|
-
import {
|
|
994
|
+
import { useState as useState2 } from "react";
|
|
995
|
+
import { Box as Box3, Text as Text4, useInput as useInput2 } from "ink";
|
|
996
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
997
|
+
var OPTIONS = [
|
|
998
|
+
{ decision: "yes", labelKey: "yes", shortcut: "y" },
|
|
999
|
+
{ decision: "session_allow", labelKey: "session", shortcut: "s" },
|
|
1000
|
+
{ decision: "no", labelKey: "no", shortcut: "n" }
|
|
1001
|
+
];
|
|
203
1002
|
function PermissionPrompt({ request }) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
1003
|
+
const [index, setIndex] = useState2(0);
|
|
1004
|
+
useInput2((input, key) => {
|
|
1005
|
+
if (key.upArrow) {
|
|
1006
|
+
setIndex((i) => (i - 1 + OPTIONS.length) % OPTIONS.length);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (key.downArrow) {
|
|
1010
|
+
setIndex((i) => (i + 1) % OPTIONS.length);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (key.return) {
|
|
1014
|
+
request.resolve(OPTIONS[index].decision);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (key.escape) {
|
|
1018
|
+
request.resolve("no");
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const lower = input?.toLowerCase?.();
|
|
1022
|
+
for (let i = 0; i < OPTIONS.length; i++) {
|
|
1023
|
+
const o = OPTIONS[i];
|
|
1024
|
+
if (lower === o.shortcut || input === String(i + 1)) {
|
|
1025
|
+
request.resolve(o.decision);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
209
1028
|
}
|
|
210
1029
|
});
|
|
211
|
-
return /* @__PURE__ */
|
|
212
|
-
/* @__PURE__ */
|
|
1030
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", marginY: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1031
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "yellow", bold: true, children: [
|
|
213
1032
|
"\u23F5 Approve ",
|
|
214
1033
|
request.toolName,
|
|
215
1034
|
"?"
|
|
216
1035
|
] }),
|
|
217
|
-
/* @__PURE__ */
|
|
218
|
-
/* @__PURE__ */
|
|
1036
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: request.summary }),
|
|
1037
|
+
/* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginTop: 1, children: OPTIONS.map((o, i) => {
|
|
1038
|
+
const focused = i === index;
|
|
1039
|
+
const label = labelFor(o.labelKey, request.toolName);
|
|
1040
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: focused ? "cyan" : void 0, bold: focused, children: [
|
|
1041
|
+
focused ? "\u203A " : " ",
|
|
1042
|
+
i + 1,
|
|
1043
|
+
". ",
|
|
1044
|
+
label,
|
|
1045
|
+
" ",
|
|
1046
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1047
|
+
"(",
|
|
1048
|
+
o.shortcut,
|
|
1049
|
+
")"
|
|
1050
|
+
] })
|
|
1051
|
+
] }, o.decision);
|
|
1052
|
+
}) }),
|
|
1053
|
+
/* @__PURE__ */ jsx4(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter confirm \xB7 y/s/n shortcut \xB7 Esc=no" }) })
|
|
219
1054
|
] });
|
|
220
1055
|
}
|
|
1056
|
+
function labelFor(key, toolName) {
|
|
1057
|
+
switch (key) {
|
|
1058
|
+
case "yes":
|
|
1059
|
+
return "Yes";
|
|
1060
|
+
case "session":
|
|
1061
|
+
return `Yes, allow ${toolName} for the rest of this session`;
|
|
1062
|
+
case "no":
|
|
1063
|
+
return "No";
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
221
1066
|
|
|
222
1067
|
// src/components/ModelSelector.tsx
|
|
223
|
-
import { Box as Box5, Text as
|
|
1068
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
224
1069
|
|
|
225
1070
|
// src/components/Selector.tsx
|
|
226
|
-
import { useState } from "react";
|
|
227
|
-
import { Box as Box4, Text as
|
|
228
|
-
import { jsx as
|
|
1071
|
+
import { useState as useState3 } from "react";
|
|
1072
|
+
import { Box as Box4, Text as Text5, useInput as useInput3 } from "ink";
|
|
1073
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
229
1074
|
var POINTER_COLOR = "#A855F7";
|
|
230
1075
|
function Selector({
|
|
231
1076
|
items,
|
|
@@ -238,8 +1083,8 @@ function Selector({
|
|
|
238
1083
|
onCancel
|
|
239
1084
|
}) {
|
|
240
1085
|
const safeInitial = Math.max(0, Math.min(initialIndex, items.length - 1));
|
|
241
|
-
const [index, setIndex] =
|
|
242
|
-
|
|
1086
|
+
const [index, setIndex] = useState3(safeInitial);
|
|
1087
|
+
useInput3((_, key) => {
|
|
243
1088
|
if (key.upArrow) {
|
|
244
1089
|
setIndex((i) => Math.max(0, i - 1));
|
|
245
1090
|
} else if (key.downArrow) {
|
|
@@ -255,7 +1100,7 @@ function Selector({
|
|
|
255
1100
|
const start = Math.max(0, Math.min(index - Math.floor(window / 2), len - window));
|
|
256
1101
|
const end = Math.min(len, start + window);
|
|
257
1102
|
const visible = items.slice(start, end);
|
|
258
|
-
return /* @__PURE__ */
|
|
1103
|
+
return /* @__PURE__ */ jsxs5(
|
|
259
1104
|
Box4,
|
|
260
1105
|
{
|
|
261
1106
|
flexDirection: "column",
|
|
@@ -264,20 +1109,20 @@ function Selector({
|
|
|
264
1109
|
borderStyle: "round",
|
|
265
1110
|
borderColor: "cyan",
|
|
266
1111
|
children: [
|
|
267
|
-
(title || hint) && /* @__PURE__ */
|
|
268
|
-
title && /* @__PURE__ */
|
|
269
|
-
title && hint && /* @__PURE__ */
|
|
270
|
-
hint && /* @__PURE__ */
|
|
1112
|
+
(title || hint) && /* @__PURE__ */ jsxs5(Box4, { marginBottom: 1, children: [
|
|
1113
|
+
title && /* @__PURE__ */ jsx5(Text5, { bold: true, children: title }),
|
|
1114
|
+
title && hint && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " " }),
|
|
1115
|
+
hint && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: hint })
|
|
271
1116
|
] }),
|
|
272
1117
|
visible.map((item, i) => {
|
|
273
1118
|
const realIndex = start + i;
|
|
274
1119
|
const focused = realIndex === index;
|
|
275
|
-
return /* @__PURE__ */
|
|
276
|
-
/* @__PURE__ */
|
|
1120
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "row", children: [
|
|
1121
|
+
/* @__PURE__ */ jsx5(Text5, { color: POINTER_COLOR, bold: true, children: focused ? "\u203A " : " " }),
|
|
277
1122
|
renderRow(item, focused)
|
|
278
1123
|
] }, realIndex);
|
|
279
1124
|
}),
|
|
280
|
-
window < len && /* @__PURE__ */
|
|
1125
|
+
window < len && /* @__PURE__ */ jsx5(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
281
1126
|
"(",
|
|
282
1127
|
start + 1,
|
|
283
1128
|
"-",
|
|
@@ -292,24 +1137,24 @@ function Selector({
|
|
|
292
1137
|
}
|
|
293
1138
|
|
|
294
1139
|
// src/components/ModelSelector.tsx
|
|
295
|
-
import { jsx as
|
|
1140
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
296
1141
|
function ModelSelector({ request }) {
|
|
297
|
-
const { items, currentId, resolve:
|
|
1142
|
+
const { items, currentId, resolve: resolve6 } = request;
|
|
298
1143
|
const initialIndex = Math.max(
|
|
299
1144
|
0,
|
|
300
1145
|
items.findIndex((m) => m.id === currentId)
|
|
301
1146
|
);
|
|
302
1147
|
const labelWidth = Math.max(...items.map((m) => (m.name ?? m.id).length));
|
|
303
|
-
return /* @__PURE__ */
|
|
1148
|
+
return /* @__PURE__ */ jsx6(
|
|
304
1149
|
Selector,
|
|
305
1150
|
{
|
|
306
1151
|
items,
|
|
307
1152
|
initialIndex,
|
|
308
1153
|
title: "Select model",
|
|
309
1154
|
hint: "\u2191\u2193 navigate \xB7 Enter confirm \xB7 Esc cancel",
|
|
310
|
-
onSubmit: (m) =>
|
|
311
|
-
onCancel: () =>
|
|
312
|
-
renderRow: (m, _focused) => /* @__PURE__ */
|
|
1155
|
+
onSubmit: (m) => resolve6(m),
|
|
1156
|
+
onCancel: () => resolve6(null),
|
|
1157
|
+
renderRow: (m, _focused) => /* @__PURE__ */ jsx6(ModelRow, { model: m, active: m.id === currentId, labelWidth })
|
|
313
1158
|
}
|
|
314
1159
|
);
|
|
315
1160
|
}
|
|
@@ -322,17 +1167,17 @@ function ModelRow({
|
|
|
322
1167
|
const label = (model.name ?? model.id).padEnd(labelWidth);
|
|
323
1168
|
const vendor = model.vendor ? `[${model.vendor}]` : "";
|
|
324
1169
|
const caps = formatCaps(model);
|
|
325
|
-
return /* @__PURE__ */
|
|
326
|
-
/* @__PURE__ */
|
|
1170
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "row", children: [
|
|
1171
|
+
/* @__PURE__ */ jsxs6(Text6, { color: active ? "green" : void 0, children: [
|
|
327
1172
|
dot,
|
|
328
1173
|
" "
|
|
329
1174
|
] }),
|
|
330
|
-
/* @__PURE__ */
|
|
331
|
-
/* @__PURE__ */
|
|
1175
|
+
/* @__PURE__ */ jsx6(Text6, { children: label }),
|
|
1176
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
332
1177
|
" ",
|
|
333
1178
|
vendor
|
|
334
1179
|
] }),
|
|
335
|
-
caps && /* @__PURE__ */
|
|
1180
|
+
caps && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
336
1181
|
" ",
|
|
337
1182
|
caps
|
|
338
1183
|
] })
|
|
@@ -346,15 +1191,15 @@ function formatCaps(m) {
|
|
|
346
1191
|
}
|
|
347
1192
|
|
|
348
1193
|
// src/components/SessionSelector.tsx
|
|
349
|
-
import { Box as Box6, Text as
|
|
350
|
-
import { jsx as
|
|
1194
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
1195
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
351
1196
|
function SessionSelector({ request }) {
|
|
352
|
-
const { items, currentId, resolve:
|
|
1197
|
+
const { items, currentId, resolve: resolve6 } = request;
|
|
353
1198
|
const initialIndex = Math.max(
|
|
354
1199
|
0,
|
|
355
1200
|
items.findIndex((s) => s.id === currentId)
|
|
356
1201
|
);
|
|
357
|
-
return /* @__PURE__ */
|
|
1202
|
+
return /* @__PURE__ */ jsx7(
|
|
358
1203
|
Selector,
|
|
359
1204
|
{
|
|
360
1205
|
items,
|
|
@@ -362,9 +1207,9 @@ function SessionSelector({ request }) {
|
|
|
362
1207
|
maxVisible: 12,
|
|
363
1208
|
title: "Resume session",
|
|
364
1209
|
hint: "\u2191\u2193 navigate \xB7 Enter load \xB7 Esc cancel",
|
|
365
|
-
onSubmit: (s) =>
|
|
366
|
-
onCancel: () =>
|
|
367
|
-
renderRow: (s) => /* @__PURE__ */
|
|
1210
|
+
onSubmit: (s) => resolve6(s),
|
|
1211
|
+
onCancel: () => resolve6(null),
|
|
1212
|
+
renderRow: (s) => /* @__PURE__ */ jsx7(SessionRow, { session: s, active: s.id === currentId })
|
|
368
1213
|
}
|
|
369
1214
|
);
|
|
370
1215
|
}
|
|
@@ -373,18 +1218,18 @@ function SessionRow({ session, active }) {
|
|
|
373
1218
|
const time = formatTime(session.createdAt);
|
|
374
1219
|
const count = `[${String(session.messageCount).padStart(2)} msgs]`;
|
|
375
1220
|
const preview = session.preview ?? "(empty)";
|
|
376
|
-
return /* @__PURE__ */
|
|
377
|
-
/* @__PURE__ */
|
|
378
|
-
/* @__PURE__ */
|
|
379
|
-
/* @__PURE__ */
|
|
1221
|
+
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "row", children: [
|
|
1222
|
+
/* @__PURE__ */ jsx7(Text7, { color: active ? "green" : void 0, children: active ? "\u25CF " : " " }),
|
|
1223
|
+
/* @__PURE__ */ jsx7(Text7, { children: id8 }),
|
|
1224
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
380
1225
|
" ",
|
|
381
1226
|
time
|
|
382
1227
|
] }),
|
|
383
|
-
/* @__PURE__ */
|
|
1228
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
384
1229
|
" ",
|
|
385
1230
|
count
|
|
386
1231
|
] }),
|
|
387
|
-
/* @__PURE__ */
|
|
1232
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
388
1233
|
" ",
|
|
389
1234
|
preview
|
|
390
1235
|
] })
|
|
@@ -397,9 +1242,340 @@ function formatTime(iso) {
|
|
|
397
1242
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
398
1243
|
}
|
|
399
1244
|
|
|
1245
|
+
// src/components/QuestionPicker.tsx
|
|
1246
|
+
import { useState as useState4 } from "react";
|
|
1247
|
+
import { Box as Box7, Text as Text8, useInput as useInput4 } from "ink";
|
|
1248
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1249
|
+
var POINTER_COLOR2 = "#A855F7";
|
|
1250
|
+
var FOCUS_BG = "#5B5598";
|
|
1251
|
+
var FOCUS_FG = "white";
|
|
1252
|
+
var SUBMIT_COLOR = "green";
|
|
1253
|
+
var NOTES_LABEL_COLOR = "yellow";
|
|
1254
|
+
var PREVIEW_BORDER = "gray";
|
|
1255
|
+
var TAB_GAP = 3;
|
|
1256
|
+
var PREVIEW_MIN_WIDTH = 28;
|
|
1257
|
+
function QuestionPicker({ request }) {
|
|
1258
|
+
const { questions } = request;
|
|
1259
|
+
const N = questions.length;
|
|
1260
|
+
const hasTabs = N > 1;
|
|
1261
|
+
const submitTabIndex = N;
|
|
1262
|
+
const [tabIndex, setTabIndex] = useState4(0);
|
|
1263
|
+
const [states, setStates] = useState4(
|
|
1264
|
+
() => questions.map(() => ({
|
|
1265
|
+
optionIndex: 0,
|
|
1266
|
+
selected: /* @__PURE__ */ new Set(),
|
|
1267
|
+
notes: "",
|
|
1268
|
+
notesEditing: false,
|
|
1269
|
+
notesDraft: ""
|
|
1270
|
+
}))
|
|
1271
|
+
);
|
|
1272
|
+
const currentQ = tabIndex < N ? questions[tabIndex] : null;
|
|
1273
|
+
const isMulti = currentQ?.multiSelect === true;
|
|
1274
|
+
const onSubmitChip = hasTabs && tabIndex === submitTabIndex;
|
|
1275
|
+
const anyPreview = !!currentQ?.options.some((o) => o.preview);
|
|
1276
|
+
const focusedOpt = currentQ?.options[states[tabIndex]?.optionIndex ?? 0];
|
|
1277
|
+
const focusedPreview = anyPreview ? focusedOpt?.preview ?? "" : "";
|
|
1278
|
+
const buildResponses = (cancelled) => {
|
|
1279
|
+
if (cancelled) return questions.map(() => ({ cancelled: true, selections: [] }));
|
|
1280
|
+
return questions.map((q, qi) => ({
|
|
1281
|
+
cancelled: false,
|
|
1282
|
+
selections: Array.from(states[qi].selected).sort((a, b) => a - b).map((i) => q.options[i].label),
|
|
1283
|
+
notes: states[qi].notes
|
|
1284
|
+
}));
|
|
1285
|
+
};
|
|
1286
|
+
const submit = () => request.resolve(buildResponses(false));
|
|
1287
|
+
const cancel = () => request.resolve(buildResponses(true));
|
|
1288
|
+
const updateState = (qi, mut) => {
|
|
1289
|
+
setStates((prev) => {
|
|
1290
|
+
const next = [...prev];
|
|
1291
|
+
next[qi] = mut(prev[qi]);
|
|
1292
|
+
return next;
|
|
1293
|
+
});
|
|
1294
|
+
};
|
|
1295
|
+
const toggleOption = (qi, oi) => {
|
|
1296
|
+
updateState(qi, (s) => {
|
|
1297
|
+
const sel = new Set(s.selected);
|
|
1298
|
+
if (sel.has(oi)) sel.delete(oi);
|
|
1299
|
+
else sel.add(oi);
|
|
1300
|
+
return { ...s, selected: sel };
|
|
1301
|
+
});
|
|
1302
|
+
};
|
|
1303
|
+
const selectSingleOption = (qi, oi) => {
|
|
1304
|
+
updateState(qi, (s) => ({ ...s, selected: /* @__PURE__ */ new Set([oi]) }));
|
|
1305
|
+
if (!hasTabs) {
|
|
1306
|
+
request.resolve([
|
|
1307
|
+
{
|
|
1308
|
+
cancelled: false,
|
|
1309
|
+
selections: [questions[0].options[oi].label],
|
|
1310
|
+
notes: states[0].notes
|
|
1311
|
+
}
|
|
1312
|
+
]);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (qi < N - 1) setTabIndex(qi + 1);
|
|
1316
|
+
else setTabIndex(submitTabIndex);
|
|
1317
|
+
};
|
|
1318
|
+
const singleQMultiSubmitRowIndex = !hasTabs && isMulti ? currentQ.options.length : -1;
|
|
1319
|
+
const optionRowCount = !hasTabs && isMulti ? currentQ.options.length + 1 : currentQ?.options.length ?? 0;
|
|
1320
|
+
const currentNotesEditing = tabIndex < N ? states[tabIndex].notesEditing : false;
|
|
1321
|
+
useInput4((input, key) => {
|
|
1322
|
+
if (currentNotesEditing) {
|
|
1323
|
+
if (key.escape) {
|
|
1324
|
+
updateState(tabIndex, (s2) => ({ ...s2, notesEditing: false, notesDraft: "" }));
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (key.return) {
|
|
1328
|
+
updateState(tabIndex, (s2) => ({
|
|
1329
|
+
...s2,
|
|
1330
|
+
notesEditing: false,
|
|
1331
|
+
notes: s2.notesDraft,
|
|
1332
|
+
notesDraft: ""
|
|
1333
|
+
}));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (key.backspace || key.delete) {
|
|
1337
|
+
updateState(tabIndex, (s2) => ({
|
|
1338
|
+
...s2,
|
|
1339
|
+
notesDraft: s2.notesDraft.slice(0, -1)
|
|
1340
|
+
}));
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (key.ctrl || key.tab || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.meta) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (input) {
|
|
1347
|
+
updateState(tabIndex, (s2) => ({ ...s2, notesDraft: s2.notesDraft + input }));
|
|
1348
|
+
}
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (key.escape) {
|
|
1352
|
+
cancel();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (hasTabs && (key.tab || key.leftArrow || key.rightArrow)) {
|
|
1356
|
+
if (key.leftArrow || key.shift && key.tab) {
|
|
1357
|
+
setTabIndex((t) => Math.max(0, t - 1));
|
|
1358
|
+
} else {
|
|
1359
|
+
setTabIndex((t) => Math.min(submitTabIndex, t + 1));
|
|
1360
|
+
}
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (onSubmitChip) {
|
|
1364
|
+
if (key.return) submit();
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const qi = tabIndex;
|
|
1368
|
+
const s = states[qi];
|
|
1369
|
+
if (key.upArrow) {
|
|
1370
|
+
updateState(qi, (st) => ({ ...st, optionIndex: Math.max(0, st.optionIndex - 1) }));
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if (key.downArrow) {
|
|
1374
|
+
updateState(qi, (st) => ({
|
|
1375
|
+
...st,
|
|
1376
|
+
optionIndex: Math.min(optionRowCount - 1, st.optionIndex + 1)
|
|
1377
|
+
}));
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (input === "n" && !key.ctrl && !key.meta) {
|
|
1381
|
+
updateState(qi, (st) => ({
|
|
1382
|
+
...st,
|
|
1383
|
+
notesEditing: true,
|
|
1384
|
+
notesDraft: st.notes
|
|
1385
|
+
}));
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (key.return) {
|
|
1389
|
+
if (!hasTabs && isMulti && s.optionIndex === singleQMultiSubmitRowIndex) {
|
|
1390
|
+
submit();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if (isMulti) toggleOption(qi, s.optionIndex);
|
|
1394
|
+
else selectSingleOption(qi, s.optionIndex);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (isMulti && input === " " && s.optionIndex !== singleQMultiSubmitRowIndex) {
|
|
1398
|
+
toggleOption(qi, s.optionIndex);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1402
|
+
/* @__PURE__ */ jsx8(Divider, {}),
|
|
1403
|
+
hasTabs && /* @__PURE__ */ jsx8(Box7, { marginTop: 0, children: /* @__PURE__ */ jsx8(TabBar, { questions, states, tabIndex }) }),
|
|
1404
|
+
tabIndex < N && /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1405
|
+
/* @__PURE__ */ jsx8(Text8, { children: questions[tabIndex].question }),
|
|
1406
|
+
/* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", marginTop: 1, children: [
|
|
1407
|
+
/* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: anyPreview ? 0 : 1, flexShrink: 0, marginRight: anyPreview ? 2 : 0, children: /* @__PURE__ */ jsx8(
|
|
1408
|
+
OptionsList,
|
|
1409
|
+
{
|
|
1410
|
+
options: questions[tabIndex].options,
|
|
1411
|
+
focusedIndex: states[tabIndex].optionIndex,
|
|
1412
|
+
selected: states[tabIndex].selected,
|
|
1413
|
+
isMulti,
|
|
1414
|
+
submitRowIndex: singleQMultiSubmitRowIndex
|
|
1415
|
+
}
|
|
1416
|
+
) }),
|
|
1417
|
+
anyPreview && /* @__PURE__ */ jsx8(Box7, { flexGrow: 1, minWidth: PREVIEW_MIN_WIDTH, children: /* @__PURE__ */ jsx8(PreviewPanel, { content: focusedPreview }) })
|
|
1418
|
+
] }),
|
|
1419
|
+
/* @__PURE__ */ jsx8(
|
|
1420
|
+
NotesLine,
|
|
1421
|
+
{
|
|
1422
|
+
notes: states[tabIndex].notes,
|
|
1423
|
+
editing: states[tabIndex].notesEditing,
|
|
1424
|
+
draft: states[tabIndex].notesDraft
|
|
1425
|
+
}
|
|
1426
|
+
)
|
|
1427
|
+
] }),
|
|
1428
|
+
onSubmitChip && /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx8(SubmitPreview, { questions, states }) }),
|
|
1429
|
+
/* @__PURE__ */ jsx8(Divider, {}),
|
|
1430
|
+
/* @__PURE__ */ jsx8(Box7, { children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: hintLine(hasTabs, isMulti, currentNotesEditing) }) })
|
|
1431
|
+
] });
|
|
1432
|
+
}
|
|
1433
|
+
function Divider() {
|
|
1434
|
+
return /* @__PURE__ */ jsx8(Box7, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray" });
|
|
1435
|
+
}
|
|
1436
|
+
function TabBar({
|
|
1437
|
+
questions,
|
|
1438
|
+
states,
|
|
1439
|
+
tabIndex
|
|
1440
|
+
}) {
|
|
1441
|
+
const submitIndex = questions.length;
|
|
1442
|
+
const canLeft = tabIndex > 0;
|
|
1443
|
+
const canRight = tabIndex < submitIndex;
|
|
1444
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", flexWrap: "wrap", children: [
|
|
1445
|
+
/* @__PURE__ */ jsx8(Box7, { marginRight: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: !canLeft, children: "\u2190" }) }),
|
|
1446
|
+
questions.map((q, i) => {
|
|
1447
|
+
const focused = i === tabIndex;
|
|
1448
|
+
const isMulti = q.multiSelect === true;
|
|
1449
|
+
const count = states[i].selected.size;
|
|
1450
|
+
const answered = count > 0;
|
|
1451
|
+
const mark = answered ? isMulti ? `[${count}]` : "\u2714" : "\u25A1";
|
|
1452
|
+
return /* @__PURE__ */ jsx8(Box7, { marginRight: TAB_GAP, children: /* @__PURE__ */ jsx8(
|
|
1453
|
+
Text8,
|
|
1454
|
+
{
|
|
1455
|
+
backgroundColor: focused ? FOCUS_BG : void 0,
|
|
1456
|
+
color: focused ? FOCUS_FG : void 0,
|
|
1457
|
+
bold: focused,
|
|
1458
|
+
dimColor: !focused,
|
|
1459
|
+
children: ` ${mark} ${q.header} `
|
|
1460
|
+
}
|
|
1461
|
+
) }, i);
|
|
1462
|
+
}),
|
|
1463
|
+
/* @__PURE__ */ jsx8(Box7, { marginRight: 1, children: /* @__PURE__ */ jsx8(
|
|
1464
|
+
Text8,
|
|
1465
|
+
{
|
|
1466
|
+
backgroundColor: tabIndex === submitIndex ? FOCUS_BG : void 0,
|
|
1467
|
+
color: tabIndex === submitIndex ? FOCUS_FG : SUBMIT_COLOR,
|
|
1468
|
+
bold: tabIndex === submitIndex,
|
|
1469
|
+
dimColor: tabIndex !== submitIndex,
|
|
1470
|
+
children: " \u2714 Submit "
|
|
1471
|
+
}
|
|
1472
|
+
) }),
|
|
1473
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: !canRight, children: "\u2192" })
|
|
1474
|
+
] });
|
|
1475
|
+
}
|
|
1476
|
+
function OptionsList({
|
|
1477
|
+
options,
|
|
1478
|
+
focusedIndex,
|
|
1479
|
+
selected,
|
|
1480
|
+
isMulti,
|
|
1481
|
+
submitRowIndex
|
|
1482
|
+
}) {
|
|
1483
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
1484
|
+
options.map((opt, i) => {
|
|
1485
|
+
const focused = i === focusedIndex;
|
|
1486
|
+
const checked = selected.has(i);
|
|
1487
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
1488
|
+
/* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", children: [
|
|
1489
|
+
/* @__PURE__ */ jsx8(Text8, { color: POINTER_COLOR2, bold: true, children: focused ? "\u203A " : " " }),
|
|
1490
|
+
isMulti && /* @__PURE__ */ jsx8(Text8, { color: checked ? "green" : void 0, children: checked ? "[x] " : "[ ] " }),
|
|
1491
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: `${i + 1}. ` }),
|
|
1492
|
+
/* @__PURE__ */ jsx8(Text8, { color: focused ? POINTER_COLOR2 : void 0, bold: focused, children: opt.label })
|
|
1493
|
+
] }),
|
|
1494
|
+
opt.description && /* @__PURE__ */ jsx8(Box7, { marginLeft: isMulti ? 6 : 5, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "truncate-end", children: opt.description }) })
|
|
1495
|
+
] }, i);
|
|
1496
|
+
}),
|
|
1497
|
+
submitRowIndex >= 0 && /* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", marginTop: 1, children: [
|
|
1498
|
+
/* @__PURE__ */ jsx8(Text8, { color: POINTER_COLOR2, bold: true, children: focusedIndex === submitRowIndex ? "\u203A " : " " }),
|
|
1499
|
+
/* @__PURE__ */ jsx8(
|
|
1500
|
+
Text8,
|
|
1501
|
+
{
|
|
1502
|
+
color: focusedIndex === submitRowIndex ? SUBMIT_COLOR : void 0,
|
|
1503
|
+
bold: focusedIndex === submitRowIndex,
|
|
1504
|
+
dimColor: focusedIndex !== submitRowIndex,
|
|
1505
|
+
children: `\u2500\u2500 Submit (${selected.size} selected)`
|
|
1506
|
+
}
|
|
1507
|
+
)
|
|
1508
|
+
] })
|
|
1509
|
+
] });
|
|
1510
|
+
}
|
|
1511
|
+
function PreviewPanel({ content }) {
|
|
1512
|
+
const lines = content ? content.split("\n") : ["(no preview)"];
|
|
1513
|
+
return /* @__PURE__ */ jsx8(
|
|
1514
|
+
Box7,
|
|
1515
|
+
{
|
|
1516
|
+
borderStyle: "round",
|
|
1517
|
+
borderColor: PREVIEW_BORDER,
|
|
1518
|
+
flexDirection: "column",
|
|
1519
|
+
paddingX: 1,
|
|
1520
|
+
flexGrow: 1,
|
|
1521
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate-end", dimColor: !content, children: line || " " }, i))
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
function NotesLine({
|
|
1526
|
+
notes,
|
|
1527
|
+
editing,
|
|
1528
|
+
draft
|
|
1529
|
+
}) {
|
|
1530
|
+
if (editing) {
|
|
1531
|
+
return /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", children: [
|
|
1532
|
+
/* @__PURE__ */ jsx8(Text8, { color: NOTES_LABEL_COLOR, bold: true, children: "Notes: " }),
|
|
1533
|
+
/* @__PURE__ */ jsx8(Text8, { children: draft }),
|
|
1534
|
+
/* @__PURE__ */ jsx8(Text8, { color: POINTER_COLOR2, children: "\u258E" }),
|
|
1535
|
+
/* @__PURE__ */ jsx8(Box7, { flexGrow: 1, marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "(Enter to save \xB7 Esc to discard)" }) })
|
|
1536
|
+
] });
|
|
1537
|
+
}
|
|
1538
|
+
return /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: [
|
|
1539
|
+
/* @__PURE__ */ jsx8(Text8, { color: NOTES_LABEL_COLOR, bold: true, children: "Notes: " }),
|
|
1540
|
+
notes ? /* @__PURE__ */ jsx8(Text8, { children: notes }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "press n to add notes" })
|
|
1541
|
+
] });
|
|
1542
|
+
}
|
|
1543
|
+
function SubmitPreview({
|
|
1544
|
+
questions,
|
|
1545
|
+
states
|
|
1546
|
+
}) {
|
|
1547
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
1548
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Review" }),
|
|
1549
|
+
questions.map((q, qi) => {
|
|
1550
|
+
const sel = Array.from(states[qi].selected).sort((a, b) => a - b).map((i) => q.options[i].label);
|
|
1551
|
+
const notes = states[qi].notes.trim();
|
|
1552
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginLeft: 2, children: [
|
|
1553
|
+
/* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", children: [
|
|
1554
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: `${q.header}: ` }),
|
|
1555
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: sel.length === 0, children: sel.length > 0 ? sel.join(", ") : "(no answer)" })
|
|
1556
|
+
] }),
|
|
1557
|
+
notes && /* @__PURE__ */ jsxs8(Box7, { marginLeft: 2, flexDirection: "row", children: [
|
|
1558
|
+
/* @__PURE__ */ jsx8(Text8, { color: NOTES_LABEL_COLOR, children: "notes: " }),
|
|
1559
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: notes })
|
|
1560
|
+
] })
|
|
1561
|
+
] }, qi);
|
|
1562
|
+
})
|
|
1563
|
+
] });
|
|
1564
|
+
}
|
|
1565
|
+
function hintLine(hasTabs, isMulti, editingNotes) {
|
|
1566
|
+
if (editingNotes) {
|
|
1567
|
+
return "Enter to save \xB7 Esc to discard \xB7 backspace to delete";
|
|
1568
|
+
}
|
|
1569
|
+
const parts = ["Enter to select", "\u2191/\u2193 to navigate", "n to add notes"];
|
|
1570
|
+
if (hasTabs) parts.push("Tab to switch questions");
|
|
1571
|
+
if (isMulti) parts.push("Space to toggle");
|
|
1572
|
+
parts.push("Esc to cancel");
|
|
1573
|
+
return parts.join(" \xB7 ");
|
|
1574
|
+
}
|
|
1575
|
+
|
|
400
1576
|
// src/components/SlashAutocomplete.tsx
|
|
401
|
-
import { Box as
|
|
402
|
-
import { jsx as
|
|
1577
|
+
import { Box as Box8, Text as Text9 } from "ink";
|
|
1578
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
403
1579
|
var DEFAULT_MAX = 10;
|
|
404
1580
|
var SLASH_COLOR = "#A855F7";
|
|
405
1581
|
function SlashAutocomplete({ matches, index, maxVisible = DEFAULT_MAX }) {
|
|
@@ -407,35 +1583,34 @@ function SlashAutocomplete({ matches, index, maxVisible = DEFAULT_MAX }) {
|
|
|
407
1583
|
const start = Math.max(0, Math.min(index - Math.floor(maxVisible / 2), matches.length - maxVisible));
|
|
408
1584
|
const end = Math.min(matches.length, start + maxVisible);
|
|
409
1585
|
const visible = matches.slice(start, end);
|
|
410
|
-
const nameWidth = Math.max(...matches.map((c) => c.name.length
|
|
411
|
-
return /* @__PURE__ */
|
|
1586
|
+
const nameWidth = Math.max(...matches.map((c) => c.name.length));
|
|
1587
|
+
return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
412
1588
|
visible.map((cmd, i) => {
|
|
413
1589
|
const realIndex = start + i;
|
|
414
|
-
return /* @__PURE__ */
|
|
1590
|
+
return /* @__PURE__ */ jsx9(Row, { cmd, focused: realIndex === index, nameWidth }, cmd.name);
|
|
415
1591
|
}),
|
|
416
|
-
matches.length > visible.length && /* @__PURE__ */
|
|
1592
|
+
matches.length > visible.length && /* @__PURE__ */ jsx9(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
417
1593
|
"\u2191\u2193 select \xB7 Tab/Enter accept \xB7 Esc cancel (",
|
|
418
1594
|
matches.length - visible.length,
|
|
419
1595
|
" more)"
|
|
420
1596
|
] }) }),
|
|
421
|
-
matches.length <= visible.length && /* @__PURE__ */
|
|
1597
|
+
matches.length <= visible.length && /* @__PURE__ */ jsx9(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191\u2193 select \xB7 Tab/Enter accept \xB7 Esc cancel" }) })
|
|
422
1598
|
] });
|
|
423
1599
|
}
|
|
424
1600
|
function Row({ cmd, focused, nameWidth }) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
/* @__PURE__ */ jsxs7(Text7, { color: focused ? SLASH_COLOR : void 0, bold: focused, children: [
|
|
1601
|
+
const padded = cmd.name.padEnd(nameWidth);
|
|
1602
|
+
return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "row", children: [
|
|
1603
|
+
/* @__PURE__ */ jsxs9(Text9, { color: focused ? SLASH_COLOR : void 0, bold: focused, children: [
|
|
429
1604
|
"/",
|
|
430
1605
|
padded
|
|
431
1606
|
] }),
|
|
432
|
-
/* @__PURE__ */
|
|
433
|
-
/* @__PURE__ */
|
|
1607
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1608
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: cmd.description })
|
|
434
1609
|
] });
|
|
435
1610
|
}
|
|
436
1611
|
|
|
437
1612
|
// src/components/PermissionModeBar.tsx
|
|
438
|
-
import { Box as
|
|
1613
|
+
import { Box as Box9, Text as Text10 } from "ink";
|
|
439
1614
|
|
|
440
1615
|
// src/permission/index.ts
|
|
441
1616
|
var MODE_CYCLE = [
|
|
@@ -459,6 +1634,8 @@ var MODE_COLOR = {
|
|
|
459
1634
|
var PermissionGate = class {
|
|
460
1635
|
rules;
|
|
461
1636
|
mode = "default";
|
|
1637
|
+
/** Session 级 allow:用户在 PermissionPrompt 选 "yes, for session" 后填充。 */
|
|
1638
|
+
sessionAllow = /* @__PURE__ */ new Set();
|
|
462
1639
|
constructor(rules = {}) {
|
|
463
1640
|
this.rules = {
|
|
464
1641
|
allow: rules.allow ?? [],
|
|
@@ -478,8 +1655,16 @@ var PermissionGate = class {
|
|
|
478
1655
|
this.mode = MODE_CYCLE[(i + 1) % MODE_CYCLE.length];
|
|
479
1656
|
return this.mode;
|
|
480
1657
|
}
|
|
1658
|
+
/** 用户在 PermissionPrompt 选 "yes, allow for session" 时记下。 */
|
|
1659
|
+
allowForSession(toolName) {
|
|
1660
|
+
this.sessionAllow.add(toolName);
|
|
1661
|
+
}
|
|
1662
|
+
isSessionAllowed(toolName) {
|
|
1663
|
+
return this.sessionAllow.has(toolName);
|
|
1664
|
+
}
|
|
481
1665
|
decide(input) {
|
|
482
1666
|
if (this.matches(this.rules.deny, input)) return "deny";
|
|
1667
|
+
if (this.sessionAllow.has(input.toolName)) return "allow";
|
|
483
1668
|
switch (this.mode) {
|
|
484
1669
|
case "bypassPermissions":
|
|
485
1670
|
return "allow";
|
|
@@ -533,7 +1718,7 @@ var PermissionGate = class {
|
|
|
533
1718
|
};
|
|
534
1719
|
|
|
535
1720
|
// src/components/PermissionModeBar.tsx
|
|
536
|
-
import { jsx as
|
|
1721
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
537
1722
|
function PermissionModeBar({ mode, compact }) {
|
|
538
1723
|
const color = MODE_COLOR[mode];
|
|
539
1724
|
const label = MODE_LABEL[mode];
|
|
@@ -545,20 +1730,235 @@ function PermissionModeBar({ mode, compact }) {
|
|
|
545
1730
|
plan: "[plan]",
|
|
546
1731
|
bypassPermissions: "[bypass]"
|
|
547
1732
|
};
|
|
548
|
-
return /* @__PURE__ */
|
|
549
|
-
/* @__PURE__ */
|
|
550
|
-
/* @__PURE__ */
|
|
1733
|
+
return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "row", children: [
|
|
1734
|
+
/* @__PURE__ */ jsx10(Text10, { color, bold: isBypass, children: short[mode] }),
|
|
1735
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " shift+tab" })
|
|
551
1736
|
] });
|
|
552
1737
|
}
|
|
553
|
-
return /* @__PURE__ */
|
|
554
|
-
/* @__PURE__ */
|
|
555
|
-
"\
|
|
1738
|
+
return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "row", children: [
|
|
1739
|
+
/* @__PURE__ */ jsxs10(Text10, { color, bold: isBypass, children: [
|
|
1740
|
+
"\u25B8\u25B8 ",
|
|
556
1741
|
label
|
|
557
1742
|
] }),
|
|
558
|
-
/* @__PURE__ */
|
|
1743
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " (shift+tab to cycle)" })
|
|
559
1744
|
] });
|
|
560
1745
|
}
|
|
561
1746
|
|
|
1747
|
+
// src/components/FooterStatus.tsx
|
|
1748
|
+
import { Box as Box10, Text as Text11 } from "ink";
|
|
1749
|
+
import { Fragment, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1750
|
+
var BAR_TOTAL_WIDE = 10;
|
|
1751
|
+
var BAR_TOTAL_COMPACT = 6;
|
|
1752
|
+
function FooterStatus({
|
|
1753
|
+
sessionId,
|
|
1754
|
+
model,
|
|
1755
|
+
contextWindow,
|
|
1756
|
+
lastInputTokens,
|
|
1757
|
+
sessionInputTokens,
|
|
1758
|
+
sessionOutputTokens,
|
|
1759
|
+
termWidth
|
|
1760
|
+
}) {
|
|
1761
|
+
const sid = sessionId.slice(0, 8);
|
|
1762
|
+
const hasCtx = contextWindow > 0;
|
|
1763
|
+
const pct = hasCtx ? Math.min(100, Math.round(lastInputTokens / contextWindow * 100)) : 0;
|
|
1764
|
+
const ctxColor = pct >= 90 ? "red" : pct >= 70 ? "yellow" : "green";
|
|
1765
|
+
const SEP = /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \u2502 " });
|
|
1766
|
+
const SEP_DOT = /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \xB7 " });
|
|
1767
|
+
const renderBar = (barW) => {
|
|
1768
|
+
const filled = Math.round(pct / 100 * barW);
|
|
1769
|
+
const empty = barW - filled;
|
|
1770
|
+
return /* @__PURE__ */ jsxs11(Fragment, { children: [
|
|
1771
|
+
filled > 0 && /* @__PURE__ */ jsx11(Text11, { color: ctxColor, children: "\u2588".repeat(filled) }),
|
|
1772
|
+
empty > 0 && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2591".repeat(empty) })
|
|
1773
|
+
] });
|
|
1774
|
+
};
|
|
1775
|
+
if (termWidth < 60) {
|
|
1776
|
+
return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "row", children: [
|
|
1777
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: sid }),
|
|
1778
|
+
SEP_DOT,
|
|
1779
|
+
/* @__PURE__ */ jsx11(Text11, { color: "magenta", children: model }),
|
|
1780
|
+
hasCtx && /* @__PURE__ */ jsxs11(Fragment, { children: [
|
|
1781
|
+
SEP_DOT,
|
|
1782
|
+
renderBar(BAR_TOTAL_COMPACT),
|
|
1783
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: ` ${pct}%` })
|
|
1784
|
+
] })
|
|
1785
|
+
] });
|
|
1786
|
+
}
|
|
1787
|
+
if (termWidth < 100) {
|
|
1788
|
+
return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "row", children: [
|
|
1789
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: `@${sid}` }),
|
|
1790
|
+
SEP,
|
|
1791
|
+
/* @__PURE__ */ jsx11(Text11, { color: "magenta", children: model }),
|
|
1792
|
+
hasCtx && /* @__PURE__ */ jsxs11(Fragment, { children: [
|
|
1793
|
+
SEP,
|
|
1794
|
+
/* @__PURE__ */ jsx11(Text11, { children: "ctx: " }),
|
|
1795
|
+
renderBar(BAR_TOTAL_COMPACT),
|
|
1796
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: ` ${pct}%` })
|
|
1797
|
+
] })
|
|
1798
|
+
] });
|
|
1799
|
+
}
|
|
1800
|
+
return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "row", children: [
|
|
1801
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: `@${sid}` }),
|
|
1802
|
+
SEP,
|
|
1803
|
+
/* @__PURE__ */ jsx11(Text11, { color: "magenta", children: model }),
|
|
1804
|
+
hasCtx && /* @__PURE__ */ jsxs11(Fragment, { children: [
|
|
1805
|
+
SEP,
|
|
1806
|
+
/* @__PURE__ */ jsx11(Text11, { children: "ctx: " }),
|
|
1807
|
+
renderBar(BAR_TOTAL_WIDE),
|
|
1808
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: ` ${pct}%` }),
|
|
1809
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: ` ${formatTokens(lastInputTokens)}/${formatTokens(contextWindow)}` })
|
|
1810
|
+
] }),
|
|
1811
|
+
SEP,
|
|
1812
|
+
/* @__PURE__ */ jsx11(Text11, { children: "tok: " }),
|
|
1813
|
+
/* @__PURE__ */ jsx11(Text11, { color: "green", children: `\u2191${formatTokens(sessionInputTokens)}` }),
|
|
1814
|
+
/* @__PURE__ */ jsx11(Text11, { children: " " }),
|
|
1815
|
+
/* @__PURE__ */ jsx11(Text11, { color: "blueBright", children: `\u2193${formatTokens(sessionOutputTokens)}` })
|
|
1816
|
+
] });
|
|
1817
|
+
}
|
|
1818
|
+
function formatTokens(n) {
|
|
1819
|
+
if (n < 1e3) return String(n);
|
|
1820
|
+
if (n < 1e4) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|
|
1821
|
+
if (n < 1e6) return Math.round(n / 1e3) + "k";
|
|
1822
|
+
return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// src/components/ProgressBanner.tsx
|
|
1826
|
+
import { useEffect as useEffect2, useState as useState5 } from "react";
|
|
1827
|
+
import { Box as Box11, Text as Text12 } from "ink";
|
|
1828
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1829
|
+
var BAR_WIDTH = 42;
|
|
1830
|
+
var TICK_MS = 400;
|
|
1831
|
+
var TIP_ROTATE_SEC = 5;
|
|
1832
|
+
function ProgressBanner({ state }) {
|
|
1833
|
+
const [now, setNow] = useState5(Date.now());
|
|
1834
|
+
useEffect2(() => {
|
|
1835
|
+
const t = setInterval(() => setNow(Date.now()), TICK_MS);
|
|
1836
|
+
return () => clearInterval(t);
|
|
1837
|
+
}, []);
|
|
1838
|
+
const elapsedSec = Math.max(0, Math.floor((now - state.startTime) / 1e3));
|
|
1839
|
+
const percent = Math.max(0, Math.min(99, Math.floor(state.getPercent())));
|
|
1840
|
+
const filled = Math.floor(percent / 100 * BAR_WIDTH);
|
|
1841
|
+
const empty = BAR_WIDTH - filled;
|
|
1842
|
+
const bar = "\u25B0".repeat(filled) + "\u25B1".repeat(empty);
|
|
1843
|
+
const tip = state.tips.length ? state.tips[Math.floor(elapsedSec / TIP_ROTATE_SEC) % state.tips.length] : "";
|
|
1844
|
+
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
1845
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
1846
|
+
/* @__PURE__ */ jsx12(Text12, { color: "cyan", bold: true, children: "\u2726 " }),
|
|
1847
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "cyan", children: [
|
|
1848
|
+
state.title,
|
|
1849
|
+
"..."
|
|
1850
|
+
] }),
|
|
1851
|
+
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: ` (${elapsedSec}s)` })
|
|
1852
|
+
] }),
|
|
1853
|
+
/* @__PURE__ */ jsxs12(Box11, { marginLeft: 2, children: [
|
|
1854
|
+
/* @__PURE__ */ jsx12(Text12, { color: "cyan", children: bar }),
|
|
1855
|
+
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: ` ${percent}%` })
|
|
1856
|
+
] }),
|
|
1857
|
+
tip && /* @__PURE__ */ jsx12(Box11, { marginLeft: 2, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: `\u2514 Tip: ${tip}` }) })
|
|
1858
|
+
] });
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/components/StatusLine.tsx
|
|
1862
|
+
import { useEffect as useEffect4, useState as useState7 } from "react";
|
|
1863
|
+
import { Box as Box12, Text as Text14 } from "ink";
|
|
1864
|
+
|
|
1865
|
+
// src/components/Shimmer.tsx
|
|
1866
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
1867
|
+
import { Text as Text13 } from "ink";
|
|
1868
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1869
|
+
var FRAME_MS = 100;
|
|
1870
|
+
var TRAIL = 4;
|
|
1871
|
+
function Shimmer({ text, bold = true }) {
|
|
1872
|
+
const chars = Array.from(text);
|
|
1873
|
+
const cycle = chars.length + TRAIL;
|
|
1874
|
+
const [phase, setPhase] = useState6(0);
|
|
1875
|
+
useEffect3(() => {
|
|
1876
|
+
const id = setInterval(() => {
|
|
1877
|
+
setPhase((p) => (p + 1) % cycle);
|
|
1878
|
+
}, FRAME_MS);
|
|
1879
|
+
return () => clearInterval(id);
|
|
1880
|
+
}, [cycle]);
|
|
1881
|
+
return /* @__PURE__ */ jsx13(Text13, { children: chars.map((ch, i) => {
|
|
1882
|
+
const d = Math.abs(i - phase);
|
|
1883
|
+
if (d === 0) {
|
|
1884
|
+
return /* @__PURE__ */ jsx13(Text13, { color: "white", bold, children: ch }, i);
|
|
1885
|
+
}
|
|
1886
|
+
if (d === 1) {
|
|
1887
|
+
return /* @__PURE__ */ jsx13(Text13, { color: "white", children: ch }, i);
|
|
1888
|
+
}
|
|
1889
|
+
return /* @__PURE__ */ jsx13(Text13, { color: "gray", dimColor: true, children: ch }, i);
|
|
1890
|
+
}) });
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/components/StatusLine.tsx
|
|
1894
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1895
|
+
var TICK_MS2 = 400;
|
|
1896
|
+
function StatusLine({ startTime, firstTextTime, inputTokens, runningTool, lang }) {
|
|
1897
|
+
const [now, setNow] = useState7(Date.now());
|
|
1898
|
+
useEffect4(() => {
|
|
1899
|
+
const t = setInterval(() => setNow(Date.now()), TICK_MS2);
|
|
1900
|
+
return () => clearInterval(t);
|
|
1901
|
+
}, []);
|
|
1902
|
+
const elapsedSec = Math.max(0, Math.floor((now - startTime) / 1e3));
|
|
1903
|
+
const mainLabel = lang === "zh-CN" ? "\u5DE5\u4F5C\u4E2D" : "Working";
|
|
1904
|
+
const parts = [formatDuration(elapsedSec)];
|
|
1905
|
+
if (inputTokens > 0) {
|
|
1906
|
+
parts.push(`\u2191 ${formatTokens2(inputTokens)} tokens`);
|
|
1907
|
+
}
|
|
1908
|
+
if (firstTextTime !== null) {
|
|
1909
|
+
const thinkSec = Math.max(0, Math.floor((firstTextTime - startTime) / 1e3));
|
|
1910
|
+
parts.push(
|
|
1911
|
+
lang === "zh-CN" ? `\u601D\u8003 ${formatDuration(thinkSec)}` : `thought for ${formatDuration(thinkSec)}`
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
1915
|
+
/* @__PURE__ */ jsxs13(Box12, { flexDirection: "row", children: [
|
|
1916
|
+
/* @__PURE__ */ jsx14(Text14, { color: "gray", children: "\u25CF " }),
|
|
1917
|
+
/* @__PURE__ */ jsx14(Shimmer, { text: mainLabel }),
|
|
1918
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${parts.join(" \xB7 ")})` })
|
|
1919
|
+
] }),
|
|
1920
|
+
runningTool && /* @__PURE__ */ jsxs13(Box12, { flexDirection: "row", marginLeft: 2, marginTop: 0, children: [
|
|
1921
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "\u21B3 " }),
|
|
1922
|
+
/* @__PURE__ */ jsx14(Text14, { color: "cyan", children: runningTool })
|
|
1923
|
+
] })
|
|
1924
|
+
] });
|
|
1925
|
+
}
|
|
1926
|
+
function formatTokens2(n) {
|
|
1927
|
+
if (n < 1e3) return String(n);
|
|
1928
|
+
if (n < 1e4) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|
|
1929
|
+
if (n < 1e6) return Math.round(n / 1e3) + "k";
|
|
1930
|
+
return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
1931
|
+
}
|
|
1932
|
+
function formatDuration(sec) {
|
|
1933
|
+
if (sec < 60) return `${sec}s`;
|
|
1934
|
+
const m = Math.floor(sec / 60);
|
|
1935
|
+
const s = sec % 60;
|
|
1936
|
+
return s === 0 ? `${m}m` : `${m}m${s}s`;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/ui/termTitle.ts
|
|
1940
|
+
var ENABLED = (() => {
|
|
1941
|
+
if (!process.stdout.isTTY) return false;
|
|
1942
|
+
if (process.env.MUSE_NO_TITLE === "1") return false;
|
|
1943
|
+
return true;
|
|
1944
|
+
})();
|
|
1945
|
+
var lastTitle = "";
|
|
1946
|
+
function sanitize(s) {
|
|
1947
|
+
return s.replace(/[\x00-\x1f\x7f]/g, "");
|
|
1948
|
+
}
|
|
1949
|
+
function setTerminalTitle(title) {
|
|
1950
|
+
if (!ENABLED) return;
|
|
1951
|
+
const clean = sanitize(title);
|
|
1952
|
+
if (clean === lastTitle) return;
|
|
1953
|
+
lastTitle = clean;
|
|
1954
|
+
process.stdout.write(`\x1B]0;${clean}\x07`);
|
|
1955
|
+
}
|
|
1956
|
+
function resetTerminalTitle() {
|
|
1957
|
+
if (!ENABLED) return;
|
|
1958
|
+
lastTitle = "";
|
|
1959
|
+
process.stdout.write(`\x1B]0;\x07`);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
562
1962
|
// src/llm/providers/openai-compatible.ts
|
|
563
1963
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
564
1964
|
import { streamText, jsonSchema, tool } from "ai";
|
|
@@ -668,17 +2068,45 @@ var OpenAICompatibleClient = class {
|
|
|
668
2068
|
const { messages, tools, systemPrompt, temperature, maxTokens, abortSignal } = opts;
|
|
669
2069
|
const aiMessages = convertMessages(messages, systemPrompt);
|
|
670
2070
|
const aiTools = tools ? convertTools(tools) : void 0;
|
|
2071
|
+
let attempt = 0;
|
|
2072
|
+
const maxAttempts = 3;
|
|
2073
|
+
let result;
|
|
2074
|
+
while (true) {
|
|
2075
|
+
try {
|
|
2076
|
+
result = streamText({
|
|
2077
|
+
model: this.modelProvider,
|
|
2078
|
+
messages: aiMessages,
|
|
2079
|
+
tools: aiTools,
|
|
2080
|
+
temperature,
|
|
2081
|
+
maxTokens,
|
|
2082
|
+
abortSignal
|
|
2083
|
+
});
|
|
2084
|
+
break;
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
if (abortSignal?.aborted) {
|
|
2087
|
+
yield { type: "error", error: err instanceof Error ? err : new Error(String(err)) };
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
if (!isRetryable(err) || attempt >= maxAttempts - 1) {
|
|
2091
|
+
yield { type: "error", error: err instanceof Error ? err : new Error(String(err)) };
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
const delay = 1e3 * Math.pow(2, attempt);
|
|
2095
|
+
log.warn(`LLM connect failed (attempt ${attempt + 1}/${maxAttempts}); retrying in ${delay}ms`, {
|
|
2096
|
+
msg: err instanceof Error ? err.message : String(err)
|
|
2097
|
+
});
|
|
2098
|
+
await sleep(delay, abortSignal);
|
|
2099
|
+
attempt += 1;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (!result) {
|
|
2103
|
+
yield { type: "error", error: new Error("Internal: stream result is undefined after retry loop.") };
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
const stream = result.fullStream;
|
|
671
2107
|
try {
|
|
672
|
-
const result = streamText({
|
|
673
|
-
model: this.modelProvider,
|
|
674
|
-
messages: aiMessages,
|
|
675
|
-
tools: aiTools,
|
|
676
|
-
temperature,
|
|
677
|
-
maxTokens,
|
|
678
|
-
abortSignal
|
|
679
|
-
});
|
|
680
2108
|
const seenToolCalls = /* @__PURE__ */ new Set();
|
|
681
|
-
for await (const part of
|
|
2109
|
+
for await (const part of stream) {
|
|
682
2110
|
switch (part.type) {
|
|
683
2111
|
case "text-delta":
|
|
684
2112
|
yield { type: "text", delta: part.textDelta };
|
|
@@ -784,6 +2212,33 @@ function convertTools(tools) {
|
|
|
784
2212
|
}
|
|
785
2213
|
return result;
|
|
786
2214
|
}
|
|
2215
|
+
function isRetryable(err) {
|
|
2216
|
+
if (!(err instanceof Error)) return false;
|
|
2217
|
+
const msg = err.message.toLowerCase();
|
|
2218
|
+
const code = err.code ?? "";
|
|
2219
|
+
if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "ECONNREFUSED" || code === "EAI_AGAIN") {
|
|
2220
|
+
return true;
|
|
2221
|
+
}
|
|
2222
|
+
if (msg.includes("fetch failed") || msg.includes("network") || msg.includes("socket hang up") || msg.includes("under maintenance") || msg.includes("rate limit") || msg.includes("429") || msg.includes("502") || msg.includes("503") || msg.includes("504")) {
|
|
2223
|
+
return true;
|
|
2224
|
+
}
|
|
2225
|
+
return false;
|
|
2226
|
+
}
|
|
2227
|
+
async function sleep(ms, abortSignal) {
|
|
2228
|
+
await new Promise((resolve6, reject) => {
|
|
2229
|
+
if (abortSignal?.aborted) return reject(new Error("aborted"));
|
|
2230
|
+
const t = setTimeout(() => {
|
|
2231
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2232
|
+
resolve6();
|
|
2233
|
+
}, ms);
|
|
2234
|
+
const onAbort = () => {
|
|
2235
|
+
clearTimeout(t);
|
|
2236
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2237
|
+
reject(new Error("aborted"));
|
|
2238
|
+
};
|
|
2239
|
+
abortSignal?.addEventListener("abort", onAbort);
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
787
2242
|
function mapFinishReason(reason) {
|
|
788
2243
|
switch (reason) {
|
|
789
2244
|
case "stop":
|
|
@@ -884,10 +2339,7 @@ function setActiveModelEnv(entry) {
|
|
|
884
2339
|
function createLLMClientFromModelEntry(entry) {
|
|
885
2340
|
const apiKey = process.env[ACTIVE_API_KEY_ENV] ?? "";
|
|
886
2341
|
if (!apiKey && !entry.baseUrl.includes("localhost")) {
|
|
887
|
-
throw new MuseError(
|
|
888
|
-
`Model "${entry.id}" has no apiKey in env ${ACTIVE_API_KEY_ENV}. Check models.json (or models.local.json) and ensure setActiveModelEnv() was called.`,
|
|
889
|
-
"MISSING_API_KEY"
|
|
890
|
-
);
|
|
2342
|
+
throw new MuseError(buildMissingKeyMessage(entry), "MISSING_API_KEY");
|
|
891
2343
|
}
|
|
892
2344
|
const capabilities = {};
|
|
893
2345
|
if (entry.supportsToolCall !== void 0) capabilities.toolCalling = entry.supportsToolCall;
|
|
@@ -901,6 +2353,34 @@ function createLLMClientFromModelEntry(entry) {
|
|
|
901
2353
|
capabilities
|
|
902
2354
|
});
|
|
903
2355
|
}
|
|
2356
|
+
function buildMissingKeyMessage(entry) {
|
|
2357
|
+
const envVars = (entry._apiKeyEnvVars ?? []).filter(
|
|
2358
|
+
(v) => !process.env[v]
|
|
2359
|
+
);
|
|
2360
|
+
const head = `Model "${entry.id}" needs an API key but none was found.`;
|
|
2361
|
+
if (envVars.length > 0) {
|
|
2362
|
+
const list = envVars.map((v) => `$${v}`).join(", ");
|
|
2363
|
+
const fixVar = envVars[0];
|
|
2364
|
+
return [
|
|
2365
|
+
head,
|
|
2366
|
+
``,
|
|
2367
|
+
`Cause: ~/.muse/models.local.json sets apiKey to a placeholder referencing ${list},`,
|
|
2368
|
+
` but the shell environment does not have ${envVars.length > 1 ? "those variables" : "that variable"} set.`,
|
|
2369
|
+
``,
|
|
2370
|
+
`Fix (pick one):`,
|
|
2371
|
+
` 1. Replace the \${${fixVar}} placeholder in ~/.muse/models.local.json with the literal key`,
|
|
2372
|
+
` (recommended \u2014 the file is local-only and never enters git).`,
|
|
2373
|
+
` 2. Export the variable in your shell:`,
|
|
2374
|
+
` export ${fixVar}=<your-key>`
|
|
2375
|
+
].join("\n");
|
|
2376
|
+
}
|
|
2377
|
+
return [
|
|
2378
|
+
head,
|
|
2379
|
+
``,
|
|
2380
|
+
`Edit ~/.muse/models.local.json and set "apiKey" on the "${entry.id}" entry`,
|
|
2381
|
+
`(plain text is fine \u2014 the file stays local-only).`
|
|
2382
|
+
].join("\n");
|
|
2383
|
+
}
|
|
904
2384
|
function createLLMClient(opts) {
|
|
905
2385
|
const { provider, model, providers } = opts;
|
|
906
2386
|
const config = providers[provider];
|
|
@@ -933,6 +2413,32 @@ function createLLMClient(opts) {
|
|
|
933
2413
|
);
|
|
934
2414
|
}
|
|
935
2415
|
|
|
2416
|
+
// src/loop/todos.ts
|
|
2417
|
+
var TodoStore = class {
|
|
2418
|
+
items = [];
|
|
2419
|
+
list() {
|
|
2420
|
+
return this.items.slice();
|
|
2421
|
+
}
|
|
2422
|
+
set(items) {
|
|
2423
|
+
this.items = items.slice();
|
|
2424
|
+
}
|
|
2425
|
+
clear() {
|
|
2426
|
+
this.items = [];
|
|
2427
|
+
}
|
|
2428
|
+
/** 把当前清单格式化为 system prompt 段落;无任务时返回空串。 */
|
|
2429
|
+
toPromptSection() {
|
|
2430
|
+
if (this.items.length === 0) return "";
|
|
2431
|
+
const lines = this.items.map((t, i) => {
|
|
2432
|
+
const marker = t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[~]" : "[ ]";
|
|
2433
|
+
return ` ${i + 1}. ${marker} ${t.content}`;
|
|
2434
|
+
});
|
|
2435
|
+
return `# Current todos
|
|
2436
|
+
${lines.join("\n")}
|
|
2437
|
+
|
|
2438
|
+
Update via TodoWrite as you make progress. Keep exactly one item in_progress at a time.`;
|
|
2439
|
+
}
|
|
2440
|
+
};
|
|
2441
|
+
|
|
936
2442
|
// src/loop/agent.ts
|
|
937
2443
|
var Agent = class {
|
|
938
2444
|
constructor(ctx) {
|
|
@@ -940,6 +2446,7 @@ var Agent = class {
|
|
|
940
2446
|
}
|
|
941
2447
|
ctx;
|
|
942
2448
|
messages = [];
|
|
2449
|
+
todos = new TodoStore();
|
|
943
2450
|
getMessages() {
|
|
944
2451
|
return this.messages;
|
|
945
2452
|
}
|
|
@@ -956,10 +2463,14 @@ var Agent = class {
|
|
|
956
2463
|
const tools = this.ctx.tools.toLLMDefinitions(
|
|
957
2464
|
mode === "plan" ? (t) => t.permission === "read" : void 0
|
|
958
2465
|
);
|
|
2466
|
+
const todoSection = this.todos.toPromptSection();
|
|
2467
|
+
const systemPrompt = todoSection ? `${this.ctx.systemPrompt}
|
|
2468
|
+
|
|
2469
|
+
${todoSection}` : this.ctx.systemPrompt;
|
|
959
2470
|
const stream = this.ctx.llm.stream({
|
|
960
2471
|
messages: this.messages,
|
|
961
2472
|
tools,
|
|
962
|
-
systemPrompt
|
|
2473
|
+
systemPrompt,
|
|
963
2474
|
abortSignal: this.ctx.abortSignal
|
|
964
2475
|
});
|
|
965
2476
|
const assistantParts = [];
|
|
@@ -983,6 +2494,7 @@ var Agent = class {
|
|
|
983
2494
|
this.ctx.events?.onTurnEnd?.();
|
|
984
2495
|
return;
|
|
985
2496
|
}
|
|
2497
|
+
this.ctx.events?.onAssistantTurn?.();
|
|
986
2498
|
for (const call of toolCallsToRun) {
|
|
987
2499
|
await this.runToolCall(call);
|
|
988
2500
|
}
|
|
@@ -1048,27 +2560,37 @@ var Agent = class {
|
|
|
1048
2560
|
return;
|
|
1049
2561
|
}
|
|
1050
2562
|
if (decision === "ask") {
|
|
1051
|
-
|
|
1052
|
-
if (
|
|
2563
|
+
const userDecision = await this.ctx.events?.onPermissionRequest?.(call.name, call.args, summary) ?? "no";
|
|
2564
|
+
if (userDecision === "no") {
|
|
1053
2565
|
this.recordToolResult(call.id, call.name, `User rejected ${call.name}.`, true);
|
|
1054
2566
|
return;
|
|
1055
2567
|
}
|
|
2568
|
+
if (userDecision === "session_allow") {
|
|
2569
|
+
this.ctx.permissions.allowForSession(call.name);
|
|
2570
|
+
}
|
|
2571
|
+
approved = true;
|
|
1056
2572
|
}
|
|
1057
2573
|
const toolCtx = {
|
|
1058
2574
|
cwd: this.ctx.cwd,
|
|
1059
2575
|
abortSignal: this.ctx.abortSignal,
|
|
1060
|
-
askPermission: async () => true
|
|
2576
|
+
askPermission: async () => true,
|
|
1061
2577
|
// 已在外层处理
|
|
2578
|
+
todos: this.todos,
|
|
2579
|
+
askQuestions: this.ctx.events?.onAskQuestions ? (qs) => this.ctx.events.onAskQuestions(qs) : void 0
|
|
1062
2580
|
};
|
|
1063
2581
|
const result = await this.ctx.tools.execute(call.name, call.args, toolCtx);
|
|
1064
|
-
this.recordToolResult(call.id, call.name, result.content, result.isError ?? false, result.summary);
|
|
2582
|
+
this.recordToolResult(call.id, call.name, result.content, result.isError ?? false, result.summary, result.diff, result.kind);
|
|
1065
2583
|
}
|
|
1066
|
-
recordToolResult(id, name, content, isError, summary) {
|
|
2584
|
+
recordToolResult(id, name, content, isError, summary, diff, kind) {
|
|
1067
2585
|
const toolMsg = {
|
|
1068
2586
|
role: "tool",
|
|
1069
2587
|
toolUseId: id,
|
|
1070
2588
|
content,
|
|
1071
|
-
isError
|
|
2589
|
+
isError,
|
|
2590
|
+
toolName: name,
|
|
2591
|
+
...diff ? { diff } : {},
|
|
2592
|
+
...summary ? { summary } : {},
|
|
2593
|
+
...kind ? { kind } : {}
|
|
1072
2594
|
};
|
|
1073
2595
|
this.messages.push(toolMsg);
|
|
1074
2596
|
this.ctx.session.append({ type: "message", time: (/* @__PURE__ */ new Date()).toISOString(), message: toolMsg });
|
|
@@ -1079,7 +2601,7 @@ var Agent = class {
|
|
|
1079
2601
|
// src/loop/system-prompt.ts
|
|
1080
2602
|
import { homedir as homedir2 } from "os";
|
|
1081
2603
|
function buildSystemPrompt(opts) {
|
|
1082
|
-
const { cwd, model, provider, lang, toolNames } = opts;
|
|
2604
|
+
const { cwd, model, provider, lang, toolNames, memoryIndex } = opts;
|
|
1083
2605
|
const home = homedir2();
|
|
1084
2606
|
const displayCwd = cwd.startsWith(home) ? cwd.replace(home, "~") : cwd;
|
|
1085
2607
|
const sections = [];
|
|
@@ -1104,18 +2626,113 @@ Prefer the dedicated tool over Bash when one fits (Read for file reading, Edit f
|
|
|
1104
2626
|
- If a command may be destructive (rm -rf, force push, drop table, etc.), warn first and let the user run it manually.
|
|
1105
2627
|
- When the user asks a question that does not need tools, just answer.`
|
|
1106
2628
|
);
|
|
2629
|
+
if (toolNames.includes("TodoWrite")) {
|
|
2630
|
+
sections.push(
|
|
2631
|
+
`# Task management
|
|
2632
|
+
- For non-trivial, multi-step work, use TodoWrite to plan and track progress.
|
|
2633
|
+
- Keep exactly one task in_progress; mark a task completed immediately when done.
|
|
2634
|
+
- Skip it for trivial single-step requests.`
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
1107
2637
|
if (lang === "zh-CN") {
|
|
1108
2638
|
sections.push(`# Output language
|
|
1109
2639
|
Reply in Chinese (\u7B80\u4F53\u4E2D\u6587) unless the user writes in English.`);
|
|
1110
2640
|
}
|
|
1111
|
-
|
|
2641
|
+
if (memoryIndex && memoryIndex.trim()) {
|
|
2642
|
+
sections.push(
|
|
2643
|
+
`# Memory (long-term)
|
|
2644
|
+
Below is MEMORY.md \u2014 your index of persistent facts about the user, project, and prior feedback. Each line points at a file you can MemoryRead. Use MemoryWrite to record new durable knowledge (user role/preferences, validated decisions, project facts, external references). Do NOT save things derivable from the repo or git history.
|
|
2645
|
+
|
|
2646
|
+
` + memoryIndex
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
return sections.join("\n\n");
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// src/loop/memory.ts
|
|
2653
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2654
|
+
import { existsSync } from "fs";
|
|
2655
|
+
import { homedir as homedir3 } from "os";
|
|
2656
|
+
import { join as join2 } from "path";
|
|
2657
|
+
import { createHash } from "crypto";
|
|
2658
|
+
function projectHash(cwd) {
|
|
2659
|
+
return createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
2660
|
+
}
|
|
2661
|
+
function memoryDir(cwd) {
|
|
2662
|
+
return join2(homedir3(), ".muse", "projects", projectHash(cwd), "memory");
|
|
2663
|
+
}
|
|
2664
|
+
function memoryIndexPath(cwd) {
|
|
2665
|
+
return join2(memoryDir(cwd), "MEMORY.md");
|
|
2666
|
+
}
|
|
2667
|
+
function memoryFilePath(cwd, name) {
|
|
2668
|
+
return join2(memoryDir(cwd), `${name}.md`);
|
|
2669
|
+
}
|
|
2670
|
+
async function loadMemoryIndex(cwd, maxLines = 200) {
|
|
2671
|
+
const path = memoryIndexPath(cwd);
|
|
2672
|
+
if (!existsSync(path)) return "";
|
|
2673
|
+
try {
|
|
2674
|
+
const raw = await readFile(path, "utf-8");
|
|
2675
|
+
const lines = raw.split("\n");
|
|
2676
|
+
if (lines.length <= maxLines) return raw.trim();
|
|
2677
|
+
return lines.slice(0, maxLines).join("\n").trim() + `
|
|
2678
|
+
... [truncated; ${lines.length - maxLines} more lines]`;
|
|
2679
|
+
} catch {
|
|
2680
|
+
return "";
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
async function readMemoryFile(cwd, name) {
|
|
2684
|
+
const path = memoryFilePath(cwd, name);
|
|
2685
|
+
if (!existsSync(path)) {
|
|
2686
|
+
throw new Error(`Memory "${name}" does not exist at ${path}.`);
|
|
2687
|
+
}
|
|
2688
|
+
return readFile(path, "utf-8");
|
|
2689
|
+
}
|
|
2690
|
+
async function writeMemory(cwd, opts) {
|
|
2691
|
+
const dir = memoryDir(cwd);
|
|
2692
|
+
await mkdir(dir, { recursive: true });
|
|
2693
|
+
const filePath = memoryFilePath(cwd, opts.name);
|
|
2694
|
+
const frontmatter = [
|
|
2695
|
+
"---",
|
|
2696
|
+
`name: ${opts.name}`,
|
|
2697
|
+
`description: ${opts.description.replace(/\n/g, " ").trim()}`,
|
|
2698
|
+
`metadata:`,
|
|
2699
|
+
` type: ${opts.type}`,
|
|
2700
|
+
"---"
|
|
2701
|
+
].join("\n");
|
|
2702
|
+
const content = `${frontmatter}
|
|
2703
|
+
|
|
2704
|
+
${opts.body.trim()}
|
|
2705
|
+
`;
|
|
2706
|
+
await writeFile(filePath, content, "utf-8");
|
|
2707
|
+
const indexPath = memoryIndexPath(cwd);
|
|
2708
|
+
let index = "";
|
|
2709
|
+
if (existsSync(indexPath)) index = await readFile(indexPath, "utf-8");
|
|
2710
|
+
const lines = index ? index.split("\n") : [];
|
|
2711
|
+
const linePrefix = `- [${opts.name}](${opts.name}.md)`;
|
|
2712
|
+
const newLine = `${linePrefix} \u2014 ${opts.description.replace(/\n/g, " ").trim()}`;
|
|
2713
|
+
const existing = lines.findIndex((l) => l.startsWith(linePrefix));
|
|
2714
|
+
let indexUpdated = false;
|
|
2715
|
+
if (existing >= 0) {
|
|
2716
|
+
if (lines[existing] !== newLine) {
|
|
2717
|
+
lines[existing] = newLine;
|
|
2718
|
+
indexUpdated = true;
|
|
2719
|
+
}
|
|
2720
|
+
} else {
|
|
2721
|
+
lines.push(newLine);
|
|
2722
|
+
indexUpdated = true;
|
|
2723
|
+
}
|
|
2724
|
+
if (indexUpdated) {
|
|
2725
|
+
const out = lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
2726
|
+
await writeFile(indexPath, out, "utf-8");
|
|
2727
|
+
}
|
|
2728
|
+
return { filePath, indexUpdated };
|
|
1112
2729
|
}
|
|
1113
2730
|
|
|
1114
2731
|
// src/config/loader.ts
|
|
1115
|
-
import { readFile } from "fs/promises";
|
|
1116
|
-
import { existsSync } from "fs";
|
|
1117
|
-
import { homedir as
|
|
1118
|
-
import { join as
|
|
2732
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2733
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2734
|
+
import { homedir as homedir4 } from "os";
|
|
2735
|
+
import { join as join3, resolve } from "path";
|
|
1119
2736
|
|
|
1120
2737
|
// src/config/types.ts
|
|
1121
2738
|
import { z } from "zod";
|
|
@@ -1125,8 +2742,8 @@ var ProviderConfigSchema = z.object({
|
|
|
1125
2742
|
extraHeaders: z.record(z.string()).optional()
|
|
1126
2743
|
}).passthrough();
|
|
1127
2744
|
var LLMConfigSchema = z.object({
|
|
1128
|
-
provider: z.string().optional().describe("Fallback provider preset (only used when no models.json entry matches)."),
|
|
1129
|
-
model: z.string().optional().describe("Active model id; should match an id in models.json."),
|
|
2745
|
+
provider: z.string().optional().describe("Fallback provider preset (only used when no models.local.json entry matches)."),
|
|
2746
|
+
model: z.string().optional().describe("Active model id; should match an id in models.local.json."),
|
|
1130
2747
|
temperature: z.number().min(0).max(2).optional(),
|
|
1131
2748
|
maxTokens: z.number().int().positive().optional()
|
|
1132
2749
|
});
|
|
@@ -1191,7 +2808,7 @@ var DEFAULTS = {
|
|
|
1191
2808
|
ollama: { baseUrl: "http://localhost:11434/v1" }
|
|
1192
2809
|
},
|
|
1193
2810
|
permissions: {
|
|
1194
|
-
allow: ["Read", "Grep", "Glob"],
|
|
2811
|
+
allow: ["Read", "Grep", "Glob", "TodoWrite"],
|
|
1195
2812
|
ask: ["Write", "Edit", "Bash"],
|
|
1196
2813
|
deny: [],
|
|
1197
2814
|
defaultMode: "ask"
|
|
@@ -1202,9 +2819,9 @@ var DEFAULTS = {
|
|
|
1202
2819
|
}
|
|
1203
2820
|
};
|
|
1204
2821
|
async function readJsonIfExists(path) {
|
|
1205
|
-
if (!
|
|
2822
|
+
if (!existsSync2(path)) return void 0;
|
|
1206
2823
|
try {
|
|
1207
|
-
const raw = await
|
|
2824
|
+
const raw = await readFile2(path, "utf-8");
|
|
1208
2825
|
return JSON.parse(raw);
|
|
1209
2826
|
} catch (err) {
|
|
1210
2827
|
log.warn(`Failed to parse settings at ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1232,9 +2849,9 @@ async function loadSettings(cwd = process.cwd()) {
|
|
|
1232
2849
|
const sources = ["<defaults>"];
|
|
1233
2850
|
let merged = DEFAULTS;
|
|
1234
2851
|
const candidates = [
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
2852
|
+
join3(homedir4(), ".muse", "settings.json"),
|
|
2853
|
+
join3(cwd, ".muse", "settings.json"),
|
|
2854
|
+
join3(cwd, ".muse", "settings.local.json")
|
|
1238
2855
|
];
|
|
1239
2856
|
for (const path of candidates) {
|
|
1240
2857
|
const raw = await readJsonIfExists(path);
|
|
@@ -1261,10 +2878,10 @@ async function loadSettings(cwd = process.cwd()) {
|
|
|
1261
2878
|
}
|
|
1262
2879
|
|
|
1263
2880
|
// src/config/models.ts
|
|
1264
|
-
import { readFile as
|
|
1265
|
-
import { existsSync as
|
|
1266
|
-
import { homedir as
|
|
1267
|
-
import { join as
|
|
2881
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2882
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2883
|
+
import { homedir as homedir5 } from "os";
|
|
2884
|
+
import { join as join4 } from "path";
|
|
1268
2885
|
import { z as z2 } from "zod";
|
|
1269
2886
|
var ModelEntryInputSchema = z2.object({
|
|
1270
2887
|
id: z2.string().min(1),
|
|
@@ -1285,65 +2902,61 @@ var ModelsRegistryInputSchema = z2.object({
|
|
|
1285
2902
|
/** 不填 = 全部 models 都进 selector;填了就是 selector 子集(按顺序)。 */
|
|
1286
2903
|
availableModels: z2.array(z2.string()).optional()
|
|
1287
2904
|
}).passthrough();
|
|
1288
|
-
var
|
|
1289
|
-
join3(homedir4(), ".muse", "models.json"),
|
|
1290
|
-
join3(homedir4(), ".muse", "models.local.json")
|
|
1291
|
-
];
|
|
2905
|
+
var MODELS_PATH = () => join4(homedir5(), ".muse", "models.local.json");
|
|
1292
2906
|
async function loadModelsRegistry() {
|
|
1293
2907
|
const sources = [];
|
|
1294
2908
|
const errors = [];
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
}
|
|
1307
|
-
const parsed = ModelsRegistryInputSchema.safeParse(raw);
|
|
1308
|
-
if (!parsed.success) {
|
|
1309
|
-
const msg = formatZodIssues2(parsed.error.issues);
|
|
1310
|
-
log.warn(`Invalid models registry at ${path}: ${msg}`);
|
|
1311
|
-
errors.push({ path, message: msg });
|
|
1312
|
-
continue;
|
|
1313
|
-
}
|
|
1314
|
-
const normalized = {
|
|
1315
|
-
...parsed.data,
|
|
1316
|
-
models: parsed.data.models.map(normalizeModelEntry)
|
|
1317
|
-
};
|
|
1318
|
-
merged = mergeRegistries(merged, normalized);
|
|
1319
|
-
sources.push(path);
|
|
2909
|
+
const path = MODELS_PATH();
|
|
2910
|
+
if (!existsSync3(path)) {
|
|
2911
|
+
return { registry: void 0, sources, errors };
|
|
2912
|
+
}
|
|
2913
|
+
let raw;
|
|
2914
|
+
try {
|
|
2915
|
+
raw = JSON.parse(await readFile3(path, "utf-8"));
|
|
2916
|
+
} catch (err) {
|
|
2917
|
+
const msg = `JSON parse error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2918
|
+
log.warn(`Failed to parse ${path}: ${msg}`);
|
|
2919
|
+
errors.push({ path, message: msg });
|
|
2920
|
+
return { registry: void 0, sources, errors };
|
|
1320
2921
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
2922
|
+
const parsed = ModelsRegistryInputSchema.safeParse(raw);
|
|
2923
|
+
if (!parsed.success) {
|
|
2924
|
+
const msg = formatZodIssues2(parsed.error.issues);
|
|
2925
|
+
log.warn(`Invalid models registry at ${path}: ${msg}`);
|
|
2926
|
+
errors.push({ path, message: msg });
|
|
2927
|
+
return { registry: void 0, sources, errors };
|
|
2928
|
+
}
|
|
2929
|
+
const normalized = {
|
|
2930
|
+
...parsed.data,
|
|
2931
|
+
models: parsed.data.models.map(normalizeModelEntry)
|
|
2932
|
+
};
|
|
2933
|
+
sources.push(path);
|
|
2934
|
+
const expanded = expandEnvVars(normalized);
|
|
1323
2935
|
return { registry: expanded, sources, errors };
|
|
1324
2936
|
}
|
|
1325
2937
|
function formatZodIssues2(issues) {
|
|
1326
2938
|
return issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
|
|
1327
2939
|
}
|
|
1328
|
-
function mergeRegistries(low, high) {
|
|
1329
|
-
if (!low) return high;
|
|
1330
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1331
|
-
for (const m of low.models) byId.set(m.id, m);
|
|
1332
|
-
for (const m of high.models) byId.set(m.id, m);
|
|
1333
|
-
return {
|
|
1334
|
-
...low,
|
|
1335
|
-
...high,
|
|
1336
|
-
models: [...byId.values()],
|
|
1337
|
-
availableModels: high.availableModels ?? low.availableModels
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
2940
|
function normalizeModelEntry(entry) {
|
|
1341
2941
|
let baseUrl = (entry.baseUrl ?? entry.url ?? "").replace(/\/+$/, "");
|
|
1342
2942
|
if (baseUrl.endsWith("/chat/completions")) {
|
|
1343
2943
|
baseUrl = baseUrl.slice(0, -"/chat/completions".length);
|
|
1344
2944
|
}
|
|
1345
2945
|
const { url: _url, ...rest } = entry;
|
|
1346
|
-
|
|
2946
|
+
const apiKeyEnvVars = entry.apiKey ? extractEnvVars(entry.apiKey) : [];
|
|
2947
|
+
return {
|
|
2948
|
+
...rest,
|
|
2949
|
+
baseUrl,
|
|
2950
|
+
...apiKeyEnvVars.length > 0 ? { _apiKeyEnvVars: apiKeyEnvVars } : {}
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
var ENV_PLACEHOLDER = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
2954
|
+
function extractEnvVars(s) {
|
|
2955
|
+
const out = [];
|
|
2956
|
+
let m;
|
|
2957
|
+
ENV_PLACEHOLDER.lastIndex = 0;
|
|
2958
|
+
while ((m = ENV_PLACEHOLDER.exec(s)) !== null) out.push(m[1]);
|
|
2959
|
+
return out;
|
|
1347
2960
|
}
|
|
1348
2961
|
function findEntry(registry, modelId) {
|
|
1349
2962
|
return registry.models.find((m) => m.id === modelId);
|
|
@@ -1452,7 +3065,7 @@ async function compactMessages(messages, opts) {
|
|
|
1452
3065
|
}
|
|
1453
3066
|
const older = messages.slice(0, cutoff);
|
|
1454
3067
|
const recent = messages.slice(cutoff);
|
|
1455
|
-
const summary = await summarizeConversation(older, opts.llm, opts.abortSignal);
|
|
3068
|
+
const summary = await summarizeConversation(older, opts.llm, opts.abortSignal, opts.onProgress);
|
|
1456
3069
|
const summaryMessage = {
|
|
1457
3070
|
role: "user",
|
|
1458
3071
|
content: `[Previous conversation summary]
|
|
@@ -1497,7 +3110,7 @@ function hasUnresolvedToolUse(older) {
|
|
|
1497
3110
|
}
|
|
1498
3111
|
return false;
|
|
1499
3112
|
}
|
|
1500
|
-
async function summarizeConversation(older, llm, abortSignal) {
|
|
3113
|
+
async function summarizeConversation(older, llm, abortSignal, onProgress) {
|
|
1501
3114
|
const transcript = renderTranscript(older);
|
|
1502
3115
|
const prompt = [
|
|
1503
3116
|
{
|
|
@@ -1517,8 +3130,10 @@ ${transcript}
|
|
|
1517
3130
|
];
|
|
1518
3131
|
let text = "";
|
|
1519
3132
|
for await (const ev of llm.stream({ messages: prompt, abortSignal })) {
|
|
1520
|
-
if (ev.type === "text")
|
|
1521
|
-
|
|
3133
|
+
if (ev.type === "text") {
|
|
3134
|
+
text += ev.delta;
|
|
3135
|
+
onProgress?.(text.length);
|
|
3136
|
+
} else if (ev.type === "error") throw ev.error;
|
|
1522
3137
|
}
|
|
1523
3138
|
return text.trim() || "(empty summary)";
|
|
1524
3139
|
}
|
|
@@ -1579,16 +3194,16 @@ function getMCPStatus(settings) {
|
|
|
1579
3194
|
}
|
|
1580
3195
|
|
|
1581
3196
|
// src/session/jsonl.ts
|
|
1582
|
-
import { appendFile, mkdir, readdir, readFile as
|
|
1583
|
-
import { existsSync as
|
|
1584
|
-
import { homedir as
|
|
1585
|
-
import { dirname as dirname2, join as
|
|
1586
|
-
import { createHash, randomUUID } from "crypto";
|
|
1587
|
-
function
|
|
1588
|
-
return
|
|
3197
|
+
import { appendFile, mkdir as mkdir2, readdir, readFile as readFile4, stat } from "fs/promises";
|
|
3198
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3199
|
+
import { homedir as homedir6 } from "os";
|
|
3200
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
3201
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
3202
|
+
function projectHash2(cwd) {
|
|
3203
|
+
return createHash2("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
1589
3204
|
}
|
|
1590
3205
|
function sessionsDir(cwd) {
|
|
1591
|
-
return
|
|
3206
|
+
return join5(homedir6(), ".muse", "projects", projectHash2(cwd), "sessions");
|
|
1592
3207
|
}
|
|
1593
3208
|
var Session = class _Session {
|
|
1594
3209
|
meta;
|
|
@@ -1599,8 +3214,8 @@ var Session = class _Session {
|
|
|
1599
3214
|
static async create(cwd) {
|
|
1600
3215
|
const id = randomUUID();
|
|
1601
3216
|
const dir = sessionsDir(cwd);
|
|
1602
|
-
await
|
|
1603
|
-
const path =
|
|
3217
|
+
await mkdir2(dir, { recursive: true });
|
|
3218
|
+
const path = join5(dir, `${id}.jsonl`);
|
|
1604
3219
|
const meta = {
|
|
1605
3220
|
id,
|
|
1606
3221
|
cwd,
|
|
@@ -1616,7 +3231,7 @@ var Session = class _Session {
|
|
|
1616
3231
|
}
|
|
1617
3232
|
static async resolve(cwd, idOrPrefix) {
|
|
1618
3233
|
const dir = sessionsDir(cwd);
|
|
1619
|
-
if (!
|
|
3234
|
+
if (!existsSync4(dir)) return void 0;
|
|
1620
3235
|
const entries = await readdir(dir);
|
|
1621
3236
|
const matches = entries.filter((e) => e.endsWith(".jsonl") && e.startsWith(idOrPrefix));
|
|
1622
3237
|
if (matches.length === 0) return void 0;
|
|
@@ -1624,12 +3239,12 @@ var Session = class _Session {
|
|
|
1624
3239
|
throw new Error(`Ambiguous session id "${idOrPrefix}" matches ${matches.length} sessions; use more characters.`);
|
|
1625
3240
|
}
|
|
1626
3241
|
const top = matches[0];
|
|
1627
|
-
const st = await stat(
|
|
3242
|
+
const st = await stat(join5(dir, top));
|
|
1628
3243
|
return {
|
|
1629
3244
|
id: top.replace(/\.jsonl$/, ""),
|
|
1630
3245
|
cwd,
|
|
1631
3246
|
createdAt: st.mtime.toISOString(),
|
|
1632
|
-
path:
|
|
3247
|
+
path: join5(dir, top)
|
|
1633
3248
|
};
|
|
1634
3249
|
}
|
|
1635
3250
|
/**
|
|
@@ -1638,13 +3253,13 @@ var Session = class _Session {
|
|
|
1638
3253
|
*/
|
|
1639
3254
|
static async listAll(cwd, limit) {
|
|
1640
3255
|
const dir = sessionsDir(cwd);
|
|
1641
|
-
if (!
|
|
3256
|
+
if (!existsSync4(dir)) return [];
|
|
1642
3257
|
const entries = await readdir(dir);
|
|
1643
3258
|
const files = entries.filter((e) => e.endsWith(".jsonl"));
|
|
1644
3259
|
if (files.length === 0) return [];
|
|
1645
3260
|
const stats = await Promise.all(
|
|
1646
3261
|
files.map(async (f) => {
|
|
1647
|
-
const path =
|
|
3262
|
+
const path = join5(dir, f);
|
|
1648
3263
|
const st = await stat(path);
|
|
1649
3264
|
return { file: f, path, mtime: st.mtime };
|
|
1650
3265
|
})
|
|
@@ -1681,7 +3296,7 @@ var Session = class _Session {
|
|
|
1681
3296
|
const line = JSON.stringify(event) + "\n";
|
|
1682
3297
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
1683
3298
|
try {
|
|
1684
|
-
await
|
|
3299
|
+
await mkdir2(dirname2(this.meta.path), { recursive: true });
|
|
1685
3300
|
await appendFile(this.meta.path, line, "utf-8");
|
|
1686
3301
|
} catch (err) {
|
|
1687
3302
|
log.warn(`session append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1690,8 +3305,8 @@ var Session = class _Session {
|
|
|
1690
3305
|
return this.writeQueue;
|
|
1691
3306
|
}
|
|
1692
3307
|
async readAll() {
|
|
1693
|
-
if (!
|
|
1694
|
-
const raw = await
|
|
3308
|
+
if (!existsSync4(this.meta.path)) return [];
|
|
3309
|
+
const raw = await readFile4(this.meta.path, "utf-8");
|
|
1695
3310
|
const events = [];
|
|
1696
3311
|
for (const line of raw.split("\n")) {
|
|
1697
3312
|
if (!line.trim()) continue;
|
|
@@ -1706,7 +3321,7 @@ var Session = class _Session {
|
|
|
1706
3321
|
async function readSummary(meta) {
|
|
1707
3322
|
let events = [];
|
|
1708
3323
|
try {
|
|
1709
|
-
const raw = await
|
|
3324
|
+
const raw = await readFile4(meta.path, "utf-8");
|
|
1710
3325
|
for (const line of raw.split("\n")) {
|
|
1711
3326
|
if (!line.trim()) continue;
|
|
1712
3327
|
try {
|
|
@@ -1728,9 +3343,9 @@ async function readSummary(meta) {
|
|
|
1728
3343
|
}
|
|
1729
3344
|
|
|
1730
3345
|
// src/slash/_format.ts
|
|
1731
|
-
import { homedir as
|
|
3346
|
+
import { homedir as homedir7 } from "os";
|
|
1732
3347
|
function shortPath(p) {
|
|
1733
|
-
const home =
|
|
3348
|
+
const home = homedir7();
|
|
1734
3349
|
if (p === home) return "~";
|
|
1735
3350
|
if (p.startsWith(home + "/")) return "~" + p.slice(home.length);
|
|
1736
3351
|
return p;
|
|
@@ -1828,6 +3443,18 @@ var COST = {
|
|
|
1828
3443
|
return { display: lines.join("\n") };
|
|
1829
3444
|
}
|
|
1830
3445
|
};
|
|
3446
|
+
var COMPACT_TIPS = [
|
|
3447
|
+
"Shift+Tab cycles permission modes (default / acceptEdits / plan / bypass)",
|
|
3448
|
+
"/mode plan drafts changes without executing them",
|
|
3449
|
+
"/cost shows token usage and estimated spend",
|
|
3450
|
+
"/resume picks up a previous session in this directory",
|
|
3451
|
+
"muse --continue resumes the last session on startup",
|
|
3452
|
+
"MemoryWrite saves persistent knowledge across sessions",
|
|
3453
|
+
"TodoWrite keeps the model honest on multi-step tasks",
|
|
3454
|
+
'Pipe to muse: cat bug.log | muse "explain this"',
|
|
3455
|
+
"Ctrl+C exits immediately; Esc rejects a pending tool"
|
|
3456
|
+
];
|
|
3457
|
+
var COMPACT_ESTIMATED_CHARS = 1800;
|
|
1831
3458
|
var COMPACT = {
|
|
1832
3459
|
name: "compact",
|
|
1833
3460
|
description: "summarize older messages to free up context space",
|
|
@@ -1837,23 +3464,39 @@ var COMPACT = {
|
|
|
1837
3464
|
const { flags } = parseArgs(ctx.args);
|
|
1838
3465
|
const keepRecent = typeof flags.keep === "string" ? Math.max(1, parseInt(flags.keep, 10)) : 4;
|
|
1839
3466
|
if (Number.isNaN(keepRecent)) return { display: `Invalid --keep value: ${flags.keep}` };
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
3467
|
+
const progressRef = { chars: 0 };
|
|
3468
|
+
ctx.actions.showProgress({
|
|
3469
|
+
title: "Compacting conversation",
|
|
3470
|
+
tips: COMPACT_TIPS,
|
|
3471
|
+
getPercent: () => progressRef.chars / COMPACT_ESTIMATED_CHARS * 100
|
|
3472
|
+
});
|
|
3473
|
+
try {
|
|
3474
|
+
const result = await compactMessages(ctx.history, {
|
|
3475
|
+
llm: ctx.llm,
|
|
3476
|
+
keepRecent,
|
|
3477
|
+
onProgress: (chars) => {
|
|
3478
|
+
progressRef.chars = chars;
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
if (result.noop) {
|
|
3482
|
+
return { display: `(history has ${result.originalCount} messages; not enough to compact with --keep ${keepRecent})` };
|
|
3483
|
+
}
|
|
3484
|
+
ctx.actions.setMessages(result.newMessages);
|
|
3485
|
+
const preview = result.summary.length > 240 ? result.summary.slice(0, 240) + "\u2026" : result.summary;
|
|
3486
|
+
return {
|
|
3487
|
+
display: `Compacted ${result.originalCount} \u2192 ${result.newCount} messages (kept last ${keepRecent}).
|
|
1848
3488
|
|
|
1849
3489
|
Summary:
|
|
1850
3490
|
${preview}`
|
|
1851
|
-
|
|
3491
|
+
};
|
|
3492
|
+
} finally {
|
|
3493
|
+
ctx.actions.hideProgress();
|
|
3494
|
+
}
|
|
1852
3495
|
}
|
|
1853
3496
|
};
|
|
1854
3497
|
var MODELS = {
|
|
1855
3498
|
name: "models",
|
|
1856
|
-
description: "pick a model from ~/.muse/models.json (\u2191\u2193 to navigate)",
|
|
3499
|
+
description: "pick a model from ~/.muse/models.local.json (\u2191\u2193 to navigate)",
|
|
1857
3500
|
async execute(ctx) {
|
|
1858
3501
|
let registry = ctx.modelsRegistry;
|
|
1859
3502
|
let errors = [];
|
|
@@ -1871,7 +3514,7 @@ var MODELS = {
|
|
|
1871
3514
|
const visible = visibleEntries(registry);
|
|
1872
3515
|
if (visible.length === 0) {
|
|
1873
3516
|
return {
|
|
1874
|
-
display: `models.json has no available models.
|
|
3517
|
+
display: `models.local.json has no available models.
|
|
1875
3518
|
Check that "availableModels" lists at least one id present in "models".`
|
|
1876
3519
|
};
|
|
1877
3520
|
}
|
|
@@ -1889,7 +3532,7 @@ Check that "availableModels" lists at least one id present in "models".`
|
|
|
1889
3532
|
};
|
|
1890
3533
|
function renderLoadErrors(errors) {
|
|
1891
3534
|
return [
|
|
1892
|
-
`models.json was found but failed to load:`,
|
|
3535
|
+
`models.local.json was found but failed to load:`,
|
|
1893
3536
|
``,
|
|
1894
3537
|
...errors.flatMap((e) => [` ${shortPath(e.path)}`, ` ${e.message}`]),
|
|
1895
3538
|
``,
|
|
@@ -1900,7 +3543,7 @@ function renderLoadErrors(errors) {
|
|
|
1900
3543
|
function renderEmptyRegistryHint() {
|
|
1901
3544
|
return [
|
|
1902
3545
|
`No models registry found.`,
|
|
1903
|
-
`Create ~/.muse/models.json with a "models" array. Example:`,
|
|
3546
|
+
`Create ~/.muse/models.local.json with a "models" array. Example:`,
|
|
1904
3547
|
``,
|
|
1905
3548
|
`{`,
|
|
1906
3549
|
` "models": [`,
|
|
@@ -2038,6 +3681,44 @@ async function loadAndReport(meta, ctx) {
|
|
|
2038
3681
|
display: `Resumed session ${meta.id.slice(0, 8)} (${messages.length} messages from ${formatTime2(meta.createdAt)}).`
|
|
2039
3682
|
};
|
|
2040
3683
|
}
|
|
3684
|
+
var MODE_ALIASES = {
|
|
3685
|
+
default: "default",
|
|
3686
|
+
normal: "default",
|
|
3687
|
+
acceptedits: "acceptEdits",
|
|
3688
|
+
"accept-edits": "acceptEdits",
|
|
3689
|
+
accept: "acceptEdits",
|
|
3690
|
+
edits: "acceptEdits",
|
|
3691
|
+
plan: "plan",
|
|
3692
|
+
bypass: "bypassPermissions",
|
|
3693
|
+
bypasspermissions: "bypassPermissions"
|
|
3694
|
+
};
|
|
3695
|
+
var MODE_CMD = {
|
|
3696
|
+
name: "mode",
|
|
3697
|
+
description: "show or switch the permission mode (alternative to Shift+Tab)",
|
|
3698
|
+
argsHint: "[default|acceptEdits|plan|bypassPermissions]",
|
|
3699
|
+
execute(ctx) {
|
|
3700
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
3701
|
+
if (!arg) {
|
|
3702
|
+
const cur = ctx.actions.getMode();
|
|
3703
|
+
const lines = [`Current permission mode: ${cur} \u2014 ${MODE_LABEL[cur]}`, ``, `Available modes:`];
|
|
3704
|
+
for (const m of MODE_CYCLE) {
|
|
3705
|
+
const marker = m === cur ? "\u25CF" : " ";
|
|
3706
|
+
lines.push(` ${marker} ${m.padEnd(20)} ${MODE_LABEL[m]}`);
|
|
3707
|
+
}
|
|
3708
|
+
lines.push(``, `Switch: /mode <name> or Shift+Tab to cycle`);
|
|
3709
|
+
return { display: lines.join("\n") };
|
|
3710
|
+
}
|
|
3711
|
+
const target = MODE_ALIASES[arg];
|
|
3712
|
+
if (!target) {
|
|
3713
|
+
return {
|
|
3714
|
+
display: `Unknown mode "${ctx.args.trim()}". Valid: ${MODE_CYCLE.join(" | ")}`
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
if (target === ctx.actions.getMode()) return { display: `Already in ${target} mode.` };
|
|
3718
|
+
ctx.actions.setMode(target);
|
|
3719
|
+
return { display: `Switched to ${target} \u2014 ${MODE_LABEL[target]}` };
|
|
3720
|
+
}
|
|
3721
|
+
};
|
|
2041
3722
|
var BUILTIN_SLASH_COMMANDS = [
|
|
2042
3723
|
HELP,
|
|
2043
3724
|
CLEAR,
|
|
@@ -2045,31 +3726,53 @@ var BUILTIN_SLASH_COMMANDS = [
|
|
|
2045
3726
|
MODELS,
|
|
2046
3727
|
CONFIG,
|
|
2047
3728
|
MCP,
|
|
3729
|
+
MODE_CMD,
|
|
2048
3730
|
COST,
|
|
2049
3731
|
RESUME,
|
|
2050
3732
|
QUIT
|
|
2051
3733
|
];
|
|
2052
3734
|
|
|
2053
3735
|
// src/app.tsx
|
|
2054
|
-
import { jsx as
|
|
3736
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2055
3737
|
function reducer(state, action) {
|
|
2056
3738
|
switch (action.type) {
|
|
2057
3739
|
case "user_submit":
|
|
2058
|
-
return {
|
|
3740
|
+
return {
|
|
3741
|
+
...state,
|
|
3742
|
+
streamingText: "",
|
|
3743
|
+
status: "streaming",
|
|
3744
|
+
runningTool: null,
|
|
3745
|
+
turnStartTime: Date.now(),
|
|
3746
|
+
turnFirstTextTime: null,
|
|
3747
|
+
turnInputTokens: 0
|
|
3748
|
+
};
|
|
2059
3749
|
case "history_set":
|
|
2060
3750
|
return { ...state, history: action.messages };
|
|
2061
3751
|
case "stream_delta":
|
|
2062
|
-
return {
|
|
3752
|
+
return {
|
|
3753
|
+
...state,
|
|
3754
|
+
streamingText: state.streamingText + action.delta,
|
|
3755
|
+
status: state.status === "tool" ? "streaming" : state.status,
|
|
3756
|
+
runningTool: null,
|
|
3757
|
+
turnFirstTextTime: state.turnFirstTextTime ?? Date.now()
|
|
3758
|
+
};
|
|
2063
3759
|
case "stream_reset":
|
|
2064
3760
|
return { ...state, streamingText: "" };
|
|
2065
3761
|
case "set_status":
|
|
2066
|
-
return {
|
|
3762
|
+
return {
|
|
3763
|
+
...state,
|
|
3764
|
+
status: action.status,
|
|
3765
|
+
runningTool: action.status === "tool" ? state.runningTool : null
|
|
3766
|
+
};
|
|
3767
|
+
case "tool_start":
|
|
3768
|
+
return { ...state, status: "tool", runningTool: action.name };
|
|
2067
3769
|
case "add_usage":
|
|
2068
3770
|
return {
|
|
2069
3771
|
...state,
|
|
2070
3772
|
inputTokens: state.inputTokens + action.usage.inputTokens,
|
|
2071
3773
|
outputTokens: state.outputTokens + action.usage.outputTokens,
|
|
2072
|
-
totalTokens: state.totalTokens + action.usage.totalTokens
|
|
3774
|
+
totalTokens: state.totalTokens + action.usage.totalTokens,
|
|
3775
|
+
turnInputTokens: state.turnInputTokens + action.usage.inputTokens
|
|
2073
3776
|
};
|
|
2074
3777
|
}
|
|
2075
3778
|
}
|
|
@@ -2087,34 +3790,66 @@ function App({
|
|
|
2087
3790
|
initialMessages
|
|
2088
3791
|
}) {
|
|
2089
3792
|
const { exit } = useApp();
|
|
2090
|
-
const { stdout } =
|
|
3793
|
+
const { stdout } = useStdout2();
|
|
2091
3794
|
const termWidth = stdout?.columns ?? 80;
|
|
2092
|
-
const [llm, setLLM] =
|
|
2093
|
-
const [permissions, setPermissions] =
|
|
2094
|
-
const [settings, setSettings] =
|
|
2095
|
-
const [settingsSources, setSettingsSources] =
|
|
2096
|
-
const [modelsRegistry, setModelsRegistry] =
|
|
2097
|
-
const [mode, setMode] =
|
|
3795
|
+
const [llm, setLLM] = useState8(initialLLM);
|
|
3796
|
+
const [permissions, setPermissions] = useState8(initialPermissions);
|
|
3797
|
+
const [settings, setSettings] = useState8(initialSettings);
|
|
3798
|
+
const [settingsSources, setSettingsSources] = useState8(initialSources);
|
|
3799
|
+
const [modelsRegistry, setModelsRegistry] = useState8(initialModelsRegistry);
|
|
3800
|
+
const [mode, setMode] = useState8(initialPermissions.getMode());
|
|
2098
3801
|
const [state, dispatch] = useReducer(reducer, {
|
|
2099
3802
|
history: initialMessages ?? [],
|
|
2100
3803
|
streamingText: "",
|
|
2101
3804
|
status: "idle",
|
|
3805
|
+
runningTool: null,
|
|
2102
3806
|
inputTokens: 0,
|
|
2103
3807
|
outputTokens: 0,
|
|
2104
|
-
totalTokens: 0
|
|
3808
|
+
totalTokens: 0,
|
|
3809
|
+
turnStartTime: 0,
|
|
3810
|
+
turnFirstTextTime: null,
|
|
3811
|
+
turnInputTokens: 0
|
|
2105
3812
|
});
|
|
2106
3813
|
const messagesRef = useRef(initialMessages ?? []);
|
|
2107
|
-
const [input, setInput] =
|
|
2108
|
-
const [inputRemountKey, setInputRemountKey] =
|
|
3814
|
+
const [input, setInput] = useState8("");
|
|
3815
|
+
const [inputRemountKey, setInputRemountKey] = useState8(0);
|
|
2109
3816
|
const commitInput = (value) => {
|
|
2110
3817
|
setInput(value);
|
|
2111
3818
|
setInputRemountKey((k) => k + 1);
|
|
2112
3819
|
};
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
3820
|
+
const pasteRegistryRef = useRef({
|
|
3821
|
+
map: /* @__PURE__ */ new Map(),
|
|
3822
|
+
nextId: 1
|
|
3823
|
+
});
|
|
3824
|
+
const handlePaste = useCallback((chunk) => {
|
|
3825
|
+
const reg = pasteRegistryRef.current;
|
|
3826
|
+
const id = reg.nextId++;
|
|
3827
|
+
reg.map.set(id, chunk);
|
|
3828
|
+
const lines = chunk.split("\n").length;
|
|
3829
|
+
return `[Pasted text #${id} +${lines} lines]`;
|
|
3830
|
+
}, []);
|
|
3831
|
+
const [pending, setPending] = useState8(null);
|
|
3832
|
+
const [picker, setPicker] = useState8(null);
|
|
3833
|
+
const [sessionPicker, setSessionPicker] = useState8(null);
|
|
3834
|
+
const [questionPicker, setQuestionPicker] = useState8(null);
|
|
3835
|
+
const [autocompleteIndex, setAutocompleteIndex] = useState8(0);
|
|
3836
|
+
const [progress, setProgress] = useState8(null);
|
|
2117
3837
|
const agentRef = useRef(null);
|
|
3838
|
+
const queuedInputsRef = useRef([]);
|
|
3839
|
+
const [queuedInputs, setQueuedInputs] = useState8([]);
|
|
3840
|
+
const enqueueInput = (text) => {
|
|
3841
|
+
queuedInputsRef.current.push(text);
|
|
3842
|
+
setQueuedInputs([...queuedInputsRef.current]);
|
|
3843
|
+
};
|
|
3844
|
+
const dequeueInput = () => {
|
|
3845
|
+
if (queuedInputsRef.current.length === 0) return null;
|
|
3846
|
+
const front = queuedInputsRef.current.shift();
|
|
3847
|
+
setQueuedInputs([...queuedInputsRef.current]);
|
|
3848
|
+
return front;
|
|
3849
|
+
};
|
|
3850
|
+
const inputHistoryRef = useRef(extractUserInputs(initialMessages ?? []));
|
|
3851
|
+
const historyIndexRef = useRef(-1);
|
|
3852
|
+
const savedDraftRef = useRef("");
|
|
2118
3853
|
const slash = useMemo2(() => {
|
|
2119
3854
|
const r = new SlashRegistry();
|
|
2120
3855
|
r.registerAll(BUILTIN_SLASH_COMMANDS);
|
|
@@ -2131,17 +3866,48 @@ function App({
|
|
|
2131
3866
|
) : all;
|
|
2132
3867
|
return { matches, query };
|
|
2133
3868
|
}, [input, slash]);
|
|
2134
|
-
|
|
3869
|
+
useEffect5(() => {
|
|
2135
3870
|
const len = autocomplete?.matches.length ?? 0;
|
|
2136
3871
|
if (autocompleteIndex >= len) setAutocompleteIndex(0);
|
|
2137
3872
|
}, [autocomplete, autocompleteIndex]);
|
|
2138
|
-
|
|
3873
|
+
useEffect5(() => {
|
|
3874
|
+
const project = basename(cwd) || "muse";
|
|
3875
|
+
const baseIdle = `muse \xB7 ${project}`;
|
|
3876
|
+
if (state.status === "idle") {
|
|
3877
|
+
setTerminalTitle(baseIdle);
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3881
|
+
let i = 0;
|
|
3882
|
+
const id = setInterval(() => {
|
|
3883
|
+
const frame = FRAMES[i % FRAMES.length];
|
|
3884
|
+
const tail = state.runningTool ? ` \xB7 ${state.runningTool}` : "";
|
|
3885
|
+
setTerminalTitle(`${frame} muse \xB7 ${project}${tail}`);
|
|
3886
|
+
i++;
|
|
3887
|
+
}, 100);
|
|
3888
|
+
return () => clearInterval(id);
|
|
3889
|
+
}, [state.status, state.runningTool, cwd]);
|
|
3890
|
+
useEffect5(() => {
|
|
3891
|
+
return () => resetTerminalTitle();
|
|
3892
|
+
}, []);
|
|
3893
|
+
const [memoryIndex, setMemoryIndex] = useState8("");
|
|
3894
|
+
useEffect5(() => {
|
|
3895
|
+
let cancelled = false;
|
|
3896
|
+
loadMemoryIndex(cwd).then((idx) => {
|
|
3897
|
+
if (!cancelled) setMemoryIndex(idx);
|
|
3898
|
+
});
|
|
3899
|
+
return () => {
|
|
3900
|
+
cancelled = true;
|
|
3901
|
+
};
|
|
3902
|
+
}, [cwd]);
|
|
3903
|
+
useEffect5(() => {
|
|
2139
3904
|
const systemPrompt = buildSystemPrompt({
|
|
2140
3905
|
cwd,
|
|
2141
3906
|
model: llm.model,
|
|
2142
3907
|
provider: llm.providerName,
|
|
2143
3908
|
lang,
|
|
2144
|
-
toolNames: tools.list().map((t) => t.name)
|
|
3909
|
+
toolNames: tools.list().map((t) => t.name),
|
|
3910
|
+
memoryIndex
|
|
2145
3911
|
});
|
|
2146
3912
|
const agent = new Agent({
|
|
2147
3913
|
llm,
|
|
@@ -2152,7 +3918,23 @@ function App({
|
|
|
2152
3918
|
systemPrompt,
|
|
2153
3919
|
events: {
|
|
2154
3920
|
onText: (delta) => dispatch({ type: "stream_delta", delta }),
|
|
2155
|
-
onToolCallStart: () => dispatch({ type: "
|
|
3921
|
+
onToolCallStart: (_id, name) => dispatch({ type: "tool_start", name }),
|
|
3922
|
+
// assistant 流刚结束、这一批 calls 已落到 messages 但 tool 还没开始执行:
|
|
3923
|
+
// 立刻同步 history,让所有 ⏺ Tool(...) 调用头一次性显示出来(之前要等第一个
|
|
3924
|
+
// result 才能见到任何东西,看起来像"卡死了")
|
|
3925
|
+
onAssistantTurn: () => {
|
|
3926
|
+
const msgs = [...agent.getMessages()];
|
|
3927
|
+
messagesRef.current = msgs;
|
|
3928
|
+
dispatch({ type: "history_set", messages: msgs });
|
|
3929
|
+
dispatch({ type: "stream_reset" });
|
|
3930
|
+
},
|
|
3931
|
+
// 每个 tool result 到位就同步:result 立刻挂到对应 ⏺ 调用的 └ 树枝下方
|
|
3932
|
+
onToolResult: () => {
|
|
3933
|
+
const msgs = [...agent.getMessages()];
|
|
3934
|
+
messagesRef.current = msgs;
|
|
3935
|
+
dispatch({ type: "history_set", messages: msgs });
|
|
3936
|
+
dispatch({ type: "set_status", status: "streaming" });
|
|
3937
|
+
},
|
|
2156
3938
|
onUsage: (usage) => dispatch({ type: "add_usage", usage }),
|
|
2157
3939
|
onTurnEnd: () => {
|
|
2158
3940
|
const msgs = [...agent.getMessages()];
|
|
@@ -2160,6 +3942,20 @@ function App({
|
|
|
2160
3942
|
dispatch({ type: "history_set", messages: msgs });
|
|
2161
3943
|
dispatch({ type: "stream_reset" });
|
|
2162
3944
|
dispatch({ type: "set_status", status: "idle" });
|
|
3945
|
+
const next = dequeueInput();
|
|
3946
|
+
if (next) {
|
|
3947
|
+
setTimeout(() => {
|
|
3948
|
+
dispatch({ type: "user_submit" });
|
|
3949
|
+
const expanded = expandPastes(next, pasteRegistryRef.current.map);
|
|
3950
|
+
agent.runTurn(expanded).catch((err) => {
|
|
3951
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
3952
|
+
dispatch({ type: "stream_delta", delta: `
|
|
3953
|
+
[error] ${m}
|
|
3954
|
+
` });
|
|
3955
|
+
dispatch({ type: "set_status", status: "idle" });
|
|
3956
|
+
});
|
|
3957
|
+
}, 0);
|
|
3958
|
+
}
|
|
2163
3959
|
},
|
|
2164
3960
|
onError: (err) => {
|
|
2165
3961
|
dispatch({ type: "stream_delta", delta: `
|
|
@@ -2167,15 +3963,18 @@ function App({
|
|
|
2167
3963
|
` });
|
|
2168
3964
|
dispatch({ type: "set_status", status: "idle" });
|
|
2169
3965
|
},
|
|
2170
|
-
onPermissionRequest: (toolName, args, summary) => new Promise((
|
|
2171
|
-
setPending({ toolName, args, summary, resolve:
|
|
3966
|
+
onPermissionRequest: (toolName, args, summary) => new Promise((resolve6) => {
|
|
3967
|
+
setPending({ toolName, args, summary, resolve: resolve6 });
|
|
3968
|
+
}),
|
|
3969
|
+
onAskQuestions: (questions) => new Promise((resolve6) => {
|
|
3970
|
+
setQuestionPicker({ questions, resolve: resolve6 });
|
|
2172
3971
|
})
|
|
2173
3972
|
}
|
|
2174
3973
|
});
|
|
2175
3974
|
agent.setMessages(messagesRef.current);
|
|
2176
3975
|
agentRef.current = agent;
|
|
2177
|
-
}, [llm, tools, permissions, session, cwd, lang]);
|
|
2178
|
-
|
|
3976
|
+
}, [llm, tools, permissions, session, cwd, lang, memoryIndex]);
|
|
3977
|
+
useInput5(
|
|
2179
3978
|
(inputKey, key) => {
|
|
2180
3979
|
if (key.ctrl && inputKey === "c") {
|
|
2181
3980
|
exit();
|
|
@@ -2186,22 +3985,43 @@ function App({
|
|
|
2186
3985
|
setMode(next);
|
|
2187
3986
|
return;
|
|
2188
3987
|
}
|
|
2189
|
-
if (
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
3988
|
+
if (autocomplete && autocomplete.matches.length > 0) {
|
|
3989
|
+
const len = autocomplete.matches.length;
|
|
3990
|
+
if (key.upArrow) {
|
|
3991
|
+
setAutocompleteIndex((i) => (i - 1 + len) % len);
|
|
3992
|
+
} else if (key.downArrow) {
|
|
3993
|
+
setAutocompleteIndex((i) => (i + 1) % len);
|
|
3994
|
+
} else if (key.tab) {
|
|
3995
|
+
const picked = autocomplete.matches[autocompleteIndex];
|
|
3996
|
+
if (picked) commitInput(`/${picked.name}`);
|
|
3997
|
+
} else if (key.escape) {
|
|
3998
|
+
commitInput("");
|
|
3999
|
+
}
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
const hist = inputHistoryRef.current;
|
|
4003
|
+
if (key.upArrow && hist.length > 0) {
|
|
4004
|
+
const cur = historyIndexRef.current;
|
|
4005
|
+
if (cur === -1) savedDraftRef.current = input;
|
|
4006
|
+
const next = Math.min(cur + 1, hist.length - 1);
|
|
4007
|
+
historyIndexRef.current = next;
|
|
4008
|
+
commitInput(hist[hist.length - 1 - next] ?? "");
|
|
2193
4009
|
} else if (key.downArrow) {
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
commitInput("");
|
|
4010
|
+
const cur = historyIndexRef.current;
|
|
4011
|
+
if (cur === -1) return;
|
|
4012
|
+
const next = cur - 1;
|
|
4013
|
+
historyIndexRef.current = next;
|
|
4014
|
+
if (next === -1) commitInput(savedDraftRef.current);
|
|
4015
|
+
else commitInput(hist[hist.length - 1 - next] ?? "");
|
|
2200
4016
|
}
|
|
2201
4017
|
},
|
|
2202
|
-
|
|
4018
|
+
// 模型在跑时也要响应键盘(让用户能 Ctrl+C / Shift+Tab / autocomplete 导航);
|
|
4019
|
+
// 仅模态弹起时让出键盘所有权
|
|
4020
|
+
{ isActive: !pending && !picker && !sessionPicker && !questionPicker }
|
|
2203
4021
|
);
|
|
2204
|
-
const acceptingInput =
|
|
4022
|
+
const acceptingInput = pending === null && picker === null && sessionPicker === null && questionPicker === null;
|
|
4023
|
+
const inputVisible = pending === null && picker === null && sessionPicker === null;
|
|
4024
|
+
const inputPlaceholder = questionPicker ? "Chat about this" : void 0;
|
|
2205
4025
|
const actions = useMemo2(
|
|
2206
4026
|
() => ({
|
|
2207
4027
|
setMessages: (msgs) => {
|
|
@@ -2209,11 +4029,11 @@ function App({
|
|
|
2209
4029
|
agentRef.current?.setMessages(msgs);
|
|
2210
4030
|
dispatch({ type: "history_set", messages: msgs });
|
|
2211
4031
|
},
|
|
2212
|
-
pickModel: (items, currentId) => new Promise((
|
|
2213
|
-
setPicker({ items, currentId, resolve:
|
|
4032
|
+
pickModel: (items, currentId) => new Promise((resolve6) => {
|
|
4033
|
+
setPicker({ items, currentId, resolve: resolve6 });
|
|
2214
4034
|
}),
|
|
2215
|
-
pickSession: (items, currentId) => new Promise((
|
|
2216
|
-
setSessionPicker({ items, currentId, resolve:
|
|
4035
|
+
pickSession: (items, currentId) => new Promise((resolve6) => {
|
|
4036
|
+
setSessionPicker({ items, currentId, resolve: resolve6 });
|
|
2217
4037
|
}),
|
|
2218
4038
|
switchModel: async (modelId) => {
|
|
2219
4039
|
if (!modelsRegistry) throw new Error("No models registry loaded.");
|
|
@@ -2224,6 +4044,20 @@ function App({
|
|
|
2224
4044
|
setLLM(next);
|
|
2225
4045
|
await persistActiveModel(modelId);
|
|
2226
4046
|
},
|
|
4047
|
+
getMode: () => permissions.getMode(),
|
|
4048
|
+
setMode: (m) => {
|
|
4049
|
+
permissions.setMode(m);
|
|
4050
|
+
setMode(m);
|
|
4051
|
+
},
|
|
4052
|
+
showProgress: (opts) => {
|
|
4053
|
+
setProgress({
|
|
4054
|
+
title: opts.title,
|
|
4055
|
+
tips: opts.tips ?? [],
|
|
4056
|
+
getPercent: opts.getPercent ?? (() => 0),
|
|
4057
|
+
startTime: Date.now()
|
|
4058
|
+
});
|
|
4059
|
+
},
|
|
4060
|
+
hideProgress: () => setProgress(null),
|
|
2227
4061
|
reloadSettings: async () => {
|
|
2228
4062
|
const { settings: nextSettings, sources } = await loadSettings(cwd);
|
|
2229
4063
|
const { registry: nextModels } = await loadModelsRegistry();
|
|
@@ -2253,7 +4087,7 @@ function App({
|
|
|
2253
4087
|
return { settings: nextSettings, sources };
|
|
2254
4088
|
}
|
|
2255
4089
|
}),
|
|
2256
|
-
[cwd, modelsRegistry, llm.model]
|
|
4090
|
+
[cwd, modelsRegistry, llm.model, permissions]
|
|
2257
4091
|
);
|
|
2258
4092
|
const handleSubmit = useCallback(
|
|
2259
4093
|
async (value) => {
|
|
@@ -2271,6 +4105,10 @@ function App({
|
|
|
2271
4105
|
}
|
|
2272
4106
|
const parsed = parseSlash(trimmed);
|
|
2273
4107
|
if (parsed) {
|
|
4108
|
+
if (state.status !== "idle") {
|
|
4109
|
+
commitInput("");
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
2274
4112
|
const cmd = slash.get(parsed.name);
|
|
2275
4113
|
commitInput("");
|
|
2276
4114
|
if (!cmd) {
|
|
@@ -2305,9 +4143,25 @@ function App({
|
|
|
2305
4143
|
return;
|
|
2306
4144
|
}
|
|
2307
4145
|
commitInput("");
|
|
4146
|
+
const hist = inputHistoryRef.current;
|
|
4147
|
+
if (hist[hist.length - 1] !== trimmed) hist.push(trimmed);
|
|
4148
|
+
if (hist.length > 200) hist.shift();
|
|
4149
|
+
historyIndexRef.current = -1;
|
|
4150
|
+
savedDraftRef.current = "";
|
|
4151
|
+
if (state.status !== "idle") {
|
|
4152
|
+
enqueueInput(trimmed);
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
2308
4155
|
dispatch({ type: "user_submit" });
|
|
4156
|
+
const expanded = expandPastes(trimmed, pasteRegistryRef.current.map);
|
|
4157
|
+
{
|
|
4158
|
+
const userMsg = { role: "user", content: expanded };
|
|
4159
|
+
const next = [...messagesRef.current, userMsg];
|
|
4160
|
+
messagesRef.current = next;
|
|
4161
|
+
dispatch({ type: "history_set", messages: next });
|
|
4162
|
+
}
|
|
2309
4163
|
try {
|
|
2310
|
-
await agentRef.current?.runTurn(
|
|
4164
|
+
await agentRef.current?.runTurn(expanded);
|
|
2311
4165
|
} catch (err) {
|
|
2312
4166
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2313
4167
|
dispatch({ type: "stream_delta", delta: `
|
|
@@ -2316,7 +4170,7 @@ function App({
|
|
|
2316
4170
|
dispatch({ type: "set_status", status: "idle" });
|
|
2317
4171
|
}
|
|
2318
4172
|
},
|
|
2319
|
-
[slash, cwd, llm, session, settings, settingsSources, modelsRegistry, state.inputTokens, state.outputTokens, state.totalTokens, actions, autocomplete, autocompleteIndex]
|
|
4173
|
+
[slash, cwd, llm, session, settings, settingsSources, modelsRegistry, state.inputTokens, state.outputTokens, state.totalTokens, state.status, actions, autocomplete, autocompleteIndex]
|
|
2320
4174
|
);
|
|
2321
4175
|
function appendAssistantText(text) {
|
|
2322
4176
|
const msg = { role: "assistant", content: [{ type: "text", text }] };
|
|
@@ -2334,25 +4188,47 @@ function App({
|
|
|
2334
4188
|
}
|
|
2335
4189
|
}
|
|
2336
4190
|
const banner = !showBanner ? null : pickBanner(termWidth, { version: "0.1.0", model: llm.model, cwd: shortCwd(cwd) });
|
|
2337
|
-
|
|
4191
|
+
const { resultsByCallId, inlinedIds } = useMemo2(() => {
|
|
4192
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4193
|
+
const used = /* @__PURE__ */ new Set();
|
|
4194
|
+
for (const m of state.history) {
|
|
4195
|
+
if (m.role === "tool" && m.toolUseId) byId.set(m.toolUseId, m);
|
|
4196
|
+
if (m.role === "assistant" && Array.isArray(m.content)) {
|
|
4197
|
+
for (const p of m.content) {
|
|
4198
|
+
if (p.type === "tool_use") used.add(p.id);
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
return { resultsByCallId: byId, inlinedIds: used };
|
|
4203
|
+
}, [state.history]);
|
|
4204
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
2338
4205
|
banner,
|
|
2339
|
-
/* @__PURE__ */
|
|
2340
|
-
state.history.map((msg, i) =>
|
|
2341
|
-
|
|
4206
|
+
/* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginTop: 1, children: [
|
|
4207
|
+
state.history.map((msg, i) => {
|
|
4208
|
+
if (msg.role === "tool" && inlinedIds.has(msg.toolUseId)) return null;
|
|
4209
|
+
return /* @__PURE__ */ jsx15(MessageView, { message: msg, resultsByCallId }, i);
|
|
4210
|
+
}),
|
|
4211
|
+
state.streamingText && /* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", marginTop: 1, children: [
|
|
4212
|
+
/* @__PURE__ */ jsxs14(Text15, { color: "cyan", children: [
|
|
4213
|
+
DOT,
|
|
4214
|
+
" "
|
|
4215
|
+
] }),
|
|
4216
|
+
/* @__PURE__ */ jsx15(Box13, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx15(Text15, { children: state.streamingText }) })
|
|
4217
|
+
] })
|
|
2342
4218
|
] }),
|
|
2343
|
-
pending && /* @__PURE__ */
|
|
4219
|
+
pending && /* @__PURE__ */ jsx15(
|
|
2344
4220
|
PermissionPrompt,
|
|
2345
4221
|
{
|
|
2346
4222
|
request: {
|
|
2347
4223
|
...pending,
|
|
2348
|
-
resolve: (
|
|
2349
|
-
pending.resolve(
|
|
4224
|
+
resolve: (decision) => {
|
|
4225
|
+
pending.resolve(decision);
|
|
2350
4226
|
setPending(null);
|
|
2351
4227
|
}
|
|
2352
4228
|
}
|
|
2353
4229
|
}
|
|
2354
4230
|
),
|
|
2355
|
-
picker && /* @__PURE__ */
|
|
4231
|
+
picker && /* @__PURE__ */ jsx15(
|
|
2356
4232
|
ModelSelector,
|
|
2357
4233
|
{
|
|
2358
4234
|
request: {
|
|
@@ -2364,7 +4240,7 @@ function App({
|
|
|
2364
4240
|
}
|
|
2365
4241
|
}
|
|
2366
4242
|
),
|
|
2367
|
-
sessionPicker && /* @__PURE__ */
|
|
4243
|
+
sessionPicker && /* @__PURE__ */ jsx15(
|
|
2368
4244
|
SessionSelector,
|
|
2369
4245
|
{
|
|
2370
4246
|
request: {
|
|
@@ -2376,38 +4252,111 @@ function App({
|
|
|
2376
4252
|
}
|
|
2377
4253
|
}
|
|
2378
4254
|
),
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
4255
|
+
questionPicker && /* @__PURE__ */ jsx15(
|
|
4256
|
+
QuestionPicker,
|
|
4257
|
+
{
|
|
4258
|
+
request: {
|
|
4259
|
+
questions: questionPicker.questions,
|
|
4260
|
+
resolve: (responses) => {
|
|
4261
|
+
questionPicker.resolve(responses);
|
|
4262
|
+
setQuestionPicker(null);
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
),
|
|
4267
|
+
state.status !== "idle" && /* @__PURE__ */ jsx15(
|
|
4268
|
+
StatusLine,
|
|
4269
|
+
{
|
|
4270
|
+
startTime: state.turnStartTime,
|
|
4271
|
+
firstTextTime: state.turnFirstTextTime,
|
|
4272
|
+
inputTokens: state.turnInputTokens,
|
|
4273
|
+
runningTool: state.runningTool,
|
|
4274
|
+
lang
|
|
4275
|
+
}
|
|
4276
|
+
),
|
|
4277
|
+
progress && /* @__PURE__ */ jsx15(ProgressBanner, { state: progress }),
|
|
4278
|
+
inputVisible && /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
4279
|
+
queuedInputs.length > 0 && /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [
|
|
4280
|
+
queuedInputs.map((q, i) => /* @__PURE__ */ jsx15(Text15, { color: "yellow", dimColor: true, children: `\u21B3 queued: ${q.length > 60 ? q.slice(0, 60) + "\u2026" : q}` }, i)),
|
|
4281
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` (will send after current turn \xB7 ${queuedInputs.length} pending)` })
|
|
4282
|
+
] }),
|
|
4283
|
+
/* @__PURE__ */ jsxs14(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
4284
|
+
/* @__PURE__ */ jsx15(Text15, { backgroundColor: "#1c1c1c", children: " ".repeat(Math.max(1, termWidth - 1)) }),
|
|
4285
|
+
/* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", children: [
|
|
4286
|
+
/* @__PURE__ */ jsx15(Text15, { backgroundColor: "#1c1c1c", color: "gray", bold: true, children: " \u203A " }),
|
|
4287
|
+
/* @__PURE__ */ jsx15(
|
|
4288
|
+
BgTextInput,
|
|
4289
|
+
{
|
|
4290
|
+
value: input,
|
|
4291
|
+
onChange: setInput,
|
|
4292
|
+
onSubmit: handleSubmit,
|
|
4293
|
+
width: Math.max(10, termWidth - 4),
|
|
4294
|
+
backgroundColor: "#1c1c1c",
|
|
4295
|
+
isActive: acceptingInput,
|
|
4296
|
+
onPaste: handlePaste,
|
|
4297
|
+
placeholder: inputPlaceholder
|
|
4298
|
+
},
|
|
4299
|
+
inputRemountKey
|
|
4300
|
+
)
|
|
4301
|
+
] }),
|
|
4302
|
+
/* @__PURE__ */ jsx15(Text15, { backgroundColor: "#1c1c1c", children: " ".repeat(Math.max(1, termWidth - 1)) })
|
|
2383
4303
|
] }),
|
|
2384
|
-
autocomplete && autocomplete.matches.length > 0 && /* @__PURE__ */
|
|
4304
|
+
autocomplete && autocomplete.matches.length > 0 && /* @__PURE__ */ jsx15(SlashAutocomplete, { matches: autocomplete.matches, index: autocompleteIndex })
|
|
2385
4305
|
] }),
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
4306
|
+
/* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
4307
|
+
/* @__PURE__ */ jsx15(
|
|
4308
|
+
FooterStatus,
|
|
4309
|
+
{
|
|
4310
|
+
sessionId: session.meta.id,
|
|
4311
|
+
model: llm.model,
|
|
4312
|
+
contextWindow: llm.capabilities.maxContextWindow,
|
|
4313
|
+
lastInputTokens: state.turnInputTokens,
|
|
4314
|
+
sessionInputTokens: state.inputTokens,
|
|
4315
|
+
sessionOutputTokens: state.outputTokens,
|
|
4316
|
+
termWidth
|
|
4317
|
+
}
|
|
4318
|
+
),
|
|
4319
|
+
/* @__PURE__ */ jsx15(PermissionModeBar, { mode, compact: termWidth < 60 })
|
|
4320
|
+
] })
|
|
2389
4321
|
] });
|
|
2390
4322
|
}
|
|
4323
|
+
function extractUserInputs(messages) {
|
|
4324
|
+
const out = [];
|
|
4325
|
+
for (const m of messages) {
|
|
4326
|
+
if (m.role !== "user") continue;
|
|
4327
|
+
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
4328
|
+
if (text.startsWith("[Previous conversation summary]")) continue;
|
|
4329
|
+
if (text.trim()) out.push(text);
|
|
4330
|
+
}
|
|
4331
|
+
return out;
|
|
4332
|
+
}
|
|
4333
|
+
var PASTE_PLACEHOLDER_RE = /\[Pasted text #(\d+) \+\d+ lines\]/g;
|
|
4334
|
+
function expandPastes(value, map) {
|
|
4335
|
+
return value.replace(PASTE_PLACEHOLDER_RE, (full, id) => {
|
|
4336
|
+
const text = map.get(Number(id));
|
|
4337
|
+
return text ?? full;
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
2391
4340
|
function shortCwd(cwd) {
|
|
2392
|
-
const home =
|
|
4341
|
+
const home = homedir8();
|
|
2393
4342
|
if (cwd === home) return "~";
|
|
2394
4343
|
if (cwd.startsWith(home + "/")) return "~" + cwd.slice(home.length);
|
|
2395
4344
|
return cwd;
|
|
2396
4345
|
}
|
|
2397
4346
|
async function persistActiveModel(modelId) {
|
|
2398
|
-
const path =
|
|
4347
|
+
const path = join6(homedir8(), ".muse", "settings.json");
|
|
2399
4348
|
let current = {};
|
|
2400
|
-
if (
|
|
4349
|
+
if (existsSync5(path)) {
|
|
2401
4350
|
try {
|
|
2402
|
-
current = JSON.parse(await
|
|
4351
|
+
current = JSON.parse(await readFile5(path, "utf-8"));
|
|
2403
4352
|
} catch {
|
|
2404
4353
|
current = {};
|
|
2405
4354
|
}
|
|
2406
4355
|
}
|
|
2407
4356
|
const llm = current.llm ?? {};
|
|
2408
4357
|
const next = { ...current, llm: { ...llm, model: modelId } };
|
|
2409
|
-
await
|
|
2410
|
-
await
|
|
4358
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
4359
|
+
await writeFile2(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
2411
4360
|
}
|
|
2412
4361
|
|
|
2413
4362
|
// src/tools/registry.ts
|
|
@@ -2521,8 +4470,8 @@ var ToolRegistry = class {
|
|
|
2521
4470
|
};
|
|
2522
4471
|
|
|
2523
4472
|
// src/tools/builtin/read.ts
|
|
2524
|
-
import { readFile as
|
|
2525
|
-
import { resolve as
|
|
4473
|
+
import { readFile as readFile6, stat as stat2 } from "fs/promises";
|
|
4474
|
+
import { resolve as resolve3, isAbsolute } from "path";
|
|
2526
4475
|
import { z as z3 } from "zod";
|
|
2527
4476
|
|
|
2528
4477
|
// src/tools/types.ts
|
|
@@ -2537,6 +4486,48 @@ function defineTool(def) {
|
|
|
2537
4486
|
};
|
|
2538
4487
|
}
|
|
2539
4488
|
|
|
4489
|
+
// src/tools/_sensitive.ts
|
|
4490
|
+
import { homedir as homedir9 } from "os";
|
|
4491
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
4492
|
+
var HOME = homedir9();
|
|
4493
|
+
var SENSITIVE_DIRS = [
|
|
4494
|
+
resolve2(HOME, ".ssh"),
|
|
4495
|
+
resolve2(HOME, ".aws"),
|
|
4496
|
+
resolve2(HOME, ".gnupg"),
|
|
4497
|
+
resolve2(HOME, ".config", "gh")
|
|
4498
|
+
];
|
|
4499
|
+
var SENSITIVE_FILES = [
|
|
4500
|
+
resolve2(HOME, ".kube", "config"),
|
|
4501
|
+
resolve2(HOME, ".netrc"),
|
|
4502
|
+
resolve2(HOME, ".pypirc")
|
|
4503
|
+
];
|
|
4504
|
+
var SENSITIVE_BASENAMES = /* @__PURE__ */ new Set([
|
|
4505
|
+
"id_rsa",
|
|
4506
|
+
"id_ed25519",
|
|
4507
|
+
"id_ecdsa",
|
|
4508
|
+
"id_dsa"
|
|
4509
|
+
]);
|
|
4510
|
+
var ENV_PATTERN2 = /(?:^|\/)\.env(\..+)?$/;
|
|
4511
|
+
function checkSensitivePath(path) {
|
|
4512
|
+
const abs = resolve2(path);
|
|
4513
|
+
for (const dir of SENSITIVE_DIRS) {
|
|
4514
|
+
if (abs === dir || abs.startsWith(dir + "/")) {
|
|
4515
|
+
return { blocked: true, reason: `sensitive directory ${dir.replace(HOME, "~")}` };
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
for (const f of SENSITIVE_FILES) {
|
|
4519
|
+
if (abs === f) return { blocked: true, reason: `sensitive file ${f.replace(HOME, "~")}` };
|
|
4520
|
+
}
|
|
4521
|
+
const base = basename2(abs);
|
|
4522
|
+
if (SENSITIVE_BASENAMES.has(base)) {
|
|
4523
|
+
return { blocked: true, reason: `private key filename ${base}` };
|
|
4524
|
+
}
|
|
4525
|
+
if (ENV_PATTERN2.test(abs)) {
|
|
4526
|
+
return { blocked: true, reason: `.env file (may contain secrets)` };
|
|
4527
|
+
}
|
|
4528
|
+
return { blocked: false };
|
|
4529
|
+
}
|
|
4530
|
+
|
|
2540
4531
|
// src/tools/builtin/read.ts
|
|
2541
4532
|
var ReadArgs = z3.object({
|
|
2542
4533
|
file_path: z3.string().describe("Absolute or cwd-relative path to the file."),
|
|
@@ -2552,7 +4543,11 @@ var ReadTool = defineTool({
|
|
|
2552
4543
|
permission: "read",
|
|
2553
4544
|
summarize: (args) => `Read(${args.file_path}${args.offset != null ? `, offset=${args.offset}` : ""}${args.limit != null ? `, limit=${args.limit}` : ""})`,
|
|
2554
4545
|
async execute(args, ctx) {
|
|
2555
|
-
const path = isAbsolute(args.file_path) ? args.file_path :
|
|
4546
|
+
const path = isAbsolute(args.file_path) ? args.file_path : resolve3(ctx.cwd, args.file_path);
|
|
4547
|
+
const sensitive = checkSensitivePath(path);
|
|
4548
|
+
if (sensitive.blocked) {
|
|
4549
|
+
return { content: `Refused: ${path} matches sensitive path policy (${sensitive.reason}).`, isError: true };
|
|
4550
|
+
}
|
|
2556
4551
|
let info;
|
|
2557
4552
|
try {
|
|
2558
4553
|
info = await stat2(path);
|
|
@@ -2562,7 +4557,7 @@ var ReadTool = defineTool({
|
|
|
2562
4557
|
if (!info.isFile()) {
|
|
2563
4558
|
throw new ToolError(`Not a regular file: ${path}`, "Read");
|
|
2564
4559
|
}
|
|
2565
|
-
const content = await
|
|
4560
|
+
const content = await readFile6(path, "utf-8");
|
|
2566
4561
|
const lines = content.split(/\r?\n/);
|
|
2567
4562
|
const offset = args.offset ?? 0;
|
|
2568
4563
|
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
@@ -2585,9 +4580,26 @@ var ReadTool = defineTool({
|
|
|
2585
4580
|
});
|
|
2586
4581
|
|
|
2587
4582
|
// src/tools/builtin/write.ts
|
|
2588
|
-
import { writeFile as
|
|
2589
|
-
import { resolve as
|
|
4583
|
+
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir4, stat as stat3 } from "fs/promises";
|
|
4584
|
+
import { resolve as resolve4, isAbsolute as isAbsolute2, dirname as dirname4 } from "path";
|
|
2590
4585
|
import { z as z4 } from "zod";
|
|
4586
|
+
|
|
4587
|
+
// src/tools/_diff.ts
|
|
4588
|
+
import { createPatch } from "diff";
|
|
4589
|
+
var MAX_DIFF_LINES = 200;
|
|
4590
|
+
function makeUnifiedDiff(filePath, oldContent, newContent) {
|
|
4591
|
+
if (oldContent === newContent) return "";
|
|
4592
|
+
const patch = createPatch(filePath, oldContent, newContent, "before", "after", { context: 3 });
|
|
4593
|
+
return truncate(patch);
|
|
4594
|
+
}
|
|
4595
|
+
function truncate(diff) {
|
|
4596
|
+
const lines = diff.split("\n");
|
|
4597
|
+
if (lines.length <= MAX_DIFF_LINES) return diff;
|
|
4598
|
+
return lines.slice(0, MAX_DIFF_LINES).join("\n") + `
|
|
4599
|
+
... [${lines.length - MAX_DIFF_LINES} more diff lines truncated]`;
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
// src/tools/builtin/write.ts
|
|
2591
4603
|
var WriteArgs = z4.object({
|
|
2592
4604
|
file_path: z4.string().describe("Absolute or cwd-relative path to the file."),
|
|
2593
4605
|
content: z4.string().describe("Full content of the file.")
|
|
@@ -2599,25 +4611,33 @@ var WriteTool = defineTool({
|
|
|
2599
4611
|
permission: "write",
|
|
2600
4612
|
summarize: (args) => `Write(${args.file_path}, ${args.content.length} chars)`,
|
|
2601
4613
|
async execute(args, ctx) {
|
|
2602
|
-
const path = isAbsolute2(args.file_path) ? args.file_path :
|
|
4614
|
+
const path = isAbsolute2(args.file_path) ? args.file_path : resolve4(ctx.cwd, args.file_path);
|
|
4615
|
+
const sensitive = checkSensitivePath(path);
|
|
4616
|
+
if (sensitive.blocked) {
|
|
4617
|
+
return { content: `Refused: ${path} matches sensitive path policy (${sensitive.reason}).`, isError: true };
|
|
4618
|
+
}
|
|
2603
4619
|
let existed = false;
|
|
4620
|
+
let oldContent = "";
|
|
2604
4621
|
try {
|
|
2605
4622
|
const info = await stat3(path);
|
|
2606
4623
|
existed = info.isFile();
|
|
4624
|
+
if (existed) oldContent = await readFile7(path, "utf-8");
|
|
2607
4625
|
} catch {
|
|
2608
4626
|
}
|
|
2609
|
-
await
|
|
2610
|
-
await
|
|
4627
|
+
await mkdir4(dirname4(path), { recursive: true });
|
|
4628
|
+
await writeFile3(path, args.content, "utf-8");
|
|
4629
|
+
const diff = makeUnifiedDiff(args.file_path, oldContent, args.content);
|
|
2611
4630
|
return {
|
|
2612
4631
|
content: existed ? `Overwrote ${path} (${args.content.length} bytes).` : `Created ${path} (${args.content.length} bytes).`,
|
|
2613
|
-
summary: `${existed ? "Overwrote" : "Created"} ${args.file_path}
|
|
4632
|
+
summary: `${existed ? "Overwrote" : "Created"} ${args.file_path}`,
|
|
4633
|
+
diff: diff || void 0
|
|
2614
4634
|
};
|
|
2615
4635
|
}
|
|
2616
4636
|
});
|
|
2617
4637
|
|
|
2618
4638
|
// src/tools/builtin/edit.ts
|
|
2619
|
-
import { readFile as
|
|
2620
|
-
import { resolve as
|
|
4639
|
+
import { readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
|
|
4640
|
+
import { resolve as resolve5, isAbsolute as isAbsolute3 } from "path";
|
|
2621
4641
|
import { z as z5 } from "zod";
|
|
2622
4642
|
var EditArgs = z5.object({
|
|
2623
4643
|
file_path: z5.string().describe("Absolute or cwd-relative path to the file."),
|
|
@@ -2632,10 +4652,14 @@ var EditTool = defineTool({
|
|
|
2632
4652
|
permission: "write",
|
|
2633
4653
|
summarize: (args) => `Edit(${args.file_path})`,
|
|
2634
4654
|
async execute(args, ctx) {
|
|
2635
|
-
const path = isAbsolute3(args.file_path) ? args.file_path :
|
|
4655
|
+
const path = isAbsolute3(args.file_path) ? args.file_path : resolve5(ctx.cwd, args.file_path);
|
|
4656
|
+
const sensitive = checkSensitivePath(path);
|
|
4657
|
+
if (sensitive.blocked) {
|
|
4658
|
+
return { content: `Refused: ${path} matches sensitive path policy (${sensitive.reason}).`, isError: true };
|
|
4659
|
+
}
|
|
2636
4660
|
let content;
|
|
2637
4661
|
try {
|
|
2638
|
-
content = await
|
|
4662
|
+
content = await readFile8(path, "utf-8");
|
|
2639
4663
|
} catch (err) {
|
|
2640
4664
|
throw new ToolError(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`, "Edit", err);
|
|
2641
4665
|
}
|
|
@@ -2656,10 +4680,12 @@ var EditTool = defineTool({
|
|
|
2656
4680
|
};
|
|
2657
4681
|
}
|
|
2658
4682
|
const newContent = args.replace_all ? content.split(args.old_string).join(args.new_string) : content.replace(args.old_string, args.new_string);
|
|
2659
|
-
await
|
|
4683
|
+
await writeFile4(path, newContent, "utf-8");
|
|
4684
|
+
const diff = makeUnifiedDiff(args.file_path, content, newContent);
|
|
2660
4685
|
return {
|
|
2661
4686
|
content: `Edited ${path}: replaced ${args.replace_all ? occurrences : 1} occurrence(s).`,
|
|
2662
|
-
summary: `Edited ${args.file_path}
|
|
4687
|
+
summary: `Edited ${args.file_path}`,
|
|
4688
|
+
diff: diff || void 0
|
|
2663
4689
|
};
|
|
2664
4690
|
}
|
|
2665
4691
|
});
|
|
@@ -2738,8 +4764,8 @@ var BashTool = defineTool({
|
|
|
2738
4764
|
maxBuffer: MAX_OUTPUT_BYTES * 2,
|
|
2739
4765
|
cancelSignal: ctx.abortSignal
|
|
2740
4766
|
});
|
|
2741
|
-
const stdout =
|
|
2742
|
-
const stderr =
|
|
4767
|
+
const stdout = truncate2(result.stdout ?? "", MAX_OUTPUT_BYTES, "stdout");
|
|
4768
|
+
const stderr = truncate2(result.stderr ?? "", MAX_OUTPUT_BYTES, "stderr");
|
|
2743
4769
|
const parts = [];
|
|
2744
4770
|
if (stdout) parts.push(`<stdout>
|
|
2745
4771
|
${stdout}
|
|
@@ -2763,7 +4789,7 @@ ${stderr}
|
|
|
2763
4789
|
}
|
|
2764
4790
|
}
|
|
2765
4791
|
});
|
|
2766
|
-
function
|
|
4792
|
+
function truncate2(text, max, label) {
|
|
2767
4793
|
if (text.length <= max) return text;
|
|
2768
4794
|
return text.slice(0, max) + `
|
|
2769
4795
|
... [${label} truncated, original ${text.length} bytes]`;
|
|
@@ -2872,6 +4898,298 @@ var GlobTool = defineTool({
|
|
|
2872
4898
|
}
|
|
2873
4899
|
});
|
|
2874
4900
|
|
|
4901
|
+
// src/tools/builtin/todo.ts
|
|
4902
|
+
import { z as z9 } from "zod";
|
|
4903
|
+
var TodoSchema = z9.object({
|
|
4904
|
+
content: z9.string().describe("Imperative one-line task description (e.g. 'Run the test suite')."),
|
|
4905
|
+
status: z9.enum(["pending", "in_progress", "completed"]).describe("Current status."),
|
|
4906
|
+
activeForm: z9.string().optional().describe("Present-continuous form for the spinner (e.g. 'Running the test suite').")
|
|
4907
|
+
});
|
|
4908
|
+
var TodoWriteArgs = z9.object({
|
|
4909
|
+
todos: z9.array(TodoSchema).describe("Full list. Replaces the current store.")
|
|
4910
|
+
});
|
|
4911
|
+
var TodoWriteTool = defineTool({
|
|
4912
|
+
name: "TodoWrite",
|
|
4913
|
+
description: "Maintain a structured task list for the current session. Pass the FULL list every call (it replaces the store). Mark exactly one task in_progress at a time; mark completed immediately when done; do not batch completions. Use when the task has 3+ distinct steps or is non-trivial. Skip for single trivial actions.",
|
|
4914
|
+
parameters: TodoWriteArgs,
|
|
4915
|
+
permission: "read",
|
|
4916
|
+
summarize: (args) => `TodoWrite(${args.todos.length} items)`,
|
|
4917
|
+
async execute(args, ctx) {
|
|
4918
|
+
if (!ctx.todos) {
|
|
4919
|
+
return {
|
|
4920
|
+
content: "TodoWrite is unavailable: this agent run has no todo store. (Internal bug; tell the user.)",
|
|
4921
|
+
isError: true
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
ctx.todos.set(args.todos);
|
|
4925
|
+
const summary = args.todos.map((t, i) => `${i + 1}. ${t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[~]" : "[ ]"} ${t.content}`).join("\n");
|
|
4926
|
+
return {
|
|
4927
|
+
content: `Updated todos (${args.todos.length} items):
|
|
4928
|
+
${summary}`,
|
|
4929
|
+
summary: `Todos: ${args.todos.filter((t) => t.status === "completed").length}/${args.todos.length} done`
|
|
4930
|
+
};
|
|
4931
|
+
}
|
|
4932
|
+
});
|
|
4933
|
+
|
|
4934
|
+
// src/tools/builtin/webfetch.ts
|
|
4935
|
+
import { z as z10 } from "zod";
|
|
4936
|
+
var WebFetchArgs = z10.object({
|
|
4937
|
+
url: z10.string().describe("Fully-qualified URL. http will be upgraded to https."),
|
|
4938
|
+
prompt: z10.string().optional().describe(
|
|
4939
|
+
"What information to look for. The host returns the page content; the LLM should then read it to answer the prompt."
|
|
4940
|
+
)
|
|
4941
|
+
});
|
|
4942
|
+
var MAX_RESPONSE_BYTES = 1e6;
|
|
4943
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
4944
|
+
var PRIVATE_HOST_PATTERNS = [
|
|
4945
|
+
/^localhost$/i,
|
|
4946
|
+
/^127\./,
|
|
4947
|
+
/^0\.0\.0\.0$/,
|
|
4948
|
+
/^169\.254\./,
|
|
4949
|
+
/^10\./,
|
|
4950
|
+
/^192\.168\./,
|
|
4951
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
4952
|
+
/^::1$/,
|
|
4953
|
+
/^fc[0-9a-f]{2}:/i,
|
|
4954
|
+
/^fe80:/i
|
|
4955
|
+
];
|
|
4956
|
+
function isPrivateHost(hostname) {
|
|
4957
|
+
return PRIVATE_HOST_PATTERNS.some((p) => p.test(hostname));
|
|
4958
|
+
}
|
|
4959
|
+
var WebFetchTool = defineTool({
|
|
4960
|
+
name: "WebFetch",
|
|
4961
|
+
description: "Fetch a URL and return its textual content (HTML stripped to a markdown-ish form). Use for reading documentation, blog posts, or API specs. Private/loopback hosts are blocked. If the URL redirects to a different host, the redirect target is returned for you to re-fetch.",
|
|
4962
|
+
parameters: WebFetchArgs,
|
|
4963
|
+
permission: "network",
|
|
4964
|
+
summarize: (args) => `WebFetch(${args.url})`,
|
|
4965
|
+
async execute(args, ctx) {
|
|
4966
|
+
let target;
|
|
4967
|
+
try {
|
|
4968
|
+
target = new URL(args.url);
|
|
4969
|
+
} catch {
|
|
4970
|
+
return { content: `Invalid URL: ${args.url}`, isError: true };
|
|
4971
|
+
}
|
|
4972
|
+
if (target.protocol === "http:") {
|
|
4973
|
+
target.protocol = "https:";
|
|
4974
|
+
}
|
|
4975
|
+
if (target.protocol !== "https:") {
|
|
4976
|
+
return { content: `Refused: only http(s) URLs are allowed.`, isError: true };
|
|
4977
|
+
}
|
|
4978
|
+
if (isPrivateHost(target.hostname)) {
|
|
4979
|
+
return { content: `Refused: ${target.hostname} is a private/loopback host (SSRF guard).`, isError: true };
|
|
4980
|
+
}
|
|
4981
|
+
const controller = new AbortController();
|
|
4982
|
+
const onAbort = () => controller.abort();
|
|
4983
|
+
ctx.abortSignal?.addEventListener("abort", onAbort);
|
|
4984
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
4985
|
+
try {
|
|
4986
|
+
const resp = await fetch(target.toString(), {
|
|
4987
|
+
redirect: "manual",
|
|
4988
|
+
signal: controller.signal,
|
|
4989
|
+
headers: { "user-agent": "muse-cli/0.1" }
|
|
4990
|
+
});
|
|
4991
|
+
if (resp.status >= 300 && resp.status < 400) {
|
|
4992
|
+
const loc = resp.headers.get("location");
|
|
4993
|
+
if (loc) {
|
|
4994
|
+
try {
|
|
4995
|
+
const redirectURL = new URL(loc, target);
|
|
4996
|
+
if (redirectURL.hostname !== target.hostname) {
|
|
4997
|
+
return {
|
|
4998
|
+
content: `Redirect to a different host: ${redirectURL.toString()}
|
|
4999
|
+
Re-fetch the new URL explicitly if you trust it.`,
|
|
5000
|
+
summary: `Redirect to a different host: ${redirectURL.toString()}`,
|
|
5001
|
+
kind: "warn"
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
return {
|
|
5005
|
+
content: `Redirect (same host): ${redirectURL.toString()}
|
|
5006
|
+
Re-fetch the new URL to continue.`,
|
|
5007
|
+
summary: `Redirect \u2192 ${redirectURL.pathname}`,
|
|
5008
|
+
kind: "warn"
|
|
5009
|
+
};
|
|
5010
|
+
} catch {
|
|
5011
|
+
return { content: `Redirect with unparseable location: ${loc}`, isError: true };
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
if (!resp.ok) {
|
|
5016
|
+
return { content: `HTTP ${resp.status} ${resp.statusText} for ${target.toString()}`, isError: true };
|
|
5017
|
+
}
|
|
5018
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
5019
|
+
const reader = resp.body?.getReader();
|
|
5020
|
+
if (!reader) return { content: `Empty response body.`, isError: true };
|
|
5021
|
+
const chunks = [];
|
|
5022
|
+
let total = 0;
|
|
5023
|
+
while (true) {
|
|
5024
|
+
const { value, done } = await reader.read();
|
|
5025
|
+
if (done) break;
|
|
5026
|
+
if (value) {
|
|
5027
|
+
total += value.byteLength;
|
|
5028
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
5029
|
+
await reader.cancel();
|
|
5030
|
+
chunks.push(value.slice(0, value.byteLength - (total - MAX_RESPONSE_BYTES)));
|
|
5031
|
+
break;
|
|
5032
|
+
}
|
|
5033
|
+
chunks.push(value);
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
const body = new TextDecoder("utf-8", { fatal: false }).decode(Buffer.concat(chunks.map((c) => Buffer.from(c))));
|
|
5037
|
+
let processed = body;
|
|
5038
|
+
if (/^text\/html|application\/xhtml/i.test(contentType)) {
|
|
5039
|
+
processed = htmlToText(body);
|
|
5040
|
+
}
|
|
5041
|
+
const summary = args.prompt ? `# WebFetch result for: ${args.prompt}` : `Fetched ${target.hostname} (${total} bytes${total >= MAX_RESPONSE_BYTES ? ", truncated" : ""})`;
|
|
5042
|
+
const truncated = processed.length > 2e5 ? processed.slice(0, 2e5) + "\n\n... [truncated]" : processed;
|
|
5043
|
+
const preface = args.prompt ? `# WebFetch result for: ${args.prompt}
|
|
5044
|
+
|
|
5045
|
+
Source: ${target.toString()}
|
|
5046
|
+
|
|
5047
|
+
` : `Source: ${target.toString()}
|
|
5048
|
+
|
|
5049
|
+
`;
|
|
5050
|
+
return { content: preface + truncated, summary };
|
|
5051
|
+
} catch (err) {
|
|
5052
|
+
if (err.name === "AbortError") {
|
|
5053
|
+
return { content: `WebFetch aborted (timeout or user cancel).`, isError: true };
|
|
5054
|
+
}
|
|
5055
|
+
return { content: `WebFetch failed: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
5056
|
+
} finally {
|
|
5057
|
+
clearTimeout(timer);
|
|
5058
|
+
ctx.abortSignal?.removeEventListener("abort", onAbort);
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
});
|
|
5062
|
+
function htmlToText(html) {
|
|
5063
|
+
let s = html;
|
|
5064
|
+
s = s.replace(/<(script|style|svg|noscript)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
|
|
5065
|
+
s = s.replace(/<!--[\s\S]*?-->/g, "");
|
|
5066
|
+
s = s.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, lvl, txt) => {
|
|
5067
|
+
return `
|
|
5068
|
+
|
|
5069
|
+
${"#".repeat(parseInt(lvl, 10))} ${stripTags(txt).trim()}
|
|
5070
|
+
|
|
5071
|
+
`;
|
|
5072
|
+
});
|
|
5073
|
+
s = s.replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_m, href, txt) => {
|
|
5074
|
+
const label = stripTags(txt).trim();
|
|
5075
|
+
return label ? `[${label}](${href})` : href;
|
|
5076
|
+
});
|
|
5077
|
+
s = s.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, (_m, txt) => `
|
|
5078
|
+
- ${stripTags(txt).trim()}`);
|
|
5079
|
+
s = s.replace(/<(p|div|section|article|header|footer|main|aside|nav|pre|blockquote|br|hr)\b[^>]*>/gi, "\n");
|
|
5080
|
+
s = s.replace(/<\/(p|div|section|article|header|footer|main|aside|nav|pre|blockquote)>/gi, "\n");
|
|
5081
|
+
s = s.replace(/<code\b[^>]*>([\s\S]*?)<\/code>/gi, (_m, txt) => `\`${stripTags(txt)}\``);
|
|
5082
|
+
s = stripTags(s);
|
|
5083
|
+
s = s.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
5084
|
+
s = s.replace(/\n{3,}/g, "\n\n").trim();
|
|
5085
|
+
return s;
|
|
5086
|
+
}
|
|
5087
|
+
function stripTags(s) {
|
|
5088
|
+
return s.replace(/<[^>]+>/g, "");
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// src/tools/builtin/memory.ts
|
|
5092
|
+
import { z as z11 } from "zod";
|
|
5093
|
+
var TYPES = ["user", "feedback", "project", "reference"];
|
|
5094
|
+
var MemoryWriteArgs = z11.object({
|
|
5095
|
+
name: z11.string().regex(/^[a-z0-9][a-z0-9-_]*$/i, "must be a kebab- or snake-style slug").describe("Short kebab/snake slug; used as filename (<name>.md) and index key."),
|
|
5096
|
+
description: z11.string().describe("One-line summary used in MEMORY.md index (decides future relevance)."),
|
|
5097
|
+
type: z11.enum(TYPES).describe("user | feedback | project | reference"),
|
|
5098
|
+
body: z11.string().describe("Memory content (markdown). For feedback/project, lead with the rule/fact then **Why:** and **How to apply:** lines.")
|
|
5099
|
+
});
|
|
5100
|
+
var MemoryWriteTool = defineTool({
|
|
5101
|
+
name: "MemoryWrite",
|
|
5102
|
+
description: "Save a long-term memory file under ~/.muse/projects/<hash>/memory/<name>.md and update MEMORY.md index. Use for: user role/preferences, validated approach decisions (feedback), project facts (auto-convert relative dates), external system references. Do NOT save: code patterns derivable from the repo, git history, fix recipes, ephemeral task state.",
|
|
5103
|
+
parameters: MemoryWriteArgs,
|
|
5104
|
+
permission: "write",
|
|
5105
|
+
summarize: (args) => `MemoryWrite(${args.name}, type=${args.type})`,
|
|
5106
|
+
async execute(args, ctx) {
|
|
5107
|
+
const { filePath, indexUpdated } = await writeMemory(ctx.cwd, {
|
|
5108
|
+
name: args.name,
|
|
5109
|
+
description: args.description,
|
|
5110
|
+
type: args.type,
|
|
5111
|
+
body: args.body
|
|
5112
|
+
});
|
|
5113
|
+
return {
|
|
5114
|
+
content: `Saved memory "${args.name}" (${args.type}) \u2192 ${filePath}${indexUpdated ? "\nMEMORY.md updated." : ""}`,
|
|
5115
|
+
summary: `MemoryWrite ${args.name}`
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
});
|
|
5119
|
+
var MemoryReadArgs = z11.object({
|
|
5120
|
+
name: z11.string().describe("Memory slug to read (no .md extension).")
|
|
5121
|
+
});
|
|
5122
|
+
var MemoryReadTool = defineTool({
|
|
5123
|
+
name: "MemoryRead",
|
|
5124
|
+
description: "Read a specific long-term memory file by name. Use after seeing it referenced in MEMORY.md (which is auto-injected into the system prompt).",
|
|
5125
|
+
parameters: MemoryReadArgs,
|
|
5126
|
+
permission: "read",
|
|
5127
|
+
summarize: (args) => `MemoryRead(${args.name})`,
|
|
5128
|
+
async execute(args, ctx) {
|
|
5129
|
+
try {
|
|
5130
|
+
const content = await readMemoryFile(ctx.cwd, args.name);
|
|
5131
|
+
return { content, summary: `MemoryRead ${args.name}` };
|
|
5132
|
+
} catch (err) {
|
|
5133
|
+
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
});
|
|
5137
|
+
|
|
5138
|
+
// src/tools/builtin/ask-user-question.ts
|
|
5139
|
+
import { z as z12 } from "zod";
|
|
5140
|
+
var AskQuestionOptionSchema = z12.object({
|
|
5141
|
+
label: z12.string().min(1).describe("Option text shown to the user. Concise (1-5 words)."),
|
|
5142
|
+
description: z12.string().optional().describe("Optional one-line explanation of what this option means."),
|
|
5143
|
+
preview: z12.string().optional().describe(
|
|
5144
|
+
"Optional rich preview rendered in a right-side panel when this option is focused. Use for code/diagram/config snippets that help compare options visually. Multi-line text supported."
|
|
5145
|
+
)
|
|
5146
|
+
});
|
|
5147
|
+
var AskQuestionSchema = z12.object({
|
|
5148
|
+
question: z12.string().min(1).describe("Full question text (end with ?)."),
|
|
5149
|
+
header: z12.string().min(1).max(16).describe("Very short label (chip), max 12 chars. E.g. 'Auth method'."),
|
|
5150
|
+
options: z12.array(AskQuestionOptionSchema).min(2).max(4).describe("2-4 options. Mutually exclusive unless multiSelect=true."),
|
|
5151
|
+
multiSelect: z12.boolean().optional().describe("Allow multiple selections. Default false.")
|
|
5152
|
+
});
|
|
5153
|
+
var AskUserQuestionArgs = z12.object({
|
|
5154
|
+
questions: z12.array(AskQuestionSchema).min(1).max(4).describe("1-4 questions to ask the user sequentially.")
|
|
5155
|
+
});
|
|
5156
|
+
var AskUserQuestionTool = defineTool({
|
|
5157
|
+
name: "AskUserQuestion",
|
|
5158
|
+
description: "Ask the user one or more multiple-choice questions when their input is needed to proceed. Each question has 2-4 options. Use multiSelect=true for non-mutually-exclusive choices. Prefer this over plain-text questions when the answer space is bounded. If the user presses Esc, the entire batch is treated as cancelled.",
|
|
5159
|
+
parameters: AskUserQuestionArgs,
|
|
5160
|
+
permission: "read",
|
|
5161
|
+
summarize: (args) => `AskUserQuestion(${args.questions.length} question${args.questions.length === 1 ? "" : "s"})`,
|
|
5162
|
+
async execute(args, ctx) {
|
|
5163
|
+
if (!ctx.askQuestions) {
|
|
5164
|
+
return {
|
|
5165
|
+
content: "AskUserQuestion is unavailable: this agent run has no question handler. (Internal bug; tell the user.)",
|
|
5166
|
+
isError: true
|
|
5167
|
+
};
|
|
5168
|
+
}
|
|
5169
|
+
const responses = await ctx.askQuestions(args.questions);
|
|
5170
|
+
if (responses.length > 0 && responses[0].cancelled) {
|
|
5171
|
+
return {
|
|
5172
|
+
content: "User cancelled (Esc). No answers were collected.",
|
|
5173
|
+
isError: false
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
const blocks = args.questions.map((q, qi) => {
|
|
5177
|
+
const r = responses[qi];
|
|
5178
|
+
const sel = r?.selections ?? [];
|
|
5179
|
+
const ans = sel.length === 0 ? "(no answer)" : sel.join(", ");
|
|
5180
|
+
const notes = r?.notes?.trim();
|
|
5181
|
+
return notes ? `Q: ${q.question}
|
|
5182
|
+
A: ${ans}
|
|
5183
|
+
Notes: ${notes}` : `Q: ${q.question}
|
|
5184
|
+
A: ${ans}`;
|
|
5185
|
+
});
|
|
5186
|
+
return {
|
|
5187
|
+
content: blocks.join("\n\n"),
|
|
5188
|
+
summary: `Asked ${args.questions.length} question${args.questions.length === 1 ? "" : "s"}`
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
});
|
|
5192
|
+
|
|
2875
5193
|
// src/tools/builtin/index.ts
|
|
2876
5194
|
var BUILTIN_TOOLS = [
|
|
2877
5195
|
ReadTool,
|
|
@@ -2879,16 +5197,21 @@ var BUILTIN_TOOLS = [
|
|
|
2879
5197
|
EditTool,
|
|
2880
5198
|
BashTool,
|
|
2881
5199
|
GrepTool,
|
|
2882
|
-
GlobTool
|
|
5200
|
+
GlobTool,
|
|
5201
|
+
TodoWriteTool,
|
|
5202
|
+
WebFetchTool,
|
|
5203
|
+
MemoryReadTool,
|
|
5204
|
+
MemoryWriteTool,
|
|
5205
|
+
AskUserQuestionTool
|
|
2883
5206
|
];
|
|
2884
5207
|
|
|
2885
5208
|
// src/cli.tsx
|
|
2886
|
-
import { jsx as
|
|
5209
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2887
5210
|
var VERSION = "0.1.0";
|
|
2888
5211
|
async function main() {
|
|
2889
5212
|
const program = new Command();
|
|
2890
5213
|
program.name("muse").description("A TypeScript agent CLI built around OpenAI-compatible APIs. First-class support for self-hostable and Chinese LLMs.").version(VERSION, "-v, --version", "print version");
|
|
2891
|
-
program.argument("[prompt...]", "one-shot prompt (omit for interactive mode)").option("-m, --model <model>", "override model").option("-p, --provider <provider>", "override provider").option("--no-banner", "skip startup banner").option("--quiet", "minimal output (implies --no-banner)").option("--continue", "resume last session in this directory").option("--debug", "verbose logging").action(async (promptArgs, opts) => {
|
|
5214
|
+
program.argument("[prompt...]", "one-shot prompt (omit for interactive mode)").option("-m, --model <model>", "override model").option("-p, --provider <provider>", "override provider").option("--no-banner", "skip startup banner").option("--quiet", "minimal output (implies --no-banner)").option("--continue", "resume last session in this directory").option("--mode <mode>", "initial permission mode (default|acceptEdits|plan|bypassPermissions)").option("--debug", "verbose logging").action(async (promptArgs, opts) => {
|
|
2892
5215
|
if (opts.debug) log.setLevel("debug");
|
|
2893
5216
|
const cwd = process.cwd();
|
|
2894
5217
|
const { settings, sources } = await loadSettings(cwd);
|
|
@@ -2908,7 +5231,7 @@ async function main() {
|
|
|
2908
5231
|
llmModelName = llm.model;
|
|
2909
5232
|
} else {
|
|
2910
5233
|
if (!provider || !model) {
|
|
2911
|
-
die("No model configured. Either define one in ~/.muse/models.json or set llm.provider+llm.model in settings.json.");
|
|
5234
|
+
die("No model configured. Either define one in ~/.muse/models.local.json or set llm.provider+llm.model in settings.json.");
|
|
2912
5235
|
}
|
|
2913
5236
|
llm = createLLMClient({ provider, model, providers: settings.providers ?? {} });
|
|
2914
5237
|
llmProviderName = provider;
|
|
@@ -2921,24 +5244,62 @@ async function main() {
|
|
|
2921
5244
|
const tools = new ToolRegistry();
|
|
2922
5245
|
tools.registerAll(BUILTIN_TOOLS);
|
|
2923
5246
|
const permissions = new PermissionGate(settings.permissions);
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
5247
|
+
if (opts.mode) {
|
|
5248
|
+
const valid = ["default", "acceptEdits", "plan", "bypassPermissions"];
|
|
5249
|
+
if (!valid.includes(opts.mode)) {
|
|
5250
|
+
die(`Invalid --mode "${opts.mode}". Valid: ${valid.join(", ")}`);
|
|
5251
|
+
}
|
|
5252
|
+
permissions.setMode(opts.mode);
|
|
5253
|
+
}
|
|
5254
|
+
let session;
|
|
5255
|
+
let initialMessages;
|
|
5256
|
+
if (opts.continue) {
|
|
5257
|
+
const latest = await Session.findLatest(cwd);
|
|
5258
|
+
if (latest) {
|
|
5259
|
+
const opened = await Session.open(latest);
|
|
5260
|
+
session = opened.session;
|
|
5261
|
+
initialMessages = Session.messagesFromEvents(opened.events);
|
|
5262
|
+
log.debug("resumed session", { id: latest.id, messages: initialMessages.length });
|
|
5263
|
+
} else {
|
|
5264
|
+
session = await Session.create(cwd);
|
|
5265
|
+
await session.append({
|
|
5266
|
+
type: "session_start",
|
|
5267
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5268
|
+
cwd,
|
|
5269
|
+
provider: llmProviderName,
|
|
5270
|
+
model: llmModelName
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
} else {
|
|
5274
|
+
session = await Session.create(cwd);
|
|
5275
|
+
await session.append({
|
|
5276
|
+
type: "session_start",
|
|
5277
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5278
|
+
cwd,
|
|
5279
|
+
provider: llmProviderName,
|
|
5280
|
+
model: llmModelName
|
|
5281
|
+
});
|
|
5282
|
+
}
|
|
2932
5283
|
const showBanner = !opts.quiet && opts.banner !== false;
|
|
2933
5284
|
const lang = settings.ui?.lang ?? "en";
|
|
2934
5285
|
const pipedInput = await readStdinIfPiped();
|
|
2935
5286
|
const oneShotPrompt = [...promptArgs ?? [], pipedInput].filter(Boolean).join("\n").trim();
|
|
2936
5287
|
if (oneShotPrompt) {
|
|
2937
|
-
await runOneShot({
|
|
5288
|
+
await runOneShot({
|
|
5289
|
+
llm,
|
|
5290
|
+
tools,
|
|
5291
|
+
permissions,
|
|
5292
|
+
session,
|
|
5293
|
+
cwd,
|
|
5294
|
+
lang,
|
|
5295
|
+
prompt: oneShotPrompt,
|
|
5296
|
+
quiet: opts.quiet ?? false,
|
|
5297
|
+
initialMessages
|
|
5298
|
+
});
|
|
2938
5299
|
return;
|
|
2939
5300
|
}
|
|
2940
5301
|
const { waitUntilExit } = render(
|
|
2941
|
-
/* @__PURE__ */
|
|
5302
|
+
/* @__PURE__ */ jsx16(
|
|
2942
5303
|
App,
|
|
2943
5304
|
{
|
|
2944
5305
|
llm,
|
|
@@ -2951,7 +5312,8 @@ async function main() {
|
|
|
2951
5312
|
modelsSources,
|
|
2952
5313
|
cwd,
|
|
2953
5314
|
lang,
|
|
2954
|
-
showBanner
|
|
5315
|
+
showBanner,
|
|
5316
|
+
initialMessages
|
|
2955
5317
|
}
|
|
2956
5318
|
)
|
|
2957
5319
|
);
|
|
@@ -2966,12 +5328,14 @@ async function readStdinIfPiped() {
|
|
|
2966
5328
|
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
2967
5329
|
}
|
|
2968
5330
|
async function runOneShot(opts) {
|
|
5331
|
+
const memoryIndex = await loadMemoryIndex(opts.cwd);
|
|
2969
5332
|
const systemPrompt = buildSystemPrompt({
|
|
2970
5333
|
cwd: opts.cwd,
|
|
2971
5334
|
model: opts.llm.model,
|
|
2972
5335
|
provider: opts.llm.providerName,
|
|
2973
5336
|
lang: opts.lang,
|
|
2974
|
-
toolNames: opts.tools.list().map((t) => t.name)
|
|
5337
|
+
toolNames: opts.tools.list().map((t) => t.name),
|
|
5338
|
+
memoryIndex
|
|
2975
5339
|
});
|
|
2976
5340
|
const agent = new Agent({
|
|
2977
5341
|
llm: opts.llm,
|
|
@@ -2994,10 +5358,11 @@ async function runOneShot(opts) {
|
|
|
2994
5358
|
if (!opts.quiet) process.stderr.write(`
|
|
2995
5359
|
[denied: ${toolName} \u2014 ${summary}; run in interactive mode to approve]
|
|
2996
5360
|
`);
|
|
2997
|
-
return
|
|
5361
|
+
return "no";
|
|
2998
5362
|
}
|
|
2999
5363
|
}
|
|
3000
5364
|
});
|
|
5365
|
+
if (opts.initialMessages?.length) agent.setMessages(opts.initialMessages);
|
|
3001
5366
|
await agent.runTurn(opts.prompt);
|
|
3002
5367
|
process.stdout.write("\n");
|
|
3003
5368
|
}
|