@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/node/tui.js CHANGED
@@ -21831,8 +21831,6 @@ var init_allowlist = __esm(() => {
21831
21831
  "node",
21832
21832
  "npm",
21833
21833
  "npx",
21834
- "pnpm",
21835
- "yarn",
21836
21834
  "deno",
21837
21835
  "python",
21838
21836
  "python3",
@@ -21840,9 +21838,6 @@ var init_allowlist = __esm(() => {
21840
21838
  "pip3",
21841
21839
  "uv",
21842
21840
  "git",
21843
- "go",
21844
- "cargo",
21845
- "rustc",
21846
21841
  "tsc",
21847
21842
  "gh",
21848
21843
  "docker",
@@ -21867,20 +21862,18 @@ var init_allowlist = __esm(() => {
21867
21862
  "biome",
21868
21863
  "eslint",
21869
21864
  "prettier",
21870
- "vite"
21865
+ "vite",
21866
+ "8gent"
21871
21867
  ]);
21872
21868
  SHORT_VERSION = {
21873
21869
  node: "-v",
21874
21870
  bun: "-v",
21875
21871
  npm: "-v",
21876
- pnpm: "-v",
21877
- yarn: "-v",
21878
21872
  deno: "-V",
21879
21873
  python: "-V",
21880
21874
  python3: "-V",
21881
21875
  ffmpeg: "-version",
21882
- ffprobe: "-version",
21883
- go: "version"
21876
+ ffprobe: "-version"
21884
21877
  };
21885
21878
  SECRET_ARG = /(^|\/)\.env(\.[\w.-]+)?$|(^|\/)\.env\b|id_rsa|id_ed25519|id_ecdsa|(^|\/)\.ssh(\/|$)|\.aws\/|credentials|\.netrc|\.npmrc|\.git-credentials|keychain|\.pem$|\.key$|\.p12$/;
21886
21879
  REDIR_RE2 = /(?<![<>&\d])(\d?|&)(>>?|<)(\|?)\s*(&\d+|[^\s;|&<>()]+)/g;
@@ -33286,7 +33279,7 @@ var init_permissions = __esm(() => {
33286
33279
  var require_package = __commonJS(function(exports2, module2) {
33287
33280
  module2.exports = {
33288
33281
  name: "@8gi-foundation/8gent-code",
33289
- version: "0.20.15",
33282
+ version: "0.20.17",
33290
33283
  private: false,
33291
33284
  engines: {
33292
33285
  node: ">=22.5"
@@ -66788,6 +66781,12 @@ var init_slash_commands = __esm(() => {
66788
66781
  description: "Enable infinite mode (autonomous until done)",
66789
66782
  usage: "/infinite [task]"
66790
66783
  },
66784
+ {
66785
+ name: "mode",
66786
+ aliases: [],
66787
+ description: "Set this tab's permission mode: plan, ask, guarded or infinite (same as Shift+Tab)",
66788
+ usage: "/mode <plan|ask|guarded|infinite>"
66789
+ },
66791
66790
  {
66792
66791
  name: "onboarding",
66793
66792
  aliases: ["onboard", "setup", "intro"],
@@ -77734,8 +77733,31 @@ function emptyReplyStall(toolCalls) {
77734
77733
  function isEmptyReplyStall(line) {
77735
77734
  return line.startsWith(EMPTY_REPLY_STALL_PREFIX);
77736
77735
  }
77737
- function emptyReplyNote(toolCalls) {
77738
- return `[harness] No reply: ${emptyReplyStall(toolCalls)}.`;
77736
+ function refusedCalls(log, max = 3) {
77737
+ const out = [];
77738
+ let total = 0;
77739
+ for (const e of log) {
77740
+ const m = /^\[[^\]]*\b(?:BLOCKED|DENIED)\b[^\]]*\]\s*([^\n]*)/.exec((e.result ?? "").trimStart());
77741
+ if (!m)
77742
+ continue;
77743
+ total++;
77744
+ if (out.length >= max)
77745
+ continue;
77746
+ const a = e.args;
77747
+ const first = typeof a?.command === "string" ? a.command.trim().split(/\s+/)[0] ?? "" : "";
77748
+ const bin = /^[A-Za-z][\w.+-]{0,40}$/.test(first) ? ` ${first}` : "";
77749
+ const why = /declin/i.test(m[1]) ? "declined" : "blocked by policy";
77750
+ out.push(`${e.name}${bin}: ${why}`);
77751
+ }
77752
+ return { total, shown: out };
77753
+ }
77754
+ function emptyReplyNote(toolCalls, log = []) {
77755
+ const base = `[harness] No reply: ${emptyReplyStall(toolCalls)}.`;
77756
+ const { total, shown } = refusedCalls(log);
77757
+ if (total === 0)
77758
+ return base;
77759
+ const more = total > shown.length ? ` (and ${total - shown.length} more)` : "";
77760
+ return `${base} ${total} of the calls were refused, so they did not run: ${shown.join("; ")}${more}.`;
77739
77761
  }
77740
77762
  function isEmptyReplyNote(reply) {
77741
77763
  return reply.startsWith("[harness] No reply:");
@@ -77828,7 +77850,7 @@ ${summary}`;
77828
77850
  if (unverified.length === 0)
77829
77851
  return { content, rounds, toolLog, unverified: [] };
77830
77852
  const notes = [
77831
- empty ? emptyReplyNote(toolLog.length) : content.trimEnd(),
77853
+ empty ? emptyReplyNote(toolLog.length, toolLog) : content.trimEnd(),
77832
77854
  unfulfilled.length > 0 ? formatHarnessNote(unfulfilled) : ""
77833
77855
  ].filter((n) => n.trim() !== "");
77834
77856
  return { content: notes.join(`
@@ -570362,7 +570384,7 @@ onboarding preferences skip animations adhd quarantine toolshed skills design ev
570362
570384
  resume history continue compact chat agent github debug music dj pet export fork rewind branch cron deploy
570363
570385
  vercel telegram router rename spawn settings quiet term create-app hands eyes handeyes goal subgoal build
570364
570386
  notes ideas btw questions projects terminal reload exit
570365
- copy hotkeys tree import update route privacy consult bug me allow-read`.split(/\s+/));
570387
+ copy hotkeys tree import update route privacy consult bug me allow-read mode`.split(/\s+/));
570366
570388
  });
570367
570389
 
570368
570390
  // packages/extensions/index.ts
@@ -576446,7 +576468,9 @@ function defaultSessionsDir2() {
576446
576468
 
576447
576469
  class SessionWriter {
576448
576470
  filePath;
576449
- fd;
576471
+ fd = -1;
576472
+ dev = -1;
576473
+ ino = -1;
576450
576474
  persist;
576451
576475
  seq = 0;
576452
576476
  sessionId;
@@ -576478,15 +576502,47 @@ class SessionWriter {
576478
576502
  fs78.mkdirSync(dir, { recursive: true });
576479
576503
  }
576480
576504
  this.filePath = path107.join(dir, `${sessionId}.jsonl`);
576481
- this.fd = fs78.openSync(this.filePath, "a");
576505
+ this.open();
576506
+ }
576507
+ open() {
576508
+ const c = fs78.constants;
576509
+ const fd = fs78.openSync(this.filePath, c.O_WRONLY | c.O_APPEND | c.O_CREAT | (c.O_NOFOLLOW ?? 0), 384);
576510
+ const st = fs78.fstatSync(fd);
576511
+ if (!st.isFile()) {
576512
+ fs78.closeSync(fd);
576513
+ throw new Error(`${this.filePath} is not a regular file`);
576514
+ }
576515
+ this.fd = fd;
576516
+ this.dev = st.dev;
576517
+ this.ino = st.ino;
576518
+ }
576519
+ fdIsOurs() {
576520
+ if (this.fd < 0)
576521
+ return false;
576522
+ try {
576523
+ const st = fs78.fstatSync(this.fd);
576524
+ return st.dev === this.dev && st.ino === this.ino;
576525
+ } catch {
576526
+ return false;
576527
+ }
576482
576528
  }
576483
576529
  write(entry) {
576484
576530
  if (!this.persist)
576485
576531
  return;
576486
576532
  const line = `${JSON.stringify(entry)}
576487
576533
  `;
576488
- fs78.writeSync(this.fd, line);
576534
+ try {
576535
+ if (!this.fdIsOurs())
576536
+ this.open();
576537
+ fs78.writeSync(this.fd, line);
576538
+ } catch (err) {
576539
+ this.fd = -1;
576540
+ this.skippedWrites++;
576541
+ this.lastWriteError = `session log not written: ${err instanceof Error ? err.message : String(err)}`;
576542
+ }
576489
576543
  }
576544
+ lastWriteError = null;
576545
+ skippedWrites = 0;
576490
576546
  nextSeq() {
576491
576547
  return this.seq++;
576492
576548
  }
@@ -576696,8 +576752,9 @@ class SessionWriter {
576696
576752
  reportId: reportId ?? null
576697
576753
  }
576698
576754
  });
576699
- if (this.persist)
576755
+ if (this.persist && this.fdIsOurs())
576700
576756
  fs78.closeSync(this.fd);
576757
+ this.fd = -1;
576701
576758
  }
576702
576759
  getFilePath() {
576703
576760
  return this.filePath;
@@ -580415,6 +580472,52 @@ var init_registry = __esm(() => {
580415
580472
  ];
580416
580473
  });
580417
580474
 
580475
+ // packages/eight/small-talk.ts
580476
+ function flatten2(text) {
580477
+ return text.toLowerCase().replace(/^\[mode: [^\]]*\]\s*/, "").replace(/['’]/g, "").replace(/[^a-z0-9 ]+/g, " ").replace(/\s+/g, " ").trim();
580478
+ }
580479
+ function isSmallTalk(text) {
580480
+ const flat = flatten2(text);
580481
+ return flat.length > 0 && flat.length <= 40 && GREETING.test(flat);
580482
+ }
580483
+ function isGreeting(text) {
580484
+ return isSmallTalk(text) && !THANKS.test(flatten2(text));
580485
+ }
580486
+ 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.";
580487
+ var init_small_talk = __esm(() => {
580488
+ CORE = [
580489
+ "h+i+",
580490
+ "h+e+y+",
580491
+ "h+e+l+o+",
580492
+ "hiya",
580493
+ "howdy",
580494
+ "y+o+",
580495
+ "sup",
580496
+ "wa+s+s?u+p+",
580497
+ "w?h?a+t?s? ?u+p+",
580498
+ "wh?a+t+s? go+d+",
580499
+ "good (?:morning|afternoon|evening|night)",
580500
+ "gm",
580501
+ "gn",
580502
+ "morning",
580503
+ "evening",
580504
+ "how are you(?: doing| today)?",
580505
+ "how are things",
580506
+ "hows it going",
580507
+ "how you doing",
580508
+ "you there",
580509
+ "anyone there",
580510
+ "ping",
580511
+ "thanks(?: a lot| so much)?",
580512
+ "thank you(?: so much)?",
580513
+ "thx",
580514
+ "ty",
580515
+ "cheers"
580516
+ ].join("|");
580517
+ GREETING = new RegExp(`^(?:(?:hi+|hey+|hello+|yo+|hiya|howdy) )?(?:${CORE})(?: (?:there|again|everyone|all|team|mate|man|bro|friend|8gent|eight))?$`);
580518
+ THANKS = /^(?:thanks|thank you|thx|ty|cheers)\b/;
580519
+ });
580520
+
580418
580521
  // packages/eight/verify-gate.ts
580419
580522
  function isVerifyGateEnabled(env = process.env) {
580420
580523
  const v = (env.EIGHT_VERIFY_GATE ?? "").trim().toLowerCase();
@@ -580457,6 +580560,755 @@ var init_verify_gate = __esm(() => {
580457
580560
  `);
580458
580561
  });
580459
580562
 
580563
+ // packages/voice/termux.ts
580564
+ import { constants as constants16, accessSync as accessSync5, readFileSync as readFileSync97 } from "node:fs";
580565
+ import { delimiter as delimiter8, join as join155 } from "node:path";
580566
+ function isAndroidHost(platform = process.platform, env = process.env) {
580567
+ return platform === "android" || !!env.TERMUX_VERSION || String(env.PREFIX ?? "").includes("/com.termux/");
580568
+ }
580569
+ function findOnPath(name, env = process.env) {
580570
+ for (const dir of String(env.PATH ?? "").split(delimiter8)) {
580571
+ if (!dir)
580572
+ continue;
580573
+ const candidate = join155(dir, name);
580574
+ try {
580575
+ accessSync5(candidate, constants16.X_OK);
580576
+ return candidate;
580577
+ } catch {}
580578
+ }
580579
+ return null;
580580
+ }
580581
+ function voiceOutputOn(env = process.env, readSettings = () => {
580582
+ const home = env.EIGHT_HOME || join155(env.HOME ?? "", ".8gent");
580583
+ return readFileSync97(join155(home, "settings.json"), "utf8");
580584
+ }) {
580585
+ if (env.EIGHT_VOICE === "1")
580586
+ return true;
580587
+ try {
580588
+ return JSON.parse(readSettings())?.voice?.outputEnabled === true;
580589
+ } catch {
580590
+ return false;
580591
+ }
580592
+ }
580593
+ function ctrlRAction(key, input, android = isAndroidHost()) {
580594
+ if (!key.ctrl || input !== "r")
580595
+ return "ignore";
580596
+ return android ? "android-line" : "toggle";
580597
+ }
580598
+ 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;
580599
+ var init_termux = __esm(() => {
580600
+ ANDROID_VOICE_LINE_SUBS = [
580601
+ "",
580602
+ "chat",
580603
+ "conversation",
580604
+ "talk",
580605
+ "record",
580606
+ "listen",
580607
+ "stt",
580608
+ "status",
580609
+ "stop"
580610
+ ];
580611
+ });
580612
+
580613
+ // packages/runtime/node-runtime.ts
580614
+ import { execFile as execFile7 } from "node:child_process";
580615
+ import { existsSync as existsSync105 } from "node:fs";
580616
+ import { homedir as homedir78 } from "node:os";
580617
+ import { join as join156 } from "node:path";
580618
+ import { promisify as promisify6 } from "node:util";
580619
+ function parseNodeVersion(raw) {
580620
+ const trimmed = raw.trim();
580621
+ const match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
580622
+ if (!match)
580623
+ return null;
580624
+ return {
580625
+ major: Number.parseInt(match[1], 10),
580626
+ minor: Number.parseInt(match[2], 10),
580627
+ patch: Number.parseInt(match[3], 10)
580628
+ };
580629
+ }
580630
+ function satisfiesMinVersion(v, min) {
580631
+ const minParsed = parseNodeVersion(min);
580632
+ if (!minParsed)
580633
+ return true;
580634
+ if (v.major !== minParsed.major)
580635
+ return v.major > minParsed.major;
580636
+ if (v.minor !== minParsed.minor)
580637
+ return v.minor > minParsed.minor;
580638
+ return v.patch >= minParsed.patch;
580639
+ }
580640
+ function pickPlatformAsset(input) {
580641
+ const byArch = SUPPORTED[input.platform];
580642
+ if (!byArch) {
580643
+ throw new Error(`unsupported platform: ${input.platform}`);
580644
+ }
580645
+ const asset = byArch[input.arch];
580646
+ if (!asset) {
580647
+ throw new Error(`unsupported arch: ${input.arch} on ${input.platform}`);
580648
+ }
580649
+ return asset;
580650
+ }
580651
+ function buildDownloadUrl(opts) {
580652
+ const cleanVersion = opts.version.startsWith("v") ? opts.version.slice(1) : opts.version;
580653
+ const v = `v${cleanVersion}`;
580654
+ const fileBase = `node-${v}-${opts.asset.os}-${opts.asset.arch}`;
580655
+ return `https://nodejs.org/dist/${v}/${fileBase}.${opts.asset.ext}`;
580656
+ }
580657
+ function defaultRoot() {
580658
+ return join156(homedir78(), ".8gent", "runtime");
580659
+ }
580660
+ function getRuntimeDir(loc) {
580661
+ const root = loc.root ?? defaultRoot();
580662
+ return join156(root, `node-${loc.version}`);
580663
+ }
580664
+ function resolvedNodeBinPath(opts) {
580665
+ const dir = getRuntimeDir(opts);
580666
+ if (opts.platform === "win32") {
580667
+ return join156(dir, "node.exe");
580668
+ }
580669
+ return join156(dir, "bin", "node");
580670
+ }
580671
+ async function isRuntimeReady(opts) {
580672
+ const platform = opts.platform ?? process.platform;
580673
+ const bin = resolvedNodeBinPath({
580674
+ root: opts.root,
580675
+ version: opts.version,
580676
+ platform
580677
+ });
580678
+ if (!existsSync105(bin))
580679
+ return false;
580680
+ const min = opts.minVersion ?? opts.version;
580681
+ try {
580682
+ const { stdout } = await execFileAsync4(bin, ["--version"], {
580683
+ timeout: 3000
580684
+ });
580685
+ const parsed = parseNodeVersion(stdout);
580686
+ if (!parsed)
580687
+ return false;
580688
+ return satisfiesMinVersion(parsed, min);
580689
+ } catch {
580690
+ return false;
580691
+ }
580692
+ }
580693
+ var execFileAsync4, SUPPORTED;
580694
+ var init_node_runtime = __esm(() => {
580695
+ execFileAsync4 = promisify6(execFile7);
580696
+ SUPPORTED = {
580697
+ darwin: {
580698
+ arm64: { os: "darwin", arch: "arm64", ext: "tar.gz" },
580699
+ x64: { os: "darwin", arch: "x64", ext: "tar.gz" }
580700
+ },
580701
+ linux: {
580702
+ arm64: { os: "linux", arch: "arm64", ext: "tar.xz" },
580703
+ x64: { os: "linux", arch: "x64", ext: "tar.xz" }
580704
+ },
580705
+ win32: {
580706
+ x64: { os: "win", arch: "x64", ext: "zip" },
580707
+ arm64: { os: "win", arch: "arm64", ext: "zip" }
580708
+ }
580709
+ };
580710
+ });
580711
+
580712
+ // packages/runtime/installer.ts
580713
+ import { spawn as spawn23 } from "node:child_process";
580714
+ import {
580715
+ createWriteStream,
580716
+ existsSync as existsSync106,
580717
+ mkdirSync as mkdirSync74,
580718
+ mkdtempSync as mkdtempSync11,
580719
+ readdirSync as readdirSync26,
580720
+ renameSync as renameSync16,
580721
+ rmSync as rmSync14
580722
+ } from "node:fs";
580723
+ import { join as join157 } from "node:path";
580724
+ import { Readable as Readable2 } from "node:stream";
580725
+ import { pipeline } from "node:stream/promises";
580726
+ function planInstall(input) {
580727
+ const platform = input.platform ?? process.platform;
580728
+ const arch = input.arch ?? process.arch;
580729
+ const asset = pickPlatformAsset({ platform, arch });
580730
+ const url = buildDownloadUrl({ version: input.version, asset });
580731
+ const runtimeDir = getRuntimeDir({ root: input.root, version: input.version });
580732
+ const binPath = resolvedNodeBinPath({
580733
+ root: input.root,
580734
+ version: input.version,
580735
+ platform
580736
+ });
580737
+ return {
580738
+ version: input.version,
580739
+ url,
580740
+ asset,
580741
+ runtimeDir,
580742
+ binPath,
580743
+ archiveExt: asset.ext
580744
+ };
580745
+ }
580746
+ async function installRuntime(input) {
580747
+ const plan = planInstall(input);
580748
+ const platform = input.platform ?? process.platform;
580749
+ const minVersion = input.minVersion ?? input.version;
580750
+ const ready = await isRuntimeReady({
580751
+ root: input.root,
580752
+ version: input.version,
580753
+ platform,
580754
+ minVersion
580755
+ });
580756
+ if (ready || input.dryRun) {
580757
+ return {
580758
+ ...plan,
580759
+ action: ready ? "already-ready" : "dry-run"
580760
+ };
580761
+ }
580762
+ const runtimeRoot = plan.runtimeDir.replace(/\/node-[^/]+$/, "");
580763
+ mkdirSync74(runtimeRoot, { recursive: true });
580764
+ const stagingDir = mkdtempSync11(join157(runtimeRoot, ".dl-"));
580765
+ const archivePath = join157(stagingDir, `node.${plan.archiveExt}`);
580766
+ try {
580767
+ await downloadFile(plan.url, archivePath);
580768
+ const extractRoot = join157(stagingDir, "extract");
580769
+ mkdirSync74(extractRoot, { recursive: true });
580770
+ await extractArchive(archivePath, extractRoot, plan.archiveExt);
580771
+ const children = readdirSync26(extractRoot);
580772
+ const nodeDir = children.find((n) => n.startsWith("node-"));
580773
+ if (!nodeDir) {
580774
+ throw new Error(`could not find node-* dir inside ${extractRoot}`);
580775
+ }
580776
+ const extractedPath = join157(extractRoot, nodeDir);
580777
+ if (existsSync106(plan.runtimeDir)) {
580778
+ rmSync14(plan.runtimeDir, { recursive: true, force: true });
580779
+ }
580780
+ renameSync16(extractedPath, plan.runtimeDir);
580781
+ } catch (err) {
580782
+ return {
580783
+ ...plan,
580784
+ action: "failed",
580785
+ error: err instanceof Error ? err.message : String(err)
580786
+ };
580787
+ } finally {
580788
+ try {
580789
+ rmSync14(stagingDir, { recursive: true, force: true });
580790
+ } catch {}
580791
+ }
580792
+ const reReady = await isRuntimeReady({
580793
+ root: input.root,
580794
+ version: input.version,
580795
+ platform,
580796
+ minVersion
580797
+ });
580798
+ if (!reReady) {
580799
+ return {
580800
+ ...plan,
580801
+ action: "failed",
580802
+ error: `installed runtime at ${plan.binPath} but it does not satisfy minVersion ${minVersion}`
580803
+ };
580804
+ }
580805
+ return {
580806
+ ...plan,
580807
+ action: "installed"
580808
+ };
580809
+ }
580810
+ async function downloadFile(url, dest) {
580811
+ const res = await fetch(url, { redirect: "follow" });
580812
+ if (!res.ok || !res.body) {
580813
+ throw new Error(`download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
580814
+ }
580815
+ const out = createWriteStream(dest);
580816
+ await pipeline(Readable2.fromWeb(res.body), out);
580817
+ }
580818
+ async function extractArchive(archivePath, destDir, ext) {
580819
+ if (ext === "tar.gz" || ext === "tar.xz") {
580820
+ await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
580821
+ } else if (ext === "zip") {
580822
+ try {
580823
+ await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
580824
+ } catch {
580825
+ await runCmd("powershell", [
580826
+ "-Command",
580827
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
580828
+ ]);
580829
+ }
580830
+ } else {
580831
+ throw new Error(`unsupported archive ext: ${ext}`);
580832
+ }
580833
+ }
580834
+ function runCmd(command, args) {
580835
+ return new Promise((resolve, reject) => {
580836
+ const proc = spawn23(command, args, { stdio: "ignore" });
580837
+ proc.on("error", (err) => reject(err));
580838
+ proc.on("close", (code) => {
580839
+ if (code === 0)
580840
+ resolve();
580841
+ else
580842
+ reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
580843
+ });
580844
+ });
580845
+ }
580846
+ var init_installer = __esm(() => {
580847
+ init_node_runtime();
580848
+ });
580849
+
580850
+ // packages/runtime/contract-validator.ts
580851
+ var init_contract_validator = __esm(() => {
580852
+ init_types3();
580853
+ });
580854
+
580855
+ // packages/runtime/index.ts
580856
+ import { join as join158 } from "node:path";
580857
+ import { execFile as execFile8 } from "node:child_process";
580858
+ import { promisify as promisify7 } from "node:util";
580859
+ async function ensureNodeFor(minVersion) {
580860
+ try {
580861
+ const { stdout } = await execFileAsync5("node", ["--version"], {
580862
+ timeout: 3000
580863
+ });
580864
+ const parsed = parseNodeVersion(stdout);
580865
+ if (parsed && satisfiesMinVersion(parsed, minVersion)) {
580866
+ return null;
580867
+ }
580868
+ } catch {}
580869
+ const ready = await isRuntimeReady({
580870
+ version: MANAGED_NODE_VERSION,
580871
+ minVersion
580872
+ });
580873
+ const dir = getRuntimeDir({ version: MANAGED_NODE_VERSION });
580874
+ const binDir = process.platform === "win32" ? dir : join158(dir, "bin");
580875
+ if (ready)
580876
+ return binDir;
580877
+ const result = await installRuntime({
580878
+ version: MANAGED_NODE_VERSION,
580879
+ minVersion
580880
+ });
580881
+ if (result.action === "failed") {
580882
+ throw new Error(`failed to install Node ${MANAGED_NODE_VERSION}: ${result.error ?? "unknown"}`);
580883
+ }
580884
+ return binDir;
580885
+ }
580886
+ var execFileAsync5, MANAGED_NODE_VERSION = "22.14.0";
580887
+ var init_runtime = __esm(() => {
580888
+ init_installer();
580889
+ init_node_runtime();
580890
+ init_node_runtime();
580891
+ init_installer();
580892
+ init_contract_validator();
580893
+ execFileAsync5 = promisify7(execFile8);
580894
+ });
580895
+
580896
+ // apps/tui/src/lib/external-agent-runner.ts
580897
+ import { execSync as execSync6, spawn as spawn24 } from "node:child_process";
580898
+ function getPreset(id) {
580899
+ return EXTERNAL_AGENT_PRESETS[id.toLowerCase()] ?? null;
580900
+ }
580901
+ function listPresetIds() {
580902
+ return Object.keys(EXTERNAL_AGENT_PRESETS);
580903
+ }
580904
+ function isInstalled(preset) {
580905
+ try {
580906
+ execSync6(`command -v "${preset.command}"`, {
580907
+ stdio: "ignore",
580908
+ timeout: 1500
580909
+ });
580910
+ return true;
580911
+ } catch {
580912
+ return false;
580913
+ }
580914
+ }
580915
+ async function installAgent(preset, onLine) {
580916
+ const start = performance.now();
580917
+ if (!preset.install) {
580918
+ return {
580919
+ ok: false,
580920
+ stdout: "",
580921
+ stderr: "",
580922
+ durationMs: 0,
580923
+ command: "",
580924
+ error: `No install recipe for ${preset.id}. Install ${preset.command} manually and try again.`
580925
+ };
580926
+ }
580927
+ const command = preset.install.command;
580928
+ return new Promise((resolve) => {
580929
+ let stdout = "";
580930
+ let stderr = "";
580931
+ let exited = false;
580932
+ const proc = spawnShell(command, {
580933
+ posix: ["bash", "-lc"],
580934
+ stdio: ["ignore", "pipe", "pipe"],
580935
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" }
580936
+ });
580937
+ const finish = (result) => {
580938
+ if (exited)
580939
+ return;
580940
+ exited = true;
580941
+ clearTimeout(timer);
580942
+ resolve({
580943
+ ...result,
580944
+ durationMs: Math.round(performance.now() - start),
580945
+ command
580946
+ });
580947
+ };
580948
+ const pipeLines = (chunk, source) => {
580949
+ const text = chunk.toString();
580950
+ if (source === "stdout")
580951
+ stdout += text;
580952
+ else
580953
+ stderr += text;
580954
+ if (onLine) {
580955
+ for (const line of text.split(/\r?\n/)) {
580956
+ if (line.trim().length > 0)
580957
+ onLine(line, source);
580958
+ }
580959
+ }
580960
+ };
580961
+ proc.stdout?.on("data", (c) => pipeLines(c, "stdout"));
580962
+ proc.stderr?.on("data", (c) => pipeLines(c, "stderr"));
580963
+ proc.on("error", (err) => {
580964
+ finish({ ok: false, stdout, stderr, error: err.message });
580965
+ });
580966
+ proc.on("close", (code) => {
580967
+ finish({
580968
+ ok: code === 0,
580969
+ stdout,
580970
+ stderr,
580971
+ error: code === 0 ? undefined : stderr.trim().slice(0, 800) || `installer exited with code ${code}`
580972
+ });
580973
+ });
580974
+ const timer = setTimeout(() => {
580975
+ proc.kill("SIGTERM");
580976
+ finish({ ok: false, stdout, stderr, error: "install timed out after 5 minutes" });
580977
+ }, 5 * 60 * 1000);
580978
+ });
580979
+ }
580980
+ async function ensureInstalled(preset, onLine) {
580981
+ if (isInstalled(preset))
580982
+ return true;
580983
+ if (!preset.install) {
580984
+ onLine?.(`${preset.command}: not installed and no auto-install recipe is configured.`, "info");
580985
+ return false;
580986
+ }
580987
+ onLine?.(`${preset.command}: not installed, running ${preset.install.command}`, "info");
580988
+ const result = await installAgent(preset, (line, source) => onLine?.(line, source));
580989
+ if (!result.ok) {
580990
+ onLine?.(`Install failed: ${result.error ?? "unknown error"}`, "info");
580991
+ return false;
580992
+ }
580993
+ const stillThere = isInstalled(preset);
580994
+ if (!stillThere) {
580995
+ 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");
580996
+ }
580997
+ return stillThere;
580998
+ }
580999
+ function composePrompt(history, prompt) {
581000
+ const lines = [];
581001
+ for (const m of history) {
581002
+ const tag = m.role === "user" ? "User" : m.role === "assistant" ? "Assistant" : m.role;
581003
+ lines.push(`${tag}: ${m.content}`);
581004
+ }
581005
+ lines.push(`User: ${prompt}`);
581006
+ lines.push("Assistant:");
581007
+ return lines.join(`
581008
+
581009
+ `);
581010
+ }
581011
+ async function runExternalAgent(preset, prompt, signal) {
581012
+ const start = performance.now();
581013
+ const argv = [...preset.args];
581014
+ if (preset.promptMode === "flag") {
581015
+ argv.push(preset.flagName ?? "-p", prompt);
581016
+ } else if (preset.promptMode === "arg") {
581017
+ argv.push(prompt);
581018
+ }
581019
+ let pathPrefix = "";
581020
+ if (preset.requiresNodeVersion) {
581021
+ try {
581022
+ await Promise.resolve().then(() => init_runtime());
581023
+ const binDir = await ensureNodeFor(preset.requiresNodeVersion);
581024
+ if (binDir)
581025
+ pathPrefix = `${binDir}:`;
581026
+ } catch (err) {
581027
+ return {
581028
+ ok: false,
581029
+ text: "",
581030
+ durationMs: Math.round(performance.now() - start),
581031
+ command: `${preset.command} ${argv.join(" ")}`,
581032
+ error: `managed Node runtime install failed: ${err instanceof Error ? err.message : String(err)}`,
581033
+ exitCode: null
581034
+ };
581035
+ }
581036
+ }
581037
+ return new Promise((resolve) => {
581038
+ let stdout = "";
581039
+ let stderr = "";
581040
+ let exited = false;
581041
+ const proc = spawn24(preset.command, argv, {
581042
+ stdio: ["pipe", "pipe", "pipe"],
581043
+ env: {
581044
+ ...process.env,
581045
+ PATH: `${pathPrefix}${process.env.PATH ?? ""}`,
581046
+ NO_COLOR: "1",
581047
+ FORCE_COLOR: "0",
581048
+ "8GENT_NO_INTRO": "1",
581049
+ AI_SDK_LOG_WARNINGS: "false"
581050
+ }
581051
+ });
581052
+ const finish = (result) => {
581053
+ if (exited)
581054
+ return;
581055
+ exited = true;
581056
+ clearTimeout(timer);
581057
+ signal?.removeEventListener("abort", onAbort);
581058
+ resolve({
581059
+ ...result,
581060
+ durationMs: Math.round(performance.now() - start),
581061
+ command: `${preset.command} ${argv.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`
581062
+ });
581063
+ };
581064
+ proc.on("error", (err) => {
581065
+ finish({
581066
+ ok: false,
581067
+ text: "",
581068
+ error: err.code === "ENOENT" ? `${preset.command}: not installed (no binary on $PATH)` : err.message,
581069
+ exitCode: null
581070
+ });
581071
+ });
581072
+ proc.stdout?.on("data", (chunk) => {
581073
+ stdout += chunk.toString();
581074
+ });
581075
+ proc.stderr?.on("data", (chunk) => {
581076
+ stderr += chunk.toString();
581077
+ });
581078
+ proc.on("close", (code) => {
581079
+ let text = preset.parseStdout ? preset.parseStdout(stdout).trim() : stdout.trim();
581080
+ try {
581081
+ init_keys();
581082
+ if (text)
581083
+ text = redactAndWarn(text);
581084
+ } catch {}
581085
+ if (code === 0 || code !== null && text.length > 0) {
581086
+ finish({ ok: code === 0, text, exitCode: code, error: code === 0 ? undefined : stderr.trim().slice(0, 400) });
581087
+ } else {
581088
+ finish({
581089
+ ok: false,
581090
+ text,
581091
+ exitCode: code,
581092
+ error: stderr.trim().slice(0, 400) || `exited with code ${code}`
581093
+ });
581094
+ }
581095
+ });
581096
+ if (preset.promptMode === "stdin" && proc.stdin) {
581097
+ proc.stdin.write(prompt);
581098
+ proc.stdin.end();
581099
+ } else if (proc.stdin) {
581100
+ proc.stdin.end();
581101
+ }
581102
+ const timer = setTimeout(() => {
581103
+ proc.kill("SIGTERM");
581104
+ finish({
581105
+ ok: false,
581106
+ text: stdout,
581107
+ error: `timeout after ${preset.timeoutMs ?? 90000}ms`,
581108
+ exitCode: null
581109
+ });
581110
+ }, preset.timeoutMs ?? 90000);
581111
+ const onAbort = () => {
581112
+ proc.kill("SIGTERM");
581113
+ finish({ ok: false, text: stdout, error: "aborted", exitCode: null });
581114
+ };
581115
+ signal?.addEventListener("abort", onAbort, { once: true });
581116
+ });
581117
+ }
581118
+ var stripAnsi2 = (s) => s.replace(/\[[0-9;?]*[a-zA-Z]/g, "").replace(/\][^]*/g, ""), EXTERNAL_AGENT_PRESETS;
581119
+ var init_external_agent_runner = __esm(() => {
581120
+ init_shell();
581121
+ EXTERNAL_AGENT_PRESETS = {
581122
+ claude: {
581123
+ id: "claude",
581124
+ label: "Sparring",
581125
+ command: "claude",
581126
+ promptMode: "flag",
581127
+ flagName: "-p",
581128
+ args: [],
581129
+ timeoutMs: 120000,
581130
+ parseStdout: stripAnsi2,
581131
+ homepage: "https://docs.claude.com/en/docs/claude-code",
581132
+ preferWindow: true,
581133
+ install: {
581134
+ command: "npm install -g --prefix=$HOME/.npm-global @anthropic-ai/claude-code",
581135
+ 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.'
581136
+ }
581137
+ },
581138
+ codex: {
581139
+ id: "codex",
581140
+ label: "OpenAI Codex",
581141
+ command: "codex",
581142
+ promptMode: "arg",
581143
+ args: ["exec"],
581144
+ timeoutMs: 120000,
581145
+ parseStdout: stripAnsi2,
581146
+ homepage: "https://github.com/openai/codex",
581147
+ preferWindow: true,
581148
+ install: {
581149
+ command: "npm install -g --prefix=$HOME/.npm-global @openai/codex",
581150
+ notes: "Binary at ~/.npm-global/bin/codex. Add `~/.npm-global/bin` to PATH if needed. Requires `export OPENAI_API_KEY=sk-...` before /spawn."
581151
+ }
581152
+ },
581153
+ hermes: {
581154
+ id: "hermes",
581155
+ label: "Hermes Agent",
581156
+ command: "hermes",
581157
+ preferWindow: true,
581158
+ promptMode: "flag",
581159
+ flagName: "-z",
581160
+ args: [],
581161
+ timeoutMs: 120000,
581162
+ parseStdout: stripAnsi2,
581163
+ homepage: "https://github.com/NousResearch/hermes-agent",
581164
+ install: {
581165
+ command: "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup --no-venv",
581166
+ notes: "Hermes needs Python ≥ 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)."
581167
+ }
581168
+ },
581169
+ openclaw: {
581170
+ id: "openclaw",
581171
+ label: "OpenClaw",
581172
+ command: "openclaw",
581173
+ preferWindow: true,
581174
+ promptMode: "arg",
581175
+ args: ["run", "--headless"],
581176
+ timeoutMs: 120000,
581177
+ parseStdout: stripAnsi2,
581178
+ homepage: "https://github.com/openclaw/openclaw",
581179
+ install: {
581180
+ command: "npm install -g --prefix=$HOME/.npm-global openclaw@latest",
581181
+ 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.)"
581182
+ },
581183
+ requiresNodeVersion: "22.14.0"
581184
+ },
581185
+ pi: {
581186
+ preferWindow: true,
581187
+ id: "pi",
581188
+ label: "Pi (pi-mono)",
581189
+ command: "pi",
581190
+ promptMode: "flag",
581191
+ flagName: "-p",
581192
+ args: [],
581193
+ timeoutMs: 120000,
581194
+ parseStdout: stripAnsi2,
581195
+ homepage: "https://pi.dev",
581196
+ install: {
581197
+ command: "npm install -g --prefix=$HOME/.npm-global @mariozechner/pi-coding-agent",
581198
+ 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)."
581199
+ }
581200
+ },
581201
+ "8gent": {
581202
+ id: "8gent",
581203
+ label: "8gent (nested)",
581204
+ command: "8gent",
581205
+ promptMode: "arg",
581206
+ args: ["run"],
581207
+ timeoutMs: 180000,
581208
+ parseStdout: stripAnsi2,
581209
+ homepage: "https://8gent.dev",
581210
+ install: {
581211
+ command: "npm install -g --prefix=$HOME/.npm-global @8gi-foundation/8gent-code --force",
581212
+ 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."
581213
+ }
581214
+ }
581215
+ };
581216
+ });
581217
+
581218
+ // packages/eight/welcome.ts
581219
+ import { existsSync as existsSync107 } from "node:fs";
581220
+ import { basename as basename51, delimiter as delimiter9, dirname as dirname75, isAbsolute as isAbsolute50, join as join159, relative as relative34, resolve as resolve72 } from "node:path";
581221
+ function pathOutsideFolder(path, cwd) {
581222
+ const root = resolve72(cwd);
581223
+ return path.split(delimiter9).filter((dir) => {
581224
+ if (!dir || !isAbsolute50(dir))
581225
+ return false;
581226
+ const rel = relative34(root, resolve72(dir));
581227
+ return rel.startsWith("..") || isAbsolute50(rel);
581228
+ }).join(delimiter9);
581229
+ }
581230
+ function safeModelLabel(model) {
581231
+ const m = model.trim();
581232
+ if (!m || m.length > 80 || /:\/\/|[@?\s]/.test(m))
581233
+ return "a custom model";
581234
+ return m;
581235
+ }
581236
+ function inGitRepo(dir) {
581237
+ let d = dir;
581238
+ for (let i = 0;i < 64; i++) {
581239
+ if (existsSync107(join159(d, ".git")))
581240
+ return true;
581241
+ const up = dirname75(d);
581242
+ if (up === d)
581243
+ return false;
581244
+ d = up;
581245
+ }
581246
+ return false;
581247
+ }
581248
+ function hostLabel(android, platform, arch) {
581249
+ if (android)
581250
+ return `Android (Termux, ${arch})`;
581251
+ const os = platform === "darwin" ? "macOS" : platform === "linux" ? "Linux" : platform === "win32" ? "Windows" : platform;
581252
+ return `${os} ${arch}`;
581253
+ }
581254
+ async function knownPresets() {
581255
+ const runner = await Promise.resolve().then(() => (init_external_agent_runner(), {}));
581256
+ const out = [];
581257
+ for (const id of listPresetIds()) {
581258
+ const p = getPreset(id);
581259
+ if (p)
581260
+ out.push({ id: p.id, command: p.command });
581261
+ }
581262
+ return out;
581263
+ }
581264
+ async function gatherWelcomeFacts(opts) {
581265
+ const env = opts.env ?? process.env;
581266
+ const android = isAndroidHost(opts.platform ?? process.platform, env);
581267
+ const cwd = resolve72(opts.cwd);
581268
+ const home = env.HOME ? resolve72(env.HOME) : null;
581269
+ const lookupEnv = { ...env, PATH: pathOutsideFolder(String(env.PATH ?? ""), cwd) };
581270
+ const presets = (opts.presets ?? await knownPresets()).filter((p) => p.id !== "8gent");
581271
+ return {
581272
+ version: String(import_package.default.version),
581273
+ host: hostLabel(android, opts.platform ?? process.platform, opts.arch ?? process.arch),
581274
+ android,
581275
+ model: safeModelLabel(opts.model),
581276
+ provider: opts.provider,
581277
+ presetIds: presets.map((p) => p.id),
581278
+ installed: presets.filter((p) => findOnPath(p.command, lookupEnv) !== null).map((p) => p.id),
581279
+ folder: cwd === home ? "~" : basename51(cwd) || "/",
581280
+ gitRepo: inGitRepo(cwd)
581281
+ };
581282
+ }
581283
+ function listWords(items) {
581284
+ if (items.length <= 1)
581285
+ return items.join("");
581286
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
581287
+ }
581288
+ function buildWelcome(f) {
581289
+ const where = f.android ? "phone" : "machine";
581290
+ const lines = [`Hi, I'm 8gent v${f.version} on ${f.host}, using ${f.model} via ${f.provider}.`];
581291
+ if (f.installed.length > 0) {
581292
+ 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.`);
581293
+ } else {
581294
+ lines.push(`I checked PATH for ${listWords(f.presetIds)}: none installed, so it's just me here.`);
581295
+ }
581296
+ 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.`);
581297
+ const next = [];
581298
+ if (f.installed.length > 0)
581299
+ next.push(`run ${f.installed[0]}`);
581300
+ next.push(f.gitRepo ? "what changed in the last commit" : "make a folder called demo with a hello web page");
581301
+ next.push("what can you do");
581302
+ lines.push("Try:", ...next.map((n) => ` "${n}"`));
581303
+ return lines.join(`
581304
+ `);
581305
+ }
581306
+ var import_package;
581307
+ var init_welcome = __esm(() => {
581308
+ init_termux();
581309
+ import_package = __toESM(require_package(), 1);
581310
+ });
581311
+
580460
581312
  // packages/eight/failover-provider-config.ts
580461
581313
  function providerConfigForStep(session, step) {
580462
581314
  if (step.provider === session.name) {
@@ -580978,8 +581830,8 @@ var init_local_turn_error = __esm(() => {
580978
581830
  });
580979
581831
 
580980
581832
  // packages/eight/output-gate.ts
580981
- import { existsSync as existsSync105 } from "node:fs";
580982
- import { isAbsolute as isAbsolute50, relative as relative34, resolve as resolve72 } from "node:path";
581833
+ import { existsSync as existsSync108 } from "node:fs";
581834
+ import { isAbsolute as isAbsolute51, relative as relative35, resolve as resolve73 } from "node:path";
580983
581835
  function isOutputGateEnabled(env = process.env, option) {
580984
581836
  if (option !== undefined)
580985
581837
  return option;
@@ -581011,10 +581863,10 @@ class OutputGate {
581011
581863
  events = [];
581012
581864
  constructor(task, cwd, enabled) {
581013
581865
  this.cwd = cwd;
581014
- this.required = !enabled ? [] : namedOutputs(task).map((shown) => ({ shown, abs: resolve72(cwd, shown) })).filter(({ abs }) => !relative34(cwd, abs).startsWith("..") && !isAbsolute50(relative34(cwd, abs))).filter(({ abs }) => !existsSync105(abs));
581866
+ this.required = !enabled ? [] : namedOutputs(task).map((shown) => ({ shown, abs: resolve73(cwd, shown) })).filter(({ abs }) => !relative35(cwd, abs).startsWith("..") && !isAbsolute51(relative35(cwd, abs))).filter(({ abs }) => !existsSync108(abs));
581015
581867
  }
581016
581868
  missing() {
581017
- return this.required.filter(({ abs }) => !existsSync105(abs)).map(({ shown }) => shown);
581869
+ return this.required.filter(({ abs }) => !existsSync108(abs)).map(({ shown }) => shown);
581018
581870
  }
581019
581871
  check(reply) {
581020
581872
  const missing = this.missing();
@@ -581052,7 +581904,7 @@ var init_output_gate = __esm(() => {
581052
581904
  function stripPunctuation(s) {
581053
581905
  return s.replace(/[.,;:!?]+$/g, "").trim();
581054
581906
  }
581055
- function isGreeting(message) {
581907
+ function isGreeting2(message) {
581056
581908
  const stripped = stripPunctuation(message.toLowerCase()).trim();
581057
581909
  if (stripped.length === 0)
581058
581910
  return true;
@@ -581073,7 +581925,7 @@ class PreToolRouter {
581073
581925
  classify(userMessage, _projectContext) {
581074
581926
  const msg = userMessage ?? "";
581075
581927
  const trimmed = msg.trim();
581076
- if (trimmed.length === 0 || isGreeting(trimmed)) {
581928
+ if (trimmed.length === 0 || isGreeting2(trimmed)) {
581077
581929
  return {
581078
581930
  strategy: "none",
581079
581931
  args: {},
@@ -581345,12 +582197,12 @@ __export(exports_system_prompt, {
581345
582197
  styleHasGuide: () => styleHasGuide,
581346
582198
  workingProjectNote: () => workingProjectNote
581347
582199
  });
581348
- import { readFileSync as readFileSync97 } from "node:fs";
581349
- import { basename as basename51, join as join155 } from "node:path";
582200
+ import { existsSync as existsSync109, readFileSync as readFileSync98 } from "node:fs";
582201
+ import { basename as basename52, join as join160, resolve as resolve74 } from "node:path";
581350
582202
  function buildBoardContextSegment(path = BOARD_CONTEXT_PATH) {
581351
582203
  let text;
581352
582204
  try {
581353
- text = readFileSync97(path, "utf-8").trim();
582205
+ text = readFileSync98(path, "utf-8").trim();
581354
582206
  } catch {
581355
582207
  return "";
581356
582208
  }
@@ -581377,8 +582229,20 @@ function communicationStyleLine(style) {
581377
582229
  function styleHasGuide(style) {
581378
582230
  return isCommunicationStyle(style) && STYLE_GUIDE[style] !== "";
581379
582231
  }
582232
+ function isPlainHome(dir, home = resolveHome()) {
582233
+ try {
582234
+ return home !== "" && resolve74(dir) === resolve74(home) && !existsSync109(join160(dir, ".git"));
582235
+ } catch {
582236
+ return false;
582237
+ }
582238
+ }
581380
582239
  function workingProjectNote(dir) {
581381
- const name = basename51(dir) || dir;
582240
+ const name = basename52(dir) || dir;
582241
+ if (isPlainHome(dir)) {
582242
+ return `
582243
+
582244
+ **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.**`;
582245
+ }
581382
582246
  if (name === "8gent-code") {
581383
582247
  return `
581384
582248
 
@@ -581722,7 +582586,7 @@ var init_system_prompt = __esm(() => {
581722
582586
  init_tool_registry();
581723
582587
  init_soul_layers();
581724
582588
  IDENTITY_SEGMENT = composeSoulPrompt("owner");
581725
- BOARD_CONTEXT_PATH = join155(resolveHome(), ".8gent", "board-context.md");
582589
+ BOARD_CONTEXT_PATH = join160(resolveHome(), ".8gent", "board-context.md");
581726
582590
  ACTION_FIRST_STYLE = [
581727
582591
  "Shape every reply so the reader can act without rereading:",
581728
582592
  ACTION_FIRST_PRECEDENCE,
@@ -582076,11 +582940,11 @@ var init_toolshed_bridge = __esm(() => {
582076
582940
  });
582077
582941
 
582078
582942
  // packages/ai/embedding-cache.ts
582079
- import { join as join156 } from "node:path";
582943
+ import { join as join161 } from "node:path";
582080
582944
  var CACHE_DIR, CACHE_PATH;
582081
582945
  var init_embedding_cache = __esm(() => {
582082
- CACHE_DIR = join156(process.env.HOME || "~", ".8gent");
582083
- CACHE_PATH = join156(CACHE_DIR, "embedding-cache.json");
582946
+ CACHE_DIR = join161(process.env.HOME || "~", ".8gent");
582947
+ CACHE_PATH = join161(CACHE_DIR, "embedding-cache.json");
582084
582948
  });
582085
582949
 
582086
582950
  // packages/ai/edge-inference.ts
@@ -582359,20 +583223,20 @@ var init_quick_answer = __esm(() => {
582359
583223
  });
582360
583224
 
582361
583225
  // packages/eight/session-scratch.ts
582362
- import { lstatSync as lstatSync30, mkdirSync as mkdirSync74, rmSync as rmSync14 } from "node:fs";
583226
+ import { lstatSync as lstatSync30, mkdirSync as mkdirSync75, rmSync as rmSync15 } from "node:fs";
582363
583227
  import { tmpdir as tmpdir19 } from "node:os";
582364
- import { join as join157 } from "node:path";
583228
+ import { join as join162 } from "node:path";
582365
583229
  function removeAllAtExit() {
582366
583230
  for (const dir of [...live2])
582367
583231
  removeSessionScratch(dir);
582368
583232
  }
582369
583233
  function sessionScratchPath(sessionId) {
582370
- return join157(tmpdir19(), `8gent-${sessionId}`);
583234
+ return join162(tmpdir19(), `8gent-${sessionId}`);
582371
583235
  }
582372
583236
  function createSessionScratch(sessionId) {
582373
583237
  const dir = sessionScratchPath(sessionId);
582374
583238
  try {
582375
- mkdirSync74(dir, { mode: 448 });
583239
+ mkdirSync75(dir, { mode: 448 });
582376
583240
  live2.add(dir);
582377
583241
  if (!exitHandlerSet) {
582378
583242
  exitHandlerSet = true;
@@ -582393,7 +583257,7 @@ function removeSessionScratch(dir) {
582393
583257
  return;
582394
583258
  if (typeof process.getuid === "function" && st.uid !== process.getuid())
582395
583259
  return;
582396
- rmSync14(dir, { recursive: true, force: true });
583260
+ rmSync15(dir, { recursive: true, force: true });
582397
583261
  } catch {}
582398
583262
  }
582399
583263
  function sessionScratchNote(dir) {
@@ -590272,19 +591136,19 @@ var init_opportunity_scanner = () => {};
590272
591136
  var init_capability_matcher = () => {};
590273
591137
 
590274
591138
  // packages/proactive/work-tracker.ts
590275
- import { join as join160 } from "node:path";
591139
+ import { join as join165 } from "node:path";
590276
591140
  var DATA_DIR3, STORE_PATH;
590277
591141
  var init_work_tracker = __esm(() => {
590278
- DATA_DIR3 = join160(process.env.HOME || "~", ".8gent");
590279
- STORE_PATH = join160(DATA_DIR3, "opportunities.json");
591142
+ DATA_DIR3 = join165(process.env.HOME || "~", ".8gent");
591143
+ STORE_PATH = join165(DATA_DIR3, "opportunities.json");
590280
591144
  });
590281
591145
 
590282
591146
  // packages/proactive/revenue-engine.ts
590283
- import { join as join161 } from "node:path";
591147
+ import { join as join166 } from "node:path";
590284
591148
  var DATA_DIR4, STORE_PATH2;
590285
591149
  var init_revenue_engine = __esm(() => {
590286
- DATA_DIR4 = join161(process.env.HOME || "~", ".8gent");
590287
- STORE_PATH2 = join161(DATA_DIR4, "revenue.json");
591150
+ DATA_DIR4 = join166(process.env.HOME || "~", ".8gent");
591151
+ STORE_PATH2 = join166(DATA_DIR4, "revenue.json");
590288
591152
  });
590289
591153
 
590290
591154
  // packages/proactive/deliverable-generator.ts
@@ -590297,11 +591161,11 @@ var init_client_outreach = () => {};
590297
591161
  var init_content_packaging = () => {};
590298
591162
 
590299
591163
  // packages/tools/browser/cache.ts
590300
- import { homedir as homedir80 } from "node:os";
590301
- import { join as join162 } from "node:path";
591164
+ import { homedir as homedir81 } from "node:os";
591165
+ import { join as join167 } from "node:path";
590302
591166
  var CACHE_DIR2, TTL_MS, MAX_CACHE_BYTES;
590303
591167
  var init_cache2 = __esm(() => {
590304
- CACHE_DIR2 = join162(process.env.EIGHT_DATA_DIR || join162(homedir80(), ".8gent"), "browser-cache");
591168
+ CACHE_DIR2 = join167(process.env.EIGHT_DATA_DIR || join167(homedir81(), ".8gent"), "browser-cache");
590305
591169
  TTL_MS = 60 * 60 * 1000;
590306
591170
  MAX_CACHE_BYTES = 100 * 1024 * 1024;
590307
591171
  });
@@ -591044,7 +591908,7 @@ var init_voice3 = __esm(() => {
591044
591908
 
591045
591909
  // packages/eight/observation-pack.ts
591046
591910
  import { createHash as createHash21 } from "node:crypto";
591047
- import { readFileSync as readFileSync99 } from "node:fs";
591911
+ import { readFileSync as readFileSync100 } from "node:fs";
591048
591912
  function observationPackEnabled(env = process.env) {
591049
591913
  return env.EIGHT_OBSERVATION_PACK === "1";
591050
591914
  }
@@ -591064,7 +591928,7 @@ class ObservationStore {
591064
591928
  const path = this.paths.get(handle);
591065
591929
  if (!path)
591066
591930
  throw new Error(`no saved output with handle "${handle}" in this session`);
591067
- return readFileSync99(path, "utf8").slice(offset, offset + limit);
591931
+ return readFileSync100(path, "utf8").slice(offset, offset + limit);
591068
591932
  }
591069
591933
  size(handle) {
591070
591934
  return this.read(handle).length;
@@ -592073,6 +592937,7 @@ class Agent {
592073
592937
  steerQueue = [];
592074
592938
  activeTurn = null;
592075
592939
  turnCounter = 0;
592940
+ welcomed = false;
592076
592941
  stopRequested = false;
592077
592942
  orchestratorBus;
592078
592943
  toolRegistry;
@@ -592540,14 +593405,15 @@ ${LOCAL_LOGIN_NOTE}`;
592540
593405
  instructions,
592541
593406
  localCoreTools,
592542
593407
  chatStartTime,
592543
- textForAgent
593408
+ textForAgent,
593409
+ smallTalk = false
592544
593410
  } = opts;
592545
593411
  const signal = this.armController().signal;
592546
593412
  const allow = new Set(localCoreTools);
592547
593413
  const specs = toolDefsToSpecs(this.executor.getToolDefinitions(), allow);
592548
593414
  let stepNumber = 0;
592549
593415
  let breakerStop = null;
592550
- if (this.extensionToolsAllowed()) {
593416
+ if (this.extensionToolsAllowed() && !smallTalk) {
592551
593417
  for (const t of this.extensionHost.listTools()) {
592552
593418
  if (this.modeAllows(t.name)) {
592553
593419
  specs.push({ name: t.name, description: t.description, parameters: t.parameters });
@@ -593111,6 +593977,21 @@ ${LOCAL_LOGIN_NOTE}`;
593111
593977
  console.log(`[turn-review] ${line}`);
593112
593978
  }
593113
593979
  }
593980
+ async greetingWelcome(text) {
593981
+ if (this.welcomed || this.config.headless || this.agentDepth > 0 || !isGreeting(text))
593982
+ return null;
593983
+ try {
593984
+ const facts = await gatherWelcomeFacts({
593985
+ model: String(this.config.model),
593986
+ provider: String(this.config.runtime),
593987
+ cwd: this.config.workingDirectory || process.cwd()
593988
+ });
593989
+ this.welcomed = true;
593990
+ return buildWelcome(facts);
593991
+ } catch {
593992
+ return null;
593993
+ }
593994
+ }
593114
593995
  async runTurn(userMessage, imageBase64, imageMimeType, imageOriginal) {
593115
593996
  await this.extensionsReady;
593116
593997
  this.loopDetector.reset();
@@ -593125,6 +594006,17 @@ ${LOCAL_LOGIN_NOTE}`;
593125
594006
  this.turnGitBaseline = asksForCommit(userMessage) ? gitSnapshot(this.config.workingDirectory || process.cwd()) : null;
593126
594007
  this.executor.beginTurn(userMessage);
593127
594008
  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);
594009
+ const smallTalkTurn = isSmallTalk(textForAgent) && !imageBase64 && this.config.agentScope !== "__table__";
594010
+ if (smallTalkTurn) {
594011
+ const welcome = await this.greetingWelcome(textForAgent);
594012
+ if (welcome) {
594013
+ this.messageHistory.push({ role: "user", content: textForAgent });
594014
+ this.messageHistory.push({ role: "assistant", content: welcome });
594015
+ this.sessionWriter.writeUserMessage(textForAgent);
594016
+ return welcome;
594017
+ }
594018
+ this.messageHistory.push({ role: "user", content: harnessNote(SMALL_TALK_NOTE) });
594019
+ }
593128
594020
  let visionId = null;
593129
594021
  let visionFailed = false;
593130
594022
  if (imageBase64 && await this.promptImageSeen()) {
@@ -593195,7 +594087,7 @@ ${result.description.slice(0, 200)}${result.description.length > 200 ? "..." : "
593195
594087
  }
593196
594088
  }
593197
594089
  const quickPrompt = quickLaneEnabled() && classifyPrompt(textForAgent) === "quick";
593198
- if (needsClarification(textForAgent) && !imageBase64 && !quickPrompt && !isNoToolScope(this.config.agentScope)) {
594090
+ if (needsClarification(textForAgent) && !imageBase64 && !quickPrompt && !smallTalkTurn && !isNoToolScope(this.config.agentScope)) {
593199
594091
  this.proactiveGatherer = createGatherer(textForAgent);
593200
594092
  const question = this.proactiveGatherer.getCurrentQuestion();
593201
594093
  if (question) {
@@ -593217,7 +594109,7 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
593217
594109
  this.kanban.moveTask(this.currentBmadTask.id, "ready");
593218
594110
  this.kanban.moveTask(this.currentBmadTask.id, "in_progress");
593219
594111
  }
593220
- if (!this.proactiveGatherer && !quickPrompt) {
594112
+ if (!this.proactiveGatherer && !quickPrompt && !smallTalkTurn) {
593221
594113
  await this.tryRunPreToolRouter(textForAgent);
593222
594114
  }
593223
594115
  const needsPlanningGate2 = !isVerbatimScope(this.config.agentScope) && needsPlanningGate(textForAgent);
@@ -593362,9 +594254,9 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
593362
594254
  "search_symbols",
593363
594255
  "recall"
593364
594256
  ]);
593365
- const scopedTools = isNoToolScope(this.config.agentScope) ? {} : this.config.agentScope === "__table__" ? Object.fromEntries(Object.entries(providerTools).filter(([k]) => TABLE_SESSION_TOOLS.has(k))) : providerTools;
594257
+ const scopedTools = isNoToolScope(this.config.agentScope) || smallTalkTurn ? {} : this.config.agentScope === "__table__" ? Object.fromEntries(Object.entries(providerTools).filter(([k]) => TABLE_SESSION_TOOLS.has(k))) : providerTools;
593366
594258
  const withExtensions = { ...scopedTools };
593367
- if (this.extensionToolsAllowed()) {
594259
+ if (this.extensionToolsAllowed() && !smallTalkTurn) {
593368
594260
  for (const t of this.extensionHost.listTools()) {
593369
594261
  if (t.name in withExtensions)
593370
594262
  continue;
@@ -593376,7 +594268,7 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
593376
594268
  }
593377
594269
  }
593378
594270
  const effectiveTools = this.modeFilter(withExtensions);
593379
- const textToolAllowlist = (isNoToolScope(this.config.agentScope) ? [] : this.config.agentScope === "__table__" ? [...TABLE_SESSION_TOOLS] : localCoreTools).filter((name) => getModeManager().isToolAllowed(name));
594271
+ const textToolAllowlist = (isNoToolScope(this.config.agentScope) || smallTalkTurn ? [] : this.config.agentScope === "__table__" ? [...TABLE_SESSION_TOOLS] : localCoreTools).filter((name) => getModeManager().isToolAllowed(name));
593380
594272
  const harness = this.harnessProfileFor(providerConfig.model, isLocalProvider);
593381
594273
  Object.assign(this.runtimeParams, {
593382
594274
  voiceChatActive: getRuntimeParams().voiceChatActive,
@@ -593401,7 +594293,8 @@ Ask the user naturally; don't mention this instruction. After they answer, proce
593401
594293
  `),
593402
594294
  localCoreTools: textToolAllowlist,
593403
594295
  chatStartTime,
593404
- textForAgent
594296
+ textForAgent,
594297
+ smallTalk: smallTalkTurn
593405
594298
  });
593406
594299
  await this.readServerContextWindow();
593407
594300
  await this.compactIfNeeded(providerConfig);
@@ -594086,7 +594979,7 @@ ${summary || " (no provider errors recorded)"}`);
594086
594979
  };
594087
594980
  }
594088
594981
  const blankNative = gatedNative.content.trim() === "";
594089
- const content = blankNative ? emptyReplyNote(this.turnToolLedger.length) : gatedNative.content;
594982
+ const content = blankNative ? emptyReplyNote(this.turnToolLedger.length, this.turnToolLedger) : gatedNative.content;
594090
594983
  const flavor = voice.getFlavor("complete");
594091
594984
  const flavoredContent = fixCompletedLine(gatedNative.violated || isVerbatimScope(this.config.agentScope) ? content : flavorResponse(content, flavor)).text;
594092
594985
  this.messageHistory.push({ role: "assistant", content: flavoredContent });
@@ -594801,7 +595694,9 @@ var init_agent2 = __esm(() => {
594801
595694
  init_modes();
594802
595695
  init_planning_gate();
594803
595696
  init_registry();
595697
+ init_small_talk();
594804
595698
  init_verify_gate();
595699
+ init_welcome();
594805
595700
  init_local_tool_scope();
594806
595701
  init_local_turn_error();
594807
595702
  init_output_gate();
@@ -595295,9 +596190,9 @@ var init_install = __esm(() => {
595295
596190
  });
595296
596191
 
595297
596192
  // packages/eight/source-update.ts
595298
- import { execFile as execFile7 } from "node:child_process";
595299
- import { existsSync as existsSync110, realpathSync as realpathSync36, statSync as statSync37 } from "node:fs";
595300
- import { homedir as homedir84 } from "node:os";
596193
+ import { execFile as execFile9 } from "node:child_process";
596194
+ import { existsSync as existsSync114, realpathSync as realpathSync36, statSync as statSync37 } from "node:fs";
596195
+ import { homedir as homedir85 } from "node:os";
595301
596196
  import * as nodePath from "node:path";
595302
596197
  function scrubbedEnv(base) {
595303
596198
  const out = {};
@@ -595395,7 +596290,7 @@ function modulePathToFs(modulePath) {
595395
596290
  }
595396
596291
  return s;
595397
596292
  }
595398
- function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync110(lib(platform).join(d, ".git"))) {
596293
+ function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync114(lib(platform).join(d, ".git"))) {
595399
596294
  const p = lib(platform);
595400
596295
  const fsPath = modulePathToFs(modulePath);
595401
596296
  let dir = p.dirname(platform === "win32" ? fsPath.replace(/\//g, "\\") : fsPath);
@@ -595411,7 +596306,7 @@ function findCheckoutRoot(modulePath, platform, hasGit = (d) => existsSync110(li
595411
596306
  }
595412
596307
  function findBrowserCheckout(o) {
595413
596308
  const p = lib(o.platform);
595414
- const hasGit = o.hasGit ?? ((d) => existsSync110(p.join(d, ".git")));
596309
+ const hasGit = o.hasGit ?? ((d) => existsSync114(p.join(d, ".git")));
595415
596310
  const looked = [];
595416
596311
  let note;
595417
596312
  const cands = [];
@@ -595737,7 +596632,7 @@ async function runSourceUpdate(deps, opts, report) {
595737
596632
  git,
595738
596633
  bun: bun ?? "bun",
595739
596634
  platform,
595740
- fileExists: deps.fileExists ?? ((f) => existsSync110(f)),
596635
+ fileExists: deps.fileExists ?? ((f) => existsSync114(f)),
595741
596636
  now: deps.now ?? (() => new Date),
595742
596637
  baseEnv: env,
595743
596638
  forgeHost: env.EIGHT_FORGE_HOST,
@@ -595761,7 +596656,7 @@ async function runSourceUpdate(deps, opts, report) {
595761
596656
  report?.({ updated: true, to: short2(code.to ?? "") });
595762
596657
  const found = findBrowserCheckout({
595763
596658
  env,
595764
- home: deps.home ?? homedir84(),
596659
+ home: deps.home ?? homedir85(),
595765
596660
  platform,
595766
596661
  codeRoot: root,
595767
596662
  hasGit: deps.hasGit
@@ -595787,7 +596682,7 @@ async function runSourceUpdate(deps, opts, report) {
595787
596682
  return bad ? 1 : 0;
595788
596683
  }
595789
596684
  var T_LOCAL = 30000, T_FETCH = 120000, T_INSTALL = 600000, ENV_KEEP, defaultExec = (file, args, opts) => new Promise((resolve) => {
595790
- execFile7(file, args, {
596685
+ execFile9(file, args, {
595791
596686
  cwd: opts.cwd,
595792
596687
  timeout: opts.timeoutMs,
595793
596688
  maxBuffer: 33554432,
@@ -596050,16 +596945,16 @@ var init_cli_maintenance = __esm(() => {
596050
596945
  });
596051
596946
 
596052
596947
  // packages/eight/update-resume.ts
596053
- import { mkdirSync as mkdirSync78, readFileSync as readFileSync103, rmSync as rmSync15, writeFileSync as writeFileSync61 } from "node:fs";
596054
- import { join as join167 } from "node:path";
596948
+ import { mkdirSync as mkdirSync79, readFileSync as readFileSync104, rmSync as rmSync16, writeFileSync as writeFileSync61 } from "node:fs";
596949
+ import { join as join172 } from "node:path";
596055
596950
  function dataDir4(env = process.env) {
596056
- return join167(resolveHome(env), ".8gent");
596951
+ return join172(resolveHome(env), ".8gent");
596057
596952
  }
596058
596953
  function writeResumeRequest(req, env = process.env) {
596059
- mkdirSync78(dataDir4(env), { recursive: true });
596954
+ mkdirSync79(dataDir4(env), { recursive: true });
596060
596955
  writeFileSync61(file(env), JSON.stringify(req), "utf8");
596061
596956
  }
596062
- var RELAUNCH_EXIT_CODE = 75, file = (env) => join167(dataDir4(env), "update-resume.json");
596957
+ var RELAUNCH_EXIT_CODE = 75, file = (env) => join172(dataDir4(env), "update-resume.json");
596063
596958
  var init_update_resume = __esm(() => {
596064
596959
  init_home();
596065
596960
  });
@@ -596071,8 +596966,8 @@ __export(exports_update_notice, {
596071
596966
  maybeUpdateNotice: () => maybeUpdateNotice,
596072
596967
  realNoticeDeps: () => realNoticeDeps
596073
596968
  });
596074
- import { mkdirSync as mkdirSync79, readFileSync as readFileSync104, writeFileSync as writeFileSync62 } from "node:fs";
596075
- import { join as join168 } from "node:path";
596969
+ import { mkdirSync as mkdirSync80, readFileSync as readFileSync105, writeFileSync as writeFileSync62 } from "node:fs";
596970
+ import { join as join173 } from "node:path";
596076
596971
  function noticeText(from, to) {
596077
596972
  return `Update available (${from} -> ${to}). Run 8gent update, or type /update.`;
596078
596973
  }
@@ -596086,7 +596981,7 @@ function updateCheckDisabled(env, settingsFile) {
596086
596981
  return true;
596087
596982
  if (settingsFile) {
596088
596983
  try {
596089
- const s = JSON.parse(readFileSync104(settingsFile, "utf8"));
596984
+ const s = JSON.parse(readFileSync105(settingsFile, "utf8"));
596090
596985
  if (s.updateCheck === false)
596091
596986
  return true;
596092
596987
  } catch {}
@@ -596123,7 +597018,7 @@ async function maybeUpdateNotice(d) {
596123
597018
  function realNoticeDeps(o) {
596124
597019
  const env = o.env ?? process.env;
596125
597020
  const platform = o.platform ?? process.platform;
596126
- const stateFile = join168(dataDir4(env), "update-check.json");
597021
+ const stateFile = join173(dataDir4(env), "update-check.json");
596127
597022
  const channel = detectChannel({ modulePath: o.modulePath, embedded: o.embedded });
596128
597023
  const srcDeps = { modulePath: o.modulePath, env, platform, exec: o.exec };
596129
597024
  return {
@@ -596131,7 +597026,7 @@ function realNoticeDeps(o) {
596131
597026
  disabled: () => !isUpdateCheckEnabled(env) || updateCheckDisabled(env, o.settingsFile),
596132
597027
  readState: () => {
596133
597028
  try {
596134
- const s = JSON.parse(readFileSync104(stateFile, "utf8"));
597029
+ const s = JSON.parse(readFileSync105(stateFile, "utf8"));
596135
597030
  return typeof s.checkedAt === "number" ? s : null;
596136
597031
  } catch {
596137
597032
  return null;
@@ -596139,7 +597034,7 @@ function realNoticeDeps(o) {
596139
597034
  },
596140
597035
  writeState: (s) => {
596141
597036
  try {
596142
- mkdirSync79(dataDir4(env), { recursive: true });
597037
+ mkdirSync80(dataDir4(env), { recursive: true });
596143
597038
  writeFileSync62(stateFile, JSON.stringify(s), "utf8");
596144
597039
  } catch {}
596145
597040
  },
@@ -596183,7 +597078,7 @@ var init_update_notice = __esm(() => {
596183
597078
  });
596184
597079
 
596185
597080
  // packages/voice/cloud-transcriber.ts
596186
- import { existsSync as existsSync111 } from "node:fs";
597081
+ import { existsSync as existsSync115 } from "node:fs";
596187
597082
  async function transcribeCloud(wavPath, options) {
596188
597083
  const {
596189
597084
  apiKey,
@@ -596195,7 +597090,7 @@ async function transcribeCloud(wavPath, options) {
596195
597090
  if (!apiKey) {
596196
597091
  throw new Error("OpenAI API key is required for cloud transcription. Set OPENAI_API_KEY environment variable.");
596197
597092
  }
596198
- if (!existsSync111(wavPath)) {
597093
+ if (!existsSync115(wavPath)) {
596199
597094
  throw new Error(`WAV file not found: ${wavPath}`);
596200
597095
  }
596201
597096
  const startTime = Date.now();
@@ -596299,11 +597194,11 @@ var init_types4 = __esm(() => {
596299
597194
 
596300
597195
  // packages/voice/model-manager.ts
596301
597196
  import { EventEmitter as EventEmitter15 } from "node:events";
596302
- import { existsSync as existsSync112, mkdirSync as mkdirSync80, realpathSync as realpathSync37, unlinkSync as unlinkSync3 } from "node:fs";
596303
- import { homedir as homedir85 } from "node:os";
596304
- import { join as join169, sep as sep30 } from "node:path";
597197
+ import { existsSync as existsSync116, mkdirSync as mkdirSync81, realpathSync as realpathSync37, unlinkSync as unlinkSync4 } from "node:fs";
597198
+ import { homedir as homedir86 } from "node:os";
597199
+ import { join as join174, sep as sep30 } from "node:path";
596305
597200
  function resolveWhisperModel(opts = {}) {
596306
- const exists = opts.exists ?? existsSync112;
597201
+ const exists = opts.exists ?? existsSync116;
596307
597202
  const dirs = [
596308
597203
  resolveModelsPath(opts.modelsDir ?? "~/.8gent/models/whisper"),
596309
597204
  ...opts.extraDirs ?? []
@@ -596313,7 +597208,7 @@ function resolveWhisperModel(opts = {}) {
596313
597208
  for (const m of WHISPER_MODEL_RANK) {
596314
597209
  if (!english && m.filename.endsWith(".en.bin"))
596315
597210
  continue;
596316
- const path = join169(dir, m.filename);
597211
+ const path = join174(dir, m.filename);
596317
597212
  if (!exists(path))
596318
597213
  continue;
596319
597214
  if (!opts.exists && !isInside2(dir, path))
@@ -596342,7 +597237,7 @@ function missingWhisperModelMessage(modelsDir = "~/.8gent/models/whisper") {
596342
597237
  }
596343
597238
  function resolveModelsPath(configPath) {
596344
597239
  if (configPath.startsWith("~")) {
596345
- return join169(homedir85(), configPath.slice(1));
597240
+ return join174(homedir86(), configPath.slice(1));
596346
597241
  }
596347
597242
  return configPath;
596348
597243
  }
@@ -596364,8 +597259,8 @@ var init_model_manager = __esm(() => {
596364
597259
  this.modelsDir = resolveModelsPath(modelsPath);
596365
597260
  }
596366
597261
  ensureModelsDir() {
596367
- if (!existsSync112(this.modelsDir)) {
596368
- mkdirSync80(this.modelsDir, { recursive: true });
597262
+ if (!existsSync116(this.modelsDir)) {
597263
+ mkdirSync81(this.modelsDir, { recursive: true });
596369
597264
  }
596370
597265
  }
596371
597266
  getModelsDir() {
@@ -596375,10 +597270,10 @@ var init_model_manager = __esm(() => {
596375
597270
  return WHISPER_MODELS[name];
596376
597271
  }
596377
597272
  getModelPath(name) {
596378
- return join169(this.modelsDir, WHISPER_MODELS[name].filename);
597273
+ return join174(this.modelsDir, WHISPER_MODELS[name].filename);
596379
597274
  }
596380
597275
  isModelDownloaded(name) {
596381
- return existsSync112(this.getModelPath(name));
597276
+ return existsSync116(this.getModelPath(name));
596382
597277
  }
596383
597278
  listModels() {
596384
597279
  return Object.keys(WHISPER_MODELS).map((name) => ({
@@ -596393,7 +597288,7 @@ var init_model_manager = __esm(() => {
596393
597288
  async downloadModel(name) {
596394
597289
  const info = WHISPER_MODELS[name];
596395
597290
  const destPath = this.getModelPath(name);
596396
- if (existsSync112(destPath)) {
597291
+ if (existsSync116(destPath)) {
596397
597292
  return destPath;
596398
597293
  }
596399
597294
  this.ensureModelsDir();
@@ -596444,9 +597339,9 @@ var init_model_manager = __esm(() => {
596444
597339
  this.emit("download-complete", { model: name, path: destPath });
596445
597340
  return destPath;
596446
597341
  } catch (err) {
596447
- if (existsSync112(destPath)) {
597342
+ if (existsSync116(destPath)) {
596448
597343
  try {
596449
- unlinkSync3(destPath);
597344
+ unlinkSync4(destPath);
596450
597345
  } catch {}
596451
597346
  }
596452
597347
  const message = err instanceof Error ? err.message : "Download failed";
@@ -596456,8 +597351,8 @@ var init_model_manager = __esm(() => {
596456
597351
  }
596457
597352
  deleteModel(name) {
596458
597353
  const path = this.getModelPath(name);
596459
- if (existsSync112(path)) {
596460
- unlinkSync3(path);
597354
+ if (existsSync116(path)) {
597355
+ unlinkSync4(path);
596461
597356
  return true;
596462
597357
  }
596463
597358
  return false;
@@ -596467,7 +597362,7 @@ var init_model_manager = __esm(() => {
596467
597362
  let totalBytes = 0;
596468
597363
  for (const name of Object.keys(WHISPER_MODELS)) {
596469
597364
  const path = this.getModelPath(name);
596470
- if (existsSync112(path)) {
597365
+ if (existsSync116(path)) {
596471
597366
  try {
596472
597367
  const stat = Bun.file(path);
596473
597368
  models[name] = stat.size;
@@ -596480,61 +597375,11 @@ var init_model_manager = __esm(() => {
596480
597375
  };
596481
597376
  });
596482
597377
 
596483
- // packages/voice/termux.ts
596484
- import { constants as constants15, accessSync as accessSync5, readFileSync as readFileSync105 } from "node:fs";
596485
- import { delimiter as delimiter8, join as join170 } from "node:path";
596486
- function isAndroidHost(platform = process.platform, env = process.env) {
596487
- return platform === "android" || !!env.TERMUX_VERSION || String(env.PREFIX ?? "").includes("/com.termux/");
596488
- }
596489
- function findOnPath(name, env = process.env) {
596490
- for (const dir of String(env.PATH ?? "").split(delimiter8)) {
596491
- if (!dir)
596492
- continue;
596493
- const candidate = join170(dir, name);
596494
- try {
596495
- accessSync5(candidate, constants15.X_OK);
596496
- return candidate;
596497
- } catch {}
596498
- }
596499
- return null;
596500
- }
596501
- function voiceOutputOn(env = process.env, readSettings = () => {
596502
- const home = env.EIGHT_HOME || join170(env.HOME ?? "", ".8gent");
596503
- return readFileSync105(join170(home, "settings.json"), "utf8");
596504
- }) {
596505
- if (env.EIGHT_VOICE === "1")
596506
- return true;
596507
- try {
596508
- return JSON.parse(readSettings())?.voice?.outputEnabled === true;
596509
- } catch {
596510
- return false;
596511
- }
596512
- }
596513
- function ctrlRAction(key, input, android = isAndroidHost()) {
596514
- if (!key.ctrl || input !== "r")
596515
- return "ignore";
596516
- return android ? "android-line" : "toggle";
596517
- }
596518
- 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;
596519
- var init_termux = __esm(() => {
596520
- ANDROID_VOICE_LINE_SUBS = [
596521
- "",
596522
- "chat",
596523
- "conversation",
596524
- "talk",
596525
- "record",
596526
- "listen",
596527
- "stt",
596528
- "status",
596529
- "stop"
596530
- ];
596531
- });
596532
-
596533
597378
  // packages/voice/recorder.ts
596534
597379
  import { EventEmitter as EventEmitter16 } from "node:events";
596535
- import { closeSync as closeSync21, existsSync as existsSync113, openSync as openSync21, readSync as readSync13, statSync as statSync38, unlinkSync as unlinkSync4 } from "node:fs";
597380
+ import { closeSync as closeSync21, existsSync as existsSync117, openSync as openSync21, readSync as readSync13, statSync as statSync38, unlinkSync as unlinkSync5 } from "node:fs";
596536
597381
  import { tmpdir as tmpdir20 } from "node:os";
596537
- import { join as join171 } from "node:path";
597382
+ import { join as join175 } from "node:path";
596538
597383
  async function findSoxPath() {
596539
597384
  try {
596540
597385
  const proc = spawn(["which", "rec"], {
@@ -596614,7 +597459,7 @@ var init_recorder = __esm(() => {
596614
597459
  channels: opts.channels ?? 1,
596615
597460
  bitDepth: opts.bitDepth ?? 16,
596616
597461
  maxDurationSeconds: opts.maxDurationSeconds ?? 30,
596617
- outputPath: opts.outputPath ?? join171(tmpdir20(), `8gent-voice-${Date.now()}.wav`)
597462
+ outputPath: opts.outputPath ?? join175(tmpdir20(), `8gent-voice-${Date.now()}.wav`)
596618
597463
  };
596619
597464
  this.outputPath = this.options.outputPath;
596620
597465
  }
@@ -596630,7 +597475,7 @@ var init_recorder = __esm(() => {
596630
597475
  throw new Error(`sox not installed. ${soxCheck.installHint}`);
596631
597476
  }
596632
597477
  this.device = null;
596633
- this.outputPath = this.options.outputPath.includes("8gent-voice-") ? join171(tmpdir20(), `8gent-voice-${Date.now()}.wav`) : this.options.outputPath;
597478
+ this.outputPath = this.options.outputPath.includes("8gent-voice-") ? join175(tmpdir20(), `8gent-voice-${Date.now()}.wav`) : this.options.outputPath;
596634
597479
  const args = [
596635
597480
  "-q",
596636
597481
  "-r",
@@ -596734,8 +597579,8 @@ var init_recorder = __esm(() => {
596734
597579
  }
596735
597580
  static cleanupFile(path) {
596736
597581
  try {
596737
- if (existsSync113(path)) {
596738
- unlinkSync4(path);
597582
+ if (existsSync117(path)) {
597583
+ unlinkSync5(path);
596739
597584
  }
596740
597585
  } catch {}
596741
597586
  }
@@ -596754,15 +597599,15 @@ var init_recorder = __esm(() => {
596754
597599
  });
596755
597600
 
596756
597601
  // packages/voice/transcriber.ts
596757
- import { existsSync as existsSync114 } from "node:fs";
596758
- import { arch as arch2, homedir as homedir86, platform as platform9 } from "node:os";
596759
- import { join as join172 } from "node:path";
597602
+ import { existsSync as existsSync118 } from "node:fs";
597603
+ import { arch as arch2, homedir as homedir87, platform as platform9 } from "node:os";
597604
+ import { join as join176 } from "node:path";
596760
597605
  function getWhisperBinaryPath() {
596761
- return join172(WHISPER_BIN_DIR, WHISPER_BIN_NAME);
597606
+ return join176(WHISPER_BIN_DIR, WHISPER_BIN_NAME);
596762
597607
  }
596763
597608
  async function findWhisperBinary() {
596764
597609
  const managedPath = getWhisperBinaryPath();
596765
- if (existsSync114(managedPath)) {
597610
+ if (existsSync118(managedPath)) {
596766
597611
  return managedPath;
596767
597612
  }
596768
597613
  const names = ["whisper-cpp", "whisper-cli", "whisper", "main"];
@@ -596777,7 +597622,7 @@ async function findWhisperBinary() {
596777
597622
  } catch {}
596778
597623
  }
596779
597624
  for (const brewName of ["/opt/homebrew/bin/whisper-cpp", "/opt/homebrew/bin/whisper-cli"]) {
596780
- if (existsSync114(brewName)) {
597625
+ if (existsSync118(brewName)) {
596781
597626
  return brewName;
596782
597627
  }
596783
597628
  }
@@ -596785,13 +597630,13 @@ async function findWhisperBinary() {
596785
597630
  }
596786
597631
  async function transcribeLocal(wavPath, options) {
596787
597632
  const { binaryPath, modelPath, language = "en", timeoutMs = 30000, threads } = options;
596788
- if (!existsSync114(wavPath)) {
597633
+ if (!existsSync118(wavPath)) {
596789
597634
  throw new Error(`WAV file not found: ${wavPath}`);
596790
597635
  }
596791
- if (!existsSync114(binaryPath)) {
597636
+ if (!existsSync118(binaryPath)) {
596792
597637
  throw new Error(`Whisper binary not found: ${binaryPath}`);
596793
597638
  }
596794
- if (!existsSync114(modelPath)) {
597639
+ if (!existsSync118(modelPath)) {
596795
597640
  throw new Error(`Model file not found: ${modelPath}`);
596796
597641
  }
596797
597642
  const startTime = Date.now();
@@ -596879,13 +597724,13 @@ function extractModelName(modelPath) {
596879
597724
  var WHISPER_BIN_DIR, WHISPER_BIN_NAME = "whisper-cpp";
596880
597725
  var init_transcriber = __esm(() => {
596881
597726
  init_bun_shim();
596882
- WHISPER_BIN_DIR = join172(homedir86(), ".8gent", "bin");
597727
+ WHISPER_BIN_DIR = join176(homedir87(), ".8gent", "bin");
596883
597728
  });
596884
597729
 
596885
597730
  // packages/decide/backends/neudecide.ts
596886
- import { mkdtempSync as mkdtempSync11, rmSync as rmSync16, writeFileSync as writeFileSync63 } from "node:fs";
597731
+ import { mkdtempSync as mkdtempSync12, rmSync as rmSync17, writeFileSync as writeFileSync63 } from "node:fs";
596887
597732
  import { tmpdir as tmpdir22 } from "node:os";
596888
- import { dirname as dirname78, join as join175 } from "node:path";
597733
+ import { dirname as dirname79, join as join179 } from "node:path";
596889
597734
  function wavDurationMs(buf) {
596890
597735
  if (buf.length < 12)
596891
597736
  return null;
@@ -596942,7 +597787,7 @@ function pythonRunner(env = process.env, spawn = Bun.spawn) {
596942
597787
  const proc = spawn([python, "-I", "-c", PY, wavPath, toolsJson], {
596943
597788
  stdout: "pipe",
596944
597789
  stderr: "pipe",
596945
- cwd: dirname78(wavPath),
597790
+ cwd: dirname79(wavPath),
596946
597791
  env: { ...env, HF_HUB_OFFLINE: "1" }
596947
597792
  });
596948
597793
  const kill = () => {
@@ -596980,15 +597825,15 @@ class NeuDecideBackend {
596980
597825
  description: c.description,
596981
597826
  parameters: { type: "object", properties: {} }
596982
597827
  }));
596983
- const dir = mkdtempSync11(join175(tmpdir22(), "8gent-neudecide-"));
596984
- const wavPath = join175(dir, "in.wav");
597828
+ const dir = mkdtempSync12(join179(tmpdir22(), "8gent-neudecide-"));
597829
+ const wavPath = join179(dir, "in.wav");
596985
597830
  const t0 = performance.now();
596986
597831
  let out;
596987
597832
  try {
596988
597833
  writeFileSync63(wavPath, request.audio);
596989
597834
  out = await this.run(wavPath, JSON.stringify(tools), request.signal);
596990
597835
  } finally {
596991
- rmSync16(dir, { recursive: true, force: true });
597836
+ rmSync17(dir, { recursive: true, force: true });
596992
597837
  }
596993
597838
  const latencyMs = performance.now() - t0;
596994
597839
  let calls;
@@ -597066,289 +597911,6 @@ var init_neudecide = __esm(() => {
597066
597911
  ];
597067
597912
  });
597068
597913
 
597069
- // packages/runtime/node-runtime.ts
597070
- import { execFile as execFile8 } from "node:child_process";
597071
- import { existsSync as existsSync119 } from "node:fs";
597072
- import { homedir as homedir88 } from "node:os";
597073
- import { join as join179 } from "node:path";
597074
- import { promisify as promisify6 } from "node:util";
597075
- function parseNodeVersion(raw) {
597076
- const trimmed = raw.trim();
597077
- const match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
597078
- if (!match)
597079
- return null;
597080
- return {
597081
- major: Number.parseInt(match[1], 10),
597082
- minor: Number.parseInt(match[2], 10),
597083
- patch: Number.parseInt(match[3], 10)
597084
- };
597085
- }
597086
- function satisfiesMinVersion(v, min) {
597087
- const minParsed = parseNodeVersion(min);
597088
- if (!minParsed)
597089
- return true;
597090
- if (v.major !== minParsed.major)
597091
- return v.major > minParsed.major;
597092
- if (v.minor !== minParsed.minor)
597093
- return v.minor > minParsed.minor;
597094
- return v.patch >= minParsed.patch;
597095
- }
597096
- function pickPlatformAsset(input) {
597097
- const byArch = SUPPORTED[input.platform];
597098
- if (!byArch) {
597099
- throw new Error(`unsupported platform: ${input.platform}`);
597100
- }
597101
- const asset = byArch[input.arch];
597102
- if (!asset) {
597103
- throw new Error(`unsupported arch: ${input.arch} on ${input.platform}`);
597104
- }
597105
- return asset;
597106
- }
597107
- function buildDownloadUrl(opts) {
597108
- const cleanVersion = opts.version.startsWith("v") ? opts.version.slice(1) : opts.version;
597109
- const v = `v${cleanVersion}`;
597110
- const fileBase = `node-${v}-${opts.asset.os}-${opts.asset.arch}`;
597111
- return `https://nodejs.org/dist/${v}/${fileBase}.${opts.asset.ext}`;
597112
- }
597113
- function defaultRoot() {
597114
- return join179(homedir88(), ".8gent", "runtime");
597115
- }
597116
- function getRuntimeDir(loc) {
597117
- const root = loc.root ?? defaultRoot();
597118
- return join179(root, `node-${loc.version}`);
597119
- }
597120
- function resolvedNodeBinPath(opts) {
597121
- const dir = getRuntimeDir(opts);
597122
- if (opts.platform === "win32") {
597123
- return join179(dir, "node.exe");
597124
- }
597125
- return join179(dir, "bin", "node");
597126
- }
597127
- async function isRuntimeReady(opts) {
597128
- const platform = opts.platform ?? process.platform;
597129
- const bin = resolvedNodeBinPath({
597130
- root: opts.root,
597131
- version: opts.version,
597132
- platform
597133
- });
597134
- if (!existsSync119(bin))
597135
- return false;
597136
- const min = opts.minVersion ?? opts.version;
597137
- try {
597138
- const { stdout } = await execFileAsync4(bin, ["--version"], {
597139
- timeout: 3000
597140
- });
597141
- const parsed = parseNodeVersion(stdout);
597142
- if (!parsed)
597143
- return false;
597144
- return satisfiesMinVersion(parsed, min);
597145
- } catch {
597146
- return false;
597147
- }
597148
- }
597149
- var execFileAsync4, SUPPORTED;
597150
- var init_node_runtime = __esm(() => {
597151
- execFileAsync4 = promisify6(execFile8);
597152
- SUPPORTED = {
597153
- darwin: {
597154
- arm64: { os: "darwin", arch: "arm64", ext: "tar.gz" },
597155
- x64: { os: "darwin", arch: "x64", ext: "tar.gz" }
597156
- },
597157
- linux: {
597158
- arm64: { os: "linux", arch: "arm64", ext: "tar.xz" },
597159
- x64: { os: "linux", arch: "x64", ext: "tar.xz" }
597160
- },
597161
- win32: {
597162
- x64: { os: "win", arch: "x64", ext: "zip" },
597163
- arm64: { os: "win", arch: "arm64", ext: "zip" }
597164
- }
597165
- };
597166
- });
597167
-
597168
- // packages/runtime/installer.ts
597169
- import { spawn as spawn23 } from "node:child_process";
597170
- import {
597171
- createWriteStream,
597172
- existsSync as existsSync120,
597173
- mkdirSync as mkdirSync83,
597174
- mkdtempSync as mkdtempSync12,
597175
- readdirSync as readdirSync27,
597176
- renameSync as renameSync17,
597177
- rmSync as rmSync17
597178
- } from "node:fs";
597179
- import { join as join180 } from "node:path";
597180
- import { Readable as Readable2 } from "node:stream";
597181
- import { pipeline } from "node:stream/promises";
597182
- function planInstall(input) {
597183
- const platform = input.platform ?? process.platform;
597184
- const arch = input.arch ?? process.arch;
597185
- const asset = pickPlatformAsset({ platform, arch });
597186
- const url = buildDownloadUrl({ version: input.version, asset });
597187
- const runtimeDir = getRuntimeDir({ root: input.root, version: input.version });
597188
- const binPath = resolvedNodeBinPath({
597189
- root: input.root,
597190
- version: input.version,
597191
- platform
597192
- });
597193
- return {
597194
- version: input.version,
597195
- url,
597196
- asset,
597197
- runtimeDir,
597198
- binPath,
597199
- archiveExt: asset.ext
597200
- };
597201
- }
597202
- async function installRuntime(input) {
597203
- const plan = planInstall(input);
597204
- const platform = input.platform ?? process.platform;
597205
- const minVersion = input.minVersion ?? input.version;
597206
- const ready = await isRuntimeReady({
597207
- root: input.root,
597208
- version: input.version,
597209
- platform,
597210
- minVersion
597211
- });
597212
- if (ready || input.dryRun) {
597213
- return {
597214
- ...plan,
597215
- action: ready ? "already-ready" : "dry-run"
597216
- };
597217
- }
597218
- const runtimeRoot = plan.runtimeDir.replace(/\/node-[^/]+$/, "");
597219
- mkdirSync83(runtimeRoot, { recursive: true });
597220
- const stagingDir = mkdtempSync12(join180(runtimeRoot, ".dl-"));
597221
- const archivePath = join180(stagingDir, `node.${plan.archiveExt}`);
597222
- try {
597223
- await downloadFile(plan.url, archivePath);
597224
- const extractRoot = join180(stagingDir, "extract");
597225
- mkdirSync83(extractRoot, { recursive: true });
597226
- await extractArchive(archivePath, extractRoot, plan.archiveExt);
597227
- const children = readdirSync27(extractRoot);
597228
- const nodeDir = children.find((n) => n.startsWith("node-"));
597229
- if (!nodeDir) {
597230
- throw new Error(`could not find node-* dir inside ${extractRoot}`);
597231
- }
597232
- const extractedPath = join180(extractRoot, nodeDir);
597233
- if (existsSync120(plan.runtimeDir)) {
597234
- rmSync17(plan.runtimeDir, { recursive: true, force: true });
597235
- }
597236
- renameSync17(extractedPath, plan.runtimeDir);
597237
- } catch (err) {
597238
- return {
597239
- ...plan,
597240
- action: "failed",
597241
- error: err instanceof Error ? err.message : String(err)
597242
- };
597243
- } finally {
597244
- try {
597245
- rmSync17(stagingDir, { recursive: true, force: true });
597246
- } catch {}
597247
- }
597248
- const reReady = await isRuntimeReady({
597249
- root: input.root,
597250
- version: input.version,
597251
- platform,
597252
- minVersion
597253
- });
597254
- if (!reReady) {
597255
- return {
597256
- ...plan,
597257
- action: "failed",
597258
- error: `installed runtime at ${plan.binPath} but it does not satisfy minVersion ${minVersion}`
597259
- };
597260
- }
597261
- return {
597262
- ...plan,
597263
- action: "installed"
597264
- };
597265
- }
597266
- async function downloadFile(url, dest) {
597267
- const res = await fetch(url, { redirect: "follow" });
597268
- if (!res.ok || !res.body) {
597269
- throw new Error(`download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
597270
- }
597271
- const out = createWriteStream(dest);
597272
- await pipeline(Readable2.fromWeb(res.body), out);
597273
- }
597274
- async function extractArchive(archivePath, destDir, ext) {
597275
- if (ext === "tar.gz" || ext === "tar.xz") {
597276
- await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
597277
- } else if (ext === "zip") {
597278
- try {
597279
- await runCmd("tar", ["-xf", archivePath, "-C", destDir]);
597280
- } catch {
597281
- await runCmd("powershell", [
597282
- "-Command",
597283
- `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
597284
- ]);
597285
- }
597286
- } else {
597287
- throw new Error(`unsupported archive ext: ${ext}`);
597288
- }
597289
- }
597290
- function runCmd(command, args) {
597291
- return new Promise((resolve, reject) => {
597292
- const proc = spawn23(command, args, { stdio: "ignore" });
597293
- proc.on("error", (err) => reject(err));
597294
- proc.on("close", (code) => {
597295
- if (code === 0)
597296
- resolve();
597297
- else
597298
- reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
597299
- });
597300
- });
597301
- }
597302
- var init_installer = __esm(() => {
597303
- init_node_runtime();
597304
- });
597305
-
597306
- // packages/runtime/contract-validator.ts
597307
- var init_contract_validator = __esm(() => {
597308
- init_types3();
597309
- });
597310
-
597311
- // packages/runtime/index.ts
597312
- import { join as join181 } from "node:path";
597313
- import { execFile as execFile9 } from "node:child_process";
597314
- import { promisify as promisify7 } from "node:util";
597315
- async function ensureNodeFor(minVersion) {
597316
- try {
597317
- const { stdout } = await execFileAsync5("node", ["--version"], {
597318
- timeout: 3000
597319
- });
597320
- const parsed = parseNodeVersion(stdout);
597321
- if (parsed && satisfiesMinVersion(parsed, minVersion)) {
597322
- return null;
597323
- }
597324
- } catch {}
597325
- const ready = await isRuntimeReady({
597326
- version: MANAGED_NODE_VERSION,
597327
- minVersion
597328
- });
597329
- const dir = getRuntimeDir({ version: MANAGED_NODE_VERSION });
597330
- const binDir = process.platform === "win32" ? dir : join181(dir, "bin");
597331
- if (ready)
597332
- return binDir;
597333
- const result = await installRuntime({
597334
- version: MANAGED_NODE_VERSION,
597335
- minVersion
597336
- });
597337
- if (result.action === "failed") {
597338
- throw new Error(`failed to install Node ${MANAGED_NODE_VERSION}: ${result.error ?? "unknown"}`);
597339
- }
597340
- return binDir;
597341
- }
597342
- var execFileAsync5, MANAGED_NODE_VERSION = "22.14.0";
597343
- var init_runtime = __esm(() => {
597344
- init_installer();
597345
- init_node_runtime();
597346
- init_node_runtime();
597347
- init_installer();
597348
- init_contract_validator();
597349
- execFileAsync5 = promisify7(execFile9);
597350
- });
597351
-
597352
597914
  // packages/goal/types.ts
597353
597915
  var DEFAULT_BUDGET, SATISFIED_CONFIDENCE_FLOOR = 0.75;
597354
597916
  var init_types5 = __esm(() => {
@@ -599212,7 +599774,7 @@ Nothing was changed. Type /update again to apply.`);
599212
599774
  // apps/tui/src/lib/telegram-setup.ts
599213
599775
  import {
599214
599776
  chmodSync as chmodSync6,
599215
- existsSync as existsSync135,
599777
+ existsSync as existsSync137,
599216
599778
  lstatSync as lstatSync31,
599217
599779
  mkdirSync as mkdirSync96,
599218
599780
  readFileSync as readFileSync121,
@@ -599221,7 +599783,7 @@ import {
599221
599783
  writeFileSync as writeFileSync76
599222
599784
  } from "node:fs";
599223
599785
  import { homedir as homedir95 } from "node:os";
599224
- import { dirname as dirname83, join as join201 } from "node:path";
599786
+ import { dirname as dirname84, join as join202 } from "node:path";
599225
599787
  function redact3(text, key) {
599226
599788
  let out = text;
599227
599789
  if (key) {
@@ -599238,7 +599800,7 @@ function cleanLabel(raw, max = 40) {
599238
599800
  }
599239
599801
  function ensureEnvFile(file) {
599240
599802
  const path = expand4(file);
599241
- const dir = dirname83(path);
599803
+ const dir = dirname84(path);
599242
599804
  const notes = [];
599243
599805
  const kind = (p) => {
599244
599806
  try {
@@ -599332,9 +599894,9 @@ async function getSeenChats(key) {
599332
599894
  return { chats: [...seen.values()] };
599333
599895
  }
599334
599896
  function mergeSettings(chatIds, envFile, tokenVar) {
599335
- const path = join201(homeDir4(), ".8gent", "settings.json");
599897
+ const path = join202(homeDir4(), ".8gent", "settings.json");
599336
599898
  let settings = {};
599337
- if (existsSync135(path)) {
599899
+ if (existsSync137(path)) {
599338
599900
  try {
599339
599901
  const parsed = JSON.parse(readFileSync121(path, "utf8"));
599340
599902
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
@@ -599357,7 +599919,7 @@ function mergeSettings(chatIds, envFile, tokenVar) {
599357
599919
  botEnvFile: envFile,
599358
599920
  botTokenVar: tokenVar
599359
599921
  };
599360
- mkdirSync96(dirname83(path), { recursive: true });
599922
+ mkdirSync96(dirname84(path), { recursive: true });
599361
599923
  const tmp = `${path}.tmp-${process.pid}`;
599362
599924
  rmSync18(tmp, { force: true });
599363
599925
  writeFileSync76(tmp, `${JSON.stringify(settings, null, 2)}
@@ -599367,7 +599929,7 @@ function mergeSettings(chatIds, envFile, tokenVar) {
599367
599929
  }
599368
599930
  function configured() {
599369
599931
  try {
599370
- const pm = JSON.parse(readFileSync121(join201(homeDir4(), ".8gent", "settings.json"), "utf8"))?.postMessage;
599932
+ const pm = JSON.parse(readFileSync121(join202(homeDir4(), ".8gent", "settings.json"), "utf8"))?.postMessage;
599371
599933
  const file = typeof pm?.botEnvFile === "string" ? pm.botEnvFile : DEFAULT_ENV_FILE;
599372
599934
  const name = typeof pm?.botTokenVar === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(pm.botTokenVar) ? pm.botTokenVar : DEFAULT_TOKEN_VAR;
599373
599935
  return { file, name };
@@ -599442,7 +600004,7 @@ async function runTelegramSetup2(args) {
599442
600004
  ].join(`
599443
600005
  `));
599444
600006
  }
599445
- 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.";
600007
+ 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("~/") ? join202(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.";
599446
600008
  var init_telegram_setup = __esm(() => {
599447
600009
  init_post_message();
599448
600010
  BOT_TOKEN_RE2 = /(?:bot)?\d{6,}:[A-Za-z0-9_-]{20,}/g;
@@ -599701,11 +600263,11 @@ Personal 8gent mini-app. Iteration is driven by \`SKILL.md\`. Run tests with \`b
599701
600263
  }
599702
600264
 
599703
600265
  // packages/app-creator/creator.ts
599704
- import { existsSync as existsSync136, mkdirSync as mkdirSync98, writeFileSync as writeFileSync78 } from "node:fs";
600266
+ import { existsSync as existsSync138, mkdirSync as mkdirSync98, writeFileSync as writeFileSync78 } from "node:fs";
599705
600267
  import { homedir as homedir96 } from "node:os";
599706
- import { dirname as dirname84, join as join203 } from "node:path";
600268
+ import { dirname as dirname85, join as join204 } from "node:path";
599707
600269
  function defaultAppsRoot() {
599708
- return join203(homedir96(), ".8gent", "apps");
600270
+ return join204(homedir96(), ".8gent", "apps");
599709
600271
  }
599710
600272
  function buildManifest2(input) {
599711
600273
  const slug = slugify3(input.name);
@@ -599739,7 +600301,7 @@ function draftApp(input) {
599739
600301
  };
599740
600302
  }
599741
600303
  const m = built.manifest;
599742
- const dir = join203(root, m.name);
600304
+ const dir = join204(root, m.name);
599743
600305
  const files = {
599744
600306
  "manifest.json": `${JSON.stringify(m, null, 2)}
599745
600307
  `,
@@ -599759,7 +600321,7 @@ function createApp(input) {
599759
600321
  return { ...draft, persisted: false, written: [] };
599760
600322
  }
599761
600323
  const dir = draft.dir;
599762
- const targetExists = existsSync136(dir);
600324
+ const targetExists = existsSync138(dir);
599763
600325
  if (targetExists && !input.allowOverwrite) {
599764
600326
  return {
599765
600327
  ...draft,
@@ -599770,11 +600332,11 @@ function createApp(input) {
599770
600332
  ]
599771
600333
  };
599772
600334
  }
599773
- mkdirSync98(join203(dir, "tests"), { recursive: true });
600335
+ mkdirSync98(join204(dir, "tests"), { recursive: true });
599774
600336
  const written = [];
599775
600337
  for (const [rel, contents] of Object.entries(draft.files)) {
599776
- const full = join203(dir, rel);
599777
- mkdirSync98(dirname84(full), { recursive: true });
600338
+ const full = join204(dir, rel);
600339
+ mkdirSync98(dirname85(full), { recursive: true });
599778
600340
  writeFileSync78(full, contents);
599779
600341
  written.push(full);
599780
600342
  }
@@ -600606,8 +601168,8 @@ var init_producer = __esm(() => {
600606
601168
  });
600607
601169
 
600608
601170
  // packages/pet/companion.ts
600609
- import { existsSync as existsSync140, mkdirSync as mkdirSync104, readFileSync as readFileSync123, writeFileSync as writeFileSync80 } from "node:fs";
600610
- import { join as join205 } from "node:path";
601171
+ import { existsSync as existsSync142, mkdirSync as mkdirSync104, readFileSync as readFileSync123, writeFileSync as writeFileSync80 } from "node:fs";
601172
+ import { join as join206 } from "node:path";
600611
601173
  function seedRng(seed) {
600612
601174
  let h = 0;
600613
601175
  for (let i = 0;i < seed.length; i++) {
@@ -600745,12 +601307,12 @@ function generateCompanion(sessionId) {
600745
601307
  }
600746
601308
  function getDeckPath() {
600747
601309
  const home = process.env.HOME || process.env.USERPROFILE || "~";
600748
- return join205(home, ".8gent", "companion-deck.json");
601310
+ return join206(home, ".8gent", "companion-deck.json");
600749
601311
  }
600750
601312
  function loadDeck() {
600751
601313
  const deckPath = getDeckPath();
600752
601314
  try {
600753
- if (existsSync140(deckPath)) {
601315
+ if (existsSync142(deckPath)) {
600754
601316
  return JSON.parse(readFileSync123(deckPath, "utf-8"));
600755
601317
  }
600756
601318
  } catch {}
@@ -601194,7 +601756,7 @@ var init_companion = __esm(() => {
601194
601756
  // packages/orchestration/adaptive-pipeline.ts
601195
601757
  import { spawn as spawn26 } from "node:child_process";
601196
601758
  import { homedir as homedir97 } from "node:os";
601197
- import { join as join206 } from "node:path";
601759
+ import { join as join207 } from "node:path";
601198
601760
 
601199
601761
  class Obstacle {
601200
601762
  type;
@@ -601683,7 +602245,7 @@ var init_adaptive_pipeline = __esm(() => {
601683
602245
  init_role_config();
601684
602246
  OLLAMA_URL2 = process.env.OLLAMA_BASE_URL || "http://localhost:11434";
601685
602247
  LMSTUDIO_URL2 = process.env.LMSTUDIO_BASE_URL || "http://localhost:1234/v1";
601686
- BRIDGE_PATH2 = process.env.APPLE_FOUNDATION_BRIDGE || join206(homedir97(), ".8gent", "bin", "apple-foundation-bridge");
602248
+ BRIDGE_PATH2 = process.env.APPLE_FOUNDATION_BRIDGE || join207(homedir97(), ".8gent", "bin", "apple-foundation-bridge");
601687
602249
  ((Severity) => {
601688
602250
  Severity[Severity["Trivial"] = 1] = "Trivial";
601689
602251
  Severity[Severity["Moderate"] = 2] = "Moderate";
@@ -611562,7 +612124,7 @@ init_local_model_server();
611562
612124
  init_sequential_pipeline();
611563
612125
  init_speak();
611564
612126
  var import_react104 = __toESM(require_react(), 1);
611565
- var import_package = __toESM(require_package(), 1);
612127
+ var import_package2 = __toESM(require_package(), 1);
611566
612128
 
611567
612129
  // apps/tui/src/components/ActivityMonitor.tsx
611568
612130
  var import_react31 = __toESM(require_react(), 1);
@@ -622006,234 +622568,6 @@ function useImageInput(options = {}) {
622006
622568
  // apps/tui/src/components/message-list.tsx
622007
622569
  var import_react63 = __toESM(require_react(), 1);
622008
622570
 
622009
- // apps/tui/src/lib/chat-layout.ts
622010
- var SHELL_CHROME = 4;
622011
- var COLUMN_GAP = 1;
622012
- var ACTIVITY_RAIL_WIDTH = 34;
622013
- function chatColumnWidth(cols, shell, min = 24) {
622014
- let width = cols - SHELL_CHROME;
622015
- if (shell.planWidth && shell.planWidth > 0)
622016
- width -= shell.planWidth + COLUMN_GAP;
622017
- if (shell.activity)
622018
- width -= ACTIVITY_RAIL_WIDTH + COLUMN_GAP;
622019
- return Math.max(min, width);
622020
- }
622021
- var USER_BUBBLE_SHARE = 0.78;
622022
- function bubbleWidths(contentWidth, role) {
622023
- const inner = Math.max(16, contentWidth - 2);
622024
- const bubble = role === "user" ? Math.max(16, Math.floor(inner * USER_BUBBLE_SHARE)) : inner;
622025
- return { bubble, wrap: Math.max(8, bubble - 2) };
622026
- }
622027
-
622028
- // apps/tui/src/lib/tool-trail.ts
622029
- var COLLAPSE_THRESHOLD = 8;
622030
- var MAX_TRAIL_ROWS = 12;
622031
- var PATH_KEYS = ["path", "file_path", "filePath", "file", "directory", "dir"];
622032
- var COMMAND_KEYS = ["command", "cmd"];
622033
- var QUERY_KEYS = ["query", "pattern", "q", "search", "url"];
622034
- function oneLine(s) {
622035
- return s.replace(/\s+/g, " ").trim().slice(0, 200);
622036
- }
622037
- function pick3(args, keys) {
622038
- for (const k of keys) {
622039
- const v = args[k];
622040
- if (typeof v === "string" && v.trim())
622041
- return oneLine(v);
622042
- }
622043
- return null;
622044
- }
622045
- function summarizeToolArgs(tool, args) {
622046
- if (!args || typeof args !== "object")
622047
- return "";
622048
- const isCommand = /command|shell|exec|bash/.test(tool);
622049
- 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];
622050
- for (const keys of order) {
622051
- const hit = pick3(args, keys);
622052
- if (hit)
622053
- return hit;
622054
- }
622055
- for (const v of Object.values(args)) {
622056
- if (typeof v === "string" && v.trim())
622057
- return oneLine(v);
622058
- }
622059
- return "";
622060
- }
622061
- var GATE_PREFIX = /^\s*\[([A-Z0-9_ -]*\b(?:BLOCKED|DENIED)\b[A-Z0-9_ -]*)\]/;
622062
- var OTHER_BLOCK = /^\s*(?:Path blocked by path-guard|Path traversal blocked)|Hook blocked tool/;
622063
- var RULE_NAME = /\[([a-z0-9]+(?:-[a-z0-9]+)+)\]/;
622064
- function classifyToolResult(success, preview) {
622065
- const text = preview ?? "";
622066
- const gate = text.match(GATE_PREFIX);
622067
- if (gate || OTHER_BLOCK.test(text)) {
622068
- if (gate && /DENIED/.test(gate[1]))
622069
- return { status: "blocked", reason: "denied" };
622070
- const rule = text.match(RULE_NAME);
622071
- return { status: "blocked", reason: rule ? `blocked: [${rule[1]}]` : "blocked" };
622072
- }
622073
- const exit = text.match(/^\s*Exit code (-?\d+)/);
622074
- if (exit && exit[1] !== "0")
622075
- return { status: "fail", reason: `exit ${exit[1]}` };
622076
- if (!success || /^\s*(?:Error\b|Unknown tool:)/.test(text)) {
622077
- const first = (text.split(`
622078
- `).find((l) => l.trim()) ?? "").replace(/^\s*Error(?: running tool "[^"]*")?:?\s*/, "").trim();
622079
- if (!first)
622080
- return { status: "fail", reason: "error" };
622081
- return { status: "fail", reason: first.length > 40 ? `${first.slice(0, 39)}…` : first };
622082
- }
622083
- return { status: "ok" };
622084
- }
622085
- function toTrailEntry(event) {
622086
- const { status, reason } = classifyToolResult(event.success !== false, event.resultPreview);
622087
- const entry = {
622088
- tool: event.toolName,
622089
- summary: summarizeToolArgs(event.toolName, event.args),
622090
- status
622091
- };
622092
- if (reason)
622093
- entry.reason = reason;
622094
- if (event.toolName === "update_plan") {
622095
- const items = event.args?.plan;
622096
- if (Array.isArray(items)) {
622097
- 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" }));
622098
- if (plan.length > 0)
622099
- entry.plan = plan;
622100
- }
622101
- }
622102
- return entry;
622103
- }
622104
- function isReadTool(tool) {
622105
- return /^(?:read_|list_|get_|search|grep|glob|find|view|web_search|web_fetch)/.test(tool);
622106
- }
622107
- function commonDir(paths) {
622108
- const split = paths.map((p) => p.split("/").slice(0, -1));
622109
- const first = split[0] ?? [];
622110
- let n = 0;
622111
- while (n < first.length && split.every((s) => s[n] === first[n]))
622112
- n++;
622113
- return n > 0 ? `${first.slice(0, n).join("/")}/` : "";
622114
- }
622115
- function collapseTrail(entries, maxRows = MAX_TRAIL_ROWS) {
622116
- let rows = entries.map((e) => ({ ...e, count: 1 }));
622117
- if (entries.length > COLLAPSE_THRESHOLD) {
622118
- const out = [];
622119
- for (let i = 0;i < entries.length; ) {
622120
- const e = entries[i];
622121
- let j = i + 1;
622122
- if (e.status === "ok" && isReadTool(e.tool)) {
622123
- while (j < entries.length && entries[j].tool === e.tool && entries[j].status === "ok")
622124
- j++;
622125
- }
622126
- if (j - i > 1) {
622127
- const run = entries.slice(i, j);
622128
- out.push({
622129
- tool: e.tool,
622130
- status: "ok",
622131
- count: run.length,
622132
- summary: commonDir(run.map((r) => r.summary))
622133
- });
622134
- } else {
622135
- out.push({ ...e, count: 1 });
622136
- }
622137
- i = j;
622138
- }
622139
- rows = out;
622140
- }
622141
- const cap = Math.max(1, Math.floor(maxRows));
622142
- if (rows.length > cap) {
622143
- let toFold = rows.length - cap + 1;
622144
- const fold = new Set;
622145
- for (const wantOk of [true, false]) {
622146
- for (let i = 0;i < rows.length && toFold > 0; i++) {
622147
- if (fold.has(i) || wantOk && rows[i].status !== "ok")
622148
- continue;
622149
- fold.add(i);
622150
- toFold--;
622151
- }
622152
- }
622153
- const folded = rows.filter((_, i) => fold.has(i));
622154
- const count = (s) => folded.filter((r) => r.status === s).reduce((n, r) => n + r.count, 0);
622155
- const failed = count("fail");
622156
- const blocked = count("blocked");
622157
- const summary = {
622158
- tool: "",
622159
- summary: "",
622160
- status: failed > 0 ? "fail" : blocked > 0 ? "blocked" : "ok",
622161
- count: folded.reduce((n, r) => n + r.count, 0),
622162
- folded: true,
622163
- all: fold.size === rows.length
622164
- };
622165
- if (failed > 0)
622166
- summary.failed = failed;
622167
- if (blocked > 0)
622168
- summary.blocked = blocked;
622169
- rows = [summary, ...rows.filter((_, i) => !fold.has(i))];
622170
- }
622171
- return rows;
622172
- }
622173
- var ICONS = { ok: "✓", fail: "✗", blocked: "⊘" };
622174
- function formatTrailRow(row, width) {
622175
- const icon = ICONS[row.status];
622176
- const budget = Math.max(1, width - icon.length - 1);
622177
- if (row.folded) {
622178
- const parts = [`${row.count} ${row.all ? "calls" : "earlier calls"}`];
622179
- if (row.failed)
622180
- parts.push(`${row.failed} failed`);
622181
- if (row.blocked)
622182
- parts.push(`${row.blocked} blocked`);
622183
- return { icon, text: clip(parts.join(", "), budget) };
622184
- }
622185
- const head = row.count > 1 ? `${row.tool} ×${row.count}` : row.tool;
622186
- const tail = row.reason ? ` (${row.reason})` : "";
622187
- let summary = row.count > 1 ? row.summary ? `(${row.summary}…)` : "" : row.summary;
622188
- if (!summary)
622189
- return { icon, text: clip(`${head}${tail}`, budget) };
622190
- const room = budget - head.length - 1 - tail.length;
622191
- if (room < 4)
622192
- return { icon, text: clip(`${head}${tail}`, budget) };
622193
- if (summary.length > room)
622194
- summary = `${summary.slice(0, room - 1)}…`;
622195
- return { icon, text: `${head} ${summary}${tail}` };
622196
- }
622197
- function clip(s, width) {
622198
- return s.length <= width ? s : `${s.slice(0, Math.max(0, width - 1))}…`;
622199
- }
622200
- function buildChatItems(messages) {
622201
- const items = [];
622202
- let pending = [];
622203
- let pendingFirst = null;
622204
- const flush = () => {
622205
- if (pending.length > 0 && pendingFirst) {
622206
- items.push({
622207
- message: { ...pendingFirst, id: `trail-${pendingFirst.id}`, role: "tool", content: "" },
622208
- trail: pending
622209
- });
622210
- }
622211
- pending = [];
622212
- pendingFirst = null;
622213
- };
622214
- for (const m of messages) {
622215
- if (m.role === "tool") {
622216
- if (m.toolTrail) {
622217
- if (!pendingFirst)
622218
- pendingFirst = m;
622219
- pending.push(m.toolTrail);
622220
- }
622221
- continue;
622222
- }
622223
- if (m.role === "assistant") {
622224
- items.push({ message: m, trail: pending });
622225
- pending = [];
622226
- pendingFirst = null;
622227
- continue;
622228
- }
622229
- if (m.role === "user")
622230
- flush();
622231
- items.push({ message: m, trail: [] });
622232
- }
622233
- flush();
622234
- return items;
622235
- }
622236
-
622237
622571
  // apps/tui/src/hooks/useMouseScroll.ts
622238
622572
  var import_react57 = __toESM(require_react(), 1);
622239
622573
  function useMouseScroll({
@@ -622259,111 +622593,25 @@ function useMouseScroll({
622259
622593
  }, [enabled, step]);
622260
622594
  }
622261
622595
 
622262
- // apps/tui/src/components/fade-transition.tsx
622263
- var import_react58 = __toESM(require_react(), 1);
622264
- var jsx_dev_runtime30 = __toESM(require_jsx_dev_runtime(), 1);
622265
- function FadeIn({ children, duration = 300, delay = 0, onComplete }) {
622266
- const [started, setStarted] = import_react58.useState(false);
622267
- import_react58.useEffect(() => {
622268
- const timeout = setTimeout(() => {
622269
- setStarted(true);
622270
- }, delay);
622271
- return () => clearTimeout(timeout);
622272
- }, [delay]);
622273
- import_react58.useEffect(() => {
622274
- if (!started)
622275
- return;
622276
- const timeout = setTimeout(() => {
622277
- onComplete?.();
622278
- }, duration);
622279
- return () => clearTimeout(timeout);
622280
- }, [started, duration, onComplete]);
622281
- if (!started)
622282
- return null;
622283
- return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
622284
- children
622285
- }, undefined, false, undefined, this);
622286
- }
622287
-
622288
- // apps/tui/src/components/ToolTrail.tsx
622289
- var import_react59 = __toESM(require_react(), 1);
622290
- var jsx_dev_runtime31 = __toESM(require_jsx_dev_runtime(), 1);
622291
- function iconColor(status) {
622292
- return status === "ok" ? t.green : status === "fail" ? t.red : t.orange;
622293
- }
622294
- function useLandingRows(total, animate) {
622295
- const [shown, setShown] = import_react59.useState(total);
622296
- const shownRef = import_react59.useRef(total);
622297
- shownRef.current = shown;
622298
- const landingRef = import_react59.useRef(null);
622299
- import_react59.useEffect(() => {
622300
- const from = shownRef.current;
622301
- if (total <= from + 1 || !motionEnabled(animate)) {
622302
- landingRef.current = null;
622303
- setShown(total);
622304
- return;
622305
- }
622306
- landingRef.current = total;
622307
- const step = staggerFor(total - from);
622308
- let n = from + 1;
622309
- setShown(n);
622310
- const id = setInterval(() => {
622311
- n += 1;
622312
- setShown(Math.min(total, n));
622313
- if (n >= total) {
622314
- landingRef.current = null;
622315
- clearInterval(id);
622316
- }
622317
- }, step);
622318
- return () => clearInterval(id);
622319
- }, [total, animate]);
622320
- if (landingRef.current === total)
622321
- return Math.min(shown, total);
622322
- if (total <= shown + 1)
622323
- return total;
622324
- return shown;
622325
- }
622326
- function ToolTrail({
622327
- entries,
622328
- width,
622329
- maxRows,
622330
- animate = true
622331
- }) {
622332
- const rows = collapseTrail(entries, maxRows);
622333
- const visible = useLandingRows(rows.length, animate);
622334
- if (rows.length === 0)
622335
- return null;
622336
- return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
622337
- flexDirection: "column",
622338
- flexShrink: 0,
622339
- children: rows.slice(0, visible).map((row, i) => {
622340
- const { text } = formatTrailRow(row, width);
622341
- const g = glyphs();
622342
- const icon = row.status === "ok" ? g.ok : row.status === "fail" ? g.fail : g.blocked;
622343
- return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
622344
- width,
622345
- children: [
622346
- /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
622347
- color: iconColor(row.status),
622348
- children: icon
622349
- }, undefined, false, undefined, this),
622350
- /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
622351
- color: row.status === "ok" ? t.muted : t.textPrimary,
622352
- wrap: "truncate-end",
622353
- children: ` ${text}`
622354
- }, undefined, false, undefined, this)
622355
- ]
622356
- }, i, true, undefined, this);
622357
- })
622358
- }, undefined, false, undefined, this);
622596
+ // apps/tui/src/lib/chat-layout.ts
622597
+ var SHELL_CHROME = 4;
622598
+ var COLUMN_GAP = 1;
622599
+ var ACTIVITY_RAIL_WIDTH = 34;
622600
+ function chatColumnWidth(cols, shell, min = 24) {
622601
+ let width = cols - SHELL_CHROME;
622602
+ if (shell.planWidth && shell.planWidth > 0)
622603
+ width -= shell.planWidth + COLUMN_GAP;
622604
+ if (shell.activity)
622605
+ width -= ACTIVITY_RAIL_WIDTH + COLUMN_GAP;
622606
+ return Math.max(min, width);
622359
622607
  }
622360
- function toolTrailRows(entries, maxRows) {
622361
- return entries.length === 0 ? 0 : collapseTrail(entries, maxRows).length;
622608
+ var USER_BUBBLE_SHARE = 0.78;
622609
+ function bubbleWidths(contentWidth, role) {
622610
+ const inner = Math.max(16, contentWidth - 2);
622611
+ const bubble = role === "user" ? Math.max(16, Math.floor(inner * USER_BUBBLE_SHARE)) : inner;
622612
+ return { bubble, wrap: Math.max(8, bubble - 2) };
622362
622613
  }
622363
622614
 
622364
- // apps/tui/src/components/TurnResults.tsx
622365
- var import_react60 = __toESM(require_react(), 1);
622366
-
622367
622615
  // apps/tui/src/lib/inline-markdown.ts
622368
622616
  var MAX_DEPTH3 = 3;
622369
622617
  var INDENT_STEP = 2;
@@ -622678,6 +622926,215 @@ function blockRows(block, width) {
622678
622926
  }
622679
622927
  }
622680
622928
 
622929
+ // apps/tui/src/lib/tool-trail.ts
622930
+ var COLLAPSE_THRESHOLD = 8;
622931
+ var MAX_TRAIL_ROWS = 12;
622932
+ var PATH_KEYS = ["path", "file_path", "filePath", "file", "directory", "dir"];
622933
+ var COMMAND_KEYS = ["command", "cmd"];
622934
+ var QUERY_KEYS = ["query", "pattern", "q", "search", "url"];
622935
+ function oneLine(s) {
622936
+ return s.replace(/\s+/g, " ").trim().slice(0, 200);
622937
+ }
622938
+ function pick3(args, keys) {
622939
+ for (const k of keys) {
622940
+ const v = args[k];
622941
+ if (typeof v === "string" && v.trim())
622942
+ return oneLine(v);
622943
+ }
622944
+ return null;
622945
+ }
622946
+ function summarizeToolArgs(tool, args) {
622947
+ if (!args || typeof args !== "object")
622948
+ return "";
622949
+ const isCommand = /command|shell|exec|bash/.test(tool);
622950
+ 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];
622951
+ for (const keys of order) {
622952
+ const hit = pick3(args, keys);
622953
+ if (hit)
622954
+ return hit;
622955
+ }
622956
+ for (const v of Object.values(args)) {
622957
+ if (typeof v === "string" && v.trim())
622958
+ return oneLine(v);
622959
+ }
622960
+ return "";
622961
+ }
622962
+ var GATE_PREFIX = /^\s*\[([A-Z0-9_ -]*\b(?:BLOCKED|DENIED)\b[A-Z0-9_ -]*)\]/;
622963
+ var OTHER_BLOCK = /^\s*(?:Path blocked by path-guard|Path traversal blocked)|Hook blocked tool/;
622964
+ var RULE_NAME = /\[([a-z0-9]+(?:-[a-z0-9]+)+)\]/;
622965
+ function classifyToolResult(success, preview) {
622966
+ const text = preview ?? "";
622967
+ const gate = text.match(GATE_PREFIX);
622968
+ if (gate || OTHER_BLOCK.test(text)) {
622969
+ if (gate && /DENIED/.test(gate[1]))
622970
+ return { status: "blocked", reason: "denied" };
622971
+ const rule = text.match(RULE_NAME);
622972
+ return { status: "blocked", reason: rule ? `blocked: [${rule[1]}]` : "blocked" };
622973
+ }
622974
+ const exit = text.match(/^\s*Exit code (-?\d+)/);
622975
+ if (exit && exit[1] !== "0")
622976
+ return { status: "fail", reason: `exit ${exit[1]}` };
622977
+ if (!success || /^\s*(?:Error\b|Unknown tool:)/.test(text)) {
622978
+ const first = (text.split(`
622979
+ `).find((l) => l.trim()) ?? "").replace(/^\s*Error(?: running tool "[^"]*")?:?\s*/, "").trim();
622980
+ if (!first)
622981
+ return { status: "fail", reason: "error" };
622982
+ return { status: "fail", reason: first.length > 40 ? `${first.slice(0, 39)}…` : first };
622983
+ }
622984
+ return { status: "ok" };
622985
+ }
622986
+ function toTrailEntry(event) {
622987
+ const { status, reason } = classifyToolResult(event.success !== false, event.resultPreview);
622988
+ const entry = {
622989
+ tool: event.toolName,
622990
+ summary: summarizeToolArgs(event.toolName, event.args),
622991
+ status
622992
+ };
622993
+ if (reason)
622994
+ entry.reason = reason;
622995
+ if (event.toolName === "update_plan") {
622996
+ const items = event.args?.plan;
622997
+ if (Array.isArray(items)) {
622998
+ 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" }));
622999
+ if (plan.length > 0)
623000
+ entry.plan = plan;
623001
+ }
623002
+ }
623003
+ return entry;
623004
+ }
623005
+ function isReadTool(tool) {
623006
+ return /^(?:read_|list_|get_|search|grep|glob|find|view|web_search|web_fetch)/.test(tool);
623007
+ }
623008
+ function commonDir(paths) {
623009
+ const split = paths.map((p) => p.split("/").slice(0, -1));
623010
+ const first = split[0] ?? [];
623011
+ let n = 0;
623012
+ while (n < first.length && split.every((s) => s[n] === first[n]))
623013
+ n++;
623014
+ return n > 0 ? `${first.slice(0, n).join("/")}/` : "";
623015
+ }
623016
+ function collapseTrail(entries, maxRows = MAX_TRAIL_ROWS) {
623017
+ let rows = entries.map((e) => ({ ...e, count: 1 }));
623018
+ if (entries.length > COLLAPSE_THRESHOLD) {
623019
+ const out = [];
623020
+ for (let i = 0;i < entries.length; ) {
623021
+ const e = entries[i];
623022
+ let j = i + 1;
623023
+ if (e.status === "ok" && isReadTool(e.tool)) {
623024
+ while (j < entries.length && entries[j].tool === e.tool && entries[j].status === "ok")
623025
+ j++;
623026
+ }
623027
+ if (j - i > 1) {
623028
+ const run = entries.slice(i, j);
623029
+ out.push({
623030
+ tool: e.tool,
623031
+ status: "ok",
623032
+ count: run.length,
623033
+ summary: commonDir(run.map((r) => r.summary))
623034
+ });
623035
+ } else {
623036
+ out.push({ ...e, count: 1 });
623037
+ }
623038
+ i = j;
623039
+ }
623040
+ rows = out;
623041
+ }
623042
+ const cap = Math.max(1, Math.floor(maxRows));
623043
+ if (rows.length > cap) {
623044
+ let toFold = rows.length - cap + 1;
623045
+ const fold = new Set;
623046
+ for (const wantOk of [true, false]) {
623047
+ for (let i = 0;i < rows.length && toFold > 0; i++) {
623048
+ if (fold.has(i) || wantOk && rows[i].status !== "ok")
623049
+ continue;
623050
+ fold.add(i);
623051
+ toFold--;
623052
+ }
623053
+ }
623054
+ const folded = rows.filter((_, i) => fold.has(i));
623055
+ const count = (s) => folded.filter((r) => r.status === s).reduce((n, r) => n + r.count, 0);
623056
+ const failed = count("fail");
623057
+ const blocked = count("blocked");
623058
+ const summary = {
623059
+ tool: "",
623060
+ summary: "",
623061
+ status: failed > 0 ? "fail" : blocked > 0 ? "blocked" : "ok",
623062
+ count: folded.reduce((n, r) => n + r.count, 0),
623063
+ folded: true,
623064
+ all: fold.size === rows.length
623065
+ };
623066
+ if (failed > 0)
623067
+ summary.failed = failed;
623068
+ if (blocked > 0)
623069
+ summary.blocked = blocked;
623070
+ rows = [summary, ...rows.filter((_, i) => !fold.has(i))];
623071
+ }
623072
+ return rows;
623073
+ }
623074
+ var ICONS = { ok: "✓", fail: "✗", blocked: "⊘" };
623075
+ function formatTrailRow(row, width) {
623076
+ const icon = ICONS[row.status];
623077
+ const budget = Math.max(1, width - icon.length - 1);
623078
+ if (row.folded) {
623079
+ const parts = [`${row.count} ${row.all ? "calls" : "earlier calls"}`];
623080
+ if (row.failed)
623081
+ parts.push(`${row.failed} failed`);
623082
+ if (row.blocked)
623083
+ parts.push(`${row.blocked} blocked`);
623084
+ return { icon, text: clip(parts.join(", "), budget) };
623085
+ }
623086
+ const head = row.count > 1 ? `${row.tool} ×${row.count}` : row.tool;
623087
+ const tail = row.reason ? ` (${row.reason})` : "";
623088
+ let summary = row.count > 1 ? row.summary ? `(${row.summary}…)` : "" : row.summary;
623089
+ if (!summary)
623090
+ return { icon, text: clip(`${head}${tail}`, budget) };
623091
+ const room = budget - head.length - 1 - tail.length;
623092
+ if (room < 4)
623093
+ return { icon, text: clip(`${head}${tail}`, budget) };
623094
+ if (summary.length > room)
623095
+ summary = `${summary.slice(0, room - 1)}…`;
623096
+ return { icon, text: `${head} ${summary}${tail}` };
623097
+ }
623098
+ function clip(s, width) {
623099
+ return s.length <= width ? s : `${s.slice(0, Math.max(0, width - 1))}…`;
623100
+ }
623101
+ function buildChatItems(messages) {
623102
+ const items = [];
623103
+ let pending = [];
623104
+ let pendingFirst = null;
623105
+ const flush = () => {
623106
+ if (pending.length > 0 && pendingFirst) {
623107
+ items.push({
623108
+ message: { ...pendingFirst, id: `trail-${pendingFirst.id}`, role: "tool", content: "" },
623109
+ trail: pending
623110
+ });
623111
+ }
623112
+ pending = [];
623113
+ pendingFirst = null;
623114
+ };
623115
+ for (const m of messages) {
623116
+ if (m.role === "tool") {
623117
+ if (m.toolTrail) {
623118
+ if (!pendingFirst)
623119
+ pendingFirst = m;
623120
+ pending.push(m.toolTrail);
623121
+ }
623122
+ continue;
623123
+ }
623124
+ if (m.role === "assistant") {
623125
+ items.push({ message: m, trail: pending });
623126
+ pending = [];
623127
+ pendingFirst = null;
623128
+ continue;
623129
+ }
623130
+ if (m.role === "user")
623131
+ flush();
623132
+ items.push({ message: m, trail: [] });
623133
+ }
623134
+ flush();
623135
+ return items;
623136
+ }
623137
+
622681
623138
  // apps/tui/src/lib/activity-rail-derivation.ts
622682
623139
  function deriveTools(messages, isProcessing, limit = 5) {
622683
623140
  const ended = new Map;
@@ -623149,8 +623606,85 @@ function turnResultRows(trail, maxRows, plan) {
623149
623606
  return buildTurnResults(trail, maxRows, plan).length;
623150
623607
  }
623151
623608
 
623609
+ // apps/tui/src/components/ToolTrail.tsx
623610
+ var import_react58 = __toESM(require_react(), 1);
623611
+ var jsx_dev_runtime30 = __toESM(require_jsx_dev_runtime(), 1);
623612
+ function iconColor(status) {
623613
+ return status === "ok" ? t.green : status === "fail" ? t.red : t.orange;
623614
+ }
623615
+ function useLandingRows(total, animate) {
623616
+ const [shown, setShown] = import_react58.useState(total);
623617
+ const shownRef = import_react58.useRef(total);
623618
+ shownRef.current = shown;
623619
+ const landingRef = import_react58.useRef(null);
623620
+ import_react58.useEffect(() => {
623621
+ const from = shownRef.current;
623622
+ if (total <= from + 1 || !motionEnabled(animate)) {
623623
+ landingRef.current = null;
623624
+ setShown(total);
623625
+ return;
623626
+ }
623627
+ landingRef.current = total;
623628
+ const step = staggerFor(total - from);
623629
+ let n = from + 1;
623630
+ setShown(n);
623631
+ const id = setInterval(() => {
623632
+ n += 1;
623633
+ setShown(Math.min(total, n));
623634
+ if (n >= total) {
623635
+ landingRef.current = null;
623636
+ clearInterval(id);
623637
+ }
623638
+ }, step);
623639
+ return () => clearInterval(id);
623640
+ }, [total, animate]);
623641
+ if (landingRef.current === total)
623642
+ return Math.min(shown, total);
623643
+ if (total <= shown + 1)
623644
+ return total;
623645
+ return shown;
623646
+ }
623647
+ function ToolTrail({
623648
+ entries,
623649
+ width,
623650
+ maxRows,
623651
+ animate = true
623652
+ }) {
623653
+ const rows = collapseTrail(entries, maxRows);
623654
+ const visible = useLandingRows(rows.length, animate);
623655
+ if (rows.length === 0)
623656
+ return null;
623657
+ return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
623658
+ flexDirection: "column",
623659
+ flexShrink: 0,
623660
+ children: rows.slice(0, visible).map((row, i) => {
623661
+ const { text } = formatTrailRow(row, width);
623662
+ const g = glyphs();
623663
+ const icon = row.status === "ok" ? g.ok : row.status === "fail" ? g.fail : g.blocked;
623664
+ return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
623665
+ width,
623666
+ children: [
623667
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text, {
623668
+ color: iconColor(row.status),
623669
+ children: icon
623670
+ }, undefined, false, undefined, this),
623671
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text, {
623672
+ color: row.status === "ok" ? t.muted : t.textPrimary,
623673
+ wrap: "truncate-end",
623674
+ children: ` ${text}`
623675
+ }, undefined, false, undefined, this)
623676
+ ]
623677
+ }, i, true, undefined, this);
623678
+ })
623679
+ }, undefined, false, undefined, this);
623680
+ }
623681
+ function toolTrailRows(entries, maxRows) {
623682
+ return entries.length === 0 ? 0 : collapseTrail(entries, maxRows).length;
623683
+ }
623684
+
623152
623685
  // apps/tui/src/components/TurnResults.tsx
623153
- var jsx_dev_runtime32 = __toESM(require_jsx_dev_runtime(), 1);
623686
+ var import_react59 = __toESM(require_react(), 1);
623687
+ var jsx_dev_runtime31 = __toESM(require_jsx_dev_runtime(), 1);
623154
623688
  function iconFor(status) {
623155
623689
  const g = glyphs();
623156
623690
  if (status === "ok")
@@ -623162,8 +623696,8 @@ function iconFor(status) {
623162
623696
  return { icon: g.pending, color: t.muted };
623163
623697
  }
623164
623698
  function useResultLanding(total, land) {
623165
- const [shown, setShown] = import_react60.useState(land ? 0 : total);
623166
- import_react60.useEffect(() => {
623699
+ const [shown, setShown] = import_react59.useState(land ? 0 : total);
623700
+ import_react59.useEffect(() => {
623167
623701
  if (!land) {
623168
623702
  setShown(total);
623169
623703
  return;
@@ -623203,12 +623737,12 @@ function TurnResults({
623203
623737
  const visible = useResultLanding(rows.length, land && motionEnabled(animate));
623204
623738
  if (rows.length === 0)
623205
623739
  return null;
623206
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
623740
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
623207
623741
  flexDirection: "column",
623208
623742
  flexShrink: 0,
623209
623743
  children: rows.map((row, i) => {
623210
623744
  if (i >= visible) {
623211
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
623745
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
623212
623746
  height: 1,
623213
623747
  width
623214
623748
  }, i, false, undefined, this);
@@ -623216,55 +623750,55 @@ function TurnResults({
623216
623750
  const { icon, color } = iconFor(row.status);
623217
623751
  const fit = fitResultRow(row, width);
623218
623752
  const quiet = row.status === "pending" || row.kind === "fold";
623219
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
623753
+ return /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Box_default, {
623220
623754
  width,
623221
623755
  height: 1,
623222
- children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623756
+ children: /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623223
623757
  wrap: "truncate-end",
623224
623758
  children: [
623225
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623759
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623226
623760
  color,
623227
623761
  children: icon
623228
623762
  }, undefined, false, undefined, this),
623229
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623763
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623230
623764
  children: " "
623231
623765
  }, undefined, false, undefined, this),
623232
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623766
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623233
623767
  bold: !quiet,
623234
623768
  color: quiet ? t.muted : t.textPrimary,
623235
623769
  children: fit.verb
623236
623770
  }, undefined, false, undefined, this),
623237
- fit.spans ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(jsx_dev_runtime32.Fragment, {
623771
+ fit.spans ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(jsx_dev_runtime31.Fragment, {
623238
623772
  children: [
623239
- fit.verb ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623773
+ fit.verb ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623240
623774
  children: " "
623241
623775
  }, undefined, false, undefined, this) : null,
623242
- fit.spans.map((s, j) => s.code ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623776
+ fit.spans.map((s, j) => s.code ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623243
623777
  color: t.textPrimary,
623244
623778
  backgroundColor: t.border,
623245
623779
  children: s.text
623246
- }, j, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623780
+ }, j, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623247
623781
  color: quiet ? t.muted : t.textSecondary,
623248
623782
  children: s.text
623249
623783
  }, j, false, undefined, this))
623250
623784
  ]
623251
- }, undefined, true, undefined, this) : fit.text ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623785
+ }, undefined, true, undefined, this) : fit.text ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623252
623786
  color: quiet ? t.muted : t.textSecondary,
623253
623787
  children: ` ${fit.text}`
623254
623788
  }, undefined, false, undefined, this) : null,
623255
- fit.chip ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(jsx_dev_runtime32.Fragment, {
623789
+ fit.chip ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(jsx_dev_runtime31.Fragment, {
623256
623790
  children: [
623257
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623791
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623258
623792
  children: " "
623259
623793
  }, undefined, false, undefined, this),
623260
- /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623794
+ /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623261
623795
  color: t.textPrimary,
623262
623796
  backgroundColor: t.border,
623263
623797
  children: ` ${fit.chip} `
623264
623798
  }, undefined, false, undefined, this)
623265
623799
  ]
623266
623800
  }, undefined, true, undefined, this) : null,
623267
- fit.note ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text, {
623801
+ fit.note ? /* @__PURE__ */ jsx_dev_runtime31.jsxDEV(Text, {
623268
623802
  color: row.status === "fail" ? t.red : t.muted,
623269
623803
  children: `${" ".repeat(fit.noteGap ?? 2)}${fit.note}`
623270
623804
  }, undefined, false, undefined, this) : null
@@ -623275,6 +623809,32 @@ function TurnResults({
623275
623809
  }, undefined, false, undefined, this);
623276
623810
  }
623277
623811
 
623812
+ // apps/tui/src/components/fade-transition.tsx
623813
+ var import_react60 = __toESM(require_react(), 1);
623814
+ var jsx_dev_runtime32 = __toESM(require_jsx_dev_runtime(), 1);
623815
+ function FadeIn({ children, duration = 300, delay = 0, onComplete }) {
623816
+ const [started, setStarted] = import_react60.useState(false);
623817
+ import_react60.useEffect(() => {
623818
+ const timeout = setTimeout(() => {
623819
+ setStarted(true);
623820
+ }, delay);
623821
+ return () => clearTimeout(timeout);
623822
+ }, [delay]);
623823
+ import_react60.useEffect(() => {
623824
+ if (!started)
623825
+ return;
623826
+ const timeout = setTimeout(() => {
623827
+ onComplete?.();
623828
+ }, duration);
623829
+ return () => clearTimeout(timeout);
623830
+ }, [started, duration, onComplete]);
623831
+ if (!started)
623832
+ return null;
623833
+ return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
623834
+ children
623835
+ }, undefined, false, undefined, this);
623836
+ }
623837
+
623278
623838
  // apps/tui/src/components/sound-effects.tsx
623279
623839
  var import_react61 = __toESM(require_react(), 1);
623280
623840
  var BELL = "\x07";
@@ -623687,6 +624247,7 @@ function MessageList({
623687
624247
  showAnimations,
623688
624248
  isLatestAssistant: message.id === lastAssistantId,
623689
624249
  trail: trailById.get(message.id) ?? [],
624250
+ turnRunning,
623690
624251
  trailMaxRows: trailCaps.get(message.id),
623691
624252
  onAnimationStart: (id) => {
623692
624253
  animatedIdsRef.current.add(id);
@@ -623718,6 +624279,7 @@ function MessageItem({
623718
624279
  showAnimations,
623719
624280
  isLatestAssistant = false,
623720
624281
  trail = [],
624282
+ turnRunning = false,
623721
624283
  trailMaxRows,
623722
624284
  onAnimationStart
623723
624285
  }) {
@@ -623903,7 +624465,7 @@ function MessageItem({
623903
624465
  animate: showAnimations
623904
624466
  }, undefined, false, undefined, this)
623905
624467
  }, undefined, false, undefined, this),
623906
- !isUser && !message.content.trim() && trail.length > 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(MutedText, {
624468
+ !isUser && !turnRunning && !message.content.trim() && trail.length > 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(MutedText, {
623907
624469
  children: "No reply."
623908
624470
  }, undefined, false, undefined, this) : null,
623909
624471
  message.content.trim() || trail.length === 0 ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
@@ -624065,7 +624627,11 @@ function MarkdownBody({
624065
624627
  })
624066
624628
  }, undefined, false, undefined, this);
624067
624629
  }
624068
- function InlineSpans({ spans, color, adhdMode }) {
624630
+ function InlineSpans({
624631
+ spans,
624632
+ color,
624633
+ adhdMode
624634
+ }) {
624069
624635
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
624070
624636
  color,
624071
624637
  wrap: "wrap",
@@ -626476,9 +627042,9 @@ init_cloud_transcriber();
626476
627042
  init_types4();
626477
627043
 
626478
627044
  // packages/voice/voice-chat.ts
626479
- import { existsSync as existsSync115, unlinkSync as unlinkSync5 } from "node:fs";
627045
+ import { existsSync as existsSync119, unlinkSync as unlinkSync6 } from "node:fs";
626480
627046
  import { tmpdir as tmpdir21 } from "node:os";
626481
- import { join as join173 } from "node:path";
627047
+ import { join as join177 } from "node:path";
626482
627048
 
626483
627049
  // packages/voice/tts-engine.ts
626484
627050
  init_termux();
@@ -626886,7 +627452,7 @@ class VoiceChatLoop {
626886
627452
  await this.speakText(response);
626887
627453
  }
626888
627454
  async listenForSpeech() {
626889
- const wavPath = join173(tmpdir21(), `8gent-voicechat-${Date.now()}.wav`);
627455
+ const wavPath = join177(tmpdir21(), `8gent-voicechat-${Date.now()}.wav`);
626890
627456
  const silenceSec = (this.silenceMs / 1000).toFixed(1);
626891
627457
  try {
626892
627458
  const recProc = Bun.spawn([
@@ -626935,7 +627501,7 @@ class VoiceChatLoop {
626935
627501
  cleanupFile(wavPath);
626936
627502
  throw new VoiceChatFatalError(describeRecFailure(exitCode, why));
626937
627503
  }
626938
- if (!existsSync115(wavPath)) {
627504
+ if (!existsSync119(wavPath)) {
626939
627505
  this.reportError("No audio was recorded. Check that your microphone is the system input.");
626940
627506
  return null;
626941
627507
  }
@@ -627059,8 +627625,8 @@ function sleep2(ms) {
627059
627625
  }
627060
627626
  function cleanupFile(path) {
627061
627627
  try {
627062
- if (existsSync115(path))
627063
- unlinkSync5(path);
627628
+ if (existsSync119(path))
627629
+ unlinkSync6(path);
627064
627630
  } catch {}
627065
627631
  }
627066
627632
  var HEARD_ONLY_MARK = "[interrupted]";
@@ -627118,14 +627684,14 @@ function splitIntoSentences(text) {
627118
627684
  return chunks.length ? chunks : [text];
627119
627685
  }
627120
627686
  // packages/voice/full-duplex-provider.ts
627121
- import { execSync as execSync6 } from "node:child_process";
627122
- import { existsSync as existsSync116 } from "node:fs";
627123
- import { homedir as homedir87 } from "node:os";
627124
- import { join as join174 } from "node:path";
627125
- var MOSHI_HF_CACHE_PATH = join174(homedir87(), ".cache", "huggingface", "hub", "models--kyutai--moshiko-pytorch-bf16", "snapshots");
627687
+ import { execSync as execSync7 } from "node:child_process";
627688
+ import { existsSync as existsSync120 } from "node:fs";
627689
+ import { homedir as homedir88 } from "node:os";
627690
+ import { join as join178 } from "node:path";
627691
+ var MOSHI_HF_CACHE_PATH = join178(homedir88(), ".cache", "huggingface", "hub", "models--kyutai--moshiko-pytorch-bf16", "snapshots");
627126
627692
  function tryExec(cmd) {
627127
627693
  try {
627128
- execSync6(cmd, { stdio: "ignore", timeout: 5000 });
627694
+ execSync7(cmd, { stdio: "ignore", timeout: 5000 });
627129
627695
  return true;
627130
627696
  } catch {
627131
627697
  return false;
@@ -627137,7 +627703,7 @@ async function detectCapabilities() {
627137
627703
  const hasAppleSilicon = platform === "darwin" && arch === "arm64";
627138
627704
  const hasMLX = hasAppleSilicon && tryExec('python3 -c "import mlx"');
627139
627705
  const hasMoshiInstalled = tryExec('python3 -c "import moshi"');
627140
- const hasMoshiModel = existsSync116(MOSHI_HF_CACHE_PATH);
627706
+ const hasMoshiModel = existsSync120(MOSHI_HF_CACHE_PATH);
627141
627707
  const hasCUDA = tryExec("nvidia-smi");
627142
627708
  const hasNIMKey = !!process.env.NVIDIA_NIM_KEY;
627143
627709
  return {
@@ -628169,8 +628735,8 @@ function useWorkspaceTabs() {
628169
628735
 
628170
628736
  // apps/tui/src/lib/adhd-audio.ts
628171
628737
  init_bun_shim();
628172
- import { existsSync as existsSync118, mkdirSync as mkdirSync82 } from "node:fs";
628173
- import { join as join177 } from "node:path";
628738
+ import { existsSync as existsSync122, mkdirSync as mkdirSync83 } from "node:fs";
628739
+ import { join as join181 } from "node:path";
628174
628740
  var DEFAULT_CONFIG4 = {
628175
628741
  duration: 180,
628176
628742
  bpm: null,
@@ -628209,11 +628775,11 @@ var SOUNDSCAPES = {
628209
628775
  bpm: 70
628210
628776
  }
628211
628777
  };
628212
- var CACHE_DIR3 = join177(process.env.HOME || "~", ".8gent", "adhd-audio");
628213
- var CONFIG_PATH = join177(CACHE_DIR3, "config.json");
628778
+ var CACHE_DIR3 = join181(process.env.HOME || "~", ".8gent", "adhd-audio");
628779
+ var CONFIG_PATH = join181(CACHE_DIR3, "config.json");
628214
628780
  function loadConfig2() {
628215
628781
  try {
628216
- if (existsSync118(CONFIG_PATH)) {
628782
+ if (existsSync122(CONFIG_PATH)) {
628217
628783
  const raw = JSON.parse(__require("node:fs").readFileSync(CONFIG_PATH, "utf-8"));
628218
628784
  return { ...DEFAULT_CONFIG4, ...raw };
628219
628785
  }
@@ -628222,8 +628788,8 @@ function loadConfig2() {
628222
628788
  }
628223
628789
  function saveConfig2(config) {
628224
628790
  try {
628225
- if (!existsSync118(CACHE_DIR3))
628226
- mkdirSync82(CACHE_DIR3, { recursive: true });
628791
+ if (!existsSync122(CACHE_DIR3))
628792
+ mkdirSync83(CACHE_DIR3, { recursive: true });
628227
628793
  __require("node:fs").writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
628228
628794
  } catch {}
628229
628795
  }
@@ -628234,8 +628800,8 @@ class ADHDAudio {
628234
628800
  generating = false;
628235
628801
  _config;
628236
628802
  constructor() {
628237
- if (!existsSync118(CACHE_DIR3)) {
628238
- mkdirSync82(CACHE_DIR3, { recursive: true });
628803
+ if (!existsSync122(CACHE_DIR3)) {
628804
+ mkdirSync83(CACHE_DIR3, { recursive: true });
628239
628805
  }
628240
628806
  this._config = loadConfig2();
628241
628807
  }
@@ -628261,7 +628827,7 @@ class ADHDAudio {
628261
628827
  for (const soundscape of Object.keys(SOUNDSCAPES)) {
628262
628828
  const path = this.getCachePath(soundscape);
628263
628829
  try {
628264
- if (existsSync118(path))
628830
+ if (existsSync122(path))
628265
628831
  __require("node:fs").unlinkSync(path);
628266
628832
  } catch {}
628267
628833
  }
@@ -628277,10 +628843,10 @@ class ADHDAudio {
628277
628843
  }
628278
628844
  }
628279
628845
  getCachePath(soundscape) {
628280
- return join177(CACHE_DIR3, `${soundscape}.mp3`);
628846
+ return join181(CACHE_DIR3, `${soundscape}.mp3`);
628281
628847
  }
628282
628848
  hasCached(soundscape) {
628283
- return existsSync118(this.getCachePath(soundscape));
628849
+ return existsSync122(this.getCachePath(soundscape));
628284
628850
  }
628285
628851
  async generate(soundscape) {
628286
628852
  const preset = SOUNDSCAPES[soundscape];
@@ -628689,325 +629255,8 @@ class DaemonSessionClient {
628689
629255
  }
628690
629256
  }
628691
629257
 
628692
- // apps/tui/src/lib/external-agent-runner.ts
628693
- init_shell();
628694
- import { execSync as execSync7, spawn as spawn24 } from "node:child_process";
628695
- var stripAnsi2 = (s) => s.replace(/\[[0-9;?]*[a-zA-Z]/g, "").replace(/\][^]*/g, "");
628696
- var EXTERNAL_AGENT_PRESETS = {
628697
- claude: {
628698
- id: "claude",
628699
- label: "Sparring",
628700
- command: "claude",
628701
- promptMode: "flag",
628702
- flagName: "-p",
628703
- args: [],
628704
- timeoutMs: 120000,
628705
- parseStdout: stripAnsi2,
628706
- homepage: "https://docs.claude.com/en/docs/claude-code",
628707
- preferWindow: true,
628708
- install: {
628709
- command: "npm install -g --prefix=$HOME/.npm-global @anthropic-ai/claude-code",
628710
- 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.'
628711
- }
628712
- },
628713
- codex: {
628714
- id: "codex",
628715
- label: "OpenAI Codex",
628716
- command: "codex",
628717
- promptMode: "arg",
628718
- args: ["exec"],
628719
- timeoutMs: 120000,
628720
- parseStdout: stripAnsi2,
628721
- homepage: "https://github.com/openai/codex",
628722
- preferWindow: true,
628723
- install: {
628724
- command: "npm install -g --prefix=$HOME/.npm-global @openai/codex",
628725
- notes: "Binary at ~/.npm-global/bin/codex. Add `~/.npm-global/bin` to PATH if needed. Requires `export OPENAI_API_KEY=sk-...` before /spawn."
628726
- }
628727
- },
628728
- hermes: {
628729
- id: "hermes",
628730
- label: "Hermes Agent",
628731
- command: "hermes",
628732
- preferWindow: true,
628733
- promptMode: "flag",
628734
- flagName: "-z",
628735
- args: [],
628736
- timeoutMs: 120000,
628737
- parseStdout: stripAnsi2,
628738
- homepage: "https://github.com/NousResearch/hermes-agent",
628739
- install: {
628740
- command: "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup --no-venv",
628741
- notes: "Hermes needs Python ≥ 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)."
628742
- }
628743
- },
628744
- openclaw: {
628745
- id: "openclaw",
628746
- label: "OpenClaw",
628747
- command: "openclaw",
628748
- preferWindow: true,
628749
- promptMode: "arg",
628750
- args: ["run", "--headless"],
628751
- timeoutMs: 120000,
628752
- parseStdout: stripAnsi2,
628753
- homepage: "https://github.com/openclaw/openclaw",
628754
- install: {
628755
- command: "npm install -g --prefix=$HOME/.npm-global openclaw@latest",
628756
- 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.)"
628757
- },
628758
- requiresNodeVersion: "22.14.0"
628759
- },
628760
- pi: {
628761
- preferWindow: true,
628762
- id: "pi",
628763
- label: "Pi (pi-mono)",
628764
- command: "pi",
628765
- promptMode: "flag",
628766
- flagName: "-p",
628767
- args: [],
628768
- timeoutMs: 120000,
628769
- parseStdout: stripAnsi2,
628770
- homepage: "https://pi.dev",
628771
- install: {
628772
- command: "npm install -g --prefix=$HOME/.npm-global @mariozechner/pi-coding-agent",
628773
- 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)."
628774
- }
628775
- },
628776
- "8gent": {
628777
- id: "8gent",
628778
- label: "8gent (nested)",
628779
- command: "8gent",
628780
- promptMode: "arg",
628781
- args: ["run"],
628782
- timeoutMs: 180000,
628783
- parseStdout: stripAnsi2,
628784
- homepage: "https://8gent.dev",
628785
- install: {
628786
- command: "npm install -g --prefix=$HOME/.npm-global @8gi-foundation/8gent-code --force",
628787
- 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."
628788
- }
628789
- }
628790
- };
628791
- function getPreset(id) {
628792
- return EXTERNAL_AGENT_PRESETS[id.toLowerCase()] ?? null;
628793
- }
628794
- function listPresetIds() {
628795
- return Object.keys(EXTERNAL_AGENT_PRESETS);
628796
- }
628797
- function isInstalled(preset) {
628798
- try {
628799
- execSync7(`command -v "${preset.command}"`, {
628800
- stdio: "ignore",
628801
- timeout: 1500
628802
- });
628803
- return true;
628804
- } catch {
628805
- return false;
628806
- }
628807
- }
628808
- async function installAgent(preset, onLine) {
628809
- const start = performance.now();
628810
- if (!preset.install) {
628811
- return {
628812
- ok: false,
628813
- stdout: "",
628814
- stderr: "",
628815
- durationMs: 0,
628816
- command: "",
628817
- error: `No install recipe for ${preset.id}. Install ${preset.command} manually and try again.`
628818
- };
628819
- }
628820
- const command = preset.install.command;
628821
- return new Promise((resolve) => {
628822
- let stdout = "";
628823
- let stderr = "";
628824
- let exited = false;
628825
- const proc = spawnShell(command, {
628826
- posix: ["bash", "-lc"],
628827
- stdio: ["ignore", "pipe", "pipe"],
628828
- env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" }
628829
- });
628830
- const finish = (result) => {
628831
- if (exited)
628832
- return;
628833
- exited = true;
628834
- clearTimeout(timer);
628835
- resolve({
628836
- ...result,
628837
- durationMs: Math.round(performance.now() - start),
628838
- command
628839
- });
628840
- };
628841
- const pipeLines = (chunk, source) => {
628842
- const text = chunk.toString();
628843
- if (source === "stdout")
628844
- stdout += text;
628845
- else
628846
- stderr += text;
628847
- if (onLine) {
628848
- for (const line of text.split(/\r?\n/)) {
628849
- if (line.trim().length > 0)
628850
- onLine(line, source);
628851
- }
628852
- }
628853
- };
628854
- proc.stdout?.on("data", (c) => pipeLines(c, "stdout"));
628855
- proc.stderr?.on("data", (c) => pipeLines(c, "stderr"));
628856
- proc.on("error", (err) => {
628857
- finish({ ok: false, stdout, stderr, error: err.message });
628858
- });
628859
- proc.on("close", (code) => {
628860
- finish({
628861
- ok: code === 0,
628862
- stdout,
628863
- stderr,
628864
- error: code === 0 ? undefined : stderr.trim().slice(0, 800) || `installer exited with code ${code}`
628865
- });
628866
- });
628867
- const timer = setTimeout(() => {
628868
- proc.kill("SIGTERM");
628869
- finish({ ok: false, stdout, stderr, error: "install timed out after 5 minutes" });
628870
- }, 5 * 60 * 1000);
628871
- });
628872
- }
628873
- async function ensureInstalled(preset, onLine) {
628874
- if (isInstalled(preset))
628875
- return true;
628876
- if (!preset.install) {
628877
- onLine?.(`${preset.command}: not installed and no auto-install recipe is configured.`, "info");
628878
- return false;
628879
- }
628880
- onLine?.(`${preset.command}: not installed, running ${preset.install.command}`, "info");
628881
- const result = await installAgent(preset, (line, source) => onLine?.(line, source));
628882
- if (!result.ok) {
628883
- onLine?.(`Install failed: ${result.error ?? "unknown error"}`, "info");
628884
- return false;
628885
- }
628886
- const stillThere = isInstalled(preset);
628887
- if (!stillThere) {
628888
- 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");
628889
- }
628890
- return stillThere;
628891
- }
628892
- function composePrompt(history, prompt) {
628893
- const lines = [];
628894
- for (const m of history) {
628895
- const tag = m.role === "user" ? "User" : m.role === "assistant" ? "Assistant" : m.role;
628896
- lines.push(`${tag}: ${m.content}`);
628897
- }
628898
- lines.push(`User: ${prompt}`);
628899
- lines.push("Assistant:");
628900
- return lines.join(`
628901
-
628902
- `);
628903
- }
628904
- async function runExternalAgent(preset, prompt, signal) {
628905
- const start = performance.now();
628906
- const argv = [...preset.args];
628907
- if (preset.promptMode === "flag") {
628908
- argv.push(preset.flagName ?? "-p", prompt);
628909
- } else if (preset.promptMode === "arg") {
628910
- argv.push(prompt);
628911
- }
628912
- let pathPrefix = "";
628913
- if (preset.requiresNodeVersion) {
628914
- try {
628915
- await Promise.resolve().then(() => init_runtime());
628916
- const binDir = await ensureNodeFor(preset.requiresNodeVersion);
628917
- if (binDir)
628918
- pathPrefix = `${binDir}:`;
628919
- } catch (err) {
628920
- return {
628921
- ok: false,
628922
- text: "",
628923
- durationMs: Math.round(performance.now() - start),
628924
- command: `${preset.command} ${argv.join(" ")}`,
628925
- error: `managed Node runtime install failed: ${err instanceof Error ? err.message : String(err)}`,
628926
- exitCode: null
628927
- };
628928
- }
628929
- }
628930
- return new Promise((resolve) => {
628931
- let stdout = "";
628932
- let stderr = "";
628933
- let exited = false;
628934
- const proc = spawn24(preset.command, argv, {
628935
- stdio: ["pipe", "pipe", "pipe"],
628936
- env: {
628937
- ...process.env,
628938
- PATH: `${pathPrefix}${process.env.PATH ?? ""}`,
628939
- NO_COLOR: "1",
628940
- FORCE_COLOR: "0",
628941
- "8GENT_NO_INTRO": "1",
628942
- AI_SDK_LOG_WARNINGS: "false"
628943
- }
628944
- });
628945
- const finish = (result) => {
628946
- if (exited)
628947
- return;
628948
- exited = true;
628949
- clearTimeout(timer);
628950
- signal?.removeEventListener("abort", onAbort);
628951
- resolve({
628952
- ...result,
628953
- durationMs: Math.round(performance.now() - start),
628954
- command: `${preset.command} ${argv.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`
628955
- });
628956
- };
628957
- proc.on("error", (err) => {
628958
- finish({
628959
- ok: false,
628960
- text: "",
628961
- error: err.code === "ENOENT" ? `${preset.command}: not installed (no binary on $PATH)` : err.message,
628962
- exitCode: null
628963
- });
628964
- });
628965
- proc.stdout?.on("data", (chunk) => {
628966
- stdout += chunk.toString();
628967
- });
628968
- proc.stderr?.on("data", (chunk) => {
628969
- stderr += chunk.toString();
628970
- });
628971
- proc.on("close", (code) => {
628972
- let text = preset.parseStdout ? preset.parseStdout(stdout).trim() : stdout.trim();
628973
- try {
628974
- init_keys();
628975
- if (text)
628976
- text = redactAndWarn(text);
628977
- } catch {}
628978
- if (code === 0 || code !== null && text.length > 0) {
628979
- finish({ ok: code === 0, text, exitCode: code, error: code === 0 ? undefined : stderr.trim().slice(0, 400) });
628980
- } else {
628981
- finish({
628982
- ok: false,
628983
- text,
628984
- exitCode: code,
628985
- error: stderr.trim().slice(0, 400) || `exited with code ${code}`
628986
- });
628987
- }
628988
- });
628989
- if (preset.promptMode === "stdin" && proc.stdin) {
628990
- proc.stdin.write(prompt);
628991
- proc.stdin.end();
628992
- } else if (proc.stdin) {
628993
- proc.stdin.end();
628994
- }
628995
- const timer = setTimeout(() => {
628996
- proc.kill("SIGTERM");
628997
- finish({
628998
- ok: false,
628999
- text: stdout,
629000
- error: `timeout after ${preset.timeoutMs ?? 90000}ms`,
629001
- exitCode: null
629002
- });
629003
- }, preset.timeoutMs ?? 90000);
629004
- const onAbort = () => {
629005
- proc.kill("SIGTERM");
629006
- finish({ ok: false, text: stdout, error: "aborted", exitCode: null });
629007
- };
629008
- signal?.addEventListener("abort", onAbort, { once: true });
629009
- });
629010
- }
629258
+ // apps/tui/src/app.tsx
629259
+ init_external_agent_runner();
629011
629260
 
629012
629261
  // packages/daemon/goal-rpc.ts
629013
629262
  import { randomUUID as randomUUID7 } from "node:crypto";
@@ -630290,7 +630539,7 @@ class InProcessGoalTransport {
630290
630539
  // apps/tui/src/lib/intro-gate.ts
630291
630540
  init_home();
630292
630541
  import { mkdirSync as mkdirSync85, readFileSync as readFileSync109, writeFileSync as writeFileSync65 } from "node:fs";
630293
- import { join as join184 } from "node:path";
630542
+ import { join as join185 } from "node:path";
630294
630543
  function shouldShowIntro({ setting, env, seenVersion, version }) {
630295
630544
  if (setting === "off")
630296
630545
  return false;
@@ -630301,7 +630550,7 @@ function shouldShowIntro({ setting, env, seenVersion, version }) {
630301
630550
  return seenVersion !== version;
630302
630551
  }
630303
630552
  function seenPath(home) {
630304
- return join184(home, ".8gent", "intro-seen");
630553
+ return join185(home, ".8gent", "intro-seen");
630305
630554
  }
630306
630555
  function readSeenVersion(home = resolveHome()) {
630307
630556
  try {
@@ -630313,7 +630562,7 @@ function readSeenVersion(home = resolveHome()) {
630313
630562
  }
630314
630563
  function markIntroSeen(version, home = resolveHome()) {
630315
630564
  try {
630316
- mkdirSync85(join184(home, ".8gent"), { recursive: true });
630565
+ mkdirSync85(join185(home, ".8gent"), { recursive: true });
630317
630566
  writeFileSync65(seenPath(home), `${version}
630318
630567
  `);
630319
630568
  } catch {}
@@ -632006,12 +632255,12 @@ function sanitizeFreeText(value) {
632006
632255
  // packages/voice/audio-gate.ts
632007
632256
  init_home();
632008
632257
  import { readFileSync as readFileSync111 } from "node:fs";
632009
- import { join as join187 } from "node:path";
632258
+ import { join as join188 } from "node:path";
632010
632259
  function audioOutputAllowed(env = process.env) {
632011
632260
  if (env.EIGHT_VOICE === "1")
632012
632261
  return true;
632013
632262
  try {
632014
- const raw = JSON.parse(readFileSync111(join187(resolveHome(env), ".8gent", "settings.json"), "utf8"));
632263
+ const raw = JSON.parse(readFileSync111(join188(resolveHome(env), ".8gent", "settings.json"), "utf8"));
632015
632264
  return raw?.voice?.outputEnabled === true;
632016
632265
  } catch {
632017
632266
  return false;
@@ -633185,10 +633434,10 @@ function snapshot(manager) {
633185
633434
 
633186
633435
  // apps/tui/src/hooks/useBodyParts.ts
633187
633436
  var import_react83 = __toESM(require_react(), 1);
633188
- import { existsSync as existsSync123 } from "node:fs";
633437
+ import { existsSync as existsSync125 } from "node:fs";
633189
633438
  import { execSync as execSync8 } from "node:child_process";
633190
633439
  import { homedir as homedir90 } from "node:os";
633191
- import { join as join188 } from "node:path";
633440
+ import { join as join189 } from "node:path";
633192
633441
  var PULSE_FLASH_MS = 150;
633193
633442
  function hasCliclick() {
633194
633443
  try {
@@ -633199,7 +633448,7 @@ function hasCliclick() {
633199
633448
  }
633200
633449
  }
633201
633450
  function hasAxBridge() {
633202
- return existsSync123(join188(homedir90(), ".8gent", "bin", "8gent-ax-bridge"));
633451
+ return existsSync125(join189(homedir90(), ".8gent", "bin", "8gent-ax-bridge"));
633203
633452
  }
633204
633453
  function detectDefaultBodyPartsState() {
633205
633454
  const handsOk = hasCliclick();
@@ -633487,6 +633736,7 @@ function onChange(fn) {
633487
633736
  init_shell();
633488
633737
  init_secret_scanner();
633489
633738
  init_path_guard();
633739
+ init_permission_mode();
633490
633740
  init_eight_browser();
633491
633741
 
633492
633742
  // apps/tui/src/lib/consult-command.ts
@@ -633527,8 +633777,16 @@ function sendRefusal(command) {
633527
633777
  return null;
633528
633778
  }
633529
633779
  function planModeRefusal2(mode) {
633530
- return mode === "plan" ? "Plan mode runs nothing, shell lines included. Shift+Tab to change mode, then run it again." : null;
633780
+ 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;
633781
+ }
633782
+ function parseModeArgs(args) {
633783
+ const words = args.flatMap((a) => a.trim().split(/\s+/)).filter(Boolean);
633784
+ if (words.length !== 1)
633785
+ return null;
633786
+ const name = words[0].toLowerCase();
633787
+ return isPermissionMode(name) ? name : null;
633531
633788
  }
633789
+ var MODE_USAGE = "Usage: /mode plan | ask | guarded | infinite (this tab's permission mode).";
633532
633790
  var ANSI2 = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[ -/]*[0-~]/g;
633533
633791
  var CONTROL4 = /[\x00-\x08\x0b-\x1f\x7f]/g;
633534
633792
  function cleanOutput(raw) {
@@ -634787,6 +635045,24 @@ ${r.message}`;
634787
635045
  // apps/tui/src/app.tsx
634788
635046
  init_slash_commands();
634789
635047
 
635048
+ // apps/tui/src/lib/term-intent.ts
635049
+ var VERBS2 = "run|open|start|launch|fire up|spin up|boot";
635050
+ var POLITE = "(?:please\\s+|can you\\s+|could you\\s+)?";
635051
+ var WHERE = "(?:\\s+in\\s+(?:a\\s+)?(?:new\\s+)?(?:tab|terminal(?:\\s+tab)?))?";
635052
+ var PRESET_INTENT = new RegExp(`^${POLITE}(?:${VERBS2})\\s+(?:up\\s+)?([a-z0-9][a-z0-9_-]*)(${WHERE})(?:\\s+please)?$`);
635053
+ function detectTermPresetIntent(input, presetIds) {
635054
+ const text = input.trim().toLowerCase().replace(/[.!?]+$/, "").replace(/\s+/g, " ");
635055
+ if (!text || text.length > 60)
635056
+ return null;
635057
+ const m = PRESET_INTENT.exec(text);
635058
+ if (!m)
635059
+ return null;
635060
+ const id = presetIds.find((p) => p.toLowerCase() === m[1]);
635061
+ if (!id)
635062
+ return null;
635063
+ return m[2] ? [id, "--in-tab"] : [id];
635064
+ }
635065
+
634790
635066
  // apps/tui/src/lib/turn-outcome.ts
634791
635067
  init_local_turn_error();
634792
635068
  function turnEndedInError(messages) {
@@ -634804,8 +635080,8 @@ function turnEndedInError(messages) {
634804
635080
  }
634805
635081
 
634806
635082
  // apps/tui/src/screens/BTWView.tsx
634807
- import { existsSync as existsSync125, mkdirSync as mkdirSync89, readFileSync as readFileSync113, writeFileSync as writeFileSync69 } from "node:fs";
634808
- import { join as join190 } from "node:path";
635083
+ import { existsSync as existsSync127, mkdirSync as mkdirSync89, readFileSync as readFileSync113, writeFileSync as writeFileSync69 } from "node:fs";
635084
+ import { join as join191 } from "node:path";
634809
635085
  var import_react85 = __toESM(require_react(), 1);
634810
635086
 
634811
635087
  // apps/tui/src/components/primitives/Divider.tsx
@@ -634817,13 +635093,13 @@ var Divider = ({ width = 60, character = "─" }) => /* @__PURE__ */ jsx_dev_run
634817
635093
 
634818
635094
  // apps/tui/src/screens/BTWView.tsx
634819
635095
  var jsx_dev_runtime50 = __toESM(require_jsx_dev_runtime(), 1);
634820
- var DATA_DIR5 = join190(process.env.HOME || "~", ".8gent", "tabs");
635096
+ var DATA_DIR5 = join191(process.env.HOME || "~", ".8gent", "tabs");
634821
635097
  function loadBTW() {
634822
635098
  try {
634823
- if (!existsSync125(DATA_DIR5))
635099
+ if (!existsSync127(DATA_DIR5))
634824
635100
  mkdirSync89(DATA_DIR5, { recursive: true });
634825
- const filepath = join190(DATA_DIR5, "btw.json");
634826
- if (existsSync125(filepath)) {
635101
+ const filepath = join191(DATA_DIR5, "btw.json");
635102
+ if (existsSync127(filepath)) {
634827
635103
  const raw = JSON.parse(readFileSync113(filepath, "utf-8"));
634828
635104
  return raw.items || [];
634829
635105
  }
@@ -634832,9 +635108,9 @@ function loadBTW() {
634832
635108
  }
634833
635109
  function saveBTW(items) {
634834
635110
  try {
634835
- if (!existsSync125(DATA_DIR5))
635111
+ if (!existsSync127(DATA_DIR5))
634836
635112
  mkdirSync89(DATA_DIR5, { recursive: true });
634837
- writeFileSync69(join190(DATA_DIR5, "btw.json"), JSON.stringify({ items }, null, 2));
635113
+ writeFileSync69(join191(DATA_DIR5, "btw.json"), JSON.stringify({ items }, null, 2));
634838
635114
  } catch {}
634839
635115
  }
634840
635116
  function sortItems(items) {
@@ -635167,17 +635443,17 @@ function HistoryScreen({ conversations, onSelect, onBack }) {
635167
635443
  }
635168
635444
 
635169
635445
  // apps/tui/src/screens/IdeasView.tsx
635170
- import { existsSync as existsSync126, mkdirSync as mkdirSync90, readFileSync as readFileSync114, writeFileSync as writeFileSync70 } from "node:fs";
635171
- import { join as join191 } from "node:path";
635446
+ import { existsSync as existsSync128, mkdirSync as mkdirSync90, readFileSync as readFileSync114, writeFileSync as writeFileSync70 } from "node:fs";
635447
+ import { join as join192 } from "node:path";
635172
635448
  var import_react87 = __toESM(require_react(), 1);
635173
635449
  var jsx_dev_runtime52 = __toESM(require_jsx_dev_runtime(), 1);
635174
- var DATA_DIR6 = join191(process.env.HOME || "~", ".8gent", "tabs");
635450
+ var DATA_DIR6 = join192(process.env.HOME || "~", ".8gent", "tabs");
635175
635451
  function loadIdeas() {
635176
635452
  try {
635177
- if (!existsSync126(DATA_DIR6))
635453
+ if (!existsSync128(DATA_DIR6))
635178
635454
  mkdirSync90(DATA_DIR6, { recursive: true });
635179
- const filepath = join191(DATA_DIR6, "ideas.json");
635180
- if (existsSync126(filepath)) {
635455
+ const filepath = join192(DATA_DIR6, "ideas.json");
635456
+ if (existsSync128(filepath)) {
635181
635457
  const raw = JSON.parse(readFileSync114(filepath, "utf-8"));
635182
635458
  return raw.ideas || [];
635183
635459
  }
@@ -635186,9 +635462,9 @@ function loadIdeas() {
635186
635462
  }
635187
635463
  function saveIdeas(ideas) {
635188
635464
  try {
635189
- if (!existsSync126(DATA_DIR6))
635465
+ if (!existsSync128(DATA_DIR6))
635190
635466
  mkdirSync90(DATA_DIR6, { recursive: true });
635191
- writeFileSync70(join191(DATA_DIR6, "ideas.json"), JSON.stringify({ ideas }, null, 2));
635467
+ writeFileSync70(join192(DATA_DIR6, "ideas.json"), JSON.stringify({ ideas }, null, 2));
635192
635468
  } catch {}
635193
635469
  }
635194
635470
  function extractTags2(text) {
@@ -635649,11 +635925,11 @@ function KeyVaultScreen({
635649
635925
  }
635650
635926
 
635651
635927
  // apps/tui/src/screens/MusicPlayerView.tsx
635652
- import { existsSync as existsSync127, readdirSync as readdirSync28, statSync as statSync39 } from "node:fs";
635653
- import { basename as basename53, join as join192 } from "node:path";
635928
+ import { existsSync as existsSync129, readdirSync as readdirSync28, statSync as statSync39 } from "node:fs";
635929
+ import { basename as basename54, join as join193 } from "node:path";
635654
635930
  var import_react89 = __toESM(require_react(), 1);
635655
635931
  var jsx_dev_runtime54 = __toESM(require_jsx_dev_runtime(), 1);
635656
- var CACHE_DIR4 = join192(process.env.HOME || "~", ".8gent", "adhd-audio");
635932
+ var CACHE_DIR4 = join193(process.env.HOME || "~", ".8gent", "adhd-audio");
635657
635933
  function formatBytes(bytes) {
635658
635934
  if (bytes < 1024)
635659
635935
  return `${bytes}B`;
@@ -635671,20 +635947,20 @@ function formatDate(date) {
635671
635947
  }
635672
635948
  function loadTracks(currentTrack) {
635673
635949
  const tracks = [];
635674
- if (!existsSync127(CACHE_DIR4))
635950
+ if (!existsSync129(CACHE_DIR4))
635675
635951
  return tracks;
635676
635952
  try {
635677
635953
  const files = readdirSync28(CACHE_DIR4).filter((f) => f.endsWith(".mp3") || f.endsWith(".wav"));
635678
635954
  for (const file of files) {
635679
- const filePath = join192(CACHE_DIR4, file);
635955
+ const filePath = join193(CACHE_DIR4, file);
635680
635956
  const stat = statSync39(filePath);
635681
- const name = basename53(file, ".mp3").replace(/-/g, " ");
635957
+ const name = basename54(file, ".mp3").replace(/-/g, " ");
635682
635958
  tracks.push({
635683
635959
  name: name.charAt(0).toUpperCase() + name.slice(1),
635684
635960
  path: filePath,
635685
635961
  size: formatBytes(stat.size),
635686
635962
  modified: formatDate(stat.mtime),
635687
- playing: currentTrack === name.toLowerCase() || currentTrack === basename53(file, ".mp3")
635963
+ playing: currentTrack === name.toLowerCase() || currentTrack === basename54(file, ".mp3")
635688
635964
  });
635689
635965
  }
635690
635966
  } catch {}
@@ -636180,18 +636456,18 @@ function NarratorView({ tasks, narratorText, maxHeight }) {
636180
636456
  }
636181
636457
 
636182
636458
  // apps/tui/src/screens/NotesView.tsx
636183
- import { existsSync as existsSync128, mkdirSync as mkdirSync91, readFileSync as readFileSync115, writeFileSync as writeFileSync71 } from "node:fs";
636184
- import { join as join193 } from "node:path";
636459
+ import { existsSync as existsSync130, mkdirSync as mkdirSync91, readFileSync as readFileSync115, writeFileSync as writeFileSync71 } from "node:fs";
636460
+ import { join as join194 } from "node:path";
636185
636461
  var import_react91 = __toESM(require_react(), 1);
636186
636462
  var jsx_dev_runtime59 = __toESM(require_jsx_dev_runtime(), 1);
636187
- var DATA_DIR7 = join193(process.env.HOME || "~", ".8gent", "tabs");
636463
+ var DATA_DIR7 = join194(process.env.HOME || "~", ".8gent", "tabs");
636188
636464
  var EMPTY_TAB_NAMES = [];
636189
636465
  function loadNotes() {
636190
636466
  try {
636191
- if (!existsSync128(DATA_DIR7))
636467
+ if (!existsSync130(DATA_DIR7))
636192
636468
  mkdirSync91(DATA_DIR7, { recursive: true });
636193
- const filepath = join193(DATA_DIR7, "notes.json");
636194
- if (existsSync128(filepath)) {
636469
+ const filepath = join194(DATA_DIR7, "notes.json");
636470
+ if (existsSync130(filepath)) {
636195
636471
  const raw = JSON.parse(readFileSync115(filepath, "utf-8"));
636196
636472
  return raw.notes || [];
636197
636473
  }
@@ -636200,9 +636476,9 @@ function loadNotes() {
636200
636476
  }
636201
636477
  function saveNotes(notes) {
636202
636478
  try {
636203
- if (!existsSync128(DATA_DIR7))
636479
+ if (!existsSync130(DATA_DIR7))
636204
636480
  mkdirSync91(DATA_DIR7, { recursive: true });
636205
- writeFileSync71(join193(DATA_DIR7, "notes.json"), JSON.stringify({ notes }, null, 2));
636481
+ writeFileSync71(join194(DATA_DIR7, "notes.json"), JSON.stringify({ notes }, null, 2));
636206
636482
  } catch {}
636207
636483
  }
636208
636484
  function NotesView({
@@ -637395,40 +637671,40 @@ function OnboardingScreen({
637395
637671
 
637396
637672
  // apps/tui/src/screens/ProjectsView.tsx
637397
637673
  import { execSync as execSync9 } from "node:child_process";
637398
- import { existsSync as existsSync129, mkdirSync as mkdirSync92, readFileSync as readFileSync116, readdirSync as readdirSync29, statSync as statSync40, writeFileSync as writeFileSync72 } from "node:fs";
637674
+ import { existsSync as existsSync131, mkdirSync as mkdirSync92, readFileSync as readFileSync116, readdirSync as readdirSync29, statSync as statSync40, writeFileSync as writeFileSync72 } from "node:fs";
637399
637675
  import { homedir as homedir91 } from "node:os";
637400
- import { basename as basename54, join as join194 } from "node:path";
637676
+ import { basename as basename55, join as join195 } from "node:path";
637401
637677
  var import_react96 = __toESM(require_react(), 1);
637402
637678
  var jsx_dev_runtime61 = __toESM(require_jsx_dev_runtime(), 1);
637403
- var SAVED_PROJECTS_PATH = join194(homedir91(), ".8gent", "tabs", "projects.json");
637679
+ var SAVED_PROJECTS_PATH = join195(homedir91(), ".8gent", "tabs", "projects.json");
637404
637680
  var SCAN_DIRS = [
637405
637681
  homedir91(),
637406
- join194(homedir91(), "Documents"),
637407
- join194(homedir91(), "Projects"),
637408
- join194(homedir91(), "Code")
637682
+ join195(homedir91(), "Documents"),
637683
+ join195(homedir91(), "Projects"),
637684
+ join195(homedir91(), "Code")
637409
637685
  ];
637410
637686
  var PROJECT_MARKERS = ["package.json", ".git", "CLAUDE.md", ".8gent"];
637411
637687
  function isProjectDir(dirPath) {
637412
637688
  try {
637413
- return PROJECT_MARKERS.some((marker) => existsSync129(join194(dirPath, marker)));
637689
+ return PROJECT_MARKERS.some((marker) => existsSync131(join195(dirPath, marker)));
637414
637690
  } catch {
637415
637691
  return false;
637416
637692
  }
637417
637693
  }
637418
637694
  function getProjectName(dirPath) {
637419
637695
  try {
637420
- const pkgPath = join194(dirPath, "package.json");
637421
- if (existsSync129(pkgPath)) {
637696
+ const pkgPath = join195(dirPath, "package.json");
637697
+ if (existsSync131(pkgPath)) {
637422
637698
  const pkg = JSON.parse(readFileSync116(pkgPath, "utf8"));
637423
637699
  if (pkg.name)
637424
637700
  return pkg.name;
637425
637701
  }
637426
637702
  } catch {}
637427
- return basename54(dirPath);
637703
+ return basename55(dirPath);
637428
637704
  }
637429
637705
  function getGitBranch2(dirPath) {
637430
637706
  try {
637431
- if (!existsSync129(join194(dirPath, ".git")))
637707
+ if (!existsSync131(join195(dirPath, ".git")))
637432
637708
  return;
637433
637709
  const result = execSync9(`git -C "${dirPath}" branch --show-current 2>/dev/null`, {
637434
637710
  encoding: "utf8",
@@ -637448,7 +637724,7 @@ function getLastModified(dirPath) {
637448
637724
  }
637449
637725
  }
637450
637726
  function has8gent(dirPath) {
637451
- return existsSync129(join194(dirPath, ".8gent"));
637727
+ return existsSync131(join195(dirPath, ".8gent"));
637452
637728
  }
637453
637729
  function buildProjectEntry(dirPath) {
637454
637730
  return {
@@ -637465,7 +637741,7 @@ function scanForProjects(currentPath) {
637465
637741
  found.add(currentPath);
637466
637742
  results.push(buildProjectEntry(currentPath));
637467
637743
  for (const scanDir of SCAN_DIRS) {
637468
- if (!existsSync129(scanDir))
637744
+ if (!existsSync131(scanDir))
637469
637745
  continue;
637470
637746
  try {
637471
637747
  const entries = readdirSync29(scanDir, { withFileTypes: true });
@@ -637474,7 +637750,7 @@ function scanForProjects(currentPath) {
637474
637750
  continue;
637475
637751
  if (entry.name.startsWith("."))
637476
637752
  continue;
637477
- const fullPath = join194(scanDir, entry.name);
637753
+ const fullPath = join195(scanDir, entry.name);
637478
637754
  if (found.has(fullPath))
637479
637755
  continue;
637480
637756
  if (isProjectDir(fullPath)) {
@@ -637497,7 +637773,7 @@ function scanForProjects(currentPath) {
637497
637773
  }
637498
637774
  function loadSavedProjects() {
637499
637775
  try {
637500
- if (!existsSync129(SAVED_PROJECTS_PATH))
637776
+ if (!existsSync131(SAVED_PROJECTS_PATH))
637501
637777
  return [];
637502
637778
  const raw = readFileSync116(SAVED_PROJECTS_PATH, "utf8");
637503
637779
  const data = JSON.parse(raw);
@@ -637508,7 +637784,7 @@ function loadSavedProjects() {
637508
637784
  }
637509
637785
  function saveSavedProjects(projects) {
637510
637786
  try {
637511
- const dir = join194(homedir91(), ".8gent", "tabs");
637787
+ const dir = join195(homedir91(), ".8gent", "tabs");
637512
637788
  mkdirSync92(dir, { recursive: true });
637513
637789
  const data = { projects };
637514
637790
  writeFileSync72(SAVED_PROJECTS_PATH, JSON.stringify(data, null, 2), "utf8");
@@ -637520,7 +637796,7 @@ function mergeProjects(scanned, saved) {
637520
637796
  for (const s of saved) {
637521
637797
  if (paths.has(s.path))
637522
637798
  continue;
637523
- if (!existsSync129(s.path))
637799
+ if (!existsSync131(s.path))
637524
637800
  continue;
637525
637801
  paths.add(s.path);
637526
637802
  extra.push(buildProjectEntry(s.path));
@@ -637588,7 +637864,7 @@ function ProjectsView({
637588
637864
  if (addMode) {
637589
637865
  if (key.return) {
637590
637866
  const trimmed = addInput.trim();
637591
- if (trimmed && existsSync129(trimmed)) {
637867
+ if (trimmed && existsSync131(trimmed)) {
637592
637868
  const newEntry = {
637593
637869
  ...buildProjectEntry(trimmed),
637594
637870
  addedAt: new Date().toISOString()
@@ -637775,17 +638051,17 @@ function ProjectsView({
637775
638051
  }
637776
638052
 
637777
638053
  // apps/tui/src/screens/QuestionsView.tsx
637778
- import { existsSync as existsSync130, mkdirSync as mkdirSync93, readFileSync as readFileSync117, writeFileSync as writeFileSync73 } from "node:fs";
637779
- import { join as join195 } from "node:path";
638054
+ import { existsSync as existsSync132, mkdirSync as mkdirSync93, readFileSync as readFileSync117, writeFileSync as writeFileSync73 } from "node:fs";
638055
+ import { join as join196 } from "node:path";
637780
638056
  var import_react97 = __toESM(require_react(), 1);
637781
638057
  var jsx_dev_runtime62 = __toESM(require_jsx_dev_runtime(), 1);
637782
- var DATA_DIR8 = join195(process.env.HOME || "~", ".8gent", "tabs");
638058
+ var DATA_DIR8 = join196(process.env.HOME || "~", ".8gent", "tabs");
637783
638059
  function loadQuestions() {
637784
638060
  try {
637785
- if (!existsSync130(DATA_DIR8))
638061
+ if (!existsSync132(DATA_DIR8))
637786
638062
  mkdirSync93(DATA_DIR8, { recursive: true });
637787
- const filepath = join195(DATA_DIR8, "questions.json");
637788
- if (existsSync130(filepath)) {
638063
+ const filepath = join196(DATA_DIR8, "questions.json");
638064
+ if (existsSync132(filepath)) {
637789
638065
  const raw = JSON.parse(readFileSync117(filepath, "utf-8"));
637790
638066
  return raw.questions || [];
637791
638067
  }
@@ -637794,9 +638070,9 @@ function loadQuestions() {
637794
638070
  }
637795
638071
  function saveQuestions(questions) {
637796
638072
  try {
637797
- if (!existsSync130(DATA_DIR8))
638073
+ if (!existsSync132(DATA_DIR8))
637798
638074
  mkdirSync93(DATA_DIR8, { recursive: true });
637799
- writeFileSync73(join195(DATA_DIR8, "questions.json"), JSON.stringify({ questions }, null, 2));
638075
+ writeFileSync73(join196(DATA_DIR8, "questions.json"), JSON.stringify({ questions }, null, 2));
637800
638076
  } catch {}
637801
638077
  }
637802
638078
  function QuestionsView({ visible, data, onUpdateData, onClose }) {
@@ -639082,13 +639358,13 @@ import * as fs104 from "node:fs";
639082
639358
  import * as pathMod from "node:path";
639083
639359
 
639084
639360
  // apps/tui/src/lib/package-scripts.ts
639085
- import { existsSync as existsSync131 } from "node:fs";
639086
- import { join as join196, resolve as resolve73 } from "node:path";
639361
+ import { existsSync as existsSync133 } from "node:fs";
639362
+ import { join as join197, resolve as resolve75 } from "node:path";
639087
639363
  var __dirname = (import.meta.dirname + "/../../apps/tui/src/lib");
639088
- var PACKAGE_ROOT = resolve73(__dirname, "../../../..");
639364
+ var PACKAGE_ROOT = resolve75(__dirname, "../../../..");
639089
639365
  function packageBinScript(name, root = PACKAGE_ROOT) {
639090
- const candidate = join196(root, "bin", name);
639091
- return existsSync131(candidate) ? candidate : null;
639366
+ const candidate = join197(root, "bin", name);
639367
+ return existsSync133(candidate) ? candidate : null;
639092
639368
  }
639093
639369
 
639094
639370
  // apps/tui/src/app.tsx
@@ -639693,10 +639969,10 @@ function App2({
639693
639969
  setting,
639694
639970
  env: process.env,
639695
639971
  seenVersion: readSeenVersion(),
639696
- version: import_package.default.version
639972
+ version: import_package2.default.version
639697
639973
  });
639698
639974
  if (show)
639699
- markIntroSeen(import_package.default.version);
639975
+ markIntroSeen(import_package2.default.version);
639700
639976
  return show;
639701
639977
  });
639702
639978
  const [processingStage, setProcessingStage] = import_react104.useState("planning");
@@ -641595,7 +641871,7 @@ Keyboard shortcuts:
641595
641871
  setViewMode((prev) => prev === "kanban" ? "chat" : "kanban");
641596
641872
  break;
641597
641873
  case "bug":
641598
- addSystemMessage(runBugCommand(args, { version: import_package.default.version }).text);
641874
+ addSystemMessage(runBugCommand(args, { version: import_package2.default.version }).text);
641599
641875
  break;
641600
641876
  case "allow-read":
641601
641877
  addSystemMessage(runAllowReadCommand(args, process.cwd(), (d) => agent?.allowReadDir(d) ?? null));
@@ -641928,6 +642204,19 @@ ${lines.join(`
641928
642204
  agent.cleanup().catch(() => {});
641929
642205
  exit();
641930
642206
  break;
642207
+ case "mode": {
642208
+ const next = parseModeArgs(args);
642209
+ if (!next) {
642210
+ addSystemMessage(`${permSwitchLine(activePermMode)}
642211
+ ${MODE_USAGE}`);
642212
+ break;
642213
+ }
642214
+ setTabPermMode(activeTabId, next);
642215
+ const shown = permView(permHolderFor(activeTabId));
642216
+ addSystemMessage(permSwitchLine(shown.mode, shown.held));
642217
+ showPermToast({ tabId: activeTabId, ...shown });
642218
+ break;
642219
+ }
641931
642220
  case "infinite": {
641932
642221
  const next = infiniteModeActive ? "ask" : "infinite";
641933
642222
  setTabPermMode(activeTabId, next);
@@ -644012,6 +644301,26 @@ ${found.slice(0, 40).map((k) => ` ${k.name} ${k.description}`).join(`
644012
644301
  }
644013
644302
  return;
644014
644303
  }
644304
+ if (input && !attached) {
644305
+ const termArgs = detectTermPresetIntent(input, listPresetIds());
644306
+ if (termArgs) {
644307
+ appendToTab(tabId, {
644308
+ id: `user-${Date.now()}`,
644309
+ role: "user",
644310
+ content: bubbleContent,
644311
+ timestamp: new Date
644312
+ });
644313
+ setRecentCommands((prev) => [bubbleContent, ...prev].slice(0, 20));
644314
+ const refused = planModeRefusal2(permView(permHolderFor(tabId)).mode);
644315
+ if (refused) {
644316
+ addSystemMessage(refused);
644317
+ return;
644318
+ }
644319
+ addSystemMessage(`Opening ${termArgs[0]} (same as /term ${termArgs.join(" ")}).`);
644320
+ handleSlashCommand("term", termArgs);
644321
+ return;
644322
+ }
644323
+ }
644015
644324
  if (input && !attached) {
644016
644325
  const buildTask = detectBuildIntent(input);
644017
644326
  if (buildTask) {
@@ -644761,7 +645070,7 @@ ${result.commands.map((c) => ` $ ${c}`).join(`
644761
645070
  value: { enabled: adhdMode, ratio: 0.5 },
644762
645071
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(FixedFrame, {
644763
645072
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(IntroBanner, {
644764
- version: import_package.default.version,
645073
+ version: import_package2.default.version,
644765
645074
  animate: showAnimations,
644766
645075
  onDone: (carried) => {
644767
645076
  bootTrace("intro-done", carried ? "by-key" : "timeout");
@@ -644835,7 +645144,7 @@ ${result.commands.map((c) => ` $ ${c}`).join(`
644835
645144
  flexShrink: 0,
644836
645145
  children: /* @__PURE__ */ jsx_dev_runtime67.jsxDEV(HeaderBar, {
644837
645146
  updateAvailable: updateInfo,
644838
- version: import_package.default.version,
645147
+ version: import_package2.default.version,
644839
645148
  workspacePath: process.cwd(),
644840
645149
  branch: git.branch,
644841
645150
  syncStatus: git.noRepo ? "no repo" : git.sync,