@dexto/core 1.6.24 → 1.6.26

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 (80) hide show
  1. package/dist/agent/DextoAgent.cjs +52 -17
  2. package/dist/agent/DextoAgent.d.ts +11 -6
  3. package/dist/agent/DextoAgent.d.ts.map +1 -1
  4. package/dist/agent/DextoAgent.js +52 -17
  5. package/dist/agent/state-manager.cjs +6 -0
  6. package/dist/agent/state-manager.d.ts +4 -0
  7. package/dist/agent/state-manager.d.ts.map +1 -1
  8. package/dist/agent/state-manager.js +6 -0
  9. package/dist/approval/manager.cjs +328 -178
  10. package/dist/approval/manager.d.ts +39 -31
  11. package/dist/approval/manager.d.ts.map +1 -1
  12. package/dist/approval/manager.js +328 -178
  13. package/dist/approval/session-approval-store.cjs +91 -0
  14. package/dist/approval/session-approval-store.d.ts +55 -0
  15. package/dist/approval/session-approval-store.d.ts.map +1 -0
  16. package/dist/approval/session-approval-store.js +68 -0
  17. package/dist/llm/executor/stream-processor.cjs +24 -15
  18. package/dist/llm/executor/stream-processor.d.ts +5 -0
  19. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  20. package/dist/llm/executor/stream-processor.js +24 -15
  21. package/dist/llm/executor/turn-executor.cjs +7 -3
  22. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  23. package/dist/llm/executor/turn-executor.js +7 -3
  24. package/dist/llm/services/factory.cjs +10 -4
  25. package/dist/llm/services/factory.d.ts +2 -21
  26. package/dist/llm/services/factory.d.ts.map +1 -1
  27. package/dist/llm/services/factory.js +11 -7
  28. package/dist/llm/services/types.d.ts +33 -2
  29. package/dist/llm/services/types.d.ts.map +1 -1
  30. package/dist/llm/services/vercel.cjs +4 -5
  31. package/dist/llm/services/vercel.d.ts +3 -3
  32. package/dist/llm/services/vercel.d.ts.map +1 -1
  33. package/dist/llm/services/vercel.js +2 -3
  34. package/dist/logger/default-logger-factory.d.ts +12 -12
  35. package/dist/logger/v2/schemas.d.ts +6 -6
  36. package/dist/mcp/schemas.d.ts +10 -10
  37. package/dist/session/chat-session.cjs +39 -41
  38. package/dist/session/chat-session.d.ts +22 -12
  39. package/dist/session/chat-session.d.ts.map +1 -1
  40. package/dist/session/chat-session.js +39 -41
  41. package/dist/session/message-queue-store.cjs +75 -0
  42. package/dist/session/message-queue-store.d.ts +16 -0
  43. package/dist/session/message-queue-store.d.ts.map +1 -0
  44. package/dist/session/message-queue-store.js +52 -0
  45. package/dist/session/message-queue.cjs +140 -46
  46. package/dist/session/message-queue.d.ts +18 -6
  47. package/dist/session/message-queue.d.ts.map +1 -1
  48. package/dist/session/message-queue.js +140 -46
  49. package/dist/session/session-manager.cjs +130 -25
  50. package/dist/session/session-manager.d.ts +18 -1
  51. package/dist/session/session-manager.d.ts.map +1 -1
  52. package/dist/session/session-manager.js +130 -25
  53. package/dist/session/title-generator.cjs +9 -2
  54. package/dist/session/title-generator.d.ts +2 -0
  55. package/dist/session/title-generator.d.ts.map +1 -1
  56. package/dist/session/title-generator.js +9 -2
  57. package/dist/telemetry/errors.cjs +2 -2
  58. package/dist/telemetry/errors.js +2 -2
  59. package/dist/telemetry/index.d.ts +1 -1
  60. package/dist/telemetry/index.d.ts.map +1 -1
  61. package/dist/telemetry/index.js +3 -1
  62. package/dist/telemetry/telemetry.cjs +62 -21
  63. package/dist/telemetry/telemetry.d.ts +14 -0
  64. package/dist/telemetry/telemetry.d.ts.map +1 -1
  65. package/dist/telemetry/telemetry.js +62 -21
  66. package/dist/test-utils/session-state-stores.cjs +68 -0
  67. package/dist/test-utils/session-state-stores.js +42 -0
  68. package/dist/tools/session-tool-preferences-store.cjs +86 -0
  69. package/dist/tools/session-tool-preferences-store.d.ts +29 -0
  70. package/dist/tools/session-tool-preferences-store.d.ts.map +1 -0
  71. package/dist/tools/session-tool-preferences-store.js +63 -0
  72. package/dist/tools/tool-manager.cjs +131 -32
  73. package/dist/tools/tool-manager.d.ts +17 -6
  74. package/dist/tools/tool-manager.d.ts.map +1 -1
  75. package/dist/tools/tool-manager.js +131 -32
  76. package/dist/utils/service-initializer.cjs +38 -5
  77. package/dist/utils/service-initializer.d.ts +11 -1
  78. package/dist/utils/service-initializer.d.ts.map +1 -1
  79. package/dist/utils/service-initializer.js +36 -4
  80. package/package.json +1 -1
@@ -9,6 +9,7 @@ import {
9
9
  import { DextoLogComponent } from "../logger/v2/types.js";
10
10
  import { DextoRuntimeError, ErrorScope, ErrorType } from "../errors/index.js";
11
11
  import { HookErrorCode } from "../hooks/error-codes.js";
12
+ import { MessageQueueService } from "./message-queue.js";
12
13
  import { getUsagePricingMetadata, hasMeaningfulTokenUsage } from "../llm/usage-metadata.js";
13
14
  import { parseCodexBaseURL } from "../llm/providers/codex-base-url.js";
14
15
  class ChatSession {
@@ -17,7 +18,7 @@ class ChatSession {
17
18
  *
18
19
  * Each session creates its own isolated services:
19
20
  * - ConversationHistoryProvider (with session-specific storage, shared across LLM switches)
20
- * - LLMService (creates its own properly-typed ContextManager internally)
21
+ * - LLM service (creates its own properly-typed ContextManager internally)
21
22
  * - SessionEventBus (session-local event handling with forwarding)
22
23
  *
23
24
  * @param services - The shared services from the agent (state manager, prompt, client managers, etc.)
@@ -29,6 +30,12 @@ class ChatSession {
29
30
  this.id = id;
30
31
  this.logger = logger.createChild(DextoLogComponent.SESSION);
31
32
  this.eventBus = new SessionEventBus();
33
+ this.messageQueue = new MessageQueueService(
34
+ this.eventBus,
35
+ this.logger,
36
+ this.id,
37
+ this.services.messageQueueStore
38
+ );
32
39
  this.setupEventForwarding();
33
40
  this.logger.debug(`ChatSession ${this.id}: Created, awaiting initialization`);
34
41
  }
@@ -52,10 +59,15 @@ class ChatSession {
52
59
  /**
53
60
  * Handles AI model interactions, tool execution, and response generation for this session.
54
61
  *
55
- * Each session has its own LLMService instance that uses the session's
62
+ * Each session has its own LLM runtime instance that uses the session's
56
63
  * ContextManager and event bus.
57
64
  */
58
65
  llmService;
66
+ /**
67
+ * Durable queued follow-up messages for this session.
68
+ * Reused across LLM switches so mid-task follow-ups survive service recreation.
69
+ */
70
+ messageQueue;
59
71
  /**
60
72
  * Map of event forwarder functions for cleanup.
61
73
  * Stores the bound functions so they can be removed from the event bus.
@@ -147,33 +159,35 @@ class ChatSession {
147
159
  async initializeServices() {
148
160
  const runtimeConfig = this.services.stateManager.getRuntimeConfig(this.id);
149
161
  const llmConfig = runtimeConfig.llm;
150
- const workspace = await this.services.workspaceManager?.getWorkspace();
162
+ await this.messageQueue.initialize();
151
163
  this.historyProvider = createDatabaseHistoryProvider(
152
164
  this.services.storageManager.getDatabase(),
153
165
  this.id,
154
166
  this.logger
155
167
  );
156
- const compactionStrategy = this.services.compactionStrategy;
157
- this.llmService = createLLMService(
168
+ this.llmService = await this.createSessionLLMService(llmConfig, runtimeConfig.usageScopeId);
169
+ this.logger.debug(`ChatSession ${this.id}: Services initialized with storage`);
170
+ }
171
+ async createSessionLLMService(llmConfig, usageScopeId) {
172
+ const workspace = await this.services.workspaceManager?.getWorkspace();
173
+ const options = {
174
+ usageScopeId,
175
+ compactionStrategy: this.services.compactionStrategy,
176
+ ...workspace?.path !== void 0 && { cwd: workspace.path },
177
+ messageQueue: this.messageQueue
178
+ };
179
+ return createLLMService(
158
180
  llmConfig,
159
181
  this.services.toolManager,
160
182
  this.services.systemPromptManager,
161
183
  this.historyProvider,
162
- // Pass history provider for service to use
163
184
  this.eventBus,
164
- // Use session event bus
165
185
  this.id,
166
186
  this.services.resourceManager,
167
- // Pass ResourceManager for blob storage
168
187
  this.logger,
169
- // Pass logger for dependency injection
170
- {
171
- usageScopeId: runtimeConfig.usageScopeId,
172
- compactionStrategy,
173
- cwd: workspace?.path
174
- }
188
+ options,
189
+ this.services.languageModelFactory
175
190
  );
176
- this.logger.debug(`ChatSession ${this.id}: Services initialized with storage`);
177
191
  }
178
192
  /**
179
193
  * Saves a blocked interaction to history when a plugin blocks execution.
@@ -429,9 +443,9 @@ class ChatSession {
429
443
  return this.llmService.getContextManager();
430
444
  }
431
445
  /**
432
- * Gets the session's LLMService instance.
446
+ * Gets the session's LLM service instance.
433
447
  *
434
- * @returns The LLMService for this session
448
+ * @returns The session LLM service for this session
435
449
  */
436
450
  getLLMService() {
437
451
  return this.llmService;
@@ -458,26 +472,10 @@ class ChatSession {
458
472
  async switchLLM(newLLMConfig) {
459
473
  try {
460
474
  const runtimeConfig = this.services.stateManager.getRuntimeConfig(this.id);
461
- const workspace = await this.services.workspaceManager?.getWorkspace();
462
- const compactionStrategy = this.services.compactionStrategy;
463
- const newLLMService = createLLMService(
475
+ this.llmService = await this.createSessionLLMService(
464
476
  newLLMConfig,
465
- this.services.toolManager,
466
- this.services.systemPromptManager,
467
- this.historyProvider,
468
- // Pass the SAME history provider - preserves conversation!
469
- this.eventBus,
470
- // Use session event bus
471
- this.id,
472
- this.services.resourceManager,
473
- this.logger,
474
- {
475
- usageScopeId: runtimeConfig.usageScopeId,
476
- compactionStrategy,
477
- cwd: workspace?.path
478
- }
477
+ runtimeConfig.usageScopeId
479
478
  );
480
- this.llmService = newLLMService;
481
479
  this.logger.info(
482
480
  `ChatSession ${this.id}: LLM switched to ${newLLMConfig.provider}/${newLLMConfig.model}`
483
481
  );
@@ -543,8 +541,8 @@ class ChatSession {
543
541
  * @param message The user message to queue
544
542
  * @returns Queue position and message ID
545
543
  */
546
- queueMessage(message) {
547
- return this.llmService.getMessageQueue().enqueue(message);
544
+ async queueMessage(message) {
545
+ return await this.llmService.getMessageQueue().enqueue(message);
548
546
  }
549
547
  /**
550
548
  * Get all messages currently in the queue.
@@ -558,17 +556,17 @@ class ChatSession {
558
556
  * @param id Message ID to remove
559
557
  * @returns true if message was found and removed; false otherwise
560
558
  */
561
- removeQueuedMessage(id) {
562
- return this.llmService.getMessageQueue().remove(id);
559
+ async removeQueuedMessage(id) {
560
+ return await this.llmService.getMessageQueue().remove(id);
563
561
  }
564
562
  /**
565
563
  * Clear all queued messages.
566
564
  * @returns Number of messages that were cleared
567
565
  */
568
- clearMessageQueue() {
566
+ async clearMessageQueue() {
569
567
  const queue = this.llmService.getMessageQueue();
570
568
  const count = queue.pendingCount();
571
- queue.clear();
569
+ await queue.clear();
572
570
  return count;
573
571
  }
574
572
  /**
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var message_queue_store_exports = {};
20
+ __export(message_queue_store_exports, {
21
+ MessageQueueStore: () => MessageQueueStore
22
+ });
23
+ module.exports = __toCommonJS(message_queue_store_exports);
24
+ class MessageQueueStore {
25
+ constructor(storageManager, logger, options = {}) {
26
+ this.storageManager = storageManager;
27
+ this.logger = logger;
28
+ const cacheTtlMs = options.cacheTtlMs ?? 36e5;
29
+ this.cacheTtlSeconds = Math.max(1, Math.floor(cacheTtlMs / 1e3));
30
+ }
31
+ cacheTtlSeconds;
32
+ buildKey(sessionId) {
33
+ return `session-message-queue:${sessionId}`;
34
+ }
35
+ async load(sessionId) {
36
+ const key = this.buildKey(sessionId);
37
+ const cached = await this.storageManager.getCache().get(key);
38
+ if (Array.isArray(cached)) {
39
+ return structuredClone(cached);
40
+ }
41
+ const stored = await this.storageManager.getDatabase().get(key);
42
+ if (!Array.isArray(stored)) {
43
+ if (stored !== void 0) {
44
+ this.logger.warn("Invalid persisted message queue encountered; ignoring state", {
45
+ key
46
+ });
47
+ }
48
+ return [];
49
+ }
50
+ const cloned = structuredClone(stored);
51
+ await this.storageManager.getCache().set(key, cloned, this.cacheTtlSeconds);
52
+ return cloned;
53
+ }
54
+ async save(sessionId, queue) {
55
+ const key = this.buildKey(sessionId);
56
+ if (queue.length === 0) {
57
+ await this.delete(sessionId);
58
+ return;
59
+ }
60
+ const cloned = structuredClone(queue);
61
+ await this.storageManager.getDatabase().set(key, cloned);
62
+ await this.storageManager.getCache().set(key, cloned, this.cacheTtlSeconds);
63
+ }
64
+ async delete(sessionId) {
65
+ const key = this.buildKey(sessionId);
66
+ await Promise.all([
67
+ this.storageManager.getDatabase().delete(key),
68
+ this.storageManager.getCache().delete(key)
69
+ ]);
70
+ }
71
+ }
72
+ // Annotate the CommonJS export names for ESM import in node:
73
+ 0 && (module.exports = {
74
+ MessageQueueStore
75
+ });
@@ -0,0 +1,16 @@
1
+ import type { Logger } from '../logger/v2/types.js';
2
+ import type { StorageManager } from '../storage/index.js';
3
+ import type { QueuedMessage } from './types.js';
4
+ export declare class MessageQueueStore {
5
+ private readonly storageManager;
6
+ private readonly logger;
7
+ private readonly cacheTtlSeconds;
8
+ constructor(storageManager: StorageManager, logger: Logger, options?: {
9
+ cacheTtlMs?: number;
10
+ });
11
+ private buildKey;
12
+ load(sessionId: string): Promise<QueuedMessage[]>;
13
+ save(sessionId: string, queue: QueuedMessage[]): Promise<void>;
14
+ delete(sessionId: string): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=message-queue-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-queue-store.d.ts","sourceRoot":"","sources":["../../src/session/message-queue-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,iBAAiB;IAItB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJ3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAGpB,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,MAAM,EAC/B,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO;IAMzC,OAAO,CAAC,QAAQ;IAIV,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAsBjD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9D,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOjD"}
@@ -0,0 +1,52 @@
1
+ import "../chunk-PTJYTZNU.js";
2
+ class MessageQueueStore {
3
+ constructor(storageManager, logger, options = {}) {
4
+ this.storageManager = storageManager;
5
+ this.logger = logger;
6
+ const cacheTtlMs = options.cacheTtlMs ?? 36e5;
7
+ this.cacheTtlSeconds = Math.max(1, Math.floor(cacheTtlMs / 1e3));
8
+ }
9
+ cacheTtlSeconds;
10
+ buildKey(sessionId) {
11
+ return `session-message-queue:${sessionId}`;
12
+ }
13
+ async load(sessionId) {
14
+ const key = this.buildKey(sessionId);
15
+ const cached = await this.storageManager.getCache().get(key);
16
+ if (Array.isArray(cached)) {
17
+ return structuredClone(cached);
18
+ }
19
+ const stored = await this.storageManager.getDatabase().get(key);
20
+ if (!Array.isArray(stored)) {
21
+ if (stored !== void 0) {
22
+ this.logger.warn("Invalid persisted message queue encountered; ignoring state", {
23
+ key
24
+ });
25
+ }
26
+ return [];
27
+ }
28
+ const cloned = structuredClone(stored);
29
+ await this.storageManager.getCache().set(key, cloned, this.cacheTtlSeconds);
30
+ return cloned;
31
+ }
32
+ async save(sessionId, queue) {
33
+ const key = this.buildKey(sessionId);
34
+ if (queue.length === 0) {
35
+ await this.delete(sessionId);
36
+ return;
37
+ }
38
+ const cloned = structuredClone(queue);
39
+ await this.storageManager.getDatabase().set(key, cloned);
40
+ await this.storageManager.getCache().set(key, cloned, this.cacheTtlSeconds);
41
+ }
42
+ async delete(sessionId) {
43
+ const key = this.buildKey(sessionId);
44
+ await Promise.all([
45
+ this.storageManager.getDatabase().delete(key),
46
+ this.storageManager.getCache().delete(key)
47
+ ]);
48
+ }
49
+ }
50
+ export {
51
+ MessageQueueStore
52
+ };
@@ -21,15 +21,71 @@ __export(message_queue_exports, {
21
21
  MessageQueueService: () => MessageQueueService
22
22
  });
23
23
  module.exports = __toCommonJS(message_queue_exports);
24
+ class EphemeralMessageQueueStore {
25
+ async load(sessionId) {
26
+ void sessionId;
27
+ return [];
28
+ }
29
+ async save(sessionId, queue) {
30
+ void sessionId;
31
+ void queue;
32
+ }
33
+ async delete(sessionId) {
34
+ void sessionId;
35
+ }
36
+ }
24
37
  function generateId() {
25
38
  return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
26
39
  }
27
40
  class MessageQueueService {
28
- constructor(eventBus, logger) {
41
+ constructor(eventBus, logger, sessionId, store) {
29
42
  this.eventBus = eventBus;
30
43
  this.logger = logger;
44
+ this.sessionId = sessionId;
45
+ this.store = store;
31
46
  }
32
47
  queue = [];
48
+ mutationLock = Promise.resolve();
49
+ initialized = false;
50
+ initializationPromise = null;
51
+ static createEphemeral(eventBus, logger, sessionId) {
52
+ return new MessageQueueService(
53
+ eventBus,
54
+ logger,
55
+ sessionId,
56
+ new EphemeralMessageQueueStore()
57
+ );
58
+ }
59
+ async initialize() {
60
+ this.initializationPromise ??= this.runWithMutationLock(async () => {
61
+ if (this.initialized) {
62
+ return;
63
+ }
64
+ this.queue = await this.store.load(this.sessionId);
65
+ if (this.queue.length > 0) {
66
+ this.logger.debug(
67
+ `Restored ${this.queue.length} queued message(s) for session ${this.sessionId}`
68
+ );
69
+ }
70
+ this.initialized = true;
71
+ }).catch((error) => {
72
+ this.initializationPromise = null;
73
+ throw error;
74
+ });
75
+ await this.initializationPromise;
76
+ }
77
+ async persistQueue() {
78
+ await this.store.save(this.sessionId, this.queue);
79
+ }
80
+ runWithMutationLock(fn) {
81
+ const currentResult = this.mutationLock.catch(() => {
82
+ }).then(() => fn());
83
+ this.mutationLock = currentResult.then(
84
+ () => void 0,
85
+ () => void 0
86
+ );
87
+ return currentResult;
88
+ }
33
89
  /**
34
90
  * Add a message to the queue.
35
91
  * Called by API endpoint - returns immediately with queue position.
@@ -37,25 +93,33 @@ class MessageQueueService {
37
93
  * @param message The user message to queue
38
94
  * @returns Queue position and message ID
39
95
  */
40
- enqueue(message) {
41
- const queuedMsg = {
42
- id: generateId(),
43
- content: message.content,
44
- queuedAt: Date.now(),
45
- ...message.metadata !== void 0 && { metadata: message.metadata },
46
- ...message.kind !== void 0 && { kind: message.kind }
47
- };
48
- this.queue.push(queuedMsg);
49
- this.logger.debug(`Message queued: ${queuedMsg.id}, position: ${this.queue.length}`);
50
- this.eventBus.emit("message:queued", {
51
- position: this.queue.length,
52
- id: queuedMsg.id
96
+ async enqueue(message) {
97
+ return await this.runWithMutationLock(async () => {
98
+ const queuedMsg = {
99
+ id: generateId(),
100
+ content: message.content,
101
+ queuedAt: Date.now(),
102
+ ...message.metadata !== void 0 && { metadata: message.metadata },
103
+ ...message.kind !== void 0 && { kind: message.kind }
104
+ };
105
+ this.queue.push(queuedMsg);
106
+ try {
107
+ await this.persistQueue();
108
+ } catch (error) {
109
+ this.queue.pop();
110
+ throw error;
111
+ }
112
+ this.logger.debug(`Message queued: ${queuedMsg.id}, position: ${this.queue.length}`);
113
+ this.eventBus.emit("message:queued", {
114
+ position: this.queue.length,
115
+ id: queuedMsg.id
116
+ });
117
+ return {
118
+ queued: true,
119
+ position: this.queue.length,
120
+ id: queuedMsg.id
121
+ };
53
122
  });
54
- return {
55
- queued: true,
56
- position: this.queue.length,
57
- id: queuedMsg.id
58
- };
59
123
  }
60
124
  /**
61
125
  * Dequeue ALL pending messages and coalesce into single injection.
@@ -76,22 +140,30 @@ class MessageQueueService {
76
140
  *
77
141
  * @returns Coalesced message or null if queue is empty
78
142
  */
79
- dequeueAll() {
80
- if (this.queue.length === 0) return null;
81
- const messages = [...this.queue];
82
- this.queue = [];
83
- const combined = this.coalesce(messages);
84
- this.logger.debug(
85
- `Dequeued ${messages.length} message(s): ${messages.map((m) => m.id).join(", ")}`
86
- );
87
- this.eventBus.emit("message:dequeued", {
88
- count: messages.length,
89
- ids: messages.map((m) => m.id),
90
- coalesced: messages.length > 1,
91
- content: combined.combinedContent,
92
- messages
143
+ async dequeueAll() {
144
+ return await this.runWithMutationLock(async () => {
145
+ if (this.queue.length === 0) return null;
146
+ const messages = [...this.queue];
147
+ this.queue = [];
148
+ try {
149
+ await this.persistQueue();
150
+ } catch (error) {
151
+ this.queue = messages;
152
+ throw error;
153
+ }
154
+ const combined = this.coalesce(messages);
155
+ this.logger.debug(
156
+ `Dequeued ${messages.length} message(s): ${messages.map((m) => m.id).join(", ")}`
157
+ );
158
+ this.eventBus.emit("message:dequeued", {
159
+ count: messages.length,
160
+ ids: messages.map((m) => m.id),
161
+ coalesced: messages.length > 1,
162
+ content: combined.combinedContent,
163
+ messages
164
+ });
165
+ return combined;
93
166
  });
94
- return combined;
95
167
  }
96
168
  /**
97
169
  * Coalesce multiple messages into one (multimodal-aware).
@@ -187,8 +259,20 @@ class MessageQueueService {
187
259
  * Clear all pending messages without processing.
188
260
  * Used during cleanup/abort.
189
261
  */
190
- clear() {
191
- this.queue = [];
262
+ async clear() {
263
+ await this.runWithMutationLock(async () => {
264
+ if (this.queue.length === 0) {
265
+ return;
266
+ }
267
+ const previousQueue = [...this.queue];
268
+ this.queue = [];
269
+ try {
270
+ await this.persistQueue();
271
+ } catch (error) {
272
+ this.queue = previousQueue;
273
+ throw error;
274
+ }
275
+ });
192
276
  }
193
277
  /**
194
278
  * Get all queued messages (for UI display).
@@ -207,16 +291,26 @@ class MessageQueueService {
207
291
  * Remove a single queued message by ID.
208
292
  * @returns true if message was found and removed; false otherwise
209
293
  */
210
- remove(id) {
211
- const index = this.queue.findIndex((m) => m.id === id);
212
- if (index === -1) {
213
- this.logger.debug(`Remove failed: message ${id} not found in queue`);
214
- return false;
215
- }
216
- this.queue.splice(index, 1);
217
- this.logger.debug(`Message removed: ${id}, remaining: ${this.queue.length}`);
218
- this.eventBus.emit("message:removed", { id });
219
- return true;
294
+ async remove(id) {
295
+ return await this.runWithMutationLock(async () => {
296
+ const index = this.queue.findIndex((m) => m.id === id);
297
+ if (index === -1) {
298
+ this.logger.debug(`Remove failed: message ${id} not found in queue`);
299
+ return false;
300
+ }
301
+ const [removed] = this.queue.splice(index, 1);
302
+ try {
303
+ await this.persistQueue();
304
+ } catch (error) {
305
+ if (removed) {
306
+ this.queue.splice(index, 0, removed);
307
+ }
308
+ throw error;
309
+ }
310
+ this.logger.debug(`Message removed: ${id}, remaining: ${this.queue.length}`);
311
+ this.eventBus.emit("message:removed", { id });
312
+ return true;
313
+ });
220
314
  }
221
315
  }
222
316
  // Annotate the CommonJS export names for ESM import in node:
@@ -2,6 +2,8 @@ import type { SessionEventBus } from '../events/index.js';
2
2
  import type { QueuedMessage, CoalescedMessage } from './types.js';
3
3
  import type { ContentPart } from '../context/types.js';
4
4
  import type { Logger } from '../logger/v2/types.js';
5
+ import type { MessageQueueStore } from './message-queue-store.js';
6
+ type MessageQueueBackingStore = Pick<MessageQueueStore, 'load' | 'save' | 'delete'>;
5
7
  /**
6
8
  * Input for enqueuing a user message to the queue.
7
9
  * (Not to be confused with UserMessage from context/types.ts which represents
@@ -46,8 +48,17 @@ export interface UserMessageInput {
46
48
  export declare class MessageQueueService {
47
49
  private eventBus;
48
50
  private logger;
51
+ private sessionId;
52
+ private store;
49
53
  private queue;
50
- constructor(eventBus: SessionEventBus, logger: Logger);
54
+ private mutationLock;
55
+ private initialized;
56
+ private initializationPromise;
57
+ static createEphemeral(eventBus: SessionEventBus, logger: Logger, sessionId: string): MessageQueueService;
58
+ constructor(eventBus: SessionEventBus, logger: Logger, sessionId: string, store: MessageQueueBackingStore);
59
+ initialize(): Promise<void>;
60
+ private persistQueue;
61
+ private runWithMutationLock;
51
62
  /**
52
63
  * Add a message to the queue.
53
64
  * Called by API endpoint - returns immediately with queue position.
@@ -55,11 +66,11 @@ export declare class MessageQueueService {
55
66
  * @param message The user message to queue
56
67
  * @returns Queue position and message ID
57
68
  */
58
- enqueue(message: UserMessageInput): {
69
+ enqueue(message: UserMessageInput): Promise<{
59
70
  queued: true;
60
71
  position: number;
61
72
  id: string;
62
- };
73
+ }>;
63
74
  /**
64
75
  * Dequeue ALL pending messages and coalesce into single injection.
65
76
  * Called by executor between steps.
@@ -79,7 +90,7 @@ export declare class MessageQueueService {
79
90
  *
80
91
  * @returns Coalesced message or null if queue is empty
81
92
  */
82
- dequeueAll(): CoalescedMessage | null;
93
+ dequeueAll(): Promise<CoalescedMessage | null>;
83
94
  /**
84
95
  * Coalesce multiple messages into one (multimodal-aware).
85
96
  * Strategy: Combine with per-kind formatting, preserve all media.
@@ -97,7 +108,7 @@ export declare class MessageQueueService {
97
108
  * Clear all pending messages without processing.
98
109
  * Used during cleanup/abort.
99
110
  */
100
- clear(): void;
111
+ clear(): Promise<void>;
101
112
  /**
102
113
  * Get all queued messages (for UI display).
103
114
  * Returns a shallow copy to prevent external mutation.
@@ -111,6 +122,7 @@ export declare class MessageQueueService {
111
122
  * Remove a single queued message by ID.
112
123
  * @returns true if message was found and removed; false otherwise
113
124
  */
114
- remove(id: string): boolean;
125
+ remove(id: string): Promise<boolean>;
115
126
  }
127
+ export {};
116
128
  //# sourceMappingURL=message-queue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-queue.d.ts","sourceRoot":"","sources":["../../src/session/message-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AASpD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,sCAAsC;IACtC,IAAI,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,mBAAmB;IAIxB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IAJlB,OAAO,CAAC,KAAK,CAAuB;gBAGxB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,MAAM;IAG1B;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAyBlF;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,IAAI,gBAAgB,GAAG,IAAI;IAuBrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAuGhB;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACH,MAAM,IAAI,aAAa,EAAE;IAIzB;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI1C;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAY9B"}
1
+ {"version":3,"file":"message-queue.d.ts","sourceRoot":"","sources":["../../src/session/message-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,KAAK,wBAAwB,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AAyBpF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,sCAAsC;IACtC,IAAI,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,mBAAmB;IAoBxB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,KAAK;IAtBjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,qBAAqB,CAA8B;IAE3D,MAAM,CAAC,eAAe,CAClB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAClB,mBAAmB;gBAUV,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,wBAAwB;IAGrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAsBnB,YAAY;IAI1B,OAAO,CAAC,mBAAmB;IAS3B;;;;;;OAMG;IACG,OAAO,CACT,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAkC1D;;;;;;;;;;;;;;;;;;OAkBG;IACG,UAAU,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAgCpD;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAuGhB;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;;OAGG;IACH,MAAM,IAAI,aAAa,EAAE;IAIzB;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI1C;;;OAGG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwB7C"}