reasonix 0.30.3 → 0.30.5
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/README.md +17 -0
- package/README.zh-CN.md +17 -0
- package/dist/cli/{chunk-COFBA5FV.js → chunk-VWFJNLIK.js} +50 -8
- package/dist/cli/chunk-VWFJNLIK.js.map +1 -0
- package/dist/cli/index.js +901 -642
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-VF7B6BWR.js → prompt-XHICFAYN.js} +2 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +185 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-COFBA5FV.js.map +0 -1
- /package/dist/cli/{prompt-VF7B6BWR.js.map → prompt-XHICFAYN.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1480,6 +1480,10 @@ var SessionStats = class {
|
|
|
1480
1480
|
_carryoverCost = 0;
|
|
1481
1481
|
/** Turn count from prior runs of a resumed session. */
|
|
1482
1482
|
_carryoverTurns = 0;
|
|
1483
|
+
_carryoverCacheHit = 0;
|
|
1484
|
+
_carryoverCacheMiss = 0;
|
|
1485
|
+
/** Last turn's promptTokens before exit — surfaced via summary() until the next live turn lands. */
|
|
1486
|
+
_carryoverLastPromptTokens = 0;
|
|
1483
1487
|
/** Seed totals from a resumed session's persisted meta — only call once at construction. */
|
|
1484
1488
|
seedCarryover(opts) {
|
|
1485
1489
|
if (typeof opts.totalCostUsd === "number" && opts.totalCostUsd > 0) {
|
|
@@ -1488,6 +1492,15 @@ var SessionStats = class {
|
|
|
1488
1492
|
if (typeof opts.turnCount === "number" && opts.turnCount > 0) {
|
|
1489
1493
|
this._carryoverTurns = opts.turnCount;
|
|
1490
1494
|
}
|
|
1495
|
+
if (typeof opts.cacheHitTokens === "number" && opts.cacheHitTokens > 0) {
|
|
1496
|
+
this._carryoverCacheHit = opts.cacheHitTokens;
|
|
1497
|
+
}
|
|
1498
|
+
if (typeof opts.cacheMissTokens === "number" && opts.cacheMissTokens > 0) {
|
|
1499
|
+
this._carryoverCacheMiss = opts.cacheMissTokens;
|
|
1500
|
+
}
|
|
1501
|
+
if (typeof opts.lastPromptTokens === "number" && opts.lastPromptTokens > 0) {
|
|
1502
|
+
this._carryoverLastPromptTokens = opts.lastPromptTokens;
|
|
1503
|
+
}
|
|
1491
1504
|
}
|
|
1492
1505
|
record(turn, model, usage) {
|
|
1493
1506
|
const cost = costUsd(model, usage);
|
|
@@ -1518,8 +1531,8 @@ var SessionStats = class {
|
|
|
1518
1531
|
return this.turns.reduce((sum, t) => sum + outputCostUsd(t.model, t.usage), 0);
|
|
1519
1532
|
}
|
|
1520
1533
|
get aggregateCacheHitRatio() {
|
|
1521
|
-
let hit =
|
|
1522
|
-
let miss =
|
|
1534
|
+
let hit = this._carryoverCacheHit;
|
|
1535
|
+
let miss = this._carryoverCacheMiss;
|
|
1523
1536
|
for (const t of this.turns) {
|
|
1524
1537
|
hit += t.usage.promptCacheHitTokens;
|
|
1525
1538
|
miss += t.usage.promptCacheMissTokens;
|
|
@@ -1537,7 +1550,7 @@ var SessionStats = class {
|
|
|
1537
1550
|
claudeEquivalentUsd: round(this.totalClaudeEquivalent, 6),
|
|
1538
1551
|
savingsVsClaudePct: round(this.savingsVsClaude * 100, 2),
|
|
1539
1552
|
cacheHitRatio: round(this.aggregateCacheHitRatio, 4),
|
|
1540
|
-
lastPromptTokens: last?.usage.promptTokens ??
|
|
1553
|
+
lastPromptTokens: last?.usage.promptTokens ?? this._carryoverLastPromptTokens,
|
|
1541
1554
|
lastTurnCostUsd: round(last?.cost ?? 0, 6)
|
|
1542
1555
|
};
|
|
1543
1556
|
}
|
|
@@ -2269,15 +2282,18 @@ var StormBreaker = class {
|
|
|
2269
2282
|
windowSize;
|
|
2270
2283
|
threshold;
|
|
2271
2284
|
isMutating;
|
|
2285
|
+
isStormExempt;
|
|
2272
2286
|
recent = [];
|
|
2273
|
-
constructor(windowSize = 6, threshold = 3, isMutating) {
|
|
2287
|
+
constructor(windowSize = 6, threshold = 3, isMutating, isStormExempt) {
|
|
2274
2288
|
this.windowSize = windowSize;
|
|
2275
2289
|
this.threshold = threshold;
|
|
2276
2290
|
this.isMutating = isMutating;
|
|
2291
|
+
this.isStormExempt = isStormExempt;
|
|
2277
2292
|
}
|
|
2278
2293
|
inspect(call) {
|
|
2279
2294
|
const name = call.function?.name;
|
|
2280
2295
|
if (!name) return { suppress: false };
|
|
2296
|
+
if (this.isStormExempt?.(call)) return { suppress: false };
|
|
2281
2297
|
const args = call.function?.arguments ?? "";
|
|
2282
2298
|
const mutating = this.isMutating ? this.isMutating(call) : false;
|
|
2283
2299
|
const readOnly = !mutating;
|
|
@@ -2378,7 +2394,12 @@ var ToolCallRepair = class {
|
|
|
2378
2394
|
opts;
|
|
2379
2395
|
constructor(opts) {
|
|
2380
2396
|
this.opts = opts;
|
|
2381
|
-
this.storm = new StormBreaker(
|
|
2397
|
+
this.storm = new StormBreaker(
|
|
2398
|
+
opts.stormWindow ?? 6,
|
|
2399
|
+
opts.stormThreshold ?? 3,
|
|
2400
|
+
opts.isMutating,
|
|
2401
|
+
opts.isStormExempt
|
|
2402
|
+
);
|
|
2382
2403
|
}
|
|
2383
2404
|
/** Called at start of every user turn — fresh intent shouldn't inherit old repetition state. */
|
|
2384
2405
|
resetStorm() {
|
|
@@ -2521,9 +2542,15 @@ var CacheFirstLoop = class {
|
|
|
2521
2542
|
}
|
|
2522
2543
|
return def.readOnly !== true;
|
|
2523
2544
|
};
|
|
2545
|
+
const isStormExempt = (call) => {
|
|
2546
|
+
const name = call.function?.name;
|
|
2547
|
+
if (!name) return false;
|
|
2548
|
+
return registry.get(name)?.stormExempt === true;
|
|
2549
|
+
};
|
|
2524
2550
|
this.repair = new ToolCallRepair({
|
|
2525
2551
|
allowedToolNames: allowedNames,
|
|
2526
2552
|
isMutating,
|
|
2553
|
+
isStormExempt,
|
|
2527
2554
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
2528
2555
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
2529
2556
|
});
|
|
@@ -2541,7 +2568,10 @@ var CacheFirstLoop = class {
|
|
|
2541
2568
|
const meta = loadSessionMeta(this.sessionName);
|
|
2542
2569
|
this.stats.seedCarryover({
|
|
2543
2570
|
totalCostUsd: meta.totalCostUsd,
|
|
2544
|
-
turnCount: meta.turnCount
|
|
2571
|
+
turnCount: meta.turnCount,
|
|
2572
|
+
cacheHitTokens: meta.cacheHitTokens,
|
|
2573
|
+
cacheMissTokens: meta.cacheMissTokens,
|
|
2574
|
+
lastPromptTokens: meta.lastPromptTokens
|
|
2545
2575
|
});
|
|
2546
2576
|
}
|
|
2547
2577
|
if (healedCount > 0) {
|
|
@@ -3637,19 +3667,19 @@ ${mem.content}
|
|
|
3637
3667
|
import { createHash as createHash2 } from "crypto";
|
|
3638
3668
|
import {
|
|
3639
3669
|
existsSync as existsSync7,
|
|
3640
|
-
mkdirSync as
|
|
3670
|
+
mkdirSync as mkdirSync3,
|
|
3641
3671
|
readFileSync as readFileSync8,
|
|
3642
3672
|
readdirSync as readdirSync4,
|
|
3643
3673
|
unlinkSync as unlinkSync2,
|
|
3644
|
-
writeFileSync as
|
|
3674
|
+
writeFileSync as writeFileSync3
|
|
3645
3675
|
} from "fs";
|
|
3646
3676
|
import { homedir as homedir4 } from "os";
|
|
3647
3677
|
import { join as join7, resolve as resolve3 } from "path";
|
|
3648
3678
|
|
|
3649
3679
|
// src/skills.ts
|
|
3650
|
-
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
3680
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3651
3681
|
import { homedir as homedir3 } from "os";
|
|
3652
|
-
import { join as join6, resolve as resolve2 } from "path";
|
|
3682
|
+
import { dirname as dirname3, join as join6, resolve as resolve2 } from "path";
|
|
3653
3683
|
|
|
3654
3684
|
// src/prompt-fragments.ts
|
|
3655
3685
|
var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
|
|
@@ -3754,6 +3784,31 @@ var SkillStore = class {
|
|
|
3754
3784
|
}
|
|
3755
3785
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
3756
3786
|
}
|
|
3787
|
+
/** Scaffold a new skill stub at the chosen scope. Refuses to overwrite. */
|
|
3788
|
+
create(name, scope) {
|
|
3789
|
+
if (!isValidSkillName(name)) {
|
|
3790
|
+
return { error: `invalid skill name: "${name}" \u2014 use letters, digits, _, -, .` };
|
|
3791
|
+
}
|
|
3792
|
+
if (scope === "project" && !this.projectRoot) {
|
|
3793
|
+
return { error: "project scope requires a workspace \u2014 run from `reasonix code`" };
|
|
3794
|
+
}
|
|
3795
|
+
const root = scope === "project" ? join6(this.projectRoot ?? "", ".reasonix", SKILLS_DIRNAME) : join6(this.homeDir, ".reasonix", SKILLS_DIRNAME);
|
|
3796
|
+
const flat = join6(root, `${name}.md`);
|
|
3797
|
+
const folder = join6(root, name, SKILL_FILE);
|
|
3798
|
+
if (existsSync6(folder)) {
|
|
3799
|
+
return { error: `skill "${name}" already exists at ${folder}` };
|
|
3800
|
+
}
|
|
3801
|
+
mkdirSync2(dirname3(flat), { recursive: true });
|
|
3802
|
+
try {
|
|
3803
|
+
writeFileSync2(flat, skillStubBody(name), { encoding: "utf8", flag: "wx" });
|
|
3804
|
+
} catch (err) {
|
|
3805
|
+
if (err.code === "EEXIST") {
|
|
3806
|
+
return { error: `skill "${name}" already exists at ${flat}` };
|
|
3807
|
+
}
|
|
3808
|
+
throw err;
|
|
3809
|
+
}
|
|
3810
|
+
return { path: flat };
|
|
3811
|
+
}
|
|
3757
3812
|
/** Resolve one skill by name. Returns `null` if not found or malformed. */
|
|
3758
3813
|
read(name) {
|
|
3759
3814
|
if (!isValidSkillName(name)) return null;
|
|
@@ -3813,6 +3868,22 @@ var SkillStore = class {
|
|
|
3813
3868
|
function parseRunAs(raw) {
|
|
3814
3869
|
return raw?.trim() === "subagent" ? "subagent" : "inline";
|
|
3815
3870
|
}
|
|
3871
|
+
function skillStubBody(name) {
|
|
3872
|
+
return `---
|
|
3873
|
+
name: ${name}
|
|
3874
|
+
description: One-liner \u2014 what does this skill do?
|
|
3875
|
+
---
|
|
3876
|
+
|
|
3877
|
+
# ${name}
|
|
3878
|
+
|
|
3879
|
+
Replace this body with the playbook the model should follow when this skill is invoked.
|
|
3880
|
+
|
|
3881
|
+
Tips:
|
|
3882
|
+
- Reference tools by name (run_command, edit_file, search_content, ...)
|
|
3883
|
+
- Add \`runAs: subagent\` to frontmatter to spawn an isolated subagent loop
|
|
3884
|
+
- Add \`allowed-tools: read_file, search_content\` to scope a subagent's tools
|
|
3885
|
+
`;
|
|
3886
|
+
}
|
|
3816
3887
|
function skillIndexLine(s) {
|
|
3817
3888
|
const safeDesc = s.description.replace(/\n/g, " ").trim();
|
|
3818
3889
|
const tag = s.runAs === "subagent" ? " [\u{1F9EC} subagent]" : "";
|
|
@@ -4055,7 +4126,7 @@ function scopeDir(opts) {
|
|
|
4055
4126
|
return join7(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
|
|
4056
4127
|
}
|
|
4057
4128
|
function ensureDir(p) {
|
|
4058
|
-
if (!existsSync7(p))
|
|
4129
|
+
if (!existsSync7(p)) mkdirSync3(p, { recursive: true });
|
|
4059
4130
|
}
|
|
4060
4131
|
function parseFrontmatter2(raw) {
|
|
4061
4132
|
const lines = raw.split(/\r?\n/);
|
|
@@ -4200,7 +4271,7 @@ var MemoryStore = class {
|
|
|
4200
4271
|
const file = join7(dir, `${name}.md`);
|
|
4201
4272
|
const content = `${formatFrontmatter(entry)}${body}
|
|
4202
4273
|
`;
|
|
4203
|
-
|
|
4274
|
+
writeFileSync3(file, content, "utf8");
|
|
4204
4275
|
this.regenerateIndex(input.scope);
|
|
4205
4276
|
return file;
|
|
4206
4277
|
}
|
|
@@ -4241,7 +4312,7 @@ var MemoryStore = class {
|
|
|
4241
4312
|
lines.push(`- [${name}](${name}.md) \u2014 (malformed, check frontmatter)`);
|
|
4242
4313
|
}
|
|
4243
4314
|
}
|
|
4244
|
-
|
|
4315
|
+
writeFileSync3(indexPath, `${lines.join("\n")}
|
|
4245
4316
|
`, "utf8");
|
|
4246
4317
|
}
|
|
4247
4318
|
};
|
|
@@ -4662,7 +4733,9 @@ function registerFilesystemTools(registry, opts) {
|
|
|
4662
4733
|
const normRoot = pathMod3.resolve(rootDir);
|
|
4663
4734
|
const rel = pathMod3.relative(normRoot, resolved);
|
|
4664
4735
|
if (rel.startsWith("..") || pathMod3.isAbsolute(rel)) {
|
|
4665
|
-
throw new Error(
|
|
4736
|
+
throw new Error(
|
|
4737
|
+
`path escapes sandbox root (${normRoot}): ${raw} \u2014 workspace is pinned at launch; quit and relaunch with \`reasonix code --dir <path>\` to work in a different folder`
|
|
4738
|
+
);
|
|
4666
4739
|
}
|
|
4667
4740
|
return resolved;
|
|
4668
4741
|
};
|
|
@@ -4675,6 +4748,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
4675
4748
|
- range: "A-B" \u2192 inclusive line range A..B, 1-indexed (e.g. "120-180" around an edit site)
|
|
4676
4749
|
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker rather than dumping everything. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first, then read_file with a range around the hit \u2014 one scoped read beats three full-file reads.`,
|
|
4677
4750
|
readOnly: true,
|
|
4751
|
+
stormExempt: true,
|
|
4678
4752
|
parameters: {
|
|
4679
4753
|
type: "object",
|
|
4680
4754
|
properties: {
|
|
@@ -4755,6 +4829,7 @@ ${slice.join("\n")}`;
|
|
|
4755
4829
|
parallelSafe: true,
|
|
4756
4830
|
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
4757
4831
|
readOnly: true,
|
|
4832
|
+
stormExempt: true,
|
|
4758
4833
|
parameters: {
|
|
4759
4834
|
type: "object",
|
|
4760
4835
|
properties: {
|
|
@@ -5856,9 +5931,9 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
|
5856
5931
|
import * as pathMod7 from "path";
|
|
5857
5932
|
|
|
5858
5933
|
// src/config.ts
|
|
5859
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
5934
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
5860
5935
|
import { homedir as homedir5 } from "os";
|
|
5861
|
-
import { dirname as
|
|
5936
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
5862
5937
|
function defaultConfigPath() {
|
|
5863
5938
|
return join10(homedir5(), ".reasonix", "config.json");
|
|
5864
5939
|
}
|
|
@@ -5872,8 +5947,8 @@ function readConfig(path2 = defaultConfigPath()) {
|
|
|
5872
5947
|
return {};
|
|
5873
5948
|
}
|
|
5874
5949
|
function writeConfig(cfg, path2 = defaultConfigPath()) {
|
|
5875
|
-
|
|
5876
|
-
|
|
5950
|
+
mkdirSync4(dirname5(path2), { recursive: true });
|
|
5951
|
+
writeFileSync4(path2, JSON.stringify(cfg, null, 2), "utf8");
|
|
5877
5952
|
try {
|
|
5878
5953
|
chmodSync2(path2, 384);
|
|
5879
5954
|
} catch {
|
|
@@ -6015,7 +6090,8 @@ var JobRegistry = class {
|
|
|
6015
6090
|
},
|
|
6016
6091
|
closedPromise: Promise.resolve(),
|
|
6017
6092
|
signalClosed: () => {
|
|
6018
|
-
}
|
|
6093
|
+
},
|
|
6094
|
+
outputWaiters: /* @__PURE__ */ new Set()
|
|
6019
6095
|
};
|
|
6020
6096
|
this.jobs.set(id2, job2);
|
|
6021
6097
|
return {
|
|
@@ -6051,7 +6127,8 @@ var JobRegistry = class {
|
|
|
6051
6127
|
readyPromise,
|
|
6052
6128
|
signalReady: readyResolve,
|
|
6053
6129
|
closedPromise,
|
|
6054
|
-
signalClosed: closedResolve
|
|
6130
|
+
signalClosed: closedResolve,
|
|
6131
|
+
outputWaiters: /* @__PURE__ */ new Set()
|
|
6055
6132
|
};
|
|
6056
6133
|
this.jobs.set(id, job);
|
|
6057
6134
|
let readyMatched = false;
|
|
@@ -6078,6 +6155,11 @@ ${job.output.slice(start)}`;
|
|
|
6078
6155
|
}
|
|
6079
6156
|
}
|
|
6080
6157
|
}
|
|
6158
|
+
if (job.outputWaiters.size > 0) {
|
|
6159
|
+
const waiters = [...job.outputWaiters];
|
|
6160
|
+
job.outputWaiters.clear();
|
|
6161
|
+
for (const wake of waiters) wake();
|
|
6162
|
+
}
|
|
6081
6163
|
};
|
|
6082
6164
|
child.stdout?.on("data", onData);
|
|
6083
6165
|
child.stderr?.on("data", onData);
|
|
@@ -6139,6 +6221,39 @@ ${job.output.slice(start)}`;
|
|
|
6139
6221
|
spawnError: job.spawnError
|
|
6140
6222
|
};
|
|
6141
6223
|
}
|
|
6224
|
+
async waitForJob(id, opts = {}) {
|
|
6225
|
+
const job = this.jobs.get(id);
|
|
6226
|
+
if (!job) return null;
|
|
6227
|
+
if (!job.running) {
|
|
6228
|
+
return {
|
|
6229
|
+
exited: true,
|
|
6230
|
+
exitCode: job.exitCode,
|
|
6231
|
+
latestOutput: job.output
|
|
6232
|
+
};
|
|
6233
|
+
}
|
|
6234
|
+
const timeoutMs = Math.max(0, Math.min(3e4, opts.timeoutMs ?? 5e3));
|
|
6235
|
+
const startOutput = job.output;
|
|
6236
|
+
let wakeOutput = null;
|
|
6237
|
+
const outputPromise = new Promise((resolve10) => {
|
|
6238
|
+
wakeOutput = resolve10;
|
|
6239
|
+
job.outputWaiters.add(resolve10);
|
|
6240
|
+
});
|
|
6241
|
+
let timer = null;
|
|
6242
|
+
await Promise.race([
|
|
6243
|
+
job.closedPromise,
|
|
6244
|
+
outputPromise,
|
|
6245
|
+
new Promise((resolve10) => {
|
|
6246
|
+
timer = setTimeout(resolve10, timeoutMs);
|
|
6247
|
+
})
|
|
6248
|
+
]);
|
|
6249
|
+
if (timer) clearTimeout(timer);
|
|
6250
|
+
if (wakeOutput) job.outputWaiters.delete(wakeOutput);
|
|
6251
|
+
return {
|
|
6252
|
+
exited: !job.running,
|
|
6253
|
+
exitCode: job.exitCode,
|
|
6254
|
+
latestOutput: latestOutputSince(startOutput, job.output)
|
|
6255
|
+
};
|
|
6256
|
+
}
|
|
6142
6257
|
/** SIGTERM, wait graceMs, then SIGKILL. Idempotent on already-exited jobs. */
|
|
6143
6258
|
async stop(id, opts = {}) {
|
|
6144
6259
|
const job = this.jobs.get(id);
|
|
@@ -6218,6 +6333,11 @@ function snapshot(job) {
|
|
|
6218
6333
|
spawnError: job.spawnError
|
|
6219
6334
|
};
|
|
6220
6335
|
}
|
|
6336
|
+
function latestOutputSince(before, after) {
|
|
6337
|
+
if (!before) return after;
|
|
6338
|
+
if (after.startsWith(before)) return after.slice(before.length);
|
|
6339
|
+
return after;
|
|
6340
|
+
}
|
|
6221
6341
|
|
|
6222
6342
|
// src/tools/shell/exec.ts
|
|
6223
6343
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
@@ -7197,6 +7317,7 @@ function registerShellTools(registry, opts) {
|
|
|
7197
7317
|
description: "Read the latest output of a background job started with `run_background`. By default returns the tail of the buffer (last 80 lines). Pass `since` (the `byteLength` from a previous call) to stream only new content incrementally. Tells you whether the job is still running, so you can stop polling when it's done.",
|
|
7198
7318
|
readOnly: true,
|
|
7199
7319
|
parallelSafe: true,
|
|
7320
|
+
stormExempt: true,
|
|
7200
7321
|
parameters: {
|
|
7201
7322
|
type: "object",
|
|
7202
7323
|
properties: {
|
|
@@ -7221,6 +7342,32 @@ function registerShellTools(registry, opts) {
|
|
|
7221
7342
|
return formatJobRead(args.jobId, out);
|
|
7222
7343
|
}
|
|
7223
7344
|
});
|
|
7345
|
+
registry.register({
|
|
7346
|
+
name: "wait_for_job",
|
|
7347
|
+
description: "Block until a background job exits or produces new output, bounded by `timeoutMs`. Use this instead of polling `job_output` with identical args when you're intentionally waiting for state to change. Returns JSON with `exited`, `exitCode`, and `latestOutput`.",
|
|
7348
|
+
readOnly: true,
|
|
7349
|
+
parameters: {
|
|
7350
|
+
type: "object",
|
|
7351
|
+
properties: {
|
|
7352
|
+
jobId: { type: "integer", description: "Job id returned by run_background." },
|
|
7353
|
+
timeoutMs: {
|
|
7354
|
+
type: "integer",
|
|
7355
|
+
description: "Max time to block before returning if nothing changes. Clamped to 0..30000. Default 5000."
|
|
7356
|
+
}
|
|
7357
|
+
},
|
|
7358
|
+
required: ["jobId"]
|
|
7359
|
+
},
|
|
7360
|
+
fn: async (args) => {
|
|
7361
|
+
const out = await jobs.waitForJob(args.jobId, { timeoutMs: args.timeoutMs });
|
|
7362
|
+
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
7363
|
+
return {
|
|
7364
|
+
jobId: args.jobId,
|
|
7365
|
+
exited: out.exited,
|
|
7366
|
+
exitCode: out.exitCode,
|
|
7367
|
+
latestOutput: out.latestOutput
|
|
7368
|
+
};
|
|
7369
|
+
}
|
|
7370
|
+
});
|
|
7224
7371
|
registry.register({
|
|
7225
7372
|
name: "stop_job",
|
|
7226
7373
|
description: "Stop a background job started with `run_background`. SIGTERM first; SIGKILL after a short grace period if it doesn't exit cleanly. Returns the final output + exit code. Safe to call on an already-exited job.",
|
|
@@ -7242,6 +7389,7 @@ function registerShellTools(registry, opts) {
|
|
|
7242
7389
|
description: "List every background job started this session \u2014 running and exited \u2014 with id, command, pid, status. Use when you've lost track of which job_id corresponds to which process, or to see what's still alive.",
|
|
7243
7390
|
readOnly: true,
|
|
7244
7391
|
parallelSafe: true,
|
|
7392
|
+
stormExempt: true,
|
|
7245
7393
|
parameters: { type: "object", properties: {} },
|
|
7246
7394
|
fn: async () => {
|
|
7247
7395
|
const all = jobs.list();
|
|
@@ -8174,16 +8322,16 @@ function truncate(s, n) {
|
|
|
8174
8322
|
}
|
|
8175
8323
|
|
|
8176
8324
|
// src/version.ts
|
|
8177
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
8325
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
8178
8326
|
import { homedir as homedir6 } from "os";
|
|
8179
|
-
import { dirname as
|
|
8327
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
8180
8328
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8181
8329
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
8182
8330
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8183
8331
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
8184
8332
|
function readPackageVersion() {
|
|
8185
8333
|
try {
|
|
8186
|
-
let dir =
|
|
8334
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
8187
8335
|
for (let i = 0; i < 6; i++) {
|
|
8188
8336
|
const p = join11(dir, "package.json");
|
|
8189
8337
|
if (existsSync9(p)) {
|
|
@@ -8192,7 +8340,7 @@ function readPackageVersion() {
|
|
|
8192
8340
|
return pkg.version;
|
|
8193
8341
|
}
|
|
8194
8342
|
}
|
|
8195
|
-
const parent =
|
|
8343
|
+
const parent = dirname6(dir);
|
|
8196
8344
|
if (parent === dir) break;
|
|
8197
8345
|
dir = parent;
|
|
8198
8346
|
}
|
|
@@ -8218,8 +8366,8 @@ function readCache(homeDirOverride) {
|
|
|
8218
8366
|
function writeCache(entry, homeDirOverride) {
|
|
8219
8367
|
try {
|
|
8220
8368
|
const p = cachePath(homeDirOverride);
|
|
8221
|
-
|
|
8222
|
-
|
|
8369
|
+
mkdirSync5(dirname6(p), { recursive: true });
|
|
8370
|
+
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
8223
8371
|
} catch {
|
|
8224
8372
|
}
|
|
8225
8373
|
}
|
|
@@ -9023,15 +9171,15 @@ import {
|
|
|
9023
9171
|
existsSync as existsSync10,
|
|
9024
9172
|
fstatSync,
|
|
9025
9173
|
ftruncateSync,
|
|
9026
|
-
mkdirSync as
|
|
9174
|
+
mkdirSync as mkdirSync6,
|
|
9027
9175
|
openSync as openSync2,
|
|
9028
9176
|
readFileSync as readFileSync13,
|
|
9029
9177
|
readSync,
|
|
9030
9178
|
unlinkSync as unlinkSync3,
|
|
9031
|
-
writeFileSync as
|
|
9179
|
+
writeFileSync as writeFileSync6,
|
|
9032
9180
|
writeSync
|
|
9033
9181
|
} from "fs";
|
|
9034
|
-
import { dirname as
|
|
9182
|
+
import { dirname as dirname7, resolve as resolve9 } from "path";
|
|
9035
9183
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
9036
9184
|
function parseEditBlocks(text) {
|
|
9037
9185
|
const out = [];
|
|
@@ -9061,7 +9209,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
9061
9209
|
const searchEmpty = block.search.length === 0;
|
|
9062
9210
|
if (searchEmpty) {
|
|
9063
9211
|
try {
|
|
9064
|
-
|
|
9212
|
+
mkdirSync6(dirname7(absTarget), { recursive: true });
|
|
9065
9213
|
const fd = openSync2(absTarget, "wx");
|
|
9066
9214
|
try {
|
|
9067
9215
|
writeSync(fd, block.replace);
|
|
@@ -9176,7 +9324,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
9176
9324
|
message: "removed (the edit had created it)"
|
|
9177
9325
|
};
|
|
9178
9326
|
}
|
|
9179
|
-
|
|
9327
|
+
writeFileSync6(abs, snap.prevContent, "utf8");
|
|
9180
9328
|
return {
|
|
9181
9329
|
path: snap.path,
|
|
9182
9330
|
status: "applied",
|
|
@@ -9352,6 +9500,7 @@ You have TWO tools for running shell commands, and picking the right one is non-
|
|
|
9352
9500
|
|
|
9353
9501
|
After \`run_background\`, tools available to you:
|
|
9354
9502
|
- \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
|
|
9503
|
+
- \`wait_for_job(jobId, timeoutMs?)\` \u2014 block until the job exits or emits new output. Prefer this over repeating identical \`job_output\` calls while you're intentionally waiting.
|
|
9355
9504
|
- \`list_jobs\` \u2014 see every job this session (running + exited).
|
|
9356
9505
|
- \`stop_job(jobId)\` \u2014 SIGTERM \u2192 SIGKILL after grace. Stop before switching port / config.
|
|
9357
9506
|
|
|
@@ -9439,17 +9588,17 @@ import {
|
|
|
9439
9588
|
closeSync as closeSync3,
|
|
9440
9589
|
existsSync as existsSync12,
|
|
9441
9590
|
fstatSync as fstatSync2,
|
|
9442
|
-
mkdirSync as
|
|
9591
|
+
mkdirSync as mkdirSync7,
|
|
9443
9592
|
openSync as openSync3,
|
|
9444
9593
|
readFileSync as readFileSync15,
|
|
9445
9594
|
readSync as readSync2,
|
|
9446
9595
|
renameSync as renameSync2,
|
|
9447
9596
|
statSync as statSync5,
|
|
9448
9597
|
unlinkSync as unlinkSync4,
|
|
9449
|
-
writeFileSync as
|
|
9598
|
+
writeFileSync as writeFileSync7
|
|
9450
9599
|
} from "fs";
|
|
9451
9600
|
import { homedir as homedir7 } from "os";
|
|
9452
|
-
import { dirname as
|
|
9601
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
9453
9602
|
function defaultUsageLogPath(homeDirOverride) {
|
|
9454
9603
|
return join13(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
|
|
9455
9604
|
}
|
|
@@ -9490,7 +9639,7 @@ function compactUsageLogIfLarge(path2, now) {
|
|
|
9490
9639
|
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
9491
9640
|
const tmp = `${path2}.compacting`;
|
|
9492
9641
|
try {
|
|
9493
|
-
|
|
9642
|
+
writeFileSync7(tmp, kept.length > 0 ? `${kept.join("\n")}
|
|
9494
9643
|
` : "", "utf8");
|
|
9495
9644
|
renameSync2(tmp, path2);
|
|
9496
9645
|
} catch {
|
|
@@ -9516,7 +9665,7 @@ function appendUsage(input) {
|
|
|
9516
9665
|
if (input.subagent) record.subagent = input.subagent;
|
|
9517
9666
|
const path2 = input.path ?? defaultUsageLogPath();
|
|
9518
9667
|
try {
|
|
9519
|
-
|
|
9668
|
+
mkdirSync7(dirname8(path2), { recursive: true });
|
|
9520
9669
|
appendFileSync2(path2, `${JSON.stringify(record)}
|
|
9521
9670
|
`, "utf8");
|
|
9522
9671
|
compactUsageLogIfLarge(path2, record.ts);
|