@loopstack/meeting-notes-example-workflow 0.20.7 → 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.
- package/README.md +39 -39
- package/dist/documents/meeting-notes-document.d.ts +2 -5
- package/dist/documents/meeting-notes-document.d.ts.map +1 -1
- package/dist/documents/meeting-notes-document.js +3 -11
- package/dist/documents/meeting-notes-document.js.map +1 -1
- package/dist/documents/meeting-notes-document.yaml +10 -10
- package/dist/documents/optimized-notes-document.d.ts +6 -3
- package/dist/documents/optimized-notes-document.d.ts.map +1 -1
- package/dist/documents/optimized-notes-document.js +7 -11
- package/dist/documents/optimized-notes-document.js.map +1 -1
- package/dist/documents/optimized-notes-document.yaml +33 -33
- package/dist/meeting-notes.ui.yaml +10 -0
- package/dist/meeting-notes.workflow.d.ts +13 -14
- package/dist/meeting-notes.workflow.d.ts.map +1 -1
- package/dist/meeting-notes.workflow.js +53 -37
- package/dist/meeting-notes.workflow.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/meeting-notes.workflow.spec.ts +83 -162
- package/src/documents/meeting-notes-document.ts +5 -9
- package/src/documents/meeting-notes-document.yaml +10 -10
- package/src/documents/optimized-notes-document.ts +9 -7
- package/src/documents/optimized-notes-document.yaml +33 -33
- package/src/meeting-notes.ui.yaml +10 -0
- package/src/meeting-notes.workflow.ts +49 -31
- package/dist/meeting-notes.workflow.yaml +0 -76
- package/src/meeting-notes.workflow.yaml +0 -76
package/README.md
CHANGED
|
@@ -87,17 +87,17 @@ Add action buttons to documents that trigger transitions. These are defined in t
|
|
|
87
87
|
# meeting-notes-document.yaml
|
|
88
88
|
type: document
|
|
89
89
|
ui:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
text:
|
|
93
|
-
title: Text
|
|
94
|
-
widget: textarea
|
|
95
|
-
actions:
|
|
96
|
-
- type: button
|
|
97
|
-
widget: button
|
|
98
|
-
transition: user_response
|
|
90
|
+
widgets:
|
|
91
|
+
- widget: form
|
|
99
92
|
options:
|
|
100
|
-
|
|
93
|
+
properties:
|
|
94
|
+
text:
|
|
95
|
+
title: Text
|
|
96
|
+
widget: textarea
|
|
97
|
+
actions:
|
|
98
|
+
- type: button
|
|
99
|
+
transition: user_response
|
|
100
|
+
label: 'Optimize Notes'
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
When clicked, the button triggers the `user_response` transition with the current document content.
|
|
@@ -135,7 +135,7 @@ export const MeetingNotesDocumentSchema = z.object({
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
@Document({
|
|
138
|
-
|
|
138
|
+
uiConfig: __dirname + '/meeting-notes-document.yaml',
|
|
139
139
|
})
|
|
140
140
|
export class MeetingNotesDocument implements DocumentInterface {
|
|
141
141
|
@Input({
|
|
@@ -167,35 +167,35 @@ Configure the document UI with ordering, collapsible arrays, and confirm button:
|
|
|
167
167
|
# optimized-notes-document.yaml
|
|
168
168
|
type: document
|
|
169
169
|
ui:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- date
|
|
173
|
-
- summary
|
|
174
|
-
- participants
|
|
175
|
-
- decisions
|
|
176
|
-
- actionItems
|
|
177
|
-
properties:
|
|
178
|
-
date:
|
|
179
|
-
title: Date
|
|
180
|
-
summary:
|
|
181
|
-
title: Summary
|
|
182
|
-
widget: textarea
|
|
183
|
-
participants:
|
|
184
|
-
title: Participants
|
|
185
|
-
collapsed: true
|
|
186
|
-
items:
|
|
187
|
-
title: Participant
|
|
188
|
-
actionItems:
|
|
189
|
-
title: Action Items
|
|
190
|
-
collapsed: true
|
|
191
|
-
items:
|
|
192
|
-
title: Action Item
|
|
193
|
-
actions:
|
|
194
|
-
- type: button
|
|
195
|
-
widget: button
|
|
196
|
-
transition: confirm
|
|
170
|
+
widgets:
|
|
171
|
+
- widget: form
|
|
197
172
|
options:
|
|
198
|
-
|
|
173
|
+
order:
|
|
174
|
+
- date
|
|
175
|
+
- summary
|
|
176
|
+
- participants
|
|
177
|
+
- decisions
|
|
178
|
+
- actionItems
|
|
179
|
+
properties:
|
|
180
|
+
date:
|
|
181
|
+
title: Date
|
|
182
|
+
summary:
|
|
183
|
+
title: Summary
|
|
184
|
+
widget: textarea
|
|
185
|
+
participants:
|
|
186
|
+
title: Participants
|
|
187
|
+
collapsed: true
|
|
188
|
+
items:
|
|
189
|
+
title: Participant
|
|
190
|
+
actionItems:
|
|
191
|
+
title: Action Items
|
|
192
|
+
collapsed: true
|
|
193
|
+
items:
|
|
194
|
+
title: Action Item
|
|
195
|
+
actions:
|
|
196
|
+
- type: button
|
|
197
|
+
transition: confirm
|
|
198
|
+
label: 'Confirm'
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
#### 7. AI Document Generation
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { DocumentInterface } from '@loopstack/common';
|
|
3
2
|
export declare const MeetingNotesDocumentSchema: z.ZodObject<{
|
|
4
3
|
text: z.ZodString;
|
|
5
4
|
}, z.core.$strip>;
|
|
6
|
-
export declare class MeetingNotesDocument
|
|
7
|
-
|
|
8
|
-
text: string;
|
|
9
|
-
};
|
|
5
|
+
export declare class MeetingNotesDocument {
|
|
6
|
+
text: string;
|
|
10
7
|
}
|
|
11
8
|
//# sourceMappingURL=meeting-notes-document.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meeting-notes-document.d.ts","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"meeting-notes-document.d.ts","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,0BAA0B;;iBAErC,CAAC;AAEH,qBAIa,oBAAoB;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
@@ -5,9 +5,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
9
|
exports.MeetingNotesDocument = exports.MeetingNotesDocumentSchema = void 0;
|
|
13
10
|
const zod_1 = require("zod");
|
|
@@ -16,18 +13,13 @@ exports.MeetingNotesDocumentSchema = zod_1.z.object({
|
|
|
16
13
|
text: zod_1.z.string(),
|
|
17
14
|
});
|
|
18
15
|
let MeetingNotesDocument = class MeetingNotesDocument {
|
|
19
|
-
|
|
16
|
+
text;
|
|
20
17
|
};
|
|
21
18
|
exports.MeetingNotesDocument = MeetingNotesDocument;
|
|
22
|
-
__decorate([
|
|
23
|
-
(0, common_1.Input)({
|
|
24
|
-
schema: exports.MeetingNotesDocumentSchema,
|
|
25
|
-
}),
|
|
26
|
-
__metadata("design:type", Object)
|
|
27
|
-
], MeetingNotesDocument.prototype, "content", void 0);
|
|
28
19
|
exports.MeetingNotesDocument = MeetingNotesDocument = __decorate([
|
|
29
20
|
(0, common_1.Document)({
|
|
30
|
-
|
|
21
|
+
schema: exports.MeetingNotesDocumentSchema,
|
|
22
|
+
uiConfig: __dirname + '/meeting-notes-document.yaml',
|
|
31
23
|
})
|
|
32
24
|
], MeetingNotesDocument);
|
|
33
25
|
//# sourceMappingURL=meeting-notes-document.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meeting-notes-document.js","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"meeting-notes-document.js","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,6BAAwB;AACxB,8CAA6C;AAEhC,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC;AAMI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAC/B,IAAI,CAAS;CACd,CAAA;AAFY,oDAAoB;+BAApB,oBAAoB;IAJhC,IAAA,iBAAQ,EAAC;QACR,MAAM,EAAE,kCAA0B;QAClC,QAAQ,EAAE,SAAS,GAAG,8BAA8B;KACrD,CAAC;GACW,oBAAoB,CAEhC"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
type: document
|
|
2
2
|
ui:
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { DocumentInterface } from '@loopstack/common';
|
|
3
2
|
export declare const OptimizedMeetingNotesDocumentSchema: z.ZodObject<{
|
|
4
3
|
date: z.ZodString;
|
|
5
4
|
summary: z.ZodString;
|
|
@@ -7,7 +6,11 @@ export declare const OptimizedMeetingNotesDocumentSchema: z.ZodObject<{
|
|
|
7
6
|
decisions: z.ZodArray<z.ZodString>;
|
|
8
7
|
actionItems: z.ZodArray<z.ZodString>;
|
|
9
8
|
}, z.core.$strip>;
|
|
10
|
-
export declare class OptimizedNotesDocument
|
|
11
|
-
|
|
9
|
+
export declare class OptimizedNotesDocument {
|
|
10
|
+
date: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
participants: string[];
|
|
13
|
+
decisions: string[];
|
|
14
|
+
actionItems: string[];
|
|
12
15
|
}
|
|
13
16
|
//# sourceMappingURL=optimized-notes-document.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimized-notes-document.d.ts","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"optimized-notes-document.d.ts","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,mCAAmC;;;;;;iBAM9C,CAAC;AAEH,qBAIa,sBAAsB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB"}
|
|
@@ -5,9 +5,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
9
|
exports.OptimizedNotesDocument = exports.OptimizedMeetingNotesDocumentSchema = void 0;
|
|
13
10
|
const zod_1 = require("zod");
|
|
@@ -20,18 +17,17 @@ exports.OptimizedMeetingNotesDocumentSchema = zod_1.z.object({
|
|
|
20
17
|
actionItems: zod_1.z.array(zod_1.z.string()),
|
|
21
18
|
});
|
|
22
19
|
let OptimizedNotesDocument = class OptimizedNotesDocument {
|
|
23
|
-
|
|
20
|
+
date;
|
|
21
|
+
summary;
|
|
22
|
+
participants;
|
|
23
|
+
decisions;
|
|
24
|
+
actionItems;
|
|
24
25
|
};
|
|
25
26
|
exports.OptimizedNotesDocument = OptimizedNotesDocument;
|
|
26
|
-
__decorate([
|
|
27
|
-
(0, common_1.Input)({
|
|
28
|
-
schema: exports.OptimizedMeetingNotesDocumentSchema,
|
|
29
|
-
}),
|
|
30
|
-
__metadata("design:type", Object)
|
|
31
|
-
], OptimizedNotesDocument.prototype, "content", void 0);
|
|
32
27
|
exports.OptimizedNotesDocument = OptimizedNotesDocument = __decorate([
|
|
33
28
|
(0, common_1.Document)({
|
|
34
|
-
|
|
29
|
+
schema: exports.OptimizedMeetingNotesDocumentSchema,
|
|
30
|
+
uiConfig: __dirname + '/optimized-notes-document.yaml',
|
|
35
31
|
})
|
|
36
32
|
], OptimizedNotesDocument);
|
|
37
33
|
//# sourceMappingURL=optimized-notes-document.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimized-notes-document.js","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"optimized-notes-document.js","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,6BAAwB;AACxB,8CAA6C;AAEhC,QAAA,mCAAmC,GAAG,OAAC,CAAC,MAAM,CAAC;IAC1D,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,YAAY,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IACjC,SAAS,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IAC9B,WAAW,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;CACjC,CAAC,CAAC;AAMI,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IACjC,IAAI,CAAS;IACb,OAAO,CAAS;IAChB,YAAY,CAAW;IACvB,SAAS,CAAW;IACpB,WAAW,CAAW;CACvB,CAAA;AANY,wDAAsB;iCAAtB,sBAAsB;IAJlC,IAAA,iBAAQ,EAAC;QACR,MAAM,EAAE,2CAAmC;QAC3C,QAAQ,EAAE,SAAS,GAAG,gCAAgC;KACvD,CAAC;GACW,sBAAsB,CAMlC"}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
type: document
|
|
2
2
|
ui:
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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,20 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ClaudeGenerateDocument } from '@loopstack/claude-module';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { OptimizedMeetingNotesDocumentSchema
|
|
6
|
-
export declare class MeetingNotesWorkflow {
|
|
3
|
+
import { BaseWorkflow } from '@loopstack/common';
|
|
4
|
+
import { MeetingNotesDocumentSchema } from './documents/meeting-notes-document';
|
|
5
|
+
import { OptimizedMeetingNotesDocumentSchema } from './documents/optimized-notes-document';
|
|
6
|
+
export declare class MeetingNotesWorkflow extends BaseWorkflow<{
|
|
7
|
+
inputText: string;
|
|
8
|
+
}> {
|
|
7
9
|
claudeGenerateDocument: ClaudeGenerateDocument;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
args: {
|
|
10
|
+
meetingNotes?: z.infer<typeof MeetingNotesDocumentSchema>;
|
|
11
|
+
optimizedNotes?: z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
|
|
12
|
+
createForm(args: {
|
|
12
13
|
inputText: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
runtime: any;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
userResponse(payload: z.infer<typeof MeetingNotesDocumentSchema>): Promise<void>;
|
|
16
|
+
optimizeNotes(): Promise<void>;
|
|
17
|
+
confirm(payload: z.infer<typeof OptimizedMeetingNotesDocumentSchema>): Promise<void>;
|
|
19
18
|
}
|
|
20
19
|
//# sourceMappingURL=meeting-notes.workflow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meeting-notes.workflow.d.ts","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"meeting-notes.workflow.d.ts","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAoD,MAAM,mBAAmB,CAAC;AACnG,OAAO,EAAwB,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACtG,OAAO,EAAE,mCAAmC,EAA0B,MAAM,sCAAsC,CAAC;AAEnH,qBAUa,oBAAqB,SAAQ,YAAY,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;IAC7D,sBAAsB,EAAE,sBAAsB,CAAC;IAE7D,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAC;IAG/D,UAAU,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE;IAWtC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC;IAMhE,aAAa;IAgBb,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC;CAI3E"}
|
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
35
|
-
__metadata("design:type",
|
|
36
|
-
|
|
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.
|
|
39
|
-
__metadata("design:type",
|
|
40
|
-
|
|
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.
|
|
43
|
-
__metadata("design:type",
|
|
44
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
12
|
+
"version": "0.21.0",
|
|
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.
|
|
34
|
-
"@loopstack/common": "^0.
|
|
35
|
-
"@loopstack/core": "^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,
|
|
4
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
expect(
|
|
69
|
-
expect(mockCreateDocument.execute).toHaveBeenCalledWith(
|
|
53
|
+
expect(result.documents).toHaveLength(1);
|
|
54
|
+
expect(result.documents[0]).toEqual(
|
|
70
55
|
expect.objectContaining({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
data: { content: mockUserEditedNotes },
|
|
134
|
-
});
|
|
135
|
-
mockClaudeGenerateDocumentWithState.execute.mockResolvedValue({
|
|
136
|
-
data: { content: mockOptimizedNotes },
|
|
137
|
-
});
|
|
86
|
+
mockClaudeGenerateDocument.call.mockResolvedValue({ data: undefined });
|
|
138
87
|
|
|
139
|
-
|
|
140
|
-
|
|
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: '
|
|
144
|
-
workflowId
|
|
145
|
-
payload:
|
|
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
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
expect(
|
|
159
|
-
expect.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
//
|
|
168
|
-
expect(
|
|
169
|
-
expect(
|
|
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
|
-
|
|
173
|
-
|
|
122
|
+
claude: { model: 'claude-sonnet-4-6' },
|
|
123
|
+
response: expect.objectContaining({ id: 'final' }),
|
|
124
|
+
prompt: expect.stringContaining('meeting notes'),
|
|
174
125
|
}),
|
|
175
|
-
|
|
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('
|
|
131
|
+
describe('resume from notes_optimized', () => {
|
|
191
132
|
it('should complete workflow when user confirms optimized notes', async () => {
|
|
192
|
-
const
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
234
|
-
payload:
|
|
152
|
+
workflowId,
|
|
153
|
+
payload: optimizedPayload,
|
|
235
154
|
},
|
|
236
155
|
},
|
|
237
|
-
} as RunContext;
|
|
156
|
+
} as unknown as RunContext;
|
|
238
157
|
|
|
239
|
-
const result = await
|
|
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
|
-
//
|
|
246
|
-
expect(
|
|
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
|
|
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
|
-
|
|
9
|
+
schema: MeetingNotesDocumentSchema,
|
|
10
|
+
uiConfig: __dirname + '/meeting-notes-document.yaml',
|
|
10
11
|
})
|
|
11
|
-
export class MeetingNotesDocument
|
|
12
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
13
|
+
schema: OptimizedMeetingNotesDocumentSchema,
|
|
14
|
+
uiConfig: __dirname + '/optimized-notes-document.yaml',
|
|
14
15
|
})
|
|
15
|
-
export class OptimizedNotesDocument
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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,43 +1,61 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ClaudeGenerateDocument } from '@loopstack/claude-module';
|
|
3
|
-
import {
|
|
4
|
-
import { CreateDocument } from '@loopstack/core';
|
|
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
|
-
|
|
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 {
|
|
17
|
+
export class MeetingNotesWorkflow extends BaseWorkflow<{ inputText: string }> {
|
|
12
18
|
@InjectTool() claudeGenerateDocument: ClaudeGenerateDocument;
|
|
13
|
-
@InjectTool() createDocument: CreateDocument;
|
|
14
|
-
@InjectDocument() meetingNotesDocument: MeetingNotesDocument;
|
|
15
|
-
@InjectDocument() optimizedNotesDocument: OptimizedNotesDocument;
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
@
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
@
|
|
42
|
-
|
|
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
|
}
|
|
@@ -1,76 +0,0 @@
|
|
|
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'
|
|
11
|
-
|
|
12
|
-
transitions:
|
|
13
|
-
- id: create_form
|
|
14
|
-
from: start
|
|
15
|
-
to: waiting_for_response
|
|
16
|
-
call:
|
|
17
|
-
- id: form
|
|
18
|
-
tool: createDocument
|
|
19
|
-
args:
|
|
20
|
-
id: input
|
|
21
|
-
document: meetingNotesDocument
|
|
22
|
-
update:
|
|
23
|
-
content:
|
|
24
|
-
text: |
|
|
25
|
-
Unstructured Notes:
|
|
26
|
-
|
|
27
|
-
{{ args.inputText }}
|
|
28
|
-
|
|
29
|
-
- id: user_response
|
|
30
|
-
from: waiting_for_response
|
|
31
|
-
to: response_received
|
|
32
|
-
trigger: manual
|
|
33
|
-
call:
|
|
34
|
-
- id: create_response
|
|
35
|
-
tool: createDocument
|
|
36
|
-
args:
|
|
37
|
-
id: input
|
|
38
|
-
document: meetingNotesDocument
|
|
39
|
-
update:
|
|
40
|
-
content: ${{ runtime.transition.payload }}
|
|
41
|
-
assign:
|
|
42
|
-
meetingNotes: ${{ result.data.content }}
|
|
43
|
-
|
|
44
|
-
- id: optimize_notes
|
|
45
|
-
from: response_received
|
|
46
|
-
to: notes_optimized
|
|
47
|
-
call:
|
|
48
|
-
- id: prompt
|
|
49
|
-
tool: claudeGenerateDocument
|
|
50
|
-
args:
|
|
51
|
-
claude:
|
|
52
|
-
model: claude-sonnet-4-6
|
|
53
|
-
response:
|
|
54
|
-
id: final
|
|
55
|
-
document: optimizedNotesDocument
|
|
56
|
-
prompt: |
|
|
57
|
-
Extract all information from the provided meeting notes into the structured document.
|
|
58
|
-
|
|
59
|
-
<Meeting Notes>
|
|
60
|
-
{{ state.meetingNotes.text }}
|
|
61
|
-
</Meeting Notes>
|
|
62
|
-
|
|
63
|
-
- id: confirm
|
|
64
|
-
from: notes_optimized
|
|
65
|
-
to: end
|
|
66
|
-
trigger: manual
|
|
67
|
-
call:
|
|
68
|
-
- id: create_response
|
|
69
|
-
tool: createDocument
|
|
70
|
-
args:
|
|
71
|
-
id: final
|
|
72
|
-
document: optimizedNotesDocument
|
|
73
|
-
update:
|
|
74
|
-
content: ${{ runtime.transition.payload }}
|
|
75
|
-
assign:
|
|
76
|
-
optimizedNotes: ${{ result.data.content }}
|
|
@@ -1,76 +0,0 @@
|
|
|
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'
|
|
11
|
-
|
|
12
|
-
transitions:
|
|
13
|
-
- id: create_form
|
|
14
|
-
from: start
|
|
15
|
-
to: waiting_for_response
|
|
16
|
-
call:
|
|
17
|
-
- id: form
|
|
18
|
-
tool: createDocument
|
|
19
|
-
args:
|
|
20
|
-
id: input
|
|
21
|
-
document: meetingNotesDocument
|
|
22
|
-
update:
|
|
23
|
-
content:
|
|
24
|
-
text: |
|
|
25
|
-
Unstructured Notes:
|
|
26
|
-
|
|
27
|
-
{{ args.inputText }}
|
|
28
|
-
|
|
29
|
-
- id: user_response
|
|
30
|
-
from: waiting_for_response
|
|
31
|
-
to: response_received
|
|
32
|
-
trigger: manual
|
|
33
|
-
call:
|
|
34
|
-
- id: create_response
|
|
35
|
-
tool: createDocument
|
|
36
|
-
args:
|
|
37
|
-
id: input
|
|
38
|
-
document: meetingNotesDocument
|
|
39
|
-
update:
|
|
40
|
-
content: ${{ runtime.transition.payload }}
|
|
41
|
-
assign:
|
|
42
|
-
meetingNotes: ${{ result.data.content }}
|
|
43
|
-
|
|
44
|
-
- id: optimize_notes
|
|
45
|
-
from: response_received
|
|
46
|
-
to: notes_optimized
|
|
47
|
-
call:
|
|
48
|
-
- id: prompt
|
|
49
|
-
tool: claudeGenerateDocument
|
|
50
|
-
args:
|
|
51
|
-
claude:
|
|
52
|
-
model: claude-sonnet-4-6
|
|
53
|
-
response:
|
|
54
|
-
id: final
|
|
55
|
-
document: optimizedNotesDocument
|
|
56
|
-
prompt: |
|
|
57
|
-
Extract all information from the provided meeting notes into the structured document.
|
|
58
|
-
|
|
59
|
-
<Meeting Notes>
|
|
60
|
-
{{ state.meetingNotes.text }}
|
|
61
|
-
</Meeting Notes>
|
|
62
|
-
|
|
63
|
-
- id: confirm
|
|
64
|
-
from: notes_optimized
|
|
65
|
-
to: end
|
|
66
|
-
trigger: manual
|
|
67
|
-
call:
|
|
68
|
-
- id: create_response
|
|
69
|
-
tool: createDocument
|
|
70
|
-
args:
|
|
71
|
-
id: final
|
|
72
|
-
document: optimizedNotesDocument
|
|
73
|
-
update:
|
|
74
|
-
content: ${{ runtime.transition.payload }}
|
|
75
|
-
assign:
|
|
76
|
-
optimizedNotes: ${{ result.data.content }}
|