@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.2 → 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 +31 -8
- package/README.zh.md +31 -8
- package/lib/client.js +603 -515
- package/lib/index.js +299 -137
- package/lib/typert.host.js +373 -183
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +216 -124
- 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 +11 -16
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/sessions/lineage.d.ts +4 -1
- package/lib/types/client/sessions/lineage.js +2 -1
- package/lib/types/client/sessions/manager.d.ts +66 -59
- package/lib/types/client/sessions/manager.js +249 -277
- package/lib/types/client/sessions/projection-store.d.ts +32 -13
- package/lib/types/client/sessions/projection-store.js +45 -13
- package/lib/types/client/sessions/remotes.d.ts +1 -2
- package/lib/types/client/sessions/service.d.ts +23 -35
- package/lib/types/client/sessions/service.js +35 -47
- package/lib/types/client/sessions/session.d.ts +18 -16
- package/lib/types/client/sessions/session.js +112 -50
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.js +72 -36
- package/lib/types/control.d.ts +2 -4
- package/lib/types/control.js +2 -44
- package/lib/types/history.js +1 -1
- package/lib/types/index.d.ts +32 -5
- package/lib/types/index.js +117 -13
- package/lib/types/list.js +24 -15
- package/lib/types/skill-catalog.js +60 -50
- package/lib/types/types.d.ts +37 -23
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
package/lib/types/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** Session Remote owner: cold reads, explicit Agent commands, and live control state. */
|
|
2
2
|
import { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import { nativeFileApplications, openNativeFileApplication } from '@deepseek-ai/dsh-native-command';
|
|
4
5
|
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
5
6
|
import type { SessionInspection } from '@deepseek-ai/dsh-session-persistence';
|
|
6
7
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
7
8
|
import { type ApiSessionAgentResult } from './agent.ts';
|
|
8
9
|
import { buildModelCatalog } from './catalog.ts';
|
|
9
|
-
import type { ModelCatalog, SessionAttachmentRequest, SessionAttachmentValue, SessionCancelRequest, SessionCancelValue, SessionControlFrame, SessionCreateRequest, SessionCreateValue, SessionFollowFrame, SessionFollowRequest, SessionForkRequest, SessionForkValue, SessionListRequest, SessionListValue, SessionOpenWorkspacePathRequest, SessionOpenWorkspacePathValue, SessionPage, SessionPageRequest, SessionPromptRequest, SessionPromptValue, SessionRenameRequest, SessionRenameValue, SessionSearchRequest, SessionSearchValue, SessionSelectModelRequest, SessionSelectModelValue, SessionUpdateQueueRequest, SessionUpdateQueueValue } from './types.ts';
|
|
10
|
+
import type { ModelCatalog, SessionWorkspacePathApplication, SessionAttachmentRequest, SessionAttachmentValue, SessionCancelRequest, SessionCancelValue, SessionControlFrame, SessionCreateRequest, SessionCreateValue, SessionFollowFrame, SessionFollowRequest, SessionForkRequest, SessionForkValue, SessionListRequest, SessionListValue, SessionOpenWorkspacePathRequest, SessionOpenWorkspacePathValue, SessionPage, SessionPageRequest, SessionPromptRequest, SessionPromptValue, SessionRenameRequest, SessionRenameValue, SessionSearchRequest, SessionSearchValue, SessionSelectModelRequest, SessionSelectModelValue, SessionProjectionsRequest, SessionProjectionsValue, SessionUpdateQueueRequest, SessionUpdateQueueValue } from './types.ts';
|
|
10
11
|
export type * from './types.ts';
|
|
11
12
|
export { ApiSessionNotFound } from './agent.ts';
|
|
12
13
|
export { SessionFileReferences } from './file-references.ts';
|
|
@@ -26,6 +27,10 @@ export interface Config {
|
|
|
26
27
|
export interface SessionControllerInternals {
|
|
27
28
|
/** Native default-application handoff. */
|
|
28
29
|
readonly openPath?: (path: string, signal: AbortSignal) => Promise<void>;
|
|
30
|
+
/** Native file-association query. */
|
|
31
|
+
readonly fileApplications?: typeof nativeFileApplications;
|
|
32
|
+
/** Explicit registered-application handoff. */
|
|
33
|
+
readonly openFileApplication?: typeof openNativeFileApplication;
|
|
29
34
|
/** Native file-manager handoff. */
|
|
30
35
|
readonly revealPath?: (path: string, signal: AbortSignal) => Promise<void>;
|
|
31
36
|
/** Native handoff availability probe. */
|
|
@@ -41,6 +46,8 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
41
46
|
private readonly history;
|
|
42
47
|
private readonly listState;
|
|
43
48
|
private readonly openPath;
|
|
49
|
+
private readonly fileApplications;
|
|
50
|
+
private readonly openFileApplication;
|
|
44
51
|
private readonly revealPath;
|
|
45
52
|
private readonly canOpenPath;
|
|
46
53
|
private readonly promotions;
|
|
@@ -110,13 +117,24 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
110
117
|
fileManager: 'finder' | 'explorer' | 'directory' | null;
|
|
111
118
|
};
|
|
112
119
|
/**
|
|
113
|
-
*
|
|
120
|
+
* Verify one path through the composed filesystem and open it on the Host desktop.
|
|
114
121
|
* @param request - path after best-effort Session workspace resolution.
|
|
115
122
|
* @param signal - caller lifetime; abort terminates the native command.
|
|
116
123
|
* @returns confirmation after the native opener accepts the path.
|
|
117
|
-
* @throws RemoteError when the request is invalid, cancelled, or the opener fails.
|
|
124
|
+
* @throws RemoteError when the request is invalid, has no verified Host mapping, is cancelled, or the opener fails.
|
|
118
125
|
*/
|
|
119
126
|
openWorkspacePath(request: SessionOpenWorkspacePathRequest, signal: AbortSignal): Promise<SessionOpenWorkspacePathValue>;
|
|
127
|
+
/**
|
|
128
|
+
* Query current file handlers on the serving desktop without activating an Agent.
|
|
129
|
+
* @param request - file path in Host filesystem syntax.
|
|
130
|
+
* @param signal - caller lifetime, propagated to filesystem and desktop queries.
|
|
131
|
+
* @returns OS application names, icons, and default selection; empty when desktop opening is unavailable.
|
|
132
|
+
* @throws RemoteError when the path is invalid, the query is cancelled, or native discovery fails.
|
|
133
|
+
*/
|
|
134
|
+
workspacePathApplications(request: {
|
|
135
|
+
readonly path: string;
|
|
136
|
+
}, signal: AbortSignal): Promise<readonly SessionWorkspacePathApplication[]>;
|
|
137
|
+
private verifyDesktopPath;
|
|
120
138
|
/**
|
|
121
139
|
* Rename one Session after explicitly resuming it.
|
|
122
140
|
* @param request - Session identity and proposed title.
|
|
@@ -124,8 +142,10 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
124
142
|
*/
|
|
125
143
|
rename(request: SessionRenameRequest): Promise<SessionRenameValue>;
|
|
126
144
|
/**
|
|
127
|
-
* Fork one cold-readable
|
|
128
|
-
*
|
|
145
|
+
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
146
|
+
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
147
|
+
* synthetic fork closers.
|
|
148
|
+
* @param request - source Session and optional exact inclusive event boundary.
|
|
129
149
|
* @returns the new Session identity.
|
|
130
150
|
*/
|
|
131
151
|
fork(request: SessionForkRequest): Promise<SessionForkValue>;
|
|
@@ -169,6 +189,13 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
169
189
|
* frames and optional cursorless assistant-stream frames.
|
|
170
190
|
*/
|
|
171
191
|
follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
|
|
192
|
+
/**
|
|
193
|
+
* Read all registered projections without activating an Agent.
|
|
194
|
+
* @param request - Session whose current values are required.
|
|
195
|
+
* @param signal - cancellation for the Session observation.
|
|
196
|
+
* @returns complete baseline, or null when the Session does not exist.
|
|
197
|
+
*/
|
|
198
|
+
projections(request: SessionProjectionsRequest, signal: AbortSignal): Promise<SessionProjectionsValue>;
|
|
172
199
|
/**
|
|
173
200
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
174
201
|
* @param signal - cancellation owned by the Remote stream carrier.
|
package/lib/types/index.js
CHANGED
|
@@ -86,9 +86,11 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
86
86
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
87
87
|
});
|
|
88
88
|
import { hostname } from 'node:os';
|
|
89
|
+
import { resolve } from 'node:path';
|
|
89
90
|
import z from '@deepseek-ai/schemastery';
|
|
90
91
|
import { errorChain } from '@deepseek-ai/dsh-llm';
|
|
91
|
-
import { canOpenNativePath, nativeFileManager,
|
|
92
|
+
import { canOpenNativePath, nativeFileManager, nativeFileApplications, openNativeFileApplication, openNativeAssociatedPath, revealNativePath } from '@deepseek-ai/dsh-native-command';
|
|
93
|
+
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
92
94
|
import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
93
95
|
import { ApiSessionAgentController, inspectApiSession, } from "./agent.js";
|
|
94
96
|
import { SessionCommandController } from "./commands.js";
|
|
@@ -100,6 +102,7 @@ import { buildModelCatalog } from "./catalog.js";
|
|
|
100
102
|
import { installModelSelectionProjection } from "./model-selection-projection.js";
|
|
101
103
|
import { SessionSkillCatalog } from "./skill-catalog.js";
|
|
102
104
|
import { SessionMediaReferences } from "./media-references.js";
|
|
105
|
+
import { ArchivedSessionGate } from "./archived-session-gate.js";
|
|
103
106
|
export { ApiSessionNotFound } from "./agent.js";
|
|
104
107
|
export { SessionFileReferences } from "./file-references.js";
|
|
105
108
|
export { SessionSkillCatalog } from "./skill-catalog.js";
|
|
@@ -114,6 +117,7 @@ let SessionController = (() => {
|
|
|
114
117
|
let _modelCatalog_decorators;
|
|
115
118
|
let _canOpenWorkspacePath_decorators;
|
|
116
119
|
let _openWorkspacePath_decorators;
|
|
120
|
+
let _workspacePathApplications_decorators;
|
|
117
121
|
let _rename_decorators;
|
|
118
122
|
let _fork_decorators;
|
|
119
123
|
let _prompt_decorators;
|
|
@@ -122,6 +126,7 @@ let SessionController = (() => {
|
|
|
122
126
|
let _cancel_decorators;
|
|
123
127
|
let _page_decorators;
|
|
124
128
|
let _follow_decorators;
|
|
129
|
+
let _projections_decorators;
|
|
125
130
|
let _control_decorators;
|
|
126
131
|
return class SessionController extends _classSuper {
|
|
127
132
|
static {
|
|
@@ -133,6 +138,7 @@ let SessionController = (() => {
|
|
|
133
138
|
_modelCatalog_decorators = [Remote('modelCatalog')];
|
|
134
139
|
_canOpenWorkspacePath_decorators = [Remote];
|
|
135
140
|
_openWorkspacePath_decorators = [Remote('openWorkspacePath')];
|
|
141
|
+
_workspacePathApplications_decorators = [Remote('workspacePathApplications')];
|
|
136
142
|
_rename_decorators = [Remote('rename')];
|
|
137
143
|
_fork_decorators = [Remote('fork')];
|
|
138
144
|
_prompt_decorators = [Remote('prompt')];
|
|
@@ -141,6 +147,7 @@ let SessionController = (() => {
|
|
|
141
147
|
_cancel_decorators = [Remote('cancel')];
|
|
142
148
|
_page_decorators = [Remote('page')];
|
|
143
149
|
_follow_decorators = [Remote({ mode: 'stream' })];
|
|
150
|
+
_projections_decorators = [Remote('projections')];
|
|
144
151
|
_control_decorators = [Remote({ mode: 'stream' })];
|
|
145
152
|
__esDecorate(this, null, _list_decorators, { kind: "method", name: "list", static: false, private: false, access: { has: obj => "list" in obj, get: obj => obj.list }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
146
153
|
__esDecorate(this, null, _search_decorators, { kind: "method", name: "search", static: false, private: false, access: { has: obj => "search" in obj, get: obj => obj.search }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -149,6 +156,7 @@ let SessionController = (() => {
|
|
|
149
156
|
__esDecorate(this, null, _modelCatalog_decorators, { kind: "method", name: "modelCatalog", static: false, private: false, access: { has: obj => "modelCatalog" in obj, get: obj => obj.modelCatalog }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
150
157
|
__esDecorate(this, null, _canOpenWorkspacePath_decorators, { kind: "method", name: "canOpenWorkspacePath", static: false, private: false, access: { has: obj => "canOpenWorkspacePath" in obj, get: obj => obj.canOpenWorkspacePath }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
151
158
|
__esDecorate(this, null, _openWorkspacePath_decorators, { kind: "method", name: "openWorkspacePath", static: false, private: false, access: { has: obj => "openWorkspacePath" in obj, get: obj => obj.openWorkspacePath }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
159
|
+
__esDecorate(this, null, _workspacePathApplications_decorators, { kind: "method", name: "workspacePathApplications", static: false, private: false, access: { has: obj => "workspacePathApplications" in obj, get: obj => obj.workspacePathApplications }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
152
160
|
__esDecorate(this, null, _rename_decorators, { kind: "method", name: "rename", static: false, private: false, access: { has: obj => "rename" in obj, get: obj => obj.rename }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
153
161
|
__esDecorate(this, null, _fork_decorators, { kind: "method", name: "fork", static: false, private: false, access: { has: obj => "fork" in obj, get: obj => obj.fork }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
154
162
|
__esDecorate(this, null, _prompt_decorators, { kind: "method", name: "prompt", static: false, private: false, access: { has: obj => "prompt" in obj, get: obj => obj.prompt }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -157,6 +165,7 @@ let SessionController = (() => {
|
|
|
157
165
|
__esDecorate(this, null, _cancel_decorators, { kind: "method", name: "cancel", static: false, private: false, access: { has: obj => "cancel" in obj, get: obj => obj.cancel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
158
166
|
__esDecorate(this, null, _page_decorators, { kind: "method", name: "page", static: false, private: false, access: { has: obj => "page" in obj, get: obj => obj.page }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
159
167
|
__esDecorate(this, null, _follow_decorators, { kind: "method", name: "follow", static: false, private: false, access: { has: obj => "follow" in obj, get: obj => obj.follow }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
168
|
+
__esDecorate(this, null, _projections_decorators, { kind: "method", name: "projections", static: false, private: false, access: { has: obj => "projections" in obj, get: obj => obj.projections }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
160
169
|
__esDecorate(this, null, _control_decorators, { kind: "method", name: "control", static: false, private: false, access: { has: obj => "control" in obj, get: obj => obj.control }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
161
170
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
162
171
|
}
|
|
@@ -165,6 +174,7 @@ let SessionController = (() => {
|
|
|
165
174
|
'agents',
|
|
166
175
|
'attachments',
|
|
167
176
|
'fileUploads',
|
|
177
|
+
'fs',
|
|
168
178
|
'llm',
|
|
169
179
|
'sessions',
|
|
170
180
|
'sessionProjections',
|
|
@@ -181,6 +191,8 @@ let SessionController = (() => {
|
|
|
181
191
|
history;
|
|
182
192
|
listState;
|
|
183
193
|
openPath;
|
|
194
|
+
fileApplications;
|
|
195
|
+
openFileApplication;
|
|
184
196
|
revealPath;
|
|
185
197
|
canOpenPath;
|
|
186
198
|
promotions = new Set();
|
|
@@ -208,19 +220,32 @@ let SessionController = (() => {
|
|
|
208
220
|
}, 'session-controller.promotions');
|
|
209
221
|
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation); });
|
|
210
222
|
this.listState = new ApiSessionList(ctx);
|
|
211
|
-
this.
|
|
223
|
+
this.fileApplications = internals.fileApplications ?? nativeFileApplications;
|
|
224
|
+
this.openFileApplication = internals.openFileApplication ?? openNativeFileApplication;
|
|
225
|
+
this.openPath = internals.openPath ?? openNativeAssociatedPath;
|
|
212
226
|
this.revealPath = internals.revealPath ?? revealNativePath;
|
|
213
227
|
this.canOpenPath = internals.canOpenPath
|
|
214
228
|
?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()));
|
|
215
229
|
ctx.plugin(SessionFileReferences);
|
|
216
230
|
ctx.plugin(SessionMediaReferences);
|
|
217
231
|
ctx.plugin(SessionSkillCatalog);
|
|
232
|
+
// An archived Session, or a subagent descendant of one, runs no model step
|
|
233
|
+
// until it is restored; what it still runs is stopped by the owners that
|
|
234
|
+
// answer the Workspace registry's archive-admission events.
|
|
235
|
+
ctx.plugin(ArchivedSessionGate);
|
|
218
236
|
ctx.on('session/created', (session) => {
|
|
219
237
|
ctx.emit('api-session/added', this.listState.summaryFor(session));
|
|
220
238
|
});
|
|
221
239
|
ctx.on('session/disposed', (session) => {
|
|
222
240
|
ctx.emit('api-session/removed', session.id);
|
|
223
241
|
});
|
|
242
|
+
const publishAgentAvailability = ({ agent }) => {
|
|
243
|
+
if (ctx.sessions.get(agent.id) === agent.session) {
|
|
244
|
+
ctx.emit('api-session/added', this.listState.summaryFor(agent.session));
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
ctx.on('agent/created', publishAgentAvailability);
|
|
248
|
+
ctx.on('agent/disposed', publishAgentAvailability);
|
|
224
249
|
ctx.on('agent/status', ({ agent, status }) => {
|
|
225
250
|
ctx.emit('api-session/status', agent.id, status === 'running');
|
|
226
251
|
});
|
|
@@ -344,29 +369,65 @@ let SessionController = (() => {
|
|
|
344
369
|
return { name: hostname(), available: fileManager !== null && this.canOpenPath(), fileManager };
|
|
345
370
|
}
|
|
346
371
|
/**
|
|
347
|
-
*
|
|
372
|
+
* Verify one path through the composed filesystem and open it on the Host desktop.
|
|
348
373
|
* @param request - path after best-effort Session workspace resolution.
|
|
349
374
|
* @param signal - caller lifetime; abort terminates the native command.
|
|
350
375
|
* @returns confirmation after the native opener accepts the path.
|
|
351
|
-
* @throws RemoteError when the request is invalid, cancelled, or the opener fails.
|
|
376
|
+
* @throws RemoteError when the request is invalid, has no verified Host mapping, is cancelled, or the opener fails.
|
|
352
377
|
*/
|
|
353
378
|
async openWorkspacePath(request, signal) {
|
|
354
|
-
if (request.path.length === 0) {
|
|
355
|
-
throw new RemoteError('gateway/bad-request', 'session.openWorkspacePath requires a non-empty path', {});
|
|
356
|
-
}
|
|
357
|
-
signal.throwIfAborted();
|
|
358
379
|
try {
|
|
380
|
+
const path = await this.verifyDesktopPath(request.path, signal);
|
|
359
381
|
if (request.action === 'reveal')
|
|
360
|
-
await this.revealPath(
|
|
382
|
+
await this.revealPath(path, signal);
|
|
383
|
+
else if (request.application !== undefined)
|
|
384
|
+
await this.openFileApplication(path, request.application, signal);
|
|
361
385
|
else
|
|
362
|
-
await this.openPath(
|
|
386
|
+
await this.openPath(path, signal);
|
|
363
387
|
return { opened: true };
|
|
364
388
|
}
|
|
365
389
|
catch (error) {
|
|
366
390
|
if (signal.aborted)
|
|
367
391
|
throw new RemoteError('gateway/cancelled', 'path open was aborted', {});
|
|
368
|
-
|
|
392
|
+
if (error instanceof RemoteError)
|
|
393
|
+
throw error;
|
|
394
|
+
throw new RemoteError('gateway/internal', 'path open failed', {}, { cause: error });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Query current file handlers on the serving desktop without activating an Agent.
|
|
399
|
+
* @param request - file path in Host filesystem syntax.
|
|
400
|
+
* @param signal - caller lifetime, propagated to filesystem and desktop queries.
|
|
401
|
+
* @returns OS application names, icons, and default selection; empty when desktop opening is unavailable.
|
|
402
|
+
* @throws RemoteError when the path is invalid, the query is cancelled, or native discovery fails.
|
|
403
|
+
*/
|
|
404
|
+
async workspacePathApplications(request, signal) {
|
|
405
|
+
if (!this.canOpenPath())
|
|
406
|
+
return [];
|
|
407
|
+
try {
|
|
408
|
+
const path = await this.verifyDesktopPath(request.path, signal);
|
|
409
|
+
return await this.fileApplications(path, signal);
|
|
369
410
|
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (signal.aborted)
|
|
413
|
+
throw new RemoteError('gateway/cancelled', 'application query was aborted', {});
|
|
414
|
+
if (error instanceof RemoteError)
|
|
415
|
+
throw error;
|
|
416
|
+
throw new RemoteError('gateway/internal', 'file application query failed', {}, { cause: error });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async verifyDesktopPath(path, signal) {
|
|
420
|
+
if (path.length === 0)
|
|
421
|
+
throw new RemoteError('gateway/bad-request', 'A non-empty file path is required', {});
|
|
422
|
+
signal.throwIfAborted();
|
|
423
|
+
const hostPath = resolve(path);
|
|
424
|
+
const { fs } = this.ctx;
|
|
425
|
+
const mapped = fs.processPathFromHostPath(hostPath);
|
|
426
|
+
if (mapped === undefined || fs.processPath(await fs.resolve(mapped, { signal })) !== hostPath) {
|
|
427
|
+
throw new RemoteError('gateway/bad-request', 'Path has no verified Host path', {});
|
|
428
|
+
}
|
|
429
|
+
signal.throwIfAborted();
|
|
430
|
+
return hostPath;
|
|
370
431
|
}
|
|
371
432
|
/**
|
|
372
433
|
* Rename one Session after explicitly resuming it.
|
|
@@ -377,8 +438,10 @@ let SessionController = (() => {
|
|
|
377
438
|
return this.commands.rename(request);
|
|
378
439
|
}
|
|
379
440
|
/**
|
|
380
|
-
* Fork one cold-readable
|
|
381
|
-
*
|
|
441
|
+
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
442
|
+
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
443
|
+
* synthetic fork closers.
|
|
444
|
+
* @param request - source Session and optional exact inclusive event boundary.
|
|
382
445
|
* @returns the new Session identity.
|
|
383
446
|
*/
|
|
384
447
|
fork(request) {
|
|
@@ -437,6 +500,47 @@ let SessionController = (() => {
|
|
|
437
500
|
follow(request, signal) {
|
|
438
501
|
return this.history.follow(request, signal);
|
|
439
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Read all registered projections without activating an Agent.
|
|
505
|
+
* @param request - Session whose current values are required.
|
|
506
|
+
* @param signal - cancellation for the Session observation.
|
|
507
|
+
* @returns complete baseline, or null when the Session does not exist.
|
|
508
|
+
*/
|
|
509
|
+
async projections(request, signal) {
|
|
510
|
+
const { sessionId } = request;
|
|
511
|
+
if (sessionId.length === 0) {
|
|
512
|
+
throw new RemoteError('gateway/bad-request', 'sessionId must not be empty', {});
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
516
|
+
try {
|
|
517
|
+
const observation = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId, { signal }), false);
|
|
518
|
+
const projections = observation.projections;
|
|
519
|
+
if (projections === undefined) {
|
|
520
|
+
throw new RemoteError('session/projections-unavailable', 'Session projections are unavailable', {});
|
|
521
|
+
}
|
|
522
|
+
return { asOfSeq: projections.asOfSeq, values: projections.values };
|
|
523
|
+
}
|
|
524
|
+
catch (e_2) {
|
|
525
|
+
env_2.error = e_2;
|
|
526
|
+
env_2.hasError = true;
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
__disposeResources(env_2);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
if (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND')
|
|
534
|
+
return null;
|
|
535
|
+
if (signal.aborted
|
|
536
|
+
|| (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_ABORTED')) {
|
|
537
|
+
throw new RemoteError('gateway/cancelled', 'Session projection read was cancelled', {}, { cause: error });
|
|
538
|
+
}
|
|
539
|
+
if (error instanceof RemoteError)
|
|
540
|
+
throw error;
|
|
541
|
+
throw new RemoteError('gateway/internal', 'Session projection read failed', {}, { cause: error });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
440
544
|
/**
|
|
441
545
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
442
546
|
* @param signal - cancellation owned by the Remote stream carrier.
|
package/lib/types/list.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** Cold-safe Session list and search projection. */
|
|
2
|
-
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
3
2
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
4
3
|
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
5
4
|
import { z } from 'zod';
|
|
@@ -90,6 +89,7 @@ export class ApiSessionList {
|
|
|
90
89
|
return {
|
|
91
90
|
sessionId: session.id,
|
|
92
91
|
updatedAt: updatedAt(session.header, metadata),
|
|
92
|
+
agentAvailable: this.ctx.agents.get(session.id)?.session === session,
|
|
93
93
|
running: this.ctx.agents.get(session.id)?.status === 'running',
|
|
94
94
|
blank: metadata?.blank ?? session.seq === 0,
|
|
95
95
|
...listFields(session.header),
|
|
@@ -128,6 +128,7 @@ export class ApiSessionList {
|
|
|
128
128
|
return {
|
|
129
129
|
sessionId: header.id,
|
|
130
130
|
updatedAt: updatedAt(header, metadata),
|
|
131
|
+
agentAvailable: false,
|
|
131
132
|
running: false,
|
|
132
133
|
// A large, metadata-less, or inaccessible cache miss remains unknown and visible.
|
|
133
134
|
blank: metadata?.blank ?? false,
|
|
@@ -246,21 +247,16 @@ export class ApiSessionList {
|
|
|
246
247
|
}
|
|
247
248
|
projectionsFor(header, session) {
|
|
248
249
|
try {
|
|
250
|
+
if (session !== undefined) {
|
|
251
|
+
// The live registry computed the block for this Session: its watermark
|
|
252
|
+
// shares the sequence space of the Session's baselines and frames.
|
|
253
|
+
return hintsOf('sequenced', this.ctx.sessionProjections.cachedSnapshot(session));
|
|
254
|
+
}
|
|
255
|
+
// A cold row reads the persisted cache by header alone; the cache serves
|
|
256
|
+
// seeded and unseeded lifecycles alike because a listing never seeds a
|
|
257
|
+
// fold. The watermark is the stored record's own.
|
|
249
258
|
const cache = this.ctx.get('sessionProjectionCache');
|
|
250
|
-
|
|
251
|
-
? header.isSeeded
|
|
252
|
-
? undefined
|
|
253
|
-
: cache?.cachedSnapshot(header, SessionLogOffset(0))
|
|
254
|
-
?? cache?.cachedPredecessorTitle(header, SessionLogOffset(0))
|
|
255
|
-
: this.ctx.sessionProjections.cachedSnapshot(session);
|
|
256
|
-
return block !== undefined && Object.keys(block.values).length > 0
|
|
257
|
-
? {
|
|
258
|
-
asOfSeq: block.asOfSeq,
|
|
259
|
-
// Listing hints contain every currently cached wire value but remain
|
|
260
|
-
// partial: missing cells and cache rows are never materialized here.
|
|
261
|
-
values: block.values,
|
|
262
|
-
}
|
|
263
|
-
: undefined;
|
|
259
|
+
return hintsOf('cached', cache?.cachedSnapshot(header) ?? cache?.cachedPredecessorTitle(header));
|
|
264
260
|
}
|
|
265
261
|
catch (error) {
|
|
266
262
|
this.ctx.logger.warn(`api-session.list: projection column for "${header.id}" failed; serving the row without it: ${String(error)}`);
|
|
@@ -268,6 +264,19 @@ export class ApiSessionList {
|
|
|
268
264
|
}
|
|
269
265
|
}
|
|
270
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Wrap one projection block as Session-list hints of the named sequence space.
|
|
269
|
+
* @param kind - which sequence space the block's watermark belongs to.
|
|
270
|
+
* @param block - the block, or `undefined` when no source served one.
|
|
271
|
+
* @returns the hints, or `undefined` when the block is absent or carries no value.
|
|
272
|
+
*/
|
|
273
|
+
function hintsOf(kind, block) {
|
|
274
|
+
if (block === undefined || Object.keys(block.values).length === 0)
|
|
275
|
+
return undefined;
|
|
276
|
+
// Listing hints contain every wire value the source currently holds but
|
|
277
|
+
// remain partial: missing cells and cache rows are never materialized here.
|
|
278
|
+
return { kind, asOfSeq: block.asOfSeq, values: block.values };
|
|
279
|
+
}
|
|
271
280
|
function normalizeSearchQuery(query) {
|
|
272
281
|
const normalized = query.trim();
|
|
273
282
|
if (normalized.length === 0) {
|
|
@@ -114,72 +114,82 @@ let SessionSkillCatalog = (() => {
|
|
|
114
114
|
* @throws RemoteError when the Session cannot be inspected or no registry can serve it.
|
|
115
115
|
*/
|
|
116
116
|
async list(request, signal) {
|
|
117
|
-
void
|
|
118
|
-
const { sessionId } = request;
|
|
119
|
-
let cwd;
|
|
120
|
-
let agentPreset;
|
|
117
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
121
118
|
try {
|
|
122
|
-
|
|
119
|
+
void signal;
|
|
120
|
+
const { sessionId } = request;
|
|
121
|
+
let cwd;
|
|
122
|
+
let agentPreset;
|
|
123
123
|
try {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
125
|
+
try {
|
|
126
|
+
const observation = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
127
|
+
if (observation.projections === undefined) {
|
|
128
|
+
throw new Error('skill catalog requires a projected Session observation');
|
|
129
|
+
}
|
|
130
|
+
cwd = observation.header.cwd;
|
|
131
|
+
agentPreset = observation.projections.values.agentPreset ?? undefined;
|
|
132
|
+
}
|
|
133
|
+
catch (e_1) {
|
|
134
|
+
env_2.error = e_1;
|
|
135
|
+
env_2.hasError = true;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
__disposeResources(env_2);
|
|
127
139
|
}
|
|
128
|
-
cwd = observation.header.cwd;
|
|
129
|
-
agentPreset = observation.projections.values.agentPreset ?? undefined;
|
|
130
140
|
}
|
|
131
|
-
catch (
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error instanceof SessionQueryError
|
|
143
|
+
&& error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') {
|
|
144
|
+
throw new RemoteError('session/not-found', `session "${sessionId}" not found`, { sessionId });
|
|
145
|
+
}
|
|
146
|
+
throw new RemoteError('gateway/internal', `session "${sessionId}" could not be inspected: ${String(error)}`, {});
|
|
134
147
|
}
|
|
135
|
-
|
|
136
|
-
|
|
148
|
+
if (cwd === undefined) {
|
|
149
|
+
throw new RemoteError('gateway/internal', `session "${sessionId}" has no project cwd`, {});
|
|
137
150
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
const live = this.ctx.agents.get(sessionId);
|
|
152
|
+
const presets = this.ctx.get('agentPresets');
|
|
153
|
+
const scoped = live === undefined ? undefined : presets?.serviceFor(live, 'skills');
|
|
154
|
+
const skillRegistry = scoped ?? this.ctx.get('skills');
|
|
155
|
+
if (skillRegistry === undefined) {
|
|
156
|
+
throw new RemoteError('gateway/internal', 'skill registry is absent: neither this session\'s agent preset nor the host composition mounts @deepseek-ai/dsh-skill', {});
|
|
157
|
+
}
|
|
158
|
+
const lease = __addDisposableResource(env_1, live === undefined ? await this.scopeFor(agentPreset) : undefined, true);
|
|
159
|
+
const scope = live ?? lease?.key;
|
|
160
|
+
try {
|
|
161
|
+
const skills = (await skillRegistry.list({ cwd, scope })).filter(isUserInvocable);
|
|
162
|
+
return {
|
|
163
|
+
skills: skills.map(skill => ({
|
|
164
|
+
name: skill.name,
|
|
165
|
+
...skill.path === undefined ? {} : { path: skill.path },
|
|
166
|
+
description: skill.description,
|
|
167
|
+
...skill.whenToUse === undefined ? {} : { whenToUse: skill.whenToUse },
|
|
168
|
+
modelInvocable: skill.invocation.modelInvocable,
|
|
169
|
+
})),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
throw new RemoteError('gateway/internal', `skill listing failed: ${String(error)}`, {});
|
|
143
174
|
}
|
|
144
|
-
throw new RemoteError('gateway/internal', `session "${sessionId}" could not be inspected: ${String(error)}`, {});
|
|
145
|
-
}
|
|
146
|
-
if (cwd === undefined) {
|
|
147
|
-
throw new RemoteError('gateway/internal', `session "${sessionId}" has no project cwd`, {});
|
|
148
|
-
}
|
|
149
|
-
const live = this.ctx.agents.get(sessionId);
|
|
150
|
-
const presets = this.ctx.get('agentPresets');
|
|
151
|
-
const scoped = live === undefined ? undefined : presets?.serviceFor(live, 'skills');
|
|
152
|
-
const skillRegistry = scoped ?? this.ctx.get('skills');
|
|
153
|
-
if (skillRegistry === undefined) {
|
|
154
|
-
throw new RemoteError('gateway/internal', 'skill registry is absent: neither this session\'s agent preset nor the host composition mounts @deepseek-ai/dsh-skill', {});
|
|
155
175
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
skills: skills.map(skill => ({
|
|
161
|
-
name: skill.name,
|
|
162
|
-
...skill.path === undefined ? {} : { path: skill.path },
|
|
163
|
-
description: skill.description,
|
|
164
|
-
...skill.whenToUse === undefined ? {} : { whenToUse: skill.whenToUse },
|
|
165
|
-
modelInvocable: skill.invocation.modelInvocable,
|
|
166
|
-
})),
|
|
167
|
-
};
|
|
176
|
+
catch (e_2) {
|
|
177
|
+
env_1.error = e_2;
|
|
178
|
+
env_1.hasError = true;
|
|
168
179
|
}
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
finally {
|
|
181
|
+
const result_1 = __disposeResources(env_1);
|
|
182
|
+
if (result_1)
|
|
183
|
+
await result_1;
|
|
171
184
|
}
|
|
172
185
|
}
|
|
173
186
|
/** Resolve a live or standing preset scope without creating an Agent. */
|
|
174
|
-
async scopeFor(
|
|
175
|
-
const live = this.ctx.agents.get(sessionId);
|
|
176
|
-
if (live !== undefined)
|
|
177
|
-
return live;
|
|
187
|
+
async scopeFor(agentPreset) {
|
|
178
188
|
const presets = this.ctx.get('agentPresets');
|
|
179
189
|
if (presets === undefined)
|
|
180
190
|
return undefined;
|
|
181
191
|
try {
|
|
182
|
-
return await presets.
|
|
192
|
+
return await presets.acquireScope(agentPreset);
|
|
183
193
|
}
|
|
184
194
|
catch {
|
|
185
195
|
// An unknown or unusable recorded preset falls back to the global registry.
|