@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.js CHANGED
@@ -5037,7 +5037,8 @@ var init_constants = __esm({
5037
5037
  RESERVED_EVENT_TYPE_PREFIXES = [
5038
5038
  "EXTENSION_",
5039
5039
  "cross_fortress_",
5040
- "multi_master_"
5040
+ "multi_master_",
5041
+ "cross_harness_approval_"
5041
5042
  ];
5042
5043
  RESERVED_EXTENSION_ENVELOPE_KEYS = [
5043
5044
  "cross_fortress_read_grant",
@@ -9229,9 +9230,9 @@ function fingerprintDID(did) {
9229
9230
  return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
9230
9231
  }
9231
9232
  function countInjectionsToday(audit) {
9232
- const startOfDay = /* @__PURE__ */ new Date();
9233
- startOfDay.setHours(0, 0, 0, 0);
9234
- const cutoff = startOfDay.getTime();
9233
+ const startOfDay2 = /* @__PURE__ */ new Date();
9234
+ startOfDay2.setHours(0, 0, 0, 0);
9235
+ const cutoff = startOfDay2.getTime();
9235
9236
  return audit.filter((e) => {
9236
9237
  const ts = new Date(e.timestamp).getTime();
9237
9238
  if (isNaN(ts) || ts < cutoff) return false;
@@ -9240,9 +9241,9 @@ function countInjectionsToday(audit) {
9240
9241
  }).length;
9241
9242
  }
9242
9243
  function countProofsToday(audit) {
9243
- const startOfDay = /* @__PURE__ */ new Date();
9244
- startOfDay.setHours(0, 0, 0, 0);
9245
- const cutoff = startOfDay.getTime();
9244
+ const startOfDay2 = /* @__PURE__ */ new Date();
9245
+ startOfDay2.setHours(0, 0, 0, 0);
9246
+ const cutoff = startOfDay2.getTime();
9246
9247
  return audit.filter((e) => {
9247
9248
  if (e.layer !== "l3") return false;
9248
9249
  if (!PROOF_CREATION_OPS.has(e.operation)) return false;
@@ -17211,6 +17212,24 @@ async function handleApprovalInboxRoute(deps, req, res) {
17211
17212
  await handleStream2(deps, res);
17212
17213
  return true;
17213
17214
  }
17215
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/revision`) {
17216
+ const revision = await deps.aggregator.getRevision();
17217
+ writeJSON4(res, 200, { ok: true, data: { revision } });
17218
+ return true;
17219
+ }
17220
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/sync`) {
17221
+ const sinceRaw = url.searchParams.get("since_revision");
17222
+ const sinceParsed = sinceRaw === null ? 0 : Number.parseInt(sinceRaw, 10);
17223
+ const sinceRevision = Number.isFinite(sinceParsed) && sinceParsed >= 0 ? sinceParsed : 0;
17224
+ const limit = parseLimit2(
17225
+ url.searchParams.get("limit"),
17226
+ APPROVAL_INBOX_DEFAULT_LIMIT,
17227
+ APPROVAL_INBOX_MAX_LIMIT
17228
+ );
17229
+ const delta = await deps.aggregator.getSync({ sinceRevision, limit });
17230
+ writeJSON4(res, 200, { ok: true, data: delta });
17231
+ return true;
17232
+ }
17214
17233
  if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/history`) {
17215
17234
  const limit = parseLimit2(
17216
17235
  url.searchParams.get("limit"),
@@ -20353,6 +20372,20 @@ var init_approval_aggregator = __esm({
20353
20372
  hydrated = false;
20354
20373
  /** Active SSE listeners. */
20355
20374
  listeners = /* @__PURE__ */ new Set();
20375
+ /**
20376
+ * Monotonic revision counter, bumped on every mutation (ingest of new
20377
+ * entry, resolve, expire, delete). Hydrated from max(last_modified_revision)
20378
+ * across persisted entries on first read; in-memory after that. v1.3
20379
+ * Upsilon-4.
20380
+ */
20381
+ currentRevision = 0;
20382
+ /**
20383
+ * Removal tombstones: aggregator_id -> revision at removal. Used by the
20384
+ * sync API to surface "removed" entries to mobile consumers between
20385
+ * polls. In-memory only; server restart clears tombstones (mobile
20386
+ * bootstraps via `list()` on reconnect). v1.3 Upsilon-4.
20387
+ */
20388
+ removedTombstones = /* @__PURE__ */ new Map();
20356
20389
  constructor(deps) {
20357
20390
  this.storage = deps.storage;
20358
20391
  this.encryptionKey = derivePurposeKey(
@@ -20387,6 +20420,113 @@ var init_approval_aggregator = __esm({
20387
20420
  this.listeners.add(listener);
20388
20421
  return () => this.listeners.delete(listener);
20389
20422
  }
20423
+ /**
20424
+ * Current aggregator revision. v1.3 Upsilon-4. Mobile companions
20425
+ * poll the lightweight `/revision` route to detect that something
20426
+ * changed before fetching a full sync delta.
20427
+ */
20428
+ async getRevision() {
20429
+ await this.hydrate();
20430
+ return this.currentRevision;
20431
+ }
20432
+ /**
20433
+ * Compute a delta since `sinceRevision`. v1.3 Upsilon-4. Mobile
20434
+ * clients poll this for cheap state-sync. Behavior:
20435
+ * - `added`: entries whose `created_at_revision > sinceRevision`.
20436
+ * - `changed`: entries that existed at `sinceRevision` but had a
20437
+ * status transition (resolve, expire) since.
20438
+ * - `removed`: aggregator_ids deleted after `sinceRevision`.
20439
+ * - `revision`: current aggregator revision; pass this back as
20440
+ * `sinceRevision` on the next call.
20441
+ *
20442
+ * `limit` caps the total count returned across all three lists,
20443
+ * prioritized as added -> changed -> removed (newer-state first).
20444
+ * When more changes exist than fit, the next call with the returned
20445
+ * revision will pick up the rest because each entry's
20446
+ * last_modified_revision is unchanged by truncation.
20447
+ */
20448
+ async getSync(opts) {
20449
+ await this.hydrate();
20450
+ await this.expireStale();
20451
+ const sinceRevision = opts?.sinceRevision ?? 0;
20452
+ const cap = Math.min(
20453
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
20454
+ this.maxListLimit
20455
+ );
20456
+ const added = [];
20457
+ const changed = [];
20458
+ for (const entry of this.entries.values()) {
20459
+ const lastMod = entry.last_modified_revision ?? 0;
20460
+ if (lastMod <= sinceRevision) continue;
20461
+ const createdRev = entry.created_at_revision ?? 0;
20462
+ if (createdRev > sinceRevision) {
20463
+ added.push(entry);
20464
+ } else {
20465
+ changed.push(entry);
20466
+ }
20467
+ }
20468
+ const removed = [];
20469
+ for (const [id, rev] of this.removedTombstones) {
20470
+ if (rev > sinceRevision) removed.push(id);
20471
+ }
20472
+ added.sort(
20473
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20474
+ );
20475
+ changed.sort(
20476
+ (a, b) => (a.last_modified_revision ?? 0) - (b.last_modified_revision ?? 0)
20477
+ );
20478
+ let remaining = cap;
20479
+ const addedOut = added.slice(0, Math.max(0, remaining));
20480
+ remaining -= addedOut.length;
20481
+ const changedOut = changed.slice(0, Math.max(0, remaining));
20482
+ remaining -= changedOut.length;
20483
+ const removedOut = removed.slice(0, Math.max(0, remaining));
20484
+ return {
20485
+ revision: this.currentRevision,
20486
+ added: addedOut,
20487
+ changed: changedOut,
20488
+ removed: removedOut
20489
+ };
20490
+ }
20491
+ /**
20492
+ * Delete an entry. Drops the in-memory record, the persisted bundle,
20493
+ * and the at-rest payload (if a payload store is wired). Records a
20494
+ * tombstone with the new revision so sync-API consumers see a
20495
+ * `removed` delta. Returns true when an entry was deleted, false on
20496
+ * unknown id. v1.3 Upsilon-4. Reserved for v1.4+ retention housekeeping;
20497
+ * Upsilon-4 ships the surface so mobile sync-API tests can exercise the
20498
+ * removal path.
20499
+ */
20500
+ async deleteEntry(aggregatorId) {
20501
+ await this.hydrate();
20502
+ const entry = this.entries.get(aggregatorId);
20503
+ if (!entry) return false;
20504
+ const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20505
+ this.entries.delete(aggregatorId);
20506
+ this.dedupIndex.delete(dedupKey);
20507
+ this.fullPayloads.delete(aggregatorId);
20508
+ for (const [corr, id] of this.correlationIndex) {
20509
+ if (id === aggregatorId) this.correlationIndex.delete(corr);
20510
+ }
20511
+ try {
20512
+ await this.storage.delete(APPROVAL_AGGREGATOR_NAMESPACE, aggregatorId);
20513
+ } catch {
20514
+ }
20515
+ if (this.payloadStore) {
20516
+ try {
20517
+ await this.payloadStore.deletePayload(aggregatorId);
20518
+ } catch {
20519
+ }
20520
+ }
20521
+ const revision = this.nextRevision();
20522
+ this.removedTombstones.set(aggregatorId, revision);
20523
+ this.emit({ type: "removed", entry: { ...entry } });
20524
+ return true;
20525
+ }
20526
+ nextRevision() {
20527
+ this.currentRevision += 1;
20528
+ return this.currentRevision;
20529
+ }
20390
20530
  /**
20391
20531
  * Ingest a gate event. Returns the aggregator entry on first sight,
20392
20532
  * `null` when deduped. Resolution events update the existing record;
@@ -20597,6 +20737,7 @@ var init_approval_aggregator = __esm({
20597
20737
  entry.status = decision;
20598
20738
  entry.resolved_at = this.now().toISOString();
20599
20739
  entry.resolved_by = operatorId;
20740
+ entry.last_modified_revision = this.nextRevision();
20600
20741
  await this.persist(entry);
20601
20742
  this.auditLog.append(
20602
20743
  "l2",
@@ -20648,6 +20789,7 @@ var init_approval_aggregator = __esm({
20648
20789
  const expires = new Date(now.getTime() + this.pendingTtlMs);
20649
20790
  const hubInboxId = this.resolveHubInboxItemId(event);
20650
20791
  const enforcementChain = this.resolveEnforcementChain(event);
20792
+ const revision = this.nextRevision();
20651
20793
  const entry = {
20652
20794
  aggregator_id: id,
20653
20795
  source_harness: ctx.source_harness,
@@ -20659,6 +20801,8 @@ var init_approval_aggregator = __esm({
20659
20801
  status: "pending",
20660
20802
  created_at: now.toISOString(),
20661
20803
  expires_at: expires.toISOString(),
20804
+ created_at_revision: revision,
20805
+ last_modified_revision: revision,
20662
20806
  ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {},
20663
20807
  ...enforcementChain.length > 0 ? { enforcement_chain: enforcementChain } : {}
20664
20808
  };
@@ -20701,6 +20845,7 @@ var init_approval_aggregator = __esm({
20701
20845
  entry.status = status;
20702
20846
  entry.resolved_at = event.resolution.decided_at;
20703
20847
  entry.resolved_by = event.resolution.decided_by;
20848
+ entry.last_modified_revision = this.nextRevision();
20704
20849
  await this.persist(entry);
20705
20850
  this.auditLog.append(
20706
20851
  "l2",
@@ -20763,6 +20908,7 @@ var init_approval_aggregator = __esm({
20763
20908
  entry.status = "expired";
20764
20909
  entry.resolved_at = this.now().toISOString();
20765
20910
  entry.resolved_by = "system_ttl";
20911
+ entry.last_modified_revision = this.nextRevision();
20766
20912
  await this.persist(entry);
20767
20913
  this.auditLog.append(
20768
20914
  "l2",
@@ -20809,6 +20955,10 @@ var init_approval_aggregator = __esm({
20809
20955
  this.entries.set(entry.aggregator_id, entry);
20810
20956
  const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
20811
20957
  this.dedupIndex.set(dedupKey, entry.aggregator_id);
20958
+ const lastMod = entry.last_modified_revision ?? 0;
20959
+ if (lastMod > this.currentRevision) {
20960
+ this.currentRevision = lastMod;
20961
+ }
20812
20962
  } catch {
20813
20963
  }
20814
20964
  }
@@ -33789,9 +33939,10 @@ function isTrivialQuery(query) {
33789
33939
  if (norm.length < 8) return true;
33790
33940
  return TRIVIAL_GREETINGS.has(norm);
33791
33941
  }
33792
- function classifyQuery(query) {
33942
+ function classifyQuery(query, parsedGrammar) {
33793
33943
  const normalized = query.toLowerCase();
33794
33944
  const matches = [];
33945
+ const grammarAgent = parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null;
33795
33946
  for (const spec of CATEGORY_KEYWORDS) {
33796
33947
  const matchedPhrases = [];
33797
33948
  for (const pattern of spec.patterns) {
@@ -33803,11 +33954,14 @@ function classifyQuery(query) {
33803
33954
  }
33804
33955
  if (matchedPhrases.length === 0) continue;
33805
33956
  const confidence = Math.min(1, 0.4 + 0.3 * matchedPhrases.length);
33957
+ const wantsAgentHint = spec.category === "agent_state" || spec.category === "agent_activity";
33958
+ const agent_name_hint = wantsAgentHint ? grammarAgent ?? extractAgentNameHint(query) : null;
33806
33959
  matches.push({
33807
33960
  category: spec.category,
33808
33961
  confidence,
33809
33962
  matched_keywords: matchedPhrases,
33810
- agent_name_hint: spec.category === "agent_state" || spec.category === "agent_activity" ? extractAgentNameHint(query) : null
33963
+ agent_name_hint,
33964
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33811
33965
  });
33812
33966
  }
33813
33967
  matches.sort((a, b) => {
@@ -33816,45 +33970,67 @@ function classifyQuery(query) {
33816
33970
  });
33817
33971
  return matches;
33818
33972
  }
33973
+ function fetcherHintsFromGrammar(parsed) {
33974
+ if (!parsed) return void 0;
33975
+ const hasTime = parsed.time_range !== null;
33976
+ const hasAgents = parsed.agent_names.length > 0;
33977
+ const hasEvents = parsed.event_types.length > 0;
33978
+ if (!hasTime && !hasAgents && !hasEvents) return void 0;
33979
+ const hints = {};
33980
+ if (parsed.time_range) {
33981
+ const range = parsed.time_range;
33982
+ hints.time_range = {
33983
+ start: range.start,
33984
+ end: range.end,
33985
+ ...range.relative_label !== void 0 ? { relative_label: range.relative_label } : {}
33986
+ };
33987
+ }
33988
+ if (hasAgents) hints.agent_names = parsed.agent_names;
33989
+ if (hasEvents) hints.event_types = parsed.event_types;
33990
+ return hints;
33991
+ }
33819
33992
  function approxTokenLen(text) {
33820
33993
  return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
33821
33994
  }
33822
- async function runFetcher(match, fetchers) {
33995
+ async function runFetcher(match, fetchers, hints) {
33823
33996
  switch (match.category) {
33824
33997
  case "templates":
33825
- return fetchers.templates();
33998
+ return fetchers.templates(hints);
33826
33999
  case "agent_state":
33827
- return fetchers.agent_state(match.agent_name_hint);
34000
+ return fetchers.agent_state(match.agent_name_hint, hints);
33828
34001
  case "agent_activity":
33829
- return fetchers.agent_activity(match.agent_name_hint);
34002
+ return fetchers.agent_activity(match.agent_name_hint, hints);
33830
34003
  case "audit_log":
33831
- return fetchers.audit_log();
34004
+ return fetchers.audit_log(hints);
33832
34005
  case "sentinel_findings":
33833
- return fetchers.sentinel_findings();
34006
+ return fetchers.sentinel_findings(hints);
33834
34007
  case "anomaly_alerts":
33835
- return fetchers.anomaly_alerts();
34008
+ return fetchers.anomaly_alerts(hints);
33836
34009
  case "recent_receipts":
33837
- return fetchers.recent_receipts();
34010
+ return fetchers.recent_receipts(hints);
33838
34011
  case "verascore_deltas":
33839
- return fetchers.verascore_deltas();
34012
+ return fetchers.verascore_deltas(hints);
33840
34013
  }
33841
34014
  }
33842
- function trivialMatch(category) {
34015
+ function trivialMatch(category, parsedGrammar) {
33843
34016
  return {
33844
34017
  category,
33845
34018
  confidence: 0.5,
33846
34019
  matched_keywords: ["llm-assist"],
33847
- agent_name_hint: null
34020
+ agent_name_hint: parsedGrammar && parsedGrammar.agent_names.length > 0 ? parsedGrammar.agent_names[0] ?? null : null,
34021
+ ...parsedGrammar !== void 0 ? { query_grammar: parsedGrammar } : {}
33848
34022
  };
33849
34023
  }
33850
34024
  async function foldContext(query, fetchers, opts) {
33851
34025
  const budget = opts?.maxTokens ?? DEFAULT_DYNAMIC_CONTEXT_TOKEN_BUDGET;
33852
- let matches = classifyQuery(query);
34026
+ const parsed = opts?.parsed ?? null;
34027
+ const hints = fetcherHintsFromGrammar(parsed);
34028
+ let matches = classifyQuery(query, parsed);
33853
34029
  if (matches.length === 0 && !isTrivialQuery(query) && opts?.llmAssistClassify) {
33854
34030
  try {
33855
34031
  const picked = await opts.llmAssistClassify(query, CONTEXT_CATEGORIES);
33856
34032
  if (picked !== "none" && CONTEXT_CATEGORIES.includes(picked)) {
33857
- matches = [trivialMatch(picked)];
34033
+ matches = [trivialMatch(picked, parsed)];
33858
34034
  }
33859
34035
  } catch {
33860
34036
  }
@@ -33865,7 +34041,7 @@ async function foldContext(query, fetchers, opts) {
33865
34041
  const attempts = [];
33866
34042
  for (const match of matches) {
33867
34043
  try {
33868
- const text = await runFetcher(match, fetchers);
34044
+ const text = await runFetcher(match, fetchers, hints);
33869
34045
  const trimmed = text.trim();
33870
34046
  if (trimmed.length > 0) {
33871
34047
  attempts.push({ category: match.category, text: trimmed });
@@ -34037,6 +34213,533 @@ var init_concierge_context_router = __esm({
34037
34213
  };
34038
34214
  }
34039
34215
  });
34216
+
34217
+ // src/composition/constants.ts
34218
+ var COMPOSITION_EVENT_TYPES;
34219
+ var init_constants4 = __esm({
34220
+ "src/composition/constants.ts"() {
34221
+ init_constants();
34222
+ COMPOSITION_EVENT_TYPES = [
34223
+ "composition_receipt_packed",
34224
+ "composition_receipt_verified",
34225
+ "composition_mandate_verified",
34226
+ "composition_verascore_published",
34227
+ "composition_sidecar_spawned",
34228
+ "composition_sidecar_crashed",
34229
+ "composition_sidecar_recovered",
34230
+ "composition_degraded",
34231
+ "composition_recovered"
34232
+ ];
34233
+ }
34234
+ });
34235
+
34236
+ // src/chat/concierge-query-grammar.ts
34237
+ function resolveTimeRange(query, now) {
34238
+ const normalized = query.trim();
34239
+ const lower = normalized.toLowerCase();
34240
+ const fromTo = lower.match(
34241
+ /\b(?:from|between)\s+(.+?)\s+(?:to|and|-|until)\s+([\w:.\-+t /]+)/i
34242
+ );
34243
+ if (fromTo) {
34244
+ const aSlice = fromTo[1];
34245
+ const bSlice = fromTo[2];
34246
+ if (aSlice !== void 0 && bSlice !== void 0) {
34247
+ const a = parseInstant(aSlice, now);
34248
+ const b = parseInstant(bSlice, now);
34249
+ if (a && b) {
34250
+ const start = a.getTime() <= b.getTime() ? a : b;
34251
+ const end = a.getTime() <= b.getTime() ? b : a;
34252
+ return {
34253
+ range: { start, end },
34254
+ matchedSubstring: fromTo[0]
34255
+ };
34256
+ }
34257
+ }
34258
+ }
34259
+ const sinceMatch = lower.match(/\bsince\s+([\w:.\-+t /]+)/i);
34260
+ if (sinceMatch) {
34261
+ const slice = sinceMatch[1];
34262
+ if (slice !== void 0) {
34263
+ const start = parseInstant(slice, now);
34264
+ if (start) {
34265
+ return {
34266
+ range: { start, end: now },
34267
+ matchedSubstring: sinceMatch[0]
34268
+ };
34269
+ }
34270
+ }
34271
+ }
34272
+ if (/\byesterday\b/.test(lower)) {
34273
+ const startOfToday = startOfDay(now);
34274
+ const start = new Date(startOfToday.getTime() - MS_PER_DAY);
34275
+ const end = new Date(startOfToday.getTime() - 1);
34276
+ return {
34277
+ range: { start, end, relative_label: "yesterday" },
34278
+ matchedSubstring: "yesterday"
34279
+ };
34280
+ }
34281
+ if (/\btoday\b/.test(lower)) {
34282
+ return {
34283
+ range: {
34284
+ start: startOfDay(now),
34285
+ end: now,
34286
+ relative_label: "today"
34287
+ },
34288
+ matchedSubstring: "today"
34289
+ };
34290
+ }
34291
+ const compactHours = lower.match(/\blast\s+(\d+)\s*h\b/i);
34292
+ if (compactHours) {
34293
+ const tok = compactHours[1];
34294
+ if (tok !== void 0) {
34295
+ const n = Number.parseInt(tok, 10);
34296
+ if (Number.isFinite(n) && n > 0) {
34297
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34298
+ return {
34299
+ range: { start, end: now, relative_label: `last ${n}h` },
34300
+ matchedSubstring: compactHours[0]
34301
+ };
34302
+ }
34303
+ }
34304
+ }
34305
+ const hoursMatch = lower.match(
34306
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:hr\b|hrs\b|hour|hours)/i
34307
+ );
34308
+ if (hoursMatch) {
34309
+ const tok = hoursMatch[1];
34310
+ if (tok !== void 0) {
34311
+ const n = parseCount(tok);
34312
+ if (n !== null && n > 0) {
34313
+ const start = new Date(now.getTime() - n * MS_PER_HOUR);
34314
+ return {
34315
+ range: { start, end: now, relative_label: `past ${n} hour${n === 1 ? "" : "s"}` },
34316
+ matchedSubstring: hoursMatch[0]
34317
+ };
34318
+ }
34319
+ }
34320
+ }
34321
+ if (/\b(?:past|last)\s+hour\b/.test(lower)) {
34322
+ const start = new Date(now.getTime() - MS_PER_HOUR);
34323
+ return {
34324
+ range: { start, end: now, relative_label: "past hour" },
34325
+ matchedSubstring: lower.match(/\b(?:past|last)\s+hour\b/i)[0]
34326
+ };
34327
+ }
34328
+ const daysMatch = lower.match(
34329
+ /\b(?:past|last)\s+([\w]+|\d+)\s*(?:d\b|day|days)/i
34330
+ );
34331
+ if (daysMatch) {
34332
+ const tok = daysMatch[1];
34333
+ if (tok !== void 0) {
34334
+ const n = parseCount(tok);
34335
+ if (n !== null && n > 0) {
34336
+ const start = new Date(now.getTime() - n * MS_PER_DAY);
34337
+ return {
34338
+ range: { start, end: now, relative_label: `past ${n} day${n === 1 ? "" : "s"}` },
34339
+ matchedSubstring: daysMatch[0]
34340
+ };
34341
+ }
34342
+ }
34343
+ }
34344
+ if (/\b(?:past|last)\s+day\b/.test(lower)) {
34345
+ const start = new Date(now.getTime() - MS_PER_DAY);
34346
+ return {
34347
+ range: { start, end: now, relative_label: "past day" },
34348
+ matchedSubstring: lower.match(/\b(?:past|last)\s+day\b/i)[0]
34349
+ };
34350
+ }
34351
+ if (/\bthis\s+week\b/.test(lower)) {
34352
+ const start = startOfWeek(now);
34353
+ return {
34354
+ range: { start, end: now, relative_label: "this week" },
34355
+ matchedSubstring: lower.match(/\bthis\s+week\b/i)[0]
34356
+ };
34357
+ }
34358
+ if (/\b(?:past|last)\s+week\b/.test(lower)) {
34359
+ const start = new Date(now.getTime() - 7 * MS_PER_DAY);
34360
+ return {
34361
+ range: { start, end: now, relative_label: "past week" },
34362
+ matchedSubstring: lower.match(/\b(?:past|last)\s+week\b/i)[0]
34363
+ };
34364
+ }
34365
+ const isoMatch = normalized.match(
34366
+ /\b(\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:\d{2})?)?)\b/
34367
+ );
34368
+ if (isoMatch) {
34369
+ const tok = isoMatch[1];
34370
+ if (tok !== void 0) {
34371
+ const parsed = parseInstant(tok, now);
34372
+ if (parsed) {
34373
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(tok);
34374
+ if (isDateOnly) {
34375
+ return {
34376
+ range: {
34377
+ start: parsed,
34378
+ end: new Date(parsed.getTime() + MS_PER_DAY - 1)
34379
+ },
34380
+ matchedSubstring: tok
34381
+ };
34382
+ }
34383
+ return {
34384
+ range: {
34385
+ start: new Date(parsed.getTime() - 30 * 60 * 1e3),
34386
+ end: new Date(parsed.getTime() + 30 * 60 * 1e3)
34387
+ },
34388
+ matchedSubstring: tok
34389
+ };
34390
+ }
34391
+ }
34392
+ }
34393
+ return null;
34394
+ }
34395
+ function parseInstant(token, now) {
34396
+ const trimmed = token.trim().replace(/[,.!?;]+$/g, "");
34397
+ if (!trimmed) return null;
34398
+ const lower = trimmed.toLowerCase();
34399
+ if (lower === "now") return now;
34400
+ if (lower === "today") return startOfDay(now);
34401
+ if (lower === "yesterday") {
34402
+ return new Date(startOfDay(now).getTime() - MS_PER_DAY);
34403
+ }
34404
+ const isoLike = trimmed.replace(" ", "T");
34405
+ const parsed = new Date(isoLike);
34406
+ if (!Number.isNaN(parsed.getTime())) return parsed;
34407
+ return null;
34408
+ }
34409
+ function parseCount(token) {
34410
+ const lower = token.toLowerCase();
34411
+ if (/^\d+$/.test(lower)) {
34412
+ const n = Number.parseInt(lower, 10);
34413
+ return Number.isFinite(n) ? n : null;
34414
+ }
34415
+ return NUMBER_WORDS[lower] ?? null;
34416
+ }
34417
+ function startOfDay(d) {
34418
+ const out = new Date(d);
34419
+ out.setHours(0, 0, 0, 0);
34420
+ return out;
34421
+ }
34422
+ function startOfWeek(d) {
34423
+ const out = startOfDay(d);
34424
+ const dayOfWeek = out.getDay();
34425
+ const offsetToMonday = (dayOfWeek + 6) % 7;
34426
+ out.setDate(out.getDate() - offsetToMonday);
34427
+ return out;
34428
+ }
34429
+ function listFromRegistry(registry) {
34430
+ if (!registry) return [];
34431
+ if (Array.isArray(registry)) return registry;
34432
+ if (typeof registry.list === "function") {
34433
+ return registry.list();
34434
+ }
34435
+ return [];
34436
+ }
34437
+ function extractAgentNames(query, registry) {
34438
+ const records = listFromRegistry(registry);
34439
+ if (records.length === 0) return { matched: [], flagged: false };
34440
+ const lowerQuery = query.toLowerCase();
34441
+ const compactQuery = lowerQuery.replace(/[\s_-]+/g, "");
34442
+ const matched = [];
34443
+ const seen = /* @__PURE__ */ new Set();
34444
+ for (const rec of records) {
34445
+ const id = rec.agent_id;
34446
+ if (!id || seen.has(id)) continue;
34447
+ const idLower = id.toLowerCase();
34448
+ if (idLower.length < 3) continue;
34449
+ const idCompact = idLower.replace(/[\s_-]+/g, "");
34450
+ const wordRe = new RegExp(
34451
+ `\\b${idLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
34452
+ "i"
34453
+ );
34454
+ if (wordRe.test(query)) {
34455
+ matched.push(id);
34456
+ seen.add(id);
34457
+ continue;
34458
+ }
34459
+ if (idCompact.length >= 4 && compactQuery.includes(idCompact)) {
34460
+ matched.push(id);
34461
+ seen.add(id);
34462
+ }
34463
+ }
34464
+ const agentMention = lowerQuery.match(/\bagent\s+([a-z0-9_-]{3,40})/i);
34465
+ const flagged = matched.length === 0 && agentMention !== null && agentMention[1] !== void 0 && !records.some((r) => r.agent_id.toLowerCase() === agentMention[1]?.toLowerCase());
34466
+ return { matched, flagged };
34467
+ }
34468
+ function escapeRegex(s) {
34469
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34470
+ }
34471
+ function extractEventTypes(query, enumValues) {
34472
+ const lower = query.toLowerCase();
34473
+ const matched = [];
34474
+ const seen = /* @__PURE__ */ new Set();
34475
+ for (const ev of enumValues) {
34476
+ if (seen.has(ev)) continue;
34477
+ const re = new RegExp(`\\b${escapeRegex(ev)}\\b`, "i");
34478
+ if (re.test(query)) {
34479
+ matched.push(ev);
34480
+ seen.add(ev);
34481
+ }
34482
+ }
34483
+ for (const syn of EVENT_SYNONYMS) {
34484
+ const re = new RegExp(
34485
+ `\\b${syn.phrase.split(/\s+/).map(escapeRegex).join("\\s+")}\\b`,
34486
+ "i"
34487
+ );
34488
+ if (re.test(query)) {
34489
+ for (const c of syn.canonical) {
34490
+ if (seen.has(c)) continue;
34491
+ if (!enumValues.includes(c)) continue;
34492
+ matched.push(c);
34493
+ seen.add(c);
34494
+ }
34495
+ }
34496
+ }
34497
+ const globMatches = lower.match(/\b([a-z_]+)_\*/g) ?? [];
34498
+ for (const glob of globMatches) {
34499
+ const prefix = glob.slice(0, -2);
34500
+ for (const ev of enumValues) {
34501
+ if (seen.has(ev)) continue;
34502
+ if (ev.startsWith(prefix)) {
34503
+ matched.push(ev);
34504
+ seen.add(ev);
34505
+ }
34506
+ }
34507
+ }
34508
+ const eventNounMention = /\b(?:event|events|class|classes)\b/i.test(query) && matched.length === 0;
34509
+ return { matched, flagged: eventNounMention };
34510
+ }
34511
+ function deriveIntentPhrase(query, stripTokens) {
34512
+ let out = query;
34513
+ for (const tok of stripTokens) {
34514
+ if (!tok) continue;
34515
+ const re = new RegExp(escapeRegex(tok), "gi");
34516
+ out = out.replace(re, " ");
34517
+ }
34518
+ return out.replace(/\s+/g, " ").trim();
34519
+ }
34520
+ function computeConfidence(parsed) {
34521
+ const dims = [
34522
+ { present: parsed.hasTimeMention, resolved: parsed.timeResolved },
34523
+ { present: parsed.hasAgentMention, resolved: parsed.agentResolved },
34524
+ { present: parsed.hasEventMention, resolved: parsed.eventResolved }
34525
+ ];
34526
+ const present = dims.filter((d) => d.present);
34527
+ let base;
34528
+ if (present.length === 0) {
34529
+ base = parsed.intentEmpty ? 0 : 0.3;
34530
+ } else {
34531
+ const resolved = present.filter((d) => d.resolved).length;
34532
+ base = resolved / present.length;
34533
+ }
34534
+ const adjusted = base - 0.15 * parsed.ambiguityCount;
34535
+ if (adjusted < 0) return 0;
34536
+ if (adjusted > 1) return 1;
34537
+ return adjusted;
34538
+ }
34539
+ function parseQuery(query, opts) {
34540
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
34541
+ const enumValues = opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES;
34542
+ const original = query ?? "";
34543
+ const trimmed = original.trim();
34544
+ if (trimmed.length === 0) {
34545
+ return {
34546
+ time_range: null,
34547
+ agent_names: [],
34548
+ event_types: [],
34549
+ intent_phrase: "",
34550
+ ambiguity_flags: ["no_signal_extracted"],
34551
+ parse_confidence: 0
34552
+ };
34553
+ }
34554
+ const ambiguity_flags = /* @__PURE__ */ new Set();
34555
+ const timeMatch = resolveTimeRange(trimmed, now);
34556
+ const hasTimeMention = TIME_MENTION_PROBE.test(trimmed);
34557
+ if (hasTimeMention && !timeMatch) {
34558
+ ambiguity_flags.add("unknown_time_token");
34559
+ }
34560
+ const agentResult = extractAgentNames(trimmed, opts?.registry);
34561
+ if (agentResult.flagged) {
34562
+ ambiguity_flags.add("unknown_agent_token");
34563
+ }
34564
+ const hasAgentMention = AGENT_MENTION_PROBE.test(trimmed);
34565
+ const eventResult = extractEventTypes(trimmed, enumValues);
34566
+ const hasEventMention = EVENT_MENTION_PROBE.test(trimmed);
34567
+ if (eventResult.flagged) {
34568
+ ambiguity_flags.add("unknown_event_token");
34569
+ }
34570
+ const stripTokens = [];
34571
+ if (timeMatch) stripTokens.push(timeMatch.matchedSubstring);
34572
+ for (const name of agentResult.matched) stripTokens.push(name);
34573
+ for (const ev of eventResult.matched) {
34574
+ if (trimmed.toLowerCase().includes(ev.toLowerCase())) {
34575
+ stripTokens.push(ev);
34576
+ }
34577
+ }
34578
+ const intent_phrase = deriveIntentPhrase(trimmed, stripTokens);
34579
+ const parse_confidence = computeConfidence({
34580
+ hasTimeMention,
34581
+ timeResolved: timeMatch !== null,
34582
+ hasAgentMention,
34583
+ agentResolved: agentResult.matched.length > 0,
34584
+ hasEventMention,
34585
+ eventResolved: eventResult.matched.length > 0,
34586
+ intentEmpty: intent_phrase.length === 0,
34587
+ ambiguityCount: ambiguity_flags.size
34588
+ });
34589
+ if (timeMatch === null && agentResult.matched.length === 0 && eventResult.matched.length === 0 && intent_phrase.length === 0) {
34590
+ ambiguity_flags.add("no_signal_extracted");
34591
+ }
34592
+ return {
34593
+ time_range: timeMatch ? timeMatch.range : null,
34594
+ agent_names: agentResult.matched,
34595
+ event_types: eventResult.matched,
34596
+ intent_phrase,
34597
+ ambiguity_flags: Array.from(ambiguity_flags),
34598
+ parse_confidence
34599
+ };
34600
+ }
34601
+ function isLowConfidence(parsed) {
34602
+ return parsed.parse_confidence < LLM_ASSIST_THRESHOLD;
34603
+ }
34604
+ async function parseQueryWithLlmAssist(query, llmAssist, opts) {
34605
+ const parsed = parseQuery(query, opts);
34606
+ if (!llmAssist || !isLowConfidence(parsed)) return parsed;
34607
+ let completion;
34608
+ try {
34609
+ completion = await llmAssist(query, parsed);
34610
+ } catch {
34611
+ return parsed;
34612
+ }
34613
+ if (!completion || typeof completion !== "object") return parsed;
34614
+ const merged = { ...parsed };
34615
+ if (parsed.time_range === null && completion.time_range) {
34616
+ merged.time_range = completion.time_range;
34617
+ }
34618
+ if (parsed.agent_names.length === 0 && Array.isArray(completion.agent_names)) {
34619
+ merged.agent_names = completion.agent_names.filter(
34620
+ (s) => typeof s === "string" && s.length > 0
34621
+ );
34622
+ }
34623
+ if (parsed.event_types.length === 0 && Array.isArray(completion.event_types)) {
34624
+ const allowed = new Set(opts?.eventClassEnum ?? CANONICAL_AUDIT_EVENT_CLASSES);
34625
+ merged.event_types = completion.event_types.filter(
34626
+ (s) => typeof s === "string" && allowed.has(s)
34627
+ );
34628
+ }
34629
+ merged.parse_confidence = Math.max(
34630
+ parsed.parse_confidence,
34631
+ computeConfidence({
34632
+ hasTimeMention: TIME_MENTION_PROBE.test(query),
34633
+ timeResolved: merged.time_range !== null,
34634
+ hasAgentMention: AGENT_MENTION_PROBE.test(query),
34635
+ agentResolved: merged.agent_names.length > 0,
34636
+ hasEventMention: EVENT_MENTION_PROBE.test(query),
34637
+ eventResolved: merged.event_types.length > 0,
34638
+ intentEmpty: merged.intent_phrase.length === 0,
34639
+ ambiguityCount: merged.ambiguity_flags.length
34640
+ })
34641
+ );
34642
+ return merged;
34643
+ }
34644
+ function auditSafeSummary(parsed) {
34645
+ return {
34646
+ time_range: parsed.time_range ? {
34647
+ start_iso: parsed.time_range.start.toISOString(),
34648
+ end_iso: parsed.time_range.end.toISOString(),
34649
+ ...parsed.time_range.relative_label !== void 0 ? { relative_label: parsed.time_range.relative_label } : {}
34650
+ } : null,
34651
+ agent_names: [...parsed.agent_names],
34652
+ event_types: [...parsed.event_types],
34653
+ ambiguity_flags: [...parsed.ambiguity_flags],
34654
+ parse_confidence: parsed.parse_confidence
34655
+ };
34656
+ }
34657
+ var CANONICAL_AUDIT_EVENT_CLASSES, EVENT_SYNONYMS, MS_PER_HOUR, MS_PER_DAY, NUMBER_WORDS, TIME_MENTION_PROBE, AGENT_MENTION_PROBE, EVENT_MENTION_PROBE, LLM_ASSIST_THRESHOLD;
34658
+ var init_concierge_query_grammar = __esm({
34659
+ "src/chat/concierge-query-grammar.ts"() {
34660
+ init_constants4();
34661
+ init_operator_chat_audit_events();
34662
+ CANONICAL_AUDIT_EVENT_CLASSES = [
34663
+ // Lifecycle / policy
34664
+ "policy_change",
34665
+ "approval_request",
34666
+ "audit_truncate",
34667
+ "lockdown",
34668
+ "unwrap",
34669
+ // Exit bundle (Tier 1)
34670
+ "exit_bundle_export",
34671
+ "exit_bundle_import_activate",
34672
+ "exit_bundle_rekey",
34673
+ // Cross-harness approval aggregator
34674
+ "cross_harness_approval_aggregated",
34675
+ "cross_harness_approval_resolved",
34676
+ "cross_harness_approval_deduped",
34677
+ "cross_harness_approval_payload_decrypted",
34678
+ "cross_harness_approval_audit_trail_viewed",
34679
+ "cross_harness_approval_replayed",
34680
+ // Composition (full set from constants.ts)
34681
+ ...COMPOSITION_EVENT_TYPES,
34682
+ // Operator chat / concierge (full set from OPERATOR_CHAT_OPS)
34683
+ OPERATOR_CHAT_OPS.CONCIERGE_CHAT,
34684
+ OPERATOR_CHAT_OPS.AGENT_INSPECT_PANEL_OPENED,
34685
+ OPERATOR_CHAT_OPS.CONCIERGE_HISTORY_READ,
34686
+ OPERATOR_CHAT_OPS.CONCIERGE_THREAD_DELETED,
34687
+ OPERATOR_CHAT_OPS.CONCIERGE_MEMORY_READ_FAILED,
34688
+ OPERATOR_CHAT_OPS.CONCIERGE_CONTEXT_FETCHER_FAILED,
34689
+ // Bridge / commitment
34690
+ "bridge_commit",
34691
+ "bridge_verify",
34692
+ "bridge_attest",
34693
+ "proof_commitment",
34694
+ "proof_reveal",
34695
+ // Reputation
34696
+ "reputation_export",
34697
+ "reputation_import",
34698
+ "reputation_publish",
34699
+ "reputation_record",
34700
+ "reputation_query"
34701
+ ];
34702
+ EVENT_SYNONYMS = [
34703
+ { phrase: "approvals", canonical: ["approval_request", "cross_harness_approval_aggregated", "cross_harness_approval_resolved"] },
34704
+ { phrase: "approval", canonical: ["approval_request"] },
34705
+ { phrase: "policy changes", canonical: ["policy_change"] },
34706
+ { phrase: "policy change", canonical: ["policy_change"] },
34707
+ { phrase: "policy edits", canonical: ["policy_change"] },
34708
+ { phrase: "lockdowns", canonical: ["lockdown"] },
34709
+ { phrase: "exit bundles", canonical: ["exit_bundle_export", "exit_bundle_import_activate"] },
34710
+ { phrase: "exit bundle", canonical: ["exit_bundle_export"] },
34711
+ { phrase: "audit truncations", canonical: ["audit_truncate"] },
34712
+ { phrase: "audit truncation", canonical: ["audit_truncate"] },
34713
+ { phrase: "compositions", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34714
+ { phrase: "receipts", canonical: ["composition_receipt_packed", "composition_receipt_verified"] },
34715
+ { phrase: "receipt verifications", canonical: ["composition_receipt_verified"] },
34716
+ { phrase: "concierge chats", canonical: [OPERATOR_CHAT_OPS.CONCIERGE_CHAT] },
34717
+ { phrase: "cross harness approvals", canonical: ["cross_harness_approval_aggregated", "cross_harness_approval_resolved"] }
34718
+ ];
34719
+ MS_PER_HOUR = 60 * 60 * 1e3;
34720
+ MS_PER_DAY = 24 * MS_PER_HOUR;
34721
+ NUMBER_WORDS = {
34722
+ a: 1,
34723
+ an: 1,
34724
+ one: 1,
34725
+ two: 2,
34726
+ three: 3,
34727
+ four: 4,
34728
+ five: 5,
34729
+ six: 6,
34730
+ seven: 7,
34731
+ eight: 8,
34732
+ nine: 9,
34733
+ ten: 10,
34734
+ twelve: 12,
34735
+ twentyfour: 24
34736
+ };
34737
+ TIME_MENTION_PROBE = /\b(yesterday|today|now|past|last|this\s+week|this\s+month|since|from|between|\d{4}-\d{2}-\d{2})\b/i;
34738
+ AGENT_MENTION_PROBE = /\bagent[s]?\b/i;
34739
+ EVENT_MENTION_PROBE = /\b(event|events|class|classes|approvals?|policy)\b/i;
34740
+ LLM_ASSIST_THRESHOLD = 0.5;
34741
+ }
34742
+ });
34040
34743
  function approxTokenLen2(text) {
34041
34744
  return Math.ceil(text.length / 4);
34042
34745
  }
@@ -34069,6 +34772,7 @@ var init_operator_chat_service = __esm({
34069
34772
  init_operator_chat_audit_events();
34070
34773
  init_operator_chat_types();
34071
34774
  init_concierge_context_router();
34775
+ init_concierge_query_grammar();
34072
34776
  DEFAULT_CONCIERGE_MAX_TOKENS = 512;
34073
34777
  DEFAULT_CONCIERGE_HISTORY_WINDOW_TURNS = 10;
34074
34778
  DEFAULT_CONCIERGE_HISTORY_FRESHNESS_MS = 24 * 60 * 60 * 1e3;
@@ -34119,6 +34823,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34119
34823
  contextFetchers;
34120
34824
  contextLlmAssist;
34121
34825
  dynamicContextBudget;
34826
+ agentRegistry;
34827
+ grammarLlmAssist;
34122
34828
  /**
34123
34829
  * In-memory thread_id assigned to the active concierge session.
34124
34830
  * The first sendConcierge call after construction allocates a fresh
@@ -34157,6 +34863,12 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34157
34863
  this.contextLlmAssist = deps.conciergeContextLlmAssist;
34158
34864
  }
34159
34865
  this.dynamicContextBudget = deps.conciergeDynamicContextBudget !== void 0 && deps.conciergeDynamicContextBudget > 0 ? deps.conciergeDynamicContextBudget : DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET;
34866
+ if (deps.conciergeAgentRegistry) {
34867
+ this.agentRegistry = deps.conciergeAgentRegistry;
34868
+ }
34869
+ if (deps.conciergeGrammarLlmAssist) {
34870
+ this.grammarLlmAssist = deps.conciergeGrammarLlmAssist;
34871
+ }
34160
34872
  }
34161
34873
  // ── Concierge ─────────────────────────────────────────────────────────
34162
34874
  /**
@@ -34215,6 +34927,7 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34215
34927
  await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
34216
34928
  });
34217
34929
  }
34930
+ const parsedGrammar = await this.runGrammarParse(filterResult.filtered);
34218
34931
  const start = Date.now();
34219
34932
  let conciergeBody;
34220
34933
  let servedBy = "disabled";
@@ -34233,7 +34946,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34233
34946
  outcome = "substrate_disabled";
34234
34947
  } else {
34235
34948
  const dynamicResult = await this.runDynamicContextFold(
34236
- filterResult.filtered
34949
+ filterResult.filtered,
34950
+ parsedGrammar
34237
34951
  );
34238
34952
  dynamicCategoriesIncluded = dynamicResult.categoriesIncluded;
34239
34953
  const context = await this.assembleConciergeContext(
@@ -34304,7 +35018,8 @@ Note: this is a static reference block (v1.2.x). Dynamic context injection (live
34304
35018
  ...this.memory ? {
34305
35019
  prior_turns_folded: memoryReadFailureReason === null ? priorTurns.length : 0
34306
35020
  } : {},
34307
- ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {}
35021
+ ...this.contextFetchers ? { dynamic_context_categories: [...dynamicCategoriesIncluded] } : {},
35022
+ parsed_grammar: auditSafeSummary(parsedGrammar)
34308
35023
  };
34309
35024
  this.emit(OPERATOR_CHAT_OPS.CONCIERGE_CHAT, payload, outcome === "ok" ? "success" : "failure");
34310
35025
  return {
@@ -34519,8 +35234,12 @@ ${inbox}`
34519
35234
  * proceeds with no fold. Returns the rendered section + the list of
34520
35235
  * categories whose data made it into the section (used for the
34521
35236
  * round-trip audit emission).
35237
+ *
35238
+ * Tau-4: receives the pre-parsed `ParsedQuery` and forwards it as the
35239
+ * `parsed` opt to `foldContext`, so fetchers see the structured
35240
+ * `FetcherHints` derived from it.
34522
35241
  */
34523
- async runDynamicContextFold(query) {
35242
+ async runDynamicContextFold(query, parsedGrammar) {
34524
35243
  if (!this.contextFetchers) {
34525
35244
  return { section: "", categoriesIncluded: [] };
34526
35245
  }
@@ -34529,10 +35248,24 @@ ${inbox}`
34529
35248
  ...this.contextLlmAssist ? { llmAssistClassify: this.contextLlmAssist } : {},
34530
35249
  onFetcherFailure: (category, error) => {
34531
35250
  this.emitContextFetcherFailed(category, classifyFetcherError(error));
34532
- }
35251
+ },
35252
+ parsed: parsedGrammar
34533
35253
  });
34534
35254
  return result;
34535
35255
  }
35256
+ /**
35257
+ * WP-V1.3-9 Tau-4: parse the (PII-filtered) operator query into a
35258
+ * `ParsedQuery`. Routes through the LLM-assist completion hook when
35259
+ * configured and the rule-based parse is below
35260
+ * `LLM_ASSIST_THRESHOLD`. Always returns a parse object (never
35261
+ * throws) so the audit emission can carry the result unconditionally.
35262
+ */
35263
+ async runGrammarParse(query) {
35264
+ return parseQueryWithLlmAssist(query, this.grammarLlmAssist, {
35265
+ ...this.agentRegistry !== void 0 ? { registry: this.agentRegistry } : {},
35266
+ eventClassEnum: CANONICAL_AUDIT_EVENT_CLASSES
35267
+ });
35268
+ }
34536
35269
  /**
34537
35270
  * Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
34538
35271
  * of the fold path so the dynamic-context handler stays readable.
@@ -37044,7 +37777,7 @@ var init_memory = __esm({
37044
37777
 
37045
37778
  // src/contracts/v1.1/constants.ts
37046
37779
  var SIGNATURE_SCHEME_V12, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
37047
- var init_constants4 = __esm({
37780
+ var init_constants5 = __esm({
37048
37781
  "src/contracts/v1.1/constants.ts"() {
37049
37782
  SIGNATURE_SCHEME_V12 = "ed25519-v1";
37050
37783
  EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
@@ -37462,7 +38195,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
37462
38195
  var InvalidExitBundleError, PRIVATE_MATERIAL_KEYS;
37463
38196
  var init_verifier2 = __esm({
37464
38197
  "src/exit/verifier.ts"() {
37465
- init_constants4();
38198
+ init_constants5();
37466
38199
  init_exit_bundle_manifest();
37467
38200
  init_encoding();
37468
38201
  init_hashing();
@@ -38274,7 +39007,7 @@ var init_bundle = __esm({
38274
39007
  "src/exit/bundle.ts"() {
38275
39008
  init_state_store();
38276
39009
  init_config();
38277
- init_constants4();
39010
+ init_constants5();
38278
39011
  init_canonical_json();
38279
39012
  init_hashing();
38280
39013
  init_encoding();