jules-orchestrator-kit 0.32.8 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/agentctl.mjs +131 -10
- package/index.mjs +19 -0
- package/package.json +1 -1
- package/scripts/doc-sync-check.mjs +69 -8
- package/scripts/utils.mjs +6 -2
- package/src/budget.mjs +345 -0
- package/src/config.mjs +71 -3
- package/src/dashboard.mjs +2 -1
- package/src/engine.mjs +24 -2
- package/src/mcp.mjs +15 -4
- package/src/ops/command-registry.mjs +24 -1
- package/src/ops/doctor-registry.mjs +87 -5
- package/src/ops/next-step.mjs +111 -0
- package/src/state.mjs +162 -57
- package/src/version.mjs +27 -0
- package/src/wizard-init.mjs +57 -28
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ Autonomous coding agents can write software at 100× human speed—but unconstra
|
|
|
120
120
|
|
|
121
121
|
* **🚀 Zero-Test Bootstrapping (`agentctl bootstrap`):** Synthesizes deterministic syntax-check and smoke-test verification oracles for untested legacy repositories so agents always operate against a falsifiable feedback loop.
|
|
122
122
|
|
|
123
|
-
* **📈 Proven Scale & Reliability:** Empirically tested with **
|
|
123
|
+
* **📈 Proven Scale & Reliability:** Empirically tested with **510 unit tests across 77 suites passing in < 10.0s**. An adversarial red-team suite (`test/adversarial-claims.test.mjs`) continuously attempts to falsify the safety guarantees documented above — including cross-platform probes for the case-insensitive filesystems on macOS and Windows — and a documentation-sync gate (`scripts/doc-sync-check.mjs`) blocks any release whose docs have drifted from the code.
|
|
124
124
|
|
|
125
125
|
<br/>
|
|
126
126
|
|
|
@@ -297,9 +297,9 @@ scope:
|
|
|
297
297
|
limits:
|
|
298
298
|
diffKb: 75 # 75 KB Diff Payload Governor limit
|
|
299
299
|
promptKb: 50 # Maximum prompt payload size
|
|
300
|
-
dailyTasks: 300 #
|
|
300
|
+
dailyTasks: 300 # Task quota per rolling 24h window (not per calendar day)
|
|
301
301
|
repairAttempts: 3 # Maximum OODA repair iterations
|
|
302
|
-
concurrency:
|
|
302
|
+
concurrency: 15 # Worker slots; the ultra plan allows up to 60
|
|
303
303
|
|
|
304
304
|
# Dynamic Complexity & Cost Router — opt-in, disabled by default.
|
|
305
305
|
# Provider-agnostic: "fast"/"complex" accept any provider key ("jules" |
|
package/bin/agentctl.mjs
CHANGED
|
@@ -5,18 +5,37 @@ import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
|
|
7
7
|
import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
|
|
8
|
-
import { acquireLock, releaseLock, lockStatus,
|
|
8
|
+
import { acquireLock, releaseLock, lockStatus, getQueueDir } from "../src/state.mjs";
|
|
9
9
|
import { worktreePrune } from "../src/git.mjs";
|
|
10
10
|
import { reapOrphanedIntents, reapStaleMutexDirs } from "../src/journal.mjs";
|
|
11
|
+
import { KIT_VERSION } from "../src/version.mjs";
|
|
12
|
+
import { budgetStatus, listOpenReservations, releaseOpenReservations, resolveConcurrency } from "../src/budget.mjs";
|
|
11
13
|
|
|
12
14
|
const args = process.argv.slice(2);
|
|
13
15
|
const command = args[0];
|
|
14
16
|
|
|
15
|
-
export const VERSION =
|
|
17
|
+
export const VERSION = KIT_VERSION;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render the budget line so the number is never mistaken for the vendor's own
|
|
21
|
+
* counter. The ledger counts what *this checkout* dispatched; sessions started
|
|
22
|
+
* from the web UI or another machine spend the same quota unseen, so a bare
|
|
23
|
+
* "N / M used" invites the reader to trust a figure that cannot be complete.
|
|
24
|
+
* "last 24h", not "today": the provider's allowance resets on a rolling
|
|
25
|
+
* window, so a figure labelled by the calendar day would be a different number
|
|
26
|
+
* from the one being enforced.
|
|
27
|
+
* @param {{ used: number, limit: number, source: string, certain: boolean }} b
|
|
28
|
+
*/
|
|
29
|
+
export function formatBudgetLine(b) {
|
|
30
|
+
const scope = `${b.used} / ${b.limit} used in the last 24h (this repo)`;
|
|
31
|
+
if (b.source === "learned") return `${scope} — provider refused further work`;
|
|
32
|
+
if (b.certain) return `${scope} — limit from ${b.source === "env" ? "JULES_DAILY_BUDGET" : "config"}`;
|
|
33
|
+
return `${scope} — limit estimated from tier "${b.tier || "?"}", not enforced`;
|
|
34
|
+
}
|
|
16
35
|
|
|
17
36
|
export function printHelp() {
|
|
18
37
|
console.log(`
|
|
19
|
-
🚀 agentctl
|
|
38
|
+
🚀 agentctl v${VERSION} — Universal Agent Orchestrator & Safety Gatekeeper
|
|
20
39
|
|
|
21
40
|
Usage: agentctl <command> [options]
|
|
22
41
|
|
|
@@ -41,6 +60,7 @@ Commands:
|
|
|
41
60
|
rollback Restore git state & working tree to atomic pre-flight checkpoint
|
|
42
61
|
resume Resume warm session with human response (--response "<text>")
|
|
43
62
|
status Display queue and system status summary
|
|
63
|
+
budget Show the 24h task budget, worker slots and their provenance (reset --yes)
|
|
44
64
|
scan Scan codebase for TODO/FIXME task candidates
|
|
45
65
|
hydrate [prompt] Prepend active system learnings and baton-pass state to a prompt
|
|
46
66
|
harvest Harvest failure traces and record/quarantine resolution rules
|
|
@@ -64,11 +84,33 @@ Options:
|
|
|
64
84
|
|
|
65
85
|
|
|
66
86
|
async function main() {
|
|
67
|
-
if (
|
|
87
|
+
if (command === "--help" || command === "-h") {
|
|
68
88
|
printHelp();
|
|
69
89
|
process.exit(0);
|
|
70
90
|
}
|
|
71
91
|
|
|
92
|
+
// Bare `agentctl` answers "what do I do next" rather than dumping thirty
|
|
93
|
+
// commands. The help text is a reference for people who already know the
|
|
94
|
+
// tool; a newcomer cannot tell which entry is step one, and guessing wrong
|
|
95
|
+
// costs them a confusing failure instead of a hint.
|
|
96
|
+
if (!command) {
|
|
97
|
+
const { resolveNextStep, renderNextStep } = await import("../src/ops/next-step.mjs");
|
|
98
|
+
const cwd = process.cwd();
|
|
99
|
+
let budgetLine = "";
|
|
100
|
+
let where = cwd;
|
|
101
|
+
try {
|
|
102
|
+
const nextRoot = resolveRoot();
|
|
103
|
+
where = nextRoot;
|
|
104
|
+
budgetLine = formatBudgetLine(budgetStatus(loadConfig(nextRoot), nextRoot));
|
|
105
|
+
} catch (_) {
|
|
106
|
+
// Outside a repository there is no root to load a config from; the
|
|
107
|
+
// next step below is `git init`, which does not need one.
|
|
108
|
+
}
|
|
109
|
+
const next = resolveNextStep(where);
|
|
110
|
+
console.log(renderNextStep({ version: VERSION, root: where, next, budgetLine: next.blocking ? "" : budgetLine }));
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
73
115
|
console.log(`agentctl v${VERSION}`);
|
|
74
116
|
process.exit(0);
|
|
@@ -282,6 +324,55 @@ async function main() {
|
|
|
282
324
|
break;
|
|
283
325
|
}
|
|
284
326
|
|
|
327
|
+
case "budget": {
|
|
328
|
+
const action = args[1];
|
|
329
|
+
const b = budgetStatus(config, root);
|
|
330
|
+
|
|
331
|
+
if (action === "reset") {
|
|
332
|
+
// The count is local-only, so an operator who knows their real usage
|
|
333
|
+
// must be able to correct it. Appending `budget_released` keeps the
|
|
334
|
+
// hash chain intact — the ledger is corrected forwards, never edited.
|
|
335
|
+
const dryRun = args.includes("--dry-run");
|
|
336
|
+
const confirmed = args.includes("--yes") || args.includes("-y");
|
|
337
|
+
if (!dryRun && !confirmed) {
|
|
338
|
+
const open = listOpenReservations(root);
|
|
339
|
+
console.log(`Would release ${open.length} open reservation(s) from the last 24 hours.`);
|
|
340
|
+
console.log("This rewrites nothing — it appends `budget_released` entries.");
|
|
341
|
+
console.log("Re-run with --yes to confirm, or --dry-run for detail.");
|
|
342
|
+
process.exit(0);
|
|
343
|
+
}
|
|
344
|
+
const res = releaseOpenReservations({ root, dryRun, reason: "operator-reconcile" });
|
|
345
|
+
const verb = dryRun ? "Would release" : "Released";
|
|
346
|
+
console.log(`${verb} ${res.released} reservation(s) (${res.committed} committed, ${res.uncommitted} never closed).`);
|
|
347
|
+
if (!dryRun) {
|
|
348
|
+
const after = budgetStatus(loadConfig(root), root);
|
|
349
|
+
console.log(`Daily Budget : ${formatBudgetLine(after)}`);
|
|
350
|
+
}
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (args.includes("--json")) {
|
|
355
|
+
console.log(JSON.stringify({ ok: true, budget: { ...b, scope: "this-repository" } }, null, 2));
|
|
356
|
+
process.exit(0);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const slots = resolveConcurrency(config);
|
|
360
|
+
console.log(`Task Budget : ${formatBudgetLine(b)}`);
|
|
361
|
+
console.log(` ${b.note}`);
|
|
362
|
+
console.log(` Window opened at ${b.windowStart} — the quota resets ${b.windowHours}h after each task,`);
|
|
363
|
+
console.log(" not at midnight, so this count spans yesterday's ledger too.");
|
|
364
|
+
console.log(` Open reservations in the window: ${listOpenReservations(root).length}`);
|
|
365
|
+
console.log("");
|
|
366
|
+
console.log(`Worker Slots : ${slots.concurrency} concurrent`);
|
|
367
|
+
console.log(` ${slots.note}`);
|
|
368
|
+
console.log("");
|
|
369
|
+
console.log("The ledger counts this checkout only — sessions started from the Jules");
|
|
370
|
+
console.log("web UI or another machine spend the same quota without appearing here.");
|
|
371
|
+
console.log("Use `agentctl budget reset --yes` to reconcile a count you know is wrong.");
|
|
372
|
+
process.exit(0);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
|
|
285
376
|
case "lock": {
|
|
286
377
|
const action = args[1];
|
|
287
378
|
if (action === "acquire") {
|
|
@@ -313,6 +404,24 @@ async function main() {
|
|
|
313
404
|
}
|
|
314
405
|
|
|
315
406
|
case "doctor": {
|
|
407
|
+
const { values } = parseArgs({
|
|
408
|
+
args: args.slice(1),
|
|
409
|
+
options: { json: { type: "boolean", short: "j" } },
|
|
410
|
+
allowPositionals: true,
|
|
411
|
+
strict: false,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// runDoctorChecks() and its seven checks existed but nothing rendered
|
|
415
|
+
// them: this command printed a hand-written summary, so findings like a
|
|
416
|
+
// git-tracked .env were computed, tested, and never shown to anyone.
|
|
417
|
+
const { runDoctorChecks } = await import("../src/ops/doctor-registry.mjs");
|
|
418
|
+
const report = await runDoctorChecks({ root });
|
|
419
|
+
|
|
420
|
+
if (values.json) {
|
|
421
|
+
console.log(JSON.stringify(report, null, 2));
|
|
422
|
+
process.exit(report.summary.fail > 0 ? 1 : 0);
|
|
423
|
+
}
|
|
424
|
+
|
|
316
425
|
console.log(`\n🔍 agentctl System Diagnostics (v${VERSION})`);
|
|
317
426
|
console.log(`--------------------------------------------------`);
|
|
318
427
|
console.log(` Project Root : ${root}`);
|
|
@@ -320,10 +429,22 @@ async function main() {
|
|
|
320
429
|
console.log(` Detected Stack : ${detectStack(root).stack}`);
|
|
321
430
|
console.log(` Test Command : ${config.verify.test || "(None)"}`);
|
|
322
431
|
console.log(` Build Command : ${config.verify.build || "(None)"}`);
|
|
323
|
-
|
|
324
|
-
console.log(
|
|
325
|
-
|
|
326
|
-
|
|
432
|
+
console.log(` Daily Budget : ${formatBudgetLine(budgetStatus(config, root))}`);
|
|
433
|
+
console.log(`--------------------------------------------------`);
|
|
434
|
+
|
|
435
|
+
const icon = { pass: "✅", warn: "⚠️ ", fail: "❌", skip: "⏭️ ", unknown: "❔" };
|
|
436
|
+
for (const r of report.results) {
|
|
437
|
+
console.log(` ${icon[r.status] || "•"} ${r.title}`);
|
|
438
|
+
if (r.status !== "pass") {
|
|
439
|
+
console.log(` ${r.summary}`);
|
|
440
|
+
for (const fix of r.remediation || []) console.log(` → ${fix.summary}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const { pass, warn, fail } = report.summary;
|
|
445
|
+
console.log(`--------------------------------------------------`);
|
|
446
|
+
console.log(` ${pass} passed, ${warn} warning(s), ${fail} failure(s)\n`);
|
|
447
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
327
448
|
break;
|
|
328
449
|
}
|
|
329
450
|
|
|
@@ -578,8 +699,8 @@ async function main() {
|
|
|
578
699
|
console.log(` Project Root : ${root}`);
|
|
579
700
|
console.log(` Pending Tasks : ${files.length}`);
|
|
580
701
|
console.log(` Active VFS Locks : ${lockStatus(root).length}`);
|
|
581
|
-
const budget =
|
|
582
|
-
console.log(` Daily Budget : ${budget
|
|
702
|
+
const budget = budgetStatus(config, root);
|
|
703
|
+
console.log(` Daily Budget : ${formatBudgetLine(budget)}`);
|
|
583
704
|
console.log(`--------------------------------------------------\n`);
|
|
584
705
|
process.exit(0);
|
|
585
706
|
break;
|
package/index.mjs
CHANGED
|
@@ -49,6 +49,9 @@ export {
|
|
|
49
49
|
rollbackBudgetReservation,
|
|
50
50
|
withBudget,
|
|
51
51
|
checkDailyBudget,
|
|
52
|
+
scanBudgetWindow,
|
|
53
|
+
getLedgerPathsInWindow,
|
|
54
|
+
ROLLING_WINDOW_MS,
|
|
52
55
|
acquireLock,
|
|
53
56
|
releaseLock,
|
|
54
57
|
lockStatus,
|
|
@@ -130,3 +133,19 @@ export {
|
|
|
130
133
|
generateEvidenceMarkdown,
|
|
131
134
|
computeEvidenceHash,
|
|
132
135
|
} from "./src/evidence.mjs";
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
resolveDailyLimit,
|
|
139
|
+
budgetStatus,
|
|
140
|
+
readObservedCeiling,
|
|
141
|
+
readActiveCeiling,
|
|
142
|
+
recordObservedCeiling,
|
|
143
|
+
isDailyQuotaRejection,
|
|
144
|
+
listOpenReservations,
|
|
145
|
+
releaseOpenReservations,
|
|
146
|
+
resolveConcurrency,
|
|
147
|
+
CEILING_FILE,
|
|
148
|
+
} from "./src/budget.mjs";
|
|
149
|
+
export { KIT_VERSION } from "./src/version.mjs";
|
|
150
|
+
export { VENDOR_TIERS, FALLBACK_TIER } from "./src/config.mjs";
|
|
151
|
+
export { tierOptions } from "./src/wizard-init.mjs";
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Exit codes: 0 = in sync, 1 = drift detected.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
19
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { execSync } from "node:child_process";
|
|
22
22
|
import { resolveRoot } from "../src/config.mjs";
|
|
@@ -60,6 +60,33 @@ function readIfExists(path) {
|
|
|
60
60
|
return existsSync(path) ? readFileSync(path, "utf-8") : null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Version numbers that appear in shipped sources but do not describe this kit.
|
|
65
|
+
* Kept as an explicit, justified list rather than a looser pattern, so a real
|
|
66
|
+
* drift can never slip through by resembling one of these.
|
|
67
|
+
*/
|
|
68
|
+
const FOREIGN_VERSIONS = [
|
|
69
|
+
{ file: "src/ops/doctor-registry.mjs", value: "20.0.0", why: "minimum supported Node.js runtime" },
|
|
70
|
+
{ file: "src/ops/ide-scaffold.mjs", value: "2.0.0", why: "VS Code tasks.json schema version" },
|
|
71
|
+
{ file: "src/version.mjs", value: "0.0.0", why: "documented fallback when package.json is absent" },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
function isForeignVersion(relPath, value) {
|
|
75
|
+
return FOREIGN_VERSIONS.some((f) => f.file === relPath && f.value === value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Every .mjs under `dir`, recursively. */
|
|
79
|
+
function listSourceFiles(dir, acc = []) {
|
|
80
|
+
if (!existsSync(dir)) return acc;
|
|
81
|
+
for (const entry of readdirSync(dir)) {
|
|
82
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
83
|
+
const full = join(dir, entry);
|
|
84
|
+
if (statSync(full).isDirectory()) listSourceFiles(full, acc);
|
|
85
|
+
else if (entry.endsWith(".mjs")) acc.push(full);
|
|
86
|
+
}
|
|
87
|
+
return acc;
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
/**
|
|
64
91
|
* Verifies documentation is in sync with package.json and the real test counts.
|
|
65
92
|
* @param {string} [root]
|
|
@@ -80,18 +107,52 @@ export function checkDocSync(root = process.cwd(), opts = {}) {
|
|
|
80
107
|
if (cli === null) {
|
|
81
108
|
add("agentctl present", false, "bin/agentctl.mjs not found");
|
|
82
109
|
} else {
|
|
110
|
+
// Either the version is derived from the manifest — which cannot drift —
|
|
111
|
+
// or it is a literal, which must match. The derived form is preferred;
|
|
112
|
+
// this check exists to stop a literal creeping back in unnoticed.
|
|
113
|
+
const derived = /export const VERSION\s*=\s*KIT_VERSION/.test(cli);
|
|
83
114
|
const constMatch = cli.match(/export const VERSION\s*=\s*"([^"]+)"/);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
if (derived) {
|
|
116
|
+
const versionModule = readIfExists(join(root, "src", "version.mjs"));
|
|
117
|
+
const readsManifest = Boolean(versionModule && /package\.json/.test(versionModule));
|
|
118
|
+
add(
|
|
119
|
+
"agentctl VERSION const",
|
|
120
|
+
readsManifest,
|
|
121
|
+
readsManifest ? "derived from package.json via src/version.mjs" : "VERSION derives from KIT_VERSION but src/version.mjs does not read package.json"
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
add(
|
|
125
|
+
"agentctl VERSION const",
|
|
126
|
+
constMatch?.[1] === version,
|
|
127
|
+
constMatch ? `found "${constMatch[1]}", expected "${version}"` : "no `export const VERSION` found"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
89
130
|
|
|
90
|
-
|
|
131
|
+
// Scan everything shipped, not just the CLI: the MCP server, dashboard and
|
|
132
|
+
// init wizard each hardcoded a version and sat three minor releases behind
|
|
133
|
+
// while this gate only ever looked at bin/agentctl.mjs and stayed green.
|
|
134
|
+
const shipped = [
|
|
135
|
+
...listSourceFiles(join(root, "src")),
|
|
136
|
+
join(root, "bin", "agentctl.mjs"),
|
|
137
|
+
];
|
|
138
|
+
const stale = [];
|
|
139
|
+
for (const file of shipped) {
|
|
140
|
+
const text = readIfExists(file);
|
|
141
|
+
if (text === null) continue;
|
|
142
|
+
const rel = file.replace(root + "/", "");
|
|
143
|
+
for (const m of text.matchAll(/\bv?(\d+\.\d+\.\d+)\b/g)) {
|
|
144
|
+
if (m[1] === version) continue;
|
|
145
|
+
// Only flag figures presented as *this kit's* version.
|
|
146
|
+
const context = text.slice(Math.max(0, m.index - 60), m.index).toLowerCase();
|
|
147
|
+
if (!/version|agentctl|orchestrator kit|kit config/.test(context)) continue;
|
|
148
|
+
if (isForeignVersion(rel, m[1], context)) continue;
|
|
149
|
+
stale.push(`${rel}: ${m[1]}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
91
152
|
add(
|
|
92
153
|
"agentctl banner/version strings",
|
|
93
154
|
stale.length === 0,
|
|
94
|
-
stale.length ? `stale version string(s): ${[...new Set(stale)].join(", ")}` : `all reference v${version}`
|
|
155
|
+
stale.length ? `stale version string(s): ${[...new Set(stale)].join(", ")}` : `all shipped sources reference v${version}`
|
|
95
156
|
);
|
|
96
157
|
}
|
|
97
158
|
|
package/scripts/utils.mjs
CHANGED
|
@@ -142,9 +142,13 @@ export function checkDailyBudget(arg1 = resolveRoot(), arg2 = 300) {
|
|
|
142
142
|
* explicit root to keep callers (notably tests) off the operator's real ledger.
|
|
143
143
|
*/
|
|
144
144
|
export function reserveDailyBudget(maxSessions = 300, taskKey = "", root = resolveRoot()) {
|
|
145
|
-
|
|
145
|
+
// The id is what makes the reservation releasable. Written without one, a
|
|
146
|
+
// reservation counted against the day and no rollback, commit or reconcile
|
|
147
|
+
// could ever name it again — it stayed charged until the ledger rotated.
|
|
148
|
+
const reservationId = `res-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
149
|
+
appendLedger({ event: "budget_reserved", reservationId, key: taskKey }, root);
|
|
146
150
|
const check = checkDailyBudget(root, maxSessions);
|
|
147
|
-
return { ok: check.ok, used: check.used, budget: maxSessions };
|
|
151
|
+
return { ok: check.ok, used: check.used, budget: maxSessions, reservationId };
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
export function verifyLedgerIntegrity(filePath) {
|