@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.
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/dist/src/checkpoint.d.ts +97 -0
- package/dist/src/checkpoint.d.ts.map +1 -0
- package/dist/src/checkpoint.js +200 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/execution-transcript.d.ts +30 -0
- package/dist/src/execution-transcript.d.ts.map +1 -0
- package/dist/src/execution-transcript.js +70 -0
- package/dist/src/execution-transcript.js.map +1 -0
- package/dist/src/executor.d.ts +49 -0
- package/dist/src/executor.d.ts.map +1 -0
- package/dist/src/executor.js +390 -0
- package/dist/src/executor.js.map +1 -0
- package/dist/src/guards.d.ts +7 -0
- package/dist/src/guards.d.ts.map +1 -0
- package/dist/src/guards.js +13 -0
- package/dist/src/guards.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/ledger.d.ts +12 -0
- package/dist/src/ledger.d.ts.map +1 -0
- package/dist/src/ledger.js +53 -0
- package/dist/src/ledger.js.map +1 -0
- package/dist/src/resumable-executor.d.ts +39 -0
- package/dist/src/resumable-executor.d.ts.map +1 -0
- package/dist/src/resumable-executor.js +354 -0
- package/dist/src/resumable-executor.js.map +1 -0
- package/dist/src/retry.d.ts +7 -0
- package/dist/src/retry.d.ts.map +1 -0
- package/dist/src/retry.js +25 -0
- package/dist/src/retry.js.map +1 -0
- package/dist/src/tool-envelope.d.ts +14 -0
- package/dist/src/tool-envelope.d.ts.map +1 -0
- package/dist/src/tool-envelope.js +84 -0
- package/dist/src/tool-envelope.js.map +1 -0
- package/dist/tests/resumable.test.d.ts +2 -0
- package/dist/tests/resumable.test.d.ts.map +1 -0
- package/dist/tests/resumable.test.js +337 -0
- package/dist/tests/resumable.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +29 -0
- package/src/checkpoint.ts +311 -0
- package/src/execution-transcript.ts +108 -0
- package/src/executor.ts +540 -0
- package/src/guards.ts +21 -0
- package/src/index.ts +9 -0
- package/src/ledger.ts +63 -0
- package/src/resumable-executor.ts +471 -0
- package/src/retry.ts +37 -0
- package/src/tool-envelope.ts +113 -0
- package/tests/resumable.test.ts +421 -0
- 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
|
+
});
|