@deepseek-ai/dsh-api-session-controller 0.1.2-alpha.5 → 0.1.3-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.
Files changed (34) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +6 -6
  3. package/README.zh.md +6 -6
  4. package/lib/client.js +592 -28
  5. package/lib/index.js +269 -214
  6. package/lib/typert.host.js +183 -141
  7. package/lib/typert.remote-client.js +79 -97
  8. package/lib/types/assistant-stream.d.ts +26 -0
  9. package/lib/types/assistant-stream.js +83 -0
  10. package/lib/types/client/contract/events.d.ts +38 -7
  11. package/lib/types/client/contract/events.js +21 -0
  12. package/lib/types/client/contract/session.d.ts +7 -7
  13. package/lib/types/client/contract/snapshot.d.ts +15 -2
  14. package/lib/types/client/index.d.ts +2 -2
  15. package/lib/types/client/index.js +2 -0
  16. package/lib/types/client/sessions/assistant-stream.d.ts +51 -0
  17. package/lib/types/client/sessions/assistant-stream.js +168 -0
  18. package/lib/types/client/sessions/history-records.d.ts +2 -2
  19. package/lib/types/client/sessions/history-records.js +3 -8
  20. package/lib/types/client/sessions/queue-mirror.js +3 -3
  21. package/lib/types/client/sessions/remotes.d.ts +2 -2
  22. package/lib/types/client/sessions/session.d.ts +3 -1
  23. package/lib/types/client/sessions/session.js +63 -15
  24. package/lib/types/client/transport.d.ts +7 -3
  25. package/lib/types/client/transport.js +18 -3
  26. package/lib/types/commands.js +98 -28
  27. package/lib/types/history.d.ts +2 -1
  28. package/lib/types/history.js +66 -37
  29. package/lib/types/index.d.ts +4 -4
  30. package/lib/types/index.js +13 -5
  31. package/lib/types/list.d.ts +2 -9
  32. package/lib/types/list.js +10 -124
  33. package/lib/types/types.d.ts +71 -29
  34. package/package.json +63 -57
@@ -53,12 +53,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
53
53
  });
54
54
  import { randomUUID } from 'node:crypto';
55
55
  import { brandString } from '@deepseek-ai/dsh-brand';
56
- import { AttachmentError, admitPromptContent } from '@deepseek-ai/dsh-attachment';
57
- import { ReasoningEffortId, createUserMessage, freezeMessage, } from '@deepseek-ai/dsh-llm';
56
+ import { AttachmentError } from '@deepseek-ai/dsh-attachment';
57
+ import { ReasoningEffortId, assistantStreamChunks, createUserMessage, freezeMessage, } from '@deepseek-ai/dsh-llm';
58
58
  import { SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session';
59
59
  import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
60
60
  import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title';
61
61
  import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
62
+ import { assertNever } from '@deepseek-ai/dsh-util-values';
62
63
  import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol';
63
64
  import { ApiSessionCwdConflict, ApiSessionNotFound, ApiSessionPresetConflict, ApiSessionSubagentOwnership, apiSessionSubagentOwnershipError, hasApiSessionSubagentOwner, inspectApiSession, } from "./agent.js";
64
65
  /** Implements Session business commands delegated by the Session Controller Remote service. */
@@ -282,6 +283,8 @@ export class SessionCommandController {
282
283
  throw new RemoteError('session/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: request.clientTimeZone });
283
284
  }
284
285
  const agent = await this.resolveAgent(request.sessionId);
286
+ if (hasPromptRequest(agent, request.requestId))
287
+ return { accepted: true };
285
288
  const selection = this.agents.selectionFor(agent).current;
286
289
  if (!routeServed(this.ctx, selection.provider)) {
287
290
  throw new RemoteError('session/model-unavailable', `no adapter serves provider "${selection.provider}"; select a model for this session`, { provider: selection.provider, model: selection.model });
@@ -294,19 +297,35 @@ export class SessionCommandController {
294
297
  const hasImage = request.content.some(part => part.type === 'image');
295
298
  const admit = async () => {
296
299
  try {
297
- if (hasImage) {
298
- const current = this.agents.selectionFor(agent).current;
299
- const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
300
- if (model.inputModalities !== undefined && !model.inputModalities.includes('image')) {
301
- throw new RemoteError('session/attachment-invalid', `Model "${current.model}" does not support image input.`, { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' });
300
+ const env_2 = { stack: [], error: void 0, hasError: false };
301
+ try {
302
+ if (hasImage) {
303
+ const current = this.agents.selectionFor(agent).current;
304
+ const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
305
+ if (model.inputModalities !== undefined && !model.inputModalities.includes('image')) {
306
+ throw new RemoteError('session/attachment-invalid', `Model "${current.model}" does not support image input.`, { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' });
307
+ }
308
+ }
309
+ const admission = resolvePromptFileReceipts(request.content, receiptId => this.ctx.fileUploads.resolve(agent, receiptId));
310
+ const content = await this.ctx.attachments.admitPromptContent(admission.content);
311
+ const message = createUserMessage({ content, source });
312
+ if (this.ctx.agents.get(agent.id) !== agent) {
313
+ throw new RemoteError('session/not-found', `session "${agent.id}" was disposed during prompt admission`, { sessionId: agent.id });
302
314
  }
315
+ const binding = __addDisposableResource(env_2, this.ctx.fileUploads.bindPrompt(agent, admission.receiptIds, request.requestId), false);
316
+ if (request.mode === 'steer')
317
+ agent.steer(message);
318
+ else
319
+ agent.followup(message);
320
+ binding.commit();
321
+ }
322
+ catch (e_2) {
323
+ env_2.error = e_2;
324
+ env_2.hasError = true;
325
+ }
326
+ finally {
327
+ __disposeResources(env_2);
303
328
  }
304
- const content = await admitPromptContent(this.ctx.attachments, request.content);
305
- const message = createUserMessage({ content, source });
306
- if (request.mode === 'steer')
307
- agent.steer(message);
308
- else
309
- agent.followup(message);
310
329
  }
311
330
  catch (error) {
312
331
  if (remoteErrorOf(error) !== undefined)
@@ -365,12 +384,18 @@ export class SessionCommandController {
365
384
  throw new RemoteError('session/attachment-invalid', 'queue edits accept text content only', { reason: 'QUEUE_EDIT_NON_TEXT' });
366
385
  }
367
386
  const agent = this.ctx.agents.get(request.sessionId);
368
- if (agent !== undefined && hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
369
- throw apiSessionSubagentOwnershipError(request.sessionId);
370
- }
371
387
  if (agent === undefined) {
372
388
  throw new RemoteError('session/queue-item-not-found', 'queued item is no longer pending', { itemId: request.itemId });
373
389
  }
390
+ if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
391
+ const identity = this.ctx.sessionProjections
392
+ .snapshot(agent.session, ['subagent'])
393
+ .values.subagent;
394
+ if (identity?.mode !== 'continuable'
395
+ || !agent.session.isOwnSeq(identity.seq)) {
396
+ throw apiSessionSubagentOwnershipError(request.sessionId);
397
+ }
398
+ }
374
399
  const nextTurn = agent.inbox.nextTurn.find(message => message.id === request.itemId);
375
400
  const nextStep = agent.inbox.nextStep.find(message => message.id === request.itemId);
376
401
  const located = nextTurn === undefined
@@ -383,16 +408,28 @@ export class SessionCommandController {
383
408
  if (request.action.kind === 'steer' && (target !== 'next-turn' || agent.status !== 'running')) {
384
409
  throw new RemoteError('session/steer-unavailable', 'current turn no longer accepts steering', { itemId: request.itemId });
385
410
  }
386
- if (request.action.kind === 'edit') {
387
- agent.inbox.replace(request.itemId, freezeMessage({
388
- ...message,
389
- content: [...request.action.content],
390
- }));
391
- }
392
- else {
393
- agent.inbox.remove(request.itemId);
394
- if (request.action.kind === 'steer')
411
+ switch (request.action.kind) {
412
+ case 'edit':
413
+ agent.inbox.replace(request.itemId, freezeMessage({
414
+ ...message,
415
+ content: [...request.action.content],
416
+ }));
417
+ break;
418
+ case 'remove': {
419
+ agent.inbox.remove(request.itemId);
420
+ const source = message.source;
421
+ if (source.kind === 'user' && 'rpcId' in source) {
422
+ this.ctx.fileUploads.retirePrompt(agent, source.rpcId);
423
+ }
424
+ break;
425
+ }
426
+ case 'steer':
427
+ agent.inbox.remove(request.itemId);
395
428
  agent.steer(message);
429
+ break;
430
+ /* v8 ignore next 2 -- closed-union exhaustiveness guard */
431
+ default:
432
+ assertNever(request.action, 'queue action');
396
433
  }
397
434
  return { accepted: true };
398
435
  }
@@ -462,6 +499,34 @@ export class SessionCommandController {
462
499
  return undefined;
463
500
  }
464
501
  }
502
+ function resolvePromptFileReceipts(content, stagedFile) {
503
+ const receiptIds = new Set();
504
+ const resolved = content.map((part) => {
505
+ if (part.type !== 'file')
506
+ return part;
507
+ const attachment = stagedFile(part.receiptId);
508
+ if (attachment === undefined) {
509
+ throw new RemoteError('session/attachment-invalid', 'File was not uploaded for this session.', { reason: 'FILE_NOT_STAGED' });
510
+ }
511
+ receiptIds.add(part.receiptId);
512
+ return { type: 'file', attachment };
513
+ });
514
+ return { content: resolved, receiptIds: [...receiptIds] };
515
+ }
516
+ function hasPromptRequest(agent, requestId) {
517
+ const matches = (message) => {
518
+ const source = message.source;
519
+ return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId;
520
+ };
521
+ if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches))
522
+ return true;
523
+ return agent.session.snapshotEvents().some((event) => {
524
+ if (event.type !== 'user/message')
525
+ return false;
526
+ const source = event.data.source;
527
+ return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId;
528
+ });
529
+ }
465
530
  function imageBlockIn(content, match) {
466
531
  if (!Array.isArray(content))
467
532
  return undefined;
@@ -495,9 +560,14 @@ function imageInEvent(event, match) {
495
560
  if (found !== undefined)
496
561
  return found;
497
562
  }
498
- return event.type === 'assistant/chunk' && data.chunk?.type === 'block-end'
499
- ? imageBlockIn([data.chunk.block], match)
500
- : undefined;
563
+ if (event.type === 'assistant/message' || event.type === 'assistant/attempt') {
564
+ for (const chunk of assistantStreamChunks(event.data.stream, 'block-end')) {
565
+ const found = imageBlockIn([chunk.block], match);
566
+ if (found !== undefined)
567
+ return found;
568
+ }
569
+ }
570
+ return undefined;
501
571
  }
502
572
  function referencedImage(events, attachmentId) {
503
573
  for (const event of events) {
@@ -7,6 +7,7 @@ export declare class SessionHistoryController {
7
7
  private readonly ctx;
8
8
  private readonly promote;
9
9
  private readonly closeFollowers;
10
+ private readonly assistantStreams;
10
11
  /**
11
12
  * @param ctx - Host context carrying Session query and projection services.
12
13
  * @param promote - starts ordinary Session activation after snapshot delivery.
@@ -23,7 +24,7 @@ export declare class SessionHistoryController {
23
24
  * Follow events appended after an initial cursor on one durable address.
24
25
  * @param request - durable address and last committed sequence already held by the caller.
25
26
  * @param signal - stream cancellation owned by the Remote carrier.
26
- * @returns a complete opening snapshot followed by gap-free event frames.
27
+ * @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
27
28
  */
28
29
  follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
29
30
  private sourceFor;
@@ -53,9 +53,9 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
53
53
  });
54
54
  import { Deque } from '@deepseek-ai/dsh-deque';
55
55
  import { isAppendSurfaceEvent, SessionLogOffset, SessionSeq, } from '@deepseek-ai/dsh-session';
56
- import { isChunkRow, packChunkRuns } from '@deepseek-ai/dsh-session/chunk-rows';
57
56
  import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
58
57
  import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
58
+ import { SessionAssistantStreamAccumulator } from "./assistant-stream.js";
59
59
  const DEFAULT_MAX_MESSAGES = 50;
60
60
  const MESSAGE_TYPES = new Set(['user/message', 'assistant/message']);
61
61
  /** Implements cold-safe history operations delegated by the Session Controller. */
@@ -63,6 +63,7 @@ export class SessionHistoryController {
63
63
  ctx;
64
64
  promote;
65
65
  closeFollowers = new Set();
66
+ assistantStreams = new Map();
66
67
  /**
67
68
  * @param ctx - Host context carrying Session query and projection services.
68
69
  * @param promote - starts ordinary Session activation after snapshot delivery.
@@ -70,6 +71,17 @@ export class SessionHistoryController {
70
71
  constructor(ctx, promote) {
71
72
  this.ctx = ctx;
72
73
  this.promote = promote;
74
+ ctx.on('agent/assistant-stream', ({ agent, frame }) => {
75
+ let stream = this.assistantStreams.get(agent.session.id);
76
+ if (stream === undefined) {
77
+ stream = new SessionAssistantStreamAccumulator();
78
+ this.assistantStreams.set(agent.session.id, stream);
79
+ }
80
+ stream.accept(frame, cursorBeforeNext(agent.session.seq));
81
+ }, { global: true });
82
+ ctx.on('agent/disposed', ({ agent }) => {
83
+ this.assistantStreams.delete(agent.session.id);
84
+ }, { global: true });
73
85
  ctx.effect(() => () => {
74
86
  for (const close of this.closeFollowers)
75
87
  close();
@@ -122,7 +134,7 @@ export class SessionHistoryController {
122
134
  * Follow events appended after an initial cursor on one durable address.
123
135
  * @param request - durable address and last committed sequence already held by the caller.
124
136
  * @param signal - stream cancellation owned by the Remote carrier.
125
- * @returns a complete opening snapshot followed by gap-free event frames.
137
+ * @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
126
138
  */
127
139
  async *follow(request, signal) {
128
140
  validateFollowRequest(request);
@@ -130,6 +142,7 @@ export class SessionHistoryController {
130
142
  const target = addressId(address);
131
143
  const buffered = new Deque();
132
144
  let snapshotCursor;
145
+ let assistantStreamOrdinal = 0;
133
146
  let wake;
134
147
  const notify = () => {
135
148
  const resume = wake;
@@ -145,7 +158,7 @@ export class SessionHistoryController {
145
158
  const disposeEvent = this.ctx.on('session/event', (session, event) => {
146
159
  if (session.id !== target)
147
160
  return;
148
- buffered.pushBack(event);
161
+ buffered.pushBack({ type: 'event', event });
149
162
  notify();
150
163
  }, { global: true });
151
164
  const disposeCreated = this.ctx.on('session/created', (session) => {
@@ -158,10 +171,22 @@ export class SessionHistoryController {
158
171
  ? session.firstLiveSeq
159
172
  : SessionLogOffset(snapshotCursor + 1));
160
173
  for (let index = suffix.length - 1; index >= 0; index -= 1) {
161
- buffered.pushFront(suffix[index]);
174
+ buffered.pushFront({ type: 'event', event: suffix[index] });
162
175
  }
163
176
  notify();
164
177
  }, { global: true });
178
+ const disposeAssistantStream = request.assistantStream !== true
179
+ ? undefined
180
+ : this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {
181
+ if (agent.session.id !== target)
182
+ return;
183
+ buffered.pushBack({
184
+ type: 'assistant-stream',
185
+ frame: wireAssistantStreamFrame(frame, cursorBeforeNext(agent.session.seq)),
186
+ ordinal: ++assistantStreamOrdinal,
187
+ });
188
+ notify();
189
+ }, { global: true });
165
190
  const onAbort = () => { notify(); };
166
191
  signal.addEventListener('abort', onAbort, { once: true });
167
192
  try {
@@ -173,15 +198,24 @@ export class SessionHistoryController {
173
198
  const cursor = source.cursor;
174
199
  snapshotCursor = cursor;
175
200
  const page = paginate(events, undefined, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
201
+ const assistantStream = request.assistantStream === true
202
+ ? this.assistantStreams.get(target)?.snapshot() ?? { revision: 0 }
203
+ : undefined;
204
+ // The accumulator snapshot and this watermark are synchronous. Frames
205
+ // through the cut are represented or superseded by that baseline,
206
+ // including larger revisions from a retired Agent; later revision
207
+ // resets reach Client continuity validation.
208
+ const assistantStreamOrdinalCut = assistantStreamOrdinal;
176
209
  yield {
177
210
  type: 'snapshot',
178
- header: wireHeader(source.header, source.inheritedEventCount),
211
+ header: wireHeader(source.header),
179
212
  cursor,
180
213
  records: pageRecords(page.events),
181
214
  hasMore: page.hasMore,
182
215
  projections: source.projections === undefined
183
216
  ? { asOfSeq: cursor, values: {} }
184
217
  : projectionBlock(source.projections),
218
+ ...assistantStream === undefined ? {} : { assistantStream },
185
219
  };
186
220
  if (address.kind === 'session' && source.source === 'prepared') {
187
221
  const promotion = source.retain();
@@ -200,14 +234,20 @@ export class SessionHistoryController {
200
234
  await new Promise((resolve) => { wake = resolve; });
201
235
  continue;
202
236
  }
237
+ if (item.type === 'assistant-stream') {
238
+ if (item.ordinal > assistantStreamOrdinalCut) {
239
+ yield { type: 'assistant-stream', frame: item.frame };
240
+ }
241
+ continue;
242
+ }
203
243
  const expectedSeq = SessionSeq(nextOffset);
204
- if (item.seq < expectedSeq)
244
+ if (item.event.seq < expectedSeq)
205
245
  continue;
206
- if (item.seq !== expectedSeq) {
246
+ if (item.event.seq !== expectedSeq) {
207
247
  throw new RemoteError('gateway/internal', `session event stream skipped seq ${String(expectedSeq)}`, {});
208
248
  }
209
249
  nextOffset = SessionLogOffset(nextOffset + 1);
210
- yield entryFor(item);
250
+ yield entryFor(item.event);
211
251
  }
212
252
  }
213
253
  catch (e_2) {
@@ -223,6 +263,7 @@ export class SessionHistoryController {
223
263
  signal.removeEventListener('abort', onAbort);
224
264
  disposeCreated();
225
265
  disposeEvent();
266
+ disposeAssistantStream?.();
226
267
  }
227
268
  }
228
269
  async sourceFor(address, signal, withProjections) {
@@ -253,6 +294,19 @@ export class SessionHistoryController {
253
294
  }
254
295
  }
255
296
  }
297
+ function cursorBeforeNext(nextSeq) {
298
+ return nextSeq === 0 ? -1 : SessionSeq(nextSeq - 1);
299
+ }
300
+ function wireAssistantStreamFrame(frame, durableCursor) {
301
+ if (frame.type === 'start')
302
+ return { ...frame, startedAfterSeq: durableCursor };
303
+ if (frame.type === 'end')
304
+ return frame;
305
+ return {
306
+ ...frame,
307
+ chunk: frame.chunk,
308
+ };
309
+ }
256
310
  function projectionBlock(snapshot) {
257
311
  return {
258
312
  asOfSeq: snapshot.asOfSeq,
@@ -354,13 +408,9 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
354
408
  }
355
409
  return { events: events.slice(cut, end), hasMore: cut > 0 };
356
410
  }
357
- /** Translate logical Session metadata to the unchanged v0 browser wire. */
358
- function wireHeader(header, inheritedEventCount) {
359
- const { isSeeded, ...wire } = header;
360
- return {
361
- ...wire,
362
- ...isSeeded ? { seedLength: inheritedEventCount } : {},
363
- };
411
+ /** Translate current logical Session metadata to the browser wire. */
412
+ function wireHeader(header) {
413
+ return { ...header };
364
414
  }
365
415
  function entryFor(event) {
366
416
  return {
@@ -369,29 +419,8 @@ function entryFor(event) {
369
419
  event: event,
370
420
  };
371
421
  }
372
- function chunkEntryFor(row) {
373
- switch (row.type) {
374
- case 'text-chunks':
375
- return {
376
- type: 'chunks',
377
- event: { type: 'chunkrow/text-chunks', seq: row.seq0, time: row.time0, data: row.data },
378
- };
379
- case 'reasoning-chunks':
380
- return {
381
- type: 'chunks',
382
- event: { type: 'chunkrow/reasoning-chunks', seq: row.seq0, time: row.time0, data: row.data },
383
- };
384
- case 'tool-call-chunks':
385
- return {
386
- type: 'chunks',
387
- event: { type: 'chunkrow/tool-call-chunks', seq: row.seq0, time: row.time0, data: row.data },
388
- };
389
- }
390
- }
391
422
  /** Encode one bounded logical page without changing its pagination cut. */
392
423
  function pageRecords(events) {
393
- return packChunkRuns(events).map(record => isChunkRow(record)
394
- ? chunkEntryFor(record)
395
- : entryFor(record));
424
+ return events.map(entryFor);
396
425
  }
397
426
  //# sourceMappingURL=history.js.map
@@ -19,8 +19,6 @@ declare module '@deepseek-ai/cordis' {
19
19
  }
20
20
  /** Session Controller deployment policy. */
21
21
  export interface Config {
22
- /** Maximum cold Session artifact size eligible for one full projection observation. */
23
- readonly coldBlankProbeMaxBytes?: number;
24
22
  /** Override platform desktop-opener detection. */
25
23
  readonly nativeOpen?: boolean;
26
24
  }
@@ -45,7 +43,8 @@ export declare class SessionController extends TypertRemoteService {
45
43
  private readonly promotions;
46
44
  /**
47
45
  * @param ctx - Host context containing the Session capability assembly.
48
- * @param config - cold-list observation policy.
46
+ * @param config - native-opener deployment policy.
47
+ * @param internals - host integrations replaceable by direct unit tests.
49
48
  */
50
49
  constructor(ctx: Context, config: Config, internals?: SessionControllerInternals);
51
50
  private promote;
@@ -154,7 +153,8 @@ export declare class SessionController extends TypertRemoteService {
154
153
  * Follow one Session log from its opening or resume cursor.
155
154
  * @param request - durable address and last committed sequence already held by the caller.
156
155
  * @param signal - cancellation owned by the Remote stream carrier.
157
- * @returns a complete opening snapshot followed by gap-free event frames.
156
+ * @returns a complete opening snapshot followed by gap-free durable event
157
+ * frames and optional cursorless assistant-stream frames.
158
158
  */
159
159
  follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
160
160
  /**
@@ -94,7 +94,7 @@ import { SessionCommandController } from "./commands.js";
94
94
  import { SessionControlController } from "./control.js";
95
95
  import { SessionHistoryController } from "./history.js";
96
96
  import { SessionFileReferences } from "./file-references.js";
97
- import { ApiSessionList, DEFAULT_COLD_BLANK_PROBE_MAX_BYTES } from "./list.js";
97
+ import { ApiSessionList } from "./list.js";
98
98
  import { buildModelCatalog } from "./catalog.js";
99
99
  import { installModelSelectionProjection } from "./model-selection-projection.js";
100
100
  import { SessionSkillCatalog } from "./skill-catalog.js";
@@ -162,6 +162,7 @@ let SessionController = (() => {
162
162
  'agentDefaultModel',
163
163
  'agents',
164
164
  'attachments',
165
+ 'fileUploads',
165
166
  'llm',
166
167
  'sessions',
167
168
  'sessionProjections',
@@ -170,7 +171,6 @@ let SessionController = (() => {
170
171
  'workspaceRegistry',
171
172
  ];
172
173
  static Config = z.object({
173
- coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
174
174
  nativeOpen: z.boolean(),
175
175
  });
176
176
  agents = __runInitializers(this, _instanceExtraInitializers);
@@ -183,13 +183,20 @@ let SessionController = (() => {
183
183
  promotions = new Set();
184
184
  /**
185
185
  * @param ctx - Host context containing the Session capability assembly.
186
- * @param config - cold-list observation policy.
186
+ * @param config - native-opener deployment policy.
187
+ * @param internals - host integrations replaceable by direct unit tests.
187
188
  */
188
189
  constructor(ctx, config, internals = {}) {
189
190
  super(ctx, 'sessionController', { namespace: 'session' });
190
191
  installModelSelectionProjection(ctx);
191
192
  this.agents = new ApiSessionAgentController(ctx);
192
193
  this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
194
+ ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => {
195
+ const result = await this.agents.resolveAgent(sessionId);
196
+ if ('error' in result)
197
+ throw result.error;
198
+ return result.agent;
199
+ }), 'session-controller: file-upload Agent resolver');
193
200
  this.controlState = new SessionControlController(ctx);
194
201
  // Registered before history so reverse-order teardown closes every
195
202
  // follower before waiting for already-admitted promotions.
@@ -197,7 +204,7 @@ let SessionController = (() => {
197
204
  await Promise.allSettled([...this.promotions]);
198
205
  }, 'session-controller.promotions');
199
206
  this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation); });
200
- this.listState = new ApiSessionList(ctx, config.coldBlankProbeMaxBytes ?? DEFAULT_COLD_BLANK_PROBE_MAX_BYTES);
207
+ this.listState = new ApiSessionList(ctx);
201
208
  this.openPath = internals.openPath ?? openNativePath;
202
209
  this.canOpenPath = internals.canOpenPath
203
210
  ?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()));
@@ -407,7 +414,8 @@ let SessionController = (() => {
407
414
  * Follow one Session log from its opening or resume cursor.
408
415
  * @param request - durable address and last committed sequence already held by the caller.
409
416
  * @param signal - cancellation owned by the Remote stream carrier.
410
- * @returns a complete opening snapshot followed by gap-free event frames.
417
+ * @returns a complete opening snapshot followed by gap-free durable event
418
+ * frames and optional cursorless assistant-stream frames.
411
419
  */
412
420
  follow(request, signal) {
413
421
  return this.history.follow(request, signal);
@@ -2,8 +2,6 @@
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
3
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
4
4
  import type { SessionListMetadata, SessionSearchValue, SessionSummary } from './types.ts';
5
- /** Default maximum artifact size eligible for one cold projection observation. */
6
- export declare const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024;
7
5
  /**
8
6
  * Advance the Session-list metadata projection by one committed event.
9
7
  * @param state - metadata before the event.
@@ -21,12 +19,8 @@ export declare function truncateUnicodeCodePoints(value: string, maximum: number
21
19
  /** Owns list projection registration, bounded cold summaries, and authorized search. */
22
20
  export declare class ApiSessionList {
23
21
  private readonly ctx;
24
- private readonly coldBlankProbeMaxBytes;
25
- /**
26
- * @param ctx - Host context carrying Session, query, persistence, and projection services.
27
- * @param coldBlankProbeMaxBytes - maximum physical artifact size eligible for a full observation.
28
- */
29
- constructor(ctx: Context, coldBlankProbeMaxBytes: number);
22
+ /** @param ctx - Host context carrying Session, query, persistence, and projection services. */
23
+ constructor(ctx: Context);
30
24
  /**
31
25
  * Build one current attached-Session summary.
32
26
  * @param session - attached Session to summarize.
@@ -40,7 +34,6 @@ export declare class ApiSessionList {
40
34
  */
41
35
  list(signal?: AbortSignal): Promise<SessionSummary[]>;
42
36
  private summarizeCold;
43
- private probeSmallCold;
44
37
  /**
45
38
  * Search current visible message content without activating any matching Session.
46
39
  * @param query - literal message-content query.