miii-agent 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +400 -139
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6,16 +6,27 @@ 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
 
13
13
  // src/ollama/client.ts
14
14
  import { Ollama } from "ollama";
15
+ import { execFileSync } from "child_process";
15
16
  var ollama = new Ollama({
16
17
  host: process.env.OLLAMA_HOST ?? "http://localhost:11434"
17
18
  });
19
+ var OLLAMA_NOT_INSTALLED = "Ollama is not installed. Install it with: npm i -g ollama\nOr download from https://ollama.com/download";
18
20
  var OLLAMA_NOT_RUNNING = "Ollama is not running. Start it with: ollama serve";
21
+ function ollamaInstalled() {
22
+ try {
23
+ const cmd = process.platform === "win32" ? "where" : "which";
24
+ execFileSync(cmd, ["ollama"], { stdio: "ignore" });
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
19
30
  var HARMONY_RE = /<\|?\/?(?:channel|message|start|end|return|constrain|assistant|user|system|developer|tool|tool_call|tool_response|final|analysis|commentary)\|?>/gi;
20
31
  var CHANNEL_LABEL_RE = /^(?:analysis|commentary|final)\s*(?=\w)/i;
21
32
  function stripHarmony(s) {
@@ -266,19 +277,53 @@ function ModelsView({ models, cursor, model, ollamaHost, effort }) {
266
277
  ] });
267
278
  }
268
279
 
269
- // src/ui/CommandPalette.tsx
280
+ // src/ui/SessionsView.tsx
270
281
  import { Box as Box5, Text as Text5 } from "ink";
271
282
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
283
+ function relativeTime(iso) {
284
+ const diff = Date.now() - new Date(iso).getTime();
285
+ const min = Math.floor(diff / 6e4);
286
+ if (min < 1) return "just now";
287
+ if (min < 60) return `${min}m ago`;
288
+ const hr = Math.floor(min / 60);
289
+ if (hr < 24) return `${hr}h ago`;
290
+ const d = Math.floor(hr / 24);
291
+ return `${d}d ago`;
292
+ }
293
+ function SessionsView({ sessions, cursor }) {
294
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
295
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "resume session" }),
296
+ /* @__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) => {
297
+ const active = i === cursor;
298
+ const label = s.title;
299
+ return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
300
+ /* @__PURE__ */ jsxs5(Text5, { color: active ? "blue" : void 0, dimColor: !active, children: [
301
+ active ? "\u276F " : " ",
302
+ label
303
+ ] }),
304
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `\xB7 ${s.messageCount} msgs \xB7 ${relativeTime(s.updatedAt)}` })
305
+ ] }, s.id);
306
+ }) }),
307
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate enter resume d delete esc cancel" }) })
308
+ ] });
309
+ }
310
+
311
+ // src/ui/CommandPalette.tsx
312
+ import { Box as Box6, Text as Text6 } from "ink";
313
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
272
314
  var COMMANDS = [
273
315
  { name: "/models", description: "switch model or adjust effort" },
316
+ { name: "/new", description: "save current session and start fresh" },
317
+ { name: "/sessions", description: "list sessions and resume one" },
274
318
  { name: "/clear", description: "clear chat and reset context" },
275
319
  { name: "/exit", description: "quit miii" }
276
320
  ];
277
321
  function CommandPalette({ filter, cursor }) {
278
322
  const filtered = COMMANDS.filter((c) => c.name.startsWith(filter));
279
323
  if (filtered.length === 0) return null;
280
- return /* @__PURE__ */ jsxs5(
281
- Box5,
324
+ const nameWidth = Math.max(...filtered.map((c) => c.name.length));
325
+ return /* @__PURE__ */ jsxs6(
326
+ Box6,
282
327
  {
283
328
  flexDirection: "column",
284
329
  borderStyle: "round",
@@ -289,15 +334,15 @@ function CommandPalette({ filter, cursor }) {
289
334
  children: [
290
335
  filtered.map((cmd, i) => {
291
336
  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: [
337
+ return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
338
+ /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
294
339
  active ? "\u276F " : " ",
295
- cmd.name
340
+ cmd.name.padEnd(nameWidth)
296
341
  ] }),
297
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: cmd.description })
342
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: cmd.description })
298
343
  ] }, cmd.name);
299
344
  }),
300
- /* @__PURE__ */ jsx5(Box5, { marginTop: 0, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate tab/enter autocomplete esc dismiss" }) })
345
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 0, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191\u2193 navigate tab/enter autocomplete esc dismiss" }) })
301
346
  ]
302
347
  }
303
348
  );
@@ -306,11 +351,150 @@ function filteredCommands(filter) {
306
351
  return COMMANDS.filter((c) => c.name.startsWith(filter));
307
352
  }
308
353
 
354
+ // src/session/store.ts
355
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
356
+ import { join as join2 } from "path";
357
+ import { randomUUID } from "crypto";
358
+ var SESSION_DIR = join2(process.cwd(), ".miii", "session");
359
+ function newSessionId() {
360
+ return randomUUID();
361
+ }
362
+ function sessionPath(id) {
363
+ return join2(SESSION_DIR, `${id}.jsonl`);
364
+ }
365
+ function messageText(m) {
366
+ if (typeof m.content === "string") return m.content;
367
+ return m.content.map((b) => {
368
+ if (b.type === "text") return b.text;
369
+ if (b.type === "tool_use") return `[tool ${b.name}]`;
370
+ if (b.type === "tool_result") return "[result]";
371
+ return "";
372
+ }).join(" ");
373
+ }
374
+ function firstUserText(messages) {
375
+ const first = messages.find((m) => m.role === "user");
376
+ if (!first) return "untitled";
377
+ return messageText(first).trim().slice(0, 80) || "untitled";
378
+ }
379
+ function readMeta(id) {
380
+ try {
381
+ const raw = readFileSync2(sessionPath(id), "utf-8");
382
+ const firstLine = raw.slice(0, raw.indexOf("\n") === -1 ? raw.length : raw.indexOf("\n"));
383
+ const parsed = JSON.parse(firstLine);
384
+ if (parsed.type !== "meta") return null;
385
+ const { type: _t, ...meta } = parsed;
386
+ return meta;
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
391
+ function persistSession(id, messages, title) {
392
+ if (!messages.length) return;
393
+ mkdirSync2(SESSION_DIR, { recursive: true });
394
+ const existing = readMeta(id);
395
+ const now = (/* @__PURE__ */ new Date()).toISOString();
396
+ const meta = {
397
+ id,
398
+ createdAt: existing?.createdAt ?? now,
399
+ updatedAt: now,
400
+ title: title ?? existing?.title ?? firstUserText(messages),
401
+ messageCount: messages.length
402
+ };
403
+ const lines = [JSON.stringify({ type: "meta", ...meta })];
404
+ for (const message of messages) {
405
+ lines.push(JSON.stringify({ type: "message", message }));
406
+ }
407
+ writeFileSync2(sessionPath(id), lines.join("\n") + "\n", "utf-8");
408
+ }
409
+ function listSessions() {
410
+ if (!existsSync2(SESSION_DIR)) return [];
411
+ const metas = [];
412
+ for (const file of readdirSync(SESSION_DIR)) {
413
+ if (!file.endsWith(".jsonl")) continue;
414
+ const meta = readMeta(file.replace(/\.jsonl$/, ""));
415
+ if (meta) metas.push(meta);
416
+ }
417
+ return metas.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
418
+ }
419
+ function deleteSession(id) {
420
+ try {
421
+ rmSync(sessionPath(id), { force: true });
422
+ } catch {
423
+ }
424
+ }
425
+ function loadSession(id) {
426
+ try {
427
+ const raw = readFileSync2(sessionPath(id), "utf-8");
428
+ const messages = [];
429
+ for (const line of raw.split("\n")) {
430
+ if (!line.trim()) continue;
431
+ const parsed = JSON.parse(line);
432
+ if (parsed.type === "message") messages.push(parsed.message);
433
+ }
434
+ return messages;
435
+ } catch {
436
+ return [];
437
+ }
438
+ }
439
+ function toDisplayMessages(history) {
440
+ const out = [];
441
+ for (const m of history) {
442
+ if (m.role === "system") continue;
443
+ const blocks = Array.isArray(m.content) ? m.content : [{ type: "text", text: m.content }];
444
+ if (m.role === "user") {
445
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
446
+ const results = blocks.filter((b) => b.type === "tool_result");
447
+ if (results.length && out.length) {
448
+ const last = out[out.length - 1];
449
+ last.tool_results = [
450
+ ...last.tool_results ?? [],
451
+ ...results.map((r) => ({
452
+ tool_use_id: r.tool_use_id,
453
+ content: r.content,
454
+ is_error: r.is_error
455
+ }))
456
+ ];
457
+ }
458
+ if (text.trim()) out.push({ role: "user", content: text });
459
+ } else {
460
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
461
+ const uses = blocks.filter((b) => b.type === "tool_use").map((b) => ({ id: b.id, name: b.name, input: b.input }));
462
+ out.push({
463
+ role: "assistant",
464
+ content: text,
465
+ tool_uses: uses.length ? uses : void 0
466
+ });
467
+ }
468
+ }
469
+ return out;
470
+ }
471
+ async function summarizeMessage(model, text) {
472
+ const fallback = text.trim().slice(0, 80) || "untitled";
473
+ const prompt = `Summarize this user request as a short title, 3-6 words, no punctuation. Reply with the title only.
474
+
475
+ Request:
476
+ ${text.slice(0, 2e3)}`;
477
+ try {
478
+ let out = "";
479
+ for await (const chunk of chat(
480
+ model,
481
+ [{ role: "user", content: prompt }],
482
+ void 0,
483
+ { temperature: 0.2, num_predict: 32 }
484
+ )) {
485
+ if (chunk.content) out += chunk.content;
486
+ }
487
+ return out.trim().split("\n").filter(Boolean)[0]?.trim() || fallback;
488
+ } catch {
489
+ return fallback;
490
+ }
491
+ }
492
+
309
493
  // 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";
494
+ import { Box as Box7, Text as Text7 } from "ink";
495
+ import { readdirSync as readdirSync2 } from "fs";
496
+ import { join as join3, relative } from "path";
497
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
314
498
  var IGNORE = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".miii"]);
315
499
  var MAX_RESULTS = 10;
316
500
  var MAX_SCAN = 2e3;
@@ -323,13 +507,13 @@ function listFiles(cwd) {
323
507
  const dir = stack.pop();
324
508
  let entries;
325
509
  try {
326
- entries = readdirSync(dir, { withFileTypes: true });
510
+ entries = readdirSync2(dir, { withFileTypes: true });
327
511
  } catch {
328
512
  continue;
329
513
  }
330
514
  for (const e of entries) {
331
515
  if (IGNORE.has(e.name) || e.name.startsWith(".")) continue;
332
- const full = join2(dir, e.name);
516
+ const full = join3(dir, e.name);
333
517
  if (e.isDirectory()) stack.push(full);
334
518
  else if (e.isFile()) out.push(relative(cwd, full));
335
519
  if (out.length >= MAX_SCAN) break;
@@ -363,8 +547,8 @@ function searchFiles(cwd, query) {
363
547
  }
364
548
  function FilePicker({ matches, cursor }) {
365
549
  if (matches.length === 0) return null;
366
- return /* @__PURE__ */ jsxs6(
367
- Box6,
550
+ return /* @__PURE__ */ jsxs7(
551
+ Box7,
368
552
  {
369
553
  flexDirection: "column",
370
554
  borderStyle: "round",
@@ -375,24 +559,24 @@ function FilePicker({ matches, cursor }) {
375
559
  children: [
376
560
  matches.map((f, i) => {
377
561
  const active = i === cursor;
378
- return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
562
+ return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
379
563
  active ? "\u276F " : " ",
380
564
  f
381
565
  ] }) }, f);
382
566
  }),
383
- /* @__PURE__ */ jsx6(Box6, { marginTop: 0, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191\u2193 navigate tab insert esc dismiss" }) })
567
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 0, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191\u2193 navigate tab insert esc dismiss" }) })
384
568
  ]
385
569
  }
386
570
  );
387
571
  }
388
572
 
389
573
  // src/ui/ChatView.tsx
390
- import { Box as Box8, Text as Text8 } from "ink";
574
+ import { Box as Box9, Text as Text9 } from "ink";
391
575
 
392
576
  // src/ui/ThinkingBlock.tsx
393
577
  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";
578
+ import { Box as Box8, Text as Text8 } from "ink";
579
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
396
580
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
397
581
  var globalThinkingVisible = false;
398
582
  var listeners = /* @__PURE__ */ new Set();
@@ -418,20 +602,20 @@ function ThinkingBlock({ content }) {
418
602
  const t = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80);
419
603
  return () => clearInterval(t);
420
604
  }, []);
421
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
422
- /* @__PURE__ */ jsxs7(Box7, { children: [
423
- /* @__PURE__ */ jsxs7(Text7, { color: "blue", children: [
605
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
606
+ /* @__PURE__ */ jsxs8(Box8, { children: [
607
+ /* @__PURE__ */ jsxs8(Text8, { color: "blue", children: [
424
608
  FRAMES[frame],
425
609
  " "
426
610
  ] }),
427
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, italic: true, children: "thinking\u2026" }),
428
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
611
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: "thinking\u2026" }),
612
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
429
613
  " \xB7 ctrl+t to ",
430
614
  visible ? "hide" : "show",
431
615
  " thoughts"
432
616
  ] })
433
617
  ] }),
434
- visible && content ? /* @__PURE__ */ jsx7(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, italic: true, children: content }) }) : null
618
+ visible && content ? /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: content }) }) : null
435
619
  ] });
436
620
  }
437
621
 
@@ -439,12 +623,14 @@ function ThinkingBlock({ content }) {
439
623
  var EMPTY_STATE_HINTS = [
440
624
  "\u2022 explain @file \u2014 reference a file with @",
441
625
  "\u2022 /models \u2014 switch model or effort",
442
- "\u2022 ctrl+t \u2014 toggle thinking \xB7 esc \u2014 cancel"
626
+ "\u2022 /new \u2014 start a new chat",
627
+ "\u2022 /sessions \u2014 view saved chats",
628
+ "\u2022 ctrl+t \u2014 toggle thinking"
443
629
  ];
444
630
  var EMPTY_STATE_TITLE = "Ask anything, or try:";
445
631
 
446
632
  // src/ui/ChatView.tsx
447
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
633
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
448
634
  function formatTokens(n) {
449
635
  if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + "k";
450
636
  return String(n);
@@ -470,27 +656,27 @@ function FileEditBlock({
470
656
  const MAX = 16;
471
657
  const shown = previewLines.slice(0, MAX);
472
658
  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: [
659
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
660
+ /* @__PURE__ */ jsxs9(Box9, { children: [
661
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25CF " }),
662
+ /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
477
663
  label,
478
664
  " "
479
665
  ] }),
480
- /* @__PURE__ */ jsx8(Text8, { children: "(" }),
481
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: path }),
482
- /* @__PURE__ */ jsx8(Text8, { children: ")" })
666
+ /* @__PURE__ */ jsx9(Text9, { children: "(" }),
667
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: path }),
668
+ /* @__PURE__ */ jsx9(Text9, { children: ")" })
483
669
  ] }),
484
- /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
670
+ /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
485
671
  "\u23BF ",
486
672
  removed > 0 ? `Added ${added} lines, removed ${removed} lines` : `Added ${added} lines`
487
673
  ] }) }),
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: [
674
+ 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
675
  ln.sign,
490
676
  " ",
491
677
  ln.text
492
678
  ] }) }, i)),
493
- extra > 0 && /* @__PURE__ */ jsx8(Box8, { marginLeft: 4, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
679
+ extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
494
680
  "\u2026 ",
495
681
  extra,
496
682
  " more lines"
@@ -563,7 +749,7 @@ function ToolResultBlock({ result, toolName }) {
563
749
  const lines = content.split("\n");
564
750
  const showMulti = (toolName === "run_bash" || toolName === "grep" || toolName === "glob" || result.is_error) && lines.length > 1;
565
751
  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: [
752
+ return /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
567
753
  "\u23BF ",
568
754
  summarizeResult(result, toolName)
569
755
  ] }) });
@@ -572,13 +758,13 @@ function ToolResultBlock({ result, toolName }) {
572
758
  const MAX_LINE_WIDTH = 200;
573
759
  const shown = lines.slice(0, MAX_LINES).map((l) => truncate(l, MAX_LINE_WIDTH));
574
760
  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: [
761
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
762
+ /* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
577
763
  "\u23BF ",
578
764
  summarizeResult(result, toolName)
579
765
  ] }),
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: [
766
+ 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)),
767
+ extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
582
768
  "\u2026 ",
583
769
  extra,
584
770
  " more lines"
@@ -591,7 +777,7 @@ function ToolUseLine({ use, result }) {
591
777
  const content = input.content ?? "";
592
778
  const added = countLines(content);
593
779
  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 });
780
+ return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Write", path: input.path ?? "", added, removed: 0, previewLines: preview });
595
781
  }
596
782
  if (use.name === "edit_file" && !result?.is_error) {
597
783
  const input = use.input;
@@ -603,34 +789,34 @@ function ToolUseLine({ use, result }) {
603
789
  ...oldS.split("\n").map((t) => ({ sign: "-", text: t })),
604
790
  ...newS.split("\n").map((t) => ({ sign: "+", text: t }))
605
791
  ];
606
- return /* @__PURE__ */ jsx8(FileEditBlock, { label: "Update", path: input.path ?? "", added, removed, previewLines: preview });
792
+ return /* @__PURE__ */ jsx9(FileEditBlock, { label: "Update", path: input.path ?? "", added, removed, previewLines: preview });
607
793
  }
608
794
  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: [
795
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
796
+ /* @__PURE__ */ jsxs9(Box9, { children: [
797
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25CF " }),
798
+ /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
613
799
  label,
614
800
  " "
615
801
  ] }),
616
- /* @__PURE__ */ jsx8(Text8, { children: "(" }),
617
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: arg }),
618
- /* @__PURE__ */ jsx8(Text8, { children: ")" })
802
+ /* @__PURE__ */ jsx9(Text9, { children: "(" }),
803
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: arg }),
804
+ /* @__PURE__ */ jsx9(Text9, { children: ")" })
619
805
  ] }),
620
- result && /* @__PURE__ */ jsx8(ToolResultBlock, { result, toolName: use.name })
806
+ result && /* @__PURE__ */ jsx9(ToolResultBlock, { result, toolName: use.name })
621
807
  ] });
622
808
  }
623
809
  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 }) })
810
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
811
+ msg.content && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
812
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF " }),
813
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
628
814
  ] }),
629
815
  msg.tool_uses?.map((u) => {
630
816
  const r = msg.tool_results?.find((x) => x.tool_use_id === u.id);
631
- return /* @__PURE__ */ jsx8(ToolUseLine, { use: u, result: r }, u.id);
817
+ return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
632
818
  }),
633
- msg.tokens && /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
819
+ msg.tokens && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
634
820
  `\u21B3 Completed \xB7 ${formatTokens(msg.tokens.prompt_eval + msg.tokens.eval)} tokens`,
635
821
  msg.duration != null ? ` \xB7 ${formatDuration(msg.duration)}` : ""
636
822
  ] }) })
@@ -659,15 +845,15 @@ function PermissionPrompt({ req, cursor }) {
659
845
  { label: "No", key: "no" }
660
846
  ];
661
847
  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: [
848
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
849
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", bold: true, children: "Tool use" }),
850
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
665
851
  "Allow ",
666
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: label }),
852
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: label }),
667
853
  "?"
668
854
  ] }) }),
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: [
855
+ summary && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: summary }) }),
856
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsxs9(Text9, { color: i === cursor ? "blue" : void 0, children: [
671
857
  i === cursor ? "\u276F " : " ",
672
858
  i + 1,
673
859
  ". ",
@@ -688,33 +874,33 @@ function ChatView({
688
874
  activeToolResults
689
875
  }) {
690
876
  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: [
877
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
878
+ empty && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
879
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: EMPTY_STATE_TITLE }),
880
+ EMPTY_STATE_HINTS.map((h, i) => /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
695
881
  " ",
696
882
  h
697
883
  ] }, i))
698
884
  ] }),
699
885
  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)
886
+ (msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
887
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u25CF " }),
888
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
889
+ ] }, i) : /* @__PURE__ */ jsx9(AssistantMessage, { msg }, i)
704
890
  ),
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 }) })
891
+ thinking && /* @__PURE__ */ jsx9(ThinkingBlock, { content: thinkingContent }),
892
+ streaming && streamingContent && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
893
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF " }),
894
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: streamingContent }) })
709
895
  ] }),
710
896
  activeToolUses?.map((u) => {
711
897
  const r = activeToolResults?.find((x) => x.tool_use_id === u.id);
712
- return /* @__PURE__ */ jsx8(ToolUseLine, { use: u, result: r }, u.id);
898
+ return /* @__PURE__ */ jsx9(ToolUseLine, { use: u, result: r }, u.id);
713
899
  }),
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 })
900
+ pendingPermission && /* @__PURE__ */ jsx9(PermissionPrompt, { req: pendingPermission, cursor: permissionCursor }),
901
+ error && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
902
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: "\u25CF " }),
903
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: error })
718
904
  ] })
719
905
  ] });
720
906
  }
@@ -723,7 +909,7 @@ function ChatView({
723
909
  import { useState as useState3, useRef } from "react";
724
910
 
725
911
  // src/tools/edit_file.ts
726
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
912
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
727
913
  var edit_file = {
728
914
  name: "edit_file",
729
915
  description: "Replace an exact string in a file. old_str must be unique.",
@@ -737,7 +923,7 @@ var edit_file = {
737
923
  required: ["path", "old_str", "new_str"]
738
924
  },
739
925
  handler: ({ path, old_str, new_str }) => {
740
- const src = readFileSync2(path, "utf-8");
926
+ const src = readFileSync3(path, "utf-8");
741
927
  const first = src.indexOf(old_str);
742
928
  if (first === -1) {
743
929
  return { content: `old_str not found in ${path}`, is_error: true };
@@ -745,13 +931,13 @@ var edit_file = {
745
931
  if (src.indexOf(old_str, first + 1) !== -1) {
746
932
  return { content: `old_str not unique in ${path}`, is_error: true };
747
933
  }
748
- writeFileSync2(path, src.slice(0, first) + new_str + src.slice(first + old_str.length), "utf-8");
934
+ writeFileSync3(path, src.slice(0, first) + new_str + src.slice(first + old_str.length), "utf-8");
749
935
  return { content: `Edited ${path}` };
750
936
  }
751
937
  };
752
938
 
753
939
  // src/tools/read_file.ts
754
- import { readFileSync as readFileSync3 } from "fs";
940
+ import { readFileSync as readFileSync4 } from "fs";
755
941
  var read_file = {
756
942
  name: "read_file",
757
943
  description: "Read entire file contents as UTF-8 text.",
@@ -765,7 +951,7 @@ var read_file = {
765
951
  handler: ({ path }) => {
766
952
  try {
767
953
  const MAX = 2e5;
768
- const raw = readFileSync3(path, "utf-8");
954
+ const raw = readFileSync4(path, "utf-8");
769
955
  const truncated = raw.length > MAX;
770
956
  const body = truncated ? raw.slice(0, MAX) + `
771
957
  [truncated: ${raw.length - MAX} more chars]` : raw;
@@ -777,7 +963,7 @@ var read_file = {
777
963
  };
778
964
 
779
965
  // src/tools/write_file.ts
780
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
966
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
781
967
  import { dirname } from "path";
782
968
  var write_file = {
783
969
  name: "write_file",
@@ -792,8 +978,8 @@ var write_file = {
792
978
  },
793
979
  handler: ({ path, content }) => {
794
980
  try {
795
- mkdirSync2(dirname(path), { recursive: true });
796
- writeFileSync3(path, content, "utf-8");
981
+ mkdirSync3(dirname(path), { recursive: true });
982
+ writeFileSync4(path, content, "utf-8");
797
983
  return { content: `Wrote ${path} (${content.length} bytes)` };
798
984
  } catch (err) {
799
985
  return { content: err instanceof Error ? err.message : String(err), is_error: true };
@@ -1582,19 +1768,28 @@ function useKeyboard(opts) {
1582
1768
  cfg,
1583
1769
  setCfg,
1584
1770
  setActiveCtx,
1585
- pendingPermissionRef,
1586
- permissionCursor,
1587
- setPermissionCursor,
1588
- resolvePermission,
1589
- busyRef,
1590
- abortRef,
1771
+ agent,
1591
1772
  input,
1592
1773
  setInput,
1593
1774
  paletteCursor,
1594
1775
  setPaletteCursor,
1595
1776
  filePickerCursor,
1596
1777
  setFilePickerCursor,
1778
+ sessionId,
1779
+ setSessionId,
1780
+ sessions,
1781
+ setSessions,
1782
+ setNotice
1783
+ } = opts;
1784
+ const {
1785
+ pendingPermissionRef,
1786
+ permissionCursor,
1787
+ setPermissionCursor,
1788
+ resolvePermission,
1789
+ busyRef,
1790
+ abortRef,
1597
1791
  sendMessage,
1792
+ agentHistory,
1598
1793
  setMessages,
1599
1794
  setAgentHistory,
1600
1795
  setStreamingContent,
@@ -1602,7 +1797,17 @@ function useKeyboard(opts) {
1602
1797
  setActiveToolUses,
1603
1798
  setActiveToolResults,
1604
1799
  setError
1605
- } = opts;
1800
+ } = agent;
1801
+ function clearSession() {
1802
+ setMessages(() => []);
1803
+ setAgentHistory([]);
1804
+ setStreamingContent("");
1805
+ setThinkingContent("");
1806
+ setActiveToolUses([]);
1807
+ setActiveToolResults([]);
1808
+ setError(null);
1809
+ setNotice(null);
1810
+ }
1606
1811
  const effort = cfg.effort ?? "medium";
1607
1812
  useInput((char, key) => {
1608
1813
  if (key.ctrl && char === "c") {
@@ -1649,6 +1854,44 @@ function useKeyboard(opts) {
1649
1854
  }
1650
1855
  return;
1651
1856
  }
1857
+ if (state === "sessions") {
1858
+ if (key.upArrow) {
1859
+ setCursor((i) => Math.max(0, i - 1));
1860
+ return;
1861
+ }
1862
+ if (key.downArrow) {
1863
+ setCursor((i) => Math.min(sessions.length - 1, i + 1));
1864
+ return;
1865
+ }
1866
+ if (key.escape) {
1867
+ setState("ready");
1868
+ return;
1869
+ }
1870
+ if ((char === "d" || char === "x" || key.delete || key.backspace) && sessions[cursor]) {
1871
+ const meta = sessions[cursor];
1872
+ deleteSession(meta.id);
1873
+ const next = listSessions();
1874
+ setSessions(next);
1875
+ setCursor((i) => Math.max(0, Math.min(i, next.length - 1)));
1876
+ setNotice(`deleted \xB7 ${meta.title}`);
1877
+ return;
1878
+ }
1879
+ if (key.return && sessions[cursor]) {
1880
+ const meta = sessions[cursor];
1881
+ const history = loadSession(meta.id);
1882
+ setAgentHistory(history);
1883
+ setMessages(toDisplayMessages(history));
1884
+ setStreamingContent("");
1885
+ setThinkingContent("");
1886
+ setActiveToolUses([]);
1887
+ setActiveToolResults([]);
1888
+ setError(null);
1889
+ setSessionId(meta.id);
1890
+ setNotice(`resumed \xB7 ${meta.title}`);
1891
+ setState("ready");
1892
+ }
1893
+ return;
1894
+ }
1652
1895
  if (state === "ready" && pendingPermissionRef.current) {
1653
1896
  if (key.upArrow) {
1654
1897
  setPermissionCursor((i) => Math.max(0, i - 1));
@@ -1713,16 +1956,30 @@ function useKeyboard(opts) {
1713
1956
  setCursor(() => Math.max(0, models.findIndex((m) => m === cfg.model)));
1714
1957
  setState("models");
1715
1958
  } else if (trimmed === "/clear") {
1716
- setMessages(() => []);
1717
- setAgentHistory([]);
1718
- setStreamingContent("");
1719
- setThinkingContent("");
1720
- setActiveToolUses([]);
1721
- setActiveToolResults([]);
1722
- setError(null);
1959
+ clearSession();
1960
+ } else if (trimmed === "/new") {
1961
+ if (agentHistory.length) setNotice("session saved");
1962
+ setSessionId(newSessionId());
1963
+ clearSession();
1964
+ } else if (trimmed === "/sessions") {
1965
+ setSessions(listSessions());
1966
+ setCursor(() => 0);
1967
+ setState("sessions");
1723
1968
  } else if (trimmed === "/exit") {
1724
1969
  exit();
1725
1970
  } else if (trimmed) {
1971
+ setNotice(null);
1972
+ if (!agentHistory.length && cfg.model) {
1973
+ const id = sessionId;
1974
+ const model = cfg.model;
1975
+ void (async () => {
1976
+ try {
1977
+ const title = await summarizeMessage(model, trimmed);
1978
+ persistSession(id, [{ role: "user", content: trimmed }], title);
1979
+ } catch {
1980
+ }
1981
+ })();
1982
+ }
1726
1983
  sendMessage(trimmed);
1727
1984
  }
1728
1985
  setInput(() => "");
@@ -1782,7 +2039,7 @@ async function checkForUpdate() {
1782
2039
  }
1783
2040
 
1784
2041
  // src/ui/App.tsx
1785
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2042
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1786
2043
  function App() {
1787
2044
  const { exit } = useApp();
1788
2045
  const cwd = process.cwd().replace(homedir2(), "~").split(sep).join("/");
@@ -1793,6 +2050,10 @@ function App() {
1793
2050
  const [state, setState] = useState4("loading");
1794
2051
  const [cursor, setCursor] = useState4(0);
1795
2052
  const [updateAvailable, setUpdateAvailable] = useState4(null);
2053
+ const [ollamaDown, setOllamaDown] = useState4(false);
2054
+ const [sessionId, setSessionId] = useState4(() => newSessionId());
2055
+ const [sessions, setSessions] = useState4([]);
2056
+ const [notice, setNotice] = useState4(null);
1796
2057
  const [input, setInput] = useState4("");
1797
2058
  const [paletteCursor, setPaletteCursor] = useState4(0);
1798
2059
  const [filePickerCursor, setFilePickerCursor] = useState4(0);
@@ -1802,6 +2063,9 @@ function App() {
1802
2063
  if (v) setUpdateAvailable(v);
1803
2064
  });
1804
2065
  }, []);
2066
+ useEffect3(() => {
2067
+ if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
2068
+ }, [agent.agentHistory, sessionId]);
1805
2069
  useEffect3(() => {
1806
2070
  listModels().then((m) => {
1807
2071
  setModels(m);
@@ -1815,7 +2079,8 @@ function App() {
1815
2079
  });
1816
2080
  }).catch((err) => {
1817
2081
  const msg = err instanceof Error ? err.message : String(err);
1818
- agent.setError(msg);
2082
+ agent.setError(ollamaInstalled() ? msg : OLLAMA_NOT_INSTALLED);
2083
+ setOllamaDown(true);
1819
2084
  setModels([]);
1820
2085
  setState(cfg.model ? "ready" : "select-model");
1821
2086
  });
@@ -1831,26 +2096,18 @@ function App() {
1831
2096
  cfg,
1832
2097
  setCfg,
1833
2098
  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,
2099
+ agent,
1840
2100
  input,
1841
2101
  setInput,
1842
2102
  paletteCursor,
1843
2103
  setPaletteCursor,
1844
2104
  filePickerCursor,
1845
2105
  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
2106
+ sessionId,
2107
+ setSessionId,
2108
+ sessions,
2109
+ setSessions,
2110
+ setNotice
1854
2111
  });
1855
2112
  const effort = cfg.effort ?? "medium";
1856
2113
  const contextWarning = (() => {
@@ -1860,11 +2117,11 @@ function App() {
1860
2117
  if (used < activeCtx * 0.7) return null;
1861
2118
  return Math.round(used / activeCtx * 100);
1862
2119
  })();
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(
2120
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
2121
+ /* @__PURE__ */ jsx10(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
2122
+ 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` }) }),
2123
+ state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "connecting to ollama\u2026" }) }),
2124
+ agent.error && state !== "ready" && /* @__PURE__ */ jsx10(
1868
2125
  ChatView,
1869
2126
  {
1870
2127
  messages: [],
@@ -1874,12 +2131,12 @@ function App() {
1874
2131
  error: agent.error
1875
2132
  }
1876
2133
  ),
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" }) })
2134
+ state === "select-model" && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginLeft: 2, children: [
2135
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "no model configured \u2014 select one" }),
2136
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(ModelList, { models, cursor }) }),
2137
+ models.length > 0 && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 navigate enter select ctrl+c quit" }) })
1881
2138
  ] }),
1882
- state === "models" && /* @__PURE__ */ jsx9(
2139
+ state === "models" && /* @__PURE__ */ jsx10(
1883
2140
  ModelsView,
1884
2141
  {
1885
2142
  models,
@@ -1889,8 +2146,10 @@ function App() {
1889
2146
  effort
1890
2147
  }
1891
2148
  ),
1892
- state === "ready" && /* @__PURE__ */ jsxs9(Fragment2, { children: [
1893
- /* @__PURE__ */ jsx9(
2149
+ state === "sessions" && /* @__PURE__ */ jsx10(SessionsView, { sessions, cursor }),
2150
+ state === "ready" && /* @__PURE__ */ jsxs10(Fragment2, { children: [
2151
+ notice && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: `\u2713 ${notice}` }) }),
2152
+ /* @__PURE__ */ jsx10(
1894
2153
  ChatView,
1895
2154
  {
1896
2155
  messages: agent.messages,
@@ -1905,15 +2164,17 @@ function App() {
1905
2164
  activeToolResults: agent.activeToolResults
1906
2165
  }
1907
2166
  ),
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` }) }),
2167
+ input.startsWith("/") && /* @__PURE__ */ jsx10(CommandPalette, { filter: input, cursor: paletteCursor }),
2168
+ 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
2169
  !input.startsWith("/") && (() => {
1911
2170
  const m = parseMention(input);
1912
2171
  if (!m) return null;
1913
- return /* @__PURE__ */ jsx9(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
2172
+ return /* @__PURE__ */ jsx10(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
1914
2173
  })(),
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" }) })
2174
+ !ollamaDown && /* @__PURE__ */ jsxs10(Fragment2, { children: [
2175
+ /* @__PURE__ */ jsx10(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
2176
+ !agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "type / to see commands" }) })
2177
+ ] })
1917
2178
  ] })
1918
2179
  ] });
1919
2180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miii-agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Terminal AI coding agent powered by Ollama",
5
5
  "type": "module",
6
6
  "bin": {