@sanctuary-framework/mcp-server 1.1.6 → 1.2.0
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/README.md +112 -52
- package/dist/cli.cjs +4819 -227
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4817 -229
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +4632 -394
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1072 -14
- package/dist/index.d.ts +1072 -14
- package/dist/index.js +4630 -396
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.d.ts
CHANGED
|
@@ -2965,6 +2965,9 @@ type HubDisplayTemplateArg = {
|
|
|
2965
2965
|
} | {
|
|
2966
2966
|
kind: "policy_id";
|
|
2967
2967
|
value: string;
|
|
2968
|
+
} | {
|
|
2969
|
+
kind: "channel_template_id";
|
|
2970
|
+
value: string;
|
|
2968
2971
|
} | {
|
|
2969
2972
|
kind: "destination_category";
|
|
2970
2973
|
value: PrivacyDestinationCategory;
|
|
@@ -4130,10 +4133,981 @@ type HubInboxAction = (typeof HUB_INBOX_ACTIONS)[number];
|
|
|
4130
4133
|
declare const HUB_AGENT_CONTROL_ACTIONS: readonly ["pause", "resume", "restart", "unwrap", "lockdown"];
|
|
4131
4134
|
type HubAgentControlAction = (typeof HUB_AGENT_CONTROL_ACTIONS)[number];
|
|
4132
4135
|
|
|
4133
|
-
/** Channel-template identifiers per
|
|
4134
|
-
declare const CHANNEL_TEMPLATE_IDS: readonly ["
|
|
4136
|
+
/** Channel-template identifiers per v1.2 design-canonical starter set. */
|
|
4137
|
+
declare const CHANNEL_TEMPLATE_IDS: readonly ["request-approve-act", "read-then-report", "scheduled-digest", "plan-draft-only", "fortress-relay", "concierge-loop"];
|
|
4135
4138
|
type ChannelTemplateId = (typeof CHANNEL_TEMPLATE_IDS)[number];
|
|
4136
4139
|
|
|
4140
|
+
/**
|
|
4141
|
+
* Sanctuary MCP Server — Intelligence Substrate Selector Types
|
|
4142
|
+
*
|
|
4143
|
+
* The substrate selector lets operators pick (and re-pick) the LLM substrate
|
|
4144
|
+
* that powers Sanctuary's intelligence-layer surfaces: concierge, sentinel
|
|
4145
|
+
* scoring, gate explanation, privacy filter Tier 2, template suggestion,
|
|
4146
|
+
* direct-agent gate advisory.
|
|
4147
|
+
*
|
|
4148
|
+
* Per Erik directive 2026-04-29: the selector IS the architecture. Sanctuary's
|
|
4149
|
+
* job is to make the tradeoffs visible and let the operator choose. Multi-option
|
|
4150
|
+
* framing is preserved; no single substrate is surfaced as a winner.
|
|
4151
|
+
*
|
|
4152
|
+
* Per the position paper Intelligence_Layer_Drift_Audit_and_Substrate_Resolution_2026-04-29.md
|
|
4153
|
+
* (sections 4 + 5), four substrates ship as operator-pickable:
|
|
4154
|
+
*
|
|
4155
|
+
* - local: Ollama-hosted local model (Gemma 2 2B / Phi-4 Mini / Llama 3.1 8B)
|
|
4156
|
+
* - venice: Venice.ai privacy-respecting hosted relay
|
|
4157
|
+
* - frontier-with-filter: operator's frontier API account, Privacy Filter Tier 2 pre-egress
|
|
4158
|
+
* - hybrid: per-surface routing across the above
|
|
4159
|
+
* - disabled: surface refuses LLM calls; Tier 1 regex fallback for privacy filter
|
|
4160
|
+
*
|
|
4161
|
+
* Per-surface defaults (Erik ratification 2026-04-29):
|
|
4162
|
+
* - concierge: local (Gemma 2 2B)
|
|
4163
|
+
* - sentinel-scoring: local (rules + Phi-4 Mini on escalation)
|
|
4164
|
+
* - gate-explanation: templates v1.2; small LLM v1.3+ for custom policies
|
|
4165
|
+
* - privacy-filter-tier-2: local (NER + small LLM PII classifier)
|
|
4166
|
+
* - direct-agent-gate-advisor: same as concierge by default
|
|
4167
|
+
* - template-suggestion: same as concierge by default
|
|
4168
|
+
*/
|
|
4169
|
+
/**
|
|
4170
|
+
* The set of intelligence-layer surfaces the selector routes for.
|
|
4171
|
+
*
|
|
4172
|
+
* The enum is closed. Adding a surface requires updating the selector's
|
|
4173
|
+
* default-config builder, the audit emission shape registry, and the
|
|
4174
|
+
* transparency UI in the same PR.
|
|
4175
|
+
*/
|
|
4176
|
+
type Surface = "concierge" | "direct-agent-gate-advisor" | "sentinel-scoring" | "gate-explanation" | "privacy-filter-tier-2" | "template-suggestion";
|
|
4177
|
+
/**
|
|
4178
|
+
* The substrate choice an operator may bind to a surface.
|
|
4179
|
+
*/
|
|
4180
|
+
type SubstrateChoice = "local" | "venice" | "frontier-with-filter" | "hybrid" | "disabled";
|
|
4181
|
+
/**
|
|
4182
|
+
* Local-model picks for the Ollama-backed local substrate.
|
|
4183
|
+
*
|
|
4184
|
+
* Bundled tiers per position paper §5 hardware-tier table:
|
|
4185
|
+
* - gemma-2-2b (Baseline / 8GB RAM)
|
|
4186
|
+
* - phi-4-mini (Mid / 16GB RAM)
|
|
4187
|
+
* - llama-3.1-8b (Pro / 32GB+ RAM)
|
|
4188
|
+
*
|
|
4189
|
+
* Custom Ollama-installed models can be specified via `customModelTag` on the
|
|
4190
|
+
* substrate config; the selector probes Ollama for availability at runtime.
|
|
4191
|
+
*/
|
|
4192
|
+
type LocalModelPick = "gemma-2-2b" | "phi-4-mini" | "llama-3.1-8b";
|
|
4193
|
+
/**
|
|
4194
|
+
* Frontier providers operator may pick for the frontier-with-filter substrate.
|
|
4195
|
+
* v1.2 ships three; v1.3+ may add Mistral / xAI / Cohere.
|
|
4196
|
+
*/
|
|
4197
|
+
type FrontierProvider = "anthropic" | "openai" | "google";
|
|
4198
|
+
/**
|
|
4199
|
+
* What the selector does when its chosen substrate fails (e.g. Ollama not
|
|
4200
|
+
* running, Venice API unreachable, frontier API key revoked).
|
|
4201
|
+
*
|
|
4202
|
+
* Per surface, operator-configurable. Defaults per position paper §5:
|
|
4203
|
+
* - concierge: degrade-silent (next-tier substrate)
|
|
4204
|
+
* - sentinel-scoring: conservative-deny (refuse rather than route elsewhere)
|
|
4205
|
+
* - privacy-filter-tier-2: degrade-silent (Tier 1 regex always works)
|
|
4206
|
+
* - others: conservative-deny
|
|
4207
|
+
*/
|
|
4208
|
+
type FallbackBehavior = "conservative-deny" | "degrade-silent" | "disable-surface";
|
|
4209
|
+
/**
|
|
4210
|
+
* Per-surface routing rules for the hybrid substrate.
|
|
4211
|
+
*
|
|
4212
|
+
* v1.2 ships per-surface routing only — operator pre-binds each surface to a
|
|
4213
|
+
* concrete substrate. Per-query sensitivity classification routing is deferred
|
|
4214
|
+
* to v1.3+.
|
|
4215
|
+
*/
|
|
4216
|
+
interface HybridRoutingRules {
|
|
4217
|
+
perSurface: Record<Surface, Exclude<SubstrateChoice, "hybrid">>;
|
|
4218
|
+
}
|
|
4219
|
+
/**
|
|
4220
|
+
* Operator-readable status badge for the transparency UI and chat headers.
|
|
4221
|
+
*
|
|
4222
|
+
* Backends emit a stable shape (no free-form prose); the dashboard's template
|
|
4223
|
+
* registry renders it. This mirrors the v1.1 hub `HubDisplayTemplateArg`
|
|
4224
|
+
* discipline so secret-leakage into operator-facing copy stays structurally
|
|
4225
|
+
* impossible.
|
|
4226
|
+
*/
|
|
4227
|
+
interface SubstrateBadge {
|
|
4228
|
+
surface: Surface;
|
|
4229
|
+
substrate: SubstrateChoice;
|
|
4230
|
+
/** Stable badge label key resolved by dashboard template registry. */
|
|
4231
|
+
labelKey: string;
|
|
4232
|
+
/** Stable tradeoff body key resolved by dashboard template registry. */
|
|
4233
|
+
tradeoffKey: string;
|
|
4234
|
+
/** Stable status; green = working; yellow = degraded; red = disabled / failing. */
|
|
4235
|
+
status: "green" | "yellow" | "red";
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* Frontier provider configs. Operator's API key per provider is stored
|
|
4239
|
+
* encrypted; never leaves the fortress except as outbound HTTPS to the
|
|
4240
|
+
* provider after Privacy Filter Tier 2 redaction.
|
|
4241
|
+
*/
|
|
4242
|
+
interface FrontierProviderConfig {
|
|
4243
|
+
/** Operator's per-provider API key (encrypted at rest). */
|
|
4244
|
+
anthropic?: string;
|
|
4245
|
+
openai?: string;
|
|
4246
|
+
google?: string;
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Operator-overridable substrate config. Persisted encrypted under the
|
|
4250
|
+
* fortress master key in storage namespace `_intelligence`.
|
|
4251
|
+
*
|
|
4252
|
+
* v1.0 of this shape (`version: 1`) is the only shape this PR ships. Future
|
|
4253
|
+
* v1.x extensions (per-query sensitivity classifier, MLX runtime pick,
|
|
4254
|
+
* per-patron substrate scoping) MUST bump this version and provide a
|
|
4255
|
+
* forward-compat migration in the selector.
|
|
4256
|
+
*/
|
|
4257
|
+
interface SubstrateConfig {
|
|
4258
|
+
version: 1;
|
|
4259
|
+
/** Operator's per-surface choice. Defaults from position paper §5. */
|
|
4260
|
+
perSurface: Record<Surface, SubstrateChoice>;
|
|
4261
|
+
/** Operator's local-model picks per surface, when 'local' is chosen. */
|
|
4262
|
+
localModelPicks: Partial<Record<Surface, LocalModelPick>>;
|
|
4263
|
+
/** Custom Ollama model tag override per surface (overrides localModelPicks if set). */
|
|
4264
|
+
customLocalModelTags?: Partial<Record<Surface, string>>;
|
|
4265
|
+
/** Ollama HTTP endpoint; defaults to http://localhost:11434. */
|
|
4266
|
+
ollamaEndpoint?: string;
|
|
4267
|
+
/** Venice API key, if 'venice' chosen for any surface. Encrypted at rest. */
|
|
4268
|
+
veniceApiKey?: string;
|
|
4269
|
+
/** Venice model selection; defaults to llama-3.1-70b. */
|
|
4270
|
+
veniceModel?: string;
|
|
4271
|
+
/** Frontier provider configs, if 'frontier-with-filter' chosen. */
|
|
4272
|
+
frontierConfig: FrontierProviderConfig;
|
|
4273
|
+
/** Hybrid routing rules, if 'hybrid' chosen for any surface. */
|
|
4274
|
+
hybridRules?: HybridRoutingRules;
|
|
4275
|
+
/** Fallback behavior per surface. */
|
|
4276
|
+
fallback: Record<Surface, FallbackBehavior>;
|
|
4277
|
+
/**
|
|
4278
|
+
* Operator preference for the picker modal: when true, picking a
|
|
4279
|
+
* substrate + key for any one surface fans out to every surface in one
|
|
4280
|
+
* save (Finding SS, v1.2.0-rc.1). Defaults to true; operators wanting
|
|
4281
|
+
* per-surface granularity flip the picker toggle off and the next
|
|
4282
|
+
* single-surface choice persists this back to false.
|
|
4283
|
+
*/
|
|
4284
|
+
applyToAllSurfaces?: boolean;
|
|
4285
|
+
/** ISO8601 timestamp of last operator change. */
|
|
4286
|
+
updatedAt: string;
|
|
4287
|
+
}
|
|
4288
|
+
interface SummarizeRequest {
|
|
4289
|
+
kind: "summarize";
|
|
4290
|
+
context: string;
|
|
4291
|
+
query: string;
|
|
4292
|
+
maxTokens?: number;
|
|
4293
|
+
}
|
|
4294
|
+
interface ClassifyRequest {
|
|
4295
|
+
kind: "classify";
|
|
4296
|
+
items: string[];
|
|
4297
|
+
categories: string[];
|
|
4298
|
+
maxTokens?: number;
|
|
4299
|
+
}
|
|
4300
|
+
interface RedactRequest {
|
|
4301
|
+
kind: "redact";
|
|
4302
|
+
text: string;
|
|
4303
|
+
}
|
|
4304
|
+
/**
|
|
4305
|
+
* Per-substrate response envelope. Substrate clients return content + telemetry;
|
|
4306
|
+
* audit-emission and badge-rendering happen at the selector layer.
|
|
4307
|
+
*/
|
|
4308
|
+
interface SubstrateResponse {
|
|
4309
|
+
/** The substrate that actually served this invocation (may differ from chosen if fallback fired). */
|
|
4310
|
+
servedBy: SubstrateChoice;
|
|
4311
|
+
/** Stable failure-class enum on failure; null on success. */
|
|
4312
|
+
failureClass: SubstrateFailureClass | null;
|
|
4313
|
+
/** Body shape varies by request kind. */
|
|
4314
|
+
body: {
|
|
4315
|
+
kind: "summarize";
|
|
4316
|
+
text: string;
|
|
4317
|
+
} | {
|
|
4318
|
+
kind: "classify";
|
|
4319
|
+
results: {
|
|
4320
|
+
category: string;
|
|
4321
|
+
confidence: number;
|
|
4322
|
+
}[];
|
|
4323
|
+
} | {
|
|
4324
|
+
kind: "redact";
|
|
4325
|
+
redacted: string;
|
|
4326
|
+
placeholders: Record<string, string>;
|
|
4327
|
+
} | {
|
|
4328
|
+
kind: "failure";
|
|
4329
|
+
message: string;
|
|
4330
|
+
};
|
|
4331
|
+
/** ISO8601 of when invocation completed. */
|
|
4332
|
+
completedAt: string;
|
|
4333
|
+
/** Total wall-clock latency in ms. */
|
|
4334
|
+
latencyMs: number;
|
|
4335
|
+
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Stable failure-class enum. Backends return one of these when a substrate
|
|
4338
|
+
* invocation fails; the selector's audit emission carries this verbatim.
|
|
4339
|
+
*
|
|
4340
|
+
* Adding a new failure class requires updating the audit-event consumer and
|
|
4341
|
+
* the dashboard status-mapping in the same PR.
|
|
4342
|
+
*/
|
|
4343
|
+
type SubstrateFailureClass = "substrate_unavailable" | "substrate_misconfigured" | "substrate_rate_limited" | "substrate_auth_failed" | "substrate_timeout" | "substrate_capability_unsupported" | "substrate_disabled" | "internal_error";
|
|
4344
|
+
/**
|
|
4345
|
+
* Capability profile for each substrate. Surfaces consult this before
|
|
4346
|
+
* invoking; if the substrate cannot serve the request kind, the selector
|
|
4347
|
+
* applies the fallback policy.
|
|
4348
|
+
*/
|
|
4349
|
+
interface SubstrateCapability {
|
|
4350
|
+
summarize: boolean;
|
|
4351
|
+
classify: boolean;
|
|
4352
|
+
redact: boolean;
|
|
4353
|
+
}
|
|
4354
|
+
/**
|
|
4355
|
+
* The handle a surface holds after `selector.getSubstrate(surface)`. The
|
|
4356
|
+
* surface invokes via the typed methods; the substrate client implements them.
|
|
4357
|
+
*
|
|
4358
|
+
* Surfaces MUST treat any thrown error as a `substrate_unavailable` failure;
|
|
4359
|
+
* the selector's invoke wrapper catches and emits the audit event.
|
|
4360
|
+
*/
|
|
4361
|
+
interface SubstrateHandle {
|
|
4362
|
+
surface: Surface;
|
|
4363
|
+
substrate: SubstrateChoice;
|
|
4364
|
+
badge: SubstrateBadge;
|
|
4365
|
+
capability: SubstrateCapability;
|
|
4366
|
+
/**
|
|
4367
|
+
* Optional operator-readable display string for the chat / dashboard headers.
|
|
4368
|
+
* Resolved from the badge labelKey; produced by the selector at handle issue,
|
|
4369
|
+
* not by the substrate client.
|
|
4370
|
+
*/
|
|
4371
|
+
displayLabel: string;
|
|
4372
|
+
summarize?: (req: SummarizeRequest) => Promise<SubstrateResponse>;
|
|
4373
|
+
classify?: (req: ClassifyRequest) => Promise<SubstrateResponse>;
|
|
4374
|
+
redact?: (req: RedactRequest) => Promise<SubstrateResponse>;
|
|
4375
|
+
}
|
|
4376
|
+
/**
|
|
4377
|
+
* Operator-visible per-surface status report rendered by the transparency UI.
|
|
4378
|
+
*
|
|
4379
|
+
* The dashboard SPA consumes this via the `/api/hub/intelligence/status`
|
|
4380
|
+
* route; the picker modal consumes it before substrate selection to surface
|
|
4381
|
+
* hardware-capability hints.
|
|
4382
|
+
*/
|
|
4383
|
+
interface SubstrateStatusReport {
|
|
4384
|
+
version: "1.2";
|
|
4385
|
+
generatedAt: string;
|
|
4386
|
+
surfaces: SurfaceStatus[];
|
|
4387
|
+
hardware: HardwareCapabilityReport;
|
|
4388
|
+
}
|
|
4389
|
+
interface SurfaceStatus {
|
|
4390
|
+
surface: Surface;
|
|
4391
|
+
chosen: SubstrateChoice;
|
|
4392
|
+
badge: SubstrateBadge;
|
|
4393
|
+
/** Substrate-side health probe result. */
|
|
4394
|
+
health: "ok" | "degraded" | "unavailable";
|
|
4395
|
+
/** Stable failure class when health != "ok"; null when "ok". */
|
|
4396
|
+
failureClass: SubstrateFailureClass | null;
|
|
4397
|
+
/**
|
|
4398
|
+
* Recent runtime + validation failures for this surface, capped at 5
|
|
4399
|
+
* entries and time-windowed to 24 hours. Sourced from real chat-call
|
|
4400
|
+
* outcomes (and validateKey results for substrates that expose it),
|
|
4401
|
+
* NOT from the static probe-time misconfig check.
|
|
4402
|
+
*
|
|
4403
|
+
* Per Finding VV (v1.2.0-rc.1): the operator-visible badge degrades to
|
|
4404
|
+
* "yellow" / "degraded" whenever this array is non-empty even if the
|
|
4405
|
+
* static probe still says ok. This closes the truth-telling gap where
|
|
4406
|
+
* the dashboard reported all surfaces healthy after the runtime chat
|
|
4407
|
+
* had already failed against the same substrate.
|
|
4408
|
+
*/
|
|
4409
|
+
recentFailures: RecentFailureEntry[];
|
|
4410
|
+
}
|
|
4411
|
+
/**
|
|
4412
|
+
* One observed substrate failure surfaced via `/api/hub/intelligence/status`.
|
|
4413
|
+
* Carries enough metadata for the operator to triage (when, what failure
|
|
4414
|
+
* class, brief operator-safe snippet) without leaking request bodies,
|
|
4415
|
+
* response bodies, or operator credentials.
|
|
4416
|
+
*
|
|
4417
|
+
* Snippets are bounded human-readable strings (e.g. "venice configured
|
|
4418
|
+
* model not found") drawn from the substrate failure-message field; the
|
|
4419
|
+
* selector strips anything resembling an API key or PII before retention.
|
|
4420
|
+
* Operators wanting full forensic detail consult the L2 audit log; this
|
|
4421
|
+
* surface is the at-a-glance triage path.
|
|
4422
|
+
*/
|
|
4423
|
+
interface RecentFailureEntry {
|
|
4424
|
+
/** ISO8601 timestamp of when the failure was observed. */
|
|
4425
|
+
ts: string;
|
|
4426
|
+
/** Stable failure-class enum carried verbatim from the substrate response. */
|
|
4427
|
+
failureClass: SubstrateFailureClass;
|
|
4428
|
+
/** Bounded operator-safe snippet describing the failure. */
|
|
4429
|
+
snippet: string;
|
|
4430
|
+
}
|
|
4431
|
+
interface HardwareCapabilityReport {
|
|
4432
|
+
/** Detected total RAM in GB. Self-reported via os.totalmem(). */
|
|
4433
|
+
totalRamGb: number;
|
|
4434
|
+
/** Apple Silicon family if detectable, or "other". */
|
|
4435
|
+
cpuArch: "apple-silicon-m1" | "apple-silicon-m2" | "apple-silicon-m3" | "apple-silicon-m4" | "apple-silicon-other" | "x86_64" | "other";
|
|
4436
|
+
/** Computed tier per position paper §5 (baseline / mid / pro / below-baseline). */
|
|
4437
|
+
tier: "below-baseline" | "baseline" | "mid" | "pro";
|
|
4438
|
+
/** Recommended local model tier per detected hardware. */
|
|
4439
|
+
recommendedLocalModel: LocalModelPick | null;
|
|
4440
|
+
/** Whether Ollama is reachable at the configured endpoint. */
|
|
4441
|
+
ollamaReachable: boolean;
|
|
4442
|
+
/** Models present in Ollama at the time of the report. Empty if not reachable. */
|
|
4443
|
+
ollamaModels: string[];
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
/**
|
|
4447
|
+
* Sanctuary MCP Server — Frontier-with-Filter Substrate
|
|
4448
|
+
*
|
|
4449
|
+
* Operator's frontier API account (Anthropic / OpenAI / Google) wrapped with
|
|
4450
|
+
* mandatory pre-egress Privacy Filter Tier 2 redaction.
|
|
4451
|
+
*
|
|
4452
|
+
* Per position paper §5: every query routes through Privacy Filter Tier 2
|
|
4453
|
+
* BEFORE the frontier API call. The Privacy Filter event log captures the
|
|
4454
|
+
* pre-redaction match count so the operator can audit what was sent.
|
|
4455
|
+
*
|
|
4456
|
+
* Honesty discipline (CLAUDE.md):
|
|
4457
|
+
* - Tradeoff badge text explicitly names the leakage
|
|
4458
|
+
* - Required caveat about subtle PII (paraphrased addresses, contextual
|
|
4459
|
+
* identifiers) appears in tradeoff text
|
|
4460
|
+
* - Operator MUST acknowledge before configuration completes (enforced
|
|
4461
|
+
* by the picker modal in the dashboard SPA, deferred to the v1.2 UI
|
|
4462
|
+
* follow-up commit)
|
|
4463
|
+
*
|
|
4464
|
+
* Provider clients are kept minimal: one Chat Completions / Messages call
|
|
4465
|
+
* per surface request, no streaming in v1.2 (streaming is a v1.3 chat
|
|
4466
|
+
* surface enhancement).
|
|
4467
|
+
*/
|
|
4468
|
+
|
|
4469
|
+
/**
|
|
4470
|
+
* Pre-egress redaction hook. The substrate adapter calls this on every
|
|
4471
|
+
* outbound text; returns the redacted text + placeholder map. The Privacy
|
|
4472
|
+
* Filter Tier 2 implementation lives in `l2-operational/privacy-filter.ts`
|
|
4473
|
+
* (extended in this PR to wire the substrate-aware Tier 2 path).
|
|
4474
|
+
*
|
|
4475
|
+
* For v1.2, the redactor passed in is responsible for emitting the audit
|
|
4476
|
+
* event `intelligence_pii_redaction_event` via the selector's auditLog
|
|
4477
|
+
* binding. The frontier substrate is provider-agnostic about how redaction
|
|
4478
|
+
* happens; it just refuses to call the frontier API on `failureClass !== null`.
|
|
4479
|
+
*/
|
|
4480
|
+
type FrontierRedactor = (text: string) => Promise<{
|
|
4481
|
+
redacted: string;
|
|
4482
|
+
matchCount: number;
|
|
4483
|
+
}>;
|
|
4484
|
+
|
|
4485
|
+
/**
|
|
4486
|
+
* Sanctuary MCP Server — Intelligence Substrate Selector
|
|
4487
|
+
*
|
|
4488
|
+
* The selector IS the architecture (Erik directive 2026-04-29). Operators
|
|
4489
|
+
* pick (and re-pick) the LLM substrate that powers each intelligence-layer
|
|
4490
|
+
* surface; the selector binds choices to surfaces, provides a typed
|
|
4491
|
+
* `SubstrateHandle` to consumers, applies operator-configured fallback
|
|
4492
|
+
* behavior on substrate failure, and emits audit events for every change
|
|
4493
|
+
* and every invocation.
|
|
4494
|
+
*
|
|
4495
|
+
* Architecture per position paper section 5:
|
|
4496
|
+
* ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
4497
|
+
* │ concierge│ │ sentinel │ │ gate-expl│ │ priv-f-2 │ ... surfaces
|
|
4498
|
+
* └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
4499
|
+
* ▼ ▼ ▼ ▼
|
|
4500
|
+
* ┌──────────────────────────────────────────────────┐
|
|
4501
|
+
* │ SubstrateSelector (per-surface routing) │
|
|
4502
|
+
* └────┬──────────┬──────────┬──────────┬────────────┘
|
|
4503
|
+
* ▼ ▼ ▼ ▼
|
|
4504
|
+
* ┌────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
4505
|
+
* │ Local │ │ Venice │ │ Frontier │ │ Disabled │
|
|
4506
|
+
* │(Ollama)│ │ (relay) │ │ + filter │ │ (refuse) │
|
|
4507
|
+
* └────────┘ └──────────┘ └──────────┘ └──────────┘
|
|
4508
|
+
*
|
|
4509
|
+
* The hybrid substrate is per-surface routing only at v1.2 — operator
|
|
4510
|
+
* pre-binds each surface to a concrete substrate; per-query sensitivity
|
|
4511
|
+
* classification routing is deferred to v1.3+.
|
|
4512
|
+
*
|
|
4513
|
+
* Audit emission discipline:
|
|
4514
|
+
* Every public method that changes config or invokes a substrate appends
|
|
4515
|
+
* an `IntelligenceAuditPayload` to the L2 audit log. The selector never
|
|
4516
|
+
* stores raw request bodies, response bodies, or operator credentials in
|
|
4517
|
+
* audit details; only safe metadata (surface, substrate, hashes, latency,
|
|
4518
|
+
* failure-class enum). The event payload contracts live in
|
|
4519
|
+
* `contracts/v1.2/intelligence-events.ts`.
|
|
4520
|
+
*
|
|
4521
|
+
* Sovereignty invariants preserved:
|
|
4522
|
+
* - Operator API keys live in the encrypted SubstrateConfig persisted
|
|
4523
|
+
* under the fortress master key.
|
|
4524
|
+
* - Frontier-with-filter substrate ALWAYS routes through a redactor
|
|
4525
|
+
* before any frontier API call; the redactor argument is required in
|
|
4526
|
+
* the constructor; passing a null redactor disables the substrate.
|
|
4527
|
+
* - Sentinel-scoring surface defaults to `conservative-deny` fallback so
|
|
4528
|
+
* substrate failure cannot silently allow an unverified tool call.
|
|
4529
|
+
*/
|
|
4530
|
+
|
|
4531
|
+
/**
|
|
4532
|
+
* Configuration the selector needs at construction. The audit-log binding
|
|
4533
|
+
* is required: a selector without an audit log violates the audit-emission
|
|
4534
|
+
* invariant and is structurally rejected.
|
|
4535
|
+
*/
|
|
4536
|
+
interface SelectorConfig {
|
|
4537
|
+
storage: StorageBackend;
|
|
4538
|
+
masterKey: Uint8Array;
|
|
4539
|
+
auditLog: AuditLog;
|
|
4540
|
+
/** Identity that owns the substrate config. Stamped on every audit event. */
|
|
4541
|
+
identityId: string;
|
|
4542
|
+
/**
|
|
4543
|
+
* Pre-egress redactor for the frontier-with-filter substrate. Defaults
|
|
4544
|
+
* to `IDENTITY_REDACTOR` until Privacy Filter Tier 2 wires through.
|
|
4545
|
+
* Passing a custom redactor at construction allows tests + the Tier 2
|
|
4546
|
+
* commit to install a real implementation without modifying the
|
|
4547
|
+
* selector.
|
|
4548
|
+
*/
|
|
4549
|
+
redactor?: FrontierRedactor;
|
|
4550
|
+
/**
|
|
4551
|
+
* Optional fetch override for substrate clients. Forwarded to Ollama,
|
|
4552
|
+
* Venice, and frontier client constructors so tests can stub out
|
|
4553
|
+
* network calls.
|
|
4554
|
+
*/
|
|
4555
|
+
fetchImpl?: typeof fetch;
|
|
4556
|
+
}
|
|
4557
|
+
declare class SubstrateSelector {
|
|
4558
|
+
private store;
|
|
4559
|
+
private auditLog;
|
|
4560
|
+
private identityId;
|
|
4561
|
+
private redactor;
|
|
4562
|
+
private fetchImpl;
|
|
4563
|
+
private config;
|
|
4564
|
+
private loaded;
|
|
4565
|
+
/**
|
|
4566
|
+
* Per-surface ring buffer of recent runtime + validation failures. See
|
|
4567
|
+
* `RECENT_FAILURES_CAP` and `RECENT_FAILURES_WINDOW_MS`. Populated by
|
|
4568
|
+
* `recordRecentFailure()` from the `invoke()` failure path and from the
|
|
4569
|
+
* post-config validation hook on substrates that expose validateKey.
|
|
4570
|
+
* Drives the operator-visible degrade in `getOperatorVisibleStatus()`.
|
|
4571
|
+
*/
|
|
4572
|
+
private recentFailures;
|
|
4573
|
+
constructor(cfg: SelectorConfig);
|
|
4574
|
+
/**
|
|
4575
|
+
* Load (or initialize) the operator's substrate config. Emits the
|
|
4576
|
+
* `intelligence_config_loaded` audit event regardless of branch so the
|
|
4577
|
+
* audit chain shows config-load activity on boot.
|
|
4578
|
+
*/
|
|
4579
|
+
load(): Promise<void>;
|
|
4580
|
+
/**
|
|
4581
|
+
* Operator picked (or re-picked) a substrate for a surface. Persists the
|
|
4582
|
+
* change and emits `intelligence_substrate_chosen`. The picker modal
|
|
4583
|
+
* surfaces the tradeoff text BEFORE invoking this method; the audit
|
|
4584
|
+
* payload captures the hash of that text for auditor verification.
|
|
4585
|
+
*/
|
|
4586
|
+
setPerSurfaceChoice(surface: Surface, substrate: SubstrateChoice): Promise<void>;
|
|
4587
|
+
/**
|
|
4588
|
+
* Apply a substrate choice to every surface in one save. Used by the
|
|
4589
|
+
* picker modal's "Apply to all surfaces" affordance (Finding SS,
|
|
4590
|
+
* v1.2.0-rc.1). Persists the per-surface map mutation in a single
|
|
4591
|
+
* write and emits ONE `intelligence_bulk_substrate_chosen` audit
|
|
4592
|
+
* event rather than six per-surface events.
|
|
4593
|
+
*
|
|
4594
|
+
* If `localModelPick` is provided AND substrate === "local", the same
|
|
4595
|
+
* pick is applied to every surface; ignored for other substrates.
|
|
4596
|
+
*
|
|
4597
|
+
* Per-surface bindings the operator had previously customized are
|
|
4598
|
+
* captured in the audit payload's `prior_substrates` map so the
|
|
4599
|
+
* forensic record makes the configuration discontinuity visible.
|
|
4600
|
+
*/
|
|
4601
|
+
applyChoiceToAllSurfaces(substrate: SubstrateChoice, opts?: {
|
|
4602
|
+
localModelPick?: LocalModelPick | null;
|
|
4603
|
+
}): Promise<void>;
|
|
4604
|
+
/**
|
|
4605
|
+
* Persist the operator's "Apply to all surfaces" preference. The
|
|
4606
|
+
* picker modal calls this when the operator flips the toggle so the
|
|
4607
|
+
* preference survives a dashboard reload. Does not emit a discrete
|
|
4608
|
+
* audit event; the next bulk or per-surface choice carries the
|
|
4609
|
+
* operator-visible signal.
|
|
4610
|
+
*/
|
|
4611
|
+
setApplyToAllPreference(value: boolean): Promise<void>;
|
|
4612
|
+
/**
|
|
4613
|
+
* Operator picked a local model for a surface bound to the local
|
|
4614
|
+
* substrate. Mirrors `setPerSurfaceChoice` for the model-picker case.
|
|
4615
|
+
* Does not emit a separate audit event class; the next
|
|
4616
|
+
* `intelligence_substrate_chosen` event captures the change and the
|
|
4617
|
+
* dashboard transparency UI calls this method only as part of a binding
|
|
4618
|
+
* flow that already audited the substrate choice.
|
|
4619
|
+
*/
|
|
4620
|
+
setLocalModelPick(surface: Surface, pick: LocalModelPick | null): Promise<void>;
|
|
4621
|
+
/**
|
|
4622
|
+
* Set the Venice API key. Stored as part of the encrypted
|
|
4623
|
+
* SubstrateConfig record. Does not emit a substrate_chosen event by
|
|
4624
|
+
* itself; the dashboard flow always pairs key entry with a substrate
|
|
4625
|
+
* choice and the choice carries the audit semantics.
|
|
4626
|
+
*
|
|
4627
|
+
* Post-set validation (Finding VV, v1.2.0-rc.1): if a non-null key is
|
|
4628
|
+
* provided, the selector probes Venice with `validateKey()` and records
|
|
4629
|
+
* a failure entry on every Venice-bound surface when the result is not
|
|
4630
|
+
* `"ok"`. This lets the operator-visible status degrade from validation
|
|
4631
|
+
* outcomes alone, not just from runtime chat-call failures, so a broken
|
|
4632
|
+
* key or drifted model is visible before the operator's first chat
|
|
4633
|
+
* attempt. Probe failures (timeout, transport) are not recorded; the
|
|
4634
|
+
* runtime path will surface those when the operator actually invokes.
|
|
4635
|
+
*/
|
|
4636
|
+
setVeniceApiKey(apiKey: string | null): Promise<void>;
|
|
4637
|
+
/**
|
|
4638
|
+
* Probe Venice with the given API key and record a failure entry on
|
|
4639
|
+
* every surface bound to Venice when the probe returns anything other
|
|
4640
|
+
* than `"ok"`. Failures map to stable substrate failure-class enum
|
|
4641
|
+
* values: `invalid-key` -> `substrate_auth_failed`, `invalid-model` ->
|
|
4642
|
+
* `substrate_misconfigured`, `unreachable` -> swallow (runtime path
|
|
4643
|
+
* will surface).
|
|
4644
|
+
*/
|
|
4645
|
+
private validateVeniceAndRecord;
|
|
4646
|
+
/**
|
|
4647
|
+
* Set a frontier provider's API key. See `setVeniceApiKey` for the
|
|
4648
|
+
* audit-semantics rationale.
|
|
4649
|
+
*/
|
|
4650
|
+
setFrontierApiKey(provider: FrontierProvider, apiKey: string | null): Promise<void>;
|
|
4651
|
+
/**
|
|
4652
|
+
* Set the hybrid routing rules. The picker modal calls this when the
|
|
4653
|
+
* operator saves the hybrid configuration tab. Validates the rules
|
|
4654
|
+
* (every surface bound, no `hybrid` recursion) before persist; throws
|
|
4655
|
+
* on invalid input so the operator-facing form can surface the failure.
|
|
4656
|
+
*
|
|
4657
|
+
* Setting rules does NOT change any surface's per-surface choice; the
|
|
4658
|
+
* operator must also pick `hybrid` for the surfaces they want routed
|
|
4659
|
+
* through these rules. This separation keeps the flow obvious in the
|
|
4660
|
+
* UI (set rules + then choose hybrid for each surface that should use
|
|
4661
|
+
* them) and lets the operator test rule changes without mass-flipping
|
|
4662
|
+
* surfaces to hybrid.
|
|
4663
|
+
*/
|
|
4664
|
+
setHybridRules(rules: HybridRoutingRules): Promise<void>;
|
|
4665
|
+
/**
|
|
4666
|
+
* Override the per-surface fallback behavior. Defaults are set per
|
|
4667
|
+
* position paper section 5; the picker modal surfaces the tradeoff and
|
|
4668
|
+
* the operator can override.
|
|
4669
|
+
*/
|
|
4670
|
+
setFallbackBehavior(surface: Surface, fallback: FallbackBehavior): Promise<void>;
|
|
4671
|
+
/**
|
|
4672
|
+
* Reset every binding to the per-surface defaults. Emits
|
|
4673
|
+
* `intelligence_config_reset` so post-reset audit trails make the
|
|
4674
|
+
* configuration discontinuity visible.
|
|
4675
|
+
*/
|
|
4676
|
+
resetToDefaults(): Promise<void>;
|
|
4677
|
+
/**
|
|
4678
|
+
* Snapshot the current config (read-only). Tests + the dashboard
|
|
4679
|
+
* transparency UI consume this; mutations always go through the
|
|
4680
|
+
* setter methods so audit-emission stays consistent.
|
|
4681
|
+
*/
|
|
4682
|
+
getConfig(): SubstrateConfig;
|
|
4683
|
+
/**
|
|
4684
|
+
* Install (or replace) the redactor used by the frontier-with-filter
|
|
4685
|
+
* substrate. Bootstrap code calls this after constructing the selector
|
|
4686
|
+
* with the default IDENTITY_REDACTOR; the Privacy Filter Tier 2
|
|
4687
|
+
* commit's `buildPrivacyTier2Redactor` produces the redactor that
|
|
4688
|
+
* closes over the selector for audit emission. Late-binding via this
|
|
4689
|
+
* method avoids the circular construction dependency
|
|
4690
|
+
* (selector needs redactor; redactor needs selector for emit).
|
|
4691
|
+
*
|
|
4692
|
+
* Subsequent calls to getSubstrate("...frontier-with-filter") will use
|
|
4693
|
+
* the installed redactor; already-issued handles continue to use the
|
|
4694
|
+
* redactor that was installed at handle-issue time. Consumers that
|
|
4695
|
+
* cache handles SHOULD re-issue after installRedactor.
|
|
4696
|
+
*/
|
|
4697
|
+
installRedactor(redactor: FrontierRedactor): void;
|
|
4698
|
+
/**
|
|
4699
|
+
* Build a typed SubstrateHandle for the given surface. Lazily
|
|
4700
|
+
* instantiates the substrate client based on the operator's binding;
|
|
4701
|
+
* the handle carries the substrate label, tradeoff badge, capability
|
|
4702
|
+
* surface, and bound `summarize` / `classify` / `redact` methods that
|
|
4703
|
+
* delegate to the substrate client.
|
|
4704
|
+
*
|
|
4705
|
+
* For the `disabled` substrate, the returned handle has the capability
|
|
4706
|
+
* surface zeroed and no methods bound; consumers checking
|
|
4707
|
+
* `handle.capability.summarize === false` short-circuit to the
|
|
4708
|
+
* surface's static fallback (templates for gate-explanation, Tier 1
|
|
4709
|
+
* regex for privacy-filter-tier-2, etc.).
|
|
4710
|
+
*
|
|
4711
|
+
* For the `hybrid` substrate, this method delegates to the per-surface
|
|
4712
|
+
* routing rules (set via `setPerSurfaceChoice` on a substrate other
|
|
4713
|
+
* than `hybrid`); v1.2 ships per-surface routing only.
|
|
4714
|
+
*/
|
|
4715
|
+
getSubstrate(surface: Surface): Promise<SubstrateHandle>;
|
|
4716
|
+
/**
|
|
4717
|
+
* Operator-visible per-surface status report. Consumers: the dashboard
|
|
4718
|
+
* transparency UI and the `/api/hub/intelligence/status` route.
|
|
4719
|
+
*
|
|
4720
|
+
* Probes hardware + Ollama at call time. The dashboard re-fetches every
|
|
4721
|
+
* 5 minutes to refresh status badges (degraded -> ok flip when Ollama
|
|
4722
|
+
* comes back up, and so on).
|
|
4723
|
+
*/
|
|
4724
|
+
getOperatorVisibleStatus(): Promise<SubstrateStatusReport>;
|
|
4725
|
+
/**
|
|
4726
|
+
* Probe the host machine's hardware capability. v1.2 reports total RAM
|
|
4727
|
+
* + CPU arch (Apple Silicon family if detectable) and computes the tier
|
|
4728
|
+
* per position paper section 5. Used by the picker modal to surface
|
|
4729
|
+
* "below-baseline -> recommend Venice" guidance.
|
|
4730
|
+
*/
|
|
4731
|
+
probeHardware(): Promise<HardwareCapabilityReport>;
|
|
4732
|
+
/**
|
|
4733
|
+
* Invoke a substrate with audit emission. Wraps the underlying
|
|
4734
|
+
* SubstrateHandle method, applies fallback behavior on failure per the
|
|
4735
|
+
* operator's per-surface config, and emits `intelligence_substrate_invoked`
|
|
4736
|
+
* + (on failure) `intelligence_substrate_failure`.
|
|
4737
|
+
*
|
|
4738
|
+
* Consumers SHOULD call this method rather than calling `handle.summarize`
|
|
4739
|
+
* etc. directly; the handle is exposed for cases where audit emission
|
|
4740
|
+
* needs to be explicitly skipped (probe paths, tests).
|
|
4741
|
+
*/
|
|
4742
|
+
invokeSummarize(surface: Surface, req: SummarizeRequest): Promise<SubstrateResponse>;
|
|
4743
|
+
invokeClassify(surface: Surface, req: ClassifyRequest): Promise<SubstrateResponse>;
|
|
4744
|
+
invokeRedact(surface: Surface, req: RedactRequest): Promise<SubstrateResponse>;
|
|
4745
|
+
private ensureLoaded;
|
|
4746
|
+
private invoke;
|
|
4747
|
+
/**
|
|
4748
|
+
* Append a failure entry to the per-surface ring buffer. The selector
|
|
4749
|
+
* uses this in the `invoke()` failure path and in the post-config
|
|
4750
|
+
* `setVeniceApiKey` validation hook so the operator-visible badge
|
|
4751
|
+
* degrades from runtime AND from validation outcomes.
|
|
4752
|
+
*
|
|
4753
|
+
* Operator-safe by construction: snippets are bounded by
|
|
4754
|
+
* `FAILURE_SNIPPET_MAX_LEN` and pulled from substrate `failure.message`
|
|
4755
|
+
* fields (which substrates produce for operator-readable output and
|
|
4756
|
+
* never include API keys or PII). The cap + window prune happen on
|
|
4757
|
+
* every read in `recentFailuresFor()` so the ring buffer never grows.
|
|
4758
|
+
*/
|
|
4759
|
+
private recordRecentFailure;
|
|
4760
|
+
/**
|
|
4761
|
+
* Read recent failures for a surface, pruning entries older than the
|
|
4762
|
+
* 24-hour window. Pruning happens lazily on read so a long-quiet surface
|
|
4763
|
+
* does not retain stale entries from yesterday's debugging.
|
|
4764
|
+
*/
|
|
4765
|
+
private recentFailuresFor;
|
|
4766
|
+
/**
|
|
4767
|
+
* Test-only seam: lets selector tests assert on recorded failures
|
|
4768
|
+
* without poking the private ring buffer. Returns a defensive copy.
|
|
4769
|
+
*/
|
|
4770
|
+
getRecentFailuresForTest(surface: Surface): RecentFailureEntry[];
|
|
4771
|
+
/**
|
|
4772
|
+
* Test-only seam: lets selector tests seed a recent-failure entry on a
|
|
4773
|
+
* surface so the buffer-clear behavior added for Finding ZZ (v1.2.0-rc.2)
|
|
4774
|
+
* can be exercised without spinning up a real failing substrate. Mirrors
|
|
4775
|
+
* `getRecentFailuresForTest`; never call from production paths.
|
|
4776
|
+
*/
|
|
4777
|
+
recordRecentFailureForTest(surface: Surface, failureClass: SubstrateFailureClass, snippet: string): void;
|
|
4778
|
+
/**
|
|
4779
|
+
* Build a substrate handle for a surface bound to a concrete choice.
|
|
4780
|
+
* The substrate-client constructor is paid lazily so the selector
|
|
4781
|
+
* doesn't pre-spawn HTTP clients for substrates the operator never
|
|
4782
|
+
* touches.
|
|
4783
|
+
*/
|
|
4784
|
+
private buildHandle;
|
|
4785
|
+
private disabledHandle;
|
|
4786
|
+
private localHandle;
|
|
4787
|
+
private veniceHandle;
|
|
4788
|
+
private frontierHandle;
|
|
4789
|
+
private probeSurfaceHealth;
|
|
4790
|
+
private makeBadge;
|
|
4791
|
+
private fallbackAction;
|
|
4792
|
+
/**
|
|
4793
|
+
* Internal hook for the Privacy Filter Tier 2 commit. Lets a redactor
|
|
4794
|
+
* audit-emit a `pii_redaction_event` payload through the selector
|
|
4795
|
+
* without exposing the AuditLog binding directly to redactor
|
|
4796
|
+
* implementations.
|
|
4797
|
+
*/
|
|
4798
|
+
emitRedactionEvent(args: {
|
|
4799
|
+
surface: Surface;
|
|
4800
|
+
substrate: SubstrateChoice;
|
|
4801
|
+
matchCount: number;
|
|
4802
|
+
filterTier: 1 | 2;
|
|
4803
|
+
}): void;
|
|
4804
|
+
private emit;
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
/**
|
|
4808
|
+
* Sanctuary MCP Server — Operator Chat Types
|
|
4809
|
+
*
|
|
4810
|
+
* Distinct from the existing agent-to-agent mesh chat (`chat-service.ts`).
|
|
4811
|
+
* Operator chat is the operator-fortress concierge surface: an operator
|
|
4812
|
+
* types into the v1.1 dashboard, Sanctuary persists the conversation,
|
|
4813
|
+
* audits every message, and routes responses through the substrate
|
|
4814
|
+
* selector.
|
|
4815
|
+
*
|
|
4816
|
+
* The direct-agent surface (operator-to-wrapped-agent conversation) was
|
|
4817
|
+
* removed in the v1.2 reshape. The concierge surface (operator-to-
|
|
4818
|
+
* Sanctuary) is the only operator chat surface that ships in v1.2.
|
|
4819
|
+
*
|
|
4820
|
+
* Sovereignty invariants:
|
|
4821
|
+
* - Conversation history is encrypted at rest under the fortress master
|
|
4822
|
+
* key via an HKDF-derived purpose key.
|
|
4823
|
+
* - Audit emission is non-optional: every operator submit and every
|
|
4824
|
+
* concierge reply lands in the L2 audit log.
|
|
4825
|
+
* - No raw message bodies are stored in audit `details`; only message
|
|
4826
|
+
* hashes and safe metadata. Bodies live in the encrypted chat store
|
|
4827
|
+
* that the operator can export, delete, or rotate.
|
|
4828
|
+
*/
|
|
4829
|
+
|
|
4830
|
+
/**
|
|
4831
|
+
* The chat surface a message belongs to. Concierge is the only operator
|
|
4832
|
+
* chat surface in v1.2; the type stays a single-member union for
|
|
4833
|
+
* forward-compatibility if additional Sanctuary-side surfaces land later.
|
|
4834
|
+
*/
|
|
4835
|
+
type OperatorChatSurface = "concierge";
|
|
4836
|
+
/**
|
|
4837
|
+
* Message-role enum. Concierge has two roles: `operator` (the human) and
|
|
4838
|
+
* `concierge` (Sanctuary's response).
|
|
4839
|
+
*/
|
|
4840
|
+
type OperatorChatRole = "operator" | "concierge";
|
|
4841
|
+
/**
|
|
4842
|
+
* One message in a concierge thread. The body is stored encrypted at
|
|
4843
|
+
* rest; the in-memory shape carries the cleartext for service consumers,
|
|
4844
|
+
* and the audit emission carries only `message_hash` + safe metadata.
|
|
4845
|
+
*/
|
|
4846
|
+
interface OperatorChatMessage {
|
|
4847
|
+
/** Stable message id (uuid) assigned at create time. */
|
|
4848
|
+
message_id: string;
|
|
4849
|
+
/** Surface this message belongs to. */
|
|
4850
|
+
surface: OperatorChatSurface;
|
|
4851
|
+
/** Sender role. */
|
|
4852
|
+
role: OperatorChatRole;
|
|
4853
|
+
/** Cleartext body. NEVER appears in audit `details`. */
|
|
4854
|
+
body: string;
|
|
4855
|
+
/** ISO8601 timestamp the message was created. */
|
|
4856
|
+
created_at: string;
|
|
4857
|
+
/**
|
|
4858
|
+
* For concierge response messages: which substrate served the response.
|
|
4859
|
+
* Null on operator-sent messages. Surfaces in the chat header badge.
|
|
4860
|
+
*/
|
|
4861
|
+
served_by?: SubstrateChoice;
|
|
4862
|
+
/**
|
|
4863
|
+
* For concierge response messages: latency of the substrate call in ms.
|
|
4864
|
+
* Operator-side surfaces this in the message tooltip / activity feed.
|
|
4865
|
+
*/
|
|
4866
|
+
substrate_latency_ms?: number;
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* The shape persisted to disk under `_chat/<surface>/<thread-key>`.
|
|
4870
|
+
*
|
|
4871
|
+
* Threads are append-only; the persisted record is read-rewritten on
|
|
4872
|
+
* every message append. v1.2 caps thread length at
|
|
4873
|
+
* `OPERATOR_CHAT_MAX_THREAD_LENGTH`; older messages drop off when the
|
|
4874
|
+
* cap is exceeded.
|
|
4875
|
+
*/
|
|
4876
|
+
interface OperatorChatThread {
|
|
4877
|
+
/** Forward-compat field. v1.2 uses 1; bump on schema change. */
|
|
4878
|
+
version: 1;
|
|
4879
|
+
/** Surface (concierge in v1.2). */
|
|
4880
|
+
surface: OperatorChatSurface;
|
|
4881
|
+
/** Thread key; concierge is fortress-scoped under `CONCIERGE_THREAD_KEY`. */
|
|
4882
|
+
thread_key: string;
|
|
4883
|
+
/** Append-only message log; oldest first. */
|
|
4884
|
+
messages: OperatorChatMessage[];
|
|
4885
|
+
/** ISO8601 timestamp the thread was last updated. */
|
|
4886
|
+
updated_at: string;
|
|
4887
|
+
}
|
|
4888
|
+
/**
|
|
4889
|
+
* Concierge response envelope. Carries the substrate response (or
|
|
4890
|
+
* fallback message) plus the operator-visible substrate badge.
|
|
4891
|
+
*/
|
|
4892
|
+
interface ConciergeResponse {
|
|
4893
|
+
/** The response message persisted into the concierge thread. */
|
|
4894
|
+
message: OperatorChatMessage;
|
|
4895
|
+
/** The substrate that served this query. */
|
|
4896
|
+
served_by: SubstrateChoice;
|
|
4897
|
+
/** Operator-visible label rendered next to the response. */
|
|
4898
|
+
display_label: string;
|
|
4899
|
+
/** Outcome class for activity-feed projection. */
|
|
4900
|
+
outcome: "ok" | "substrate_failure" | "substrate_disabled";
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
/**
|
|
4904
|
+
* Sanctuary MCP Server — Operator Chat Persistence
|
|
4905
|
+
*
|
|
4906
|
+
* Encrypted at-rest store for the operator-chat concierge thread.
|
|
4907
|
+
* Mirrors the `IntelligenceConfigStore` shape but addresses records by
|
|
4908
|
+
* `(surface, thread-key)` for forward-compatibility.
|
|
4909
|
+
*
|
|
4910
|
+
* Storage layout:
|
|
4911
|
+
* namespace: `_chat` (underscore-prefixed = reserved L1 namespace)
|
|
4912
|
+
* key: `concierge.{thread_key}`
|
|
4913
|
+
* payload: AES-256-GCM-encrypted `OperatorChatThread` JSON, key
|
|
4914
|
+
* derived via HKDF from the fortress master key with info
|
|
4915
|
+
* string `operator-chat-store-v1`.
|
|
4916
|
+
*
|
|
4917
|
+
* The direct-agent surface (per-agent threads + session records) was
|
|
4918
|
+
* removed in the v1.2 reshape. The concierge surface stores under a
|
|
4919
|
+
* single record with sentinel thread-key `_fortress`
|
|
4920
|
+
* (CONCIERGE_THREAD_KEY).
|
|
4921
|
+
*
|
|
4922
|
+
* Append-only semantics:
|
|
4923
|
+
* Threads are append-only at the message level. Persist is full-record
|
|
4924
|
+
* read-modify-write; the cap on thread length is enforced at append time
|
|
4925
|
+
* (oldest messages fall off when `OPERATOR_CHAT_MAX_THREAD_LENGTH` is
|
|
4926
|
+
* exceeded). The audit log retains every message regardless.
|
|
4927
|
+
*/
|
|
4928
|
+
|
|
4929
|
+
/**
|
|
4930
|
+
* Encrypted operator chat persistence.
|
|
4931
|
+
*/
|
|
4932
|
+
declare class OperatorChatStore {
|
|
4933
|
+
private storage;
|
|
4934
|
+
private encryptionKey;
|
|
4935
|
+
constructor(storage: StorageBackend, masterKey: Uint8Array);
|
|
4936
|
+
/**
|
|
4937
|
+
* Load a thread. Returns null if no record exists or if the on-disk
|
|
4938
|
+
* record is corrupt; callers treat both as "empty thread, start
|
|
4939
|
+
* fresh" and emit no audit event for the absence.
|
|
4940
|
+
*/
|
|
4941
|
+
loadThread(surface: OperatorChatSurface, threadKey: string): Promise<OperatorChatThread | null>;
|
|
4942
|
+
/**
|
|
4943
|
+
* Persist a thread. Caller is responsible for cap enforcement; the
|
|
4944
|
+
* `appendMessage` helper below drops oldest messages before persist
|
|
4945
|
+
* when the cap is exceeded.
|
|
4946
|
+
*/
|
|
4947
|
+
saveThread(thread: OperatorChatThread): Promise<void>;
|
|
4948
|
+
/**
|
|
4949
|
+
* Append a message to a thread, creating the thread if it doesn't
|
|
4950
|
+
* exist, and persist. Enforces the per-thread length cap by dropping
|
|
4951
|
+
* oldest messages first. Returns the persisted thread.
|
|
4952
|
+
*/
|
|
4953
|
+
appendMessage(surface: OperatorChatSurface, threadKey: string, message: OperatorChatMessage): Promise<OperatorChatThread>;
|
|
4954
|
+
/**
|
|
4955
|
+
* Delete a thread. Used by operator-driven thread reset. The audit
|
|
4956
|
+
* log retains the per-message history regardless.
|
|
4957
|
+
*/
|
|
4958
|
+
deleteThread(surface: OperatorChatSurface, threadKey: string): Promise<void>;
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
/**
|
|
4962
|
+
* Sanctuary MCP Server — Operator Chat Service
|
|
4963
|
+
*
|
|
4964
|
+
* The operator-fortress concierge surface. Distinct from the
|
|
4965
|
+
* agent-to-agent mesh chat (`chat-service.ts`) which handles libp2p +
|
|
4966
|
+
* OpenMLS group sessions among wrapped agents.
|
|
4967
|
+
*
|
|
4968
|
+
* The direct-agent surface (operator-to-wrapped-agent conversation) was
|
|
4969
|
+
* removed in the v1.2 reshape; the concierge surface is the only
|
|
4970
|
+
* operator-chat surface in v1.2.
|
|
4971
|
+
*
|
|
4972
|
+
* Concierge surface (path: `sendConcierge`):
|
|
4973
|
+
* 1. PII-filter the operator query (Tier 1 regex).
|
|
4974
|
+
* 2. Build fortress context from audit log + activity feed +
|
|
4975
|
+
* agent registry (`assembleConciergeContext`).
|
|
4976
|
+
* 3. Invoke the substrate selector at `surface: "concierge"`.
|
|
4977
|
+
* 4. Persist operator query + concierge response in the concierge
|
|
4978
|
+
* thread under `_chat/concierge.<sentinel>`.
|
|
4979
|
+
* 5. Emit `operator_concierge_chat` audit event with safe metadata.
|
|
4980
|
+
*
|
|
4981
|
+
* Sovereignty invariants:
|
|
4982
|
+
* - Audit emission is non-optional on every public method that mutates
|
|
4983
|
+
* the chat state. Construction with no audit log is structurally
|
|
4984
|
+
* rejected.
|
|
4985
|
+
* - The substrate selector is OPTIONAL: a service constructed without
|
|
4986
|
+
* one degrades to "Concierge unavailable; substrate not configured".
|
|
4987
|
+
* The wiring layer can instantiate the service before the substrate
|
|
4988
|
+
* selector when bootstrap order requires it.
|
|
4989
|
+
* - The PII filter is the Tier 1 regex shipped in
|
|
4990
|
+
* `l2-operational/privacy-filter.ts`; Tier 2 NER+LLM redaction lives
|
|
4991
|
+
* in the substrate-selector layer (substrate-routed), not here.
|
|
4992
|
+
*/
|
|
4993
|
+
|
|
4994
|
+
/**
|
|
4995
|
+
* Snapshot of fortress state surfaces the concierge consults to
|
|
4996
|
+
* answer operator queries. Caller (the hub-service wiring) supplies
|
|
4997
|
+
* the providers; the service wires them into the substrate prompt.
|
|
4998
|
+
*
|
|
4999
|
+
* Each provider returns plain strings the substrate selector folds
|
|
5000
|
+
* into a single context blob; the service does not attempt to
|
|
5001
|
+
* structure the prompt beyond stitching with newlines. Keeping the
|
|
5002
|
+
* prompt-shape decisions in the service-construction layer (rather
|
|
5003
|
+
* than in the wiring) lets the dashboard team iterate concierge
|
|
5004
|
+
* prompts without touching server code.
|
|
5005
|
+
*/
|
|
5006
|
+
interface ConciergeContextProviders {
|
|
5007
|
+
/** Recent activity-feed entries as a free-form string, oldest first. */
|
|
5008
|
+
recentActivity: () => Promise<string>;
|
|
5009
|
+
/** Wrapped-agent inventory as a free-form string. */
|
|
5010
|
+
agentInventory: () => Promise<string>;
|
|
5011
|
+
/** Open inbox items as a free-form string. */
|
|
5012
|
+
openInbox: () => Promise<string>;
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* PII filter the concierge applies to operator queries before passing
|
|
5016
|
+
* them to the substrate. v1.2 wires the Tier 1 regex; the substrate
|
|
5017
|
+
* selector handles Tier 2 NER+LLM redaction internally on egress paths.
|
|
5018
|
+
*
|
|
5019
|
+
* Returns the filtered query plus the count of redactions performed
|
|
5020
|
+
* (surfaced in the audit event for operator visibility).
|
|
5021
|
+
*/
|
|
5022
|
+
interface ConciergePiiFilter {
|
|
5023
|
+
filter(input: string): {
|
|
5024
|
+
filtered: string;
|
|
5025
|
+
redactions: number;
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
/**
|
|
5029
|
+
* Construction inputs for the operator chat service.
|
|
5030
|
+
*/
|
|
5031
|
+
interface OperatorChatServiceDeps {
|
|
5032
|
+
/** Encrypted at-rest store; built from fortress master key. */
|
|
5033
|
+
store: OperatorChatStore;
|
|
5034
|
+
/** Audit log for `operator_concierge_chat` events. */
|
|
5035
|
+
auditLog: AuditLog;
|
|
5036
|
+
/** Operator identity that owns the chat session. */
|
|
5037
|
+
identityId: string;
|
|
5038
|
+
/**
|
|
5039
|
+
* Substrate selector for concierge LLM calls. Optional: when omitted
|
|
5040
|
+
* the concierge surface degrades to a "substrate not configured"
|
|
5041
|
+
* response.
|
|
5042
|
+
*/
|
|
5043
|
+
substrateSelector?: SubstrateSelector;
|
|
5044
|
+
/**
|
|
5045
|
+
* Concierge context providers. Required when a substrate selector
|
|
5046
|
+
* is wired; otherwise unused. Wiring layer assembles these from the
|
|
5047
|
+
* activity feed, agent registry, and inbox aggregator.
|
|
5048
|
+
*/
|
|
5049
|
+
conciergeContextProviders?: ConciergeContextProviders;
|
|
5050
|
+
/**
|
|
5051
|
+
* PII filter applied to concierge queries pre-substrate. Optional;
|
|
5052
|
+
* when omitted the service still works (no redactions performed).
|
|
5053
|
+
* v1.2 wiring passes the Tier 1 regex.
|
|
5054
|
+
*/
|
|
5055
|
+
conciergePiiFilter?: ConciergePiiFilter;
|
|
5056
|
+
/**
|
|
5057
|
+
* Stable max tokens for concierge responses. Defaults to 512 (small
|
|
5058
|
+
* enough for snappy local-model latency, large enough for a useful
|
|
5059
|
+
* summary). Operator-tunable via dashboard config.
|
|
5060
|
+
*/
|
|
5061
|
+
conciergeMaxTokens?: number;
|
|
5062
|
+
}
|
|
5063
|
+
declare class OperatorChatService {
|
|
5064
|
+
private store;
|
|
5065
|
+
private auditLog;
|
|
5066
|
+
private identityId;
|
|
5067
|
+
private substrateSelector?;
|
|
5068
|
+
private contextProviders?;
|
|
5069
|
+
private piiFilter?;
|
|
5070
|
+
private conciergeMaxTokens;
|
|
5071
|
+
constructor(deps: OperatorChatServiceDeps);
|
|
5072
|
+
/**
|
|
5073
|
+
* Operator submit on the concierge surface. Persists the operator's
|
|
5074
|
+
* query, fans out to the substrate selector, persists the response,
|
|
5075
|
+
* and emits the audit event.
|
|
5076
|
+
*
|
|
5077
|
+
* On substrate failure or absence: persists an honest "concierge
|
|
5078
|
+
* unavailable" response and audit-emits with `outcome` = the
|
|
5079
|
+
* appropriate failure class. The chat history reflects what the
|
|
5080
|
+
* operator sees on the page (no silent dropping).
|
|
5081
|
+
*/
|
|
5082
|
+
sendConcierge(query: string): Promise<ConciergeResponse>;
|
|
5083
|
+
/**
|
|
5084
|
+
* Read the persisted concierge thread, oldest message first. Returns
|
|
5085
|
+
* an empty array when no thread exists yet.
|
|
5086
|
+
*/
|
|
5087
|
+
getConciergeHistory(): Promise<OperatorChatMessage[]>;
|
|
5088
|
+
/**
|
|
5089
|
+
* Stitch fortress state into a single context blob the substrate
|
|
5090
|
+
* folds into its summarization prompt.
|
|
5091
|
+
*
|
|
5092
|
+
* The blob is intentionally plain text (not structured JSON) because
|
|
5093
|
+
* small local models (Gemma 2 2B) handle plain text more reliably
|
|
5094
|
+
* than nested structures. Format:
|
|
5095
|
+
*
|
|
5096
|
+
* ```
|
|
5097
|
+
* ## Recent activity
|
|
5098
|
+
* <recentActivity output>
|
|
5099
|
+
*
|
|
5100
|
+
* ## Wrapped agents
|
|
5101
|
+
* <agentInventory output>
|
|
5102
|
+
*
|
|
5103
|
+
* ## Open inbox
|
|
5104
|
+
* <openInbox output>
|
|
5105
|
+
* ```
|
|
5106
|
+
*/
|
|
5107
|
+
private assembleConciergeContext;
|
|
5108
|
+
private emit;
|
|
5109
|
+
}
|
|
5110
|
+
|
|
4137
5111
|
/**
|
|
4138
5112
|
* Sanctuary v1.1. Operator Hub API Types
|
|
4139
5113
|
*
|
|
@@ -4171,6 +5145,11 @@ interface HubTier1ApprovalEnqueuedResult {
|
|
|
4171
5145
|
*/
|
|
4172
5146
|
operation_category: HubApprovalPendingItem["operation_category"];
|
|
4173
5147
|
}
|
|
5148
|
+
interface HubTemplateBindingApprovalEnqueuedResult extends HubTier1ApprovalEnqueuedResult {
|
|
5149
|
+
current_template_id?: ChannelTemplateId;
|
|
5150
|
+
proposed_template_id: ChannelTemplateId;
|
|
5151
|
+
compiled_policy_id: string;
|
|
5152
|
+
}
|
|
4174
5153
|
/**
|
|
4175
5154
|
* Result returned from a fortress-scope Tier-1 action that has been
|
|
4176
5155
|
* deferred to operator approval. Distinguished from the per-agent shape by
|
|
@@ -4225,11 +5204,7 @@ interface HubAgentController {
|
|
|
4225
5204
|
* approval lands.
|
|
4226
5205
|
*/
|
|
4227
5206
|
bindPolicy(agentId: string, policyId: string): Promise<void>;
|
|
4228
|
-
/**
|
|
4229
|
-
* Apply a non-Tier-1 channel-template binding. Channel templates are
|
|
4230
|
-
* compositions over the canonical four slots; binding does not require
|
|
4231
|
-
* Tier 1 approval at v1.1.
|
|
4232
|
-
*/
|
|
5207
|
+
/** Apply an operator-approved channel-template binding. */
|
|
4233
5208
|
bindChannelTemplate(agentId: string, templateId: ChannelTemplateId): Promise<void>;
|
|
4234
5209
|
}
|
|
4235
5210
|
/**
|
|
@@ -4362,6 +5337,11 @@ interface HubServiceDeps {
|
|
|
4362
5337
|
* returning the in-memory projection.
|
|
4363
5338
|
*/
|
|
4364
5339
|
readPersistedLocalAgents?: () => LocalAgentRecord[];
|
|
5340
|
+
/**
|
|
5341
|
+
* Optional persisted agent writer. Used when approved hub actions mutate
|
|
5342
|
+
* registry-local fields that should survive refresh and restart.
|
|
5343
|
+
*/
|
|
5344
|
+
writePersistedLocalAgents?: (records: LocalAgentRecord[]) => void;
|
|
4365
5345
|
/** Inbox aggregation sources. */
|
|
4366
5346
|
inboxSources: HubInboxSources;
|
|
4367
5347
|
/** Activity feed sources. */
|
|
@@ -4391,6 +5371,44 @@ interface HubServiceDeps {
|
|
|
4391
5371
|
* Defaults to `() => new Date()` when omitted.
|
|
4392
5372
|
*/
|
|
4393
5373
|
now?: () => Date;
|
|
5374
|
+
/**
|
|
5375
|
+
* Optional operator-chat service. When supplied, the hub exposes the
|
|
5376
|
+
* concierge chat endpoints; when omitted, those routes return
|
|
5377
|
+
* HubCapabilityError. Construction-time injection lets the dashboard
|
|
5378
|
+
* wiring layer build the chat service against the same audit log +
|
|
5379
|
+
* master key + storage backend as the rest of the v1.1 hub without
|
|
5380
|
+
* forcing the chat-service constructor through a circular import.
|
|
5381
|
+
* The direct-agent chat surface was removed in the v1.2 reshape
|
|
5382
|
+
* (2026-04-30); the field name is retained for source compatibility.
|
|
5383
|
+
*/
|
|
5384
|
+
operatorChat?: OperatorChatService;
|
|
5385
|
+
}
|
|
5386
|
+
/**
|
|
5387
|
+
* Click-to-inspect panel returned by `openAgentInspectPanel`. The panel
|
|
5388
|
+
* surfaces the agent's recent activity, pending approvals routed through
|
|
5389
|
+
* this agent, and policy summary at a glance, repurposing the click-
|
|
5390
|
+
* to-chat wire-up shipped in PR #98 + PR #100. The direct-agent chat
|
|
5391
|
+
* surface was removed in the v1.2 reshape; this panel is the operational
|
|
5392
|
+
* destination for the click.
|
|
5393
|
+
*/
|
|
5394
|
+
interface HubAgentInspectPanel {
|
|
5395
|
+
/** The agent the panel was opened on. */
|
|
5396
|
+
agent_id: string;
|
|
5397
|
+
/** ISO8601 timestamp the panel was opened. */
|
|
5398
|
+
opened_at: string;
|
|
5399
|
+
/** Recent activity-feed entries for this agent, oldest first. */
|
|
5400
|
+
recent_activity: HubActivityFeedEntry[];
|
|
5401
|
+
/**
|
|
5402
|
+
* Open Tier 1 inbox items routed through this agent. Empty when the
|
|
5403
|
+
* agent has no pending approvals.
|
|
5404
|
+
*/
|
|
5405
|
+
pending_approvals: HubInboxItem[];
|
|
5406
|
+
/**
|
|
5407
|
+
* Bound policy summary for this agent. Null when the agent has no
|
|
5408
|
+
* channel-template binding (e.g. immediately after wrap, before the
|
|
5409
|
+
* operator picks a template).
|
|
5410
|
+
*/
|
|
5411
|
+
policy_summary: HubPolicySummary | null;
|
|
4394
5412
|
}
|
|
4395
5413
|
|
|
4396
5414
|
/**
|
|
@@ -4438,10 +5456,10 @@ declare class HubService {
|
|
|
4438
5456
|
*/
|
|
4439
5457
|
bindAgentPolicy(agentId: string, policyId: string): HubTier1ApprovalEnqueuedResult;
|
|
4440
5458
|
/**
|
|
4441
|
-
* Bind a different channel template.
|
|
4442
|
-
*
|
|
5459
|
+
* Bind a different channel template. Tier 1: enqueues a policy_change
|
|
5460
|
+
* approval item and only applies the binding after operator approval.
|
|
4443
5461
|
*/
|
|
4444
|
-
bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown):
|
|
5462
|
+
bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): HubTemplateBindingApprovalEnqueuedResult;
|
|
4445
5463
|
/**
|
|
4446
5464
|
* Enqueue a fortress-scope Tier 1 lockdown. One inbox item is created;
|
|
4447
5465
|
* on operator approval the handler iterates `agentController.lockdown`
|
|
@@ -4484,6 +5502,33 @@ declare class HubService {
|
|
|
4484
5502
|
* Test/inspection helper. Not part of the public service surface.
|
|
4485
5503
|
*/
|
|
4486
5504
|
inboxStoreSize(): number;
|
|
5505
|
+
private requireOperatorChat;
|
|
5506
|
+
/**
|
|
5507
|
+
* Operator submit on the concierge chat surface. Pass-through to the
|
|
5508
|
+
* operator-chat-service which owns substrate-selector routing, PII
|
|
5509
|
+
* filtering, persistence, and audit emission.
|
|
5510
|
+
*/
|
|
5511
|
+
sendConcierge(query: string): Promise<ConciergeResponse>;
|
|
5512
|
+
/**
|
|
5513
|
+
* Read the persisted concierge thread.
|
|
5514
|
+
*/
|
|
5515
|
+
getConciergeHistory(): Promise<OperatorChatMessage[]>;
|
|
5516
|
+
/**
|
|
5517
|
+
* Open the click-to-inspect/approve panel for a wrapped agent. The
|
|
5518
|
+
* panel surfaces recent activity routed through this agent, pending
|
|
5519
|
+
* Tier 1 approvals on this agent, and the bound policy summary,
|
|
5520
|
+
* repurposing the click-to-chat wire-up from PR #98 + PR #100 to a
|
|
5521
|
+
* substrate-aligned inspect destination after the v1.2 reshape removed
|
|
5522
|
+
* direct-agent chat.
|
|
5523
|
+
*
|
|
5524
|
+
* Synchronous open shape preserved (caller's `await` semantics
|
|
5525
|
+
* unchanged from the prior `openDirectAgentSession` surface). The
|
|
5526
|
+
* panel is read-only; no side effects beyond the audit-event emission
|
|
5527
|
+
* for the panel-open itself.
|
|
5528
|
+
*/
|
|
5529
|
+
openAgentInspectPanel(agentId: string, options?: {
|
|
5530
|
+
activityLimit?: number;
|
|
5531
|
+
}): Promise<HubAgentInspectPanel>;
|
|
4487
5532
|
}
|
|
4488
5533
|
|
|
4489
5534
|
/**
|
|
@@ -4509,16 +5554,29 @@ declare class HubService {
|
|
|
4509
5554
|
* the v1.2 work to project those into operator cards.
|
|
4510
5555
|
* - Activity feed reads from the real audit log. This is the one source
|
|
4511
5556
|
* that's already complete in v1.1.0 and just needs to be plugged in.
|
|
4512
|
-
* - Agent controller errors on
|
|
4513
|
-
*
|
|
4514
|
-
*
|
|
4515
|
-
*
|
|
5557
|
+
* - Agent controller errors on runtime harness actions that v1.1 cannot
|
|
5558
|
+
* honestly execute. Channel-template binding is registry-local in v1.2,
|
|
5559
|
+
* so its controller hook is a no-op and HubService persists the binding
|
|
5560
|
+
* after Tier 1 approval.
|
|
4516
5561
|
*/
|
|
4517
5562
|
|
|
4518
5563
|
interface V11Bindings {
|
|
4519
5564
|
hubService: HubService;
|
|
4520
5565
|
identityId: string;
|
|
4521
5566
|
fortressId: string;
|
|
5567
|
+
/**
|
|
5568
|
+
* Optional Intelligence Substrate Selector. Dispatch layer routes
|
|
5569
|
+
* `/api/hub/intelligence/*` here when set; absent means the routes
|
|
5570
|
+
* 503 with a "selector not configured" body.
|
|
5571
|
+
*/
|
|
5572
|
+
intelligenceSelector?: SubstrateSelector;
|
|
5573
|
+
/**
|
|
5574
|
+
* Optional Operator Chat Service. Constructed when storage + masterKey
|
|
5575
|
+
* are passed to `buildV11Bindings`. The HubService accepts this
|
|
5576
|
+
* directly via its `operatorChat` dep; callers that want the harness-
|
|
5577
|
+
* side `recordAgentReply` integration also need the service handle.
|
|
5578
|
+
*/
|
|
5579
|
+
operatorChatService?: OperatorChatService;
|
|
4522
5580
|
}
|
|
4523
5581
|
|
|
4524
5582
|
/**
|