dsh-pause-session 0.2.0 → 0.2.1
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/CHANGELOG.md +32 -39
- package/lib/client.js +95 -70
- package/lib/client.js.map +1 -1
- package/lib/index.js +187 -182
- package/lib/index.js.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/remote.d.ts +6 -0
- package/lib/types/composition.d.ts +35 -61
- package/lib/types/index.d.ts +1 -1
- package/lib/types/release-order.d.ts +8 -4
- package/lib/types/resume-activation.d.ts +14 -5
- package/lib/types/resume.d.ts +2 -4
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SessionId, deriveEventMessage, foldRequestHeader, foldSurface, interruptedTurnClosers } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { Remote, RemoteError, TypertRemoteService, principalOf, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
+
import { foldSubagentDescriptor } from "@deepseek-ai/dsh-subagent";
|
|
6
7
|
import { DomainError, defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
7
8
|
//#region lib/types/history.js
|
|
8
9
|
/**
|
|
@@ -730,7 +731,13 @@ function mountGlobalPause(ctx, watch, registry, deps) {
|
|
|
730
731
|
//#endregion
|
|
731
732
|
//#region lib/types/release-order.js
|
|
732
733
|
/**
|
|
733
|
-
* The order a
|
|
734
|
+
* The order a batch release walks: parent before child, shallowest first.
|
|
735
|
+
*
|
|
736
|
+
* A single tree release already has the pre-order from its own walk, so this
|
|
737
|
+
* module serves the batch path, where the recorded sessions may span several
|
|
738
|
+
* trees. `listDescendants` stamps each row with its distance from the listing
|
|
739
|
+
* root, so one catalog read per recorded root places every member. A session
|
|
740
|
+
* the walk never reached is its own root and releases with the first level.
|
|
734
741
|
* @module dsh-pause-session/release-order
|
|
735
742
|
*/
|
|
736
743
|
/**
|
|
@@ -738,100 +745,54 @@ function mountGlobalPause(ctx, watch, registry, deps) {
|
|
|
738
745
|
* @param ctx - host context carrying the optional subagent catalog.
|
|
739
746
|
* @param sessionIds - every recorded session, in registry order.
|
|
740
747
|
* @param signal - cancellation for each catalog read.
|
|
741
|
-
* @returns
|
|
748
|
+
* @returns one list per depth level, shallowest first.
|
|
742
749
|
*/
|
|
743
750
|
async function releaseOrder(ctx, sessionIds, signal) {
|
|
744
|
-
const
|
|
745
|
-
const parentOf = /* @__PURE__ */ new Map();
|
|
751
|
+
const depth = /* @__PURE__ */ new Map();
|
|
746
752
|
const subagents = ctx.get("subagents");
|
|
747
|
-
if (subagents !== void 0)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
753
|
+
if (subagents !== void 0) {
|
|
754
|
+
const owned = new Set(sessionIds);
|
|
755
|
+
for (const sessionId of sessionIds) {
|
|
756
|
+
signal?.throwIfAborted();
|
|
757
|
+
if (depth.has(sessionId)) continue;
|
|
758
|
+
depth.set(sessionId, 0);
|
|
759
|
+
try {
|
|
760
|
+
for (const row of await subagents.listDescendants(sessionId, signal)) {
|
|
761
|
+
if (row.kind !== "child" || !owned.has(row.id)) continue;
|
|
762
|
+
depth.set(row.id, row.depth);
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (signal?.aborted === true) throw error;
|
|
766
|
+
ctx.logger.warn(`pause: could not read the descendants of "${sessionId}": ${describeError(error)}`);
|
|
767
|
+
}
|
|
754
768
|
}
|
|
755
769
|
}
|
|
756
|
-
const depthOf = (sessionId) => {
|
|
757
|
-
let steps = 0;
|
|
758
|
-
const seen = /* @__PURE__ */ new Set([sessionId]);
|
|
759
|
-
for (let at = parentOf.get(sessionId); at !== void 0; at = parentOf.get(at)) {
|
|
760
|
-
if (seen.has(at)) break;
|
|
761
|
-
seen.add(at);
|
|
762
|
-
steps += 1;
|
|
763
|
-
}
|
|
764
|
-
return steps;
|
|
765
|
-
};
|
|
766
770
|
const levels = /* @__PURE__ */ new Map();
|
|
767
771
|
for (const sessionId of sessionIds) {
|
|
768
|
-
const level =
|
|
772
|
+
const level = depth.get(sessionId) ?? 0;
|
|
769
773
|
const bucket = levels.get(level);
|
|
770
774
|
if (bucket === void 0) levels.set(level, [sessionId]);
|
|
771
775
|
else bucket.push(sessionId);
|
|
772
776
|
}
|
|
773
|
-
return {
|
|
774
|
-
parents: parentOf,
|
|
775
|
-
levels: [...levels.keys()].sort((left, right) => left - right).map((level) => levels.get(level) ?? [])
|
|
776
|
-
};
|
|
777
|
+
return { levels: [...levels.keys()].sort((left, right) => left - right).map((level) => levels.get(level) ?? []) };
|
|
777
778
|
}
|
|
778
779
|
//#endregion
|
|
779
780
|
//#region lib/types/composition.js
|
|
780
781
|
/**
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* Activation is not only a persisted Session: the Agent preset supplies the
|
|
784
|
-
* scoped tools, prompt sections, skills, and — for a subagent child — the
|
|
785
|
-
* delegation context, persona, and tool restriction that were in force before
|
|
786
|
-
* the park. A resume that loads the log without that setup starts a different
|
|
787
|
-
* Agent on the same history.
|
|
782
|
+
* What a parked Session needs to come back as itself.
|
|
788
783
|
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
* that are reapplied only on cold resume.
|
|
784
|
+
* A root Session is loaded directly: the Agent preset it ran supplies the
|
|
785
|
+
* scoped tools, prompt sections, and skills, so the resume mounts that preset
|
|
786
|
+
* before the Agent is published. A subagent child is not loaded here at all —
|
|
787
|
+
* the subagent runtime owns its activation, composition, and tree membership,
|
|
788
|
+
* and cold-resumes it when this plugin delivers the wake through
|
|
789
|
+
* `ctx.subagents.prompt`.
|
|
796
790
|
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
* A profile that mounts one but cannot read the Session projection cannot
|
|
800
|
-
* prove what composition the Session ran, so it fails the resume instead of
|
|
801
|
-
* silently starting a different Agent.
|
|
791
|
+
* The two cases are told apart by one read of the Session's own log, which is
|
|
792
|
+
* the same read the press-time resumability check already pays for.
|
|
802
793
|
* @module dsh-pause-session/composition
|
|
803
794
|
*/
|
|
804
795
|
/**
|
|
805
|
-
* A stored subagent route that no resume may reactivate.
|
|
806
|
-
*
|
|
807
|
-
* A one-shot child is terminal by contract; activating it as a root Agent
|
|
808
|
-
* would turn a finished delegation into an ordinary conversation that happens
|
|
809
|
-
* to share its Session id.
|
|
810
|
-
*/
|
|
811
|
-
var UnresumableSessionError = class extends Error {
|
|
812
|
-
constructor(sessionId) {
|
|
813
|
-
super(`subagent session "${String(sessionId)}" has a one-shot route and cannot be resumed`);
|
|
814
|
-
this.name = "UnresumableSessionError";
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
/**
|
|
818
|
-
* Whether the child's own log selected a preset before its descriptor.
|
|
819
|
-
*
|
|
820
|
-
* The creation-time selection precedes the first `subagent/descriptor`; a
|
|
821
|
-
* selection after the descriptor belongs to the child's own later history and
|
|
822
|
-
* does not decide which composition cold resume joins. This mirrors the
|
|
823
|
-
* subagent runtime's own cold-resume rule.
|
|
824
|
-
* @param events - the child's own log suffix, without the fork-inherited prefix.
|
|
825
|
-
* @returns whether the child carries an independent preset selection.
|
|
826
|
-
*/
|
|
827
|
-
function hasIndependentPresetSelection(events) {
|
|
828
|
-
for (const event of events) {
|
|
829
|
-
if (event.type === "subagent/descriptor") return false;
|
|
830
|
-
if (event.type === "agent-preset/selected") return true;
|
|
831
|
-
}
|
|
832
|
-
return false;
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
796
|
* Whether a local Session may be parked under a promise of a later resume.
|
|
836
797
|
*
|
|
837
798
|
* A normal root Session has no child descriptor and is resumable. A local
|
|
@@ -860,64 +821,40 @@ async function isResumableSession(ctx, sessionId, signal) {
|
|
|
860
821
|
}
|
|
861
822
|
}
|
|
862
823
|
/**
|
|
863
|
-
* Read one parked Session's
|
|
824
|
+
* Read one parked Session's own log and decide how it comes back.
|
|
864
825
|
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
826
|
+
* A child returns as `child` with no setup: the subagent runtime restores the
|
|
827
|
+
* composition from the descriptor and the preset projection, and mounting a
|
|
828
|
+
* second one here would start a different Agent on the same history.
|
|
829
|
+
*
|
|
830
|
+
* A root resolves the preset it ran — the projection, never the creation-time
|
|
831
|
+
* header alone, because a blank Session may select another preset before its
|
|
832
|
+
* first turn — and hands back the setup that mounts it. A profile with no
|
|
833
|
+
* preset roster has no scoped composition to restore and resumes unchanged.
|
|
872
834
|
* @param ctx - host context carrying the optional preset and Session-query services.
|
|
873
835
|
* @param sessionId - the parked Session being resumed.
|
|
874
|
-
* @param parent - the live direct parent, when a child is resumed under it.
|
|
875
836
|
* @param signal - cancellation for the observation read.
|
|
876
|
-
* @returns
|
|
877
|
-
* @throws
|
|
878
|
-
* @throws when a mounted preset roster cannot read the Session projection, or
|
|
879
|
-
* a recorded preset cannot be restored.
|
|
837
|
+
* @returns how this Session comes back.
|
|
838
|
+
* @throws when a mounted preset roster cannot read the Session projection.
|
|
880
839
|
*/
|
|
881
|
-
async function
|
|
840
|
+
async function readParkedComposition(ctx, sessionId, signal) {
|
|
882
841
|
const presets = ctx.get("agentPresets");
|
|
883
842
|
const query = ctx.get("sessionQuery");
|
|
884
843
|
if (query === void 0) {
|
|
885
844
|
if (presets !== void 0) throw new Error("resuming a composed Agent requires the session-query service");
|
|
886
|
-
return {};
|
|
845
|
+
return { kind: "root" };
|
|
887
846
|
}
|
|
888
847
|
const observation = await query.observeSession(sessionId, { ...signal === void 0 ? {} : { signal } });
|
|
889
848
|
try {
|
|
849
|
+
if (foldSubagentDescriptor(observation.events.slice(observation.inheritedEventCount)) !== void 0 || observation.header.origin === "subagent") return { kind: "child" };
|
|
850
|
+
if (presets === void 0) return { kind: "root" };
|
|
890
851
|
const projections = observation.projections;
|
|
891
|
-
|
|
892
|
-
const
|
|
893
|
-
if (descriptor?.mode === "one-shot") throw new UnresumableSessionError(sessionId);
|
|
894
|
-
if (presets === void 0 || projections === void 0) {
|
|
895
|
-
if (presets !== void 0) throw new Error("resuming a composed Agent requires projected Session state");
|
|
896
|
-
return {};
|
|
897
|
-
}
|
|
898
|
-
if (parent === void 0) {
|
|
899
|
-
const resolved = await presets.resolve(projections.values.agentPreset ?? void 0);
|
|
900
|
-
return { setup: async (agentCtx) => {
|
|
901
|
-
await presets.mount(agentCtx, resolved.id);
|
|
902
|
-
} };
|
|
903
|
-
}
|
|
904
|
-
const independentlySelected = hasIndependentPresetSelection(ownEvents);
|
|
905
|
-
const presetId = independentlySelected ? projections.values.agentPreset : void 0;
|
|
906
|
-
if (independentlySelected && typeof presetId !== "string") throw new Error(`subagent "${String(sessionId)}" has no projected agent preset to restore`);
|
|
907
|
-
const composition = {
|
|
908
|
-
...typeof presetId === "string" ? { agentPreset: presetId } : {},
|
|
909
|
-
...descriptor?.mode === "continuable" && descriptor.persona !== void 0 ? { persona: descriptor.persona } : {},
|
|
910
|
-
...descriptor?.mode === "continuable" && descriptor.toolFilter !== void 0 ? { toolFilter: descriptor.toolFilter } : {}
|
|
911
|
-
};
|
|
912
|
-
const agentOptions = descriptor?.mode === "continuable" ? {
|
|
913
|
-
...descriptor.agentProvider === void 0 ? {} : { provider: descriptor.agentProvider },
|
|
914
|
-
...descriptor.agentModel === void 0 ? {} : { model: descriptor.agentModel },
|
|
915
|
-
...descriptor.agentReasoningEffort === void 0 ? {} : { reasoningEffort: descriptor.agentReasoningEffort }
|
|
916
|
-
} : void 0;
|
|
852
|
+
if (projections === void 0) throw new Error("resuming a composed Agent requires projected Session state");
|
|
853
|
+
const resolved = await presets.resolve(projections.values.agentPreset ?? void 0);
|
|
917
854
|
return {
|
|
918
|
-
|
|
855
|
+
kind: "root",
|
|
919
856
|
setup: async (agentCtx) => {
|
|
920
|
-
await
|
|
857
|
+
await presets.mount(agentCtx, resolved.id);
|
|
921
858
|
}
|
|
922
859
|
};
|
|
923
860
|
} finally {
|
|
@@ -965,16 +902,25 @@ function summarizeTreeRelease(released) {
|
|
|
965
902
|
//#endregion
|
|
966
903
|
//#region lib/types/resume-activation.js
|
|
967
904
|
/**
|
|
968
|
-
* Reactivate one parked
|
|
969
|
-
*
|
|
905
|
+
* Reactivate one parked Session and deliver the wake it owes.
|
|
906
|
+
*
|
|
907
|
+
* The two Session kinds come back through different owners. An ordinary
|
|
908
|
+
* Session is loaded by this plugin and given the route its own log recorded.
|
|
909
|
+
* A subagent child belongs to the subagent runtime: delivering the wake through
|
|
910
|
+
* `ctx.subagents.prompt` is what cold-resumes it as a managed activation, under
|
|
911
|
+
* its live tree root, with its persisted composition and tree membership
|
|
912
|
+
* restored by the runtime itself.
|
|
913
|
+
*
|
|
914
|
+
* Both paths keep the same release order: the durable record goes first, then
|
|
915
|
+
* the gate, so a record that cannot be dropped leaves the Session parked.
|
|
970
916
|
* @module dsh-pause-session/resume-activation
|
|
971
917
|
*/
|
|
972
918
|
/**
|
|
973
919
|
* Build the activation closure.
|
|
974
|
-
* @param ctx - host context carrying the agent store and goal service.
|
|
920
|
+
* @param ctx - host context carrying the agent store, subagent runtime, and goal service.
|
|
975
921
|
* @param gate - the pause gate released after activation.
|
|
976
922
|
* @param registry - the durable list the release forgets from.
|
|
977
|
-
* @param options - the route knowledge the wake content depends on.
|
|
923
|
+
* @param options - the route knowledge the root wake content depends on.
|
|
978
924
|
* @returns the activation methods the resume path uses.
|
|
979
925
|
*/
|
|
980
926
|
function createActivator(ctx, gate, registry, options) {
|
|
@@ -1023,27 +969,29 @@ function createActivator(ctx, gate, registry, options) {
|
|
|
1023
969
|
ctx.logger.warn(`pause: could not re-open the goal of "${agent.id}": ${describeError(error)}`);
|
|
1024
970
|
}
|
|
1025
971
|
};
|
|
1026
|
-
|
|
1027
|
-
|
|
972
|
+
/** Drop the durable record, then the gate: the order every release keeps. */
|
|
973
|
+
const releaseRecord = async (sessionId) => {
|
|
974
|
+
try {
|
|
975
|
+
await registry.forget(sessionId);
|
|
976
|
+
} catch (error) {
|
|
977
|
+
ctx.logger.warn(`pause: could not drop "${sessionId}" from the pause registry: ${describeError(error)}`);
|
|
978
|
+
return {
|
|
979
|
+
kind: "error",
|
|
980
|
+
text: UNRECORDED_TEXT
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
gate.resume(sessionId);
|
|
984
|
+
};
|
|
985
|
+
/** Load an ordinary Session, give it its recorded route, and deliver the wake. */
|
|
986
|
+
const activateRoot = async (sessionId, request, rearm, composition, signal) => {
|
|
1028
987
|
let agent;
|
|
1029
988
|
try {
|
|
1030
|
-
const composition = await restoreComposition(ctx, sessionId, parent, signal);
|
|
1031
989
|
agent = (await ctx.root.agents.resume({
|
|
1032
990
|
resumeSessionId: sessionId,
|
|
1033
|
-
...
|
|
1034
|
-
...composition,
|
|
991
|
+
...composition.setup === void 0 ? {} : { setup: composition.setup },
|
|
1035
992
|
...signal === void 0 ? {} : { signal }
|
|
1036
993
|
})).agent;
|
|
1037
994
|
} catch (error) {
|
|
1038
|
-
if (error instanceof UnresumableSessionError) return {
|
|
1039
|
-
sessionId,
|
|
1040
|
-
resumed: false,
|
|
1041
|
-
refusal: "unresumable",
|
|
1042
|
-
answer: {
|
|
1043
|
-
kind: "error",
|
|
1044
|
-
text: UNRESUMABLE_TEXT
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
995
|
ctx.logger.warn(`pause: could not open "${sessionId}" from its stored log: ${describeError(error)}`);
|
|
1048
996
|
return {
|
|
1049
997
|
sessionId,
|
|
@@ -1057,22 +1005,78 @@ function createActivator(ctx, gate, registry, options) {
|
|
|
1057
1005
|
}
|
|
1058
1006
|
ensureRoute(agent);
|
|
1059
1007
|
wake(agent, request);
|
|
1008
|
+
const refused = await releaseRecord(sessionId);
|
|
1009
|
+
if (refused !== void 0) return {
|
|
1010
|
+
sessionId,
|
|
1011
|
+
resumed: false,
|
|
1012
|
+
refusal: "unrecorded",
|
|
1013
|
+
answer: refused
|
|
1014
|
+
};
|
|
1015
|
+
if (rearm) rearmGoal(agent);
|
|
1016
|
+
return {
|
|
1017
|
+
sessionId,
|
|
1018
|
+
resumed: true,
|
|
1019
|
+
answer: {
|
|
1020
|
+
kind: "success",
|
|
1021
|
+
text: RESUMED_TEXT
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
};
|
|
1025
|
+
/**
|
|
1026
|
+
* Hand a subagent child the wake through the subagent runtime.
|
|
1027
|
+
*
|
|
1028
|
+
* The runtime owns the activation, so it is also the only thing that can
|
|
1029
|
+
* cold-resume the child under its live tree root. An empty message would be
|
|
1030
|
+
* dropped by a route whose conversion discards it, and the route is unknown
|
|
1031
|
+
* before the child is live, so the marker is the content that always wakes it.
|
|
1032
|
+
*/
|
|
1033
|
+
const activateChild = async (sessionId, request, rearm, signal) => {
|
|
1034
|
+
const subagents = ctx.get("subagents");
|
|
1035
|
+
if (subagents === void 0) {
|
|
1036
|
+
ctx.logger.warn(`pause: subagent runtime is not mounted; opening "${sessionId}" as a root Session`);
|
|
1037
|
+
return await activateRoot(sessionId, request, rearm, { kind: "root" }, signal);
|
|
1038
|
+
}
|
|
1039
|
+
const content = [{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: request.kind === "message" ? request.text : WAKE_MARKER
|
|
1042
|
+
}];
|
|
1060
1043
|
try {
|
|
1061
|
-
await
|
|
1044
|
+
await subagents.prompt({
|
|
1045
|
+
requestId: randomUUID(),
|
|
1046
|
+
childSessionId: sessionId,
|
|
1047
|
+
delivery: "queue",
|
|
1048
|
+
content
|
|
1049
|
+
}, signal ?? new AbortController().signal);
|
|
1062
1050
|
} catch (error) {
|
|
1063
|
-
|
|
1051
|
+
if (remoteErrorOf(error)?.code === "subagent/not-resumable") return {
|
|
1052
|
+
sessionId,
|
|
1053
|
+
resumed: false,
|
|
1054
|
+
refusal: "unresumable",
|
|
1055
|
+
answer: {
|
|
1056
|
+
kind: "error",
|
|
1057
|
+
text: UNRESUMABLE_TEXT
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
ctx.logger.warn(`pause: could not continue subagent "${sessionId}": ${describeError(error)}`);
|
|
1064
1061
|
return {
|
|
1065
1062
|
sessionId,
|
|
1066
1063
|
resumed: false,
|
|
1067
|
-
refusal: "
|
|
1064
|
+
refusal: "activation-failed",
|
|
1068
1065
|
answer: {
|
|
1069
1066
|
kind: "error",
|
|
1070
|
-
text:
|
|
1067
|
+
text: ACTIVATION_FAILED_TEXT
|
|
1071
1068
|
}
|
|
1072
1069
|
};
|
|
1073
1070
|
}
|
|
1074
|
-
|
|
1075
|
-
if (
|
|
1071
|
+
const refused = await releaseRecord(sessionId);
|
|
1072
|
+
if (refused !== void 0) return {
|
|
1073
|
+
sessionId,
|
|
1074
|
+
resumed: false,
|
|
1075
|
+
refusal: "unrecorded",
|
|
1076
|
+
answer: refused
|
|
1077
|
+
};
|
|
1078
|
+
const agent = ctx.agents.get(sessionId);
|
|
1079
|
+
if (rearm && agent !== void 0) rearmGoal(agent);
|
|
1076
1080
|
return {
|
|
1077
1081
|
sessionId,
|
|
1078
1082
|
resumed: true,
|
|
@@ -1082,6 +1086,24 @@ function createActivator(ctx, gate, registry, options) {
|
|
|
1082
1086
|
}
|
|
1083
1087
|
};
|
|
1084
1088
|
};
|
|
1089
|
+
const activate = async (sessionId, request, rearm, signal) => {
|
|
1090
|
+
let composition;
|
|
1091
|
+
try {
|
|
1092
|
+
composition = await readParkedComposition(ctx, sessionId, signal);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
ctx.logger.warn(`pause: could not read the composition of "${sessionId}": ${describeError(error)}`);
|
|
1095
|
+
return {
|
|
1096
|
+
sessionId,
|
|
1097
|
+
resumed: false,
|
|
1098
|
+
refusal: "activation-failed",
|
|
1099
|
+
answer: {
|
|
1100
|
+
kind: "error",
|
|
1101
|
+
text: ACTIVATION_FAILED_TEXT
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return composition.kind === "child" ? await activateChild(sessionId, request, rearm, signal) : await activateRoot(sessionId, request, rearm, composition, signal);
|
|
1106
|
+
};
|
|
1085
1107
|
return {
|
|
1086
1108
|
activate,
|
|
1087
1109
|
ensureRoute,
|
|
@@ -1156,11 +1178,10 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1156
1178
|
* @param finalize - whether the decision continues the session (`'release'`)
|
|
1157
1179
|
* or only answers it (`'review'`); a review activates nothing and writes
|
|
1158
1180
|
* nothing.
|
|
1159
|
-
* @param parentId - the direct parent a batch release already brought back, when there is one.
|
|
1160
1181
|
* @param signal - cancellation for the cold load and setup.
|
|
1161
1182
|
* @returns what happened, and the copy for whoever asked.
|
|
1162
1183
|
*/
|
|
1163
|
-
const decideUnclean = async (sessionId, request, finalize,
|
|
1184
|
+
const decideUnclean = async (sessionId, request, finalize, signal) => {
|
|
1164
1185
|
const record = registry.get(sessionId);
|
|
1165
1186
|
if (record === void 0) return {
|
|
1166
1187
|
sessionId,
|
|
@@ -1181,7 +1202,7 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1181
1202
|
}
|
|
1182
1203
|
};
|
|
1183
1204
|
const live = ctx.agents.get(sessionId);
|
|
1184
|
-
if (live === void 0) return await activate(sessionId, request, record.rearmGoal,
|
|
1205
|
+
if (live === void 0) return await activate(sessionId, request, record.rearmGoal, signal);
|
|
1185
1206
|
if (!gate.isPaused(sessionId)) {
|
|
1186
1207
|
ensureRoute(live);
|
|
1187
1208
|
wake(live, request);
|
|
@@ -1238,11 +1259,10 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1238
1259
|
* back is the press's own undo, and it delivers no wake.
|
|
1239
1260
|
* @param sessionId - the recorded session to resume.
|
|
1240
1261
|
* @param request - which wake this resume delivers; see {@link WakeRequest}.
|
|
1241
|
-
* @param parentId - the direct parent a batch release already brought back, when there is one.
|
|
1242
1262
|
* @param signal - cancellation for the cold load and setup.
|
|
1243
1263
|
* @returns what happened, and the copy for whoever asked.
|
|
1244
1264
|
*/
|
|
1245
|
-
const resumeSession = async (sessionId, request,
|
|
1265
|
+
const resumeSession = async (sessionId, request, signal) => {
|
|
1246
1266
|
const record = registry.get(sessionId);
|
|
1247
1267
|
const live = ctx.agents.get(sessionId);
|
|
1248
1268
|
if (live !== void 0) {
|
|
@@ -1337,18 +1357,8 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1337
1357
|
text: UNCLEAN_TEXT
|
|
1338
1358
|
}
|
|
1339
1359
|
};
|
|
1340
|
-
return await activate(sessionId, request, record.rearmGoal,
|
|
1360
|
+
return await activate(sessionId, request, record.rearmGoal, signal);
|
|
1341
1361
|
};
|
|
1342
|
-
/**
|
|
1343
|
-
* Order the recorded sessions so a session is released after the parent it
|
|
1344
|
-
* hangs from: waking a child needs its direct parent to be live first. The
|
|
1345
|
-
* relation is read from the parent catalogs themselves (which children this
|
|
1346
|
-
* session actually has), never guessed from ids; a branch whose catalog
|
|
1347
|
-
* cannot be read simply has no known parent and is released with the roots.
|
|
1348
|
-
* @param sessionIds - every recorded session, in registry order.
|
|
1349
|
-
* @param signal - cancellation for each catalog read.
|
|
1350
|
-
* @returns the parent of each child, and one list per depth level, shallowest first.
|
|
1351
|
-
*/
|
|
1352
1362
|
return {
|
|
1353
1363
|
resumeSession,
|
|
1354
1364
|
decideUnclean,
|
|
@@ -1382,27 +1392,22 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1382
1392
|
answer
|
|
1383
1393
|
});
|
|
1384
1394
|
}
|
|
1385
|
-
else {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
answer: {
|
|
1397
|
-
kind: "error",
|
|
1398
|
-
text: UNCLEAN_TEXT
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
continue;
|
|
1395
|
+
else for (const member of registered) {
|
|
1396
|
+
signal?.throwIfAborted();
|
|
1397
|
+
const record = registry.get(member);
|
|
1398
|
+
if (record !== void 0 && record.phase !== "safe") {
|
|
1399
|
+
released.push({
|
|
1400
|
+
sessionId: member,
|
|
1401
|
+
resumed: false,
|
|
1402
|
+
refusal: "not-clean",
|
|
1403
|
+
answer: {
|
|
1404
|
+
kind: "error",
|
|
1405
|
+
text: UNCLEAN_TEXT
|
|
1402
1406
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1407
|
+
});
|
|
1408
|
+
continue;
|
|
1405
1409
|
}
|
|
1410
|
+
released.push(await resumeSession(member, { kind: "silent" }, signal));
|
|
1406
1411
|
}
|
|
1407
1412
|
const root = released.find((attempt) => attempt.sessionId === sessionId);
|
|
1408
1413
|
const refusals = released.filter((attempt) => !attempt.resumed && attempt.refusal !== void 0).map((attempt) => ({
|
|
@@ -1427,7 +1432,7 @@ function installResumePath(ctx, gate, registry, options, deps) {
|
|
|
1427
1432
|
const attempts = [];
|
|
1428
1433
|
for (const level of release.levels) {
|
|
1429
1434
|
signal?.throwIfAborted();
|
|
1430
|
-
attempts.push(...await Promise.all(level.map((sessionId) => resumeSession(sessionId, { kind: "silent" },
|
|
1435
|
+
attempts.push(...await Promise.all(level.map((sessionId) => resumeSession(sessionId, { kind: "silent" }, signal))));
|
|
1431
1436
|
}
|
|
1432
1437
|
return attempts;
|
|
1433
1438
|
},
|
|
@@ -1617,12 +1622,12 @@ function mountTriageList(ctx, registry, resumePath, windowEvents = TRIAGE_WINDOW
|
|
|
1617
1622
|
text: TRIAGE_LEFT_TEXT
|
|
1618
1623
|
};
|
|
1619
1624
|
if (action === "goal") return await resumePath.setRearmGoal(sessionId, !record.rearmGoal);
|
|
1620
|
-
if (action === "review") return (await resumePath.decideUnclean(sessionId, { kind: "none" }, "review",
|
|
1625
|
+
if (action === "review") return (await resumePath.decideUnclean(sessionId, { kind: "none" }, "review", signal)).answer;
|
|
1621
1626
|
const wake = action === "continue" ? { kind: "silent" } : {
|
|
1622
1627
|
kind: "message",
|
|
1623
1628
|
text: UNKNOWN_OUTCOME_NOTE
|
|
1624
1629
|
};
|
|
1625
|
-
return (await resumePath.decideUnclean(sessionId, wake, "release",
|
|
1630
|
+
return (await resumePath.decideUnclean(sessionId, wake, "release", signal)).answer;
|
|
1626
1631
|
};
|
|
1627
1632
|
return {
|
|
1628
1633
|
read: async (caller, signal) => {
|