@remembrance-ai/opencode-plugin 0.1.55 → 0.1.62

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.
@@ -38,11 +38,13 @@ function defaultAgentKeyIdForPublicKey(publicKey) {
38
38
  }
39
39
  function buildAgentKeyRegistrationSigningPayload(args) {
40
40
  const signedAt = args.signedAt instanceof Date ? args.signedAt.toISOString() : args.signedAt;
41
+ const ownerBinding = args.ownerBinding?.trim() || null;
41
42
  return canonicalJson({
42
- version: "v1",
43
+ version: ownerBinding ? "v2" : "v1",
43
44
  purpose: "remembrance-agent-key-registration",
44
45
  provider: args.provider,
45
46
  key_id: args.keyId ?? defaultAgentKeyIdForPublicKey(args.publicKey),
47
+ ...ownerBinding ? { owner_binding: ownerBinding } : {},
46
48
  public_key_hash: hashValue(args.publicKey),
47
49
  subject: args.subject ?? null,
48
50
  signed_at: signedAt
@@ -5120,6 +5122,299 @@ function stringValue(value) {
5120
5122
  return typeof value === "string" && value ? value : null;
5121
5123
  }
5122
5124
 
5125
+ // ../core/src/agent-preferences.ts
5126
+ var MAX_EFFECTIVE_PREFERENCES = 32;
5127
+ var MAX_SKILL_PREFERENCE_TRAITS = 40;
5128
+ var preferenceIdentifierSchema = external_exports.string().trim().toLowerCase().min(1).max(96).regex(/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/);
5129
+ var preferenceKeySchema = preferenceIdentifierSchema;
5130
+ var preferenceValueSchema = preferenceIdentifierSchema;
5131
+ var preferenceEffectSchema = external_exports.enum([
5132
+ "presentation",
5133
+ "workflow",
5134
+ "strategy_selection"
5135
+ ]);
5136
+ var preferenceStrengthSchema = external_exports.enum(["prefer", "avoid"]);
5137
+ var BUILT_IN_PREFERENCE_DEFINITIONS = {
5138
+ comment_density: {
5139
+ label: "Comment density",
5140
+ effect: "presentation",
5141
+ values: {
5142
+ sparse: "Use few comments and reserve them for decisions that code cannot explain.",
5143
+ balanced: "Use comments selectively for intent and non-obvious behavior.",
5144
+ detailed: "Use detailed comments where they improve maintainability."
5145
+ }
5146
+ },
5147
+ comment_focus: {
5148
+ label: "Comment focus",
5149
+ effect: "presentation",
5150
+ values: {
5151
+ intent_only: "Comment intent and tradeoffs rather than restating code.",
5152
+ tricky_logic: "Prioritize comments around tricky or non-obvious logic.",
5153
+ api_contracts: "Prioritize comments that clarify API and data contracts.",
5154
+ comprehensive: "Comment intent, tricky logic, and important contracts comprehensively."
5155
+ }
5156
+ },
5157
+ explanation_depth: {
5158
+ label: "Explanation depth",
5159
+ effect: "presentation",
5160
+ values: {
5161
+ concise: "Keep explanations concise and focused on decisions and outcomes.",
5162
+ balanced: "Balance concise conclusions with enough rationale to verify the work.",
5163
+ detailed: "Provide detailed rationale, tradeoffs, and verification context."
5164
+ }
5165
+ },
5166
+ output_organization: {
5167
+ label: "Output organization",
5168
+ effect: "presentation",
5169
+ values: {
5170
+ compact: "Use a compact response with minimal structural overhead.",
5171
+ structured: "Use clear sections when they improve scanability.",
5172
+ step_by_step: "Present the work as an ordered sequence of steps."
5173
+ }
5174
+ }
5175
+ };
5176
+ var PREFERENCE_KEYS = Object.keys(
5177
+ BUILT_IN_PREFERENCE_DEFINITIONS
5178
+ );
5179
+ var PREFERENCE_VALUE_OPTIONS = Object.fromEntries(
5180
+ Object.entries(BUILT_IN_PREFERENCE_DEFINITIONS).map(([key, definition]) => [
5181
+ key,
5182
+ Object.keys(definition.values)
5183
+ ])
5184
+ );
5185
+ var preferenceSettingBaseSchema = external_exports.object({
5186
+ key: preferenceKeySchema,
5187
+ value: preferenceValueSchema,
5188
+ // Built-in definitions can omit these fields. Extensible definitions carry
5189
+ // their complete bounded meaning with the observation so no hot-path lookup
5190
+ // or code release is needed to understand a new preference type.
5191
+ label: external_exports.string().trim().min(1).max(96).optional(),
5192
+ behavior: external_exports.string().trim().min(1).max(320).optional(),
5193
+ effect: preferenceEffectSchema.optional(),
5194
+ strength: preferenceStrengthSchema.optional(),
5195
+ definition_version: external_exports.number().int().min(1).max(1e6).optional()
5196
+ }).strict();
5197
+ var preferenceSettingSchema = preferenceSettingBaseSchema.superRefine(
5198
+ (setting, ctx) => {
5199
+ const builtIn = builtInPreferenceDefinition(setting.key);
5200
+ if (builtIn) {
5201
+ const builtInBehavior = builtIn.values[setting.value];
5202
+ if (!builtInBehavior) {
5203
+ ctx.addIssue({
5204
+ code: external_exports.ZodIssueCode.custom,
5205
+ path: ["value"],
5206
+ message: `Unsupported ${setting.key} value`
5207
+ });
5208
+ }
5209
+ if (setting.label && setting.label !== builtIn.label) {
5210
+ ctx.addIssue({
5211
+ code: external_exports.ZodIssueCode.custom,
5212
+ path: ["label"],
5213
+ message: `${setting.key} uses its canonical label`
5214
+ });
5215
+ }
5216
+ if (setting.behavior && setting.behavior !== builtInBehavior) {
5217
+ ctx.addIssue({
5218
+ code: external_exports.ZodIssueCode.custom,
5219
+ path: ["behavior"],
5220
+ message: `${setting.key} uses the canonical behavior for ${setting.value}`
5221
+ });
5222
+ }
5223
+ if (setting.effect && setting.effect !== builtIn.effect) {
5224
+ ctx.addIssue({
5225
+ code: external_exports.ZodIssueCode.custom,
5226
+ path: ["effect"],
5227
+ message: `${setting.key} is a ${builtIn.effect} preference`
5228
+ });
5229
+ }
5230
+ if (setting.strength && setting.strength !== "prefer") {
5231
+ ctx.addIssue({
5232
+ code: external_exports.ZodIssueCode.custom,
5233
+ path: ["strength"],
5234
+ message: `${setting.key} uses prefer; define a custom key for inverse behavior`
5235
+ });
5236
+ }
5237
+ if (setting.definition_version !== void 0 && setting.definition_version !== 1) {
5238
+ ctx.addIssue({
5239
+ code: external_exports.ZodIssueCode.custom,
5240
+ path: ["definition_version"],
5241
+ message: `${setting.key} uses definition version 1`
5242
+ });
5243
+ }
5244
+ return;
5245
+ }
5246
+ for (const field of ["label", "behavior", "effect"]) {
5247
+ if (!setting[field]) {
5248
+ ctx.addIssue({
5249
+ code: external_exports.ZodIssueCode.custom,
5250
+ path: [field],
5251
+ message: `${field} is required for an extensible preference`
5252
+ });
5253
+ }
5254
+ }
5255
+ }
5256
+ );
5257
+ var preferenceScopeSchema = external_exports.enum([
5258
+ "task",
5259
+ "organization",
5260
+ "member",
5261
+ "member_runtime",
5262
+ "installation",
5263
+ "project",
5264
+ "skill",
5265
+ "domain"
5266
+ ]);
5267
+ var preferenceSourceSchema = external_exports.enum([
5268
+ "mandatory_org",
5269
+ "explicit_task",
5270
+ "explicit_project",
5271
+ "explicit_member_runtime",
5272
+ "explicit_member",
5273
+ "learned_member_runtime",
5274
+ "learned_member",
5275
+ "explicit_installation",
5276
+ "learned_installation",
5277
+ "recommended_org",
5278
+ "skill_default"
5279
+ ]);
5280
+ var preferenceObservationSchema = external_exports.object({
5281
+ setting: preferenceSettingSchema,
5282
+ scope: preferenceScopeSchema,
5283
+ source: external_exports.enum([
5284
+ "explicit_user",
5285
+ "agent_observed",
5286
+ "admin_override",
5287
+ "admin_required"
5288
+ ]),
5289
+ task_hash: external_exports.string().min(1).max(128),
5290
+ project_key: external_exports.string().min(1).max(128).nullable().default(null),
5291
+ skill_slug: external_exports.string().min(1).max(512).nullable().default(null),
5292
+ domain: external_exports.string().min(1).max(512).nullable().default(null),
5293
+ confidence: external_exports.number().min(0).max(1),
5294
+ observed_at: external_exports.string().datetime()
5295
+ }).strict();
5296
+ function boundedUniquePreferenceSettingsSchema(duplicateMessage) {
5297
+ return external_exports.array(preferenceSettingSchema).max(MAX_EFFECTIVE_PREFERENCES).superRefine((settings, ctx) => {
5298
+ const seen = /* @__PURE__ */ new Set();
5299
+ settings.forEach((setting, index) => {
5300
+ if (seen.has(setting.key)) {
5301
+ ctx.addIssue({
5302
+ code: external_exports.ZodIssueCode.custom,
5303
+ path: [index, "key"],
5304
+ message: duplicateMessage
5305
+ });
5306
+ }
5307
+ seen.add(setting.key);
5308
+ });
5309
+ });
5310
+ }
5311
+ var taskPreferenceSettingsSchema = boundedUniquePreferenceSettingsSchema(
5312
+ "Task preferences must use each key at most once"
5313
+ );
5314
+ var skillPreferenceDefaultsSchema = boundedUniquePreferenceSettingsSchema(
5315
+ "Skill preference defaults must use each key at most once"
5316
+ );
5317
+ var skillPreferenceTraitSchema = external_exports.object({
5318
+ key: preferenceKeySchema,
5319
+ value: preferenceValueSchema,
5320
+ relationship: external_exports.enum(["supports", "conflicts"]).default("supports"),
5321
+ locked: external_exports.boolean().default(false),
5322
+ reason: external_exports.string().trim().min(1).max(200).nullable().default(null)
5323
+ }).strict();
5324
+ var skillPreferenceTraitsSchema = external_exports.array(skillPreferenceTraitSchema).max(MAX_SKILL_PREFERENCE_TRAITS).superRefine((traits, ctx) => {
5325
+ const seen = /* @__PURE__ */ new Set();
5326
+ traits.forEach((trait, index) => {
5327
+ const identity = `${trait.key}:${trait.value}:${trait.relationship}`;
5328
+ if (seen.has(identity)) {
5329
+ ctx.addIssue({
5330
+ code: external_exports.ZodIssueCode.custom,
5331
+ path: [index],
5332
+ message: "Skill preference traits must be unique"
5333
+ });
5334
+ }
5335
+ seen.add(identity);
5336
+ });
5337
+ });
5338
+ var preferenceCompatibilityRelationshipSchema = external_exports.enum([
5339
+ "supports",
5340
+ "conflicts",
5341
+ "neutral",
5342
+ "uncertain"
5343
+ ]);
5344
+ var preferenceCompatibilitySourceSchema = external_exports.enum([
5345
+ "classifier",
5346
+ "member_feedback",
5347
+ "organization_override",
5348
+ "legacy_trait"
5349
+ ]);
5350
+ var preferenceCompatibilityAssessmentSchema = external_exports.object({
5351
+ preference_fingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
5352
+ preference_key: preferenceKeySchema,
5353
+ preference_value: preferenceValueSchema,
5354
+ relationship: preferenceCompatibilityRelationshipSchema,
5355
+ confidence: external_exports.number().min(0).max(1),
5356
+ rationale: external_exports.string().trim().min(1).max(200).nullable().default(null),
5357
+ classification_version: external_exports.string().trim().min(1).max(96),
5358
+ source: preferenceCompatibilitySourceSchema,
5359
+ locked: external_exports.boolean().default(false)
5360
+ }).strict();
5361
+ var preferenceCompatibilityAssessmentsSchema = external_exports.array(preferenceCompatibilityAssessmentSchema).max(MAX_EFFECTIVE_PREFERENCES * 2);
5362
+ var preferenceCompatibilityClassifierResultSchema = external_exports.object({
5363
+ relationship: preferenceCompatibilityRelationshipSchema,
5364
+ confidence: external_exports.number().min(0).max(1),
5365
+ rationale: external_exports.string().trim().min(1).max(200),
5366
+ // `locked` means the skill declares this behavior as a required step. It
5367
+ // can block surgical application after an explicit invocation, but never
5368
+ // lets a preference bypass a skill or organization requirement.
5369
+ locked: external_exports.boolean()
5370
+ }).strict();
5371
+ var preferenceCompatibilityFeedbackReasonCodeSchema = external_exports.enum([
5372
+ "workflow_alignment",
5373
+ "strategy_alignment",
5374
+ "presentation_alignment",
5375
+ "task_mismatch",
5376
+ "skill_requirement",
5377
+ "other_observed"
5378
+ ]);
5379
+ var preferenceCompatibilityFeedbackRequestSchema = external_exports.object({
5380
+ query_id: external_exports.string().trim().min(1).max(160),
5381
+ result_id: external_exports.string().trim().min(1).max(160),
5382
+ preference_fingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
5383
+ skill_slug: external_exports.string().trim().toLowerCase().min(1).max(160).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
5384
+ skill_version_id: external_exports.string().trim().min(1).max(160),
5385
+ relationship: external_exports.enum(["supports", "conflicts"]),
5386
+ evidence_source: external_exports.enum(["explicit_user", "agent_observed"]),
5387
+ reason_code: preferenceCompatibilityFeedbackReasonCodeSchema.nullable().default(null)
5388
+ }).strict();
5389
+ var REMEMBRANCE_PREFERENCE_COMPATIBILITY_FEEDBACK_TOOL_DESCRIPTION = "After actually opening or invoking a skill, record whether that exact version supports or conflicts with one server-issued active working-preference fingerprint. This is private to the authenticated organization, requires the same verified installation principal that used the correlated query/result, and never changes the skill or another organization's ranking. Use evidence_source explicit_user only for a user's explicit assessment; otherwise use agent_observed. Send only correlation IDs, the returned fingerprint, exact slug/version id, relationship, source, and an optional allowlisted reason_code; never send prompts, source code, paths, free-text feedback, or a caller-generated idempotency hash.";
5390
+ var preferenceCompatibilityAdminActionSchema = external_exports.discriminatedUnion(
5391
+ "action",
5392
+ [
5393
+ external_exports.object({
5394
+ action: external_exports.literal("reclassify"),
5395
+ preference_fingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
5396
+ skill_version_id: external_exports.string().trim().min(1).max(160)
5397
+ }).strict(),
5398
+ external_exports.object({
5399
+ action: external_exports.literal("override"),
5400
+ preference_fingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
5401
+ skill_version_id: external_exports.string().trim().min(1).max(160),
5402
+ relationship: external_exports.enum(["supports", "conflicts"]),
5403
+ rationale: external_exports.string().trim().min(1).max(200),
5404
+ locked: external_exports.boolean().default(false)
5405
+ }).strict(),
5406
+ external_exports.object({
5407
+ action: external_exports.literal("reset"),
5408
+ preference_fingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
5409
+ skill_version_id: external_exports.string().trim().min(1).max(160)
5410
+ }).strict()
5411
+ ]
5412
+ );
5413
+ var INFERRED_MAX_AGE_MS = 180 * 864e5;
5414
+ function builtInPreferenceDefinition(key) {
5415
+ return BUILT_IN_PREFERENCE_DEFINITIONS[key] ?? null;
5416
+ }
5417
+
5123
5418
  // ../core/src/agent-guidance.ts
5124
5419
  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.";
5125
5420
  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.";
@@ -5246,6 +5541,7 @@ var REMEMBRANCE_MCP_READ_TOOLS = [
5246
5541
  "query_skills",
5247
5542
  "list_skills",
5248
5543
  "invoke_skill",
5544
+ "get_effective_preferences",
5249
5545
  "get_skill",
5250
5546
  "get_resource",
5251
5547
  "get_value_proof"
@@ -5253,12 +5549,15 @@ var REMEMBRANCE_MCP_READ_TOOLS = [
5253
5549
  var REMEMBRANCE_MCP_FEEDBACK_TOOLS = [
5254
5550
  "submit_query_feedback",
5255
5551
  "submit_feedback",
5552
+ "submit_preference_compatibility_feedback",
5256
5553
  "report_task_outcome"
5257
5554
  ];
5258
5555
  var REMEMBRANCE_MCP_PRIVATE_CONTRIBUTION_TOOLS = [
5259
5556
  "submit_remembrance",
5260
5557
  "propose_private_skill",
5261
- "submit_suggestion"
5558
+ "submit_suggestion",
5559
+ "record_preference",
5560
+ "link_current_installation"
5262
5561
  ];
5263
5562
  var REMEMBRANCE_MCP_OPTIONAL_SHARED_CONTRIBUTION_TOOLS = [
5264
5563
  "propose_skill_idea",
@@ -5336,6 +5635,49 @@ function remembranceSkillResourceHandle(entry) {
5336
5635
  );
5337
5636
  }
5338
5637
 
5638
+ // ../core/src/skill-topology.ts
5639
+ var skillTopologyActionSchema = external_exports.enum([
5640
+ "amend",
5641
+ "specialize",
5642
+ "strategy_fork",
5643
+ "independent_skill",
5644
+ "preference",
5645
+ "evidence_only",
5646
+ "hold"
5647
+ ]);
5648
+ var SKILL_TOPOLOGY_ACTIONS = skillTopologyActionSchema.options;
5649
+ var skillScopePredicateSchema = external_exports.object({
5650
+ dimension: external_exports.enum([
5651
+ "runtime",
5652
+ "runtime_version",
5653
+ "platform",
5654
+ "language",
5655
+ "framework",
5656
+ "dependency",
5657
+ "scale",
5658
+ "task_stage"
5659
+ ]),
5660
+ operator: external_exports.enum(["equals", "includes", "at_least", "at_most"]),
5661
+ value: external_exports.string().trim().min(1).max(256)
5662
+ }).strict();
5663
+ var skillTopologyAssessmentSchema = external_exports.object({
5664
+ action: skillTopologyActionSchema,
5665
+ confidence: external_exports.number().min(0).max(1),
5666
+ subjectivity: external_exports.number().min(0).max(1),
5667
+ generalizability: external_exports.number().min(0).max(1),
5668
+ specificity_delta: external_exports.number().min(-1).max(1),
5669
+ approach_divergence: external_exports.number().min(0).max(1),
5670
+ stable_conditions: external_exports.array(skillScopePredicateSchema).max(12).default([]),
5671
+ use_when: external_exports.array(external_exports.string().trim().min(1).max(512)).max(20).default([]),
5672
+ avoid_when: external_exports.array(external_exports.string().trim().min(1).max(512)).max(20).default([]),
5673
+ preference_setting: preferenceSettingSchema.nullable().default(null),
5674
+ rationale: external_exports.string().trim().min(1).max(2e3)
5675
+ }).strict();
5676
+ var skillTopologyRoutingHintSchema = external_exports.object({
5677
+ suggested_action: skillTopologyActionSchema,
5678
+ conditions: external_exports.array(skillScopePredicateSchema).max(12).default([])
5679
+ }).strict();
5680
+
5339
5681
  // ../core/src/schemas.ts
5340
5682
  var DEFAULT_MUTATION_BODY_LIMIT_BYTES = 256 * 1024;
5341
5683
  var MAX_SHORT_TEXT_LENGTH = 512;
@@ -5554,7 +5896,11 @@ var agentSchema = external_exports.object({
5554
5896
  var queryTaskSchema = external_exports.object({
5555
5897
  domain: boundedString(MAX_SHORT_TEXT_LENGTH),
5556
5898
  summary: boundedString(MAX_LONG_TEXT_LENGTH),
5557
- constraints: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(40).default([])
5899
+ constraints: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(40).default([]),
5900
+ // Explicit task-local preferences are normalized data, not raw prompt text.
5901
+ // They are resolved for this request only and are never persisted as a
5902
+ // durable member preference unless the caller separately records them.
5903
+ preferences: taskPreferenceSettingsSchema.optional()
5558
5904
  });
5559
5905
  var queryRuntimeSchema = external_exports.enum([
5560
5906
  "codex",
@@ -5566,12 +5912,23 @@ var queryRuntimeSchema = external_exports.enum([
5566
5912
  "other",
5567
5913
  "unknown"
5568
5914
  ]);
5915
+ var runtimeHostSurfaceSchema = external_exports.enum([
5916
+ "desktop",
5917
+ "cli",
5918
+ "extension",
5919
+ "gateway",
5920
+ "unknown"
5921
+ ]);
5922
+ var runtimeProfileVersionSchema = external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/);
5569
5923
  var queryDirectiveIdSchema = external_exports.string().regex(/^dir_[A-Za-z0-9_-]{16,80}$/);
5570
5924
  var queryClientContextSchema = external_exports.object({
5571
5925
  surface: external_exports.enum(["plugin_hook", "mcp", "rest", "unknown"]).default("unknown"),
5572
5926
  runtime: queryRuntimeSchema.optional(),
5573
5927
  trigger_reason: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
5574
- directive_id: queryDirectiveIdSchema.optional()
5928
+ directive_id: queryDirectiveIdSchema.optional(),
5929
+ // A local, installation-keyed digest. It lets one engineer retain
5930
+ // project-scoped preferences without disclosing a path or repository.
5931
+ project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).optional()
5575
5932
  }).strict();
5576
5933
  var reasoningEffortSchema = external_exports.enum([
5577
5934
  "none",
@@ -5794,31 +6151,145 @@ var agentTaskOutcomeRequestSchema = external_exports.object({
5794
6151
  });
5795
6152
  }
5796
6153
  });
5797
- var economicsSessionChallengeSchema = external_exports.object({
6154
+ var runtimeProfileRegistrationSchema = external_exports.object({
6155
+ runtime: queryRuntimeSchema,
6156
+ surface: external_exports.enum(["plugin_hook", "mcp", "rest", "unknown"]),
6157
+ host_surface: runtimeHostSurfaceSchema.default("unknown"),
6158
+ client_name: external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9 ._+-]*$/),
6159
+ client_version: runtimeProfileVersionSchema.nullable().optional(),
6160
+ runtime_version: runtimeProfileVersionSchema.nullable().optional(),
6161
+ profile_key: external_exports.string().regex(/^rpf_[A-Za-z0-9_-]{16,120}$/)
6162
+ }).strict();
6163
+ var principalSessionChallengeSchema = external_exports.object({
5798
6164
  action: external_exports.literal("challenge"),
5799
6165
  provider: economicsSessionProviderSchema,
5800
- key_id: boundedString(MAX_SHORT_TEXT_LENGTH)
6166
+ key_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6167
+ runtime_profile: runtimeProfileRegistrationSchema.nullable().optional(),
6168
+ member_link_token: external_exports.string().regex(/^mlink_[A-Za-z0-9_-]{24,160}$/).nullable().optional()
5801
6169
  }).strict();
5802
- var economicsSessionExchangeSchema = external_exports.object({
6170
+ var principalSessionExchangeSchema = external_exports.object({
5803
6171
  action: external_exports.literal("exchange"),
5804
6172
  provider: economicsSessionProviderSchema,
5805
6173
  key_id: boundedString(MAX_SHORT_TEXT_LENGTH),
5806
6174
  challenge_id: boundedString(MAX_SHORT_TEXT_LENGTH),
5807
6175
  signature: boundedString(MAX_LONG_TEXT_LENGTH)
5808
6176
  }).strict();
5809
- var economicsSessionRequestSchema = external_exports.discriminatedUnion("action", [
5810
- economicsSessionChallengeSchema,
5811
- economicsSessionExchangeSchema
6177
+ var principalSessionRequestSchema = external_exports.discriminatedUnion("action", [
6178
+ principalSessionChallengeSchema,
6179
+ principalSessionExchangeSchema
5812
6180
  ]);
6181
+ var recordPreferenceRequestSchema = external_exports.object({
6182
+ setting: preferenceSettingSchema,
6183
+ // Organization guidance is dashboard-owned policy. Agent transports may
6184
+ // record only identity-bound or task-context preference observations.
6185
+ scope: external_exports.enum([
6186
+ "auto",
6187
+ "member",
6188
+ "member_runtime",
6189
+ "installation",
6190
+ "project",
6191
+ "skill",
6192
+ "domain"
6193
+ ]),
6194
+ source_category: external_exports.enum(["explicit_user", "agent_observed"]),
6195
+ evidence_hash: external_exports.string().regex(/^[a-f0-9]{32,128}$/i),
6196
+ task_hash: external_exports.string().regex(/^[a-f0-9]{32,128}$/i),
6197
+ project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).nullable().optional(),
6198
+ skill_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
6199
+ domain: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
6200
+ confidence: external_exports.number().min(0).max(1)
6201
+ }).strict().superRefine((value, ctx) => {
6202
+ const required = value.scope === "project" ? "project_key" : value.scope === "skill" ? "skill_slug" : value.scope === "domain" ? "domain" : null;
6203
+ if (required && !value[required]) {
6204
+ ctx.addIssue({
6205
+ code: external_exports.ZodIssueCode.custom,
6206
+ path: [required],
6207
+ message: `${required} is required for ${value.scope} preferences`
6208
+ });
6209
+ }
6210
+ if (value.source_category === "agent_observed" && value.confidence >= 1) {
6211
+ ctx.addIssue({
6212
+ code: external_exports.ZodIssueCode.custom,
6213
+ path: ["confidence"],
6214
+ message: "Inferred observations cannot claim absolute confidence"
6215
+ });
6216
+ }
6217
+ });
6218
+ var effectivePreferencesRequestSchema = external_exports.object({
6219
+ // Omit or send [] to resolve every active bounded preference. Supplying
6220
+ // keys is an optional projection for clients that need only a subset.
6221
+ keys: external_exports.array(preferenceKeySchema).max(MAX_EFFECTIVE_PREFERENCES).default([]),
6222
+ task_preferences: taskPreferenceSettingsSchema.optional(),
6223
+ project_key: external_exports.string().regex(/^prj_[A-Za-z0-9_-]{12,120}$/).nullable().optional(),
6224
+ skill_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
6225
+ domains: external_exports.array(boundedString(MAX_SHORT_TEXT_LENGTH)).max(20).default([])
6226
+ }).strict();
5813
6227
  var agentPrincipalRegistrationRequestSchema = external_exports.object({
5814
6228
  display_name: boundedString(MAX_SHORT_TEXT_LENGTH),
5815
6229
  provider: agentProviderSchema,
5816
6230
  runtime_version: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
5817
6231
  parent_principal_id: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional()
5818
6232
  }).strict();
5819
- var agentPrincipalUpdateRequestSchema = external_exports.object({
5820
- principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
5821
- action: external_exports.enum(["deactivate", "reactivate"])
6233
+ var agentPrincipalUpdateRequestSchema = external_exports.discriminatedUnion(
6234
+ "action",
6235
+ [
6236
+ external_exports.object({
6237
+ principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6238
+ action: external_exports.enum(["deactivate", "reactivate"])
6239
+ }).strict(),
6240
+ external_exports.object({
6241
+ principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6242
+ action: external_exports.literal("rename"),
6243
+ display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
6244
+ }).strict()
6245
+ ]
6246
+ );
6247
+ var agentInstallationManagementRequestSchema = external_exports.discriminatedUnion(
6248
+ "action",
6249
+ [
6250
+ external_exports.object({
6251
+ action: external_exports.literal("rename"),
6252
+ installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6253
+ display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
6254
+ }).strict(),
6255
+ external_exports.object({
6256
+ action: external_exports.literal("rename_runtime_profile"),
6257
+ runtime_profile_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6258
+ display_name: boundedString(MAX_SHORT_TEXT_LENGTH)
6259
+ }).strict(),
6260
+ external_exports.object({
6261
+ action: external_exports.literal("set_status"),
6262
+ installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6263
+ status: external_exports.enum(["active", "inactive"])
6264
+ }).strict(),
6265
+ external_exports.object({
6266
+ action: external_exports.literal("assign_member"),
6267
+ installation_id: boundedString(MAX_SHORT_TEXT_LENGTH),
6268
+ member_principal_id: boundedString(MAX_SHORT_TEXT_LENGTH)
6269
+ }).strict(),
6270
+ external_exports.object({
6271
+ action: external_exports.literal("unlink_member"),
6272
+ installation_id: boundedString(MAX_SHORT_TEXT_LENGTH)
6273
+ }).strict()
6274
+ ]
6275
+ );
6276
+ var dashboardPreferenceMutationSchema = external_exports.discriminatedUnion(
6277
+ "action",
6278
+ [
6279
+ external_exports.object({
6280
+ action: external_exports.literal("set"),
6281
+ setting: preferenceSettingSchema,
6282
+ scope: external_exports.enum(["member", "organization"]),
6283
+ enforcement: external_exports.enum(["recommended", "required"]).default("recommended")
6284
+ }).strict(),
6285
+ external_exports.object({
6286
+ action: external_exports.enum(["reset", "undo"]),
6287
+ revision_id: boundedString(MAX_SHORT_TEXT_LENGTH)
6288
+ }).strict()
6289
+ ]
6290
+ );
6291
+ var agentMemberLinkRequestSchema = external_exports.object({
6292
+ member_link_token: external_exports.string().regex(/^mlink_[A-Za-z0-9_-]{24,160}$/)
5822
6293
  }).strict();
5823
6294
  var agentPrincipalKeyBindingRequestSchema = external_exports.object({
5824
6295
  principal_id: boundedString(MAX_SHORT_TEXT_LENGTH),
@@ -5901,6 +6372,7 @@ var remembrancePayloadSchema = external_exports.object({
5901
6372
  }).strict().optional(),
5902
6373
  enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
5903
6374
  suggested_update: suggestedUpdateSchema.default({ kind: "none" }),
6375
+ routing_hint: skillTopologyRoutingHintSchema.optional(),
5904
6376
  evidence: evidenceSchema.default({ artifact_hashes: [] }),
5905
6377
  // Client-only directive: "sign this with my local TOFU key" (the MCP
5906
6378
  // submit_remembrance tool exposes it, and feedback-next-step tells REST
@@ -5935,6 +6407,8 @@ var skillMetadataSchema = external_exports.object({
5935
6407
  permissions: external_exports.record(external_exports.boolean()).default({}),
5936
6408
  contraindications: external_exports.array(external_exports.string().max(MAX_LONG_TEXT_LENGTH)).max(80).default([]),
5937
6409
  applicability: skillApplicabilitySchema.optional(),
6410
+ preference_defaults: skillPreferenceDefaultsSchema.optional(),
6411
+ preference_traits: skillPreferenceTraitsSchema.optional(),
5938
6412
  feedback_url: external_exports.string().url(),
5939
6413
  install_command: external_exports.string().min(1),
5940
6414
  stats: external_exports.object({
@@ -5967,6 +6441,7 @@ var skillIdeaRequestSchema = external_exports.object({
5967
6441
  domain_slug: boundedString(MAX_SHORT_TEXT_LENGTH).nullable().optional(),
5968
6442
  proposed_skill_md: external_exports.string().max(MAX_LONG_TEXT_LENGTH).nullable().optional(),
5969
6443
  proposed_metadata: boundedJsonRecord().default({}),
6444
+ routing_hint: skillTopologyRoutingHintSchema.optional(),
5970
6445
  enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
5971
6446
  idempotency_key: boundedString(MAX_SHORT_TEXT_LENGTH).optional()
5972
6447
  });
@@ -5979,6 +6454,7 @@ var suggestionRequestSchema = external_exports.object({
5979
6454
  summary: boundedString(MAX_LONG_TEXT_LENGTH),
5980
6455
  diff_text: external_exports.string().max(MAX_LONG_TEXT_LENGTH).nullable().optional(),
5981
6456
  payload: boundedJsonRecord().default({}),
6457
+ routing_hint: skillTopologyRoutingHintSchema.optional(),
5982
6458
  enterprise_encryption: enterpriseEncryptedPayloadEnvelopeSchema.optional(),
5983
6459
  idempotency_key: boundedString(MAX_SHORT_TEXT_LENGTH).optional()
5984
6460
  });
@@ -6059,6 +6535,7 @@ var attestationKeyRegistrationRequestSchema = external_exports.object({
6059
6535
  agent: agentSchema.optional(),
6060
6536
  proof: external_exports.object({
6061
6537
  algorithm: external_exports.enum(["ed25519"]).default("ed25519"),
6538
+ owner_binding: external_exports.string().regex(/^areg_[A-Za-z0-9_-]{24,120}$/).optional(),
6062
6539
  signed_at: external_exports.string().datetime(),
6063
6540
  signature: boundedString(MAX_LONG_TEXT_LENGTH)
6064
6541
  }),
@@ -6257,7 +6734,7 @@ var seedSkills = [
6257
6734
  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.",
6258
6735
  status: "active",
6259
6736
  visibility: "public",
6260
- version: "0.1.17",
6737
+ version: "0.1.19",
6261
6738
  domains: ["agent-skills", "mcp", "resource-discovery"],
6262
6739
  tags: [
6263
6740
  "remembrance",
@@ -6308,7 +6785,7 @@ var seedSkills = [
6308
6785
  "gemini",
6309
6786
  "rest"
6310
6787
  ],
6311
- version: "0.1.17",
6788
+ version: "0.1.19",
6312
6789
  status: "active",
6313
6790
  visibility: "public",
6314
6791
  providers: ["codex", "claude", "cursor", "openclaw", "generic"],
@@ -7974,7 +8451,14 @@ var verifierOutputSchema = external_exports.object({
7974
8451
  }).nullable().default(null),
7975
8452
  // Advisory retrieval enrichment (see proposedSkillMetadataSchema). Optional
7976
8453
  // and defaults to null so existing verifier outputs remain valid.
7977
- proposed_metadata: proposedSkillMetadataSchema.nullable().default(null)
8454
+ proposed_metadata: proposedSkillMetadataSchema.nullable().default(null),
8455
+ // Advisory evidence-routing assessment. Deterministic routing and the
8456
+ // existing safety/review gates decide whether it can create a candidate.
8457
+ // Topology is advisory and newer than the base verifier contract. A legacy
8458
+ // or malformed topology fragment must not discard an otherwise valid safety
8459
+ // verdict; it degrades to null, which the topology router retains as
8460
+ // evidence-only rather than allowing an unclassified mutation.
8461
+ topology_assessment: skillTopologyAssessmentSchema.nullable().default(null).catch(null)
7978
8462
  });
7979
8463
 
7980
8464
  // src/server.ts
@@ -7982,6 +8466,8 @@ import { existsSync as existsSync3 } from "node:fs";
7982
8466
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
7983
8467
  import { dirname as dirname2 } from "node:path";
7984
8468
  import {
8469
+ createHash as createHash3,
8470
+ createHmac,
7985
8471
  createPrivateKey as createPrivateKey2,
7986
8472
  generateKeyPairSync,
7987
8473
  sign as signPayload
@@ -9343,6 +9829,30 @@ var toolDefinitions = [
9343
9829
  "/api/v1/agent/skill-invocations",
9344
9830
  agentSkillInvocationRequestSchema
9345
9831
  ),
9832
+ tool(
9833
+ "get_effective_preferences",
9834
+ "Return bounded, task-relevant working preferences for this verified installation and optional linked member/runtime profile. project_key is an opaque local project-context hash; paths and repository names are never transmitted. A clear current-task preference overrides every non-mandatory stored preference; Required organization guidance remains authoritative. Preferences may steer among already-relevant skills and surgically alter discretionary presentation, workflow, or strategy choices. They never change relevance tiers or weaken applicability, safety, authorization, privacy, required skill steps, validation, or review. Clients without a valid principal session remain organization-level and receive no personal preferences.",
9835
+ "/api/v1/agent/preferences/effective",
9836
+ effectivePreferencesRequestSchema
9837
+ ),
9838
+ tool(
9839
+ "record_preference",
9840
+ "Record a bounded, privacy-safe preference observation for the current verified installation. Built-in settings need only key and value; a custom setting also supplies its plain-language label, behavior, presentation/workflow/strategy_selection effect, prefer/avoid direction, and definition version. Send normalized settings and hashes only, never prompt text. A valid principal session is required. Explicit user preferences apply immediately; inferred observations activate only after Remembrance's consistency and confidence thresholds are met.",
9841
+ "/api/v1/agent/preferences",
9842
+ recordPreferenceRequestSchema
9843
+ ),
9844
+ tool(
9845
+ "submit_preference_compatibility_feedback",
9846
+ REMEMBRANCE_PREFERENCE_COMPATIBILITY_FEEDBACK_TOOL_DESCRIPTION,
9847
+ "/api/v1/agent/preferences/compatibility-feedback",
9848
+ preferenceCompatibilityFeedbackRequestSchema
9849
+ ),
9850
+ tool(
9851
+ "link_current_installation",
9852
+ "Link this verified installation to the signed-in organization member using a single-use dashboard token. The token expires after ten minutes and is consumed exactly once. A valid organization principal session is required; this never changes the organization API key or creates another billed agent.",
9853
+ "/api/v1/agent/member-links",
9854
+ agentMemberLinkRequestSchema
9855
+ ),
9346
9856
  tool(
9347
9857
  "get_skill",
9348
9858
  "Fetch the exact active reviewed skill body and version after query_skills returns a known slug. The response includes skill_md, version_id, and source. Pass query_id and result_id from that response so Remembrance can measure whether surfaced guidance was opened. Do not guess private or inactive slugs.",
@@ -9465,7 +9975,7 @@ function localTool(name, description, local, schema) {
9465
9975
  };
9466
9976
  }
9467
9977
  function annotationsForTool(name) {
9468
- const readOnly = name === "list_skills" || name === "get_value_proof" || name === "run_connection_doctor";
9978
+ const readOnly = name === "list_skills" || name === "get_effective_preferences" || name === "get_value_proof" || name === "run_connection_doctor";
9469
9979
  const openWorld = name === "submit_feedback" || name === "submit_remembrance" || name === "propose_skill_idea" || name === "submit_suggestion" || name === "submit_resource" || name === "submit_resource_review";
9470
9980
  return {
9471
9981
  readOnlyHint: readOnly,
@@ -9866,107 +10376,104 @@ function remembranceConfigPath(env = process.env, homeDirectory = homedir3()) {
9866
10376
  return join3(configRoot, "remembrance", "config.json");
9867
10377
  }
9868
10378
  function resolveApiCredential(env = process.env, fileSystem = nodeFileSystem2) {
9869
- const apiConfiguration2 = resolveApiConfiguration(env, fileSystem);
10379
+ return resolveApiAccess(env, fileSystem).credential;
10380
+ }
10381
+ function resolveApiAccess(env = process.env, fileSystem = nodeFileSystem2) {
10382
+ const shared = readSharedConfigSnapshot(env, fileSystem);
10383
+ const configuration = resolveApiConfigurationFromSnapshot(env, shared);
9870
10384
  const environmentKey = env.REMEMBRANCE_API_KEY?.trim();
10385
+ let credential;
9871
10386
  if (environmentKey) {
9872
10387
  const explicitBinding = env.REMEMBRANCE_API_KEY_ORIGIN?.trim();
9873
10388
  const binding = explicitBinding ? normalizeApiUrl(explicitBinding, env) : { baseUrl: DEFAULT_API_URL, issue: null };
9874
10389
  if (!binding.baseUrl || binding.issue) {
9875
- return unusableDestinationBinding();
10390
+ credential = unusableDestinationBinding();
10391
+ } else {
10392
+ credential = credentialForDestination(
10393
+ {
10394
+ apiKey: environmentKey,
10395
+ source: "environment",
10396
+ boundBaseUrl: binding.baseUrl
10397
+ },
10398
+ configuration
10399
+ );
10400
+ }
10401
+ } else if (!shared.present) {
10402
+ credential = { apiKey: "", source: "none" };
10403
+ } else if (!shared.parsed || Object.prototype.hasOwnProperty.call(shared.parsed, "apiKey") && (typeof shared.parsed.apiKey !== "string" || !shared.parsed.apiKey.trim())) {
10404
+ credential = unusableSharedConfig();
10405
+ } else if (!shared.config.apiKey) {
10406
+ credential = { apiKey: "", source: "none" };
10407
+ } else {
10408
+ const binding = shared.config.apiUrl ? normalizeApiUrl(shared.config.apiUrl, env) : { baseUrl: DEFAULT_API_URL, issue: null };
10409
+ if (!binding.baseUrl || binding.issue) {
10410
+ credential = unusableSharedConfig();
10411
+ } else {
10412
+ credential = credentialForDestination(
10413
+ {
10414
+ apiKey: shared.config.apiKey,
10415
+ source: "shared_config",
10416
+ boundBaseUrl: binding.baseUrl
10417
+ },
10418
+ configuration
10419
+ );
9876
10420
  }
9877
- return credentialForDestination(
9878
- {
9879
- apiKey: environmentKey,
9880
- source: "environment",
9881
- boundBaseUrl: binding.baseUrl
9882
- },
9883
- apiConfiguration2
9884
- );
9885
10421
  }
10422
+ return {
10423
+ configuration,
10424
+ credential,
10425
+ memberLinkToken: env.REMEMBRANCE_MEMBER_LINK_TOKEN?.trim() ?? shared.config.memberLinkToken ?? ""
10426
+ };
10427
+ }
10428
+ function isUnusableCredentialSource(source) {
10429
+ return source.startsWith("unusable_");
10430
+ }
10431
+ function readSharedConfigSnapshot(env, fileSystem) {
9886
10432
  const path = remembranceConfigPath(env);
9887
10433
  if (!fileSystem.exists(path)) {
9888
- return { apiKey: "", source: "none" };
10434
+ return { present: false, parsed: null, config: {} };
9889
10435
  }
9890
10436
  try {
9891
10437
  const parsed = JSON.parse(
9892
10438
  fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
9893
10439
  );
9894
- if (!isRecord3(parsed)) {
9895
- return unusableSharedConfig();
9896
- }
9897
- if (Object.prototype.hasOwnProperty.call(parsed, "apiKey") && (typeof parsed.apiKey !== "string" || !parsed.apiKey.trim())) {
9898
- return unusableSharedConfig();
9899
- }
9900
- const config = normalizeConfig(parsed);
9901
- if (!config.apiKey) {
9902
- return { apiKey: "", source: "none" };
9903
- }
9904
- const binding = config.apiUrl ? normalizeApiUrl(config.apiUrl, env) : { baseUrl: DEFAULT_API_URL, issue: null };
9905
- if (!binding.baseUrl || binding.issue) {
9906
- return unusableSharedConfig();
9907
- }
9908
- return credentialForDestination(
9909
- {
9910
- apiKey: config.apiKey,
9911
- source: "shared_config",
9912
- boundBaseUrl: binding.baseUrl
9913
- },
9914
- apiConfiguration2
9915
- );
10440
+ return isRecord3(parsed) ? { present: true, parsed, config: normalizeConfig(parsed) } : { present: true, parsed: null, config: {} };
9916
10441
  } catch {
9917
- return unusableSharedConfig();
10442
+ return { present: true, parsed: null, config: {} };
9918
10443
  }
9919
10444
  }
9920
- function resolveApiKey(env = process.env, fileSystem = nodeFileSystem2) {
9921
- return resolveApiCredential(env, fileSystem).apiKey;
9922
- }
9923
- function resolveApiConfiguration(env = process.env, fileSystem = nodeFileSystem2) {
10445
+ function resolveApiConfigurationFromSnapshot(env, shared) {
9924
10446
  const environmentUrl = env.REMEMBRANCE_API_URL?.trim();
9925
10447
  if (environmentUrl) {
9926
- const normalized = normalizeApiUrl(environmentUrl, env);
9927
- if (normalized.baseUrl !== null) {
9928
- return { baseUrl: normalized.baseUrl, source: "environment" };
10448
+ const normalized2 = normalizeApiUrl(environmentUrl, env);
10449
+ if (normalized2.baseUrl !== null) {
10450
+ return { baseUrl: normalized2.baseUrl, source: "environment" };
9929
10451
  }
9930
10452
  return {
9931
10453
  baseUrl: DEFAULT_API_URL,
9932
10454
  source: "unusable_environment",
9933
- issue: normalized.issue
10455
+ issue: normalized2.issue
9934
10456
  };
9935
10457
  }
9936
- const path = remembranceConfigPath(env);
9937
- if (!fileSystem.exists(path)) {
10458
+ if (!shared.present) {
9938
10459
  return { baseUrl: DEFAULT_API_URL, source: "default" };
9939
10460
  }
9940
- try {
9941
- const parsed = JSON.parse(
9942
- fileSystem.read(path, MAX_LOCAL_CONFIG_BYTES)
9943
- );
9944
- if (!isRecord3(parsed)) {
9945
- return {
9946
- baseUrl: DEFAULT_API_URL,
9947
- source: "unusable_shared_config",
9948
- issue: "invalid_url"
9949
- };
9950
- }
9951
- if (!Object.prototype.hasOwnProperty.call(parsed, "apiUrl")) {
9952
- return { baseUrl: DEFAULT_API_URL, source: "default" };
9953
- }
9954
- const normalized = normalizeApiUrl(parsed.apiUrl, env);
9955
- if (normalized.baseUrl !== null) {
9956
- return { baseUrl: normalized.baseUrl, source: "shared_config" };
9957
- }
9958
- return {
9959
- baseUrl: DEFAULT_API_URL,
9960
- source: "unusable_shared_config",
9961
- issue: normalized.issue
9962
- };
9963
- } catch {
10461
+ if (!shared.parsed) {
9964
10462
  return {
9965
10463
  baseUrl: DEFAULT_API_URL,
9966
10464
  source: "unusable_shared_config",
9967
10465
  issue: "invalid_url"
9968
10466
  };
9969
10467
  }
10468
+ if (!Object.prototype.hasOwnProperty.call(shared.parsed, "apiUrl")) {
10469
+ return { baseUrl: DEFAULT_API_URL, source: "default" };
10470
+ }
10471
+ const normalized = normalizeApiUrl(shared.parsed.apiUrl, env);
10472
+ return normalized.baseUrl !== null ? { baseUrl: normalized.baseUrl, source: "shared_config" } : {
10473
+ baseUrl: DEFAULT_API_URL,
10474
+ source: "unusable_shared_config",
10475
+ issue: normalized.issue
10476
+ };
9970
10477
  }
9971
10478
  function sharedConfigStatus(env = process.env, fileSystem = nodeFileSystem2) {
9972
10479
  const path = remembranceConfigPath(env);
@@ -10348,9 +10855,11 @@ function normalizeConfig(value) {
10348
10855
  if (!isRecord3(value)) return {};
10349
10856
  const apiKey = typeof value.apiKey === "string" ? value.apiKey.trim() : "";
10350
10857
  const apiUrl = typeof value.apiUrl === "string" ? value.apiUrl.trim() : "";
10858
+ const memberLinkToken = typeof value.memberLinkToken === "string" ? value.memberLinkToken.trim() : "";
10351
10859
  return {
10352
10860
  ...apiKey ? { apiKey } : {},
10353
- ...apiUrl ? { apiUrl } : {}
10861
+ ...apiUrl ? { apiUrl } : {},
10862
+ ...memberLinkToken && /^mlink_[A-Za-z0-9_-]{24,160}$/.test(memberLinkToken) ? { memberLinkToken } : {}
10354
10863
  };
10355
10864
  }
10356
10865
  function normalizeApiUrl(value, env) {
@@ -10492,17 +11001,38 @@ async function checkClientUpdate(input) {
10492
11001
  }
10493
11002
 
10494
11003
  // src/server.ts
10495
- var apiConfiguration = resolveApiConfiguration();
10496
- var apiBase = apiConfiguration.baseUrl;
10497
11004
  var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
10498
11005
  var DOCTOR_PROBE_TIMEOUT_MS = 7500;
10499
- var SERVER_VERSION = true ? "0.1.55" : "0.0.0-dev";
11006
+ var SERVER_VERSION = true ? "0.1.62" : "0.0.0-dev";
10500
11007
  var tools = toolDefinitions;
10501
11008
  var doctorCliRequested = process.argv[2] === "doctor";
10502
11009
  var inputBuffer = Buffer.alloc(0);
10503
11010
  var clientFraming = "ndjson";
10504
- var cachedEconomicsSession = null;
11011
+ var cachedPrincipalSession = null;
11012
+ var connectedClientInfo = null;
10505
11013
  var cachedValueProofKeys = null;
11014
+ var PRINCIPAL_SESSION_REQUIRED_TOOLS = /* @__PURE__ */ new Set([
11015
+ "record_preference",
11016
+ "submit_preference_compatibility_feedback",
11017
+ "link_current_installation"
11018
+ ]);
11019
+ var PRINCIPAL_SESSION_AWAITED_TOOLS = /* @__PURE__ */ new Set([
11020
+ ...PRINCIPAL_SESSION_REQUIRED_TOOLS,
11021
+ "report_task_outcome"
11022
+ ]);
11023
+ function apiAccessKey(access) {
11024
+ return createHash3("sha256").update(
11025
+ [
11026
+ access.configuration.baseUrl,
11027
+ access.credential.source,
11028
+ access.credential.apiKey
11029
+ ].join("\0"),
11030
+ "utf8"
11031
+ ).digest("hex");
11032
+ }
11033
+ function resetPrincipalSessionCacheForTests() {
11034
+ cachedPrincipalSession = null;
11035
+ }
10506
11036
  if (!doctorCliRequested) {
10507
11037
  process.stdin.on("data", (chunk) => {
10508
11038
  inputBuffer = Buffer.concat([inputBuffer, chunk]);
@@ -10592,7 +11122,10 @@ async function handleRequest(request) {
10592
11122
  }
10593
11123
  async function dispatchJsonRpcRequest(request) {
10594
11124
  if (request.method === "initialize") {
10595
- const clientUpdate = await currentClientUpdate();
11125
+ connectedClientInfo = normalizedMcpClientInfo(request.params?.clientInfo);
11126
+ const access = resolveApiAccess();
11127
+ void ensurePrincipalSessionToken(access).catch(() => null);
11128
+ const clientUpdate = await currentClientUpdate(access);
10596
11129
  return {
10597
11130
  id: request.id,
10598
11131
  result: {
@@ -10709,47 +11242,54 @@ function messageLooksLikeZodError(message) {
10709
11242
  }
10710
11243
  async function callTool(definition, rawArguments) {
10711
11244
  const payload = definition.schema.parse(rawArguments ?? {});
11245
+ const access = resolveApiAccess();
10712
11246
  if (definition.local === "bootstrap_agent_identity") {
10713
- return bootstrapAgentIdentity(payload);
11247
+ return bootstrapAgentIdentity(payload, access);
10714
11248
  }
10715
11249
  if (definition.local === "queue_private_skill_import") {
10716
- return queuePrivateSkillHandoff(payload, { uploadBaseUrl: apiBase });
11250
+ return queuePrivateSkillHandoff(payload, {
11251
+ uploadBaseUrl: access.configuration.baseUrl
11252
+ });
10717
11253
  }
10718
11254
  if (definition.name === "run_connection_doctor") {
10719
- return runLocalConnectionDoctor(payload);
11255
+ return runLocalConnectionDoctor(
11256
+ payload,
11257
+ "mcp_tool",
11258
+ access
11259
+ );
10720
11260
  }
10721
11261
  if (definition.name === "submit_feedback") {
10722
- return submitFeedback(payload);
11262
+ return submitFeedback(payload, access);
10723
11263
  }
10724
11264
  if (definition.name === "submit_remembrance") {
10725
- return submitRemembrance(payload);
11265
+ return submitRemembrance(payload, access);
10726
11266
  }
10727
11267
  const localConfigurationError = remoteConfigurationError(
10728
- apiConfiguration.source,
10729
- resolveApiCredential().source,
10730
- apiConfiguration.issue
11268
+ access.configuration.source,
11269
+ access.credential.source,
11270
+ access.configuration.issue
10731
11271
  );
10732
- const result = definition.name === "get_connection_status" && localConfigurationError ? { ok: false, status: 0, error: localConfigurationError } : await callRemembrance(definition, payload);
11272
+ const result = definition.name === "get_connection_status" && localConfigurationError ? { ok: false, status: 0, error: localConfigurationError } : await callRemembrance(definition, payload, { access });
10733
11273
  if (definition.name === "get_connection_status") {
10734
11274
  const status = localConnectionStatus(result, {
10735
- apiBase,
10736
- apiUrlSource: apiConfiguration.source,
11275
+ apiBase: access.configuration.baseUrl,
11276
+ apiUrlSource: access.configuration.source,
10737
11277
  pluginVersion: SERVER_VERSION
10738
11278
  });
10739
11279
  if (!localConfigurationError) {
10740
- void reportDegradedClientHealth(status).catch(() => void 0);
11280
+ void reportDegradedClientHealth(status, access).catch(() => void 0);
10741
11281
  }
10742
11282
  return status;
10743
11283
  }
10744
11284
  return result;
10745
11285
  }
10746
11286
  function remoteConfigurationError(apiUrlSource, credentialSource, apiUrlIssue) {
10747
- if (credentialSource === "unusable_destination_binding") {
10748
- return "The Remembrance API key is not bound to the configured registry destination. For a custom registry, store apiKey and apiUrl together in the shared config, or set REMEMBRANCE_API_KEY_ORIGIN to the exact REMEMBRANCE_API_URL value. Remote tools remain paused so the key cannot be forwarded elsewhere.";
10749
- }
10750
11287
  if (apiUrlSource === "unusable_environment") {
10751
11288
  return apiConfigurationErrorMessage(apiUrlIssue, "REMEMBRANCE_API_URL");
10752
11289
  }
11290
+ if (credentialSource === "unusable_destination_binding") {
11291
+ return "The Remembrance API key is not bound to the configured registry destination. For a custom registry, store apiKey and apiUrl together in the shared config, or set REMEMBRANCE_API_KEY_ORIGIN to the exact REMEMBRANCE_API_URL value. Remote tools remain paused so the key cannot be forwarded elsewhere.";
11292
+ }
10753
11293
  if (apiUrlSource === "unusable_shared_config" || credentialSource === "unusable_shared_config") {
10754
11294
  return "The shared Remembrance config exists but is unreadable or invalid. Fix or intentionally remove ~/.config/remembrance/config.json before using remote Remembrance tools.";
10755
11295
  }
@@ -10829,7 +11369,7 @@ function isSafePluginVersion(value) {
10829
11369
  function isSafeHostVersion(value) {
10830
11370
  return value === null || typeof value === "string" && value.length <= 64 && /^[0-9A-Za-z._+ -]*$/.test(value.trim());
10831
11371
  }
10832
- async function reportDegradedClientHealth(status) {
11372
+ async function reportDegradedClientHealth(status, access = resolveApiAccess()) {
10833
11373
  if (/^(0|false|no)$/i.test(process.env.REMEMBRANCE_HEALTH_REPORTING ?? "")) {
10834
11374
  return;
10835
11375
  }
@@ -10838,25 +11378,30 @@ async function reportDegradedClientHealth(status) {
10838
11378
  const controller = new AbortController();
10839
11379
  const timeout = setTimeout(() => controller.abort(), 1500);
10840
11380
  try {
10841
- const apiKey = resolveApiKey();
10842
- await fetch(`${apiBase}/api/v1/agent/client-health-reports`, {
10843
- method: "POST",
10844
- headers: {
10845
- "Content-Type": "application/json",
10846
- ...apiKey ? { "X-Remembrance-API-Key": apiKey } : {},
10847
- "User-Agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
10848
- },
10849
- body: JSON.stringify(report),
10850
- signal: controller.signal
10851
- });
11381
+ const apiKey = access.credential.apiKey;
11382
+ await fetch(
11383
+ `${access.configuration.baseUrl}/api/v1/agent/client-health-reports`,
11384
+ {
11385
+ method: "POST",
11386
+ headers: {
11387
+ "Content-Type": "application/json",
11388
+ ...apiKey ? { "X-Remembrance-API-Key": apiKey } : {},
11389
+ "User-Agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
11390
+ },
11391
+ body: JSON.stringify(report),
11392
+ signal: controller.signal
11393
+ }
11394
+ );
10852
11395
  } finally {
10853
11396
  clearTimeout(timeout);
10854
11397
  }
10855
11398
  }
10856
- async function submitRemembrance(payload) {
11399
+ async function submitRemembrance(payload, access) {
10857
11400
  const { verified_attestation: verifiedAttestation, ...request } = payload;
10858
11401
  if (!verifiedAttestation) {
10859
- return callRemembrance(mustFindTool("submit_remembrance"), request);
11402
+ return callRemembrance(mustFindTool("submit_remembrance"), request, {
11403
+ access
11404
+ });
10860
11405
  }
10861
11406
  const skillSlug = request.skill?.slug;
10862
11407
  if (!skillSlug) {
@@ -10872,20 +11417,27 @@ async function submitRemembrance(payload) {
10872
11417
  };
10873
11418
  const attestation = await signedRemembranceAttestation(
10874
11419
  remembrancePayload,
10875
- skillSlug
11420
+ skillSlug,
11421
+ access
11422
+ );
11423
+ return callRemembrance(
11424
+ mustFindTool("submit_remembrance"),
11425
+ {
11426
+ ...remembrancePayload,
11427
+ evidence: {
11428
+ ...evidence,
11429
+ attestation
11430
+ }
11431
+ },
11432
+ { access }
10876
11433
  );
10877
- return callRemembrance(mustFindTool("submit_remembrance"), {
10878
- ...remembrancePayload,
10879
- evidence: {
10880
- ...evidence,
10881
- attestation
10882
- }
10883
- });
10884
11434
  }
10885
- async function submitFeedback(payload) {
11435
+ async function submitFeedback(payload, access) {
10886
11436
  const { verified_attestation: verifiedAttestation, ...request } = payload;
10887
11437
  if (!verifiedAttestation) {
10888
- return callRemembrance(mustFindTool("submit_feedback"), request);
11438
+ return callRemembrance(mustFindTool("submit_feedback"), request, {
11439
+ access
11440
+ });
10889
11441
  }
10890
11442
  const identity = await ensureSigningIdentity();
10891
11443
  const agent = request.agent ?? {
@@ -10920,18 +11472,23 @@ async function submitFeedback(payload) {
10920
11472
  };
10921
11473
  const attestation = await signedRemembranceAttestation(
10922
11474
  remembrancePayload,
10923
- request.skill_slug
11475
+ request.skill_slug,
11476
+ access
11477
+ );
11478
+ return callRemembrance(
11479
+ mustFindTool("submit_feedback"),
11480
+ {
11481
+ ...request,
11482
+ agent,
11483
+ evidence: {
11484
+ ...evidence,
11485
+ attestation
11486
+ }
11487
+ },
11488
+ { access }
10924
11489
  );
10925
- return callRemembrance(mustFindTool("submit_feedback"), {
10926
- ...request,
10927
- agent,
10928
- evidence: {
10929
- ...evidence,
10930
- attestation
10931
- }
10932
- });
10933
11490
  }
10934
- async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
11491
+ async function signedRemembranceAttestation(remembrancePayload, skillSlug, access) {
10935
11492
  const identity = await ensureSigningIdentity();
10936
11493
  const evidenceHash = attestationEvidenceHashForRemembrance(remembrancePayload);
10937
11494
  const challenge = await callRemembrance(
@@ -10943,7 +11500,8 @@ async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
10943
11500
  subject: identity.subject,
10944
11501
  skill_slug: skillSlug,
10945
11502
  evidence_hash: evidenceHash
10946
- }
11503
+ },
11504
+ { access }
10947
11505
  );
10948
11506
  if (challenge.ok === false) {
10949
11507
  throw new Error("Unable to create Remembrance attestation challenge.");
@@ -10970,7 +11528,7 @@ async function signedRemembranceAttestation(remembrancePayload, skillSlug) {
10970
11528
  signature
10971
11529
  };
10972
11530
  }
10973
- async function bootstrapAgentIdentity(args) {
11531
+ async function bootstrapAgentIdentity(args, access = resolveApiAccess()) {
10974
11532
  const keyPath = identityPath(args.key_path);
10975
11533
  let reusedExistingIdentity = !args.force_rotate && existsSync3(keyPath);
10976
11534
  let identity = reusedExistingIdentity ? await readIdentity(keyPath) : null;
@@ -10993,10 +11551,14 @@ async function bootstrapAgentIdentity(args) {
10993
11551
  mismatchedFields.push("key_id");
10994
11552
  }
10995
11553
  }
11554
+ const ownerBinding = await agentRegistrationOwnerBinding(access).catch(
11555
+ () => null
11556
+ );
10996
11557
  const signedAt = (/* @__PURE__ */ new Date()).toISOString();
10997
11558
  const proofPayload = buildAgentKeyRegistrationSigningPayload({
10998
11559
  provider: identity.provider,
10999
11560
  keyId: identity.key_id,
11561
+ ownerBinding,
11000
11562
  publicKey: identity.public_key,
11001
11563
  subject: identity.subject,
11002
11564
  signedAt
@@ -11015,13 +11577,15 @@ async function bootstrapAgentIdentity(args) {
11015
11577
  subject: identity.subject,
11016
11578
  proof: {
11017
11579
  algorithm: "ed25519",
11580
+ ...ownerBinding ? { owner_binding: ownerBinding } : {},
11018
11581
  signed_at: signedAt,
11019
11582
  signature: proofSignature
11020
11583
  },
11021
11584
  metadata: {
11022
11585
  registered_by: "@remembrance-ai/mcp-server"
11023
11586
  }
11024
- }
11587
+ },
11588
+ { access }
11025
11589
  );
11026
11590
  if (registration.ok !== true) {
11027
11591
  throw new McpPublicError(
@@ -11039,6 +11603,33 @@ async function bootstrapAgentIdentity(args) {
11039
11603
  registration
11040
11604
  };
11041
11605
  }
11606
+ async function agentRegistrationOwnerBinding(access) {
11607
+ const credential = access.credential;
11608
+ const configurationError = remoteConfigurationError(
11609
+ access.configuration.source,
11610
+ credential.source,
11611
+ access.configuration.issue
11612
+ );
11613
+ if (configurationError) throw new McpPublicError(configurationError);
11614
+ const headers = {
11615
+ accept: "application/json",
11616
+ "user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
11617
+ };
11618
+ if (credential.apiKey) {
11619
+ headers["x-remembrance-api-key"] = credential.apiKey;
11620
+ }
11621
+ const response = await fetch(
11622
+ `${access.configuration.baseUrl}/api/v1/agent/keys/register`,
11623
+ {
11624
+ method: "GET",
11625
+ headers
11626
+ }
11627
+ );
11628
+ if (!response.ok) return null;
11629
+ const payload = await response.json();
11630
+ const candidate = String(payload.owner_binding ?? "").trim();
11631
+ return /^areg_[A-Za-z0-9_-]{24,120}$/.test(candidate) ? candidate : null;
11632
+ }
11042
11633
  async function createAndPersistIdentity(args, keyPath) {
11043
11634
  const { privateKey, publicKey } = generateKeyPairSync("ed25519");
11044
11635
  const publicKeyPem = String(
@@ -11096,44 +11687,56 @@ async function readIdentity(path = identityPath()) {
11096
11687
  }
11097
11688
  var automaticIdentityBootstrap = null;
11098
11689
  async function ensureSigningIdentity() {
11099
- if (automaticIdentityBootstrap) return automaticIdentityBootstrap;
11100
- const existing = await readIdentity();
11101
- if (automaticIdentityBootstrap) return automaticIdentityBootstrap;
11690
+ const keyPath = identityPath();
11691
+ if (automaticIdentityBootstrap?.keyPath === keyPath) {
11692
+ return automaticIdentityBootstrap.promise;
11693
+ }
11694
+ const existing = await readIdentity(keyPath);
11695
+ if (automaticIdentityBootstrap?.keyPath === keyPath) {
11696
+ return automaticIdentityBootstrap.promise;
11697
+ }
11102
11698
  if (existing) return existing;
11103
- if (!automaticIdentityBootstrap) {
11104
- automaticIdentityBootstrap = (async () => {
11105
- await bootstrapAgentIdentity({});
11106
- const created = await readIdentity();
11107
- if (!created) {
11108
- throw new McpPublicError(
11109
- "Remembrance could not initialize the local signing identity. Run bootstrap_agent_identity and retry."
11110
- );
11111
- }
11112
- return created;
11113
- })().finally(() => {
11699
+ const promise = (async () => {
11700
+ await bootstrapAgentIdentity({ key_path: keyPath });
11701
+ const created = await readIdentity(keyPath);
11702
+ if (!created) {
11703
+ throw new McpPublicError(
11704
+ "Remembrance could not initialize the local signing identity. Run bootstrap_agent_identity and retry."
11705
+ );
11706
+ }
11707
+ return created;
11708
+ })();
11709
+ automaticIdentityBootstrap = { keyPath, promise };
11710
+ const clearBootstrap = () => {
11711
+ if (automaticIdentityBootstrap?.promise === promise) {
11114
11712
  automaticIdentityBootstrap = null;
11115
- });
11116
- }
11117
- return automaticIdentityBootstrap;
11713
+ }
11714
+ };
11715
+ void promise.then(clearBootstrap, clearBootstrap);
11716
+ return promise;
11118
11717
  }
11119
11718
  async function callRemembrance(definition, rawArguments, options = {}) {
11719
+ const access = options.access ?? resolveApiAccess();
11120
11720
  const parsed = definition.schema.parse(rawArguments ?? {});
11721
+ const projectKey = definition.name === "query_skills" || definition.name === "invoke_skill" ? await localProjectKey().catch(() => null) : null;
11722
+ const callerClientContext = parsed.client_context ?? {};
11121
11723
  const payload = definition.name === "query_skills" || definition.name === "invoke_skill" ? {
11122
11724
  ...parsed,
11123
11725
  client_context: {
11124
- ...parsed.client_context ?? {},
11125
- surface: "mcp"
11726
+ ...callerClientContext,
11727
+ surface: "mcp",
11728
+ ...callerClientContext.project_key || !projectKey ? {} : { project_key: projectKey }
11126
11729
  }
11127
11730
  } : parsed;
11128
11731
  const headers = {
11129
11732
  "content-type": "application/json",
11130
11733
  "user-agent": `@remembrance-ai/mcp-server/${SERVER_VERSION}`
11131
11734
  };
11132
- const credential = resolveApiCredential();
11735
+ const credential = access.credential;
11133
11736
  const configurationError = remoteConfigurationError(
11134
- apiConfiguration.source,
11737
+ access.configuration.source,
11135
11738
  credential.source,
11136
- apiConfiguration.issue
11739
+ access.configuration.issue
11137
11740
  );
11138
11741
  if (configurationError && definition.name !== "get_connection_status") {
11139
11742
  throw new McpPublicError(configurationError);
@@ -11143,11 +11746,21 @@ async function callRemembrance(definition, rawArguments, options = {}) {
11143
11746
  headers["x-remembrance-api-key"] = apiKey;
11144
11747
  }
11145
11748
  if (!options.skipEconomicsSession && definition.name !== "register_agent_key" && definition.name !== "request_attestation_challenge" && definition.name !== "get_connection_status") {
11146
- const economicsSession = await ensureEconomicsSessionToken().catch(
11147
- () => null
11148
- );
11149
- if (economicsSession) {
11150
- headers["x-remembrance-economics-session"] = economicsSession;
11749
+ let principalSession = currentPrincipalSessionToken(access);
11750
+ if (!principalSession && PRINCIPAL_SESSION_AWAITED_TOOLS.has(definition.name)) {
11751
+ principalSession = await ensurePrincipalSessionToken(access).catch(
11752
+ () => null
11753
+ );
11754
+ }
11755
+ if (!principalSession && PRINCIPAL_SESSION_REQUIRED_TOOLS.has(definition.name)) {
11756
+ throw new McpPublicError(
11757
+ "This operation requires a verified local installation session. Check the organization key and local TOFU identity, then retry."
11758
+ );
11759
+ }
11760
+ if (principalSession) {
11761
+ headers["x-remembrance-principal-session"] = principalSession;
11762
+ } else if (definition.name === "query_skills" || definition.name === "invoke_skill") {
11763
+ void ensurePrincipalSessionToken(access).catch(() => null);
11151
11764
  }
11152
11765
  }
11153
11766
  const endpoint = endpointFor(definition, payload);
@@ -11159,25 +11772,51 @@ async function callRemembrance(definition, rawArguments, options = {}) {
11159
11772
  let response;
11160
11773
  let body;
11161
11774
  try {
11162
- response = await fetch(`${apiBase}${endpoint}`, {
11163
- method: definition.method ?? "POST",
11164
- headers,
11165
- body: definition.method === "GET" ? void 0 : JSON.stringify(payload),
11166
- signal: controller.signal
11167
- });
11168
- try {
11169
- body = await readBoundedResponseJson(response);
11170
- } catch {
11171
- if (controller.signal.aborted) {
11172
- return {
11173
- ok: false,
11174
- status: 0,
11175
- error: "Remembrance API request timed out."
11775
+ const execute = async () => {
11776
+ const nextResponse = await fetch(
11777
+ `${access.configuration.baseUrl}${endpoint}`,
11778
+ {
11779
+ method: definition.method ?? "POST",
11780
+ headers,
11781
+ body: definition.method === "GET" ? void 0 : JSON.stringify(payload),
11782
+ signal: controller.signal
11783
+ }
11784
+ );
11785
+ let nextBody;
11786
+ try {
11787
+ nextBody = await readBoundedResponseJson(nextResponse);
11788
+ } catch {
11789
+ if (controller.signal.aborted) {
11790
+ throw new DOMException("Request timed out", "AbortError");
11791
+ }
11792
+ nextBody = {
11793
+ error: nextResponse.ok ? "Remembrance API returned an unreadable or oversized response." : "Remembrance API request failed."
11176
11794
  };
11177
11795
  }
11178
- body = {
11179
- error: response.ok ? "Remembrance API returned an unreadable or oversized response." : "Remembrance API request failed."
11180
- };
11796
+ return { response: nextResponse, body: nextBody };
11797
+ };
11798
+ ({ response, body } = await execute());
11799
+ const sentPrincipalSession = headers["x-remembrance-principal-session"] ?? null;
11800
+ const refreshSignaled = response.headers.get("x-remembrance-principal-session-status") === "refresh_required";
11801
+ const authenticationFailed = principalSessionAuthenticationFailed(
11802
+ response,
11803
+ body
11804
+ );
11805
+ if (sentPrincipalSession && refreshSignaled && !authenticationFailed) {
11806
+ cachedPrincipalSession = null;
11807
+ void ensurePrincipalSessionToken(access).catch(() => null);
11808
+ } else if (sentPrincipalSession && authenticationFailed) {
11809
+ cachedPrincipalSession = null;
11810
+ const refreshed = await ensurePrincipalSessionToken(access).catch(
11811
+ () => null
11812
+ );
11813
+ if (refreshed && refreshed !== sentPrincipalSession) {
11814
+ headers["x-remembrance-principal-session"] = refreshed;
11815
+ ({ response, body } = await execute());
11816
+ } else if (!PRINCIPAL_SESSION_REQUIRED_TOOLS.has(definition.name)) {
11817
+ delete headers["x-remembrance-principal-session"];
11818
+ ({ response, body } = await execute());
11819
+ }
11181
11820
  }
11182
11821
  } catch (error) {
11183
11822
  return {
@@ -11188,7 +11827,10 @@ async function callRemembrance(definition, rawArguments, options = {}) {
11188
11827
  } finally {
11189
11828
  clearTimeout(timeout);
11190
11829
  }
11191
- const verifiedBody = definition.name === "get_value_proof" && response.ok ? await verifySignedValueProofWithKeyRefresh(body, fetchValueProofKeySet) : body;
11830
+ const verifiedBody = definition.name === "get_value_proof" && response.ok ? await verifySignedValueProofWithKeyRefresh(
11831
+ body,
11832
+ (forceRefresh) => fetchValueProofKeySet(forceRefresh, access)
11833
+ ) : body;
11192
11834
  return {
11193
11835
  ok: response.ok,
11194
11836
  status: response.status,
@@ -11198,27 +11840,52 @@ async function callRemembrance(definition, rawArguments, options = {}) {
11198
11840
  body: verifiedBody
11199
11841
  };
11200
11842
  }
11201
- async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
11843
+ var cachedLocalProjectKey;
11844
+ async function localProjectKey() {
11845
+ const configured = process.env.REMEMBRANCE_PROJECT_KEY?.trim() ?? "";
11846
+ if (/^prj_[A-Za-z0-9_-]{12,120}$/.test(configured)) return configured;
11847
+ const projectPath = (process.env.REMEMBRANCE_PROJECT_PATH ?? process.env.PWD ?? process.cwd()).trim();
11848
+ if (!projectPath) return null;
11849
+ const identity = await ensureSigningIdentity();
11850
+ if (cachedLocalProjectKey?.identityKeyId === identity.key_id && cachedLocalProjectKey.projectPath === projectPath) {
11851
+ return cachedLocalProjectKey.value;
11852
+ }
11853
+ const value = `prj_${createHmac("sha256", identity.private_key).update(`remembrance-project-v1:${projectPath}`, "utf8").digest("base64url").slice(0, 32)}`;
11854
+ cachedLocalProjectKey = {
11855
+ identityKeyId: identity.key_id,
11856
+ projectPath,
11857
+ value
11858
+ };
11859
+ return value;
11860
+ }
11861
+ function principalSessionAuthenticationFailed(response, body) {
11862
+ if (response.status !== 401 && response.status !== 403) return false;
11863
+ if (!isRecord4(body) || typeof body.error !== "string") return false;
11864
+ return /principal session|economics session|installation session|agent principal|installation principal/i.test(
11865
+ body.error
11866
+ );
11867
+ }
11868
+ async function runLocalConnectionDoctor(options, execution = "mcp_tool", access = resolveApiAccess()) {
11202
11869
  const connectionDefinition = mustFindTool("get_connection_status");
11203
11870
  const localConfigurationError = remoteConfigurationError(
11204
- apiConfiguration.source,
11205
- resolveApiCredential().source,
11206
- apiConfiguration.issue
11871
+ access.configuration.source,
11872
+ access.credential.source,
11873
+ access.configuration.issue
11207
11874
  );
11208
11875
  const connectionPromise = localConfigurationError ? Promise.resolve({
11209
11876
  ok: false,
11210
11877
  status: 0,
11211
11878
  error: localConfigurationError
11212
- }) : callDoctorReadProbe(connectionDefinition, {});
11213
- const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }) : Promise.resolve(null);
11879
+ }) : callDoctorReadProbe(connectionDefinition, {}, access);
11880
+ const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }, access) : Promise.resolve(null);
11214
11881
  const [connectionResult, catalogResult, clientUpdate] = await Promise.all([
11215
11882
  connectionPromise,
11216
11883
  catalogPromise,
11217
- currentClientUpdate()
11884
+ currentClientUpdate(access)
11218
11885
  ]);
11219
11886
  const connectionStatus = localConnectionStatus(connectionResult, {
11220
- apiBase,
11221
- apiUrlSource: apiConfiguration.source,
11887
+ apiBase: access.configuration.baseUrl,
11888
+ apiUrlSource: access.configuration.source,
11222
11889
  pluginVersion: SERVER_VERSION
11223
11890
  });
11224
11891
  const activeReadProbe = {
@@ -11244,23 +11911,25 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool") {
11244
11911
  client_update: clientUpdate
11245
11912
  });
11246
11913
  }
11247
- async function currentClientUpdate() {
11914
+ async function currentClientUpdate(access = resolveApiAccess()) {
11248
11915
  const configuredSurface = process.env.REMEMBRANCE_PLUGIN_HOST?.trim().toLowerCase();
11249
11916
  const surface = configuredSurface && isPluginHostSurface(configuredSurface) ? configuredSurface : "mcp";
11250
11917
  return checkClientUpdate({
11251
- apiBase,
11918
+ apiBase: access.configuration.baseUrl,
11252
11919
  currentVersion: SERVER_VERSION,
11253
11920
  surface
11254
11921
  });
11255
11922
  }
11256
- async function callDoctorReadProbe(definition, payload) {
11923
+ async function callDoctorReadProbe(definition, payload, access) {
11257
11924
  const first = await callRemembrance(definition, payload, {
11258
11925
  skipEconomicsSession: true,
11259
- timeoutMs: DOCTOR_PROBE_TIMEOUT_MS
11926
+ timeoutMs: DOCTOR_PROBE_TIMEOUT_MS,
11927
+ access
11260
11928
  });
11261
11929
  return isTransientDoctorProbeFailure(first) ? callRemembrance(definition, payload, {
11262
11930
  skipEconomicsSession: true,
11263
- timeoutMs: DOCTOR_PROBE_TIMEOUT_MS
11931
+ timeoutMs: DOCTOR_PROBE_TIMEOUT_MS,
11932
+ access
11264
11933
  }) : first;
11265
11934
  }
11266
11935
  function isTransientDoctorProbeFailure(result) {
@@ -11324,16 +11993,16 @@ async function runConnectionDoctorCli(args = process.argv.slice(3)) {
11324
11993
  );
11325
11994
  return report.status === "blocked" ? 1 : 0;
11326
11995
  }
11327
- async function fetchValueProofKeySet(forceRefresh) {
11996
+ async function fetchValueProofKeySet(forceRefresh, access = resolveApiAccess()) {
11328
11997
  const now = Date.now();
11329
- if (!forceRefresh && cachedValueProofKeys && cachedValueProofKeys.expiresAt > now) {
11998
+ if (!forceRefresh && cachedValueProofKeys && cachedValueProofKeys.apiBase === access.configuration.baseUrl && cachedValueProofKeys.expiresAt > now) {
11330
11999
  return cachedValueProofKeys.value;
11331
12000
  }
11332
12001
  const controller = new AbortController();
11333
12002
  const timeout = setTimeout(() => controller.abort(), apiTimeoutMs());
11334
12003
  try {
11335
12004
  const response = await fetch(
11336
- `${apiBase}/.well-known/remembrance-value-proof-keys.json`,
12005
+ `${access.configuration.baseUrl}/.well-known/remembrance-value-proof-keys.json`,
11337
12006
  {
11338
12007
  headers: { accept: "application/json" },
11339
12008
  signal: controller.signal
@@ -11347,7 +12016,8 @@ async function fetchValueProofKeySet(forceRefresh) {
11347
12016
  const value = await readBoundedResponseJson(response);
11348
12017
  cachedValueProofKeys = {
11349
12018
  value,
11350
- expiresAt: now + valueProofKeyCacheTtlMs(response.headers)
12019
+ expiresAt: now + valueProofKeyCacheTtlMs(response.headers),
12020
+ apiBase: access.configuration.baseUrl
11351
12021
  };
11352
12022
  return value;
11353
12023
  } finally {
@@ -11465,18 +12135,38 @@ async function fetchSkillCatalog(input) {
11465
12135
  }
11466
12136
  return skillCatalogResponseSchema.parse(response.body);
11467
12137
  }
11468
- async function ensureEconomicsSessionToken() {
11469
- const identity = await readIdentity();
11470
- if (!identity) return null;
12138
+ async function ensurePrincipalSessionToken(access = resolveApiAccess()) {
12139
+ if (isUnusableCredentialSource(access.configuration.source) || isUnusableCredentialSource(access.credential.source)) {
12140
+ return null;
12141
+ }
12142
+ const identity = await ensureSigningIdentity();
11471
12143
  const identityKey = `${identity.provider}:${identity.key_id}`;
11472
- if (cachedEconomicsSession && cachedEconomicsSession.identityKey === identityKey && cachedEconomicsSession.expiresAt > Date.now() + 6e4) {
11473
- return cachedEconomicsSession.token;
12144
+ const accessKey = apiAccessKey(access);
12145
+ if (cachedPrincipalSession && cachedPrincipalSession.identityKey === identityKey && cachedPrincipalSession.accessKey === accessKey && cachedPrincipalSession.expiresAt > Date.now() + 6e4) {
12146
+ return cachedPrincipalSession.token;
11474
12147
  }
11475
- const challengeResponse = await directEconomicsSessionRequest({
12148
+ const memberLinkToken = access.memberLinkToken;
12149
+ const challengePayload = {
11476
12150
  action: "challenge",
11477
12151
  provider: identity.provider,
11478
- key_id: identity.key_id
11479
- });
12152
+ key_id: identity.key_id,
12153
+ runtime_profile: localRuntimeProfile(identity),
12154
+ ...memberLinkToken ? { member_link_token: memberLinkToken } : {}
12155
+ };
12156
+ let challengeResponse = await directPrincipalSessionRequest(
12157
+ challengePayload,
12158
+ access
12159
+ );
12160
+ if (!challengeResponse) {
12161
+ await bootstrapAgentIdentity({}, access).catch(() => null);
12162
+ challengeResponse = await directPrincipalSessionRequest(
12163
+ {
12164
+ ...challengePayload,
12165
+ member_link_token: void 0
12166
+ },
12167
+ access
12168
+ );
12169
+ }
11480
12170
  const challenge = challengeResponse;
11481
12171
  if (!challenge.challenge_id || !challenge.signing_payload) return null;
11482
12172
  const signature = signPayload(
@@ -11484,38 +12174,100 @@ async function ensureEconomicsSessionToken() {
11484
12174
  Buffer.from(challenge.signing_payload),
11485
12175
  createPrivateKey2(identity.private_key)
11486
12176
  ).toString("base64url");
11487
- const exchangeResponse = await directEconomicsSessionRequest({
11488
- action: "exchange",
11489
- provider: identity.provider,
11490
- key_id: identity.key_id,
11491
- challenge_id: challenge.challenge_id,
11492
- signature
11493
- });
12177
+ const exchangeResponse = await directPrincipalSessionRequest(
12178
+ {
12179
+ action: "exchange",
12180
+ provider: identity.provider,
12181
+ key_id: identity.key_id,
12182
+ challenge_id: challenge.challenge_id,
12183
+ signature
12184
+ },
12185
+ access
12186
+ );
11494
12187
  const exchange = exchangeResponse;
11495
12188
  if (!exchange.session_token || !exchange.expires_at) return null;
11496
- cachedEconomicsSession = {
12189
+ cachedPrincipalSession = {
11497
12190
  token: exchange.session_token,
11498
12191
  expiresAt: new Date(exchange.expires_at).getTime(),
11499
- identityKey
12192
+ identityKey,
12193
+ accessKey
11500
12194
  };
11501
- return cachedEconomicsSession.token;
12195
+ return cachedPrincipalSession.token;
11502
12196
  }
11503
- async function directEconomicsSessionRequest(payload) {
12197
+ function currentPrincipalSessionToken(access) {
12198
+ if (!cachedPrincipalSession) return null;
12199
+ if (cachedPrincipalSession.accessKey !== apiAccessKey(access) || cachedPrincipalSession.expiresAt <= Date.now() + 6e4) {
12200
+ cachedPrincipalSession = null;
12201
+ return null;
12202
+ }
12203
+ return cachedPrincipalSession.token;
12204
+ }
12205
+ async function directPrincipalSessionRequest(payload, access) {
11504
12206
  const controller = new AbortController();
11505
12207
  const timeout = setTimeout(() => controller.abort(), apiTimeoutMs());
11506
12208
  try {
11507
- const response = await fetch(`${apiBase}/api/v1/agent/economics/session`, {
11508
- method: "POST",
11509
- headers: { "content-type": "application/json" },
11510
- body: JSON.stringify(payload),
11511
- signal: controller.signal
11512
- });
12209
+ const response = await fetch(
12210
+ `${access.configuration.baseUrl}/api/v1/agent/principal-sessions`,
12211
+ {
12212
+ method: "POST",
12213
+ headers: { "content-type": "application/json" },
12214
+ body: JSON.stringify(payload),
12215
+ signal: controller.signal
12216
+ }
12217
+ );
11513
12218
  if (!response.ok) return null;
11514
12219
  return response.json();
11515
12220
  } finally {
11516
12221
  clearTimeout(timeout);
11517
12222
  }
11518
12223
  }
12224
+ function normalizedMcpClientInfo(value) {
12225
+ if (!isRecord4(value)) return null;
12226
+ const name = boundedLocalProfileField(value.name);
12227
+ if (!name) return null;
12228
+ return {
12229
+ name,
12230
+ version: boundedLocalProfileField(value.version)
12231
+ };
12232
+ }
12233
+ function localRuntimeProfile(identity) {
12234
+ const configuredSurface = String(process.env.REMEMBRANCE_PLUGIN_HOST ?? "").trim().toLowerCase();
12235
+ const runtime = isPluginHostSurface(configuredSurface) ? configuredSurface : "other";
12236
+ const clientName = connectedClientInfo?.name ?? agentHostBySurface(runtime)?.host_name ?? "MCP";
12237
+ const hostSurface = localRuntimeHostSurface(runtime, clientName);
12238
+ const profileDigest = createHash3("sha256").update(
12239
+ [identity.key_id, runtime, hostSurface, clientName.toLowerCase()].join(
12240
+ ":"
12241
+ ),
12242
+ "utf8"
12243
+ ).digest("base64url");
12244
+ return {
12245
+ runtime,
12246
+ surface: "mcp",
12247
+ host_surface: hostSurface,
12248
+ client_name: clientName,
12249
+ client_version: SERVER_VERSION,
12250
+ runtime_version: connectedClientInfo?.version ?? null,
12251
+ profile_key: `rpf_${profileDigest}`
12252
+ };
12253
+ }
12254
+ function localRuntimeHostSurface(runtime, clientName) {
12255
+ const configured = String(process.env.REMEMBRANCE_HOST_SURFACE ?? "").trim().toLowerCase();
12256
+ if (configured === "desktop" || configured === "cli" || configured === "extension" || configured === "gateway" || configured === "unknown") {
12257
+ return configured;
12258
+ }
12259
+ if (/desktop|chatgpt|codex app/i.test(clientName)) return "desktop";
12260
+ if (/cursor|visual studio|vscode/i.test(clientName)) return "extension";
12261
+ if (runtime === "openclaw") return "gateway";
12262
+ if (runtime === "cursor" || runtime === "vs_code") return "extension";
12263
+ if (runtime === "claude_code" || runtime === "opencode") return "cli";
12264
+ return "unknown";
12265
+ }
12266
+ function boundedLocalProfileField(value) {
12267
+ if (typeof value !== "string") return null;
12268
+ const normalized = value.trim().replace(/[\u0000-\u001f\u007f]/g, "");
12269
+ return normalized ? normalized.slice(0, 512) : null;
12270
+ }
11519
12271
  function mustFindTool(name) {
11520
12272
  const tool2 = tools.find((item) => item.name === name);
11521
12273
  if (!tool2) {
@@ -11577,6 +12329,7 @@ export {
11577
12329
  readJsonRpcMessages,
11578
12330
  remoteConfigurationError,
11579
12331
  reportDegradedClientHealth,
12332
+ resetPrincipalSessionCacheForTests,
11580
12333
  resetValueProofKeyCacheForTests,
11581
12334
  runConnectionDoctorCli,
11582
12335
  runLocalConnectionDoctor