diff --git a/.aiwg/reports/g-icm-01-interface-inventory.json b/.aiwg/reports/g-icm-01-interface-inventory.json index aa108649e..8ae1a672a 100644 --- a/.aiwg/reports/g-icm-01-interface-inventory.json +++ b/.aiwg/reports/g-icm-01-interface-inventory.json @@ -3,9 +3,9 @@ "control_id": "G-ICM-01", "project_id": "github.com/chronodeai/agentmemory", "source_identity": { - "commit_sha": "5df4a9441d0c2d583498b953c6d3f1f0b78803bb", - "commit_tree_sha": "38f5054811be14f96cf7950b09649a2a2d710002", - "inventory_input_sha256": "fe2dea65299cdc4eeba6e309dccce74b9a001fcd71f9edcee0656897c65e1c4c" + "commit_sha": "023433b12303e2b98e4e709c02425493b3e6df48", + "commit_tree_sha": "f87647c615d93a42014f3a4b28603412b0bf7e11", + "inventory_input_sha256": "291b5a4f9644ce81144bf8cc6190e786a834315172a759d24a44a22c5f013855" }, "public_route_allowlist": [ "GET /agentmemory/livez" @@ -38,7 +38,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3682" + "source": "src/triggers/api.ts:3710" }, { "surface_id": "REST:POST:/agentmemory/actions", @@ -48,7 +48,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3638" + "source": "src/triggers/api.ts:3666" }, { "surface_id": "REST:POST:/agentmemory/actions/edges", @@ -58,7 +58,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3723" + "source": "src/triggers/api.ts:3751" }, { "surface_id": "REST:GET:/agentmemory/actions/get", @@ -68,7 +68,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3700" + "source": "src/triggers/api.ts:3728" }, { "surface_id": "REST:POST:/agentmemory/actions/update", @@ -78,7 +78,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3664" + "source": "src/triggers/api.ts:3692" }, { "surface_id": "REST:GET:/agentmemory/audit", @@ -88,7 +88,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3079" + "source": "src/triggers/api.ts:3107" }, { "surface_id": "REST:POST:/agentmemory/auto-forget", @@ -108,7 +108,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4239" + "source": "src/triggers/api.ts:4267" }, { "surface_id": "REST:GET:/agentmemory/branch/sessions", @@ -118,7 +118,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4269" + "source": "src/triggers/api.ts:4297" }, { "surface_id": "REST:GET:/agentmemory/branch/worktrees", @@ -128,7 +128,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4254" + "source": "src/triggers/api.ts:4282" }, { "surface_id": "REST:POST:/agentmemory/cascade-update", @@ -138,7 +138,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4564" + "source": "src/triggers/api.ts:4592" }, { "surface_id": "REST:GET:/agentmemory/checkpoints", @@ -148,7 +148,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4006" + "source": "src/triggers/api.ts:4034" }, { "surface_id": "REST:POST:/agentmemory/checkpoints", @@ -158,7 +158,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3965" + "source": "src/triggers/api.ts:3993" }, { "surface_id": "REST:POST:/agentmemory/checkpoints/resolve", @@ -168,7 +168,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3989" + "source": "src/triggers/api.ts:4017" }, { "surface_id": "REST:GET:/agentmemory/claude-bridge/read", @@ -254,7 +254,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:2974" + "source": "src/triggers/api.ts:3002" }, { "surface_id": "REST:POST:/agentmemory/context", @@ -300,7 +300,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4464" + "source": "src/triggers/api.ts:4492" }, { "surface_id": "REST:POST:/agentmemory/crystals/auto", @@ -310,7 +310,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4476" + "source": "src/triggers/api.ts:4504" }, { "surface_id": "REST:POST:/agentmemory/crystals/create", @@ -320,7 +320,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4422" + "source": "src/triggers/api.ts:4450" }, { "surface_id": "REST:POST:/agentmemory/diagnostics", @@ -330,7 +330,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4485" + "source": "src/triggers/api.ts:4513" }, { "surface_id": "REST:GET:/agentmemory/diagnostics/followup", @@ -352,7 +352,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4494" + "source": "src/triggers/api.ts:4522" }, { "surface_id": "REST:POST:/agentmemory/enrich", @@ -402,7 +402,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4533" + "source": "src/triggers/api.ts:4561" }, { "surface_id": "REST:POST:/agentmemory/facets", @@ -412,7 +412,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4504" + "source": "src/triggers/api.ts:4532" }, { "surface_id": "REST:POST:/agentmemory/facets/query", @@ -422,7 +422,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4523" + "source": "src/triggers/api.ts:4551" }, { "surface_id": "REST:POST:/agentmemory/facets/remove", @@ -432,7 +432,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4514" + "source": "src/triggers/api.ts:4542" }, { "surface_id": "REST:GET:/agentmemory/facets/stats", @@ -442,7 +442,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4542" + "source": "src/triggers/api.ts:4570" }, { "surface_id": "REST:POST:/agentmemory/file-context", @@ -462,7 +462,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4224" + "source": "src/triggers/api.ts:4252" }, { "surface_id": "REST:POST:/agentmemory/forget", @@ -482,7 +482,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3742" + "source": "src/triggers/api.ts:3770" }, { "surface_id": "REST:POST:/agentmemory/generate-rules", @@ -502,7 +502,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3149" + "source": "src/triggers/api.ts:3177" }, { "surface_id": "REST:DELETE:/agentmemory/governance/memories", @@ -512,7 +512,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3124" + "source": "src/triggers/api.ts:3152" }, { "surface_id": "REST:POST:/agentmemory/graph/build", @@ -522,7 +522,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:2940" + "source": "src/triggers/api.ts:2968" }, { "surface_id": "REST:POST:/agentmemory/graph/extract", @@ -542,7 +542,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:2813" + "source": "src/triggers/api.ts:2841" }, { "surface_id": "REST:POST:/agentmemory/graph/query", @@ -624,7 +624,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4764" + "source": "src/triggers/api.ts:4792" }, { "surface_id": "REST:POST:/agentmemory/insights/search", @@ -634,7 +634,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4786" + "source": "src/triggers/api.ts:4814" }, { "surface_id": "REST:POST:/agentmemory/leases/acquire", @@ -644,7 +644,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3778" + "source": "src/triggers/api.ts:3806" }, { "surface_id": "REST:POST:/agentmemory/leases/release", @@ -654,7 +654,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3797" + "source": "src/triggers/api.ts:3825" }, { "surface_id": "REST:POST:/agentmemory/leases/renew", @@ -664,7 +664,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3816" + "source": "src/triggers/api.ts:3844" }, { "surface_id": "REST:GET:/agentmemory/lessons", @@ -674,7 +674,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4628" + "source": "src/triggers/api.ts:4656" }, { "surface_id": "REST:POST:/agentmemory/lessons", @@ -684,7 +684,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4593" + "source": "src/triggers/api.ts:4621" }, { "surface_id": "REST:POST:/agentmemory/lessons/delete", @@ -694,7 +694,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4694" + "source": "src/triggers/api.ts:4722" }, { "surface_id": "REST:POST:/agentmemory/lessons/search", @@ -704,7 +704,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4648" + "source": "src/triggers/api.ts:4676" }, { "surface_id": "REST:POST:/agentmemory/lessons/strengthen", @@ -714,7 +714,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4679" + "source": "src/triggers/api.ts:4707" }, { "surface_id": "REST:GET:/agentmemory/livez", @@ -734,7 +734,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3294" + "source": "src/triggers/api.ts:3322" }, { "surface_id": "REST:GET:/agentmemory/memories/:id", @@ -744,7 +744,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3315" + "source": "src/triggers/api.ts:3343" }, { "surface_id": "REST:GET:/agentmemory/mesh/export", @@ -754,7 +754,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4197" + "source": "src/triggers/api.ts:4225" }, { "surface_id": "REST:GET:/agentmemory/mesh/peers", @@ -764,7 +764,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4051" + "source": "src/triggers/api.ts:4079" }, { "surface_id": "REST:POST:/agentmemory/mesh/peers", @@ -774,7 +774,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4031" + "source": "src/triggers/api.ts:4059" }, { "surface_id": "REST:POST:/agentmemory/mesh/receive", @@ -784,7 +784,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4093" + "source": "src/triggers/api.ts:4121" }, { "surface_id": "REST:POST:/agentmemory/mesh/sync", @@ -794,7 +794,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4073" + "source": "src/triggers/api.ts:4101" }, { "surface_id": "REST:POST:/agentmemory/migrate", @@ -814,7 +814,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3759" + "source": "src/triggers/api.ts:3787" }, { "surface_id": "REST:GET:/agentmemory/observations", @@ -846,7 +846,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4711" + "source": "src/triggers/api.ts:4739" }, { "surface_id": "REST:POST:/agentmemory/patterns", @@ -866,7 +866,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3343" + "source": "src/triggers/api.ts:3371" }, { "surface_id": "REST:GET:/agentmemory/profile", @@ -922,7 +922,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4730" + "source": "src/triggers/api.ts:4758" }, { "surface_id": "REST:GET:/agentmemory/relations", @@ -932,7 +932,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3357" + "source": "src/triggers/api.ts:3385" }, { "surface_id": "REST:POST:/agentmemory/relations", @@ -992,7 +992,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3854" + "source": "src/triggers/api.ts:3882" }, { "surface_id": "REST:POST:/agentmemory/routines", @@ -1002,7 +1002,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3838" + "source": "src/triggers/api.ts:3866" }, { "surface_id": "REST:POST:/agentmemory/routines/run", @@ -1012,7 +1012,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3873" + "source": "src/triggers/api.ts:3901" }, { "surface_id": "REST:GET:/agentmemory/routines/status", @@ -1022,7 +1022,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3891" + "source": "src/triggers/api.ts:3919" }, { "surface_id": "REST:POST:/agentmemory/search", @@ -1044,7 +1044,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3329" + "source": "src/triggers/api.ts:3357" }, { "surface_id": "REST:GET:/agentmemory/sentinels", @@ -1054,7 +1054,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4350" + "source": "src/triggers/api.ts:4378" }, { "surface_id": "REST:POST:/agentmemory/sentinels", @@ -1064,7 +1064,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4313" + "source": "src/triggers/api.ts:4341" }, { "surface_id": "REST:POST:/agentmemory/sentinels/cancel", @@ -1074,7 +1074,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4341" + "source": "src/triggers/api.ts:4369" }, { "surface_id": "REST:POST:/agentmemory/sentinels/check", @@ -1084,7 +1084,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4331" + "source": "src/triggers/api.ts:4359" }, { "surface_id": "REST:POST:/agentmemory/sentinels/trigger", @@ -1094,7 +1094,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4323" + "source": "src/triggers/api.ts:4351" }, { "surface_id": "REST:GET:/agentmemory/session/by-commit", @@ -1162,7 +1162,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3940" + "source": "src/triggers/api.ts:3968" }, { "surface_id": "REST:POST:/agentmemory/signals/send", @@ -1172,7 +1172,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3916" + "source": "src/triggers/api.ts:3944" }, { "surface_id": "REST:GET:/agentmemory/sketches", @@ -1182,7 +1182,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4399" + "source": "src/triggers/api.ts:4427" }, { "surface_id": "REST:POST:/agentmemory/sketches", @@ -1192,7 +1192,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4360" + "source": "src/triggers/api.ts:4388" }, { "surface_id": "REST:POST:/agentmemory/sketches/add", @@ -1202,7 +1202,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4370" + "source": "src/triggers/api.ts:4398" }, { "surface_id": "REST:POST:/agentmemory/sketches/discard", @@ -1212,7 +1212,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4390" + "source": "src/triggers/api.ts:4418" }, { "surface_id": "REST:POST:/agentmemory/sketches/gc", @@ -1222,7 +1222,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4407" + "source": "src/triggers/api.ts:4435" }, { "surface_id": "REST:POST:/agentmemory/sketches/promote", @@ -1232,7 +1232,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4380" + "source": "src/triggers/api.ts:4408" }, { "surface_id": "REST:DELETE:/agentmemory/slot", @@ -1242,7 +1242,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3588" + "source": "src/triggers/api.ts:3616" }, { "surface_id": "REST:GET:/agentmemory/slot", @@ -1252,7 +1252,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3467" + "source": "src/triggers/api.ts:3495" }, { "surface_id": "REST:POST:/agentmemory/slot", @@ -1262,7 +1262,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3518" + "source": "src/triggers/api.ts:3546" }, { "surface_id": "REST:POST:/agentmemory/slot/append", @@ -1272,7 +1272,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3542" + "source": "src/triggers/api.ts:3570" }, { "surface_id": "REST:POST:/agentmemory/slot/reflect", @@ -1282,7 +1282,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3610" + "source": "src/triggers/api.ts:3638" }, { "surface_id": "REST:POST:/agentmemory/slot/replace", @@ -1292,7 +1292,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3568" + "source": "src/triggers/api.ts:3596" }, { "surface_id": "REST:GET:/agentmemory/slots", @@ -1302,7 +1302,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3447" + "source": "src/triggers/api.ts:3475" }, { "surface_id": "REST:POST:/agentmemory/smart-search", @@ -1322,7 +1322,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3189" + "source": "src/triggers/api.ts:3217" }, { "surface_id": "REST:POST:/agentmemory/snapshot/restore", @@ -1332,7 +1332,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3210" + "source": "src/triggers/api.ts:3238" }, { "surface_id": "REST:GET:/agentmemory/snapshots", @@ -1342,7 +1342,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3170" + "source": "src/triggers/api.ts:3198" }, { "surface_id": "REST:POST:/agentmemory/summarize", @@ -1364,7 +1364,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3023" + "source": "src/triggers/api.ts:3051" }, { "surface_id": "REST:GET:/agentmemory/team/profile", @@ -1374,7 +1374,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3041" + "source": "src/triggers/api.ts:3069" }, { "surface_id": "REST:POST:/agentmemory/team/share", @@ -1384,7 +1384,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3003" + "source": "src/triggers/api.ts:3031" }, { "surface_id": "REST:POST:/agentmemory/timeline", @@ -1404,7 +1404,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4552" + "source": "src/triggers/api.ts:4580" }, { "surface_id": "REST:GET:/agentmemory/viewer", @@ -1414,7 +1414,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4299" + "source": "src/triggers/api.ts:4327" }, { "surface_id": "REST:POST:/agentmemory/vision-embed", @@ -1424,7 +1424,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3432" + "source": "src/triggers/api.ts:3460" }, { "surface_id": "REST:POST:/agentmemory/vision-search", @@ -1434,7 +1434,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:3400" + "source": "src/triggers/api.ts:3428" } ], "mcp_transport": [ @@ -2222,28 +2222,28 @@ ], "provider_attempt_sites": [ { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:159:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:163:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:159" + "source": "src/cli/connect/index.ts:163" }, { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:176:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:180:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:176" + "source": "src/cli/connect/index.ts:180" }, { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:189:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:193:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:189" + "source": "src/cli/connect/index.ts:193" }, { "surface_id": "PROVIDER:ATTEMPT:src/eval/self-correct.ts:14:compress", @@ -2655,7 +2655,7 @@ { "surface_id": "REST:GET:/agentmemory/actions", "type": "rest", - "source": "src/triggers/api.ts:3682", + "source": "src/triggers/api.ts:3710", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2680,7 +2680,7 @@ { "surface_id": "REST:POST:/agentmemory/actions", "type": "rest", - "source": "src/triggers/api.ts:3638", + "source": "src/triggers/api.ts:3666", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2705,7 +2705,7 @@ { "surface_id": "REST:POST:/agentmemory/actions/edges", "type": "rest", - "source": "src/triggers/api.ts:3723", + "source": "src/triggers/api.ts:3751", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2730,7 +2730,7 @@ { "surface_id": "REST:GET:/agentmemory/actions/get", "type": "rest", - "source": "src/triggers/api.ts:3700", + "source": "src/triggers/api.ts:3728", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2755,7 +2755,7 @@ { "surface_id": "REST:POST:/agentmemory/actions/update", "type": "rest", - "source": "src/triggers/api.ts:3664", + "source": "src/triggers/api.ts:3692", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2780,7 +2780,7 @@ { "surface_id": "REST:GET:/agentmemory/audit", "type": "rest", - "source": "src/triggers/api.ts:3079", + "source": "src/triggers/api.ts:3107", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2830,7 +2830,7 @@ { "surface_id": "REST:GET:/agentmemory/branch/detect", "type": "rest", - "source": "src/triggers/api.ts:4239", + "source": "src/triggers/api.ts:4267", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2855,7 +2855,7 @@ { "surface_id": "REST:GET:/agentmemory/branch/sessions", "type": "rest", - "source": "src/triggers/api.ts:4269", + "source": "src/triggers/api.ts:4297", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2880,7 +2880,7 @@ { "surface_id": "REST:GET:/agentmemory/branch/worktrees", "type": "rest", - "source": "src/triggers/api.ts:4254", + "source": "src/triggers/api.ts:4282", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2905,7 +2905,7 @@ { "surface_id": "REST:POST:/agentmemory/cascade-update", "type": "rest", - "source": "src/triggers/api.ts:4564", + "source": "src/triggers/api.ts:4592", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2930,7 +2930,7 @@ { "surface_id": "REST:GET:/agentmemory/checkpoints", "type": "rest", - "source": "src/triggers/api.ts:4006", + "source": "src/triggers/api.ts:4034", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2955,7 +2955,7 @@ { "surface_id": "REST:POST:/agentmemory/checkpoints", "type": "rest", - "source": "src/triggers/api.ts:3965", + "source": "src/triggers/api.ts:3993", "auth_control": "required", "control_ids": [ "ICM-04", @@ -2980,7 +2980,7 @@ { "surface_id": "REST:POST:/agentmemory/checkpoints/resolve", "type": "rest", - "source": "src/triggers/api.ts:3989", + "source": "src/triggers/api.ts:4017", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3170,7 +3170,7 @@ { "surface_id": "REST:POST:/agentmemory/consolidate-pipeline", "type": "rest", - "source": "src/triggers/api.ts:2974", + "source": "src/triggers/api.ts:3002", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3270,7 +3270,7 @@ { "surface_id": "REST:GET:/agentmemory/crystals", "type": "rest", - "source": "src/triggers/api.ts:4464", + "source": "src/triggers/api.ts:4492", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3295,7 +3295,7 @@ { "surface_id": "REST:POST:/agentmemory/crystals/auto", "type": "rest", - "source": "src/triggers/api.ts:4476", + "source": "src/triggers/api.ts:4504", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3320,7 +3320,7 @@ { "surface_id": "REST:POST:/agentmemory/crystals/create", "type": "rest", - "source": "src/triggers/api.ts:4422", + "source": "src/triggers/api.ts:4450", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3345,7 +3345,7 @@ { "surface_id": "REST:POST:/agentmemory/diagnostics", "type": "rest", - "source": "src/triggers/api.ts:4485", + "source": "src/triggers/api.ts:4513", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3395,7 +3395,7 @@ { "surface_id": "REST:POST:/agentmemory/diagnostics/heal", "type": "rest", - "source": "src/triggers/api.ts:4494", + "source": "src/triggers/api.ts:4522", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3520,7 +3520,7 @@ { "surface_id": "REST:GET:/agentmemory/facets", "type": "rest", - "source": "src/triggers/api.ts:4533", + "source": "src/triggers/api.ts:4561", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3545,7 +3545,7 @@ { "surface_id": "REST:POST:/agentmemory/facets", "type": "rest", - "source": "src/triggers/api.ts:4504", + "source": "src/triggers/api.ts:4532", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3570,7 +3570,7 @@ { "surface_id": "REST:POST:/agentmemory/facets/query", "type": "rest", - "source": "src/triggers/api.ts:4523", + "source": "src/triggers/api.ts:4551", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3595,7 +3595,7 @@ { "surface_id": "REST:POST:/agentmemory/facets/remove", "type": "rest", - "source": "src/triggers/api.ts:4514", + "source": "src/triggers/api.ts:4542", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3620,7 +3620,7 @@ { "surface_id": "REST:GET:/agentmemory/facets/stats", "type": "rest", - "source": "src/triggers/api.ts:4542", + "source": "src/triggers/api.ts:4570", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3670,7 +3670,7 @@ { "surface_id": "REST:POST:/agentmemory/flow/compress", "type": "rest", - "source": "src/triggers/api.ts:4224", + "source": "src/triggers/api.ts:4252", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3720,7 +3720,7 @@ { "surface_id": "REST:GET:/agentmemory/frontier", "type": "rest", - "source": "src/triggers/api.ts:3742", + "source": "src/triggers/api.ts:3770", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3770,7 +3770,7 @@ { "surface_id": "REST:POST:/agentmemory/governance/bulk-delete", "type": "rest", - "source": "src/triggers/api.ts:3149", + "source": "src/triggers/api.ts:3177", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3795,7 +3795,7 @@ { "surface_id": "REST:DELETE:/agentmemory/governance/memories", "type": "rest", - "source": "src/triggers/api.ts:3124", + "source": "src/triggers/api.ts:3152", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3820,7 +3820,7 @@ { "surface_id": "REST:POST:/agentmemory/graph/build", "type": "rest", - "source": "src/triggers/api.ts:2940", + "source": "src/triggers/api.ts:2968", "auth_control": "required", "control_ids": [ "ICM-04", @@ -3870,7 +3870,7 @@ { "surface_id": "REST:POST:/agentmemory/graph/import-graphify", "type": "rest", - "source": "src/triggers/api.ts:2813", + "source": "src/triggers/api.ts:2841", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4061,7 +4061,7 @@ { "surface_id": "REST:GET:/agentmemory/insights", "type": "rest", - "source": "src/triggers/api.ts:4764", + "source": "src/triggers/api.ts:4792", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4081,7 +4081,7 @@ { "surface_id": "REST:POST:/agentmemory/insights/search", "type": "rest", - "source": "src/triggers/api.ts:4786", + "source": "src/triggers/api.ts:4814", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4101,7 +4101,7 @@ { "surface_id": "REST:POST:/agentmemory/leases/acquire", "type": "rest", - "source": "src/triggers/api.ts:3778", + "source": "src/triggers/api.ts:3806", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4126,7 +4126,7 @@ { "surface_id": "REST:POST:/agentmemory/leases/release", "type": "rest", - "source": "src/triggers/api.ts:3797", + "source": "src/triggers/api.ts:3825", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4151,7 +4151,7 @@ { "surface_id": "REST:POST:/agentmemory/leases/renew", "type": "rest", - "source": "src/triggers/api.ts:3816", + "source": "src/triggers/api.ts:3844", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4176,7 +4176,7 @@ { "surface_id": "REST:GET:/agentmemory/lessons", "type": "rest", - "source": "src/triggers/api.ts:4628", + "source": "src/triggers/api.ts:4656", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4196,7 +4196,7 @@ { "surface_id": "REST:POST:/agentmemory/lessons", "type": "rest", - "source": "src/triggers/api.ts:4593", + "source": "src/triggers/api.ts:4621", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4216,7 +4216,7 @@ { "surface_id": "REST:POST:/agentmemory/lessons/delete", "type": "rest", - "source": "src/triggers/api.ts:4694", + "source": "src/triggers/api.ts:4722", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4236,7 +4236,7 @@ { "surface_id": "REST:POST:/agentmemory/lessons/search", "type": "rest", - "source": "src/triggers/api.ts:4648", + "source": "src/triggers/api.ts:4676", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4256,7 +4256,7 @@ { "surface_id": "REST:POST:/agentmemory/lessons/strengthen", "type": "rest", - "source": "src/triggers/api.ts:4679", + "source": "src/triggers/api.ts:4707", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4301,7 +4301,7 @@ { "surface_id": "REST:GET:/agentmemory/memories", "type": "rest", - "source": "src/triggers/api.ts:3294", + "source": "src/triggers/api.ts:3322", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4326,7 +4326,7 @@ { "surface_id": "REST:GET:/agentmemory/memories/:id", "type": "rest", - "source": "src/triggers/api.ts:3315", + "source": "src/triggers/api.ts:3343", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4351,7 +4351,7 @@ { "surface_id": "REST:GET:/agentmemory/mesh/export", "type": "rest", - "source": "src/triggers/api.ts:4197", + "source": "src/triggers/api.ts:4225", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4376,7 +4376,7 @@ { "surface_id": "REST:GET:/agentmemory/mesh/peers", "type": "rest", - "source": "src/triggers/api.ts:4051", + "source": "src/triggers/api.ts:4079", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4401,7 +4401,7 @@ { "surface_id": "REST:POST:/agentmemory/mesh/peers", "type": "rest", - "source": "src/triggers/api.ts:4031", + "source": "src/triggers/api.ts:4059", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4426,7 +4426,7 @@ { "surface_id": "REST:POST:/agentmemory/mesh/receive", "type": "rest", - "source": "src/triggers/api.ts:4093", + "source": "src/triggers/api.ts:4121", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4451,7 +4451,7 @@ { "surface_id": "REST:POST:/agentmemory/mesh/sync", "type": "rest", - "source": "src/triggers/api.ts:4073", + "source": "src/triggers/api.ts:4101", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4497,7 +4497,7 @@ { "surface_id": "REST:GET:/agentmemory/next", "type": "rest", - "source": "src/triggers/api.ts:3759", + "source": "src/triggers/api.ts:3787", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4572,7 +4572,7 @@ { "surface_id": "REST:POST:/agentmemory/obsidian/export", "type": "rest", - "source": "src/triggers/api.ts:4711", + "source": "src/triggers/api.ts:4739", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4622,7 +4622,7 @@ { "surface_id": "REST:GET:/agentmemory/procedural", "type": "rest", - "source": "src/triggers/api.ts:3343", + "source": "src/triggers/api.ts:3371", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4736,7 +4736,7 @@ { "surface_id": "REST:POST:/agentmemory/reflect", "type": "rest", - "source": "src/triggers/api.ts:4730", + "source": "src/triggers/api.ts:4758", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4761,7 +4761,7 @@ { "surface_id": "REST:GET:/agentmemory/relations", "type": "rest", - "source": "src/triggers/api.ts:3357", + "source": "src/triggers/api.ts:3385", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4906,7 +4906,7 @@ { "surface_id": "REST:GET:/agentmemory/routines", "type": "rest", - "source": "src/triggers/api.ts:3854", + "source": "src/triggers/api.ts:3882", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4931,7 +4931,7 @@ { "surface_id": "REST:POST:/agentmemory/routines", "type": "rest", - "source": "src/triggers/api.ts:3838", + "source": "src/triggers/api.ts:3866", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4956,7 +4956,7 @@ { "surface_id": "REST:POST:/agentmemory/routines/run", "type": "rest", - "source": "src/triggers/api.ts:3873", + "source": "src/triggers/api.ts:3901", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4981,7 +4981,7 @@ { "surface_id": "REST:GET:/agentmemory/routines/status", "type": "rest", - "source": "src/triggers/api.ts:3891", + "source": "src/triggers/api.ts:3919", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5031,7 +5031,7 @@ { "surface_id": "REST:GET:/agentmemory/semantic", "type": "rest", - "source": "src/triggers/api.ts:3329", + "source": "src/triggers/api.ts:3357", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5056,7 +5056,7 @@ { "surface_id": "REST:GET:/agentmemory/sentinels", "type": "rest", - "source": "src/triggers/api.ts:4350", + "source": "src/triggers/api.ts:4378", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5081,7 +5081,7 @@ { "surface_id": "REST:POST:/agentmemory/sentinels", "type": "rest", - "source": "src/triggers/api.ts:4313", + "source": "src/triggers/api.ts:4341", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5106,7 +5106,7 @@ { "surface_id": "REST:POST:/agentmemory/sentinels/cancel", "type": "rest", - "source": "src/triggers/api.ts:4341", + "source": "src/triggers/api.ts:4369", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5131,7 +5131,7 @@ { "surface_id": "REST:POST:/agentmemory/sentinels/check", "type": "rest", - "source": "src/triggers/api.ts:4331", + "source": "src/triggers/api.ts:4359", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5156,7 +5156,7 @@ { "surface_id": "REST:POST:/agentmemory/sentinels/trigger", "type": "rest", - "source": "src/triggers/api.ts:4323", + "source": "src/triggers/api.ts:4351", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5296,7 +5296,7 @@ { "surface_id": "REST:GET:/agentmemory/signals", "type": "rest", - "source": "src/triggers/api.ts:3940", + "source": "src/triggers/api.ts:3968", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5321,7 +5321,7 @@ { "surface_id": "REST:POST:/agentmemory/signals/send", "type": "rest", - "source": "src/triggers/api.ts:3916", + "source": "src/triggers/api.ts:3944", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5346,7 +5346,7 @@ { "surface_id": "REST:GET:/agentmemory/sketches", "type": "rest", - "source": "src/triggers/api.ts:4399", + "source": "src/triggers/api.ts:4427", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5371,7 +5371,7 @@ { "surface_id": "REST:POST:/agentmemory/sketches", "type": "rest", - "source": "src/triggers/api.ts:4360", + "source": "src/triggers/api.ts:4388", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5396,7 +5396,7 @@ { "surface_id": "REST:POST:/agentmemory/sketches/add", "type": "rest", - "source": "src/triggers/api.ts:4370", + "source": "src/triggers/api.ts:4398", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5421,7 +5421,7 @@ { "surface_id": "REST:POST:/agentmemory/sketches/discard", "type": "rest", - "source": "src/triggers/api.ts:4390", + "source": "src/triggers/api.ts:4418", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5446,7 +5446,7 @@ { "surface_id": "REST:POST:/agentmemory/sketches/gc", "type": "rest", - "source": "src/triggers/api.ts:4407", + "source": "src/triggers/api.ts:4435", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5471,7 +5471,7 @@ { "surface_id": "REST:POST:/agentmemory/sketches/promote", "type": "rest", - "source": "src/triggers/api.ts:4380", + "source": "src/triggers/api.ts:4408", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5496,7 +5496,7 @@ { "surface_id": "REST:DELETE:/agentmemory/slot", "type": "rest", - "source": "src/triggers/api.ts:3588", + "source": "src/triggers/api.ts:3616", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5520,7 +5520,7 @@ { "surface_id": "REST:GET:/agentmemory/slot", "type": "rest", - "source": "src/triggers/api.ts:3467", + "source": "src/triggers/api.ts:3495", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5544,7 +5544,7 @@ { "surface_id": "REST:POST:/agentmemory/slot", "type": "rest", - "source": "src/triggers/api.ts:3518", + "source": "src/triggers/api.ts:3546", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5568,7 +5568,7 @@ { "surface_id": "REST:POST:/agentmemory/slot/append", "type": "rest", - "source": "src/triggers/api.ts:3542", + "source": "src/triggers/api.ts:3570", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5592,7 +5592,7 @@ { "surface_id": "REST:POST:/agentmemory/slot/reflect", "type": "rest", - "source": "src/triggers/api.ts:3610", + "source": "src/triggers/api.ts:3638", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5616,7 +5616,7 @@ { "surface_id": "REST:POST:/agentmemory/slot/replace", "type": "rest", - "source": "src/triggers/api.ts:3568", + "source": "src/triggers/api.ts:3596", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5640,7 +5640,7 @@ { "surface_id": "REST:GET:/agentmemory/slots", "type": "rest", - "source": "src/triggers/api.ts:3447", + "source": "src/triggers/api.ts:3475", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5689,7 +5689,7 @@ { "surface_id": "REST:POST:/agentmemory/snapshot/create", "type": "rest", - "source": "src/triggers/api.ts:3189", + "source": "src/triggers/api.ts:3217", "auth_control": "required", "control_ids": [ "ICM-13" @@ -5710,7 +5710,7 @@ { "surface_id": "REST:POST:/agentmemory/snapshot/restore", "type": "rest", - "source": "src/triggers/api.ts:3210", + "source": "src/triggers/api.ts:3238", "auth_control": "required", "control_ids": [ "ICM-13" @@ -5731,7 +5731,7 @@ { "surface_id": "REST:GET:/agentmemory/snapshots", "type": "rest", - "source": "src/triggers/api.ts:3170", + "source": "src/triggers/api.ts:3198", "auth_control": "required", "control_ids": [ "ICM-13" @@ -5777,7 +5777,7 @@ { "surface_id": "REST:GET:/agentmemory/team/feed", "type": "rest", - "source": "src/triggers/api.ts:3023", + "source": "src/triggers/api.ts:3051", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5802,7 +5802,7 @@ { "surface_id": "REST:GET:/agentmemory/team/profile", "type": "rest", - "source": "src/triggers/api.ts:3041", + "source": "src/triggers/api.ts:3069", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5827,7 +5827,7 @@ { "surface_id": "REST:POST:/agentmemory/team/share", "type": "rest", - "source": "src/triggers/api.ts:3003", + "source": "src/triggers/api.ts:3031", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5877,7 +5877,7 @@ { "surface_id": "REST:POST:/agentmemory/verify", "type": "rest", - "source": "src/triggers/api.ts:4552", + "source": "src/triggers/api.ts:4580", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5902,7 +5902,7 @@ { "surface_id": "REST:GET:/agentmemory/viewer", "type": "rest", - "source": "src/triggers/api.ts:4299", + "source": "src/triggers/api.ts:4327", "auth_control": "required", "control_ids": [ "ICM-11", @@ -5926,7 +5926,7 @@ { "surface_id": "REST:POST:/agentmemory/vision-embed", "type": "rest", - "source": "src/triggers/api.ts:3432", + "source": "src/triggers/api.ts:3460", "auth_control": "required", "control_ids": [ "ICM-04", @@ -5951,7 +5951,7 @@ { "surface_id": "REST:POST:/agentmemory/vision-search", "type": "rest", - "source": "src/triggers/api.ts:3400", + "source": "src/triggers/api.ts:3428", "auth_control": "required", "control_ids": [ "ICM-05", @@ -10663,12 +10663,12 @@ ] }, { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:159:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:163:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:159", + "source": "src/cli/connect/index.ts:163", "type": "provider-attempt", "auth_control": "processing-policy", "control_ids": [ @@ -10689,12 +10689,12 @@ ] }, { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:176:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:180:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:176", + "source": "src/cli/connect/index.ts:180", "type": "provider-attempt", "auth_control": "processing-policy", "control_ids": [ @@ -10715,12 +10715,12 @@ ] }, { - "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:189:summarize", + "surface_id": "PROVIDER:ATTEMPT:src/cli/connect/index.ts:193:summarize", "purpose": "query", "kind": "invocation", "method": "summarize", "receiver": null, - "source": "src/cli/connect/index.ts:189", + "source": "src/cli/connect/index.ts:193", "type": "provider-attempt", "auth_control": "processing-policy", "control_ids": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index dff405935..6d4c9f15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,15 @@ Adversarial-review fixes over the shipped sync wave. ### Fixed -- **Graphify import path scoping** (security). An explicit path on `mem::graph::import-graphify` (`POST /agentmemory/graph/import-graphify`) could point anywhere the daemon can read. Explicit paths must now resolve inside the requested project cwd and keep the `graph.json` basename; violations return a generic error that never echoes the attempted path, and stat failures for explicit paths stay equally non-specific. +- **Graphify import path scoping** (security). An explicit `path`/`cwd` on `mem::graph::import-graphify` (`POST /agentmemory/graph/import-graphify`) could point anywhere the daemon can read — both fields were client-controlled, and a symlinked `graph.json` escaped the containment check. Explicit path/cwd imports are now ADMIN-only: the REST route requires administrative authorization whenever a request carries either field, while capability callers import the default `/graphify-out/graph.json` (failing honestly when absent). The engine anchors its computed default to its own working directory, ignores caller-supplied cwd values, keeps the `graph.json` basename and under-daemon-cwd checks as defense in depth; violations return a generic error that never echoes the attempted path, and stat failures for explicit paths stay equally non-specific. - **Credential redaction in the identity-fallback warning** (security). The stderr warning for an unnormalizable git remote wrote the raw `git remote get-url` output, leaking embedded passwords; remotes are masked before logging (scheme and host/path preserved, credentials replaced). - **Project capability secret hardening** (security). Zero-touch provisioning creates the credential directory with mode 0700, refuses a symlink parked at the credential path instead of following it, keeps a pre-existing populated secret when only its permission tightening fails, and removes a freshly written secret whose securing chmod failed rather than leaving it readable. - **`--data-dir` honored during `.env` hydration** (correctness). The CLI hydrated `/.env` before folding the flag into the environment, so a flagged run silently read its `.env` from the default `~/.agentmemory`; the fold now happens first. - **Standalone MCP persist path follows the data dir** (correctness). The shim kept a duplicate resolver pinned to `~/.agentmemory`; it now delegates to the shared config resolver after folding `.env`, preserving the `STANDALONE_PERSIST_PATH` override while honoring `AGENTMEMORY_DATA_DIR`. -- **Consolidation cooldown released on rejected dispatch** (correctness). A rejected `mem::consolidate-pipeline` / `mem::auto-crystallize` Void dispatch left the cooldown marker standing for the whole window with no pipeline behind it; rejection handlers clear the marker best-effort so the next eligible stop retries. +- **Consolidation cooldown released on rejected dispatch** (correctness). A rejected `mem::consolidate-pipeline` / `mem::auto-crystallize` Void dispatch left the cooldown marker standing for the whole window with no pipeline behind it; rejection handlers clear the marker best-effort so the next eligible stop retries. Follow-up hardening closed two races in that release: each dispatch now claims the marker with its own token and deletes on rejection only when the marker still carries it, so a rejected crystallize can no longer clear the debounce under a still-running pipeline, and a late rejection from an older cycle cannot erase a newer cycle's fresh marker. +- **CLI boot ordering** (correctness). `--help` printed usage after the legacy-data-dir warning ran, emitting spurious stderr noise on every help invocation; help now exits right after flag parsing. The legacy warning itself moved after `.env` hydration so an `AGENTMEMORY_DATA_DIR` declared only in `/.env` suppresses it instead of producing a false alarm. +- **Standalone persist path trim restored** (correctness). A whitespace-only `STANDALONE_PERSIST_PATH` was honored verbatim as a relative file path; the value is trimmed first and empty results fall back to `/standalone.json`. +- **Capability provisioning failures are logged** (info). `connect` and onboarding swallowed credential-provisioning errors silently; both now write one stderr line pointing at `agentmemory doctor`, keeping the never-block-wiring semantics. - **Origin provenance on peer Memory upserts** (security). Mesh receive/pull wrote peer memories without provenance; records lacking an Origin now gain shared-channel provenance via the shared keep-or-mark factory, and peer-provided origins are preserved. - **Search-index readiness after startup reconciliation** (perf). Reconciliation walks the full memory corpus but never marked the index ready, forcing live saves onto full-scan fallbacks until an explicit rebuild; success now sets the same readiness flag a rebuild does. - **Per-project user overrides follow the data dir** (info). User project config overrides are read from `/projects/.yaml`, falling back to the legacy `~/.agentmemory/projects/.yaml` copy so existing overrides keep working. diff --git a/ci/r13-test-manifest.json b/ci/r13-test-manifest.json index 67712212b..0d1068d2a 100644 --- a/ci/r13-test-manifest.json +++ b/ci/r13-test-manifest.json @@ -1,5 +1,5 @@ { "count": 176, "sha256": "4ef314289367720f8cbe199ca8f2823eea416ec3fa0c861fefcbaf0550d530f1", - "content_sha256": "542b9f58957d1ac9fa9421abb47bbd4a985dae5a3a1fe6ee4b2f1916d527693e" + "content_sha256": "87a73e351114bac87891494dcb5115636a51608b7c7f8cc0f79ca8809a13145d" } diff --git a/package-lock.json b/package-lock.json index fa874c898..df4a835e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agentmemory/agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agentmemory/agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "license": "Apache-2.0", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.3.142", diff --git a/package.json b/package.json index 022a09199..c102528f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agentmemory/agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives", "type": "module", "main": "dist/index.mjs", diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 66a08efb9..97dfb4db2 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@agentmemory/mcp", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "description": "Standalone MCP server for agentmemory — thin shim that re-exposes @agentmemory/agentmemory's MCP entrypoint", "type": "module", "bin": { @@ -28,7 +28,7 @@ "homepage": "https://github.com/rohitg00/agentmemory#readme", "bugs": "https://github.com/rohitg00/agentmemory/issues", "dependencies": { - "@agentmemory/agentmemory": "0.9.30-chronode.2" + "@agentmemory/agentmemory": "0.9.30-chronode.3" }, "publishConfig": { "access": "public", diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index beb559157..f04e47b97 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 17 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", diff --git a/plugin/.codex-plugin/plugin.json b/plugin/.codex-plugin/plugin.json index bdbb95f81..63a581e3d 100644 --- a/plugin/.codex-plugin/plugin.json +++ b/plugin/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 11 hooks, 60 MCP tools, 17 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", diff --git a/plugin/plugin.json b/plugin/plugin.json index 3c662ec7f..d9d716781 100644 --- a/plugin/plugin.json +++ b/plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentmemory", - "version": "0.9.30-chronode.2", + "version": "0.9.30-chronode.3", "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 17 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", diff --git a/plugin/scripts/_auth-1Z57rc-e.mjs b/plugin/scripts/_auth-CBScPKV6.mjs similarity index 99% rename from plugin/scripts/_auth-1Z57rc-e.mjs rename to plugin/scripts/_auth-CBScPKV6.mjs index 823def7d0..5a8b59295 100644 --- a/plugin/scripts/_auth-1Z57rc-e.mjs +++ b/plugin/scripts/_auth-CBScPKV6.mjs @@ -1,4 +1,4 @@ -import { i as isStrictCapabilityMode, n as PROJECT_CAPABILITY_PROJECT_HEADER, o as hydrateProcessEnvFromFile, r as createProjectCapabilityToken, s as resolveDataDir } from "./auth-DkiaFluQ.mjs"; +import { i as isStrictCapabilityMode, n as PROJECT_CAPABILITY_PROJECT_HEADER, o as hydrateProcessEnvFromFile, r as createProjectCapabilityToken, s as resolveDataDir } from "./auth-DYDHBWPd.mjs"; import { createRequire } from "node:module"; import { existsSync, readFileSync, realpathSync } from "node:fs"; import { isAbsolute, join, relative, resolve } from "node:path"; diff --git a/plugin/scripts/_capture-Ba1NCNW7.mjs b/plugin/scripts/_capture-DH0HGaDe.mjs similarity index 99% rename from plugin/scripts/_capture-Ba1NCNW7.mjs rename to plugin/scripts/_capture-DH0HGaDe.mjs index 07d866062..8a2834e2e 100644 --- a/plugin/scripts/_capture-Ba1NCNW7.mjs +++ b/plugin/scripts/_capture-DH0HGaDe.mjs @@ -1,4 +1,4 @@ -import { a as normalizedProjectPath, r as isProjectPathExcluded } from "./_auth-1Z57rc-e.mjs"; +import { a as normalizedProjectPath, r as isProjectPathExcluded } from "./_auth-CBScPKV6.mjs"; import { resolve } from "node:path"; import { createHash } from "node:crypto"; import { execFileSync } from "node:child_process"; diff --git a/plugin/scripts/_delivery-BPnYIm56.mjs b/plugin/scripts/_delivery-Bevmy6M8.mjs similarity index 97% rename from plugin/scripts/_delivery-BPnYIm56.mjs rename to plugin/scripts/_delivery-Bevmy6M8.mjs index 57e463c17..55cafb397 100644 --- a/plugin/scripts/_delivery-BPnYIm56.mjs +++ b/plugin/scripts/_delivery-Bevmy6M8.mjs @@ -1,4 +1,4 @@ -import { n as projectAuthHeaders } from "./_auth-1Z57rc-e.mjs"; +import { n as projectAuthHeaders } from "./_auth-CBScPKV6.mjs"; //#region src/hooks/_delivery.ts var HookDeliveryError = class extends Error { retryable; diff --git a/plugin/scripts/_observe-delivery-BSYpE5r3.mjs b/plugin/scripts/_observe-delivery-BsKR4_co.mjs similarity index 97% rename from plugin/scripts/_observe-delivery-BSYpE5r3.mjs rename to plugin/scripts/_observe-delivery-BsKR4_co.mjs index 6e821def0..cfda1c291 100644 --- a/plugin/scripts/_observe-delivery-BSYpE5r3.mjs +++ b/plugin/scripts/_observe-delivery-BsKR4_co.mjs @@ -1,4 +1,4 @@ -import { n as projectAuthHeaders } from "./_auth-1Z57rc-e.mjs"; +import { n as projectAuthHeaders } from "./_auth-CBScPKV6.mjs"; //#region src/hooks/_observe-delivery.ts const MAX_ATTEMPTS = 2; const REQUEST_TIMEOUT_MS = 250; diff --git a/plugin/scripts/_project-BqDfPlX6.mjs b/plugin/scripts/_project-BQWFXz1a.mjs similarity index 95% rename from plugin/scripts/_project-BqDfPlX6.mjs rename to plugin/scripts/_project-BQWFXz1a.mjs index 6d2cd31eb..b302d3c03 100644 --- a/plugin/scripts/_project-BqDfPlX6.mjs +++ b/plugin/scripts/_project-BQWFXz1a.mjs @@ -1,4 +1,4 @@ -import { i as loadAgentmemoryEnvironment, o as resolveProjectConfig } from "./_auth-1Z57rc-e.mjs"; +import { i as loadAgentmemoryEnvironment, o as resolveProjectConfig } from "./_auth-CBScPKV6.mjs"; //#region src/hooks/_project.ts loadAgentmemoryEnvironment(); /** diff --git a/plugin/scripts/auth-DkiaFluQ.mjs b/plugin/scripts/auth-DYDHBWPd.mjs similarity index 96% rename from plugin/scripts/auth-DkiaFluQ.mjs rename to plugin/scripts/auth-DYDHBWPd.mjs index 86bdd5292..957b371ed 100644 --- a/plugin/scripts/auth-DkiaFluQ.mjs +++ b/plugin/scripts/auth-DYDHBWPd.mjs @@ -92,7 +92,8 @@ function getMergedEnv(overrides) { }; } function getStandalonePersistPath() { - return getMergedEnv()["STANDALONE_PERSIST_PATH"] || join(resolveDataDir(), "standalone.json"); + const configured = getMergedEnv()["STANDALONE_PERSIST_PATH"]?.trim(); + return configured ? configured : join(resolveDataDir(), "standalone.json"); } randomBytes(32); const PROJECT_CAPABILITY_TOKEN_VERSION = "amcap1"; diff --git a/plugin/scripts/notification.mjs b/plugin/scripts/notification.mjs index 24f6164df..90cc07d72 100755 --- a/plugin/scripts/notification.mjs +++ b/plugin/scripts/notification.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; //#region src/hooks/notification.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/post-commit.mjs b/plugin/scripts/post-commit.mjs index ce823e21b..e17b2b017 100755 --- a/plugin/scripts/post-commit.mjs +++ b/plugin/scripts/post-commit.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-BPnYIm56.mjs"; -import { n as credentialFreeWorktreeId, r as parseCommitTransitions } from "./_capture-Ba1NCNW7.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-Bevmy6M8.mjs"; +import { n as credentialFreeWorktreeId, r as parseCommitTransitions } from "./_capture-DH0HGaDe.mjs"; import { resolve } from "node:path"; import { execFile } from "node:child_process"; import { pathToFileURL } from "node:url"; diff --git a/plugin/scripts/post-tool-failure.mjs b/plugin/scripts/post-tool-failure.mjs index ead2742e3..ac76f0a96 100755 --- a/plugin/scripts/post-tool-failure.mjs +++ b/plugin/scripts/post-tool-failure.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { o as resolveProjectConfig } from "./_auth-1Z57rc-e.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; -import { t as captureToolEvent } from "./_capture-Ba1NCNW7.mjs"; +import { o as resolveProjectConfig } from "./_auth-CBScPKV6.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; +import { t as captureToolEvent } from "./_capture-DH0HGaDe.mjs"; //#region src/hooks/post-tool-failure.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/post-tool-use.mjs b/plugin/scripts/post-tool-use.mjs index db2d8c3f6..5288f60be 100755 --- a/plugin/scripts/post-tool-use.mjs +++ b/plugin/scripts/post-tool-use.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { o as resolveProjectConfig } from "./_auth-1Z57rc-e.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; -import { t as captureToolEvent } from "./_capture-Ba1NCNW7.mjs"; +import { o as resolveProjectConfig } from "./_auth-CBScPKV6.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; +import { t as captureToolEvent } from "./_capture-DH0HGaDe.mjs"; //#region src/hooks/post-tool-use.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/pre-compact.mjs b/plugin/scripts/pre-compact.mjs index 0d245fcb4..ee3f13222 100755 --- a/plugin/scripts/pre-compact.mjs +++ b/plugin/scripts/pre-compact.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { i as loadAgentmemoryEnvironment, n as projectAuthHeaders, t as contextAcknowledgementSecret } from "./_auth-1Z57rc-e.mjs"; -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; +import { i as loadAgentmemoryEnvironment, n as projectAuthHeaders, t as contextAcknowledgementSecret } from "./_auth-CBScPKV6.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; import { createHash, createHmac, randomUUID } from "node:crypto"; //#region src/hooks/pre-compact.ts function isSdkChildContext(payload) { diff --git a/plugin/scripts/pre-tool-use.mjs b/plugin/scripts/pre-tool-use.mjs index 10c5ba596..154616431 100755 --- a/plugin/scripts/pre-tool-use.mjs +++ b/plugin/scripts/pre-tool-use.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { i as loadAgentmemoryEnvironment, n as projectAuthHeaders } from "./_auth-1Z57rc-e.mjs"; -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; +import { i as loadAgentmemoryEnvironment, n as projectAuthHeaders } from "./_auth-CBScPKV6.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; //#region src/hooks/pre-tool-use.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/prompt-submit.mjs b/plugin/scripts/prompt-submit.mjs index ecf6d6593..4ff126221 100755 --- a/plugin/scripts/prompt-submit.mjs +++ b/plugin/scripts/prompt-submit.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; //#region src/hooks/prompt-submit.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/session-end.mjs b/plugin/scripts/session-end.mjs index 8e0c13794..ac812eb8f 100755 --- a/plugin/scripts/session-end.mjs +++ b/plugin/scripts/session-end.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-BPnYIm56.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-Bevmy6M8.mjs"; //#region src/hooks/session-end.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/session-start.mjs b/plugin/scripts/session-start.mjs index 66863564d..255c4480c 100755 --- a/plugin/scripts/session-start.mjs +++ b/plugin/scripts/session-start.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { o as resolveProjectConfig } from "./_auth-1Z57rc-e.mjs"; -import "./_project-BqDfPlX6.mjs"; -import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-BPnYIm56.mjs"; +import { o as resolveProjectConfig } from "./_auth-CBScPKV6.mjs"; +import "./_project-BQWFXz1a.mjs"; +import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-Bevmy6M8.mjs"; //#region src/hooks/session-start.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/standalone.mjs b/plugin/scripts/standalone.mjs index 24c8c243b..877fc0b06 100755 --- a/plugin/scripts/standalone.mjs +++ b/plugin/scripts/standalone.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node import { n as generateId, t as KV } from "./schema-Dttua2Zo.mjs"; -import { a as getStandalonePersistPath$1, i as isStrictCapabilityMode, n as PROJECT_CAPABILITY_PROJECT_HEADER, o as hydrateProcessEnvFromFile, r as createProjectCapabilityToken } from "./auth-DkiaFluQ.mjs"; +import { a as getStandalonePersistPath$1, i as isStrictCapabilityMode, n as PROJECT_CAPABILITY_PROJECT_HEADER, o as hydrateProcessEnvFromFile, r as createProjectCapabilityToken } from "./auth-DYDHBWPd.mjs"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { homedir } from "node:os"; @@ -1761,7 +1761,7 @@ function getAllTools() { } //#endregion //#region src/version.ts -const VERSION = "0.9.30-chronode.2"; +const VERSION = "0.9.30-chronode.3"; process.env["AGENTMEMORY_BUILD_ID"]; process.env["AGENTMEMORY_VIEWER_BUILD_ID"]; //#endregion diff --git a/plugin/scripts/stop.mjs b/plugin/scripts/stop.mjs index 2f94ce8ec..206a8371c 100755 --- a/plugin/scripts/stop.mjs +++ b/plugin/scripts/stop.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-BPnYIm56.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportHookDeliveryFailure, t as deliverProjectRequest } from "./_delivery-Bevmy6M8.mjs"; //#region src/hooks/stop.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/subagent-start.mjs b/plugin/scripts/subagent-start.mjs index d8f0d716d..6ec063c11 100755 --- a/plugin/scripts/subagent-start.mjs +++ b/plugin/scripts/subagent-start.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; //#region src/hooks/subagent-start.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/subagent-stop.mjs b/plugin/scripts/subagent-stop.mjs index f87a72123..9aa6523a3 100755 --- a/plugin/scripts/subagent-stop.mjs +++ b/plugin/scripts/subagent-stop.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; //#region src/hooks/subagent-stop.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/plugin/scripts/task-completed.mjs b/plugin/scripts/task-completed.mjs index aa8e1aba9..f75d9e780 100755 --- a/plugin/scripts/task-completed.mjs +++ b/plugin/scripts/task-completed.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { t as resolveProject } from "./_project-BqDfPlX6.mjs"; -import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BSYpE5r3.mjs"; +import { t as resolveProject } from "./_project-BQWFXz1a.mjs"; +import { n as reportObservationDeliveryFailure, t as deliverObservation } from "./_observe-delivery-BsKR4_co.mjs"; //#region src/hooks/task-completed.ts function isSdkChildContext(payload) { if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; diff --git a/src/cli.ts b/src/cli.ts index e2f9dad30..5969aae63 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -91,6 +91,11 @@ import { const ALL_TOOLS_COUNT = getAllTools().length; const CORE_TOOLS_COUNT = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length; +// Default pinned iii-engine version. Declared before boot sequencing so the +// --help text can name it without reaching the hydrated-environment const +// below (help exits before hydration by design). +const III_PIN_DEFAULT = "0.11.2"; + const __dirname = dirname(fileURLToPath(import.meta.url)); const args = process.argv.slice(2); const IS_WINDOWS = platform() === "win32"; @@ -127,80 +132,9 @@ const dataDirFlagValue = readDataDirFlag(args); if (dataDirFlagValue !== undefined && dataDirFlagValue.trim().length > 0) { process.env["AGENTMEMORY_DATA_DIR"] = dataDirFlagValue.trim(); } -// Upstream adopts a legacy cwd ./data store automatically; this fork does -// not. When state would silently land somewhere different from an existing -// local ./data store, tell the operator how to opt in explicitly. -warnOnLegacyDataDir(); - -// Fold ~/.agentmemory/.env into process.env before anything reads config -// from the environment (the iii version pin, --tools/--port/--instance -// handling, engine boot). Fill-missing-only: a real process.env value — -// including one set by a CLI flag above — always wins over the file. -hydrateProcessEnvFromFile(); - -// Pinned iii-engine version. The unpinned `install.iii.dev/iii/main/install.sh` -// script tracks `latest`, which made every fresh agentmemory install pull -// engine 0.11.6 — and 0.11.6 introduces a new sandbox-everything-via- -// `iii worker add` worker model that agentmemory hasn't been refactored -// for yet (we still use the pre-sandbox worker registration model). The -// architectural mismatch surfaces as EPIPE reconnect loops and empty -// search results after save. Pin to v0.11.2 — the last engine that runs -// agentmemory's current worker model cleanly — until the refactor lands. -// Override env var AGENTMEMORY_III_VERSION lets users on the sandbox -// model already point at a newer engine without us cutting a release. -const IIPINNED_VERSION = - process.env["AGENTMEMORY_III_VERSION"] || "0.11.2"; - -// Map Node platform/arch → the asset name iii-hq/iii ships under -// https://github.com/iii-hq/iii/releases/download/iii/v/ -function iiiReleaseAsset(): string | null { - const p = platform(); - const a = process.arch; - if (p === "darwin" && a === "arm64") - return "iii-aarch64-apple-darwin.tar.gz"; - if (p === "darwin" && a === "x64") - return "iii-x86_64-apple-darwin.tar.gz"; - if (p === "linux" && a === "x64") - return "iii-x86_64-unknown-linux-gnu.tar.gz"; - if (p === "linux" && a === "arm64") - return "iii-aarch64-unknown-linux-gnu.tar.gz"; - if (p === "linux" && a === "arm") - return "iii-armv7-unknown-linux-gnueabihf.tar.gz"; - if (p === "win32" && a === "x64") - return "iii-x86_64-pc-windows-msvc.zip"; - if (p === "win32" && a === "arm64") - return "iii-aarch64-pc-windows-msvc.zip"; - return null; -} - -function iiiReleaseUrl(): string | null { - const asset = iiiReleaseAsset(); - if (!asset) return null; - // Tag name is monorepo-prefixed: `iii/v0.11.2`. Slash is URL-encoded - // by GitHub when serving the download path, hence `iii/v...` not `iii%2Fv...`. - return `https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset}`; -} - -function vlog(msg: string): void { - if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`); -} - -function wrapList(items: readonly string[], indent: number, width = 78): string { - const lines: string[] = []; - let line = ""; - for (const item of items) { - const joined = line ? `${line}, ${item}` : item; - if (line && indent + joined.length > width) { - lines.push(`${line},`); - line = item; - } else { - line = joined; - } - } - lines.push(line); - return lines.join(`\n${" ".repeat(indent)}`); -} +// --help / -h early exit. Print + exit before any side effect beyond the +// flag fold above: no legacy-dir warning, no .env hydration, no engine boot. if (args.includes("--help") || args.includes("-h")) { console.log(` agentmemory — persistent memory for AI coding agents @@ -257,7 +191,7 @@ Environment: Honored by status, doctor, and MCP shim commands. AGENTMEMORY_USE_DOCKER=1 Prefer the bundled docker-compose path over the native iii-engine binary on first run. - AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}). + AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${III_PIN_DEFAULT}). AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS Window (seconds) for the smart-search follow-up diagnostic (default 30). Long values overcount, short values undercount. @@ -274,6 +208,83 @@ Quick start: process.exit(0); } +// Fold ~/.agentmemory/.env into process.env before anything reads config +// from the environment (the iii version pin, --tools/--port/--instance +// handling, engine boot). Fill-missing-only: a real process.env value — +// including one set by a CLI flag above — always wins over the file. +hydrateProcessEnvFromFile(); + +// Upstream adopts a legacy cwd ./data store automatically; this fork does +// not. When state would silently land somewhere different from an existing +// local ./data store, tell the operator how to opt in explicitly. Runs after +// hydration so an AGENTMEMORY_DATA_DIR declared only in /.env is +// already folded into the environment and suppresses the spurious warning. +warnOnLegacyDataDir(); + +// Pinned iii-engine version. The unpinned `install.iii.dev/iii/main/install.sh` +// script tracks `latest`, which made every fresh agentmemory install pull +// engine 0.11.6 — and 0.11.6 introduces a new sandbox-everything-via- +// `iii worker add` worker model that agentmemory hasn't been refactored +// for yet (we still use the pre-sandbox worker registration model). The +// architectural mismatch surfaces as EPIPE reconnect loops and empty +// search results after save. Pin to v0.11.2 — the last engine that runs +// agentmemory's current worker model cleanly — until the refactor lands. +// Override env var AGENTMEMORY_III_VERSION lets users on the sandbox +// model already point at a newer engine without us cutting a release. +const IIPINNED_VERSION = + process.env["AGENTMEMORY_III_VERSION"] || III_PIN_DEFAULT; + +// Map Node platform/arch → the asset name iii-hq/iii ships under +// https://github.com/iii-hq/iii/releases/download/iii/v/ +function iiiReleaseAsset(): string | null { + const p = platform(); + const a = process.arch; + if (p === "darwin" && a === "arm64") + return "iii-aarch64-apple-darwin.tar.gz"; + if (p === "darwin" && a === "x64") + return "iii-x86_64-apple-darwin.tar.gz"; + if (p === "linux" && a === "x64") + return "iii-x86_64-unknown-linux-gnu.tar.gz"; + if (p === "linux" && a === "arm64") + return "iii-aarch64-unknown-linux-gnu.tar.gz"; + if (p === "linux" && a === "arm") + return "iii-armv7-unknown-linux-gnueabihf.tar.gz"; + if (p === "win32" && a === "x64") + return "iii-x86_64-pc-windows-msvc.zip"; + if (p === "win32" && a === "arm64") + return "iii-aarch64-pc-windows-msvc.zip"; + return null; +} + +function iiiReleaseUrl(): string | null { + const asset = iiiReleaseAsset(); + if (!asset) return null; + // Tag name is monorepo-prefixed: `iii/v0.11.2`. Slash is URL-encoded + // by GitHub when serving the download path, hence `iii/v...` not `iii%2Fv...`. + return `https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset}`; +} + +function vlog(msg: string): void { + if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`); +} + +function wrapList(items: readonly string[], indent: number, width = 78): string { + const lines: string[] = []; + let line = ""; + for (const item of items) { + const joined = line ? `${line}, ${item}` : item; + if (line && indent + joined.length > width) { + lines.push(`${line},`); + line = item; + } else { + line = joined; + } + } + lines.push(line); + return lines.join(`\n${" ".repeat(indent)}`); +} + + const toolsIdx = args.indexOf("--tools"); if (toolsIdx !== -1 && args[toolsIdx + 1]) { const toolsMode = args[toolsIdx + 1]!; diff --git a/src/cli/connect/capability-secret.ts b/src/cli/connect/capability-secret.ts index 5c2b2355f..3ef00d6ba 100644 --- a/src/cli/connect/capability-secret.ts +++ b/src/cli/connect/capability-secret.ts @@ -55,7 +55,19 @@ export function ensureProjectCapabilitySecret(): CapabilityProvisionResult { if (existsSync(path)) { // lstat, not stat: a symlink parked at the credential path must be // refused before any read or write can follow it to another target. - if (lstatSync(path).isSymbolicLink()) { + let symlinked: boolean; + try { + symlinked = lstatSync(path).isSymbolicLink(); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === "ENOENT") { + // The file vanished between existsSync and lstat. Match the + // surrounding unreadable-file outcome instead of throwing raw; + // doctor surfaces the missing credential. + return { path, provisioned: false, reused: false }; + } + throw err; + } + if (symlinked) { throw new Error( "project capability secret path is a symlink; refusing", ); diff --git a/src/cli/connect/index.ts b/src/cli/connect/index.ts index 43ea1b182..75a62529e 100644 --- a/src/cli/connect/index.ts +++ b/src/cli/connect/index.ts @@ -128,9 +128,13 @@ export async function runConnect(args: string[]): Promise { `Generated project capability credential at ${capability.path} (mode 0600).`, ); } - } catch { + } catch (err) { // Provisioning must never block wiring; doctor reports a missing - // credential and can generate one later. + // credential and can generate one later. The failure is still logged: + // a silently unwired credential defeats the zero-touch guarantee. + process.stderr.write( + `[agentmemory] capability credential provisioning failed: ${err instanceof Error ? err.message : String(err)} — run 'agentmemory doctor'\n`, + ); } } diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index 3d7cc3744..382d9a576 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -331,8 +331,13 @@ async function wireSelectedAgents(agents: string[]): Promise { `Generated project capability credential at ${capability.path} (mode 0600).`, ); } - } catch { + } catch (err) { // Doctor reports and repairs a missing credential; never block wiring. + // The failure is still logged: a silently unwired credential defeats + // the zero-touch guarantee this provisioning exists for. + process.stderr.write( + `[agentmemory] capability credential provisioning failed: ${err instanceof Error ? err.message : String(err)} — run 'agentmemory doctor'\n`, + ); } for (const name of agents) { diff --git a/src/config.ts b/src/config.ts index ec0c0d09b..8fd8e0a23 100644 --- a/src/config.ts +++ b/src/config.ts @@ -500,10 +500,10 @@ export function isStandaloneMcp(): boolean { export function getStandalonePersistPath(): string { const env = getMergedEnv(); - return ( - env["STANDALONE_PERSIST_PATH"] || - join(resolveDataDir(), "standalone.json") - ); + // Trim before falling back: a whitespace-only value is not a path, and + // honoring it verbatim would persist state to a relative file named " ". + const configured = env["STANDALONE_PERSIST_PATH"]?.trim(); + return configured ? configured : join(resolveDataDir(), "standalone.json"); } const VALID_PROVIDERS = new Set([ diff --git a/src/functions/graph-import.ts b/src/functions/graph-import.ts index 5f6930f95..28ef6ab63 100644 --- a/src/functions/graph-import.ts +++ b/src/functions/graph-import.ts @@ -217,20 +217,26 @@ export function registerGraphImportFunction(sdk: ISdk, kv: StateKV): void { data as { project?: unknown; scope?: unknown }, "mem::graph::import-graphify", ); + // The daemon's own working directory is the only trusted base for the + // computed default path. A caller-supplied cwd is ignored here — both + // cwd and path are client-controlled on the REST surface, so the route + // admin-gates them before they can reach this function, and this check + // anchors to a value the client cannot steer. const explicitPath = typeof data?.path === "string" ? data.path : undefined; - const cwd = typeof data?.cwd === "string" ? data.cwd : process.cwd(); - const path = explicitPath ?? join(cwd, "graphify-out", "graph.json"); + const baseDir = process.cwd(); + const path = explicitPath ?? join(baseDir, "graphify-out", "graph.json"); if (explicitPath !== undefined) { - // The explicit path is client-supplied, so constrain it to the - // graphify artifact inside the project checkout before any stat, - // read, or error echo can touch it. + // Defense in depth for authorized callers too: an explicit path must + // stay a graph.json under the daemon's checkout, so no stat, read, + // or error echo can reach an arbitrary location (including through a + // relocated symlink directory). const resolvedExplicit = resolve(explicitPath); - const resolvedCwd = resolve(cwd); - const insideCwd = - resolvedExplicit === resolvedCwd || - resolvedExplicit.startsWith(resolvedCwd + sep); - if (basename(resolvedExplicit) !== "graph.json" || !insideCwd) { + const resolvedBase = resolve(baseDir); + const insideBase = + resolvedExplicit === resolvedBase || + resolvedExplicit.startsWith(resolvedBase + sep); + if (basename(resolvedExplicit) !== "graph.json" || !insideBase) { return { success: false, error: "path must be a graph.json inside the project cwd", diff --git a/src/triggers/api.ts b/src/triggers/api.ts index 2bb8ce9e8..95f2f17f2 100644 --- a/src/triggers/api.ts +++ b/src/triggers/api.ts @@ -2765,6 +2765,34 @@ export function registerApiTriggers( if (authErr) return authErr; const project = asNonEmptyString(req.body?.project); const requestsGlobal = req.body?.scope === "global"; + const explicitPath = + typeof req.body?.path === "string" ? req.body.path : undefined; + const explicitCwd = + typeof req.body?.cwd === "string" ? req.body.cwd : undefined; + // A caller-supplied path/cwd steers which file the daemon reads, and + // the engine anchors its containment checks to its own process.cwd() + // rather than any client value. Requests that carry either field are + // therefore administrative, gated exactly like global-scope + // governance/export; capability callers import the default + // /graphify-out/graph.json (which fails honestly when it + // is absent). + if ((explicitPath !== undefined || explicitCwd !== undefined) && !requestsGlobal) { + const adminDecision = authorizeAdministrativeRequest( + req.headers, + adminSecret, + ); + if (!adminDecision.authorized) { + return { + status_code: adminDecision.statusCode, + body: { + error: + adminDecision.error === "authentication_unavailable" + ? "global_authentication_unavailable" + : "global_unauthorized", + }, + }; + } + } if (!project && !requestsGlobal) { return { status_code: 400, @@ -2775,8 +2803,8 @@ export function registerApiTriggers( const rawResult = await sdk.trigger({ function_id: "mem::graph::import-graphify", payload: { - ...(typeof req.body?.path === "string" && { path: req.body.path }), - ...(typeof req.body?.cwd === "string" && { cwd: req.body.cwd }), + ...(explicitPath !== undefined && { path: explicitPath }), + ...(explicitCwd !== undefined && { cwd: explicitCwd }), ...(project ? { project } : {}), ...(requestsGlobal ? { scope: req.body?.scope } : {}), }, diff --git a/src/triggers/events.ts b/src/triggers/events.ts index 8e075c0fd..3732bf8b3 100644 --- a/src/triggers/events.ts +++ b/src/triggers/events.ts @@ -28,20 +28,33 @@ import { const MAX_BACKGROUND_PIPELINE_ATTEMPTS = 3; // Global marker recording when corpus consolidation last ran, used to debounce -// the per-turn session-stop fan-out. +// the per-turn session-stop fan-out. `token` names the dispatch that owns the +// current claim: each Void dispatch rewrites the marker with its own token +// right before firing, and a rejection handler deletes the marker only when +// it still carries that exact token (compare-and-delete). A stale rejection +// from an older cycle therefore cannot erase a newer cycle's debounce, and a +// rejected dispatch cannot release the cooldown while its sibling dispatch +// still holds a newer claim. const CONSOLIDATION_MARKER_KEY = "consolidation:lastRun"; -async function consolidationDueUnserialized(kv: StateKV): Promise { +type ConsolidationCooldownClaim = { at: number; token: string }; + +async function consolidationDueUnserialized( + kv: StateKV, +): Promise { const cooldownMs = getConsolidationCooldownMs(); - if (cooldownMs <= 0) return true; // debounce disabled + if (cooldownMs <= 0) return { at: Date.now(), token: "" }; // debounce disabled const now = Date.now(); const marker = await kv .get<{ at?: number }>(KV.config, CONSOLIDATION_MARKER_KEY) .catch(() => null); const lastAt = typeof marker?.at === "number" ? marker.at : 0; - if (now - lastAt < cooldownMs) return false; - await kv.set(KV.config, CONSOLIDATION_MARKER_KEY, { at: now }).catch(() => {}); - return true; + if (now - lastAt < cooldownMs) return null; + const token = generateId("ccm"); + await kv + .set(KV.config, CONSOLIDATION_MARKER_KEY, { at: now, token }) + .catch(() => {}); + return { at: now, token }; } // Concurrent session-stop events would otherwise interleave the marker @@ -49,11 +62,13 @@ async function consolidationDueUnserialized(kv: StateKV): Promise { // check through an in-process chain so exactly one concurrent caller wins. let consolidationCheckChain: Promise = Promise.resolve(); -function consolidationDue(kv: StateKV): Promise { +function consolidationDue( + kv: StateKV, +): Promise { const result = consolidationCheckChain.then(() => consolidationDueUnserialized(kv), ); - consolidationCheckChain = result.catch(() => false); + consolidationCheckChain = result.catch(() => null); return result; } @@ -859,24 +874,46 @@ export function registerEventTriggers(sdk: ISdk, kv: StateKV): void { // storm. Bound the global corpus consolidation to once per cooldown // window; AGENTMEMORY_CONSOLIDATION_COOLDOWN_MS=0 disables the debounce. if (isConsolidationEnabled()) { - const due = await consolidationDue(kv); - if (due) { + const claim = await consolidationDue(kv); + if (claim) { // Same dispatch discipline as the slot-reflect / graph-extract // fan-outs above: tolerate synchronous throws and non-promise // returns from sdk.trigger, log async rejections without failing // the stop lifecycle. - const releaseConsolidationCooldown = (): void => { - // The marker was written before the dispatch, so a rejected - // (or never-started) pipeline would otherwise pin the cooldown - // for the whole window with no work behind it. Plain kv delete: - // the marker is debounce bookkeeping, not pipeline state, and - // needs no lock. Best-effort only — the next stop re-checks. - kv.delete(KV.config, CONSOLIDATION_MARKER_KEY).catch(() => {}); - }; - const fireVoid = ( + // + // Each dispatch claims the cooldown marker with its own token right + // before firing; on rejection it re-reads the marker and deletes it + // ONLY when it still carries that token. A rejected dispatch thus + // releases the window only while nothing newer stands behind the + // marker: a crystallize rejection cannot clear the claim under a + // still-running consolidate-pipeline, and a late rejection from an + // older cycle cannot erase a newer cycle's fresh marker. Plain async + // kv ops — no locks around dispatch, marker writes stay lock-free. + const fireVoid = async ( function_id: string, payload: Record, - ) => { + ): Promise => { + const token = generateId("ccm"); + await kv + .set(KV.config, CONSOLIDATION_MARKER_KEY, { + at: claim.at, + token, + }) + .catch(() => {}); + const releaseOnRejection = (): void => { + void (async () => { + const marker = await kv + .get<{ at?: number; token?: string }>( + KV.config, + CONSOLIDATION_MARKER_KEY, + ) + .catch(() => null); + if (!marker || marker.token !== token) return; + await kv + .delete(KV.config, CONSOLIDATION_MARKER_KEY) + .catch(() => {}); + })(); + }; try { const dispatched = sdk.trigger({ function_id, @@ -890,7 +927,7 @@ export function registerEventTriggers(sdk: ISdk, kv: StateKV): void { pipelineRunId, error: err instanceof Error ? err.message : String(err), }); - releaseConsolidationCooldown(); + releaseOnRejection(); }); } catch (err) { logger.warn(function_id + " trigger failed", { @@ -899,16 +936,19 @@ export function registerEventTriggers(sdk: ISdk, kv: StateKV): void { pipelineRunId, error: err instanceof Error ? err.message : String(err), }); - releaseConsolidationCooldown(); + releaseOnRejection(); } }; - fireVoid("mem::consolidate-pipeline", { - tier: "all", - force: true, + // Crystallize fires first so the heavier consolidate-pipeline holds + // the newest claim for most of the cycle: its rejection then finds a + // token mismatch and leaves the debounce standing. + await fireVoid("mem::auto-crystallize", { + olderThanDays: 0, project: data.project, }); - fireVoid("mem::auto-crystallize", { - olderThanDays: 0, + await fireVoid("mem::consolidate-pipeline", { + tier: "all", + force: true, project: data.project, }); } diff --git a/src/version.ts b/src/version.ts index 60413cef4..6af4c9dbb 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,4 +1,4 @@ -export const VERSION = "0.9.30-chronode.2"; +export const VERSION = "0.9.30-chronode.3"; export const EXPORT_FORMAT_VERSION = "0.9.28" as const; export const API_CONTRACT_VERSION = 1; export const BACKEND_BUILD_ID = diff --git a/test/capability-secret-hardening.test.ts b/test/capability-secret-hardening.test.ts index 7ad5c4c43..1793e6487 100644 --- a/test/capability-secret-hardening.test.ts +++ b/test/capability-secret-hardening.test.ts @@ -12,9 +12,10 @@ import { import { join } from "node:path"; import { tmpdir } from "node:os"; -// chmod is the failure surface under test; every other fs call must hit the -// real filesystem. The flag toggles the fault injection per test. +// chmod and lstat are the failure surfaces under test; every other fs call +// must hit the real filesystem. The flags toggle fault injection per test. const chmodFault = vi.hoisted(() => ({ fail: false })); +const lstatFault = vi.hoisted(() => ({ failEnoent: false })); vi.mock("node:fs", async (importOriginal) => { const actual = await importOriginal(); return { @@ -27,6 +28,15 @@ vi.mock("node:fs", async (importOriginal) => { } return actual.chmodSync(path, mode); }) as typeof actual.chmodSync, + lstatSync: ((path: Parameters[0]) => { + if (lstatFault.failEnoent) { + throw Object.assign( + new Error("ENOENT: no such file or directory, lstat"), + { code: "ENOENT" }, + ); + } + return actual.lstatSync(path); + }) as typeof actual.lstatSync, }; }); @@ -47,6 +57,7 @@ describe("project capability secret hardening", () => { process.env["HOME"] = sandboxHome; delete process.env["AGENTMEMORY_PROJECT_CAPABILITY_SECRET_FILE"]; chmodFault.fail = false; + lstatFault.failEnoent = false; stderrWrites.length = 0; vi.spyOn(process.stderr, "write").mockImplementation(((chunk: unknown) => { stderrWrites.push(String(chunk)); @@ -103,4 +114,17 @@ describe("project capability secret hardening", () => { expect(readFileSync(path, "utf8")).toBe("user-chosen-secret\n"); expect(stderrWrites.join("")).toContain("could not tighten permissions"); }); + + it("treats an ENOENT race on lstat as nothing reused instead of crashing", () => { + const path = projectCapabilitySecretFile(); + mkdirSync(join(path, ".."), { recursive: true }); + writeFileSync(path, "user-chosen-secret\n", { mode: 0o600 }); + // existsSync saw the file; the file vanishes before lstat runs. + lstatFault.failEnoent = true; + + const result = ensureProjectCapabilitySecret(); + expect(result).toEqual({ path, provisioned: false, reused: false }); + // No fresh credential was provisioned over the vanished path. + expect(readFileSync(path, "utf8")).toBe("user-chosen-secret\n"); + }); }); diff --git a/test/consolidation-lifecycle.test.ts b/test/consolidation-lifecycle.test.ts index 3cd8fbb54..0be9f0d75 100644 --- a/test/consolidation-lifecycle.test.ts +++ b/test/consolidation-lifecycle.test.ts @@ -389,4 +389,95 @@ describe("session-stop consolidation lifecycle", () => { ]); expect(await marker()).not.toBeNull(); }); + + it("keeps the marker when crystallize is rejected but the pipeline holds a newer claim", async () => { + await kv.set(KV.sessions, "s-x1", sessionRow("s-x1")); + await kv.set(KV.sessions, "s-x2", sessionRow("s-x2")); + registerRuntime(); + // Crystallize is rejected every time; the pipeline succeeds. The + // pipeline fires after crystallize and therefore owns the newest + // claim on the marker. + let crystallizeDispatches = 0; + (sdk as unknown as { fns: Map Promise> }).fns.set( + "mem::auto-crystallize", + async () => { + crystallizeDispatches += 1; + throw new Error("SIMULATED_CRYSTALLIZE_REJECTION"); + }, + ); + + await stop({ sessionId: "s-x1" }); + await flush(); + + expect(crystallizeDispatches).toBe(1); + expect(consolidationCalls()).toHaveLength(1); + const survivor = await marker(); + expect(typeof survivor?.at).toBe("number"); + expect(typeof survivor?.token).toBe("string"); + + // The running pipeline must stay protected: a stop inside the cooldown + // window is still debounced even though crystallize was rejected. + await stop({ sessionId: "s-x2" }); + await flush(); + expect(crystallizeDispatches).toBe(1); + expect(consolidationCalls()).toHaveLength(1); + }); + + it("a stale rejection cannot erase a newer cycle's marker", async () => { + await kv.set(KV.sessions, "s-t1", sessionRow("s-t1")); + await kv.set(KV.sessions, "s-t2", sessionRow("s-t2")); + await kv.set(KV.sessions, "s-t3", sessionRow("s-t3")); + registerRuntime(); + // First pipeline dispatch hangs until the test releases it, simulating + // a rejection that lands long after newer cycles have claimed the mark. + let pipelineDispatches = 0; + let rejectFirstDispatch!: (err: Error) => void; + const gated = new Promise((_resolve, reject) => { + rejectFirstDispatch = reject; + }); + (sdk as unknown as { fns: Map Promise> }).fns.set( + "mem::consolidate-pipeline", + async (payload) => { + pipelineDispatches += 1; + if (pipelineDispatches === 1) return gated; + calls.push({ + functionId: "mem::consolidate-pipeline", + payload: payload as Record, + }); + return { success: true }; + }, + ); + + // Cycle 1: crystallize succeeds, pipeline dispatch #1 stays pending. + await stop({ sessionId: "s-t1" }); + await flush(); + expect(pipelineDispatches).toBe(1); + expect(await marker()).not.toBeNull(); + + // Cycle 2 becomes eligible only after the window elapses; it writes a + // fresh marker and completes successfully. + const stale = (await marker())!; + await kv.set(KV.config, "consolidation:lastRun", { + at: (stale.at ?? Date.now()) - 300_001, + token: "superseded-cycle-1-claim", + }); + await stop({ sessionId: "s-t2" }); + await flush(); + expect(pipelineDispatches).toBe(2); + const fresh = await marker(); + expect(fresh).not.toBeNull(); + expect(fresh?.token).not.toBe("superseded-cycle-1-claim"); + + // The cycle-1 rejection finally lands: its compare-and-delete finds a + // foreign token and leaves the newer debounce standing. + rejectFirstDispatch(new Error("SIMULATED_LATE_REJECTION")); + await flush(); + expect(await marker()).toEqual(fresh); + + // A further stop inside the window stays debounced by that survivor. + await stop({ sessionId: "s-t3" }); + await flush(); + expect(pipelineDispatches).toBe(2); + expect(crystallizeCalls()).toHaveLength(2); + }); }); diff --git a/test/data-dir.test.ts b/test/data-dir.test.ts index b3e9238a0..ed324b41d 100644 --- a/test/data-dir.test.ts +++ b/test/data-dir.test.ts @@ -1,4 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { spawnSync } from "node:child_process"; +import { createRequire } from "node:module"; +import { pathToFileURL } from "node:url"; import { mkdirSync, mkdtempSync, @@ -6,7 +9,7 @@ import { writeFileSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { DATA_DIR_ENV, defaultDataDir, @@ -21,6 +24,8 @@ import { const ORIGINAL_HOME = process.env["HOME"]; const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; +const repoRoot = resolve(import.meta.dirname, ".."); + let sandboxHome: string; let sandboxCwd: string; @@ -251,6 +256,48 @@ describe("config integration", () => { return await import("../src/config.js"); } + it("a .env-declared AGENTMEMORY_DATA_DIR suppresses the legacy warning after hydration", async () => { + // Boot-order regression guard: warnOnLegacyDataDir must run AFTER + // hydrateProcessEnvFromFile(), so a value declared only in + // ~/.agentmemory/.env is already folded into the environment and the + // resolver sees an explicit data dir instead of the default. + const relocated = join(sandboxHome, "relocated"); + const envDir = join(sandboxHome, ".agentmemory"); + mkdirSync(relocated, { recursive: true }); + mkdirSync(envDir, { recursive: true }); + writeFileSync(join(envDir, ".env"), `${DATA_DIR_ENV}=${relocated}\n`); + const legacyCwd = mkdtempSync(join(tmpdir(), "agentmemory-legacy-cwd-")); + try { + mkdirSync(join(legacyCwd, "data"), { recursive: true }); + writeFileSync(join(legacyCwd, "data", "iii-config.yaml"), ""); + + const before: string[] = []; + expect( + warnOnLegacyDataDir({ + cwd: legacyCwd, + write: (message) => before.push(message), + }), + ).toBe(true); + + const cfg = await freshConfig(); + cfg.hydrateProcessEnvFromFile(); + try { + const after: string[] = []; + expect( + warnOnLegacyDataDir({ + cwd: legacyCwd, + write: (message) => after.push(message), + }), + ).toBe(false); + expect(after).toHaveLength(0); + } finally { + delete process.env[DATA_DIR_ENV]; + } + } finally { + rmSync(legacyCwd, { recursive: true, force: true }); + } + }); + it("loadConfig().dataDir follows AGENTMEMORY_DATA_DIR over the default", async () => { const cfg = await freshConfig(); expect(cfg.loadConfig().dataDir).toBe(join(homedir(), ".agentmemory")); @@ -299,6 +346,15 @@ describe("config integration", () => { expect(cfg.getStandalonePersistPath()).toBe(join(sandboxHome, "custom.json")); }); + it("a whitespace-only STANDALONE_PERSIST_PATH falls back to the data-dir path", async () => { + process.env[DATA_DIR_ENV] = join(sandboxHome, "relocated"); + process.env["STANDALONE_PERSIST_PATH"] = " "; + const cfg = await freshConfig(); + expect(cfg.getStandalonePersistPath()).toBe( + join(join(sandboxHome, "relocated"), "standalone.json"), + ); + }); + it("hydrates from the flag-folded data dir when AGENTMEMORY_DATA_DIR is set before boot", async () => { // Mirrors the CLI boot order: the --data-dir flag is folded into // process.env first, then hydrateProcessEnvFromFile() reads @@ -330,3 +386,48 @@ describe("config integration", () => { delete process.env["AM_FLAGFOLD_PROBE"]; }); }); + +describe("--help boot ordering", () => { + let sandboxHome: string; + let legacyCwd: string; + + beforeEach(() => { + sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-help-home-")); + legacyCwd = mkdtempSync(join(tmpdir(), "agentmemory-help-cwd-")); + // A legacy store plus a default home: with the pre-fix order (warning + // before the --help exit) this exact tree produced stderr noise on + // every `--help` run. + mkdirSync(join(legacyCwd, "data"), { recursive: true }); + writeFileSync(join(legacyCwd, "data", "state_store.db"), ""); + }); + + afterEach(() => { + rmSync(sandboxHome, { recursive: true, force: true }); + rmSync(legacyCwd, { recursive: true, force: true }); + delete process.env[DATA_DIR_ENV]; + }); + + it("prints usage without a legacy-dir warning side effect", () => { + const childEnv: NodeJS.ProcessEnv = { ...process.env }; + delete childEnv[DATA_DIR_ENV]; + childEnv["HOME"] = sandboxHome; + childEnv["USERPROFILE"] = sandboxHome; + // The child runs from the legacy-store cwd, so both the tsx hook and + // the CLI entry must be passed as absolute specifiers. + const requireFromRepo = createRequire(join(repoRoot, "package.json")); + const result = spawnSync( + process.execPath, + [ + "--import", + pathToFileURL(requireFromRepo.resolve("tsx")).href, + join(repoRoot, "src", "cli.ts"), + "--help", + ], + { cwd: legacyCwd, encoding: "utf8", env: childEnv }, + ); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Usage: agentmemory"); + expect(result.stderr).not.toContain("legacy ./data store"); + expect(result.stderr).not.toContain("--data-dir"); + }); +}); diff --git a/test/graph-import-scope.test.ts b/test/graph-import-scope.test.ts index 6a199b5b5..484ac536e 100644 --- a/test/graph-import-scope.test.ts +++ b/test/graph-import-scope.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join } from "node:path"; +import { join } from "node:path"; vi.mock("../src/logger.js", () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, @@ -12,10 +12,14 @@ import { registerGraphImportFunction, } from "../src/functions/graph-import.js"; import type { GraphifyImportResult } from "../src/functions/graph-import.js"; +import { registerApiTriggers } from "../src/triggers/api.js"; +import { createProjectCapabilityToken } from "../src/auth.js"; import { KV } from "../src/state/schema.js"; -// Client-supplied explicit paths must stay inside the project cwd and keep -// the graph.json basename; violations are rejected with a generic message +// The engine anchors every path decision to its own process.cwd(): a +// client-supplied cwd is ignored, explicit paths must stay a graph.json +// under that directory, and on the REST surface any request carrying path +// or cwd is admin-gated. Violations are rejected with a generic message // that does not echo the attempted path. const FIXTURE = JSON.stringify({ nodes: [{ id: "n1", label: "extract", file_type: "code" }], @@ -42,8 +46,34 @@ function mockKV() { }; } +type Handler = (payload?: unknown) => Promise; + +function integratedHarness() { + const functions = new Map(); + const sdk = { + registerFunction: ( + idOrOptions: string | { id: string }, + handler: Handler, + ) => { + const id = + typeof idOrOptions === "string" ? idOrOptions : idOrOptions.id; + functions.set(id, handler); + }, + registerTrigger: () => {}, + // Route handlers dispatch into the real registered functions, so the + // REST gating and the engine containment are exercised end to end. + trigger: async (input: { function_id: string; payload: unknown }) => { + const fn = functions.get(input.function_id); + if (!fn) throw new Error(`No function: ${input.function_id}`); + return fn(input.payload); + }, + }; + return { sdk, functions }; +} + describe("mem::graph::import-graphify path scoping", () => { let tmp: string; + let originalCwd: string; let kv: ReturnType; let trigger: (payload: unknown) => Promise; @@ -62,9 +92,15 @@ describe("mem::graph::import-graphify path scoping", () => { registerTrigger: () => {}, } as never; registerGraphImportFunction(sdk, kv as never); + originalCwd = process.cwd(); + process.chdir(tmp); + // process.cwd() canonicalizes symlinked parents (macOS /var -> /private/var), + // and the engine anchors to it — derive every expectation from this value. + tmp = process.cwd(); }); afterEach(() => { + process.chdir(originalCwd); rmSync(tmp, { recursive: true, force: true }); }); @@ -107,7 +143,35 @@ describe("mem::graph::import-graphify path scoping", () => { expect(await kv.list(KV.graphNodes)).toHaveLength(1); }); - it("keeps the stat-failure pointer generic for explicit paths and specific for the default", async () => { + it("ignores a client-supplied cwd and computes the default from the daemon cwd", async () => { + // The fixture exists at /graphify-out/graph.json; a caller + // pointing cwd elsewhere must not relocate the read. + const decoy = mkdtempSync(join(tmpdir(), "am-graphify-decoy-")); + try { + mkdirSync(join(decoy, "graphify-out"), { recursive: true }); + writeFileSync( + join(decoy, "graphify-out", "graph.json"), + JSON.stringify({ nodes: [{ id: "evil", label: "evil" }] }), + ); + const result = await trigger({ cwd: decoy, project: PROJECT }); + expect(result.success).toBe(true); + expect(result.path).toBe(join(tmp, "graphify-out", "graph.json")); + const imported = await kv.list(KV.graphNodes); + expect(imported).toHaveLength(1); + expect((imported[0] as { name?: string }).name).toBe("extract"); + } finally { + rmSync(decoy, { recursive: true, force: true }); + } + }); + + it("fails honestly when the default artifact is absent at the daemon cwd", async () => { + rmSync(join(tmp, "graphify-out", "graph.json")); + const result = await trigger({ project: PROJECT }); + expect(result.success).toBe(false); + expect(result.error).toContain("Run graphify first"); + }); + + it("keeps the stat-failure pointer generic for explicit paths", async () => { const missingExplicit = join(tmp, "missing-dir", "graph.json"); const explicitResult = await trigger({ path: missingExplicit, @@ -118,18 +182,147 @@ describe("mem::graph::import-graphify path scoping", () => { expect(explicitResult.error).toContain("Run graphify first"); expect(explicitResult.error).not.toContain(missingExplicit); expect(explicitResult.path).toBeUndefined(); + }); - const defaultResult = await trigger({ - cwd: join(tmp, "nowhere"), - project: PROJECT, - }); - expect(defaultResult.success).toBe(false); - expect(defaultResult.error).toContain( - join(tmp, "nowhere", "graphify-out", "graph.json"), + it("reports the exact default path on stat failure at the daemon cwd", async () => { + // The client cannot steer the default lookup (no cwd field is honored), + // so its stat failure may safely name the concrete location. + rmSync(join(tmp, "graphify-out", "graph.json")); + const result = await trigger({ project: PROJECT }); + expect(result.success).toBe(false); + expect(result.error).toContain(join(tmp, "graphify-out", "graph.json")); + expect(result.path).toBe(join(tmp, "graphify-out", "graph.json")); + }); +}); + +describe("api::graph-import-graphify path/cwd admin gating", () => { + let tmp: string; + let originalCwd: string; + + function capabilityHeaders(project: string): Record { + const now = Math.floor(Date.now() / 1000); + const token = createProjectCapabilityToken( + { + version: 1, + audience: "agentmemory", + project, + expiresAt: now + 60, + issuedAt: now, + }, + "capability-secret", ); - expect(defaultResult.path).toBe( - join(tmp, "nowhere", "graphify-out", "graph.json"), + return { authorization: `Bearer ${token}` }; + } + + beforeEach(() => { + tmp = mkdtempSync(join(tmpdir(), "am-graphify-route-")); + mkdirSync(join(tmp, "graphify-out"), { recursive: true }); + writeFileSync(join(tmp, "graphify-out", "graph.json"), FIXTURE); + originalCwd = process.cwd(); + process.chdir(tmp); + // Canonicalize like the engine's process.cwd() anchor (macOS /var -> /private/var). + tmp = process.cwd(); + }); + + afterEach(() => { + process.chdir(originalCwd); + rmSync(tmp, { recursive: true, force: true }); + }); + + function registerRoute() { + const { sdk, functions } = integratedHarness(); + registerGraphImportFunction(sdk as never, mockKV() as never); + registerApiTriggers( + sdk as never, + mockKV() as never, + "legacy-secret", + undefined, + undefined, + "admin-secret", + "capability-secret", + true, + ); + return ( + body: Record, + headers?: Record, + ): Promise<{ status_code: number; body: Record }> => + functions.get("api::graph-import-graphify")!({ + headers, + query_params: {}, + body, + }) as Promise<{ status_code: number; body: Record }>; + } + + it("rejects a capability request carrying an explicit path with 401", async () => { + const route = registerRoute(); + const response = await route( + { project: PROJECT, path: join(tmp, "graphify-out", "graph.json") }, + capabilityHeaders(PROJECT), + ); + expect(response.status_code).toBe(401); + expect(response.body).toEqual({ error: "global_unauthorized" }); + }); + + it("rejects a capability request carrying only cwd with 401", async () => { + const route = registerRoute(); + const response = await route( + { project: PROJECT, cwd: tmp }, + capabilityHeaders(PROJECT), + ); + expect(response.status_code).toBe(401); + expect(response.body).toEqual({ error: "global_unauthorized" }); + }); + + it("proceeds without path/cwd and imports the default daemon-cwd artifact", async () => { + const route = registerRoute(); + const response = await route( + { project: PROJECT }, + capabilityHeaders(PROJECT), + ); + expect(response.status_code).toBe(200); + expect(response.body).toMatchObject({ + success: true, + path: join(tmp, "graphify-out", "graph.json"), + nodesImported: 1, + }); + }); + + it("fails honestly when the default artifact is absent for a capability request", async () => { + const route = registerRoute(); + rmSync(join(tmp, "graphify-out", "graph.json")); + const response = await route({ project: PROJECT }, capabilityHeaders(PROJECT)); + expect(response.body).toMatchObject({ success: false }); + expect(String(response.body.error)).toContain("Run graphify first"); + }); + + it("rejects an admin explicit path outside the daemon cwd generically", async () => { + const route = registerRoute(); + const outsideDir = mkdtempSync(join(tmpdir(), "am-graphify-route-out-")); + try { + const outside = join(outsideDir, "graph.json"); + writeFileSync(outside, FIXTURE); + const response = await route( + { project: PROJECT, path: outside }, + { authorization: "Bearer admin-secret" }, + ); + expect(response.status_code).toBe(400); + expect(response.body).toMatchObject({ + success: false, + error: "path must be a graph.json inside the project cwd", + }); + expect(JSON.stringify(response.body)).not.toContain(outside); + } finally { + rmSync(outsideDir, { recursive: true, force: true }); + } + }); + + it("imports an admin explicit graph.json inside the daemon cwd", async () => { + const route = registerRoute(); + const response = await route( + { project: PROJECT, path: join(tmp, "graphify-out", "graph.json") }, + { authorization: "Bearer admin-secret" }, ); - expect(dirname(missingExplicit)).toBeTruthy(); + expect(response.status_code).toBe(200); + expect(response.body).toMatchObject({ success: true, nodesImported: 1 }); }); }); diff --git a/test/graph-import.test.ts b/test/graph-import.test.ts index 9077399e7..088e963e8 100644 --- a/test/graph-import.test.ts +++ b/test/graph-import.test.ts @@ -140,6 +140,7 @@ describe("parseGraphifyGraph", () => { describe("mem::graph::import-graphify", () => { let tmp: string; + let originalCwd: string; let kv: ReturnType; let sdk: ReturnType; @@ -150,16 +151,24 @@ describe("mem::graph::import-graphify", () => { kv = mockKV(); sdk = mockSdk(); registerGraphImportFunction(sdk as never, kv as never); + // The engine computes its default path from its own process.cwd(); run + // against a sandbox checkout instead of the vitest process directory. + originalCwd = process.cwd(); + process.chdir(tmp); + // Canonicalize like the engine's anchor on symlinked parents + // (macOS /var -> /private/var). + tmp = process.cwd(); }); afterEach(() => { + process.chdir(originalCwd); rmSync(tmp, { recursive: true, force: true }); }); it("imports nodes and edges into the memory graph", async () => { const result = (await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: tmp, project: PROJECT }, + payload: { project: PROJECT }, })) as { success: boolean; newNodes: number; @@ -182,11 +191,11 @@ describe("mem::graph::import-graphify", () => { it("re-import is idempotent: second run merges instead of duplicating", async () => { await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: tmp, project: PROJECT }, + payload: { project: PROJECT }, }); const second = (await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: tmp, project: PROJECT }, + payload: { project: PROJECT }, })) as { success: boolean; newNodes: number; newEdges: number }; expect(second.success).toBe(true); @@ -209,20 +218,21 @@ describe("mem::graph::import-graphify", () => { }); it("fails cleanly with a pointer when graph.json is absent", async () => { + rmSync(join(tmp, "graphify-out", "graph.json")); const result = (await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: join(tmp, "nowhere"), project: PROJECT }, + payload: { project: PROJECT }, })) as { success: boolean; error: string }; expect(result.success).toBe(false); expect(result.error).toContain("Run graphify first"); }); - it("accepts an explicit path inside the project cwd", async () => { + it("accepts an explicit path inside the daemon cwd", async () => { const alt = join(tmp, "graph.json"); writeFileSync(alt, JSON.stringify(FIXTURE)); const result = (await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { path: alt, cwd: tmp, project: PROJECT }, + payload: { path: alt, project: PROJECT }, })) as { success: boolean; nodesImported: number }; expect(result.success).toBe(true); expect(result.nodesImported).toBe(4); @@ -232,7 +242,7 @@ describe("mem::graph::import-graphify", () => { writeFileSync(join(tmp, "graphify-out", "graph.json"), "{not json"); const result = (await (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: tmp, project: PROJECT }, + payload: { project: PROJECT }, })) as { success: boolean }; expect(result.success).toBe(false); expect(await kv.list(KV.graphNodes)).toHaveLength(0); @@ -242,7 +252,7 @@ describe("mem::graph::import-graphify", () => { await expect( (sdk as any).trigger({ function_id: "mem::graph::import-graphify", - payload: { cwd: tmp }, + payload: {}, }), ).rejects.toThrow("project is required"); });