@ordewell/core 0.10.8 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{IFileSystem-C4_mpfze.d.ts → IFileSystem-B8qRNC_6.d.ts} +3 -1
- package/dist/{IFileSystem-DUgeWWQN.d.mts → IFileSystem-C9W5mRYA.d.mts} +3 -1
- package/dist/{ModeResolver-4qNjxcDs.d.mts → ModeResolver-C1PN5FYG.d.mts} +1 -1
- package/dist/{ModeResolver-DGK2rWHE.d.ts → ModeResolver-CaPnoU8N.d.ts} +1 -1
- package/dist/{Task-C0o4i3Nn.d.mts → Task-hFfRS9rN.d.mts} +6 -3
- package/dist/{Task-C0o4i3Nn.d.ts → Task-hFfRS9rN.d.ts} +6 -3
- package/dist/{chunk-DMG4ISUG.mjs → chunk-O6LQU3W6.mjs} +8 -1
- package/dist/{chunk-DMG4ISUG.mjs.map → chunk-O6LQU3W6.mjs.map} +1 -1
- package/dist/index.d.mts +35 -12
- package/dist/index.d.ts +35 -12
- package/dist/index.js +147 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +141 -25
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +1 -1
- package/dist/{runSettingsText-BgXvajj2.d.ts → runSettingsText-CGKX3aeV.d.ts} +38 -9
- package/dist/{runSettingsText-BgOmLxdw.d.mts → runSettingsText-rLoYQlUH.d.mts} +38 -9
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -111,7 +111,7 @@ import {
|
|
|
111
111
|
unsendLatest,
|
|
112
112
|
usageLine,
|
|
113
113
|
validateTaskEdit
|
|
114
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-O6LQU3W6.mjs";
|
|
115
115
|
import {
|
|
116
116
|
PLAN_ENVELOPE_KEY,
|
|
117
117
|
PlanParseError,
|
|
@@ -9072,6 +9072,8 @@ var RunnerProcess = class {
|
|
|
9072
9072
|
markEnded;
|
|
9073
9073
|
child = null;
|
|
9074
9074
|
exitStatus = null;
|
|
9075
|
+
/** The process itself reported its end, before its pipes drained; see {@link hasExited}. */
|
|
9076
|
+
exitReported = false;
|
|
9075
9077
|
stderrTail = "";
|
|
9076
9078
|
spawnEnv = {};
|
|
9077
9079
|
/** Stdout readers' end-of-stream flushes, for an end the pipe itself never reports. */
|
|
@@ -9122,6 +9124,7 @@ var RunnerProcess = class {
|
|
|
9122
9124
|
});
|
|
9123
9125
|
child.on("close", (code, signal) => this.end({ code, signal }));
|
|
9124
9126
|
child.once("exit", (code, signal) => {
|
|
9127
|
+
this.exitReported = true;
|
|
9125
9128
|
const grace = setTimeout(() => {
|
|
9126
9129
|
for (const flush of [...this.stdoutEnds]) flush();
|
|
9127
9130
|
this.end({ code, signal });
|
|
@@ -9176,6 +9179,15 @@ ${err.message}`);
|
|
|
9176
9179
|
get exit() {
|
|
9177
9180
|
return this.exitStatus;
|
|
9178
9181
|
}
|
|
9182
|
+
/**
|
|
9183
|
+
* Whether the process has ended, for any reason. Unlike {@link exit} this is
|
|
9184
|
+
* true from its own 'exit', not only once 'close' has drained the pipes: a
|
|
9185
|
+
* runner that has exited can take no further turn even while a descendant
|
|
9186
|
+
* holds its stdout or stderr.
|
|
9187
|
+
*/
|
|
9188
|
+
get hasExited() {
|
|
9189
|
+
return this.exitStatus !== null || this.exitReported;
|
|
9190
|
+
}
|
|
9179
9191
|
/** The exit code a listener is told: -1 for a signal or a spawn failure. */
|
|
9180
9192
|
get exitCode() {
|
|
9181
9193
|
return this.exitStatus?.code ?? -1;
|
|
@@ -9374,6 +9386,9 @@ var StdioAgentAdapter = class {
|
|
|
9374
9386
|
nativeSessionId() {
|
|
9375
9387
|
return this.sessionId;
|
|
9376
9388
|
}
|
|
9389
|
+
get exited() {
|
|
9390
|
+
return this.runner.hasExited;
|
|
9391
|
+
}
|
|
9377
9392
|
onProcessExit(listener) {
|
|
9378
9393
|
const fire = () => listener(this.runner.exitCode, this.exitMessage());
|
|
9379
9394
|
if (this.runner.exit) queueMicrotask(fire);
|
|
@@ -13187,7 +13202,7 @@ var OpenCodeAdapter = class {
|
|
|
13187
13202
|
return this.runner.ended;
|
|
13188
13203
|
}
|
|
13189
13204
|
get exited() {
|
|
13190
|
-
return this.runner.
|
|
13205
|
+
return this.runner.hasExited;
|
|
13191
13206
|
}
|
|
13192
13207
|
async start(opts) {
|
|
13193
13208
|
try {
|
|
@@ -19891,10 +19906,21 @@ var GitWorktreeIsolation = class {
|
|
|
19891
19906
|
const entry = record.repos[repo.path];
|
|
19892
19907
|
if (!entry?.changed) continue;
|
|
19893
19908
|
const merged = await this.tryGit(repo.root, ["merge-base", "--is-ancestor", entry.landedTip ?? repo.integrationBranch, "HEAD"]);
|
|
19894
|
-
if (!merged.ok) return false;
|
|
19909
|
+
if (!merged.ok && !await this.integrationGone(repo)) return false;
|
|
19895
19910
|
}
|
|
19896
19911
|
return true;
|
|
19897
19912
|
}
|
|
19913
|
+
/**
|
|
19914
|
+
* The repo's integration branch is gone — deleted by hand, or by a squash
|
|
19915
|
+
* merge's clean-up — in a repo git can read. Merge all merges that branch by
|
|
19916
|
+
* name, so no merge can bring the work in again, and it is not offered as
|
|
19917
|
+
* work to merge. A repo git cannot read at all (moved, or git failing)
|
|
19918
|
+
* proves nothing either way and counts as not merged.
|
|
19919
|
+
*/
|
|
19920
|
+
async integrationGone(repo) {
|
|
19921
|
+
if (!(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", "HEAD"])).ok) return false;
|
|
19922
|
+
return !(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", `refs/heads/${repo.integrationBranch}`])).ok;
|
|
19923
|
+
}
|
|
19898
19924
|
async snapshotTree(workspaceRoot, exclude) {
|
|
19899
19925
|
const scan = await this.scanGroup(workspaceRoot);
|
|
19900
19926
|
if ("refused" in scan) return null;
|
|
@@ -23042,6 +23068,8 @@ var IsolationRunController = class {
|
|
|
23042
23068
|
listener;
|
|
23043
23069
|
liveTasks;
|
|
23044
23070
|
run = null;
|
|
23071
|
+
/** An adoption stays scoped even while cleanup temporarily leaves no run record. */
|
|
23072
|
+
adoptions = 0;
|
|
23045
23073
|
/** Copied paths already reported for the current run: every task gets the same copies. */
|
|
23046
23074
|
reportedCopies = /* @__PURE__ */ new Set();
|
|
23047
23075
|
reportedSetupFailures = /* @__PURE__ */ new Set();
|
|
@@ -23117,18 +23145,27 @@ var IsolationRunController = class {
|
|
|
23117
23145
|
* Take over a plan's persisted isolation, or none for a plan that has not
|
|
23118
23146
|
* isolated yet. Whatever a crashed process left behind for the run — a
|
|
23119
23147
|
* worktree still marked active, a directory no record owns — is pruned,
|
|
23120
|
-
* while kept, failed and conflicted worktrees stay for the user.
|
|
23148
|
+
* while kept, failed and conflicted worktrees stay for the user. Then git is
|
|
23149
|
+
* asked which landed work the user has merged since the record was saved:
|
|
23150
|
+
* a settled run whose landed work is all in HEAD has nothing left to hand
|
|
23151
|
+
* over, so it is cleared up as Merge all would have, unless it still holds
|
|
23152
|
+
* work that never landed (ADR-0013).
|
|
23121
23153
|
*
|
|
23122
|
-
* Deliberately not reported
|
|
23123
|
-
*
|
|
23124
|
-
* would otherwise write a new session file on every reload.
|
|
23154
|
+
* Deliberately not reported to the listener: adopting is not a change of the
|
|
23155
|
+
* session's own, and a host that adopts without persisting (VS Code's
|
|
23156
|
+
* restore) would otherwise write a new session file on every reload. What
|
|
23157
|
+
* git changed is returned instead, for the caller to save where it can and
|
|
23158
|
+
* announce. A plan loaded meanwhile supersedes this adoption, which then
|
|
23159
|
+
* touches nothing and resolves `unchanged`.
|
|
23125
23160
|
*/
|
|
23126
23161
|
async adopt(state) {
|
|
23162
|
+
const adoption = ++this.adoptions;
|
|
23127
23163
|
this.run = state?.run ?? null;
|
|
23128
23164
|
this.resolvers = { ...state?.resolvers ?? {} };
|
|
23129
|
-
|
|
23165
|
+
const run = this.run;
|
|
23166
|
+
if (!run) return "unchanged";
|
|
23130
23167
|
try {
|
|
23131
|
-
const pruned = await this.isolation.pruneOrphans(
|
|
23168
|
+
const pruned = await this.isolation.pruneOrphans(run);
|
|
23132
23169
|
for (const task of pruned.kept) {
|
|
23133
23170
|
this.tell("warn", `Task "${task.title}" was still holding unlanded work when this plan was re-opened, so its worktree and branch were kept. Retry it to land the work, or review it by hand.`);
|
|
23134
23171
|
}
|
|
@@ -23136,6 +23173,24 @@ var IsolationRunController = class {
|
|
|
23136
23173
|
} catch (err) {
|
|
23137
23174
|
this.tell("warn", `Could not prune leftover worktrees: ${err instanceof Error ? err.message : String(err)}`);
|
|
23138
23175
|
}
|
|
23176
|
+
if (this.run !== run) return "unchanged";
|
|
23177
|
+
const found = await this.findLanded(run);
|
|
23178
|
+
if (this.run !== run) return "unchanged";
|
|
23179
|
+
const outcome = found ? "changed" : "unchanged";
|
|
23180
|
+
if (this.isOpen || !this.allMerged(run) || this.holdouts(run).length > 0) return outcome;
|
|
23181
|
+
const resolvers = this.resolvers;
|
|
23182
|
+
this.run = null;
|
|
23183
|
+
this.resolvers = {};
|
|
23184
|
+
const discarded = await this.discardMerged(run);
|
|
23185
|
+
if (this.adoptions !== adoption) return "unchanged";
|
|
23186
|
+
if (!discarded) {
|
|
23187
|
+
if (this.run === null) {
|
|
23188
|
+
this.run = run;
|
|
23189
|
+
this.resolvers = resolvers;
|
|
23190
|
+
}
|
|
23191
|
+
return outcome;
|
|
23192
|
+
}
|
|
23193
|
+
return this.isOpen ? "changed" : "cleared";
|
|
23139
23194
|
}
|
|
23140
23195
|
/** Open a run if none is, leaving whether it isolates to its first change task. */
|
|
23141
23196
|
open() {
|
|
@@ -23292,8 +23347,7 @@ var IsolationRunController = class {
|
|
|
23292
23347
|
const run = this.run;
|
|
23293
23348
|
if (mode !== "isolated" || !run) return;
|
|
23294
23349
|
await this.refreshInHead();
|
|
23295
|
-
|
|
23296
|
-
if (records.some((r) => r.status === "merged") && handoffOf(run).landed.length === 0) {
|
|
23350
|
+
if (this.allMerged(run)) {
|
|
23297
23351
|
this.tell("info", "Everything this run landed is merged into your branch already.");
|
|
23298
23352
|
await this.clearMerged(run);
|
|
23299
23353
|
return;
|
|
@@ -23320,11 +23374,21 @@ var IsolationRunController = class {
|
|
|
23320
23374
|
*/
|
|
23321
23375
|
async refreshInHead() {
|
|
23322
23376
|
const run = this.run;
|
|
23323
|
-
if (!run
|
|
23377
|
+
if (!run) return false;
|
|
23378
|
+
const found = await this.findLanded(run);
|
|
23379
|
+
if (found) this.listener.changed();
|
|
23380
|
+
return found;
|
|
23381
|
+
}
|
|
23382
|
+
/** Mark the run's landed work git finds in HEAD; true when some was found. */
|
|
23383
|
+
async findLanded(run) {
|
|
23384
|
+
if (!Object.values(run.tasks).some((r) => r.status === "merged" && !r.inHead)) return false;
|
|
23324
23385
|
const found = await this.isolation.findInHead(run).catch(() => []);
|
|
23325
|
-
if (found.length > 0) this.listener.changed();
|
|
23326
23386
|
return found.length > 0;
|
|
23327
23387
|
}
|
|
23388
|
+
/** The run landed something and every landing is in the user's HEAD. */
|
|
23389
|
+
allMerged(run) {
|
|
23390
|
+
return Object.values(run.tasks).some((r) => r.status === "merged") && handoffOf(run).landed.length === 0;
|
|
23391
|
+
}
|
|
23328
23392
|
/**
|
|
23329
23393
|
* The tracked state of the workspace as an attempt whose tree is checked
|
|
23330
23394
|
* starts (see `attemptKind.checksTree`), for {@link filesChangedSince}
|
|
@@ -23446,7 +23510,7 @@ var IsolationRunController = class {
|
|
|
23446
23510
|
*/
|
|
23447
23511
|
async clearMerged(run) {
|
|
23448
23512
|
const live = this.liveRecords(run);
|
|
23449
|
-
const unlanded =
|
|
23513
|
+
const unlanded = this.holdouts(run).filter((r) => !live.includes(r));
|
|
23450
23514
|
if (live.length > 0 || unlanded.length > 0) {
|
|
23451
23515
|
const why = [
|
|
23452
23516
|
...live.length > 0 ? [`${tasksNamed(live)} ${live.length === 1 ? "is" : "are"} still running or waiting on you`] : [],
|
|
@@ -23456,13 +23520,22 @@ var IsolationRunController = class {
|
|
|
23456
23520
|
this.tell("info", `The run is not cleared up: ${why.join(", and ")}, so ${one ? "its worktree stays" : "their worktrees stay"}, and so do the run's branches.`);
|
|
23457
23521
|
return;
|
|
23458
23522
|
}
|
|
23523
|
+
if (await this.discardMerged(run)) this.forget();
|
|
23524
|
+
}
|
|
23525
|
+
/** The records that keep a settled run from being cleared up: live, or holding work that never landed. */
|
|
23526
|
+
holdouts(run) {
|
|
23527
|
+
const live = this.liveTasks();
|
|
23528
|
+
return Object.values(run.tasks).filter((r) => live.has(r.taskId) || r.status !== "merged");
|
|
23529
|
+
}
|
|
23530
|
+
/** The run's worktrees and merged branches go; false, said, when git would not let them. */
|
|
23531
|
+
async discardMerged(run) {
|
|
23459
23532
|
try {
|
|
23460
23533
|
this.report(await this.isolation.discard(run, { integration: "delete-merged" }));
|
|
23534
|
+
return true;
|
|
23461
23535
|
} catch (err) {
|
|
23462
23536
|
this.tell("warn", `Merged, but could not clean up the run's worktrees and branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
23463
|
-
return;
|
|
23537
|
+
return false;
|
|
23464
23538
|
}
|
|
23465
|
-
this.forget();
|
|
23466
23539
|
}
|
|
23467
23540
|
/** Say what a removal kept back instead of deleting: where the work went, or which worktree stayed. */
|
|
23468
23541
|
report(removal) {
|
|
@@ -27117,17 +27190,37 @@ var CliAgentAiService = class {
|
|
|
27117
27190
|
* `startConversation` reseeds from Ordewell's transcript, which is the same
|
|
27118
27191
|
* degradation `restoreChat` already performs on every surface.
|
|
27119
27192
|
*
|
|
27193
|
+
* A process can end while idle without Ordewell stopping it — the OS, the
|
|
27194
|
+
* CLI itself, a sleeping machine, a signal sent to its group — and every
|
|
27195
|
+
* path of ours that kills one also drops its adapter. So an adapter still
|
|
27196
|
+
* held whose process has ended died on its own, and is restarted here
|
|
27197
|
+
* rather than answering the next message with its exit code.
|
|
27198
|
+
*
|
|
27120
27199
|
* Also restarted when the policy's decision on the runner's own search has
|
|
27121
27200
|
* changed since the process was spawned: the running thread would otherwise
|
|
27122
27201
|
* keep searching unasked after Full turned Guarded. Codex 0.162 applies
|
|
27123
27202
|
* `thread/resume`'s `config.web_search` either way (checked live).
|
|
27124
27203
|
*/
|
|
27125
27204
|
async ensureAdapter(session) {
|
|
27126
|
-
|
|
27205
|
+
const live = session.adapter;
|
|
27206
|
+
if (live && !live.exited && session.nativeSearch === this.nativeSearch(session)) return live;
|
|
27127
27207
|
session.stop();
|
|
27128
27208
|
const start = session.start;
|
|
27129
27209
|
if (!start) throw new Error("No active planner conversation.");
|
|
27130
|
-
|
|
27210
|
+
const resumeSessionId = session.nativeSessionId ?? void 0;
|
|
27211
|
+
try {
|
|
27212
|
+
const adapter = await this.startAdapter({ ...start.startOptions, resumeSessionId, resumeCost: session.sessionCost ?? void 0 }, start.tools, session);
|
|
27213
|
+
const resumed = adapter.nativeSessionId();
|
|
27214
|
+
if (resumeSessionId && resumed && resumed !== resumeSessionId) {
|
|
27215
|
+
throw new Error("The runner started a different session instead.");
|
|
27216
|
+
}
|
|
27217
|
+
return adapter;
|
|
27218
|
+
} catch (err) {
|
|
27219
|
+
if (!resumeSessionId || session.discarded.aborted) throw err;
|
|
27220
|
+
if (this.session === session) this.session = null;
|
|
27221
|
+
session.discard();
|
|
27222
|
+
throw new Error(`The ${this.runner} planner's session could not be resumed (${err instanceof Error ? err.message : String(err)}). Send your message again: the planner picks the conversation up from its transcript.`);
|
|
27223
|
+
}
|
|
27131
27224
|
}
|
|
27132
27225
|
/**
|
|
27133
27226
|
* What the planner gets of the repository's config, its runner being
|
|
@@ -31590,9 +31683,9 @@ var Session = class {
|
|
|
31590
31683
|
runSnapshot() {
|
|
31591
31684
|
if (!this.plan) return EMPTY_RUN;
|
|
31592
31685
|
const planned = reduceRun(EMPTY_RUN, { type: "plan_snapshot", plan: surfacePlan({ ...this.plan, tasks: this.store.snapshot() }) });
|
|
31593
|
-
const
|
|
31594
|
-
const
|
|
31595
|
-
return this.
|
|
31686
|
+
const handed = this.sentHandoff ? reduceRun(planned, { type: "isolation_handoff", ...this.sentHandoff }) : planned;
|
|
31687
|
+
const view = reduceRun(handed, this.events.statusMessage());
|
|
31688
|
+
return this.sentHeld ? reduceRun(view, { type: "plan_edit_held", held: this.sentHeld }) : view;
|
|
31596
31689
|
}
|
|
31597
31690
|
/** The ancestors a task waits on at its merge gate (ADR-0020); empty when it waits for no Merge all. */
|
|
31598
31691
|
mergeGate(taskId) {
|
|
@@ -31782,12 +31875,31 @@ var Session = class {
|
|
|
31782
31875
|
}
|
|
31783
31876
|
carryOverQueuedEdits(plan);
|
|
31784
31877
|
if (adopting) {
|
|
31785
|
-
void this.
|
|
31878
|
+
void this.adoptRun(plan, opts?.persist !== false || opts?.sessionId !== void 0);
|
|
31786
31879
|
this.sentHandoff = this.owedHandoff();
|
|
31787
31880
|
}
|
|
31788
31881
|
if (opts?.persist !== false) this.persist();
|
|
31789
31882
|
if (this.usage.hasUsage) this.broadcast(this.usage.message());
|
|
31790
31883
|
}
|
|
31884
|
+
/**
|
|
31885
|
+
* Take the loaded plan's run over, then correct what the surfaces were told
|
|
31886
|
+
* by what git found: the saved record cannot know what the user merged
|
|
31887
|
+
* since, and its handoff would offer Merge all for work already in their
|
|
31888
|
+
* branch. The status update says what landed work is still unmerged, which
|
|
31889
|
+
* a handoff folds to (see `reduceRun`), and `run_closed` that the run was
|
|
31890
|
+
* cleared up. Saved only where the load itself may save (`persists`), so a
|
|
31891
|
+
* restore with no session file to write keeps the correction in memory for
|
|
31892
|
+
* its next save. A plan loaded meanwhile is not this one's to correct.
|
|
31893
|
+
* Starts nothing (ADR-0044).
|
|
31894
|
+
*/
|
|
31895
|
+
async adoptRun(plan, persists) {
|
|
31896
|
+
const outcome = await this.runs.adopt(plan.isolation ?? null);
|
|
31897
|
+
if (this.closed || this.plan !== plan || outcome === "unchanged") return;
|
|
31898
|
+
plan.isolation = this.runs.planIsolation ?? void 0;
|
|
31899
|
+
if (persists) this.persist();
|
|
31900
|
+
this.events.status(plan);
|
|
31901
|
+
if (outcome === "cleared") this.broadcast({ type: "run_closed" });
|
|
31902
|
+
}
|
|
31791
31903
|
destroy() {
|
|
31792
31904
|
this.closed = true;
|
|
31793
31905
|
this.dropPlanScope();
|
|
@@ -32683,11 +32795,13 @@ var STATE_FILE = "state.json";
|
|
|
32683
32795
|
function getStatePath(baseDir) {
|
|
32684
32796
|
return path33.join(getStateDir(baseDir), STATE_FILE);
|
|
32685
32797
|
}
|
|
32686
|
-
|
|
32798
|
+
var SESSION_ID_KEY = "sessionId";
|
|
32799
|
+
function saveState(plan, baseDir, sessionId) {
|
|
32687
32800
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
32688
32801
|
ensureDir(path33.dirname(statePath));
|
|
32689
32802
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
32690
|
-
|
|
32803
|
+
const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
|
|
32804
|
+
fs17.writeFileSync(statePath, JSON.stringify(file, null, 2));
|
|
32691
32805
|
}
|
|
32692
32806
|
function loadState(baseDir, logger = defaultLogger) {
|
|
32693
32807
|
const dir = baseDir ?? process.cwd();
|
|
@@ -32700,10 +32814,12 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
32700
32814
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
32701
32815
|
return null;
|
|
32702
32816
|
}
|
|
32817
|
+
const sessionId = parsed[SESSION_ID_KEY];
|
|
32818
|
+
delete parsed[SESSION_ID_KEY];
|
|
32703
32819
|
const plan = parsed;
|
|
32704
32820
|
migratePlanStateIsolation(plan);
|
|
32705
32821
|
migrateLoadedTasks(plan.tasks);
|
|
32706
|
-
return plan;
|
|
32822
|
+
return typeof sessionId === "string" && sessionId ? { plan, sessionId } : { plan };
|
|
32707
32823
|
} catch (err) {
|
|
32708
32824
|
logger.warn("stateStore", `failed to load state from ${statePath}; falling back to empty state`, err);
|
|
32709
32825
|
return null;
|