@loopstack/meeting-notes-example-workflow 0.20.6 → 0.21.0

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 (29) hide show
  1. package/README.md +40 -40
  2. package/dist/documents/meeting-notes-document.d.ts +2 -5
  3. package/dist/documents/meeting-notes-document.d.ts.map +1 -1
  4. package/dist/documents/meeting-notes-document.js +3 -11
  5. package/dist/documents/meeting-notes-document.js.map +1 -1
  6. package/dist/documents/meeting-notes-document.yaml +10 -10
  7. package/dist/documents/optimized-notes-document.d.ts +6 -3
  8. package/dist/documents/optimized-notes-document.d.ts.map +1 -1
  9. package/dist/documents/optimized-notes-document.js +7 -11
  10. package/dist/documents/optimized-notes-document.js.map +1 -1
  11. package/dist/documents/optimized-notes-document.yaml +33 -33
  12. package/dist/meeting-notes-example.module.js +3 -3
  13. package/dist/meeting-notes-example.module.js.map +1 -1
  14. package/dist/meeting-notes.ui.yaml +10 -0
  15. package/dist/meeting-notes.workflow.d.ts +15 -16
  16. package/dist/meeting-notes.workflow.d.ts.map +1 -1
  17. package/dist/meeting-notes.workflow.js +57 -41
  18. package/dist/meeting-notes.workflow.js.map +1 -1
  19. package/package.json +4 -4
  20. package/src/__tests__/meeting-notes.workflow.spec.ts +87 -168
  21. package/src/documents/meeting-notes-document.ts +5 -9
  22. package/src/documents/meeting-notes-document.yaml +10 -10
  23. package/src/documents/optimized-notes-document.ts +9 -7
  24. package/src/documents/optimized-notes-document.yaml +33 -33
  25. package/src/meeting-notes-example.module.ts +3 -3
  26. package/src/meeting-notes.ui.yaml +10 -0
  27. package/src/meeting-notes.workflow.ts +51 -33
  28. package/dist/meeting-notes.workflow.yaml +0 -77
  29. package/src/meeting-notes.workflow.yaml +0 -77
@@ -1,9 +1,8 @@
1
1
  import { TestingModule } from '@nestjs/testing';
2
- import { AiGenerateDocument, AiModule } from '@loopstack/ai-module';
3
- import { RunContext, generateObjectFingerprint, getBlockTools } from '@loopstack/common';
4
- import { WorkflowProcessorService } from '@loopstack/core';
5
- import { CoreUiModule, CreateDocument } from '@loopstack/core-ui-module';
6
- import { ToolMock, createWorkflowTest } from '@loopstack/testing';
2
+ import { ClaudeGenerateDocument, ClaudeModule } from '@loopstack/claude-module';
3
+ import { RunContext, WorkflowEntity, getBlockTools } from '@loopstack/common';
4
+ import { LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
5
+ import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';
7
6
  import { MeetingNotesDocument } from '../documents/meeting-notes-document';
8
7
  import { OptimizedNotesDocument } from '../documents/optimized-notes-document';
9
8
  import { MeetingNotesWorkflow } from '../meeting-notes.workflow';
@@ -13,30 +12,21 @@ describe('MeetingNotesWorkflow', () => {
13
12
  let workflow: MeetingNotesWorkflow;
14
13
  let processor: WorkflowProcessorService;
15
14
 
16
- let mockCreateDocument: ToolMock;
17
-
18
- const mockInitialNotes = {
19
- text: `
20
- - meeting 1.1.2025
21
- - budget: need 2 cut costs sarah said
22
- - hire new person?? --> marketing
23
- - vendor pricing - follow up needed by anna`,
24
- };
15
+ let mockClaudeGenerateDocument: ToolMock;
25
16
 
26
17
  beforeEach(async () => {
27
18
  module = await createWorkflowTest()
28
19
  .forWorkflow(MeetingNotesWorkflow)
29
- .withImports(CoreUiModule, AiModule)
20
+ .withImports(LoopCoreModule, ClaudeModule)
30
21
  .withProvider(MeetingNotesDocument)
31
22
  .withProvider(OptimizedNotesDocument)
32
- .withToolOverride(CreateDocument)
33
- .withToolOverride(AiGenerateDocument)
23
+ .withToolOverride(ClaudeGenerateDocument)
34
24
  .compile();
35
25
 
36
26
  workflow = module.get(MeetingNotesWorkflow);
37
27
  processor = module.get(WorkflowProcessorService);
38
28
 
39
- mockCreateDocument = module.get(CreateDocument);
29
+ mockClaudeGenerateDocument = module.get(ClaudeGenerateDocument);
40
30
  });
41
31
 
42
32
  afterEach(async () => {
@@ -46,152 +36,103 @@ describe('MeetingNotesWorkflow', () => {
46
36
  describe('initialization', () => {
47
37
  it('should be defined with correct tools', () => {
48
38
  expect(workflow).toBeDefined();
49
- expect(getBlockTools(workflow)).toContain('createDocument');
50
- expect(getBlockTools(workflow)).toContain('aiGenerateDocument');
39
+ expect(getBlockTools(workflow)).toContain('claudeGenerateDocument');
51
40
  });
52
41
  });
53
42
 
54
43
  describe('initial step', () => {
55
- const context = {} as RunContext;
56
-
57
44
  it('should execute initial step and stop at waiting_for_response', async () => {
58
- mockCreateDocument.execute.mockResolvedValue({
59
- data: { content: mockInitialNotes },
60
- });
45
+ const context = createStatelessContext();
61
46
 
62
47
  const result = await processor.process(workflow, {}, context);
63
48
 
64
- // Should execute without errors and stop at waiting_for_response (manual step)
65
49
  expect(result.hasError).toBe(false);
66
50
  expect(result.stop).toBe(true);
51
+ expect(result.place).toBe('waiting_for_response');
67
52
 
68
- // Should call CreateDocument once for the initial form
69
- expect(mockCreateDocument.execute).toHaveBeenCalledTimes(1);
70
- expect(mockCreateDocument.execute).toHaveBeenCalledWith(
53
+ expect(result.documents).toHaveLength(1);
54
+ expect(result.documents[0]).toEqual(
71
55
  expect.objectContaining({
72
- id: 'input',
73
- update: {
74
- content: {
75
- text: expect.stringContaining('1.1.2025'),
76
- },
77
- },
56
+ className: 'MeetingNotesDocument',
57
+ content: expect.objectContaining({
58
+ text: expect.stringContaining('1.1.2025'),
59
+ }),
78
60
  }),
79
- expect.anything(),
80
- expect.anything(),
81
- expect.anything(),
82
61
  );
83
-
84
- // // Verify history contains expected places
85
- // const history = result.state.getHistory();
86
- // const places = history.map((h) => h.metadata?.place);
87
- // expect(places).toContain('waiting_for_response');
88
62
  });
89
- });
90
63
 
91
- describe('user response step', () => {
92
- it('should process user response and generate optimized notes', async () => {
93
- const mockUserEditedNotes = {
94
- text: `Meeting Notes - January 1, 2025
95
- - Budget discussion: need to cut costs (Sarah's input)
96
- - Hiring: new person needed for marketing
97
- - Vendor pricing: follow up needed by Anna`,
98
- };
64
+ it('should use custom input text when provided', async () => {
65
+ const context = createStatelessContext();
99
66
 
100
- const mockOptimizedNotes = {
101
- date: '2025-01-01',
102
- summary: 'Budget and hiring discussion',
103
- participants: ['Sarah', 'Anna'],
104
- decisions: ['Cut costs', 'Hire for marketing'],
105
- actionItems: ['Follow up on vendor pricing'],
106
- };
67
+ const result = await processor.process(workflow, { inputText: 'Custom meeting notes here' }, context);
107
68
 
108
- const args = { inputText: mockInitialNotes.text };
109
-
110
- // Create module with existing workflow state
111
- const moduleWithState = await createWorkflowTest()
112
- .forWorkflow(MeetingNotesWorkflow)
113
- .withImports(CoreUiModule, AiModule)
114
- .withProvider(MeetingNotesDocument)
115
- .withProvider(OptimizedNotesDocument)
116
- .withToolOverride(CreateDocument)
117
- .withToolOverride(AiGenerateDocument)
118
- .withExistingWorkflow({
119
- id: '123',
120
- place: 'waiting_for_response',
121
- hashRecord: {
122
- options: generateObjectFingerprint(args), // previously run with same arguments
123
- },
124
- })
125
- .compile();
69
+ expect(result.hasError).toBe(false);
70
+ expect(result.stop).toBe(true);
126
71
 
127
- const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
128
- const processorWithState = moduleWithState.get(WorkflowProcessorService);
72
+ expect(result.documents[0]).toEqual(
73
+ expect.objectContaining({
74
+ content: expect.objectContaining({
75
+ text: expect.stringContaining('Custom meeting notes here'),
76
+ }),
77
+ }),
78
+ );
79
+ });
80
+ });
129
81
 
130
- const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
131
- const mockAiGenerateDocumentWithState: ToolMock = moduleWithState.get(AiGenerateDocument);
82
+ describe('resume from waiting_for_response', () => {
83
+ it('should process user response and call LLM to optimize notes', async () => {
84
+ const workflowId = '00000000-0000-0000-0000-000000000001';
132
85
 
133
- mockCreateDocumentWithState.execute.mockResolvedValue({
134
- data: { content: mockUserEditedNotes },
135
- });
136
- mockAiGenerateDocumentWithState.execute.mockResolvedValue({
137
- data: { content: mockOptimizedNotes },
138
- });
86
+ mockClaudeGenerateDocument.call.mockResolvedValue({ data: undefined });
139
87
 
140
- // Context with user payload for manual transition
141
- const contextWithPayload = {
88
+ const context = {
89
+ workflowEntity: {
90
+ id: workflowId,
91
+ place: 'waiting_for_response',
92
+ documents: [],
93
+ } as Partial<WorkflowEntity>,
142
94
  payload: {
143
95
  transition: {
144
- id: 'user_response',
145
- workflowId: '123',
146
- payload: mockUserEditedNotes,
96
+ id: 'userResponse',
97
+ workflowId,
98
+ payload: { text: 'Cleaned up meeting notes from user' },
147
99
  },
148
100
  },
149
- } as RunContext;
101
+ } as unknown as RunContext;
150
102
 
151
- const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
103
+ const result = await processor.process(workflow, {}, context);
152
104
 
153
- // Should execute and stop at notes_optimized (next manual step)
154
105
  expect(result.hasError).toBe(false);
155
106
  expect(result.stop).toBe(true);
156
-
157
- // Should call CreateDocument once for user response
158
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
159
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledWith(
160
- expect.objectContaining({
161
- id: 'input',
162
- }),
163
- expect.anything(),
164
- expect.anything(),
165
- expect.anything(),
107
+ expect(result.place).toBe('notes_optimized');
108
+
109
+ // User response should have been saved as document
110
+ expect(result.documents).toEqual(
111
+ expect.arrayContaining([
112
+ expect.objectContaining({
113
+ className: 'MeetingNotesDocument',
114
+ }),
115
+ ]),
166
116
  );
167
117
 
168
- // Should call AiGenerateDocument once
169
- expect(mockAiGenerateDocumentWithState.execute).toHaveBeenCalledTimes(1);
170
- expect(mockAiGenerateDocumentWithState.execute).toHaveBeenCalledWith(
118
+ // LLM should have been called to optimize notes
119
+ expect(mockClaudeGenerateDocument.call).toHaveBeenCalledTimes(1);
120
+ expect(mockClaudeGenerateDocument.call).toHaveBeenCalledWith(
171
121
  expect.objectContaining({
172
- llm: {
173
- provider: 'openai',
174
- model: 'gpt-4o',
175
- },
122
+ claude: { model: 'claude-sonnet-4-6' },
123
+ response: expect.objectContaining({ id: 'final' }),
124
+ prompt: expect.stringContaining('meeting notes'),
176
125
  }),
177
- expect.anything(),
178
- expect.anything(),
179
- expect.anything(),
126
+ undefined,
180
127
  );
181
-
182
- // // Verify history contains expected places
183
- // const history = result.state.getHistory();
184
- // const places = history.map((h) => h.metadata?.place);
185
- // expect(places).toContain('response_received');
186
- // expect(places).toContain('notes_optimized');
187
-
188
- await moduleWithState.close();
189
128
  });
190
129
  });
191
130
 
192
- describe('confirm step', () => {
131
+ describe('resume from notes_optimized', () => {
193
132
  it('should complete workflow when user confirms optimized notes', async () => {
194
- const mockFinalNotes = {
133
+ const workflowId = '00000000-0000-0000-0000-000000000002';
134
+
135
+ const optimizedPayload = {
195
136
  date: '2025-01-01',
196
137
  summary: 'Budget discussion with updates',
197
138
  participants: ['Sarah', 'Anna', 'Bob'],
@@ -199,60 +140,38 @@ describe('MeetingNotesWorkflow', () => {
199
140
  actionItems: ['Follow up on vendor pricing by Friday'],
200
141
  };
201
142
 
202
- const args = { inputText: 'any text' };
203
-
204
- // Create module with existing workflow state after AI optimization
205
- const moduleWithState = await createWorkflowTest()
206
- .forWorkflow(MeetingNotesWorkflow)
207
- .withImports(CoreUiModule, AiModule)
208
- .withProvider(MeetingNotesDocument)
209
- .withProvider(OptimizedNotesDocument)
210
- .withToolOverride(CreateDocument)
211
- .withToolOverride(AiGenerateDocument)
212
- .withExistingWorkflow({
213
- id: '123',
143
+ const context = {
144
+ workflowEntity: {
145
+ id: workflowId,
214
146
  place: 'notes_optimized',
215
- hashRecord: {
216
- options: generateObjectFingerprint(args), // previously run with same arguments
217
- },
218
- })
219
- .compile();
220
-
221
- const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
222
- const processorWithState = moduleWithState.get(WorkflowProcessorService);
223
-
224
- const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
225
-
226
- mockCreateDocumentWithState.execute.mockResolvedValue({
227
- data: { content: mockFinalNotes },
228
- });
229
-
230
- // Context with user confirmation for manual transition
231
- const contextWithPayload = {
147
+ documents: [],
148
+ } as Partial<WorkflowEntity>,
232
149
  payload: {
233
150
  transition: {
234
151
  id: 'confirm',
235
- workflowId: '123',
236
- payload: mockFinalNotes,
152
+ workflowId,
153
+ payload: optimizedPayload,
237
154
  },
238
155
  },
239
- } as RunContext;
156
+ } as unknown as RunContext;
240
157
 
241
- const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
158
+ const result = await processor.process(workflow, {}, context);
242
159
 
243
- // Should complete and reach end state
244
160
  expect(result.hasError).toBe(false);
245
161
  expect(result.stop).toBe(false);
162
+ expect(result.place).toBe('end');
163
+
164
+ // Confirmed payload should have been saved as OptimizedNotesDocument
165
+ expect(result.documents).toEqual(
166
+ expect.arrayContaining([
167
+ expect.objectContaining({
168
+ className: 'OptimizedNotesDocument',
169
+ }),
170
+ ]),
171
+ );
246
172
 
247
- // Should call CreateDocument once for final confirmation
248
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
249
-
250
- // // Verify history contains expected places including end
251
- // const history = result.state.getHistory();
252
- // const places = history.map((h) => h.metadata?.place);
253
- // expect(places).toContain('end');
254
-
255
- await moduleWithState.close();
173
+ // No additional LLM calls during confirmation
174
+ expect(mockClaudeGenerateDocument.call).not.toHaveBeenCalled();
256
175
  });
257
176
  });
258
177
  });
@@ -1,18 +1,14 @@
1
1
  import { z } from 'zod';
2
- import { Document, DocumentInterface, Input } from '@loopstack/common';
2
+ import { Document } from '@loopstack/common';
3
3
 
4
4
  export const MeetingNotesDocumentSchema = z.object({
5
5
  text: z.string(),
6
6
  });
7
7
 
8
8
  @Document({
9
- configFile: __dirname + '/meeting-notes-document.yaml',
9
+ schema: MeetingNotesDocumentSchema,
10
+ uiConfig: __dirname + '/meeting-notes-document.yaml',
10
11
  })
11
- export class MeetingNotesDocument implements DocumentInterface {
12
- @Input({
13
- schema: MeetingNotesDocumentSchema,
14
- })
15
- content: {
16
- text: string;
17
- };
12
+ export class MeetingNotesDocument {
13
+ text: string;
18
14
  }
@@ -1,13 +1,13 @@
1
1
  type: document
2
2
  ui:
3
- form:
4
- properties:
5
- text:
6
- title: Text
7
- widget: textarea
8
- actions:
9
- - type: button
10
- widget: button
3
+ widgets:
4
+ - widget: form
11
5
  options:
12
- transition: user_response
13
- label: 'Optimize Notes'
6
+ properties:
7
+ text:
8
+ title: Text
9
+ widget: textarea
10
+ actions:
11
+ - type: button
12
+ transition: userResponse
13
+ label: 'Optimize Notes'
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { Document, DocumentInterface, Input } from '@loopstack/common';
2
+ import { Document } from '@loopstack/common';
3
3
 
4
4
  export const OptimizedMeetingNotesDocumentSchema = z.object({
5
5
  date: z.string(),
@@ -10,11 +10,13 @@ export const OptimizedMeetingNotesDocumentSchema = z.object({
10
10
  });
11
11
 
12
12
  @Document({
13
- configFile: __dirname + '/optimized-notes-document.yaml',
13
+ schema: OptimizedMeetingNotesDocumentSchema,
14
+ uiConfig: __dirname + '/optimized-notes-document.yaml',
14
15
  })
15
- export class OptimizedNotesDocument implements DocumentInterface {
16
- @Input({
17
- schema: OptimizedMeetingNotesDocumentSchema,
18
- })
19
- content: z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
16
+ export class OptimizedNotesDocument {
17
+ date: string;
18
+ summary: string;
19
+ participants: string[];
20
+ decisions: string[];
21
+ actionItems: string[];
20
22
  }
@@ -1,36 +1,36 @@
1
1
  type: document
2
2
  ui:
3
- form:
4
- order:
5
- - date
6
- - summary
7
- - participants
8
- - decisions
9
- - actionItems
10
- properties:
11
- date:
12
- title: Date
13
- summary:
14
- title: Summary
15
- widget: textarea
16
- participants:
17
- title: Participants
18
- collapsed: true
19
- items:
20
- title: Participant
21
- decisions:
22
- title: Decisions
23
- collapsed: true
24
- items:
25
- title: Decision
26
- actionItems:
27
- title: Action Items
28
- collapsed: true
29
- items:
30
- title: Action Item
31
- actions:
32
- - type: button
33
- widget: button
3
+ widgets:
4
+ - widget: form
34
5
  options:
35
- transition: confirm
36
- label: 'Confirm'
6
+ order:
7
+ - date
8
+ - summary
9
+ - participants
10
+ - decisions
11
+ - actionItems
12
+ properties:
13
+ date:
14
+ title: Date
15
+ summary:
16
+ title: Summary
17
+ widget: textarea
18
+ participants:
19
+ title: Participants
20
+ collapsed: true
21
+ items:
22
+ title: Participant
23
+ decisions:
24
+ title: Decisions
25
+ collapsed: true
26
+ items:
27
+ title: Decision
28
+ actionItems:
29
+ title: Action Items
30
+ collapsed: true
31
+ items:
32
+ title: Action Item
33
+ actions:
34
+ - type: button
35
+ transition: confirm
36
+ label: 'Confirm'
@@ -1,12 +1,12 @@
1
1
  import { Module } from '@nestjs/common';
2
- import { AiModule } from '@loopstack/ai-module';
3
- import { CoreUiModule } from '@loopstack/core-ui-module';
2
+ import { ClaudeModule } from '@loopstack/claude-module';
3
+ import { LoopCoreModule } from '@loopstack/core';
4
4
  import { MeetingNotesDocument } from './documents/meeting-notes-document';
5
5
  import { OptimizedNotesDocument } from './documents/optimized-notes-document';
6
6
  import { MeetingNotesWorkflow } from './meeting-notes.workflow';
7
7
 
8
8
  @Module({
9
- imports: [CoreUiModule, AiModule],
9
+ imports: [LoopCoreModule, ClaudeModule],
10
10
  providers: [MeetingNotesWorkflow, MeetingNotesDocument, OptimizedNotesDocument],
11
11
  exports: [MeetingNotesWorkflow],
12
12
  })
@@ -0,0 +1,10 @@
1
+ title: 'Human-in-the-loop Demo (Meeting Notes Optimizer)'
2
+
3
+ description: 'A demo workflow to demonstrate how to use AI to structure meeting notes.'
4
+
5
+ ui:
6
+ form:
7
+ properties:
8
+ inputText:
9
+ title: 'Text'
10
+ widget: 'textarea'
@@ -1,43 +1,61 @@
1
1
  import { z } from 'zod';
2
- import { AiGenerateDocument } from '@loopstack/ai-module';
3
- import { InjectDocument, InjectTool, Input, Runtime, State, Workflow } from '@loopstack/common';
4
- import { CreateDocument } from '@loopstack/core-ui-module';
2
+ import { ClaudeGenerateDocument } from '@loopstack/claude-module';
3
+ import { BaseWorkflow, Final, Initial, InjectTool, Transition, Workflow } from '@loopstack/common';
5
4
  import { MeetingNotesDocument, MeetingNotesDocumentSchema } from './documents/meeting-notes-document';
6
5
  import { OptimizedMeetingNotesDocumentSchema, OptimizedNotesDocument } from './documents/optimized-notes-document';
7
6
 
8
7
  @Workflow({
9
- configFile: __dirname + '/meeting-notes.workflow.yaml',
8
+ uiConfig: __dirname + '/meeting-notes.ui.yaml',
9
+ schema: z.object({
10
+ inputText: z
11
+ .string()
12
+ .default(
13
+ '- meeting 1.1.2025\n- budget: need 2 cut costs sarah said\n- hire new person?? --> marketing\n- vendor pricing - follow up needed by anna',
14
+ ),
15
+ }),
10
16
  })
11
- export class MeetingNotesWorkflow {
12
- @InjectTool() aiGenerateDocument: AiGenerateDocument;
13
- @InjectTool() createDocument: CreateDocument;
14
- @InjectDocument() meetingNotesDocument: MeetingNotesDocument;
15
- @InjectDocument() optimizedNotesDocument: OptimizedNotesDocument;
17
+ export class MeetingNotesWorkflow extends BaseWorkflow<{ inputText: string }> {
18
+ @InjectTool() claudeGenerateDocument: ClaudeGenerateDocument;
16
19
 
17
- @Input({
18
- schema: z.object({
19
- inputText: z
20
- .string()
21
- .default(
22
- '- meeting 1.1.2025\n- budget: need 2 cut costs sarah said\n- hire new person?? --> marketing\n- vendor pricing - follow up needed by anna',
23
- ),
24
- }),
25
- })
26
- args: {
27
- inputText: string;
28
- };
20
+ meetingNotes?: z.infer<typeof MeetingNotesDocumentSchema>;
21
+ optimizedNotes?: z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
29
22
 
30
- @State({
31
- schema: z.object({
32
- meetingNotes: MeetingNotesDocumentSchema.optional(),
33
- optimizedNotes: OptimizedMeetingNotesDocumentSchema.optional(),
34
- }),
35
- })
36
- state: {
37
- meetingNotes?: z.infer<typeof MeetingNotesDocumentSchema>;
38
- optimizedNotes?: z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
39
- };
23
+ @Initial({ to: 'waiting_for_response' })
24
+ async createForm(args: { inputText: string }) {
25
+ await this.repository.save(
26
+ MeetingNotesDocument,
27
+ {
28
+ text: `Unstructured Notes:\n\n${args.inputText}`,
29
+ },
30
+ { id: 'input' },
31
+ );
32
+ }
40
33
 
41
- @Runtime()
42
- runtime: any;
34
+ @Transition({ from: 'waiting_for_response', to: 'response_received', wait: true, schema: MeetingNotesDocumentSchema })
35
+ async userResponse(payload: z.infer<typeof MeetingNotesDocumentSchema>) {
36
+ const result = await this.repository.save(MeetingNotesDocument, payload, { id: 'input' });
37
+ this.meetingNotes = result.content as z.infer<typeof MeetingNotesDocumentSchema>;
38
+ }
39
+
40
+ @Transition({ from: 'response_received', to: 'notes_optimized' })
41
+ async optimizeNotes() {
42
+ await this.claudeGenerateDocument.call({
43
+ claude: { model: 'claude-sonnet-4-6' },
44
+ response: {
45
+ id: 'final',
46
+ document: OptimizedNotesDocument,
47
+ },
48
+ prompt: `Extract all information from the provided meeting notes into the structured document.
49
+
50
+ <Meeting Notes>
51
+ ${this.meetingNotes?.text}
52
+ </Meeting Notes>`,
53
+ });
54
+ }
55
+
56
+ @Final({ from: 'notes_optimized', wait: true, schema: OptimizedMeetingNotesDocumentSchema })
57
+ async confirm(payload: z.infer<typeof OptimizedMeetingNotesDocumentSchema>) {
58
+ const result = await this.repository.save(OptimizedNotesDocument, payload, { id: 'final' });
59
+ this.optimizedNotes = result.content as z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
60
+ }
43
61
  }