agentledger-runtime 1.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.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # AgentLedger Node / TypeScript Runtime
2
+
3
+ This directory contains the dependency-free Node/TypeScript-compatible runtime-core baseline for AgentLedger 1.0.2.
4
+
5
+ It runs a native local runtime loop, participates in the shared Python/Go/TypeScript/Rust conformance gate, and should be treated as runtime-core aligned; concrete production adapters are shipped separately as they mature.
6
+
7
+ ## Current Status
8
+
9
+ Implemented:
10
+
11
+ - local JSON store with atomic file writes and in-memory store for tests
12
+ - run/session/step state model
13
+ - leased step claim, heartbeat, lease recovery, and cancellation fencing
14
+ - `AgentContext` with state patch writes and runtime-managed tool calls
15
+ - `ToolGateway` with Tool Ledger idempotency for side-effect tools
16
+ - evidence export, replay, trace/diff/debug consumers, time-travel timeline, repro helpers
17
+ - policy denial, approval pause/resume, sandbox fail-closed behavior
18
+ - cost records, budget enforcement, and failure attribution
19
+ - media artifact refs and stream checkpoint refs in evidence/replay
20
+ - scheduler facade, worker service, failure injection, adversarial review, evidence regression
21
+ - MCP-style and dependency-free framework adapters
22
+ - `.d.ts` declarations
23
+ - official optional adapter APIs for Postgres, S3/MinIO, OTLP transport, and Docker sandbox manifests
24
+ - CLI for `conformance`, `contract validate`, and `contract export`
25
+
26
+ Not claimed yet:
27
+
28
+ - live Postgres/S3/Docker/OTLP service-backed hardening
29
+ - full framework-native packages
30
+ - full media processing and stream transport adapters
31
+
32
+
33
+ ## Install
34
+
35
+ Use the published npm package in a Node.js project:
36
+
37
+ ```bash
38
+ npm install agentledger-runtime
39
+ ```
40
+
41
+ Import the runtime API:
42
+
43
+ ```js
44
+ import { Runtime, simpleRun } from 'agentledger-runtime';
45
+ ```
46
+
47
+ Install or run the CLI through npm package binaries after installation:
48
+
49
+ ```bash
50
+ npx agentledger-ts --help
51
+ npx agentledger-ts quickstart
52
+ ```
53
+
54
+ From this repository, use `node src/cli.js ...` while developing.
55
+
56
+ ## Quickstart
57
+
58
+ ```js
59
+ import { Runtime, exportEvidence } from 'agentledger-runtime';
60
+
61
+ const rt = await Runtime.local('.agentledger-ts/state.json');
62
+ rt.registerTool({
63
+ name: 'docs.echo',
64
+ func: async (args) => ({ echo: args.text }),
65
+ });
66
+
67
+ const { runId } = await rt.createRun({ input: 'hello' });
68
+ await rt.runOnce({
69
+ runId,
70
+ workerId: 'worker-ts',
71
+ agentRole: 'Agent',
72
+ agent: async (ctx, state) => {
73
+ const result = await ctx.callTool('docs.echo', { text: state.input });
74
+ await ctx.writeState('result', result);
75
+ },
76
+ });
77
+
78
+ console.log(exportEvidence(rt.store, runId).run.run_id);
79
+ ```
80
+
81
+ ## Adapter Quickstart
82
+
83
+ ```js
84
+ import { PostgresAdapter, S3BlobStoreAdapter, DockerSandboxAdapter } from 'agentledger-runtime';
85
+
86
+ await new PostgresAdapter(injectedSqlClient).applyMigrations();
87
+
88
+ const s3 = new S3BlobStoreAdapter(injectedObjectClient, { bucket: 'agentledger-test' });
89
+ const { ref } = await s3.putJSON({ answer: 'ok' });
90
+ console.log(ref);
91
+
92
+ const manifest = new DockerSandboxAdapter().manifest({ network: 'deny' }, ['echo', 'ok']);
93
+ console.log(manifest);
94
+ ```
95
+
96
+ ## Verify
97
+
98
+ ```bash
99
+ cd typescript
100
+ npm test
101
+ npm run check
102
+ npm run conformance
103
+ npm run contract:validate
104
+ ```
105
+
106
+ From the repository root:
107
+
108
+ ```bash
109
+ python3.11 scripts/check_language_parity.py --json-report /tmp/agentledger-language-parity.json
110
+ ```
111
+
112
+ ## Compatibility Target
113
+
114
+ ```text
115
+ ../contracts/agentledger.runtime.v1.json
116
+ ../contracts/conformance/runtime_semantics.v1.json
117
+ ../docs/LANGUAGE_QUICKSTART.md
118
+ ../docs/LANGUAGE_PARITY_MATRIX.md
119
+ ```
@@ -0,0 +1,22 @@
1
+ # AgentLedger TypeScript Examples
2
+
3
+ Run from `typescript/` unless noted.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ cd typescript
9
+ node examples/quickstart/quickstart.js
10
+ ```
11
+
12
+ Source: `quickstart/quickstart.js`
13
+
14
+ ## CLI Quickstart
15
+
16
+ ```bash
17
+ cd typescript
18
+ node src/cli.js quickstart
19
+ node src/cli.js conformance
20
+ ```
21
+
22
+ Package surface: `agentledger-runtime`. See `../README.md` for package metadata and API examples.
@@ -0,0 +1,7 @@
1
+ import { simpleRun } from '../../src/index.js';
2
+
3
+ const result = await simpleRun(async (_ctx, state) => ({ message: 'hello from typescript', input: state.input }), {
4
+ initialState: { input: 'world' },
5
+ });
6
+
7
+ console.log(JSON.stringify({ run_id: result.run_id, output: result.output, state: result.state }, null, 2));
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Travel Assistant Demo — AgentLedger TypeScript Interactive Demo
4
+ * ================================================================
5
+ * 旅游助手交互式演示 — 每一步展示数据库里的实际变化
6
+ *
7
+ * Usage:
8
+ * node typescript/examples/travel_assistant/travel_assistant.js
9
+ * node typescript/examples/travel_assistant/travel_assistant.js .agentledger-ts
10
+ */
11
+
12
+ import { createInterface } from 'node:readline';
13
+ import {
14
+ Runtime, JSONStore, LocalBlobStore, PolicyEngine, BudgetController,
15
+ BudgetLimits, RetryableAgentError,
16
+ exportEvidence, replay, costAttribution,
17
+ } from '../../src/index.js';
18
+
19
+ // ════════════════════════════════════════════════════════════
20
+ // ANSI Colors
21
+ // ════════════════════════════════════════════════════════════
22
+ const C = {
23
+ R: '\x1b[91m', G: '\x1b[92m', Y: '\x1b[93m', B: '\x1b[94m',
24
+ M: '\x1b[95m', C: '\x1b[96m', BOLD: '\x1b[1m', DIM: '\x1b[2m', RST: '\x1b[0m',
25
+ };
26
+
27
+ // ════════════════════════════════════════════════════════════
28
+ // Mock data
29
+ // ════════════════════════════════════════════════════════════
30
+ const MOCK_FLIGHTS = [
31
+ { id: 'FL-002', from_city: 'Beijing', from_code: 'PEK', to_city: 'Tokyo', to_code: 'NRT', date: '2025-06-15', airline: 'JAL', price_usd: 580 },
32
+ ];
33
+ const MOCK_HOTELS = [
34
+ { id: 'HT-002', city: 'Tokyo', name: 'APA Hotel Shinjuku', nightly_usd: 85, stars: 3 },
35
+ ];
36
+ const MOCK_WEATHER = { Tokyo: { temp_c: 24, condition: 'Partly Cloudy', humidity: 65 } };
37
+ const bookingDB = {};
38
+
39
+ // ════════════════════════════════════════════════════════════
40
+ // Tool implementations
41
+ // ════════════════════════════════════════════════════════════
42
+
43
+ function searchFlights(args) {
44
+ const origin = (args.from || '').trim().toLowerCase();
45
+ const dest = (args.to || '').trim().toLowerCase();
46
+ const results = MOCK_FLIGHTS.filter(f =>
47
+ (f.from_city.toLowerCase().includes(origin) || f.from_code.toLowerCase().includes(origin)) &&
48
+ (f.to_city.toLowerCase().includes(dest) || f.to_code.toLowerCase().includes(dest))
49
+ );
50
+ return { results, count: results.length };
51
+ }
52
+
53
+ function searchHotels(args) {
54
+ const city = (args.city || '').trim().toLowerCase();
55
+ const results = MOCK_HOTELS.filter(h => h.city.toLowerCase() === city);
56
+ return { results, count: results.length };
57
+ }
58
+
59
+ function checkWeather(args) {
60
+ const city = (args.city || '').trim();
61
+ return { city, ...(MOCK_WEATHER[city] || { temp_c: 20, condition: 'Unknown' }) };
62
+ }
63
+
64
+ function bookFlight(args) {
65
+ const ref = `BK-F-${args.flight_id}-${(args.passenger || '').slice(0, 3).toUpperCase()}`;
66
+ if (bookingDB[ref]) return bookingDB[ref];
67
+ const f = MOCK_FLIGHTS.find(f => f.id === args.flight_id);
68
+ if (!f) throw new Error(`Flight not found: ${args.flight_id}`);
69
+ const booking = { booking_ref: ref, type: 'flight', airline: f.airline, price_usd: f.price_usd, status: 'confirmed', external_id: ref };
70
+ bookingDB[ref] = booking;
71
+ return booking;
72
+ }
73
+
74
+ function bookHotel(args) {
75
+ const ref = `BK-H-${args.hotel_id}-${(args.guest || '').slice(0, 3).toUpperCase()}`;
76
+ if (bookingDB[ref]) return bookingDB[ref];
77
+ const h = MOCK_HOTELS.find(h => h.id === args.hotel_id);
78
+ if (!h) throw new Error(`Hotel not found: ${args.hotel_id}`);
79
+ const booking = { booking_ref: ref, type: 'hotel', name: h.name, price_total_usd: h.nightly_usd * 5, status: 'confirmed', external_id: ref };
80
+ bookingDB[ref] = booking;
81
+ return booking;
82
+ }
83
+
84
+ // ════════════════════════════════════════════════════════════
85
+ // Agent function
86
+ // ════════════════════════════════════════════════════════════
87
+
88
+ async function travelPlanner(ctx, state) {
89
+ // Phase 1: Research
90
+ const flights = await ctx.callTool('travel.search_flights', { from: 'Beijing', to: 'Tokyo' });
91
+ const hotels = await ctx.callTool('travel.search_hotels', { city: 'Tokyo' });
92
+ const weather = await ctx.callTool('travel.check_weather', { city: 'Tokyo' });
93
+ await ctx.writeState('research', { flights: flights.count, hotels: hotels.count, weather: weather.temp_c });
94
+
95
+ // Phase 2: Book flight (approval required)
96
+ const flight = await ctx.callTool('travel.book_flight', {
97
+ flight_id: 'FL-002', passenger: 'Demo User',
98
+ _logical_operation: 'book-demo-flight',
99
+ });
100
+
101
+ // Phase 3: Simulated crash on attempt 2
102
+ if (ctx.attempt === 2) {
103
+ throw new RetryableAgentError('after flight booking');
104
+ }
105
+
106
+ // Phase 4: Book hotel (approval required)
107
+ const hotel = await ctx.callTool('travel.book_hotel', {
108
+ hotel_id: 'HT-002', check_in: '2025-06-15', check_out: '2025-06-20',
109
+ guest: 'Demo User', _logical_operation: 'book-demo-hotel',
110
+ });
111
+
112
+ await ctx.writeState('bookings', { flight: flight.booking_ref, hotel: hotel.booking_ref });
113
+ await ctx.writeState('trip_status', 'confirmed');
114
+ }
115
+
116
+ // ════════════════════════════════════════════════════════════
117
+ // Display helpers
118
+ // ════════════════════════════════════════════════════════════
119
+
120
+ function wait(msg = 'Press Enter to continue') {
121
+ return new Promise((resolve) => {
122
+ process.stdout.write(`\n${C.DIM} ⏎ ${msg}...${C.RST}`);
123
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
124
+ rl.once('line', () => { rl.close(); resolve(); });
125
+ });
126
+ }
127
+
128
+ function showRows(label, headers, rows, color = C.C) {
129
+ if (!rows || rows.length === 0) {
130
+ console.log(`\n ${color}${label}:${C.RST} ${C.DIM}(empty)${C.RST}`);
131
+ return;
132
+ }
133
+ console.log(`\n ${color}${label} (${rows.length} rows):${C.RST}`);
134
+ for (const row of rows) {
135
+ const items = headers.map((h, i) => `${h}=${C.BOLD}${row[i]}${C.RST}`);
136
+ console.log(` ${C.DIM}${items.join(' | ')}${C.RST}`);
137
+ }
138
+ }
139
+
140
+ function showDB(store, runID) {
141
+ // Runs
142
+ const runData = store._data?.runs?.[runID]; // eslint-disable-line no-underscore-dangle
143
+ const runRows = [];
144
+ if (runData) {
145
+ const shortID = runData.run_id.length > 24 ? runData.run_id.slice(0, 24) + '...' : runData.run_id;
146
+ runRows.push([shortID, runData.status, String(runData.state_version)]);
147
+ }
148
+ showRows('Runs', ['run_id', 'status', 'state_version'], runRows, C.B);
149
+
150
+ // Steps
151
+ const steps = store.steps(runID);
152
+ const stepRows = steps.map(s => {
153
+ const shortID = s.step_id.length > 24 ? s.step_id.slice(0, 24) + '...' : s.step_id;
154
+ return [shortID, s.status, String(s.attempt)];
155
+ });
156
+ showRows('Steps', ['step_id', 'status', 'attempt'], stepRows, C.B);
157
+
158
+ // Tool Ledger
159
+ const ledger = store.ledger(runID);
160
+ const ledgerRows = ledger.map(tl => {
161
+ const key = tl.idempotency_key;
162
+ const parts = key.split(':');
163
+ const shortKey = parts.length >= 2 ? parts[parts.length - 2] + ':' + parts[parts.length - 1].slice(0, 10) : key.slice(0, 25);
164
+ return [tl.tool_name, tl.status, shortKey];
165
+ });
166
+ showRows('Tool Ledger', ['tool', 'status', 'idemp_key'], ledgerRows, C.Y);
167
+
168
+ // Approval requests
169
+ const approvals = store.approvalRequests(runID);
170
+ const approvalRows = approvals.map(a => [a.tool_name, a.status, a.approved_by || '-']);
171
+ showRows('Approval Requests', ['tool', 'status', 'approved_by'], approvalRows, C.R);
172
+
173
+ console.log();
174
+ }
175
+
176
+ // ════════════════════════════════════════════════════════════
177
+ // Main
178
+ // ════════════════════════════════════════════════════════════
179
+
180
+ async function main() {
181
+ const root = process.argv[2] || '.agentledger-ts';
182
+
183
+ // Intro
184
+ console.log(`\n${C.BOLD}${C.C} ╔════════════════════════════════════════════════════╗${C.RST}`);
185
+ console.log(`${C.BOLD}${C.C} ║ AgentLedger Travel Assistant (TS) — Interactive Demo ║${C.RST}`);
186
+ console.log(`${C.BOLD}${C.C} ║ See real database state at every step ║${C.RST}`);
187
+ console.log(`${C.BOLD}${C.C} ╚════════════════════════════════════════════════════╝${C.RST}`);
188
+
189
+ console.log(`\n ${C.DIM}AgentLedger — Durable Execution Runtime for AI Agents${C.RST}`);
190
+ console.log(` ┌────────────────────────────────────────────────────┐`);
191
+ console.log(` │ ${C.G}✓${C.RST} Durable execution — crash recovery │`);
192
+ console.log(` │ ${C.G}✓${C.RST} Tool Ledger — idempotent replay │`);
193
+ console.log(` │ ${C.G}✓${C.RST} Approval gates — human-in-the-loop │`);
194
+ console.log(` │ ${C.G}✓${C.RST} Policy engine — role-based access │`);
195
+ console.log(` │ ${C.G}✓${C.RST} Budget control — tool call limits │`);
196
+ console.log(` │ ${C.G}✓${C.RST} Evidence export — full audit trail │`);
197
+ console.log(` └────────────────────────────────────────────────────┘`);
198
+
199
+ await wait('Press Enter to start / 按 Enter 开始');
200
+
201
+ // ════════════════════════════════════════════════════════
202
+ // Step 1: Setup
203
+ // ════════════════════════════════════════════════════════
204
+ console.log(`\n${C.BOLD}${C.B}${'═'.repeat(60)}${C.RST}`);
205
+ console.log(`${C.BOLD}${C.B} Step 1: Initialize — Register tools, configure policy${C.RST}`);
206
+ console.log(`${C.BOLD}${C.B}${'═'.repeat(60)}${C.RST}`);
207
+
208
+ const store = await JSONStore.open(`${root}/state.json`);
209
+ const rt = new Runtime(store);
210
+ rt.setBudget({ maxToolCalls: 25 });
211
+
212
+ for (const t of ['travel.search_flights', 'travel.search_hotels', 'travel.check_weather',
213
+ 'travel.book_flight', 'travel.book_hotel']) {
214
+ rt.policy.allowTool('TravelPlanner', t);
215
+ }
216
+
217
+ rt.registerTool({
218
+ name: 'travel.search_flights', version: 'v1', sideEffect: 'none', riskLevel: 'low',
219
+ inputSchema: { type: 'object', required: ['from', 'to'] },
220
+ outputSchema: { type: 'object' },
221
+ func: searchFlights,
222
+ });
223
+ rt.registerTool({
224
+ name: 'travel.search_hotels', version: 'v1', sideEffect: 'none', riskLevel: 'low',
225
+ inputSchema: { type: 'object', required: ['city'] },
226
+ outputSchema: { type: 'object' },
227
+ func: searchHotels,
228
+ });
229
+ rt.registerTool({
230
+ name: 'travel.check_weather', version: 'v1', sideEffect: 'none', riskLevel: 'low',
231
+ inputSchema: { type: 'object', required: ['city'] },
232
+ outputSchema: { type: 'object' },
233
+ func: checkWeather,
234
+ });
235
+ rt.registerTool({
236
+ name: 'travel.book_flight', version: 'v1', sideEffect: 'external_write', riskLevel: 'high',
237
+ idempotencyRequired: true, approvalRequired: true,
238
+ inputSchema: { type: 'object', required: ['flight_id', 'passenger'] },
239
+ outputSchema: { type: 'object' },
240
+ func: bookFlight,
241
+ });
242
+ rt.registerTool({
243
+ name: 'travel.book_hotel', version: 'v1', sideEffect: 'external_write', riskLevel: 'high',
244
+ idempotencyRequired: true, approvalRequired: true,
245
+ inputSchema: { type: 'object', required: ['hotel_id', 'guest'] },
246
+ outputSchema: { type: 'object' },
247
+ func: bookHotel,
248
+ });
249
+
250
+ const { runId } = await rt.createRun({ trip: 'Tokyo', budget_usd: 3000 });
251
+ console.log(`\n ${C.B}Run created: ${C.BOLD}${runId}${C.RST}`);
252
+ showDB(store, runId);
253
+ await wait('Press Enter to continue');
254
+
255
+ // ════════════════════════════════════════════════════════
256
+ // Step 2: Attempt 1 — Approval interception
257
+ // ════════════════════════════════════════════════════════
258
+ console.log(`\n${C.BOLD}${C.R}${'═'.repeat(60)}${C.RST}`);
259
+ console.log(`${C.BOLD}${C.R} Step 2: Attempt 1 — Agent runs → Approval triggered${C.RST}`);
260
+ console.log(`${C.BOLD}${C.R}${'═'.repeat(60)}${C.RST}`);
261
+ console.log(`\n ${C.DIM}Agent executing: search flights → search hotels → check weather → book flight...${C.RST}`);
262
+
263
+ await rt.runOnce({ runId, workerId: 'worker-node', agentRole: 'TravelPlanner', agent: travelPlanner });
264
+
265
+ console.log(`\n ${C.R}book_flight triggered approval! Runtime paused, waiting for human.${C.RST}`);
266
+ showDB(store, runId);
267
+ console.log(` ${C.R}Note: Tool Ledger has RESERVED entry, approval status is PENDING${C.RST}`);
268
+ await wait('Press Enter to approve / 按 Enter 审批');
269
+
270
+ for (const req of store.approvalRequests(runId)) {
271
+ if (req.status === 'PENDING') {
272
+ await store.approveRequest(req.approval_id, { approver: 'traveler', reason: 'Within budget, approved' });
273
+ console.log(`\n ${C.G}✅ Approved: ${req.tool_name} — by traveler${C.RST}`);
274
+ }
275
+ }
276
+ showDB(store, runId);
277
+ await wait('Press Enter to continue');
278
+
279
+ // ════════════════════════════════════════════════════════
280
+ // Step 3: Attempt 2 — Execute + Crash
281
+ // ════════════════════════════════════════════════════════
282
+ console.log(`\n${C.BOLD}${C.Y}${'═'.repeat(60)}${C.RST}`);
283
+ console.log(`${C.BOLD}${C.Y} Step 3: Attempt 2 — Approved → Execute booking → Simulated crash${C.RST}`);
284
+ console.log(`${C.BOLD}${C.Y}${'═'.repeat(60)}${C.RST}`);
285
+ console.log(`\n ${C.DIM}Re-running agent (approval passed, book_flight will execute)...${C.RST}`);
286
+
287
+ await rt.runOnce({ runId, workerId: 'worker-node', agentRole: 'TravelPlanner', agent: travelPlanner });
288
+
289
+ console.log(`\n ${C.Y}Agent booked flight, then crashed before committing state!${C.RST}`);
290
+ console.log(` ${C.Y}Flight is booked in external system, but agent state was NOT persisted.${C.RST}`);
291
+ showDB(store, runId);
292
+ console.log(` ${C.Y}Key: Tool Ledger book_flight status = ${C.G}SUCCEEDED${C.Y} (external side effect executed)${C.RST}`);
293
+ console.log(` ${C.Y} Step status = retry_scheduled (state not committed, waiting for retry)${C.RST}`);
294
+ await wait('Press Enter to continue');
295
+
296
+ // ════════════════════════════════════════════════════════
297
+ // Step 4: Attempt 3 — Recovery + Hotel approval
298
+ // ════════════════════════════════════════════════════════
299
+ console.log(`\n${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
300
+ console.log(`${C.BOLD}${C.G} Step 4: Attempt 3 — Crash recovery → Tool Ledger idempotent replay${C.RST}`);
301
+ console.log(`${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
302
+ console.log(`\n ${C.DIM}Agent re-executes. book_flight: Tool Ledger sees SUCCEEDED record...${C.RST}`);
303
+ console.log(` ${C.DIM}${C.G}→ Returns cached result, no duplicate API call, no double charge!${C.RST}`);
304
+
305
+ await rt.runOnce({ runId, workerId: 'worker-node', agentRole: 'TravelPlanner', agent: travelPlanner });
306
+
307
+ console.log(`\n ${C.G}✅ Flight idempotent replay successful! (no duplicate _book_flight call)${C.RST}`);
308
+ console.log(` ${C.R}Hotel booking → triggers approval again${C.RST}`);
309
+
310
+ for (const req of store.approvalRequests(runId)) {
311
+ if (req.status === 'PENDING') {
312
+ await store.approveRequest(req.approval_id, { approver: 'traveler', reason: 'Hotel within budget, approved' });
313
+ console.log(`\n ${C.G}✅ Approved: ${req.tool_name} — by traveler${C.RST}`);
314
+ }
315
+ }
316
+ showDB(store, runId);
317
+ await wait('Press Enter to continue');
318
+
319
+ // ════════════════════════════════════════════════════════
320
+ // Step 5: Attempt 4 — Complete
321
+ // ════════════════════════════════════════════════════════
322
+ console.log(`\n${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
323
+ console.log(`${C.BOLD}${C.G} Step 5: Attempt 4 — Hotel approved → Full execution → State committed${C.RST}`);
324
+ console.log(`${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
325
+
326
+ const ok = await rt.runOnce({ runId, workerId: 'worker-node', agentRole: 'TravelPlanner', agent: travelPlanner });
327
+ if (!ok) {
328
+ console.error('Recovery failed');
329
+ process.exit(1);
330
+ }
331
+ if (Object.keys(bookingDB).length !== 2) {
332
+ console.error(`Expected 2 bookings, got ${Object.keys(bookingDB).length}`);
333
+ process.exit(1);
334
+ }
335
+
336
+ console.log(`\n ${C.G}✅ Travel planning complete! State persisted to database.${C.RST}`);
337
+ showDB(store, runId);
338
+ console.log(` ${C.G}Step status = completed, State has bookings + trip_status${C.RST}`);
339
+ console.log(` ${C.G}External bookings: [${Object.keys(bookingDB).join(', ')}] (2 total, no duplicates)${C.RST}`);
340
+ await wait('Press Enter to continue');
341
+
342
+ // ════════════════════════════════════════════════════════
343
+ // Step 6: Evidence + Cost + Replay
344
+ // ════════════════════════════════════════════════════════
345
+ console.log(`\n${C.BOLD}${C.M}${'═'.repeat(60)}${C.RST}`);
346
+ console.log(`${C.BOLD}${C.M} Step 6: Evidence export + Cost attribution + Replay verification${C.RST}`);
347
+ console.log(`${C.BOLD}${C.M}${'═'.repeat(60)}${C.RST}`);
348
+
349
+ const bundle = exportEvidence(store, runId);
350
+ const replayResult = replay(store, runId);
351
+ const cost = costAttribution(store, runId);
352
+
353
+ console.log(`\n ${C.M}Cost attribution: ${cost.total?.tool_calls ?? 0} tool calls${C.RST}`);
354
+ console.log(` ${C.M}Replay: ${replayResult.event_count} events, safe=${C.G}${replayResult.replay_safe}${C.RST}`);
355
+ console.log(` ${C.M}Evidence bundle: ${bundle.events.length} events total${C.RST}`);
356
+
357
+ // Final summary
358
+ console.log(`\n${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
359
+ console.log(`${C.BOLD}${C.G} Summary: What AgentLedger (TypeScript) did in this demo${C.RST}`);
360
+ console.log(`${C.BOLD}${C.G}${'═'.repeat(60)}${C.RST}`);
361
+ console.log(`
362
+ ┌──────────────────────────────────────────────────────────┐
363
+ │ │
364
+ │ ${C.G}✓ Durable execution${C.RST} Crash → auto retry, state preserved │
365
+ │ Step: retry_scheduled → completed │
366
+ │ │
367
+ │ ${C.G}✓ Tool Ledger${C.RST} Idempotent replay, flight booked ${C.BOLD}1x${C.RST} only │
368
+ │ SUCCEEDED → cached result on retry │
369
+ │ │
370
+ │ ${C.G}✓ Approval gates${C.RST} Flight + hotel each trigger approval │
371
+ │ approval_requests records in store │
372
+ │ │
373
+ │ ${C.G}✓ Policy engine${C.RST} Each tool call checked by policy │
374
+ │ TravelPlanner role allowed │
375
+ │ │
376
+ │ ${C.G}✓ Budget control${C.RST} Tracked ${cost.total?.tool_calls ?? 0} tool calls │
377
+ │ BudgetController.beforeToolCall() │
378
+ │ │
379
+ │ ${C.G}✓ Evidence export${C.RST} ${bundle.events.length} events recorded │
380
+ │ events stored in JSON store │
381
+ │ │
382
+ │ ${C.G}✓ Cost attribution${C.RST} Auto-recorded per run │
383
+ │ CostAttribution by agent │
384
+ │ │
385
+ │ ${C.G}✓ Replay engine${C.RST} Event hash verification passed │
386
+ │ Verify history without re-running │
387
+ │ │
388
+ └──────────────────────────────────────────────────────────┘
389
+ `);
390
+
391
+ console.log(` ${C.DIM}Storage file: ${root}/state.json${C.RST}`);
392
+ console.log(` ${C.DIM}Run: node typescript/examples/travel_assistant/travel_assistant.js${C.RST}`);
393
+ console.log();
394
+ }
395
+
396
+ main().catch(err => { console.error(err); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "agentledger-runtime",
3
+ "version": "1.0.2",
4
+ "private": false,
5
+ "description": "Dependency-free Node/TypeScript-compatible runtime for AgentLedger.",
6
+ "type": "module",
7
+ "main": "src/index.js",
8
+ "types": "src/index.d.ts",
9
+ "scripts": {
10
+ "test": "node --test",
11
+ "check": "node --check src/index.js && node --check test/runtime.test.js",
12
+ "conformance": "node src/cli.js conformance",
13
+ "contract:validate": "node src/cli.js contract validate"
14
+ },
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "bin": {
19
+ "agentledger-ts": "src/cli.js"
20
+ }
21
+ }