@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 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).toISOString();
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.prepare("DELETE FROM workflow_steps WHERE workflow_id=?").run(r.id);
278
- db.prepare("DELETE FROM workflows WHERE id=?").run(r.id);
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 shift = db.prepare(
423
- "UPDATE workflow_steps SET seq = seq + ? WHERE workflow_id = ? AND seq >= ?"
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
- shift.run(steps.length, workflowId, afterSeq + 1);
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.prepare(`INSERT INTO claude_ingest_offset (claude_session_id, byte_offset, last_ingested_at)
1223
- VALUES (?, ?, ?)
1224
- 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());
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);
@@ -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).toISOString();
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.prepare("DELETE FROM workflow_steps WHERE workflow_id=?").run(r.id);
236
- db.prepare("DELETE FROM workflows WHERE id=?").run(r.id);
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 shift = db.prepare(
381
- "UPDATE workflow_steps SET seq = seq + ? WHERE workflow_id = ? AND seq >= ?"
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
- shift.run(steps.length, workflowId, afterSeq + 1);
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.prepare(`INSERT INTO claude_ingest_offset (claude_session_id, byte_offset, last_ingested_at)
1181
- VALUES (?, ?, ?)
1182
- 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());
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, "awaiting_approval");
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.4",
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",