@sanctuary-framework/mcp-server 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16312,6 +16312,27 @@ async function handleApprovalInboxRoute(deps, req, res) {
16312
16312
  await handleStream2(deps, res);
16313
16313
  return true;
16314
16314
  }
16315
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
16316
+ const limit = parseLimit2(
16317
+ url.searchParams.get("limit"),
16318
+ APPROVAL_INBOX_DEFAULT_LIMIT,
16319
+ APPROVAL_INBOX_MAX_LIMIT
16320
+ );
16321
+ const statusRaw = url.searchParams.get("status");
16322
+ const sinceTs = url.searchParams.get("since") ?? void 0;
16323
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
16324
+ const filterStatus = statusRaw && isStatusFilter(statusRaw) && statusRaw !== "pending" ? statusRaw : void 0;
16325
+ const entries = await deps.aggregator.getHistory(
16326
+ {
16327
+ limit,
16328
+ ...filterStatus !== void 0 ? { status: filterStatus } : {},
16329
+ ...sinceTs !== void 0 ? { sinceTs } : {}
16330
+ },
16331
+ operatorId
16332
+ );
16333
+ writeJSON4(res, 200, { ok: true, data: { entries } });
16334
+ return true;
16335
+ }
16315
16336
  if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
16316
16337
  const limit = parseLimit2(
16317
16338
  url.searchParams.get("limit"),
@@ -16334,11 +16355,39 @@ async function handleApprovalInboxRoute(deps, req, res) {
16334
16355
  writeJSON4(res, 404, { ok: false, error: "not_found", path });
16335
16356
  return true;
16336
16357
  }
16337
- if (method === "GET" && entryMatch.action === null) {
16338
- const entries = await deps.aggregator.list({ limit: APPROVAL_INBOX_MAX_LIMIT });
16339
- const entry = entries.find(
16340
- (e) => e.aggregator_id === entryMatch.aggregatorId
16358
+ if (method === "GET" && entryMatch.action === "audit-trail") {
16359
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
16360
+ if (!entry) {
16361
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
16362
+ return true;
16363
+ }
16364
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
16365
+ const trail = await deps.aggregator.getAuditTrail(
16366
+ entryMatch.aggregatorId,
16367
+ operatorId
16341
16368
  );
16369
+ writeJSON4(res, 200, { ok: true, data: { entry, audit_trail: trail } });
16370
+ return true;
16371
+ }
16372
+ if (method === "GET" && entryMatch.action === "payload") {
16373
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
16374
+ if (!entry) {
16375
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
16376
+ return true;
16377
+ }
16378
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
16379
+ const payload = await deps.aggregator.getFullPayloadWithAudit(
16380
+ entryMatch.aggregatorId,
16381
+ operatorId
16382
+ );
16383
+ writeJSON4(res, 200, {
16384
+ ok: true,
16385
+ data: { entry, request_payload: payload }
16386
+ });
16387
+ return true;
16388
+ }
16389
+ if (method === "GET" && entryMatch.action === null) {
16390
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
16342
16391
  if (!entry) {
16343
16392
  writeJSON4(res, 404, { ok: false, error: "not_found" });
16344
16393
  return true;
@@ -19325,7 +19374,10 @@ var APPROVAL_AGGREGATOR_HKDF_INFO = "l2-approval-aggregator-v1";
19325
19374
  var APPROVAL_AGGREGATOR_AUDIT_OPS = {
19326
19375
  AGGREGATED: "cross_harness_approval_aggregated",
19327
19376
  RESOLVED: "cross_harness_approval_resolved",
19328
- DEDUPED: "cross_harness_approval_deduped"
19377
+ DEDUPED: "cross_harness_approval_deduped",
19378
+ PAYLOAD_DECRYPTED: "cross_harness_approval_payload_decrypted",
19379
+ AUDIT_TRAIL_VIEWED: "cross_harness_approval_audit_trail_viewed",
19380
+ REPLAYED: "cross_harness_approval_replayed"
19329
19381
  };
19330
19382
  var DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
19331
19383
  var DEFAULT_MAX_LIST_LIMIT = 200;
@@ -19341,6 +19393,8 @@ var ApprovalAggregator = class {
19341
19393
  now;
19342
19394
  resolveSourceContext;
19343
19395
  resolveHubInboxItemId;
19396
+ payloadStore;
19397
+ resolveEnforcementChain;
19344
19398
  /** Cached entries by `aggregator_id`. */
19345
19399
  entries = /* @__PURE__ */ new Map();
19346
19400
  /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
@@ -19370,6 +19424,14 @@ var ApprovalAggregator = class {
19370
19424
  source_agent_id: this.fortressId
19371
19425
  }));
19372
19426
  this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
19427
+ this.payloadStore = deps.payloadStore ?? null;
19428
+ this.resolveEnforcementChain = deps.resolveEnforcementChain ?? ((event) => [
19429
+ {
19430
+ layer: "l2",
19431
+ event: `approval_required:${event.operation}`,
19432
+ timestamp: event.request_timestamp
19433
+ }
19434
+ ]);
19373
19435
  }
19374
19436
  /**
19375
19437
  * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
@@ -19420,13 +19482,152 @@ var ApprovalAggregator = class {
19420
19482
  }
19421
19483
  /**
19422
19484
  * Return the original (unhashed) request payload for the entry. Returns
19423
- * `null` when the entry is unknown or the payload was evicted (e.g. the
19424
- * process restarted; payloads are in-memory only at v1.3 Upsilon-1).
19485
+ * `null` when the entry is unknown. When the in-memory payload map has
19486
+ * been evicted (e.g. after a server restart) and a `payloadStore` was
19487
+ * provided, the at-rest bundle is decrypted and the in-memory map is
19488
+ * refilled. Audit emission lives on the `*WithAudit` variant; this base
19489
+ * accessor is silent so internal callers can read without polluting the
19490
+ * audit trail.
19425
19491
  */
19426
19492
  async getFullPayload(aggregatorId) {
19427
19493
  await this.hydrate();
19428
19494
  if (!this.entries.has(aggregatorId)) return null;
19429
- return this.fullPayloads.get(aggregatorId) ?? null;
19495
+ const cached = this.fullPayloads.get(aggregatorId);
19496
+ if (cached !== void 0) return cached;
19497
+ if (this.payloadStore) {
19498
+ try {
19499
+ const restored = await this.payloadStore.loadPayload(aggregatorId);
19500
+ if (restored !== null) {
19501
+ this.fullPayloads.set(aggregatorId, restored);
19502
+ return restored;
19503
+ }
19504
+ } catch {
19505
+ }
19506
+ }
19507
+ return null;
19508
+ }
19509
+ /**
19510
+ * Return the entry record for the given id, or null when unknown.
19511
+ * Idempotent. v1.3 Upsilon-3.
19512
+ */
19513
+ async getEntry(aggregatorId) {
19514
+ await this.hydrate();
19515
+ return this.entries.get(aggregatorId) ?? null;
19516
+ }
19517
+ /**
19518
+ * Audited variant of `getFullPayload`. Emits the
19519
+ * `cross_harness_approval_payload_decrypted` audit event before
19520
+ * returning. Used by the operator-facing /payload replay route.
19521
+ * v1.3 Upsilon-3.
19522
+ */
19523
+ async getFullPayloadWithAudit(aggregatorId, operatorId) {
19524
+ const payload = await this.getFullPayload(aggregatorId);
19525
+ if (payload === null) return null;
19526
+ const entry = this.entries.get(aggregatorId);
19527
+ this.auditLog.append(
19528
+ "l2",
19529
+ APPROVAL_AGGREGATOR_AUDIT_OPS.PAYLOAD_DECRYPTED,
19530
+ operatorId,
19531
+ {
19532
+ aggregator_id: aggregatorId,
19533
+ ...entry ? {
19534
+ source_harness: entry.source_harness,
19535
+ source_agent_id: entry.source_agent_id,
19536
+ entry_status: entry.status
19537
+ } : {}
19538
+ }
19539
+ );
19540
+ return payload;
19541
+ }
19542
+ /**
19543
+ * Return the audit-log entries that led to and surround this approval.
19544
+ * Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
19545
+ * DEDUPED, replay events) all carry `details.aggregator_id` and link
19546
+ * directly. Gate-side emissions (`gate_*:operation`) do not carry the
19547
+ * aggregator id at v1.3, so they are matched via timestamp window
19548
+ * (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
19549
+ * pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
19550
+ * v1.3 Upsilon-3.
19551
+ */
19552
+ async getAuditTrail(aggregatorId, operatorId) {
19553
+ await this.hydrate();
19554
+ const entry = this.entries.get(aggregatorId);
19555
+ if (!entry) {
19556
+ return [];
19557
+ }
19558
+ const sinceMs = Date.parse(entry.created_at) - 1e3;
19559
+ const sinceIso = new Date(sinceMs).toISOString();
19560
+ const queried = await this.auditLog.query({ since: sinceIso, limit: 1e3 });
19561
+ const operationPart = entry.policy_rule_id.includes(":") ? entry.policy_rule_id.slice(entry.policy_rule_id.indexOf(":") + 1) : entry.policy_rule_id;
19562
+ const lifetimeStart = sinceMs;
19563
+ const lifetimeEnd = entry.resolved_at ? Date.parse(entry.resolved_at) + 1e3 : Date.parse(entry.expires_at) + 1e3;
19564
+ const matches = [];
19565
+ for (const audit of queried.entries) {
19566
+ const detailsId = audit.details !== void 0 ? audit.details["aggregator_id"] : void 0;
19567
+ if (detailsId === aggregatorId) {
19568
+ matches.push(audit);
19569
+ continue;
19570
+ }
19571
+ const auditMs = Date.parse(audit.timestamp);
19572
+ if (auditMs < lifetimeStart || auditMs > lifetimeEnd) continue;
19573
+ if (audit.operation.endsWith(`:${operationPart}`)) {
19574
+ matches.push(audit);
19575
+ }
19576
+ }
19577
+ matches.sort(
19578
+ (a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0
19579
+ );
19580
+ this.auditLog.append(
19581
+ "l2",
19582
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AUDIT_TRAIL_VIEWED,
19583
+ operatorId,
19584
+ {
19585
+ aggregator_id: aggregatorId,
19586
+ entry_status: entry.status,
19587
+ match_count: matches.length
19588
+ }
19589
+ );
19590
+ return matches;
19591
+ }
19592
+ /**
19593
+ * List historical (resolved) approvals. Excludes pending entries by
19594
+ * design: `list()` is the pending-inbox surface and `getHistory()` is
19595
+ * the resolved-replay surface. Emits REPLAYED on each call. v1.3
19596
+ * Upsilon-3.
19597
+ */
19598
+ async getHistory(opts, operatorId) {
19599
+ await this.hydrate();
19600
+ await this.expireStale();
19601
+ const limit = Math.min(
19602
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
19603
+ this.maxListLimit
19604
+ );
19605
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
19606
+ const matching = [];
19607
+ for (const entry of this.entries.values()) {
19608
+ if (entry.status === "pending") continue;
19609
+ if (opts?.status && entry.status !== opts.status) continue;
19610
+ const stamp = Date.parse(entry.resolved_at ?? entry.created_at);
19611
+ if (stamp < sinceMs) continue;
19612
+ matching.push(entry);
19613
+ }
19614
+ matching.sort((a, b) => {
19615
+ const aStamp = a.resolved_at ?? a.created_at;
19616
+ const bStamp = b.resolved_at ?? b.created_at;
19617
+ return bStamp.localeCompare(aStamp);
19618
+ });
19619
+ const sliced = matching.slice(0, limit);
19620
+ this.auditLog.append(
19621
+ "l2",
19622
+ APPROVAL_AGGREGATOR_AUDIT_OPS.REPLAYED,
19623
+ operatorId,
19624
+ {
19625
+ result_count: sliced.length,
19626
+ ...opts?.status !== void 0 ? { status_filter: opts.status } : {},
19627
+ ...opts?.sinceTs !== void 0 ? { since: opts.sinceTs } : {}
19628
+ }
19629
+ );
19630
+ return sliced;
19430
19631
  }
19431
19632
  /**
19432
19633
  * Resolve an entry. Used by both:
@@ -19500,6 +19701,7 @@ var ApprovalAggregator = class {
19500
19701
  const now = this.now();
19501
19702
  const expires = new Date(now.getTime() + this.pendingTtlMs);
19502
19703
  const hubInboxId = this.resolveHubInboxItemId(event);
19704
+ const enforcementChain = this.resolveEnforcementChain(event);
19503
19705
  const entry = {
19504
19706
  aggregator_id: id,
19505
19707
  source_harness: ctx.source_harness,
@@ -19511,13 +19713,20 @@ var ApprovalAggregator = class {
19511
19713
  status: "pending",
19512
19714
  created_at: now.toISOString(),
19513
19715
  expires_at: expires.toISOString(),
19514
- ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
19716
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
19717
+ ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
19515
19718
  };
19516
19719
  this.entries.set(id, entry);
19517
19720
  this.dedupIndex.set(dedupKey, id);
19518
19721
  this.correlationIndex.set(event.correlation_id, id);
19519
19722
  this.fullPayloads.set(id, event.context);
19520
19723
  await this.persist(entry);
19724
+ if (this.payloadStore) {
19725
+ try {
19726
+ await this.payloadStore.savePayload(id, event.context);
19727
+ } catch {
19728
+ }
19729
+ }
19521
19730
  this.auditLog.append(
19522
19731
  "l2",
19523
19732
  APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
@@ -19826,6 +20035,143 @@ function makeRedirectResolverFromPolicySupplier(supplier) {
19826
20035
  };
19827
20036
  }
19828
20037
 
20038
+ // src/principal-policy/aggregator-store.ts
20039
+ init_encryption();
20040
+ init_encoding();
20041
+ var AGGREGATOR_PAYLOAD_NAMESPACE = "_approval_aggregator_payloads";
20042
+ var AGGREGATOR_PAYLOAD_KEY_PREFIX = "payload.";
20043
+ var HKDF_INFO = "l2-approval-aggregator-payload-v1";
20044
+ var DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS = 30;
20045
+ var MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
20046
+ var AggregatorPayloadStore = class {
20047
+ storage;
20048
+ encryptionKey;
20049
+ fortressId;
20050
+ retentionDays;
20051
+ constructor(opts) {
20052
+ this.storage = opts.storage;
20053
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO);
20054
+ this.fortressId = opts.fortressId;
20055
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS;
20056
+ }
20057
+ /**
20058
+ * Persist `payload` under the given aggregator_id. Idempotent; calling
20059
+ * twice with the same id rewrites the bundle (retention_until is
20060
+ * recomputed). Returns the bundle's retention_until ISO-8601 timestamp
20061
+ * so callers can log it.
20062
+ */
20063
+ async savePayload(aggregatorId, payload) {
20064
+ const now = /* @__PURE__ */ new Date();
20065
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
20066
+ const retentionUntil = new Date(now.getTime() + retentionMs);
20067
+ const bundle = {
20068
+ version: 1,
20069
+ aggregator_id: aggregatorId,
20070
+ fortress_id: this.fortressId,
20071
+ created_at: now.toISOString(),
20072
+ retention_until: retentionUntil.toISOString(),
20073
+ payload
20074
+ };
20075
+ const aad = stringToBytes(aggregatorId);
20076
+ const plaintext = stringToBytes(JSON.stringify(bundle));
20077
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
20078
+ await this.storage.write(
20079
+ AGGREGATOR_PAYLOAD_NAMESPACE,
20080
+ payloadKey(aggregatorId),
20081
+ stringToBytes(JSON.stringify(envelope))
20082
+ );
20083
+ return bundle.retention_until;
20084
+ }
20085
+ /**
20086
+ * Read the persisted payload for the aggregator_id. Returns null if no
20087
+ * bundle exists, the bundle is corrupted, or AAD binding fails.
20088
+ */
20089
+ async loadPayload(aggregatorId) {
20090
+ const key = payloadKey(aggregatorId);
20091
+ let raw;
20092
+ try {
20093
+ raw = await this.storage.read(AGGREGATOR_PAYLOAD_NAMESPACE, key);
20094
+ } catch {
20095
+ return null;
20096
+ }
20097
+ if (!raw) return null;
20098
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
20099
+ try {
20100
+ const envelope = JSON.parse(bytesToString(raw));
20101
+ const aad = stringToBytes(aggregatorId);
20102
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
20103
+ const parsed = JSON.parse(
20104
+ bytesToString(plaintext)
20105
+ );
20106
+ if (parsed.version !== 1) return null;
20107
+ if (parsed.aggregator_id !== aggregatorId) return null;
20108
+ return parsed.payload;
20109
+ } catch {
20110
+ return null;
20111
+ }
20112
+ }
20113
+ /**
20114
+ * Delete the persisted payload. Returns true when a bundle was removed,
20115
+ * false when none existed.
20116
+ */
20117
+ async deletePayload(aggregatorId) {
20118
+ const key = payloadKey(aggregatorId);
20119
+ const existed = await this.storage.exists(
20120
+ AGGREGATOR_PAYLOAD_NAMESPACE,
20121
+ key
20122
+ );
20123
+ if (!existed) return false;
20124
+ try {
20125
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, key);
20126
+ } catch {
20127
+ return false;
20128
+ }
20129
+ return true;
20130
+ }
20131
+ /**
20132
+ * Drop expired payload bundles. Returns the count of bundles pruned.
20133
+ * Caller wires this into the cocoon-unlock initialization path.
20134
+ */
20135
+ async pruneExpired(now) {
20136
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
20137
+ const entries = await this.storage.list(
20138
+ AGGREGATOR_PAYLOAD_NAMESPACE,
20139
+ AGGREGATOR_PAYLOAD_KEY_PREFIX
20140
+ );
20141
+ let pruned = 0;
20142
+ for (const meta of entries) {
20143
+ const aggregatorId = stripKeyPrefix(meta.key);
20144
+ if (aggregatorId === null) continue;
20145
+ const raw = await this.storage.read(
20146
+ AGGREGATOR_PAYLOAD_NAMESPACE,
20147
+ meta.key
20148
+ );
20149
+ if (!raw) continue;
20150
+ try {
20151
+ const envelope = JSON.parse(bytesToString(raw));
20152
+ const aad = stringToBytes(aggregatorId);
20153
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
20154
+ const parsed = JSON.parse(
20155
+ bytesToString(plaintext)
20156
+ );
20157
+ if (parsed.retention_until <= cutoff) {
20158
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, meta.key);
20159
+ pruned += 1;
20160
+ }
20161
+ } catch {
20162
+ }
20163
+ }
20164
+ return { pruned };
20165
+ }
20166
+ };
20167
+ function payloadKey(aggregatorId) {
20168
+ return `${AGGREGATOR_PAYLOAD_KEY_PREFIX}${aggregatorId}`;
20169
+ }
20170
+ function stripKeyPrefix(key) {
20171
+ if (!key.startsWith(AGGREGATOR_PAYLOAD_KEY_PREFIX)) return null;
20172
+ return key.slice(AGGREGATOR_PAYLOAD_KEY_PREFIX.length);
20173
+ }
20174
+
19829
20175
  // src/principal-policy/tools.ts
19830
20176
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
19831
20177
  return [
@@ -31986,20 +32332,291 @@ var OPERATOR_CHAT_OPS = {
31986
32332
  * turns; the concierge degrades to single-turn after emitting. Body
31987
32333
  * carries thread_id + a stable failure_reason enum.
31988
32334
  */
31989
- CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed"
32335
+ CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed",
32336
+ /**
32337
+ * Concierge dynamic-context fetcher failed (WP-V1.3-9 Tau-3). Emitted
32338
+ * when a category fetcher throws while assembling the dynamic context
32339
+ * fold. The concierge omits that category and continues; the user-
32340
+ * facing query is never broken. Body carries category + failure_reason.
32341
+ */
32342
+ CONCIERGE_CONTEXT_FETCHER_FAILED: "operator_concierge_context_fetcher_failed"
31990
32343
  };
31991
32344
 
31992
32345
  // src/chat/operator-chat-types.ts
31993
32346
  var OPERATOR_CHAT_MAX_THREAD_LENGTH = 500;
31994
32347
  var CONCIERGE_THREAD_KEY = "_fortress";
31995
32348
 
32349
+ // src/chat/concierge-context-router.ts
32350
+ var APPROX_CHARS_PER_TOKEN = 4;
32351
+ var DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET = 2e3;
32352
+ var DYNAMIC_CONTEXT_SECTION_HEADER = "## Live fortress context";
32353
+ var CONTEXT_CATEGORIES = [
32354
+ "templates",
32355
+ "agent_state",
32356
+ "agent_activity",
32357
+ "audit_log",
32358
+ "sentinel_findings",
32359
+ "anomaly_alerts",
32360
+ "recent_receipts",
32361
+ "verascore_deltas"
32362
+ ];
32363
+ function phrasePattern(phrase) {
32364
+ const escaped = phrase.toLowerCase().split(/\s+/).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
32365
+ return { source: `\\b${escaped}\\b`, phrase };
32366
+ }
32367
+ var CATEGORY_KEYWORDS = [
32368
+ {
32369
+ category: "templates",
32370
+ patterns: [
32371
+ "templates",
32372
+ "template",
32373
+ "channel templates",
32374
+ "channel template",
32375
+ "list templates",
32376
+ "available templates",
32377
+ "what templates"
32378
+ ].map(phrasePattern)
32379
+ },
32380
+ {
32381
+ category: "agent_state",
32382
+ patterns: [
32383
+ "state",
32384
+ "status",
32385
+ "agent state",
32386
+ "agent status",
32387
+ "status of agent",
32388
+ "status of agents",
32389
+ "state of",
32390
+ "doing"
32391
+ ].map(phrasePattern)
32392
+ },
32393
+ {
32394
+ category: "agent_activity",
32395
+ patterns: [
32396
+ "activity",
32397
+ "agent activity",
32398
+ "what did",
32399
+ "recent activity"
32400
+ ].map(phrasePattern)
32401
+ },
32402
+ {
32403
+ category: "audit_log",
32404
+ patterns: [
32405
+ "audit log",
32406
+ "audit",
32407
+ "log entry",
32408
+ "log entries",
32409
+ "what happened",
32410
+ "show me events",
32411
+ "event class"
32412
+ ].map(phrasePattern)
32413
+ },
32414
+ {
32415
+ category: "sentinel_findings",
32416
+ patterns: [
32417
+ "sentinel",
32418
+ "sentinels",
32419
+ "warning",
32420
+ "warnings",
32421
+ "alert",
32422
+ "alerts",
32423
+ "whats wrong",
32424
+ "what's wrong",
32425
+ "findings"
32426
+ ].map(phrasePattern)
32427
+ },
32428
+ {
32429
+ category: "anomaly_alerts",
32430
+ patterns: [
32431
+ "anomaly",
32432
+ "anomalies",
32433
+ "spike",
32434
+ "unusual",
32435
+ "outlier"
32436
+ ].map(phrasePattern)
32437
+ },
32438
+ {
32439
+ category: "recent_receipts",
32440
+ patterns: [
32441
+ "receipt",
32442
+ "receipts",
32443
+ "concordia",
32444
+ "commitment",
32445
+ "commitments",
32446
+ "chain",
32447
+ "chains"
32448
+ ].map(phrasePattern)
32449
+ },
32450
+ {
32451
+ category: "verascore_deltas",
32452
+ patterns: [
32453
+ "verascore",
32454
+ "vera score",
32455
+ "trust score",
32456
+ "reputation"
32457
+ ].map(phrasePattern)
32458
+ }
32459
+ ];
32460
+ function extractAgentNameHint(query) {
32461
+ const agentPattern = /\bagent\s+["']?([A-Za-z][\w-]{0,40})["']?/i;
32462
+ const m = query.match(agentPattern);
32463
+ if (m && m[1]) return m[1];
32464
+ const quoted = query.match(/["']([A-Za-z][\w-]{0,40})["']/);
32465
+ if (quoted && quoted[1]) return quoted[1];
32466
+ return null;
32467
+ }
32468
+ var TRIVIAL_GREETINGS = /* @__PURE__ */ new Set([
32469
+ "hi",
32470
+ "hello",
32471
+ "hey",
32472
+ "yo",
32473
+ "ok",
32474
+ "thanks",
32475
+ "thx",
32476
+ "thank you"
32477
+ ]);
32478
+ function isTrivialQuery(query) {
32479
+ const norm = query.trim().toLowerCase();
32480
+ if (norm.length === 0) return true;
32481
+ if (norm.length < 8) return true;
32482
+ return TRIVIAL_GREETINGS.has(norm);
32483
+ }
32484
+ function classifyQuery(query) {
32485
+ const normalized = query.toLowerCase();
32486
+ const matches = [];
32487
+ for (const spec of CATEGORY_KEYWORDS) {
32488
+ const matchedPhrases = [];
32489
+ for (const pattern of spec.patterns) {
32490
+ if (matchedPhrases.includes(pattern.phrase)) continue;
32491
+ const re = new RegExp(pattern.source, "i");
32492
+ if (re.test(normalized)) {
32493
+ matchedPhrases.push(pattern.phrase);
32494
+ }
32495
+ }
32496
+ if (matchedPhrases.length === 0) continue;
32497
+ const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
32498
+ matches.push({
32499
+ category: spec.category,
32500
+ confidence,
32501
+ matched_keywords: matchedPhrases,
32502
+ agent_name_hint: spec.category === "agent_state" || spec.category === "agent_activity" ? extractAgentNameHint(query) : null
32503
+ });
32504
+ }
32505
+ matches.sort((a, b) => {
32506
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
32507
+ return CONTEXT_CATEGORIES.indexOf(a.category) - CONTEXT_CATEGORIES.indexOf(b.category);
32508
+ });
32509
+ return matches;
32510
+ }
32511
+ function approxTokenLen(text) {
32512
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
32513
+ }
32514
+ var CATEGORY_LABELS = {
32515
+ templates: "Templates",
32516
+ agent_state: "Agent state",
32517
+ agent_activity: "Agent activity",
32518
+ audit_log: "Audit log",
32519
+ sentinel_findings: "Sentinel findings",
32520
+ anomaly_alerts: "Anomaly alerts",
32521
+ recent_receipts: "Recent receipts",
32522
+ verascore_deltas: "Verascore deltas"
32523
+ };
32524
+ async function runFetcher(match, fetchers) {
32525
+ switch (match.category) {
32526
+ case "templates":
32527
+ return fetchers.templates();
32528
+ case "agent_state":
32529
+ return fetchers.agent_state(match.agent_name_hint);
32530
+ case "agent_activity":
32531
+ return fetchers.agent_activity(match.agent_name_hint);
32532
+ case "audit_log":
32533
+ return fetchers.audit_log();
32534
+ case "sentinel_findings":
32535
+ return fetchers.sentinel_findings();
32536
+ case "anomaly_alerts":
32537
+ return fetchers.anomaly_alerts();
32538
+ case "recent_receipts":
32539
+ return fetchers.recent_receipts();
32540
+ case "verascore_deltas":
32541
+ return fetchers.verascore_deltas();
32542
+ }
32543
+ }
32544
+ function trivialMatch(category) {
32545
+ return {
32546
+ category,
32547
+ confidence: 0.5,
32548
+ matched_keywords: ["llm-assist"],
32549
+ agent_name_hint: null
32550
+ };
32551
+ }
32552
+ async function foldContext(query, fetchers, opts) {
32553
+ const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
32554
+ let matches = classifyQuery(query);
32555
+ if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
32556
+ try {
32557
+ const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
32558
+ if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
32559
+ matches = [trivialMatch(picked)];
32560
+ }
32561
+ } catch {
32562
+ }
32563
+ }
32564
+ if (matches.length === 0) {
32565
+ return { section: "", categoriesIncluded: [] };
32566
+ }
32567
+ const attempts = [];
32568
+ for (const match of matches) {
32569
+ try {
32570
+ const text = await runFetcher(match, fetchers);
32571
+ const trimmed = text.trim();
32572
+ if (trimmed.length > 0) {
32573
+ attempts.push({ category: match.category, text: trimmed });
32574
+ }
32575
+ } catch (err) {
32576
+ opts?.onFetcherFailure?.(match.category, err);
32577
+ }
32578
+ }
32579
+ if (attempts.length === 0) {
32580
+ return { section: "", categoriesIncluded: [] };
32581
+ }
32582
+ const headerTokens = approxTokenLen(`${DYNAMIC_CONTEXT_SECTION_HEADER}
32583
+ `);
32584
+ const sepTokens = approxTokenLen("\n\n");
32585
+ let runningTokens = headerTokens;
32586
+ const kept = [];
32587
+ for (const attempt of attempts) {
32588
+ const block = `### ${CATEGORY_LABELS[attempt.category]}
32589
+ ${attempt.text}`;
32590
+ const tokens = approxTokenLen(block) + (kept.length > 0 ? sepTokens : 0);
32591
+ if (kept.length === 0) {
32592
+ kept.push(attempt);
32593
+ runningTokens += tokens;
32594
+ continue;
32595
+ }
32596
+ if (runningTokens + tokens > budget) break;
32597
+ kept.push(attempt);
32598
+ runningTokens += tokens;
32599
+ }
32600
+ const blocks = kept.map(
32601
+ (k) => `### ${CATEGORY_LABELS[k.category]}
32602
+ ${k.text}`
32603
+ );
32604
+ const section = `${DYNAMIC_CONTEXT_SECTION_HEADER}
32605
+ ${blocks.join("\n\n")}`;
32606
+ return {
32607
+ section,
32608
+ categoriesIncluded: kept.map((k) => k.category)
32609
+ };
32610
+ }
32611
+
31996
32612
  // src/chat/operator-chat-service.ts
31997
32613
  var DEFAULT_CONCIERGE_MAX_TOKENS = 512;
31998
32614
  var DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
31999
32615
  var DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
32000
32616
  var DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET = 500;
32001
32617
  var DEFAULT_CONCIERGE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
32002
- function approxTokenLen(text) {
32618
+ var DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET = 2e3;
32619
+ function approxTokenLen2(text) {
32003
32620
  return Math.ceil(text.length / 4);
32004
32621
  }
32005
32622
  var SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
@@ -32043,6 +32660,9 @@ var OperatorChatService = class {
32043
32660
  historyTokenBudget;
32044
32661
  sessionTtlMs;
32045
32662
  clock;
32663
+ contextFetchers;
32664
+ contextLlmAssist;
32665
+ dynamicContextBudget;
32046
32666
  /**
32047
32667
  * In-memory thread_id assigned to the active concierge session.
32048
32668
  * The first sendConcierge call after construction allocates a fresh
@@ -32074,6 +32694,13 @@ var OperatorChatService = class {
32074
32694
  this.historyTokenBudget = deps.conciergeHistoryTokenBudget !== void 0 && deps.conciergeHistoryTokenBudget > 0 ? deps.conciergeHistoryTokenBudget : DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET;
32075
32695
  this.sessionTtlMs = deps.conciergeSessionTtlMs !== void 0 && deps.conciergeSessionTtlMs > 0 ? deps.conciergeSessionTtlMs : DEFAULT_CONCIERGE_SESSION_TTL_MS;
32076
32696
  this.clock = deps.conciergeClock ?? (() => Date.now());
32697
+ if (deps.conciergeContextFetchers) {
32698
+ this.contextFetchers = deps.conciergeContextFetchers;
32699
+ }
32700
+ if (deps.conciergeContextLlmAssist) {
32701
+ this.contextLlmAssist = deps.conciergeContextLlmAssist;
32702
+ }
32703
+ this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
32077
32704
  }
32078
32705
  // ── Concierge ─────────────────────────────────────────────────────────
32079
32706
  /**
@@ -32137,6 +32764,7 @@ var OperatorChatService = class {
32137
32764
  let servedBy = "disabled";
32138
32765
  let displayLabel = "Concierge: substrate not configured";
32139
32766
  let outcome = "substrate_disabled";
32767
+ let dynamicCategoriesIncluded = [];
32140
32768
  if (!this.substrateSelector) {
32141
32769
  conciergeBody = "Concierge unavailable. The substrate selector is not configured for this fortress. Pick a substrate in the Policy center to enable concierge replies.";
32142
32770
  } else {
@@ -32148,7 +32776,14 @@ var OperatorChatService = class {
32148
32776
  conciergeBody = "Concierge unavailable. The chosen substrate does not support summarization. Pick a different substrate in the Policy center.";
32149
32777
  outcome = "substrate_disabled";
32150
32778
  } else {
32151
- const context = await this.assembleConciergeContext(priorTurns);
32779
+ const dynamicResult = await this.runDynamicContextFold(
32780
+ filterResult.filtered
32781
+ );
32782
+ dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
32783
+ const context = await this.assembleConciergeContext(
32784
+ priorTurns,
32785
+ dynamicResult.section
32786
+ );
32152
32787
  const response = await this.substrateSelector.invokeSummarize(
32153
32788
  "concierge",
32154
32789
  {
@@ -32212,7 +32847,8 @@ var OperatorChatService = class {
32212
32847
  ...assistantTurnId !== void 0 ? { turn_index: assistantTurnId } : {},
32213
32848
  ...this.memory ? {
32214
32849
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
32215
- } : {}
32850
+ } : {},
32851
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {}
32216
32852
  };
32217
32853
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
32218
32854
  return {
@@ -32363,10 +32999,13 @@ var OperatorChatService = class {
32363
32999
  * ## Sanctuary reference
32364
33000
  * <static domain reference block>
32365
33001
  *
33002
+ * ## Live fortress context ← WP-V1.3-9 Tau-3, when present
33003
+ * ### <Category>
33004
+ * <fetcher payload>
33005
+ *
32366
33006
  * ## Prior conversation ← WP-V1.3-9 Tau-2, when present
32367
33007
  * OPERATOR: ...
32368
33008
  * CONCIERGE: ...
32369
- * ---
32370
33009
  *
32371
33010
  * ## Recent activity
32372
33011
  * <recentActivity output>
@@ -32385,13 +33024,14 @@ var OperatorChatService = class {
32385
33024
  * if available; the v1.2 selector does not expose one, so structured
32386
33025
  * serialization is the canonical path for v1.3.
32387
33026
  */
32388
- async assembleConciergeContext(priorTurns = []) {
33027
+ async assembleConciergeContext(priorTurns = [], dynamicSection = "") {
32389
33028
  const ref = `## Sanctuary reference
32390
33029
  ${SANCTUARY_DOMAIN_REFERENCE}`;
32391
33030
  const priorSection = this.formatPriorTurnsSection(priorTurns);
32392
33031
  if (!this.contextProviders) {
32393
33032
  return [
32394
33033
  ref,
33034
+ ...dynamicSection ? [dynamicSection] : [],
32395
33035
  ...priorSection ? [priorSection] : [],
32396
33036
  "## Recent activity\n(no providers wired)",
32397
33037
  "## Wrapped agents\n(no providers wired)",
@@ -32405,6 +33045,7 @@ ${SANCTUARY_DOMAIN_REFERENCE}`;
32405
33045
  ]);
32406
33046
  return [
32407
33047
  ref,
33048
+ ...dynamicSection ? [dynamicSection] : [],
32408
33049
  ...priorSection ? [priorSection] : [],
32409
33050
  `## Recent activity
32410
33051
  ${activity}`,
@@ -32414,6 +33055,51 @@ ${agents}`,
32414
33055
  ${inbox}`
32415
33056
  ].join("\n\n");
32416
33057
  }
33058
+ /**
33059
+ * Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
33060
+ * trip. Fail-soft on every axis: missing fetchers short-circuit to
33061
+ * an empty fold, fetcher failures emit a per-category audit event
33062
+ * and are omitted from the rendered section, an LLM-assist failure
33063
+ * proceeds with no fold. Returns the rendered section + the list of
33064
+ * categories whose data made it into the section (used for the
33065
+ * round-trip audit emission).
33066
+ */
33067
+ async runDynamicContextFold(query) {
33068
+ if (!this.contextFetchers) {
33069
+ return { section: "", categoriesIncluded: [] };
33070
+ }
33071
+ const result = await foldContext(query, this.contextFetchers, {
33072
+ maxTokens: this.dynamicContextBudget,
33073
+ ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
33074
+ onFetcherFailure: (category, error) => {
33075
+ this.emitContextFetcherFailed(category, classifyFetcherError(error));
33076
+ }
33077
+ });
33078
+ return result;
33079
+ }
33080
+ /**
33081
+ * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
33082
+ * of the fold path so the dynamic-context handler stays readable.
33083
+ * Emits with `result: "failure"` since the named category dropped
33084
+ * from the rendered section for this round-trip.
33085
+ */
33086
+ emitContextFetcherFailed(category, failureReason) {
33087
+ const payload = {
33088
+ version: "1.2",
33089
+ event_id: makeEventId("conc-ctxfail"),
33090
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
33091
+ identity_id: this.identityId,
33092
+ kind: "operator_concierge_context_fetcher_failed",
33093
+ surface: "concierge",
33094
+ category,
33095
+ failure_reason: failureReason
33096
+ };
33097
+ this.emit(
33098
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
33099
+ payload,
33100
+ "failure"
33101
+ );
33102
+ }
32417
33103
  /**
32418
33104
  * Render the prior-conversation section with token-budget enforcement
32419
33105
  * (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
@@ -32424,14 +33110,14 @@ ${inbox}`
32424
33110
  if (turns.length === 0) return "";
32425
33111
  const HEADER = "## Prior conversation";
32426
33112
  const lines = turns.map(formatPriorTurnLine);
32427
- const headerTokens = approxTokenLen(`${HEADER}
33113
+ const headerTokens = approxTokenLen2(`${HEADER}
32428
33114
  `);
32429
- const sepTokens = approxTokenLen("\n");
33115
+ const sepTokens = approxTokenLen2("\n");
32430
33116
  let runningTokens = headerTokens;
32431
33117
  let runningLines = [];
32432
33118
  for (let i = lines.length - 1; i >= 0; i--) {
32433
33119
  const line = lines[i];
32434
- const tokens = approxTokenLen(line) + (runningLines.length > 0 ? sepTokens : 0);
33120
+ const tokens = approxTokenLen2(line) + (runningLines.length > 0 ? sepTokens : 0);
32435
33121
  if (runningTokens + tokens > this.historyTokenBudget) break;
32436
33122
  runningTokens += tokens;
32437
33123
  runningLines.push(line);
@@ -32455,6 +33141,17 @@ ${runningLines.join("\n")}`;
32455
33141
  function makeEventId(prefix) {
32456
33142
  return `${prefix}-${Date.now()}-${randomUUID().slice(0, 8)}`;
32457
33143
  }
33144
+ function classifyFetcherError(error) {
33145
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
33146
+ if (msg.includes("timeout") || msg.includes("timed out")) return "timeout";
33147
+ if (msg.includes("schema") || msg.includes("invalid shape")) {
33148
+ return "schema_mismatch";
33149
+ }
33150
+ if (msg.includes("io") || msg.includes("read") || msg.includes("enoent") || msg.includes("eacces")) {
33151
+ return "io_failed";
33152
+ }
33153
+ return "unknown";
33154
+ }
32458
33155
  function formatPriorTurnLine(turn) {
32459
33156
  const label = turn.role === "user" ? "OPERATOR" : "CONCIERGE";
32460
33157
  return `${label}: ${turn.content}`;
@@ -32467,7 +33164,7 @@ function hashOf(input) {
32467
33164
  init_encryption();
32468
33165
  init_encoding();
32469
33166
  var OPERATOR_CHAT_NAMESPACE = "_chat";
32470
- var HKDF_INFO = "operator-chat-store-v1";
33167
+ var HKDF_INFO2 = "operator-chat-store-v1";
32471
33168
  function chatStorageKey(surface, threadKey) {
32472
33169
  return `${surface}.${threadKey}`;
32473
33170
  }
@@ -32476,7 +33173,7 @@ var OperatorChatStore = class {
32476
33173
  encryptionKey;
32477
33174
  constructor(storage, masterKey) {
32478
33175
  this.storage = storage;
32479
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO);
33176
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
32480
33177
  }
32481
33178
  /**
32482
33179
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -32560,9 +33257,9 @@ init_encryption();
32560
33257
  init_encoding();
32561
33258
  var CONCIERGE_MEMORY_NAMESPACE = "_chat";
32562
33259
  var CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
32563
- var HKDF_INFO2 = "concierge-memory-store-v1";
33260
+ var HKDF_INFO3 = "concierge-memory-store-v1";
32564
33261
  var DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
32565
- var MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
33262
+ var MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
32566
33263
  var ConciergeMemoryStore = class {
32567
33264
  storage;
32568
33265
  encryptionKey;
@@ -32571,7 +33268,7 @@ var ConciergeMemoryStore = class {
32571
33268
  locks;
32572
33269
  constructor(opts) {
32573
33270
  this.storage = opts.storage;
32574
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
33271
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
32575
33272
  this.fortressId = opts.fortressId;
32576
33273
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
32577
33274
  this.locks = /* @__PURE__ */ new Map();
@@ -32650,7 +33347,7 @@ var ConciergeMemoryStore = class {
32650
33347
  return { ok: false, reason: "io_failed" };
32651
33348
  }
32652
33349
  if (!raw) return { ok: true, turns: [] };
32653
- if (raw.length > MAX_BUNDLE_BYTES2) {
33350
+ if (raw.length > MAX_BUNDLE_BYTES3) {
32654
33351
  return { ok: false, reason: "oversize_bundle" };
32655
33352
  }
32656
33353
  let envelope;
@@ -32697,7 +33394,7 @@ var ConciergeMemoryStore = class {
32697
33394
  );
32698
33395
  const summaries = [];
32699
33396
  for (const meta of entries) {
32700
- const threadId = stripKeyPrefix(meta.key);
33397
+ const threadId = stripKeyPrefix2(meta.key);
32701
33398
  if (threadId === null) continue;
32702
33399
  const bundle = await this.loadBundle(threadId);
32703
33400
  if (!bundle || bundle.turns.length === 0) continue;
@@ -32750,7 +33447,7 @@ var ConciergeMemoryStore = class {
32750
33447
  );
32751
33448
  let pruned = 0;
32752
33449
  for (const meta of entries) {
32753
- const threadId = stripKeyPrefix(meta.key);
33450
+ const threadId = stripKeyPrefix2(meta.key);
32754
33451
  if (threadId === null) continue;
32755
33452
  pruned += await this.withLock(threadId, async () => {
32756
33453
  const bundle = await this.loadBundle(threadId);
@@ -32781,7 +33478,7 @@ var ConciergeMemoryStore = class {
32781
33478
  return null;
32782
33479
  }
32783
33480
  if (!raw) return null;
32784
- if (raw.length > MAX_BUNDLE_BYTES2) return null;
33481
+ if (raw.length > MAX_BUNDLE_BYTES3) return null;
32785
33482
  try {
32786
33483
  const envelope = JSON.parse(bytesToString(raw));
32787
33484
  const aad = stringToBytes(threadId);
@@ -32834,7 +33531,7 @@ var ConciergeMemoryStore = class {
32834
33531
  function bundleKey(threadId) {
32835
33532
  return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
32836
33533
  }
32837
- function stripKeyPrefix(key) {
33534
+ function stripKeyPrefix2(key) {
32838
33535
  if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
32839
33536
  return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
32840
33537
  }
@@ -32903,7 +33600,18 @@ function buildV11Bindings(inputs) {
32903
33600
  registry
32904
33601
  }),
32905
33602
  conciergePiiFilter: buildConciergePiiFilter(),
32906
- conciergeMemory
33603
+ conciergeMemory,
33604
+ conciergeContextFetchers: buildConciergeContextFetchers({
33605
+ auditLog: inputs.auditLog,
33606
+ identityId: inputs.identityId,
33607
+ registry
33608
+ }),
33609
+ ...inputs.intelligenceSelector ? {
33610
+ conciergeContextLlmAssist: buildConciergeContextLlmAssist({
33611
+ selector: inputs.intelligenceSelector,
33612
+ identityId: inputs.identityId
33613
+ })
33614
+ } : {}
32907
33615
  });
32908
33616
  }
32909
33617
  const hubService = new HubService({
@@ -32964,6 +33672,107 @@ function buildConciergeContextProviders(args) {
32964
33672
  }
32965
33673
  };
32966
33674
  }
33675
+ function buildConciergeContextFetchers(args) {
33676
+ const empty = async () => "";
33677
+ return {
33678
+ templates: async () => {
33679
+ const entries = listTemplates();
33680
+ if (entries.length === 0) return "(no templates installed)";
33681
+ const lines = entries.map((e) => {
33682
+ const m = e.metadata;
33683
+ return `${m.name} (tier ${m.tier}, channel ${m.channel}, target ${m.target_archetype})`;
33684
+ });
33685
+ return lines.join("\n");
33686
+ },
33687
+ agent_state: async (agentNameHint) => {
33688
+ const records = args.registry.list({ identity_id: args.identityId });
33689
+ if (records.length === 0) return "(no wrapped agents)";
33690
+ const filtered = agentNameHint ? records.filter(
33691
+ (r) => r.agent_id.toLowerCase().includes(agentNameHint.toLowerCase()) || r.harness.toLowerCase().includes(agentNameHint.toLowerCase())
33692
+ ) : records;
33693
+ const target = filtered.length > 0 ? filtered : records;
33694
+ const lines = target.slice(0, 20).map((r) => {
33695
+ const tmpl = typeof r.channel_template_id === "string" ? r.channel_template_id : "no_template";
33696
+ return `${r.agent_id} harness=${r.harness} status=${r.status} template=${tmpl}`;
33697
+ });
33698
+ return lines.join("\n");
33699
+ },
33700
+ agent_activity: async (agentNameHint) => {
33701
+ const result = await args.auditLog.query({ limit: 50 });
33702
+ const owned = result.entries.filter(
33703
+ (e) => e.identity_id === args.identityId
33704
+ );
33705
+ const filtered = agentNameHint ? owned.filter((e) => {
33706
+ const agentId = e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : "";
33707
+ return agentId.toLowerCase().includes(agentNameHint.toLowerCase());
33708
+ }) : owned;
33709
+ const tail = (filtered.length > 0 ? filtered : owned).slice(-20);
33710
+ if (tail.length === 0) return "(no activity)";
33711
+ return tail.map((e) => {
33712
+ const agentId = (e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : null) ?? "_fortress";
33713
+ return `${e.timestamp} ${e.layer}.${e.operation} agent=${agentId} result=${e.result}`;
33714
+ }).join("\n");
33715
+ },
33716
+ audit_log: async () => {
33717
+ const result = await args.auditLog.query({ limit: 30 });
33718
+ const owned = result.entries.filter(
33719
+ (e) => e.identity_id === args.identityId
33720
+ );
33721
+ if (owned.length === 0) return "(no audit log entries)";
33722
+ return owned.slice(-30).map(
33723
+ (e) => `${e.timestamp} ${e.layer}.${e.operation} result=${e.result}`
33724
+ ).join("\n");
33725
+ },
33726
+ sentinel_findings: empty,
33727
+ anomaly_alerts: empty,
33728
+ recent_receipts: async () => {
33729
+ const result = await args.auditLog.query({ limit: 100 });
33730
+ const owned = result.entries.filter(
33731
+ (e) => e.identity_id === args.identityId && e.operation.startsWith("composition_")
33732
+ );
33733
+ if (owned.length === 0) return "(no recent composition events)";
33734
+ return owned.slice(-15).map((e) => `${e.timestamp} ${e.operation} result=${e.result}`).join("\n");
33735
+ },
33736
+ verascore_deltas: empty
33737
+ };
33738
+ }
33739
+ function buildConciergeContextLlmAssist(args) {
33740
+ return async (query, categories) => {
33741
+ const labelList = categories.map((c) => `- ${c}`).join("\n");
33742
+ const prompt = `You are a router. Classify the operator's query into one of the categories below or "none".
33743
+ Reply with exactly one token: one category name or "none".
33744
+
33745
+ Categories:
33746
+ ${labelList}
33747
+
33748
+ Query: ${query}
33749
+
33750
+ Category:`;
33751
+ try {
33752
+ const handle = await args.selector.getSubstrate("concierge");
33753
+ if (!handle.capability.summarize) return "none";
33754
+ const response = await args.selector.invokeSummarize("concierge", {
33755
+ kind: "summarize",
33756
+ context: prompt,
33757
+ query: "Output the single category token.",
33758
+ maxTokens: 16
33759
+ });
33760
+ if (response.failureClass || response.body.kind !== "summarize") {
33761
+ return "none";
33762
+ }
33763
+ const raw = response.body.text.trim().toLowerCase();
33764
+ const head = raw.split(/\s|[.,!?:;]/)[0] ?? "";
33765
+ const normalized = head.replace(/[^a-z_]/g, "");
33766
+ const known = categories;
33767
+ if (known.includes(normalized)) {
33768
+ return normalized;
33769
+ }
33770
+ return "none";
33771
+ } catch {
33772
+ return "none";
33773
+ }
33774
+ };
33775
+ }
32967
33776
  function buildConciergePiiFilter() {
32968
33777
  return {
32969
33778
  filter(input) {
@@ -33095,13 +33904,13 @@ init_encryption();
33095
33904
  init_encoding();
33096
33905
  var INTELLIGENCE_NAMESPACE = "_intelligence";
33097
33906
  var SUBSTRATE_CONFIG_KEY = "substrate-config";
33098
- var HKDF_INFO3 = "intelligence-substrate-config";
33907
+ var HKDF_INFO4 = "intelligence-substrate-config";
33099
33908
  var IntelligenceConfigStore = class {
33100
33909
  storage;
33101
33910
  encryptionKey;
33102
33911
  constructor(storage, masterKey) {
33103
33912
  this.storage = storage;
33104
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
33913
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
33105
33914
  }
33106
33915
  /**
33107
33916
  * Load the operator's substrate config from disk. Returns the config
@@ -37047,12 +37856,18 @@ ${err.message}
37047
37856
  } : void 0;
37048
37857
  const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
37049
37858
  const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
37859
+ const aggregatorPayloadStore = new AggregatorPayloadStore({
37860
+ storage,
37861
+ masterKey,
37862
+ fortressId: fortressIdForAggregator
37863
+ });
37050
37864
  const approvalAggregator = new ApprovalAggregator({
37051
37865
  storage,
37052
37866
  masterKey,
37053
37867
  auditLog,
37054
37868
  identityId: aggregatorIdentityId,
37055
- fortressId: fortressIdForAggregator
37869
+ fortressId: fortressIdForAggregator,
37870
+ payloadStore: aggregatorPayloadStore
37056
37871
  });
37057
37872
  const wrappedApprovalChannel = new AggregatorBackedChannel({
37058
37873
  underlying: approvalChannel,