@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.1 → 0.1.7-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +42 -7
  3. package/README.zh.md +42 -7
  4. package/lib/client.js +1037 -992
  5. package/lib/index.js +314 -183
  6. package/lib/typert.host.js +539 -331
  7. package/lib/typert.remote-client.d.ts +10 -5
  8. package/lib/typert.remote-client.js +374 -264
  9. package/lib/types/agent.d.ts +1 -1
  10. package/lib/types/agent.js +8 -1
  11. package/lib/types/archived-session-gate.d.ts +30 -0
  12. package/lib/types/archived-session-gate.js +52 -0
  13. package/lib/types/client/contract/session.d.ts +2 -0
  14. package/lib/types/client/contract/sessions.d.ts +65 -35
  15. package/lib/types/client/contract/snapshot.d.ts +0 -14
  16. package/lib/types/client/index.d.ts +12 -3
  17. package/lib/types/client/index.js +16 -5
  18. package/lib/types/client/scope.d.ts +13 -3
  19. package/lib/types/client/scope.js +15 -22
  20. package/lib/types/client/sessions/lineage.d.ts +5 -5
  21. package/lib/types/client/sessions/lineage.js +3 -4
  22. package/lib/types/client/sessions/manager.d.ts +91 -105
  23. package/lib/types/client/sessions/manager.js +307 -434
  24. package/lib/types/client/sessions/projection-store.d.ts +34 -21
  25. package/lib/types/client/sessions/projection-store.js +48 -24
  26. package/lib/types/client/sessions/remotes.d.ts +1 -2
  27. package/lib/types/client/sessions/service.d.ts +53 -150
  28. package/lib/types/client/sessions/service.js +267 -264
  29. package/lib/types/client/sessions/session.d.ts +24 -35
  30. package/lib/types/client/sessions/session.js +131 -73
  31. package/lib/types/commands.d.ts +6 -4
  32. package/lib/types/commands.js +82 -40
  33. package/lib/types/control.d.ts +2 -4
  34. package/lib/types/control.js +2 -84
  35. package/lib/types/history.js +1 -1
  36. package/lib/types/index.d.ts +34 -7
  37. package/lib/types/index.js +118 -14
  38. package/lib/types/list.js +24 -15
  39. package/lib/types/skill-catalog.js +60 -50
  40. package/lib/types/types.d.ts +40 -40
  41. package/lib/types/types.js +0 -1
  42. package/package.json +71 -76
  43. package/lib/types/client/sessions/queue-mirror.d.ts +0 -26
  44. package/lib/types/client/sessions/queue-mirror.js +0 -61
@@ -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, openNativePath, revealNativePath } from '@deepseek-ai/dsh-native-command';
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.openPath = internals.openPath ?? openNativePath;
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
- * Open one path prepared by a Session-aware caller on the Host desktop.
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(request.path, signal);
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(request.path, signal);
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
- throw new RemoteError('gateway/internal', `path open failed: ${error instanceof Error ? error.message : String(error)}`, {});
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 completed-turn prefix into a new Session.
381
- * @param request - source Session and optional event anchor.
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) {
@@ -403,7 +466,7 @@ let SessionController = (() => {
403
466
  return this.commands.attachment(request);
404
467
  }
405
468
  /**
406
- * Mutate one still-pending queue occurrence on a live Agent.
469
+ * Mutate one still-pending queue occurrence, resuming a cold Agent first.
407
470
  * @param request - Session, queue item, and requested mutation.
408
471
  * @returns acknowledgement that the queue mutation was applied.
409
472
  */
@@ -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
- const block = session === undefined
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 signal;
118
- const { sessionId } = request;
119
- let cwd;
120
- let agentPreset;
117
+ const env_1 = { stack: [], error: void 0, hasError: false };
121
118
  try {
122
- const env_1 = { stack: [], error: void 0, hasError: false };
119
+ void signal;
120
+ const { sessionId } = request;
121
+ let cwd;
122
+ let agentPreset;
123
123
  try {
124
- const observation = __addDisposableResource(env_1, await this.ctx.sessionQuery.observeSession(sessionId), false);
125
- if (observation.projections === undefined) {
126
- throw new Error('skill catalog requires a projected Session observation');
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 (e_1) {
132
- env_1.error = e_1;
133
- env_1.hasError = true;
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
- finally {
136
- __disposeResources(env_1);
148
+ if (cwd === undefined) {
149
+ throw new RemoteError('gateway/internal', `session "${sessionId}" has no project cwd`, {});
137
150
  }
138
- }
139
- catch (error) {
140
- if (error instanceof SessionQueryError
141
- && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') {
142
- throw new RemoteError('session/not-found', `session "${sessionId}" not found`, { sessionId });
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
- const scope = await this.scopeFor(sessionId, agentPreset);
157
- try {
158
- const skills = (await skillRegistry.list({ cwd, scope })).filter(isUserInvocable);
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
- catch (error) {
170
- throw new RemoteError('gateway/internal', `skill listing failed: ${String(error)}`, {});
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(sessionId, agentPreset) {
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.standingKeyFor(agentPreset);
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.
@@ -1,11 +1,11 @@
1
1
  /** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
2
+ import type { NativeFileApplication } from '@deepseek-ai/dsh-native-command/types';
2
3
  import type { AttachmentIdType, ImageAttachmentLimits, ImageAttachmentRef, ImageMediaType } from '@deepseek-ai/dsh-attachment';
3
4
  import type { Branded } from '@deepseek-ai/dsh-brand';
4
5
  import type { LlmAttemptId, MessageId } from '@deepseek-ai/dsh-llm/brand';
5
- import type { ContentBlock } from '@deepseek-ai/dsh-llm';
6
+ import type { TextBlock } from '@deepseek-ai/dsh-llm';
6
7
  import type { SessionId, SessionSeqCursor } from '@deepseek-ai/dsh-session/types';
7
8
  import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types';
8
- import type { JobId } from '@deepseek-ai/dsh-jobs/brand';
9
9
  import type { JsonValue } from '@deepseek-ai/dsh-util-values';
10
10
  import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
11
11
  declare module '@deepseek-ai/dsh-session-projection/types' {
@@ -42,10 +42,24 @@ export interface SessionListMetadata {
42
42
  /** Latest human-authored prompt time in the folded prefix. */
43
43
  readonly lastPromptAt: number | null;
44
44
  }
45
- /** Every available cached wire value used as partial, possibly stale Session-list hints. */
45
+ /**
46
+ * Every available wire value a Session-list row carries as partial, possibly
47
+ * stale hints. `kind` and `asOfSeq` are independent facts: `kind` says which
48
+ * sequence space `asOfSeq` belongs to, and therefore how a client may merge
49
+ * the block; `asOfSeq` is the producer's watermark in that space.
50
+ */
46
51
  export interface SessionProjectionHints {
52
+ /**
53
+ * `sequenced`: the Host's live registry produced the block for an attached
54
+ * Session, so `asOfSeq` is comparable with baselines and frames of the same
55
+ * connection. `cached`: a header-only listing viewed the block from the
56
+ * persisted projection cache, so `asOfSeq` is the stored record's own
57
+ * watermark and must not be compared with the connected Session's values.
58
+ */
59
+ readonly kind: 'cached' | 'sequenced';
60
+ /** Watermark of the block in the sequence space named by `kind`. */
47
61
  readonly asOfSeq: number;
48
- /** Provider-validated values present in the cache; omitted keys remain unknown. */
62
+ /** Provider-validated values present in the block; omitted keys remain unknown. */
49
63
  readonly values: SessionProjectionValues;
50
64
  }
51
65
  /** Complete projection values at an exact Session event cursor. */
@@ -135,7 +149,7 @@ export interface ModelCatalog {
135
149
  export type QueueAction = {
136
150
  readonly kind: 'edit';
137
151
  /** Non-empty text-only replacement content. */
138
- readonly content: readonly ContentBlock[];
152
+ readonly content: readonly TextBlock[];
139
153
  } | {
140
154
  readonly kind: 'remove';
141
155
  } | {
@@ -143,6 +157,8 @@ export type QueueAction = {
143
157
  };
144
158
  /** One Session list entry. */
145
159
  export interface SessionSummary {
160
+ /** Whether this Session currently owns a live Agent. */
161
+ readonly agentAvailable: boolean;
146
162
  readonly sessionId: SessionId;
147
163
  readonly updatedAt: number;
148
164
  readonly running: boolean;
@@ -172,6 +188,10 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
172
188
  readonly requestedCwd: string;
173
189
  readonly existingCwd?: string;
174
190
  };
191
+ 'session/projections-unavailable': Record<string, never>;
192
+ 'session/writer-held': {
193
+ readonly sessionId: SessionId;
194
+ };
175
195
  'session/agent-busy': {
176
196
  readonly reason: string;
177
197
  };
@@ -284,6 +304,7 @@ export interface SessionRenameValue {
284
304
  /** Session fork request. */
285
305
  export interface SessionForkRequest {
286
306
  readonly sessionId: SessionId;
307
+ /** Exact inclusive source event seq; omission selects the latest completed-turn prefix. */
287
308
  readonly atSeq?: number;
288
309
  }
289
310
  /** Identity of a newly forked Session. */
@@ -336,6 +357,8 @@ export interface SessionCancelValue {
336
357
  export interface SessionOpenWorkspacePathRequest {
337
358
  /** File-manager navigation when requested; omission uses the default application. */
338
359
  readonly action?: 'reveal';
360
+ /** Registered application identifier; ignored for reveal. Omission preserves the operating system default. */
361
+ readonly application?: string;
339
362
  /** Path after best-effort Session workspace resolution, in Host filesystem syntax. */
340
363
  readonly path: string;
341
364
  }
@@ -363,8 +386,14 @@ export type SessionAddress = {
363
386
  readonly kind: 'subagent';
364
387
  readonly parentSessionId: SessionId;
365
388
  readonly childSessionId: SessionId;
366
- readonly mode: 'one-shot' | 'continuable';
389
+ readonly mode: 'one-shot' | 'continuable' | 'unknown';
367
390
  };
391
+ /** One non-activating Session projection read. */
392
+ export interface SessionProjectionsRequest {
393
+ readonly sessionId: SessionId;
394
+ }
395
+ /** Complete Session projection baseline; null when the Session does not exist. */
396
+ export type SessionProjectionsValue = SessionProjectionBaseline | null;
368
397
  /** One raw Session event in the Remote journal. */
369
398
  export interface SessionEventEntry {
370
399
  readonly type: 'event';
@@ -486,32 +515,8 @@ export type SessionFollowFrame = {
486
515
  readonly type: 'assistant-stream';
487
516
  readonly frame: SessionAssistantStreamFrame;
488
517
  };
489
- /** One pending inbox occurrence in the authoritative queue snapshot. */
490
- export interface SessionQueuedItem {
491
- readonly id: MessageId;
492
- readonly placement: 'queued' | 'steering' | 'context';
493
- /** Prompt-RPC identity from the queued message's user source; clients retire the matching local submission echo on it. */
494
- readonly rpcId?: SessionRequestId;
495
- /** JSON-safe message fields consumed by pending-queue presentation. */
496
- readonly message: {
497
- readonly id: MessageId;
498
- readonly content: readonly JsonValue[];
499
- };
500
- }
501
- /** Browser-safe background-job row. */
502
- export interface SessionJob {
503
- readonly id: JobId;
504
- readonly kind: string;
505
- readonly label: string;
506
- readonly status: 'running' | 'stopping' | 'completed' | 'killed' | 'failed';
507
- readonly detail?: string;
508
- readonly startedAt: number;
509
- readonly finishedAt?: number;
510
- }
511
518
  /** Complete live control baseline emitted once per control stream generation. */
512
519
  export interface SessionControlBaseline {
513
- readonly queues: Readonly<Record<SessionId, readonly SessionQueuedItem[]>>;
514
- readonly jobs: Readonly<Record<SessionId, readonly SessionJob[]>>;
515
520
  readonly projections: Readonly<Record<SessionId, SessionProjectionBaseline>>;
516
521
  }
517
522
  /** One finished projection value and its durable watermark. */
@@ -525,23 +530,16 @@ export interface SessionProjectionUpdate {
525
530
  export type SessionControlFrame = {
526
531
  readonly type: 'baseline';
527
532
  readonly value: SessionControlBaseline;
528
- } | {
529
- readonly type: 'queue';
530
- readonly sessionId: SessionId;
531
- readonly items: readonly SessionQueuedItem[];
532
- } | {
533
- readonly type: 'jobs';
534
- readonly sessionId: SessionId;
535
- readonly jobs: readonly SessionJob[];
536
533
  } | ({
537
534
  readonly type: 'projection';
538
535
  } & SessionProjectionUpdate);
539
536
  declare module '@deepseek-ai/cordis' {
540
537
  interface Events {
541
538
  /**
542
- * A Session became visible to Session list consumers.
539
+ * A Session became visible or its Agent was created or disposed.
540
+ * Consumers upsert the summary and replace its current running and availability state.
543
541
  * @mode emit
544
- * @param summary - initial list row for the Session.
542
+ * @param summary - current list row for the Session.
545
543
  */
546
544
  'api-session/added'(summary: SessionSummary): void;
547
545
  /**
@@ -575,4 +573,6 @@ declare module '@deepseek-ai/cordis' {
575
573
  }
576
574
  /** JSON-compatible projection value accepted by list consumers. */
577
575
  export type SessionProjectionValue = JsonValue;
576
+ /** Application metadata returned by the serving desktop for one file. */
577
+ export type SessionWorkspacePathApplication = NativeFileApplication;
578
578
  //# sourceMappingURL=types.d.ts.map
@@ -1,4 +1,3 @@
1
- /** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
2
1
  /** Maximum number of Sessions returned by one search. */
3
2
  export const SESSION_SEARCH_RESULT_LIMIT = 20;
4
3
  /** Maximum search snippet length in Unicode code points. */