@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/cli.js CHANGED
@@ -17211,6 +17211,27 @@ async function handleApprovalInboxRoute(deps, req, res) {
17211
17211
  await handleStream2(deps, res);
17212
17212
  return true;
17213
17213
  }
17214
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17215
+ const limit = parseLimit2(
17216
+ url.searchParams.get("limit"),
17217
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17218
+ APPROVAL_INBOX_MAX_LIMIT
17219
+ );
17220
+ const statusRaw = url.searchParams.get("status");
17221
+ const sinceTs = url.searchParams.get("since") ?? void 0;
17222
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17223
+ const filterStatus = statusRaw && isStatusFilter(statusRaw) && statusRaw !== "pending" ? statusRaw : void 0;
17224
+ const entries = await deps.aggregator.getHistory(
17225
+ {
17226
+ limit,
17227
+ ...filterStatus !== void 0 ? { status: filterStatus } : {},
17228
+ ...sinceTs !== void 0 ? { sinceTs } : {}
17229
+ },
17230
+ operatorId
17231
+ );
17232
+ writeJSON4(res, 200, { ok: true, data: { entries } });
17233
+ return true;
17234
+ }
17214
17235
  if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
17215
17236
  const limit = parseLimit2(
17216
17237
  url.searchParams.get("limit"),
@@ -17233,11 +17254,39 @@ async function handleApprovalInboxRoute(deps, req, res) {
17233
17254
  writeJSON4(res, 404, { ok: false, error: "not_found", path });
17234
17255
  return true;
17235
17256
  }
17236
- if (method === "GET" && entryMatch.action === null) {
17237
- const entries = await deps.aggregator.list({ limit: APPROVAL_INBOX_MAX_LIMIT });
17238
- const entry = entries.find(
17239
- (e) => e.aggregator_id === entryMatch.aggregatorId
17257
+ if (method === "GET" && entryMatch.action === "audit-trail") {
17258
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17259
+ if (!entry) {
17260
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17261
+ return true;
17262
+ }
17263
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17264
+ const trail = await deps.aggregator.getAuditTrail(
17265
+ entryMatch.aggregatorId,
17266
+ operatorId
17240
17267
  );
17268
+ writeJSON4(res, 200, { ok: true, data: { entry, audit_trail: trail } });
17269
+ return true;
17270
+ }
17271
+ if (method === "GET" && entryMatch.action === "payload") {
17272
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17273
+ if (!entry) {
17274
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17275
+ return true;
17276
+ }
17277
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17278
+ const payload = await deps.aggregator.getFullPayloadWithAudit(
17279
+ entryMatch.aggregatorId,
17280
+ operatorId
17281
+ );
17282
+ writeJSON4(res, 200, {
17283
+ ok: true,
17284
+ data: { entry, request_payload: payload }
17285
+ });
17286
+ return true;
17287
+ }
17288
+ if (method === "GET" && entryMatch.action === null) {
17289
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17241
17290
  if (!entry) {
17242
17291
  writeJSON4(res, 404, { ok: false, error: "not_found" });
17243
17292
  return true;
@@ -20271,7 +20320,10 @@ var init_approval_aggregator = __esm({
20271
20320
  APPROVAL_AGGREGATOR_AUDIT_OPS = {
20272
20321
  AGGREGATED: "cross_harness_approval_aggregated",
20273
20322
  RESOLVED: "cross_harness_approval_resolved",
20274
- DEDUPED: "cross_harness_approval_deduped"
20323
+ DEDUPED: "cross_harness_approval_deduped",
20324
+ PAYLOAD_DECRYPTED: "cross_harness_approval_payload_decrypted",
20325
+ AUDIT_TRAIL_VIEWED: "cross_harness_approval_audit_trail_viewed",
20326
+ REPLAYED: "cross_harness_approval_replayed"
20275
20327
  };
20276
20328
  DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
20277
20329
  DEFAULT_MAX_LIST_LIMIT = 200;
@@ -20287,6 +20339,8 @@ var init_approval_aggregator = __esm({
20287
20339
  now;
20288
20340
  resolveSourceContext;
20289
20341
  resolveHubInboxItemId;
20342
+ payloadStore;
20343
+ resolveEnforcementChain;
20290
20344
  /** Cached entries by `aggregator_id`. */
20291
20345
  entries = /* @__PURE__ */ new Map();
20292
20346
  /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
@@ -20316,6 +20370,14 @@ var init_approval_aggregator = __esm({
20316
20370
  source_agent_id: this.fortressId
20317
20371
  }));
20318
20372
  this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
20373
+ this.payloadStore = deps.payloadStore ?? null;
20374
+ this.resolveEnforcementChain = deps.resolveEnforcementChain ?? ((event) => [
20375
+ {
20376
+ layer: "l2",
20377
+ event: `approval_required:${event.operation}`,
20378
+ timestamp: event.request_timestamp
20379
+ }
20380
+ ]);
20319
20381
  }
20320
20382
  /**
20321
20383
  * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
@@ -20366,13 +20428,152 @@ var init_approval_aggregator = __esm({
20366
20428
  }
20367
20429
  /**
20368
20430
  * Return the original (unhashed) request payload for the entry. Returns
20369
- * `null` when the entry is unknown or the payload was evicted (e.g. the
20370
- * process restarted; payloads are in-memory only at v1.3 Upsilon-1).
20431
+ * `null` when the entry is unknown. When the in-memory payload map has
20432
+ * been evicted (e.g. after a server restart) and a `payloadStore` was
20433
+ * provided, the at-rest bundle is decrypted and the in-memory map is
20434
+ * refilled. Audit emission lives on the `*WithAudit` variant; this base
20435
+ * accessor is silent so internal callers can read without polluting the
20436
+ * audit trail.
20371
20437
  */
20372
20438
  async getFullPayload(aggregatorId) {
20373
20439
  await this.hydrate();
20374
20440
  if (!this.entries.has(aggregatorId)) return null;
20375
- return this.fullPayloads.get(aggregatorId) ?? null;
20441
+ const cached = this.fullPayloads.get(aggregatorId);
20442
+ if (cached !== void 0) return cached;
20443
+ if (this.payloadStore) {
20444
+ try {
20445
+ const restored = await this.payloadStore.loadPayload(aggregatorId);
20446
+ if (restored !== null) {
20447
+ this.fullPayloads.set(aggregatorId, restored);
20448
+ return restored;
20449
+ }
20450
+ } catch {
20451
+ }
20452
+ }
20453
+ return null;
20454
+ }
20455
+ /**
20456
+ * Return the entry record for the given id, or null when unknown.
20457
+ * Idempotent. v1.3 Upsilon-3.
20458
+ */
20459
+ async getEntry(aggregatorId) {
20460
+ await this.hydrate();
20461
+ return this.entries.get(aggregatorId) ?? null;
20462
+ }
20463
+ /**
20464
+ * Audited variant of `getFullPayload`. Emits the
20465
+ * `cross_harness_approval_payload_decrypted` audit event before
20466
+ * returning. Used by the operator-facing /payload replay route.
20467
+ * v1.3 Upsilon-3.
20468
+ */
20469
+ async getFullPayloadWithAudit(aggregatorId, operatorId) {
20470
+ const payload = await this.getFullPayload(aggregatorId);
20471
+ if (payload === null) return null;
20472
+ const entry = this.entries.get(aggregatorId);
20473
+ this.auditLog.append(
20474
+ "l2",
20475
+ APPROVAL_AGGREGATOR_AUDIT_OPS.PAYLOAD_DECRYPTED,
20476
+ operatorId,
20477
+ {
20478
+ aggregator_id: aggregatorId,
20479
+ ...entry ? {
20480
+ source_harness: entry.source_harness,
20481
+ source_agent_id: entry.source_agent_id,
20482
+ entry_status: entry.status
20483
+ } : {}
20484
+ }
20485
+ );
20486
+ return payload;
20487
+ }
20488
+ /**
20489
+ * Return the audit-log entries that led to and surround this approval.
20490
+ * Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
20491
+ * DEDUPED, replay events) all carry `details.aggregator_id` and link
20492
+ * directly. Gate-side emissions (`gate_*:operation`) do not carry the
20493
+ * aggregator id at v1.3, so they are matched via timestamp window
20494
+ * (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
20495
+ * pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
20496
+ * v1.3 Upsilon-3.
20497
+ */
20498
+ async getAuditTrail(aggregatorId, operatorId) {
20499
+ await this.hydrate();
20500
+ const entry = this.entries.get(aggregatorId);
20501
+ if (!entry) {
20502
+ return [];
20503
+ }
20504
+ const sinceMs = Date.parse(entry.created_at) - 1e3;
20505
+ const sinceIso = new Date(sinceMs).toISOString();
20506
+ const queried = await this.auditLog.query({ since: sinceIso, limit: 1e3 });
20507
+ const operationPart = entry.policy_rule_id.includes(":") ? entry.policy_rule_id.slice(entry.policy_rule_id.indexOf(":") + 1) : entry.policy_rule_id;
20508
+ const lifetimeStart = sinceMs;
20509
+ const lifetimeEnd = entry.resolved_at ? Date.parse(entry.resolved_at) + 1e3 : Date.parse(entry.expires_at) + 1e3;
20510
+ const matches = [];
20511
+ for (const audit of queried.entries) {
20512
+ const detailsId = audit.details !== void 0 ? audit.details["aggregator_id"] : void 0;
20513
+ if (detailsId === aggregatorId) {
20514
+ matches.push(audit);
20515
+ continue;
20516
+ }
20517
+ const auditMs = Date.parse(audit.timestamp);
20518
+ if (auditMs < lifetimeStart || auditMs > lifetimeEnd) continue;
20519
+ if (audit.operation.endsWith(`:${operationPart}`)) {
20520
+ matches.push(audit);
20521
+ }
20522
+ }
20523
+ matches.sort(
20524
+ (a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0
20525
+ );
20526
+ this.auditLog.append(
20527
+ "l2",
20528
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AUDIT_TRAIL_VIEWED,
20529
+ operatorId,
20530
+ {
20531
+ aggregator_id: aggregatorId,
20532
+ entry_status: entry.status,
20533
+ match_count: matches.length
20534
+ }
20535
+ );
20536
+ return matches;
20537
+ }
20538
+ /**
20539
+ * List historical (resolved) approvals. Excludes pending entries by
20540
+ * design: `list()` is the pending-inbox surface and `getHistory()` is
20541
+ * the resolved-replay surface. Emits REPLAYED on each call. v1.3
20542
+ * Upsilon-3.
20543
+ */
20544
+ async getHistory(opts, operatorId) {
20545
+ await this.hydrate();
20546
+ await this.expireStale();
20547
+ const limit = Math.min(
20548
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20549
+ this.maxListLimit
20550
+ );
20551
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
20552
+ const matching = [];
20553
+ for (const entry of this.entries.values()) {
20554
+ if (entry.status === "pending") continue;
20555
+ if (opts?.status && entry.status !== opts.status) continue;
20556
+ const stamp = Date.parse(entry.resolved_at ?? entry.created_at);
20557
+ if (stamp < sinceMs) continue;
20558
+ matching.push(entry);
20559
+ }
20560
+ matching.sort((a, b) => {
20561
+ const aStamp = a.resolved_at ?? a.created_at;
20562
+ const bStamp = b.resolved_at ?? b.created_at;
20563
+ return bStamp.localeCompare(aStamp);
20564
+ });
20565
+ const sliced = matching.slice(0, limit);
20566
+ this.auditLog.append(
20567
+ "l2",
20568
+ APPROVAL_AGGREGATOR_AUDIT_OPS.REPLAYED,
20569
+ operatorId,
20570
+ {
20571
+ result_count: sliced.length,
20572
+ ...opts?.status !== void 0 ? { status_filter: opts.status } : {},
20573
+ ...opts?.sinceTs !== void 0 ? { since: opts.sinceTs } : {}
20574
+ }
20575
+ );
20576
+ return sliced;
20376
20577
  }
20377
20578
  /**
20378
20579
  * Resolve an entry. Used by both:
@@ -20446,6 +20647,7 @@ var init_approval_aggregator = __esm({
20446
20647
  const now = this.now();
20447
20648
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20448
20649
  const hubInboxId = this.resolveHubInboxItemId(event);
20650
+ const enforcementChain = this.resolveEnforcementChain(event);
20449
20651
  const entry = {
20450
20652
  aggregator_id: id,
20451
20653
  source_harness: ctx.source_harness,
@@ -20457,13 +20659,20 @@ var init_approval_aggregator = __esm({
20457
20659
  status: "pending",
20458
20660
  created_at: now.toISOString(),
20459
20661
  expires_at: expires.toISOString(),
20460
- ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
20662
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20663
+ ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20461
20664
  };
20462
20665
  this.entries.set(id, entry);
20463
20666
  this.dedupIndex.set(dedupKey, id);
20464
20667
  this.correlationIndex.set(event.correlation_id, id);
20465
20668
  this.fullPayloads.set(id, event.context);
20466
20669
  await this.persist(entry);
20670
+ if (this.payloadStore) {
20671
+ try {
20672
+ await this.payloadStore.savePayload(id, event.context);
20673
+ } catch {
20674
+ }
20675
+ }
20467
20676
  this.auditLog.append(
20468
20677
  "l2",
20469
20678
  APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
@@ -20779,6 +20988,149 @@ var init_aggregator_backed_channel = __esm({
20779
20988
  }
20780
20989
  });
20781
20990
 
20991
+ // src/principal-policy/aggregator-store.ts
20992
+ function payloadKey(aggregatorId) {
20993
+ return `${AGGREGATOR_PAYLOAD_KEY_PREFIX}${aggregatorId}`;
20994
+ }
20995
+ function stripKeyPrefix(key) {
20996
+ if (!key.startsWith(AGGREGATOR_PAYLOAD_KEY_PREFIX)) return null;
20997
+ return key.slice(AGGREGATOR_PAYLOAD_KEY_PREFIX.length);
20998
+ }
20999
+ var AGGREGATOR_PAYLOAD_NAMESPACE, AGGREGATOR_PAYLOAD_KEY_PREFIX, HKDF_INFO, DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS, MAX_BUNDLE_BYTES2, AggregatorPayloadStore;
21000
+ var init_aggregator_store = __esm({
21001
+ "src/principal-policy/aggregator-store.ts"() {
21002
+ init_encryption();
21003
+ init_key_derivation();
21004
+ init_encoding();
21005
+ AGGREGATOR_PAYLOAD_NAMESPACE = "_approval_aggregator_payloads";
21006
+ AGGREGATOR_PAYLOAD_KEY_PREFIX = "payload.";
21007
+ HKDF_INFO = "l2-approval-aggregator-payload-v1";
21008
+ DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS = 30;
21009
+ MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
21010
+ AggregatorPayloadStore = class {
21011
+ storage;
21012
+ encryptionKey;
21013
+ fortressId;
21014
+ retentionDays;
21015
+ constructor(opts) {
21016
+ this.storage = opts.storage;
21017
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO);
21018
+ this.fortressId = opts.fortressId;
21019
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS;
21020
+ }
21021
+ /**
21022
+ * Persist `payload` under the given aggregator_id. Idempotent; calling
21023
+ * twice with the same id rewrites the bundle (retention_until is
21024
+ * recomputed). Returns the bundle's retention_until ISO-8601 timestamp
21025
+ * so callers can log it.
21026
+ */
21027
+ async savePayload(aggregatorId, payload) {
21028
+ const now = /* @__PURE__ */ new Date();
21029
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
21030
+ const retentionUntil = new Date(now.getTime() + retentionMs);
21031
+ const bundle = {
21032
+ version: 1,
21033
+ aggregator_id: aggregatorId,
21034
+ fortress_id: this.fortressId,
21035
+ created_at: now.toISOString(),
21036
+ retention_until: retentionUntil.toISOString(),
21037
+ payload
21038
+ };
21039
+ const aad = stringToBytes(aggregatorId);
21040
+ const plaintext = stringToBytes(JSON.stringify(bundle));
21041
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
21042
+ await this.storage.write(
21043
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21044
+ payloadKey(aggregatorId),
21045
+ stringToBytes(JSON.stringify(envelope))
21046
+ );
21047
+ return bundle.retention_until;
21048
+ }
21049
+ /**
21050
+ * Read the persisted payload for the aggregator_id. Returns null if no
21051
+ * bundle exists, the bundle is corrupted, or AAD binding fails.
21052
+ */
21053
+ async loadPayload(aggregatorId) {
21054
+ const key = payloadKey(aggregatorId);
21055
+ let raw;
21056
+ try {
21057
+ raw = await this.storage.read(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21058
+ } catch {
21059
+ return null;
21060
+ }
21061
+ if (!raw) return null;
21062
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
21063
+ try {
21064
+ const envelope = JSON.parse(bytesToString(raw));
21065
+ const aad = stringToBytes(aggregatorId);
21066
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21067
+ const parsed = JSON.parse(
21068
+ bytesToString(plaintext)
21069
+ );
21070
+ if (parsed.version !== 1) return null;
21071
+ if (parsed.aggregator_id !== aggregatorId) return null;
21072
+ return parsed.payload;
21073
+ } catch {
21074
+ return null;
21075
+ }
21076
+ }
21077
+ /**
21078
+ * Delete the persisted payload. Returns true when a bundle was removed,
21079
+ * false when none existed.
21080
+ */
21081
+ async deletePayload(aggregatorId) {
21082
+ const key = payloadKey(aggregatorId);
21083
+ const existed = await this.storage.exists(
21084
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21085
+ key
21086
+ );
21087
+ if (!existed) return false;
21088
+ try {
21089
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21090
+ } catch {
21091
+ return false;
21092
+ }
21093
+ return true;
21094
+ }
21095
+ /**
21096
+ * Drop expired payload bundles. Returns the count of bundles pruned.
21097
+ * Caller wires this into the cocoon-unlock initialization path.
21098
+ */
21099
+ async pruneExpired(now) {
21100
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
21101
+ const entries = await this.storage.list(
21102
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21103
+ AGGREGATOR_PAYLOAD_KEY_PREFIX
21104
+ );
21105
+ let pruned = 0;
21106
+ for (const meta of entries) {
21107
+ const aggregatorId = stripKeyPrefix(meta.key);
21108
+ if (aggregatorId === null) continue;
21109
+ const raw = await this.storage.read(
21110
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21111
+ meta.key
21112
+ );
21113
+ if (!raw) continue;
21114
+ try {
21115
+ const envelope = JSON.parse(bytesToString(raw));
21116
+ const aad = stringToBytes(aggregatorId);
21117
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21118
+ const parsed = JSON.parse(
21119
+ bytesToString(plaintext)
21120
+ );
21121
+ if (parsed.retention_until <= cutoff) {
21122
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, meta.key);
21123
+ pruned += 1;
21124
+ }
21125
+ } catch {
21126
+ }
21127
+ }
21128
+ return { pruned };
21129
+ }
21130
+ };
21131
+ }
21132
+ });
21133
+
20782
21134
  // src/principal-policy/tools.ts
20783
21135
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
20784
21136
  return [
@@ -33397,7 +33749,14 @@ var init_operator_chat_audit_events = __esm({
33397
33749
  * turns; the concierge degrades to single-turn after emitting. Body
33398
33750
  * carries thread_id + a stable failure_reason enum.
33399
33751
  */
33400
- CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed"
33752
+ CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed",
33753
+ /**
33754
+ * Concierge dynamic-context fetcher failed (WP-V1.3-9 Tau-3). Emitted
33755
+ * when a category fetcher throws while assembling the dynamic context
33756
+ * fold. The concierge omits that category and continues; the user-
33757
+ * facing query is never broken. Body carries category + failure_reason.
33758
+ */
33759
+ CONCIERGE_CONTEXT_FETCHER_FAILED: "operator_concierge_context_fetcher_failed"
33401
33760
  };
33402
33761
  }
33403
33762
  });
@@ -33410,12 +33769,291 @@ var init_operator_chat_types = __esm({
33410
33769
  CONCIERGE_THREAD_KEY = "_fortress";
33411
33770
  }
33412
33771
  });
33772
+
33773
+ // src/chat/concierge-context-router.ts
33774
+ function phrasePattern(phrase) {
33775
+ const escaped = phrase.toLowerCase().split(/\s+/).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
33776
+ return { source: `\\b${escaped}\\b`, phrase };
33777
+ }
33778
+ function extractAgentNameHint(query) {
33779
+ const agentPattern = /\bagent\s+["']?([A-Za-z][\w-]{0,40})["']?/i;
33780
+ const m = query.match(agentPattern);
33781
+ if (m && m[1]) return m[1];
33782
+ const quoted = query.match(/["']([A-Za-z][\w-]{0,40})["']/);
33783
+ if (quoted && quoted[1]) return quoted[1];
33784
+ return null;
33785
+ }
33786
+ function isTrivialQuery(query) {
33787
+ const norm = query.trim().toLowerCase();
33788
+ if (norm.length === 0) return true;
33789
+ if (norm.length < 8) return true;
33790
+ return TRIVIAL_GREETINGS.has(norm);
33791
+ }
33792
+ function classifyQuery(query) {
33793
+ const normalized = query.toLowerCase();
33794
+ const matches = [];
33795
+ for (const spec of CATEGORY_KEYWORDS) {
33796
+ const matchedPhrases = [];
33797
+ for (const pattern of spec.patterns) {
33798
+ if (matchedPhrases.includes(pattern.phrase)) continue;
33799
+ const re = new RegExp(pattern.source, "i");
33800
+ if (re.test(normalized)) {
33801
+ matchedPhrases.push(pattern.phrase);
33802
+ }
33803
+ }
33804
+ if (matchedPhrases.length === 0) continue;
33805
+ const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
33806
+ matches.push({
33807
+ category: spec.category,
33808
+ confidence,
33809
+ matched_keywords: matchedPhrases,
33810
+ agent_name_hint: spec.category === "agent_state" || spec.category === "agent_activity" ? extractAgentNameHint(query) : null
33811
+ });
33812
+ }
33813
+ matches.sort((a, b) => {
33814
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
33815
+ return CONTEXT_CATEGORIES.indexOf(a.category) - CONTEXT_CATEGORIES.indexOf(b.category);
33816
+ });
33817
+ return matches;
33818
+ }
33413
33819
  function approxTokenLen(text) {
33820
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
33821
+ }
33822
+ async function runFetcher(match, fetchers) {
33823
+ switch (match.category) {
33824
+ case "templates":
33825
+ return fetchers.templates();
33826
+ case "agent_state":
33827
+ return fetchers.agent_state(match.agent_name_hint);
33828
+ case "agent_activity":
33829
+ return fetchers.agent_activity(match.agent_name_hint);
33830
+ case "audit_log":
33831
+ return fetchers.audit_log();
33832
+ case "sentinel_findings":
33833
+ return fetchers.sentinel_findings();
33834
+ case "anomaly_alerts":
33835
+ return fetchers.anomaly_alerts();
33836
+ case "recent_receipts":
33837
+ return fetchers.recent_receipts();
33838
+ case "verascore_deltas":
33839
+ return fetchers.verascore_deltas();
33840
+ }
33841
+ }
33842
+ function trivialMatch(category) {
33843
+ return {
33844
+ category,
33845
+ confidence: 0.5,
33846
+ matched_keywords: ["llm-assist"],
33847
+ agent_name_hint: null
33848
+ };
33849
+ }
33850
+ async function foldContext(query, fetchers, opts) {
33851
+ const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
33852
+ let matches = classifyQuery(query);
33853
+ if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
33854
+ try {
33855
+ const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
33856
+ if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
33857
+ matches = [trivialMatch(picked)];
33858
+ }
33859
+ } catch {
33860
+ }
33861
+ }
33862
+ if (matches.length === 0) {
33863
+ return { section: "", categoriesIncluded: [] };
33864
+ }
33865
+ const attempts = [];
33866
+ for (const match of matches) {
33867
+ try {
33868
+ const text = await runFetcher(match, fetchers);
33869
+ const trimmed = text.trim();
33870
+ if (trimmed.length > 0) {
33871
+ attempts.push({ category: match.category, text: trimmed });
33872
+ }
33873
+ } catch (err) {
33874
+ opts?.onFetcherFailure?.(match.category, err);
33875
+ }
33876
+ }
33877
+ if (attempts.length === 0) {
33878
+ return { section: "", categoriesIncluded: [] };
33879
+ }
33880
+ const headerTokens = approxTokenLen(`${DYNAMIC_CONTEXT_SECTION_HEADER}
33881
+ `);
33882
+ const sepTokens = approxTokenLen("\n\n");
33883
+ let runningTokens = headerTokens;
33884
+ const kept = [];
33885
+ for (const attempt of attempts) {
33886
+ const block = `### ${CATEGORY_LABELS[attempt.category]}
33887
+ ${attempt.text}`;
33888
+ const tokens = approxTokenLen(block) + (kept.length > 0 ? sepTokens : 0);
33889
+ if (kept.length === 0) {
33890
+ kept.push(attempt);
33891
+ runningTokens += tokens;
33892
+ continue;
33893
+ }
33894
+ if (runningTokens + tokens > budget) break;
33895
+ kept.push(attempt);
33896
+ runningTokens += tokens;
33897
+ }
33898
+ const blocks = kept.map(
33899
+ (k) => `### ${CATEGORY_LABELS[k.category]}
33900
+ ${k.text}`
33901
+ );
33902
+ const section = `${DYNAMIC_CONTEXT_SECTION_HEADER}
33903
+ ${blocks.join("\n\n")}`;
33904
+ return {
33905
+ section,
33906
+ categoriesIncluded: kept.map((k) => k.category)
33907
+ };
33908
+ }
33909
+ var APPROX_CHARS_PER_TOKEN, DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET, DYNAMIC_CONTEXT_SECTION_HEADER, CONTEXT_CATEGORIES, CATEGORY_KEYWORDS, TRIVIAL_GREETINGS, CATEGORY_LABELS;
33910
+ var init_concierge_context_router = __esm({
33911
+ "src/chat/concierge-context-router.ts"() {
33912
+ APPROX_CHARS_PER_TOKEN = 4;
33913
+ DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET = 2e3;
33914
+ DYNAMIC_CONTEXT_SECTION_HEADER = "## Live fortress context";
33915
+ CONTEXT_CATEGORIES = [
33916
+ "templates",
33917
+ "agent_state",
33918
+ "agent_activity",
33919
+ "audit_log",
33920
+ "sentinel_findings",
33921
+ "anomaly_alerts",
33922
+ "recent_receipts",
33923
+ "verascore_deltas"
33924
+ ];
33925
+ CATEGORY_KEYWORDS = [
33926
+ {
33927
+ category: "templates",
33928
+ patterns: [
33929
+ "templates",
33930
+ "template",
33931
+ "channel templates",
33932
+ "channel template",
33933
+ "list templates",
33934
+ "available templates",
33935
+ "what templates"
33936
+ ].map(phrasePattern)
33937
+ },
33938
+ {
33939
+ category: "agent_state",
33940
+ patterns: [
33941
+ "state",
33942
+ "status",
33943
+ "agent state",
33944
+ "agent status",
33945
+ "status of agent",
33946
+ "status of agents",
33947
+ "state of",
33948
+ "doing"
33949
+ ].map(phrasePattern)
33950
+ },
33951
+ {
33952
+ category: "agent_activity",
33953
+ patterns: [
33954
+ "activity",
33955
+ "agent activity",
33956
+ "what did",
33957
+ "recent activity"
33958
+ ].map(phrasePattern)
33959
+ },
33960
+ {
33961
+ category: "audit_log",
33962
+ patterns: [
33963
+ "audit log",
33964
+ "audit",
33965
+ "log entry",
33966
+ "log entries",
33967
+ "what happened",
33968
+ "show me events",
33969
+ "event class"
33970
+ ].map(phrasePattern)
33971
+ },
33972
+ {
33973
+ category: "sentinel_findings",
33974
+ patterns: [
33975
+ "sentinel",
33976
+ "sentinels",
33977
+ "warning",
33978
+ "warnings",
33979
+ "alert",
33980
+ "alerts",
33981
+ "whats wrong",
33982
+ "what's wrong",
33983
+ "findings"
33984
+ ].map(phrasePattern)
33985
+ },
33986
+ {
33987
+ category: "anomaly_alerts",
33988
+ patterns: [
33989
+ "anomaly",
33990
+ "anomalies",
33991
+ "spike",
33992
+ "unusual",
33993
+ "outlier"
33994
+ ].map(phrasePattern)
33995
+ },
33996
+ {
33997
+ category: "recent_receipts",
33998
+ patterns: [
33999
+ "receipt",
34000
+ "receipts",
34001
+ "concordia",
34002
+ "commitment",
34003
+ "commitments",
34004
+ "chain",
34005
+ "chains"
34006
+ ].map(phrasePattern)
34007
+ },
34008
+ {
34009
+ category: "verascore_deltas",
34010
+ patterns: [
34011
+ "verascore",
34012
+ "vera score",
34013
+ "trust score",
34014
+ "reputation"
34015
+ ].map(phrasePattern)
34016
+ }
34017
+ ];
34018
+ TRIVIAL_GREETINGS = /* @__PURE__ */ new Set([
34019
+ "hi",
34020
+ "hello",
34021
+ "hey",
34022
+ "yo",
34023
+ "ok",
34024
+ "thanks",
34025
+ "thx",
34026
+ "thank you"
34027
+ ]);
34028
+ CATEGORY_LABELS = {
34029
+ templates: "Templates",
34030
+ agent_state: "Agent state",
34031
+ agent_activity: "Agent activity",
34032
+ audit_log: "Audit log",
34033
+ sentinel_findings: "Sentinel findings",
34034
+ anomaly_alerts: "Anomaly alerts",
34035
+ recent_receipts: "Recent receipts",
34036
+ verascore_deltas: "Verascore deltas"
34037
+ };
34038
+ }
34039
+ });
34040
+ function approxTokenLen2(text) {
33414
34041
  return Math.ceil(text.length / 4);
33415
34042
  }
33416
34043
  function makeEventId(prefix) {
33417
34044
  return `${prefix}-${Date.now()}-${randomUUID().slice(0, 8)}`;
33418
34045
  }
34046
+ function classifyFetcherError(error) {
34047
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
34048
+ if (msg.includes("timeout") || msg.includes("timed out")) return "timeout";
34049
+ if (msg.includes("schema") || msg.includes("invalid shape")) {
34050
+ return "schema_mismatch";
34051
+ }
34052
+ if (msg.includes("io") || msg.includes("read") || msg.includes("enoent") || msg.includes("eacces")) {
34053
+ return "io_failed";
34054
+ }
34055
+ return "unknown";
34056
+ }
33419
34057
  function formatPriorTurnLine(turn) {
33420
34058
  const label = turn.role === "user" ? "OPERATOR" : "CONCIERGE";
33421
34059
  return `${label}: ${turn.content}`;
@@ -33423,18 +34061,20 @@ function formatPriorTurnLine(turn) {
33423
34061
  function hashOf(input) {
33424
34062
  return hashToString(sha256(stringToBytes(input)));
33425
34063
  }
33426
- var DEFAULT_CONCIERGE_MAX_TOKENS, DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS, DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS, DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET, DEFAULT_CONCIERGE_SESSION_TTL_MS, SANCTUARY_DOMAIN_REFERENCE, OperatorChatService;
34064
+ var DEFAULT_CONCIERGE_MAX_TOKENS, DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS, DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS, DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET, DEFAULT_CONCIERGE_SESSION_TTL_MS, DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET, SANCTUARY_DOMAIN_REFERENCE, OperatorChatService;
33427
34065
  var init_operator_chat_service = __esm({
33428
34066
  "src/chat/operator-chat-service.ts"() {
33429
34067
  init_hashing();
33430
34068
  init_encoding();
33431
34069
  init_operator_chat_audit_events();
33432
34070
  init_operator_chat_types();
34071
+ init_concierge_context_router();
33433
34072
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
33434
34073
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
33435
34074
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
33436
34075
  DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET = 500;
33437
34076
  DEFAULT_CONCIERGE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
34077
+ DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET = 2e3;
33438
34078
  SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
33439
34079
  1. Castle Wall: OS-boundary egress filter enforced at the kernel level. Blocks unauthorized outbound calls even from prompt-injected agents.
33440
34080
  2. Sentinels: internal observation via process introspection. Surfaces anomalies to the operator; does not enforce.
@@ -33476,6 +34116,9 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33476
34116
  historyTokenBudget;
33477
34117
  sessionTtlMs;
33478
34118
  clock;
34119
+ contextFetchers;
34120
+ contextLlmAssist;
34121
+ dynamicContextBudget;
33479
34122
  /**
33480
34123
  * In-memory thread_id assigned to the active concierge session.
33481
34124
  * The first sendConcierge call after construction allocates a fresh
@@ -33507,6 +34150,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33507
34150
  this.historyTokenBudget = deps.conciergeHistoryTokenBudget !== void 0 && deps.conciergeHistoryTokenBudget > 0 ? deps.conciergeHistoryTokenBudget : DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET;
33508
34151
  this.sessionTtlMs = deps.conciergeSessionTtlMs !== void 0 && deps.conciergeSessionTtlMs > 0 ? deps.conciergeSessionTtlMs : DEFAULT_CONCIERGE_SESSION_TTL_MS;
33509
34152
  this.clock = deps.conciergeClock ?? (() => Date.now());
34153
+ if (deps.conciergeContextFetchers) {
34154
+ this.contextFetchers = deps.conciergeContextFetchers;
34155
+ }
34156
+ if (deps.conciergeContextLlmAssist) {
34157
+ this.contextLlmAssist = deps.conciergeContextLlmAssist;
34158
+ }
34159
+ this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
33510
34160
  }
33511
34161
  // ── Concierge ─────────────────────────────────────────────────────────
33512
34162
  /**
@@ -33570,6 +34220,7 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33570
34220
  let servedBy = "disabled";
33571
34221
  let displayLabel = "Concierge: substrate not configured";
33572
34222
  let outcome = "substrate_disabled";
34223
+ let dynamicCategoriesIncluded = [];
33573
34224
  if (!this.substrateSelector) {
33574
34225
  conciergeBody = "Concierge unavailable. The substrate selector is not configured for this fortress. Pick a substrate in the Policy center to enable concierge replies.";
33575
34226
  } else {
@@ -33581,7 +34232,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33581
34232
  conciergeBody = "Concierge unavailable. The chosen substrate does not support summarization. Pick a different substrate in the Policy center.";
33582
34233
  outcome = "substrate_disabled";
33583
34234
  } else {
33584
- const context = await this.assembleConciergeContext(priorTurns);
34235
+ const dynamicResult = await this.runDynamicContextFold(
34236
+ filterResult.filtered
34237
+ );
34238
+ dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34239
+ const context = await this.assembleConciergeContext(
34240
+ priorTurns,
34241
+ dynamicResult.section
34242
+ );
33585
34243
  const response = await this.substrateSelector.invokeSummarize(
33586
34244
  "concierge",
33587
34245
  {
@@ -33645,7 +34303,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33645
34303
  ...assistantTurnId !== void 0 ? { turn_index: assistantTurnId } : {},
33646
34304
  ...this.memory ? {
33647
34305
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
33648
- } : {}
34306
+ } : {},
34307
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {}
33649
34308
  };
33650
34309
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
33651
34310
  return {
@@ -33796,10 +34455,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33796
34455
  * ## Sanctuary reference
33797
34456
  * <static domain reference block>
33798
34457
  *
34458
+ * ## Live fortress context ← WP-V1.3-9 Tau-3, when present
34459
+ * ### <Category>
34460
+ * <fetcher payload>
34461
+ *
33799
34462
  * ## Prior conversation ← WP-V1.3-9 Tau-2, when present
33800
34463
  * OPERATOR: ...
33801
34464
  * CONCIERGE: ...
33802
- * ---
33803
34465
  *
33804
34466
  * ## Recent activity
33805
34467
  * <recentActivity output>
@@ -33818,13 +34480,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33818
34480
  * if available; the v1.2 selector does not expose one, so structured
33819
34481
  * serialization is the canonical path for v1.3.
33820
34482
  */
33821
- async assembleConciergeContext(priorTurns = []) {
34483
+ async assembleConciergeContext(priorTurns = [], dynamicSection = "") {
33822
34484
  const ref = `## Sanctuary reference
33823
34485
  ${SANCTUARY_DOMAIN_REFERENCE}`;
33824
34486
  const priorSection = this.formatPriorTurnsSection(priorTurns);
33825
34487
  if (!this.contextProviders) {
33826
34488
  return [
33827
34489
  ref,
34490
+ ...dynamicSection ? [dynamicSection] : [],
33828
34491
  ...priorSection ? [priorSection] : [],
33829
34492
  "## Recent activity\n(no providers wired)",
33830
34493
  "## Wrapped agents\n(no providers wired)",
@@ -33838,6 +34501,7 @@ ${SANCTUARY_DOMAIN_REFERENCE}`;
33838
34501
  ]);
33839
34502
  return [
33840
34503
  ref,
34504
+ ...dynamicSection ? [dynamicSection] : [],
33841
34505
  ...priorSection ? [priorSection] : [],
33842
34506
  `## Recent activity
33843
34507
  ${activity}`,
@@ -33847,6 +34511,51 @@ ${agents}`,
33847
34511
  ${inbox}`
33848
34512
  ].join("\n\n");
33849
34513
  }
34514
+ /**
34515
+ * Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
34516
+ * trip. Fail-soft on every axis: missing fetchers short-circuit to
34517
+ * an empty fold, fetcher failures emit a per-category audit event
34518
+ * and are omitted from the rendered section, an LLM-assist failure
34519
+ * proceeds with no fold. Returns the rendered section + the list of
34520
+ * categories whose data made it into the section (used for the
34521
+ * round-trip audit emission).
34522
+ */
34523
+ async runDynamicContextFold(query) {
34524
+ if (!this.contextFetchers) {
34525
+ return { section: "", categoriesIncluded: [] };
34526
+ }
34527
+ const result = await foldContext(query, this.contextFetchers, {
34528
+ maxTokens: this.dynamicContextBudget,
34529
+ ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
34530
+ onFetcherFailure: (category, error) => {
34531
+ this.emitContextFetcherFailed(category, classifyFetcherError(error));
34532
+ }
34533
+ });
34534
+ return result;
34535
+ }
34536
+ /**
34537
+ * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
34538
+ * of the fold path so the dynamic-context handler stays readable.
34539
+ * Emits with `result: "failure"` since the named category dropped
34540
+ * from the rendered section for this round-trip.
34541
+ */
34542
+ emitContextFetcherFailed(category, failureReason) {
34543
+ const payload = {
34544
+ version: "1.2",
34545
+ event_id: makeEventId("conc-ctxfail"),
34546
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
34547
+ identity_id: this.identityId,
34548
+ kind: "operator_concierge_context_fetcher_failed",
34549
+ surface: "concierge",
34550
+ category,
34551
+ failure_reason: failureReason
34552
+ };
34553
+ this.emit(
34554
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
34555
+ payload,
34556
+ "failure"
34557
+ );
34558
+ }
33850
34559
  /**
33851
34560
  * Render the prior-conversation section with token-budget enforcement
33852
34561
  * (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
@@ -33857,14 +34566,14 @@ ${inbox}`
33857
34566
  if (turns.length === 0) return "";
33858
34567
  const HEADER = "## Prior conversation";
33859
34568
  const lines = turns.map(formatPriorTurnLine);
33860
- const headerTokens = approxTokenLen(`${HEADER}
34569
+ const headerTokens = approxTokenLen2(`${HEADER}
33861
34570
  `);
33862
- const sepTokens = approxTokenLen("\n");
34571
+ const sepTokens = approxTokenLen2("\n");
33863
34572
  let runningTokens = headerTokens;
33864
34573
  let runningLines = [];
33865
34574
  for (let i = lines.length - 1; i >= 0; i--) {
33866
34575
  const line = lines[i];
33867
- const tokens = approxTokenLen(line) + (runningLines.length > 0 ? sepTokens : 0);
34576
+ const tokens = approxTokenLen2(line) + (runningLines.length > 0 ? sepTokens : 0);
33868
34577
  if (runningTokens + tokens > this.historyTokenBudget) break;
33869
34578
  runningTokens += tokens;
33870
34579
  runningLines.push(line);
@@ -33892,7 +34601,7 @@ ${runningLines.join("\n")}`;
33892
34601
  function chatStorageKey(surface, threadKey) {
33893
34602
  return `${surface}.${threadKey}`;
33894
34603
  }
33895
- var OPERATOR_CHAT_NAMESPACE, HKDF_INFO, OperatorChatStore;
34604
+ var OPERATOR_CHAT_NAMESPACE, HKDF_INFO2, OperatorChatStore;
33896
34605
  var init_operator_chat_store = __esm({
33897
34606
  "src/chat/operator-chat-store.ts"() {
33898
34607
  init_encryption();
@@ -33900,13 +34609,13 @@ var init_operator_chat_store = __esm({
33900
34609
  init_encoding();
33901
34610
  init_operator_chat_types();
33902
34611
  OPERATOR_CHAT_NAMESPACE = "_chat";
33903
- HKDF_INFO = "operator-chat-store-v1";
34612
+ HKDF_INFO2 = "operator-chat-store-v1";
33904
34613
  OperatorChatStore = class {
33905
34614
  storage;
33906
34615
  encryptionKey;
33907
34616
  constructor(storage, masterKey) {
33908
34617
  this.storage = storage;
33909
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO);
34618
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
33910
34619
  }
33911
34620
  /**
33912
34621
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -33991,7 +34700,7 @@ var init_operator_chat_store = __esm({
33991
34700
  function bundleKey(threadId) {
33992
34701
  return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
33993
34702
  }
33994
- function stripKeyPrefix(key) {
34703
+ function stripKeyPrefix2(key) {
33995
34704
  if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
33996
34705
  return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
33997
34706
  }
@@ -34002,7 +34711,7 @@ function lastTurnId(bundle) {
34002
34711
  }
34003
34712
  return max;
34004
34713
  }
34005
- var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO2, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES2, ConciergeMemoryStore;
34714
+ var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO3, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
34006
34715
  var init_concierge_memory_store = __esm({
34007
34716
  "src/chat/concierge-memory-store.ts"() {
34008
34717
  init_encryption();
@@ -34010,9 +34719,9 @@ var init_concierge_memory_store = __esm({
34010
34719
  init_encoding();
34011
34720
  CONCIERGE_MEMORY_NAMESPACE = "_chat";
34012
34721
  CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
34013
- HKDF_INFO2 = "concierge-memory-store-v1";
34722
+ HKDF_INFO3 = "concierge-memory-store-v1";
34014
34723
  DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
34015
- MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
34724
+ MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
34016
34725
  ConciergeMemoryStore = class {
34017
34726
  storage;
34018
34727
  encryptionKey;
@@ -34021,7 +34730,7 @@ var init_concierge_memory_store = __esm({
34021
34730
  locks;
34022
34731
  constructor(opts) {
34023
34732
  this.storage = opts.storage;
34024
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
34733
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
34025
34734
  this.fortressId = opts.fortressId;
34026
34735
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
34027
34736
  this.locks = /* @__PURE__ */ new Map();
@@ -34100,7 +34809,7 @@ var init_concierge_memory_store = __esm({
34100
34809
  return { ok: false, reason: "io_failed" };
34101
34810
  }
34102
34811
  if (!raw) return { ok: true, turns: [] };
34103
- if (raw.length > MAX_BUNDLE_BYTES2) {
34812
+ if (raw.length > MAX_BUNDLE_BYTES3) {
34104
34813
  return { ok: false, reason: "oversize_bundle" };
34105
34814
  }
34106
34815
  let envelope;
@@ -34147,7 +34856,7 @@ var init_concierge_memory_store = __esm({
34147
34856
  );
34148
34857
  const summaries = [];
34149
34858
  for (const meta of entries) {
34150
- const threadId = stripKeyPrefix(meta.key);
34859
+ const threadId = stripKeyPrefix2(meta.key);
34151
34860
  if (threadId === null) continue;
34152
34861
  const bundle = await this.loadBundle(threadId);
34153
34862
  if (!bundle || bundle.turns.length === 0) continue;
@@ -34200,7 +34909,7 @@ var init_concierge_memory_store = __esm({
34200
34909
  );
34201
34910
  let pruned = 0;
34202
34911
  for (const meta of entries) {
34203
- const threadId = stripKeyPrefix(meta.key);
34912
+ const threadId = stripKeyPrefix2(meta.key);
34204
34913
  if (threadId === null) continue;
34205
34914
  pruned += await this.withLock(threadId, async () => {
34206
34915
  const bundle = await this.loadBundle(threadId);
@@ -34231,7 +34940,7 @@ var init_concierge_memory_store = __esm({
34231
34940
  return null;
34232
34941
  }
34233
34942
  if (!raw) return null;
34234
- if (raw.length > MAX_BUNDLE_BYTES2) return null;
34943
+ if (raw.length > MAX_BUNDLE_BYTES3) return null;
34235
34944
  try {
34236
34945
  const envelope = JSON.parse(bytesToString(raw));
34237
34946
  const aad = stringToBytes(threadId);
@@ -34322,7 +35031,18 @@ function buildV11Bindings(inputs) {
34322
35031
  registry
34323
35032
  }),
34324
35033
  conciergePiiFilter: buildConciergePiiFilter(),
34325
- conciergeMemory
35034
+ conciergeMemory,
35035
+ conciergeContextFetchers: buildConciergeContextFetchers({
35036
+ auditLog: inputs.auditLog,
35037
+ identityId: inputs.identityId,
35038
+ registry
35039
+ }),
35040
+ ...inputs.intelligenceSelector ? {
35041
+ conciergeContextLlmAssist: buildConciergeContextLlmAssist({
35042
+ selector: inputs.intelligenceSelector,
35043
+ identityId: inputs.identityId
35044
+ })
35045
+ } : {}
34326
35046
  });
34327
35047
  }
34328
35048
  const hubService = new HubService({
@@ -34383,6 +35103,107 @@ function buildConciergeContextProviders(args) {
34383
35103
  }
34384
35104
  };
34385
35105
  }
35106
+ function buildConciergeContextFetchers(args) {
35107
+ const empty = async () => "";
35108
+ return {
35109
+ templates: async () => {
35110
+ const entries = listTemplates();
35111
+ if (entries.length === 0) return "(no templates installed)";
35112
+ const lines = entries.map((e) => {
35113
+ const m = e.metadata;
35114
+ return `${m.name} (tier ${m.tier}, channel ${m.channel}, target ${m.target_archetype})`;
35115
+ });
35116
+ return lines.join("\n");
35117
+ },
35118
+ agent_state: async (agentNameHint) => {
35119
+ const records = args.registry.list({ identity_id: args.identityId });
35120
+ if (records.length === 0) return "(no wrapped agents)";
35121
+ const filtered = agentNameHint ? records.filter(
35122
+ (r) => r.agent_id.toLowerCase().includes(agentNameHint.toLowerCase()) || r.harness.toLowerCase().includes(agentNameHint.toLowerCase())
35123
+ ) : records;
35124
+ const target = filtered.length > 0 ? filtered : records;
35125
+ const lines = target.slice(0, 20).map((r) => {
35126
+ const tmpl = typeof r.channel_template_id === "string" ? r.channel_template_id : "no_template";
35127
+ return `${r.agent_id} harness=${r.harness} status=${r.status} template=${tmpl}`;
35128
+ });
35129
+ return lines.join("\n");
35130
+ },
35131
+ agent_activity: async (agentNameHint) => {
35132
+ const result = await args.auditLog.query({ limit: 50 });
35133
+ const owned = result.entries.filter(
35134
+ (e) => e.identity_id === args.identityId
35135
+ );
35136
+ const filtered = agentNameHint ? owned.filter((e) => {
35137
+ const agentId = e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : "";
35138
+ return agentId.toLowerCase().includes(agentNameHint.toLowerCase());
35139
+ }) : owned;
35140
+ const tail = (filtered.length > 0 ? filtered : owned).slice(-20);
35141
+ if (tail.length === 0) return "(no activity)";
35142
+ return tail.map((e) => {
35143
+ const agentId = (e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : null) ?? "_fortress";
35144
+ return `${e.timestamp} ${e.layer}.${e.operation} agent=${agentId} result=${e.result}`;
35145
+ }).join("\n");
35146
+ },
35147
+ audit_log: async () => {
35148
+ const result = await args.auditLog.query({ limit: 30 });
35149
+ const owned = result.entries.filter(
35150
+ (e) => e.identity_id === args.identityId
35151
+ );
35152
+ if (owned.length === 0) return "(no audit log entries)";
35153
+ return owned.slice(-30).map(
35154
+ (e) => `${e.timestamp} ${e.layer}.${e.operation} result=${e.result}`
35155
+ ).join("\n");
35156
+ },
35157
+ sentinel_findings: empty,
35158
+ anomaly_alerts: empty,
35159
+ recent_receipts: async () => {
35160
+ const result = await args.auditLog.query({ limit: 100 });
35161
+ const owned = result.entries.filter(
35162
+ (e) => e.identity_id === args.identityId && e.operation.startsWith("composition_")
35163
+ );
35164
+ if (owned.length === 0) return "(no recent composition events)";
35165
+ return owned.slice(-15).map((e) => `${e.timestamp} ${e.operation} result=${e.result}`).join("\n");
35166
+ },
35167
+ verascore_deltas: empty
35168
+ };
35169
+ }
35170
+ function buildConciergeContextLlmAssist(args) {
35171
+ return async (query, categories) => {
35172
+ const labelList = categories.map((c) => `- ${c}`).join("\n");
35173
+ const prompt2 = `You are a router. Classify the operator's query into one of the categories below or "none".
35174
+ Reply with exactly one token: one category name or "none".
35175
+
35176
+ Categories:
35177
+ ${labelList}
35178
+
35179
+ Query: ${query}
35180
+
35181
+ Category:`;
35182
+ try {
35183
+ const handle = await args.selector.getSubstrate("concierge");
35184
+ if (!handle.capability.summarize) return "none";
35185
+ const response = await args.selector.invokeSummarize("concierge", {
35186
+ kind: "summarize",
35187
+ context: prompt2,
35188
+ query: "Output the single category token.",
35189
+ maxTokens: 16
35190
+ });
35191
+ if (response.failureClass || response.body.kind !== "summarize") {
35192
+ return "none";
35193
+ }
35194
+ const raw = response.body.text.trim().toLowerCase();
35195
+ const head = raw.split(/\s|[.,!?:;]/)[0] ?? "";
35196
+ const normalized = head.replace(/[^a-z_]/g, "");
35197
+ const known = categories;
35198
+ if (known.includes(normalized)) {
35199
+ return normalized;
35200
+ }
35201
+ return "none";
35202
+ } catch {
35203
+ return "none";
35204
+ }
35205
+ };
35206
+ }
34386
35207
  function buildConciergePiiFilter() {
34387
35208
  return {
34388
35209
  filter(input) {
@@ -34410,6 +35231,7 @@ var init_wiring = __esm({
34410
35231
  init_agent_registry_persistence();
34411
35232
  init_operator_chat_index();
34412
35233
  init_privacy_filter();
35234
+ init_registry();
34413
35235
  CapabilityErrorAgentController = class {
34414
35236
  fail(action) {
34415
35237
  throw new HubCapabilityError(
@@ -34557,7 +35379,7 @@ var init_defaults = __esm({
34557
35379
  });
34558
35380
 
34559
35381
  // src/intelligence/policy-store.ts
34560
- var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO3, IntelligenceConfigStore;
35382
+ var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO4, IntelligenceConfigStore;
34561
35383
  var init_policy_store = __esm({
34562
35384
  "src/intelligence/policy-store.ts"() {
34563
35385
  init_encryption();
@@ -34566,13 +35388,13 @@ var init_policy_store = __esm({
34566
35388
  init_defaults();
34567
35389
  INTELLIGENCE_NAMESPACE = "_intelligence";
34568
35390
  SUBSTRATE_CONFIG_KEY = "substrate-config";
34569
- HKDF_INFO3 = "intelligence-substrate-config";
35391
+ HKDF_INFO4 = "intelligence-substrate-config";
34570
35392
  IntelligenceConfigStore = class {
34571
35393
  storage;
34572
35394
  encryptionKey;
34573
35395
  constructor(storage, masterKey) {
34574
35396
  this.storage = storage;
34575
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
35397
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
34576
35398
  }
34577
35399
  /**
34578
35400
  * Load the operator's substrate config from disk. Returns the config
@@ -38490,12 +39312,18 @@ ${err.message}
38490
39312
  } : void 0;
38491
39313
  const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
38492
39314
  const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
39315
+ const aggregatorPayloadStore = new AggregatorPayloadStore({
39316
+ storage,
39317
+ masterKey,
39318
+ fortressId: fortressIdForAggregator
39319
+ });
38493
39320
  const approvalAggregator = new ApprovalAggregator({
38494
39321
  storage,
38495
39322
  masterKey,
38496
39323
  auditLog,
38497
39324
  identityId: aggregatorIdentityId,
38498
- fortressId: fortressIdForAggregator
39325
+ fortressId: fortressIdForAggregator,
39326
+ payloadStore: aggregatorPayloadStore
38499
39327
  });
38500
39328
  const wrappedApprovalChannel = new AggregatorBackedChannel({
38501
39329
  underlying: approvalChannel,
@@ -38709,6 +39537,7 @@ var init_src = __esm({
38709
39537
  init_gate();
38710
39538
  init_approval_aggregator();
38711
39539
  init_aggregator_backed_channel();
39540
+ init_aggregator_store();
38712
39541
  init_tools4();
38713
39542
  init_router();
38714
39543
  init_router();