@minded-ai/mindedjs 1.0.121 → 1.0.122-beta-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 (102) hide show
  1. package/dist/agent.d.ts +4 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +69 -9
  4. package/dist/agent.js.map +1 -1
  5. package/dist/browserTask/README.md +419 -0
  6. package/dist/browserTask/browserAgent.py +632 -0
  7. package/dist/browserTask/captcha_isolated.png +0 -0
  8. package/dist/browserTask/executeBrowserTask.ts +79 -0
  9. package/dist/browserTask/requirements.txt +8 -0
  10. package/dist/browserTask/setup.sh +144 -0
  11. package/dist/cli/index.js +14 -14
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/edges/edgeFactory.js +2 -2
  14. package/dist/edges/edgeFactory.js.map +1 -1
  15. package/dist/internalTools/retell.d.ts +12 -0
  16. package/dist/internalTools/retell.d.ts.map +1 -0
  17. package/dist/internalTools/retell.js +54 -0
  18. package/dist/internalTools/retell.js.map +1 -0
  19. package/dist/internalTools/sendPlaceholderMessage.d.ts +14 -0
  20. package/dist/internalTools/sendPlaceholderMessage.d.ts.map +1 -0
  21. package/dist/internalTools/sendPlaceholderMessage.js +61 -0
  22. package/dist/internalTools/sendPlaceholderMessage.js.map +1 -0
  23. package/dist/interrupts/BaseInterruptSessionManager.d.ts +52 -0
  24. package/dist/interrupts/BaseInterruptSessionManager.d.ts.map +1 -0
  25. package/dist/interrupts/BaseInterruptSessionManager.js +40 -0
  26. package/dist/interrupts/BaseInterruptSessionManager.js.map +1 -0
  27. package/dist/interrupts/MemoryInterruptSessionManager.d.ts +14 -0
  28. package/dist/interrupts/MemoryInterruptSessionManager.d.ts.map +1 -0
  29. package/dist/interrupts/MemoryInterruptSessionManager.js +60 -0
  30. package/dist/interrupts/MemoryInterruptSessionManager.js.map +1 -0
  31. package/dist/interrupts/MindedInterruptSessionManager.d.ts +13 -0
  32. package/dist/interrupts/MindedInterruptSessionManager.d.ts.map +1 -0
  33. package/dist/interrupts/MindedInterruptSessionManager.js +151 -0
  34. package/dist/interrupts/MindedInterruptSessionManager.js.map +1 -0
  35. package/dist/interrupts/interruptSessionManagerFactory.d.ts +3 -0
  36. package/dist/interrupts/interruptSessionManagerFactory.d.ts.map +1 -0
  37. package/dist/interrupts/interruptSessionManagerFactory.js +46 -0
  38. package/dist/interrupts/interruptSessionManagerFactory.js.map +1 -0
  39. package/dist/nodes/addAppToolNode.js +1 -1
  40. package/dist/nodes/addAppToolNode.js.map +1 -1
  41. package/dist/nodes/addBrowserTaskNode.js +2 -2
  42. package/dist/nodes/addBrowserTaskNode.js.map +1 -1
  43. package/dist/nodes/addHumanInTheLoopNode.d.ts.map +1 -1
  44. package/dist/nodes/addHumanInTheLoopNode.js +2 -1
  45. package/dist/nodes/addHumanInTheLoopNode.js.map +1 -1
  46. package/dist/nodes/addJumpToNode.js +1 -1
  47. package/dist/nodes/addJumpToNode.js.map +1 -1
  48. package/dist/nodes/addPromptNode.d.ts.map +1 -1
  49. package/dist/nodes/addPromptNode.js +88 -7
  50. package/dist/nodes/addPromptNode.js.map +1 -1
  51. package/dist/nodes/addToolNode.d.ts.map +1 -1
  52. package/dist/nodes/addToolNode.js +5 -1
  53. package/dist/nodes/addToolNode.js.map +1 -1
  54. package/dist/nodes/addToolRunNode.d.ts.map +1 -1
  55. package/dist/nodes/addToolRunNode.js +1 -0
  56. package/dist/nodes/addToolRunNode.js.map +1 -1
  57. package/dist/nodes/compilePrompt.d.ts.map +1 -1
  58. package/dist/nodes/compilePrompt.js +1 -0
  59. package/dist/nodes/compilePrompt.js.map +1 -1
  60. package/dist/platform/mindedConnection.js +12 -12
  61. package/dist/platform/mindedConnection.js.map +1 -1
  62. package/dist/platform/mindedConnectionTypes.d.ts +151 -1
  63. package/dist/platform/mindedConnectionTypes.d.ts.map +1 -1
  64. package/dist/platform/mindedConnectionTypes.js +9 -0
  65. package/dist/platform/mindedConnectionTypes.js.map +1 -1
  66. package/dist/playbooks/playbooks.d.ts.map +1 -1
  67. package/dist/playbooks/playbooks.js +33 -12
  68. package/dist/playbooks/playbooks.js.map +1 -1
  69. package/dist/types/Agent.types.d.ts +2 -0
  70. package/dist/types/Agent.types.d.ts.map +1 -1
  71. package/dist/types/Agent.types.js.map +1 -1
  72. package/dist/types/LangGraph.types.d.ts +2 -2
  73. package/dist/types/LangGraph.types.d.ts.map +1 -1
  74. package/dist/types/LangGraph.types.js +3 -1
  75. package/dist/types/LangGraph.types.js.map +1 -1
  76. package/dist/voice/voiceSession.d.ts.map +1 -1
  77. package/dist/voice/voiceSession.js +9 -1
  78. package/dist/voice/voiceSession.js.map +1 -1
  79. package/docs/low-code-editor/nodes.md +21 -12
  80. package/docs/low-code-editor/playbooks.md +50 -32
  81. package/package.json +1 -1
  82. package/src/agent.ts +93 -19
  83. package/src/cli/index.ts +14 -14
  84. package/src/edges/edgeFactory.ts +2 -2
  85. package/src/interrupts/BaseInterruptSessionManager.ts +96 -0
  86. package/src/interrupts/MemoryInterruptSessionManager.ts +63 -0
  87. package/src/interrupts/MindedInterruptSessionManager.ts +162 -0
  88. package/src/interrupts/interruptSessionManagerFactory.ts +20 -0
  89. package/src/nodes/addAppToolNode.ts +1 -1
  90. package/src/nodes/addBrowserTaskNode.ts +3 -3
  91. package/src/nodes/addHumanInTheLoopNode.ts +2 -1
  92. package/src/nodes/addJumpToNode.ts +1 -1
  93. package/src/nodes/addPromptNode.ts +95 -11
  94. package/src/nodes/addToolNode.ts +6 -2
  95. package/src/nodes/addToolRunNode.ts +1 -1
  96. package/src/nodes/compilePrompt.ts +2 -2
  97. package/src/platform/mindedConnection.ts +12 -12
  98. package/src/platform/mindedConnectionTypes.ts +187 -0
  99. package/src/playbooks/playbooks.ts +36 -13
  100. package/src/types/Agent.types.ts +2 -0
  101. package/src/types/LangGraph.types.ts +3 -1
  102. package/src/voice/voiceSession.ts +9 -1
package/src/agent.ts CHANGED
@@ -36,6 +36,8 @@ import {
36
36
  import { createLlmInstance } from './llm/createLlmInstance';
37
37
  import { createCheckpointSaver } from './checkpointer/checkpointSaverFactory';
38
38
  import { getConfig } from './platform/config';
39
+ import { InterruptSessionManager, InterruptType } from './interrupts/BaseInterruptSessionManager';
40
+ import { createInterruptSessionManager } from './interrupts/interruptSessionManagerFactory';
39
41
  import { BaseMessage, HumanMessage } from '@langchain/core/messages';
40
42
  import triggerTypeToDefaultMessage from './triggers/triggerTypeToDefaultMessage';
41
43
  import appActionRunnerToolCreator from './internalTools/appActionRunnerTool';
@@ -55,6 +57,7 @@ type CreateAgentParams<Memory> = {
55
57
  tools: Tool<any, Memory>[];
56
58
  platformToken?: string;
57
59
  memorySaver?: BaseCheckpointSaver;
60
+ interruptSessionManager?: InterruptSessionManager;
58
61
  };
59
62
 
60
63
  /**
@@ -87,6 +90,8 @@ export class Agent {
87
90
 
88
91
  // Langgraph memory saver. In memory for local development, Custom for Platform
89
92
  private checkpointer!: BaseCheckpointSaver;
93
+ // Interrupt session manager. In memory for local development, Custom for Platform
94
+ public interruptSessionManager!: InterruptSessionManager;
90
95
  // Langgraph compiled graph
91
96
  public compiledGraph!: CompiledGraph;
92
97
  // Cache for secrets to avoid repeated API calls
@@ -157,7 +162,7 @@ export class Agent {
157
162
 
158
163
  private async init(params: CreateAgentParams<z.infer<typeof this.memorySchema>>): Promise<void> {
159
164
  try {
160
- const { config, tools, memorySaver } = params;
165
+ const { config, tools, memorySaver, interruptSessionManager } = params;
161
166
  const { runLocally } = getConfig();
162
167
  if (!runLocally) {
163
168
  await mindedConnection.start();
@@ -196,6 +201,7 @@ export class Agent {
196
201
  const libraryActionsRunnerTools = this.initLibraryActionsRunnerTools();
197
202
  this.tools = [...tools, ...appActionsRunnerTools, ...libraryActionsRunnerTools];
198
203
  this.checkpointer = memorySaver || createCheckpointSaver();
204
+ this.interruptSessionManager = interruptSessionManager || createInterruptSessionManager();
199
205
 
200
206
  // call here methods that needs environment variables to be loaded
201
207
  this.llm = createLlmInstance(config.llm);
@@ -387,6 +393,10 @@ export class Agent {
387
393
  // Try to parse empty object through the schema to apply default values
388
394
  // If parsing fails due to required fields without defaults, use empty object as fallback
389
395
  const parseResult = this.memorySchema.safeParse({});
396
+ if (!parseResult.success) {
397
+ logger.error({ msg: 'Failed to parse memory schema', error: parseResult.error });
398
+ throw new Error('Failed to parse memory schema');
399
+ }
390
400
  const initialMemory = parseResult.success ? parseResult.data : {};
391
401
 
392
402
  const initialState = {
@@ -426,14 +436,14 @@ export class Agent {
426
436
 
427
437
  return appName
428
438
  ? createHistoryStep<AppTriggerHistoryStep>(currentHistory, {
429
- ...baseStep,
430
- type: NodeType.TRIGGER,
431
- appName,
432
- })
439
+ ...baseStep,
440
+ type: NodeType.TRIGGER,
441
+ appName,
442
+ })
433
443
  : createHistoryStep<TriggerHistoryStep>(currentHistory, {
434
- ...baseStep,
435
- type: NodeType.TRIGGER,
436
- });
444
+ ...baseStep,
445
+ type: NodeType.TRIGGER,
446
+ });
437
447
  }
438
448
 
439
449
  /**
@@ -464,10 +474,24 @@ export class Agent {
464
474
  * });
465
475
  * ```
466
476
  */
467
- public async invoke({ triggerBody, triggerName, sessionId, appName }: AgentInvokeParams) {
477
+ public async invoke({ triggerBody, triggerName, sessionId, appName, bypassSessionCheck }: AgentInvokeParams): Promise<any> {
468
478
  sessionId = sessionId ?? uuidv4();
469
479
  try {
470
480
  await this.waitForInitialization();
481
+
482
+ // Try to acquire lock atomically (unless bypassing session check)
483
+ if (!bypassSessionCheck && !(await this.interruptSessionManager.lock(sessionId))) {
484
+ // Could not acquire lock, session is being processed - enqueue the message
485
+ logger.info({ msg: 'Enqueuing message', sessionId, triggerBody, triggerName, appName });
486
+ await this.interruptSessionManager.enqueueMessage(sessionId, {
487
+ triggerBody,
488
+ triggerName,
489
+ appName,
490
+ });
491
+ return;
492
+ }
493
+ // Session lock acquired, proceed with processing
494
+
471
495
  let messages: Array<BaseMessage> = [];
472
496
  let memoryUpdate = {};
473
497
  let sessionType: SessionType = SessionType.TEXT;
@@ -503,7 +527,8 @@ export class Agent {
503
527
  const handlerResult = results.find((r) => r !== undefined);
504
528
  if (handlerResult) {
505
529
  if (!handlerResult.isQualified) {
506
- logger.debug({ msg: '[Trigger] Disqualified', triggerName, triggerBody, sessionId });
530
+ logger.info({ message: '[Trigger] Disqualified', triggerName, triggerBody, sessionId });
531
+ await this.interruptSessionManager.release(sessionId);
507
532
  return;
508
533
  }
509
534
  memoryUpdate = handlerResult.memory || {};
@@ -536,13 +561,37 @@ export class Agent {
536
561
 
537
562
  let res;
538
563
  // Resume interruption
539
- if (state.tasks?.[0]?.interrupts?.length > 0) {
540
- res = await this.compiledGraph.invoke(
541
- new Command({
542
- resume: { memory: memoryUpdate, messages, history: historyStep, sessionId, sessionType },
543
- }),
544
- langraphConfig,
545
- );
564
+ if (state.tasks?.[state.tasks.length - 1]?.interrupts?.length > 0) {
565
+ const lastTask = state.tasks[state.tasks.length - 1];
566
+ const interrupt = lastTask.interrupts[0];
567
+ const interruptValue = (interrupt as any).value;
568
+
569
+ if (interruptValue?.type === InterruptType.HUMAN_IN_THE_LOOP) {
570
+ // For HUMAN_IN_THE_LOOP, use resume with the full object
571
+ res = await this.compiledGraph.invoke(
572
+ new Command({
573
+ resume: { memory: memoryUpdate, messages, history: historyStep, sessionId, sessionType, overrideStartFromNodeId: null },
574
+ }),
575
+ langraphConfig,
576
+ );
577
+ } else if (interruptValue?.type === InterruptType.NEW_TRIGGERS) {
578
+ // For NEW_TRIGGERS, check if there's an updateStateObject to apply first
579
+ const finalState = { memory: memoryUpdate, messages, history: [historyStep], sessionId, sessionType };
580
+ if (interruptValue.updateStateObject) {
581
+ finalState.messages = [...interruptValue.updateStateObject.messages, ...messages];
582
+ finalState.history = [...(interruptValue.updateStateObject.history || []), historyStep];
583
+ //add handlers for other state fields as needed
584
+ }
585
+
586
+ // Then use update with the full object and empty resume
587
+ res = await this.compiledGraph.invoke(
588
+ new Command({
589
+ update: finalState,
590
+ resume: '',
591
+ }),
592
+ langraphConfig,
593
+ );
594
+ }
546
595
  } else if (state.values.overrideStartFromNodeId) {
547
596
  res = await this.compiledGraph.invoke(
548
597
  new Command({
@@ -564,6 +613,26 @@ export class Agent {
564
613
  langraphConfig,
565
614
  );
566
615
  }
616
+ const nextMessage = await this.interruptSessionManager.dequeue(sessionId);
617
+ if (nextMessage) {
618
+ // Dequeue the first message and process it recursively
619
+ // Recursively process the next message with bypass flag
620
+ const invokeParams: AgentInvokeParams = {
621
+ triggerBody: nextMessage.triggerBody,
622
+ triggerName: nextMessage.triggerName,
623
+ sessionId: sessionId,
624
+ bypassSessionCheck: true,
625
+ };
626
+ if (nextMessage.appName) {
627
+ invokeParams.appName = nextMessage.appName;
628
+ }
629
+ logger.info({ msg: 'Invoking next message in the queue', invokeParams });
630
+ return await this.invoke(invokeParams);
631
+ }
632
+
633
+ // Release the session lock
634
+ await this.interruptSessionManager.release(sessionId);
635
+
567
636
  return res;
568
637
  } catch (error: any) {
569
638
  logger.error({
@@ -572,6 +641,10 @@ export class Agent {
572
641
  stack: error.stack,
573
642
  sessionId,
574
643
  });
644
+
645
+ // Release the session lock on error
646
+ await this.interruptSessionManager.release(sessionId);
647
+
575
648
  const state = await this.compiledGraph.getState(this.getLangraphConfig(sessionId));
576
649
  this.emit(AgentEvents.ERROR, { error: error instanceof Error ? error : new Error(JSON.stringify(error)), state: state.values });
577
650
  throw error;
@@ -763,7 +836,7 @@ export class Agent {
763
836
  // Hangup / end session handler
764
837
  connection.on(mindedConnectionSocketMessageType.VOICE_SESSION_END, (message) => {
765
838
  const hangup = message as BaseVoiceMessage;
766
- logger.debug({ message: '[Voice] Dashboard eneded voice session', sessionId: hangup.sessionId });
839
+ logger.debug({ msg: '[Voice] Dashboard eneded voice session', sessionId: hangup.sessionId });
767
840
  const voiceSession = this.voiceSessions.get(hangup.sessionId);
768
841
  if (voiceSession) {
769
842
  voiceSession.hangup();
@@ -812,7 +885,8 @@ export class Agent {
812
885
  voiceId: voiceTrigger.voiceId,
813
886
  });
814
887
  await voiceSession.init();
815
- logger.debug({ message: '[Voice] Voice session initialized', sessionId: params.sessionId });
888
+ logger.debug({ msg: '[Voice] Voice session initialized', sessionId: params.sessionId });
889
+
816
890
  this.voiceSessions.set(params.sessionId, voiceSession);
817
891
 
818
892
  // Emit voice session start event
package/src/cli/index.ts CHANGED
@@ -19,7 +19,7 @@ function setToken(token: string): void {
19
19
  if (!fs.existsSync(envPath)) {
20
20
  // Create .env file with the token
21
21
  fs.writeFileSync(envPath, tokenLine + '\n');
22
- logger.info(`Created ${envPath} and added token`);
22
+ logger.info({ msg: `Created ${envPath} and added token` });
23
23
  } else {
24
24
  // Read existing .env file
25
25
  const envContent = fs.readFileSync(envPath, 'utf8');
@@ -29,12 +29,12 @@ function setToken(token: string): void {
29
29
  // Replace existing token
30
30
  const updatedContent = envContent.replace(/MINDED_CONNECTION_TOKEN=.*/, tokenLine);
31
31
  fs.writeFileSync(envPath, updatedContent);
32
- logger.info(`Updated MINDED_CONNECTION_TOKEN in ${envPath}`);
32
+ logger.info({ msg: `Updated MINDED_CONNECTION_TOKEN in ${envPath}` });
33
33
  } else {
34
34
  // Append token to existing file
35
35
  const newContent = envContent.endsWith('\n') ? envContent + tokenLine + '\n' : envContent + '\n' + tokenLine + '\n';
36
36
  fs.writeFileSync(envPath, newContent);
37
- logger.info(`Added MINDED_CONNECTION_TOKEN to ${envPath}`);
37
+ logger.info({ msg: `Added MINDED_CONNECTION_TOKEN to ${envPath}` });
38
38
  }
39
39
  }
40
40
  }
@@ -44,7 +44,7 @@ function generateLambdaHandler(): void {
44
44
 
45
45
  // Check if minded.json exists
46
46
  if (!fs.existsSync(mindedConfigPath)) {
47
- logger.error('minded.json not found in the current directory');
47
+ logger.error({ msg: 'minded.json not found in the current directory' });
48
48
  process.exit(1);
49
49
  }
50
50
 
@@ -54,14 +54,14 @@ function generateLambdaHandler(): void {
54
54
  const configContent = fs.readFileSync(mindedConfigPath, 'utf8');
55
55
  mindedConfig = JSON.parse(configContent);
56
56
  } catch (error) {
57
- logger.error('Failed to read or parse minded.json:', error);
57
+ logger.error({ msg: 'Failed to read or parse minded.json:', error });
58
58
  process.exit(1);
59
59
  }
60
60
 
61
61
  // Get the agent path
62
62
  const agentPath = mindedConfig.agent;
63
63
  if (!agentPath) {
64
- logger.error('No agent path found in minded.json');
64
+ logger.error({ msg: 'No agent path found in minded.json' });
65
65
  process.exit(1);
66
66
  }
67
67
 
@@ -91,7 +91,7 @@ function generateLambdaHandler(): void {
91
91
  }
92
92
 
93
93
  if (!templateContent) {
94
- logger.error('Could not find Lambda handler template file');
94
+ logger.error({ msg: 'Could not find Lambda handler template file' });
95
95
  process.exit(1);
96
96
  }
97
97
 
@@ -102,10 +102,10 @@ function generateLambdaHandler(): void {
102
102
  // Write the Lambda handler to index.ts at the root
103
103
  const outputPath = path.join(process.cwd(), 'index.ts');
104
104
  fs.writeFileSync(outputPath, templateContent);
105
- logger.info(`Generated Lambda handler at ${outputPath}`);
105
+ logger.info({ msg: `Generated Lambda handler at ${outputPath}` });
106
106
 
107
107
  // Compile the index.ts file using TypeScript
108
- logger.info('Compiling the Lambda handler...');
108
+ logger.info({ msg: 'Compiling the Lambda handler...' });
109
109
 
110
110
  try {
111
111
  // Read tsconfig.json to get the outDir
@@ -138,12 +138,12 @@ function generateLambdaHandler(): void {
138
138
  cwd: process.cwd(),
139
139
  });
140
140
 
141
- logger.info(`Successfully compiled Lambda handler to ${outDir}/index.js`);
141
+ logger.info({ msg: `Successfully compiled Lambda handler to ${outDir}/index.js` });
142
142
  } catch (compileError) {
143
- logger.error({ message: 'Failed to compile Lambda handler', error: compileError });
143
+ logger.error({ msg: 'Failed to compile Lambda handler', error: compileError });
144
144
  }
145
145
  } catch (error) {
146
- logger.error({ message: 'Failed to generate Lambda handler', error: error });
146
+ logger.error({ msg: 'Failed to generate Lambda handler', error: error });
147
147
  process.exit(1);
148
148
  }
149
149
  }
@@ -155,14 +155,14 @@ function main() {
155
155
  if (command === 'token') {
156
156
  const token = args[1];
157
157
  if (!token) {
158
- logger.error('Please provide a token');
158
+ logger.error({ msg: 'Please provide a token' });
159
159
  process.exit(1);
160
160
  }
161
161
  setToken(token);
162
162
  } else if (command === 'generate-lambda-ts-handler') {
163
163
  generateLambdaHandler();
164
164
  } else {
165
- logger.error('Unknown command. Available commands: token, generate-lambda-ts-handler');
165
+ logger.error({ msg: 'Unknown command. Available commands: token, generate-lambda-ts-handler' });
166
166
  process.exit(1);
167
167
  }
168
168
  }
@@ -60,7 +60,7 @@ export const edgeFactory = ({
60
60
  if (result) {
61
61
  return result;
62
62
  } else {
63
- logger.debug('No logical conditions matched, continuing to prompt conditions');
63
+ logger.debug({ msg: 'No logical conditions matched, continuing to prompt conditions' });
64
64
  }
65
65
  }
66
66
 
@@ -85,7 +85,7 @@ export const edgeFactory = ({
85
85
 
86
86
  // Fallback: stay at current source node
87
87
  const source = originalNode?.name || sourceNode;
88
- logger.info(`No conditions matched, returning to source: ${source}`);
88
+ logger.info({ msg: `No conditions matched, returning to source: ${source}` });
89
89
  return source;
90
90
  };
91
91
  };
@@ -0,0 +1,96 @@
1
+ import { State } from '../types/LangGraph.types';
2
+ import { GraphInterrupt } from '@langchain/langgraph';
3
+ import { logger } from '../utils/logger';
4
+
5
+ export enum InterruptType {
6
+ NEW_TRIGGERS = 'NEW_TRIGGERS',
7
+ HUMAN_IN_THE_LOOP = 'HUMAN_IN_THE_LOOP',
8
+ }
9
+
10
+ export const QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH = 'QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH';
11
+
12
+ export interface QueuedMessage {
13
+ triggerBody: any;
14
+ triggerName: string;
15
+ appName?: string;
16
+ }
17
+
18
+ // export interface UpdateStateObject {
19
+ // memory: any;
20
+ // messages: any[];
21
+ // history: any[];
22
+ // sessionId: string;
23
+ // sessionType: SessionType;
24
+ // }
25
+ export interface InterruptPayload {
26
+ type: InterruptType.NEW_TRIGGERS;
27
+ updateStateObject?: Partial<State>;
28
+ id?: string;
29
+ }
30
+
31
+ export interface InterruptSessionManager {
32
+ isProcessed(sessionId: string): boolean | Promise<boolean>;
33
+ /**
34
+ * Atomically attempts to acquire a lock for the given session.
35
+ * This method should only succeed if the session is not already being processed.
36
+ *
37
+ * @param sessionId - The session ID to lock
38
+ * @returns true if the lock was successfully acquired, false if the session is already locked
39
+ */
40
+ lock(sessionId: string): boolean | Promise<boolean>;
41
+ release(sessionId: string): void | Promise<void>;
42
+ enqueueMessage(sessionId: string, message: QueuedMessage): void | Promise<void>;
43
+ dequeueAll(sessionId: string): QueuedMessage[] | Promise<QueuedMessage[]>;
44
+ dequeue(sessionId: string): QueuedMessage | null | Promise<QueuedMessage | null>;
45
+ checkQueueAndInterrupt(sessionId: string, updateStateObject?: Partial<State>): Promise<boolean>;
46
+ }
47
+
48
+ export abstract class BaseInterruptSessionManager implements InterruptSessionManager {
49
+ // Abstract methods that subclasses must implement for queue management
50
+ abstract isProcessed(sessionId: string): boolean | Promise<boolean>;
51
+ /**
52
+ * Atomically attempts to acquire a lock for the given session.
53
+ * This method should only succeed if the session is not already being processed.
54
+ * Implementations must ensure this operation is atomic to prevent race conditions.
55
+ *
56
+ * @param sessionId - The session ID to lock
57
+ * @returns true if the lock was successfully acquired, false if the session is already locked
58
+ */
59
+ abstract lock(sessionId: string): boolean | Promise<boolean>;
60
+ abstract release(sessionId: string): void | Promise<void>;
61
+ abstract enqueueMessage(sessionId: string, message: QueuedMessage): void | Promise<void>;
62
+ abstract dequeueAll(sessionId: string): QueuedMessage[] | Promise<QueuedMessage[]>;
63
+ abstract dequeue(sessionId: string): QueuedMessage | null | Promise<QueuedMessage | null>;
64
+
65
+ // Abstract method to check if queue has messages - this is the only queue-specific part
66
+ protected abstract hasQueuedMessages(sessionId: string): boolean | Promise<boolean>;
67
+ protected abstract getQueuedMessages(sessionId: string): QueuedMessage[] | Promise<QueuedMessage[]>;
68
+
69
+ // Common implementation of checkQueueAndInterrupt
70
+ async checkQueueAndInterrupt(sessionId: string, updateStateObject?: Partial<State>): Promise<boolean> {
71
+ if (await this.hasQueuedMessages(sessionId)) {
72
+ logger.trace({ msg: 'graph has queued messagess, interrupting graph', sessionId });
73
+
74
+ // Interrupt the graph with NEW_TRIGGERS flag and optional updateStateObject
75
+ const interruptPayload: InterruptPayload = { type: InterruptType.NEW_TRIGGERS };
76
+ if (updateStateObject) {
77
+ interruptPayload.updateStateObject = {
78
+ messages: updateStateObject.messages,
79
+ memory: updateStateObject.memory,
80
+ history: updateStateObject.history,
81
+ sessionId: updateStateObject.sessionId,
82
+ sessionType: updateStateObject.sessionType,
83
+ };
84
+ }
85
+
86
+ throw new GraphInterrupt([
87
+ {
88
+ value: interruptPayload,
89
+ when: 'during',
90
+ resumable: true,
91
+ },
92
+ ]);
93
+ }
94
+ return false;
95
+ }
96
+ }
@@ -0,0 +1,63 @@
1
+ import { BaseInterruptSessionManager, QueuedMessage, QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH } from './BaseInterruptSessionManager';
2
+
3
+ export class MemoryInterruptSessionManager extends BaseInterruptSessionManager {
4
+ private sessionProcessing: Map<string, boolean> = new Map();
5
+ private sessionMessageQueues: Map<string, QueuedMessage[]> = new Map();
6
+
7
+ isProcessed(sessionId: string): boolean {
8
+ return this.sessionProcessing.get(sessionId) || false;
9
+ }
10
+
11
+ lock(sessionId: string): boolean {
12
+ const isCurrentlyProcessed = this.sessionProcessing.get(sessionId) || false;
13
+ if (isCurrentlyProcessed) {
14
+ return false; // Could not acquire lock
15
+ }
16
+ this.sessionProcessing.set(sessionId, true);
17
+ return true; // Successfully acquired lock
18
+ }
19
+
20
+ release(sessionId: string): void {
21
+ this.sessionProcessing.delete(sessionId);
22
+ }
23
+
24
+ enqueueMessage(sessionId: string, message: QueuedMessage): void {
25
+ if (!this.sessionMessageQueues.has(sessionId)) {
26
+ this.sessionMessageQueues.set(sessionId, []);
27
+ }
28
+ this.sessionMessageQueues.get(sessionId)!.push(message);
29
+ }
30
+
31
+ dequeueAll(sessionId: string): QueuedMessage[] {
32
+ const messages = this.sessionMessageQueues.get(sessionId) || [];
33
+ const result = [...messages];
34
+ this.sessionMessageQueues.set(sessionId, []);
35
+ return result;
36
+ }
37
+
38
+ dequeue(sessionId: string): QueuedMessage | null {
39
+ const messages = this.sessionMessageQueues.get(sessionId) || [];
40
+ if (messages.length === 0) {
41
+ return null;
42
+ }
43
+ const firstMessage = messages.shift()!;
44
+ this.sessionMessageQueues.set(sessionId, messages);
45
+
46
+ // If the dequeued message has the special trigger name, recursively dequeue the next one
47
+ if (firstMessage.triggerName === QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH) {
48
+ return this.dequeue(sessionId);
49
+ }
50
+
51
+ return firstMessage;
52
+ }
53
+
54
+ // Implementation of abstract methods from BaseInterruptSessionManager
55
+ protected hasQueuedMessages(sessionId: string): boolean {
56
+ const queue = this.sessionMessageQueues.get(sessionId) || [];
57
+ return queue.length > 0;
58
+ }
59
+
60
+ protected getQueuedMessages(sessionId: string): QueuedMessage[] {
61
+ return this.sessionMessageQueues.get(sessionId) || [];
62
+ }
63
+ }
@@ -0,0 +1,162 @@
1
+ import { BaseInterruptSessionManager, QueuedMessage, QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH } from './BaseInterruptSessionManager';
2
+ import * as mindedConnection from '../platform/mindedConnection';
3
+ import {
4
+ mindedConnectionSocketMessageType,
5
+ InterruptSessionIsProcessedRequest,
6
+ InterruptSessionIsProcessedResponse,
7
+ InterruptSessionLockRequest,
8
+ InterruptSessionLockResponse,
9
+ InterruptSessionReleaseRequest,
10
+ InterruptSessionReleaseResponse,
11
+ InterruptSessionEnqueueRequest,
12
+ InterruptSessionEnqueueResponse,
13
+ InterruptSessionDequeueAllRequest,
14
+ InterruptSessionDequeueAllResponse,
15
+ InterruptSessionDequeueRequest,
16
+ InterruptSessionDequeueResponse,
17
+ InterruptSessionHasMessagesRequest,
18
+ InterruptSessionHasMessagesResponse,
19
+ InterruptSessionGetMessagesRequest,
20
+ InterruptSessionGetMessagesResponse,
21
+ } from '../platform/mindedConnectionTypes';
22
+
23
+ export class MindedInterruptSessionManager extends BaseInterruptSessionManager {
24
+ constructor() {
25
+ super();
26
+ }
27
+
28
+ async isProcessed(sessionId: string): Promise<boolean> {
29
+ try {
30
+ const response = await mindedConnection.awaitEmit<InterruptSessionIsProcessedRequest, InterruptSessionIsProcessedResponse>(
31
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_IS_PROCESSED,
32
+ {
33
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_IS_PROCESSED,
34
+ sessionId,
35
+ },
36
+ );
37
+ return response.isProcessed ?? false;
38
+ } catch (error) {
39
+ console.error('Error checking if session is processed:', error);
40
+ return false;
41
+ }
42
+ }
43
+
44
+ async lock(sessionId: string): Promise<boolean> {
45
+ try {
46
+ const response = await mindedConnection.awaitEmit<InterruptSessionLockRequest, InterruptSessionLockResponse>(
47
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_LOCK,
48
+ {
49
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_LOCK,
50
+ sessionId,
51
+ },
52
+ );
53
+ return response.lockAcquired ?? false;
54
+ } catch (error) {
55
+ console.error('Error locking session:', error);
56
+ return false;
57
+ }
58
+ }
59
+
60
+ async release(sessionId: string): Promise<void> {
61
+ try {
62
+ await mindedConnection.awaitEmit<InterruptSessionReleaseRequest, InterruptSessionReleaseResponse>(
63
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_RELEASE,
64
+ {
65
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_RELEASE,
66
+ sessionId,
67
+ },
68
+ );
69
+ } catch (error) {
70
+ console.error('Error releasing session:', error);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ async enqueueMessage(sessionId: string, message: QueuedMessage): Promise<void> {
76
+ try {
77
+ await mindedConnection.awaitEmit<InterruptSessionEnqueueRequest, InterruptSessionEnqueueResponse>(
78
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_ENQUEUE,
79
+ {
80
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_ENQUEUE,
81
+ sessionId,
82
+ message,
83
+ },
84
+ );
85
+ } catch (error) {
86
+ console.error('Error enqueuing message:', error);
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ async dequeueAll(sessionId: string): Promise<QueuedMessage[]> {
92
+ try {
93
+ const response = await mindedConnection.awaitEmit<InterruptSessionDequeueAllRequest, InterruptSessionDequeueAllResponse>(
94
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_DEQUEUE_ALL,
95
+ {
96
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_DEQUEUE_ALL,
97
+ sessionId,
98
+ },
99
+ );
100
+ return response.messages ?? [];
101
+ } catch (error) {
102
+ console.error('Error dequeuing all messages:', error);
103
+ return [];
104
+ }
105
+ }
106
+
107
+ async dequeue(sessionId: string): Promise<QueuedMessage | null> {
108
+ try {
109
+ const response = await mindedConnection.awaitEmit<InterruptSessionDequeueRequest, InterruptSessionDequeueResponse>(
110
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_DEQUEUE,
111
+ {
112
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_DEQUEUE,
113
+ sessionId,
114
+ },
115
+ );
116
+ const message = response.message ?? null;
117
+
118
+ // If the dequeued message has the special trigger name, recursively dequeue the next one
119
+ if (message && message.triggerName === QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH) {
120
+ return this.dequeue(sessionId);
121
+ }
122
+
123
+ return message;
124
+ } catch (error) {
125
+ console.error('Error dequeuing message:', error);
126
+ return null;
127
+ }
128
+ }
129
+
130
+ // Implementation of abstract methods from BaseInterruptSessionManager
131
+ protected async hasQueuedMessages(sessionId: string): Promise<boolean> {
132
+ try {
133
+ const response = await mindedConnection.awaitEmit<InterruptSessionHasMessagesRequest, InterruptSessionHasMessagesResponse>(
134
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_HAS_MESSAGES,
135
+ {
136
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_HAS_MESSAGES,
137
+ sessionId,
138
+ },
139
+ );
140
+ return response.hasMessages ?? false;
141
+ } catch (error) {
142
+ console.error('Error checking if session has messages:', error);
143
+ return false;
144
+ }
145
+ }
146
+
147
+ protected async getQueuedMessages(sessionId: string): Promise<QueuedMessage[]> {
148
+ try {
149
+ const response = await mindedConnection.awaitEmit<InterruptSessionGetMessagesRequest, InterruptSessionGetMessagesResponse>(
150
+ mindedConnectionSocketMessageType.INTERRUPT_SESSION_GET_MESSAGES,
151
+ {
152
+ type: mindedConnectionSocketMessageType.INTERRUPT_SESSION_GET_MESSAGES,
153
+ sessionId,
154
+ },
155
+ );
156
+ return response.messages ?? [];
157
+ } catch (error) {
158
+ console.error('Error getting queued messages:', error);
159
+ return [];
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,20 @@
1
+ import { InterruptSessionManager } from './BaseInterruptSessionManager';
2
+ import { MemoryInterruptSessionManager } from './MemoryInterruptSessionManager';
3
+ import { MindedInterruptSessionManager } from './MindedInterruptSessionManager';
4
+ import * as mindedConnection from '../platform/mindedConnection';
5
+ import { getConfig } from '../platform/config';
6
+ import { logger } from '../utils/logger';
7
+
8
+ export function createInterruptSessionManager(): InterruptSessionManager {
9
+ const { runLocally } = getConfig();
10
+ if (runLocally) {
11
+ logger.info({ msg: 'Using memory interrupt session manager' });
12
+ return new MemoryInterruptSessionManager();
13
+ } else {
14
+ if (!mindedConnection.isConnected()) {
15
+ throw new Error('MindedConnection is required for platform interrupt session manager');
16
+ }
17
+ logger.info({ msg: 'Using Minded interrupt session manager' });
18
+ return new MindedInterruptSessionManager();
19
+ }
20
+ }
@@ -69,7 +69,7 @@ export const addAppToolNode = async ({
69
69
  User instructions for choosing tool parameters are:
70
70
  ${node.prompt ? `${node.prompt}` : 'no instructions set by the user'}`;
71
71
 
72
- const compiledPrompt = compilePrompt(message, { state, currentTime: new Date().toISOString() });
72
+ const compiledPrompt = compilePrompt(message, { memory: state.memory, system: { currentTime: new Date().toISOString() } });
73
73
 
74
74
  const AIToolCallMessage = await llm
75
75
  .bindTools([tool], {