@clinebot/core 0.0.11 → 0.0.13

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-config-loader.d.ts +1 -1
  3. package/dist/agents/agent-config-parser.d.ts +5 -2
  4. package/dist/agents/index.d.ts +1 -1
  5. package/dist/agents/plugin-config-loader.d.ts +4 -0
  6. package/dist/agents/plugin-loader.d.ts +1 -0
  7. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  8. package/dist/agents/plugin-sandbox.d.ts +4 -0
  9. package/dist/index.node.d.ts +5 -0
  10. package/dist/index.node.js +685 -413
  11. package/dist/runtime/commands.d.ts +11 -0
  12. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  13. package/dist/runtime/skills.d.ts +13 -0
  14. package/dist/session/default-session-manager.d.ts +5 -0
  15. package/dist/session/session-config-builder.d.ts +4 -1
  16. package/dist/session/session-manager.d.ts +1 -0
  17. package/dist/session/session-service.d.ts +22 -22
  18. package/dist/session/unified-session-persistence-service.d.ts +12 -6
  19. package/dist/session/utils/helpers.d.ts +2 -2
  20. package/dist/session/utils/types.d.ts +9 -0
  21. package/dist/tools/definitions.d.ts +2 -2
  22. package/dist/tools/presets.d.ts +3 -3
  23. package/dist/tools/schemas.d.ts +15 -14
  24. package/dist/types/config.d.ts +5 -0
  25. package/dist/types/events.d.ts +22 -0
  26. package/package.json +5 -4
  27. package/src/agents/agent-config-loader.test.ts +2 -0
  28. package/src/agents/agent-config-loader.ts +1 -0
  29. package/src/agents/agent-config-parser.ts +12 -5
  30. package/src/agents/index.ts +1 -0
  31. package/src/agents/plugin-config-loader.test.ts +49 -0
  32. package/src/agents/plugin-config-loader.ts +10 -73
  33. package/src/agents/plugin-loader.test.ts +127 -1
  34. package/src/agents/plugin-loader.ts +72 -5
  35. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  36. package/src/agents/plugin-sandbox.test.ts +198 -1
  37. package/src/agents/plugin-sandbox.ts +223 -353
  38. package/src/index.node.ts +14 -0
  39. package/src/runtime/commands.test.ts +98 -0
  40. package/src/runtime/commands.ts +83 -0
  41. package/src/runtime/hook-file-hooks.test.ts +1 -1
  42. package/src/runtime/hook-file-hooks.ts +16 -6
  43. package/src/runtime/index.ts +10 -0
  44. package/src/runtime/runtime-builder.test.ts +67 -0
  45. package/src/runtime/runtime-builder.ts +70 -16
  46. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  47. package/src/runtime/skills.ts +44 -0
  48. package/src/runtime/workflows.ts +20 -29
  49. package/src/session/default-session-manager.e2e.test.ts +52 -33
  50. package/src/session/default-session-manager.test.ts +453 -1
  51. package/src/session/default-session-manager.ts +210 -12
  52. package/src/session/rpc-session-service.ts +14 -96
  53. package/src/session/session-config-builder.ts +2 -0
  54. package/src/session/session-manager.ts +1 -0
  55. package/src/session/session-service.ts +127 -64
  56. package/src/session/session-team-coordination.ts +30 -0
  57. package/src/session/unified-session-persistence-service.test.ts +3 -3
  58. package/src/session/unified-session-persistence-service.ts +159 -141
  59. package/src/session/utils/helpers.ts +22 -41
  60. package/src/session/utils/types.ts +10 -0
  61. package/src/storage/sqlite-team-store.ts +16 -5
  62. package/src/tools/definitions.test.ts +137 -8
  63. package/src/tools/definitions.ts +115 -70
  64. package/src/tools/presets.test.ts +2 -3
  65. package/src/tools/presets.ts +3 -3
  66. package/src/tools/schemas.ts +28 -28
  67. package/src/types/config.ts +5 -0
  68. package/src/types/events.ts +23 -0
@@ -64,7 +64,7 @@ import { SessionManifestSchema } from "./session-manifest";
64
64
  import type {
65
65
  CoreSessionService,
66
66
  RootSessionArtifacts,
67
- SessionRowShape,
67
+ SessionRow,
68
68
  } from "./session-service";
69
69
  import {
70
70
  buildTeamRunContinuationPrompt,
@@ -223,6 +223,7 @@ export class DefaultSessionManager implements SessionManager {
223
223
  hookPath,
224
224
  sessionId,
225
225
  this.defaultTelemetry,
226
+ (e) => void this.handlePluginEvent(sessionId, e),
226
227
  );
227
228
  const providerConfig = buildResolvedProviderConfig(
228
229
  effectiveConfig,
@@ -307,6 +308,8 @@ export class DefaultSessionManager implements SessionManager {
307
308
  activeTeamRunIds: new Set<string>(),
308
309
  pendingTeamRunUpdates: [],
309
310
  teamRunWaiters: [],
311
+ pendingPrompts: [],
312
+ drainingPendingPrompts: false,
310
313
  pluginSandboxShutdown,
311
314
  };
312
315
  this.sessions.set(sessionId, active);
@@ -349,8 +352,18 @@ export class DefaultSessionManager implements SessionManager {
349
352
  promptLength: input.prompt.length,
350
353
  userImageCount: input.userImages?.length ?? 0,
351
354
  userFileCount: input.userFiles?.length ?? 0,
355
+ delivery: input.delivery ?? "immediate",
352
356
  },
353
357
  });
358
+ if (input.delivery === "queue" || input.delivery === "steer") {
359
+ this.enqueuePendingPrompt(input.sessionId, {
360
+ prompt: input.prompt,
361
+ delivery: input.delivery,
362
+ userImages: input.userImages,
363
+ userFiles: input.userFiles,
364
+ });
365
+ return undefined;
366
+ }
354
367
  try {
355
368
  const result = await this.runTurn(session, {
356
369
  prompt: input.prompt,
@@ -360,6 +373,9 @@ export class DefaultSessionManager implements SessionManager {
360
373
  if (!session.interactive) {
361
374
  await this.finalizeSingleRun(session, result.finishReason);
362
375
  }
376
+ queueMicrotask(() => {
377
+ void this.drainPendingPrompts(input.sessionId);
378
+ });
363
379
  return result;
364
380
  } catch (error) {
365
381
  await this.failSession(session);
@@ -446,8 +462,8 @@ export class DefaultSessionManager implements SessionManager {
446
462
 
447
463
  async readTranscript(sessionId: string, maxChars?: number): Promise<string> {
448
464
  const row = await this.getRow(sessionId);
449
- if (!row?.transcript_path || !existsSync(row.transcript_path)) return "";
450
- const raw = readFileSync(row.transcript_path, "utf8");
465
+ if (!row?.transcriptPath || !existsSync(row.transcriptPath)) return "";
466
+ const raw = readFileSync(row.transcriptPath, "utf8");
451
467
  if (typeof maxChars === "number" && Number.isFinite(maxChars)) {
452
468
  return raw.slice(-Math.max(0, Math.floor(maxChars)));
453
469
  }
@@ -456,7 +472,7 @@ export class DefaultSessionManager implements SessionManager {
456
472
 
457
473
  async readMessages(sessionId: string): Promise<LlmsProviders.Message[]> {
458
474
  const row = await this.getRow(sessionId);
459
- const messagesPath = row?.messages_path?.trim();
475
+ const messagesPath = row?.messagesPath?.trim();
460
476
  if (!messagesPath || !existsSync(messagesPath)) return [];
461
477
  try {
462
478
  const raw = readFileSync(messagesPath, "utf8").trim();
@@ -475,8 +491,8 @@ export class DefaultSessionManager implements SessionManager {
475
491
 
476
492
  async readHooks(sessionId: string, limit = 200): Promise<unknown[]> {
477
493
  const row = await this.getRow(sessionId);
478
- if (!row?.hook_path || !existsSync(row.hook_path)) return [];
479
- const lines = readFileSync(row.hook_path, "utf8")
494
+ if (!row?.hookPath || !existsSync(row.hookPath)) return [];
495
+ const lines = readFileSync(row.hookPath, "utf8")
480
496
  .split("\n")
481
497
  .filter((line) => line.trim().length > 0);
482
498
  return lines.slice(-Math.max(1, Math.floor(limit))).map((line) => {
@@ -766,6 +782,151 @@ export class DefaultSessionManager implements SessionManager {
766
782
  this.emitStatus(session.sessionId, status);
767
783
  }
768
784
 
785
+ private async handlePluginEvent(
786
+ rootSessionId: string,
787
+ event: { name: string; payload?: unknown },
788
+ ): Promise<void> {
789
+ if (
790
+ event.name !== "steer_message" &&
791
+ event.name !== "queue_message" &&
792
+ event.name !== "pending_prompt"
793
+ ) {
794
+ return;
795
+ }
796
+ const payload =
797
+ event.payload && typeof event.payload === "object"
798
+ ? (event.payload as Record<string, unknown>)
799
+ : undefined;
800
+ const targetSessionId =
801
+ typeof payload?.sessionId === "string" &&
802
+ payload.sessionId.trim().length > 0
803
+ ? payload.sessionId.trim()
804
+ : rootSessionId;
805
+ const prompt =
806
+ typeof payload?.prompt === "string" ? payload.prompt.trim() : "";
807
+ if (!prompt) {
808
+ return;
809
+ }
810
+ const delivery =
811
+ event.name === "steer_message"
812
+ ? "steer"
813
+ : event.name === "queue_message"
814
+ ? "queue"
815
+ : payload?.delivery === "steer"
816
+ ? "steer"
817
+ : "queue";
818
+ this.enqueuePendingPrompt(targetSessionId, {
819
+ prompt,
820
+ delivery,
821
+ });
822
+ }
823
+
824
+ private enqueuePendingPrompt(
825
+ sessionId: string,
826
+ entry: {
827
+ prompt: string;
828
+ delivery: "queue" | "steer";
829
+ userImages?: string[];
830
+ userFiles?: string[];
831
+ },
832
+ ): void {
833
+ const session = this.sessions.get(sessionId);
834
+ if (!session) {
835
+ return;
836
+ }
837
+ const { prompt, delivery, userImages, userFiles } = entry;
838
+ const existingIndex = session.pendingPrompts.findIndex(
839
+ (queued) => queued.prompt === prompt,
840
+ );
841
+ if (existingIndex >= 0) {
842
+ const [existing] = session.pendingPrompts.splice(existingIndex, 1);
843
+ if (delivery === "steer" || existing.delivery === "steer") {
844
+ session.pendingPrompts.unshift({
845
+ id: existing.id,
846
+ prompt,
847
+ delivery: "steer",
848
+ userImages: userImages ?? existing.userImages,
849
+ userFiles: userFiles ?? existing.userFiles,
850
+ });
851
+ } else {
852
+ session.pendingPrompts.push({
853
+ ...existing,
854
+ userImages: userImages ?? existing.userImages,
855
+ userFiles: userFiles ?? existing.userFiles,
856
+ });
857
+ }
858
+ } else if (delivery === "steer") {
859
+ session.pendingPrompts.unshift({
860
+ id: `pending_${Date.now()}_${nanoid(5)}`,
861
+ prompt,
862
+ delivery,
863
+ userImages,
864
+ userFiles,
865
+ });
866
+ } else {
867
+ session.pendingPrompts.push({
868
+ id: `pending_${Date.now()}_${nanoid(5)}`,
869
+ prompt,
870
+ delivery,
871
+ userImages,
872
+ userFiles,
873
+ });
874
+ }
875
+ this.emitPendingPrompts(session);
876
+ queueMicrotask(() => {
877
+ void this.drainPendingPrompts(sessionId);
878
+ });
879
+ }
880
+
881
+ private async drainPendingPrompts(sessionId: string): Promise<void> {
882
+ const session = this.sessions.get(sessionId);
883
+ if (!session || session.drainingPendingPrompts) {
884
+ return;
885
+ }
886
+ const canStartRun =
887
+ typeof (session.agent as Agent & { canStartRun?: () => boolean })
888
+ .canStartRun === "function"
889
+ ? (
890
+ session.agent as Agent & {
891
+ canStartRun: () => boolean;
892
+ }
893
+ ).canStartRun()
894
+ : true;
895
+ if (!canStartRun) {
896
+ return;
897
+ }
898
+ const next = session.pendingPrompts.shift();
899
+ if (!next) {
900
+ return;
901
+ }
902
+ this.emitPendingPrompts(session);
903
+ this.emitPendingPromptSubmitted(session, next);
904
+ session.drainingPendingPrompts = true;
905
+ try {
906
+ await this.send({
907
+ sessionId,
908
+ prompt: next.prompt,
909
+ userImages: next.userImages,
910
+ userFiles: next.userFiles,
911
+ });
912
+ } catch (error) {
913
+ const message = error instanceof Error ? error.message : String(error);
914
+ if (message.includes("already in progress")) {
915
+ session.pendingPrompts.unshift(next);
916
+ this.emitPendingPrompts(session);
917
+ } else {
918
+ throw error;
919
+ }
920
+ } finally {
921
+ session.drainingPendingPrompts = false;
922
+ if (session.pendingPrompts.length > 0) {
923
+ queueMicrotask(() => {
924
+ void this.drainPendingPrompts(sessionId);
925
+ });
926
+ }
927
+ }
928
+ }
929
+
769
930
  // ── Agent event handling ────────────────────────────────────────────
770
931
 
771
932
  private onAgentEvent(
@@ -791,6 +952,45 @@ export class DefaultSessionManager implements SessionManager {
791
952
  handleAgentEvent(ctx, event);
792
953
  }
793
954
 
955
+ private emitPendingPrompts(session: ActiveSession): void {
956
+ this.emit({
957
+ type: "pending_prompts",
958
+ payload: {
959
+ sessionId: session.sessionId,
960
+ prompts: session.pendingPrompts.map((entry) => ({
961
+ id: entry.id,
962
+ prompt: entry.prompt,
963
+ delivery: entry.delivery,
964
+ attachmentCount:
965
+ (entry.userImages?.length ?? 0) + (entry.userFiles?.length ?? 0),
966
+ })),
967
+ },
968
+ });
969
+ }
970
+
971
+ private emitPendingPromptSubmitted(
972
+ session: ActiveSession,
973
+ entry: {
974
+ id: string;
975
+ prompt: string;
976
+ delivery: "queue" | "steer";
977
+ userImages?: string[];
978
+ userFiles?: string[];
979
+ },
980
+ ): void {
981
+ this.emit({
982
+ type: "pending_prompt_submitted",
983
+ payload: {
984
+ sessionId: session.sessionId,
985
+ id: entry.id,
986
+ prompt: entry.prompt,
987
+ delivery: entry.delivery,
988
+ attachmentCount:
989
+ (entry.userImages?.length ?? 0) + (entry.userFiles?.length ?? 0),
990
+ },
991
+ });
992
+ }
993
+
794
994
  // ── Spawn / sub-agents ──────────────────────────────────────────────
795
995
 
796
996
  private createSpawnTool(
@@ -964,20 +1164,18 @@ export class DefaultSessionManager implements SessionManager {
964
1164
  for (const listener of this.listeners) listener(event);
965
1165
  }
966
1166
 
967
- private async listRows(limit: number): Promise<SessionRowShape[]> {
968
- return this.invoke<SessionRowShape[]>(
1167
+ private async listRows(limit: number): Promise<SessionRow[]> {
1168
+ return this.invoke<SessionRow[]>(
969
1169
  "listSessions",
970
1170
  Math.min(Math.max(1, Math.floor(limit)), MAX_SCAN_LIMIT),
971
1171
  );
972
1172
  }
973
1173
 
974
- private async getRow(
975
- sessionId: string,
976
- ): Promise<SessionRowShape | undefined> {
1174
+ private async getRow(sessionId: string): Promise<SessionRow | undefined> {
977
1175
  const target = sessionId.trim();
978
1176
  if (!target) return undefined;
979
1177
  const rows = await this.listRows(MAX_SCAN_LIMIT);
980
- return rows.find((row) => row.session_id === target);
1178
+ return rows.find((row) => row.sessionId === target);
981
1179
  }
982
1180
 
983
1181
  // ── Session service invocation ──────────────────────────────────────
@@ -1,91 +1,13 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
2
  import { RpcSessionClient, type RpcSessionRow } from "@clinebot/rpc";
3
- import { nowIso } from "./session-artifacts";
4
- import type { SessionRowShape } from "./session-service";
3
+ import type { SessionRow } from "./session-service";
5
4
  import type {
6
5
  PersistedSessionUpdateInput,
7
6
  SessionPersistenceAdapter,
8
7
  } from "./unified-session-persistence-service";
9
8
  import { UnifiedSessionPersistenceService } from "./unified-session-persistence-service";
10
9
 
11
- function toShape(row: RpcSessionRow): SessionRowShape {
12
- return {
13
- session_id: row.sessionId,
14
- source: row.source,
15
- pid: row.pid,
16
- started_at: row.startedAt,
17
- ended_at: row.endedAt ?? null,
18
- exit_code: row.exitCode ?? null,
19
- status: row.status,
20
- status_lock: row.statusLock,
21
- interactive: row.interactive ? 1 : 0,
22
- provider: row.provider,
23
- model: row.model,
24
- cwd: row.cwd,
25
- workspace_root: row.workspaceRoot,
26
- team_name: row.teamName ?? null,
27
- enable_tools: row.enableTools ? 1 : 0,
28
- enable_spawn: row.enableSpawn ? 1 : 0,
29
- enable_teams: row.enableTeams ? 1 : 0,
30
- parent_session_id: row.parentSessionId ?? null,
31
- parent_agent_id: row.parentAgentId ?? null,
32
- agent_id: row.agentId ?? null,
33
- conversation_id: row.conversationId ?? null,
34
- is_subagent: row.isSubagent ? 1 : 0,
35
- prompt: row.prompt ?? null,
36
- metadata_json: row.metadata ? JSON.stringify(row.metadata) : null,
37
- transcript_path: row.transcriptPath,
38
- hook_path: row.hookPath,
39
- messages_path: row.messagesPath ?? null,
40
- updated_at: row.updatedAt,
41
- };
42
- }
43
-
44
- function fromShape(row: SessionRowShape): RpcSessionRow {
45
- return {
46
- sessionId: row.session_id,
47
- source: row.source,
48
- pid: row.pid,
49
- startedAt: row.started_at,
50
- endedAt: row.ended_at ?? null,
51
- exitCode: row.exit_code ?? null,
52
- status: row.status,
53
- statusLock: row.status_lock ?? 0,
54
- interactive: row.interactive === 1,
55
- provider: row.provider,
56
- model: row.model,
57
- cwd: row.cwd,
58
- workspaceRoot: row.workspace_root,
59
- teamName: row.team_name ?? undefined,
60
- enableTools: row.enable_tools === 1,
61
- enableSpawn: row.enable_spawn === 1,
62
- enableTeams: row.enable_teams === 1,
63
- parentSessionId: row.parent_session_id ?? undefined,
64
- parentAgentId: row.parent_agent_id ?? undefined,
65
- agentId: row.agent_id ?? undefined,
66
- conversationId: row.conversation_id ?? undefined,
67
- isSubagent: row.is_subagent === 1,
68
- prompt: row.prompt ?? undefined,
69
- metadata: (() => {
70
- if (!row.metadata_json) {
71
- return undefined;
72
- }
73
- try {
74
- const parsed = JSON.parse(row.metadata_json) as unknown;
75
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
76
- return parsed as Record<string, unknown>;
77
- }
78
- } catch {
79
- // Ignore malformed metadata payloads.
80
- }
81
- return undefined;
82
- })(),
83
- transcriptPath: row.transcript_path,
84
- hookPath: row.hook_path,
85
- messagesPath: row.messages_path ?? undefined,
86
- updatedAt: row.updated_at ?? nowIso(),
87
- };
88
- }
10
+ // ── Adapter ──────────────────────────────────────────────────────────
89
11
 
90
12
  class RpcSessionPersistenceAdapter implements SessionPersistenceAdapter {
91
13
  constructor(private readonly client: RpcSessionClient) {}
@@ -94,39 +16,34 @@ class RpcSessionPersistenceAdapter implements SessionPersistenceAdapter {
94
16
  return "";
95
17
  }
96
18
 
97
- async upsertSession(row: SessionRowShape): Promise<void> {
98
- await this.client.upsertSession(fromShape(row));
19
+ async upsertSession(row: SessionRow): Promise<void> {
20
+ await this.client.upsertSession(row as RpcSessionRow);
99
21
  }
100
22
 
101
- async getSession(sessionId: string): Promise<SessionRowShape | undefined> {
23
+ async getSession(sessionId: string): Promise<SessionRow | undefined> {
102
24
  const row = await this.client.getSession(sessionId);
103
- return row ? toShape(row) : undefined;
25
+ return (row as SessionRow | undefined) ?? undefined;
104
26
  }
105
27
 
106
28
  async listSessions(options: {
107
29
  limit: number;
108
30
  parentSessionId?: string;
109
31
  status?: string;
110
- }): Promise<SessionRowShape[]> {
32
+ }): Promise<SessionRow[]> {
111
33
  const rows = await this.client.listSessions(options);
112
- return rows.map((row) => toShape(row));
34
+ return rows as SessionRow[];
113
35
  }
114
36
 
115
37
  async updateSession(
116
38
  input: PersistedSessionUpdateInput,
117
39
  ): Promise<{ updated: boolean; statusLock: number }> {
118
- const changed = await this.client.updateSession({
40
+ return this.client.updateSession({
119
41
  sessionId: input.sessionId,
120
42
  status: input.status,
121
43
  endedAt: input.endedAt,
122
44
  exitCode: input.exitCode,
123
45
  prompt: input.prompt,
124
- metadata:
125
- input.metadataJson === undefined
126
- ? undefined
127
- : input.metadataJson
128
- ? (JSON.parse(input.metadataJson) as Record<string, unknown>)
129
- : null,
46
+ metadata: input.metadata,
130
47
  parentSessionId: input.parentSessionId,
131
48
  parentAgentId: input.parentAgentId,
132
49
  agentId: input.agentId,
@@ -134,11 +51,10 @@ class RpcSessionPersistenceAdapter implements SessionPersistenceAdapter {
134
51
  expectedStatusLock: input.expectedStatusLock,
135
52
  setRunning: input.setRunning,
136
53
  });
137
- return changed;
138
54
  }
139
55
 
140
56
  async deleteSession(sessionId: string, cascade: boolean): Promise<boolean> {
141
- return await this.client.deleteSession(sessionId, cascade);
57
+ return this.client.deleteSession(sessionId, cascade);
142
58
  }
143
59
 
144
60
  async enqueueSpawnRequest(input: {
@@ -154,10 +70,12 @@ class RpcSessionPersistenceAdapter implements SessionPersistenceAdapter {
154
70
  rootSessionId: string,
155
71
  parentAgentId: string,
156
72
  ): Promise<string | undefined> {
157
- return await this.client.claimSpawnRequest(rootSessionId, parentAgentId);
73
+ return this.client.claimSpawnRequest(rootSessionId, parentAgentId);
158
74
  }
159
75
  }
160
76
 
77
+ // ── Service ──────────────────────────────────────────────────────────
78
+
161
79
  export interface RpcCoreSessionServiceOptions {
162
80
  address?: string;
163
81
  sessionsDir: string;
@@ -24,6 +24,7 @@ export async function buildEffectiveConfig(
24
24
  hookPath: string,
25
25
  sessionId: string,
26
26
  defaultTelemetry: ITelemetryService | undefined,
27
+ onPluginEvent?: (event: { name: string; payload?: unknown }) => void,
27
28
  ): Promise<{
28
29
  config: CoreSessionConfig;
29
30
  pluginSandboxShutdown?: () => Promise<void>;
@@ -54,6 +55,7 @@ export async function buildEffectiveConfig(
54
55
  pluginPaths: input.config.pluginPaths,
55
56
  workspacePath,
56
57
  cwd: input.config.cwd,
58
+ onEvent: onPluginEvent,
57
59
  });
58
60
  const effectiveExtensions = mergeAgentExtensions(
59
61
  input.config.extensions,
@@ -38,6 +38,7 @@ export interface SendSessionInput {
38
38
  prompt: string;
39
39
  userImages?: string[];
40
40
  userFiles?: string[];
41
+ delivery?: "queue" | "steer";
41
42
  }
42
43
 
43
44
  export interface SessionAccumulatedUsage {