miii-agent 0.1.1 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +407 -141
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createElement } from "react";
|
|
|
6
6
|
|
|
7
7
|
// src/ui/App.tsx
|
|
8
8
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
9
|
-
import { Box as
|
|
9
|
+
import { Box as Box10, Text as Text10, useApp } from "ink";
|
|
10
10
|
import { homedir as homedir2 } from "os";
|
|
11
11
|
import { sep } from "path";
|
|
12
12
|
|
|
@@ -195,8 +195,8 @@ function ModelList({ models, cursor, activeModel, showActive }) {
|
|
|
195
195
|
"<model>"
|
|
196
196
|
] });
|
|
197
197
|
}
|
|
198
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "
|
|
199
|
-
i === cursor ? "\
|
|
198
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "blue" : void 0, dimColor: i !== cursor, children: [
|
|
199
|
+
i === cursor ? "\u276F " : " ",
|
|
200
200
|
m,
|
|
201
201
|
showActive && m === activeModel ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (active)" }) : null
|
|
202
202
|
] }, m)) });
|
|
@@ -266,35 +266,72 @@ function ModelsView({ models, cursor, model, ollamaHost, effort }) {
|
|
|
266
266
|
] });
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
// src/ui/
|
|
269
|
+
// src/ui/SessionsView.tsx
|
|
270
270
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
271
271
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
272
|
+
function relativeTime(iso) {
|
|
273
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
274
|
+
const min = Math.floor(diff / 6e4);
|
|
275
|
+
if (min < 1) return "just now";
|
|
276
|
+
if (min < 60) return `${min}m ago`;
|
|
277
|
+
const hr = Math.floor(min / 60);
|
|
278
|
+
if (hr < 24) return `${hr}h ago`;
|
|
279
|
+
const d = Math.floor(hr / 24);
|
|
280
|
+
return `${d}d ago`;
|
|
281
|
+
}
|
|
282
|
+
function SessionsView({ sessions, cursor }) {
|
|
283
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
|
|
284
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "resume session" }),
|
|
285
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: sessions.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "no saved sessions yet" }) : sessions.map((s, i) => {
|
|
286
|
+
const active = i === cursor;
|
|
287
|
+
const label = s.title;
|
|
288
|
+
return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
289
|
+
/* @__PURE__ */ jsxs5(Text5, { color: active ? "blue" : void 0, dimColor: !active, children: [
|
|
290
|
+
active ? "\u276F " : " ",
|
|
291
|
+
label
|
|
292
|
+
] }),
|
|
293
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `\xB7 ${s.messageCount} msgs \xB7 ${relativeTime(s.updatedAt)}` })
|
|
294
|
+
] }, s.id);
|
|
295
|
+
}) }),
|
|
296
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate enter resume d delete esc cancel" }) })
|
|
297
|
+
] });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/ui/CommandPalette.tsx
|
|
301
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
302
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
272
303
|
var COMMANDS = [
|
|
273
304
|
{ name: "/models", description: "switch model or adjust effort" },
|
|
305
|
+
{ name: "/new", description: "save current session and start fresh" },
|
|
306
|
+
{ name: "/sessions", description: "list sessions and resume one" },
|
|
274
307
|
{ name: "/clear", description: "clear chat and reset context" },
|
|
275
308
|
{ name: "/exit", description: "quit miii" }
|
|
276
309
|
];
|
|
277
310
|
function CommandPalette({ filter, cursor }) {
|
|
278
311
|
const filtered = COMMANDS.filter((c) => c.name.startsWith(filter));
|
|
279
312
|
if (filtered.length === 0) return null;
|
|
280
|
-
|
|
281
|
-
|
|
313
|
+
const nameWidth = Math.max(...filtered.map((c) => c.name.length));
|
|
314
|
+
return /* @__PURE__ */ jsxs6(
|
|
315
|
+
Box6,
|
|
282
316
|
{
|
|
283
317
|
flexDirection: "column",
|
|
284
|
-
borderStyle: "
|
|
285
|
-
borderColor: "
|
|
318
|
+
borderStyle: "round",
|
|
319
|
+
borderColor: "gray",
|
|
286
320
|
marginX: 1,
|
|
287
321
|
marginBottom: 0,
|
|
288
322
|
paddingX: 1,
|
|
289
323
|
children: [
|
|
290
324
|
filtered.map((cmd, i) => {
|
|
291
325
|
const active = i === cursor;
|
|
292
|
-
return /* @__PURE__ */
|
|
293
|
-
/* @__PURE__ */
|
|
294
|
-
|
|
326
|
+
return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
327
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
|
|
328
|
+
active ? "\u276F " : " ",
|
|
329
|
+
cmd.name.padEnd(nameWidth)
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: cmd.description })
|
|
295
332
|
] }, cmd.name);
|
|
296
333
|
}),
|
|
297
|
-
/* @__PURE__ */
|
|
334
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 0, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191\u2193 navigate tab/enter autocomplete esc dismiss" }) })
|
|
298
335
|
]
|
|
299
336
|
}
|
|
300
337
|
);
|
|
@@ -303,11 +340,150 @@ function filteredCommands(filter) {
|
|
|
303
340
|
return COMMANDS.filter((c) => c.name.startsWith(filter));
|
|
304
341
|
}
|
|
305
342
|
|
|
343
|
+
// src/session/store.ts
|
|
344
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
345
|
+
import { join as join2 } from "path";
|
|
346
|
+
import { randomUUID } from "crypto";
|
|
347
|
+
var SESSION_DIR = join2(process.cwd(), ".miii", "session");
|
|
348
|
+
function newSessionId() {
|
|
349
|
+
return randomUUID();
|
|
350
|
+
}
|
|
351
|
+
function sessionPath(id) {
|
|
352
|
+
return join2(SESSION_DIR, `${id}.jsonl`);
|
|
353
|
+
}
|
|
354
|
+
function messageText(m) {
|
|
355
|
+
if (typeof m.content === "string") return m.content;
|
|
356
|
+
return m.content.map((b) => {
|
|
357
|
+
if (b.type === "text") return b.text;
|
|
358
|
+
if (b.type === "tool_use") return `[tool ${b.name}]`;
|
|
359
|
+
if (b.type === "tool_result") return "[result]";
|
|
360
|
+
return "";
|
|
361
|
+
}).join(" ");
|
|
362
|
+
}
|
|
363
|
+
function firstUserText(messages) {
|
|
364
|
+
const first = messages.find((m) => m.role === "user");
|
|
365
|
+
if (!first) return "untitled";
|
|
366
|
+
return messageText(first).trim().slice(0, 80) || "untitled";
|
|
367
|
+
}
|
|
368
|
+
function readMeta(id) {
|
|
369
|
+
try {
|
|
370
|
+
const raw = readFileSync2(sessionPath(id), "utf-8");
|
|
371
|
+
const firstLine = raw.slice(0, raw.indexOf("\n") === -1 ? raw.length : raw.indexOf("\n"));
|
|
372
|
+
const parsed = JSON.parse(firstLine);
|
|
373
|
+
if (parsed.type !== "meta") return null;
|
|
374
|
+
const { type: _t, ...meta } = parsed;
|
|
375
|
+
return meta;
|
|
376
|
+
} catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function persistSession(id, messages, title) {
|
|
381
|
+
if (!messages.length) return;
|
|
382
|
+
mkdirSync2(SESSION_DIR, { recursive: true });
|
|
383
|
+
const existing = readMeta(id);
|
|
384
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
385
|
+
const meta = {
|
|
386
|
+
id,
|
|
387
|
+
createdAt: existing?.createdAt ?? now,
|
|
388
|
+
updatedAt: now,
|
|
389
|
+
title: title ?? existing?.title ?? firstUserText(messages),
|
|
390
|
+
messageCount: messages.length
|
|
391
|
+
};
|
|
392
|
+
const lines = [JSON.stringify({ type: "meta", ...meta })];
|
|
393
|
+
for (const message of messages) {
|
|
394
|
+
lines.push(JSON.stringify({ type: "message", message }));
|
|
395
|
+
}
|
|
396
|
+
writeFileSync2(sessionPath(id), lines.join("\n") + "\n", "utf-8");
|
|
397
|
+
}
|
|
398
|
+
function listSessions() {
|
|
399
|
+
if (!existsSync2(SESSION_DIR)) return [];
|
|
400
|
+
const metas = [];
|
|
401
|
+
for (const file of readdirSync(SESSION_DIR)) {
|
|
402
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
403
|
+
const meta = readMeta(file.replace(/\.jsonl$/, ""));
|
|
404
|
+
if (meta) metas.push(meta);
|
|
405
|
+
}
|
|
406
|
+
return metas.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
407
|
+
}
|
|
408
|
+
function deleteSession(id) {
|
|
409
|
+
try {
|
|
410
|
+
rmSync(sessionPath(id), { force: true });
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function loadSession(id) {
|
|
415
|
+
try {
|
|
416
|
+
const raw = readFileSync2(sessionPath(id), "utf-8");
|
|
417
|
+
const messages = [];
|
|
418
|
+
for (const line of raw.split("\n")) {
|
|
419
|
+
if (!line.trim()) continue;
|
|
420
|
+
const parsed = JSON.parse(line);
|
|
421
|
+
if (parsed.type === "message") messages.push(parsed.message);
|
|
422
|
+
}
|
|
423
|
+
return messages;
|
|
424
|
+
} catch {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function toDisplayMessages(history) {
|
|
429
|
+
const out = [];
|
|
430
|
+
for (const m of history) {
|
|
431
|
+
if (m.role === "system") continue;
|
|
432
|
+
const blocks = Array.isArray(m.content) ? m.content : [{ type: "text", text: m.content }];
|
|
433
|
+
if (m.role === "user") {
|
|
434
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
435
|
+
const results = blocks.filter((b) => b.type === "tool_result");
|
|
436
|
+
if (results.length && out.length) {
|
|
437
|
+
const last = out[out.length - 1];
|
|
438
|
+
last.tool_results = [
|
|
439
|
+
...last.tool_results ?? [],
|
|
440
|
+
...results.map((r) => ({
|
|
441
|
+
tool_use_id: r.tool_use_id,
|
|
442
|
+
content: r.content,
|
|
443
|
+
is_error: r.is_error
|
|
444
|
+
}))
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
if (text.trim()) out.push({ role: "user", content: text });
|
|
448
|
+
} else {
|
|
449
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
450
|
+
const uses = blocks.filter((b) => b.type === "tool_use").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
451
|
+
out.push({
|
|
452
|
+
role: "assistant",
|
|
453
|
+
content: text,
|
|
454
|
+
tool_uses: uses.length ? uses : void 0
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
async function summarizeMessage(model, text) {
|
|
461
|
+
const fallback = text.trim().slice(0, 80) || "untitled";
|
|
462
|
+
const prompt = `Summarize this user request as a short title, 3-6 words, no punctuation. Reply with the title only.
|
|
463
|
+
|
|
464
|
+
Request:
|
|
465
|
+
${text.slice(0, 2e3)}`;
|
|
466
|
+
try {
|
|
467
|
+
let out = "";
|
|
468
|
+
for await (const chunk of chat(
|
|
469
|
+
model,
|
|
470
|
+
[{ role: "user", content: prompt }],
|
|
471
|
+
void 0,
|
|
472
|
+
{ temperature: 0.2, num_predict: 32 }
|
|
473
|
+
)) {
|
|
474
|
+
if (chunk.content) out += chunk.content;
|
|
475
|
+
}
|
|
476
|
+
return out.trim().split("\n").filter(Boolean)[0]?.trim() || fallback;
|
|
477
|
+
} catch {
|
|
478
|
+
return fallback;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
306
482
|
// src/ui/FilePicker.tsx
|
|
307
|
-
import { Box as
|
|
308
|
-
import { readdirSync } from "fs";
|
|
309
|
-
import { join as
|
|
310
|
-
import { jsx as
|
|
483
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
484
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
485
|
+
import { join as join3, relative } from "path";
|
|
486
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
311
487
|
var IGNORE = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".miii"]);
|
|
312
488
|
var MAX_RESULTS = 10;
|
|
313
489
|
var MAX_SCAN = 2e3;
|
|
@@ -320,13 +496,13 @@ function listFiles(cwd) {
|
|
|
320
496
|
const dir = stack.pop();
|
|
321
497
|
let entries;
|
|
322
498
|
try {
|
|
323
|
-
entries =
|
|
499
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
324
500
|
} catch {
|
|
325
501
|
continue;
|
|
326
502
|
}
|
|
327
503
|
for (const e of entries) {
|
|
328
504
|
if (IGNORE.has(e.name) || e.name.startsWith(".")) continue;
|
|
329
|
-
const full =
|
|
505
|
+
const full = join3(dir, e.name);
|
|
330
506
|
if (e.isDirectory()) stack.push(full);
|
|
331
507
|
else if (e.isFile()) out.push(relative(cwd, full));
|
|
332
508
|
if (out.length >= MAX_SCAN) break;
|
|
@@ -360,36 +536,36 @@ function searchFiles(cwd, query) {
|
|
|
360
536
|
}
|
|
361
537
|
function FilePicker({ matches, cursor }) {
|
|
362
538
|
if (matches.length === 0) return null;
|
|
363
|
-
return /* @__PURE__ */
|
|
364
|
-
|
|
539
|
+
return /* @__PURE__ */ jsxs7(
|
|
540
|
+
Box7,
|
|
365
541
|
{
|
|
366
542
|
flexDirection: "column",
|
|
367
|
-
borderStyle: "
|
|
368
|
-
borderColor: "
|
|
543
|
+
borderStyle: "round",
|
|
544
|
+
borderColor: "gray",
|
|
369
545
|
marginX: 1,
|
|
370
546
|
marginBottom: 0,
|
|
371
547
|
paddingX: 1,
|
|
372
548
|
children: [
|
|
373
549
|
matches.map((f, i) => {
|
|
374
550
|
const active = i === cursor;
|
|
375
|
-
return /* @__PURE__ */
|
|
376
|
-
active ? "\
|
|
551
|
+
return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
|
|
552
|
+
active ? "\u276F " : " ",
|
|
377
553
|
f
|
|
378
554
|
] }) }, f);
|
|
379
555
|
}),
|
|
380
|
-
/* @__PURE__ */
|
|
556
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 0, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191\u2193 navigate tab insert esc dismiss" }) })
|
|
381
557
|
]
|
|
382
558
|
}
|
|
383
559
|
);
|
|
384
560
|
}
|
|
385
561
|
|
|
386
562
|
// src/ui/ChatView.tsx
|
|
387
|
-
import { Box as
|
|
563
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
388
564
|
|
|
389
565
|
// src/ui/ThinkingBlock.tsx
|
|
390
566
|
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
391
|
-
import { Box as
|
|
392
|
-
import { jsx as
|
|
567
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
568
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
393
569
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
394
570
|
var globalThinkingVisible = false;
|
|
395
571
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -415,25 +591,35 @@ function ThinkingBlock({ content }) {
|
|
|
415
591
|
const t = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80);
|
|
416
592
|
return () => clearInterval(t);
|
|
417
593
|
}, []);
|
|
418
|
-
return /* @__PURE__ */
|
|
419
|
-
/* @__PURE__ */
|
|
420
|
-
/* @__PURE__ */
|
|
594
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
595
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
596
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "blue", children: [
|
|
421
597
|
FRAMES[frame],
|
|
422
598
|
" "
|
|
423
599
|
] }),
|
|
424
|
-
/* @__PURE__ */
|
|
425
|
-
/* @__PURE__ */
|
|
600
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: "thinking\u2026" }),
|
|
601
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
426
602
|
" \xB7 ctrl+t to ",
|
|
427
603
|
visible ? "hide" : "show",
|
|
428
604
|
" thoughts"
|
|
429
605
|
] })
|
|
430
606
|
] }),
|
|
431
|
-
visible && content ? /* @__PURE__ */
|
|
607
|
+
visible && content ? /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: content }) }) : null
|
|
432
608
|
] });
|
|
433
609
|
}
|
|
434
610
|
|
|
611
|
+
// src/ui/constants.ts
|
|
612
|
+
var EMPTY_STATE_HINTS = [
|
|
613
|
+
"\u2022 explain @file \u2014 reference a file with @",
|
|
614
|
+
"\u2022 /models \u2014 switch model or effort",
|
|
615
|
+
"\u2022 /new \u2014 start a new chat",
|
|
616
|
+
"\u2022 /sessions \u2014 view saved chats",
|
|
617
|
+
"\u2022 ctrl+t \u2014 toggle thinking"
|
|
618
|
+
];
|
|
619
|
+
var EMPTY_STATE_TITLE = "Ask anything, or try:";
|
|
620
|
+
|
|
435
621
|
// src/ui/ChatView.tsx
|
|
436
|
-
import { jsx as
|
|
622
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
437
623
|
function formatTokens(n) {
|
|
438
624
|
if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + "k";
|
|
439
625
|
return String(n);
|
|
@@ -459,27 +645,27 @@ function FileEditBlock({
|
|
|
459
645
|
const MAX = 16;
|
|
460
646
|
const shown = previewLines.slice(0, MAX);
|
|
461
647
|
const extra = previewLines.length - shown.length;
|
|
462
|
-
return /* @__PURE__ */
|
|
463
|
-
/* @__PURE__ */
|
|
464
|
-
/* @__PURE__ */
|
|
465
|
-
/* @__PURE__ */
|
|
648
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
|
|
649
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
650
|
+
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25CF " }),
|
|
651
|
+
/* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
|
|
466
652
|
label,
|
|
467
653
|
" "
|
|
468
654
|
] }),
|
|
469
|
-
/* @__PURE__ */
|
|
470
|
-
/* @__PURE__ */
|
|
471
|
-
/* @__PURE__ */
|
|
655
|
+
/* @__PURE__ */ jsx9(Text9, { children: "(" }),
|
|
656
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: path }),
|
|
657
|
+
/* @__PURE__ */ jsx9(Text9, { children: ")" })
|
|
472
658
|
] }),
|
|
473
|
-
/* @__PURE__ */
|
|
659
|
+
/* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
474
660
|
"\u23BF ",
|
|
475
661
|
removed > 0 ? `Added ${added} lines, removed ${removed} lines` : `Added ${added} lines`
|
|
476
662
|
] }) }),
|
|
477
|
-
shown.map((ln, i) => /* @__PURE__ */
|
|
663
|
+
shown.map((ln, i) => /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { color: ln.sign === "+" ? "green" : ln.sign === "-" ? "red" : void 0, dimColor: ln.sign === " ", children: [
|
|
478
664
|
ln.sign,
|
|
479
665
|
" ",
|
|
480
666
|
ln.text
|
|
481
667
|
] }) }, i)),
|
|
482
|
-
extra > 0 && /* @__PURE__ */
|
|
668
|
+
extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
483
669
|
"\u2026 ",
|
|
484
670
|
extra,
|
|
485
671
|
" more lines"
|
|
@@ -552,7 +738,7 @@ function ToolResultBlock({ result, toolName }) {
|
|
|
552
738
|
const lines = content.split("\n");
|
|
553
739
|
const showMulti = (toolName === "run_bash" || toolName === "grep" || toolName === "glob" || result.is_error) && lines.length > 1;
|
|
554
740
|
if (!showMulti) {
|
|
555
|
-
return /* @__PURE__ */
|
|
741
|
+
return /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
|
|
556
742
|
"\u23BF ",
|
|
557
743
|
summarizeResult(result, toolName)
|
|
558
744
|
] }) });
|
|
@@ -561,13 +747,13 @@ function ToolResultBlock({ result, toolName }) {
|
|
|
561
747
|
const MAX_LINE_WIDTH = 200;
|
|
562
748
|
const shown = lines.slice(0, MAX_LINES).map((l) => truncate(l, MAX_LINE_WIDTH));
|
|
563
749
|
const extra = lines.length - shown.length;
|
|
564
|
-
return /* @__PURE__ */
|
|
565
|
-
/* @__PURE__ */
|
|
750
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
|
|
751
|
+
/* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
|
|
566
752
|
"\u23BF ",
|
|
567
753
|
summarizeResult(result, toolName)
|
|
568
754
|
] }),
|
|
569
|
-
shown.map((ln, i) => /* @__PURE__ */
|
|
570
|
-
extra > 0 && /* @__PURE__ */
|
|
755
|
+
shown.map((ln, i) => /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsx9(Text9, { color: result.is_error ? "red" : void 0, dimColor: true, children: ln || " " }) }, i)),
|
|
756
|
+
extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
571
757
|
"\u2026 ",
|
|
572
758
|
extra,
|
|
573
759
|
" more lines"
|
|
@@ -580,7 +766,7 @@ function ToolUseLine({ use, result }) {
|
|
|
580
766
|
const content = input.content ?? "";
|
|
581
767
|
const added = countLines(content);
|
|
582
768
|
const preview = content.split("\n").map((t) => ({ sign: "+", text: t }));
|
|
583
|
-
return /* @__PURE__ */
|
|
769
|
+
return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Write", path: input.path ?? "", added, removed: 0, previewLines: preview });
|
|
584
770
|
}
|
|
585
771
|
if (use.name === "edit_file" && !result?.is_error) {
|
|
586
772
|
const input = use.input;
|
|
@@ -592,34 +778,34 @@ function ToolUseLine({ use, result }) {
|
|
|
592
778
|
...oldS.split("\n").map((t) => ({ sign: "-", text: t })),
|
|
593
779
|
...newS.split("\n").map((t) => ({ sign: "+", text: t }))
|
|
594
780
|
];
|
|
595
|
-
return /* @__PURE__ */
|
|
781
|
+
return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Update", path: input.path ?? "", added, removed, previewLines: preview });
|
|
596
782
|
}
|
|
597
783
|
const { label, arg } = toolHeader(use);
|
|
598
|
-
return /* @__PURE__ */
|
|
599
|
-
/* @__PURE__ */
|
|
600
|
-
/* @__PURE__ */
|
|
601
|
-
/* @__PURE__ */
|
|
784
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
|
|
785
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
786
|
+
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25CF " }),
|
|
787
|
+
/* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
|
|
602
788
|
label,
|
|
603
789
|
" "
|
|
604
790
|
] }),
|
|
605
|
-
/* @__PURE__ */
|
|
606
|
-
/* @__PURE__ */
|
|
607
|
-
/* @__PURE__ */
|
|
791
|
+
/* @__PURE__ */ jsx9(Text9, { children: "(" }),
|
|
792
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: arg }),
|
|
793
|
+
/* @__PURE__ */ jsx9(Text9, { children: ")" })
|
|
608
794
|
] }),
|
|
609
|
-
result && /* @__PURE__ */
|
|
795
|
+
result && /* @__PURE__ */ jsx9(ToolResultBlock, { result, toolName: use.name })
|
|
610
796
|
] });
|
|
611
797
|
}
|
|
612
798
|
function AssistantMessage({ msg }) {
|
|
613
|
-
return /* @__PURE__ */
|
|
614
|
-
msg.content && /* @__PURE__ */
|
|
615
|
-
/* @__PURE__ */
|
|
616
|
-
/* @__PURE__ */
|
|
799
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
800
|
+
msg.content && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
|
|
801
|
+
/* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF " }),
|
|
802
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
|
|
617
803
|
] }),
|
|
618
804
|
msg.tool_uses?.map((u) => {
|
|
619
805
|
const r = msg.tool_results?.find((x) => x.tool_use_id === u.id);
|
|
620
|
-
return /* @__PURE__ */
|
|
806
|
+
return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
|
|
621
807
|
}),
|
|
622
|
-
msg.tokens && /* @__PURE__ */
|
|
808
|
+
msg.tokens && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
623
809
|
`\u21B3 Completed \xB7 ${formatTokens(msg.tokens.prompt_eval + msg.tokens.eval)} tokens`,
|
|
624
810
|
msg.duration != null ? ` \xB7 ${formatDuration(msg.duration)}` : ""
|
|
625
811
|
] }) })
|
|
@@ -642,20 +828,21 @@ function summarizeInput(input) {
|
|
|
642
828
|
return "";
|
|
643
829
|
}
|
|
644
830
|
function PermissionPrompt({ req, cursor }) {
|
|
831
|
+
const label = TOOL_LABEL[req.toolName] ?? req.toolName;
|
|
645
832
|
const options = [
|
|
646
833
|
{ label: "Yes", key: "yes" },
|
|
647
|
-
{ label: "No
|
|
834
|
+
{ label: "No", key: "no" }
|
|
648
835
|
];
|
|
649
836
|
const summary = summarizeInput(req.input);
|
|
650
|
-
return /* @__PURE__ */
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
/* @__PURE__ */
|
|
837
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
|
|
838
|
+
/* @__PURE__ */ jsx9(Text9, { color: "blue", bold: true, children: "Tool use" }),
|
|
839
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
653
840
|
"Allow ",
|
|
654
|
-
/* @__PURE__ */
|
|
841
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: label }),
|
|
655
842
|
"?"
|
|
656
843
|
] }) }),
|
|
657
|
-
summary && /* @__PURE__ */
|
|
658
|
-
/* @__PURE__ */
|
|
844
|
+
summary && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: summary }) }),
|
|
845
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsxs9(Text9, { color: i === cursor ? "blue" : void 0, children: [
|
|
659
846
|
i === cursor ? "\u276F " : " ",
|
|
660
847
|
i + 1,
|
|
661
848
|
". ",
|
|
@@ -675,26 +862,34 @@ function ChatView({
|
|
|
675
862
|
activeToolUses,
|
|
676
863
|
activeToolResults
|
|
677
864
|
}) {
|
|
678
|
-
|
|
865
|
+
const empty = messages.length === 0 && !streaming && !thinking && !pendingPermission && !error;
|
|
866
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
|
|
867
|
+
empty && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
868
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: EMPTY_STATE_TITLE }),
|
|
869
|
+
EMPTY_STATE_HINTS.map((h, i) => /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
870
|
+
" ",
|
|
871
|
+
h
|
|
872
|
+
] }, i))
|
|
873
|
+
] }),
|
|
679
874
|
messages.map(
|
|
680
|
-
(msg, i) => msg.role === "user" ? /* @__PURE__ */
|
|
681
|
-
/* @__PURE__ */
|
|
682
|
-
/* @__PURE__ */
|
|
683
|
-
] }, i) : /* @__PURE__ */
|
|
875
|
+
(msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
|
|
876
|
+
/* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u25CF " }),
|
|
877
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
|
|
878
|
+
] }, i) : /* @__PURE__ */ jsx9(AssistantMessage, { msg }, i)
|
|
684
879
|
),
|
|
685
|
-
thinking && /* @__PURE__ */
|
|
686
|
-
streaming && streamingContent && /* @__PURE__ */
|
|
687
|
-
/* @__PURE__ */
|
|
688
|
-
/* @__PURE__ */
|
|
880
|
+
thinking && /* @__PURE__ */ jsx9(ThinkingBlock, { content: thinkingContent }),
|
|
881
|
+
streaming && streamingContent && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
|
|
882
|
+
/* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF " }),
|
|
883
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: streamingContent }) })
|
|
689
884
|
] }),
|
|
690
885
|
activeToolUses?.map((u) => {
|
|
691
886
|
const r = activeToolResults?.find((x) => x.tool_use_id === u.id);
|
|
692
|
-
return /* @__PURE__ */
|
|
887
|
+
return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
|
|
693
888
|
}),
|
|
694
|
-
pendingPermission && /* @__PURE__ */
|
|
695
|
-
error && /* @__PURE__ */
|
|
696
|
-
/* @__PURE__ */
|
|
697
|
-
/* @__PURE__ */
|
|
889
|
+
pendingPermission && /* @__PURE__ */ jsx9(PermissionPrompt, { req: pendingPermission, cursor: permissionCursor }),
|
|
890
|
+
error && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
|
|
891
|
+
/* @__PURE__ */ jsx9(Text9, { color: "red", children: "\u25CF " }),
|
|
892
|
+
/* @__PURE__ */ jsx9(Text9, { color: "red", children: error })
|
|
698
893
|
] })
|
|
699
894
|
] });
|
|
700
895
|
}
|
|
@@ -703,7 +898,7 @@ function ChatView({
|
|
|
703
898
|
import { useState as useState3, useRef } from "react";
|
|
704
899
|
|
|
705
900
|
// src/tools/edit_file.ts
|
|
706
|
-
import { readFileSync as
|
|
901
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
707
902
|
var edit_file = {
|
|
708
903
|
name: "edit_file",
|
|
709
904
|
description: "Replace an exact string in a file. old_str must be unique.",
|
|
@@ -717,7 +912,7 @@ var edit_file = {
|
|
|
717
912
|
required: ["path", "old_str", "new_str"]
|
|
718
913
|
},
|
|
719
914
|
handler: ({ path, old_str, new_str }) => {
|
|
720
|
-
const src =
|
|
915
|
+
const src = readFileSync3(path, "utf-8");
|
|
721
916
|
const first = src.indexOf(old_str);
|
|
722
917
|
if (first === -1) {
|
|
723
918
|
return { content: `old_str not found in ${path}`, is_error: true };
|
|
@@ -725,13 +920,13 @@ var edit_file = {
|
|
|
725
920
|
if (src.indexOf(old_str, first + 1) !== -1) {
|
|
726
921
|
return { content: `old_str not unique in ${path}`, is_error: true };
|
|
727
922
|
}
|
|
728
|
-
|
|
923
|
+
writeFileSync3(path, src.slice(0, first) + new_str + src.slice(first + old_str.length), "utf-8");
|
|
729
924
|
return { content: `Edited ${path}` };
|
|
730
925
|
}
|
|
731
926
|
};
|
|
732
927
|
|
|
733
928
|
// src/tools/read_file.ts
|
|
734
|
-
import { readFileSync as
|
|
929
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
735
930
|
var read_file = {
|
|
736
931
|
name: "read_file",
|
|
737
932
|
description: "Read entire file contents as UTF-8 text.",
|
|
@@ -745,7 +940,7 @@ var read_file = {
|
|
|
745
940
|
handler: ({ path }) => {
|
|
746
941
|
try {
|
|
747
942
|
const MAX = 2e5;
|
|
748
|
-
const raw =
|
|
943
|
+
const raw = readFileSync4(path, "utf-8");
|
|
749
944
|
const truncated = raw.length > MAX;
|
|
750
945
|
const body = truncated ? raw.slice(0, MAX) + `
|
|
751
946
|
[truncated: ${raw.length - MAX} more chars]` : raw;
|
|
@@ -757,7 +952,7 @@ var read_file = {
|
|
|
757
952
|
};
|
|
758
953
|
|
|
759
954
|
// src/tools/write_file.ts
|
|
760
|
-
import { writeFileSync as
|
|
955
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
761
956
|
import { dirname } from "path";
|
|
762
957
|
var write_file = {
|
|
763
958
|
name: "write_file",
|
|
@@ -772,8 +967,8 @@ var write_file = {
|
|
|
772
967
|
},
|
|
773
968
|
handler: ({ path, content }) => {
|
|
774
969
|
try {
|
|
775
|
-
|
|
776
|
-
|
|
970
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
971
|
+
writeFileSync4(path, content, "utf-8");
|
|
777
972
|
return { content: `Wrote ${path} (${content.length} bytes)` };
|
|
778
973
|
} catch (err) {
|
|
779
974
|
return { content: err instanceof Error ? err.message : String(err), is_error: true };
|
|
@@ -1562,19 +1757,28 @@ function useKeyboard(opts) {
|
|
|
1562
1757
|
cfg,
|
|
1563
1758
|
setCfg,
|
|
1564
1759
|
setActiveCtx,
|
|
1565
|
-
|
|
1566
|
-
permissionCursor,
|
|
1567
|
-
setPermissionCursor,
|
|
1568
|
-
resolvePermission,
|
|
1569
|
-
busyRef,
|
|
1570
|
-
abortRef,
|
|
1760
|
+
agent,
|
|
1571
1761
|
input,
|
|
1572
1762
|
setInput,
|
|
1573
1763
|
paletteCursor,
|
|
1574
1764
|
setPaletteCursor,
|
|
1575
1765
|
filePickerCursor,
|
|
1576
1766
|
setFilePickerCursor,
|
|
1767
|
+
sessionId,
|
|
1768
|
+
setSessionId,
|
|
1769
|
+
sessions,
|
|
1770
|
+
setSessions,
|
|
1771
|
+
setNotice
|
|
1772
|
+
} = opts;
|
|
1773
|
+
const {
|
|
1774
|
+
pendingPermissionRef,
|
|
1775
|
+
permissionCursor,
|
|
1776
|
+
setPermissionCursor,
|
|
1777
|
+
resolvePermission,
|
|
1778
|
+
busyRef,
|
|
1779
|
+
abortRef,
|
|
1577
1780
|
sendMessage,
|
|
1781
|
+
agentHistory,
|
|
1578
1782
|
setMessages,
|
|
1579
1783
|
setAgentHistory,
|
|
1580
1784
|
setStreamingContent,
|
|
@@ -1582,7 +1786,17 @@ function useKeyboard(opts) {
|
|
|
1582
1786
|
setActiveToolUses,
|
|
1583
1787
|
setActiveToolResults,
|
|
1584
1788
|
setError
|
|
1585
|
-
} =
|
|
1789
|
+
} = agent;
|
|
1790
|
+
function clearSession() {
|
|
1791
|
+
setMessages(() => []);
|
|
1792
|
+
setAgentHistory([]);
|
|
1793
|
+
setStreamingContent("");
|
|
1794
|
+
setThinkingContent("");
|
|
1795
|
+
setActiveToolUses([]);
|
|
1796
|
+
setActiveToolResults([]);
|
|
1797
|
+
setError(null);
|
|
1798
|
+
setNotice(null);
|
|
1799
|
+
}
|
|
1586
1800
|
const effort = cfg.effort ?? "medium";
|
|
1587
1801
|
useInput((char, key) => {
|
|
1588
1802
|
if (key.ctrl && char === "c") {
|
|
@@ -1629,6 +1843,44 @@ function useKeyboard(opts) {
|
|
|
1629
1843
|
}
|
|
1630
1844
|
return;
|
|
1631
1845
|
}
|
|
1846
|
+
if (state === "sessions") {
|
|
1847
|
+
if (key.upArrow) {
|
|
1848
|
+
setCursor((i) => Math.max(0, i - 1));
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (key.downArrow) {
|
|
1852
|
+
setCursor((i) => Math.min(sessions.length - 1, i + 1));
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
if (key.escape) {
|
|
1856
|
+
setState("ready");
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if ((char === "d" || char === "x" || key.delete || key.backspace) && sessions[cursor]) {
|
|
1860
|
+
const meta = sessions[cursor];
|
|
1861
|
+
deleteSession(meta.id);
|
|
1862
|
+
const next = listSessions();
|
|
1863
|
+
setSessions(next);
|
|
1864
|
+
setCursor((i) => Math.max(0, Math.min(i, next.length - 1)));
|
|
1865
|
+
setNotice(`deleted \xB7 ${meta.title}`);
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
if (key.return && sessions[cursor]) {
|
|
1869
|
+
const meta = sessions[cursor];
|
|
1870
|
+
const history = loadSession(meta.id);
|
|
1871
|
+
setAgentHistory(history);
|
|
1872
|
+
setMessages(toDisplayMessages(history));
|
|
1873
|
+
setStreamingContent("");
|
|
1874
|
+
setThinkingContent("");
|
|
1875
|
+
setActiveToolUses([]);
|
|
1876
|
+
setActiveToolResults([]);
|
|
1877
|
+
setError(null);
|
|
1878
|
+
setSessionId(meta.id);
|
|
1879
|
+
setNotice(`resumed \xB7 ${meta.title}`);
|
|
1880
|
+
setState("ready");
|
|
1881
|
+
}
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1632
1884
|
if (state === "ready" && pendingPermissionRef.current) {
|
|
1633
1885
|
if (key.upArrow) {
|
|
1634
1886
|
setPermissionCursor((i) => Math.max(0, i - 1));
|
|
@@ -1693,16 +1945,30 @@ function useKeyboard(opts) {
|
|
|
1693
1945
|
setCursor(() => Math.max(0, models.findIndex((m) => m === cfg.model)));
|
|
1694
1946
|
setState("models");
|
|
1695
1947
|
} else if (trimmed === "/clear") {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1948
|
+
clearSession();
|
|
1949
|
+
} else if (trimmed === "/new") {
|
|
1950
|
+
if (agentHistory.length) setNotice("session saved");
|
|
1951
|
+
setSessionId(newSessionId());
|
|
1952
|
+
clearSession();
|
|
1953
|
+
} else if (trimmed === "/sessions") {
|
|
1954
|
+
setSessions(listSessions());
|
|
1955
|
+
setCursor(() => 0);
|
|
1956
|
+
setState("sessions");
|
|
1703
1957
|
} else if (trimmed === "/exit") {
|
|
1704
1958
|
exit();
|
|
1705
1959
|
} else if (trimmed) {
|
|
1960
|
+
setNotice(null);
|
|
1961
|
+
if (!agentHistory.length && cfg.model) {
|
|
1962
|
+
const id = sessionId;
|
|
1963
|
+
const model = cfg.model;
|
|
1964
|
+
void (async () => {
|
|
1965
|
+
try {
|
|
1966
|
+
const title = await summarizeMessage(model, trimmed);
|
|
1967
|
+
persistSession(id, [{ role: "user", content: trimmed }], title);
|
|
1968
|
+
} catch {
|
|
1969
|
+
}
|
|
1970
|
+
})();
|
|
1971
|
+
}
|
|
1706
1972
|
sendMessage(trimmed);
|
|
1707
1973
|
}
|
|
1708
1974
|
setInput(() => "");
|
|
@@ -1762,7 +2028,7 @@ async function checkForUpdate() {
|
|
|
1762
2028
|
}
|
|
1763
2029
|
|
|
1764
2030
|
// src/ui/App.tsx
|
|
1765
|
-
import { Fragment as Fragment2, jsx as
|
|
2031
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1766
2032
|
function App() {
|
|
1767
2033
|
const { exit } = useApp();
|
|
1768
2034
|
const cwd = process.cwd().replace(homedir2(), "~").split(sep).join("/");
|
|
@@ -1773,6 +2039,9 @@ function App() {
|
|
|
1773
2039
|
const [state, setState] = useState4("loading");
|
|
1774
2040
|
const [cursor, setCursor] = useState4(0);
|
|
1775
2041
|
const [updateAvailable, setUpdateAvailable] = useState4(null);
|
|
2042
|
+
const [sessionId, setSessionId] = useState4(() => newSessionId());
|
|
2043
|
+
const [sessions, setSessions] = useState4([]);
|
|
2044
|
+
const [notice, setNotice] = useState4(null);
|
|
1776
2045
|
const [input, setInput] = useState4("");
|
|
1777
2046
|
const [paletteCursor, setPaletteCursor] = useState4(0);
|
|
1778
2047
|
const [filePickerCursor, setFilePickerCursor] = useState4(0);
|
|
@@ -1782,6 +2051,9 @@ function App() {
|
|
|
1782
2051
|
if (v) setUpdateAvailable(v);
|
|
1783
2052
|
});
|
|
1784
2053
|
}, []);
|
|
2054
|
+
useEffect3(() => {
|
|
2055
|
+
if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
|
|
2056
|
+
}, [agent.agentHistory, sessionId]);
|
|
1785
2057
|
useEffect3(() => {
|
|
1786
2058
|
listModels().then((m) => {
|
|
1787
2059
|
setModels(m);
|
|
@@ -1811,26 +2083,18 @@ function App() {
|
|
|
1811
2083
|
cfg,
|
|
1812
2084
|
setCfg,
|
|
1813
2085
|
setActiveCtx,
|
|
1814
|
-
|
|
1815
|
-
permissionCursor: agent.permissionCursor,
|
|
1816
|
-
setPermissionCursor: agent.setPermissionCursor,
|
|
1817
|
-
resolvePermission: agent.resolvePermission,
|
|
1818
|
-
busyRef: agent.busyRef,
|
|
1819
|
-
abortRef: agent.abortRef,
|
|
2086
|
+
agent,
|
|
1820
2087
|
input,
|
|
1821
2088
|
setInput,
|
|
1822
2089
|
paletteCursor,
|
|
1823
2090
|
setPaletteCursor,
|
|
1824
2091
|
filePickerCursor,
|
|
1825
2092
|
setFilePickerCursor,
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
setActiveToolUses: agent.setActiveToolUses,
|
|
1832
|
-
setActiveToolResults: agent.setActiveToolResults,
|
|
1833
|
-
setError: agent.setError
|
|
2093
|
+
sessionId,
|
|
2094
|
+
setSessionId,
|
|
2095
|
+
sessions,
|
|
2096
|
+
setSessions,
|
|
2097
|
+
setNotice
|
|
1834
2098
|
});
|
|
1835
2099
|
const effort = cfg.effort ?? "medium";
|
|
1836
2100
|
const contextWarning = (() => {
|
|
@@ -1840,11 +2104,11 @@ function App() {
|
|
|
1840
2104
|
if (used < activeCtx * 0.7) return null;
|
|
1841
2105
|
return Math.round(used / activeCtx * 100);
|
|
1842
2106
|
})();
|
|
1843
|
-
return /* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
1845
|
-
updateAvailable && /* @__PURE__ */
|
|
1846
|
-
state === "loading" && !agent.error && /* @__PURE__ */
|
|
1847
|
-
agent.error && state !== "ready" && /* @__PURE__ */
|
|
2107
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
2108
|
+
/* @__PURE__ */ jsx10(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
|
|
2109
|
+
updateAvailable && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: npm i -g miii-agent` }) }),
|
|
2110
|
+
state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "connecting to ollama\u2026" }) }),
|
|
2111
|
+
agent.error && state !== "ready" && /* @__PURE__ */ jsx10(
|
|
1848
2112
|
ChatView,
|
|
1849
2113
|
{
|
|
1850
2114
|
messages: [],
|
|
@@ -1854,12 +2118,12 @@ function App() {
|
|
|
1854
2118
|
error: agent.error
|
|
1855
2119
|
}
|
|
1856
2120
|
),
|
|
1857
|
-
state === "select-model" && /* @__PURE__ */
|
|
1858
|
-
/* @__PURE__ */
|
|
1859
|
-
/* @__PURE__ */
|
|
1860
|
-
models.length > 0 && /* @__PURE__ */
|
|
2121
|
+
state === "select-model" && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginLeft: 2, children: [
|
|
2122
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "no model configured \u2014 select one" }),
|
|
2123
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(ModelList, { models, cursor }) }),
|
|
2124
|
+
models.length > 0 && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 navigate enter select ctrl+c quit" }) })
|
|
1861
2125
|
] }),
|
|
1862
|
-
state === "models" && /* @__PURE__ */
|
|
2126
|
+
state === "models" && /* @__PURE__ */ jsx10(
|
|
1863
2127
|
ModelsView,
|
|
1864
2128
|
{
|
|
1865
2129
|
models,
|
|
@@ -1869,8 +2133,10 @@ function App() {
|
|
|
1869
2133
|
effort
|
|
1870
2134
|
}
|
|
1871
2135
|
),
|
|
1872
|
-
state === "
|
|
1873
|
-
|
|
2136
|
+
state === "sessions" && /* @__PURE__ */ jsx10(SessionsView, { sessions, cursor }),
|
|
2137
|
+
state === "ready" && /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
2138
|
+
notice && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: `\u2713 ${notice}` }) }),
|
|
2139
|
+
/* @__PURE__ */ jsx10(
|
|
1874
2140
|
ChatView,
|
|
1875
2141
|
{
|
|
1876
2142
|
messages: agent.messages,
|
|
@@ -1885,15 +2151,15 @@ function App() {
|
|
|
1885
2151
|
activeToolResults: agent.activeToolResults
|
|
1886
2152
|
}
|
|
1887
2153
|
),
|
|
1888
|
-
input.startsWith("/") && /* @__PURE__ */
|
|
1889
|
-
contextWarning !== null && /* @__PURE__ */
|
|
2154
|
+
input.startsWith("/") && /* @__PURE__ */ jsx10(CommandPalette, { filter: input, cursor: paletteCursor }),
|
|
2155
|
+
contextWarning !== null && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: `\u26A0 context ${contextWarning}% full \u2014 run /clear and start fresh` }) }),
|
|
1890
2156
|
!input.startsWith("/") && (() => {
|
|
1891
2157
|
const m = parseMention(input);
|
|
1892
2158
|
if (!m) return null;
|
|
1893
|
-
return /* @__PURE__ */
|
|
2159
|
+
return /* @__PURE__ */ jsx10(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
|
|
1894
2160
|
})(),
|
|
1895
|
-
/* @__PURE__ */
|
|
1896
|
-
!agent.busy && /* @__PURE__ */
|
|
2161
|
+
/* @__PURE__ */ jsx10(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
|
|
2162
|
+
!agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "type / to see commands" }) })
|
|
1897
2163
|
] })
|
|
1898
2164
|
] });
|
|
1899
2165
|
}
|