@positronic/core 0.0.1 → 0.0.2

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 (79) hide show
  1. package/CLAUDE.md +141 -0
  2. package/dist/src/adapters/types.js +1 -16
  3. package/dist/src/clients/types.js +4 -1
  4. package/dist/src/dsl/brain-runner.js +487 -0
  5. package/dist/src/dsl/brain-runner.test.js +733 -0
  6. package/dist/src/dsl/brain.js +1128 -0
  7. package/dist/src/dsl/brain.test.js +4225 -0
  8. package/dist/src/dsl/constants.js +6 -6
  9. package/dist/src/dsl/json-patch.js +37 -9
  10. package/dist/src/index.js +11 -10
  11. package/dist/src/resources/resources.js +371 -0
  12. package/dist/src/test-utils.js +474 -0
  13. package/dist/src/testing.js +3 -0
  14. package/dist/types/adapters/types.d.ts +3 -8
  15. package/dist/types/adapters/types.d.ts.map +1 -1
  16. package/dist/types/clients/types.d.ts +46 -6
  17. package/dist/types/clients/types.d.ts.map +1 -1
  18. package/dist/types/dsl/brain-runner.d.ts +24 -0
  19. package/dist/types/dsl/brain-runner.d.ts.map +1 -0
  20. package/dist/types/dsl/brain.d.ts +136 -0
  21. package/dist/types/dsl/brain.d.ts.map +1 -0
  22. package/dist/types/dsl/constants.d.ts +5 -5
  23. package/dist/types/dsl/constants.d.ts.map +1 -1
  24. package/dist/types/dsl/json-patch.d.ts +2 -1
  25. package/dist/types/dsl/json-patch.d.ts.map +1 -1
  26. package/dist/types/index.d.ts +13 -11
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/types/resources/resource-loader.d.ts +6 -0
  29. package/dist/types/resources/resource-loader.d.ts.map +1 -0
  30. package/dist/types/resources/resources.d.ts +23 -0
  31. package/dist/types/resources/resources.d.ts.map +1 -0
  32. package/dist/types/test-utils.d.ts +94 -0
  33. package/dist/types/test-utils.d.ts.map +1 -0
  34. package/dist/types/testing.d.ts +2 -0
  35. package/dist/types/testing.d.ts.map +1 -0
  36. package/docs/core-testing-guide.md +289 -0
  37. package/package.json +26 -7
  38. package/src/adapters/types.ts +3 -22
  39. package/src/clients/types.ts +50 -10
  40. package/src/dsl/brain-runner.test.ts +384 -0
  41. package/src/dsl/brain-runner.ts +111 -0
  42. package/src/dsl/brain.test.ts +1981 -0
  43. package/src/dsl/brain.ts +740 -0
  44. package/src/dsl/constants.ts +6 -6
  45. package/src/dsl/json-patch.ts +24 -9
  46. package/src/dsl/types.ts +1 -1
  47. package/src/index.ts +30 -16
  48. package/src/resources/resource-loader.ts +8 -0
  49. package/src/resources/resources.ts +267 -0
  50. package/src/test-utils.ts +254 -0
  51. package/test/resources.test.ts +248 -0
  52. package/tsconfig.json +2 -2
  53. package/.swcrc +0 -31
  54. package/dist/src/dsl/extensions.js +0 -19
  55. package/dist/src/dsl/workflow-runner.js +0 -93
  56. package/dist/src/dsl/workflow.js +0 -308
  57. package/dist/src/file-stores/local-file-store.js +0 -12
  58. package/dist/src/utils/temp-files.js +0 -27
  59. package/dist/types/dsl/extensions.d.ts +0 -18
  60. package/dist/types/dsl/extensions.d.ts.map +0 -1
  61. package/dist/types/dsl/workflow-runner.d.ts +0 -28
  62. package/dist/types/dsl/workflow-runner.d.ts.map +0 -1
  63. package/dist/types/dsl/workflow.d.ts +0 -118
  64. package/dist/types/dsl/workflow.d.ts.map +0 -1
  65. package/dist/types/file-stores/local-file-store.d.ts +0 -7
  66. package/dist/types/file-stores/local-file-store.d.ts.map +0 -1
  67. package/dist/types/file-stores/types.d.ts +0 -4
  68. package/dist/types/file-stores/types.d.ts.map +0 -1
  69. package/dist/types/utils/temp-files.d.ts +0 -12
  70. package/dist/types/utils/temp-files.d.ts.map +0 -1
  71. package/src/dsl/extensions.ts +0 -58
  72. package/src/dsl/workflow-runner.test.ts +0 -203
  73. package/src/dsl/workflow-runner.ts +0 -146
  74. package/src/dsl/workflow.test.ts +0 -1435
  75. package/src/dsl/workflow.ts +0 -554
  76. package/src/file-stores/local-file-store.ts +0 -11
  77. package/src/file-stores/types.ts +0 -3
  78. package/src/utils/temp-files.ts +0 -46
  79. /package/dist/src/{file-stores/types.js → resources/resource-loader.js} +0 -0
@@ -1,203 +0,0 @@
1
- import { WorkflowRunner } from './workflow-runner';
2
- import { workflow } from './workflow';
3
- import { WORKFLOW_EVENTS, STATUS } from './constants';
4
- import type { FileStore } from '../file-stores/types';
5
-
6
- class TestFileStore implements FileStore {
7
- async readFile(path: string) {
8
- return Promise.resolve('');
9
- }
10
- }
11
-
12
- describe('WorkflowRunner', () => {
13
- const mockClient = {
14
- execute: jest.fn()
15
- };
16
-
17
- const mockLogger = {
18
- log: jest.fn()
19
- };
20
-
21
- const mockAdapter = {
22
- dispatch: jest.fn()
23
- };
24
-
25
- const fileStore = new TestFileStore();
26
-
27
- beforeEach(() => {
28
- jest.clearAllMocks();
29
- });
30
-
31
- it('should run a workflow and dispatch events to adapters', async () => {
32
- const runner = new WorkflowRunner({
33
- adapters: [mockAdapter],
34
- fileStore,
35
- logger: mockLogger,
36
- verbose: false,
37
- client: mockClient
38
- });
39
-
40
- const testWorkflow = workflow('Test Workflow')
41
- .step('First Step', () => ({ value: 42 }))
42
- .step('Async Step', async ({ state}) => {
43
- await new Promise(resolve => setTimeout(resolve, 10));
44
- return { ...state, asyncValue: 'completed' };
45
- })
46
- .step('Final Step', ({ state }) => ({
47
- ...state,
48
- finalValue: state.value * 2
49
- }));
50
-
51
- await runner.run(testWorkflow);
52
-
53
- // Verify adapter received all events in correct order
54
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
55
- expect.objectContaining({
56
- type: WORKFLOW_EVENTS.START,
57
- workflowTitle: 'Test Workflow'
58
- })
59
- );
60
-
61
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
62
- expect.objectContaining({
63
- type: WORKFLOW_EVENTS.STEP_COMPLETE,
64
- stepTitle: 'First Step'
65
- })
66
- );
67
-
68
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
69
- expect.objectContaining({
70
- type: WORKFLOW_EVENTS.STEP_COMPLETE,
71
- stepTitle: 'Async Step'
72
- })
73
- );
74
-
75
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
76
- expect.objectContaining({
77
- type: WORKFLOW_EVENTS.STEP_COMPLETE,
78
- stepTitle: 'Final Step'
79
- })
80
- );
81
-
82
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
83
- expect.objectContaining({
84
- type: WORKFLOW_EVENTS.COMPLETE,
85
- status: STATUS.COMPLETE
86
- })
87
- );
88
-
89
- // Verify the order of events
90
- const stepCompletions = mockAdapter.dispatch.mock.calls
91
- .filter(call => call[0].type === WORKFLOW_EVENTS.STEP_COMPLETE)
92
- .map(call => call[0].stepTitle);
93
-
94
- expect(stepCompletions).toEqual([
95
- 'First Step',
96
- 'Async Step',
97
- 'Final Step'
98
- ]);
99
- });
100
-
101
- it('should log final state when verbose is true', async () => {
102
- const runner = new WorkflowRunner({
103
- adapters: [],
104
- fileStore,
105
- logger: mockLogger,
106
- verbose: true,
107
- client: mockClient
108
- });
109
-
110
- const testWorkflow = workflow('Test Workflow')
111
- .step('Test Step', () => ({ value: 42 }));
112
-
113
- await runner.run(testWorkflow);
114
-
115
- expect(mockLogger.log).toHaveBeenCalledWith(
116
- expect.stringContaining('Workflow completed:')
117
- );
118
- expect(mockLogger.log).toHaveBeenCalledWith(
119
- expect.stringContaining('"value": 42')
120
- );
121
- });
122
-
123
- it('should handle workflow errors', async () => {
124
- const runner = new WorkflowRunner({
125
- adapters: [mockAdapter],
126
- fileStore,
127
- logger: mockLogger,
128
- verbose: true,
129
- client: mockClient
130
- });
131
-
132
- const errorWorkflow = workflow('Error Workflow')
133
- .step('Error Step', () => {
134
- throw new Error('Test error');
135
- });
136
-
137
- try {
138
- await runner.run(errorWorkflow);
139
- } catch (error) {
140
- // Expected error
141
- }
142
-
143
- // Verify error event was dispatched
144
- expect(mockAdapter.dispatch).toHaveBeenCalledWith(
145
- expect.objectContaining({
146
- type: WORKFLOW_EVENTS.ERROR,
147
- error: expect.objectContaining({
148
- message: 'Test error'
149
- })
150
- })
151
- );
152
- });
153
-
154
- it('should truncate long values in verbose output', async () => {
155
- const runner = new WorkflowRunner({
156
- adapters: [],
157
- fileStore,
158
- logger: mockLogger,
159
- verbose: true,
160
- client: mockClient
161
- });
162
-
163
- const longString = 'a'.repeat(1000); // Make string much longer
164
- const testWorkflow = workflow('Test Workflow')
165
- .step('Test Step', () => ({
166
- longString,
167
- nested: { longString }
168
- }));
169
-
170
- await runner.run(testWorkflow);
171
-
172
- const logCall = mockLogger.log.mock.calls.find(call =>
173
- call[0].includes('Workflow completed:')
174
- );
175
-
176
- expect(logCall[0]).toContain('...');
177
- expect(logCall[0].length).toBeLessThan(longString.length);
178
- });
179
-
180
- it('should maintain state between steps', async () => {
181
- const runner = new WorkflowRunner({
182
- adapters: [],
183
- fileStore,
184
- logger: mockLogger,
185
- verbose: true,
186
- client: mockClient
187
- });
188
-
189
- const testWorkflow = workflow('Test Workflow')
190
- .step('First Step', () => ({ count: 1 }))
191
- .step('Second Step', ({ state }) => ({
192
- count: state.count + 1
193
- }));
194
-
195
- await runner.run(testWorkflow);
196
-
197
- const finalLog = mockLogger.log.mock.calls.find(call =>
198
- call[0].includes('Workflow completed:')
199
- );
200
-
201
- expect(finalLog[0]).toContain('"count": 2');
202
- });
203
- });
@@ -1,146 +0,0 @@
1
- import { WORKFLOW_EVENTS } from './constants';
2
- import { applyPatches } from './json-patch';
3
- import type { Adapter } from "../adapters/types";
4
- import type { FileStore } from "../file-stores/types";
5
- import type { SerializedStep, Workflow } from './workflow';
6
- import type { State } from './types';
7
- import type { PromptClient } from '../clients/types';
8
-
9
- interface Logger {
10
- log(...args: any[]): void;
11
- }
12
-
13
- export class WorkflowRunner {
14
- constructor(
15
- private options: {
16
- adapters: Adapter[],
17
- fileStore: FileStore,
18
- logger: Logger,
19
- verbose: boolean,
20
- client: PromptClient
21
- }
22
- ) {}
23
-
24
- async run<
25
- TOptions extends object = {},
26
- TState extends State = {}
27
- >(
28
- workflow: Workflow<TOptions, TState>,
29
- {
30
- initialState = {} as TState,
31
- options,
32
- initialCompletedSteps,
33
- workflowRunId,
34
- endAfter
35
- }: {
36
- initialState?: TState,
37
- options?: TOptions,
38
- initialCompletedSteps?: SerializedStep[] | never,
39
- workflowRunId?: string | never,
40
- endAfter?: number
41
- } = {}
42
- ) {
43
- const {
44
- adapters,
45
- logger: { log },
46
- verbose,
47
- fileStore,
48
- client,
49
- } = this.options;
50
-
51
- let currentState = initialState ?? ({} as TState);
52
- let stepNumber = 1;
53
-
54
- // Apply any patches from completed steps
55
- // to the initial state so that the workflow
56
- // starts with a state that reflects all of the completed steps.
57
- // Need to do this when a workflow is restarted with completed steps.
58
- initialCompletedSteps?.forEach(step => {
59
- if (step.patch) {
60
- currentState = applyPatches(currentState, [step.patch]) as TState;
61
- stepNumber++;
62
- }
63
- });
64
-
65
- const workflowRun = workflowRunId && initialCompletedSteps
66
- ? workflow.run({ initialState, initialCompletedSteps, workflowRunId, options, client, fileStore })
67
- : workflow.run({ initialState, options, client, fileStore });
68
-
69
- for await (const event of workflowRun) {
70
- // Dispatch event to all adapters
71
- await Promise.all(
72
- adapters.map(adapter => adapter.dispatch(event))
73
- );
74
-
75
- // Update current state when steps complete
76
- if (event.type === WORKFLOW_EVENTS.STEP_COMPLETE) {
77
- if (event.patch) {
78
- currentState = applyPatches(currentState, [event.patch]) as TState;
79
- }
80
-
81
- // Check if we should stop after this step
82
- if (endAfter && stepNumber >= endAfter) {
83
- // Log final state if verbose
84
- if (verbose) {
85
- log(`\nWorkflow stopped after step ${endAfter} as requested: \n\n ${JSON.stringify(
86
- this.truncateDeep(structuredClone(currentState)), null, 2
87
- )}`);
88
- }
89
- return;
90
- }
91
-
92
- stepNumber++;
93
- }
94
-
95
- // Log final state on workflow completion/error if verbose
96
- if ((
97
- event.type === WORKFLOW_EVENTS.COMPLETE ||
98
- event.type === WORKFLOW_EVENTS.ERROR
99
- ) && verbose) {
100
- log(`\nWorkflow completed: \n\n ${JSON.stringify(
101
- this.truncateDeep(structuredClone(currentState)), null, 2
102
- )}`);
103
- }
104
- }
105
- }
106
-
107
- private truncateDeep(obj: any, maxLength: number = 100): any {
108
- if (obj === null || obj === undefined) return obj;
109
-
110
- if (typeof obj === 'string') {
111
- return obj.length > maxLength ? obj.slice(0, maxLength) + '...' : obj;
112
- }
113
-
114
- if (Array.isArray(obj)) {
115
- if (obj.length === 0) return obj;
116
-
117
- let truncatedArray = [];
118
- let currentLength = 2; // Account for [] brackets
119
-
120
- for (let i = 0; i < obj.length; i++) {
121
- const processedItem = this.truncateDeep(obj[i], maxLength);
122
- const itemStr = JSON.stringify(processedItem);
123
-
124
- if (currentLength + itemStr.length + (i > 0 ? 1 : 0) > maxLength) {
125
- truncatedArray.push(`... (${obj.length})`);
126
- break;
127
- }
128
-
129
- truncatedArray.push(processedItem);
130
- currentLength += itemStr.length + (i > 0 ? 1 : 0); // Add 1 for comma
131
- }
132
-
133
- return truncatedArray;
134
- }
135
-
136
- if (typeof obj === 'object') {
137
- const truncated: Record<string, any> = {};
138
- for (const [key, value] of Object.entries(obj)) {
139
- truncated[key] = this.truncateDeep(value, maxLength);
140
- }
141
- return truncated;
142
- }
143
-
144
- return obj;
145
- }
146
- }