@crazx/dsh-api-session-controller 0.1.2-alpha.3.zw.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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +74 -0
  4. package/README.zh.md +74 -0
  5. package/lib/client.js +2724 -0
  6. package/lib/index.js +2826 -0
  7. package/lib/invariant.js +13 -0
  8. package/lib/typert.host.d.ts +3 -0
  9. package/lib/typert.host.js +2574 -0
  10. package/lib/typert.remote-client.d.ts +67 -0
  11. package/lib/typert.remote-client.js +1119 -0
  12. package/lib/types/agent.d.ts +156 -0
  13. package/lib/types/agent.js +537 -0
  14. package/lib/types/catalog.d.ts +11 -0
  15. package/lib/types/catalog.js +58 -0
  16. package/lib/types/client/contract/events.d.ts +71 -0
  17. package/lib/types/client/contract/events.js +91 -0
  18. package/lib/types/client/contract/session.d.ts +150 -0
  19. package/lib/types/client/contract/session.js +2 -0
  20. package/lib/types/client/contract/sessions.d.ts +125 -0
  21. package/lib/types/client/contract/sessions.js +2 -0
  22. package/lib/types/client/contract/snapshot.d.ts +82 -0
  23. package/lib/types/client/contract/snapshot.js +2 -0
  24. package/lib/types/client/index.d.ts +30 -0
  25. package/lib/types/client/index.js +48 -0
  26. package/lib/types/client/ordered-baseline.d.ts +12 -0
  27. package/lib/types/client/ordered-baseline.js +41 -0
  28. package/lib/types/client/scope.d.ts +36 -0
  29. package/lib/types/client/scope.js +54 -0
  30. package/lib/types/client/sessions/history-records.d.ts +22 -0
  31. package/lib/types/client/sessions/history-records.js +31 -0
  32. package/lib/types/client/sessions/lineage.d.ts +38 -0
  33. package/lib/types/client/sessions/lineage.js +56 -0
  34. package/lib/types/client/sessions/manager.d.ts +280 -0
  35. package/lib/types/client/sessions/manager.js +894 -0
  36. package/lib/types/client/sessions/notifier.d.ts +39 -0
  37. package/lib/types/client/sessions/notifier.js +98 -0
  38. package/lib/types/client/sessions/projection-store.d.ts +108 -0
  39. package/lib/types/client/sessions/projection-store.js +129 -0
  40. package/lib/types/client/sessions/queue-mirror.d.ts +26 -0
  41. package/lib/types/client/sessions/queue-mirror.js +61 -0
  42. package/lib/types/client/sessions/remotes.d.ts +30 -0
  43. package/lib/types/client/sessions/remotes.js +8 -0
  44. package/lib/types/client/sessions/service.d.ts +349 -0
  45. package/lib/types/client/sessions/service.js +574 -0
  46. package/lib/types/client/sessions/session.d.ts +294 -0
  47. package/lib/types/client/sessions/session.js +711 -0
  48. package/lib/types/client/time-zone.d.ts +8 -0
  49. package/lib/types/client/time-zone.js +14 -0
  50. package/lib/types/client/transport.d.ts +73 -0
  51. package/lib/types/client/transport.js +106 -0
  52. package/lib/types/commands.d.ts +69 -0
  53. package/lib/types/commands.js +544 -0
  54. package/lib/types/control.d.ts +23 -0
  55. package/lib/types/control.js +192 -0
  56. package/lib/types/file-references.d.ts +27 -0
  57. package/lib/types/file-references.js +69 -0
  58. package/lib/types/history.d.ts +31 -0
  59. package/lib/types/history.js +376 -0
  60. package/lib/types/index.d.ts +171 -0
  61. package/lib/types/index.js +424 -0
  62. package/lib/types/invariant.d.ts +9 -0
  63. package/lib/types/invariant.js +12 -0
  64. package/lib/types/list.d.ts +53 -0
  65. package/lib/types/list.js +405 -0
  66. package/lib/types/model-selection-projection.d.ts +8 -0
  67. package/lib/types/model-selection-projection.js +66 -0
  68. package/lib/types/remote-events.d.ts +8 -0
  69. package/lib/types/remote-events.js +2 -0
  70. package/lib/types/skill-catalog.d.ts +28 -0
  71. package/lib/types/skill-catalog.js +192 -0
  72. package/lib/types/types.d.ts +505 -0
  73. package/lib/types/types.js +6 -0
  74. package/package.json +154 -0
@@ -0,0 +1,544 @@
1
+ /** Session commands whose activation policy is explicit at each Remote method. */
2
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
3
+ if (value !== null && value !== void 0) {
4
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
5
+ var dispose, inner;
6
+ if (async) {
7
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
8
+ dispose = value[Symbol.asyncDispose];
9
+ }
10
+ if (dispose === void 0) {
11
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
12
+ dispose = value[Symbol.dispose];
13
+ if (async) inner = dispose;
14
+ }
15
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
16
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
17
+ env.stack.push({ value: value, dispose: dispose, async: async });
18
+ }
19
+ else if (async) {
20
+ env.stack.push({ async: true });
21
+ }
22
+ return value;
23
+ };
24
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
25
+ return function (env) {
26
+ function fail(e) {
27
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
28
+ env.hasError = true;
29
+ }
30
+ var r, s = 0;
31
+ function next() {
32
+ while (r = env.stack.pop()) {
33
+ try {
34
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
35
+ if (r.dispose) {
36
+ var result = r.dispose.call(r.value);
37
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
38
+ }
39
+ else s |= 1;
40
+ }
41
+ catch (e) {
42
+ fail(e);
43
+ }
44
+ }
45
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
46
+ if (env.hasError) throw env.error;
47
+ }
48
+ return next();
49
+ };
50
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
51
+ var e = new Error(message);
52
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
+ });
54
+ import { randomUUID } from 'node:crypto';
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';
58
+ import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
59
+ import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title';
60
+ import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
61
+ import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol';
62
+ import { ApiSessionCwdConflict, ApiSessionNotFound, ApiSessionPresetConflict, ApiSessionSubagentOwnership, apiSessionSubagentOwnershipError, hasApiSessionSubagentOwner, inspectApiSession, } from "./agent.js";
63
+ /** Implements Session business commands delegated by the Session Controller Remote service. */
64
+ export class SessionCommandController {
65
+ ctx;
66
+ agents;
67
+ defaultCwd;
68
+ /**
69
+ * @param ctx - Host context carrying Agent, model, attachment, title, and Workspace services.
70
+ * @param agents - sole owner of create, resume, and Session-local model selection.
71
+ * @param defaultCwd - project directory used when create names neither a Workspace nor a cwd.
72
+ */
73
+ constructor(ctx, agents, defaultCwd) {
74
+ this.ctx = ctx;
75
+ this.agents = agents;
76
+ this.defaultCwd = defaultCwd;
77
+ }
78
+ /**
79
+ * Create or idempotently adopt one ordinary Session.
80
+ * @param request - requested identity, location, and Agent preset.
81
+ * @returns the Session identity and resolved preset when configured.
82
+ */
83
+ async create(request) {
84
+ if (request.workspaceId !== undefined && request.cwd !== undefined) {
85
+ throw new RemoteError('gateway/bad-request', 'session.create accepts workspaceId or cwd, not both', {});
86
+ }
87
+ const sessionId = request.sessionId ?? brandString(`session-${randomUUID()}`);
88
+ let workspace;
89
+ if (request.workspaceId !== undefined) {
90
+ workspace = this.ctx.workspaceRegistry.get(request.workspaceId);
91
+ if (workspace === undefined) {
92
+ throw new RemoteError('workspace/not-found', `workspace "${request.workspaceId}" not found`, {
93
+ workspaceId: request.workspaceId,
94
+ });
95
+ }
96
+ }
97
+ const cwd = workspace?.path ?? request.cwd ?? this.defaultCwd;
98
+ let adopted;
99
+ try {
100
+ adopted = await this.agents.ensureSession(sessionId, cwd, request.sessionId !== undefined, request.agentPreset);
101
+ }
102
+ catch (error) {
103
+ this.rejectCreation(sessionId, error);
104
+ }
105
+ if (workspace !== undefined) {
106
+ try {
107
+ await workspace.attachSession(sessionId);
108
+ }
109
+ catch (error) {
110
+ throw new RemoteError('session/workspace-attach-failed', `session "${sessionId}" was created but could not attach to workspace "${workspace.id}": ${String(error)}`, { sessionId, workspaceId: workspace.id });
111
+ }
112
+ }
113
+ const agentPreset = this.agents.presetForSession(adopted.session);
114
+ return { sessionId, ...(agentPreset === undefined ? {} : { agentPreset }) };
115
+ }
116
+ /**
117
+ * Validate and install one Session-local model selection.
118
+ * @param request - Session identity and requested model selection.
119
+ * @returns the normalized selection installed for the Session.
120
+ */
121
+ async selectModel(request) {
122
+ const agent = await this.resolveAgent(request.sessionId);
123
+ return this.agents.serializeImageAdmission(agent, async () => {
124
+ const persistEffortMemory = async (effort) => {
125
+ try {
126
+ await this.ctx.agentDefaultModel.rememberEffort(request.provider, request.model, effort === undefined ? undefined : ReasoningEffortId(effort));
127
+ }
128
+ catch (error) {
129
+ this.ctx.logger.warn(`session-controller: the model switch applies to this Session but its effort memory was not updated: ${String(error)}`);
130
+ }
131
+ };
132
+ try {
133
+ // An absent effort on a DIFFERENT route is a bare model pick: restore
134
+ // the effort the user last explicitly chose there before the adapter
135
+ // default materializes. On the SAME route an absent effort is the
136
+ // explicit provider-default gesture; the post-success write clears it.
137
+ const current = this.agents.selectionFor(agent).current;
138
+ const sameRoute = current.provider === request.provider && current.model === request.model;
139
+ let requested = request.reasoningEffort;
140
+ if (requested === undefined && !sameRoute) {
141
+ const remembered = this.ctx.agentDefaultModel.recallEffort(request.provider, request.model);
142
+ if (remembered !== undefined) {
143
+ const info = await this.ctx.llm.resolveModelInfo(request.provider, request.model);
144
+ if (info.reasoning?.efforts.some(effort => effort.id === remembered)) {
145
+ requested = remembered;
146
+ }
147
+ else {
148
+ await persistEffortMemory(undefined);
149
+ }
150
+ }
151
+ }
152
+ const resolved = await this.ctx.llm.resolveCallConfig({
153
+ provider: request.provider,
154
+ model: request.model,
155
+ ...(requested === undefined
156
+ ? {}
157
+ : { reasoningEffort: ReasoningEffortId(requested) }),
158
+ });
159
+ const selected = {
160
+ provider: resolved.provider,
161
+ model: resolved.model,
162
+ ...(resolved.reasoningEffort === undefined
163
+ ? {}
164
+ : { reasoningEffort: resolved.reasoningEffort }),
165
+ };
166
+ this.agents.selectForNextRequest(agent, selected);
167
+ try {
168
+ await this.ctx.agentDefaultModel.saveSelection(selected);
169
+ }
170
+ catch (error) {
171
+ this.ctx.logger.warn(`session-controller: model selection changed for the Session but the default was not saved: ${String(error)}`);
172
+ }
173
+ // Memory records only explicit effort statements: a wire-stated effort
174
+ // is remembered, and a same-route pick without one clears the route.
175
+ // A consulted bare pick never writes.
176
+ if (request.reasoningEffort !== undefined) {
177
+ await persistEffortMemory(resolved.reasoningEffort);
178
+ }
179
+ else if (sameRoute) {
180
+ await persistEffortMemory(undefined);
181
+ }
182
+ return { selected: { ...selected } };
183
+ }
184
+ catch (error) {
185
+ if (remoteErrorOf(error) !== undefined)
186
+ throw error;
187
+ throw new RemoteError('session/model-unavailable', error instanceof Error ? error.message : String(error), { provider: request.provider, model: request.model });
188
+ }
189
+ });
190
+ }
191
+ /**
192
+ * Normalize and append a user-owned Session title.
193
+ * @param request - Session identity and proposed title.
194
+ * @returns the accepted title and durable event sequence.
195
+ */
196
+ async rename(request) {
197
+ const agent = await this.resolveAgent(request.sessionId);
198
+ const titles = this.ctx.get('sessionTitle');
199
+ if (titles === undefined) {
200
+ throw new RemoteError('gateway/internal', 'renaming is unavailable: this deployment mounts no session-title service', {});
201
+ }
202
+ try {
203
+ const accepted = titles.rename(agent.session, request.title);
204
+ return { title: accepted.title, seq: accepted.eventSeq };
205
+ }
206
+ catch (error) {
207
+ if (error instanceof SessionTitleInvalidError) {
208
+ throw new RemoteError('session/title-invalid', error.message, { sessionId: request.sessionId });
209
+ }
210
+ throw new RemoteError('gateway/internal', `failed to rename session "${request.sessionId}": ${String(error)}`, {});
211
+ }
212
+ }
213
+ /**
214
+ * Create a new ordinary Session from one completed-turn prefix.
215
+ * @param request - source Session and optional event anchor.
216
+ * @returns the new Session identity.
217
+ */
218
+ async fork(request) {
219
+ const env_1 = { stack: [], error: void 0, hasError: false };
220
+ try {
221
+ if (request.atSeq !== undefined
222
+ && (!Number.isInteger(request.atSeq) || request.atSeq < 0)) {
223
+ throw new RemoteError('gateway/bad-request', 'atSeq must be a non-negative integer', {});
224
+ }
225
+ let observed;
226
+ try {
227
+ observed = await this.ctx.sessionQuery.observeSession(request.sessionId);
228
+ }
229
+ catch (error) {
230
+ if (error instanceof SessionQueryError
231
+ && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') {
232
+ throw new RemoteError('session/not-found', `session "${request.sessionId}" not found`, {
233
+ sessionId: request.sessionId,
234
+ });
235
+ }
236
+ throw new RemoteError('gateway/internal', `fork source unavailable for session "${request.sessionId}": ${String(error)}`, {});
237
+ }
238
+ const source = __addDisposableResource(env_1, observed, false);
239
+ const lastSeq = source.events.at(-1)?.seq ?? -1;
240
+ const atSeq = request.atSeq;
241
+ const anchoredBoundary = atSeq === undefined
242
+ ? undefined
243
+ : source.events.find(event => event.type === 'turn/end' && event.seq >= atSeq);
244
+ const boundary = anchoredBoundary
245
+ ?? (atSeq === undefined || atSeq > lastSeq
246
+ ? source.events.findLast(event => event.type === 'turn/end')
247
+ : undefined);
248
+ if (boundary === undefined) {
249
+ throw new RemoteError('session/fork-unavailable', atSeq !== undefined && atSeq <= lastSeq
250
+ ? `session "${request.sessionId}" has not completed the turn containing event ${String(atSeq)}`
251
+ : `session "${request.sessionId}" has no completed turn to fork from`, { sessionId: request.sessionId });
252
+ }
253
+ let cut = boundary.seq + 1;
254
+ while (cut < source.events.length && source.events[cut]?.type !== 'turn/start')
255
+ cut++;
256
+ let workspace;
257
+ try {
258
+ workspace = await this.forkWorkspace(source.header);
259
+ }
260
+ catch (error) {
261
+ throw new RemoteError('gateway/internal', `failed to resolve fork workspace for session "${request.sessionId}": ${String(error)}`, {});
262
+ }
263
+ const childId = brandString(`session-${randomUUID()}`);
264
+ const composition = await this.agents.composeAgent(this.agents.presetForObservation(source));
265
+ try {
266
+ const { provider, model } = this.ctx.agentDefaultModel.currentSelection();
267
+ await this.ctx.agents.create({
268
+ sessionId: childId,
269
+ seed: source.events.slice(0, cut),
270
+ meta: {
271
+ ...(source.header.cwd === undefined ? {} : { cwd: source.header.cwd }),
272
+ parentSession: source.header.id,
273
+ seedLength: cut,
274
+ ...(composition.agentPreset === undefined
275
+ ? {}
276
+ : { agentPreset: composition.agentPreset }),
277
+ },
278
+ agentOptions: { provider, model },
279
+ setup: composition.setup,
280
+ });
281
+ }
282
+ catch (error) {
283
+ throw new RemoteError('gateway/internal', `failed to fork session "${request.sessionId}": ${String(error)}`, {});
284
+ }
285
+ if (workspace !== undefined) {
286
+ try {
287
+ await workspace.attachSession(childId);
288
+ }
289
+ catch (error) {
290
+ throw new RemoteError('session/workspace-attach-failed', `session "${childId}" was forked but could not attach to workspace "${workspace.id}": ${String(error)}`, { sessionId: childId, workspaceId: workspace.id });
291
+ }
292
+ }
293
+ return { sessionId: childId };
294
+ }
295
+ catch (e_1) {
296
+ env_1.error = e_1;
297
+ env_1.hasError = true;
298
+ }
299
+ finally {
300
+ __disposeResources(env_1);
301
+ }
302
+ }
303
+ /**
304
+ * Admit one browser prompt after explicit Agent resume and image validation.
305
+ * @param request - Session identity, prompt content, source metadata, and delivery mode.
306
+ * @returns acknowledgement that the Agent accepted the prompt.
307
+ */
308
+ async prompt(request) {
309
+ const clientTimeZone = request.clientTimeZone === undefined
310
+ ? undefined
311
+ : canonicalClientTimeZone(request.clientTimeZone);
312
+ if (request.clientTimeZone !== undefined && clientTimeZone === undefined) {
313
+ throw new RemoteError('session/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: request.clientTimeZone });
314
+ }
315
+ const agent = await this.resolveAgent(request.sessionId);
316
+ const selection = this.agents.selectionFor(agent).current;
317
+ if (!routeServed(this.ctx, selection.provider)) {
318
+ throw new RemoteError('session/model-unavailable', `no adapter serves provider "${selection.provider}"; select a model for this session`, { provider: selection.provider, model: selection.model });
319
+ }
320
+ const source = {
321
+ kind: 'user',
322
+ rpcId: request.requestId,
323
+ ...(clientTimeZone === undefined ? {} : { clientTimeZone }),
324
+ };
325
+ const hasImage = request.content.some(part => part.type === 'image');
326
+ const admit = async () => {
327
+ try {
328
+ if (hasImage) {
329
+ const current = this.agents.selectionFor(agent).current;
330
+ const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
331
+ if (model.inputModalities !== undefined && !model.inputModalities.includes('image')) {
332
+ throw new RemoteError('session/attachment-invalid', `Model "${current.model}" does not support image input.`, { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' });
333
+ }
334
+ }
335
+ const content = await admitPromptContent(this.ctx.attachments, request.content);
336
+ const message = createUserMessage({ content, source });
337
+ if (request.mode === 'steer')
338
+ agent.steer(message);
339
+ else
340
+ agent.followup(message);
341
+ }
342
+ catch (error) {
343
+ if (remoteErrorOf(error) !== undefined)
344
+ throw error;
345
+ if (error instanceof AttachmentError) {
346
+ throw new RemoteError('session/attachment-invalid', error.message, { reason: error.code });
347
+ }
348
+ throw new RemoteError('session/agent-busy', 'prompt rejected', { reason: String(error) });
349
+ }
350
+ return { accepted: true };
351
+ };
352
+ return hasImage ? this.agents.serializeImageAdmission(agent, admit) : admit();
353
+ }
354
+ /**
355
+ * Read one durable image after proving the Session log references it.
356
+ * @param request - Session and attachment identities used for authorization.
357
+ * @returns the durable attachment reference and base64-encoded bytes.
358
+ */
359
+ async attachment(request) {
360
+ let source;
361
+ try {
362
+ source = await this.readSessionState(request.sessionId);
363
+ }
364
+ catch (error) {
365
+ if (error instanceof ApiSessionNotFound) {
366
+ throw new RemoteError('session/not-found', error.message, { sessionId: request.sessionId });
367
+ }
368
+ throw new RemoteError('gateway/internal', `attachment authorization unavailable for session "${request.sessionId}": ${String(error)}`, {});
369
+ }
370
+ const ref = referencedImage(source.events, String(request.attachmentId));
371
+ if (ref === undefined) {
372
+ throw new RemoteError('session/attachment-invalid', 'Image is not referenced by this session.', { reason: 'ATTACHMENT_NOT_REFERENCED' });
373
+ }
374
+ try {
375
+ const stored = await this.ctx.attachments.readImage(ref);
376
+ return {
377
+ attachment: stored.ref,
378
+ data: Buffer.from(stored.data).toString('base64'),
379
+ };
380
+ }
381
+ catch (error) {
382
+ if (error instanceof AttachmentError) {
383
+ throw new RemoteError('session/attachment-invalid', error.message, { reason: error.code });
384
+ }
385
+ throw new RemoteError('gateway/internal', 'Unable to read image attachment.', {});
386
+ }
387
+ }
388
+ /**
389
+ * Mutate one still-pending queue occurrence without resuming a cold Agent.
390
+ * @param request - Session, queue item, and requested mutation.
391
+ * @returns acknowledgement that the queue mutation was applied.
392
+ */
393
+ updateQueue(request) {
394
+ if (request.action.kind === 'edit'
395
+ && request.action.content.some(block => block.type !== 'text')) {
396
+ throw new RemoteError('session/attachment-invalid', 'queue edits accept text content only', { reason: 'QUEUE_EDIT_NON_TEXT' });
397
+ }
398
+ const agent = this.ctx.agents.get(request.sessionId);
399
+ if (agent !== undefined && hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
400
+ throw apiSessionSubagentOwnershipError(request.sessionId);
401
+ }
402
+ if (agent === undefined) {
403
+ throw new RemoteError('session/queue-item-not-found', 'queued item is no longer pending', { itemId: request.itemId });
404
+ }
405
+ const nextTurn = agent.inbox.nextTurn.find(message => message.id === request.itemId);
406
+ const nextStep = agent.inbox.nextStep.find(message => message.id === request.itemId);
407
+ const located = nextTurn === undefined
408
+ ? nextStep === undefined ? undefined : { target: 'next-step', message: nextStep }
409
+ : { target: 'next-turn', message: nextTurn };
410
+ if (located === undefined) {
411
+ throw new RemoteError('session/queue-item-not-found', 'queued item is no longer pending', { itemId: request.itemId });
412
+ }
413
+ const { target, message } = located;
414
+ if (request.action.kind === 'steer' && (target !== 'next-turn' || agent.status !== 'running')) {
415
+ throw new RemoteError('session/steer-unavailable', 'current turn no longer accepts steering', { itemId: request.itemId });
416
+ }
417
+ if (request.action.kind === 'edit') {
418
+ agent.inbox.replace(request.itemId, freezeMessage({
419
+ ...message,
420
+ content: [...request.action.content],
421
+ }));
422
+ }
423
+ else {
424
+ agent.inbox.remove(request.itemId);
425
+ if (request.action.kind === 'steer')
426
+ agent.steer(message);
427
+ }
428
+ return { accepted: true };
429
+ }
430
+ /**
431
+ * Cancel one live ordinary Agent while retaining pending inbox work.
432
+ * @param request - Session whose active Agent turn is cancelled.
433
+ * @returns acknowledgement that cancellation was requested.
434
+ */
435
+ cancel(request) {
436
+ const agent = this.ctx.agents.get(request.sessionId);
437
+ if (agent === undefined) {
438
+ throw new RemoteError('session/not-found', `session "${request.sessionId}" not found (not attached)`, { sessionId: request.sessionId });
439
+ }
440
+ if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
441
+ throw apiSessionSubagentOwnershipError(request.sessionId);
442
+ }
443
+ agent.cancel({ kind: 'user' }, { keepInbox: true });
444
+ return { accepted: true };
445
+ }
446
+ async resolveAgent(sessionId) {
447
+ const found = await this.agents.resolveAgent(sessionId);
448
+ if ('error' in found)
449
+ throw found.error;
450
+ return found.agent;
451
+ }
452
+ rejectCreation(sessionId, error) {
453
+ if (remoteErrorOf(error) !== undefined)
454
+ throw error;
455
+ if (error instanceof ApiSessionPresetConflict) {
456
+ throw new RemoteError('agent-preset/conflict', error.message, {
457
+ sessionId: error.sessionId,
458
+ requestedPreset: error.requestedPreset,
459
+ ...(error.existingPreset === undefined ? {} : { existingPreset: error.existingPreset }),
460
+ });
461
+ }
462
+ if (error instanceof ApiSessionCwdConflict) {
463
+ throw new RemoteError('session/conflict', error.message, {
464
+ sessionId: error.sessionId,
465
+ requestedCwd: error.requestedCwd,
466
+ ...(error.existingCwd === undefined ? {} : { existingCwd: error.existingCwd }),
467
+ });
468
+ }
469
+ if (error instanceof ApiSessionSubagentOwnership) {
470
+ throw apiSessionSubagentOwnershipError(error.sessionId);
471
+ }
472
+ throw new RemoteError('gateway/internal', `failed to create session "${sessionId}": ${String(error)}`, {});
473
+ }
474
+ async readSessionState(sessionId) {
475
+ const attached = this.ctx.sessions.get(sessionId);
476
+ if (attached !== undefined) {
477
+ return { id: attached.id, header: attached.header, events: [...attached.events] };
478
+ }
479
+ const inspected = await inspectApiSession(this.ctx, sessionId);
480
+ return { id: inspected.meta.id, header: inspected.meta, events: inspected.events };
481
+ }
482
+ async forkWorkspace(source) {
483
+ const workspaces = this.ctx.workspaceRegistry.list();
484
+ const direct = workspaces.find(workspace => workspace.sessionIds.includes(source.id));
485
+ if (direct !== undefined || source.origin !== 'subagent')
486
+ return direct;
487
+ const lineage = await this.ctx.sessionQuery.traceSession(source.id);
488
+ for (const ancestor of lineage.ancestors) {
489
+ const workspace = workspaces.find(candidate => candidate.sessionIds.includes(ancestor.header.id));
490
+ if (workspace !== undefined)
491
+ return workspace;
492
+ }
493
+ return undefined;
494
+ }
495
+ }
496
+ function imageBlockIn(content, match) {
497
+ if (!Array.isArray(content))
498
+ return undefined;
499
+ for (const value of content) {
500
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
501
+ continue;
502
+ const block = value;
503
+ if (block.type === 'image' && typeof block.attachment === 'object' && block.attachment !== null) {
504
+ const ref = block.attachment;
505
+ if (match(ref))
506
+ return ref;
507
+ }
508
+ if (block.type === 'tool-result') {
509
+ const nested = imageBlockIn(block.content, match);
510
+ if (nested !== undefined)
511
+ return nested;
512
+ }
513
+ }
514
+ return undefined;
515
+ }
516
+ function imageInEvent(event, match) {
517
+ const data = event.data;
518
+ const direct = imageBlockIn(data.content, match);
519
+ if (direct !== undefined)
520
+ return direct;
521
+ const message = imageBlockIn(data.message?.content, match);
522
+ if (message !== undefined)
523
+ return message;
524
+ for (const inserted of data.inserted ?? []) {
525
+ const found = imageBlockIn(inserted.content, match);
526
+ if (found !== undefined)
527
+ return found;
528
+ }
529
+ return event.type === 'assistant/chunk' && data.chunk?.type === 'block-end'
530
+ ? imageBlockIn([data.chunk.block], match)
531
+ : undefined;
532
+ }
533
+ function referencedImage(events, attachmentId) {
534
+ for (const event of events) {
535
+ const found = imageInEvent(event, ref => String(ref.attachmentId) === attachmentId);
536
+ if (found !== undefined)
537
+ return found;
538
+ }
539
+ return undefined;
540
+ }
541
+ function routeServed(ctx, provider) {
542
+ return ctx.llm.listProviders().some(entry => entry.id === provider);
543
+ }
544
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1,23 @@
1
+ /** Live Session queue, jobs, and projection state with reconnect baselines. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { SessionControlFrame } from './types.ts';
4
+ /** Owns the Host-wide Session control stream. */
5
+ export declare class SessionControlController {
6
+ private readonly ctx;
7
+ private readonly streams;
8
+ /** @param ctx - Host context carrying live Agent, projection, and jobs services. */
9
+ constructor(ctx: Context);
10
+ /**
11
+ * Open one generation of Host-wide live control state.
12
+ * @param signal - Remote stream cancellation.
13
+ * @returns one complete baseline followed by live replacement frames.
14
+ */
15
+ control(signal: AbortSignal): AsyncIterable<SessionControlFrame>;
16
+ private baseline;
17
+ private projectionBaseline;
18
+ private onSessionEvent;
19
+ private onJobsChanged;
20
+ private jobsFor;
21
+ private broadcast;
22
+ }
23
+ //# sourceMappingURL=control.d.ts.map