@hasna/mementos 0.14.75 → 0.14.79

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 (63) hide show
  1. package/bun.lock +93 -0
  2. package/dist/cli/commands/info-context.d.ts.map +1 -1
  3. package/dist/cli/commands/info-stale.d.ts.map +1 -1
  4. package/dist/cli/commands/io-export.d.ts.map +1 -1
  5. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  6. package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
  8. package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
  9. package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
  10. package/dist/cli/commands/project.d.ts.map +1 -1
  11. package/dist/cli/commands/system-watch.d.ts.map +1 -1
  12. package/dist/cli/helpers.d.ts +38 -0
  13. package/dist/cli/helpers.d.ts.map +1 -1
  14. package/dist/cli/index.js +2009 -334
  15. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
  16. package/dist/db/memory-project-link.d.ts +13 -0
  17. package/dist/db/memory-project-link.d.ts.map +1 -0
  18. package/dist/db/migrations.d.ts.map +1 -1
  19. package/dist/db/pg-migrations.d.ts +0 -6
  20. package/dist/db/pg-migrations.d.ts.map +1 -1
  21. package/dist/db/projects.d.ts +22 -1
  22. package/dist/db/projects.d.ts.map +1 -1
  23. package/dist/index.d.ts +5 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2828 -163
  26. package/dist/lib/package-version.d.ts +3 -0
  27. package/dist/lib/package-version.d.ts.map +1 -0
  28. package/dist/mcp/index.js +414 -0
  29. package/dist/memory-project-link/index.d.ts +2 -0
  30. package/dist/memory-project-link/index.d.ts.map +1 -0
  31. package/dist/memory-project-link/schema.d.ts +5 -0
  32. package/dist/memory-project-link/schema.d.ts.map +1 -0
  33. package/dist/project-registration/authority.d.ts +39 -0
  34. package/dist/project-registration/authority.d.ts.map +1 -0
  35. package/dist/project-registration/http.d.ts +29 -0
  36. package/dist/project-registration/http.d.ts.map +1 -0
  37. package/dist/project-registration/index.d.ts +8 -0
  38. package/dist/project-registration/index.d.ts.map +1 -0
  39. package/dist/project-registration/project-references.d.ts +71 -0
  40. package/dist/project-registration/project-references.d.ts.map +1 -0
  41. package/dist/project-registration/schema.d.ts +5 -0
  42. package/dist/project-registration/schema.d.ts.map +1 -0
  43. package/dist/project-registration/types.d.ts +168 -0
  44. package/dist/project-registration/types.d.ts.map +1 -0
  45. package/dist/project-registration.d.ts +2 -0
  46. package/dist/project-registration.d.ts.map +1 -0
  47. package/dist/project-registration.js +1496 -0
  48. package/dist/sdk/index.d.ts +127 -0
  49. package/dist/sdk/index.d.ts.map +1 -1
  50. package/dist/sdk/index.js +74 -0
  51. package/dist/server/index.d.ts +1 -0
  52. package/dist/server/index.d.ts.map +1 -1
  53. package/dist/server/index.js +2573 -138
  54. package/dist/server/routes/memories.d.ts +1 -0
  55. package/dist/server/routes/memories.d.ts.map +1 -1
  56. package/dist/server/routes/memory-project-link.d.ts +2 -0
  57. package/dist/server/routes/memory-project-link.d.ts.map +1 -0
  58. package/dist/server/routes/project-registration.d.ts +2 -0
  59. package/dist/server/routes/project-registration.d.ts.map +1 -0
  60. package/dist/types/index.d.ts +119 -0
  61. package/dist/types/index.d.ts.map +1 -1
  62. package/package.json +9 -3
  63. 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 (
@@ -4163,6 +4553,18 @@ UPDATE memory_audit_log SET new_value_hash = NULL
4163
4553
  AND new_value_hash NOT GLOB '*[^0-9A-F]*';
4164
4554
 
4165
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);
4166
4568
  `
4167
4569
  ];
4168
4570
  });
@@ -5734,13 +6136,247 @@ var init_memories = __esm(() => {
5734
6136
  init_api_mode();
5735
6137
  });
5736
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
+
5737
6365
  // src/db/projects.ts
5738
6366
  var exports_projects = {};
5739
6367
  __export(exports_projects, {
6368
+ updateProject: () => updateProject,
6369
+ rollbackProjectUpdate: () => rollbackProjectUpdate,
5740
6370
  registerProject: () => registerProject,
6371
+ previewProjectUpdate: () => previewProjectUpdate,
5741
6372
  listProjects: () => listProjects,
5742
- getProject: () => getProject
6373
+ getProjectUpdateReceipt: () => getProjectUpdateReceipt,
6374
+ getProject: () => getProject,
6375
+ applyProjectUpdate: () => applyProjectUpdate,
6376
+ ProjectGuardedUpdateError: () => ProjectGuardedUpdateError,
6377
+ ProjectCollisionError: () => ProjectCollisionError
5743
6378
  });
6379
+ import { createHash } from "crypto";
5744
6380
  function parseProjectRow(row) {
5745
6381
  return {
5746
6382
  id: row["id"],
@@ -5752,6 +6388,213 @@ function parseProjectRow(row) {
5752
6388
  updated_at: row["updated_at"]
5753
6389
  };
5754
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
+ }
5755
6598
  function registerProject(name, path, description, memoryPrefix, db) {
5756
6599
  if (!db && isApiMode()) {
5757
6600
  const { data } = apiJson("POST", "/projects", {
@@ -5805,9 +6648,280 @@ function listProjects(db) {
5805
6648
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
5806
6649
  return rows.map(parseProjectRow);
5807
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;
5808
6894
  var init_projects = __esm(() => {
5809
6895
  init_database();
5810
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}$/;
5811
6925
  });
5812
6926
 
5813
6927
  // src/db/entities.ts
@@ -6026,6 +7140,7 @@ __export(exports_helpers, {
6026
7140
  resolveMemoryId: () => resolveMemoryId,
6027
7141
  resolveKeyOrId: () => resolveKeyOrId,
6028
7142
  resolveEntityArg: () => resolveEntityArg,
7143
+ resolveAgentFilter: () => resolveAgentFilter,
6029
7144
  readFileConfig: () => readFileConfig,
6030
7145
  printPageHint: () => printPageHint,
6031
7146
  positiveIntOrDefault: () => positiveIntOrDefault,
@@ -6056,13 +7171,13 @@ __export(exports_helpers, {
6056
7171
  DEFAULT_COMPACT_LIMIT: () => DEFAULT_COMPACT_LIMIT
6057
7172
  });
6058
7173
  import chalk from "chalk";
6059
- import { readFileSync as readFileSync2 } from "fs";
6060
- import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
6061
- 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";
6062
7177
  function getPackageVersion() {
6063
7178
  try {
6064
- const pkgPath = join5(dirname2(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
6065
- 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"));
6066
7181
  return pkg.version || "0.0.0";
6067
7182
  } catch {
6068
7183
  return "0.0.0";
@@ -6292,8 +7407,19 @@ function resolveMemoryId(partialId) {
6292
7407
  }
6293
7408
  return id;
6294
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
+ }
6295
7421
  function resolveKeyOrId(keyOrId, opts, globalOpts) {
6296
- const agentId = opts.agent || globalOpts.agent;
7422
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
6297
7423
  const projectPath = opts.project || globalOpts.project;
6298
7424
  let projectId;
6299
7425
  if (projectPath) {
@@ -6578,7 +7704,7 @@ function validateConfigKeyValue(key, value, DEFAULT_CONFIG) {
6578
7704
  }
6579
7705
  function getConfigPath() {
6580
7706
  const { homedir: homedir3 } = __require("os");
6581
- return join5(homedir3(), ".hasna", "mementos", "config.json");
7707
+ return join6(homedir3(), ".hasna", "mementos", "config.json");
6582
7708
  }
6583
7709
  function readFileConfig() {
6584
7710
  const { existsSync: existsSync4 } = __require("fs");
@@ -6586,7 +7712,7 @@ function readFileConfig() {
6586
7712
  if (!existsSync4(configPath))
6587
7713
  return {};
6588
7714
  try {
6589
- const data = JSON.parse(readFileSync2(configPath, "utf-8"));
7715
+ const data = JSON.parse(readFileSync3(configPath, "utf-8"));
6590
7716
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
6591
7717
  throw new Error("expected a JSON object");
6592
7718
  }
@@ -6599,7 +7725,7 @@ function readFileConfig() {
6599
7725
  function writeFileConfig(data) {
6600
7726
  const { existsSync: existsSync4, writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = __require("fs");
6601
7727
  const configPath = getConfigPath();
6602
- const dir = dirname2(configPath);
7728
+ const dir = dirname3(configPath);
6603
7729
  if (!existsSync4(dir))
6604
7730
  mkdirSync3(dir, { recursive: true });
6605
7731
  writeFileSync3(configPath, JSON.stringify(data, null, 2) + `
@@ -6610,6 +7736,7 @@ var init_helpers = __esm(() => {
6610
7736
  init_database();
6611
7737
  init_api_mode();
6612
7738
  init_memories();
7739
+ init_agents();
6613
7740
  init_projects();
6614
7741
  init_entities();
6615
7742
  scopeColor = {
@@ -6640,232 +7767,6 @@ var init_helpers = __esm(() => {
6640
7767
  VALID_CATEGORIES = ["preference", "fact", "knowledge", "history"];
6641
7768
  });
6642
7769
 
6643
- // src/db/agents.ts
6644
- var exports_agents = {};
6645
- __export(exports_agents, {
6646
- updateAgent: () => updateAgent,
6647
- touchAgent: () => touchAgent,
6648
- registerAgent: () => registerAgent,
6649
- listAgentsByProject: () => listAgentsByProject,
6650
- listAgents: () => listAgents,
6651
- getAgent: () => getAgent
6652
- });
6653
- function parseAgentRow(row) {
6654
- return {
6655
- id: row["id"],
6656
- name: row["name"],
6657
- session_id: row["session_id"] || null,
6658
- description: row["description"] || null,
6659
- role: row["role"] || null,
6660
- metadata: JSON.parse(row["metadata"] || "{}"),
6661
- active_project_id: row["active_project_id"] || null,
6662
- created_at: row["created_at"],
6663
- last_seen_at: row["last_seen_at"]
6664
- };
6665
- }
6666
- function registerAgent(name, sessionId, description, role, projectId, db) {
6667
- if (!db && isApiMode()) {
6668
- const { data } = apiJson("POST", "/agents", {
6669
- name,
6670
- session_id: sessionId,
6671
- description,
6672
- role,
6673
- project_id: projectId
6674
- });
6675
- return data;
6676
- }
6677
- const d = db || getDatabase();
6678
- const timestamp = now();
6679
- const normalizedName = name.trim().toLowerCase();
6680
- if (projectId) {
6681
- const resolvedProjectId = resolvePartialId(d, "projects", projectId);
6682
- if (!resolvedProjectId) {
6683
- throw new Error(`Project not found: ${projectId}`);
6684
- }
6685
- projectId = resolvedProjectId;
6686
- }
6687
- const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
6688
- if (existing) {
6689
- const existingId = existing["id"];
6690
- const existingSessionId = existing["session_id"] || null;
6691
- const existingLastSeen = existing["last_seen_at"];
6692
- if (sessionId && existingSessionId && existingSessionId !== sessionId) {
6693
- const lastSeenMs = new Date(existingLastSeen).getTime();
6694
- const nowMs = Date.now();
6695
- if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
6696
- throw new AgentConflictError({
6697
- existing_id: existingId,
6698
- existing_name: normalizedName,
6699
- last_seen_at: existingLastSeen,
6700
- session_hint: existingSessionId.slice(0, 8),
6701
- working_dir: null
6702
- });
6703
- }
6704
- }
6705
- d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
6706
- timestamp,
6707
- sessionId ?? existingSessionId,
6708
- existingId
6709
- ]);
6710
- if (description) {
6711
- d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
6712
- }
6713
- if (role) {
6714
- d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
6715
- }
6716
- if (projectId !== undefined) {
6717
- d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
6718
- }
6719
- return getAgent(existingId, d);
6720
- }
6721
- const id = shortUuid();
6722
- 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]);
6723
- return getAgent(id, d);
6724
- }
6725
- function getAgent(idOrName, db) {
6726
- if (!db && isApiMode()) {
6727
- const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
6728
- if (status === 404 || !data)
6729
- return null;
6730
- return data;
6731
- }
6732
- const d = db || getDatabase();
6733
- let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
6734
- if (row)
6735
- return parseAgentRow(row);
6736
- row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
6737
- if (row)
6738
- return parseAgentRow(row);
6739
- const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
6740
- if (rows.length === 1)
6741
- return parseAgentRow(rows[0]);
6742
- return null;
6743
- }
6744
- function normalizedAgentListFilter(filter) {
6745
- const limit = Number.isFinite(filter.limit) ? Math.max(0, Math.floor(filter.limit)) : undefined;
6746
- const offset = Number.isFinite(filter.offset) ? Math.max(0, Math.floor(filter.offset)) : undefined;
6747
- return { limit, offset };
6748
- }
6749
- function isDatabaseAdapter(value) {
6750
- return typeof value === "object" && value !== null && "query" in value && typeof value.query === "function";
6751
- }
6752
- function paginatedAgentQuery(baseSql, baseParams, filter) {
6753
- const { limit, offset } = normalizedAgentListFilter(filter);
6754
- let sql = `${baseSql} ORDER BY created_at ASC, id ASC`;
6755
- const params = [...baseParams];
6756
- if (limit !== undefined) {
6757
- sql += " LIMIT ?";
6758
- params.push(limit);
6759
- } else if ((offset ?? 0) > 0) {
6760
- sql += " LIMIT ?";
6761
- params.push(UNBOUNDED_AGENT_LIST_LIMIT);
6762
- }
6763
- if ((offset ?? 0) > 0) {
6764
- sql += " OFFSET ?";
6765
- params.push(offset);
6766
- }
6767
- return { sql, params };
6768
- }
6769
- function listAgents(filterOrDb = {}, db) {
6770
- const explicitDb = isDatabaseAdapter(filterOrDb) ? filterOrDb : db;
6771
- const filter = isDatabaseAdapter(filterOrDb) ? {} : filterOrDb;
6772
- const normalized = normalizedAgentListFilter(filter);
6773
- if (!explicitDb && isApiMode()) {
6774
- const q = toQuery({
6775
- limit: normalized.limit,
6776
- offset: normalized.offset
6777
- });
6778
- const { data } = apiJson("GET", `/agents${q}`);
6779
- return data?.agents ?? [];
6780
- }
6781
- const d = explicitDb || getDatabase();
6782
- const { sql, params } = paginatedAgentQuery("SELECT * FROM agents", [], normalized);
6783
- const rows = d.query(sql).all(...params);
6784
- return rows.map(parseAgentRow);
6785
- }
6786
- function touchAgent(idOrName, db) {
6787
- if (!db && isApiMode()) {
6788
- const agent2 = getAgent(idOrName);
6789
- if (!agent2)
6790
- return;
6791
- apiJson("PATCH", `/agents/${encodeURIComponent(agent2.id)}`, {});
6792
- return;
6793
- }
6794
- const d = db || getDatabase();
6795
- const agent = getAgent(idOrName, d);
6796
- if (!agent)
6797
- return;
6798
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
6799
- }
6800
- function listAgentsByProject(projectId, filterOrDb = {}, db) {
6801
- const explicitDb = isDatabaseAdapter(filterOrDb) ? filterOrDb : db;
6802
- const filter = isDatabaseAdapter(filterOrDb) ? {} : filterOrDb;
6803
- const normalized = normalizedAgentListFilter(filter);
6804
- if (!explicitDb && isApiMode()) {
6805
- const q = toQuery({ project_id: projectId, ...normalized });
6806
- const { data } = apiJson("GET", `/agents${q}`);
6807
- return data?.agents ?? [];
6808
- }
6809
- const d = explicitDb || getDatabase();
6810
- const resolvedId = resolvePartialId(d, "projects", projectId) || projectId;
6811
- const { sql, params } = paginatedAgentQuery("SELECT * FROM agents WHERE active_project_id = ?", [resolvedId], normalized);
6812
- const rows = d.query(sql).all(...params);
6813
- return rows.map(parseAgentRow);
6814
- }
6815
- function updateAgent(id, updates, db) {
6816
- if (!db && isApiMode()) {
6817
- const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
6818
- if (status === 404 || !data)
6819
- return null;
6820
- return data;
6821
- }
6822
- const d = db || getDatabase();
6823
- const agent = getAgent(id, d);
6824
- if (!agent)
6825
- return null;
6826
- const timestamp = now();
6827
- if (updates.name) {
6828
- const normalizedNewName = updates.name.trim().toLowerCase();
6829
- if (normalizedNewName !== agent.name) {
6830
- const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
6831
- if (existing) {
6832
- throw new Error(`Agent name already taken: ${normalizedNewName}`);
6833
- }
6834
- d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
6835
- }
6836
- }
6837
- if (updates.description !== undefined) {
6838
- d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
6839
- }
6840
- if (updates.role !== undefined) {
6841
- d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
6842
- }
6843
- if (updates.metadata !== undefined) {
6844
- d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
6845
- }
6846
- if ("active_project_id" in updates) {
6847
- let resolvedProjectId = updates.active_project_id ?? null;
6848
- if (resolvedProjectId) {
6849
- const fullId = resolvePartialId(d, "projects", resolvedProjectId);
6850
- if (!fullId) {
6851
- throw new Error(`Project not found: ${resolvedProjectId}`);
6852
- }
6853
- resolvedProjectId = fullId;
6854
- }
6855
- d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProjectId, agent.id]);
6856
- }
6857
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
6858
- return getAgent(agent.id, d);
6859
- }
6860
- var CONFLICT_WINDOW_MS, UNBOUNDED_AGENT_LIST_LIMIT;
6861
- var init_agents = __esm(() => {
6862
- init_types();
6863
- init_database();
6864
- init_api_mode();
6865
- CONFLICT_WINDOW_MS = 30 * 60 * 1000;
6866
- UNBOUNDED_AGENT_LIST_LIMIT = Number.MAX_SAFE_INTEGER;
6867
- });
6868
-
6869
7770
  // src/lib/poll.ts
6870
7771
  var exports_poll = {};
6871
7772
  __export(exports_poll, {
@@ -11375,11 +12276,11 @@ __export(exports_session_registry, {
11375
12276
  cleanStaleSessions: () => cleanStaleSessions
11376
12277
  });
11377
12278
  import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
11378
- import { dirname as dirname5, join as join8 } from "path";
12279
+ import { dirname as dirname6, join as join9 } from "path";
11379
12280
  function getDb() {
11380
12281
  if (_db2)
11381
12282
  return _db2;
11382
- const dir = dirname5(DB_PATH);
12283
+ const dir = dirname6(DB_PATH);
11383
12284
  if (!existsSync8(dir))
11384
12285
  mkdirSync5(dir, { recursive: true });
11385
12286
  _db2 = new SqliteAdapter(DB_PATH);
@@ -11555,12 +12456,13 @@ function closeRegistry() {
11555
12456
  var DB_PATH, _db2 = null;
11556
12457
  var init_session_registry = __esm(() => {
11557
12458
  init_storage();
11558
- 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");
11559
12460
  });
11560
12461
 
11561
12462
  // src/db/pg-migrations.ts
11562
12463
  var PG_MIGRATIONS;
11563
12464
  var init_pg_migrations = __esm(() => {
12465
+ init_schema();
11564
12466
  PG_MIGRATIONS = [
11565
12467
  `
11566
12468
  CREATE TABLE IF NOT EXISTS projects (
@@ -12322,6 +13224,18 @@ var init_pg_migrations = __esm(() => {
12322
13224
  $$ LANGUAGE plpgsql;
12323
13225
 
12324
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;
12325
13239
  `
12326
13240
  ];
12327
13241
  });
@@ -13471,7 +14385,7 @@ var handleResult = (ctx, result) => {
13471
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 = {
13472
14386
  message: `Input not instance of ${cls.name}`
13473
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;
13474
- var init_types2 = __esm(() => {
14388
+ var init_types3 = __esm(() => {
13475
14389
  init_ZodError();
13476
14390
  init_errors();
13477
14391
  init_errorUtil();
@@ -16392,7 +17306,7 @@ var init_external = __esm(() => {
16392
17306
  init_parseUtil();
16393
17307
  init_typeAliases();
16394
17308
  init_util();
16395
- init_types2();
17309
+ init_types3();
16396
17310
  init_ZodError();
16397
17311
  });
16398
17312
 
@@ -59051,9 +59965,9 @@ var {
59051
59965
 
59052
59966
  // src/cli/index.tsx
59053
59967
  init_database();
59054
- import { readFileSync as readFileSync8 } from "fs";
59055
- import { dirname as dirname7, join as join12 } from "path";
59056
- 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";
59057
59971
 
59058
59972
  // src/db/machines.ts
59059
59973
  init_database();
@@ -59853,11 +60767,638 @@ init_helpers();
59853
60767
  init_database();
59854
60768
  init_api_mode();
59855
60769
  init_memories();
59856
- init_projects();
59857
- init_agents();
59858
60770
  import chalk2 from "chalk";
59859
60771
  import { resolve as resolve3 } from "path";
59860
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
+
59861
61402
  // src/lib/duration.ts
59862
61403
  var UNIT_MS = {
59863
61404
  s: 1000,
@@ -59907,6 +61448,92 @@ init_types();
59907
61448
  init_helpers();
59908
61449
  function registerCrudCommands(program2) {
59909
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
+ });
59910
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) => {
59911
61538
  try {
59912
61539
  const globalOpts = program2.opts();
@@ -60305,7 +61932,7 @@ function registerTailCommand(program2) {
60305
61932
  try {
60306
61933
  const globalOpts = program2.opts();
60307
61934
  const jsonMode = !!globalOpts.json;
60308
- const agentId = opts.agent || globalOpts.agent;
61935
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60309
61936
  const projectPath = opts.project || globalOpts.project;
60310
61937
  let projectId;
60311
61938
  if (projectPath) {
@@ -60461,14 +62088,7 @@ function registerSearchCommand(program2) {
60461
62088
  if (project)
60462
62089
  projectId = project.id;
60463
62090
  }
60464
- const agentName = opts.agent || globalOpts.agent;
60465
- let agentId;
60466
- if (agentName) {
60467
- const { getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
60468
- const agent = getAgent2(agentName);
60469
- if (agent)
60470
- agentId = agent.id;
60471
- }
62091
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60472
62092
  const filter = {
60473
62093
  scope: opts.scope,
60474
62094
  category: opts.category,
@@ -60742,7 +62362,7 @@ function registerRecallCommand(program2) {
60742
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) => {
60743
62363
  try {
60744
62364
  const globalOpts = program2.opts();
60745
- const agentId = opts.agent || globalOpts.agent;
62365
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60746
62366
  const projectPath = opts.project || globalOpts.project;
60747
62367
  let projectId;
60748
62368
  if (projectPath) {
@@ -60815,7 +62435,7 @@ function registerListCommand(program2) {
60815
62435
  const requestedLimit = opts.limit;
60816
62436
  const limit = positiveIntOrDefault(requestedLimit, isStructured ? 50 : DEFAULT_COMPACT_LIMIT);
60817
62437
  const offset = cursorOrOffset(opts.cursor, opts.offset);
60818
- const agentId = opts.agent || globalOpts.agent;
62438
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
60819
62439
  const projectPath = opts.project || globalOpts.project;
60820
62440
  let projectId;
60821
62441
  if (projectPath) {
@@ -61180,7 +62800,6 @@ mementos report \u2014 last ${days} days
61180
62800
  import chalk15 from "chalk";
61181
62801
  import { resolve as resolve9 } from "path";
61182
62802
  init_projects();
61183
- init_agents();
61184
62803
  init_helpers();
61185
62804
  function registerStaleCommand(program2) {
61186
62805
  const handleError = makeHandleError(program2);
@@ -61199,13 +62818,7 @@ function registerStaleCommand(program2) {
61199
62818
  if (project)
61200
62819
  projectId = project.id;
61201
62820
  }
61202
- const agentName = opts.agent || globalOpts.agent;
61203
- let agentId;
61204
- if (agentName) {
61205
- const agent = getAgent(agentName);
61206
- if (agent)
61207
- agentId = agent.id;
61208
- }
62821
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61209
62822
  const rows = getStaleMemories({
61210
62823
  days,
61211
62824
  project_id: projectId,
@@ -61334,7 +62947,7 @@ function registerContextCommand(program2) {
61334
62947
  const scope = opts.scope;
61335
62948
  const categoriesRaw = opts.categories;
61336
62949
  const categories = categoriesRaw ? categoriesRaw.split(",").map((c) => c.trim()) : undefined;
61337
- const agentId = opts.agent || globalOpts.agent;
62950
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61338
62951
  const projectPath = opts.project || globalOpts.project;
61339
62952
  const visibleMachineId = resolveVisibleMachineId(opts.machine);
61340
62953
  let projectId;
@@ -61459,7 +63072,7 @@ function registerExportCommand(program2) {
61459
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) => {
61460
63073
  try {
61461
63074
  const globalOpts = program2.opts();
61462
- const agentId = opts.agent || globalOpts.agent;
63075
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
61463
63076
  const projectPath = opts.project || globalOpts.project;
61464
63077
  let projectId;
61465
63078
  if (projectPath) {
@@ -61487,7 +63100,7 @@ init_memories();
61487
63100
  init_helpers();
61488
63101
  import chalk18 from "chalk";
61489
63102
  import { resolve as resolve12 } from "path";
61490
- import { readFileSync as readFileSync3 } from "fs";
63103
+ import { readFileSync as readFileSync4 } from "fs";
61491
63104
  function registerImportCommand(program2) {
61492
63105
  const handleError = makeHandleError(program2);
61493
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) => {
@@ -61497,7 +63110,7 @@ function registerImportCommand(program2) {
61497
63110
  if (file === "-" || !file && !process.stdin.isTTY) {
61498
63111
  raw = await Bun.stdin.text();
61499
63112
  } else if (file) {
61500
- raw = readFileSync3(resolve12(file), "utf-8");
63113
+ raw = readFileSync4(resolve12(file), "utf-8");
61501
63114
  } else {
61502
63115
  console.error(chalk18.red("No input: provide a file path, use '-' for stdin, or pipe data."));
61503
63116
  process.exit(1);
@@ -61527,9 +63140,9 @@ function registerImportCommand(program2) {
61527
63140
  import chalk19 from "chalk";
61528
63141
 
61529
63142
  // src/lib/config.ts
61530
- 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";
61531
63144
  import { homedir as homedir3 } from "os";
61532
- 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";
61533
63146
  function isInMemoryDb2(path) {
61534
63147
  return path === ":memory:" || path.startsWith("file::memory:");
61535
63148
  }
@@ -61592,11 +63205,11 @@ function isValidCategory(value) {
61592
63205
  return VALID_CATEGORIES2.includes(value);
61593
63206
  }
61594
63207
  function loadConfig() {
61595
- const configPath = join6(homeDir(), ".hasna", "mementos", "config.json");
63208
+ const configPath = join7(homeDir(), ".hasna", "mementos", "config.json");
61596
63209
  let fileConfig = {};
61597
63210
  if (existsSync4(configPath)) {
61598
63211
  try {
61599
- const raw = readFileSync4(configPath, "utf-8");
63212
+ const raw = readFileSync5(configPath, "utf-8");
61600
63213
  fileConfig = JSON.parse(raw);
61601
63214
  } catch {}
61602
63215
  }
@@ -61622,11 +63235,11 @@ function findFileWalkingUp(filename) {
61622
63235
  let dir = process.cwd();
61623
63236
  const legacyHomeMementosDb = resolve13(homeDir(), ".mementos", "mementos.db");
61624
63237
  while (true) {
61625
- const candidate = join6(dir, filename);
63238
+ const candidate = join7(dir, filename);
61626
63239
  if (existsSync4(candidate) && resolve13(candidate) !== legacyHomeMementosDb) {
61627
63240
  return candidate;
61628
63241
  }
61629
- const parent = dirname3(dir);
63242
+ const parent = dirname4(dir);
61630
63243
  if (parent === dir) {
61631
63244
  return null;
61632
63245
  }
@@ -61636,10 +63249,10 @@ function findFileWalkingUp(filename) {
61636
63249
  function findGitRoot2() {
61637
63250
  let dir = process.cwd();
61638
63251
  while (true) {
61639
- if (existsSync4(join6(dir, ".git"))) {
63252
+ if (existsSync4(join7(dir, ".git"))) {
61640
63253
  return dir;
61641
63254
  }
61642
- const parent = dirname3(dir);
63255
+ const parent = dirname4(dir);
61643
63256
  if (parent === dir) {
61644
63257
  return null;
61645
63258
  }
@@ -61647,17 +63260,17 @@ function findGitRoot2() {
61647
63260
  }
61648
63261
  }
61649
63262
  function profilesDir() {
61650
- return join6(homeDir(), ".hasna", "mementos", "profiles");
63263
+ return join7(homeDir(), ".hasna", "mementos", "profiles");
61651
63264
  }
61652
63265
  function globalConfigPath() {
61653
- return join6(homeDir(), ".hasna", "mementos", "config.json");
63266
+ return join7(homeDir(), ".hasna", "mementos", "config.json");
61654
63267
  }
61655
63268
  function readGlobalConfig() {
61656
63269
  const p = globalConfigPath();
61657
63270
  if (!existsSync4(p))
61658
63271
  return {};
61659
63272
  try {
61660
- return JSON.parse(readFileSync4(p, "utf-8"));
63273
+ return JSON.parse(readFileSync5(p, "utf-8"));
61661
63274
  } catch {
61662
63275
  return {};
61663
63276
  }
@@ -61667,7 +63280,7 @@ function readGlobalConfigForWrite() {
61667
63280
  if (!existsSync4(p))
61668
63281
  return {};
61669
63282
  try {
61670
- const data = JSON.parse(readFileSync4(p, "utf-8"));
63283
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
61671
63284
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
61672
63285
  throw new Error("expected a JSON object");
61673
63286
  }
@@ -61679,7 +63292,7 @@ function readGlobalConfigForWrite() {
61679
63292
  }
61680
63293
  function writeGlobalConfig(data) {
61681
63294
  const p = globalConfigPath();
61682
- ensureDir2(dirname3(p));
63295
+ ensureDir2(dirname4(p));
61683
63296
  writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
61684
63297
  }
61685
63298
  function getActiveProfile() {
@@ -61705,7 +63318,7 @@ function listProfiles() {
61705
63318
  return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
61706
63319
  }
61707
63320
  function deleteProfile(name) {
61708
- const dbPath = join6(profilesDir(), `${name}.db`);
63321
+ const dbPath = join7(profilesDir(), `${name}.db`);
61709
63322
  if (!existsSync4(dbPath))
61710
63323
  return false;
61711
63324
  unlinkSync2(dbPath);
@@ -61715,10 +63328,10 @@ function deleteProfile(name) {
61715
63328
  }
61716
63329
  function getDbPath2() {
61717
63330
  const _home = homeDir();
61718
- const _newDir = join6(_home, ".hasna", "mementos");
61719
- const _oldDir = join6(_home, ".mementos");
63331
+ const _newDir = join7(_home, ".hasna", "mementos");
63332
+ const _oldDir = join7(_home, ".mementos");
61720
63333
  if (!existsSync4(_newDir) && existsSync4(_oldDir)) {
61721
- mkdirSync3(join6(_home, ".hasna"), { recursive: true });
63334
+ mkdirSync3(join7(_home, ".hasna"), { recursive: true });
61722
63335
  cpSync2(_oldDir, _newDir, { recursive: true });
61723
63336
  }
61724
63337
  const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
@@ -61727,30 +63340,30 @@ function getDbPath2() {
61727
63340
  return envDbPath;
61728
63341
  }
61729
63342
  const resolved = resolve13(envDbPath);
61730
- ensureDir2(dirname3(resolved));
63343
+ ensureDir2(dirname4(resolved));
61731
63344
  return resolved;
61732
63345
  }
61733
63346
  const profile = getActiveProfile();
61734
63347
  if (profile) {
61735
- const profilePath = join6(profilesDir(), `${profile}.db`);
61736
- ensureDir2(dirname3(profilePath));
63348
+ const profilePath = join7(profilesDir(), `${profile}.db`);
63349
+ ensureDir2(dirname4(profilePath));
61737
63350
  return profilePath;
61738
63351
  }
61739
63352
  const dbScope = process.env["MEMENTOS_DB_SCOPE"];
61740
63353
  if (dbScope === "project") {
61741
63354
  const gitRoot = findGitRoot2();
61742
63355
  if (gitRoot) {
61743
- const dbPath = join6(gitRoot, ".mementos", "mementos.db");
61744
- ensureDir2(dirname3(dbPath));
63356
+ const dbPath = join7(gitRoot, ".mementos", "mementos.db");
63357
+ ensureDir2(dirname4(dbPath));
61745
63358
  return dbPath;
61746
63359
  }
61747
63360
  }
61748
- const found = findFileWalkingUp(join6(".mementos", "mementos.db"));
63361
+ const found = findFileWalkingUp(join7(".mementos", "mementos.db"));
61749
63362
  if (found) {
61750
63363
  return found;
61751
63364
  }
61752
- const fallback = join6(homeDir(), ".hasna", "mementos", "mementos.db");
61753
- ensureDir2(dirname3(fallback));
63365
+ const fallback = join7(homeDir(), ".hasna", "mementos", "mementos.db");
63366
+ ensureDir2(dirname4(fallback));
61754
63367
  return fallback;
61755
63368
  }
61756
63369
  function ensureDir2(dir) {
@@ -61871,7 +63484,7 @@ function registerCleanCommand(program2) {
61871
63484
  init_database();
61872
63485
  init_helpers();
61873
63486
  import chalk20 from "chalk";
61874
- import { resolve as resolve14, dirname as dirname4 } from "path";
63487
+ import { resolve as resolve14, dirname as dirname5 } from "path";
61875
63488
  import { existsSync as existsSync5, statSync, copyFileSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
61876
63489
  function registerBackupCommand(program2) {
61877
63490
  const handleError = makeHandleError(program2);
@@ -61938,7 +63551,7 @@ function registerBackupCommand(program2) {
61938
63551
  const ts = now3.toISOString().replace(/[-:T]/g, "").replace(/\..+/, "").slice(0, 15);
61939
63552
  dest = resolve14(backupsDir, `mementos-${ts}.db`);
61940
63553
  }
61941
- const destDir = dirname4(dest);
63554
+ const destDir = dirname5(dest);
61942
63555
  if (!existsSync5(destDir)) {
61943
63556
  mkdirSync4(destDir, { recursive: true });
61944
63557
  }
@@ -62020,8 +63633,8 @@ function registerRestoreCommand(program2) {
62020
63633
  }
62021
63634
  let backupCount = 0;
62022
63635
  try {
62023
- const { Database: Database2 } = __require("bun:sqlite");
62024
- const backupDb = new Database2(source, { readonly: true });
63636
+ const { Database: Database3 } = __require("bun:sqlite");
63637
+ const backupDb = new Database3(source, { readonly: true });
62025
63638
  const row = backupDb.query("SELECT COUNT(*) as count FROM memories").get();
62026
63639
  backupCount = row?.count ?? 0;
62027
63640
  backupDb.close();
@@ -62297,9 +63910,13 @@ import { resolve as resolve16 } from "path";
62297
63910
  init_helpers();
62298
63911
  function registerProjectCommands(program2) {
62299
63912
  const handleError = makeHandleError(program2);
62300
- 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) => {
62301
63914
  try {
62302
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
+ }
62303
63920
  if (opts.add) {
62304
63921
  const name = opts.name;
62305
63922
  const path = opts.path;
@@ -62318,6 +63935,64 @@ function registerProjectCommands(program2) {
62318
63935
  }
62319
63936
  return;
62320
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
+ }
62321
63996
  const allProjects = listProjects();
62322
63997
  const limit = positiveIntOrDefault(opts.limit, DEFAULT_COMPACT_LIMIT);
62323
63998
  const offset = cursorOrOffset(opts.cursor, opts.offset) ?? 0;
@@ -63156,10 +64831,10 @@ init_memories();
63156
64831
  init_agents();
63157
64832
  init_projects();
63158
64833
  import chalk27 from "chalk";
63159
- import { join as join7 } from "path";
64834
+ import { join as join8 } from "path";
63160
64835
  import { homedir as homedir4 } from "os";
63161
64836
  import {
63162
- readFileSync as readFileSync5,
64837
+ readFileSync as readFileSync6,
63163
64838
  existsSync as existsSync7,
63164
64839
  accessSync,
63165
64840
  statSync as statSync3,
@@ -63347,9 +65022,9 @@ function registerDoctorCommand(program2) {
63347
65022
  checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
63348
65023
  }
63349
65024
  try {
63350
- const settingsFilePath = join7(homedir4(), ".claude", "settings.json");
65025
+ const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
63351
65026
  if (existsSync7(settingsFilePath)) {
63352
- const settings = JSON.parse(readFileSync5(settingsFilePath, "utf-8"));
65027
+ const settings = JSON.parse(readFileSync6(settingsFilePath, "utf-8"));
63353
65028
  const hooksObj = settings["hooks"] || {};
63354
65029
  const stopHooks = hooksObj["Stop"] || [];
63355
65030
  const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
@@ -63365,7 +65040,7 @@ function registerDoctorCommand(program2) {
63365
65040
  checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
63366
65041
  }
63367
65042
  if (process.platform === "darwin") {
63368
- const plistFilePath = join7(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
65043
+ const plistFilePath = join8(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
63369
65044
  checks.push({
63370
65045
  name: "Auto-start",
63371
65046
  status: existsSync7(plistFilePath) ? "ok" : "warn",
@@ -63448,9 +65123,9 @@ async function runCloudDoctor(globalOpts, checks) {
63448
65123
  checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
63449
65124
  }
63450
65125
  try {
63451
- const settingsFilePath = join7(homedir4(), ".claude", "settings.json");
65126
+ const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
63452
65127
  if (existsSync7(settingsFilePath)) {
63453
- const settings = JSON.parse(readFileSync5(settingsFilePath, "utf-8"));
65128
+ const settings = JSON.parse(readFileSync6(settingsFilePath, "utf-8"));
63454
65129
  const hooksObj = settings["hooks"] || {};
63455
65130
  const stopHooks = hooksObj["Stop"] || [];
63456
65131
  const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
@@ -63466,7 +65141,7 @@ async function runCloudDoctor(globalOpts, checks) {
63466
65141
  checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
63467
65142
  }
63468
65143
  if (process.platform === "darwin") {
63469
- const plistFilePath = join7(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
65144
+ const plistFilePath = join8(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
63470
65145
  checks.push({
63471
65146
  name: "Auto-start",
63472
65147
  status: existsSync7(plistFilePath) ? "ok" : "warn",
@@ -64583,7 +66258,7 @@ function registerWatchCommand(program2) {
64583
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) => {
64584
66259
  try {
64585
66260
  const globalOpts = program2.opts();
64586
- const agentId = opts.agent || globalOpts.agent;
66261
+ const agentId = resolveAgentFilter(opts.agent || globalOpts.agent);
64587
66262
  const projectPath = opts.project || globalOpts.project;
64588
66263
  let projectId;
64589
66264
  if (projectPath) {
@@ -65457,15 +67132,15 @@ function registerStorageCommands(program2) {
65457
67132
  // src/cli/commands/init.ts
65458
67133
  import chalk41 from "chalk";
65459
67134
  import {
65460
- readFileSync as readFileSync6,
67135
+ readFileSync as readFileSync7,
65461
67136
  writeFileSync as writeFileSync4,
65462
67137
  existsSync as existsSync9,
65463
67138
  copyFileSync as copyFileSync3,
65464
67139
  mkdirSync as mkdirSync6
65465
67140
  } from "fs";
65466
- import { dirname as dirname6, join as join9 } from "path";
67141
+ import { dirname as dirname7, join as join10 } from "path";
65467
67142
  import { homedir as homedir5 } from "os";
65468
- import { fileURLToPath as fileURLToPath3 } from "url";
67143
+ import { fileURLToPath as fileURLToPath4 } from "url";
65469
67144
  function registerInitCommand(program2) {
65470
67145
  program2.command("init").description("One-command setup: register MCP, install stop hook, configure auto-start").action(async () => {
65471
67146
  const { platform: platform2 } = process;
@@ -65524,9 +67199,9 @@ function registerInitCommand(program2) {
65524
67199
  } else {
65525
67200
  console.log(chalk41.green(" \u2713 MCP server registered with Claude Code"));
65526
67201
  }
65527
- const hooksDir = join9(home, ".claude", "hooks");
65528
- const hookDest = join9(hooksDir, "mementos-stop-hook.ts");
65529
- 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");
65530
67205
  const hookCommand = `bun ${hookDest}`;
65531
67206
  let hookAlreadyInstalled = false;
65532
67207
  let hookError = null;
@@ -65534,7 +67209,7 @@ function registerInitCommand(program2) {
65534
67209
  let settings = {};
65535
67210
  if (existsSync9(settingsPath)) {
65536
67211
  try {
65537
- settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
67212
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
65538
67213
  } catch {
65539
67214
  settings = {};
65540
67215
  }
@@ -65549,11 +67224,11 @@ function registerInitCommand(program2) {
65549
67224
  mkdirSync6(hooksDir, { recursive: true });
65550
67225
  }
65551
67226
  if (!existsSync9(hookDest)) {
65552
- const packageDir = dirname6(dirname6(fileURLToPath3(import.meta.url)));
67227
+ const packageDir = dirname7(dirname7(fileURLToPath4(import.meta.url)));
65553
67228
  const candidatePaths = [
65554
- join9(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
65555
- join9(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
65556
- 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")
65557
67232
  ];
65558
67233
  let hookSourceFound = false;
65559
67234
  for (const src of candidatePaths) {
@@ -65623,7 +67298,7 @@ main().catch(() => {});
65623
67298
  if (!isMac) {
65624
67299
  console.log(chalk41.dim(` \xB7 Auto-start skipped (not macOS \u2014 platform: ${platform2})`));
65625
67300
  } else {
65626
- const plistPath = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
67301
+ const plistPath = join10(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
65627
67302
  const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
65628
67303
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65629
67304
  <plist version="1.0">
@@ -65651,7 +67326,7 @@ main().catch(() => {});
65651
67326
  if (existsSync9(plistPath)) {
65652
67327
  autoStartAlreadyInstalled = true;
65653
67328
  } else {
65654
- const launchAgentsDir = join9(home, "Library", "LaunchAgents");
67329
+ const launchAgentsDir = join10(home, "Library", "LaunchAgents");
65655
67330
  if (!existsSync9(launchAgentsDir)) {
65656
67331
  mkdirSync6(launchAgentsDir, { recursive: true });
65657
67332
  }
@@ -65668,7 +67343,7 @@ main().catch(() => {});
65668
67343
  console.log(chalk41.green(" \u2713 Auto-start configured (starts on login)"));
65669
67344
  }
65670
67345
  if (!autoStartAlreadyInstalled && !autoStartError) {
65671
- const plistPath2 = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
67346
+ const plistPath2 = join10(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
65672
67347
  const loadResult = await run(["launchctl", "load", plistPath2]);
65673
67348
  if (!loadResult.ok) {
65674
67349
  console.log(chalk41.dim(` \xB7 launchctl load: ${loadResult.output || "already loaded"}`));
@@ -66788,7 +68463,7 @@ import {
66788
68463
  readdirSync as readdirSync4
66789
68464
  } from "fs";
66790
68465
  import { homedir as homedir7 } from "os";
66791
- import { join as join11 } from "path";
68466
+ import { join as join12 } from "path";
66792
68467
  import chalk43 from "chalk";
66793
68468
 
66794
68469
  // src/lib/gatherer.ts
@@ -66865,17 +68540,17 @@ var gatherTrainingData = async (options = {}) => {
66865
68540
  };
66866
68541
 
66867
68542
  // src/lib/model-config.ts
66868
- 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";
66869
68544
  import { homedir as homedir6 } from "os";
66870
- import { join as join10 } from "path";
68545
+ import { join as join11 } from "path";
66871
68546
  var DEFAULT_MODEL = "gpt-4o-mini";
66872
- var CONFIG_DIR = join10(homedir6(), ".hasna", "mementos");
66873
- 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");
66874
68549
  function readConfig() {
66875
68550
  if (!existsSync10(CONFIG_PATH))
66876
68551
  return {};
66877
68552
  try {
66878
- const raw = readFileSync7(CONFIG_PATH, "utf-8");
68553
+ const raw = readFileSync8(CONFIG_PATH, "utf-8");
66879
68554
  return JSON.parse(raw);
66880
68555
  } catch {
66881
68556
  return {};
@@ -66930,12 +68605,12 @@ function makeBrainsCommand() {
66930
68605
  limit: opts.limit,
66931
68606
  since
66932
68607
  });
66933
- const outputDir = opts.output ?? join11(homedir7(), ".hasna", "mementos", "training");
68608
+ const outputDir = opts.output ?? join12(homedir7(), ".hasna", "mementos", "training");
66934
68609
  if (!existsSync11(outputDir)) {
66935
68610
  mkdirSync8(outputDir, { recursive: true });
66936
68611
  }
66937
68612
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
66938
- const outputPath = join11(outputDir, `mementos-training-${timestamp}.jsonl`);
68613
+ const outputPath = join12(outputDir, `mementos-training-${timestamp}.jsonl`);
66939
68614
  const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
66940
68615
  `);
66941
68616
  writeFileSync6(outputPath, jsonl + `
@@ -66959,7 +68634,7 @@ function makeBrainsCommand() {
66959
68634
  try {
66960
68635
  let datasetPath = opts.dataset;
66961
68636
  if (!datasetPath) {
66962
- const trainingDir = join11(homedir7(), ".hasna", "mementos", "training");
68637
+ const trainingDir = join12(homedir7(), ".hasna", "mementos", "training");
66963
68638
  if (!existsSync11(trainingDir)) {
66964
68639
  printError("No training data found. Run `mementos brains gather` first.");
66965
68640
  process.exit(1);
@@ -66970,7 +68645,7 @@ function makeBrainsCommand() {
66970
68645
  printError("No JSONL training files found. Run `mementos brains gather` first.");
66971
68646
  process.exit(1);
66972
68647
  }
66973
- datasetPath = join11(trainingDir, latestFile);
68648
+ datasetPath = join12(trainingDir, latestFile);
66974
68649
  }
66975
68650
  if (!datasetPath || !existsSync11(datasetPath)) {
66976
68651
  printError(`Dataset file not found: ${datasetPath ?? "(unresolved)"}`);
@@ -67098,8 +68773,8 @@ function registerAllCommands(program2) {
67098
68773
  // src/cli/index.tsx
67099
68774
  function getPackageVersion2() {
67100
68775
  try {
67101
- const pkgPath = join12(dirname7(fileURLToPath4(import.meta.url)), "..", "..", "package.json");
67102
- 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"));
67103
68778
  return pkg.version || "0.0.0";
67104
68779
  } catch {
67105
68780
  return "0.0.0";