@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.cjs CHANGED
@@ -17218,6 +17218,27 @@ async function handleApprovalInboxRoute(deps, req, res) {
17218
17218
  await handleStream2(deps, res);
17219
17219
  return true;
17220
17220
  }
17221
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17222
+ const limit = parseLimit2(
17223
+ url.searchParams.get("limit"),
17224
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17225
+ APPROVAL_INBOX_MAX_LIMIT
17226
+ );
17227
+ const statusRaw = url.searchParams.get("status");
17228
+ const sinceTs = url.searchParams.get("since") ?? void 0;
17229
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17230
+ const filterStatus = statusRaw && isStatusFilter(statusRaw) && statusRaw !== "pending" ? statusRaw : void 0;
17231
+ const entries = await deps.aggregator.getHistory(
17232
+ {
17233
+ limit,
17234
+ ...filterStatus !== void 0 ? { status: filterStatus } : {},
17235
+ ...sinceTs !== void 0 ? { sinceTs } : {}
17236
+ },
17237
+ operatorId
17238
+ );
17239
+ writeJSON4(res, 200, { ok: true, data: { entries } });
17240
+ return true;
17241
+ }
17221
17242
  if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
17222
17243
  const limit = parseLimit2(
17223
17244
  url.searchParams.get("limit"),
@@ -17240,11 +17261,39 @@ async function handleApprovalInboxRoute(deps, req, res) {
17240
17261
  writeJSON4(res, 404, { ok: false, error: "not_found", path });
17241
17262
  return true;
17242
17263
  }
17243
- if (method === "GET" && entryMatch.action === null) {
17244
- const entries = await deps.aggregator.list({ limit: APPROVAL_INBOX_MAX_LIMIT });
17245
- const entry = entries.find(
17246
- (e) => e.aggregator_id === entryMatch.aggregatorId
17264
+ if (method === "GET" && entryMatch.action === "audit-trail") {
17265
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17266
+ if (!entry) {
17267
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17268
+ return true;
17269
+ }
17270
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17271
+ const trail = await deps.aggregator.getAuditTrail(
17272
+ entryMatch.aggregatorId,
17273
+ operatorId
17247
17274
  );
17275
+ writeJSON4(res, 200, { ok: true, data: { entry, audit_trail: trail } });
17276
+ return true;
17277
+ }
17278
+ if (method === "GET" && entryMatch.action === "payload") {
17279
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17280
+ if (!entry) {
17281
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17282
+ return true;
17283
+ }
17284
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17285
+ const payload = await deps.aggregator.getFullPayloadWithAudit(
17286
+ entryMatch.aggregatorId,
17287
+ operatorId
17288
+ );
17289
+ writeJSON4(res, 200, {
17290
+ ok: true,
17291
+ data: { entry, request_payload: payload }
17292
+ });
17293
+ return true;
17294
+ }
17295
+ if (method === "GET" && entryMatch.action === null) {
17296
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17248
17297
  if (!entry) {
17249
17298
  writeJSON4(res, 404, { ok: false, error: "not_found" });
17250
17299
  return true;
@@ -20278,7 +20327,10 @@ var init_approval_aggregator = __esm({
20278
20327
  APPROVAL_AGGREGATOR_AUDIT_OPS = {
20279
20328
  AGGREGATED: "cross_harness_approval_aggregated",
20280
20329
  RESOLVED: "cross_harness_approval_resolved",
20281
- DEDUPED: "cross_harness_approval_deduped"
20330
+ DEDUPED: "cross_harness_approval_deduped",
20331
+ PAYLOAD_DECRYPTED: "cross_harness_approval_payload_decrypted",
20332
+ AUDIT_TRAIL_VIEWED: "cross_harness_approval_audit_trail_viewed",
20333
+ REPLAYED: "cross_harness_approval_replayed"
20282
20334
  };
20283
20335
  DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
20284
20336
  DEFAULT_MAX_LIST_LIMIT = 200;
@@ -20294,6 +20346,8 @@ var init_approval_aggregator = __esm({
20294
20346
  now;
20295
20347
  resolveSourceContext;
20296
20348
  resolveHubInboxItemId;
20349
+ payloadStore;
20350
+ resolveEnforcementChain;
20297
20351
  /** Cached entries by `aggregator_id`. */
20298
20352
  entries = /* @__PURE__ */ new Map();
20299
20353
  /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
@@ -20323,6 +20377,14 @@ var init_approval_aggregator = __esm({
20323
20377
  source_agent_id: this.fortressId
20324
20378
  }));
20325
20379
  this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
20380
+ this.payloadStore = deps.payloadStore ?? null;
20381
+ this.resolveEnforcementChain = deps.resolveEnforcementChain ?? ((event) => [
20382
+ {
20383
+ layer: "l2",
20384
+ event: `approval_required:${event.operation}`,
20385
+ timestamp: event.request_timestamp
20386
+ }
20387
+ ]);
20326
20388
  }
20327
20389
  /**
20328
20390
  * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
@@ -20373,13 +20435,152 @@ var init_approval_aggregator = __esm({
20373
20435
  }
20374
20436
  /**
20375
20437
  * Return the original (unhashed) request payload for the entry. Returns
20376
- * `null` when the entry is unknown or the payload was evicted (e.g. the
20377
- * process restarted; payloads are in-memory only at v1.3 Upsilon-1).
20438
+ * `null` when the entry is unknown. When the in-memory payload map has
20439
+ * been evicted (e.g. after a server restart) and a `payloadStore` was
20440
+ * provided, the at-rest bundle is decrypted and the in-memory map is
20441
+ * refilled. Audit emission lives on the `*WithAudit` variant; this base
20442
+ * accessor is silent so internal callers can read without polluting the
20443
+ * audit trail.
20378
20444
  */
20379
20445
  async getFullPayload(aggregatorId) {
20380
20446
  await this.hydrate();
20381
20447
  if (!this.entries.has(aggregatorId)) return null;
20382
- return this.fullPayloads.get(aggregatorId) ?? null;
20448
+ const cached = this.fullPayloads.get(aggregatorId);
20449
+ if (cached !== void 0) return cached;
20450
+ if (this.payloadStore) {
20451
+ try {
20452
+ const restored = await this.payloadStore.loadPayload(aggregatorId);
20453
+ if (restored !== null) {
20454
+ this.fullPayloads.set(aggregatorId, restored);
20455
+ return restored;
20456
+ }
20457
+ } catch {
20458
+ }
20459
+ }
20460
+ return null;
20461
+ }
20462
+ /**
20463
+ * Return the entry record for the given id, or null when unknown.
20464
+ * Idempotent. v1.3 Upsilon-3.
20465
+ */
20466
+ async getEntry(aggregatorId) {
20467
+ await this.hydrate();
20468
+ return this.entries.get(aggregatorId) ?? null;
20469
+ }
20470
+ /**
20471
+ * Audited variant of `getFullPayload`. Emits the
20472
+ * `cross_harness_approval_payload_decrypted` audit event before
20473
+ * returning. Used by the operator-facing /payload replay route.
20474
+ * v1.3 Upsilon-3.
20475
+ */
20476
+ async getFullPayloadWithAudit(aggregatorId, operatorId) {
20477
+ const payload = await this.getFullPayload(aggregatorId);
20478
+ if (payload === null) return null;
20479
+ const entry = this.entries.get(aggregatorId);
20480
+ this.auditLog.append(
20481
+ "l2",
20482
+ APPROVAL_AGGREGATOR_AUDIT_OPS.PAYLOAD_DECRYPTED,
20483
+ operatorId,
20484
+ {
20485
+ aggregator_id: aggregatorId,
20486
+ ...entry ? {
20487
+ source_harness: entry.source_harness,
20488
+ source_agent_id: entry.source_agent_id,
20489
+ entry_status: entry.status
20490
+ } : {}
20491
+ }
20492
+ );
20493
+ return payload;
20494
+ }
20495
+ /**
20496
+ * Return the audit-log entries that led to and surround this approval.
20497
+ * Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
20498
+ * DEDUPED, replay events) all carry `details.aggregator_id` and link
20499
+ * directly. Gate-side emissions (`gate_*:operation`) do not carry the
20500
+ * aggregator id at v1.3, so they are matched via timestamp window
20501
+ * (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
20502
+ * pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
20503
+ * v1.3 Upsilon-3.
20504
+ */
20505
+ async getAuditTrail(aggregatorId, operatorId) {
20506
+ await this.hydrate();
20507
+ const entry = this.entries.get(aggregatorId);
20508
+ if (!entry) {
20509
+ return [];
20510
+ }
20511
+ const sinceMs = Date.parse(entry.created_at) - 1e3;
20512
+ const sinceIso = new Date(sinceMs).toISOString();
20513
+ const queried = await this.auditLog.query({ since: sinceIso, limit: 1e3 });
20514
+ const operationPart = entry.policy_rule_id.includes(":") ? entry.policy_rule_id.slice(entry.policy_rule_id.indexOf(":") + 1) : entry.policy_rule_id;
20515
+ const lifetimeStart = sinceMs;
20516
+ const lifetimeEnd = entry.resolved_at ? Date.parse(entry.resolved_at) + 1e3 : Date.parse(entry.expires_at) + 1e3;
20517
+ const matches = [];
20518
+ for (const audit of queried.entries) {
20519
+ const detailsId = audit.details !== void 0 ? audit.details["aggregator_id"] : void 0;
20520
+ if (detailsId === aggregatorId) {
20521
+ matches.push(audit);
20522
+ continue;
20523
+ }
20524
+ const auditMs = Date.parse(audit.timestamp);
20525
+ if (auditMs < lifetimeStart || auditMs > lifetimeEnd) continue;
20526
+ if (audit.operation.endsWith(`:${operationPart}`)) {
20527
+ matches.push(audit);
20528
+ }
20529
+ }
20530
+ matches.sort(
20531
+ (a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0
20532
+ );
20533
+ this.auditLog.append(
20534
+ "l2",
20535
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AUDIT_TRAIL_VIEWED,
20536
+ operatorId,
20537
+ {
20538
+ aggregator_id: aggregatorId,
20539
+ entry_status: entry.status,
20540
+ match_count: matches.length
20541
+ }
20542
+ );
20543
+ return matches;
20544
+ }
20545
+ /**
20546
+ * List historical (resolved) approvals. Excludes pending entries by
20547
+ * design: `list()` is the pending-inbox surface and `getHistory()` is
20548
+ * the resolved-replay surface. Emits REPLAYED on each call. v1.3
20549
+ * Upsilon-3.
20550
+ */
20551
+ async getHistory(opts, operatorId) {
20552
+ await this.hydrate();
20553
+ await this.expireStale();
20554
+ const limit = Math.min(
20555
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20556
+ this.maxListLimit
20557
+ );
20558
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
20559
+ const matching = [];
20560
+ for (const entry of this.entries.values()) {
20561
+ if (entry.status === "pending") continue;
20562
+ if (opts?.status && entry.status !== opts.status) continue;
20563
+ const stamp = Date.parse(entry.resolved_at ?? entry.created_at);
20564
+ if (stamp < sinceMs) continue;
20565
+ matching.push(entry);
20566
+ }
20567
+ matching.sort((a, b) => {
20568
+ const aStamp = a.resolved_at ?? a.created_at;
20569
+ const bStamp = b.resolved_at ?? b.created_at;
20570
+ return bStamp.localeCompare(aStamp);
20571
+ });
20572
+ const sliced = matching.slice(0, limit);
20573
+ this.auditLog.append(
20574
+ "l2",
20575
+ APPROVAL_AGGREGATOR_AUDIT_OPS.REPLAYED,
20576
+ operatorId,
20577
+ {
20578
+ result_count: sliced.length,
20579
+ ...opts?.status !== void 0 ? { status_filter: opts.status } : {},
20580
+ ...opts?.sinceTs !== void 0 ? { since: opts.sinceTs } : {}
20581
+ }
20582
+ );
20583
+ return sliced;
20383
20584
  }
20384
20585
  /**
20385
20586
  * Resolve an entry. Used by both:
@@ -20453,6 +20654,7 @@ var init_approval_aggregator = __esm({
20453
20654
  const now = this.now();
20454
20655
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20455
20656
  const hubInboxId = this.resolveHubInboxItemId(event);
20657
+ const enforcementChain = this.resolveEnforcementChain(event);
20456
20658
  const entry = {
20457
20659
  aggregator_id: id,
20458
20660
  source_harness: ctx.source_harness,
@@ -20464,13 +20666,20 @@ var init_approval_aggregator = __esm({
20464
20666
  status: "pending",
20465
20667
  created_at: now.toISOString(),
20466
20668
  expires_at: expires.toISOString(),
20467
- ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
20669
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20670
+ ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20468
20671
  };
20469
20672
  this.entries.set(id, entry);
20470
20673
  this.dedupIndex.set(dedupKey, id);
20471
20674
  this.correlationIndex.set(event.correlation_id, id);
20472
20675
  this.fullPayloads.set(id, event.context);
20473
20676
  await this.persist(entry);
20677
+ if (this.payloadStore) {
20678
+ try {
20679
+ await this.payloadStore.savePayload(id, event.context);
20680
+ } catch {
20681
+ }
20682
+ }
20474
20683
  this.auditLog.append(
20475
20684
  "l2",
20476
20685
  APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
@@ -20786,6 +20995,149 @@ var init_aggregator_backed_channel = __esm({
20786
20995
  }
20787
20996
  });
20788
20997
 
20998
+ // src/principal-policy/aggregator-store.ts
20999
+ function payloadKey(aggregatorId) {
21000
+ return `${AGGREGATOR_PAYLOAD_KEY_PREFIX}${aggregatorId}`;
21001
+ }
21002
+ function stripKeyPrefix(key) {
21003
+ if (!key.startsWith(AGGREGATOR_PAYLOAD_KEY_PREFIX)) return null;
21004
+ return key.slice(AGGREGATOR_PAYLOAD_KEY_PREFIX.length);
21005
+ }
21006
+ var AGGREGATOR_PAYLOAD_NAMESPACE, AGGREGATOR_PAYLOAD_KEY_PREFIX, HKDF_INFO, DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS, MAX_BUNDLE_BYTES2, AggregatorPayloadStore;
21007
+ var init_aggregator_store = __esm({
21008
+ "src/principal-policy/aggregator-store.ts"() {
21009
+ init_encryption();
21010
+ init_key_derivation();
21011
+ init_encoding();
21012
+ AGGREGATOR_PAYLOAD_NAMESPACE = "_approval_aggregator_payloads";
21013
+ AGGREGATOR_PAYLOAD_KEY_PREFIX = "payload.";
21014
+ HKDF_INFO = "l2-approval-aggregator-payload-v1";
21015
+ DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS = 30;
21016
+ MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
21017
+ AggregatorPayloadStore = class {
21018
+ storage;
21019
+ encryptionKey;
21020
+ fortressId;
21021
+ retentionDays;
21022
+ constructor(opts) {
21023
+ this.storage = opts.storage;
21024
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO);
21025
+ this.fortressId = opts.fortressId;
21026
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS;
21027
+ }
21028
+ /**
21029
+ * Persist `payload` under the given aggregator_id. Idempotent; calling
21030
+ * twice with the same id rewrites the bundle (retention_until is
21031
+ * recomputed). Returns the bundle's retention_until ISO-8601 timestamp
21032
+ * so callers can log it.
21033
+ */
21034
+ async savePayload(aggregatorId, payload) {
21035
+ const now = /* @__PURE__ */ new Date();
21036
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
21037
+ const retentionUntil = new Date(now.getTime() + retentionMs);
21038
+ const bundle = {
21039
+ version: 1,
21040
+ aggregator_id: aggregatorId,
21041
+ fortress_id: this.fortressId,
21042
+ created_at: now.toISOString(),
21043
+ retention_until: retentionUntil.toISOString(),
21044
+ payload
21045
+ };
21046
+ const aad = stringToBytes(aggregatorId);
21047
+ const plaintext = stringToBytes(JSON.stringify(bundle));
21048
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
21049
+ await this.storage.write(
21050
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21051
+ payloadKey(aggregatorId),
21052
+ stringToBytes(JSON.stringify(envelope))
21053
+ );
21054
+ return bundle.retention_until;
21055
+ }
21056
+ /**
21057
+ * Read the persisted payload for the aggregator_id. Returns null if no
21058
+ * bundle exists, the bundle is corrupted, or AAD binding fails.
21059
+ */
21060
+ async loadPayload(aggregatorId) {
21061
+ const key = payloadKey(aggregatorId);
21062
+ let raw;
21063
+ try {
21064
+ raw = await this.storage.read(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21065
+ } catch {
21066
+ return null;
21067
+ }
21068
+ if (!raw) return null;
21069
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
21070
+ try {
21071
+ const envelope = JSON.parse(bytesToString(raw));
21072
+ const aad = stringToBytes(aggregatorId);
21073
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21074
+ const parsed = JSON.parse(
21075
+ bytesToString(plaintext)
21076
+ );
21077
+ if (parsed.version !== 1) return null;
21078
+ if (parsed.aggregator_id !== aggregatorId) return null;
21079
+ return parsed.payload;
21080
+ } catch {
21081
+ return null;
21082
+ }
21083
+ }
21084
+ /**
21085
+ * Delete the persisted payload. Returns true when a bundle was removed,
21086
+ * false when none existed.
21087
+ */
21088
+ async deletePayload(aggregatorId) {
21089
+ const key = payloadKey(aggregatorId);
21090
+ const existed = await this.storage.exists(
21091
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21092
+ key
21093
+ );
21094
+ if (!existed) return false;
21095
+ try {
21096
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21097
+ } catch {
21098
+ return false;
21099
+ }
21100
+ return true;
21101
+ }
21102
+ /**
21103
+ * Drop expired payload bundles. Returns the count of bundles pruned.
21104
+ * Caller wires this into the cocoon-unlock initialization path.
21105
+ */
21106
+ async pruneExpired(now) {
21107
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
21108
+ const entries = await this.storage.list(
21109
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21110
+ AGGREGATOR_PAYLOAD_KEY_PREFIX
21111
+ );
21112
+ let pruned = 0;
21113
+ for (const meta of entries) {
21114
+ const aggregatorId = stripKeyPrefix(meta.key);
21115
+ if (aggregatorId === null) continue;
21116
+ const raw = await this.storage.read(
21117
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21118
+ meta.key
21119
+ );
21120
+ if (!raw) continue;
21121
+ try {
21122
+ const envelope = JSON.parse(bytesToString(raw));
21123
+ const aad = stringToBytes(aggregatorId);
21124
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21125
+ const parsed = JSON.parse(
21126
+ bytesToString(plaintext)
21127
+ );
21128
+ if (parsed.retention_until <= cutoff) {
21129
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, meta.key);
21130
+ pruned += 1;
21131
+ }
21132
+ } catch {
21133
+ }
21134
+ }
21135
+ return { pruned };
21136
+ }
21137
+ };
21138
+ }
21139
+ });
21140
+
20789
21141
  // src/principal-policy/tools.ts
20790
21142
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
20791
21143
  return [
@@ -33404,7 +33756,14 @@ var init_operator_chat_audit_events = __esm({
33404
33756
  * turns; the concierge degrades to single-turn after emitting. Body
33405
33757
  * carries thread_id + a stable failure_reason enum.
33406
33758
  */
33407
- CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed"
33759
+ CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed",
33760
+ /**
33761
+ * Concierge dynamic-context fetcher failed (WP-V1.3-9 Tau-3). Emitted
33762
+ * when a category fetcher throws while assembling the dynamic context
33763
+ * fold. The concierge omits that category and continues; the user-
33764
+ * facing query is never broken. Body carries category + failure_reason.
33765
+ */
33766
+ CONCIERGE_CONTEXT_FETCHER_FAILED: "operator_concierge_context_fetcher_failed"
33408
33767
  };
33409
33768
  }
33410
33769
  });
@@ -33417,12 +33776,291 @@ var init_operator_chat_types = __esm({
33417
33776
  CONCIERGE_THREAD_KEY = "_fortress";
33418
33777
  }
33419
33778
  });
33779
+
33780
+ // src/chat/concierge-context-router.ts
33781
+ function phrasePattern(phrase) {
33782
+ const escaped = phrase.toLowerCase().split(/\s+/).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
33783
+ return { source: `\\b${escaped}\\b`, phrase };
33784
+ }
33785
+ function extractAgentNameHint(query) {
33786
+ const agentPattern = /\bagent\s+["']?([A-Za-z][\w-]{0,40})["']?/i;
33787
+ const m = query.match(agentPattern);
33788
+ if (m && m[1]) return m[1];
33789
+ const quoted = query.match(/["']([A-Za-z][\w-]{0,40})["']/);
33790
+ if (quoted && quoted[1]) return quoted[1];
33791
+ return null;
33792
+ }
33793
+ function isTrivialQuery(query) {
33794
+ const norm = query.trim().toLowerCase();
33795
+ if (norm.length === 0) return true;
33796
+ if (norm.length < 8) return true;
33797
+ return TRIVIAL_GREETINGS.has(norm);
33798
+ }
33799
+ function classifyQuery(query) {
33800
+ const normalized = query.toLowerCase();
33801
+ const matches = [];
33802
+ for (const spec of CATEGORY_KEYWORDS) {
33803
+ const matchedPhrases = [];
33804
+ for (const pattern of spec.patterns) {
33805
+ if (matchedPhrases.includes(pattern.phrase)) continue;
33806
+ const re = new RegExp(pattern.source, "i");
33807
+ if (re.test(normalized)) {
33808
+ matchedPhrases.push(pattern.phrase);
33809
+ }
33810
+ }
33811
+ if (matchedPhrases.length === 0) continue;
33812
+ const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
33813
+ matches.push({
33814
+ category: spec.category,
33815
+ confidence,
33816
+ matched_keywords: matchedPhrases,
33817
+ agent_name_hint: spec.category === "agent_state" || spec.category === "agent_activity" ? extractAgentNameHint(query) : null
33818
+ });
33819
+ }
33820
+ matches.sort((a, b) => {
33821
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
33822
+ return CONTEXT_CATEGORIES.indexOf(a.category) - CONTEXT_CATEGORIES.indexOf(b.category);
33823
+ });
33824
+ return matches;
33825
+ }
33420
33826
  function approxTokenLen(text) {
33827
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
33828
+ }
33829
+ async function runFetcher(match, fetchers) {
33830
+ switch (match.category) {
33831
+ case "templates":
33832
+ return fetchers.templates();
33833
+ case "agent_state":
33834
+ return fetchers.agent_state(match.agent_name_hint);
33835
+ case "agent_activity":
33836
+ return fetchers.agent_activity(match.agent_name_hint);
33837
+ case "audit_log":
33838
+ return fetchers.audit_log();
33839
+ case "sentinel_findings":
33840
+ return fetchers.sentinel_findings();
33841
+ case "anomaly_alerts":
33842
+ return fetchers.anomaly_alerts();
33843
+ case "recent_receipts":
33844
+ return fetchers.recent_receipts();
33845
+ case "verascore_deltas":
33846
+ return fetchers.verascore_deltas();
33847
+ }
33848
+ }
33849
+ function trivialMatch(category) {
33850
+ return {
33851
+ category,
33852
+ confidence: 0.5,
33853
+ matched_keywords: ["llm-assist"],
33854
+ agent_name_hint: null
33855
+ };
33856
+ }
33857
+ async function foldContext(query, fetchers, opts) {
33858
+ const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
33859
+ let matches = classifyQuery(query);
33860
+ if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
33861
+ try {
33862
+ const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
33863
+ if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
33864
+ matches = [trivialMatch(picked)];
33865
+ }
33866
+ } catch {
33867
+ }
33868
+ }
33869
+ if (matches.length === 0) {
33870
+ return { section: "", categoriesIncluded: [] };
33871
+ }
33872
+ const attempts = [];
33873
+ for (const match of matches) {
33874
+ try {
33875
+ const text = await runFetcher(match, fetchers);
33876
+ const trimmed = text.trim();
33877
+ if (trimmed.length > 0) {
33878
+ attempts.push({ category: match.category, text: trimmed });
33879
+ }
33880
+ } catch (err) {
33881
+ opts?.onFetcherFailure?.(match.category, err);
33882
+ }
33883
+ }
33884
+ if (attempts.length === 0) {
33885
+ return { section: "", categoriesIncluded: [] };
33886
+ }
33887
+ const headerTokens = approxTokenLen(`${DYNAMIC_CONTEXT_SECTION_HEADER}
33888
+ `);
33889
+ const sepTokens = approxTokenLen("\n\n");
33890
+ let runningTokens = headerTokens;
33891
+ const kept = [];
33892
+ for (const attempt of attempts) {
33893
+ const block = `### ${CATEGORY_LABELS[attempt.category]}
33894
+ ${attempt.text}`;
33895
+ const tokens = approxTokenLen(block) + (kept.length > 0 ? sepTokens : 0);
33896
+ if (kept.length === 0) {
33897
+ kept.push(attempt);
33898
+ runningTokens += tokens;
33899
+ continue;
33900
+ }
33901
+ if (runningTokens + tokens > budget) break;
33902
+ kept.push(attempt);
33903
+ runningTokens += tokens;
33904
+ }
33905
+ const blocks = kept.map(
33906
+ (k) => `### ${CATEGORY_LABELS[k.category]}
33907
+ ${k.text}`
33908
+ );
33909
+ const section = `${DYNAMIC_CONTEXT_SECTION_HEADER}
33910
+ ${blocks.join("\n\n")}`;
33911
+ return {
33912
+ section,
33913
+ categoriesIncluded: kept.map((k) => k.category)
33914
+ };
33915
+ }
33916
+ var APPROX_CHARS_PER_TOKEN, DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET, DYNAMIC_CONTEXT_SECTION_HEADER, CONTEXT_CATEGORIES, CATEGORY_KEYWORDS, TRIVIAL_GREETINGS, CATEGORY_LABELS;
33917
+ var init_concierge_context_router = __esm({
33918
+ "src/chat/concierge-context-router.ts"() {
33919
+ APPROX_CHARS_PER_TOKEN = 4;
33920
+ DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET = 2e3;
33921
+ DYNAMIC_CONTEXT_SECTION_HEADER = "## Live fortress context";
33922
+ CONTEXT_CATEGORIES = [
33923
+ "templates",
33924
+ "agent_state",
33925
+ "agent_activity",
33926
+ "audit_log",
33927
+ "sentinel_findings",
33928
+ "anomaly_alerts",
33929
+ "recent_receipts",
33930
+ "verascore_deltas"
33931
+ ];
33932
+ CATEGORY_KEYWORDS = [
33933
+ {
33934
+ category: "templates",
33935
+ patterns: [
33936
+ "templates",
33937
+ "template",
33938
+ "channel templates",
33939
+ "channel template",
33940
+ "list templates",
33941
+ "available templates",
33942
+ "what templates"
33943
+ ].map(phrasePattern)
33944
+ },
33945
+ {
33946
+ category: "agent_state",
33947
+ patterns: [
33948
+ "state",
33949
+ "status",
33950
+ "agent state",
33951
+ "agent status",
33952
+ "status of agent",
33953
+ "status of agents",
33954
+ "state of",
33955
+ "doing"
33956
+ ].map(phrasePattern)
33957
+ },
33958
+ {
33959
+ category: "agent_activity",
33960
+ patterns: [
33961
+ "activity",
33962
+ "agent activity",
33963
+ "what did",
33964
+ "recent activity"
33965
+ ].map(phrasePattern)
33966
+ },
33967
+ {
33968
+ category: "audit_log",
33969
+ patterns: [
33970
+ "audit log",
33971
+ "audit",
33972
+ "log entry",
33973
+ "log entries",
33974
+ "what happened",
33975
+ "show me events",
33976
+ "event class"
33977
+ ].map(phrasePattern)
33978
+ },
33979
+ {
33980
+ category: "sentinel_findings",
33981
+ patterns: [
33982
+ "sentinel",
33983
+ "sentinels",
33984
+ "warning",
33985
+ "warnings",
33986
+ "alert",
33987
+ "alerts",
33988
+ "whats wrong",
33989
+ "what's wrong",
33990
+ "findings"
33991
+ ].map(phrasePattern)
33992
+ },
33993
+ {
33994
+ category: "anomaly_alerts",
33995
+ patterns: [
33996
+ "anomaly",
33997
+ "anomalies",
33998
+ "spike",
33999
+ "unusual",
34000
+ "outlier"
34001
+ ].map(phrasePattern)
34002
+ },
34003
+ {
34004
+ category: "recent_receipts",
34005
+ patterns: [
34006
+ "receipt",
34007
+ "receipts",
34008
+ "concordia",
34009
+ "commitment",
34010
+ "commitments",
34011
+ "chain",
34012
+ "chains"
34013
+ ].map(phrasePattern)
34014
+ },
34015
+ {
34016
+ category: "verascore_deltas",
34017
+ patterns: [
34018
+ "verascore",
34019
+ "vera score",
34020
+ "trust score",
34021
+ "reputation"
34022
+ ].map(phrasePattern)
34023
+ }
34024
+ ];
34025
+ TRIVIAL_GREETINGS = /* @__PURE__ */ new Set([
34026
+ "hi",
34027
+ "hello",
34028
+ "hey",
34029
+ "yo",
34030
+ "ok",
34031
+ "thanks",
34032
+ "thx",
34033
+ "thank you"
34034
+ ]);
34035
+ CATEGORY_LABELS = {
34036
+ templates: "Templates",
34037
+ agent_state: "Agent state",
34038
+ agent_activity: "Agent activity",
34039
+ audit_log: "Audit log",
34040
+ sentinel_findings: "Sentinel findings",
34041
+ anomaly_alerts: "Anomaly alerts",
34042
+ recent_receipts: "Recent receipts",
34043
+ verascore_deltas: "Verascore deltas"
34044
+ };
34045
+ }
34046
+ });
34047
+ function approxTokenLen2(text) {
33421
34048
  return Math.ceil(text.length / 4);
33422
34049
  }
33423
34050
  function makeEventId(prefix) {
33424
34051
  return `${prefix}-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
33425
34052
  }
34053
+ function classifyFetcherError(error) {
34054
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
34055
+ if (msg.includes("timeout") || msg.includes("timed out")) return "timeout";
34056
+ if (msg.includes("schema") || msg.includes("invalid shape")) {
34057
+ return "schema_mismatch";
34058
+ }
34059
+ if (msg.includes("io") || msg.includes("read") || msg.includes("enoent") || msg.includes("eacces")) {
34060
+ return "io_failed";
34061
+ }
34062
+ return "unknown";
34063
+ }
33426
34064
  function formatPriorTurnLine(turn) {
33427
34065
  const label = turn.role === "user" ? "OPERATOR" : "CONCIERGE";
33428
34066
  return `${label}: ${turn.content}`;
@@ -33430,18 +34068,20 @@ function formatPriorTurnLine(turn) {
33430
34068
  function hashOf(input) {
33431
34069
  return hashToString(sha256.sha256(stringToBytes(input)));
33432
34070
  }
33433
- 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;
34071
+ 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;
33434
34072
  var init_operator_chat_service = __esm({
33435
34073
  "src/chat/operator-chat-service.ts"() {
33436
34074
  init_hashing();
33437
34075
  init_encoding();
33438
34076
  init_operator_chat_audit_events();
33439
34077
  init_operator_chat_types();
34078
+ init_concierge_context_router();
33440
34079
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
33441
34080
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
33442
34081
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
33443
34082
  DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET = 500;
33444
34083
  DEFAULT_CONCIERGE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
34084
+ DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET = 2e3;
33445
34085
  SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
33446
34086
  1. Castle Wall: OS-boundary egress filter enforced at the kernel level. Blocks unauthorized outbound calls even from prompt-injected agents.
33447
34087
  2. Sentinels: internal observation via process introspection. Surfaces anomalies to the operator; does not enforce.
@@ -33483,6 +34123,9 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33483
34123
  historyTokenBudget;
33484
34124
  sessionTtlMs;
33485
34125
  clock;
34126
+ contextFetchers;
34127
+ contextLlmAssist;
34128
+ dynamicContextBudget;
33486
34129
  /**
33487
34130
  * In-memory thread_id assigned to the active concierge session.
33488
34131
  * The first sendConcierge call after construction allocates a fresh
@@ -33514,6 +34157,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33514
34157
  this.historyTokenBudget = deps.conciergeHistoryTokenBudget !== void 0 && deps.conciergeHistoryTokenBudget > 0 ? deps.conciergeHistoryTokenBudget : DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET;
33515
34158
  this.sessionTtlMs = deps.conciergeSessionTtlMs !== void 0 && deps.conciergeSessionTtlMs > 0 ? deps.conciergeSessionTtlMs : DEFAULT_CONCIERGE_SESSION_TTL_MS;
33516
34159
  this.clock = deps.conciergeClock ?? (() => Date.now());
34160
+ if (deps.conciergeContextFetchers) {
34161
+ this.contextFetchers = deps.conciergeContextFetchers;
34162
+ }
34163
+ if (deps.conciergeContextLlmAssist) {
34164
+ this.contextLlmAssist = deps.conciergeContextLlmAssist;
34165
+ }
34166
+ this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
33517
34167
  }
33518
34168
  // ── Concierge ─────────────────────────────────────────────────────────
33519
34169
  /**
@@ -33577,6 +34227,7 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33577
34227
  let servedBy = "disabled";
33578
34228
  let displayLabel = "Concierge: substrate not configured";
33579
34229
  let outcome = "substrate_disabled";
34230
+ let dynamicCategoriesIncluded = [];
33580
34231
  if (!this.substrateSelector) {
33581
34232
  conciergeBody = "Concierge unavailable. The substrate selector is not configured for this fortress. Pick a substrate in the Policy center to enable concierge replies.";
33582
34233
  } else {
@@ -33588,7 +34239,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33588
34239
  conciergeBody = "Concierge unavailable. The chosen substrate does not support summarization. Pick a different substrate in the Policy center.";
33589
34240
  outcome = "substrate_disabled";
33590
34241
  } else {
33591
- const context = await this.assembleConciergeContext(priorTurns);
34242
+ const dynamicResult = await this.runDynamicContextFold(
34243
+ filterResult.filtered
34244
+ );
34245
+ dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34246
+ const context = await this.assembleConciergeContext(
34247
+ priorTurns,
34248
+ dynamicResult.section
34249
+ );
33592
34250
  const response = await this.substrateSelector.invokeSummarize(
33593
34251
  "concierge",
33594
34252
  {
@@ -33652,7 +34310,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33652
34310
  ...assistantTurnId !== void 0 ? { turn_index: assistantTurnId } : {},
33653
34311
  ...this.memory ? {
33654
34312
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
33655
- } : {}
34313
+ } : {},
34314
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {}
33656
34315
  };
33657
34316
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
33658
34317
  return {
@@ -33803,10 +34462,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33803
34462
  * ## Sanctuary reference
33804
34463
  * <static domain reference block>
33805
34464
  *
34465
+ * ## Live fortress context ← WP-V1.3-9 Tau-3, when present
34466
+ * ### <Category>
34467
+ * <fetcher payload>
34468
+ *
33806
34469
  * ## Prior conversation ← WP-V1.3-9 Tau-2, when present
33807
34470
  * OPERATOR: ...
33808
34471
  * CONCIERGE: ...
33809
- * ---
33810
34472
  *
33811
34473
  * ## Recent activity
33812
34474
  * <recentActivity output>
@@ -33825,13 +34487,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33825
34487
  * if available; the v1.2 selector does not expose one, so structured
33826
34488
  * serialization is the canonical path for v1.3.
33827
34489
  */
33828
- async assembleConciergeContext(priorTurns = []) {
34490
+ async assembleConciergeContext(priorTurns = [], dynamicSection = "") {
33829
34491
  const ref = `## Sanctuary reference
33830
34492
  ${SANCTUARY_DOMAIN_REFERENCE}`;
33831
34493
  const priorSection = this.formatPriorTurnsSection(priorTurns);
33832
34494
  if (!this.contextProviders) {
33833
34495
  return [
33834
34496
  ref,
34497
+ ...dynamicSection ? [dynamicSection] : [],
33835
34498
  ...priorSection ? [priorSection] : [],
33836
34499
  "## Recent activity\n(no providers wired)",
33837
34500
  "## Wrapped agents\n(no providers wired)",
@@ -33845,6 +34508,7 @@ ${SANCTUARY_DOMAIN_REFERENCE}`;
33845
34508
  ]);
33846
34509
  return [
33847
34510
  ref,
34511
+ ...dynamicSection ? [dynamicSection] : [],
33848
34512
  ...priorSection ? [priorSection] : [],
33849
34513
  `## Recent activity
33850
34514
  ${activity}`,
@@ -33854,6 +34518,51 @@ ${agents}`,
33854
34518
  ${inbox}`
33855
34519
  ].join("\n\n");
33856
34520
  }
34521
+ /**
34522
+ * Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
34523
+ * trip. Fail-soft on every axis: missing fetchers short-circuit to
34524
+ * an empty fold, fetcher failures emit a per-category audit event
34525
+ * and are omitted from the rendered section, an LLM-assist failure
34526
+ * proceeds with no fold. Returns the rendered section + the list of
34527
+ * categories whose data made it into the section (used for the
34528
+ * round-trip audit emission).
34529
+ */
34530
+ async runDynamicContextFold(query) {
34531
+ if (!this.contextFetchers) {
34532
+ return { section: "", categoriesIncluded: [] };
34533
+ }
34534
+ const result = await foldContext(query, this.contextFetchers, {
34535
+ maxTokens: this.dynamicContextBudget,
34536
+ ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
34537
+ onFetcherFailure: (category, error) => {
34538
+ this.emitContextFetcherFailed(category, classifyFetcherError(error));
34539
+ }
34540
+ });
34541
+ return result;
34542
+ }
34543
+ /**
34544
+ * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
34545
+ * of the fold path so the dynamic-context handler stays readable.
34546
+ * Emits with `result: "failure"` since the named category dropped
34547
+ * from the rendered section for this round-trip.
34548
+ */
34549
+ emitContextFetcherFailed(category, failureReason) {
34550
+ const payload = {
34551
+ version: "1.2",
34552
+ event_id: makeEventId("conc-ctxfail"),
34553
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
34554
+ identity_id: this.identityId,
34555
+ kind: "operator_concierge_context_fetcher_failed",
34556
+ surface: "concierge",
34557
+ category,
34558
+ failure_reason: failureReason
34559
+ };
34560
+ this.emit(
34561
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
34562
+ payload,
34563
+ "failure"
34564
+ );
34565
+ }
33857
34566
  /**
33858
34567
  * Render the prior-conversation section with token-budget enforcement
33859
34568
  * (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
@@ -33864,14 +34573,14 @@ ${inbox}`
33864
34573
  if (turns.length === 0) return "";
33865
34574
  const HEADER = "## Prior conversation";
33866
34575
  const lines = turns.map(formatPriorTurnLine);
33867
- const headerTokens = approxTokenLen(`${HEADER}
34576
+ const headerTokens = approxTokenLen2(`${HEADER}
33868
34577
  `);
33869
- const sepTokens = approxTokenLen("\n");
34578
+ const sepTokens = approxTokenLen2("\n");
33870
34579
  let runningTokens = headerTokens;
33871
34580
  let runningLines = [];
33872
34581
  for (let i = lines.length - 1; i >= 0; i--) {
33873
34582
  const line = lines[i];
33874
- const tokens = approxTokenLen(line) + (runningLines.length > 0 ? sepTokens : 0);
34583
+ const tokens = approxTokenLen2(line) + (runningLines.length > 0 ? sepTokens : 0);
33875
34584
  if (runningTokens + tokens > this.historyTokenBudget) break;
33876
34585
  runningTokens += tokens;
33877
34586
  runningLines.push(line);
@@ -33899,7 +34608,7 @@ ${runningLines.join("\n")}`;
33899
34608
  function chatStorageKey(surface, threadKey) {
33900
34609
  return `${surface}.${threadKey}`;
33901
34610
  }
33902
- var OPERATOR_CHAT_NAMESPACE, HKDF_INFO, OperatorChatStore;
34611
+ var OPERATOR_CHAT_NAMESPACE, HKDF_INFO2, OperatorChatStore;
33903
34612
  var init_operator_chat_store = __esm({
33904
34613
  "src/chat/operator-chat-store.ts"() {
33905
34614
  init_encryption();
@@ -33907,13 +34616,13 @@ var init_operator_chat_store = __esm({
33907
34616
  init_encoding();
33908
34617
  init_operator_chat_types();
33909
34618
  OPERATOR_CHAT_NAMESPACE = "_chat";
33910
- HKDF_INFO = "operator-chat-store-v1";
34619
+ HKDF_INFO2 = "operator-chat-store-v1";
33911
34620
  OperatorChatStore = class {
33912
34621
  storage;
33913
34622
  encryptionKey;
33914
34623
  constructor(storage, masterKey) {
33915
34624
  this.storage = storage;
33916
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO);
34625
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
33917
34626
  }
33918
34627
  /**
33919
34628
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -33998,7 +34707,7 @@ var init_operator_chat_store = __esm({
33998
34707
  function bundleKey(threadId) {
33999
34708
  return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
34000
34709
  }
34001
- function stripKeyPrefix(key) {
34710
+ function stripKeyPrefix2(key) {
34002
34711
  if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
34003
34712
  return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
34004
34713
  }
@@ -34009,7 +34718,7 @@ function lastTurnId(bundle) {
34009
34718
  }
34010
34719
  return max;
34011
34720
  }
34012
- var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO2, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES2, ConciergeMemoryStore;
34721
+ var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO3, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
34013
34722
  var init_concierge_memory_store = __esm({
34014
34723
  "src/chat/concierge-memory-store.ts"() {
34015
34724
  init_encryption();
@@ -34017,9 +34726,9 @@ var init_concierge_memory_store = __esm({
34017
34726
  init_encoding();
34018
34727
  CONCIERGE_MEMORY_NAMESPACE = "_chat";
34019
34728
  CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
34020
- HKDF_INFO2 = "concierge-memory-store-v1";
34729
+ HKDF_INFO3 = "concierge-memory-store-v1";
34021
34730
  DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
34022
- MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
34731
+ MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
34023
34732
  ConciergeMemoryStore = class {
34024
34733
  storage;
34025
34734
  encryptionKey;
@@ -34028,7 +34737,7 @@ var init_concierge_memory_store = __esm({
34028
34737
  locks;
34029
34738
  constructor(opts) {
34030
34739
  this.storage = opts.storage;
34031
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
34740
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
34032
34741
  this.fortressId = opts.fortressId;
34033
34742
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
34034
34743
  this.locks = /* @__PURE__ */ new Map();
@@ -34107,7 +34816,7 @@ var init_concierge_memory_store = __esm({
34107
34816
  return { ok: false, reason: "io_failed" };
34108
34817
  }
34109
34818
  if (!raw) return { ok: true, turns: [] };
34110
- if (raw.length > MAX_BUNDLE_BYTES2) {
34819
+ if (raw.length > MAX_BUNDLE_BYTES3) {
34111
34820
  return { ok: false, reason: "oversize_bundle" };
34112
34821
  }
34113
34822
  let envelope;
@@ -34154,7 +34863,7 @@ var init_concierge_memory_store = __esm({
34154
34863
  );
34155
34864
  const summaries = [];
34156
34865
  for (const meta of entries) {
34157
- const threadId = stripKeyPrefix(meta.key);
34866
+ const threadId = stripKeyPrefix2(meta.key);
34158
34867
  if (threadId === null) continue;
34159
34868
  const bundle = await this.loadBundle(threadId);
34160
34869
  if (!bundle || bundle.turns.length === 0) continue;
@@ -34207,7 +34916,7 @@ var init_concierge_memory_store = __esm({
34207
34916
  );
34208
34917
  let pruned = 0;
34209
34918
  for (const meta of entries) {
34210
- const threadId = stripKeyPrefix(meta.key);
34919
+ const threadId = stripKeyPrefix2(meta.key);
34211
34920
  if (threadId === null) continue;
34212
34921
  pruned += await this.withLock(threadId, async () => {
34213
34922
  const bundle = await this.loadBundle(threadId);
@@ -34238,7 +34947,7 @@ var init_concierge_memory_store = __esm({
34238
34947
  return null;
34239
34948
  }
34240
34949
  if (!raw) return null;
34241
- if (raw.length > MAX_BUNDLE_BYTES2) return null;
34950
+ if (raw.length > MAX_BUNDLE_BYTES3) return null;
34242
34951
  try {
34243
34952
  const envelope = JSON.parse(bytesToString(raw));
34244
34953
  const aad = stringToBytes(threadId);
@@ -34329,7 +35038,18 @@ function buildV11Bindings(inputs) {
34329
35038
  registry
34330
35039
  }),
34331
35040
  conciergePiiFilter: buildConciergePiiFilter(),
34332
- conciergeMemory
35041
+ conciergeMemory,
35042
+ conciergeContextFetchers: buildConciergeContextFetchers({
35043
+ auditLog: inputs.auditLog,
35044
+ identityId: inputs.identityId,
35045
+ registry
35046
+ }),
35047
+ ...inputs.intelligenceSelector ? {
35048
+ conciergeContextLlmAssist: buildConciergeContextLlmAssist({
35049
+ selector: inputs.intelligenceSelector,
35050
+ identityId: inputs.identityId
35051
+ })
35052
+ } : {}
34333
35053
  });
34334
35054
  }
34335
35055
  const hubService = new HubService({
@@ -34390,6 +35110,107 @@ function buildConciergeContextProviders(args) {
34390
35110
  }
34391
35111
  };
34392
35112
  }
35113
+ function buildConciergeContextFetchers(args) {
35114
+ const empty = async () => "";
35115
+ return {
35116
+ templates: async () => {
35117
+ const entries = listTemplates();
35118
+ if (entries.length === 0) return "(no templates installed)";
35119
+ const lines = entries.map((e) => {
35120
+ const m = e.metadata;
35121
+ return `${m.name} (tier ${m.tier}, channel ${m.channel}, target ${m.target_archetype})`;
35122
+ });
35123
+ return lines.join("\n");
35124
+ },
35125
+ agent_state: async (agentNameHint) => {
35126
+ const records = args.registry.list({ identity_id: args.identityId });
35127
+ if (records.length === 0) return "(no wrapped agents)";
35128
+ const filtered = agentNameHint ? records.filter(
35129
+ (r) => r.agent_id.toLowerCase().includes(agentNameHint.toLowerCase()) || r.harness.toLowerCase().includes(agentNameHint.toLowerCase())
35130
+ ) : records;
35131
+ const target = filtered.length > 0 ? filtered : records;
35132
+ const lines = target.slice(0, 20).map((r) => {
35133
+ const tmpl = typeof r.channel_template_id === "string" ? r.channel_template_id : "no_template";
35134
+ return `${r.agent_id} harness=${r.harness} status=${r.status} template=${tmpl}`;
35135
+ });
35136
+ return lines.join("\n");
35137
+ },
35138
+ agent_activity: async (agentNameHint) => {
35139
+ const result = await args.auditLog.query({ limit: 50 });
35140
+ const owned = result.entries.filter(
35141
+ (e) => e.identity_id === args.identityId
35142
+ );
35143
+ const filtered = agentNameHint ? owned.filter((e) => {
35144
+ const agentId = e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : "";
35145
+ return agentId.toLowerCase().includes(agentNameHint.toLowerCase());
35146
+ }) : owned;
35147
+ const tail = (filtered.length > 0 ? filtered : owned).slice(-20);
35148
+ if (tail.length === 0) return "(no activity)";
35149
+ return tail.map((e) => {
35150
+ const agentId = (e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : null) ?? "_fortress";
35151
+ return `${e.timestamp} ${e.layer}.${e.operation} agent=${agentId} result=${e.result}`;
35152
+ }).join("\n");
35153
+ },
35154
+ audit_log: async () => {
35155
+ const result = await args.auditLog.query({ limit: 30 });
35156
+ const owned = result.entries.filter(
35157
+ (e) => e.identity_id === args.identityId
35158
+ );
35159
+ if (owned.length === 0) return "(no audit log entries)";
35160
+ return owned.slice(-30).map(
35161
+ (e) => `${e.timestamp} ${e.layer}.${e.operation} result=${e.result}`
35162
+ ).join("\n");
35163
+ },
35164
+ sentinel_findings: empty,
35165
+ anomaly_alerts: empty,
35166
+ recent_receipts: async () => {
35167
+ const result = await args.auditLog.query({ limit: 100 });
35168
+ const owned = result.entries.filter(
35169
+ (e) => e.identity_id === args.identityId && e.operation.startsWith("composition_")
35170
+ );
35171
+ if (owned.length === 0) return "(no recent composition events)";
35172
+ return owned.slice(-15).map((e) => `${e.timestamp} ${e.operation} result=${e.result}`).join("\n");
35173
+ },
35174
+ verascore_deltas: empty
35175
+ };
35176
+ }
35177
+ function buildConciergeContextLlmAssist(args) {
35178
+ return async (query, categories) => {
35179
+ const labelList = categories.map((c) => `- ${c}`).join("\n");
35180
+ const prompt2 = `You are a router. Classify the operator's query into one of the categories below or "none".
35181
+ Reply with exactly one token: one category name or "none".
35182
+
35183
+ Categories:
35184
+ ${labelList}
35185
+
35186
+ Query: ${query}
35187
+
35188
+ Category:`;
35189
+ try {
35190
+ const handle = await args.selector.getSubstrate("concierge");
35191
+ if (!handle.capability.summarize) return "none";
35192
+ const response = await args.selector.invokeSummarize("concierge", {
35193
+ kind: "summarize",
35194
+ context: prompt2,
35195
+ query: "Output the single category token.",
35196
+ maxTokens: 16
35197
+ });
35198
+ if (response.failureClass || response.body.kind !== "summarize") {
35199
+ return "none";
35200
+ }
35201
+ const raw = response.body.text.trim().toLowerCase();
35202
+ const head = raw.split(/\s|[.,!?:;]/)[0] ?? "";
35203
+ const normalized = head.replace(/[^a-z_]/g, "");
35204
+ const known = categories;
35205
+ if (known.includes(normalized)) {
35206
+ return normalized;
35207
+ }
35208
+ return "none";
35209
+ } catch {
35210
+ return "none";
35211
+ }
35212
+ };
35213
+ }
34393
35214
  function buildConciergePiiFilter() {
34394
35215
  return {
34395
35216
  filter(input) {
@@ -34417,6 +35238,7 @@ var init_wiring = __esm({
34417
35238
  init_agent_registry_persistence();
34418
35239
  init_operator_chat_index();
34419
35240
  init_privacy_filter();
35241
+ init_registry();
34420
35242
  CapabilityErrorAgentController = class {
34421
35243
  fail(action) {
34422
35244
  throw new HubCapabilityError(
@@ -34564,7 +35386,7 @@ var init_defaults = __esm({
34564
35386
  });
34565
35387
 
34566
35388
  // src/intelligence/policy-store.ts
34567
- var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO3, IntelligenceConfigStore;
35389
+ var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO4, IntelligenceConfigStore;
34568
35390
  var init_policy_store = __esm({
34569
35391
  "src/intelligence/policy-store.ts"() {
34570
35392
  init_encryption();
@@ -34573,13 +35395,13 @@ var init_policy_store = __esm({
34573
35395
  init_defaults();
34574
35396
  INTELLIGENCE_NAMESPACE = "_intelligence";
34575
35397
  SUBSTRATE_CONFIG_KEY = "substrate-config";
34576
- HKDF_INFO3 = "intelligence-substrate-config";
35398
+ HKDF_INFO4 = "intelligence-substrate-config";
34577
35399
  IntelligenceConfigStore = class {
34578
35400
  storage;
34579
35401
  encryptionKey;
34580
35402
  constructor(storage, masterKey) {
34581
35403
  this.storage = storage;
34582
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
35404
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
34583
35405
  }
34584
35406
  /**
34585
35407
  * Load the operator's substrate config from disk. Returns the config
@@ -38497,12 +39319,18 @@ ${err.message}
38497
39319
  } : void 0;
38498
39320
  const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
38499
39321
  const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
39322
+ const aggregatorPayloadStore = new AggregatorPayloadStore({
39323
+ storage,
39324
+ masterKey,
39325
+ fortressId: fortressIdForAggregator
39326
+ });
38500
39327
  const approvalAggregator = new ApprovalAggregator({
38501
39328
  storage,
38502
39329
  masterKey,
38503
39330
  auditLog,
38504
39331
  identityId: aggregatorIdentityId,
38505
- fortressId: fortressIdForAggregator
39332
+ fortressId: fortressIdForAggregator,
39333
+ payloadStore: aggregatorPayloadStore
38506
39334
  });
38507
39335
  const wrappedApprovalChannel = new AggregatorBackedChannel({
38508
39336
  underlying: approvalChannel,
@@ -38716,6 +39544,7 @@ var init_src = __esm({
38716
39544
  init_gate();
38717
39545
  init_approval_aggregator();
38718
39546
  init_aggregator_backed_channel();
39547
+ init_aggregator_store();
38719
39548
  init_tools4();
38720
39549
  init_router();
38721
39550
  init_router();