miii-agent 0.1.4 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +384 -138
  2. 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 Box9, Text as Text9, useApp } from "ink";
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
 
@@ -266,19 +266,53 @@ function ModelsView({ models, cursor, model, ollamaHost, effort }) {
266
266
  ] });
267
267
  }
268
268
 
269
- // src/ui/CommandPalette.tsx
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
- return /* @__PURE__ */ jsxs5(
281
- Box5,
313
+ const nameWidth = Math.max(...filtered.map((c) => c.name.length));
314
+ return /* @__PURE__ */ jsxs6(
315
+ Box6,
282
316
  {
283
317
  flexDirection: "column",
284
318
  borderStyle: "round",
@@ -289,15 +323,15 @@ function CommandPalette({ filter, cursor }) {
289
323
  children: [
290
324
  filtered.map((cmd, i) => {
291
325
  const active = i === cursor;
292
- return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
293
- /* @__PURE__ */ jsxs5(Text5, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
326
+ return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
327
+ /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
294
328
  active ? "\u276F " : " ",
295
- cmd.name
329
+ cmd.name.padEnd(nameWidth)
296
330
  ] }),
297
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: cmd.description })
331
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: cmd.description })
298
332
  ] }, cmd.name);
299
333
  }),
300
- /* @__PURE__ */ jsx5(Box5, { marginTop: 0, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate tab/enter autocomplete esc dismiss" }) })
334
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 0, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191\u2193 navigate tab/enter autocomplete esc dismiss" }) })
301
335
  ]
302
336
  }
303
337
  );
@@ -306,11 +340,150 @@ function filteredCommands(filter) {
306
340
  return COMMANDS.filter((c) => c.name.startsWith(filter));
307
341
  }
308
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
+
309
482
  // src/ui/FilePicker.tsx
310
- import { Box as Box6, Text as Text6 } from "ink";
311
- import { readdirSync } from "fs";
312
- import { join as join2, relative } from "path";
313
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
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";
314
487
  var IGNORE = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".miii"]);
315
488
  var MAX_RESULTS = 10;
316
489
  var MAX_SCAN = 2e3;
@@ -323,13 +496,13 @@ function listFiles(cwd) {
323
496
  const dir = stack.pop();
324
497
  let entries;
325
498
  try {
326
- entries = readdirSync(dir, { withFileTypes: true });
499
+ entries = readdirSync2(dir, { withFileTypes: true });
327
500
  } catch {
328
501
  continue;
329
502
  }
330
503
  for (const e of entries) {
331
504
  if (IGNORE.has(e.name) || e.name.startsWith(".")) continue;
332
- const full = join2(dir, e.name);
505
+ const full = join3(dir, e.name);
333
506
  if (e.isDirectory()) stack.push(full);
334
507
  else if (e.isFile()) out.push(relative(cwd, full));
335
508
  if (out.length >= MAX_SCAN) break;
@@ -363,8 +536,8 @@ function searchFiles(cwd, query) {
363
536
  }
364
537
  function FilePicker({ matches, cursor }) {
365
538
  if (matches.length === 0) return null;
366
- return /* @__PURE__ */ jsxs6(
367
- Box6,
539
+ return /* @__PURE__ */ jsxs7(
540
+ Box7,
368
541
  {
369
542
  flexDirection: "column",
370
543
  borderStyle: "round",
@@ -375,24 +548,24 @@ function FilePicker({ matches, cursor }) {
375
548
  children: [
376
549
  matches.map((f, i) => {
377
550
  const active = i === cursor;
378
- return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
551
+ return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
379
552
  active ? "\u276F " : " ",
380
553
  f
381
554
  ] }) }, f);
382
555
  }),
383
- /* @__PURE__ */ jsx6(Box6, { marginTop: 0, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191\u2193 navigate tab insert esc dismiss" }) })
556
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 0, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191\u2193 navigate tab insert esc dismiss" }) })
384
557
  ]
385
558
  }
386
559
  );
387
560
  }
388
561
 
389
562
  // src/ui/ChatView.tsx
390
- import { Box as Box8, Text as Text8 } from "ink";
563
+ import { Box as Box9, Text as Text9 } from "ink";
391
564
 
392
565
  // src/ui/ThinkingBlock.tsx
393
566
  import { useState as useState2, useEffect as useEffect2 } from "react";
394
- import { Box as Box7, Text as Text7 } from "ink";
395
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
567
+ import { Box as Box8, Text as Text8 } from "ink";
568
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
396
569
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
397
570
  var globalThinkingVisible = false;
398
571
  var listeners = /* @__PURE__ */ new Set();
@@ -418,20 +591,20 @@ function ThinkingBlock({ content }) {
418
591
  const t = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80);
419
592
  return () => clearInterval(t);
420
593
  }, []);
421
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
422
- /* @__PURE__ */ jsxs7(Box7, { children: [
423
- /* @__PURE__ */ jsxs7(Text7, { color: "blue", children: [
594
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
595
+ /* @__PURE__ */ jsxs8(Box8, { children: [
596
+ /* @__PURE__ */ jsxs8(Text8, { color: "blue", children: [
424
597
  FRAMES[frame],
425
598
  " "
426
599
  ] }),
427
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, italic: true, children: "thinking\u2026" }),
428
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
600
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: "thinking\u2026" }),
601
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
429
602
  " \xB7 ctrl+t to ",
430
603
  visible ? "hide" : "show",
431
604
  " thoughts"
432
605
  ] })
433
606
  ] }),
434
- visible && content ? /* @__PURE__ */ jsx7(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, italic: true, children: content }) }) : null
607
+ visible && content ? /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: content }) }) : null
435
608
  ] });
436
609
  }
437
610
 
@@ -439,12 +612,14 @@ function ThinkingBlock({ content }) {
439
612
  var EMPTY_STATE_HINTS = [
440
613
  "\u2022 explain @file \u2014 reference a file with @",
441
614
  "\u2022 /models \u2014 switch model or effort",
442
- "\u2022 ctrl+t \u2014 toggle thinking \xB7 esc \u2014 cancel"
615
+ "\u2022 /new \u2014 start a new chat",
616
+ "\u2022 /sessions \u2014 view saved chats",
617
+ "\u2022 ctrl+t \u2014 toggle thinking"
443
618
  ];
444
619
  var EMPTY_STATE_TITLE = "Ask anything, or try:";
445
620
 
446
621
  // src/ui/ChatView.tsx
447
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
622
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
448
623
  function formatTokens(n) {
449
624
  if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + "k";
450
625
  return String(n);
@@ -470,27 +645,27 @@ function FileEditBlock({
470
645
  const MAX = 16;
471
646
  const shown = previewLines.slice(0, MAX);
472
647
  const extra = previewLines.length - shown.length;
473
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
474
- /* @__PURE__ */ jsxs8(Box8, { children: [
475
- /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25CF " }),
476
- /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
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: [
477
652
  label,
478
653
  " "
479
654
  ] }),
480
- /* @__PURE__ */ jsx8(Text8, { children: "(" }),
481
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: path }),
482
- /* @__PURE__ */ jsx8(Text8, { children: ")" })
655
+ /* @__PURE__ */ jsx9(Text9, { children: "(" }),
656
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: path }),
657
+ /* @__PURE__ */ jsx9(Text9, { children: ")" })
483
658
  ] }),
484
- /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
659
+ /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
485
660
  "\u23BF ",
486
661
  removed > 0 ? `Added ${added} lines, removed ${removed} lines` : `Added ${added} lines`
487
662
  ] }) }),
488
- shown.map((ln, i) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 4, children: /* @__PURE__ */ jsxs8(Text8, { color: ln.sign === "+" ? "green" : ln.sign === "-" ? "red" : void 0, dimColor: ln.sign === " ", children: [
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: [
489
664
  ln.sign,
490
665
  " ",
491
666
  ln.text
492
667
  ] }) }, i)),
493
- extra > 0 && /* @__PURE__ */ jsx8(Box8, { marginLeft: 4, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
668
+ extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
494
669
  "\u2026 ",
495
670
  extra,
496
671
  " more lines"
@@ -563,7 +738,7 @@ function ToolResultBlock({ result, toolName }) {
563
738
  const lines = content.split("\n");
564
739
  const showMulti = (toolName === "run_bash" || toolName === "grep" || toolName === "glob" || result.is_error) && lines.length > 1;
565
740
  if (!showMulti) {
566
- return /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
741
+ return /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
567
742
  "\u23BF ",
568
743
  summarizeResult(result, toolName)
569
744
  ] }) });
@@ -572,13 +747,13 @@ function ToolResultBlock({ result, toolName }) {
572
747
  const MAX_LINE_WIDTH = 200;
573
748
  const shown = lines.slice(0, MAX_LINES).map((l) => truncate(l, MAX_LINE_WIDTH));
574
749
  const extra = lines.length - shown.length;
575
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
576
- /* @__PURE__ */ jsxs8(Text8, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
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: [
577
752
  "\u23BF ",
578
753
  summarizeResult(result, toolName)
579
754
  ] }),
580
- shown.map((ln, i) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 4, children: /* @__PURE__ */ jsx8(Text8, { color: result.is_error ? "red" : void 0, dimColor: true, children: ln || " " }) }, i)),
581
- extra > 0 && /* @__PURE__ */ jsx8(Box8, { marginLeft: 4, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
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: [
582
757
  "\u2026 ",
583
758
  extra,
584
759
  " more lines"
@@ -591,7 +766,7 @@ function ToolUseLine({ use, result }) {
591
766
  const content = input.content ?? "";
592
767
  const added = countLines(content);
593
768
  const preview = content.split("\n").map((t) => ({ sign: "+", text: t }));
594
- return /* @__PURE__ */ jsx8(FileEditBlock, { label: "Write", path: input.path ?? "", added, removed: 0, previewLines: preview });
769
+ return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Write", path: input.path ?? "", added, removed: 0, previewLines: preview });
595
770
  }
596
771
  if (use.name === "edit_file" && !result?.is_error) {
597
772
  const input = use.input;
@@ -603,34 +778,34 @@ function ToolUseLine({ use, result }) {
603
778
  ...oldS.split("\n").map((t) => ({ sign: "-", text: t })),
604
779
  ...newS.split("\n").map((t) => ({ sign: "+", text: t }))
605
780
  ];
606
- return /* @__PURE__ */ jsx8(FileEditBlock, { label: "Update", path: input.path ?? "", added, removed, previewLines: preview });
781
+ return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Update", path: input.path ?? "", added, removed, previewLines: preview });
607
782
  }
608
783
  const { label, arg } = toolHeader(use);
609
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
610
- /* @__PURE__ */ jsxs8(Box8, { children: [
611
- /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u25CF " }),
612
- /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
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: [
613
788
  label,
614
789
  " "
615
790
  ] }),
616
- /* @__PURE__ */ jsx8(Text8, { children: "(" }),
617
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: arg }),
618
- /* @__PURE__ */ jsx8(Text8, { children: ")" })
791
+ /* @__PURE__ */ jsx9(Text9, { children: "(" }),
792
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: arg }),
793
+ /* @__PURE__ */ jsx9(Text9, { children: ")" })
619
794
  ] }),
620
- result && /* @__PURE__ */ jsx8(ToolResultBlock, { result, toolName: use.name })
795
+ result && /* @__PURE__ */ jsx9(ToolResultBlock, { result, toolName: use.name })
621
796
  ] });
622
797
  }
623
798
  function AssistantMessage({ msg }) {
624
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
625
- msg.content && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", children: [
626
- /* @__PURE__ */ jsx8(Text8, { color: "white", children: "\u25CF " }),
627
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx8(Text8, { children: msg.content }) })
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 }) })
628
803
  ] }),
629
804
  msg.tool_uses?.map((u) => {
630
805
  const r = msg.tool_results?.find((x) => x.tool_use_id === u.id);
631
- return /* @__PURE__ */ jsx8(ToolUseLine, { use: u, result: r }, u.id);
806
+ return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
632
807
  }),
633
- msg.tokens && /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
808
+ msg.tokens && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
634
809
  `\u21B3 Completed \xB7 ${formatTokens(msg.tokens.prompt_eval + msg.tokens.eval)} tokens`,
635
810
  msg.duration != null ? ` \xB7 ${formatDuration(msg.duration)}` : ""
636
811
  ] }) })
@@ -659,15 +834,15 @@ function PermissionPrompt({ req, cursor }) {
659
834
  { label: "No", key: "no" }
660
835
  ];
661
836
  const summary = summarizeInput(req.input);
662
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
663
- /* @__PURE__ */ jsx8(Text8, { color: "blue", bold: true, children: "Tool use" }),
664
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
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: [
665
840
  "Allow ",
666
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: label }),
841
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: label }),
667
842
  "?"
668
843
  ] }) }),
669
- summary && /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: summary }) }),
670
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsxs8(Text8, { color: i === cursor ? "blue" : void 0, children: [
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: [
671
846
  i === cursor ? "\u276F " : " ",
672
847
  i + 1,
673
848
  ". ",
@@ -688,33 +863,33 @@ function ChatView({
688
863
  activeToolResults
689
864
  }) {
690
865
  const empty = messages.length === 0 && !streaming && !thinking && !pendingPermission && !error;
691
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
692
- empty && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
693
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: EMPTY_STATE_TITLE }),
694
- EMPTY_STATE_HINTS.map((h, i) => /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
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: [
695
870
  " ",
696
871
  h
697
872
  ] }, i))
698
873
  ] }),
699
874
  messages.map(
700
- (msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", marginBottom: 1, children: [
701
- /* @__PURE__ */ jsx8(Text8, { color: "blue", children: "\u25CF " }),
702
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx8(Text8, { children: msg.content }) })
703
- ] }, i) : /* @__PURE__ */ jsx8(AssistantMessage, { msg }, i)
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)
704
879
  ),
705
- thinking && /* @__PURE__ */ jsx8(ThinkingBlock, { content: thinkingContent }),
706
- streaming && streamingContent && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", marginBottom: 1, children: [
707
- /* @__PURE__ */ jsx8(Text8, { color: "white", children: "\u25CF " }),
708
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx8(Text8, { children: streamingContent }) })
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 }) })
709
884
  ] }),
710
885
  activeToolUses?.map((u) => {
711
886
  const r = activeToolResults?.find((x) => x.tool_use_id === u.id);
712
- return /* @__PURE__ */ jsx8(ToolUseLine, { use: u, result: r }, u.id);
887
+ return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
713
888
  }),
714
- pendingPermission && /* @__PURE__ */ jsx8(PermissionPrompt, { req: pendingPermission, cursor: permissionCursor }),
715
- error && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", marginBottom: 1, children: [
716
- /* @__PURE__ */ jsx8(Text8, { color: "red", children: "\u25CF " }),
717
- /* @__PURE__ */ jsx8(Text8, { color: "red", children: error })
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 })
718
893
  ] })
719
894
  ] });
720
895
  }
@@ -723,7 +898,7 @@ function ChatView({
723
898
  import { useState as useState3, useRef } from "react";
724
899
 
725
900
  // src/tools/edit_file.ts
726
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
901
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
727
902
  var edit_file = {
728
903
  name: "edit_file",
729
904
  description: "Replace an exact string in a file. old_str must be unique.",
@@ -737,7 +912,7 @@ var edit_file = {
737
912
  required: ["path", "old_str", "new_str"]
738
913
  },
739
914
  handler: ({ path, old_str, new_str }) => {
740
- const src = readFileSync2(path, "utf-8");
915
+ const src = readFileSync3(path, "utf-8");
741
916
  const first = src.indexOf(old_str);
742
917
  if (first === -1) {
743
918
  return { content: `old_str not found in ${path}`, is_error: true };
@@ -745,13 +920,13 @@ var edit_file = {
745
920
  if (src.indexOf(old_str, first + 1) !== -1) {
746
921
  return { content: `old_str not unique in ${path}`, is_error: true };
747
922
  }
748
- writeFileSync2(path, src.slice(0, first) + new_str + src.slice(first + old_str.length), "utf-8");
923
+ writeFileSync3(path, src.slice(0, first) + new_str + src.slice(first + old_str.length), "utf-8");
749
924
  return { content: `Edited ${path}` };
750
925
  }
751
926
  };
752
927
 
753
928
  // src/tools/read_file.ts
754
- import { readFileSync as readFileSync3 } from "fs";
929
+ import { readFileSync as readFileSync4 } from "fs";
755
930
  var read_file = {
756
931
  name: "read_file",
757
932
  description: "Read entire file contents as UTF-8 text.",
@@ -765,7 +940,7 @@ var read_file = {
765
940
  handler: ({ path }) => {
766
941
  try {
767
942
  const MAX = 2e5;
768
- const raw = readFileSync3(path, "utf-8");
943
+ const raw = readFileSync4(path, "utf-8");
769
944
  const truncated = raw.length > MAX;
770
945
  const body = truncated ? raw.slice(0, MAX) + `
771
946
  [truncated: ${raw.length - MAX} more chars]` : raw;
@@ -777,7 +952,7 @@ var read_file = {
777
952
  };
778
953
 
779
954
  // src/tools/write_file.ts
780
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
955
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
781
956
  import { dirname } from "path";
782
957
  var write_file = {
783
958
  name: "write_file",
@@ -792,8 +967,8 @@ var write_file = {
792
967
  },
793
968
  handler: ({ path, content }) => {
794
969
  try {
795
- mkdirSync2(dirname(path), { recursive: true });
796
- writeFileSync3(path, content, "utf-8");
970
+ mkdirSync3(dirname(path), { recursive: true });
971
+ writeFileSync4(path, content, "utf-8");
797
972
  return { content: `Wrote ${path} (${content.length} bytes)` };
798
973
  } catch (err) {
799
974
  return { content: err instanceof Error ? err.message : String(err), is_error: true };
@@ -1582,19 +1757,28 @@ function useKeyboard(opts) {
1582
1757
  cfg,
1583
1758
  setCfg,
1584
1759
  setActiveCtx,
1585
- pendingPermissionRef,
1586
- permissionCursor,
1587
- setPermissionCursor,
1588
- resolvePermission,
1589
- busyRef,
1590
- abortRef,
1760
+ agent,
1591
1761
  input,
1592
1762
  setInput,
1593
1763
  paletteCursor,
1594
1764
  setPaletteCursor,
1595
1765
  filePickerCursor,
1596
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,
1597
1780
  sendMessage,
1781
+ agentHistory,
1598
1782
  setMessages,
1599
1783
  setAgentHistory,
1600
1784
  setStreamingContent,
@@ -1602,7 +1786,17 @@ function useKeyboard(opts) {
1602
1786
  setActiveToolUses,
1603
1787
  setActiveToolResults,
1604
1788
  setError
1605
- } = opts;
1789
+ } = agent;
1790
+ function clearSession() {
1791
+ setMessages(() => []);
1792
+ setAgentHistory([]);
1793
+ setStreamingContent("");
1794
+ setThinkingContent("");
1795
+ setActiveToolUses([]);
1796
+ setActiveToolResults([]);
1797
+ setError(null);
1798
+ setNotice(null);
1799
+ }
1606
1800
  const effort = cfg.effort ?? "medium";
1607
1801
  useInput((char, key) => {
1608
1802
  if (key.ctrl && char === "c") {
@@ -1649,6 +1843,44 @@ function useKeyboard(opts) {
1649
1843
  }
1650
1844
  return;
1651
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
+ }
1652
1884
  if (state === "ready" && pendingPermissionRef.current) {
1653
1885
  if (key.upArrow) {
1654
1886
  setPermissionCursor((i) => Math.max(0, i - 1));
@@ -1713,16 +1945,30 @@ function useKeyboard(opts) {
1713
1945
  setCursor(() => Math.max(0, models.findIndex((m) => m === cfg.model)));
1714
1946
  setState("models");
1715
1947
  } else if (trimmed === "/clear") {
1716
- setMessages(() => []);
1717
- setAgentHistory([]);
1718
- setStreamingContent("");
1719
- setThinkingContent("");
1720
- setActiveToolUses([]);
1721
- setActiveToolResults([]);
1722
- setError(null);
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");
1723
1957
  } else if (trimmed === "/exit") {
1724
1958
  exit();
1725
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
+ }
1726
1972
  sendMessage(trimmed);
1727
1973
  }
1728
1974
  setInput(() => "");
@@ -1782,7 +2028,7 @@ async function checkForUpdate() {
1782
2028
  }
1783
2029
 
1784
2030
  // src/ui/App.tsx
1785
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2031
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1786
2032
  function App() {
1787
2033
  const { exit } = useApp();
1788
2034
  const cwd = process.cwd().replace(homedir2(), "~").split(sep).join("/");
@@ -1793,6 +2039,9 @@ function App() {
1793
2039
  const [state, setState] = useState4("loading");
1794
2040
  const [cursor, setCursor] = useState4(0);
1795
2041
  const [updateAvailable, setUpdateAvailable] = useState4(null);
2042
+ const [sessionId, setSessionId] = useState4(() => newSessionId());
2043
+ const [sessions, setSessions] = useState4([]);
2044
+ const [notice, setNotice] = useState4(null);
1796
2045
  const [input, setInput] = useState4("");
1797
2046
  const [paletteCursor, setPaletteCursor] = useState4(0);
1798
2047
  const [filePickerCursor, setFilePickerCursor] = useState4(0);
@@ -1802,6 +2051,9 @@ function App() {
1802
2051
  if (v) setUpdateAvailable(v);
1803
2052
  });
1804
2053
  }, []);
2054
+ useEffect3(() => {
2055
+ if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
2056
+ }, [agent.agentHistory, sessionId]);
1805
2057
  useEffect3(() => {
1806
2058
  listModels().then((m) => {
1807
2059
  setModels(m);
@@ -1831,26 +2083,18 @@ function App() {
1831
2083
  cfg,
1832
2084
  setCfg,
1833
2085
  setActiveCtx,
1834
- pendingPermissionRef: agent.pendingPermissionRef,
1835
- permissionCursor: agent.permissionCursor,
1836
- setPermissionCursor: agent.setPermissionCursor,
1837
- resolvePermission: agent.resolvePermission,
1838
- busyRef: agent.busyRef,
1839
- abortRef: agent.abortRef,
2086
+ agent,
1840
2087
  input,
1841
2088
  setInput,
1842
2089
  paletteCursor,
1843
2090
  setPaletteCursor,
1844
2091
  filePickerCursor,
1845
2092
  setFilePickerCursor,
1846
- sendMessage: agent.sendMessage,
1847
- setMessages: agent.setMessages,
1848
- setAgentHistory: agent.setAgentHistory,
1849
- setStreamingContent: agent.setStreamingContent,
1850
- setThinkingContent: agent.setThinkingContent,
1851
- setActiveToolUses: agent.setActiveToolUses,
1852
- setActiveToolResults: agent.setActiveToolResults,
1853
- setError: agent.setError
2093
+ sessionId,
2094
+ setSessionId,
2095
+ sessions,
2096
+ setSessions,
2097
+ setNotice
1854
2098
  });
1855
2099
  const effort = cfg.effort ?? "medium";
1856
2100
  const contextWarning = (() => {
@@ -1860,11 +2104,11 @@ function App() {
1860
2104
  if (used < activeCtx * 0.7) return null;
1861
2105
  return Math.round(used / activeCtx * 100);
1862
2106
  })();
1863
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, children: [
1864
- /* @__PURE__ */ jsx9(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
1865
- updateAvailable && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: npm i -g miii-agent` }) }),
1866
- state === "loading" && !agent.error && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "connecting to ollama\u2026" }) }),
1867
- agent.error && state !== "ready" && /* @__PURE__ */ jsx9(
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(
1868
2112
  ChatView,
1869
2113
  {
1870
2114
  messages: [],
@@ -1874,12 +2118,12 @@ function App() {
1874
2118
  error: agent.error
1875
2119
  }
1876
2120
  ),
1877
- state === "select-model" && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
1878
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "no model configured \u2014 select one" }),
1879
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(ModelList, { models, cursor }) }),
1880
- models.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191\u2193 navigate enter select ctrl+c quit" }) })
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" }) })
1881
2125
  ] }),
1882
- state === "models" && /* @__PURE__ */ jsx9(
2126
+ state === "models" && /* @__PURE__ */ jsx10(
1883
2127
  ModelsView,
1884
2128
  {
1885
2129
  models,
@@ -1889,8 +2133,10 @@ function App() {
1889
2133
  effort
1890
2134
  }
1891
2135
  ),
1892
- state === "ready" && /* @__PURE__ */ jsxs9(Fragment2, { children: [
1893
- /* @__PURE__ */ jsx9(
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(
1894
2140
  ChatView,
1895
2141
  {
1896
2142
  messages: agent.messages,
@@ -1905,15 +2151,15 @@ function App() {
1905
2151
  activeToolResults: agent.activeToolResults
1906
2152
  }
1907
2153
  ),
1908
- input.startsWith("/") && /* @__PURE__ */ jsx9(CommandPalette, { filter: input, cursor: paletteCursor }),
1909
- contextWarning !== null && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: `\u26A0 context ${contextWarning}% full \u2014 run /clear and start fresh` }) }),
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` }) }),
1910
2156
  !input.startsWith("/") && (() => {
1911
2157
  const m = parseMention(input);
1912
2158
  if (!m) return null;
1913
- return /* @__PURE__ */ jsx9(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
2159
+ return /* @__PURE__ */ jsx10(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
1914
2160
  })(),
1915
- /* @__PURE__ */ jsx9(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
1916
- !agent.busy && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "type / to see commands" }) })
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" }) })
1917
2163
  ] })
1918
2164
  ] });
1919
2165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miii-agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Terminal AI coding agent powered by Ollama",
5
5
  "type": "module",
6
6
  "bin": {