@deepseek-ai/dsh-api-session-controller 0.2.1-alpha.1 → 0.2.1-alpha.3
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 +6 -2
- package/README.zh.md +6 -2
- package/lib/client.js +55 -26
- package/lib/index.js +265 -110
- package/lib/typert.host.js +1328 -590
- package/lib/typert.remote-client.d.ts +25 -2
- package/lib/typert.remote-client.js +1102 -561
- package/lib/types/agent.d.ts +61 -17
- package/lib/types/agent.js +148 -84
- package/lib/types/client/contract/sessions.d.ts +10 -5
- package/lib/types/client/sessions/lineage.d.ts +2 -0
- package/lib/types/client/sessions/manager.d.ts +10 -7
- package/lib/types/client/sessions/manager.js +42 -14
- package/lib/types/client/sessions/projection-store.d.ts +3 -3
- package/lib/types/client/sessions/projection-store.js +1 -1
- package/lib/types/client/sessions/service.d.ts +11 -6
- package/lib/types/client/sessions/service.js +9 -5
- package/lib/types/client/sessions/session.js +1 -1
- package/lib/types/commands.d.ts +20 -6
- package/lib/types/commands.js +28 -34
- package/lib/types/index.d.ts +11 -8
- package/lib/types/index.js +105 -25
- package/lib/types/list.js +6 -4
- package/lib/types/skill-catalog.js +7 -6
- package/lib/types/types.d.ts +28 -5
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +83 -77
package/lib/index.js
CHANGED
|
@@ -4,18 +4,18 @@ import z from "@deepseek-ai/schemastery";
|
|
|
4
4
|
import { AssistantStreamAccumulator, ReasoningEffortId, assistantStreamChunks, createUserMessage, errorChain, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { canOpenNativePath, nativeFileApplications, nativeFileManager, openNativeAssociatedPath, openNativeFileApplication, revealNativePath } from "@deepseek-ai/dsh-native-command";
|
|
6
6
|
import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
7
|
-
import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
7
|
+
import { Remote, RemoteError, TypertRemoteService, principalOf, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
|
+
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
9
|
+
import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
8
11
|
import { mkdir } from "node:fs/promises";
|
|
9
12
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
10
|
-
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
13
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
14
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
13
15
|
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
14
16
|
import { buildForkSeed } from "@deepseek-ai/dsh-session/fork";
|
|
15
17
|
import { SessionLogOffset, SessionSeq, isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
|
|
16
18
|
import { SessionTitleInvalidError } from "@deepseek-ai/dsh-session-title";
|
|
17
|
-
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
18
|
-
import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
19
19
|
import { Deque } from "@deepseek-ai/dsh-deque";
|
|
20
20
|
import { performance } from "node:perf_hooks";
|
|
21
21
|
import { scheduler } from "node:timers/promises";
|
|
@@ -106,18 +106,18 @@ var ApiSessionCwdConflict = class extends Error {
|
|
|
106
106
|
this.existingCwd = existingCwd;
|
|
107
107
|
}
|
|
108
108
|
};
|
|
109
|
-
/**
|
|
109
|
+
/** Reuse of a Session named an Agent preset while the deployment composes no preset roster. */
|
|
110
110
|
var ApiSessionPresetConflict = class extends Error {
|
|
111
111
|
sessionId;
|
|
112
112
|
requestedPreset;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
super(existingPreset === void 0 ? `session "${sessionId}" records no agent preset and cannot be adopted under "${requestedPreset}"` : `session "${sessionId}" runs agent preset "${existingPreset}", not "${requestedPreset}"`);
|
|
113
|
+
constructor(sessionId, requestedPreset) {
|
|
114
|
+
super(`the deployment composes no agent preset roster, so session "${sessionId}" cannot run preset "${requestedPreset}"`);
|
|
116
115
|
this.sessionId = sessionId;
|
|
117
116
|
this.requestedPreset = requestedPreset;
|
|
118
|
-
this.existingPreset = existingPreset;
|
|
119
117
|
}
|
|
120
118
|
};
|
|
119
|
+
/** A blank Session chosen for reuse started a turn before this Host acquired its writer. */
|
|
120
|
+
var ApiSessionStartedSinceObserved = class extends Error {};
|
|
121
121
|
/**
|
|
122
122
|
* Test whether generic Session routing must leave an identity to subagent routing.
|
|
123
123
|
* @param ctx - Host context carrying the Agent ownership registry.
|
|
@@ -180,7 +180,7 @@ async function inspectApiSession(ctx, sessionId, signal) {
|
|
|
180
180
|
var ApiSessionAgentController = class {
|
|
181
181
|
ctx;
|
|
182
182
|
resumes = /* @__PURE__ */ new Map();
|
|
183
|
-
|
|
183
|
+
claims = /* @__PURE__ */ new Map();
|
|
184
184
|
selections = /* @__PURE__ */ new WeakMap();
|
|
185
185
|
imageAdmissionChains = /* @__PURE__ */ new WeakMap();
|
|
186
186
|
/** @param ctx - Host context carrying Agent, model, persistence, and Typert services. */
|
|
@@ -245,35 +245,26 @@ var ApiSessionAgentController = class {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
248
|
+
* Create a Session for a new conversation, reusing the named blank Session
|
|
249
|
+
* while it is still reusable. A reused Session runs the requested preset, or
|
|
250
|
+
* the configured default when the request names none; the preset it recorded
|
|
251
|
+
* is not resolved, and a change is logged as `agent-preset/selected` before
|
|
252
|
+
* the Agent is published. A named Session that has started a turn, fails the
|
|
253
|
+
* caller's placement rule, or is written by another owner stays unchanged,
|
|
254
|
+
* and a new Session is created instead. Requests naming one identity run in
|
|
255
|
+
* arrival order, including their preset resolution, and check the named
|
|
256
|
+
* Session's directory and ownership before resolving the preset.
|
|
257
|
+
* @param request - optional Session to reuse, directory, preset, and placement rule.
|
|
258
|
+
* @returns the reused or created Session and the preset it ran when this request finished.
|
|
259
|
+
* @throws ApiSessionCwdConflict when the named Session belongs to another directory.
|
|
260
|
+
* @throws ApiSessionSubagentOwnership when subagent routing owns the named Session.
|
|
261
|
+
* @throws RemoteError `agent-preset/not-found` or `agent-preset/invalid` for the requested or default preset.
|
|
262
|
+
* @throws ApiSessionPresetConflict when no roster is composed and the request names a preset for a reused Session.
|
|
254
263
|
*/
|
|
255
|
-
async
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
const live = this.ctx.agents.get(sessionId);
|
|
260
|
-
if (live !== void 0) {
|
|
261
|
-
if (hasApiSessionSubagentOwner(this.ctx, live.session, live)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
262
|
-
return live;
|
|
263
|
-
}
|
|
264
|
-
const attached = this.ctx.sessions.get(sessionId);
|
|
265
|
-
if (attached !== void 0 && hasApiSessionSubagentOwner(this.ctx, attached, void 0)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
266
|
-
throw error;
|
|
267
|
-
}).finally(() => {
|
|
268
|
-
this.creations.delete(sessionId);
|
|
269
|
-
});
|
|
270
|
-
this.creations.set(sessionId, creation);
|
|
271
|
-
}
|
|
272
|
-
const agent = await creation;
|
|
273
|
-
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
274
|
-
if (presetId !== void 0) this.assertPresetUnchanged(sessionId, presetId, this.presetForSession(agent.session));
|
|
275
|
-
if (agent.session.header.cwd !== cwd) throw new ApiSessionCwdConflict(sessionId, cwd, agent.session.header.cwd);
|
|
276
|
-
return agent;
|
|
264
|
+
async createSession(request) {
|
|
265
|
+
const named = request.sessionId;
|
|
266
|
+
if (named === void 0) return this.created(await this.createMinted(request.cwd, await this.composeAgent(request.agentPreset)));
|
|
267
|
+
return this.inOrder(named, async () => this.created(await this.claim(named, request)));
|
|
277
268
|
}
|
|
278
269
|
/**
|
|
279
270
|
* Install or return the Session-local model selection used by prompt assembly.
|
|
@@ -411,44 +402,118 @@ var ApiSessionAgentController = class {
|
|
|
411
402
|
setup: composition.setup
|
|
412
403
|
})).agent;
|
|
413
404
|
}
|
|
414
|
-
async
|
|
405
|
+
async inOrder(sessionId, operation) {
|
|
406
|
+
const result = (this.claims.get(sessionId) ?? Promise.resolve()).then(operation);
|
|
407
|
+
const settled = result.then(() => void 0, () => void 0);
|
|
408
|
+
this.claims.set(sessionId, settled);
|
|
409
|
+
try {
|
|
410
|
+
return await result;
|
|
411
|
+
} finally {
|
|
412
|
+
if (this.claims.get(sessionId) === settled) this.claims.delete(sessionId);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** @returns the reused Session, a new Session under `sessionId` when none is stored, or a new Session under a minted identity. */
|
|
416
|
+
async claim(sessionId, request) {
|
|
415
417
|
const attached = this.ctx.sessions.get(sessionId);
|
|
416
418
|
const live = this.ctx.agents.get(sessionId);
|
|
417
419
|
if (attached !== void 0 && hasApiSessionSubagentOwner(this.ctx, attached, live)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
418
|
-
if (live !== void 0)
|
|
419
|
-
|
|
420
|
+
if (live !== void 0) {
|
|
421
|
+
if (live.session.header.cwd !== request.cwd) throw new ApiSessionCwdConflict(live.id, request.cwd, live.session.header.cwd);
|
|
422
|
+
const composition = await this.composeAgent(request.agentPreset);
|
|
423
|
+
return await this.reuseLive(live, request, composition) ?? this.createMinted(request.cwd, composition);
|
|
424
|
+
}
|
|
425
|
+
let observation;
|
|
426
|
+
try {
|
|
427
|
+
observation = await this.ctx.sessionQuery.observeSession(sessionId);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (!(error instanceof SessionQueryError) || error.code !== "SESSION_QUERY_SESSION_NOT_FOUND") throw error;
|
|
430
|
+
return this.createAgent(sessionId, request.cwd, await this.composeAgent(request.agentPreset));
|
|
431
|
+
}
|
|
432
|
+
let reusable;
|
|
433
|
+
{
|
|
420
434
|
const env_3 = {
|
|
421
435
|
stack: [],
|
|
422
436
|
error: void 0,
|
|
423
437
|
hasError: false
|
|
424
438
|
};
|
|
425
439
|
try {
|
|
426
|
-
const
|
|
427
|
-
if (hasApiSessionSubagentOwner(this.ctx, { header:
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
this.assertPresetUnchanged(sessionId, presetId, storedPreset);
|
|
431
|
-
const composition = await this.composeAgent(storedPreset);
|
|
432
|
-
return (await this.ctx.agents.resume({
|
|
433
|
-
resumeSessionId: sessionId,
|
|
434
|
-
agentOptions: this.agentOptions(),
|
|
435
|
-
setup: composition.setup
|
|
436
|
-
})).agent;
|
|
440
|
+
const observed = __addDisposableResource$4(env_3, observation, false);
|
|
441
|
+
if (hasApiSessionSubagentOwner(this.ctx, { header: observed.header }, void 0)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
442
|
+
if (observed.header.cwd !== request.cwd) throw new ApiSessionCwdConflict(sessionId, request.cwd, observed.header.cwd);
|
|
443
|
+
reusable = request.reusable(sessionId) && !observed.events.some((event) => event.type === "turn/start");
|
|
437
444
|
} catch (e_3) {
|
|
438
445
|
env_3.error = e_3;
|
|
439
446
|
env_3.hasError = true;
|
|
440
447
|
} finally {
|
|
441
448
|
__disposeResources$4(env_3);
|
|
442
449
|
}
|
|
450
|
+
}
|
|
451
|
+
const composition = await this.composeAgent(request.agentPreset);
|
|
452
|
+
if (!reusable) return this.createMinted(request.cwd, composition);
|
|
453
|
+
this.assertPresetApplicable(sessionId, request.agentPreset);
|
|
454
|
+
try {
|
|
455
|
+
return (await this.ctx.agents.resume({
|
|
456
|
+
resumeSessionId: sessionId,
|
|
457
|
+
agentOptions: this.agentOptions(),
|
|
458
|
+
setup: this.reuseSetup(composition)
|
|
459
|
+
})).agent;
|
|
443
460
|
} catch (error) {
|
|
444
|
-
if (
|
|
461
|
+
if (error instanceof ApiSessionStartedSinceObserved) return this.createMinted(request.cwd, composition);
|
|
462
|
+
if (!(error instanceof Error) || error.name !== "SessionAlreadyOwnedError") throw error;
|
|
463
|
+
return this.ctx.agents.get(sessionId) === void 0 ? this.createMinted(request.cwd, composition) : this.claim(sessionId, request);
|
|
445
464
|
}
|
|
465
|
+
}
|
|
466
|
+
/** @returns the live blank Session under the requested preset, or undefined when it cannot be reused. */
|
|
467
|
+
async reuseLive(live, request, composition) {
|
|
468
|
+
if (!request.reusable(live.id) || !this.isBlank(live.session)) return void 0;
|
|
469
|
+
this.assertPresetApplicable(live.id, request.agentPreset);
|
|
470
|
+
const current = this.presetForSession(live.session);
|
|
471
|
+
const presets = this.ctx.get("agentPresets");
|
|
472
|
+
const target = composition.agentPreset;
|
|
473
|
+
if (presets === void 0 || target === void 0 || target === current) return live;
|
|
474
|
+
try {
|
|
475
|
+
await presets.select(live, target);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (remoteErrorOf(error)?.code !== "agent-preset/locked") throw error;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
return live;
|
|
481
|
+
}
|
|
482
|
+
/** Setup that re-checks blankness under the acquired writer, then records a preset change before publication. */
|
|
483
|
+
reuseSetup(composition) {
|
|
484
|
+
return async (agentCtx, agent) => {
|
|
485
|
+
if (!this.isBlank(agent.session)) throw new ApiSessionStartedSinceObserved();
|
|
486
|
+
const commit = await composition.setup(agentCtx, agent);
|
|
487
|
+
const target = composition.agentPreset;
|
|
488
|
+
if (target !== void 0 && this.presetForSession(agent.session) !== target) agent.session.append("agent-preset/selected", { agentPreset: target });
|
|
489
|
+
return commit;
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
/** Whether no turn has started, by the predicate the preset registry's `select` enforces. */
|
|
493
|
+
isBlank(session) {
|
|
494
|
+
const boundary = this.ctx.sessionProjections.stateOf(session, "turnBoundary");
|
|
495
|
+
return boundary === void 0 || boundary.openTurnStartSeq === null && boundary.lastTurn === 0;
|
|
496
|
+
}
|
|
497
|
+
/** Without a roster, reuse cannot apply a named preset. */
|
|
498
|
+
assertPresetApplicable(sessionId, requested) {
|
|
499
|
+
if (requested === void 0 || this.ctx.get("agentPresets") !== void 0) return;
|
|
500
|
+
throw new ApiSessionPresetConflict(sessionId, requested);
|
|
501
|
+
}
|
|
502
|
+
created(agent) {
|
|
503
|
+
return {
|
|
504
|
+
agent,
|
|
505
|
+
agentPreset: this.presetForSession(agent.session)
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
createMinted(cwd, composition) {
|
|
509
|
+
return this.createAgent(brandString(`session-${randomUUID()}`), cwd, composition);
|
|
510
|
+
}
|
|
511
|
+
async createAgent(sessionId, cwd, composition) {
|
|
446
512
|
try {
|
|
447
513
|
await mkdir(cwd, { recursive: true });
|
|
448
514
|
} catch (error) {
|
|
449
515
|
throw new Error(`failed to ensure project directory "${cwd}": ${String(error)}`, { cause: error });
|
|
450
516
|
}
|
|
451
|
-
const composition = await this.composeAgent(presetId);
|
|
452
517
|
return (await this.ctx.agents.create({
|
|
453
518
|
sessionId,
|
|
454
519
|
agentOptions: this.agentOptions(),
|
|
@@ -478,10 +543,6 @@ var ApiSessionAgentController = class {
|
|
|
478
543
|
if (observation.projections === void 0) throw new Error("api-session: Agent activation requires a projected Session observation");
|
|
479
544
|
return observation.projections.values.agentPreset ?? void 0;
|
|
480
545
|
}
|
|
481
|
-
assertPresetUnchanged(sessionId, requested, existing) {
|
|
482
|
-
if (requested === void 0 || requested === existing) return;
|
|
483
|
-
throw new ApiSessionPresetConflict(sessionId, requested, existing);
|
|
484
|
-
}
|
|
485
546
|
};
|
|
486
547
|
function agentModelSelection(selection) {
|
|
487
548
|
return {
|
|
@@ -648,6 +709,11 @@ var __disposeResources$3 = (function(SuppressedError) {
|
|
|
648
709
|
var e = new Error(message);
|
|
649
710
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
650
711
|
});
|
|
712
|
+
/**
|
|
713
|
+
* Whether prompt content carries non-whitespace text or an attachment.
|
|
714
|
+
* @param content - submitted or edited prompt parts.
|
|
715
|
+
* @returns false for content the Agent must not receive.
|
|
716
|
+
*/
|
|
651
717
|
function hasPromptContent(content) {
|
|
652
718
|
return content.some((part) => part.type !== "text" || part.text.trim().length > 0);
|
|
653
719
|
}
|
|
@@ -681,25 +747,31 @@ var SessionCommandController = class {
|
|
|
681
747
|
this.defaultCwd = defaultCwd;
|
|
682
748
|
}
|
|
683
749
|
/**
|
|
684
|
-
* Create
|
|
685
|
-
*
|
|
686
|
-
* @
|
|
750
|
+
* Create one ordinary Session for a new conversation, reusing the named
|
|
751
|
+
* blank Session while it is an unarchived member of the requested Workspace.
|
|
752
|
+
* @param request - optional blank Session to reuse, location, and Agent preset.
|
|
753
|
+
* @returns the created or reused Session identity and its preset when configured.
|
|
687
754
|
*/
|
|
688
755
|
async create(request) {
|
|
689
756
|
if (request.workspaceId !== void 0 && request.cwd !== void 0) throw new RemoteError("gateway/bad-request", "session.create accepts workspaceId or cwd, not both", {});
|
|
690
|
-
const sessionId = request.sessionId ?? brandString(`session-${randomUUID()}`);
|
|
691
757
|
let workspace;
|
|
692
758
|
if (request.workspaceId !== void 0) {
|
|
693
759
|
workspace = this.ctx.workspaceRegistry.get(request.workspaceId);
|
|
694
760
|
if (workspace === void 0) throw new RemoteError("workspace/not-found", `workspace "${request.workspaceId}" not found`, { workspaceId: request.workspaceId });
|
|
695
761
|
}
|
|
696
762
|
const cwd = workspace?.path ?? request.cwd ?? this.defaultCwd;
|
|
697
|
-
let
|
|
763
|
+
let created;
|
|
698
764
|
try {
|
|
699
|
-
|
|
765
|
+
created = await this.agents.createSession({
|
|
766
|
+
...request.sessionId === void 0 ? {} : { sessionId: request.sessionId },
|
|
767
|
+
cwd,
|
|
768
|
+
...request.agentPreset === void 0 ? {} : { agentPreset: request.agentPreset },
|
|
769
|
+
reusable: (sessionId) => !this.ctx.workspaceRegistry.archivedSessionIds.includes(sessionId) && (workspace === void 0 || workspace.sessionIds.includes(sessionId))
|
|
770
|
+
});
|
|
700
771
|
} catch (error) {
|
|
701
|
-
this.rejectCreation(
|
|
772
|
+
this.rejectCreation(error);
|
|
702
773
|
}
|
|
774
|
+
const sessionId = created.agent.id;
|
|
703
775
|
if (workspace !== void 0) try {
|
|
704
776
|
await workspace.attachSession(sessionId);
|
|
705
777
|
} catch (error) {
|
|
@@ -708,10 +780,9 @@ var SessionCommandController = class {
|
|
|
708
780
|
workspaceId: workspace.id
|
|
709
781
|
});
|
|
710
782
|
}
|
|
711
|
-
const agentPreset = this.agents.presetForSession(adopted.session);
|
|
712
783
|
return {
|
|
713
784
|
sessionId,
|
|
714
|
-
...agentPreset === void 0 ? {} : { agentPreset }
|
|
785
|
+
...created.agentPreset === void 0 ? {} : { agentPreset: created.agentPreset }
|
|
715
786
|
};
|
|
716
787
|
}
|
|
717
788
|
/**
|
|
@@ -845,21 +916,15 @@ var SessionCommandController = class {
|
|
|
845
916
|
}
|
|
846
917
|
}
|
|
847
918
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
919
|
+
* Admit one prompt after Agent and attachment validation; the Remote method
|
|
920
|
+
* has already validated the content and built the durable message source.
|
|
921
|
+
* @param request - Session identity, prompt content, and delivery mode.
|
|
922
|
+
* @param source - the `user-rpc` source recorded on the accepted message.
|
|
850
923
|
* @returns acknowledgement that the Agent accepted the prompt.
|
|
851
924
|
*/
|
|
852
|
-
async prompt(request) {
|
|
853
|
-
if (!hasPromptContent(request.content)) throw new RemoteError("gateway/bad-request", "prompt content must include non-whitespace text or an attachment", {});
|
|
854
|
-
const clientTimeZone = request.clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(request.clientTimeZone);
|
|
855
|
-
if (request.clientTimeZone !== void 0 && clientTimeZone === void 0) throw new RemoteError("session/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: request.clientTimeZone });
|
|
925
|
+
async prompt(request, source) {
|
|
856
926
|
const agent = await this.resolveAgent(request.sessionId);
|
|
857
927
|
if (hasPromptRequest(agent, request.requestId)) return { accepted: true };
|
|
858
|
-
const source = {
|
|
859
|
-
kind: "user",
|
|
860
|
-
rpcId: request.requestId,
|
|
861
|
-
...clientTimeZone === void 0 ? {} : { clientTimeZone }
|
|
862
|
-
};
|
|
863
928
|
const hasImage = request.content.some((part) => part.type === "image");
|
|
864
929
|
const admit = async () => {
|
|
865
930
|
try {
|
|
@@ -1005,13 +1070,11 @@ var SessionCommandController = class {
|
|
|
1005
1070
|
if ("error" in found) throw found.error;
|
|
1006
1071
|
return found.agent;
|
|
1007
1072
|
}
|
|
1008
|
-
rejectCreation(
|
|
1073
|
+
rejectCreation(error) {
|
|
1009
1074
|
if (remoteErrorOf(error) !== void 0) throw error;
|
|
1010
|
-
if (error instanceof Error && error.name === "SessionAlreadyOwnedError") throw new RemoteError("session/writer-held", error.message, { sessionId });
|
|
1011
1075
|
if (error instanceof ApiSessionPresetConflict) throw new RemoteError("agent-preset/conflict", error.message, {
|
|
1012
1076
|
sessionId: error.sessionId,
|
|
1013
|
-
requestedPreset: error.requestedPreset
|
|
1014
|
-
...error.existingPreset === void 0 ? {} : { existingPreset: error.existingPreset }
|
|
1077
|
+
requestedPreset: error.requestedPreset
|
|
1015
1078
|
});
|
|
1016
1079
|
if (error instanceof ApiSessionCwdConflict) throw new RemoteError("session/conflict", error.message, {
|
|
1017
1080
|
sessionId: error.sessionId,
|
|
@@ -1019,7 +1082,7 @@ var SessionCommandController = class {
|
|
|
1019
1082
|
...error.existingCwd === void 0 ? {} : { existingCwd: error.existingCwd }
|
|
1020
1083
|
});
|
|
1021
1084
|
if (error instanceof ApiSessionSubagentOwnership) throw apiSessionSubagentOwnershipError(error.sessionId);
|
|
1022
|
-
throw new RemoteError("gateway/internal", `failed to create session
|
|
1085
|
+
throw new RemoteError("gateway/internal", `failed to create session: ${String(error)}`, {});
|
|
1023
1086
|
}
|
|
1024
1087
|
async readSessionState(sessionId) {
|
|
1025
1088
|
const attached = this.ctx.sessions.get(sessionId);
|
|
@@ -1881,6 +1944,7 @@ var ApiSessionList = class {
|
|
|
1881
1944
|
sessionId: session.id,
|
|
1882
1945
|
updatedAt: updatedAt(session.header, metadata),
|
|
1883
1946
|
agentAvailable: this.ctx.agents.get(session.id)?.session === session,
|
|
1947
|
+
formatStatus: "current",
|
|
1884
1948
|
running: this.ctx.agents.get(session.id)?.status === "running",
|
|
1885
1949
|
blank: metadata?.blank ?? session.seq === 0,
|
|
1886
1950
|
...listFields(session.header),
|
|
@@ -1903,16 +1967,16 @@ var ApiSessionList = class {
|
|
|
1903
1967
|
signal?.throwIfAborted();
|
|
1904
1968
|
const live = this.ctx.sessions.get(record.header.id);
|
|
1905
1969
|
if (live !== void 0) items.push(this.summaryFor(live));
|
|
1906
|
-
else if (record.header.cwd !== void 0) cold.push(record
|
|
1970
|
+
else if (record.header.cwd !== void 0) cold.push(record);
|
|
1907
1971
|
if (performance.now() >= yieldDeadline) {
|
|
1908
1972
|
await scheduler.yield();
|
|
1909
1973
|
signal?.throwIfAborted();
|
|
1910
1974
|
yieldDeadline = performance.now() + this.workSliceMs;
|
|
1911
1975
|
}
|
|
1912
1976
|
}
|
|
1913
|
-
for (const
|
|
1977
|
+
for (const record of cold) {
|
|
1914
1978
|
signal?.throwIfAborted();
|
|
1915
|
-
items.push(this.summarizeCold(
|
|
1979
|
+
items.push(this.summarizeCold(record));
|
|
1916
1980
|
if (performance.now() >= yieldDeadline) {
|
|
1917
1981
|
await scheduler.yield();
|
|
1918
1982
|
signal?.throwIfAborted();
|
|
@@ -1923,13 +1987,14 @@ var ApiSessionList = class {
|
|
|
1923
1987
|
items.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1924
1988
|
return items;
|
|
1925
1989
|
}
|
|
1926
|
-
summarizeCold(header) {
|
|
1990
|
+
summarizeCold({ header, formatStatus }) {
|
|
1927
1991
|
const projections = this.projectionsFor(header, void 0);
|
|
1928
1992
|
const metadata = projections?.values.sessionListMetadata;
|
|
1929
1993
|
return {
|
|
1930
1994
|
sessionId: header.id,
|
|
1931
1995
|
updatedAt: updatedAt(header, metadata),
|
|
1932
1996
|
agentAvailable: false,
|
|
1997
|
+
...formatStatus === void 0 ? {} : { formatStatus },
|
|
1933
1998
|
running: false,
|
|
1934
1999
|
blank: metadata?.blank ?? false,
|
|
1935
2000
|
...listFields(header),
|
|
@@ -2261,7 +2326,8 @@ let SessionSkillCatalog = (() => {
|
|
|
2261
2326
|
static inject = [
|
|
2262
2327
|
"agents",
|
|
2263
2328
|
"sessionQuery",
|
|
2264
|
-
"typert"
|
|
2329
|
+
"typert",
|
|
2330
|
+
"workingDirectory"
|
|
2265
2331
|
];
|
|
2266
2332
|
/** @param ctx - Host context carrying Session reads and optional skill/preset services. */
|
|
2267
2333
|
constructor(ctx) {
|
|
@@ -2294,8 +2360,10 @@ let SessionSkillCatalog = (() => {
|
|
|
2294
2360
|
try {
|
|
2295
2361
|
const observation = __addDisposableResource$1(env_2, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
2296
2362
|
if (observation.projections === void 0) throw new Error("skill catalog requires a projected Session observation");
|
|
2297
|
-
cwd = observation.header.cwd;
|
|
2363
|
+
cwd = observation.projections.values.workingDirectory ?? observation.header.cwd ?? this.ctx.workingDirectory.defaultDirectory;
|
|
2298
2364
|
agentPreset = observation.projections.values.agentPreset ?? void 0;
|
|
2365
|
+
const live = this.ctx.agents.get(sessionId);
|
|
2366
|
+
if (live !== void 0) cwd = await this.ctx.workingDirectory.ensure(live, signal);
|
|
2299
2367
|
} catch (e_1) {
|
|
2300
2368
|
env_2.error = e_1;
|
|
2301
2369
|
env_2.hasError = true;
|
|
@@ -2306,7 +2374,6 @@ let SessionSkillCatalog = (() => {
|
|
|
2306
2374
|
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new RemoteError("session/not-found", `session "${sessionId}" not found`, { sessionId });
|
|
2307
2375
|
throw new RemoteError("gateway/internal", `session "${sessionId}" could not be inspected: ${String(error)}`, {});
|
|
2308
2376
|
}
|
|
2309
|
-
if (cwd === void 0) throw new RemoteError("gateway/internal", `session "${sessionId}" has no project cwd`, {});
|
|
2310
2377
|
const live = this.ctx.agents.get(sessionId);
|
|
2311
2378
|
const presets = this.ctx.get("agentPresets");
|
|
2312
2379
|
const skillRegistry = (live === void 0 ? void 0 : presets?.serviceFor(live, "skills")) ?? this.ctx.get("skills");
|
|
@@ -2571,6 +2638,15 @@ var __disposeResources = (function(SuppressedError) {
|
|
|
2571
2638
|
var e = new Error(message);
|
|
2572
2639
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
2573
2640
|
});
|
|
2641
|
+
/**
|
|
2642
|
+
* Read the Session resource a history address names: the Session itself, or
|
|
2643
|
+
* the child Session whose transcript a subagent address reads.
|
|
2644
|
+
* @param request - history request carrying the durable address.
|
|
2645
|
+
* @returns the Session whose access the read requires.
|
|
2646
|
+
*/
|
|
2647
|
+
function addressedSession(request) {
|
|
2648
|
+
return request.address.kind === "session" ? request.address.sessionId : request.address.childSessionId;
|
|
2649
|
+
}
|
|
2574
2650
|
/** Host service backing the generated `ctx.remote.session` namespace. */
|
|
2575
2651
|
let SessionController = (() => {
|
|
2576
2652
|
let _classSuper = TypertRemoteService;
|
|
@@ -2612,8 +2688,14 @@ let SessionController = (() => {
|
|
|
2612
2688
|
_attachment_decorators = [Remote("attachment")];
|
|
2613
2689
|
_updateQueue_decorators = [Remote("updateQueue")];
|
|
2614
2690
|
_cancel_decorators = [Remote("cancel")];
|
|
2615
|
-
_page_decorators = [Remote(
|
|
2616
|
-
|
|
2691
|
+
_page_decorators = [Remote({
|
|
2692
|
+
name: "page",
|
|
2693
|
+
resources: { session: addressedSession }
|
|
2694
|
+
})];
|
|
2695
|
+
_follow_decorators = [Remote({
|
|
2696
|
+
mode: "stream",
|
|
2697
|
+
resources: { session: addressedSession }
|
|
2698
|
+
})];
|
|
2617
2699
|
_projections_decorators = [Remote("projections")];
|
|
2618
2700
|
_control_decorators = [Remote({ mode: "stream" })];
|
|
2619
2701
|
__esDecorate(this, null, _list_decorators, {
|
|
@@ -2839,6 +2921,7 @@ let SessionController = (() => {
|
|
|
2839
2921
|
"fileUploads",
|
|
2840
2922
|
"fs",
|
|
2841
2923
|
"llm",
|
|
2924
|
+
"remoteAccess",
|
|
2842
2925
|
"sessions",
|
|
2843
2926
|
"sessionProjections",
|
|
2844
2927
|
"sessionQuery",
|
|
@@ -2867,6 +2950,7 @@ let SessionController = (() => {
|
|
|
2867
2950
|
*/
|
|
2868
2951
|
constructor(ctx, config, internals = {}) {
|
|
2869
2952
|
super(ctx, "sessionController", { namespace: "session" });
|
|
2953
|
+
ctx.typert.resources.register("session", { field: "sessionId" });
|
|
2870
2954
|
const resolved = SessionController.Config(config);
|
|
2871
2955
|
installModelSelectionProjection(ctx);
|
|
2872
2956
|
this.agents = new ApiSessionAgentController(ctx);
|
|
@@ -2975,21 +3059,33 @@ let SessionController = (() => {
|
|
|
2975
3059
|
* @returns visible Session summaries ordered by activity.
|
|
2976
3060
|
*/
|
|
2977
3061
|
async list(_request, signal) {
|
|
2978
|
-
return { items: await this.listState.list(signal)
|
|
3062
|
+
return { items: (await this.listState.list(signal)).filter((item) => this.ctx.remoteAccess.allows({
|
|
3063
|
+
kind: "session",
|
|
3064
|
+
id: item.sessionId
|
|
3065
|
+
})) };
|
|
2979
3066
|
}
|
|
2980
3067
|
/**
|
|
2981
|
-
* Search visible Session content without resuming an Agent.
|
|
3068
|
+
* Search visible Session content without resuming an Agent. A non-owner
|
|
3069
|
+
* receives only the hits whose Session `allows()` admits; `hasMore`
|
|
3070
|
+
* describes the bounded result before that filtering.
|
|
2982
3071
|
* @param request - literal message-content query.
|
|
2983
3072
|
* @param signal - cancellation for list and search reads.
|
|
2984
3073
|
* @returns authorized bounded Session search results.
|
|
2985
3074
|
*/
|
|
2986
|
-
search(request, signal) {
|
|
2987
|
-
|
|
3075
|
+
async search(request, signal) {
|
|
3076
|
+
const result = await this.listState.search(request.query, signal);
|
|
3077
|
+
return {
|
|
3078
|
+
...result,
|
|
3079
|
+
items: result.items.filter((item) => this.ctx.remoteAccess.allows({
|
|
3080
|
+
kind: "session",
|
|
3081
|
+
id: item.sessionId
|
|
3082
|
+
}))
|
|
3083
|
+
};
|
|
2988
3084
|
}
|
|
2989
3085
|
/**
|
|
2990
|
-
* Create
|
|
2991
|
-
* @param request -
|
|
2992
|
-
* @returns the Session identity and
|
|
3086
|
+
* Create one ordinary Session for a new conversation, reusing the named blank Session while it is reusable.
|
|
3087
|
+
* @param request - optional blank Session to reuse, location, and Agent preset.
|
|
3088
|
+
* @returns the created or reused Session identity and its preset when configured.
|
|
2993
3089
|
*/
|
|
2994
3090
|
create(request) {
|
|
2995
3091
|
return this.commands.create(request);
|
|
@@ -3104,10 +3200,13 @@ let SessionController = (() => {
|
|
|
3104
3200
|
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
3105
3201
|
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
3106
3202
|
* synthetic fork closers.
|
|
3107
|
-
* @param request - source Session
|
|
3203
|
+
* @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
|
|
3108
3204
|
* @returns the new Session identity.
|
|
3109
3205
|
*/
|
|
3110
|
-
fork(request) {
|
|
3206
|
+
async fork(request) {
|
|
3207
|
+
if (request.allowMigration === false && this.ctx.sessions.get(request.sessionId) === void 0) {
|
|
3208
|
+
if ((await this.ctx.get("sessionPersistence")?.stat(request.sessionId))?.formatStatus === "migration-required" && this.ctx.sessions.get(request.sessionId) === void 0) throw new RemoteError("session/migration-required", "Open the source Session to complete migration before forking", { sessionId: request.sessionId });
|
|
3209
|
+
}
|
|
3111
3210
|
return this.commands.fork(request);
|
|
3112
3211
|
}
|
|
3113
3212
|
/**
|
|
@@ -3116,9 +3215,19 @@ let SessionController = (() => {
|
|
|
3116
3215
|
* @param signal - caller cancellation before prompt admission begins.
|
|
3117
3216
|
* @returns acknowledgement that the Agent accepted the prompt.
|
|
3118
3217
|
*/
|
|
3119
|
-
prompt(request, signal) {
|
|
3218
|
+
async prompt(request, signal) {
|
|
3120
3219
|
signal.throwIfAborted();
|
|
3121
|
-
|
|
3220
|
+
if (!hasPromptContent(request.content)) throw new RemoteError("gateway/bad-request", "prompt content must include non-whitespace text or an attachment", {});
|
|
3221
|
+
const clientTimeZone = request.clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(request.clientTimeZone);
|
|
3222
|
+
if (request.clientTimeZone !== void 0 && clientTimeZone === void 0) throw new RemoteError("session/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: request.clientTimeZone });
|
|
3223
|
+
const principal = principalOf(this.ctx.invocation);
|
|
3224
|
+
const source = {
|
|
3225
|
+
kind: "user",
|
|
3226
|
+
rpcId: request.requestId,
|
|
3227
|
+
...clientTimeZone === void 0 ? {} : { clientTimeZone },
|
|
3228
|
+
...principal === void 0 ? {} : { principal }
|
|
3229
|
+
};
|
|
3230
|
+
return this.commands.prompt(request, source);
|
|
3122
3231
|
}
|
|
3123
3232
|
/**
|
|
3124
3233
|
* Read one image proven reachable from the addressed Session log.
|
|
@@ -3164,10 +3273,10 @@ let SessionController = (() => {
|
|
|
3164
3273
|
return this.history.follow(request, signal);
|
|
3165
3274
|
}
|
|
3166
3275
|
/**
|
|
3167
|
-
* Read
|
|
3276
|
+
* Read exact projections, returning cached hints only when migration is required.
|
|
3168
3277
|
* @param request - Session whose current values are required.
|
|
3169
|
-
* @param signal - cancellation for the Session
|
|
3170
|
-
* @returns
|
|
3278
|
+
* @param signal - cancellation for the Session read.
|
|
3279
|
+
* @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
|
|
3171
3280
|
*/
|
|
3172
3281
|
async projections(request, signal) {
|
|
3173
3282
|
const { sessionId } = request;
|
|
@@ -3179,9 +3288,22 @@ let SessionController = (() => {
|
|
|
3179
3288
|
hasError: false
|
|
3180
3289
|
};
|
|
3181
3290
|
try {
|
|
3291
|
+
signal.throwIfAborted();
|
|
3292
|
+
const live = this.liveProjections(sessionId);
|
|
3293
|
+
if (live !== void 0) return live;
|
|
3294
|
+
const stored = await this.ctx.get("sessionPersistence")?.stat(sessionId, { signal });
|
|
3295
|
+
signal.throwIfAborted();
|
|
3296
|
+
const attached = this.liveProjections(sessionId);
|
|
3297
|
+
if (attached !== void 0) return attached;
|
|
3298
|
+
if (stored === void 0) return null;
|
|
3299
|
+
if (stored.formatStatus === "migration-required") return {
|
|
3300
|
+
kind: "migration-required",
|
|
3301
|
+
values: { ...(this.ctx.get("sessionProjectionCache")?.cachedSnapshot(stored.header))?.values }
|
|
3302
|
+
};
|
|
3182
3303
|
const projections = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId, { signal }), false).projections;
|
|
3183
3304
|
if (projections === void 0) throw new RemoteError("session/projections-unavailable", "Session projections are unavailable", {});
|
|
3184
3305
|
return {
|
|
3306
|
+
kind: "sequenced",
|
|
3185
3307
|
asOfSeq: projections.asOfSeq,
|
|
3186
3308
|
values: projections.values
|
|
3187
3309
|
};
|
|
@@ -3192,19 +3314,52 @@ let SessionController = (() => {
|
|
|
3192
3314
|
__disposeResources(env_2);
|
|
3193
3315
|
}
|
|
3194
3316
|
} catch (error) {
|
|
3195
|
-
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") return null;
|
|
3317
|
+
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") return this.liveProjections(sessionId) ?? null;
|
|
3196
3318
|
if (signal.aborted || error instanceof SessionQueryError && error.code === "SESSION_QUERY_ABORTED") throw new RemoteError("gateway/cancelled", "Session projection read was cancelled", {}, { cause: error });
|
|
3197
3319
|
if (error instanceof RemoteError) throw error;
|
|
3198
3320
|
throw new RemoteError("gateway/internal", "Session projection read failed", {}, { cause: error });
|
|
3199
3321
|
}
|
|
3200
3322
|
}
|
|
3323
|
+
liveProjections(sessionId) {
|
|
3324
|
+
const session = this.ctx.sessions.get(sessionId);
|
|
3325
|
+
if (session === void 0) return void 0;
|
|
3326
|
+
const snapshot = this.ctx.sessionProjections.snapshot(session);
|
|
3327
|
+
return {
|
|
3328
|
+
kind: "sequenced",
|
|
3329
|
+
asOfSeq: snapshot.asOfSeq,
|
|
3330
|
+
values: snapshot.values
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3201
3333
|
/**
|
|
3202
3334
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
3203
3335
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
3204
3336
|
* @returns one complete baseline followed by live replacement frames.
|
|
3205
3337
|
*/
|
|
3206
|
-
control(signal) {
|
|
3207
|
-
|
|
3338
|
+
async *control(signal) {
|
|
3339
|
+
for await (const frame of this.controlState.control(signal)) switch (frame.type) {
|
|
3340
|
+
case "baseline": {
|
|
3341
|
+
const projections = Object.fromEntries(Object.entries(frame.value.projections).filter(([sessionId]) => this.ctx.remoteAccess.allows({
|
|
3342
|
+
kind: "session",
|
|
3343
|
+
id: sessionId
|
|
3344
|
+
})));
|
|
3345
|
+
yield {
|
|
3346
|
+
type: "baseline",
|
|
3347
|
+
value: {
|
|
3348
|
+
...frame.value,
|
|
3349
|
+
projections
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
break;
|
|
3353
|
+
}
|
|
3354
|
+
case "projection":
|
|
3355
|
+
if (this.ctx.remoteAccess.allows({
|
|
3356
|
+
kind: "session",
|
|
3357
|
+
id: frame.sessionId
|
|
3358
|
+
})) yield frame;
|
|
3359
|
+
break;
|
|
3360
|
+
/* v8 ignore next -- closed control-frame union. */
|
|
3361
|
+
default: assertNever(frame);
|
|
3362
|
+
}
|
|
3208
3363
|
}
|
|
3209
3364
|
};
|
|
3210
3365
|
})();
|