@remembrance-ai/opencode-plugin 0.1.53 → 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.
@@ -90,7 +90,14 @@ var AGENT_HOSTS = [
90
90
  public_mirror: true,
91
91
  client_release: true,
92
92
  release_smoke: true,
93
- release_gate: "host",
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
- release_gate: "host",
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
- release_gate: "source_contract",
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
- release_gate: "host",
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
- release_gate: "source_contract",
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
- release_gate: "host",
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
- release_gate: "transport",
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
- release_gate: "transport",
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,6 +4696,13 @@ 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
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.`;
@@ -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
- organization_write_authorized: scope === "organization" && submitAuthorized,
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. Do not retry private repository content through another transport or claim submission. If the blocked contribution is an organization skill, use the local-only queue_private_skill_import tool or bundled handoff script, then wait for an organization admin import receipt. 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.";
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.14",
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.14",
6311
+ version: "0.1.17",
6001
6312
  status: "active",
6002
6313
  visibility: "public",
6003
6314
  providers: ["codex", "claude", "cursor", "openclaw", "generic"],
@@ -6406,7 +6717,8 @@ The MCP annotation for \`propose_private_skill\` is non-read-only and
6406
6717
  closed-world: it makes a network request, but can change only the authenticated
6407
6718
  organization's private review queue and cannot change publicly visible internet
6408
6719
  state. Closed-world does not mean zero-network. Only
6409
- \`queue_private_skill_import\` is the local, zero-network fallback.
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.
6410
6722
 
6411
6723
  Example text to merge into the tenant-specific guardian policy:
6412
6724
 
@@ -6441,12 +6753,14 @@ over a user's local \`[auto_review].policy\`. See the official Codex
6441
6753
  [managed configuration](https://learn.chatgpt.com/docs/enterprise/managed-configuration#configure-automatic-review-policy)
6442
6754
  documentation for the current schema and deployment options.
6443
6755
 
6444
- If the organization does not approve direct egress, keep the host denial. Use
6445
- \`queue_private_skill_import\` locally, or run the bundled
6446
- \`scripts/queue-private-skill-import.mjs\` helper, then have an organization
6447
- admin upload the mode-0600 JSON at **Dashboard > Skills > Import**. The handoff
6448
- never contains an API key or organization id, never contacts Remembrance, and
6449
- does not count as submitted until the dashboard returns an import batch receipt.
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.
6450
6764
 
6451
6765
  If Codex still sees \`<your org key>\` after restart, remove stale
6452
6766
  \`REMEMBRANCE_API_KEY\` exports from shell profiles such as \`~/.zshrc\` and
@@ -6783,6 +7097,17 @@ npx skills add dreamarkinc/remembrance-skills --skill remembrancer
6783
7097
  The entry skill is REST-only and self-contained. The same skill directory can
6784
7098
  be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
6785
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
+
6786
7111
  ## Validate after setup
6787
7112
 
6788
7113
  1. Start a fresh agent session.
@@ -6837,10 +7162,12 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
6837
7162
  that should not appear anonymously.
6838
7163
  13. With an organization submission key, submit one disposable redacted skill
6839
7164
  through propose_private_skill and verify it appears only in that
6840
- organization's review queue. If host policy denies the export, run
6841
- queue_private_skill_import instead, upload its JSON in the dashboard, and
6842
- verify the agent reports a local queue receipt separately from the server
6843
- import receipt.
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.
6844
7171
  14. If using local MCP, verify local_signing_identity in get_connection_status.
6845
7172
  A missing opaque identity initializes automatically on the first signed
6846
7173
  contribution; bootstrap_agent_identity with no arguments is an optional
@@ -9069,19 +9396,19 @@ var toolDefinitions = [
9069
9396
  ),
9070
9397
  tool(
9071
9398
  "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; if the host blocks that export, use the local queue_private_skill_import fallback instead of retrying through another transport.",
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.",
9073
9400
  "/api/v1/agent/remembrances",
9074
9401
  remembranceToolSchema
9075
9402
  ),
9076
9403
  tool(
9077
9404
  "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, queue_private_skill_import locally and do not claim submission.",
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.",
9079
9406
  "/api/v1/agent/skill-ideas",
9080
9407
  skillIdeaRequestSchema
9081
9408
  ),
9082
9409
  tool(
9083
9410
  "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, queue_private_skill_import locally instead; never retry through another transport or claim submission without a receipt.",
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.",
9085
9412
  "/api/v1/agent/private-skill-ideas",
9086
9413
  skillIdeaRequestSchema
9087
9414
  ),
@@ -9365,7 +9692,7 @@ function localAgentIdentityPath(env = process.env, homeDirectory = homedir2()) {
9365
9692
  );
9366
9693
  }
9367
9694
  function parseStoredIdentity(value) {
9368
- if (!isRecord(value)) return null;
9695
+ if (!isRecord2(value)) return null;
9369
9696
  if (!supportedProviders.has(value.provider)) {
9370
9697
  return null;
9371
9698
  }
@@ -9500,7 +9827,7 @@ function identityStatus(args) {
9500
9827
  } : null
9501
9828
  };
9502
9829
  }
9503
- function isRecord(value) {
9830
+ function isRecord2(value) {
9504
9831
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9505
9832
  }
9506
9833
 
@@ -9514,6 +9841,20 @@ var PLUGIN_HEALTH_COMPONENTS = [
9514
9841
  ];
9515
9842
  var PLUGIN_HEALTH_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
9516
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
+ ]);
9517
9858
  var nodeFileSystem2 = {
9518
9859
  exists: existsSync2,
9519
9860
  read: (path, maxBytes = MAX_LOCAL_CONFIG_BYTES) => readSecureLocalText(path, { maxBytes }),
@@ -9550,7 +9891,7 @@ function resolveApiCredential(env = process.env, fileSystem = nodeFileSystem2) {
9550
9891
  const parsed = JSON.parse(
9551
9892
  fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
9552
9893
  );
9553
- if (!isRecord2(parsed)) {
9894
+ if (!isRecord3(parsed)) {
9554
9895
  return unusableSharedConfig();
9555
9896
  }
9556
9897
  if (Object.prototype.hasOwnProperty.call(parsed, "apiKey") && (typeof parsed.apiKey !== "string" || !parsed.apiKey.trim())) {
@@ -9600,7 +9941,7 @@ function resolveApiConfiguration(env = process.env, fileSystem = nodeFileSystem2
9600
9941
  const parsed = JSON.parse(
9601
9942
  fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
9602
9943
  );
9603
- if (!isRecord2(parsed)) {
9944
+ if (!isRecord3(parsed)) {
9604
9945
  return {
9605
9946
  baseUrl: DEFAULT_API_URL,
9606
9947
  source: "unusable_shared_config",
@@ -9655,7 +9996,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
9655
9996
  const parsed = JSON.parse(
9656
9997
  fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
9657
9998
  );
9658
- validJson = isRecord2(parsed);
9999
+ validJson = isRecord3(parsed);
9659
10000
  config = normalizeConfig(parsed);
9660
10001
  } catch {
9661
10002
  }
@@ -9670,7 +10011,7 @@ function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
9670
10011
  };
9671
10012
  }
9672
10013
  function localConnectionStatus(result, options) {
9673
- const response = isRecord2(result) ? result : {};
10014
+ const response = isRecord3(result) ? result : {};
9674
10015
  const env = options.env ?? process.env;
9675
10016
  const fileSystem = options.fileSystem ?? nodeFileSystem2;
9676
10017
  const credential = resolveApiCredential(env, fileSystem);
@@ -9688,6 +10029,11 @@ function localConnectionStatus(result, options) {
9688
10029
  apiDestinationSource: options.apiUrlSource,
9689
10030
  now: options.now
9690
10031
  });
10032
+ const hostPolicy = localHostPolicyStatus({
10033
+ env,
10034
+ fileSystem,
10035
+ now: options.now
10036
+ });
9691
10037
  const registryReady = response.ok === true;
9692
10038
  const insecureCredential = sharedConfig.api_key_present && sharedConfig.secure_permissions === false;
9693
10039
  const unknownCredentialPermissions = sharedConfig.api_key_present && sharedConfig.secure_permissions === null;
@@ -9708,7 +10054,79 @@ function localConnectionStatus(result, options) {
9708
10054
  },
9709
10055
  registry: response.body ?? (response.error ? { error: String(response.error) } : null),
9710
10056
  local_signing_identity: localAgentIdentityStatus(env, fileSystem),
9711
- 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."
9712
10130
  };
9713
10131
  }
9714
10132
  function localPluginLifecycleHealth(options) {
@@ -9752,7 +10170,7 @@ function localPluginLifecycleHealth(options) {
9752
10170
  const parsed = JSON.parse(
9753
10171
  fileSystem.read(path, MAX_LOCAL_LIFECYCLE_MARKER_BYTES)
9754
10172
  );
9755
- if (!isRecord2(parsed) || parsed.surface !== surface) {
10173
+ if (!isRecord3(parsed) || parsed.surface !== surface) {
9756
10174
  continue;
9757
10175
  }
9758
10176
  const candidateMs = typeof parsed.last_seen_at === "string" ? Date.parse(parsed.last_seen_at) : Number.NEGATIVE_INFINITY;
@@ -9774,7 +10192,7 @@ function localPluginLifecycleHealth(options) {
9774
10192
  ]
9775
10193
  };
9776
10194
  }
9777
- const componentRecord = isRecord2(marker.components) ? marker.components : {};
10195
+ const componentRecord = isRecord3(marker.components) ? marker.components : {};
9778
10196
  const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
9779
10197
  const sessionStartMs = validLifecycleTimestamp(
9780
10198
  componentRecord.session_start,
@@ -9927,7 +10345,7 @@ function lifecycleMarkerPaths(surface, healthDir, fileSystem) {
9927
10345
  return [...new Set(sessionPaths)].slice(0, MAX_LOCAL_LIFECYCLE_MARKERS);
9928
10346
  }
9929
10347
  function normalizeConfig(value) {
9930
- if (!isRecord2(value)) return {};
10348
+ if (!isRecord3(value)) return {};
9931
10349
  const apiKey = typeof value.apiKey === "string" ? value.apiKey.trim() : "";
9932
10350
  const apiUrl = typeof value.apiUrl === "string" ? value.apiUrl.trim() : "";
9933
10351
  return {
@@ -10018,16 +10436,67 @@ function validLifecycleTimestamp(value, nowMs) {
10018
10436
  }
10019
10437
  return parsed;
10020
10438
  }
10021
- function isRecord2(value) {
10439
+ function isRecord3(value) {
10022
10440
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10023
10441
  }
10024
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
+
10025
10494
  // src/server.ts
10026
10495
  var apiConfiguration = resolveApiConfiguration();
10027
10496
  var apiBase = apiConfiguration.baseUrl;
10028
10497
  var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
10029
10498
  var DOCTOR_PROBE_TIMEOUT_MS = 7500;
10030
- var SERVER_VERSION = true ? "0.1.53" : "0.0.0-dev";
10499
+ var SERVER_VERSION = true ? "0.1.55" : "0.0.0-dev";
10031
10500
  var tools = toolDefinitions;
10032
10501
  var doctorCliRequested = process.argv[2] === "doctor";
10033
10502
  var inputBuffer = Buffer.alloc(0);
@@ -10123,6 +10592,7 @@ async function handleRequest(request) {
10123
10592
  }
10124
10593
  async function dispatchJsonRpcRequest(request) {
10125
10594
  if (request.method === "initialize") {
10595
+ const clientUpdate = await currentClientUpdate();
10126
10596
  return {
10127
10597
  id: request.id,
10128
10598
  result: {
@@ -10132,7 +10602,9 @@ async function dispatchJsonRpcRequest(request) {
10132
10602
  name: "@remembrance-ai/mcp-server",
10133
10603
  version: SERVER_VERSION
10134
10604
  },
10135
- instructions: REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
10605
+ instructions: clientUpdate?.status === "update_available" ? `${REMEMBRANCE_MCP_SERVER_INSTRUCTIONS}
10606
+
10607
+ ${clientUpdate.notice}` : REMEMBRANCE_MCP_SERVER_INSTRUCTIONS
10136
10608
  }
10137
10609
  };
10138
10610
  }
@@ -10293,7 +10765,7 @@ function apiConfigurationErrorMessage(issue, setting) {
10293
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.`;
10294
10766
  }
10295
10767
  function clientHealthReportFromConnectionStatus(status) {
10296
- const health = isRecord3(status.plugin_health) ? status.plugin_health : null;
10768
+ const health = isRecord4(status.plugin_health) ? status.plugin_health : null;
10297
10769
  if (!health || health.expected !== true || health.status !== "degraded") {
10298
10770
  return null;
10299
10771
  }
@@ -10303,9 +10775,9 @@ function clientHealthReportFromConnectionStatus(status) {
10303
10775
  }
10304
10776
  const host = agentHostBySurface(surface);
10305
10777
  if (!host || !host.plugin) return null;
10306
- const components = isRecord3(health.components) ? health.components : {};
10307
- const observed = (name) => isRecord3(components[name]) && components[name]?.observed === true ? "active" : "not_observed";
10308
- const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) => isRecord3(issue) ? issue.code : null).filter((code) => typeof code === "string") : [];
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") : [];
10309
10781
  const allowedIssueCodes = /* @__PURE__ */ new Set([
10310
10782
  "native_hooks_not_observed",
10311
10783
  "session_start_not_observed",
@@ -10654,7 +11126,8 @@ async function callRemembrance(definition, rawArguments, options = {}) {
10654
11126
  }
10655
11127
  } : parsed;
10656
11128
  const headers = {
10657
- "content-type": "application/json"
11129
+ "content-type": "application/json",
11130
+ "user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
10658
11131
  };
10659
11132
  const credential = resolveApiCredential();
10660
11133
  const configurationError = remoteConfigurationError(
@@ -10738,9 +11211,10 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
10738
11211
  error: localConfigurationError
10739
11212
  }) : callDoctorReadProbe(connectionDefinition, {});
10740
11213
  const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }) : Promise.resolve(null);
10741
- const [connectionResult, catalogResult] = await Promise.all([
11214
+ const [connectionResult, catalogResult, clientUpdate] = await Promise.all([
10742
11215
  connectionPromise,
10743
- catalogPromise
11216
+ catalogPromise,
11217
+ currentClientUpdate()
10744
11218
  ]);
10745
11219
  const connectionStatus = localConnectionStatus(connectionResult, {
10746
11220
  apiBase,
@@ -10753,8 +11227,8 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
10753
11227
  item_count: null
10754
11228
  };
10755
11229
  if (options.active_read_probe && !localConfigurationError) {
10756
- const resultRecord = isRecord3(catalogResult) ? catalogResult : {};
10757
- const body = isRecord3(resultRecord.body) ? resultRecord.body : {};
11230
+ const resultRecord = isRecord4(catalogResult) ? catalogResult : {};
11231
+ const body = isRecord4(resultRecord.body) ? resultRecord.body : {};
10758
11232
  const skills = Array.isArray(body.skills) ? body.skills : null;
10759
11233
  activeReadProbe.succeeded = resultRecord.ok === true && skills !== null;
10760
11234
  activeReadProbe.item_count = skills ? skills.length : null;
@@ -10766,7 +11240,17 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
10766
11240
  transport: execution === "standalone_cli" ? "standalone_cli" : "local_stdio_mcp",
10767
11241
  host_registration_observed: execution === "mcp_tool",
10768
11242
  active_read_probe: activeReadProbe,
10769
- 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
10770
11254
  });
10771
11255
  }
10772
11256
  async function callDoctorReadProbe(definition, payload) {
@@ -10780,18 +11264,24 @@ async function callDoctorReadProbe(definition, payload) {
10780
11264
  }) : first;
10781
11265
  }
10782
11266
  function isTransientDoctorProbeFailure(result) {
10783
- if (!isRecord3(result) || result.ok === true) return false;
11267
+ if (!isRecord4(result) || result.ok === true) return false;
10784
11268
  const status = typeof result.status === "number" ? result.status : 0;
10785
11269
  return status === 0 || status === 408 || status === 425 || status >= 500;
10786
11270
  }
10787
11271
  function formatConnectionDoctorReport(report) {
11272
+ const clientUpdate = report.client_update ?? {
11273
+ status: "unavailable",
11274
+ latest_version: null
11275
+ };
10788
11276
  const lines = [
10789
11277
  `Remembrance connection doctor: ${report.status.toUpperCase()}`,
10790
11278
  `Transport: ${report.transport}`,
10791
11279
  `Scope: ${report.scope}`,
10792
11280
  `Destination: ${report.destination.kind}`,
10793
11281
  `Query ready: ${report.safe_to_query ? "yes" : "no"}`,
10794
- `Organization submissions: ${report.organization_write_authorized ? "authorized" : "not authorized"}`,
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})` : ""}`,
10795
11285
  `Signed contributions: ${report.signed_contributions_ready === null ? "not observable" : report.signed_contributions_ready ? "ready" : "not ready"}`,
10796
11286
  ""
10797
11287
  ];
@@ -11033,7 +11523,7 @@ function mustFindTool(name) {
11033
11523
  }
11034
11524
  return tool2;
11035
11525
  }
11036
- function isRecord3(value) {
11526
+ function isRecord4(value) {
11037
11527
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11038
11528
  }
11039
11529
  function identityPath(explicit) {