@llblab/pi-kit 0.25.0 → 0.26.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/BACKLOG.md +5 -1
- package/CHANGELOG.md +6 -0
- package/README.md +9 -7
- package/node_modules/@llblab/pi-actors/AGENTS.md +2 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-actors/LICENSE +21 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +4 -3
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +6 -3
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +8 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +48 -3
- package/node_modules/@llblab/pi-claude-usage/index.ts +8 -1159
- package/node_modules/@llblab/pi-claude-usage/lib/extension.ts +30 -0
- package/node_modules/@llblab/pi-claude-usage/lib/fast.ts +24 -0
- package/node_modules/@llblab/pi-claude-usage/lib/query.ts +146 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +297 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +366 -0
- package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +44 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +221 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage.ts +128 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +9 -5
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/LICENSE +21 -0
- package/node_modules/@llblab/pi-clean-room/README.md +1 -1
- package/node_modules/@llblab/pi-clean-room/package.json +3 -2
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +9 -6
- package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +75 -17
- package/node_modules/@llblab/pi-codex-usage/index.ts +8 -1602
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +25 -0
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +23 -0
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +368 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +347 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +435 -0
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +45 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage-store.ts +229 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +425 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +11 -6
- package/node_modules/@llblab/pi-command-fast/AGENTS.md +7 -0
- package/node_modules/@llblab/pi-command-fast/BACKLOG.md +9 -0
- package/node_modules/@llblab/pi-command-fast/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-command-fast/LICENSE +21 -0
- package/node_modules/@llblab/pi-command-fast/README.md +42 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.d.ts +8 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.js +52 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.d.ts +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.js +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.d.ts +10 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.js +81 -0
- package/node_modules/@llblab/pi-command-fast/package.json +49 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-grow-loop/LICENSE +21 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/package.json +3 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +6 -5
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +7 -1
- package/node_modules/@llblab/pi-state-flow/LICENSE +21 -0
- package/node_modules/@llblab/pi-state-flow/README.md +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +238 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +16 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +60 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +8 -5
- package/node_modules/@llblab/pi-state-flow/dist/package.json +10 -9
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +11 -7
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +4 -2
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +14 -13
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +221 -46
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +14 -5
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +52 -1
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +7 -5
- package/node_modules/@llblab/pi-state-flow/package.json +10 -9
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +7 -7
|
@@ -20,7 +20,7 @@ import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAss
|
|
|
20
20
|
import { readProjectedState, readStatePath } from "./query.js";
|
|
21
21
|
import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.js";
|
|
22
22
|
import { TemporalRuntime } from "./runtime.js";
|
|
23
|
-
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
23
|
+
import { discoverSnapshotData, findAssistantToolBatch, findBranchPolicy, findPassiveStopBoundary, hasPendingFork, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
24
24
|
import { hasCompiledSkillArtifact, hashSkillSource, registeredSkillResolver, SkillReadTracker } from "./skills.js";
|
|
25
25
|
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, preRuntimeCheckpoint } from "./snapshot.js";
|
|
26
26
|
import { emptyState, projectStateForModel } from "./state.js";
|
|
@@ -49,15 +49,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
49
49
|
let startActivation;
|
|
50
50
|
let forkInitialization = false;
|
|
51
51
|
let branchRestoration;
|
|
52
|
+
let deferredBranch;
|
|
52
53
|
const restorationOperations = new Set();
|
|
53
54
|
let responseReconciliation;
|
|
54
55
|
let sharedInspectionLifetime = new AbortController();
|
|
56
|
+
let memoryToolLifetime = new AbortController();
|
|
55
57
|
let completedRunAccepted = false;
|
|
56
58
|
let turnAcceptedForBackup = false;
|
|
57
59
|
let compactionPlan;
|
|
58
60
|
let compactionInFlight = false;
|
|
59
61
|
let compactionStopped = false;
|
|
60
|
-
const
|
|
62
|
+
const compactionPrefix = `state-flow-boundary:${randomUUID()}:`;
|
|
63
|
+
let compactionMarker;
|
|
61
64
|
let passiveContinuation;
|
|
62
65
|
let bootstrapContinuation;
|
|
63
66
|
const contextProjection = new ContextProjection();
|
|
@@ -69,7 +72,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
69
72
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
70
73
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => notifyActiveContext(message));
|
|
71
74
|
let backupPending = false;
|
|
72
|
-
|
|
75
|
+
let backupLifetime = new AbortController();
|
|
73
76
|
const backupOperations = new Set();
|
|
74
77
|
let shuttingDown = false;
|
|
75
78
|
let pushFailureNotified = false;
|
|
@@ -170,6 +173,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
170
173
|
sharedInspectionLifetime.abort(new Error("State Flow inspection was cancelled; select the scope again"));
|
|
171
174
|
sharedInspectionLifetime = new AbortController();
|
|
172
175
|
}
|
|
176
|
+
function cancelBackupWork() {
|
|
177
|
+
backupLifetime.abort();
|
|
178
|
+
backupLifetime = new AbortController();
|
|
179
|
+
backupPending = false;
|
|
180
|
+
}
|
|
173
181
|
function cancelInferencePreparation() {
|
|
174
182
|
inferencePreparation?.controller.abort();
|
|
175
183
|
inferencePreparation = undefined;
|
|
@@ -259,22 +267,35 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
259
267
|
async function inspectTelegramState(scope) {
|
|
260
268
|
if (!activeContext || shuttingDown)
|
|
261
269
|
throw new Error("State Flow is not attached to an active session yet");
|
|
262
|
-
|
|
270
|
+
const ctx = activeContext;
|
|
271
|
+
const owner = sessionAddress(ctx).key;
|
|
272
|
+
const cwd = ctx.cwd;
|
|
273
|
+
const off = snapshot.config.mode === "off";
|
|
274
|
+
const privateScope = scope === "session" || scope === "effective";
|
|
275
|
+
if (!off && privateScope)
|
|
263
276
|
assertSelectedBranchAvailable();
|
|
264
|
-
|
|
277
|
+
// Off operator reads are disposable: they confer no selected-branch/cache authority.
|
|
278
|
+
const selected = off ? createRuntime(ctx) : runtime ??= createRuntime(ctx);
|
|
265
279
|
const signal = sharedInspectionLifetime.signal;
|
|
266
|
-
|
|
280
|
+
let privateAvailable = true;
|
|
281
|
+
if (off && privateScope)
|
|
282
|
+
privateAvailable = await selected.refreshCurrentMemory(signal) !== undefined;
|
|
283
|
+
else if (!selected.view || (scope !== "session" && !modePersistenceError))
|
|
267
284
|
await selected.refreshShared(signal);
|
|
268
285
|
signal.throwIfAborted();
|
|
269
|
-
if (runtime !== selected)
|
|
286
|
+
if (activeContext !== ctx || ctx.cwd !== cwd || sessionAddress(ctx).key !== owner || (!off && runtime !== selected)) {
|
|
270
287
|
throw new Error("State Flow inspection selection changed; select the scope again");
|
|
271
|
-
|
|
288
|
+
}
|
|
289
|
+
if (!privateAvailable)
|
|
290
|
+
throw new Error("Current State Flow session memory is unavailable");
|
|
291
|
+
if (!off && privateScope)
|
|
272
292
|
assertSelectedBranchAvailable();
|
|
273
293
|
if (!selected.view)
|
|
274
294
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
if (!off)
|
|
296
|
+
installScopeStates();
|
|
297
|
+
const state = selected.readView(0, scope === "effective" ? undefined : scope);
|
|
298
|
+
return { state: { ...projectModelState(state), ...(state.lazy === undefined ? {} : { lazy: structuredClone(state.lazy) }) }, revisions: temporalScopeRevisions(selected.view), signal };
|
|
278
299
|
}
|
|
279
300
|
function syncStateFlowTools() {
|
|
280
301
|
const active = pi.getActiveTools();
|
|
@@ -414,8 +435,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
414
435
|
return resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
415
436
|
}
|
|
416
437
|
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
417
|
-
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner) {
|
|
438
|
+
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner, requestedMode) {
|
|
418
439
|
contextProjection.reset();
|
|
440
|
+
memoryToolLifetime.abort();
|
|
441
|
+
memoryToolLifetime = new AbortController();
|
|
419
442
|
cancelBranchRestoration();
|
|
420
443
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
421
444
|
if (!startOwner) {
|
|
@@ -429,6 +452,54 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
429
452
|
artifactReads.setCandidates([]);
|
|
430
453
|
telegramStartPending = false;
|
|
431
454
|
activeContext = ctx;
|
|
455
|
+
if (!startOwner) {
|
|
456
|
+
let mode = config.inactiveMode;
|
|
457
|
+
let adoptDefault = false;
|
|
458
|
+
let withoutRuntime = false;
|
|
459
|
+
let persistenceError;
|
|
460
|
+
let pendingFork = false;
|
|
461
|
+
try {
|
|
462
|
+
const branch = ctx.sessionManager.getBranch();
|
|
463
|
+
const discovery = discoverSnapshotData(branch);
|
|
464
|
+
withoutRuntime = discovery.candidates.length === 0 && discovery.errors.length === 0;
|
|
465
|
+
adoptDefault = withoutRuntime && isNewSession(sessionStartReason, branch);
|
|
466
|
+
pendingFork = hasPendingFork(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
467
|
+
const inherited = sessionStartReason === "fork" || pendingFork;
|
|
468
|
+
const policy = findBranchPolicy(branch, inherited ? undefined : ctx.sessionManager.getSessionId(), inherited || retainsPhysicalSessionProjection(sessionStartReason) ? PASSIVE_STOP_ENTRY_TYPE : undefined, config.inactiveMode);
|
|
469
|
+
mode = policy?.mode ?? (adoptDefault ? config.mode : config.inactiveMode);
|
|
470
|
+
persistenceError = inherited
|
|
471
|
+
? findBranchPolicy(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode)?.persistenceError
|
|
472
|
+
: policy?.persistenceError;
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Off needs only policy; unavailable native evidence grants no memory authority.
|
|
476
|
+
}
|
|
477
|
+
if (mode === "off") {
|
|
478
|
+
cancelBackupWork();
|
|
479
|
+
const owner = sessionAddress(ctx).key;
|
|
480
|
+
const reason = pendingFork || (deferredBranch?.owner === owner && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork"
|
|
481
|
+
&& retainsPhysicalSessionProjection(sessionStartReason)) ? "fork" : sessionStartReason;
|
|
482
|
+
if (reason === "fork" && !pendingFork)
|
|
483
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { owner: ctx.sessionManager.getSessionId(), forkPending: true });
|
|
484
|
+
deferredBranch = { reason, owner, cwd: ctx.cwd };
|
|
485
|
+
runtime = undefined;
|
|
486
|
+
snapshot = emptySnapshot("off");
|
|
487
|
+
branchStartsWithoutRuntime = withoutRuntime;
|
|
488
|
+
selectedHistoryExpired = false;
|
|
489
|
+
modePersistenceError = persistenceError;
|
|
490
|
+
forkInitialization = reason === "fork";
|
|
491
|
+
installScopeStates();
|
|
492
|
+
artifactHints = {};
|
|
493
|
+
if (adoptDefault)
|
|
494
|
+
appendCheckpoint();
|
|
495
|
+
syncStateFlowTools();
|
|
496
|
+
updateUi(ctx);
|
|
497
|
+
return Promise.resolve();
|
|
498
|
+
}
|
|
499
|
+
if (pendingFork)
|
|
500
|
+
sessionStartReason = "fork";
|
|
501
|
+
}
|
|
502
|
+
deferredBranch = undefined;
|
|
432
503
|
runtime = createRuntime(ctx);
|
|
433
504
|
installScopeStates();
|
|
434
505
|
branchStartsWithoutRuntime = false;
|
|
@@ -499,9 +570,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
499
570
|
snapshot = migrationFailure({}, "State Flow branch restoration is pending", selection.kind === "current" ? selection.mode
|
|
500
571
|
: selection.kind === "auto-start" ? config.inactiveMode : inactiveSelection(selection.checkpoint.mode));
|
|
501
572
|
}
|
|
573
|
+
if (requestedMode)
|
|
574
|
+
snapshot = deactivateEpisode(snapshot, requestedMode);
|
|
502
575
|
const pending = {
|
|
503
576
|
controller: new AbortController(),
|
|
504
577
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
578
|
+
...(requestedMode === undefined ? {} : { requestedMode }),
|
|
505
579
|
};
|
|
506
580
|
branchRestoration = pending;
|
|
507
581
|
const operation = attachBranch(ctx, pending, selection, sessionStartReason, notifyRecovery, skipped);
|
|
@@ -660,6 +734,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
660
734
|
}
|
|
661
735
|
return passive;
|
|
662
736
|
}
|
|
737
|
+
function selectedContinuation(ctx, reason) {
|
|
738
|
+
if (!retainsPhysicalSessionProjection(reason) || !runtime?.view)
|
|
739
|
+
return undefined;
|
|
740
|
+
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
741
|
+
return boundary === undefined ? undefined : createPassiveContinuation(projectModelState(effectiveState), boundary.at, boundary.from, boundary.preserveContext);
|
|
742
|
+
}
|
|
663
743
|
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
664
744
|
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
665
745
|
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
@@ -668,16 +748,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
668
748
|
else
|
|
669
749
|
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
670
750
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
passiveContinuation = continuation;
|
|
678
|
-
else if (snapshot.meta.bootstrap)
|
|
679
|
-
bootstrapContinuation = continuation;
|
|
680
|
-
}
|
|
751
|
+
const continuation = selectedMemory ? selectedContinuation(ctx, reason) : undefined;
|
|
752
|
+
if (continuation !== undefined) {
|
|
753
|
+
if (!isActive())
|
|
754
|
+
passiveContinuation = continuation;
|
|
755
|
+
else if (snapshot.meta.bootstrap)
|
|
756
|
+
bootstrapContinuation = continuation;
|
|
681
757
|
}
|
|
682
758
|
if (notifyRecovery && isActive() && skipped > 0) {
|
|
683
759
|
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
@@ -764,6 +840,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
764
840
|
return new Text(separatedOutput(theme.fg("dim", formatPatchStateArguments(context.args))), 0, 0);
|
|
765
841
|
},
|
|
766
842
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
843
|
+
signal = signal ? AbortSignal.any([signal, memoryToolLifetime.signal]) : memoryToolLifetime.signal;
|
|
767
844
|
try {
|
|
768
845
|
if (!memoryToolsAvailable())
|
|
769
846
|
throw new Error("State Flow tools are off for this session");
|
|
@@ -844,11 +921,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
844
921
|
attempted = undefined;
|
|
845
922
|
}
|
|
846
923
|
const cause = diagnosticText(error);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
924
|
+
if (!signal.aborted && memoryToolsAvailable())
|
|
925
|
+
recordDiagnostic(cause, /concurrently|advanced/.test(cause) ? "publication-conflict" : "invalid-patch", ctx, {
|
|
926
|
+
input: attempted,
|
|
927
|
+
tool: PATCH_STATE_TOOL_NAME,
|
|
928
|
+
toolCallId,
|
|
929
|
+
});
|
|
852
930
|
throw separatedFailure(error);
|
|
853
931
|
}
|
|
854
932
|
},
|
|
@@ -886,7 +964,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
886
964
|
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
887
965
|
};
|
|
888
966
|
try {
|
|
889
|
-
if (
|
|
967
|
+
if (deferredBranch?.reason === "fork")
|
|
968
|
+
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
969
|
+
else if (!activeContext)
|
|
890
970
|
await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
891
971
|
else if (branchRestoration?.operation)
|
|
892
972
|
await waitForRecovery(branchRestoration.operation, signal);
|
|
@@ -925,7 +1005,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
925
1005
|
const initiallyActive = isActive();
|
|
926
1006
|
let accepted = false;
|
|
927
1007
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
928
|
-
const
|
|
1008
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
1009
|
+
const isCurrent = () => startActivation?.controller === pending && !signal.aborted && runtime === selected && !shuttingDown
|
|
929
1010
|
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
930
1011
|
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && isActive() === (accepted || initiallyActive);
|
|
931
1012
|
const superseded = () => {
|
|
@@ -934,15 +1015,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
934
1015
|
};
|
|
935
1016
|
try {
|
|
936
1017
|
const activation = createRuntime(ctx);
|
|
937
|
-
const
|
|
1018
|
+
const deferred = deferredBranch;
|
|
1019
|
+
if (deferred) {
|
|
1020
|
+
const discovery = discoverSnapshotData(ctx.sessionManager.getBranch());
|
|
1021
|
+
const retained = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
1022
|
+
branchStartsWithoutRuntime = retained.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
1023
|
+
}
|
|
1024
|
+
// Off activation owns one cancellable current-head publication, not an independent read-only restore.
|
|
938
1025
|
const result = await activation.withStartTransaction((current, publish) => {
|
|
939
1026
|
signal.throwIfAborted();
|
|
940
1027
|
if (!isCurrent())
|
|
941
1028
|
throw new Error("State Flow activation selection changed while awaiting publication");
|
|
942
1029
|
if (!current && !branchStartsWithoutRuntime)
|
|
943
1030
|
throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
944
|
-
const
|
|
945
|
-
|
|
1031
|
+
const boundary = deferred && retainsPhysicalSessionProjection(deferred.reason)
|
|
1032
|
+
? findPassiveStopBoundary(ctx.sessionManager.getBranch(), owner, PASSIVE_STOP_ENTRY_TYPE) : undefined;
|
|
1033
|
+
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
946
1034
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
947
1035
|
const recoveredCurrent = selectedHistoryExpired;
|
|
948
1036
|
const publication = publish(activated);
|
|
@@ -954,6 +1042,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
954
1042
|
selectedHistoryExpired = false;
|
|
955
1043
|
modePersistenceError = undefined;
|
|
956
1044
|
installScopeStates();
|
|
1045
|
+
const continuation = passiveContinuation ?? bootstrapContinuation ?? (deferred ? selectedContinuation(ctx, deferred.reason) : undefined);
|
|
1046
|
+
deferredBranch = undefined;
|
|
957
1047
|
clearRunTransient();
|
|
958
1048
|
contextProjection.reset();
|
|
959
1049
|
passiveContinuation = undefined;
|
|
@@ -963,6 +1053,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
963
1053
|
recordPolicyPublication(publication, ctx);
|
|
964
1054
|
syncStateFlowTools();
|
|
965
1055
|
updateUi(ctx);
|
|
1056
|
+
if (forkInitialization) {
|
|
1057
|
+
forkInitialization = false;
|
|
1058
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
1059
|
+
}
|
|
966
1060
|
appendCheckpoint();
|
|
967
1061
|
ctx.ui.notify(recoveredCurrent
|
|
968
1062
|
? "State Flow active from current session memory; unavailable historical state was not restored."
|
|
@@ -1012,9 +1106,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1012
1106
|
updateUi(ctx);
|
|
1013
1107
|
}
|
|
1014
1108
|
function deactivateStateFlow(ctx, mode) {
|
|
1015
|
-
cancelStartActivation();
|
|
1016
1109
|
if (shuttingDown)
|
|
1017
1110
|
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
1111
|
+
if (mode === "off")
|
|
1112
|
+
return selectOff(ctx);
|
|
1113
|
+
cancelStartActivation();
|
|
1018
1114
|
telegramStartPending = false;
|
|
1019
1115
|
const persisting = inactivePersistence;
|
|
1020
1116
|
if (persisting?.operation && !persisting.published) {
|
|
@@ -1030,7 +1126,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1030
1126
|
if (restoring)
|
|
1031
1127
|
restoring.requestedMode = mode;
|
|
1032
1128
|
if (restoring?.awaitingAcceptance) {
|
|
1033
|
-
//
|
|
1129
|
+
// Passive keeps independently owned restoration; Off withdraws it through selectOff.
|
|
1034
1130
|
applyInactiveMode(ctx, mode);
|
|
1035
1131
|
return Promise.resolve({ ok: true, message: `State Flow ${mode}; memory restoration continues` });
|
|
1036
1132
|
}
|
|
@@ -1043,13 +1139,99 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1043
1139
|
cancelInactivePersistence();
|
|
1044
1140
|
const pending = { controller: new AbortController() };
|
|
1045
1141
|
inactivePersistence = pending;
|
|
1046
|
-
const operation =
|
|
1142
|
+
const operation = deferredBranch && mode === "passive" && !branchStartsWithoutRuntime
|
|
1143
|
+
? acquirePassiveMode(ctx, pending, deferredBranch.reason)
|
|
1144
|
+
: persistInactiveMode(ctx, pending, mode);
|
|
1047
1145
|
pending.operation = operation;
|
|
1048
1146
|
const finished = () => { if (inactivePersistence === pending)
|
|
1049
1147
|
inactivePersistence = undefined; };
|
|
1050
1148
|
void operation.then(finished, finished);
|
|
1051
1149
|
return operation;
|
|
1052
1150
|
}
|
|
1151
|
+
/** Off owns cancellation and native policy only; no canonical transaction or semantic handoff. */
|
|
1152
|
+
function selectOff(ctx) {
|
|
1153
|
+
const address = sessionAddress(ctx);
|
|
1154
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
1155
|
+
const branch = ctx.sessionManager.getBranch();
|
|
1156
|
+
const selected = runtime;
|
|
1157
|
+
const sameOwner = selected?.sessionKey === address.key && selected.cwd === ctx.cwd;
|
|
1158
|
+
const forkRecorded = hasPendingFork(branch, owner, PASSIVE_STOP_ENTRY_TYPE);
|
|
1159
|
+
const pendingFork = forkRecorded || (sameOwner && forkInitialization) || (deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork");
|
|
1160
|
+
const policy = findBranchPolicy(branch, owner, PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode);
|
|
1161
|
+
const sameDeferred = deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd;
|
|
1162
|
+
const current = sameOwner || sameDeferred ? snapshot : emptySnapshot("off");
|
|
1163
|
+
if (!sameOwner && !sameDeferred)
|
|
1164
|
+
modePersistenceError = policy?.persistenceError;
|
|
1165
|
+
const alreadyOff = current.config.mode === "off" && policy?.mode === "off";
|
|
1166
|
+
const incoming = sameOwner || sameDeferred ? current.meta.bootstrap ? bootstrapContinuation : passiveContinuation : undefined;
|
|
1167
|
+
const unfinished = current.meta.specification !== undefined || (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted)
|
|
1168
|
+
|| (isActive() && hasUncheckpointedConversation(branch));
|
|
1169
|
+
const at = incoming?.startedAt ?? Date.now();
|
|
1170
|
+
const from = incoming ? incoming.activeRunStartedAt : (sameOwner || sameDeferred) && (!ctx.isIdle() || unfinished) ? runAnchorTimestamp : undefined;
|
|
1171
|
+
const preserveContext = modePersistenceError !== undefined || (incoming ? incoming.preserveContext : current.meta.bootstrap === true || (unfinished && from === undefined));
|
|
1172
|
+
const discovery = discoverSnapshotData(branch);
|
|
1173
|
+
const preRuntime = (sameOwner && branchStartsWithoutRuntime) || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
1174
|
+
let checkpoint;
|
|
1175
|
+
try {
|
|
1176
|
+
checkpoint = !modePersistenceError && current.meta.validation?.attempt !== 0
|
|
1177
|
+
? preRuntime ? preRuntimeCheckpoint("off") : sameOwner && selected?.view ? selected.retainedCheckpoint(deactivateEpisode(current, "off")) : undefined
|
|
1178
|
+
: undefined;
|
|
1179
|
+
}
|
|
1180
|
+
catch {
|
|
1181
|
+
// An unusable cached bookmark cannot prevent native Off policy or authorize replacement memory.
|
|
1182
|
+
}
|
|
1183
|
+
cancelStartActivation();
|
|
1184
|
+
cancelBranchRestoration();
|
|
1185
|
+
cancelInactivePersistence();
|
|
1186
|
+
cancelBackupWork();
|
|
1187
|
+
memoryToolLifetime.abort();
|
|
1188
|
+
memoryToolLifetime = new AbortController();
|
|
1189
|
+
clearRunTransient();
|
|
1190
|
+
contextProjection.reset();
|
|
1191
|
+
telegramStartPending = false;
|
|
1192
|
+
bootstrapContinuation = undefined;
|
|
1193
|
+
passiveContinuation = undefined;
|
|
1194
|
+
artifactInvalidations = [];
|
|
1195
|
+
artifactHints = {};
|
|
1196
|
+
artifactReads.setCandidates([]);
|
|
1197
|
+
runtime = undefined;
|
|
1198
|
+
snapshot = emptySnapshot("off");
|
|
1199
|
+
selectedHistoryExpired = false;
|
|
1200
|
+
branchStartsWithoutRuntime = preRuntime;
|
|
1201
|
+
forkInitialization = pendingFork;
|
|
1202
|
+
deferredBranch = { owner: address.key, cwd: ctx.cwd, reason: pendingFork ? "fork" : undefined };
|
|
1203
|
+
activeContext = ctx;
|
|
1204
|
+
installScopeStates();
|
|
1205
|
+
syncStateFlowTools();
|
|
1206
|
+
updateUi(ctx);
|
|
1207
|
+
try {
|
|
1208
|
+
if (!alreadyOff || (pendingFork && !forkRecorded)) {
|
|
1209
|
+
if (!checkpoint || !preRuntime || pendingFork || preserveContext || from !== undefined || modePersistenceError)
|
|
1210
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1211
|
+
owner, at, mode: "off", memoryDeferred: true,
|
|
1212
|
+
...(from === undefined ? {} : { from }),
|
|
1213
|
+
...(preserveContext ? { preserveContext: true } : {}),
|
|
1214
|
+
...(pendingFork ? { forkPending: true } : {}),
|
|
1215
|
+
...(modePersistenceError === undefined ? {} : { persistenceError: modePersistenceError }),
|
|
1216
|
+
});
|
|
1217
|
+
if (checkpoint)
|
|
1218
|
+
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
1219
|
+
}
|
|
1220
|
+
return Promise.resolve({ ok: true, message: alreadyOff ? "State Flow is already off" : "State Flow off" });
|
|
1221
|
+
}
|
|
1222
|
+
catch (error) {
|
|
1223
|
+
const message = conciseDiagnostic(`State Flow off; native mode recording failed: ${diagnosticText(error)}`);
|
|
1224
|
+
notifyProblem(ctx, message, "error");
|
|
1225
|
+
return Promise.resolve({ ok: false, message });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function acquirePassiveMode(ctx, pending, reason) {
|
|
1229
|
+
await restoreActiveBranch(ctx, reason, true, pending.controller, "passive");
|
|
1230
|
+
if (inactivePersistence !== pending || pending.controller.signal.aborted || shuttingDown) {
|
|
1231
|
+
return { ok: false, message: "State Flow mode change was superseded" };
|
|
1232
|
+
}
|
|
1233
|
+
return persistInactiveMode(ctx, pending, "passive");
|
|
1234
|
+
}
|
|
1053
1235
|
async function persistInactiveMode(ctx, pending, mode) {
|
|
1054
1236
|
let selected = runtime;
|
|
1055
1237
|
const owner = ctx.sessionManager.getSessionId();
|
|
@@ -1080,7 +1262,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1080
1262
|
const handoff = (wasActive || modePersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1081
1263
|
? createPassiveContinuation(projectModelState(effectiveState), incomingBoundary?.startedAt ?? stoppedAt, incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined, modePersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)))
|
|
1082
1264
|
: undefined;
|
|
1083
|
-
//
|
|
1265
|
+
// Repeated Passive choices keep the already selected continuation boundary.
|
|
1084
1266
|
passiveContinuation = handoff ?? (!wasActive ? passiveContinuation : undefined);
|
|
1085
1267
|
return handoff;
|
|
1086
1268
|
};
|
|
@@ -1400,24 +1582,26 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1400
1582
|
}
|
|
1401
1583
|
});
|
|
1402
1584
|
pi.on("session_before_compact", (event) => {
|
|
1403
|
-
if (!
|
|
1585
|
+
if (event.reason !== "manual" || !event.customInstructions?.startsWith("state-flow-boundary:"))
|
|
1404
1586
|
return;
|
|
1405
|
-
|
|
1587
|
+
const marker = compactionMarker;
|
|
1588
|
+
if (shuttingDown || compactionStopped || !isActive() || !compactionPlan || !marker || event.customInstructions !== marker)
|
|
1406
1589
|
return { cancel: true };
|
|
1407
|
-
const result = stateFlowCompactionResult(compactionPlan,
|
|
1590
|
+
const result = stateFlowCompactionResult(compactionPlan, marker, event);
|
|
1408
1591
|
if (result === undefined || "cancel" in result)
|
|
1409
1592
|
return result;
|
|
1410
1593
|
return { compaction: result };
|
|
1411
1594
|
});
|
|
1412
1595
|
pi.on("agent_before_settle", async (_event, ctx) => {
|
|
1413
|
-
if (shuttingDown || !turnAcceptedForBackup)
|
|
1596
|
+
if (shuttingDown || !memoryToolsAvailable() || !turnAcceptedForBackup)
|
|
1414
1597
|
return;
|
|
1415
1598
|
turnAcceptedForBackup = false;
|
|
1416
1599
|
if (!backupPending)
|
|
1417
1600
|
return;
|
|
1418
1601
|
backupPending = false;
|
|
1419
1602
|
const operationSignal = ctx.signal;
|
|
1420
|
-
const
|
|
1603
|
+
const lifetime = backupLifetime.signal;
|
|
1604
|
+
const signal = operationSignal ? AbortSignal.any([lifetime, operationSignal]) : lifetime;
|
|
1421
1605
|
const selected = runtime;
|
|
1422
1606
|
try {
|
|
1423
1607
|
if (existsSync(join(repositoryRoot, ".git"))) {
|
|
@@ -1432,10 +1616,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1432
1616
|
finally {
|
|
1433
1617
|
backupOperations.delete(operation);
|
|
1434
1618
|
}
|
|
1435
|
-
if (signal.aborted)
|
|
1619
|
+
if (signal.aborted || snapshot.config.mode === "off")
|
|
1436
1620
|
return;
|
|
1437
1621
|
startStateFlowBackupPush(repositoryRoot, (error) => {
|
|
1438
|
-
if (shuttingDown)
|
|
1622
|
+
if (shuttingDown || lifetime.aborted || snapshot.config.mode === "off")
|
|
1439
1623
|
return;
|
|
1440
1624
|
const message = diagnosticText(error);
|
|
1441
1625
|
const recorded = diagnosticWriter.recordBackupPushFailure(pushSessionId, pushCwd, message);
|
|
@@ -1445,7 +1629,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1445
1629
|
notifyActiveContext(recorded
|
|
1446
1630
|
? `State Flow Git backup push failed; state is saved locally. Details: ${stateFlowLogPath(agentDir)}. A later accepted turn retries.`
|
|
1447
1631
|
: `State Flow Git backup push failed; local diagnostics unavailable: ${message}`);
|
|
1448
|
-
}, () => { pushFailureNotified = false; });
|
|
1632
|
+
}, () => { pushFailureNotified = false; }, lifetime);
|
|
1449
1633
|
}
|
|
1450
1634
|
}
|
|
1451
1635
|
catch (error) {
|
|
@@ -1476,11 +1660,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1476
1660
|
if (!plan)
|
|
1477
1661
|
return;
|
|
1478
1662
|
compactionPlan = plan;
|
|
1663
|
+
const marker = compactionMarker = `${compactionPrefix}${randomUUID()}`;
|
|
1479
1664
|
compactionInFlight = true;
|
|
1480
1665
|
// Pi dispatches deferred companion prompts after all settled handlers return.
|
|
1481
1666
|
await new Promise((resolve) => {
|
|
1482
|
-
const finished = () => {
|
|
1483
|
-
|
|
1667
|
+
const finished = () => {
|
|
1668
|
+
if (compactionMarker === marker) {
|
|
1669
|
+
compactionPlan = undefined;
|
|
1670
|
+
compactionInFlight = false;
|
|
1671
|
+
}
|
|
1672
|
+
resolve();
|
|
1673
|
+
};
|
|
1674
|
+
ctx.compact({ customInstructions: marker, onComplete: finished, onError: finished });
|
|
1484
1675
|
});
|
|
1485
1676
|
});
|
|
1486
1677
|
pi.on("session_compact", () => { contextProjection.reset(); });
|
|
@@ -1497,6 +1688,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1497
1688
|
});
|
|
1498
1689
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
1499
1690
|
shuttingDown = true;
|
|
1691
|
+
memoryToolLifetime.abort();
|
|
1500
1692
|
contextProjection.reset();
|
|
1501
1693
|
const restoring = cancelBranchRestoration();
|
|
1502
1694
|
const starting = cancelStartActivation();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Await a coherent capture; hosts without cancellation may refuse contention instead of hanging Abort. */
|
|
2
2
|
export declare function backupCurrentStateFlowFiles(repositoryRoot: string, signal?: AbortSignal, waitForLock?: boolean): Promise<string | undefined>;
|
|
3
3
|
/** Skip overlapping pushes; the next accepted turn can push the latest HEAD. */
|
|
4
|
-
export declare function startStateFlowBackupPush(repositoryRoot: string, onFailure: (error: unknown) => void, onSuccess?: () => void): boolean;
|
|
4
|
+
export declare function startStateFlowBackupPush(repositoryRoot: string, onFailure: (error: unknown) => void, onSuccess?: () => void, signal?: AbortSignal): boolean;
|
|
5
5
|
/** Resolve only after the push process has closed (including timeout termination). */
|
|
6
6
|
export declare function awaitInFlightBackupPushes(repositoryRoot: string): Promise<void>;
|
|
7
7
|
/** Push the current backup commit to its explicitly configured branch remote without blocking settlement. */
|
|
8
|
-
export declare function pushCurrentStateFlowBackup(repositoryRoot: string): Promise<{
|
|
8
|
+
export declare function pushCurrentStateFlowBackup(repositoryRoot: string, signal?: AbortSignal): Promise<{
|
|
9
9
|
commit: string;
|
|
10
10
|
remote: string;
|
|
11
11
|
ref: string;
|
|
@@ -204,18 +204,20 @@ export function backupCurrentStateFlowFiles(repositoryRoot, signal, waitForLock
|
|
|
204
204
|
return withBackupLock(repositoryRoot, (root) => commitCurrentOwnedFiles(root, currentHead(root), signal, waitForLock), signal, waitForLock);
|
|
205
205
|
}
|
|
206
206
|
/** Skip overlapping pushes; the next accepted turn can push the latest HEAD. */
|
|
207
|
-
export function startStateFlowBackupPush(repositoryRoot, onFailure, onSuccess) {
|
|
207
|
+
export function startStateFlowBackupPush(repositoryRoot, onFailure, onSuccess, signal) {
|
|
208
208
|
const root = resolve(repositoryRoot);
|
|
209
|
-
if (activePushes.has(root))
|
|
209
|
+
if (signal?.aborted || activePushes.has(root))
|
|
210
210
|
return false;
|
|
211
|
-
const push = pushCurrentStateFlowBackup(root).then((result) => {
|
|
212
|
-
if (result) {
|
|
211
|
+
const push = pushCurrentStateFlowBackup(root, signal).then((result) => {
|
|
212
|
+
if (result && !signal?.aborted) {
|
|
213
213
|
try {
|
|
214
214
|
onSuccess?.();
|
|
215
215
|
}
|
|
216
216
|
catch { /* Reporting cannot change push acceptance. */ }
|
|
217
217
|
}
|
|
218
218
|
}, (error) => {
|
|
219
|
+
if (signal?.aborted)
|
|
220
|
+
return;
|
|
219
221
|
try {
|
|
220
222
|
onFailure(error);
|
|
221
223
|
}
|
|
@@ -231,15 +233,17 @@ export async function awaitInFlightBackupPushes(repositoryRoot) {
|
|
|
231
233
|
await push;
|
|
232
234
|
}
|
|
233
235
|
/** Push the current backup commit to its explicitly configured branch remote without blocking settlement. */
|
|
234
|
-
export function pushCurrentStateFlowBackup(repositoryRoot) {
|
|
236
|
+
export function pushCurrentStateFlowBackup(repositoryRoot, signal) {
|
|
235
237
|
return new Promise((resolvePush, rejectPush) => {
|
|
236
238
|
let root;
|
|
237
239
|
let commit;
|
|
238
240
|
let destination;
|
|
239
241
|
try {
|
|
242
|
+
signal?.throwIfAborted();
|
|
240
243
|
root = assertRepositoryRoot(repositoryRoot);
|
|
241
244
|
commit = currentHead(root);
|
|
242
245
|
destination = configuredPushDestination(root);
|
|
246
|
+
signal?.throwIfAborted();
|
|
243
247
|
}
|
|
244
248
|
catch (error) {
|
|
245
249
|
rejectPush(new Error(redactGitDiagnostic(error instanceof Error ? error.message : String(error))));
|
|
@@ -282,6 +286,7 @@ export function pushCurrentStateFlowBackup(repositoryRoot) {
|
|
|
282
286
|
return;
|
|
283
287
|
settled = true;
|
|
284
288
|
clearTimeout(timeout);
|
|
289
|
+
signal?.removeEventListener("abort", cancelled);
|
|
285
290
|
if (error)
|
|
286
291
|
rejectPush(error);
|
|
287
292
|
else
|
|
@@ -297,5 +302,11 @@ export function pushCurrentStateFlowBackup(repositoryRoot) {
|
|
|
297
302
|
child.stderr?.destroy(); });
|
|
298
303
|
child.once("close", (code, endedBy) => finish(failure ?? (code === 0 ? undefined
|
|
299
304
|
: new Error(`Git backup push failed (${endedBy ?? code}): ${redactGitDiagnostic(stderr.trim()) || "no diagnostic output"}`))));
|
|
305
|
+
function cancelled() {
|
|
306
|
+
terminate(signal?.reason instanceof Error ? signal.reason : new Error("Git backup push cancelled"));
|
|
307
|
+
}
|
|
308
|
+
signal?.addEventListener("abort", cancelled, { once: true });
|
|
309
|
+
if (signal?.aborted)
|
|
310
|
+
cancelled();
|
|
300
311
|
});
|
|
301
312
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InactiveMode } from "./snapshot.ts";
|
|
1
|
+
import { type InactiveMode, type StateFlowMode } from "./snapshot.ts";
|
|
2
2
|
export declare const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
|
|
3
3
|
interface BranchEntry {
|
|
4
4
|
type?: unknown;
|
|
@@ -26,6 +26,8 @@ export interface PassiveStopBoundary {
|
|
|
26
26
|
persistenceError?: string;
|
|
27
27
|
/** The inactive mode selected by that failed change; legacy markers omit it. */
|
|
28
28
|
mode?: InactiveMode;
|
|
29
|
+
/** Native-only Off choice; canonical runtime mode is deliberately not updated. */
|
|
30
|
+
memoryDeferred?: true;
|
|
29
31
|
}
|
|
30
32
|
export interface SnapshotDiscovery {
|
|
31
33
|
candidates: unknown[];
|
|
@@ -33,6 +35,13 @@ export interface SnapshotDiscovery {
|
|
|
33
35
|
}
|
|
34
36
|
/** Enumerate active-branch snapshots newest-first while containing hostile entries. */
|
|
35
37
|
export declare function discoverSnapshotData(branch: readonly BranchEntry[]): SnapshotDiscovery;
|
|
38
|
+
/** Read native mode policy only; semantic checkpoint validity remains the recovery owner's concern. */
|
|
39
|
+
export declare function findBranchPolicy(branch: readonly BranchEntry[], sessionId: string | undefined, stopEntryType: string | undefined, inactiveMode: InactiveMode): {
|
|
40
|
+
mode: StateFlowMode;
|
|
41
|
+
persistenceError?: string;
|
|
42
|
+
} | undefined;
|
|
43
|
+
/** Native policy bookkeeping retains an unacquired fork across extension reloads. */
|
|
44
|
+
export declare function hasPendingFork(branch: readonly BranchEntry[], sessionId: string, entryType: string): boolean;
|
|
36
45
|
export declare function snapshotDataNewestFirst(branch: readonly BranchEntry[]): unknown[];
|
|
37
46
|
export declare function latestSnapshotData(branch: readonly BranchEntry[]): unknown;
|
|
38
47
|
export declare function hasPriorConversation(branch: readonly BranchEntry[]): boolean;
|