@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.2 → 0.1.7-alpha.2
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 +34 -9
- package/README.zh.md +34 -9
- package/lib/client.js +774 -641
- package/lib/index.js +318 -143
- package/lib/typert.host.js +384 -186
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +225 -125
- 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 +11 -4
- package/lib/types/client/contract/sessions.d.ts +11 -16
- package/lib/types/client/contract/snapshot.d.ts +1 -1
- 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 +38 -13
- package/lib/types/client/sessions/projection-store.js +54 -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 +23 -19
- package/lib/types/client/sessions/session.js +158 -59
- package/lib/types/client/transport.js +5 -2
- 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 +25 -10
- 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 +45 -25
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
package/lib/types/control.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/** Live Session
|
|
1
|
+
/** Live Session projection state with reconnect baselines. */
|
|
2
2
|
import { Deque } from '@deepseek-ai/dsh-deque';
|
|
3
3
|
/** Owns the Host-wide Session control stream. */
|
|
4
4
|
export class SessionControlController {
|
|
5
5
|
ctx;
|
|
6
6
|
streams = new Set();
|
|
7
|
-
/** @param ctx - Host context carrying live Agent
|
|
7
|
+
/** @param ctx - Host context carrying live Agent and projection services. */
|
|
8
8
|
constructor(ctx) {
|
|
9
9
|
this.ctx = ctx;
|
|
10
10
|
ctx.sessionProjections.onChanged((session, key, value, seq) => {
|
|
@@ -16,14 +16,6 @@ export class SessionControlController {
|
|
|
16
16
|
seq,
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
|
-
ctx.inject(['jobs'], (jobsCtx) => {
|
|
20
|
-
jobsCtx.jobs.onJobsChanged((owner) => { this.onJobsChanged(owner); });
|
|
21
|
-
});
|
|
22
|
-
ctx.on('session/created', (session) => {
|
|
23
|
-
const jobs = this.jobsFor(this.ctx.agents.get(session.id));
|
|
24
|
-
if (jobs.length > 0)
|
|
25
|
-
this.broadcast({ type: 'jobs', sessionId: session.id, jobs });
|
|
26
|
-
});
|
|
27
19
|
ctx.effect(() => () => {
|
|
28
20
|
for (const stream of this.streams)
|
|
29
21
|
stream.end();
|
|
@@ -50,13 +42,7 @@ export class SessionControlController {
|
|
|
50
42
|
}
|
|
51
43
|
baseline() {
|
|
52
44
|
const sessions = this.ctx.sessions.list();
|
|
53
|
-
const jobs = Object.create(null);
|
|
54
|
-
for (const session of sessions) {
|
|
55
|
-
const agent = this.ctx.agents.get(session.id);
|
|
56
|
-
jobs[session.id] = this.jobsFor(agent);
|
|
57
|
-
}
|
|
58
45
|
return {
|
|
59
|
-
jobs,
|
|
60
46
|
projections: this.projectionBaseline(sessions),
|
|
61
47
|
};
|
|
62
48
|
}
|
|
@@ -72,23 +58,6 @@ export class SessionControlController {
|
|
|
72
58
|
}
|
|
73
59
|
return blocks;
|
|
74
60
|
}
|
|
75
|
-
onJobsChanged(owner) {
|
|
76
|
-
if (owner !== undefined) {
|
|
77
|
-
this.broadcast({ type: 'jobs', sessionId: owner.id, jobs: this.jobsFor(owner) });
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
for (const session of this.ctx.sessions.list()) {
|
|
81
|
-
this.broadcast({
|
|
82
|
-
type: 'jobs',
|
|
83
|
-
sessionId: session.id,
|
|
84
|
-
jobs: this.jobsFor(this.ctx.agents.get(session.id)),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
jobsFor(agent) {
|
|
89
|
-
const jobs = this.ctx.get('jobs');
|
|
90
|
-
return jobs === undefined ? [] : jobs.list(agent).map(jobView);
|
|
91
|
-
}
|
|
92
61
|
broadcast(frame) {
|
|
93
62
|
for (const stream of this.streams)
|
|
94
63
|
stream.push(frame);
|
|
@@ -135,15 +104,4 @@ class ControlQueue {
|
|
|
135
104
|
}
|
|
136
105
|
}
|
|
137
106
|
}
|
|
138
|
-
function jobView(job) {
|
|
139
|
-
return {
|
|
140
|
-
id: job.id,
|
|
141
|
-
kind: job.kind,
|
|
142
|
-
label: job.label,
|
|
143
|
-
status: job.status,
|
|
144
|
-
...(job.detail === undefined ? {} : { detail: job.detail }),
|
|
145
|
-
startedAt: job.startedAt,
|
|
146
|
-
...(job.finishedAt === undefined ? {} : { finishedAt: job.finishedAt }),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
107
|
//# sourceMappingURL=control.js.map
|
package/lib/types/history.js
CHANGED
|
@@ -115,7 +115,7 @@ export class SessionHistoryController {
|
|
|
115
115
|
if (throughSeq >= 0 && sourceLog[throughSeq]?.seq !== throughSeq) {
|
|
116
116
|
throw new RemoteError('gateway/internal', `session log does not contain through seq ${String(throughSeq)}`, {});
|
|
117
117
|
}
|
|
118
|
-
const page = paginate(sourceLog, beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, throughSeq);
|
|
118
|
+
const page = paginate(sourceLog, beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, throughSeq, request.turnWindow);
|
|
119
119
|
const records = pageRecords(page.events);
|
|
120
120
|
return {
|
|
121
121
|
records,
|
|
@@ -137,7 +137,7 @@ export class SessionHistoryController {
|
|
|
137
137
|
* @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
|
|
138
138
|
*/
|
|
139
139
|
async *follow(request, signal) {
|
|
140
|
-
|
|
140
|
+
validateHistoryWindow(request);
|
|
141
141
|
const { address } = request;
|
|
142
142
|
const target = addressId(address);
|
|
143
143
|
const buffered = new Deque();
|
|
@@ -198,7 +198,7 @@ export class SessionHistoryController {
|
|
|
198
198
|
signal.throwIfAborted();
|
|
199
199
|
const cursor = source.cursor;
|
|
200
200
|
snapshotCursor = cursor;
|
|
201
|
-
const page = paginate(events, undefined, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
|
|
201
|
+
const page = paginate(events, undefined, request.maxMessages ?? DEFAULT_MAX_MESSAGES, cursor, request.turnWindow);
|
|
202
202
|
const assistantStream = request.assistantStream === true
|
|
203
203
|
? this.assistantStreams.get(target)?.snapshot() ?? { revision: 0 }
|
|
204
204
|
: undefined;
|
|
@@ -327,16 +327,23 @@ function validatePageRequest(request) {
|
|
|
327
327
|
|| Object.is(request.beforeSeq, -0))) {
|
|
328
328
|
throw new RemoteError('gateway/bad-request', 'beforeSeq must be a non-negative safe integer', {});
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
&& (!Number.isSafeInteger(request.maxMessages) || request.maxMessages <= 0)) {
|
|
332
|
-
throw new RemoteError('gateway/bad-request', 'maxMessages must be a positive safe integer', {});
|
|
333
|
-
}
|
|
330
|
+
validateHistoryWindow(request);
|
|
334
331
|
}
|
|
335
|
-
function
|
|
332
|
+
function validateHistoryWindow(request) {
|
|
336
333
|
if (request.maxMessages !== undefined
|
|
337
334
|
&& (!Number.isSafeInteger(request.maxMessages) || request.maxMessages <= 0)) {
|
|
338
335
|
throw new RemoteError('gateway/bad-request', 'maxMessages must be a positive safe integer', {});
|
|
339
336
|
}
|
|
337
|
+
const window = request.turnWindow;
|
|
338
|
+
if (window !== undefined) {
|
|
339
|
+
if (!Number.isSafeInteger(window.minMessages) || window.minMessages <= 0
|
|
340
|
+
|| window.minMessages > (request.maxMessages ?? DEFAULT_MAX_MESSAGES)) {
|
|
341
|
+
throw new RemoteError('gateway/bad-request', 'turnWindow.minMessages must be a positive safe integer no greater than maxMessages', {});
|
|
342
|
+
}
|
|
343
|
+
if (!Number.isSafeInteger(window.minTurns) || window.minTurns <= 0) {
|
|
344
|
+
throw new RemoteError('gateway/bad-request', 'turnWindow.minTurns must be a positive safe integer', {});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
340
347
|
}
|
|
341
348
|
function addressId(address) {
|
|
342
349
|
return address.kind === 'session' ? address.sessionId : address.childSessionId;
|
|
@@ -370,7 +377,7 @@ function validateAddress(address, header, inheritedEventCount, projections) {
|
|
|
370
377
|
reason: 'unsupported',
|
|
371
378
|
});
|
|
372
379
|
}
|
|
373
|
-
if (identity.mode !== address.mode) {
|
|
380
|
+
if (address.mode !== 'unknown' && identity.mode !== address.mode) {
|
|
374
381
|
throw new RemoteError('subagent/unauthorized', 'subagent mode does not match the supplied address', {
|
|
375
382
|
childSessionId: address.childSessionId,
|
|
376
383
|
});
|
|
@@ -385,12 +392,20 @@ function rejectNotFound(address) {
|
|
|
385
392
|
childSessionId: address.childSessionId,
|
|
386
393
|
});
|
|
387
394
|
}
|
|
388
|
-
function paginate(events, beforeSeq, maxMessages, throughSeq
|
|
395
|
+
function paginate(events, beforeSeq, maxMessages, throughSeq, turnWindow) {
|
|
389
396
|
const end = SessionLogOffset(Math.min(throughSeq + 1, beforeSeq ?? throughSeq + 1));
|
|
390
397
|
let count = 0;
|
|
398
|
+
let turns = 0;
|
|
391
399
|
let cut = SessionLogOffset(0);
|
|
392
400
|
for (let index = end - 1; index >= 0; index--) {
|
|
393
401
|
const event = events[index];
|
|
402
|
+
if (turnWindow !== undefined && event.type === 'turn/start') {
|
|
403
|
+
turns++;
|
|
404
|
+
if (count >= turnWindow.minMessages && turns >= turnWindow.minTurns) {
|
|
405
|
+
cut = SessionLogOffset(index);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
394
409
|
if (!MESSAGE_TYPES.has(event.type) || !isAppendSurfaceEvent(event))
|
|
395
410
|
continue;
|
|
396
411
|
count++;
|
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) {
|