@remembrance-ai/opencode-plugin 0.1.52 → 0.1.55
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 +21 -7
- package/opencode.json +2 -2
- package/package.json +1 -1
- package/scripts/hook-core.mjs +690 -21
- package/servers/remembrance-mcp.mjs +564 -67
- package/skills/remembrancer/references/remembrance-payloads.md +6 -4
- package/skills/remembrancer/references/remembrance-setup.md +48 -25
- package/src/index.mjs +84 -5
|
@@ -90,7 +90,14 @@ var AGENT_HOSTS = [
|
|
|
90
90
|
public_mirror: true,
|
|
91
91
|
client_release: true,
|
|
92
92
|
release_smoke: true,
|
|
93
|
-
|
|
93
|
+
release_evidence: [
|
|
94
|
+
"artifact_exact",
|
|
95
|
+
"adapter_contract",
|
|
96
|
+
"transport_contract",
|
|
97
|
+
"host_discovery",
|
|
98
|
+
"host_process",
|
|
99
|
+
"authenticated_tool"
|
|
100
|
+
],
|
|
94
101
|
plugin: true,
|
|
95
102
|
hooks: {
|
|
96
103
|
session_start: true,
|
|
@@ -110,7 +117,12 @@ var AGENT_HOSTS = [
|
|
|
110
117
|
public_mirror: true,
|
|
111
118
|
client_release: true,
|
|
112
119
|
release_smoke: true,
|
|
113
|
-
|
|
120
|
+
release_evidence: [
|
|
121
|
+
"artifact_exact",
|
|
122
|
+
"adapter_contract",
|
|
123
|
+
"transport_contract",
|
|
124
|
+
"host_discovery"
|
|
125
|
+
],
|
|
114
126
|
plugin: true,
|
|
115
127
|
hooks: {
|
|
116
128
|
session_start: true,
|
|
@@ -130,7 +142,12 @@ var AGENT_HOSTS = [
|
|
|
130
142
|
public_mirror: true,
|
|
131
143
|
client_release: true,
|
|
132
144
|
release_smoke: true,
|
|
133
|
-
|
|
145
|
+
release_evidence: [
|
|
146
|
+
"artifact_exact",
|
|
147
|
+
"adapter_contract",
|
|
148
|
+
"transport_contract",
|
|
149
|
+
"source_contract"
|
|
150
|
+
],
|
|
134
151
|
plugin: true,
|
|
135
152
|
hooks: {
|
|
136
153
|
session_start: true,
|
|
@@ -150,7 +167,12 @@ var AGENT_HOSTS = [
|
|
|
150
167
|
public_mirror: true,
|
|
151
168
|
client_release: true,
|
|
152
169
|
release_smoke: true,
|
|
153
|
-
|
|
170
|
+
release_evidence: [
|
|
171
|
+
"artifact_exact",
|
|
172
|
+
"adapter_contract",
|
|
173
|
+
"transport_contract",
|
|
174
|
+
"host_discovery"
|
|
175
|
+
],
|
|
154
176
|
plugin: true,
|
|
155
177
|
hooks: {
|
|
156
178
|
session_start: true,
|
|
@@ -170,7 +192,12 @@ var AGENT_HOSTS = [
|
|
|
170
192
|
public_mirror: true,
|
|
171
193
|
client_release: true,
|
|
172
194
|
release_smoke: true,
|
|
173
|
-
|
|
195
|
+
release_evidence: [
|
|
196
|
+
"artifact_exact",
|
|
197
|
+
"adapter_contract",
|
|
198
|
+
"transport_contract",
|
|
199
|
+
"source_contract"
|
|
200
|
+
],
|
|
174
201
|
plugin: true,
|
|
175
202
|
hooks: {
|
|
176
203
|
session_start: true,
|
|
@@ -190,7 +217,14 @@ var AGENT_HOSTS = [
|
|
|
190
217
|
public_mirror: true,
|
|
191
218
|
client_release: true,
|
|
192
219
|
release_smoke: true,
|
|
193
|
-
|
|
220
|
+
release_evidence: [
|
|
221
|
+
"artifact_exact",
|
|
222
|
+
"adapter_contract",
|
|
223
|
+
"transport_contract",
|
|
224
|
+
"host_discovery",
|
|
225
|
+
"host_process",
|
|
226
|
+
"authenticated_tool"
|
|
227
|
+
],
|
|
194
228
|
plugin: true,
|
|
195
229
|
hooks: {
|
|
196
230
|
session_start: true,
|
|
@@ -210,7 +244,7 @@ var AGENT_HOSTS = [
|
|
|
210
244
|
public_mirror: false,
|
|
211
245
|
client_release: true,
|
|
212
246
|
release_smoke: true,
|
|
213
|
-
|
|
247
|
+
release_evidence: ["artifact_exact", "transport_contract"],
|
|
214
248
|
plugin: false,
|
|
215
249
|
hooks: {
|
|
216
250
|
session_start: false,
|
|
@@ -230,7 +264,7 @@ var AGENT_HOSTS = [
|
|
|
230
264
|
public_mirror: false,
|
|
231
265
|
client_release: false,
|
|
232
266
|
release_smoke: true,
|
|
233
|
-
|
|
267
|
+
release_evidence: ["transport_contract"],
|
|
234
268
|
plugin: false,
|
|
235
269
|
hooks: {
|
|
236
270
|
session_start: false,
|
|
@@ -4397,6 +4431,182 @@ var clientHealthReportSchema = external_exports.object({
|
|
|
4397
4431
|
}
|
|
4398
4432
|
});
|
|
4399
4433
|
|
|
4434
|
+
// ../core/src/client-release.ts
|
|
4435
|
+
var CLIENT_RELEASE_SCHEMA_VERSION = "1";
|
|
4436
|
+
var CODEX_UPDATE_COMMAND = [
|
|
4437
|
+
'CODEX_CLI="${CODEX_CLI:-$(command -v codex || true)}"',
|
|
4438
|
+
'[ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/ChatGPT.app/Contents/Resources/codex"',
|
|
4439
|
+
'[ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/Codex.app/Contents/Resources/codex"',
|
|
4440
|
+
`[ -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; }`,
|
|
4441
|
+
'"$CODEX_CLI" plugin marketplace upgrade remembrance',
|
|
4442
|
+
'"$CODEX_CLI" plugin add remembrance@remembrance --json'
|
|
4443
|
+
].join("\n");
|
|
4444
|
+
var CLIENT_UPDATE_GUIDANCE = {
|
|
4445
|
+
codex: {
|
|
4446
|
+
surface: "codex",
|
|
4447
|
+
host_name: "Codex",
|
|
4448
|
+
command: CODEX_UPDATE_COMMAND,
|
|
4449
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled Codex update command exactly as shown.",
|
|
4450
|
+
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."
|
|
4451
|
+
},
|
|
4452
|
+
claude_code: {
|
|
4453
|
+
surface: "claude_code",
|
|
4454
|
+
host_name: "Claude Code",
|
|
4455
|
+
command: "claude plugin marketplace update remembrance\nclaude plugin update remembrance@remembrance",
|
|
4456
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled Claude Code update command exactly as shown.",
|
|
4457
|
+
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."
|
|
4458
|
+
},
|
|
4459
|
+
cursor: {
|
|
4460
|
+
surface: "cursor",
|
|
4461
|
+
host_name: "Cursor",
|
|
4462
|
+
command: null,
|
|
4463
|
+
update_action: "Tell the user to open Cursor settings, refresh the marketplace that provides Remembrance, and choose Update for the Remembrance plugin.",
|
|
4464
|
+
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."
|
|
4465
|
+
},
|
|
4466
|
+
openclaw: {
|
|
4467
|
+
surface: "openclaw",
|
|
4468
|
+
host_name: "OpenClaw",
|
|
4469
|
+
command: "openclaw plugins update remembrance\nopenclaw remembrance setup",
|
|
4470
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled OpenClaw update and setup commands exactly as shown.",
|
|
4471
|
+
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."
|
|
4472
|
+
},
|
|
4473
|
+
vs_code: {
|
|
4474
|
+
surface: "vs_code",
|
|
4475
|
+
host_name: "VS Code",
|
|
4476
|
+
command: null,
|
|
4477
|
+
update_action: "Tell the user to refresh the marketplace or managed source that provides Remembrance and update the Remembrance plugin there.",
|
|
4478
|
+
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."
|
|
4479
|
+
},
|
|
4480
|
+
opencode: {
|
|
4481
|
+
surface: "opencode",
|
|
4482
|
+
host_name: "opencode",
|
|
4483
|
+
command: "npx -y @remembrance-ai/opencode-plugin@latest setup",
|
|
4484
|
+
update_action: "Ask the user for permission to update Remembrance. If approved, run the bundled opencode setup command exactly as shown.",
|
|
4485
|
+
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."
|
|
4486
|
+
},
|
|
4487
|
+
mcp: {
|
|
4488
|
+
surface: "mcp",
|
|
4489
|
+
host_name: "MCP client",
|
|
4490
|
+
command: null,
|
|
4491
|
+
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.",
|
|
4492
|
+
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."
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
var CLIENT_RELEASE_SURFACES = AGENT_HOSTS.filter(
|
|
4496
|
+
(host) => host.client_release
|
|
4497
|
+
).map((host) => host.surface);
|
|
4498
|
+
function parseStableClientVersion(value) {
|
|
4499
|
+
if (typeof value !== "string") return null;
|
|
4500
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(value);
|
|
4501
|
+
if (!match) return null;
|
|
4502
|
+
const parts = match.slice(1).map(Number);
|
|
4503
|
+
if (parts.length !== 3 || parts.some((part) => !Number.isSafeInteger(part) || part > 9999)) {
|
|
4504
|
+
return null;
|
|
4505
|
+
}
|
|
4506
|
+
return parts;
|
|
4507
|
+
}
|
|
4508
|
+
function compareClientVersions(left, right) {
|
|
4509
|
+
const leftParts = parseStableClientVersion(left);
|
|
4510
|
+
const rightParts = parseStableClientVersion(right);
|
|
4511
|
+
if (!leftParts || !rightParts) {
|
|
4512
|
+
throw new Error(
|
|
4513
|
+
"Client versions must use stable major.minor.patch syntax."
|
|
4514
|
+
);
|
|
4515
|
+
}
|
|
4516
|
+
for (let index = 0; index < 3; index += 1) {
|
|
4517
|
+
const difference = leftParts[index] - rightParts[index];
|
|
4518
|
+
if (difference !== 0) return Math.sign(difference);
|
|
4519
|
+
}
|
|
4520
|
+
return 0;
|
|
4521
|
+
}
|
|
4522
|
+
function parseClientReleaseManifest(value) {
|
|
4523
|
+
if (!isRecord(value)) return null;
|
|
4524
|
+
if (value.schema_version !== CLIENT_RELEASE_SCHEMA_VERSION) return null;
|
|
4525
|
+
const latestVersion = value.latest_version;
|
|
4526
|
+
if (typeof latestVersion !== "string" || !parseStableClientVersion(latestVersion)) {
|
|
4527
|
+
return null;
|
|
4528
|
+
}
|
|
4529
|
+
if (typeof value.published_at !== "string" || !isCanonicalIsoTimestamp(value.published_at)) {
|
|
4530
|
+
return null;
|
|
4531
|
+
}
|
|
4532
|
+
if (!Array.isArray(value.surfaces)) return null;
|
|
4533
|
+
const validSurfaces = new Set(CLIENT_RELEASE_SURFACES);
|
|
4534
|
+
const surfaces = value.surfaces.filter(
|
|
4535
|
+
(surface) => typeof surface === "string" && validSurfaces.has(surface)
|
|
4536
|
+
);
|
|
4537
|
+
if (surfaces.length !== value.surfaces.length || new Set(surfaces).size !== surfaces.length) {
|
|
4538
|
+
return null;
|
|
4539
|
+
}
|
|
4540
|
+
return {
|
|
4541
|
+
schema_version: CLIENT_RELEASE_SCHEMA_VERSION,
|
|
4542
|
+
latest_version: latestVersion,
|
|
4543
|
+
published_at: new Date(value.published_at).toISOString(),
|
|
4544
|
+
surfaces
|
|
4545
|
+
};
|
|
4546
|
+
}
|
|
4547
|
+
function resolveClientUpdateStatus(input) {
|
|
4548
|
+
if (!parseStableClientVersion(input.currentVersion)) {
|
|
4549
|
+
return {
|
|
4550
|
+
status: "unavailable",
|
|
4551
|
+
current_version: input.currentVersion,
|
|
4552
|
+
reason: "invalid_current_version"
|
|
4553
|
+
};
|
|
4554
|
+
}
|
|
4555
|
+
const manifest = parseClientReleaseManifest(input.manifest);
|
|
4556
|
+
if (!manifest) {
|
|
4557
|
+
return {
|
|
4558
|
+
status: "unavailable",
|
|
4559
|
+
current_version: input.currentVersion,
|
|
4560
|
+
reason: "invalid_manifest"
|
|
4561
|
+
};
|
|
4562
|
+
}
|
|
4563
|
+
if (!manifest.surfaces.includes(input.surface)) {
|
|
4564
|
+
return {
|
|
4565
|
+
status: "unavailable",
|
|
4566
|
+
current_version: input.currentVersion,
|
|
4567
|
+
reason: "surface_not_published"
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
if (compareClientVersions(manifest.latest_version, input.currentVersion) <= 0) {
|
|
4571
|
+
return {
|
|
4572
|
+
status: "current",
|
|
4573
|
+
current_version: input.currentVersion,
|
|
4574
|
+
latest_version: manifest.latest_version
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
const guidance = CLIENT_UPDATE_GUIDANCE[input.surface];
|
|
4578
|
+
return {
|
|
4579
|
+
status: "update_available",
|
|
4580
|
+
current_version: input.currentVersion,
|
|
4581
|
+
latest_version: manifest.latest_version,
|
|
4582
|
+
guidance,
|
|
4583
|
+
notice: clientUpdateNotice({
|
|
4584
|
+
currentVersion: input.currentVersion,
|
|
4585
|
+
latestVersion: manifest.latest_version,
|
|
4586
|
+
guidance
|
|
4587
|
+
})
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
function clientUpdateNotice(input) {
|
|
4591
|
+
const command = input.guidance.command ? `
|
|
4592
|
+
Trusted update command bundled with this installed client:
|
|
4593
|
+
${input.guidance.command}` : "";
|
|
4594
|
+
return [
|
|
4595
|
+
`Remembrance update available: installed ${input.currentVersion}, published ${input.latestVersion}.`,
|
|
4596
|
+
input.guidance.update_action,
|
|
4597
|
+
command,
|
|
4598
|
+
input.guidance.restart_action,
|
|
4599
|
+
"Do not claim the new version is active until a fresh process reports it."
|
|
4600
|
+
].filter(Boolean).join("\n");
|
|
4601
|
+
}
|
|
4602
|
+
function isRecord(value) {
|
|
4603
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4604
|
+
}
|
|
4605
|
+
function isCanonicalIsoTimestamp(value) {
|
|
4606
|
+
const timestamp = Date.parse(value);
|
|
4607
|
+
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4400
4610
|
// ../core/src/client-health-alerts.ts
|
|
4401
4611
|
var secureWebhookUrlSchema = external_exports.string().trim().url().max(2048).superRefine((value, context) => {
|
|
4402
4612
|
let parsed;
|
|
@@ -4486,9 +4696,16 @@ function isUnsafeDestinationHostname(hostname) {
|
|
|
4486
4696
|
}
|
|
4487
4697
|
|
|
4488
4698
|
// ../core/src/connection-doctor.ts
|
|
4699
|
+
var HOST_POLICY_OPERATION_CLASSES = /* @__PURE__ */ new Set([
|
|
4700
|
+
"query",
|
|
4701
|
+
"private_contribution",
|
|
4702
|
+
"contribution",
|
|
4703
|
+
"feedback",
|
|
4704
|
+
"other"
|
|
4705
|
+
]);
|
|
4489
4706
|
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
4707
|
function codexHookReviewAction(followupTool) {
|
|
4491
|
-
return `Open Terminal and launch Codex with the command below.
|
|
4708
|
+
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.`;
|
|
4492
4709
|
}
|
|
4493
4710
|
function buildConnectionDoctorReport(input) {
|
|
4494
4711
|
const status = record(input.connection_status);
|
|
@@ -4498,6 +4715,7 @@ function buildConnectionDoctorReport(input) {
|
|
|
4498
4715
|
const capabilities = record(authentication.capabilities);
|
|
4499
4716
|
const sharedConfig = record(transport.shared_config);
|
|
4500
4717
|
const pluginHealth = record(status.plugin_health);
|
|
4718
|
+
const hostPolicy = record(pluginHealth.host_policy);
|
|
4501
4719
|
const identity = record(status.local_signing_identity);
|
|
4502
4720
|
const destination = destinationFromTransport(transport);
|
|
4503
4721
|
const credentialSource = stringValue(transport.credential_source) ?? "none";
|
|
@@ -4629,6 +4847,22 @@ function buildConnectionDoctorReport(input) {
|
|
|
4629
4847
|
)
|
|
4630
4848
|
);
|
|
4631
4849
|
checks.push(signingIdentityCheck(identity, input.transport));
|
|
4850
|
+
checks.push(clientUpdateCheck(input.client_update ?? null));
|
|
4851
|
+
const hostPolicyStatus = hostPolicyStatusValue(hostPolicy.status);
|
|
4852
|
+
checks.push(
|
|
4853
|
+
hostPolicyStatus === "recent_denial" ? warning(
|
|
4854
|
+
"host_policy_observability",
|
|
4855
|
+
"The host recently blocked a Remembrance operation before transport; no content was sent.",
|
|
4856
|
+
"review_host_policy",
|
|
4857
|
+
"Review the host's trusted-service or data-export policy. Querying remains available."
|
|
4858
|
+
) : hostPolicyStatus === "no_recent_denial" ? pass(
|
|
4859
|
+
"host_policy_observability",
|
|
4860
|
+
"This plugin can observe host policy denials; none were observed recently. This does not grant permission for future submissions."
|
|
4861
|
+
) : notApplicable(
|
|
4862
|
+
"host_policy_observability",
|
|
4863
|
+
"This host does not expose a reliable pre-transport policy-denial event to the plugin."
|
|
4864
|
+
)
|
|
4865
|
+
);
|
|
4632
4866
|
const blockingIds = /* @__PURE__ */ new Set([
|
|
4633
4867
|
"configuration",
|
|
4634
4868
|
"credential_security",
|
|
@@ -4644,6 +4878,7 @@ function buildConnectionDoctorReport(input) {
|
|
|
4644
4878
|
(check) => check.status === "warning" || check.status === "fail"
|
|
4645
4879
|
);
|
|
4646
4880
|
const readSucceeded = !input.active_read_probe.requested || input.active_read_probe.succeeded === true;
|
|
4881
|
+
const registrySubmissionAuthorized = scope === "organization" && submitAuthorized;
|
|
4647
4882
|
return {
|
|
4648
4883
|
schema_version: "1",
|
|
4649
4884
|
status: blocked ? "blocked" : attention ? "attention" : "healthy",
|
|
@@ -4651,12 +4886,88 @@ function buildConnectionDoctorReport(input) {
|
|
|
4651
4886
|
scope,
|
|
4652
4887
|
destination,
|
|
4653
4888
|
safe_to_query: !blocked && registryReady && queryAuthorized && readSucceeded,
|
|
4654
|
-
|
|
4889
|
+
registry_submission_authorized: registrySubmissionAuthorized,
|
|
4890
|
+
organization_write_authorized: registrySubmissionAuthorized,
|
|
4891
|
+
host_policy: {
|
|
4892
|
+
status: hostPolicyStatus,
|
|
4893
|
+
observable: hostPolicy.observable === true,
|
|
4894
|
+
...hostPolicyOperationClassValue(hostPolicy.operation_class) ? {
|
|
4895
|
+
operation_class: hostPolicyOperationClassValue(
|
|
4896
|
+
hostPolicy.operation_class
|
|
4897
|
+
)
|
|
4898
|
+
} : {},
|
|
4899
|
+
...isoTimestampValue(hostPolicy.last_denial_at) ? { last_denial_at: isoTimestampValue(hostPolicy.last_denial_at) } : {}
|
|
4900
|
+
},
|
|
4901
|
+
client_update: clientUpdateSummary(input.client_update ?? null),
|
|
4655
4902
|
signed_contributions_ready: input.transport === "hosted_http_mcp" ? null : identity.status === "ready",
|
|
4656
4903
|
active_read_probe: input.active_read_probe,
|
|
4657
4904
|
checks
|
|
4658
4905
|
};
|
|
4659
4906
|
}
|
|
4907
|
+
function clientUpdateCheck(update) {
|
|
4908
|
+
if (!update) {
|
|
4909
|
+
return notApplicable(
|
|
4910
|
+
"client_update",
|
|
4911
|
+
"The published client release could not be checked. This does not affect querying."
|
|
4912
|
+
);
|
|
4913
|
+
}
|
|
4914
|
+
if (update.status === "update_available") {
|
|
4915
|
+
return warning(
|
|
4916
|
+
"client_update",
|
|
4917
|
+
`Remembrance ${update.latest_version} is published; this process is running ${update.current_version}.`,
|
|
4918
|
+
"update_remembrance_client",
|
|
4919
|
+
`${update.guidance.update_action} ${update.guidance.restart_action} Do not claim the new version is active until a fresh process reports it.`,
|
|
4920
|
+
void 0,
|
|
4921
|
+
false,
|
|
4922
|
+
update.guidance.command ?? void 0
|
|
4923
|
+
);
|
|
4924
|
+
}
|
|
4925
|
+
if (update.status === "current") {
|
|
4926
|
+
return pass(
|
|
4927
|
+
"client_update",
|
|
4928
|
+
`This process is current at Remembrance ${update.current_version}.`
|
|
4929
|
+
);
|
|
4930
|
+
}
|
|
4931
|
+
return notApplicable(
|
|
4932
|
+
"client_update",
|
|
4933
|
+
"The installed or published client version could not be compared safely."
|
|
4934
|
+
);
|
|
4935
|
+
}
|
|
4936
|
+
function clientUpdateSummary(update) {
|
|
4937
|
+
if (!update) {
|
|
4938
|
+
return {
|
|
4939
|
+
status: "unavailable",
|
|
4940
|
+
current_version: null,
|
|
4941
|
+
latest_version: null
|
|
4942
|
+
};
|
|
4943
|
+
}
|
|
4944
|
+
if (update.status === "current" || update.status === "update_available") {
|
|
4945
|
+
return {
|
|
4946
|
+
status: update.status,
|
|
4947
|
+
current_version: update.current_version,
|
|
4948
|
+
latest_version: update.latest_version
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
return {
|
|
4952
|
+
status: "unavailable",
|
|
4953
|
+
current_version: update.current_version,
|
|
4954
|
+
latest_version: null
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
function hostPolicyOperationClassValue(value) {
|
|
4958
|
+
if (typeof value !== "string" || !HOST_POLICY_OPERATION_CLASSES.has(value)) {
|
|
4959
|
+
return void 0;
|
|
4960
|
+
}
|
|
4961
|
+
return value;
|
|
4962
|
+
}
|
|
4963
|
+
function isoTimestampValue(value) {
|
|
4964
|
+
if (typeof value !== "string") return void 0;
|
|
4965
|
+
const parsed = Date.parse(value);
|
|
4966
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : void 0;
|
|
4967
|
+
}
|
|
4968
|
+
function hostPolicyStatusValue(value) {
|
|
4969
|
+
return value === "recent_denial" || value === "no_recent_denial" ? value : "not_observable";
|
|
4970
|
+
}
|
|
4660
4971
|
function readProbeCheck(probe) {
|
|
4661
4972
|
if (!probe.requested) {
|
|
4662
4973
|
return notApplicable(
|
|
@@ -4813,7 +5124,7 @@ function stringValue(value) {
|
|
|
4813
5124
|
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
5125
|
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
5126
|
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.
|
|
5127
|
+
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
5128
|
|
|
4818
5129
|
// ../core/src/redaction.ts
|
|
4819
5130
|
var SECRET_PATTERN_SPECS = [
|
|
@@ -5946,7 +6257,7 @@ var seedSkills = [
|
|
|
5946
6257
|
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
6258
|
status: "active",
|
|
5948
6259
|
visibility: "public",
|
|
5949
|
-
version: "0.1.
|
|
6260
|
+
version: "0.1.17",
|
|
5950
6261
|
domains: ["agent-skills", "mcp", "resource-discovery"],
|
|
5951
6262
|
tags: [
|
|
5952
6263
|
"remembrance",
|
|
@@ -5997,7 +6308,7 @@ var seedSkills = [
|
|
|
5997
6308
|
"gemini",
|
|
5998
6309
|
"rest"
|
|
5999
6310
|
],
|
|
6000
|
-
version: "0.1.
|
|
6311
|
+
version: "0.1.17",
|
|
6001
6312
|
status: "active",
|
|
6002
6313
|
visibility: "public",
|
|
6003
6314
|
providers: ["codex", "claude", "cursor", "openclaw", "generic"],
|
|
@@ -6189,15 +6500,18 @@ CODEX_CLI="\${CODEX_CLI:-$(command -v codex || true)}"
|
|
|
6189
6500
|
This command handles both first install and update. If zsh says
|
|
6190
6501
|
"codex: command not found", it discovers the current ChatGPT desktop bundle or
|
|
6191
6502
|
the legacy Codex app bundle without requiring a shell alias. The final command
|
|
6192
|
-
opens Codex CLI so its secure hook review can be completed immediately
|
|
6503
|
+
opens Codex CLI so its secure hook review can be completed immediately when
|
|
6504
|
+
Codex requires it.
|
|
6193
6505
|
|
|
6194
6506
|
Codex will not execute plugin hooks until their exact definitions are trusted.
|
|
6195
|
-
In the Codex window opened by the installer,
|
|
6196
|
-
|
|
6507
|
+
In the Codex window opened by the installer, if Codex shows a **Hooks need
|
|
6508
|
+
review** screen, choose **Review hooks** and trust only the Remembrance
|
|
6197
6509
|
\`SessionStart\`, \`UserPromptSubmit\`, \`PostToolUse\`, and \`Stop\` hooks.
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6510
|
+
If no review screen appears, continue: Codex may be reusing an existing valid
|
|
6511
|
+
trust decision. Changed hook definitions show the same review screen again;
|
|
6512
|
+
never use the automation-only trust bypass for normal installation. Fully
|
|
6513
|
+
restart Codex, submit one prompt, use one Remembrance tool, complete one turn,
|
|
6514
|
+
and run \`run_connection_doctor\`.
|
|
6201
6515
|
|
|
6202
6516
|
OpenClaw:
|
|
6203
6517
|
|
|
@@ -6255,10 +6569,11 @@ Integrations & MCP**, and verify query, invocation, feedback, and contribution
|
|
|
6255
6569
|
receipts in both local and cloud runs.
|
|
6256
6570
|
|
|
6257
6571
|
After installing any native plugin, restart the agent app/session and approve
|
|
6258
|
-
the runtime's trust request. For Codex, complete the
|
|
6259
|
-
above
|
|
6260
|
-
|
|
6261
|
-
|
|
6572
|
+
the runtime's trust request when one appears. For Codex, complete the hook
|
|
6573
|
+
review above if Codex requests it; unchanged, previously trusted definitions
|
|
6574
|
+
may not show another review screen. Changed hook definitions show the review
|
|
6575
|
+
screen again. A currently running Codex or Claude thread usually cannot
|
|
6576
|
+
hot-load newly installed plugin tools.
|
|
6262
6577
|
|
|
6263
6578
|
## Enterprise/org key setup
|
|
6264
6579
|
|
|
@@ -6402,7 +6717,8 @@ The MCP annotation for \`propose_private_skill\` is non-read-only and
|
|
|
6402
6717
|
closed-world: it makes a network request, but can change only the authenticated
|
|
6403
6718
|
organization's private review queue and cannot change publicly visible internet
|
|
6404
6719
|
state. Closed-world does not mean zero-network. Only
|
|
6405
|
-
\`queue_private_skill_import\` is
|
|
6720
|
+
\`queue_private_skill_import\` is a local, zero-network handoff tool, and it
|
|
6721
|
+
should run only when an organization admin explicitly requests that handoff.
|
|
6406
6722
|
|
|
6407
6723
|
Example text to merge into the tenant-specific guardian policy:
|
|
6408
6724
|
|
|
@@ -6437,12 +6753,14 @@ over a user's local \`[auto_review].policy\`. See the official Codex
|
|
|
6437
6753
|
[managed configuration](https://learn.chatgpt.com/docs/enterprise/managed-configuration#configure-automatic-review-policy)
|
|
6438
6754
|
documentation for the current schema and deployment options.
|
|
6439
6755
|
|
|
6440
|
-
If the organization does not approve direct egress, keep the host denial.
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6756
|
+
If the organization does not approve direct egress, keep the host denial. The
|
|
6757
|
+
plugin reports one content-free local alert, does not retry, and does not create
|
|
6758
|
+
a handoff automatically. Only when an organization admin explicitly requests a
|
|
6759
|
+
handoff, use \`queue_private_skill_import\` locally or run the bundled
|
|
6760
|
+
\`scripts/queue-private-skill-import.mjs\` helper, then have the admin upload the
|
|
6761
|
+
mode-0600 JSON at **Dashboard > Skills > Import**. The handoff never contains an
|
|
6762
|
+
API key or organization id, never contacts Remembrance, and does not count as
|
|
6763
|
+
submitted until the dashboard returns an import batch receipt.
|
|
6446
6764
|
|
|
6447
6765
|
If Codex still sees \`<your org key>\` after restart, remove stale
|
|
6448
6766
|
\`REMEMBRANCE_API_KEY\` exports from shell profiles such as \`~/.zshrc\` and
|
|
@@ -6779,6 +7097,17 @@ npx skills add dreamarkinc/remembrance-skills --skill remembrancer
|
|
|
6779
7097
|
The entry skill is REST-only and self-contained. The same skill directory can
|
|
6780
7098
|
be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
6781
7099
|
|
|
7100
|
+
## Verified client updates
|
|
7101
|
+
|
|
7102
|
+
Native plugins and local MCP check the credential-free public release manifest
|
|
7103
|
+
at startup and through \`run_connection_doctor\`. The check sends no API key,
|
|
7104
|
+
fails open when the registry is unavailable, and never runs a command returned
|
|
7105
|
+
by the network. When a newer verified release exists, the agent must ask before
|
|
7106
|
+
running the update command bundled with its installed client, then tell the user
|
|
7107
|
+
which Codex, Claude Code, Cursor, OpenClaw, VS Code, OpenCode, or MCP host must
|
|
7108
|
+
be reloaded, reopened, or fully restarted. Set
|
|
7109
|
+
\`REMEMBRANCE_CLIENT_UPDATE_CHECK=0\` to disable only this advisory check.
|
|
7110
|
+
|
|
6782
7111
|
## Validate after setup
|
|
6783
7112
|
|
|
6784
7113
|
1. Start a fresh agent session.
|
|
@@ -6833,10 +7162,12 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
|
6833
7162
|
that should not appear anonymously.
|
|
6834
7163
|
13. With an organization submission key, submit one disposable redacted skill
|
|
6835
7164
|
through propose_private_skill and verify it appears only in that
|
|
6836
|
-
organization's review queue.
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
7165
|
+
organization's review queue. In a separate negative scenario, simulate a
|
|
7166
|
+
host-policy denial and verify the plugin reports the fixed content-free alert
|
|
7167
|
+
once, persists no blocked content, performs no retry, and creates no handoff.
|
|
7168
|
+
Test queue_private_skill_import only as a separate administrator-requested
|
|
7169
|
+
manual handoff, with its local receipt distinguished from a server import
|
|
7170
|
+
receipt.
|
|
6840
7171
|
14. If using local MCP, verify local_signing_identity in get_connection_status.
|
|
6841
7172
|
A missing opaque identity initializes automatically on the first signed
|
|
6842
7173
|
contribution; bootstrap_agent_identity with no arguments is an optional
|
|
@@ -6846,10 +7177,13 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
|
6846
7177
|
|
|
6847
7178
|
- "Plugin installed, but no tools": restart the agent app/session; confirm the
|
|
6848
7179
|
plugin is enabled and contains the runtime-specific manifest. For Codex,
|
|
6849
|
-
launch the installer-provided command
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
7180
|
+
launch the installer-provided command. If Codex shows a hook review, choose
|
|
7181
|
+
**Review hooks** and trust only the listed Remembrance hooks; if it does not,
|
|
7182
|
+
continue because Codex may be reusing an existing valid trust decision.
|
|
7183
|
+
Fully restart Codex, submit one prompt, use one Remembrance tool, complete one
|
|
7184
|
+
turn, and run \`run_connection_doctor\`. If the lifecycle remains incomplete,
|
|
7185
|
+
update or reinstall the plugin and repeat the check. Updates that change
|
|
7186
|
+
hooks show the review screen again.
|
|
6853
7187
|
- "Agent has tools but does not use them": first verify a concrete query receipt,
|
|
6854
7188
|
then test a short contextual follow-up such as "fix these issues". Native
|
|
6855
7189
|
prompt hooks should inject a full-conversation query reminder, and completion
|
|
@@ -9062,19 +9396,19 @@ var toolDefinitions = [
|
|
|
9062
9396
|
),
|
|
9063
9397
|
tool(
|
|
9064
9398
|
"submit_remembrance",
|
|
9065
|
-
"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
|
|
9399
|
+
"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.",
|
|
9066
9400
|
"/api/v1/agent/remembrances",
|
|
9067
9401
|
remembranceToolSchema
|
|
9068
9402
|
),
|
|
9069
9403
|
tool(
|
|
9070
9404
|
"propose_skill_idea",
|
|
9071
|
-
"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,
|
|
9405
|
+
"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.",
|
|
9072
9406
|
"/api/v1/agent/skill-ideas",
|
|
9073
9407
|
skillIdeaRequestSchema
|
|
9074
9408
|
),
|
|
9075
9409
|
tool(
|
|
9076
9410
|
"propose_private_skill",
|
|
9077
|
-
"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,
|
|
9411
|
+
"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.",
|
|
9078
9412
|
"/api/v1/agent/private-skill-ideas",
|
|
9079
9413
|
skillIdeaRequestSchema
|
|
9080
9414
|
),
|
|
@@ -9358,7 +9692,7 @@ function localAgentIdentityPath(env = process.env, homeDirectory = homedir2()) {
|
|
|
9358
9692
|
);
|
|
9359
9693
|
}
|
|
9360
9694
|
function parseStoredIdentity(value) {
|
|
9361
|
-
if (!
|
|
9695
|
+
if (!isRecord2(value)) return null;
|
|
9362
9696
|
if (!supportedProviders.has(value.provider)) {
|
|
9363
9697
|
return null;
|
|
9364
9698
|
}
|
|
@@ -9493,7 +9827,7 @@ function identityStatus(args) {
|
|
|
9493
9827
|
} : null
|
|
9494
9828
|
};
|
|
9495
9829
|
}
|
|
9496
|
-
function
|
|
9830
|
+
function isRecord2(value) {
|
|
9497
9831
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
9498
9832
|
}
|
|
9499
9833
|
|
|
@@ -9507,6 +9841,20 @@ var PLUGIN_HEALTH_COMPONENTS = [
|
|
|
9507
9841
|
];
|
|
9508
9842
|
var PLUGIN_HEALTH_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9509
9843
|
var PLUGIN_HEALTH_MAX_FUTURE_SKEW_MS = 5 * 60 * 1e3;
|
|
9844
|
+
var HOST_POLICY_OBSERVATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
9845
|
+
var HOST_POLICY_OBSERVABLE_SURFACES = /* @__PURE__ */ new Set([
|
|
9846
|
+
"claude_code",
|
|
9847
|
+
"cursor",
|
|
9848
|
+
"openclaw",
|
|
9849
|
+
"opencode"
|
|
9850
|
+
]);
|
|
9851
|
+
var HOST_POLICY_OPERATION_CLASSES2 = /* @__PURE__ */ new Set([
|
|
9852
|
+
"query",
|
|
9853
|
+
"private_contribution",
|
|
9854
|
+
"contribution",
|
|
9855
|
+
"feedback",
|
|
9856
|
+
"other"
|
|
9857
|
+
]);
|
|
9510
9858
|
var nodeFileSystem2 = {
|
|
9511
9859
|
exists: existsSync2,
|
|
9512
9860
|
read: (path, maxBytes = MAX_LOCAL_CONFIG_BYTES) => readSecureLocalText(path, { maxBytes }),
|
|
@@ -9543,7 +9891,7 @@ function resolveApiCredential(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
9543
9891
|
const parsed = JSON.parse(
|
|
9544
9892
|
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9545
9893
|
);
|
|
9546
|
-
if (!
|
|
9894
|
+
if (!isRecord3(parsed)) {
|
|
9547
9895
|
return unusableSharedConfig();
|
|
9548
9896
|
}
|
|
9549
9897
|
if (Object.prototype.hasOwnProperty.call(parsed, "apiKey") && (typeof parsed.apiKey !== "string" || !parsed.apiKey.trim())) {
|
|
@@ -9593,7 +9941,7 @@ function resolveApiConfiguration(env = process.env, fileSystem = nodeFileSystem2
|
|
|
9593
9941
|
const parsed = JSON.parse(
|
|
9594
9942
|
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9595
9943
|
);
|
|
9596
|
-
if (!
|
|
9944
|
+
if (!isRecord3(parsed)) {
|
|
9597
9945
|
return {
|
|
9598
9946
|
baseUrl: DEFAULT_API_URL,
|
|
9599
9947
|
source: "unusable_shared_config",
|
|
@@ -9648,7 +9996,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
9648
9996
|
const parsed = JSON.parse(
|
|
9649
9997
|
fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
|
|
9650
9998
|
);
|
|
9651
|
-
validJson =
|
|
9999
|
+
validJson = isRecord3(parsed);
|
|
9652
10000
|
config = normalizeConfig(parsed);
|
|
9653
10001
|
} catch {
|
|
9654
10002
|
}
|
|
@@ -9663,7 +10011,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
9663
10011
|
};
|
|
9664
10012
|
}
|
|
9665
10013
|
function localConnectionStatus(result, options) {
|
|
9666
|
-
const response =
|
|
10014
|
+
const response = isRecord3(result) ? result : {};
|
|
9667
10015
|
const env = options.env ?? process.env;
|
|
9668
10016
|
const fileSystem = options.fileSystem ?? nodeFileSystem2;
|
|
9669
10017
|
const credential = resolveApiCredential(env, fileSystem);
|
|
@@ -9681,6 +10029,11 @@ function localConnectionStatus(result, options) {
|
|
|
9681
10029
|
apiDestinationSource: options.apiUrlSource,
|
|
9682
10030
|
now: options.now
|
|
9683
10031
|
});
|
|
10032
|
+
const hostPolicy = localHostPolicyStatus({
|
|
10033
|
+
env,
|
|
10034
|
+
fileSystem,
|
|
10035
|
+
now: options.now
|
|
10036
|
+
});
|
|
9684
10037
|
const registryReady = response.ok === true;
|
|
9685
10038
|
const insecureCredential = sharedConfig.api_key_present && sharedConfig.secure_permissions === false;
|
|
9686
10039
|
const unknownCredentialPermissions = sharedConfig.api_key_present && sharedConfig.secure_permissions === null;
|
|
@@ -9701,7 +10054,79 @@ function localConnectionStatus(result, options) {
|
|
|
9701
10054
|
},
|
|
9702
10055
|
registry: response.body ?? (response.error ? { error: String(response.error) } : null),
|
|
9703
10056
|
local_signing_identity: localAgentIdentityStatus(env, fileSystem),
|
|
9704
|
-
plugin_health: pluginHealth
|
|
10057
|
+
plugin_health: { ...pluginHealth, host_policy: hostPolicy }
|
|
10058
|
+
};
|
|
10059
|
+
}
|
|
10060
|
+
function localHostPolicyStatus(options) {
|
|
10061
|
+
const env = options.env ?? process.env;
|
|
10062
|
+
const fileSystem = options.fileSystem ?? nodeFileSystem2;
|
|
10063
|
+
const rawSurface = String(env.REMEMBRANCE_PLUGIN_HOST ?? "").trim().toLowerCase();
|
|
10064
|
+
if (!isPluginHostSurface(rawSurface)) {
|
|
10065
|
+
return {
|
|
10066
|
+
status: "not_observable",
|
|
10067
|
+
observable: false,
|
|
10068
|
+
explanation: "This connection cannot observe a host policy decision made before MCP transport."
|
|
10069
|
+
};
|
|
10070
|
+
}
|
|
10071
|
+
const surface = rawSurface;
|
|
10072
|
+
if (!HOST_POLICY_OBSERVABLE_SURFACES.has(surface)) {
|
|
10073
|
+
return {
|
|
10074
|
+
status: "not_observable",
|
|
10075
|
+
observable: false,
|
|
10076
|
+
explanation: "This host does not expose a reliable failed-tool or permission event to the plugin."
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10079
|
+
const directory = env.REMEMBRANCE_PLUGIN_ALERT_DIR ? String(env.REMEMBRANCE_PLUGIN_ALERT_DIR) : join3(homedir3(), ".cache", "remembrance", "plugin-alerts");
|
|
10080
|
+
const names = fileSystem.list ? (() => {
|
|
10081
|
+
try {
|
|
10082
|
+
return fileSystem.list?.(directory) ?? [];
|
|
10083
|
+
} catch {
|
|
10084
|
+
return [];
|
|
10085
|
+
}
|
|
10086
|
+
})() : [];
|
|
10087
|
+
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
10088
|
+
let latest = null;
|
|
10089
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
10090
|
+
for (const name of names.filter(
|
|
10091
|
+
(value) => new RegExp(`^${surface}\\.[a-f0-9]{24}\\.json$`).test(value)
|
|
10092
|
+
).slice(0, MAX_LOCAL_LIFECYCLE_MARKERS)) {
|
|
10093
|
+
try {
|
|
10094
|
+
const sessionHash = name.split(".")[1];
|
|
10095
|
+
const parsed = JSON.parse(
|
|
10096
|
+
fileSystem.read(
|
|
10097
|
+
join3(directory, name),
|
|
10098
|
+
MAX_LOCAL_LIFECYCLE_MARKER_BYTES
|
|
10099
|
+
)
|
|
10100
|
+
);
|
|
10101
|
+
if (!isRecord3(parsed) || parsed.schema_version !== 1 || parsed.surface !== surface || parsed.session_hash !== sessionHash || !Array.isArray(parsed.observations)) {
|
|
10102
|
+
continue;
|
|
10103
|
+
}
|
|
10104
|
+
for (const candidate of parsed.observations) {
|
|
10105
|
+
if (!isRecord3(candidate)) continue;
|
|
10106
|
+
const seenMs = typeof candidate.last_seen_at === "string" ? Date.parse(candidate.last_seen_at) : Number.NaN;
|
|
10107
|
+
if (Number.isFinite(seenMs) && seenMs <= nowMs + PLUGIN_HEALTH_MAX_FUTURE_SKEW_MS && nowMs - seenMs <= HOST_POLICY_OBSERVATION_MAX_AGE_MS && seenMs > latestMs) {
|
|
10108
|
+
latest = candidate;
|
|
10109
|
+
latestMs = seenMs;
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
} catch {
|
|
10113
|
+
}
|
|
10114
|
+
}
|
|
10115
|
+
if (!latest) {
|
|
10116
|
+
return {
|
|
10117
|
+
status: "no_recent_denial",
|
|
10118
|
+
observable: true,
|
|
10119
|
+
explanation: "No recent host policy denial has been observed. This is not proof that future submissions are permitted."
|
|
10120
|
+
};
|
|
10121
|
+
}
|
|
10122
|
+
return {
|
|
10123
|
+
status: "recent_denial",
|
|
10124
|
+
observable: true,
|
|
10125
|
+
operation_class: HOST_POLICY_OPERATION_CLASSES2.has(
|
|
10126
|
+
String(latest.operation_class ?? "")
|
|
10127
|
+
) ? latest.operation_class : "other",
|
|
10128
|
+
last_denial_at: new Date(latestMs).toISOString(),
|
|
10129
|
+
explanation: "The host recently blocked a Remembrance operation before transport; no content was sent."
|
|
9705
10130
|
};
|
|
9706
10131
|
}
|
|
9707
10132
|
function localPluginLifecycleHealth(options) {
|
|
@@ -9745,7 +10170,7 @@ function localPluginLifecycleHealth(options) {
|
|
|
9745
10170
|
const parsed = JSON.parse(
|
|
9746
10171
|
fileSystem.read(path, MAX_LOCAL_LIFECYCLE_MARKER_BYTES)
|
|
9747
10172
|
);
|
|
9748
|
-
if (!
|
|
10173
|
+
if (!isRecord3(parsed) || parsed.surface !== surface) {
|
|
9749
10174
|
continue;
|
|
9750
10175
|
}
|
|
9751
10176
|
const candidateMs = typeof parsed.last_seen_at === "string" ? Date.parse(parsed.last_seen_at) : Number.NEGATIVE_INFINITY;
|
|
@@ -9767,7 +10192,7 @@ function localPluginLifecycleHealth(options) {
|
|
|
9767
10192
|
]
|
|
9768
10193
|
};
|
|
9769
10194
|
}
|
|
9770
|
-
const componentRecord =
|
|
10195
|
+
const componentRecord = isRecord3(marker.components) ? marker.components : {};
|
|
9771
10196
|
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
9772
10197
|
const sessionStartMs = validLifecycleTimestamp(
|
|
9773
10198
|
componentRecord.session_start,
|
|
@@ -9920,7 +10345,7 @@ function lifecycleMarkerPaths(surface, healthDir, fileSystem) {
|
|
|
9920
10345
|
return [...new Set(sessionPaths)].slice(0, MAX_LOCAL_LIFECYCLE_MARKERS);
|
|
9921
10346
|
}
|
|
9922
10347
|
function normalizeConfig(value) {
|
|
9923
|
-
if (!
|
|
10348
|
+
if (!isRecord3(value)) return {};
|
|
9924
10349
|
const apiKey = typeof value.apiKey === "string" ? value.apiKey.trim() : "";
|
|
9925
10350
|
const apiUrl = typeof value.apiUrl === "string" ? value.apiUrl.trim() : "";
|
|
9926
10351
|
return {
|
|
@@ -10011,16 +10436,67 @@ function validLifecycleTimestamp(value, nowMs) {
|
|
|
10011
10436
|
}
|
|
10012
10437
|
return parsed;
|
|
10013
10438
|
}
|
|
10014
|
-
function
|
|
10439
|
+
function isRecord3(value) {
|
|
10015
10440
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
10016
10441
|
}
|
|
10017
10442
|
|
|
10443
|
+
// src/client-update.ts
|
|
10444
|
+
var MAX_MANIFEST_BYTES = 16 * 1024;
|
|
10445
|
+
var DEFAULT_TIMEOUT_MS = 1200;
|
|
10446
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10447
|
+
var cache = /* @__PURE__ */ new Map();
|
|
10448
|
+
async function checkClientUpdate(input) {
|
|
10449
|
+
const env = input.env ?? process.env;
|
|
10450
|
+
if (/^(0|false|no)$/i.test(env.REMEMBRANCE_CLIENT_UPDATE_CHECK ?? "")) {
|
|
10451
|
+
return null;
|
|
10452
|
+
}
|
|
10453
|
+
if (!parseStableClientVersion(input.currentVersion)) return null;
|
|
10454
|
+
const now = input.now ?? Date.now();
|
|
10455
|
+
const url = `${input.apiBase.replace(/\/+$/, "")}/.well-known/remembrance-client-release.json`;
|
|
10456
|
+
const cached = cache.get(url);
|
|
10457
|
+
let manifest;
|
|
10458
|
+
if (cached && cached.expiresAt > now) {
|
|
10459
|
+
manifest = cached.value;
|
|
10460
|
+
} else {
|
|
10461
|
+
const controller = new AbortController();
|
|
10462
|
+
const timer = setTimeout(
|
|
10463
|
+
() => controller.abort(),
|
|
10464
|
+
input.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
10465
|
+
);
|
|
10466
|
+
try {
|
|
10467
|
+
const response = await (input.fetchImpl ?? fetch)(url, {
|
|
10468
|
+
headers: { accept: "application/json" },
|
|
10469
|
+
signal: controller.signal
|
|
10470
|
+
});
|
|
10471
|
+
if (!response.ok) return null;
|
|
10472
|
+
const text = await response.text();
|
|
10473
|
+
if (Buffer.byteLength(text, "utf8") > MAX_MANIFEST_BYTES) return null;
|
|
10474
|
+
try {
|
|
10475
|
+
manifest = JSON.parse(text);
|
|
10476
|
+
} catch {
|
|
10477
|
+
return null;
|
|
10478
|
+
}
|
|
10479
|
+
cache.set(url, { value: manifest, expiresAt: now + CACHE_TTL_MS });
|
|
10480
|
+
} catch {
|
|
10481
|
+
return null;
|
|
10482
|
+
} finally {
|
|
10483
|
+
clearTimeout(timer);
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
const status = resolveClientUpdateStatus({
|
|
10487
|
+
currentVersion: input.currentVersion,
|
|
10488
|
+
manifest,
|
|
10489
|
+
surface: input.surface
|
|
10490
|
+
});
|
|
10491
|
+
return status.status === "unavailable" ? null : status;
|
|
10492
|
+
}
|
|
10493
|
+
|
|
10018
10494
|
// src/server.ts
|
|
10019
10495
|
var apiConfiguration = resolveApiConfiguration();
|
|
10020
10496
|
var apiBase = apiConfiguration.baseUrl;
|
|
10021
10497
|
var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
10022
10498
|
var DOCTOR_PROBE_TIMEOUT_MS = 7500;
|
|
10023
|
-
var SERVER_VERSION = true ? "0.1.
|
|
10499
|
+
var SERVER_VERSION = true ? "0.1.55" : "0.0.0-dev";
|
|
10024
10500
|
var tools = toolDefinitions;
|
|
10025
10501
|
var doctorCliRequested = process.argv[2] === "doctor";
|
|
10026
10502
|
var inputBuffer = Buffer.alloc(0);
|
|
@@ -10116,6 +10592,7 @@ async function handleRequest(request) {
|
|
|
10116
10592
|
}
|
|
10117
10593
|
async function dispatchJsonRpcRequest(request) {
|
|
10118
10594
|
if (request.method === "initialize") {
|
|
10595
|
+
const clientUpdate = await currentClientUpdate();
|
|
10119
10596
|
return {
|
|
10120
10597
|
id: request.id,
|
|
10121
10598
|
result: {
|
|
@@ -10125,7 +10602,9 @@ async function dispatchJsonRpcRequest(request) {
|
|
|
10125
10602
|
name: "@remembrance-ai/mcp-server",
|
|
10126
10603
|
version: SERVER_VERSION
|
|
10127
10604
|
},
|
|
10128
|
-
instructions: REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
|
|
10605
|
+
instructions: clientUpdate?.status === "update_available" ? `${REMEMBRANCE_MCP_SERVER_INSTRUCTIONS}
|
|
10606
|
+
|
|
10607
|
+
${clientUpdate.notice}` : REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
|
|
10129
10608
|
}
|
|
10130
10609
|
};
|
|
10131
10610
|
}
|
|
@@ -10286,7 +10765,7 @@ function apiConfigurationErrorMessage(issue, setting) {
|
|
|
10286
10765
|
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.`;
|
|
10287
10766
|
}
|
|
10288
10767
|
function clientHealthReportFromConnectionStatus(status) {
|
|
10289
|
-
const health =
|
|
10768
|
+
const health = isRecord4(status.plugin_health) ? status.plugin_health : null;
|
|
10290
10769
|
if (!health || health.expected !== true || health.status !== "degraded") {
|
|
10291
10770
|
return null;
|
|
10292
10771
|
}
|
|
@@ -10296,9 +10775,9 @@ function clientHealthReportFromConnectionStatus(status) {
|
|
|
10296
10775
|
}
|
|
10297
10776
|
const host = agentHostBySurface(surface);
|
|
10298
10777
|
if (!host || !host.plugin) return null;
|
|
10299
|
-
const components =
|
|
10300
|
-
const observed = (name) =>
|
|
10301
|
-
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) =>
|
|
10778
|
+
const components = isRecord4(health.components) ? health.components : {};
|
|
10779
|
+
const observed = (name) => isRecord4(components[name]) && components[name]?.observed === true ? "active" : "not_observed";
|
|
10780
|
+
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) => isRecord4(issue) ? issue.code : null).filter((code) => typeof code === "string") : [];
|
|
10302
10781
|
const allowedIssueCodes = /* @__PURE__ */ new Set([
|
|
10303
10782
|
"native_hooks_not_observed",
|
|
10304
10783
|
"session_start_not_observed",
|
|
@@ -10647,7 +11126,8 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
10647
11126
|
}
|
|
10648
11127
|
} : parsed;
|
|
10649
11128
|
const headers = {
|
|
10650
|
-
"content-type": "application/json"
|
|
11129
|
+
"content-type": "application/json",
|
|
11130
|
+
"user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
|
|
10651
11131
|
};
|
|
10652
11132
|
const credential = resolveApiCredential();
|
|
10653
11133
|
const configurationError = remoteConfigurationError(
|
|
@@ -10731,9 +11211,10 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
|
|
|
10731
11211
|
error: localConfigurationError
|
|
10732
11212
|
}) : callDoctorReadProbe(connectionDefinition, {});
|
|
10733
11213
|
const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }) : Promise.resolve(null);
|
|
10734
|
-
const [connectionResult, catalogResult] = await Promise.all([
|
|
11214
|
+
const [connectionResult, catalogResult, clientUpdate] = await Promise.all([
|
|
10735
11215
|
connectionPromise,
|
|
10736
|
-
catalogPromise
|
|
11216
|
+
catalogPromise,
|
|
11217
|
+
currentClientUpdate()
|
|
10737
11218
|
]);
|
|
10738
11219
|
const connectionStatus = localConnectionStatus(connectionResult, {
|
|
10739
11220
|
apiBase,
|
|
@@ -10746,8 +11227,8 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
|
|
|
10746
11227
|
item_count: null
|
|
10747
11228
|
};
|
|
10748
11229
|
if (options.active_read_probe && !localConfigurationError) {
|
|
10749
|
-
const resultRecord =
|
|
10750
|
-
const body =
|
|
11230
|
+
const resultRecord = isRecord4(catalogResult) ? catalogResult : {};
|
|
11231
|
+
const body = isRecord4(resultRecord.body) ? resultRecord.body : {};
|
|
10751
11232
|
const skills = Array.isArray(body.skills) ? body.skills : null;
|
|
10752
11233
|
activeReadProbe.succeeded = resultRecord.ok === true && skills !== null;
|
|
10753
11234
|
activeReadProbe.item_count = skills ? skills.length : null;
|
|
@@ -10759,7 +11240,17 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
|
|
|
10759
11240
|
transport: execution === "standalone_cli" ? "standalone_cli" : "local_stdio_mcp",
|
|
10760
11241
|
host_registration_observed: execution === "mcp_tool",
|
|
10761
11242
|
active_read_probe: activeReadProbe,
|
|
10762
|
-
check_organization_write_authorization: options.check_organization_write_authorization
|
|
11243
|
+
check_organization_write_authorization: options.check_organization_write_authorization,
|
|
11244
|
+
client_update: clientUpdate
|
|
11245
|
+
});
|
|
11246
|
+
}
|
|
11247
|
+
async function currentClientUpdate() {
|
|
11248
|
+
const configuredSurface = process.env.REMEMBRANCE_PLUGIN_HOST?.trim().toLowerCase();
|
|
11249
|
+
const surface = configuredSurface && isPluginHostSurface(configuredSurface) ? configuredSurface : "mcp";
|
|
11250
|
+
return checkClientUpdate({
|
|
11251
|
+
apiBase,
|
|
11252
|
+
currentVersion: SERVER_VERSION,
|
|
11253
|
+
surface
|
|
10763
11254
|
});
|
|
10764
11255
|
}
|
|
10765
11256
|
async function callDoctorReadProbe(definition, payload) {
|
|
@@ -10773,18 +11264,24 @@ async function callDoctorReadProbe(definition, payload) {
|
|
|
10773
11264
|
}) : first;
|
|
10774
11265
|
}
|
|
10775
11266
|
function isTransientDoctorProbeFailure(result) {
|
|
10776
|
-
if (!
|
|
11267
|
+
if (!isRecord4(result) || result.ok === true) return false;
|
|
10777
11268
|
const status = typeof result.status === "number" ? result.status : 0;
|
|
10778
11269
|
return status === 0 || status === 408 || status === 425 || status >= 500;
|
|
10779
11270
|
}
|
|
10780
11271
|
function formatConnectionDoctorReport(report) {
|
|
11272
|
+
const clientUpdate = report.client_update ?? {
|
|
11273
|
+
status: "unavailable",
|
|
11274
|
+
latest_version: null
|
|
11275
|
+
};
|
|
10781
11276
|
const lines = [
|
|
10782
11277
|
`Remembrance connection doctor: ${report.status.toUpperCase()}`,
|
|
10783
11278
|
`Transport: ${report.transport}`,
|
|
10784
11279
|
`Scope: ${report.scope}`,
|
|
10785
11280
|
`Destination: ${report.destination.kind}`,
|
|
10786
11281
|
`Query ready: ${report.safe_to_query ? "yes" : "no"}`,
|
|
10787
|
-
`
|
|
11282
|
+
`Registry submissions: ${report.registry_submission_authorized ? "authorized" : "not authorized"}`,
|
|
11283
|
+
`Host policy observation: ${report.host_policy?.status ?? "not_observable"}`,
|
|
11284
|
+
`Client update: ${clientUpdate.status}${clientUpdate.latest_version ? ` (published ${clientUpdate.latest_version})` : ""}`,
|
|
10788
11285
|
`Signed contributions: ${report.signed_contributions_ready === null ? "not observable" : report.signed_contributions_ready ? "ready" : "not ready"}`,
|
|
10789
11286
|
""
|
|
10790
11287
|
];
|
|
@@ -11026,7 +11523,7 @@ function mustFindTool(name) {
|
|
|
11026
11523
|
}
|
|
11027
11524
|
return tool2;
|
|
11028
11525
|
}
|
|
11029
|
-
function
|
|
11526
|
+
function isRecord4(value) {
|
|
11030
11527
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
11031
11528
|
}
|
|
11032
11529
|
function identityPath(explicit) {
|