reasonix 0.7.12 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{chunk-5DZMZCCW.js → chunk-WRG56OKI.js} +166 -2
- package/dist/cli/chunk-WRG56OKI.js.map +1 -0
- package/dist/cli/index.js +919 -245
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-2OABSPAW.js → prompt-LJ44NWSU.js} +2 -2
- package/dist/index.d.ts +33 -13
- package/dist/index.js +169 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-5DZMZCCW.js.map +0 -1
- /package/dist/cli/{prompt-2OABSPAW.js.map → prompt-LJ44NWSU.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
memoryEnabled,
|
|
11
11
|
readProjectMemory,
|
|
12
12
|
sanitizeMemoryName
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WRG56OKI.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -3174,7 +3174,7 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3174
3174
|
const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3175
3175
|
const rootAbs = resolve(root);
|
|
3176
3176
|
const out = [];
|
|
3177
|
-
const
|
|
3177
|
+
const walk3 = (dirAbs, dirRel) => {
|
|
3178
3178
|
if (out.length >= maxResults) return;
|
|
3179
3179
|
let entries;
|
|
3180
3180
|
try {
|
|
@@ -3188,7 +3188,7 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3188
3188
|
const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
|
|
3189
3189
|
if (ent.isDirectory()) {
|
|
3190
3190
|
if (ent.name.startsWith(".") || ignore.has(ent.name)) continue;
|
|
3191
|
-
|
|
3191
|
+
walk3(join5(dirAbs, ent.name), relPath);
|
|
3192
3192
|
} else if (ent.isFile()) {
|
|
3193
3193
|
let mtimeMs = 0;
|
|
3194
3194
|
try {
|
|
@@ -3199,7 +3199,7 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3199
3199
|
}
|
|
3200
3200
|
}
|
|
3201
3201
|
};
|
|
3202
|
-
|
|
3202
|
+
walk3(rootAbs, "");
|
|
3203
3203
|
return out;
|
|
3204
3204
|
}
|
|
3205
3205
|
var AT_PICKER_PREFIX = /(?:^|\s)@([a-zA-Z0-9_./\\-]*)$/;
|
|
@@ -3338,6 +3338,108 @@ var defaultFs = {
|
|
|
3338
3338
|
},
|
|
3339
3339
|
read: (p) => readFileSync5(p, "utf8")
|
|
3340
3340
|
};
|
|
3341
|
+
var AT_URL_PATTERN = /(?<=^|\s)@(https?:\/\/\S+)/g;
|
|
3342
|
+
var DEFAULT_AT_URL_MAX_CHARS = 32e3;
|
|
3343
|
+
async function expandAtUrls(text, opts = {}) {
|
|
3344
|
+
const maxChars = opts.maxChars ?? DEFAULT_AT_URL_MAX_CHARS;
|
|
3345
|
+
const fetcher = opts.fetcher;
|
|
3346
|
+
if (!fetcher) {
|
|
3347
|
+
throw new Error("expandAtUrls: fetcher option is required (wire src/tools/web.ts:webFetch)");
|
|
3348
|
+
}
|
|
3349
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3350
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
3351
|
+
const order = [];
|
|
3352
|
+
for (const match of text.matchAll(AT_URL_PATTERN)) {
|
|
3353
|
+
const rawUrl = match[1] ?? "";
|
|
3354
|
+
const url = stripUrlTail(rawUrl);
|
|
3355
|
+
if (!url) continue;
|
|
3356
|
+
if (seen.has(url)) continue;
|
|
3357
|
+
const cached2 = opts.cache?.get(url);
|
|
3358
|
+
if (cached2) {
|
|
3359
|
+
seen.set(url, cached2);
|
|
3360
|
+
if (cached2.body) bodies.set(url, cached2.body);
|
|
3361
|
+
order.push(url);
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
let expansion;
|
|
3365
|
+
let body = "";
|
|
3366
|
+
try {
|
|
3367
|
+
const page = await fetcher(url, {
|
|
3368
|
+
maxChars,
|
|
3369
|
+
timeoutMs: opts.timeoutMs,
|
|
3370
|
+
signal: opts.signal
|
|
3371
|
+
});
|
|
3372
|
+
body = page.text;
|
|
3373
|
+
expansion = {
|
|
3374
|
+
token: `@${url}`,
|
|
3375
|
+
url,
|
|
3376
|
+
ok: true,
|
|
3377
|
+
title: page.title,
|
|
3378
|
+
chars: body.length,
|
|
3379
|
+
truncated: page.truncated
|
|
3380
|
+
};
|
|
3381
|
+
} catch (err) {
|
|
3382
|
+
const message = err.message ?? String(err);
|
|
3383
|
+
let skip = "fetch-error";
|
|
3384
|
+
if (/aborted|timeout/i.test(message)) skip = "timeout";
|
|
3385
|
+
else if (/40\d|forbidden|access denied|captcha/i.test(message)) skip = "blocked";
|
|
3386
|
+
expansion = {
|
|
3387
|
+
token: `@${url}`,
|
|
3388
|
+
url,
|
|
3389
|
+
ok: false,
|
|
3390
|
+
skip,
|
|
3391
|
+
error: message
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
seen.set(url, expansion);
|
|
3395
|
+
if (body) bodies.set(url, body);
|
|
3396
|
+
if (opts.cache) opts.cache.set(url, { ...expansion, body });
|
|
3397
|
+
order.push(url);
|
|
3398
|
+
}
|
|
3399
|
+
if (seen.size === 0) return { text, expansions: [] };
|
|
3400
|
+
const expansions = order.map((u) => seen.get(u)).filter(Boolean);
|
|
3401
|
+
const blocks = [];
|
|
3402
|
+
for (const ex of expansions) {
|
|
3403
|
+
if (ex.ok) {
|
|
3404
|
+
const titleAttr = ex.title ? ` title="${escapeAttr(ex.title)}"` : "";
|
|
3405
|
+
const truncTag = ex.truncated ? ' truncated="true"' : "";
|
|
3406
|
+
const body = bodies.get(ex.url) ?? "";
|
|
3407
|
+
blocks.push(`<url href="${ex.url}"${titleAttr}${truncTag}>
|
|
3408
|
+
${body}
|
|
3409
|
+
</url>`);
|
|
3410
|
+
} else {
|
|
3411
|
+
const reasonAttr = ex.skip ?? "fetch-error";
|
|
3412
|
+
blocks.push(`<url href="${ex.url}" skipped="${reasonAttr}" />`);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
const augmented = `${text}
|
|
3416
|
+
|
|
3417
|
+
[Referenced URLs]
|
|
3418
|
+
${blocks.join("\n\n")}`;
|
|
3419
|
+
return { text: augmented, expansions };
|
|
3420
|
+
}
|
|
3421
|
+
function stripUrlTail(raw) {
|
|
3422
|
+
let s = raw;
|
|
3423
|
+
while (s.length > 0) {
|
|
3424
|
+
const last = s[s.length - 1];
|
|
3425
|
+
if (".,;:!?".includes(last)) {
|
|
3426
|
+
s = s.slice(0, -1);
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
if (")]}>".includes(last)) {
|
|
3430
|
+
const open = { ")": "(", "]": "[", "}": "{", ">": "<" }[last];
|
|
3431
|
+
if (!s.includes(open)) {
|
|
3432
|
+
s = s.slice(0, -1);
|
|
3433
|
+
continue;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
break;
|
|
3437
|
+
}
|
|
3438
|
+
return s;
|
|
3439
|
+
}
|
|
3440
|
+
function escapeAttr(s) {
|
|
3441
|
+
return s.replace(/"/g, """).replace(/[\r\n]+/g, " ").trim();
|
|
3442
|
+
}
|
|
3341
3443
|
|
|
3342
3444
|
// src/tools/filesystem.ts
|
|
3343
3445
|
import { promises as fs } from "fs";
|
|
@@ -3564,7 +3666,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3564
3666
|
let totalBytes = 0;
|
|
3565
3667
|
let truncated = false;
|
|
3566
3668
|
const PER_DIR_CHILD_CAP = 50;
|
|
3567
|
-
const
|
|
3669
|
+
const walk3 = async (dir, depth) => {
|
|
3568
3670
|
if (truncated) return;
|
|
3569
3671
|
if (depth > maxDepth) return;
|
|
3570
3672
|
let entries;
|
|
@@ -3604,11 +3706,11 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3604
3706
|
lines.push(line);
|
|
3605
3707
|
emitted++;
|
|
3606
3708
|
if (e.isDirectory() && !skip) {
|
|
3607
|
-
await
|
|
3709
|
+
await walk3(pathMod.join(dir, e.name), depth + 1);
|
|
3608
3710
|
}
|
|
3609
3711
|
}
|
|
3610
3712
|
};
|
|
3611
|
-
await
|
|
3713
|
+
await walk3(startAbs, 0);
|
|
3612
3714
|
return lines.join("\n") || "(empty tree)";
|
|
3613
3715
|
}
|
|
3614
3716
|
});
|
|
@@ -3638,7 +3740,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3638
3740
|
}
|
|
3639
3741
|
const matches = [];
|
|
3640
3742
|
let totalBytes = 0;
|
|
3641
|
-
const
|
|
3743
|
+
const walk3 = async (dir) => {
|
|
3642
3744
|
let entries;
|
|
3643
3745
|
try {
|
|
3644
3746
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -3658,10 +3760,10 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3658
3760
|
matches.push(rel);
|
|
3659
3761
|
totalBytes += rel.length + 1;
|
|
3660
3762
|
}
|
|
3661
|
-
if (e.isDirectory()) await
|
|
3763
|
+
if (e.isDirectory()) await walk3(full);
|
|
3662
3764
|
}
|
|
3663
3765
|
};
|
|
3664
|
-
await
|
|
3766
|
+
await walk3(startAbs);
|
|
3665
3767
|
return matches.length === 0 ? "(no matches)" : matches.join("\n");
|
|
3666
3768
|
}
|
|
3667
3769
|
});
|
|
@@ -3711,7 +3813,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3711
3813
|
let totalBytes = 0;
|
|
3712
3814
|
let scanned = 0;
|
|
3713
3815
|
let truncated = false;
|
|
3714
|
-
const
|
|
3816
|
+
const walk3 = async (dir) => {
|
|
3715
3817
|
if (truncated) return;
|
|
3716
3818
|
let entries;
|
|
3717
3819
|
try {
|
|
@@ -3723,7 +3825,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3723
3825
|
if (truncated) return;
|
|
3724
3826
|
if (e.isDirectory()) {
|
|
3725
3827
|
if (!includeDeps && SKIP_DIR_NAMES.has(e.name)) continue;
|
|
3726
|
-
await
|
|
3828
|
+
await walk3(pathMod.join(dir, e.name));
|
|
3727
3829
|
continue;
|
|
3728
3830
|
}
|
|
3729
3831
|
if (!e.isFile()) continue;
|
|
@@ -3766,7 +3868,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3766
3868
|
scanned++;
|
|
3767
3869
|
}
|
|
3768
3870
|
};
|
|
3769
|
-
await
|
|
3871
|
+
await walk3(startAbs);
|
|
3770
3872
|
if (matches.length === 0) {
|
|
3771
3873
|
return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
|
|
3772
3874
|
}
|
|
@@ -4531,9 +4633,9 @@ async function spawnSubagent(opts) {
|
|
|
4531
4633
|
usage
|
|
4532
4634
|
};
|
|
4533
4635
|
}
|
|
4534
|
-
function aggregateChildUsage(
|
|
4636
|
+
function aggregateChildUsage(loop2) {
|
|
4535
4637
|
const agg = new Usage();
|
|
4536
|
-
for (const t of
|
|
4638
|
+
for (const t of loop2.stats.turns) {
|
|
4537
4639
|
agg.promptTokens += t.usage.promptTokens;
|
|
4538
4640
|
agg.completionTokens += t.usage.completionTokens;
|
|
4539
4641
|
agg.totalTokens += t.usage.totalTokens;
|
|
@@ -5204,7 +5306,7 @@ function registerShellTools(registry, opts) {
|
|
|
5204
5306
|
const snapshot2 = opts.extraAllowed ?? [];
|
|
5205
5307
|
return () => snapshot2;
|
|
5206
5308
|
})();
|
|
5207
|
-
const
|
|
5309
|
+
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
5208
5310
|
registry.register({
|
|
5209
5311
|
name: "run_command",
|
|
5210
5312
|
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 ONE process per call, NO shell expansion. `&&`, `||`, `|`, `;`, `>`, `<`, `2>&1` are all rejected up-front \u2014 split into separate calls and combine results in reasoning. Example: instead of `grep foo *.ts | wc -l`, use `grep -c foo *.ts`; instead of `cd sub && npm test`, use `npm test --prefix sub` (or whatever --cwd flag the binary accepts).\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project. If a tool needs a subdirectory, pass it via the tool's own flag (`npm --prefix`, `cargo -C`, `git -C`, `pytest tests/\u2026`), NOT via a preceding `cd`.\n\u2022 Avoid commands with unbounded output (`netstat -ano`, `find /`, etc.) \u2014 they waste tokens. Filter at source: `netstat -ano -p TCP`, `find src -name '*.ts'`, `grep -c`, `wc -l`.\n\nCommon read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
@@ -5213,7 +5315,7 @@ function registerShellTools(registry, opts) {
|
|
|
5213
5315
|
// during planning. Anything that would otherwise trigger a
|
|
5214
5316
|
// confirmation prompt is treated as "not read-only" and bounced.
|
|
5215
5317
|
readOnlyCheck: (args) => {
|
|
5216
|
-
if (
|
|
5318
|
+
if (isAllowAll()) return true;
|
|
5217
5319
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
5218
5320
|
if (!cmd) return false;
|
|
5219
5321
|
return isAllowed(cmd, getExtraAllowed());
|
|
@@ -5235,7 +5337,7 @@ function registerShellTools(registry, opts) {
|
|
|
5235
5337
|
fn: async (args, ctx) => {
|
|
5236
5338
|
const cmd = args.command.trim();
|
|
5237
5339
|
if (!cmd) throw new Error("run_command: empty command");
|
|
5238
|
-
if (!
|
|
5340
|
+
if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
|
|
5239
5341
|
throw new NeedsConfirmationError(cmd);
|
|
5240
5342
|
}
|
|
5241
5343
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
@@ -5268,7 +5370,7 @@ function registerShellTools(registry, opts) {
|
|
|
5268
5370
|
fn: async (args, ctx) => {
|
|
5269
5371
|
const cmd = args.command.trim();
|
|
5270
5372
|
if (!cmd) throw new Error("run_background: empty command");
|
|
5271
|
-
if (!
|
|
5373
|
+
if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
|
|
5272
5374
|
throw new NeedsConfirmationError(cmd);
|
|
5273
5375
|
}
|
|
5274
5376
|
const result = await jobs2.start(cmd, {
|
|
@@ -7111,12 +7213,12 @@ function formatLogSize(path = defaultUsageLogPath()) {
|
|
|
7111
7213
|
}
|
|
7112
7214
|
|
|
7113
7215
|
// src/cli/commands/chat.tsx
|
|
7114
|
-
import { existsSync as
|
|
7216
|
+
import { existsSync as existsSync13, statSync as statSync7 } from "fs";
|
|
7115
7217
|
import { render } from "ink";
|
|
7116
7218
|
import React26, { useState as useState12 } from "react";
|
|
7117
7219
|
|
|
7118
7220
|
// src/cli/ui/App.tsx
|
|
7119
|
-
import { Box as Box21, Static, useApp, useStdout as useStdout8 } from "ink";
|
|
7221
|
+
import { Box as Box21, Static, Text as Text19, useApp, useStdout as useStdout8 } from "ink";
|
|
7120
7222
|
import React23, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
|
|
7121
7223
|
|
|
7122
7224
|
// src/code/pending-edits.ts
|
|
@@ -7991,7 +8093,8 @@ function formatAllBlockDiffs(blocks, opts = {}) {
|
|
|
7991
8093
|
const added = countLines2(b.replace);
|
|
7992
8094
|
const tag = b.search === "" ? "NEW " : " ";
|
|
7993
8095
|
if (i > 0) out.push("");
|
|
7994
|
-
|
|
8096
|
+
const label = opts.numbered ? `[${i + 1}] ` : "";
|
|
8097
|
+
out.push(` ${label}${tag}${b.path} (-${removed} +${added} lines)`);
|
|
7995
8098
|
out.push(...formatEditBlockDiff(b, opts));
|
|
7996
8099
|
}
|
|
7997
8100
|
return out;
|
|
@@ -9466,10 +9569,9 @@ function ModeStatusBar({
|
|
|
9466
9569
|
if (planMode) {
|
|
9467
9570
|
return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label: "PLAN MODE", bg: "red", flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, " writes gated \xB7 /plan off to leave"), jobsTag);
|
|
9468
9571
|
}
|
|
9469
|
-
const
|
|
9470
|
-
const
|
|
9471
|
-
const
|
|
9472
|
-
const mid = isAuto ? "edits land now \xB7 u to undo" : pendingCount > 0 ? `${pendingCount} queued \xB7 y apply \xB7 n discard` : "edits queued \xB7 y apply \xB7 n discard";
|
|
9572
|
+
const label = editMode === "yolo" ? "YOLO" : editMode === "auto" ? "AUTO" : "REVIEW";
|
|
9573
|
+
const bg = editMode === "yolo" ? "red" : editMode === "auto" ? "magenta" : "cyan";
|
|
9574
|
+
const mid = editMode === "yolo" ? "edits + shell auto \xB7 /undo to roll back" : editMode === "auto" ? "edits land now \xB7 u to undo" : pendingCount > 0 ? `${pendingCount} queued \xB7 y apply \xB7 n discard` : "edits queued \xB7 y apply \xB7 n discard";
|
|
9473
9575
|
return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label, bg, flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 Shift+Tab to flip`), jobsTag);
|
|
9474
9576
|
}
|
|
9475
9577
|
function ModeBarFrame({ children }) {
|
|
@@ -10866,10 +10968,50 @@ function formatEditResults(results) {
|
|
|
10866
10968
|
return [header2, ...lines].join("\n");
|
|
10867
10969
|
}
|
|
10868
10970
|
function formatPendingPreview(blocks) {
|
|
10869
|
-
const
|
|
10870
|
-
const
|
|
10971
|
+
const partial = blocks.length > 1 ? " \xB7 /apply N or 1,3-4 for partial" : "";
|
|
10972
|
+
const header2 = `\u25B8 ${blocks.length} pending edit block(s) \u2014 /apply (or y) to commit \xB7 /discard (or n) to drop${partial}`;
|
|
10973
|
+
const diffLines = formatAllBlockDiffs(blocks, { numbered: blocks.length > 1 });
|
|
10871
10974
|
return [header2, ...diffLines].join("\n");
|
|
10872
10975
|
}
|
|
10976
|
+
function parseEditIndices(raw, max) {
|
|
10977
|
+
const trimmed = raw.trim();
|
|
10978
|
+
if (!trimmed) return { ok: [] };
|
|
10979
|
+
if (max <= 0) return { error: "no pending edits to address" };
|
|
10980
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10981
|
+
const tokens = trimmed.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
10982
|
+
if (tokens.length === 0) return { ok: [] };
|
|
10983
|
+
for (const tok of tokens) {
|
|
10984
|
+
const range = tok.match(/^(\d+)-(\d+)$/);
|
|
10985
|
+
if (range) {
|
|
10986
|
+
const a = Number.parseInt(range[1] ?? "", 10);
|
|
10987
|
+
const b = Number.parseInt(range[2] ?? "", 10);
|
|
10988
|
+
if (!Number.isFinite(a) || !Number.isFinite(b) || a < 1 || b < 1) {
|
|
10989
|
+
return { error: `invalid range: "${tok}"` };
|
|
10990
|
+
}
|
|
10991
|
+
const lo = Math.min(a, b);
|
|
10992
|
+
const hi = Math.max(a, b);
|
|
10993
|
+
if (hi > max) return { error: `index ${hi} out of range (max ${max})` };
|
|
10994
|
+
for (let i = lo; i <= hi; i++) seen.add(i);
|
|
10995
|
+
continue;
|
|
10996
|
+
}
|
|
10997
|
+
if (!/^\d+$/.test(tok)) return { error: `invalid index: "${tok}"` };
|
|
10998
|
+
const n = Number.parseInt(tok, 10);
|
|
10999
|
+
if (!Number.isFinite(n) || n < 1) return { error: `invalid index: "${tok}"` };
|
|
11000
|
+
if (n > max) return { error: `index ${n} out of range (max ${max})` };
|
|
11001
|
+
seen.add(n);
|
|
11002
|
+
}
|
|
11003
|
+
return { ok: [...seen].sort((a, b) => a - b) };
|
|
11004
|
+
}
|
|
11005
|
+
function partitionEdits(edits, indices1Based) {
|
|
11006
|
+
const picked = new Set(indices1Based);
|
|
11007
|
+
const selected = [];
|
|
11008
|
+
const remaining = [];
|
|
11009
|
+
for (let i = 0; i < edits.length; i++) {
|
|
11010
|
+
if (picked.has(i + 1)) selected.push(edits[i]);
|
|
11011
|
+
else remaining.push(edits[i]);
|
|
11012
|
+
}
|
|
11013
|
+
return { selected, remaining };
|
|
11014
|
+
}
|
|
10873
11015
|
function formatUndoRows(results) {
|
|
10874
11016
|
return results.map((r) => {
|
|
10875
11017
|
const mark = r.status === "applied" ? "\u2713" : "\u2717";
|
|
@@ -10885,6 +11027,130 @@ function describeRepair(repair) {
|
|
|
10885
11027
|
return parts.length ? `[repair] ${parts.join(", ")}` : "";
|
|
10886
11028
|
}
|
|
10887
11029
|
|
|
11030
|
+
// src/cli/ui/hash-memory.ts
|
|
11031
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
11032
|
+
import { homedir as homedir6 } from "os";
|
|
11033
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
11034
|
+
var PROJECT_HEADER = `# Reasonix project memory
|
|
11035
|
+
|
|
11036
|
+
Notes the user pinned via the \`#\` prompt prefix. The whole file is
|
|
11037
|
+
loaded into the immutable system prefix every session \u2014 keep it terse.
|
|
11038
|
+
|
|
11039
|
+
`;
|
|
11040
|
+
var GLOBAL_HEADER = `# Reasonix global memory
|
|
11041
|
+
|
|
11042
|
+
Cross-project notes the user pinned via the \`#g\` prompt prefix. Loaded
|
|
11043
|
+
into every Reasonix session's prefix regardless of working directory.
|
|
11044
|
+
Private to this machine \u2014 not committed anywhere.
|
|
11045
|
+
|
|
11046
|
+
`;
|
|
11047
|
+
function detectHashMemory(text) {
|
|
11048
|
+
if (text.startsWith("\\#")) {
|
|
11049
|
+
return { kind: "escape", text: text.slice(1) };
|
|
11050
|
+
}
|
|
11051
|
+
if (!text.startsWith("#")) return null;
|
|
11052
|
+
if (text.startsWith("##")) return null;
|
|
11053
|
+
if (/^#g\s*$/.test(text)) return null;
|
|
11054
|
+
const globalMatch = /^#g\s+(.+)$/s.exec(text);
|
|
11055
|
+
if (globalMatch) {
|
|
11056
|
+
const body2 = globalMatch[1].trim();
|
|
11057
|
+
if (!body2) return null;
|
|
11058
|
+
return { kind: "memory-global", note: body2 };
|
|
11059
|
+
}
|
|
11060
|
+
const body = text.slice(1).trim();
|
|
11061
|
+
if (!body) return null;
|
|
11062
|
+
return { kind: "memory", note: body };
|
|
11063
|
+
}
|
|
11064
|
+
function appendProjectMemory(rootDir, note) {
|
|
11065
|
+
return appendBulletToFile(join12(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
|
|
11066
|
+
}
|
|
11067
|
+
var GLOBAL_MEMORY_DIR = ".reasonix";
|
|
11068
|
+
var GLOBAL_MEMORY_FILE = "REASONIX.md";
|
|
11069
|
+
function globalMemoryPath(homeDir = homedir6()) {
|
|
11070
|
+
return join12(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
|
|
11071
|
+
}
|
|
11072
|
+
function appendGlobalMemory(note, homeDir) {
|
|
11073
|
+
return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
|
|
11074
|
+
}
|
|
11075
|
+
function appendBulletToFile(path, note, newFileHeader) {
|
|
11076
|
+
const trimmed = note.trim();
|
|
11077
|
+
if (!trimmed) throw new Error("note body cannot be empty");
|
|
11078
|
+
const bullet = `- ${trimmed}
|
|
11079
|
+
`;
|
|
11080
|
+
if (!existsSync11(path)) {
|
|
11081
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
11082
|
+
writeFileSync7(path, `${newFileHeader}${bullet}`, "utf8");
|
|
11083
|
+
return { path, created: true };
|
|
11084
|
+
}
|
|
11085
|
+
let prefix = "";
|
|
11086
|
+
try {
|
|
11087
|
+
const existing = readFileSync14(path, "utf8");
|
|
11088
|
+
if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
|
|
11089
|
+
} catch {
|
|
11090
|
+
}
|
|
11091
|
+
appendFileSync3(path, `${prefix}${bullet}`, "utf8");
|
|
11092
|
+
return { path, created: false };
|
|
11093
|
+
}
|
|
11094
|
+
|
|
11095
|
+
// src/cli/ui/loop.ts
|
|
11096
|
+
var MIN_LOOP_INTERVAL_MS = 5e3;
|
|
11097
|
+
var MAX_LOOP_INTERVAL_MS = 6 * 60 * 6e4;
|
|
11098
|
+
function parseLoopInterval(raw) {
|
|
11099
|
+
const s = raw.trim().toLowerCase();
|
|
11100
|
+
if (!s) return null;
|
|
11101
|
+
const m = /^([0-9]+(?:\.[0-9]+)?)(s|sec|secs|m|min|mins|h|hr|hrs)?$/.exec(s);
|
|
11102
|
+
if (!m) return null;
|
|
11103
|
+
const n = Number.parseFloat(m[1] ?? "");
|
|
11104
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
11105
|
+
const unit = m[2] ?? "s";
|
|
11106
|
+
let ms;
|
|
11107
|
+
if (unit === "s" || unit === "sec" || unit === "secs") ms = Math.round(n * 1e3);
|
|
11108
|
+
else if (unit === "m" || unit === "min" || unit === "mins") ms = Math.round(n * 6e4);
|
|
11109
|
+
else if (unit === "h" || unit === "hr" || unit === "hrs") ms = Math.round(n * 60 * 6e4);
|
|
11110
|
+
else return null;
|
|
11111
|
+
if (ms < MIN_LOOP_INTERVAL_MS) return null;
|
|
11112
|
+
if (ms > MAX_LOOP_INTERVAL_MS) return null;
|
|
11113
|
+
return { ms };
|
|
11114
|
+
}
|
|
11115
|
+
function parseLoopCommand(args) {
|
|
11116
|
+
if (args.length === 0) return { kind: "status" };
|
|
11117
|
+
const first = (args[0] ?? "").toLowerCase();
|
|
11118
|
+
if (args.length === 1 && (first === "stop" || first === "off" || first === "cancel")) {
|
|
11119
|
+
return { kind: "stop" };
|
|
11120
|
+
}
|
|
11121
|
+
const interval = parseLoopInterval(args[0] ?? "");
|
|
11122
|
+
if (!interval) {
|
|
11123
|
+
return {
|
|
11124
|
+
kind: "error",
|
|
11125
|
+
message: "usage: /loop <interval> <prompt> (interval = 5s..6h, e.g. 30s, 5m, 1h)\n /loop stop (cancel an active loop)\n /loop (show active-loop status)"
|
|
11126
|
+
};
|
|
11127
|
+
}
|
|
11128
|
+
const prompt = args.slice(1).join(" ").trim();
|
|
11129
|
+
if (!prompt) {
|
|
11130
|
+
return {
|
|
11131
|
+
kind: "error",
|
|
11132
|
+
message: `usage: /loop ${args[0]} <prompt> \u2014 interval is fine but the prompt is missing.`
|
|
11133
|
+
};
|
|
11134
|
+
}
|
|
11135
|
+
return { kind: "start", intervalMs: interval.ms, prompt };
|
|
11136
|
+
}
|
|
11137
|
+
function formatLoopStatus(prompt, nextFireMs, iter) {
|
|
11138
|
+
const preview = prompt.length > 36 ? `${prompt.slice(0, 33)}\u2026` : prompt;
|
|
11139
|
+
const when = nextFireMs <= 0 ? "firing now" : `next in ${formatDuration2(nextFireMs)}`;
|
|
11140
|
+
return `loop: \`${preview}\` \xB7 ${when} \xB7 iter ${iter}`;
|
|
11141
|
+
}
|
|
11142
|
+
function formatDuration2(ms) {
|
|
11143
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
11144
|
+
const totalSec = Math.round(ms / 1e3);
|
|
11145
|
+
if (totalSec < 60) return `${totalSec}s`;
|
|
11146
|
+
const m = Math.floor(totalSec / 60);
|
|
11147
|
+
const s = totalSec % 60;
|
|
11148
|
+
if (m < 60) return s === 0 ? `${m}m` : `${m}m${s}s`;
|
|
11149
|
+
const h = Math.floor(m / 60);
|
|
11150
|
+
const mm = m % 60;
|
|
11151
|
+
return mm === 0 ? `${h}h` : `${h}h${mm}m`;
|
|
11152
|
+
}
|
|
11153
|
+
|
|
10888
11154
|
// src/cli/ui/mcp-browse.ts
|
|
10889
11155
|
function formatResourceList(servers) {
|
|
10890
11156
|
const lines = [];
|
|
@@ -11190,10 +11456,30 @@ var SLASH_COMMANDS = [
|
|
|
11190
11456
|
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
11191
11457
|
{ cmd: "clear", summary: "clear visible scrollback only (log/context kept)" },
|
|
11192
11458
|
{ cmd: "new", summary: "start a fresh conversation (clear context + scrollback)" },
|
|
11459
|
+
{
|
|
11460
|
+
cmd: "loop",
|
|
11461
|
+
argsHint: "<5s..6h> <prompt> \xB7 stop \xB7 (no args = status)",
|
|
11462
|
+
summary: "auto-resubmit <prompt> every <interval> until you type something / Esc / /loop stop"
|
|
11463
|
+
},
|
|
11193
11464
|
{ cmd: "exit", summary: "quit the TUI" },
|
|
11194
11465
|
// Code-mode only
|
|
11195
|
-
{
|
|
11196
|
-
|
|
11466
|
+
{
|
|
11467
|
+
cmd: "apply",
|
|
11468
|
+
argsHint: "[N|N,M|N-M]",
|
|
11469
|
+
summary: "commit pending edit blocks to disk (no arg \u2192 all; `1`, `1,3`, or `1-4` \u2192 that subset, rest stay pending)",
|
|
11470
|
+
contextual: "code"
|
|
11471
|
+
},
|
|
11472
|
+
{
|
|
11473
|
+
cmd: "discard",
|
|
11474
|
+
argsHint: "[N|N,M|N-M]",
|
|
11475
|
+
summary: "drop pending edit blocks without writing (no arg \u2192 all; indices \u2192 that subset)",
|
|
11476
|
+
contextual: "code"
|
|
11477
|
+
},
|
|
11478
|
+
{
|
|
11479
|
+
cmd: "walk",
|
|
11480
|
+
summary: "step through pending edits one block at a time (git-add-p style: y/n per block, a apply rest, A flip AUTO)",
|
|
11481
|
+
contextual: "code"
|
|
11482
|
+
},
|
|
11197
11483
|
{ cmd: "undo", summary: "roll back the last applied edit batch", contextual: "code" },
|
|
11198
11484
|
{
|
|
11199
11485
|
cmd: "history",
|
|
@@ -11226,10 +11512,10 @@ var SLASH_COMMANDS = [
|
|
|
11226
11512
|
},
|
|
11227
11513
|
{
|
|
11228
11514
|
cmd: "mode",
|
|
11229
|
-
argsHint: "[review|auto]",
|
|
11230
|
-
summary: "edit-gate: review (queue
|
|
11515
|
+
argsHint: "[review|auto|yolo]",
|
|
11516
|
+
summary: "edit-gate: review (queue) \xB7 auto (apply+undo) \xB7 yolo (apply+auto-shell). Shift+Tab cycles.",
|
|
11231
11517
|
contextual: "code",
|
|
11232
|
-
argCompleter: ["review", "auto"]
|
|
11518
|
+
argCompleter: ["review", "auto", "yolo"]
|
|
11233
11519
|
},
|
|
11234
11520
|
{ cmd: "jobs", summary: "list background jobs started by run_background", contextual: "code" },
|
|
11235
11521
|
{
|
|
@@ -11282,7 +11568,7 @@ function parseSlash(text) {
|
|
|
11282
11568
|
}
|
|
11283
11569
|
|
|
11284
11570
|
// src/cli/commands/stats.ts
|
|
11285
|
-
import { existsSync as
|
|
11571
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
11286
11572
|
function statsCommand(opts) {
|
|
11287
11573
|
if (opts.transcript) {
|
|
11288
11574
|
transcriptSummary(opts.transcript);
|
|
@@ -11291,11 +11577,11 @@ function statsCommand(opts) {
|
|
|
11291
11577
|
dashboard(opts);
|
|
11292
11578
|
}
|
|
11293
11579
|
function transcriptSummary(path) {
|
|
11294
|
-
if (!
|
|
11580
|
+
if (!existsSync12(path)) {
|
|
11295
11581
|
console.error(`no such transcript: ${path}`);
|
|
11296
11582
|
process.exit(1);
|
|
11297
11583
|
}
|
|
11298
|
-
const lines =
|
|
11584
|
+
const lines = readFileSync15(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
11299
11585
|
let assistantTurns = 0;
|
|
11300
11586
|
let toolCalls = 0;
|
|
11301
11587
|
let lastTurn = 0;
|
|
@@ -11410,7 +11696,7 @@ function pad(s, width, align = "left") {
|
|
|
11410
11696
|
}
|
|
11411
11697
|
|
|
11412
11698
|
// src/cli/ui/slash/handlers/admin.ts
|
|
11413
|
-
var hooks = (args,
|
|
11699
|
+
var hooks = (args, loop2, ctx) => {
|
|
11414
11700
|
const sub = (args[0] ?? "").toLowerCase();
|
|
11415
11701
|
if (sub === "reload") {
|
|
11416
11702
|
if (!ctx.reloadHooks) {
|
|
@@ -11426,7 +11712,7 @@ var hooks = (args, loop, ctx) => {
|
|
|
11426
11712
|
info: "usage: /hooks list active hooks\n /hooks reload re-read settings.json files"
|
|
11427
11713
|
};
|
|
11428
11714
|
}
|
|
11429
|
-
const all =
|
|
11715
|
+
const all = loop2.hooks;
|
|
11430
11716
|
const projPath = ctx.codeRoot ? projectSettingsPath(ctx.codeRoot) : void 0;
|
|
11431
11717
|
const globPath = globalSettingsPath();
|
|
11432
11718
|
if (all.length === 0) {
|
|
@@ -11528,8 +11814,8 @@ var clear = () => ({
|
|
|
11528
11814
|
clear: true,
|
|
11529
11815
|
info: "\u25B8 terminal cleared (viewport + scrollback). Context (message log) is intact \u2014 next turn still sees everything. Use /new to start fresh, or /forget to delete the session entirely."
|
|
11530
11816
|
});
|
|
11531
|
-
var resetLog = (_args,
|
|
11532
|
-
const { dropped } =
|
|
11817
|
+
var resetLog = (_args, loop2) => {
|
|
11818
|
+
const { dropped } = loop2.clearLog();
|
|
11533
11819
|
return {
|
|
11534
11820
|
clear: true,
|
|
11535
11821
|
info: `\u25B8 new conversation \u2014 dropped ${dropped} message(s) from context. Same session, fresh slate.`
|
|
@@ -11557,8 +11843,14 @@ var keys = () => ({
|
|
|
11557
11843
|
" /<name> slash command; Tab/Enter picks from the suggestion list",
|
|
11558
11844
|
" @<path> inline a file under [Referenced files] (code mode).",
|
|
11559
11845
|
" Trailing `@\u2026` opens a file picker; \u2191/\u2193 navigate, Tab/Enter pick.",
|
|
11846
|
+
" @https://... fetch the URL, strip HTML, inline under [Referenced URLs].",
|
|
11847
|
+
" Cached per session \u2014 same URL twice fetches once.",
|
|
11560
11848
|
" !<cmd> run <cmd> as shell in the sandbox root; output goes into context",
|
|
11561
11849
|
" so the model sees it next turn. No allowlist gate.",
|
|
11850
|
+
" #<note> append <note> to <project>/REASONIX.md (committable, team-shared).",
|
|
11851
|
+
" #g <note> append <note> to ~/.reasonix/REASONIX.md (global, never committed).",
|
|
11852
|
+
" Both pin into the immutable prefix every future session.",
|
|
11853
|
+
" Use `\\#literal` if you actually want a `#` heading sent to the model.",
|
|
11562
11854
|
"",
|
|
11563
11855
|
"Pickers (slash + @-mention):",
|
|
11564
11856
|
" \u2191 / \u2193 navigate the suggestion list",
|
|
@@ -11597,15 +11889,16 @@ var help = () => ({
|
|
|
11597
11889
|
" /skill [sub] list / run user skills (project/.reasonix/skills + ~/.reasonix/skills).",
|
|
11598
11890
|
" subs: list | show <name> | <name> [args] (injects skill body as user turn)",
|
|
11599
11891
|
" /retry truncate & resend your last message (fresh sample from the model)",
|
|
11600
|
-
" /apply
|
|
11601
|
-
" /discard
|
|
11892
|
+
" /apply [N|1,3|1-4] (code mode) commit pending edit blocks (no arg \u2192 all; index \u2192 subset)",
|
|
11893
|
+
" /discard [N|1,3|1-4] (code mode) drop pending edits (no arg \u2192 all; index \u2192 subset)",
|
|
11894
|
+
" /walk (code mode) step through pending edits one block at a time (y/n per block, a apply rest, A flip AUTO)",
|
|
11602
11895
|
" /undo (code mode) roll back the latest non-undone edit batch",
|
|
11603
11896
|
" /history (code mode) list every edit batch this session",
|
|
11604
11897
|
" /show [id] (code mode) dump a stored edit diff (newest when id omitted)",
|
|
11605
11898
|
' /commit "msg" (code mode) git add -A && git commit -m "msg"',
|
|
11606
11899
|
" /plan [on|off] (code mode) toggle read-only plan mode; writes gated behind submit_plan + your approval",
|
|
11607
11900
|
" /apply-plan (code mode) force-approve pending/in-text plan (fallback)",
|
|
11608
|
-
" /mode [review|auto]
|
|
11901
|
+
" /mode [review|auto|yolo] (code mode) review = queue \xB7 auto = apply+undo banner \xB7 yolo = apply+auto-shell. Shift+Tab cycles all three.",
|
|
11609
11902
|
" /jobs (code mode) list background processes (run_background) \u2014 running and exited",
|
|
11610
11903
|
" /kill <id> (code mode) stop a background job by id (SIGTERM \u2192 SIGKILL)",
|
|
11611
11904
|
" /logs <id> [lines] (code mode) tail a background job's output (default 80 lines)",
|
|
@@ -11613,6 +11906,7 @@ var help = () => ({
|
|
|
11613
11906
|
" /forget delete the current session from disk",
|
|
11614
11907
|
" /new start fresh: drop all context + clear scrollback",
|
|
11615
11908
|
" /clear clear displayed scrollback only (context kept \u2014 model still sees it)",
|
|
11909
|
+
" /loop <interval> <prompt> auto-resubmit <prompt> every <interval> (5s..6h). /loop stop \xB7 type anything to cancel.",
|
|
11616
11910
|
" /exit quit",
|
|
11617
11911
|
"",
|
|
11618
11912
|
"Shell shortcut:",
|
|
@@ -11621,10 +11915,23 @@ var help = () => ({
|
|
|
11621
11915
|
" No allowlist gate \u2014 user-typed = explicit consent.",
|
|
11622
11916
|
" Example: !git status !ls src/ !npm test",
|
|
11623
11917
|
"",
|
|
11918
|
+
"Quick memory:",
|
|
11919
|
+
" #<note> append <note> to <project>/REASONIX.md (committable).",
|
|
11920
|
+
" Example: #findByEmail must be case-insensitive",
|
|
11921
|
+
" #g <note> append <note> to ~/.reasonix/REASONIX.md (global, never committed).",
|
|
11922
|
+
" Example: #g always run pnpm not npm",
|
|
11923
|
+
" Both pin into every future session's prefix. Faster than /memory.",
|
|
11924
|
+
" Use `\\#text` to send a literal `#text` to the model.",
|
|
11925
|
+
"",
|
|
11624
11926
|
"File references (code mode):",
|
|
11625
11927
|
" @path/to/file inline file content under [Referenced files] on send.",
|
|
11626
11928
|
" Type `@` to open the picker (\u2191\u2193 navigate, Tab/Enter pick).",
|
|
11627
11929
|
"",
|
|
11930
|
+
"URL references:",
|
|
11931
|
+
" @https://example.com fetch the URL, strip HTML, inline under [Referenced URLs].",
|
|
11932
|
+
" Same URL twice in one session fetches once (in-mem cache).",
|
|
11933
|
+
" Trailing sentence punctuation (./,/)) is stripped automatically.",
|
|
11934
|
+
"",
|
|
11628
11935
|
"Presets (branch + harvest are NEVER auto-enabled \u2014 opt-in only):",
|
|
11629
11936
|
" fast v4-flash \xB7 effort=high cheapest \xB7 quick Q&A, one-line edits",
|
|
11630
11937
|
" smart v4-flash \xB7 effort=max \u2190 default \xB7 day-to-day coding",
|
|
@@ -11644,8 +11951,8 @@ var help = () => ({
|
|
|
11644
11951
|
var setup = () => ({
|
|
11645
11952
|
info: "To reconfigure (preset, MCP servers, API key), exit this chat and run `reasonix setup`. Changes take effect on next launch."
|
|
11646
11953
|
});
|
|
11647
|
-
var retry = (_args,
|
|
11648
|
-
const prev =
|
|
11954
|
+
var retry = (_args, loop2) => {
|
|
11955
|
+
const prev = loop2.retryLastUser();
|
|
11649
11956
|
if (!prev) {
|
|
11650
11957
|
return {
|
|
11651
11958
|
info: "nothing to retry \u2014 no prior user message in this session's log."
|
|
@@ -11657,6 +11964,37 @@ var retry = (_args, loop) => {
|
|
|
11657
11964
|
resubmit: prev
|
|
11658
11965
|
};
|
|
11659
11966
|
};
|
|
11967
|
+
var loop = (args, _loop, ctx) => {
|
|
11968
|
+
if (!ctx.startLoop || !ctx.stopLoop || !ctx.getLoopStatus) {
|
|
11969
|
+
return {
|
|
11970
|
+
info: "/loop is only available in the interactive TUI (not in run/replay)."
|
|
11971
|
+
};
|
|
11972
|
+
}
|
|
11973
|
+
const cmd = parseLoopCommand(args);
|
|
11974
|
+
if (cmd.kind === "error") return { info: cmd.message };
|
|
11975
|
+
if (cmd.kind === "stop") {
|
|
11976
|
+
const wasActive = ctx.getLoopStatus() !== null;
|
|
11977
|
+
ctx.stopLoop();
|
|
11978
|
+
return {
|
|
11979
|
+
info: wasActive ? "\u25B8 loop stopped." : "no active loop to stop."
|
|
11980
|
+
};
|
|
11981
|
+
}
|
|
11982
|
+
if (cmd.kind === "status") {
|
|
11983
|
+
const status2 = ctx.getLoopStatus();
|
|
11984
|
+
if (!status2) {
|
|
11985
|
+
return {
|
|
11986
|
+
info: "no active loop. Start one with `/loop <interval> <prompt>` (e.g. /loop 30s npm test).\nCancels on: /loop stop \xB7 Esc \xB7 /clear \xB7 /new \xB7 any user-typed prompt."
|
|
11987
|
+
};
|
|
11988
|
+
}
|
|
11989
|
+
return { info: `\u25B8 ${formatLoopStatus(status2.prompt, status2.nextFireMs, status2.iter)}` };
|
|
11990
|
+
}
|
|
11991
|
+
ctx.startLoop(cmd.intervalMs, cmd.prompt);
|
|
11992
|
+
return {
|
|
11993
|
+
info: `\u25B8 loop started \u2014 re-submitting "${cmd.prompt}" every ${formatDuration2(
|
|
11994
|
+
cmd.intervalMs
|
|
11995
|
+
)}. Type anything (or /loop stop) to cancel.`
|
|
11996
|
+
};
|
|
11997
|
+
};
|
|
11660
11998
|
var handlers2 = {
|
|
11661
11999
|
exit,
|
|
11662
12000
|
quit: exit,
|
|
@@ -11667,7 +12005,8 @@ var handlers2 = {
|
|
|
11667
12005
|
help,
|
|
11668
12006
|
"?": help,
|
|
11669
12007
|
setup,
|
|
11670
|
-
retry
|
|
12008
|
+
retry,
|
|
12009
|
+
loop
|
|
11671
12010
|
};
|
|
11672
12011
|
|
|
11673
12012
|
// src/cli/ui/slash/helpers.ts
|
|
@@ -11790,22 +12129,33 @@ var show = (args, _loop, ctx) => {
|
|
|
11790
12129
|
}
|
|
11791
12130
|
return { info: ctx.codeShowEdit(args) };
|
|
11792
12131
|
};
|
|
11793
|
-
var apply = (
|
|
12132
|
+
var apply = (args, _loop, ctx) => {
|
|
11794
12133
|
if (!ctx.codeApply) {
|
|
11795
12134
|
return {
|
|
11796
12135
|
info: "/apply is only available inside `reasonix code` (nothing to apply here)."
|
|
11797
12136
|
};
|
|
11798
12137
|
}
|
|
11799
|
-
|
|
12138
|
+
const parsed = parseIndicesArg(args, ctx.pendingEditCount ?? 0);
|
|
12139
|
+
if ("error" in parsed) return { info: `/apply: ${parsed.error}` };
|
|
12140
|
+
return { info: ctx.codeApply(parsed.indices) };
|
|
11800
12141
|
};
|
|
11801
|
-
var discard = (
|
|
12142
|
+
var discard = (args, _loop, ctx) => {
|
|
11802
12143
|
if (!ctx.codeDiscard) {
|
|
11803
12144
|
return {
|
|
11804
12145
|
info: "/discard is only available inside `reasonix code`."
|
|
11805
12146
|
};
|
|
11806
12147
|
}
|
|
11807
|
-
|
|
12148
|
+
const parsed = parseIndicesArg(args, ctx.pendingEditCount ?? 0);
|
|
12149
|
+
if ("error" in parsed) return { info: `/discard: ${parsed.error}` };
|
|
12150
|
+
return { info: ctx.codeDiscard(parsed.indices) };
|
|
11808
12151
|
};
|
|
12152
|
+
function parseIndicesArg(args, max) {
|
|
12153
|
+
const raw = args.join(",").replace(/,+/g, ",").replace(/^,|,$/g, "");
|
|
12154
|
+
if (!raw) return { indices: [] };
|
|
12155
|
+
const parsed = parseEditIndices(raw, max);
|
|
12156
|
+
if ("error" in parsed) return { error: parsed.error };
|
|
12157
|
+
return { indices: parsed.ok };
|
|
12158
|
+
}
|
|
11809
12159
|
var plan = (args, _loop, ctx) => {
|
|
11810
12160
|
if (!ctx.setPlanMode) {
|
|
11811
12161
|
return {
|
|
@@ -11852,15 +12202,17 @@ var mode = (args, _loop, ctx) => {
|
|
|
11852
12202
|
let target;
|
|
11853
12203
|
if (raw === "review") target = "review";
|
|
11854
12204
|
else if (raw === "auto") target = "auto";
|
|
12205
|
+
else if (raw === "yolo") target = "yolo";
|
|
11855
12206
|
else if (raw === "") {
|
|
11856
|
-
target = current === "
|
|
12207
|
+
target = current === "review" ? "auto" : current === "auto" ? "yolo" : "review";
|
|
11857
12208
|
} else {
|
|
11858
|
-
return {
|
|
12209
|
+
return {
|
|
12210
|
+
info: "usage: /mode <review|auto|yolo> (Shift+Tab also cycles)"
|
|
12211
|
+
};
|
|
11859
12212
|
}
|
|
11860
12213
|
ctx.setEditMode(target);
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
};
|
|
12214
|
+
const banner = target === "yolo" ? "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run with no prompt. /undo still rolls back edits. Use carefully." : target === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo, or /undo later. Shell commands still ask." : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)";
|
|
12215
|
+
return { info: banner };
|
|
11864
12216
|
};
|
|
11865
12217
|
var commit = (args, _loop, ctx) => {
|
|
11866
12218
|
if (!ctx.codeRoot) {
|
|
@@ -11877,6 +12229,14 @@ var commit = (args, _loop, ctx) => {
|
|
|
11877
12229
|
}
|
|
11878
12230
|
return runGitCommit(ctx.codeRoot, message);
|
|
11879
12231
|
};
|
|
12232
|
+
var walk2 = (_args, _loop, ctx) => {
|
|
12233
|
+
if (!ctx.startWalkthrough) {
|
|
12234
|
+
return {
|
|
12235
|
+
info: "/walk is only available inside `reasonix code`."
|
|
12236
|
+
};
|
|
12237
|
+
}
|
|
12238
|
+
return { info: ctx.startWalkthrough() };
|
|
12239
|
+
};
|
|
11880
12240
|
var handlers3 = {
|
|
11881
12241
|
undo,
|
|
11882
12242
|
history,
|
|
@@ -11887,7 +12247,8 @@ var handlers3 = {
|
|
|
11887
12247
|
"apply-plan": applyPlan,
|
|
11888
12248
|
applyplan: applyPlan,
|
|
11889
12249
|
mode,
|
|
11890
|
-
commit
|
|
12250
|
+
commit,
|
|
12251
|
+
walk: walk2
|
|
11891
12252
|
};
|
|
11892
12253
|
|
|
11893
12254
|
// src/cli/ui/slash/handlers/jobs.ts
|
|
@@ -11952,10 +12313,10 @@ var handlers4 = {
|
|
|
11952
12313
|
};
|
|
11953
12314
|
|
|
11954
12315
|
// src/cli/ui/slash/handlers/mcp.ts
|
|
11955
|
-
var mcp = (_args,
|
|
12316
|
+
var mcp = (_args, loop2, ctx) => {
|
|
11956
12317
|
const servers = ctx.mcpServers ?? [];
|
|
11957
12318
|
const specs = ctx.mcpSpecs ?? [];
|
|
11958
|
-
const toolSpecs =
|
|
12319
|
+
const toolSpecs = loop2.prefix.toolSpecs ?? [];
|
|
11959
12320
|
if (servers.length === 0 && specs.length === 0 && toolSpecs.length === 0) {
|
|
11960
12321
|
return {
|
|
11961
12322
|
info: 'no MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp "<spec>". `reasonix mcp list` shows the catalog.'
|
|
@@ -12144,14 +12505,14 @@ var memory = (args, _loop, ctx) => {
|
|
|
12144
12505
|
var handlers6 = { memory };
|
|
12145
12506
|
|
|
12146
12507
|
// src/cli/ui/slash/handlers/model.ts
|
|
12147
|
-
var model = (args,
|
|
12508
|
+
var model = (args, loop2, ctx) => {
|
|
12148
12509
|
const id = args[0];
|
|
12149
12510
|
const known = ctx.models ?? null;
|
|
12150
12511
|
if (!id) {
|
|
12151
12512
|
const hint = known && known.length > 0 ? known.join(" | ") : "try deepseek-v4-flash or deepseek-v4-pro \u2014 run /models to fetch the live list";
|
|
12152
12513
|
return { info: `usage: /model <id> (${hint})` };
|
|
12153
12514
|
}
|
|
12154
|
-
|
|
12515
|
+
loop2.configure({ model: id });
|
|
12155
12516
|
if (known && known.length > 0 && !known.includes(id)) {
|
|
12156
12517
|
return {
|
|
12157
12518
|
info: `model \u2192 ${id} (\u26A0 not in the fetched catalog: ${known.join(", ")}. If this is wrong the next call will 400 \u2014 run /models to refresh.)`
|
|
@@ -12159,7 +12520,7 @@ var model = (args, loop, ctx) => {
|
|
|
12159
12520
|
}
|
|
12160
12521
|
return { info: `model \u2192 ${id}` };
|
|
12161
12522
|
};
|
|
12162
|
-
var models = (_args,
|
|
12523
|
+
var models = (_args, loop2, ctx) => {
|
|
12163
12524
|
const list = ctx.models ?? null;
|
|
12164
12525
|
if (list === null) {
|
|
12165
12526
|
ctx.refreshModels?.();
|
|
@@ -12172,7 +12533,7 @@ var models = (_args, loop, ctx) => {
|
|
|
12172
12533
|
info: "DeepSeek /models returned an empty list. Try /models again, or check your account status at api-docs.deepseek.com."
|
|
12173
12534
|
};
|
|
12174
12535
|
}
|
|
12175
|
-
const current =
|
|
12536
|
+
const current = loop2.model;
|
|
12176
12537
|
const lines = list.map((id) => id === current ? `\u25B8 ${id} (current)` : ` ${id}`);
|
|
12177
12538
|
return {
|
|
12178
12539
|
info: [
|
|
@@ -12184,18 +12545,18 @@ var models = (_args, loop, ctx) => {
|
|
|
12184
12545
|
].join("\n")
|
|
12185
12546
|
};
|
|
12186
12547
|
};
|
|
12187
|
-
var harvest2 = (args,
|
|
12548
|
+
var harvest2 = (args, loop2) => {
|
|
12188
12549
|
const arg = (args[0] ?? "").toLowerCase();
|
|
12189
|
-
const on = arg === "" ? !
|
|
12190
|
-
|
|
12191
|
-
if (
|
|
12550
|
+
const on = arg === "" ? !loop2.harvestEnabled : arg === "on" || arg === "true" || arg === "1";
|
|
12551
|
+
loop2.configure({ harvest: on });
|
|
12552
|
+
if (loop2.harvestEnabled) {
|
|
12192
12553
|
return {
|
|
12193
12554
|
info: "harvest \u2192 on (Pillar-2 plan-state extraction \xB7 +1 cheap flash call per turn \xB7 opt-in only; no preset turns it on)"
|
|
12194
12555
|
};
|
|
12195
12556
|
}
|
|
12196
12557
|
return { info: "harvest \u2192 off" };
|
|
12197
12558
|
};
|
|
12198
|
-
var preset = (args,
|
|
12559
|
+
var preset = (args, loop2) => {
|
|
12199
12560
|
const name = (args[0] ?? "").toLowerCase();
|
|
12200
12561
|
const applyAndPersist = (effort2) => {
|
|
12201
12562
|
try {
|
|
@@ -12204,7 +12565,7 @@ var preset = (args, loop) => {
|
|
|
12204
12565
|
}
|
|
12205
12566
|
};
|
|
12206
12567
|
if (name === "fast" || name === "default") {
|
|
12207
|
-
|
|
12568
|
+
loop2.configure({
|
|
12208
12569
|
model: "deepseek-v4-flash",
|
|
12209
12570
|
reasoningEffort: "high",
|
|
12210
12571
|
harvest: false,
|
|
@@ -12214,7 +12575,7 @@ var preset = (args, loop) => {
|
|
|
12214
12575
|
return { info: "preset \u2192 fast (v4-flash \xB7 effort=high \xB7 cheapest)" };
|
|
12215
12576
|
}
|
|
12216
12577
|
if (name === "smart") {
|
|
12217
|
-
|
|
12578
|
+
loop2.configure({
|
|
12218
12579
|
model: "deepseek-v4-flash",
|
|
12219
12580
|
reasoningEffort: "max",
|
|
12220
12581
|
harvest: false,
|
|
@@ -12224,7 +12585,7 @@ var preset = (args, loop) => {
|
|
|
12224
12585
|
return { info: "preset \u2192 smart (v4-flash \xB7 effort=max \xB7 default \xB7 ~1.5\xD7 fast)" };
|
|
12225
12586
|
}
|
|
12226
12587
|
if (name === "max" || name === "best") {
|
|
12227
|
-
|
|
12588
|
+
loop2.configure({
|
|
12228
12589
|
model: "deepseek-v4-pro",
|
|
12229
12590
|
reasoningEffort: "max",
|
|
12230
12591
|
harvest: false,
|
|
@@ -12237,10 +12598,10 @@ var preset = (args, loop) => {
|
|
|
12237
12598
|
}
|
|
12238
12599
|
return { info: "usage: /preset <fast|smart|max>" };
|
|
12239
12600
|
};
|
|
12240
|
-
var branch = (args,
|
|
12601
|
+
var branch = (args, loop2) => {
|
|
12241
12602
|
const raw = (args[0] ?? "").toLowerCase();
|
|
12242
12603
|
if (raw === "" || raw === "off" || raw === "0" || raw === "1") {
|
|
12243
|
-
|
|
12604
|
+
loop2.configure({ branch: 1 });
|
|
12244
12605
|
return { info: "branch \u2192 off" };
|
|
12245
12606
|
}
|
|
12246
12607
|
const n = Number.parseInt(raw, 10);
|
|
@@ -12250,36 +12611,36 @@ var branch = (args, loop) => {
|
|
|
12250
12611
|
if (n > 8) {
|
|
12251
12612
|
return { info: "branch budget capped at 8 to prevent runaway cost" };
|
|
12252
12613
|
}
|
|
12253
|
-
|
|
12614
|
+
loop2.configure({ branch: n });
|
|
12254
12615
|
return {
|
|
12255
12616
|
info: `branch \u2192 ${n} (runs ${n} parallel samples per turn \xB7 ${n}\xD7 per-turn cost \xB7 streaming disabled \xB7 manual only, no preset enables branching)`
|
|
12256
12617
|
};
|
|
12257
12618
|
};
|
|
12258
|
-
var effort = (args,
|
|
12619
|
+
var effort = (args, loop2) => {
|
|
12259
12620
|
const raw = (args[0] ?? "").toLowerCase();
|
|
12260
12621
|
if (raw === "") {
|
|
12261
12622
|
return {
|
|
12262
|
-
info: `reasoning_effort \u2192 ${
|
|
12623
|
+
info: `reasoning_effort \u2192 ${loop2.reasoningEffort} (use /effort high for cheaper/faster, /effort max for the agent-class default \xB7 persisted across relaunches)`
|
|
12263
12624
|
};
|
|
12264
12625
|
}
|
|
12265
12626
|
if (raw !== "high" && raw !== "max") {
|
|
12266
12627
|
return { info: "usage: /effort <high|max>" };
|
|
12267
12628
|
}
|
|
12268
|
-
|
|
12629
|
+
loop2.configure({ reasoningEffort: raw });
|
|
12269
12630
|
try {
|
|
12270
12631
|
saveReasoningEffort(raw);
|
|
12271
12632
|
} catch {
|
|
12272
12633
|
}
|
|
12273
12634
|
return { info: `reasoning_effort \u2192 ${raw} (persisted)` };
|
|
12274
12635
|
};
|
|
12275
|
-
var pro = (args,
|
|
12636
|
+
var pro = (args, loop2, ctx) => {
|
|
12276
12637
|
const arg = (args[0] ?? "").toLowerCase();
|
|
12277
12638
|
if (arg === "off" || arg === "cancel" || arg === "disarm") {
|
|
12278
|
-
if (!
|
|
12639
|
+
if (!loop2.proArmed) {
|
|
12279
12640
|
return { info: "nothing armed \u2014 /pro with no args will arm pro for your next turn" };
|
|
12280
12641
|
}
|
|
12281
12642
|
if (ctx.disarmPro) ctx.disarmPro();
|
|
12282
|
-
else
|
|
12643
|
+
else loop2.disarmPro();
|
|
12283
12644
|
return { info: "\u25B8 /pro disarmed \u2014 next turn falls back to the current preset" };
|
|
12284
12645
|
}
|
|
12285
12646
|
if (arg && arg !== "on" && arg !== "arm") {
|
|
@@ -12288,7 +12649,7 @@ var pro = (args, loop, ctx) => {
|
|
|
12288
12649
|
};
|
|
12289
12650
|
}
|
|
12290
12651
|
if (ctx.armPro) ctx.armPro();
|
|
12291
|
-
else
|
|
12652
|
+
else loop2.armProForNextTurn();
|
|
12292
12653
|
return {
|
|
12293
12654
|
info: `\u25B8 /pro armed \u2014 your NEXT message runs on ${ESCALATION_MODEL_ID} regardless of preset. Auto-disarms after one turn. Use /preset max for a persistent switch.`
|
|
12294
12655
|
};
|
|
@@ -12305,8 +12666,8 @@ var handlers7 = {
|
|
|
12305
12666
|
};
|
|
12306
12667
|
|
|
12307
12668
|
// src/cli/ui/slash/handlers/observability.ts
|
|
12308
|
-
var think = (_args,
|
|
12309
|
-
const raw =
|
|
12669
|
+
var think = (_args, loop2) => {
|
|
12670
|
+
const raw = loop2.scratch.reasoning;
|
|
12310
12671
|
if (!raw || !raw.trim()) {
|
|
12311
12672
|
return {
|
|
12312
12673
|
info: "no reasoning cached. `/think` shows the full thinking-mode thought for the most recent turn \u2014 only thinking-mode models (deepseek-v4-flash / -v4-pro / -reasoner) produce it, and only once the turn completes."
|
|
@@ -12348,10 +12709,10 @@ var tool = (args, _loop, ctx) => {
|
|
|
12348
12709
|
${entry.text}`
|
|
12349
12710
|
};
|
|
12350
12711
|
};
|
|
12351
|
-
var context = (_args,
|
|
12352
|
-
const systemTokens = countTokens(
|
|
12353
|
-
const toolsTokens = countTokens(JSON.stringify(
|
|
12354
|
-
const entries =
|
|
12712
|
+
var context = (_args, loop2) => {
|
|
12713
|
+
const systemTokens = countTokens(loop2.prefix.system);
|
|
12714
|
+
const toolsTokens = countTokens(JSON.stringify(loop2.prefix.toolSpecs));
|
|
12715
|
+
const entries = loop2.log.toMessages();
|
|
12355
12716
|
let userTokens = 0;
|
|
12356
12717
|
let assistantTokens = 0;
|
|
12357
12718
|
let toolResultTokens = 0;
|
|
@@ -12376,7 +12737,7 @@ var context = (_args, loop) => {
|
|
|
12376
12737
|
}
|
|
12377
12738
|
const logTokens = userTokens + assistantTokens + toolResultTokens + toolCallTokens;
|
|
12378
12739
|
const total = systemTokens + toolsTokens + logTokens;
|
|
12379
|
-
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[
|
|
12740
|
+
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[loop2.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
12380
12741
|
const pct2 = (n) => total > 0 ? `${Math.round(n / total * 100)}%`.padStart(4) : " 0%";
|
|
12381
12742
|
const row2 = (label, n, note = "") => ` ${label.padEnd(20)}${compactNum(n).padStart(8)} tokens ${pct2(n)}${note ? ` ${note}` : ""}`;
|
|
12382
12743
|
const lines = [
|
|
@@ -12385,7 +12746,7 @@ var context = (_args, loop) => {
|
|
|
12385
12746
|
)}% of window)`,
|
|
12386
12747
|
"",
|
|
12387
12748
|
row2("system prompt", systemTokens),
|
|
12388
|
-
row2("tool specs", toolsTokens, `(${
|
|
12749
|
+
row2("tool specs", toolsTokens, `(${loop2.prefix.toolSpecs.length} tools)`),
|
|
12389
12750
|
row2("log (all turns)", logTokens, `(${entries.length} messages)`),
|
|
12390
12751
|
` user ${compactNum(userTokens).padStart(8)} tokens`,
|
|
12391
12752
|
` assistant ${compactNum(assistantTokens).padStart(8)} tokens`,
|
|
@@ -12408,23 +12769,23 @@ var context = (_args, loop) => {
|
|
|
12408
12769
|
);
|
|
12409
12770
|
return { info: lines.join("\n") };
|
|
12410
12771
|
};
|
|
12411
|
-
var status = (_args,
|
|
12412
|
-
const branchBudget =
|
|
12413
|
-
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[
|
|
12414
|
-
const lastPromptTokens =
|
|
12772
|
+
var status = (_args, loop2, ctx) => {
|
|
12773
|
+
const branchBudget = loop2.branchOptions.budget ?? 1;
|
|
12774
|
+
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[loop2.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
12775
|
+
const lastPromptTokens = loop2.stats.summary().lastPromptTokens;
|
|
12415
12776
|
const ctxPct = ctxMax > 0 ? Math.round(lastPromptTokens / ctxMax * 100) : 0;
|
|
12416
12777
|
const ctxLine = lastPromptTokens > 0 ? ` ctx ${compactNum(lastPromptTokens)}/${compactNum(ctxMax)} (${ctxPct}%)` : " ctx no turns yet";
|
|
12417
12778
|
const pending = ctx.pendingEditCount ?? 0;
|
|
12418
|
-
const sessionLine =
|
|
12779
|
+
const sessionLine = loop2.sessionName ? ` session "${loop2.sessionName}" \xB7 ${loop2.log.length} messages in log (resumed ${loop2.resumedMessageCount})` : " session (ephemeral \u2014 no persistence)";
|
|
12419
12780
|
const mcpCount = ctx.mcpSpecs?.length ?? 0;
|
|
12420
|
-
const toolCount =
|
|
12781
|
+
const toolCount = loop2.prefix.toolSpecs.length;
|
|
12421
12782
|
const mcpLine = ` mcp ${mcpCount} server(s), ${toolCount} tool(s) in registry`;
|
|
12422
12783
|
const pendingLine = pending > 0 ? ` edits ${pending} pending (/apply to commit, /discard to drop)` : "";
|
|
12423
12784
|
const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
|
|
12424
|
-
const modeLine = ctx.editMode === "auto" ? " mode AUTO \u2014 edits apply immediately (u to undo within 5s \xB7 Shift+Tab to flip)" : ctx.editMode === "review" ? " mode review \u2014 edits queue for /apply or y (Shift+Tab to flip)" : "";
|
|
12785
|
+
const modeLine = ctx.editMode === "yolo" ? " mode YOLO \u2014 edits + shell auto-run with no prompt (/undo still rolls back \xB7 Shift+Tab to flip)" : ctx.editMode === "auto" ? " mode AUTO \u2014 edits apply immediately (u to undo within 5s \xB7 Shift+Tab to flip)" : ctx.editMode === "review" ? " mode review \u2014 edits queue for /apply or y (Shift+Tab to flip)" : "";
|
|
12425
12786
|
const lines = [
|
|
12426
|
-
` model ${
|
|
12427
|
-
` flags harvest=${
|
|
12787
|
+
` model ${loop2.model}`,
|
|
12788
|
+
` flags harvest=${loop2.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop2.stream ? "on" : "off"} \xB7 effort=${loop2.reasoningEffort}`,
|
|
12428
12789
|
ctxLine,
|
|
12429
12790
|
mcpLine,
|
|
12430
12791
|
sessionLine
|
|
@@ -12434,10 +12795,10 @@ var status = (_args, loop, ctx) => {
|
|
|
12434
12795
|
if (modeLine) lines.push(modeLine);
|
|
12435
12796
|
return { info: lines.join("\n") };
|
|
12436
12797
|
};
|
|
12437
|
-
var compact = (args,
|
|
12798
|
+
var compact = (args, loop2) => {
|
|
12438
12799
|
const tight = Number.parseInt(args[0] ?? "", 10);
|
|
12439
12800
|
const cap = Number.isFinite(tight) && tight >= 100 ? tight : 4e3;
|
|
12440
|
-
const { healedCount, tokensSaved, charsSaved } =
|
|
12801
|
+
const { healedCount, tokensSaved, charsSaved } = loop2.compact(cap);
|
|
12441
12802
|
if (healedCount === 0) {
|
|
12442
12803
|
return {
|
|
12443
12804
|
info: `\u25B8 nothing to compact \u2014 no tool result or tool-call args in history exceed ${cap.toLocaleString()} tokens.`
|
|
@@ -12458,8 +12819,8 @@ var handlers8 = {
|
|
|
12458
12819
|
|
|
12459
12820
|
// src/cli/ui/slash/handlers/plans.ts
|
|
12460
12821
|
import { basename } from "path";
|
|
12461
|
-
var plans = (_args,
|
|
12462
|
-
const sessionName =
|
|
12822
|
+
var plans = (_args, loop2) => {
|
|
12823
|
+
const sessionName = loop2.sessionName;
|
|
12463
12824
|
if (!sessionName) {
|
|
12464
12825
|
return {
|
|
12465
12826
|
info: "no session attached \u2014 `/plans` is per-session. Run `reasonix code` in a project to get a session."
|
|
@@ -12500,8 +12861,8 @@ var plans = (_args, loop) => {
|
|
|
12500
12861
|
}
|
|
12501
12862
|
return { info: lines.join("\n") };
|
|
12502
12863
|
};
|
|
12503
|
-
var replay = (args,
|
|
12504
|
-
const sessionName =
|
|
12864
|
+
var replay = (args, loop2) => {
|
|
12865
|
+
const sessionName = loop2.sessionName;
|
|
12505
12866
|
if (!sessionName) {
|
|
12506
12867
|
return {
|
|
12507
12868
|
info: "no session attached \u2014 `/replay` is per-session. Run `reasonix code` in a project to get a session."
|
|
@@ -12541,7 +12902,7 @@ var handlers9 = {
|
|
|
12541
12902
|
};
|
|
12542
12903
|
|
|
12543
12904
|
// src/cli/ui/slash/handlers/sessions.ts
|
|
12544
|
-
var sessions = (_args,
|
|
12905
|
+
var sessions = (_args, loop2) => {
|
|
12545
12906
|
const items = listSessions();
|
|
12546
12907
|
if (items.length === 0) {
|
|
12547
12908
|
return {
|
|
@@ -12552,7 +12913,7 @@ var sessions = (_args, loop) => {
|
|
|
12552
12913
|
for (const s of items) {
|
|
12553
12914
|
const sizeKb = (s.size / 1024).toFixed(1);
|
|
12554
12915
|
const when = s.mtime.toISOString().replace("T", " ").slice(0, 16);
|
|
12555
|
-
const marker = s.name ===
|
|
12916
|
+
const marker = s.name === loop2.sessionName ? "\u25B8" : " ";
|
|
12556
12917
|
lines.push(
|
|
12557
12918
|
` ${marker} ${s.name.padEnd(22)} ${String(s.messageCount).padStart(5)} msgs ${sizeKb.padStart(7)} KB ${when}`
|
|
12558
12919
|
);
|
|
@@ -12561,11 +12922,11 @@ var sessions = (_args, loop) => {
|
|
|
12561
12922
|
lines.push("Resume with: reasonix chat --session <name>");
|
|
12562
12923
|
return { info: lines.join("\n") };
|
|
12563
12924
|
};
|
|
12564
|
-
var forget = (_args,
|
|
12565
|
-
if (!
|
|
12925
|
+
var forget = (_args, loop2) => {
|
|
12926
|
+
if (!loop2.sessionName) {
|
|
12566
12927
|
return { info: "not in a session \u2014 nothing to forget" };
|
|
12567
12928
|
}
|
|
12568
|
-
const name =
|
|
12929
|
+
const name = loop2.sessionName;
|
|
12569
12930
|
const ok = deleteSession(name);
|
|
12570
12931
|
return {
|
|
12571
12932
|
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?)`
|
|
@@ -12666,9 +13027,9 @@ var HANDLERS = {
|
|
|
12666
13027
|
...handlers10,
|
|
12667
13028
|
...handlers11
|
|
12668
13029
|
};
|
|
12669
|
-
function handleSlash(cmd, args,
|
|
13030
|
+
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
12670
13031
|
const h = HANDLERS[cmd];
|
|
12671
|
-
if (h) return h(args,
|
|
13032
|
+
if (h) return h(args, loop2, ctx);
|
|
12672
13033
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
12673
13034
|
}
|
|
12674
13035
|
|
|
@@ -13020,14 +13381,14 @@ function useEditHistory(codeMode) {
|
|
|
13020
13381
|
|
|
13021
13382
|
// src/cli/ui/useSessionInfo.ts
|
|
13022
13383
|
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState8 } from "react";
|
|
13023
|
-
function useSessionInfo(
|
|
13384
|
+
function useSessionInfo(loop2) {
|
|
13024
13385
|
const [balance, setBalance] = useState8(null);
|
|
13025
13386
|
const [models2, setModels] = useState8(null);
|
|
13026
13387
|
const [latestVersion, setLatestVersion] = useState8(null);
|
|
13027
13388
|
useEffect4(() => {
|
|
13028
13389
|
let cancelled = false;
|
|
13029
13390
|
void (async () => {
|
|
13030
|
-
const bal = await
|
|
13391
|
+
const bal = await loop2.client.getBalance().catch(() => null);
|
|
13031
13392
|
if (cancelled || !bal || !bal.balance_infos.length) return;
|
|
13032
13393
|
const primary = bal.balance_infos[0];
|
|
13033
13394
|
setBalance({ currency: primary.currency, total: Number(primary.total_balance) });
|
|
@@ -13035,18 +13396,18 @@ function useSessionInfo(loop) {
|
|
|
13035
13396
|
return () => {
|
|
13036
13397
|
cancelled = true;
|
|
13037
13398
|
};
|
|
13038
|
-
}, [
|
|
13399
|
+
}, [loop2]);
|
|
13039
13400
|
useEffect4(() => {
|
|
13040
13401
|
let cancelled = false;
|
|
13041
13402
|
void (async () => {
|
|
13042
|
-
const list = await
|
|
13403
|
+
const list = await loop2.client.listModels().catch(() => null);
|
|
13043
13404
|
if (cancelled || !list) return;
|
|
13044
13405
|
setModels(list.data.map((m) => m.id));
|
|
13045
13406
|
})();
|
|
13046
13407
|
return () => {
|
|
13047
13408
|
cancelled = true;
|
|
13048
13409
|
};
|
|
13049
|
-
}, [
|
|
13410
|
+
}, [loop2]);
|
|
13050
13411
|
useEffect4(() => {
|
|
13051
13412
|
let cancelled = false;
|
|
13052
13413
|
void (async () => {
|
|
@@ -13061,19 +13422,19 @@ function useSessionInfo(loop) {
|
|
|
13061
13422
|
const updateAvailable = latestVersion && compareVersions(VERSION, latestVersion) < 0 ? latestVersion : null;
|
|
13062
13423
|
const refreshBalance = useCallback3(() => {
|
|
13063
13424
|
void (async () => {
|
|
13064
|
-
const bal = await
|
|
13425
|
+
const bal = await loop2.client.getBalance().catch(() => null);
|
|
13065
13426
|
if (bal?.balance_infos.length) {
|
|
13066
13427
|
const p = bal.balance_infos[0];
|
|
13067
13428
|
setBalance({ currency: p.currency, total: Number(p.total_balance) });
|
|
13068
13429
|
}
|
|
13069
13430
|
})();
|
|
13070
|
-
}, [
|
|
13431
|
+
}, [loop2]);
|
|
13071
13432
|
const refreshModels = useCallback3(() => {
|
|
13072
13433
|
void (async () => {
|
|
13073
|
-
const list = await
|
|
13434
|
+
const list = await loop2.client.listModels().catch(() => null);
|
|
13074
13435
|
if (list) setModels(list.data.map((m) => m.id));
|
|
13075
13436
|
})();
|
|
13076
|
-
}, [
|
|
13437
|
+
}, [loop2]);
|
|
13077
13438
|
const refreshLatestVersion = useCallback3(() => {
|
|
13078
13439
|
void (async () => {
|
|
13079
13440
|
const fresh = await getLatestVersion({ force: true });
|
|
@@ -13151,6 +13512,17 @@ function useSubagent({ session, setHistorical }) {
|
|
|
13151
13512
|
// src/cli/ui/App.tsx
|
|
13152
13513
|
var FLUSH_INTERVAL_MS = 100;
|
|
13153
13514
|
var PLAIN_UI = process.env.REASONIX_UI === "plain";
|
|
13515
|
+
function LoopStatusRow({
|
|
13516
|
+
loop: loop2
|
|
13517
|
+
}) {
|
|
13518
|
+
const [, setTick] = React23.useState(0);
|
|
13519
|
+
React23.useEffect(() => {
|
|
13520
|
+
const id = setInterval(() => setTick((t) => t + 1), 1e3);
|
|
13521
|
+
return () => clearInterval(id);
|
|
13522
|
+
}, []);
|
|
13523
|
+
const nextFireMs = Math.max(0, loop2.nextFireAt - Date.now());
|
|
13524
|
+
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`));
|
|
13525
|
+
}
|
|
13154
13526
|
function App({
|
|
13155
13527
|
model: model2,
|
|
13156
13528
|
system,
|
|
@@ -13170,6 +13542,9 @@ function App({
|
|
|
13170
13542
|
const [input, setInput] = useState10("");
|
|
13171
13543
|
const [busy, setBusy] = useState10(false);
|
|
13172
13544
|
const abortedThisTurn = useRef6(false);
|
|
13545
|
+
useEffect6(() => {
|
|
13546
|
+
busyRef.current = busy;
|
|
13547
|
+
}, [busy]);
|
|
13173
13548
|
const [ongoingTool, setOngoingTool] = useState10(null);
|
|
13174
13549
|
const [toolProgress, setToolProgress] = useState10(null);
|
|
13175
13550
|
const { stdout: stdout2 } = useStdout8();
|
|
@@ -13223,6 +13598,7 @@ function App({
|
|
|
13223
13598
|
const [pendingCount, setPendingCount] = useState10(0);
|
|
13224
13599
|
const syncPendingCount = useCallback4(() => {
|
|
13225
13600
|
setPendingCount(pendingEdits.current.length);
|
|
13601
|
+
setPendingTick((t) => t + 1);
|
|
13226
13602
|
}, []);
|
|
13227
13603
|
const [editMode, setEditMode] = useState10(() => codeMode ? loadEditMode() : "review");
|
|
13228
13604
|
const editModeRef = useRef6(editMode);
|
|
@@ -13231,6 +13607,8 @@ function App({
|
|
|
13231
13607
|
if (codeMode) saveEditMode(editMode);
|
|
13232
13608
|
}, [editMode, codeMode]);
|
|
13233
13609
|
const [pendingEditReview, setPendingEditReview] = useState10(null);
|
|
13610
|
+
const [walkthroughActive, setWalkthroughActive] = useState10(false);
|
|
13611
|
+
const [pendingTick, setPendingTick] = useState10(0);
|
|
13234
13612
|
const editReviewResolveRef = useRef6(null);
|
|
13235
13613
|
const turnEditPolicyRef = useRef6("ask");
|
|
13236
13614
|
const [modeFlash, setModeFlash] = useState10(false);
|
|
@@ -13261,6 +13639,16 @@ function App({
|
|
|
13261
13639
|
const promptHistory = useRef6([]);
|
|
13262
13640
|
const historyCursor = useRef6(-1);
|
|
13263
13641
|
const assistantIterCounter = useRef6(0);
|
|
13642
|
+
const atUrlCache = useRef6(/* @__PURE__ */ new Map());
|
|
13643
|
+
const [activeLoop, setActiveLoop] = useState10(null);
|
|
13644
|
+
const loopTimerRef = useRef6(null);
|
|
13645
|
+
const handleSubmitRef = useRef6(null);
|
|
13646
|
+
const busyRef = useRef6(false);
|
|
13647
|
+
const activeLoopRef = useRef6(activeLoop);
|
|
13648
|
+
const loopFiringRef = useRef6(false);
|
|
13649
|
+
useEffect6(() => {
|
|
13650
|
+
activeLoopRef.current = activeLoop;
|
|
13651
|
+
}, [activeLoop]);
|
|
13264
13652
|
const toolHistoryRef = useRef6([]);
|
|
13265
13653
|
const planStepsRef = useRef6(null);
|
|
13266
13654
|
const completedStepIdsRef = useRef6(/* @__PURE__ */ new Set());
|
|
@@ -13305,7 +13693,7 @@ function App({
|
|
|
13305
13693
|
};
|
|
13306
13694
|
}, []);
|
|
13307
13695
|
const loopRef = useRef6(null);
|
|
13308
|
-
const
|
|
13696
|
+
const loop2 = useMemo3(() => {
|
|
13309
13697
|
if (loopRef.current) return loopRef.current;
|
|
13310
13698
|
const client = new DeepSeekClient();
|
|
13311
13699
|
if (tools && !tools.has("run_skill")) {
|
|
@@ -13354,8 +13742,8 @@ function App({
|
|
|
13354
13742
|
return l;
|
|
13355
13743
|
}, [model2, system, harvest3, branch2, session, tools, codeMode]);
|
|
13356
13744
|
useEffect6(() => {
|
|
13357
|
-
|
|
13358
|
-
}, [
|
|
13745
|
+
loop2.hooks = hookList;
|
|
13746
|
+
}, [loop2, hookList]);
|
|
13359
13747
|
const {
|
|
13360
13748
|
balance,
|
|
13361
13749
|
models: models2,
|
|
@@ -13364,7 +13752,7 @@ function App({
|
|
|
13364
13752
|
refreshBalance,
|
|
13365
13753
|
refreshModels,
|
|
13366
13754
|
refreshLatestVersion
|
|
13367
|
-
} = useSessionInfo(
|
|
13755
|
+
} = useSessionInfo(loop2);
|
|
13368
13756
|
const {
|
|
13369
13757
|
slashMatches,
|
|
13370
13758
|
slashSelected,
|
|
@@ -13407,13 +13795,13 @@ function App({
|
|
|
13407
13795
|
text: "\u25B8 ephemeral chat (no session persistence) \u2014 drop --no-session to enable"
|
|
13408
13796
|
}
|
|
13409
13797
|
]);
|
|
13410
|
-
} else if (
|
|
13798
|
+
} else if (loop2.resumedMessageCount > 0) {
|
|
13411
13799
|
setHistorical((prev) => [
|
|
13412
13800
|
...prev,
|
|
13413
13801
|
{
|
|
13414
13802
|
id: `sys-resume-${Date.now()}`,
|
|
13415
13803
|
role: "info",
|
|
13416
|
-
text: `\u25B8 resumed session "${session}" with ${
|
|
13804
|
+
text: `\u25B8 resumed session "${session}" with ${loop2.resumedMessageCount} prior messages \xB7 /forget to start over \xB7 /sessions to list`
|
|
13417
13805
|
}
|
|
13418
13806
|
]);
|
|
13419
13807
|
} else {
|
|
@@ -13476,7 +13864,7 @@ function App({
|
|
|
13476
13864
|
]);
|
|
13477
13865
|
markEditModeHintShown();
|
|
13478
13866
|
}
|
|
13479
|
-
}, [session,
|
|
13867
|
+
}, [session, loop2, codeMode, syncPendingCount]);
|
|
13480
13868
|
const quitProcess = useCallback4(() => {
|
|
13481
13869
|
transcriptRef.current?.end();
|
|
13482
13870
|
process.exit(0);
|
|
@@ -13506,25 +13894,40 @@ function App({
|
|
|
13506
13894
|
setPendingEditReview(null);
|
|
13507
13895
|
resolve8("reject");
|
|
13508
13896
|
}
|
|
13509
|
-
|
|
13897
|
+
if (activeLoopRef.current) stopLoop();
|
|
13898
|
+
loop2.abort();
|
|
13510
13899
|
return;
|
|
13511
13900
|
}
|
|
13512
|
-
if (
|
|
13901
|
+
if (key.escape && !busy && activeLoopRef.current) {
|
|
13902
|
+
stopLoop();
|
|
13903
|
+
return;
|
|
13904
|
+
}
|
|
13905
|
+
if (key.escape && walkthroughActive) {
|
|
13906
|
+
setWalkthroughActive(false);
|
|
13907
|
+
const remaining = pendingEdits.current.length;
|
|
13908
|
+
setHistorical((prev) => [
|
|
13909
|
+
...prev,
|
|
13910
|
+
{
|
|
13911
|
+
id: `walk-esc-${Date.now()}`,
|
|
13912
|
+
role: "info",
|
|
13913
|
+
text: remaining > 0 ? `\u25B8 walk cancelled \u2014 ${remaining} block(s) still pending.` : "\u25B8 walk cancelled."
|
|
13914
|
+
}
|
|
13915
|
+
]);
|
|
13916
|
+
return;
|
|
13917
|
+
}
|
|
13918
|
+
if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
|
|
13513
13919
|
setEditMode((m) => {
|
|
13514
|
-
const next = m === "
|
|
13920
|
+
const next = m === "review" ? "auto" : m === "auto" ? "yolo" : "review";
|
|
13921
|
+
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)";
|
|
13515
13922
|
setHistorical((prev) => [
|
|
13516
13923
|
...prev,
|
|
13517
|
-
{
|
|
13518
|
-
id: `mode-${Date.now()}`,
|
|
13519
|
-
role: "info",
|
|
13520
|
-
text: next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo" : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)"
|
|
13521
|
-
}
|
|
13924
|
+
{ id: `mode-${Date.now()}`, role: "info", text: message }
|
|
13522
13925
|
]);
|
|
13523
13926
|
return next;
|
|
13524
13927
|
});
|
|
13525
13928
|
return;
|
|
13526
13929
|
}
|
|
13527
|
-
if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
|
|
13930
|
+
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
|
|
13528
13931
|
// history entry — the keybind is useful long after the 5-second
|
|
13529
13932
|
// banner expires, which users rightly want.
|
|
13530
13933
|
(undoBanner || hasUndoable())) {
|
|
@@ -13625,7 +14028,7 @@ function App({
|
|
|
13625
14028
|
}
|
|
13626
14029
|
return formatEditResults(results);
|
|
13627
14030
|
};
|
|
13628
|
-
if (editModeRef.current === "auto") return applyNow();
|
|
14031
|
+
if (editModeRef.current === "auto" || editModeRef.current === "yolo") return applyNow();
|
|
13629
14032
|
if (turnEditPolicyRef.current === "apply-all") return applyNow();
|
|
13630
14033
|
const choice = await new Promise((resolveChoice) => {
|
|
13631
14034
|
editReviewResolveRef.current = resolveChoice;
|
|
@@ -13674,30 +14077,51 @@ function App({
|
|
|
13674
14077
|
tools.setToolInterceptor(null);
|
|
13675
14078
|
};
|
|
13676
14079
|
}, [tools, codeMode, session, recordEdit, armUndoBanner, syncPendingCount, setEditMode]);
|
|
13677
|
-
const codeApply = useCallback4(
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
14080
|
+
const codeApply = useCallback4(
|
|
14081
|
+
(indices) => {
|
|
14082
|
+
if (!codeMode) return "not in code mode";
|
|
14083
|
+
const blocks = pendingEdits.current;
|
|
14084
|
+
if (blocks.length === 0) {
|
|
14085
|
+
return "nothing pending \u2014 the model hasn't proposed edits since the last /apply or /discard.";
|
|
14086
|
+
}
|
|
14087
|
+
const useSubset = indices !== void 0 && indices.length > 0;
|
|
14088
|
+
const { selected, remaining } = useSubset ? partitionEdits(blocks, indices) : { selected: blocks, remaining: [] };
|
|
14089
|
+
if (selected.length === 0) {
|
|
14090
|
+
return "\u25B8 no edits matched those indices \u2014 nothing applied. Use /apply with no args to commit them all.";
|
|
14091
|
+
}
|
|
14092
|
+
const snaps = snapshotBeforeEdits(selected, codeMode.rootDir);
|
|
14093
|
+
const results = applyEditBlocks(selected, codeMode.rootDir);
|
|
14094
|
+
const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
|
|
14095
|
+
if (anyApplied) recordEdit("review-apply", selected, results, snaps);
|
|
14096
|
+
pendingEdits.current = remaining;
|
|
14097
|
+
if (remaining.length === 0) clearPendingEdits(session ?? null);
|
|
14098
|
+
else savePendingEdits(session ?? null, remaining);
|
|
14099
|
+
syncPendingCount();
|
|
14100
|
+
const tail = remaining.length > 0 ? `
|
|
14101
|
+
\u25B8 ${remaining.length} edit block(s) still pending \u2014 /apply or /discard to clear them.` : "";
|
|
14102
|
+
return formatEditResults(results) + tail;
|
|
14103
|
+
},
|
|
14104
|
+
[codeMode, session, syncPendingCount, recordEdit]
|
|
14105
|
+
);
|
|
14106
|
+
const codeDiscard = useCallback4(
|
|
14107
|
+
(indices) => {
|
|
14108
|
+
const blocks = pendingEdits.current;
|
|
14109
|
+
if (blocks.length === 0) return "nothing pending to discard.";
|
|
14110
|
+
const useSubset = indices !== void 0 && indices.length > 0;
|
|
14111
|
+
const { selected, remaining } = useSubset ? partitionEdits(blocks, indices) : { selected: blocks, remaining: [] };
|
|
14112
|
+
if (selected.length === 0) {
|
|
14113
|
+
return "\u25B8 no edits matched those indices \u2014 nothing discarded.";
|
|
14114
|
+
}
|
|
14115
|
+
pendingEdits.current = remaining;
|
|
14116
|
+
if (remaining.length === 0) clearPendingEdits(session ?? null);
|
|
14117
|
+
else savePendingEdits(session ?? null, remaining);
|
|
14118
|
+
syncPendingCount();
|
|
14119
|
+
const tail = remaining.length > 0 ? ` (${remaining.length} block(s) still pending)` : ". Nothing was written to disk.";
|
|
14120
|
+
return `\u25B8 discarded ${selected.length} pending edit block(s)${tail}`;
|
|
14121
|
+
},
|
|
14122
|
+
[session, syncPendingCount]
|
|
14123
|
+
);
|
|
14124
|
+
const prefixHash = loop2.prefix.fingerprint;
|
|
13701
14125
|
const writeTranscript = useCallback4(
|
|
13702
14126
|
(ev) => {
|
|
13703
14127
|
const stream = transcriptRef.current;
|
|
@@ -13716,10 +14140,98 @@ function App({
|
|
|
13716
14140
|
const clearPendingPlan = useCallback4(() => {
|
|
13717
14141
|
setPendingPlan(null);
|
|
13718
14142
|
}, []);
|
|
14143
|
+
const stopLoop = useCallback4(() => {
|
|
14144
|
+
if (loopTimerRef.current) {
|
|
14145
|
+
clearTimeout(loopTimerRef.current);
|
|
14146
|
+
loopTimerRef.current = null;
|
|
14147
|
+
}
|
|
14148
|
+
setActiveLoop((cur) => {
|
|
14149
|
+
if (!cur) return cur;
|
|
14150
|
+
setHistorical((prev) => [
|
|
14151
|
+
...prev,
|
|
14152
|
+
{
|
|
14153
|
+
id: `loop-stop-${Date.now()}`,
|
|
14154
|
+
role: "info",
|
|
14155
|
+
text: `\u25B8 loop stopped (after ${cur.iter} iter${cur.iter === 1 ? "" : "s"}).`
|
|
14156
|
+
}
|
|
14157
|
+
]);
|
|
14158
|
+
return null;
|
|
14159
|
+
});
|
|
14160
|
+
}, []);
|
|
14161
|
+
const startLoop = useCallback4((intervalMs, prompt) => {
|
|
14162
|
+
if (loopTimerRef.current) {
|
|
14163
|
+
clearTimeout(loopTimerRef.current);
|
|
14164
|
+
loopTimerRef.current = null;
|
|
14165
|
+
}
|
|
14166
|
+
setActiveLoop({
|
|
14167
|
+
prompt,
|
|
14168
|
+
intervalMs,
|
|
14169
|
+
nextFireAt: Date.now() + intervalMs,
|
|
14170
|
+
iter: 0
|
|
14171
|
+
});
|
|
14172
|
+
}, []);
|
|
14173
|
+
const startWalkthrough = useCallback4(() => {
|
|
14174
|
+
if (!codeMode) {
|
|
14175
|
+
return "/walk is only available inside `reasonix code`.";
|
|
14176
|
+
}
|
|
14177
|
+
if (pendingEdits.current.length === 0) {
|
|
14178
|
+
return "nothing pending \u2014 nothing to walk through.";
|
|
14179
|
+
}
|
|
14180
|
+
setWalkthroughActive(true);
|
|
14181
|
+
return `\u25B8 walking ${pendingEdits.current.length} edit block(s) \u2014 y apply \xB7 n reject \xB7 a apply rest \xB7 A flip to AUTO \xB7 Esc cancels (keeps remaining queued).`;
|
|
14182
|
+
}, [codeMode]);
|
|
14183
|
+
const handleWalkChoice = useCallback4(
|
|
14184
|
+
(choice) => {
|
|
14185
|
+
if (choice === "apply") {
|
|
14186
|
+
const out = codeApply([1]);
|
|
14187
|
+
setHistorical((prev) => [...prev, { id: `walk-${Date.now()}`, role: "info", text: out }]);
|
|
14188
|
+
} else if (choice === "reject") {
|
|
14189
|
+
const out = codeDiscard([1]);
|
|
14190
|
+
setHistorical((prev) => [...prev, { id: `walk-${Date.now()}`, role: "info", text: out }]);
|
|
14191
|
+
} else if (choice === "apply-rest-of-turn") {
|
|
14192
|
+
const out = codeApply();
|
|
14193
|
+
setHistorical((prev) => [...prev, { id: `walk-${Date.now()}`, role: "info", text: out }]);
|
|
14194
|
+
setWalkthroughActive(false);
|
|
14195
|
+
return;
|
|
14196
|
+
} else if (choice === "flip-to-auto") {
|
|
14197
|
+
setEditMode("auto");
|
|
14198
|
+
saveEditMode("auto");
|
|
14199
|
+
const out = codeApply([1]);
|
|
14200
|
+
setHistorical((prev) => [
|
|
14201
|
+
...prev,
|
|
14202
|
+
{ id: `walk-${Date.now()}`, role: "info", text: out },
|
|
14203
|
+
{
|
|
14204
|
+
id: `walk-flip-${Date.now()}`,
|
|
14205
|
+
role: "info",
|
|
14206
|
+
text: "\u25B8 flipped to AUTO mode \u2014 future edits will apply immediately. Walk exited."
|
|
14207
|
+
}
|
|
14208
|
+
]);
|
|
14209
|
+
setWalkthroughActive(false);
|
|
14210
|
+
return;
|
|
14211
|
+
}
|
|
14212
|
+
if (pendingEdits.current.length === 0) setWalkthroughActive(false);
|
|
14213
|
+
},
|
|
14214
|
+
[codeApply, codeDiscard]
|
|
14215
|
+
);
|
|
14216
|
+
const getLoopStatus = useCallback4(() => {
|
|
14217
|
+
const cur = activeLoopRef.current;
|
|
14218
|
+
if (!cur) return null;
|
|
14219
|
+
return {
|
|
14220
|
+
prompt: cur.prompt,
|
|
14221
|
+
intervalMs: cur.intervalMs,
|
|
14222
|
+
iter: cur.iter,
|
|
14223
|
+
nextFireMs: Math.max(0, cur.nextFireAt - Date.now())
|
|
14224
|
+
};
|
|
14225
|
+
}, []);
|
|
13719
14226
|
const handleSubmit = useCallback4(
|
|
13720
14227
|
async (raw) => {
|
|
13721
14228
|
let text = raw.trim();
|
|
13722
|
-
if (!text
|
|
14229
|
+
if (!text) return;
|
|
14230
|
+
if (activeLoopRef.current && !loopFiringRef.current) {
|
|
14231
|
+
stopLoop();
|
|
14232
|
+
}
|
|
14233
|
+
loopFiringRef.current = false;
|
|
14234
|
+
if (busy) return;
|
|
13723
14235
|
if (atMatches && atMatches.length > 0 && atPicker) {
|
|
13724
14236
|
const sel = atMatches[atSelected] ?? atMatches[0];
|
|
13725
14237
|
if (sel) {
|
|
@@ -13751,6 +14263,38 @@ function App({
|
|
|
13751
14263
|
promptHistory.current.push(text);
|
|
13752
14264
|
return;
|
|
13753
14265
|
}
|
|
14266
|
+
const hashParse = detectHashMemory(text);
|
|
14267
|
+
if (hashParse?.kind === "memory" || hashParse?.kind === "memory-global") {
|
|
14268
|
+
const isGlobal = hashParse.kind === "memory-global";
|
|
14269
|
+
const memRoot = codeMode?.rootDir ?? process.cwd();
|
|
14270
|
+
promptHistory.current.push(text);
|
|
14271
|
+
try {
|
|
14272
|
+
const result = isGlobal ? appendGlobalMemory(hashParse.note) : appendProjectMemory(memRoot, hashParse.note);
|
|
14273
|
+
const verb = result.created ? "created" : "appended to";
|
|
14274
|
+
const scopeTag = isGlobal ? "global" : "project";
|
|
14275
|
+
setHistorical((prev) => [
|
|
14276
|
+
...prev,
|
|
14277
|
+
{
|
|
14278
|
+
id: `hash-${Date.now()}`,
|
|
14279
|
+
role: "info",
|
|
14280
|
+
text: `\u25B8 noted (${scopeTag}) \u2014 ${verb} ${result.path}`
|
|
14281
|
+
}
|
|
14282
|
+
]);
|
|
14283
|
+
} catch (err) {
|
|
14284
|
+
setHistorical((prev) => [
|
|
14285
|
+
...prev,
|
|
14286
|
+
{
|
|
14287
|
+
id: `hash-e-${Date.now()}`,
|
|
14288
|
+
role: "warning",
|
|
14289
|
+
text: `# memory write failed: ${err.message}`
|
|
14290
|
+
}
|
|
14291
|
+
]);
|
|
14292
|
+
}
|
|
14293
|
+
return;
|
|
14294
|
+
}
|
|
14295
|
+
if (hashParse?.kind === "escape") {
|
|
14296
|
+
text = hashParse.text;
|
|
14297
|
+
}
|
|
13754
14298
|
const bangCmd = detectBangCommand(text);
|
|
13755
14299
|
if (bangCmd !== null) {
|
|
13756
14300
|
const bangRoot = codeMode?.rootDir ?? process.cwd();
|
|
@@ -13776,7 +14320,7 @@ function App({
|
|
|
13776
14320
|
...prev,
|
|
13777
14321
|
{ id: `bang-o-${Date.now()}`, role: "info", text: formatted }
|
|
13778
14322
|
]);
|
|
13779
|
-
|
|
14323
|
+
loop2.appendAndPersist({
|
|
13780
14324
|
role: "user",
|
|
13781
14325
|
content: formatBangUserMessage(bangCmd, formatted)
|
|
13782
14326
|
});
|
|
@@ -13808,7 +14352,7 @@ function App({
|
|
|
13808
14352
|
}
|
|
13809
14353
|
const slash = parseSlash(text);
|
|
13810
14354
|
if (slash) {
|
|
13811
|
-
const result = handleSlash(slash.cmd, slash.args,
|
|
14355
|
+
const result = handleSlash(slash.cmd, slash.args, loop2, {
|
|
13812
14356
|
mcpSpecs,
|
|
13813
14357
|
mcpServers,
|
|
13814
14358
|
codeUndo: codeMode ? codeUndo : void 0,
|
|
@@ -13826,13 +14370,17 @@ function App({
|
|
|
13826
14370
|
editMode: codeMode ? editMode : void 0,
|
|
13827
14371
|
setEditMode: codeMode ? setEditMode : void 0,
|
|
13828
14372
|
armPro: () => {
|
|
13829
|
-
|
|
14373
|
+
loop2.armProForNextTurn();
|
|
13830
14374
|
setProArmed(true);
|
|
13831
14375
|
},
|
|
13832
14376
|
disarmPro: () => {
|
|
13833
|
-
|
|
14377
|
+
loop2.disarmPro();
|
|
13834
14378
|
setProArmed(false);
|
|
13835
14379
|
},
|
|
14380
|
+
startLoop,
|
|
14381
|
+
stopLoop,
|
|
14382
|
+
getLoopStatus,
|
|
14383
|
+
startWalkthrough: codeMode ? startWalkthrough : void 0,
|
|
13836
14384
|
jobs: codeMode?.jobs,
|
|
13837
14385
|
postInfo: (text2) => setHistorical((prev) => [
|
|
13838
14386
|
...prev,
|
|
@@ -13849,6 +14397,7 @@ function App({
|
|
|
13849
14397
|
refreshModels
|
|
13850
14398
|
});
|
|
13851
14399
|
if (result.exit) {
|
|
14400
|
+
if (activeLoopRef.current) stopLoop();
|
|
13852
14401
|
transcriptRef.current?.end();
|
|
13853
14402
|
exit2();
|
|
13854
14403
|
return;
|
|
@@ -13867,6 +14416,7 @@ function App({
|
|
|
13867
14416
|
clearPendingEdits(session ?? null);
|
|
13868
14417
|
syncPendingCount();
|
|
13869
14418
|
}
|
|
14419
|
+
if (activeLoopRef.current) stopLoop();
|
|
13870
14420
|
return;
|
|
13871
14421
|
}
|
|
13872
14422
|
if (result.clear) {
|
|
@@ -13877,6 +14427,7 @@ function App({
|
|
|
13877
14427
|
clearPendingEdits(session ?? null);
|
|
13878
14428
|
syncPendingCount();
|
|
13879
14429
|
}
|
|
14430
|
+
if (activeLoopRef.current) stopLoop();
|
|
13880
14431
|
return;
|
|
13881
14432
|
}
|
|
13882
14433
|
if (result.info) {
|
|
@@ -14005,8 +14556,47 @@ function App({
|
|
|
14005
14556
|
}
|
|
14006
14557
|
}
|
|
14007
14558
|
}
|
|
14559
|
+
if (/(?:^|\s)@https?:\/\//.test(text)) {
|
|
14560
|
+
try {
|
|
14561
|
+
const urlExpanded = await expandAtUrls(modelInput, {
|
|
14562
|
+
fetcher: webFetch,
|
|
14563
|
+
cache: atUrlCache.current
|
|
14564
|
+
});
|
|
14565
|
+
if (urlExpanded.expansions.length > 0) {
|
|
14566
|
+
modelInput = urlExpanded.text;
|
|
14567
|
+
const inlined = urlExpanded.expansions.filter((ex) => ex.ok).map((ex) => {
|
|
14568
|
+
const tag = ex.title ? `${ex.title} (${ex.url})` : ex.url;
|
|
14569
|
+
const trunc = ex.truncated ? " \xB7 truncated" : "";
|
|
14570
|
+
return `${tag} \xB7 ${(ex.chars ?? 0).toLocaleString()} chars${trunc}`;
|
|
14571
|
+
});
|
|
14572
|
+
const skipped = urlExpanded.expansions.filter((ex) => !ex.ok).map((ex) => `${ex.url} (${ex.skip ?? "fetch-error"})`);
|
|
14573
|
+
const parts = [];
|
|
14574
|
+
if (inlined.length > 0) parts.push(`inlined ${inlined.join("; ")}`);
|
|
14575
|
+
if (skipped.length > 0) parts.push(`skipped ${skipped.join("; ")}`);
|
|
14576
|
+
if (parts.length > 0) {
|
|
14577
|
+
setHistorical((prev) => [
|
|
14578
|
+
...prev,
|
|
14579
|
+
{
|
|
14580
|
+
id: `aturl-${Date.now()}`,
|
|
14581
|
+
role: "info",
|
|
14582
|
+
text: `\u25B8 @url: ${parts.join("; ")}`
|
|
14583
|
+
}
|
|
14584
|
+
]);
|
|
14585
|
+
}
|
|
14586
|
+
}
|
|
14587
|
+
} catch (err) {
|
|
14588
|
+
setHistorical((prev) => [
|
|
14589
|
+
...prev,
|
|
14590
|
+
{
|
|
14591
|
+
id: `aturl-e-${Date.now()}`,
|
|
14592
|
+
role: "warning",
|
|
14593
|
+
text: `@url expansion failed: ${err.message}`
|
|
14594
|
+
}
|
|
14595
|
+
]);
|
|
14596
|
+
}
|
|
14597
|
+
}
|
|
14008
14598
|
try {
|
|
14009
|
-
for await (const ev of
|
|
14599
|
+
for await (const ev of loop2.step(modelInput)) {
|
|
14010
14600
|
writeTranscript(ev);
|
|
14011
14601
|
if (ev.role !== "status") {
|
|
14012
14602
|
setStatusLine((cur) => cur ? null : cur);
|
|
@@ -14046,7 +14636,7 @@ function App({
|
|
|
14046
14636
|
flush();
|
|
14047
14637
|
const repairNote = ev.repair ? describeRepair(ev.repair) : "";
|
|
14048
14638
|
setStreaming(null);
|
|
14049
|
-
setSummary(
|
|
14639
|
+
setSummary(loop2.stats.summary());
|
|
14050
14640
|
if (ev.stats?.usage) {
|
|
14051
14641
|
appendUsage({
|
|
14052
14642
|
session: session ?? null,
|
|
@@ -14080,7 +14670,7 @@ function App({
|
|
|
14080
14670
|
if (codeMode && finalText && !ev.forcedSummary) {
|
|
14081
14671
|
const blocks = parseEditBlocks(finalText);
|
|
14082
14672
|
if (blocks.length > 0) {
|
|
14083
|
-
if (editModeRef.current === "auto") {
|
|
14673
|
+
if (editModeRef.current === "auto" || editModeRef.current === "yolo") {
|
|
14084
14674
|
const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
|
|
14085
14675
|
const results = applyEditBlocks(blocks, codeMode.rootDir);
|
|
14086
14676
|
const good = results.some(
|
|
@@ -14292,7 +14882,7 @@ function App({
|
|
|
14292
14882
|
event: "Stop",
|
|
14293
14883
|
cwd: hookCwd,
|
|
14294
14884
|
lastAssistantText: streamRef.text,
|
|
14295
|
-
turn:
|
|
14885
|
+
turn: loop2.stats.summary().turns
|
|
14296
14886
|
}
|
|
14297
14887
|
});
|
|
14298
14888
|
for (const o of stopReport.outcomes) {
|
|
@@ -14313,7 +14903,7 @@ function App({
|
|
|
14313
14903
|
setOngoingTool(null);
|
|
14314
14904
|
setToolProgress(null);
|
|
14315
14905
|
setStatusLine(null);
|
|
14316
|
-
setSummary(
|
|
14906
|
+
setSummary(loop2.stats.summary());
|
|
14317
14907
|
setBusy(false);
|
|
14318
14908
|
setTurnOnPro(false);
|
|
14319
14909
|
refreshBalance();
|
|
@@ -14331,7 +14921,7 @@ function App({
|
|
|
14331
14921
|
exit2,
|
|
14332
14922
|
hookCwd,
|
|
14333
14923
|
hookList,
|
|
14334
|
-
|
|
14924
|
+
loop2,
|
|
14335
14925
|
latestVersion,
|
|
14336
14926
|
mcpSpecs,
|
|
14337
14927
|
mcpServers,
|
|
@@ -14360,9 +14950,51 @@ function App({
|
|
|
14360
14950
|
refreshModels,
|
|
14361
14951
|
proArmed,
|
|
14362
14952
|
persistPlanState,
|
|
14363
|
-
stdout2
|
|
14953
|
+
stdout2,
|
|
14954
|
+
stopLoop,
|
|
14955
|
+
startLoop,
|
|
14956
|
+
getLoopStatus,
|
|
14957
|
+
startWalkthrough
|
|
14364
14958
|
]
|
|
14365
14959
|
);
|
|
14960
|
+
useEffect6(() => {
|
|
14961
|
+
handleSubmitRef.current = handleSubmit;
|
|
14962
|
+
}, [handleSubmit]);
|
|
14963
|
+
useEffect6(() => {
|
|
14964
|
+
if (!activeLoop) return;
|
|
14965
|
+
const delay = Math.max(0, activeLoop.nextFireAt - Date.now());
|
|
14966
|
+
const timer = setTimeout(async () => {
|
|
14967
|
+
loopTimerRef.current = null;
|
|
14968
|
+
if (busyRef.current) {
|
|
14969
|
+
setActiveLoop((cur2) => cur2 ? { ...cur2, nextFireAt: Date.now() + 1e3 } : cur2);
|
|
14970
|
+
return;
|
|
14971
|
+
}
|
|
14972
|
+
const cur = activeLoopRef.current;
|
|
14973
|
+
if (!cur) return;
|
|
14974
|
+
const nextIter = cur.iter + 1;
|
|
14975
|
+
setActiveLoop(
|
|
14976
|
+
(c) => c ? { ...c, iter: nextIter, nextFireAt: Date.now() + cur.intervalMs } : c
|
|
14977
|
+
);
|
|
14978
|
+
setHistorical((prev) => [
|
|
14979
|
+
...prev,
|
|
14980
|
+
{
|
|
14981
|
+
id: `loop-fire-${Date.now()}`,
|
|
14982
|
+
role: "info",
|
|
14983
|
+
text: `\u25B8 /loop iter ${nextIter} \u2192 ${cur.prompt}`
|
|
14984
|
+
}
|
|
14985
|
+
]);
|
|
14986
|
+
loopFiringRef.current = true;
|
|
14987
|
+
try {
|
|
14988
|
+
await handleSubmitRef.current?.(cur.prompt);
|
|
14989
|
+
} catch {
|
|
14990
|
+
stopLoop();
|
|
14991
|
+
} finally {
|
|
14992
|
+
loopFiringRef.current = false;
|
|
14993
|
+
}
|
|
14994
|
+
}, delay);
|
|
14995
|
+
loopTimerRef.current = timer;
|
|
14996
|
+
return () => clearTimeout(timer);
|
|
14997
|
+
}, [activeLoop, stopLoop]);
|
|
14366
14998
|
const handleShellConfirm = useCallback4(
|
|
14367
14999
|
async (choice) => {
|
|
14368
15000
|
const pending = pendingShell;
|
|
@@ -14452,13 +15084,13 @@ ${body}`;
|
|
|
14452
15084
|
}
|
|
14453
15085
|
}
|
|
14454
15086
|
if (busy) {
|
|
14455
|
-
|
|
15087
|
+
loop2.abort();
|
|
14456
15088
|
setQueuedSubmit(synthetic);
|
|
14457
15089
|
} else {
|
|
14458
15090
|
await handleSubmit(synthetic);
|
|
14459
15091
|
}
|
|
14460
15092
|
},
|
|
14461
|
-
[pendingShell, codeMode, handleSubmit, busy,
|
|
15093
|
+
[pendingShell, codeMode, handleSubmit, busy, loop2]
|
|
14462
15094
|
);
|
|
14463
15095
|
useEffect6(() => {
|
|
14464
15096
|
if (!busy && queuedSubmit !== null) {
|
|
@@ -14496,13 +15128,13 @@ ${body}`;
|
|
|
14496
15128
|
{ id: `plan-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
14497
15129
|
]);
|
|
14498
15130
|
if (busy) {
|
|
14499
|
-
|
|
15131
|
+
loop2.abort();
|
|
14500
15132
|
setQueuedSubmit(synthetic);
|
|
14501
15133
|
} else {
|
|
14502
15134
|
await handleSubmit(synthetic);
|
|
14503
15135
|
}
|
|
14504
15136
|
},
|
|
14505
|
-
[pendingPlan, togglePlanMode, busy,
|
|
15137
|
+
[pendingPlan, togglePlanMode, busy, loop2, handleSubmit, persistPlanState]
|
|
14506
15138
|
);
|
|
14507
15139
|
const handlePlanConfirmRef = useRef6(handlePlanConfirm);
|
|
14508
15140
|
useEffect6(() => {
|
|
@@ -14553,13 +15185,13 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
14553
15185
|
{ id: `plan-${staged.mode}-${Date.now()}`, role: "info", text: marker }
|
|
14554
15186
|
]);
|
|
14555
15187
|
if (busy) {
|
|
14556
|
-
|
|
15188
|
+
loop2.abort();
|
|
14557
15189
|
setQueuedSubmit(synthetic);
|
|
14558
15190
|
} else {
|
|
14559
15191
|
await handleSubmit(synthetic);
|
|
14560
15192
|
}
|
|
14561
15193
|
},
|
|
14562
|
-
[stagedInput, togglePlanMode, busy,
|
|
15194
|
+
[stagedInput, togglePlanMode, busy, loop2, handleSubmit]
|
|
14563
15195
|
);
|
|
14564
15196
|
const handleStagedInputCancel = useCallback4(() => {
|
|
14565
15197
|
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
@@ -14588,13 +15220,13 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
14588
15220
|
{ id: `cp-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
14589
15221
|
]);
|
|
14590
15222
|
if (busy) {
|
|
14591
|
-
|
|
15223
|
+
loop2.abort();
|
|
14592
15224
|
setQueuedSubmit(synthetic);
|
|
14593
15225
|
} else {
|
|
14594
15226
|
await handleSubmit(synthetic);
|
|
14595
15227
|
}
|
|
14596
15228
|
},
|
|
14597
|
-
[pendingCheckpoint, busy,
|
|
15229
|
+
[pendingCheckpoint, busy, loop2, handleSubmit]
|
|
14598
15230
|
);
|
|
14599
15231
|
const handleCheckpointConfirmRef = useRef6(handleCheckpointConfirm);
|
|
14600
15232
|
useEffect6(() => {
|
|
@@ -14622,13 +15254,13 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
14622
15254
|
{ id: `cp-revise-${Date.now()}`, role: "info", text: marker }
|
|
14623
15255
|
]);
|
|
14624
15256
|
if (busy) {
|
|
14625
|
-
|
|
15257
|
+
loop2.abort();
|
|
14626
15258
|
setQueuedSubmit(synthetic);
|
|
14627
15259
|
} else {
|
|
14628
15260
|
await handleSubmit(synthetic);
|
|
14629
15261
|
}
|
|
14630
15262
|
},
|
|
14631
|
-
[stagedCheckpointRevise, busy,
|
|
15263
|
+
[stagedCheckpointRevise, busy, loop2, handleSubmit]
|
|
14632
15264
|
);
|
|
14633
15265
|
const handleCheckpointReviseCancel = useCallback4(() => {
|
|
14634
15266
|
const snap = stagedCheckpointRevise;
|
|
@@ -14651,7 +15283,7 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
14651
15283
|
{ id: `choice-cancel-${Date.now()}`, role: "info", text: "\u25B8 choice cancelled" }
|
|
14652
15284
|
]);
|
|
14653
15285
|
if (busy) {
|
|
14654
|
-
|
|
15286
|
+
loop2.abort();
|
|
14655
15287
|
setQueuedSubmit(synthetic2);
|
|
14656
15288
|
} else {
|
|
14657
15289
|
await handleSubmit(synthetic2);
|
|
@@ -14666,13 +15298,13 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
14666
15298
|
{ id: `choice-pick-${Date.now()}`, role: "info", text: `\u25B8 chose ${label}` }
|
|
14667
15299
|
]);
|
|
14668
15300
|
if (busy) {
|
|
14669
|
-
|
|
15301
|
+
loop2.abort();
|
|
14670
15302
|
setQueuedSubmit(synthetic);
|
|
14671
15303
|
} else {
|
|
14672
15304
|
await handleSubmit(synthetic);
|
|
14673
15305
|
}
|
|
14674
15306
|
},
|
|
14675
|
-
[pendingChoice, busy,
|
|
15307
|
+
[pendingChoice, busy, loop2, handleSubmit]
|
|
14676
15308
|
);
|
|
14677
15309
|
const handleChoiceConfirmRef = useRef6(handleChoiceConfirm);
|
|
14678
15310
|
useEffect6(() => {
|
|
@@ -14697,13 +15329,13 @@ Read it carefully and proceed \u2014 don't snap back to the options you listed u
|
|
|
14697
15329
|
{ id: `choice-custom-${Date.now()}`, role: "info", text: marker }
|
|
14698
15330
|
]);
|
|
14699
15331
|
if (busy) {
|
|
14700
|
-
|
|
15332
|
+
loop2.abort();
|
|
14701
15333
|
setQueuedSubmit(synthetic);
|
|
14702
15334
|
} else {
|
|
14703
15335
|
await handleSubmit(synthetic);
|
|
14704
15336
|
}
|
|
14705
15337
|
},
|
|
14706
|
-
[busy,
|
|
15338
|
+
[busy, loop2, handleSubmit]
|
|
14707
15339
|
);
|
|
14708
15340
|
const handleChoiceCustomCancel = useCallback4(() => {
|
|
14709
15341
|
const snap = stagedChoiceCustom;
|
|
@@ -14722,7 +15354,7 @@ Read it carefully and proceed \u2014 don't snap back to the options you listed u
|
|
|
14722
15354
|
{ id: `revise-reject-${Date.now()}`, role: "info", text: "\u25B8 revision rejected" }
|
|
14723
15355
|
]);
|
|
14724
15356
|
if (busy) {
|
|
14725
|
-
|
|
15357
|
+
loop2.abort();
|
|
14726
15358
|
setQueuedSubmit(synthetic2);
|
|
14727
15359
|
} else {
|
|
14728
15360
|
await handleSubmit(synthetic2);
|
|
@@ -14757,13 +15389,13 @@ ${snap.remainingSteps.map((s, i) => ` ${i + 1}. ${s.id} \xB7 ${s.title} \u2014
|
|
|
14757
15389
|
|
|
14758
15390
|
Continue executing from the next pending step. Call mark_step_complete after each one as before.`;
|
|
14759
15391
|
if (busy) {
|
|
14760
|
-
|
|
15392
|
+
loop2.abort();
|
|
14761
15393
|
setQueuedSubmit(synthetic);
|
|
14762
15394
|
} else {
|
|
14763
15395
|
await handleSubmit(synthetic);
|
|
14764
15396
|
}
|
|
14765
15397
|
},
|
|
14766
|
-
[pendingRevision, busy,
|
|
15398
|
+
[pendingRevision, busy, loop2, handleSubmit, persistPlanState]
|
|
14767
15399
|
);
|
|
14768
15400
|
const handleReviseConfirmRef = useRef6(handleReviseConfirm);
|
|
14769
15401
|
useEffect6(() => {
|
|
@@ -14776,17 +15408,17 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
14776
15408
|
return /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(
|
|
14777
15409
|
TickerProvider,
|
|
14778
15410
|
{
|
|
14779
|
-
disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingEditReview || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
|
|
15411
|
+
disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingEditReview || walkthroughActive || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
|
|
14780
15412
|
},
|
|
14781
15413
|
/* @__PURE__ */ React23.createElement(Box21, { flexDirection: "column" }, /* @__PURE__ */ React23.createElement(
|
|
14782
15414
|
StatsPanel,
|
|
14783
15415
|
{
|
|
14784
15416
|
summary,
|
|
14785
|
-
model:
|
|
15417
|
+
model: loop2.model,
|
|
14786
15418
|
prefixHash,
|
|
14787
|
-
harvestOn:
|
|
14788
|
-
branchBudget:
|
|
14789
|
-
reasoningEffort:
|
|
15419
|
+
harvestOn: loop2.harvestEnabled,
|
|
15420
|
+
branchBudget: loop2.branchOptions.budget,
|
|
15421
|
+
reasoningEffort: loop2.reasoningEffort,
|
|
14790
15422
|
planMode,
|
|
14791
15423
|
editMode: codeMode ? editMode : void 0,
|
|
14792
15424
|
balance,
|
|
@@ -14875,6 +15507,13 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
14875
15507
|
}
|
|
14876
15508
|
}
|
|
14877
15509
|
}
|
|
15510
|
+
) : walkthroughActive && pendingEdits.current.length > 0 ? /* @__PURE__ */ React23.createElement(
|
|
15511
|
+
EditConfirm,
|
|
15512
|
+
{
|
|
15513
|
+
key: `walk-${pendingTick}`,
|
|
15514
|
+
block: pendingEdits.current[0],
|
|
15515
|
+
onChoose: handleWalkChoice
|
|
15516
|
+
}
|
|
14878
15517
|
) : /* @__PURE__ */ React23.createElement(React23.Fragment, null, codeMode ? /* @__PURE__ */ React23.createElement(
|
|
14879
15518
|
ModeStatusBar,
|
|
14880
15519
|
{
|
|
@@ -14885,7 +15524,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
14885
15524
|
undoArmed: !!undoBanner || hasUndoable(),
|
|
14886
15525
|
jobs: codeMode.jobs
|
|
14887
15526
|
}
|
|
14888
|
-
) : null, /* @__PURE__ */ React23.createElement(
|
|
15527
|
+
) : null, activeLoop ? /* @__PURE__ */ React23.createElement(LoopStatusRow, { loop: activeLoop }) : null, /* @__PURE__ */ React23.createElement(
|
|
14889
15528
|
PromptInput,
|
|
14890
15529
|
{
|
|
14891
15530
|
value: input,
|
|
@@ -14916,7 +15555,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
14916
15555
|
}
|
|
14917
15556
|
|
|
14918
15557
|
// src/cli/ui/SessionPicker.tsx
|
|
14919
|
-
import { Box as Box22, Text as
|
|
15558
|
+
import { Box as Box22, Text as Text20 } from "ink";
|
|
14920
15559
|
import React24 from "react";
|
|
14921
15560
|
function SessionPicker({
|
|
14922
15561
|
sessionName,
|
|
@@ -14924,7 +15563,7 @@ function SessionPicker({
|
|
|
14924
15563
|
lastActive,
|
|
14925
15564
|
onChoose
|
|
14926
15565
|
}) {
|
|
14927
|
-
return /* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React24.createElement(Box22, { marginBottom: 1 }, /* @__PURE__ */ React24.createElement(
|
|
15566
|
+
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(
|
|
14928
15567
|
SingleSelect,
|
|
14929
15568
|
{
|
|
14930
15569
|
initialValue: "new",
|
|
@@ -14947,7 +15586,7 @@ function SessionPicker({
|
|
|
14947
15586
|
],
|
|
14948
15587
|
onSubmit: (v) => onChoose(v)
|
|
14949
15588
|
}
|
|
14950
|
-
), /* @__PURE__ */ React24.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(
|
|
15589
|
+
), /* @__PURE__ */ React24.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] select")));
|
|
14951
15590
|
}
|
|
14952
15591
|
function relativeTime2(date) {
|
|
14953
15592
|
const ms = Date.now() - date.getTime();
|
|
@@ -14963,7 +15602,7 @@ function relativeTime2(date) {
|
|
|
14963
15602
|
}
|
|
14964
15603
|
|
|
14965
15604
|
// src/cli/ui/Setup.tsx
|
|
14966
|
-
import { Box as Box23, Text as
|
|
15605
|
+
import { Box as Box23, Text as Text21, useApp as useApp2 } from "ink";
|
|
14967
15606
|
import TextInput from "ink-text-input";
|
|
14968
15607
|
import React25, { useState as useState11 } from "react";
|
|
14969
15608
|
function Setup({ onReady }) {
|
|
@@ -14989,7 +15628,7 @@ function Setup({ onReady }) {
|
|
|
14989
15628
|
}
|
|
14990
15629
|
onReady(trimmed);
|
|
14991
15630
|
};
|
|
14992
|
-
return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React25.createElement(
|
|
15631
|
+
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(
|
|
14993
15632
|
TextInput,
|
|
14994
15633
|
{
|
|
14995
15634
|
value,
|
|
@@ -14998,7 +15637,7 @@ function Setup({ onReady }) {
|
|
|
14998
15637
|
mask: "\u2022",
|
|
14999
15638
|
placeholder: "sk-..."
|
|
15000
15639
|
}
|
|
15001
|
-
)), error ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(
|
|
15640
|
+
)), 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.)")));
|
|
15002
15641
|
}
|
|
15003
15642
|
|
|
15004
15643
|
// src/cli/commands/chat.tsx
|
|
@@ -15139,7 +15778,7 @@ async function chatCommand(opts) {
|
|
|
15139
15778
|
const prior = loadSessionMessages(opts.session);
|
|
15140
15779
|
if (prior.length > 0) {
|
|
15141
15780
|
const p = sessionPath(opts.session);
|
|
15142
|
-
const mtime =
|
|
15781
|
+
const mtime = existsSync13(p) ? statSync7(p).mtime : /* @__PURE__ */ new Date();
|
|
15143
15782
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
15144
15783
|
}
|
|
15145
15784
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -15172,7 +15811,7 @@ async function chatCommand(opts) {
|
|
|
15172
15811
|
// src/cli/commands/code.tsx
|
|
15173
15812
|
import { basename as basename2, resolve as resolve7 } from "path";
|
|
15174
15813
|
async function codeCommand(opts = {}) {
|
|
15175
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
15814
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-LJ44NWSU.js");
|
|
15176
15815
|
const rootDir = resolve7(opts.dir ?? process.cwd());
|
|
15177
15816
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
15178
15817
|
const tools = new ToolRegistry();
|
|
@@ -15186,6 +15825,10 @@ async function codeCommand(opts = {}) {
|
|
|
15186
15825
|
// via ShellConfirm mid-session takes effect on the next shell call
|
|
15187
15826
|
// instead of waiting for `/new` or a relaunch.
|
|
15188
15827
|
extraAllowed: () => loadProjectShellAllowed(rootDir),
|
|
15828
|
+
// `yolo` edit-mode disables shell confirmations entirely. Re-read
|
|
15829
|
+
// from config on each dispatch so /mode yolo (or Shift+Tab cycling
|
|
15830
|
+
// through to it) flips the gate live without forcing a relaunch.
|
|
15831
|
+
allowAll: () => loadEditMode() === "yolo",
|
|
15189
15832
|
jobs: jobs2
|
|
15190
15833
|
});
|
|
15191
15834
|
registerPlanTool(tools);
|
|
@@ -15212,38 +15855,38 @@ async function codeCommand(opts = {}) {
|
|
|
15212
15855
|
}
|
|
15213
15856
|
|
|
15214
15857
|
// src/cli/commands/diff.ts
|
|
15215
|
-
import { writeFileSync as
|
|
15858
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
15216
15859
|
import { basename as basename3 } from "path";
|
|
15217
15860
|
import { render as render2 } from "ink";
|
|
15218
15861
|
import React29 from "react";
|
|
15219
15862
|
|
|
15220
15863
|
// src/cli/ui/DiffApp.tsx
|
|
15221
|
-
import { Box as Box25, Static as Static2, Text as
|
|
15864
|
+
import { Box as Box25, Static as Static2, Text as Text23, useApp as useApp3, useInput } from "ink";
|
|
15222
15865
|
import React28, { useState as useState13 } from "react";
|
|
15223
15866
|
|
|
15224
15867
|
// src/cli/ui/RecordView.tsx
|
|
15225
|
-
import { Box as Box24, Text as
|
|
15868
|
+
import { Box as Box24, Text as Text22 } from "ink";
|
|
15226
15869
|
import React27 from "react";
|
|
15227
15870
|
function RecordView({ rec, compact: compact2 = false }) {
|
|
15228
15871
|
const toolArgsMax = compact2 ? 120 : 200;
|
|
15229
15872
|
const toolContentMax = compact2 ? 200 : 400;
|
|
15230
15873
|
if (rec.role === "user") {
|
|
15231
15874
|
const content = rec.content.includes("\n") ? rec.content.split("\n").join("\n ") : rec.content;
|
|
15232
|
-
return /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(
|
|
15875
|
+
return /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React27.createElement(Text22, null, content));
|
|
15233
15876
|
}
|
|
15234
15877
|
if (rec.role === "assistant_final") {
|
|
15235
|
-
return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(
|
|
15878
|
+
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)"));
|
|
15236
15879
|
}
|
|
15237
15880
|
if (rec.role === "tool") {
|
|
15238
|
-
return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React27.createElement(
|
|
15881
|
+
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)));
|
|
15239
15882
|
}
|
|
15240
15883
|
if (rec.role === "error") {
|
|
15241
|
-
return /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(
|
|
15884
|
+
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));
|
|
15242
15885
|
}
|
|
15243
15886
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
15244
15887
|
return null;
|
|
15245
15888
|
}
|
|
15246
|
-
return /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(
|
|
15889
|
+
return /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
15247
15890
|
}
|
|
15248
15891
|
function CacheBadge({ usage }) {
|
|
15249
15892
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -15252,7 +15895,7 @@ function CacheBadge({ usage }) {
|
|
|
15252
15895
|
if (total === 0) return null;
|
|
15253
15896
|
const pct2 = hit / total * 100;
|
|
15254
15897
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
15255
|
-
return /* @__PURE__ */ React27.createElement(
|
|
15898
|
+
return /* @__PURE__ */ React27.createElement(Text22, null, /* @__PURE__ */ React27.createElement(Text22, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React27.createElement(Text22, { color }, pct2.toFixed(1), "%"));
|
|
15256
15899
|
}
|
|
15257
15900
|
function truncate2(s, max) {
|
|
15258
15901
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -15286,7 +15929,7 @@ function DiffApp({ report }) {
|
|
|
15286
15929
|
}
|
|
15287
15930
|
});
|
|
15288
15931
|
const pair = report.pairs[idx];
|
|
15289
|
-
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(
|
|
15932
|
+
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")));
|
|
15290
15933
|
}
|
|
15291
15934
|
function DiffHeader({ report }) {
|
|
15292
15935
|
const a = report.a;
|
|
@@ -15304,7 +15947,7 @@ function DiffHeader({ report }) {
|
|
|
15304
15947
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
15305
15948
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
15306
15949
|
}
|
|
15307
|
-
return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React28.createElement(Box25, { justifyContent: "space-between" }, /* @__PURE__ */ React28.createElement(
|
|
15950
|
+
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);
|
|
15308
15951
|
}
|
|
15309
15952
|
function Pane({
|
|
15310
15953
|
label,
|
|
@@ -15320,21 +15963,21 @@ function Pane({
|
|
|
15320
15963
|
borderStyle: "single",
|
|
15321
15964
|
borderColor: headerColor
|
|
15322
15965
|
},
|
|
15323
|
-
/* @__PURE__ */ React28.createElement(
|
|
15324
|
-
records.length === 0 ? /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(
|
|
15966
|
+
/* @__PURE__ */ React28.createElement(Text23, { color: headerColor, bold: true }, label),
|
|
15967
|
+
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 }))
|
|
15325
15968
|
);
|
|
15326
15969
|
}
|
|
15327
15970
|
function KindBadge({ kind }) {
|
|
15328
15971
|
if (kind === "match") {
|
|
15329
|
-
return /* @__PURE__ */ React28.createElement(
|
|
15972
|
+
return /* @__PURE__ */ React28.createElement(Text23, { color: "green" }, "\u2713 match");
|
|
15330
15973
|
}
|
|
15331
15974
|
if (kind === "diverge") {
|
|
15332
|
-
return /* @__PURE__ */ React28.createElement(
|
|
15975
|
+
return /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, "\u2605 diverge");
|
|
15333
15976
|
}
|
|
15334
15977
|
if (kind === "only_in_a") {
|
|
15335
|
-
return /* @__PURE__ */ React28.createElement(
|
|
15978
|
+
return /* @__PURE__ */ React28.createElement(Text23, { color: "blue" }, "\u2190 only in A");
|
|
15336
15979
|
}
|
|
15337
|
-
return /* @__PURE__ */ React28.createElement(
|
|
15980
|
+
return /* @__PURE__ */ React28.createElement(Text23, { color: "magenta" }, "\u2192 only in B");
|
|
15338
15981
|
}
|
|
15339
15982
|
function paneRecords(pair, side) {
|
|
15340
15983
|
if (!pair) return [];
|
|
@@ -15359,7 +16002,7 @@ async function diffCommand(opts) {
|
|
|
15359
16002
|
if (wantMarkdown) {
|
|
15360
16003
|
console.log(renderSummaryTable(report));
|
|
15361
16004
|
const md = renderMarkdown(report);
|
|
15362
|
-
|
|
16005
|
+
writeFileSync8(opts.mdPath, md, "utf8");
|
|
15363
16006
|
console.log(`
|
|
15364
16007
|
markdown report written to ${opts.mdPath}`);
|
|
15365
16008
|
return;
|
|
@@ -15509,7 +16152,7 @@ import { render as render3 } from "ink";
|
|
|
15509
16152
|
import React31 from "react";
|
|
15510
16153
|
|
|
15511
16154
|
// src/cli/ui/ReplayApp.tsx
|
|
15512
|
-
import { Box as Box26, Static as Static3, Text as
|
|
16155
|
+
import { Box as Box26, Static as Static3, Text as Text24, useApp as useApp4, useInput as useInput2 } from "ink";
|
|
15513
16156
|
import React30, { useMemo as useMemo4, useState as useState14 } from "react";
|
|
15514
16157
|
function ReplayApp({ meta, pages }) {
|
|
15515
16158
|
const { exit: exit2 } = useApp4();
|
|
@@ -15557,7 +16200,7 @@ function ReplayApp({ meta, pages }) {
|
|
|
15557
16200
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
15558
16201
|
prefixHash
|
|
15559
16202
|
}
|
|
15560
|
-
), /* @__PURE__ */ React30.createElement(Box26, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React30.createElement(Box26, { justifyContent: "space-between" }, /* @__PURE__ */ React30.createElement(
|
|
16203
|
+
), /* @__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")));
|
|
15561
16204
|
}
|
|
15562
16205
|
|
|
15563
16206
|
// src/cli/commands/replay.ts
|
|
@@ -15736,7 +16379,7 @@ async function runCommand2(opts) {
|
|
|
15736
16379
|
system: opts.system,
|
|
15737
16380
|
toolSpecs: tools?.specs()
|
|
15738
16381
|
});
|
|
15739
|
-
const
|
|
16382
|
+
const loop2 = new CacheFirstLoop({
|
|
15740
16383
|
client,
|
|
15741
16384
|
prefix,
|
|
15742
16385
|
tools,
|
|
@@ -15761,7 +16404,7 @@ async function runCommand2(opts) {
|
|
|
15761
16404
|
});
|
|
15762
16405
|
}
|
|
15763
16406
|
try {
|
|
15764
|
-
for await (const ev of
|
|
16407
|
+
for await (const ev of loop2.step(opts.task)) {
|
|
15765
16408
|
if (ev.role === "assistant_delta" && ev.content) process.stdout.write(ev.content);
|
|
15766
16409
|
if (ev.role === "tool") process.stdout.write(`
|
|
15767
16410
|
[tool ${ev.toolName}] ${ev.content}
|
|
@@ -15780,7 +16423,7 @@ async function runCommand2(opts) {
|
|
|
15780
16423
|
} finally {
|
|
15781
16424
|
transcriptStream?.end();
|
|
15782
16425
|
}
|
|
15783
|
-
const s =
|
|
16426
|
+
const s = loop2.stats.summary();
|
|
15784
16427
|
process.stdout.write(
|
|
15785
16428
|
`
|
|
15786
16429
|
\u2014 turns:${s.turns} cache:${(s.cacheHitRatio * 100).toFixed(1)}% cost:$${s.totalCostUsd.toFixed(6)} save-vs-claude:${s.savingsVsClaudePct.toFixed(1)}%
|
|
@@ -15877,7 +16520,7 @@ import { render as render4 } from "ink";
|
|
|
15877
16520
|
import React33 from "react";
|
|
15878
16521
|
|
|
15879
16522
|
// src/cli/ui/Wizard.tsx
|
|
15880
|
-
import { Box as Box27, Text as
|
|
16523
|
+
import { Box as Box27, Text as Text25, useApp as useApp5, useInput as useInput3 } from "ink";
|
|
15881
16524
|
import TextInput2 from "ink-text-input";
|
|
15882
16525
|
import React32, { useState as useState15 } from "react";
|
|
15883
16526
|
|
|
@@ -15951,7 +16594,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
15951
16594
|
setStep("mcp");
|
|
15952
16595
|
}
|
|
15953
16596
|
}
|
|
15954
|
-
), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16597
|
+
), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] confirm \xB7 [Esc] cancel")));
|
|
15955
16598
|
}
|
|
15956
16599
|
if (step === "mcp") {
|
|
15957
16600
|
return /* @__PURE__ */ React32.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React32.createElement(
|
|
@@ -16005,8 +16648,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16005
16648
|
}
|
|
16006
16649
|
), specs.map((spec, i) => (
|
|
16007
16650
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
16008
|
-
/* @__PURE__ */ React32.createElement(Box27, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React32.createElement(
|
|
16009
|
-
)), /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16651
|
+
/* @__PURE__ */ React32.createElement(Box27, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React32.createElement(Text25, { dimColor: true }, "\xB7 ", spec))
|
|
16652
|
+
)), /* @__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(
|
|
16010
16653
|
ReviewConfirm,
|
|
16011
16654
|
{
|
|
16012
16655
|
onConfirm: () => {
|
|
@@ -16032,7 +16675,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16032
16675
|
}
|
|
16033
16676
|
));
|
|
16034
16677
|
}
|
|
16035
|
-
return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16678
|
+
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 }));
|
|
16036
16679
|
}
|
|
16037
16680
|
function ApiKeyStep({
|
|
16038
16681
|
onSubmit,
|
|
@@ -16040,7 +16683,7 @@ function ApiKeyStep({
|
|
|
16040
16683
|
onError
|
|
16041
16684
|
}) {
|
|
16042
16685
|
const [value, setValue] = useState15("");
|
|
16043
|
-
return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16686
|
+
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(
|
|
16044
16687
|
TextInput2,
|
|
16045
16688
|
{
|
|
16046
16689
|
value,
|
|
@@ -16057,7 +16700,7 @@ function ApiKeyStep({
|
|
|
16057
16700
|
mask: "\u2022",
|
|
16058
16701
|
placeholder: "sk-..."
|
|
16059
16702
|
}
|
|
16060
|
-
)), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16703
|
+
)), 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);
|
|
16061
16704
|
}
|
|
16062
16705
|
function McpArgsStep({
|
|
16063
16706
|
entry,
|
|
@@ -16066,7 +16709,7 @@ function McpArgsStep({
|
|
|
16066
16709
|
onError
|
|
16067
16710
|
}) {
|
|
16068
16711
|
const [value, setValue] = useState15("");
|
|
16069
|
-
return /* @__PURE__ */ React32.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column" }, /* @__PURE__ */ React32.createElement(
|
|
16712
|
+
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(
|
|
16070
16713
|
TextInput2,
|
|
16071
16714
|
{
|
|
16072
16715
|
value,
|
|
@@ -16082,7 +16725,7 @@ function McpArgsStep({
|
|
|
16082
16725
|
},
|
|
16083
16726
|
placeholder: placeholderFor(entry)
|
|
16084
16727
|
}
|
|
16085
|
-
)), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(
|
|
16728
|
+
)), error ? /* @__PURE__ */ React32.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(Text25, { color: "red" }, error)) : null));
|
|
16086
16729
|
}
|
|
16087
16730
|
function ReviewConfirm({ onConfirm }) {
|
|
16088
16731
|
useInput3((_i, key) => {
|
|
@@ -16102,10 +16745,10 @@ function StepFrame({
|
|
|
16102
16745
|
total,
|
|
16103
16746
|
children
|
|
16104
16747
|
}) {
|
|
16105
|
-
return /* @__PURE__ */ React32.createElement(Box27, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React32.createElement(Box27, null, /* @__PURE__ */ React32.createElement(
|
|
16748
|
+
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));
|
|
16106
16749
|
}
|
|
16107
16750
|
function SummaryLine({ label, value }) {
|
|
16108
|
-
return /* @__PURE__ */ React32.createElement(Box27, null, /* @__PURE__ */ React32.createElement(
|
|
16751
|
+
return /* @__PURE__ */ React32.createElement(Box27, null, /* @__PURE__ */ React32.createElement(Text25, null, label.padEnd(12)), /* @__PURE__ */ React32.createElement(Text25, { bold: true }, value));
|
|
16109
16752
|
}
|
|
16110
16753
|
function presetItems() {
|
|
16111
16754
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -16295,6 +16938,16 @@ function resolveSession(flag, configSession) {
|
|
|
16295
16938
|
if (typeof configSession === "string" && configSession.length > 0) return configSession;
|
|
16296
16939
|
return "default";
|
|
16297
16940
|
}
|
|
16941
|
+
function resolveContinueFlag(flag, fallbackSession, getLatestSession, warn = () => {
|
|
16942
|
+
}) {
|
|
16943
|
+
if (!flag) return { session: fallbackSession, forceResume: false };
|
|
16944
|
+
const latest = getLatestSession();
|
|
16945
|
+
if (!latest) {
|
|
16946
|
+
warn("\u25B8 -c/--continue: no saved sessions yet \u2014 starting a fresh one.");
|
|
16947
|
+
return { session: fallbackSession, forceResume: false };
|
|
16948
|
+
}
|
|
16949
|
+
return { session: latest.name, forceResume: true };
|
|
16950
|
+
}
|
|
16298
16951
|
|
|
16299
16952
|
// src/cli/index.ts
|
|
16300
16953
|
var DEFAULT_SYSTEM = `You are Reasonix, a helpful DeepSeek-powered assistant. Be concise and accurate. Use tools when available.
|
|
@@ -16319,21 +16972,32 @@ The signal isn't a topic list \u2014 it's: "if I'm wrong about this, is it becau
|
|
|
16319
16972
|
|
|
16320
16973
|
${ESCALATION_CONTRACT}`;
|
|
16321
16974
|
var program = new Command();
|
|
16322
|
-
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION)
|
|
16323
|
-
|
|
16975
|
+
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION).option(
|
|
16976
|
+
"-c, --continue",
|
|
16977
|
+
"Resume the most recently used chat session without showing the picker."
|
|
16978
|
+
);
|
|
16979
|
+
program.action(async (opts) => {
|
|
16324
16980
|
const cfg = readConfig();
|
|
16325
16981
|
if (!cfg.setupCompleted) {
|
|
16326
16982
|
await setupCommand({});
|
|
16327
16983
|
return;
|
|
16328
16984
|
}
|
|
16329
16985
|
const defaults = resolveDefaults({});
|
|
16986
|
+
const continueOpts = resolveContinueFlag(
|
|
16987
|
+
opts.continue,
|
|
16988
|
+
defaults.session,
|
|
16989
|
+
() => listSessions()[0],
|
|
16990
|
+
(msg) => process.stderr.write(`${msg}
|
|
16991
|
+
`)
|
|
16992
|
+
);
|
|
16330
16993
|
await chatCommand({
|
|
16331
16994
|
model: defaults.model,
|
|
16332
16995
|
system: applyMemoryStack(DEFAULT_SYSTEM, process.cwd()),
|
|
16333
16996
|
harvest: defaults.harvest,
|
|
16334
16997
|
branch: defaults.branch,
|
|
16335
|
-
session:
|
|
16336
|
-
mcp: defaults.mcp
|
|
16998
|
+
session: continueOpts.session,
|
|
16999
|
+
mcp: defaults.mcp,
|
|
17000
|
+
forceResume: continueOpts.forceResume
|
|
16337
17001
|
});
|
|
16338
17002
|
});
|
|
16339
17003
|
program.command("setup").description("Interactive wizard \u2014 API key, preset, MCP servers. Re-run any time to reconfigure.").action(async () => {
|
|
@@ -16365,7 +17029,10 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
16365
17029
|
"--branch <n>",
|
|
16366
17030
|
"Self-consistency: run N parallel samples per turn (N\xD7 cost). Manual only \u2014 never auto-enabled.",
|
|
16367
17031
|
(v) => Number.parseInt(v, 10)
|
|
16368
|
-
).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option(
|
|
17032
|
+
).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option(
|
|
17033
|
+
"-c, --continue",
|
|
17034
|
+
"Resume the most-recently-used session (any name) without showing the picker."
|
|
17035
|
+
).option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option(
|
|
16369
17036
|
"--mcp <spec>",
|
|
16370
17037
|
'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
|
|
16371
17038
|
(value, previous = []) => [...previous, value],
|
|
@@ -16383,16 +17050,23 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
16383
17050
|
preset: opts.preset,
|
|
16384
17051
|
noConfig: opts.config === false
|
|
16385
17052
|
});
|
|
17053
|
+
const continueOpts = opts.resume ? { session: defaults.session, forceResume: true } : resolveContinueFlag(
|
|
17054
|
+
opts.continue,
|
|
17055
|
+
defaults.session,
|
|
17056
|
+
() => listSessions()[0],
|
|
17057
|
+
(msg) => process.stderr.write(`${msg}
|
|
17058
|
+
`)
|
|
17059
|
+
);
|
|
16386
17060
|
await chatCommand({
|
|
16387
17061
|
model: defaults.model,
|
|
16388
17062
|
system: applyMemoryStack(opts.system, process.cwd()),
|
|
16389
17063
|
transcript: opts.transcript,
|
|
16390
17064
|
harvest: defaults.harvest,
|
|
16391
17065
|
branch: defaults.branch,
|
|
16392
|
-
session:
|
|
17066
|
+
session: continueOpts.session,
|
|
16393
17067
|
mcp: defaults.mcp,
|
|
16394
17068
|
mcpPrefix: opts.mcpPrefix,
|
|
16395
|
-
forceResume:
|
|
17069
|
+
forceResume: continueOpts.forceResume,
|
|
16396
17070
|
forceNew: !!opts.new
|
|
16397
17071
|
});
|
|
16398
17072
|
});
|