@sanctuary-framework/mcp-server 1.2.5 → 1.2.7

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
@@ -5037,7 +5037,8 @@ var init_constants = __esm({
5037
5037
  RESERVED_EVENT_TYPE_PREFIXES = [
5038
5038
  "EXTENSION_",
5039
5039
  "cross_fortress_",
5040
- "multi_master_"
5040
+ "multi_master_",
5041
+ "cross_harness_approval_"
5041
5042
  ];
5042
5043
  RESERVED_EXTENSION_ENVELOPE_KEYS = [
5043
5044
  "cross_fortress_read_grant",
@@ -9229,9 +9230,9 @@ function fingerprintDID(did) {
9229
9230
  return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
9230
9231
  }
9231
9232
  function countInjectionsToday(audit) {
9232
- const startOfDay = /* @__PURE__ */ new Date();
9233
- startOfDay.setHours(0, 0, 0, 0);
9234
- const cutoff = startOfDay.getTime();
9233
+ const startOfDay2 = /* @__PURE__ */ new Date();
9234
+ startOfDay2.setHours(0, 0, 0, 0);
9235
+ const cutoff = startOfDay2.getTime();
9235
9236
  return audit.filter((e) => {
9236
9237
  const ts = new Date(e.timestamp).getTime();
9237
9238
  if (isNaN(ts) || ts < cutoff) return false;
@@ -9240,9 +9241,9 @@ function countInjectionsToday(audit) {
9240
9241
  }).length;
9241
9242
  }
9242
9243
  function countProofsToday(audit) {
9243
- const startOfDay = /* @__PURE__ */ new Date();
9244
- startOfDay.setHours(0, 0, 0, 0);
9245
- const cutoff = startOfDay.getTime();
9244
+ const startOfDay2 = /* @__PURE__ */ new Date();
9245
+ startOfDay2.setHours(0, 0, 0, 0);
9246
+ const cutoff = startOfDay2.getTime();
9246
9247
  return audit.filter((e) => {
9247
9248
  if (e.layer !== "l3") return false;
9248
9249
  if (!PROOF_CREATION_OPS.has(e.operation)) return false;
@@ -17211,6 +17212,45 @@ async function handleApprovalInboxRoute(deps, req, res) {
17211
17212
  await handleStream2(deps, res);
17212
17213
  return true;
17213
17214
  }
17215
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/revision`) {
17216
+ const revision = await deps.aggregator.getRevision();
17217
+ writeJSON4(res, 200, { ok: true, data: { revision } });
17218
+ return true;
17219
+ }
17220
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/sync`) {
17221
+ const sinceRaw = url.searchParams.get("since_revision");
17222
+ const sinceParsed = sinceRaw === null ? 0 : Number.parseInt(sinceRaw, 10);
17223
+ const sinceRevision = Number.isFinite(sinceParsed) && sinceParsed >= 0 ? sinceParsed : 0;
17224
+ const limit = parseLimit2(
17225
+ url.searchParams.get("limit"),
17226
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17227
+ APPROVAL_INBOX_MAX_LIMIT
17228
+ );
17229
+ const delta = await deps.aggregator.getSync({ sinceRevision, limit });
17230
+ writeJSON4(res, 200, { ok: true, data: delta });
17231
+ return true;
17232
+ }
17233
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17234
+ const limit = parseLimit2(
17235
+ url.searchParams.get("limit"),
17236
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17237
+ APPROVAL_INBOX_MAX_LIMIT
17238
+ );
17239
+ const statusRaw = url.searchParams.get("status");
17240
+ const sinceTs = url.searchParams.get("since") ?? void 0;
17241
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17242
+ const filterStatus = statusRaw && isStatusFilter(statusRaw) && statusRaw !== "pending" ? statusRaw : void 0;
17243
+ const entries = await deps.aggregator.getHistory(
17244
+ {
17245
+ limit,
17246
+ ...filterStatus !== void 0 ? { status: filterStatus } : {},
17247
+ ...sinceTs !== void 0 ? { sinceTs } : {}
17248
+ },
17249
+ operatorId
17250
+ );
17251
+ writeJSON4(res, 200, { ok: true, data: { entries } });
17252
+ return true;
17253
+ }
17214
17254
  if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
17215
17255
  const limit = parseLimit2(
17216
17256
  url.searchParams.get("limit"),
@@ -17233,11 +17273,39 @@ async function handleApprovalInboxRoute(deps, req, res) {
17233
17273
  writeJSON4(res, 404, { ok: false, error: "not_found", path });
17234
17274
  return true;
17235
17275
  }
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
17276
+ if (method === "GET" && entryMatch.action === "audit-trail") {
17277
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17278
+ if (!entry) {
17279
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17280
+ return true;
17281
+ }
17282
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17283
+ const trail = await deps.aggregator.getAuditTrail(
17284
+ entryMatch.aggregatorId,
17285
+ operatorId
17240
17286
  );
17287
+ writeJSON4(res, 200, { ok: true, data: { entry, audit_trail: trail } });
17288
+ return true;
17289
+ }
17290
+ if (method === "GET" && entryMatch.action === "payload") {
17291
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17292
+ if (!entry) {
17293
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17294
+ return true;
17295
+ }
17296
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17297
+ const payload = await deps.aggregator.getFullPayloadWithAudit(
17298
+ entryMatch.aggregatorId,
17299
+ operatorId
17300
+ );
17301
+ writeJSON4(res, 200, {
17302
+ ok: true,
17303
+ data: { entry, request_payload: payload }
17304
+ });
17305
+ return true;
17306
+ }
17307
+ if (method === "GET" && entryMatch.action === null) {
17308
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17241
17309
  if (!entry) {
17242
17310
  writeJSON4(res, 404, { ok: false, error: "not_found" });
17243
17311
  return true;
@@ -20271,7 +20339,10 @@ var init_approval_aggregator = __esm({
20271
20339
  APPROVAL_AGGREGATOR_AUDIT_OPS = {
20272
20340
  AGGREGATED: "cross_harness_approval_aggregated",
20273
20341
  RESOLVED: "cross_harness_approval_resolved",
20274
- DEDUPED: "cross_harness_approval_deduped"
20342
+ DEDUPED: "cross_harness_approval_deduped",
20343
+ PAYLOAD_DECRYPTED: "cross_harness_approval_payload_decrypted",
20344
+ AUDIT_TRAIL_VIEWED: "cross_harness_approval_audit_trail_viewed",
20345
+ REPLAYED: "cross_harness_approval_replayed"
20275
20346
  };
20276
20347
  DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
20277
20348
  DEFAULT_MAX_LIST_LIMIT = 200;
@@ -20287,6 +20358,8 @@ var init_approval_aggregator = __esm({
20287
20358
  now;
20288
20359
  resolveSourceContext;
20289
20360
  resolveHubInboxItemId;
20361
+ payloadStore;
20362
+ resolveEnforcementChain;
20290
20363
  /** Cached entries by `aggregator_id`. */
20291
20364
  entries = /* @__PURE__ */ new Map();
20292
20365
  /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
@@ -20299,6 +20372,20 @@ var init_approval_aggregator = __esm({
20299
20372
  hydrated = false;
20300
20373
  /** Active SSE listeners. */
20301
20374
  listeners = /* @__PURE__ */ new Set();
20375
+ /**
20376
+ * Monotonic revision counter, bumped on every mutation (ingest of new
20377
+ * entry, resolve, expire, delete). Hydrated from max(last_modified_revision)
20378
+ * across persisted entries on first read; in-memory after that. v1.3
20379
+ * Upsilon-4.
20380
+ */
20381
+ currentRevision = 0;
20382
+ /**
20383
+ * Removal tombstones: aggregator_id -> revision at removal. Used by the
20384
+ * sync API to surface "removed" entries to mobile consumers between
20385
+ * polls. In-memory only; server restart clears tombstones (mobile
20386
+ * bootstraps via `list()` on reconnect). v1.3 Upsilon-4.
20387
+ */
20388
+ removedTombstones = /* @__PURE__ */ new Map();
20302
20389
  constructor(deps) {
20303
20390
  this.storage = deps.storage;
20304
20391
  this.encryptionKey = derivePurposeKey(
@@ -20316,6 +20403,14 @@ var init_approval_aggregator = __esm({
20316
20403
  source_agent_id: this.fortressId
20317
20404
  }));
20318
20405
  this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
20406
+ this.payloadStore = deps.payloadStore ?? null;
20407
+ this.resolveEnforcementChain = deps.resolveEnforcementChain ?? ((event) => [
20408
+ {
20409
+ layer: "l2",
20410
+ event: `approval_required:${event.operation}`,
20411
+ timestamp: event.request_timestamp
20412
+ }
20413
+ ]);
20319
20414
  }
20320
20415
  /**
20321
20416
  * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
@@ -20325,6 +20420,113 @@ var init_approval_aggregator = __esm({
20325
20420
  this.listeners.add(listener);
20326
20421
  return () => this.listeners.delete(listener);
20327
20422
  }
20423
+ /**
20424
+ * Current aggregator revision. v1.3 Upsilon-4. Mobile companions
20425
+ * poll the lightweight `/revision` route to detect that something
20426
+ * changed before fetching a full sync delta.
20427
+ */
20428
+ async getRevision() {
20429
+ await this.hydrate();
20430
+ return this.currentRevision;
20431
+ }
20432
+ /**
20433
+ * Compute a delta since `sinceRevision`. v1.3 Upsilon-4. Mobile
20434
+ * clients poll this for cheap state-sync. Behavior:
20435
+ * - `added`: entries whose `created_at_revision > sinceRevision`.
20436
+ * - `changed`: entries that existed at `sinceRevision` but had a
20437
+ * status transition (resolve, expire) since.
20438
+ * - `removed`: aggregator_ids deleted after `sinceRevision`.
20439
+ * - `revision`: current aggregator revision; pass this back as
20440
+ * `sinceRevision` on the next call.
20441
+ *
20442
+ * `limit` caps the total count returned across all three lists,
20443
+ * prioritized as added -> changed -> removed (newer-state first).
20444
+ * When more changes exist than fit, the next call with the returned
20445
+ * revision will pick up the rest because each entry's
20446
+ * last_modified_revision is unchanged by truncation.
20447
+ */
20448
+ async getSync(opts) {
20449
+ await this.hydrate();
20450
+ await this.expireStale();
20451
+ const sinceRevision = opts?.sinceRevision ?? 0;
20452
+ const cap = Math.min(
20453
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20454
+ this.maxListLimit
20455
+ );
20456
+ const added = [];
20457
+ const changed = [];
20458
+ for (const entry of this.entries.values()) {
20459
+ const lastMod = entry.last_modified_revision ?? 0;
20460
+ if (lastMod <= sinceRevision) continue;
20461
+ const createdRev = entry.created_at_revision ?? 0;
20462
+ if (createdRev > sinceRevision) {
20463
+ added.push(entry);
20464
+ } else {
20465
+ changed.push(entry);
20466
+ }
20467
+ }
20468
+ const removed = [];
20469
+ for (const [id, rev] of this.removedTombstones) {
20470
+ if (rev > sinceRevision) removed.push(id);
20471
+ }
20472
+ added.sort(
20473
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20474
+ );
20475
+ changed.sort(
20476
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20477
+ );
20478
+ let remaining = cap;
20479
+ const addedOut = added.slice(0, Math.max(0, remaining));
20480
+ remaining -= addedOut.length;
20481
+ const changedOut = changed.slice(0, Math.max(0, remaining));
20482
+ remaining -= changedOut.length;
20483
+ const removedOut = removed.slice(0, Math.max(0, remaining));
20484
+ return {
20485
+ revision: this.currentRevision,
20486
+ added: addedOut,
20487
+ changed: changedOut,
20488
+ removed: removedOut
20489
+ };
20490
+ }
20491
+ /**
20492
+ * Delete an entry. Drops the in-memory record, the persisted bundle,
20493
+ * and the at-rest payload (if a payload store is wired). Records a
20494
+ * tombstone with the new revision so sync-API consumers see a
20495
+ * `removed` delta. Returns true when an entry was deleted, false on
20496
+ * unknown id. v1.3 Upsilon-4. Reserved for v1.4+ retention housekeeping;
20497
+ * Upsilon-4 ships the surface so mobile sync-API tests can exercise the
20498
+ * removal path.
20499
+ */
20500
+ async deleteEntry(aggregatorId) {
20501
+ await this.hydrate();
20502
+ const entry = this.entries.get(aggregatorId);
20503
+ if (!entry) return false;
20504
+ const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20505
+ this.entries.delete(aggregatorId);
20506
+ this.dedupIndex.delete(dedupKey);
20507
+ this.fullPayloads.delete(aggregatorId);
20508
+ for (const [corr, id] of this.correlationIndex) {
20509
+ if (id === aggregatorId) this.correlationIndex.delete(corr);
20510
+ }
20511
+ try {
20512
+ await this.storage.delete(APPROVAL_AGGREGATOR_NAMESPACE, aggregatorId);
20513
+ } catch {
20514
+ }
20515
+ if (this.payloadStore) {
20516
+ try {
20517
+ await this.payloadStore.deletePayload(aggregatorId);
20518
+ } catch {
20519
+ }
20520
+ }
20521
+ const revision = this.nextRevision();
20522
+ this.removedTombstones.set(aggregatorId, revision);
20523
+ this.emit({ type: "removed", entry: { ...entry } });
20524
+ return true;
20525
+ }
20526
+ nextRevision() {
20527
+ this.currentRevision += 1;
20528
+ return this.currentRevision;
20529
+ }
20328
20530
  /**
20329
20531
  * Ingest a gate event. Returns the aggregator entry on first sight,
20330
20532
  * `null` when deduped. Resolution events update the existing record;
@@ -20366,13 +20568,152 @@ var init_approval_aggregator = __esm({
20366
20568
  }
20367
20569
  /**
20368
20570
  * 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).
20571
+ * `null` when the entry is unknown. When the in-memory payload map has
20572
+ * been evicted (e.g. after a server restart) and a `payloadStore` was
20573
+ * provided, the at-rest bundle is decrypted and the in-memory map is
20574
+ * refilled. Audit emission lives on the `*WithAudit` variant; this base
20575
+ * accessor is silent so internal callers can read without polluting the
20576
+ * audit trail.
20371
20577
  */
20372
20578
  async getFullPayload(aggregatorId) {
20373
20579
  await this.hydrate();
20374
20580
  if (!this.entries.has(aggregatorId)) return null;
20375
- return this.fullPayloads.get(aggregatorId) ?? null;
20581
+ const cached = this.fullPayloads.get(aggregatorId);
20582
+ if (cached !== void 0) return cached;
20583
+ if (this.payloadStore) {
20584
+ try {
20585
+ const restored = await this.payloadStore.loadPayload(aggregatorId);
20586
+ if (restored !== null) {
20587
+ this.fullPayloads.set(aggregatorId, restored);
20588
+ return restored;
20589
+ }
20590
+ } catch {
20591
+ }
20592
+ }
20593
+ return null;
20594
+ }
20595
+ /**
20596
+ * Return the entry record for the given id, or null when unknown.
20597
+ * Idempotent. v1.3 Upsilon-3.
20598
+ */
20599
+ async getEntry(aggregatorId) {
20600
+ await this.hydrate();
20601
+ return this.entries.get(aggregatorId) ?? null;
20602
+ }
20603
+ /**
20604
+ * Audited variant of `getFullPayload`. Emits the
20605
+ * `cross_harness_approval_payload_decrypted` audit event before
20606
+ * returning. Used by the operator-facing /payload replay route.
20607
+ * v1.3 Upsilon-3.
20608
+ */
20609
+ async getFullPayloadWithAudit(aggregatorId, operatorId) {
20610
+ const payload = await this.getFullPayload(aggregatorId);
20611
+ if (payload === null) return null;
20612
+ const entry = this.entries.get(aggregatorId);
20613
+ this.auditLog.append(
20614
+ "l2",
20615
+ APPROVAL_AGGREGATOR_AUDIT_OPS.PAYLOAD_DECRYPTED,
20616
+ operatorId,
20617
+ {
20618
+ aggregator_id: aggregatorId,
20619
+ ...entry ? {
20620
+ source_harness: entry.source_harness,
20621
+ source_agent_id: entry.source_agent_id,
20622
+ entry_status: entry.status
20623
+ } : {}
20624
+ }
20625
+ );
20626
+ return payload;
20627
+ }
20628
+ /**
20629
+ * Return the audit-log entries that led to and surround this approval.
20630
+ * Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
20631
+ * DEDUPED, replay events) all carry `details.aggregator_id` and link
20632
+ * directly. Gate-side emissions (`gate_*:operation`) do not carry the
20633
+ * aggregator id at v1.3, so they are matched via timestamp window
20634
+ * (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
20635
+ * pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
20636
+ * v1.3 Upsilon-3.
20637
+ */
20638
+ async getAuditTrail(aggregatorId, operatorId) {
20639
+ await this.hydrate();
20640
+ const entry = this.entries.get(aggregatorId);
20641
+ if (!entry) {
20642
+ return [];
20643
+ }
20644
+ const sinceMs = Date.parse(entry.created_at) - 1e3;
20645
+ const sinceIso = new Date(sinceMs).toISOString();
20646
+ const queried = await this.auditLog.query({ since: sinceIso, limit: 1e3 });
20647
+ const operationPart = entry.policy_rule_id.includes(":") ? entry.policy_rule_id.slice(entry.policy_rule_id.indexOf(":") + 1) : entry.policy_rule_id;
20648
+ const lifetimeStart = sinceMs;
20649
+ const lifetimeEnd = entry.resolved_at ? Date.parse(entry.resolved_at) + 1e3 : Date.parse(entry.expires_at) + 1e3;
20650
+ const matches = [];
20651
+ for (const audit of queried.entries) {
20652
+ const detailsId = audit.details !== void 0 ? audit.details["aggregator_id"] : void 0;
20653
+ if (detailsId === aggregatorId) {
20654
+ matches.push(audit);
20655
+ continue;
20656
+ }
20657
+ const auditMs = Date.parse(audit.timestamp);
20658
+ if (auditMs < lifetimeStart || auditMs > lifetimeEnd) continue;
20659
+ if (audit.operation.endsWith(`:${operationPart}`)) {
20660
+ matches.push(audit);
20661
+ }
20662
+ }
20663
+ matches.sort(
20664
+ (a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0
20665
+ );
20666
+ this.auditLog.append(
20667
+ "l2",
20668
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AUDIT_TRAIL_VIEWED,
20669
+ operatorId,
20670
+ {
20671
+ aggregator_id: aggregatorId,
20672
+ entry_status: entry.status,
20673
+ match_count: matches.length
20674
+ }
20675
+ );
20676
+ return matches;
20677
+ }
20678
+ /**
20679
+ * List historical (resolved) approvals. Excludes pending entries by
20680
+ * design: `list()` is the pending-inbox surface and `getHistory()` is
20681
+ * the resolved-replay surface. Emits REPLAYED on each call. v1.3
20682
+ * Upsilon-3.
20683
+ */
20684
+ async getHistory(opts, operatorId) {
20685
+ await this.hydrate();
20686
+ await this.expireStale();
20687
+ const limit = Math.min(
20688
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20689
+ this.maxListLimit
20690
+ );
20691
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
20692
+ const matching = [];
20693
+ for (const entry of this.entries.values()) {
20694
+ if (entry.status === "pending") continue;
20695
+ if (opts?.status && entry.status !== opts.status) continue;
20696
+ const stamp = Date.parse(entry.resolved_at ?? entry.created_at);
20697
+ if (stamp < sinceMs) continue;
20698
+ matching.push(entry);
20699
+ }
20700
+ matching.sort((a, b) => {
20701
+ const aStamp = a.resolved_at ?? a.created_at;
20702
+ const bStamp = b.resolved_at ?? b.created_at;
20703
+ return bStamp.localeCompare(aStamp);
20704
+ });
20705
+ const sliced = matching.slice(0, limit);
20706
+ this.auditLog.append(
20707
+ "l2",
20708
+ APPROVAL_AGGREGATOR_AUDIT_OPS.REPLAYED,
20709
+ operatorId,
20710
+ {
20711
+ result_count: sliced.length,
20712
+ ...opts?.status !== void 0 ? { status_filter: opts.status } : {},
20713
+ ...opts?.sinceTs !== void 0 ? { since: opts.sinceTs } : {}
20714
+ }
20715
+ );
20716
+ return sliced;
20376
20717
  }
20377
20718
  /**
20378
20719
  * Resolve an entry. Used by both:
@@ -20396,6 +20737,7 @@ var init_approval_aggregator = __esm({
20396
20737
  entry.status = decision;
20397
20738
  entry.resolved_at = this.now().toISOString();
20398
20739
  entry.resolved_by = operatorId;
20740
+ entry.last_modified_revision = this.nextRevision();
20399
20741
  await this.persist(entry);
20400
20742
  this.auditLog.append(
20401
20743
  "l2",
@@ -20446,6 +20788,8 @@ var init_approval_aggregator = __esm({
20446
20788
  const now = this.now();
20447
20789
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20448
20790
  const hubInboxId = this.resolveHubInboxItemId(event);
20791
+ const enforcementChain = this.resolveEnforcementChain(event);
20792
+ const revision = this.nextRevision();
20449
20793
  const entry = {
20450
20794
  aggregator_id: id,
20451
20795
  source_harness: ctx.source_harness,
@@ -20457,13 +20801,22 @@ var init_approval_aggregator = __esm({
20457
20801
  status: "pending",
20458
20802
  created_at: now.toISOString(),
20459
20803
  expires_at: expires.toISOString(),
20460
- ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
20804
+ created_at_revision: revision,
20805
+ last_modified_revision: revision,
20806
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20807
+ ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20461
20808
  };
20462
20809
  this.entries.set(id, entry);
20463
20810
  this.dedupIndex.set(dedupKey, id);
20464
20811
  this.correlationIndex.set(event.correlation_id, id);
20465
20812
  this.fullPayloads.set(id, event.context);
20466
20813
  await this.persist(entry);
20814
+ if (this.payloadStore) {
20815
+ try {
20816
+ await this.payloadStore.savePayload(id, event.context);
20817
+ } catch {
20818
+ }
20819
+ }
20467
20820
  this.auditLog.append(
20468
20821
  "l2",
20469
20822
  APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
@@ -20492,6 +20845,7 @@ var init_approval_aggregator = __esm({
20492
20845
  entry.status = status;
20493
20846
  entry.resolved_at = event.resolution.decided_at;
20494
20847
  entry.resolved_by = event.resolution.decided_by;
20848
+ entry.last_modified_revision = this.nextRevision();
20495
20849
  await this.persist(entry);
20496
20850
  this.auditLog.append(
20497
20851
  "l2",
@@ -20554,6 +20908,7 @@ var init_approval_aggregator = __esm({
20554
20908
  entry.status = "expired";
20555
20909
  entry.resolved_at = this.now().toISOString();
20556
20910
  entry.resolved_by = "system_ttl";
20911
+ entry.last_modified_revision = this.nextRevision();
20557
20912
  await this.persist(entry);
20558
20913
  this.auditLog.append(
20559
20914
  "l2",
@@ -20600,6 +20955,10 @@ var init_approval_aggregator = __esm({
20600
20955
  this.entries.set(entry.aggregator_id, entry);
20601
20956
  const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20602
20957
  this.dedupIndex.set(dedupKey, entry.aggregator_id);
20958
+ const lastMod = entry.last_modified_revision ?? 0;
20959
+ if (lastMod > this.currentRevision) {
20960
+ this.currentRevision = lastMod;
20961
+ }
20603
20962
  } catch {
20604
20963
  }
20605
20964
  }
@@ -20779,6 +21138,149 @@ var init_aggregator_backed_channel = __esm({
20779
21138
  }
20780
21139
  });
20781
21140
 
21141
+ // src/principal-policy/aggregator-store.ts
21142
+ function payloadKey(aggregatorId) {
21143
+ return `${AGGREGATOR_PAYLOAD_KEY_PREFIX}${aggregatorId}`;
21144
+ }
21145
+ function stripKeyPrefix(key) {
21146
+ if (!key.startsWith(AGGREGATOR_PAYLOAD_KEY_PREFIX)) return null;
21147
+ return key.slice(AGGREGATOR_PAYLOAD_KEY_PREFIX.length);
21148
+ }
21149
+ var AGGREGATOR_PAYLOAD_NAMESPACE, AGGREGATOR_PAYLOAD_KEY_PREFIX, HKDF_INFO, DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS, MAX_BUNDLE_BYTES2, AggregatorPayloadStore;
21150
+ var init_aggregator_store = __esm({
21151
+ "src/principal-policy/aggregator-store.ts"() {
21152
+ init_encryption();
21153
+ init_key_derivation();
21154
+ init_encoding();
21155
+ AGGREGATOR_PAYLOAD_NAMESPACE = "_approval_aggregator_payloads";
21156
+ AGGREGATOR_PAYLOAD_KEY_PREFIX = "payload.";
21157
+ HKDF_INFO = "l2-approval-aggregator-payload-v1";
21158
+ DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS = 30;
21159
+ MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
21160
+ AggregatorPayloadStore = class {
21161
+ storage;
21162
+ encryptionKey;
21163
+ fortressId;
21164
+ retentionDays;
21165
+ constructor(opts) {
21166
+ this.storage = opts.storage;
21167
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO);
21168
+ this.fortressId = opts.fortressId;
21169
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS;
21170
+ }
21171
+ /**
21172
+ * Persist `payload` under the given aggregator_id. Idempotent; calling
21173
+ * twice with the same id rewrites the bundle (retention_until is
21174
+ * recomputed). Returns the bundle's retention_until ISO-8601 timestamp
21175
+ * so callers can log it.
21176
+ */
21177
+ async savePayload(aggregatorId, payload) {
21178
+ const now = /* @__PURE__ */ new Date();
21179
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
21180
+ const retentionUntil = new Date(now.getTime() + retentionMs);
21181
+ const bundle = {
21182
+ version: 1,
21183
+ aggregator_id: aggregatorId,
21184
+ fortress_id: this.fortressId,
21185
+ created_at: now.toISOString(),
21186
+ retention_until: retentionUntil.toISOString(),
21187
+ payload
21188
+ };
21189
+ const aad = stringToBytes(aggregatorId);
21190
+ const plaintext = stringToBytes(JSON.stringify(bundle));
21191
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
21192
+ await this.storage.write(
21193
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21194
+ payloadKey(aggregatorId),
21195
+ stringToBytes(JSON.stringify(envelope))
21196
+ );
21197
+ return bundle.retention_until;
21198
+ }
21199
+ /**
21200
+ * Read the persisted payload for the aggregator_id. Returns null if no
21201
+ * bundle exists, the bundle is corrupted, or AAD binding fails.
21202
+ */
21203
+ async loadPayload(aggregatorId) {
21204
+ const key = payloadKey(aggregatorId);
21205
+ let raw;
21206
+ try {
21207
+ raw = await this.storage.read(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21208
+ } catch {
21209
+ return null;
21210
+ }
21211
+ if (!raw) return null;
21212
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
21213
+ try {
21214
+ const envelope = JSON.parse(bytesToString(raw));
21215
+ const aad = stringToBytes(aggregatorId);
21216
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21217
+ const parsed = JSON.parse(
21218
+ bytesToString(plaintext)
21219
+ );
21220
+ if (parsed.version !== 1) return null;
21221
+ if (parsed.aggregator_id !== aggregatorId) return null;
21222
+ return parsed.payload;
21223
+ } catch {
21224
+ return null;
21225
+ }
21226
+ }
21227
+ /**
21228
+ * Delete the persisted payload. Returns true when a bundle was removed,
21229
+ * false when none existed.
21230
+ */
21231
+ async deletePayload(aggregatorId) {
21232
+ const key = payloadKey(aggregatorId);
21233
+ const existed = await this.storage.exists(
21234
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21235
+ key
21236
+ );
21237
+ if (!existed) return false;
21238
+ try {
21239
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21240
+ } catch {
21241
+ return false;
21242
+ }
21243
+ return true;
21244
+ }
21245
+ /**
21246
+ * Drop expired payload bundles. Returns the count of bundles pruned.
21247
+ * Caller wires this into the cocoon-unlock initialization path.
21248
+ */
21249
+ async pruneExpired(now) {
21250
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
21251
+ const entries = await this.storage.list(
21252
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21253
+ AGGREGATOR_PAYLOAD_KEY_PREFIX
21254
+ );
21255
+ let pruned = 0;
21256
+ for (const meta of entries) {
21257
+ const aggregatorId = stripKeyPrefix(meta.key);
21258
+ if (aggregatorId === null) continue;
21259
+ const raw = await this.storage.read(
21260
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21261
+ meta.key
21262
+ );
21263
+ if (!raw) continue;
21264
+ try {
21265
+ const envelope = JSON.parse(bytesToString(raw));
21266
+ const aad = stringToBytes(aggregatorId);
21267
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21268
+ const parsed = JSON.parse(
21269
+ bytesToString(plaintext)
21270
+ );
21271
+ if (parsed.retention_until <= cutoff) {
21272
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, meta.key);
21273
+ pruned += 1;
21274
+ }
21275
+ } catch {
21276
+ }
21277
+ }
21278
+ return { pruned };
21279
+ }
21280
+ };
21281
+ }
21282
+ });
21283
+
20782
21284
  // src/principal-policy/tools.ts
20783
21285
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
20784
21286
  return [
@@ -33397,7 +33899,14 @@ var init_operator_chat_audit_events = __esm({
33397
33899
  * turns; the concierge degrades to single-turn after emitting. Body
33398
33900
  * carries thread_id + a stable failure_reason enum.
33399
33901
  */
33400
- CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed"
33902
+ CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed",
33903
+ /**
33904
+ * Concierge dynamic-context fetcher failed (WP-V1.3-9 Tau-3). Emitted
33905
+ * when a category fetcher throws while assembling the dynamic context
33906
+ * fold. The concierge omits that category and continues; the user-
33907
+ * facing query is never broken. Body carries category + failure_reason.
33908
+ */
33909
+ CONCIERGE_CONTEXT_FETCHER_FAILED: "operator_concierge_context_fetcher_failed"
33401
33910
  };
33402
33911
  }
33403
33912
  });
@@ -33410,12 +33919,844 @@ var init_operator_chat_types = __esm({
33410
33919
  CONCIERGE_THREAD_KEY = "_fortress";
33411
33920
  }
33412
33921
  });
33922
+
33923
+ // src/chat/concierge-context-router.ts
33924
+ function phrasePattern(phrase) {
33925
+ const escaped = phrase.toLowerCase().split(/\s+/).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
33926
+ return { source: `\\b${escaped}\\b`, phrase };
33927
+ }
33928
+ function extractAgentNameHint(query) {
33929
+ const agentPattern = /\bagent\s+["']?([A-Za-z][\w-]{0,40})["']?/i;
33930
+ const m = query.match(agentPattern);
33931
+ if (m && m[1]) return m[1];
33932
+ const quoted = query.match(/["']([A-Za-z][\w-]{0,40})["']/);
33933
+ if (quoted && quoted[1]) return quoted[1];
33934
+ return null;
33935
+ }
33936
+ function isTrivialQuery(query) {
33937
+ const norm = query.trim().toLowerCase();
33938
+ if (norm.length === 0) return true;
33939
+ if (norm.length < 8) return true;
33940
+ return TRIVIAL_GREETINGS.has(norm);
33941
+ }
33942
+ function classifyQuery(query, parsedGrammar) {
33943
+ const normalized = query.toLowerCase();
33944
+ const matches = [];
33945
+ const grammarAgent = parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null;
33946
+ for (const spec of CATEGORY_KEYWORDS) {
33947
+ const matchedPhrases = [];
33948
+ for (const pattern of spec.patterns) {
33949
+ if (matchedPhrases.includes(pattern.phrase)) continue;
33950
+ const re = new RegExp(pattern.source, "i");
33951
+ if (re.test(normalized)) {
33952
+ matchedPhrases.push(pattern.phrase);
33953
+ }
33954
+ }
33955
+ if (matchedPhrases.length === 0) continue;
33956
+ const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
33957
+ const wantsAgentHint = spec.category === "agent_state" || spec.category === "agent_activity";
33958
+ const agent_name_hint = wantsAgentHint ? grammarAgent ?? extractAgentNameHint(query) : null;
33959
+ matches.push({
33960
+ category: spec.category,
33961
+ confidence,
33962
+ matched_keywords: matchedPhrases,
33963
+ agent_name_hint,
33964
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33965
+ });
33966
+ }
33967
+ matches.sort((a, b) => {
33968
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
33969
+ return CONTEXT_CATEGORIES.indexOf(a.category) - CONTEXT_CATEGORIES.indexOf(b.category);
33970
+ });
33971
+ return matches;
33972
+ }
33973
+ function fetcherHintsFromGrammar(parsed) {
33974
+ if (!parsed) return void 0;
33975
+ const hasTime = parsed.time_range !== null;
33976
+ const hasAgents = parsed.agent_names.length > 0;
33977
+ const hasEvents = parsed.event_types.length > 0;
33978
+ if (!hasTime && !hasAgents && !hasEvents) return void 0;
33979
+ const hints = {};
33980
+ if (parsed.time_range) {
33981
+ const range = parsed.time_range;
33982
+ hints.time_range = {
33983
+ start: range.start,
33984
+ end: range.end,
33985
+ ...range.relative_label !== void 0 ? { relative_label: range.relative_label } : {}
33986
+ };
33987
+ }
33988
+ if (hasAgents) hints.agent_names = parsed.agent_names;
33989
+ if (hasEvents) hints.event_types = parsed.event_types;
33990
+ return hints;
33991
+ }
33413
33992
  function approxTokenLen(text) {
33993
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
33994
+ }
33995
+ async function runFetcher(match, fetchers, hints) {
33996
+ switch (match.category) {
33997
+ case "templates":
33998
+ return fetchers.templates(hints);
33999
+ case "agent_state":
34000
+ return fetchers.agent_state(match.agent_name_hint, hints);
34001
+ case "agent_activity":
34002
+ return fetchers.agent_activity(match.agent_name_hint, hints);
34003
+ case "audit_log":
34004
+ return fetchers.audit_log(hints);
34005
+ case "sentinel_findings":
34006
+ return fetchers.sentinel_findings(hints);
34007
+ case "anomaly_alerts":
34008
+ return fetchers.anomaly_alerts(hints);
34009
+ case "recent_receipts":
34010
+ return fetchers.recent_receipts(hints);
34011
+ case "verascore_deltas":
34012
+ return fetchers.verascore_deltas(hints);
34013
+ }
34014
+ }
34015
+ function trivialMatch(category, parsedGrammar) {
34016
+ return {
34017
+ category,
34018
+ confidence: 0.5,
34019
+ matched_keywords: ["llm-assist"],
34020
+ agent_name_hint: parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null,
34021
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
34022
+ };
34023
+ }
34024
+ async function foldContext(query, fetchers, opts) {
34025
+ const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
34026
+ const parsed = opts?.parsed ?? null;
34027
+ const hints = fetcherHintsFromGrammar(parsed);
34028
+ let matches = classifyQuery(query, parsed);
34029
+ if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
34030
+ try {
34031
+ const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
34032
+ if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
34033
+ matches = [trivialMatch(picked, parsed)];
34034
+ }
34035
+ } catch {
34036
+ }
34037
+ }
34038
+ if (matches.length === 0) {
34039
+ return { section: "", categoriesIncluded: [] };
34040
+ }
34041
+ const attempts = [];
34042
+ for (const match of matches) {
34043
+ try {
34044
+ const text = await runFetcher(match, fetchers, hints);
34045
+ const trimmed = text.trim();
34046
+ if (trimmed.length > 0) {
34047
+ attempts.push({ category: match.category, text: trimmed });
34048
+ }
34049
+ } catch (err) {
34050
+ opts?.onFetcherFailure?.(match.category, err);
34051
+ }
34052
+ }
34053
+ if (attempts.length === 0) {
34054
+ return { section: "", categoriesIncluded: [] };
34055
+ }
34056
+ const headerTokens = approxTokenLen(`${DYNAMIC_CONTEXT_SECTION_HEADER}
34057
+ `);
34058
+ const sepTokens = approxTokenLen("\n\n");
34059
+ let runningTokens = headerTokens;
34060
+ const kept = [];
34061
+ for (const attempt of attempts) {
34062
+ const block = `### ${CATEGORY_LABELS[attempt.category]}
34063
+ ${attempt.text}`;
34064
+ const tokens = approxTokenLen(block) + (kept.length > 0 ? sepTokens : 0);
34065
+ if (kept.length === 0) {
34066
+ kept.push(attempt);
34067
+ runningTokens += tokens;
34068
+ continue;
34069
+ }
34070
+ if (runningTokens + tokens > budget) break;
34071
+ kept.push(attempt);
34072
+ runningTokens += tokens;
34073
+ }
34074
+ const blocks = kept.map(
34075
+ (k) => `### ${CATEGORY_LABELS[k.category]}
34076
+ ${k.text}`
34077
+ );
34078
+ const section = `${DYNAMIC_CONTEXT_SECTION_HEADER}
34079
+ ${blocks.join("\n\n")}`;
34080
+ return {
34081
+ section,
34082
+ categoriesIncluded: kept.map((k) => k.category)
34083
+ };
34084
+ }
34085
+ var APPROX_CHARS_PER_TOKEN, DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET, DYNAMIC_CONTEXT_SECTION_HEADER, CONTEXT_CATEGORIES, CATEGORY_KEYWORDS, TRIVIAL_GREETINGS, CATEGORY_LABELS;
34086
+ var init_concierge_context_router = __esm({
34087
+ "src/chat/concierge-context-router.ts"() {
34088
+ APPROX_CHARS_PER_TOKEN = 4;
34089
+ DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET = 2e3;
34090
+ DYNAMIC_CONTEXT_SECTION_HEADER = "## Live fortress context";
34091
+ CONTEXT_CATEGORIES = [
34092
+ "templates",
34093
+ "agent_state",
34094
+ "agent_activity",
34095
+ "audit_log",
34096
+ "sentinel_findings",
34097
+ "anomaly_alerts",
34098
+ "recent_receipts",
34099
+ "verascore_deltas"
34100
+ ];
34101
+ CATEGORY_KEYWORDS = [
34102
+ {
34103
+ category: "templates",
34104
+ patterns: [
34105
+ "templates",
34106
+ "template",
34107
+ "channel templates",
34108
+ "channel template",
34109
+ "list templates",
34110
+ "available templates",
34111
+ "what templates"
34112
+ ].map(phrasePattern)
34113
+ },
34114
+ {
34115
+ category: "agent_state",
34116
+ patterns: [
34117
+ "state",
34118
+ "status",
34119
+ "agent state",
34120
+ "agent status",
34121
+ "status of agent",
34122
+ "status of agents",
34123
+ "state of",
34124
+ "doing"
34125
+ ].map(phrasePattern)
34126
+ },
34127
+ {
34128
+ category: "agent_activity",
34129
+ patterns: [
34130
+ "activity",
34131
+ "agent activity",
34132
+ "what did",
34133
+ "recent activity"
34134
+ ].map(phrasePattern)
34135
+ },
34136
+ {
34137
+ category: "audit_log",
34138
+ patterns: [
34139
+ "audit log",
34140
+ "audit",
34141
+ "log entry",
34142
+ "log entries",
34143
+ "what happened",
34144
+ "show me events",
34145
+ "event class"
34146
+ ].map(phrasePattern)
34147
+ },
34148
+ {
34149
+ category: "sentinel_findings",
34150
+ patterns: [
34151
+ "sentinel",
34152
+ "sentinels",
34153
+ "warning",
34154
+ "warnings",
34155
+ "alert",
34156
+ "alerts",
34157
+ "whats wrong",
34158
+ "what's wrong",
34159
+ "findings"
34160
+ ].map(phrasePattern)
34161
+ },
34162
+ {
34163
+ category: "anomaly_alerts",
34164
+ patterns: [
34165
+ "anomaly",
34166
+ "anomalies",
34167
+ "spike",
34168
+ "unusual",
34169
+ "outlier"
34170
+ ].map(phrasePattern)
34171
+ },
34172
+ {
34173
+ category: "recent_receipts",
34174
+ patterns: [
34175
+ "receipt",
34176
+ "receipts",
34177
+ "concordia",
34178
+ "commitment",
34179
+ "commitments",
34180
+ "chain",
34181
+ "chains"
34182
+ ].map(phrasePattern)
34183
+ },
34184
+ {
34185
+ category: "verascore_deltas",
34186
+ patterns: [
34187
+ "verascore",
34188
+ "vera score",
34189
+ "trust score",
34190
+ "reputation"
34191
+ ].map(phrasePattern)
34192
+ }
34193
+ ];
34194
+ TRIVIAL_GREETINGS = /* @__PURE__ */ new Set([
34195
+ "hi",
34196
+ "hello",
34197
+ "hey",
34198
+ "yo",
34199
+ "ok",
34200
+ "thanks",
34201
+ "thx",
34202
+ "thank you"
34203
+ ]);
34204
+ CATEGORY_LABELS = {
34205
+ templates: "Templates",
34206
+ agent_state: "Agent state",
34207
+ agent_activity: "Agent activity",
34208
+ audit_log: "Audit log",
34209
+ sentinel_findings: "Sentinel findings",
34210
+ anomaly_alerts: "Anomaly alerts",
34211
+ recent_receipts: "Recent receipts",
34212
+ verascore_deltas: "Verascore deltas"
34213
+ };
34214
+ }
34215
+ });
34216
+
34217
+ // src/composition/constants.ts
34218
+ var COMPOSITION_EVENT_TYPES;
34219
+ var init_constants4 = __esm({
34220
+ "src/composition/constants.ts"() {
34221
+ init_constants();
34222
+ COMPOSITION_EVENT_TYPES = [
34223
+ "composition_receipt_packed",
34224
+ "composition_receipt_verified",
34225
+ "composition_mandate_verified",
34226
+ "composition_verascore_published",
34227
+ "composition_sidecar_spawned",
34228
+ "composition_sidecar_crashed",
34229
+ "composition_sidecar_recovered",
34230
+ "composition_degraded",
34231
+ "composition_recovered"
34232
+ ];
34233
+ }
34234
+ });
34235
+
34236
+ // src/chat/concierge-query-grammar.ts
34237
+ function resolveTimeRange(query, now) {
34238
+ const normalized = query.trim();
34239
+ const lower = normalized.toLowerCase();
34240
+ const fromTo = lower.match(
34241
+ /\b(?:from|between)\s+(.+?)\s+(?:to|and|-|until)\s+([\w:.\-+t /]+)/i
34242
+ );
34243
+ if (fromTo) {
34244
+ const aSlice = fromTo[1];
34245
+ const bSlice = fromTo[2];
34246
+ if (aSlice !== void 0 && bSlice !== void 0) {
34247
+ const a = parseInstant(aSlice, now);
34248
+ const b = parseInstant(bSlice, now);
34249
+ if (a && b) {
34250
+ const start = a.getTime() <= b.getTime() ? a : b;
34251
+ const end = a.getTime() <= b.getTime() ? b : a;
34252
+ return {
34253
+ range: { start, end },
34254
+ matchedSubstring: fromTo[0]
34255
+ };
34256
+ }
34257
+ }
34258
+ }
34259
+ const sinceMatch = lower.match(/\bsince\s+([\w:.\-+t /]+)/i);
34260
+ if (sinceMatch) {
34261
+ const slice = sinceMatch[1];
34262
+ if (slice !== void 0) {
34263
+ const start = parseInstant(slice, now);
34264
+ if (start) {
34265
+ return {
34266
+ range: { start, end: now },
34267
+ matchedSubstring: sinceMatch[0]
34268
+ };
34269
+ }
34270
+ }
34271
+ }
34272
+ if (/\byesterday\b/.test(lower)) {
34273
+ const startOfToday = startOfDay(now);
34274
+ const start = new Date(startOfToday.getTime() - MS_PER_DAY);
34275
+ const end = new Date(startOfToday.getTime() - 1);
34276
+ return {
34277
+ range: { start, end, relative_label: "yesterday" },
34278
+ matchedSubstring: "yesterday"
34279
+ };
34280
+ }
34281
+ if (/\btoday\b/.test(lower)) {
34282
+ return {
34283
+ range: {
34284
+ start: startOfDay(now),
34285
+ end: now,
34286
+ relative_label: "today"
34287
+ },
34288
+ matchedSubstring: "today"
34289
+ };
34290
+ }
34291
+ const compactHours = lower.match(/\blast\s+(\d+)\s*h\b/i);
34292
+ if (compactHours) {
34293
+ const tok = compactHours[1];
34294
+ if (tok !== void 0) {
34295
+ const n = Number.parseInt(tok, 10);
34296
+ if (Number.isFinite(n) && n > 0) {
34297
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34298
+ return {
34299
+ range: { start, end: now, relative_label: `last ${n}h` },
34300
+ matchedSubstring: compactHours[0]
34301
+ };
34302
+ }
34303
+ }
34304
+ }
34305
+ const hoursMatch = lower.match(
34306
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:hr\b|hrs\b|hour|hours)/i
34307
+ );
34308
+ if (hoursMatch) {
34309
+ const tok = hoursMatch[1];
34310
+ if (tok !== void 0) {
34311
+ const n = parseCount(tok);
34312
+ if (n !== null && n > 0) {
34313
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34314
+ return {
34315
+ range: { start, end: now, relative_label: `past ${n} hour${n === 1 ? "" : "s"}` },
34316
+ matchedSubstring: hoursMatch[0]
34317
+ };
34318
+ }
34319
+ }
34320
+ }
34321
+ if (/\b(?:past|last)\s+hour\b/.test(lower)) {
34322
+ const start = new Date(now.getTime() - MS_PER_HOUR);
34323
+ return {
34324
+ range: { start, end: now, relative_label: "past hour" },
34325
+ matchedSubstring: lower.match(/\b(?:past|last)\s+hour\b/i)[0]
34326
+ };
34327
+ }
34328
+ const daysMatch = lower.match(
34329
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:d\b|day|days)/i
34330
+ );
34331
+ if (daysMatch) {
34332
+ const tok = daysMatch[1];
34333
+ if (tok !== void 0) {
34334
+ const n = parseCount(tok);
34335
+ if (n !== null && n > 0) {
34336
+ const start = new Date(now.getTime() - n * MS_PER_DAY);
34337
+ return {
34338
+ range: { start, end: now, relative_label: `past ${n} day${n === 1 ? "" : "s"}` },
34339
+ matchedSubstring: daysMatch[0]
34340
+ };
34341
+ }
34342
+ }
34343
+ }
34344
+ if (/\b(?:past|last)\s+day\b/.test(lower)) {
34345
+ const start = new Date(now.getTime() - MS_PER_DAY);
34346
+ return {
34347
+ range: { start, end: now, relative_label: "past day" },
34348
+ matchedSubstring: lower.match(/\b(?:past|last)\s+day\b/i)[0]
34349
+ };
34350
+ }
34351
+ if (/\bthis\s+week\b/.test(lower)) {
34352
+ const start = startOfWeek(now);
34353
+ return {
34354
+ range: { start, end: now, relative_label: "this week" },
34355
+ matchedSubstring: lower.match(/\bthis\s+week\b/i)[0]
34356
+ };
34357
+ }
34358
+ if (/\b(?:past|last)\s+week\b/.test(lower)) {
34359
+ const start = new Date(now.getTime() - 7 * MS_PER_DAY);
34360
+ return {
34361
+ range: { start, end: now, relative_label: "past week" },
34362
+ matchedSubstring: lower.match(/\b(?:past|last)\s+week\b/i)[0]
34363
+ };
34364
+ }
34365
+ const isoMatch = normalized.match(
34366
+ /\b(\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:\d{2})?)?)\b/
34367
+ );
34368
+ if (isoMatch) {
34369
+ const tok = isoMatch[1];
34370
+ if (tok !== void 0) {
34371
+ const parsed = parseInstant(tok, now);
34372
+ if (parsed) {
34373
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(tok);
34374
+ if (isDateOnly) {
34375
+ return {
34376
+ range: {
34377
+ start: parsed,
34378
+ end: new Date(parsed.getTime() + MS_PER_DAY - 1)
34379
+ },
34380
+ matchedSubstring: tok
34381
+ };
34382
+ }
34383
+ return {
34384
+ range: {
34385
+ start: new Date(parsed.getTime() - 30 * 60 * 1e3),
34386
+ end: new Date(parsed.getTime() + 30 * 60 * 1e3)
34387
+ },
34388
+ matchedSubstring: tok
34389
+ };
34390
+ }
34391
+ }
34392
+ }
34393
+ return null;
34394
+ }
34395
+ function parseInstant(token, now) {
34396
+ const trimmed = token.trim().replace(/[,.!?;]+$/g, "");
34397
+ if (!trimmed) return null;
34398
+ const lower = trimmed.toLowerCase();
34399
+ if (lower === "now") return now;
34400
+ if (lower === "today") return startOfDay(now);
34401
+ if (lower === "yesterday") {
34402
+ return new Date(startOfDay(now).getTime() - MS_PER_DAY);
34403
+ }
34404
+ const isoLike = trimmed.replace(" ", "T");
34405
+ const parsed = new Date(isoLike);
34406
+ if (!Number.isNaN(parsed.getTime())) return parsed;
34407
+ return null;
34408
+ }
34409
+ function parseCount(token) {
34410
+ const lower = token.toLowerCase();
34411
+ if (/^\d+$/.test(lower)) {
34412
+ const n = Number.parseInt(lower, 10);
34413
+ return Number.isFinite(n) ? n : null;
34414
+ }
34415
+ return NUMBER_WORDS[lower] ?? null;
34416
+ }
34417
+ function startOfDay(d) {
34418
+ const out = new Date(d);
34419
+ out.setHours(0, 0, 0, 0);
34420
+ return out;
34421
+ }
34422
+ function startOfWeek(d) {
34423
+ const out = startOfDay(d);
34424
+ const dayOfWeek = out.getDay();
34425
+ const offsetToMonday = (dayOfWeek + 6) % 7;
34426
+ out.setDate(out.getDate() - offsetToMonday);
34427
+ return out;
34428
+ }
34429
+ function listFromRegistry(registry) {
34430
+ if (!registry) return [];
34431
+ if (Array.isArray(registry)) return registry;
34432
+ if (typeof registry.list === "function") {
34433
+ return registry.list();
34434
+ }
34435
+ return [];
34436
+ }
34437
+ function extractAgentNames(query, registry) {
34438
+ const records = listFromRegistry(registry);
34439
+ if (records.length === 0) return { matched: [], flagged: false };
34440
+ const lowerQuery = query.toLowerCase();
34441
+ const compactQuery = lowerQuery.replace(/[\s_-]+/g, "");
34442
+ const matched = [];
34443
+ const seen = /* @__PURE__ */ new Set();
34444
+ for (const rec of records) {
34445
+ const id = rec.agent_id;
34446
+ if (!id || seen.has(id)) continue;
34447
+ const idLower = id.toLowerCase();
34448
+ if (idLower.length < 3) continue;
34449
+ const idCompact = idLower.replace(/[\s_-]+/g, "");
34450
+ const wordRe = new RegExp(
34451
+ `\\b${idLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
34452
+ "i"
34453
+ );
34454
+ if (wordRe.test(query)) {
34455
+ matched.push(id);
34456
+ seen.add(id);
34457
+ continue;
34458
+ }
34459
+ if (idCompact.length >= 4 && compactQuery.includes(idCompact)) {
34460
+ matched.push(id);
34461
+ seen.add(id);
34462
+ }
34463
+ }
34464
+ const agentMention = lowerQuery.match(/\bagent\s+([a-z0-9_-]{3,40})/i);
34465
+ const flagged = matched.length === 0 && agentMention !== null && agentMention[1] !== void 0 && !records.some((r) => r.agent_id.toLowerCase() === agentMention[1]?.toLowerCase());
34466
+ return { matched, flagged };
34467
+ }
34468
+ function escapeRegex(s) {
34469
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34470
+ }
34471
+ function extractEventTypes(query, enumValues) {
34472
+ const lower = query.toLowerCase();
34473
+ const matched = [];
34474
+ const seen = /* @__PURE__ */ new Set();
34475
+ for (const ev of enumValues) {
34476
+ if (seen.has(ev)) continue;
34477
+ const re = new RegExp(`\\b${escapeRegex(ev)}\\b`, "i");
34478
+ if (re.test(query)) {
34479
+ matched.push(ev);
34480
+ seen.add(ev);
34481
+ }
34482
+ }
34483
+ for (const syn of EVENT_SYNONYMS) {
34484
+ const re = new RegExp(
34485
+ `\\b${syn.phrase.split(/\s+/).map(escapeRegex).join("\\s+")}\\b`,
34486
+ "i"
34487
+ );
34488
+ if (re.test(query)) {
34489
+ for (const c of syn.canonical) {
34490
+ if (seen.has(c)) continue;
34491
+ if (!enumValues.includes(c)) continue;
34492
+ matched.push(c);
34493
+ seen.add(c);
34494
+ }
34495
+ }
34496
+ }
34497
+ const globMatches = lower.match(/\b([a-z_]+)_\*/g) ?? [];
34498
+ for (const glob of globMatches) {
34499
+ const prefix = glob.slice(0, -2);
34500
+ for (const ev of enumValues) {
34501
+ if (seen.has(ev)) continue;
34502
+ if (ev.startsWith(prefix)) {
34503
+ matched.push(ev);
34504
+ seen.add(ev);
34505
+ }
34506
+ }
34507
+ }
34508
+ const eventNounMention = /\b(?:event|events|class|classes)\b/i.test(query) && matched.length === 0;
34509
+ return { matched, flagged: eventNounMention };
34510
+ }
34511
+ function deriveIntentPhrase(query, stripTokens) {
34512
+ let out = query;
34513
+ for (const tok of stripTokens) {
34514
+ if (!tok) continue;
34515
+ const re = new RegExp(escapeRegex(tok), "gi");
34516
+ out = out.replace(re, " ");
34517
+ }
34518
+ return out.replace(/\s+/g, " ").trim();
34519
+ }
34520
+ function computeConfidence(parsed) {
34521
+ const dims = [
34522
+ { present: parsed.hasTimeMention, resolved: parsed.timeResolved },
34523
+ { present: parsed.hasAgentMention, resolved: parsed.agentResolved },
34524
+ { present: parsed.hasEventMention, resolved: parsed.eventResolved }
34525
+ ];
34526
+ const present = dims.filter((d) => d.present);
34527
+ let base;
34528
+ if (present.length === 0) {
34529
+ base = parsed.intentEmpty ? 0 : 0.3;
34530
+ } else {
34531
+ const resolved = present.filter((d) => d.resolved).length;
34532
+ base = resolved / present.length;
34533
+ }
34534
+ const adjusted = base - 0.15 * parsed.ambiguityCount;
34535
+ if (adjusted < 0) return 0;
34536
+ if (adjusted > 1) return 1;
34537
+ return adjusted;
34538
+ }
34539
+ function parseQuery(query, opts) {
34540
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
34541
+ const enumValues = opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES;
34542
+ const original = query ?? "";
34543
+ const trimmed = original.trim();
34544
+ if (trimmed.length === 0) {
34545
+ return {
34546
+ time_range: null,
34547
+ agent_names: [],
34548
+ event_types: [],
34549
+ intent_phrase: "",
34550
+ ambiguity_flags: ["no_signal_extracted"],
34551
+ parse_confidence: 0
34552
+ };
34553
+ }
34554
+ const ambiguity_flags = /* @__PURE__ */ new Set();
34555
+ const timeMatch = resolveTimeRange(trimmed, now);
34556
+ const hasTimeMention = TIME_MENTION_PROBE.test(trimmed);
34557
+ if (hasTimeMention && !timeMatch) {
34558
+ ambiguity_flags.add("unknown_time_token");
34559
+ }
34560
+ const agentResult = extractAgentNames(trimmed, opts?.registry);
34561
+ if (agentResult.flagged) {
34562
+ ambiguity_flags.add("unknown_agent_token");
34563
+ }
34564
+ const hasAgentMention = AGENT_MENTION_PROBE.test(trimmed);
34565
+ const eventResult = extractEventTypes(trimmed, enumValues);
34566
+ const hasEventMention = EVENT_MENTION_PROBE.test(trimmed);
34567
+ if (eventResult.flagged) {
34568
+ ambiguity_flags.add("unknown_event_token");
34569
+ }
34570
+ const stripTokens = [];
34571
+ if (timeMatch) stripTokens.push(timeMatch.matchedSubstring);
34572
+ for (const name of agentResult.matched) stripTokens.push(name);
34573
+ for (const ev of eventResult.matched) {
34574
+ if (trimmed.toLowerCase().includes(ev.toLowerCase())) {
34575
+ stripTokens.push(ev);
34576
+ }
34577
+ }
34578
+ const intent_phrase = deriveIntentPhrase(trimmed, stripTokens);
34579
+ const parse_confidence = computeConfidence({
34580
+ hasTimeMention,
34581
+ timeResolved: timeMatch !== null,
34582
+ hasAgentMention,
34583
+ agentResolved: agentResult.matched.length > 0,
34584
+ hasEventMention,
34585
+ eventResolved: eventResult.matched.length > 0,
34586
+ intentEmpty: intent_phrase.length === 0,
34587
+ ambiguityCount: ambiguity_flags.size
34588
+ });
34589
+ if (timeMatch === null && agentResult.matched.length === 0 && eventResult.matched.length === 0 && intent_phrase.length === 0) {
34590
+ ambiguity_flags.add("no_signal_extracted");
34591
+ }
34592
+ return {
34593
+ time_range: timeMatch ? timeMatch.range : null,
34594
+ agent_names: agentResult.matched,
34595
+ event_types: eventResult.matched,
34596
+ intent_phrase,
34597
+ ambiguity_flags: Array.from(ambiguity_flags),
34598
+ parse_confidence
34599
+ };
34600
+ }
34601
+ function isLowConfidence(parsed) {
34602
+ return parsed.parse_confidence < LLM_ASSIST_THRESHOLD;
34603
+ }
34604
+ async function parseQueryWithLlmAssist(query, llmAssist, opts) {
34605
+ const parsed = parseQuery(query, opts);
34606
+ if (!llmAssist || !isLowConfidence(parsed)) return parsed;
34607
+ let completion;
34608
+ try {
34609
+ completion = await llmAssist(query, parsed);
34610
+ } catch {
34611
+ return parsed;
34612
+ }
34613
+ if (!completion || typeof completion !== "object") return parsed;
34614
+ const merged = { ...parsed };
34615
+ if (parsed.time_range === null && completion.time_range) {
34616
+ merged.time_range = completion.time_range;
34617
+ }
34618
+ if (parsed.agent_names.length === 0 && Array.isArray(completion.agent_names)) {
34619
+ merged.agent_names = completion.agent_names.filter(
34620
+ (s) => typeof s === "string" && s.length > 0
34621
+ );
34622
+ }
34623
+ if (parsed.event_types.length === 0 && Array.isArray(completion.event_types)) {
34624
+ const allowed = new Set(opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES);
34625
+ merged.event_types = completion.event_types.filter(
34626
+ (s) => typeof s === "string" && allowed.has(s)
34627
+ );
34628
+ }
34629
+ merged.parse_confidence = Math.max(
34630
+ parsed.parse_confidence,
34631
+ computeConfidence({
34632
+ hasTimeMention: TIME_MENTION_PROBE.test(query),
34633
+ timeResolved: merged.time_range !== null,
34634
+ hasAgentMention: AGENT_MENTION_PROBE.test(query),
34635
+ agentResolved: merged.agent_names.length > 0,
34636
+ hasEventMention: EVENT_MENTION_PROBE.test(query),
34637
+ eventResolved: merged.event_types.length > 0,
34638
+ intentEmpty: merged.intent_phrase.length === 0,
34639
+ ambiguityCount: merged.ambiguity_flags.length
34640
+ })
34641
+ );
34642
+ return merged;
34643
+ }
34644
+ function auditSafeSummary(parsed) {
34645
+ return {
34646
+ time_range: parsed.time_range ? {
34647
+ start_iso: parsed.time_range.start.toISOString(),
34648
+ end_iso: parsed.time_range.end.toISOString(),
34649
+ ...parsed.time_range.relative_label !== void 0 ? { relative_label: parsed.time_range.relative_label } : {}
34650
+ } : null,
34651
+ agent_names: [...parsed.agent_names],
34652
+ event_types: [...parsed.event_types],
34653
+ ambiguity_flags: [...parsed.ambiguity_flags],
34654
+ parse_confidence: parsed.parse_confidence
34655
+ };
34656
+ }
34657
+ var CANONICAL_AUDIT_EVENT_CLASSES, EVENT_SYNONYMS, MS_PER_HOUR, MS_PER_DAY, NUMBER_WORDS, TIME_MENTION_PROBE, AGENT_MENTION_PROBE, EVENT_MENTION_PROBE, LLM_ASSIST_THRESHOLD;
34658
+ var init_concierge_query_grammar = __esm({
34659
+ "src/chat/concierge-query-grammar.ts"() {
34660
+ init_constants4();
34661
+ init_operator_chat_audit_events();
34662
+ CANONICAL_AUDIT_EVENT_CLASSES = [
34663
+ // Lifecycle / policy
34664
+ "policy_change",
34665
+ "approval_request",
34666
+ "audit_truncate",
34667
+ "lockdown",
34668
+ "unwrap",
34669
+ // Exit bundle (Tier 1)
34670
+ "exit_bundle_export",
34671
+ "exit_bundle_import_activate",
34672
+ "exit_bundle_rekey",
34673
+ // Cross-harness approval aggregator
34674
+ "cross_harness_approval_aggregated",
34675
+ "cross_harness_approval_resolved",
34676
+ "cross_harness_approval_deduped",
34677
+ "cross_harness_approval_payload_decrypted",
34678
+ "cross_harness_approval_audit_trail_viewed",
34679
+ "cross_harness_approval_replayed",
34680
+ // Composition (full set from constants.ts)
34681
+ ...COMPOSITION_EVENT_TYPES,
34682
+ // Operator chat / concierge (full set from OPERATOR_CHAT_OPS)
34683
+ OPERATOR_CHAT_OPS.CONCIERGE_CHAT,
34684
+ OPERATOR_CHAT_OPS.AGENT_INSPECT_PANEL_OPENED,
34685
+ OPERATOR_CHAT_OPS.CONCIERGE_HISTORY_READ,
34686
+ OPERATOR_CHAT_OPS.CONCIERGE_THREAD_DELETED,
34687
+ OPERATOR_CHAT_OPS.CONCIERGE_MEMORY_READ_FAILED,
34688
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
34689
+ // Bridge / commitment
34690
+ "bridge_commit",
34691
+ "bridge_verify",
34692
+ "bridge_attest",
34693
+ "proof_commitment",
34694
+ "proof_reveal",
34695
+ // Reputation
34696
+ "reputation_export",
34697
+ "reputation_import",
34698
+ "reputation_publish",
34699
+ "reputation_record",
34700
+ "reputation_query"
34701
+ ];
34702
+ EVENT_SYNONYMS = [
34703
+ { phrase: "approvals", canonical: ["approval_request", "cross_harness_approval_aggregated", "cross_harness_approval_resolved"] },
34704
+ { phrase: "approval", canonical: ["approval_request"] },
34705
+ { phrase: "policy changes", canonical: ["policy_change"] },
34706
+ { phrase: "policy change", canonical: ["policy_change"] },
34707
+ { phrase: "policy edits", canonical: ["policy_change"] },
34708
+ { phrase: "lockdowns", canonical: ["lockdown"] },
34709
+ { phrase: "exit bundles", canonical: ["exit_bundle_export", "exit_bundle_import_activate"] },
34710
+ { phrase: "exit bundle", canonical: ["exit_bundle_export"] },
34711
+ { phrase: "audit truncations", canonical: ["audit_truncate"] },
34712
+ { phrase: "audit truncation", canonical: ["audit_truncate"] },
34713
+ { phrase: "compositions", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34714
+ { phrase: "receipts", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34715
+ { phrase: "receipt verifications", canonical: ["composition_receipt_verified"] },
34716
+ { phrase: "concierge chats", canonical: [OPERATOR_CHAT_OPS.CONCIERGE_CHAT] },
34717
+ { phrase: "cross harness approvals", canonical: ["cross_harness_approval_aggregated", "cross_harness_approval_resolved"] }
34718
+ ];
34719
+ MS_PER_HOUR = 60 * 60 * 1e3;
34720
+ MS_PER_DAY = 24 * MS_PER_HOUR;
34721
+ NUMBER_WORDS = {
34722
+ a: 1,
34723
+ an: 1,
34724
+ one: 1,
34725
+ two: 2,
34726
+ three: 3,
34727
+ four: 4,
34728
+ five: 5,
34729
+ six: 6,
34730
+ seven: 7,
34731
+ eight: 8,
34732
+ nine: 9,
34733
+ ten: 10,
34734
+ twelve: 12,
34735
+ twentyfour: 24
34736
+ };
34737
+ TIME_MENTION_PROBE = /\b(yesterday|today|now|past|last|this\s+week|this\s+month|since|from|between|\d{4}-\d{2}-\d{2})\b/i;
34738
+ AGENT_MENTION_PROBE = /\bagent[s]?\b/i;
34739
+ EVENT_MENTION_PROBE = /\b(event|events|class|classes|approvals?|policy)\b/i;
34740
+ LLM_ASSIST_THRESHOLD = 0.5;
34741
+ }
34742
+ });
34743
+ function approxTokenLen2(text) {
33414
34744
  return Math.ceil(text.length / 4);
33415
34745
  }
33416
34746
  function makeEventId(prefix) {
33417
34747
  return `${prefix}-${Date.now()}-${randomUUID().slice(0, 8)}`;
33418
34748
  }
34749
+ function classifyFetcherError(error) {
34750
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
34751
+ if (msg.includes("timeout") || msg.includes("timed out")) return "timeout";
34752
+ if (msg.includes("schema") || msg.includes("invalid shape")) {
34753
+ return "schema_mismatch";
34754
+ }
34755
+ if (msg.includes("io") || msg.includes("read") || msg.includes("enoent") || msg.includes("eacces")) {
34756
+ return "io_failed";
34757
+ }
34758
+ return "unknown";
34759
+ }
33419
34760
  function formatPriorTurnLine(turn) {
33420
34761
  const label = turn.role === "user" ? "OPERATOR" : "CONCIERGE";
33421
34762
  return `${label}: ${turn.content}`;
@@ -33423,18 +34764,21 @@ function formatPriorTurnLine(turn) {
33423
34764
  function hashOf(input) {
33424
34765
  return hashToString(sha256(stringToBytes(input)));
33425
34766
  }
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;
34767
+ 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
34768
  var init_operator_chat_service = __esm({
33428
34769
  "src/chat/operator-chat-service.ts"() {
33429
34770
  init_hashing();
33430
34771
  init_encoding();
33431
34772
  init_operator_chat_audit_events();
33432
34773
  init_operator_chat_types();
34774
+ init_concierge_context_router();
34775
+ init_concierge_query_grammar();
33433
34776
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
33434
34777
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
33435
34778
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
33436
34779
  DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET = 500;
33437
34780
  DEFAULT_CONCIERGE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
34781
+ DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET = 2e3;
33438
34782
  SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
33439
34783
  1. Castle Wall: OS-boundary egress filter enforced at the kernel level. Blocks unauthorized outbound calls even from prompt-injected agents.
33440
34784
  2. Sentinels: internal observation via process introspection. Surfaces anomalies to the operator; does not enforce.
@@ -33476,6 +34820,11 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33476
34820
  historyTokenBudget;
33477
34821
  sessionTtlMs;
33478
34822
  clock;
34823
+ contextFetchers;
34824
+ contextLlmAssist;
34825
+ dynamicContextBudget;
34826
+ agentRegistry;
34827
+ grammarLlmAssist;
33479
34828
  /**
33480
34829
  * In-memory thread_id assigned to the active concierge session.
33481
34830
  * The first sendConcierge call after construction allocates a fresh
@@ -33507,6 +34856,19 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33507
34856
  this.historyTokenBudget = deps.conciergeHistoryTokenBudget !== void 0 && deps.conciergeHistoryTokenBudget > 0 ? deps.conciergeHistoryTokenBudget : DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET;
33508
34857
  this.sessionTtlMs = deps.conciergeSessionTtlMs !== void 0 && deps.conciergeSessionTtlMs > 0 ? deps.conciergeSessionTtlMs : DEFAULT_CONCIERGE_SESSION_TTL_MS;
33509
34858
  this.clock = deps.conciergeClock ?? (() => Date.now());
34859
+ if (deps.conciergeContextFetchers) {
34860
+ this.contextFetchers = deps.conciergeContextFetchers;
34861
+ }
34862
+ if (deps.conciergeContextLlmAssist) {
34863
+ this.contextLlmAssist = deps.conciergeContextLlmAssist;
34864
+ }
34865
+ this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
34866
+ if (deps.conciergeAgentRegistry) {
34867
+ this.agentRegistry = deps.conciergeAgentRegistry;
34868
+ }
34869
+ if (deps.conciergeGrammarLlmAssist) {
34870
+ this.grammarLlmAssist = deps.conciergeGrammarLlmAssist;
34871
+ }
33510
34872
  }
33511
34873
  // ── Concierge ─────────────────────────────────────────────────────────
33512
34874
  /**
@@ -33565,11 +34927,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33565
34927
  await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
33566
34928
  });
33567
34929
  }
34930
+ const parsedGrammar = await this.runGrammarParse(filterResult.filtered);
33568
34931
  const start = Date.now();
33569
34932
  let conciergeBody;
33570
34933
  let servedBy = "disabled";
33571
34934
  let displayLabel = "Concierge: substrate not configured";
33572
34935
  let outcome = "substrate_disabled";
34936
+ let dynamicCategoriesIncluded = [];
33573
34937
  if (!this.substrateSelector) {
33574
34938
  conciergeBody = "Concierge unavailable. The substrate selector is not configured for this fortress. Pick a substrate in the Policy center to enable concierge replies.";
33575
34939
  } else {
@@ -33581,7 +34945,15 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33581
34945
  conciergeBody = "Concierge unavailable. The chosen substrate does not support summarization. Pick a different substrate in the Policy center.";
33582
34946
  outcome = "substrate_disabled";
33583
34947
  } else {
33584
- const context = await this.assembleConciergeContext(priorTurns);
34948
+ const dynamicResult = await this.runDynamicContextFold(
34949
+ filterResult.filtered,
34950
+ parsedGrammar
34951
+ );
34952
+ dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34953
+ const context = await this.assembleConciergeContext(
34954
+ priorTurns,
34955
+ dynamicResult.section
34956
+ );
33585
34957
  const response = await this.substrateSelector.invokeSummarize(
33586
34958
  "concierge",
33587
34959
  {
@@ -33645,7 +35017,9 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33645
35017
  ...assistantTurnId !== void 0 ? { turn_index: assistantTurnId } : {},
33646
35018
  ...this.memory ? {
33647
35019
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
33648
- } : {}
35020
+ } : {},
35021
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {},
35022
+ parsed_grammar: auditSafeSummary(parsedGrammar)
33649
35023
  };
33650
35024
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
33651
35025
  return {
@@ -33796,10 +35170,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33796
35170
  * ## Sanctuary reference
33797
35171
  * <static domain reference block>
33798
35172
  *
35173
+ * ## Live fortress context ← WP-V1.3-9 Tau-3, when present
35174
+ * ### <Category>
35175
+ * <fetcher payload>
35176
+ *
33799
35177
  * ## Prior conversation ← WP-V1.3-9 Tau-2, when present
33800
35178
  * OPERATOR: ...
33801
35179
  * CONCIERGE: ...
33802
- * ---
33803
35180
  *
33804
35181
  * ## Recent activity
33805
35182
  * <recentActivity output>
@@ -33818,13 +35195,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33818
35195
  * if available; the v1.2 selector does not expose one, so structured
33819
35196
  * serialization is the canonical path for v1.3.
33820
35197
  */
33821
- async assembleConciergeContext(priorTurns = []) {
35198
+ async assembleConciergeContext(priorTurns = [], dynamicSection = "") {
33822
35199
  const ref = `## Sanctuary reference
33823
35200
  ${SANCTUARY_DOMAIN_REFERENCE}`;
33824
35201
  const priorSection = this.formatPriorTurnsSection(priorTurns);
33825
35202
  if (!this.contextProviders) {
33826
35203
  return [
33827
35204
  ref,
35205
+ ...dynamicSection ? [dynamicSection] : [],
33828
35206
  ...priorSection ? [priorSection] : [],
33829
35207
  "## Recent activity\n(no providers wired)",
33830
35208
  "## Wrapped agents\n(no providers wired)",
@@ -33838,6 +35216,7 @@ ${SANCTUARY_DOMAIN_REFERENCE}`;
33838
35216
  ]);
33839
35217
  return [
33840
35218
  ref,
35219
+ ...dynamicSection ? [dynamicSection] : [],
33841
35220
  ...priorSection ? [priorSection] : [],
33842
35221
  `## Recent activity
33843
35222
  ${activity}`,
@@ -33847,6 +35226,69 @@ ${agents}`,
33847
35226
  ${inbox}`
33848
35227
  ].join("\n\n");
33849
35228
  }
35229
+ /**
35230
+ * Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
35231
+ * trip. Fail-soft on every axis: missing fetchers short-circuit to
35232
+ * an empty fold, fetcher failures emit a per-category audit event
35233
+ * and are omitted from the rendered section, an LLM-assist failure
35234
+ * proceeds with no fold. Returns the rendered section + the list of
35235
+ * categories whose data made it into the section (used for the
35236
+ * round-trip audit emission).
35237
+ *
35238
+ * Tau-4: receives the pre-parsed `ParsedQuery` and forwards it as the
35239
+ * `parsed` opt to `foldContext`, so fetchers see the structured
35240
+ * `FetcherHints` derived from it.
35241
+ */
35242
+ async runDynamicContextFold(query, parsedGrammar) {
35243
+ if (!this.contextFetchers) {
35244
+ return { section: "", categoriesIncluded: [] };
35245
+ }
35246
+ const result = await foldContext(query, this.contextFetchers, {
35247
+ maxTokens: this.dynamicContextBudget,
35248
+ ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
35249
+ onFetcherFailure: (category, error) => {
35250
+ this.emitContextFetcherFailed(category, classifyFetcherError(error));
35251
+ },
35252
+ parsed: parsedGrammar
35253
+ });
35254
+ return result;
35255
+ }
35256
+ /**
35257
+ * WP-V1.3-9 Tau-4: parse the (PII-filtered) operator query into a
35258
+ * `ParsedQuery`. Routes through the LLM-assist completion hook when
35259
+ * configured and the rule-based parse is below
35260
+ * `LLM_ASSIST_THRESHOLD`. Always returns a parse object (never
35261
+ * throws) so the audit emission can carry the result unconditionally.
35262
+ */
35263
+ async runGrammarParse(query) {
35264
+ return parseQueryWithLlmAssist(query, this.grammarLlmAssist, {
35265
+ ...this.agentRegistry !== void 0 ? { registry: this.agentRegistry } : {},
35266
+ eventClassEnum: CANONICAL_AUDIT_EVENT_CLASSES
35267
+ });
35268
+ }
35269
+ /**
35270
+ * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
35271
+ * of the fold path so the dynamic-context handler stays readable.
35272
+ * Emits with `result: "failure"` since the named category dropped
35273
+ * from the rendered section for this round-trip.
35274
+ */
35275
+ emitContextFetcherFailed(category, failureReason) {
35276
+ const payload = {
35277
+ version: "1.2",
35278
+ event_id: makeEventId("conc-ctxfail"),
35279
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
35280
+ identity_id: this.identityId,
35281
+ kind: "operator_concierge_context_fetcher_failed",
35282
+ surface: "concierge",
35283
+ category,
35284
+ failure_reason: failureReason
35285
+ };
35286
+ this.emit(
35287
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
35288
+ payload,
35289
+ "failure"
35290
+ );
35291
+ }
33850
35292
  /**
33851
35293
  * Render the prior-conversation section with token-budget enforcement
33852
35294
  * (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
@@ -33857,14 +35299,14 @@ ${inbox}`
33857
35299
  if (turns.length === 0) return "";
33858
35300
  const HEADER = "## Prior conversation";
33859
35301
  const lines = turns.map(formatPriorTurnLine);
33860
- const headerTokens = approxTokenLen(`${HEADER}
35302
+ const headerTokens = approxTokenLen2(`${HEADER}
33861
35303
  `);
33862
- const sepTokens = approxTokenLen("\n");
35304
+ const sepTokens = approxTokenLen2("\n");
33863
35305
  let runningTokens = headerTokens;
33864
35306
  let runningLines = [];
33865
35307
  for (let i = lines.length - 1; i >= 0; i--) {
33866
35308
  const line = lines[i];
33867
- const tokens = approxTokenLen(line) + (runningLines.length > 0 ? sepTokens : 0);
35309
+ const tokens = approxTokenLen2(line) + (runningLines.length > 0 ? sepTokens : 0);
33868
35310
  if (runningTokens + tokens > this.historyTokenBudget) break;
33869
35311
  runningTokens += tokens;
33870
35312
  runningLines.push(line);
@@ -33892,7 +35334,7 @@ ${runningLines.join("\n")}`;
33892
35334
  function chatStorageKey(surface, threadKey) {
33893
35335
  return `${surface}.${threadKey}`;
33894
35336
  }
33895
- var OPERATOR_CHAT_NAMESPACE, HKDF_INFO, OperatorChatStore;
35337
+ var OPERATOR_CHAT_NAMESPACE, HKDF_INFO2, OperatorChatStore;
33896
35338
  var init_operator_chat_store = __esm({
33897
35339
  "src/chat/operator-chat-store.ts"() {
33898
35340
  init_encryption();
@@ -33900,13 +35342,13 @@ var init_operator_chat_store = __esm({
33900
35342
  init_encoding();
33901
35343
  init_operator_chat_types();
33902
35344
  OPERATOR_CHAT_NAMESPACE = "_chat";
33903
- HKDF_INFO = "operator-chat-store-v1";
35345
+ HKDF_INFO2 = "operator-chat-store-v1";
33904
35346
  OperatorChatStore = class {
33905
35347
  storage;
33906
35348
  encryptionKey;
33907
35349
  constructor(storage, masterKey) {
33908
35350
  this.storage = storage;
33909
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO);
35351
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
33910
35352
  }
33911
35353
  /**
33912
35354
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -33991,7 +35433,7 @@ var init_operator_chat_store = __esm({
33991
35433
  function bundleKey(threadId) {
33992
35434
  return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
33993
35435
  }
33994
- function stripKeyPrefix(key) {
35436
+ function stripKeyPrefix2(key) {
33995
35437
  if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
33996
35438
  return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
33997
35439
  }
@@ -34002,7 +35444,7 @@ function lastTurnId(bundle) {
34002
35444
  }
34003
35445
  return max;
34004
35446
  }
34005
- var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO2, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES2, ConciergeMemoryStore;
35447
+ var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO3, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
34006
35448
  var init_concierge_memory_store = __esm({
34007
35449
  "src/chat/concierge-memory-store.ts"() {
34008
35450
  init_encryption();
@@ -34010,9 +35452,9 @@ var init_concierge_memory_store = __esm({
34010
35452
  init_encoding();
34011
35453
  CONCIERGE_MEMORY_NAMESPACE = "_chat";
34012
35454
  CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
34013
- HKDF_INFO2 = "concierge-memory-store-v1";
35455
+ HKDF_INFO3 = "concierge-memory-store-v1";
34014
35456
  DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
34015
- MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
35457
+ MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
34016
35458
  ConciergeMemoryStore = class {
34017
35459
  storage;
34018
35460
  encryptionKey;
@@ -34021,7 +35463,7 @@ var init_concierge_memory_store = __esm({
34021
35463
  locks;
34022
35464
  constructor(opts) {
34023
35465
  this.storage = opts.storage;
34024
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
35466
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
34025
35467
  this.fortressId = opts.fortressId;
34026
35468
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
34027
35469
  this.locks = /* @__PURE__ */ new Map();
@@ -34100,7 +35542,7 @@ var init_concierge_memory_store = __esm({
34100
35542
  return { ok: false, reason: "io_failed" };
34101
35543
  }
34102
35544
  if (!raw) return { ok: true, turns: [] };
34103
- if (raw.length > MAX_BUNDLE_BYTES2) {
35545
+ if (raw.length > MAX_BUNDLE_BYTES3) {
34104
35546
  return { ok: false, reason: "oversize_bundle" };
34105
35547
  }
34106
35548
  let envelope;
@@ -34147,7 +35589,7 @@ var init_concierge_memory_store = __esm({
34147
35589
  );
34148
35590
  const summaries = [];
34149
35591
  for (const meta of entries) {
34150
- const threadId = stripKeyPrefix(meta.key);
35592
+ const threadId = stripKeyPrefix2(meta.key);
34151
35593
  if (threadId === null) continue;
34152
35594
  const bundle = await this.loadBundle(threadId);
34153
35595
  if (!bundle || bundle.turns.length === 0) continue;
@@ -34200,7 +35642,7 @@ var init_concierge_memory_store = __esm({
34200
35642
  );
34201
35643
  let pruned = 0;
34202
35644
  for (const meta of entries) {
34203
- const threadId = stripKeyPrefix(meta.key);
35645
+ const threadId = stripKeyPrefix2(meta.key);
34204
35646
  if (threadId === null) continue;
34205
35647
  pruned += await this.withLock(threadId, async () => {
34206
35648
  const bundle = await this.loadBundle(threadId);
@@ -34231,7 +35673,7 @@ var init_concierge_memory_store = __esm({
34231
35673
  return null;
34232
35674
  }
34233
35675
  if (!raw) return null;
34234
- if (raw.length > MAX_BUNDLE_BYTES2) return null;
35676
+ if (raw.length > MAX_BUNDLE_BYTES3) return null;
34235
35677
  try {
34236
35678
  const envelope = JSON.parse(bytesToString(raw));
34237
35679
  const aad = stringToBytes(threadId);
@@ -34322,7 +35764,18 @@ function buildV11Bindings(inputs) {
34322
35764
  registry
34323
35765
  }),
34324
35766
  conciergePiiFilter: buildConciergePiiFilter(),
34325
- conciergeMemory
35767
+ conciergeMemory,
35768
+ conciergeContextFetchers: buildConciergeContextFetchers({
35769
+ auditLog: inputs.auditLog,
35770
+ identityId: inputs.identityId,
35771
+ registry
35772
+ }),
35773
+ ...inputs.intelligenceSelector ? {
35774
+ conciergeContextLlmAssist: buildConciergeContextLlmAssist({
35775
+ selector: inputs.intelligenceSelector,
35776
+ identityId: inputs.identityId
35777
+ })
35778
+ } : {}
34326
35779
  });
34327
35780
  }
34328
35781
  const hubService = new HubService({
@@ -34383,6 +35836,107 @@ function buildConciergeContextProviders(args) {
34383
35836
  }
34384
35837
  };
34385
35838
  }
35839
+ function buildConciergeContextFetchers(args) {
35840
+ const empty = async () => "";
35841
+ return {
35842
+ templates: async () => {
35843
+ const entries = listTemplates();
35844
+ if (entries.length === 0) return "(no templates installed)";
35845
+ const lines = entries.map((e) => {
35846
+ const m = e.metadata;
35847
+ return `${m.name} (tier ${m.tier}, channel ${m.channel}, target ${m.target_archetype})`;
35848
+ });
35849
+ return lines.join("\n");
35850
+ },
35851
+ agent_state: async (agentNameHint) => {
35852
+ const records = args.registry.list({ identity_id: args.identityId });
35853
+ if (records.length === 0) return "(no wrapped agents)";
35854
+ const filtered = agentNameHint ? records.filter(
35855
+ (r) => r.agent_id.toLowerCase().includes(agentNameHint.toLowerCase()) || r.harness.toLowerCase().includes(agentNameHint.toLowerCase())
35856
+ ) : records;
35857
+ const target = filtered.length > 0 ? filtered : records;
35858
+ const lines = target.slice(0, 20).map((r) => {
35859
+ const tmpl = typeof r.channel_template_id === "string" ? r.channel_template_id : "no_template";
35860
+ return `${r.agent_id} harness=${r.harness} status=${r.status} template=${tmpl}`;
35861
+ });
35862
+ return lines.join("\n");
35863
+ },
35864
+ agent_activity: async (agentNameHint) => {
35865
+ const result = await args.auditLog.query({ limit: 50 });
35866
+ const owned = result.entries.filter(
35867
+ (e) => e.identity_id === args.identityId
35868
+ );
35869
+ const filtered = agentNameHint ? owned.filter((e) => {
35870
+ const agentId = e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : "";
35871
+ return agentId.toLowerCase().includes(agentNameHint.toLowerCase());
35872
+ }) : owned;
35873
+ const tail = (filtered.length > 0 ? filtered : owned).slice(-20);
35874
+ if (tail.length === 0) return "(no activity)";
35875
+ return tail.map((e) => {
35876
+ const agentId = (e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : null) ?? "_fortress";
35877
+ return `${e.timestamp} ${e.layer}.${e.operation} agent=${agentId} result=${e.result}`;
35878
+ }).join("\n");
35879
+ },
35880
+ audit_log: async () => {
35881
+ const result = await args.auditLog.query({ limit: 30 });
35882
+ const owned = result.entries.filter(
35883
+ (e) => e.identity_id === args.identityId
35884
+ );
35885
+ if (owned.length === 0) return "(no audit log entries)";
35886
+ return owned.slice(-30).map(
35887
+ (e) => `${e.timestamp} ${e.layer}.${e.operation} result=${e.result}`
35888
+ ).join("\n");
35889
+ },
35890
+ sentinel_findings: empty,
35891
+ anomaly_alerts: empty,
35892
+ recent_receipts: async () => {
35893
+ const result = await args.auditLog.query({ limit: 100 });
35894
+ const owned = result.entries.filter(
35895
+ (e) => e.identity_id === args.identityId && e.operation.startsWith("composition_")
35896
+ );
35897
+ if (owned.length === 0) return "(no recent composition events)";
35898
+ return owned.slice(-15).map((e) => `${e.timestamp} ${e.operation} result=${e.result}`).join("\n");
35899
+ },
35900
+ verascore_deltas: empty
35901
+ };
35902
+ }
35903
+ function buildConciergeContextLlmAssist(args) {
35904
+ return async (query, categories) => {
35905
+ const labelList = categories.map((c) => `- ${c}`).join("\n");
35906
+ const prompt2 = `You are a router. Classify the operator's query into one of the categories below or "none".
35907
+ Reply with exactly one token: one category name or "none".
35908
+
35909
+ Categories:
35910
+ ${labelList}
35911
+
35912
+ Query: ${query}
35913
+
35914
+ Category:`;
35915
+ try {
35916
+ const handle = await args.selector.getSubstrate("concierge");
35917
+ if (!handle.capability.summarize) return "none";
35918
+ const response = await args.selector.invokeSummarize("concierge", {
35919
+ kind: "summarize",
35920
+ context: prompt2,
35921
+ query: "Output the single category token.",
35922
+ maxTokens: 16
35923
+ });
35924
+ if (response.failureClass || response.body.kind !== "summarize") {
35925
+ return "none";
35926
+ }
35927
+ const raw = response.body.text.trim().toLowerCase();
35928
+ const head = raw.split(/\s|[.,!?:;]/)[0] ?? "";
35929
+ const normalized = head.replace(/[^a-z_]/g, "");
35930
+ const known = categories;
35931
+ if (known.includes(normalized)) {
35932
+ return normalized;
35933
+ }
35934
+ return "none";
35935
+ } catch {
35936
+ return "none";
35937
+ }
35938
+ };
35939
+ }
34386
35940
  function buildConciergePiiFilter() {
34387
35941
  return {
34388
35942
  filter(input) {
@@ -34410,6 +35964,7 @@ var init_wiring = __esm({
34410
35964
  init_agent_registry_persistence();
34411
35965
  init_operator_chat_index();
34412
35966
  init_privacy_filter();
35967
+ init_registry();
34413
35968
  CapabilityErrorAgentController = class {
34414
35969
  fail(action) {
34415
35970
  throw new HubCapabilityError(
@@ -34557,7 +36112,7 @@ var init_defaults = __esm({
34557
36112
  });
34558
36113
 
34559
36114
  // src/intelligence/policy-store.ts
34560
- var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO3, IntelligenceConfigStore;
36115
+ var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO4, IntelligenceConfigStore;
34561
36116
  var init_policy_store = __esm({
34562
36117
  "src/intelligence/policy-store.ts"() {
34563
36118
  init_encryption();
@@ -34566,13 +36121,13 @@ var init_policy_store = __esm({
34566
36121
  init_defaults();
34567
36122
  INTELLIGENCE_NAMESPACE = "_intelligence";
34568
36123
  SUBSTRATE_CONFIG_KEY = "substrate-config";
34569
- HKDF_INFO3 = "intelligence-substrate-config";
36124
+ HKDF_INFO4 = "intelligence-substrate-config";
34570
36125
  IntelligenceConfigStore = class {
34571
36126
  storage;
34572
36127
  encryptionKey;
34573
36128
  constructor(storage, masterKey) {
34574
36129
  this.storage = storage;
34575
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
36130
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
34576
36131
  }
34577
36132
  /**
34578
36133
  * Load the operator's substrate config from disk. Returns the config
@@ -36222,7 +37777,7 @@ var init_memory = __esm({
36222
37777
 
36223
37778
  // src/contracts/v1.1/constants.ts
36224
37779
  var SIGNATURE_SCHEME_V12, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
36225
- var init_constants4 = __esm({
37780
+ var init_constants5 = __esm({
36226
37781
  "src/contracts/v1.1/constants.ts"() {
36227
37782
  SIGNATURE_SCHEME_V12 = "ed25519-v1";
36228
37783
  EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
@@ -36640,7 +38195,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
36640
38195
  var InvalidExitBundleError, PRIVATE_MATERIAL_KEYS;
36641
38196
  var init_verifier2 = __esm({
36642
38197
  "src/exit/verifier.ts"() {
36643
- init_constants4();
38198
+ init_constants5();
36644
38199
  init_exit_bundle_manifest();
36645
38200
  init_encoding();
36646
38201
  init_hashing();
@@ -37452,7 +39007,7 @@ var init_bundle = __esm({
37452
39007
  "src/exit/bundle.ts"() {
37453
39008
  init_state_store();
37454
39009
  init_config();
37455
- init_constants4();
39010
+ init_constants5();
37456
39011
  init_canonical_json();
37457
39012
  init_hashing();
37458
39013
  init_encoding();
@@ -38490,12 +40045,18 @@ ${err.message}
38490
40045
  } : void 0;
38491
40046
  const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
38492
40047
  const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
40048
+ const aggregatorPayloadStore = new AggregatorPayloadStore({
40049
+ storage,
40050
+ masterKey,
40051
+ fortressId: fortressIdForAggregator
40052
+ });
38493
40053
  const approvalAggregator = new ApprovalAggregator({
38494
40054
  storage,
38495
40055
  masterKey,
38496
40056
  auditLog,
38497
40057
  identityId: aggregatorIdentityId,
38498
- fortressId: fortressIdForAggregator
40058
+ fortressId: fortressIdForAggregator,
40059
+ payloadStore: aggregatorPayloadStore
38499
40060
  });
38500
40061
  const wrappedApprovalChannel = new AggregatorBackedChannel({
38501
40062
  underlying: approvalChannel,
@@ -38709,6 +40270,7 @@ var init_src = __esm({
38709
40270
  init_gate();
38710
40271
  init_approval_aggregator();
38711
40272
  init_aggregator_backed_channel();
40273
+ init_aggregator_store();
38712
40274
  init_tools4();
38713
40275
  init_router();
38714
40276
  init_router();