@hasna/mementos 0.14.74 → 0.14.78

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 (68) hide show
  1. package/bun.lock +93 -0
  2. package/dist/cli/commands/agent.d.ts.map +1 -1
  3. package/dist/cli/commands/info-context.d.ts.map +1 -1
  4. package/dist/cli/commands/info-stale.d.ts.map +1 -1
  5. package/dist/cli/commands/io-export.d.ts.map +1 -1
  6. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
  8. package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
  9. package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
  10. package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
  11. package/dist/cli/commands/project.d.ts.map +1 -1
  12. package/dist/cli/commands/system-watch.d.ts.map +1 -1
  13. package/dist/cli/helpers.d.ts +38 -0
  14. package/dist/cli/helpers.d.ts.map +1 -1
  15. package/dist/cli/index.js +2077 -299
  16. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
  17. package/dist/db/agents.d.ts +6 -0
  18. package/dist/db/agents.d.ts.map +1 -1
  19. package/dist/db/audit.d.ts +14 -0
  20. package/dist/db/audit.d.ts.map +1 -1
  21. package/dist/db/memory-project-link.d.ts +13 -0
  22. package/dist/db/memory-project-link.d.ts.map +1 -0
  23. package/dist/db/migrations.d.ts.map +1 -1
  24. package/dist/db/pg-migrations.d.ts +0 -6
  25. package/dist/db/pg-migrations.d.ts.map +1 -1
  26. package/dist/db/projects.d.ts +22 -1
  27. package/dist/db/projects.d.ts.map +1 -1
  28. package/dist/index.d.ts +5 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2909 -173
  31. package/dist/lib/package-version.d.ts +3 -0
  32. package/dist/lib/package-version.d.ts.map +1 -0
  33. package/dist/mcp/index.js +524 -10
  34. package/dist/memory-project-link/index.d.ts +2 -0
  35. package/dist/memory-project-link/index.d.ts.map +1 -0
  36. package/dist/memory-project-link/schema.d.ts +5 -0
  37. package/dist/memory-project-link/schema.d.ts.map +1 -0
  38. package/dist/project-registration/authority.d.ts +39 -0
  39. package/dist/project-registration/authority.d.ts.map +1 -0
  40. package/dist/project-registration/http.d.ts +29 -0
  41. package/dist/project-registration/http.d.ts.map +1 -0
  42. package/dist/project-registration/index.d.ts +8 -0
  43. package/dist/project-registration/index.d.ts.map +1 -0
  44. package/dist/project-registration/project-references.d.ts +71 -0
  45. package/dist/project-registration/project-references.d.ts.map +1 -0
  46. package/dist/project-registration/schema.d.ts +5 -0
  47. package/dist/project-registration/schema.d.ts.map +1 -0
  48. package/dist/project-registration/types.d.ts +168 -0
  49. package/dist/project-registration/types.d.ts.map +1 -0
  50. package/dist/project-registration.d.ts +2 -0
  51. package/dist/project-registration.d.ts.map +1 -0
  52. package/dist/project-registration.js +1496 -0
  53. package/dist/sdk/index.d.ts +127 -0
  54. package/dist/sdk/index.d.ts.map +1 -1
  55. package/dist/sdk/index.js +74 -0
  56. package/dist/server/index.d.ts +1 -0
  57. package/dist/server/index.d.ts.map +1 -1
  58. package/dist/server/index.js +2667 -149
  59. package/dist/server/routes/memories.d.ts +1 -0
  60. package/dist/server/routes/memories.d.ts.map +1 -1
  61. package/dist/server/routes/memory-project-link.d.ts +2 -0
  62. package/dist/server/routes/memory-project-link.d.ts.map +1 -0
  63. package/dist/server/routes/project-registration.d.ts +2 -0
  64. package/dist/server/routes/project-registration.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +119 -0
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/package.json +9 -3
  68. package/dashboard/bun.lock +0 -519
package/dist/cli/index.js CHANGED
@@ -3210,6 +3210,395 @@ var init_api_mode = __esm(() => {
3210
3210
  };
3211
3211
  });
3212
3212
 
3213
+ // src/project-registration/schema.ts
3214
+ function sqliteMementosProjectRegistrationSchemaSql() {
3215
+ return `
3216
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
3217
+ receipt_id TEXT PRIMARY KEY,
3218
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3219
+ route TEXT NOT NULL,
3220
+ package_version TEXT NOT NULL,
3221
+ authority_id TEXT NOT NULL,
3222
+ tenant_id TEXT NOT NULL,
3223
+ corpus_id TEXT NOT NULL,
3224
+ operation_id TEXT NOT NULL,
3225
+ step_id TEXT NOT NULL,
3226
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
3227
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
3228
+ target_selector TEXT NOT NULL,
3229
+ idempotency_key TEXT NOT NULL,
3230
+ request_digest TEXT NOT NULL,
3231
+ precondition_digest TEXT NOT NULL,
3232
+ normalized_call_digest TEXT NOT NULL,
3233
+ outcome TEXT NOT NULL CHECK(outcome IN (
3234
+ 'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
3235
+ )),
3236
+ reason TEXT,
3237
+ target_id TEXT,
3238
+ result_revision TEXT,
3239
+ result_digest TEXT,
3240
+ duplicate_of_receipt_id TEXT,
3241
+ accepted_receipt_id TEXT,
3242
+ created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
3243
+ created_at TEXT NOT NULL
3244
+ );
3245
+
3246
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
3247
+ ON mementos_project_registration_receipts (
3248
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
3249
+ resource_kind, direction, idempotency_key, target_selector
3250
+ );
3251
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
3252
+ ON mementos_project_registration_receipts (
3253
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
3254
+ resource_kind, direction
3255
+ )
3256
+ WHERE outcome = 'accepted';
3257
+
3258
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
3259
+ authority_id TEXT NOT NULL,
3260
+ tenant_id TEXT NOT NULL,
3261
+ corpus_id TEXT NOT NULL,
3262
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
3263
+ target_selector TEXT NOT NULL,
3264
+ operation_id TEXT NOT NULL,
3265
+ step_id TEXT NOT NULL,
3266
+ direction TEXT NOT NULL CHECK(direction = 'forward'),
3267
+ idempotency_key TEXT NOT NULL,
3268
+ request_digest TEXT NOT NULL,
3269
+ precondition_digest TEXT NOT NULL,
3270
+ normalized_call_digest TEXT NOT NULL,
3271
+ state TEXT NOT NULL CHECK(state IN (
3272
+ 'pending', 'accepted', 'terminal_nonacceptance', 'removed'
3273
+ )),
3274
+ target_id TEXT,
3275
+ accepted_receipt_id TEXT,
3276
+ result_revision TEXT,
3277
+ result_digest TEXT,
3278
+ removed_receipt_id TEXT,
3279
+ created_at TEXT NOT NULL,
3280
+ updated_at TEXT NOT NULL,
3281
+ PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector),
3282
+ UNIQUE(accepted_receipt_id)
3283
+ );
3284
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
3285
+ ON mementos_project_registration_bindings(
3286
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
3287
+ )
3288
+ WHERE target_id IS NOT NULL;
3289
+
3290
+ CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_update
3291
+ BEFORE UPDATE ON mementos_project_registration_receipts
3292
+ BEGIN
3293
+ SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
3294
+ END;
3295
+
3296
+ CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_delete
3297
+ BEFORE DELETE ON mementos_project_registration_receipts
3298
+ BEGIN
3299
+ SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
3300
+ END;
3301
+ `;
3302
+ }
3303
+ function postgresMementosProjectRegistrationSchemaSql() {
3304
+ return `
3305
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
3306
+ receipt_id TEXT PRIMARY KEY,
3307
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3308
+ route TEXT NOT NULL,
3309
+ package_version TEXT NOT NULL,
3310
+ authority_id TEXT NOT NULL,
3311
+ tenant_id TEXT NOT NULL,
3312
+ corpus_id TEXT NOT NULL,
3313
+ operation_id TEXT NOT NULL,
3314
+ step_id TEXT NOT NULL,
3315
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
3316
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
3317
+ target_selector TEXT NOT NULL,
3318
+ idempotency_key TEXT NOT NULL,
3319
+ request_digest TEXT NOT NULL,
3320
+ precondition_digest TEXT NOT NULL,
3321
+ normalized_call_digest TEXT NOT NULL,
3322
+ outcome TEXT NOT NULL CHECK(outcome IN (
3323
+ 'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
3324
+ )),
3325
+ reason TEXT,
3326
+ target_id TEXT,
3327
+ result_revision TEXT,
3328
+ result_digest TEXT,
3329
+ duplicate_of_receipt_id TEXT,
3330
+ accepted_receipt_id TEXT,
3331
+ created_by_operation BOOLEAN NOT NULL,
3332
+ created_at TIMESTAMPTZ NOT NULL
3333
+ );
3334
+
3335
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
3336
+ ON mementos_project_registration_receipts (
3337
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
3338
+ resource_kind, direction, idempotency_key, target_selector
3339
+ );
3340
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
3341
+ ON mementos_project_registration_receipts (
3342
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
3343
+ resource_kind, direction
3344
+ )
3345
+ WHERE outcome = 'accepted';
3346
+
3347
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
3348
+ authority_id TEXT NOT NULL,
3349
+ tenant_id TEXT NOT NULL,
3350
+ corpus_id TEXT NOT NULL,
3351
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
3352
+ target_selector TEXT NOT NULL,
3353
+ operation_id TEXT NOT NULL,
3354
+ step_id TEXT NOT NULL,
3355
+ direction TEXT NOT NULL CHECK(direction = 'forward'),
3356
+ idempotency_key TEXT NOT NULL,
3357
+ request_digest TEXT NOT NULL,
3358
+ precondition_digest TEXT NOT NULL,
3359
+ normalized_call_digest TEXT NOT NULL,
3360
+ state TEXT NOT NULL CHECK(state IN (
3361
+ 'pending', 'accepted', 'terminal_nonacceptance', 'removed'
3362
+ )),
3363
+ target_id TEXT,
3364
+ accepted_receipt_id TEXT UNIQUE,
3365
+ result_revision TEXT,
3366
+ result_digest TEXT,
3367
+ removed_receipt_id TEXT,
3368
+ created_at TIMESTAMPTZ NOT NULL,
3369
+ updated_at TIMESTAMPTZ NOT NULL,
3370
+ PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector)
3371
+ );
3372
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
3373
+ ON mementos_project_registration_bindings(
3374
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
3375
+ )
3376
+ WHERE target_id IS NOT NULL;
3377
+
3378
+ CREATE OR REPLACE FUNCTION mementos_project_registration_receipts_immutable()
3379
+ RETURNS trigger
3380
+ LANGUAGE plpgsql
3381
+ AS $$
3382
+ BEGIN
3383
+ RAISE EXCEPTION 'mementos project registration receipts are immutable';
3384
+ END;
3385
+ $$;
3386
+ DROP TRIGGER IF EXISTS mementos_project_registration_receipts_immutable
3387
+ ON mementos_project_registration_receipts;
3388
+ CREATE TRIGGER mementos_project_registration_receipts_immutable
3389
+ BEFORE UPDATE OR DELETE ON mementos_project_registration_receipts
3390
+ FOR EACH ROW EXECUTE FUNCTION mementos_project_registration_receipts_immutable();
3391
+ `;
3392
+ }
3393
+ function sqliteMementosProjectGuardedUpdateSchemaSql() {
3394
+ return `
3395
+ CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
3396
+ receipt_id TEXT PRIMARY KEY,
3397
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3398
+ route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
3399
+ package_version TEXT NOT NULL,
3400
+ authority_id TEXT NOT NULL,
3401
+ tenant_id TEXT NOT NULL,
3402
+ corpus_id TEXT NOT NULL,
3403
+ operation_id TEXT NOT NULL,
3404
+ step_id TEXT NOT NULL,
3405
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
3406
+ idempotency_key TEXT NOT NULL,
3407
+ request_digest TEXT NOT NULL,
3408
+ outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
3409
+ target_id TEXT NOT NULL,
3410
+ expected_revision TEXT NOT NULL,
3411
+ result_revision TEXT NOT NULL,
3412
+ result_digest TEXT NOT NULL,
3413
+ accepted_receipt_id TEXT,
3414
+ before_project_json TEXT NOT NULL,
3415
+ after_project_json TEXT NOT NULL,
3416
+ created_at TEXT NOT NULL,
3417
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
3418
+ );
3419
+
3420
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
3421
+ ON mementos_project_update_receipts(
3422
+ authority_id, tenant_id, corpus_id, target_id, created_at
3423
+ );
3424
+
3425
+ CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_update
3426
+ BEFORE UPDATE ON mementos_project_update_receipts
3427
+ BEGIN
3428
+ SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
3429
+ END;
3430
+
3431
+ CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_delete
3432
+ BEFORE DELETE ON mementos_project_update_receipts
3433
+ BEGIN
3434
+ SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
3435
+ END;
3436
+ `;
3437
+ }
3438
+ function postgresMementosProjectGuardedUpdateSchemaSql() {
3439
+ return `
3440
+ CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
3441
+ receipt_id TEXT PRIMARY KEY,
3442
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3443
+ route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
3444
+ package_version TEXT NOT NULL,
3445
+ authority_id TEXT NOT NULL,
3446
+ tenant_id TEXT NOT NULL,
3447
+ corpus_id TEXT NOT NULL,
3448
+ operation_id TEXT NOT NULL,
3449
+ step_id TEXT NOT NULL,
3450
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
3451
+ idempotency_key TEXT NOT NULL,
3452
+ request_digest TEXT NOT NULL,
3453
+ outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
3454
+ target_id TEXT NOT NULL,
3455
+ expected_revision TEXT NOT NULL,
3456
+ result_revision TEXT NOT NULL,
3457
+ result_digest TEXT NOT NULL,
3458
+ accepted_receipt_id TEXT,
3459
+ before_project_json JSONB NOT NULL,
3460
+ after_project_json JSONB NOT NULL,
3461
+ created_at TIMESTAMPTZ NOT NULL,
3462
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
3463
+ );
3464
+
3465
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
3466
+ ON mementos_project_update_receipts(
3467
+ authority_id, tenant_id, corpus_id, target_id, created_at
3468
+ );
3469
+
3470
+ CREATE OR REPLACE FUNCTION mementos_project_update_receipts_immutable()
3471
+ RETURNS trigger
3472
+ LANGUAGE plpgsql
3473
+ AS $$
3474
+ BEGIN
3475
+ RAISE EXCEPTION 'mementos project update receipts are immutable';
3476
+ END;
3477
+ $$;
3478
+ DROP TRIGGER IF EXISTS mementos_project_update_receipts_immutable
3479
+ ON mementos_project_update_receipts;
3480
+ CREATE TRIGGER mementos_project_update_receipts_immutable
3481
+ BEFORE UPDATE OR DELETE ON mementos_project_update_receipts
3482
+ FOR EACH ROW EXECUTE FUNCTION mementos_project_update_receipts_immutable();
3483
+ `;
3484
+ }
3485
+
3486
+ // src/memory-project-link/schema.ts
3487
+ function sqliteMementosMemoryProjectLinkSchemaSql() {
3488
+ return `
3489
+ CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
3490
+ receipt_id TEXT PRIMARY KEY,
3491
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3492
+ route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
3493
+ package_version TEXT NOT NULL,
3494
+ authority_id TEXT NOT NULL,
3495
+ tenant_id TEXT NOT NULL,
3496
+ corpus_id TEXT NOT NULL,
3497
+ operation_id TEXT NOT NULL,
3498
+ step_id TEXT NOT NULL,
3499
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
3500
+ idempotency_key TEXT NOT NULL,
3501
+ request_digest TEXT NOT NULL,
3502
+ outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
3503
+ target_memory_id TEXT NOT NULL,
3504
+ requested_project_id TEXT NOT NULL,
3505
+ expected_memory_version INTEGER NOT NULL,
3506
+ expected_memory_revision TEXT NOT NULL,
3507
+ expected_project_revision TEXT,
3508
+ result_memory_version INTEGER NOT NULL,
3509
+ result_memory_revision TEXT NOT NULL,
3510
+ result_memory_digest TEXT NOT NULL,
3511
+ result_project_revision TEXT,
3512
+ result_project_digest TEXT,
3513
+ accepted_receipt_id TEXT,
3514
+ before_link_json TEXT NOT NULL,
3515
+ after_link_json TEXT NOT NULL,
3516
+ before_project_revision TEXT,
3517
+ before_project_digest TEXT,
3518
+ after_project_revision TEXT,
3519
+ after_project_digest TEXT,
3520
+ created_at TEXT NOT NULL,
3521
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
3522
+ );
3523
+
3524
+ CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
3525
+ ON mementos_memory_project_link_receipts(
3526
+ authority_id, tenant_id, corpus_id, target_memory_id, created_at
3527
+ );
3528
+
3529
+ CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_update
3530
+ BEFORE UPDATE ON mementos_memory_project_link_receipts
3531
+ BEGIN
3532
+ SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
3533
+ END;
3534
+
3535
+ CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_delete
3536
+ BEFORE DELETE ON mementos_memory_project_link_receipts
3537
+ BEGIN
3538
+ SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
3539
+ END;
3540
+ `;
3541
+ }
3542
+ function postgresMementosMemoryProjectLinkSchemaSql() {
3543
+ return `
3544
+ CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
3545
+ receipt_id TEXT PRIMARY KEY,
3546
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
3547
+ route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
3548
+ package_version TEXT NOT NULL,
3549
+ authority_id TEXT NOT NULL,
3550
+ tenant_id TEXT NOT NULL,
3551
+ corpus_id TEXT NOT NULL,
3552
+ operation_id TEXT NOT NULL,
3553
+ step_id TEXT NOT NULL,
3554
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
3555
+ idempotency_key TEXT NOT NULL,
3556
+ request_digest TEXT NOT NULL,
3557
+ outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
3558
+ target_memory_id TEXT NOT NULL,
3559
+ requested_project_id TEXT NOT NULL,
3560
+ expected_memory_version INTEGER NOT NULL,
3561
+ expected_memory_revision TEXT NOT NULL,
3562
+ expected_project_revision TEXT,
3563
+ result_memory_version INTEGER NOT NULL,
3564
+ result_memory_revision TEXT NOT NULL,
3565
+ result_memory_digest TEXT NOT NULL,
3566
+ result_project_revision TEXT,
3567
+ result_project_digest TEXT,
3568
+ accepted_receipt_id TEXT,
3569
+ before_link_json JSONB NOT NULL,
3570
+ after_link_json JSONB NOT NULL,
3571
+ before_project_revision TEXT,
3572
+ before_project_digest TEXT,
3573
+ after_project_revision TEXT,
3574
+ after_project_digest TEXT,
3575
+ created_at TIMESTAMPTZ NOT NULL,
3576
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
3577
+ );
3578
+
3579
+ CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
3580
+ ON mementos_memory_project_link_receipts(
3581
+ authority_id, tenant_id, corpus_id, target_memory_id, created_at
3582
+ );
3583
+
3584
+ CREATE OR REPLACE FUNCTION mementos_memory_project_link_receipts_immutable()
3585
+ RETURNS trigger
3586
+ LANGUAGE plpgsql
3587
+ AS $$
3588
+ BEGIN
3589
+ RAISE EXCEPTION 'mementos memory project link receipts are immutable';
3590
+ END;
3591
+ $$;
3592
+ DROP TRIGGER IF EXISTS mementos_memory_project_link_receipts_immutable
3593
+ ON mementos_memory_project_link_receipts;
3594
+ CREATE TRIGGER mementos_memory_project_link_receipts_immutable
3595
+ BEFORE UPDATE OR DELETE ON mementos_memory_project_link_receipts
3596
+ FOR EACH ROW EXECUTE FUNCTION mementos_memory_project_link_receipts_immutable();
3597
+ `;
3598
+ }
3599
+ var MEMENTOS_MEMORY_PROJECT_LINK_ROUTE = "mementos.memory-project-link.v1";
3600
+ var init_schema = () => {};
3601
+
3213
3602
  // src/db/migrations.ts
3214
3603
  var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
3215
3604
  CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
@@ -3230,6 +3619,7 @@ BEGIN
3230
3619
  END;
3231
3620
  `, MIGRATIONS;
3232
3621
  var init_migrations = __esm(() => {
3622
+ init_schema();
3233
3623
  MIGRATIONS = [
3234
3624
  `
3235
3625
  CREATE TABLE IF NOT EXISTS projects (
@@ -4130,6 +4520,51 @@ INSERT OR IGNORE INTO _migrations (id) VALUES (35);
4130
4520
  `
4131
4521
  ${MEMORY_VERSION_SNAPSHOT_TRIGGER}
4132
4522
  INSERT OR IGNORE INTO _migrations (id) VALUES (36);
4523
+ `,
4524
+ `
4525
+ DROP TRIGGER IF EXISTS audit_memory_insert;
4526
+ CREATE TRIGGER audit_memory_insert AFTER INSERT ON memories BEGIN
4527
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
4528
+ VALUES (hex(randomblob(4)), new.id, new.key, 'create', new.agent_id, datetime('now'));
4529
+ END;
4530
+
4531
+ DROP TRIGGER IF EXISTS audit_memory_update;
4532
+ CREATE TRIGGER audit_memory_update AFTER UPDATE ON memories BEGIN
4533
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, changes, created_at)
4534
+ VALUES (hex(randomblob(4)), new.id, new.key, 'update', new.agent_id,
4535
+ json_object('version_from', old.version, 'version_to', new.version, 'importance_from', old.importance, 'importance_to', new.importance),
4536
+ datetime('now'));
4537
+ END;
4538
+
4539
+ DROP TRIGGER IF EXISTS audit_memory_delete;
4540
+ CREATE TRIGGER audit_memory_delete AFTER DELETE ON memories BEGIN
4541
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
4542
+ VALUES (hex(randomblob(4)), old.id, old.key, 'delete', old.agent_id, datetime('now'));
4543
+ END;
4544
+
4545
+ UPDATE memory_audit_log SET old_value_hash = NULL
4546
+ WHERE old_value_hash IS NOT NULL
4547
+ AND length(old_value_hash) = 32
4548
+ AND old_value_hash NOT GLOB '*[^0-9A-F]*';
4549
+
4550
+ UPDATE memory_audit_log SET new_value_hash = NULL
4551
+ WHERE new_value_hash IS NOT NULL
4552
+ AND length(new_value_hash) = 32
4553
+ AND new_value_hash NOT GLOB '*[^0-9A-F]*';
4554
+
4555
+ INSERT OR IGNORE INTO _migrations (id) VALUES (37);
4556
+ `,
4557
+ `
4558
+ ${sqliteMementosProjectRegistrationSchemaSql()}
4559
+ INSERT OR IGNORE INTO _migrations (id) VALUES (38);
4560
+ `,
4561
+ `
4562
+ ${sqliteMementosProjectGuardedUpdateSchemaSql()}
4563
+ INSERT OR IGNORE INTO _migrations (id) VALUES (39);
4564
+ `,
4565
+ `
4566
+ ${sqliteMementosMemoryProjectLinkSchemaSql()}
4567
+ INSERT OR IGNORE INTO _migrations (id) VALUES (40);
4133
4568
  `
4134
4569
  ];
4135
4570
  });
@@ -5701,13 +6136,247 @@ var init_memories = __esm(() => {
5701
6136
  init_api_mode();
5702
6137
  });
5703
6138
 
6139
+ // src/db/agents.ts
6140
+ function parseAgentRow(row) {
6141
+ return {
6142
+ id: row["id"],
6143
+ name: row["name"],
6144
+ session_id: row["session_id"] || null,
6145
+ description: row["description"] || null,
6146
+ role: row["role"] || null,
6147
+ metadata: JSON.parse(row["metadata"] || "{}"),
6148
+ active_project_id: row["active_project_id"] || null,
6149
+ created_at: row["created_at"],
6150
+ last_seen_at: row["last_seen_at"]
6151
+ };
6152
+ }
6153
+ function registerAgent(name, sessionId, description, role, projectId, db) {
6154
+ if (!db && isApiMode()) {
6155
+ const { data } = apiJson("POST", "/agents", {
6156
+ name,
6157
+ session_id: sessionId,
6158
+ description,
6159
+ role,
6160
+ project_id: projectId
6161
+ });
6162
+ return data;
6163
+ }
6164
+ const d = db || getDatabase();
6165
+ const timestamp = now();
6166
+ const normalizedName = name.trim().toLowerCase();
6167
+ if (projectId) {
6168
+ const resolvedProjectId = resolvePartialId(d, "projects", projectId);
6169
+ if (!resolvedProjectId) {
6170
+ throw new Error(`Project not found: ${projectId}`);
6171
+ }
6172
+ projectId = resolvedProjectId;
6173
+ }
6174
+ const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
6175
+ if (existing) {
6176
+ const existingId = existing["id"];
6177
+ const existingSessionId = existing["session_id"] || null;
6178
+ const existingLastSeen = existing["last_seen_at"];
6179
+ if (sessionId && existingSessionId && existingSessionId !== sessionId) {
6180
+ const lastSeenMs = new Date(existingLastSeen).getTime();
6181
+ const nowMs = Date.now();
6182
+ if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
6183
+ throw new AgentConflictError({
6184
+ existing_id: existingId,
6185
+ existing_name: normalizedName,
6186
+ last_seen_at: existingLastSeen,
6187
+ session_hint: existingSessionId.slice(0, 8),
6188
+ working_dir: null
6189
+ });
6190
+ }
6191
+ }
6192
+ d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
6193
+ timestamp,
6194
+ sessionId ?? existingSessionId,
6195
+ existingId
6196
+ ]);
6197
+ if (description) {
6198
+ d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
6199
+ }
6200
+ if (role) {
6201
+ d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
6202
+ }
6203
+ if (projectId !== undefined) {
6204
+ d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
6205
+ }
6206
+ return getAgent(existingId, d);
6207
+ }
6208
+ const id = shortUuid();
6209
+ d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
6210
+ return getAgent(id, d);
6211
+ }
6212
+ function getAgent(idOrName, db) {
6213
+ if (!db && isApiMode()) {
6214
+ const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
6215
+ if (status === 404 || !data)
6216
+ return null;
6217
+ return data;
6218
+ }
6219
+ const d = db || getDatabase();
6220
+ let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
6221
+ if (row)
6222
+ return parseAgentRow(row);
6223
+ row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
6224
+ if (row)
6225
+ return parseAgentRow(row);
6226
+ const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
6227
+ if (rows.length === 1)
6228
+ return parseAgentRow(rows[0]);
6229
+ return null;
6230
+ }
6231
+ function normalizedAgentListFilter(filter) {
6232
+ const limit = Number.isFinite(filter.limit) ? Math.max(0, Math.floor(filter.limit)) : undefined;
6233
+ const offset = Number.isFinite(filter.offset) ? Math.max(0, Math.floor(filter.offset)) : undefined;
6234
+ return { limit, offset };
6235
+ }
6236
+ function isDatabaseAdapter(value) {
6237
+ return typeof value === "object" && value !== null && "query" in value && typeof value.query === "function";
6238
+ }
6239
+ function paginatedAgentQuery(baseSql, baseParams, filter) {
6240
+ const { limit, offset } = normalizedAgentListFilter(filter);
6241
+ let sql = `${baseSql} ORDER BY created_at ASC, id ASC`;
6242
+ const params = [...baseParams];
6243
+ if (limit !== undefined) {
6244
+ sql += " LIMIT ?";
6245
+ params.push(limit);
6246
+ } else if ((offset ?? 0) > 0) {
6247
+ sql += " LIMIT ?";
6248
+ params.push(UNBOUNDED_AGENT_LIST_LIMIT);
6249
+ }
6250
+ if ((offset ?? 0) > 0) {
6251
+ sql += " OFFSET ?";
6252
+ params.push(offset);
6253
+ }
6254
+ return { sql, params };
6255
+ }
6256
+ function listAgents(filterOrDb = {}, db) {
6257
+ const explicitDb = isDatabaseAdapter(filterOrDb) ? filterOrDb : db;
6258
+ const filter = isDatabaseAdapter(filterOrDb) ? {} : filterOrDb;
6259
+ const normalized = normalizedAgentListFilter(filter);
6260
+ if (!explicitDb && isApiMode()) {
6261
+ const q = toQuery({
6262
+ limit: normalized.limit,
6263
+ offset: normalized.offset
6264
+ });
6265
+ const { data } = apiJson("GET", `/agents${q}`);
6266
+ return data?.agents ?? [];
6267
+ }
6268
+ const d = explicitDb || getDatabase();
6269
+ const { sql, params } = paginatedAgentQuery("SELECT * FROM agents", [], normalized);
6270
+ const rows = d.query(sql).all(...params);
6271
+ return rows.map(parseAgentRow);
6272
+ }
6273
+ function touchAgent(idOrName, db) {
6274
+ if (!db && isApiMode()) {
6275
+ const agent2 = getAgent(idOrName);
6276
+ if (!agent2)
6277
+ return;
6278
+ apiJson("PATCH", `/agents/${encodeURIComponent(agent2.id)}`, {});
6279
+ return;
6280
+ }
6281
+ const d = db || getDatabase();
6282
+ const agent = getAgent(idOrName, d);
6283
+ if (!agent)
6284
+ return;
6285
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
6286
+ }
6287
+ function updateAgent(id, updates, db) {
6288
+ if (!db && isApiMode()) {
6289
+ const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
6290
+ if (status === 404 || !data)
6291
+ return null;
6292
+ return data;
6293
+ }
6294
+ const d = db || getDatabase();
6295
+ const agent = getAgent(id, d);
6296
+ if (!agent)
6297
+ return null;
6298
+ const timestamp = now();
6299
+ if (updates.name) {
6300
+ const normalizedNewName = updates.name.trim().toLowerCase();
6301
+ if (normalizedNewName !== agent.name) {
6302
+ const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
6303
+ if (existing) {
6304
+ throw new Error(`Agent name already taken: ${normalizedNewName}`);
6305
+ }
6306
+ d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
6307
+ }
6308
+ }
6309
+ if (updates.description !== undefined) {
6310
+ d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
6311
+ }
6312
+ if (updates.role !== undefined) {
6313
+ d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
6314
+ }
6315
+ if (updates.metadata !== undefined) {
6316
+ d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
6317
+ }
6318
+ if ("active_project_id" in updates) {
6319
+ let resolvedProjectId = updates.active_project_id ?? null;
6320
+ if (resolvedProjectId) {
6321
+ const fullId = resolvePartialId(d, "projects", resolvedProjectId);
6322
+ if (!fullId) {
6323
+ throw new Error(`Project not found: ${resolvedProjectId}`);
6324
+ }
6325
+ resolvedProjectId = fullId;
6326
+ }
6327
+ d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProjectId, agent.id]);
6328
+ }
6329
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
6330
+ return getAgent(agent.id, d);
6331
+ }
6332
+ var CONFLICT_WINDOW_MS, UNBOUNDED_AGENT_LIST_LIMIT;
6333
+ var init_agents = __esm(() => {
6334
+ init_types();
6335
+ init_database();
6336
+ init_api_mode();
6337
+ CONFLICT_WINDOW_MS = 30 * 60 * 1000;
6338
+ UNBOUNDED_AGENT_LIST_LIMIT = Number.MAX_SAFE_INTEGER;
6339
+ });
6340
+
6341
+ // src/lib/package-version.ts
6342
+ import { readFileSync as readFileSync2 } from "fs";
6343
+ import { dirname as dirname2, join as join5 } from "path";
6344
+ import { fileURLToPath as fileURLToPath2 } from "url";
6345
+ function getMementosPackageVersion() {
6346
+ const here = dirname2(fileURLToPath2(import.meta.url));
6347
+ for (const candidate of [
6348
+ join5(here, "..", "..", "package.json"),
6349
+ join5(here, "..", "package.json")
6350
+ ]) {
6351
+ try {
6352
+ const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
6353
+ if (typeof parsed.version === "string" && parsed.version.trim())
6354
+ return parsed.version;
6355
+ } catch {}
6356
+ }
6357
+ return "0.0.0";
6358
+ }
6359
+ var init_package_version = () => {};
6360
+
6361
+ // src/project-registration/types.ts
6362
+ var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
6363
+ var init_types2 = () => {};
6364
+
5704
6365
  // src/db/projects.ts
5705
6366
  var exports_projects = {};
5706
6367
  __export(exports_projects, {
6368
+ updateProject: () => updateProject,
6369
+ rollbackProjectUpdate: () => rollbackProjectUpdate,
5707
6370
  registerProject: () => registerProject,
6371
+ previewProjectUpdate: () => previewProjectUpdate,
5708
6372
  listProjects: () => listProjects,
5709
- getProject: () => getProject
6373
+ getProjectUpdateReceipt: () => getProjectUpdateReceipt,
6374
+ getProject: () => getProject,
6375
+ applyProjectUpdate: () => applyProjectUpdate,
6376
+ ProjectGuardedUpdateError: () => ProjectGuardedUpdateError,
6377
+ ProjectCollisionError: () => ProjectCollisionError
5710
6378
  });
6379
+ import { createHash } from "crypto";
5711
6380
  function parseProjectRow(row) {
5712
6381
  return {
5713
6382
  id: row["id"],
@@ -5719,6 +6388,213 @@ function parseProjectRow(row) {
5719
6388
  updated_at: row["updated_at"]
5720
6389
  };
5721
6390
  }
6391
+ function canonicalizeProjectUpdateValue(value) {
6392
+ if (Array.isArray(value))
6393
+ return value.map(canonicalizeProjectUpdateValue);
6394
+ if (!value || typeof value !== "object")
6395
+ return value;
6396
+ const output = {};
6397
+ for (const key of Object.keys(value).sort()) {
6398
+ const item = value[key];
6399
+ if (item !== undefined)
6400
+ output[key] = canonicalizeProjectUpdateValue(item);
6401
+ }
6402
+ return output;
6403
+ }
6404
+ function canonicalProjectUpdateJson(value) {
6405
+ return JSON.stringify(canonicalizeProjectUpdateValue(value));
6406
+ }
6407
+ function digestProjectUpdateValue(value) {
6408
+ return createHash("sha256").update(canonicalProjectUpdateJson(value)).digest("hex");
6409
+ }
6410
+ function timestampString(value) {
6411
+ return value instanceof Date ? value.toISOString() : String(value);
6412
+ }
6413
+ function parseProjectJson(value) {
6414
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
6415
+ return parsed;
6416
+ }
6417
+ function projectUpdateReceiptFromRow(row) {
6418
+ return {
6419
+ receipt_id: String(row["receipt_id"]),
6420
+ authority: "mementos",
6421
+ route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
6422
+ package_version: String(row["package_version"]),
6423
+ authority_id: String(row["authority_id"]),
6424
+ tenant_id: String(row["tenant_id"]),
6425
+ corpus_id: String(row["corpus_id"]),
6426
+ operation_id: String(row["operation_id"]),
6427
+ step_id: String(row["step_id"]),
6428
+ direction: row["direction"],
6429
+ idempotency_key: String(row["idempotency_key"]),
6430
+ request_digest: String(row["request_digest"]),
6431
+ outcome: "accepted",
6432
+ target_id: String(row["target_id"]),
6433
+ expected_revision: timestampString(row["expected_revision"]),
6434
+ result_revision: timestampString(row["result_revision"]),
6435
+ result_digest: String(row["result_digest"]),
6436
+ accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
6437
+ before_project: parseProjectJson(row["before_project_json"]),
6438
+ after_project: parseProjectJson(row["after_project_json"]),
6439
+ created_at: timestampString(row["created_at"])
6440
+ };
6441
+ }
6442
+ function normalizeProjectUpdateInput(input) {
6443
+ const normalized = {};
6444
+ if (input.name !== undefined)
6445
+ normalized.name = input.name.trim();
6446
+ if (input.path !== undefined)
6447
+ normalized.path = input.path.trim();
6448
+ if (input.description !== undefined)
6449
+ normalized.description = input.description;
6450
+ if (input.memory_prefix !== undefined)
6451
+ normalized.memory_prefix = input.memory_prefix;
6452
+ if (Object.keys(normalized).length === 0) {
6453
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "At least one project field must be provided");
6454
+ }
6455
+ if (normalized.name !== undefined && normalized.name.length === 0) {
6456
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project name cannot be empty");
6457
+ }
6458
+ if (normalized.path !== undefined && normalized.path.length === 0) {
6459
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project path cannot be empty");
6460
+ }
6461
+ return normalized;
6462
+ }
6463
+ function assertProjectUpdateIdentity(identity) {
6464
+ if (identity.authority_id !== PROJECT_UPDATE_AUTHORITY.authority_id || identity.tenant_id !== PROJECT_UPDATE_AUTHORITY.tenant_id || identity.corpus_id !== PROJECT_UPDATE_AUTHORITY.corpus_id) {
6465
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
6466
+ }
6467
+ }
6468
+ function assertBoundedIdentifier(value, field) {
6469
+ if (!BOUNDED_IDENTIFIER.test(value)) {
6470
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
6471
+ }
6472
+ }
6473
+ function assertProjectUpdateRequest(request) {
6474
+ assertProjectUpdateIdentity(request);
6475
+ assertBoundedIdentifier(request.operation_id, "operation_id");
6476
+ assertBoundedIdentifier(request.step_id, "step_id");
6477
+ assertBoundedIdentifier(request.idempotency_key, "idempotency_key");
6478
+ if (!request.expected_revision || request.expected_revision.length > 128) {
6479
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "expected_revision is required and must be bounded");
6480
+ }
6481
+ }
6482
+ function getProjectByExactId(id, db) {
6483
+ const row = db.query("SELECT * FROM projects WHERE id = ? LIMIT 1").get(id);
6484
+ return row ? parseProjectRow(row) : null;
6485
+ }
6486
+ function assertNoProjectCollision(id, input, db) {
6487
+ if (input.name !== undefined) {
6488
+ const collision = db.query("SELECT id FROM projects WHERE LOWER(name) = LOWER(?) AND id != ? LIMIT 1").get(input.name, id);
6489
+ if (collision) {
6490
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project name already exists: ${input.name}`, { field: "name" });
6491
+ }
6492
+ }
6493
+ if (input.path !== undefined) {
6494
+ const collision = db.query("SELECT id FROM projects WHERE path = ? AND id != ? LIMIT 1").get(input.path, id);
6495
+ if (collision) {
6496
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project path already exists: ${input.path}`, { field: "path" });
6497
+ }
6498
+ }
6499
+ }
6500
+ function nextProjectRevision(previous) {
6501
+ const candidate = now();
6502
+ const previousMs = Date.parse(previous);
6503
+ const candidateMs = Date.parse(candidate);
6504
+ if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
6505
+ return new Date(previousMs + 1).toISOString();
6506
+ }
6507
+ return candidate;
6508
+ }
6509
+ function projectWithUpdates(project, updates, revision) {
6510
+ return {
6511
+ ...project,
6512
+ ...updates.name !== undefined ? { name: updates.name } : {},
6513
+ ...updates.path !== undefined ? { path: updates.path } : {},
6514
+ ...updates.description !== undefined ? { description: updates.description } : {},
6515
+ ...updates.memory_prefix !== undefined ? { memory_prefix: updates.memory_prefix } : {},
6516
+ updated_at: revision
6517
+ };
6518
+ }
6519
+ function findProjectUpdateReceiptByKey(db, identity, direction, idempotencyKey) {
6520
+ const row = db.query(`
6521
+ SELECT * FROM mementos_project_update_receipts
6522
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
6523
+ AND direction = ? AND idempotency_key = ?
6524
+ LIMIT 1
6525
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
6526
+ return row ? projectUpdateReceiptFromRow(row) : null;
6527
+ }
6528
+ function findProjectUpdateReceiptById(db, identity, receiptId) {
6529
+ const row = db.query(`
6530
+ SELECT * FROM mementos_project_update_receipts
6531
+ WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
6532
+ LIMIT 1
6533
+ `).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
6534
+ return row ? projectUpdateReceiptFromRow(row) : null;
6535
+ }
6536
+ function insertProjectUpdateReceipt(db, receipt) {
6537
+ db.run(`
6538
+ INSERT INTO mementos_project_update_receipts (
6539
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
6540
+ corpus_id, operation_id, step_id, direction, idempotency_key,
6541
+ request_digest, outcome, target_id, expected_revision, result_revision,
6542
+ result_digest, accepted_receipt_id, before_project_json,
6543
+ after_project_json, created_at
6544
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6545
+ `, [
6546
+ receipt.receipt_id,
6547
+ receipt.authority,
6548
+ receipt.route,
6549
+ receipt.package_version,
6550
+ receipt.authority_id,
6551
+ receipt.tenant_id,
6552
+ receipt.corpus_id,
6553
+ receipt.operation_id,
6554
+ receipt.step_id,
6555
+ receipt.direction,
6556
+ receipt.idempotency_key,
6557
+ receipt.request_digest,
6558
+ receipt.outcome,
6559
+ receipt.target_id,
6560
+ receipt.expected_revision,
6561
+ receipt.result_revision,
6562
+ receipt.result_digest,
6563
+ receipt.accepted_receipt_id,
6564
+ canonicalProjectUpdateJson(receipt.before_project),
6565
+ canonicalProjectUpdateJson(receipt.after_project),
6566
+ receipt.created_at
6567
+ ]);
6568
+ }
6569
+ function makeProjectUpdateReceipt(input) {
6570
+ const createdAt = now();
6571
+ const logical = {
6572
+ authority: "mementos",
6573
+ route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
6574
+ package_version: getMementosPackageVersion(),
6575
+ authority_id: input.request.authority_id,
6576
+ tenant_id: input.request.tenant_id,
6577
+ corpus_id: input.request.corpus_id,
6578
+ operation_id: input.request.operation_id,
6579
+ step_id: input.request.step_id,
6580
+ direction: input.direction,
6581
+ idempotency_key: input.request.idempotency_key,
6582
+ request_digest: input.request_digest,
6583
+ outcome: "accepted",
6584
+ target_id: input.target_id,
6585
+ expected_revision: input.request.expected_revision,
6586
+ result_revision: input.after_project.updated_at,
6587
+ result_digest: digestProjectUpdateValue(input.after_project),
6588
+ accepted_receipt_id: input.accepted_receipt_id ?? null,
6589
+ before_project: input.before_project,
6590
+ after_project: input.after_project,
6591
+ created_at: createdAt
6592
+ };
6593
+ return {
6594
+ receipt_id: `mpur_${digestProjectUpdateValue(logical).slice(0, 40)}`,
6595
+ ...logical
6596
+ };
6597
+ }
5722
6598
  function registerProject(name, path, description, memoryPrefix, db) {
5723
6599
  if (!db && isApiMode()) {
5724
6600
  const { data } = apiJson("POST", "/projects", {
@@ -5772,9 +6648,280 @@ function listProjects(db) {
5772
6648
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
5773
6649
  return rows.map(parseProjectRow);
5774
6650
  }
6651
+ function previewProjectUpdate(id, request, db) {
6652
+ assertProjectUpdateRequest(request);
6653
+ const normalized = normalizeProjectUpdateInput(request.updates);
6654
+ if (!db && isApiMode()) {
6655
+ const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: true });
6656
+ return data;
6657
+ }
6658
+ const d = db || getDatabase();
6659
+ const project = getProjectByExactId(id, d);
6660
+ if (!project) {
6661
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
6662
+ }
6663
+ if (project.updated_at !== request.expected_revision) {
6664
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project revision changed before the guarded update dry run", { expected_revision: request.expected_revision, current_revision: project.updated_at });
6665
+ }
6666
+ assertNoProjectCollision(id, normalized, d);
6667
+ return {
6668
+ dry_run: true,
6669
+ applied: false,
6670
+ project: projectWithUpdates(project, normalized, project.updated_at),
6671
+ receipt: null
6672
+ };
6673
+ }
6674
+ function applyProjectUpdate(id, request, db) {
6675
+ assertProjectUpdateRequest(request);
6676
+ const normalized = normalizeProjectUpdateInput(request.updates);
6677
+ if (!db && isApiMode()) {
6678
+ const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: false });
6679
+ return data;
6680
+ }
6681
+ const d = db || getDatabase();
6682
+ const requestDigest = digestProjectUpdateValue({
6683
+ ...request,
6684
+ target_id: id,
6685
+ direction: "forward",
6686
+ updates: normalized
6687
+ });
6688
+ return d.transaction(() => {
6689
+ const prior = findProjectUpdateReceiptByKey(d, request, "forward", request.idempotency_key);
6690
+ if (prior) {
6691
+ if (prior.request_digest !== requestDigest || prior.target_id !== id) {
6692
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different request");
6693
+ }
6694
+ const current = getProjectByExactId(id, d);
6695
+ if (!current || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(prior.after_project)) {
6696
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted guarded update target drifted after its immutable receipt");
6697
+ }
6698
+ return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
6699
+ }
6700
+ const before = getProjectByExactId(id, d);
6701
+ if (!before) {
6702
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
6703
+ }
6704
+ if (before.updated_at !== request.expected_revision) {
6705
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project revision changed before the guarded update", { expected_revision: request.expected_revision, current_revision: before.updated_at });
6706
+ }
6707
+ assertNoProjectCollision(id, normalized, d);
6708
+ const revision = nextProjectRevision(before.updated_at);
6709
+ const after = projectWithUpdates(before, normalized, revision);
6710
+ const result = d.run(`
6711
+ UPDATE projects
6712
+ SET name = ?, path = ?, description = ?, memory_prefix = ?, updated_at = ?
6713
+ WHERE id = ? AND updated_at = ?
6714
+ `, [
6715
+ after.name,
6716
+ after.path,
6717
+ after.description,
6718
+ after.memory_prefix,
6719
+ after.updated_at,
6720
+ id,
6721
+ request.expected_revision
6722
+ ]);
6723
+ if (result.changes !== 1) {
6724
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project compare-and-swap did not update exactly one row");
6725
+ }
6726
+ const readback = getProjectByExactId(id, d);
6727
+ if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(after)) {
6728
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project guarded update did not read back exactly under the stable ID");
6729
+ }
6730
+ const receipt = makeProjectUpdateReceipt({
6731
+ request: { ...request, updates: normalized },
6732
+ direction: "forward",
6733
+ request_digest: requestDigest,
6734
+ target_id: id,
6735
+ before_project: before,
6736
+ after_project: readback
6737
+ });
6738
+ insertProjectUpdateReceipt(d, receipt);
6739
+ return { dry_run: false, applied: true, project: readback, receipt };
6740
+ });
6741
+ }
6742
+ function rollbackProjectUpdate(id, request, db) {
6743
+ assertProjectUpdateRequest(request);
6744
+ assertBoundedIdentifier(request.accepted_receipt_id, "accepted_receipt_id");
6745
+ if (!db && isApiMode()) {
6746
+ const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-rollback`, request);
6747
+ return data;
6748
+ }
6749
+ const d = db || getDatabase();
6750
+ const requestDigest = digestProjectUpdateValue({
6751
+ ...request,
6752
+ target_id: id,
6753
+ direction: "rollback"
6754
+ });
6755
+ return d.transaction(() => {
6756
+ const prior = findProjectUpdateReceiptByKey(d, request, "rollback", request.idempotency_key);
6757
+ if (prior) {
6758
+ if (prior.request_digest !== requestDigest || prior.target_id !== id) {
6759
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different rollback request");
6760
+ }
6761
+ const current2 = getProjectByExactId(id, d);
6762
+ if (!current2 || canonicalProjectUpdateJson(current2) !== canonicalProjectUpdateJson(prior.after_project)) {
6763
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted rollback target drifted after its immutable receipt");
6764
+ }
6765
+ return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
6766
+ }
6767
+ const accepted = findProjectUpdateReceiptById(d, request, request.accepted_receipt_id);
6768
+ if (!accepted || accepted.direction !== "forward" || accepted.target_id !== id) {
6769
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "accepted forward update receipt was not found for this exact project");
6770
+ }
6771
+ const current = getProjectByExactId(id, d);
6772
+ if (!current) {
6773
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
6774
+ }
6775
+ if (current.updated_at !== request.expected_revision || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(accepted.after_project)) {
6776
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project no longer matches the accepted forward receipt", { expected_revision: request.expected_revision, current_revision: current.updated_at });
6777
+ }
6778
+ assertNoProjectCollision(id, accepted.before_project, d);
6779
+ const restored = accepted.before_project;
6780
+ const result = d.run(`
6781
+ UPDATE projects
6782
+ SET name = ?, path = ?, description = ?, memory_prefix = ?,
6783
+ created_at = ?, updated_at = ?
6784
+ WHERE id = ? AND updated_at = ?
6785
+ `, [
6786
+ restored.name,
6787
+ restored.path,
6788
+ restored.description,
6789
+ restored.memory_prefix,
6790
+ restored.created_at,
6791
+ restored.updated_at,
6792
+ id,
6793
+ request.expected_revision
6794
+ ]);
6795
+ if (result.changes !== 1) {
6796
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project rollback compare-and-swap did not update exactly one row");
6797
+ }
6798
+ const readback = getProjectByExactId(id, d);
6799
+ if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(restored)) {
6800
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project rollback did not restore the exact prior row");
6801
+ }
6802
+ const receipt = makeProjectUpdateReceipt({
6803
+ request,
6804
+ direction: "rollback",
6805
+ request_digest: requestDigest,
6806
+ target_id: id,
6807
+ before_project: current,
6808
+ after_project: readback,
6809
+ accepted_receipt_id: accepted.receipt_id
6810
+ });
6811
+ insertProjectUpdateReceipt(d, receipt);
6812
+ return { dry_run: false, applied: true, project: readback, receipt };
6813
+ });
6814
+ }
6815
+ function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db) {
6816
+ assertProjectUpdateIdentity(identity);
6817
+ if (!db && isApiMode()) {
6818
+ const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
6819
+ return data;
6820
+ }
6821
+ const d = db || getDatabase();
6822
+ const receipt = findProjectUpdateReceiptById(d, identity, receiptId);
6823
+ if (!receipt || receipt.target_id !== id) {
6824
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "immutable project update receipt was not found for this exact project");
6825
+ }
6826
+ return receipt;
6827
+ }
6828
+ function updateProject(id, input, db) {
6829
+ let normalizedInput;
6830
+ try {
6831
+ normalizedInput = normalizeProjectUpdateInput(input);
6832
+ } catch (error) {
6833
+ if (error instanceof ProjectGuardedUpdateError)
6834
+ throw new Error(error.message);
6835
+ throw error;
6836
+ }
6837
+ if (!db && isApiMode()) {
6838
+ const { status, data } = apiJson("PATCH", `/projects/${encodeURIComponent(id)}`, normalizedInput, { allow404: true });
6839
+ if (status === 404 || !data)
6840
+ return null;
6841
+ if (data.id !== id) {
6842
+ throw new Error(`Project update did not persist for ${id}: server returned a different stable ID (${data.id})`);
6843
+ }
6844
+ for (const field of ["name", "path", "description", "memory_prefix"]) {
6845
+ if (normalizedInput[field] !== undefined && data[field] !== normalizedInput[field]) {
6846
+ throw new Error(`Project update did not persist for ${id}: ${field} remained ${JSON.stringify(data[field])}`);
6847
+ }
6848
+ }
6849
+ return data;
6850
+ }
6851
+ const d = db || getDatabase();
6852
+ return d.transaction(() => {
6853
+ const existingRow = d.query("SELECT * FROM projects WHERE id = ?").get(id);
6854
+ if (!existingRow)
6855
+ return null;
6856
+ const existing = parseProjectRow(existingRow);
6857
+ const nextName = normalizedInput.name;
6858
+ const nextPath = normalizedInput.path;
6859
+ if (nextName !== undefined) {
6860
+ const collision = d.query("SELECT id FROM projects WHERE LOWER(name) = LOWER(?) AND id != ?").get(nextName, existing.id);
6861
+ if (collision)
6862
+ throw new ProjectCollisionError("name", nextName);
6863
+ }
6864
+ if (nextPath !== undefined) {
6865
+ const collision = d.query("SELECT id FROM projects WHERE path = ? AND id != ?").get(nextPath, existing.id);
6866
+ if (collision)
6867
+ throw new ProjectCollisionError("path", nextPath);
6868
+ }
6869
+ const sets = ["updated_at = ?"];
6870
+ const values = [now()];
6871
+ if (nextName !== undefined) {
6872
+ sets.push("name = ?");
6873
+ values.push(nextName);
6874
+ }
6875
+ if (nextPath !== undefined) {
6876
+ sets.push("path = ?");
6877
+ values.push(nextPath);
6878
+ }
6879
+ if (normalizedInput.description !== undefined) {
6880
+ sets.push("description = ?");
6881
+ values.push(normalizedInput.description);
6882
+ }
6883
+ if (normalizedInput.memory_prefix !== undefined) {
6884
+ sets.push("memory_prefix = ?");
6885
+ values.push(normalizedInput.memory_prefix);
6886
+ }
6887
+ values.push(existing.id);
6888
+ d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, values);
6889
+ const updated = d.query("SELECT * FROM projects WHERE id = ?").get(existing.id);
6890
+ return updated ? parseProjectRow(updated) : null;
6891
+ });
6892
+ }
6893
+ var ProjectCollisionError, ProjectGuardedUpdateError, PROJECT_UPDATE_AUTHORITY, BOUNDED_IDENTIFIER;
5775
6894
  var init_projects = __esm(() => {
5776
6895
  init_database();
5777
6896
  init_api_mode();
6897
+ init_package_version();
6898
+ init_types2();
6899
+ ProjectCollisionError = class ProjectCollisionError extends Error {
6900
+ field;
6901
+ value;
6902
+ constructor(field, value) {
6903
+ super(`Project ${field} already exists: ${value}`);
6904
+ this.field = field;
6905
+ this.value = value;
6906
+ this.name = "ProjectCollisionError";
6907
+ }
6908
+ };
6909
+ ProjectGuardedUpdateError = class ProjectGuardedUpdateError extends Error {
6910
+ code;
6911
+ details;
6912
+ constructor(code, message, details = {}) {
6913
+ super(message);
6914
+ this.code = code;
6915
+ this.details = details;
6916
+ this.name = "ProjectGuardedUpdateError";
6917
+ }
6918
+ };
6919
+ PROJECT_UPDATE_AUTHORITY = {
6920
+ authority_id: "mementos",
6921
+ tenant_id: "default",
6922
+ corpus_id: "default"
6923
+ };
6924
+ BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
5778
6925
  });
5779
6926
 
5780
6927
  // src/db/entities.ts
@@ -5993,6 +7140,7 @@ __export(exports_helpers, {
5993
7140
  resolveMemoryId: () => resolveMemoryId,
5994
7141
  resolveKeyOrId: () => resolveKeyOrId,
5995
7142
  resolveEntityArg: () => resolveEntityArg,
7143
+ resolveAgentFilter: () => resolveAgentFilter,
5996
7144
  readFileConfig: () => readFileConfig,
5997
7145
  printPageHint: () => printPageHint,
5998
7146
  positiveIntOrDefault: () => positiveIntOrDefault,
@@ -6023,13 +7171,13 @@ __export(exports_helpers, {
6023
7171
  DEFAULT_COMPACT_LIMIT: () => DEFAULT_COMPACT_LIMIT
6024
7172
  });
6025
7173
  import chalk from "chalk";
6026
- import { readFileSync as readFileSync2 } from "fs";
6027
- import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
6028
- import { fileURLToPath as fileURLToPath2 } from "url";
7174
+ import { readFileSync as readFileSync3 } from "fs";
7175
+ import { dirname as dirname3, join as join6, resolve as resolve2 } from "path";
7176
+ import { fileURLToPath as fileURLToPath3 } from "url";
6029
7177
  function getPackageVersion() {
6030
7178
  try {
6031
- const pkgPath = join5(dirname2(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
6032
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
7179
+ const pkgPath = join6(dirname3(fileURLToPath3(import.meta.url)), "..", "..", "package.json");
7180
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
6033
7181
  return pkg.version || "0.0.0";
6034
7182
  } catch {
6035
7183
  return "0.0.0";
@@ -6259,8 +7407,19 @@ function resolveMemoryId(partialId) {
6259
7407
  }
6260
7408
  return id;
6261
7409
  }
7410
+ function resolveAgentFilter(nameOrId) {
7411
+ if (!nameOrId)
7412
+ return;
7413
+ const agent = getAgent(nameOrId);
7414
+ if (agent)
7415
+ return agent.id;
7416
+ process.stderr.write(`Warning: no agent named '${nameOrId}' is registered, so this empty result may be a
7417
+ ` + ` mistyped name rather than an empty set. Check with 'mementos agents'.
7418
+ `);
7419
+ return nameOrId;
7420
+ }
6262
7421
  function resolveKeyOrId(keyOrId, opts, globalOpts) {
6263
- const agentId = opts.agent || globalOpts.agent;
7422
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
6264
7423
  const projectPath = opts.project || globalOpts.project;
6265
7424
  let projectId;
6266
7425
  if (projectPath) {
@@ -6545,7 +7704,7 @@ function validateConfigKeyValue(key, value, DEFAULT_CONFIG) {
6545
7704
  }
6546
7705
  function getConfigPath() {
6547
7706
  const { homedir: homedir3 } = __require("os");
6548
- return join5(homedir3(), ".hasna", "mementos", "config.json");
7707
+ return join6(homedir3(), ".hasna", "mementos", "config.json");
6549
7708
  }
6550
7709
  function readFileConfig() {
6551
7710
  const { existsSync: existsSync4 } = __require("fs");
@@ -6553,7 +7712,7 @@ function readFileConfig() {
6553
7712
  if (!existsSync4(configPath))
6554
7713
  return {};
6555
7714
  try {
6556
- const data = JSON.parse(readFileSync2(configPath, "utf-8"));
7715
+ const data = JSON.parse(readFileSync3(configPath, "utf-8"));
6557
7716
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
6558
7717
  throw new Error("expected a JSON object");
6559
7718
  }
@@ -6566,7 +7725,7 @@ function readFileConfig() {
6566
7725
  function writeFileConfig(data) {
6567
7726
  const { existsSync: existsSync4, writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = __require("fs");
6568
7727
  const configPath = getConfigPath();
6569
- const dir = dirname2(configPath);
7728
+ const dir = dirname3(configPath);
6570
7729
  if (!existsSync4(dir))
6571
7730
  mkdirSync3(dir, { recursive: true });
6572
7731
  writeFileSync3(configPath, JSON.stringify(data, null, 2) + `
@@ -6577,6 +7736,7 @@ var init_helpers = __esm(() => {
6577
7736
  init_database();
6578
7737
  init_api_mode();
6579
7738
  init_memories();
7739
+ init_agents();
6580
7740
  init_projects();
6581
7741
  init_entities();
6582
7742
  scopeColor = {
@@ -6607,194 +7767,6 @@ var init_helpers = __esm(() => {
6607
7767
  VALID_CATEGORIES = ["preference", "fact", "knowledge", "history"];
6608
7768
  });
6609
7769
 
6610
- // src/db/agents.ts
6611
- var exports_agents = {};
6612
- __export(exports_agents, {
6613
- updateAgent: () => updateAgent,
6614
- touchAgent: () => touchAgent,
6615
- registerAgent: () => registerAgent,
6616
- listAgentsByProject: () => listAgentsByProject,
6617
- listAgents: () => listAgents,
6618
- getAgent: () => getAgent
6619
- });
6620
- function parseAgentRow(row) {
6621
- return {
6622
- id: row["id"],
6623
- name: row["name"],
6624
- session_id: row["session_id"] || null,
6625
- description: row["description"] || null,
6626
- role: row["role"] || null,
6627
- metadata: JSON.parse(row["metadata"] || "{}"),
6628
- active_project_id: row["active_project_id"] || null,
6629
- created_at: row["created_at"],
6630
- last_seen_at: row["last_seen_at"]
6631
- };
6632
- }
6633
- function registerAgent(name, sessionId, description, role, projectId, db) {
6634
- if (!db && isApiMode()) {
6635
- const { data } = apiJson("POST", "/agents", {
6636
- name,
6637
- session_id: sessionId,
6638
- description,
6639
- role,
6640
- project_id: projectId
6641
- });
6642
- return data;
6643
- }
6644
- const d = db || getDatabase();
6645
- const timestamp = now();
6646
- const normalizedName = name.trim().toLowerCase();
6647
- if (projectId) {
6648
- const resolvedProjectId = resolvePartialId(d, "projects", projectId);
6649
- if (!resolvedProjectId) {
6650
- throw new Error(`Project not found: ${projectId}`);
6651
- }
6652
- projectId = resolvedProjectId;
6653
- }
6654
- const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
6655
- if (existing) {
6656
- const existingId = existing["id"];
6657
- const existingSessionId = existing["session_id"] || null;
6658
- const existingLastSeen = existing["last_seen_at"];
6659
- if (sessionId && existingSessionId && existingSessionId !== sessionId) {
6660
- const lastSeenMs = new Date(existingLastSeen).getTime();
6661
- const nowMs = Date.now();
6662
- if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
6663
- throw new AgentConflictError({
6664
- existing_id: existingId,
6665
- existing_name: normalizedName,
6666
- last_seen_at: existingLastSeen,
6667
- session_hint: existingSessionId.slice(0, 8),
6668
- working_dir: null
6669
- });
6670
- }
6671
- }
6672
- d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
6673
- timestamp,
6674
- sessionId ?? existingSessionId,
6675
- existingId
6676
- ]);
6677
- if (description) {
6678
- d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
6679
- }
6680
- if (role) {
6681
- d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
6682
- }
6683
- if (projectId !== undefined) {
6684
- d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
6685
- }
6686
- return getAgent(existingId, d);
6687
- }
6688
- const id = shortUuid();
6689
- d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
6690
- return getAgent(id, d);
6691
- }
6692
- function getAgent(idOrName, db) {
6693
- if (!db && isApiMode()) {
6694
- const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
6695
- if (status === 404 || !data)
6696
- return null;
6697
- return data;
6698
- }
6699
- const d = db || getDatabase();
6700
- let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
6701
- if (row)
6702
- return parseAgentRow(row);
6703
- row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
6704
- if (row)
6705
- return parseAgentRow(row);
6706
- const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
6707
- if (rows.length === 1)
6708
- return parseAgentRow(rows[0]);
6709
- return null;
6710
- }
6711
- function listAgents(db) {
6712
- if (!db && isApiMode()) {
6713
- const { data } = apiJson("GET", "/agents");
6714
- return data?.agents ?? [];
6715
- }
6716
- const d = db || getDatabase();
6717
- const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
6718
- return rows.map(parseAgentRow);
6719
- }
6720
- function touchAgent(idOrName, db) {
6721
- if (!db && isApiMode()) {
6722
- const agent2 = getAgent(idOrName);
6723
- if (!agent2)
6724
- return;
6725
- apiJson("PATCH", `/agents/${encodeURIComponent(agent2.id)}`, {});
6726
- return;
6727
- }
6728
- const d = db || getDatabase();
6729
- const agent = getAgent(idOrName, d);
6730
- if (!agent)
6731
- return;
6732
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
6733
- }
6734
- function listAgentsByProject(projectId, db) {
6735
- if (!db && isApiMode()) {
6736
- const q = toQuery({ project_id: projectId });
6737
- const { data } = apiJson("GET", `/agents${q}`);
6738
- return data?.agents ?? [];
6739
- }
6740
- const d = db || getDatabase();
6741
- const resolvedId = resolvePartialId(d, "projects", projectId) || projectId;
6742
- const rows = d.query("SELECT * FROM agents WHERE active_project_id = ? ORDER BY last_seen_at DESC").all(resolvedId);
6743
- return rows.map(parseAgentRow);
6744
- }
6745
- function updateAgent(id, updates, db) {
6746
- if (!db && isApiMode()) {
6747
- const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
6748
- if (status === 404 || !data)
6749
- return null;
6750
- return data;
6751
- }
6752
- const d = db || getDatabase();
6753
- const agent = getAgent(id, d);
6754
- if (!agent)
6755
- return null;
6756
- const timestamp = now();
6757
- if (updates.name) {
6758
- const normalizedNewName = updates.name.trim().toLowerCase();
6759
- if (normalizedNewName !== agent.name) {
6760
- const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
6761
- if (existing) {
6762
- throw new Error(`Agent name already taken: ${normalizedNewName}`);
6763
- }
6764
- d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
6765
- }
6766
- }
6767
- if (updates.description !== undefined) {
6768
- d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
6769
- }
6770
- if (updates.role !== undefined) {
6771
- d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
6772
- }
6773
- if (updates.metadata !== undefined) {
6774
- d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
6775
- }
6776
- if ("active_project_id" in updates) {
6777
- let resolvedProjectId = updates.active_project_id ?? null;
6778
- if (resolvedProjectId) {
6779
- const fullId = resolvePartialId(d, "projects", resolvedProjectId);
6780
- if (!fullId) {
6781
- throw new Error(`Project not found: ${resolvedProjectId}`);
6782
- }
6783
- resolvedProjectId = fullId;
6784
- }
6785
- d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProjectId, agent.id]);
6786
- }
6787
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
6788
- return getAgent(agent.id, d);
6789
- }
6790
- var CONFLICT_WINDOW_MS;
6791
- var init_agents = __esm(() => {
6792
- init_types();
6793
- init_database();
6794
- init_api_mode();
6795
- CONFLICT_WINDOW_MS = 30 * 60 * 1000;
6796
- });
6797
-
6798
7770
  // src/lib/poll.ts
6799
7771
  var exports_poll = {};
6800
7772
  __export(exports_poll, {
@@ -11304,11 +12276,11 @@ __export(exports_session_registry, {
11304
12276
  cleanStaleSessions: () => cleanStaleSessions
11305
12277
  });
11306
12278
  import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
11307
- import { dirname as dirname5, join as join8 } from "path";
12279
+ import { dirname as dirname6, join as join9 } from "path";
11308
12280
  function getDb() {
11309
12281
  if (_db2)
11310
12282
  return _db2;
11311
- const dir = dirname5(DB_PATH);
12283
+ const dir = dirname6(DB_PATH);
11312
12284
  if (!existsSync8(dir))
11313
12285
  mkdirSync5(dir, { recursive: true });
11314
12286
  _db2 = new SqliteAdapter(DB_PATH);
@@ -11484,12 +12456,13 @@ function closeRegistry() {
11484
12456
  var DB_PATH, _db2 = null;
11485
12457
  var init_session_registry = __esm(() => {
11486
12458
  init_storage();
11487
- DB_PATH = join8(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
12459
+ DB_PATH = join9(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
11488
12460
  });
11489
12461
 
11490
12462
  // src/db/pg-migrations.ts
11491
12463
  var PG_MIGRATIONS;
11492
12464
  var init_pg_migrations = __esm(() => {
12465
+ init_schema();
11493
12466
  PG_MIGRATIONS = [
11494
12467
  `
11495
12468
  CREATE TABLE IF NOT EXISTS projects (
@@ -12222,6 +13195,47 @@ var init_pg_migrations = __esm(() => {
12222
13195
  EXECUTE FUNCTION snapshot_memory_version();
12223
13196
 
12224
13197
  INSERT INTO _migrations (id) VALUES (36) ON CONFLICT DO NOTHING;
13198
+ `,
13199
+ `
13200
+ CREATE OR REPLACE FUNCTION audit_memory_insert() RETURNS trigger AS $$
13201
+ BEGIN
13202
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, new_value_hash, created_at)
13203
+ VALUES (gen_random_uuid()::text, NEW.id, NEW.key, 'create', NEW.agent_id, md5(COALESCE(NEW.value, '')), NOW());
13204
+ RETURN NEW;
13205
+ END;
13206
+ $$ LANGUAGE plpgsql;
13207
+
13208
+ CREATE OR REPLACE FUNCTION audit_memory_update() RETURNS trigger AS $$
13209
+ BEGIN
13210
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, old_value_hash, new_value_hash, changes, created_at)
13211
+ VALUES (gen_random_uuid()::text, NEW.id, NEW.key, 'update', NEW.agent_id, md5(COALESCE(OLD.value, '')), md5(COALESCE(NEW.value, '')),
13212
+ json_build_object('version_from', OLD.version, 'version_to', NEW.version, 'importance_from', OLD.importance, 'importance_to', NEW.importance)::text,
13213
+ NOW());
13214
+ RETURN NEW;
13215
+ END;
13216
+ $$ LANGUAGE plpgsql;
13217
+
13218
+ CREATE OR REPLACE FUNCTION audit_memory_delete() RETURNS trigger AS $$
13219
+ BEGIN
13220
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, old_value_hash, created_at)
13221
+ VALUES (gen_random_uuid()::text, OLD.id, OLD.key, 'delete', OLD.agent_id, md5(COALESCE(OLD.value, '')), NOW());
13222
+ RETURN OLD;
13223
+ END;
13224
+ $$ LANGUAGE plpgsql;
13225
+
13226
+ INSERT INTO _migrations (id) VALUES (37) ON CONFLICT DO NOTHING;
13227
+ `,
13228
+ `
13229
+ ${postgresMementosProjectRegistrationSchemaSql()}
13230
+ INSERT INTO _migrations (id) VALUES (38) ON CONFLICT DO NOTHING;
13231
+ `,
13232
+ `
13233
+ ${postgresMementosProjectGuardedUpdateSchemaSql()}
13234
+ INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
13235
+ `,
13236
+ `
13237
+ ${postgresMementosMemoryProjectLinkSchemaSql()}
13238
+ INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
12225
13239
  `
12226
13240
  ];
12227
13241
  });
@@ -13371,7 +14385,7 @@ var handleResult = (ctx, result) => {
13371
14385
  }, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, BRAND, ZodBranded, ZodPipeline, ZodReadonly, late, ZodFirstPartyTypeKind, instanceOfType = (cls, params = {
13372
14386
  message: `Input not instance of ${cls.name}`
13373
14387
  }) => custom((data) => data instanceof cls, params), stringType, numberType, nanType, bigIntType, booleanType, dateType, symbolType, undefinedType, nullType, anyType, unknownType, neverType, voidType, arrayType, objectType, strictObjectType, unionType, discriminatedUnionType, intersectionType, tupleType, recordType, mapType, setType, functionType, lazyType, literalType, enumType, nativeEnumType, promiseType, effectsType, optionalType, nullableType, preprocessType, pipelineType, ostring = () => stringType().optional(), onumber = () => numberType().optional(), oboolean = () => booleanType().optional(), coerce, NEVER;
13374
- var init_types2 = __esm(() => {
14388
+ var init_types3 = __esm(() => {
13375
14389
  init_ZodError();
13376
14390
  init_errors();
13377
14391
  init_errorUtil();
@@ -16292,7 +17306,7 @@ var init_external = __esm(() => {
16292
17306
  init_parseUtil();
16293
17307
  init_typeAliases();
16294
17308
  init_util();
16295
- init_types2();
17309
+ init_types3();
16296
17310
  init_ZodError();
16297
17311
  });
16298
17312
 
@@ -58951,9 +59965,9 @@ var {
58951
59965
 
58952
59966
  // src/cli/index.tsx
58953
59967
  init_database();
58954
- import { readFileSync as readFileSync8 } from "fs";
58955
- import { dirname as dirname7, join as join12 } from "path";
58956
- import { fileURLToPath as fileURLToPath4 } from "url";
59968
+ import { readFileSync as readFileSync9 } from "fs";
59969
+ import { dirname as dirname8, join as join13 } from "path";
59970
+ import { fileURLToPath as fileURLToPath5 } from "url";
58957
59971
 
58958
59972
  // src/db/machines.ts
58959
59973
  init_database();
@@ -59753,11 +60767,638 @@ init_helpers();
59753
60767
  init_database();
59754
60768
  init_api_mode();
59755
60769
  init_memories();
59756
- init_projects();
59757
- init_agents();
59758
60770
  import chalk2 from "chalk";
59759
60771
  import { resolve as resolve3 } from "path";
59760
60772
 
60773
+ // src/db/memory-project-link.ts
60774
+ init_storage();
60775
+ init_api_mode();
60776
+ init_database();
60777
+ init_memories();
60778
+ init_package_version();
60779
+ init_schema();
60780
+ import { createHash as createHash2 } from "crypto";
60781
+
60782
+ class MemoryProjectLinkError extends Error {
60783
+ code;
60784
+ details;
60785
+ constructor(code, message, details = {}) {
60786
+ super(message);
60787
+ this.code = code;
60788
+ this.details = details;
60789
+ this.name = "MemoryProjectLinkError";
60790
+ }
60791
+ }
60792
+ var LINK_AUTHORITY = {
60793
+ authority_id: "mementos",
60794
+ tenant_id: "default",
60795
+ corpus_id: "default"
60796
+ };
60797
+ var BOUNDED_IDENTIFIER2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
60798
+ function canonicalize(value) {
60799
+ if (Array.isArray(value))
60800
+ return value.map(canonicalize);
60801
+ if (!value || typeof value !== "object")
60802
+ return value;
60803
+ const output = {};
60804
+ for (const key of Object.keys(value).sort()) {
60805
+ const item = value[key];
60806
+ if (item !== undefined)
60807
+ output[key] = canonicalize(item);
60808
+ }
60809
+ return output;
60810
+ }
60811
+ function canonicalJson(value) {
60812
+ return JSON.stringify(canonicalize(value));
60813
+ }
60814
+ function digest(value) {
60815
+ return createHash2("sha256").update(canonicalJson(value)).digest("hex");
60816
+ }
60817
+ function timestampString2(value) {
60818
+ return value instanceof Date ? value.toISOString() : String(value);
60819
+ }
60820
+ function nullableTimestamp(value) {
60821
+ return value === null || value === undefined ? null : timestampString2(value);
60822
+ }
60823
+ function normalizeMemoryTimestamps(memory) {
60824
+ return {
60825
+ ...memory,
60826
+ created_at: timestampString2(memory.created_at),
60827
+ updated_at: timestampString2(memory.updated_at),
60828
+ accessed_at: nullableTimestamp(memory.accessed_at),
60829
+ expires_at: nullableTimestamp(memory.expires_at),
60830
+ valid_from: nullableTimestamp(memory.valid_from),
60831
+ valid_until: nullableTimestamp(memory.valid_until),
60832
+ ingested_at: nullableTimestamp(memory.ingested_at)
60833
+ };
60834
+ }
60835
+ function parseProjectRow2(row) {
60836
+ return {
60837
+ id: String(row["id"]),
60838
+ name: String(row["name"]),
60839
+ path: String(row["path"]),
60840
+ description: row["description"] === null ? null : String(row["description"] ?? "") || null,
60841
+ memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"] ?? "") || null,
60842
+ created_at: timestampString2(row["created_at"]),
60843
+ updated_at: timestampString2(row["updated_at"])
60844
+ };
60845
+ }
60846
+ function memoryMutationState(memory) {
60847
+ const { access_count: _accessCount, accessed_at: _accessedAt, ...state } = memory;
60848
+ return state;
60849
+ }
60850
+ function memoryDigest(memory) {
60851
+ return digest(memoryMutationState(memory));
60852
+ }
60853
+ function projectDigest(project) {
60854
+ return digest(project);
60855
+ }
60856
+ function snapshot(memory) {
60857
+ return {
60858
+ memory_id: memory.id,
60859
+ project_id: memory.project_id,
60860
+ memory_version: memory.version,
60861
+ memory_revision: timestampString2(memory.updated_at),
60862
+ memory_digest: memoryDigest(memory)
60863
+ };
60864
+ }
60865
+ function parseSnapshot(value) {
60866
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
60867
+ return parsed;
60868
+ }
60869
+ function receiptFromRow(row) {
60870
+ return {
60871
+ receipt_id: String(row["receipt_id"]),
60872
+ authority: "mementos",
60873
+ route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
60874
+ package_version: String(row["package_version"]),
60875
+ authority_id: String(row["authority_id"]),
60876
+ tenant_id: String(row["tenant_id"]),
60877
+ corpus_id: String(row["corpus_id"]),
60878
+ operation_id: String(row["operation_id"]),
60879
+ step_id: String(row["step_id"]),
60880
+ direction: row["direction"],
60881
+ idempotency_key: String(row["idempotency_key"]),
60882
+ request_digest: String(row["request_digest"]),
60883
+ outcome: row["outcome"],
60884
+ target_memory_id: String(row["target_memory_id"]),
60885
+ requested_project_id: String(row["requested_project_id"]),
60886
+ expected_memory_version: Number(row["expected_memory_version"]),
60887
+ expected_memory_revision: timestampString2(row["expected_memory_revision"]),
60888
+ expected_project_revision: nullableTimestamp(row["expected_project_revision"]),
60889
+ result_memory_version: Number(row["result_memory_version"]),
60890
+ result_memory_revision: timestampString2(row["result_memory_revision"]),
60891
+ result_memory_digest: String(row["result_memory_digest"]),
60892
+ result_project_revision: nullableTimestamp(row["result_project_revision"]),
60893
+ result_project_digest: row["result_project_digest"] === null ? null : String(row["result_project_digest"]),
60894
+ accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
60895
+ before_link: parseSnapshot(row["before_link_json"]),
60896
+ after_link: parseSnapshot(row["after_link_json"]),
60897
+ before_project_revision: nullableTimestamp(row["before_project_revision"]),
60898
+ before_project_digest: row["before_project_digest"] === null ? null : String(row["before_project_digest"]),
60899
+ after_project_revision: nullableTimestamp(row["after_project_revision"]),
60900
+ after_project_digest: row["after_project_digest"] === null ? null : String(row["after_project_digest"]),
60901
+ created_at: timestampString2(row["created_at"])
60902
+ };
60903
+ }
60904
+ function assertIdentity(identity) {
60905
+ if (identity.authority_id !== LINK_AUTHORITY.authority_id || identity.tenant_id !== LINK_AUTHORITY.tenant_id || identity.corpus_id !== LINK_AUTHORITY.corpus_id) {
60906
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", "memory project link does not match this authority, tenant, and corpus");
60907
+ }
60908
+ }
60909
+ function assertBoundedIdentifier2(value, field) {
60910
+ if (!BOUNDED_IDENTIFIER2.test(value)) {
60911
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
60912
+ }
60913
+ }
60914
+ function assertMemoryGuard(version, revision) {
60915
+ if (!Number.isSafeInteger(version) || version < 1) {
60916
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_version must be a positive safe integer");
60917
+ }
60918
+ if (!revision || revision.length > 128) {
60919
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_revision is required and must be bounded");
60920
+ }
60921
+ }
60922
+ function assertForwardRequest(request) {
60923
+ assertIdentity(request);
60924
+ assertBoundedIdentifier2(request.operation_id, "operation_id");
60925
+ assertBoundedIdentifier2(request.step_id, "step_id");
60926
+ assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
60927
+ assertBoundedIdentifier2(request.target_project_id, "target_project_id");
60928
+ assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
60929
+ if (!request.expected_project_revision || request.expected_project_revision.length > 128) {
60930
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_project_revision is required and must be bounded");
60931
+ }
60932
+ }
60933
+ function assertRollbackRequest(request) {
60934
+ assertIdentity(request);
60935
+ assertBoundedIdentifier2(request.operation_id, "operation_id");
60936
+ assertBoundedIdentifier2(request.step_id, "step_id");
60937
+ assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
60938
+ assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
60939
+ assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
60940
+ }
60941
+ function forwardRequestDigest(memoryId, request) {
60942
+ return digest({
60943
+ authority_id: request.authority_id,
60944
+ tenant_id: request.tenant_id,
60945
+ corpus_id: request.corpus_id,
60946
+ operation_id: request.operation_id,
60947
+ step_id: request.step_id,
60948
+ idempotency_key: request.idempotency_key,
60949
+ expected_memory_version: request.expected_memory_version,
60950
+ expected_memory_revision: request.expected_memory_revision,
60951
+ target_project_id: request.target_project_id,
60952
+ expected_project_revision: request.expected_project_revision,
60953
+ direction: "forward",
60954
+ target_memory_id: memoryId
60955
+ });
60956
+ }
60957
+ function rollbackRequestDigest(memoryId, request) {
60958
+ return digest({
60959
+ authority_id: request.authority_id,
60960
+ tenant_id: request.tenant_id,
60961
+ corpus_id: request.corpus_id,
60962
+ operation_id: request.operation_id,
60963
+ step_id: request.step_id,
60964
+ idempotency_key: request.idempotency_key,
60965
+ expected_memory_version: request.expected_memory_version,
60966
+ expected_memory_revision: request.expected_memory_revision,
60967
+ accepted_receipt_id: request.accepted_receipt_id,
60968
+ direction: "rollback",
60969
+ target_memory_id: memoryId
60970
+ });
60971
+ }
60972
+ function forUpdateSuffix(db, lock) {
60973
+ return lock && db instanceof PgAdapter ? " FOR UPDATE" : "";
60974
+ }
60975
+ function getMemoryByExactId(id, db, lock = false) {
60976
+ const row = db.query(`SELECT * FROM memories WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
60977
+ return row ? normalizeMemoryTimestamps(parseMemoryRow(row)) : null;
60978
+ }
60979
+ function getProjectByExactId2(id, db, lock = false) {
60980
+ const row = db.query(`SELECT * FROM projects WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
60981
+ return row ? parseProjectRow2(row) : null;
60982
+ }
60983
+ function lockProjects(ids, db) {
60984
+ const projects = new Map;
60985
+ const uniqueIds = [...new Set(ids.filter((id) => Boolean(id)))].sort();
60986
+ for (const id of uniqueIds) {
60987
+ const project = getProjectByExactId2(id, db, true);
60988
+ if (!project) {
60989
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
60990
+ }
60991
+ projects.set(id, project);
60992
+ }
60993
+ return projects;
60994
+ }
60995
+ function assertMemoryPrecondition(memory, expectedVersion, expectedRevision) {
60996
+ if (memory.version !== expectedVersion || memory.updated_at !== expectedRevision) {
60997
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory changed before the guarded project link", {
60998
+ expected_memory_version: expectedVersion,
60999
+ current_memory_version: memory.version,
61000
+ expected_memory_revision: expectedRevision,
61001
+ current_memory_revision: memory.updated_at
61002
+ });
61003
+ }
61004
+ }
61005
+ function assertProjectPrecondition(project, expectedRevision) {
61006
+ if (project.updated_at !== expectedRevision) {
61007
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_PROJECT", "Project changed before the guarded memory link", {
61008
+ expected_project_revision: expectedRevision,
61009
+ current_project_revision: project.updated_at
61010
+ });
61011
+ }
61012
+ }
61013
+ function assertNoTargetBucketCollision(memory, projectId, db) {
61014
+ const collision = db.query(`
61015
+ SELECT id FROM memories
61016
+ WHERE id != ? AND key = ? AND scope = ?
61017
+ AND COALESCE(agent_id, '') = COALESCE(?, '')
61018
+ AND COALESCE(project_id, '') = COALESCE(?, '')
61019
+ AND COALESCE(session_id, '') = COALESCE(?, '')
61020
+ LIMIT 1
61021
+ `).get(memory.id, memory.key, memory.scope, memory.agent_id, projectId, memory.session_id);
61022
+ if (collision) {
61023
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_COLLISION", "Linking this memory would collide with another memory in the target project bucket", { collision_id: collision.id });
61024
+ }
61025
+ }
61026
+ function nextRevision(previous) {
61027
+ const candidate = now();
61028
+ const previousMs = Date.parse(previous);
61029
+ const candidateMs = Date.parse(candidate);
61030
+ if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
61031
+ return new Date(previousMs + 1).toISOString();
61032
+ }
61033
+ return candidate;
61034
+ }
61035
+ function findReceiptByKey(db, identity, direction, idempotencyKey) {
61036
+ const row = db.query(`
61037
+ SELECT * FROM mementos_memory_project_link_receipts
61038
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
61039
+ AND direction = ? AND idempotency_key = ?
61040
+ LIMIT 1
61041
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
61042
+ return row ? receiptFromRow(row) : null;
61043
+ }
61044
+ function findReceiptById(db, identity, receiptId) {
61045
+ const row = db.query(`
61046
+ SELECT * FROM mementos_memory_project_link_receipts
61047
+ WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
61048
+ LIMIT 1
61049
+ `).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
61050
+ return row ? receiptFromRow(row) : null;
61051
+ }
61052
+ function insertReceipt(db, receipt) {
61053
+ db.run(`
61054
+ INSERT INTO mementos_memory_project_link_receipts (
61055
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
61056
+ corpus_id, operation_id, step_id, direction, idempotency_key,
61057
+ request_digest, outcome, target_memory_id, requested_project_id,
61058
+ expected_memory_version, expected_memory_revision,
61059
+ expected_project_revision, result_memory_version,
61060
+ result_memory_revision, result_memory_digest, result_project_revision,
61061
+ result_project_digest, accepted_receipt_id, before_link_json,
61062
+ after_link_json, before_project_revision, before_project_digest,
61063
+ after_project_revision, after_project_digest, created_at
61064
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
61065
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
61066
+ `, [
61067
+ receipt.receipt_id,
61068
+ receipt.authority,
61069
+ receipt.route,
61070
+ receipt.package_version,
61071
+ receipt.authority_id,
61072
+ receipt.tenant_id,
61073
+ receipt.corpus_id,
61074
+ receipt.operation_id,
61075
+ receipt.step_id,
61076
+ receipt.direction,
61077
+ receipt.idempotency_key,
61078
+ receipt.request_digest,
61079
+ receipt.outcome,
61080
+ receipt.target_memory_id,
61081
+ receipt.requested_project_id,
61082
+ receipt.expected_memory_version,
61083
+ receipt.expected_memory_revision,
61084
+ receipt.expected_project_revision,
61085
+ receipt.result_memory_version,
61086
+ receipt.result_memory_revision,
61087
+ receipt.result_memory_digest,
61088
+ receipt.result_project_revision,
61089
+ receipt.result_project_digest,
61090
+ receipt.accepted_receipt_id,
61091
+ canonicalJson(receipt.before_link),
61092
+ canonicalJson(receipt.after_link),
61093
+ receipt.before_project_revision,
61094
+ receipt.before_project_digest,
61095
+ receipt.after_project_revision,
61096
+ receipt.after_project_digest,
61097
+ receipt.created_at
61098
+ ]);
61099
+ }
61100
+ function makeReceipt(input) {
61101
+ const beforeLink = snapshot(input.beforeMemory);
61102
+ const afterLink = snapshot(input.afterMemory);
61103
+ const logical = {
61104
+ authority: "mementos",
61105
+ route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
61106
+ package_version: getMementosPackageVersion(),
61107
+ authority_id: input.request.authority_id,
61108
+ tenant_id: input.request.tenant_id,
61109
+ corpus_id: input.request.corpus_id,
61110
+ operation_id: input.request.operation_id,
61111
+ step_id: input.request.step_id,
61112
+ direction: input.direction,
61113
+ idempotency_key: input.request.idempotency_key,
61114
+ request_digest: input.requestDigest,
61115
+ outcome: input.outcome,
61116
+ target_memory_id: input.targetMemoryId,
61117
+ requested_project_id: input.requestedProjectId,
61118
+ expected_memory_version: input.request.expected_memory_version,
61119
+ expected_memory_revision: input.request.expected_memory_revision,
61120
+ expected_project_revision: input.expectedProjectRevision,
61121
+ result_memory_version: afterLink.memory_version,
61122
+ result_memory_revision: afterLink.memory_revision,
61123
+ result_memory_digest: afterLink.memory_digest,
61124
+ result_project_revision: input.afterProject?.updated_at ?? null,
61125
+ result_project_digest: input.afterProject ? projectDigest(input.afterProject) : null,
61126
+ accepted_receipt_id: input.acceptedReceiptId ?? null,
61127
+ before_link: beforeLink,
61128
+ after_link: afterLink,
61129
+ before_project_revision: input.beforeProject?.updated_at ?? null,
61130
+ before_project_digest: input.beforeProject ? projectDigest(input.beforeProject) : null,
61131
+ after_project_revision: input.afterProject?.updated_at ?? null,
61132
+ after_project_digest: input.afterProject ? projectDigest(input.afterProject) : null
61133
+ };
61134
+ return {
61135
+ receipt_id: `mmpl_${digest(logical).slice(0, 40)}`,
61136
+ ...logical,
61137
+ created_at: now()
61138
+ };
61139
+ }
61140
+ function sameSnapshot(memory, expected) {
61141
+ return canonicalJson(snapshot(memory)) === canonicalJson(expected);
61142
+ }
61143
+ function assertProjectSnapshot(project, revision, expectedDigest) {
61144
+ if (!revision && !expectedDigest) {
61145
+ if (project) {
61146
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "receipt expected no linked project but a project row was resolved");
61147
+ }
61148
+ return;
61149
+ }
61150
+ if (!project || project.updated_at !== revision || projectDigest(project) !== expectedDigest) {
61151
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_PROJECT", "Project no longer matches the immutable memory-link receipt", { expected_project_revision: revision, current_project_revision: project?.updated_at ?? null });
61152
+ }
61153
+ }
61154
+ function replayResult(db, receipt, requestDigest, memoryId) {
61155
+ if (receipt.request_digest !== requestDigest || receipt.target_memory_id !== memoryId) {
61156
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different memory project-link request");
61157
+ }
61158
+ const memory = getMemoryByExactId(memoryId, db);
61159
+ if (!memory || !sameSnapshot(memory, receipt.after_link)) {
61160
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "accepted memory project-link target drifted after its immutable receipt");
61161
+ }
61162
+ const project = receipt.after_link.project_id ? getProjectByExactId2(receipt.after_link.project_id, db) : null;
61163
+ assertProjectSnapshot(project, receipt.after_project_revision, receipt.after_project_digest);
61164
+ return {
61165
+ dry_run: false,
61166
+ applied: receipt.outcome === "accepted",
61167
+ no_change: receipt.outcome === "no_change",
61168
+ memory,
61169
+ project,
61170
+ receipt
61171
+ };
61172
+ }
61173
+ function previewMemoryProjectLink(memoryId, request, db) {
61174
+ assertForwardRequest(request);
61175
+ assertBoundedIdentifier2(memoryId, "memory_id");
61176
+ if (!db && isApiMode()) {
61177
+ const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: true });
61178
+ return data;
61179
+ }
61180
+ const d = db || getDatabase();
61181
+ const memory = getMemoryByExactId(memoryId, d);
61182
+ if (!memory) {
61183
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
61184
+ }
61185
+ const project = getProjectByExactId2(request.target_project_id, d);
61186
+ if (!project) {
61187
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${request.target_project_id}`);
61188
+ }
61189
+ assertMemoryPrecondition(memory, request.expected_memory_version, request.expected_memory_revision);
61190
+ assertProjectPrecondition(project, request.expected_project_revision);
61191
+ const noChange = memory.project_id === project.id;
61192
+ if (!noChange)
61193
+ assertNoTargetBucketCollision(memory, project.id, d);
61194
+ return {
61195
+ dry_run: true,
61196
+ applied: false,
61197
+ no_change: noChange,
61198
+ memory: noChange ? memory : { ...memory, project_id: project.id },
61199
+ project,
61200
+ receipt: null
61201
+ };
61202
+ }
61203
+ function applyMemoryProjectLink(memoryId, request, db) {
61204
+ assertForwardRequest(request);
61205
+ assertBoundedIdentifier2(memoryId, "memory_id");
61206
+ if (!db && isApiMode()) {
61207
+ const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: false });
61208
+ return data;
61209
+ }
61210
+ const d = db || getDatabase();
61211
+ const requestDigest = forwardRequestDigest(memoryId, request);
61212
+ return d.transaction(() => {
61213
+ const prior = findReceiptByKey(d, request, "forward", request.idempotency_key);
61214
+ if (prior)
61215
+ return replayResult(d, prior, requestDigest, memoryId);
61216
+ const before = getMemoryByExactId(memoryId, d, true);
61217
+ if (!before) {
61218
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
61219
+ }
61220
+ const committedPrior = findReceiptByKey(d, request, "forward", request.idempotency_key);
61221
+ if (committedPrior)
61222
+ return replayResult(d, committedPrior, requestDigest, memoryId);
61223
+ assertMemoryPrecondition(before, request.expected_memory_version, request.expected_memory_revision);
61224
+ const projects = lockProjects([before.project_id, request.target_project_id], d);
61225
+ const targetProject = projects.get(request.target_project_id);
61226
+ const beforeProject = before.project_id ? projects.get(before.project_id) : null;
61227
+ assertProjectPrecondition(targetProject, request.expected_project_revision);
61228
+ const noChange = before.project_id === request.target_project_id;
61229
+ if (noChange) {
61230
+ const receipt2 = makeReceipt({
61231
+ request,
61232
+ direction: "forward",
61233
+ requestDigest,
61234
+ outcome: "no_change",
61235
+ targetMemoryId: memoryId,
61236
+ requestedProjectId: request.target_project_id,
61237
+ expectedProjectRevision: request.expected_project_revision,
61238
+ beforeMemory: before,
61239
+ afterMemory: before,
61240
+ beforeProject,
61241
+ afterProject: targetProject
61242
+ });
61243
+ insertReceipt(d, receipt2);
61244
+ return {
61245
+ dry_run: false,
61246
+ applied: false,
61247
+ no_change: true,
61248
+ memory: before,
61249
+ project: targetProject,
61250
+ receipt: receipt2
61251
+ };
61252
+ }
61253
+ assertNoTargetBucketCollision(before, targetProject.id, d);
61254
+ const revision = nextRevision(before.updated_at);
61255
+ const result = d.run(`
61256
+ UPDATE memories
61257
+ SET project_id = ?, updated_at = ?
61258
+ WHERE id = ? AND version = ? AND updated_at = ?
61259
+ AND COALESCE(project_id, '') = COALESCE(?, '')
61260
+ `, [
61261
+ targetProject.id,
61262
+ revision,
61263
+ memoryId,
61264
+ before.version,
61265
+ before.updated_at,
61266
+ before.project_id
61267
+ ]);
61268
+ if (result.changes === 0) {
61269
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link compare-and-swap did not update exactly one row");
61270
+ }
61271
+ const after = getMemoryByExactId(memoryId, d);
61272
+ const expectedAfter = { ...before, project_id: targetProject.id, updated_at: revision };
61273
+ if (!after || canonicalJson(memoryMutationState(after)) !== canonicalJson(memoryMutationState(expectedAfter))) {
61274
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project link did not read back exactly under the stable ID");
61275
+ }
61276
+ const receipt = makeReceipt({
61277
+ request,
61278
+ direction: "forward",
61279
+ requestDigest,
61280
+ outcome: "accepted",
61281
+ targetMemoryId: memoryId,
61282
+ requestedProjectId: request.target_project_id,
61283
+ expectedProjectRevision: request.expected_project_revision,
61284
+ beforeMemory: before,
61285
+ afterMemory: after,
61286
+ beforeProject,
61287
+ afterProject: targetProject
61288
+ });
61289
+ insertReceipt(d, receipt);
61290
+ return {
61291
+ dry_run: false,
61292
+ applied: true,
61293
+ no_change: false,
61294
+ memory: after,
61295
+ project: targetProject,
61296
+ receipt
61297
+ };
61298
+ });
61299
+ }
61300
+ function rollbackMemoryProjectLink(memoryId, request, db) {
61301
+ assertRollbackRequest(request);
61302
+ assertBoundedIdentifier2(memoryId, "memory_id");
61303
+ if (!db && isApiMode()) {
61304
+ const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link-rollback`, request);
61305
+ return data;
61306
+ }
61307
+ const d = db || getDatabase();
61308
+ const requestDigest = rollbackRequestDigest(memoryId, request);
61309
+ return d.transaction(() => {
61310
+ const prior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
61311
+ if (prior)
61312
+ return replayResult(d, prior, requestDigest, memoryId);
61313
+ const accepted = findReceiptById(d, request, request.accepted_receipt_id);
61314
+ if (!accepted || accepted.direction !== "forward" || accepted.target_memory_id !== memoryId) {
61315
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "accepted forward link receipt was not found for this exact memory");
61316
+ }
61317
+ if (accepted.outcome !== "accepted") {
61318
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_ROLLBACKABLE", "a no-change memory project-link receipt has no mutation to roll back");
61319
+ }
61320
+ const current = getMemoryByExactId(memoryId, d, true);
61321
+ if (!current) {
61322
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
61323
+ }
61324
+ const committedPrior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
61325
+ if (committedPrior)
61326
+ return replayResult(d, committedPrior, requestDigest, memoryId);
61327
+ assertMemoryPrecondition(current, request.expected_memory_version, request.expected_memory_revision);
61328
+ if (!sameSnapshot(current, accepted.after_link)) {
61329
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory no longer matches the accepted forward link receipt");
61330
+ }
61331
+ const projects = lockProjects([accepted.after_link.project_id, accepted.before_link.project_id], d);
61332
+ const beforeProject = accepted.after_link.project_id ? projects.get(accepted.after_link.project_id) : null;
61333
+ const afterProject = accepted.before_link.project_id ? projects.get(accepted.before_link.project_id) : null;
61334
+ assertProjectSnapshot(beforeProject, accepted.after_project_revision, accepted.after_project_digest);
61335
+ assertProjectSnapshot(afterProject, accepted.before_project_revision, accepted.before_project_digest);
61336
+ assertNoTargetBucketCollision(current, accepted.before_link.project_id, d);
61337
+ const result = d.run(`
61338
+ UPDATE memories
61339
+ SET project_id = ?, updated_at = ?
61340
+ WHERE id = ? AND version = ? AND updated_at = ?
61341
+ AND COALESCE(project_id, '') = COALESCE(?, '')
61342
+ `, [
61343
+ accepted.before_link.project_id,
61344
+ accepted.before_link.memory_revision,
61345
+ memoryId,
61346
+ current.version,
61347
+ current.updated_at,
61348
+ current.project_id
61349
+ ]);
61350
+ if (result.changes === 0) {
61351
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link rollback compare-and-swap did not update exactly one row");
61352
+ }
61353
+ const restored = getMemoryByExactId(memoryId, d);
61354
+ if (!restored || !sameSnapshot(restored, accepted.before_link)) {
61355
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project-link rollback did not restore the exact prior linkage");
61356
+ }
61357
+ const receipt = makeReceipt({
61358
+ request,
61359
+ direction: "rollback",
61360
+ requestDigest,
61361
+ outcome: "accepted",
61362
+ targetMemoryId: memoryId,
61363
+ requestedProjectId: accepted.requested_project_id,
61364
+ expectedProjectRevision: null,
61365
+ beforeMemory: current,
61366
+ afterMemory: restored,
61367
+ beforeProject,
61368
+ afterProject,
61369
+ acceptedReceiptId: accepted.receipt_id
61370
+ });
61371
+ insertReceipt(d, receipt);
61372
+ return {
61373
+ dry_run: false,
61374
+ applied: true,
61375
+ no_change: false,
61376
+ memory: restored,
61377
+ project: afterProject,
61378
+ receipt
61379
+ };
61380
+ });
61381
+ }
61382
+ function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = LINK_AUTHORITY, db) {
61383
+ assertIdentity(identity);
61384
+ assertBoundedIdentifier2(memoryId, "memory_id");
61385
+ assertBoundedIdentifier2(receiptId, "receipt_id");
61386
+ if (!db && isApiMode()) {
61387
+ const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/project-link-receipts/lookup`, { ...identity, receipt_id: receiptId });
61388
+ return data;
61389
+ }
61390
+ const d = db || getDatabase();
61391
+ const receipt = findReceiptById(d, identity, receiptId);
61392
+ if (!receipt || receipt.target_memory_id !== memoryId) {
61393
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "immutable memory project-link receipt was not found for this exact memory");
61394
+ }
61395
+ return receipt;
61396
+ }
61397
+
61398
+ // src/cli/commands/memory-cmd-crud.ts
61399
+ init_projects();
61400
+ init_agents();
61401
+
59761
61402
  // src/lib/duration.ts
59762
61403
  var UNIT_MS = {
59763
61404
  s: 1000,
@@ -59807,6 +61448,92 @@ init_types();
59807
61448
  init_helpers();
59808
61449
  function registerCrudCommands(program2) {
59809
61450
  const handleError = makeHandleError(program2);
61451
+ program2.command("link-project <memory-id>").description("Guardedly link an existing memory to an existing project").option("--project-id <id>", "Exact stable ID of the existing target project").option("--expected-memory-version <n>", "Exact memory version required for compare-and-set", parseInt).option("--expected-memory-revision <revision>", "Exact memory updated_at revision required for compare-and-set").option("--expected-project-revision <revision>", "Exact target project updated_at revision required for compare-and-set").option("--idempotency-key <key>", "Caller-owned key for one guarded link or rollback").option("--operation-id <id>", "Operation identifier (defaults to the idempotency key)").option("--step-id <id>", "Step identifier").option("--dry-run", "Validate and preview the guarded link without writing").option("--rollback-receipt <id>", "Restore the exact prior linkage from an accepted receipt").option("--lookup-receipt <id>", "Read one immutable receipt for this exact memory").action((memoryId, opts) => {
61452
+ try {
61453
+ const globalOpts = program2.opts();
61454
+ const rollbackReceipt = opts.rollbackReceipt;
61455
+ const lookupReceipt = opts.lookupReceipt;
61456
+ const projectId = opts.projectId;
61457
+ if (rollbackReceipt && lookupReceipt) {
61458
+ throw new Error("--rollback-receipt and --lookup-receipt cannot be used together");
61459
+ }
61460
+ if (lookupReceipt) {
61461
+ if (opts.dryRun || projectId || rollbackReceipt) {
61462
+ throw new Error("Receipt lookup cannot be combined with link, rollback, or dry-run options");
61463
+ }
61464
+ const receipt = getMemoryProjectLinkReceipt(memoryId, lookupReceipt, {
61465
+ authority_id: "mementos",
61466
+ tenant_id: "default",
61467
+ corpus_id: "default"
61468
+ });
61469
+ if (globalOpts.json)
61470
+ outputJson(receipt);
61471
+ else {
61472
+ console.log(chalk2.green("Memory project-link receipt:"));
61473
+ console.log(` ${chalk2.bold("Receipt:")} ${receipt.receipt_id}`);
61474
+ console.log(` ${chalk2.bold("Memory:")} ${receipt.target_memory_id}`);
61475
+ console.log(` ${chalk2.bold("Before:")} ${receipt.before_link.project_id ?? "none"}`);
61476
+ console.log(` ${chalk2.bold("After:")} ${receipt.after_link.project_id ?? "none"}`);
61477
+ }
61478
+ return;
61479
+ }
61480
+ const expectedMemoryVersion = opts.expectedMemoryVersion;
61481
+ const expectedMemoryRevision = opts.expectedMemoryRevision;
61482
+ const idempotencyKey = opts.idempotencyKey;
61483
+ if (!expectedMemoryVersion || !expectedMemoryRevision || !idempotencyKey) {
61484
+ throw new Error("--expected-memory-version, --expected-memory-revision, and --idempotency-key are required");
61485
+ }
61486
+ if (opts.dryRun && rollbackReceipt) {
61487
+ throw new Error("Dry-run memory project-link rollback is not supported");
61488
+ }
61489
+ if (projectId && rollbackReceipt) {
61490
+ throw new Error("--project-id and --rollback-receipt cannot be used together");
61491
+ }
61492
+ const common = {
61493
+ authority_id: "mementos",
61494
+ tenant_id: "default",
61495
+ corpus_id: "default",
61496
+ operation_id: opts.operationId ?? idempotencyKey,
61497
+ step_id: opts.stepId ?? (rollbackReceipt ? "mementos_memory_project_link_rollback" : "mementos_memory_project_link"),
61498
+ idempotency_key: idempotencyKey,
61499
+ expected_memory_version: expectedMemoryVersion,
61500
+ expected_memory_revision: expectedMemoryRevision
61501
+ };
61502
+ let result;
61503
+ if (rollbackReceipt) {
61504
+ result = rollbackMemoryProjectLink(memoryId, {
61505
+ ...common,
61506
+ accepted_receipt_id: rollbackReceipt
61507
+ });
61508
+ } else {
61509
+ const expectedProjectRevision = opts.expectedProjectRevision;
61510
+ if (!projectId || !expectedProjectRevision) {
61511
+ throw new Error("--project-id and --expected-project-revision are required for a memory project link");
61512
+ }
61513
+ const request = {
61514
+ ...common,
61515
+ target_project_id: projectId,
61516
+ expected_project_revision: expectedProjectRevision
61517
+ };
61518
+ result = opts.dryRun ? previewMemoryProjectLink(memoryId, request) : applyMemoryProjectLink(memoryId, request);
61519
+ }
61520
+ if (globalOpts.json) {
61521
+ outputJson(result);
61522
+ } else {
61523
+ const label = result.dry_run ? "Memory project-link preview:" : result.no_change ? "Memory already linked:" : "Memory project linkage updated:";
61524
+ console.log(chalk2.green(label));
61525
+ console.log(` ${chalk2.bold("Memory:")} ${result.memory.id}`);
61526
+ console.log(` ${chalk2.bold("Project:")} ${result.memory.project_id ?? "none"}`);
61527
+ console.log(` ${chalk2.bold("Version:")} ${result.memory.version}`);
61528
+ console.log(` ${chalk2.bold("Revision:")} ${result.memory.updated_at}`);
61529
+ if (result.receipt) {
61530
+ console.log(` ${chalk2.bold("Receipt:")} ${result.receipt.receipt_id}`);
61531
+ }
61532
+ }
61533
+ } catch (error) {
61534
+ handleError(error);
61535
+ }
61536
+ });
59810
61537
  program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>", `Category: ${MEMORY_CATEGORIES.join(", ")}`).option("--scope <scope>", `Scope: ${MEMORY_SCOPES.join(", ")}`).option("--importance <n>", "Importance 1-10", parseInt).option("--tags <tags>", "Comma-separated tags").option("--summary <text>", "Brief summary").option("--ttl <duration>", "Time-to-live: 30s, 5m, 2h, 1d, 1w, or milliseconds").option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").option("--dedupe <mode>", "Conflict handling: merge (default, upsert the matching row), create (fork a new row under the same key), error").action((key, value, opts) => {
59811
61538
  try {
59812
61539
  const globalOpts = program2.opts();
@@ -60205,7 +61932,7 @@ function registerTailCommand(program2) {
60205
61932
  try {
60206
61933
  const globalOpts = program2.opts();
60207
61934
  const jsonMode = !!globalOpts.json;
60208
- const agentId = opts.agent || globalOpts.agent;
61935
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60209
61936
  const projectPath = opts.project || globalOpts.project;
60210
61937
  let projectId;
60211
61938
  if (projectPath) {
@@ -60361,14 +62088,7 @@ function registerSearchCommand(program2) {
60361
62088
  if (project)
60362
62089
  projectId = project.id;
60363
62090
  }
60364
- const agentName = opts.agent || globalOpts.agent;
60365
- let agentId;
60366
- if (agentName) {
60367
- const { getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
60368
- const agent = getAgent2(agentName);
60369
- if (agent)
60370
- agentId = agent.id;
60371
- }
62091
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60372
62092
  const filter = {
60373
62093
  scope: opts.scope,
60374
62094
  category: opts.category,
@@ -60642,7 +62362,7 @@ function registerRecallCommand(program2) {
60642
62362
  program2.command("recall <key>").alias("get").description("Recall a memory by exact key (use --fuzzy to fall back to the nearest match)").option("--scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--fuzzy", "If the exact key is absent, return the nearest match instead (exits 2)").action((key, opts) => {
60643
62363
  try {
60644
62364
  const globalOpts = program2.opts();
60645
- const agentId = opts.agent || globalOpts.agent;
62365
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60646
62366
  const projectPath = opts.project || globalOpts.project;
60647
62367
  let projectId;
60648
62368
  if (projectPath) {
@@ -60715,7 +62435,7 @@ function registerListCommand(program2) {
60715
62435
  const requestedLimit = opts.limit;
60716
62436
  const limit = positiveIntOrDefault(requestedLimit, isStructured ? 50 : DEFAULT_COMPACT_LIMIT);
60717
62437
  const offset = cursorOrOffset(opts.cursor, opts.offset);
60718
- const agentId = opts.agent || globalOpts.agent;
62438
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60719
62439
  const projectPath = opts.project || globalOpts.project;
60720
62440
  let projectId;
60721
62441
  if (projectPath) {
@@ -61080,7 +62800,6 @@ mementos report \u2014 last ${days} days
61080
62800
  import chalk15 from "chalk";
61081
62801
  import { resolve as resolve9 } from "path";
61082
62802
  init_projects();
61083
- init_agents();
61084
62803
  init_helpers();
61085
62804
  function registerStaleCommand(program2) {
61086
62805
  const handleError = makeHandleError(program2);
@@ -61099,13 +62818,7 @@ function registerStaleCommand(program2) {
61099
62818
  if (project)
61100
62819
  projectId = project.id;
61101
62820
  }
61102
- const agentName = opts.agent || globalOpts.agent;
61103
- let agentId;
61104
- if (agentName) {
61105
- const agent = getAgent(agentName);
61106
- if (agent)
61107
- agentId = agent.id;
61108
- }
62821
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61109
62822
  const rows = getStaleMemories({
61110
62823
  days,
61111
62824
  project_id: projectId,
@@ -61234,7 +62947,7 @@ function registerContextCommand(program2) {
61234
62947
  const scope = opts.scope;
61235
62948
  const categoriesRaw = opts.categories;
61236
62949
  const categories = categoriesRaw ? categoriesRaw.split(",").map((c) => c.trim()) : undefined;
61237
- const agentId = opts.agent || globalOpts.agent;
62950
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61238
62951
  const projectPath = opts.project || globalOpts.project;
61239
62952
  const visibleMachineId = resolveVisibleMachineId(opts.machine);
61240
62953
  let projectId;
@@ -61359,7 +63072,7 @@ function registerExportCommand(program2) {
61359
63072
  program2.command("export").description("Export memories as JSON").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((opts) => {
61360
63073
  try {
61361
63074
  const globalOpts = program2.opts();
61362
- const agentId = opts.agent || globalOpts.agent;
63075
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61363
63076
  const projectPath = opts.project || globalOpts.project;
61364
63077
  let projectId;
61365
63078
  if (projectPath) {
@@ -61387,7 +63100,7 @@ init_memories();
61387
63100
  init_helpers();
61388
63101
  import chalk18 from "chalk";
61389
63102
  import { resolve as resolve12 } from "path";
61390
- import { readFileSync as readFileSync3 } from "fs";
63103
+ import { readFileSync as readFileSync4 } from "fs";
61391
63104
  function registerImportCommand(program2) {
61392
63105
  const handleError = makeHandleError(program2);
61393
63106
  program2.command("import [file]").description("Import memories from a JSON file or stdin (use '-' or pipe data)").option("--overwrite", "Overwrite existing memories (default: merge)").action(async (file, opts) => {
@@ -61397,7 +63110,7 @@ function registerImportCommand(program2) {
61397
63110
  if (file === "-" || !file && !process.stdin.isTTY) {
61398
63111
  raw = await Bun.stdin.text();
61399
63112
  } else if (file) {
61400
- raw = readFileSync3(resolve12(file), "utf-8");
63113
+ raw = readFileSync4(resolve12(file), "utf-8");
61401
63114
  } else {
61402
63115
  console.error(chalk18.red("No input: provide a file path, use '-' for stdin, or pipe data."));
61403
63116
  process.exit(1);
@@ -61427,9 +63140,9 @@ function registerImportCommand(program2) {
61427
63140
  import chalk19 from "chalk";
61428
63141
 
61429
63142
  // src/lib/config.ts
61430
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
63143
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
61431
63144
  import { homedir as homedir3 } from "os";
61432
- import { basename, dirname as dirname3, join as join6, resolve as resolve13 } from "path";
63145
+ import { basename, dirname as dirname4, join as join7, resolve as resolve13 } from "path";
61433
63146
  function isInMemoryDb2(path) {
61434
63147
  return path === ":memory:" || path.startsWith("file::memory:");
61435
63148
  }
@@ -61492,11 +63205,11 @@ function isValidCategory(value) {
61492
63205
  return VALID_CATEGORIES2.includes(value);
61493
63206
  }
61494
63207
  function loadConfig() {
61495
- const configPath = join6(homeDir(), ".hasna", "mementos", "config.json");
63208
+ const configPath = join7(homeDir(), ".hasna", "mementos", "config.json");
61496
63209
  let fileConfig = {};
61497
63210
  if (existsSync4(configPath)) {
61498
63211
  try {
61499
- const raw = readFileSync4(configPath, "utf-8");
63212
+ const raw = readFileSync5(configPath, "utf-8");
61500
63213
  fileConfig = JSON.parse(raw);
61501
63214
  } catch {}
61502
63215
  }
@@ -61522,11 +63235,11 @@ function findFileWalkingUp(filename) {
61522
63235
  let dir = process.cwd();
61523
63236
  const legacyHomeMementosDb = resolve13(homeDir(), ".mementos", "mementos.db");
61524
63237
  while (true) {
61525
- const candidate = join6(dir, filename);
63238
+ const candidate = join7(dir, filename);
61526
63239
  if (existsSync4(candidate) && resolve13(candidate) !== legacyHomeMementosDb) {
61527
63240
  return candidate;
61528
63241
  }
61529
- const parent = dirname3(dir);
63242
+ const parent = dirname4(dir);
61530
63243
  if (parent === dir) {
61531
63244
  return null;
61532
63245
  }
@@ -61536,10 +63249,10 @@ function findFileWalkingUp(filename) {
61536
63249
  function findGitRoot2() {
61537
63250
  let dir = process.cwd();
61538
63251
  while (true) {
61539
- if (existsSync4(join6(dir, ".git"))) {
63252
+ if (existsSync4(join7(dir, ".git"))) {
61540
63253
  return dir;
61541
63254
  }
61542
- const parent = dirname3(dir);
63255
+ const parent = dirname4(dir);
61543
63256
  if (parent === dir) {
61544
63257
  return null;
61545
63258
  }
@@ -61547,17 +63260,17 @@ function findGitRoot2() {
61547
63260
  }
61548
63261
  }
61549
63262
  function profilesDir() {
61550
- return join6(homeDir(), ".hasna", "mementos", "profiles");
63263
+ return join7(homeDir(), ".hasna", "mementos", "profiles");
61551
63264
  }
61552
63265
  function globalConfigPath() {
61553
- return join6(homeDir(), ".hasna", "mementos", "config.json");
63266
+ return join7(homeDir(), ".hasna", "mementos", "config.json");
61554
63267
  }
61555
63268
  function readGlobalConfig() {
61556
63269
  const p = globalConfigPath();
61557
63270
  if (!existsSync4(p))
61558
63271
  return {};
61559
63272
  try {
61560
- return JSON.parse(readFileSync4(p, "utf-8"));
63273
+ return JSON.parse(readFileSync5(p, "utf-8"));
61561
63274
  } catch {
61562
63275
  return {};
61563
63276
  }
@@ -61567,7 +63280,7 @@ function readGlobalConfigForWrite() {
61567
63280
  if (!existsSync4(p))
61568
63281
  return {};
61569
63282
  try {
61570
- const data = JSON.parse(readFileSync4(p, "utf-8"));
63283
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
61571
63284
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
61572
63285
  throw new Error("expected a JSON object");
61573
63286
  }
@@ -61579,7 +63292,7 @@ function readGlobalConfigForWrite() {
61579
63292
  }
61580
63293
  function writeGlobalConfig(data) {
61581
63294
  const p = globalConfigPath();
61582
- ensureDir2(dirname3(p));
63295
+ ensureDir2(dirname4(p));
61583
63296
  writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
61584
63297
  }
61585
63298
  function getActiveProfile() {
@@ -61605,7 +63318,7 @@ function listProfiles() {
61605
63318
  return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
61606
63319
  }
61607
63320
  function deleteProfile(name) {
61608
- const dbPath = join6(profilesDir(), `${name}.db`);
63321
+ const dbPath = join7(profilesDir(), `${name}.db`);
61609
63322
  if (!existsSync4(dbPath))
61610
63323
  return false;
61611
63324
  unlinkSync2(dbPath);
@@ -61615,10 +63328,10 @@ function deleteProfile(name) {
61615
63328
  }
61616
63329
  function getDbPath2() {
61617
63330
  const _home = homeDir();
61618
- const _newDir = join6(_home, ".hasna", "mementos");
61619
- const _oldDir = join6(_home, ".mementos");
63331
+ const _newDir = join7(_home, ".hasna", "mementos");
63332
+ const _oldDir = join7(_home, ".mementos");
61620
63333
  if (!existsSync4(_newDir) && existsSync4(_oldDir)) {
61621
- mkdirSync3(join6(_home, ".hasna"), { recursive: true });
63334
+ mkdirSync3(join7(_home, ".hasna"), { recursive: true });
61622
63335
  cpSync2(_oldDir, _newDir, { recursive: true });
61623
63336
  }
61624
63337
  const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
@@ -61627,30 +63340,30 @@ function getDbPath2() {
61627
63340
  return envDbPath;
61628
63341
  }
61629
63342
  const resolved = resolve13(envDbPath);
61630
- ensureDir2(dirname3(resolved));
63343
+ ensureDir2(dirname4(resolved));
61631
63344
  return resolved;
61632
63345
  }
61633
63346
  const profile = getActiveProfile();
61634
63347
  if (profile) {
61635
- const profilePath = join6(profilesDir(), `${profile}.db`);
61636
- ensureDir2(dirname3(profilePath));
63348
+ const profilePath = join7(profilesDir(), `${profile}.db`);
63349
+ ensureDir2(dirname4(profilePath));
61637
63350
  return profilePath;
61638
63351
  }
61639
63352
  const dbScope = process.env["MEMENTOS_DB_SCOPE"];
61640
63353
  if (dbScope === "project") {
61641
63354
  const gitRoot = findGitRoot2();
61642
63355
  if (gitRoot) {
61643
- const dbPath = join6(gitRoot, ".mementos", "mementos.db");
61644
- ensureDir2(dirname3(dbPath));
63356
+ const dbPath = join7(gitRoot, ".mementos", "mementos.db");
63357
+ ensureDir2(dirname4(dbPath));
61645
63358
  return dbPath;
61646
63359
  }
61647
63360
  }
61648
- const found = findFileWalkingUp(join6(".mementos", "mementos.db"));
63361
+ const found = findFileWalkingUp(join7(".mementos", "mementos.db"));
61649
63362
  if (found) {
61650
63363
  return found;
61651
63364
  }
61652
- const fallback = join6(homeDir(), ".hasna", "mementos", "mementos.db");
61653
- ensureDir2(dirname3(fallback));
63365
+ const fallback = join7(homeDir(), ".hasna", "mementos", "mementos.db");
63366
+ ensureDir2(dirname4(fallback));
61654
63367
  return fallback;
61655
63368
  }
61656
63369
  function ensureDir2(dir) {
@@ -61771,7 +63484,7 @@ function registerCleanCommand(program2) {
61771
63484
  init_database();
61772
63485
  init_helpers();
61773
63486
  import chalk20 from "chalk";
61774
- import { resolve as resolve14, dirname as dirname4 } from "path";
63487
+ import { resolve as resolve14, dirname as dirname5 } from "path";
61775
63488
  import { existsSync as existsSync5, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
61776
63489
  function registerBackupCommand(program2) {
61777
63490
  const handleError = makeHandleError(program2);
@@ -61838,7 +63551,7 @@ function registerBackupCommand(program2) {
61838
63551
  const ts = now3.toISOString().replace(/[-:T]/g, "").replace(/\..+/, "").slice(0, 15);
61839
63552
  dest = resolve14(backupsDir, `mementos-${ts}.db`);
61840
63553
  }
61841
- const destDir = dirname4(dest);
63554
+ const destDir = dirname5(dest);
61842
63555
  if (!existsSync5(destDir)) {
61843
63556
  mkdirSync4(destDir, { recursive: true });
61844
63557
  }
@@ -61920,8 +63633,8 @@ function registerRestoreCommand(program2) {
61920
63633
  }
61921
63634
  let backupCount = 0;
61922
63635
  try {
61923
- const { Database: Database2 } = __require("bun:sqlite");
61924
- const backupDb = new Database2(source, { readonly: true });
63636
+ const { Database: Database3 } = __require("bun:sqlite");
63637
+ const backupDb = new Database3(source, { readonly: true });
61925
63638
  const row = backupDb.query("SELECT COUNT(*) as count FROM memories").get();
61926
63639
  backupCount = row?.count ?? 0;
61927
63640
  backupDb.close();
@@ -62050,14 +63763,17 @@ function registerAgentCommands(program2) {
62050
63763
  program2.command("agents").description("List all registered agents").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action((opts) => {
62051
63764
  try {
62052
63765
  const globalOpts = program2.opts();
62053
- const allAgents = listAgents();
62054
63766
  const limit = positiveIntOrDefault(opts.limit, DEFAULT_COMPACT_LIMIT);
62055
63767
  const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
62056
- const agents = globalOpts.json ? allAgents : allAgents.slice(offset, offset + limit + 1);
63768
+ const explicitPagination = opts.limit !== undefined || opts.cursor !== undefined || opts.offset !== undefined;
63769
+ const agents = listAgents({
63770
+ limit: globalOpts.json ? explicitPagination ? limit : undefined : limit + 1,
63771
+ offset
63772
+ });
62057
63773
  const hasMore = !globalOpts.json && agents.length > limit;
62058
63774
  const displayAgents = hasMore ? agents.slice(0, limit) : agents;
62059
63775
  if (globalOpts.json) {
62060
- outputJson(allAgents);
63776
+ outputJson(agents);
62061
63777
  return;
62062
63778
  }
62063
63779
  if (displayAgents.length === 0) {
@@ -62194,9 +63910,13 @@ import { resolve as resolve16 } from "path";
62194
63910
  init_helpers();
62195
63911
  function registerProjectCommands(program2) {
62196
63912
  const handleError = makeHandleError(program2);
62197
- program2.command("projects").description("Manage projects").option("--add", "Add a new project").option("--name <name>", "Project name").option("--path <path>", "Project path").option("--description <text>", "Project description").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action((opts) => {
63913
+ program2.command("projects").description("Manage projects").option("--add", "Add a new project").option("--update <id>", "Update a project by its exact stable ID").option("--name <name>", "Project name").option("--path <path>", "Project path").option("--description <text>", "Project description").option("--memory-prefix <prefix>", "Project memory prefix").option("--expected-revision <revision>", "Exact updated_at revision required for compare-and-swap").option("--idempotency-key <key>", "Caller-owned key for one guarded mutation").option("--operation-id <id>", "Operation identifier (defaults to the idempotency key)").option("--step-id <id>", "Step identifier (defaults to mementos_project_update)").option("--dry-run", "Validate and preview the guarded update without writing").option("--rollback-receipt <id>", "Restore the exact before snapshot from an accepted update receipt").option("--limit <n>", "Max results (compact default: 20)", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action((opts) => {
62198
63914
  try {
62199
63915
  const globalOpts = program2.opts();
63916
+ if (opts.add && opts.update) {
63917
+ console.error(chalk23.red("--add and --update cannot be used together"));
63918
+ process.exit(1);
63919
+ }
62200
63920
  if (opts.add) {
62201
63921
  const name = opts.name;
62202
63922
  const path = opts.path;
@@ -62215,6 +63935,64 @@ function registerProjectCommands(program2) {
62215
63935
  }
62216
63936
  return;
62217
63937
  }
63938
+ if (opts.update) {
63939
+ const updates = {};
63940
+ if (opts.name !== undefined)
63941
+ updates.name = opts.name;
63942
+ if (opts.path !== undefined)
63943
+ updates.path = resolve16(opts.path);
63944
+ if (opts.description !== undefined) {
63945
+ updates.description = opts.description;
63946
+ }
63947
+ if (opts.memoryPrefix !== undefined) {
63948
+ updates.memory_prefix = opts.memoryPrefix;
63949
+ }
63950
+ const rollbackReceipt = opts.rollbackReceipt;
63951
+ if (Object.keys(updates).length === 0 && !rollbackReceipt) {
63952
+ console.error(chalk23.red("At least one of --name, --path, --description, or --memory-prefix is required when updating"));
63953
+ process.exit(1);
63954
+ }
63955
+ if (Object.keys(updates).length > 0 && rollbackReceipt) {
63956
+ console.error(chalk23.red("Project fields and --rollback-receipt cannot be used together"));
63957
+ process.exit(1);
63958
+ }
63959
+ if (opts.dryRun && rollbackReceipt) {
63960
+ console.error(chalk23.red("Dry-run project rollback is not supported"));
63961
+ process.exit(1);
63962
+ }
63963
+ const expectedRevision = opts.expectedRevision;
63964
+ const idempotencyKey = opts.idempotencyKey;
63965
+ if (!expectedRevision || !idempotencyKey) {
63966
+ console.error(chalk23.red("--expected-revision and --idempotency-key are required for every project update"));
63967
+ process.exit(1);
63968
+ }
63969
+ const common = {
63970
+ authority_id: "mementos",
63971
+ tenant_id: "default",
63972
+ corpus_id: "default",
63973
+ operation_id: opts.operationId ?? idempotencyKey,
63974
+ step_id: opts.stepId ?? (rollbackReceipt ? "mementos_project_rollback" : "mementos_project_update"),
63975
+ idempotency_key: idempotencyKey,
63976
+ expected_revision: expectedRevision
63977
+ };
63978
+ const result = rollbackReceipt ? rollbackProjectUpdate(opts.update, {
63979
+ ...common,
63980
+ accepted_receipt_id: rollbackReceipt
63981
+ }) : opts.dryRun ? previewProjectUpdate(opts.update, { ...common, updates }) : applyProjectUpdate(opts.update, { ...common, updates });
63982
+ if (globalOpts.json) {
63983
+ outputJson(result);
63984
+ } else {
63985
+ console.log(chalk23.green(result.dry_run ? "Project update preview:" : "Project updated:"));
63986
+ console.log(` ${chalk23.bold("ID:")} ${result.project.id}`);
63987
+ console.log(` ${chalk23.bold("Name:")} ${result.project.name}`);
63988
+ console.log(` ${chalk23.bold("Path:")} ${result.project.path}`);
63989
+ console.log(` ${chalk23.bold("Revision:")} ${result.project.updated_at}`);
63990
+ if (result.receipt) {
63991
+ console.log(` ${chalk23.bold("Receipt:")} ${result.receipt.receipt_id}`);
63992
+ }
63993
+ }
63994
+ return;
63995
+ }
62218
63996
  const allProjects = listProjects();
62219
63997
  const limit = positiveIntOrDefault(opts.limit, DEFAULT_COMPACT_LIMIT);
62220
63998
  const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
@@ -63053,10 +64831,10 @@ init_memories();
63053
64831
  init_agents();
63054
64832
  init_projects();
63055
64833
  import chalk27 from "chalk";
63056
- import { join as join7 } from "path";
64834
+ import { join as join8 } from "path";
63057
64835
  import { homedir as homedir4 } from "os";
63058
64836
  import {
63059
- readFileSync as readFileSync5,
64837
+ readFileSync as readFileSync6,
63060
64838
  existsSync as existsSync7,
63061
64839
  accessSync,
63062
64840
  statSync as statSync3,
@@ -63244,9 +65022,9 @@ function registerDoctorCommand(program2) {
63244
65022
  checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
63245
65023
  }
63246
65024
  try {
63247
- const settingsFilePath = join7(homedir4(), ".claude", "settings.json");
65025
+ const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
63248
65026
  if (existsSync7(settingsFilePath)) {
63249
- const settings = JSON.parse(readFileSync5(settingsFilePath, "utf-8"));
65027
+ const settings = JSON.parse(readFileSync6(settingsFilePath, "utf-8"));
63250
65028
  const hooksObj = settings["hooks"] || {};
63251
65029
  const stopHooks = hooksObj["Stop"] || [];
63252
65030
  const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
@@ -63262,7 +65040,7 @@ function registerDoctorCommand(program2) {
63262
65040
  checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
63263
65041
  }
63264
65042
  if (process.platform === "darwin") {
63265
- const plistFilePath = join7(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
65043
+ const plistFilePath = join8(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
63266
65044
  checks.push({
63267
65045
  name: "Auto-start",
63268
65046
  status: existsSync7(plistFilePath) ? "ok" : "warn",
@@ -63345,9 +65123,9 @@ async function runCloudDoctor(globalOpts, checks) {
63345
65123
  checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
63346
65124
  }
63347
65125
  try {
63348
- const settingsFilePath = join7(homedir4(), ".claude", "settings.json");
65126
+ const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
63349
65127
  if (existsSync7(settingsFilePath)) {
63350
- const settings = JSON.parse(readFileSync5(settingsFilePath, "utf-8"));
65128
+ const settings = JSON.parse(readFileSync6(settingsFilePath, "utf-8"));
63351
65129
  const hooksObj = settings["hooks"] || {};
63352
65130
  const stopHooks = hooksObj["Stop"] || [];
63353
65131
  const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
@@ -63363,7 +65141,7 @@ async function runCloudDoctor(globalOpts, checks) {
63363
65141
  checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
63364
65142
  }
63365
65143
  if (process.platform === "darwin") {
63366
- const plistFilePath = join7(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
65144
+ const plistFilePath = join8(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
63367
65145
  checks.push({
63368
65146
  name: "Auto-start",
63369
65147
  status: existsSync7(plistFilePath) ? "ok" : "warn",
@@ -64480,7 +66258,7 @@ function registerWatchCommand(program2) {
64480
66258
  program2.command("watch").description("Watch for new and changed memories in real-time").option("--scope <scope>", "Scope filter: global, shared, private, working").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history, procedural, resource").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds", parseInt).action((opts) => {
64481
66259
  try {
64482
66260
  const globalOpts = program2.opts();
64483
- const agentId = opts.agent || globalOpts.agent;
66261
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
64484
66262
  const projectPath = opts.project || globalOpts.project;
64485
66263
  let projectId;
64486
66264
  if (projectPath) {
@@ -65354,15 +67132,15 @@ function registerStorageCommands(program2) {
65354
67132
  // src/cli/commands/init.ts
65355
67133
  import chalk41 from "chalk";
65356
67134
  import {
65357
- readFileSync as readFileSync6,
67135
+ readFileSync as readFileSync7,
65358
67136
  writeFileSync as writeFileSync4,
65359
67137
  existsSync as existsSync9,
65360
67138
  copyFileSync as copyFileSync3,
65361
67139
  mkdirSync as mkdirSync6
65362
67140
  } from "fs";
65363
- import { dirname as dirname6, join as join9 } from "path";
67141
+ import { dirname as dirname7, join as join10 } from "path";
65364
67142
  import { homedir as homedir5 } from "os";
65365
- import { fileURLToPath as fileURLToPath3 } from "url";
67143
+ import { fileURLToPath as fileURLToPath4 } from "url";
65366
67144
  function registerInitCommand(program2) {
65367
67145
  program2.command("init").description("One-command setup: register MCP, install stop hook, configure auto-start").action(async () => {
65368
67146
  const { platform: platform2 } = process;
@@ -65421,9 +67199,9 @@ function registerInitCommand(program2) {
65421
67199
  } else {
65422
67200
  console.log(chalk41.green(" \u2713 MCP server registered with Claude Code"));
65423
67201
  }
65424
- const hooksDir = join9(home, ".claude", "hooks");
65425
- const hookDest = join9(hooksDir, "mementos-stop-hook.ts");
65426
- const settingsPath = join9(home, ".claude", "settings.json");
67202
+ const hooksDir = join10(home, ".claude", "hooks");
67203
+ const hookDest = join10(hooksDir, "mementos-stop-hook.ts");
67204
+ const settingsPath = join10(home, ".claude", "settings.json");
65427
67205
  const hookCommand = `bun ${hookDest}`;
65428
67206
  let hookAlreadyInstalled = false;
65429
67207
  let hookError = null;
@@ -65431,7 +67209,7 @@ function registerInitCommand(program2) {
65431
67209
  let settings = {};
65432
67210
  if (existsSync9(settingsPath)) {
65433
67211
  try {
65434
- settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
67212
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
65435
67213
  } catch {
65436
67214
  settings = {};
65437
67215
  }
@@ -65446,11 +67224,11 @@ function registerInitCommand(program2) {
65446
67224
  mkdirSync6(hooksDir, { recursive: true });
65447
67225
  }
65448
67226
  if (!existsSync9(hookDest)) {
65449
- const packageDir = dirname6(dirname6(fileURLToPath3(import.meta.url)));
67227
+ const packageDir = dirname7(dirname7(fileURLToPath4(import.meta.url)));
65450
67228
  const candidatePaths = [
65451
- join9(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
65452
- join9(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
65453
- join9(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
67229
+ join10(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
67230
+ join10(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
67231
+ join10(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
65454
67232
  ];
65455
67233
  let hookSourceFound = false;
65456
67234
  for (const src of candidatePaths) {
@@ -65520,7 +67298,7 @@ main().catch(() => {});
65520
67298
  if (!isMac) {
65521
67299
  console.log(chalk41.dim(` \xB7 Auto-start skipped (not macOS \u2014 platform: ${platform2})`));
65522
67300
  } else {
65523
- const plistPath = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
67301
+ const plistPath = join10(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
65524
67302
  const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
65525
67303
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65526
67304
  <plist version="1.0">
@@ -65548,7 +67326,7 @@ main().catch(() => {});
65548
67326
  if (existsSync9(plistPath)) {
65549
67327
  autoStartAlreadyInstalled = true;
65550
67328
  } else {
65551
- const launchAgentsDir = join9(home, "Library", "LaunchAgents");
67329
+ const launchAgentsDir = join10(home, "Library", "LaunchAgents");
65552
67330
  if (!existsSync9(launchAgentsDir)) {
65553
67331
  mkdirSync6(launchAgentsDir, { recursive: true });
65554
67332
  }
@@ -65565,7 +67343,7 @@ main().catch(() => {});
65565
67343
  console.log(chalk41.green(" \u2713 Auto-start configured (starts on login)"));
65566
67344
  }
65567
67345
  if (!autoStartAlreadyInstalled && !autoStartError) {
65568
- const plistPath2 = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
67346
+ const plistPath2 = join10(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
65569
67347
  const loadResult = await run(["launchctl", "load", plistPath2]);
65570
67348
  if (!loadResult.ok) {
65571
67349
  console.log(chalk41.dim(` \xB7 launchctl load: ${loadResult.output || "already loaded"}`));
@@ -66685,7 +68463,7 @@ import {
66685
68463
  readdirSync as readdirSync4
66686
68464
  } from "fs";
66687
68465
  import { homedir as homedir7 } from "os";
66688
- import { join as join11 } from "path";
68466
+ import { join as join12 } from "path";
66689
68467
  import chalk43 from "chalk";
66690
68468
 
66691
68469
  // src/lib/gatherer.ts
@@ -66762,17 +68540,17 @@ var gatherTrainingData = async (options = {}) => {
66762
68540
  };
66763
68541
 
66764
68542
  // src/lib/model-config.ts
66765
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
68543
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
66766
68544
  import { homedir as homedir6 } from "os";
66767
- import { join as join10 } from "path";
68545
+ import { join as join11 } from "path";
66768
68546
  var DEFAULT_MODEL = "gpt-4o-mini";
66769
- var CONFIG_DIR = join10(homedir6(), ".hasna", "mementos");
66770
- var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
68547
+ var CONFIG_DIR = join11(homedir6(), ".hasna", "mementos");
68548
+ var CONFIG_PATH = join11(CONFIG_DIR, "config.json");
66771
68549
  function readConfig() {
66772
68550
  if (!existsSync10(CONFIG_PATH))
66773
68551
  return {};
66774
68552
  try {
66775
- const raw = readFileSync7(CONFIG_PATH, "utf-8");
68553
+ const raw = readFileSync8(CONFIG_PATH, "utf-8");
66776
68554
  return JSON.parse(raw);
66777
68555
  } catch {
66778
68556
  return {};
@@ -66827,12 +68605,12 @@ function makeBrainsCommand() {
66827
68605
  limit: opts.limit,
66828
68606
  since
66829
68607
  });
66830
- const outputDir = opts.output ?? join11(homedir7(), ".hasna", "mementos", "training");
68608
+ const outputDir = opts.output ?? join12(homedir7(), ".hasna", "mementos", "training");
66831
68609
  if (!existsSync11(outputDir)) {
66832
68610
  mkdirSync8(outputDir, { recursive: true });
66833
68611
  }
66834
68612
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
66835
- const outputPath = join11(outputDir, `mementos-training-${timestamp}.jsonl`);
68613
+ const outputPath = join12(outputDir, `mementos-training-${timestamp}.jsonl`);
66836
68614
  const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
66837
68615
  `);
66838
68616
  writeFileSync6(outputPath, jsonl + `
@@ -66856,7 +68634,7 @@ function makeBrainsCommand() {
66856
68634
  try {
66857
68635
  let datasetPath = opts.dataset;
66858
68636
  if (!datasetPath) {
66859
- const trainingDir = join11(homedir7(), ".hasna", "mementos", "training");
68637
+ const trainingDir = join12(homedir7(), ".hasna", "mementos", "training");
66860
68638
  if (!existsSync11(trainingDir)) {
66861
68639
  printError("No training data found. Run `mementos brains gather` first.");
66862
68640
  process.exit(1);
@@ -66867,7 +68645,7 @@ function makeBrainsCommand() {
66867
68645
  printError("No JSONL training files found. Run `mementos brains gather` first.");
66868
68646
  process.exit(1);
66869
68647
  }
66870
- datasetPath = join11(trainingDir, latestFile);
68648
+ datasetPath = join12(trainingDir, latestFile);
66871
68649
  }
66872
68650
  if (!datasetPath || !existsSync11(datasetPath)) {
66873
68651
  printError(`Dataset file not found: ${datasetPath ?? "(unresolved)"}`);
@@ -66995,8 +68773,8 @@ function registerAllCommands(program2) {
66995
68773
  // src/cli/index.tsx
66996
68774
  function getPackageVersion2() {
66997
68775
  try {
66998
- const pkgPath = join12(dirname7(fileURLToPath4(import.meta.url)), "..", "..", "package.json");
66999
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
68776
+ const pkgPath = join13(dirname8(fileURLToPath5(import.meta.url)), "..", "..", "package.json");
68777
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
67000
68778
  return pkg.version || "0.0.0";
67001
68779
  } catch {
67002
68780
  return "0.0.0";