@loopstack/meeting-notes-example-workflow 0.20.7 → 0.21.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.
@@ -13,17 +13,39 @@ exports.MeetingNotesWorkflow = void 0;
13
13
  const zod_1 = require("zod");
14
14
  const claude_module_1 = require("@loopstack/claude-module");
15
15
  const common_1 = require("@loopstack/common");
16
- const core_1 = require("@loopstack/core");
17
16
  const meeting_notes_document_1 = require("./documents/meeting-notes-document");
18
17
  const optimized_notes_document_1 = require("./documents/optimized-notes-document");
19
- let MeetingNotesWorkflow = class MeetingNotesWorkflow {
18
+ let MeetingNotesWorkflow = class MeetingNotesWorkflow extends common_1.BaseWorkflow {
20
19
  claudeGenerateDocument;
21
- createDocument;
22
- meetingNotesDocument;
23
- optimizedNotesDocument;
24
- args;
25
- state;
26
- runtime;
20
+ meetingNotes;
21
+ optimizedNotes;
22
+ async createForm(args) {
23
+ await this.repository.save(meeting_notes_document_1.MeetingNotesDocument, {
24
+ text: `Unstructured Notes:\n\n${args.inputText}`,
25
+ }, { id: 'input' });
26
+ }
27
+ async userResponse(payload) {
28
+ const result = await this.repository.save(meeting_notes_document_1.MeetingNotesDocument, payload, { id: 'input' });
29
+ this.meetingNotes = result.content;
30
+ }
31
+ async optimizeNotes() {
32
+ await this.claudeGenerateDocument.call({
33
+ claude: { model: 'claude-sonnet-4-6' },
34
+ response: {
35
+ id: 'final',
36
+ document: optimized_notes_document_1.OptimizedNotesDocument,
37
+ },
38
+ prompt: `Extract all information from the provided meeting notes into the structured document.
39
+
40
+ <Meeting Notes>
41
+ ${this.meetingNotes?.text}
42
+ </Meeting Notes>`,
43
+ });
44
+ }
45
+ async confirm(payload) {
46
+ const result = await this.repository.save(optimized_notes_document_1.OptimizedNotesDocument, payload, { id: 'final' });
47
+ this.optimizedNotes = result.content;
48
+ }
27
49
  };
28
50
  exports.MeetingNotesWorkflow = MeetingNotesWorkflow;
29
51
  __decorate([
@@ -31,43 +53,37 @@ __decorate([
31
53
  __metadata("design:type", claude_module_1.ClaudeGenerateDocument)
32
54
  ], MeetingNotesWorkflow.prototype, "claudeGenerateDocument", void 0);
33
55
  __decorate([
34
- (0, common_1.InjectTool)(),
35
- __metadata("design:type", core_1.CreateDocument)
36
- ], MeetingNotesWorkflow.prototype, "createDocument", void 0);
56
+ (0, common_1.Initial)({ to: 'waiting_for_response' }),
57
+ __metadata("design:type", Function),
58
+ __metadata("design:paramtypes", [Object]),
59
+ __metadata("design:returntype", Promise)
60
+ ], MeetingNotesWorkflow.prototype, "createForm", null);
37
61
  __decorate([
38
- (0, common_1.InjectDocument)(),
39
- __metadata("design:type", meeting_notes_document_1.MeetingNotesDocument)
40
- ], MeetingNotesWorkflow.prototype, "meetingNotesDocument", void 0);
62
+ (0, common_1.Transition)({ from: 'waiting_for_response', to: 'response_received', wait: true, schema: meeting_notes_document_1.MeetingNotesDocumentSchema }),
63
+ __metadata("design:type", Function),
64
+ __metadata("design:paramtypes", [Object]),
65
+ __metadata("design:returntype", Promise)
66
+ ], MeetingNotesWorkflow.prototype, "userResponse", null);
41
67
  __decorate([
42
- (0, common_1.InjectDocument)(),
43
- __metadata("design:type", optimized_notes_document_1.OptimizedNotesDocument)
44
- ], MeetingNotesWorkflow.prototype, "optimizedNotesDocument", void 0);
68
+ (0, common_1.Transition)({ from: 'response_received', to: 'notes_optimized' }),
69
+ __metadata("design:type", Function),
70
+ __metadata("design:paramtypes", []),
71
+ __metadata("design:returntype", Promise)
72
+ ], MeetingNotesWorkflow.prototype, "optimizeNotes", null);
45
73
  __decorate([
46
- (0, common_1.Input)({
74
+ (0, common_1.Final)({ from: 'notes_optimized', wait: true, schema: optimized_notes_document_1.OptimizedMeetingNotesDocumentSchema }),
75
+ __metadata("design:type", Function),
76
+ __metadata("design:paramtypes", [Object]),
77
+ __metadata("design:returntype", Promise)
78
+ ], MeetingNotesWorkflow.prototype, "confirm", null);
79
+ exports.MeetingNotesWorkflow = MeetingNotesWorkflow = __decorate([
80
+ (0, common_1.Workflow)({
81
+ uiConfig: __dirname + '/meeting-notes.ui.yaml',
47
82
  schema: zod_1.z.object({
48
83
  inputText: zod_1.z
49
84
  .string()
50
85
  .default('- 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'),
51
86
  }),
52
- }),
53
- __metadata("design:type", Object)
54
- ], MeetingNotesWorkflow.prototype, "args", void 0);
55
- __decorate([
56
- (0, common_1.State)({
57
- schema: zod_1.z.object({
58
- meetingNotes: meeting_notes_document_1.MeetingNotesDocumentSchema.optional(),
59
- optimizedNotes: optimized_notes_document_1.OptimizedMeetingNotesDocumentSchema.optional(),
60
- }),
61
- }),
62
- __metadata("design:type", Object)
63
- ], MeetingNotesWorkflow.prototype, "state", void 0);
64
- __decorate([
65
- (0, common_1.Runtime)(),
66
- __metadata("design:type", Object)
67
- ], MeetingNotesWorkflow.prototype, "runtime", void 0);
68
- exports.MeetingNotesWorkflow = MeetingNotesWorkflow = __decorate([
69
- (0, common_1.Workflow)({
70
- configFile: __dirname + '/meeting-notes.workflow.yaml',
71
87
  })
72
88
  ], MeetingNotesWorkflow);
73
89
  //# sourceMappingURL=meeting-notes.workflow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meeting-notes.workflow.js","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,4DAAkE;AAClE,8CAAgG;AAChG,0CAAiD;AACjD,+EAAsG;AACtG,mFAAmH;AAK5G,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACjB,sBAAsB,CAAyB;IAC/C,cAAc,CAAiB;IAC3B,oBAAoB,CAAuB;IAC3C,sBAAsB,CAAyB;IAWjE,IAAI,CAEF;IAQF,KAAK,CAGH;IAGF,OAAO,CAAM;CACd,CAAA;AAhCY,oDAAoB;AACjB;IAAb,IAAA,mBAAU,GAAE;8BAAyB,sCAAsB;oEAAC;AAC/C;IAAb,IAAA,mBAAU,GAAE;8BAAiB,qBAAc;4DAAC;AAC3B;IAAjB,IAAA,uBAAc,GAAE;8BAAuB,6CAAoB;kEAAC;AAC3C;IAAjB,IAAA,uBAAc,GAAE;8BAAyB,iDAAsB;oEAAC;AAWjE;IATC,IAAA,cAAK,EAAC;QACL,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,SAAS,EAAE,OAAC;iBACT,MAAM,EAAE;iBACR,OAAO,CACN,2IAA2I,CAC5I;SACJ,CAAC;KACH,CAAC;;kDAGA;AAQF;IANC,IAAA,cAAK,EAAC;QACL,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,YAAY,EAAE,mDAA0B,CAAC,QAAQ,EAAE;YACnD,cAAc,EAAE,8DAAmC,CAAC,QAAQ,EAAE;SAC/D,CAAC;KACH,CAAC;;mDAIA;AAGF;IADC,IAAA,gBAAO,GAAE;;qDACG;+BA/BF,oBAAoB;IAHhC,IAAA,iBAAQ,EAAC;QACR,UAAU,EAAE,SAAS,GAAG,8BAA8B;KACvD,CAAC;GACW,oBAAoB,CAgChC"}
1
+ {"version":3,"file":"meeting-notes.workflow.js","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,4DAAkE;AAClE,8CAAmG;AACnG,+EAAsG;AACtG,mFAAmH;AAY5G,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,qBAAmC;IAC7D,sBAAsB,CAAyB;IAE7D,YAAY,CAA8C;IAC1D,cAAc,CAAuD;IAG/D,AAAN,KAAK,CAAC,UAAU,CAAC,IAA2B;QAC1C,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CACxB,6CAAoB,EACpB;YACE,IAAI,EAAE,0BAA0B,IAAI,CAAC,SAAS,EAAE;SACjD,EACD,EAAE,EAAE,EAAE,OAAO,EAAE,CAChB,CAAC;IACJ,CAAC;IAGK,AAAN,KAAK,CAAC,YAAY,CAAC,OAAmD;QACpE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,6CAAoB,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,OAAqD,CAAC;IACnF,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC;YACrC,MAAM,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE;YACtC,QAAQ,EAAE;gBACR,EAAE,EAAE,OAAO;gBACX,QAAQ,EAAE,iDAAsB;aACjC;YACD,MAAM,EAAE;;;EAGZ,IAAI,CAAC,YAAY,EAAE,IAAI;iBACR;SACZ,CAAC,CAAC;IACL,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAC,OAA4D;QACxE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,iDAAsB,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC5F,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,OAA8D,CAAC;IAC9F,CAAC;CACF,CAAA;AA5CY,oDAAoB;AACjB;IAAb,IAAA,mBAAU,GAAE;8BAAyB,sCAAsB;oEAAC;AAMvD;IADL,IAAA,gBAAO,EAAC,EAAE,EAAE,EAAE,sBAAsB,EAAE,CAAC;;;;sDASvC;AAGK;IADL,IAAA,mBAAU,EAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,mDAA0B,EAAE,CAAC;;;;wDAIrH;AAGK;IADL,IAAA,mBAAU,EAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC;;;;yDAchE;AAGK;IADL,IAAA,cAAK,EAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,8DAAmC,EAAE,CAAC;;;;mDAI3F;+BA3CU,oBAAoB;IAVhC,IAAA,iBAAQ,EAAC;QACR,QAAQ,EAAE,SAAS,GAAG,wBAAwB;QAC9C,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,SAAS,EAAE,OAAC;iBACT,MAAM,EAAE;iBACR,OAAO,CACN,2IAA2I,CAC5I;SACJ,CAAC;KACH,CAAC;GACW,oBAAoB,CA4ChC"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "summary",
10
10
  "workflow"
11
11
  ],
12
- "version": "0.20.7",
12
+ "version": "0.21.1",
13
13
  "license": "Apache-2.0",
14
14
  "author": {
15
15
  "name": "Jakob Klippel",
@@ -30,9 +30,9 @@
30
30
  "watch": "nest build --watch"
31
31
  },
32
32
  "dependencies": {
33
- "@loopstack/claude-module": "^0.21.1",
34
- "@loopstack/common": "^0.24.0",
35
- "@loopstack/core": "^0.24.0",
33
+ "@loopstack/claude-module": "^0.22.0",
34
+ "@loopstack/common": "^0.25.0",
35
+ "@loopstack/core": "^0.25.0",
36
36
  "@nestjs/common": "^11.1.14",
37
37
  "zod": "^4.3.6"
38
38
  },
@@ -1,8 +1,8 @@
1
1
  import { TestingModule } from '@nestjs/testing';
2
2
  import { ClaudeGenerateDocument, ClaudeModule } from '@loopstack/claude-module';
3
- import { RunContext, generateObjectFingerprint, getBlockTools } from '@loopstack/common';
4
- import { CreateDocument, LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
5
- import { ToolMock, createWorkflowTest } from '@loopstack/testing';
3
+ import { RunContext, WorkflowEntity, getBlockTools } from '@loopstack/common';
4
+ import { LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
5
+ import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';
6
6
  import { MeetingNotesDocument } from '../documents/meeting-notes-document';
7
7
  import { OptimizedNotesDocument } from '../documents/optimized-notes-document';
8
8
  import { MeetingNotesWorkflow } from '../meeting-notes.workflow';
@@ -12,15 +12,7 @@ describe('MeetingNotesWorkflow', () => {
12
12
  let workflow: MeetingNotesWorkflow;
13
13
  let processor: WorkflowProcessorService;
14
14
 
15
- let mockCreateDocument: ToolMock;
16
-
17
- const mockInitialNotes = {
18
- text: `
19
- - meeting 1.1.2025
20
- - budget: need 2 cut costs sarah said
21
- - hire new person?? --> marketing
22
- - vendor pricing - follow up needed by anna`,
23
- };
15
+ let mockClaudeGenerateDocument: ToolMock;
24
16
 
25
17
  beforeEach(async () => {
26
18
  module = await createWorkflowTest()
@@ -28,14 +20,13 @@ describe('MeetingNotesWorkflow', () => {
28
20
  .withImports(LoopCoreModule, ClaudeModule)
29
21
  .withProvider(MeetingNotesDocument)
30
22
  .withProvider(OptimizedNotesDocument)
31
- .withToolOverride(CreateDocument)
32
23
  .withToolOverride(ClaudeGenerateDocument)
33
24
  .compile();
34
25
 
35
26
  workflow = module.get(MeetingNotesWorkflow);
36
27
  processor = module.get(WorkflowProcessorService);
37
28
 
38
- mockCreateDocument = module.get(CreateDocument);
29
+ mockClaudeGenerateDocument = module.get(ClaudeGenerateDocument);
39
30
  });
40
31
 
41
32
  afterEach(async () => {
@@ -45,151 +36,103 @@ describe('MeetingNotesWorkflow', () => {
45
36
  describe('initialization', () => {
46
37
  it('should be defined with correct tools', () => {
47
38
  expect(workflow).toBeDefined();
48
- expect(getBlockTools(workflow)).toContain('createDocument');
49
39
  expect(getBlockTools(workflow)).toContain('claudeGenerateDocument');
50
40
  });
51
41
  });
52
42
 
53
43
  describe('initial step', () => {
54
- const context = {} as RunContext;
55
-
56
44
  it('should execute initial step and stop at waiting_for_response', async () => {
57
- mockCreateDocument.execute.mockResolvedValue({
58
- data: { content: mockInitialNotes },
59
- });
45
+ const context = createStatelessContext();
60
46
 
61
47
  const result = await processor.process(workflow, {}, context);
62
48
 
63
- // Should execute without errors and stop at waiting_for_response (manual step)
64
49
  expect(result.hasError).toBe(false);
65
50
  expect(result.stop).toBe(true);
51
+ expect(result.place).toBe('waiting_for_response');
66
52
 
67
- // Should call CreateDocument once for the initial form
68
- expect(mockCreateDocument.execute).toHaveBeenCalledTimes(1);
69
- expect(mockCreateDocument.execute).toHaveBeenCalledWith(
53
+ expect(result.documents).toHaveLength(1);
54
+ expect(result.documents[0]).toEqual(
70
55
  expect.objectContaining({
71
- id: 'input',
72
- update: {
73
- content: {
74
- text: expect.stringContaining('1.1.2025'),
75
- },
76
- },
56
+ className: 'MeetingNotesDocument',
57
+ content: expect.objectContaining({
58
+ text: expect.stringContaining('1.1.2025'),
59
+ }),
77
60
  }),
78
- expect.anything(),
79
- expect.anything(),
80
- expect.anything(),
81
61
  );
82
-
83
- // // Verify history contains expected places
84
- // const history = result.state.getHistory();
85
- // const places = history.map((h) => h.metadata?.place);
86
- // expect(places).toContain('waiting_for_response');
87
62
  });
88
- });
89
63
 
90
- describe('user response step', () => {
91
- it('should process user response and generate optimized notes', async () => {
92
- const mockUserEditedNotes = {
93
- text: `Meeting Notes - January 1, 2025
94
- - Budget discussion: need to cut costs (Sarah's input)
95
- - Hiring: new person needed for marketing
96
- - Vendor pricing: follow up needed by Anna`,
97
- };
64
+ it('should use custom input text when provided', async () => {
65
+ const context = createStatelessContext();
98
66
 
99
- const mockOptimizedNotes = {
100
- date: '2025-01-01',
101
- summary: 'Budget and hiring discussion',
102
- participants: ['Sarah', 'Anna'],
103
- decisions: ['Cut costs', 'Hire for marketing'],
104
- actionItems: ['Follow up on vendor pricing'],
105
- };
67
+ const result = await processor.process(workflow, { inputText: 'Custom meeting notes here' }, context);
106
68
 
107
- const args = { inputText: mockInitialNotes.text };
108
-
109
- // Create module with existing workflow state
110
- const moduleWithState = await createWorkflowTest()
111
- .forWorkflow(MeetingNotesWorkflow)
112
- .withImports(LoopCoreModule, ClaudeModule)
113
- .withProvider(MeetingNotesDocument)
114
- .withProvider(OptimizedNotesDocument)
115
- .withToolOverride(CreateDocument)
116
- .withToolOverride(ClaudeGenerateDocument)
117
- .withExistingWorkflow({
118
- id: '123',
119
- place: 'waiting_for_response',
120
- hashRecord: {
121
- options: generateObjectFingerprint(args), // previously run with same arguments
122
- },
123
- })
124
- .compile();
69
+ expect(result.hasError).toBe(false);
70
+ expect(result.stop).toBe(true);
125
71
 
126
- const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
127
- 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
+ });
128
81
 
129
- const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
130
- const mockClaudeGenerateDocumentWithState: ToolMock = moduleWithState.get(ClaudeGenerateDocument);
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';
131
85
 
132
- mockCreateDocumentWithState.execute.mockResolvedValue({
133
- data: { content: mockUserEditedNotes },
134
- });
135
- mockClaudeGenerateDocumentWithState.execute.mockResolvedValue({
136
- data: { content: mockOptimizedNotes },
137
- });
86
+ mockClaudeGenerateDocument.call.mockResolvedValue({ data: undefined });
138
87
 
139
- // Context with user payload for manual transition
140
- const contextWithPayload = {
88
+ const context = {
89
+ workflowEntity: {
90
+ id: workflowId,
91
+ place: 'waiting_for_response',
92
+ documents: [],
93
+ } as Partial<WorkflowEntity>,
141
94
  payload: {
142
95
  transition: {
143
- id: 'user_response',
144
- workflowId: '123',
145
- payload: mockUserEditedNotes,
96
+ id: 'userResponse',
97
+ workflowId,
98
+ payload: { text: 'Cleaned up meeting notes from user' },
146
99
  },
147
100
  },
148
- } as RunContext;
101
+ } as unknown as RunContext;
149
102
 
150
- const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
103
+ const result = await processor.process(workflow, {}, context);
151
104
 
152
- // Should execute and stop at notes_optimized (next manual step)
153
105
  expect(result.hasError).toBe(false);
154
106
  expect(result.stop).toBe(true);
155
-
156
- // Should call CreateDocument once for user response
157
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
158
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledWith(
159
- expect.objectContaining({
160
- id: 'input',
161
- }),
162
- expect.anything(),
163
- expect.anything(),
164
- 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
+ ]),
165
116
  );
166
117
 
167
- // Should call ClaudeGenerateDocument once
168
- expect(mockClaudeGenerateDocumentWithState.execute).toHaveBeenCalledTimes(1);
169
- expect(mockClaudeGenerateDocumentWithState.execute).toHaveBeenCalledWith(
118
+ // LLM should have been called to optimize notes
119
+ expect(mockClaudeGenerateDocument.call).toHaveBeenCalledTimes(1);
120
+ expect(mockClaudeGenerateDocument.call).toHaveBeenCalledWith(
170
121
  expect.objectContaining({
171
- claude: {
172
- model: 'claude-sonnet-4-6',
173
- },
122
+ claude: { model: 'claude-sonnet-4-6' },
123
+ response: expect.objectContaining({ id: 'final' }),
124
+ prompt: expect.stringContaining('meeting notes'),
174
125
  }),
175
- expect.anything(),
176
- expect.anything(),
177
- expect.anything(),
126
+ undefined,
178
127
  );
179
-
180
- // // Verify history contains expected places
181
- // const history = result.state.getHistory();
182
- // const places = history.map((h) => h.metadata?.place);
183
- // expect(places).toContain('response_received');
184
- // expect(places).toContain('notes_optimized');
185
-
186
- await moduleWithState.close();
187
128
  });
188
129
  });
189
130
 
190
- describe('confirm step', () => {
131
+ describe('resume from notes_optimized', () => {
191
132
  it('should complete workflow when user confirms optimized notes', async () => {
192
- const mockFinalNotes = {
133
+ const workflowId = '00000000-0000-0000-0000-000000000002';
134
+
135
+ const optimizedPayload = {
193
136
  date: '2025-01-01',
194
137
  summary: 'Budget discussion with updates',
195
138
  participants: ['Sarah', 'Anna', 'Bob'],
@@ -197,60 +140,38 @@ describe('MeetingNotesWorkflow', () => {
197
140
  actionItems: ['Follow up on vendor pricing by Friday'],
198
141
  };
199
142
 
200
- const args = { inputText: 'any text' };
201
-
202
- // Create module with existing workflow state after AI optimization
203
- const moduleWithState = await createWorkflowTest()
204
- .forWorkflow(MeetingNotesWorkflow)
205
- .withImports(LoopCoreModule, ClaudeModule)
206
- .withProvider(MeetingNotesDocument)
207
- .withProvider(OptimizedNotesDocument)
208
- .withToolOverride(CreateDocument)
209
- .withToolOverride(ClaudeGenerateDocument)
210
- .withExistingWorkflow({
211
- id: '123',
143
+ const context = {
144
+ workflowEntity: {
145
+ id: workflowId,
212
146
  place: 'notes_optimized',
213
- hashRecord: {
214
- options: generateObjectFingerprint(args), // previously run with same arguments
215
- },
216
- })
217
- .compile();
218
-
219
- const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
220
- const processorWithState = moduleWithState.get(WorkflowProcessorService);
221
-
222
- const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
223
-
224
- mockCreateDocumentWithState.execute.mockResolvedValue({
225
- data: { content: mockFinalNotes },
226
- });
227
-
228
- // Context with user confirmation for manual transition
229
- const contextWithPayload = {
147
+ documents: [],
148
+ } as Partial<WorkflowEntity>,
230
149
  payload: {
231
150
  transition: {
232
151
  id: 'confirm',
233
- workflowId: '123',
234
- payload: mockFinalNotes,
152
+ workflowId,
153
+ payload: optimizedPayload,
235
154
  },
236
155
  },
237
- } as RunContext;
156
+ } as unknown as RunContext;
238
157
 
239
- const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
158
+ const result = await processor.process(workflow, {}, context);
240
159
 
241
- // Should complete and reach end state
242
160
  expect(result.hasError).toBe(false);
243
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
+ );
244
172
 
245
- // Should call CreateDocument once for final confirmation
246
- expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
247
-
248
- // // Verify history contains expected places including end
249
- // const history = result.state.getHistory();
250
- // const places = history.map((h) => h.metadata?.place);
251
- // expect(places).toContain('end');
252
-
253
- await moduleWithState.close();
173
+ // No additional LLM calls during confirmation
174
+ expect(mockClaudeGenerateDocument.call).not.toHaveBeenCalled();
254
175
  });
255
176
  });
256
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'
@@ -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'