@remembrance-ai/opencode-plugin 0.1.53 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -8
- package/opencode.json +4 -9
- package/package.json +1 -1
- package/scripts/hook-core.mjs +1839 -175
- package/servers/remembrance-mcp.mjs +1431 -274
- package/skills/remembrancer/SKILL.md +64 -39
- package/skills/remembrancer/references/attestation-rest.md +43 -5
- package/skills/remembrancer/references/identity-preferences-topology.md +221 -0
- package/skills/remembrancer/references/remembrance-payloads.md +19 -12
- package/skills/remembrancer/references/remembrance-setup.md +42 -11
- package/src/index.mjs +94 -5
|
@@ -38,11 +38,13 @@ function defaultAgentKeyIdForPublicKey(publicKey) {
|
|
|
38
38
|
}
|
|
39
39
|
function buildAgentKeyRegistrationSigningPayload(args) {
|
|
40
40
|
const signedAt = args.signedAt instanceof Date ? args.signedAt.toISOString() : args.signedAt;
|
|
41
|
+
const ownerBinding = args.ownerBinding?.trim() || null;
|
|
41
42
|
return canonicalJson({
|
|
42
|
-
version: "v1",
|
|
43
|
+
version: ownerBinding ? "v2" : "v1",
|
|
43
44
|
purpose: "remembrance-agent-key-registration",
|
|
44
45
|
provider: args.provider,
|
|
45
46
|
key_id: args.keyId ?? defaultAgentKeyIdForPublicKey(args.publicKey),
|
|
47
|
+
...ownerBinding ? { owner_binding: ownerBinding } : {},
|
|
46
48
|
public_key_hash: hashValue(args.publicKey),
|
|
47
49
|
subject: args.subject ?? null,
|
|
48
50
|
signed_at: signedAt
|
|
@@ -90,7 +92,14 @@ var AGENT_HOSTS = [
|
|
|
90
92
|
public_mirror: true,
|
|
91
93
|
client_release: true,
|
|
92
94
|
release_smoke: true,
|
|
93
|
-
|
|
95
|
+
release_evidence: [
|
|
96
|
+
"artifact_exact",
|
|
97
|
+
"adapter_contract",
|
|
98
|
+
"transport_contract",
|
|
99
|
+
"host_discovery",
|
|
100
|
+
"host_process",
|
|
101
|
+
"authenticated_tool"
|
|
102
|
+
],
|
|
94
103
|
plugin: true,
|
|
95
104
|
hooks: {
|
|
96
105
|
session_start: true,
|
|
@@ -110,7 +119,12 @@ var AGENT_HOSTS = [
|
|
|
110
119
|
public_mirror: true,
|
|
111
120
|
client_release: true,
|
|
112
121
|
release_smoke: true,
|
|
113
|
-
|
|
122
|
+
release_evidence: [
|
|
123
|
+
"artifact_exact",
|
|
124
|
+
"adapter_contract",
|
|
125
|
+
"transport_contract",
|
|
126
|
+
"host_discovery"
|
|
127
|
+
],
|
|
114
128
|
plugin: true,
|
|
115
129
|
hooks: {
|
|
116
130
|
session_start: true,
|
|
@@ -130,7 +144,12 @@ var AGENT_HOSTS = [
|
|
|
130
144
|
public_mirror: true,
|
|
131
145
|
client_release: true,
|
|
132
146
|
release_smoke: true,
|
|
133
|
-
|
|
147
|
+
release_evidence: [
|
|
148
|
+
"artifact_exact",
|
|
149
|
+
"adapter_contract",
|
|
150
|
+
"transport_contract",
|
|
151
|
+
"source_contract"
|
|
152
|
+
],
|
|
134
153
|
plugin: true,
|
|
135
154
|
hooks: {
|
|
136
155
|
session_start: true,
|
|
@@ -150,7 +169,12 @@ var AGENT_HOSTS = [
|
|
|
150
169
|
public_mirror: true,
|
|
151
170
|
client_release: true,
|
|
152
171
|
release_smoke: true,
|
|
153
|
-
|
|
172
|
+
release_evidence: [
|
|
173
|
+
"artifact_exact",
|
|
174
|
+
"adapter_contract",
|
|
175
|
+
"transport_contract",
|
|
176
|
+
"host_discovery"
|
|
177
|
+
],
|
|
154
178
|
plugin: true,
|
|
155
179
|
hooks: {
|
|
156
180
|
session_start: true,
|
|
@@ -170,7 +194,12 @@ var AGENT_HOSTS = [
|
|
|
170
194
|
public_mirror: true,
|
|
171
195
|
client_release: true,
|
|
172
196
|
release_smoke: true,
|
|
173
|
-
|
|
197
|
+
release_evidence: [
|
|
198
|
+
"artifact_exact",
|
|
199
|
+
"adapter_contract",
|
|
200
|
+
"transport_contract",
|
|
201
|
+
"source_contract"
|
|
202
|
+
],
|
|
174
203
|
plugin: true,
|
|
175
204
|
hooks: {
|
|
176
205
|
session_start: true,
|
|
@@ -190,7 +219,14 @@ var AGENT_HOSTS = [
|
|
|
190
219
|
public_mirror: true,
|
|
191
220
|
client_release: true,
|
|
192
221
|
release_smoke: true,
|
|
193
|
-
|
|
222
|
+
release_evidence: [
|
|
223
|
+
"artifact_exact",
|
|
224
|
+
"adapter_contract",
|
|
225
|
+
"transport_contract",
|
|
226
|
+
"host_discovery",
|
|
227
|
+
"host_process",
|
|
228
|
+
"authenticated_tool"
|
|
229
|
+
],
|
|
194
230
|
plugin: true,
|
|
195
231
|
hooks: {
|
|
196
232
|
session_start: true,
|
|
@@ -210,7 +246,7 @@ var AGENT_HOSTS = [
|
|
|
210
246
|
public_mirror: false,
|
|
211
247
|
client_release: true,
|
|
212
248
|
release_smoke: true,
|
|
213
|
-
|
|
249
|
+
release_evidence: ["artifact_exact", "transport_contract"],
|
|
214
250
|
plugin: false,
|
|
215
251
|
hooks: {
|
|
216
252
|
session_start: false,
|
|
@@ -230,7 +266,7 @@ var AGENT_HOSTS = [
|
|
|
230
266
|
public_mirror: false,
|
|
231
267
|
client_release: false,
|
|
232
268
|
release_smoke: true,
|
|
233
|
-
|
|
269
|
+
release_evidence: ["transport_contract"],
|
|
234
270
|
plugin: false,
|
|
235
271
|
hooks: {
|
|
236
272
|
session_start: false,
|
|
@@ -4397,6 +4433,182 @@ var clientHealthReportSchema = external_exports.object({
|
|
|
4397
4433
|
}
|
|
4398
4434
|
});
|
|
4399
4435
|
|
|
4436
|
+
// ../core/src/client-release.ts
|
|
4437
|
+
var CLIENT_RELEASE_SCHEMA_VERSION = "1";
|
|
4438
|
+
var CODEX_UPDATE_COMMAND = [
|
|
4439
|
+
'CODEX_CLI="${CODEX_CLI:-$(command -v codex || true)}"',
|
|
4440
|
+
'[ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/ChatGPT.app/Contents/Resources/codex"',
|
|
4441
|
+
'[ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/Codex.app/Contents/Resources/codex"',
|
|
4442
|
+
`[ -x "$CODEX_CLI" ] || { printf '%s\\n' "Codex CLI not found. Install the Codex CLI, or update the ChatGPT desktop app, then try again." >&2; exit 1; }`,
|
|
4443
|
+
'"$CODEX_CLI" plugin marketplace upgrade remembrance',
|
|
4444
|
+
'"$CODEX_CLI" plugin add remembrance@remembrance --json'
|
|
4445
|
+
].join("\n");
|
|
4446
|
+
var CLIENT_UPDATE_GUIDANCE = {
|
|
4447
|
+
codex: {
|
|
4448
|
+
surface: "codex",
|
|
4449
|
+
host_name: "Codex",
|
|
4450
|
+
command: CODEX_UPDATE_COMMAND,
|
|
4451
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled Codex update command exactly as shown.",
|
|
4452
|
+
restart_action: "After the command succeeds, tell the user to fully quit and reopen Codex. The current process continues using the installed version until restart."
|
|
4453
|
+
},
|
|
4454
|
+
claude_code: {
|
|
4455
|
+
surface: "claude_code",
|
|
4456
|
+
host_name: "Claude Code",
|
|
4457
|
+
command: "claude plugin marketplace update remembrance\nclaude plugin update remembrance@remembrance",
|
|
4458
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled Claude Code update command exactly as shown.",
|
|
4459
|
+
restart_action: "After the command succeeds, tell the user to run /reload-plugins or fully quit and reopen Claude Code. The current plugin process remains on the installed version until reload or restart."
|
|
4460
|
+
},
|
|
4461
|
+
cursor: {
|
|
4462
|
+
surface: "cursor",
|
|
4463
|
+
host_name: "Cursor",
|
|
4464
|
+
command: null,
|
|
4465
|
+
update_action: "Tell the user to open Cursor settings, refresh the marketplace that provides Remembrance, and choose Update for the Remembrance plugin.",
|
|
4466
|
+
restart_action: "After Cursor reports that the update completed, tell the user to fully quit and reopen Cursor. The current process continues using the installed version until restart."
|
|
4467
|
+
},
|
|
4468
|
+
openclaw: {
|
|
4469
|
+
surface: "openclaw",
|
|
4470
|
+
host_name: "OpenClaw",
|
|
4471
|
+
command: "openclaw plugins update remembrance\nopenclaw remembrance setup",
|
|
4472
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled OpenClaw update and setup commands exactly as shown.",
|
|
4473
|
+
restart_action: "After the commands succeed, tell the user to restart the OpenClaw Gateway unless its managed reload already restarted it, then begin a new agent session."
|
|
4474
|
+
},
|
|
4475
|
+
vs_code: {
|
|
4476
|
+
surface: "vs_code",
|
|
4477
|
+
host_name: "VS Code",
|
|
4478
|
+
command: null,
|
|
4479
|
+
update_action: "Tell the user to refresh the marketplace or managed source that provides Remembrance and update the Remembrance plugin there.",
|
|
4480
|
+
restart_action: "After the update completes, tell the user to reload the VS Code window or fully quit and reopen VS Code. The current extension host continues using the installed version until reload or restart."
|
|
4481
|
+
},
|
|
4482
|
+
opencode: {
|
|
4483
|
+
surface: "opencode",
|
|
4484
|
+
host_name: "opencode",
|
|
4485
|
+
command: "npx -y @remembrance-ai/opencode-plugin@latest setup",
|
|
4486
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled opencode setup command exactly as shown.",
|
|
4487
|
+
restart_action: "After the command succeeds, tell the user to fully quit and reopen opencode. The current process continues using the installed version until restart."
|
|
4488
|
+
},
|
|
4489
|
+
mcp: {
|
|
4490
|
+
surface: "mcp",
|
|
4491
|
+
host_name: "MCP client",
|
|
4492
|
+
command: null,
|
|
4493
|
+
update_action: "Ask the user for permission to update the Remembrance MCP registration. If approved, inspect only that registration: an unpinned npx @remembrance-ai/mcp-server command needs no edit, while a pinned package version should be changed to the published latest version. Do not edit unrelated MCP configuration.",
|
|
4494
|
+
restart_action: "After updating the registration, tell the user to fully restart the host that launches the MCP server. The current MCP process continues using the installed version until restart."
|
|
4495
|
+
}
|
|
4496
|
+
};
|
|
4497
|
+
var CLIENT_RELEASE_SURFACES = AGENT_HOSTS.filter(
|
|
4498
|
+
(host) => host.client_release
|
|
4499
|
+
).map((host) => host.surface);
|
|
4500
|
+
function parseStableClientVersion(value) {
|
|
4501
|
+
if (typeof value !== "string") return null;
|
|
4502
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(value);
|
|
4503
|
+
if (!match) return null;
|
|
4504
|
+
const parts = match.slice(1).map(Number);
|
|
4505
|
+
if (parts.length !== 3 || parts.some((part) => !Number.isSafeInteger(part) || part > 9999)) {
|
|
4506
|
+
return null;
|
|
4507
|
+
}
|
|
4508
|
+
return parts;
|
|
4509
|
+
}
|
|
4510
|
+
function compareClientVersions(left, right) {
|
|
4511
|
+
const leftParts = parseStableClientVersion(left);
|
|
4512
|
+
const rightParts = parseStableClientVersion(right);
|
|
4513
|
+
if (!leftParts || !rightParts) {
|
|
4514
|
+
throw new Error(
|
|
4515
|
+
"Client versions must use stable major.minor.patch syntax."
|
|
4516
|
+
);
|
|
4517
|
+
}
|
|
4518
|
+
for (let index = 0; index < 3; index += 1) {
|
|
4519
|
+
const difference = leftParts[index] - rightParts[index];
|
|
4520
|
+
if (difference !== 0) return Math.sign(difference);
|
|
4521
|
+
}
|
|
4522
|
+
return 0;
|
|
4523
|
+
}
|
|
4524
|
+
function parseClientReleaseManifest(value) {
|
|
4525
|
+
if (!isRecord(value)) return null;
|
|
4526
|
+
if (value.schema_version !== CLIENT_RELEASE_SCHEMA_VERSION) return null;
|
|
4527
|
+
const latestVersion = value.latest_version;
|
|
4528
|
+
if (typeof latestVersion !== "string" || !parseStableClientVersion(latestVersion)) {
|
|
4529
|
+
return null;
|
|
4530
|
+
}
|
|
4531
|
+
if (typeof value.published_at !== "string" || !isCanonicalIsoTimestamp(value.published_at)) {
|
|
4532
|
+
return null;
|
|
4533
|
+
}
|
|
4534
|
+
if (!Array.isArray(value.surfaces)) return null;
|
|
4535
|
+
const validSurfaces = new Set(CLIENT_RELEASE_SURFACES);
|
|
4536
|
+
const surfaces = value.surfaces.filter(
|
|
4537
|
+
(surface) => typeof surface === "string" && validSurfaces.has(surface)
|
|
4538
|
+
);
|
|
4539
|
+
if (surfaces.length !== value.surfaces.length || new Set(surfaces).size !== surfaces.length) {
|
|
4540
|
+
return null;
|
|
4541
|
+
}
|
|
4542
|
+
return {
|
|
4543
|
+
schema_version: CLIENT_RELEASE_SCHEMA_VERSION,
|
|
4544
|
+
latest_version: latestVersion,
|
|
4545
|
+
published_at: new Date(value.published_at).toISOString(),
|
|
4546
|
+
surfaces
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
function resolveClientUpdateStatus(input) {
|
|
4550
|
+
if (!parseStableClientVersion(input.currentVersion)) {
|
|
4551
|
+
return {
|
|
4552
|
+
status: "unavailable",
|
|
4553
|
+
current_version: input.currentVersion,
|
|
4554
|
+
reason: "invalid_current_version"
|
|
4555
|
+
};
|
|
4556
|
+
}
|
|
4557
|
+
const manifest = parseClientReleaseManifest(input.manifest);
|
|
4558
|
+
if (!manifest) {
|
|
4559
|
+
return {
|
|
4560
|
+
status: "unavailable",
|
|
4561
|
+
current_version: input.currentVersion,
|
|
4562
|
+
reason: "invalid_manifest"
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
4565
|
+
if (!manifest.surfaces.includes(input.surface)) {
|
|
4566
|
+
return {
|
|
4567
|
+
status: "unavailable",
|
|
4568
|
+
current_version: input.currentVersion,
|
|
4569
|
+
reason: "surface_not_published"
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
if (compareClientVersions(manifest.latest_version, input.currentVersion) <= 0) {
|
|
4573
|
+
return {
|
|
4574
|
+
status: "current",
|
|
4575
|
+
current_version: input.currentVersion,
|
|
4576
|
+
latest_version: manifest.latest_version
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4579
|
+
const guidance = CLIENT_UPDATE_GUIDANCE[input.surface];
|
|
4580
|
+
return {
|
|
4581
|
+
status: "update_available",
|
|
4582
|
+
current_version: input.currentVersion,
|
|
4583
|
+
latest_version: manifest.latest_version,
|
|
4584
|
+
guidance,
|
|
4585
|
+
notice: clientUpdateNotice({
|
|
4586
|
+
currentVersion: input.currentVersion,
|
|
4587
|
+
latestVersion: manifest.latest_version,
|
|
4588
|
+
guidance
|
|
4589
|
+
})
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
function clientUpdateNotice(input) {
|
|
4593
|
+
const command = input.guidance.command ? `
|
|
4594
|
+
Trusted update command bundled with this installed client:
|
|
4595
|
+
${input.guidance.command}` : "";
|
|
4596
|
+
return [
|
|
4597
|
+
`Remembrance update available: installed ${input.currentVersion}, published ${input.latestVersion}.`,
|
|
4598
|
+
input.guidance.update_action,
|
|
4599
|
+
command,
|
|
4600
|
+
input.guidance.restart_action,
|
|
4601
|
+
"Do not claim the new version is active until a fresh process reports it."
|
|
4602
|
+
].filter(Boolean).join("\n");
|
|
4603
|
+
}
|
|
4604
|
+
function isRecord(value) {
|
|
4605
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4606
|
+
}
|
|
4607
|
+
function isCanonicalIsoTimestamp(value) {
|
|
4608
|
+
const timestamp = Date.parse(value);
|
|
4609
|
+
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4400
4612
|
// ../core/src/client-health-alerts.ts
|
|
4401
4613
|
var secureWebhookUrlSchema = external_exports.string().trim().url().max(2048).superRefine((value, context) => {
|
|
4402
4614
|
let parsed;
|
|
@@ -4486,6 +4698,13 @@ function isUnsafeDestinationHostname(hostname) {
|
|
|
4486
4698
|
}
|
|
4487
4699
|
|
|
4488
4700
|
// ../core/src/connection-doctor.ts
|
|
4701
|
+
var HOST_POLICY_OPERATION_CLASSES = /* @__PURE__ */ new Set([
|
|
4702
|
+
"query",
|
|
4703
|
+
"private_contribution",
|
|
4704
|
+
"contribution",
|
|
4705
|
+
"feedback",
|
|
4706
|
+
"other"
|
|
4707
|
+
]);
|
|
4489
4708
|
var CODEX_HOOK_REVIEW_COMMAND = `CODEX_CLI="\${CODEX_CLI:-$(command -v codex || true)}"; [ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/ChatGPT.app/Contents/Resources/codex"; [ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/Codex.app/Contents/Resources/codex"; [ -x "$CODEX_CLI" ] || { printf '%s\\n' "Codex CLI not found. Install the Codex CLI, or install or update the ChatGPT desktop app on macOS, then try again." >&2; exit 1; }; "$CODEX_CLI"`;
|
|
4490
4709
|
function codexHookReviewAction(followupTool) {
|
|
4491
4710
|
return `Open Terminal and launch Codex with the command below. If Codex shows a Hooks need review screen, choose Review hooks and trust only the Remembrance SessionStart, UserPromptSubmit, PostToolUse, and Stop hooks. If no review screen appears, continue: Codex may be reusing an existing valid trust decision. Fully restart Codex, submit one prompt, use one Remembrance tool, complete one turn, and call ${followupTool} again. If the lifecycle warning remains, update or reinstall Remembrance and repeat this check.`;
|
|
@@ -4498,6 +4717,7 @@ function buildConnectionDoctorReport(input) {
|
|
|
4498
4717
|
const capabilities = record(authentication.capabilities);
|
|
4499
4718
|
const sharedConfig = record(transport.shared_config);
|
|
4500
4719
|
const pluginHealth = record(status.plugin_health);
|
|
4720
|
+
const hostPolicy = record(pluginHealth.host_policy);
|
|
4501
4721
|
const identity = record(status.local_signing_identity);
|
|
4502
4722
|
const destination = destinationFromTransport(transport);
|
|
4503
4723
|
const credentialSource = stringValue(transport.credential_source) ?? "none";
|
|
@@ -4629,6 +4849,22 @@ function buildConnectionDoctorReport(input) {
|
|
|
4629
4849
|
)
|
|
4630
4850
|
);
|
|
4631
4851
|
checks.push(signingIdentityCheck(identity, input.transport));
|
|
4852
|
+
checks.push(clientUpdateCheck(input.client_update ?? null));
|
|
4853
|
+
const hostPolicyStatus = hostPolicyStatusValue(hostPolicy.status);
|
|
4854
|
+
checks.push(
|
|
4855
|
+
hostPolicyStatus === "recent_denial" ? warning(
|
|
4856
|
+
"host_policy_observability",
|
|
4857
|
+
"The host recently blocked a Remembrance operation before transport; no content was sent.",
|
|
4858
|
+
"review_host_policy",
|
|
4859
|
+
"Review the host's trusted-service or data-export policy. Querying remains available."
|
|
4860
|
+
) : hostPolicyStatus === "no_recent_denial" ? pass(
|
|
4861
|
+
"host_policy_observability",
|
|
4862
|
+
"This plugin can observe host policy denials; none were observed recently. This does not grant permission for future submissions."
|
|
4863
|
+
) : notApplicable(
|
|
4864
|
+
"host_policy_observability",
|
|
4865
|
+
"This host does not expose a reliable pre-transport policy-denial event to the plugin."
|
|
4866
|
+
)
|
|
4867
|
+
);
|
|
4632
4868
|
const blockingIds = /* @__PURE__ */ new Set([
|
|
4633
4869
|
"configuration",
|
|
4634
4870
|
"credential_security",
|
|
@@ -4644,6 +4880,7 @@ function buildConnectionDoctorReport(input) {
|
|
|
4644
4880
|
(check) => check.status === "warning" || check.status === "fail"
|
|
4645
4881
|
);
|
|
4646
4882
|
const readSucceeded = !input.active_read_probe.requested || input.active_read_probe.succeeded === true;
|
|
4883
|
+
const registrySubmissionAuthorized = scope === "organization" && submitAuthorized;
|
|
4647
4884
|
return {
|
|
4648
4885
|
schema_version: "1",
|
|
4649
4886
|
status: blocked ? "blocked" : attention ? "attention" : "healthy",
|
|
@@ -4651,12 +4888,88 @@ function buildConnectionDoctorReport(input) {
|
|
|
4651
4888
|
scope,
|
|
4652
4889
|
destination,
|
|
4653
4890
|
safe_to_query: !blocked && registryReady && queryAuthorized && readSucceeded,
|
|
4654
|
-
|
|
4891
|
+
registry_submission_authorized: registrySubmissionAuthorized,
|
|
4892
|
+
organization_write_authorized: registrySubmissionAuthorized,
|
|
4893
|
+
host_policy: {
|
|
4894
|
+
status: hostPolicyStatus,
|
|
4895
|
+
observable: hostPolicy.observable === true,
|
|
4896
|
+
...hostPolicyOperationClassValue(hostPolicy.operation_class) ? {
|
|
4897
|
+
operation_class: hostPolicyOperationClassValue(
|
|
4898
|
+
hostPolicy.operation_class
|
|
4899
|
+
)
|
|
4900
|
+
} : {},
|
|
4901
|
+
...isoTimestampValue(hostPolicy.last_denial_at) ? { last_denial_at: isoTimestampValue(hostPolicy.last_denial_at) } : {}
|
|
4902
|
+
},
|
|
4903
|
+
client_update: clientUpdateSummary(input.client_update ?? null),
|
|
4655
4904
|
signed_contributions_ready: input.transport === "hosted_http_mcp" ? null : identity.status === "ready",
|
|
4656
4905
|
active_read_probe: input.active_read_probe,
|
|
4657
4906
|
checks
|
|
4658
4907
|
};
|
|
4659
4908
|
}
|
|
4909
|
+
function clientUpdateCheck(update) {
|
|
4910
|
+
if (!update) {
|
|
4911
|
+
return notApplicable(
|
|
4912
|
+
"client_update",
|
|
4913
|
+
"The published client release could not be checked. This does not affect querying."
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
if (update.status === "update_available") {
|
|
4917
|
+
return warning(
|
|
4918
|
+
"client_update",
|
|
4919
|
+
`Remembrance ${update.latest_version} is published; this process is running ${update.current_version}.`,
|
|
4920
|
+
"update_remembrance_client",
|
|
4921
|
+
`${update.guidance.update_action} ${update.guidance.restart_action} Do not claim the new version is active until a fresh process reports it.`,
|
|
4922
|
+
void 0,
|
|
4923
|
+
false,
|
|
4924
|
+
update.guidance.command ?? void 0
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
if (update.status === "current") {
|
|
4928
|
+
return pass(
|
|
4929
|
+
"client_update",
|
|
4930
|
+
`This process is current at Remembrance ${update.current_version}.`
|
|
4931
|
+
);
|
|
4932
|
+
}
|
|
4933
|
+
return notApplicable(
|
|
4934
|
+
"client_update",
|
|
4935
|
+
"The installed or published client version could not be compared safely."
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
function clientUpdateSummary(update) {
|
|
4939
|
+
if (!update) {
|
|
4940
|
+
return {
|
|
4941
|
+
status: "unavailable",
|
|
4942
|
+
current_version: null,
|
|
4943
|
+
latest_version: null
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
if (update.status === "current" || update.status === "update_available") {
|
|
4947
|
+
return {
|
|
4948
|
+
status: update.status,
|
|
4949
|
+
current_version: update.current_version,
|
|
4950
|
+
latest_version: update.latest_version
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
return {
|
|
4954
|
+
status: "unavailable",
|
|
4955
|
+
current_version: update.current_version,
|
|
4956
|
+
latest_version: null
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4959
|
+
function hostPolicyOperationClassValue(value) {
|
|
4960
|
+
if (typeof value !== "string" || !HOST_POLICY_OPERATION_CLASSES.has(value)) {
|
|
4961
|
+
return void 0;
|
|
4962
|
+
}
|
|
4963
|
+
return value;
|
|
4964
|
+
}
|
|
4965
|
+
function isoTimestampValue(value) {
|
|
4966
|
+
if (typeof value !== "string") return void 0;
|
|
4967
|
+
const parsed = Date.parse(value);
|
|
4968
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : void 0;
|
|
4969
|
+
}
|
|
4970
|
+
function hostPolicyStatusValue(value) {
|
|
4971
|
+
return value === "recent_denial" || value === "no_recent_denial" ? value : "not_observable";
|
|
4972
|
+
}
|
|
4660
4973
|
function readProbeCheck(probe) {
|
|
4661
4974
|
if (!probe.requested) {
|
|
4662
4975
|
return notApplicable(
|
|
@@ -4809,11 +5122,229 @@ function stringValue(value) {
|
|
|
4809
5122
|
return typeof value === "string" && value ? value : null;
|
|
4810
5123
|
}
|
|
4811
5124
|
|
|
5125
|
+
// ../core/src/agent-preferences.ts
|
|
5126
|
+
var MAX_EFFECTIVE_PREFERENCES = 32;
|
|
5127
|
+
var MAX_SKILL_PREFERENCE_TRAITS = 40;
|
|
5128
|
+
var preferenceIdentifierSchema = external_exports.string().trim().toLowerCase().min(1).max(96).regex(/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/);
|
|
5129
|
+
var preferenceKeySchema = preferenceIdentifierSchema;
|
|
5130
|
+
var preferenceValueSchema = preferenceIdentifierSchema;
|
|
5131
|
+
var preferenceEffectSchema = external_exports.enum([
|
|
5132
|
+
"presentation",
|
|
5133
|
+
"workflow",
|
|
5134
|
+
"strategy_selection"
|
|
5135
|
+
]);
|
|
5136
|
+
var preferenceStrengthSchema = external_exports.enum(["prefer", "avoid"]);
|
|
5137
|
+
var BUILT_IN_PREFERENCE_DEFINITIONS = {
|
|
5138
|
+
comment_density: {
|
|
5139
|
+
label: "Comment density",
|
|
5140
|
+
effect: "presentation",
|
|
5141
|
+
values: {
|
|
5142
|
+
sparse: "Use few comments and reserve them for decisions that code cannot explain.",
|
|
5143
|
+
balanced: "Use comments selectively for intent and non-obvious behavior.",
|
|
5144
|
+
detailed: "Use detailed comments where they improve maintainability."
|
|
5145
|
+
}
|
|
5146
|
+
},
|
|
5147
|
+
comment_focus: {
|
|
5148
|
+
label: "Comment focus",
|
|
5149
|
+
effect: "presentation",
|
|
5150
|
+
values: {
|
|
5151
|
+
intent_only: "Comment intent and tradeoffs rather than restating code.",
|
|
5152
|
+
tricky_logic: "Prioritize comments around tricky or non-obvious logic.",
|
|
5153
|
+
api_contracts: "Prioritize comments that clarify API and data contracts.",
|
|
5154
|
+
comprehensive: "Comment intent, tricky logic, and important contracts comprehensively."
|
|
5155
|
+
}
|
|
5156
|
+
},
|
|
5157
|
+
explanation_depth: {
|
|
5158
|
+
label: "Explanation depth",
|
|
5159
|
+
effect: "presentation",
|
|
5160
|
+
values: {
|
|
5161
|
+
concise: "Keep explanations concise and focused on decisions and outcomes.",
|
|
5162
|
+
balanced: "Balance concise conclusions with enough rationale to verify the work.",
|
|
5163
|
+
detailed: "Provide detailed rationale, tradeoffs, and verification context."
|
|
5164
|
+
}
|
|
5165
|
+
},
|
|
5166
|
+
output_organization: {
|
|
5167
|
+
label: "Output organization",
|
|
5168
|
+
effect: "presentation",
|
|
5169
|
+
values: {
|
|
5170
|
+
compact: "Use a compact response with minimal structural overhead.",
|
|
5171
|
+
structured: "Use clear sections when they improve scanability.",
|
|
5172
|
+
step_by_step: "Present the work as an ordered sequence of steps."
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
};
|
|
5176
|
+
var PREFERENCE_KEYS = Object.keys(
|
|
5177
|
+
BUILT_IN_PREFERENCE_DEFINITIONS
|
|
5178
|
+
);
|
|
5179
|
+
var PREFERENCE_VALUE_OPTIONS = Object.fromEntries(
|
|
5180
|
+
Object.entries(BUILT_IN_PREFERENCE_DEFINITIONS).map(([key, definition]) => [
|
|
5181
|
+
key,
|
|
5182
|
+
Object.keys(definition.values)
|
|
5183
|
+
])
|
|
5184
|
+
);
|
|
5185
|
+
var preferenceSettingBaseSchema = external_exports.object({
|
|
5186
|
+
key: preferenceKeySchema,
|
|
5187
|
+
value: preferenceValueSchema,
|
|
5188
|
+
// Built-in definitions can omit these fields. Extensible definitions carry
|
|
5189
|
+
// their complete bounded meaning with the observation so no hot-path lookup
|
|
5190
|
+
// or code release is needed to understand a new preference type.
|
|
5191
|
+
label: external_exports.string().trim().min(1).max(96).optional(),
|
|
5192
|
+
behavior: external_exports.string().trim().min(1).max(320).optional(),
|
|
5193
|
+
effect: preferenceEffectSchema.optional(),
|
|
5194
|
+
strength: preferenceStrengthSchema.optional(),
|
|
5195
|
+
definition_version: external_exports.number().int().min(1).max(1e6).optional()
|
|
5196
|
+
}).strict();
|
|
5197
|
+
var preferenceSettingSchema = preferenceSettingBaseSchema.superRefine(
|
|
5198
|
+
(setting, ctx) => {
|
|
5199
|
+
const builtIn = builtInPreferenceDefinition(setting.key);
|
|
5200
|
+
if (builtIn) {
|
|
5201
|
+
const builtInBehavior = builtIn.values[setting.value];
|
|
5202
|
+
if (!builtInBehavior) {
|
|
5203
|
+
ctx.addIssue({
|
|
5204
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5205
|
+
path: ["value"],
|
|
5206
|
+
message: `Unsupported ${setting.key} value`
|
|
5207
|
+
});
|
|
5208
|
+
}
|
|
5209
|
+
if (setting.label && setting.label !== builtIn.label) {
|
|
5210
|
+
ctx.addIssue({
|
|
5211
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5212
|
+
path: ["label"],
|
|
5213
|
+
message: `${setting.key} uses its canonical label`
|
|
5214
|
+
});
|
|
5215
|
+
}
|
|
5216
|
+
if (setting.behavior && setting.behavior !== builtInBehavior) {
|
|
5217
|
+
ctx.addIssue({
|
|
5218
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5219
|
+
path: ["behavior"],
|
|
5220
|
+
message: `${setting.key} uses the canonical behavior for ${setting.value}`
|
|
5221
|
+
});
|
|
5222
|
+
}
|
|
5223
|
+
if (setting.effect && setting.effect !== builtIn.effect) {
|
|
5224
|
+
ctx.addIssue({
|
|
5225
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5226
|
+
path: ["effect"],
|
|
5227
|
+
message: `${setting.key} is a ${builtIn.effect} preference`
|
|
5228
|
+
});
|
|
5229
|
+
}
|
|
5230
|
+
if (setting.strength && setting.strength !== "prefer") {
|
|
5231
|
+
ctx.addIssue({
|
|
5232
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5233
|
+
path: ["strength"],
|
|
5234
|
+
message: `${setting.key} uses prefer; define a custom key for inverse behavior`
|
|
5235
|
+
});
|
|
5236
|
+
}
|
|
5237
|
+
if (setting.definition_version !== void 0 && setting.definition_version !== 1) {
|
|
5238
|
+
ctx.addIssue({
|
|
5239
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5240
|
+
path: ["definition_version"],
|
|
5241
|
+
message: `${setting.key} uses definition version 1`
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
return;
|
|
5245
|
+
}
|
|
5246
|
+
for (const field of ["label", "behavior", "effect"]) {
|
|
5247
|
+
if (!setting[field]) {
|
|
5248
|
+
ctx.addIssue({
|
|
5249
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5250
|
+
path: [field],
|
|
5251
|
+
message: `${field} is required for an extensible preference`
|
|
5252
|
+
});
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
);
|
|
5257
|
+
var preferenceScopeSchema = external_exports.enum([
|
|
5258
|
+
"task",
|
|
5259
|
+
"organization",
|
|
5260
|
+
"member",
|
|
5261
|
+
"member_runtime",
|
|
5262
|
+
"installation",
|
|
5263
|
+
"project",
|
|
5264
|
+
"skill",
|
|
5265
|
+
"domain"
|
|
5266
|
+
]);
|
|
5267
|
+
var preferenceSourceSchema = external_exports.enum([
|
|
5268
|
+
"mandatory_org",
|
|
5269
|
+
"explicit_task",
|
|
5270
|
+
"explicit_project",
|
|
5271
|
+
"explicit_member_runtime",
|
|
5272
|
+
"explicit_member",
|
|
5273
|
+
"learned_member_runtime",
|
|
5274
|
+
"learned_member",
|
|
5275
|
+
"explicit_installation",
|
|
5276
|
+
"learned_installation",
|
|
5277
|
+
"recommended_org",
|
|
5278
|
+
"skill_default"
|
|
5279
|
+
]);
|
|
5280
|
+
var preferenceObservationSchema = external_exports.object({
|
|
5281
|
+
setting: preferenceSettingSchema,
|
|
5282
|
+
scope: preferenceScopeSchema,
|
|
5283
|
+
source: external_exports.enum([
|
|
5284
|
+
"explicit_user",
|
|
5285
|
+
"agent_observed",
|
|
5286
|
+
"admin_override",
|
|
5287
|
+
"admin_required"
|
|
5288
|
+
]),
|
|
5289
|
+
task_hash: external_exports.string().min(1).max(128),
|
|
5290
|
+
project_key: external_exports.string().min(1).max(128).nullable().default(null),
|
|
5291
|
+
skill_slug: external_exports.string().min(1).max(512).nullable().default(null),
|
|
5292
|
+
domain: external_exports.string().min(1).max(512).nullable().default(null),
|
|
5293
|
+
confidence: external_exports.number().min(0).max(1),
|
|
5294
|
+
observed_at: external_exports.string().datetime()
|
|
5295
|
+
}).strict();
|
|
5296
|
+
function boundedUniquePreferenceSettingsSchema(duplicateMessage) {
|
|
5297
|
+
return external_exports.array(preferenceSettingSchema).max(MAX_EFFECTIVE_PREFERENCES).superRefine((settings, ctx) => {
|
|
5298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5299
|
+
settings.forEach((setting, index) => {
|
|
5300
|
+
if (seen.has(setting.key)) {
|
|
5301
|
+
ctx.addIssue({
|
|
5302
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5303
|
+
path: [index, "key"],
|
|
5304
|
+
message: duplicateMessage
|
|
5305
|
+
});
|
|
5306
|
+
}
|
|
5307
|
+
seen.add(setting.key);
|
|
5308
|
+
});
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
5311
|
+
var taskPreferenceSettingsSchema = boundedUniquePreferenceSettingsSchema(
|
|
5312
|
+
"Task preferences must use each key at most once"
|
|
5313
|
+
);
|
|
5314
|
+
var skillPreferenceDefaultsSchema = boundedUniquePreferenceSettingsSchema(
|
|
5315
|
+
"Skill preference defaults must use each key at most once"
|
|
5316
|
+
);
|
|
5317
|
+
var skillPreferenceTraitSchema = external_exports.object({
|
|
5318
|
+
key: preferenceKeySchema,
|
|
5319
|
+
value: preferenceValueSchema,
|
|
5320
|
+
relationship: external_exports.enum(["supports", "conflicts"]).default("supports"),
|
|
5321
|
+
locked: external_exports.boolean().default(false),
|
|
5322
|
+
reason: external_exports.string().trim().min(1).max(200).nullable().default(null)
|
|
5323
|
+
}).strict();
|
|
5324
|
+
var skillPreferenceTraitsSchema = external_exports.array(skillPreferenceTraitSchema).max(MAX_SKILL_PREFERENCE_TRAITS).superRefine((traits, ctx) => {
|
|
5325
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5326
|
+
traits.forEach((trait, index) => {
|
|
5327
|
+
const identity = `${trait.key}:${trait.value}:${trait.relationship}`;
|
|
5328
|
+
if (seen.has(identity)) {
|
|
5329
|
+
ctx.addIssue({
|
|
5330
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5331
|
+
path: [index],
|
|
5332
|
+
message: "Skill preference traits must be unique"
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
seen.add(identity);
|
|
5336
|
+
});
|
|
5337
|
+
});
|
|
5338
|
+
var INFERRED_MAX_AGE_MS = 180 * 864e5;
|
|
5339
|
+
function builtInPreferenceDefinition(key) {
|
|
5340
|
+
return BUILT_IN_PREFERENCE_DEFINITIONS[key] ?? null;
|
|
5341
|
+
}
|
|
5342
|
+
|
|
4812
5343
|
// ../core/src/agent-guidance.ts
|
|
4813
5344
|
var REMEMBRANCE_QUERY_TOOL_DESCRIPTION = "Call before non-trivial service, API, tool, library, workflow, UI, review, test, security, or deployment work to find relevant Remembrance skills and resources. For short context-dependent follow-ups, infer the concrete task from the full conversation, preserve any plugin-supplied client_context.directive_id/runtime/trigger_reason, and query anyway. Compare each candidate's bounded why_matched evidence and applicability conditions before opening it; discard stated unlikely or corner-case mismatches. High matches should be fetched before custom work; possible and exploratory matches remain optional. Do not use for broad web search or one-off facts.";
|
|
4814
5345
|
var REMEMBRANCE_CONNECTION_STATUS_TOOL_DESCRIPTION = "Inspect the active Remembrance transport, credential source, and authenticated registry scope without exposing credentials. Call this before diagnosing authentication, checking environment variables, or making anonymous REST/browser probes. Local or bundled MCP can read REMEMBRANCE_API_KEY or ~/.config/remembrance/config.json; hosted MCP cannot read local files and authenticates from its request header. When a declared native lifecycle is degraded, local MCP may submit bounded, content-free health evidence unless REMEMBRANCE_HEALTH_REPORTING=0. Never infer the plugin's scope from REMEMBRANCE_API_KEY alone.";
|
|
4815
5346
|
var REMEMBRANCE_CONNECTION_DOCTOR_TOOL_DESCRIPTION = "Run a bounded, non-mutating Remembrance connection diagnostic. It verifies the active transport, a redacted registry destination, authenticated scope, query authorization, a real catalog read, and organization submission capability. Local MCP also checks shared-config permissions, destination consistency, native plugin lifecycle, and signing identity; hosted MCP marks local-only checks unavailable. It never submits content, creates query demand, opens a review item, or returns credentials, absolute paths, private registry URLs, prompts, or repository data.";
|
|
4816
|
-
var REMEMBRANCE_MCP_SERVER_INSTRUCTIONS = "Remembrance is shared operational memory for agents. Before claiming that Remembrance is anonymous, unconfigured, partially active, or missing an organization key, call run_connection_doctor for the transport you will actually use. It performs a safe catalog read and returns exact bounded remediation without submitting content. Use get_connection_status only when you need the underlying transport and lifecycle fields. An unset REMEMBRANCE_API_KEY does not prove that a local plugin is anonymous because local hooks and bundled MCP may read ~/.config/remembrance/config.json. Conversely, hosted MCP cannot read that local file. Anonymous curl or browser probes describe only those requests, not plugin authentication. Never print or request the raw key. When the user explicitly names an authorized Remembrance skill or supplies a remembrance://skills/{slug} URI, resolve ambiguous names with list_skills using its normalized slug-prefix filter, then call invoke_skill with an exact returned slug. Never guess a slug. list_skills is catalog resolution, not relevance search; use query_skills when the user wants relevant candidates rather than a known selection. invoke_skill rechecks authorization and organization policy, resolves the current reviewed version, and returns the only full instruction body. Catalog listings and MCP resource reads are lightweight handles, not skill use. Do not run query_skills merely to rediscover that explicit selection, and do not submit query-fit feedback for it. After meaningful direct use, follow the returned task-outcome and post-use feedback instructions once. BEFORE non-trivial service/API/tool/library/workflow/UI/review/test/security/deployment work, call query_skills to reuse proven skills and resources instead of rediscovering them. A local repository change still qualifies when it involves a reusable workflow or lesson; skip only genuinely trivial throwaway edits and one-off facts. For short follow-ups such as 'fix these issues', 'continue', or 'try again', infer the concrete task, domain, and constraints from the full conversation and still call query_skills; do not wait for the current prompt to repeat trigger keywords. Treat match_tier as a decision aid, then inspect why_matched and applicability before opening a result. These fields show bounded matched terms/capabilities, satisfied and missed constraints, qualitative lexical/semantic evidence, declared scope, use conditions, and exclusions without exposing unstable raw ranking scores. Discard an unlikely or irrelevant corner-case result and report fit: poor; do not force its use. For a remaining high match, call get_skill or get_resource before custom work and pass the returned query_id/result_id so the surfaced-to-fetch funnel closes; possible and exploratory matches are optional. The response also includes approximate context tokens, verified uses, risk, tags, permissions, dependencies, and contraindications so you can weigh the detour safely. Honor query_skills.skill_access on every response. When policy is org_only, use only returned organization skills and never substitute bundled or live public skill references; if an organization key is configured and the query is unavailable, fail closed because the policy cannot be confirmed. AFTER query_skills, call submit_query_feedback once with one complete set of explicit good/partial/poor judgments using the returned query_id and result_id values; leave unrated results neutral, and remember that a poor query match is not the same as a globally bad skill. Reuse the same organization or anonymous auth scope as the query. AFTER you actually use a skill or resource, close the post-use loop with submit_feedback (useful true/false plus a one-line lesson and the originating query_id/result_id; it returns a ready submit_remembrance payload), then submit_remembrance if the lesson is reusable. If nothing fit and you built a new workflow, use propose_private_skill for an organization-private candidate. With an organization key, propose_skill_idea also remains organization-private. Never remove, suppress, or bypass that key to force a public submission; submit privately, then use the reviewed public-propagation flow when the organization chooses to share it. When delegating, pass the selected slug, exact version, query_id, and result_id to the subagent; it should invoke/fetch that result or run its own full-context query. The parent reports the terminal outcome unless the subagent creates its own invocation. Before finishing any reusable task, self-check for a missed query. If you catch your own mistake, the user catches one, CI/deploy fails, a security issue appears, or you fix a release/versioning miss, submit a failure_report remembrance even if no skill was used; raw MCP clients have no plugin Stop hook to remind you later. A host privacy or tenant-policy denial happens before Remembrance receives the request.
|
|
5347
|
+
var REMEMBRANCE_MCP_SERVER_INSTRUCTIONS = "Remembrance is shared operational memory for agents. Before claiming that Remembrance is anonymous, unconfigured, partially active, or missing an organization key, call run_connection_doctor for the transport you will actually use. It performs a safe catalog read and returns exact bounded remediation without submitting content. Use get_connection_status only when you need the underlying transport and lifecycle fields. An unset REMEMBRANCE_API_KEY does not prove that a local plugin is anonymous because local hooks and bundled MCP may read ~/.config/remembrance/config.json. Conversely, hosted MCP cannot read that local file. Anonymous curl or browser probes describe only those requests, not plugin authentication. Never print or request the raw key. When the user explicitly names an authorized Remembrance skill or supplies a remembrance://skills/{slug} URI, resolve ambiguous names with list_skills using its normalized slug-prefix filter, then call invoke_skill with an exact returned slug. Never guess a slug. list_skills is catalog resolution, not relevance search; use query_skills when the user wants relevant candidates rather than a known selection. invoke_skill rechecks authorization and organization policy, resolves the current reviewed version, and returns the only full instruction body. Catalog listings and MCP resource reads are lightweight handles, not skill use. Do not run query_skills merely to rediscover that explicit selection, and do not submit query-fit feedback for it. After meaningful direct use, follow the returned task-outcome and post-use feedback instructions once. BEFORE non-trivial service/API/tool/library/workflow/UI/review/test/security/deployment work, call query_skills to reuse proven skills and resources instead of rediscovering them. A local repository change still qualifies when it involves a reusable workflow or lesson; skip only genuinely trivial throwaway edits and one-off facts. For short follow-ups such as 'fix these issues', 'continue', or 'try again', infer the concrete task, domain, and constraints from the full conversation and still call query_skills; do not wait for the current prompt to repeat trigger keywords. Treat match_tier as a decision aid, then inspect why_matched and applicability before opening a result. These fields show bounded matched terms/capabilities, satisfied and missed constraints, qualitative lexical/semantic evidence, declared scope, use conditions, and exclusions without exposing unstable raw ranking scores. Discard an unlikely or irrelevant corner-case result and report fit: poor; do not force its use. For a remaining high match, call get_skill or get_resource before custom work and pass the returned query_id/result_id so the surfaced-to-fetch funnel closes; possible and exploratory matches are optional. The response also includes approximate context tokens, verified uses, risk, tags, permissions, dependencies, and contraindications so you can weigh the detour safely. Honor query_skills.skill_access on every response. When policy is org_only, use only returned organization skills and never substitute bundled or live public skill references; if an organization key is configured and the query is unavailable, fail closed because the policy cannot be confirmed. AFTER query_skills, call submit_query_feedback once with one complete set of explicit good/partial/poor judgments using the returned query_id and result_id values; leave unrated results neutral, and remember that a poor query match is not the same as a globally bad skill. Reuse the same organization or anonymous auth scope as the query. AFTER you actually use a skill or resource, close the post-use loop with submit_feedback (useful true/false plus a one-line lesson and the originating query_id/result_id; it returns a ready submit_remembrance payload), then submit_remembrance if the lesson is reusable. If nothing fit and you built a new workflow, use propose_private_skill for an organization-private candidate. With an organization key, propose_skill_idea also remains organization-private. Never remove, suppress, or bypass that key to force a public submission; submit privately, then use the reviewed public-propagation flow when the organization chooses to share it. When delegating, pass the selected slug, exact version, query_id, and result_id to the subagent; it should invoke/fetch that result or run its own full-context query. The parent reports the terminal outcome unless the subagent creates its own invocation. Before finishing any reusable task, self-check for a missed query. If you catch your own mistake, the user catches one, CI/deploy fails, a security issue appears, or you fix a release/versioning miss, submit a failure_report remembrance even if no skill was used; raw MCP clients have no plugin Stop hook to remind you later. A host privacy or tenant-policy denial happens before Remembrance receives the request. Tell the user: 'Remembrance was blocked by host policy before reaching Remembrance. Nothing was sent. Querying remains available.' Do not retry the blocked content through another transport or claim submission. Attach evidence (reproduction detail, artifact hashes, or an attestation); evidence-less public reports wait in unverified intake until corroborated. Redact secrets, private URLs, credentials, raw logs, and proprietary content; submit summaries and hashes, not raw traces.";
|
|
4817
5348
|
|
|
4818
5349
|
// ../core/src/redaction.ts
|
|
4819
5350
|
var SECRET_PATTERN_SPECS = [
|
|
@@ -4935,6 +5466,7 @@ var REMEMBRANCE_MCP_READ_TOOLS = [
|
|
|
4935
5466
|
"query_skills",
|
|
4936
5467
|
"list_skills",
|
|
4937
5468
|
"invoke_skill",
|
|
5469
|
+
"get_effective_preferences",
|
|
4938
5470
|
"get_skill",
|
|
4939
5471
|
"get_resource",
|
|
4940
5472
|
"get_value_proof"
|
|
@@ -4947,7 +5479,9 @@ var REMEMBRANCE_MCP_FEEDBACK_TOOLS = [
|
|
|
4947
5479
|
var REMEMBRANCE_MCP_PRIVATE_CONTRIBUTION_TOOLS = [
|
|
4948
5480
|
"submit_remembrance",
|
|
4949
5481
|
"propose_private_skill",
|
|
4950
|
-
"submit_suggestion"
|
|
5482
|
+
"submit_suggestion",
|
|
5483
|
+
"record_preference",
|
|
5484
|
+
"link_current_installation"
|
|
4951
5485
|
];
|
|
4952
5486
|
var REMEMBRANCE_MCP_OPTIONAL_SHARED_CONTRIBUTION_TOOLS = [
|
|
4953
5487
|
"propose_skill_idea",
|
|
@@ -5025,6 +5559,49 @@ function remembranceSkillResourceHandle(entry) {
|
|
|
5025
5559
|
);
|
|
5026
5560
|
}
|
|
5027
5561
|
|
|
5562
|
+
// ../core/src/skill-topology.ts
|
|
5563
|
+
var skillTopologyActionSchema = external_exports.enum([
|
|
5564
|
+
"amend",
|
|
5565
|
+
"specialize",
|
|
5566
|
+
"strategy_fork",
|
|
5567
|
+
"independent_skill",
|
|
5568
|
+
"preference",
|
|
5569
|
+
"evidence_only",
|
|
5570
|
+
"hold"
|
|
5571
|
+
]);
|
|
5572
|
+
var SKILL_TOPOLOGY_ACTIONS = skillTopologyActionSchema.options;
|
|
5573
|
+
var skillScopePredicateSchema = external_exports.object({
|
|
5574
|
+
dimension: external_exports.enum([
|
|
5575
|
+
"runtime",
|
|
5576
|
+
"runtime_version",
|
|
5577
|
+
"platform",
|
|
5578
|
+
"language",
|
|
5579
|
+
"framework",
|
|
5580
|
+
"dependency",
|
|
5581
|
+
"scale",
|
|
5582
|
+
"task_stage"
|
|
5583
|
+
]),
|
|
5584
|
+
operator: external_exports.enum(["equals", "includes", "at_least", "at_most"]),
|
|
5585
|
+
value: external_exports.string().trim().min(1).max(256)
|
|
5586
|
+
}).strict();
|
|
5587
|
+
var skillTopologyAssessmentSchema = external_exports.object({
|
|
5588
|
+
action: skillTopologyActionSchema,
|
|
5589
|
+
confidence: external_exports.number().min(0).max(1),
|
|
5590
|
+
subjectivity: external_exports.number().min(0).max(1),
|
|
5591
|
+
generalizability: external_exports.number().min(0).max(1),
|
|
5592
|
+
specificity_delta: external_exports.number().min(-1).max(1),
|
|
5593
|
+
approach_divergence: external_exports.number().min(0).max(1),
|
|
5594
|
+
stable_conditions: external_exports.array(skillScopePredicateSchema).max(12).default([]),
|
|
5595
|
+
use_when: external_exports.array(external_exports.string().trim().min(1).max(512)).max(20).default([]),
|
|
5596
|
+
avoid_when: external_exports.array(external_exports.string().trim().min(1).max(512)).max(20).default([]),
|
|
5597
|
+
preference_setting: preferenceSettingSchema.nullable().default(null),
|
|
5598
|
+
rationale: external_exports.string().trim().min(1).max(2e3)
|
|
5599
|
+
}).strict();
|
|
5600
|
+
var skillTopologyRoutingHintSchema = external_exports.object({
|
|
5601
|
+
suggested_action: skillTopologyActionSchema,
|
|
5602
|
+
conditions: external_exports.array(skillScopePredicateSchema).max(12).default([])
|
|
5603
|
+
}).strict();
|
|
5604
|
+
|
|
5028
5605
|
// ../core/src/schemas.ts
|
|
5029
5606
|
var DEFAULT_MUTATION_BODY_LIMIT_BYTES = 256 * 1024;
|
|
5030
5607
|
var MAX_SHORT_TEXT_LENGTH = 512;
|
|
@@ -5243,7 +5820,11 @@ var agentSchema = external_exports.object({
|
|
|
5243
5820
|
var queryTaskSchema = external_exports.object({
|
|
5244
5821
|
domain: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
5245
5822
|
summary: boundedString(MAX_LONG_TEXT_LENGTH),
|
|
5246
|
-
constraints: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(40).default([])
|
|
5823
|
+
constraints: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(40).default([]),
|
|
5824
|
+
// Explicit task-local preferences are normalized data, not raw prompt text.
|
|
5825
|
+
// They are resolved for this request only and are never persisted as a
|
|
5826
|
+
// durable member preference unless the caller separately records them.
|
|
5827
|
+
preferences: taskPreferenceSettingsSchema.optional()
|
|
5247
5828
|
});
|
|
5248
5829
|
var queryRuntimeSchema = external_exports.enum([
|
|
5249
5830
|
"codex",
|
|
@@ -5255,12 +5836,23 @@ var queryRuntimeSchema = external_exports.enum([
|
|
|
5255
5836
|
"other",
|
|
5256
5837
|
"unknown"
|
|
5257
5838
|
]);
|
|
5839
|
+
var runtimeHostSurfaceSchema = external_exports.enum([
|
|
5840
|
+
"desktop",
|
|
5841
|
+
"cli",
|
|
5842
|
+
"extension",
|
|
5843
|
+
"gateway",
|
|
5844
|
+
"unknown"
|
|
5845
|
+
]);
|
|
5846
|
+
var runtimeProfileVersionSchema = external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/);
|
|
5258
5847
|
var queryDirectiveIdSchema = external_exports.string().regex(/^dir_[A-Za-z0-9_-]{16,80}$/);
|
|
5259
5848
|
var queryClientContextSchema = external_exports.object({
|
|
5260
5849
|
surface: external_exports.enum(["plugin_hook", "mcp", "rest", "unknown"]).default("unknown"),
|
|
5261
5850
|
runtime: queryRuntimeSchema.optional(),
|
|
5262
5851
|
trigger_reason: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
|
|
5263
|
-
directive_id: queryDirectiveIdSchema.optional()
|
|
5852
|
+
directive_id: queryDirectiveIdSchema.optional(),
|
|
5853
|
+
// A local, installation-keyed digest. It lets one engineer retain
|
|
5854
|
+
// project-scoped preferences without disclosing a path or repository.
|
|
5855
|
+
project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).optional()
|
|
5264
5856
|
}).strict();
|
|
5265
5857
|
var reasoningEffortSchema = external_exports.enum([
|
|
5266
5858
|
"none",
|
|
@@ -5483,31 +6075,145 @@ var agentTaskOutcomeRequestSchema = external_exports.object({
|
|
|
5483
6075
|
});
|
|
5484
6076
|
}
|
|
5485
6077
|
});
|
|
5486
|
-
var
|
|
6078
|
+
var runtimeProfileRegistrationSchema = external_exports.object({
|
|
6079
|
+
runtime: queryRuntimeSchema,
|
|
6080
|
+
surface: external_exports.enum(["plugin_hook", "mcp", "rest", "unknown"]),
|
|
6081
|
+
host_surface: runtimeHostSurfaceSchema.default("unknown"),
|
|
6082
|
+
client_name: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9 ._+-]*$/),
|
|
6083
|
+
client_version: runtimeProfileVersionSchema.nullable().optional(),
|
|
6084
|
+
runtime_version: runtimeProfileVersionSchema.nullable().optional(),
|
|
6085
|
+
profile_key: external_exports.string().regex(/^rpf_[A-Za-z0-9_-]{16,120}$/)
|
|
6086
|
+
}).strict();
|
|
6087
|
+
var principalSessionChallengeSchema = external_exports.object({
|
|
5487
6088
|
action: external_exports.literal("challenge"),
|
|
5488
6089
|
provider: economicsSessionProviderSchema,
|
|
5489
|
-
key_id: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6090
|
+
key_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6091
|
+
runtime_profile: runtimeProfileRegistrationSchema.nullable().optional(),
|
|
6092
|
+
member_link_token: external_exports.string().regex(/^mlink_[A-Za-z0-9_-]{24,160}$/).nullable().optional()
|
|
5490
6093
|
}).strict();
|
|
5491
|
-
var
|
|
6094
|
+
var principalSessionExchangeSchema = external_exports.object({
|
|
5492
6095
|
action: external_exports.literal("exchange"),
|
|
5493
6096
|
provider: economicsSessionProviderSchema,
|
|
5494
6097
|
key_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
5495
6098
|
challenge_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
5496
6099
|
signature: boundedString(MAX_LONG_TEXT_LENGTH)
|
|
5497
6100
|
}).strict();
|
|
5498
|
-
var
|
|
5499
|
-
|
|
5500
|
-
|
|
6101
|
+
var principalSessionRequestSchema = external_exports.discriminatedUnion("action", [
|
|
6102
|
+
principalSessionChallengeSchema,
|
|
6103
|
+
principalSessionExchangeSchema
|
|
5501
6104
|
]);
|
|
6105
|
+
var recordPreferenceRequestSchema = external_exports.object({
|
|
6106
|
+
setting: preferenceSettingSchema,
|
|
6107
|
+
// Organization guidance is dashboard-owned policy. Agent transports may
|
|
6108
|
+
// record only identity-bound or task-context preference observations.
|
|
6109
|
+
scope: external_exports.enum([
|
|
6110
|
+
"auto",
|
|
6111
|
+
"member",
|
|
6112
|
+
"member_runtime",
|
|
6113
|
+
"installation",
|
|
6114
|
+
"project",
|
|
6115
|
+
"skill",
|
|
6116
|
+
"domain"
|
|
6117
|
+
]),
|
|
6118
|
+
source_category: external_exports.enum(["explicit_user", "agent_observed"]),
|
|
6119
|
+
evidence_hash: external_exports.string().regex(/^[a-f0-9]{32,128}$/i),
|
|
6120
|
+
task_hash: external_exports.string().regex(/^[a-f0-9]{32,128}$/i),
|
|
6121
|
+
project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).nullable().optional(),
|
|
6122
|
+
skill_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
|
|
6123
|
+
domain: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
|
|
6124
|
+
confidence: external_exports.number().min(0).max(1)
|
|
6125
|
+
}).strict().superRefine((value, ctx) => {
|
|
6126
|
+
const required = value.scope === "project" ? "project_key" : value.scope === "skill" ? "skill_slug" : value.scope === "domain" ? "domain" : null;
|
|
6127
|
+
if (required && !value[required]) {
|
|
6128
|
+
ctx.addIssue({
|
|
6129
|
+
code: external_exports.ZodIssueCode.custom,
|
|
6130
|
+
path: [required],
|
|
6131
|
+
message: `${required} is required for ${value.scope} preferences`
|
|
6132
|
+
});
|
|
6133
|
+
}
|
|
6134
|
+
if (value.source_category === "agent_observed" && value.confidence >= 1) {
|
|
6135
|
+
ctx.addIssue({
|
|
6136
|
+
code: external_exports.ZodIssueCode.custom,
|
|
6137
|
+
path: ["confidence"],
|
|
6138
|
+
message: "Inferred observations cannot claim absolute confidence"
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
6141
|
+
});
|
|
6142
|
+
var effectivePreferencesRequestSchema = external_exports.object({
|
|
6143
|
+
// Omit or send [] to resolve every active bounded preference. Supplying
|
|
6144
|
+
// keys is an optional projection for clients that need only a subset.
|
|
6145
|
+
keys: external_exports.array(preferenceKeySchema).max(MAX_EFFECTIVE_PREFERENCES).default([]),
|
|
6146
|
+
task_preferences: taskPreferenceSettingsSchema.optional(),
|
|
6147
|
+
project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).nullable().optional(),
|
|
6148
|
+
skill_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
|
|
6149
|
+
domains: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(20).default([])
|
|
6150
|
+
}).strict();
|
|
5502
6151
|
var agentPrincipalRegistrationRequestSchema = external_exports.object({
|
|
5503
6152
|
display_name: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
5504
6153
|
provider: agentProviderSchema,
|
|
5505
6154
|
runtime_version: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
|
|
5506
6155
|
parent_principal_id: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional()
|
|
5507
6156
|
}).strict();
|
|
5508
|
-
var agentPrincipalUpdateRequestSchema = external_exports.
|
|
5509
|
-
|
|
5510
|
-
|
|
6157
|
+
var agentPrincipalUpdateRequestSchema = external_exports.discriminatedUnion(
|
|
6158
|
+
"action",
|
|
6159
|
+
[
|
|
6160
|
+
external_exports.object({
|
|
6161
|
+
principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6162
|
+
action: external_exports.enum(["deactivate", "reactivate"])
|
|
6163
|
+
}).strict(),
|
|
6164
|
+
external_exports.object({
|
|
6165
|
+
principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6166
|
+
action: external_exports.literal("rename"),
|
|
6167
|
+
display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6168
|
+
}).strict()
|
|
6169
|
+
]
|
|
6170
|
+
);
|
|
6171
|
+
var agentInstallationManagementRequestSchema = external_exports.discriminatedUnion(
|
|
6172
|
+
"action",
|
|
6173
|
+
[
|
|
6174
|
+
external_exports.object({
|
|
6175
|
+
action: external_exports.literal("rename"),
|
|
6176
|
+
installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6177
|
+
display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6178
|
+
}).strict(),
|
|
6179
|
+
external_exports.object({
|
|
6180
|
+
action: external_exports.literal("rename_runtime_profile"),
|
|
6181
|
+
runtime_profile_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6182
|
+
display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6183
|
+
}).strict(),
|
|
6184
|
+
external_exports.object({
|
|
6185
|
+
action: external_exports.literal("set_status"),
|
|
6186
|
+
installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6187
|
+
status: external_exports.enum(["active", "inactive"])
|
|
6188
|
+
}).strict(),
|
|
6189
|
+
external_exports.object({
|
|
6190
|
+
action: external_exports.literal("assign_member"),
|
|
6191
|
+
installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
6192
|
+
member_principal_id: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6193
|
+
}).strict(),
|
|
6194
|
+
external_exports.object({
|
|
6195
|
+
action: external_exports.literal("unlink_member"),
|
|
6196
|
+
installation_id: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6197
|
+
}).strict()
|
|
6198
|
+
]
|
|
6199
|
+
);
|
|
6200
|
+
var dashboardPreferenceMutationSchema = external_exports.discriminatedUnion(
|
|
6201
|
+
"action",
|
|
6202
|
+
[
|
|
6203
|
+
external_exports.object({
|
|
6204
|
+
action: external_exports.literal("set"),
|
|
6205
|
+
setting: preferenceSettingSchema,
|
|
6206
|
+
scope: external_exports.enum(["member", "organization"]),
|
|
6207
|
+
enforcement: external_exports.enum(["recommended", "required"]).default("recommended")
|
|
6208
|
+
}).strict(),
|
|
6209
|
+
external_exports.object({
|
|
6210
|
+
action: external_exports.enum(["reset", "undo"]),
|
|
6211
|
+
revision_id: boundedString(MAX_SHORT_TEXT_LENGTH)
|
|
6212
|
+
}).strict()
|
|
6213
|
+
]
|
|
6214
|
+
);
|
|
6215
|
+
var agentMemberLinkRequestSchema = external_exports.object({
|
|
6216
|
+
member_link_token: external_exports.string().regex(/^mlink_[A-Za-z0-9_-]{24,160}$/)
|
|
5511
6217
|
}).strict();
|
|
5512
6218
|
var agentPrincipalKeyBindingRequestSchema = external_exports.object({
|
|
5513
6219
|
principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
@@ -5590,6 +6296,7 @@ var remembrancePayloadSchema = external_exports.object({
|
|
|
5590
6296
|
}).strict().optional(),
|
|
5591
6297
|
enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
|
|
5592
6298
|
suggested_update: suggestedUpdateSchema.default({ kind: "none" }),
|
|
6299
|
+
routing_hint: skillTopologyRoutingHintSchema.optional(),
|
|
5593
6300
|
evidence: evidenceSchema.default({ artifact_hashes: [] }),
|
|
5594
6301
|
// Client-only directive: "sign this with my local TOFU key" (the MCP
|
|
5595
6302
|
// submit_remembrance tool exposes it, and feedback-next-step tells REST
|
|
@@ -5624,6 +6331,8 @@ var skillMetadataSchema = external_exports.object({
|
|
|
5624
6331
|
permissions: external_exports.record(external_exports.boolean()).default({}),
|
|
5625
6332
|
contraindications: external_exports.array(external_exports.string().max(MAX_LONG_TEXT_LENGTH)).max(80).default([]),
|
|
5626
6333
|
applicability: skillApplicabilitySchema.optional(),
|
|
6334
|
+
preference_defaults: skillPreferenceDefaultsSchema.optional(),
|
|
6335
|
+
preference_traits: skillPreferenceTraitsSchema.optional(),
|
|
5627
6336
|
feedback_url: external_exports.string().url(),
|
|
5628
6337
|
install_command: external_exports.string().min(1),
|
|
5629
6338
|
stats: external_exports.object({
|
|
@@ -5656,6 +6365,7 @@ var skillIdeaRequestSchema = external_exports.object({
|
|
|
5656
6365
|
domain_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
|
|
5657
6366
|
proposed_skill_md: external_exports.string().max(MAX_LONG_TEXT_LENGTH).nullable().optional(),
|
|
5658
6367
|
proposed_metadata: boundedJsonRecord().default({}),
|
|
6368
|
+
routing_hint: skillTopologyRoutingHintSchema.optional(),
|
|
5659
6369
|
enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
|
|
5660
6370
|
idempotency_key: boundedString(MAX_SHORT_TEXT_LENGTH).optional()
|
|
5661
6371
|
});
|
|
@@ -5668,6 +6378,7 @@ var suggestionRequestSchema = external_exports.object({
|
|
|
5668
6378
|
summary: boundedString(MAX_LONG_TEXT_LENGTH),
|
|
5669
6379
|
diff_text: external_exports.string().max(MAX_LONG_TEXT_LENGTH).nullable().optional(),
|
|
5670
6380
|
payload: boundedJsonRecord().default({}),
|
|
6381
|
+
routing_hint: skillTopologyRoutingHintSchema.optional(),
|
|
5671
6382
|
enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
|
|
5672
6383
|
idempotency_key: boundedString(MAX_SHORT_TEXT_LENGTH).optional()
|
|
5673
6384
|
});
|
|
@@ -5748,6 +6459,7 @@ var attestationKeyRegistrationRequestSchema = external_exports.object({
|
|
|
5748
6459
|
agent: agentSchema.optional(),
|
|
5749
6460
|
proof: external_exports.object({
|
|
5750
6461
|
algorithm: external_exports.enum(["ed25519"]).default("ed25519"),
|
|
6462
|
+
owner_binding: external_exports.string().regex(/^areg_[A-Za-z0-9_-]{24,120}$/).optional(),
|
|
5751
6463
|
signed_at: external_exports.string().datetime(),
|
|
5752
6464
|
signature: boundedString(MAX_LONG_TEXT_LENGTH)
|
|
5753
6465
|
}),
|
|
@@ -5946,7 +6658,7 @@ var seedSkills = [
|
|
|
5946
6658
|
summary: "Operational setup and troubleshooting workflow for Remembrance across Claude Code, Codex, OpenClaw, Cursor, Gemini, MCP, REST, skill-only installs, enterprise keys, and local agent identity.",
|
|
5947
6659
|
status: "active",
|
|
5948
6660
|
visibility: "public",
|
|
5949
|
-
version: "0.1.
|
|
6661
|
+
version: "0.1.18",
|
|
5950
6662
|
domains: ["agent-skills", "mcp", "resource-discovery"],
|
|
5951
6663
|
tags: [
|
|
5952
6664
|
"remembrance",
|
|
@@ -5997,7 +6709,7 @@ var seedSkills = [
|
|
|
5997
6709
|
"gemini",
|
|
5998
6710
|
"rest"
|
|
5999
6711
|
],
|
|
6000
|
-
version: "0.1.
|
|
6712
|
+
version: "0.1.18",
|
|
6001
6713
|
status: "active",
|
|
6002
6714
|
visibility: "public",
|
|
6003
6715
|
providers: ["codex", "claude", "cursor", "openclaw", "generic"],
|
|
@@ -6406,7 +7118,8 @@ The MCP annotation for \`propose_private_skill\` is non-read-only and
|
|
|
6406
7118
|
closed-world: it makes a network request, but can change only the authenticated
|
|
6407
7119
|
organization's private review queue and cannot change publicly visible internet
|
|
6408
7120
|
state. Closed-world does not mean zero-network. Only
|
|
6409
|
-
\`queue_private_skill_import\` is
|
|
7121
|
+
\`queue_private_skill_import\` is a local, zero-network handoff tool, and it
|
|
7122
|
+
should run only when an organization admin explicitly requests that handoff.
|
|
6410
7123
|
|
|
6411
7124
|
Example text to merge into the tenant-specific guardian policy:
|
|
6412
7125
|
|
|
@@ -6441,12 +7154,14 @@ over a user's local \`[auto_review].policy\`. See the official Codex
|
|
|
6441
7154
|
[managed configuration](https://learn.chatgpt.com/docs/enterprise/managed-configuration#configure-automatic-review-policy)
|
|
6442
7155
|
documentation for the current schema and deployment options.
|
|
6443
7156
|
|
|
6444
|
-
If the organization does not approve direct egress, keep the host denial.
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
7157
|
+
If the organization does not approve direct egress, keep the host denial. The
|
|
7158
|
+
plugin reports one content-free local alert, does not retry, and does not create
|
|
7159
|
+
a handoff automatically. Only when an organization admin explicitly requests a
|
|
7160
|
+
handoff, use \`queue_private_skill_import\` locally or run the bundled
|
|
7161
|
+
\`scripts/queue-private-skill-import.mjs\` helper, then have the admin upload the
|
|
7162
|
+
mode-0600 JSON at **Dashboard > Skills > Import**. The handoff never contains an
|
|
7163
|
+
API key or organization id, never contacts Remembrance, and does not count as
|
|
7164
|
+
submitted until the dashboard returns an import batch receipt.
|
|
6450
7165
|
|
|
6451
7166
|
If Codex still sees \`<your org key>\` after restart, remove stale
|
|
6452
7167
|
\`REMEMBRANCE_API_KEY\` exports from shell profiles such as \`~/.zshrc\` and
|
|
@@ -6783,6 +7498,17 @@ npx skills add dreamarkinc/remembrance-skills --skill remembrancer
|
|
|
6783
7498
|
The entry skill is REST-only and self-contained. The same skill directory can
|
|
6784
7499
|
be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
6785
7500
|
|
|
7501
|
+
## Verified client updates
|
|
7502
|
+
|
|
7503
|
+
Native plugins and local MCP check the credential-free public release manifest
|
|
7504
|
+
at startup and through \`run_connection_doctor\`. The check sends no API key,
|
|
7505
|
+
fails open when the registry is unavailable, and never runs a command returned
|
|
7506
|
+
by the network. When a newer verified release exists, the agent must ask before
|
|
7507
|
+
running the update command bundled with its installed client, then tell the user
|
|
7508
|
+
which Codex, Claude Code, Cursor, OpenClaw, VS Code, OpenCode, or MCP host must
|
|
7509
|
+
be reloaded, reopened, or fully restarted. Set
|
|
7510
|
+
\`REMEMBRANCE_CLIENT_UPDATE_CHECK=0\` to disable only this advisory check.
|
|
7511
|
+
|
|
6786
7512
|
## Validate after setup
|
|
6787
7513
|
|
|
6788
7514
|
1. Start a fresh agent session.
|
|
@@ -6837,10 +7563,12 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
|
6837
7563
|
that should not appear anonymously.
|
|
6838
7564
|
13. With an organization submission key, submit one disposable redacted skill
|
|
6839
7565
|
through propose_private_skill and verify it appears only in that
|
|
6840
|
-
organization's review queue.
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
7566
|
+
organization's review queue. In a separate negative scenario, simulate a
|
|
7567
|
+
host-policy denial and verify the plugin reports the fixed content-free alert
|
|
7568
|
+
once, persists no blocked content, performs no retry, and creates no handoff.
|
|
7569
|
+
Test queue_private_skill_import only as a separate administrator-requested
|
|
7570
|
+
manual handoff, with its local receipt distinguished from a server import
|
|
7571
|
+
receipt.
|
|
6844
7572
|
14. If using local MCP, verify local_signing_identity in get_connection_status.
|
|
6845
7573
|
A missing opaque identity initializes automatically on the first signed
|
|
6846
7574
|
contribution; bootstrap_agent_identity with no arguments is an optional
|
|
@@ -7647,7 +8375,14 @@ var verifierOutputSchema = external_exports.object({
|
|
|
7647
8375
|
}).nullable().default(null),
|
|
7648
8376
|
// Advisory retrieval enrichment (see proposedSkillMetadataSchema). Optional
|
|
7649
8377
|
// and defaults to null so existing verifier outputs remain valid.
|
|
7650
|
-
proposed_metadata: proposedSkillMetadataSchema.nullable().default(null)
|
|
8378
|
+
proposed_metadata: proposedSkillMetadataSchema.nullable().default(null),
|
|
8379
|
+
// Advisory evidence-routing assessment. Deterministic routing and the
|
|
8380
|
+
// existing safety/review gates decide whether it can create a candidate.
|
|
8381
|
+
// Topology is advisory and newer than the base verifier contract. A legacy
|
|
8382
|
+
// or malformed topology fragment must not discard an otherwise valid safety
|
|
8383
|
+
// verdict; it degrades to null, which the topology router retains as
|
|
8384
|
+
// evidence-only rather than allowing an unclassified mutation.
|
|
8385
|
+
topology_assessment: skillTopologyAssessmentSchema.nullable().default(null).catch(null)
|
|
7651
8386
|
});
|
|
7652
8387
|
|
|
7653
8388
|
// src/server.ts
|
|
@@ -7655,6 +8390,8 @@ import { existsSync as existsSync3 } from "node:fs";
|
|
|
7655
8390
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
7656
8391
|
import { dirname as dirname2 } from "node:path";
|
|
7657
8392
|
import {
|
|
8393
|
+
createHash as createHash3,
|
|
8394
|
+
createHmac,
|
|
7658
8395
|
createPrivateKey as createPrivateKey2,
|
|
7659
8396
|
generateKeyPairSync,
|
|
7660
8397
|
sign as signPayload
|
|
@@ -9016,6 +9753,24 @@ var toolDefinitions = [
|
|
|
9016
9753
|
"/api/v1/agent/skill-invocations",
|
|
9017
9754
|
agentSkillInvocationRequestSchema
|
|
9018
9755
|
),
|
|
9756
|
+
tool(
|
|
9757
|
+
"get_effective_preferences",
|
|
9758
|
+
"Return bounded, task-relevant working preferences for this verified installation and optional linked member/runtime profile. project_key is an opaque local project-context hash; paths and repository names are never transmitted. A clear current-task preference overrides every non-mandatory stored preference; Required organization guidance remains authoritative. Preferences may steer among already-relevant skills and surgically alter discretionary presentation, workflow, or strategy choices. They never change relevance tiers or weaken applicability, safety, authorization, privacy, required skill steps, validation, or review. Clients without a valid principal session remain organization-level and receive no personal preferences.",
|
|
9759
|
+
"/api/v1/agent/preferences/effective",
|
|
9760
|
+
effectivePreferencesRequestSchema
|
|
9761
|
+
),
|
|
9762
|
+
tool(
|
|
9763
|
+
"record_preference",
|
|
9764
|
+
"Record a bounded, privacy-safe preference observation for the current verified installation. Built-in settings need only key and value; a custom setting also supplies its plain-language label, behavior, presentation/workflow/strategy_selection effect, prefer/avoid direction, and definition version. Send normalized settings and hashes only, never prompt text. A valid principal session is required. Explicit user preferences apply immediately; inferred observations activate only after Remembrance's consistency and confidence thresholds are met.",
|
|
9765
|
+
"/api/v1/agent/preferences",
|
|
9766
|
+
recordPreferenceRequestSchema
|
|
9767
|
+
),
|
|
9768
|
+
tool(
|
|
9769
|
+
"link_current_installation",
|
|
9770
|
+
"Link this verified installation to the signed-in organization member using a single-use dashboard token. The token expires after ten minutes and is consumed exactly once. A valid organization principal session is required; this never changes the organization API key or creates another billed agent.",
|
|
9771
|
+
"/api/v1/agent/member-links",
|
|
9772
|
+
agentMemberLinkRequestSchema
|
|
9773
|
+
),
|
|
9019
9774
|
tool(
|
|
9020
9775
|
"get_skill",
|
|
9021
9776
|
"Fetch the exact active reviewed skill body and version after query_skills returns a known slug. The response includes skill_md, version_id, and source. Pass query_id and result_id from that response so Remembrance can measure whether surfaced guidance was opened. Do not guess private or inactive slugs.",
|
|
@@ -9069,19 +9824,19 @@ var toolDefinitions = [
|
|
|
9069
9824
|
),
|
|
9070
9825
|
tool(
|
|
9071
9826
|
"submit_remembrance",
|
|
9072
|
-
"Submit a full remembrance payload with detailed reusable evidence, including high-value self-corrections, user-caught mistakes, CI/deploy failures, and release/versioning misses as type failure_report. Never include credentials, secrets, raw logs, or unrelated repository content. Repository-derived private instructions may be sent only when the user or organization has approved Remembrance as a destination
|
|
9827
|
+
"Submit a full remembrance payload with detailed reusable evidence, including high-value self-corrections, user-caught mistakes, CI/deploy failures, and release/versioning misses as type failure_report. Never include credentials, secrets, raw logs, or unrelated repository content. Repository-derived private instructions may be sent only when the user or organization has approved Remembrance as a destination. If the host blocks that export, report that nothing was sent and do not retry through another transport or create a handoff unless an organization admin explicitly requests one.",
|
|
9073
9828
|
"/api/v1/agent/remembrances",
|
|
9074
9829
|
remembranceToolSchema
|
|
9075
9830
|
),
|
|
9076
9831
|
tool(
|
|
9077
9832
|
"propose_skill_idea",
|
|
9078
|
-
"Propose a missing reusable skill when query_skills has no useful result. THIS TOOL'S VISIBILITY IS NOT FIXED: an active organization API key keeps the candidate inside that organization's review queue, while intentionally calling without a key creates a PUBLIC candidate. A supplied invalid/inactive key fails with 401 and a valid key without submission:create fails with 403; neither failure creates a candidate. Prefer propose_private_skill for anything repository-derived, organization-specific, or otherwise not intended for the public registry because that endpoint structurally requires organization auth. Use propose_skill_idea only when a public candidate is an acceptable outcome. Check `visibility` (organization_private or public_candidate) and `owner_scope` in the successful response, and report where the candidate landed. Never remove or bypass an organization key to force public submission: submit privately, then use the reviewed public-propagation flow. Send repository-derived private instructions only when Remembrance is an organization-approved destination. If host policy blocks the export,
|
|
9833
|
+
"Propose a missing reusable skill when query_skills has no useful result. THIS TOOL'S VISIBILITY IS NOT FIXED: an active organization API key keeps the candidate inside that organization's review queue, while intentionally calling without a key creates a PUBLIC candidate. A supplied invalid/inactive key fails with 401 and a valid key without submission:create fails with 403; neither failure creates a candidate. Prefer propose_private_skill for anything repository-derived, organization-specific, or otherwise not intended for the public registry because that endpoint structurally requires organization auth. Use propose_skill_idea only when a public candidate is an acceptable outcome. Check `visibility` (organization_private or public_candidate) and `owner_scope` in the successful response, and report where the candidate landed. Never remove or bypass an organization key to force public submission: submit privately, then use the reviewed public-propagation flow. Send repository-derived private instructions only when Remembrance is an organization-approved destination. If host policy blocks the export, report that nothing was sent, do not retry, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
9079
9834
|
"/api/v1/agent/skill-ideas",
|
|
9080
9835
|
skillIdeaRequestSchema
|
|
9081
9836
|
),
|
|
9082
9837
|
tool(
|
|
9083
9838
|
"propose_private_skill",
|
|
9084
|
-
"Submit a proposed skill only to the authenticated organization's private review queue. Privacy here is STRUCTURAL, not credential-dependent: this endpoint rejects anonymous use and never creates a public candidate, so unlike propose_skill_idea it cannot silently fall through to public when a key fails to resolve \u2014 it fails closed with 401/403 instead. Prefer it whenever the content is repository-derived, organization-specific, or must not reach the public registry; that preference is about removing the credential from the privacy decision, not about propose_skill_idea being unsafe when a key is present. Use it for repository-derived instructions only when Remembrance is an organization-approved destination. The response reports `visibility` (always organization_private here) \u2014 a rejection means nothing was submitted. If host policy blocks the export,
|
|
9839
|
+
"Submit a proposed skill only to the authenticated organization's private review queue. Privacy here is STRUCTURAL, not credential-dependent: this endpoint rejects anonymous use and never creates a public candidate, so unlike propose_skill_idea it cannot silently fall through to public when a key fails to resolve \u2014 it fails closed with 401/403 instead. Prefer it whenever the content is repository-derived, organization-specific, or must not reach the public registry; that preference is about removing the credential from the privacy decision, not about propose_skill_idea being unsafe when a key is present. Use it for repository-derived instructions only when Remembrance is an organization-approved destination. The response reports `visibility` (always organization_private here) \u2014 a rejection means nothing was submitted. If host policy blocks the export, report that nothing was sent, never retry through another transport, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
9085
9840
|
"/api/v1/agent/private-skill-ideas",
|
|
9086
9841
|
skillIdeaRequestSchema
|
|
9087
9842
|
),
|
|
@@ -9138,7 +9893,7 @@ function localTool(name, description, local, schema) {
|
|
|
9138
9893
|
};
|
|
9139
9894
|
}
|
|
9140
9895
|
function annotationsForTool(name) {
|
|
9141
|
-
const readOnly = name === "list_skills" || name === "get_value_proof" || name === "run_connection_doctor";
|
|
9896
|
+
const readOnly = name === "list_skills" || name === "get_effective_preferences" || name === "get_value_proof" || name === "run_connection_doctor";
|
|
9142
9897
|
const openWorld = name === "submit_feedback" || name === "submit_remembrance" || name === "propose_skill_idea" || name === "submit_suggestion" || name === "submit_resource" || name === "submit_resource_review";
|
|
9143
9898
|
return {
|
|
9144
9899
|
readOnlyHint: readOnly,
|
|
@@ -9365,7 +10120,7 @@ function localAgentIdentityPath(env = process.env, homeDirectory = homedir2()) {
|
|
|
9365
10120
|
);
|
|
9366
10121
|
}
|
|
9367
10122
|
function parseStoredIdentity(value) {
|
|
9368
|
-
if (!
|
|
10123
|
+
if (!isRecord2(value)) return null;
|
|
9369
10124
|
if (!supportedProviders.has(value.provider)) {
|
|
9370
10125
|
return null;
|
|
9371
10126
|
}
|
|
@@ -9500,7 +10255,7 @@ function identityStatus(args) {
|
|
|
9500
10255
|
} : null
|
|
9501
10256
|
};
|
|
9502
10257
|
}
|
|
9503
|
-
function
|
|
10258
|
+
function isRecord2(value) {
|
|
9504
10259
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
9505
10260
|
}
|
|
9506
10261
|
|
|
@@ -9514,6 +10269,20 @@ var PLUGIN_HEALTH_COMPONENTS = [
|
|
|
9514
10269
|
];
|
|
9515
10270
|
var PLUGIN_HEALTH_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9516
10271
|
var PLUGIN_HEALTH_MAX_FUTURE_SKEW_MS = 5 * 60 * 1e3;
|
|
10272
|
+
var HOST_POLICY_OBSERVATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
10273
|
+
var HOST_POLICY_OBSERVABLE_SURFACES = /* @__PURE__ */ new Set([
|
|
10274
|
+
"claude_code",
|
|
10275
|
+
"cursor",
|
|
10276
|
+
"openclaw",
|
|
10277
|
+
"opencode"
|
|
10278
|
+
]);
|
|
10279
|
+
var HOST_POLICY_OPERATION_CLASSES2 = /* @__PURE__ */ new Set([
|
|
10280
|
+
"query",
|
|
10281
|
+
"private_contribution",
|
|
10282
|
+
"contribution",
|
|
10283
|
+
"feedback",
|
|
10284
|
+
"other"
|
|
10285
|
+
]);
|
|
9517
10286
|
var nodeFileSystem2 = {
|
|
9518
10287
|
exists: existsSync2,
|
|
9519
10288
|
read: (path, maxBytes = MAX_LOCAL_CONFIG_BYTES) => readSecureLocalText(path, { maxBytes }),
|
|
@@ -9525,107 +10294,104 @@ function remembranceConfigPath(env = process.env, homeDirectory = homedir3()) {
|
|
|
9525
10294
|
return join3(configRoot, "remembrance", "config.json");
|
|
9526
10295
|
}
|
|
9527
10296
|
function resolveApiCredential(env = process.env, fileSystem = nodeFileSystem2) {
|
|
9528
|
-
|
|
10297
|
+
return resolveApiAccess(env, fileSystem).credential;
|
|
10298
|
+
}
|
|
10299
|
+
function resolveApiAccess(env = process.env, fileSystem = nodeFileSystem2) {
|
|
10300
|
+
const shared = readSharedConfigSnapshot(env, fileSystem);
|
|
10301
|
+
const configuration = resolveApiConfigurationFromSnapshot(env, shared);
|
|
9529
10302
|
const environmentKey = env.REMEMBRANCE_API_KEY?.trim();
|
|
10303
|
+
let credential;
|
|
9530
10304
|
if (environmentKey) {
|
|
9531
10305
|
const explicitBinding = env.REMEMBRANCE_API_KEY_ORIGIN?.trim();
|
|
9532
10306
|
const binding = explicitBinding ? normalizeApiUrl(explicitBinding, env) : { baseUrl: DEFAULT_API_URL, issue: null };
|
|
9533
10307
|
if (!binding.baseUrl || binding.issue) {
|
|
9534
|
-
|
|
10308
|
+
credential = unusableDestinationBinding();
|
|
10309
|
+
} else {
|
|
10310
|
+
credential = credentialForDestination(
|
|
10311
|
+
{
|
|
10312
|
+
apiKey: environmentKey,
|
|
10313
|
+
source: "environment",
|
|
10314
|
+
boundBaseUrl: binding.baseUrl
|
|
10315
|
+
},
|
|
10316
|
+
configuration
|
|
10317
|
+
);
|
|
10318
|
+
}
|
|
10319
|
+
} else if (!shared.present) {
|
|
10320
|
+
credential = { apiKey: "", source: "none" };
|
|
10321
|
+
} else if (!shared.parsed || Object.prototype.hasOwnProperty.call(shared.parsed, "apiKey") && (typeof shared.parsed.apiKey !== "string" || !shared.parsed.apiKey.trim())) {
|
|
10322
|
+
credential = unusableSharedConfig();
|
|
10323
|
+
} else if (!shared.config.apiKey) {
|
|
10324
|
+
credential = { apiKey: "", source: "none" };
|
|
10325
|
+
} else {
|
|
10326
|
+
const binding = shared.config.apiUrl ? normalizeApiUrl(shared.config.apiUrl, env) : { baseUrl: DEFAULT_API_URL, issue: null };
|
|
10327
|
+
if (!binding.baseUrl || binding.issue) {
|
|
10328
|
+
credential = unusableSharedConfig();
|
|
10329
|
+
} else {
|
|
10330
|
+
credential = credentialForDestination(
|
|
10331
|
+
{
|
|
10332
|
+
apiKey: shared.config.apiKey,
|
|
10333
|
+
source: "shared_config",
|
|
10334
|
+
boundBaseUrl: binding.baseUrl
|
|
10335
|
+
},
|
|
10336
|
+
configuration
|
|
10337
|
+
);
|
|
9535
10338
|
}
|
|
9536
|
-
return credentialForDestination(
|
|
9537
|
-
{
|
|
9538
|
-
apiKey: environmentKey,
|
|
9539
|
-
source: "environment",
|
|
9540
|
-
boundBaseUrl: binding.baseUrl
|
|
9541
|
-
},
|
|
9542
|
-
apiConfiguration2
|
|
9543
|
-
);
|
|
9544
10339
|
}
|
|
10340
|
+
return {
|
|
10341
|
+
configuration,
|
|
10342
|
+
credential,
|
|
10343
|
+
memberLinkToken: env.REMEMBRANCE_MEMBER_LINK_TOKEN?.trim() ?? shared.config.memberLinkToken ?? ""
|
|
10344
|
+
};
|
|
10345
|
+
}
|
|
10346
|
+
function isUnusableCredentialSource(source) {
|
|
10347
|
+
return source.startsWith("unusable_");
|
|
10348
|
+
}
|
|
10349
|
+
function readSharedConfigSnapshot(env, fileSystem) {
|
|
9545
10350
|
const path = remembranceConfigPath(env);
|
|
9546
10351
|
if (!fileSystem.exists(path)) {
|
|
9547
|
-
return {
|
|
10352
|
+
return { present: false, parsed: null, config: {} };
|
|
9548
10353
|
}
|
|
9549
10354
|
try {
|
|
9550
10355
|
const parsed = JSON.parse(
|
|
9551
10356
|
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9552
10357
|
);
|
|
9553
|
-
|
|
9554
|
-
return unusableSharedConfig();
|
|
9555
|
-
}
|
|
9556
|
-
if (Object.prototype.hasOwnProperty.call(parsed, "apiKey") && (typeof parsed.apiKey !== "string" || !parsed.apiKey.trim())) {
|
|
9557
|
-
return unusableSharedConfig();
|
|
9558
|
-
}
|
|
9559
|
-
const config = normalizeConfig(parsed);
|
|
9560
|
-
if (!config.apiKey) {
|
|
9561
|
-
return { apiKey: "", source: "none" };
|
|
9562
|
-
}
|
|
9563
|
-
const binding = config.apiUrl ? normalizeApiUrl(config.apiUrl, env) : { baseUrl: DEFAULT_API_URL, issue: null };
|
|
9564
|
-
if (!binding.baseUrl || binding.issue) {
|
|
9565
|
-
return unusableSharedConfig();
|
|
9566
|
-
}
|
|
9567
|
-
return credentialForDestination(
|
|
9568
|
-
{
|
|
9569
|
-
apiKey: config.apiKey,
|
|
9570
|
-
source: "shared_config",
|
|
9571
|
-
boundBaseUrl: binding.baseUrl
|
|
9572
|
-
},
|
|
9573
|
-
apiConfiguration2
|
|
9574
|
-
);
|
|
10358
|
+
return isRecord3(parsed) ? { present: true, parsed, config: normalizeConfig(parsed) } : { present: true, parsed: null, config: {} };
|
|
9575
10359
|
} catch {
|
|
9576
|
-
return
|
|
10360
|
+
return { present: true, parsed: null, config: {} };
|
|
9577
10361
|
}
|
|
9578
10362
|
}
|
|
9579
|
-
function
|
|
9580
|
-
return resolveApiCredential(env, fileSystem).apiKey;
|
|
9581
|
-
}
|
|
9582
|
-
function resolveApiConfiguration(env = process.env, fileSystem = nodeFileSystem2) {
|
|
10363
|
+
function resolveApiConfigurationFromSnapshot(env, shared) {
|
|
9583
10364
|
const environmentUrl = env.REMEMBRANCE_API_URL?.trim();
|
|
9584
10365
|
if (environmentUrl) {
|
|
9585
|
-
const
|
|
9586
|
-
if (
|
|
9587
|
-
return { baseUrl:
|
|
10366
|
+
const normalized2 = normalizeApiUrl(environmentUrl, env);
|
|
10367
|
+
if (normalized2.baseUrl !== null) {
|
|
10368
|
+
return { baseUrl: normalized2.baseUrl, source: "environment" };
|
|
9588
10369
|
}
|
|
9589
10370
|
return {
|
|
9590
10371
|
baseUrl: DEFAULT_API_URL,
|
|
9591
10372
|
source: "unusable_environment",
|
|
9592
|
-
issue:
|
|
10373
|
+
issue: normalized2.issue
|
|
9593
10374
|
};
|
|
9594
10375
|
}
|
|
9595
|
-
|
|
9596
|
-
if (!fileSystem.exists(path)) {
|
|
10376
|
+
if (!shared.present) {
|
|
9597
10377
|
return { baseUrl: DEFAULT_API_URL, source: "default" };
|
|
9598
10378
|
}
|
|
9599
|
-
|
|
9600
|
-
const parsed = JSON.parse(
|
|
9601
|
-
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9602
|
-
);
|
|
9603
|
-
if (!isRecord2(parsed)) {
|
|
9604
|
-
return {
|
|
9605
|
-
baseUrl: DEFAULT_API_URL,
|
|
9606
|
-
source: "unusable_shared_config",
|
|
9607
|
-
issue: "invalid_url"
|
|
9608
|
-
};
|
|
9609
|
-
}
|
|
9610
|
-
if (!Object.prototype.hasOwnProperty.call(parsed, "apiUrl")) {
|
|
9611
|
-
return { baseUrl: DEFAULT_API_URL, source: "default" };
|
|
9612
|
-
}
|
|
9613
|
-
const normalized = normalizeApiUrl(parsed.apiUrl, env);
|
|
9614
|
-
if (normalized.baseUrl !== null) {
|
|
9615
|
-
return { baseUrl: normalized.baseUrl, source: "shared_config" };
|
|
9616
|
-
}
|
|
9617
|
-
return {
|
|
9618
|
-
baseUrl: DEFAULT_API_URL,
|
|
9619
|
-
source: "unusable_shared_config",
|
|
9620
|
-
issue: normalized.issue
|
|
9621
|
-
};
|
|
9622
|
-
} catch {
|
|
10379
|
+
if (!shared.parsed) {
|
|
9623
10380
|
return {
|
|
9624
10381
|
baseUrl: DEFAULT_API_URL,
|
|
9625
10382
|
source: "unusable_shared_config",
|
|
9626
10383
|
issue: "invalid_url"
|
|
9627
10384
|
};
|
|
9628
10385
|
}
|
|
10386
|
+
if (!Object.prototype.hasOwnProperty.call(shared.parsed, "apiUrl")) {
|
|
10387
|
+
return { baseUrl: DEFAULT_API_URL, source: "default" };
|
|
10388
|
+
}
|
|
10389
|
+
const normalized = normalizeApiUrl(shared.parsed.apiUrl, env);
|
|
10390
|
+
return normalized.baseUrl !== null ? { baseUrl: normalized.baseUrl, source: "shared_config" } : {
|
|
10391
|
+
baseUrl: DEFAULT_API_URL,
|
|
10392
|
+
source: "unusable_shared_config",
|
|
10393
|
+
issue: normalized.issue
|
|
10394
|
+
};
|
|
9629
10395
|
}
|
|
9630
10396
|
function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
|
|
9631
10397
|
const path = remembranceConfigPath(env);
|
|
@@ -9655,7 +10421,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
9655
10421
|
const parsed = JSON.parse(
|
|
9656
10422
|
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9657
10423
|
);
|
|
9658
|
-
validJson =
|
|
10424
|
+
validJson = isRecord3(parsed);
|
|
9659
10425
|
config = normalizeConfig(parsed);
|
|
9660
10426
|
} catch {
|
|
9661
10427
|
}
|
|
@@ -9670,7 +10436,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
9670
10436
|
};
|
|
9671
10437
|
}
|
|
9672
10438
|
function localConnectionStatus(result, options) {
|
|
9673
|
-
const response =
|
|
10439
|
+
const response = isRecord3(result) ? result : {};
|
|
9674
10440
|
const env = options.env ?? process.env;
|
|
9675
10441
|
const fileSystem = options.fileSystem ?? nodeFileSystem2;
|
|
9676
10442
|
const credential = resolveApiCredential(env, fileSystem);
|
|
@@ -9688,6 +10454,11 @@ function localConnectionStatus(result, options) {
|
|
|
9688
10454
|
apiDestinationSource: options.apiUrlSource,
|
|
9689
10455
|
now: options.now
|
|
9690
10456
|
});
|
|
10457
|
+
const hostPolicy = localHostPolicyStatus({
|
|
10458
|
+
env,
|
|
10459
|
+
fileSystem,
|
|
10460
|
+
now: options.now
|
|
10461
|
+
});
|
|
9691
10462
|
const registryReady = response.ok === true;
|
|
9692
10463
|
const insecureCredential = sharedConfig.api_key_present && sharedConfig.secure_permissions === false;
|
|
9693
10464
|
const unknownCredentialPermissions = sharedConfig.api_key_present && sharedConfig.secure_permissions === null;
|
|
@@ -9708,7 +10479,79 @@ function localConnectionStatus(result, options) {
|
|
|
9708
10479
|
},
|
|
9709
10480
|
registry: response.body ?? (response.error ? { error: String(response.error) } : null),
|
|
9710
10481
|
local_signing_identity: localAgentIdentityStatus(env, fileSystem),
|
|
9711
|
-
plugin_health: pluginHealth
|
|
10482
|
+
plugin_health: { ...pluginHealth, host_policy: hostPolicy }
|
|
10483
|
+
};
|
|
10484
|
+
}
|
|
10485
|
+
function localHostPolicyStatus(options) {
|
|
10486
|
+
const env = options.env ?? process.env;
|
|
10487
|
+
const fileSystem = options.fileSystem ?? nodeFileSystem2;
|
|
10488
|
+
const rawSurface = String(env.REMEMBRANCE_PLUGIN_HOST ?? "").trim().toLowerCase();
|
|
10489
|
+
if (!isPluginHostSurface(rawSurface)) {
|
|
10490
|
+
return {
|
|
10491
|
+
status: "not_observable",
|
|
10492
|
+
observable: false,
|
|
10493
|
+
explanation: "This connection cannot observe a host policy decision made before MCP transport."
|
|
10494
|
+
};
|
|
10495
|
+
}
|
|
10496
|
+
const surface = rawSurface;
|
|
10497
|
+
if (!HOST_POLICY_OBSERVABLE_SURFACES.has(surface)) {
|
|
10498
|
+
return {
|
|
10499
|
+
status: "not_observable",
|
|
10500
|
+
observable: false,
|
|
10501
|
+
explanation: "This host does not expose a reliable failed-tool or permission event to the plugin."
|
|
10502
|
+
};
|
|
10503
|
+
}
|
|
10504
|
+
const directory = env.REMEMBRANCE_PLUGIN_ALERT_DIR ? String(env.REMEMBRANCE_PLUGIN_ALERT_DIR) : join3(homedir3(), ".cache", "remembrance", "plugin-alerts");
|
|
10505
|
+
const names = fileSystem.list ? (() => {
|
|
10506
|
+
try {
|
|
10507
|
+
return fileSystem.list?.(directory) ?? [];
|
|
10508
|
+
} catch {
|
|
10509
|
+
return [];
|
|
10510
|
+
}
|
|
10511
|
+
})() : [];
|
|
10512
|
+
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
10513
|
+
let latest = null;
|
|
10514
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
10515
|
+
for (const name of names.filter(
|
|
10516
|
+
(value) => new RegExp(`^${surface}\\.[a-f0-9]{24}\\.json$`).test(value)
|
|
10517
|
+
).slice(0, MAX_LOCAL_LIFECYCLE_MARKERS)) {
|
|
10518
|
+
try {
|
|
10519
|
+
const sessionHash = name.split(".")[1];
|
|
10520
|
+
const parsed = JSON.parse(
|
|
10521
|
+
fileSystem.read(
|
|
10522
|
+
join3(directory, name),
|
|
10523
|
+
MAX_LOCAL_LIFECYCLE_MARKER_BYTES
|
|
10524
|
+
)
|
|
10525
|
+
);
|
|
10526
|
+
if (!isRecord3(parsed) || parsed.schema_version !== 1 || parsed.surface !== surface || parsed.session_hash !== sessionHash || !Array.isArray(parsed.observations)) {
|
|
10527
|
+
continue;
|
|
10528
|
+
}
|
|
10529
|
+
for (const candidate of parsed.observations) {
|
|
10530
|
+
if (!isRecord3(candidate)) continue;
|
|
10531
|
+
const seenMs = typeof candidate.last_seen_at === "string" ? Date.parse(candidate.last_seen_at) : Number.NaN;
|
|
10532
|
+
if (Number.isFinite(seenMs) && seenMs <= nowMs + PLUGIN_HEALTH_MAX_FUTURE_SKEW_MS && nowMs - seenMs <= HOST_POLICY_OBSERVATION_MAX_AGE_MS && seenMs > latestMs) {
|
|
10533
|
+
latest = candidate;
|
|
10534
|
+
latestMs = seenMs;
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10537
|
+
} catch {
|
|
10538
|
+
}
|
|
10539
|
+
}
|
|
10540
|
+
if (!latest) {
|
|
10541
|
+
return {
|
|
10542
|
+
status: "no_recent_denial",
|
|
10543
|
+
observable: true,
|
|
10544
|
+
explanation: "No recent host policy denial has been observed. This is not proof that future submissions are permitted."
|
|
10545
|
+
};
|
|
10546
|
+
}
|
|
10547
|
+
return {
|
|
10548
|
+
status: "recent_denial",
|
|
10549
|
+
observable: true,
|
|
10550
|
+
operation_class: HOST_POLICY_OPERATION_CLASSES2.has(
|
|
10551
|
+
String(latest.operation_class ?? "")
|
|
10552
|
+
) ? latest.operation_class : "other",
|
|
10553
|
+
last_denial_at: new Date(latestMs).toISOString(),
|
|
10554
|
+
explanation: "The host recently blocked a Remembrance operation before transport; no content was sent."
|
|
9712
10555
|
};
|
|
9713
10556
|
}
|
|
9714
10557
|
function localPluginLifecycleHealth(options) {
|
|
@@ -9752,7 +10595,7 @@ function localPluginLifecycleHealth(options) {
|
|
|
9752
10595
|
const parsed = JSON.parse(
|
|
9753
10596
|
fileSystem.read(path, MAX_LOCAL_LIFECYCLE_MARKER_BYTES)
|
|
9754
10597
|
);
|
|
9755
|
-
if (!
|
|
10598
|
+
if (!isRecord3(parsed) || parsed.surface !== surface) {
|
|
9756
10599
|
continue;
|
|
9757
10600
|
}
|
|
9758
10601
|
const candidateMs = typeof parsed.last_seen_at === "string" ? Date.parse(parsed.last_seen_at) : Number.NEGATIVE_INFINITY;
|
|
@@ -9774,7 +10617,7 @@ function localPluginLifecycleHealth(options) {
|
|
|
9774
10617
|
]
|
|
9775
10618
|
};
|
|
9776
10619
|
}
|
|
9777
|
-
const componentRecord =
|
|
10620
|
+
const componentRecord = isRecord3(marker.components) ? marker.components : {};
|
|
9778
10621
|
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
9779
10622
|
const sessionStartMs = validLifecycleTimestamp(
|
|
9780
10623
|
componentRecord.session_start,
|
|
@@ -9927,12 +10770,14 @@ function lifecycleMarkerPaths(surface, healthDir, fileSystem) {
|
|
|
9927
10770
|
return [...new Set(sessionPaths)].slice(0, MAX_LOCAL_LIFECYCLE_MARKERS);
|
|
9928
10771
|
}
|
|
9929
10772
|
function normalizeConfig(value) {
|
|
9930
|
-
if (!
|
|
10773
|
+
if (!isRecord3(value)) return {};
|
|
9931
10774
|
const apiKey = typeof value.apiKey === "string" ? value.apiKey.trim() : "";
|
|
9932
10775
|
const apiUrl = typeof value.apiUrl === "string" ? value.apiUrl.trim() : "";
|
|
10776
|
+
const memberLinkToken = typeof value.memberLinkToken === "string" ? value.memberLinkToken.trim() : "";
|
|
9933
10777
|
return {
|
|
9934
10778
|
...apiKey ? { apiKey } : {},
|
|
9935
|
-
...apiUrl ? { apiUrl } : {}
|
|
10779
|
+
...apiUrl ? { apiUrl } : {},
|
|
10780
|
+
...memberLinkToken && /^mlink_[A-Za-z0-9_-]{24,160}$/.test(memberLinkToken) ? { memberLinkToken } : {}
|
|
9936
10781
|
};
|
|
9937
10782
|
}
|
|
9938
10783
|
function normalizeApiUrl(value, env) {
|
|
@@ -10018,22 +10863,93 @@ function validLifecycleTimestamp(value, nowMs) {
|
|
|
10018
10863
|
}
|
|
10019
10864
|
return parsed;
|
|
10020
10865
|
}
|
|
10021
|
-
function
|
|
10866
|
+
function isRecord3(value) {
|
|
10022
10867
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
10023
10868
|
}
|
|
10024
10869
|
|
|
10870
|
+
// src/client-update.ts
|
|
10871
|
+
var MAX_MANIFEST_BYTES = 16 * 1024;
|
|
10872
|
+
var DEFAULT_TIMEOUT_MS = 1200;
|
|
10873
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10874
|
+
var cache = /* @__PURE__ */ new Map();
|
|
10875
|
+
async function checkClientUpdate(input) {
|
|
10876
|
+
const env = input.env ?? process.env;
|
|
10877
|
+
if (/^(0|false|no)$/i.test(env.REMEMBRANCE_CLIENT_UPDATE_CHECK ?? "")) {
|
|
10878
|
+
return null;
|
|
10879
|
+
}
|
|
10880
|
+
if (!parseStableClientVersion(input.currentVersion)) return null;
|
|
10881
|
+
const now = input.now ?? Date.now();
|
|
10882
|
+
const url = `${input.apiBase.replace(/\/+$/, "")}/.well-known/remembrance-client-release.json`;
|
|
10883
|
+
const cached = cache.get(url);
|
|
10884
|
+
let manifest;
|
|
10885
|
+
if (cached && cached.expiresAt > now) {
|
|
10886
|
+
manifest = cached.value;
|
|
10887
|
+
} else {
|
|
10888
|
+
const controller = new AbortController();
|
|
10889
|
+
const timer = setTimeout(
|
|
10890
|
+
() => controller.abort(),
|
|
10891
|
+
input.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
10892
|
+
);
|
|
10893
|
+
try {
|
|
10894
|
+
const response = await (input.fetchImpl ?? fetch)(url, {
|
|
10895
|
+
headers: { accept: "application/json" },
|
|
10896
|
+
signal: controller.signal
|
|
10897
|
+
});
|
|
10898
|
+
if (!response.ok) return null;
|
|
10899
|
+
const text = await response.text();
|
|
10900
|
+
if (Buffer.byteLength(text, "utf8") > MAX_MANIFEST_BYTES) return null;
|
|
10901
|
+
try {
|
|
10902
|
+
manifest = JSON.parse(text);
|
|
10903
|
+
} catch {
|
|
10904
|
+
return null;
|
|
10905
|
+
}
|
|
10906
|
+
cache.set(url, { value: manifest, expiresAt: now + CACHE_TTL_MS });
|
|
10907
|
+
} catch {
|
|
10908
|
+
return null;
|
|
10909
|
+
} finally {
|
|
10910
|
+
clearTimeout(timer);
|
|
10911
|
+
}
|
|
10912
|
+
}
|
|
10913
|
+
const status = resolveClientUpdateStatus({
|
|
10914
|
+
currentVersion: input.currentVersion,
|
|
10915
|
+
manifest,
|
|
10916
|
+
surface: input.surface
|
|
10917
|
+
});
|
|
10918
|
+
return status.status === "unavailable" ? null : status;
|
|
10919
|
+
}
|
|
10920
|
+
|
|
10025
10921
|
// src/server.ts
|
|
10026
|
-
var apiConfiguration = resolveApiConfiguration();
|
|
10027
|
-
var apiBase = apiConfiguration.baseUrl;
|
|
10028
10922
|
var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
10029
10923
|
var DOCTOR_PROBE_TIMEOUT_MS = 7500;
|
|
10030
|
-
var SERVER_VERSION = true ? "0.1.
|
|
10924
|
+
var SERVER_VERSION = true ? "0.1.60" : "0.0.0-dev";
|
|
10031
10925
|
var tools = toolDefinitions;
|
|
10032
10926
|
var doctorCliRequested = process.argv[2] === "doctor";
|
|
10033
10927
|
var inputBuffer = Buffer.alloc(0);
|
|
10034
10928
|
var clientFraming = "ndjson";
|
|
10035
|
-
var
|
|
10929
|
+
var cachedPrincipalSession = null;
|
|
10930
|
+
var connectedClientInfo = null;
|
|
10036
10931
|
var cachedValueProofKeys = null;
|
|
10932
|
+
var PRINCIPAL_SESSION_REQUIRED_TOOLS = /* @__PURE__ */ new Set([
|
|
10933
|
+
"record_preference",
|
|
10934
|
+
"link_current_installation"
|
|
10935
|
+
]);
|
|
10936
|
+
var PRINCIPAL_SESSION_AWAITED_TOOLS = /* @__PURE__ */ new Set([
|
|
10937
|
+
...PRINCIPAL_SESSION_REQUIRED_TOOLS,
|
|
10938
|
+
"report_task_outcome"
|
|
10939
|
+
]);
|
|
10940
|
+
function apiAccessKey(access) {
|
|
10941
|
+
return createHash3("sha256").update(
|
|
10942
|
+
[
|
|
10943
|
+
access.configuration.baseUrl,
|
|
10944
|
+
access.credential.source,
|
|
10945
|
+
access.credential.apiKey
|
|
10946
|
+
].join("\0"),
|
|
10947
|
+
"utf8"
|
|
10948
|
+
).digest("hex");
|
|
10949
|
+
}
|
|
10950
|
+
function resetPrincipalSessionCacheForTests() {
|
|
10951
|
+
cachedPrincipalSession = null;
|
|
10952
|
+
}
|
|
10037
10953
|
if (!doctorCliRequested) {
|
|
10038
10954
|
process.stdin.on("data", (chunk) => {
|
|
10039
10955
|
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
@@ -10123,6 +11039,10 @@ async function handleRequest(request) {
|
|
|
10123
11039
|
}
|
|
10124
11040
|
async function dispatchJsonRpcRequest(request) {
|
|
10125
11041
|
if (request.method === "initialize") {
|
|
11042
|
+
connectedClientInfo = normalizedMcpClientInfo(request.params?.clientInfo);
|
|
11043
|
+
const access = resolveApiAccess();
|
|
11044
|
+
void ensurePrincipalSessionToken(access).catch(() => null);
|
|
11045
|
+
const clientUpdate = await currentClientUpdate(access);
|
|
10126
11046
|
return {
|
|
10127
11047
|
id: request.id,
|
|
10128
11048
|
result: {
|
|
@@ -10132,7 +11052,9 @@ async function dispatchJsonRpcRequest(request) {
|
|
|
10132
11052
|
name: "@remembrance-ai/mcp-server",
|
|
10133
11053
|
version: SERVER_VERSION
|
|
10134
11054
|
},
|
|
10135
|
-
instructions: REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
|
|
11055
|
+
instructions: clientUpdate?.status === "update_available" ? `${REMEMBRANCE_MCP_SERVER_INSTRUCTIONS}
|
|
11056
|
+
|
|
11057
|
+
${clientUpdate.notice}` : REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
|
|
10136
11058
|
}
|
|
10137
11059
|
};
|
|
10138
11060
|
}
|
|
@@ -10237,47 +11159,54 @@ function messageLooksLikeZodError(message) {
|
|
|
10237
11159
|
}
|
|
10238
11160
|
async function callTool(definition, rawArguments) {
|
|
10239
11161
|
const payload = definition.schema.parse(rawArguments ?? {});
|
|
11162
|
+
const access = resolveApiAccess();
|
|
10240
11163
|
if (definition.local === "bootstrap_agent_identity") {
|
|
10241
|
-
return bootstrapAgentIdentity(payload);
|
|
11164
|
+
return bootstrapAgentIdentity(payload, access);
|
|
10242
11165
|
}
|
|
10243
11166
|
if (definition.local === "queue_private_skill_import") {
|
|
10244
|
-
return queuePrivateSkillHandoff(payload, {
|
|
11167
|
+
return queuePrivateSkillHandoff(payload, {
|
|
11168
|
+
uploadBaseUrl: access.configuration.baseUrl
|
|
11169
|
+
});
|
|
10245
11170
|
}
|
|
10246
11171
|
if (definition.name === "run_connection_doctor") {
|
|
10247
|
-
return runLocalConnectionDoctor(
|
|
11172
|
+
return runLocalConnectionDoctor(
|
|
11173
|
+
payload,
|
|
11174
|
+
"mcp_tool",
|
|
11175
|
+
access
|
|
11176
|
+
);
|
|
10248
11177
|
}
|
|
10249
11178
|
if (definition.name === "submit_feedback") {
|
|
10250
|
-
return submitFeedback(payload);
|
|
11179
|
+
return submitFeedback(payload, access);
|
|
10251
11180
|
}
|
|
10252
11181
|
if (definition.name === "submit_remembrance") {
|
|
10253
|
-
return submitRemembrance(payload);
|
|
11182
|
+
return submitRemembrance(payload, access);
|
|
10254
11183
|
}
|
|
10255
11184
|
const localConfigurationError = remoteConfigurationError(
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
11185
|
+
access.configuration.source,
|
|
11186
|
+
access.credential.source,
|
|
11187
|
+
access.configuration.issue
|
|
10259
11188
|
);
|
|
10260
|
-
const result = definition.name === "get_connection_status" && localConfigurationError ? { ok: false, status: 0, error: localConfigurationError } : await callRemembrance(definition, payload);
|
|
11189
|
+
const result = definition.name === "get_connection_status" && localConfigurationError ? { ok: false, status: 0, error: localConfigurationError } : await callRemembrance(definition, payload, { access });
|
|
10261
11190
|
if (definition.name === "get_connection_status") {
|
|
10262
11191
|
const status = localConnectionStatus(result, {
|
|
10263
|
-
apiBase,
|
|
10264
|
-
apiUrlSource:
|
|
11192
|
+
apiBase: access.configuration.baseUrl,
|
|
11193
|
+
apiUrlSource: access.configuration.source,
|
|
10265
11194
|
pluginVersion: SERVER_VERSION
|
|
10266
11195
|
});
|
|
10267
11196
|
if (!localConfigurationError) {
|
|
10268
|
-
void reportDegradedClientHealth(status).catch(() => void 0);
|
|
11197
|
+
void reportDegradedClientHealth(status, access).catch(() => void 0);
|
|
10269
11198
|
}
|
|
10270
11199
|
return status;
|
|
10271
11200
|
}
|
|
10272
11201
|
return result;
|
|
10273
11202
|
}
|
|
10274
11203
|
function remoteConfigurationError(apiUrlSource, credentialSource, apiUrlIssue) {
|
|
10275
|
-
if (credentialSource === "unusable_destination_binding") {
|
|
10276
|
-
return "The Remembrance API key is not bound to the configured registry destination. For a custom registry, store apiKey and apiUrl together in the shared config, or set REMEMBRANCE_API_KEY_ORIGIN to the exact REMEMBRANCE_API_URL value. Remote tools remain paused so the key cannot be forwarded elsewhere.";
|
|
10277
|
-
}
|
|
10278
11204
|
if (apiUrlSource === "unusable_environment") {
|
|
10279
11205
|
return apiConfigurationErrorMessage(apiUrlIssue, "REMEMBRANCE_API_URL");
|
|
10280
11206
|
}
|
|
11207
|
+
if (credentialSource === "unusable_destination_binding") {
|
|
11208
|
+
return "The Remembrance API key is not bound to the configured registry destination. For a custom registry, store apiKey and apiUrl together in the shared config, or set REMEMBRANCE_API_KEY_ORIGIN to the exact REMEMBRANCE_API_URL value. Remote tools remain paused so the key cannot be forwarded elsewhere.";
|
|
11209
|
+
}
|
|
10281
11210
|
if (apiUrlSource === "unusable_shared_config" || credentialSource === "unusable_shared_config") {
|
|
10282
11211
|
return "The shared Remembrance config exists but is unreadable or invalid. Fix or intentionally remove ~/.config/remembrance/config.json before using remote Remembrance tools.";
|
|
10283
11212
|
}
|
|
@@ -10293,7 +11222,7 @@ function apiConfigurationErrorMessage(issue, setting) {
|
|
|
10293
11222
|
return `${setting} is invalid. Set it to an absolute HTTP(S) registry URL without credentials, query parameters, or fragments, or intentionally remove it before using remote Remembrance tools.`;
|
|
10294
11223
|
}
|
|
10295
11224
|
function clientHealthReportFromConnectionStatus(status) {
|
|
10296
|
-
const health =
|
|
11225
|
+
const health = isRecord4(status.plugin_health) ? status.plugin_health : null;
|
|
10297
11226
|
if (!health || health.expected !== true || health.status !== "degraded") {
|
|
10298
11227
|
return null;
|
|
10299
11228
|
}
|
|
@@ -10303,9 +11232,9 @@ function clientHealthReportFromConnectionStatus(status) {
|
|
|
10303
11232
|
}
|
|
10304
11233
|
const host = agentHostBySurface(surface);
|
|
10305
11234
|
if (!host || !host.plugin) return null;
|
|
10306
|
-
const components =
|
|
10307
|
-
const observed = (name) =>
|
|
10308
|
-
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) =>
|
|
11235
|
+
const components = isRecord4(health.components) ? health.components : {};
|
|
11236
|
+
const observed = (name) => isRecord4(components[name]) && components[name]?.observed === true ? "active" : "not_observed";
|
|
11237
|
+
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) => isRecord4(issue) ? issue.code : null).filter((code) => typeof code === "string") : [];
|
|
10309
11238
|
const allowedIssueCodes = /* @__PURE__ */ new Set([
|
|
10310
11239
|
"native_hooks_not_observed",
|
|
10311
11240
|
"session_start_not_observed",
|
|
@@ -10357,7 +11286,7 @@ function isSafePluginVersion(value) {
|
|
|
10357
11286
|
function isSafeHostVersion(value) {
|
|
10358
11287
|
return value === null || typeof value === "string" && value.length <= 64 && /^[0-9A-Za-z._+ -]*$/.test(value.trim());
|
|
10359
11288
|
}
|
|
10360
|
-
async function reportDegradedClientHealth(status) {
|
|
11289
|
+
async function reportDegradedClientHealth(status, access = resolveApiAccess()) {
|
|
10361
11290
|
if (/^(0|false|no)$/i.test(process.env.REMEMBRANCE_HEALTH_REPORTING ?? "")) {
|
|
10362
11291
|
return;
|
|
10363
11292
|
}
|
|
@@ -10366,25 +11295,30 @@ async function reportDegradedClientHealth(status) {
|
|
|
10366
11295
|
const controller = new AbortController();
|
|
10367
11296
|
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
10368
11297
|
try {
|
|
10369
|
-
const apiKey =
|
|
10370
|
-
await fetch(
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
11298
|
+
const apiKey = access.credential.apiKey;
|
|
11299
|
+
await fetch(
|
|
11300
|
+
`${access.configuration.baseUrl}/api/v1/agent/client-health-reports`,
|
|
11301
|
+
{
|
|
11302
|
+
method: "POST",
|
|
11303
|
+
headers: {
|
|
11304
|
+
"Content-Type": "application/json",
|
|
11305
|
+
...apiKey ? { "X-Remembrance-API-Key": apiKey } : {},
|
|
11306
|
+
"User-Agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
|
|
11307
|
+
},
|
|
11308
|
+
body: JSON.stringify(report),
|
|
11309
|
+
signal: controller.signal
|
|
11310
|
+
}
|
|
11311
|
+
);
|
|
10380
11312
|
} finally {
|
|
10381
11313
|
clearTimeout(timeout);
|
|
10382
11314
|
}
|
|
10383
11315
|
}
|
|
10384
|
-
async function submitRemembrance(payload) {
|
|
11316
|
+
async function submitRemembrance(payload, access) {
|
|
10385
11317
|
const { verified_attestation: verifiedAttestation, ...request } = payload;
|
|
10386
11318
|
if (!verifiedAttestation) {
|
|
10387
|
-
return callRemembrance(mustFindTool("submit_remembrance"), request
|
|
11319
|
+
return callRemembrance(mustFindTool("submit_remembrance"), request, {
|
|
11320
|
+
access
|
|
11321
|
+
});
|
|
10388
11322
|
}
|
|
10389
11323
|
const skillSlug = request.skill?.slug;
|
|
10390
11324
|
if (!skillSlug) {
|
|
@@ -10400,20 +11334,27 @@ async function submitRemembrance(payload) {
|
|
|
10400
11334
|
};
|
|
10401
11335
|
const attestation = await signedRemembranceAttestation(
|
|
10402
11336
|
remembrancePayload,
|
|
10403
|
-
skillSlug
|
|
11337
|
+
skillSlug,
|
|
11338
|
+
access
|
|
11339
|
+
);
|
|
11340
|
+
return callRemembrance(
|
|
11341
|
+
mustFindTool("submit_remembrance"),
|
|
11342
|
+
{
|
|
11343
|
+
...remembrancePayload,
|
|
11344
|
+
evidence: {
|
|
11345
|
+
...evidence,
|
|
11346
|
+
attestation
|
|
11347
|
+
}
|
|
11348
|
+
},
|
|
11349
|
+
{ access }
|
|
10404
11350
|
);
|
|
10405
|
-
return callRemembrance(mustFindTool("submit_remembrance"), {
|
|
10406
|
-
...remembrancePayload,
|
|
10407
|
-
evidence: {
|
|
10408
|
-
...evidence,
|
|
10409
|
-
attestation
|
|
10410
|
-
}
|
|
10411
|
-
});
|
|
10412
11351
|
}
|
|
10413
|
-
async function submitFeedback(payload) {
|
|
11352
|
+
async function submitFeedback(payload, access) {
|
|
10414
11353
|
const { verified_attestation: verifiedAttestation, ...request } = payload;
|
|
10415
11354
|
if (!verifiedAttestation) {
|
|
10416
|
-
return callRemembrance(mustFindTool("submit_feedback"), request
|
|
11355
|
+
return callRemembrance(mustFindTool("submit_feedback"), request, {
|
|
11356
|
+
access
|
|
11357
|
+
});
|
|
10417
11358
|
}
|
|
10418
11359
|
const identity = await ensureSigningIdentity();
|
|
10419
11360
|
const agent = request.agent ?? {
|
|
@@ -10448,18 +11389,23 @@ async function submitFeedback(payload) {
|
|
|
10448
11389
|
};
|
|
10449
11390
|
const attestation = await signedRemembranceAttestation(
|
|
10450
11391
|
remembrancePayload,
|
|
10451
|
-
request.skill_slug
|
|
11392
|
+
request.skill_slug,
|
|
11393
|
+
access
|
|
11394
|
+
);
|
|
11395
|
+
return callRemembrance(
|
|
11396
|
+
mustFindTool("submit_feedback"),
|
|
11397
|
+
{
|
|
11398
|
+
...request,
|
|
11399
|
+
agent,
|
|
11400
|
+
evidence: {
|
|
11401
|
+
...evidence,
|
|
11402
|
+
attestation
|
|
11403
|
+
}
|
|
11404
|
+
},
|
|
11405
|
+
{ access }
|
|
10452
11406
|
);
|
|
10453
|
-
return callRemembrance(mustFindTool("submit_feedback"), {
|
|
10454
|
-
...request,
|
|
10455
|
-
agent,
|
|
10456
|
-
evidence: {
|
|
10457
|
-
...evidence,
|
|
10458
|
-
attestation
|
|
10459
|
-
}
|
|
10460
|
-
});
|
|
10461
11407
|
}
|
|
10462
|
-
async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
|
|
11408
|
+
async function signedRemembranceAttestation(remembrancePayload, skillSlug, access) {
|
|
10463
11409
|
const identity = await ensureSigningIdentity();
|
|
10464
11410
|
const evidenceHash = attestationEvidenceHashForRemembrance(remembrancePayload);
|
|
10465
11411
|
const challenge = await callRemembrance(
|
|
@@ -10471,7 +11417,8 @@ async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
|
|
|
10471
11417
|
subject: identity.subject,
|
|
10472
11418
|
skill_slug: skillSlug,
|
|
10473
11419
|
evidence_hash: evidenceHash
|
|
10474
|
-
}
|
|
11420
|
+
},
|
|
11421
|
+
{ access }
|
|
10475
11422
|
);
|
|
10476
11423
|
if (challenge.ok === false) {
|
|
10477
11424
|
throw new Error("Unable to create Remembrance attestation challenge.");
|
|
@@ -10498,7 +11445,7 @@ async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
|
|
|
10498
11445
|
signature
|
|
10499
11446
|
};
|
|
10500
11447
|
}
|
|
10501
|
-
async function bootstrapAgentIdentity(args) {
|
|
11448
|
+
async function bootstrapAgentIdentity(args, access = resolveApiAccess()) {
|
|
10502
11449
|
const keyPath = identityPath(args.key_path);
|
|
10503
11450
|
let reusedExistingIdentity = !args.force_rotate && existsSync3(keyPath);
|
|
10504
11451
|
let identity = reusedExistingIdentity ? await readIdentity(keyPath) : null;
|
|
@@ -10521,10 +11468,14 @@ async function bootstrapAgentIdentity(args) {
|
|
|
10521
11468
|
mismatchedFields.push("key_id");
|
|
10522
11469
|
}
|
|
10523
11470
|
}
|
|
11471
|
+
const ownerBinding = await agentRegistrationOwnerBinding(access).catch(
|
|
11472
|
+
() => null
|
|
11473
|
+
);
|
|
10524
11474
|
const signedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10525
11475
|
const proofPayload = buildAgentKeyRegistrationSigningPayload({
|
|
10526
11476
|
provider: identity.provider,
|
|
10527
11477
|
keyId: identity.key_id,
|
|
11478
|
+
ownerBinding,
|
|
10528
11479
|
publicKey: identity.public_key,
|
|
10529
11480
|
subject: identity.subject,
|
|
10530
11481
|
signedAt
|
|
@@ -10543,13 +11494,15 @@ async function bootstrapAgentIdentity(args) {
|
|
|
10543
11494
|
subject: identity.subject,
|
|
10544
11495
|
proof: {
|
|
10545
11496
|
algorithm: "ed25519",
|
|
11497
|
+
...ownerBinding ? { owner_binding: ownerBinding } : {},
|
|
10546
11498
|
signed_at: signedAt,
|
|
10547
11499
|
signature: proofSignature
|
|
10548
11500
|
},
|
|
10549
11501
|
metadata: {
|
|
10550
11502
|
registered_by: "@remembrance-ai/mcp-server"
|
|
10551
11503
|
}
|
|
10552
|
-
}
|
|
11504
|
+
},
|
|
11505
|
+
{ access }
|
|
10553
11506
|
);
|
|
10554
11507
|
if (registration.ok !== true) {
|
|
10555
11508
|
throw new McpPublicError(
|
|
@@ -10567,6 +11520,33 @@ async function bootstrapAgentIdentity(args) {
|
|
|
10567
11520
|
registration
|
|
10568
11521
|
};
|
|
10569
11522
|
}
|
|
11523
|
+
async function agentRegistrationOwnerBinding(access) {
|
|
11524
|
+
const credential = access.credential;
|
|
11525
|
+
const configurationError = remoteConfigurationError(
|
|
11526
|
+
access.configuration.source,
|
|
11527
|
+
credential.source,
|
|
11528
|
+
access.configuration.issue
|
|
11529
|
+
);
|
|
11530
|
+
if (configurationError) throw new McpPublicError(configurationError);
|
|
11531
|
+
const headers = {
|
|
11532
|
+
accept: "application/json",
|
|
11533
|
+
"user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
|
|
11534
|
+
};
|
|
11535
|
+
if (credential.apiKey) {
|
|
11536
|
+
headers["x-remembrance-api-key"] = credential.apiKey;
|
|
11537
|
+
}
|
|
11538
|
+
const response = await fetch(
|
|
11539
|
+
`${access.configuration.baseUrl}/api/v1/agent/keys/register`,
|
|
11540
|
+
{
|
|
11541
|
+
method: "GET",
|
|
11542
|
+
headers
|
|
11543
|
+
}
|
|
11544
|
+
);
|
|
11545
|
+
if (!response.ok) return null;
|
|
11546
|
+
const payload = await response.json();
|
|
11547
|
+
const candidate = String(payload.owner_binding ?? "").trim();
|
|
11548
|
+
return /^areg_[A-Za-z0-9_-]{24,120}$/.test(candidate) ? candidate : null;
|
|
11549
|
+
}
|
|
10570
11550
|
async function createAndPersistIdentity(args, keyPath) {
|
|
10571
11551
|
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
10572
11552
|
const publicKeyPem = String(
|
|
@@ -10624,43 +11604,56 @@ async function readIdentity(path = identityPath()) {
|
|
|
10624
11604
|
}
|
|
10625
11605
|
var automaticIdentityBootstrap = null;
|
|
10626
11606
|
async function ensureSigningIdentity() {
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
11607
|
+
const keyPath = identityPath();
|
|
11608
|
+
if (automaticIdentityBootstrap?.keyPath === keyPath) {
|
|
11609
|
+
return automaticIdentityBootstrap.promise;
|
|
11610
|
+
}
|
|
11611
|
+
const existing = await readIdentity(keyPath);
|
|
11612
|
+
if (automaticIdentityBootstrap?.keyPath === keyPath) {
|
|
11613
|
+
return automaticIdentityBootstrap.promise;
|
|
11614
|
+
}
|
|
10630
11615
|
if (existing) return existing;
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
11616
|
+
const promise = (async () => {
|
|
11617
|
+
await bootstrapAgentIdentity({ key_path: keyPath });
|
|
11618
|
+
const created = await readIdentity(keyPath);
|
|
11619
|
+
if (!created) {
|
|
11620
|
+
throw new McpPublicError(
|
|
11621
|
+
"Remembrance could not initialize the local signing identity. Run bootstrap_agent_identity and retry."
|
|
11622
|
+
);
|
|
11623
|
+
}
|
|
11624
|
+
return created;
|
|
11625
|
+
})();
|
|
11626
|
+
automaticIdentityBootstrap = { keyPath, promise };
|
|
11627
|
+
const clearBootstrap = () => {
|
|
11628
|
+
if (automaticIdentityBootstrap?.promise === promise) {
|
|
10642
11629
|
automaticIdentityBootstrap = null;
|
|
10643
|
-
}
|
|
10644
|
-
}
|
|
10645
|
-
|
|
11630
|
+
}
|
|
11631
|
+
};
|
|
11632
|
+
void promise.then(clearBootstrap, clearBootstrap);
|
|
11633
|
+
return promise;
|
|
10646
11634
|
}
|
|
10647
11635
|
async function callRemembrance(definition, rawArguments, options = {}) {
|
|
11636
|
+
const access = options.access ?? resolveApiAccess();
|
|
10648
11637
|
const parsed = definition.schema.parse(rawArguments ?? {});
|
|
11638
|
+
const projectKey = definition.name === "query_skills" || definition.name === "invoke_skill" ? await localProjectKey().catch(() => null) : null;
|
|
11639
|
+
const callerClientContext = parsed.client_context ?? {};
|
|
10649
11640
|
const payload = definition.name === "query_skills" || definition.name === "invoke_skill" ? {
|
|
10650
11641
|
...parsed,
|
|
10651
11642
|
client_context: {
|
|
10652
|
-
...
|
|
10653
|
-
surface: "mcp"
|
|
11643
|
+
...callerClientContext,
|
|
11644
|
+
surface: "mcp",
|
|
11645
|
+
...callerClientContext.project_key || !projectKey ? {} : { project_key: projectKey }
|
|
10654
11646
|
}
|
|
10655
11647
|
} : parsed;
|
|
10656
11648
|
const headers = {
|
|
10657
|
-
"content-type": "application/json"
|
|
11649
|
+
"content-type": "application/json",
|
|
11650
|
+
"user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
|
|
10658
11651
|
};
|
|
10659
|
-
const credential =
|
|
11652
|
+
const credential = access.credential;
|
|
10660
11653
|
const configurationError = remoteConfigurationError(
|
|
10661
|
-
|
|
11654
|
+
access.configuration.source,
|
|
10662
11655
|
credential.source,
|
|
10663
|
-
|
|
11656
|
+
access.configuration.issue
|
|
10664
11657
|
);
|
|
10665
11658
|
if (configurationError && definition.name !== "get_connection_status") {
|
|
10666
11659
|
throw new McpPublicError(configurationError);
|
|
@@ -10670,11 +11663,21 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
10670
11663
|
headers["x-remembrance-api-key"] = apiKey;
|
|
10671
11664
|
}
|
|
10672
11665
|
if (!options.skipEconomicsSession && definition.name !== "register_agent_key" && definition.name !== "request_attestation_challenge" && definition.name !== "get_connection_status") {
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
11666
|
+
let principalSession = currentPrincipalSessionToken(access);
|
|
11667
|
+
if (!principalSession && PRINCIPAL_SESSION_AWAITED_TOOLS.has(definition.name)) {
|
|
11668
|
+
principalSession = await ensurePrincipalSessionToken(access).catch(
|
|
11669
|
+
() => null
|
|
11670
|
+
);
|
|
11671
|
+
}
|
|
11672
|
+
if (!principalSession && PRINCIPAL_SESSION_REQUIRED_TOOLS.has(definition.name)) {
|
|
11673
|
+
throw new McpPublicError(
|
|
11674
|
+
"This operation requires a verified local installation session. Check the organization key and local TOFU identity, then retry."
|
|
11675
|
+
);
|
|
11676
|
+
}
|
|
11677
|
+
if (principalSession) {
|
|
11678
|
+
headers["x-remembrance-principal-session"] = principalSession;
|
|
11679
|
+
} else if (definition.name === "query_skills" || definition.name === "invoke_skill") {
|
|
11680
|
+
void ensurePrincipalSessionToken(access).catch(() => null);
|
|
10678
11681
|
}
|
|
10679
11682
|
}
|
|
10680
11683
|
const endpoint = endpointFor(definition, payload);
|
|
@@ -10686,25 +11689,48 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
10686
11689
|
let response;
|
|
10687
11690
|
let body;
|
|
10688
11691
|
try {
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
11692
|
+
const execute = async () => {
|
|
11693
|
+
const nextResponse = await fetch(
|
|
11694
|
+
`${access.configuration.baseUrl}${endpoint}`,
|
|
11695
|
+
{
|
|
11696
|
+
method: definition.method ?? "POST",
|
|
11697
|
+
headers,
|
|
11698
|
+
body: definition.method === "GET" ? void 0 : JSON.stringify(payload),
|
|
11699
|
+
signal: controller.signal
|
|
11700
|
+
}
|
|
11701
|
+
);
|
|
11702
|
+
let nextBody;
|
|
11703
|
+
try {
|
|
11704
|
+
nextBody = await readBoundedResponseJson(nextResponse);
|
|
11705
|
+
} catch {
|
|
11706
|
+
if (controller.signal.aborted) {
|
|
11707
|
+
throw new DOMException("Request timed out", "AbortError");
|
|
11708
|
+
}
|
|
11709
|
+
nextBody = {
|
|
11710
|
+
error: nextResponse.ok ? "Remembrance API returned an unreadable or oversized response." : "Remembrance API request failed."
|
|
10703
11711
|
};
|
|
10704
11712
|
}
|
|
10705
|
-
body
|
|
10706
|
-
|
|
10707
|
-
|
|
11713
|
+
return { response: nextResponse, body: nextBody };
|
|
11714
|
+
};
|
|
11715
|
+
({ response, body } = await execute());
|
|
11716
|
+
const sentPrincipalSession = headers["x-remembrance-principal-session"] ?? null;
|
|
11717
|
+
const refreshSignaled = response.headers.get("x-remembrance-principal-session-status") === "refresh_required";
|
|
11718
|
+
const authenticationFailed = principalSessionAuthenticationFailed(
|
|
11719
|
+
response,
|
|
11720
|
+
body
|
|
11721
|
+
);
|
|
11722
|
+
if (sentPrincipalSession && refreshSignaled && !authenticationFailed) {
|
|
11723
|
+
cachedPrincipalSession = null;
|
|
11724
|
+
void ensurePrincipalSessionToken(access).catch(() => null);
|
|
11725
|
+
} else if (sentPrincipalSession && authenticationFailed) {
|
|
11726
|
+
cachedPrincipalSession = null;
|
|
11727
|
+
const refreshed = await ensurePrincipalSessionToken(access).catch(
|
|
11728
|
+
() => null
|
|
11729
|
+
);
|
|
11730
|
+
if (refreshed && refreshed !== sentPrincipalSession) {
|
|
11731
|
+
headers["x-remembrance-principal-session"] = refreshed;
|
|
11732
|
+
({ response, body } = await execute());
|
|
11733
|
+
}
|
|
10708
11734
|
}
|
|
10709
11735
|
} catch (error) {
|
|
10710
11736
|
return {
|
|
@@ -10715,7 +11741,10 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
10715
11741
|
} finally {
|
|
10716
11742
|
clearTimeout(timeout);
|
|
10717
11743
|
}
|
|
10718
|
-
const verifiedBody = definition.name === "get_value_proof" && response.ok ? await verifySignedValueProofWithKeyRefresh(
|
|
11744
|
+
const verifiedBody = definition.name === "get_value_proof" && response.ok ? await verifySignedValueProofWithKeyRefresh(
|
|
11745
|
+
body,
|
|
11746
|
+
(forceRefresh) => fetchValueProofKeySet(forceRefresh, access)
|
|
11747
|
+
) : body;
|
|
10719
11748
|
return {
|
|
10720
11749
|
ok: response.ok,
|
|
10721
11750
|
status: response.status,
|
|
@@ -10725,26 +11754,52 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
10725
11754
|
body: verifiedBody
|
|
10726
11755
|
};
|
|
10727
11756
|
}
|
|
10728
|
-
|
|
11757
|
+
var cachedLocalProjectKey;
|
|
11758
|
+
async function localProjectKey() {
|
|
11759
|
+
const configured = process.env.REMEMBRANCE_PROJECT_KEY?.trim() ?? "";
|
|
11760
|
+
if (/^prj_[A-Za-z0-9_-]{12,120}$/.test(configured)) return configured;
|
|
11761
|
+
const projectPath = (process.env.REMEMBRANCE_PROJECT_PATH ?? process.env.PWD ?? process.cwd()).trim();
|
|
11762
|
+
if (!projectPath) return null;
|
|
11763
|
+
const identity = await ensureSigningIdentity();
|
|
11764
|
+
if (cachedLocalProjectKey?.identityKeyId === identity.key_id && cachedLocalProjectKey.projectPath === projectPath) {
|
|
11765
|
+
return cachedLocalProjectKey.value;
|
|
11766
|
+
}
|
|
11767
|
+
const value = `prj_${createHmac("sha256", identity.private_key).update(`remembrance-project-v1:${projectPath}`, "utf8").digest("base64url").slice(0, 32)}`;
|
|
11768
|
+
cachedLocalProjectKey = {
|
|
11769
|
+
identityKeyId: identity.key_id,
|
|
11770
|
+
projectPath,
|
|
11771
|
+
value
|
|
11772
|
+
};
|
|
11773
|
+
return value;
|
|
11774
|
+
}
|
|
11775
|
+
function principalSessionAuthenticationFailed(response, body) {
|
|
11776
|
+
if (response.status !== 401 && response.status !== 403) return false;
|
|
11777
|
+
if (!isRecord4(body) || typeof body.error !== "string") return false;
|
|
11778
|
+
return /principal session|economics session|installation session/i.test(
|
|
11779
|
+
body.error
|
|
11780
|
+
);
|
|
11781
|
+
}
|
|
11782
|
+
async function runLocalConnectionDoctor(options, execution = "mcp_tool", access = resolveApiAccess()) {
|
|
10729
11783
|
const connectionDefinition = mustFindTool("get_connection_status");
|
|
10730
11784
|
const localConfigurationError = remoteConfigurationError(
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
11785
|
+
access.configuration.source,
|
|
11786
|
+
access.credential.source,
|
|
11787
|
+
access.configuration.issue
|
|
10734
11788
|
);
|
|
10735
11789
|
const connectionPromise = localConfigurationError ? Promise.resolve({
|
|
10736
11790
|
ok: false,
|
|
10737
11791
|
status: 0,
|
|
10738
11792
|
error: localConfigurationError
|
|
10739
|
-
}) : callDoctorReadProbe(connectionDefinition, {});
|
|
10740
|
-
const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }) : Promise.resolve(null);
|
|
10741
|
-
const [connectionResult, catalogResult] = await Promise.all([
|
|
11793
|
+
}) : callDoctorReadProbe(connectionDefinition, {}, access);
|
|
11794
|
+
const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }, access) : Promise.resolve(null);
|
|
11795
|
+
const [connectionResult, catalogResult, clientUpdate] = await Promise.all([
|
|
10742
11796
|
connectionPromise,
|
|
10743
|
-
catalogPromise
|
|
11797
|
+
catalogPromise,
|
|
11798
|
+
currentClientUpdate(access)
|
|
10744
11799
|
]);
|
|
10745
11800
|
const connectionStatus = localConnectionStatus(connectionResult, {
|
|
10746
|
-
apiBase,
|
|
10747
|
-
apiUrlSource:
|
|
11801
|
+
apiBase: access.configuration.baseUrl,
|
|
11802
|
+
apiUrlSource: access.configuration.source,
|
|
10748
11803
|
pluginVersion: SERVER_VERSION
|
|
10749
11804
|
});
|
|
10750
11805
|
const activeReadProbe = {
|
|
@@ -10753,8 +11808,8 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
|
|
|
10753
11808
|
item_count: null
|
|
10754
11809
|
};
|
|
10755
11810
|
if (options.active_read_probe && !localConfigurationError) {
|
|
10756
|
-
const resultRecord =
|
|
10757
|
-
const body =
|
|
11811
|
+
const resultRecord = isRecord4(catalogResult) ? catalogResult : {};
|
|
11812
|
+
const body = isRecord4(resultRecord.body) ? resultRecord.body : {};
|
|
10758
11813
|
const skills = Array.isArray(body.skills) ? body.skills : null;
|
|
10759
11814
|
activeReadProbe.succeeded = resultRecord.ok === true && skills !== null;
|
|
10760
11815
|
activeReadProbe.item_count = skills ? skills.length : null;
|
|
@@ -10766,32 +11821,50 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
|
|
|
10766
11821
|
transport: execution === "standalone_cli" ? "standalone_cli" : "local_stdio_mcp",
|
|
10767
11822
|
host_registration_observed: execution === "mcp_tool",
|
|
10768
11823
|
active_read_probe: activeReadProbe,
|
|
10769
|
-
check_organization_write_authorization: options.check_organization_write_authorization
|
|
11824
|
+
check_organization_write_authorization: options.check_organization_write_authorization,
|
|
11825
|
+
client_update: clientUpdate
|
|
10770
11826
|
});
|
|
10771
11827
|
}
|
|
10772
|
-
async function
|
|
11828
|
+
async function currentClientUpdate(access = resolveApiAccess()) {
|
|
11829
|
+
const configuredSurface = process.env.REMEMBRANCE_PLUGIN_HOST?.trim().toLowerCase();
|
|
11830
|
+
const surface = configuredSurface && isPluginHostSurface(configuredSurface) ? configuredSurface : "mcp";
|
|
11831
|
+
return checkClientUpdate({
|
|
11832
|
+
apiBase: access.configuration.baseUrl,
|
|
11833
|
+
currentVersion: SERVER_VERSION,
|
|
11834
|
+
surface
|
|
11835
|
+
});
|
|
11836
|
+
}
|
|
11837
|
+
async function callDoctorReadProbe(definition, payload, access) {
|
|
10773
11838
|
const first = await callRemembrance(definition, payload, {
|
|
10774
11839
|
skipEconomicsSession: true,
|
|
10775
|
-
timeoutMs: DOCTOR_PROBE_TIMEOUT_MS
|
|
11840
|
+
timeoutMs: DOCTOR_PROBE_TIMEOUT_MS,
|
|
11841
|
+
access
|
|
10776
11842
|
});
|
|
10777
11843
|
return isTransientDoctorProbeFailure(first) ? callRemembrance(definition, payload, {
|
|
10778
11844
|
skipEconomicsSession: true,
|
|
10779
|
-
timeoutMs: DOCTOR_PROBE_TIMEOUT_MS
|
|
11845
|
+
timeoutMs: DOCTOR_PROBE_TIMEOUT_MS,
|
|
11846
|
+
access
|
|
10780
11847
|
}) : first;
|
|
10781
11848
|
}
|
|
10782
11849
|
function isTransientDoctorProbeFailure(result) {
|
|
10783
|
-
if (!
|
|
11850
|
+
if (!isRecord4(result) || result.ok === true) return false;
|
|
10784
11851
|
const status = typeof result.status === "number" ? result.status : 0;
|
|
10785
11852
|
return status === 0 || status === 408 || status === 425 || status >= 500;
|
|
10786
11853
|
}
|
|
10787
11854
|
function formatConnectionDoctorReport(report) {
|
|
11855
|
+
const clientUpdate = report.client_update ?? {
|
|
11856
|
+
status: "unavailable",
|
|
11857
|
+
latest_version: null
|
|
11858
|
+
};
|
|
10788
11859
|
const lines = [
|
|
10789
11860
|
`Remembrance connection doctor: ${report.status.toUpperCase()}`,
|
|
10790
11861
|
`Transport: ${report.transport}`,
|
|
10791
11862
|
`Scope: ${report.scope}`,
|
|
10792
11863
|
`Destination: ${report.destination.kind}`,
|
|
10793
11864
|
`Query ready: ${report.safe_to_query ? "yes" : "no"}`,
|
|
10794
|
-
`
|
|
11865
|
+
`Registry submissions: ${report.registry_submission_authorized ? "authorized" : "not authorized"}`,
|
|
11866
|
+
`Host policy observation: ${report.host_policy?.status ?? "not_observable"}`,
|
|
11867
|
+
`Client update: ${clientUpdate.status}${clientUpdate.latest_version ? ` (published ${clientUpdate.latest_version})` : ""}`,
|
|
10795
11868
|
`Signed contributions: ${report.signed_contributions_ready === null ? "not observable" : report.signed_contributions_ready ? "ready" : "not ready"}`,
|
|
10796
11869
|
""
|
|
10797
11870
|
];
|
|
@@ -10834,16 +11907,16 @@ async function runConnectionDoctorCli(args = process.argv.slice(3)) {
|
|
|
10834
11907
|
);
|
|
10835
11908
|
return report.status === "blocked" ? 1 : 0;
|
|
10836
11909
|
}
|
|
10837
|
-
async function fetchValueProofKeySet(forceRefresh) {
|
|
11910
|
+
async function fetchValueProofKeySet(forceRefresh, access = resolveApiAccess()) {
|
|
10838
11911
|
const now = Date.now();
|
|
10839
|
-
if (!forceRefresh && cachedValueProofKeys && cachedValueProofKeys.expiresAt > now) {
|
|
11912
|
+
if (!forceRefresh && cachedValueProofKeys && cachedValueProofKeys.apiBase === access.configuration.baseUrl && cachedValueProofKeys.expiresAt > now) {
|
|
10840
11913
|
return cachedValueProofKeys.value;
|
|
10841
11914
|
}
|
|
10842
11915
|
const controller = new AbortController();
|
|
10843
11916
|
const timeout = setTimeout(() => controller.abort(), apiTimeoutMs());
|
|
10844
11917
|
try {
|
|
10845
11918
|
const response = await fetch(
|
|
10846
|
-
`${
|
|
11919
|
+
`${access.configuration.baseUrl}/.well-known/remembrance-value-proof-keys.json`,
|
|
10847
11920
|
{
|
|
10848
11921
|
headers: { accept: "application/json" },
|
|
10849
11922
|
signal: controller.signal
|
|
@@ -10857,7 +11930,8 @@ async function fetchValueProofKeySet(forceRefresh) {
|
|
|
10857
11930
|
const value = await readBoundedResponseJson(response);
|
|
10858
11931
|
cachedValueProofKeys = {
|
|
10859
11932
|
value,
|
|
10860
|
-
expiresAt: now + valueProofKeyCacheTtlMs(response.headers)
|
|
11933
|
+
expiresAt: now + valueProofKeyCacheTtlMs(response.headers),
|
|
11934
|
+
apiBase: access.configuration.baseUrl
|
|
10861
11935
|
};
|
|
10862
11936
|
return value;
|
|
10863
11937
|
} finally {
|
|
@@ -10975,18 +12049,38 @@ async function fetchSkillCatalog(input) {
|
|
|
10975
12049
|
}
|
|
10976
12050
|
return skillCatalogResponseSchema.parse(response.body);
|
|
10977
12051
|
}
|
|
10978
|
-
async function
|
|
10979
|
-
|
|
10980
|
-
|
|
12052
|
+
async function ensurePrincipalSessionToken(access = resolveApiAccess()) {
|
|
12053
|
+
if (isUnusableCredentialSource(access.configuration.source) || isUnusableCredentialSource(access.credential.source)) {
|
|
12054
|
+
return null;
|
|
12055
|
+
}
|
|
12056
|
+
const identity = await ensureSigningIdentity();
|
|
10981
12057
|
const identityKey = `${identity.provider}:${identity.key_id}`;
|
|
10982
|
-
|
|
10983
|
-
|
|
12058
|
+
const accessKey = apiAccessKey(access);
|
|
12059
|
+
if (cachedPrincipalSession && cachedPrincipalSession.identityKey === identityKey && cachedPrincipalSession.accessKey === accessKey && cachedPrincipalSession.expiresAt > Date.now() + 6e4) {
|
|
12060
|
+
return cachedPrincipalSession.token;
|
|
10984
12061
|
}
|
|
10985
|
-
const
|
|
12062
|
+
const memberLinkToken = access.memberLinkToken;
|
|
12063
|
+
const challengePayload = {
|
|
10986
12064
|
action: "challenge",
|
|
10987
12065
|
provider: identity.provider,
|
|
10988
|
-
key_id: identity.key_id
|
|
10989
|
-
|
|
12066
|
+
key_id: identity.key_id,
|
|
12067
|
+
runtime_profile: localRuntimeProfile(identity),
|
|
12068
|
+
...memberLinkToken ? { member_link_token: memberLinkToken } : {}
|
|
12069
|
+
};
|
|
12070
|
+
let challengeResponse = await directPrincipalSessionRequest(
|
|
12071
|
+
challengePayload,
|
|
12072
|
+
access
|
|
12073
|
+
);
|
|
12074
|
+
if (!challengeResponse) {
|
|
12075
|
+
await bootstrapAgentIdentity({}, access).catch(() => null);
|
|
12076
|
+
challengeResponse = await directPrincipalSessionRequest(
|
|
12077
|
+
{
|
|
12078
|
+
...challengePayload,
|
|
12079
|
+
member_link_token: void 0
|
|
12080
|
+
},
|
|
12081
|
+
access
|
|
12082
|
+
);
|
|
12083
|
+
}
|
|
10990
12084
|
const challenge = challengeResponse;
|
|
10991
12085
|
if (!challenge.challenge_id || !challenge.signing_payload) return null;
|
|
10992
12086
|
const signature = signPayload(
|
|
@@ -10994,38 +12088,100 @@ async function ensureEconomicsSessionToken() {
|
|
|
10994
12088
|
Buffer.from(challenge.signing_payload),
|
|
10995
12089
|
createPrivateKey2(identity.private_key)
|
|
10996
12090
|
).toString("base64url");
|
|
10997
|
-
const exchangeResponse = await
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
12091
|
+
const exchangeResponse = await directPrincipalSessionRequest(
|
|
12092
|
+
{
|
|
12093
|
+
action: "exchange",
|
|
12094
|
+
provider: identity.provider,
|
|
12095
|
+
key_id: identity.key_id,
|
|
12096
|
+
challenge_id: challenge.challenge_id,
|
|
12097
|
+
signature
|
|
12098
|
+
},
|
|
12099
|
+
access
|
|
12100
|
+
);
|
|
11004
12101
|
const exchange = exchangeResponse;
|
|
11005
12102
|
if (!exchange.session_token || !exchange.expires_at) return null;
|
|
11006
|
-
|
|
12103
|
+
cachedPrincipalSession = {
|
|
11007
12104
|
token: exchange.session_token,
|
|
11008
12105
|
expiresAt: new Date(exchange.expires_at).getTime(),
|
|
11009
|
-
identityKey
|
|
12106
|
+
identityKey,
|
|
12107
|
+
accessKey
|
|
11010
12108
|
};
|
|
11011
|
-
return
|
|
12109
|
+
return cachedPrincipalSession.token;
|
|
12110
|
+
}
|
|
12111
|
+
function currentPrincipalSessionToken(access) {
|
|
12112
|
+
if (!cachedPrincipalSession) return null;
|
|
12113
|
+
if (cachedPrincipalSession.accessKey !== apiAccessKey(access) || cachedPrincipalSession.expiresAt <= Date.now() + 6e4) {
|
|
12114
|
+
cachedPrincipalSession = null;
|
|
12115
|
+
return null;
|
|
12116
|
+
}
|
|
12117
|
+
return cachedPrincipalSession.token;
|
|
11012
12118
|
}
|
|
11013
|
-
async function
|
|
12119
|
+
async function directPrincipalSessionRequest(payload, access) {
|
|
11014
12120
|
const controller = new AbortController();
|
|
11015
12121
|
const timeout = setTimeout(() => controller.abort(), apiTimeoutMs());
|
|
11016
12122
|
try {
|
|
11017
|
-
const response = await fetch(
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
12123
|
+
const response = await fetch(
|
|
12124
|
+
`${access.configuration.baseUrl}/api/v1/agent/principal-sessions`,
|
|
12125
|
+
{
|
|
12126
|
+
method: "POST",
|
|
12127
|
+
headers: { "content-type": "application/json" },
|
|
12128
|
+
body: JSON.stringify(payload),
|
|
12129
|
+
signal: controller.signal
|
|
12130
|
+
}
|
|
12131
|
+
);
|
|
11023
12132
|
if (!response.ok) return null;
|
|
11024
12133
|
return response.json();
|
|
11025
12134
|
} finally {
|
|
11026
12135
|
clearTimeout(timeout);
|
|
11027
12136
|
}
|
|
11028
12137
|
}
|
|
12138
|
+
function normalizedMcpClientInfo(value) {
|
|
12139
|
+
if (!isRecord4(value)) return null;
|
|
12140
|
+
const name = boundedLocalProfileField(value.name);
|
|
12141
|
+
if (!name) return null;
|
|
12142
|
+
return {
|
|
12143
|
+
name,
|
|
12144
|
+
version: boundedLocalProfileField(value.version)
|
|
12145
|
+
};
|
|
12146
|
+
}
|
|
12147
|
+
function localRuntimeProfile(identity) {
|
|
12148
|
+
const configuredSurface = String(process.env.REMEMBRANCE_PLUGIN_HOST ?? "").trim().toLowerCase();
|
|
12149
|
+
const runtime = isPluginHostSurface(configuredSurface) ? configuredSurface : "other";
|
|
12150
|
+
const clientName = connectedClientInfo?.name ?? agentHostBySurface(runtime)?.host_name ?? "MCP";
|
|
12151
|
+
const hostSurface = localRuntimeHostSurface(runtime, clientName);
|
|
12152
|
+
const profileDigest = createHash3("sha256").update(
|
|
12153
|
+
[identity.key_id, runtime, hostSurface, clientName.toLowerCase()].join(
|
|
12154
|
+
":"
|
|
12155
|
+
),
|
|
12156
|
+
"utf8"
|
|
12157
|
+
).digest("base64url");
|
|
12158
|
+
return {
|
|
12159
|
+
runtime,
|
|
12160
|
+
surface: "mcp",
|
|
12161
|
+
host_surface: hostSurface,
|
|
12162
|
+
client_name: clientName,
|
|
12163
|
+
client_version: SERVER_VERSION,
|
|
12164
|
+
runtime_version: connectedClientInfo?.version ?? null,
|
|
12165
|
+
profile_key: `rpf_${profileDigest}`
|
|
12166
|
+
};
|
|
12167
|
+
}
|
|
12168
|
+
function localRuntimeHostSurface(runtime, clientName) {
|
|
12169
|
+
const configured = String(process.env.REMEMBRANCE_HOST_SURFACE ?? "").trim().toLowerCase();
|
|
12170
|
+
if (configured === "desktop" || configured === "cli" || configured === "extension" || configured === "gateway" || configured === "unknown") {
|
|
12171
|
+
return configured;
|
|
12172
|
+
}
|
|
12173
|
+
if (/desktop|chatgpt|codex app/i.test(clientName)) return "desktop";
|
|
12174
|
+
if (/cursor|visual studio|vscode/i.test(clientName)) return "extension";
|
|
12175
|
+
if (runtime === "openclaw") return "gateway";
|
|
12176
|
+
if (runtime === "cursor" || runtime === "vs_code") return "extension";
|
|
12177
|
+
if (runtime === "claude_code" || runtime === "opencode") return "cli";
|
|
12178
|
+
return "unknown";
|
|
12179
|
+
}
|
|
12180
|
+
function boundedLocalProfileField(value) {
|
|
12181
|
+
if (typeof value !== "string") return null;
|
|
12182
|
+
const normalized = value.trim().replace(/[\u0000-\u001f\u007f]/g, "");
|
|
12183
|
+
return normalized ? normalized.slice(0, 512) : null;
|
|
12184
|
+
}
|
|
11029
12185
|
function mustFindTool(name) {
|
|
11030
12186
|
const tool2 = tools.find((item) => item.name === name);
|
|
11031
12187
|
if (!tool2) {
|
|
@@ -11033,7 +12189,7 @@ function mustFindTool(name) {
|
|
|
11033
12189
|
}
|
|
11034
12190
|
return tool2;
|
|
11035
12191
|
}
|
|
11036
|
-
function
|
|
12192
|
+
function isRecord4(value) {
|
|
11037
12193
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
11038
12194
|
}
|
|
11039
12195
|
function identityPath(explicit) {
|
|
@@ -11087,6 +12243,7 @@ export {
|
|
|
11087
12243
|
readJsonRpcMessages,
|
|
11088
12244
|
remoteConfigurationError,
|
|
11089
12245
|
reportDegradedClientHealth,
|
|
12246
|
+
resetPrincipalSessionCacheForTests,
|
|
11090
12247
|
resetValueProofKeyCacheForTests,
|
|
11091
12248
|
runConnectionDoctorCli,
|
|
11092
12249
|
runLocalConnectionDoctor
|