@juli-os/ledger 0.1.4 → 0.1.5
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/dist/index.js +49 -29
- package/dist/juli-ledger.js +50 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -228,6 +228,7 @@ var dedupPattern = (key) => {
|
|
|
228
228
|
const jsonValue = JSON.stringify(key).slice(1, -1);
|
|
229
229
|
return `%"dedup_key":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
230
230
|
};
|
|
231
|
+
var SEQ_PARK_BASE = 1e9;
|
|
231
232
|
var SEND_STEP_LIKE_CUTOFF = "2026-09-19";
|
|
232
233
|
var rowToWorkflow = (r) => ({
|
|
233
234
|
id: str(r["id"]),
|
|
@@ -267,15 +268,23 @@ var rowToEvent = (r) => ({
|
|
|
267
268
|
});
|
|
268
269
|
var pruneImpl = (db, retentionDays, nowIso) => {
|
|
269
270
|
if (retentionDays <= 0) return { events: 0, workflows: 0 };
|
|
270
|
-
const cutoff = new Date(new Date(nowIso).getTime() - retentionDays * 864e5)
|
|
271
|
+
const cutoff = rfc3339(new Date(new Date(nowIso).getTime() - retentionDays * 864e5));
|
|
271
272
|
const ev = db.prepare("DELETE FROM events WHERE ts < ?").run(cutoff);
|
|
272
273
|
const wfRows = db.prepare(
|
|
273
274
|
`SELECT id FROM workflows WHERE status IN (${sqlInList(PRUNE_WORKFLOW_STATUSES)}) AND updated_at < ?`
|
|
274
275
|
).all(cutoff);
|
|
275
276
|
let wfCount = 0;
|
|
276
277
|
for (const r of wfRows) {
|
|
277
|
-
db.
|
|
278
|
-
|
|
278
|
+
db.exec("BEGIN");
|
|
279
|
+
try {
|
|
280
|
+
db.prepare("DELETE FROM workflow_steps WHERE workflow_id=?").run(r.id);
|
|
281
|
+
db.prepare("DELETE FROM relays WHERE workflow_id=?").run(r.id);
|
|
282
|
+
db.prepare("DELETE FROM workflows WHERE id=?").run(r.id);
|
|
283
|
+
db.exec("COMMIT");
|
|
284
|
+
} catch (e) {
|
|
285
|
+
db.exec("ROLLBACK");
|
|
286
|
+
throw e;
|
|
287
|
+
}
|
|
279
288
|
wfCount++;
|
|
280
289
|
}
|
|
281
290
|
return { events: Number(ev.changes), workflows: wfCount };
|
|
@@ -320,7 +329,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
320
329
|
r.status AS status, r.created_at AS created_at FROM runs r
|
|
321
330
|
JOIN workflows w ON w.id = r.workflow_id
|
|
322
331
|
WHERE r.session=? AND r.status IN ('running','idle') AND w.status='running'
|
|
323
|
-
ORDER BY r.created_at DESC LIMIT 1`
|
|
332
|
+
ORDER BY r.created_at DESC, r.rowid DESC LIMIT 1`
|
|
324
333
|
).get(session);
|
|
325
334
|
if (!r) return null;
|
|
326
335
|
return {
|
|
@@ -419,12 +428,16 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
419
428
|
`INSERT INTO workflow_steps (id, workflow_id, seq, kind, title, status, input, updated_at)
|
|
420
429
|
VALUES (?, ?, ?, ?, ?, 'pending', ?, ?)`
|
|
421
430
|
);
|
|
422
|
-
const
|
|
423
|
-
|
|
431
|
+
const park = db.prepare(
|
|
432
|
+
`UPDATE workflow_steps SET seq = seq + ${SEQ_PARK_BASE} WHERE workflow_id = ? AND seq >= ?`
|
|
433
|
+
);
|
|
434
|
+
const restore = db.prepare(
|
|
435
|
+
`UPDATE workflow_steps SET seq = seq - ${SEQ_PARK_BASE} + ? WHERE workflow_id = ? AND seq >= ${SEQ_PARK_BASE}`
|
|
424
436
|
);
|
|
425
437
|
db.exec("BEGIN");
|
|
426
438
|
try {
|
|
427
|
-
|
|
439
|
+
park.run(workflowId, afterSeq + 1);
|
|
440
|
+
restore.run(steps.length, workflowId);
|
|
428
441
|
steps.forEach((st, i) => {
|
|
429
442
|
insert.run(
|
|
430
443
|
ids.newId("st"),
|
|
@@ -474,7 +487,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
474
487
|
},
|
|
475
488
|
pendingRelays() {
|
|
476
489
|
return db.prepare(`SELECT id, workflow_id, step_id, session, text, marker, created_at, attempts,
|
|
477
|
-
COALESCE(last_error,'') AS last_error FROM relays WHERE delivered_at IS NULL ORDER BY created_at`).all().map((r) => ({
|
|
490
|
+
COALESCE(last_error,'') AS last_error FROM relays WHERE delivered_at IS NULL ORDER BY created_at, rowid`).all().map((r) => ({
|
|
478
491
|
id: str(r["id"]),
|
|
479
492
|
workflowId: str(r["workflow_id"]),
|
|
480
493
|
stepId: str(r["step_id"]),
|
|
@@ -706,7 +719,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
706
719
|
const placeholders = statuses.map(() => "?").join(",");
|
|
707
720
|
const r = db.prepare(
|
|
708
721
|
`SELECT id, run_id, content, status, created_at FROM prompts
|
|
709
|
-
WHERE run_id=? AND status IN (${placeholders}) ORDER BY created_at DESC LIMIT 1`
|
|
722
|
+
WHERE run_id=? AND status IN (${placeholders}) ORDER BY created_at DESC, rowid DESC LIMIT 1`
|
|
710
723
|
).get(run.id, ...statuses);
|
|
711
724
|
if (!r) return null;
|
|
712
725
|
return {
|
|
@@ -1201,27 +1214,34 @@ var ingestFile = (db, claudeId, transcriptPath) => {
|
|
|
1201
1214
|
if (lastNL < 0) return 0;
|
|
1202
1215
|
const complete = text.slice(0, lastNL + 1);
|
|
1203
1216
|
let count = 0;
|
|
1204
|
-
for (const line of complete.split("\n")) {
|
|
1205
|
-
if (line === "") continue;
|
|
1206
|
-
const rec = parseAssistantLine(line);
|
|
1207
|
-
if (!rec) continue;
|
|
1208
|
-
const sess = tmuxOf(db, claudeId);
|
|
1209
|
-
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1210
|
-
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1211
|
-
`turn=${String(rec.callContext).slice(0, 40)}`,
|
|
1212
|
-
rec.model,
|
|
1213
|
-
rec.inputTokens,
|
|
1214
|
-
rec.outputTokens,
|
|
1215
|
-
rec.cacheRead,
|
|
1216
|
-
rec.timestamp,
|
|
1217
|
-
sess
|
|
1218
|
-
);
|
|
1219
|
-
count++;
|
|
1220
|
-
}
|
|
1221
1217
|
const newOffset = offset + Buffer.byteLength(complete, "utf8");
|
|
1222
|
-
db.
|
|
1223
|
-
|
|
1224
|
-
|
|
1218
|
+
db.exec("BEGIN");
|
|
1219
|
+
try {
|
|
1220
|
+
for (const line of complete.split("\n")) {
|
|
1221
|
+
if (line === "") continue;
|
|
1222
|
+
const rec = parseAssistantLine(line);
|
|
1223
|
+
if (!rec) continue;
|
|
1224
|
+
const sess = tmuxOf(db, claudeId);
|
|
1225
|
+
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1226
|
+
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1227
|
+
`turn=${String(rec.callContext).slice(0, 40)}`,
|
|
1228
|
+
rec.model,
|
|
1229
|
+
rec.inputTokens,
|
|
1230
|
+
rec.outputTokens,
|
|
1231
|
+
rec.cacheRead,
|
|
1232
|
+
rec.timestamp,
|
|
1233
|
+
sess
|
|
1234
|
+
);
|
|
1235
|
+
count++;
|
|
1236
|
+
}
|
|
1237
|
+
db.prepare(`INSERT INTO claude_ingest_offset (claude_session_id, byte_offset, last_ingested_at)
|
|
1238
|
+
VALUES (?, ?, ?)
|
|
1239
|
+
ON CONFLICT(claude_session_id) DO UPDATE SET byte_offset=excluded.byte_offset, last_ingested_at=excluded.last_ingested_at`).run(claudeId, newOffset, (/* @__PURE__ */ new Date()).toISOString());
|
|
1240
|
+
db.exec("COMMIT");
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
db.exec("ROLLBACK");
|
|
1243
|
+
throw e;
|
|
1244
|
+
}
|
|
1225
1245
|
return count;
|
|
1226
1246
|
} finally {
|
|
1227
1247
|
closeSync(fd);
|
package/dist/juli-ledger.js
CHANGED
|
@@ -186,6 +186,7 @@ var dedupPattern = (key) => {
|
|
|
186
186
|
const jsonValue = JSON.stringify(key).slice(1, -1);
|
|
187
187
|
return `%"dedup_key":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
188
188
|
};
|
|
189
|
+
var SEQ_PARK_BASE = 1e9;
|
|
189
190
|
var SEND_STEP_LIKE_CUTOFF = "2026-09-19";
|
|
190
191
|
var rowToWorkflow = (r) => ({
|
|
191
192
|
id: str(r["id"]),
|
|
@@ -225,15 +226,23 @@ var rowToEvent = (r) => ({
|
|
|
225
226
|
});
|
|
226
227
|
var pruneImpl = (db, retentionDays, nowIso) => {
|
|
227
228
|
if (retentionDays <= 0) return { events: 0, workflows: 0 };
|
|
228
|
-
const cutoff = new Date(new Date(nowIso).getTime() - retentionDays * 864e5)
|
|
229
|
+
const cutoff = rfc3339(new Date(new Date(nowIso).getTime() - retentionDays * 864e5));
|
|
229
230
|
const ev = db.prepare("DELETE FROM events WHERE ts < ?").run(cutoff);
|
|
230
231
|
const wfRows = db.prepare(
|
|
231
232
|
`SELECT id FROM workflows WHERE status IN (${sqlInList(PRUNE_WORKFLOW_STATUSES)}) AND updated_at < ?`
|
|
232
233
|
).all(cutoff);
|
|
233
234
|
let wfCount = 0;
|
|
234
235
|
for (const r of wfRows) {
|
|
235
|
-
db.
|
|
236
|
-
|
|
236
|
+
db.exec("BEGIN");
|
|
237
|
+
try {
|
|
238
|
+
db.prepare("DELETE FROM workflow_steps WHERE workflow_id=?").run(r.id);
|
|
239
|
+
db.prepare("DELETE FROM relays WHERE workflow_id=?").run(r.id);
|
|
240
|
+
db.prepare("DELETE FROM workflows WHERE id=?").run(r.id);
|
|
241
|
+
db.exec("COMMIT");
|
|
242
|
+
} catch (e) {
|
|
243
|
+
db.exec("ROLLBACK");
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
237
246
|
wfCount++;
|
|
238
247
|
}
|
|
239
248
|
return { events: Number(ev.changes), workflows: wfCount };
|
|
@@ -278,7 +287,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
278
287
|
r.status AS status, r.created_at AS created_at FROM runs r
|
|
279
288
|
JOIN workflows w ON w.id = r.workflow_id
|
|
280
289
|
WHERE r.session=? AND r.status IN ('running','idle') AND w.status='running'
|
|
281
|
-
ORDER BY r.created_at DESC LIMIT 1`
|
|
290
|
+
ORDER BY r.created_at DESC, r.rowid DESC LIMIT 1`
|
|
282
291
|
).get(session);
|
|
283
292
|
if (!r) return null;
|
|
284
293
|
return {
|
|
@@ -377,12 +386,16 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
377
386
|
`INSERT INTO workflow_steps (id, workflow_id, seq, kind, title, status, input, updated_at)
|
|
378
387
|
VALUES (?, ?, ?, ?, ?, 'pending', ?, ?)`
|
|
379
388
|
);
|
|
380
|
-
const
|
|
381
|
-
|
|
389
|
+
const park = db.prepare(
|
|
390
|
+
`UPDATE workflow_steps SET seq = seq + ${SEQ_PARK_BASE} WHERE workflow_id = ? AND seq >= ?`
|
|
391
|
+
);
|
|
392
|
+
const restore = db.prepare(
|
|
393
|
+
`UPDATE workflow_steps SET seq = seq - ${SEQ_PARK_BASE} + ? WHERE workflow_id = ? AND seq >= ${SEQ_PARK_BASE}`
|
|
382
394
|
);
|
|
383
395
|
db.exec("BEGIN");
|
|
384
396
|
try {
|
|
385
|
-
|
|
397
|
+
park.run(workflowId, afterSeq + 1);
|
|
398
|
+
restore.run(steps.length, workflowId);
|
|
386
399
|
steps.forEach((st, i) => {
|
|
387
400
|
insert.run(
|
|
388
401
|
ids.newId("st"),
|
|
@@ -432,7 +445,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
432
445
|
},
|
|
433
446
|
pendingRelays() {
|
|
434
447
|
return db.prepare(`SELECT id, workflow_id, step_id, session, text, marker, created_at, attempts,
|
|
435
|
-
COALESCE(last_error,'') AS last_error FROM relays WHERE delivered_at IS NULL ORDER BY created_at`).all().map((r) => ({
|
|
448
|
+
COALESCE(last_error,'') AS last_error FROM relays WHERE delivered_at IS NULL ORDER BY created_at, rowid`).all().map((r) => ({
|
|
436
449
|
id: str(r["id"]),
|
|
437
450
|
workflowId: str(r["workflow_id"]),
|
|
438
451
|
stepId: str(r["step_id"]),
|
|
@@ -664,7 +677,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
664
677
|
const placeholders = statuses.map(() => "?").join(",");
|
|
665
678
|
const r = db.prepare(
|
|
666
679
|
`SELECT id, run_id, content, status, created_at FROM prompts
|
|
667
|
-
WHERE run_id=? AND status IN (${placeholders}) ORDER BY created_at DESC LIMIT 1`
|
|
680
|
+
WHERE run_id=? AND status IN (${placeholders}) ORDER BY created_at DESC, rowid DESC LIMIT 1`
|
|
668
681
|
).get(run.id, ...statuses);
|
|
669
682
|
if (!r) return null;
|
|
670
683
|
return {
|
|
@@ -1159,27 +1172,34 @@ var ingestFile = (db, claudeId, transcriptPath) => {
|
|
|
1159
1172
|
if (lastNL < 0) return 0;
|
|
1160
1173
|
const complete = text.slice(0, lastNL + 1);
|
|
1161
1174
|
let count = 0;
|
|
1162
|
-
for (const line of complete.split("\n")) {
|
|
1163
|
-
if (line === "") continue;
|
|
1164
|
-
const rec = parseAssistantLine(line);
|
|
1165
|
-
if (!rec) continue;
|
|
1166
|
-
const sess = tmuxOf(db, claudeId);
|
|
1167
|
-
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1168
|
-
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1169
|
-
`turn=${String(rec.callContext).slice(0, 40)}`,
|
|
1170
|
-
rec.model,
|
|
1171
|
-
rec.inputTokens,
|
|
1172
|
-
rec.outputTokens,
|
|
1173
|
-
rec.cacheRead,
|
|
1174
|
-
rec.timestamp,
|
|
1175
|
-
sess
|
|
1176
|
-
);
|
|
1177
|
-
count++;
|
|
1178
|
-
}
|
|
1179
1175
|
const newOffset = offset + Buffer.byteLength(complete, "utf8");
|
|
1180
|
-
db.
|
|
1181
|
-
|
|
1182
|
-
|
|
1176
|
+
db.exec("BEGIN");
|
|
1177
|
+
try {
|
|
1178
|
+
for (const line of complete.split("\n")) {
|
|
1179
|
+
if (line === "") continue;
|
|
1180
|
+
const rec = parseAssistantLine(line);
|
|
1181
|
+
if (!rec) continue;
|
|
1182
|
+
const sess = tmuxOf(db, claudeId);
|
|
1183
|
+
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1184
|
+
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1185
|
+
`turn=${String(rec.callContext).slice(0, 40)}`,
|
|
1186
|
+
rec.model,
|
|
1187
|
+
rec.inputTokens,
|
|
1188
|
+
rec.outputTokens,
|
|
1189
|
+
rec.cacheRead,
|
|
1190
|
+
rec.timestamp,
|
|
1191
|
+
sess
|
|
1192
|
+
);
|
|
1193
|
+
count++;
|
|
1194
|
+
}
|
|
1195
|
+
db.prepare(`INSERT INTO claude_ingest_offset (claude_session_id, byte_offset, last_ingested_at)
|
|
1196
|
+
VALUES (?, ?, ?)
|
|
1197
|
+
ON CONFLICT(claude_session_id) DO UPDATE SET byte_offset=excluded.byte_offset, last_ingested_at=excluded.last_ingested_at`).run(claudeId, newOffset, (/* @__PURE__ */ new Date()).toISOString());
|
|
1198
|
+
db.exec("COMMIT");
|
|
1199
|
+
} catch (e) {
|
|
1200
|
+
db.exec("ROLLBACK");
|
|
1201
|
+
throw e;
|
|
1202
|
+
}
|
|
1183
1203
|
return count;
|
|
1184
1204
|
} finally {
|
|
1185
1205
|
closeSync(fd);
|
|
@@ -1220,7 +1240,7 @@ function demo() {
|
|
|
1220
1240
|
const w1 = store.createWorkflow({ kind: "case", title: "Client inquiry: website redesign", meta: { from: "client@acme.com", message_id: "demo-001" } });
|
|
1221
1241
|
store.createStep({ workflowId: w1.id, seq: 1, kind: "agent", title: "Understand the request" });
|
|
1222
1242
|
store.createStep({ workflowId: w1.id, seq: 2, kind: "agent", title: "Draft a proposal" });
|
|
1223
|
-
store.setWorkflowStatus(w1.id, "
|
|
1243
|
+
store.setWorkflowStatus(w1.id, "running");
|
|
1224
1244
|
const w2 = store.createWorkflow({ kind: "case", title: "Monthly report automation", meta: { from: "ops@acme.com" } });
|
|
1225
1245
|
store.setWorkflowStatus(w2.id, "completed");
|
|
1226
1246
|
const cf = { ...emptyCaseFile(/* @__PURE__ */ new Date()), original: { from: "client@acme.com", subject: "Website redesign" } };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juli-os/ledger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one zero-dependency SQLite layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|