@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/types/agent.d.ts
CHANGED
|
@@ -21,12 +21,38 @@ export declare class ApiSessionCwdConflict extends Error {
|
|
|
21
21
|
readonly existingCwd: string | undefined;
|
|
22
22
|
constructor(sessionId: SessionId, requestedCwd: string, existingCwd: string | undefined);
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/** Reuse of a Session named an Agent preset while the deployment composes no preset roster. */
|
|
25
25
|
export declare class ApiSessionPresetConflict extends Error {
|
|
26
26
|
readonly sessionId: SessionId;
|
|
27
27
|
readonly requestedPreset: string;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
constructor(sessionId: SessionId, requestedPreset: string);
|
|
29
|
+
}
|
|
30
|
+
/** Placement and Agent preset for one Session created for a new conversation. */
|
|
31
|
+
export interface ApiSessionCreateRequest {
|
|
32
|
+
/** Blank Session to reuse, or the identity of a new Session when none is stored under it. */
|
|
33
|
+
readonly sessionId?: SessionId;
|
|
34
|
+
/** Directory the Session owns. */
|
|
35
|
+
readonly cwd: string;
|
|
36
|
+
/** Preset the Session runs; the configured default when omitted. */
|
|
37
|
+
readonly agentPreset?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Placement rule the caller owns, such as Workspace membership and archive state.
|
|
40
|
+
* @param sessionId - existing Session named by the request.
|
|
41
|
+
* @returns whether that Session may be reused.
|
|
42
|
+
*/
|
|
43
|
+
readonly reusable: (sessionId: SessionId) => boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The Session one creation request created or reused. */
|
|
46
|
+
export interface ApiSessionCreated {
|
|
47
|
+
/** Live Agent of the Session. */
|
|
48
|
+
readonly agent: Agent;
|
|
49
|
+
/** Preset the Session ran when the request finished, before a later request for the same identity; absent without a roster. */
|
|
50
|
+
readonly agentPreset: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Resolved preset and pre-publication Agent setup for one create or resume. */
|
|
53
|
+
interface ApiSessionComposition {
|
|
54
|
+
readonly agentPreset?: string;
|
|
55
|
+
readonly setup: AgentSetup;
|
|
30
56
|
}
|
|
31
57
|
/** Failures produced while resolving one ordinary Session identity to its live Agent. */
|
|
32
58
|
export type ApiSessionAgentError = RemoteError<'session/not-found' | 'session/agent-busy' | 'session/writer-held' | 'gateway/internal'>;
|
|
@@ -66,7 +92,7 @@ export declare function inspectApiSession(ctx: Context, sessionId: SessionId, si
|
|
|
66
92
|
export declare class ApiSessionAgentController {
|
|
67
93
|
private readonly ctx;
|
|
68
94
|
private readonly resumes;
|
|
69
|
-
private readonly
|
|
95
|
+
private readonly claims;
|
|
70
96
|
private readonly selections;
|
|
71
97
|
private readonly imageAdmissionChains;
|
|
72
98
|
/** @param ctx - Host context carrying Agent, model, persistence, and Typert services. */
|
|
@@ -85,14 +111,23 @@ export declare class ApiSessionAgentController {
|
|
|
85
111
|
resolveObservedAgent(observation: SessionObservation): Promise<ApiSessionAgentResult>;
|
|
86
112
|
private resolve;
|
|
87
113
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
114
|
+
* Create a Session for a new conversation, reusing the named blank Session
|
|
115
|
+
* while it is still reusable. A reused Session runs the requested preset, or
|
|
116
|
+
* the configured default when the request names none; the preset it recorded
|
|
117
|
+
* is not resolved, and a change is logged as `agent-preset/selected` before
|
|
118
|
+
* the Agent is published. A named Session that has started a turn, fails the
|
|
119
|
+
* caller's placement rule, or is written by another owner stays unchanged,
|
|
120
|
+
* and a new Session is created instead. Requests naming one identity run in
|
|
121
|
+
* arrival order, including their preset resolution, and check the named
|
|
122
|
+
* Session's directory and ownership before resolving the preset.
|
|
123
|
+
* @param request - optional Session to reuse, directory, preset, and placement rule.
|
|
124
|
+
* @returns the reused or created Session and the preset it ran when this request finished.
|
|
125
|
+
* @throws ApiSessionCwdConflict when the named Session belongs to another directory.
|
|
126
|
+
* @throws ApiSessionSubagentOwnership when subagent routing owns the named Session.
|
|
127
|
+
* @throws RemoteError `agent-preset/not-found` or `agent-preset/invalid` for the requested or default preset.
|
|
128
|
+
* @throws ApiSessionPresetConflict when no roster is composed and the request names a preset for a reused Session.
|
|
94
129
|
*/
|
|
95
|
-
|
|
130
|
+
createSession(request: ApiSessionCreateRequest): Promise<ApiSessionCreated>;
|
|
96
131
|
/**
|
|
97
132
|
* Install or return the Session-local model selection used by prompt assembly.
|
|
98
133
|
* @param agent - live Agent that owns the selection.
|
|
@@ -132,14 +167,24 @@ export declare class ApiSessionAgentController {
|
|
|
132
167
|
* @param presetId - requested preset or the configured default when omitted.
|
|
133
168
|
* @returns the resolved preset identity and Agent setup callback.
|
|
134
169
|
*/
|
|
135
|
-
composeAgent(presetId: string | undefined): Promise<
|
|
136
|
-
readonly agentPreset?: string;
|
|
137
|
-
readonly setup: AgentSetup;
|
|
138
|
-
}>;
|
|
170
|
+
composeAgent(presetId: string | undefined): Promise<ApiSessionComposition>;
|
|
139
171
|
private liveAgent;
|
|
140
172
|
private resume;
|
|
141
173
|
private resumeObserved;
|
|
142
|
-
private
|
|
174
|
+
private inOrder;
|
|
175
|
+
/** @returns the reused Session, a new Session under `sessionId` when none is stored, or a new Session under a minted identity. */
|
|
176
|
+
private claim;
|
|
177
|
+
/** @returns the live blank Session under the requested preset, or undefined when it cannot be reused. */
|
|
178
|
+
private reuseLive;
|
|
179
|
+
/** Setup that re-checks blankness under the acquired writer, then records a preset change before publication. */
|
|
180
|
+
private reuseSetup;
|
|
181
|
+
/** Whether no turn has started, by the predicate the preset registry's `select` enforces. */
|
|
182
|
+
private isBlank;
|
|
183
|
+
/** Without a roster, reuse cannot apply a named preset. */
|
|
184
|
+
private assertPresetApplicable;
|
|
185
|
+
private created;
|
|
186
|
+
private createMinted;
|
|
187
|
+
private createAgent;
|
|
143
188
|
private agentOptions;
|
|
144
189
|
private installSelection;
|
|
145
190
|
/**
|
|
@@ -148,7 +193,6 @@ export declare class ApiSessionAgentController {
|
|
|
148
193
|
* @returns the current preset, or undefined when the capability is absent.
|
|
149
194
|
*/
|
|
150
195
|
presetForObservation(observation: SessionObservation): string | undefined;
|
|
151
|
-
private assertPresetUnchanged;
|
|
152
196
|
}
|
|
153
197
|
export {};
|
|
154
198
|
//# sourceMappingURL=agent.d.ts.map
|
package/lib/types/agent.js
CHANGED
|
@@ -51,11 +51,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
var e = new Error(message);
|
|
52
52
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
53
|
});
|
|
54
|
+
import { randomUUID } from 'node:crypto';
|
|
54
55
|
import { mkdir } from 'node:fs/promises';
|
|
55
56
|
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
57
|
+
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
56
58
|
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
57
59
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
58
|
-
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
60
|
+
import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol';
|
|
59
61
|
/** Cold Session identity absent from persistence. */
|
|
60
62
|
export class ApiSessionNotFound extends Error {
|
|
61
63
|
}
|
|
@@ -82,20 +84,19 @@ export class ApiSessionCwdConflict extends Error {
|
|
|
82
84
|
this.existingCwd = existingCwd;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
|
-
/**
|
|
87
|
+
/** Reuse of a Session named an Agent preset while the deployment composes no preset roster. */
|
|
86
88
|
export class ApiSessionPresetConflict extends Error {
|
|
87
89
|
sessionId;
|
|
88
90
|
requestedPreset;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
super(existingPreset === undefined
|
|
92
|
-
? `session "${sessionId}" records no agent preset and cannot be adopted under "${requestedPreset}"`
|
|
93
|
-
: `session "${sessionId}" runs agent preset "${existingPreset}", not "${requestedPreset}"`);
|
|
91
|
+
constructor(sessionId, requestedPreset) {
|
|
92
|
+
super(`the deployment composes no agent preset roster, so session "${sessionId}" cannot run preset "${requestedPreset}"`);
|
|
94
93
|
this.sessionId = sessionId;
|
|
95
94
|
this.requestedPreset = requestedPreset;
|
|
96
|
-
this.existingPreset = existingPreset;
|
|
97
95
|
}
|
|
98
96
|
}
|
|
97
|
+
/** A blank Session chosen for reuse started a turn before this Host acquired its writer. */
|
|
98
|
+
class ApiSessionStartedSinceObserved extends Error {
|
|
99
|
+
}
|
|
99
100
|
/**
|
|
100
101
|
* Test whether generic Session routing must leave an identity to subagent routing.
|
|
101
102
|
* @param ctx - Host context carrying the Agent ownership registry.
|
|
@@ -164,7 +165,7 @@ export async function inspectApiSession(ctx, sessionId, signal) {
|
|
|
164
165
|
export class ApiSessionAgentController {
|
|
165
166
|
ctx;
|
|
166
167
|
resumes = new Map();
|
|
167
|
-
|
|
168
|
+
claims = new Map();
|
|
168
169
|
selections = new WeakMap();
|
|
169
170
|
imageAdmissionChains = new WeakMap();
|
|
170
171
|
/** @param ctx - Host context carrying Agent, model, persistence, and Typert services. */
|
|
@@ -248,45 +249,27 @@ export class ApiSessionAgentController {
|
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
252
|
+
* Create a Session for a new conversation, reusing the named blank Session
|
|
253
|
+
* while it is still reusable. A reused Session runs the requested preset, or
|
|
254
|
+
* the configured default when the request names none; the preset it recorded
|
|
255
|
+
* is not resolved, and a change is logged as `agent-preset/selected` before
|
|
256
|
+
* the Agent is published. A named Session that has started a turn, fails the
|
|
257
|
+
* caller's placement rule, or is written by another owner stays unchanged,
|
|
258
|
+
* and a new Session is created instead. Requests naming one identity run in
|
|
259
|
+
* arrival order, including their preset resolution, and check the named
|
|
260
|
+
* Session's directory and ownership before resolving the preset.
|
|
261
|
+
* @param request - optional Session to reuse, directory, preset, and placement rule.
|
|
262
|
+
* @returns the reused or created Session and the preset it ran when this request finished.
|
|
263
|
+
* @throws ApiSessionCwdConflict when the named Session belongs to another directory.
|
|
264
|
+
* @throws ApiSessionSubagentOwnership when subagent routing owns the named Session.
|
|
265
|
+
* @throws RemoteError `agent-preset/not-found` or `agent-preset/invalid` for the requested or default preset.
|
|
266
|
+
* @throws ApiSessionPresetConflict when no roster is composed and the request names a preset for a reused Session.
|
|
257
267
|
*/
|
|
258
|
-
async
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const live = this.ctx.agents.get(sessionId);
|
|
264
|
-
if (live !== undefined) {
|
|
265
|
-
if (hasApiSessionSubagentOwner(this.ctx, live.session, live)) {
|
|
266
|
-
throw new ApiSessionSubagentOwnership(sessionId);
|
|
267
|
-
}
|
|
268
|
-
return live;
|
|
269
|
-
}
|
|
270
|
-
const attached = this.ctx.sessions.get(sessionId);
|
|
271
|
-
if (attached !== undefined && hasApiSessionSubagentOwner(this.ctx, attached, undefined)) {
|
|
272
|
-
throw new ApiSessionSubagentOwnership(sessionId);
|
|
273
|
-
}
|
|
274
|
-
throw error;
|
|
275
|
-
})
|
|
276
|
-
.finally(() => { this.creations.delete(sessionId); });
|
|
277
|
-
this.creations.set(sessionId, creation);
|
|
278
|
-
}
|
|
279
|
-
const agent = await creation;
|
|
280
|
-
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
281
|
-
throw new ApiSessionSubagentOwnership(sessionId);
|
|
282
|
-
}
|
|
283
|
-
if (presetId !== undefined) {
|
|
284
|
-
this.assertPresetUnchanged(sessionId, presetId, this.presetForSession(agent.session));
|
|
285
|
-
}
|
|
286
|
-
if (agent.session.header.cwd !== cwd) {
|
|
287
|
-
throw new ApiSessionCwdConflict(sessionId, cwd, agent.session.header.cwd);
|
|
288
|
-
}
|
|
289
|
-
return agent;
|
|
268
|
+
async createSession(request) {
|
|
269
|
+
const named = request.sessionId;
|
|
270
|
+
if (named === undefined)
|
|
271
|
+
return this.created(await this.createMinted(request.cwd, await this.composeAgent(request.agentPreset)));
|
|
272
|
+
return this.inOrder(named, async () => this.created(await this.claim(named, request)));
|
|
290
273
|
}
|
|
291
274
|
/**
|
|
292
275
|
* Install or return the Session-local model selection used by prompt assembly.
|
|
@@ -451,55 +434,141 @@ export class ApiSessionAgentController {
|
|
|
451
434
|
setup: composition.setup,
|
|
452
435
|
})).agent;
|
|
453
436
|
}
|
|
454
|
-
async
|
|
437
|
+
async inOrder(sessionId, operation) {
|
|
438
|
+
const result = (this.claims.get(sessionId) ?? Promise.resolve()).then(operation);
|
|
439
|
+
const settled = result.then(() => undefined, () => undefined);
|
|
440
|
+
this.claims.set(sessionId, settled);
|
|
441
|
+
try {
|
|
442
|
+
return await result;
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
if (this.claims.get(sessionId) === settled)
|
|
446
|
+
this.claims.delete(sessionId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** @returns the reused Session, a new Session under `sessionId` when none is stored, or a new Session under a minted identity. */
|
|
450
|
+
async claim(sessionId, request) {
|
|
455
451
|
const attached = this.ctx.sessions.get(sessionId);
|
|
456
452
|
const live = this.ctx.agents.get(sessionId);
|
|
457
453
|
if (attached !== undefined && hasApiSessionSubagentOwner(this.ctx, attached, live)) {
|
|
458
454
|
throw new ApiSessionSubagentOwnership(sessionId);
|
|
459
455
|
}
|
|
460
|
-
if (live !== undefined)
|
|
461
|
-
|
|
462
|
-
|
|
456
|
+
if (live !== undefined) {
|
|
457
|
+
if (live.session.header.cwd !== request.cwd) {
|
|
458
|
+
throw new ApiSessionCwdConflict(live.id, request.cwd, live.session.header.cwd);
|
|
459
|
+
}
|
|
460
|
+
const composition = await this.composeAgent(request.agentPreset);
|
|
461
|
+
return await this.reuseLive(live, request, composition) ?? this.createMinted(request.cwd, composition);
|
|
462
|
+
}
|
|
463
|
+
let observation;
|
|
464
|
+
try {
|
|
465
|
+
observation = await this.ctx.sessionQuery.observeSession(sessionId);
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (!(error instanceof SessionQueryError) || error.code !== 'SESSION_QUERY_SESSION_NOT_FOUND')
|
|
469
|
+
throw error;
|
|
470
|
+
return this.createAgent(sessionId, request.cwd, await this.composeAgent(request.agentPreset));
|
|
471
|
+
}
|
|
472
|
+
let reusable;
|
|
473
|
+
{
|
|
474
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
463
475
|
try {
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (hasApiSessionSubagentOwner(this.ctx, { header: observation.header }, undefined)) {
|
|
468
|
-
throw new ApiSessionSubagentOwnership(sessionId);
|
|
469
|
-
}
|
|
470
|
-
if (observation.header.cwd !== cwd) {
|
|
471
|
-
throw new ApiSessionCwdConflict(sessionId, cwd, observation.header.cwd);
|
|
472
|
-
}
|
|
473
|
-
const storedPreset = this.presetForObservation(observation);
|
|
474
|
-
this.assertPresetUnchanged(sessionId, presetId, storedPreset);
|
|
475
|
-
const composition = await this.composeAgent(storedPreset);
|
|
476
|
-
return (await this.ctx.agents.resume({
|
|
477
|
-
resumeSessionId: sessionId,
|
|
478
|
-
agentOptions: this.agentOptions(),
|
|
479
|
-
setup: composition.setup,
|
|
480
|
-
})).agent;
|
|
481
|
-
}
|
|
482
|
-
catch (e_3) {
|
|
483
|
-
env_3.error = e_3;
|
|
484
|
-
env_3.hasError = true;
|
|
476
|
+
const observed = __addDisposableResource(env_3, observation, false);
|
|
477
|
+
if (hasApiSessionSubagentOwner(this.ctx, { header: observed.header }, undefined)) {
|
|
478
|
+
throw new ApiSessionSubagentOwnership(sessionId);
|
|
485
479
|
}
|
|
486
|
-
|
|
487
|
-
|
|
480
|
+
if (observed.header.cwd !== request.cwd) {
|
|
481
|
+
throw new ApiSessionCwdConflict(sessionId, request.cwd, observed.header.cwd);
|
|
488
482
|
}
|
|
483
|
+
reusable = request.reusable(sessionId) && !observed.events.some(event => event.type === 'turn/start');
|
|
484
|
+
}
|
|
485
|
+
catch (e_3) {
|
|
486
|
+
env_3.error = e_3;
|
|
487
|
+
env_3.hasError = true;
|
|
489
488
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|| error.code !== 'SESSION_QUERY_SESSION_NOT_FOUND')
|
|
493
|
-
throw error;
|
|
489
|
+
finally {
|
|
490
|
+
__disposeResources(env_3);
|
|
494
491
|
}
|
|
495
492
|
}
|
|
493
|
+
const composition = await this.composeAgent(request.agentPreset);
|
|
494
|
+
if (!reusable)
|
|
495
|
+
return this.createMinted(request.cwd, composition);
|
|
496
|
+
this.assertPresetApplicable(sessionId, request.agentPreset);
|
|
497
|
+
try {
|
|
498
|
+
return (await this.ctx.agents.resume({
|
|
499
|
+
resumeSessionId: sessionId,
|
|
500
|
+
agentOptions: this.agentOptions(),
|
|
501
|
+
setup: this.reuseSetup(composition),
|
|
502
|
+
})).agent;
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (error instanceof ApiSessionStartedSinceObserved)
|
|
506
|
+
return this.createMinted(request.cwd, composition);
|
|
507
|
+
if (!(error instanceof Error) || error.name !== 'SessionAlreadyOwnedError')
|
|
508
|
+
throw error;
|
|
509
|
+
// Another resume in this process may hold the writer for the Agent it published.
|
|
510
|
+
return this.ctx.agents.get(sessionId) === undefined
|
|
511
|
+
? this.createMinted(request.cwd, composition)
|
|
512
|
+
: this.claim(sessionId, request);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/** @returns the live blank Session under the requested preset, or undefined when it cannot be reused. */
|
|
516
|
+
async reuseLive(live, request, composition) {
|
|
517
|
+
if (!request.reusable(live.id) || !this.isBlank(live.session))
|
|
518
|
+
return undefined;
|
|
519
|
+
this.assertPresetApplicable(live.id, request.agentPreset);
|
|
520
|
+
const current = this.presetForSession(live.session);
|
|
521
|
+
const presets = this.ctx.get('agentPresets');
|
|
522
|
+
const target = composition.agentPreset;
|
|
523
|
+
if (presets === undefined || target === undefined || target === current)
|
|
524
|
+
return live;
|
|
525
|
+
try {
|
|
526
|
+
await presets.select(live, target);
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
if (remoteErrorOf(error)?.code !== 'agent-preset/locked')
|
|
530
|
+
throw error;
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
return live;
|
|
534
|
+
}
|
|
535
|
+
/** Setup that re-checks blankness under the acquired writer, then records a preset change before publication. */
|
|
536
|
+
reuseSetup(composition) {
|
|
537
|
+
return async (agentCtx, agent) => {
|
|
538
|
+
if (!this.isBlank(agent.session))
|
|
539
|
+
throw new ApiSessionStartedSinceObserved();
|
|
540
|
+
const commit = await composition.setup(agentCtx, agent);
|
|
541
|
+
const target = composition.agentPreset;
|
|
542
|
+
if (target !== undefined && this.presetForSession(agent.session) !== target) {
|
|
543
|
+
agent.session.append('agent-preset/selected', { agentPreset: target });
|
|
544
|
+
}
|
|
545
|
+
return commit;
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/** Whether no turn has started, by the predicate the preset registry's `select` enforces. */
|
|
549
|
+
isBlank(session) {
|
|
550
|
+
const boundary = this.ctx.sessionProjections.stateOf(session, 'turnBoundary');
|
|
551
|
+
return boundary === undefined || (boundary.openTurnStartSeq === null && boundary.lastTurn === 0);
|
|
552
|
+
}
|
|
553
|
+
/** Without a roster, reuse cannot apply a named preset. */
|
|
554
|
+
assertPresetApplicable(sessionId, requested) {
|
|
555
|
+
if (requested === undefined || this.ctx.get('agentPresets') !== undefined)
|
|
556
|
+
return;
|
|
557
|
+
throw new ApiSessionPresetConflict(sessionId, requested);
|
|
558
|
+
}
|
|
559
|
+
created(agent) {
|
|
560
|
+
return { agent, agentPreset: this.presetForSession(agent.session) };
|
|
561
|
+
}
|
|
562
|
+
createMinted(cwd, composition) {
|
|
563
|
+
return this.createAgent(brandString(`session-${randomUUID()}`), cwd, composition);
|
|
564
|
+
}
|
|
565
|
+
async createAgent(sessionId, cwd, composition) {
|
|
496
566
|
try {
|
|
497
567
|
await mkdir(cwd, { recursive: true });
|
|
498
568
|
}
|
|
499
569
|
catch (error) {
|
|
500
570
|
throw new Error(`failed to ensure project directory "${cwd}": ${String(error)}`, { cause: error });
|
|
501
571
|
}
|
|
502
|
-
const composition = await this.composeAgent(presetId);
|
|
503
572
|
return (await this.ctx.agents.create({
|
|
504
573
|
sessionId,
|
|
505
574
|
agentOptions: this.agentOptions(),
|
|
@@ -528,11 +597,6 @@ export class ApiSessionAgentController {
|
|
|
528
597
|
}
|
|
529
598
|
return observation.projections.values.agentPreset ?? undefined;
|
|
530
599
|
}
|
|
531
|
-
assertPresetUnchanged(sessionId, requested, existing) {
|
|
532
|
-
if (requested === undefined || requested === existing)
|
|
533
|
-
return;
|
|
534
|
-
throw new ApiSessionPresetConflict(sessionId, requested, existing);
|
|
535
|
-
}
|
|
536
600
|
}
|
|
537
601
|
function agentModelSelection(selection) {
|
|
538
602
|
return {
|
|
@@ -73,23 +73,26 @@ export interface ISessions {
|
|
|
73
73
|
*/
|
|
74
74
|
readonly searchResultLimit: number;
|
|
75
75
|
/**
|
|
76
|
-
* Create
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* Create a Session for a new conversation on the Host. The Host reuses the
|
|
77
|
+
* named blank Session while it is reusable and otherwise creates another; the
|
|
78
|
+
* Session runs the named preset, or the Host default when none is named.
|
|
79
|
+
* @param opts - target workspace or directory, an optional blank Session to reuse, and an optional Agent preset.
|
|
80
|
+
* @returns the catalogued identity the Host created or reused; retain it before borrowing its binding.
|
|
79
81
|
*/
|
|
80
82
|
create(opts?: {
|
|
81
83
|
workspaceId?: WorkspaceId;
|
|
82
84
|
cwd?: string;
|
|
83
85
|
sessionId?: SessionId;
|
|
86
|
+
agentPreset?: string;
|
|
84
87
|
}): Promise<SessionId>;
|
|
85
88
|
/**
|
|
86
89
|
* Resolve an already discovered direct-parent address without opening it.
|
|
87
90
|
* @param id - possible addressed child id.
|
|
88
|
-
* @returns a retained or
|
|
91
|
+
* @returns a retained, catalog-derived, or unknown-mode listed address, without retaining a new selection or scope.
|
|
89
92
|
*/
|
|
90
93
|
subagentAddress(id: SessionId): SubagentAddress | undefined;
|
|
91
94
|
/**
|
|
92
|
-
*
|
|
95
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
93
96
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
94
97
|
* @returns completion of the current or newly started refresh.
|
|
95
98
|
*/
|
|
@@ -126,6 +129,8 @@ export interface ISessions {
|
|
|
126
129
|
atSeq?: number;
|
|
127
130
|
increaseTitle?: boolean;
|
|
128
131
|
onCreated?: (childId: SessionId) => void;
|
|
132
|
+
/** False refuses source migration; omission preserves the Host default. */
|
|
133
|
+
allowMigration?: boolean;
|
|
129
134
|
}): Promise<SessionId>;
|
|
130
135
|
/**
|
|
131
136
|
* Borrow an already-retained Agent-scoped Context without extending its lifetime.
|
|
@@ -21,6 +21,8 @@ export interface SessionListEntry {
|
|
|
21
21
|
parentSessionId?: SessionId;
|
|
22
22
|
/** Coarse durable origin for navigation filtering; not a continuation capability. */
|
|
23
23
|
origin?: 'subagent';
|
|
24
|
+
/** Format classification supplied by the Host list. */
|
|
25
|
+
formatStatus?: SessionSummary['formatStatus'];
|
|
24
26
|
cwd?: string;
|
|
25
27
|
/** Current host-computed projection values for list consumers. */
|
|
26
28
|
projectionValues?: Readonly<Partial<SessionProjectionMap>>;
|
|
@@ -35,7 +35,7 @@ export interface SessionListSnapshot {
|
|
|
35
35
|
/** Shared projection values and the lifecycle of their explicit baseline read. */
|
|
36
36
|
export interface SessionProjectionSnapshot {
|
|
37
37
|
readonly values: Readonly<Partial<SessionProjectionMap>>;
|
|
38
|
-
readonly state: 'idle' | 'loading' | 'ready' | 'error';
|
|
38
|
+
readonly state: 'idle' | 'loading' | 'ready' | 'migration-required' | 'error';
|
|
39
39
|
readonly error: RemoteFailure | null;
|
|
40
40
|
}
|
|
41
41
|
/** Instance cluster + frame entry + the session list. */
|
|
@@ -86,8 +86,9 @@ export declare class SessionManager {
|
|
|
86
86
|
resolveTarget(target: SessionTarget): SessionId;
|
|
87
87
|
/**
|
|
88
88
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
89
|
-
* @param sessionId - possible child id in
|
|
90
|
-
* @returns A retained
|
|
89
|
+
* @param sessionId - possible child id in received Session metadata.
|
|
90
|
+
* @returns A retained address, then a catalog address, then an unknown-mode address
|
|
91
|
+
* from a listed subagent header; undefined without that evidence.
|
|
91
92
|
*/
|
|
92
93
|
subagentAddress(sessionId: SessionId): SubagentAddress | undefined;
|
|
93
94
|
/**
|
|
@@ -130,7 +131,7 @@ export declare class SessionManager {
|
|
|
130
131
|
/** Resident per-session projection store (create-on-demand; outlives instantiation). */
|
|
131
132
|
private projectionStore;
|
|
132
133
|
/**
|
|
133
|
-
*
|
|
134
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
134
135
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
135
136
|
* @returns completion of the current or newly started read.
|
|
136
137
|
*/
|
|
@@ -154,13 +155,14 @@ export declare class SessionManager {
|
|
|
154
155
|
* Contract session.create; on success merge into summaries immediately (no
|
|
155
156
|
* wait for the next refresh). A created session is blank by definition
|
|
156
157
|
* (entity birth precedes the first message).
|
|
157
|
-
* @param opts - target workspace or working directory,
|
|
158
|
-
* @returns the create result.
|
|
158
|
+
* @param opts - target workspace or working directory, an optional blank Session to reuse, and an optional Agent preset.
|
|
159
|
+
* @returns the create result, naming the Session the Host created or reused.
|
|
159
160
|
*/
|
|
160
161
|
create(opts?: {
|
|
161
162
|
workspaceId?: WorkspaceId;
|
|
162
163
|
cwd?: string;
|
|
163
164
|
sessionId?: SessionId;
|
|
165
|
+
agentPreset?: string;
|
|
164
166
|
}): Promise<RemoteResult<{
|
|
165
167
|
sessionId: SessionId;
|
|
166
168
|
}>>;
|
|
@@ -171,12 +173,13 @@ export declare class SessionManager {
|
|
|
171
173
|
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
172
174
|
* lineage rides parentSessionId. A child published before Workspace
|
|
173
175
|
* attachment fails is also reconciled into the list.
|
|
174
|
-
* @param opts - source session
|
|
176
|
+
* @param opts - source session, optional exact inclusive boundary seq, and permission to start migration.
|
|
175
177
|
* @returns the fork result (the child session id).
|
|
176
178
|
*/
|
|
177
179
|
fork(opts: {
|
|
178
180
|
sessionId: SessionId;
|
|
179
181
|
atSeq?: SessionSeq;
|
|
182
|
+
allowMigration?: boolean;
|
|
180
183
|
}): Promise<RemoteResult<{
|
|
181
184
|
sessionId: SessionId;
|
|
182
185
|
}>>;
|