@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.1 → 0.1.7-alpha.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/README.i18n.yaml +2 -2
- package/README.md +42 -7
- package/README.zh.md +42 -7
- package/lib/client.js +1037 -992
- package/lib/index.js +314 -183
- package/lib/typert.host.js +539 -331
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +374 -264
- package/lib/types/agent.d.ts +1 -1
- package/lib/types/agent.js +8 -1
- package/lib/types/archived-session-gate.d.ts +30 -0
- package/lib/types/archived-session-gate.js +52 -0
- package/lib/types/client/contract/session.d.ts +2 -0
- package/lib/types/client/contract/sessions.d.ts +65 -35
- package/lib/types/client/contract/snapshot.d.ts +0 -14
- package/lib/types/client/index.d.ts +12 -3
- package/lib/types/client/index.js +16 -5
- package/lib/types/client/scope.d.ts +13 -3
- package/lib/types/client/scope.js +15 -22
- package/lib/types/client/sessions/lineage.d.ts +5 -5
- package/lib/types/client/sessions/lineage.js +3 -4
- package/lib/types/client/sessions/manager.d.ts +91 -105
- package/lib/types/client/sessions/manager.js +307 -434
- package/lib/types/client/sessions/projection-store.d.ts +34 -21
- package/lib/types/client/sessions/projection-store.js +48 -24
- package/lib/types/client/sessions/remotes.d.ts +1 -2
- package/lib/types/client/sessions/service.d.ts +53 -150
- package/lib/types/client/sessions/service.js +267 -264
- package/lib/types/client/sessions/session.d.ts +24 -35
- package/lib/types/client/sessions/session.js +131 -73
- package/lib/types/commands.d.ts +6 -4
- package/lib/types/commands.js +82 -40
- package/lib/types/control.d.ts +2 -4
- package/lib/types/control.js +2 -84
- package/lib/types/history.js +1 -1
- package/lib/types/index.d.ts +34 -7
- package/lib/types/index.js +118 -14
- package/lib/types/list.js +24 -15
- package/lib/types/skill-catalog.js +60 -50
- package/lib/types/types.d.ts +40 -40
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
- package/lib/types/client/sessions/queue-mirror.d.ts +0 -26
- package/lib/types/client/sessions/queue-mirror.js +0 -61
package/lib/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { hostname } from "node:os";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2
3
|
import z from "@deepseek-ai/schemastery";
|
|
3
4
|
import { AssistantStreamAccumulator, ReasoningEffortId, assistantStreamChunks, createUserMessage, errorChain, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { canOpenNativePath, nativeFileManager,
|
|
5
|
+
import { canOpenNativePath, nativeFileApplications, nativeFileManager, openNativeAssociatedPath, openNativeFileApplication, revealNativePath } from "@deepseek-ai/dsh-native-command";
|
|
6
|
+
import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
5
7
|
import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
8
|
import { mkdir } from "node:fs/promises";
|
|
7
9
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
8
|
-
import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
9
10
|
import { randomUUID } from "node:crypto";
|
|
10
11
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
11
12
|
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
13
|
+
import { buildForkSeed } from "@deepseek-ai/dsh-session/fork";
|
|
12
14
|
import { SessionLogOffset, SessionSeq, isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
|
|
13
15
|
import { SessionTitleInvalidError } from "@deepseek-ai/dsh-session-title";
|
|
14
16
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
@@ -16,7 +18,6 @@ import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
|
16
18
|
import { Deque } from "@deepseek-ai/dsh-deque";
|
|
17
19
|
import { z as z$1 } from "zod";
|
|
18
20
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
19
|
-
import { isAbsolute } from "node:path";
|
|
20
21
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
21
22
|
import mime from "mime-types";
|
|
22
23
|
//#region lib/types/agent.js
|
|
@@ -227,7 +228,8 @@ var ApiSessionAgentController = class {
|
|
|
227
228
|
this.resumes.set(sessionId, resume);
|
|
228
229
|
}
|
|
229
230
|
try {
|
|
230
|
-
|
|
231
|
+
const agent = await resume;
|
|
232
|
+
return this.liveAgent(sessionId) ?? { agent };
|
|
231
233
|
} catch (error) {
|
|
232
234
|
if (error instanceof ApiSessionNotFound) return { error: new RemoteError("session/not-found", error.message, { sessionId }) };
|
|
233
235
|
if (error instanceof ApiSessionSubagentOwnership) return { error: apiSessionSubagentOwnershipError(error.sessionId) };
|
|
@@ -235,6 +237,7 @@ var ApiSessionAgentController = class {
|
|
|
235
237
|
if (raced !== void 0) return raced;
|
|
236
238
|
const racedSession = this.ctx.sessions.get(sessionId);
|
|
237
239
|
if (racedSession !== void 0 && hasApiSessionSubagentOwner(this.ctx, racedSession, void 0)) return { error: apiSessionSubagentOwnershipError(sessionId) };
|
|
240
|
+
if (error instanceof Error && error.name === "SessionAlreadyOwnedError") return { error: new RemoteError("session/writer-held", error.message, { sessionId }) };
|
|
238
241
|
return { error: new RemoteError("gateway/internal", `resume failed for session "${sessionId}": ${String(error)}`, {}) };
|
|
239
242
|
}
|
|
240
243
|
}
|
|
@@ -548,6 +551,20 @@ var __disposeResources$3 = (function(SuppressedError) {
|
|
|
548
551
|
function hasPromptContent(content) {
|
|
549
552
|
return content.some((part) => part.type !== "text" || part.text.trim().length > 0);
|
|
550
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Resolve the omitted-`atSeq` default to the latest completed-turn prefix,
|
|
556
|
+
* including standalone events before the next turn begins.
|
|
557
|
+
*/
|
|
558
|
+
function latestCompletedPrefixBoundary(events) {
|
|
559
|
+
const lastTurnEnd = events.findLast((event) => event.type === "turn/end");
|
|
560
|
+
if (lastTurnEnd === void 0) return void 0;
|
|
561
|
+
let boundary = lastTurnEnd.seq;
|
|
562
|
+
for (const next of events.slice(boundary + 1)) {
|
|
563
|
+
if (next.type === "turn/start" || next.type === "user/message" && next.surfaceOp === "append" || next.type === "agent/inbox/spliced") break;
|
|
564
|
+
boundary = next.seq;
|
|
565
|
+
}
|
|
566
|
+
return boundary;
|
|
567
|
+
}
|
|
551
568
|
/** Implements Session business commands delegated by the Session Controller Remote service. */
|
|
552
569
|
var SessionCommandController = class {
|
|
553
570
|
ctx;
|
|
@@ -653,8 +670,10 @@ var SessionCommandController = class {
|
|
|
653
670
|
}
|
|
654
671
|
}
|
|
655
672
|
/**
|
|
656
|
-
* Create a new ordinary Session from
|
|
657
|
-
*
|
|
673
|
+
* Create a new ordinary Session from an exact event prefix. An explicit
|
|
674
|
+
* `atSeq` is the inclusive cut; an omitted value selects the latest
|
|
675
|
+
* completed-turn prefix. An open cut receives synthetic fork closers.
|
|
676
|
+
* @param request - source Session and optional exact event boundary.
|
|
658
677
|
* @returns the new Session identity.
|
|
659
678
|
*/
|
|
660
679
|
async fork(request) {
|
|
@@ -678,10 +697,9 @@ var SessionCommandController = class {
|
|
|
678
697
|
throw new RemoteError("gateway/internal", `fork source unavailable for session "${request.sessionId}": ${String(error)}`, {});
|
|
679
698
|
}
|
|
680
699
|
const source = __addDisposableResource$3(env_1, observed, false);
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const cut = SessionLogOffset(boundary.seq + 1);
|
|
700
|
+
const boundary = atSeq ?? latestCompletedPrefixBoundary(source.events);
|
|
701
|
+
if (boundary === void 0 || source.events[boundary]?.seq !== boundary) throw new RemoteError("session/fork-unavailable", request.atSeq === void 0 ? `session "${request.sessionId}" has no completed turn to fork from` : `event ${String(request.atSeq)} does not exist in session "${request.sessionId}" (last seq: ${String(source.events.at(-1)?.seq ?? "none")})`, { sessionId: request.sessionId });
|
|
702
|
+
const seed = buildForkSeed(source.events, boundary);
|
|
685
703
|
let workspace;
|
|
686
704
|
try {
|
|
687
705
|
workspace = await this.forkWorkspace(source.header);
|
|
@@ -694,8 +712,8 @@ var SessionCommandController = class {
|
|
|
694
712
|
const { provider, model } = this.ctx.agentDefaultModel.currentSelection();
|
|
695
713
|
await this.ctx.agents.create({
|
|
696
714
|
sessionId: childId,
|
|
697
|
-
seed
|
|
698
|
-
inheritedEventCount:
|
|
715
|
+
seed,
|
|
716
|
+
inheritedEventCount: SessionLogOffset(boundary + 1),
|
|
699
717
|
meta: {
|
|
700
718
|
...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
|
|
701
719
|
parentSession: source.header.id,
|
|
@@ -814,17 +832,24 @@ var SessionCommandController = class {
|
|
|
814
832
|
}
|
|
815
833
|
}
|
|
816
834
|
/**
|
|
817
|
-
* Mutate one
|
|
835
|
+
* Mutate one pending Inbox occurrence, restoring an ordinary cold Agent when needed.
|
|
818
836
|
* @param request - Session, queue item, and requested mutation.
|
|
819
837
|
* @returns acknowledgement that the queue mutation was applied.
|
|
820
838
|
*/
|
|
821
|
-
updateQueue(request) {
|
|
839
|
+
async updateQueue(request) {
|
|
822
840
|
if (request.action.kind === "edit") {
|
|
823
841
|
if (request.action.content.some((block) => block.type !== "text")) throw new RemoteError("session/attachment-invalid", "queue edits accept text content only", { reason: "QUEUE_EDIT_NON_TEXT" });
|
|
824
842
|
if (!hasPromptContent(request.action.content)) throw new RemoteError("gateway/bad-request", "queue edit content must include non-whitespace text", {});
|
|
825
843
|
}
|
|
826
|
-
|
|
827
|
-
if (agent === void 0)
|
|
844
|
+
let agent = this.ctx.agents.get(request.sessionId);
|
|
845
|
+
if (agent === void 0) {
|
|
846
|
+
const found = await this.agents.resolveAgent(request.sessionId);
|
|
847
|
+
if ("error" in found) {
|
|
848
|
+
if (found.error.code !== "session/not-found") throw found.error;
|
|
849
|
+
throw new RemoteError("session/queue-item-not-found", "queued item is no longer pending", { itemId: request.itemId });
|
|
850
|
+
}
|
|
851
|
+
agent = found.agent;
|
|
852
|
+
}
|
|
828
853
|
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
829
854
|
const identity = this.ctx.sessionProjections.snapshot(agent.session, ["subagent"]).values.subagent;
|
|
830
855
|
if (identity?.mode !== "continuable" || !agent.session.isOwnSeq(identity.seq)) throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
@@ -882,6 +907,7 @@ var SessionCommandController = class {
|
|
|
882
907
|
}
|
|
883
908
|
rejectCreation(sessionId, error) {
|
|
884
909
|
if (remoteErrorOf(error) !== void 0) throw error;
|
|
910
|
+
if (error instanceof Error && error.name === "SessionAlreadyOwnedError") throw new RemoteError("session/writer-held", error.message, { sessionId });
|
|
885
911
|
if (error instanceof ApiSessionPresetConflict) throw new RemoteError("agent-preset/conflict", error.message, {
|
|
886
912
|
sessionId: error.sessionId,
|
|
887
913
|
requestedPreset: error.requestedPreset,
|
|
@@ -957,23 +983,39 @@ function imageBlockIn(content, match) {
|
|
|
957
983
|
const ref = block.attachment;
|
|
958
984
|
if (match(ref)) return ref;
|
|
959
985
|
}
|
|
960
|
-
if (block.type === "tool-result") {
|
|
961
|
-
const nested = imageBlockIn(block.content, match);
|
|
962
|
-
if (nested !== void 0) return nested;
|
|
963
|
-
}
|
|
964
986
|
}
|
|
965
987
|
}
|
|
988
|
+
/** Read only first-party declared content fields; unknown event payloads stay opaque. */
|
|
966
989
|
function imageInEvent(event, match) {
|
|
967
990
|
const data = event.data;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
991
|
+
switch (event.type) {
|
|
992
|
+
case "user/message":
|
|
993
|
+
case "tool/ptc-dispatch": return imageBlockIn(data.content, match);
|
|
994
|
+
case "system/message":
|
|
995
|
+
case "developer/message":
|
|
996
|
+
case "tool/result":
|
|
997
|
+
case "team/message/queued": return imageBlockIn(data.message?.content, match);
|
|
998
|
+
case "agent/inbox/spliced": {
|
|
999
|
+
const messages = data.inserted;
|
|
1000
|
+
if (!Array.isArray(messages)) return void 0;
|
|
1001
|
+
for (const message of messages) {
|
|
1002
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) continue;
|
|
1003
|
+
const found = imageBlockIn(message.content, match);
|
|
1004
|
+
if (found !== void 0) return found;
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
case "compaction/summary": return imageBlockIn(data.summary, match) ?? imageBlockIn(data.rawOutput, match);
|
|
1009
|
+
case "assistant/message": {
|
|
1010
|
+
const found = imageBlockIn(data.message?.content, match);
|
|
1011
|
+
if (found !== void 0) return found;
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
case "assistant/attempt": break;
|
|
1015
|
+
default: return;
|
|
975
1016
|
}
|
|
976
|
-
|
|
1017
|
+
const assistant = event;
|
|
1018
|
+
for (const chunk of assistantStreamChunks(assistant.data.stream, "block-end")) {
|
|
977
1019
|
const found = imageBlockIn([chunk.block], match);
|
|
978
1020
|
if (found !== void 0) return found;
|
|
979
1021
|
}
|
|
@@ -989,12 +1031,12 @@ function routeServed(ctx, provider) {
|
|
|
989
1031
|
}
|
|
990
1032
|
//#endregion
|
|
991
1033
|
//#region lib/types/control.js
|
|
992
|
-
/** Live Session
|
|
1034
|
+
/** Live Session projection state with reconnect baselines. */
|
|
993
1035
|
/** Owns the Host-wide Session control stream. */
|
|
994
1036
|
var SessionControlController = class {
|
|
995
1037
|
ctx;
|
|
996
1038
|
streams = /* @__PURE__ */ new Set();
|
|
997
|
-
/** @param ctx - Host context carrying live Agent
|
|
1039
|
+
/** @param ctx - Host context carrying live Agent and projection services. */
|
|
998
1040
|
constructor(ctx) {
|
|
999
1041
|
this.ctx = ctx;
|
|
1000
1042
|
ctx.sessionProjections.onChanged((session, key, value, seq) => {
|
|
@@ -1005,26 +1047,6 @@ var SessionControlController = class {
|
|
|
1005
1047
|
value,
|
|
1006
1048
|
seq
|
|
1007
1049
|
});
|
|
1008
|
-
if (key !== "inbox") return;
|
|
1009
|
-
if (this.ctx.agents.get(session.id)?.session !== session) return;
|
|
1010
|
-
this.broadcast({
|
|
1011
|
-
type: "queue",
|
|
1012
|
-
sessionId: session.id,
|
|
1013
|
-
items: queueItemsFromInbox(value)
|
|
1014
|
-
});
|
|
1015
|
-
});
|
|
1016
|
-
ctx.inject(["jobs"], (jobsCtx) => {
|
|
1017
|
-
jobsCtx.jobs.onJobsChanged((owner) => {
|
|
1018
|
-
this.onJobsChanged(owner);
|
|
1019
|
-
});
|
|
1020
|
-
});
|
|
1021
|
-
ctx.on("session/created", (session) => {
|
|
1022
|
-
const jobs = this.jobsFor(this.ctx.agents.get(session.id));
|
|
1023
|
-
if (jobs.length > 0) this.broadcast({
|
|
1024
|
-
type: "jobs",
|
|
1025
|
-
sessionId: session.id,
|
|
1026
|
-
jobs
|
|
1027
|
-
});
|
|
1028
1050
|
});
|
|
1029
1051
|
ctx.effect(() => () => {
|
|
1030
1052
|
for (const stream of this.streams) stream.end();
|
|
@@ -1053,18 +1075,7 @@ var SessionControlController = class {
|
|
|
1053
1075
|
}
|
|
1054
1076
|
baseline() {
|
|
1055
1077
|
const sessions = this.ctx.sessions.list();
|
|
1056
|
-
|
|
1057
|
-
const jobs = Object.create(null);
|
|
1058
|
-
for (const session of sessions) {
|
|
1059
|
-
const agent = this.ctx.agents.get(session.id);
|
|
1060
|
-
queues[session.id] = agent?.session === session ? queueItems(agent) : [];
|
|
1061
|
-
jobs[session.id] = this.jobsFor(agent);
|
|
1062
|
-
}
|
|
1063
|
-
return {
|
|
1064
|
-
queues,
|
|
1065
|
-
jobs,
|
|
1066
|
-
projections: this.projectionBaseline(sessions)
|
|
1067
|
-
};
|
|
1078
|
+
return { projections: this.projectionBaseline(sessions) };
|
|
1068
1079
|
}
|
|
1069
1080
|
projectionBaseline(sessions) {
|
|
1070
1081
|
const blocks = Object.create(null);
|
|
@@ -1077,25 +1088,6 @@ var SessionControlController = class {
|
|
|
1077
1088
|
}
|
|
1078
1089
|
return blocks;
|
|
1079
1090
|
}
|
|
1080
|
-
onJobsChanged(owner) {
|
|
1081
|
-
if (owner !== void 0) {
|
|
1082
|
-
this.broadcast({
|
|
1083
|
-
type: "jobs",
|
|
1084
|
-
sessionId: owner.id,
|
|
1085
|
-
jobs: this.jobsFor(owner)
|
|
1086
|
-
});
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
for (const session of this.ctx.sessions.list()) this.broadcast({
|
|
1090
|
-
type: "jobs",
|
|
1091
|
-
sessionId: session.id,
|
|
1092
|
-
jobs: this.jobsFor(this.ctx.agents.get(session.id))
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
jobsFor(agent) {
|
|
1096
|
-
const jobs = this.ctx.get("jobs");
|
|
1097
|
-
return jobs === void 0 ? [] : jobs.list(agent).map(jobView);
|
|
1098
|
-
}
|
|
1099
1091
|
broadcast(frame) {
|
|
1100
1092
|
for (const stream of this.streams) stream.push(frame);
|
|
1101
1093
|
}
|
|
@@ -1141,47 +1133,6 @@ var ControlQueue = class {
|
|
|
1141
1133
|
}
|
|
1142
1134
|
}
|
|
1143
1135
|
};
|
|
1144
|
-
function queueItems(agent) {
|
|
1145
|
-
return queueItemsFromInbox({
|
|
1146
|
-
"next-turn": agent.inbox.nextTurn,
|
|
1147
|
-
"next-step": agent.inbox.nextStep
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
function queueItemsFromInbox(inbox) {
|
|
1151
|
-
return [...inbox["next-turn"].map((message) => ({
|
|
1152
|
-
id: message.id,
|
|
1153
|
-
placement: "queued",
|
|
1154
|
-
...promptRpcId(message),
|
|
1155
|
-
message: {
|
|
1156
|
-
id: message.id,
|
|
1157
|
-
content: message.content
|
|
1158
|
-
}
|
|
1159
|
-
})), ...inbox["next-step"].map((message) => ({
|
|
1160
|
-
id: message.id,
|
|
1161
|
-
placement: message.source.kind === "user" ? "steering" : "context",
|
|
1162
|
-
...promptRpcId(message),
|
|
1163
|
-
message: {
|
|
1164
|
-
id: message.id,
|
|
1165
|
-
content: message.content
|
|
1166
|
-
}
|
|
1167
|
-
}))];
|
|
1168
|
-
}
|
|
1169
|
-
/** Prompt-RPC identity carried by a browser-submitted message's user source. */
|
|
1170
|
-
function promptRpcId(message) {
|
|
1171
|
-
const source = message.source;
|
|
1172
|
-
return source.kind === "user" && "rpcId" in source ? { rpcId: source.rpcId } : {};
|
|
1173
|
-
}
|
|
1174
|
-
function jobView(job) {
|
|
1175
|
-
return {
|
|
1176
|
-
id: job.id,
|
|
1177
|
-
kind: job.kind,
|
|
1178
|
-
label: job.label,
|
|
1179
|
-
status: job.status,
|
|
1180
|
-
...job.detail === void 0 ? {} : { detail: job.detail },
|
|
1181
|
-
startedAt: job.startedAt,
|
|
1182
|
-
...job.finishedAt === void 0 ? {} : { finishedAt: job.finishedAt }
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
1136
|
//#endregion
|
|
1186
1137
|
//#region lib/types/assistant-stream.js
|
|
1187
1138
|
/** Process-local assistant state retained for reconnecting Web followers. */
|
|
@@ -1589,7 +1540,7 @@ function validateAddress(address, header, inheritedEventCount, projections) {
|
|
|
1589
1540
|
childSessionId: address.childSessionId,
|
|
1590
1541
|
reason: "unsupported"
|
|
1591
1542
|
});
|
|
1592
|
-
if (identity.mode !== address.mode) throw new RemoteError("subagent/unauthorized", "subagent mode does not match the supplied address", { childSessionId: address.childSessionId });
|
|
1543
|
+
if (address.mode !== "unknown" && identity.mode !== address.mode) throw new RemoteError("subagent/unauthorized", "subagent mode does not match the supplied address", { childSessionId: address.childSessionId });
|
|
1593
1544
|
}
|
|
1594
1545
|
function rejectNotFound(address) {
|
|
1595
1546
|
if (address.kind === "session") throw new RemoteError("session/not-found", `session "${address.sessionId}" not found`, { sessionId: address.sessionId });
|
|
@@ -1814,6 +1765,7 @@ var ApiSessionList = class {
|
|
|
1814
1765
|
return {
|
|
1815
1766
|
sessionId: session.id,
|
|
1816
1767
|
updatedAt: updatedAt(session.header, metadata),
|
|
1768
|
+
agentAvailable: this.ctx.agents.get(session.id)?.session === session,
|
|
1817
1769
|
running: this.ctx.agents.get(session.id)?.status === "running",
|
|
1818
1770
|
blank: metadata?.blank ?? session.seq === 0,
|
|
1819
1771
|
...listFields(session.header),
|
|
@@ -1850,6 +1802,7 @@ var ApiSessionList = class {
|
|
|
1850
1802
|
return {
|
|
1851
1803
|
sessionId: header.id,
|
|
1852
1804
|
updatedAt: updatedAt(header, metadata),
|
|
1805
|
+
agentAvailable: false,
|
|
1853
1806
|
running: false,
|
|
1854
1807
|
blank: metadata?.blank ?? false,
|
|
1855
1808
|
...listFields(header),
|
|
@@ -1946,18 +1899,29 @@ var ApiSessionList = class {
|
|
|
1946
1899
|
}
|
|
1947
1900
|
projectionsFor(header, session) {
|
|
1948
1901
|
try {
|
|
1902
|
+
if (session !== void 0) return hintsOf("sequenced", this.ctx.sessionProjections.cachedSnapshot(session));
|
|
1949
1903
|
const cache = this.ctx.get("sessionProjectionCache");
|
|
1950
|
-
|
|
1951
|
-
return block !== void 0 && Object.keys(block.values).length > 0 ? {
|
|
1952
|
-
asOfSeq: block.asOfSeq,
|
|
1953
|
-
values: block.values
|
|
1954
|
-
} : void 0;
|
|
1904
|
+
return hintsOf("cached", cache?.cachedSnapshot(header) ?? cache?.cachedPredecessorTitle(header));
|
|
1955
1905
|
} catch (error) {
|
|
1956
1906
|
this.ctx.logger.warn(`api-session.list: projection column for "${header.id}" failed; serving the row without it: ${String(error)}`);
|
|
1957
1907
|
return;
|
|
1958
1908
|
}
|
|
1959
1909
|
}
|
|
1960
1910
|
};
|
|
1911
|
+
/**
|
|
1912
|
+
* Wrap one projection block as Session-list hints of the named sequence space.
|
|
1913
|
+
* @param kind - which sequence space the block's watermark belongs to.
|
|
1914
|
+
* @param block - the block, or `undefined` when no source served one.
|
|
1915
|
+
* @returns the hints, or `undefined` when the block is absent or carries no value.
|
|
1916
|
+
*/
|
|
1917
|
+
function hintsOf(kind, block) {
|
|
1918
|
+
if (block === void 0 || Object.keys(block.values).length === 0) return void 0;
|
|
1919
|
+
return {
|
|
1920
|
+
kind,
|
|
1921
|
+
asOfSeq: block.asOfSeq,
|
|
1922
|
+
values: block.values
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1961
1925
|
function normalizeSearchQuery(query) {
|
|
1962
1926
|
const normalized = query.trim();
|
|
1963
1927
|
if (normalized.length === 0) throw new RemoteError("gateway/bad-request", "session search query must not be empty", {});
|
|
@@ -2242,59 +2206,71 @@ let SessionSkillCatalog = (() => {
|
|
|
2242
2206
|
* @throws RemoteError when the Session cannot be inspected or no registry can serve it.
|
|
2243
2207
|
*/
|
|
2244
2208
|
async list(request, signal) {
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2247
|
-
|
|
2209
|
+
const env_1 = {
|
|
2210
|
+
stack: [],
|
|
2211
|
+
error: void 0,
|
|
2212
|
+
hasError: false
|
|
2213
|
+
};
|
|
2248
2214
|
try {
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
hasError: false
|
|
2253
|
-
};
|
|
2215
|
+
const { sessionId } = request;
|
|
2216
|
+
let cwd;
|
|
2217
|
+
let agentPreset;
|
|
2254
2218
|
try {
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2219
|
+
const env_2 = {
|
|
2220
|
+
stack: [],
|
|
2221
|
+
error: void 0,
|
|
2222
|
+
hasError: false
|
|
2223
|
+
};
|
|
2224
|
+
try {
|
|
2225
|
+
const observation = __addDisposableResource$1(env_2, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
2226
|
+
if (observation.projections === void 0) throw new Error("skill catalog requires a projected Session observation");
|
|
2227
|
+
cwd = observation.header.cwd;
|
|
2228
|
+
agentPreset = observation.projections.values.agentPreset ?? void 0;
|
|
2229
|
+
} catch (e_1) {
|
|
2230
|
+
env_2.error = e_1;
|
|
2231
|
+
env_2.hasError = true;
|
|
2232
|
+
} finally {
|
|
2233
|
+
__disposeResources$1(env_2);
|
|
2234
|
+
}
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new RemoteError("session/not-found", `session "${sessionId}" not found`, { sessionId });
|
|
2237
|
+
throw new RemoteError("gateway/internal", `session "${sessionId}" could not be inspected: ${String(error)}`, {});
|
|
2264
2238
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
}
|
|
2286
|
-
} catch (
|
|
2287
|
-
|
|
2239
|
+
if (cwd === void 0) throw new RemoteError("gateway/internal", `session "${sessionId}" has no project cwd`, {});
|
|
2240
|
+
const live = this.ctx.agents.get(sessionId);
|
|
2241
|
+
const presets = this.ctx.get("agentPresets");
|
|
2242
|
+
const skillRegistry = (live === void 0 ? void 0 : presets?.serviceFor(live, "skills")) ?? this.ctx.get("skills");
|
|
2243
|
+
if (skillRegistry === void 0) throw new RemoteError("gateway/internal", "skill registry is absent: neither this session's agent preset nor the host composition mounts @deepseek-ai/dsh-skill", {});
|
|
2244
|
+
const lease = __addDisposableResource$1(env_1, live === void 0 ? await this.scopeFor(agentPreset) : void 0, true);
|
|
2245
|
+
const scope = live ?? lease?.key;
|
|
2246
|
+
try {
|
|
2247
|
+
return { skills: (await skillRegistry.list({
|
|
2248
|
+
cwd,
|
|
2249
|
+
scope
|
|
2250
|
+
})).filter(isUserInvocable).map((skill) => ({
|
|
2251
|
+
name: skill.name,
|
|
2252
|
+
...skill.path === void 0 ? {} : { path: skill.path },
|
|
2253
|
+
description: skill.description,
|
|
2254
|
+
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
2255
|
+
modelInvocable: skill.invocation.modelInvocable
|
|
2256
|
+
})) };
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
throw new RemoteError("gateway/internal", `skill listing failed: ${String(error)}`, {});
|
|
2259
|
+
}
|
|
2260
|
+
} catch (e_2) {
|
|
2261
|
+
env_1.error = e_2;
|
|
2262
|
+
env_1.hasError = true;
|
|
2263
|
+
} finally {
|
|
2264
|
+
const result_1 = __disposeResources$1(env_1);
|
|
2265
|
+
if (result_1) await result_1;
|
|
2288
2266
|
}
|
|
2289
2267
|
}
|
|
2290
2268
|
/** Resolve a live or standing preset scope without creating an Agent. */
|
|
2291
|
-
async scopeFor(
|
|
2292
|
-
const live = this.ctx.agents.get(sessionId);
|
|
2293
|
-
if (live !== void 0) return live;
|
|
2269
|
+
async scopeFor(agentPreset) {
|
|
2294
2270
|
const presets = this.ctx.get("agentPresets");
|
|
2295
2271
|
if (presets === void 0) return void 0;
|
|
2296
2272
|
try {
|
|
2297
|
-
return await presets.
|
|
2273
|
+
return await presets.acquireScope(agentPreset);
|
|
2298
2274
|
} catch {
|
|
2299
2275
|
return;
|
|
2300
2276
|
}
|
|
@@ -2375,6 +2351,58 @@ const SessionMediaReferences = {
|
|
|
2375
2351
|
}
|
|
2376
2352
|
};
|
|
2377
2353
|
//#endregion
|
|
2354
|
+
//#region lib/types/archived-session-gate.js
|
|
2355
|
+
/**
|
|
2356
|
+
* The controller's admission gate for archived Sessions: an archived
|
|
2357
|
+
* Session, or a subagent descendant of one, must not run a model step until
|
|
2358
|
+
* it is restored. The work a Session still runs is reported and stopped by
|
|
2359
|
+
* its owners — the Agent registry (`turn`), the job registry seam (`job`),
|
|
2360
|
+
* the Subagent runtime (`subagent`), and the Schedule plugin (`schedule`) —
|
|
2361
|
+
* through the Workspace registry's archive-admission events.
|
|
2362
|
+
*/
|
|
2363
|
+
/**
|
|
2364
|
+
* The gate as a plugin for `ctx.plugin(...)`: it loads once the Agent
|
|
2365
|
+
* registry, Session store, and Workspace registry are available and unwinds
|
|
2366
|
+
* with the plugin's fiber. A late waking delivery to an archived Session — a
|
|
2367
|
+
* subagent settlement, a queued follow-up — proposes a step the gate rejects,
|
|
2368
|
+
* which the loop ends as `blocked` without a request; unarchiving lifts the
|
|
2369
|
+
* gate for the whole lineage.
|
|
2370
|
+
*/
|
|
2371
|
+
const ArchivedSessionGate = {
|
|
2372
|
+
name: "archived-session-gate",
|
|
2373
|
+
inject: [
|
|
2374
|
+
"agents",
|
|
2375
|
+
"sessions",
|
|
2376
|
+
"workspaceRegistry"
|
|
2377
|
+
],
|
|
2378
|
+
apply(ctx) {
|
|
2379
|
+
ctx.on("agent/pre-step", (payload, next) => underArchivedSession(ctx, payload.agent) ? Promise.resolve({ kind: "reject" }) : next());
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
/**
|
|
2383
|
+
* Whether the Agent's Session, or a Session above it in its subagent lineage,
|
|
2384
|
+
* is archived. Lineage follows the durable header fields through
|
|
2385
|
+
* subagent-origin Sessions only: a fork of an archived Session is an
|
|
2386
|
+
* independent conversation.
|
|
2387
|
+
* @param ctx - Host context.
|
|
2388
|
+
* @param agent - the Agent proposing a step.
|
|
2389
|
+
* @returns whether an archived Session owns the step.
|
|
2390
|
+
*/
|
|
2391
|
+
function underArchivedSession(ctx, agent) {
|
|
2392
|
+
const archived = ctx.workspaceRegistry.archivedSessionIds;
|
|
2393
|
+
let header = agent.session.header;
|
|
2394
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2395
|
+
while (!visited.has(header.id)) {
|
|
2396
|
+
if (archived.includes(header.id)) return true;
|
|
2397
|
+
visited.add(header.id);
|
|
2398
|
+
if (header.origin !== "subagent" || header.parentSession === void 0) return false;
|
|
2399
|
+
const parent = ctx.sessions.get(header.parentSession);
|
|
2400
|
+
if (parent === void 0) return archived.includes(header.parentSession);
|
|
2401
|
+
header = parent.header;
|
|
2402
|
+
}
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
//#endregion
|
|
2378
2406
|
//#region lib/types/index.js
|
|
2379
2407
|
/** Session Remote owner: cold reads, explicit Agent commands, and live control state. */
|
|
2380
2408
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
@@ -2484,6 +2512,7 @@ let SessionController = (() => {
|
|
|
2484
2512
|
let _modelCatalog_decorators;
|
|
2485
2513
|
let _canOpenWorkspacePath_decorators;
|
|
2486
2514
|
let _openWorkspacePath_decorators;
|
|
2515
|
+
let _workspacePathApplications_decorators;
|
|
2487
2516
|
let _rename_decorators;
|
|
2488
2517
|
let _fork_decorators;
|
|
2489
2518
|
let _prompt_decorators;
|
|
@@ -2492,6 +2521,7 @@ let SessionController = (() => {
|
|
|
2492
2521
|
let _cancel_decorators;
|
|
2493
2522
|
let _page_decorators;
|
|
2494
2523
|
let _follow_decorators;
|
|
2524
|
+
let _projections_decorators;
|
|
2495
2525
|
let _control_decorators;
|
|
2496
2526
|
return class SessionController extends _classSuper {
|
|
2497
2527
|
static {
|
|
@@ -2503,6 +2533,7 @@ let SessionController = (() => {
|
|
|
2503
2533
|
_modelCatalog_decorators = [Remote("modelCatalog")];
|
|
2504
2534
|
_canOpenWorkspacePath_decorators = [Remote];
|
|
2505
2535
|
_openWorkspacePath_decorators = [Remote("openWorkspacePath")];
|
|
2536
|
+
_workspacePathApplications_decorators = [Remote("workspacePathApplications")];
|
|
2506
2537
|
_rename_decorators = [Remote("rename")];
|
|
2507
2538
|
_fork_decorators = [Remote("fork")];
|
|
2508
2539
|
_prompt_decorators = [Remote("prompt")];
|
|
@@ -2511,6 +2542,7 @@ let SessionController = (() => {
|
|
|
2511
2542
|
_cancel_decorators = [Remote("cancel")];
|
|
2512
2543
|
_page_decorators = [Remote("page")];
|
|
2513
2544
|
_follow_decorators = [Remote({ mode: "stream" })];
|
|
2545
|
+
_projections_decorators = [Remote("projections")];
|
|
2514
2546
|
_control_decorators = [Remote({ mode: "stream" })];
|
|
2515
2547
|
__esDecorate(this, null, _list_decorators, {
|
|
2516
2548
|
kind: "method",
|
|
@@ -2589,6 +2621,17 @@ let SessionController = (() => {
|
|
|
2589
2621
|
},
|
|
2590
2622
|
metadata: _metadata
|
|
2591
2623
|
}, null, _instanceExtraInitializers);
|
|
2624
|
+
__esDecorate(this, null, _workspacePathApplications_decorators, {
|
|
2625
|
+
kind: "method",
|
|
2626
|
+
name: "workspacePathApplications",
|
|
2627
|
+
static: false,
|
|
2628
|
+
private: false,
|
|
2629
|
+
access: {
|
|
2630
|
+
has: (obj) => "workspacePathApplications" in obj,
|
|
2631
|
+
get: (obj) => obj.workspacePathApplications
|
|
2632
|
+
},
|
|
2633
|
+
metadata: _metadata
|
|
2634
|
+
}, null, _instanceExtraInitializers);
|
|
2592
2635
|
__esDecorate(this, null, _rename_decorators, {
|
|
2593
2636
|
kind: "method",
|
|
2594
2637
|
name: "rename",
|
|
@@ -2677,6 +2720,17 @@ let SessionController = (() => {
|
|
|
2677
2720
|
},
|
|
2678
2721
|
metadata: _metadata
|
|
2679
2722
|
}, null, _instanceExtraInitializers);
|
|
2723
|
+
__esDecorate(this, null, _projections_decorators, {
|
|
2724
|
+
kind: "method",
|
|
2725
|
+
name: "projections",
|
|
2726
|
+
static: false,
|
|
2727
|
+
private: false,
|
|
2728
|
+
access: {
|
|
2729
|
+
has: (obj) => "projections" in obj,
|
|
2730
|
+
get: (obj) => obj.projections
|
|
2731
|
+
},
|
|
2732
|
+
metadata: _metadata
|
|
2733
|
+
}, null, _instanceExtraInitializers);
|
|
2680
2734
|
__esDecorate(this, null, _control_decorators, {
|
|
2681
2735
|
kind: "method",
|
|
2682
2736
|
name: "control",
|
|
@@ -2700,6 +2754,7 @@ let SessionController = (() => {
|
|
|
2700
2754
|
"agents",
|
|
2701
2755
|
"attachments",
|
|
2702
2756
|
"fileUploads",
|
|
2757
|
+
"fs",
|
|
2703
2758
|
"llm",
|
|
2704
2759
|
"sessions",
|
|
2705
2760
|
"sessionProjections",
|
|
@@ -2714,6 +2769,8 @@ let SessionController = (() => {
|
|
|
2714
2769
|
history;
|
|
2715
2770
|
listState;
|
|
2716
2771
|
openPath;
|
|
2772
|
+
fileApplications;
|
|
2773
|
+
openFileApplication;
|
|
2717
2774
|
revealPath;
|
|
2718
2775
|
canOpenPath;
|
|
2719
2776
|
promotions = /* @__PURE__ */ new Set();
|
|
@@ -2740,18 +2797,26 @@ let SessionController = (() => {
|
|
|
2740
2797
|
this.promote(observation);
|
|
2741
2798
|
});
|
|
2742
2799
|
this.listState = new ApiSessionList(ctx);
|
|
2743
|
-
this.
|
|
2800
|
+
this.fileApplications = internals.fileApplications ?? nativeFileApplications;
|
|
2801
|
+
this.openFileApplication = internals.openFileApplication ?? openNativeFileApplication;
|
|
2802
|
+
this.openPath = internals.openPath ?? openNativeAssociatedPath;
|
|
2744
2803
|
this.revealPath = internals.revealPath ?? revealNativePath;
|
|
2745
2804
|
this.canOpenPath = internals.canOpenPath ?? (() => config.nativeOpen ?? (internals.openPath !== void 0 || canOpenNativePath()));
|
|
2746
2805
|
ctx.plugin(SessionFileReferences);
|
|
2747
2806
|
ctx.plugin(SessionMediaReferences);
|
|
2748
2807
|
ctx.plugin(SessionSkillCatalog);
|
|
2808
|
+
ctx.plugin(ArchivedSessionGate);
|
|
2749
2809
|
ctx.on("session/created", (session) => {
|
|
2750
2810
|
ctx.emit("api-session/added", this.listState.summaryFor(session));
|
|
2751
2811
|
});
|
|
2752
2812
|
ctx.on("session/disposed", (session) => {
|
|
2753
2813
|
ctx.emit("api-session/removed", session.id);
|
|
2754
2814
|
});
|
|
2815
|
+
const publishAgentAvailability = ({ agent }) => {
|
|
2816
|
+
if (ctx.sessions.get(agent.id) === agent.session) ctx.emit("api-session/added", this.listState.summaryFor(agent.session));
|
|
2817
|
+
};
|
|
2818
|
+
ctx.on("agent/created", publishAgentAvailability);
|
|
2819
|
+
ctx.on("agent/disposed", publishAgentAvailability);
|
|
2755
2820
|
ctx.on("agent/status", ({ agent, status }) => {
|
|
2756
2821
|
ctx.emit("api-session/status", agent.id, status === "running");
|
|
2757
2822
|
});
|
|
@@ -2877,24 +2942,53 @@ let SessionController = (() => {
|
|
|
2877
2942
|
};
|
|
2878
2943
|
}
|
|
2879
2944
|
/**
|
|
2880
|
-
*
|
|
2945
|
+
* Verify one path through the composed filesystem and open it on the Host desktop.
|
|
2881
2946
|
* @param request - path after best-effort Session workspace resolution.
|
|
2882
2947
|
* @param signal - caller lifetime; abort terminates the native command.
|
|
2883
2948
|
* @returns confirmation after the native opener accepts the path.
|
|
2884
|
-
* @throws RemoteError when the request is invalid, cancelled, or the opener fails.
|
|
2949
|
+
* @throws RemoteError when the request is invalid, has no verified Host mapping, is cancelled, or the opener fails.
|
|
2885
2950
|
*/
|
|
2886
2951
|
async openWorkspacePath(request, signal) {
|
|
2887
|
-
if (request.path.length === 0) throw new RemoteError("gateway/bad-request", "session.openWorkspacePath requires a non-empty path", {});
|
|
2888
|
-
signal.throwIfAborted();
|
|
2889
2952
|
try {
|
|
2890
|
-
|
|
2891
|
-
|
|
2953
|
+
const path = await this.verifyDesktopPath(request.path, signal);
|
|
2954
|
+
if (request.action === "reveal") await this.revealPath(path, signal);
|
|
2955
|
+
else if (request.application !== void 0) await this.openFileApplication(path, request.application, signal);
|
|
2956
|
+
else await this.openPath(path, signal);
|
|
2892
2957
|
return { opened: true };
|
|
2893
2958
|
} catch (error) {
|
|
2894
2959
|
if (signal.aborted) throw new RemoteError("gateway/cancelled", "path open was aborted", {});
|
|
2895
|
-
|
|
2960
|
+
if (error instanceof RemoteError) throw error;
|
|
2961
|
+
throw new RemoteError("gateway/internal", "path open failed", {}, { cause: error });
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Query current file handlers on the serving desktop without activating an Agent.
|
|
2966
|
+
* @param request - file path in Host filesystem syntax.
|
|
2967
|
+
* @param signal - caller lifetime, propagated to filesystem and desktop queries.
|
|
2968
|
+
* @returns OS application names, icons, and default selection; empty when desktop opening is unavailable.
|
|
2969
|
+
* @throws RemoteError when the path is invalid, the query is cancelled, or native discovery fails.
|
|
2970
|
+
*/
|
|
2971
|
+
async workspacePathApplications(request, signal) {
|
|
2972
|
+
if (!this.canOpenPath()) return [];
|
|
2973
|
+
try {
|
|
2974
|
+
const path = await this.verifyDesktopPath(request.path, signal);
|
|
2975
|
+
return await this.fileApplications(path, signal);
|
|
2976
|
+
} catch (error) {
|
|
2977
|
+
if (signal.aborted) throw new RemoteError("gateway/cancelled", "application query was aborted", {});
|
|
2978
|
+
if (error instanceof RemoteError) throw error;
|
|
2979
|
+
throw new RemoteError("gateway/internal", "file application query failed", {}, { cause: error });
|
|
2896
2980
|
}
|
|
2897
2981
|
}
|
|
2982
|
+
async verifyDesktopPath(path, signal) {
|
|
2983
|
+
if (path.length === 0) throw new RemoteError("gateway/bad-request", "A non-empty file path is required", {});
|
|
2984
|
+
signal.throwIfAborted();
|
|
2985
|
+
const hostPath = resolve(path);
|
|
2986
|
+
const { fs } = this.ctx;
|
|
2987
|
+
const mapped = fs.processPathFromHostPath(hostPath);
|
|
2988
|
+
if (mapped === void 0 || fs.processPath(await fs.resolve(mapped, { signal })) !== hostPath) throw new RemoteError("gateway/bad-request", "Path has no verified Host path", {});
|
|
2989
|
+
signal.throwIfAborted();
|
|
2990
|
+
return hostPath;
|
|
2991
|
+
}
|
|
2898
2992
|
/**
|
|
2899
2993
|
* Rename one Session after explicitly resuming it.
|
|
2900
2994
|
* @param request - Session identity and proposed title.
|
|
@@ -2904,8 +2998,10 @@ let SessionController = (() => {
|
|
|
2904
2998
|
return this.commands.rename(request);
|
|
2905
2999
|
}
|
|
2906
3000
|
/**
|
|
2907
|
-
* Fork one cold-readable
|
|
2908
|
-
*
|
|
3001
|
+
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
3002
|
+
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
3003
|
+
* synthetic fork closers.
|
|
3004
|
+
* @param request - source Session and optional exact inclusive event boundary.
|
|
2909
3005
|
* @returns the new Session identity.
|
|
2910
3006
|
*/
|
|
2911
3007
|
fork(request) {
|
|
@@ -2930,7 +3026,7 @@ let SessionController = (() => {
|
|
|
2930
3026
|
return this.commands.attachment(request);
|
|
2931
3027
|
}
|
|
2932
3028
|
/**
|
|
2933
|
-
* Mutate one still-pending queue occurrence
|
|
3029
|
+
* Mutate one still-pending queue occurrence, resuming a cold Agent first.
|
|
2934
3030
|
* @param request - Session, queue item, and requested mutation.
|
|
2935
3031
|
* @returns acknowledgement that the queue mutation was applied.
|
|
2936
3032
|
*/
|
|
@@ -2965,6 +3061,41 @@ let SessionController = (() => {
|
|
|
2965
3061
|
return this.history.follow(request, signal);
|
|
2966
3062
|
}
|
|
2967
3063
|
/**
|
|
3064
|
+
* Read all registered projections without activating an Agent.
|
|
3065
|
+
* @param request - Session whose current values are required.
|
|
3066
|
+
* @param signal - cancellation for the Session observation.
|
|
3067
|
+
* @returns complete baseline, or null when the Session does not exist.
|
|
3068
|
+
*/
|
|
3069
|
+
async projections(request, signal) {
|
|
3070
|
+
const { sessionId } = request;
|
|
3071
|
+
if (sessionId.length === 0) throw new RemoteError("gateway/bad-request", "sessionId must not be empty", {});
|
|
3072
|
+
try {
|
|
3073
|
+
const env_2 = {
|
|
3074
|
+
stack: [],
|
|
3075
|
+
error: void 0,
|
|
3076
|
+
hasError: false
|
|
3077
|
+
};
|
|
3078
|
+
try {
|
|
3079
|
+
const projections = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId, { signal }), false).projections;
|
|
3080
|
+
if (projections === void 0) throw new RemoteError("session/projections-unavailable", "Session projections are unavailable", {});
|
|
3081
|
+
return {
|
|
3082
|
+
asOfSeq: projections.asOfSeq,
|
|
3083
|
+
values: projections.values
|
|
3084
|
+
};
|
|
3085
|
+
} catch (e_2) {
|
|
3086
|
+
env_2.error = e_2;
|
|
3087
|
+
env_2.hasError = true;
|
|
3088
|
+
} finally {
|
|
3089
|
+
__disposeResources(env_2);
|
|
3090
|
+
}
|
|
3091
|
+
} catch (error) {
|
|
3092
|
+
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") return null;
|
|
3093
|
+
if (signal.aborted || error instanceof SessionQueryError && error.code === "SESSION_QUERY_ABORTED") throw new RemoteError("gateway/cancelled", "Session projection read was cancelled", {}, { cause: error });
|
|
3094
|
+
if (error instanceof RemoteError) throw error;
|
|
3095
|
+
throw new RemoteError("gateway/internal", "Session projection read failed", {}, { cause: error });
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
2968
3099
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
2969
3100
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
2970
3101
|
* @returns one complete baseline followed by live replacement frames.
|