reasonix 0.11.0 → 0.11.2

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/cli/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  memoryEnabled,
11
11
  readProjectMemory,
12
12
  sanitizeMemoryName
13
- } from "./chunk-GXABXQMU.js";
13
+ } from "./chunk-JDVY4JDU.js";
14
14
 
15
15
  // src/cli/index.ts
16
16
  import { Command } from "commander";
@@ -112,7 +112,7 @@ import { createParser } from "eventsource-parser";
112
112
 
113
113
  // src/retry.ts
114
114
  var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
115
- async function fetchWithRetry(fetchFn, url, init, opts = {}) {
115
+ async function fetchWithRetry(fetchFn, url, init2, opts = {}) {
116
116
  const maxAttempts = opts.maxAttempts ?? 4;
117
117
  const initial = opts.initialBackoffMs ?? 500;
118
118
  const cap = opts.maxBackoffMs ?? 1e4;
@@ -121,7 +121,7 @@ async function fetchWithRetry(fetchFn, url, init, opts = {}) {
121
121
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
122
122
  if (opts.signal?.aborted) throw new Error("aborted");
123
123
  try {
124
- const resp = await fetchFn(url, init);
124
+ const resp = await fetchFn(url, init2);
125
125
  if (resp.ok || !retryable.has(resp.status)) return resp;
126
126
  if (attempt === maxAttempts - 1) return resp;
127
127
  await resp.text().catch(() => void 0);
@@ -156,8 +156,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
156
156
  }
157
157
  function sleep(ms, signal) {
158
158
  if (ms <= 0) return Promise.resolve();
159
- return new Promise((resolve9, reject) => {
160
- const timer = setTimeout(resolve9, ms);
159
+ return new Promise((resolve12, reject) => {
160
+ const timer = setTimeout(resolve12, ms);
161
161
  if (signal) {
162
162
  const onAbort = () => {
163
163
  clearTimeout(timer);
@@ -642,7 +642,7 @@ function matchesTool(hook, toolName) {
642
642
  }
643
643
  }
644
644
  function defaultSpawner(input) {
645
- return new Promise((resolve9) => {
645
+ return new Promise((resolve12) => {
646
646
  const child = spawn(input.command, {
647
647
  cwd: input.cwd,
648
648
  shell: true,
@@ -669,7 +669,7 @@ function defaultSpawner(input) {
669
669
  });
670
670
  child.once("error", (err) => {
671
671
  clearTimeout(timer);
672
- resolve9({
672
+ resolve12({
673
673
  exitCode: null,
674
674
  stdout: stdout3,
675
675
  stderr,
@@ -679,7 +679,7 @@ function defaultSpawner(input) {
679
679
  });
680
680
  child.once("close", (code) => {
681
681
  clearTimeout(timer);
682
- resolve9({
682
+ resolve12({
683
683
  exitCode: code,
684
684
  stdout: stdout3.trim(),
685
685
  stderr: stderr.trim(),
@@ -720,8 +720,8 @@ async function runHooks(opts) {
720
720
  for (const hook of matching) {
721
721
  const start = Date.now();
722
722
  const timeoutMs = hook.timeout ?? DEFAULT_TIMEOUTS_MS[event];
723
- const cwd = hook.cwd ?? opts.payload.cwd;
724
- const raw = await spawner({ command: hook.command, cwd, stdin: stdin4, timeoutMs });
723
+ const cwd2 = hook.cwd ?? opts.payload.cwd;
724
+ const raw = await spawner({ command: hook.command, cwd: cwd2, stdin: stdin4, timeoutMs });
725
725
  const decision = decideOutcome(event, raw);
726
726
  outcomes.push({
727
727
  hook,
@@ -1867,7 +1867,11 @@ var CacheFirstLoop = class {
1867
1867
  * tool call is one array length check.
1868
1868
  */
1869
1869
  hooks;
1870
- /** `cwd` reported to hook stdin. Resolved once at construction. */
1870
+ /**
1871
+ * `cwd` reported to hook stdin. Mutable so `/cwd` can switch the
1872
+ * working directory mid-session — the App keeps it in sync with
1873
+ * the same currentRootDir that drives tool re-registration.
1874
+ */
1871
1875
  hookCwd;
1872
1876
  /** Number of messages that were pre-loaded from the session file. */
1873
1877
  resumedMessageCount;
@@ -2344,6 +2348,7 @@ var CacheFirstLoop = class {
2344
2348
  };
2345
2349
  this.autoCompactToolResultsOnTurnEnd();
2346
2350
  yield { turn: this._turn, role: "done", content: stoppedMsg };
2351
+ this._turnAbort = new AbortController();
2347
2352
  return;
2348
2353
  }
2349
2354
  if (iter > 0) {
@@ -2437,8 +2442,8 @@ var CacheFirstLoop = class {
2437
2442
  }
2438
2443
  );
2439
2444
  for (let k = 0; k < budget; k++) {
2440
- const sample = queue.shift() ?? await new Promise((resolve9) => {
2441
- waiter = resolve9;
2445
+ const sample = queue.shift() ?? await new Promise((resolve12) => {
2446
+ waiter = resolve12;
2442
2447
  });
2443
2448
  yield {
2444
2449
  turn: this._turn,
@@ -5168,7 +5173,7 @@ async function runCommand(cmd, opts) {
5168
5173
  };
5169
5174
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
5170
5175
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
5171
- return await new Promise((resolve9, reject) => {
5176
+ return await new Promise((resolve12, reject) => {
5172
5177
  let child;
5173
5178
  try {
5174
5179
  child = spawn3(bin, args, effectiveSpawnOpts);
@@ -5176,7 +5181,9 @@ async function runCommand(cmd, opts) {
5176
5181
  reject(err);
5177
5182
  return;
5178
5183
  }
5179
- let buf = "";
5184
+ const chunks = [];
5185
+ let totalBytes = 0;
5186
+ const byteCap = maxChars * 2 * 4;
5180
5187
  let timedOut = false;
5181
5188
  const killTimer = setTimeout(() => {
5182
5189
  timedOut = true;
@@ -5185,8 +5192,16 @@ async function runCommand(cmd, opts) {
5185
5192
  const onAbort = () => child.kill("SIGKILL");
5186
5193
  opts.signal?.addEventListener("abort", onAbort, { once: true });
5187
5194
  const onData = (chunk) => {
5188
- buf += chunk.toString();
5189
- if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
5195
+ const b = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
5196
+ if (totalBytes >= byteCap) return;
5197
+ const remaining = byteCap - totalBytes;
5198
+ if (b.length > remaining) {
5199
+ chunks.push(b.subarray(0, remaining));
5200
+ totalBytes = byteCap;
5201
+ } else {
5202
+ chunks.push(b);
5203
+ totalBytes += b.length;
5204
+ }
5190
5205
  };
5191
5206
  child.stdout?.on("data", onData);
5192
5207
  child.stderr?.on("data", onData);
@@ -5198,13 +5213,29 @@ async function runCommand(cmd, opts) {
5198
5213
  child.on("close", (code) => {
5199
5214
  clearTimeout(killTimer);
5200
5215
  opts.signal?.removeEventListener("abort", onAbort);
5216
+ const merged = Buffer.concat(chunks);
5217
+ const buf = smartDecodeOutput(merged);
5201
5218
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
5202
5219
 
5203
5220
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
5204
- resolve9({ exitCode: code, output, timedOut });
5221
+ resolve12({ exitCode: code, output, timedOut });
5205
5222
  });
5206
5223
  });
5207
5224
  }
5225
+ function smartDecodeOutput(buf) {
5226
+ if (buf.length === 0) return "";
5227
+ try {
5228
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
5229
+ } catch {
5230
+ }
5231
+ if (process.platform === "win32") {
5232
+ try {
5233
+ return new TextDecoder("gb18030").decode(buf);
5234
+ } catch {
5235
+ }
5236
+ }
5237
+ return buf.toString("utf8");
5238
+ }
5208
5239
  function resolveExecutable(cmd, opts = {}) {
5209
5240
  const platform = opts.platform ?? process.platform;
5210
5241
  if (platform !== "win32") return cmd;
@@ -6391,7 +6422,7 @@ var McpClient = class {
6391
6422
  const id = this.nextId++;
6392
6423
  const frame = { jsonrpc: "2.0", id, method, params };
6393
6424
  let abortHandler = null;
6394
- const promise = new Promise((resolve9, reject) => {
6425
+ const promise = new Promise((resolve12, reject) => {
6395
6426
  const timeout = setTimeout(() => {
6396
6427
  this.pending.delete(id);
6397
6428
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -6400,7 +6431,7 @@ var McpClient = class {
6400
6431
  );
6401
6432
  }, this.requestTimeoutMs);
6402
6433
  this.pending.set(id, {
6403
- resolve: resolve9,
6434
+ resolve: resolve12,
6404
6435
  reject,
6405
6436
  timeout
6406
6437
  });
@@ -6523,12 +6554,12 @@ var StdioTransport = class {
6523
6554
  }
6524
6555
  async send(message) {
6525
6556
  if (this.closed) throw new Error("MCP transport is closed");
6526
- return new Promise((resolve9, reject) => {
6557
+ return new Promise((resolve12, reject) => {
6527
6558
  const line = `${JSON.stringify(message)}
6528
6559
  `;
6529
6560
  this.child.stdin.write(line, "utf8", (err) => {
6530
6561
  if (err) reject(err);
6531
- else resolve9();
6562
+ else resolve12();
6532
6563
  });
6533
6564
  });
6534
6565
  }
@@ -6539,8 +6570,8 @@ var StdioTransport = class {
6539
6570
  continue;
6540
6571
  }
6541
6572
  if (this.closed) return;
6542
- const next = await new Promise((resolve9) => {
6543
- this.waiters.push(resolve9);
6573
+ const next = await new Promise((resolve12) => {
6574
+ this.waiters.push(resolve12);
6544
6575
  });
6545
6576
  if (next === null) return;
6546
6577
  yield next;
@@ -6606,8 +6637,8 @@ var SseTransport = class {
6606
6637
  constructor(opts) {
6607
6638
  this.url = opts.url;
6608
6639
  this.headers = opts.headers ?? {};
6609
- this.endpointReady = new Promise((resolve9, reject) => {
6610
- this.resolveEndpoint = resolve9;
6640
+ this.endpointReady = new Promise((resolve12, reject) => {
6641
+ this.resolveEndpoint = resolve12;
6611
6642
  this.rejectEndpoint = reject;
6612
6643
  });
6613
6644
  this.endpointReady.catch(() => void 0);
@@ -6634,8 +6665,8 @@ var SseTransport = class {
6634
6665
  continue;
6635
6666
  }
6636
6667
  if (this.closed) return;
6637
- const next = await new Promise((resolve9) => {
6638
- this.waiters.push(resolve9);
6668
+ const next = await new Promise((resolve12) => {
6669
+ this.waiters.push(resolve12);
6639
6670
  });
6640
6671
  if (next === null) return;
6641
6672
  yield next;
@@ -7223,13 +7254,14 @@ function formatLogSize(path5 = defaultUsageLogPath()) {
7223
7254
  }
7224
7255
 
7225
7256
  // src/cli/commands/chat.tsx
7226
- import { existsSync as existsSync13, statSync as statSync7 } from "fs";
7257
+ import { existsSync as existsSync16, statSync as statSync9 } from "fs";
7227
7258
  import { render } from "ink";
7228
- import React26, { useState as useState12 } from "react";
7259
+ import React27, { useState as useState12 } from "react";
7229
7260
 
7230
7261
  // src/cli/ui/App.tsx
7231
- import { Box as Box21, Static, Text as Text19, useApp, useStdout as useStdout8 } from "ink";
7232
- import React23, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
7262
+ import * as pathMod7 from "path";
7263
+ import { Box as Box22, Static, Text as Text20, useApp, useStdout as useStdout8 } from "ink";
7264
+ import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
7233
7265
 
7234
7266
  // src/code/pending-edits.ts
7235
7267
  import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
@@ -7521,6 +7553,60 @@ ${skill2.body}${argsBlock}`;
7521
7553
  return registry;
7522
7554
  }
7523
7555
 
7556
+ // src/tools/workspace.ts
7557
+ import { existsSync as existsSync11, statSync as statSync6 } from "fs";
7558
+ import * as pathMod4 from "path";
7559
+ var WorkspaceConfirmationError = class extends Error {
7560
+ path;
7561
+ constructor(path5) {
7562
+ super(
7563
+ `change_workspace: switching to "${path5}" needs the user's approval before it takes effect. STOP calling tools now \u2014 the TUI has already prompted the user to press Enter (switch) or Esc (deny). Wait for their next message; it will either confirm the switch (and your subsequent file/shell tools will resolve against the new root) or tell you to continue without changing directories.`
7564
+ );
7565
+ this.name = "WorkspaceConfirmationError";
7566
+ this.path = path5;
7567
+ }
7568
+ };
7569
+ function registerWorkspaceTool(registry) {
7570
+ registry.register({
7571
+ name: "change_workspace",
7572
+ description: "Switch the session's working directory to a different project root. Re-registers filesystem / shell / memory tools against the new path so subsequent file reads, edits, and run_command calls all land there. EVERY switch requires explicit user approval via a modal \u2014 do NOT batch switches or chain a switch with subsequent tool calls before the user has confirmed. Use ONLY when the user explicitly asked to change directory or open a different project; never use to 'preview' a sibling repo. MCP servers stay anchored to the original launch root (their child processes can't be reconnected mid-session); the modal warns the user about this.",
7573
+ parameters: {
7574
+ type: "object",
7575
+ required: ["path"],
7576
+ properties: {
7577
+ path: {
7578
+ type: "string",
7579
+ description: "Target directory. Absolute paths land verbatim. Leading `~` expands to the user's home. Relative paths resolve against the user's launch cwd (not the current session root, so paths the user typed in chat resolve where they expect)."
7580
+ }
7581
+ }
7582
+ },
7583
+ fn: (rawArgs) => {
7584
+ const args = rawArgs ?? {};
7585
+ if (typeof args.path !== "string" || args.path.trim() === "") {
7586
+ throw new Error("change_workspace: `path` must be a non-empty string");
7587
+ }
7588
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
7589
+ const expanded = args.path.startsWith("~") && home ? pathMod4.join(home, args.path.slice(1)) : args.path;
7590
+ const abs = pathMod4.resolve(expanded);
7591
+ if (!existsSync11(abs)) {
7592
+ throw new Error(`change_workspace: path does not exist \u2014 ${abs}`);
7593
+ }
7594
+ try {
7595
+ if (!statSync6(abs).isDirectory()) {
7596
+ throw new Error(`change_workspace: not a directory \u2014 ${abs}`);
7597
+ }
7598
+ } catch (err) {
7599
+ if (err.code === "ENOENT") {
7600
+ throw new Error(`change_workspace: path does not exist \u2014 ${abs}`);
7601
+ }
7602
+ throw err;
7603
+ }
7604
+ throw new WorkspaceConfirmationError(abs);
7605
+ }
7606
+ });
7607
+ return registry;
7608
+ }
7609
+
7524
7610
  // src/cli/ui/AtMentionSuggestions.tsx
7525
7611
  import { Box, Text } from "ink";
7526
7612
  import React from "react";
@@ -8315,8 +8401,8 @@ function RiskLegend() {
8315
8401
  var PlanStepList = React8.memo(PlanStepListInner);
8316
8402
 
8317
8403
  // src/cli/ui/markdown.tsx
8318
- import { readFileSync as readFileSync13, statSync as statSync6 } from "fs";
8319
- import { isAbsolute as isAbsolute4, join as join11 } from "path";
8404
+ import { readFileSync as readFileSync13, statSync as statSync7 } from "fs";
8405
+ import { isAbsolute as isAbsolute4, join as join12 } from "path";
8320
8406
  import { Box as Box8, Text as Text7 } from "ink";
8321
8407
  import React9 from "react";
8322
8408
  var SUPERSCRIPT = {
@@ -8358,7 +8444,32 @@ function toSubscript(s) {
8358
8444
  for (const c of s) out += SUBSCRIPT[c] ?? c;
8359
8445
  return out;
8360
8446
  }
8447
+ var HAS_MATH_RE = new RegExp(
8448
+ [
8449
+ "\\$",
8450
+ // dollar-delimited (block or inline)
8451
+ "\\\\[([]",
8452
+ // \( or \[
8453
+ "\\\\[a-zA-Z]+\\s*\\{",
8454
+ // \anyCommand{...} — covers catch-all braced transforms
8455
+ // Bare (no-brace) LaTeX commands the pipeline knows how to handle.
8456
+ // Listed explicitly because a generic `\\[a-zA-Z]+` would also match
8457
+ // Windows paths (`F:\TEST1`) and re-introduce the bug we're fixing.
8458
+ "\\\\(?:cdot|times|div|pm|mp|leq|geq|neq|approx|in|notin|infty|sum|prod|int|alpha|beta|gamma|delta|theta|lambda|mu|pi|sigma|phi|omega|implies|iff|to|rightarrow|leftarrow|Rightarrow|Leftarrow|ldots|cdots|quad|qquad)(?![a-zA-Z])",
8459
+ "[\\^_]\\{",
8460
+ // LaTeX braced super/subscript: ^{2}, _{ij}
8461
+ "\\^[0-9+\\-n](?![A-Za-z])",
8462
+ // LaTeX single-char super: ^2, ^-, ^n
8463
+ "_[0-9+\\-](?![A-Za-z])",
8464
+ // LaTeX single-char sub: _1, _+, _-
8465
+ "\\^[A-Za-z0-9+\\-]+\\^",
8466
+ // Pandoc super: ^2^, ^abc^
8467
+ "(?<!~)~[A-Za-z0-9+\\-]+~(?!~)"
8468
+ // Pandoc sub: ~2~ (lookarounds avoid ~~strike~~)
8469
+ ].join("|")
8470
+ );
8361
8471
  function stripMath(s) {
8472
+ if (!HAS_MATH_RE.test(s)) return s;
8362
8473
  return s.replace(/\$\$([\s\S]+?)\$\$/g, (_m, c) => `
8363
8474
 
8364
8475
  ${c.trim()}
@@ -8532,7 +8643,7 @@ function validateCitation(url, projectRoot) {
8532
8643
  const parts = parseCitationUrl(url);
8533
8644
  if (!parts || !parts.path) return { ok: false, reason: "empty path" };
8534
8645
  const normalized = parts.path.replace(/^[/\\]+/, "");
8535
- const baseFullPath = isAbsolute4(normalized) ? normalized : join11(projectRoot, normalized);
8646
+ const baseFullPath = isAbsolute4(normalized) ? normalized : join12(projectRoot, normalized);
8536
8647
  const siblings = SIBLING_EXTENSIONS.get(extOf(baseFullPath)) ?? [];
8537
8648
  const candidates = [
8538
8649
  baseFullPath,
@@ -8542,7 +8653,7 @@ function validateCitation(url, projectRoot) {
8542
8653
  let stat = null;
8543
8654
  for (const candidate of candidates) {
8544
8655
  try {
8545
- stat = statSync6(candidate);
8656
+ stat = statSync7(candidate);
8546
8657
  fullPath = candidate;
8547
8658
  break;
8548
8659
  } catch {
@@ -10945,6 +11056,39 @@ function Hint({ cmd, desc }) {
10945
11056
  return /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.accent }, cmd.padEnd(8)), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, ` ${desc}`));
10946
11057
  }
10947
11058
 
11059
+ // src/cli/ui/WorkspaceConfirm.tsx
11060
+ import { Box as Box21, Text as Text19 } from "ink";
11061
+ import React23 from "react";
11062
+ function WorkspaceConfirm({
11063
+ path: path5,
11064
+ currentRoot,
11065
+ mcpServerCount,
11066
+ onChoose
11067
+ }) {
11068
+ const subtitle = mcpServerCount > 0 ? `MCP servers (${mcpServerCount}) stay anchored to the original launch root.` : "Re-registers filesystem / shell / memory tools at the new path.";
11069
+ return /* @__PURE__ */ React23.createElement(ModalCard, { accent: "#f59e0b", icon: "\u21C4", title: "switch workspace", subtitle }, /* @__PURE__ */ React23.createElement(Box21, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React23.createElement(Box21, null, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "from "), /* @__PURE__ */ React23.createElement(Text19, { color: "#a3a3a3" }, currentRoot)), /* @__PURE__ */ React23.createElement(Box21, null, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "to "), /* @__PURE__ */ React23.createElement(Text19, { color: "#67e8f9", bold: true }, path5))), /* @__PURE__ */ React23.createElement(
11070
+ SingleSelect,
11071
+ {
11072
+ initialValue: "switch",
11073
+ items: [
11074
+ {
11075
+ value: "switch",
11076
+ label: "Switch",
11077
+ hint: "Re-register filesystem / shell / memory tools against the new root."
11078
+ },
11079
+ {
11080
+ value: "deny",
11081
+ label: "Deny",
11082
+ hint: "Tell the model the user refused; it will continue without changing directories."
11083
+ }
11084
+ ],
11085
+ onSubmit: (v) => onChoose(v),
11086
+ onCancel: () => onChoose("deny"),
11087
+ footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] deny"
11088
+ }
11089
+ ));
11090
+ }
11091
+
10948
11092
  // src/cli/ui/bang.ts
10949
11093
  function detectBangCommand(text) {
10950
11094
  if (!text.startsWith("!")) return null;
@@ -11038,9 +11182,9 @@ function describeRepair(repair) {
11038
11182
  }
11039
11183
 
11040
11184
  // src/cli/ui/hash-memory.ts
11041
- import { appendFileSync as appendFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
11185
+ import { appendFileSync as appendFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
11042
11186
  import { homedir as homedir6 } from "os";
11043
- import { dirname as dirname10, join as join12 } from "path";
11187
+ import { dirname as dirname10, join as join13 } from "path";
11044
11188
  var PROJECT_HEADER = `# Reasonix project memory
11045
11189
 
11046
11190
  Notes the user pinned via the \`#\` prompt prefix. The whole file is
@@ -11072,12 +11216,12 @@ function detectHashMemory(text) {
11072
11216
  return { kind: "memory", note: body };
11073
11217
  }
11074
11218
  function appendProjectMemory(rootDir, note) {
11075
- return appendBulletToFile(join12(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
11219
+ return appendBulletToFile(join13(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
11076
11220
  }
11077
11221
  var GLOBAL_MEMORY_DIR = ".reasonix";
11078
11222
  var GLOBAL_MEMORY_FILE = "REASONIX.md";
11079
11223
  function globalMemoryPath(homeDir = homedir6()) {
11080
- return join12(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
11224
+ return join13(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
11081
11225
  }
11082
11226
  function appendGlobalMemory(note, homeDir) {
11083
11227
  return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
@@ -11087,7 +11231,7 @@ function appendBulletToFile(path5, note, newFileHeader) {
11087
11231
  if (!trimmed) throw new Error("note body cannot be empty");
11088
11232
  const bullet = `- ${trimmed}
11089
11233
  `;
11090
- if (!existsSync11(path5)) {
11234
+ if (!existsSync12(path5)) {
11091
11235
  mkdirSync8(dirname10(path5), { recursive: true });
11092
11236
  writeFileSync7(path5, `${newFileHeader}${bullet}`, "utf8");
11093
11237
  return { path: path5, created: true };
@@ -11435,6 +11579,11 @@ var SLASH_COMMANDS = [
11435
11579
  argsHint: "[reload]",
11436
11580
  summary: "list active hooks (settings.json under .reasonix/) \xB7 reload re-reads from disk"
11437
11581
  },
11582
+ {
11583
+ cmd: "cwd",
11584
+ argsHint: "<path>",
11585
+ summary: "switch session working directory (re-registers code tools, reloads hooks; MCP servers stay)"
11586
+ },
11438
11587
  {
11439
11588
  cmd: "update",
11440
11589
  summary: "show current vs latest version + the shell command to upgrade"
@@ -11477,6 +11626,13 @@ var SLASH_COMMANDS = [
11477
11626
  },
11478
11627
  { cmd: "exit", summary: "quit the TUI" },
11479
11628
  // Code-mode only
11629
+ {
11630
+ cmd: "init",
11631
+ argsHint: "[force]",
11632
+ summary: "scan the project and synthesize a baseline REASONIX.md (model writes; review with /apply). `force` overwrites an existing file.",
11633
+ contextual: "code",
11634
+ argCompleter: ["force"]
11635
+ },
11480
11636
  {
11481
11637
  cmd: "apply",
11482
11638
  argsHint: "[N|N,M|N-M]",
@@ -11581,8 +11737,12 @@ function parseSlash(text) {
11581
11737
  return { cmd, args: parts.slice(1) };
11582
11738
  }
11583
11739
 
11740
+ // src/cli/ui/slash/handlers/admin.ts
11741
+ import { existsSync as existsSync14, statSync as statSync8 } from "fs";
11742
+ import * as pathMod5 from "path";
11743
+
11584
11744
  // src/cli/commands/stats.ts
11585
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
11745
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
11586
11746
  function statsCommand(opts) {
11587
11747
  if (opts.transcript) {
11588
11748
  transcriptSummary(opts.transcript);
@@ -11591,7 +11751,7 @@ function statsCommand(opts) {
11591
11751
  dashboard(opts);
11592
11752
  }
11593
11753
  function transcriptSummary(path5) {
11594
- if (!existsSync12(path5)) {
11754
+ if (!existsSync13(path5)) {
11595
11755
  console.error(`no such transcript: ${path5}`);
11596
11756
  process.exit(1);
11597
11757
  }
@@ -11815,9 +11975,51 @@ var stats = () => {
11815
11975
  const agg = aggregateUsage(records);
11816
11976
  return { info: renderDashboard(agg, path5) };
11817
11977
  };
11978
+ var cwd = (args, _loop, ctx) => {
11979
+ if (!ctx.setCwd) {
11980
+ return {
11981
+ info: "/cwd is not available in this context (no setCwd callback wired)."
11982
+ };
11983
+ }
11984
+ const raw = (args[0] ?? "").trim();
11985
+ if (!raw) {
11986
+ return {
11987
+ info: "usage: /cwd <path> (absolute or relative, ~ expands to home)"
11988
+ };
11989
+ }
11990
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11991
+ const expanded = raw.startsWith("~") && home ? pathMod5.join(home, raw.slice(1)) : raw;
11992
+ const abs = pathMod5.resolve(expanded);
11993
+ if (!existsSync14(abs)) {
11994
+ return { info: `\u25B8 /cwd: path does not exist \u2014 ${abs}` };
11995
+ }
11996
+ let isDir = false;
11997
+ try {
11998
+ isDir = statSync8(abs).isDirectory();
11999
+ } catch {
12000
+ }
12001
+ if (!isDir) {
12002
+ return { info: `\u25B8 /cwd: not a directory \u2014 ${abs}` };
12003
+ }
12004
+ let info;
12005
+ try {
12006
+ info = ctx.setCwd(abs);
12007
+ } catch (err) {
12008
+ return { info: `\u25B8 /cwd failed: ${err.message}` };
12009
+ }
12010
+ const lines = [info];
12011
+ if (ctx.mcpServers && ctx.mcpServers.length > 0) {
12012
+ lines.push(
12013
+ ` note: ${ctx.mcpServers.length} MCP server(s) still anchored to the original cwd \u2014`,
12014
+ " their tools won't follow this switch. Restart the session for full reset."
12015
+ );
12016
+ }
12017
+ return { info: lines.join("\n") };
12018
+ };
11818
12019
  var handlers = {
11819
12020
  hook: hooks,
11820
12021
  hooks,
12022
+ cwd,
11821
12023
  update,
11822
12024
  stats
11823
12025
  };
@@ -12265,6 +12467,103 @@ var handlers3 = {
12265
12467
  walk: walk2
12266
12468
  };
12267
12469
 
12470
+ // src/cli/ui/slash/handlers/init.ts
12471
+ import { existsSync as existsSync15 } from "fs";
12472
+ import * as pathMod6 from "path";
12473
+ var INIT_PROMPT = [
12474
+ "# Task: Initialize REASONIX.md",
12475
+ "",
12476
+ "I want you to generate a REASONIX.md at the project root that captures",
12477
+ "the working knowledge a future Reasonix session needs to be productive",
12478
+ "here. This file is auto-pinned into your system prompt every launch,",
12479
+ "so its size and accuracy matter.",
12480
+ "",
12481
+ "## Hard constraints (do NOT relax these)",
12482
+ "",
12483
+ "- **Length cap: \u2264 80 lines / 3KB total.** Be concise. If you can't fit a",
12484
+ " section, drop it.",
12485
+ "- **Only document things you can verify by reading files.** Do NOT",
12486
+ " speculate about architectural intent, future roadmap, or design",
12487
+ " rationale. If it isn't obvious from the code, leave it out.",
12488
+ "- **No placeholder text.** No 'TODO: describe X', no 'Add more here'.",
12489
+ " Either state a fact or omit the section.",
12490
+ "",
12491
+ "## Procedure",
12492
+ "",
12493
+ "1. Read the top of any existing README* file.",
12494
+ "2. Read the manifest (package.json / Cargo.toml / pyproject.toml /",
12495
+ " go.mod / etc.) \u2014 pick whichever exists.",
12496
+ "3. `directory_tree` 1-2 levels deep on the project root, skipping",
12497
+ " common build/dependency dirs (node_modules, dist, target, .git,",
12498
+ " venv, __pycache__).",
12499
+ "4. Identify: primary language + framework, top-level layout, test",
12500
+ " runner, lint/format setup, build/run/test scripts, any non-obvious",
12501
+ " convention with visible evidence (commit message format, import",
12502
+ " order, naming pattern).",
12503
+ "5. Write REASONIX.md with the sections below, skipping any you can't",
12504
+ " fill from evidence.",
12505
+ "",
12506
+ "## Sections to use (skip ones with no evidence)",
12507
+ "",
12508
+ "- **Stack** \u2014 language + framework + 3-5 key deps. One line each.",
12509
+ "- **Layout** \u2014 top-level dirs and what lives in each. One line each.",
12510
+ "- **Commands** \u2014 verbatim from `scripts` block (or equivalent):",
12511
+ " build / test / lint / typecheck / dev / format. Whatever exists.",
12512
+ "- **Conventions** \u2014 only things visible in the code. Examples:",
12513
+ " '*.test.ts colocated with source', 'named exports only',",
12514
+ " 'commits use Conventional Commits prefix'. If you can't find any",
12515
+ " CONVENTION evidence, omit the whole section.",
12516
+ "- **Watch out for** \u2014 gotchas a new contributor would benefit from",
12517
+ " knowing BEFORE editing. Examples: 'edit_file SEARCH must match",
12518
+ " byte-for-byte', 'this dir is generated, don't edit by hand'.",
12519
+ " Omit if you find nothing concrete.",
12520
+ "",
12521
+ "## Output",
12522
+ "",
12523
+ "Write the result to `REASONIX.md` in the project root using the",
12524
+ "filesystem tools (edit_file with empty SEARCH if creating new,",
12525
+ "write_file if overwriting). After writing, STOP \u2014 do not summarize",
12526
+ "what you did, do not propose follow-up tasks. The user will review",
12527
+ "the pending edit via /apply.",
12528
+ "",
12529
+ "Start now."
12530
+ ].join("\n");
12531
+ var init = (args, _loop, ctx) => {
12532
+ if (!ctx.codeRoot) {
12533
+ return {
12534
+ info: [
12535
+ "/init only works in code mode (it needs filesystem tools).",
12536
+ "Run `reasonix code [path]` to start a session rooted at the",
12537
+ "project you want to initialize, then run /init."
12538
+ ].join("\n")
12539
+ };
12540
+ }
12541
+ const force = (args[0] ?? "").toLowerCase() === "force";
12542
+ const target = pathMod6.join(ctx.codeRoot, "REASONIX.md");
12543
+ if (existsSync15(target) && !force) {
12544
+ return {
12545
+ info: [
12546
+ `\u25B8 REASONIX.md already exists at ${target}`,
12547
+ "",
12548
+ " /init force regenerate from scratch (overwrites)",
12549
+ "",
12550
+ " Or edit it by hand \u2014 it's just markdown. The current file is",
12551
+ " pinned into the system prompt every launch as-is."
12552
+ ].join("\n")
12553
+ };
12554
+ }
12555
+ return {
12556
+ info: [
12557
+ "\u25B8 /init \u2014 model will scan the project and synthesize REASONIX.md.",
12558
+ " The result lands as a pending edit; review with /apply or /walk."
12559
+ ].join("\n"),
12560
+ resubmit: INIT_PROMPT
12561
+ };
12562
+ };
12563
+ var handlers4 = {
12564
+ init
12565
+ };
12566
+
12268
12567
  // src/cli/ui/slash/handlers/jobs.ts
12269
12568
  var jobs = (_args, _loop, ctx) => {
12270
12569
  if (!ctx.jobs) {
@@ -12320,7 +12619,7 @@ $ ${out.command}`;
12320
12619
  return { info: out.output ? `${header2}
12321
12620
  ${out.output}` : header2 };
12322
12621
  };
12323
- var handlers4 = {
12622
+ var handlers5 = {
12324
12623
  jobs,
12325
12624
  kill,
12326
12625
  logs
@@ -12381,7 +12680,7 @@ var mcp = (_args, loop2, ctx) => {
12381
12680
  lines.push("To change this set, exit and run `reasonix setup`.");
12382
12681
  return { info: lines.join("\n") };
12383
12682
  };
12384
- var handlers5 = { mcp };
12683
+ var handlers6 = { mcp };
12385
12684
 
12386
12685
  // src/cli/ui/slash/handlers/memory.ts
12387
12686
  var memory = (args, _loop, ctx) => {
@@ -12516,7 +12815,7 @@ var memory = (args, _loop, ctx) => {
12516
12815
  );
12517
12816
  return { info: parts.join("\n") };
12518
12817
  };
12519
- var handlers6 = { memory };
12818
+ var handlers7 = { memory };
12520
12819
 
12521
12820
  // src/cli/ui/slash/handlers/model.ts
12522
12821
  var model = (args, loop2, ctx) => {
@@ -12669,7 +12968,7 @@ var pro = (args, loop2, ctx) => {
12669
12968
  };
12670
12969
  };
12671
12970
  var ESCALATION_MODEL_ID = "deepseek-v4-pro";
12672
- var handlers7 = {
12971
+ var handlers8 = {
12673
12972
  model,
12674
12973
  models,
12675
12974
  harvest: harvest2,
@@ -12822,7 +13121,7 @@ var compact = (args, loop2) => {
12822
13121
  info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
12823
13122
  };
12824
13123
  };
12825
- var handlers8 = {
13124
+ var handlers9 = {
12826
13125
  think,
12827
13126
  reasoning: think,
12828
13127
  tool,
@@ -12910,7 +13209,7 @@ var replay = (args, loop2) => {
12910
13209
  }
12911
13210
  };
12912
13211
  };
12913
- var handlers9 = {
13212
+ var handlers10 = {
12914
13213
  plans,
12915
13214
  replay
12916
13215
  };
@@ -13173,7 +13472,7 @@ async function startOllamaDaemon(opts = {}) {
13173
13472
  return { ready: false, pid };
13174
13473
  }
13175
13474
  async function pullOllamaModel(modelName, opts = {}) {
13176
- return new Promise((resolve9) => {
13475
+ return new Promise((resolve12) => {
13177
13476
  const child = spawn5("ollama", ["pull", modelName], {
13178
13477
  stdio: ["ignore", "pipe", "pipe"],
13179
13478
  windowsHide: true
@@ -13185,8 +13484,8 @@ async function pullOllamaModel(modelName, opts = {}) {
13185
13484
  }
13186
13485
  streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
13187
13486
  streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
13188
- child.once("exit", (code) => resolve9(code ?? -1));
13189
- child.once("error", () => resolve9(-1));
13487
+ child.once("exit", (code) => resolve12(code ?? -1));
13488
+ child.once("error", () => resolve12(-1));
13190
13489
  });
13191
13490
  }
13192
13491
  function streamLines(stream, cb) {
@@ -13283,7 +13582,7 @@ async function readIndexMeta(rootDir) {
13283
13582
  return null;
13284
13583
  }
13285
13584
  }
13286
- var handlers10 = {
13585
+ var handlers11 = {
13287
13586
  semantic
13288
13587
  };
13289
13588
 
@@ -13318,7 +13617,7 @@ var forget = (_args, loop2) => {
13318
13617
  info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
13319
13618
  };
13320
13619
  };
13321
- var handlers11 = {
13620
+ var handlers12 = {
13322
13621
  sessions,
13323
13622
  forget
13324
13623
  };
@@ -13394,7 +13693,7 @@ ${found.body}${argsLine}`;
13394
13693
  resubmit: payload
13395
13694
  };
13396
13695
  };
13397
- var handlers12 = {
13696
+ var handlers13 = {
13398
13697
  skill,
13399
13698
  skills: skill
13400
13699
  };
@@ -13412,7 +13711,8 @@ var HANDLERS = {
13412
13711
  ...handlers9,
13413
13712
  ...handlers10,
13414
13713
  ...handlers11,
13415
- ...handlers12
13714
+ ...handlers12,
13715
+ ...handlers13
13416
13716
  };
13417
13717
  function handleSlash(cmd, args, loop2, ctx = {}) {
13418
13718
  const h = HANDLERS[cmd];
@@ -13426,6 +13726,7 @@ function useCompletionPickers({
13426
13726
  input,
13427
13727
  setInput,
13428
13728
  codeMode,
13729
+ rootDir,
13429
13730
  models: models2,
13430
13731
  mcpServers
13431
13732
  }) {
@@ -13443,13 +13744,13 @@ function useCompletionPickers({
13443
13744
  }, [slashMatches]);
13444
13745
  const [atSelected, setAtSelected] = useState6(0);
13445
13746
  const atFiles = useMemo2(() => {
13446
- if (!codeMode?.rootDir) return [];
13747
+ if (!codeMode) return [];
13447
13748
  try {
13448
- return listFilesWithStatsSync(codeMode.rootDir, { maxResults: 500 });
13749
+ return listFilesWithStatsSync(rootDir, { maxResults: 500 });
13449
13750
  } catch {
13450
13751
  return [];
13451
13752
  }
13452
- }, [codeMode?.rootDir]);
13753
+ }, [codeMode, rootDir]);
13453
13754
  const recentFilesRef = useRef3([]);
13454
13755
  const recordRecentFile = useCallback((p) => {
13455
13756
  const list = recentFilesRef.current;
@@ -13459,10 +13760,10 @@ function useCompletionPickers({
13459
13760
  if (list.length > 20) list.length = 20;
13460
13761
  }, []);
13461
13762
  const atPicker = useMemo2(() => {
13462
- if (!codeMode?.rootDir) return null;
13763
+ if (!codeMode) return null;
13463
13764
  if (slashMatches !== null) return null;
13464
13765
  return detectAtPicker(input);
13465
- }, [codeMode?.rootDir, input, slashMatches]);
13766
+ }, [codeMode, input, slashMatches]);
13466
13767
  const atMatches = useMemo2(() => {
13467
13768
  if (!atPicker) return null;
13468
13769
  return rankPickerCandidates(atFiles, atPicker.query, {
@@ -13902,13 +14203,13 @@ var PLAIN_UI = process.env.REASONIX_UI === "plain";
13902
14203
  function LoopStatusRow({
13903
14204
  loop: loop2
13904
14205
  }) {
13905
- const [, setTick] = React23.useState(0);
13906
- React23.useEffect(() => {
14206
+ const [, setTick] = React24.useState(0);
14207
+ React24.useEffect(() => {
13907
14208
  const id = setInterval(() => setTick((t2) => t2 + 1), 1e3);
13908
14209
  return () => clearInterval(id);
13909
14210
  }, []);
13910
14211
  const nextFireMs = Math.max(0, loop2.nextFireAt - Date.now());
13911
- return /* @__PURE__ */ React23.createElement(Box21, null, /* @__PURE__ */ React23.createElement(Text19, { color: "cyan" }, `\u25B8 ${formatLoopStatus(loop2.prompt, nextFireMs, loop2.iter)} \xB7 /loop stop or type to cancel`));
14212
+ return /* @__PURE__ */ React24.createElement(Box22, null, /* @__PURE__ */ React24.createElement(Text20, { color: "cyan" }, `\u25B8 ${formatLoopStatus(loop2.prompt, nextFireMs, loop2.iter)} \xB7 /loop stop or type to cancel`));
13912
14213
  }
13913
14214
  function App({
13914
14215
  model: model2,
@@ -13967,10 +14268,12 @@ function App({
13967
14268
  setHistorical
13968
14269
  });
13969
14270
  const [statusLine, setStatusLine] = useState10(null);
14271
+ const [currentRootDir, setCurrentRootDir] = useState10(
14272
+ () => codeMode?.rootDir ?? process.cwd()
14273
+ );
13970
14274
  const [hookList, setHookList] = useState10(
13971
14275
  () => loadHooks({ projectRoot: codeMode?.rootDir })
13972
14276
  );
13973
- const hookCwd = codeMode?.rootDir ?? process.cwd();
13974
14277
  const {
13975
14278
  undoBanner,
13976
14279
  recordEdit,
@@ -14012,6 +14315,7 @@ function App({
14012
14315
  }, 1200);
14013
14316
  }, [editMode]);
14014
14317
  const [pendingShell, setPendingShell] = useState10(null);
14318
+ const [pendingWorkspace, setPendingWorkspace] = useState10(null);
14015
14319
  const [pendingPlan, setPendingPlan] = useState10(null);
14016
14320
  const [stagedInput, setStagedInput] = useState10(null);
14017
14321
  const [pendingCheckpoint, setPendingCheckpoint] = useState10(null);
@@ -14106,6 +14410,9 @@ function App({
14106
14410
  }
14107
14411
  });
14108
14412
  }
14413
+ if (tools && !tools.has("change_workspace")) {
14414
+ registerWorkspaceTool(tools);
14415
+ }
14109
14416
  const prefix = new ImmutablePrefix({
14110
14417
  system,
14111
14418
  toolSpecs: tools?.specs()
@@ -14119,7 +14426,7 @@ function App({
14119
14426
  branch: branch2,
14120
14427
  session,
14121
14428
  hooks: hookList,
14122
- hookCwd,
14429
+ hookCwd: currentRootDir,
14123
14430
  // Restore the user's last-chosen effort cap. Without this a
14124
14431
  // `/effort high` silently reverted to `max` on relaunch — the
14125
14432
  // loop's constructor default wins over persisted state.
@@ -14131,6 +14438,49 @@ function App({
14131
14438
  useEffect6(() => {
14132
14439
  loop2.hooks = hookList;
14133
14440
  }, [loop2, hookList]);
14441
+ const applyCwdChange = useCallback4(
14442
+ (newRoot) => {
14443
+ setCurrentRootDir(newRoot);
14444
+ const fresh = loadHooks({ projectRoot: codeMode ? newRoot : void 0 });
14445
+ setHookList(fresh);
14446
+ const codeRebound = codeMode?.reregisterTools !== void 0;
14447
+ if (codeMode?.reregisterTools) {
14448
+ codeMode.reregisterTools(newRoot);
14449
+ }
14450
+ if (tools) {
14451
+ registerSkillTools(tools, {
14452
+ projectRoot: codeMode ? newRoot : void 0,
14453
+ subagentRunner: async (skill2, task) => {
14454
+ const result = await spawnSubagent({
14455
+ client: loop2.client,
14456
+ parentRegistry: tools,
14457
+ system: skill2.body,
14458
+ task,
14459
+ model: skill2.model,
14460
+ sink: subagentSinkRef.current,
14461
+ skillName: skill2.name
14462
+ });
14463
+ return formatSubagentResult(result);
14464
+ }
14465
+ });
14466
+ }
14467
+ const lines = [`\u25B8 cwd \u2192 ${newRoot}`, ` hooks reloaded (${fresh.length} active)`];
14468
+ if (codeMode) {
14469
+ lines.push(
14470
+ codeRebound ? " filesystem / shell / memory tools rebound to new root" : " warning: reregisterTools callback missing \u2014 tool sandbox unchanged"
14471
+ );
14472
+ lines.push(
14473
+ " note: system prompt context (gitignore, REASONIX.md stack) was",
14474
+ " baked at session start and still references the original root."
14475
+ );
14476
+ }
14477
+ return lines.join("\n");
14478
+ },
14479
+ [codeMode, loop2, tools, subagentSinkRef]
14480
+ );
14481
+ useEffect6(() => {
14482
+ loop2.hookCwd = currentRootDir;
14483
+ }, [loop2, currentRootDir]);
14134
14484
  const {
14135
14485
  balance,
14136
14486
  models: models2,
@@ -14155,7 +14505,14 @@ function App({
14155
14505
  slashArgSelected,
14156
14506
  setSlashArgSelected,
14157
14507
  pickSlashArg
14158
- } = useCompletionPickers({ input, setInput, codeMode, models: models2, mcpServers });
14508
+ } = useCompletionPickers({
14509
+ input,
14510
+ setInput,
14511
+ codeMode,
14512
+ rootDir: currentRootDir,
14513
+ models: models2,
14514
+ mcpServers
14515
+ });
14159
14516
  useEffect6(() => {
14160
14517
  if (!progressSink) return;
14161
14518
  progressSink.current = (info) => {
@@ -14275,11 +14632,11 @@ function App({
14275
14632
  if (key.escape && busy) {
14276
14633
  if (abortedThisTurn.current) return;
14277
14634
  abortedThisTurn.current = true;
14278
- const resolve9 = editReviewResolveRef.current;
14279
- if (resolve9) {
14635
+ const resolve12 = editReviewResolveRef.current;
14636
+ if (resolve12) {
14280
14637
  editReviewResolveRef.current = null;
14281
14638
  setPendingEditReview(null);
14282
- resolve9("reject");
14639
+ resolve12("reject");
14283
14640
  }
14284
14641
  if (activeLoopRef.current) stopLoop();
14285
14642
  loop2.abort();
@@ -14302,7 +14659,7 @@ function App({
14302
14659
  ]);
14303
14660
  return;
14304
14661
  }
14305
- if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
14662
+ if (codeMode && key.shift && key.tab && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
14306
14663
  setEditMode((m) => {
14307
14664
  const next = m === "review" ? "auto" : m === "auto" ? "yolo" : "review";
14308
14665
  const message = next === "yolo" ? "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully." : next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo. Shell commands still ask." : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)";
@@ -14314,7 +14671,7 @@ function App({
14314
14671
  });
14315
14672
  return;
14316
14673
  }
14317
- if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
14674
+ if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
14318
14675
  // history entry — the keybind is useful long after the 5-second
14319
14676
  // banner expires, which users rightly want.
14320
14677
  (undoBanner || hasUndoable())) {
@@ -14403,11 +14760,11 @@ function App({
14403
14760
  block = { path: relPath, search, replace, offset: 0 };
14404
14761
  } else {
14405
14762
  const content = typeof args.content === "string" ? args.content : "";
14406
- block = toWholeFileEditBlock(relPath, content, codeMode.rootDir);
14763
+ block = toWholeFileEditBlock(relPath, content, currentRootDir);
14407
14764
  }
14408
14765
  const applyNow = () => {
14409
- const snaps = snapshotBeforeEdits([block], codeMode.rootDir);
14410
- const results = applyEditBlocks([block], codeMode.rootDir);
14766
+ const snaps = snapshotBeforeEdits([block], currentRootDir);
14767
+ const results = applyEditBlocks([block], currentRootDir);
14411
14768
  const good = results.some((r) => r.status === "applied" || r.status === "created");
14412
14769
  if (good) {
14413
14770
  recordEdit("auto", [block], results, snaps);
@@ -14476,8 +14833,8 @@ function App({
14476
14833
  if (selected.length === 0) {
14477
14834
  return "\u25B8 no edits matched those indices \u2014 nothing applied. Use /apply with no args to commit them all.";
14478
14835
  }
14479
- const snaps = snapshotBeforeEdits(selected, codeMode.rootDir);
14480
- const results = applyEditBlocks(selected, codeMode.rootDir);
14836
+ const snaps = snapshotBeforeEdits(selected, currentRootDir);
14837
+ const results = applyEditBlocks(selected, currentRootDir);
14481
14838
  const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
14482
14839
  if (anyApplied) recordEdit("review-apply", selected, results, snaps);
14483
14840
  pendingEdits.current = remaining;
@@ -14488,7 +14845,7 @@ function App({
14488
14845
  \u25B8 ${remaining.length} edit block(s) still pending \u2014 /apply or /discard to clear them.` : "";
14489
14846
  return formatEditResults(results) + tail;
14490
14847
  },
14491
- [codeMode, session, syncPendingCount, recordEdit]
14848
+ [codeMode, currentRootDir, session, syncPendingCount, recordEdit]
14492
14849
  );
14493
14850
  const codeDiscard = useCallback4(
14494
14851
  (indices) => {
@@ -14653,7 +15010,7 @@ function App({
14653
15010
  const hashParse = detectHashMemory(text);
14654
15011
  if (hashParse?.kind === "memory" || hashParse?.kind === "memory-global") {
14655
15012
  const isGlobal = hashParse.kind === "memory-global";
14656
- const memRoot = codeMode?.rootDir ?? process.cwd();
15013
+ const memRoot = currentRootDir;
14657
15014
  promptHistory.current.push(text);
14658
15015
  try {
14659
15016
  const result = isGlobal ? appendGlobalMemory(hashParse.note) : appendProjectMemory(memRoot, hashParse.note);
@@ -14684,7 +15041,7 @@ function App({
14684
15041
  }
14685
15042
  const bangCmd = detectBangCommand(text);
14686
15043
  if (bangCmd !== null) {
14687
- const bangRoot = codeMode?.rootDir ?? process.cwd();
15044
+ const bangRoot = currentRootDir;
14688
15045
  promptHistory.current.push(text);
14689
15046
  setHistorical((prev) => [
14690
15047
  ...prev,
@@ -14747,10 +15104,10 @@ function App({
14747
15104
  codeDiscard: codeMode ? codeDiscard : void 0,
14748
15105
  codeHistory: codeMode ? codeHistory : void 0,
14749
15106
  codeShowEdit: codeMode ? codeShowEdit : void 0,
14750
- codeRoot: codeMode?.rootDir,
15107
+ codeRoot: codeMode ? currentRootDir : void 0,
14751
15108
  pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
14752
15109
  toolHistory: () => toolHistoryRef.current,
14753
- memoryRoot: codeMode?.rootDir ?? process.cwd(),
15110
+ memoryRoot: currentRootDir,
14754
15111
  planMode,
14755
15112
  setPlanMode: codeMode ? togglePlanMode : void 0,
14756
15113
  clearPendingPlan: codeMode ? clearPendingPlan : void 0,
@@ -14774,10 +15131,11 @@ function App({
14774
15131
  { id: `sys-late-${Date.now()}-${Math.random()}`, role: "info", text: text2 }
14775
15132
  ]),
14776
15133
  reloadHooks: () => {
14777
- const fresh = loadHooks({ projectRoot: codeMode?.rootDir });
15134
+ const fresh = loadHooks({ projectRoot: codeMode ? currentRootDir : void 0 });
14778
15135
  setHookList(fresh);
14779
15136
  return fresh.length;
14780
15137
  },
15138
+ setCwd: (newRoot) => applyCwdChange(newRoot),
14781
15139
  latestVersion,
14782
15140
  refreshLatestVersion,
14783
15141
  models: models2,
@@ -14858,7 +15216,7 @@ function App({
14858
15216
  if (hookList.some((h) => h.event === "UserPromptSubmit")) {
14859
15217
  const promptReport = await runHooks({
14860
15218
  hooks: hookList,
14861
- payload: { event: "UserPromptSubmit", cwd: hookCwd, prompt: text }
15219
+ payload: { event: "UserPromptSubmit", cwd: currentRootDir, prompt: text }
14862
15220
  });
14863
15221
  if (promptReport.outcomes.length > 0) {
14864
15222
  setHistorical((prev) => [
@@ -14926,8 +15284,8 @@ function App({
14926
15284
  };
14927
15285
  const timer = PLAIN_UI ? null : setInterval(flush, FLUSH_INTERVAL_MS);
14928
15286
  let modelInput = text;
14929
- if (codeMode?.rootDir) {
14930
- const expanded = expandAtMentions(text, codeMode.rootDir);
15287
+ if (codeMode) {
15288
+ const expanded = expandAtMentions(text, currentRootDir);
14931
15289
  if (expanded.expansions.length > 0) {
14932
15290
  modelInput = expanded.text;
14933
15291
  const inlined = expanded.expansions.filter((ex) => ex.ok).map((ex) => `${ex.path} (${(ex.bytes ?? 0).toLocaleString()} bytes)`);
@@ -15058,8 +15416,8 @@ function App({
15058
15416
  const blocks = parseEditBlocks(finalText);
15059
15417
  if (blocks.length > 0) {
15060
15418
  if (editModeRef.current === "auto" || editModeRef.current === "yolo") {
15061
- const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
15062
- const results = applyEditBlocks(blocks, codeMode.rootDir);
15419
+ const snaps = snapshotBeforeEdits(blocks, currentRootDir);
15420
+ const results = applyEditBlocks(blocks, currentRootDir);
15063
15421
  const good = results.some(
15064
15422
  (r) => r.status === "applied" || r.status === "created"
15065
15423
  );
@@ -15145,6 +15503,18 @@ function App({
15145
15503
  } catch {
15146
15504
  }
15147
15505
  }
15506
+ if (ev.toolName === "change_workspace" && ev.content.includes('"WorkspaceConfirmationError:') && ev.toolArgs) {
15507
+ try {
15508
+ const parsed = JSON.parse(ev.toolArgs);
15509
+ if (typeof parsed.path === "string" && parsed.path.trim()) {
15510
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
15511
+ const expanded = parsed.path.startsWith("~") && home ? pathMod7.join(home, parsed.path.slice(1)) : parsed.path;
15512
+ const abs = pathMod7.resolve(expanded);
15513
+ setPendingWorkspace({ path: abs });
15514
+ }
15515
+ } catch {
15516
+ }
15517
+ }
15148
15518
  if (codeMode && ev.toolName === "submit_plan" && ev.content.includes('"PlanProposedError:')) {
15149
15519
  try {
15150
15520
  const parsed = JSON.parse(ev.content);
@@ -15267,7 +15637,7 @@ function App({
15267
15637
  hooks: hookList,
15268
15638
  payload: {
15269
15639
  event: "Stop",
15270
- cwd: hookCwd,
15640
+ cwd: currentRootDir,
15271
15641
  lastAssistantText: streamRef.text,
15272
15642
  turn: loop2.stats.summary().turns
15273
15643
  }
@@ -15305,8 +15675,8 @@ function App({
15305
15675
  codeMode,
15306
15676
  codeShowEdit,
15307
15677
  codeUndo,
15678
+ currentRootDir,
15308
15679
  exit2,
15309
- hookCwd,
15310
15680
  hookList,
15311
15681
  loop2,
15312
15682
  latestVersion,
@@ -15341,7 +15711,8 @@ function App({
15341
15711
  stopLoop,
15342
15712
  startLoop,
15343
15713
  getLoopStatus,
15344
- startWalkthrough
15714
+ startWalkthrough,
15715
+ applyCwdChange
15345
15716
  ]
15346
15717
  );
15347
15718
  useEffect6(() => {
@@ -15398,13 +15769,13 @@ function App({
15398
15769
  } else {
15399
15770
  if (choice === "always_allow") {
15400
15771
  const prefix = derivePrefix(cmd);
15401
- addProjectShellAllowed(codeMode.rootDir, prefix);
15772
+ addProjectShellAllowed(currentRootDir, prefix);
15402
15773
  setHistorical((prev) => [
15403
15774
  ...prev,
15404
15775
  {
15405
15776
  id: `sh-allow-${Date.now()}`,
15406
15777
  role: "info",
15407
- text: `\u25B8 always allowed "${prefix}" for ${codeMode.rootDir}`
15778
+ text: `\u25B8 always allowed "${prefix}" for ${currentRootDir}`
15408
15779
  }
15409
15780
  ]);
15410
15781
  }
@@ -15421,7 +15792,7 @@ function App({
15421
15792
  let jobId = null;
15422
15793
  let preview = "";
15423
15794
  try {
15424
- const res = await codeMode.jobs.start(cmd, { cwd: codeMode.rootDir });
15795
+ const res = await codeMode.jobs.start(cmd, { cwd: currentRootDir });
15425
15796
  startedOk = true;
15426
15797
  jobId = res.jobId;
15427
15798
  preview = res.preview;
@@ -15455,7 +15826,7 @@ ${msg}`;
15455
15826
  } else {
15456
15827
  let body;
15457
15828
  try {
15458
- const res = await runCommand(cmd, { cwd: codeMode.rootDir });
15829
+ const res = await runCommand(cmd, { cwd: currentRootDir });
15459
15830
  body = formatCommandResult(cmd, res);
15460
15831
  } catch (err) {
15461
15832
  body = `$ ${cmd}
@@ -15477,7 +15848,7 @@ ${body}`;
15477
15848
  await handleSubmit(synthetic);
15478
15849
  }
15479
15850
  },
15480
- [pendingShell, codeMode, handleSubmit, busy, loop2]
15851
+ [pendingShell, codeMode, currentRootDir, handleSubmit, busy, loop2]
15481
15852
  );
15482
15853
  useEffect6(() => {
15483
15854
  if (!busy && queuedSubmit !== null) {
@@ -15486,6 +15857,40 @@ ${body}`;
15486
15857
  void handleSubmit(text);
15487
15858
  }
15488
15859
  }, [busy, queuedSubmit, handleSubmit]);
15860
+ const handleWorkspaceConfirm = useCallback4(
15861
+ async (choice) => {
15862
+ const pending = pendingWorkspace;
15863
+ if (!pending) return;
15864
+ const target = pending.path;
15865
+ setPendingWorkspace(null);
15866
+ let synthetic;
15867
+ if (choice === "deny") {
15868
+ setHistorical((prev) => [
15869
+ ...prev,
15870
+ {
15871
+ id: `ws-deny-${Date.now()}`,
15872
+ role: "info",
15873
+ text: `\u25B8 denied workspace switch: ${target}`
15874
+ }
15875
+ ]);
15876
+ synthetic = `I denied switching the workspace to \`${target}\`. Please continue without changing directories.`;
15877
+ } else {
15878
+ const info = applyCwdChange(target);
15879
+ setHistorical((prev) => [
15880
+ ...prev,
15881
+ { id: `ws-switch-${Date.now()}`, role: "info", text: info }
15882
+ ]);
15883
+ synthetic = `I approved the workspace switch. The session is now rooted at \`${target}\` \u2014 your filesystem / shell / memory tools resolve against that path on every subsequent call. Continue with my original request from this new root.`;
15884
+ }
15885
+ if (busy) {
15886
+ loop2.abort();
15887
+ setQueuedSubmit(synthetic);
15888
+ } else {
15889
+ await handleSubmit(synthetic);
15890
+ }
15891
+ },
15892
+ [pendingWorkspace, applyCwdChange, busy, loop2, handleSubmit]
15893
+ );
15489
15894
  const handlePlanConfirm = useCallback4(
15490
15895
  async (choice) => {
15491
15896
  const hadPendingPlan = pendingPlan !== null;
@@ -15792,12 +16197,12 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15792
16197
  async (choice) => handleReviseConfirmRef.current(choice),
15793
16198
  []
15794
16199
  );
15795
- return /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(
16200
+ return /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(
15796
16201
  TickerProvider,
15797
16202
  {
15798
- disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingEditReview || walkthroughActive || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
16203
+ disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingWorkspace || !!pendingEditReview || walkthroughActive || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
15799
16204
  },
15800
- /* @__PURE__ */ React23.createElement(Box21, { flexDirection: "column" }, /* @__PURE__ */ React23.createElement(
16205
+ /* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React24.createElement(
15801
16206
  StatsPanel,
15802
16207
  {
15803
16208
  summary,
@@ -15814,28 +16219,28 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15814
16219
  proArmed,
15815
16220
  escalated: turnOnPro
15816
16221
  }
15817
- ), /* @__PURE__ */ React23.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React23.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !historical.some((e) => e.role === "user" || e.role === "assistant") && !busy && !streaming ? /* @__PURE__ */ React23.createElement(WelcomeBanner, { inCodeMode: !!codeMode }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React23.createElement(Box21, { marginY: 1 }, /* @__PURE__ */ React23.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && ongoingTool ? /* @__PURE__ */ React23.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && subagentActivity ? /* @__PURE__ */ React23.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !ongoingTool && statusLine ? /* @__PURE__ */ React23.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision ? /* @__PURE__ */ React23.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React23.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React23.createElement(
16222
+ ), /* @__PURE__ */ React24.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React24.createElement(EventRow, { key: item.id, event: item, projectRoot: currentRootDir })), !historical.some((e) => e.role === "user" || e.role === "assistant") && !busy && !streaming ? /* @__PURE__ */ React24.createElement(WelcomeBanner, { inCodeMode: !!codeMode }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React24.createElement(Box22, { marginY: 1 }, /* @__PURE__ */ React24.createElement(EventRow, { event: streaming, projectRoot: currentRootDir })) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && ongoingTool ? /* @__PURE__ */ React24.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && subagentActivity ? /* @__PURE__ */ React24.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !ongoingTool && statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision ? /* @__PURE__ */ React24.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React24.createElement(
15818
16223
  PlanRefineInput,
15819
16224
  {
15820
16225
  mode: stagedInput.mode,
15821
16226
  onSubmit: handleStagedInputSubmit,
15822
16227
  onCancel: handleStagedInputCancel
15823
16228
  }
15824
- ) : stagedCheckpointRevise ? /* @__PURE__ */ React23.createElement(
16229
+ ) : stagedCheckpointRevise ? /* @__PURE__ */ React24.createElement(
15825
16230
  PlanRefineInput,
15826
16231
  {
15827
16232
  mode: "checkpoint-revise",
15828
16233
  onSubmit: handleCheckpointReviseSubmit,
15829
16234
  onCancel: handleCheckpointReviseCancel
15830
16235
  }
15831
- ) : stagedChoiceCustom ? /* @__PURE__ */ React23.createElement(
16236
+ ) : stagedChoiceCustom ? /* @__PURE__ */ React24.createElement(
15832
16237
  PlanRefineInput,
15833
16238
  {
15834
16239
  mode: "choice-custom",
15835
16240
  onSubmit: handleChoiceCustomSubmit,
15836
16241
  onCancel: handleChoiceCustomCancel
15837
16242
  }
15838
- ) : pendingChoice ? /* @__PURE__ */ React23.createElement(
16243
+ ) : pendingChoice ? /* @__PURE__ */ React24.createElement(
15839
16244
  ChoiceConfirm,
15840
16245
  {
15841
16246
  question: pendingChoice.question,
@@ -15843,7 +16248,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15843
16248
  allowCustom: pendingChoice.allowCustom,
15844
16249
  onChoose: stableHandleChoiceConfirm
15845
16250
  }
15846
- ) : pendingRevision ? /* @__PURE__ */ React23.createElement(
16251
+ ) : pendingRevision ? /* @__PURE__ */ React24.createElement(
15847
16252
  PlanReviseConfirm,
15848
16253
  {
15849
16254
  reason: pendingRevision.reason,
@@ -15854,7 +16259,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15854
16259
  summary: pendingRevision.summary,
15855
16260
  onChoose: stableHandleReviseConfirm
15856
16261
  }
15857
- ) : pendingCheckpoint ? /* @__PURE__ */ React23.createElement(
16262
+ ) : pendingCheckpoint ? /* @__PURE__ */ React24.createElement(
15858
16263
  PlanCheckpointConfirm,
15859
16264
  {
15860
16265
  stepId: pendingCheckpoint.stepId,
@@ -15865,16 +16270,16 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15865
16270
  completedStepIds: completedStepIdsRef.current,
15866
16271
  onChoose: stableHandleCheckpointConfirm
15867
16272
  }
15868
- ) : pendingPlan ? /* @__PURE__ */ React23.createElement(
16273
+ ) : pendingPlan ? /* @__PURE__ */ React24.createElement(
15869
16274
  PlanConfirm,
15870
16275
  {
15871
16276
  plan: pendingPlan,
15872
16277
  steps: planStepsRef.current ?? void 0,
15873
16278
  summary: planSummaryRef.current ?? void 0,
15874
16279
  onChoose: stableHandlePlanConfirm,
15875
- projectRoot: hookCwd
16280
+ projectRoot: currentRootDir
15876
16281
  }
15877
- ) : pendingShell ? /* @__PURE__ */ React23.createElement(
16282
+ ) : pendingShell ? /* @__PURE__ */ React24.createElement(
15878
16283
  ShellConfirm,
15879
16284
  {
15880
16285
  command: pendingShell.command,
@@ -15882,26 +16287,34 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15882
16287
  kind: pendingShell.kind,
15883
16288
  onChoose: handleShellConfirm
15884
16289
  }
15885
- ) : pendingEditReview ? /* @__PURE__ */ React23.createElement(
16290
+ ) : pendingWorkspace ? /* @__PURE__ */ React24.createElement(
16291
+ WorkspaceConfirm,
16292
+ {
16293
+ path: pendingWorkspace.path,
16294
+ currentRoot: currentRootDir,
16295
+ mcpServerCount: mcpServers?.length ?? 0,
16296
+ onChoose: handleWorkspaceConfirm
16297
+ }
16298
+ ) : pendingEditReview ? /* @__PURE__ */ React24.createElement(
15886
16299
  EditConfirm,
15887
16300
  {
15888
16301
  block: pendingEditReview,
15889
16302
  onChoose: (choice) => {
15890
- const resolve9 = editReviewResolveRef.current;
15891
- if (resolve9) {
16303
+ const resolve12 = editReviewResolveRef.current;
16304
+ if (resolve12) {
15892
16305
  editReviewResolveRef.current = null;
15893
- resolve9(choice);
16306
+ resolve12(choice);
15894
16307
  }
15895
16308
  }
15896
16309
  }
15897
- ) : walkthroughActive && pendingEdits.current.length > 0 ? /* @__PURE__ */ React23.createElement(
16310
+ ) : walkthroughActive && pendingEdits.current.length > 0 ? /* @__PURE__ */ React24.createElement(
15898
16311
  EditConfirm,
15899
16312
  {
15900
16313
  key: `walk-${pendingTick}`,
15901
16314
  block: pendingEdits.current[0],
15902
16315
  onChoose: handleWalkChoice
15903
16316
  }
15904
- ) : /* @__PURE__ */ React23.createElement(React23.Fragment, null, codeMode ? /* @__PURE__ */ React23.createElement(
16317
+ ) : /* @__PURE__ */ React24.createElement(React24.Fragment, null, codeMode ? /* @__PURE__ */ React24.createElement(
15905
16318
  ModeStatusBar,
15906
16319
  {
15907
16320
  editMode,
@@ -15911,7 +16324,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15911
16324
  undoArmed: !!undoBanner || hasUndoable(),
15912
16325
  jobs: codeMode.jobs
15913
16326
  }
15914
- ) : null, activeLoop ? /* @__PURE__ */ React23.createElement(LoopStatusRow, { loop: activeLoop }) : null, /* @__PURE__ */ React23.createElement(
16327
+ ) : null, activeLoop ? /* @__PURE__ */ React24.createElement(LoopStatusRow, { loop: activeLoop }) : null, /* @__PURE__ */ React24.createElement(
15915
16328
  PromptInput,
15916
16329
  {
15917
16330
  value: input,
@@ -15921,14 +16334,14 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15921
16334
  onHistoryPrev: recallPrev,
15922
16335
  onHistoryNext: recallNext
15923
16336
  }
15924
- ), /* @__PURE__ */ React23.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React23.createElement(
16337
+ ), /* @__PURE__ */ React24.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React24.createElement(
15925
16338
  AtMentionSuggestions,
15926
16339
  {
15927
16340
  matches: atMatches,
15928
16341
  selectedIndex: atSelected,
15929
16342
  query: atPicker?.query ?? ""
15930
16343
  }
15931
- ), slashArgContext ? /* @__PURE__ */ React23.createElement(
16344
+ ), slashArgContext ? /* @__PURE__ */ React24.createElement(
15932
16345
  SlashArgPicker,
15933
16346
  {
15934
16347
  matches: slashArgMatches,
@@ -15942,15 +16355,15 @@ Continue executing from the next pending step. Call mark_step_complete after eac
15942
16355
  }
15943
16356
 
15944
16357
  // src/cli/ui/SessionPicker.tsx
15945
- import { Box as Box22, Text as Text20 } from "ink";
15946
- import React24 from "react";
16358
+ import { Box as Box23, Text as Text21 } from "ink";
16359
+ import React25 from "react";
15947
16360
  function SessionPicker({
15948
16361
  sessionName,
15949
16362
  messageCount,
15950
16363
  lastActive,
15951
16364
  onChoose
15952
16365
  }) {
15953
- return /* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React24.createElement(Box22, { marginBottom: 1 }, /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, ` \xB7 last active ${relativeTime2(lastActive)}`)), /* @__PURE__ */ React24.createElement(
16366
+ return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React25.createElement(Box23, { marginBottom: 1 }, /* @__PURE__ */ React25.createElement(Text21, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, ` \xB7 last active ${relativeTime2(lastActive)}`)), /* @__PURE__ */ React25.createElement(
15954
16367
  SingleSelect,
15955
16368
  {
15956
16369
  initialValue: "new",
@@ -15973,7 +16386,7 @@ function SessionPicker({
15973
16386
  ],
15974
16387
  onSubmit: (v) => onChoose(v)
15975
16388
  }
15976
- ), /* @__PURE__ */ React24.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] select")));
16389
+ ), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] select")));
15977
16390
  }
15978
16391
  function relativeTime2(date) {
15979
16392
  const ms = Date.now() - date.getTime();
@@ -15989,9 +16402,9 @@ function relativeTime2(date) {
15989
16402
  }
15990
16403
 
15991
16404
  // src/cli/ui/Setup.tsx
15992
- import { Box as Box23, Text as Text21, useApp as useApp2 } from "ink";
16405
+ import { Box as Box24, Text as Text22, useApp as useApp2 } from "ink";
15993
16406
  import TextInput from "ink-text-input";
15994
- import React25, { useState as useState11 } from "react";
16407
+ import React26, { useState as useState11 } from "react";
15995
16408
  function Setup({ onReady }) {
15996
16409
  const [value, setValue] = useState11("");
15997
16410
  const [error, setError] = useState11(null);
@@ -16015,7 +16428,7 @@ function Setup({ onReady }) {
16015
16428
  }
16016
16429
  onReady(trimmed);
16017
16430
  };
16018
- return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React25.createElement(Text21, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React25.createElement(
16431
+ return /* @__PURE__ */ React26.createElement(Box24, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React26.createElement(Text22, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React26.createElement(
16019
16432
  TextInput,
16020
16433
  {
16021
16434
  value,
@@ -16024,7 +16437,7 @@ function Setup({ onReady }) {
16024
16437
  mask: "\u2022",
16025
16438
  placeholder: "sk-..."
16026
16439
  }
16027
- )), error ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { color: "red" }, error)) : value ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "(Type /exit to abort.)")));
16440
+ )), error ? /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { color: "red" }, error)) : value ? /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "(Type /exit to abort.)")));
16028
16441
  }
16029
16442
 
16030
16443
  // src/cli/commands/chat.tsx
@@ -16040,7 +16453,7 @@ function Root({
16040
16453
  const [key, setKey] = useState12(initialKey);
16041
16454
  const [pending, setPending] = useState12(sessionPreview);
16042
16455
  if (!key) {
16043
- return /* @__PURE__ */ React26.createElement(
16456
+ return /* @__PURE__ */ React27.createElement(
16044
16457
  Setup,
16045
16458
  {
16046
16459
  onReady: (k) => {
@@ -16052,7 +16465,7 @@ function Root({
16052
16465
  }
16053
16466
  process.env.DEEPSEEK_API_KEY = key;
16054
16467
  if (pending && appProps.session) {
16055
- return /* @__PURE__ */ React26.createElement(KeystrokeProvider, null, /* @__PURE__ */ React26.createElement(
16468
+ return /* @__PURE__ */ React27.createElement(KeystrokeProvider, null, /* @__PURE__ */ React27.createElement(
16056
16469
  SessionPicker,
16057
16470
  {
16058
16471
  sessionName: appProps.session,
@@ -16067,7 +16480,7 @@ function Root({
16067
16480
  }
16068
16481
  ));
16069
16482
  }
16070
- return /* @__PURE__ */ React26.createElement(KeystrokeProvider, null, /* @__PURE__ */ React26.createElement(
16483
+ return /* @__PURE__ */ React27.createElement(KeystrokeProvider, null, /* @__PURE__ */ React27.createElement(
16071
16484
  App,
16072
16485
  {
16073
16486
  model: appProps.model,
@@ -16165,14 +16578,14 @@ async function chatCommand(opts) {
16165
16578
  const prior = loadSessionMessages(opts.session);
16166
16579
  if (prior.length > 0) {
16167
16580
  const p = sessionPath(opts.session);
16168
- const mtime = existsSync13(p) ? statSync7(p).mtime : /* @__PURE__ */ new Date();
16581
+ const mtime = existsSync16(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
16169
16582
  sessionPreview = { messageCount: prior.length, lastActive: mtime };
16170
16583
  }
16171
16584
  } else if (opts.session && opts.forceNew) {
16172
16585
  rewriteSession(opts.session, []);
16173
16586
  }
16174
16587
  const { waitUntilExit } = render(
16175
- /* @__PURE__ */ React26.createElement(
16588
+ /* @__PURE__ */ React27.createElement(
16176
16589
  Root,
16177
16590
  {
16178
16591
  initialKey,
@@ -16196,7 +16609,7 @@ async function chatCommand(opts) {
16196
16609
  }
16197
16610
 
16198
16611
  // src/cli/commands/code.tsx
16199
- import { basename as basename2, resolve as resolve7 } from "path";
16612
+ import { basename as basename2, resolve as resolve10 } from "path";
16200
16613
 
16201
16614
  // src/index/semantic/builder.ts
16202
16615
  import { promises as fs5 } from "fs";
@@ -16843,29 +17256,32 @@ async function bootstrapSemanticSearchInCodeMode(registry, rootDir, opts = {}) {
16843
17256
 
16844
17257
  // src/cli/commands/code.tsx
16845
17258
  async function codeCommand(opts = {}) {
16846
- const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-FMYQ7IDW.js");
16847
- const rootDir = resolve7(opts.dir ?? process.cwd());
17259
+ const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-YRY4HPMZ.js");
17260
+ const rootDir = resolve10(opts.dir ?? process.cwd());
16848
17261
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
16849
17262
  const tools = new ToolRegistry();
16850
- registerFilesystemTools(tools, { rootDir });
16851
17263
  const jobs2 = new JobRegistry();
16852
- registerShellTools(tools, {
16853
- rootDir,
16854
- // Per-project "always allow" list persisted from prior ShellConfirm
16855
- // choices; merged on top of the built-in allowlist in shell.ts.
16856
- // GETTER form — re-read every dispatch so a prefix the user adds
16857
- // via ShellConfirm mid-session takes effect on the next shell call
16858
- // instead of waiting for `/new` or a relaunch.
16859
- extraAllowed: () => loadProjectShellAllowed(rootDir),
16860
- // `yolo` edit-mode disables shell confirmations entirely. Re-read
16861
- // from config on each dispatch so /mode yolo (or Shift+Tab cycling
16862
- // through to it) flips the gate live without forcing a relaunch.
16863
- allowAll: () => loadEditMode() === "yolo",
16864
- jobs: jobs2
16865
- });
17264
+ const registerRootedTools = (root) => {
17265
+ registerFilesystemTools(tools, { rootDir: root });
17266
+ registerShellTools(tools, {
17267
+ rootDir: root,
17268
+ // Per-project "always allow" list persisted from prior ShellConfirm
17269
+ // choices; merged on top of the built-in allowlist in shell.ts.
17270
+ // GETTER form re-read every dispatch so a prefix the user adds
17271
+ // via ShellConfirm mid-session takes effect on the next shell call
17272
+ // instead of waiting for `/new` or a relaunch.
17273
+ extraAllowed: () => loadProjectShellAllowed(root),
17274
+ // `yolo` edit-mode disables shell confirmations entirely. Re-read
17275
+ // from config on each dispatch so /mode yolo (or Shift+Tab cycling
17276
+ // through to it) flips the gate live without forcing a relaunch.
17277
+ allowAll: () => loadEditMode() === "yolo",
17278
+ jobs: jobs2
17279
+ });
17280
+ registerMemoryTools(tools, { projectRoot: root });
17281
+ };
17282
+ registerRootedTools(rootDir);
16866
17283
  registerPlanTool(tools);
16867
17284
  registerChoiceTool(tools);
16868
- registerMemoryTools(tools, { projectRoot: rootDir });
16869
17285
  const semantic2 = await bootstrapSemanticSearchInCodeMode(tools, rootDir);
16870
17286
  process.stderr.write(
16871
17287
  `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)${semantic2.enabled ? " \xB7 semantic_search on" : ""}
@@ -16881,7 +17297,7 @@ async function codeCommand(opts = {}) {
16881
17297
  transcript: opts.transcript,
16882
17298
  session,
16883
17299
  seedTools: tools,
16884
- codeMode: { rootDir, jobs: jobs2 },
17300
+ codeMode: { rootDir, jobs: jobs2, reregisterTools: registerRootedTools },
16885
17301
  forceResume: opts.forceResume,
16886
17302
  forceNew: opts.forceNew
16887
17303
  });
@@ -16891,35 +17307,35 @@ async function codeCommand(opts = {}) {
16891
17307
  import { writeFileSync as writeFileSync8 } from "fs";
16892
17308
  import { basename as basename3 } from "path";
16893
17309
  import { render as render2 } from "ink";
16894
- import React29 from "react";
17310
+ import React30 from "react";
16895
17311
 
16896
17312
  // src/cli/ui/DiffApp.tsx
16897
- import { Box as Box25, Static as Static2, Text as Text23, useApp as useApp3, useInput } from "ink";
16898
- import React28, { useState as useState13 } from "react";
17313
+ import { Box as Box26, Static as Static2, Text as Text24, useApp as useApp3, useInput } from "ink";
17314
+ import React29, { useState as useState13 } from "react";
16899
17315
 
16900
17316
  // src/cli/ui/RecordView.tsx
16901
- import { Box as Box24, Text as Text22 } from "ink";
16902
- import React27 from "react";
17317
+ import { Box as Box25, Text as Text23 } from "ink";
17318
+ import React28 from "react";
16903
17319
  function RecordView({ rec, compact: compact2 = false }) {
16904
17320
  const toolArgsMax = compact2 ? 120 : 200;
16905
17321
  const toolContentMax = compact2 ? 200 : 400;
16906
17322
  if (rec.role === "user") {
16907
17323
  const content = rec.content.includes("\n") ? rec.content.split("\n").join("\n ") : rec.content;
16908
- return /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React27.createElement(Text22, null, content));
17324
+ return /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React28.createElement(Text23, null, content));
16909
17325
  }
16910
17326
  if (rec.role === "assistant_final") {
16911
- return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React27.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React27.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React27.createElement(Text22, null, rec.content) : /* @__PURE__ */ React27.createElement(Text22, { dimColor: true, italic: true }, "(tool-call response only)"));
17327
+ return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React28.createElement(Box25, null, /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React28.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React28.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React28.createElement(Text23, null, rec.content) : /* @__PURE__ */ React28.createElement(Text23, { dimColor: true, italic: true }, "(tool-call response only)"));
16912
17328
  }
16913
17329
  if (rec.role === "tool") {
16914
- return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, " args: ", truncate2(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, " \u2192 ", truncate2(rec.content, toolContentMax)));
17330
+ return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " args: ", truncate2(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \u2192 ", truncate2(rec.content, toolContentMax)));
16915
17331
  }
16916
17332
  if (rec.role === "error") {
16917
- return /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React27.createElement(Text22, { color: "red" }, rec.error ?? rec.content));
17333
+ return /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React28.createElement(Text23, { color: "red" }, rec.error ?? rec.content));
16918
17334
  }
16919
17335
  if (rec.role === "done" || rec.role === "assistant_delta") {
16920
17336
  return null;
16921
17337
  }
16922
- return /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, "[", rec.role, "] ", rec.content));
17338
+ return /* @__PURE__ */ React28.createElement(Box25, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, "[", rec.role, "] ", rec.content));
16923
17339
  }
16924
17340
  function CacheBadge({ usage }) {
16925
17341
  const hit = usage.prompt_cache_hit_tokens ?? 0;
@@ -16928,7 +17344,7 @@ function CacheBadge({ usage }) {
16928
17344
  if (total === 0) return null;
16929
17345
  const pct2 = hit / total * 100;
16930
17346
  const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
16931
- return /* @__PURE__ */ React27.createElement(Text22, null, /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React27.createElement(Text22, { color }, pct2.toFixed(1), "%"));
17347
+ return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color }, pct2.toFixed(1), "%"));
16932
17348
  }
16933
17349
  function truncate2(s, max) {
16934
17350
  return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
@@ -16962,7 +17378,7 @@ function DiffApp({ report }) {
16962
17378
  }
16963
17379
  });
16964
17380
  const pair = report.pairs[idx];
16965
- return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column" }, /* @__PURE__ */ React28.createElement(DiffHeader, { report }), /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React28.createElement(Text23, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React28.createElement(Text23, null, pair ? /* @__PURE__ */ React28.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React28.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React28.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React28.createElement(Text23, null, pair.divergenceNote)) : null, /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "j"), "/", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "k"), "/", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "N"), "/", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "g"), "/", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "q"), " ", "quit")));
17381
+ return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column" }, /* @__PURE__ */ React29.createElement(DiffHeader, { report }), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React29.createElement(Text24, null, pair ? /* @__PURE__ */ React29.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React29.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React29.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React29.createElement(Text24, null, pair.divergenceNote)) : null, /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "j"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "k"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "N"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "g"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "q"), " ", "quit")));
16966
17382
  }
16967
17383
  function DiffHeader({ report }) {
16968
17384
  const a = report.a;
@@ -16980,15 +17396,15 @@ function DiffHeader({ report }) {
16980
17396
  } else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
16981
17397
  prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
16982
17398
  }
16983
- return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React28.createElement(Box25, { justifyContent: "space-between" }, /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React28.createElement(Text23, { color: "blue" }, a.label), /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " vs B="), /* @__PURE__ */ React28.createElement(Text23, { color: "magenta" }, b.label)), /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, "cache "), /* @__PURE__ */ React28.createElement(Text23, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React28.createElement(Text23, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React28.createElement(Text23, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, "cost "), /* @__PURE__ */ React28.createElement(Text23, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React28.createElement(Text23, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React28.createElement(Text23, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, "model calls "), /* @__PURE__ */ React28.createElement(Text23, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true, italic: true }, prefixLine)) : null);
17399
+ return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React29.createElement(Box26, { justifyContent: "space-between" }, /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React29.createElement(Text24, { color: "blue" }, a.label), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " vs B="), /* @__PURE__ */ React29.createElement(Text24, { color: "magenta" }, b.label)), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "cache "), /* @__PURE__ */ React29.createElement(Text24, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React29.createElement(Text24, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React29.createElement(Text24, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "cost "), /* @__PURE__ */ React29.createElement(Text24, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React29.createElement(Text24, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React29.createElement(Text24, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "model calls "), /* @__PURE__ */ React29.createElement(Text24, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true, italic: true }, prefixLine)) : null);
16984
17400
  }
16985
17401
  function Pane({
16986
17402
  label,
16987
17403
  headerColor,
16988
17404
  records
16989
17405
  }) {
16990
- return /* @__PURE__ */ React28.createElement(
16991
- Box25,
17406
+ return /* @__PURE__ */ React29.createElement(
17407
+ Box26,
16992
17408
  {
16993
17409
  flexDirection: "column",
16994
17410
  flexGrow: 1,
@@ -16996,21 +17412,21 @@ function Pane({
16996
17412
  borderStyle: "single",
16997
17413
  borderColor: headerColor
16998
17414
  },
16999
- /* @__PURE__ */ React28.createElement(Text23, { color: headerColor, bold: true }, label),
17000
- records.length === 0 ? /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React28.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React28.createElement(RecordView, { key, rec, compact: true }))
17415
+ /* @__PURE__ */ React29.createElement(Text24, { color: headerColor, bold: true }, label),
17416
+ records.length === 0 ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React29.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React29.createElement(RecordView, { key, rec, compact: true }))
17001
17417
  );
17002
17418
  }
17003
17419
  function KindBadge({ kind }) {
17004
17420
  if (kind === "match") {
17005
- return /* @__PURE__ */ React28.createElement(Text23, { color: "green" }, "\u2713 match");
17421
+ return /* @__PURE__ */ React29.createElement(Text24, { color: "green" }, "\u2713 match");
17006
17422
  }
17007
17423
  if (kind === "diverge") {
17008
- return /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, "\u2605 diverge");
17424
+ return /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "\u2605 diverge");
17009
17425
  }
17010
17426
  if (kind === "only_in_a") {
17011
- return /* @__PURE__ */ React28.createElement(Text23, { color: "blue" }, "\u2190 only in A");
17427
+ return /* @__PURE__ */ React29.createElement(Text24, { color: "blue" }, "\u2190 only in A");
17012
17428
  }
17013
- return /* @__PURE__ */ React28.createElement(Text23, { color: "magenta" }, "\u2192 only in B");
17429
+ return /* @__PURE__ */ React29.createElement(Text24, { color: "magenta" }, "\u2192 only in B");
17014
17430
  }
17015
17431
  function paneRecords(pair, side) {
17016
17432
  if (!pair) return [];
@@ -17041,7 +17457,7 @@ markdown report written to ${opts.mdPath}`);
17041
17457
  return;
17042
17458
  }
17043
17459
  if (wantTui) {
17044
- const { waitUntilExit } = render2(React29.createElement(DiffApp, { report }), {
17460
+ const { waitUntilExit } = render2(React30.createElement(DiffApp, { report }), {
17045
17461
  exitOnCtrlC: true,
17046
17462
  patchConsole: false
17047
17463
  });
@@ -17052,7 +17468,7 @@ markdown report written to ${opts.mdPath}`);
17052
17468
  }
17053
17469
 
17054
17470
  // src/cli/commands/index.ts
17055
- import { resolve as resolve8 } from "path";
17471
+ import { resolve as resolve11 } from "path";
17056
17472
 
17057
17473
  // src/index/semantic/preflight.ts
17058
17474
  import { stdin as stdin2, stdout } from "process";
@@ -17126,7 +17542,7 @@ async function confirm(question, defaultYes) {
17126
17542
 
17127
17543
  // src/cli/commands/index.ts
17128
17544
  async function indexCommand(opts = {}) {
17129
- const root = resolve8(opts.dir ?? process.cwd());
17545
+ const root = resolve11(opts.dir ?? process.cwd());
17130
17546
  const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
17131
17547
  const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
17132
17548
  const preflightOk = await ollamaPreflight({
@@ -17376,11 +17792,11 @@ function pad2(s, width) {
17376
17792
 
17377
17793
  // src/cli/commands/replay.ts
17378
17794
  import { render as render3 } from "ink";
17379
- import React31 from "react";
17795
+ import React32 from "react";
17380
17796
 
17381
17797
  // src/cli/ui/ReplayApp.tsx
17382
- import { Box as Box26, Static as Static3, Text as Text24, useApp as useApp4, useInput as useInput2 } from "ink";
17383
- import React30, { useMemo as useMemo4, useState as useState14 } from "react";
17798
+ import { Box as Box27, Static as Static3, Text as Text25, useApp as useApp4, useInput as useInput2 } from "ink";
17799
+ import React31, { useMemo as useMemo4, useState as useState14 } from "react";
17384
17800
  function ReplayApp({ meta, pages }) {
17385
17801
  const { exit: exit2 } = useApp4();
17386
17802
  const maxIdx = Math.max(0, pages.length - 1);
@@ -17420,14 +17836,14 @@ function ReplayApp({ meta, pages }) {
17420
17836
  const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
17421
17837
  const currentPage = pages[idx];
17422
17838
  const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
17423
- return /* @__PURE__ */ React30.createElement(Box26, { flexDirection: "column" }, /* @__PURE__ */ React30.createElement(
17839
+ return /* @__PURE__ */ React31.createElement(Box27, { flexDirection: "column" }, /* @__PURE__ */ React31.createElement(
17424
17840
  StatsPanel,
17425
17841
  {
17426
17842
  summary,
17427
17843
  model: cumStats.models[0] ?? meta?.model ?? "?",
17428
17844
  prefixHash
17429
17845
  }
17430
- ), /* @__PURE__ */ React30.createElement(Box26, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React30.createElement(Box26, { justifyContent: "space-between" }, /* @__PURE__ */ React30.createElement(Text24, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React30.createElement(Text24, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React30.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React30.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React30.createElement(Text24, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React30.createElement(Box26, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React30.createElement(Text24, { dimColor: true }, /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "j"), "/", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "k"), "/", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React30.createElement(Text24, { bold: true }, "q"), " quit")));
17846
+ ), /* @__PURE__ */ React31.createElement(Box27, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React31.createElement(Box27, { justifyContent: "space-between" }, /* @__PURE__ */ React31.createElement(Text25, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React31.createElement(Text25, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React31.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React31.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React31.createElement(Text25, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React31.createElement(Box27, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React31.createElement(Text25, { dimColor: true }, /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "j"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "k"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "q"), " quit")));
17431
17847
  }
17432
17848
 
17433
17849
  // src/cli/commands/replay.ts
@@ -17439,7 +17855,7 @@ async function replayCommand(opts) {
17439
17855
  }
17440
17856
  const { parsed } = replayFromFile(opts.path);
17441
17857
  const pages = groupRecordsByTurn(parsed.records);
17442
- const { waitUntilExit } = render3(React31.createElement(ReplayApp, { meta: parsed.meta, pages }), {
17858
+ const { waitUntilExit } = render3(React32.createElement(ReplayApp, { meta: parsed.meta, pages }), {
17443
17859
  exitOnCtrlC: true,
17444
17860
  patchConsole: false
17445
17861
  });
@@ -17744,12 +18160,12 @@ function truncate3(s, max) {
17744
18160
 
17745
18161
  // src/cli/commands/setup.tsx
17746
18162
  import { render as render4 } from "ink";
17747
- import React33 from "react";
18163
+ import React34 from "react";
17748
18164
 
17749
18165
  // src/cli/ui/Wizard.tsx
17750
- import { Box as Box27, Text as Text25, useApp as useApp5, useInput as useInput3 } from "ink";
18166
+ import { Box as Box28, Text as Text26, useApp as useApp5, useInput as useInput3 } from "ink";
17751
18167
  import TextInput2 from "ink-text-input";
17752
- import React32, { useState as useState15 } from "react";
18168
+ import React33, { useState as useState15 } from "react";
17753
18169
 
17754
18170
  // src/cli/ui/presets.ts
17755
18171
  var PRESETS = {
@@ -17797,7 +18213,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17797
18213
  if (key.escape && step !== "saved" && onCancel) onCancel();
17798
18214
  });
17799
18215
  if (step === "apiKey") {
17800
- return /* @__PURE__ */ React32.createElement(
18216
+ return /* @__PURE__ */ React33.createElement(
17801
18217
  ApiKeyStep,
17802
18218
  {
17803
18219
  onSubmit: (key) => {
@@ -17811,7 +18227,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17811
18227
  );
17812
18228
  }
17813
18229
  if (step === "preset") {
17814
- return /* @__PURE__ */ React32.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React32.createElement(
18230
+ return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React33.createElement(
17815
18231
  SingleSelect,
17816
18232
  {
17817
18233
  items: presetItems(),
@@ -17821,10 +18237,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17821
18237
  setStep("mcp");
17822
18238
  }
17823
18239
  }
17824
- ), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] confirm \xB7 [Esc] cancel")));
18240
+ ), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] confirm \xB7 [Esc] cancel")));
17825
18241
  }
17826
18242
  if (step === "mcp") {
17827
- return /* @__PURE__ */ React32.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React32.createElement(
18243
+ return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React33.createElement(
17828
18244
  MultiSelect,
17829
18245
  {
17830
18246
  items: mcpItems(),
@@ -17849,7 +18265,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17849
18265
  }
17850
18266
  const currentName = pending[0];
17851
18267
  const entry = CATALOG_BY_NAME.get(currentName);
17852
- return /* @__PURE__ */ React32.createElement(
18268
+ return /* @__PURE__ */ React33.createElement(
17853
18269
  McpArgsStep,
17854
18270
  {
17855
18271
  entry,
@@ -17867,7 +18283,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17867
18283
  }
17868
18284
  if (step === "review") {
17869
18285
  const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
17870
- return /* @__PURE__ */ React32.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column" }, /* @__PURE__ */ React32.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React32.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React32.createElement(
18286
+ return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column" }, /* @__PURE__ */ React33.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React33.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React33.createElement(
17871
18287
  SummaryLine,
17872
18288
  {
17873
18289
  label: "MCP",
@@ -17875,8 +18291,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17875
18291
  }
17876
18292
  ), specs.map((spec, i) => (
17877
18293
  // biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
17878
- /* @__PURE__ */ React32.createElement(Box27, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "\xB7 ", spec))
17879
- )), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { color: "red" }, error)) : null, /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "[Enter] save \xB7 [Esc] cancel"))), /* @__PURE__ */ React32.createElement(
18294
+ /* @__PURE__ */ React33.createElement(Box28, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "\xB7 ", spec))
18295
+ )), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : null, /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[Enter] save \xB7 [Esc] cancel"))), /* @__PURE__ */ React33.createElement(
17880
18296
  ReviewConfirm,
17881
18297
  {
17882
18298
  onConfirm: () => {
@@ -17902,7 +18318,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
17902
18318
  }
17903
18319
  ));
17904
18320
  }
17905
- return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React32.createElement(Text25, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "[Enter] to exit")), /* @__PURE__ */ React32.createElement(ExitOnEnter, { onExit: exit2 }));
18321
+ return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[Enter] to exit")), /* @__PURE__ */ React33.createElement(ExitOnEnter, { onExit: exit2 }));
17906
18322
  }
17907
18323
  function ApiKeyStep({
17908
18324
  onSubmit,
@@ -17910,7 +18326,7 @@ function ApiKeyStep({
17910
18326
  onError
17911
18327
  }) {
17912
18328
  const [value, setValue] = useState15("");
17913
- return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React32.createElement(Text25, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React32.createElement(
18329
+ return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React33.createElement(
17914
18330
  TextInput2,
17915
18331
  {
17916
18332
  value,
@@ -17927,7 +18343,7 @@ function ApiKeyStep({
17927
18343
  mask: "\u2022",
17928
18344
  placeholder: "sk-..."
17929
18345
  }
17930
- )), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { color: "red" }, error)) : value ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "preview: ", redactKey(value))) : null);
18346
+ )), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : value ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "preview: ", redactKey(value))) : null);
17931
18347
  }
17932
18348
  function McpArgsStep({
17933
18349
  entry,
@@ -17936,7 +18352,7 @@ function McpArgsStep({
17936
18352
  onError
17937
18353
  }) {
17938
18354
  const [value, setValue] = useState15("");
17939
- return /* @__PURE__ */ React32.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column" }, /* @__PURE__ */ React32.createElement(Text25, null, entry.summary), entry.note ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, null, "Required parameter: "), /* @__PURE__ */ React32.createElement(Text25, { bold: true }, entry.userArgs)), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React32.createElement(
18355
+ return /* @__PURE__ */ React33.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column" }, /* @__PURE__ */ React33.createElement(Text26, null, entry.summary), entry.note ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Required parameter: "), /* @__PURE__ */ React33.createElement(Text26, { bold: true }, entry.userArgs)), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React33.createElement(
17940
18356
  TextInput2,
17941
18357
  {
17942
18358
  value,
@@ -17952,7 +18368,7 @@ function McpArgsStep({
17952
18368
  },
17953
18369
  placeholder: placeholderFor(entry)
17954
18370
  }
17955
- )), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { color: "red" }, error)) : null));
18371
+ )), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : null));
17956
18372
  }
17957
18373
  function ReviewConfirm({ onConfirm }) {
17958
18374
  useInput3((_i, key) => {
@@ -17972,10 +18388,10 @@ function StepFrame({
17972
18388
  total,
17973
18389
  children
17974
18390
  }) {
17975
- return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React32.createElement(Box27, null, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React32.createElement(Text25, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1, flexDirection: "column" }, children));
18391
+ return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Box28, null, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1, flexDirection: "column" }, children));
17976
18392
  }
17977
18393
  function SummaryLine({ label, value }) {
17978
- return /* @__PURE__ */ React32.createElement(Box27, null, /* @__PURE__ */ React32.createElement(Text25, null, label.padEnd(12)), /* @__PURE__ */ React32.createElement(Text25, { bold: true }, value));
18394
+ return /* @__PURE__ */ React33.createElement(Box28, null, /* @__PURE__ */ React33.createElement(Text26, null, label.padEnd(12)), /* @__PURE__ */ React33.createElement(Text26, { bold: true }, value));
17979
18395
  }
17980
18396
  function presetItems() {
17981
18397
  return ["fast", "smart", "max"].map((name) => ({
@@ -18031,7 +18447,7 @@ async function setupCommand(_opts = {}) {
18031
18447
  const existingKey = loadApiKey();
18032
18448
  const existing = readConfig();
18033
18449
  const { waitUntilExit, unmount } = render4(
18034
- /* @__PURE__ */ React33.createElement(
18450
+ /* @__PURE__ */ React34.createElement(
18035
18451
  Wizard,
18036
18452
  {
18037
18453
  existingApiKey: existingKey,
@@ -18079,13 +18495,13 @@ function planUpdate(input) {
18079
18495
  };
18080
18496
  }
18081
18497
  function defaultSpawn(argv) {
18082
- return new Promise((resolve9, reject) => {
18498
+ return new Promise((resolve12, reject) => {
18083
18499
  const child = spawn6(argv[0], argv.slice(1), {
18084
18500
  stdio: "inherit",
18085
18501
  shell: process.platform === "win32"
18086
18502
  });
18087
18503
  child.once("error", reject);
18088
- child.once("exit", (code) => resolve9(code ?? 1));
18504
+ child.once("exit", (code) => resolve12(code ?? 1));
18089
18505
  });
18090
18506
  }
18091
18507
  async function updateCommand(opts = {}) {