@raegent/earshot 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/main.js +1917 -1300
  2. package/dist/main.js.map +26 -21
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -14446,6 +14446,11 @@ function openBrowser(url) {
14446
14446
  child.unref();
14447
14447
  } catch {}
14448
14448
  }
14449
+ // packages/providers/src/types.ts
14450
+ var REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh"];
14451
+ function isReasoningEffort(value) {
14452
+ return typeof value === "string" && REASONING_EFFORTS.includes(value);
14453
+ }
14449
14454
  // packages/core/src/context/agents-md.ts
14450
14455
  var MEMORY_FILENAMES = ["AGENTS.md", "CLAUDE.md"];
14451
14456
  async function readIfPresent(path) {
@@ -14902,6 +14907,8 @@ Actions that change files, run commands or reach the network ` + "prompt the use
14902
14907
  }
14903
14908
  }
14904
14909
  // packages/core/src/model.ts
14910
+ var DEFAULT_MODEL = "anthropic/claude-opus-5";
14911
+
14905
14912
  class MissingCredentialsError extends Error {
14906
14913
  provider;
14907
14914
  constructor(provider) {
@@ -15174,6 +15181,8 @@ async function loadSettings(cwd) {
15174
15181
  let defaultMode;
15175
15182
  let curiosity;
15176
15183
  let maxCostUsd;
15184
+ let defaultModel;
15185
+ const reasoningEfforts = {};
15177
15186
  for (const scope of scopes) {
15178
15187
  const path = settingsPath(scope, cwd);
15179
15188
  const file = await readSettings(path).catch((error) => {
@@ -15182,6 +15191,15 @@ async function loadSettings(cwd) {
15182
15191
  });
15183
15192
  if (!file)
15184
15193
  continue;
15194
+ if (typeof file.defaultModel === "string" && file.defaultModel.trim() !== "") {
15195
+ defaultModel = file.defaultModel.trim();
15196
+ }
15197
+ for (const [model, effort] of Object.entries(file.reasoningEfforts ?? {})) {
15198
+ if (isReasoningEffort(effort))
15199
+ reasoningEfforts[model] = effort;
15200
+ else
15201
+ problems.push(`${path}: reasoning effort for "${model}" is invalid`);
15202
+ }
15185
15203
  if (file.curiosity !== undefined) {
15186
15204
  if (isCuriosity(file.curiosity))
15187
15205
  curiosity = file.curiosity;
@@ -15226,9 +15244,36 @@ async function loadSettings(cwd) {
15226
15244
  ...defaultMode ? { defaultMode } : {},
15227
15245
  ...curiosity ? { curiosity } : {},
15228
15246
  ...maxCostUsd !== undefined ? { maxCostUsd } : {},
15247
+ ...defaultModel ? { defaultModel } : {},
15248
+ reasoningEfforts,
15229
15249
  problems
15230
15250
  };
15231
15251
  }
15252
+ async function persistDefaultModel(model, scope, cwd) {
15253
+ const path = settingsPath(scope, cwd);
15254
+ const existing = await readSettings(path).catch(() => {
15255
+ return;
15256
+ }) ?? {};
15257
+ await mkdir3(dirname3(path), { recursive: true });
15258
+ await writeFile3(path, `${JSON.stringify({ ...existing, defaultModel: model }, null, 2)}
15259
+ `, "utf8");
15260
+ return path;
15261
+ }
15262
+ async function persistReasoningEffort(model, effort, scope, cwd) {
15263
+ const path = settingsPath(scope, cwd);
15264
+ const existing = await readSettings(path).catch(() => {
15265
+ return;
15266
+ }) ?? {};
15267
+ const reasoningEfforts = { ...existing.reasoningEfforts ?? {} };
15268
+ if (effort === undefined)
15269
+ delete reasoningEfforts[model];
15270
+ else
15271
+ reasoningEfforts[model] = effort;
15272
+ await mkdir3(dirname3(path), { recursive: true });
15273
+ await writeFile3(path, `${JSON.stringify({ ...existing, reasoningEfforts }, null, 2)}
15274
+ `, "utf8");
15275
+ return path;
15276
+ }
15232
15277
  async function persistRule(rule, scope, cwd) {
15233
15278
  const path = settingsPath(scope, cwd);
15234
15279
  const existing = await readSettings(path).catch(() => {
@@ -17095,6 +17140,7 @@ class Agent {
17095
17140
  queued = [];
17096
17141
  rules;
17097
17142
  resolved;
17143
+ effort;
17098
17144
  mode;
17099
17145
  totalCostUsd = 0;
17100
17146
  maxCostUsd;
@@ -17131,10 +17177,17 @@ class Agent {
17131
17177
  this.systemPrompt = options.system;
17132
17178
  this.maxCostUsd = options.maxCostUsd;
17133
17179
  this.resolved = options.model;
17180
+ this.effort = options.reasoningEffort;
17134
17181
  }
17135
17182
  get model() {
17136
17183
  return this.resolved;
17137
17184
  }
17185
+ get reasoningEffort() {
17186
+ return this.effort;
17187
+ }
17188
+ setReasoningEffort(effort) {
17189
+ this.effort = effort;
17190
+ }
17138
17191
  async changeModel(ref) {
17139
17192
  const resolved = await resolveModel(this.options.registry, ref);
17140
17193
  this.resolved = resolved;
@@ -17295,6 +17348,7 @@ ${context.join(`
17295
17348
  system: this.effectiveSystem,
17296
17349
  messages,
17297
17350
  tools: this.offeredTools(),
17351
+ ...this.effort ? { reasoningEffort: this.effort } : {},
17298
17352
  abortSignal: signal
17299
17353
  })) {
17300
17354
  switch (event.type) {
@@ -17650,6 +17704,7 @@ If something is failing, fix it or say what is ` + `failing and why - do not des
17650
17704
  for await (const event of streamModel(this.options.registry, this.resolved, {
17651
17705
  system: SUMMARY_PROMPT,
17652
17706
  messages: [...messages, { role: "user", content: [{ type: "text", text: SUMMARY_PROMPT }] }],
17707
+ ...this.effort ? { reasoningEffort: this.effort } : {},
17653
17708
  abortSignal: signal
17654
17709
  })) {
17655
17710
  if (event.type === "text_delta")
@@ -18234,17 +18289,40 @@ async function listSessions(cwd) {
18234
18289
  const meta = entries.find((entry) => entry.type === "meta");
18235
18290
  if (!info || meta?.type !== "meta")
18236
18291
  continue;
18292
+ const configuration = latestConfiguration(entries);
18237
18293
  infos.push({
18238
18294
  id: name.replace(/\.jsonl$/, ""),
18239
18295
  path,
18240
18296
  cwd: meta.cwd,
18241
- model: meta.model,
18297
+ model: configuration.model ?? meta.model,
18298
+ ...configuration.reasoningEffort ? { reasoningEffort: configuration.reasoningEffort } : {},
18242
18299
  updatedAt: info.mtimeMs,
18243
18300
  preview: firstUserText(entries)
18244
18301
  });
18245
18302
  }
18246
18303
  return infos.sort((a, b) => b.updatedAt - a.updatedAt || b.id.localeCompare(a.id));
18247
18304
  }
18305
+ function latestConfiguration(entries) {
18306
+ const meta = entries.find((entry) => entry.type === "meta");
18307
+ let model = meta?.type === "meta" ? meta.model : undefined;
18308
+ let reasoningEffort;
18309
+ let reasoningConfigured = false;
18310
+ for (const entry of branchTo([...entries])) {
18311
+ if (entry.type !== "configuration")
18312
+ continue;
18313
+ if (entry.model !== undefined)
18314
+ model = entry.model;
18315
+ if (entry.reasoningEffort !== undefined) {
18316
+ reasoningConfigured = true;
18317
+ reasoningEffort = entry.reasoningEffort === null ? undefined : entry.reasoningEffort;
18318
+ }
18319
+ }
18320
+ return {
18321
+ ...model ? { model } : {},
18322
+ ...reasoningEffort ? { reasoningEffort } : {},
18323
+ reasoningConfigured
18324
+ };
18325
+ }
18248
18326
  async function latestSession(cwd) {
18249
18327
  return (await listSessions(cwd))[0];
18250
18328
  }
@@ -18381,7 +18459,7 @@ class ShadowGit {
18381
18459
  }
18382
18460
 
18383
18461
  // packages/core/src/version.ts
18384
- var VERSION = "0.3.2";
18462
+ var VERSION = "0.4.0";
18385
18463
 
18386
18464
  // packages/core/src/session/repair.ts
18387
18465
  var REPAIR_TEXT = "No result was recorded for this call: earshot exited before the tool finished. " + "The call may or may not have run, so treat its effect as unknown and check " + "the current state rather than assuming either outcome.";
@@ -18419,21 +18497,26 @@ class NoSessionToResumeError extends Error {
18419
18497
  async function createSession(options) {
18420
18498
  const registry2 = options.registry ?? buildRegistry();
18421
18499
  const settings2 = await loadSettings(options.cwd);
18500
+ const resumePath = options.ephemeral ? undefined : await resolveResumePath(options);
18501
+ const resumeEntries = resumePath ? await readEntries(resumePath) : [];
18502
+ const resumedConfiguration = latestConfiguration(resumeEntries);
18503
+ const requestedModel = options.model ?? resumedConfiguration.model ?? settings2.defaultModel ?? DEFAULT_MODEL;
18504
+ const configuredEffort = options.reasoningEffort !== undefined ? options.reasoningEffort : resumedConfiguration.reasoningConfigured ? resumedConfiguration.reasoningEffort : settings2.reasoningEfforts[requestedModel];
18422
18505
  const mode = options.mode ?? settings2.defaultMode ?? "ask";
18423
18506
  const curiosity = options.curiosity ?? settings2.curiosity ?? "normal";
18424
18507
  const maxCostUsd = options.maxCostUsd !== undefined ? options.maxCostUsd > 0 ? options.maxCostUsd : undefined : settings2.maxCostUsd;
18425
- const resolved = await resolveModel(registry2, options.model, {
18508
+ const resolved = await resolveModel(registry2, requestedModel, {
18426
18509
  ...options.apiKey ? { apiKey: options.apiKey } : {}
18427
18510
  });
18428
18511
  const modelRef = `${resolved.provider.id}/${resolved.model.id}`;
18512
+ const reasoningEffort = resolved.model.capabilities.reasoning ? configuredEffort ?? undefined : undefined;
18429
18513
  const discovered = options.noExtensions ? { skills: [], commands: [], problems: [] } : await discoverExtensions(options.cwd);
18430
18514
  let store2;
18431
18515
  let replayed = [];
18432
18516
  const repairProblems = [];
18433
18517
  if (!options.ephemeral) {
18434
- const resumePath = await resolveResumePath(options);
18435
18518
  if (resumePath) {
18436
- const entries = await readEntries(resumePath);
18519
+ const entries = resumeEntries;
18437
18520
  replayed = messagesOf(branchTo(entries));
18438
18521
  store2 = await SessionStore.open(resumePath).catch(() => {
18439
18522
  return;
@@ -18485,6 +18568,7 @@ ${started.context.join(`
18485
18568
  const agentOptions = {
18486
18569
  registry: registry2,
18487
18570
  model: resolved,
18571
+ ...reasoningEffort ? { reasoningEffort } : {},
18488
18572
  cwd: options.cwd,
18489
18573
  system,
18490
18574
  mode,
@@ -18551,7 +18635,7 @@ ${started.context.join(`
18551
18635
  const from = entryId ?? store2.tailId;
18552
18636
  const entries = await readEntries(store2.path);
18553
18637
  const forked = await SessionStore.create(options.cwd, {
18554
- model: modelRef,
18638
+ model: `${agent.model.provider.id}/${agent.model.model.id}`,
18555
18639
  version: VERSION,
18556
18640
  ...from ? { forkedFrom: { sessionId: store2.id, entryId: from } } : {}
18557
18641
  }).catch(() => {
@@ -18562,10 +18646,18 @@ ${started.context.join(`
18562
18646
  const kept = messagesOf(branchTo(entries, from ?? undefined));
18563
18647
  for (const message of kept)
18564
18648
  await forked.appendMessage(message);
18649
+ await forked.append({
18650
+ type: "configuration",
18651
+ model: `${agent.model.provider.id}/${agent.model.model.id}`,
18652
+ reasoningEffort: agent.reasoningEffort ?? null
18653
+ });
18565
18654
  agent.replaceHistory(kept);
18566
18655
  store2 = forked;
18567
18656
  return forked.id;
18568
18657
  },
18658
+ async recordConfiguration(configuration) {
18659
+ await store2?.append({ type: "configuration", ...configuration });
18660
+ },
18569
18661
  async undo() {
18570
18662
  if (!shadow)
18571
18663
  return;
@@ -18650,7 +18742,8 @@ function parseArgs(argv) {
18650
18742
  "models",
18651
18743
  "acp",
18652
18744
  "doctor",
18653
- "update"
18745
+ "update",
18746
+ "sessions"
18654
18747
  ]);
18655
18748
  const command = positionals[0] !== undefined && known.has(positionals[0]) ? positionals[0] : undefined;
18656
18749
  return { command, flags, positionals: command ? positionals.slice(1) : positionals };
@@ -19578,7 +19671,6 @@ async function trustedExtensions(cwd) {
19578
19671
  }
19579
19672
 
19580
19673
  // packages/cli/src/commands/acp.ts
19581
- var DEFAULT_MODEL = "anthropic/claude-opus-5";
19582
19674
  async function acpCommand(args) {
19583
19675
  const requested = args.flags["permission-mode"];
19584
19676
  let mode;
@@ -19590,7 +19682,8 @@ async function acpCommand(args) {
19590
19682
  }
19591
19683
  mode = requested;
19592
19684
  }
19593
- const model2 = typeof args.flags.model === "string" ? args.flags.model : DEFAULT_MODEL;
19685
+ const requestedModel = typeof args.flags.model === "string" ? args.flags.model : undefined;
19686
+ const model2 = requestedModel ?? DEFAULT_MODEL;
19594
19687
  const apiKey = typeof args.flags["api-key"] === "string" ? args.flags["api-key"] : undefined;
19595
19688
  const sessionFactory = async ({ cwd, resumeSessionId }) => {
19596
19689
  const extensions = await startExtensions(cwd);
@@ -19606,7 +19699,7 @@ async function acpCommand(args) {
19606
19699
  try {
19607
19700
  return await createSession({
19608
19701
  cwd,
19609
- model: model2,
19702
+ ...requestedModel ? { model: requestedModel } : {},
19610
19703
  extraTools: extensions.tools,
19611
19704
  problems: extensions.problems,
19612
19705
  onDispose: () => extensions.close(),
@@ -20049,7 +20142,6 @@ function toStreamRecord(event) {
20049
20142
  }
20050
20143
 
20051
20144
  // packages/cli/src/commands/headless.ts
20052
- var DEFAULT_MODEL2 = "anthropic/claude-opus-5";
20053
20145
  async function headlessCommand(prompt, args) {
20054
20146
  const flags = args.flags;
20055
20147
  const requestedFormat = typeof flags["output-format"] === "string" ? flags["output-format"] : "text";
@@ -20080,6 +20172,12 @@ async function headlessCommand(prompt, args) {
20080
20172
  const maxCostUsd = parseMaxCost(flags["max-cost"]);
20081
20173
  if (maxCostUsd === "invalid") {
20082
20174
  process.stderr.write(`"${flags["max-cost"]}" is not an amount in dollars
20175
+ `);
20176
+ return 2;
20177
+ }
20178
+ const reasoningEffort = parseReasoningEffort(flags["reasoning-effort"]);
20179
+ if (reasoningEffort === "invalid") {
20180
+ process.stderr.write(`"${flags["reasoning-effort"]}" is not a reasoning effort
20083
20181
  `);
20084
20182
  return 2;
20085
20183
  }
@@ -20091,7 +20189,8 @@ async function headlessCommand(prompt, args) {
20091
20189
  extraTools: extensions.tools,
20092
20190
  problems: extensions.problems,
20093
20191
  onDispose: () => extensions.close(),
20094
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL2,
20192
+ ...typeof flags.model === "string" ? { model: flags.model } : {},
20193
+ ...reasoningEffort !== undefined ? { reasoningEffort } : {},
20095
20194
  ...mode ? { mode } : {},
20096
20195
  ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
20097
20196
  ...curiosity ? { curiosity } : {},
@@ -20170,7 +20269,7 @@ async function headlessCommand(prompt, args) {
20170
20269
  costUsd: session2.agent.costUsd,
20171
20270
  durationMs: Date.now() - startedAt,
20172
20271
  numMessages: session2.agent.history.length,
20173
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL2,
20272
+ model: `${session2.agent.model.provider.id}/${session2.agent.model.model.id}`,
20174
20273
  permissionMode: session2.agent.permissionMode,
20175
20274
  ...session2.store ? { sessionId: session2.store.id } : {},
20176
20275
  ...failure ? { error: failure } : {}
@@ -20180,6 +20279,15 @@ async function headlessCommand(prompt, args) {
20180
20279
  `);
20181
20280
  return exitCode;
20182
20281
  }
20282
+ function parseReasoningEffort(value) {
20283
+ if (value === undefined)
20284
+ return;
20285
+ if (value === "auto")
20286
+ return null;
20287
+ if (typeof value !== "string")
20288
+ return "invalid";
20289
+ return ["none", "low", "medium", "high", "xhigh"].includes(value) ? value : "invalid";
20290
+ }
20183
20291
  function resumeFrom(flags) {
20184
20292
  if (typeof flags.resume === "string")
20185
20293
  return { resume: { path: flags.resume } };
@@ -20287,8 +20395,8 @@ run \`earshot models\` to see what is available
20287
20395
  }
20288
20396
 
20289
20397
  // packages/tui/src/app.tsx
20290
- import { Box as Box9, Static, Text as Text10, useApp, useInput as useInput4 } from "ink";
20291
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
20398
+ import { Box as Box12, Static, Text as Text14, useApp as useApp3, useInput as useInput7 } from "ink";
20399
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
20292
20400
 
20293
20401
  // packages/tui/src/commands.ts
20294
20402
  var SPECS = [
@@ -20301,6 +20409,16 @@ var SPECS = [
20301
20409
  args: "[ref]",
20302
20410
  summary: "Show the model in use, or switch to another for the rest of the session"
20303
20411
  },
20412
+ {
20413
+ name: "reasoning",
20414
+ args: "[auto|none|low|medium|high|xhigh]",
20415
+ summary: "Show or change reasoning effort for the current model"
20416
+ },
20417
+ {
20418
+ name: "thinking",
20419
+ args: "[show|hide]",
20420
+ summary: "Show or hide streamed model reasoning"
20421
+ },
20304
20422
  {
20305
20423
  name: "mode",
20306
20424
  args: "<plan|ask|accept-edits|auto|yolo>",
@@ -20359,6 +20477,11 @@ var SPECS = [
20359
20477
  summary: "List this session's prompts, numbered",
20360
20478
  idleOnly: true
20361
20479
  },
20480
+ {
20481
+ name: "sessions",
20482
+ summary: "Browse and resume chats saved for this project",
20483
+ idleOnly: true
20484
+ },
20362
20485
  {
20363
20486
  name: "rewind",
20364
20487
  args: "<n>",
@@ -20404,8 +20527,9 @@ function commandRows() {
20404
20527
  return rows;
20405
20528
  }
20406
20529
 
20407
- // packages/tui/src/components/command-menu.tsx
20408
- import { Box, Text } from "ink";
20530
+ // packages/tui/src/components/activity.tsx
20531
+ import { Text } from "ink";
20532
+ import { useEffect, useState } from "react";
20409
20533
 
20410
20534
  // packages/tui/src/theme.ts
20411
20535
  var theme = {
@@ -20437,8 +20561,24 @@ var MODE_COLOR = {
20437
20561
  yolo: theme.danger
20438
20562
  };
20439
20563
 
20440
- // packages/tui/src/components/command-menu.tsx
20564
+ // packages/tui/src/components/activity.tsx
20441
20565
  import { jsxDEV } from "react/jsx-dev-runtime";
20566
+ var FRAMES = ["· ", "·· ", "···", " ··", " ·", " "];
20567
+ function Activity({ label }) {
20568
+ const [frame, setFrame] = useState(0);
20569
+ useEffect(() => {
20570
+ const timer = setInterval(() => setFrame((value) => (value + 1) % FRAMES.length), 120);
20571
+ return () => clearInterval(timer);
20572
+ }, []);
20573
+ return /* @__PURE__ */ jsxDEV(Text, {
20574
+ color: theme.muted,
20575
+ children: label ? `${label} ${FRAMES[frame]}` : FRAMES[frame]
20576
+ }, undefined, false, undefined, this);
20577
+ }
20578
+
20579
+ // packages/tui/src/components/command-menu.tsx
20580
+ import { Box, Text as Text2 } from "ink";
20581
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20442
20582
  var SUMMARY_WIDTH = 58;
20443
20583
  var VISIBLE = 10;
20444
20584
  function menuEntries(query, commands, busy) {
@@ -20499,9 +20639,9 @@ function ranked(items, query) {
20499
20639
  }
20500
20640
  function CommandMenu({ entries, selected }) {
20501
20641
  if (entries.length === 0) {
20502
- return /* @__PURE__ */ jsxDEV(Box, {
20642
+ return /* @__PURE__ */ jsxDEV2(Box, {
20503
20643
  marginTop: 1,
20504
- children: /* @__PURE__ */ jsxDEV(Text, {
20644
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20505
20645
  color: theme.muted,
20506
20646
  children: "no command matches"
20507
20647
  }, undefined, false, undefined, this)
@@ -20510,31 +20650,31 @@ function CommandMenu({ entries, selected }) {
20510
20650
  const start = Math.min(Math.max(0, selected - VISIBLE + 1), Math.max(0, entries.length - VISIBLE));
20511
20651
  const shown = entries.slice(start, start + VISIBLE);
20512
20652
  const hidden = entries.length - shown.length;
20513
- return /* @__PURE__ */ jsxDEV(Box, {
20653
+ return /* @__PURE__ */ jsxDEV2(Box, {
20514
20654
  flexDirection: "column",
20515
20655
  marginTop: 1,
20516
20656
  children: [
20517
20657
  shown.map((entry, index) => {
20518
20658
  const active = start + index === selected;
20519
- return /* @__PURE__ */ jsxDEV(Box, {
20659
+ return /* @__PURE__ */ jsxDEV2(Box, {
20520
20660
  children: [
20521
- /* @__PURE__ */ jsxDEV(Text, {
20661
+ /* @__PURE__ */ jsxDEV2(Text2, {
20522
20662
  color: active ? theme.user : theme.muted,
20523
20663
  children: active ? "› " : " "
20524
20664
  }, undefined, false, undefined, this),
20525
- /* @__PURE__ */ jsxDEV(Text, {
20665
+ /* @__PURE__ */ jsxDEV2(Text2, {
20526
20666
  ...rowColor(entry, active),
20527
20667
  children: entry.label.padEnd(34)
20528
20668
  }, undefined, false, undefined, this),
20529
- /* @__PURE__ */ jsxDEV(Text, {
20669
+ /* @__PURE__ */ jsxDEV2(Text2, {
20530
20670
  color: theme.muted,
20531
20671
  children: clamp(`${entry.scope ? `[${entry.scope}] ` : ""}${entry.summary}${entry.disabled ? " (not while a turn is running)" : ""}`)
20532
20672
  }, undefined, false, undefined, this)
20533
20673
  ]
20534
20674
  }, entry.label, true, undefined, this);
20535
20675
  }),
20536
- /* @__PURE__ */ jsxDEV(Box, {
20537
- children: /* @__PURE__ */ jsxDEV(Text, {
20676
+ /* @__PURE__ */ jsxDEV2(Box, {
20677
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20538
20678
  color: theme.muted,
20539
20679
  children: [
20540
20680
  hidden > 0 ? ` ${hidden} more · ` : " ",
@@ -20547,13 +20687,13 @@ function CommandMenu({ entries, selected }) {
20547
20687
  }
20548
20688
 
20549
20689
  // packages/tui/src/components/markdown.tsx
20550
- import { Box as Box2, Text as Text2 } from "ink";
20551
- import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
20690
+ import { Box as Box2, Text as Text3 } from "ink";
20691
+ import { jsxDEV as jsxDEV3, Fragment } from "react/jsx-dev-runtime";
20552
20692
  function Markdown({ text: text2 }) {
20553
20693
  const blocks = splitBlocks(text2);
20554
- return /* @__PURE__ */ jsxDEV2(Box2, {
20694
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20555
20695
  flexDirection: "column",
20556
- children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV2(Block, {
20696
+ children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV3(Block, {
20557
20697
  block
20558
20698
  }, index, false, undefined, this))
20559
20699
  }, undefined, false, undefined, this);
@@ -20632,19 +20772,19 @@ function splitBlocks(text2) {
20632
20772
  function Block({ block }) {
20633
20773
  switch (block.kind) {
20634
20774
  case "code":
20635
- return /* @__PURE__ */ jsxDEV2(Box2, {
20775
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20636
20776
  flexDirection: "column",
20637
20777
  marginY: 1,
20638
20778
  paddingLeft: 2,
20639
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20779
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20640
20780
  color: theme.tool,
20641
20781
  children: line
20642
20782
  }, index, false, undefined, this))
20643
20783
  }, undefined, false, undefined, this);
20644
20784
  case "heading":
20645
- return /* @__PURE__ */ jsxDEV2(Box2, {
20785
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20646
20786
  marginTop: block.level <= 2 ? 1 : 0,
20647
- children: /* @__PURE__ */ jsxDEV2(Text2, {
20787
+ children: /* @__PURE__ */ jsxDEV3(Text3, {
20648
20788
  bold: true,
20649
20789
  underline: block.level === 1,
20650
20790
  color: theme.assistant,
@@ -20652,29 +20792,29 @@ function Block({ block }) {
20652
20792
  }, undefined, false, undefined, this)
20653
20793
  }, undefined, false, undefined, this);
20654
20794
  case "rule":
20655
- return /* @__PURE__ */ jsxDEV2(Text2, {
20795
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20656
20796
  color: theme.muted,
20657
20797
  children: "─".repeat(40)
20658
20798
  }, undefined, false, undefined, this);
20659
20799
  case "list":
20660
- return /* @__PURE__ */ jsxDEV2(Box2, {
20800
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20661
20801
  flexDirection: "column",
20662
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20802
+ children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20663
20803
  children: [
20664
20804
  " ",
20665
20805
  block.ordered ? `${index + 1}.` : "-",
20666
20806
  " ",
20667
- /* @__PURE__ */ jsxDEV2(Inline, {
20807
+ /* @__PURE__ */ jsxDEV3(Inline, {
20668
20808
  text: item
20669
20809
  }, undefined, false, undefined, this)
20670
20810
  ]
20671
20811
  }, index, true, undefined, this))
20672
20812
  }, undefined, false, undefined, this);
20673
20813
  case "quote":
20674
- return /* @__PURE__ */ jsxDEV2(Box2, {
20814
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20675
20815
  flexDirection: "column",
20676
20816
  paddingLeft: 1,
20677
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20817
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20678
20818
  color: theme.muted,
20679
20819
  italic: true,
20680
20820
  children: [
@@ -20684,8 +20824,8 @@ function Block({ block }) {
20684
20824
  }, index, true, undefined, this))
20685
20825
  }, undefined, false, undefined, this);
20686
20826
  default:
20687
- return /* @__PURE__ */ jsxDEV2(Text2, {
20688
- children: /* @__PURE__ */ jsxDEV2(Inline, {
20827
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20828
+ children: /* @__PURE__ */ jsxDEV3(Inline, {
20689
20829
  text: block.text
20690
20830
  }, undefined, false, undefined, this)
20691
20831
  }, undefined, false, undefined, this);
@@ -20697,41 +20837,41 @@ function inlineToText(text2) {
20697
20837
  function Inline({ text: text2 }) {
20698
20838
  const pattern = /(\*\*.+?\*\*|__.+?__|`.+?`|\*.+?\*|_.+?_)/g;
20699
20839
  const parts = text2.split(pattern);
20700
- return /* @__PURE__ */ jsxDEV2(Fragment, {
20840
+ return /* @__PURE__ */ jsxDEV3(Fragment, {
20701
20841
  children: parts.map((part, index) => {
20702
20842
  if (part === "")
20703
20843
  return null;
20704
20844
  if (part.startsWith("**") && part.endsWith("**")) {
20705
- return /* @__PURE__ */ jsxDEV2(Text2, {
20845
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20706
20846
  bold: true,
20707
20847
  children: part.slice(2, -2)
20708
20848
  }, index, false, undefined, this);
20709
20849
  }
20710
20850
  if (part.startsWith("__") && part.endsWith("__")) {
20711
- return /* @__PURE__ */ jsxDEV2(Text2, {
20851
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20712
20852
  bold: true,
20713
20853
  children: part.slice(2, -2)
20714
20854
  }, index, false, undefined, this);
20715
20855
  }
20716
20856
  if (part.startsWith("`") && part.endsWith("`")) {
20717
- return /* @__PURE__ */ jsxDEV2(Text2, {
20857
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20718
20858
  color: theme.tool,
20719
20859
  children: part.slice(1, -1)
20720
20860
  }, index, false, undefined, this);
20721
20861
  }
20722
20862
  if (part.startsWith("*") && part.endsWith("*")) {
20723
- return /* @__PURE__ */ jsxDEV2(Text2, {
20863
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20724
20864
  italic: true,
20725
20865
  children: part.slice(1, -1)
20726
20866
  }, index, false, undefined, this);
20727
20867
  }
20728
20868
  if (part.startsWith("_") && part.endsWith("_")) {
20729
- return /* @__PURE__ */ jsxDEV2(Text2, {
20869
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20730
20870
  italic: true,
20731
20871
  children: part.slice(1, -1)
20732
20872
  }, index, false, undefined, this);
20733
20873
  }
20734
- return /* @__PURE__ */ jsxDEV2(Text2, {
20874
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20735
20875
  children: part
20736
20876
  }, index, false, undefined, this);
20737
20877
  })
@@ -20739,24 +20879,24 @@ function Inline({ text: text2 }) {
20739
20879
  }
20740
20880
 
20741
20881
  // packages/tui/src/components/memory-capture.tsx
20742
- import { Box as Box3, Text as Text3 } from "ink";
20743
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
20882
+ import { Box as Box3, Text as Text4 } from "ink";
20883
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20744
20884
  function MemoryCapture({ candidate }) {
20745
- return /* @__PURE__ */ jsxDEV3(Box3, {
20885
+ return /* @__PURE__ */ jsxDEV4(Box3, {
20746
20886
  marginTop: 1,
20747
20887
  children: [
20748
- /* @__PURE__ */ jsxDEV3(Text3, {
20888
+ /* @__PURE__ */ jsxDEV4(Text4, {
20749
20889
  color: theme.accent,
20750
20890
  children: "remember "
20751
20891
  }, undefined, false, undefined, this),
20752
- /* @__PURE__ */ jsxDEV3(Text3, {
20892
+ /* @__PURE__ */ jsxDEV4(Text4, {
20753
20893
  children: [
20754
20894
  "“",
20755
20895
  candidate.text,
20756
20896
  "”"
20757
20897
  ]
20758
20898
  }, undefined, true, undefined, this),
20759
- /* @__PURE__ */ jsxDEV3(Text3, {
20899
+ /* @__PURE__ */ jsxDEV4(Text4, {
20760
20900
  color: theme.muted,
20761
20901
  children: "? ctrl+r for this project · ctrl+g everywhere"
20762
20902
  }, undefined, false, undefined, this)
@@ -20765,12 +20905,12 @@ function MemoryCapture({ candidate }) {
20765
20905
  }
20766
20906
 
20767
20907
  // packages/tui/src/components/permission.tsx
20768
- import { Box as Box5, Text as Text5, useInput } from "ink";
20769
- import { useState } from "react";
20908
+ import { Box as Box5, Text as Text6, useInput } from "ink";
20909
+ import { useState as useState2 } from "react";
20770
20910
 
20771
20911
  // packages/tui/src/components/diff.tsx
20772
- import { Box as Box4, Text as Text4 } from "ink";
20773
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20912
+ import { Box as Box4, Text as Text5 } from "ink";
20913
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20774
20914
  var MAX_LINES = 60;
20775
20915
  function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20776
20916
  const lines = diff2.split(`
@@ -20778,15 +20918,15 @@ function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20778
20918
  const body = lines.filter((line) => !line.startsWith("---") && !line.startsWith("+++"));
20779
20919
  const shown = body.slice(0, maxLines);
20780
20920
  const hidden = body.length - shown.length;
20781
- return /* @__PURE__ */ jsxDEV4(Box4, {
20921
+ return /* @__PURE__ */ jsxDEV5(Box4, {
20782
20922
  flexDirection: "column",
20783
20923
  children: [
20784
- shown.map((line, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20924
+ shown.map((line, index) => /* @__PURE__ */ jsxDEV5(Text5, {
20785
20925
  color: colorFor(line),
20786
20926
  wrap: "truncate-end",
20787
20927
  children: line === "" ? " " : line
20788
20928
  }, index, false, undefined, this)),
20789
- hidden > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
20929
+ hidden > 0 && /* @__PURE__ */ jsxDEV5(Text5, {
20790
20930
  color: theme.muted,
20791
20931
  children: [
20792
20932
  " ",
@@ -20821,9 +20961,9 @@ function diffStat(diff2) {
20821
20961
  }
20822
20962
 
20823
20963
  // packages/tui/src/components/permission.tsx
20824
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20964
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20825
20965
  function PermissionPrompt({ request, reason, onChoice }) {
20826
- const [selected, setSelected] = useState(0);
20966
+ const [selected, setSelected] = useState2(0);
20827
20967
  const options = [
20828
20968
  { label: "Allow once", choice: { kind: "allow-once" }, color: theme.assistant },
20829
20969
  {
@@ -20849,32 +20989,32 @@ function PermissionPrompt({ request, reason, onChoice }) {
20849
20989
  });
20850
20990
  const isDiff = request.detail.includes(`
20851
20991
  @@`) || request.detail.startsWith("---");
20852
- return /* @__PURE__ */ jsxDEV5(Box5, {
20992
+ return /* @__PURE__ */ jsxDEV6(Box5, {
20853
20993
  flexDirection: "column",
20854
20994
  borderStyle: "round",
20855
20995
  borderColor: theme.warning,
20856
20996
  paddingX: 1,
20857
20997
  children: [
20858
- /* @__PURE__ */ jsxDEV5(Text5, {
20998
+ /* @__PURE__ */ jsxDEV6(Text6, {
20859
20999
  bold: true,
20860
21000
  color: theme.warning,
20861
21001
  children: request.title
20862
21002
  }, undefined, false, undefined, this),
20863
- /* @__PURE__ */ jsxDEV5(Text5, {
21003
+ /* @__PURE__ */ jsxDEV6(Text6, {
20864
21004
  color: theme.muted,
20865
21005
  children: reason
20866
21006
  }, undefined, false, undefined, this),
20867
- /* @__PURE__ */ jsxDEV5(Box5, {
21007
+ /* @__PURE__ */ jsxDEV6(Box5, {
20868
21008
  marginY: 1,
20869
21009
  flexDirection: "column",
20870
- children: isDiff ? /* @__PURE__ */ jsxDEV5(DiffView, {
21010
+ children: isDiff ? /* @__PURE__ */ jsxDEV6(DiffView, {
20871
21011
  diff: request.detail
20872
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Text5, {
21012
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(Text6, {
20873
21013
  wrap: "wrap",
20874
21014
  children: request.detail
20875
21015
  }, undefined, false, undefined, this)
20876
21016
  }, undefined, false, undefined, this),
20877
- options.map((option, index) => /* @__PURE__ */ jsxDEV5(Text5, {
21017
+ options.map((option, index) => /* @__PURE__ */ jsxDEV6(Text6, {
20878
21018
  color: index === selected ? theme.accent : option.color,
20879
21019
  children: [
20880
21020
  index === selected ? "❯ " : " ",
@@ -20889,13 +21029,13 @@ function truncate2(value, max) {
20889
21029
  }
20890
21030
 
20891
21031
  // packages/tui/src/components/question.tsx
20892
- import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
20893
- import { useState as useState2 } from "react";
21032
+ import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
21033
+ import { useState as useState3 } from "react";
20894
21034
 
20895
21035
  // packages/tui/src/components/text-input.tsx
20896
- import { Text as Text6, useInput as useInput2 } from "ink";
20897
- import { useEffect, useRef } from "react";
20898
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
21036
+ import { Text as Text7, useInput as useInput2 } from "ink";
21037
+ import { useEffect as useEffect2, useRef } from "react";
21038
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
20899
21039
  function TextInput({
20900
21040
  value,
20901
21041
  onChange,
@@ -20904,7 +21044,7 @@ function TextInput({
20904
21044
  isActive = true
20905
21045
  }) {
20906
21046
  const buffer = useRef(value);
20907
- useEffect(() => {
21047
+ useEffect2(() => {
20908
21048
  buffer.current = value;
20909
21049
  }, [value]);
20910
21050
  useInput2((input, key2) => {
@@ -20929,23 +21069,23 @@ function TextInput({
20929
21069
  }
20930
21070
  }, { isActive });
20931
21071
  if (value === "") {
20932
- return /* @__PURE__ */ jsxDEV6(Text6, {
21072
+ return /* @__PURE__ */ jsxDEV7(Text7, {
20933
21073
  children: [
20934
- /* @__PURE__ */ jsxDEV6(Text6, {
21074
+ /* @__PURE__ */ jsxDEV7(Text7, {
20935
21075
  inverse: true,
20936
21076
  children: " "
20937
21077
  }, undefined, false, undefined, this),
20938
- /* @__PURE__ */ jsxDEV6(Text6, {
21078
+ /* @__PURE__ */ jsxDEV7(Text7, {
20939
21079
  color: theme.muted,
20940
21080
  children: placeholder
20941
21081
  }, undefined, false, undefined, this)
20942
21082
  ]
20943
21083
  }, undefined, true, undefined, this);
20944
21084
  }
20945
- return /* @__PURE__ */ jsxDEV6(Text6, {
21085
+ return /* @__PURE__ */ jsxDEV7(Text7, {
20946
21086
  children: [
20947
21087
  value,
20948
- /* @__PURE__ */ jsxDEV6(Text6, {
21088
+ /* @__PURE__ */ jsxDEV7(Text7, {
20949
21089
  inverse: true,
20950
21090
  children: " "
20951
21091
  }, undefined, false, undefined, this)
@@ -20954,11 +21094,11 @@ function TextInput({
20954
21094
  }
20955
21095
 
20956
21096
  // packages/tui/src/components/question.tsx
20957
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
21097
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
20958
21098
  function QuestionPrompt({ question, options = [], onAnswer }) {
20959
- const [selected, setSelected] = useState2(0);
20960
- const [typing, setTyping] = useState2(options.length === 0);
20961
- const [value, setValue] = useState2("");
21099
+ const [selected, setSelected] = useState3(0);
21100
+ const [typing, setTyping] = useState3(options.length === 0);
21101
+ const [value, setValue] = useState3("");
20962
21102
  useInput3((input, key2) => {
20963
21103
  if (key2.upArrow)
20964
21104
  setSelected((n) => (n + options.length - 1) % options.length);
@@ -20971,29 +21111,29 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20971
21111
  setValue(input);
20972
21112
  }
20973
21113
  }, { isActive: !typing });
20974
- return /* @__PURE__ */ jsxDEV7(Box6, {
21114
+ return /* @__PURE__ */ jsxDEV8(Box6, {
20975
21115
  flexDirection: "column",
20976
21116
  borderStyle: "round",
20977
21117
  borderColor: theme.accent,
20978
21118
  paddingX: 1,
20979
21119
  children: [
20980
- /* @__PURE__ */ jsxDEV7(Text7, {
21120
+ /* @__PURE__ */ jsxDEV8(Text8, {
20981
21121
  bold: true,
20982
21122
  color: theme.accent,
20983
21123
  children: question
20984
21124
  }, undefined, false, undefined, this),
20985
- !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV7(Text7, {
21125
+ !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV8(Text8, {
20986
21126
  color: index === selected ? theme.accent : theme.muted,
20987
21127
  children: [
20988
21128
  index === selected ? "❯ " : " ",
20989
21129
  option
20990
21130
  ]
20991
21131
  }, option, true, undefined, this)),
20992
- !typing && /* @__PURE__ */ jsxDEV7(Text7, {
21132
+ !typing && /* @__PURE__ */ jsxDEV8(Text8, {
20993
21133
  color: theme.muted,
20994
21134
  children: "or start typing to answer in your own words"
20995
21135
  }, undefined, false, undefined, this),
20996
- typing && /* @__PURE__ */ jsxDEV7(TextInput, {
21136
+ typing && /* @__PURE__ */ jsxDEV8(TextInput, {
20997
21137
  value,
20998
21138
  onChange: setValue,
20999
21139
  onSubmit: (answer) => onAnswer(answer.trim()),
@@ -21003,11 +21143,60 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
21003
21143
  }, undefined, true, undefined, this);
21004
21144
  }
21005
21145
 
21146
+ // packages/tui/src/components/reasoning-picker.tsx
21147
+ import { Box as Box7, Text as Text9, useInput as useInput4 } from "ink";
21148
+ import { useState as useState4 } from "react";
21149
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21150
+ var choices = ["auto", "none", "low", "medium", "high", "xhigh"];
21151
+ function ReasoningPicker({
21152
+ current,
21153
+ onDone,
21154
+ onCancel
21155
+ }) {
21156
+ const [cursor, setCursor] = useState4(Math.max(0, choices.indexOf(current ?? "auto")));
21157
+ useInput4((_input, key2) => {
21158
+ if (key2.escape)
21159
+ return onCancel();
21160
+ if (key2.upArrow)
21161
+ setCursor((value) => value <= 0 ? choices.length - 1 : value - 1);
21162
+ if (key2.downArrow)
21163
+ setCursor((value) => value >= choices.length - 1 ? 0 : value + 1);
21164
+ if (key2.return) {
21165
+ const selected = choices[cursor] ?? "auto";
21166
+ onDone(selected === "auto" ? undefined : selected);
21167
+ }
21168
+ });
21169
+ return /* @__PURE__ */ jsxDEV9(Box7, {
21170
+ flexDirection: "column",
21171
+ children: [
21172
+ /* @__PURE__ */ jsxDEV9(Text9, {
21173
+ children: "reasoning effort"
21174
+ }, undefined, false, undefined, this),
21175
+ /* @__PURE__ */ jsxDEV9(Box7, {
21176
+ flexDirection: "column",
21177
+ marginTop: 1,
21178
+ children: choices.map((choice, index) => /* @__PURE__ */ jsxDEV9(Text9, {
21179
+ color: index === cursor ? theme.user : theme.muted,
21180
+ children: [
21181
+ index === cursor ? "› " : " ",
21182
+ choice
21183
+ ]
21184
+ }, choice, true, undefined, this))
21185
+ }, undefined, false, undefined, this),
21186
+ /* @__PURE__ */ jsxDEV9(Text9, {
21187
+ color: theme.muted,
21188
+ children: "↑↓ choose · enter select · esc cancel"
21189
+ }, undefined, false, undefined, this)
21190
+ ]
21191
+ }, undefined, true, undefined, this);
21192
+ }
21193
+
21006
21194
  // packages/tui/src/components/status.tsx
21007
- import { Box as Box7, Text as Text8 } from "ink";
21008
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
21195
+ import { Box as Box8, Text as Text10 } from "ink";
21196
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
21009
21197
  function StatusLine({
21010
21198
  model: model2,
21199
+ reasoningEffort,
21011
21200
  mode,
21012
21201
  costUsd,
21013
21202
  todos,
@@ -21019,27 +21208,34 @@ function StatusLine({
21019
21208
  const done = todos.filter((todo2) => todo2.status === "done").length;
21020
21209
  const current = todos.find((todo2) => todo2.status === "in_progress");
21021
21210
  const used = context2.window > 0 ? Math.min(100, context2.tokens / context2.window * 100) : 0;
21022
- return /* @__PURE__ */ jsxDEV8(Box7, {
21211
+ return /* @__PURE__ */ jsxDEV10(Box8, {
21023
21212
  children: [
21024
- /* @__PURE__ */ jsxDEV8(Text8, {
21213
+ /* @__PURE__ */ jsxDEV10(Text10, {
21025
21214
  color: MODE_COLOR[mode] ?? theme.muted,
21026
21215
  children: MODE_LABEL[mode] ?? mode
21027
21216
  }, undefined, false, undefined, this),
21028
- /* @__PURE__ */ jsxDEV8(Text8, {
21217
+ /* @__PURE__ */ jsxDEV10(Text10, {
21029
21218
  color: theme.muted,
21030
21219
  children: [
21031
21220
  " · ",
21032
21221
  model2
21033
21222
  ]
21034
21223
  }, undefined, true, undefined, this),
21035
- /* @__PURE__ */ jsxDEV8(Text8, {
21224
+ reasoningEffort && /* @__PURE__ */ jsxDEV10(Text10, {
21225
+ color: theme.muted,
21226
+ children: [
21227
+ " · ",
21228
+ reasoningEffort
21229
+ ]
21230
+ }, undefined, true, undefined, this),
21231
+ /* @__PURE__ */ jsxDEV10(Text10, {
21036
21232
  color: theme.muted,
21037
21233
  children: [
21038
21234
  " · $",
21039
21235
  costUsd.toFixed(4)
21040
21236
  ]
21041
21237
  }, undefined, true, undefined, this),
21042
- context2.window > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21238
+ context2.window > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21043
21239
  color: used >= 80 ? theme.warning : theme.muted,
21044
21240
  children: [
21045
21241
  " · ",
@@ -21047,7 +21243,7 @@ function StatusLine({
21047
21243
  "% ctx"
21048
21244
  ]
21049
21245
  }, undefined, true, undefined, this),
21050
- compacted > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21246
+ compacted > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21051
21247
  color: theme.muted,
21052
21248
  children: [
21053
21249
  " · ",
@@ -21055,7 +21251,7 @@ function StatusLine({
21055
21251
  " summarised"
21056
21252
  ]
21057
21253
  }, undefined, true, undefined, this),
21058
- todos.length > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21254
+ todos.length > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21059
21255
  color: theme.muted,
21060
21256
  children: [
21061
21257
  " ",
@@ -21066,11 +21262,11 @@ function StatusLine({
21066
21262
  current ? ` ${truncate3(current.text, 40)}` : ""
21067
21263
  ]
21068
21264
  }, undefined, true, undefined, this),
21069
- busy && /* @__PURE__ */ jsxDEV8(Text8, {
21265
+ busy && /* @__PURE__ */ jsxDEV10(Text10, {
21070
21266
  color: theme.warning,
21071
21267
  children: " · working (esc to interrupt)"
21072
21268
  }, undefined, false, undefined, this),
21073
- queued > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21269
+ queued > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21074
21270
  color: theme.accent,
21075
21271
  children: [
21076
21272
  " · ",
@@ -21086,8 +21282,8 @@ function truncate3(value, max) {
21086
21282
  }
21087
21283
 
21088
21284
  // packages/tui/src/components/tool-block.tsx
21089
- import { Box as Box8, Text as Text9 } from "ink";
21090
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21285
+ import { Box as Box9, Text as Text11 } from "ink";
21286
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
21091
21287
  var PREVIEW_LINES = 8;
21092
21288
  function ToolBlock({
21093
21289
  name,
@@ -21106,27 +21302,27 @@ function ToolBlock({
21106
21302
  const showAll = expanded || isError;
21107
21303
  const shown = showAll ? lines : lines.slice(0, PREVIEW_LINES);
21108
21304
  const hidden = lines.length - shown.length;
21109
- return /* @__PURE__ */ jsxDEV9(Box8, {
21305
+ return /* @__PURE__ */ jsxDEV11(Box9, {
21110
21306
  flexDirection: "column",
21111
21307
  marginTop: 1,
21112
21308
  children: [
21113
- /* @__PURE__ */ jsxDEV9(Text9, {
21309
+ /* @__PURE__ */ jsxDEV11(Text11, {
21114
21310
  color,
21115
21311
  children: [
21116
21312
  marker2,
21117
21313
  " ",
21118
- /* @__PURE__ */ jsxDEV9(Text9, {
21314
+ /* @__PURE__ */ jsxDEV11(Text11, {
21119
21315
  bold: true,
21120
21316
  children: name
21121
21317
  }, undefined, false, undefined, this),
21122
- title ? /* @__PURE__ */ jsxDEV9(Text9, {
21318
+ title ? /* @__PURE__ */ jsxDEV11(Text11, {
21123
21319
  color: theme.muted,
21124
21320
  children: [
21125
21321
  " ",
21126
21322
  title
21127
21323
  ]
21128
21324
  }, undefined, true, undefined, this) : null,
21129
- isDiff && !showAll ? /* @__PURE__ */ jsxDEV9(Text9, {
21325
+ isDiff && !showAll ? /* @__PURE__ */ jsxDEV11(Text11, {
21130
21326
  color: theme.muted,
21131
21327
  children: [
21132
21328
  " ",
@@ -21135,12 +21331,12 @@ function ToolBlock({
21135
21331
  }, undefined, true, undefined, this) : null
21136
21332
  ]
21137
21333
  }, undefined, true, undefined, this),
21138
- isDiff && showAll ? /* @__PURE__ */ jsxDEV9(Box8, {
21334
+ isDiff && showAll ? /* @__PURE__ */ jsxDEV11(Box9, {
21139
21335
  marginLeft: 2,
21140
- children: /* @__PURE__ */ jsxDEV9(DiffView, {
21336
+ children: /* @__PURE__ */ jsxDEV11(DiffView, {
21141
21337
  diff: output
21142
21338
  }, undefined, false, undefined, this)
21143
- }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV9(Text9, {
21339
+ }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV11(Text11, {
21144
21340
  color: theme.muted,
21145
21341
  wrap: "truncate-end",
21146
21342
  children: [
@@ -21148,7 +21344,7 @@ function ToolBlock({
21148
21344
  line
21149
21345
  ]
21150
21346
  }, index, true, undefined, this)),
21151
- hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV9(Text9, {
21347
+ hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV11(Text11, {
21152
21348
  color: theme.muted,
21153
21349
  children: [
21154
21350
  " ",
@@ -21161,1295 +21357,1640 @@ function ToolBlock({
21161
21357
  }, undefined, true, undefined, this);
21162
21358
  }
21163
21359
 
21164
- // packages/tui/src/app.tsx
21165
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
21166
- var sequence = 0;
21167
- var nextId = () => `item_${sequence++}`;
21168
- function promptLabel(prompt) {
21169
- if (typeof prompt === "string")
21170
- return prompt;
21171
- return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
21172
- `);
21173
- }
21174
- function App({ session: session2, model: initialModel, initialPrompt }) {
21360
+ // packages/tui/src/onboarding.tsx
21361
+ import { Box as Box10, Text as Text12, useApp, useInput as useInput5 } from "ink";
21362
+ import { useCallback, useRef as useRef2, useState as useState5 } from "react";
21363
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
21364
+ var EFFORTS = ["auto", "none", "low", "medium", "high", "xhigh"];
21365
+ function Onboarding({
21366
+ onDone,
21367
+ embedded = false,
21368
+ defaultScope = "global",
21369
+ ...rest
21370
+ }) {
21175
21371
  const { exit } = useApp();
21176
- const agent3 = session2.agent;
21177
- const [model2, setModel] = useState3(initialModel);
21178
- const [items, setItems] = useState3(() => session2.problems.map((problem) => ({
21179
- kind: "notice",
21180
- id: nextId(),
21181
- text: `warning: ${problem}`,
21182
- color: theme.warning
21183
- })));
21184
- const [live, setLive] = useState3("");
21185
- const [runningTool, setRunningTool] = useState3();
21186
- const [input, setInput] = useState3("");
21187
- const [menuIndex, setMenuIndex] = useState3(0);
21188
- const [busy, setBusy] = useState3(false);
21189
- const [mode, setMode] = useState3(agent3.permissionMode);
21190
- const [cost, setCost] = useState3(0);
21191
- const [todos, setTodos] = useState3([]);
21192
- const [queued, setQueued] = useState3(0);
21193
- const [candidate, setCandidate] = useState3();
21194
- const [context2, setContext] = useState3(() => agent3.contextUse);
21195
- const [compacted, setCompacted] = useState3(0);
21196
- const [pending, setPending] = useState3();
21197
- const [question, setQuestion] = useState3();
21198
- const controller = useRef2(undefined);
21199
- const push = useCallback((item) => setItems((current) => [...current, item]), []);
21200
- const pendingRef = useRef2(setPending);
21201
- pendingRef.current = setPending;
21202
- const questionRef = useRef2(setQuestion);
21203
- questionRef.current = setQuestion;
21204
- useEffect2(() => {
21205
- session2.installPrompt((request, reason) => new Promise((resolve4) => {
21206
- pendingRef.current({ request, reason, resolve: resolve4 });
21207
- }));
21208
- session2.installAsk((q, options) => new Promise((resolve4) => {
21209
- questionRef.current({ question: q, ...options ? { options } : {}, resolve: resolve4 });
21210
- }));
21211
- }, [session2]);
21212
- const modeBeforePlan = useRef2(undefined);
21213
- const lastAssistantText = useRef2("");
21214
- const runTurn = useCallback(async (prompt) => {
21215
- setBusy(true);
21216
- lastAssistantText.current = "";
21217
- push({ kind: "user", id: nextId(), text: promptLabel(prompt) });
21218
- const abort = new AbortController;
21219
- controller.current = abort;
21220
- let assistantText = "";
21372
+ const options = useRef2(rest);
21373
+ options.current = rest;
21374
+ const [screen, setScreen] = useState5({ name: "choose" });
21375
+ const [cursor, setCursor] = useState5(0);
21376
+ const [input, setInput] = useState5("");
21377
+ const [error, setError] = useState5();
21378
+ const secret = useRef2("");
21379
+ const providers = order(rest.providers, rest.wanted);
21380
+ const finish = useCallback((result) => {
21381
+ onDone(result);
21382
+ if (!embedded)
21383
+ exit();
21384
+ }, [embedded, exit, onDone]);
21385
+ const complete = useCallback((provider, model2, scope2 = defaultScope, effort) => finish({
21386
+ outcome: "ready",
21387
+ providerId: provider.id,
21388
+ model: `${provider.id}/${model2.id}`,
21389
+ scope: scope2,
21390
+ ...effort ? { reasoningEffort: effort } : {}
21391
+ }), [defaultScope, finish]);
21392
+ const readyForEffort = useCallback((provider, model2) => {
21393
+ const remembered = options.current.reasoningFor?.(`${provider.id}/${model2.id}`);
21394
+ setCursor(remembered ? Math.max(0, EFFORTS.indexOf(remembered)) : 0);
21395
+ if (model2.reasoning)
21396
+ setScreen({ name: "effort", provider, model: model2 });
21397
+ else
21398
+ complete(provider, model2);
21399
+ }, [complete]);
21400
+ const verify2 = useCallback(async (provider, model2) => {
21401
+ setScreen({ name: "probing", provider, model: model2 });
21402
+ const result = await options.current.probe(provider.id, model2.id);
21403
+ if (result.ok) {
21404
+ readyForEffort(provider, model2);
21405
+ return;
21406
+ }
21407
+ if (result.reason === "rejected")
21408
+ await options.current.forgetKey(provider.id).catch(() => {});
21409
+ setScreen({ name: "failed", provider, model: model2, result });
21410
+ }, [readyForEffort]);
21411
+ const submitKey = useCallback(async () => {
21412
+ const selected = screen.name === "key" ? screen : undefined;
21413
+ const key2 = secret.current;
21414
+ secret.current = "";
21415
+ setInput("");
21416
+ if (!selected)
21417
+ return;
21418
+ if (key2.trim() === "") {
21419
+ setError("a key is needed, or press esc to go back");
21420
+ return;
21421
+ }
21422
+ setError(undefined);
21423
+ await options.current.storeKey(selected.provider.id, key2.trim());
21424
+ await verify2(selected.provider, selected.model);
21425
+ }, [screen, verify2]);
21426
+ const startSignIn = useCallback(async (provider, model2) => {
21427
+ setScreen({ name: "oauth", provider, model: model2 });
21221
21428
  try {
21222
- for await (const event of agent3.runTurn(prompt, abort.signal)) {
21223
- switch (event.type) {
21224
- case "text_delta":
21225
- assistantText += event.text;
21226
- setLive(assistantText);
21227
- break;
21228
- case "intent":
21229
- if (event.text === undefined) {
21230
- push({
21231
- kind: "notice",
21232
- id: nextId(),
21233
- text: `about to run ${event.calls} tool call${event.calls === 1 ? "" : "s"} without saying why`,
21234
- color: theme.warning
21235
- });
21236
- }
21237
- break;
21238
- case "tool_start":
21239
- if (assistantText.trim() !== "") {
21240
- push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
21241
- assistantText = "";
21242
- setLive("");
21243
- }
21244
- setRunningTool(event.call.toolName);
21245
- break;
21246
- case "tool_end": {
21247
- setRunningTool(undefined);
21248
- const output = event.result.output.type === "text" ? event.result.output.value : "";
21249
- push({
21250
- kind: "tool",
21251
- id: nextId(),
21252
- name: event.toolName,
21253
- ...event.result.title ? { title: event.result.title } : {},
21254
- output,
21255
- ...event.result.isError ? { isError: true } : {}
21256
- });
21257
- setTodos(agent3.todos.list());
21258
- break;
21259
- }
21260
- case "usage":
21261
- setCost(agent3.costUsd);
21262
- setContext(agent3.contextUse);
21263
- break;
21264
- case "budget":
21265
- push({
21266
- kind: "notice",
21267
- id: nextId(),
21268
- text: event.raisedTo === undefined ? `stopped: $${event.spentUsd.toFixed(2)} spent against a ` + `$${event.limitUsd.toFixed(2)} budget` : `budget raised to $${event.raisedTo.toFixed(2)} after ` + `$${event.spentUsd.toFixed(2)} spent`,
21269
- color: theme.warning
21270
- });
21271
- break;
21272
- case "verification":
21273
- push({
21274
- kind: "tool",
21275
- id: nextId(),
21276
- name: event.result.command,
21277
- title: `${event.result.command} - exit ${event.result.exitCode ?? "killed"}`,
21278
- output: event.result.output,
21279
- ...event.result.exitCode === 0 ? {} : { isError: true }
21280
- });
21281
- break;
21282
- case "subagent":
21283
- push({
21284
- kind: "notice",
21285
- id: nextId(),
21286
- text: `subagent "${event.description}": ${event.steps} step${event.steps === 1 ? "" : "s"}, $${event.costUsd.toFixed(4)}`
21287
- });
21288
- break;
21289
- case "hook":
21290
- if (event.blocked) {
21291
- push({
21292
- kind: "notice",
21293
- id: nextId(),
21294
- text: `${event.event} hook blocked this: ${event.blocked}`,
21295
- color: theme.warning
21296
- });
21297
- }
21298
- for (const problem of event.problems) {
21299
- push({ kind: "notice", id: nextId(), text: problem, color: theme.warning });
21300
- }
21301
- break;
21302
- case "compacted":
21303
- setCompacted((count) => count + event.replaced);
21304
- push({
21305
- kind: "notice",
21306
- id: nextId(),
21307
- text: `compacted: ${event.replaced} earlier messages are now a summary`
21308
- });
21309
- break;
21310
- case "error":
21311
- push({
21312
- kind: "notice",
21313
- id: nextId(),
21314
- text: `error: ${event.error.message}`,
21315
- color: theme.danger
21316
- });
21317
- break;
21318
- case "turn_end":
21319
- if (event.reason === "aborted") {
21320
- push({
21321
- kind: "notice",
21322
- id: nextId(),
21323
- text: "interrupted",
21324
- color: theme.warning
21325
- });
21326
- }
21327
- if (event.reason === "max_steps") {
21328
- push({
21329
- kind: "notice",
21330
- id: nextId(),
21331
- text: "stopped: step limit reached",
21332
- color: theme.warning
21333
- });
21334
- }
21335
- break;
21336
- default:
21337
- break;
21338
- }
21339
- setQueued(agent3.pendingSteers);
21340
- }
21341
- } finally {
21342
- if (assistantText.trim() !== "") {
21343
- lastAssistantText.current = assistantText;
21344
- push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
21345
- }
21346
- setLive("");
21347
- setRunningTool(undefined);
21348
- setBusy(false);
21349
- setQueued(agent3.pendingSteers);
21350
- controller.current = undefined;
21351
- }
21352
- }, [agent3, push]);
21353
- const started = useRef2(false);
21354
- useEffect2(() => {
21355
- if (started.current || !initialPrompt)
21356
- return;
21357
- started.current = true;
21358
- runTurn(initialPrompt);
21359
- }, [initialPrompt, runTurn]);
21360
- const showMemories = useCallback(async (argument) => {
21361
- const [verb, ...rest] = (argument ?? "").split(/\s+/);
21362
- const id = rest.join(" ").trim();
21363
- if (verb === "forget" && id) {
21364
- const gone = await deleteMemory(id, agent3.cwd);
21365
- if (gone)
21366
- await refreshSystemPrompt(agent3, model2, session2.skills);
21367
- push({
21368
- kind: "notice",
21369
- id: nextId(),
21370
- text: gone ? `forgot ${id}` : `no memory called "${id}"`,
21371
- ...gone ? {} : { color: theme.warning }
21429
+ await options.current.signIn(provider.id, (url) => setScreen({ name: "oauth", provider, model: model2, url }));
21430
+ } catch (failure) {
21431
+ setScreen({
21432
+ name: "failed",
21433
+ provider,
21434
+ model: model2,
21435
+ result: { ok: false, reason: "other", message: failure.message }
21372
21436
  });
21373
21437
  return;
21374
21438
  }
21375
- const memories = await loadMemories(agent3.cwd);
21376
- if (memories.length === 0) {
21377
- push({ kind: "notice", id: nextId(), text: "nothing remembered yet" });
21439
+ await verify2(provider, model2);
21440
+ }, [verify2]);
21441
+ const chooseModel = useCallback((provider, model2) => {
21442
+ setInput("");
21443
+ setError(undefined);
21444
+ if (provider.configured)
21445
+ readyForEffort(provider, model2);
21446
+ else if (provider.kind === "oauth")
21447
+ startSignIn(provider, model2);
21448
+ else
21449
+ setScreen({ name: "key", provider, model: model2 });
21450
+ }, [readyForEffort, startSignIn]);
21451
+ const chooseProvider = useCallback((provider) => {
21452
+ setInput("");
21453
+ setError(undefined);
21454
+ setCursor(preferredModelIndex(provider.models, rest.wantedModel));
21455
+ setScreen({ name: "model", provider });
21456
+ }, [rest.wantedModel]);
21457
+ useInput5((key2, meta) => {
21458
+ if (meta.ctrl && key2 === "c") {
21459
+ finish({ outcome: "quit" });
21378
21460
  return;
21379
21461
  }
21380
- const lines = memories.map((memory2) => {
21381
- const when = memory2.created.slice(0, 10);
21382
- const why = memory2.source ? `
21383
- from "${memory2.source}" on ${when}` : "";
21384
- return ` [${memory2.id}] (${memory2.scope}) ${memory2.text}${why}`;
21385
- });
21386
- push({
21387
- kind: "notice",
21388
- id: nextId(),
21389
- text: `${lines.join(`
21390
- `)}
21391
-
21392
- /memory forget <id> removes one`
21393
- });
21394
- }, [agent3, model2, push, session2.skills]);
21395
- const remember = useCallback(async (scope2) => {
21396
- if (!candidate)
21397
- return;
21398
- setCandidate(undefined);
21399
- const saved = await saveMemory({ ...candidate, scope: scope2 }, agent3.cwd).catch(() => {
21400
- return;
21401
- });
21402
- if (!saved) {
21403
- push({ kind: "notice", id: nextId(), text: "could not save that", color: theme.warning });
21462
+ if (screen.name === "choose") {
21463
+ if (key2 === "q") {
21464
+ finish({ outcome: "quit" });
21465
+ return;
21466
+ }
21467
+ if (meta.upArrow)
21468
+ setCursor((c) => c <= 0 ? providers.length - 1 : c - 1);
21469
+ if (meta.downArrow)
21470
+ setCursor((c) => c >= providers.length - 1 ? 0 : c + 1);
21471
+ if (meta.return) {
21472
+ const provider = providers[cursor];
21473
+ if (provider)
21474
+ chooseProvider(provider);
21475
+ }
21404
21476
  return;
21405
21477
  }
21406
- await refreshSystemPrompt(agent3, model2, session2.skills);
21407
- push({
21408
- kind: "notice",
21409
- id: nextId(),
21410
- text: `remembered [${saved.id}] (${scope2}) - /memory to review or forget it`
21411
- });
21412
- }, [agent3, candidate, model2, push, session2.skills]);
21413
- const sessionTree = useCallback(async (name, argument) => {
21414
- const entries2 = await session2.branch();
21415
- const prompts = entries2.filter((entry) => entry.type === "message" && entry.message.role === "user" && entry.message.content.some((part) => part.type === "text" && !part.text.startsWith("<self-check>")));
21416
- if (name === "tree" || !argument) {
21417
- if (prompts.length === 0) {
21418
- push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
21478
+ if (screen.name === "model") {
21479
+ if (meta.escape) {
21480
+ setInput("");
21481
+ setError(undefined);
21482
+ setCursor(0);
21483
+ setScreen({ name: "choose" });
21419
21484
  return;
21420
21485
  }
21421
- const lines = prompts.map((entry, index2) => {
21422
- const text2 = entry.type === "message" ? entry.message.content.find((part) => part.type === "text")?.text ?? "" : "";
21423
- return ` ${index2 + 1}. ${text2.split(`
21424
- `)[0]?.slice(0, 70) ?? ""}`;
21425
- });
21426
- push({
21427
- kind: "notice",
21428
- id: nextId(),
21429
- text: `${lines.join(`
21430
- `)}
21431
-
21432
- /rewind <n> goes back to one · /fork <n> branches from it`
21433
- });
21486
+ const matches3 = matchingModels(screen.provider.models, input);
21487
+ if (embedded && meta.ctrl && key2 === "g") {
21488
+ const selected = matches3[cursor];
21489
+ if (selected) {
21490
+ if (selected.reasoning) {
21491
+ setCursor(0);
21492
+ setScreen({ name: "effort", provider: screen.provider, model: selected });
21493
+ } else
21494
+ complete(screen.provider, selected, "global");
21495
+ }
21496
+ return;
21497
+ }
21498
+ if (meta.upArrow)
21499
+ setCursor((c) => c <= 0 ? Math.max(0, matches3.length - 1) : c - 1);
21500
+ if (meta.downArrow)
21501
+ setCursor((c) => c >= matches3.length - 1 ? 0 : c + 1);
21434
21502
  return;
21435
21503
  }
21436
- const index = Number.parseInt(argument, 10) - 1;
21437
- const target = prompts[index];
21438
- if (!target) {
21439
- push({
21440
- kind: "notice",
21441
- id: nextId(),
21442
- text: `no prompt ${argument} in this session - /tree lists them`,
21443
- color: theme.warning
21444
- });
21504
+ if (screen.name === "effort") {
21505
+ if (meta.escape) {
21506
+ setCursor(preferredModelIndex(screen.provider.models, rest.wantedModel));
21507
+ setScreen({ name: "model", provider: screen.provider });
21508
+ return;
21509
+ }
21510
+ if (meta.upArrow)
21511
+ setCursor((c) => c <= 0 ? EFFORTS.length - 1 : c - 1);
21512
+ if (meta.downArrow)
21513
+ setCursor((c) => c >= EFFORTS.length - 1 ? 0 : c + 1);
21514
+ const selected = EFFORTS[cursor] ?? "auto";
21515
+ if (meta.return || embedded && key2 === "g") {
21516
+ complete(screen.provider, screen.model, embedded && key2 === "g" ? "global" : defaultScope, selected === "auto" ? undefined : selected);
21517
+ }
21445
21518
  return;
21446
21519
  }
21447
- const previous = entries2[entries2.indexOf(target) - 1] ?? target;
21448
- if (name === "rewind") {
21449
- const kept = await session2.rewindTo(previous.id);
21450
- push({
21451
- kind: "notice",
21452
- id: nextId(),
21453
- text: `rewound to before prompt ${index + 1}; ${kept} message${kept === 1 ? "" : "s"} kept. Nothing was deleted - the rest is still in the transcript as another branch.`
21454
- });
21520
+ if (screen.name === "key" && !meta.escape) {
21521
+ if (meta.return) {
21522
+ submitKey();
21523
+ return;
21524
+ }
21525
+ if (meta.backspace || meta.delete) {
21526
+ secret.current = secret.current.slice(0, -1);
21527
+ setInput(secret.current);
21528
+ return;
21529
+ }
21530
+ if (meta.ctrl || meta.meta || meta.tab)
21531
+ return;
21532
+ if (key2) {
21533
+ secret.current += key2;
21534
+ setInput(secret.current);
21535
+ }
21455
21536
  return;
21456
21537
  }
21457
- const forked = await session2.fork(previous.id);
21458
- push({
21459
- kind: "notice",
21460
- id: nextId(),
21461
- text: forked ? `forked from prompt ${index + 1} into ${forked}; this session continues there and the original is untouched` : "could not fork this session",
21462
- ...forked ? {} : { color: theme.warning }
21463
- });
21464
- }, [push, session2]);
21465
- const undoLast = useCallback(async () => {
21466
- const result = await session2.undo();
21467
- if (!result) {
21468
- push({ kind: "notice", id: nextId(), text: "nothing to undo", color: theme.warning });
21538
+ if (meta.escape) {
21539
+ secret.current = "";
21540
+ setInput("");
21541
+ setError(undefined);
21542
+ setScreen(screen.name === "key" || screen.name === "oauth" || screen.name === "failed" ? { name: "model", provider: screen.provider } : { name: "choose" });
21469
21543
  return;
21470
21544
  }
21471
- const created2 = result.wasCreated.length ? ` Left in place because the batch created them: ${result.wasCreated.join(", ")}.` : "";
21472
- push({
21473
- kind: "notice",
21474
- id: nextId(),
21475
- text: result.restored.length ? `undid ${result.label}: restored ${result.restored.join(", ")}.${created2}` : `nothing to restore from ${result.label}.${created2}`
21476
- });
21477
- }, [push, session2]);
21478
- const plan2 = useCallback(async (argument) => {
21479
- const path = planPath(session2.store?.id ?? "scratch");
21480
- const [verb = "", ...rest] = (argument ?? "").split(/\s+/);
21481
- const task2 = [verb, ...rest].join(" ").trim();
21482
- if (verb === "show") {
21483
- const text2 = await readPlan(path);
21484
- push({
21485
- kind: "notice",
21486
- id: nextId(),
21487
- text: text2 ? `${path}
21488
-
21489
- ${text2}` : `no plan yet at ${path}`
21490
- });
21491
- return;
21545
+ if (screen.name === "failed" && key2 === "k" && screen.result.reason === "unreachable") {
21546
+ readyForEffort(screen.provider, screen.model);
21492
21547
  }
21493
- if (verb === "edit") {
21494
- const result = await openInEditor(path);
21495
- push({
21496
- kind: "notice",
21497
- id: nextId(),
21498
- text: result.message,
21499
- ...result.edited ? {} : { color: theme.warning }
21500
- });
21501
- return;
21502
- }
21503
- if (verb === "approve") {
21504
- const text2 = await readPlan(path);
21505
- if (!text2) {
21506
- push({
21507
- kind: "notice",
21508
- id: nextId(),
21509
- text: `there is no plan at ${path} to approve`,
21510
- color: theme.warning
21511
- });
21512
- return;
21548
+ });
21549
+ if (screen.name === "choose") {
21550
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21551
+ flexDirection: "column",
21552
+ children: [
21553
+ /* @__PURE__ */ jsxDEV12(Header, {
21554
+ embedded
21555
+ }, undefined, false, undefined, this),
21556
+ providers.map((provider, index) => /* @__PURE__ */ jsxDEV12(Box10, {
21557
+ children: [
21558
+ /* @__PURE__ */ jsxDEV12(Text12, {
21559
+ color: index === cursor ? theme.user : theme.muted,
21560
+ children: index === cursor ? "› " : " "
21561
+ }, undefined, false, undefined, this),
21562
+ /* @__PURE__ */ jsxDEV12(Text12, {
21563
+ ...index === cursor ? { color: theme.user } : {},
21564
+ children: provider.id.padEnd(14)
21565
+ }, undefined, false, undefined, this),
21566
+ /* @__PURE__ */ jsxDEV12(Text12, {
21567
+ color: theme.muted,
21568
+ children: hint(provider)
21569
+ }, undefined, false, undefined, this)
21570
+ ]
21571
+ }, provider.id, true, undefined, this)),
21572
+ /* @__PURE__ */ jsxDEV12(Box10, {
21573
+ marginTop: 1,
21574
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21575
+ color: theme.muted,
21576
+ children: "↑↓ choose · enter select · q quit"
21577
+ }, undefined, false, undefined, this)
21578
+ }, undefined, false, undefined, this),
21579
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21580
+ color: theme.warning,
21581
+ children: error
21582
+ }, undefined, false, undefined, this) : null
21583
+ ]
21584
+ }, undefined, true, undefined, this);
21585
+ }
21586
+ if (screen.name === "model") {
21587
+ const matches3 = matchingModels(screen.provider.models, input);
21588
+ const selected = matches3[cursor];
21589
+ const start = Math.max(0, Math.min(cursor - 3, matches3.length - 8));
21590
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21591
+ flexDirection: "column",
21592
+ children: [
21593
+ /* @__PURE__ */ jsxDEV12(Header, {
21594
+ embedded
21595
+ }, undefined, false, undefined, this),
21596
+ /* @__PURE__ */ jsxDEV12(Text12, {
21597
+ children: [
21598
+ "choose a model from ",
21599
+ screen.provider.id,
21600
+ " (",
21601
+ matches3.length,
21602
+ " matching)"
21603
+ ]
21604
+ }, undefined, true, undefined, this),
21605
+ /* @__PURE__ */ jsxDEV12(Box10, {
21606
+ children: [
21607
+ /* @__PURE__ */ jsxDEV12(Text12, {
21608
+ color: theme.user,
21609
+ children: "> "
21610
+ }, undefined, false, undefined, this),
21611
+ /* @__PURE__ */ jsxDEV12(TextInput, {
21612
+ value: input,
21613
+ onChange: (value) => {
21614
+ setInput(value);
21615
+ setCursor(0);
21616
+ setError(undefined);
21617
+ },
21618
+ onSubmit: () => {
21619
+ if (selected)
21620
+ chooseModel(screen.provider, selected);
21621
+ else
21622
+ setError("no model matches that search");
21623
+ },
21624
+ placeholder: "type to filter models"
21625
+ }, undefined, false, undefined, this)
21626
+ ]
21627
+ }, undefined, true, undefined, this),
21628
+ /* @__PURE__ */ jsxDEV12(Box10, {
21629
+ flexDirection: "column",
21630
+ marginTop: 1,
21631
+ children: [
21632
+ matches3.slice(start, start + 8).map((model2) => /* @__PURE__ */ jsxDEV12(Text12, {
21633
+ color: model2 === selected ? theme.user : theme.muted,
21634
+ children: [
21635
+ model2 === selected ? "› " : " ",
21636
+ model2.id,
21637
+ " · ",
21638
+ model2.name
21639
+ ]
21640
+ }, model2.id, true, undefined, this)),
21641
+ matches3.length === 0 ? /* @__PURE__ */ jsxDEV12(Text12, {
21642
+ color: theme.muted,
21643
+ children: "no matching models"
21644
+ }, undefined, false, undefined, this) : null
21645
+ ]
21646
+ }, undefined, true, undefined, this),
21647
+ /* @__PURE__ */ jsxDEV12(Box10, {
21648
+ marginTop: 1,
21649
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21650
+ color: theme.muted,
21651
+ children: "type filter · ↑↓ choose · enter select · esc back"
21652
+ }, undefined, false, undefined, this)
21653
+ }, undefined, false, undefined, this),
21654
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21655
+ color: theme.warning,
21656
+ children: error
21657
+ }, undefined, false, undefined, this) : null
21658
+ ]
21659
+ }, undefined, true, undefined, this);
21660
+ }
21661
+ if (screen.name === "key") {
21662
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21663
+ flexDirection: "column",
21664
+ children: [
21665
+ /* @__PURE__ */ jsxDEV12(Header, {
21666
+ embedded
21667
+ }, undefined, false, undefined, this),
21668
+ /* @__PURE__ */ jsxDEV12(Text12, {
21669
+ children: [
21670
+ "paste an api key for ",
21671
+ screen.provider.id
21672
+ ]
21673
+ }, undefined, true, undefined, this),
21674
+ /* @__PURE__ */ jsxDEV12(Box10, {
21675
+ marginTop: 1,
21676
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21677
+ color: theme.muted,
21678
+ children: [
21679
+ screen.provider.envVars?.length ? `or set ${screen.provider.envVars.join(" or ")} instead and restart.
21680
+ ` : "",
21681
+ "stored in your config directory, readable only by you, and never printed."
21682
+ ]
21683
+ }, undefined, true, undefined, this)
21684
+ }, undefined, false, undefined, this),
21685
+ /* @__PURE__ */ jsxDEV12(Box10, {
21686
+ marginTop: 1,
21687
+ children: [
21688
+ /* @__PURE__ */ jsxDEV12(Text12, {
21689
+ color: theme.user,
21690
+ children: "> "
21691
+ }, undefined, false, undefined, this),
21692
+ /* @__PURE__ */ jsxDEV12(Text12, {
21693
+ children: "•".repeat(input.length)
21694
+ }, undefined, false, undefined, this),
21695
+ /* @__PURE__ */ jsxDEV12(Text12, {
21696
+ inverse: true,
21697
+ children: " "
21698
+ }, undefined, false, undefined, this)
21699
+ ]
21700
+ }, undefined, true, undefined, this),
21701
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21702
+ color: theme.warning,
21703
+ children: error
21704
+ }, undefined, false, undefined, this) : null,
21705
+ /* @__PURE__ */ jsxDEV12(Box10, {
21706
+ marginTop: 1,
21707
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21708
+ color: theme.muted,
21709
+ children: "enter continue · esc back"
21710
+ }, undefined, false, undefined, this)
21711
+ }, undefined, false, undefined, this)
21712
+ ]
21713
+ }, undefined, true, undefined, this);
21714
+ }
21715
+ if (screen.name === "oauth") {
21716
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21717
+ flexDirection: "column",
21718
+ children: [
21719
+ /* @__PURE__ */ jsxDEV12(Header, {
21720
+ embedded
21721
+ }, undefined, false, undefined, this),
21722
+ /* @__PURE__ */ jsxDEV12(Text12, {
21723
+ children: [
21724
+ "signing in to ",
21725
+ screen.provider.id,
21726
+ " in your browser…"
21727
+ ]
21728
+ }, undefined, true, undefined, this),
21729
+ screen.url ? /* @__PURE__ */ jsxDEV12(Box10, {
21730
+ marginTop: 1,
21731
+ flexDirection: "column",
21732
+ children: [
21733
+ /* @__PURE__ */ jsxDEV12(Text12, {
21734
+ color: theme.muted,
21735
+ children: "if it did not open, use this link:"
21736
+ }, undefined, false, undefined, this),
21737
+ /* @__PURE__ */ jsxDEV12(Text12, {
21738
+ children: screen.url
21739
+ }, undefined, false, undefined, this)
21740
+ ]
21741
+ }, undefined, true, undefined, this) : null,
21742
+ /* @__PURE__ */ jsxDEV12(Box10, {
21743
+ marginTop: 1,
21744
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21745
+ color: theme.muted,
21746
+ children: "esc cancel"
21747
+ }, undefined, false, undefined, this)
21748
+ }, undefined, false, undefined, this)
21749
+ ]
21750
+ }, undefined, true, undefined, this);
21751
+ }
21752
+ if (screen.name === "probing") {
21753
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21754
+ flexDirection: "column",
21755
+ children: [
21756
+ /* @__PURE__ */ jsxDEV12(Header, {
21757
+ embedded
21758
+ }, undefined, false, undefined, this),
21759
+ /* @__PURE__ */ jsxDEV12(Text12, {
21760
+ color: theme.muted,
21761
+ children: [
21762
+ "checking the credentials with ",
21763
+ screen.provider.id,
21764
+ "…"
21765
+ ]
21766
+ }, undefined, true, undefined, this)
21767
+ ]
21768
+ }, undefined, true, undefined, this);
21769
+ }
21770
+ if (screen.name === "failed") {
21771
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21772
+ flexDirection: "column",
21773
+ children: [
21774
+ /* @__PURE__ */ jsxDEV12(Header, {
21775
+ embedded
21776
+ }, undefined, false, undefined, this),
21777
+ /* @__PURE__ */ jsxDEV12(Text12, {
21778
+ color: theme.warning,
21779
+ children: screen.result.reason === "rejected" ? `${screen.provider.id} rejected that credential. It has not been kept.` : screen.result.reason === "unreachable" ? `could not reach ${screen.provider.id}.` : `${screen.provider.id} said:`
21780
+ }, undefined, false, undefined, this),
21781
+ /* @__PURE__ */ jsxDEV12(Text12, {
21782
+ color: theme.muted,
21783
+ children: screen.result.message
21784
+ }, undefined, false, undefined, this),
21785
+ /* @__PURE__ */ jsxDEV12(Box10, {
21786
+ marginTop: 1,
21787
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21788
+ color: theme.muted,
21789
+ children: screen.result.reason === "unreachable" ? "k keep it anyway and carry on · esc start over · ctrl-c quit" : "esc start over · ctrl-c quit"
21790
+ }, undefined, false, undefined, this)
21791
+ }, undefined, false, undefined, this)
21792
+ ]
21793
+ }, undefined, true, undefined, this);
21794
+ }
21795
+ if (screen.name === "effort")
21796
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21797
+ flexDirection: "column",
21798
+ children: [
21799
+ /* @__PURE__ */ jsxDEV12(Header, {
21800
+ embedded
21801
+ }, undefined, false, undefined, this),
21802
+ /* @__PURE__ */ jsxDEV12(Text12, {
21803
+ children: [
21804
+ "reasoning effort for ",
21805
+ screen.model.name
21806
+ ]
21807
+ }, undefined, true, undefined, this),
21808
+ /* @__PURE__ */ jsxDEV12(Box10, {
21809
+ flexDirection: "column",
21810
+ marginTop: 1,
21811
+ children: EFFORTS.map((effort, index) => /* @__PURE__ */ jsxDEV12(Text12, {
21812
+ color: index === cursor ? theme.user : theme.muted,
21813
+ children: [
21814
+ index === cursor ? "› " : " ",
21815
+ effort
21816
+ ]
21817
+ }, effort, true, undefined, this))
21818
+ }, undefined, false, undefined, this),
21819
+ /* @__PURE__ */ jsxDEV12(Text12, {
21820
+ color: theme.muted,
21821
+ children: [
21822
+ "↑↓ choose · enter use",
21823
+ embedded ? " here · g use everywhere" : "",
21824
+ " · esc back"
21825
+ ]
21826
+ }, undefined, true, undefined, this)
21827
+ ]
21828
+ }, undefined, true, undefined, this);
21829
+ return null;
21830
+ }
21831
+ function Header({ embedded }) {
21832
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21833
+ flexDirection: "column",
21834
+ marginBottom: 1,
21835
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21836
+ children: embedded ? "switch model" : "earshot needs a model provider before it can do anything."
21837
+ }, undefined, false, undefined, this)
21838
+ }, undefined, false, undefined, this);
21839
+ }
21840
+ function hint(provider) {
21841
+ if (provider.configured)
21842
+ return provider.configured;
21843
+ if (provider.kind === "oauth")
21844
+ return "sign in with a browser - no key to paste";
21845
+ return provider.envVars?.length ? provider.envVars.join(" or ") : "api key";
21846
+ }
21847
+ function matchingModels(models, query) {
21848
+ const wanted = query.trim().toLowerCase();
21849
+ if (!wanted)
21850
+ return models;
21851
+ return models.filter((model2) => model2.id.toLowerCase().includes(wanted) || model2.name.toLowerCase().includes(wanted));
21852
+ }
21853
+ function preferredModelIndex(models, wanted) {
21854
+ if (!wanted)
21855
+ return 0;
21856
+ const modelId = wanted.includes("/") ? wanted.slice(wanted.indexOf("/") + 1) : wanted;
21857
+ const index = models.findIndex((model2) => model2.id === wanted || model2.id === modelId);
21858
+ return Math.max(0, index);
21859
+ }
21860
+ function order(providers, wanted) {
21861
+ if (!wanted)
21862
+ return providers;
21863
+ return [
21864
+ ...providers.filter((provider) => provider.id === wanted),
21865
+ ...providers.filter((provider) => provider.id !== wanted)
21866
+ ];
21867
+ }
21868
+
21869
+ // packages/tui/src/sessions.tsx
21870
+ import { Box as Box11, Text as Text13, useApp as useApp2, useInput as useInput6 } from "ink";
21871
+ import { useMemo, useState as useState6 } from "react";
21872
+ import { jsxDEV as jsxDEV13 } from "react/jsx-dev-runtime";
21873
+ function SessionPicker({
21874
+ sessions,
21875
+ onDone,
21876
+ embedded = false
21877
+ }) {
21878
+ const { exit } = useApp2();
21879
+ const [query, setQuery] = useState6("");
21880
+ const [cursor, setCursor] = useState6(0);
21881
+ const matches3 = useMemo(() => {
21882
+ const wanted = query.trim().toLowerCase();
21883
+ return wanted ? sessions.filter((session2) => `${session2.preview} ${session2.model} ${session2.id}`.toLowerCase().includes(wanted)) : sessions;
21884
+ }, [query, sessions]);
21885
+ const finish = (path) => {
21886
+ onDone(path);
21887
+ if (!embedded)
21888
+ exit();
21889
+ };
21890
+ useInput6((input, key2) => {
21891
+ if (key2.escape || input === "q" && query === "")
21892
+ return finish();
21893
+ if (key2.upArrow)
21894
+ setCursor((value) => value <= 0 ? Math.max(0, matches3.length - 1) : value - 1);
21895
+ if (key2.downArrow)
21896
+ setCursor((value) => value >= matches3.length - 1 ? 0 : value + 1);
21897
+ });
21898
+ return /* @__PURE__ */ jsxDEV13(Box11, {
21899
+ flexDirection: "column",
21900
+ children: [
21901
+ /* @__PURE__ */ jsxDEV13(Text13, {
21902
+ children: "saved chats for this project"
21903
+ }, undefined, false, undefined, this),
21904
+ /* @__PURE__ */ jsxDEV13(Box11, {
21905
+ marginTop: 1,
21906
+ children: [
21907
+ /* @__PURE__ */ jsxDEV13(Text13, {
21908
+ color: theme.user,
21909
+ children: "> "
21910
+ }, undefined, false, undefined, this),
21911
+ /* @__PURE__ */ jsxDEV13(TextInput, {
21912
+ value: query,
21913
+ onChange: (value) => {
21914
+ setQuery(value);
21915
+ setCursor(0);
21916
+ },
21917
+ onSubmit: () => {
21918
+ const selected = matches3[cursor];
21919
+ if (selected)
21920
+ finish(selected.path);
21921
+ },
21922
+ placeholder: "filter chats"
21923
+ }, undefined, false, undefined, this)
21924
+ ]
21925
+ }, undefined, true, undefined, this),
21926
+ /* @__PURE__ */ jsxDEV13(Box11, {
21927
+ flexDirection: "column",
21928
+ marginTop: 1,
21929
+ children: [
21930
+ matches3.slice(Math.max(0, cursor - 4), Math.max(0, cursor - 4) + 9).map((session2) => /* @__PURE__ */ jsxDEV13(Text13, {
21931
+ color: session2 === matches3[cursor] ? theme.user : theme.muted,
21932
+ children: [
21933
+ session2 === matches3[cursor] ? "› " : " ",
21934
+ session2.preview.padEnd(42).slice(0, 42),
21935
+ " ",
21936
+ relativeTime(session2.updatedAt).padStart(8),
21937
+ " ",
21938
+ session2.model,
21939
+ " ",
21940
+ session2.id.slice(0, 8)
21941
+ ]
21942
+ }, session2.id, true, undefined, this)),
21943
+ matches3.length === 0 && /* @__PURE__ */ jsxDEV13(Text13, {
21944
+ color: theme.muted,
21945
+ children: "no saved chats match"
21946
+ }, undefined, false, undefined, this)
21947
+ ]
21948
+ }, undefined, true, undefined, this),
21949
+ /* @__PURE__ */ jsxDEV13(Box11, {
21950
+ marginTop: 1,
21951
+ children: /* @__PURE__ */ jsxDEV13(Text13, {
21952
+ color: theme.muted,
21953
+ children: "type filter · ↑↓ choose · enter resume · esc quit"
21954
+ }, undefined, false, undefined, this)
21955
+ }, undefined, false, undefined, this)
21956
+ ]
21957
+ }, undefined, true, undefined, this);
21958
+ }
21959
+ function relativeTime(updatedAt) {
21960
+ const seconds = Math.max(0, Math.floor((Date.now() - updatedAt) / 1000));
21961
+ if (seconds < 60)
21962
+ return "now";
21963
+ const minutes = Math.floor(seconds / 60);
21964
+ if (minutes < 60)
21965
+ return `${minutes}m ago`;
21966
+ const hours = Math.floor(minutes / 60);
21967
+ if (hours < 24)
21968
+ return `${hours}h ago`;
21969
+ return `${Math.floor(hours / 24)}d ago`;
21970
+ }
21971
+
21972
+ // packages/tui/src/app.tsx
21973
+ import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
21974
+ var sequence = 0;
21975
+ var nextId = () => `item_${sequence++}`;
21976
+ function promptLabel(prompt) {
21977
+ if (typeof prompt === "string")
21978
+ return prompt;
21979
+ return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
21980
+ `);
21981
+ }
21982
+ function App({
21983
+ session: session2,
21984
+ model: initialModel,
21985
+ initialPrompt,
21986
+ modelOptions,
21987
+ onResume
21988
+ }) {
21989
+ const { exit } = useApp3();
21990
+ const agent3 = session2.agent;
21991
+ const [model2, setModel] = useState7(initialModel);
21992
+ const [items, setItems] = useState7(() => session2.problems.map((problem) => ({
21993
+ kind: "notice",
21994
+ id: nextId(),
21995
+ text: `warning: ${problem}`,
21996
+ color: theme.warning
21997
+ })));
21998
+ const [live, setLive] = useState7("");
21999
+ const [reasoningLive, setReasoningLive] = useState7("");
22000
+ const [showThinking, setShowThinking] = useState7(true);
22001
+ const [activity, setActivity] = useState7();
22002
+ const [choosingModel, setChoosingModel] = useState7(false);
22003
+ const [choosingReasoning, setChoosingReasoning] = useState7(false);
22004
+ const [sessionChoices, setSessionChoices] = useState7();
22005
+ const [runningTool, setRunningTool] = useState7();
22006
+ const [input, setInput] = useState7("");
22007
+ const [menuIndex, setMenuIndex] = useState7(0);
22008
+ const [busy, setBusy] = useState7(false);
22009
+ const [mode, setMode] = useState7(agent3.permissionMode);
22010
+ const [cost, setCost] = useState7(0);
22011
+ const [todos, setTodos] = useState7([]);
22012
+ const [queued, setQueued] = useState7(0);
22013
+ const [candidate, setCandidate] = useState7();
22014
+ const [context2, setContext] = useState7(() => agent3.contextUse);
22015
+ const [compacted, setCompacted] = useState7(0);
22016
+ const [pending, setPending] = useState7();
22017
+ const [question, setQuestion] = useState7();
22018
+ const controller = useRef3(undefined);
22019
+ const push = useCallback2((item) => setItems((current) => [...current, item]), []);
22020
+ const pendingRef = useRef3(setPending);
22021
+ pendingRef.current = setPending;
22022
+ const questionRef = useRef3(setQuestion);
22023
+ questionRef.current = setQuestion;
22024
+ useEffect3(() => {
22025
+ session2.installPrompt((request, reason) => new Promise((resolve4) => {
22026
+ pendingRef.current({ request, reason, resolve: resolve4 });
22027
+ }));
22028
+ session2.installAsk((q, options) => new Promise((resolve4) => {
22029
+ questionRef.current({ question: q, ...options ? { options } : {}, resolve: resolve4 });
22030
+ }));
22031
+ }, [session2]);
22032
+ const modeBeforePlan = useRef3(undefined);
22033
+ const lastAssistantText = useRef3("");
22034
+ const runTurn = useCallback2(async (prompt) => {
22035
+ setBusy(true);
22036
+ setActivity("preparing");
22037
+ lastAssistantText.current = "";
22038
+ push({ kind: "user", id: nextId(), text: promptLabel(prompt) });
22039
+ const abort = new AbortController;
22040
+ controller.current = abort;
22041
+ let assistantText = "";
22042
+ let reasoningText = "";
22043
+ let reasoningFlushed = false;
22044
+ try {
22045
+ for await (const event of agent3.runTurn(prompt, abort.signal)) {
22046
+ switch (event.type) {
22047
+ case "model_start":
22048
+ setActivity("thinking");
22049
+ break;
22050
+ case "text_delta":
22051
+ if (!reasoningFlushed && reasoningText.trim() !== "" && showThinking) {
22052
+ push({ kind: "reasoning", id: nextId(), text: reasoningText.trimEnd() });
22053
+ reasoningFlushed = true;
22054
+ setReasoningLive("");
22055
+ }
22056
+ setActivity(undefined);
22057
+ assistantText += event.text;
22058
+ setLive(assistantText);
22059
+ break;
22060
+ case "reasoning_delta":
22061
+ setActivity("reasoning");
22062
+ reasoningText += event.text;
22063
+ if (showThinking)
22064
+ setReasoningLive(reasoningText);
22065
+ break;
22066
+ case "intent":
22067
+ if (event.text === undefined) {
22068
+ push({
22069
+ kind: "notice",
22070
+ id: nextId(),
22071
+ text: `about to run ${event.calls} tool call${event.calls === 1 ? "" : "s"} without saying why`,
22072
+ color: theme.warning
22073
+ });
22074
+ }
22075
+ break;
22076
+ case "tool_start":
22077
+ setActivity(undefined);
22078
+ if (assistantText.trim() !== "") {
22079
+ push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
22080
+ assistantText = "";
22081
+ setLive("");
22082
+ }
22083
+ setRunningTool(event.call.toolName);
22084
+ break;
22085
+ case "tool_end": {
22086
+ setRunningTool(undefined);
22087
+ const output = event.result.output.type === "text" ? event.result.output.value : "";
22088
+ push({
22089
+ kind: "tool",
22090
+ id: nextId(),
22091
+ name: event.toolName,
22092
+ ...event.result.title ? { title: event.result.title } : {},
22093
+ output,
22094
+ ...event.result.isError ? { isError: true } : {}
22095
+ });
22096
+ setTodos(agent3.todos.list());
22097
+ break;
22098
+ }
22099
+ case "usage":
22100
+ setCost(agent3.costUsd);
22101
+ setContext(agent3.contextUse);
22102
+ break;
22103
+ case "budget":
22104
+ push({
22105
+ kind: "notice",
22106
+ id: nextId(),
22107
+ text: event.raisedTo === undefined ? `stopped: $${event.spentUsd.toFixed(2)} spent against a ` + `$${event.limitUsd.toFixed(2)} budget` : `budget raised to $${event.raisedTo.toFixed(2)} after ` + `$${event.spentUsd.toFixed(2)} spent`,
22108
+ color: theme.warning
22109
+ });
22110
+ break;
22111
+ case "verification":
22112
+ push({
22113
+ kind: "tool",
22114
+ id: nextId(),
22115
+ name: event.result.command,
22116
+ title: `${event.result.command} - exit ${event.result.exitCode ?? "killed"}`,
22117
+ output: event.result.output,
22118
+ ...event.result.exitCode === 0 ? {} : { isError: true }
22119
+ });
22120
+ break;
22121
+ case "subagent":
22122
+ push({
22123
+ kind: "notice",
22124
+ id: nextId(),
22125
+ text: `subagent "${event.description}": ${event.steps} step${event.steps === 1 ? "" : "s"}, $${event.costUsd.toFixed(4)}`
22126
+ });
22127
+ break;
22128
+ case "hook":
22129
+ if (event.blocked) {
22130
+ push({
22131
+ kind: "notice",
22132
+ id: nextId(),
22133
+ text: `${event.event} hook blocked this: ${event.blocked}`,
22134
+ color: theme.warning
22135
+ });
22136
+ }
22137
+ for (const problem of event.problems) {
22138
+ push({ kind: "notice", id: nextId(), text: problem, color: theme.warning });
22139
+ }
22140
+ break;
22141
+ case "compacted":
22142
+ setCompacted((count) => count + event.replaced);
22143
+ push({
22144
+ kind: "notice",
22145
+ id: nextId(),
22146
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
22147
+ });
22148
+ break;
22149
+ case "error":
22150
+ setActivity(undefined);
22151
+ push({
22152
+ kind: "notice",
22153
+ id: nextId(),
22154
+ text: `error: ${event.error.message}`,
22155
+ color: theme.danger
22156
+ });
22157
+ break;
22158
+ case "turn_end":
22159
+ if (event.reason === "aborted") {
22160
+ push({
22161
+ kind: "notice",
22162
+ id: nextId(),
22163
+ text: "interrupted",
22164
+ color: theme.warning
22165
+ });
22166
+ }
22167
+ if (event.reason === "max_steps") {
22168
+ push({
22169
+ kind: "notice",
22170
+ id: nextId(),
22171
+ text: "stopped: step limit reached",
22172
+ color: theme.warning
22173
+ });
22174
+ }
22175
+ break;
22176
+ default:
22177
+ break;
22178
+ }
22179
+ setQueued(agent3.pendingSteers);
21513
22180
  }
21514
- agent3.setPlan(text2);
21515
- const restored = modeBeforePlan.current ?? "ask";
21516
- agent3.setPermissionMode(restored);
21517
- setMode(restored);
21518
- modeBeforePlan.current = undefined;
21519
- push({
21520
- kind: "notice",
21521
- id: nextId(),
21522
- text: `plan approved and pinned for this run; permission mode: ${restored}`
21523
- });
21524
- return;
22181
+ } finally {
22182
+ if (!reasoningFlushed && reasoningText.trim() !== "" && showThinking) {
22183
+ push({ kind: "reasoning", id: nextId(), text: reasoningText.trimEnd() });
22184
+ }
22185
+ if (assistantText.trim() !== "") {
22186
+ lastAssistantText.current = assistantText;
22187
+ push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
22188
+ }
22189
+ setLive("");
22190
+ setReasoningLive("");
22191
+ setActivity(undefined);
22192
+ setRunningTool(undefined);
22193
+ setBusy(false);
22194
+ setQueued(agent3.pendingSteers);
22195
+ controller.current = undefined;
21525
22196
  }
21526
- if (verb === "clear") {
21527
- agent3.setPlan(undefined);
21528
- push({ kind: "notice", id: nextId(), text: "plan unpinned" });
22197
+ }, [agent3, push, showThinking]);
22198
+ const started = useRef3(false);
22199
+ useEffect3(() => {
22200
+ if (started.current || !initialPrompt)
21529
22201
  return;
21530
- }
21531
- if (task2 === "") {
22202
+ started.current = true;
22203
+ runTurn(initialPrompt);
22204
+ }, [initialPrompt, runTurn]);
22205
+ const showMemories = useCallback2(async (argument) => {
22206
+ const [verb, ...rest] = (argument ?? "").split(/\s+/);
22207
+ const id = rest.join(" ").trim();
22208
+ if (verb === "forget" && id) {
22209
+ const gone = await deleteMemory(id, agent3.cwd);
22210
+ if (gone)
22211
+ await refreshSystemPrompt(agent3, model2, session2.skills);
21532
22212
  push({
21533
22213
  kind: "notice",
21534
22214
  id: nextId(),
21535
- text: "usage: /plan <what you want planned>, then /plan edit, /plan approve",
21536
- color: theme.warning
22215
+ text: gone ? `forgot ${id}` : `no memory called "${id}"`,
22216
+ ...gone ? {} : { color: theme.warning }
21537
22217
  });
21538
22218
  return;
21539
22219
  }
21540
- modeBeforePlan.current = agent3.permissionMode;
21541
- agent3.setPermissionMode("plan");
21542
- setMode("plan");
21543
- await runTurn(`${task2}
21544
-
21545
- ${PLAN_PROMPT}`);
21546
- const drafted = lastAssistantText.current.trim();
21547
- if (drafted === "") {
21548
- push({
21549
- kind: "notice",
21550
- id: nextId(),
21551
- text: "the model produced no plan to write",
21552
- color: theme.warning
21553
- });
22220
+ const memories = await loadMemories(agent3.cwd);
22221
+ if (memories.length === 0) {
22222
+ push({ kind: "notice", id: nextId(), text: "nothing remembered yet" });
21554
22223
  return;
21555
22224
  }
21556
- await savePlan(path, drafted);
22225
+ const lines = memories.map((memory2) => {
22226
+ const when = memory2.created.slice(0, 10);
22227
+ const why = memory2.source ? `
22228
+ from "${memory2.source}" on ${when}` : "";
22229
+ return ` [${memory2.id}] (${memory2.scope}) ${memory2.text}${why}`;
22230
+ });
21557
22231
  push({
21558
22232
  kind: "notice",
21559
22233
  id: nextId(),
21560
- text: `plan written to ${path}
21561
- /plan edit to change it, /plan approve to pin it`
22234
+ text: `${lines.join(`
22235
+ `)}
22236
+
22237
+ /memory forget <id> removes one`
21562
22238
  });
21563
- }, [agent3, push, runTurn, session2.store]);
21564
- const switchModel = useCallback(async (ref) => {
21565
- if (!ref) {
21566
- const current = agent3.model;
21567
- const price = current.model.cost;
21568
- push({
21569
- kind: "notice",
21570
- id: nextId(),
21571
- text: [
21572
- ` ${current.provider.id}/${current.model.id}`,
21573
- ` context ${(current.model.contextWindow ?? 0).toLocaleString()} tokens`,
21574
- ` price $${price?.input ?? "?"} in / $${price?.output ?? "?"} out per million`,
21575
- "",
21576
- " /model <provider/model> switches; `earshot models` lists them"
21577
- ].join(`
21578
- `)
21579
- });
22239
+ }, [agent3, model2, push, session2.skills]);
22240
+ const remember = useCallback2(async (scope2) => {
22241
+ if (!candidate)
22242
+ return;
22243
+ setCandidate(undefined);
22244
+ const saved = await saveMemory({ ...candidate, scope: scope2 }, agent3.cwd).catch(() => {
22245
+ return;
22246
+ });
22247
+ if (!saved) {
22248
+ push({ kind: "notice", id: nextId(), text: "could not save that", color: theme.warning });
21580
22249
  return;
21581
22250
  }
21582
- try {
21583
- const resolved = await agent3.changeModel(ref);
21584
- const next = `${resolved.provider.id}/${resolved.model.id}`;
21585
- setModel(next);
21586
- await refreshSystemPrompt(agent3, next, session2.skills);
21587
- push({ kind: "notice", id: nextId(), text: `model: ${next}` });
21588
- } catch (error) {
21589
- push({
21590
- kind: "notice",
21591
- id: nextId(),
21592
- text: `${error.message}`,
21593
- color: theme.warning
21594
- });
21595
- }
21596
- }, [agent3, push, session2.skills]);
21597
- const compactNow = useCallback(async () => {
21598
- const abort = new AbortController;
21599
- controller.current = abort;
21600
- setBusy(true);
21601
- try {
21602
- let compactedAnything = false;
21603
- for await (const event of agent3.compactNow(abort.signal)) {
21604
- if (event.type === "compacted") {
21605
- compactedAnything = true;
21606
- setCompacted((count) => count + event.replaced);
21607
- push({
21608
- kind: "notice",
21609
- id: nextId(),
21610
- text: `compacted: ${event.replaced} earlier messages are now a summary`
21611
- });
21612
- }
21613
- }
21614
- if (!compactedAnything) {
21615
- push({ kind: "notice", id: nextId(), text: "nothing to compact yet" });
21616
- }
21617
- } catch (error) {
21618
- push({
21619
- kind: "notice",
21620
- id: nextId(),
21621
- text: `could not compact: ${error.message}`,
21622
- color: theme.warning
21623
- });
21624
- } finally {
21625
- setBusy(false);
21626
- setContext(agent3.contextUse);
21627
- controller.current = undefined;
21628
- }
21629
- }, [agent3, push]);
21630
- const handlers = {
21631
- exit: () => exit(),
21632
- help: () => push({ kind: "notice", id: nextId(), text: describeCommands(session2) }),
21633
- model: (argument) => void switchModel(argument),
21634
- compact: () => void compactNow(),
21635
- context: () => {
21636
- const { tokens, window } = agent3.contextUse;
21637
- const percent = window > 0 ? Math.round(tokens / window * 100) : 0;
21638
- const files = agent3.touchedFiles;
21639
- push({
21640
- kind: "notice",
21641
- id: nextId(),
21642
- text: [
21643
- ` model ${model2}`,
21644
- ` context ~${tokens.toLocaleString()} of ${window.toLocaleString()} tokens (${percent}%)`,
21645
- ` dropped ${compacted} earlier message${compacted === 1 ? "" : "s"} replaced by a summary`,
21646
- ` files ${files.length === 0 ? "none touched yet" : files.join(", ")}`,
21647
- "",
21648
- " /compact summarises now rather than waiting for 80%"
21649
- ].join(`
21650
- `)
21651
- });
21652
- },
21653
- cost: (argument) => {
21654
- if (argument !== undefined && argument !== "") {
21655
- const amount = Number.parseFloat(argument.replace(/^\$/, ""));
21656
- if (Number.isNaN(amount)) {
21657
- push({
21658
- kind: "notice",
21659
- id: nextId(),
21660
- text: `usage: /cost [usd] - "${argument}" is not an amount`,
21661
- color: theme.warning
21662
- });
21663
- return;
21664
- }
21665
- agent3.setBudget(amount > 0 ? amount : undefined);
21666
- push({
21667
- kind: "notice",
21668
- id: nextId(),
21669
- text: amount > 0 ? `budget: $${amount.toFixed(2)}` : "budget removed"
21670
- });
22251
+ await refreshSystemPrompt(agent3, model2, session2.skills);
22252
+ push({
22253
+ kind: "notice",
22254
+ id: nextId(),
22255
+ text: `remembered [${saved.id}] (${scope2}) - /memory to review or forget it`
22256
+ });
22257
+ }, [agent3, candidate, model2, push, session2.skills]);
22258
+ const sessionTree = useCallback2(async (name, argument) => {
22259
+ const entries2 = await session2.branch();
22260
+ const prompts = entries2.filter((entry) => entry.type === "message" && entry.message.role === "user" && entry.message.content.some((part) => part.type === "text" && !part.text.startsWith("<self-check>")));
22261
+ if (name === "tree" || !argument) {
22262
+ if (prompts.length === 0) {
22263
+ push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
21671
22264
  return;
21672
22265
  }
21673
- const budget = agent3.budgetUsd;
21674
- push({
21675
- kind: "notice",
21676
- id: nextId(),
21677
- text: ` spent $${agent3.costUsd.toFixed(4)}
21678
- ` + ` budget ${budget === undefined ? "none - /cost <usd> sets one" : `$${budget.toFixed(2)}`}`
22266
+ const lines = prompts.map((entry, index2) => {
22267
+ const text2 = entry.type === "message" ? entry.message.content.find((part) => part.type === "text")?.text ?? "" : "";
22268
+ return ` ${index2 + 1}. ${text2.split(`
22269
+ `)[0]?.slice(0, 70) ?? ""}`;
21679
22270
  });
21680
- },
21681
- todo: () => {
21682
- const todos2 = agent3.todos.list();
21683
22271
  push({
21684
22272
  kind: "notice",
21685
22273
  id: nextId(),
21686
- text: todos2.length === 0 ? "no todos in this session" : todos2.map((todo2) => ` ${todo2.status === "done" ? "x" : todo2.status === "in_progress" ? ">" : " "} ${todo2.text}`).join(`
21687
- `)
22274
+ text: `${lines.join(`
22275
+ `)}
22276
+
22277
+ /rewind <n> goes back to one · /fork <n> branches from it`
21688
22278
  });
21689
- },
21690
- permissions: () => {
21691
- const rules2 = agent3.permissionRules;
22279
+ return;
22280
+ }
22281
+ const index = Number.parseInt(argument, 10) - 1;
22282
+ const target = prompts[index];
22283
+ if (!target) {
21692
22284
  push({
21693
22285
  kind: "notice",
21694
22286
  id: nextId(),
21695
- text: [
21696
- ` mode ${agent3.permissionMode} (/mode changes it)`,
21697
- ...rules2.length === 0 ? [" rules none configured"] : [
21698
- " rules (deny always wins, whatever the mode or scope)",
21699
- ...rules2.map((rule) => ` ${rule.effect.padEnd(5)} ${rule.source} [${rule.scope}]`)
21700
- ]
21701
- ].join(`
21702
- `)
22287
+ text: `no prompt ${argument} in this session - /tree lists them`,
22288
+ color: theme.warning
21703
22289
  });
21704
- },
21705
- init: () => void runTurn(INIT_PROMPT),
21706
- mode: (argument) => {
21707
- if (argument && isPermissionMode(argument)) {
21708
- agent3.setPermissionMode(argument);
21709
- setMode(argument);
21710
- push({ kind: "notice", id: nextId(), text: `permission mode: ${argument}` });
21711
- } else {
21712
- push({
21713
- kind: "notice",
21714
- id: nextId(),
21715
- text: `usage: /mode <${PERMISSION_MODES.join("|")}>`,
21716
- color: theme.warning
21717
- });
21718
- }
21719
- },
21720
- memory: (argument) => void showMemories(argument),
21721
- tree: (argument) => void sessionTree("tree", argument),
21722
- rewind: (argument) => void sessionTree("rewind", argument),
21723
- fork: (argument) => void sessionTree("fork", argument),
21724
- undo: () => void undoLast(),
21725
- plan: (argument) => void plan2(argument),
21726
- skills: () => push({ kind: "notice", id: nextId(), text: describeExtensions(session2) })
21727
- };
21728
- const handlersRef = useRef2(handlers);
21729
- handlersRef.current = handlers;
21730
- const handleCommand = useCallback((command) => {
21731
- const body = command.slice(1).trim();
21732
- const space = body.search(/\s/);
21733
- const name = space === -1 ? body : body.slice(0, space);
21734
- const argument = space === -1 ? undefined : body.slice(space + 1).trim();
21735
- const spec = findCommand(name);
21736
- if (spec) {
21737
- if (spec.idleOnly && busy) {
21738
- push({
21739
- kind: "notice",
21740
- id: nextId(),
21741
- text: "finish or interrupt the current turn first (esc)",
21742
- color: theme.warning
21743
- });
21744
- return;
21745
- }
21746
- handlersRef.current[spec.name](argument);
21747
22290
  return;
21748
22291
  }
21749
- const custom = session2.commands.find((entry) => entry.name === name);
21750
- if (custom) {
21751
- const prompt = expandCommand(custom, argument ?? "");
21752
- if (prompt.trim() === "") {
21753
- push({
21754
- kind: "notice",
21755
- id: nextId(),
21756
- text: `/${name} expanded to nothing`,
21757
- color: theme.warning
21758
- });
21759
- return;
21760
- }
21761
- if (busy) {
21762
- agent3.steer(prompt);
21763
- setQueued(agent3.pendingSteers);
21764
- push({ kind: "user", id: nextId(), text: command });
21765
- return;
21766
- }
21767
- runTurn(prompt);
22292
+ const previous = entries2[entries2.indexOf(target) - 1] ?? target;
22293
+ if (name === "rewind") {
22294
+ const kept = await session2.rewindTo(previous.id);
22295
+ push({
22296
+ kind: "notice",
22297
+ id: nextId(),
22298
+ text: `rewound to before prompt ${index + 1}; ${kept} message${kept === 1 ? "" : "s"} kept. Nothing was deleted - the rest is still in the transcript as another branch.`
22299
+ });
21768
22300
  return;
21769
22301
  }
22302
+ const forked = await session2.fork(previous.id);
21770
22303
  push({
21771
22304
  kind: "notice",
21772
22305
  id: nextId(),
21773
- text: `unknown command "${name}"`,
21774
- color: theme.warning
22306
+ text: forked ? `forked from prompt ${index + 1} into ${forked}; this session continues there and the original is untouched` : "could not fork this session",
22307
+ ...forked ? {} : { color: theme.warning }
21775
22308
  });
21776
- }, [agent3, busy, push, runTurn, session2]);
21777
- const submit = useCallback((text2) => {
21778
- const entry = menuOpenRef.current ? menuEntriesRef.current[selectedRef.current] : undefined;
21779
- if (entry) {
21780
- setInput("");
21781
- setMenuIndex(0);
21782
- handleCommand(entry.insert);
22309
+ }, [push, session2]);
22310
+ const undoLast = useCallback2(async () => {
22311
+ const result = await session2.undo();
22312
+ if (!result) {
22313
+ push({ kind: "notice", id: nextId(), text: "nothing to undo", color: theme.warning });
21783
22314
  return;
21784
22315
  }
21785
- const trimmed = text2.trim();
21786
- setInput("");
21787
- if (trimmed === "")
21788
- return;
21789
- if (trimmed.startsWith("/")) {
21790
- handleCommand(trimmed);
22316
+ const created2 = result.wasCreated.length ? ` Left in place because the batch created them: ${result.wasCreated.join(", ")}.` : "";
22317
+ push({
22318
+ kind: "notice",
22319
+ id: nextId(),
22320
+ text: result.restored.length ? `undid ${result.label}: restored ${result.restored.join(", ")}.${created2}` : `nothing to restore from ${result.label}.${created2}`
22321
+ });
22322
+ }, [push, session2]);
22323
+ const plan2 = useCallback2(async (argument) => {
22324
+ const path = planPath(session2.store?.id ?? "scratch");
22325
+ const [verb = "", ...rest] = (argument ?? "").split(/\s+/);
22326
+ const task2 = [verb, ...rest].join(" ").trim();
22327
+ if (verb === "show") {
22328
+ const text2 = await readPlan(path);
22329
+ push({
22330
+ kind: "notice",
22331
+ id: nextId(),
22332
+ text: text2 ? `${path}
22333
+
22334
+ ${text2}` : `no plan yet at ${path}`
22335
+ });
21791
22336
  return;
21792
22337
  }
21793
- setCandidate(detectPreference(trimmed));
21794
- if (busy) {
21795
- agent3.steer(trimmed);
21796
- setQueued(agent3.pendingSteers);
21797
- push({ kind: "user", id: nextId(), text: trimmed });
22338
+ if (verb === "edit") {
22339
+ const result = await openInEditor(path);
22340
+ push({
22341
+ kind: "notice",
22342
+ id: nextId(),
22343
+ text: result.message,
22344
+ ...result.edited ? {} : { color: theme.warning }
22345
+ });
21798
22346
  return;
21799
22347
  }
21800
- runTurn(trimmed);
21801
- }, [agent3, busy, push, runTurn, handleCommand]);
21802
- const inputActive = !pending && !question;
21803
- const menuOpen = inputActive && input.startsWith("/") && !input.includes(" ");
21804
- const entries = menuOpen ? menuEntries(input.slice(1), session2.commands, busy) : [];
21805
- const selected = Math.min(menuIndex, Math.max(0, entries.length - 1));
21806
- const menuOpenRef = useRef2(menuOpen);
21807
- menuOpenRef.current = menuOpen;
21808
- const menuEntriesRef = useRef2(entries);
21809
- menuEntriesRef.current = entries;
21810
- const selectedRef = useRef2(selected);
21811
- selectedRef.current = selected;
21812
- useInput4((input_, key2) => {
21813
- if (menuOpen && entries.length > 0) {
21814
- if (key2.upArrow) {
21815
- setMenuIndex((current) => current <= 0 ? entries.length - 1 : current - 1);
21816
- return;
21817
- }
21818
- if (key2.downArrow) {
21819
- setMenuIndex((current) => current >= entries.length - 1 ? 0 : current + 1);
21820
- return;
21821
- }
21822
- if (key2.tab) {
21823
- const entry = entries[selected];
21824
- if (entry)
21825
- setInput(`${entry.insert} `);
21826
- setMenuIndex(0);
21827
- return;
21828
- }
21829
- if (key2.escape) {
21830
- setInput("");
21831
- setMenuIndex(0);
22348
+ if (verb === "approve") {
22349
+ const text2 = await readPlan(path);
22350
+ if (!text2) {
22351
+ push({
22352
+ kind: "notice",
22353
+ id: nextId(),
22354
+ text: `there is no plan at ${path} to approve`,
22355
+ color: theme.warning
22356
+ });
21832
22357
  return;
21833
22358
  }
21834
- }
21835
- if (key2.escape) {
21836
- setCandidate(undefined);
21837
- if (busy)
21838
- controller.current?.abort();
22359
+ agent3.setPlan(text2);
22360
+ const restored = modeBeforePlan.current ?? "ask";
22361
+ agent3.setPermissionMode(restored);
22362
+ setMode(restored);
22363
+ modeBeforePlan.current = undefined;
22364
+ push({
22365
+ kind: "notice",
22366
+ id: nextId(),
22367
+ text: `plan approved and pinned for this run; permission mode: ${restored}`
22368
+ });
21839
22369
  return;
21840
22370
  }
21841
- if (key2.ctrl && candidate && (input_ === "r" || input_ === "g")) {
21842
- remember(input_ === "r" ? "project" : "user");
21843
- }
21844
- }, { isActive: inputActive });
21845
- return /* @__PURE__ */ jsxDEV10(Box9, {
21846
- flexDirection: "column",
21847
- children: [
21848
- /* @__PURE__ */ jsxDEV10(Static, {
21849
- items,
21850
- children: (item) => /* @__PURE__ */ jsxDEV10(ScrollRow, {
21851
- item
21852
- }, item.id, false, undefined, this)
21853
- }, undefined, false, undefined, this),
21854
- live !== "" && /* @__PURE__ */ jsxDEV10(Box9, {
21855
- marginTop: 1,
21856
- children: /* @__PURE__ */ jsxDEV10(Markdown, {
21857
- text: live
21858
- }, undefined, false, undefined, this)
21859
- }, undefined, false, undefined, this),
21860
- runningTool && /* @__PURE__ */ jsxDEV10(ToolBlock, {
21861
- name: runningTool,
21862
- running: true
21863
- }, undefined, false, undefined, this),
21864
- pending && /* @__PURE__ */ jsxDEV10(PermissionPrompt, {
21865
- request: pending.request,
21866
- reason: pending.reason,
21867
- onChoice: (choice) => {
21868
- setPending(undefined);
21869
- pending.resolve(choice);
21870
- }
21871
- }, undefined, false, undefined, this),
21872
- question && /* @__PURE__ */ jsxDEV10(QuestionPrompt, {
21873
- question: question.question,
21874
- ...question.options ? { options: question.options } : {},
21875
- onAnswer: (answer) => {
21876
- setQuestion(undefined);
21877
- question.resolve(answer);
21878
- }
21879
- }, undefined, false, undefined, this),
21880
- candidate && inputActive && /* @__PURE__ */ jsxDEV10(MemoryCapture, {
21881
- candidate
21882
- }, undefined, false, undefined, this),
21883
- menuOpen && /* @__PURE__ */ jsxDEV10(CommandMenu, {
21884
- entries,
21885
- selected
21886
- }, undefined, false, undefined, this),
21887
- inputActive && /* @__PURE__ */ jsxDEV10(Box9, {
21888
- marginTop: 1,
21889
- children: [
21890
- /* @__PURE__ */ jsxDEV10(Text10, {
21891
- color: theme.user,
21892
- children: "> "
21893
- }, undefined, false, undefined, this),
21894
- /* @__PURE__ */ jsxDEV10(TextInput, {
21895
- value: input,
21896
- onChange: (value) => {
21897
- setInput(value);
21898
- setMenuIndex(0);
21899
- },
21900
- onSubmit: submit,
21901
- placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
21902
- }, undefined, false, undefined, this)
21903
- ]
21904
- }, undefined, true, undefined, this),
21905
- /* @__PURE__ */ jsxDEV10(StatusLine, {
21906
- model: model2,
21907
- mode,
21908
- costUsd: cost,
21909
- todos,
21910
- busy,
21911
- queued,
21912
- context: context2,
21913
- compacted
21914
- }, undefined, false, undefined, this)
21915
- ]
21916
- }, undefined, true, undefined, this);
21917
- }
21918
- function ScrollRow({ item }) {
21919
- if (item.kind === "user") {
21920
- return /* @__PURE__ */ jsxDEV10(Box9, {
21921
- marginTop: 1,
21922
- children: [
21923
- /* @__PURE__ */ jsxDEV10(Text10, {
21924
- color: theme.user,
21925
- children: "> "
21926
- }, undefined, false, undefined, this),
21927
- /* @__PURE__ */ jsxDEV10(Text10, {
21928
- children: item.text
21929
- }, undefined, false, undefined, this)
21930
- ]
21931
- }, undefined, true, undefined, this);
21932
- }
21933
- if (item.kind === "assistant") {
21934
- return /* @__PURE__ */ jsxDEV10(Box9, {
21935
- marginTop: 1,
21936
- children: /* @__PURE__ */ jsxDEV10(Markdown, {
21937
- text: item.text
21938
- }, undefined, false, undefined, this)
21939
- }, undefined, false, undefined, this);
21940
- }
21941
- if (item.kind === "tool") {
21942
- return /* @__PURE__ */ jsxDEV10(ToolBlock, {
21943
- name: item.name,
21944
- ...item.title ? { title: item.title } : {},
21945
- ...item.output ? { output: item.output } : {},
21946
- ...item.isError ? { isError: true } : {}
21947
- }, undefined, false, undefined, this);
21948
- }
21949
- return /* @__PURE__ */ jsxDEV10(Box9, {
21950
- marginTop: 1,
21951
- children: /* @__PURE__ */ jsxDEV10(Text10, {
21952
- color: item.color ?? theme.muted,
21953
- children: item.text
21954
- }, undefined, false, undefined, this)
21955
- }, undefined, false, undefined, this);
21956
- }
21957
- function describeCommands(session2) {
21958
- const lines = commandRows().map((row) => ` ${row.command.padEnd(34)} ${row.summary}`);
21959
- if (session2.commands.length > 0) {
21960
- lines.push("", " commands from this directory:");
21961
- for (const command of session2.commands) {
21962
- lines.push(` ${`/${command.name}`.padEnd(34)} ${command.description}`);
21963
- }
21964
- }
21965
- lines.push("", " type / at the prompt to filter this list and pick one");
21966
- return lines.join(`
21967
- `);
21968
- }
21969
- var INIT_PROMPT = `Write an AGENTS.md at the root of this project for a coding agent that has never seen it.
21970
-
21971
- Read enough of the repository first to be accurate. Cover: what the project is, how it is laid out, the commands to build, test and lint it, and the conventions and rules that are not obvious from the code. Prefer rules that prevent a specific failure, and say what the failure is. If an AGENTS.md or CLAUDE.md already exists, improve it in place rather than replacing it.`;
21972
- function describeExtensions(session2) {
21973
- const lines = [];
21974
- if (session2.skills.length > 0) {
21975
- lines.push("skills (the agent loads these itself when they fit):");
21976
- for (const skill of session2.skills) {
21977
- lines.push(` ${skill.name} [${skill.scope}] ${skill.description}`);
21978
- }
21979
- }
21980
- if (session2.commands.length > 0) {
21981
- if (lines.length > 0)
21982
- lines.push("");
21983
- lines.push("commands you can type:");
21984
- for (const command of session2.commands) {
21985
- lines.push(` /${command.name} [${command.scope}] ${command.description}`);
22371
+ if (verb === "clear") {
22372
+ agent3.setPlan(undefined);
22373
+ push({ kind: "notice", id: nextId(), text: "plan unpinned" });
22374
+ return;
21986
22375
  }
21987
- }
21988
- return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
21989
- `);
21990
- }
21991
- // packages/tui/src/onboarding.tsx
21992
- import { Box as Box10, Text as Text11, useApp as useApp2, useInput as useInput5 } from "ink";
21993
- import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
21994
- import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
21995
- function Onboarding({ onDone, ...rest }) {
21996
- const { exit } = useApp2();
21997
- const options = useRef3(rest);
21998
- options.current = rest;
21999
- const [screen, setScreen] = useState4({ name: "choose" });
22000
- const [cursor, setCursor] = useState4(0);
22001
- const [input, setInput] = useState4("");
22002
- const [error, setError] = useState4();
22003
- const secret = useRef3("");
22004
- const providers = order(rest.providers, rest.wanted);
22005
- const finish = useCallback2((result) => {
22006
- onDone(result);
22007
- exit();
22008
- }, [exit, onDone]);
22009
- const verify2 = useCallback2(async (provider, model2) => {
22010
- setScreen({ name: "probing", provider, model: model2 });
22011
- const result = await options.current.probe(provider.id, model2.id);
22012
- if (result.ok) {
22013
- setScreen({ name: "ready", provider, model: model2 });
22376
+ if (task2 === "") {
22377
+ push({
22378
+ kind: "notice",
22379
+ id: nextId(),
22380
+ text: "usage: /plan <what you want planned>, then /plan edit, /plan approve",
22381
+ color: theme.warning
22382
+ });
22014
22383
  return;
22015
22384
  }
22016
- if (result.reason === "rejected")
22017
- await options.current.forgetKey(provider.id).catch(() => {});
22018
- setScreen({ name: "failed", provider, model: model2, result });
22019
- }, []);
22020
- const submitKey = useCallback2(async () => {
22021
- const selected = screen.name === "key" ? screen : undefined;
22022
- const key2 = secret.current;
22023
- secret.current = "";
22024
- setInput("");
22025
- if (!selected)
22385
+ modeBeforePlan.current = agent3.permissionMode;
22386
+ agent3.setPermissionMode("plan");
22387
+ setMode("plan");
22388
+ await runTurn(`${task2}
22389
+
22390
+ ${PLAN_PROMPT}`);
22391
+ const drafted = lastAssistantText.current.trim();
22392
+ if (drafted === "") {
22393
+ push({
22394
+ kind: "notice",
22395
+ id: nextId(),
22396
+ text: "the model produced no plan to write",
22397
+ color: theme.warning
22398
+ });
22026
22399
  return;
22027
- if (key2.trim() === "") {
22028
- setError("a key is needed, or press esc to go back");
22400
+ }
22401
+ await savePlan(path, drafted);
22402
+ push({
22403
+ kind: "notice",
22404
+ id: nextId(),
22405
+ text: `plan written to ${path}
22406
+ /plan edit to change it, /plan approve to pin it`
22407
+ });
22408
+ }, [agent3, push, runTurn, session2.store]);
22409
+ const switchModel = useCallback2(async (ref) => {
22410
+ if (!ref) {
22411
+ if (modelOptions)
22412
+ setChoosingModel(true);
22413
+ else
22414
+ push({ kind: "notice", id: nextId(), text: "model picker is unavailable" });
22029
22415
  return;
22030
22416
  }
22031
- setError(undefined);
22032
- await options.current.storeKey(selected.provider.id, key2.trim());
22033
- await verify2(selected.provider, selected.model);
22034
- }, [screen, verify2]);
22035
- const startSignIn = useCallback2(async (provider, model2) => {
22036
- setScreen({ name: "oauth", provider, model: model2 });
22037
22417
  try {
22038
- await options.current.signIn(provider.id, (url) => setScreen({ name: "oauth", provider, model: model2, url }));
22039
- } catch (failure) {
22040
- setScreen({
22041
- name: "failed",
22042
- provider,
22043
- model: model2,
22044
- result: { ok: false, reason: "other", message: failure.message }
22418
+ const resolved = await agent3.changeModel(ref);
22419
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
22420
+ const effort = modelOptions?.reasoningFor?.(next);
22421
+ agent3.setReasoningEffort(effort);
22422
+ setModel(next);
22423
+ await refreshSystemPrompt(agent3, next, session2.skills);
22424
+ await modelOptions?.remember?.(next, effort, "project");
22425
+ await session2.recordConfiguration({ model: next, reasoningEffort: effort ?? null });
22426
+ push({
22427
+ kind: "notice",
22428
+ id: nextId(),
22429
+ text: `model: ${next}${effort ? ` · ${effort}` : ""}`
22430
+ });
22431
+ } catch (error) {
22432
+ push({
22433
+ kind: "notice",
22434
+ id: nextId(),
22435
+ text: `${error.message}`,
22436
+ color: theme.warning
22045
22437
  });
22438
+ }
22439
+ }, [agent3, modelOptions, push, session2]);
22440
+ const finishModelChoice = useCallback2(async (result) => {
22441
+ if (result.outcome === "quit" || !result.model) {
22442
+ setChoosingModel(false);
22046
22443
  return;
22047
22444
  }
22048
- await verify2(provider, model2);
22049
- }, [verify2]);
22050
- const chooseModel = useCallback2((provider, model2) => {
22051
- setInput("");
22052
- setError(undefined);
22053
- if (provider.configured)
22054
- verify2(provider, model2);
22055
- else if (provider.kind === "oauth")
22056
- startSignIn(provider, model2);
22057
- else
22058
- setScreen({ name: "key", provider, model: model2 });
22059
- }, [startSignIn, verify2]);
22060
- const chooseProvider = useCallback2((provider) => {
22061
- setInput("");
22062
- setError(undefined);
22063
- setCursor(preferredModelIndex(provider.models, rest.wantedModel));
22064
- setScreen({ name: "model", provider });
22065
- }, [rest.wantedModel]);
22066
- useInput5((key2, meta) => {
22067
- if (meta.ctrl && key2 === "c") {
22068
- finish({ outcome: "quit" });
22445
+ try {
22446
+ const resolved = await agent3.changeModel(result.model);
22447
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
22448
+ agent3.setReasoningEffort(result.reasoningEffort);
22449
+ setModel(next);
22450
+ await refreshSystemPrompt(agent3, next, session2.skills);
22451
+ await modelOptions?.remember?.(next, result.reasoningEffort, result.scope ?? "project");
22452
+ await session2.recordConfiguration({
22453
+ model: next,
22454
+ reasoningEffort: result.reasoningEffort ?? null
22455
+ });
22456
+ push({
22457
+ kind: "notice",
22458
+ id: nextId(),
22459
+ text: `model: ${next}${result.reasoningEffort ? ` · ${result.reasoningEffort}` : " · auto"}`
22460
+ });
22461
+ } catch (error) {
22462
+ push({
22463
+ kind: "notice",
22464
+ id: nextId(),
22465
+ text: error.message,
22466
+ color: theme.warning
22467
+ });
22468
+ } finally {
22469
+ setChoosingModel(false);
22470
+ }
22471
+ }, [agent3, modelOptions, push, session2, session2.skills]);
22472
+ const changeReasoning = useCallback2(async (argument) => {
22473
+ const value = argument?.trim().toLowerCase();
22474
+ if (!value) {
22475
+ if (!agent3.model.model.capabilities.reasoning) {
22476
+ push({
22477
+ kind: "notice",
22478
+ id: nextId(),
22479
+ text: `${agent3.model.model.name} does not support reasoning`,
22480
+ color: theme.warning
22481
+ });
22482
+ } else
22483
+ setChoosingReasoning(true);
22069
22484
  return;
22070
22485
  }
22071
- if (screen.name === "choose") {
22072
- if (key2 === "q") {
22073
- finish({ outcome: "quit" });
22074
- return;
22486
+ const effort = value === "auto" ? undefined : value;
22487
+ if (value !== "auto" && !["none", "low", "medium", "high", "xhigh"].includes(value)) {
22488
+ push({
22489
+ kind: "notice",
22490
+ id: nextId(),
22491
+ text: `unknown reasoning effort "${value}"`,
22492
+ color: theme.warning
22493
+ });
22494
+ return;
22495
+ }
22496
+ if (!agent3.model.model.capabilities.reasoning && effort !== undefined) {
22497
+ push({
22498
+ kind: "notice",
22499
+ id: nextId(),
22500
+ text: `${agent3.model.model.name} does not support reasoning`,
22501
+ color: theme.warning
22502
+ });
22503
+ return;
22504
+ }
22505
+ agent3.setReasoningEffort(effort);
22506
+ await modelOptions?.remember?.(model2, effort, "project");
22507
+ await session2.recordConfiguration({ reasoningEffort: effort ?? null });
22508
+ push({ kind: "notice", id: nextId(), text: `reasoning: ${effort ?? "auto"}` });
22509
+ }, [agent3, model2, modelOptions, push, session2]);
22510
+ const finishReasoningChoice = useCallback2(async (effort) => {
22511
+ setChoosingReasoning(false);
22512
+ agent3.setReasoningEffort(effort);
22513
+ await modelOptions?.remember?.(model2, effort, "project");
22514
+ await session2.recordConfiguration({ reasoningEffort: effort ?? null });
22515
+ push({ kind: "notice", id: nextId(), text: `reasoning: ${effort ?? "auto"}` });
22516
+ }, [agent3, model2, modelOptions, push, session2]);
22517
+ const compactNow = useCallback2(async () => {
22518
+ const abort = new AbortController;
22519
+ controller.current = abort;
22520
+ setBusy(true);
22521
+ try {
22522
+ let compactedAnything = false;
22523
+ for await (const event of agent3.compactNow(abort.signal)) {
22524
+ if (event.type === "compacted") {
22525
+ compactedAnything = true;
22526
+ setCompacted((count) => count + event.replaced);
22527
+ push({
22528
+ kind: "notice",
22529
+ id: nextId(),
22530
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
22531
+ });
22532
+ }
22075
22533
  }
22076
- if (meta.upArrow)
22077
- setCursor((c) => c <= 0 ? providers.length - 1 : c - 1);
22078
- if (meta.downArrow)
22079
- setCursor((c) => c >= providers.length - 1 ? 0 : c + 1);
22080
- if (meta.return) {
22081
- const provider = providers[cursor];
22082
- if (provider)
22083
- chooseProvider(provider);
22534
+ if (!compactedAnything) {
22535
+ push({ kind: "notice", id: nextId(), text: "nothing to compact yet" });
22084
22536
  }
22085
- return;
22537
+ } catch (error) {
22538
+ push({
22539
+ kind: "notice",
22540
+ id: nextId(),
22541
+ text: `could not compact: ${error.message}`,
22542
+ color: theme.warning
22543
+ });
22544
+ } finally {
22545
+ setBusy(false);
22546
+ setContext(agent3.contextUse);
22547
+ controller.current = undefined;
22086
22548
  }
22087
- if (screen.name === "model") {
22088
- if (meta.escape) {
22089
- setInput("");
22090
- setError(undefined);
22091
- setCursor(0);
22092
- setScreen({ name: "choose" });
22549
+ }, [agent3, push]);
22550
+ const handlers = {
22551
+ exit: () => exit(),
22552
+ help: () => push({ kind: "notice", id: nextId(), text: describeCommands(session2) }),
22553
+ model: (argument) => void switchModel(argument),
22554
+ reasoning: (argument) => void changeReasoning(argument),
22555
+ thinking: (argument) => {
22556
+ if (argument !== "show" && argument !== "hide") {
22557
+ push({
22558
+ kind: "notice",
22559
+ id: nextId(),
22560
+ text: `thinking: ${showThinking ? "shown" : "hidden"}
22561
+ /thinking <show|hide>`
22562
+ });
22563
+ return;
22564
+ }
22565
+ const shown = argument === "show";
22566
+ setShowThinking(shown);
22567
+ if (!shown)
22568
+ setReasoningLive("");
22569
+ push({ kind: "notice", id: nextId(), text: `thinking: ${shown ? "shown" : "hidden"}` });
22570
+ },
22571
+ compact: () => void compactNow(),
22572
+ context: () => {
22573
+ const { tokens, window } = agent3.contextUse;
22574
+ const percent = window > 0 ? Math.round(tokens / window * 100) : 0;
22575
+ const files = agent3.touchedFiles;
22576
+ push({
22577
+ kind: "notice",
22578
+ id: nextId(),
22579
+ text: [
22580
+ ` model ${model2}`,
22581
+ ` context ~${tokens.toLocaleString()} of ${window.toLocaleString()} tokens (${percent}%)`,
22582
+ ` dropped ${compacted} earlier message${compacted === 1 ? "" : "s"} replaced by a summary`,
22583
+ ` files ${files.length === 0 ? "none touched yet" : files.join(", ")}`,
22584
+ "",
22585
+ " /compact summarises now rather than waiting for 80%"
22586
+ ].join(`
22587
+ `)
22588
+ });
22589
+ },
22590
+ cost: (argument) => {
22591
+ if (argument !== undefined && argument !== "") {
22592
+ const amount = Number.parseFloat(argument.replace(/^\$/, ""));
22593
+ if (Number.isNaN(amount)) {
22594
+ push({
22595
+ kind: "notice",
22596
+ id: nextId(),
22597
+ text: `usage: /cost [usd] - "${argument}" is not an amount`,
22598
+ color: theme.warning
22599
+ });
22600
+ return;
22601
+ }
22602
+ agent3.setBudget(amount > 0 ? amount : undefined);
22603
+ push({
22604
+ kind: "notice",
22605
+ id: nextId(),
22606
+ text: amount > 0 ? `budget: $${amount.toFixed(2)}` : "budget removed"
22607
+ });
22608
+ return;
22609
+ }
22610
+ const budget = agent3.budgetUsd;
22611
+ push({
22612
+ kind: "notice",
22613
+ id: nextId(),
22614
+ text: ` spent $${agent3.costUsd.toFixed(4)}
22615
+ ` + ` budget ${budget === undefined ? "none - /cost <usd> sets one" : `$${budget.toFixed(2)}`}`
22616
+ });
22617
+ },
22618
+ todo: () => {
22619
+ const todos2 = agent3.todos.list();
22620
+ push({
22621
+ kind: "notice",
22622
+ id: nextId(),
22623
+ text: todos2.length === 0 ? "no todos in this session" : todos2.map((todo2) => ` ${todo2.status === "done" ? "x" : todo2.status === "in_progress" ? ">" : " "} ${todo2.text}`).join(`
22624
+ `)
22625
+ });
22626
+ },
22627
+ permissions: () => {
22628
+ const rules2 = agent3.permissionRules;
22629
+ push({
22630
+ kind: "notice",
22631
+ id: nextId(),
22632
+ text: [
22633
+ ` mode ${agent3.permissionMode} (/mode changes it)`,
22634
+ ...rules2.length === 0 ? [" rules none configured"] : [
22635
+ " rules (deny always wins, whatever the mode or scope)",
22636
+ ...rules2.map((rule) => ` ${rule.effect.padEnd(5)} ${rule.source} [${rule.scope}]`)
22637
+ ]
22638
+ ].join(`
22639
+ `)
22640
+ });
22641
+ },
22642
+ init: () => void runTurn(INIT_PROMPT),
22643
+ mode: (argument) => {
22644
+ if (argument && isPermissionMode(argument)) {
22645
+ agent3.setPermissionMode(argument);
22646
+ setMode(argument);
22647
+ push({ kind: "notice", id: nextId(), text: `permission mode: ${argument}` });
22648
+ } else {
22649
+ push({
22650
+ kind: "notice",
22651
+ id: nextId(),
22652
+ text: `usage: /mode <${PERMISSION_MODES.join("|")}>`,
22653
+ color: theme.warning
22654
+ });
22655
+ }
22656
+ },
22657
+ memory: (argument) => void showMemories(argument),
22658
+ tree: (argument) => void sessionTree("tree", argument),
22659
+ sessions: () => {
22660
+ listSessions(agent3.cwd).then((saved) => setSessionChoices(saved));
22661
+ },
22662
+ rewind: (argument) => void sessionTree("rewind", argument),
22663
+ fork: (argument) => void sessionTree("fork", argument),
22664
+ undo: () => void undoLast(),
22665
+ plan: (argument) => void plan2(argument),
22666
+ skills: () => push({ kind: "notice", id: nextId(), text: describeExtensions(session2) })
22667
+ };
22668
+ const handlersRef = useRef3(handlers);
22669
+ handlersRef.current = handlers;
22670
+ const handleCommand = useCallback2((command) => {
22671
+ const body = command.slice(1).trim();
22672
+ const space = body.search(/\s/);
22673
+ const name = space === -1 ? body : body.slice(0, space);
22674
+ const argument = space === -1 ? undefined : body.slice(space + 1).trim();
22675
+ const spec = findCommand(name);
22676
+ if (spec) {
22677
+ if (spec.idleOnly && busy) {
22678
+ push({
22679
+ kind: "notice",
22680
+ id: nextId(),
22681
+ text: "finish or interrupt the current turn first (esc)",
22682
+ color: theme.warning
22683
+ });
22093
22684
  return;
22094
22685
  }
22095
- const matches3 = matchingModels(screen.provider.models, input);
22096
- if (meta.upArrow)
22097
- setCursor((c) => c <= 0 ? Math.max(0, matches3.length - 1) : c - 1);
22098
- if (meta.downArrow)
22099
- setCursor((c) => c >= matches3.length - 1 ? 0 : c + 1);
22686
+ handlersRef.current[spec.name](argument);
22100
22687
  return;
22101
22688
  }
22102
- if (screen.name === "key" && !meta.escape) {
22103
- if (meta.return) {
22104
- submitKey();
22105
- return;
22106
- }
22107
- if (meta.backspace || meta.delete) {
22108
- secret.current = secret.current.slice(0, -1);
22109
- setInput(secret.current);
22689
+ const custom = session2.commands.find((entry) => entry.name === name);
22690
+ if (custom) {
22691
+ const prompt = expandCommand(custom, argument ?? "");
22692
+ if (prompt.trim() === "") {
22693
+ push({
22694
+ kind: "notice",
22695
+ id: nextId(),
22696
+ text: `/${name} expanded to nothing`,
22697
+ color: theme.warning
22698
+ });
22110
22699
  return;
22111
22700
  }
22112
- if (meta.ctrl || meta.meta || meta.tab)
22701
+ if (busy) {
22702
+ agent3.steer(prompt);
22703
+ setQueued(agent3.pendingSteers);
22704
+ push({ kind: "user", id: nextId(), text: command });
22113
22705
  return;
22114
- if (key2) {
22115
- secret.current += key2;
22116
- setInput(secret.current);
22117
22706
  }
22707
+ runTurn(prompt);
22118
22708
  return;
22119
22709
  }
22120
- if (meta.escape) {
22121
- secret.current = "";
22122
- setInput("");
22123
- setError(undefined);
22124
- setScreen(screen.name === "key" || screen.name === "oauth" || screen.name === "failed" ? { name: "model", provider: screen.provider } : { name: "choose" });
22125
- return;
22126
- }
22127
- if (screen.name === "failed" && key2 === "k" && screen.result.reason === "unreachable") {
22128
- setScreen({ name: "ready", provider: screen.provider, model: screen.model });
22129
- }
22130
- });
22131
- if (screen.name === "choose") {
22132
- return /* @__PURE__ */ jsxDEV11(Box10, {
22133
- flexDirection: "column",
22134
- children: [
22135
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22136
- providers.map((provider, index) => /* @__PURE__ */ jsxDEV11(Box10, {
22137
- children: [
22138
- /* @__PURE__ */ jsxDEV11(Text11, {
22139
- color: index === cursor ? theme.user : theme.muted,
22140
- children: index === cursor ? "› " : " "
22141
- }, undefined, false, undefined, this),
22142
- /* @__PURE__ */ jsxDEV11(Text11, {
22143
- ...index === cursor ? { color: theme.user } : {},
22144
- children: provider.id.padEnd(14)
22145
- }, undefined, false, undefined, this),
22146
- /* @__PURE__ */ jsxDEV11(Text11, {
22147
- color: theme.muted,
22148
- children: hint(provider)
22149
- }, undefined, false, undefined, this)
22150
- ]
22151
- }, provider.id, true, undefined, this)),
22152
- /* @__PURE__ */ jsxDEV11(Box10, {
22153
- marginTop: 1,
22154
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22155
- color: theme.muted,
22156
- children: "↑↓ choose · enter select · q quit"
22157
- }, undefined, false, undefined, this)
22158
- }, undefined, false, undefined, this),
22159
- error ? /* @__PURE__ */ jsxDEV11(Text11, {
22160
- color: theme.warning,
22161
- children: error
22162
- }, undefined, false, undefined, this) : null
22163
- ]
22164
- }, undefined, true, undefined, this);
22165
- }
22166
- if (screen.name === "model") {
22167
- const matches3 = matchingModels(screen.provider.models, input);
22168
- const selected = matches3[cursor];
22169
- const start = Math.max(0, Math.min(cursor - 3, matches3.length - 8));
22170
- return /* @__PURE__ */ jsxDEV11(Box10, {
22171
- flexDirection: "column",
22172
- children: [
22173
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22174
- /* @__PURE__ */ jsxDEV11(Text11, {
22175
- children: [
22176
- "choose a model from ",
22177
- screen.provider.id,
22178
- " (",
22179
- matches3.length,
22180
- " matching)"
22181
- ]
22182
- }, undefined, true, undefined, this),
22183
- /* @__PURE__ */ jsxDEV11(Box10, {
22184
- children: [
22185
- /* @__PURE__ */ jsxDEV11(Text11, {
22186
- color: theme.user,
22187
- children: "> "
22188
- }, undefined, false, undefined, this),
22189
- /* @__PURE__ */ jsxDEV11(TextInput, {
22190
- value: input,
22191
- onChange: (value) => {
22192
- setInput(value);
22193
- setCursor(0);
22194
- setError(undefined);
22195
- },
22196
- onSubmit: () => {
22197
- if (selected)
22198
- chooseModel(screen.provider, selected);
22199
- else
22200
- setError("no model matches that search");
22201
- },
22202
- placeholder: "type to filter models"
22203
- }, undefined, false, undefined, this)
22204
- ]
22205
- }, undefined, true, undefined, this),
22206
- /* @__PURE__ */ jsxDEV11(Box10, {
22207
- flexDirection: "column",
22208
- marginTop: 1,
22209
- children: [
22210
- matches3.slice(start, start + 8).map((model2) => /* @__PURE__ */ jsxDEV11(Text11, {
22211
- color: model2 === selected ? theme.user : theme.muted,
22212
- children: [
22213
- model2 === selected ? "› " : " ",
22214
- model2.id,
22215
- " · ",
22216
- model2.name
22217
- ]
22218
- }, model2.id, true, undefined, this)),
22219
- matches3.length === 0 ? /* @__PURE__ */ jsxDEV11(Text11, {
22220
- color: theme.muted,
22221
- children: "no matching models"
22222
- }, undefined, false, undefined, this) : null
22223
- ]
22224
- }, undefined, true, undefined, this),
22225
- /* @__PURE__ */ jsxDEV11(Box10, {
22226
- marginTop: 1,
22227
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22228
- color: theme.muted,
22229
- children: "type filter · ↑↓ choose · enter select · esc back"
22230
- }, undefined, false, undefined, this)
22231
- }, undefined, false, undefined, this),
22232
- error ? /* @__PURE__ */ jsxDEV11(Text11, {
22233
- color: theme.warning,
22234
- children: error
22235
- }, undefined, false, undefined, this) : null
22236
- ]
22237
- }, undefined, true, undefined, this);
22238
- }
22239
- if (screen.name === "key") {
22240
- return /* @__PURE__ */ jsxDEV11(Box10, {
22241
- flexDirection: "column",
22242
- children: [
22243
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22244
- /* @__PURE__ */ jsxDEV11(Text11, {
22245
- children: [
22246
- "paste an api key for ",
22247
- screen.provider.id
22248
- ]
22249
- }, undefined, true, undefined, this),
22250
- /* @__PURE__ */ jsxDEV11(Box10, {
22251
- marginTop: 1,
22252
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22253
- color: theme.muted,
22254
- children: [
22255
- screen.provider.envVars?.length ? `or set ${screen.provider.envVars.join(" or ")} instead and restart.
22256
- ` : "",
22257
- "stored in your config directory, readable only by you, and never printed."
22258
- ]
22259
- }, undefined, true, undefined, this)
22260
- }, undefined, false, undefined, this),
22261
- /* @__PURE__ */ jsxDEV11(Box10, {
22262
- marginTop: 1,
22263
- children: [
22264
- /* @__PURE__ */ jsxDEV11(Text11, {
22265
- color: theme.user,
22266
- children: "> "
22267
- }, undefined, false, undefined, this),
22268
- /* @__PURE__ */ jsxDEV11(Text11, {
22269
- children: "•".repeat(input.length)
22270
- }, undefined, false, undefined, this),
22271
- /* @__PURE__ */ jsxDEV11(Text11, {
22272
- inverse: true,
22273
- children: " "
22274
- }, undefined, false, undefined, this)
22275
- ]
22276
- }, undefined, true, undefined, this),
22277
- error ? /* @__PURE__ */ jsxDEV11(Text11, {
22278
- color: theme.warning,
22279
- children: error
22280
- }, undefined, false, undefined, this) : null,
22281
- /* @__PURE__ */ jsxDEV11(Box10, {
22282
- marginTop: 1,
22283
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22284
- color: theme.muted,
22285
- children: "enter continue · esc back"
22286
- }, undefined, false, undefined, this)
22287
- }, undefined, false, undefined, this)
22288
- ]
22289
- }, undefined, true, undefined, this);
22290
- }
22291
- if (screen.name === "oauth") {
22292
- return /* @__PURE__ */ jsxDEV11(Box10, {
22293
- flexDirection: "column",
22294
- children: [
22295
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22296
- /* @__PURE__ */ jsxDEV11(Text11, {
22297
- children: [
22298
- "signing in to ",
22299
- screen.provider.id,
22300
- " in your browser…"
22301
- ]
22302
- }, undefined, true, undefined, this),
22303
- screen.url ? /* @__PURE__ */ jsxDEV11(Box10, {
22304
- marginTop: 1,
22305
- flexDirection: "column",
22306
- children: [
22307
- /* @__PURE__ */ jsxDEV11(Text11, {
22308
- color: theme.muted,
22309
- children: "if it did not open, use this link:"
22310
- }, undefined, false, undefined, this),
22311
- /* @__PURE__ */ jsxDEV11(Text11, {
22312
- children: screen.url
22313
- }, undefined, false, undefined, this)
22314
- ]
22315
- }, undefined, true, undefined, this) : null,
22316
- /* @__PURE__ */ jsxDEV11(Box10, {
22317
- marginTop: 1,
22318
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22319
- color: theme.muted,
22320
- children: "esc cancel"
22321
- }, undefined, false, undefined, this)
22322
- }, undefined, false, undefined, this)
22323
- ]
22324
- }, undefined, true, undefined, this);
22325
- }
22326
- if (screen.name === "probing") {
22327
- return /* @__PURE__ */ jsxDEV11(Box10, {
22328
- flexDirection: "column",
22329
- children: [
22330
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22331
- /* @__PURE__ */ jsxDEV11(Text11, {
22332
- color: theme.muted,
22333
- children: [
22334
- "checking the credentials with ",
22335
- screen.provider.id,
22336
- "…"
22337
- ]
22338
- }, undefined, true, undefined, this)
22339
- ]
22340
- }, undefined, true, undefined, this);
22341
- }
22342
- if (screen.name === "failed") {
22343
- return /* @__PURE__ */ jsxDEV11(Box10, {
22344
- flexDirection: "column",
22345
- children: [
22346
- /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22347
- /* @__PURE__ */ jsxDEV11(Text11, {
22348
- color: theme.warning,
22349
- children: screen.result.reason === "rejected" ? `${screen.provider.id} rejected that credential. It has not been kept.` : screen.result.reason === "unreachable" ? `could not reach ${screen.provider.id}.` : `${screen.provider.id} said:`
22350
- }, undefined, false, undefined, this),
22351
- /* @__PURE__ */ jsxDEV11(Text11, {
22352
- color: theme.muted,
22353
- children: screen.result.message
22354
- }, undefined, false, undefined, this),
22355
- /* @__PURE__ */ jsxDEV11(Box10, {
22356
- marginTop: 1,
22357
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22358
- color: theme.muted,
22359
- children: screen.result.reason === "unreachable" ? "k keep it anyway and carry on · esc start over · ctrl-c quit" : "esc start over · ctrl-c quit"
22360
- }, undefined, false, undefined, this)
22361
- }, undefined, false, undefined, this)
22362
- ]
22363
- }, undefined, true, undefined, this);
22364
- }
22365
- return /* @__PURE__ */ jsxDEV11(Box10, {
22710
+ push({
22711
+ kind: "notice",
22712
+ id: nextId(),
22713
+ text: `unknown command "${name}"`,
22714
+ color: theme.warning
22715
+ });
22716
+ }, [agent3, busy, push, runTurn, session2]);
22717
+ const submit = useCallback2((text2) => {
22718
+ const entry = menuOpenRef.current ? menuEntriesRef.current[selectedRef.current] : undefined;
22719
+ if (entry) {
22720
+ setInput("");
22721
+ setMenuIndex(0);
22722
+ handleCommand(entry.insert);
22723
+ return;
22724
+ }
22725
+ const trimmed = text2.trim();
22726
+ setInput("");
22727
+ if (trimmed === "")
22728
+ return;
22729
+ if (trimmed.startsWith("/")) {
22730
+ handleCommand(trimmed);
22731
+ return;
22732
+ }
22733
+ setCandidate(detectPreference(trimmed));
22734
+ if (busy) {
22735
+ agent3.steer(trimmed);
22736
+ setQueued(agent3.pendingSteers);
22737
+ push({ kind: "user", id: nextId(), text: trimmed });
22738
+ return;
22739
+ }
22740
+ runTurn(trimmed);
22741
+ }, [agent3, busy, push, runTurn, handleCommand]);
22742
+ const inputActive = !pending && !question && !choosingModel && !choosingReasoning && !sessionChoices;
22743
+ const menuOpen = inputActive && input.startsWith("/") && !input.includes(" ");
22744
+ const entries = menuOpen ? menuEntries(input.slice(1), session2.commands, busy) : [];
22745
+ const selected = Math.min(menuIndex, Math.max(0, entries.length - 1));
22746
+ const menuOpenRef = useRef3(menuOpen);
22747
+ menuOpenRef.current = menuOpen;
22748
+ const menuEntriesRef = useRef3(entries);
22749
+ menuEntriesRef.current = entries;
22750
+ const selectedRef = useRef3(selected);
22751
+ selectedRef.current = selected;
22752
+ useInput7((input_, key2) => {
22753
+ if (menuOpen && entries.length > 0) {
22754
+ if (key2.upArrow) {
22755
+ setMenuIndex((current) => current <= 0 ? entries.length - 1 : current - 1);
22756
+ return;
22757
+ }
22758
+ if (key2.downArrow) {
22759
+ setMenuIndex((current) => current >= entries.length - 1 ? 0 : current + 1);
22760
+ return;
22761
+ }
22762
+ if (key2.tab) {
22763
+ const entry = entries[selected];
22764
+ if (entry)
22765
+ setInput(`${entry.insert} `);
22766
+ setMenuIndex(0);
22767
+ return;
22768
+ }
22769
+ if (key2.escape) {
22770
+ setInput("");
22771
+ setMenuIndex(0);
22772
+ return;
22773
+ }
22774
+ }
22775
+ if (key2.escape) {
22776
+ setCandidate(undefined);
22777
+ if (busy)
22778
+ controller.current?.abort();
22779
+ return;
22780
+ }
22781
+ if (key2.ctrl && candidate && (input_ === "r" || input_ === "g")) {
22782
+ remember(input_ === "r" ? "project" : "user");
22783
+ }
22784
+ }, { isActive: inputActive });
22785
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22366
22786
  flexDirection: "column",
22367
22787
  children: [
22368
- /* @__PURE__ */ jsxDEV11(Text11, {
22369
- color: theme.user,
22370
- children: [
22371
- "ready: ",
22372
- screen.provider.id,
22373
- "/",
22374
- screen.model.id
22375
- ]
22376
- }, undefined, true, undefined, this),
22377
- /* @__PURE__ */ jsxDEV11(Box10, {
22788
+ /* @__PURE__ */ jsxDEV14(Static, {
22789
+ items,
22790
+ children: (item) => /* @__PURE__ */ jsxDEV14(ScrollRow, {
22791
+ item
22792
+ }, item.id, false, undefined, this)
22793
+ }, undefined, false, undefined, this),
22794
+ choosingModel && modelOptions && /* @__PURE__ */ jsxDEV14(Onboarding, {
22795
+ ...modelOptions,
22796
+ wanted: agent3.model.provider.id,
22797
+ wantedModel: model2,
22798
+ embedded: true,
22799
+ defaultScope: "project",
22800
+ onDone: (result) => void finishModelChoice(result)
22801
+ }, undefined, false, undefined, this),
22802
+ choosingReasoning && /* @__PURE__ */ jsxDEV14(ReasoningPicker, {
22803
+ ...agent3.reasoningEffort ? { current: agent3.reasoningEffort } : {},
22804
+ onDone: finishReasoningChoice,
22805
+ onCancel: () => setChoosingReasoning(false)
22806
+ }, undefined, false, undefined, this),
22807
+ sessionChoices && /* @__PURE__ */ jsxDEV14(SessionPicker, {
22808
+ sessions: sessionChoices,
22809
+ embedded: true,
22810
+ onDone: (path) => {
22811
+ setSessionChoices(undefined);
22812
+ if (path) {
22813
+ onResume?.(path);
22814
+ exit();
22815
+ }
22816
+ }
22817
+ }, undefined, false, undefined, this),
22818
+ !choosingModel && !choosingReasoning && !sessionChoices && reasoningLive !== "" && showThinking && /* @__PURE__ */ jsxDEV14(Box12, {
22378
22819
  marginTop: 1,
22379
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22380
- color: theme.muted,
22381
- children: "what should I do? (enter to start with nothing)"
22820
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22821
+ color: theme.reasoning,
22822
+ children: reasoningLive
22823
+ }, undefined, false, undefined, this)
22824
+ }, undefined, false, undefined, this),
22825
+ !choosingModel && !choosingReasoning && live !== "" && /* @__PURE__ */ jsxDEV14(Box12, {
22826
+ marginTop: 1,
22827
+ children: /* @__PURE__ */ jsxDEV14(Markdown, {
22828
+ text: live
22829
+ }, undefined, false, undefined, this)
22830
+ }, undefined, false, undefined, this),
22831
+ runningTool && /* @__PURE__ */ jsxDEV14(ToolBlock, {
22832
+ name: runningTool,
22833
+ running: true
22834
+ }, undefined, false, undefined, this),
22835
+ !choosingModel && !choosingReasoning && activity && live === "" && !runningTool && !pending && !question && /* @__PURE__ */ jsxDEV14(Box12, {
22836
+ marginTop: 1,
22837
+ children: /* @__PURE__ */ jsxDEV14(Activity, {
22838
+ ...activity === "preparing" ? {} : { label: activity === "thinking" ? "Thinking" : "Reasoning" }
22382
22839
  }, undefined, false, undefined, this)
22383
22840
  }, undefined, false, undefined, this),
22384
- /* @__PURE__ */ jsxDEV11(Box10, {
22841
+ pending && /* @__PURE__ */ jsxDEV14(PermissionPrompt, {
22842
+ request: pending.request,
22843
+ reason: pending.reason,
22844
+ onChoice: (choice) => {
22845
+ setPending(undefined);
22846
+ pending.resolve(choice);
22847
+ }
22848
+ }, undefined, false, undefined, this),
22849
+ question && /* @__PURE__ */ jsxDEV14(QuestionPrompt, {
22850
+ question: question.question,
22851
+ ...question.options ? { options: question.options } : {},
22852
+ onAnswer: (answer) => {
22853
+ setQuestion(undefined);
22854
+ question.resolve(answer);
22855
+ }
22856
+ }, undefined, false, undefined, this),
22857
+ candidate && inputActive && /* @__PURE__ */ jsxDEV14(MemoryCapture, {
22858
+ candidate
22859
+ }, undefined, false, undefined, this),
22860
+ menuOpen && /* @__PURE__ */ jsxDEV14(CommandMenu, {
22861
+ entries,
22862
+ selected
22863
+ }, undefined, false, undefined, this),
22864
+ inputActive && /* @__PURE__ */ jsxDEV14(Box12, {
22865
+ marginTop: 1,
22385
22866
  children: [
22386
- /* @__PURE__ */ jsxDEV11(Text11, {
22867
+ /* @__PURE__ */ jsxDEV14(Text14, {
22387
22868
  color: theme.user,
22388
22869
  children: "> "
22389
22870
  }, undefined, false, undefined, this),
22390
- /* @__PURE__ */ jsxDEV11(TextInput, {
22871
+ /* @__PURE__ */ jsxDEV14(TextInput, {
22391
22872
  value: input,
22392
- onChange: setInput,
22393
- onSubmit: (text2) => finish({
22394
- outcome: "ready",
22395
- providerId: screen.provider.id,
22396
- model: `${screen.provider.id}/${screen.model.id}`,
22397
- firstPrompt: text2.trim()
22398
- })
22873
+ onChange: (value) => {
22874
+ setInput(value);
22875
+ setMenuIndex(0);
22876
+ },
22877
+ onSubmit: submit,
22878
+ placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
22399
22879
  }, undefined, false, undefined, this)
22400
22880
  ]
22401
- }, undefined, true, undefined, this)
22881
+ }, undefined, true, undefined, this),
22882
+ /* @__PURE__ */ jsxDEV14(StatusLine, {
22883
+ model: model2,
22884
+ ...agent3.reasoningEffort ? { reasoningEffort: agent3.reasoningEffort } : {},
22885
+ mode,
22886
+ costUsd: cost,
22887
+ todos,
22888
+ busy,
22889
+ queued,
22890
+ context: context2,
22891
+ compacted
22892
+ }, undefined, false, undefined, this)
22402
22893
  ]
22403
22894
  }, undefined, true, undefined, this);
22404
22895
  }
22405
- function Header() {
22406
- return /* @__PURE__ */ jsxDEV11(Box10, {
22407
- flexDirection: "column",
22408
- marginBottom: 1,
22409
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22410
- children: "earshot needs a model provider before it can do anything."
22896
+ function ScrollRow({ item }) {
22897
+ if (item.kind === "user") {
22898
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22899
+ marginTop: 1,
22900
+ children: [
22901
+ /* @__PURE__ */ jsxDEV14(Text14, {
22902
+ color: theme.user,
22903
+ children: "> "
22904
+ }, undefined, false, undefined, this),
22905
+ /* @__PURE__ */ jsxDEV14(Text14, {
22906
+ children: item.text
22907
+ }, undefined, false, undefined, this)
22908
+ ]
22909
+ }, undefined, true, undefined, this);
22910
+ }
22911
+ if (item.kind === "assistant") {
22912
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22913
+ marginTop: 1,
22914
+ children: /* @__PURE__ */ jsxDEV14(Markdown, {
22915
+ text: item.text
22916
+ }, undefined, false, undefined, this)
22917
+ }, undefined, false, undefined, this);
22918
+ }
22919
+ if (item.kind === "reasoning") {
22920
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22921
+ marginTop: 1,
22922
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22923
+ color: theme.reasoning,
22924
+ children: item.text
22925
+ }, undefined, false, undefined, this)
22926
+ }, undefined, false, undefined, this);
22927
+ }
22928
+ if (item.kind === "tool") {
22929
+ return /* @__PURE__ */ jsxDEV14(ToolBlock, {
22930
+ name: item.name,
22931
+ ...item.title ? { title: item.title } : {},
22932
+ ...item.output ? { output: item.output } : {},
22933
+ ...item.isError ? { isError: true } : {}
22934
+ }, undefined, false, undefined, this);
22935
+ }
22936
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22937
+ marginTop: 1,
22938
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22939
+ color: item.color ?? theme.muted,
22940
+ children: item.text
22411
22941
  }, undefined, false, undefined, this)
22412
22942
  }, undefined, false, undefined, this);
22413
22943
  }
22414
- function hint(provider) {
22415
- if (provider.configured)
22416
- return provider.configured;
22417
- if (provider.kind === "oauth")
22418
- return "sign in with a browser - no key to paste";
22419
- return provider.envVars?.length ? provider.envVars.join(" or ") : "api key";
22420
- }
22421
- function matchingModels(models, query) {
22422
- const wanted = query.trim().toLowerCase();
22423
- if (!wanted)
22424
- return models;
22425
- return models.filter((model2) => model2.id.toLowerCase().includes(wanted) || model2.name.toLowerCase().includes(wanted));
22426
- }
22427
- function preferredModelIndex(models, wanted) {
22428
- if (!wanted)
22429
- return 0;
22430
- const modelId = wanted.includes("/") ? wanted.slice(wanted.indexOf("/") + 1) : wanted;
22431
- const index = models.findIndex((model2) => model2.id === wanted || model2.id === modelId);
22432
- return Math.max(0, index);
22944
+ function describeCommands(session2) {
22945
+ const lines = commandRows().map((row) => ` ${row.command.padEnd(34)} ${row.summary}`);
22946
+ if (session2.commands.length > 0) {
22947
+ lines.push("", " commands from this directory:");
22948
+ for (const command of session2.commands) {
22949
+ lines.push(` ${`/${command.name}`.padEnd(34)} ${command.description}`);
22950
+ }
22951
+ }
22952
+ lines.push("", " type / at the prompt to filter this list and pick one");
22953
+ return lines.join(`
22954
+ `);
22433
22955
  }
22434
- function order(providers, wanted) {
22435
- if (!wanted)
22436
- return providers;
22437
- return [
22438
- ...providers.filter((provider) => provider.id === wanted),
22439
- ...providers.filter((provider) => provider.id !== wanted)
22440
- ];
22956
+ var INIT_PROMPT = `Write an AGENTS.md at the root of this project for a coding agent that has never seen it.
22957
+
22958
+ Read enough of the repository first to be accurate. Cover: what the project is, how it is laid out, the commands to build, test and lint it, and the conventions and rules that are not obvious from the code. Prefer rules that prevent a specific failure, and say what the failure is. If an AGENTS.md or CLAUDE.md already exists, improve it in place rather than replacing it.`;
22959
+ function describeExtensions(session2) {
22960
+ const lines = [];
22961
+ if (session2.skills.length > 0) {
22962
+ lines.push("skills (the agent loads these itself when they fit):");
22963
+ for (const skill of session2.skills) {
22964
+ lines.push(` ${skill.name} [${skill.scope}] ${skill.description}`);
22965
+ }
22966
+ }
22967
+ if (session2.commands.length > 0) {
22968
+ if (lines.length > 0)
22969
+ lines.push("");
22970
+ lines.push("commands you can type:");
22971
+ for (const command of session2.commands) {
22972
+ lines.push(` /${command.name} [${command.scope}] ${command.description}`);
22973
+ }
22974
+ }
22975
+ return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
22976
+ `);
22441
22977
  }
22442
22978
  // packages/tui/src/run.tsx
22443
22979
  import { platform as platform6 } from "node:os";
22444
22980
  import { render } from "ink";
22445
- import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
22981
+ import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
22446
22982
  var WINDOWS_MAX_FPS = 30;
22447
22983
  async function runTui(options) {
22448
22984
  const isWindows = platform6() === "win32";
22449
- const instance = render(/* @__PURE__ */ jsxDEV12(App, {
22985
+ let resumePath;
22986
+ const instance = render(/* @__PURE__ */ jsxDEV15(App, {
22450
22987
  session: options.session,
22451
22988
  model: options.model,
22452
- ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}
22989
+ ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {},
22990
+ ...options.modelOptions ? { modelOptions: options.modelOptions } : {},
22991
+ onResume: (path) => {
22992
+ resumePath = path;
22993
+ }
22453
22994
  }, undefined, false, undefined, this), {
22454
22995
  exitOnCtrlC: false,
22455
22996
  patchConsole: true,
@@ -22460,12 +23001,12 @@ async function runTui(options) {
22460
23001
  } finally {
22461
23002
  await options.session.dispose();
22462
23003
  }
22463
- return 0;
23004
+ return { exitCode: 0, ...resumePath ? { resumePath } : {} };
22464
23005
  }
22465
23006
  async function runOnboarding(options) {
22466
23007
  const isWindows = platform6() === "win32";
22467
23008
  let result = { outcome: "quit" };
22468
- const instance = render(/* @__PURE__ */ jsxDEV12(Onboarding, {
23009
+ const instance = render(/* @__PURE__ */ jsxDEV15(Onboarding, {
22469
23010
  ...options,
22470
23011
  onDone: (decided) => {
22471
23012
  result = decided;
@@ -22478,17 +23019,31 @@ async function runOnboarding(options) {
22478
23019
  await instance.waitUntilExit();
22479
23020
  return result;
22480
23021
  }
23022
+ async function runSessionPicker(sessions) {
23023
+ const isWindows = platform6() === "win32";
23024
+ let selected;
23025
+ const instance = render(/* @__PURE__ */ jsxDEV15(SessionPicker, {
23026
+ sessions,
23027
+ onDone: (path) => {
23028
+ selected = path;
23029
+ }
23030
+ }, undefined, false, undefined, this), { exitOnCtrlC: false, patchConsole: true, ...isWindows ? { maxFps: WINDOWS_MAX_FPS } : {} });
23031
+ await instance.waitUntilExit();
23032
+ return selected;
23033
+ }
22481
23034
  // packages/cli/src/onboard.ts
22482
23035
  async function buildOnboardingOptions(wanted, wantedModel) {
22483
23036
  const registry2 = buildRegistry();
22484
23037
  const store3 = new AuthStore;
23038
+ const cwd = process.cwd();
23039
+ const settings2 = await loadSettings(cwd);
22485
23040
  const providers = [];
22486
23041
  for (const provider of registry2.list()) {
22487
23042
  if (provider.auth.kind === "none")
22488
23043
  continue;
22489
23044
  providers.push(await describe3(provider, store3));
22490
23045
  }
22491
- providers.sort((a, b) => Number(b.kind === "oauth") - Number(a.kind === "oauth"));
23046
+ providers.sort((a, b) => Number(Boolean(b.configured)) - Number(Boolean(a.configured)) || Number(b.kind === "oauth") - Number(a.kind === "oauth"));
22492
23047
  return {
22493
23048
  providers,
22494
23049
  ...wanted ? { wanted } : {},
@@ -22499,7 +23054,12 @@ async function buildOnboardingOptions(wanted, wantedModel) {
22499
23054
  const credentials = await loginToOpenRouter({ onUrl });
22500
23055
  await store3.set(providerId, credentials);
22501
23056
  },
22502
- probe: (providerId, modelId) => probe(registry2, providerId, modelId)
23057
+ probe: (providerId, modelId) => probe(registry2, providerId, modelId),
23058
+ reasoningFor: (model2) => settings2.reasoningEfforts[model2],
23059
+ remember: async (model2, effort, scope2) => {
23060
+ await persistDefaultModel(model2, scope2, cwd);
23061
+ await persistReasoningEffort(model2, effort, scope2, cwd);
23062
+ }
22503
23063
  };
22504
23064
  }
22505
23065
  async function describe3(provider, store3) {
@@ -22508,7 +23068,11 @@ async function describe3(provider, store3) {
22508
23068
  });
22509
23069
  return {
22510
23070
  id: provider.id,
22511
- models: provider.models().map((model2) => ({ id: model2.id, name: model2.name })),
23071
+ models: provider.models().map((model2) => ({
23072
+ id: model2.id,
23073
+ name: model2.name,
23074
+ reasoning: model2.capabilities.reasoning
23075
+ })),
22512
23076
  kind: provider.auth.kind === "oauth" ? "oauth" : "api-key",
22513
23077
  ...envVarsOf2(provider.auth).length ? { envVars: envVarsOf2(provider.auth) } : {},
22514
23078
  ...configured ? { configured: describeConfigured(configured.type) } : {}
@@ -22564,7 +23128,6 @@ async function probe(registry2, providerId, modelId) {
22564
23128
  }
22565
23129
 
22566
23130
  // packages/cli/src/commands/interactive.ts
22567
- var DEFAULT_MODEL3 = "anthropic/claude-opus-5";
22568
23131
  async function interactiveCommand(args) {
22569
23132
  const flags = args.flags;
22570
23133
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -22605,9 +23168,14 @@ async function interactiveCommand(args) {
22605
23168
  return 2;
22606
23169
  }
22607
23170
  const extensions = await startExtensions(process.cwd());
22608
- let model2 = typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3;
23171
+ let model2 = typeof flags.model === "string" ? flags.model : undefined;
23172
+ let reasoningEffort = parseReasoningEffort2(flags["reasoning-effort"]);
23173
+ if (reasoningEffort === "invalid") {
23174
+ process.stderr.write(`"${flags["reasoning-effort"]}" is not a reasoning effort
23175
+ `);
23176
+ return 2;
23177
+ }
22609
23178
  const onboardingAllowed = flags["no-onboarding"] !== true;
22610
- let firstPrompt = initialPrompt;
22611
23179
  let attempted = false;
22612
23180
  for (;; ) {
22613
23181
  try {
@@ -22616,20 +23184,33 @@ async function interactiveCommand(args) {
22616
23184
  extraTools: extensions.tools,
22617
23185
  problems: extensions.problems,
22618
23186
  onDispose: () => extensions.close(),
22619
- model: model2,
23187
+ ...model2 ? { model: model2 } : {},
23188
+ ...reasoningEffort !== undefined ? { reasoningEffort } : {},
22620
23189
  ...mode ? { mode } : {},
22621
23190
  ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
22622
23191
  ...curiosity ? { curiosity } : {},
22623
23192
  ...maxCostUsd !== undefined ? { maxCostUsd } : {},
22624
23193
  ...resumeFrom2(flags)
22625
23194
  });
22626
- return await runTui({
23195
+ const tui = await runTui({
22627
23196
  session: session2,
22628
- model: model2,
22629
- ...firstPrompt !== "" || image ? {
22630
- initialPrompt: image ? [...firstPrompt ? [{ type: "text", text: firstPrompt }] : [], image] : firstPrompt
23197
+ model: `${session2.agent.model.provider.id}/${session2.agent.model.model.id}`,
23198
+ modelOptions: await buildOnboardingOptions(),
23199
+ ...initialPrompt !== "" || image ? {
23200
+ initialPrompt: image ? [
23201
+ ...initialPrompt ? [{ type: "text", text: initialPrompt }] : [],
23202
+ image
23203
+ ] : initialPrompt
22631
23204
  } : {}
22632
23205
  });
23206
+ if (tui.resumePath) {
23207
+ return interactiveCommand({
23208
+ command: undefined,
23209
+ flags: { ...flags, resume: tui.resumePath, continue: false },
23210
+ positionals: []
23211
+ });
23212
+ }
23213
+ return tui.exitCode;
22633
23214
  } catch (error) {
22634
23215
  if (error instanceof MissingCredentialsError && onboardingAllowed && !attempted) {
22635
23216
  attempted = true;
@@ -22639,9 +23220,11 @@ async function interactiveCommand(args) {
22639
23220
  await extensions.close();
22640
23221
  return 0;
22641
23222
  }
22642
- if (result.model)
23223
+ if (result.model) {
22643
23224
  model2 = result.model;
22644
- firstPrompt = result.firstPrompt ?? firstPrompt;
23225
+ reasoningEffort = result.reasoningEffort;
23226
+ await (await buildOnboardingOptions()).remember?.(result.model, result.reasoningEffort, "global");
23227
+ }
22645
23228
  continue;
22646
23229
  }
22647
23230
  await extensions.close();
@@ -22666,6 +23249,13 @@ run \`earshot models\` to see what is available
22666
23249
  }
22667
23250
  }
22668
23251
  }
23252
+ function parseReasoningEffort2(value) {
23253
+ if (value === undefined)
23254
+ return;
23255
+ if (typeof value !== "string")
23256
+ return "invalid";
23257
+ return ["none", "low", "medium", "high", "xhigh"].includes(value) ? value : value === "auto" ? null : "invalid";
23258
+ }
22669
23259
  function resumeFrom2(flags) {
22670
23260
  if (typeof flags.resume === "string")
22671
23261
  return { resume: { path: flags.resume } };
@@ -22785,6 +23375,29 @@ ${models.length} models across ${new Set(models.map((m) => m.providerId)).size}
22785
23375
  return 0;
22786
23376
  }
22787
23377
 
23378
+ // packages/cli/src/commands/sessions.ts
23379
+ async function sessionsCommand(args) {
23380
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
23381
+ process.stderr.write(`earshot sessions needs an interactive terminal.
23382
+ `);
23383
+ return 2;
23384
+ }
23385
+ const sessions2 = await listSessions(process.cwd());
23386
+ if (sessions2.length === 0) {
23387
+ process.stdout.write(`no saved chats for this project
23388
+ `);
23389
+ return 0;
23390
+ }
23391
+ const path = await runSessionPicker(sessions2);
23392
+ if (!path)
23393
+ return 0;
23394
+ return interactiveCommand({
23395
+ command: undefined,
23396
+ flags: { ...args.flags, resume: path },
23397
+ positionals: []
23398
+ });
23399
+ }
23400
+
22788
23401
  // packages/cli/src/commands/update.ts
22789
23402
  import { spawnSync as spawnSync2 } from "node:child_process";
22790
23403
  import { createHash as createHash4 } from "node:crypto";
@@ -23170,6 +23783,7 @@ Usage
23170
23783
  earshot acp serve editor clients over ACP v1 on stdio
23171
23784
  earshot doctor diagnose the local setup
23172
23785
  earshot update [--check] update earshot to the latest release
23786
+ earshot sessions browse and resume saved chats for this project
23173
23787
 
23174
23788
  Flags
23175
23789
  --model <provider/model> model for this session
@@ -23181,6 +23795,7 @@ Flags
23181
23795
  --image <path|https-url> attach one PNG, JPEG, GIF or WebP image
23182
23796
  --max-cost <usd> stop and ask before spending past this
23183
23797
  --curiosity <level> low, normal or high: how readily it asks
23798
+ --reasoning-effort <level> auto | none | low | medium | high | xhigh
23184
23799
  --version, -v print the version
23185
23800
  --help, -h print this help
23186
23801
  `;
@@ -23211,6 +23826,8 @@ async function main(argv = process.argv.slice(2)) {
23211
23826
  return doctorCommand(args);
23212
23827
  if (command === "update")
23213
23828
  return updateCommand(args);
23829
+ if (command === "sessions")
23830
+ return sessionsCommand(args);
23214
23831
  if (command === "acp")
23215
23832
  return acpCommand(args);
23216
23833
  if (command) {
@@ -23225,5 +23842,5 @@ async function main(argv = process.argv.slice(2)) {
23225
23842
  var code = await main();
23226
23843
  process.exitCode = code;
23227
23844
 
23228
- //# debugId=50400322D722C83B64756E2164756E21
23845
+ //# debugId=BF0091864A65A6E164756E2164756E21
23229
23846
  //# sourceMappingURL=main.js.map