@ordewell/core 0.10.7 → 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.js
CHANGED
|
@@ -10811,6 +10811,8 @@ var RunnerProcess = class {
|
|
|
10811
10811
|
markEnded;
|
|
10812
10812
|
child = null;
|
|
10813
10813
|
exitStatus = null;
|
|
10814
|
+
/** The process itself reported its end, before its pipes drained; see {@link hasExited}. */
|
|
10815
|
+
exitReported = false;
|
|
10814
10816
|
stderrTail = "";
|
|
10815
10817
|
spawnEnv = {};
|
|
10816
10818
|
/** Stdout readers' end-of-stream flushes, for an end the pipe itself never reports. */
|
|
@@ -10861,6 +10863,7 @@ var RunnerProcess = class {
|
|
|
10861
10863
|
});
|
|
10862
10864
|
child.on("close", (code, signal) => this.end({ code, signal }));
|
|
10863
10865
|
child.once("exit", (code, signal) => {
|
|
10866
|
+
this.exitReported = true;
|
|
10864
10867
|
const grace = setTimeout(() => {
|
|
10865
10868
|
for (const flush of [...this.stdoutEnds]) flush();
|
|
10866
10869
|
this.end({ code, signal });
|
|
@@ -10915,6 +10918,15 @@ ${err.message}`);
|
|
|
10915
10918
|
get exit() {
|
|
10916
10919
|
return this.exitStatus;
|
|
10917
10920
|
}
|
|
10921
|
+
/**
|
|
10922
|
+
* Whether the process has ended, for any reason. Unlike {@link exit} this is
|
|
10923
|
+
* true from its own 'exit', not only once 'close' has drained the pipes: a
|
|
10924
|
+
* runner that has exited can take no further turn even while a descendant
|
|
10925
|
+
* holds its stdout or stderr.
|
|
10926
|
+
*/
|
|
10927
|
+
get hasExited() {
|
|
10928
|
+
return this.exitStatus !== null || this.exitReported;
|
|
10929
|
+
}
|
|
10918
10930
|
/** The exit code a listener is told: -1 for a signal or a spawn failure. */
|
|
10919
10931
|
get exitCode() {
|
|
10920
10932
|
return this.exitStatus?.code ?? -1;
|
|
@@ -11113,6 +11125,9 @@ var StdioAgentAdapter = class {
|
|
|
11113
11125
|
nativeSessionId() {
|
|
11114
11126
|
return this.sessionId;
|
|
11115
11127
|
}
|
|
11128
|
+
get exited() {
|
|
11129
|
+
return this.runner.hasExited;
|
|
11130
|
+
}
|
|
11116
11131
|
onProcessExit(listener) {
|
|
11117
11132
|
const fire = () => listener(this.runner.exitCode, this.exitMessage());
|
|
11118
11133
|
if (this.runner.exit) queueMicrotask(fire);
|
|
@@ -14926,7 +14941,7 @@ var OpenCodeAdapter = class {
|
|
|
14926
14941
|
return this.runner.ended;
|
|
14927
14942
|
}
|
|
14928
14943
|
get exited() {
|
|
14929
|
-
return this.runner.
|
|
14944
|
+
return this.runner.hasExited;
|
|
14930
14945
|
}
|
|
14931
14946
|
async start(opts) {
|
|
14932
14947
|
try {
|
|
@@ -23103,10 +23118,21 @@ var GitWorktreeIsolation = class {
|
|
|
23103
23118
|
const entry = record.repos[repo.path];
|
|
23104
23119
|
if (!entry?.changed) continue;
|
|
23105
23120
|
const merged = await this.tryGit(repo.root, ["merge-base", "--is-ancestor", entry.landedTip ?? repo.integrationBranch, "HEAD"]);
|
|
23106
|
-
if (!merged.ok) return false;
|
|
23121
|
+
if (!merged.ok && !await this.integrationGone(repo)) return false;
|
|
23107
23122
|
}
|
|
23108
23123
|
return true;
|
|
23109
23124
|
}
|
|
23125
|
+
/**
|
|
23126
|
+
* The repo's integration branch is gone — deleted by hand, or by a squash
|
|
23127
|
+
* merge's clean-up — in a repo git can read. Merge all merges that branch by
|
|
23128
|
+
* name, so no merge can bring the work in again, and it is not offered as
|
|
23129
|
+
* work to merge. A repo git cannot read at all (moved, or git failing)
|
|
23130
|
+
* proves nothing either way and counts as not merged.
|
|
23131
|
+
*/
|
|
23132
|
+
async integrationGone(repo) {
|
|
23133
|
+
if (!(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", "HEAD"])).ok) return false;
|
|
23134
|
+
return !(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", `refs/heads/${repo.integrationBranch}`])).ok;
|
|
23135
|
+
}
|
|
23110
23136
|
async snapshotTree(workspaceRoot, exclude) {
|
|
23111
23137
|
const scan = await this.scanGroup(workspaceRoot);
|
|
23112
23138
|
if ("refused" in scan) return null;
|
|
@@ -26321,6 +26347,8 @@ var IsolationRunController = class {
|
|
|
26321
26347
|
listener;
|
|
26322
26348
|
liveTasks;
|
|
26323
26349
|
run = null;
|
|
26350
|
+
/** An adoption stays scoped even while cleanup temporarily leaves no run record. */
|
|
26351
|
+
adoptions = 0;
|
|
26324
26352
|
/** Copied paths already reported for the current run: every task gets the same copies. */
|
|
26325
26353
|
reportedCopies = /* @__PURE__ */ new Set();
|
|
26326
26354
|
reportedSetupFailures = /* @__PURE__ */ new Set();
|
|
@@ -26396,18 +26424,27 @@ var IsolationRunController = class {
|
|
|
26396
26424
|
* Take over a plan's persisted isolation, or none for a plan that has not
|
|
26397
26425
|
* isolated yet. Whatever a crashed process left behind for the run — a
|
|
26398
26426
|
* worktree still marked active, a directory no record owns — is pruned,
|
|
26399
|
-
* while kept, failed and conflicted worktrees stay for the user.
|
|
26427
|
+
* while kept, failed and conflicted worktrees stay for the user. Then git is
|
|
26428
|
+
* asked which landed work the user has merged since the record was saved:
|
|
26429
|
+
* a settled run whose landed work is all in HEAD has nothing left to hand
|
|
26430
|
+
* over, so it is cleared up as Merge all would have, unless it still holds
|
|
26431
|
+
* work that never landed (ADR-0013).
|
|
26400
26432
|
*
|
|
26401
|
-
* Deliberately not reported
|
|
26402
|
-
*
|
|
26403
|
-
* would otherwise write a new session file on every reload.
|
|
26433
|
+
* Deliberately not reported to the listener: adopting is not a change of the
|
|
26434
|
+
* session's own, and a host that adopts without persisting (VS Code's
|
|
26435
|
+
* restore) would otherwise write a new session file on every reload. What
|
|
26436
|
+
* git changed is returned instead, for the caller to save where it can and
|
|
26437
|
+
* announce. A plan loaded meanwhile supersedes this adoption, which then
|
|
26438
|
+
* touches nothing and resolves `unchanged`.
|
|
26404
26439
|
*/
|
|
26405
26440
|
async adopt(state) {
|
|
26441
|
+
const adoption = ++this.adoptions;
|
|
26406
26442
|
this.run = state?.run ?? null;
|
|
26407
26443
|
this.resolvers = { ...state?.resolvers ?? {} };
|
|
26408
|
-
|
|
26444
|
+
const run = this.run;
|
|
26445
|
+
if (!run) return "unchanged";
|
|
26409
26446
|
try {
|
|
26410
|
-
const pruned = await this.isolation.pruneOrphans(
|
|
26447
|
+
const pruned = await this.isolation.pruneOrphans(run);
|
|
26411
26448
|
for (const task of pruned.kept) {
|
|
26412
26449
|
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.`);
|
|
26413
26450
|
}
|
|
@@ -26415,6 +26452,24 @@ var IsolationRunController = class {
|
|
|
26415
26452
|
} catch (err) {
|
|
26416
26453
|
this.tell("warn", `Could not prune leftover worktrees: ${err instanceof Error ? err.message : String(err)}`);
|
|
26417
26454
|
}
|
|
26455
|
+
if (this.run !== run) return "unchanged";
|
|
26456
|
+
const found = await this.findLanded(run);
|
|
26457
|
+
if (this.run !== run) return "unchanged";
|
|
26458
|
+
const outcome = found ? "changed" : "unchanged";
|
|
26459
|
+
if (this.isOpen || !this.allMerged(run) || this.holdouts(run).length > 0) return outcome;
|
|
26460
|
+
const resolvers = this.resolvers;
|
|
26461
|
+
this.run = null;
|
|
26462
|
+
this.resolvers = {};
|
|
26463
|
+
const discarded = await this.discardMerged(run);
|
|
26464
|
+
if (this.adoptions !== adoption) return "unchanged";
|
|
26465
|
+
if (!discarded) {
|
|
26466
|
+
if (this.run === null) {
|
|
26467
|
+
this.run = run;
|
|
26468
|
+
this.resolvers = resolvers;
|
|
26469
|
+
}
|
|
26470
|
+
return outcome;
|
|
26471
|
+
}
|
|
26472
|
+
return this.isOpen ? "changed" : "cleared";
|
|
26418
26473
|
}
|
|
26419
26474
|
/** Open a run if none is, leaving whether it isolates to its first change task. */
|
|
26420
26475
|
open() {
|
|
@@ -26571,8 +26626,7 @@ var IsolationRunController = class {
|
|
|
26571
26626
|
const run = this.run;
|
|
26572
26627
|
if (mode !== "isolated" || !run) return;
|
|
26573
26628
|
await this.refreshInHead();
|
|
26574
|
-
|
|
26575
|
-
if (records.some((r) => r.status === "merged") && handoffOf(run).landed.length === 0) {
|
|
26629
|
+
if (this.allMerged(run)) {
|
|
26576
26630
|
this.tell("info", "Everything this run landed is merged into your branch already.");
|
|
26577
26631
|
await this.clearMerged(run);
|
|
26578
26632
|
return;
|
|
@@ -26599,11 +26653,21 @@ var IsolationRunController = class {
|
|
|
26599
26653
|
*/
|
|
26600
26654
|
async refreshInHead() {
|
|
26601
26655
|
const run = this.run;
|
|
26602
|
-
if (!run
|
|
26656
|
+
if (!run) return false;
|
|
26657
|
+
const found = await this.findLanded(run);
|
|
26658
|
+
if (found) this.listener.changed();
|
|
26659
|
+
return found;
|
|
26660
|
+
}
|
|
26661
|
+
/** Mark the run's landed work git finds in HEAD; true when some was found. */
|
|
26662
|
+
async findLanded(run) {
|
|
26663
|
+
if (!Object.values(run.tasks).some((r) => r.status === "merged" && !r.inHead)) return false;
|
|
26603
26664
|
const found = await this.isolation.findInHead(run).catch(() => []);
|
|
26604
|
-
if (found.length > 0) this.listener.changed();
|
|
26605
26665
|
return found.length > 0;
|
|
26606
26666
|
}
|
|
26667
|
+
/** The run landed something and every landing is in the user's HEAD. */
|
|
26668
|
+
allMerged(run) {
|
|
26669
|
+
return Object.values(run.tasks).some((r) => r.status === "merged") && handoffOf(run).landed.length === 0;
|
|
26670
|
+
}
|
|
26607
26671
|
/**
|
|
26608
26672
|
* The tracked state of the workspace as an attempt whose tree is checked
|
|
26609
26673
|
* starts (see `attemptKind.checksTree`), for {@link filesChangedSince}
|
|
@@ -26725,7 +26789,7 @@ var IsolationRunController = class {
|
|
|
26725
26789
|
*/
|
|
26726
26790
|
async clearMerged(run) {
|
|
26727
26791
|
const live2 = this.liveRecords(run);
|
|
26728
|
-
const unlanded =
|
|
26792
|
+
const unlanded = this.holdouts(run).filter((r) => !live2.includes(r));
|
|
26729
26793
|
if (live2.length > 0 || unlanded.length > 0) {
|
|
26730
26794
|
const why = [
|
|
26731
26795
|
...live2.length > 0 ? [`${tasksNamed(live2)} ${live2.length === 1 ? "is" : "are"} still running or waiting on you`] : [],
|
|
@@ -26735,13 +26799,22 @@ var IsolationRunController = class {
|
|
|
26735
26799
|
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.`);
|
|
26736
26800
|
return;
|
|
26737
26801
|
}
|
|
26802
|
+
if (await this.discardMerged(run)) this.forget();
|
|
26803
|
+
}
|
|
26804
|
+
/** The records that keep a settled run from being cleared up: live, or holding work that never landed. */
|
|
26805
|
+
holdouts(run) {
|
|
26806
|
+
const live2 = this.liveTasks();
|
|
26807
|
+
return Object.values(run.tasks).filter((r) => live2.has(r.taskId) || r.status !== "merged");
|
|
26808
|
+
}
|
|
26809
|
+
/** The run's worktrees and merged branches go; false, said, when git would not let them. */
|
|
26810
|
+
async discardMerged(run) {
|
|
26738
26811
|
try {
|
|
26739
26812
|
this.report(await this.isolation.discard(run, { integration: "delete-merged" }));
|
|
26813
|
+
return true;
|
|
26740
26814
|
} catch (err) {
|
|
26741
26815
|
this.tell("warn", `Merged, but could not clean up the run's worktrees and branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
26742
|
-
return;
|
|
26816
|
+
return false;
|
|
26743
26817
|
}
|
|
26744
|
-
this.forget();
|
|
26745
26818
|
}
|
|
26746
26819
|
/** Say what a removal kept back instead of deleting: where the work went, or which worktree stayed. */
|
|
26747
26820
|
report(removal) {
|
|
@@ -30396,17 +30469,37 @@ var CliAgentAiService = class {
|
|
|
30396
30469
|
* `startConversation` reseeds from Ordewell's transcript, which is the same
|
|
30397
30470
|
* degradation `restoreChat` already performs on every surface.
|
|
30398
30471
|
*
|
|
30472
|
+
* A process can end while idle without Ordewell stopping it — the OS, the
|
|
30473
|
+
* CLI itself, a sleeping machine, a signal sent to its group — and every
|
|
30474
|
+
* path of ours that kills one also drops its adapter. So an adapter still
|
|
30475
|
+
* held whose process has ended died on its own, and is restarted here
|
|
30476
|
+
* rather than answering the next message with its exit code.
|
|
30477
|
+
*
|
|
30399
30478
|
* Also restarted when the policy's decision on the runner's own search has
|
|
30400
30479
|
* changed since the process was spawned: the running thread would otherwise
|
|
30401
30480
|
* keep searching unasked after Full turned Guarded. Codex 0.162 applies
|
|
30402
30481
|
* `thread/resume`'s `config.web_search` either way (checked live).
|
|
30403
30482
|
*/
|
|
30404
30483
|
async ensureAdapter(session) {
|
|
30405
|
-
|
|
30484
|
+
const live2 = session.adapter;
|
|
30485
|
+
if (live2 && !live2.exited && session.nativeSearch === this.nativeSearch(session)) return live2;
|
|
30406
30486
|
session.stop();
|
|
30407
30487
|
const start = session.start;
|
|
30408
30488
|
if (!start) throw new Error("No active planner conversation.");
|
|
30409
|
-
|
|
30489
|
+
const resumeSessionId = session.nativeSessionId ?? void 0;
|
|
30490
|
+
try {
|
|
30491
|
+
const adapter = await this.startAdapter({ ...start.startOptions, resumeSessionId, resumeCost: session.sessionCost ?? void 0 }, start.tools, session);
|
|
30492
|
+
const resumed = adapter.nativeSessionId();
|
|
30493
|
+
if (resumeSessionId && resumed && resumed !== resumeSessionId) {
|
|
30494
|
+
throw new Error("The runner started a different session instead.");
|
|
30495
|
+
}
|
|
30496
|
+
return adapter;
|
|
30497
|
+
} catch (err) {
|
|
30498
|
+
if (!resumeSessionId || session.discarded.aborted) throw err;
|
|
30499
|
+
if (this.session === session) this.session = null;
|
|
30500
|
+
session.discard();
|
|
30501
|
+
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.`);
|
|
30502
|
+
}
|
|
30410
30503
|
}
|
|
30411
30504
|
/**
|
|
30412
30505
|
* What the planner gets of the repository's config, its runner being
|
|
@@ -31532,6 +31625,12 @@ function withoutIsolation(tasks) {
|
|
|
31532
31625
|
function handoffOf2({ type: _type, ...handoff }) {
|
|
31533
31626
|
return handoff;
|
|
31534
31627
|
}
|
|
31628
|
+
function stillToMerge(handoff, runMerge) {
|
|
31629
|
+
if (!handoff?.landed.length) return handoff;
|
|
31630
|
+
if (!runMerge) return null;
|
|
31631
|
+
const same = runMerge.landed.length === handoff.landed.length && runMerge.landed.every((t, i) => t.taskId === handoff.landed[i].taskId);
|
|
31632
|
+
return same ? handoff : { ...handoff, repos: runMerge.repos, landed: runMerge.landed };
|
|
31633
|
+
}
|
|
31535
31634
|
function planTasks(plan) {
|
|
31536
31635
|
if ("tasks" in plan) return plan.tasks;
|
|
31537
31636
|
return rejoinPlanTree(plan.pendingTasks, plan.phase === "executing" ? plan.executionLog : []);
|
|
@@ -31552,6 +31651,7 @@ function reduceRun(view, input) {
|
|
|
31552
31651
|
...view,
|
|
31553
31652
|
tasks: applyStatus(view.tasks, new Map(input.tasks.map((t) => [t.id, t]))),
|
|
31554
31653
|
runMerge: input.runMerge ?? null,
|
|
31654
|
+
handoff: stillToMerge(view.handoff, input.runMerge),
|
|
31555
31655
|
usage: input.usage
|
|
31556
31656
|
};
|
|
31557
31657
|
case "task_started":
|
|
@@ -35035,9 +35135,9 @@ var Session = class {
|
|
|
35035
35135
|
runSnapshot() {
|
|
35036
35136
|
if (!this.plan) return EMPTY_RUN;
|
|
35037
35137
|
const planned = reduceRun(EMPTY_RUN, { type: "plan_snapshot", plan: surfacePlan({ ...this.plan, tasks: this.store.snapshot() }) });
|
|
35038
|
-
const
|
|
35039
|
-
const
|
|
35040
|
-
return this.
|
|
35138
|
+
const handed = this.sentHandoff ? reduceRun(planned, { type: "isolation_handoff", ...this.sentHandoff }) : planned;
|
|
35139
|
+
const view = reduceRun(handed, this.events.statusMessage());
|
|
35140
|
+
return this.sentHeld ? reduceRun(view, { type: "plan_edit_held", held: this.sentHeld }) : view;
|
|
35041
35141
|
}
|
|
35042
35142
|
/** The ancestors a task waits on at its merge gate (ADR-0020); empty when it waits for no Merge all. */
|
|
35043
35143
|
mergeGate(taskId) {
|
|
@@ -35227,12 +35327,31 @@ var Session = class {
|
|
|
35227
35327
|
}
|
|
35228
35328
|
carryOverQueuedEdits(plan);
|
|
35229
35329
|
if (adopting) {
|
|
35230
|
-
void this.
|
|
35330
|
+
void this.adoptRun(plan, opts?.persist !== false || opts?.sessionId !== void 0);
|
|
35231
35331
|
this.sentHandoff = this.owedHandoff();
|
|
35232
35332
|
}
|
|
35233
35333
|
if (opts?.persist !== false) this.persist();
|
|
35234
35334
|
if (this.usage.hasUsage) this.broadcast(this.usage.message());
|
|
35235
35335
|
}
|
|
35336
|
+
/**
|
|
35337
|
+
* Take the loaded plan's run over, then correct what the surfaces were told
|
|
35338
|
+
* by what git found: the saved record cannot know what the user merged
|
|
35339
|
+
* since, and its handoff would offer Merge all for work already in their
|
|
35340
|
+
* branch. The status update says what landed work is still unmerged, which
|
|
35341
|
+
* a handoff folds to (see `reduceRun`), and `run_closed` that the run was
|
|
35342
|
+
* cleared up. Saved only where the load itself may save (`persists`), so a
|
|
35343
|
+
* restore with no session file to write keeps the correction in memory for
|
|
35344
|
+
* its next save. A plan loaded meanwhile is not this one's to correct.
|
|
35345
|
+
* Starts nothing (ADR-0044).
|
|
35346
|
+
*/
|
|
35347
|
+
async adoptRun(plan, persists) {
|
|
35348
|
+
const outcome = await this.runs.adopt(plan.isolation ?? null);
|
|
35349
|
+
if (this.closed || this.plan !== plan || outcome === "unchanged") return;
|
|
35350
|
+
plan.isolation = this.runs.planIsolation ?? void 0;
|
|
35351
|
+
if (persists) this.persist();
|
|
35352
|
+
this.events.status(plan);
|
|
35353
|
+
if (outcome === "cleared") this.broadcast({ type: "run_closed" });
|
|
35354
|
+
}
|
|
35236
35355
|
destroy() {
|
|
35237
35356
|
this.closed = true;
|
|
35238
35357
|
this.dropPlanScope();
|
|
@@ -36810,11 +36929,13 @@ var STATE_FILE = "state.json";
|
|
|
36810
36929
|
function getStatePath(baseDir) {
|
|
36811
36930
|
return path34.join(getStateDir(baseDir), STATE_FILE);
|
|
36812
36931
|
}
|
|
36813
|
-
|
|
36932
|
+
var SESSION_ID_KEY = "sessionId";
|
|
36933
|
+
function saveState(plan, baseDir, sessionId) {
|
|
36814
36934
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
36815
36935
|
ensureDir(path34.dirname(statePath));
|
|
36816
36936
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
36817
|
-
|
|
36937
|
+
const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
|
|
36938
|
+
fs17.writeFileSync(statePath, JSON.stringify(file, null, 2));
|
|
36818
36939
|
}
|
|
36819
36940
|
function loadState(baseDir, logger = defaultLogger) {
|
|
36820
36941
|
const dir = baseDir ?? process.cwd();
|
|
@@ -36827,10 +36948,12 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
36827
36948
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
36828
36949
|
return null;
|
|
36829
36950
|
}
|
|
36951
|
+
const sessionId = parsed[SESSION_ID_KEY];
|
|
36952
|
+
delete parsed[SESSION_ID_KEY];
|
|
36830
36953
|
const plan = parsed;
|
|
36831
36954
|
migratePlanStateIsolation(plan);
|
|
36832
36955
|
migrateLoadedTasks(plan.tasks);
|
|
36833
|
-
return plan;
|
|
36956
|
+
return typeof sessionId === "string" && sessionId ? { plan, sessionId } : { plan };
|
|
36834
36957
|
} catch (err) {
|
|
36835
36958
|
logger.warn("stateStore", `failed to load state from ${statePath}; falling back to empty state`, err);
|
|
36836
36959
|
return null;
|