@sanctuary-framework/mcp-server 1.2.6 → 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,24 @@ 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
+ }
17221
17240
  if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17222
17241
  const limit = parseLimit2(
17223
17242
  url.searchParams.get("limit"),
@@ -20360,6 +20379,20 @@ var init_approval_aggregator = __esm({
20360
20379
  hydrated = false;
20361
20380
  /** Active SSE listeners. */
20362
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();
20363
20396
  constructor(deps) {
20364
20397
  this.storage = deps.storage;
20365
20398
  this.encryptionKey = derivePurposeKey(
@@ -20394,6 +20427,113 @@ var init_approval_aggregator = __esm({
20394
20427
  this.listeners.add(listener);
20395
20428
  return () => this.listeners.delete(listener);
20396
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
+ }
20397
20537
  /**
20398
20538
  * Ingest a gate event. Returns the aggregator entry on first sight,
20399
20539
  * `null` when deduped. Resolution events update the existing record;
@@ -20604,6 +20744,7 @@ var init_approval_aggregator = __esm({
20604
20744
  entry.status = decision;
20605
20745
  entry.resolved_at = this.now().toISOString();
20606
20746
  entry.resolved_by = operatorId;
20747
+ entry.last_modified_revision = this.nextRevision();
20607
20748
  await this.persist(entry);
20608
20749
  this.auditLog.append(
20609
20750
  "l2",
@@ -20655,6 +20796,7 @@ var init_approval_aggregator = __esm({
20655
20796
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20656
20797
  const hubInboxId = this.resolveHubInboxItemId(event);
20657
20798
  const enforcementChain = this.resolveEnforcementChain(event);
20799
+ const revision = this.nextRevision();
20658
20800
  const entry = {
20659
20801
  aggregator_id: id,
20660
20802
  source_harness: ctx.source_harness,
@@ -20666,6 +20808,8 @@ var init_approval_aggregator = __esm({
20666
20808
  status: "pending",
20667
20809
  created_at: now.toISOString(),
20668
20810
  expires_at: expires.toISOString(),
20811
+ created_at_revision: revision,
20812
+ last_modified_revision: revision,
20669
20813
  ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20670
20814
  ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20671
20815
  };
@@ -20708,6 +20852,7 @@ var init_approval_aggregator = __esm({
20708
20852
  entry.status = status;
20709
20853
  entry.resolved_at = event.resolution.decided_at;
20710
20854
  entry.resolved_by = event.resolution.decided_by;
20855
+ entry.last_modified_revision = this.nextRevision();
20711
20856
  await this.persist(entry);
20712
20857
  this.auditLog.append(
20713
20858
  "l2",
@@ -20770,6 +20915,7 @@ var init_approval_aggregator = __esm({
20770
20915
  entry.status = "expired";
20771
20916
  entry.resolved_at = this.now().toISOString();
20772
20917
  entry.resolved_by = "system_ttl";
20918
+ entry.last_modified_revision = this.nextRevision();
20773
20919
  await this.persist(entry);
20774
20920
  this.auditLog.append(
20775
20921
  "l2",
@@ -20816,6 +20962,10 @@ var init_approval_aggregator = __esm({
20816
20962
  this.entries.set(entry.aggregator_id, entry);
20817
20963
  const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20818
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
+ }
20819
20969
  } catch {
20820
20970
  }
20821
20971
  }
@@ -33796,9 +33946,10 @@ function isTrivialQuery(query) {
33796
33946
  if (norm.length < 8) return true;
33797
33947
  return TRIVIAL_GREETINGS.has(norm);
33798
33948
  }
33799
- function classifyQuery(query) {
33949
+ function classifyQuery(query, parsedGrammar) {
33800
33950
  const normalized = query.toLowerCase();
33801
33951
  const matches = [];
33952
+ const grammarAgent = parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null;
33802
33953
  for (const spec of CATEGORY_KEYWORDS) {
33803
33954
  const matchedPhrases = [];
33804
33955
  for (const pattern of spec.patterns) {
@@ -33810,11 +33961,14 @@ function classifyQuery(query) {
33810
33961
  }
33811
33962
  if (matchedPhrases.length === 0) continue;
33812
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;
33813
33966
  matches.push({
33814
33967
  category: spec.category,
33815
33968
  confidence,
33816
33969
  matched_keywords: matchedPhrases,
33817
- agent_name_hint: spec.category === "agent_state" || spec.category === "agent_activity" ? extractAgentNameHint(query) : null
33970
+ agent_name_hint,
33971
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33818
33972
  });
33819
33973
  }
33820
33974
  matches.sort((a, b) => {
@@ -33823,45 +33977,67 @@ function classifyQuery(query) {
33823
33977
  });
33824
33978
  return matches;
33825
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
+ }
33826
33999
  function approxTokenLen(text) {
33827
34000
  return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
33828
34001
  }
33829
- async function runFetcher(match, fetchers) {
34002
+ async function runFetcher(match, fetchers, hints) {
33830
34003
  switch (match.category) {
33831
34004
  case "templates":
33832
- return fetchers.templates();
34005
+ return fetchers.templates(hints);
33833
34006
  case "agent_state":
33834
- return fetchers.agent_state(match.agent_name_hint);
34007
+ return fetchers.agent_state(match.agent_name_hint, hints);
33835
34008
  case "agent_activity":
33836
- return fetchers.agent_activity(match.agent_name_hint);
34009
+ return fetchers.agent_activity(match.agent_name_hint, hints);
33837
34010
  case "audit_log":
33838
- return fetchers.audit_log();
34011
+ return fetchers.audit_log(hints);
33839
34012
  case "sentinel_findings":
33840
- return fetchers.sentinel_findings();
34013
+ return fetchers.sentinel_findings(hints);
33841
34014
  case "anomaly_alerts":
33842
- return fetchers.anomaly_alerts();
34015
+ return fetchers.anomaly_alerts(hints);
33843
34016
  case "recent_receipts":
33844
- return fetchers.recent_receipts();
34017
+ return fetchers.recent_receipts(hints);
33845
34018
  case "verascore_deltas":
33846
- return fetchers.verascore_deltas();
34019
+ return fetchers.verascore_deltas(hints);
33847
34020
  }
33848
34021
  }
33849
- function trivialMatch(category) {
34022
+ function trivialMatch(category, parsedGrammar) {
33850
34023
  return {
33851
34024
  category,
33852
34025
  confidence: 0.5,
33853
34026
  matched_keywords: ["llm-assist"],
33854
- agent_name_hint: null
34027
+ agent_name_hint: parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null,
34028
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33855
34029
  };
33856
34030
  }
33857
34031
  async function foldContext(query, fetchers, opts) {
33858
34032
  const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
33859
- let matches = classifyQuery(query);
34033
+ const parsed = opts?.parsed ?? null;
34034
+ const hints = fetcherHintsFromGrammar(parsed);
34035
+ let matches = classifyQuery(query, parsed);
33860
34036
  if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
33861
34037
  try {
33862
34038
  const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
33863
34039
  if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
33864
- matches = [trivialMatch(picked)];
34040
+ matches = [trivialMatch(picked, parsed)];
33865
34041
  }
33866
34042
  } catch {
33867
34043
  }
@@ -33872,7 +34048,7 @@ async function foldContext(query, fetchers, opts) {
33872
34048
  const attempts = [];
33873
34049
  for (const match of matches) {
33874
34050
  try {
33875
- const text = await runFetcher(match, fetchers);
34051
+ const text = await runFetcher(match, fetchers, hints);
33876
34052
  const trimmed = text.trim();
33877
34053
  if (trimmed.length > 0) {
33878
34054
  attempts.push({ category: match.category, text: trimmed });
@@ -34044,6 +34220,533 @@ var init_concierge_context_router = __esm({
34044
34220
  };
34045
34221
  }
34046
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
+ });
34047
34750
  function approxTokenLen2(text) {
34048
34751
  return Math.ceil(text.length / 4);
34049
34752
  }
@@ -34076,6 +34779,7 @@ var init_operator_chat_service = __esm({
34076
34779
  init_operator_chat_audit_events();
34077
34780
  init_operator_chat_types();
34078
34781
  init_concierge_context_router();
34782
+ init_concierge_query_grammar();
34079
34783
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
34080
34784
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
34081
34785
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
@@ -34126,6 +34830,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34126
34830
  contextFetchers;
34127
34831
  contextLlmAssist;
34128
34832
  dynamicContextBudget;
34833
+ agentRegistry;
34834
+ grammarLlmAssist;
34129
34835
  /**
34130
34836
  * In-memory thread_id assigned to the active concierge session.
34131
34837
  * The first sendConcierge call after construction allocates a fresh
@@ -34164,6 +34870,12 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34164
34870
  this.contextLlmAssist = deps.conciergeContextLlmAssist;
34165
34871
  }
34166
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
+ }
34167
34879
  }
34168
34880
  // ── Concierge ─────────────────────────────────────────────────────────
34169
34881
  /**
@@ -34222,6 +34934,7 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34222
34934
  await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
34223
34935
  });
34224
34936
  }
34937
+ const parsedGrammar = await this.runGrammarParse(filterResult.filtered);
34225
34938
  const start = Date.now();
34226
34939
  let conciergeBody;
34227
34940
  let servedBy = "disabled";
@@ -34240,7 +34953,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34240
34953
  outcome = "substrate_disabled";
34241
34954
  } else {
34242
34955
  const dynamicResult = await this.runDynamicContextFold(
34243
- filterResult.filtered
34956
+ filterResult.filtered,
34957
+ parsedGrammar
34244
34958
  );
34245
34959
  dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34246
34960
  const context = await this.assembleConciergeContext(
@@ -34311,7 +35025,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34311
35025
  ...this.memory ? {
34312
35026
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
34313
35027
  } : {},
34314
- ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {}
35028
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {},
35029
+ parsed_grammar: auditSafeSummary(parsedGrammar)
34315
35030
  };
34316
35031
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
34317
35032
  return {
@@ -34526,8 +35241,12 @@ ${inbox}`
34526
35241
  * proceeds with no fold. Returns the rendered section + the list of
34527
35242
  * categories whose data made it into the section (used for the
34528
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.
34529
35248
  */
34530
- async runDynamicContextFold(query) {
35249
+ async runDynamicContextFold(query, parsedGrammar) {
34531
35250
  if (!this.contextFetchers) {
34532
35251
  return { section: "", categoriesIncluded: [] };
34533
35252
  }
@@ -34536,10 +35255,24 @@ ${inbox}`
34536
35255
  ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
34537
35256
  onFetcherFailure: (category, error) => {
34538
35257
  this.emitContextFetcherFailed(category, classifyFetcherError(error));
34539
- }
35258
+ },
35259
+ parsed: parsedGrammar
34540
35260
  });
34541
35261
  return result;
34542
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
+ }
34543
35276
  /**
34544
35277
  * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
34545
35278
  * of the fold path so the dynamic-context handler stays readable.
@@ -37051,7 +37784,7 @@ var init_memory = __esm({
37051
37784
 
37052
37785
  // src/contracts/v1.1/constants.ts
37053
37786
  var SIGNATURE_SCHEME_V12, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
37054
- var init_constants4 = __esm({
37787
+ var init_constants5 = __esm({
37055
37788
  "src/contracts/v1.1/constants.ts"() {
37056
37789
  SIGNATURE_SCHEME_V12 = "ed25519-v1";
37057
37790
  EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
@@ -37469,7 +38202,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
37469
38202
  var InvalidExitBundleError, PRIVATE_MATERIAL_KEYS;
37470
38203
  var init_verifier2 = __esm({
37471
38204
  "src/exit/verifier.ts"() {
37472
- init_constants4();
38205
+ init_constants5();
37473
38206
  init_exit_bundle_manifest();
37474
38207
  init_encoding();
37475
38208
  init_hashing();
@@ -38281,7 +39014,7 @@ var init_bundle = __esm({
38281
39014
  "src/exit/bundle.ts"() {
38282
39015
  init_state_store();
38283
39016
  init_config();
38284
- init_constants4();
39017
+ init_constants5();
38285
39018
  init_canonical_json();
38286
39019
  init_hashing();
38287
39020
  init_encoding();