reasonix 0.11.3 → 0.12.6
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/dashboard/app.css +2346 -0
- package/dashboard/app.js +3913 -0
- package/dashboard/codemirror.js +36 -0
- package/dashboard/index.html +19 -0
- package/dist/cli/{chunk-JDVY4JDU.js → chunk-PKPWI33U.js} +3 -1
- package/dist/cli/index.js +2348 -225
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-YRY4HPMZ.js → prompt-HNDDXDRH.js} +2 -2
- package/dist/index.d.ts +95 -21
- package/dist/index.js +62 -24
- package/dist/index.js.map +1 -1
- package/package.json +102 -76
- /package/dist/cli/{chunk-JDVY4JDU.js.map → chunk-PKPWI33U.js.map} +0 -0
- /package/dist/cli/{prompt-YRY4HPMZ.js.map → prompt-HNDDXDRH.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -4,13 +4,15 @@ import {
|
|
|
4
4
|
MemoryStore,
|
|
5
5
|
NEGATIVE_CLAIM_RULE,
|
|
6
6
|
PROJECT_MEMORY_FILE,
|
|
7
|
+
SKILLS_DIRNAME,
|
|
8
|
+
SKILL_FILE,
|
|
7
9
|
SkillStore,
|
|
8
10
|
TUI_FORMATTING_RULES,
|
|
9
11
|
applyMemoryStack,
|
|
10
12
|
memoryEnabled,
|
|
11
13
|
readProjectMemory,
|
|
12
14
|
sanitizeMemoryName
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PKPWI33U.js";
|
|
14
16
|
|
|
15
17
|
// src/cli/index.ts
|
|
16
18
|
import { Command } from "commander";
|
|
@@ -179,8 +181,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
179
181
|
}
|
|
180
182
|
function sleep(ms, signal) {
|
|
181
183
|
if (ms <= 0) return Promise.resolve();
|
|
182
|
-
return new Promise((
|
|
183
|
-
const timer = setTimeout(
|
|
184
|
+
return new Promise((resolve13, reject) => {
|
|
185
|
+
const timer = setTimeout(resolve13, ms);
|
|
184
186
|
if (signal) {
|
|
185
187
|
const onAbort = () => {
|
|
186
188
|
clearTimeout(timer);
|
|
@@ -665,7 +667,7 @@ function matchesTool(hook, toolName) {
|
|
|
665
667
|
}
|
|
666
668
|
}
|
|
667
669
|
function defaultSpawner(input) {
|
|
668
|
-
return new Promise((
|
|
670
|
+
return new Promise((resolve13) => {
|
|
669
671
|
const child = spawn(input.command, {
|
|
670
672
|
cwd: input.cwd,
|
|
671
673
|
shell: true,
|
|
@@ -692,7 +694,7 @@ function defaultSpawner(input) {
|
|
|
692
694
|
});
|
|
693
695
|
child.once("error", (err) => {
|
|
694
696
|
clearTimeout(timer);
|
|
695
|
-
|
|
697
|
+
resolve13({
|
|
696
698
|
exitCode: null,
|
|
697
699
|
stdout: stdout3,
|
|
698
700
|
stderr,
|
|
@@ -702,7 +704,7 @@ function defaultSpawner(input) {
|
|
|
702
704
|
});
|
|
703
705
|
child.once("close", (code) => {
|
|
704
706
|
clearTimeout(timer);
|
|
705
|
-
|
|
707
|
+
resolve13({
|
|
706
708
|
exitCode: code,
|
|
707
709
|
stdout: stdout3.trim(),
|
|
708
710
|
stderr: stderr.trim(),
|
|
@@ -1490,25 +1492,32 @@ function coerceToToolCall(candidateJson, allowedNames) {
|
|
|
1490
1492
|
var StormBreaker = class {
|
|
1491
1493
|
windowSize;
|
|
1492
1494
|
threshold;
|
|
1495
|
+
isMutating;
|
|
1493
1496
|
recent = [];
|
|
1494
|
-
constructor(windowSize = 6, threshold = 3) {
|
|
1497
|
+
constructor(windowSize = 6, threshold = 3, isMutating) {
|
|
1495
1498
|
this.windowSize = windowSize;
|
|
1496
1499
|
this.threshold = threshold;
|
|
1500
|
+
this.isMutating = isMutating;
|
|
1497
1501
|
}
|
|
1498
1502
|
inspect(call) {
|
|
1499
|
-
const
|
|
1500
|
-
if (!
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
)
|
|
1503
|
+
const name = call.function?.name;
|
|
1504
|
+
if (!name) return { suppress: false };
|
|
1505
|
+
const args = call.function?.arguments ?? "";
|
|
1506
|
+
const mutating = this.isMutating ? this.isMutating(call) : false;
|
|
1507
|
+
const readOnly = !mutating;
|
|
1508
|
+
if (mutating) {
|
|
1509
|
+
for (let i = this.recent.length - 1; i >= 0; i--) {
|
|
1510
|
+
if (this.recent[i].readOnly) this.recent.splice(i, 1);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
const count = this.recent.reduce((n, e) => e.name === name && e.args === args ? n + 1 : n, 0);
|
|
1505
1514
|
if (count >= this.threshold - 1) {
|
|
1506
1515
|
return {
|
|
1507
1516
|
suppress: true,
|
|
1508
|
-
reason: `call-storm suppressed: ${
|
|
1517
|
+
reason: `call-storm suppressed: ${name} called with identical args ${count + 1} times within window=${this.windowSize}`
|
|
1509
1518
|
};
|
|
1510
1519
|
}
|
|
1511
|
-
this.recent.push(
|
|
1520
|
+
this.recent.push({ name, args, readOnly });
|
|
1512
1521
|
while (this.recent.length > this.windowSize) this.recent.shift();
|
|
1513
1522
|
return { suppress: false };
|
|
1514
1523
|
}
|
|
@@ -1516,11 +1525,6 @@ var StormBreaker = class {
|
|
|
1516
1525
|
this.recent.length = 0;
|
|
1517
1526
|
}
|
|
1518
1527
|
};
|
|
1519
|
-
function signature(call) {
|
|
1520
|
-
const name = call.function?.name;
|
|
1521
|
-
if (!name) return null;
|
|
1522
|
-
return [name, call.function?.arguments ?? ""];
|
|
1523
|
-
}
|
|
1524
1528
|
|
|
1525
1529
|
// src/repair/truncation.ts
|
|
1526
1530
|
function repairTruncatedJson(input) {
|
|
@@ -1598,7 +1602,7 @@ var ToolCallRepair = class {
|
|
|
1598
1602
|
opts;
|
|
1599
1603
|
constructor(opts) {
|
|
1600
1604
|
this.opts = opts;
|
|
1601
|
-
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
1605
|
+
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3, opts.isMutating);
|
|
1602
1606
|
}
|
|
1603
1607
|
/**
|
|
1604
1608
|
* Drop the StormBreaker's sliding window of recent (name, args)
|
|
@@ -1622,13 +1626,13 @@ var ToolCallRepair = class {
|
|
|
1622
1626
|
allowedNames: this.opts.allowedToolNames,
|
|
1623
1627
|
maxCalls: this.opts.maxScavenge ?? 4
|
|
1624
1628
|
});
|
|
1625
|
-
const seenSignatures = new Set(declaredCalls.map(
|
|
1629
|
+
const seenSignatures = new Set(declaredCalls.map(signature));
|
|
1626
1630
|
const merged = [...declaredCalls];
|
|
1627
1631
|
for (const sc of scavenged.calls) {
|
|
1628
|
-
if (!seenSignatures.has(
|
|
1632
|
+
if (!seenSignatures.has(signature(sc))) {
|
|
1629
1633
|
merged.push(sc);
|
|
1630
1634
|
report.scavenged++;
|
|
1631
|
-
seenSignatures.add(
|
|
1635
|
+
seenSignatures.add(signature(sc));
|
|
1632
1636
|
}
|
|
1633
1637
|
}
|
|
1634
1638
|
report.notes.push(...scavenged.notes);
|
|
@@ -1654,7 +1658,7 @@ var ToolCallRepair = class {
|
|
|
1654
1658
|
return { calls: filtered, report };
|
|
1655
1659
|
}
|
|
1656
1660
|
};
|
|
1657
|
-
function
|
|
1661
|
+
function signature(call) {
|
|
1658
1662
|
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
1659
1663
|
}
|
|
1660
1664
|
|
|
@@ -1793,6 +1797,12 @@ function outputCostUsd(model2, usage) {
|
|
|
1793
1797
|
if (!p) return 0;
|
|
1794
1798
|
return usage.completionTokens * p.output / 1e6;
|
|
1795
1799
|
}
|
|
1800
|
+
function cacheSavingsUsd(model2, hitTokens) {
|
|
1801
|
+
if (hitTokens <= 0) return 0;
|
|
1802
|
+
const p = DEEPSEEK_PRICING[model2];
|
|
1803
|
+
if (!p) return 0;
|
|
1804
|
+
return hitTokens * (p.inputCacheMiss - p.inputCacheHit) / 1e6;
|
|
1805
|
+
}
|
|
1796
1806
|
function claudeEquivalentCost(usage) {
|
|
1797
1807
|
return (usage.promptTokens * CLAUDE_SONNET_PRICING.input + usage.completionTokens * CLAUDE_SONNET_PRICING.output) / 1e6;
|
|
1798
1808
|
}
|
|
@@ -1883,6 +1893,13 @@ var CacheFirstLoop = class {
|
|
|
1883
1893
|
branchOptions;
|
|
1884
1894
|
/** See ReconfigurableOptions — mutable so `/effort` can flip mid-session. */
|
|
1885
1895
|
reasoningEffort;
|
|
1896
|
+
/**
|
|
1897
|
+
* Auto-escalation toggle. `true` lets the loop self-promote to pro
|
|
1898
|
+
* mid-turn (NEEDS_PRO marker / failure threshold); `false` keeps it
|
|
1899
|
+
* pinned to `model`. Mutable so the dashboard's preset switcher can
|
|
1900
|
+
* flip it live alongside `model`.
|
|
1901
|
+
*/
|
|
1902
|
+
autoEscalate = true;
|
|
1886
1903
|
sessionName;
|
|
1887
1904
|
/**
|
|
1888
1905
|
* Hook list, mutable so `/hooks reload` can swap it without
|
|
@@ -1947,6 +1964,7 @@ var CacheFirstLoop = class {
|
|
|
1947
1964
|
this.tools = opts.tools ?? new ToolRegistry();
|
|
1948
1965
|
this.model = opts.model ?? "deepseek-v4-flash";
|
|
1949
1966
|
this.reasoningEffort = opts.reasoningEffort ?? "max";
|
|
1967
|
+
if (opts.autoEscalate !== void 0) this.autoEscalate = opts.autoEscalate;
|
|
1950
1968
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
1951
1969
|
this.hooks = opts.hooks ?? [];
|
|
1952
1970
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
@@ -1964,7 +1982,26 @@ var CacheFirstLoop = class {
|
|
|
1964
1982
|
this._streamPreference = opts.stream ?? true;
|
|
1965
1983
|
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
1966
1984
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
1967
|
-
|
|
1985
|
+
const registry = this.tools;
|
|
1986
|
+
const isMutating = (call) => {
|
|
1987
|
+
const name = call.function?.name;
|
|
1988
|
+
if (!name) return false;
|
|
1989
|
+
const def = registry.get(name);
|
|
1990
|
+
if (!def) return false;
|
|
1991
|
+
if (def.readOnlyCheck) {
|
|
1992
|
+
let args = {};
|
|
1993
|
+
try {
|
|
1994
|
+
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
1995
|
+
} catch {
|
|
1996
|
+
}
|
|
1997
|
+
try {
|
|
1998
|
+
if (def.readOnlyCheck(args)) return false;
|
|
1999
|
+
} catch {
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return def.readOnly !== true;
|
|
2003
|
+
};
|
|
2004
|
+
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames, isMutating });
|
|
1968
2005
|
this.sessionName = opts.session ?? null;
|
|
1969
2006
|
if (this.sessionName) {
|
|
1970
2007
|
const prior = loadSessionMessages(this.sessionName);
|
|
@@ -2145,6 +2182,7 @@ var CacheFirstLoop = class {
|
|
|
2145
2182
|
if (opts.model !== void 0) this.model = opts.model;
|
|
2146
2183
|
if (opts.stream !== void 0) this._streamPreference = opts.stream;
|
|
2147
2184
|
if (opts.reasoningEffort !== void 0) this.reasoningEffort = opts.reasoningEffort;
|
|
2185
|
+
if (opts.autoEscalate !== void 0) this.autoEscalate = opts.autoEscalate;
|
|
2148
2186
|
if (opts.branch !== void 0) {
|
|
2149
2187
|
if (typeof opts.branch === "number") {
|
|
2150
2188
|
this.branchOptions = { budget: opts.branch };
|
|
@@ -2260,7 +2298,7 @@ var CacheFirstLoop = class {
|
|
|
2260
2298
|
if (repair.truncationsFixed > 0) bump("truncated", repair.truncationsFixed);
|
|
2261
2299
|
if (repair.stormsBroken > 0) bump("storm-broken", repair.stormsBroken);
|
|
2262
2300
|
}
|
|
2263
|
-
if (bumped && !this._escalateThisTurn && this._turnFailureCount >= FAILURE_ESCALATION_THRESHOLD) {
|
|
2301
|
+
if (bumped && !this._escalateThisTurn && this.autoEscalate && this._turnFailureCount >= FAILURE_ESCALATION_THRESHOLD) {
|
|
2264
2302
|
this._escalateThisTurn = true;
|
|
2265
2303
|
return true;
|
|
2266
2304
|
}
|
|
@@ -2465,8 +2503,8 @@ var CacheFirstLoop = class {
|
|
|
2465
2503
|
}
|
|
2466
2504
|
);
|
|
2467
2505
|
for (let k = 0; k < budget; k++) {
|
|
2468
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2469
|
-
waiter =
|
|
2506
|
+
const sample = queue.shift() ?? await new Promise((resolve13) => {
|
|
2507
|
+
waiter = resolve13;
|
|
2470
2508
|
});
|
|
2471
2509
|
yield {
|
|
2472
2510
|
turn: this._turn,
|
|
@@ -2505,7 +2543,7 @@ var CacheFirstLoop = class {
|
|
|
2505
2543
|
const callBuf = /* @__PURE__ */ new Map();
|
|
2506
2544
|
const readyIndices = /* @__PURE__ */ new Set();
|
|
2507
2545
|
const callModel = this.modelForCurrentCall();
|
|
2508
|
-
const bufferForEscalation = callModel !== ESCALATION_MODEL;
|
|
2546
|
+
const bufferForEscalation = this.autoEscalate && callModel !== ESCALATION_MODEL;
|
|
2509
2547
|
let escalationBuf = "";
|
|
2510
2548
|
let escalationBufFlushed = false;
|
|
2511
2549
|
for await (const chunk of this.client.stream({
|
|
@@ -2617,7 +2655,7 @@ var CacheFirstLoop = class {
|
|
|
2617
2655
|
};
|
|
2618
2656
|
return;
|
|
2619
2657
|
}
|
|
2620
|
-
if (this.modelForCurrentCall() !== ESCALATION_MODEL && this.isEscalationRequest(assistantContent)) {
|
|
2658
|
+
if (this.autoEscalate && this.modelForCurrentCall() !== ESCALATION_MODEL && this.isEscalationRequest(assistantContent)) {
|
|
2621
2659
|
const { reason } = this.parseEscalationMarker(assistantContent);
|
|
2622
2660
|
this._escalateThisTurn = true;
|
|
2623
2661
|
const reasonSuffix = reason ? ` \u2014 ${reason}` : "";
|
|
@@ -3199,6 +3237,9 @@ var DEFAULT_PICKER_IGNORE_DIRS = [
|
|
|
3199
3237
|
"venv",
|
|
3200
3238
|
"__pycache__"
|
|
3201
3239
|
];
|
|
3240
|
+
function listFilesSync(root, opts = {}) {
|
|
3241
|
+
return listFilesWithStatsSync(root, opts).map((e) => e.path);
|
|
3242
|
+
}
|
|
3202
3243
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
3203
3244
|
const maxResults = Math.max(1, opts.maxResults ?? 500);
|
|
3204
3245
|
const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
@@ -5196,7 +5237,7 @@ async function runCommand(cmd, opts) {
|
|
|
5196
5237
|
};
|
|
5197
5238
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5198
5239
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5199
|
-
return await new Promise((
|
|
5240
|
+
return await new Promise((resolve13, reject) => {
|
|
5200
5241
|
let child;
|
|
5201
5242
|
try {
|
|
5202
5243
|
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
@@ -5241,7 +5282,7 @@ async function runCommand(cmd, opts) {
|
|
|
5241
5282
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5242
5283
|
|
|
5243
5284
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5244
|
-
|
|
5285
|
+
resolve13({ exitCode: code, output, timedOut });
|
|
5245
5286
|
});
|
|
5246
5287
|
});
|
|
5247
5288
|
}
|
|
@@ -6445,7 +6486,7 @@ var McpClient = class {
|
|
|
6445
6486
|
const id = this.nextId++;
|
|
6446
6487
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
6447
6488
|
let abortHandler = null;
|
|
6448
|
-
const promise = new Promise((
|
|
6489
|
+
const promise = new Promise((resolve13, reject) => {
|
|
6449
6490
|
const timeout = setTimeout(() => {
|
|
6450
6491
|
this.pending.delete(id);
|
|
6451
6492
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -6454,7 +6495,7 @@ var McpClient = class {
|
|
|
6454
6495
|
);
|
|
6455
6496
|
}, this.requestTimeoutMs);
|
|
6456
6497
|
this.pending.set(id, {
|
|
6457
|
-
resolve:
|
|
6498
|
+
resolve: resolve13,
|
|
6458
6499
|
reject,
|
|
6459
6500
|
timeout
|
|
6460
6501
|
});
|
|
@@ -6577,12 +6618,12 @@ var StdioTransport = class {
|
|
|
6577
6618
|
}
|
|
6578
6619
|
async send(message) {
|
|
6579
6620
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
6580
|
-
return new Promise((
|
|
6621
|
+
return new Promise((resolve13, reject) => {
|
|
6581
6622
|
const line = `${JSON.stringify(message)}
|
|
6582
6623
|
`;
|
|
6583
6624
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
6584
6625
|
if (err) reject(err);
|
|
6585
|
-
else
|
|
6626
|
+
else resolve13();
|
|
6586
6627
|
});
|
|
6587
6628
|
});
|
|
6588
6629
|
}
|
|
@@ -6593,8 +6634,8 @@ var StdioTransport = class {
|
|
|
6593
6634
|
continue;
|
|
6594
6635
|
}
|
|
6595
6636
|
if (this.closed) return;
|
|
6596
|
-
const next = await new Promise((
|
|
6597
|
-
this.waiters.push(
|
|
6637
|
+
const next = await new Promise((resolve13) => {
|
|
6638
|
+
this.waiters.push(resolve13);
|
|
6598
6639
|
});
|
|
6599
6640
|
if (next === null) return;
|
|
6600
6641
|
yield next;
|
|
@@ -6660,8 +6701,8 @@ var SseTransport = class {
|
|
|
6660
6701
|
constructor(opts) {
|
|
6661
6702
|
this.url = opts.url;
|
|
6662
6703
|
this.headers = opts.headers ?? {};
|
|
6663
|
-
this.endpointReady = new Promise((
|
|
6664
|
-
this.resolveEndpoint =
|
|
6704
|
+
this.endpointReady = new Promise((resolve13, reject) => {
|
|
6705
|
+
this.resolveEndpoint = resolve13;
|
|
6665
6706
|
this.rejectEndpoint = reject;
|
|
6666
6707
|
});
|
|
6667
6708
|
this.endpointReady.catch(() => void 0);
|
|
@@ -6688,8 +6729,8 @@ var SseTransport = class {
|
|
|
6688
6729
|
continue;
|
|
6689
6730
|
}
|
|
6690
6731
|
if (this.closed) return;
|
|
6691
|
-
const next = await new Promise((
|
|
6692
|
-
this.waiters.push(
|
|
6732
|
+
const next = await new Promise((resolve13) => {
|
|
6733
|
+
this.waiters.push(resolve13);
|
|
6693
6734
|
});
|
|
6694
6735
|
if (next === null) return;
|
|
6695
6736
|
yield next;
|
|
@@ -6876,8 +6917,8 @@ var StreamableHttpTransport = class {
|
|
|
6876
6917
|
continue;
|
|
6877
6918
|
}
|
|
6878
6919
|
if (this.closed) return;
|
|
6879
|
-
const next = await new Promise((
|
|
6880
|
-
this.waiters.push(
|
|
6920
|
+
const next = await new Promise((resolve13) => {
|
|
6921
|
+
this.waiters.push(resolve13);
|
|
6881
6922
|
});
|
|
6882
6923
|
if (next === null) return;
|
|
6883
6924
|
yield next;
|
|
@@ -7354,7 +7395,8 @@ function emptyBucket(label, since) {
|
|
|
7354
7395
|
cacheHitTokens: 0,
|
|
7355
7396
|
cacheMissTokens: 0,
|
|
7356
7397
|
costUsd: 0,
|
|
7357
|
-
claudeEquivUsd: 0
|
|
7398
|
+
claudeEquivUsd: 0,
|
|
7399
|
+
cacheSavingsUsd: 0
|
|
7358
7400
|
};
|
|
7359
7401
|
}
|
|
7360
7402
|
function addToBucket(b, r) {
|
|
@@ -7365,6 +7407,7 @@ function addToBucket(b, r) {
|
|
|
7365
7407
|
b.cacheMissTokens += r.cacheMissTokens;
|
|
7366
7408
|
b.costUsd += r.costUsd;
|
|
7367
7409
|
b.claudeEquivUsd += r.claudeEquivUsd;
|
|
7410
|
+
b.cacheSavingsUsd += cacheSavingsUsd(r.model, r.cacheHitTokens);
|
|
7368
7411
|
}
|
|
7369
7412
|
function aggregateUsage(records, opts = {}) {
|
|
7370
7413
|
const now = opts.now ?? Date.now();
|
|
@@ -7435,7 +7478,7 @@ function formatLogSize(path5 = defaultUsageLogPath()) {
|
|
|
7435
7478
|
}
|
|
7436
7479
|
|
|
7437
7480
|
// src/cli/commands/chat.tsx
|
|
7438
|
-
import { existsSync as
|
|
7481
|
+
import { existsSync as existsSync22, statSync as statSync13 } from "fs";
|
|
7439
7482
|
import { render } from "ink";
|
|
7440
7483
|
import React27, { useState as useState12 } from "react";
|
|
7441
7484
|
|
|
@@ -7598,62 +7641,1700 @@ function archivePlanState(sessionName) {
|
|
|
7598
7641
|
return null;
|
|
7599
7642
|
}
|
|
7600
7643
|
}
|
|
7601
|
-
function listPlanArchives(sessionName) {
|
|
7602
|
-
const dir = sessionsDir();
|
|
7603
|
-
if (!existsSync10(dir)) return [];
|
|
7604
|
-
const prefix = `${sanitizeName(sessionName)}.plan.`;
|
|
7605
|
-
const suffix = ".done.json";
|
|
7606
|
-
let entries;
|
|
7607
|
-
try {
|
|
7608
|
-
entries = readdirSync3(dir);
|
|
7609
|
-
} catch {
|
|
7610
|
-
return [];
|
|
7644
|
+
function listPlanArchives(sessionName) {
|
|
7645
|
+
const dir = sessionsDir();
|
|
7646
|
+
if (!existsSync10(dir)) return [];
|
|
7647
|
+
const prefix = `${sanitizeName(sessionName)}.plan.`;
|
|
7648
|
+
const suffix = ".done.json";
|
|
7649
|
+
let entries;
|
|
7650
|
+
try {
|
|
7651
|
+
entries = readdirSync3(dir);
|
|
7652
|
+
} catch {
|
|
7653
|
+
return [];
|
|
7654
|
+
}
|
|
7655
|
+
const summaries = [];
|
|
7656
|
+
for (const name of entries) {
|
|
7657
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix)) continue;
|
|
7658
|
+
const full = join10(dir, name);
|
|
7659
|
+
try {
|
|
7660
|
+
const raw = readFileSync12(full, "utf8");
|
|
7661
|
+
const parsed = JSON.parse(raw);
|
|
7662
|
+
if (parsed.version !== 1) continue;
|
|
7663
|
+
if (!Array.isArray(parsed.steps) || parsed.steps.length === 0) continue;
|
|
7664
|
+
const steps = parsed.steps.filter(
|
|
7665
|
+
(s) => !!s && typeof s === "object" && typeof s.id === "string" && typeof s.title === "string" && typeof s.action === "string"
|
|
7666
|
+
);
|
|
7667
|
+
if (steps.length === 0) continue;
|
|
7668
|
+
const completedStepIds = Array.isArray(parsed.completedStepIds) ? parsed.completedStepIds.filter((id) => typeof id === "string" && !!id) : [];
|
|
7669
|
+
let completedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "";
|
|
7670
|
+
if (!completedAt || Number.isNaN(Date.parse(completedAt))) {
|
|
7671
|
+
try {
|
|
7672
|
+
completedAt = statSync5(full).mtime.toISOString();
|
|
7673
|
+
} catch {
|
|
7674
|
+
completedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
const entry = { path: full, completedAt, steps, completedStepIds };
|
|
7678
|
+
if (typeof parsed.body === "string" && parsed.body) entry.body = parsed.body;
|
|
7679
|
+
if (typeof parsed.summary === "string" && parsed.summary) entry.summary = parsed.summary;
|
|
7680
|
+
summaries.push(entry);
|
|
7681
|
+
} catch {
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
summaries.sort((a, b) => b.completedAt.localeCompare(a.completedAt));
|
|
7685
|
+
return summaries;
|
|
7686
|
+
}
|
|
7687
|
+
function relativeTime(updatedAt, now = Date.now()) {
|
|
7688
|
+
const t2 = Date.parse(updatedAt);
|
|
7689
|
+
if (Number.isNaN(t2)) return updatedAt;
|
|
7690
|
+
const diffMs = Math.max(0, now - t2);
|
|
7691
|
+
const sec = Math.floor(diffMs / 1e3);
|
|
7692
|
+
if (sec < 60) return `${sec}s ago`;
|
|
7693
|
+
const min = Math.floor(sec / 60);
|
|
7694
|
+
if (min < 60) return `${min}m ago`;
|
|
7695
|
+
const hr = Math.floor(min / 60);
|
|
7696
|
+
if (hr < 24) return `${hr}h ago`;
|
|
7697
|
+
const day = Math.floor(hr / 24);
|
|
7698
|
+
if (day < 7) return `${day}d ago`;
|
|
7699
|
+
return updatedAt.slice(0, 10);
|
|
7700
|
+
}
|
|
7701
|
+
|
|
7702
|
+
// src/server/index.ts
|
|
7703
|
+
import { randomBytes } from "crypto";
|
|
7704
|
+
import { createServer } from "http";
|
|
7705
|
+
|
|
7706
|
+
// src/server/api/events.ts
|
|
7707
|
+
var PING_INTERVAL_MS = 25e3;
|
|
7708
|
+
function handleEvents(req, res, ctx) {
|
|
7709
|
+
if (!ctx.subscribeEvents) {
|
|
7710
|
+
res.writeHead(503, { "content-type": "application/json" });
|
|
7711
|
+
res.end(JSON.stringify({ error: "event stream requires an attached dashboard session." }));
|
|
7712
|
+
return;
|
|
7713
|
+
}
|
|
7714
|
+
res.writeHead(200, {
|
|
7715
|
+
"content-type": "text/event-stream",
|
|
7716
|
+
"cache-control": "no-cache",
|
|
7717
|
+
connection: "keep-alive",
|
|
7718
|
+
"x-accel-buffering": "no"
|
|
7719
|
+
// disable Nginx-style buffering if anything proxies us
|
|
7720
|
+
});
|
|
7721
|
+
const writeEvent = (event) => {
|
|
7722
|
+
if (res.writableEnded) return;
|
|
7723
|
+
try {
|
|
7724
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
7725
|
+
|
|
7726
|
+
`);
|
|
7727
|
+
} catch {
|
|
7728
|
+
}
|
|
7729
|
+
};
|
|
7730
|
+
if (ctx.isBusy) writeEvent({ kind: "busy-change", busy: ctx.isBusy() });
|
|
7731
|
+
const unsubscribe = ctx.subscribeEvents(writeEvent);
|
|
7732
|
+
const ping = setInterval(() => writeEvent({ kind: "ping" }), PING_INTERVAL_MS);
|
|
7733
|
+
ping.unref?.();
|
|
7734
|
+
const cleanup = () => {
|
|
7735
|
+
clearInterval(ping);
|
|
7736
|
+
try {
|
|
7737
|
+
unsubscribe();
|
|
7738
|
+
} catch {
|
|
7739
|
+
}
|
|
7740
|
+
if (!res.writableEnded) {
|
|
7741
|
+
try {
|
|
7742
|
+
res.end();
|
|
7743
|
+
} catch {
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
};
|
|
7747
|
+
req.on("close", cleanup);
|
|
7748
|
+
req.on("error", cleanup);
|
|
7749
|
+
res.on("close", cleanup);
|
|
7750
|
+
}
|
|
7751
|
+
|
|
7752
|
+
// src/server/assets.ts
|
|
7753
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
7754
|
+
import { dirname as dirname10, join as join11 } from "path";
|
|
7755
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7756
|
+
function resolveAssetDir() {
|
|
7757
|
+
const here = dirname10(fileURLToPath3(import.meta.url));
|
|
7758
|
+
const candidates = [
|
|
7759
|
+
join11(here, "..", "..", "dashboard"),
|
|
7760
|
+
join11(here, "..", "dashboard"),
|
|
7761
|
+
join11(here, "dashboard")
|
|
7762
|
+
];
|
|
7763
|
+
for (const c of candidates) {
|
|
7764
|
+
try {
|
|
7765
|
+
readFileSync13(join11(c, "index.html"), "utf8");
|
|
7766
|
+
return c;
|
|
7767
|
+
} catch {
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
return candidates[0];
|
|
7771
|
+
}
|
|
7772
|
+
var ASSET_DIR = resolveAssetDir();
|
|
7773
|
+
var cachedIndex = null;
|
|
7774
|
+
var cachedApp = null;
|
|
7775
|
+
var cachedCss = null;
|
|
7776
|
+
var cachedCm = null;
|
|
7777
|
+
function loadIndexTemplate() {
|
|
7778
|
+
if (cachedIndex) return cachedIndex;
|
|
7779
|
+
cachedIndex = readFileSync13(join11(ASSET_DIR, "index.html"), "utf8");
|
|
7780
|
+
return cachedIndex;
|
|
7781
|
+
}
|
|
7782
|
+
function loadApp() {
|
|
7783
|
+
if (cachedApp) return cachedApp;
|
|
7784
|
+
cachedApp = readFileSync13(join11(ASSET_DIR, "app.js"), "utf8");
|
|
7785
|
+
return cachedApp;
|
|
7786
|
+
}
|
|
7787
|
+
function loadCss() {
|
|
7788
|
+
if (cachedCss) return cachedCss;
|
|
7789
|
+
cachedCss = readFileSync13(join11(ASSET_DIR, "app.css"), "utf8");
|
|
7790
|
+
return cachedCss;
|
|
7791
|
+
}
|
|
7792
|
+
function loadCm() {
|
|
7793
|
+
if (cachedCm) return cachedCm;
|
|
7794
|
+
cachedCm = readFileSync13(join11(ASSET_DIR, "codemirror.js"), "utf8");
|
|
7795
|
+
return cachedCm;
|
|
7796
|
+
}
|
|
7797
|
+
function renderIndexHtml(token, mode2) {
|
|
7798
|
+
const tpl = loadIndexTemplate();
|
|
7799
|
+
const safeToken = token.replace(/[^a-zA-Z0-9]/g, "");
|
|
7800
|
+
return tpl.replaceAll("__REASONIX_TOKEN__", safeToken).replaceAll("__REASONIX_MODE__", mode2);
|
|
7801
|
+
}
|
|
7802
|
+
function serveAsset(name) {
|
|
7803
|
+
if (name === "app.js") {
|
|
7804
|
+
return { body: loadApp(), contentType: "application/javascript; charset=utf-8" };
|
|
7805
|
+
}
|
|
7806
|
+
if (name === "app.css") {
|
|
7807
|
+
return { body: loadCss(), contentType: "text/css; charset=utf-8" };
|
|
7808
|
+
}
|
|
7809
|
+
if (name === "codemirror.js") {
|
|
7810
|
+
return { body: loadCm(), contentType: "application/javascript; charset=utf-8" };
|
|
7811
|
+
}
|
|
7812
|
+
return null;
|
|
7813
|
+
}
|
|
7814
|
+
|
|
7815
|
+
// src/server/api/abort.ts
|
|
7816
|
+
async function handleAbort(method, _rest, _body, ctx) {
|
|
7817
|
+
if (method !== "POST") {
|
|
7818
|
+
return { status: 405, body: { error: "POST only" } };
|
|
7819
|
+
}
|
|
7820
|
+
if (!ctx.abortTurn) {
|
|
7821
|
+
return {
|
|
7822
|
+
status: 503,
|
|
7823
|
+
body: { error: "abort requires an attached dashboard session." }
|
|
7824
|
+
};
|
|
7825
|
+
}
|
|
7826
|
+
ctx.abortTurn();
|
|
7827
|
+
ctx.audit?.({ ts: Date.now(), action: "abort-turn" });
|
|
7828
|
+
return { status: 202, body: { aborted: true } };
|
|
7829
|
+
}
|
|
7830
|
+
|
|
7831
|
+
// src/server/api/edit-mode.ts
|
|
7832
|
+
function parseBody(raw) {
|
|
7833
|
+
if (!raw) return {};
|
|
7834
|
+
try {
|
|
7835
|
+
const parsed = JSON.parse(raw);
|
|
7836
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
7837
|
+
} catch {
|
|
7838
|
+
return {};
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
var VALID = /* @__PURE__ */ new Set(["review", "auto", "yolo"]);
|
|
7842
|
+
async function handleEditMode(method, _rest, body, ctx) {
|
|
7843
|
+
if (method === "GET") {
|
|
7844
|
+
return {
|
|
7845
|
+
status: 200,
|
|
7846
|
+
body: { mode: ctx.getEditMode?.() ?? null }
|
|
7847
|
+
};
|
|
7848
|
+
}
|
|
7849
|
+
if (method === "POST") {
|
|
7850
|
+
if (!ctx.setEditMode) {
|
|
7851
|
+
return {
|
|
7852
|
+
status: 503,
|
|
7853
|
+
body: { error: "edit-mode mutation requires an attached `reasonix code` session." }
|
|
7854
|
+
};
|
|
7855
|
+
}
|
|
7856
|
+
const { mode: mode2 } = parseBody(body);
|
|
7857
|
+
if (typeof mode2 !== "string" || !VALID.has(mode2)) {
|
|
7858
|
+
return { status: 400, body: { error: "mode must be review | auto | yolo" } };
|
|
7859
|
+
}
|
|
7860
|
+
const resolved = ctx.setEditMode(mode2);
|
|
7861
|
+
ctx.audit?.({ ts: Date.now(), action: "set-edit-mode", payload: { mode: resolved } });
|
|
7862
|
+
return { status: 200, body: { mode: resolved } };
|
|
7863
|
+
}
|
|
7864
|
+
return { status: 405, body: { error: "GET or POST only" } };
|
|
7865
|
+
}
|
|
7866
|
+
|
|
7867
|
+
// src/server/api/file.ts
|
|
7868
|
+
import {
|
|
7869
|
+
closeSync,
|
|
7870
|
+
existsSync as existsSync11,
|
|
7871
|
+
mkdirSync as mkdirSync8,
|
|
7872
|
+
openSync,
|
|
7873
|
+
readFileSync as readFileSync14,
|
|
7874
|
+
readSync,
|
|
7875
|
+
statSync as statSync6,
|
|
7876
|
+
writeFileSync as writeFileSync7
|
|
7877
|
+
} from "fs";
|
|
7878
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, resolve as resolve7, sep as sep2 } from "path";
|
|
7879
|
+
var MAX_BYTES = 4 * 1024 * 1024;
|
|
7880
|
+
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
7881
|
+
function parseBody2(raw) {
|
|
7882
|
+
if (!raw) return {};
|
|
7883
|
+
try {
|
|
7884
|
+
const parsed = JSON.parse(raw);
|
|
7885
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
7886
|
+
} catch {
|
|
7887
|
+
return {};
|
|
7888
|
+
}
|
|
7889
|
+
}
|
|
7890
|
+
function safeResolve(root, requested) {
|
|
7891
|
+
const rootAbs = resolve7(root);
|
|
7892
|
+
const target = isAbsolute4(requested) ? resolve7(requested) : resolve7(rootAbs, requested);
|
|
7893
|
+
if (target !== rootAbs && !target.startsWith(rootAbs + sep2)) return null;
|
|
7894
|
+
return target;
|
|
7895
|
+
}
|
|
7896
|
+
function looksBinary(path5) {
|
|
7897
|
+
let fd = null;
|
|
7898
|
+
try {
|
|
7899
|
+
fd = openSync(path5, "r");
|
|
7900
|
+
const buf = Buffer.alloc(BINARY_PROBE_BYTES);
|
|
7901
|
+
const len = readSync(fd, buf, 0, BINARY_PROBE_BYTES, 0);
|
|
7902
|
+
for (let i = 0; i < len; i++) {
|
|
7903
|
+
if (buf[i] === 0) return true;
|
|
7904
|
+
}
|
|
7905
|
+
return false;
|
|
7906
|
+
} catch {
|
|
7907
|
+
return false;
|
|
7908
|
+
} finally {
|
|
7909
|
+
if (fd !== null) {
|
|
7910
|
+
try {
|
|
7911
|
+
closeSync(fd);
|
|
7912
|
+
} catch {
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7917
|
+
async function handleFiles(method, _rest, _body, ctx) {
|
|
7918
|
+
if (method !== "GET") {
|
|
7919
|
+
return { status: 405, body: { error: "GET only" } };
|
|
7920
|
+
}
|
|
7921
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
7922
|
+
if (!cwd2) {
|
|
7923
|
+
return {
|
|
7924
|
+
status: 503,
|
|
7925
|
+
body: { error: "no project root \u2014 open `/dashboard` from `reasonix code`" }
|
|
7926
|
+
};
|
|
7927
|
+
}
|
|
7928
|
+
const files = listFilesSync(cwd2, { maxResults: 5e3 });
|
|
7929
|
+
return {
|
|
7930
|
+
status: 200,
|
|
7931
|
+
body: {
|
|
7932
|
+
root: cwd2,
|
|
7933
|
+
count: files.length,
|
|
7934
|
+
truncated: files.length === 5e3,
|
|
7935
|
+
files
|
|
7936
|
+
}
|
|
7937
|
+
};
|
|
7938
|
+
}
|
|
7939
|
+
async function handleFile(method, rest, body, ctx) {
|
|
7940
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
7941
|
+
if (!cwd2) {
|
|
7942
|
+
return { status: 503, body: { error: "no project root" } };
|
|
7943
|
+
}
|
|
7944
|
+
const requested = rest.map((s) => decodeURIComponent(s)).join("/");
|
|
7945
|
+
if (!requested) {
|
|
7946
|
+
return { status: 400, body: { error: "path required (use /api/file/<path>)" } };
|
|
7947
|
+
}
|
|
7948
|
+
const target = safeResolve(cwd2, requested);
|
|
7949
|
+
if (!target) {
|
|
7950
|
+
return { status: 403, body: { error: "path escapes project root" } };
|
|
7951
|
+
}
|
|
7952
|
+
if (method === "GET") {
|
|
7953
|
+
if (!existsSync11(target)) {
|
|
7954
|
+
return { status: 404, body: { error: "file not found" } };
|
|
7955
|
+
}
|
|
7956
|
+
const stat = statSync6(target);
|
|
7957
|
+
if (stat.isDirectory()) {
|
|
7958
|
+
return { status: 400, body: { error: "path is a directory" } };
|
|
7959
|
+
}
|
|
7960
|
+
if (stat.size > MAX_BYTES) {
|
|
7961
|
+
return {
|
|
7962
|
+
status: 413,
|
|
7963
|
+
body: { error: `file too large (${stat.size} bytes; cap ${MAX_BYTES})` }
|
|
7964
|
+
};
|
|
7965
|
+
}
|
|
7966
|
+
if (looksBinary(target)) {
|
|
7967
|
+
return {
|
|
7968
|
+
status: 415,
|
|
7969
|
+
body: { error: "file appears to be binary \u2014 editor refuses to load." }
|
|
7970
|
+
};
|
|
7971
|
+
}
|
|
7972
|
+
const content = readFileSync14(target, "utf8");
|
|
7973
|
+
return {
|
|
7974
|
+
status: 200,
|
|
7975
|
+
body: {
|
|
7976
|
+
path: requested,
|
|
7977
|
+
absolute: target,
|
|
7978
|
+
size: stat.size,
|
|
7979
|
+
mtime: stat.mtime.getTime(),
|
|
7980
|
+
content
|
|
7981
|
+
}
|
|
7982
|
+
};
|
|
7983
|
+
}
|
|
7984
|
+
if (method === "POST") {
|
|
7985
|
+
const { content } = parseBody2(body);
|
|
7986
|
+
if (typeof content !== "string") {
|
|
7987
|
+
return { status: 400, body: { error: "content (string) required" } };
|
|
7988
|
+
}
|
|
7989
|
+
if (Buffer.byteLength(content, "utf8") > MAX_BYTES) {
|
|
7990
|
+
return { status: 413, body: { error: "content exceeds 4 MB cap" } };
|
|
7991
|
+
}
|
|
7992
|
+
if (existsSync11(target) && statSync6(target).isDirectory()) {
|
|
7993
|
+
return { status: 400, body: { error: "path is a directory" } };
|
|
7994
|
+
}
|
|
7995
|
+
const parent = dirname11(target);
|
|
7996
|
+
if (!existsSync11(parent)) {
|
|
7997
|
+
mkdirSync8(parent, { recursive: true });
|
|
7998
|
+
}
|
|
7999
|
+
writeFileSync7(target, content, "utf8");
|
|
8000
|
+
ctx.audit?.({
|
|
8001
|
+
ts: Date.now(),
|
|
8002
|
+
action: "save-file",
|
|
8003
|
+
payload: { path: requested, bytes: Buffer.byteLength(content, "utf8") }
|
|
8004
|
+
});
|
|
8005
|
+
const stat = statSync6(target);
|
|
8006
|
+
return {
|
|
8007
|
+
status: 200,
|
|
8008
|
+
body: {
|
|
8009
|
+
saved: true,
|
|
8010
|
+
path: requested,
|
|
8011
|
+
size: stat.size,
|
|
8012
|
+
mtime: stat.mtime.getTime()
|
|
8013
|
+
}
|
|
8014
|
+
};
|
|
8015
|
+
}
|
|
8016
|
+
return { status: 405, body: { error: "GET or POST only" } };
|
|
8017
|
+
}
|
|
8018
|
+
|
|
8019
|
+
// src/server/api/health.ts
|
|
8020
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync7 } from "fs";
|
|
8021
|
+
import { homedir as homedir6 } from "os";
|
|
8022
|
+
import { join as join12 } from "path";
|
|
8023
|
+
function dirSize(path5) {
|
|
8024
|
+
if (!existsSync12(path5)) return { path: path5, exists: false, fileCount: 0, totalBytes: 0 };
|
|
8025
|
+
let fileCount = 0;
|
|
8026
|
+
let totalBytes = 0;
|
|
8027
|
+
try {
|
|
8028
|
+
const entries = readdirSync4(path5);
|
|
8029
|
+
for (const name of entries) {
|
|
8030
|
+
const full = join12(path5, name);
|
|
8031
|
+
try {
|
|
8032
|
+
const s = statSync7(full);
|
|
8033
|
+
if (s.isFile()) {
|
|
8034
|
+
fileCount++;
|
|
8035
|
+
totalBytes += s.size;
|
|
8036
|
+
} else if (s.isDirectory()) {
|
|
8037
|
+
try {
|
|
8038
|
+
const inner = readdirSync4(full);
|
|
8039
|
+
for (const child of inner) {
|
|
8040
|
+
try {
|
|
8041
|
+
const cs = statSync7(join12(full, child));
|
|
8042
|
+
if (cs.isFile()) {
|
|
8043
|
+
fileCount++;
|
|
8044
|
+
totalBytes += cs.size;
|
|
8045
|
+
}
|
|
8046
|
+
} catch {
|
|
8047
|
+
}
|
|
8048
|
+
}
|
|
8049
|
+
} catch {
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
} catch {
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
8055
|
+
} catch {
|
|
8056
|
+
return { path: path5, exists: true, fileCount: 0, totalBytes: 0 };
|
|
8057
|
+
}
|
|
8058
|
+
return { path: path5, exists: true, fileCount, totalBytes };
|
|
8059
|
+
}
|
|
8060
|
+
async function handleHealth(method, _rest, _body, ctx) {
|
|
8061
|
+
if (method !== "GET") {
|
|
8062
|
+
return { status: 405, body: { error: "GET only" } };
|
|
8063
|
+
}
|
|
8064
|
+
const home = homedir6();
|
|
8065
|
+
const reasonixHome = join12(home, ".reasonix");
|
|
8066
|
+
const sessionsStat = dirSize(join12(reasonixHome, "sessions"));
|
|
8067
|
+
const memoryStat = dirSize(join12(reasonixHome, "memory"));
|
|
8068
|
+
const semanticStat = dirSize(join12(reasonixHome, "semantic"));
|
|
8069
|
+
let usageBytes = 0;
|
|
8070
|
+
if (existsSync12(ctx.usageLogPath)) {
|
|
8071
|
+
try {
|
|
8072
|
+
usageBytes = statSync7(ctx.usageLogPath).size;
|
|
8073
|
+
} catch {
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
const sessions2 = listSessions();
|
|
8077
|
+
return {
|
|
8078
|
+
status: 200,
|
|
8079
|
+
body: {
|
|
8080
|
+
version: VERSION,
|
|
8081
|
+
latestVersion: ctx.getLatestVersion?.() ?? null,
|
|
8082
|
+
reasonixHome,
|
|
8083
|
+
sessions: {
|
|
8084
|
+
path: sessionsStat.path,
|
|
8085
|
+
count: sessions2.length,
|
|
8086
|
+
totalBytes: sessionsStat.totalBytes
|
|
8087
|
+
},
|
|
8088
|
+
memory: {
|
|
8089
|
+
path: memoryStat.path,
|
|
8090
|
+
fileCount: memoryStat.fileCount,
|
|
8091
|
+
totalBytes: memoryStat.totalBytes
|
|
8092
|
+
},
|
|
8093
|
+
semantic: {
|
|
8094
|
+
path: semanticStat.path,
|
|
8095
|
+
exists: semanticStat.exists,
|
|
8096
|
+
fileCount: semanticStat.fileCount,
|
|
8097
|
+
totalBytes: semanticStat.totalBytes
|
|
8098
|
+
},
|
|
8099
|
+
usageLog: {
|
|
8100
|
+
path: ctx.usageLogPath,
|
|
8101
|
+
bytes: usageBytes
|
|
8102
|
+
},
|
|
8103
|
+
jobs: ctx.jobs ? ctx.jobs.list().length : null
|
|
8104
|
+
}
|
|
8105
|
+
};
|
|
8106
|
+
}
|
|
8107
|
+
|
|
8108
|
+
// src/server/api/hooks.ts
|
|
8109
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
8110
|
+
import { dirname as dirname12 } from "path";
|
|
8111
|
+
function parseBody3(raw) {
|
|
8112
|
+
if (!raw) return {};
|
|
8113
|
+
try {
|
|
8114
|
+
const parsed = JSON.parse(raw);
|
|
8115
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8116
|
+
} catch {
|
|
8117
|
+
return {};
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
function readSettingsFile2(path5) {
|
|
8121
|
+
if (!existsSync13(path5)) return {};
|
|
8122
|
+
try {
|
|
8123
|
+
const raw = readFileSync15(path5, "utf8");
|
|
8124
|
+
const parsed = JSON.parse(raw);
|
|
8125
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8126
|
+
} catch {
|
|
8127
|
+
return {};
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
function writeSettingsFile(path5, hooksBlock) {
|
|
8131
|
+
const existing = readSettingsFile2(path5);
|
|
8132
|
+
existing.hooks = hooksBlock;
|
|
8133
|
+
mkdirSync9(dirname12(path5), { recursive: true });
|
|
8134
|
+
writeFileSync8(path5, `${JSON.stringify(existing, null, 2)}
|
|
8135
|
+
`, "utf8");
|
|
8136
|
+
}
|
|
8137
|
+
async function handleHooks(method, rest, body, ctx) {
|
|
8138
|
+
if (method === "GET" && rest.length === 0) {
|
|
8139
|
+
const projectPath = ctx.getCurrentCwd ? projectSettingsPath(ctx.getCurrentCwd() ?? "") : null;
|
|
8140
|
+
const globalPath = globalSettingsPath();
|
|
8141
|
+
const projectFile = projectPath ? readSettingsFile2(projectPath) : {};
|
|
8142
|
+
const globalFile = readSettingsFile2(globalPath);
|
|
8143
|
+
const resolved = loadHooks({ projectRoot: ctx.getCurrentCwd?.() });
|
|
8144
|
+
return {
|
|
8145
|
+
status: 200,
|
|
8146
|
+
body: {
|
|
8147
|
+
project: {
|
|
8148
|
+
path: projectPath,
|
|
8149
|
+
hooks: projectFile.hooks ?? {}
|
|
8150
|
+
},
|
|
8151
|
+
global: {
|
|
8152
|
+
path: globalPath,
|
|
8153
|
+
hooks: globalFile.hooks ?? {}
|
|
8154
|
+
},
|
|
8155
|
+
resolved,
|
|
8156
|
+
events: HOOK_EVENTS
|
|
8157
|
+
}
|
|
8158
|
+
};
|
|
8159
|
+
}
|
|
8160
|
+
if (method === "POST" && rest[0] === "save") {
|
|
8161
|
+
const { scope, hooks: hooks2 } = parseBody3(body);
|
|
8162
|
+
if (scope !== "project" && scope !== "global") {
|
|
8163
|
+
return { status: 400, body: { error: "scope must be project | global" } };
|
|
8164
|
+
}
|
|
8165
|
+
if (typeof hooks2 !== "object" || hooks2 === null) {
|
|
8166
|
+
return { status: 400, body: { error: "hooks must be an object keyed by event name" } };
|
|
8167
|
+
}
|
|
8168
|
+
let path5;
|
|
8169
|
+
if (scope === "project") {
|
|
8170
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
8171
|
+
if (!cwd2) {
|
|
8172
|
+
return {
|
|
8173
|
+
status: 503,
|
|
8174
|
+
body: { error: "no active project \u2014 open `/dashboard` from inside `reasonix code`" }
|
|
8175
|
+
};
|
|
8176
|
+
}
|
|
8177
|
+
path5 = projectSettingsPath(cwd2);
|
|
8178
|
+
} else {
|
|
8179
|
+
path5 = globalSettingsPath();
|
|
8180
|
+
}
|
|
8181
|
+
if (!path5) {
|
|
8182
|
+
return { status: 500, body: { error: "could not resolve settings path" } };
|
|
8183
|
+
}
|
|
8184
|
+
writeSettingsFile(path5, hooks2);
|
|
8185
|
+
ctx.audit?.({ ts: Date.now(), action: "save-hooks", payload: { scope, path: path5 } });
|
|
8186
|
+
return { status: 200, body: { saved: true, path: path5 } };
|
|
8187
|
+
}
|
|
8188
|
+
if (method === "POST" && rest[0] === "reload") {
|
|
8189
|
+
if (!ctx.reloadHooks) {
|
|
8190
|
+
return {
|
|
8191
|
+
status: 503,
|
|
8192
|
+
body: { error: "reload requires an attached session \u2014 App.tsx wires the callback" }
|
|
8193
|
+
};
|
|
8194
|
+
}
|
|
8195
|
+
const count = ctx.reloadHooks();
|
|
8196
|
+
ctx.audit?.({ ts: Date.now(), action: "reload-hooks", payload: { count } });
|
|
8197
|
+
return { status: 200, body: { reloaded: true, count } };
|
|
8198
|
+
}
|
|
8199
|
+
return { status: 405, body: { error: `method ${method} not supported on this path` } };
|
|
8200
|
+
}
|
|
8201
|
+
|
|
8202
|
+
// src/server/api/mcp.ts
|
|
8203
|
+
function parseBody4(raw) {
|
|
8204
|
+
if (!raw) return {};
|
|
8205
|
+
try {
|
|
8206
|
+
const parsed = JSON.parse(raw);
|
|
8207
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8208
|
+
} catch {
|
|
8209
|
+
return {};
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8212
|
+
async function handleMcp(method, rest, body, ctx) {
|
|
8213
|
+
if (method === "GET" && rest.length === 0) {
|
|
8214
|
+
const servers = (ctx.mcpServers ?? []).map((s) => ({
|
|
8215
|
+
label: s.label,
|
|
8216
|
+
spec: s.spec,
|
|
8217
|
+
toolCount: s.toolCount,
|
|
8218
|
+
protocolVersion: s.report.protocolVersion,
|
|
8219
|
+
serverInfo: s.report.serverInfo,
|
|
8220
|
+
capabilities: s.report.capabilities,
|
|
8221
|
+
tools: s.report.tools.supported ? s.report.tools.items : [],
|
|
8222
|
+
resources: s.report.resources.supported ? s.report.resources.items : [],
|
|
8223
|
+
prompts: s.report.prompts.supported ? s.report.prompts.items : [],
|
|
8224
|
+
instructions: s.report.instructions ?? null
|
|
8225
|
+
}));
|
|
8226
|
+
return {
|
|
8227
|
+
status: 200,
|
|
8228
|
+
body: {
|
|
8229
|
+
servers,
|
|
8230
|
+
canHotReload: Boolean(ctx.reloadMcp),
|
|
8231
|
+
canInvoke: Boolean(ctx.invokeMcpTool)
|
|
8232
|
+
}
|
|
8233
|
+
};
|
|
8234
|
+
}
|
|
8235
|
+
if (method === "GET" && rest[0] === "specs") {
|
|
8236
|
+
const cfg = readConfig(ctx.configPath);
|
|
8237
|
+
return { status: 200, body: { specs: cfg.mcp ?? [] } };
|
|
8238
|
+
}
|
|
8239
|
+
if (method === "POST" && rest[0] === "specs") {
|
|
8240
|
+
const { spec } = parseBody4(body);
|
|
8241
|
+
if (typeof spec !== "string" || !spec.trim()) {
|
|
8242
|
+
return { status: 400, body: { error: "spec (non-empty string) required" } };
|
|
8243
|
+
}
|
|
8244
|
+
const cfg = readConfig(ctx.configPath);
|
|
8245
|
+
const list = cfg.mcp ?? [];
|
|
8246
|
+
if (list.includes(spec)) {
|
|
8247
|
+
return { status: 200, body: { added: false, alreadyPresent: true } };
|
|
8248
|
+
}
|
|
8249
|
+
cfg.mcp = [...list, spec.trim()];
|
|
8250
|
+
writeConfig(cfg, ctx.configPath);
|
|
8251
|
+
ctx.audit?.({ ts: Date.now(), action: "add-mcp-spec", payload: { spec } });
|
|
8252
|
+
return { status: 200, body: { added: true, requiresRestart: !ctx.reloadMcp } };
|
|
8253
|
+
}
|
|
8254
|
+
if (method === "DELETE" && rest[0] === "specs") {
|
|
8255
|
+
const { spec } = parseBody4(body);
|
|
8256
|
+
if (typeof spec !== "string") {
|
|
8257
|
+
return { status: 400, body: { error: "spec (string) required" } };
|
|
8258
|
+
}
|
|
8259
|
+
const cfg = readConfig(ctx.configPath);
|
|
8260
|
+
const list = cfg.mcp ?? [];
|
|
8261
|
+
if (!list.includes(spec)) {
|
|
8262
|
+
return { status: 200, body: { removed: false } };
|
|
8263
|
+
}
|
|
8264
|
+
cfg.mcp = list.filter((s) => s !== spec);
|
|
8265
|
+
writeConfig(cfg, ctx.configPath);
|
|
8266
|
+
ctx.audit?.({ ts: Date.now(), action: "remove-mcp-spec", payload: { spec } });
|
|
8267
|
+
return { status: 200, body: { removed: true, requiresRestart: !ctx.reloadMcp } };
|
|
8268
|
+
}
|
|
8269
|
+
if (method === "POST" && rest[0] === "reload") {
|
|
8270
|
+
if (!ctx.reloadMcp) {
|
|
8271
|
+
return {
|
|
8272
|
+
status: 503,
|
|
8273
|
+
body: {
|
|
8274
|
+
error: "live MCP reload not wired in this session \u2014 restart `reasonix code` to apply spec edits."
|
|
8275
|
+
}
|
|
8276
|
+
};
|
|
8277
|
+
}
|
|
8278
|
+
const count = await ctx.reloadMcp();
|
|
8279
|
+
return { status: 200, body: { reloaded: true, count } };
|
|
8280
|
+
}
|
|
8281
|
+
if (method === "POST" && rest[0] === "invoke") {
|
|
8282
|
+
if (!ctx.invokeMcpTool) {
|
|
8283
|
+
return {
|
|
8284
|
+
status: 503,
|
|
8285
|
+
body: { error: "MCP invocation requires an attached session." }
|
|
8286
|
+
};
|
|
8287
|
+
}
|
|
8288
|
+
const { server, tool: tool2, args } = parseBody4(body);
|
|
8289
|
+
if (typeof server !== "string" || typeof tool2 !== "string") {
|
|
8290
|
+
return { status: 400, body: { error: "server + tool (strings) required" } };
|
|
8291
|
+
}
|
|
8292
|
+
try {
|
|
8293
|
+
const result = await ctx.invokeMcpTool(
|
|
8294
|
+
server,
|
|
8295
|
+
tool2,
|
|
8296
|
+
typeof args === "object" && args !== null ? args : {}
|
|
8297
|
+
);
|
|
8298
|
+
return { status: 200, body: { result } };
|
|
8299
|
+
} catch (err) {
|
|
8300
|
+
return { status: 500, body: { error: err.message } };
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
8303
|
+
return { status: 405, body: { error: `method ${method} not supported on this path` } };
|
|
8304
|
+
}
|
|
8305
|
+
|
|
8306
|
+
// src/server/api/memory.ts
|
|
8307
|
+
import { createHash as createHash2 } from "crypto";
|
|
8308
|
+
import {
|
|
8309
|
+
existsSync as existsSync14,
|
|
8310
|
+
mkdirSync as mkdirSync10,
|
|
8311
|
+
readFileSync as readFileSync16,
|
|
8312
|
+
readdirSync as readdirSync5,
|
|
8313
|
+
statSync as statSync8,
|
|
8314
|
+
unlinkSync as unlinkSync5,
|
|
8315
|
+
writeFileSync as writeFileSync9
|
|
8316
|
+
} from "fs";
|
|
8317
|
+
import { homedir as homedir7 } from "os";
|
|
8318
|
+
import { dirname as dirname13, join as join13, resolve as resolvePath } from "path";
|
|
8319
|
+
function projectHash2(rootDir) {
|
|
8320
|
+
return createHash2("sha1").update(resolvePath(rootDir)).digest("hex").slice(0, 16);
|
|
8321
|
+
}
|
|
8322
|
+
function globalMemoryDir() {
|
|
8323
|
+
return join13(homedir7(), ".reasonix", "memory", "global");
|
|
8324
|
+
}
|
|
8325
|
+
function projectMemoryDir(rootDir) {
|
|
8326
|
+
return join13(homedir7(), ".reasonix", "memory", projectHash2(rootDir));
|
|
8327
|
+
}
|
|
8328
|
+
function parseBody5(raw) {
|
|
8329
|
+
if (!raw) return {};
|
|
8330
|
+
try {
|
|
8331
|
+
const parsed = JSON.parse(raw);
|
|
8332
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8333
|
+
} catch {
|
|
8334
|
+
return {};
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
8338
|
+
function listMemoryFiles(dir) {
|
|
8339
|
+
if (!existsSync14(dir)) return [];
|
|
8340
|
+
try {
|
|
8341
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
8342
|
+
const stat = statSync8(join13(dir, f));
|
|
8343
|
+
return {
|
|
8344
|
+
name: f.replace(/\.md$/, ""),
|
|
8345
|
+
size: stat.size,
|
|
8346
|
+
mtime: stat.mtime.getTime()
|
|
8347
|
+
};
|
|
8348
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
8349
|
+
} catch {
|
|
8350
|
+
return [];
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
async function handleMemory(method, rest, body, ctx) {
|
|
8354
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
8355
|
+
const globalDir = globalMemoryDir();
|
|
8356
|
+
const projectMemDir = cwd2 ? projectMemoryDir(cwd2) : "";
|
|
8357
|
+
if (method === "GET" && rest.length === 0) {
|
|
8358
|
+
const projectMemoryPath = cwd2 ? join13(cwd2, PROJECT_MEMORY_FILE) : null;
|
|
8359
|
+
const projectMemoryExists = projectMemoryPath ? existsSync14(projectMemoryPath) : false;
|
|
8360
|
+
return {
|
|
8361
|
+
status: 200,
|
|
8362
|
+
body: {
|
|
8363
|
+
project: {
|
|
8364
|
+
path: projectMemoryPath,
|
|
8365
|
+
exists: projectMemoryExists,
|
|
8366
|
+
file: PROJECT_MEMORY_FILE
|
|
8367
|
+
},
|
|
8368
|
+
global: {
|
|
8369
|
+
path: globalDir,
|
|
8370
|
+
files: listMemoryFiles(globalDir)
|
|
8371
|
+
},
|
|
8372
|
+
projectMem: {
|
|
8373
|
+
path: projectMemDir,
|
|
8374
|
+
files: projectMemDir ? listMemoryFiles(projectMemDir) : []
|
|
8375
|
+
}
|
|
8376
|
+
}
|
|
8377
|
+
};
|
|
8378
|
+
}
|
|
8379
|
+
const [scope, ...nameParts] = rest;
|
|
8380
|
+
const name = nameParts.join("/");
|
|
8381
|
+
if (method === "GET") {
|
|
8382
|
+
if (scope === "project") {
|
|
8383
|
+
if (!cwd2) return { status: 503, body: { error: "no active project" } };
|
|
8384
|
+
const path5 = join13(cwd2, PROJECT_MEMORY_FILE);
|
|
8385
|
+
if (!existsSync14(path5)) return { status: 404, body: { error: "REASONIX.md not found" } };
|
|
8386
|
+
return { status: 200, body: { path: path5, body: readFileSync16(path5, "utf8") } };
|
|
8387
|
+
}
|
|
8388
|
+
if ((scope === "global" || scope === "project-mem") && name && SAFE_NAME.test(name)) {
|
|
8389
|
+
const dir = scope === "global" ? globalDir : projectMemDir;
|
|
8390
|
+
if (!dir) return { status: 503, body: { error: "no project root for project-mem" } };
|
|
8391
|
+
const path5 = join13(dir, `${name}.md`);
|
|
8392
|
+
if (!existsSync14(path5)) return { status: 404, body: { error: "not found" } };
|
|
8393
|
+
return { status: 200, body: { path: path5, body: readFileSync16(path5, "utf8") } };
|
|
8394
|
+
}
|
|
8395
|
+
return { status: 400, body: { error: "bad scope or name" } };
|
|
8396
|
+
}
|
|
8397
|
+
if (method === "POST") {
|
|
8398
|
+
const { body: contents } = parseBody5(body);
|
|
8399
|
+
if (typeof contents !== "string") {
|
|
8400
|
+
return { status: 400, body: { error: "body (string) required" } };
|
|
8401
|
+
}
|
|
8402
|
+
if (scope === "project") {
|
|
8403
|
+
if (!cwd2) return { status: 503, body: { error: "no active project" } };
|
|
8404
|
+
const path5 = join13(cwd2, PROJECT_MEMORY_FILE);
|
|
8405
|
+
mkdirSync10(dirname13(path5), { recursive: true });
|
|
8406
|
+
writeFileSync9(path5, contents, "utf8");
|
|
8407
|
+
ctx.audit?.({ ts: Date.now(), action: "save-memory", payload: { scope, path: path5 } });
|
|
8408
|
+
return { status: 200, body: { saved: true, path: path5 } };
|
|
8409
|
+
}
|
|
8410
|
+
if ((scope === "global" || scope === "project-mem") && name && SAFE_NAME.test(name)) {
|
|
8411
|
+
const dir = scope === "global" ? globalDir : projectMemDir;
|
|
8412
|
+
if (!dir) return { status: 503, body: { error: "no project root for project-mem" } };
|
|
8413
|
+
mkdirSync10(dir, { recursive: true });
|
|
8414
|
+
const path5 = join13(dir, `${name}.md`);
|
|
8415
|
+
writeFileSync9(path5, contents, "utf8");
|
|
8416
|
+
ctx.audit?.({ ts: Date.now(), action: "save-memory", payload: { scope, name, path: path5 } });
|
|
8417
|
+
return { status: 200, body: { saved: true, path: path5 } };
|
|
8418
|
+
}
|
|
8419
|
+
return { status: 400, body: { error: "bad scope or name" } };
|
|
8420
|
+
}
|
|
8421
|
+
if (method === "DELETE") {
|
|
8422
|
+
if ((scope === "global" || scope === "project-mem") && name && SAFE_NAME.test(name)) {
|
|
8423
|
+
const dir = scope === "global" ? globalDir : projectMemDir;
|
|
8424
|
+
if (!dir) return { status: 503, body: { error: "no project root for project-mem" } };
|
|
8425
|
+
const path5 = join13(dir, `${name}.md`);
|
|
8426
|
+
if (existsSync14(path5)) {
|
|
8427
|
+
unlinkSync5(path5);
|
|
8428
|
+
ctx.audit?.({ ts: Date.now(), action: "delete-memory", payload: { scope, name, path: path5 } });
|
|
8429
|
+
return { status: 200, body: { deleted: true } };
|
|
8430
|
+
}
|
|
8431
|
+
return { status: 404, body: { error: "not found" } };
|
|
8432
|
+
}
|
|
8433
|
+
if (scope === "project") {
|
|
8434
|
+
if (!cwd2) return { status: 503, body: { error: "no active project" } };
|
|
8435
|
+
const path5 = join13(cwd2, PROJECT_MEMORY_FILE);
|
|
8436
|
+
if (existsSync14(path5)) {
|
|
8437
|
+
unlinkSync5(path5);
|
|
8438
|
+
ctx.audit?.({ ts: Date.now(), action: "delete-memory", payload: { scope, path: path5 } });
|
|
8439
|
+
return { status: 200, body: { deleted: true } };
|
|
8440
|
+
}
|
|
8441
|
+
return { status: 404, body: { error: "not found" } };
|
|
8442
|
+
}
|
|
8443
|
+
return { status: 400, body: { error: "bad scope or name" } };
|
|
8444
|
+
}
|
|
8445
|
+
return { status: 405, body: { error: `method ${method} not supported` } };
|
|
8446
|
+
}
|
|
8447
|
+
|
|
8448
|
+
// src/server/api/messages.ts
|
|
8449
|
+
async function handleMessages(method, _rest, _body, ctx) {
|
|
8450
|
+
if (method !== "GET") {
|
|
8451
|
+
return { status: 405, body: { error: "GET only" } };
|
|
8452
|
+
}
|
|
8453
|
+
const messages = ctx.getMessages ? ctx.getMessages() : [];
|
|
8454
|
+
return {
|
|
8455
|
+
status: 200,
|
|
8456
|
+
body: {
|
|
8457
|
+
messages,
|
|
8458
|
+
busy: ctx.isBusy ? ctx.isBusy() : false
|
|
8459
|
+
}
|
|
8460
|
+
};
|
|
8461
|
+
}
|
|
8462
|
+
|
|
8463
|
+
// src/server/api/modal.ts
|
|
8464
|
+
function parseBody6(raw) {
|
|
8465
|
+
if (!raw) return {};
|
|
8466
|
+
try {
|
|
8467
|
+
const parsed = JSON.parse(raw);
|
|
8468
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8469
|
+
} catch {
|
|
8470
|
+
return {};
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
async function handleModal(method, rest, body, ctx) {
|
|
8474
|
+
if (method === "GET" && rest.length === 0) {
|
|
8475
|
+
return {
|
|
8476
|
+
status: 200,
|
|
8477
|
+
body: { modal: ctx.getActiveModal ? ctx.getActiveModal() : null }
|
|
8478
|
+
};
|
|
8479
|
+
}
|
|
8480
|
+
if (method === "POST" && rest[0] === "resolve") {
|
|
8481
|
+
const { kind, choice, text } = parseBody6(body);
|
|
8482
|
+
if (kind === "shell") {
|
|
8483
|
+
if (!ctx.resolveShellConfirm) {
|
|
8484
|
+
return { status: 503, body: { error: "shell modal resolution not wired" } };
|
|
8485
|
+
}
|
|
8486
|
+
if (choice !== "run_once" && choice !== "always_allow" && choice !== "deny") {
|
|
8487
|
+
return {
|
|
8488
|
+
status: 400,
|
|
8489
|
+
body: { error: "shell choice must be run_once / always_allow / deny" }
|
|
8490
|
+
};
|
|
8491
|
+
}
|
|
8492
|
+
ctx.resolveShellConfirm(choice);
|
|
8493
|
+
return { status: 200, body: { resolved: true } };
|
|
8494
|
+
}
|
|
8495
|
+
if (kind === "choice") {
|
|
8496
|
+
if (!ctx.resolveChoiceConfirm) {
|
|
8497
|
+
return { status: 503, body: { error: "choice modal resolution not wired" } };
|
|
8498
|
+
}
|
|
8499
|
+
const c = choice;
|
|
8500
|
+
if (!c || typeof c !== "object") {
|
|
8501
|
+
return { status: 400, body: { error: "choice must be an object with a kind field" } };
|
|
8502
|
+
}
|
|
8503
|
+
if (c.kind === "pick" && typeof c.optionId === "string") {
|
|
8504
|
+
ctx.resolveChoiceConfirm({ kind: "pick", optionId: c.optionId });
|
|
8505
|
+
return { status: 200, body: { resolved: true } };
|
|
8506
|
+
}
|
|
8507
|
+
if (c.kind === "custom" && typeof c.text === "string") {
|
|
8508
|
+
ctx.resolveChoiceConfirm({ kind: "custom", text: c.text });
|
|
8509
|
+
return { status: 200, body: { resolved: true } };
|
|
8510
|
+
}
|
|
8511
|
+
if (c.kind === "cancel") {
|
|
8512
|
+
ctx.resolveChoiceConfirm({ kind: "cancel" });
|
|
8513
|
+
return { status: 200, body: { resolved: true } };
|
|
8514
|
+
}
|
|
8515
|
+
return { status: 400, body: { error: "unknown choice resolution shape" } };
|
|
8516
|
+
}
|
|
8517
|
+
if (kind === "plan") {
|
|
8518
|
+
if (!ctx.resolvePlanConfirm) {
|
|
8519
|
+
return { status: 503, body: { error: "plan modal resolution not wired" } };
|
|
8520
|
+
}
|
|
8521
|
+
if (choice !== "approve" && choice !== "refine" && choice !== "cancel") {
|
|
8522
|
+
return { status: 400, body: { error: "plan choice must be approve / refine / cancel" } };
|
|
8523
|
+
}
|
|
8524
|
+
ctx.resolvePlanConfirm(choice, typeof text === "string" && text.trim() ? text : void 0);
|
|
8525
|
+
return { status: 200, body: { resolved: true } };
|
|
8526
|
+
}
|
|
8527
|
+
if (kind === "edit-review") {
|
|
8528
|
+
if (!ctx.resolveEditReview) {
|
|
8529
|
+
return { status: 503, body: { error: "edit-review modal resolution not wired" } };
|
|
8530
|
+
}
|
|
8531
|
+
if (choice !== "apply" && choice !== "reject" && choice !== "apply-rest-of-turn" && choice !== "flip-to-auto") {
|
|
8532
|
+
return { status: 400, body: { error: "edit-review choice invalid" } };
|
|
8533
|
+
}
|
|
8534
|
+
ctx.resolveEditReview(choice);
|
|
8535
|
+
return { status: 200, body: { resolved: true } };
|
|
8536
|
+
}
|
|
8537
|
+
return { status: 400, body: { error: `unknown modal kind: ${String(kind)}` } };
|
|
8538
|
+
}
|
|
8539
|
+
return { status: 405, body: { error: `method ${method} not supported on this path` } };
|
|
8540
|
+
}
|
|
8541
|
+
|
|
8542
|
+
// src/server/api/overview.ts
|
|
8543
|
+
async function handleOverview(method, _rest, _body, ctx) {
|
|
8544
|
+
if (method !== "GET") {
|
|
8545
|
+
return { status: 405, body: { error: "GET only" } };
|
|
8546
|
+
}
|
|
8547
|
+
const cfg = readConfig(ctx.configPath);
|
|
8548
|
+
const overview = {
|
|
8549
|
+
version: VERSION,
|
|
8550
|
+
mode: ctx.mode,
|
|
8551
|
+
latestVersion: ctx.getLatestVersion?.() ?? null,
|
|
8552
|
+
session: ctx.getSessionName?.() ?? null,
|
|
8553
|
+
cwd: ctx.getCurrentCwd?.() ?? null,
|
|
8554
|
+
model: ctx.loop?.model ?? null,
|
|
8555
|
+
editMode: ctx.getEditMode?.() ?? null,
|
|
8556
|
+
planMode: ctx.getPlanMode?.() ?? null,
|
|
8557
|
+
pendingEdits: ctx.getPendingEditCount?.() ?? null,
|
|
8558
|
+
mcpServerCount: ctx.mcpServers?.length ?? null,
|
|
8559
|
+
toolCount: ctx.tools ? ctx.tools.size : null,
|
|
8560
|
+
preset: cfg.preset ?? "auto",
|
|
8561
|
+
reasoningEffort: cfg.reasoningEffort ?? "max",
|
|
8562
|
+
stats: ctx.getStats?.() ?? null
|
|
8563
|
+
};
|
|
8564
|
+
return { status: 200, body: overview };
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
// src/server/api/permissions.ts
|
|
8568
|
+
function parseBody7(raw) {
|
|
8569
|
+
if (!raw) return {};
|
|
8570
|
+
try {
|
|
8571
|
+
const parsed = JSON.parse(raw);
|
|
8572
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8573
|
+
} catch {
|
|
8574
|
+
return {};
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
async function handlePermissions(method, rest, body, ctx) {
|
|
8578
|
+
if (method === "GET" && rest.length === 0) {
|
|
8579
|
+
const cwd3 = ctx.getCurrentCwd?.();
|
|
8580
|
+
return {
|
|
8581
|
+
status: 200,
|
|
8582
|
+
body: {
|
|
8583
|
+
currentCwd: cwd3 ?? null,
|
|
8584
|
+
editMode: ctx.getEditMode?.() ?? null,
|
|
8585
|
+
builtin: [...BUILTIN_ALLOWLIST],
|
|
8586
|
+
project: cwd3 ? loadProjectShellAllowed(cwd3, ctx.configPath) : []
|
|
8587
|
+
}
|
|
8588
|
+
};
|
|
8589
|
+
}
|
|
8590
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
8591
|
+
if (!cwd2) {
|
|
8592
|
+
return {
|
|
8593
|
+
status: 503,
|
|
8594
|
+
body: {
|
|
8595
|
+
error: "no active project \u2014 mutations require an attached dashboard session (run `/dashboard` from inside `reasonix code`)."
|
|
8596
|
+
}
|
|
8597
|
+
};
|
|
8598
|
+
}
|
|
8599
|
+
if (method === "POST" && rest.length === 0) {
|
|
8600
|
+
const { prefix } = parseBody7(body);
|
|
8601
|
+
if (typeof prefix !== "string" || !prefix.trim()) {
|
|
8602
|
+
return { status: 400, body: { error: "prefix (string) required" } };
|
|
8603
|
+
}
|
|
8604
|
+
const trimmed = prefix.trim();
|
|
8605
|
+
if (BUILTIN_ALLOWLIST.includes(trimmed)) {
|
|
8606
|
+
return {
|
|
8607
|
+
status: 409,
|
|
8608
|
+
body: {
|
|
8609
|
+
error: `\`${trimmed}\` is already in the builtin allowlist \u2014 no project entry needed.`
|
|
8610
|
+
}
|
|
8611
|
+
};
|
|
8612
|
+
}
|
|
8613
|
+
const before = loadProjectShellAllowed(cwd2, ctx.configPath);
|
|
8614
|
+
if (before.includes(trimmed)) {
|
|
8615
|
+
return { status: 200, body: { added: false, prefix: trimmed, alreadyPresent: true } };
|
|
8616
|
+
}
|
|
8617
|
+
addProjectShellAllowed(cwd2, trimmed, ctx.configPath);
|
|
8618
|
+
ctx.audit?.({
|
|
8619
|
+
ts: Date.now(),
|
|
8620
|
+
action: "add-allowlist",
|
|
8621
|
+
payload: { prefix: trimmed, project: cwd2 }
|
|
8622
|
+
});
|
|
8623
|
+
return { status: 200, body: { added: true, prefix: trimmed } };
|
|
8624
|
+
}
|
|
8625
|
+
if (method === "DELETE" && rest.length === 0) {
|
|
8626
|
+
const { prefix } = parseBody7(body);
|
|
8627
|
+
if (typeof prefix !== "string" || !prefix.trim()) {
|
|
8628
|
+
return { status: 400, body: { error: "prefix (string) required" } };
|
|
8629
|
+
}
|
|
8630
|
+
const trimmed = prefix.trim();
|
|
8631
|
+
if (BUILTIN_ALLOWLIST.includes(trimmed)) {
|
|
8632
|
+
return {
|
|
8633
|
+
status: 409,
|
|
8634
|
+
body: {
|
|
8635
|
+
error: `\`${trimmed}\` is in the builtin allowlist (read-only); builtin entries can't be removed at runtime.`
|
|
8636
|
+
}
|
|
8637
|
+
};
|
|
8638
|
+
}
|
|
8639
|
+
const removed = removeProjectShellAllowed(cwd2, trimmed, ctx.configPath);
|
|
8640
|
+
if (removed) {
|
|
8641
|
+
ctx.audit?.({
|
|
8642
|
+
ts: Date.now(),
|
|
8643
|
+
action: "remove-allowlist",
|
|
8644
|
+
payload: { prefix: trimmed, project: cwd2 }
|
|
8645
|
+
});
|
|
8646
|
+
}
|
|
8647
|
+
return { status: 200, body: { removed, prefix: trimmed } };
|
|
8648
|
+
}
|
|
8649
|
+
if (method === "POST" && rest[0] === "clear") {
|
|
8650
|
+
const { confirm: confirm2 } = parseBody7(body);
|
|
8651
|
+
if (confirm2 !== true) {
|
|
8652
|
+
return {
|
|
8653
|
+
status: 400,
|
|
8654
|
+
body: {
|
|
8655
|
+
error: "clear requires { confirm: true } in the body \u2014 guards against accidental wipe."
|
|
8656
|
+
}
|
|
8657
|
+
};
|
|
8658
|
+
}
|
|
8659
|
+
const dropped = clearProjectShellAllowed(cwd2, ctx.configPath);
|
|
8660
|
+
if (dropped > 0) {
|
|
8661
|
+
ctx.audit?.({
|
|
8662
|
+
ts: Date.now(),
|
|
8663
|
+
action: "clear-allowlist",
|
|
8664
|
+
payload: { dropped, project: cwd2 }
|
|
8665
|
+
});
|
|
8666
|
+
}
|
|
8667
|
+
return { status: 200, body: { dropped } };
|
|
8668
|
+
}
|
|
8669
|
+
return { status: 405, body: { error: `method ${method} not supported on this path` } };
|
|
8670
|
+
}
|
|
8671
|
+
|
|
8672
|
+
// src/server/api/plans.ts
|
|
8673
|
+
async function handlePlans(method, _rest, _body, _ctx) {
|
|
8674
|
+
if (method !== "GET") {
|
|
8675
|
+
return { status: 405, body: { error: "GET only" } };
|
|
8676
|
+
}
|
|
8677
|
+
const out = [];
|
|
8678
|
+
for (const session of listSessions()) {
|
|
8679
|
+
const archives = listPlanArchives(session.name);
|
|
8680
|
+
for (const a of archives) {
|
|
8681
|
+
const total = a.steps.length;
|
|
8682
|
+
const done = a.completedStepIds.length;
|
|
8683
|
+
const row2 = {
|
|
8684
|
+
session: session.name,
|
|
8685
|
+
path: a.path,
|
|
8686
|
+
completedAt: a.completedAt,
|
|
8687
|
+
totalSteps: total,
|
|
8688
|
+
completedSteps: done,
|
|
8689
|
+
completionRatio: total > 0 ? done / total : 0,
|
|
8690
|
+
steps: a.steps,
|
|
8691
|
+
completedStepIds: a.completedStepIds
|
|
8692
|
+
};
|
|
8693
|
+
if (a.summary) row2.summary = a.summary;
|
|
8694
|
+
out.push(row2);
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
out.sort((a, b) => b.completedAt.localeCompare(a.completedAt));
|
|
8698
|
+
return { status: 200, body: { plans: out } };
|
|
8699
|
+
}
|
|
8700
|
+
|
|
8701
|
+
// src/server/api/sessions.ts
|
|
8702
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
|
|
8703
|
+
function parseTranscript2(path5, maxBytes = 4 * 1024 * 1024) {
|
|
8704
|
+
let raw;
|
|
8705
|
+
try {
|
|
8706
|
+
raw = readFileSync17(path5, "utf8");
|
|
8707
|
+
} catch {
|
|
8708
|
+
return [];
|
|
8709
|
+
}
|
|
8710
|
+
if (raw.length > maxBytes) raw = raw.slice(0, maxBytes);
|
|
8711
|
+
const out = [];
|
|
8712
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
8713
|
+
if (!line.trim()) continue;
|
|
8714
|
+
try {
|
|
8715
|
+
const rec = JSON.parse(line);
|
|
8716
|
+
const role = typeof rec.role === "string" ? rec.role : "unknown";
|
|
8717
|
+
const msg = { role };
|
|
8718
|
+
if (typeof rec.content === "string") msg.content = rec.content;
|
|
8719
|
+
else if (rec.content !== void 0) msg.content = JSON.stringify(rec.content);
|
|
8720
|
+
if (typeof rec.tool_name === "string") msg.toolName = rec.tool_name;
|
|
8721
|
+
if (typeof rec.toolName === "string") msg.toolName = rec.toolName;
|
|
8722
|
+
out.push(msg);
|
|
8723
|
+
} catch {
|
|
8724
|
+
}
|
|
8725
|
+
}
|
|
8726
|
+
return out;
|
|
8727
|
+
}
|
|
8728
|
+
async function handleSessions(method, rest, _body, _ctx) {
|
|
8729
|
+
if (method !== "GET") {
|
|
8730
|
+
return { status: 405, body: { error: "GET only" } };
|
|
8731
|
+
}
|
|
8732
|
+
if (rest.length === 0) {
|
|
8733
|
+
const sessions2 = listSessions();
|
|
8734
|
+
return {
|
|
8735
|
+
status: 200,
|
|
8736
|
+
body: {
|
|
8737
|
+
sessions: sessions2.map((s) => ({
|
|
8738
|
+
name: s.name,
|
|
8739
|
+
path: s.path,
|
|
8740
|
+
size: s.size,
|
|
8741
|
+
messageCount: s.messageCount,
|
|
8742
|
+
mtime: s.mtime.getTime()
|
|
8743
|
+
}))
|
|
8744
|
+
}
|
|
8745
|
+
};
|
|
8746
|
+
}
|
|
8747
|
+
const name = decodeURIComponent(rest[0]);
|
|
8748
|
+
const path5 = sessionPath(name);
|
|
8749
|
+
if (!existsSync15(path5)) {
|
|
8750
|
+
return { status: 404, body: { error: `no such session: ${name}` } };
|
|
8751
|
+
}
|
|
8752
|
+
const messages = parseTranscript2(path5);
|
|
8753
|
+
return {
|
|
8754
|
+
status: 200,
|
|
8755
|
+
body: {
|
|
8756
|
+
name,
|
|
8757
|
+
path: path5,
|
|
8758
|
+
messages,
|
|
8759
|
+
messageCount: messages.length
|
|
8760
|
+
}
|
|
8761
|
+
};
|
|
8762
|
+
}
|
|
8763
|
+
|
|
8764
|
+
// src/server/api/settings.ts
|
|
8765
|
+
function parseBody8(raw) {
|
|
8766
|
+
if (!raw) return {};
|
|
8767
|
+
try {
|
|
8768
|
+
const parsed = JSON.parse(raw);
|
|
8769
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8770
|
+
} catch {
|
|
8771
|
+
return {};
|
|
8772
|
+
}
|
|
8773
|
+
}
|
|
8774
|
+
var VALID_PRESETS = /* @__PURE__ */ new Set(["auto", "flash", "pro", "fast", "smart", "max"]);
|
|
8775
|
+
var VALID_EFFORTS = /* @__PURE__ */ new Set(["high", "max"]);
|
|
8776
|
+
async function handleSettings(method, _rest, body, ctx) {
|
|
8777
|
+
if (method === "GET") {
|
|
8778
|
+
const cfg = readConfig(ctx.configPath);
|
|
8779
|
+
return {
|
|
8780
|
+
status: 200,
|
|
8781
|
+
body: {
|
|
8782
|
+
apiKey: cfg.apiKey ? redactKey(cfg.apiKey) : null,
|
|
8783
|
+
apiKeySet: Boolean(cfg.apiKey),
|
|
8784
|
+
baseUrl: cfg.baseUrl ?? null,
|
|
8785
|
+
preset: cfg.preset ?? "auto",
|
|
8786
|
+
reasoningEffort: cfg.reasoningEffort ?? "max",
|
|
8787
|
+
search: cfg.search !== false,
|
|
8788
|
+
editMode: cfg.editMode ?? "review",
|
|
8789
|
+
session: cfg.session ?? null,
|
|
8790
|
+
model: ctx.loop?.model ?? null,
|
|
8791
|
+
// Hint to the SPA which fields require restart.
|
|
8792
|
+
appliesAt: {
|
|
8793
|
+
apiKey: "next-session",
|
|
8794
|
+
baseUrl: "next-session",
|
|
8795
|
+
preset: "next-session",
|
|
8796
|
+
reasoningEffort: "next-turn",
|
|
8797
|
+
search: "next-session"
|
|
8798
|
+
}
|
|
8799
|
+
}
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
if (method === "POST") {
|
|
8803
|
+
const fields = parseBody8(body);
|
|
8804
|
+
const cfg = readConfig(ctx.configPath);
|
|
8805
|
+
const changed = [];
|
|
8806
|
+
if (fields.apiKey !== void 0) {
|
|
8807
|
+
if (typeof fields.apiKey !== "string" || !isPlausibleKey(fields.apiKey)) {
|
|
8808
|
+
return { status: 400, body: { error: "apiKey must be a plausible sk- token" } };
|
|
8809
|
+
}
|
|
8810
|
+
saveApiKey(fields.apiKey, ctx.configPath);
|
|
8811
|
+
changed.push("apiKey");
|
|
8812
|
+
}
|
|
8813
|
+
if (fields.baseUrl !== void 0) {
|
|
8814
|
+
if (typeof fields.baseUrl !== "string" || !fields.baseUrl.trim()) {
|
|
8815
|
+
return { status: 400, body: { error: "baseUrl must be a non-empty string" } };
|
|
8816
|
+
}
|
|
8817
|
+
cfg.baseUrl = fields.baseUrl.trim();
|
|
8818
|
+
writeConfig(cfg, ctx.configPath);
|
|
8819
|
+
changed.push("baseUrl");
|
|
8820
|
+
}
|
|
8821
|
+
if (fields.preset !== void 0) {
|
|
8822
|
+
if (typeof fields.preset !== "string" || !VALID_PRESETS.has(fields.preset)) {
|
|
8823
|
+
return { status: 400, body: { error: "preset must be auto | flash | pro" } };
|
|
8824
|
+
}
|
|
8825
|
+
cfg.preset = fields.preset;
|
|
8826
|
+
writeConfig(cfg, ctx.configPath);
|
|
8827
|
+
ctx.applyPresetLive?.(fields.preset);
|
|
8828
|
+
changed.push("preset");
|
|
8829
|
+
}
|
|
8830
|
+
if (fields.reasoningEffort !== void 0) {
|
|
8831
|
+
if (typeof fields.reasoningEffort !== "string" || !VALID_EFFORTS.has(fields.reasoningEffort)) {
|
|
8832
|
+
return { status: 400, body: { error: "reasoningEffort must be high | max" } };
|
|
8833
|
+
}
|
|
8834
|
+
saveReasoningEffort(fields.reasoningEffort, ctx.configPath);
|
|
8835
|
+
ctx.applyEffortLive?.(fields.reasoningEffort);
|
|
8836
|
+
changed.push("reasoningEffort");
|
|
8837
|
+
}
|
|
8838
|
+
if (fields.search !== void 0) {
|
|
8839
|
+
if (typeof fields.search !== "boolean") {
|
|
8840
|
+
return { status: 400, body: { error: "search must be a boolean" } };
|
|
8841
|
+
}
|
|
8842
|
+
cfg.search = fields.search;
|
|
8843
|
+
writeConfig(cfg, ctx.configPath);
|
|
8844
|
+
changed.push("search");
|
|
8845
|
+
}
|
|
8846
|
+
if (changed.length > 0) {
|
|
8847
|
+
ctx.audit?.({ ts: Date.now(), action: "set-settings", payload: { fields: changed } });
|
|
8848
|
+
}
|
|
8849
|
+
return { status: 200, body: { changed } };
|
|
8850
|
+
}
|
|
8851
|
+
return { status: 405, body: { error: "GET or POST only" } };
|
|
8852
|
+
}
|
|
8853
|
+
|
|
8854
|
+
// src/server/api/skills.ts
|
|
8855
|
+
import {
|
|
8856
|
+
existsSync as existsSync16,
|
|
8857
|
+
mkdirSync as mkdirSync11,
|
|
8858
|
+
readFileSync as readFileSync18,
|
|
8859
|
+
readdirSync as readdirSync6,
|
|
8860
|
+
rmSync,
|
|
8861
|
+
statSync as statSync9,
|
|
8862
|
+
writeFileSync as writeFileSync10
|
|
8863
|
+
} from "fs";
|
|
8864
|
+
import { homedir as homedir8 } from "os";
|
|
8865
|
+
import { dirname as dirname14, join as join14 } from "path";
|
|
8866
|
+
function parseBody9(raw) {
|
|
8867
|
+
if (!raw) return {};
|
|
8868
|
+
try {
|
|
8869
|
+
const parsed = JSON.parse(raw);
|
|
8870
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8871
|
+
} catch {
|
|
8872
|
+
return {};
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8875
|
+
var SAFE_NAME2 = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
8876
|
+
function globalSkillsDir() {
|
|
8877
|
+
return join14(homedir8(), ".reasonix", SKILLS_DIRNAME);
|
|
8878
|
+
}
|
|
8879
|
+
function projectSkillsDir(rootDir) {
|
|
8880
|
+
return join14(rootDir, ".reasonix", SKILLS_DIRNAME);
|
|
8881
|
+
}
|
|
8882
|
+
function parseFrontmatterDescription(raw) {
|
|
8883
|
+
const lines = raw.split(/\r?\n/);
|
|
8884
|
+
if (lines[0] !== "---") return void 0;
|
|
8885
|
+
for (let i = 1; i < lines.length; i++) {
|
|
8886
|
+
if (lines[i] === "---") break;
|
|
8887
|
+
const m = lines[i].match(/^description:\s*(.*)$/);
|
|
8888
|
+
if (m) return m[1].trim();
|
|
8889
|
+
}
|
|
8890
|
+
return void 0;
|
|
8891
|
+
}
|
|
8892
|
+
function listSkills(dir, scope) {
|
|
8893
|
+
if (!existsSync16(dir)) return [];
|
|
8894
|
+
const out = [];
|
|
8895
|
+
try {
|
|
8896
|
+
for (const entry of readdirSync6(dir)) {
|
|
8897
|
+
if (!SAFE_NAME2.test(entry)) continue;
|
|
8898
|
+
const skillPath = join14(dir, entry, SKILL_FILE);
|
|
8899
|
+
if (!existsSync16(skillPath)) continue;
|
|
8900
|
+
try {
|
|
8901
|
+
const stat = statSync9(skillPath);
|
|
8902
|
+
const raw = readFileSync18(skillPath, "utf8");
|
|
8903
|
+
const item = {
|
|
8904
|
+
name: entry,
|
|
8905
|
+
scope,
|
|
8906
|
+
path: skillPath,
|
|
8907
|
+
size: stat.size,
|
|
8908
|
+
mtime: stat.mtime.getTime()
|
|
8909
|
+
};
|
|
8910
|
+
const desc = parseFrontmatterDescription(raw);
|
|
8911
|
+
if (desc) item.description = desc;
|
|
8912
|
+
out.push(item);
|
|
8913
|
+
} catch {
|
|
8914
|
+
}
|
|
8915
|
+
}
|
|
8916
|
+
} catch {
|
|
8917
|
+
}
|
|
8918
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
8919
|
+
}
|
|
8920
|
+
async function handleSkills(method, rest, body, ctx) {
|
|
8921
|
+
const cwd2 = ctx.getCurrentCwd?.();
|
|
8922
|
+
if (method === "GET" && rest.length === 0) {
|
|
8923
|
+
return {
|
|
8924
|
+
status: 200,
|
|
8925
|
+
body: {
|
|
8926
|
+
global: listSkills(globalSkillsDir(), "global"),
|
|
8927
|
+
project: cwd2 ? listSkills(projectSkillsDir(cwd2), "project") : [],
|
|
8928
|
+
builtin: [
|
|
8929
|
+
{ name: "explore", scope: "builtin", description: "subagent \u2014 broad codebase survey" },
|
|
8930
|
+
{
|
|
8931
|
+
name: "research",
|
|
8932
|
+
scope: "builtin",
|
|
8933
|
+
description: "subagent \u2014 deep web + repo research"
|
|
8934
|
+
}
|
|
8935
|
+
],
|
|
8936
|
+
paths: {
|
|
8937
|
+
global: globalSkillsDir(),
|
|
8938
|
+
project: cwd2 ? projectSkillsDir(cwd2) : null
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
};
|
|
8942
|
+
}
|
|
8943
|
+
const [scope, ...nameParts] = rest;
|
|
8944
|
+
const name = nameParts.join("/");
|
|
8945
|
+
if (!scope || !name || !SAFE_NAME2.test(name)) {
|
|
8946
|
+
return { status: 400, body: { error: "expected /api/skills/<scope>/<name>" } };
|
|
8947
|
+
}
|
|
8948
|
+
if (scope !== "project" && scope !== "global") {
|
|
8949
|
+
return {
|
|
8950
|
+
status: 400,
|
|
8951
|
+
body: { error: "scope must be project | global (builtin is read-only)" }
|
|
8952
|
+
};
|
|
8953
|
+
}
|
|
8954
|
+
let dir;
|
|
8955
|
+
if (scope === "project") {
|
|
8956
|
+
if (!cwd2) {
|
|
8957
|
+
return {
|
|
8958
|
+
status: 503,
|
|
8959
|
+
body: { error: "no active project \u2014 open `/dashboard` from `reasonix code`" }
|
|
8960
|
+
};
|
|
8961
|
+
}
|
|
8962
|
+
dir = projectSkillsDir(cwd2);
|
|
8963
|
+
} else {
|
|
8964
|
+
dir = globalSkillsDir();
|
|
8965
|
+
}
|
|
8966
|
+
const skillPath = join14(dir, name, SKILL_FILE);
|
|
8967
|
+
if (method === "GET") {
|
|
8968
|
+
if (!existsSync16(skillPath)) return { status: 404, body: { error: "skill not found" } };
|
|
8969
|
+
return { status: 200, body: { path: skillPath, body: readFileSync18(skillPath, "utf8") } };
|
|
8970
|
+
}
|
|
8971
|
+
if (method === "POST") {
|
|
8972
|
+
const { body: contents } = parseBody9(body);
|
|
8973
|
+
if (typeof contents !== "string") {
|
|
8974
|
+
return { status: 400, body: { error: "body (string) required" } };
|
|
8975
|
+
}
|
|
8976
|
+
mkdirSync11(dirname14(skillPath), { recursive: true });
|
|
8977
|
+
writeFileSync10(skillPath, contents, "utf8");
|
|
8978
|
+
ctx.audit?.({
|
|
8979
|
+
ts: Date.now(),
|
|
8980
|
+
action: "save-skill",
|
|
8981
|
+
payload: { scope, name, path: skillPath }
|
|
8982
|
+
});
|
|
8983
|
+
return { status: 200, body: { saved: true, path: skillPath } };
|
|
8984
|
+
}
|
|
8985
|
+
if (method === "DELETE") {
|
|
8986
|
+
if (!existsSync16(skillPath)) return { status: 404, body: { error: "skill not found" } };
|
|
8987
|
+
rmSync(dirname14(skillPath), { recursive: true, force: true });
|
|
8988
|
+
ctx.audit?.({ ts: Date.now(), action: "delete-skill", payload: { scope, name } });
|
|
8989
|
+
return { status: 200, body: { deleted: true } };
|
|
8990
|
+
}
|
|
8991
|
+
return { status: 405, body: { error: `method ${method} not supported` } };
|
|
8992
|
+
}
|
|
8993
|
+
|
|
8994
|
+
// src/server/api/submit.ts
|
|
8995
|
+
function parseBody10(raw) {
|
|
8996
|
+
if (!raw) return {};
|
|
8997
|
+
try {
|
|
8998
|
+
const parsed = JSON.parse(raw);
|
|
8999
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
9000
|
+
} catch {
|
|
9001
|
+
return {};
|
|
9002
|
+
}
|
|
9003
|
+
}
|
|
9004
|
+
async function handleSubmit(method, _rest, body, ctx) {
|
|
9005
|
+
if (method !== "POST") {
|
|
9006
|
+
return { status: 405, body: { error: "POST only" } };
|
|
9007
|
+
}
|
|
9008
|
+
if (!ctx.submitPrompt) {
|
|
9009
|
+
return {
|
|
9010
|
+
status: 503,
|
|
9011
|
+
body: {
|
|
9012
|
+
error: "submit requires an attached dashboard session \u2014 open `/dashboard` from inside `reasonix code` or `reasonix chat`."
|
|
9013
|
+
}
|
|
9014
|
+
};
|
|
9015
|
+
}
|
|
9016
|
+
const { prompt } = parseBody10(body);
|
|
9017
|
+
if (typeof prompt !== "string" || !prompt.trim()) {
|
|
9018
|
+
return { status: 400, body: { error: "prompt (non-empty string) required" } };
|
|
9019
|
+
}
|
|
9020
|
+
const result = ctx.submitPrompt(prompt);
|
|
9021
|
+
if (!result.accepted) {
|
|
9022
|
+
return {
|
|
9023
|
+
status: 409,
|
|
9024
|
+
body: { accepted: false, reason: result.reason ?? "loop is busy" }
|
|
9025
|
+
};
|
|
9026
|
+
}
|
|
9027
|
+
ctx.audit?.({
|
|
9028
|
+
ts: Date.now(),
|
|
9029
|
+
action: "submit-prompt",
|
|
9030
|
+
payload: { length: prompt.length }
|
|
9031
|
+
});
|
|
9032
|
+
return { status: 202, body: { accepted: true } };
|
|
9033
|
+
}
|
|
9034
|
+
|
|
9035
|
+
// src/server/api/tools.ts
|
|
9036
|
+
async function handleTools(method, _rest, _body, ctx) {
|
|
9037
|
+
if (method !== "GET") {
|
|
9038
|
+
return { status: 405, body: { error: "GET only" } };
|
|
9039
|
+
}
|
|
9040
|
+
if (!ctx.tools) {
|
|
9041
|
+
return {
|
|
9042
|
+
status: 503,
|
|
9043
|
+
body: {
|
|
9044
|
+
error: "live tools view requires an attached session \u2014 run `/dashboard` from inside `reasonix code` instead of standalone `reasonix dashboard`.",
|
|
9045
|
+
available: false
|
|
9046
|
+
}
|
|
9047
|
+
};
|
|
9048
|
+
}
|
|
9049
|
+
const specs = ctx.tools.specs();
|
|
9050
|
+
const items = specs.map((s) => {
|
|
9051
|
+
const def = ctx.tools.get(s.function.name);
|
|
9052
|
+
return {
|
|
9053
|
+
name: s.function.name,
|
|
9054
|
+
description: s.function.description,
|
|
9055
|
+
schema: s.function.parameters,
|
|
9056
|
+
readOnly: Boolean(def?.readOnly),
|
|
9057
|
+
flattened: ctx.tools.wasFlattened(s.function.name)
|
|
9058
|
+
};
|
|
9059
|
+
});
|
|
9060
|
+
return {
|
|
9061
|
+
status: 200,
|
|
9062
|
+
body: {
|
|
9063
|
+
planMode: ctx.tools.planMode,
|
|
9064
|
+
total: items.length,
|
|
9065
|
+
tools: items
|
|
9066
|
+
}
|
|
9067
|
+
};
|
|
9068
|
+
}
|
|
9069
|
+
|
|
9070
|
+
// src/server/api/usage.ts
|
|
9071
|
+
function dayKey(ts) {
|
|
9072
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
9073
|
+
}
|
|
9074
|
+
function buildSeries(records) {
|
|
9075
|
+
const map = /* @__PURE__ */ new Map();
|
|
9076
|
+
for (const r of records) {
|
|
9077
|
+
const day = dayKey(r.ts);
|
|
9078
|
+
let b = map.get(day);
|
|
9079
|
+
if (!b) {
|
|
9080
|
+
b = {
|
|
9081
|
+
day,
|
|
9082
|
+
turns: 0,
|
|
9083
|
+
promptTokens: 0,
|
|
9084
|
+
completionTokens: 0,
|
|
9085
|
+
cacheHitTokens: 0,
|
|
9086
|
+
cacheMissTokens: 0,
|
|
9087
|
+
costUsd: 0,
|
|
9088
|
+
cacheSavingsUsd: 0
|
|
9089
|
+
};
|
|
9090
|
+
map.set(day, b);
|
|
9091
|
+
}
|
|
9092
|
+
b.turns += 1;
|
|
9093
|
+
b.promptTokens += r.promptTokens;
|
|
9094
|
+
b.completionTokens += r.completionTokens;
|
|
9095
|
+
b.cacheHitTokens += r.cacheHitTokens;
|
|
9096
|
+
b.cacheMissTokens += r.cacheMissTokens;
|
|
9097
|
+
b.costUsd += r.costUsd;
|
|
9098
|
+
b.cacheSavingsUsd += cacheSavingsUsd(r.model, r.cacheHitTokens);
|
|
9099
|
+
}
|
|
9100
|
+
return Array.from(map.values()).sort((a, b) => a.day.localeCompare(b.day));
|
|
9101
|
+
}
|
|
9102
|
+
async function handleUsage(method, rest, _body, ctx) {
|
|
9103
|
+
if (method !== "GET") {
|
|
9104
|
+
return { status: 405, body: { error: "GET only" } };
|
|
9105
|
+
}
|
|
9106
|
+
const records = readUsageLog(ctx.usageLogPath);
|
|
9107
|
+
if (rest[0] === "series") {
|
|
9108
|
+
return {
|
|
9109
|
+
status: 200,
|
|
9110
|
+
body: {
|
|
9111
|
+
days: buildSeries(records),
|
|
9112
|
+
recordCount: records.length
|
|
9113
|
+
}
|
|
9114
|
+
};
|
|
9115
|
+
}
|
|
9116
|
+
const agg = aggregateUsage(records);
|
|
9117
|
+
return {
|
|
9118
|
+
status: 200,
|
|
9119
|
+
body: {
|
|
9120
|
+
logPath: ctx.usageLogPath,
|
|
9121
|
+
logSize: formatLogSize(ctx.usageLogPath),
|
|
9122
|
+
recordCount: records.length,
|
|
9123
|
+
buckets: agg.buckets,
|
|
9124
|
+
byModel: agg.byModel,
|
|
9125
|
+
bySession: agg.bySession,
|
|
9126
|
+
firstSeen: agg.firstSeen,
|
|
9127
|
+
lastSeen: agg.lastSeen,
|
|
9128
|
+
subagents: agg.subagents ?? null
|
|
9129
|
+
}
|
|
9130
|
+
};
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9133
|
+
// src/server/router.ts
|
|
9134
|
+
async function handleApi(pathTail, method, body, ctx) {
|
|
9135
|
+
const normalized = pathTail.replace(/\/+$/, "");
|
|
9136
|
+
const [head, ...rest] = normalized.split("/");
|
|
9137
|
+
try {
|
|
9138
|
+
switch (head) {
|
|
9139
|
+
case "overview":
|
|
9140
|
+
return await handleOverview(method, rest, body, ctx);
|
|
9141
|
+
case "usage":
|
|
9142
|
+
return await handleUsage(method, rest, body, ctx);
|
|
9143
|
+
case "tools":
|
|
9144
|
+
return await handleTools(method, rest, body, ctx);
|
|
9145
|
+
case "permissions":
|
|
9146
|
+
return await handlePermissions(method, rest, body, ctx);
|
|
9147
|
+
case "messages":
|
|
9148
|
+
return await handleMessages(method, rest, body, ctx);
|
|
9149
|
+
case "submit":
|
|
9150
|
+
return await handleSubmit(method, rest, body, ctx);
|
|
9151
|
+
case "abort":
|
|
9152
|
+
return await handleAbort(method, rest, body, ctx);
|
|
9153
|
+
case "health":
|
|
9154
|
+
return await handleHealth(method, rest, body, ctx);
|
|
9155
|
+
case "sessions":
|
|
9156
|
+
return await handleSessions(method, rest, body, ctx);
|
|
9157
|
+
case "plans":
|
|
9158
|
+
return await handlePlans(method, rest, body, ctx);
|
|
9159
|
+
case "modal":
|
|
9160
|
+
return await handleModal(method, rest, body, ctx);
|
|
9161
|
+
case "edit-mode":
|
|
9162
|
+
return await handleEditMode(method, rest, body, ctx);
|
|
9163
|
+
case "settings":
|
|
9164
|
+
return await handleSettings(method, rest, body, ctx);
|
|
9165
|
+
case "hooks":
|
|
9166
|
+
return await handleHooks(method, rest, body, ctx);
|
|
9167
|
+
case "memory":
|
|
9168
|
+
return await handleMemory(method, rest, body, ctx);
|
|
9169
|
+
case "skills":
|
|
9170
|
+
return await handleSkills(method, rest, body, ctx);
|
|
9171
|
+
case "mcp":
|
|
9172
|
+
return await handleMcp(method, rest, body, ctx);
|
|
9173
|
+
case "files":
|
|
9174
|
+
return await handleFiles(method, rest, body, ctx);
|
|
9175
|
+
case "file":
|
|
9176
|
+
return await handleFile(method, rest, body, ctx);
|
|
9177
|
+
default:
|
|
9178
|
+
return { status: 404, body: { error: `no such endpoint: /${head}` } };
|
|
9179
|
+
}
|
|
9180
|
+
} catch (err) {
|
|
9181
|
+
return {
|
|
9182
|
+
status: 500,
|
|
9183
|
+
body: { error: `handler crashed: ${err.message}` }
|
|
9184
|
+
};
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
|
|
9188
|
+
// src/server/index.ts
|
|
9189
|
+
function mintToken() {
|
|
9190
|
+
return randomBytes(32).toString("hex");
|
|
9191
|
+
}
|
|
9192
|
+
function constantTimeEquals(a, b) {
|
|
9193
|
+
if (a.length !== b.length) return false;
|
|
9194
|
+
let mismatch = 0;
|
|
9195
|
+
for (let i = 0; i < a.length; i++) {
|
|
9196
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
9197
|
+
}
|
|
9198
|
+
return mismatch === 0;
|
|
9199
|
+
}
|
|
9200
|
+
function checkAuth(req, expectedToken, isMutation) {
|
|
9201
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
9202
|
+
const queryToken = url.searchParams.get("token") ?? "";
|
|
9203
|
+
const headerToken = typeof req.headers["x-reasonix-token"] === "string" ? req.headers["x-reasonix-token"] : "";
|
|
9204
|
+
if (isMutation) {
|
|
9205
|
+
if (!headerToken || !constantTimeEquals(headerToken, expectedToken)) {
|
|
9206
|
+
return {
|
|
9207
|
+
status: 403,
|
|
9208
|
+
body: JSON.stringify({
|
|
9209
|
+
error: "mutation requires X-Reasonix-Token header (CSRF defence \u2014 query token alone is rejected for POST/DELETE)."
|
|
9210
|
+
})
|
|
9211
|
+
};
|
|
9212
|
+
}
|
|
9213
|
+
return null;
|
|
9214
|
+
}
|
|
9215
|
+
if (queryToken && constantTimeEquals(queryToken, expectedToken) || headerToken && constantTimeEquals(headerToken, expectedToken)) {
|
|
9216
|
+
return null;
|
|
9217
|
+
}
|
|
9218
|
+
return {
|
|
9219
|
+
status: 401,
|
|
9220
|
+
body: JSON.stringify({ error: "missing or invalid token" })
|
|
9221
|
+
};
|
|
9222
|
+
}
|
|
9223
|
+
var MAX_BODY_BYTES = 256 * 1024;
|
|
9224
|
+
async function readBody(req) {
|
|
9225
|
+
let total = 0;
|
|
9226
|
+
const chunks = [];
|
|
9227
|
+
return new Promise((resolve13, reject) => {
|
|
9228
|
+
req.on("data", (chunk) => {
|
|
9229
|
+
total += chunk.length;
|
|
9230
|
+
if (total > MAX_BODY_BYTES) {
|
|
9231
|
+
reject(new Error(`body exceeds ${MAX_BODY_BYTES} bytes`));
|
|
9232
|
+
req.destroy();
|
|
9233
|
+
return;
|
|
9234
|
+
}
|
|
9235
|
+
chunks.push(chunk);
|
|
9236
|
+
});
|
|
9237
|
+
req.on("end", () => resolve13(Buffer.concat(chunks).toString("utf8")));
|
|
9238
|
+
req.on("error", reject);
|
|
9239
|
+
});
|
|
9240
|
+
}
|
|
9241
|
+
async function dispatch(req, res, ctx, expectedToken) {
|
|
9242
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
9243
|
+
const path5 = url.pathname;
|
|
9244
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
9245
|
+
const isMutation = method === "POST" || method === "DELETE" || method === "PUT";
|
|
9246
|
+
if (path5 === "/" || path5 === "/index.html") {
|
|
9247
|
+
const fail = checkAuth(req, expectedToken, false);
|
|
9248
|
+
if (fail) {
|
|
9249
|
+
res.writeHead(fail.status, { "content-type": "text/plain" });
|
|
9250
|
+
res.end("unauthorized \u2014 open the URL printed by /dashboard, including ?token=\u2026");
|
|
9251
|
+
return;
|
|
9252
|
+
}
|
|
9253
|
+
const html = renderIndexHtml(expectedToken, ctx.mode);
|
|
9254
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
9255
|
+
res.end(html);
|
|
9256
|
+
return;
|
|
7611
9257
|
}
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
if (
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
9258
|
+
if (path5.startsWith("/assets/")) {
|
|
9259
|
+
const fail = checkAuth(req, expectedToken, false);
|
|
9260
|
+
if (fail) {
|
|
9261
|
+
res.writeHead(fail.status);
|
|
9262
|
+
res.end();
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
const asset = serveAsset(path5.slice("/assets/".length));
|
|
9266
|
+
if (!asset) {
|
|
9267
|
+
res.writeHead(404);
|
|
9268
|
+
res.end("not found");
|
|
9269
|
+
return;
|
|
9270
|
+
}
|
|
9271
|
+
res.writeHead(200, { "content-type": asset.contentType });
|
|
9272
|
+
res.end(asset.body);
|
|
9273
|
+
return;
|
|
9274
|
+
}
|
|
9275
|
+
if (path5 === "/api/events") {
|
|
9276
|
+
const fail = checkAuth(req, expectedToken, false);
|
|
9277
|
+
if (fail) {
|
|
9278
|
+
res.writeHead(fail.status, { "content-type": "application/json" });
|
|
9279
|
+
res.end(fail.body);
|
|
9280
|
+
return;
|
|
9281
|
+
}
|
|
9282
|
+
handleEvents(req, res, ctx);
|
|
9283
|
+
return;
|
|
9284
|
+
}
|
|
9285
|
+
if (path5.startsWith("/api/")) {
|
|
9286
|
+
const fail = checkAuth(req, expectedToken, isMutation);
|
|
9287
|
+
if (fail) {
|
|
9288
|
+
res.writeHead(fail.status, { "content-type": "application/json" });
|
|
9289
|
+
res.end(fail.body);
|
|
9290
|
+
return;
|
|
9291
|
+
}
|
|
9292
|
+
let body = "";
|
|
9293
|
+
if (isMutation) {
|
|
9294
|
+
try {
|
|
9295
|
+
body = await readBody(req);
|
|
9296
|
+
} catch (err) {
|
|
9297
|
+
res.writeHead(413, { "content-type": "application/json" });
|
|
9298
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
9299
|
+
return;
|
|
7633
9300
|
}
|
|
7634
|
-
const entry = { path: full, completedAt, steps, completedStepIds };
|
|
7635
|
-
if (typeof parsed.body === "string" && parsed.body) entry.body = parsed.body;
|
|
7636
|
-
if (typeof parsed.summary === "string" && parsed.summary) entry.summary = parsed.summary;
|
|
7637
|
-
summaries.push(entry);
|
|
7638
|
-
} catch {
|
|
7639
9301
|
}
|
|
9302
|
+
const result = await handleApi(path5.slice("/api/".length), method, body, ctx);
|
|
9303
|
+
res.writeHead(result.status, { "content-type": "application/json" });
|
|
9304
|
+
res.end(JSON.stringify(result.body));
|
|
9305
|
+
return;
|
|
7640
9306
|
}
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
}
|
|
7644
|
-
function
|
|
7645
|
-
const
|
|
7646
|
-
|
|
7647
|
-
const
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
9307
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
9308
|
+
res.end("not found");
|
|
9309
|
+
}
|
|
9310
|
+
function startDashboardServer(ctx, opts = {}) {
|
|
9311
|
+
const token = opts.token ?? mintToken();
|
|
9312
|
+
const host = opts.host ?? "127.0.0.1";
|
|
9313
|
+
const port = opts.port ?? 0;
|
|
9314
|
+
return new Promise((resolve13, reject) => {
|
|
9315
|
+
const server = createServer((req, res) => {
|
|
9316
|
+
dispatch(req, res, ctx, token).catch((err) => {
|
|
9317
|
+
if (!res.headersSent) {
|
|
9318
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
9319
|
+
}
|
|
9320
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
9321
|
+
});
|
|
9322
|
+
});
|
|
9323
|
+
server.on("error", reject);
|
|
9324
|
+
server.listen(port, host, () => {
|
|
9325
|
+
const addr = server.address();
|
|
9326
|
+
const finalPort = addr.port;
|
|
9327
|
+
const url = `http://${host}:${finalPort}/?token=${token}`;
|
|
9328
|
+
let closed = false;
|
|
9329
|
+
const close = () => new Promise((doneResolve) => {
|
|
9330
|
+
if (closed) return doneResolve();
|
|
9331
|
+
closed = true;
|
|
9332
|
+
server.close(() => doneResolve());
|
|
9333
|
+
setTimeout(() => server.closeAllConnections?.(), 1e3).unref();
|
|
9334
|
+
});
|
|
9335
|
+
resolve13({ url, token, port: finalPort, close });
|
|
9336
|
+
});
|
|
9337
|
+
});
|
|
7657
9338
|
}
|
|
7658
9339
|
|
|
7659
9340
|
// src/tools/skills.ts
|
|
@@ -7735,7 +9416,7 @@ ${skill2.body}${argsBlock}`;
|
|
|
7735
9416
|
}
|
|
7736
9417
|
|
|
7737
9418
|
// src/tools/workspace.ts
|
|
7738
|
-
import { existsSync as
|
|
9419
|
+
import { existsSync as existsSync17, statSync as statSync10 } from "fs";
|
|
7739
9420
|
import * as pathMod4 from "path";
|
|
7740
9421
|
var WorkspaceConfirmationError = class extends Error {
|
|
7741
9422
|
path;
|
|
@@ -7769,11 +9450,11 @@ function registerWorkspaceTool(registry) {
|
|
|
7769
9450
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7770
9451
|
const expanded = args.path.startsWith("~") && home ? pathMod4.join(home, args.path.slice(1)) : args.path;
|
|
7771
9452
|
const abs = pathMod4.resolve(expanded);
|
|
7772
|
-
if (!
|
|
9453
|
+
if (!existsSync17(abs)) {
|
|
7773
9454
|
throw new Error(`change_workspace: path does not exist \u2014 ${abs}`);
|
|
7774
9455
|
}
|
|
7775
9456
|
try {
|
|
7776
|
-
if (!
|
|
9457
|
+
if (!statSync10(abs).isDirectory()) {
|
|
7777
9458
|
throw new Error(`change_workspace: not a directory \u2014 ${abs}`);
|
|
7778
9459
|
}
|
|
7779
9460
|
} catch (err) {
|
|
@@ -8582,8 +10263,8 @@ function RiskLegend() {
|
|
|
8582
10263
|
var PlanStepList = React8.memo(PlanStepListInner);
|
|
8583
10264
|
|
|
8584
10265
|
// src/cli/ui/markdown.tsx
|
|
8585
|
-
import { readFileSync as
|
|
8586
|
-
import { isAbsolute as
|
|
10266
|
+
import { readFileSync as readFileSync19, statSync as statSync11 } from "fs";
|
|
10267
|
+
import { isAbsolute as isAbsolute5, join as join16 } from "path";
|
|
8587
10268
|
import { Box as Box8, Text as Text7 } from "ink";
|
|
8588
10269
|
import React9 from "react";
|
|
8589
10270
|
var SUPERSCRIPT = {
|
|
@@ -8824,7 +10505,7 @@ function validateCitation(url, projectRoot) {
|
|
|
8824
10505
|
const parts = parseCitationUrl(url);
|
|
8825
10506
|
if (!parts || !parts.path) return { ok: false, reason: "empty path" };
|
|
8826
10507
|
const normalized = parts.path.replace(/^[/\\]+/, "");
|
|
8827
|
-
const baseFullPath =
|
|
10508
|
+
const baseFullPath = isAbsolute5(normalized) ? normalized : join16(projectRoot, normalized);
|
|
8828
10509
|
const siblings = SIBLING_EXTENSIONS.get(extOf(baseFullPath)) ?? [];
|
|
8829
10510
|
const candidates = [
|
|
8830
10511
|
baseFullPath,
|
|
@@ -8834,7 +10515,7 @@ function validateCitation(url, projectRoot) {
|
|
|
8834
10515
|
let stat = null;
|
|
8835
10516
|
for (const candidate of candidates) {
|
|
8836
10517
|
try {
|
|
8837
|
-
stat =
|
|
10518
|
+
stat = statSync11(candidate);
|
|
8838
10519
|
fullPath = candidate;
|
|
8839
10520
|
break;
|
|
8840
10521
|
} catch {
|
|
@@ -8845,7 +10526,7 @@ function validateCitation(url, projectRoot) {
|
|
|
8845
10526
|
if (parts.startLine === void 0) return { ok: true };
|
|
8846
10527
|
let lineCount;
|
|
8847
10528
|
try {
|
|
8848
|
-
lineCount =
|
|
10529
|
+
lineCount = readFileSync19(fullPath, "utf8").split("\n").length;
|
|
8849
10530
|
} catch {
|
|
8850
10531
|
return { ok: false, reason: "unreadable" };
|
|
8851
10532
|
}
|
|
@@ -11363,9 +13044,9 @@ function describeRepair(repair) {
|
|
|
11363
13044
|
}
|
|
11364
13045
|
|
|
11365
13046
|
// src/cli/ui/hash-memory.ts
|
|
11366
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
11367
|
-
import { homedir as
|
|
11368
|
-
import { dirname as
|
|
13047
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
13048
|
+
import { homedir as homedir9 } from "os";
|
|
13049
|
+
import { dirname as dirname15, join as join17 } from "path";
|
|
11369
13050
|
var PROJECT_HEADER = `# Reasonix project memory
|
|
11370
13051
|
|
|
11371
13052
|
Notes the user pinned via the \`#\` prompt prefix. The whole file is
|
|
@@ -11397,12 +13078,12 @@ function detectHashMemory(text) {
|
|
|
11397
13078
|
return { kind: "memory", note: body };
|
|
11398
13079
|
}
|
|
11399
13080
|
function appendProjectMemory(rootDir, note) {
|
|
11400
|
-
return appendBulletToFile(
|
|
13081
|
+
return appendBulletToFile(join17(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
|
|
11401
13082
|
}
|
|
11402
13083
|
var GLOBAL_MEMORY_DIR = ".reasonix";
|
|
11403
13084
|
var GLOBAL_MEMORY_FILE = "REASONIX.md";
|
|
11404
|
-
function globalMemoryPath(homeDir =
|
|
11405
|
-
return
|
|
13085
|
+
function globalMemoryPath(homeDir = homedir9()) {
|
|
13086
|
+
return join17(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
|
|
11406
13087
|
}
|
|
11407
13088
|
function appendGlobalMemory(note, homeDir) {
|
|
11408
13089
|
return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
|
|
@@ -11412,14 +13093,14 @@ function appendBulletToFile(path5, note, newFileHeader) {
|
|
|
11412
13093
|
if (!trimmed) throw new Error("note body cannot be empty");
|
|
11413
13094
|
const bullet = `- ${trimmed}
|
|
11414
13095
|
`;
|
|
11415
|
-
if (!
|
|
11416
|
-
|
|
11417
|
-
|
|
13096
|
+
if (!existsSync18(path5)) {
|
|
13097
|
+
mkdirSync12(dirname15(path5), { recursive: true });
|
|
13098
|
+
writeFileSync11(path5, `${newFileHeader}${bullet}`, "utf8");
|
|
11418
13099
|
return { path: path5, created: true };
|
|
11419
13100
|
}
|
|
11420
13101
|
let prefix = "";
|
|
11421
13102
|
try {
|
|
11422
|
-
const existing =
|
|
13103
|
+
const existing = readFileSync20(path5, "utf8");
|
|
11423
13104
|
if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
|
|
11424
13105
|
} catch {
|
|
11425
13106
|
}
|
|
@@ -11690,6 +13371,61 @@ function formatBytes2(n) {
|
|
|
11690
13371
|
return `${mb.toFixed(mb >= 10 ? 0 : 1)} MB`;
|
|
11691
13372
|
}
|
|
11692
13373
|
|
|
13374
|
+
// src/cli/ui/presets.ts
|
|
13375
|
+
var PRESETS = {
|
|
13376
|
+
// auto — flash baseline + auto-escalate to pro when the model emits
|
|
13377
|
+
// <<<NEEDS_PRO>>> OR after 3+ tool failure signals in one turn.
|
|
13378
|
+
// The default: cheap when easy, smart when hard.
|
|
13379
|
+
auto: {
|
|
13380
|
+
model: "deepseek-v4-flash",
|
|
13381
|
+
reasoningEffort: "max",
|
|
13382
|
+
autoEscalate: true,
|
|
13383
|
+
harvest: false,
|
|
13384
|
+
branch: 1
|
|
13385
|
+
},
|
|
13386
|
+
// flash — always flash, never escalate. `/pro` still arms a single
|
|
13387
|
+
// manual turn; auto-promotion is the thing this disables. Use when
|
|
13388
|
+
// you want predictable cost per turn.
|
|
13389
|
+
flash: {
|
|
13390
|
+
model: "deepseek-v4-flash",
|
|
13391
|
+
reasoningEffort: "max",
|
|
13392
|
+
autoEscalate: false,
|
|
13393
|
+
harvest: false,
|
|
13394
|
+
branch: 1
|
|
13395
|
+
},
|
|
13396
|
+
// pro — always pro. Hard pin; the model never downgrades. Use for
|
|
13397
|
+
// multi-turn architecture work where flash is just going to keep
|
|
13398
|
+
// escalating anyway and the back-and-forth wastes turns.
|
|
13399
|
+
pro: {
|
|
13400
|
+
model: "deepseek-v4-pro",
|
|
13401
|
+
reasoningEffort: "max",
|
|
13402
|
+
autoEscalate: false,
|
|
13403
|
+
harvest: false,
|
|
13404
|
+
branch: 1
|
|
13405
|
+
}
|
|
13406
|
+
};
|
|
13407
|
+
var PRESET_DESCRIPTIONS = {
|
|
13408
|
+
auto: {
|
|
13409
|
+
headline: "flash \u2192 pro on hard turns",
|
|
13410
|
+
cost: "default \xB7 ~96% turns stay on flash \xB7 pro kicks in only when needed"
|
|
13411
|
+
},
|
|
13412
|
+
flash: {
|
|
13413
|
+
headline: "v4-flash always",
|
|
13414
|
+
cost: "cheapest \xB7 predictable \xB7 /pro still works for a one-turn bump"
|
|
13415
|
+
},
|
|
13416
|
+
pro: {
|
|
13417
|
+
headline: "v4-pro always",
|
|
13418
|
+
cost: "~3\xD7 flash (5/31 discount) / ~12\xD7 full price \xB7 for hard multi-turn work"
|
|
13419
|
+
}
|
|
13420
|
+
};
|
|
13421
|
+
function resolvePreset(name) {
|
|
13422
|
+
if (name === "auto" || name === "flash" || name === "pro") return PRESETS[name];
|
|
13423
|
+
if (name === "fast") return { ...PRESETS.flash, reasoningEffort: "high" };
|
|
13424
|
+
if (name === "smart") return PRESETS.auto;
|
|
13425
|
+
if (name === "max") return PRESETS.pro;
|
|
13426
|
+
return PRESETS.auto;
|
|
13427
|
+
}
|
|
13428
|
+
|
|
11693
13429
|
// src/cli/ui/slash/commands.ts
|
|
11694
13430
|
var SLASH_COMMANDS = [
|
|
11695
13431
|
{ cmd: "help", summary: "show the full command reference" },
|
|
@@ -11766,6 +13502,12 @@ var SLASH_COMMANDS = [
|
|
|
11766
13502
|
summary: "show / edit shell allowlist (builtin read-only \xB7 per-project: ~/.reasonix/config.json)",
|
|
11767
13503
|
argCompleter: ["list", "add", "remove", "clear"]
|
|
11768
13504
|
},
|
|
13505
|
+
{
|
|
13506
|
+
cmd: "dashboard",
|
|
13507
|
+
argsHint: "[stop]",
|
|
13508
|
+
summary: "launch the embedded web dashboard (127.0.0.1, token-gated)",
|
|
13509
|
+
argCompleter: ["stop"]
|
|
13510
|
+
},
|
|
11769
13511
|
{
|
|
11770
13512
|
cmd: "cwd",
|
|
11771
13513
|
argsHint: "<path>",
|
|
@@ -11925,11 +13667,11 @@ function parseSlash(text) {
|
|
|
11925
13667
|
}
|
|
11926
13668
|
|
|
11927
13669
|
// src/cli/ui/slash/handlers/admin.ts
|
|
11928
|
-
import { existsSync as
|
|
13670
|
+
import { existsSync as existsSync20, statSync as statSync12 } from "fs";
|
|
11929
13671
|
import * as pathMod5 from "path";
|
|
11930
13672
|
|
|
11931
13673
|
// src/cli/commands/stats.ts
|
|
11932
|
-
import { existsSync as
|
|
13674
|
+
import { existsSync as existsSync19, readFileSync as readFileSync21 } from "fs";
|
|
11933
13675
|
function statsCommand(opts) {
|
|
11934
13676
|
if (opts.transcript) {
|
|
11935
13677
|
transcriptSummary(opts.transcript);
|
|
@@ -11938,11 +13680,11 @@ function statsCommand(opts) {
|
|
|
11938
13680
|
dashboard(opts);
|
|
11939
13681
|
}
|
|
11940
13682
|
function transcriptSummary(path5) {
|
|
11941
|
-
if (!
|
|
13683
|
+
if (!existsSync19(path5)) {
|
|
11942
13684
|
console.error(`no such transcript: ${path5}`);
|
|
11943
13685
|
process.exit(1);
|
|
11944
13686
|
}
|
|
11945
|
-
const lines =
|
|
13687
|
+
const lines = readFileSync21(path5, "utf8").split(/\r?\n/).filter(Boolean);
|
|
11946
13688
|
let assistantTurns = 0;
|
|
11947
13689
|
let toolCalls = 0;
|
|
11948
13690
|
let lastTurn = 0;
|
|
@@ -12031,12 +13773,13 @@ function header() {
|
|
|
12031
13773
|
pad("turns", 8, "right"),
|
|
12032
13774
|
pad("cache hit", 10, "right"),
|
|
12033
13775
|
pad("cost (USD)", 14, "right"),
|
|
13776
|
+
pad("cache saved", 14, "right"),
|
|
12034
13777
|
pad("vs Claude", 14, "right"),
|
|
12035
13778
|
pad("saved", 10, "right")
|
|
12036
13779
|
].join(" ");
|
|
12037
13780
|
}
|
|
12038
13781
|
function divider() {
|
|
12039
|
-
return "-".repeat(
|
|
13782
|
+
return "-".repeat(86);
|
|
12040
13783
|
}
|
|
12041
13784
|
function bucketRow(b) {
|
|
12042
13785
|
const hit = bucketCacheHitRatio(b);
|
|
@@ -12046,6 +13789,11 @@ function bucketRow(b) {
|
|
|
12046
13789
|
pad(b.turns.toString(), 8, "right"),
|
|
12047
13790
|
pad(b.turns > 0 ? `${(hit * 100).toFixed(1)}%` : "\u2014", 10, "right"),
|
|
12048
13791
|
pad(b.turns > 0 ? `$${b.costUsd.toFixed(6)}` : "\u2014", 14, "right"),
|
|
13792
|
+
pad(
|
|
13793
|
+
b.turns > 0 && b.cacheSavingsUsd > 0 ? `$${b.cacheSavingsUsd.toFixed(4)}` : "\u2014",
|
|
13794
|
+
14,
|
|
13795
|
+
"right"
|
|
13796
|
+
),
|
|
12049
13797
|
pad(b.turns > 0 ? `$${b.claudeEquivUsd.toFixed(4)}` : "\u2014", 14, "right"),
|
|
12050
13798
|
pad(b.turns > 0 && savings > 0 ? `${(savings * 100).toFixed(1)}%` : "\u2014", 10, "right")
|
|
12051
13799
|
].join(" ");
|
|
@@ -12177,12 +13925,12 @@ var cwd = (args, _loop, ctx) => {
|
|
|
12177
13925
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12178
13926
|
const expanded = raw.startsWith("~") && home ? pathMod5.join(home, raw.slice(1)) : raw;
|
|
12179
13927
|
const abs = pathMod5.resolve(expanded);
|
|
12180
|
-
if (!
|
|
13928
|
+
if (!existsSync20(abs)) {
|
|
12181
13929
|
return { info: `\u25B8 /cwd: path does not exist \u2014 ${abs}` };
|
|
12182
13930
|
}
|
|
12183
13931
|
let isDir = false;
|
|
12184
13932
|
try {
|
|
12185
|
-
isDir =
|
|
13933
|
+
isDir = statSync12(abs).isDirectory();
|
|
12186
13934
|
} catch {
|
|
12187
13935
|
}
|
|
12188
13936
|
if (!isDir) {
|
|
@@ -12412,6 +14160,50 @@ var handlers2 = {
|
|
|
12412
14160
|
loop
|
|
12413
14161
|
};
|
|
12414
14162
|
|
|
14163
|
+
// src/cli/ui/slash/handlers/dashboard.ts
|
|
14164
|
+
var dashboard2 = (args, _loop, ctx) => {
|
|
14165
|
+
if (!ctx.startDashboard || !ctx.getDashboardUrl) {
|
|
14166
|
+
return {
|
|
14167
|
+
info: "/dashboard is not available in this context (no startDashboard callback wired)."
|
|
14168
|
+
};
|
|
14169
|
+
}
|
|
14170
|
+
const sub = (args[0] ?? "").toLowerCase();
|
|
14171
|
+
if (sub === "stop" || sub === "off") {
|
|
14172
|
+
if (!ctx.stopDashboard) {
|
|
14173
|
+
return { info: "/dashboard stop: no stop callback wired." };
|
|
14174
|
+
}
|
|
14175
|
+
const url = ctx.getDashboardUrl();
|
|
14176
|
+
if (!url) return { info: "\u25B8 dashboard is not running." };
|
|
14177
|
+
ctx.stopDashboard();
|
|
14178
|
+
return { info: "\u25B8 dashboard stopping\u2026" };
|
|
14179
|
+
}
|
|
14180
|
+
const existing = ctx.getDashboardUrl();
|
|
14181
|
+
if (existing) {
|
|
14182
|
+
return {
|
|
14183
|
+
info: [
|
|
14184
|
+
"\u25B8 dashboard is already running:",
|
|
14185
|
+
` ${existing}`,
|
|
14186
|
+
"",
|
|
14187
|
+
"Open it in any browser. Type `/dashboard stop` to tear it down."
|
|
14188
|
+
].join("\n")
|
|
14189
|
+
};
|
|
14190
|
+
}
|
|
14191
|
+
ctx.startDashboard().then((url) => {
|
|
14192
|
+
ctx.postInfo?.(
|
|
14193
|
+
[
|
|
14194
|
+
"\u25B8 dashboard ready:",
|
|
14195
|
+
` ${url}`,
|
|
14196
|
+
"",
|
|
14197
|
+
"127.0.0.1 only \xB7 token-gated. Type `/dashboard stop` to shut down."
|
|
14198
|
+
].join("\n")
|
|
14199
|
+
);
|
|
14200
|
+
}).catch((err) => {
|
|
14201
|
+
ctx.postInfo?.(`\u25B8 dashboard failed to start: ${err.message}`);
|
|
14202
|
+
});
|
|
14203
|
+
return { info: "\u25B8 starting dashboard server\u2026" };
|
|
14204
|
+
};
|
|
14205
|
+
var handlers3 = { dashboard: dashboard2 };
|
|
14206
|
+
|
|
12415
14207
|
// src/cli/ui/slash/helpers.ts
|
|
12416
14208
|
import { spawnSync } from "child_process";
|
|
12417
14209
|
function resolveMemoryTarget(store, raw) {
|
|
@@ -12640,7 +14432,7 @@ var walk2 = (_args, _loop, ctx) => {
|
|
|
12640
14432
|
}
|
|
12641
14433
|
return { info: ctx.startWalkthrough() };
|
|
12642
14434
|
};
|
|
12643
|
-
var
|
|
14435
|
+
var handlers4 = {
|
|
12644
14436
|
undo,
|
|
12645
14437
|
history,
|
|
12646
14438
|
show,
|
|
@@ -12655,7 +14447,7 @@ var handlers3 = {
|
|
|
12655
14447
|
};
|
|
12656
14448
|
|
|
12657
14449
|
// src/cli/ui/slash/handlers/init.ts
|
|
12658
|
-
import { existsSync as
|
|
14450
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12659
14451
|
import * as pathMod6 from "path";
|
|
12660
14452
|
var INIT_PROMPT = [
|
|
12661
14453
|
"# Task: Initialize REASONIX.md",
|
|
@@ -12727,7 +14519,7 @@ var init = (args, _loop, ctx) => {
|
|
|
12727
14519
|
}
|
|
12728
14520
|
const force = (args[0] ?? "").toLowerCase() === "force";
|
|
12729
14521
|
const target = pathMod6.join(ctx.codeRoot, "REASONIX.md");
|
|
12730
|
-
if (
|
|
14522
|
+
if (existsSync21(target) && !force) {
|
|
12731
14523
|
return {
|
|
12732
14524
|
info: [
|
|
12733
14525
|
`\u25B8 REASONIX.md already exists at ${target}`,
|
|
@@ -12747,7 +14539,7 @@ var init = (args, _loop, ctx) => {
|
|
|
12747
14539
|
resubmit: INIT_PROMPT
|
|
12748
14540
|
};
|
|
12749
14541
|
};
|
|
12750
|
-
var
|
|
14542
|
+
var handlers5 = {
|
|
12751
14543
|
init
|
|
12752
14544
|
};
|
|
12753
14545
|
|
|
@@ -12806,7 +14598,7 @@ $ ${out.command}`;
|
|
|
12806
14598
|
return { info: out.output ? `${header2}
|
|
12807
14599
|
${out.output}` : header2 };
|
|
12808
14600
|
};
|
|
12809
|
-
var
|
|
14601
|
+
var handlers6 = {
|
|
12810
14602
|
jobs,
|
|
12811
14603
|
kill,
|
|
12812
14604
|
logs
|
|
@@ -12867,7 +14659,7 @@ var mcp = (_args, loop2, ctx) => {
|
|
|
12867
14659
|
lines.push("To change this set, exit and run `reasonix setup`.");
|
|
12868
14660
|
return { info: lines.join("\n") };
|
|
12869
14661
|
};
|
|
12870
|
-
var
|
|
14662
|
+
var handlers7 = { mcp };
|
|
12871
14663
|
|
|
12872
14664
|
// src/cli/ui/slash/handlers/memory.ts
|
|
12873
14665
|
var memory = (args, _loop, ctx) => {
|
|
@@ -13002,7 +14794,7 @@ var memory = (args, _loop, ctx) => {
|
|
|
13002
14794
|
);
|
|
13003
14795
|
return { info: parts.join("\n") };
|
|
13004
14796
|
};
|
|
13005
|
-
var
|
|
14797
|
+
var handlers8 = { memory };
|
|
13006
14798
|
|
|
13007
14799
|
// src/cli/ui/slash/handlers/model.ts
|
|
13008
14800
|
var model = (args, loop2, ctx) => {
|
|
@@ -13155,7 +14947,7 @@ var pro = (args, loop2, ctx) => {
|
|
|
13155
14947
|
};
|
|
13156
14948
|
};
|
|
13157
14949
|
var ESCALATION_MODEL_ID = "deepseek-v4-pro";
|
|
13158
|
-
var
|
|
14950
|
+
var handlers9 = {
|
|
13159
14951
|
model,
|
|
13160
14952
|
models,
|
|
13161
14953
|
harvest: harvest2,
|
|
@@ -13308,7 +15100,7 @@ var compact = (args, loop2) => {
|
|
|
13308
15100
|
info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
|
|
13309
15101
|
};
|
|
13310
15102
|
};
|
|
13311
|
-
var
|
|
15103
|
+
var handlers10 = {
|
|
13312
15104
|
think,
|
|
13313
15105
|
reasoning: think,
|
|
13314
15106
|
tool,
|
|
@@ -13456,7 +15248,7 @@ function renderListing(root, mode2) {
|
|
|
13456
15248
|
);
|
|
13457
15249
|
return lines.join("\n");
|
|
13458
15250
|
}
|
|
13459
|
-
var
|
|
15251
|
+
var handlers11 = {
|
|
13460
15252
|
permissions,
|
|
13461
15253
|
perms: permissions
|
|
13462
15254
|
};
|
|
@@ -13540,7 +15332,7 @@ var replay = (args, loop2) => {
|
|
|
13540
15332
|
}
|
|
13541
15333
|
};
|
|
13542
15334
|
};
|
|
13543
|
-
var
|
|
15335
|
+
var handlers12 = {
|
|
13544
15336
|
plans,
|
|
13545
15337
|
replay
|
|
13546
15338
|
};
|
|
@@ -13803,7 +15595,7 @@ async function startOllamaDaemon(opts = {}) {
|
|
|
13803
15595
|
return { ready: false, pid };
|
|
13804
15596
|
}
|
|
13805
15597
|
async function pullOllamaModel(modelName, opts = {}) {
|
|
13806
|
-
return new Promise((
|
|
15598
|
+
return new Promise((resolve13) => {
|
|
13807
15599
|
const child = spawn5("ollama", ["pull", modelName], {
|
|
13808
15600
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13809
15601
|
windowsHide: true
|
|
@@ -13815,8 +15607,8 @@ async function pullOllamaModel(modelName, opts = {}) {
|
|
|
13815
15607
|
}
|
|
13816
15608
|
streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
|
|
13817
15609
|
streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
|
|
13818
|
-
child.once("exit", (code) =>
|
|
13819
|
-
child.once("error", () =>
|
|
15610
|
+
child.once("exit", (code) => resolve13(code ?? -1));
|
|
15611
|
+
child.once("error", () => resolve13(-1));
|
|
13820
15612
|
});
|
|
13821
15613
|
}
|
|
13822
15614
|
function streamLines(stream, cb) {
|
|
@@ -13913,7 +15705,7 @@ async function readIndexMeta(rootDir) {
|
|
|
13913
15705
|
return null;
|
|
13914
15706
|
}
|
|
13915
15707
|
}
|
|
13916
|
-
var
|
|
15708
|
+
var handlers13 = {
|
|
13917
15709
|
semantic
|
|
13918
15710
|
};
|
|
13919
15711
|
|
|
@@ -13948,7 +15740,7 @@ var forget = (_args, loop2) => {
|
|
|
13948
15740
|
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?)`
|
|
13949
15741
|
};
|
|
13950
15742
|
};
|
|
13951
|
-
var
|
|
15743
|
+
var handlers14 = {
|
|
13952
15744
|
sessions,
|
|
13953
15745
|
forget
|
|
13954
15746
|
};
|
|
@@ -14024,7 +15816,7 @@ ${found.body}${argsLine}`;
|
|
|
14024
15816
|
resubmit: payload
|
|
14025
15817
|
};
|
|
14026
15818
|
};
|
|
14027
|
-
var
|
|
15819
|
+
var handlers15 = {
|
|
14028
15820
|
skill,
|
|
14029
15821
|
skills: skill
|
|
14030
15822
|
};
|
|
@@ -14044,7 +15836,8 @@ var HANDLERS = {
|
|
|
14044
15836
|
...handlers11,
|
|
14045
15837
|
...handlers12,
|
|
14046
15838
|
...handlers13,
|
|
14047
|
-
...handlers14
|
|
15839
|
+
...handlers14,
|
|
15840
|
+
...handlers15
|
|
14048
15841
|
};
|
|
14049
15842
|
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
14050
15843
|
const h = HANDLERS[cmd];
|
|
@@ -14628,6 +16421,9 @@ function App({
|
|
|
14628
16421
|
editModeRef.current = editMode;
|
|
14629
16422
|
if (codeMode) saveEditMode(editMode);
|
|
14630
16423
|
}, [editMode, codeMode]);
|
|
16424
|
+
const planModeRef = useRef6(false);
|
|
16425
|
+
const currentRootDirRef = useRef6("");
|
|
16426
|
+
const latestVersionRef = useRef6(null);
|
|
14631
16427
|
const [pendingEditReview, setPendingEditReview] = useState10(null);
|
|
14632
16428
|
const [walkthroughActive, setWalkthroughActive] = useState10(false);
|
|
14633
16429
|
const [pendingTick, setPendingTick] = useState10(0);
|
|
@@ -14673,6 +16469,9 @@ function App({
|
|
|
14673
16469
|
activeLoopRef.current = activeLoop;
|
|
14674
16470
|
}, [activeLoop]);
|
|
14675
16471
|
const toolHistoryRef = useRef6([]);
|
|
16472
|
+
const dashboardRef = useRef6(null);
|
|
16473
|
+
const eventSubscribersRef = useRef6(/* @__PURE__ */ new Set());
|
|
16474
|
+
const historicalRef = useRef6([]);
|
|
14676
16475
|
const planStepsRef = useRef6(null);
|
|
14677
16476
|
const completedStepIdsRef = useRef6(/* @__PURE__ */ new Set());
|
|
14678
16477
|
const planBodyRef = useRef6(null);
|
|
@@ -14822,6 +16621,91 @@ function App({
|
|
|
14822
16621
|
refreshModels,
|
|
14823
16622
|
refreshLatestVersion
|
|
14824
16623
|
} = useSessionInfo(loop2);
|
|
16624
|
+
useEffect6(() => {
|
|
16625
|
+
planModeRef.current = planMode;
|
|
16626
|
+
}, [planMode]);
|
|
16627
|
+
useEffect6(() => {
|
|
16628
|
+
currentRootDirRef.current = currentRootDir;
|
|
16629
|
+
}, [currentRootDir]);
|
|
16630
|
+
useEffect6(() => {
|
|
16631
|
+
latestVersionRef.current = latestVersion ?? null;
|
|
16632
|
+
}, [latestVersion]);
|
|
16633
|
+
const balanceRef = useRef6(null);
|
|
16634
|
+
useEffect6(() => {
|
|
16635
|
+
balanceRef.current = balance;
|
|
16636
|
+
}, [balance]);
|
|
16637
|
+
useEffect6(() => {
|
|
16638
|
+
historicalRef.current = historical;
|
|
16639
|
+
}, [historical]);
|
|
16640
|
+
const broadcastDashboardEvent = useCallback4((ev) => {
|
|
16641
|
+
const subs = eventSubscribersRef.current;
|
|
16642
|
+
if (subs.size === 0) return;
|
|
16643
|
+
for (const h of subs) {
|
|
16644
|
+
try {
|
|
16645
|
+
h(ev);
|
|
16646
|
+
} catch {
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
}, []);
|
|
16650
|
+
useEffect6(() => {
|
|
16651
|
+
broadcastDashboardEvent({ kind: "busy-change", busy });
|
|
16652
|
+
}, [busy, broadcastDashboardEvent]);
|
|
16653
|
+
useEffect6(() => {
|
|
16654
|
+
if (!pendingShell) return;
|
|
16655
|
+
const modal = {
|
|
16656
|
+
kind: "shell",
|
|
16657
|
+
command: pendingShell.command,
|
|
16658
|
+
allowPrefix: derivePrefix(pendingShell.command),
|
|
16659
|
+
shellKind: pendingShell.kind
|
|
16660
|
+
};
|
|
16661
|
+
broadcastDashboardEvent({ kind: "modal-up", modal });
|
|
16662
|
+
return () => {
|
|
16663
|
+
broadcastDashboardEvent({ kind: "modal-down", modalKind: "shell" });
|
|
16664
|
+
};
|
|
16665
|
+
}, [pendingShell, broadcastDashboardEvent]);
|
|
16666
|
+
useEffect6(() => {
|
|
16667
|
+
if (!pendingChoice) return;
|
|
16668
|
+
const modal = {
|
|
16669
|
+
kind: "choice",
|
|
16670
|
+
question: pendingChoice.question,
|
|
16671
|
+
options: pendingChoice.options,
|
|
16672
|
+
allowCustom: pendingChoice.allowCustom
|
|
16673
|
+
};
|
|
16674
|
+
broadcastDashboardEvent({ kind: "modal-up", modal });
|
|
16675
|
+
return () => {
|
|
16676
|
+
broadcastDashboardEvent({ kind: "modal-down", modalKind: "choice" });
|
|
16677
|
+
};
|
|
16678
|
+
}, [pendingChoice, broadcastDashboardEvent]);
|
|
16679
|
+
useEffect6(() => {
|
|
16680
|
+
if (!pendingPlan) return;
|
|
16681
|
+
broadcastDashboardEvent({
|
|
16682
|
+
kind: "modal-up",
|
|
16683
|
+
modal: { kind: "plan", body: pendingPlan }
|
|
16684
|
+
});
|
|
16685
|
+
return () => {
|
|
16686
|
+
broadcastDashboardEvent({ kind: "modal-down", modalKind: "plan" });
|
|
16687
|
+
};
|
|
16688
|
+
}, [pendingPlan, broadcastDashboardEvent]);
|
|
16689
|
+
useEffect6(() => {
|
|
16690
|
+
if (!pendingEditReview) return;
|
|
16691
|
+
const previewLines = (pendingEditReview.search || pendingEditReview.replace || "").split("\n").slice(0, 12);
|
|
16692
|
+
const preview = previewLines.join("\n");
|
|
16693
|
+
broadcastDashboardEvent({
|
|
16694
|
+
kind: "modal-up",
|
|
16695
|
+
modal: {
|
|
16696
|
+
kind: "edit-review",
|
|
16697
|
+
path: pendingEditReview.path,
|
|
16698
|
+
search: pendingEditReview.search ?? "",
|
|
16699
|
+
replace: pendingEditReview.replace ?? "",
|
|
16700
|
+
preview,
|
|
16701
|
+
total: pendingEdits.current.length,
|
|
16702
|
+
remaining: pendingEdits.current.length
|
|
16703
|
+
}
|
|
16704
|
+
});
|
|
16705
|
+
return () => {
|
|
16706
|
+
broadcastDashboardEvent({ kind: "modal-down", modalKind: "edit-review" });
|
|
16707
|
+
};
|
|
16708
|
+
}, [pendingEditReview, broadcastDashboardEvent]);
|
|
14825
16709
|
const {
|
|
14826
16710
|
slashMatches,
|
|
14827
16711
|
slashSelected,
|
|
@@ -14964,11 +16848,11 @@ function App({
|
|
|
14964
16848
|
if (key.escape && busy) {
|
|
14965
16849
|
if (abortedThisTurn.current) return;
|
|
14966
16850
|
abortedThisTurn.current = true;
|
|
14967
|
-
const
|
|
14968
|
-
if (
|
|
16851
|
+
const resolve13 = editReviewResolveRef.current;
|
|
16852
|
+
if (resolve13) {
|
|
14969
16853
|
editReviewResolveRef.current = null;
|
|
14970
16854
|
setPendingEditReview(null);
|
|
14971
|
-
|
|
16855
|
+
resolve13("reject");
|
|
14972
16856
|
}
|
|
14973
16857
|
if (activeLoopRef.current) stopLoop();
|
|
14974
16858
|
loop2.abort();
|
|
@@ -15256,6 +17140,211 @@ function App({
|
|
|
15256
17140
|
setWalkthroughActive(true);
|
|
15257
17141
|
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).`;
|
|
15258
17142
|
}, [codeMode]);
|
|
17143
|
+
const startDashboard = useCallback4(async () => {
|
|
17144
|
+
if (dashboardRef.current) return dashboardRef.current.url;
|
|
17145
|
+
const handle = await startDashboardServer({
|
|
17146
|
+
mode: "attached",
|
|
17147
|
+
configPath: defaultConfigPath(),
|
|
17148
|
+
usageLogPath: defaultUsageLogPath(),
|
|
17149
|
+
loop: loop2,
|
|
17150
|
+
tools,
|
|
17151
|
+
mcpServers,
|
|
17152
|
+
getCurrentCwd: () => codeMode ? currentRootDirRef.current : void 0,
|
|
17153
|
+
getEditMode: () => codeMode ? editModeRef.current : void 0,
|
|
17154
|
+
getPlanMode: () => planModeRef.current,
|
|
17155
|
+
getPendingEditCount: () => pendingEdits.current.length,
|
|
17156
|
+
getLatestVersion: () => latestVersionRef.current,
|
|
17157
|
+
getSessionName: () => session ?? null,
|
|
17158
|
+
setEditMode: (m) => {
|
|
17159
|
+
setEditMode(m);
|
|
17160
|
+
editModeRef.current = m;
|
|
17161
|
+
saveEditMode(m);
|
|
17162
|
+
return m;
|
|
17163
|
+
},
|
|
17164
|
+
setPlanMode: (on) => {
|
|
17165
|
+
if (codeMode) togglePlanMode(on);
|
|
17166
|
+
},
|
|
17167
|
+
applyPresetLive: (name) => {
|
|
17168
|
+
const settings = resolvePreset(name);
|
|
17169
|
+
loop2.configure({
|
|
17170
|
+
model: settings.model,
|
|
17171
|
+
autoEscalate: settings.autoEscalate,
|
|
17172
|
+
reasoningEffort: settings.reasoningEffort
|
|
17173
|
+
});
|
|
17174
|
+
},
|
|
17175
|
+
applyEffortLive: (effort2) => {
|
|
17176
|
+
loop2.configure({ reasoningEffort: effort2 });
|
|
17177
|
+
},
|
|
17178
|
+
// ---------- Chat bridge ----------
|
|
17179
|
+
getMessages: () => {
|
|
17180
|
+
const out = [];
|
|
17181
|
+
for (const ev of historicalRef.current) {
|
|
17182
|
+
if (ev.role === "user" || ev.role === "assistant" || ev.role === "info" || ev.role === "warning") {
|
|
17183
|
+
const msg = { id: ev.id, role: ev.role, text: ev.text };
|
|
17184
|
+
if (ev.reasoning) msg.reasoning = ev.reasoning;
|
|
17185
|
+
out.push(msg);
|
|
17186
|
+
} else if (ev.role === "tool") {
|
|
17187
|
+
const msg = {
|
|
17188
|
+
id: ev.id,
|
|
17189
|
+
role: "tool",
|
|
17190
|
+
text: ev.text,
|
|
17191
|
+
toolName: ev.toolName
|
|
17192
|
+
};
|
|
17193
|
+
if (ev.toolArgs) msg.toolArgs = ev.toolArgs;
|
|
17194
|
+
out.push(msg);
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
return out;
|
|
17198
|
+
},
|
|
17199
|
+
subscribeEvents: (handler) => {
|
|
17200
|
+
eventSubscribersRef.current.add(handler);
|
|
17201
|
+
return () => {
|
|
17202
|
+
eventSubscribersRef.current.delete(handler);
|
|
17203
|
+
};
|
|
17204
|
+
},
|
|
17205
|
+
submitPrompt: (text) => {
|
|
17206
|
+
if (busyRef.current) {
|
|
17207
|
+
return { accepted: false, reason: "loop is busy with a turn" };
|
|
17208
|
+
}
|
|
17209
|
+
const fn = handleSubmitRef.current;
|
|
17210
|
+
if (!fn) return { accepted: false, reason: "TUI not ready" };
|
|
17211
|
+
fn(text).catch(() => void 0);
|
|
17212
|
+
return { accepted: true };
|
|
17213
|
+
},
|
|
17214
|
+
abortTurn: () => {
|
|
17215
|
+
if (busyRef.current) loop2.abort();
|
|
17216
|
+
},
|
|
17217
|
+
isBusy: () => busyRef.current,
|
|
17218
|
+
getStats: () => {
|
|
17219
|
+
const s = loop2.stats.summary();
|
|
17220
|
+
const ctxCap = DEEPSEEK_CONTEXT_TOKENS[loop2.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
17221
|
+
return {
|
|
17222
|
+
turns: s.turns,
|
|
17223
|
+
totalCostUsd: s.totalCostUsd,
|
|
17224
|
+
lastTurnCostUsd: s.lastTurnCostUsd,
|
|
17225
|
+
totalInputCostUsd: s.totalInputCostUsd,
|
|
17226
|
+
totalOutputCostUsd: s.totalOutputCostUsd,
|
|
17227
|
+
cacheHitRatio: s.cacheHitRatio,
|
|
17228
|
+
lastPromptTokens: s.lastPromptTokens,
|
|
17229
|
+
contextCapTokens: ctxCap,
|
|
17230
|
+
// useSessionInfo's Balance is a flat { currency, total }; the
|
|
17231
|
+
// dashboard wire shape is the richer DeepSeek BalanceInfo
|
|
17232
|
+
// array (granted / topped_up split). Convert as a single-
|
|
17233
|
+
// entry array so the SPA always reads `balance[0]` shape.
|
|
17234
|
+
balance: balanceRef.current ? [
|
|
17235
|
+
{
|
|
17236
|
+
currency: balanceRef.current.currency,
|
|
17237
|
+
total_balance: String(balanceRef.current.total)
|
|
17238
|
+
}
|
|
17239
|
+
] : null
|
|
17240
|
+
};
|
|
17241
|
+
},
|
|
17242
|
+
// ---------- Modal mirroring ----------
|
|
17243
|
+
getActiveModal: () => {
|
|
17244
|
+
const ps = pendingShell;
|
|
17245
|
+
if (ps) {
|
|
17246
|
+
return {
|
|
17247
|
+
kind: "shell",
|
|
17248
|
+
command: ps.command,
|
|
17249
|
+
allowPrefix: derivePrefix(ps.command),
|
|
17250
|
+
shellKind: ps.kind
|
|
17251
|
+
};
|
|
17252
|
+
}
|
|
17253
|
+
const pc = pendingChoice;
|
|
17254
|
+
if (pc) {
|
|
17255
|
+
return {
|
|
17256
|
+
kind: "choice",
|
|
17257
|
+
question: pc.question,
|
|
17258
|
+
options: pc.options,
|
|
17259
|
+
allowCustom: pc.allowCustom
|
|
17260
|
+
};
|
|
17261
|
+
}
|
|
17262
|
+
if (pendingPlanRef.current) {
|
|
17263
|
+
return { kind: "plan", body: pendingPlanRef.current };
|
|
17264
|
+
}
|
|
17265
|
+
const er = pendingEditReview;
|
|
17266
|
+
if (er) {
|
|
17267
|
+
return {
|
|
17268
|
+
kind: "edit-review",
|
|
17269
|
+
path: er.path,
|
|
17270
|
+
search: er.search ?? "",
|
|
17271
|
+
replace: er.replace ?? "",
|
|
17272
|
+
preview: (er.search || er.replace || "").split("\n").slice(0, 12).join("\n"),
|
|
17273
|
+
total: pendingEdits.current.length,
|
|
17274
|
+
remaining: pendingEdits.current.length
|
|
17275
|
+
};
|
|
17276
|
+
}
|
|
17277
|
+
return null;
|
|
17278
|
+
},
|
|
17279
|
+
resolveShellConfirm: (choice) => {
|
|
17280
|
+
const fn = handleShellConfirmRef.current;
|
|
17281
|
+
if (fn) fn(choice).catch(() => void 0);
|
|
17282
|
+
},
|
|
17283
|
+
resolveChoiceConfirm: (choice) => {
|
|
17284
|
+
const fn = handleChoiceConfirmRef.current;
|
|
17285
|
+
if (fn) fn(choice).catch(() => void 0);
|
|
17286
|
+
},
|
|
17287
|
+
resolvePlanConfirm: (choice, text) => {
|
|
17288
|
+
if (choice === "cancel") {
|
|
17289
|
+
handlePlanConfirmRef.current("cancel").catch(() => void 0);
|
|
17290
|
+
return;
|
|
17291
|
+
}
|
|
17292
|
+
const plan2 = pendingPlanRef.current ?? "";
|
|
17293
|
+
handleStagedInputSubmitRef.current(text ?? "", { plan: plan2, mode: choice }).catch(() => void 0);
|
|
17294
|
+
},
|
|
17295
|
+
resolveEditReview: (choice) => {
|
|
17296
|
+
const resolve13 = editReviewResolveRef.current;
|
|
17297
|
+
if (resolve13) {
|
|
17298
|
+
editReviewResolveRef.current = null;
|
|
17299
|
+
setPendingEditReview(null);
|
|
17300
|
+
resolve13(choice);
|
|
17301
|
+
}
|
|
17302
|
+
},
|
|
17303
|
+
// ---------- v0.14 mutation surface ----------
|
|
17304
|
+
reloadHooks: () => {
|
|
17305
|
+
const fresh = loadHooks({ projectRoot: codeMode ? currentRootDirRef.current : void 0 });
|
|
17306
|
+
setHookList(fresh);
|
|
17307
|
+
return fresh.length;
|
|
17308
|
+
}
|
|
17309
|
+
});
|
|
17310
|
+
dashboardRef.current = handle;
|
|
17311
|
+
return handle.url;
|
|
17312
|
+
}, [
|
|
17313
|
+
loop2,
|
|
17314
|
+
tools,
|
|
17315
|
+
mcpServers,
|
|
17316
|
+
codeMode,
|
|
17317
|
+
session,
|
|
17318
|
+
togglePlanMode,
|
|
17319
|
+
pendingShell,
|
|
17320
|
+
pendingChoice,
|
|
17321
|
+
pendingEditReview
|
|
17322
|
+
]);
|
|
17323
|
+
const stopDashboard = useCallback4(async () => {
|
|
17324
|
+
const h = dashboardRef.current;
|
|
17325
|
+
if (!h) return;
|
|
17326
|
+
dashboardRef.current = null;
|
|
17327
|
+
try {
|
|
17328
|
+
await h.close();
|
|
17329
|
+
} catch {
|
|
17330
|
+
}
|
|
17331
|
+
setHistorical((prev) => [
|
|
17332
|
+
...prev,
|
|
17333
|
+
{ id: `dash-stop-${Date.now()}`, role: "info", text: "\u25B8 dashboard stopped." }
|
|
17334
|
+
]);
|
|
17335
|
+
}, []);
|
|
17336
|
+
const getDashboardUrl = useCallback4(() => {
|
|
17337
|
+
return dashboardRef.current?.url ?? null;
|
|
17338
|
+
}, []);
|
|
17339
|
+
useEffect6(() => {
|
|
17340
|
+
return () => {
|
|
17341
|
+
const h = dashboardRef.current;
|
|
17342
|
+
if (h) {
|
|
17343
|
+
dashboardRef.current = null;
|
|
17344
|
+
h.close().catch(() => void 0);
|
|
17345
|
+
}
|
|
17346
|
+
};
|
|
17347
|
+
}, []);
|
|
15259
17348
|
const handleWalkChoice = useCallback4(
|
|
15260
17349
|
(choice) => {
|
|
15261
17350
|
if (choice === "apply") {
|
|
@@ -15299,7 +17388,7 @@ function App({
|
|
|
15299
17388
|
nextFireMs: Math.max(0, cur.nextFireAt - Date.now())
|
|
15300
17389
|
};
|
|
15301
17390
|
}, []);
|
|
15302
|
-
const
|
|
17391
|
+
const handleSubmit2 = useCallback4(
|
|
15303
17392
|
async (raw) => {
|
|
15304
17393
|
let text = raw.trim();
|
|
15305
17394
|
if (!text) return;
|
|
@@ -15457,6 +17546,9 @@ function App({
|
|
|
15457
17546
|
stopLoop,
|
|
15458
17547
|
getLoopStatus,
|
|
15459
17548
|
startWalkthrough: codeMode ? startWalkthrough : void 0,
|
|
17549
|
+
startDashboard,
|
|
17550
|
+
stopDashboard,
|
|
17551
|
+
getDashboardUrl,
|
|
15460
17552
|
jobs: codeMode?.jobs,
|
|
15461
17553
|
postInfo: (text2) => setHistorical((prev) => [
|
|
15462
17554
|
...prev,
|
|
@@ -15577,6 +17669,8 @@ function App({
|
|
|
15577
17669
|
leadSeparator: prev.length > 0
|
|
15578
17670
|
}
|
|
15579
17671
|
]);
|
|
17672
|
+
const userId = `u-${Date.now()}`;
|
|
17673
|
+
broadcastDashboardEvent({ kind: "user", id: userId, text });
|
|
15580
17674
|
const assistantId = `a-${Date.now()}`;
|
|
15581
17675
|
const streamRef = { id: assistantId, text: "", reasoning: "" };
|
|
15582
17676
|
const contentBuf = { current: "" };
|
|
@@ -15675,6 +17769,38 @@ function App({
|
|
|
15675
17769
|
try {
|
|
15676
17770
|
for await (const ev of loop2.step(modelInput)) {
|
|
15677
17771
|
writeTranscript(ev);
|
|
17772
|
+
if (eventSubscribersRef.current.size > 0) {
|
|
17773
|
+
const id = `${assistantId}-${ev.role}-${Date.now()}`;
|
|
17774
|
+
if (ev.role === "assistant_delta") {
|
|
17775
|
+
broadcastDashboardEvent({
|
|
17776
|
+
kind: "assistant_delta",
|
|
17777
|
+
id: assistantId,
|
|
17778
|
+
contentDelta: ev.content || void 0,
|
|
17779
|
+
reasoningDelta: ev.reasoningDelta
|
|
17780
|
+
});
|
|
17781
|
+
} else if (ev.role === "tool_start" && ev.toolName) {
|
|
17782
|
+
broadcastDashboardEvent({
|
|
17783
|
+
kind: "tool_start",
|
|
17784
|
+
id,
|
|
17785
|
+
toolName: ev.toolName,
|
|
17786
|
+
args: ev.toolArgs
|
|
17787
|
+
});
|
|
17788
|
+
} else if (ev.role === "tool" && ev.toolName) {
|
|
17789
|
+
broadcastDashboardEvent({
|
|
17790
|
+
kind: "tool",
|
|
17791
|
+
id,
|
|
17792
|
+
toolName: ev.toolName,
|
|
17793
|
+
content: ev.content,
|
|
17794
|
+
args: ev.toolArgs
|
|
17795
|
+
});
|
|
17796
|
+
} else if (ev.role === "warning") {
|
|
17797
|
+
broadcastDashboardEvent({ kind: "warning", id, text: ev.content });
|
|
17798
|
+
} else if (ev.role === "error") {
|
|
17799
|
+
broadcastDashboardEvent({ kind: "error", id, text: ev.content });
|
|
17800
|
+
} else if (ev.role === "status") {
|
|
17801
|
+
broadcastDashboardEvent({ kind: "status", text: ev.content });
|
|
17802
|
+
}
|
|
17803
|
+
}
|
|
15678
17804
|
if (ev.role !== "status") {
|
|
15679
17805
|
setStatusLine((cur) => cur ? null : cur);
|
|
15680
17806
|
}
|
|
@@ -15713,6 +17839,12 @@ function App({
|
|
|
15713
17839
|
flush();
|
|
15714
17840
|
const repairNote = ev.repair ? describeRepair(ev.repair) : "";
|
|
15715
17841
|
setStreaming(null);
|
|
17842
|
+
broadcastDashboardEvent({
|
|
17843
|
+
kind: "assistant_final",
|
|
17844
|
+
id: assistantId,
|
|
17845
|
+
text: ev.content || streamRef.text,
|
|
17846
|
+
reasoning: streamRef.reasoning || void 0
|
|
17847
|
+
});
|
|
15716
17848
|
setSummary(loop2.stats.summary());
|
|
15717
17849
|
if (ev.stats?.usage) {
|
|
15718
17850
|
appendUsage({
|
|
@@ -15818,6 +17950,7 @@ function App({
|
|
|
15818
17950
|
role: "tool",
|
|
15819
17951
|
text: ev.content,
|
|
15820
17952
|
toolName: ev.toolName,
|
|
17953
|
+
toolArgs: ev.toolArgs,
|
|
15821
17954
|
toolIndex,
|
|
15822
17955
|
durationMs
|
|
15823
17956
|
}
|
|
@@ -16044,12 +18177,16 @@ function App({
|
|
|
16044
18177
|
startLoop,
|
|
16045
18178
|
getLoopStatus,
|
|
16046
18179
|
startWalkthrough,
|
|
18180
|
+
startDashboard,
|
|
18181
|
+
stopDashboard,
|
|
18182
|
+
getDashboardUrl,
|
|
18183
|
+
broadcastDashboardEvent,
|
|
16047
18184
|
applyCwdChange
|
|
16048
18185
|
]
|
|
16049
18186
|
);
|
|
16050
18187
|
useEffect6(() => {
|
|
16051
|
-
handleSubmitRef.current =
|
|
16052
|
-
}, [
|
|
18188
|
+
handleSubmitRef.current = handleSubmit2;
|
|
18189
|
+
}, [handleSubmit2]);
|
|
16053
18190
|
useEffect6(() => {
|
|
16054
18191
|
if (!activeLoop) return;
|
|
16055
18192
|
const delay = Math.max(0, activeLoop.nextFireAt - Date.now());
|
|
@@ -16177,18 +18314,18 @@ ${body}`;
|
|
|
16177
18314
|
loop2.abort();
|
|
16178
18315
|
setQueuedSubmit(synthetic);
|
|
16179
18316
|
} else {
|
|
16180
|
-
await
|
|
18317
|
+
await handleSubmit2(synthetic);
|
|
16181
18318
|
}
|
|
16182
18319
|
},
|
|
16183
|
-
[pendingShell, codeMode, currentRootDir,
|
|
18320
|
+
[pendingShell, codeMode, currentRootDir, handleSubmit2, busy, loop2]
|
|
16184
18321
|
);
|
|
16185
18322
|
useEffect6(() => {
|
|
16186
18323
|
if (!busy && queuedSubmit !== null) {
|
|
16187
18324
|
const text = queuedSubmit;
|
|
16188
18325
|
setQueuedSubmit(null);
|
|
16189
|
-
void
|
|
18326
|
+
void handleSubmit2(text);
|
|
16190
18327
|
}
|
|
16191
|
-
}, [busy, queuedSubmit,
|
|
18328
|
+
}, [busy, queuedSubmit, handleSubmit2]);
|
|
16192
18329
|
const handleWorkspaceConfirm = useCallback4(
|
|
16193
18330
|
async (choice) => {
|
|
16194
18331
|
const pending = pendingWorkspace;
|
|
@@ -16218,10 +18355,10 @@ ${body}`;
|
|
|
16218
18355
|
loop2.abort();
|
|
16219
18356
|
setQueuedSubmit(synthetic);
|
|
16220
18357
|
} else {
|
|
16221
|
-
await
|
|
18358
|
+
await handleSubmit2(synthetic);
|
|
16222
18359
|
}
|
|
16223
18360
|
},
|
|
16224
|
-
[pendingWorkspace, applyCwdChange, busy, loop2,
|
|
18361
|
+
[pendingWorkspace, applyCwdChange, busy, loop2, handleSubmit2]
|
|
16225
18362
|
);
|
|
16226
18363
|
const handlePlanConfirm = useCallback4(
|
|
16227
18364
|
async (choice) => {
|
|
@@ -16255,10 +18392,10 @@ ${body}`;
|
|
|
16255
18392
|
loop2.abort();
|
|
16256
18393
|
setQueuedSubmit(synthetic);
|
|
16257
18394
|
} else {
|
|
16258
|
-
await
|
|
18395
|
+
await handleSubmit2(synthetic);
|
|
16259
18396
|
}
|
|
16260
18397
|
},
|
|
16261
|
-
[pendingPlan, togglePlanMode, busy, loop2,
|
|
18398
|
+
[pendingPlan, togglePlanMode, busy, loop2, handleSubmit2, persistPlanState]
|
|
16262
18399
|
);
|
|
16263
18400
|
const handlePlanConfirmRef = useRef6(handlePlanConfirm);
|
|
16264
18401
|
useEffect6(() => {
|
|
@@ -16269,9 +18406,13 @@ ${body}`;
|
|
|
16269
18406
|
[]
|
|
16270
18407
|
);
|
|
16271
18408
|
const handleStagedInputSubmit = useCallback4(
|
|
16272
|
-
async (feedback) => {
|
|
16273
|
-
const staged = stagedInput;
|
|
16274
|
-
|
|
18409
|
+
async (feedback, override) => {
|
|
18410
|
+
const staged = override ?? stagedInput;
|
|
18411
|
+
if (override) {
|
|
18412
|
+
setPendingPlan(null);
|
|
18413
|
+
} else {
|
|
18414
|
+
setStagedInput(null);
|
|
18415
|
+
}
|
|
16275
18416
|
if (!staged) return;
|
|
16276
18417
|
const trimmed = feedback.trim();
|
|
16277
18418
|
let synthetic;
|
|
@@ -16312,11 +18453,15 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
16312
18453
|
loop2.abort();
|
|
16313
18454
|
setQueuedSubmit(synthetic);
|
|
16314
18455
|
} else {
|
|
16315
|
-
await
|
|
18456
|
+
await handleSubmit2(synthetic);
|
|
16316
18457
|
}
|
|
16317
18458
|
},
|
|
16318
|
-
[stagedInput, togglePlanMode, busy, loop2,
|
|
18459
|
+
[stagedInput, togglePlanMode, busy, loop2, handleSubmit2]
|
|
16319
18460
|
);
|
|
18461
|
+
const handleStagedInputSubmitRef = useRef6(handleStagedInputSubmit);
|
|
18462
|
+
useEffect6(() => {
|
|
18463
|
+
handleStagedInputSubmitRef.current = handleStagedInputSubmit;
|
|
18464
|
+
}, [handleStagedInputSubmit]);
|
|
16320
18465
|
const handleStagedInputCancel = useCallback4(() => {
|
|
16321
18466
|
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
16322
18467
|
setStagedInput(null);
|
|
@@ -16347,10 +18492,10 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
16347
18492
|
loop2.abort();
|
|
16348
18493
|
setQueuedSubmit(synthetic);
|
|
16349
18494
|
} else {
|
|
16350
|
-
await
|
|
18495
|
+
await handleSubmit2(synthetic);
|
|
16351
18496
|
}
|
|
16352
18497
|
},
|
|
16353
|
-
[pendingCheckpoint, busy, loop2,
|
|
18498
|
+
[pendingCheckpoint, busy, loop2, handleSubmit2]
|
|
16354
18499
|
);
|
|
16355
18500
|
const handleCheckpointConfirmRef = useRef6(handleCheckpointConfirm);
|
|
16356
18501
|
useEffect6(() => {
|
|
@@ -16381,10 +18526,10 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
16381
18526
|
loop2.abort();
|
|
16382
18527
|
setQueuedSubmit(synthetic);
|
|
16383
18528
|
} else {
|
|
16384
|
-
await
|
|
18529
|
+
await handleSubmit2(synthetic);
|
|
16385
18530
|
}
|
|
16386
18531
|
},
|
|
16387
|
-
[stagedCheckpointRevise, busy, loop2,
|
|
18532
|
+
[stagedCheckpointRevise, busy, loop2, handleSubmit2]
|
|
16388
18533
|
);
|
|
16389
18534
|
const handleCheckpointReviseCancel = useCallback4(() => {
|
|
16390
18535
|
const snap = stagedCheckpointRevise;
|
|
@@ -16410,7 +18555,7 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
16410
18555
|
loop2.abort();
|
|
16411
18556
|
setQueuedSubmit(synthetic2);
|
|
16412
18557
|
} else {
|
|
16413
|
-
await
|
|
18558
|
+
await handleSubmit2(synthetic2);
|
|
16414
18559
|
}
|
|
16415
18560
|
return;
|
|
16416
18561
|
}
|
|
@@ -16425,11 +18570,19 @@ If the feedback only tweaks how you execute (extra constraints, style preference
|
|
|
16425
18570
|
loop2.abort();
|
|
16426
18571
|
setQueuedSubmit(synthetic);
|
|
16427
18572
|
} else {
|
|
16428
|
-
await
|
|
18573
|
+
await handleSubmit2(synthetic);
|
|
16429
18574
|
}
|
|
16430
18575
|
},
|
|
16431
|
-
[pendingChoice, busy, loop2,
|
|
18576
|
+
[pendingChoice, busy, loop2, handleSubmit2]
|
|
16432
18577
|
);
|
|
18578
|
+
const handleShellConfirmRef = useRef6(handleShellConfirm);
|
|
18579
|
+
useEffect6(() => {
|
|
18580
|
+
handleShellConfirmRef.current = handleShellConfirm;
|
|
18581
|
+
}, [handleShellConfirm]);
|
|
18582
|
+
const pendingPlanRef = useRef6(null);
|
|
18583
|
+
useEffect6(() => {
|
|
18584
|
+
pendingPlanRef.current = pendingPlan;
|
|
18585
|
+
}, [pendingPlan]);
|
|
16433
18586
|
const handleChoiceConfirmRef = useRef6(handleChoiceConfirm);
|
|
16434
18587
|
useEffect6(() => {
|
|
16435
18588
|
handleChoiceConfirmRef.current = handleChoiceConfirm;
|
|
@@ -16456,10 +18609,10 @@ Read it carefully and proceed \u2014 don't snap back to the options you listed u
|
|
|
16456
18609
|
loop2.abort();
|
|
16457
18610
|
setQueuedSubmit(synthetic);
|
|
16458
18611
|
} else {
|
|
16459
|
-
await
|
|
18612
|
+
await handleSubmit2(synthetic);
|
|
16460
18613
|
}
|
|
16461
18614
|
},
|
|
16462
|
-
[busy, loop2,
|
|
18615
|
+
[busy, loop2, handleSubmit2]
|
|
16463
18616
|
);
|
|
16464
18617
|
const handleChoiceCustomCancel = useCallback4(() => {
|
|
16465
18618
|
const snap = stagedChoiceCustom;
|
|
@@ -16481,7 +18634,7 @@ Read it carefully and proceed \u2014 don't snap back to the options you listed u
|
|
|
16481
18634
|
loop2.abort();
|
|
16482
18635
|
setQueuedSubmit(synthetic2);
|
|
16483
18636
|
} else {
|
|
16484
|
-
await
|
|
18637
|
+
await handleSubmit2(synthetic2);
|
|
16485
18638
|
}
|
|
16486
18639
|
return;
|
|
16487
18640
|
}
|
|
@@ -16516,10 +18669,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
16516
18669
|
loop2.abort();
|
|
16517
18670
|
setQueuedSubmit(synthetic);
|
|
16518
18671
|
} else {
|
|
16519
|
-
await
|
|
18672
|
+
await handleSubmit2(synthetic);
|
|
16520
18673
|
}
|
|
16521
18674
|
},
|
|
16522
|
-
[pendingRevision, busy, loop2,
|
|
18675
|
+
[pendingRevision, busy, loop2, handleSubmit2, persistPlanState]
|
|
16523
18676
|
);
|
|
16524
18677
|
const handleReviseConfirmRef = useRef6(handleReviseConfirm);
|
|
16525
18678
|
useEffect6(() => {
|
|
@@ -16632,10 +18785,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
16632
18785
|
{
|
|
16633
18786
|
block: pendingEditReview,
|
|
16634
18787
|
onChoose: (choice) => {
|
|
16635
|
-
const
|
|
16636
|
-
if (
|
|
18788
|
+
const resolve13 = editReviewResolveRef.current;
|
|
18789
|
+
if (resolve13) {
|
|
16637
18790
|
editReviewResolveRef.current = null;
|
|
16638
|
-
|
|
18791
|
+
resolve13(choice);
|
|
16639
18792
|
}
|
|
16640
18793
|
}
|
|
16641
18794
|
}
|
|
@@ -16661,7 +18814,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
16661
18814
|
{
|
|
16662
18815
|
value: input,
|
|
16663
18816
|
onChange: setInput,
|
|
16664
|
-
onSubmit:
|
|
18817
|
+
onSubmit: handleSubmit2,
|
|
16665
18818
|
disabled: busy,
|
|
16666
18819
|
onHistoryPrev: recallPrev,
|
|
16667
18820
|
onHistoryNext: recallNext
|
|
@@ -16741,7 +18894,7 @@ function Setup({ onReady }) {
|
|
|
16741
18894
|
const [value, setValue] = useState11("");
|
|
16742
18895
|
const [error, setError] = useState11(null);
|
|
16743
18896
|
const { exit: exit2 } = useApp2();
|
|
16744
|
-
const
|
|
18897
|
+
const handleSubmit2 = (raw) => {
|
|
16745
18898
|
const trimmed = raw.trim();
|
|
16746
18899
|
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
16747
18900
|
exit2();
|
|
@@ -16765,7 +18918,7 @@ function Setup({ onReady }) {
|
|
|
16765
18918
|
{
|
|
16766
18919
|
value,
|
|
16767
18920
|
onChange: setValue,
|
|
16768
|
-
onSubmit:
|
|
18921
|
+
onSubmit: handleSubmit2,
|
|
16769
18922
|
mask: "\u2022",
|
|
16770
18923
|
placeholder: "sk-..."
|
|
16771
18924
|
}
|
|
@@ -16910,7 +19063,7 @@ async function chatCommand(opts) {
|
|
|
16910
19063
|
const prior = loadSessionMessages(opts.session);
|
|
16911
19064
|
if (prior.length > 0) {
|
|
16912
19065
|
const p = sessionPath(opts.session);
|
|
16913
|
-
const mtime =
|
|
19066
|
+
const mtime = existsSync22(p) ? statSync13(p).mtime : /* @__PURE__ */ new Date();
|
|
16914
19067
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
16915
19068
|
}
|
|
16916
19069
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -16941,7 +19094,7 @@ async function chatCommand(opts) {
|
|
|
16941
19094
|
}
|
|
16942
19095
|
|
|
16943
19096
|
// src/cli/commands/code.tsx
|
|
16944
|
-
import { basename as basename2, resolve as
|
|
19097
|
+
import { basename as basename2, resolve as resolve11 } from "path";
|
|
16945
19098
|
|
|
16946
19099
|
// src/index/semantic/builder.ts
|
|
16947
19100
|
import { promises as fs5 } from "fs";
|
|
@@ -17588,8 +19741,8 @@ async function bootstrapSemanticSearchInCodeMode(registry, rootDir, opts = {}) {
|
|
|
17588
19741
|
|
|
17589
19742
|
// src/cli/commands/code.tsx
|
|
17590
19743
|
async function codeCommand(opts = {}) {
|
|
17591
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
17592
|
-
const rootDir =
|
|
19744
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-HNDDXDRH.js");
|
|
19745
|
+
const rootDir = resolve11(opts.dir ?? process.cwd());
|
|
17593
19746
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
17594
19747
|
const tools = new ToolRegistry();
|
|
17595
19748
|
const jobs2 = new JobRegistry();
|
|
@@ -17636,7 +19789,7 @@ async function codeCommand(opts = {}) {
|
|
|
17636
19789
|
}
|
|
17637
19790
|
|
|
17638
19791
|
// src/cli/commands/diff.ts
|
|
17639
|
-
import { writeFileSync as
|
|
19792
|
+
import { writeFileSync as writeFileSync12 } from "fs";
|
|
17640
19793
|
import { basename as basename3 } from "path";
|
|
17641
19794
|
import { render as render2 } from "ink";
|
|
17642
19795
|
import React30 from "react";
|
|
@@ -17783,7 +19936,7 @@ async function diffCommand(opts) {
|
|
|
17783
19936
|
if (wantMarkdown) {
|
|
17784
19937
|
console.log(renderSummaryTable(report));
|
|
17785
19938
|
const md = renderMarkdown(report);
|
|
17786
|
-
|
|
19939
|
+
writeFileSync12(opts.mdPath, md, "utf8");
|
|
17787
19940
|
console.log(`
|
|
17788
19941
|
markdown report written to ${opts.mdPath}`);
|
|
17789
19942
|
return;
|
|
@@ -17800,7 +19953,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
17800
19953
|
}
|
|
17801
19954
|
|
|
17802
19955
|
// src/cli/commands/index.ts
|
|
17803
|
-
import { resolve as
|
|
19956
|
+
import { resolve as resolve12 } from "path";
|
|
17804
19957
|
|
|
17805
19958
|
// src/index/semantic/preflight.ts
|
|
17806
19959
|
import { stdin as stdin2, stdout } from "process";
|
|
@@ -17874,7 +20027,7 @@ async function confirm(question, defaultYes) {
|
|
|
17874
20027
|
|
|
17875
20028
|
// src/cli/commands/index.ts
|
|
17876
20029
|
async function indexCommand(opts = {}) {
|
|
17877
|
-
const root =
|
|
20030
|
+
const root = resolve12(opts.dir ?? process.cwd());
|
|
17878
20031
|
const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
17879
20032
|
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
17880
20033
|
const preflightOk = await ollamaPreflight({
|
|
@@ -18498,38 +20651,6 @@ import React34 from "react";
|
|
|
18498
20651
|
import { Box as Box28, Text as Text26, useApp as useApp5, useInput as useInput3 } from "ink";
|
|
18499
20652
|
import TextInput2 from "ink-text-input";
|
|
18500
20653
|
import React33, { useState as useState15 } from "react";
|
|
18501
|
-
|
|
18502
|
-
// src/cli/ui/presets.ts
|
|
18503
|
-
var PRESETS = {
|
|
18504
|
-
// fast — flash + effort=high. Quick Q&A, one-line tweaks, anything
|
|
18505
|
-
// where shallow reasoning is enough. Cheapest turn possible.
|
|
18506
|
-
fast: { model: "deepseek-v4-flash", reasoningEffort: "high", harvest: false, branch: 1 },
|
|
18507
|
-
// smart — flash + effort=max. Full thinking budget on the cheap
|
|
18508
|
-
// model. The default: handles 90%+ of coding work at a fraction
|
|
18509
|
-
// of pro's cost.
|
|
18510
|
-
smart: { model: "deepseek-v4-flash", reasoningEffort: "max", harvest: false, branch: 1 },
|
|
18511
|
-
// max — pro + effort=max. Frontier model for hard tasks: cross-
|
|
18512
|
-
// file architecture, subtle bug hunts, anything where flash's
|
|
18513
|
-
// reasoning has measurably failed. ~12× per-token vs flash; save
|
|
18514
|
-
// for when you need it, or use `/pro` to escalate a single turn.
|
|
18515
|
-
max: { model: "deepseek-v4-pro", reasoningEffort: "max", harvest: false, branch: 1 }
|
|
18516
|
-
};
|
|
18517
|
-
var PRESET_DESCRIPTIONS = {
|
|
18518
|
-
fast: {
|
|
18519
|
-
headline: "v4-flash \xB7 effort=high",
|
|
18520
|
-
cost: "cheapest \xB7 quick Q&A, one-line edits"
|
|
18521
|
-
},
|
|
18522
|
-
smart: {
|
|
18523
|
-
headline: "v4-flash \xB7 effort=max",
|
|
18524
|
-
cost: "~1.5\xD7 fast \xB7 default \xB7 day-to-day coding"
|
|
18525
|
-
},
|
|
18526
|
-
max: {
|
|
18527
|
-
headline: "v4-pro \xB7 effort=max",
|
|
18528
|
-
cost: "~12\xD7 fast \xB7 hard single-shots \xB7 use /pro for a single-turn bump"
|
|
18529
|
-
}
|
|
18530
|
-
};
|
|
18531
|
-
|
|
18532
|
-
// src/cli/ui/Wizard.tsx
|
|
18533
20654
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
18534
20655
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
18535
20656
|
const { exit: exit2 } = useApp5();
|
|
@@ -18726,7 +20847,7 @@ function SummaryLine({ label, value }) {
|
|
|
18726
20847
|
return /* @__PURE__ */ React33.createElement(Box28, null, /* @__PURE__ */ React33.createElement(Text26, null, label.padEnd(12)), /* @__PURE__ */ React33.createElement(Text26, { bold: true }, value));
|
|
18727
20848
|
}
|
|
18728
20849
|
function presetItems() {
|
|
18729
|
-
return ["
|
|
20850
|
+
return ["auto", "flash", "pro"].map((name) => ({
|
|
18730
20851
|
value: name,
|
|
18731
20852
|
label: `${name} \u2014 ${PRESET_DESCRIPTIONS[name].headline}`,
|
|
18732
20853
|
hint: PRESET_DESCRIPTIONS[name].cost
|
|
@@ -18827,13 +20948,13 @@ function planUpdate(input) {
|
|
|
18827
20948
|
};
|
|
18828
20949
|
}
|
|
18829
20950
|
function defaultSpawn(argv) {
|
|
18830
|
-
return new Promise((
|
|
20951
|
+
return new Promise((resolve13, reject) => {
|
|
18831
20952
|
const child = spawn6(argv[0], argv.slice(1), {
|
|
18832
20953
|
stdio: "inherit",
|
|
18833
20954
|
shell: process.platform === "win32"
|
|
18834
20955
|
});
|
|
18835
20956
|
child.once("error", reject);
|
|
18836
|
-
child.once("exit", (code) =>
|
|
20957
|
+
child.once("exit", (code) => resolve13(code ?? 1));
|
|
18837
20958
|
});
|
|
18838
20959
|
}
|
|
18839
20960
|
async function updateCommand(opts = {}) {
|
|
@@ -18883,7 +21004,7 @@ function versionCommand() {
|
|
|
18883
21004
|
function resolveDefaults(flags) {
|
|
18884
21005
|
const cfg = flags.noConfig ? {} : readConfig();
|
|
18885
21006
|
const preset2 = pickPreset(flags.preset, cfg.preset);
|
|
18886
|
-
const presetSettings =
|
|
21007
|
+
const presetSettings = resolvePreset(preset2);
|
|
18887
21008
|
const model2 = flags.model ?? presetSettings.model;
|
|
18888
21009
|
const reasoningEffort = presetSettings.reasoningEffort;
|
|
18889
21010
|
const harvest3 = flags.harvest === true ? true : presetSettings.harvest;
|
|
@@ -18896,10 +21017,12 @@ function resolveDefaults(flags) {
|
|
|
18896
21017
|
function pickPreset(flagPreset, configPreset) {
|
|
18897
21018
|
if (flagPreset && isPresetName(flagPreset)) return flagPreset;
|
|
18898
21019
|
if (configPreset) return configPreset;
|
|
18899
|
-
return "
|
|
21020
|
+
return "auto";
|
|
18900
21021
|
}
|
|
18901
21022
|
function isPresetName(s) {
|
|
18902
|
-
return s === "
|
|
21023
|
+
return s === "auto" || s === "flash" || s === "pro" || // Legacy names — kept callable so old `--preset smart` invocations
|
|
21024
|
+
// and stale config.json entries don't error out.
|
|
21025
|
+
s === "fast" || s === "smart" || s === "max";
|
|
18903
21026
|
}
|
|
18904
21027
|
function normalizeBranch(raw) {
|
|
18905
21028
|
if (raw === void 0) return void 0;
|