@ddse/acm-runtime 0.5.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +393 -0
  3. package/dist/src/checkpoint.d.ts +97 -0
  4. package/dist/src/checkpoint.d.ts.map +1 -0
  5. package/dist/src/checkpoint.js +200 -0
  6. package/dist/src/checkpoint.js.map +1 -0
  7. package/dist/src/execution-transcript.d.ts +30 -0
  8. package/dist/src/execution-transcript.d.ts.map +1 -0
  9. package/dist/src/execution-transcript.js +70 -0
  10. package/dist/src/execution-transcript.js.map +1 -0
  11. package/dist/src/executor.d.ts +49 -0
  12. package/dist/src/executor.d.ts.map +1 -0
  13. package/dist/src/executor.js +390 -0
  14. package/dist/src/executor.js.map +1 -0
  15. package/dist/src/guards.d.ts +7 -0
  16. package/dist/src/guards.d.ts.map +1 -0
  17. package/dist/src/guards.js +13 -0
  18. package/dist/src/guards.js.map +1 -0
  19. package/dist/src/index.d.ts +9 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +10 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/ledger.d.ts +12 -0
  24. package/dist/src/ledger.d.ts.map +1 -0
  25. package/dist/src/ledger.js +53 -0
  26. package/dist/src/ledger.js.map +1 -0
  27. package/dist/src/resumable-executor.d.ts +39 -0
  28. package/dist/src/resumable-executor.d.ts.map +1 -0
  29. package/dist/src/resumable-executor.js +354 -0
  30. package/dist/src/resumable-executor.js.map +1 -0
  31. package/dist/src/retry.d.ts +7 -0
  32. package/dist/src/retry.d.ts.map +1 -0
  33. package/dist/src/retry.js +25 -0
  34. package/dist/src/retry.js.map +1 -0
  35. package/dist/src/tool-envelope.d.ts +14 -0
  36. package/dist/src/tool-envelope.d.ts.map +1 -0
  37. package/dist/src/tool-envelope.js +84 -0
  38. package/dist/src/tool-envelope.js.map +1 -0
  39. package/dist/tests/resumable.test.d.ts +2 -0
  40. package/dist/tests/resumable.test.d.ts.map +1 -0
  41. package/dist/tests/resumable.test.js +337 -0
  42. package/dist/tests/resumable.test.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -0
  44. package/package.json +29 -0
  45. package/src/checkpoint.ts +311 -0
  46. package/src/execution-transcript.ts +108 -0
  47. package/src/executor.ts +540 -0
  48. package/src/guards.ts +21 -0
  49. package/src/index.ts +9 -0
  50. package/src/ledger.ts +63 -0
  51. package/src/resumable-executor.ts +471 -0
  52. package/src/retry.ts +37 -0
  53. package/src/tool-envelope.ts +113 -0
  54. package/tests/resumable.test.ts +421 -0
  55. package/tsconfig.json +11 -0
@@ -0,0 +1,421 @@
1
+ // Test for resumable executor
2
+ import {
3
+ executeResumablePlan,
4
+ MemoryCheckpointStore,
5
+ ResumableExecutor,
6
+ MemoryLedger,
7
+ } from '../src/index.js';
8
+ import type {
9
+ Goal,
10
+ Context,
11
+ Plan,
12
+ CapabilityRegistry,
13
+ ToolRegistry,
14
+ RunContext,
15
+ NucleusFactory,
16
+ NucleusConfig,
17
+ PreflightResult,
18
+ PostcheckResult,
19
+ NucleusInvokeResult,
20
+ InternalContextScope,
21
+ LedgerEntry,
22
+ } from '@ddse/acm-sdk';
23
+ import { Task, Nucleus } from '@ddse/acm-sdk';
24
+
25
+ // Simple test capability
26
+ class TestTask extends Task {
27
+ constructor() {
28
+ super('test-task', 'test');
29
+ }
30
+
31
+ async execute(ctx: RunContext, input: any): Promise<any> {
32
+ return { result: `executed-${input?.value || 'default'}` };
33
+ }
34
+ }
35
+
36
+ // Simple registries
37
+ class SimpleCapabilityRegistry implements CapabilityRegistry {
38
+ private tasks = new Map<string, Task>();
39
+
40
+ register(capability: any, task: Task): void {
41
+ this.tasks.set(capability.name, task);
42
+ }
43
+
44
+ resolve(capability: string): Task | undefined {
45
+ return this.tasks.get(capability);
46
+ }
47
+
48
+ list(): any[] {
49
+ return Array.from(this.tasks.keys()).map(name => ({ name }));
50
+ }
51
+
52
+ has(name: string): boolean {
53
+ return this.tasks.has(name);
54
+ }
55
+
56
+ inputSchema(name: string): unknown {
57
+ return undefined;
58
+ }
59
+
60
+ outputSchema(name: string): unknown {
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ class SimpleToolRegistry implements ToolRegistry {
66
+ get(name: string): any {
67
+ return null;
68
+ }
69
+
70
+ list(): any[] {
71
+ return [];
72
+ }
73
+ }
74
+
75
+ class TestNucleus extends Nucleus {
76
+ private scope?: InternalContextScope;
77
+
78
+ constructor(config: NucleusConfig) {
79
+ super(config);
80
+ }
81
+
82
+ async preflight(): Promise<PreflightResult> {
83
+ return { status: 'OK' };
84
+ }
85
+
86
+ async invoke(): Promise<NucleusInvokeResult> {
87
+ return { toolCalls: [] };
88
+ }
89
+
90
+ async postcheck(): Promise<PostcheckResult> {
91
+ return { status: 'COMPLETE' };
92
+ }
93
+
94
+ recordInference(promptDigest: string): LedgerEntry {
95
+ return {
96
+ id: `test-nucleus-${Date.now()}`,
97
+ ts: Date.now(),
98
+ type: 'NUCLEUS_INFERENCE',
99
+ details: {
100
+ promptDigest,
101
+ },
102
+ };
103
+ }
104
+
105
+ getInternalContext(): InternalContextScope | undefined {
106
+ return this.scope;
107
+ }
108
+
109
+ setInternalContext(scope: InternalContextScope): void {
110
+ this.scope = scope;
111
+ }
112
+ }
113
+
114
+ const testNucleusFactory: NucleusFactory = config => new TestNucleus(config);
115
+
116
+ const testNucleusConfig = {
117
+ llmCall: {
118
+ provider: 'noop',
119
+ model: 'noop',
120
+ },
121
+ };
122
+
123
+ const sharedNucleusOptions = {
124
+ nucleusFactory: testNucleusFactory,
125
+ nucleusConfig: testNucleusConfig,
126
+ };
127
+
128
+ async function testBasicCheckpoint() {
129
+ console.log('Testing basic checkpoint creation...');
130
+
131
+ const goal: Goal = {
132
+ id: 'test-goal',
133
+ intent: 'Test checkpoint creation',
134
+ };
135
+
136
+ const context: Context = {
137
+ id: 'test-context',
138
+ facts: { test: true },
139
+ };
140
+
141
+ const plan: Plan = {
142
+ id: 'test-plan',
143
+ contextRef: 'test-ref',
144
+ capabilityMapVersion: '1.0',
145
+ tasks: [
146
+ {
147
+ id: 't1',
148
+ capability: 'test',
149
+ input: { value: 'task1' },
150
+ },
151
+ {
152
+ id: 't2',
153
+ capability: 'test',
154
+ input: { value: 'task2' },
155
+ },
156
+ ],
157
+ edges: [
158
+ { from: 't1', to: 't2' },
159
+ ],
160
+ };
161
+
162
+ const capabilityRegistry = new SimpleCapabilityRegistry();
163
+ capabilityRegistry.register({ name: 'test' }, new TestTask());
164
+
165
+ const toolRegistry = new SimpleToolRegistry();
166
+ const checkpointStore = new MemoryCheckpointStore();
167
+ const ledger = new MemoryLedger();
168
+ const runId = 'test-run-1';
169
+
170
+ const result = await executeResumablePlan({
171
+ goal,
172
+ context,
173
+ plan,
174
+ capabilityRegistry,
175
+ toolRegistry,
176
+ ledger,
177
+ runId,
178
+ checkpointStore,
179
+ checkpointInterval: 1,
180
+ ...sharedNucleusOptions,
181
+ });
182
+
183
+ // Verify execution completed
184
+ if (!result.outputsByTask.t1 || !result.outputsByTask.t2) {
185
+ throw new Error('Tasks not executed');
186
+ }
187
+
188
+ if (result.outputsByTask.t1.output.result !== 'executed-task1') {
189
+ throw new Error('Task 1 output missing in execution record');
190
+ }
191
+
192
+ if (result.outputsByTask.t2.output.result !== 'executed-task2') {
193
+ throw new Error('Task 2 output missing in execution record');
194
+ }
195
+
196
+ // Verify checkpoints were created
197
+ const checkpoints = await checkpointStore.list(runId);
198
+ if (checkpoints.length < 2) {
199
+ throw new Error(`Expected at least 2 checkpoints, got ${checkpoints.length}`);
200
+ }
201
+
202
+ console.log('✅ Basic checkpoint test passed');
203
+ console.log(` Created ${checkpoints.length} checkpoints`);
204
+ }
205
+
206
+ async function testResume() {
207
+ console.log('Testing resume from checkpoint...');
208
+
209
+ const goal: Goal = {
210
+ id: 'test-goal-resume',
211
+ intent: 'Test resume functionality',
212
+ };
213
+
214
+ const context: Context = {
215
+ id: 'test-context-resume',
216
+ facts: { test: true },
217
+ };
218
+
219
+ const plan: Plan = {
220
+ id: 'test-plan-resume',
221
+ contextRef: 'test-ref-resume',
222
+ capabilityMapVersion: '1.0',
223
+ tasks: [
224
+ {
225
+ id: 't1',
226
+ capability: 'test',
227
+ input: { value: 'task1' },
228
+ },
229
+ {
230
+ id: 't2',
231
+ capability: 'test',
232
+ input: { value: 'task2' },
233
+ },
234
+ {
235
+ id: 't3',
236
+ capability: 'test',
237
+ input: { value: 'task3' },
238
+ },
239
+ ],
240
+ edges: [
241
+ { from: 't1', to: 't2' },
242
+ { from: 't2', to: 't3' },
243
+ ],
244
+ };
245
+
246
+ const capabilityRegistry = new SimpleCapabilityRegistry();
247
+ capabilityRegistry.register({ name: 'test' }, new TestTask());
248
+
249
+ const toolRegistry = new SimpleToolRegistry();
250
+ const checkpointStore = new MemoryCheckpointStore();
251
+ const ledger1 = new MemoryLedger();
252
+ const runId = 'test-run-resume';
253
+
254
+ // First execution - execute first 2 tasks
255
+ const result1 = await executeResumablePlan({
256
+ goal,
257
+ context,
258
+ plan: {
259
+ ...plan,
260
+ // Simulate interruption by only including first 2 tasks
261
+ tasks: plan.tasks.slice(0, 2),
262
+ edges: [{ from: 't1', to: 't2' }],
263
+ },
264
+ capabilityRegistry,
265
+ toolRegistry,
266
+ ledger: ledger1,
267
+ runId,
268
+ checkpointStore,
269
+ checkpointInterval: 1,
270
+ ...sharedNucleusOptions,
271
+ });
272
+
273
+ // Get the latest checkpoint
274
+ const checkpoint = await checkpointStore.get(runId);
275
+ if (!checkpoint) {
276
+ throw new Error('No checkpoint found');
277
+ }
278
+
279
+ console.log(` First execution completed, checkpoint: ${checkpoint.id}`);
280
+
281
+ // Resume from checkpoint
282
+ const ledger2 = new MemoryLedger();
283
+ const result2 = await executeResumablePlan({
284
+ goal,
285
+ context,
286
+ plan,
287
+ capabilityRegistry,
288
+ toolRegistry,
289
+ ledger: ledger2,
290
+ runId,
291
+ checkpointStore,
292
+ resumeFrom: checkpoint.id,
293
+ checkpointInterval: 1,
294
+ ...sharedNucleusOptions,
295
+ });
296
+
297
+ // Verify all tasks completed
298
+ if (!result2.outputsByTask.t1 || !result2.outputsByTask.t2 || !result2.outputsByTask.t3) {
299
+ throw new Error('Not all tasks executed after resume');
300
+ }
301
+
302
+ // Verify t1 and t2 outputs match (were restored from checkpoint)
303
+ if (result2.outputsByTask.t1.output.result !== 'executed-task1') {
304
+ throw new Error('Task 1 output not restored correctly');
305
+ }
306
+
307
+ if (result2.outputsByTask.t2.output.result !== 'executed-task2') {
308
+ throw new Error('Task 2 output not restored correctly');
309
+ }
310
+
311
+ console.log('✅ Resume test passed');
312
+ console.log(` Successfully resumed and completed task 3`);
313
+ }
314
+
315
+ async function testResumableExecutor() {
316
+ console.log('Testing ResumableExecutor class...');
317
+
318
+ const executor = new ResumableExecutor();
319
+
320
+ const goal: Goal = {
321
+ id: 'test-goal-executor',
322
+ intent: 'Test ResumableExecutor',
323
+ };
324
+
325
+ const context: Context = {
326
+ id: 'test-context-executor',
327
+ facts: { test: true },
328
+ };
329
+
330
+ const plan: Plan = {
331
+ id: 'test-plan-executor',
332
+ contextRef: 'test-ref-executor',
333
+ capabilityMapVersion: '1.0',
334
+ tasks: [
335
+ {
336
+ id: 't1',
337
+ capability: 'test',
338
+ input: { value: 'task1' },
339
+ },
340
+ ],
341
+ edges: [],
342
+ };
343
+
344
+ const capabilityRegistry = new SimpleCapabilityRegistry();
345
+ capabilityRegistry.register({ name: 'test' }, new TestTask());
346
+
347
+ const toolRegistry = new SimpleToolRegistry();
348
+ const runId = 'test-run-executor';
349
+
350
+ const result = await executor.execute({
351
+ goal,
352
+ context,
353
+ plan,
354
+ capabilityRegistry,
355
+ toolRegistry,
356
+ runId,
357
+ checkpointInterval: 1,
358
+ ...sharedNucleusOptions,
359
+ });
360
+
361
+ // Verify execution completed
362
+ if (!result.outputsByTask.t1) {
363
+ throw new Error('Task not executed');
364
+ }
365
+
366
+ if (result.outputsByTask.t1.output.result !== 'executed-task1') {
367
+ throw new Error('Task output not captured in execution record');
368
+ }
369
+
370
+ // List checkpoints
371
+ const checkpoints = await executor.listCheckpoints(runId);
372
+ if (checkpoints.length < 1) {
373
+ throw new Error('No checkpoints created');
374
+ }
375
+
376
+ console.log('✅ ResumableExecutor test passed');
377
+ }
378
+
379
+ async function runTests() {
380
+ console.log('Running Resumable Executor Tests');
381
+ console.log('==================================================');
382
+
383
+ let passed = 0;
384
+ let failed = 0;
385
+
386
+ try {
387
+ await testBasicCheckpoint();
388
+ passed++;
389
+ } catch (err) {
390
+ console.error('❌ Basic checkpoint test failed:', err);
391
+ failed++;
392
+ }
393
+
394
+ try {
395
+ await testResume();
396
+ passed++;
397
+ } catch (err) {
398
+ console.error('❌ Resume test failed:', err);
399
+ failed++;
400
+ }
401
+
402
+ try {
403
+ await testResumableExecutor();
404
+ passed++;
405
+ } catch (err) {
406
+ console.error('❌ ResumableExecutor test failed:', err);
407
+ failed++;
408
+ }
409
+
410
+ console.log('==================================================');
411
+ console.log(`Results: ${passed} passed, ${failed} failed`);
412
+
413
+ if (failed > 0) {
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ runTests().catch(err => {
419
+ console.error('Fatal error:', err);
420
+ process.exit(1);
421
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/**/*", "tests/**/*"],
8
+ "references": [
9
+ { "path": "../acm-sdk" }
10
+ ]
11
+ }