@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.
- package/bun.lock +93 -0
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-watch.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +38 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2009 -334
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
- package/dist/db/memory-project-link.d.ts +13 -0
- package/dist/db/memory-project-link.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts +0 -6
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts +22 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2828 -163
- package/dist/lib/package-version.d.ts +3 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/mcp/index.js +414 -0
- package/dist/memory-project-link/index.d.ts +2 -0
- package/dist/memory-project-link/index.d.ts.map +1 -0
- package/dist/memory-project-link/schema.d.ts +5 -0
- package/dist/memory-project-link/schema.d.ts.map +1 -0
- package/dist/project-registration/authority.d.ts +39 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +29 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/project-references.d.ts +71 -0
- package/dist/project-registration/project-references.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +5 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +168 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +1496 -0
- package/dist/sdk/index.d.ts +127 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +74 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2573 -138
- package/dist/server/routes/memories.d.ts +1 -0
- package/dist/server/routes/memories.d.ts.map +1 -1
- package/dist/server/routes/memory-project-link.d.ts +2 -0
- package/dist/server/routes/memory-project-link.d.ts.map +1 -0
- package/dist/server/routes/project-registration.d.ts +2 -0
- package/dist/server/routes/project-registration.d.ts.map +1 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -3
- 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
|
-
|
|
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
|
|
6060
|
-
import { dirname as
|
|
6061
|
-
import { fileURLToPath as
|
|
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 =
|
|
6065
|
-
const pkg = JSON.parse(
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
59055
|
-
import { dirname as
|
|
59056
|
-
import { fileURLToPath as
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
63208
|
+
const configPath = join7(homeDir(), ".hasna", "mementos", "config.json");
|
|
61596
63209
|
let fileConfig = {};
|
|
61597
63210
|
if (existsSync4(configPath)) {
|
|
61598
63211
|
try {
|
|
61599
|
-
const raw =
|
|
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 =
|
|
63238
|
+
const candidate = join7(dir, filename);
|
|
61626
63239
|
if (existsSync4(candidate) && resolve13(candidate) !== legacyHomeMementosDb) {
|
|
61627
63240
|
return candidate;
|
|
61628
63241
|
}
|
|
61629
|
-
const parent =
|
|
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(
|
|
63252
|
+
if (existsSync4(join7(dir, ".git"))) {
|
|
61640
63253
|
return dir;
|
|
61641
63254
|
}
|
|
61642
|
-
const parent =
|
|
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
|
|
63263
|
+
return join7(homeDir(), ".hasna", "mementos", "profiles");
|
|
61651
63264
|
}
|
|
61652
63265
|
function globalConfigPath() {
|
|
61653
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
61719
|
-
const _oldDir =
|
|
63331
|
+
const _newDir = join7(_home, ".hasna", "mementos");
|
|
63332
|
+
const _oldDir = join7(_home, ".mementos");
|
|
61720
63333
|
if (!existsSync4(_newDir) && existsSync4(_oldDir)) {
|
|
61721
|
-
mkdirSync3(
|
|
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(
|
|
63343
|
+
ensureDir2(dirname4(resolved));
|
|
61731
63344
|
return resolved;
|
|
61732
63345
|
}
|
|
61733
63346
|
const profile = getActiveProfile();
|
|
61734
63347
|
if (profile) {
|
|
61735
|
-
const profilePath =
|
|
61736
|
-
ensureDir2(
|
|
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 =
|
|
61744
|
-
ensureDir2(
|
|
63356
|
+
const dbPath = join7(gitRoot, ".mementos", "mementos.db");
|
|
63357
|
+
ensureDir2(dirname4(dbPath));
|
|
61745
63358
|
return dbPath;
|
|
61746
63359
|
}
|
|
61747
63360
|
}
|
|
61748
|
-
const found = findFileWalkingUp(
|
|
63361
|
+
const found = findFileWalkingUp(join7(".mementos", "mementos.db"));
|
|
61749
63362
|
if (found) {
|
|
61750
63363
|
return found;
|
|
61751
63364
|
}
|
|
61752
|
-
const fallback =
|
|
61753
|
-
ensureDir2(
|
|
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
|
|
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 =
|
|
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:
|
|
62024
|
-
const backupDb = new
|
|
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
|
|
64834
|
+
import { join as join8 } from "path";
|
|
63160
64835
|
import { homedir as homedir4 } from "os";
|
|
63161
64836
|
import {
|
|
63162
|
-
readFileSync as
|
|
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 =
|
|
65025
|
+
const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
|
|
63351
65026
|
if (existsSync7(settingsFilePath)) {
|
|
63352
|
-
const settings = JSON.parse(
|
|
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 =
|
|
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 =
|
|
65126
|
+
const settingsFilePath = join8(homedir4(), ".claude", "settings.json");
|
|
63452
65127
|
if (existsSync7(settingsFilePath)) {
|
|
63453
|
-
const settings = JSON.parse(
|
|
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 =
|
|
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
|
|
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
|
|
67141
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
65467
67142
|
import { homedir as homedir5 } from "os";
|
|
65468
|
-
import { fileURLToPath as
|
|
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 =
|
|
65528
|
-
const hookDest =
|
|
65529
|
-
const settingsPath =
|
|
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(
|
|
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 =
|
|
67227
|
+
const packageDir = dirname7(dirname7(fileURLToPath4(import.meta.url)));
|
|
65553
67228
|
const candidatePaths = [
|
|
65554
|
-
|
|
65555
|
-
|
|
65556
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
68545
|
+
import { join as join11 } from "path";
|
|
66871
68546
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
66872
|
-
var CONFIG_DIR =
|
|
66873
|
-
var CONFIG_PATH =
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
67102
|
-
const pkg = JSON.parse(
|
|
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";
|