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