@8gi-foundation/8gent-code 0.20.15 → 0.20.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js CHANGED
@@ -18697,8 +18697,6 @@ var init_allowlist = __esm(() => {
18697
18697
  "node",
18698
18698
  "npm",
18699
18699
  "npx",
18700
- "pnpm",
18701
- "yarn",
18702
18700
  "deno",
18703
18701
  "python",
18704
18702
  "python3",
@@ -18706,9 +18704,6 @@ var init_allowlist = __esm(() => {
18706
18704
  "pip3",
18707
18705
  "uv",
18708
18706
  "git",
18709
- "go",
18710
- "cargo",
18711
- "rustc",
18712
18707
  "tsc",
18713
18708
  "gh",
18714
18709
  "docker",
@@ -18733,20 +18728,18 @@ var init_allowlist = __esm(() => {
18733
18728
  "biome",
18734
18729
  "eslint",
18735
18730
  "prettier",
18736
- "vite"
18731
+ "vite",
18732
+ "8gent"
18737
18733
  ]);
18738
18734
  SHORT_VERSION = {
18739
18735
  node: "-v",
18740
18736
  bun: "-v",
18741
18737
  npm: "-v",
18742
- pnpm: "-v",
18743
- yarn: "-v",
18744
18738
  deno: "-V",
18745
18739
  python: "-V",
18746
18740
  python3: "-V",
18747
18741
  ffmpeg: "-version",
18748
- ffprobe: "-version",
18749
- go: "version"
18742
+ ffprobe: "-version"
18750
18743
  };
18751
18744
  SECRET_ARG = /(^|\/)\.env(\.[\w.-]+)?$|(^|\/)\.env\b|id_rsa|id_ed25519|id_ecdsa|(^|\/)\.ssh(\/|$)|\.aws\/|credentials|\.netrc|\.npmrc|\.git-credentials|keychain|\.pem$|\.key$|\.p12$/;
18752
18745
  REDIR_RE2 = /(?<![<>&\d])(\d?|&)(>>?|<)(\|?)\s*(&\d+|[^\s;|&<>()]+)/g;
@@ -29981,7 +29974,7 @@ var init_permissions = __esm(() => {
29981
29974
  var require_package = __commonJS(function(exports2, module2) {
29982
29975
  module2.exports = {
29983
29976
  name: "@8gi-foundation/8gent-code",
29984
- version: "0.20.15",
29977
+ version: "0.20.17",
29985
29978
  private: false,
29986
29979
  engines: {
29987
29980
  node: ">=22.5"
@@ -63483,6 +63476,12 @@ var init_slash_commands = __esm(() => {
63483
63476
  description: "Enable infinite mode (autonomous until done)",
63484
63477
  usage: "/infinite [task]"
63485
63478
  },
63479
+ {
63480
+ name: "mode",
63481
+ aliases: [],
63482
+ description: "Set this tab's permission mode: plan, ask, guarded or infinite (same as Shift+Tab)",
63483
+ usage: "/mode <plan|ask|guarded|infinite>"
63484
+ },
63486
63485
  {
63487
63486
  name: "onboarding",
63488
63487
  aliases: ["onboard", "setup", "intro"],
@@ -74427,8 +74426,31 @@ function emptyReplyStall(toolCalls) {
74427
74426
  function isEmptyReplyStall(line) {
74428
74427
  return line.startsWith(EMPTY_REPLY_STALL_PREFIX);
74429
74428
  }
74430
- function emptyReplyNote(toolCalls) {
74431
- return `[harness] No reply: ${emptyReplyStall(toolCalls)}.`;
74429
+ function refusedCalls(log, max = 3) {
74430
+ const out = [];
74431
+ let total = 0;
74432
+ for (const e of log) {
74433
+ const m = /^\[[^\]]*\b(?:BLOCKED|DENIED)\b[^\]]*\]\s*([^\n]*)/.exec((e.result ?? "").trimStart());
74434
+ if (!m)
74435
+ continue;
74436
+ total++;
74437
+ if (out.length >= max)
74438
+ continue;
74439
+ const a = e.args;
74440
+ const first = typeof a?.command === "string" ? a.command.trim().split(/\s+/)[0] ?? "" : "";
74441
+ const bin = /^[A-Za-z][\w.+-]{0,40}$/.test(first) ? ` ${first}` : "";
74442
+ const why = /declin/i.test(m[1]) ? "declined" : "blocked by policy";
74443
+ out.push(`${e.name}${bin}: ${why}`);
74444
+ }
74445
+ return { total, shown: out };
74446
+ }
74447
+ function emptyReplyNote(toolCalls, log = []) {
74448
+ const base = `[harness] No reply: ${emptyReplyStall(toolCalls)}.`;
74449
+ const { total, shown } = refusedCalls(log);
74450
+ if (total === 0)
74451
+ return base;
74452
+ const more = total > shown.length ? ` (and ${total - shown.length} more)` : "";
74453
+ return `${base} ${total} of the calls were refused, so they did not run: ${shown.join("; ")}${more}.`;
74432
74454
  }
74433
74455
  function isEmptyReplyNote(reply) {
74434
74456
  return reply.startsWith("[harness] No reply:");
@@ -74521,7 +74543,7 @@ ${summary}`;
74521
74543
  if (unverified.length === 0)
74522
74544
  return { content, rounds, toolLog, unverified: [] };
74523
74545
  const notes = [
74524
- empty ? emptyReplyNote(toolLog.length) : content.trimEnd(),
74546
+ empty ? emptyReplyNote(toolLog.length, toolLog) : content.trimEnd(),
74525
74547
  unfulfilled.length > 0 ? formatHarnessNote(unfulfilled) : ""
74526
74548
  ].filter((n) => n.trim() !== "");
74527
74549
  return { content: notes.join(`
@@ -547329,7 +547351,7 @@ onboarding preferences skip animations adhd quarantine toolshed skills design ev
547329
547351
  resume history continue compact chat agent github debug music dj pet export fork rewind branch cron deploy
547330
547352
  vercel telegram router rename spawn settings quiet term create-app hands eyes handeyes goal subgoal build
547331
547353
  notes ideas btw questions projects terminal reload exit
547332
- copy hotkeys tree import update route privacy consult bug me allow-read`.split(/\s+/));
547354
+ copy hotkeys tree import update route privacy consult bug me allow-read mode`.split(/\s+/));
547333
547355
  });
547334
547356
 
547335
547357
  // packages/extensions/index.ts
@@ -553413,7 +553435,9 @@ function defaultSessionsDir2() {
553413
553435
 
553414
553436
  class SessionWriter {
553415
553437
  filePath;
553416
- fd;
553438
+ fd = -1;
553439
+ dev = -1;
553440
+ ino = -1;
553417
553441
  persist;
553418
553442
  seq = 0;
553419
553443
  sessionId;
@@ -553445,15 +553469,47 @@ class SessionWriter {
553445
553469
  fs78.mkdirSync(dir, { recursive: true });
553446
553470
  }
553447
553471
  this.filePath = path107.join(dir, `${sessionId}.jsonl`);
553448
- this.fd = fs78.openSync(this.filePath, "a");
553472
+ this.open();
553473
+ }
553474
+ open() {
553475
+ const c = fs78.constants;
553476
+ const fd = fs78.openSync(this.filePath, c.O_WRONLY | c.O_APPEND | c.O_CREAT | (c.O_NOFOLLOW ?? 0), 384);
553477
+ const st = fs78.fstatSync(fd);
553478
+ if (!st.isFile()) {
553479
+ fs78.closeSync(fd);
553480
+ throw new Error(`${this.filePath} is not a regular file`);
553481
+ }
553482
+ this.fd = fd;
553483
+ this.dev = st.dev;
553484
+ this.ino = st.ino;
553485
+ }
553486
+ fdIsOurs() {
553487
+ if (this.fd < 0)
553488
+ return false;
553489
+ try {
553490
+ const st = fs78.fstatSync(this.fd);
553491
+ return st.dev === this.dev && st.ino === this.ino;
553492
+ } catch {
553493
+ return false;
553494
+ }
553449
553495
  }
553450
553496
  write(entry) {
553451
553497
  if (!this.persist)
553452
553498
  return;
553453
553499
  const line = `${JSON.stringify(entry)}
553454
553500
  `;
553455
- fs78.writeSync(this.fd, line);
553501
+ try {
553502
+ if (!this.fdIsOurs())
553503
+ this.open();
553504
+ fs78.writeSync(this.fd, line);
553505
+ } catch (err) {
553506
+ this.fd = -1;
553507
+ this.skippedWrites++;
553508
+ this.lastWriteError = `session log not written: ${err instanceof Error ? err.message : String(err)}`;
553509
+ }
553456
553510
  }
553511
+ lastWriteError = null;
553512
+ skippedWrites = 0;
553457
553513
  nextSeq() {
553458
553514
  return this.seq++;
553459
553515
  }
@@ -553663,8 +553719,9 @@ class SessionWriter {
553663
553719
  reportId: reportId ?? null
553664
553720
  }
553665
553721
  });
553666
- if (this.persist)
553722
+ if (this.persist && this.fdIsOurs())
553667
553723
  fs78.closeSync(this.fd);
553724
+ this.fd = -1;
553668
553725
  }
553669
553726
  getFilePath() {
553670
553727
  return this.filePath;
@@ -557382,6 +557439,52 @@ var init_registry = __esm(() => {
557382
557439
  ];
557383
557440
  });
557384
557441
 
557442
+ // packages/eight/small-talk.ts
557443
+ function flatten2(text) {
557444
+ return text.toLowerCase().replace(/^\[mode: [^\]]*\]\s*/, "").replace(/['\u2019]/g, "").replace(/[^a-z0-9 ]+/g, " ").replace(/\s+/g, " ").trim();
557445
+ }
557446
+ function isSmallTalk(text) {
557447
+ const flat = flatten2(text);
557448
+ return flat.length > 0 && flat.length <= 40 && GREETING.test(flat);
557449
+ }
557450
+ function isGreeting(text) {
557451
+ return isSmallTalk(text) && !THANKS.test(flatten2(text));
557452
+ }
557453
+ var CORE, GREETING, THANKS, SMALL_TALK_NOTE = "[SMALL TALK] The person said hello and asked for nothing. Reply in one or two plain sentences and ask what they want to work on. No tools this turn: do not look around, list files or describe their folders.";
557454
+ var init_small_talk = __esm(() => {
557455
+ CORE = [
557456
+ "h+i+",
557457
+ "h+e+y+",
557458
+ "h+e+l+o+",
557459
+ "hiya",
557460
+ "howdy",
557461
+ "y+o+",
557462
+ "sup",
557463
+ "wa+s+s?u+p+",
557464
+ "w?h?a+t?s? ?u+p+",
557465
+ "wh?a+t+s? go+d+",
557466
+ "good (?:morning|afternoon|evening|night)",
557467
+ "gm",
557468
+ "gn",
557469
+ "morning",
557470
+ "evening",
557471
+ "how are you(?: doing| today)?",
557472
+ "how are things",
557473
+ "hows it going",
557474
+ "how you doing",
557475
+ "you there",
557476
+ "anyone there",
557477
+ "ping",
557478
+ "thanks(?: a lot| so much)?",
557479
+ "thank you(?: so much)?",
557480
+ "thx",
557481
+ "ty",
557482
+ "cheers"
557483
+ ].join("|");
557484
+ GREETING = new RegExp(`^(?:(?:hi+|hey+|hello+|yo+|hiya|howdy) )?(?:${CORE})(?: (?:there|again|everyone|all|team|mate|man|bro|friend|8gent|eight))?$`);
557485
+ THANKS = /^(?:thanks|thank you|thx|ty|cheers)\b/;
557486
+ });
557487
+
557385
557488
  // packages/eight/verify-gate.ts
557386
557489
  function isVerifyGateEnabled(env = process.env) {
557387
557490
  const v = (env.EIGHT_VERIFY_GATE ?? "").trim().toLowerCase();
@@ -557424,6 +557527,755 @@ var init_verify_gate = __esm(() => {
557424
557527
  `);
557425
557528
  });
557426
557529
 
557530
+ // packages/voice/termux.ts
557531
+ import { constants as constants16, accessSync as accessSync4, readFileSync as readFileSync96 } from "fs";
557532
+ import { delimiter as delimiter7, join as join154 } from "path";
557533
+ function isAndroidHost(platform = process.platform, env = process.env) {
557534
+ return platform === "android" || !!env.TERMUX_VERSION || String(env.PREFIX ?? "").includes("/com.termux/");
557535
+ }
557536
+ function findOnPath(name, env = process.env) {
557537
+ for (const dir of String(env.PATH ?? "").split(delimiter7)) {
557538
+ if (!dir)
557539
+ continue;
557540
+ const candidate = join154(dir, name);
557541
+ try {
557542
+ accessSync4(candidate, constants16.X_OK);
557543
+ return candidate;
557544
+ } catch {}
557545
+ }
557546
+ return null;
557547
+ }
557548
+ function voiceOutputOn(env = process.env, readSettings = () => {
557549
+ const home = env.EIGHT_HOME || join154(env.HOME ?? "", ".8gent");
557550
+ return readFileSync96(join154(home, "settings.json"), "utf8");
557551
+ }) {
557552
+ if (env.EIGHT_VOICE === "1")
557553
+ return true;
557554
+ try {
557555
+ return JSON.parse(readSettings())?.voice?.outputEnabled === true;
557556
+ } catch {
557557
+ return false;
557558
+ }
557559
+ }
557560
+ function ctrlRAction(key, input, android = isAndroidHost()) {
557561
+ if (!key.ctrl || input !== "r")
557562
+ return "ignore";
557563
+ return android ? "android-line" : "toggle";
557564
+ }
557565
+ var ANDROID_DICTATION_LINE = "On Android, tap the microphone key on your keyboard; dictation lands in the prompt. Spoken replies: /voice on (needs termux-tts-speak: pkg install termux-api plus the Termux:API app).", TERMUX_TTS_HINT = "Spoken replies need termux-tts-speak: pkg install termux-api, plus the Termux:API app from F-Droid.", ANDROID_VOICE_LINE_SUBS;
557566
+ var init_termux = __esm(() => {
557567
+ ANDROID_VOICE_LINE_SUBS = [
557568
+ "",
557569
+ "chat",
557570
+ "conversation",
557571
+ "talk",
557572
+ "record",
557573
+ "listen",
557574
+ "stt",
557575
+ "status",
557576
+ "stop"
557577
+ ];
557578
+ });
557579
+
557580
+ // packages/runtime/node-runtime.ts
557581
+ import { execFile as execFile7 } from "child_process";
557582
+ import { existsSync as existsSync104 } from "fs";
557583
+ import { homedir as homedir78 } from "os";
557584
+ import { join as join155 } from "path";
557585
+ import { promisify as promisify6 } from "util";
557586
+ function parseNodeVersion(raw) {
557587
+ const trimmed = raw.trim();
557588
+ const match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
557589
+ if (!match)
557590
+ return null;
557591
+ return {
557592
+ major: Number.parseInt(match[1], 10),
557593
+ minor: Number.parseInt(match[2], 10),
557594
+ patch: Number.parseInt(match[3], 10)
557595
+ };
557596
+ }
557597
+ function satisfiesMinVersion(v, min) {
557598
+ const minParsed = parseNodeVersion(min);
557599
+ if (!minParsed)
557600
+ return true;
557601
+ if (v.major !== minParsed.major)
557602
+ return v.major > minParsed.major;
557603
+ if (v.minor !== minParsed.minor)
557604
+ return v.minor > minParsed.minor;
557605
+ return v.patch >= minParsed.patch;
557606
+ }
557607
+ function pickPlatformAsset(input) {
557608
+ const byArch = SUPPORTED[input.platform];
557609
+ if (!byArch) {
557610
+ throw new Error(`unsupported platform: ${input.platform}`);
557611
+ }
557612
+ const asset = byArch[input.arch];
557613
+ if (!asset) {
557614
+ throw new Error(`unsupported arch: ${input.arch} on ${input.platform}`);
557615
+ }
557616
+ return asset;
557617
+ }
557618
+ function buildDownloadUrl(opts) {
557619
+ const cleanVersion = opts.version.startsWith("v") ? opts.version.slice(1) : opts.version;
557620
+ const v = `v${cleanVersion}`;
557621
+ const fileBase = `node-${v}-${opts.asset.os}-${opts.asset.arch}`;
557622
+ return `https://nodejs.org/dist/${v}/${fileBase}.${opts.asset.ext}`;
557623
+ }
557624
+ function defaultRoot() {
557625
+ return join155(homedir78(), ".8gent", "runtime");
557626
+ }
557627
+ function getRuntimeDir(loc) {
557628
+ const root = loc.root ?? defaultRoot();
557629
+ return join155(root, `node-${loc.version}`);
557630
+ }
557631
+ function resolvedNodeBinPath(opts) {
557632
+ const dir = getRuntimeDir(opts);
557633
+ if (opts.platform === "win32") {
557634
+ return join155(dir, "node.exe");
557635
+ }
557636
+ return join155(dir, "bin", "node");
557637
+ }
557638
+ async function isRuntimeReady(opts) {
557639
+ const platform = opts.platform ?? process.platform;
557640
+ const bin = resolvedNodeBinPath({
557641
+ root: opts.root,
557642
+ version: opts.version,
557643
+ platform
557644
+ });
557645
+ if (!existsSync104(bin))
557646
+ return false;
557647
+ const min = opts.minVersion ?? opts.version;
557648
+ try {
557649
+ const { stdout } = await execFileAsync4(bin, ["--version"], {
557650
+ timeout: 3000
557651
+ });
557652
+ const parsed = parseNodeVersion(stdout);
557653
+ if (!parsed)
557654
+ return false;
557655
+ return satisfiesMinVersion(parsed, min);
557656
+ } catch {
557657
+ return false;
557658
+ }
557659
+ }
557660
+ var execFileAsync4, SUPPORTED;
557661
+ var init_node_runtime = __esm(() => {
557662
+ execFileAsync4 = promisify6(execFile7);
557663
+ SUPPORTED = {
557664
+ darwin: {
557665
+ arm64: { os: "darwin", arch: "arm64", ext: "tar.gz" },
557666
+ x64: { os: "darwin", arch: "x64", ext: "tar.gz" }
557667
+ },
557668
+ linux: {
557669
+ arm64: { os: "linux", arch: "arm64", ext: "tar.xz" },
557670
+ x64: { os: "linux", arch: "x64", ext: "tar.xz" }
557671
+ },
557672
+ win32: {
557673
+ x64: { os: "win", arch: "x64", ext: "zip" },
557674
+ arm64: { os: "win", arch: "arm64", ext: "zip" }
557675
+ }
557676
+ };
557677
+ });
557678
+
557679
+ // packages/runtime/installer.ts
557680
+ import { spawn as spawn26 } from "child_process";
557681
+ import {
557682
+ createWriteStream,
557683
+ existsSync as existsSync105,
557684
+ mkdirSync as mkdirSync74,
557685
+ mkdtempSync as mkdtempSync11,
557686
+ readdirSync as readdirSync26,
557687
+ renameSync as renameSync16,
557688
+ rmSync as rmSync14
557689
+ } from "fs";
557690
+ import { join as join156 } from "path";
557691
+ import { Readable } from "stream";
557692
+ import { pipeline } from "stream/promises";
557693
+ function planInstall(input) {
557694
+ const platform = input.platform ?? process.platform;
557695
+ const arch = input.arch ?? process.arch;
557696
+ const asset = pickPlatformAsset({ platform, arch });
557697
+ const url = buildDownloadUrl({ version: input.version, asset });
557698
+ const runtimeDir = getRuntimeDir({ root: input.root, version: input.version });
557699
+ const binPath = resolvedNodeBinPath({
557700
+ root: input.root,
557701
+ version: input.version,
557702
+ platform
557703
+ });
557704
+ return {
557705
+ version: input.version,
557706
+ url,
557707
+ asset,
557708
+ runtimeDir,
557709
+ binPath,
557710
+ archiveExt: asset.ext
557711
+ };
557712
+ }
557713
+ async function installRuntime(input) {
557714
+ const plan = planInstall(input);
557715
+ const platform = input.platform ?? process.platform;
557716
+ const minVersion = input.minVersion ?? input.version;
557717
+ const ready = await isRuntimeReady({
557718
+ root: input.root,
557719
+ version: input.version,
557720
+ platform,
557721
+ minVersion
557722
+ });
557723
+ if (ready || input.dryRun) {
557724
+ return {
557725
+ ...plan,
557726
+ action: ready ? "already-ready" : "dry-run"
557727
+ };
557728
+ }
557729
+ const runtimeRoot = plan.runtimeDir.replace(/\/node-[^/]+$/, "");
557730
+ mkdirSync74(runtimeRoot, { recursive: true });
557731
+ const stagingDir = mkdtempSync11(join156(runtimeRoot, ".dl-"));
557732
+ const archivePath = join156(stagingDir, `node.${plan.archiveExt}`);
557733
+ try {
557734
+ await downloadFile(plan.url, archivePath);
557735
+ const extractRoot = join156(stagingDir, "extract");
557736
+ mkdirSync74(extractRoot, { recursive: true });
557737
+ await extractArchive(archivePath, extractRoot, plan.archiveExt);
557738
+ const children = readdirSync26(extractRoot);
557739
+ const nodeDir = children.find((n) => n.startsWith("node-"));
557740
+ if (!nodeDir) {
557741
+ throw new Error(`could not find node-* dir inside ${extractRoot}`);
557742
+ }
557743
+ const extractedPath = join156(extractRoot, nodeDir);
557744
+ if (existsSync105(plan.runtimeDir)) {
557745
+ rmSync14(plan.runtimeDir, { recursive: true, force: true });
557746
+ }
557747
+ renameSync16(extractedPath, plan.runtimeDir);
557748
+ } catch (err) {
557749
+ return {
557750
+ ...plan,
557751
+ action: "failed",
557752
+ error: err instanceof Error ? err.message : String(err)
557753
+ };
557754
+ } finally {
557755
+ try {
557756
+ rmSync14(stagingDir, { recursive: true, force: true });
557757
+ } catch {}
557758
+ }
557759
+ const reReady = await isRuntimeReady({
557760
+ root: input.root,
557761
+ version: input.version,
557762
+ platform,
557763
+ minVersion
557764
+ });
557765
+ if (!reReady) {
557766
+ return {
557767
+ ...plan,
557768
+ action: "failed",
557769
+ error: `installed runtime at ${plan.binPath} but it does not satisfy minVersion ${minVersion}`
557770
+ };
557771
+ }
557772
+ return {
557773
+ ...plan,
557774
+ action: "installed"
557775
+ };
557776
+ }
557777
+ async function downloadFile(url, dest) {
557778
+ const res = await fetch(url, { redirect: "follow" });
557779
+ if (!res.ok || !res.body) {
557780
+ throw new Error(`download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
557781
+ }
557782
+ const out = createWriteStream(dest);
557783
+ await pipeline(Readable.fromWeb(res.body), out);
557784
+ }
557785
+ async function extractArchive(archivePath, destDir, ext) {
557786
+ if (ext === "tar.gz" || ext === "tar.xz") {
557787
+ await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
557788
+ } else if (ext === "zip") {
557789
+ try {
557790
+ await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
557791
+ } catch {
557792
+ await runCmd("powershell", [
557793
+ "-Command",
557794
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
557795
+ ]);
557796
+ }
557797
+ } else {
557798
+ throw new Error(`unsupported archive ext: ${ext}`);
557799
+ }
557800
+ }
557801
+ function runCmd(command, args) {
557802
+ return new Promise((resolve, reject) => {
557803
+ const proc = spawn26(command, args, { stdio: "ignore" });
557804
+ proc.on("error", (err) => reject(err));
557805
+ proc.on("close", (code) => {
557806
+ if (code === 0)
557807
+ resolve();
557808
+ else
557809
+ reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
557810
+ });
557811
+ });
557812
+ }
557813
+ var init_installer = __esm(() => {
557814
+ init_node_runtime();
557815
+ });
557816
+
557817
+ // packages/runtime/contract-validator.ts
557818
+ var init_contract_validator = __esm(() => {
557819
+ init_types3();
557820
+ });
557821
+
557822
+ // packages/runtime/index.ts
557823
+ import { join as join157 } from "path";
557824
+ import { execFile as execFile8 } from "child_process";
557825
+ import { promisify as promisify7 } from "util";
557826
+ async function ensureNodeFor(minVersion) {
557827
+ try {
557828
+ const { stdout } = await execFileAsync5("node", ["--version"], {
557829
+ timeout: 3000
557830
+ });
557831
+ const parsed = parseNodeVersion(stdout);
557832
+ if (parsed && satisfiesMinVersion(parsed, minVersion)) {
557833
+ return null;
557834
+ }
557835
+ } catch {}
557836
+ const ready = await isRuntimeReady({
557837
+ version: MANAGED_NODE_VERSION,
557838
+ minVersion
557839
+ });
557840
+ const dir = getRuntimeDir({ version: MANAGED_NODE_VERSION });
557841
+ const binDir = process.platform === "win32" ? dir : join157(dir, "bin");
557842
+ if (ready)
557843
+ return binDir;
557844
+ const result = await installRuntime({
557845
+ version: MANAGED_NODE_VERSION,
557846
+ minVersion
557847
+ });
557848
+ if (result.action === "failed") {
557849
+ throw new Error(`failed to install Node ${MANAGED_NODE_VERSION}: ${result.error ?? "unknown"}`);
557850
+ }
557851
+ return binDir;
557852
+ }
557853
+ var execFileAsync5, MANAGED_NODE_VERSION = "22.14.0";
557854
+ var init_runtime = __esm(() => {
557855
+ init_installer();
557856
+ init_node_runtime();
557857
+ init_node_runtime();
557858
+ init_installer();
557859
+ init_contract_validator();
557860
+ execFileAsync5 = promisify7(execFile8);
557861
+ });
557862
+
557863
+ // apps/tui/src/lib/external-agent-runner.ts
557864
+ import { execSync as execSync6, spawn as spawn27 } from "child_process";
557865
+ function getPreset(id) {
557866
+ return EXTERNAL_AGENT_PRESETS[id.toLowerCase()] ?? null;
557867
+ }
557868
+ function listPresetIds() {
557869
+ return Object.keys(EXTERNAL_AGENT_PRESETS);
557870
+ }
557871
+ function isInstalled(preset) {
557872
+ try {
557873
+ execSync6(`command -v "${preset.command}"`, {
557874
+ stdio: "ignore",
557875
+ timeout: 1500
557876
+ });
557877
+ return true;
557878
+ } catch {
557879
+ return false;
557880
+ }
557881
+ }
557882
+ async function installAgent(preset, onLine) {
557883
+ const start = performance.now();
557884
+ if (!preset.install) {
557885
+ return {
557886
+ ok: false,
557887
+ stdout: "",
557888
+ stderr: "",
557889
+ durationMs: 0,
557890
+ command: "",
557891
+ error: `No install recipe for ${preset.id}. Install ${preset.command} manually and try again.`
557892
+ };
557893
+ }
557894
+ const command = preset.install.command;
557895
+ return new Promise((resolve) => {
557896
+ let stdout = "";
557897
+ let stderr = "";
557898
+ let exited = false;
557899
+ const proc = spawnShell(command, {
557900
+ posix: ["bash", "-lc"],
557901
+ stdio: ["ignore", "pipe", "pipe"],
557902
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" }
557903
+ });
557904
+ const finish = (result) => {
557905
+ if (exited)
557906
+ return;
557907
+ exited = true;
557908
+ clearTimeout(timer);
557909
+ resolve({
557910
+ ...result,
557911
+ durationMs: Math.round(performance.now() - start),
557912
+ command
557913
+ });
557914
+ };
557915
+ const pipeLines = (chunk, source) => {
557916
+ const text = chunk.toString();
557917
+ if (source === "stdout")
557918
+ stdout += text;
557919
+ else
557920
+ stderr += text;
557921
+ if (onLine) {
557922
+ for (const line of text.split(/\r?\n/)) {
557923
+ if (line.trim().length > 0)
557924
+ onLine(line, source);
557925
+ }
557926
+ }
557927
+ };
557928
+ proc.stdout?.on("data", (c) => pipeLines(c, "stdout"));
557929
+ proc.stderr?.on("data", (c) => pipeLines(c, "stderr"));
557930
+ proc.on("error", (err) => {
557931
+ finish({ ok: false, stdout, stderr, error: err.message });
557932
+ });
557933
+ proc.on("close", (code) => {
557934
+ finish({
557935
+ ok: code === 0,
557936
+ stdout,
557937
+ stderr,
557938
+ error: code === 0 ? undefined : stderr.trim().slice(0, 800) || `installer exited with code ${code}`
557939
+ });
557940
+ });
557941
+ const timer = setTimeout(() => {
557942
+ proc.kill("SIGTERM");
557943
+ finish({ ok: false, stdout, stderr, error: "install timed out after 5 minutes" });
557944
+ }, 5 * 60 * 1000);
557945
+ });
557946
+ }
557947
+ async function ensureInstalled(preset, onLine) {
557948
+ if (isInstalled(preset))
557949
+ return true;
557950
+ if (!preset.install) {
557951
+ onLine?.(`${preset.command}: not installed and no auto-install recipe is configured.`, "info");
557952
+ return false;
557953
+ }
557954
+ onLine?.(`${preset.command}: not installed, running ${preset.install.command}`, "info");
557955
+ const result = await installAgent(preset, (line, source) => onLine?.(line, source));
557956
+ if (!result.ok) {
557957
+ onLine?.(`Install failed: ${result.error ?? "unknown error"}`, "info");
557958
+ return false;
557959
+ }
557960
+ const stillThere = isInstalled(preset);
557961
+ if (!stillThere) {
557962
+ onLine?.(`Install reported success but ${preset.command} is still not on $PATH. You may need to open a new shell or add the install dir to PATH.`, "info");
557963
+ }
557964
+ return stillThere;
557965
+ }
557966
+ function composePrompt(history, prompt) {
557967
+ const lines = [];
557968
+ for (const m of history) {
557969
+ const tag = m.role === "user" ? "User" : m.role === "assistant" ? "Assistant" : m.role;
557970
+ lines.push(`${tag}: ${m.content}`);
557971
+ }
557972
+ lines.push(`User: ${prompt}`);
557973
+ lines.push("Assistant:");
557974
+ return lines.join(`
557975
+
557976
+ `);
557977
+ }
557978
+ async function runExternalAgent(preset, prompt, signal) {
557979
+ const start = performance.now();
557980
+ const argv = [...preset.args];
557981
+ if (preset.promptMode === "flag") {
557982
+ argv.push(preset.flagName ?? "-p", prompt);
557983
+ } else if (preset.promptMode === "arg") {
557984
+ argv.push(prompt);
557985
+ }
557986
+ let pathPrefix = "";
557987
+ if (preset.requiresNodeVersion) {
557988
+ try {
557989
+ await Promise.resolve().then(() => init_runtime());
557990
+ const binDir = await ensureNodeFor(preset.requiresNodeVersion);
557991
+ if (binDir)
557992
+ pathPrefix = `${binDir}:`;
557993
+ } catch (err) {
557994
+ return {
557995
+ ok: false,
557996
+ text: "",
557997
+ durationMs: Math.round(performance.now() - start),
557998
+ command: `${preset.command} ${argv.join(" ")}`,
557999
+ error: `managed Node runtime install failed: ${err instanceof Error ? err.message : String(err)}`,
558000
+ exitCode: null
558001
+ };
558002
+ }
558003
+ }
558004
+ return new Promise((resolve) => {
558005
+ let stdout = "";
558006
+ let stderr = "";
558007
+ let exited = false;
558008
+ const proc = spawn27(preset.command, argv, {
558009
+ stdio: ["pipe", "pipe", "pipe"],
558010
+ env: {
558011
+ ...process.env,
558012
+ PATH: `${pathPrefix}${process.env.PATH ?? ""}`,
558013
+ NO_COLOR: "1",
558014
+ FORCE_COLOR: "0",
558015
+ "8GENT_NO_INTRO": "1",
558016
+ AI_SDK_LOG_WARNINGS: "false"
558017
+ }
558018
+ });
558019
+ const finish = (result) => {
558020
+ if (exited)
558021
+ return;
558022
+ exited = true;
558023
+ clearTimeout(timer);
558024
+ signal?.removeEventListener("abort", onAbort);
558025
+ resolve({
558026
+ ...result,
558027
+ durationMs: Math.round(performance.now() - start),
558028
+ command: `${preset.command} ${argv.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`
558029
+ });
558030
+ };
558031
+ proc.on("error", (err) => {
558032
+ finish({
558033
+ ok: false,
558034
+ text: "",
558035
+ error: err.code === "ENOENT" ? `${preset.command}: not installed (no binary on $PATH)` : err.message,
558036
+ exitCode: null
558037
+ });
558038
+ });
558039
+ proc.stdout?.on("data", (chunk) => {
558040
+ stdout += chunk.toString();
558041
+ });
558042
+ proc.stderr?.on("data", (chunk) => {
558043
+ stderr += chunk.toString();
558044
+ });
558045
+ proc.on("close", (code) => {
558046
+ let text = preset.parseStdout ? preset.parseStdout(stdout).trim() : stdout.trim();
558047
+ try {
558048
+ init_keys();
558049
+ if (text)
558050
+ text = redactAndWarn(text);
558051
+ } catch {}
558052
+ if (code === 0 || code !== null && text.length > 0) {
558053
+ finish({ ok: code === 0, text, exitCode: code, error: code === 0 ? undefined : stderr.trim().slice(0, 400) });
558054
+ } else {
558055
+ finish({
558056
+ ok: false,
558057
+ text,
558058
+ exitCode: code,
558059
+ error: stderr.trim().slice(0, 400) || `exited with code ${code}`
558060
+ });
558061
+ }
558062
+ });
558063
+ if (preset.promptMode === "stdin" && proc.stdin) {
558064
+ proc.stdin.write(prompt);
558065
+ proc.stdin.end();
558066
+ } else if (proc.stdin) {
558067
+ proc.stdin.end();
558068
+ }
558069
+ const timer = setTimeout(() => {
558070
+ proc.kill("SIGTERM");
558071
+ finish({
558072
+ ok: false,
558073
+ text: stdout,
558074
+ error: `timeout after ${preset.timeoutMs ?? 90000}ms`,
558075
+ exitCode: null
558076
+ });
558077
+ }, preset.timeoutMs ?? 90000);
558078
+ const onAbort = () => {
558079
+ proc.kill("SIGTERM");
558080
+ finish({ ok: false, text: stdout, error: "aborted", exitCode: null });
558081
+ };
558082
+ signal?.addEventListener("abort", onAbort, { once: true });
558083
+ });
558084
+ }
558085
+ var stripAnsi2 = (s) => s.replace(/\u001B\[[0-9;?]*[a-zA-Z]/g, "").replace(/\u001B\][^\u0007]*\u0007/g, ""), EXTERNAL_AGENT_PRESETS;
558086
+ var init_external_agent_runner = __esm(() => {
558087
+ init_shell();
558088
+ EXTERNAL_AGENT_PRESETS = {
558089
+ claude: {
558090
+ id: "claude",
558091
+ label: "Sparring",
558092
+ command: "claude",
558093
+ promptMode: "flag",
558094
+ flagName: "-p",
558095
+ args: [],
558096
+ timeoutMs: 120000,
558097
+ parseStdout: stripAnsi2,
558098
+ homepage: "https://docs.claude.com/en/docs/claude-code",
558099
+ preferWindow: true,
558100
+ install: {
558101
+ command: "npm install -g --prefix=$HOME/.npm-global @anthropic-ai/claude-code",
558102
+ notes: 'Binary at ~/.npm-global/bin/claude. If `claude` isn\'t on PATH after install, add `export PATH="$HOME/.npm-global/bin:$PATH"` to your shell rc. Then run `claude` once outside the TUI to authenticate with Anthropic.'
558103
+ }
558104
+ },
558105
+ codex: {
558106
+ id: "codex",
558107
+ label: "OpenAI Codex",
558108
+ command: "codex",
558109
+ promptMode: "arg",
558110
+ args: ["exec"],
558111
+ timeoutMs: 120000,
558112
+ parseStdout: stripAnsi2,
558113
+ homepage: "https://github.com/openai/codex",
558114
+ preferWindow: true,
558115
+ install: {
558116
+ command: "npm install -g --prefix=$HOME/.npm-global @openai/codex",
558117
+ notes: "Binary at ~/.npm-global/bin/codex. Add `~/.npm-global/bin` to PATH if needed. Requires `export OPENAI_API_KEY=sk-...` before /spawn."
558118
+ }
558119
+ },
558120
+ hermes: {
558121
+ id: "hermes",
558122
+ label: "Hermes Agent",
558123
+ command: "hermes",
558124
+ preferWindow: true,
558125
+ promptMode: "flag",
558126
+ flagName: "-z",
558127
+ args: [],
558128
+ timeoutMs: 120000,
558129
+ parseStdout: stripAnsi2,
558130
+ homepage: "https://github.com/NousResearch/hermes-agent",
558131
+ install: {
558132
+ command: "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup --no-venv",
558133
+ notes: "Hermes needs Python \u2265 3.11 (anaconda's default 3.10 won't work; use pyenv or brew install python@3.11). Installer symlinks `hermes` into ~/.local/bin; add it to PATH if needed. After install, run `hermes setup` or `hermes model` outside the TUI to configure a provider. Without that, /spawn'd Hermes tabs will fail with auth errors (Bedrock is the default provider)."
558134
+ }
558135
+ },
558136
+ openclaw: {
558137
+ id: "openclaw",
558138
+ label: "OpenClaw",
558139
+ command: "openclaw",
558140
+ preferWindow: true,
558141
+ promptMode: "arg",
558142
+ args: ["run", "--headless"],
558143
+ timeoutMs: 120000,
558144
+ parseStdout: stripAnsi2,
558145
+ homepage: "https://github.com/openclaw/openclaw",
558146
+ install: {
558147
+ command: "npm install -g --prefix=$HOME/.npm-global openclaw@latest",
558148
+ notes: "Binary at ~/.npm-global/bin/openclaw. Add `~/.npm-global/bin` to PATH if needed. After install run `openclaw onboard` outside the TUI to set up the gateway, workspace, and skills. (8gent now provides a managed Node 22 runtime automatically when needed; no separate nvm step required.)"
558149
+ },
558150
+ requiresNodeVersion: "22.14.0"
558151
+ },
558152
+ pi: {
558153
+ preferWindow: true,
558154
+ id: "pi",
558155
+ label: "Pi (pi-mono)",
558156
+ command: "pi",
558157
+ promptMode: "flag",
558158
+ flagName: "-p",
558159
+ args: [],
558160
+ timeoutMs: 120000,
558161
+ parseStdout: stripAnsi2,
558162
+ homepage: "https://pi.dev",
558163
+ install: {
558164
+ command: "npm install -g --prefix=$HOME/.npm-global @mariozechner/pi-coding-agent",
558165
+ notes: "Binary at ~/.npm-global/bin/pi. Add `~/.npm-global/bin` to PATH if needed. Default provider is Google (Gemini). Set GEMINI_API_KEY, or override with --provider/--model and the matching API key env var (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY)."
558166
+ }
558167
+ },
558168
+ "8gent": {
558169
+ id: "8gent",
558170
+ label: "8gent (nested)",
558171
+ command: "8gent",
558172
+ promptMode: "arg",
558173
+ args: ["run"],
558174
+ timeoutMs: 180000,
558175
+ parseStdout: stripAnsi2,
558176
+ homepage: "https://8gent.dev",
558177
+ install: {
558178
+ command: "npm install -g --prefix=$HOME/.npm-global @8gi-foundation/8gent-code --force",
558179
+ notes: "Binary at ~/.npm-global/bin/8gent. --force overwrites stale `8` / `8gent` / `8gent-code` bin symlinks (npm 9+ EEXIST). Add `~/.npm-global/bin` to PATH if not already."
558180
+ }
558181
+ }
558182
+ };
558183
+ });
558184
+
558185
+ // packages/eight/welcome.ts
558186
+ import { existsSync as existsSync106 } from "fs";
558187
+ import { basename as basename51, delimiter as delimiter8, dirname as dirname74, isAbsolute as isAbsolute49, join as join158, relative as relative34, resolve as resolve72 } from "path";
558188
+ function pathOutsideFolder(path, cwd) {
558189
+ const root = resolve72(cwd);
558190
+ return path.split(delimiter8).filter((dir) => {
558191
+ if (!dir || !isAbsolute49(dir))
558192
+ return false;
558193
+ const rel = relative34(root, resolve72(dir));
558194
+ return rel.startsWith("..") || isAbsolute49(rel);
558195
+ }).join(delimiter8);
558196
+ }
558197
+ function safeModelLabel(model) {
558198
+ const m = model.trim();
558199
+ if (!m || m.length > 80 || /:\/\/|[@?\s]/.test(m))
558200
+ return "a custom model";
558201
+ return m;
558202
+ }
558203
+ function inGitRepo(dir) {
558204
+ let d = dir;
558205
+ for (let i = 0;i < 64; i++) {
558206
+ if (existsSync106(join158(d, ".git")))
558207
+ return true;
558208
+ const up = dirname74(d);
558209
+ if (up === d)
558210
+ return false;
558211
+ d = up;
558212
+ }
558213
+ return false;
558214
+ }
558215
+ function hostLabel(android, platform, arch) {
558216
+ if (android)
558217
+ return `Android (Termux, ${arch})`;
558218
+ const os = platform === "darwin" ? "macOS" : platform === "linux" ? "Linux" : platform === "win32" ? "Windows" : platform;
558219
+ return `${os} ${arch}`;
558220
+ }
558221
+ async function knownPresets() {
558222
+ const runner = await Promise.resolve().then(() => (init_external_agent_runner(), {}));
558223
+ const out = [];
558224
+ for (const id of listPresetIds()) {
558225
+ const p = getPreset(id);
558226
+ if (p)
558227
+ out.push({ id: p.id, command: p.command });
558228
+ }
558229
+ return out;
558230
+ }
558231
+ async function gatherWelcomeFacts(opts) {
558232
+ const env = opts.env ?? process.env;
558233
+ const android = isAndroidHost(opts.platform ?? process.platform, env);
558234
+ const cwd = resolve72(opts.cwd);
558235
+ const home = env.HOME ? resolve72(env.HOME) : null;
558236
+ const lookupEnv = { ...env, PATH: pathOutsideFolder(String(env.PATH ?? ""), cwd) };
558237
+ const presets = (opts.presets ?? await knownPresets()).filter((p) => p.id !== "8gent");
558238
+ return {
558239
+ version: String(import_package.default.version),
558240
+ host: hostLabel(android, opts.platform ?? process.platform, opts.arch ?? process.arch),
558241
+ android,
558242
+ model: safeModelLabel(opts.model),
558243
+ provider: opts.provider,
558244
+ presetIds: presets.map((p) => p.id),
558245
+ installed: presets.filter((p) => findOnPath(p.command, lookupEnv) !== null).map((p) => p.id),
558246
+ folder: cwd === home ? "~" : basename51(cwd) || "/",
558247
+ gitRepo: inGitRepo(cwd)
558248
+ };
558249
+ }
558250
+ function listWords(items) {
558251
+ if (items.length <= 1)
558252
+ return items.join("");
558253
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
558254
+ }
558255
+ function buildWelcome(f) {
558256
+ const where = f.android ? "phone" : "machine";
558257
+ const lines = [`Hi, I'm 8gent v${f.version} on ${f.host}, using ${f.model} via ${f.provider}.`];
558258
+ if (f.installed.length > 0) {
558259
+ lines.push(`On this ${where} I found ${listWords(f.installed)}. Say "run ${f.installed[0]}" and I'll open it in a tab beside me.`);
558260
+ } else {
558261
+ lines.push(`I checked PATH for ${listWords(f.presetIds)}: none installed, so it's just me here.`);
558262
+ }
558263
+ lines.push(f.gitRepo ? `This folder (${f.folder}) is a git repo, so I can read its history and run its tests.` : `This folder (${f.folder}) is not a git repo. I won't look through it unless you ask.`);
558264
+ const next = [];
558265
+ if (f.installed.length > 0)
558266
+ next.push(`run ${f.installed[0]}`);
558267
+ next.push(f.gitRepo ? "what changed in the last commit" : "make a folder called demo with a hello web page");
558268
+ next.push("what can you do");
558269
+ lines.push("Try:", ...next.map((n) => ` "${n}"`));
558270
+ return lines.join(`
558271
+ `);
558272
+ }
558273
+ var import_package;
558274
+ var init_welcome = __esm(() => {
558275
+ init_termux();
558276
+ import_package = __toESM(require_package(), 1);
558277
+ });
558278
+
557427
558279
  // packages/eight/failover-provider-config.ts
557428
558280
  function providerConfigForStep(session, step) {
557429
558281
  if (step.provider === session.name) {
@@ -557945,8 +558797,8 @@ var init_local_turn_error = __esm(() => {
557945
558797
  });
557946
558798
 
557947
558799
  // packages/eight/output-gate.ts
557948
- import { existsSync as existsSync104 } from "fs";
557949
- import { isAbsolute as isAbsolute49, relative as relative34, resolve as resolve72 } from "path";
558800
+ import { existsSync as existsSync107 } from "fs";
558801
+ import { isAbsolute as isAbsolute50, relative as relative35, resolve as resolve73 } from "path";
557950
558802
  function isOutputGateEnabled(env = process.env, option) {
557951
558803
  if (option !== undefined)
557952
558804
  return option;
@@ -557978,10 +558830,10 @@ class OutputGate {
557978
558830
  events = [];
557979
558831
  constructor(task, cwd, enabled) {
557980
558832
  this.cwd = cwd;
557981
- this.required = !enabled ? [] : namedOutputs(task).map((shown) => ({ shown, abs: resolve72(cwd, shown) })).filter(({ abs }) => !relative34(cwd, abs).startsWith("..") && !isAbsolute49(relative34(cwd, abs))).filter(({ abs }) => !existsSync104(abs));
558833
+ this.required = !enabled ? [] : namedOutputs(task).map((shown) => ({ shown, abs: resolve73(cwd, shown) })).filter(({ abs }) => !relative35(cwd, abs).startsWith("..") && !isAbsolute50(relative35(cwd, abs))).filter(({ abs }) => !existsSync107(abs));
557982
558834
  }
557983
558835
  missing() {
557984
- return this.required.filter(({ abs }) => !existsSync104(abs)).map(({ shown }) => shown);
558836
+ return this.required.filter(({ abs }) => !existsSync107(abs)).map(({ shown }) => shown);
557985
558837
  }
557986
558838
  check(reply) {
557987
558839
  const missing = this.missing();
@@ -558019,7 +558871,7 @@ var init_output_gate = __esm(() => {
558019
558871
  function stripPunctuation(s) {
558020
558872
  return s.replace(/[.,;:!?]+$/g, "").trim();
558021
558873
  }
558022
- function isGreeting(message) {
558874
+ function isGreeting2(message) {
558023
558875
  const stripped = stripPunctuation(message.toLowerCase()).trim();
558024
558876
  if (stripped.length === 0)
558025
558877
  return true;
@@ -558040,7 +558892,7 @@ class PreToolRouter {
558040
558892
  classify(userMessage, _projectContext) {
558041
558893
  const msg = userMessage ?? "";
558042
558894
  const trimmed = msg.trim();
558043
- if (trimmed.length === 0 || isGreeting(trimmed)) {
558895
+ if (trimmed.length === 0 || isGreeting2(trimmed)) {
558044
558896
  return {
558045
558897
  strategy: "none",
558046
558898
  args: {},
@@ -558312,12 +559164,12 @@ __export(exports_system_prompt, {
558312
559164
  styleHasGuide: () => styleHasGuide,
558313
559165
  workingProjectNote: () => workingProjectNote
558314
559166
  });
558315
- import { readFileSync as readFileSync96 } from "fs";
558316
- import { basename as basename51, join as join154 } from "path";
559167
+ import { existsSync as existsSync108, readFileSync as readFileSync97 } from "fs";
559168
+ import { basename as basename52, join as join159, resolve as resolve74 } from "path";
558317
559169
  function buildBoardContextSegment(path = BOARD_CONTEXT_PATH) {
558318
559170
  let text;
558319
559171
  try {
558320
- text = readFileSync96(path, "utf-8").trim();
559172
+ text = readFileSync97(path, "utf-8").trim();
558321
559173
  } catch {
558322
559174
  return "";
558323
559175
  }
@@ -558344,8 +559196,20 @@ function communicationStyleLine(style) {
558344
559196
  function styleHasGuide(style) {
558345
559197
  return isCommunicationStyle(style) && STYLE_GUIDE[style] !== "";
558346
559198
  }
559199
+ function isPlainHome(dir, home = resolveHome()) {
559200
+ try {
559201
+ return home !== "" && resolve74(dir) === resolve74(home) && !existsSync108(join159(dir, ".git"));
559202
+ } catch {
559203
+ return false;
559204
+ }
559205
+ }
558347
559206
  function workingProjectNote(dir) {
558348
- const name = basename51(dir) || dir;
559207
+ const name = basename52(dir) || dir;
559208
+ if (isPlainHome(dir)) {
559209
+ return `
559210
+
559211
+ **Working folder: \`${dir}\` is the person's home folder, not a project. Do not list, search or summarise it unless they ask. Ask what they want to work on.**`;
559212
+ }
558349
559213
  if (name === "8gent-code") {
558350
559214
  return `
558351
559215
 
@@ -558689,7 +559553,7 @@ var init_system_prompt = __esm(() => {
558689
559553
  init_tool_registry();
558690
559554
  init_soul_layers();
558691
559555
  IDENTITY_SEGMENT = composeSoulPrompt("owner");
558692
- BOARD_CONTEXT_PATH = join154(resolveHome(), ".8gent", "board-context.md");
559556
+ BOARD_CONTEXT_PATH = join159(resolveHome(), ".8gent", "board-context.md");
558693
559557
  ACTION_FIRST_STYLE = [
558694
559558
  "Shape every reply so the reader can act without rereading:",
558695
559559
  ACTION_FIRST_PRECEDENCE,
@@ -559043,11 +559907,11 @@ var init_toolshed_bridge = __esm(() => {
559043
559907
  });
559044
559908
 
559045
559909
  // packages/ai/embedding-cache.ts
559046
- import { join as join155 } from "path";
559910
+ import { join as join160 } from "path";
559047
559911
  var CACHE_DIR, CACHE_PATH;
559048
559912
  var init_embedding_cache = __esm(() => {
559049
- CACHE_DIR = join155(process.env.HOME || "~", ".8gent");
559050
- CACHE_PATH = join155(CACHE_DIR, "embedding-cache.json");
559913
+ CACHE_DIR = join160(process.env.HOME || "~", ".8gent");
559914
+ CACHE_PATH = join160(CACHE_DIR, "embedding-cache.json");
559051
559915
  });
559052
559916
 
559053
559917
  // packages/ai/edge-inference.ts
@@ -559326,20 +560190,20 @@ var init_quick_answer = __esm(() => {
559326
560190
  });
559327
560191
 
559328
560192
  // packages/eight/session-scratch.ts
559329
- import { lstatSync as lstatSync30, mkdirSync as mkdirSync74, rmSync as rmSync14 } from "fs";
560193
+ import { lstatSync as lstatSync30, mkdirSync as mkdirSync75, rmSync as rmSync15 } from "fs";
559330
560194
  import { tmpdir as tmpdir19 } from "os";
559331
- import { join as join156 } from "path";
560195
+ import { join as join161 } from "path";
559332
560196
  function removeAllAtExit() {
559333
560197
  for (const dir of [...live2])
559334
560198
  removeSessionScratch(dir);
559335
560199
  }
559336
560200
  function sessionScratchPath(sessionId) {
559337
- return join156(tmpdir19(), `8gent-${sessionId}`);
560201
+ return join161(tmpdir19(), `8gent-${sessionId}`);
559338
560202
  }
559339
560203
  function createSessionScratch(sessionId) {
559340
560204
  const dir = sessionScratchPath(sessionId);
559341
560205
  try {
559342
- mkdirSync74(dir, { mode: 448 });
560206
+ mkdirSync75(dir, { mode: 448 });
559343
560207
  live2.add(dir);
559344
560208
  if (!exitHandlerSet) {
559345
560209
  exitHandlerSet = true;
@@ -559360,7 +560224,7 @@ function removeSessionScratch(dir) {
559360
560224
  return;
559361
560225
  if (typeof process.getuid === "function" && st.uid !== process.getuid())
559362
560226
  return;
559363
- rmSync14(dir, { recursive: true, force: true });
560227
+ rmSync15(dir, { recursive: true, force: true });
559364
560228
  } catch {}
559365
560229
  }
559366
560230
  function sessionScratchNote(dir) {
@@ -567239,19 +568103,19 @@ var init_opportunity_scanner = () => {};
567239
568103
  var init_capability_matcher = () => {};
567240
568104
 
567241
568105
  // packages/proactive/work-tracker.ts
567242
- import { join as join159 } from "path";
568106
+ import { join as join164 } from "path";
567243
568107
  var DATA_DIR3, STORE_PATH;
567244
568108
  var init_work_tracker = __esm(() => {
567245
- DATA_DIR3 = join159(process.env.HOME || "~", ".8gent");
567246
- STORE_PATH = join159(DATA_DIR3, "opportunities.json");
568109
+ DATA_DIR3 = join164(process.env.HOME || "~", ".8gent");
568110
+ STORE_PATH = join164(DATA_DIR3, "opportunities.json");
567247
568111
  });
567248
568112
 
567249
568113
  // packages/proactive/revenue-engine.ts
567250
- import { join as join160 } from "path";
568114
+ import { join as join165 } from "path";
567251
568115
  var DATA_DIR4, STORE_PATH2;
567252
568116
  var init_revenue_engine = __esm(() => {
567253
- DATA_DIR4 = join160(process.env.HOME || "~", ".8gent");
567254
- STORE_PATH2 = join160(DATA_DIR4, "revenue.json");
568117
+ DATA_DIR4 = join165(process.env.HOME || "~", ".8gent");
568118
+ STORE_PATH2 = join165(DATA_DIR4, "revenue.json");
567255
568119
  });
567256
568120
 
567257
568121
  // packages/proactive/deliverable-generator.ts
@@ -567264,11 +568128,11 @@ var init_client_outreach = () => {};
567264
568128
  var init_content_packaging = () => {};
567265
568129
 
567266
568130
  // packages/tools/browser/cache.ts
567267
- import { homedir as homedir80 } from "os";
567268
- import { join as join161 } from "path";
568131
+ import { homedir as homedir81 } from "os";
568132
+ import { join as join166 } from "path";
567269
568133
  var CACHE_DIR2, TTL_MS, MAX_CACHE_BYTES;
567270
568134
  var init_cache2 = __esm(() => {
567271
- CACHE_DIR2 = join161(process.env.EIGHT_DATA_DIR || join161(homedir80(), ".8gent"), "browser-cache");
568135
+ CACHE_DIR2 = join166(process.env.EIGHT_DATA_DIR || join166(homedir81(), ".8gent"), "browser-cache");
567272
568136
  TTL_MS = 60 * 60 * 1000;
567273
568137
  MAX_CACHE_BYTES = 100 * 1024 * 1024;
567274
568138
  });
@@ -568011,7 +568875,7 @@ var init_voice3 = __esm(() => {
568011
568875
 
568012
568876
  // packages/eight/observation-pack.ts
568013
568877
  import { createHash as createHash21 } from "crypto";
568014
- import { readFileSync as readFileSync98 } from "fs";
568878
+ import { readFileSync as readFileSync99 } from "fs";
568015
568879
  function observationPackEnabled(env = process.env) {
568016
568880
  return env.EIGHT_OBSERVATION_PACK === "1";
568017
568881
  }
@@ -568031,7 +568895,7 @@ class ObservationStore {
568031
568895
  const path = this.paths.get(handle);
568032
568896
  if (!path)
568033
568897
  throw new Error(`no saved output with handle "${handle}" in this session`);
568034
- return readFileSync98(path, "utf8").slice(offset, offset + limit);
568898
+ return readFileSync99(path, "utf8").slice(offset, offset + limit);
568035
568899
  }
568036
568900
  size(handle) {
568037
568901
  return this.read(handle).length;
@@ -569040,6 +569904,7 @@ class Agent {
569040
569904
  steerQueue = [];
569041
569905
  activeTurn = null;
569042
569906
  turnCounter = 0;
569907
+ welcomed = false;
569043
569908
  stopRequested = false;
569044
569909
  orchestratorBus;
569045
569910
  toolRegistry;
@@ -569507,14 +570372,15 @@ ${LOCAL_LOGIN_NOTE}`;
569507
570372
  instructions,
569508
570373
  localCoreTools,
569509
570374
  chatStartTime,
569510
- textForAgent
570375
+ textForAgent,
570376
+ smallTalk = false
569511
570377
  } = opts;
569512
570378
  const signal = this.armController().signal;
569513
570379
  const allow = new Set(localCoreTools);
569514
570380
  const specs = toolDefsToSpecs(this.executor.getToolDefinitions(), allow);
569515
570381
  let stepNumber = 0;
569516
570382
  let breakerStop = null;
569517
- if (this.extensionToolsAllowed()) {
570383
+ if (this.extensionToolsAllowed() && !smallTalk) {
569518
570384
  for (const t of this.extensionHost.listTools()) {
569519
570385
  if (this.modeAllows(t.name)) {
569520
570386
  specs.push({ name: t.name, description: t.description, parameters: t.parameters });
@@ -570078,6 +570944,21 @@ ${LOCAL_LOGIN_NOTE}`;
570078
570944
  console.log(`[turn-review] ${line}`);
570079
570945
  }
570080
570946
  }
570947
+ async greetingWelcome(text) {
570948
+ if (this.welcomed || this.config.headless || this.agentDepth > 0 || !isGreeting(text))
570949
+ return null;
570950
+ try {
570951
+ const facts = await gatherWelcomeFacts({
570952
+ model: String(this.config.model),
570953
+ provider: String(this.config.runtime),
570954
+ cwd: this.config.workingDirectory || process.cwd()
570955
+ });
570956
+ this.welcomed = true;
570957
+ return buildWelcome(facts);
570958
+ } catch {
570959
+ return null;
570960
+ }
570961
+ }
570081
570962
  async runTurn(userMessage, imageBase64, imageMimeType, imageOriginal) {
570082
570963
  await this.extensionsReady;
570083
570964
  this.loopDetector.reset();
@@ -570092,6 +570973,17 @@ ${LOCAL_LOGIN_NOTE}`;
570092
570973
  this.turnGitBaseline = asksForCommit(userMessage) ? gitSnapshot(this.config.workingDirectory || process.cwd()) : null;
570093
570974
  this.executor.beginTurn(userMessage);
570094
570975
  const textForAgent = userMessage.trim() || (imageBase64 ? "The user attached an image with no text. Describe what you see and help with anything relevant in the image." : userMessage);
570976
+ const smallTalkTurn = isSmallTalk(textForAgent) && !imageBase64 && this.config.agentScope !== "__table__";
570977
+ if (smallTalkTurn) {
570978
+ const welcome = await this.greetingWelcome(textForAgent);
570979
+ if (welcome) {
570980
+ this.messageHistory.push({ role: "user", content: textForAgent });
570981
+ this.messageHistory.push({ role: "assistant", content: welcome });
570982
+ this.sessionWriter.writeUserMessage(textForAgent);
570983
+ return welcome;
570984
+ }
570985
+ this.messageHistory.push({ role: "user", content: harnessNote(SMALL_TALK_NOTE) });
570986
+ }
570095
570987
  let visionId = null;
570096
570988
  let visionFailed = false;
570097
570989
  if (imageBase64 && await this.promptImageSeen()) {
@@ -570162,7 +571054,7 @@ ${result.description.slice(0, 200)}${result.description.length > 200 ? "..." : "
570162
571054
  }
570163
571055
  }
570164
571056
  const quickPrompt = quickLaneEnabled() && classifyPrompt(textForAgent) === "quick";
570165
- if (needsClarification(textForAgent) && !imageBase64 && !quickPrompt && !isNoToolScope(this.config.agentScope)) {
571057
+ if (needsClarification(textForAgent) && !imageBase64 && !quickPrompt && !smallTalkTurn && !isNoToolScope(this.config.agentScope)) {
570166
571058
  this.proactiveGatherer = createGatherer(textForAgent);
570167
571059
  const question = this.proactiveGatherer.getCurrentQuestion();
570168
571060
  if (question) {
@@ -570184,7 +571076,7 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
570184
571076
  this.kanban.moveTask(this.currentBmadTask.id, "ready");
570185
571077
  this.kanban.moveTask(this.currentBmadTask.id, "in_progress");
570186
571078
  }
570187
- if (!this.proactiveGatherer && !quickPrompt) {
571079
+ if (!this.proactiveGatherer && !quickPrompt && !smallTalkTurn) {
570188
571080
  await this.tryRunPreToolRouter(textForAgent);
570189
571081
  }
570190
571082
  const needsPlanningGate2 = !isVerbatimScope(this.config.agentScope) && needsPlanningGate(textForAgent);
@@ -570329,9 +571221,9 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
570329
571221
  "search_symbols",
570330
571222
  "recall"
570331
571223
  ]);
570332
- const scopedTools = isNoToolScope(this.config.agentScope) ? {} : this.config.agentScope === "__table__" ? Object.fromEntries(Object.entries(providerTools).filter(([k]) => TABLE_SESSION_TOOLS.has(k))) : providerTools;
571224
+ const scopedTools = isNoToolScope(this.config.agentScope) || smallTalkTurn ? {} : this.config.agentScope === "__table__" ? Object.fromEntries(Object.entries(providerTools).filter(([k]) => TABLE_SESSION_TOOLS.has(k))) : providerTools;
570333
571225
  const withExtensions = { ...scopedTools };
570334
- if (this.extensionToolsAllowed()) {
571226
+ if (this.extensionToolsAllowed() && !smallTalkTurn) {
570335
571227
  for (const t of this.extensionHost.listTools()) {
570336
571228
  if (t.name in withExtensions)
570337
571229
  continue;
@@ -570343,7 +571235,7 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
570343
571235
  }
570344
571236
  }
570345
571237
  const effectiveTools = this.modeFilter(withExtensions);
570346
- const textToolAllowlist = (isNoToolScope(this.config.agentScope) ? [] : this.config.agentScope === "__table__" ? [...TABLE_SESSION_TOOLS] : localCoreTools).filter((name) => getModeManager().isToolAllowed(name));
571238
+ const textToolAllowlist = (isNoToolScope(this.config.agentScope) || smallTalkTurn ? [] : this.config.agentScope === "__table__" ? [...TABLE_SESSION_TOOLS] : localCoreTools).filter((name) => getModeManager().isToolAllowed(name));
570347
571239
  const harness = this.harnessProfileFor(providerConfig.model, isLocalProvider);
570348
571240
  Object.assign(this.runtimeParams, {
570349
571241
  voiceChatActive: getRuntimeParams().voiceChatActive,
@@ -570368,7 +571260,8 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
570368
571260
  `),
570369
571261
  localCoreTools: textToolAllowlist,
570370
571262
  chatStartTime,
570371
- textForAgent
571263
+ textForAgent,
571264
+ smallTalk: smallTalkTurn
570372
571265
  });
570373
571266
  await this.readServerContextWindow();
570374
571267
  await this.compactIfNeeded(providerConfig);
@@ -571053,7 +571946,7 @@ ${summary || " (no provider errors recorded)"}`);
571053
571946
  };
571054
571947
  }
571055
571948
  const blankNative = gatedNative.content.trim() === "";
571056
- const content = blankNative ? emptyReplyNote(this.turnToolLedger.length) : gatedNative.content;
571949
+ const content = blankNative ? emptyReplyNote(this.turnToolLedger.length, this.turnToolLedger) : gatedNative.content;
571057
571950
  const flavor = voice.getFlavor("complete");
571058
571951
  const flavoredContent = fixCompletedLine(gatedNative.violated || isVerbatimScope(this.config.agentScope) ? content : flavorResponse(content, flavor)).text;
571059
571952
  this.messageHistory.push({ role: "assistant", content: flavoredContent });
@@ -571768,7 +572661,9 @@ var init_agent2 = __esm(() => {
571768
572661
  init_modes();
571769
572662
  init_planning_gate();
571770
572663
  init_registry();
572664
+ init_small_talk();
571771
572665
  init_verify_gate();
572666
+ init_welcome();
571772
572667
  init_local_tool_scope();
571773
572668
  init_local_turn_error();
571774
572669
  init_output_gate();
@@ -572262,9 +573157,9 @@ var init_install = __esm(() => {
572262
573157
  });
572263
573158
 
572264
573159
  // packages/eight/source-update.ts
572265
- import { execFile as execFile7 } from "child_process";
572266
- import { existsSync as existsSync109, realpathSync as realpathSync36, statSync as statSync36 } from "fs";
572267
- import { homedir as homedir84 } from "os";
573160
+ import { execFile as execFile9 } from "child_process";
573161
+ import { existsSync as existsSync113, realpathSync as realpathSync36, statSync as statSync36 } from "fs";
573162
+ import { homedir as homedir85 } from "os";
572268
573163
  import * as nodePath from "path";
572269
573164
  function scrubbedEnv(base) {
572270
573165
  const out = {};
@@ -572362,7 +573257,7 @@ function modulePathToFs(modulePath) {
572362
573257
  }
572363
573258
  return s;
572364
573259
  }
572365
- function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync109(lib(platform).join(d, ".git"))) {
573260
+ function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync113(lib(platform).join(d, ".git"))) {
572366
573261
  const p = lib(platform);
572367
573262
  const fsPath = modulePathToFs(modulePath);
572368
573263
  let dir = p.dirname(platform === "win32" ? fsPath.replace(/\//g, "\\") : fsPath);
@@ -572378,7 +573273,7 @@ function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync109(li
572378
573273
  }
572379
573274
  function findBrowserCheckout(o) {
572380
573275
  const p = lib(o.platform);
572381
- const hasGit = o.hasGit ?? ((d) => existsSync109(p.join(d, ".git")));
573276
+ const hasGit = o.hasGit ?? ((d) => existsSync113(p.join(d, ".git")));
572382
573277
  const looked = [];
572383
573278
  let note;
572384
573279
  const cands = [];
@@ -572704,7 +573599,7 @@ async function runSourceUpdate(deps, opts, report) {
572704
573599
  git,
572705
573600
  bun: bun ?? "bun",
572706
573601
  platform,
572707
- fileExists: deps.fileExists ?? ((f) => existsSync109(f)),
573602
+ fileExists: deps.fileExists ?? ((f) => existsSync113(f)),
572708
573603
  now: deps.now ?? (() => new Date),
572709
573604
  baseEnv: env,
572710
573605
  forgeHost: env.EIGHT_FORGE_HOST,
@@ -572728,7 +573623,7 @@ async function runSourceUpdate(deps, opts, report) {
572728
573623
  report?.({ updated: true, to: short2(code.to ?? "") });
572729
573624
  const found = findBrowserCheckout({
572730
573625
  env,
572731
- home: deps.home ?? homedir84(),
573626
+ home: deps.home ?? homedir85(),
572732
573627
  platform,
572733
573628
  codeRoot: root,
572734
573629
  hasGit: deps.hasGit
@@ -572754,7 +573649,7 @@ async function runSourceUpdate(deps, opts, report) {
572754
573649
  return bad ? 1 : 0;
572755
573650
  }
572756
573651
  var T_LOCAL = 30000, T_FETCH = 120000, T_INSTALL = 600000, ENV_KEEP, defaultExec = (file, args, opts) => new Promise((resolve) => {
572757
- execFile7(file, args, {
573652
+ execFile9(file, args, {
572758
573653
  cwd: opts.cwd,
572759
573654
  timeout: opts.timeoutMs,
572760
573655
  maxBuffer: 33554432,
@@ -573017,16 +573912,16 @@ var init_cli_maintenance = __esm(() => {
573017
573912
  });
573018
573913
 
573019
573914
  // packages/eight/update-resume.ts
573020
- import { mkdirSync as mkdirSync78, readFileSync as readFileSync102, rmSync as rmSync15, writeFileSync as writeFileSync61 } from "fs";
573021
- import { join as join166 } from "path";
573915
+ import { mkdirSync as mkdirSync79, readFileSync as readFileSync103, rmSync as rmSync16, writeFileSync as writeFileSync61 } from "fs";
573916
+ import { join as join171 } from "path";
573022
573917
  function dataDir4(env = process.env) {
573023
- return join166(resolveHome(env), ".8gent");
573918
+ return join171(resolveHome(env), ".8gent");
573024
573919
  }
573025
573920
  function writeResumeRequest(req, env = process.env) {
573026
- mkdirSync78(dataDir4(env), { recursive: true });
573921
+ mkdirSync79(dataDir4(env), { recursive: true });
573027
573922
  writeFileSync61(file(env), JSON.stringify(req), "utf8");
573028
573923
  }
573029
- var RELAUNCH_EXIT_CODE = 75, file = (env) => join166(dataDir4(env), "update-resume.json");
573924
+ var RELAUNCH_EXIT_CODE = 75, file = (env) => join171(dataDir4(env), "update-resume.json");
573030
573925
  var init_update_resume = __esm(() => {
573031
573926
  init_home();
573032
573927
  });
@@ -573038,8 +573933,8 @@ __export(exports_update_notice, {
573038
573933
  maybeUpdateNotice: () => maybeUpdateNotice,
573039
573934
  realNoticeDeps: () => realNoticeDeps
573040
573935
  });
573041
- import { mkdirSync as mkdirSync79, readFileSync as readFileSync103, writeFileSync as writeFileSync62 } from "fs";
573042
- import { join as join167 } from "path";
573936
+ import { mkdirSync as mkdirSync80, readFileSync as readFileSync104, writeFileSync as writeFileSync62 } from "fs";
573937
+ import { join as join172 } from "path";
573043
573938
  function noticeText(from, to) {
573044
573939
  return `Update available (${from} -> ${to}). Run 8gent update, or type /update.`;
573045
573940
  }
@@ -573053,7 +573948,7 @@ function updateCheckDisabled(env, settingsFile) {
573053
573948
  return true;
573054
573949
  if (settingsFile) {
573055
573950
  try {
573056
- const s = JSON.parse(readFileSync103(settingsFile, "utf8"));
573951
+ const s = JSON.parse(readFileSync104(settingsFile, "utf8"));
573057
573952
  if (s.updateCheck === false)
573058
573953
  return true;
573059
573954
  } catch {}
@@ -573090,7 +573985,7 @@ async function maybeUpdateNotice(d) {
573090
573985
  function realNoticeDeps(o) {
573091
573986
  const env = o.env ?? process.env;
573092
573987
  const platform = o.platform ?? process.platform;
573093
- const stateFile = join167(dataDir4(env), "update-check.json");
573988
+ const stateFile = join172(dataDir4(env), "update-check.json");
573094
573989
  const channel = detectChannel({ modulePath: o.modulePath, embedded: o.embedded });
573095
573990
  const srcDeps = { modulePath: o.modulePath, env, platform, exec: o.exec };
573096
573991
  return {
@@ -573098,7 +573993,7 @@ function realNoticeDeps(o) {
573098
573993
  disabled: () => !isUpdateCheckEnabled(env) || updateCheckDisabled(env, o.settingsFile),
573099
573994
  readState: () => {
573100
573995
  try {
573101
- const s = JSON.parse(readFileSync103(stateFile, "utf8"));
573996
+ const s = JSON.parse(readFileSync104(stateFile, "utf8"));
573102
573997
  return typeof s.checkedAt === "number" ? s : null;
573103
573998
  } catch {
573104
573999
  return null;
@@ -573106,7 +574001,7 @@ function realNoticeDeps(o) {
573106
574001
  },
573107
574002
  writeState: (s) => {
573108
574003
  try {
573109
- mkdirSync79(dataDir4(env), { recursive: true });
574004
+ mkdirSync80(dataDir4(env), { recursive: true });
573110
574005
  writeFileSync62(stateFile, JSON.stringify(s), "utf8");
573111
574006
  } catch {}
573112
574007
  },
@@ -573150,7 +574045,7 @@ var init_update_notice = __esm(() => {
573150
574045
  });
573151
574046
 
573152
574047
  // packages/voice/cloud-transcriber.ts
573153
- import { existsSync as existsSync110 } from "fs";
574048
+ import { existsSync as existsSync114 } from "fs";
573154
574049
  async function transcribeCloud(wavPath, options) {
573155
574050
  const {
573156
574051
  apiKey,
@@ -573162,7 +574057,7 @@ async function transcribeCloud(wavPath, options) {
573162
574057
  if (!apiKey) {
573163
574058
  throw new Error("OpenAI API key is required for cloud transcription. Set OPENAI_API_KEY environment variable.");
573164
574059
  }
573165
- if (!existsSync110(wavPath)) {
574060
+ if (!existsSync114(wavPath)) {
573166
574061
  throw new Error(`WAV file not found: ${wavPath}`);
573167
574062
  }
573168
574063
  const startTime = Date.now();
@@ -573266,11 +574161,11 @@ var init_types5 = __esm(() => {
573266
574161
 
573267
574162
  // packages/voice/model-manager.ts
573268
574163
  import { EventEmitter as EventEmitter15 } from "events";
573269
- import { existsSync as existsSync111, mkdirSync as mkdirSync80, realpathSync as realpathSync37, unlinkSync as unlinkSync3 } from "fs";
573270
- import { homedir as homedir85 } from "os";
573271
- import { join as join168, sep as sep30 } from "path";
574164
+ import { existsSync as existsSync115, mkdirSync as mkdirSync81, realpathSync as realpathSync37, unlinkSync as unlinkSync4 } from "fs";
574165
+ import { homedir as homedir86 } from "os";
574166
+ import { join as join173, sep as sep30 } from "path";
573272
574167
  function resolveWhisperModel(opts = {}) {
573273
- const exists = opts.exists ?? existsSync111;
574168
+ const exists = opts.exists ?? existsSync115;
573274
574169
  const dirs = [
573275
574170
  resolveModelsPath(opts.modelsDir ?? "~/.8gent/models/whisper"),
573276
574171
  ...opts.extraDirs ?? []
@@ -573280,7 +574175,7 @@ function resolveWhisperModel(opts = {}) {
573280
574175
  for (const m of WHISPER_MODEL_RANK) {
573281
574176
  if (!english && m.filename.endsWith(".en.bin"))
573282
574177
  continue;
573283
- const path = join168(dir, m.filename);
574178
+ const path = join173(dir, m.filename);
573284
574179
  if (!exists(path))
573285
574180
  continue;
573286
574181
  if (!opts.exists && !isInside2(dir, path))
@@ -573309,7 +574204,7 @@ function missingWhisperModelMessage(modelsDir = "~/.8gent/models/whisper") {
573309
574204
  }
573310
574205
  function resolveModelsPath(configPath) {
573311
574206
  if (configPath.startsWith("~")) {
573312
- return join168(homedir85(), configPath.slice(1));
574207
+ return join173(homedir86(), configPath.slice(1));
573313
574208
  }
573314
574209
  return configPath;
573315
574210
  }
@@ -573331,8 +574226,8 @@ var init_model_manager = __esm(() => {
573331
574226
  this.modelsDir = resolveModelsPath(modelsPath);
573332
574227
  }
573333
574228
  ensureModelsDir() {
573334
- if (!existsSync111(this.modelsDir)) {
573335
- mkdirSync80(this.modelsDir, { recursive: true });
574229
+ if (!existsSync115(this.modelsDir)) {
574230
+ mkdirSync81(this.modelsDir, { recursive: true });
573336
574231
  }
573337
574232
  }
573338
574233
  getModelsDir() {
@@ -573342,10 +574237,10 @@ var init_model_manager = __esm(() => {
573342
574237
  return WHISPER_MODELS[name];
573343
574238
  }
573344
574239
  getModelPath(name) {
573345
- return join168(this.modelsDir, WHISPER_MODELS[name].filename);
574240
+ return join173(this.modelsDir, WHISPER_MODELS[name].filename);
573346
574241
  }
573347
574242
  isModelDownloaded(name) {
573348
- return existsSync111(this.getModelPath(name));
574243
+ return existsSync115(this.getModelPath(name));
573349
574244
  }
573350
574245
  listModels() {
573351
574246
  return Object.keys(WHISPER_MODELS).map((name) => ({
@@ -573360,7 +574255,7 @@ var init_model_manager = __esm(() => {
573360
574255
  async downloadModel(name) {
573361
574256
  const info = WHISPER_MODELS[name];
573362
574257
  const destPath = this.getModelPath(name);
573363
- if (existsSync111(destPath)) {
574258
+ if (existsSync115(destPath)) {
573364
574259
  return destPath;
573365
574260
  }
573366
574261
  this.ensureModelsDir();
@@ -573411,9 +574306,9 @@ var init_model_manager = __esm(() => {
573411
574306
  this.emit("download-complete", { model: name, path: destPath });
573412
574307
  return destPath;
573413
574308
  } catch (err) {
573414
- if (existsSync111(destPath)) {
574309
+ if (existsSync115(destPath)) {
573415
574310
  try {
573416
- unlinkSync3(destPath);
574311
+ unlinkSync4(destPath);
573417
574312
  } catch {}
573418
574313
  }
573419
574314
  const message = err instanceof Error ? err.message : "Download failed";
@@ -573423,8 +574318,8 @@ var init_model_manager = __esm(() => {
573423
574318
  }
573424
574319
  deleteModel(name) {
573425
574320
  const path = this.getModelPath(name);
573426
- if (existsSync111(path)) {
573427
- unlinkSync3(path);
574321
+ if (existsSync115(path)) {
574322
+ unlinkSync4(path);
573428
574323
  return true;
573429
574324
  }
573430
574325
  return false;
@@ -573434,7 +574329,7 @@ var init_model_manager = __esm(() => {
573434
574329
  let totalBytes = 0;
573435
574330
  for (const name of Object.keys(WHISPER_MODELS)) {
573436
574331
  const path = this.getModelPath(name);
573437
- if (existsSync111(path)) {
574332
+ if (existsSync115(path)) {
573438
574333
  try {
573439
574334
  const stat = Bun.file(path);
573440
574335
  models[name] = stat.size;
@@ -573447,65 +574342,15 @@ var init_model_manager = __esm(() => {
573447
574342
  };
573448
574343
  });
573449
574344
 
573450
- // packages/voice/termux.ts
573451
- import { constants as constants15, accessSync as accessSync4, readFileSync as readFileSync104 } from "fs";
573452
- import { delimiter as delimiter7, join as join169 } from "path";
573453
- function isAndroidHost(platform = process.platform, env = process.env) {
573454
- return platform === "android" || !!env.TERMUX_VERSION || String(env.PREFIX ?? "").includes("/com.termux/");
573455
- }
573456
- function findOnPath(name, env = process.env) {
573457
- for (const dir of String(env.PATH ?? "").split(delimiter7)) {
573458
- if (!dir)
573459
- continue;
573460
- const candidate = join169(dir, name);
573461
- try {
573462
- accessSync4(candidate, constants15.X_OK);
573463
- return candidate;
573464
- } catch {}
573465
- }
573466
- return null;
573467
- }
573468
- function voiceOutputOn(env = process.env, readSettings = () => {
573469
- const home = env.EIGHT_HOME || join169(env.HOME ?? "", ".8gent");
573470
- return readFileSync104(join169(home, "settings.json"), "utf8");
573471
- }) {
573472
- if (env.EIGHT_VOICE === "1")
573473
- return true;
573474
- try {
573475
- return JSON.parse(readSettings())?.voice?.outputEnabled === true;
573476
- } catch {
573477
- return false;
573478
- }
573479
- }
573480
- function ctrlRAction(key, input, android = isAndroidHost()) {
573481
- if (!key.ctrl || input !== "r")
573482
- return "ignore";
573483
- return android ? "android-line" : "toggle";
573484
- }
573485
- var ANDROID_DICTATION_LINE = "On Android, tap the microphone key on your keyboard; dictation lands in the prompt. Spoken replies: /voice on (needs termux-tts-speak: pkg install termux-api plus the Termux:API app).", TERMUX_TTS_HINT = "Spoken replies need termux-tts-speak: pkg install termux-api, plus the Termux:API app from F-Droid.", ANDROID_VOICE_LINE_SUBS;
573486
- var init_termux = __esm(() => {
573487
- ANDROID_VOICE_LINE_SUBS = [
573488
- "",
573489
- "chat",
573490
- "conversation",
573491
- "talk",
573492
- "record",
573493
- "listen",
573494
- "stt",
573495
- "status",
573496
- "stop"
573497
- ];
573498
- });
573499
-
573500
574345
  // packages/voice/recorder.ts
573501
574346
  import { EventEmitter as EventEmitter16 } from "events";
573502
- import { closeSync as closeSync21, existsSync as existsSync112, openSync as openSync21, readSync as readSync13, statSync as statSync37, unlinkSync as unlinkSync4 } from "fs";
574347
+ import { closeSync as closeSync21, existsSync as existsSync116, openSync as openSync21, readSync as readSync13, statSync as statSync37, unlinkSync as unlinkSync5 } from "fs";
573503
574348
  import { tmpdir as tmpdir20 } from "os";
573504
- import { join as join170 } from "path";
573505
- var {spawn: spawn26 } = globalThis.Bun;
574349
+ import { join as join174 } from "path";
574350
+ var {spawn: spawn28 } = globalThis.Bun;
573506
574351
  async function findSoxPath() {
573507
574352
  try {
573508
- const proc = spawn26(["which", "rec"], {
574353
+ const proc = spawn28(["which", "rec"], {
573509
574354
  stdout: "pipe",
573510
574355
  stderr: "pipe"
573511
574356
  });
@@ -573581,7 +574426,7 @@ var init_recorder = __esm(() => {
573581
574426
  channels: opts.channels ?? 1,
573582
574427
  bitDepth: opts.bitDepth ?? 16,
573583
574428
  maxDurationSeconds: opts.maxDurationSeconds ?? 30,
573584
- outputPath: opts.outputPath ?? join170(tmpdir20(), `8gent-voice-${Date.now()}.wav`)
574429
+ outputPath: opts.outputPath ?? join174(tmpdir20(), `8gent-voice-${Date.now()}.wav`)
573585
574430
  };
573586
574431
  this.outputPath = this.options.outputPath;
573587
574432
  }
@@ -573597,7 +574442,7 @@ var init_recorder = __esm(() => {
573597
574442
  throw new Error(`sox not installed. ${soxCheck.installHint}`);
573598
574443
  }
573599
574444
  this.device = null;
573600
- this.outputPath = this.options.outputPath.includes("8gent-voice-") ? join170(tmpdir20(), `8gent-voice-${Date.now()}.wav`) : this.options.outputPath;
574445
+ this.outputPath = this.options.outputPath.includes("8gent-voice-") ? join174(tmpdir20(), `8gent-voice-${Date.now()}.wav`) : this.options.outputPath;
573601
574446
  const args = [
573602
574447
  "-q",
573603
574448
  "-r",
@@ -573611,7 +574456,7 @@ var init_recorder = __esm(() => {
573611
574456
  this.outputPath
573612
574457
  ];
573613
574458
  try {
573614
- this.process = this.deps.spawnRec ? this.deps.spawnRec(args) : spawn26(["rec", ...args], { stdout: "ignore", stderr: "ignore" });
574459
+ this.process = this.deps.spawnRec ? this.deps.spawnRec(args) : spawn28(["rec", ...args], { stdout: "ignore", stderr: "ignore" });
573615
574460
  this.isRecording = true;
573616
574461
  this.startTime = Date.now();
573617
574462
  this.emit("start");
@@ -573701,8 +574546,8 @@ var init_recorder = __esm(() => {
573701
574546
  }
573702
574547
  static cleanupFile(path) {
573703
574548
  try {
573704
- if (existsSync112(path)) {
573705
- unlinkSync4(path);
574549
+ if (existsSync116(path)) {
574550
+ unlinkSync5(path);
573706
574551
  }
573707
574552
  } catch {}
573708
574553
  }
@@ -573721,22 +574566,22 @@ var init_recorder = __esm(() => {
573721
574566
  });
573722
574567
 
573723
574568
  // packages/voice/transcriber.ts
573724
- import { existsSync as existsSync113 } from "fs";
573725
- import { arch as arch2, homedir as homedir86, platform as platform9 } from "os";
573726
- import { join as join171 } from "path";
573727
- var {spawn: spawn27 } = globalThis.Bun;
574569
+ import { existsSync as existsSync117 } from "fs";
574570
+ import { arch as arch2, homedir as homedir87, platform as platform9 } from "os";
574571
+ import { join as join175 } from "path";
574572
+ var {spawn: spawn29 } = globalThis.Bun;
573728
574573
  function getWhisperBinaryPath() {
573729
- return join171(WHISPER_BIN_DIR, WHISPER_BIN_NAME);
574574
+ return join175(WHISPER_BIN_DIR, WHISPER_BIN_NAME);
573730
574575
  }
573731
574576
  async function findWhisperBinary() {
573732
574577
  const managedPath = getWhisperBinaryPath();
573733
- if (existsSync113(managedPath)) {
574578
+ if (existsSync117(managedPath)) {
573734
574579
  return managedPath;
573735
574580
  }
573736
574581
  const names = ["whisper-cpp", "whisper-cli", "whisper", "main"];
573737
574582
  for (const name of names) {
573738
574583
  try {
573739
- const proc = spawn27(["which", name], { stdout: "pipe", stderr: "pipe", env: process.env });
574584
+ const proc = spawn29(["which", name], { stdout: "pipe", stderr: "pipe", env: process.env });
573740
574585
  const output = await new Response(proc.stdout).text();
573741
574586
  const exitCode = await proc.exited;
573742
574587
  if (exitCode === 0 && output.trim()) {
@@ -573745,7 +574590,7 @@ async function findWhisperBinary() {
573745
574590
  } catch {}
573746
574591
  }
573747
574592
  for (const brewName of ["/opt/homebrew/bin/whisper-cpp", "/opt/homebrew/bin/whisper-cli"]) {
573748
- if (existsSync113(brewName)) {
574593
+ if (existsSync117(brewName)) {
573749
574594
  return brewName;
573750
574595
  }
573751
574596
  }
@@ -573753,13 +574598,13 @@ async function findWhisperBinary() {
573753
574598
  }
573754
574599
  async function transcribeLocal(wavPath, options) {
573755
574600
  const { binaryPath, modelPath, language = "en", timeoutMs = 30000, threads } = options;
573756
- if (!existsSync113(wavPath)) {
574601
+ if (!existsSync117(wavPath)) {
573757
574602
  throw new Error(`WAV file not found: ${wavPath}`);
573758
574603
  }
573759
- if (!existsSync113(binaryPath)) {
574604
+ if (!existsSync117(binaryPath)) {
573760
574605
  throw new Error(`Whisper binary not found: ${binaryPath}`);
573761
574606
  }
573762
- if (!existsSync113(modelPath)) {
574607
+ if (!existsSync117(modelPath)) {
573763
574608
  throw new Error(`Model file not found: ${modelPath}`);
573764
574609
  }
573765
574610
  const startTime = Date.now();
@@ -573777,7 +574622,7 @@ async function transcribeLocal(wavPath, options) {
573777
574622
  args.push("-t", String(threads));
573778
574623
  }
573779
574624
  if (platform9() === "darwin" && arch2() === "arm64") {}
573780
- const proc = spawn27([binaryPath, ...args], {
574625
+ const proc = spawn29([binaryPath, ...args], {
573781
574626
  stdout: "pipe",
573782
574627
  stderr: "pipe",
573783
574628
  env: process.env
@@ -573846,13 +574691,13 @@ function extractModelName(modelPath) {
573846
574691
  }
573847
574692
  var WHISPER_BIN_DIR, WHISPER_BIN_NAME = "whisper-cpp";
573848
574693
  var init_transcriber = __esm(() => {
573849
- WHISPER_BIN_DIR = join171(homedir86(), ".8gent", "bin");
574694
+ WHISPER_BIN_DIR = join175(homedir87(), ".8gent", "bin");
573850
574695
  });
573851
574696
 
573852
574697
  // packages/decide/backends/neudecide.ts
573853
- import { mkdtempSync as mkdtempSync11, rmSync as rmSync16, writeFileSync as writeFileSync63 } from "fs";
574698
+ import { mkdtempSync as mkdtempSync12, rmSync as rmSync17, writeFileSync as writeFileSync63 } from "fs";
573854
574699
  import { tmpdir as tmpdir22 } from "os";
573855
- import { dirname as dirname77, join as join174 } from "path";
574700
+ import { dirname as dirname78, join as join178 } from "path";
573856
574701
  function wavDurationMs(buf) {
573857
574702
  if (buf.length < 12)
573858
574703
  return null;
@@ -573909,7 +574754,7 @@ function pythonRunner(env = process.env, spawn = Bun.spawn) {
573909
574754
  const proc = spawn([python, "-I", "-c", PY, wavPath, toolsJson], {
573910
574755
  stdout: "pipe",
573911
574756
  stderr: "pipe",
573912
- cwd: dirname77(wavPath),
574757
+ cwd: dirname78(wavPath),
573913
574758
  env: { ...env, HF_HUB_OFFLINE: "1" }
573914
574759
  });
573915
574760
  const kill = () => {
@@ -573947,15 +574792,15 @@ class NeuDecideBackend {
573947
574792
  description: c.description,
573948
574793
  parameters: { type: "object", properties: {} }
573949
574794
  }));
573950
- const dir = mkdtempSync11(join174(tmpdir22(), "8gent-neudecide-"));
573951
- const wavPath = join174(dir, "in.wav");
574795
+ const dir = mkdtempSync12(join178(tmpdir22(), "8gent-neudecide-"));
574796
+ const wavPath = join178(dir, "in.wav");
573952
574797
  const t0 = performance.now();
573953
574798
  let out;
573954
574799
  try {
573955
574800
  writeFileSync63(wavPath, request.audio);
573956
574801
  out = await this.run(wavPath, JSON.stringify(tools), request.signal);
573957
574802
  } finally {
573958
- rmSync16(dir, { recursive: true, force: true });
574803
+ rmSync17(dir, { recursive: true, force: true });
573959
574804
  }
573960
574805
  const latencyMs = performance.now() - t0;
573961
574806
  let calls;
@@ -574033,289 +574878,6 @@ var init_neudecide = __esm(() => {
574033
574878
  ];
574034
574879
  });
574035
574880
 
574036
- // packages/runtime/node-runtime.ts
574037
- import { execFile as execFile8 } from "child_process";
574038
- import { existsSync as existsSync118 } from "fs";
574039
- import { homedir as homedir88 } from "os";
574040
- import { join as join178 } from "path";
574041
- import { promisify as promisify6 } from "util";
574042
- function parseNodeVersion(raw) {
574043
- const trimmed = raw.trim();
574044
- const match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
574045
- if (!match)
574046
- return null;
574047
- return {
574048
- major: Number.parseInt(match[1], 10),
574049
- minor: Number.parseInt(match[2], 10),
574050
- patch: Number.parseInt(match[3], 10)
574051
- };
574052
- }
574053
- function satisfiesMinVersion(v, min) {
574054
- const minParsed = parseNodeVersion(min);
574055
- if (!minParsed)
574056
- return true;
574057
- if (v.major !== minParsed.major)
574058
- return v.major > minParsed.major;
574059
- if (v.minor !== minParsed.minor)
574060
- return v.minor > minParsed.minor;
574061
- return v.patch >= minParsed.patch;
574062
- }
574063
- function pickPlatformAsset(input) {
574064
- const byArch = SUPPORTED[input.platform];
574065
- if (!byArch) {
574066
- throw new Error(`unsupported platform: ${input.platform}`);
574067
- }
574068
- const asset = byArch[input.arch];
574069
- if (!asset) {
574070
- throw new Error(`unsupported arch: ${input.arch} on ${input.platform}`);
574071
- }
574072
- return asset;
574073
- }
574074
- function buildDownloadUrl(opts) {
574075
- const cleanVersion = opts.version.startsWith("v") ? opts.version.slice(1) : opts.version;
574076
- const v = `v${cleanVersion}`;
574077
- const fileBase = `node-${v}-${opts.asset.os}-${opts.asset.arch}`;
574078
- return `https://nodejs.org/dist/${v}/${fileBase}.${opts.asset.ext}`;
574079
- }
574080
- function defaultRoot() {
574081
- return join178(homedir88(), ".8gent", "runtime");
574082
- }
574083
- function getRuntimeDir(loc) {
574084
- const root = loc.root ?? defaultRoot();
574085
- return join178(root, `node-${loc.version}`);
574086
- }
574087
- function resolvedNodeBinPath(opts) {
574088
- const dir = getRuntimeDir(opts);
574089
- if (opts.platform === "win32") {
574090
- return join178(dir, "node.exe");
574091
- }
574092
- return join178(dir, "bin", "node");
574093
- }
574094
- async function isRuntimeReady(opts) {
574095
- const platform = opts.platform ?? process.platform;
574096
- const bin = resolvedNodeBinPath({
574097
- root: opts.root,
574098
- version: opts.version,
574099
- platform
574100
- });
574101
- if (!existsSync118(bin))
574102
- return false;
574103
- const min = opts.minVersion ?? opts.version;
574104
- try {
574105
- const { stdout } = await execFileAsync4(bin, ["--version"], {
574106
- timeout: 3000
574107
- });
574108
- const parsed = parseNodeVersion(stdout);
574109
- if (!parsed)
574110
- return false;
574111
- return satisfiesMinVersion(parsed, min);
574112
- } catch {
574113
- return false;
574114
- }
574115
- }
574116
- var execFileAsync4, SUPPORTED;
574117
- var init_node_runtime = __esm(() => {
574118
- execFileAsync4 = promisify6(execFile8);
574119
- SUPPORTED = {
574120
- darwin: {
574121
- arm64: { os: "darwin", arch: "arm64", ext: "tar.gz" },
574122
- x64: { os: "darwin", arch: "x64", ext: "tar.gz" }
574123
- },
574124
- linux: {
574125
- arm64: { os: "linux", arch: "arm64", ext: "tar.xz" },
574126
- x64: { os: "linux", arch: "x64", ext: "tar.xz" }
574127
- },
574128
- win32: {
574129
- x64: { os: "win", arch: "x64", ext: "zip" },
574130
- arm64: { os: "win", arch: "arm64", ext: "zip" }
574131
- }
574132
- };
574133
- });
574134
-
574135
- // packages/runtime/installer.ts
574136
- import { spawn as spawn29 } from "child_process";
574137
- import {
574138
- createWriteStream,
574139
- existsSync as existsSync119,
574140
- mkdirSync as mkdirSync83,
574141
- mkdtempSync as mkdtempSync12,
574142
- readdirSync as readdirSync27,
574143
- renameSync as renameSync17,
574144
- rmSync as rmSync17
574145
- } from "fs";
574146
- import { join as join179 } from "path";
574147
- import { Readable } from "stream";
574148
- import { pipeline } from "stream/promises";
574149
- function planInstall(input) {
574150
- const platform = input.platform ?? process.platform;
574151
- const arch = input.arch ?? process.arch;
574152
- const asset = pickPlatformAsset({ platform, arch });
574153
- const url = buildDownloadUrl({ version: input.version, asset });
574154
- const runtimeDir = getRuntimeDir({ root: input.root, version: input.version });
574155
- const binPath = resolvedNodeBinPath({
574156
- root: input.root,
574157
- version: input.version,
574158
- platform
574159
- });
574160
- return {
574161
- version: input.version,
574162
- url,
574163
- asset,
574164
- runtimeDir,
574165
- binPath,
574166
- archiveExt: asset.ext
574167
- };
574168
- }
574169
- async function installRuntime(input) {
574170
- const plan = planInstall(input);
574171
- const platform = input.platform ?? process.platform;
574172
- const minVersion = input.minVersion ?? input.version;
574173
- const ready = await isRuntimeReady({
574174
- root: input.root,
574175
- version: input.version,
574176
- platform,
574177
- minVersion
574178
- });
574179
- if (ready || input.dryRun) {
574180
- return {
574181
- ...plan,
574182
- action: ready ? "already-ready" : "dry-run"
574183
- };
574184
- }
574185
- const runtimeRoot = plan.runtimeDir.replace(/\/node-[^/]+$/, "");
574186
- mkdirSync83(runtimeRoot, { recursive: true });
574187
- const stagingDir = mkdtempSync12(join179(runtimeRoot, ".dl-"));
574188
- const archivePath = join179(stagingDir, `node.${plan.archiveExt}`);
574189
- try {
574190
- await downloadFile(plan.url, archivePath);
574191
- const extractRoot = join179(stagingDir, "extract");
574192
- mkdirSync83(extractRoot, { recursive: true });
574193
- await extractArchive(archivePath, extractRoot, plan.archiveExt);
574194
- const children = readdirSync27(extractRoot);
574195
- const nodeDir = children.find((n) => n.startsWith("node-"));
574196
- if (!nodeDir) {
574197
- throw new Error(`could not find node-* dir inside ${extractRoot}`);
574198
- }
574199
- const extractedPath = join179(extractRoot, nodeDir);
574200
- if (existsSync119(plan.runtimeDir)) {
574201
- rmSync17(plan.runtimeDir, { recursive: true, force: true });
574202
- }
574203
- renameSync17(extractedPath, plan.runtimeDir);
574204
- } catch (err) {
574205
- return {
574206
- ...plan,
574207
- action: "failed",
574208
- error: err instanceof Error ? err.message : String(err)
574209
- };
574210
- } finally {
574211
- try {
574212
- rmSync17(stagingDir, { recursive: true, force: true });
574213
- } catch {}
574214
- }
574215
- const reReady = await isRuntimeReady({
574216
- root: input.root,
574217
- version: input.version,
574218
- platform,
574219
- minVersion
574220
- });
574221
- if (!reReady) {
574222
- return {
574223
- ...plan,
574224
- action: "failed",
574225
- error: `installed runtime at ${plan.binPath} but it does not satisfy minVersion ${minVersion}`
574226
- };
574227
- }
574228
- return {
574229
- ...plan,
574230
- action: "installed"
574231
- };
574232
- }
574233
- async function downloadFile(url, dest) {
574234
- const res = await fetch(url, { redirect: "follow" });
574235
- if (!res.ok || !res.body) {
574236
- throw new Error(`download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
574237
- }
574238
- const out = createWriteStream(dest);
574239
- await pipeline(Readable.fromWeb(res.body), out);
574240
- }
574241
- async function extractArchive(archivePath, destDir, ext) {
574242
- if (ext === "tar.gz" || ext === "tar.xz") {
574243
- await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
574244
- } else if (ext === "zip") {
574245
- try {
574246
- await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
574247
- } catch {
574248
- await runCmd("powershell", [
574249
- "-Command",
574250
- `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
574251
- ]);
574252
- }
574253
- } else {
574254
- throw new Error(`unsupported archive ext: ${ext}`);
574255
- }
574256
- }
574257
- function runCmd(command, args) {
574258
- return new Promise((resolve, reject) => {
574259
- const proc = spawn29(command, args, { stdio: "ignore" });
574260
- proc.on("error", (err) => reject(err));
574261
- proc.on("close", (code) => {
574262
- if (code === 0)
574263
- resolve();
574264
- else
574265
- reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
574266
- });
574267
- });
574268
- }
574269
- var init_installer = __esm(() => {
574270
- init_node_runtime();
574271
- });
574272
-
574273
- // packages/runtime/contract-validator.ts
574274
- var init_contract_validator = __esm(() => {
574275
- init_types3();
574276
- });
574277
-
574278
- // packages/runtime/index.ts
574279
- import { join as join180 } from "path";
574280
- import { execFile as execFile9 } from "child_process";
574281
- import { promisify as promisify7 } from "util";
574282
- async function ensureNodeFor(minVersion) {
574283
- try {
574284
- const { stdout } = await execFileAsync5("node", ["--version"], {
574285
- timeout: 3000
574286
- });
574287
- const parsed = parseNodeVersion(stdout);
574288
- if (parsed && satisfiesMinVersion(parsed, minVersion)) {
574289
- return null;
574290
- }
574291
- } catch {}
574292
- const ready = await isRuntimeReady({
574293
- version: MANAGED_NODE_VERSION,
574294
- minVersion
574295
- });
574296
- const dir = getRuntimeDir({ version: MANAGED_NODE_VERSION });
574297
- const binDir = process.platform === "win32" ? dir : join180(dir, "bin");
574298
- if (ready)
574299
- return binDir;
574300
- const result = await installRuntime({
574301
- version: MANAGED_NODE_VERSION,
574302
- minVersion
574303
- });
574304
- if (result.action === "failed") {
574305
- throw new Error(`failed to install Node ${MANAGED_NODE_VERSION}: ${result.error ?? "unknown"}`);
574306
- }
574307
- return binDir;
574308
- }
574309
- var execFileAsync5, MANAGED_NODE_VERSION = "22.14.0";
574310
- var init_runtime = __esm(() => {
574311
- init_installer();
574312
- init_node_runtime();
574313
- init_node_runtime();
574314
- init_installer();
574315
- init_contract_validator();
574316
- execFileAsync5 = promisify7(execFile9);
574317
- });
574318
-
574319
574881
  // packages/goal/types.ts
574320
574882
  var DEFAULT_BUDGET, SATISFIED_CONFIDENCE_FLOOR = 0.75;
574321
574883
  var init_types6 = __esm(() => {
@@ -576179,7 +576741,7 @@ Nothing was changed. Type /update again to apply.`);
576179
576741
  // apps/tui/src/lib/telegram-setup.ts
576180
576742
  import {
576181
576743
  chmodSync as chmodSync6,
576182
- existsSync as existsSync134,
576744
+ existsSync as existsSync136,
576183
576745
  lstatSync as lstatSync31,
576184
576746
  mkdirSync as mkdirSync96,
576185
576747
  readFileSync as readFileSync120,
@@ -576188,7 +576750,7 @@ import {
576188
576750
  writeFileSync as writeFileSync76
576189
576751
  } from "fs";
576190
576752
  import { homedir as homedir95 } from "os";
576191
- import { dirname as dirname82, join as join200 } from "path";
576753
+ import { dirname as dirname83, join as join201 } from "path";
576192
576754
  function redact3(text, key) {
576193
576755
  let out = text;
576194
576756
  if (key) {
@@ -576205,7 +576767,7 @@ function cleanLabel(raw, max = 40) {
576205
576767
  }
576206
576768
  function ensureEnvFile(file) {
576207
576769
  const path = expand4(file);
576208
- const dir = dirname82(path);
576770
+ const dir = dirname83(path);
576209
576771
  const notes = [];
576210
576772
  const kind = (p) => {
576211
576773
  try {
@@ -576299,9 +576861,9 @@ async function getSeenChats(key) {
576299
576861
  return { chats: [...seen.values()] };
576300
576862
  }
576301
576863
  function mergeSettings(chatIds, envFile, tokenVar) {
576302
- const path = join200(homeDir4(), ".8gent", "settings.json");
576864
+ const path = join201(homeDir4(), ".8gent", "settings.json");
576303
576865
  let settings = {};
576304
- if (existsSync134(path)) {
576866
+ if (existsSync136(path)) {
576305
576867
  try {
576306
576868
  const parsed = JSON.parse(readFileSync120(path, "utf8"));
576307
576869
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
@@ -576324,7 +576886,7 @@ function mergeSettings(chatIds, envFile, tokenVar) {
576324
576886
  botEnvFile: envFile,
576325
576887
  botTokenVar: tokenVar
576326
576888
  };
576327
- mkdirSync96(dirname82(path), { recursive: true });
576889
+ mkdirSync96(dirname83(path), { recursive: true });
576328
576890
  const tmp = `${path}.tmp-${process.pid}`;
576329
576891
  rmSync18(tmp, { force: true });
576330
576892
  writeFileSync76(tmp, `${JSON.stringify(settings, null, 2)}
@@ -576334,7 +576896,7 @@ function mergeSettings(chatIds, envFile, tokenVar) {
576334
576896
  }
576335
576897
  function configured() {
576336
576898
  try {
576337
- const pm = JSON.parse(readFileSync120(join200(homeDir4(), ".8gent", "settings.json"), "utf8"))?.postMessage;
576899
+ const pm = JSON.parse(readFileSync120(join201(homeDir4(), ".8gent", "settings.json"), "utf8"))?.postMessage;
576338
576900
  const file = typeof pm?.botEnvFile === "string" ? pm.botEnvFile : DEFAULT_ENV_FILE;
576339
576901
  const name = typeof pm?.botTokenVar === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(pm.botTokenVar) ? pm.botTokenVar : DEFAULT_TOKEN_VAR;
576340
576902
  return { file, name };
@@ -576409,7 +576971,7 @@ async function runTelegramSetup2(args) {
576409
576971
  ].join(`
576410
576972
  `));
576411
576973
  }
576412
- var DEFAULT_ENV_FILE = "~/.8gent/secrets/telegram-bot.env", DEFAULT_TOKEN_VAR = "TELEGRAM_BOT_TOKEN", BOT_TOKEN_RE2, CHAT_ID_RE, homeDir4 = () => process.env.HOME || homedir95(), expand4 = (p) => p.startsWith("~/") ? join200(homeDir4(), p.slice(2)) : p, looser = (mode, allowed) => (mode & 511 & ~allowed) !== 0, RESTART = "Restart 8gent for this to take effect. The first post to each chat then asks you to approve it.";
576974
+ var DEFAULT_ENV_FILE = "~/.8gent/secrets/telegram-bot.env", DEFAULT_TOKEN_VAR = "TELEGRAM_BOT_TOKEN", BOT_TOKEN_RE2, CHAT_ID_RE, homeDir4 = () => process.env.HOME || homedir95(), expand4 = (p) => p.startsWith("~/") ? join201(homeDir4(), p.slice(2)) : p, looser = (mode, allowed) => (mode & 511 & ~allowed) !== 0, RESTART = "Restart 8gent for this to take effect. The first post to each chat then asks you to approve it.";
576413
576975
  var init_telegram_setup = __esm(() => {
576414
576976
  init_post_message();
576415
576977
  BOT_TOKEN_RE2 = /(?:bot)?\d{6,}:[A-Za-z0-9_-]{20,}/g;
@@ -576668,11 +577230,11 @@ Personal 8gent mini-app. Iteration is driven by \`SKILL.md\`. Run tests with \`b
576668
577230
  }
576669
577231
 
576670
577232
  // packages/app-creator/creator.ts
576671
- import { existsSync as existsSync135, mkdirSync as mkdirSync98, writeFileSync as writeFileSync78 } from "fs";
577233
+ import { existsSync as existsSync137, mkdirSync as mkdirSync98, writeFileSync as writeFileSync78 } from "fs";
576672
577234
  import { homedir as homedir96 } from "os";
576673
- import { dirname as dirname83, join as join202 } from "path";
577235
+ import { dirname as dirname84, join as join203 } from "path";
576674
577236
  function defaultAppsRoot() {
576675
- return join202(homedir96(), ".8gent", "apps");
577237
+ return join203(homedir96(), ".8gent", "apps");
576676
577238
  }
576677
577239
  function buildManifest2(input) {
576678
577240
  const slug = slugify3(input.name);
@@ -576706,7 +577268,7 @@ function draftApp(input) {
576706
577268
  };
576707
577269
  }
576708
577270
  const m = built.manifest;
576709
- const dir = join202(root, m.name);
577271
+ const dir = join203(root, m.name);
576710
577272
  const files = {
576711
577273
  "manifest.json": `${JSON.stringify(m, null, 2)}
576712
577274
  `,
@@ -576726,7 +577288,7 @@ function createApp(input) {
576726
577288
  return { ...draft, persisted: false, written: [] };
576727
577289
  }
576728
577290
  const dir = draft.dir;
576729
- const targetExists = existsSync135(dir);
577291
+ const targetExists = existsSync137(dir);
576730
577292
  if (targetExists && !input.allowOverwrite) {
576731
577293
  return {
576732
577294
  ...draft,
@@ -576737,11 +577299,11 @@ function createApp(input) {
576737
577299
  ]
576738
577300
  };
576739
577301
  }
576740
- mkdirSync98(join202(dir, "tests"), { recursive: true });
577302
+ mkdirSync98(join203(dir, "tests"), { recursive: true });
576741
577303
  const written = [];
576742
577304
  for (const [rel, contents] of Object.entries(draft.files)) {
576743
- const full = join202(dir, rel);
576744
- mkdirSync98(dirname83(full), { recursive: true });
577305
+ const full = join203(dir, rel);
577306
+ mkdirSync98(dirname84(full), { recursive: true });
576745
577307
  writeFileSync78(full, contents);
576746
577308
  written.push(full);
576747
577309
  }
@@ -577573,8 +578135,8 @@ var init_producer = __esm(() => {
577573
578135
  });
577574
578136
 
577575
578137
  // packages/pet/companion.ts
577576
- import { existsSync as existsSync139, mkdirSync as mkdirSync104, readFileSync as readFileSync122, writeFileSync as writeFileSync80 } from "fs";
577577
- import { join as join204 } from "path";
578138
+ import { existsSync as existsSync141, mkdirSync as mkdirSync104, readFileSync as readFileSync122, writeFileSync as writeFileSync80 } from "fs";
578139
+ import { join as join205 } from "path";
577578
578140
  function seedRng(seed) {
577579
578141
  let h = 0;
577580
578142
  for (let i = 0;i < seed.length; i++) {
@@ -577712,12 +578274,12 @@ function generateCompanion(sessionId) {
577712
578274
  }
577713
578275
  function getDeckPath() {
577714
578276
  const home = process.env.HOME || process.env.USERPROFILE || "~";
577715
- return join204(home, ".8gent", "companion-deck.json");
578277
+ return join205(home, ".8gent", "companion-deck.json");
577716
578278
  }
577717
578279
  function loadDeck() {
577718
578280
  const deckPath = getDeckPath();
577719
578281
  try {
577720
- if (existsSync139(deckPath)) {
578282
+ if (existsSync141(deckPath)) {
577721
578283
  return JSON.parse(readFileSync122(deckPath, "utf-8"));
577722
578284
  }
577723
578285
  } catch {}
@@ -578161,7 +578723,7 @@ var init_companion = __esm(() => {
578161
578723
  // packages/orchestration/adaptive-pipeline.ts
578162
578724
  import { spawn as spawn32 } from "child_process";
578163
578725
  import { homedir as homedir97 } from "os";
578164
- import { join as join205 } from "path";
578726
+ import { join as join206 } from "path";
578165
578727
 
578166
578728
  class Obstacle {
578167
578729
  type;
@@ -578650,7 +579212,7 @@ var init_adaptive_pipeline = __esm(() => {
578650
579212
  init_role_config();
578651
579213
  OLLAMA_URL2 = process.env.OLLAMA_BASE_URL || "http://localhost:11434";
578652
579214
  LMSTUDIO_URL2 = process.env.LMSTUDIO_BASE_URL || "http://localhost:1234/v1";
578653
- BRIDGE_PATH2 = process.env.APPLE_FOUNDATION_BRIDGE || join205(homedir97(), ".8gent", "bin", "apple-foundation-bridge");
579215
+ BRIDGE_PATH2 = process.env.APPLE_FOUNDATION_BRIDGE || join206(homedir97(), ".8gent", "bin", "apple-foundation-bridge");
578654
579216
  ((Severity) => {
578655
579217
  Severity[Severity["Trivial"] = 1] = "Trivial";
578656
579218
  Severity[Severity["Moderate"] = 2] = "Moderate";
@@ -588527,7 +589089,7 @@ init_local_model_server();
588527
589089
  init_sequential_pipeline();
588528
589090
  init_speak();
588529
589091
  var import_react104 = __toESM(require_react(), 1);
588530
- var import_package = __toESM(require_package(), 1);
589092
+ var import_package2 = __toESM(require_package(), 1);
588531
589093
 
588532
589094
  // apps/tui/src/components/ActivityMonitor.tsx
588533
589095
  var import_react31 = __toESM(require_react(), 1);
@@ -598971,234 +599533,6 @@ function useImageInput(options = {}) {
598971
599533
  // apps/tui/src/components/message-list.tsx
598972
599534
  var import_react63 = __toESM(require_react(), 1);
598973
599535
 
598974
- // apps/tui/src/lib/chat-layout.ts
598975
- var SHELL_CHROME = 4;
598976
- var COLUMN_GAP = 1;
598977
- var ACTIVITY_RAIL_WIDTH = 34;
598978
- function chatColumnWidth(cols, shell, min = 24) {
598979
- let width = cols - SHELL_CHROME;
598980
- if (shell.planWidth && shell.planWidth > 0)
598981
- width -= shell.planWidth + COLUMN_GAP;
598982
- if (shell.activity)
598983
- width -= ACTIVITY_RAIL_WIDTH + COLUMN_GAP;
598984
- return Math.max(min, width);
598985
- }
598986
- var USER_BUBBLE_SHARE = 0.78;
598987
- function bubbleWidths(contentWidth, role) {
598988
- const inner = Math.max(16, contentWidth - 2);
598989
- const bubble = role === "user" ? Math.max(16, Math.floor(inner * USER_BUBBLE_SHARE)) : inner;
598990
- return { bubble, wrap: Math.max(8, bubble - 2) };
598991
- }
598992
-
598993
- // apps/tui/src/lib/tool-trail.ts
598994
- var COLLAPSE_THRESHOLD = 8;
598995
- var MAX_TRAIL_ROWS = 12;
598996
- var PATH_KEYS = ["path", "file_path", "filePath", "file", "directory", "dir"];
598997
- var COMMAND_KEYS = ["command", "cmd"];
598998
- var QUERY_KEYS = ["query", "pattern", "q", "search", "url"];
598999
- function oneLine(s) {
599000
- return s.replace(/\s+/g, " ").trim().slice(0, 200);
599001
- }
599002
- function pick3(args, keys) {
599003
- for (const k of keys) {
599004
- const v = args[k];
599005
- if (typeof v === "string" && v.trim())
599006
- return oneLine(v);
599007
- }
599008
- return null;
599009
- }
599010
- function summarizeToolArgs(tool, args) {
599011
- if (!args || typeof args !== "object")
599012
- return "";
599013
- const isCommand = /command|shell|exec|bash/.test(tool);
599014
- const order = isCommand ? [COMMAND_KEYS, PATH_KEYS, QUERY_KEYS] : /search|grep|glob|find|fetch/.test(tool) ? [QUERY_KEYS, PATH_KEYS, COMMAND_KEYS] : [PATH_KEYS, COMMAND_KEYS, QUERY_KEYS];
599015
- for (const keys of order) {
599016
- const hit = pick3(args, keys);
599017
- if (hit)
599018
- return hit;
599019
- }
599020
- for (const v of Object.values(args)) {
599021
- if (typeof v === "string" && v.trim())
599022
- return oneLine(v);
599023
- }
599024
- return "";
599025
- }
599026
- var GATE_PREFIX = /^\s*\[([A-Z0-9_ -]*\b(?:BLOCKED|DENIED)\b[A-Z0-9_ -]*)\]/;
599027
- var OTHER_BLOCK = /^\s*(?:Path blocked by path-guard|Path traversal blocked)|Hook blocked tool/;
599028
- var RULE_NAME = /\[([a-z0-9]+(?:-[a-z0-9]+)+)\]/;
599029
- function classifyToolResult(success, preview) {
599030
- const text = preview ?? "";
599031
- const gate = text.match(GATE_PREFIX);
599032
- if (gate || OTHER_BLOCK.test(text)) {
599033
- if (gate && /DENIED/.test(gate[1]))
599034
- return { status: "blocked", reason: "denied" };
599035
- const rule = text.match(RULE_NAME);
599036
- return { status: "blocked", reason: rule ? `blocked: [${rule[1]}]` : "blocked" };
599037
- }
599038
- const exit = text.match(/^\s*Exit code (-?\d+)/);
599039
- if (exit && exit[1] !== "0")
599040
- return { status: "fail", reason: `exit ${exit[1]}` };
599041
- if (!success || /^\s*(?:Error\b|Unknown tool:)/.test(text)) {
599042
- const first = (text.split(`
599043
- `).find((l) => l.trim()) ?? "").replace(/^\s*Error(?: running tool "[^"]*")?:?\s*/, "").trim();
599044
- if (!first)
599045
- return { status: "fail", reason: "error" };
599046
- return { status: "fail", reason: first.length > 40 ? `${first.slice(0, 39)}\u2026` : first };
599047
- }
599048
- return { status: "ok" };
599049
- }
599050
- function toTrailEntry(event) {
599051
- const { status, reason } = classifyToolResult(event.success !== false, event.resultPreview);
599052
- const entry = {
599053
- tool: event.toolName,
599054
- summary: summarizeToolArgs(event.toolName, event.args),
599055
- status
599056
- };
599057
- if (reason)
599058
- entry.reason = reason;
599059
- if (event.toolName === "update_plan") {
599060
- const items = event.args?.plan;
599061
- if (Array.isArray(items)) {
599062
- const plan = items.map((i) => i && typeof i === "object" ? i : {}).filter((i) => typeof i.step === "string" && i.step.trim() !== "").map((i) => ({ step: i.step, status: typeof i.status === "string" ? i.status : "pending" }));
599063
- if (plan.length > 0)
599064
- entry.plan = plan;
599065
- }
599066
- }
599067
- return entry;
599068
- }
599069
- function isReadTool(tool) {
599070
- return /^(?:read_|list_|get_|search|grep|glob|find|view|web_search|web_fetch)/.test(tool);
599071
- }
599072
- function commonDir(paths) {
599073
- const split = paths.map((p) => p.split("/").slice(0, -1));
599074
- const first = split[0] ?? [];
599075
- let n = 0;
599076
- while (n < first.length && split.every((s) => s[n] === first[n]))
599077
- n++;
599078
- return n > 0 ? `${first.slice(0, n).join("/")}/` : "";
599079
- }
599080
- function collapseTrail(entries, maxRows = MAX_TRAIL_ROWS) {
599081
- let rows = entries.map((e) => ({ ...e, count: 1 }));
599082
- if (entries.length > COLLAPSE_THRESHOLD) {
599083
- const out = [];
599084
- for (let i = 0;i < entries.length; ) {
599085
- const e = entries[i];
599086
- let j = i + 1;
599087
- if (e.status === "ok" && isReadTool(e.tool)) {
599088
- while (j < entries.length && entries[j].tool === e.tool && entries[j].status === "ok")
599089
- j++;
599090
- }
599091
- if (j - i > 1) {
599092
- const run = entries.slice(i, j);
599093
- out.push({
599094
- tool: e.tool,
599095
- status: "ok",
599096
- count: run.length,
599097
- summary: commonDir(run.map((r) => r.summary))
599098
- });
599099
- } else {
599100
- out.push({ ...e, count: 1 });
599101
- }
599102
- i = j;
599103
- }
599104
- rows = out;
599105
- }
599106
- const cap = Math.max(1, Math.floor(maxRows));
599107
- if (rows.length > cap) {
599108
- let toFold = rows.length - cap + 1;
599109
- const fold = new Set;
599110
- for (const wantOk of [true, false]) {
599111
- for (let i = 0;i < rows.length && toFold > 0; i++) {
599112
- if (fold.has(i) || wantOk && rows[i].status !== "ok")
599113
- continue;
599114
- fold.add(i);
599115
- toFold--;
599116
- }
599117
- }
599118
- const folded = rows.filter((_, i) => fold.has(i));
599119
- const count = (s) => folded.filter((r) => r.status === s).reduce((n, r) => n + r.count, 0);
599120
- const failed = count("fail");
599121
- const blocked = count("blocked");
599122
- const summary = {
599123
- tool: "",
599124
- summary: "",
599125
- status: failed > 0 ? "fail" : blocked > 0 ? "blocked" : "ok",
599126
- count: folded.reduce((n, r) => n + r.count, 0),
599127
- folded: true,
599128
- all: fold.size === rows.length
599129
- };
599130
- if (failed > 0)
599131
- summary.failed = failed;
599132
- if (blocked > 0)
599133
- summary.blocked = blocked;
599134
- rows = [summary, ...rows.filter((_, i) => !fold.has(i))];
599135
- }
599136
- return rows;
599137
- }
599138
- var ICONS = { ok: "\u2713", fail: "\u2717", blocked: "\u2298" };
599139
- function formatTrailRow(row, width) {
599140
- const icon = ICONS[row.status];
599141
- const budget = Math.max(1, width - icon.length - 1);
599142
- if (row.folded) {
599143
- const parts = [`${row.count} ${row.all ? "calls" : "earlier calls"}`];
599144
- if (row.failed)
599145
- parts.push(`${row.failed} failed`);
599146
- if (row.blocked)
599147
- parts.push(`${row.blocked} blocked`);
599148
- return { icon, text: clip(parts.join(", "), budget) };
599149
- }
599150
- const head = row.count > 1 ? `${row.tool} \xD7${row.count}` : row.tool;
599151
- const tail = row.reason ? ` (${row.reason})` : "";
599152
- let summary = row.count > 1 ? row.summary ? `(${row.summary}\u2026)` : "" : row.summary;
599153
- if (!summary)
599154
- return { icon, text: clip(`${head}${tail}`, budget) };
599155
- const room = budget - head.length - 1 - tail.length;
599156
- if (room < 4)
599157
- return { icon, text: clip(`${head}${tail}`, budget) };
599158
- if (summary.length > room)
599159
- summary = `${summary.slice(0, room - 1)}\u2026`;
599160
- return { icon, text: `${head} ${summary}${tail}` };
599161
- }
599162
- function clip(s, width) {
599163
- return s.length <= width ? s : `${s.slice(0, Math.max(0, width - 1))}\u2026`;
599164
- }
599165
- function buildChatItems(messages) {
599166
- const items = [];
599167
- let pending = [];
599168
- let pendingFirst = null;
599169
- const flush = () => {
599170
- if (pending.length > 0 && pendingFirst) {
599171
- items.push({
599172
- message: { ...pendingFirst, id: `trail-${pendingFirst.id}`, role: "tool", content: "" },
599173
- trail: pending
599174
- });
599175
- }
599176
- pending = [];
599177
- pendingFirst = null;
599178
- };
599179
- for (const m of messages) {
599180
- if (m.role === "tool") {
599181
- if (m.toolTrail) {
599182
- if (!pendingFirst)
599183
- pendingFirst = m;
599184
- pending.push(m.toolTrail);
599185
- }
599186
- continue;
599187
- }
599188
- if (m.role === "assistant") {
599189
- items.push({ message: m, trail: pending });
599190
- pending = [];
599191
- pendingFirst = null;
599192
- continue;
599193
- }
599194
- if (m.role === "user")
599195
- flush();
599196
- items.push({ message: m, trail: [] });
599197
- }
599198
- flush();
599199
- return items;
599200
- }
599201
-
599202
599536
  // apps/tui/src/hooks/useMouseScroll.ts
599203
599537
  var import_react57 = __toESM(require_react(), 1);
599204
599538
  function useMouseScroll({
@@ -599224,111 +599558,25 @@ function useMouseScroll({
599224
599558
  }, [enabled, step]);
599225
599559
  }
599226
599560
 
599227
- // apps/tui/src/components/fade-transition.tsx
599228
- var import_react58 = __toESM(require_react(), 1);
599229
- var jsx_dev_runtime30 = __toESM(require_jsx_dev_runtime(), 1);
599230
- function FadeIn({ children, duration = 300, delay = 0, onComplete }) {
599231
- const [started, setStarted] = import_react58.useState(false);
599232
- import_react58.useEffect(() => {
599233
- const timeout = setTimeout(() => {
599234
- setStarted(true);
599235
- }, delay);
599236
- return () => clearTimeout(timeout);
599237
- }, [delay]);
599238
- import_react58.useEffect(() => {
599239
- if (!started)
599240
- return;
599241
- const timeout = setTimeout(() => {
599242
- onComplete?.();
599243
- }, duration);
599244
- return () => clearTimeout(timeout);
599245
- }, [started, duration, onComplete]);
599246
- if (!started)
599247
- return null;
599248
- return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
599249
- children
599250
- }, undefined, false, undefined, this);
599251
- }
599252
-
599253
- // apps/tui/src/components/ToolTrail.tsx
599254
- var import_react59 = __toESM(require_react(), 1);
599255
- var jsx_dev_runtime31 = __toESM(require_jsx_dev_runtime(), 1);
599256
- function iconColor(status) {
599257
- return status === "ok" ? t.green : status === "fail" ? t.red : t.orange;
599258
- }
599259
- function useLandingRows(total, animate) {
599260
- const [shown, setShown] = import_react59.useState(total);
599261
- const shownRef = import_react59.useRef(total);
599262
- shownRef.current = shown;
599263
- const landingRef = import_react59.useRef(null);
599264
- import_react59.useEffect(() => {
599265
- const from = shownRef.current;
599266
- if (total <= from + 1 || !motionEnabled(animate)) {
599267
- landingRef.current = null;
599268
- setShown(total);
599269
- return;
599270
- }
599271
- landingRef.current = total;
599272
- const step = staggerFor(total - from);
599273
- let n = from + 1;
599274
- setShown(n);
599275
- const id = setInterval(() => {
599276
- n += 1;
599277
- setShown(Math.min(total, n));
599278
- if (n >= total) {
599279
- landingRef.current = null;
599280
- clearInterval(id);
599281
- }
599282
- }, step);
599283
- return () => clearInterval(id);
599284
- }, [total, animate]);
599285
- if (landingRef.current === total)
599286
- return Math.min(shown, total);
599287
- if (total <= shown + 1)
599288
- return total;
599289
- return shown;
599290
- }
599291
- function ToolTrail({
599292
- entries,
599293
- width,
599294
- maxRows,
599295
- animate = true
599296
- }) {
599297
- const rows = collapseTrail(entries, maxRows);
599298
- const visible = useLandingRows(rows.length, animate);
599299
- if (rows.length === 0)
599300
- return null;
599301
- return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
599302
- flexDirection: "column",
599303
- flexShrink: 0,
599304
- children: rows.slice(0, visible).map((row, i) => {
599305
- const { text } = formatTrailRow(row, width);
599306
- const g = glyphs();
599307
- const icon = row.status === "ok" ? g.ok : row.status === "fail" ? g.fail : g.blocked;
599308
- return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
599309
- width,
599310
- children: [
599311
- /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
599312
- color: iconColor(row.status),
599313
- children: icon
599314
- }, undefined, false, undefined, this),
599315
- /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
599316
- color: row.status === "ok" ? t.muted : t.textPrimary,
599317
- wrap: "truncate-end",
599318
- children: ` ${text}`
599319
- }, undefined, false, undefined, this)
599320
- ]
599321
- }, i, true, undefined, this);
599322
- })
599323
- }, undefined, false, undefined, this);
599561
+ // apps/tui/src/lib/chat-layout.ts
599562
+ var SHELL_CHROME = 4;
599563
+ var COLUMN_GAP = 1;
599564
+ var ACTIVITY_RAIL_WIDTH = 34;
599565
+ function chatColumnWidth(cols, shell, min = 24) {
599566
+ let width = cols - SHELL_CHROME;
599567
+ if (shell.planWidth && shell.planWidth > 0)
599568
+ width -= shell.planWidth + COLUMN_GAP;
599569
+ if (shell.activity)
599570
+ width -= ACTIVITY_RAIL_WIDTH + COLUMN_GAP;
599571
+ return Math.max(min, width);
599324
599572
  }
599325
- function toolTrailRows(entries, maxRows) {
599326
- return entries.length === 0 ? 0 : collapseTrail(entries, maxRows).length;
599573
+ var USER_BUBBLE_SHARE = 0.78;
599574
+ function bubbleWidths(contentWidth, role) {
599575
+ const inner = Math.max(16, contentWidth - 2);
599576
+ const bubble = role === "user" ? Math.max(16, Math.floor(inner * USER_BUBBLE_SHARE)) : inner;
599577
+ return { bubble, wrap: Math.max(8, bubble - 2) };
599327
599578
  }
599328
599579
 
599329
- // apps/tui/src/components/TurnResults.tsx
599330
- var import_react60 = __toESM(require_react(), 1);
599331
-
599332
599580
  // apps/tui/src/lib/inline-markdown.ts
599333
599581
  var MAX_DEPTH3 = 3;
599334
599582
  var INDENT_STEP = 2;
@@ -599643,6 +599891,215 @@ function blockRows(block, width) {
599643
599891
  }
599644
599892
  }
599645
599893
 
599894
+ // apps/tui/src/lib/tool-trail.ts
599895
+ var COLLAPSE_THRESHOLD = 8;
599896
+ var MAX_TRAIL_ROWS = 12;
599897
+ var PATH_KEYS = ["path", "file_path", "filePath", "file", "directory", "dir"];
599898
+ var COMMAND_KEYS = ["command", "cmd"];
599899
+ var QUERY_KEYS = ["query", "pattern", "q", "search", "url"];
599900
+ function oneLine(s) {
599901
+ return s.replace(/\s+/g, " ").trim().slice(0, 200);
599902
+ }
599903
+ function pick3(args, keys) {
599904
+ for (const k of keys) {
599905
+ const v = args[k];
599906
+ if (typeof v === "string" && v.trim())
599907
+ return oneLine(v);
599908
+ }
599909
+ return null;
599910
+ }
599911
+ function summarizeToolArgs(tool, args) {
599912
+ if (!args || typeof args !== "object")
599913
+ return "";
599914
+ const isCommand = /command|shell|exec|bash/.test(tool);
599915
+ const order = isCommand ? [COMMAND_KEYS, PATH_KEYS, QUERY_KEYS] : /search|grep|glob|find|fetch/.test(tool) ? [QUERY_KEYS, PATH_KEYS, COMMAND_KEYS] : [PATH_KEYS, COMMAND_KEYS, QUERY_KEYS];
599916
+ for (const keys of order) {
599917
+ const hit = pick3(args, keys);
599918
+ if (hit)
599919
+ return hit;
599920
+ }
599921
+ for (const v of Object.values(args)) {
599922
+ if (typeof v === "string" && v.trim())
599923
+ return oneLine(v);
599924
+ }
599925
+ return "";
599926
+ }
599927
+ var GATE_PREFIX = /^\s*\[([A-Z0-9_ -]*\b(?:BLOCKED|DENIED)\b[A-Z0-9_ -]*)\]/;
599928
+ var OTHER_BLOCK = /^\s*(?:Path blocked by path-guard|Path traversal blocked)|Hook blocked tool/;
599929
+ var RULE_NAME = /\[([a-z0-9]+(?:-[a-z0-9]+)+)\]/;
599930
+ function classifyToolResult(success, preview) {
599931
+ const text = preview ?? "";
599932
+ const gate = text.match(GATE_PREFIX);
599933
+ if (gate || OTHER_BLOCK.test(text)) {
599934
+ if (gate && /DENIED/.test(gate[1]))
599935
+ return { status: "blocked", reason: "denied" };
599936
+ const rule = text.match(RULE_NAME);
599937
+ return { status: "blocked", reason: rule ? `blocked: [${rule[1]}]` : "blocked" };
599938
+ }
599939
+ const exit = text.match(/^\s*Exit code (-?\d+)/);
599940
+ if (exit && exit[1] !== "0")
599941
+ return { status: "fail", reason: `exit ${exit[1]}` };
599942
+ if (!success || /^\s*(?:Error\b|Unknown tool:)/.test(text)) {
599943
+ const first = (text.split(`
599944
+ `).find((l) => l.trim()) ?? "").replace(/^\s*Error(?: running tool "[^"]*")?:?\s*/, "").trim();
599945
+ if (!first)
599946
+ return { status: "fail", reason: "error" };
599947
+ return { status: "fail", reason: first.length > 40 ? `${first.slice(0, 39)}\u2026` : first };
599948
+ }
599949
+ return { status: "ok" };
599950
+ }
599951
+ function toTrailEntry(event) {
599952
+ const { status, reason } = classifyToolResult(event.success !== false, event.resultPreview);
599953
+ const entry = {
599954
+ tool: event.toolName,
599955
+ summary: summarizeToolArgs(event.toolName, event.args),
599956
+ status
599957
+ };
599958
+ if (reason)
599959
+ entry.reason = reason;
599960
+ if (event.toolName === "update_plan") {
599961
+ const items = event.args?.plan;
599962
+ if (Array.isArray(items)) {
599963
+ const plan = items.map((i) => i && typeof i === "object" ? i : {}).filter((i) => typeof i.step === "string" && i.step.trim() !== "").map((i) => ({ step: i.step, status: typeof i.status === "string" ? i.status : "pending" }));
599964
+ if (plan.length > 0)
599965
+ entry.plan = plan;
599966
+ }
599967
+ }
599968
+ return entry;
599969
+ }
599970
+ function isReadTool(tool) {
599971
+ return /^(?:read_|list_|get_|search|grep|glob|find|view|web_search|web_fetch)/.test(tool);
599972
+ }
599973
+ function commonDir(paths) {
599974
+ const split = paths.map((p) => p.split("/").slice(0, -1));
599975
+ const first = split[0] ?? [];
599976
+ let n = 0;
599977
+ while (n < first.length && split.every((s) => s[n] === first[n]))
599978
+ n++;
599979
+ return n > 0 ? `${first.slice(0, n).join("/")}/` : "";
599980
+ }
599981
+ function collapseTrail(entries, maxRows = MAX_TRAIL_ROWS) {
599982
+ let rows = entries.map((e) => ({ ...e, count: 1 }));
599983
+ if (entries.length > COLLAPSE_THRESHOLD) {
599984
+ const out = [];
599985
+ for (let i = 0;i < entries.length; ) {
599986
+ const e = entries[i];
599987
+ let j = i + 1;
599988
+ if (e.status === "ok" && isReadTool(e.tool)) {
599989
+ while (j < entries.length && entries[j].tool === e.tool && entries[j].status === "ok")
599990
+ j++;
599991
+ }
599992
+ if (j - i > 1) {
599993
+ const run = entries.slice(i, j);
599994
+ out.push({
599995
+ tool: e.tool,
599996
+ status: "ok",
599997
+ count: run.length,
599998
+ summary: commonDir(run.map((r) => r.summary))
599999
+ });
600000
+ } else {
600001
+ out.push({ ...e, count: 1 });
600002
+ }
600003
+ i = j;
600004
+ }
600005
+ rows = out;
600006
+ }
600007
+ const cap = Math.max(1, Math.floor(maxRows));
600008
+ if (rows.length > cap) {
600009
+ let toFold = rows.length - cap + 1;
600010
+ const fold = new Set;
600011
+ for (const wantOk of [true, false]) {
600012
+ for (let i = 0;i < rows.length && toFold > 0; i++) {
600013
+ if (fold.has(i) || wantOk && rows[i].status !== "ok")
600014
+ continue;
600015
+ fold.add(i);
600016
+ toFold--;
600017
+ }
600018
+ }
600019
+ const folded = rows.filter((_, i) => fold.has(i));
600020
+ const count = (s) => folded.filter((r) => r.status === s).reduce((n, r) => n + r.count, 0);
600021
+ const failed = count("fail");
600022
+ const blocked = count("blocked");
600023
+ const summary = {
600024
+ tool: "",
600025
+ summary: "",
600026
+ status: failed > 0 ? "fail" : blocked > 0 ? "blocked" : "ok",
600027
+ count: folded.reduce((n, r) => n + r.count, 0),
600028
+ folded: true,
600029
+ all: fold.size === rows.length
600030
+ };
600031
+ if (failed > 0)
600032
+ summary.failed = failed;
600033
+ if (blocked > 0)
600034
+ summary.blocked = blocked;
600035
+ rows = [summary, ...rows.filter((_, i) => !fold.has(i))];
600036
+ }
600037
+ return rows;
600038
+ }
600039
+ var ICONS = { ok: "\u2713", fail: "\u2717", blocked: "\u2298" };
600040
+ function formatTrailRow(row, width) {
600041
+ const icon = ICONS[row.status];
600042
+ const budget = Math.max(1, width - icon.length - 1);
600043
+ if (row.folded) {
600044
+ const parts = [`${row.count} ${row.all ? "calls" : "earlier calls"}`];
600045
+ if (row.failed)
600046
+ parts.push(`${row.failed} failed`);
600047
+ if (row.blocked)
600048
+ parts.push(`${row.blocked} blocked`);
600049
+ return { icon, text: clip(parts.join(", "), budget) };
600050
+ }
600051
+ const head = row.count > 1 ? `${row.tool} \xD7${row.count}` : row.tool;
600052
+ const tail = row.reason ? ` (${row.reason})` : "";
600053
+ let summary = row.count > 1 ? row.summary ? `(${row.summary}\u2026)` : "" : row.summary;
600054
+ if (!summary)
600055
+ return { icon, text: clip(`${head}${tail}`, budget) };
600056
+ const room = budget - head.length - 1 - tail.length;
600057
+ if (room < 4)
600058
+ return { icon, text: clip(`${head}${tail}`, budget) };
600059
+ if (summary.length > room)
600060
+ summary = `${summary.slice(0, room - 1)}\u2026`;
600061
+ return { icon, text: `${head} ${summary}${tail}` };
600062
+ }
600063
+ function clip(s, width) {
600064
+ return s.length <= width ? s : `${s.slice(0, Math.max(0, width - 1))}\u2026`;
600065
+ }
600066
+ function buildChatItems(messages) {
600067
+ const items = [];
600068
+ let pending = [];
600069
+ let pendingFirst = null;
600070
+ const flush = () => {
600071
+ if (pending.length > 0 && pendingFirst) {
600072
+ items.push({
600073
+ message: { ...pendingFirst, id: `trail-${pendingFirst.id}`, role: "tool", content: "" },
600074
+ trail: pending
600075
+ });
600076
+ }
600077
+ pending = [];
600078
+ pendingFirst = null;
600079
+ };
600080
+ for (const m of messages) {
600081
+ if (m.role === "tool") {
600082
+ if (m.toolTrail) {
600083
+ if (!pendingFirst)
600084
+ pendingFirst = m;
600085
+ pending.push(m.toolTrail);
600086
+ }
600087
+ continue;
600088
+ }
600089
+ if (m.role === "assistant") {
600090
+ items.push({ message: m, trail: pending });
600091
+ pending = [];
600092
+ pendingFirst = null;
600093
+ continue;
600094
+ }
600095
+ if (m.role === "user")
600096
+ flush();
600097
+ items.push({ message: m, trail: [] });
600098
+ }
600099
+ flush();
600100
+ return items;
600101
+ }
600102
+
599646
600103
  // apps/tui/src/lib/activity-rail-derivation.ts
599647
600104
  function deriveTools(messages, isProcessing, limit = 5) {
599648
600105
  const ended = new Map;
@@ -600114,8 +600571,85 @@ function turnResultRows(trail, maxRows, plan) {
600114
600571
  return buildTurnResults(trail, maxRows, plan).length;
600115
600572
  }
600116
600573
 
600574
+ // apps/tui/src/components/ToolTrail.tsx
600575
+ var import_react58 = __toESM(require_react(), 1);
600576
+ var jsx_dev_runtime30 = __toESM(require_jsx_dev_runtime(), 1);
600577
+ function iconColor(status) {
600578
+ return status === "ok" ? t.green : status === "fail" ? t.red : t.orange;
600579
+ }
600580
+ function useLandingRows(total, animate) {
600581
+ const [shown, setShown] = import_react58.useState(total);
600582
+ const shownRef = import_react58.useRef(total);
600583
+ shownRef.current = shown;
600584
+ const landingRef = import_react58.useRef(null);
600585
+ import_react58.useEffect(() => {
600586
+ const from = shownRef.current;
600587
+ if (total <= from + 1 || !motionEnabled(animate)) {
600588
+ landingRef.current = null;
600589
+ setShown(total);
600590
+ return;
600591
+ }
600592
+ landingRef.current = total;
600593
+ const step = staggerFor(total - from);
600594
+ let n = from + 1;
600595
+ setShown(n);
600596
+ const id = setInterval(() => {
600597
+ n += 1;
600598
+ setShown(Math.min(total, n));
600599
+ if (n >= total) {
600600
+ landingRef.current = null;
600601
+ clearInterval(id);
600602
+ }
600603
+ }, step);
600604
+ return () => clearInterval(id);
600605
+ }, [total, animate]);
600606
+ if (landingRef.current === total)
600607
+ return Math.min(shown, total);
600608
+ if (total <= shown + 1)
600609
+ return total;
600610
+ return shown;
600611
+ }
600612
+ function ToolTrail({
600613
+ entries,
600614
+ width,
600615
+ maxRows,
600616
+ animate = true
600617
+ }) {
600618
+ const rows = collapseTrail(entries, maxRows);
600619
+ const visible = useLandingRows(rows.length, animate);
600620
+ if (rows.length === 0)
600621
+ return null;
600622
+ return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
600623
+ flexDirection: "column",
600624
+ flexShrink: 0,
600625
+ children: rows.slice(0, visible).map((row, i) => {
600626
+ const { text } = formatTrailRow(row, width);
600627
+ const g = glyphs();
600628
+ const icon = row.status === "ok" ? g.ok : row.status === "fail" ? g.fail : g.blocked;
600629
+ return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
600630
+ width,
600631
+ children: [
600632
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text, {
600633
+ color: iconColor(row.status),
600634
+ children: icon
600635
+ }, undefined, false, undefined, this),
600636
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text, {
600637
+ color: row.status === "ok" ? t.muted : t.textPrimary,
600638
+ wrap: "truncate-end",
600639
+ children: ` ${text}`
600640
+ }, undefined, false, undefined, this)
600641
+ ]
600642
+ }, i, true, undefined, this);
600643
+ })
600644
+ }, undefined, false, undefined, this);
600645
+ }
600646
+ function toolTrailRows(entries, maxRows) {
600647
+ return entries.length === 0 ? 0 : collapseTrail(entries, maxRows).length;
600648
+ }
600649
+
600117
600650
  // apps/tui/src/components/TurnResults.tsx
600118
- var jsx_dev_runtime32 = __toESM(require_jsx_dev_runtime(), 1);
600651
+ var import_react59 = __toESM(require_react(), 1);
600652
+ var jsx_dev_runtime31 = __toESM(require_jsx_dev_runtime(), 1);
600119
600653
  function iconFor(status) {
600120
600654
  const g = glyphs();
600121
600655
  if (status === "ok")
@@ -600127,8 +600661,8 @@ function iconFor(status) {
600127
600661
  return { icon: g.pending, color: t.muted };
600128
600662
  }
600129
600663
  function useResultLanding(total, land) {
600130
- const [shown, setShown] = import_react60.useState(land ? 0 : total);
600131
- import_react60.useEffect(() => {
600664
+ const [shown, setShown] = import_react59.useState(land ? 0 : total);
600665
+ import_react59.useEffect(() => {
600132
600666
  if (!land) {
600133
600667
  setShown(total);
600134
600668
  return;
@@ -600168,12 +600702,12 @@ function TurnResults({
600168
600702
  const visible = useResultLanding(rows.length, land && motionEnabled(animate));
600169
600703
  if (rows.length === 0)
600170
600704
  return null;
600171
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
600705
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
600172
600706
  flexDirection: "column",
600173
600707
  flexShrink: 0,
600174
600708
  children: rows.map((row, i) => {
600175
600709
  if (i >= visible) {
600176
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
600710
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
600177
600711
  height: 1,
600178
600712
  width
600179
600713
  }, i, false, undefined, this);
@@ -600181,55 +600715,55 @@ function TurnResults({
600181
600715
  const { icon, color } = iconFor(row.status);
600182
600716
  const fit = fitResultRow(row, width);
600183
600717
  const quiet = row.status === "pending" || row.kind === "fold";
600184
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
600718
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
600185
600719
  width,
600186
600720
  height: 1,
600187
- children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600721
+ children: /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600188
600722
  wrap: "truncate-end",
600189
600723
  children: [
600190
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600724
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600191
600725
  color,
600192
600726
  children: icon
600193
600727
  }, undefined, false, undefined, this),
600194
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600728
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600195
600729
  children: " "
600196
600730
  }, undefined, false, undefined, this),
600197
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600731
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600198
600732
  bold: !quiet,
600199
600733
  color: quiet ? t.muted : t.textPrimary,
600200
600734
  children: fit.verb
600201
600735
  }, undefined, false, undefined, this),
600202
- fit.spans ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(jsx_dev_runtime32.Fragment, {
600736
+ fit.spans ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(jsx_dev_runtime31.Fragment, {
600203
600737
  children: [
600204
- fit.verb ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600738
+ fit.verb ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600205
600739
  children: " "
600206
600740
  }, undefined, false, undefined, this) : null,
600207
- fit.spans.map((s, j) => s.code ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600741
+ fit.spans.map((s, j) => s.code ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600208
600742
  color: t.textPrimary,
600209
600743
  backgroundColor: t.border,
600210
600744
  children: s.text
600211
- }, j, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600745
+ }, j, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600212
600746
  color: quiet ? t.muted : t.textSecondary,
600213
600747
  children: s.text
600214
600748
  }, j, false, undefined, this))
600215
600749
  ]
600216
- }, undefined, true, undefined, this) : fit.text ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600750
+ }, undefined, true, undefined, this) : fit.text ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600217
600751
  color: quiet ? t.muted : t.textSecondary,
600218
600752
  children: ` ${fit.text}`
600219
600753
  }, undefined, false, undefined, this) : null,
600220
- fit.chip ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(jsx_dev_runtime32.Fragment, {
600754
+ fit.chip ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(jsx_dev_runtime31.Fragment, {
600221
600755
  children: [
600222
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600756
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600223
600757
  children: " "
600224
600758
  }, undefined, false, undefined, this),
600225
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600759
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600226
600760
  color: t.textPrimary,
600227
600761
  backgroundColor: t.border,
600228
600762
  children: ` ${fit.chip} `
600229
600763
  }, undefined, false, undefined, this)
600230
600764
  ]
600231
600765
  }, undefined, true, undefined, this) : null,
600232
- fit.note ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
600766
+ fit.note ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
600233
600767
  color: row.status === "fail" ? t.red : t.muted,
600234
600768
  children: `${" ".repeat(fit.noteGap ?? 2)}${fit.note}`
600235
600769
  }, undefined, false, undefined, this) : null
@@ -600240,6 +600774,32 @@ function TurnResults({
600240
600774
  }, undefined, false, undefined, this);
600241
600775
  }
600242
600776
 
600777
+ // apps/tui/src/components/fade-transition.tsx
600778
+ var import_react60 = __toESM(require_react(), 1);
600779
+ var jsx_dev_runtime32 = __toESM(require_jsx_dev_runtime(), 1);
600780
+ function FadeIn({ children, duration = 300, delay = 0, onComplete }) {
600781
+ const [started, setStarted] = import_react60.useState(false);
600782
+ import_react60.useEffect(() => {
600783
+ const timeout = setTimeout(() => {
600784
+ setStarted(true);
600785
+ }, delay);
600786
+ return () => clearTimeout(timeout);
600787
+ }, [delay]);
600788
+ import_react60.useEffect(() => {
600789
+ if (!started)
600790
+ return;
600791
+ const timeout = setTimeout(() => {
600792
+ onComplete?.();
600793
+ }, duration);
600794
+ return () => clearTimeout(timeout);
600795
+ }, [started, duration, onComplete]);
600796
+ if (!started)
600797
+ return null;
600798
+ return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
600799
+ children
600800
+ }, undefined, false, undefined, this);
600801
+ }
600802
+
600243
600803
  // apps/tui/src/components/sound-effects.tsx
600244
600804
  var import_react61 = __toESM(require_react(), 1);
600245
600805
  var BELL = "\x07";
@@ -600652,6 +601212,7 @@ function MessageList({
600652
601212
  showAnimations,
600653
601213
  isLatestAssistant: message.id === lastAssistantId,
600654
601214
  trail: trailById.get(message.id) ?? [],
601215
+ turnRunning,
600655
601216
  trailMaxRows: trailCaps.get(message.id),
600656
601217
  onAnimationStart: (id) => {
600657
601218
  animatedIdsRef.current.add(id);
@@ -600683,6 +601244,7 @@ function MessageItem({
600683
601244
  showAnimations,
600684
601245
  isLatestAssistant = false,
600685
601246
  trail = [],
601247
+ turnRunning = false,
600686
601248
  trailMaxRows,
600687
601249
  onAnimationStart
600688
601250
  }) {
@@ -600868,7 +601430,7 @@ function MessageItem({
600868
601430
  animate: showAnimations
600869
601431
  }, undefined, false, undefined, this)
600870
601432
  }, undefined, false, undefined, this),
600871
- !isUser && !message.content.trim() && trail.length > 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(MutedText, {
601433
+ !isUser && !turnRunning && !message.content.trim() && trail.length > 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(MutedText, {
600872
601434
  children: "No reply."
600873
601435
  }, undefined, false, undefined, this) : null,
600874
601436
  message.content.trim() || trail.length === 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
@@ -601030,7 +601592,11 @@ function MarkdownBody({
601030
601592
  })
601031
601593
  }, undefined, false, undefined, this);
601032
601594
  }
601033
- function InlineSpans({ spans, color, adhdMode }) {
601595
+ function InlineSpans({
601596
+ spans,
601597
+ color,
601598
+ adhdMode
601599
+ }) {
601034
601600
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
601035
601601
  color,
601036
601602
  wrap: "wrap",
@@ -603441,9 +604007,9 @@ init_cloud_transcriber();
603441
604007
  init_types5();
603442
604008
 
603443
604009
  // packages/voice/voice-chat.ts
603444
- import { existsSync as existsSync114, unlinkSync as unlinkSync5 } from "fs";
604010
+ import { existsSync as existsSync118, unlinkSync as unlinkSync6 } from "fs";
603445
604011
  import { tmpdir as tmpdir21 } from "os";
603446
- import { join as join172 } from "path";
604012
+ import { join as join176 } from "path";
603447
604013
 
603448
604014
  // packages/voice/tts-engine.ts
603449
604015
  init_termux();
@@ -603851,7 +604417,7 @@ class VoiceChatLoop {
603851
604417
  await this.speakText(response);
603852
604418
  }
603853
604419
  async listenForSpeech() {
603854
- const wavPath = join172(tmpdir21(), `8gent-voicechat-${Date.now()}.wav`);
604420
+ const wavPath = join176(tmpdir21(), `8gent-voicechat-${Date.now()}.wav`);
603855
604421
  const silenceSec = (this.silenceMs / 1000).toFixed(1);
603856
604422
  try {
603857
604423
  const recProc = Bun.spawn([
@@ -603900,7 +604466,7 @@ class VoiceChatLoop {
603900
604466
  cleanupFile(wavPath);
603901
604467
  throw new VoiceChatFatalError(describeRecFailure(exitCode, why));
603902
604468
  }
603903
- if (!existsSync114(wavPath)) {
604469
+ if (!existsSync118(wavPath)) {
603904
604470
  this.reportError("No audio was recorded. Check that your microphone is the system input.");
603905
604471
  return null;
603906
604472
  }
@@ -604024,8 +604590,8 @@ function sleep2(ms) {
604024
604590
  }
604025
604591
  function cleanupFile(path) {
604026
604592
  try {
604027
- if (existsSync114(path))
604028
- unlinkSync5(path);
604593
+ if (existsSync118(path))
604594
+ unlinkSync6(path);
604029
604595
  } catch {}
604030
604596
  }
604031
604597
  var HEARD_ONLY_MARK = "[interrupted]";
@@ -604083,14 +604649,14 @@ function splitIntoSentences(text) {
604083
604649
  return chunks.length ? chunks : [text];
604084
604650
  }
604085
604651
  // packages/voice/full-duplex-provider.ts
604086
- import { execSync as execSync6 } from "child_process";
604087
- import { existsSync as existsSync115 } from "fs";
604088
- import { homedir as homedir87 } from "os";
604089
- import { join as join173 } from "path";
604090
- var MOSHI_HF_CACHE_PATH = join173(homedir87(), ".cache", "huggingface", "hub", "models--kyutai--moshiko-pytorch-bf16", "snapshots");
604652
+ import { execSync as execSync7 } from "child_process";
604653
+ import { existsSync as existsSync119 } from "fs";
604654
+ import { homedir as homedir88 } from "os";
604655
+ import { join as join177 } from "path";
604656
+ var MOSHI_HF_CACHE_PATH = join177(homedir88(), ".cache", "huggingface", "hub", "models--kyutai--moshiko-pytorch-bf16", "snapshots");
604091
604657
  function tryExec(cmd) {
604092
604658
  try {
604093
- execSync6(cmd, { stdio: "ignore", timeout: 5000 });
604659
+ execSync7(cmd, { stdio: "ignore", timeout: 5000 });
604094
604660
  return true;
604095
604661
  } catch {
604096
604662
  return false;
@@ -604102,7 +604668,7 @@ async function detectCapabilities() {
604102
604668
  const hasAppleSilicon = platform === "darwin" && arch === "arm64";
604103
604669
  const hasMLX = hasAppleSilicon && tryExec('python3 -c "import mlx"');
604104
604670
  const hasMoshiInstalled = tryExec('python3 -c "import moshi"');
604105
- const hasMoshiModel = existsSync115(MOSHI_HF_CACHE_PATH);
604671
+ const hasMoshiModel = existsSync119(MOSHI_HF_CACHE_PATH);
604106
604672
  const hasCUDA = tryExec("nvidia-smi");
604107
604673
  const hasNIMKey = !!process.env.NVIDIA_NIM_KEY;
604108
604674
  return {
@@ -605133,9 +605699,9 @@ function useWorkspaceTabs() {
605133
605699
  }
605134
605700
 
605135
605701
  // apps/tui/src/lib/adhd-audio.ts
605136
- import { existsSync as existsSync117, mkdirSync as mkdirSync82 } from "fs";
605137
- import { join as join176 } from "path";
605138
- var {spawn: spawn28 } = globalThis.Bun;
605702
+ import { existsSync as existsSync121, mkdirSync as mkdirSync83 } from "fs";
605703
+ import { join as join180 } from "path";
605704
+ var {spawn: spawn30 } = globalThis.Bun;
605139
605705
  var DEFAULT_CONFIG4 = {
605140
605706
  duration: 180,
605141
605707
  bpm: null,
@@ -605174,11 +605740,11 @@ var SOUNDSCAPES = {
605174
605740
  bpm: 70
605175
605741
  }
605176
605742
  };
605177
- var CACHE_DIR3 = join176(process.env.HOME || "~", ".8gent", "adhd-audio");
605178
- var CONFIG_PATH = join176(CACHE_DIR3, "config.json");
605743
+ var CACHE_DIR3 = join180(process.env.HOME || "~", ".8gent", "adhd-audio");
605744
+ var CONFIG_PATH = join180(CACHE_DIR3, "config.json");
605179
605745
  function loadConfig2() {
605180
605746
  try {
605181
- if (existsSync117(CONFIG_PATH)) {
605747
+ if (existsSync121(CONFIG_PATH)) {
605182
605748
  const raw = JSON.parse(__require("fs").readFileSync(CONFIG_PATH, "utf-8"));
605183
605749
  return { ...DEFAULT_CONFIG4, ...raw };
605184
605750
  }
@@ -605187,8 +605753,8 @@ function loadConfig2() {
605187
605753
  }
605188
605754
  function saveConfig2(config) {
605189
605755
  try {
605190
- if (!existsSync117(CACHE_DIR3))
605191
- mkdirSync82(CACHE_DIR3, { recursive: true });
605756
+ if (!existsSync121(CACHE_DIR3))
605757
+ mkdirSync83(CACHE_DIR3, { recursive: true });
605192
605758
  __require("fs").writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
605193
605759
  } catch {}
605194
605760
  }
@@ -605199,8 +605765,8 @@ class ADHDAudio {
605199
605765
  generating = false;
605200
605766
  _config;
605201
605767
  constructor() {
605202
- if (!existsSync117(CACHE_DIR3)) {
605203
- mkdirSync82(CACHE_DIR3, { recursive: true });
605768
+ if (!existsSync121(CACHE_DIR3)) {
605769
+ mkdirSync83(CACHE_DIR3, { recursive: true });
605204
605770
  }
605205
605771
  this._config = loadConfig2();
605206
605772
  }
@@ -605226,7 +605792,7 @@ class ADHDAudio {
605226
605792
  for (const soundscape of Object.keys(SOUNDSCAPES)) {
605227
605793
  const path = this.getCachePath(soundscape);
605228
605794
  try {
605229
- if (existsSync117(path))
605795
+ if (existsSync121(path))
605230
605796
  __require("fs").unlinkSync(path);
605231
605797
  } catch {}
605232
605798
  }
@@ -605242,10 +605808,10 @@ class ADHDAudio {
605242
605808
  }
605243
605809
  }
605244
605810
  getCachePath(soundscape) {
605245
- return join176(CACHE_DIR3, `${soundscape}.mp3`);
605811
+ return join180(CACHE_DIR3, `${soundscape}.mp3`);
605246
605812
  }
605247
605813
  hasCached(soundscape) {
605248
- return existsSync117(this.getCachePath(soundscape));
605814
+ return existsSync121(this.getCachePath(soundscape));
605249
605815
  }
605250
605816
  async generate(soundscape) {
605251
605817
  const preset = SOUNDSCAPES[soundscape];
@@ -605338,7 +605904,7 @@ class ADHDAudio {
605338
605904
  if (!this._playingFile)
605339
605905
  return;
605340
605906
  try {
605341
- this.playerProcess = spawn28(["afplay", filePath], {
605907
+ this.playerProcess = spawn30(["afplay", filePath], {
605342
605908
  stdout: "ignore",
605343
605909
  stderr: "ignore",
605344
605910
  onExit: () => {
@@ -605654,325 +606220,8 @@ class DaemonSessionClient {
605654
606220
  }
605655
606221
  }
605656
606222
 
605657
- // apps/tui/src/lib/external-agent-runner.ts
605658
- init_shell();
605659
- import { execSync as execSync7, spawn as spawn30 } from "child_process";
605660
- var stripAnsi2 = (s) => s.replace(/\u001B\[[0-9;?]*[a-zA-Z]/g, "").replace(/\u001B\][^\u0007]*\u0007/g, "");
605661
- var EXTERNAL_AGENT_PRESETS = {
605662
- claude: {
605663
- id: "claude",
605664
- label: "Sparring",
605665
- command: "claude",
605666
- promptMode: "flag",
605667
- flagName: "-p",
605668
- args: [],
605669
- timeoutMs: 120000,
605670
- parseStdout: stripAnsi2,
605671
- homepage: "https://docs.claude.com/en/docs/claude-code",
605672
- preferWindow: true,
605673
- install: {
605674
- command: "npm install -g --prefix=$HOME/.npm-global @anthropic-ai/claude-code",
605675
- notes: 'Binary at ~/.npm-global/bin/claude. If `claude` isn\'t on PATH after install, add `export PATH="$HOME/.npm-global/bin:$PATH"` to your shell rc. Then run `claude` once outside the TUI to authenticate with Anthropic.'
605676
- }
605677
- },
605678
- codex: {
605679
- id: "codex",
605680
- label: "OpenAI Codex",
605681
- command: "codex",
605682
- promptMode: "arg",
605683
- args: ["exec"],
605684
- timeoutMs: 120000,
605685
- parseStdout: stripAnsi2,
605686
- homepage: "https://github.com/openai/codex",
605687
- preferWindow: true,
605688
- install: {
605689
- command: "npm install -g --prefix=$HOME/.npm-global @openai/codex",
605690
- notes: "Binary at ~/.npm-global/bin/codex. Add `~/.npm-global/bin` to PATH if needed. Requires `export OPENAI_API_KEY=sk-...` before /spawn."
605691
- }
605692
- },
605693
- hermes: {
605694
- id: "hermes",
605695
- label: "Hermes Agent",
605696
- command: "hermes",
605697
- preferWindow: true,
605698
- promptMode: "flag",
605699
- flagName: "-z",
605700
- args: [],
605701
- timeoutMs: 120000,
605702
- parseStdout: stripAnsi2,
605703
- homepage: "https://github.com/NousResearch/hermes-agent",
605704
- install: {
605705
- command: "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup --no-venv",
605706
- notes: "Hermes needs Python \u2265 3.11 (anaconda's default 3.10 won't work; use pyenv or brew install python@3.11). Installer symlinks `hermes` into ~/.local/bin; add it to PATH if needed. After install, run `hermes setup` or `hermes model` outside the TUI to configure a provider. Without that, /spawn'd Hermes tabs will fail with auth errors (Bedrock is the default provider)."
605707
- }
605708
- },
605709
- openclaw: {
605710
- id: "openclaw",
605711
- label: "OpenClaw",
605712
- command: "openclaw",
605713
- preferWindow: true,
605714
- promptMode: "arg",
605715
- args: ["run", "--headless"],
605716
- timeoutMs: 120000,
605717
- parseStdout: stripAnsi2,
605718
- homepage: "https://github.com/openclaw/openclaw",
605719
- install: {
605720
- command: "npm install -g --prefix=$HOME/.npm-global openclaw@latest",
605721
- notes: "Binary at ~/.npm-global/bin/openclaw. Add `~/.npm-global/bin` to PATH if needed. After install run `openclaw onboard` outside the TUI to set up the gateway, workspace, and skills. (8gent now provides a managed Node 22 runtime automatically when needed; no separate nvm step required.)"
605722
- },
605723
- requiresNodeVersion: "22.14.0"
605724
- },
605725
- pi: {
605726
- preferWindow: true,
605727
- id: "pi",
605728
- label: "Pi (pi-mono)",
605729
- command: "pi",
605730
- promptMode: "flag",
605731
- flagName: "-p",
605732
- args: [],
605733
- timeoutMs: 120000,
605734
- parseStdout: stripAnsi2,
605735
- homepage: "https://pi.dev",
605736
- install: {
605737
- command: "npm install -g --prefix=$HOME/.npm-global @mariozechner/pi-coding-agent",
605738
- notes: "Binary at ~/.npm-global/bin/pi. Add `~/.npm-global/bin` to PATH if needed. Default provider is Google (Gemini). Set GEMINI_API_KEY, or override with --provider/--model and the matching API key env var (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY)."
605739
- }
605740
- },
605741
- "8gent": {
605742
- id: "8gent",
605743
- label: "8gent (nested)",
605744
- command: "8gent",
605745
- promptMode: "arg",
605746
- args: ["run"],
605747
- timeoutMs: 180000,
605748
- parseStdout: stripAnsi2,
605749
- homepage: "https://8gent.dev",
605750
- install: {
605751
- command: "npm install -g --prefix=$HOME/.npm-global @8gi-foundation/8gent-code --force",
605752
- notes: "Binary at ~/.npm-global/bin/8gent. --force overwrites stale `8` / `8gent` / `8gent-code` bin symlinks (npm 9+ EEXIST). Add `~/.npm-global/bin` to PATH if not already."
605753
- }
605754
- }
605755
- };
605756
- function getPreset(id) {
605757
- return EXTERNAL_AGENT_PRESETS[id.toLowerCase()] ?? null;
605758
- }
605759
- function listPresetIds() {
605760
- return Object.keys(EXTERNAL_AGENT_PRESETS);
605761
- }
605762
- function isInstalled(preset) {
605763
- try {
605764
- execSync7(`command -v "${preset.command}"`, {
605765
- stdio: "ignore",
605766
- timeout: 1500
605767
- });
605768
- return true;
605769
- } catch {
605770
- return false;
605771
- }
605772
- }
605773
- async function installAgent(preset, onLine) {
605774
- const start = performance.now();
605775
- if (!preset.install) {
605776
- return {
605777
- ok: false,
605778
- stdout: "",
605779
- stderr: "",
605780
- durationMs: 0,
605781
- command: "",
605782
- error: `No install recipe for ${preset.id}. Install ${preset.command} manually and try again.`
605783
- };
605784
- }
605785
- const command = preset.install.command;
605786
- return new Promise((resolve) => {
605787
- let stdout = "";
605788
- let stderr = "";
605789
- let exited = false;
605790
- const proc = spawnShell(command, {
605791
- posix: ["bash", "-lc"],
605792
- stdio: ["ignore", "pipe", "pipe"],
605793
- env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" }
605794
- });
605795
- const finish = (result) => {
605796
- if (exited)
605797
- return;
605798
- exited = true;
605799
- clearTimeout(timer);
605800
- resolve({
605801
- ...result,
605802
- durationMs: Math.round(performance.now() - start),
605803
- command
605804
- });
605805
- };
605806
- const pipeLines = (chunk, source) => {
605807
- const text = chunk.toString();
605808
- if (source === "stdout")
605809
- stdout += text;
605810
- else
605811
- stderr += text;
605812
- if (onLine) {
605813
- for (const line of text.split(/\r?\n/)) {
605814
- if (line.trim().length > 0)
605815
- onLine(line, source);
605816
- }
605817
- }
605818
- };
605819
- proc.stdout?.on("data", (c) => pipeLines(c, "stdout"));
605820
- proc.stderr?.on("data", (c) => pipeLines(c, "stderr"));
605821
- proc.on("error", (err) => {
605822
- finish({ ok: false, stdout, stderr, error: err.message });
605823
- });
605824
- proc.on("close", (code) => {
605825
- finish({
605826
- ok: code === 0,
605827
- stdout,
605828
- stderr,
605829
- error: code === 0 ? undefined : stderr.trim().slice(0, 800) || `installer exited with code ${code}`
605830
- });
605831
- });
605832
- const timer = setTimeout(() => {
605833
- proc.kill("SIGTERM");
605834
- finish({ ok: false, stdout, stderr, error: "install timed out after 5 minutes" });
605835
- }, 5 * 60 * 1000);
605836
- });
605837
- }
605838
- async function ensureInstalled(preset, onLine) {
605839
- if (isInstalled(preset))
605840
- return true;
605841
- if (!preset.install) {
605842
- onLine?.(`${preset.command}: not installed and no auto-install recipe is configured.`, "info");
605843
- return false;
605844
- }
605845
- onLine?.(`${preset.command}: not installed, running ${preset.install.command}`, "info");
605846
- const result = await installAgent(preset, (line, source) => onLine?.(line, source));
605847
- if (!result.ok) {
605848
- onLine?.(`Install failed: ${result.error ?? "unknown error"}`, "info");
605849
- return false;
605850
- }
605851
- const stillThere = isInstalled(preset);
605852
- if (!stillThere) {
605853
- onLine?.(`Install reported success but ${preset.command} is still not on $PATH. You may need to open a new shell or add the install dir to PATH.`, "info");
605854
- }
605855
- return stillThere;
605856
- }
605857
- function composePrompt(history, prompt) {
605858
- const lines = [];
605859
- for (const m of history) {
605860
- const tag = m.role === "user" ? "User" : m.role === "assistant" ? "Assistant" : m.role;
605861
- lines.push(`${tag}: ${m.content}`);
605862
- }
605863
- lines.push(`User: ${prompt}`);
605864
- lines.push("Assistant:");
605865
- return lines.join(`
605866
-
605867
- `);
605868
- }
605869
- async function runExternalAgent(preset, prompt, signal) {
605870
- const start = performance.now();
605871
- const argv = [...preset.args];
605872
- if (preset.promptMode === "flag") {
605873
- argv.push(preset.flagName ?? "-p", prompt);
605874
- } else if (preset.promptMode === "arg") {
605875
- argv.push(prompt);
605876
- }
605877
- let pathPrefix = "";
605878
- if (preset.requiresNodeVersion) {
605879
- try {
605880
- await Promise.resolve().then(() => init_runtime());
605881
- const binDir = await ensureNodeFor(preset.requiresNodeVersion);
605882
- if (binDir)
605883
- pathPrefix = `${binDir}:`;
605884
- } catch (err) {
605885
- return {
605886
- ok: false,
605887
- text: "",
605888
- durationMs: Math.round(performance.now() - start),
605889
- command: `${preset.command} ${argv.join(" ")}`,
605890
- error: `managed Node runtime install failed: ${err instanceof Error ? err.message : String(err)}`,
605891
- exitCode: null
605892
- };
605893
- }
605894
- }
605895
- return new Promise((resolve) => {
605896
- let stdout = "";
605897
- let stderr = "";
605898
- let exited = false;
605899
- const proc = spawn30(preset.command, argv, {
605900
- stdio: ["pipe", "pipe", "pipe"],
605901
- env: {
605902
- ...process.env,
605903
- PATH: `${pathPrefix}${process.env.PATH ?? ""}`,
605904
- NO_COLOR: "1",
605905
- FORCE_COLOR: "0",
605906
- "8GENT_NO_INTRO": "1",
605907
- AI_SDK_LOG_WARNINGS: "false"
605908
- }
605909
- });
605910
- const finish = (result) => {
605911
- if (exited)
605912
- return;
605913
- exited = true;
605914
- clearTimeout(timer);
605915
- signal?.removeEventListener("abort", onAbort);
605916
- resolve({
605917
- ...result,
605918
- durationMs: Math.round(performance.now() - start),
605919
- command: `${preset.command} ${argv.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`
605920
- });
605921
- };
605922
- proc.on("error", (err) => {
605923
- finish({
605924
- ok: false,
605925
- text: "",
605926
- error: err.code === "ENOENT" ? `${preset.command}: not installed (no binary on $PATH)` : err.message,
605927
- exitCode: null
605928
- });
605929
- });
605930
- proc.stdout?.on("data", (chunk) => {
605931
- stdout += chunk.toString();
605932
- });
605933
- proc.stderr?.on("data", (chunk) => {
605934
- stderr += chunk.toString();
605935
- });
605936
- proc.on("close", (code) => {
605937
- let text = preset.parseStdout ? preset.parseStdout(stdout).trim() : stdout.trim();
605938
- try {
605939
- init_keys();
605940
- if (text)
605941
- text = redactAndWarn(text);
605942
- } catch {}
605943
- if (code === 0 || code !== null && text.length > 0) {
605944
- finish({ ok: code === 0, text, exitCode: code, error: code === 0 ? undefined : stderr.trim().slice(0, 400) });
605945
- } else {
605946
- finish({
605947
- ok: false,
605948
- text,
605949
- exitCode: code,
605950
- error: stderr.trim().slice(0, 400) || `exited with code ${code}`
605951
- });
605952
- }
605953
- });
605954
- if (preset.promptMode === "stdin" && proc.stdin) {
605955
- proc.stdin.write(prompt);
605956
- proc.stdin.end();
605957
- } else if (proc.stdin) {
605958
- proc.stdin.end();
605959
- }
605960
- const timer = setTimeout(() => {
605961
- proc.kill("SIGTERM");
605962
- finish({
605963
- ok: false,
605964
- text: stdout,
605965
- error: `timeout after ${preset.timeoutMs ?? 90000}ms`,
605966
- exitCode: null
605967
- });
605968
- }, preset.timeoutMs ?? 90000);
605969
- const onAbort = () => {
605970
- proc.kill("SIGTERM");
605971
- finish({ ok: false, text: stdout, error: "aborted", exitCode: null });
605972
- };
605973
- signal?.addEventListener("abort", onAbort, { once: true });
605974
- });
605975
- }
606223
+ // apps/tui/src/app.tsx
606224
+ init_external_agent_runner();
605976
606225
 
605977
606226
  // packages/daemon/goal-rpc.ts
605978
606227
  import { randomUUID as randomUUID7 } from "crypto";
@@ -607255,7 +607504,7 @@ class InProcessGoalTransport {
607255
607504
  // apps/tui/src/lib/intro-gate.ts
607256
607505
  init_home();
607257
607506
  import { mkdirSync as mkdirSync85, readFileSync as readFileSync108, writeFileSync as writeFileSync65 } from "fs";
607258
- import { join as join183 } from "path";
607507
+ import { join as join184 } from "path";
607259
607508
  function shouldShowIntro({ setting, env, seenVersion, version }) {
607260
607509
  if (setting === "off")
607261
607510
  return false;
@@ -607266,7 +607515,7 @@ function shouldShowIntro({ setting, env, seenVersion, version }) {
607266
607515
  return seenVersion !== version;
607267
607516
  }
607268
607517
  function seenPath(home) {
607269
- return join183(home, ".8gent", "intro-seen");
607518
+ return join184(home, ".8gent", "intro-seen");
607270
607519
  }
607271
607520
  function readSeenVersion(home = resolveHome()) {
607272
607521
  try {
@@ -607278,7 +607527,7 @@ function readSeenVersion(home = resolveHome()) {
607278
607527
  }
607279
607528
  function markIntroSeen(version, home = resolveHome()) {
607280
607529
  try {
607281
- mkdirSync85(join183(home, ".8gent"), { recursive: true });
607530
+ mkdirSync85(join184(home, ".8gent"), { recursive: true });
607282
607531
  writeFileSync65(seenPath(home), `${version}
607283
607532
  `);
607284
607533
  } catch {}
@@ -608971,12 +609220,12 @@ function sanitizeFreeText(value) {
608971
609220
  // packages/voice/audio-gate.ts
608972
609221
  init_home();
608973
609222
  import { readFileSync as readFileSync110 } from "fs";
608974
- import { join as join186 } from "path";
609223
+ import { join as join187 } from "path";
608975
609224
  function audioOutputAllowed(env = process.env) {
608976
609225
  if (env.EIGHT_VOICE === "1")
608977
609226
  return true;
608978
609227
  try {
608979
- const raw = JSON.parse(readFileSync110(join186(resolveHome(env), ".8gent", "settings.json"), "utf8"));
609228
+ const raw = JSON.parse(readFileSync110(join187(resolveHome(env), ".8gent", "settings.json"), "utf8"));
608980
609229
  return raw?.voice?.outputEnabled === true;
608981
609230
  } catch {
608982
609231
  return false;
@@ -610150,10 +610399,10 @@ function snapshot(manager) {
610150
610399
 
610151
610400
  // apps/tui/src/hooks/useBodyParts.ts
610152
610401
  var import_react83 = __toESM(require_react(), 1);
610153
- import { existsSync as existsSync122 } from "fs";
610402
+ import { existsSync as existsSync124 } from "fs";
610154
610403
  import { execSync as execSync8 } from "child_process";
610155
610404
  import { homedir as homedir90 } from "os";
610156
- import { join as join187 } from "path";
610405
+ import { join as join188 } from "path";
610157
610406
  var PULSE_FLASH_MS = 150;
610158
610407
  function hasCliclick() {
610159
610408
  try {
@@ -610164,7 +610413,7 @@ function hasCliclick() {
610164
610413
  }
610165
610414
  }
610166
610415
  function hasAxBridge() {
610167
- return existsSync122(join187(homedir90(), ".8gent", "bin", "8gent-ax-bridge"));
610416
+ return existsSync124(join188(homedir90(), ".8gent", "bin", "8gent-ax-bridge"));
610168
610417
  }
610169
610418
  function detectDefaultBodyPartsState() {
610170
610419
  const handsOk = hasCliclick();
@@ -610452,6 +610701,7 @@ function onChange(fn) {
610452
610701
  init_shell();
610453
610702
  init_secret_scanner();
610454
610703
  init_path_guard();
610704
+ init_permission_mode();
610455
610705
  init_eight_browser();
610456
610706
 
610457
610707
  // apps/tui/src/lib/consult-command.ts
@@ -610492,8 +610742,16 @@ function sendRefusal(command) {
610492
610742
  return null;
610493
610743
  }
610494
610744
  function planModeRefusal2(mode) {
610495
- return mode === "plan" ? "Plan mode runs nothing, shell lines included. Shift+Tab to change mode, then run it again." : null;
610745
+ return mode === "plan" ? "Plan mode runs nothing, shell lines included. Type /mode ask (or press Shift+Tab) to change mode, then run it again." : null;
610746
+ }
610747
+ function parseModeArgs(args) {
610748
+ const words = args.flatMap((a) => a.trim().split(/\s+/)).filter(Boolean);
610749
+ if (words.length !== 1)
610750
+ return null;
610751
+ const name = words[0].toLowerCase();
610752
+ return isPermissionMode(name) ? name : null;
610496
610753
  }
610754
+ var MODE_USAGE = "Usage: /mode plan | ask | guarded | infinite (this tab's permission mode).";
610497
610755
  var ANSI2 = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[ -/]*[0-~]/g;
610498
610756
  var CONTROL4 = /[\x00-\x08\x0b-\x1f\x7f]/g;
610499
610757
  function cleanOutput(raw) {
@@ -611752,6 +612010,24 @@ ${r.message}`;
611752
612010
  // apps/tui/src/app.tsx
611753
612011
  init_slash_commands();
611754
612012
 
612013
+ // apps/tui/src/lib/term-intent.ts
612014
+ var VERBS2 = "run|open|start|launch|fire up|spin up|boot";
612015
+ var POLITE = "(?:please\\s+|can you\\s+|could you\\s+)?";
612016
+ var WHERE = "(?:\\s+in\\s+(?:a\\s+)?(?:new\\s+)?(?:tab|terminal(?:\\s+tab)?))?";
612017
+ var PRESET_INTENT = new RegExp(`^${POLITE}(?:${VERBS2})\\s+(?:up\\s+)?([a-z0-9][a-z0-9_-]*)(${WHERE})(?:\\s+please)?$`);
612018
+ function detectTermPresetIntent(input, presetIds) {
612019
+ const text = input.trim().toLowerCase().replace(/[.!?]+$/, "").replace(/\s+/g, " ");
612020
+ if (!text || text.length > 60)
612021
+ return null;
612022
+ const m = PRESET_INTENT.exec(text);
612023
+ if (!m)
612024
+ return null;
612025
+ const id = presetIds.find((p) => p.toLowerCase() === m[1]);
612026
+ if (!id)
612027
+ return null;
612028
+ return m[2] ? [id, "--in-tab"] : [id];
612029
+ }
612030
+
611755
612031
  // apps/tui/src/lib/turn-outcome.ts
611756
612032
  init_local_turn_error();
611757
612033
  function turnEndedInError(messages) {
@@ -611769,8 +612045,8 @@ function turnEndedInError(messages) {
611769
612045
  }
611770
612046
 
611771
612047
  // apps/tui/src/screens/BTWView.tsx
611772
- import { existsSync as existsSync124, mkdirSync as mkdirSync89, readFileSync as readFileSync112, writeFileSync as writeFileSync69 } from "fs";
611773
- import { join as join189 } from "path";
612048
+ import { existsSync as existsSync126, mkdirSync as mkdirSync89, readFileSync as readFileSync112, writeFileSync as writeFileSync69 } from "fs";
612049
+ import { join as join190 } from "path";
611774
612050
  var import_react85 = __toESM(require_react(), 1);
611775
612051
 
611776
612052
  // apps/tui/src/components/primitives/Divider.tsx
@@ -611782,13 +612058,13 @@ var Divider = ({ width = 60, character = "\u2500" }) => /* @__PURE__ */ jsx_dev_
611782
612058
 
611783
612059
  // apps/tui/src/screens/BTWView.tsx
611784
612060
  var jsx_dev_runtime50 = __toESM(require_jsx_dev_runtime(), 1);
611785
- var DATA_DIR5 = join189(process.env.HOME || "~", ".8gent", "tabs");
612061
+ var DATA_DIR5 = join190(process.env.HOME || "~", ".8gent", "tabs");
611786
612062
  function loadBTW() {
611787
612063
  try {
611788
- if (!existsSync124(DATA_DIR5))
612064
+ if (!existsSync126(DATA_DIR5))
611789
612065
  mkdirSync89(DATA_DIR5, { recursive: true });
611790
- const filepath = join189(DATA_DIR5, "btw.json");
611791
- if (existsSync124(filepath)) {
612066
+ const filepath = join190(DATA_DIR5, "btw.json");
612067
+ if (existsSync126(filepath)) {
611792
612068
  const raw = JSON.parse(readFileSync112(filepath, "utf-8"));
611793
612069
  return raw.items || [];
611794
612070
  }
@@ -611797,9 +612073,9 @@ function loadBTW() {
611797
612073
  }
611798
612074
  function saveBTW(items) {
611799
612075
  try {
611800
- if (!existsSync124(DATA_DIR5))
612076
+ if (!existsSync126(DATA_DIR5))
611801
612077
  mkdirSync89(DATA_DIR5, { recursive: true });
611802
- writeFileSync69(join189(DATA_DIR5, "btw.json"), JSON.stringify({ items }, null, 2));
612078
+ writeFileSync69(join190(DATA_DIR5, "btw.json"), JSON.stringify({ items }, null, 2));
611803
612079
  } catch {}
611804
612080
  }
611805
612081
  function sortItems(items) {
@@ -612132,17 +612408,17 @@ function HistoryScreen({ conversations, onSelect, onBack }) {
612132
612408
  }
612133
612409
 
612134
612410
  // apps/tui/src/screens/IdeasView.tsx
612135
- import { existsSync as existsSync125, mkdirSync as mkdirSync90, readFileSync as readFileSync113, writeFileSync as writeFileSync70 } from "fs";
612136
- import { join as join190 } from "path";
612411
+ import { existsSync as existsSync127, mkdirSync as mkdirSync90, readFileSync as readFileSync113, writeFileSync as writeFileSync70 } from "fs";
612412
+ import { join as join191 } from "path";
612137
612413
  var import_react87 = __toESM(require_react(), 1);
612138
612414
  var jsx_dev_runtime52 = __toESM(require_jsx_dev_runtime(), 1);
612139
- var DATA_DIR6 = join190(process.env.HOME || "~", ".8gent", "tabs");
612415
+ var DATA_DIR6 = join191(process.env.HOME || "~", ".8gent", "tabs");
612140
612416
  function loadIdeas() {
612141
612417
  try {
612142
- if (!existsSync125(DATA_DIR6))
612418
+ if (!existsSync127(DATA_DIR6))
612143
612419
  mkdirSync90(DATA_DIR6, { recursive: true });
612144
- const filepath = join190(DATA_DIR6, "ideas.json");
612145
- if (existsSync125(filepath)) {
612420
+ const filepath = join191(DATA_DIR6, "ideas.json");
612421
+ if (existsSync127(filepath)) {
612146
612422
  const raw = JSON.parse(readFileSync113(filepath, "utf-8"));
612147
612423
  return raw.ideas || [];
612148
612424
  }
@@ -612151,9 +612427,9 @@ function loadIdeas() {
612151
612427
  }
612152
612428
  function saveIdeas(ideas) {
612153
612429
  try {
612154
- if (!existsSync125(DATA_DIR6))
612430
+ if (!existsSync127(DATA_DIR6))
612155
612431
  mkdirSync90(DATA_DIR6, { recursive: true });
612156
- writeFileSync70(join190(DATA_DIR6, "ideas.json"), JSON.stringify({ ideas }, null, 2));
612432
+ writeFileSync70(join191(DATA_DIR6, "ideas.json"), JSON.stringify({ ideas }, null, 2));
612157
612433
  } catch {}
612158
612434
  }
612159
612435
  function extractTags2(text) {
@@ -612614,11 +612890,11 @@ function KeyVaultScreen({
612614
612890
  }
612615
612891
 
612616
612892
  // apps/tui/src/screens/MusicPlayerView.tsx
612617
- import { existsSync as existsSync126, readdirSync as readdirSync28, statSync as statSync38 } from "fs";
612618
- import { basename as basename53, join as join191 } from "path";
612893
+ import { existsSync as existsSync128, readdirSync as readdirSync28, statSync as statSync38 } from "fs";
612894
+ import { basename as basename54, join as join192 } from "path";
612619
612895
  var import_react89 = __toESM(require_react(), 1);
612620
612896
  var jsx_dev_runtime54 = __toESM(require_jsx_dev_runtime(), 1);
612621
- var CACHE_DIR4 = join191(process.env.HOME || "~", ".8gent", "adhd-audio");
612897
+ var CACHE_DIR4 = join192(process.env.HOME || "~", ".8gent", "adhd-audio");
612622
612898
  function formatBytes(bytes) {
612623
612899
  if (bytes < 1024)
612624
612900
  return `${bytes}B`;
@@ -612636,20 +612912,20 @@ function formatDate(date) {
612636
612912
  }
612637
612913
  function loadTracks(currentTrack) {
612638
612914
  const tracks = [];
612639
- if (!existsSync126(CACHE_DIR4))
612915
+ if (!existsSync128(CACHE_DIR4))
612640
612916
  return tracks;
612641
612917
  try {
612642
612918
  const files = readdirSync28(CACHE_DIR4).filter((f) => f.endsWith(".mp3") || f.endsWith(".wav"));
612643
612919
  for (const file of files) {
612644
- const filePath = join191(CACHE_DIR4, file);
612920
+ const filePath = join192(CACHE_DIR4, file);
612645
612921
  const stat = statSync38(filePath);
612646
- const name = basename53(file, ".mp3").replace(/-/g, " ");
612922
+ const name = basename54(file, ".mp3").replace(/-/g, " ");
612647
612923
  tracks.push({
612648
612924
  name: name.charAt(0).toUpperCase() + name.slice(1),
612649
612925
  path: filePath,
612650
612926
  size: formatBytes(stat.size),
612651
612927
  modified: formatDate(stat.mtime),
612652
- playing: currentTrack === name.toLowerCase() || currentTrack === basename53(file, ".mp3")
612928
+ playing: currentTrack === name.toLowerCase() || currentTrack === basename54(file, ".mp3")
612653
612929
  });
612654
612930
  }
612655
612931
  } catch {}
@@ -613145,18 +613421,18 @@ function NarratorView({ tasks, narratorText, maxHeight }) {
613145
613421
  }
613146
613422
 
613147
613423
  // apps/tui/src/screens/NotesView.tsx
613148
- import { existsSync as existsSync127, mkdirSync as mkdirSync91, readFileSync as readFileSync114, writeFileSync as writeFileSync71 } from "fs";
613149
- import { join as join192 } from "path";
613424
+ import { existsSync as existsSync129, mkdirSync as mkdirSync91, readFileSync as readFileSync114, writeFileSync as writeFileSync71 } from "fs";
613425
+ import { join as join193 } from "path";
613150
613426
  var import_react91 = __toESM(require_react(), 1);
613151
613427
  var jsx_dev_runtime59 = __toESM(require_jsx_dev_runtime(), 1);
613152
- var DATA_DIR7 = join192(process.env.HOME || "~", ".8gent", "tabs");
613428
+ var DATA_DIR7 = join193(process.env.HOME || "~", ".8gent", "tabs");
613153
613429
  var EMPTY_TAB_NAMES = [];
613154
613430
  function loadNotes() {
613155
613431
  try {
613156
- if (!existsSync127(DATA_DIR7))
613432
+ if (!existsSync129(DATA_DIR7))
613157
613433
  mkdirSync91(DATA_DIR7, { recursive: true });
613158
- const filepath = join192(DATA_DIR7, "notes.json");
613159
- if (existsSync127(filepath)) {
613434
+ const filepath = join193(DATA_DIR7, "notes.json");
613435
+ if (existsSync129(filepath)) {
613160
613436
  const raw = JSON.parse(readFileSync114(filepath, "utf-8"));
613161
613437
  return raw.notes || [];
613162
613438
  }
@@ -613165,9 +613441,9 @@ function loadNotes() {
613165
613441
  }
613166
613442
  function saveNotes(notes) {
613167
613443
  try {
613168
- if (!existsSync127(DATA_DIR7))
613444
+ if (!existsSync129(DATA_DIR7))
613169
613445
  mkdirSync91(DATA_DIR7, { recursive: true });
613170
- writeFileSync71(join192(DATA_DIR7, "notes.json"), JSON.stringify({ notes }, null, 2));
613446
+ writeFileSync71(join193(DATA_DIR7, "notes.json"), JSON.stringify({ notes }, null, 2));
613171
613447
  } catch {}
613172
613448
  }
613173
613449
  function NotesView({
@@ -614360,40 +614636,40 @@ function OnboardingScreen({
614360
614636
 
614361
614637
  // apps/tui/src/screens/ProjectsView.tsx
614362
614638
  import { execSync as execSync9 } from "child_process";
614363
- import { existsSync as existsSync128, mkdirSync as mkdirSync92, readFileSync as readFileSync115, readdirSync as readdirSync29, statSync as statSync39, writeFileSync as writeFileSync72 } from "fs";
614639
+ import { existsSync as existsSync130, mkdirSync as mkdirSync92, readFileSync as readFileSync115, readdirSync as readdirSync29, statSync as statSync39, writeFileSync as writeFileSync72 } from "fs";
614364
614640
  import { homedir as homedir91 } from "os";
614365
- import { basename as basename54, join as join193 } from "path";
614641
+ import { basename as basename55, join as join194 } from "path";
614366
614642
  var import_react96 = __toESM(require_react(), 1);
614367
614643
  var jsx_dev_runtime61 = __toESM(require_jsx_dev_runtime(), 1);
614368
- var SAVED_PROJECTS_PATH = join193(homedir91(), ".8gent", "tabs", "projects.json");
614644
+ var SAVED_PROJECTS_PATH = join194(homedir91(), ".8gent", "tabs", "projects.json");
614369
614645
  var SCAN_DIRS = [
614370
614646
  homedir91(),
614371
- join193(homedir91(), "Documents"),
614372
- join193(homedir91(), "Projects"),
614373
- join193(homedir91(), "Code")
614647
+ join194(homedir91(), "Documents"),
614648
+ join194(homedir91(), "Projects"),
614649
+ join194(homedir91(), "Code")
614374
614650
  ];
614375
614651
  var PROJECT_MARKERS = ["package.json", ".git", "CLAUDE.md", ".8gent"];
614376
614652
  function isProjectDir(dirPath) {
614377
614653
  try {
614378
- return PROJECT_MARKERS.some((marker) => existsSync128(join193(dirPath, marker)));
614654
+ return PROJECT_MARKERS.some((marker) => existsSync130(join194(dirPath, marker)));
614379
614655
  } catch {
614380
614656
  return false;
614381
614657
  }
614382
614658
  }
614383
614659
  function getProjectName(dirPath) {
614384
614660
  try {
614385
- const pkgPath = join193(dirPath, "package.json");
614386
- if (existsSync128(pkgPath)) {
614661
+ const pkgPath = join194(dirPath, "package.json");
614662
+ if (existsSync130(pkgPath)) {
614387
614663
  const pkg = JSON.parse(readFileSync115(pkgPath, "utf8"));
614388
614664
  if (pkg.name)
614389
614665
  return pkg.name;
614390
614666
  }
614391
614667
  } catch {}
614392
- return basename54(dirPath);
614668
+ return basename55(dirPath);
614393
614669
  }
614394
614670
  function getGitBranch2(dirPath) {
614395
614671
  try {
614396
- if (!existsSync128(join193(dirPath, ".git")))
614672
+ if (!existsSync130(join194(dirPath, ".git")))
614397
614673
  return;
614398
614674
  const result = execSync9(`git -C "${dirPath}" branch --show-current 2>/dev/null`, {
614399
614675
  encoding: "utf8",
@@ -614413,7 +614689,7 @@ function getLastModified(dirPath) {
614413
614689
  }
614414
614690
  }
614415
614691
  function has8gent(dirPath) {
614416
- return existsSync128(join193(dirPath, ".8gent"));
614692
+ return existsSync130(join194(dirPath, ".8gent"));
614417
614693
  }
614418
614694
  function buildProjectEntry(dirPath) {
614419
614695
  return {
@@ -614430,7 +614706,7 @@ function scanForProjects(currentPath) {
614430
614706
  found.add(currentPath);
614431
614707
  results.push(buildProjectEntry(currentPath));
614432
614708
  for (const scanDir of SCAN_DIRS) {
614433
- if (!existsSync128(scanDir))
614709
+ if (!existsSync130(scanDir))
614434
614710
  continue;
614435
614711
  try {
614436
614712
  const entries = readdirSync29(scanDir, { withFileTypes: true });
@@ -614439,7 +614715,7 @@ function scanForProjects(currentPath) {
614439
614715
  continue;
614440
614716
  if (entry.name.startsWith("."))
614441
614717
  continue;
614442
- const fullPath = join193(scanDir, entry.name);
614718
+ const fullPath = join194(scanDir, entry.name);
614443
614719
  if (found.has(fullPath))
614444
614720
  continue;
614445
614721
  if (isProjectDir(fullPath)) {
@@ -614462,7 +614738,7 @@ function scanForProjects(currentPath) {
614462
614738
  }
614463
614739
  function loadSavedProjects() {
614464
614740
  try {
614465
- if (!existsSync128(SAVED_PROJECTS_PATH))
614741
+ if (!existsSync130(SAVED_PROJECTS_PATH))
614466
614742
  return [];
614467
614743
  const raw = readFileSync115(SAVED_PROJECTS_PATH, "utf8");
614468
614744
  const data = JSON.parse(raw);
@@ -614473,7 +614749,7 @@ function loadSavedProjects() {
614473
614749
  }
614474
614750
  function saveSavedProjects(projects) {
614475
614751
  try {
614476
- const dir = join193(homedir91(), ".8gent", "tabs");
614752
+ const dir = join194(homedir91(), ".8gent", "tabs");
614477
614753
  mkdirSync92(dir, { recursive: true });
614478
614754
  const data = { projects };
614479
614755
  writeFileSync72(SAVED_PROJECTS_PATH, JSON.stringify(data, null, 2), "utf8");
@@ -614485,7 +614761,7 @@ function mergeProjects(scanned, saved) {
614485
614761
  for (const s of saved) {
614486
614762
  if (paths.has(s.path))
614487
614763
  continue;
614488
- if (!existsSync128(s.path))
614764
+ if (!existsSync130(s.path))
614489
614765
  continue;
614490
614766
  paths.add(s.path);
614491
614767
  extra.push(buildProjectEntry(s.path));
@@ -614553,7 +614829,7 @@ function ProjectsView({
614553
614829
  if (addMode) {
614554
614830
  if (key.return) {
614555
614831
  const trimmed = addInput.trim();
614556
- if (trimmed && existsSync128(trimmed)) {
614832
+ if (trimmed && existsSync130(trimmed)) {
614557
614833
  const newEntry = {
614558
614834
  ...buildProjectEntry(trimmed),
614559
614835
  addedAt: new Date().toISOString()
@@ -614740,17 +615016,17 @@ function ProjectsView({
614740
615016
  }
614741
615017
 
614742
615018
  // apps/tui/src/screens/QuestionsView.tsx
614743
- import { existsSync as existsSync129, mkdirSync as mkdirSync93, readFileSync as readFileSync116, writeFileSync as writeFileSync73 } from "fs";
614744
- import { join as join194 } from "path";
615019
+ import { existsSync as existsSync131, mkdirSync as mkdirSync93, readFileSync as readFileSync116, writeFileSync as writeFileSync73 } from "fs";
615020
+ import { join as join195 } from "path";
614745
615021
  var import_react97 = __toESM(require_react(), 1);
614746
615022
  var jsx_dev_runtime62 = __toESM(require_jsx_dev_runtime(), 1);
614747
- var DATA_DIR8 = join194(process.env.HOME || "~", ".8gent", "tabs");
615023
+ var DATA_DIR8 = join195(process.env.HOME || "~", ".8gent", "tabs");
614748
615024
  function loadQuestions() {
614749
615025
  try {
614750
- if (!existsSync129(DATA_DIR8))
615026
+ if (!existsSync131(DATA_DIR8))
614751
615027
  mkdirSync93(DATA_DIR8, { recursive: true });
614752
- const filepath = join194(DATA_DIR8, "questions.json");
614753
- if (existsSync129(filepath)) {
615028
+ const filepath = join195(DATA_DIR8, "questions.json");
615029
+ if (existsSync131(filepath)) {
614754
615030
  const raw = JSON.parse(readFileSync116(filepath, "utf-8"));
614755
615031
  return raw.questions || [];
614756
615032
  }
@@ -614759,9 +615035,9 @@ function loadQuestions() {
614759
615035
  }
614760
615036
  function saveQuestions(questions) {
614761
615037
  try {
614762
- if (!existsSync129(DATA_DIR8))
615038
+ if (!existsSync131(DATA_DIR8))
614763
615039
  mkdirSync93(DATA_DIR8, { recursive: true });
614764
- writeFileSync73(join194(DATA_DIR8, "questions.json"), JSON.stringify({ questions }, null, 2));
615040
+ writeFileSync73(join195(DATA_DIR8, "questions.json"), JSON.stringify({ questions }, null, 2));
614765
615041
  } catch {}
614766
615042
  }
614767
615043
  function QuestionsView({ visible, data, onUpdateData, onClose }) {
@@ -616047,13 +616323,13 @@ import * as fs104 from "fs";
616047
616323
  import * as pathMod from "path";
616048
616324
 
616049
616325
  // apps/tui/src/lib/package-scripts.ts
616050
- import { existsSync as existsSync130 } from "fs";
616051
- import { join as join195, resolve as resolve73 } from "path";
616326
+ import { existsSync as existsSync132 } from "fs";
616327
+ import { join as join196, resolve as resolve75 } from "path";
616052
616328
  var __dirname = (import.meta.dir + "/../apps/tui/src/lib");
616053
- var PACKAGE_ROOT = resolve73(__dirname, "../../../..");
616329
+ var PACKAGE_ROOT = resolve75(__dirname, "../../../..");
616054
616330
  function packageBinScript(name, root = PACKAGE_ROOT) {
616055
- const candidate = join195(root, "bin", name);
616056
- return existsSync130(candidate) ? candidate : null;
616331
+ const candidate = join196(root, "bin", name);
616332
+ return existsSync132(candidate) ? candidate : null;
616057
616333
  }
616058
616334
 
616059
616335
  // apps/tui/src/app.tsx
@@ -616658,10 +616934,10 @@ function App2({
616658
616934
  setting,
616659
616935
  env: process.env,
616660
616936
  seenVersion: readSeenVersion(),
616661
- version: import_package.default.version
616937
+ version: import_package2.default.version
616662
616938
  });
616663
616939
  if (show)
616664
- markIntroSeen(import_package.default.version);
616940
+ markIntroSeen(import_package2.default.version);
616665
616941
  return show;
616666
616942
  });
616667
616943
  const [processingStage, setProcessingStage] = import_react104.useState("planning");
@@ -618560,7 +618836,7 @@ Keyboard shortcuts:
618560
618836
  setViewMode((prev) => prev === "kanban" ? "chat" : "kanban");
618561
618837
  break;
618562
618838
  case "bug":
618563
- addSystemMessage(runBugCommand(args, { version: import_package.default.version }).text);
618839
+ addSystemMessage(runBugCommand(args, { version: import_package2.default.version }).text);
618564
618840
  break;
618565
618841
  case "allow-read":
618566
618842
  addSystemMessage(runAllowReadCommand(args, process.cwd(), (d) => agent?.allowReadDir(d) ?? null));
@@ -618893,6 +619169,19 @@ ${lines.join(`
618893
619169
  agent.cleanup().catch(() => {});
618894
619170
  exit();
618895
619171
  break;
619172
+ case "mode": {
619173
+ const next = parseModeArgs(args);
619174
+ if (!next) {
619175
+ addSystemMessage(`${permSwitchLine(activePermMode)}
619176
+ ${MODE_USAGE}`);
619177
+ break;
619178
+ }
619179
+ setTabPermMode(activeTabId, next);
619180
+ const shown = permView(permHolderFor(activeTabId));
619181
+ addSystemMessage(permSwitchLine(shown.mode, shown.held));
619182
+ showPermToast({ tabId: activeTabId, ...shown });
619183
+ break;
619184
+ }
618896
619185
  case "infinite": {
618897
619186
  const next = infiniteModeActive ? "ask" : "infinite";
618898
619187
  setTabPermMode(activeTabId, next);
@@ -620977,6 +621266,26 @@ ${found.slice(0, 40).map((k) => ` ${k.name} ${k.description}`).join(`
620977
621266
  }
620978
621267
  return;
620979
621268
  }
621269
+ if (input && !attached) {
621270
+ const termArgs = detectTermPresetIntent(input, listPresetIds());
621271
+ if (termArgs) {
621272
+ appendToTab(tabId, {
621273
+ id: `user-${Date.now()}`,
621274
+ role: "user",
621275
+ content: bubbleContent,
621276
+ timestamp: new Date
621277
+ });
621278
+ setRecentCommands((prev) => [bubbleContent, ...prev].slice(0, 20));
621279
+ const refused = planModeRefusal2(permView(permHolderFor(tabId)).mode);
621280
+ if (refused) {
621281
+ addSystemMessage(refused);
621282
+ return;
621283
+ }
621284
+ addSystemMessage(`Opening ${termArgs[0]} (same as /term ${termArgs.join(" ")}).`);
621285
+ handleSlashCommand("term", termArgs);
621286
+ return;
621287
+ }
621288
+ }
620980
621289
  if (input && !attached) {
620981
621290
  const buildTask = detectBuildIntent(input);
620982
621291
  if (buildTask) {
@@ -621726,7 +622035,7 @@ ${result.commands.map((c) => ` $ ${c}`).join(`
621726
622035
  value: { enabled: adhdMode, ratio: 0.5 },
621727
622036
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(FixedFrame, {
621728
622037
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(IntroBanner, {
621729
- version: import_package.default.version,
622038
+ version: import_package2.default.version,
621730
622039
  animate: showAnimations,
621731
622040
  onDone: (carried) => {
621732
622041
  bootTrace("intro-done", carried ? "by-key" : "timeout");
@@ -621800,7 +622109,7 @@ ${result.commands.map((c) => ` $ ${c}`).join(`
621800
622109
  flexShrink: 0,
621801
622110
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(HeaderBar, {
621802
622111
  updateAvailable: updateInfo,
621803
- version: import_package.default.version,
622112
+ version: import_package2.default.version,
621804
622113
  workspacePath: process.cwd(),
621805
622114
  branch: git.branch,
621806
622115
  syncStatus: git.noRepo ? "no repo" : git.sync,