@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.cjs CHANGED
@@ -5044,7 +5044,8 @@ var init_constants = __esm({
5044
5044
  RESERVED_EVENT_TYPE_PREFIXES = [
5045
5045
  "EXTENSION_",
5046
5046
  "cross_fortress_",
5047
- "multi_master_"
5047
+ "multi_master_",
5048
+ "cross_harness_approval_"
5048
5049
  ];
5049
5050
  RESERVED_EXTENSION_ENVELOPE_KEYS = [
5050
5051
  "cross_fortress_read_grant",
@@ -9236,9 +9237,9 @@ function fingerprintDID(did) {
9236
9237
  return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
9237
9238
  }
9238
9239
  function countInjectionsToday(audit) {
9239
- const startOfDay = /* @__PURE__ */ new Date();
9240
- startOfDay.setHours(0, 0, 0, 0);
9241
- const cutoff = startOfDay.getTime();
9240
+ const startOfDay2 = /* @__PURE__ */ new Date();
9241
+ startOfDay2.setHours(0, 0, 0, 0);
9242
+ const cutoff = startOfDay2.getTime();
9242
9243
  return audit.filter((e) => {
9243
9244
  const ts = new Date(e.timestamp).getTime();
9244
9245
  if (isNaN(ts) || ts < cutoff) return false;
@@ -9247,9 +9248,9 @@ function countInjectionsToday(audit) {
9247
9248
  }).length;
9248
9249
  }
9249
9250
  function countProofsToday(audit) {
9250
- const startOfDay = /* @__PURE__ */ new Date();
9251
- startOfDay.setHours(0, 0, 0, 0);
9252
- const cutoff = startOfDay.getTime();
9251
+ const startOfDay2 = /* @__PURE__ */ new Date();
9252
+ startOfDay2.setHours(0, 0, 0, 0);
9253
+ const cutoff = startOfDay2.getTime();
9253
9254
  return audit.filter((e) => {
9254
9255
  if (e.layer !== "l3") return false;
9255
9256
  if (!PROOF_CREATION_OPS.has(e.operation)) return false;
@@ -17218,6 +17219,45 @@ async function handleApprovalInboxRoute(deps, req, res) {
17218
17219
  await handleStream2(deps, res);
17219
17220
  return true;
17220
17221
  }
17222
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/revision`) {
17223
+ const revision = await deps.aggregator.getRevision();
17224
+ writeJSON4(res, 200, { ok: true, data: { revision } });
17225
+ return true;
17226
+ }
17227
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/sync`) {
17228
+ const sinceRaw = url.searchParams.get("since_revision");
17229
+ const sinceParsed = sinceRaw === null ? 0 : Number.parseInt(sinceRaw, 10);
17230
+ const sinceRevision = Number.isFinite(sinceParsed) && sinceParsed >= 0 ? sinceParsed : 0;
17231
+ const limit = parseLimit2(
17232
+ url.searchParams.get("limit"),
17233
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17234
+ APPROVAL_INBOX_MAX_LIMIT
17235
+ );
17236
+ const delta = await deps.aggregator.getSync({ sinceRevision, limit });
17237
+ writeJSON4(res, 200, { ok: true, data: delta });
17238
+ return true;
17239
+ }
17240
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17241
+ const limit = parseLimit2(
17242
+ url.searchParams.get("limit"),
17243
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17244
+ APPROVAL_INBOX_MAX_LIMIT
17245
+ );
17246
+ const statusRaw = url.searchParams.get("status");
17247
+ const sinceTs = url.searchParams.get("since") ?? void 0;
17248
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17249
+ const filterStatus = statusRaw && isStatusFilter(statusRaw) && statusRaw !== "pending" ? statusRaw : void 0;
17250
+ const entries = await deps.aggregator.getHistory(
17251
+ {
17252
+ limit,
17253
+ ...filterStatus !== void 0 ? { status: filterStatus } : {},
17254
+ ...sinceTs !== void 0 ? { sinceTs } : {}
17255
+ },
17256
+ operatorId
17257
+ );
17258
+ writeJSON4(res, 200, { ok: true, data: { entries } });
17259
+ return true;
17260
+ }
17221
17261
  if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
17222
17262
  const limit = parseLimit2(
17223
17263
  url.searchParams.get("limit"),
@@ -17240,11 +17280,39 @@ async function handleApprovalInboxRoute(deps, req, res) {
17240
17280
  writeJSON4(res, 404, { ok: false, error: "not_found", path });
17241
17281
  return true;
17242
17282
  }
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
17283
+ if (method === "GET" && entryMatch.action === "audit-trail") {
17284
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17285
+ if (!entry) {
17286
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17287
+ return true;
17288
+ }
17289
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17290
+ const trail = await deps.aggregator.getAuditTrail(
17291
+ entryMatch.aggregatorId,
17292
+ operatorId
17247
17293
  );
17294
+ writeJSON4(res, 200, { ok: true, data: { entry, audit_trail: trail } });
17295
+ return true;
17296
+ }
17297
+ if (method === "GET" && entryMatch.action === "payload") {
17298
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17299
+ if (!entry) {
17300
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
17301
+ return true;
17302
+ }
17303
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
17304
+ const payload = await deps.aggregator.getFullPayloadWithAudit(
17305
+ entryMatch.aggregatorId,
17306
+ operatorId
17307
+ );
17308
+ writeJSON4(res, 200, {
17309
+ ok: true,
17310
+ data: { entry, request_payload: payload }
17311
+ });
17312
+ return true;
17313
+ }
17314
+ if (method === "GET" && entryMatch.action === null) {
17315
+ const entry = await deps.aggregator.getEntry(entryMatch.aggregatorId);
17248
17316
  if (!entry) {
17249
17317
  writeJSON4(res, 404, { ok: false, error: "not_found" });
17250
17318
  return true;
@@ -20278,7 +20346,10 @@ var init_approval_aggregator = __esm({
20278
20346
  APPROVAL_AGGREGATOR_AUDIT_OPS = {
20279
20347
  AGGREGATED: "cross_harness_approval_aggregated",
20280
20348
  RESOLVED: "cross_harness_approval_resolved",
20281
- DEDUPED: "cross_harness_approval_deduped"
20349
+ DEDUPED: "cross_harness_approval_deduped",
20350
+ PAYLOAD_DECRYPTED: "cross_harness_approval_payload_decrypted",
20351
+ AUDIT_TRAIL_VIEWED: "cross_harness_approval_audit_trail_viewed",
20352
+ REPLAYED: "cross_harness_approval_replayed"
20282
20353
  };
20283
20354
  DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
20284
20355
  DEFAULT_MAX_LIST_LIMIT = 200;
@@ -20294,6 +20365,8 @@ var init_approval_aggregator = __esm({
20294
20365
  now;
20295
20366
  resolveSourceContext;
20296
20367
  resolveHubInboxItemId;
20368
+ payloadStore;
20369
+ resolveEnforcementChain;
20297
20370
  /** Cached entries by `aggregator_id`. */
20298
20371
  entries = /* @__PURE__ */ new Map();
20299
20372
  /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
@@ -20306,6 +20379,20 @@ var init_approval_aggregator = __esm({
20306
20379
  hydrated = false;
20307
20380
  /** Active SSE listeners. */
20308
20381
  listeners = /* @__PURE__ */ new Set();
20382
+ /**
20383
+ * Monotonic revision counter, bumped on every mutation (ingest of new
20384
+ * entry, resolve, expire, delete). Hydrated from max(last_modified_revision)
20385
+ * across persisted entries on first read; in-memory after that. v1.3
20386
+ * Upsilon-4.
20387
+ */
20388
+ currentRevision = 0;
20389
+ /**
20390
+ * Removal tombstones: aggregator_id -> revision at removal. Used by the
20391
+ * sync API to surface "removed" entries to mobile consumers between
20392
+ * polls. In-memory only; server restart clears tombstones (mobile
20393
+ * bootstraps via `list()` on reconnect). v1.3 Upsilon-4.
20394
+ */
20395
+ removedTombstones = /* @__PURE__ */ new Map();
20309
20396
  constructor(deps) {
20310
20397
  this.storage = deps.storage;
20311
20398
  this.encryptionKey = derivePurposeKey(
@@ -20323,6 +20410,14 @@ var init_approval_aggregator = __esm({
20323
20410
  source_agent_id: this.fortressId
20324
20411
  }));
20325
20412
  this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
20413
+ this.payloadStore = deps.payloadStore ?? null;
20414
+ this.resolveEnforcementChain = deps.resolveEnforcementChain ?? ((event) => [
20415
+ {
20416
+ layer: "l2",
20417
+ event: `approval_required:${event.operation}`,
20418
+ timestamp: event.request_timestamp
20419
+ }
20420
+ ]);
20326
20421
  }
20327
20422
  /**
20328
20423
  * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
@@ -20332,6 +20427,113 @@ var init_approval_aggregator = __esm({
20332
20427
  this.listeners.add(listener);
20333
20428
  return () => this.listeners.delete(listener);
20334
20429
  }
20430
+ /**
20431
+ * Current aggregator revision. v1.3 Upsilon-4. Mobile companions
20432
+ * poll the lightweight `/revision` route to detect that something
20433
+ * changed before fetching a full sync delta.
20434
+ */
20435
+ async getRevision() {
20436
+ await this.hydrate();
20437
+ return this.currentRevision;
20438
+ }
20439
+ /**
20440
+ * Compute a delta since `sinceRevision`. v1.3 Upsilon-4. Mobile
20441
+ * clients poll this for cheap state-sync. Behavior:
20442
+ * - `added`: entries whose `created_at_revision > sinceRevision`.
20443
+ * - `changed`: entries that existed at `sinceRevision` but had a
20444
+ * status transition (resolve, expire) since.
20445
+ * - `removed`: aggregator_ids deleted after `sinceRevision`.
20446
+ * - `revision`: current aggregator revision; pass this back as
20447
+ * `sinceRevision` on the next call.
20448
+ *
20449
+ * `limit` caps the total count returned across all three lists,
20450
+ * prioritized as added -> changed -> removed (newer-state first).
20451
+ * When more changes exist than fit, the next call with the returned
20452
+ * revision will pick up the rest because each entry's
20453
+ * last_modified_revision is unchanged by truncation.
20454
+ */
20455
+ async getSync(opts) {
20456
+ await this.hydrate();
20457
+ await this.expireStale();
20458
+ const sinceRevision = opts?.sinceRevision ?? 0;
20459
+ const cap = Math.min(
20460
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20461
+ this.maxListLimit
20462
+ );
20463
+ const added = [];
20464
+ const changed = [];
20465
+ for (const entry of this.entries.values()) {
20466
+ const lastMod = entry.last_modified_revision ?? 0;
20467
+ if (lastMod <= sinceRevision) continue;
20468
+ const createdRev = entry.created_at_revision ?? 0;
20469
+ if (createdRev > sinceRevision) {
20470
+ added.push(entry);
20471
+ } else {
20472
+ changed.push(entry);
20473
+ }
20474
+ }
20475
+ const removed = [];
20476
+ for (const [id, rev] of this.removedTombstones) {
20477
+ if (rev > sinceRevision) removed.push(id);
20478
+ }
20479
+ added.sort(
20480
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20481
+ );
20482
+ changed.sort(
20483
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20484
+ );
20485
+ let remaining = cap;
20486
+ const addedOut = added.slice(0, Math.max(0, remaining));
20487
+ remaining -= addedOut.length;
20488
+ const changedOut = changed.slice(0, Math.max(0, remaining));
20489
+ remaining -= changedOut.length;
20490
+ const removedOut = removed.slice(0, Math.max(0, remaining));
20491
+ return {
20492
+ revision: this.currentRevision,
20493
+ added: addedOut,
20494
+ changed: changedOut,
20495
+ removed: removedOut
20496
+ };
20497
+ }
20498
+ /**
20499
+ * Delete an entry. Drops the in-memory record, the persisted bundle,
20500
+ * and the at-rest payload (if a payload store is wired). Records a
20501
+ * tombstone with the new revision so sync-API consumers see a
20502
+ * `removed` delta. Returns true when an entry was deleted, false on
20503
+ * unknown id. v1.3 Upsilon-4. Reserved for v1.4+ retention housekeeping;
20504
+ * Upsilon-4 ships the surface so mobile sync-API tests can exercise the
20505
+ * removal path.
20506
+ */
20507
+ async deleteEntry(aggregatorId) {
20508
+ await this.hydrate();
20509
+ const entry = this.entries.get(aggregatorId);
20510
+ if (!entry) return false;
20511
+ const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20512
+ this.entries.delete(aggregatorId);
20513
+ this.dedupIndex.delete(dedupKey);
20514
+ this.fullPayloads.delete(aggregatorId);
20515
+ for (const [corr, id] of this.correlationIndex) {
20516
+ if (id === aggregatorId) this.correlationIndex.delete(corr);
20517
+ }
20518
+ try {
20519
+ await this.storage.delete(APPROVAL_AGGREGATOR_NAMESPACE, aggregatorId);
20520
+ } catch {
20521
+ }
20522
+ if (this.payloadStore) {
20523
+ try {
20524
+ await this.payloadStore.deletePayload(aggregatorId);
20525
+ } catch {
20526
+ }
20527
+ }
20528
+ const revision = this.nextRevision();
20529
+ this.removedTombstones.set(aggregatorId, revision);
20530
+ this.emit({ type: "removed", entry: { ...entry } });
20531
+ return true;
20532
+ }
20533
+ nextRevision() {
20534
+ this.currentRevision += 1;
20535
+ return this.currentRevision;
20536
+ }
20335
20537
  /**
20336
20538
  * Ingest a gate event. Returns the aggregator entry on first sight,
20337
20539
  * `null` when deduped. Resolution events update the existing record;
@@ -20373,13 +20575,152 @@ var init_approval_aggregator = __esm({
20373
20575
  }
20374
20576
  /**
20375
20577
  * 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).
20578
+ * `null` when the entry is unknown. When the in-memory payload map has
20579
+ * been evicted (e.g. after a server restart) and a `payloadStore` was
20580
+ * provided, the at-rest bundle is decrypted and the in-memory map is
20581
+ * refilled. Audit emission lives on the `*WithAudit` variant; this base
20582
+ * accessor is silent so internal callers can read without polluting the
20583
+ * audit trail.
20378
20584
  */
20379
20585
  async getFullPayload(aggregatorId) {
20380
20586
  await this.hydrate();
20381
20587
  if (!this.entries.has(aggregatorId)) return null;
20382
- return this.fullPayloads.get(aggregatorId) ?? null;
20588
+ const cached = this.fullPayloads.get(aggregatorId);
20589
+ if (cached !== void 0) return cached;
20590
+ if (this.payloadStore) {
20591
+ try {
20592
+ const restored = await this.payloadStore.loadPayload(aggregatorId);
20593
+ if (restored !== null) {
20594
+ this.fullPayloads.set(aggregatorId, restored);
20595
+ return restored;
20596
+ }
20597
+ } catch {
20598
+ }
20599
+ }
20600
+ return null;
20601
+ }
20602
+ /**
20603
+ * Return the entry record for the given id, or null when unknown.
20604
+ * Idempotent. v1.3 Upsilon-3.
20605
+ */
20606
+ async getEntry(aggregatorId) {
20607
+ await this.hydrate();
20608
+ return this.entries.get(aggregatorId) ?? null;
20609
+ }
20610
+ /**
20611
+ * Audited variant of `getFullPayload`. Emits the
20612
+ * `cross_harness_approval_payload_decrypted` audit event before
20613
+ * returning. Used by the operator-facing /payload replay route.
20614
+ * v1.3 Upsilon-3.
20615
+ */
20616
+ async getFullPayloadWithAudit(aggregatorId, operatorId) {
20617
+ const payload = await this.getFullPayload(aggregatorId);
20618
+ if (payload === null) return null;
20619
+ const entry = this.entries.get(aggregatorId);
20620
+ this.auditLog.append(
20621
+ "l2",
20622
+ APPROVAL_AGGREGATOR_AUDIT_OPS.PAYLOAD_DECRYPTED,
20623
+ operatorId,
20624
+ {
20625
+ aggregator_id: aggregatorId,
20626
+ ...entry ? {
20627
+ source_harness: entry.source_harness,
20628
+ source_agent_id: entry.source_agent_id,
20629
+ entry_status: entry.status
20630
+ } : {}
20631
+ }
20632
+ );
20633
+ return payload;
20634
+ }
20635
+ /**
20636
+ * Return the audit-log entries that led to and surround this approval.
20637
+ * Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
20638
+ * DEDUPED, replay events) all carry `details.aggregator_id` and link
20639
+ * directly. Gate-side emissions (`gate_*:operation`) do not carry the
20640
+ * aggregator id at v1.3, so they are matched via timestamp window
20641
+ * (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
20642
+ * pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
20643
+ * v1.3 Upsilon-3.
20644
+ */
20645
+ async getAuditTrail(aggregatorId, operatorId) {
20646
+ await this.hydrate();
20647
+ const entry = this.entries.get(aggregatorId);
20648
+ if (!entry) {
20649
+ return [];
20650
+ }
20651
+ const sinceMs = Date.parse(entry.created_at) - 1e3;
20652
+ const sinceIso = new Date(sinceMs).toISOString();
20653
+ const queried = await this.auditLog.query({ since: sinceIso, limit: 1e3 });
20654
+ const operationPart = entry.policy_rule_id.includes(":") ? entry.policy_rule_id.slice(entry.policy_rule_id.indexOf(":") + 1) : entry.policy_rule_id;
20655
+ const lifetimeStart = sinceMs;
20656
+ const lifetimeEnd = entry.resolved_at ? Date.parse(entry.resolved_at) + 1e3 : Date.parse(entry.expires_at) + 1e3;
20657
+ const matches = [];
20658
+ for (const audit of queried.entries) {
20659
+ const detailsId = audit.details !== void 0 ? audit.details["aggregator_id"] : void 0;
20660
+ if (detailsId === aggregatorId) {
20661
+ matches.push(audit);
20662
+ continue;
20663
+ }
20664
+ const auditMs = Date.parse(audit.timestamp);
20665
+ if (auditMs < lifetimeStart || auditMs > lifetimeEnd) continue;
20666
+ if (audit.operation.endsWith(`:${operationPart}`)) {
20667
+ matches.push(audit);
20668
+ }
20669
+ }
20670
+ matches.sort(
20671
+ (a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0
20672
+ );
20673
+ this.auditLog.append(
20674
+ "l2",
20675
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AUDIT_TRAIL_VIEWED,
20676
+ operatorId,
20677
+ {
20678
+ aggregator_id: aggregatorId,
20679
+ entry_status: entry.status,
20680
+ match_count: matches.length
20681
+ }
20682
+ );
20683
+ return matches;
20684
+ }
20685
+ /**
20686
+ * List historical (resolved) approvals. Excludes pending entries by
20687
+ * design: `list()` is the pending-inbox surface and `getHistory()` is
20688
+ * the resolved-replay surface. Emits REPLAYED on each call. v1.3
20689
+ * Upsilon-3.
20690
+ */
20691
+ async getHistory(opts, operatorId) {
20692
+ await this.hydrate();
20693
+ await this.expireStale();
20694
+ const limit = Math.min(
20695
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20696
+ this.maxListLimit
20697
+ );
20698
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
20699
+ const matching = [];
20700
+ for (const entry of this.entries.values()) {
20701
+ if (entry.status === "pending") continue;
20702
+ if (opts?.status && entry.status !== opts.status) continue;
20703
+ const stamp = Date.parse(entry.resolved_at ?? entry.created_at);
20704
+ if (stamp < sinceMs) continue;
20705
+ matching.push(entry);
20706
+ }
20707
+ matching.sort((a, b) => {
20708
+ const aStamp = a.resolved_at ?? a.created_at;
20709
+ const bStamp = b.resolved_at ?? b.created_at;
20710
+ return bStamp.localeCompare(aStamp);
20711
+ });
20712
+ const sliced = matching.slice(0, limit);
20713
+ this.auditLog.append(
20714
+ "l2",
20715
+ APPROVAL_AGGREGATOR_AUDIT_OPS.REPLAYED,
20716
+ operatorId,
20717
+ {
20718
+ result_count: sliced.length,
20719
+ ...opts?.status !== void 0 ? { status_filter: opts.status } : {},
20720
+ ...opts?.sinceTs !== void 0 ? { since: opts.sinceTs } : {}
20721
+ }
20722
+ );
20723
+ return sliced;
20383
20724
  }
20384
20725
  /**
20385
20726
  * Resolve an entry. Used by both:
@@ -20403,6 +20744,7 @@ var init_approval_aggregator = __esm({
20403
20744
  entry.status = decision;
20404
20745
  entry.resolved_at = this.now().toISOString();
20405
20746
  entry.resolved_by = operatorId;
20747
+ entry.last_modified_revision = this.nextRevision();
20406
20748
  await this.persist(entry);
20407
20749
  this.auditLog.append(
20408
20750
  "l2",
@@ -20453,6 +20795,8 @@ var init_approval_aggregator = __esm({
20453
20795
  const now = this.now();
20454
20796
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20455
20797
  const hubInboxId = this.resolveHubInboxItemId(event);
20798
+ const enforcementChain = this.resolveEnforcementChain(event);
20799
+ const revision = this.nextRevision();
20456
20800
  const entry = {
20457
20801
  aggregator_id: id,
20458
20802
  source_harness: ctx.source_harness,
@@ -20464,13 +20808,22 @@ var init_approval_aggregator = __esm({
20464
20808
  status: "pending",
20465
20809
  created_at: now.toISOString(),
20466
20810
  expires_at: expires.toISOString(),
20467
- ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
20811
+ created_at_revision: revision,
20812
+ last_modified_revision: revision,
20813
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20814
+ ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20468
20815
  };
20469
20816
  this.entries.set(id, entry);
20470
20817
  this.dedupIndex.set(dedupKey, id);
20471
20818
  this.correlationIndex.set(event.correlation_id, id);
20472
20819
  this.fullPayloads.set(id, event.context);
20473
20820
  await this.persist(entry);
20821
+ if (this.payloadStore) {
20822
+ try {
20823
+ await this.payloadStore.savePayload(id, event.context);
20824
+ } catch {
20825
+ }
20826
+ }
20474
20827
  this.auditLog.append(
20475
20828
  "l2",
20476
20829
  APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
@@ -20499,6 +20852,7 @@ var init_approval_aggregator = __esm({
20499
20852
  entry.status = status;
20500
20853
  entry.resolved_at = event.resolution.decided_at;
20501
20854
  entry.resolved_by = event.resolution.decided_by;
20855
+ entry.last_modified_revision = this.nextRevision();
20502
20856
  await this.persist(entry);
20503
20857
  this.auditLog.append(
20504
20858
  "l2",
@@ -20561,6 +20915,7 @@ var init_approval_aggregator = __esm({
20561
20915
  entry.status = "expired";
20562
20916
  entry.resolved_at = this.now().toISOString();
20563
20917
  entry.resolved_by = "system_ttl";
20918
+ entry.last_modified_revision = this.nextRevision();
20564
20919
  await this.persist(entry);
20565
20920
  this.auditLog.append(
20566
20921
  "l2",
@@ -20607,6 +20962,10 @@ var init_approval_aggregator = __esm({
20607
20962
  this.entries.set(entry.aggregator_id, entry);
20608
20963
  const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20609
20964
  this.dedupIndex.set(dedupKey, entry.aggregator_id);
20965
+ const lastMod = entry.last_modified_revision ?? 0;
20966
+ if (lastMod > this.currentRevision) {
20967
+ this.currentRevision = lastMod;
20968
+ }
20610
20969
  } catch {
20611
20970
  }
20612
20971
  }
@@ -20786,6 +21145,149 @@ var init_aggregator_backed_channel = __esm({
20786
21145
  }
20787
21146
  });
20788
21147
 
21148
+ // src/principal-policy/aggregator-store.ts
21149
+ function payloadKey(aggregatorId) {
21150
+ return `${AGGREGATOR_PAYLOAD_KEY_PREFIX}${aggregatorId}`;
21151
+ }
21152
+ function stripKeyPrefix(key) {
21153
+ if (!key.startsWith(AGGREGATOR_PAYLOAD_KEY_PREFIX)) return null;
21154
+ return key.slice(AGGREGATOR_PAYLOAD_KEY_PREFIX.length);
21155
+ }
21156
+ var AGGREGATOR_PAYLOAD_NAMESPACE, AGGREGATOR_PAYLOAD_KEY_PREFIX, HKDF_INFO, DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS, MAX_BUNDLE_BYTES2, AggregatorPayloadStore;
21157
+ var init_aggregator_store = __esm({
21158
+ "src/principal-policy/aggregator-store.ts"() {
21159
+ init_encryption();
21160
+ init_key_derivation();
21161
+ init_encoding();
21162
+ AGGREGATOR_PAYLOAD_NAMESPACE = "_approval_aggregator_payloads";
21163
+ AGGREGATOR_PAYLOAD_KEY_PREFIX = "payload.";
21164
+ HKDF_INFO = "l2-approval-aggregator-payload-v1";
21165
+ DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS = 30;
21166
+ MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
21167
+ AggregatorPayloadStore = class {
21168
+ storage;
21169
+ encryptionKey;
21170
+ fortressId;
21171
+ retentionDays;
21172
+ constructor(opts) {
21173
+ this.storage = opts.storage;
21174
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO);
21175
+ this.fortressId = opts.fortressId;
21176
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_AGGREGATOR_PAYLOAD_RETENTION_DAYS;
21177
+ }
21178
+ /**
21179
+ * Persist `payload` under the given aggregator_id. Idempotent; calling
21180
+ * twice with the same id rewrites the bundle (retention_until is
21181
+ * recomputed). Returns the bundle's retention_until ISO-8601 timestamp
21182
+ * so callers can log it.
21183
+ */
21184
+ async savePayload(aggregatorId, payload) {
21185
+ const now = /* @__PURE__ */ new Date();
21186
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
21187
+ const retentionUntil = new Date(now.getTime() + retentionMs);
21188
+ const bundle = {
21189
+ version: 1,
21190
+ aggregator_id: aggregatorId,
21191
+ fortress_id: this.fortressId,
21192
+ created_at: now.toISOString(),
21193
+ retention_until: retentionUntil.toISOString(),
21194
+ payload
21195
+ };
21196
+ const aad = stringToBytes(aggregatorId);
21197
+ const plaintext = stringToBytes(JSON.stringify(bundle));
21198
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
21199
+ await this.storage.write(
21200
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21201
+ payloadKey(aggregatorId),
21202
+ stringToBytes(JSON.stringify(envelope))
21203
+ );
21204
+ return bundle.retention_until;
21205
+ }
21206
+ /**
21207
+ * Read the persisted payload for the aggregator_id. Returns null if no
21208
+ * bundle exists, the bundle is corrupted, or AAD binding fails.
21209
+ */
21210
+ async loadPayload(aggregatorId) {
21211
+ const key = payloadKey(aggregatorId);
21212
+ let raw;
21213
+ try {
21214
+ raw = await this.storage.read(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21215
+ } catch {
21216
+ return null;
21217
+ }
21218
+ if (!raw) return null;
21219
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
21220
+ try {
21221
+ const envelope = JSON.parse(bytesToString(raw));
21222
+ const aad = stringToBytes(aggregatorId);
21223
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21224
+ const parsed = JSON.parse(
21225
+ bytesToString(plaintext)
21226
+ );
21227
+ if (parsed.version !== 1) return null;
21228
+ if (parsed.aggregator_id !== aggregatorId) return null;
21229
+ return parsed.payload;
21230
+ } catch {
21231
+ return null;
21232
+ }
21233
+ }
21234
+ /**
21235
+ * Delete the persisted payload. Returns true when a bundle was removed,
21236
+ * false when none existed.
21237
+ */
21238
+ async deletePayload(aggregatorId) {
21239
+ const key = payloadKey(aggregatorId);
21240
+ const existed = await this.storage.exists(
21241
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21242
+ key
21243
+ );
21244
+ if (!existed) return false;
21245
+ try {
21246
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, key);
21247
+ } catch {
21248
+ return false;
21249
+ }
21250
+ return true;
21251
+ }
21252
+ /**
21253
+ * Drop expired payload bundles. Returns the count of bundles pruned.
21254
+ * Caller wires this into the cocoon-unlock initialization path.
21255
+ */
21256
+ async pruneExpired(now) {
21257
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
21258
+ const entries = await this.storage.list(
21259
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21260
+ AGGREGATOR_PAYLOAD_KEY_PREFIX
21261
+ );
21262
+ let pruned = 0;
21263
+ for (const meta of entries) {
21264
+ const aggregatorId = stripKeyPrefix(meta.key);
21265
+ if (aggregatorId === null) continue;
21266
+ const raw = await this.storage.read(
21267
+ AGGREGATOR_PAYLOAD_NAMESPACE,
21268
+ meta.key
21269
+ );
21270
+ if (!raw) continue;
21271
+ try {
21272
+ const envelope = JSON.parse(bytesToString(raw));
21273
+ const aad = stringToBytes(aggregatorId);
21274
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
21275
+ const parsed = JSON.parse(
21276
+ bytesToString(plaintext)
21277
+ );
21278
+ if (parsed.retention_until <= cutoff) {
21279
+ await this.storage.delete(AGGREGATOR_PAYLOAD_NAMESPACE, meta.key);
21280
+ pruned += 1;
21281
+ }
21282
+ } catch {
21283
+ }
21284
+ }
21285
+ return { pruned };
21286
+ }
21287
+ };
21288
+ }
21289
+ });
21290
+
20789
21291
  // src/principal-policy/tools.ts
20790
21292
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
20791
21293
  return [
@@ -33404,7 +33906,14 @@ var init_operator_chat_audit_events = __esm({
33404
33906
  * turns; the concierge degrades to single-turn after emitting. Body
33405
33907
  * carries thread_id + a stable failure_reason enum.
33406
33908
  */
33407
- CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed"
33909
+ CONCIERGE_MEMORY_READ_FAILED: "operator_concierge_memory_read_failed",
33910
+ /**
33911
+ * Concierge dynamic-context fetcher failed (WP-V1.3-9 Tau-3). Emitted
33912
+ * when a category fetcher throws while assembling the dynamic context
33913
+ * fold. The concierge omits that category and continues; the user-
33914
+ * facing query is never broken. Body carries category + failure_reason.
33915
+ */
33916
+ CONCIERGE_CONTEXT_FETCHER_FAILED: "operator_concierge_context_fetcher_failed"
33408
33917
  };
33409
33918
  }
33410
33919
  });
@@ -33417,12 +33926,844 @@ var init_operator_chat_types = __esm({
33417
33926
  CONCIERGE_THREAD_KEY = "_fortress";
33418
33927
  }
33419
33928
  });
33929
+
33930
+ // src/chat/concierge-context-router.ts
33931
+ function phrasePattern(phrase) {
33932
+ const escaped = phrase.toLowerCase().split(/\s+/).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
33933
+ return { source: `\\b${escaped}\\b`, phrase };
33934
+ }
33935
+ function extractAgentNameHint(query) {
33936
+ const agentPattern = /\bagent\s+["']?([A-Za-z][\w-]{0,40})["']?/i;
33937
+ const m = query.match(agentPattern);
33938
+ if (m && m[1]) return m[1];
33939
+ const quoted = query.match(/["']([A-Za-z][\w-]{0,40})["']/);
33940
+ if (quoted && quoted[1]) return quoted[1];
33941
+ return null;
33942
+ }
33943
+ function isTrivialQuery(query) {
33944
+ const norm = query.trim().toLowerCase();
33945
+ if (norm.length === 0) return true;
33946
+ if (norm.length < 8) return true;
33947
+ return TRIVIAL_GREETINGS.has(norm);
33948
+ }
33949
+ function classifyQuery(query, parsedGrammar) {
33950
+ const normalized = query.toLowerCase();
33951
+ const matches = [];
33952
+ const grammarAgent = parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null;
33953
+ for (const spec of CATEGORY_KEYWORDS) {
33954
+ const matchedPhrases = [];
33955
+ for (const pattern of spec.patterns) {
33956
+ if (matchedPhrases.includes(pattern.phrase)) continue;
33957
+ const re = new RegExp(pattern.source, "i");
33958
+ if (re.test(normalized)) {
33959
+ matchedPhrases.push(pattern.phrase);
33960
+ }
33961
+ }
33962
+ if (matchedPhrases.length === 0) continue;
33963
+ const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
33964
+ const wantsAgentHint = spec.category === "agent_state" || spec.category === "agent_activity";
33965
+ const agent_name_hint = wantsAgentHint ? grammarAgent ?? extractAgentNameHint(query) : null;
33966
+ matches.push({
33967
+ category: spec.category,
33968
+ confidence,
33969
+ matched_keywords: matchedPhrases,
33970
+ agent_name_hint,
33971
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33972
+ });
33973
+ }
33974
+ matches.sort((a, b) => {
33975
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
33976
+ return CONTEXT_CATEGORIES.indexOf(a.category) - CONTEXT_CATEGORIES.indexOf(b.category);
33977
+ });
33978
+ return matches;
33979
+ }
33980
+ function fetcherHintsFromGrammar(parsed) {
33981
+ if (!parsed) return void 0;
33982
+ const hasTime = parsed.time_range !== null;
33983
+ const hasAgents = parsed.agent_names.length > 0;
33984
+ const hasEvents = parsed.event_types.length > 0;
33985
+ if (!hasTime && !hasAgents && !hasEvents) return void 0;
33986
+ const hints = {};
33987
+ if (parsed.time_range) {
33988
+ const range = parsed.time_range;
33989
+ hints.time_range = {
33990
+ start: range.start,
33991
+ end: range.end,
33992
+ ...range.relative_label !== void 0 ? { relative_label: range.relative_label } : {}
33993
+ };
33994
+ }
33995
+ if (hasAgents) hints.agent_names = parsed.agent_names;
33996
+ if (hasEvents) hints.event_types = parsed.event_types;
33997
+ return hints;
33998
+ }
33420
33999
  function approxTokenLen(text) {
34000
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
34001
+ }
34002
+ async function runFetcher(match, fetchers, hints) {
34003
+ switch (match.category) {
34004
+ case "templates":
34005
+ return fetchers.templates(hints);
34006
+ case "agent_state":
34007
+ return fetchers.agent_state(match.agent_name_hint, hints);
34008
+ case "agent_activity":
34009
+ return fetchers.agent_activity(match.agent_name_hint, hints);
34010
+ case "audit_log":
34011
+ return fetchers.audit_log(hints);
34012
+ case "sentinel_findings":
34013
+ return fetchers.sentinel_findings(hints);
34014
+ case "anomaly_alerts":
34015
+ return fetchers.anomaly_alerts(hints);
34016
+ case "recent_receipts":
34017
+ return fetchers.recent_receipts(hints);
34018
+ case "verascore_deltas":
34019
+ return fetchers.verascore_deltas(hints);
34020
+ }
34021
+ }
34022
+ function trivialMatch(category, parsedGrammar) {
34023
+ return {
34024
+ category,
34025
+ confidence: 0.5,
34026
+ matched_keywords: ["llm-assist"],
34027
+ agent_name_hint: parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null,
34028
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
34029
+ };
34030
+ }
34031
+ async function foldContext(query, fetchers, opts) {
34032
+ const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
34033
+ const parsed = opts?.parsed ?? null;
34034
+ const hints = fetcherHintsFromGrammar(parsed);
34035
+ let matches = classifyQuery(query, parsed);
34036
+ if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
34037
+ try {
34038
+ const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
34039
+ if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
34040
+ matches = [trivialMatch(picked, parsed)];
34041
+ }
34042
+ } catch {
34043
+ }
34044
+ }
34045
+ if (matches.length === 0) {
34046
+ return { section: "", categoriesIncluded: [] };
34047
+ }
34048
+ const attempts = [];
34049
+ for (const match of matches) {
34050
+ try {
34051
+ const text = await runFetcher(match, fetchers, hints);
34052
+ const trimmed = text.trim();
34053
+ if (trimmed.length > 0) {
34054
+ attempts.push({ category: match.category, text: trimmed });
34055
+ }
34056
+ } catch (err) {
34057
+ opts?.onFetcherFailure?.(match.category, err);
34058
+ }
34059
+ }
34060
+ if (attempts.length === 0) {
34061
+ return { section: "", categoriesIncluded: [] };
34062
+ }
34063
+ const headerTokens = approxTokenLen(`${DYNAMIC_CONTEXT_SECTION_HEADER}
34064
+ `);
34065
+ const sepTokens = approxTokenLen("\n\n");
34066
+ let runningTokens = headerTokens;
34067
+ const kept = [];
34068
+ for (const attempt of attempts) {
34069
+ const block = `### ${CATEGORY_LABELS[attempt.category]}
34070
+ ${attempt.text}`;
34071
+ const tokens = approxTokenLen(block) + (kept.length > 0 ? sepTokens : 0);
34072
+ if (kept.length === 0) {
34073
+ kept.push(attempt);
34074
+ runningTokens += tokens;
34075
+ continue;
34076
+ }
34077
+ if (runningTokens + tokens > budget) break;
34078
+ kept.push(attempt);
34079
+ runningTokens += tokens;
34080
+ }
34081
+ const blocks = kept.map(
34082
+ (k) => `### ${CATEGORY_LABELS[k.category]}
34083
+ ${k.text}`
34084
+ );
34085
+ const section = `${DYNAMIC_CONTEXT_SECTION_HEADER}
34086
+ ${blocks.join("\n\n")}`;
34087
+ return {
34088
+ section,
34089
+ categoriesIncluded: kept.map((k) => k.category)
34090
+ };
34091
+ }
34092
+ var APPROX_CHARS_PER_TOKEN, DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET, DYNAMIC_CONTEXT_SECTION_HEADER, CONTEXT_CATEGORIES, CATEGORY_KEYWORDS, TRIVIAL_GREETINGS, CATEGORY_LABELS;
34093
+ var init_concierge_context_router = __esm({
34094
+ "src/chat/concierge-context-router.ts"() {
34095
+ APPROX_CHARS_PER_TOKEN = 4;
34096
+ DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET = 2e3;
34097
+ DYNAMIC_CONTEXT_SECTION_HEADER = "## Live fortress context";
34098
+ CONTEXT_CATEGORIES = [
34099
+ "templates",
34100
+ "agent_state",
34101
+ "agent_activity",
34102
+ "audit_log",
34103
+ "sentinel_findings",
34104
+ "anomaly_alerts",
34105
+ "recent_receipts",
34106
+ "verascore_deltas"
34107
+ ];
34108
+ CATEGORY_KEYWORDS = [
34109
+ {
34110
+ category: "templates",
34111
+ patterns: [
34112
+ "templates",
34113
+ "template",
34114
+ "channel templates",
34115
+ "channel template",
34116
+ "list templates",
34117
+ "available templates",
34118
+ "what templates"
34119
+ ].map(phrasePattern)
34120
+ },
34121
+ {
34122
+ category: "agent_state",
34123
+ patterns: [
34124
+ "state",
34125
+ "status",
34126
+ "agent state",
34127
+ "agent status",
34128
+ "status of agent",
34129
+ "status of agents",
34130
+ "state of",
34131
+ "doing"
34132
+ ].map(phrasePattern)
34133
+ },
34134
+ {
34135
+ category: "agent_activity",
34136
+ patterns: [
34137
+ "activity",
34138
+ "agent activity",
34139
+ "what did",
34140
+ "recent activity"
34141
+ ].map(phrasePattern)
34142
+ },
34143
+ {
34144
+ category: "audit_log",
34145
+ patterns: [
34146
+ "audit log",
34147
+ "audit",
34148
+ "log entry",
34149
+ "log entries",
34150
+ "what happened",
34151
+ "show me events",
34152
+ "event class"
34153
+ ].map(phrasePattern)
34154
+ },
34155
+ {
34156
+ category: "sentinel_findings",
34157
+ patterns: [
34158
+ "sentinel",
34159
+ "sentinels",
34160
+ "warning",
34161
+ "warnings",
34162
+ "alert",
34163
+ "alerts",
34164
+ "whats wrong",
34165
+ "what's wrong",
34166
+ "findings"
34167
+ ].map(phrasePattern)
34168
+ },
34169
+ {
34170
+ category: "anomaly_alerts",
34171
+ patterns: [
34172
+ "anomaly",
34173
+ "anomalies",
34174
+ "spike",
34175
+ "unusual",
34176
+ "outlier"
34177
+ ].map(phrasePattern)
34178
+ },
34179
+ {
34180
+ category: "recent_receipts",
34181
+ patterns: [
34182
+ "receipt",
34183
+ "receipts",
34184
+ "concordia",
34185
+ "commitment",
34186
+ "commitments",
34187
+ "chain",
34188
+ "chains"
34189
+ ].map(phrasePattern)
34190
+ },
34191
+ {
34192
+ category: "verascore_deltas",
34193
+ patterns: [
34194
+ "verascore",
34195
+ "vera score",
34196
+ "trust score",
34197
+ "reputation"
34198
+ ].map(phrasePattern)
34199
+ }
34200
+ ];
34201
+ TRIVIAL_GREETINGS = /* @__PURE__ */ new Set([
34202
+ "hi",
34203
+ "hello",
34204
+ "hey",
34205
+ "yo",
34206
+ "ok",
34207
+ "thanks",
34208
+ "thx",
34209
+ "thank you"
34210
+ ]);
34211
+ CATEGORY_LABELS = {
34212
+ templates: "Templates",
34213
+ agent_state: "Agent state",
34214
+ agent_activity: "Agent activity",
34215
+ audit_log: "Audit log",
34216
+ sentinel_findings: "Sentinel findings",
34217
+ anomaly_alerts: "Anomaly alerts",
34218
+ recent_receipts: "Recent receipts",
34219
+ verascore_deltas: "Verascore deltas"
34220
+ };
34221
+ }
34222
+ });
34223
+
34224
+ // src/composition/constants.ts
34225
+ var COMPOSITION_EVENT_TYPES;
34226
+ var init_constants4 = __esm({
34227
+ "src/composition/constants.ts"() {
34228
+ init_constants();
34229
+ COMPOSITION_EVENT_TYPES = [
34230
+ "composition_receipt_packed",
34231
+ "composition_receipt_verified",
34232
+ "composition_mandate_verified",
34233
+ "composition_verascore_published",
34234
+ "composition_sidecar_spawned",
34235
+ "composition_sidecar_crashed",
34236
+ "composition_sidecar_recovered",
34237
+ "composition_degraded",
34238
+ "composition_recovered"
34239
+ ];
34240
+ }
34241
+ });
34242
+
34243
+ // src/chat/concierge-query-grammar.ts
34244
+ function resolveTimeRange(query, now) {
34245
+ const normalized = query.trim();
34246
+ const lower = normalized.toLowerCase();
34247
+ const fromTo = lower.match(
34248
+ /\b(?:from|between)\s+(.+?)\s+(?:to|and|-|until)\s+([\w:.\-+t /]+)/i
34249
+ );
34250
+ if (fromTo) {
34251
+ const aSlice = fromTo[1];
34252
+ const bSlice = fromTo[2];
34253
+ if (aSlice !== void 0 && bSlice !== void 0) {
34254
+ const a = parseInstant(aSlice, now);
34255
+ const b = parseInstant(bSlice, now);
34256
+ if (a && b) {
34257
+ const start = a.getTime() <= b.getTime() ? a : b;
34258
+ const end = a.getTime() <= b.getTime() ? b : a;
34259
+ return {
34260
+ range: { start, end },
34261
+ matchedSubstring: fromTo[0]
34262
+ };
34263
+ }
34264
+ }
34265
+ }
34266
+ const sinceMatch = lower.match(/\bsince\s+([\w:.\-+t /]+)/i);
34267
+ if (sinceMatch) {
34268
+ const slice = sinceMatch[1];
34269
+ if (slice !== void 0) {
34270
+ const start = parseInstant(slice, now);
34271
+ if (start) {
34272
+ return {
34273
+ range: { start, end: now },
34274
+ matchedSubstring: sinceMatch[0]
34275
+ };
34276
+ }
34277
+ }
34278
+ }
34279
+ if (/\byesterday\b/.test(lower)) {
34280
+ const startOfToday = startOfDay(now);
34281
+ const start = new Date(startOfToday.getTime() - MS_PER_DAY);
34282
+ const end = new Date(startOfToday.getTime() - 1);
34283
+ return {
34284
+ range: { start, end, relative_label: "yesterday" },
34285
+ matchedSubstring: "yesterday"
34286
+ };
34287
+ }
34288
+ if (/\btoday\b/.test(lower)) {
34289
+ return {
34290
+ range: {
34291
+ start: startOfDay(now),
34292
+ end: now,
34293
+ relative_label: "today"
34294
+ },
34295
+ matchedSubstring: "today"
34296
+ };
34297
+ }
34298
+ const compactHours = lower.match(/\blast\s+(\d+)\s*h\b/i);
34299
+ if (compactHours) {
34300
+ const tok = compactHours[1];
34301
+ if (tok !== void 0) {
34302
+ const n = Number.parseInt(tok, 10);
34303
+ if (Number.isFinite(n) && n > 0) {
34304
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34305
+ return {
34306
+ range: { start, end: now, relative_label: `last ${n}h` },
34307
+ matchedSubstring: compactHours[0]
34308
+ };
34309
+ }
34310
+ }
34311
+ }
34312
+ const hoursMatch = lower.match(
34313
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:hr\b|hrs\b|hour|hours)/i
34314
+ );
34315
+ if (hoursMatch) {
34316
+ const tok = hoursMatch[1];
34317
+ if (tok !== void 0) {
34318
+ const n = parseCount(tok);
34319
+ if (n !== null && n > 0) {
34320
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34321
+ return {
34322
+ range: { start, end: now, relative_label: `past ${n} hour${n === 1 ? "" : "s"}` },
34323
+ matchedSubstring: hoursMatch[0]
34324
+ };
34325
+ }
34326
+ }
34327
+ }
34328
+ if (/\b(?:past|last)\s+hour\b/.test(lower)) {
34329
+ const start = new Date(now.getTime() - MS_PER_HOUR);
34330
+ return {
34331
+ range: { start, end: now, relative_label: "past hour" },
34332
+ matchedSubstring: lower.match(/\b(?:past|last)\s+hour\b/i)[0]
34333
+ };
34334
+ }
34335
+ const daysMatch = lower.match(
34336
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:d\b|day|days)/i
34337
+ );
34338
+ if (daysMatch) {
34339
+ const tok = daysMatch[1];
34340
+ if (tok !== void 0) {
34341
+ const n = parseCount(tok);
34342
+ if (n !== null && n > 0) {
34343
+ const start = new Date(now.getTime() - n * MS_PER_DAY);
34344
+ return {
34345
+ range: { start, end: now, relative_label: `past ${n} day${n === 1 ? "" : "s"}` },
34346
+ matchedSubstring: daysMatch[0]
34347
+ };
34348
+ }
34349
+ }
34350
+ }
34351
+ if (/\b(?:past|last)\s+day\b/.test(lower)) {
34352
+ const start = new Date(now.getTime() - MS_PER_DAY);
34353
+ return {
34354
+ range: { start, end: now, relative_label: "past day" },
34355
+ matchedSubstring: lower.match(/\b(?:past|last)\s+day\b/i)[0]
34356
+ };
34357
+ }
34358
+ if (/\bthis\s+week\b/.test(lower)) {
34359
+ const start = startOfWeek(now);
34360
+ return {
34361
+ range: { start, end: now, relative_label: "this week" },
34362
+ matchedSubstring: lower.match(/\bthis\s+week\b/i)[0]
34363
+ };
34364
+ }
34365
+ if (/\b(?:past|last)\s+week\b/.test(lower)) {
34366
+ const start = new Date(now.getTime() - 7 * MS_PER_DAY);
34367
+ return {
34368
+ range: { start, end: now, relative_label: "past week" },
34369
+ matchedSubstring: lower.match(/\b(?:past|last)\s+week\b/i)[0]
34370
+ };
34371
+ }
34372
+ const isoMatch = normalized.match(
34373
+ /\b(\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:\d{2})?)?)\b/
34374
+ );
34375
+ if (isoMatch) {
34376
+ const tok = isoMatch[1];
34377
+ if (tok !== void 0) {
34378
+ const parsed = parseInstant(tok, now);
34379
+ if (parsed) {
34380
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(tok);
34381
+ if (isDateOnly) {
34382
+ return {
34383
+ range: {
34384
+ start: parsed,
34385
+ end: new Date(parsed.getTime() + MS_PER_DAY - 1)
34386
+ },
34387
+ matchedSubstring: tok
34388
+ };
34389
+ }
34390
+ return {
34391
+ range: {
34392
+ start: new Date(parsed.getTime() - 30 * 60 * 1e3),
34393
+ end: new Date(parsed.getTime() + 30 * 60 * 1e3)
34394
+ },
34395
+ matchedSubstring: tok
34396
+ };
34397
+ }
34398
+ }
34399
+ }
34400
+ return null;
34401
+ }
34402
+ function parseInstant(token, now) {
34403
+ const trimmed = token.trim().replace(/[,.!?;]+$/g, "");
34404
+ if (!trimmed) return null;
34405
+ const lower = trimmed.toLowerCase();
34406
+ if (lower === "now") return now;
34407
+ if (lower === "today") return startOfDay(now);
34408
+ if (lower === "yesterday") {
34409
+ return new Date(startOfDay(now).getTime() - MS_PER_DAY);
34410
+ }
34411
+ const isoLike = trimmed.replace(" ", "T");
34412
+ const parsed = new Date(isoLike);
34413
+ if (!Number.isNaN(parsed.getTime())) return parsed;
34414
+ return null;
34415
+ }
34416
+ function parseCount(token) {
34417
+ const lower = token.toLowerCase();
34418
+ if (/^\d+$/.test(lower)) {
34419
+ const n = Number.parseInt(lower, 10);
34420
+ return Number.isFinite(n) ? n : null;
34421
+ }
34422
+ return NUMBER_WORDS[lower] ?? null;
34423
+ }
34424
+ function startOfDay(d) {
34425
+ const out = new Date(d);
34426
+ out.setHours(0, 0, 0, 0);
34427
+ return out;
34428
+ }
34429
+ function startOfWeek(d) {
34430
+ const out = startOfDay(d);
34431
+ const dayOfWeek = out.getDay();
34432
+ const offsetToMonday = (dayOfWeek + 6) % 7;
34433
+ out.setDate(out.getDate() - offsetToMonday);
34434
+ return out;
34435
+ }
34436
+ function listFromRegistry(registry) {
34437
+ if (!registry) return [];
34438
+ if (Array.isArray(registry)) return registry;
34439
+ if (typeof registry.list === "function") {
34440
+ return registry.list();
34441
+ }
34442
+ return [];
34443
+ }
34444
+ function extractAgentNames(query, registry) {
34445
+ const records = listFromRegistry(registry);
34446
+ if (records.length === 0) return { matched: [], flagged: false };
34447
+ const lowerQuery = query.toLowerCase();
34448
+ const compactQuery = lowerQuery.replace(/[\s_-]+/g, "");
34449
+ const matched = [];
34450
+ const seen = /* @__PURE__ */ new Set();
34451
+ for (const rec of records) {
34452
+ const id = rec.agent_id;
34453
+ if (!id || seen.has(id)) continue;
34454
+ const idLower = id.toLowerCase();
34455
+ if (idLower.length < 3) continue;
34456
+ const idCompact = idLower.replace(/[\s_-]+/g, "");
34457
+ const wordRe = new RegExp(
34458
+ `\\b${idLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
34459
+ "i"
34460
+ );
34461
+ if (wordRe.test(query)) {
34462
+ matched.push(id);
34463
+ seen.add(id);
34464
+ continue;
34465
+ }
34466
+ if (idCompact.length >= 4 && compactQuery.includes(idCompact)) {
34467
+ matched.push(id);
34468
+ seen.add(id);
34469
+ }
34470
+ }
34471
+ const agentMention = lowerQuery.match(/\bagent\s+([a-z0-9_-]{3,40})/i);
34472
+ const flagged = matched.length === 0 && agentMention !== null && agentMention[1] !== void 0 && !records.some((r) => r.agent_id.toLowerCase() === agentMention[1]?.toLowerCase());
34473
+ return { matched, flagged };
34474
+ }
34475
+ function escapeRegex(s) {
34476
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34477
+ }
34478
+ function extractEventTypes(query, enumValues) {
34479
+ const lower = query.toLowerCase();
34480
+ const matched = [];
34481
+ const seen = /* @__PURE__ */ new Set();
34482
+ for (const ev of enumValues) {
34483
+ if (seen.has(ev)) continue;
34484
+ const re = new RegExp(`\\b${escapeRegex(ev)}\\b`, "i");
34485
+ if (re.test(query)) {
34486
+ matched.push(ev);
34487
+ seen.add(ev);
34488
+ }
34489
+ }
34490
+ for (const syn of EVENT_SYNONYMS) {
34491
+ const re = new RegExp(
34492
+ `\\b${syn.phrase.split(/\s+/).map(escapeRegex).join("\\s+")}\\b`,
34493
+ "i"
34494
+ );
34495
+ if (re.test(query)) {
34496
+ for (const c of syn.canonical) {
34497
+ if (seen.has(c)) continue;
34498
+ if (!enumValues.includes(c)) continue;
34499
+ matched.push(c);
34500
+ seen.add(c);
34501
+ }
34502
+ }
34503
+ }
34504
+ const globMatches = lower.match(/\b([a-z_]+)_\*/g) ?? [];
34505
+ for (const glob of globMatches) {
34506
+ const prefix = glob.slice(0, -2);
34507
+ for (const ev of enumValues) {
34508
+ if (seen.has(ev)) continue;
34509
+ if (ev.startsWith(prefix)) {
34510
+ matched.push(ev);
34511
+ seen.add(ev);
34512
+ }
34513
+ }
34514
+ }
34515
+ const eventNounMention = /\b(?:event|events|class|classes)\b/i.test(query) && matched.length === 0;
34516
+ return { matched, flagged: eventNounMention };
34517
+ }
34518
+ function deriveIntentPhrase(query, stripTokens) {
34519
+ let out = query;
34520
+ for (const tok of stripTokens) {
34521
+ if (!tok) continue;
34522
+ const re = new RegExp(escapeRegex(tok), "gi");
34523
+ out = out.replace(re, " ");
34524
+ }
34525
+ return out.replace(/\s+/g, " ").trim();
34526
+ }
34527
+ function computeConfidence(parsed) {
34528
+ const dims = [
34529
+ { present: parsed.hasTimeMention, resolved: parsed.timeResolved },
34530
+ { present: parsed.hasAgentMention, resolved: parsed.agentResolved },
34531
+ { present: parsed.hasEventMention, resolved: parsed.eventResolved }
34532
+ ];
34533
+ const present = dims.filter((d) => d.present);
34534
+ let base;
34535
+ if (present.length === 0) {
34536
+ base = parsed.intentEmpty ? 0 : 0.3;
34537
+ } else {
34538
+ const resolved = present.filter((d) => d.resolved).length;
34539
+ base = resolved / present.length;
34540
+ }
34541
+ const adjusted = base - 0.15 * parsed.ambiguityCount;
34542
+ if (adjusted < 0) return 0;
34543
+ if (adjusted > 1) return 1;
34544
+ return adjusted;
34545
+ }
34546
+ function parseQuery(query, opts) {
34547
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
34548
+ const enumValues = opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES;
34549
+ const original = query ?? "";
34550
+ const trimmed = original.trim();
34551
+ if (trimmed.length === 0) {
34552
+ return {
34553
+ time_range: null,
34554
+ agent_names: [],
34555
+ event_types: [],
34556
+ intent_phrase: "",
34557
+ ambiguity_flags: ["no_signal_extracted"],
34558
+ parse_confidence: 0
34559
+ };
34560
+ }
34561
+ const ambiguity_flags = /* @__PURE__ */ new Set();
34562
+ const timeMatch = resolveTimeRange(trimmed, now);
34563
+ const hasTimeMention = TIME_MENTION_PROBE.test(trimmed);
34564
+ if (hasTimeMention && !timeMatch) {
34565
+ ambiguity_flags.add("unknown_time_token");
34566
+ }
34567
+ const agentResult = extractAgentNames(trimmed, opts?.registry);
34568
+ if (agentResult.flagged) {
34569
+ ambiguity_flags.add("unknown_agent_token");
34570
+ }
34571
+ const hasAgentMention = AGENT_MENTION_PROBE.test(trimmed);
34572
+ const eventResult = extractEventTypes(trimmed, enumValues);
34573
+ const hasEventMention = EVENT_MENTION_PROBE.test(trimmed);
34574
+ if (eventResult.flagged) {
34575
+ ambiguity_flags.add("unknown_event_token");
34576
+ }
34577
+ const stripTokens = [];
34578
+ if (timeMatch) stripTokens.push(timeMatch.matchedSubstring);
34579
+ for (const name of agentResult.matched) stripTokens.push(name);
34580
+ for (const ev of eventResult.matched) {
34581
+ if (trimmed.toLowerCase().includes(ev.toLowerCase())) {
34582
+ stripTokens.push(ev);
34583
+ }
34584
+ }
34585
+ const intent_phrase = deriveIntentPhrase(trimmed, stripTokens);
34586
+ const parse_confidence = computeConfidence({
34587
+ hasTimeMention,
34588
+ timeResolved: timeMatch !== null,
34589
+ hasAgentMention,
34590
+ agentResolved: agentResult.matched.length > 0,
34591
+ hasEventMention,
34592
+ eventResolved: eventResult.matched.length > 0,
34593
+ intentEmpty: intent_phrase.length === 0,
34594
+ ambiguityCount: ambiguity_flags.size
34595
+ });
34596
+ if (timeMatch === null && agentResult.matched.length === 0 && eventResult.matched.length === 0 && intent_phrase.length === 0) {
34597
+ ambiguity_flags.add("no_signal_extracted");
34598
+ }
34599
+ return {
34600
+ time_range: timeMatch ? timeMatch.range : null,
34601
+ agent_names: agentResult.matched,
34602
+ event_types: eventResult.matched,
34603
+ intent_phrase,
34604
+ ambiguity_flags: Array.from(ambiguity_flags),
34605
+ parse_confidence
34606
+ };
34607
+ }
34608
+ function isLowConfidence(parsed) {
34609
+ return parsed.parse_confidence < LLM_ASSIST_THRESHOLD;
34610
+ }
34611
+ async function parseQueryWithLlmAssist(query, llmAssist, opts) {
34612
+ const parsed = parseQuery(query, opts);
34613
+ if (!llmAssist || !isLowConfidence(parsed)) return parsed;
34614
+ let completion;
34615
+ try {
34616
+ completion = await llmAssist(query, parsed);
34617
+ } catch {
34618
+ return parsed;
34619
+ }
34620
+ if (!completion || typeof completion !== "object") return parsed;
34621
+ const merged = { ...parsed };
34622
+ if (parsed.time_range === null && completion.time_range) {
34623
+ merged.time_range = completion.time_range;
34624
+ }
34625
+ if (parsed.agent_names.length === 0 && Array.isArray(completion.agent_names)) {
34626
+ merged.agent_names = completion.agent_names.filter(
34627
+ (s) => typeof s === "string" && s.length > 0
34628
+ );
34629
+ }
34630
+ if (parsed.event_types.length === 0 && Array.isArray(completion.event_types)) {
34631
+ const allowed = new Set(opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES);
34632
+ merged.event_types = completion.event_types.filter(
34633
+ (s) => typeof s === "string" && allowed.has(s)
34634
+ );
34635
+ }
34636
+ merged.parse_confidence = Math.max(
34637
+ parsed.parse_confidence,
34638
+ computeConfidence({
34639
+ hasTimeMention: TIME_MENTION_PROBE.test(query),
34640
+ timeResolved: merged.time_range !== null,
34641
+ hasAgentMention: AGENT_MENTION_PROBE.test(query),
34642
+ agentResolved: merged.agent_names.length > 0,
34643
+ hasEventMention: EVENT_MENTION_PROBE.test(query),
34644
+ eventResolved: merged.event_types.length > 0,
34645
+ intentEmpty: merged.intent_phrase.length === 0,
34646
+ ambiguityCount: merged.ambiguity_flags.length
34647
+ })
34648
+ );
34649
+ return merged;
34650
+ }
34651
+ function auditSafeSummary(parsed) {
34652
+ return {
34653
+ time_range: parsed.time_range ? {
34654
+ start_iso: parsed.time_range.start.toISOString(),
34655
+ end_iso: parsed.time_range.end.toISOString(),
34656
+ ...parsed.time_range.relative_label !== void 0 ? { relative_label: parsed.time_range.relative_label } : {}
34657
+ } : null,
34658
+ agent_names: [...parsed.agent_names],
34659
+ event_types: [...parsed.event_types],
34660
+ ambiguity_flags: [...parsed.ambiguity_flags],
34661
+ parse_confidence: parsed.parse_confidence
34662
+ };
34663
+ }
34664
+ 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;
34665
+ var init_concierge_query_grammar = __esm({
34666
+ "src/chat/concierge-query-grammar.ts"() {
34667
+ init_constants4();
34668
+ init_operator_chat_audit_events();
34669
+ CANONICAL_AUDIT_EVENT_CLASSES = [
34670
+ // Lifecycle / policy
34671
+ "policy_change",
34672
+ "approval_request",
34673
+ "audit_truncate",
34674
+ "lockdown",
34675
+ "unwrap",
34676
+ // Exit bundle (Tier 1)
34677
+ "exit_bundle_export",
34678
+ "exit_bundle_import_activate",
34679
+ "exit_bundle_rekey",
34680
+ // Cross-harness approval aggregator
34681
+ "cross_harness_approval_aggregated",
34682
+ "cross_harness_approval_resolved",
34683
+ "cross_harness_approval_deduped",
34684
+ "cross_harness_approval_payload_decrypted",
34685
+ "cross_harness_approval_audit_trail_viewed",
34686
+ "cross_harness_approval_replayed",
34687
+ // Composition (full set from constants.ts)
34688
+ ...COMPOSITION_EVENT_TYPES,
34689
+ // Operator chat / concierge (full set from OPERATOR_CHAT_OPS)
34690
+ OPERATOR_CHAT_OPS.CONCIERGE_CHAT,
34691
+ OPERATOR_CHAT_OPS.AGENT_INSPECT_PANEL_OPENED,
34692
+ OPERATOR_CHAT_OPS.CONCIERGE_HISTORY_READ,
34693
+ OPERATOR_CHAT_OPS.CONCIERGE_THREAD_DELETED,
34694
+ OPERATOR_CHAT_OPS.CONCIERGE_MEMORY_READ_FAILED,
34695
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
34696
+ // Bridge / commitment
34697
+ "bridge_commit",
34698
+ "bridge_verify",
34699
+ "bridge_attest",
34700
+ "proof_commitment",
34701
+ "proof_reveal",
34702
+ // Reputation
34703
+ "reputation_export",
34704
+ "reputation_import",
34705
+ "reputation_publish",
34706
+ "reputation_record",
34707
+ "reputation_query"
34708
+ ];
34709
+ EVENT_SYNONYMS = [
34710
+ { phrase: "approvals", canonical: ["approval_request", "cross_harness_approval_aggregated", "cross_harness_approval_resolved"] },
34711
+ { phrase: "approval", canonical: ["approval_request"] },
34712
+ { phrase: "policy changes", canonical: ["policy_change"] },
34713
+ { phrase: "policy change", canonical: ["policy_change"] },
34714
+ { phrase: "policy edits", canonical: ["policy_change"] },
34715
+ { phrase: "lockdowns", canonical: ["lockdown"] },
34716
+ { phrase: "exit bundles", canonical: ["exit_bundle_export", "exit_bundle_import_activate"] },
34717
+ { phrase: "exit bundle", canonical: ["exit_bundle_export"] },
34718
+ { phrase: "audit truncations", canonical: ["audit_truncate"] },
34719
+ { phrase: "audit truncation", canonical: ["audit_truncate"] },
34720
+ { phrase: "compositions", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34721
+ { phrase: "receipts", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34722
+ { phrase: "receipt verifications", canonical: ["composition_receipt_verified"] },
34723
+ { phrase: "concierge chats", canonical: [OPERATOR_CHAT_OPS.CONCIERGE_CHAT] },
34724
+ { phrase: "cross harness approvals", canonical: ["cross_harness_approval_aggregated", "cross_harness_approval_resolved"] }
34725
+ ];
34726
+ MS_PER_HOUR = 60 * 60 * 1e3;
34727
+ MS_PER_DAY = 24 * MS_PER_HOUR;
34728
+ NUMBER_WORDS = {
34729
+ a: 1,
34730
+ an: 1,
34731
+ one: 1,
34732
+ two: 2,
34733
+ three: 3,
34734
+ four: 4,
34735
+ five: 5,
34736
+ six: 6,
34737
+ seven: 7,
34738
+ eight: 8,
34739
+ nine: 9,
34740
+ ten: 10,
34741
+ twelve: 12,
34742
+ twentyfour: 24
34743
+ };
34744
+ 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;
34745
+ AGENT_MENTION_PROBE = /\bagent[s]?\b/i;
34746
+ EVENT_MENTION_PROBE = /\b(event|events|class|classes|approvals?|policy)\b/i;
34747
+ LLM_ASSIST_THRESHOLD = 0.5;
34748
+ }
34749
+ });
34750
+ function approxTokenLen2(text) {
33421
34751
  return Math.ceil(text.length / 4);
33422
34752
  }
33423
34753
  function makeEventId(prefix) {
33424
34754
  return `${prefix}-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
33425
34755
  }
34756
+ function classifyFetcherError(error) {
34757
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
34758
+ if (msg.includes("timeout") || msg.includes("timed out")) return "timeout";
34759
+ if (msg.includes("schema") || msg.includes("invalid shape")) {
34760
+ return "schema_mismatch";
34761
+ }
34762
+ if (msg.includes("io") || msg.includes("read") || msg.includes("enoent") || msg.includes("eacces")) {
34763
+ return "io_failed";
34764
+ }
34765
+ return "unknown";
34766
+ }
33426
34767
  function formatPriorTurnLine(turn) {
33427
34768
  const label = turn.role === "user" ? "OPERATOR" : "CONCIERGE";
33428
34769
  return `${label}: ${turn.content}`;
@@ -33430,18 +34771,21 @@ function formatPriorTurnLine(turn) {
33430
34771
  function hashOf(input) {
33431
34772
  return hashToString(sha256.sha256(stringToBytes(input)));
33432
34773
  }
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;
34774
+ 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
34775
  var init_operator_chat_service = __esm({
33435
34776
  "src/chat/operator-chat-service.ts"() {
33436
34777
  init_hashing();
33437
34778
  init_encoding();
33438
34779
  init_operator_chat_audit_events();
33439
34780
  init_operator_chat_types();
34781
+ init_concierge_context_router();
34782
+ init_concierge_query_grammar();
33440
34783
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
33441
34784
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
33442
34785
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
33443
34786
  DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET = 500;
33444
34787
  DEFAULT_CONCIERGE_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
34788
+ DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET = 2e3;
33445
34789
  SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
33446
34790
  1. Castle Wall: OS-boundary egress filter enforced at the kernel level. Blocks unauthorized outbound calls even from prompt-injected agents.
33447
34791
  2. Sentinels: internal observation via process introspection. Surfaces anomalies to the operator; does not enforce.
@@ -33483,6 +34827,11 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33483
34827
  historyTokenBudget;
33484
34828
  sessionTtlMs;
33485
34829
  clock;
34830
+ contextFetchers;
34831
+ contextLlmAssist;
34832
+ dynamicContextBudget;
34833
+ agentRegistry;
34834
+ grammarLlmAssist;
33486
34835
  /**
33487
34836
  * In-memory thread_id assigned to the active concierge session.
33488
34837
  * The first sendConcierge call after construction allocates a fresh
@@ -33514,6 +34863,19 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33514
34863
  this.historyTokenBudget = deps.conciergeHistoryTokenBudget !== void 0 && deps.conciergeHistoryTokenBudget > 0 ? deps.conciergeHistoryTokenBudget : DEFAULT_CONCIERGE_HISTORY_TOKEN_BUDGET;
33515
34864
  this.sessionTtlMs = deps.conciergeSessionTtlMs !== void 0 && deps.conciergeSessionTtlMs > 0 ? deps.conciergeSessionTtlMs : DEFAULT_CONCIERGE_SESSION_TTL_MS;
33516
34865
  this.clock = deps.conciergeClock ?? (() => Date.now());
34866
+ if (deps.conciergeContextFetchers) {
34867
+ this.contextFetchers = deps.conciergeContextFetchers;
34868
+ }
34869
+ if (deps.conciergeContextLlmAssist) {
34870
+ this.contextLlmAssist = deps.conciergeContextLlmAssist;
34871
+ }
34872
+ this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
34873
+ if (deps.conciergeAgentRegistry) {
34874
+ this.agentRegistry = deps.conciergeAgentRegistry;
34875
+ }
34876
+ if (deps.conciergeGrammarLlmAssist) {
34877
+ this.grammarLlmAssist = deps.conciergeGrammarLlmAssist;
34878
+ }
33517
34879
  }
33518
34880
  // ── Concierge ─────────────────────────────────────────────────────────
33519
34881
  /**
@@ -33572,11 +34934,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33572
34934
  await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
33573
34935
  });
33574
34936
  }
34937
+ const parsedGrammar = await this.runGrammarParse(filterResult.filtered);
33575
34938
  const start = Date.now();
33576
34939
  let conciergeBody;
33577
34940
  let servedBy = "disabled";
33578
34941
  let displayLabel = "Concierge: substrate not configured";
33579
34942
  let outcome = "substrate_disabled";
34943
+ let dynamicCategoriesIncluded = [];
33580
34944
  if (!this.substrateSelector) {
33581
34945
  conciergeBody = "Concierge unavailable. The substrate selector is not configured for this fortress. Pick a substrate in the Policy center to enable concierge replies.";
33582
34946
  } else {
@@ -33588,7 +34952,15 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33588
34952
  conciergeBody = "Concierge unavailable. The chosen substrate does not support summarization. Pick a different substrate in the Policy center.";
33589
34953
  outcome = "substrate_disabled";
33590
34954
  } else {
33591
- const context = await this.assembleConciergeContext(priorTurns);
34955
+ const dynamicResult = await this.runDynamicContextFold(
34956
+ filterResult.filtered,
34957
+ parsedGrammar
34958
+ );
34959
+ dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34960
+ const context = await this.assembleConciergeContext(
34961
+ priorTurns,
34962
+ dynamicResult.section
34963
+ );
33592
34964
  const response = await this.substrateSelector.invokeSummarize(
33593
34965
  "concierge",
33594
34966
  {
@@ -33652,7 +35024,9 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33652
35024
  ...assistantTurnId !== void 0 ? { turn_index: assistantTurnId } : {},
33653
35025
  ...this.memory ? {
33654
35026
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
33655
- } : {}
35027
+ } : {},
35028
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {},
35029
+ parsed_grammar: auditSafeSummary(parsedGrammar)
33656
35030
  };
33657
35031
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
33658
35032
  return {
@@ -33803,10 +35177,13 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33803
35177
  * ## Sanctuary reference
33804
35178
  * <static domain reference block>
33805
35179
  *
35180
+ * ## Live fortress context ← WP-V1.3-9 Tau-3, when present
35181
+ * ### <Category>
35182
+ * <fetcher payload>
35183
+ *
33806
35184
  * ## Prior conversation ← WP-V1.3-9 Tau-2, when present
33807
35185
  * OPERATOR: ...
33808
35186
  * CONCIERGE: ...
33809
- * ---
33810
35187
  *
33811
35188
  * ## Recent activity
33812
35189
  * <recentActivity output>
@@ -33825,13 +35202,14 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
33825
35202
  * if available; the v1.2 selector does not expose one, so structured
33826
35203
  * serialization is the canonical path for v1.3.
33827
35204
  */
33828
- async assembleConciergeContext(priorTurns = []) {
35205
+ async assembleConciergeContext(priorTurns = [], dynamicSection = "") {
33829
35206
  const ref = `## Sanctuary reference
33830
35207
  ${SANCTUARY_DOMAIN_REFERENCE}`;
33831
35208
  const priorSection = this.formatPriorTurnsSection(priorTurns);
33832
35209
  if (!this.contextProviders) {
33833
35210
  return [
33834
35211
  ref,
35212
+ ...dynamicSection ? [dynamicSection] : [],
33835
35213
  ...priorSection ? [priorSection] : [],
33836
35214
  "## Recent activity\n(no providers wired)",
33837
35215
  "## Wrapped agents\n(no providers wired)",
@@ -33845,6 +35223,7 @@ ${SANCTUARY_DOMAIN_REFERENCE}`;
33845
35223
  ]);
33846
35224
  return [
33847
35225
  ref,
35226
+ ...dynamicSection ? [dynamicSection] : [],
33848
35227
  ...priorSection ? [priorSection] : [],
33849
35228
  `## Recent activity
33850
35229
  ${activity}`,
@@ -33854,6 +35233,69 @@ ${agents}`,
33854
35233
  ${inbox}`
33855
35234
  ].join("\n\n");
33856
35235
  }
35236
+ /**
35237
+ * Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
35238
+ * trip. Fail-soft on every axis: missing fetchers short-circuit to
35239
+ * an empty fold, fetcher failures emit a per-category audit event
35240
+ * and are omitted from the rendered section, an LLM-assist failure
35241
+ * proceeds with no fold. Returns the rendered section + the list of
35242
+ * categories whose data made it into the section (used for the
35243
+ * round-trip audit emission).
35244
+ *
35245
+ * Tau-4: receives the pre-parsed `ParsedQuery` and forwards it as the
35246
+ * `parsed` opt to `foldContext`, so fetchers see the structured
35247
+ * `FetcherHints` derived from it.
35248
+ */
35249
+ async runDynamicContextFold(query, parsedGrammar) {
35250
+ if (!this.contextFetchers) {
35251
+ return { section: "", categoriesIncluded: [] };
35252
+ }
35253
+ const result = await foldContext(query, this.contextFetchers, {
35254
+ maxTokens: this.dynamicContextBudget,
35255
+ ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
35256
+ onFetcherFailure: (category, error) => {
35257
+ this.emitContextFetcherFailed(category, classifyFetcherError(error));
35258
+ },
35259
+ parsed: parsedGrammar
35260
+ });
35261
+ return result;
35262
+ }
35263
+ /**
35264
+ * WP-V1.3-9 Tau-4: parse the (PII-filtered) operator query into a
35265
+ * `ParsedQuery`. Routes through the LLM-assist completion hook when
35266
+ * configured and the rule-based parse is below
35267
+ * `LLM_ASSIST_THRESHOLD`. Always returns a parse object (never
35268
+ * throws) so the audit emission can carry the result unconditionally.
35269
+ */
35270
+ async runGrammarParse(query) {
35271
+ return parseQueryWithLlmAssist(query, this.grammarLlmAssist, {
35272
+ ...this.agentRegistry !== void 0 ? { registry: this.agentRegistry } : {},
35273
+ eventClassEnum: CANONICAL_AUDIT_EVENT_CLASSES
35274
+ });
35275
+ }
35276
+ /**
35277
+ * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
35278
+ * of the fold path so the dynamic-context handler stays readable.
35279
+ * Emits with `result: "failure"` since the named category dropped
35280
+ * from the rendered section for this round-trip.
35281
+ */
35282
+ emitContextFetcherFailed(category, failureReason) {
35283
+ const payload = {
35284
+ version: "1.2",
35285
+ event_id: makeEventId("conc-ctxfail"),
35286
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
35287
+ identity_id: this.identityId,
35288
+ kind: "operator_concierge_context_fetcher_failed",
35289
+ surface: "concierge",
35290
+ category,
35291
+ failure_reason: failureReason
35292
+ };
35293
+ this.emit(
35294
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
35295
+ payload,
35296
+ "failure"
35297
+ );
35298
+ }
33857
35299
  /**
33858
35300
  * Render the prior-conversation section with token-budget enforcement
33859
35301
  * (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
@@ -33864,14 +35306,14 @@ ${inbox}`
33864
35306
  if (turns.length === 0) return "";
33865
35307
  const HEADER = "## Prior conversation";
33866
35308
  const lines = turns.map(formatPriorTurnLine);
33867
- const headerTokens = approxTokenLen(`${HEADER}
35309
+ const headerTokens = approxTokenLen2(`${HEADER}
33868
35310
  `);
33869
- const sepTokens = approxTokenLen("\n");
35311
+ const sepTokens = approxTokenLen2("\n");
33870
35312
  let runningTokens = headerTokens;
33871
35313
  let runningLines = [];
33872
35314
  for (let i = lines.length - 1; i >= 0; i--) {
33873
35315
  const line = lines[i];
33874
- const tokens = approxTokenLen(line) + (runningLines.length > 0 ? sepTokens : 0);
35316
+ const tokens = approxTokenLen2(line) + (runningLines.length > 0 ? sepTokens : 0);
33875
35317
  if (runningTokens + tokens > this.historyTokenBudget) break;
33876
35318
  runningTokens += tokens;
33877
35319
  runningLines.push(line);
@@ -33899,7 +35341,7 @@ ${runningLines.join("\n")}`;
33899
35341
  function chatStorageKey(surface, threadKey) {
33900
35342
  return `${surface}.${threadKey}`;
33901
35343
  }
33902
- var OPERATOR_CHAT_NAMESPACE, HKDF_INFO, OperatorChatStore;
35344
+ var OPERATOR_CHAT_NAMESPACE, HKDF_INFO2, OperatorChatStore;
33903
35345
  var init_operator_chat_store = __esm({
33904
35346
  "src/chat/operator-chat-store.ts"() {
33905
35347
  init_encryption();
@@ -33907,13 +35349,13 @@ var init_operator_chat_store = __esm({
33907
35349
  init_encoding();
33908
35350
  init_operator_chat_types();
33909
35351
  OPERATOR_CHAT_NAMESPACE = "_chat";
33910
- HKDF_INFO = "operator-chat-store-v1";
35352
+ HKDF_INFO2 = "operator-chat-store-v1";
33911
35353
  OperatorChatStore = class {
33912
35354
  storage;
33913
35355
  encryptionKey;
33914
35356
  constructor(storage, masterKey) {
33915
35357
  this.storage = storage;
33916
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO);
35358
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
33917
35359
  }
33918
35360
  /**
33919
35361
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -33998,7 +35440,7 @@ var init_operator_chat_store = __esm({
33998
35440
  function bundleKey(threadId) {
33999
35441
  return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
34000
35442
  }
34001
- function stripKeyPrefix(key) {
35443
+ function stripKeyPrefix2(key) {
34002
35444
  if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
34003
35445
  return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
34004
35446
  }
@@ -34009,7 +35451,7 @@ function lastTurnId(bundle) {
34009
35451
  }
34010
35452
  return max;
34011
35453
  }
34012
- var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO2, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES2, ConciergeMemoryStore;
35454
+ var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO3, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
34013
35455
  var init_concierge_memory_store = __esm({
34014
35456
  "src/chat/concierge-memory-store.ts"() {
34015
35457
  init_encryption();
@@ -34017,9 +35459,9 @@ var init_concierge_memory_store = __esm({
34017
35459
  init_encoding();
34018
35460
  CONCIERGE_MEMORY_NAMESPACE = "_chat";
34019
35461
  CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
34020
- HKDF_INFO2 = "concierge-memory-store-v1";
35462
+ HKDF_INFO3 = "concierge-memory-store-v1";
34021
35463
  DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
34022
- MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
35464
+ MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
34023
35465
  ConciergeMemoryStore = class {
34024
35466
  storage;
34025
35467
  encryptionKey;
@@ -34028,7 +35470,7 @@ var init_concierge_memory_store = __esm({
34028
35470
  locks;
34029
35471
  constructor(opts) {
34030
35472
  this.storage = opts.storage;
34031
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
35473
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
34032
35474
  this.fortressId = opts.fortressId;
34033
35475
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
34034
35476
  this.locks = /* @__PURE__ */ new Map();
@@ -34107,7 +35549,7 @@ var init_concierge_memory_store = __esm({
34107
35549
  return { ok: false, reason: "io_failed" };
34108
35550
  }
34109
35551
  if (!raw) return { ok: true, turns: [] };
34110
- if (raw.length > MAX_BUNDLE_BYTES2) {
35552
+ if (raw.length > MAX_BUNDLE_BYTES3) {
34111
35553
  return { ok: false, reason: "oversize_bundle" };
34112
35554
  }
34113
35555
  let envelope;
@@ -34154,7 +35596,7 @@ var init_concierge_memory_store = __esm({
34154
35596
  );
34155
35597
  const summaries = [];
34156
35598
  for (const meta of entries) {
34157
- const threadId = stripKeyPrefix(meta.key);
35599
+ const threadId = stripKeyPrefix2(meta.key);
34158
35600
  if (threadId === null) continue;
34159
35601
  const bundle = await this.loadBundle(threadId);
34160
35602
  if (!bundle || bundle.turns.length === 0) continue;
@@ -34207,7 +35649,7 @@ var init_concierge_memory_store = __esm({
34207
35649
  );
34208
35650
  let pruned = 0;
34209
35651
  for (const meta of entries) {
34210
- const threadId = stripKeyPrefix(meta.key);
35652
+ const threadId = stripKeyPrefix2(meta.key);
34211
35653
  if (threadId === null) continue;
34212
35654
  pruned += await this.withLock(threadId, async () => {
34213
35655
  const bundle = await this.loadBundle(threadId);
@@ -34238,7 +35680,7 @@ var init_concierge_memory_store = __esm({
34238
35680
  return null;
34239
35681
  }
34240
35682
  if (!raw) return null;
34241
- if (raw.length > MAX_BUNDLE_BYTES2) return null;
35683
+ if (raw.length > MAX_BUNDLE_BYTES3) return null;
34242
35684
  try {
34243
35685
  const envelope = JSON.parse(bytesToString(raw));
34244
35686
  const aad = stringToBytes(threadId);
@@ -34329,7 +35771,18 @@ function buildV11Bindings(inputs) {
34329
35771
  registry
34330
35772
  }),
34331
35773
  conciergePiiFilter: buildConciergePiiFilter(),
34332
- conciergeMemory
35774
+ conciergeMemory,
35775
+ conciergeContextFetchers: buildConciergeContextFetchers({
35776
+ auditLog: inputs.auditLog,
35777
+ identityId: inputs.identityId,
35778
+ registry
35779
+ }),
35780
+ ...inputs.intelligenceSelector ? {
35781
+ conciergeContextLlmAssist: buildConciergeContextLlmAssist({
35782
+ selector: inputs.intelligenceSelector,
35783
+ identityId: inputs.identityId
35784
+ })
35785
+ } : {}
34333
35786
  });
34334
35787
  }
34335
35788
  const hubService = new HubService({
@@ -34390,6 +35843,107 @@ function buildConciergeContextProviders(args) {
34390
35843
  }
34391
35844
  };
34392
35845
  }
35846
+ function buildConciergeContextFetchers(args) {
35847
+ const empty = async () => "";
35848
+ return {
35849
+ templates: async () => {
35850
+ const entries = listTemplates();
35851
+ if (entries.length === 0) return "(no templates installed)";
35852
+ const lines = entries.map((e) => {
35853
+ const m = e.metadata;
35854
+ return `${m.name} (tier ${m.tier}, channel ${m.channel}, target ${m.target_archetype})`;
35855
+ });
35856
+ return lines.join("\n");
35857
+ },
35858
+ agent_state: async (agentNameHint) => {
35859
+ const records = args.registry.list({ identity_id: args.identityId });
35860
+ if (records.length === 0) return "(no wrapped agents)";
35861
+ const filtered = agentNameHint ? records.filter(
35862
+ (r) => r.agent_id.toLowerCase().includes(agentNameHint.toLowerCase()) || r.harness.toLowerCase().includes(agentNameHint.toLowerCase())
35863
+ ) : records;
35864
+ const target = filtered.length > 0 ? filtered : records;
35865
+ const lines = target.slice(0, 20).map((r) => {
35866
+ const tmpl = typeof r.channel_template_id === "string" ? r.channel_template_id : "no_template";
35867
+ return `${r.agent_id} harness=${r.harness} status=${r.status} template=${tmpl}`;
35868
+ });
35869
+ return lines.join("\n");
35870
+ },
35871
+ agent_activity: async (agentNameHint) => {
35872
+ const result = await args.auditLog.query({ limit: 50 });
35873
+ const owned = result.entries.filter(
35874
+ (e) => e.identity_id === args.identityId
35875
+ );
35876
+ const filtered = agentNameHint ? owned.filter((e) => {
35877
+ const agentId = e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : "";
35878
+ return agentId.toLowerCase().includes(agentNameHint.toLowerCase());
35879
+ }) : owned;
35880
+ const tail = (filtered.length > 0 ? filtered : owned).slice(-20);
35881
+ if (tail.length === 0) return "(no activity)";
35882
+ return tail.map((e) => {
35883
+ const agentId = (e.details && typeof e.details["agent_id"] === "string" ? e.details["agent_id"] : null) ?? "_fortress";
35884
+ return `${e.timestamp} ${e.layer}.${e.operation} agent=${agentId} result=${e.result}`;
35885
+ }).join("\n");
35886
+ },
35887
+ audit_log: async () => {
35888
+ const result = await args.auditLog.query({ limit: 30 });
35889
+ const owned = result.entries.filter(
35890
+ (e) => e.identity_id === args.identityId
35891
+ );
35892
+ if (owned.length === 0) return "(no audit log entries)";
35893
+ return owned.slice(-30).map(
35894
+ (e) => `${e.timestamp} ${e.layer}.${e.operation} result=${e.result}`
35895
+ ).join("\n");
35896
+ },
35897
+ sentinel_findings: empty,
35898
+ anomaly_alerts: empty,
35899
+ recent_receipts: async () => {
35900
+ const result = await args.auditLog.query({ limit: 100 });
35901
+ const owned = result.entries.filter(
35902
+ (e) => e.identity_id === args.identityId && e.operation.startsWith("composition_")
35903
+ );
35904
+ if (owned.length === 0) return "(no recent composition events)";
35905
+ return owned.slice(-15).map((e) => `${e.timestamp} ${e.operation} result=${e.result}`).join("\n");
35906
+ },
35907
+ verascore_deltas: empty
35908
+ };
35909
+ }
35910
+ function buildConciergeContextLlmAssist(args) {
35911
+ return async (query, categories) => {
35912
+ const labelList = categories.map((c) => `- ${c}`).join("\n");
35913
+ const prompt2 = `You are a router. Classify the operator's query into one of the categories below or "none".
35914
+ Reply with exactly one token: one category name or "none".
35915
+
35916
+ Categories:
35917
+ ${labelList}
35918
+
35919
+ Query: ${query}
35920
+
35921
+ Category:`;
35922
+ try {
35923
+ const handle = await args.selector.getSubstrate("concierge");
35924
+ if (!handle.capability.summarize) return "none";
35925
+ const response = await args.selector.invokeSummarize("concierge", {
35926
+ kind: "summarize",
35927
+ context: prompt2,
35928
+ query: "Output the single category token.",
35929
+ maxTokens: 16
35930
+ });
35931
+ if (response.failureClass || response.body.kind !== "summarize") {
35932
+ return "none";
35933
+ }
35934
+ const raw = response.body.text.trim().toLowerCase();
35935
+ const head = raw.split(/\s|[.,!?:;]/)[0] ?? "";
35936
+ const normalized = head.replace(/[^a-z_]/g, "");
35937
+ const known = categories;
35938
+ if (known.includes(normalized)) {
35939
+ return normalized;
35940
+ }
35941
+ return "none";
35942
+ } catch {
35943
+ return "none";
35944
+ }
35945
+ };
35946
+ }
34393
35947
  function buildConciergePiiFilter() {
34394
35948
  return {
34395
35949
  filter(input) {
@@ -34417,6 +35971,7 @@ var init_wiring = __esm({
34417
35971
  init_agent_registry_persistence();
34418
35972
  init_operator_chat_index();
34419
35973
  init_privacy_filter();
35974
+ init_registry();
34420
35975
  CapabilityErrorAgentController = class {
34421
35976
  fail(action) {
34422
35977
  throw new HubCapabilityError(
@@ -34564,7 +36119,7 @@ var init_defaults = __esm({
34564
36119
  });
34565
36120
 
34566
36121
  // src/intelligence/policy-store.ts
34567
- var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO3, IntelligenceConfigStore;
36122
+ var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO4, IntelligenceConfigStore;
34568
36123
  var init_policy_store = __esm({
34569
36124
  "src/intelligence/policy-store.ts"() {
34570
36125
  init_encryption();
@@ -34573,13 +36128,13 @@ var init_policy_store = __esm({
34573
36128
  init_defaults();
34574
36129
  INTELLIGENCE_NAMESPACE = "_intelligence";
34575
36130
  SUBSTRATE_CONFIG_KEY = "substrate-config";
34576
- HKDF_INFO3 = "intelligence-substrate-config";
36131
+ HKDF_INFO4 = "intelligence-substrate-config";
34577
36132
  IntelligenceConfigStore = class {
34578
36133
  storage;
34579
36134
  encryptionKey;
34580
36135
  constructor(storage, masterKey) {
34581
36136
  this.storage = storage;
34582
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
36137
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
34583
36138
  }
34584
36139
  /**
34585
36140
  * Load the operator's substrate config from disk. Returns the config
@@ -36229,7 +37784,7 @@ var init_memory = __esm({
36229
37784
 
36230
37785
  // src/contracts/v1.1/constants.ts
36231
37786
  var SIGNATURE_SCHEME_V12, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
36232
- var init_constants4 = __esm({
37787
+ var init_constants5 = __esm({
36233
37788
  "src/contracts/v1.1/constants.ts"() {
36234
37789
  SIGNATURE_SCHEME_V12 = "ed25519-v1";
36235
37790
  EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
@@ -36647,7 +38202,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
36647
38202
  var InvalidExitBundleError, PRIVATE_MATERIAL_KEYS;
36648
38203
  var init_verifier2 = __esm({
36649
38204
  "src/exit/verifier.ts"() {
36650
- init_constants4();
38205
+ init_constants5();
36651
38206
  init_exit_bundle_manifest();
36652
38207
  init_encoding();
36653
38208
  init_hashing();
@@ -37459,7 +39014,7 @@ var init_bundle = __esm({
37459
39014
  "src/exit/bundle.ts"() {
37460
39015
  init_state_store();
37461
39016
  init_config();
37462
- init_constants4();
39017
+ init_constants5();
37463
39018
  init_canonical_json();
37464
39019
  init_hashing();
37465
39020
  init_encoding();
@@ -38497,12 +40052,18 @@ ${err.message}
38497
40052
  } : void 0;
38498
40053
  const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
38499
40054
  const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
40055
+ const aggregatorPayloadStore = new AggregatorPayloadStore({
40056
+ storage,
40057
+ masterKey,
40058
+ fortressId: fortressIdForAggregator
40059
+ });
38500
40060
  const approvalAggregator = new ApprovalAggregator({
38501
40061
  storage,
38502
40062
  masterKey,
38503
40063
  auditLog,
38504
40064
  identityId: aggregatorIdentityId,
38505
- fortressId: fortressIdForAggregator
40065
+ fortressId: fortressIdForAggregator,
40066
+ payloadStore: aggregatorPayloadStore
38506
40067
  });
38507
40068
  const wrappedApprovalChannel = new AggregatorBackedChannel({
38508
40069
  underlying: approvalChannel,
@@ -38716,6 +40277,7 @@ var init_src = __esm({
38716
40277
  init_gate();
38717
40278
  init_approval_aggregator();
38718
40279
  init_aggregator_backed_channel();
40280
+ init_aggregator_store();
38719
40281
  init_tools4();
38720
40282
  init_router();
38721
40283
  init_router();