@tangle-network/agent-app 0.43.66 → 0.43.68

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.
Files changed (60) hide show
  1. package/dist/assistant/index.d.ts +4 -2
  2. package/dist/assistant/index.js +6 -3
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/{attachment-validation-B2FFna9E.d.ts → attachment-validation-Dv1A_Puy.d.ts} +1 -1
  5. package/dist/chat-routes/index.d.ts +4 -3
  6. package/dist/chat-routes/index.js +6 -4
  7. package/dist/chat-routes/index.js.map +1 -1
  8. package/dist/chat-store/index.d.ts +3 -2
  9. package/dist/chat-store/index.js +4 -1
  10. package/dist/chat-store/index.js.map +1 -1
  11. package/dist/{chunk-JWBZ74TW.js → chunk-7ESQUSAC.js} +5 -3
  12. package/dist/{chunk-JWBZ74TW.js.map → chunk-7ESQUSAC.js.map} +1 -1
  13. package/dist/{chunk-X3N2H6JE.js → chunk-AFNTRJQ7.js} +10 -1
  14. package/dist/chunk-AFNTRJQ7.js.map +1 -0
  15. package/dist/chunk-F2CBC4DY.js +193 -0
  16. package/dist/chunk-F2CBC4DY.js.map +1 -0
  17. package/dist/chunk-HRH7ASAG.js +759 -0
  18. package/dist/chunk-HRH7ASAG.js.map +1 -0
  19. package/dist/{chunk-YTSEDJWA.js → chunk-RXOTWZ4G.js} +22 -6
  20. package/dist/chunk-RXOTWZ4G.js.map +1 -0
  21. package/dist/chunk-UDSY2F6N.js +331 -0
  22. package/dist/chunk-UDSY2F6N.js.map +1 -0
  23. package/dist/chunk-UOAYS72M.js +80 -0
  24. package/dist/chunk-UOAYS72M.js.map +1 -0
  25. package/dist/chunk-UP33Z633.js +141 -0
  26. package/dist/chunk-UP33Z633.js.map +1 -0
  27. package/dist/{chunk-FMDMI25K.js → chunk-V55WJSR4.js} +2 -2
  28. package/dist/{chunk-YKBDH2UY.js → chunk-WL7XHLDK.js} +2 -2
  29. package/dist/{chunk-7CTIUCQ4.js → chunk-YEFFHORB.js} +2 -73
  30. package/dist/chunk-YEFFHORB.js.map +1 -0
  31. package/dist/eval-campaign/index.d.ts +2 -81
  32. package/dist/index.d.ts +5 -1
  33. package/dist/index.js +62 -8
  34. package/dist/model-resolution/index.d.ts +100 -1
  35. package/dist/model-resolution/index.js +108 -0
  36. package/dist/model-resolution/index.js.map +1 -1
  37. package/dist/{parts-Bg8qcDvB.d.ts → parts-2ymE5cs-.d.ts} +15 -2
  38. package/dist/queue-C24V13h9.d.ts +68 -0
  39. package/dist/runtime/index.js +3 -2
  40. package/dist/sandbox/index.js +3 -2
  41. package/dist/teams/index.js +5 -5
  42. package/dist/teams/invitations-api.js +4 -4
  43. package/dist/teams-react/index.js +3 -3
  44. package/dist/tools/index.js +8 -6
  45. package/dist/trust-gate-Dcm5xSva.d.ts +83 -0
  46. package/dist/types-CEchbvgz.d.ts +268 -0
  47. package/dist/web-react/index.d.ts +97 -5
  48. package/dist/web-react/index.js +31 -4
  49. package/dist/work-product/index.d.ts +331 -0
  50. package/dist/work-product/index.js +54 -0
  51. package/dist/work-product/index.js.map +1 -0
  52. package/dist/work-product-react/index.d.ts +36 -0
  53. package/dist/work-product-react/index.js +180 -0
  54. package/dist/work-product-react/index.js.map +1 -0
  55. package/package.json +15 -1
  56. package/dist/chunk-7CTIUCQ4.js.map +0 -1
  57. package/dist/chunk-X3N2H6JE.js.map +0 -1
  58. package/dist/chunk-YTSEDJWA.js.map +0 -1
  59. /package/dist/{chunk-FMDMI25K.js.map → chunk-V55WJSR4.js.map} +0 -0
  60. /package/dist/{chunk-YKBDH2UY.js.map → chunk-WL7XHLDK.js.map} +0 -0
@@ -1,3 +1,105 @@
1
+ // src/model-resolution/failover.ts
2
+ var UPSTREAM_UNAVAILABLE_CODES = [
3
+ "provider_inference_unavailable",
4
+ "upstream_unavailable",
5
+ "insufficient_quota",
6
+ "model_not_available",
7
+ "server_error",
8
+ "bad_gateway",
9
+ "service_unavailable"
10
+ ];
11
+ var UPSTREAM_UNAVAILABLE_STATUSES = [429, 500, 502, 503, 504];
12
+ var UPSTREAM_UNAVAILABLE_MESSAGES = [
13
+ "bad gateway",
14
+ "service unavailable",
15
+ "inference temporarily unavailable",
16
+ "provider inference is unavailable",
17
+ "insufficient balance",
18
+ "usage limits",
19
+ "quota exceeded",
20
+ "rate limit",
21
+ "overloaded",
22
+ "temporarily unavailable"
23
+ ];
24
+ function readString(source, key) {
25
+ const value = source[key];
26
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
27
+ }
28
+ function isUpstreamUnavailable(signal) {
29
+ if (signal === null || typeof signal !== "object") return false;
30
+ const record = signal;
31
+ if (record.success === true) return false;
32
+ const nested = record.error;
33
+ const nestedRecord = nested !== null && typeof nested === "object" ? nested : void 0;
34
+ const code = readString(record, "errorCode") ?? readString(record, "code") ?? (nestedRecord ? readString(nestedRecord, "code") ?? readString(nestedRecord, "type") : void 0);
35
+ if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return true;
36
+ for (const key of ["status", "statusCode", "httpStatus"]) {
37
+ const value = record[key];
38
+ if (typeof value === "number" && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true;
39
+ }
40
+ const message = readString(record, "message") ?? readString(record, "error") ?? (nestedRecord ? readString(nestedRecord, "message") : void 0);
41
+ if (!message) return false;
42
+ const lowered = message.toLowerCase();
43
+ return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment));
44
+ }
45
+ var ModelFailoverExhaustedError = class extends Error {
46
+ attempts;
47
+ constructor(attempts) {
48
+ const trail = attempts.map((a) => `${a.model}: ${a.reason ?? "failed"}`).join(" | ");
49
+ super(`All ${attempts.length} model(s) failed. ${trail}`);
50
+ this.name = "ModelFailoverExhaustedError";
51
+ this.attempts = attempts;
52
+ }
53
+ };
54
+ function describe(signal) {
55
+ if (signal instanceof Error) return signal.message;
56
+ if (signal !== null && typeof signal === "object") {
57
+ const record = signal;
58
+ const message = readString(record, "error") ?? readString(record, "message") ?? readString(record, "errorCode");
59
+ if (message) return message;
60
+ }
61
+ return String(signal);
62
+ }
63
+ async function runWithModelFailover(input) {
64
+ const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0);
65
+ if (models.length === 0) throw new Error("runWithModelFailover requires at least one model");
66
+ const isUnavailableResult = input.isUnavailableResult ?? ((r) => isUpstreamUnavailable(r));
67
+ const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable;
68
+ const attempts = [];
69
+ for (let index = 0; index < models.length; index += 1) {
70
+ const model = models[index];
71
+ let result;
72
+ try {
73
+ result = await input.run(model);
74
+ } catch (error) {
75
+ if (!isUnavailableError(error)) throw error;
76
+ const attempt = { model, ok: false, reason: describe(error) };
77
+ attempts.push(attempt);
78
+ const next = models[index + 1];
79
+ if (next) input.onFallback?.(attempt, next);
80
+ continue;
81
+ }
82
+ if (isUnavailableResult(result)) {
83
+ const attempt = { model, ok: false, reason: describe(result) };
84
+ attempts.push(attempt);
85
+ const next = models[index + 1];
86
+ if (next) input.onFallback?.(attempt, next);
87
+ continue;
88
+ }
89
+ attempts.push({ model, ok: true });
90
+ return { value: result, model, attempts, usedFallback: index > 0 };
91
+ }
92
+ throw new ModelFailoverExhaustedError(attempts);
93
+ }
94
+ function buildModelChain(preferred, fallbacks) {
95
+ const chain = [];
96
+ for (const model of [preferred, ...fallbacks]) {
97
+ const cleaned = typeof model === "string" ? model.trim() : "";
98
+ if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned);
99
+ }
100
+ return chain;
101
+ }
102
+
1
103
  // src/model-resolution/index.ts
2
104
  function canonicalModelId(model) {
3
105
  if (model.id.includes("/")) return model.id;
@@ -67,10 +169,16 @@ function catalogIdsForModel(model) {
67
169
  return [...ids];
68
170
  }
69
171
  export {
172
+ ModelFailoverExhaustedError,
173
+ UPSTREAM_UNAVAILABLE_CODES,
174
+ UPSTREAM_UNAVAILABLE_STATUSES,
175
+ buildModelChain,
70
176
  catalogIdsForModel,
71
177
  cleanModelId,
178
+ isUpstreamUnavailable,
72
179
  isWellFormedModelId,
73
180
  resolveChatModel,
181
+ runWithModelFailover,
74
182
  validateChatModelId
75
183
  };
76
184
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/model-resolution/failover.ts","../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Model failover — the answer to \"one upstream hit a quota wall and the customer\n * got a Bad Gateway even though the router had abundant healthy capacity\".\n *\n * This is deliberately NOT a health probe. Probing before every turn buys a\n * round-trip of latency on the happy path and still races the outage (a model\n * healthy at probe time can 502 a second later). Failover is REACTIVE: run the\n * preferred model, and on an upstream-outage signal move to the next model in\n * the chain. Zero added latency when the preferred model works.\n *\n * Two facts drive the design, both measured against a live box during the\n * 2026-07-25 Anthropic/DeepSeek outage:\n *\n * 1. An outage is NOT always a thrown error. The sandbox resolves with a\n * payload — `{ success: false, errorCode: 'provider_inference_unavailable' }`\n * — so a classifier that only inspects `catch` misses the customer-visible\n * case entirely. `isUpstreamUnavailable` inspects resolved values too.\n * 2. Catalog membership is NOT liveness. The router's `/v1/models` still listed\n * every dead model during the outage, so `validateChatModelId` admitted\n * `claude-sonnet-4-6` while every call to it returned 502. Nothing upstream\n * of the actual call can be trusted to tell you a model is serving.\n *\n * Substrate-free: the caller injects `run`, so this composes with a sandbox\n * turn, a router chat completion, or a test fake without importing any of them.\n */\n\n/**\n * Error codes that mean \"this model's upstream is unavailable — a different\n * model may still work\". Deliberately excludes codes that would fail identically\n * on every model (bad request, auth, content filter): retrying those down a\n * chain burns latency and money to reach the same failure.\n */\nexport const UPSTREAM_UNAVAILABLE_CODES: readonly string[] = [\n 'provider_inference_unavailable',\n 'upstream_unavailable',\n 'insufficient_quota',\n 'model_not_available',\n 'server_error',\n 'bad_gateway',\n 'service_unavailable',\n]\n\n/** HTTP statuses that indicate an upstream capacity/availability problem. */\nexport const UPSTREAM_UNAVAILABLE_STATUSES: readonly number[] = [429, 500, 502, 503, 504]\n\n/**\n * Message fragments emitted by real upstreams during this class of outage.\n * Matched case-insensitively as a last resort, after code and status.\n */\nconst UPSTREAM_UNAVAILABLE_MESSAGES: readonly string[] = [\n 'bad gateway',\n 'service unavailable',\n 'inference temporarily unavailable',\n 'provider inference is unavailable',\n 'insufficient balance',\n 'usage limits',\n 'quota exceeded',\n 'rate limit',\n 'overloaded',\n 'temporarily unavailable',\n]\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key]\n return typeof value === 'string' && value.trim().length > 0 ? value : undefined\n}\n\n/**\n * True when `signal` — a thrown error OR a resolved result payload — indicates\n * the model's upstream is unavailable and another model is worth trying.\n *\n * Checked in order of decreasing confidence: explicit code, HTTP status, then\n * message text. A resolved payload only counts as a failure when it carries an\n * explicit failure marker (`success: false`, or an `error`/`errorCode` field) —\n * a successful result is never misread as an outage.\n */\nexport function isUpstreamUnavailable(signal: unknown): boolean {\n if (signal === null || typeof signal !== 'object') return false\n const record = signal as Record<string, unknown>\n\n // A resolved payload that explicitly reports success is never an outage.\n if (record.success === true) return false\n\n const nested = record.error\n const nestedRecord = nested !== null && typeof nested === 'object' ? (nested as Record<string, unknown>) : undefined\n\n const code =\n readString(record, 'errorCode') ??\n readString(record, 'code') ??\n (nestedRecord ? (readString(nestedRecord, 'code') ?? readString(nestedRecord, 'type')) : undefined)\n if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return true\n\n for (const key of ['status', 'statusCode', 'httpStatus']) {\n const value = record[key]\n if (typeof value === 'number' && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true\n }\n\n const message =\n readString(record, 'message') ??\n readString(record, 'error') ??\n (nestedRecord ? readString(nestedRecord, 'message') : undefined)\n if (!message) return false\n const lowered = message.toLowerCase()\n return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment))\n}\n\n/** One model tried, and how it went. */\nexport interface ModelFailoverAttempt {\n model: string\n ok: boolean\n /** Why this model was abandoned. Absent when `ok`. */\n reason?: string\n}\n\n/** The outcome of a failover run: the value plus the full attempt trail. */\nexport interface ModelFailoverResult<T> {\n value: T\n /** The model that actually produced `value`. */\n model: string\n attempts: ModelFailoverAttempt[]\n /** True when the preferred (first) model did not serve the request. */\n usedFallback: boolean\n}\n\n/** Every model in the chain failed; carries the trail for logging. */\nexport class ModelFailoverExhaustedError extends Error {\n readonly attempts: ModelFailoverAttempt[]\n constructor(attempts: ModelFailoverAttempt[]) {\n const trail = attempts.map((a) => `${a.model}: ${a.reason ?? 'failed'}`).join(' | ')\n super(`All ${attempts.length} model(s) failed. ${trail}`)\n this.name = 'ModelFailoverExhaustedError'\n this.attempts = attempts\n }\n}\n\n/** Inputs to {@link runWithModelFailover}. */\nexport interface RunWithModelFailoverInput<T> {\n /** Preferred model first, then fallbacks in descending preference. */\n models: readonly string[]\n /** Executes one turn with the given model. */\n run: (model: string) => Promise<T>\n /**\n * Classifies a resolved result as an upstream outage. Defaults to\n * {@link isUpstreamUnavailable}, which understands the sandbox's\n * `{ success: false, errorCode }` payload.\n */\n isUnavailableResult?: (result: T) => boolean\n /** Classifies a thrown error. Defaults to {@link isUpstreamUnavailable}. */\n isUnavailableError?: (error: unknown) => boolean\n /** Observability hook fired each time a model is abandoned. */\n onFallback?: (attempt: ModelFailoverAttempt, nextModel: string) => void\n}\n\nfunction describe(signal: unknown): string {\n if (signal instanceof Error) return signal.message\n if (signal !== null && typeof signal === 'object') {\n const record = signal as Record<string, unknown>\n const message = readString(record, 'error') ?? readString(record, 'message') ?? readString(record, 'errorCode')\n if (message) return message\n }\n return String(signal)\n}\n\n/**\n * Run `run` against the first model in `models` that does not report an upstream\n * outage, falling through the chain in order.\n *\n * A non-outage failure (bad request, auth, content filter) is re-thrown\n * immediately rather than retried down the chain — those fail identically on\n * every model, so walking the chain would only multiply latency and spend.\n *\n * @throws ModelFailoverExhaustedError when every model reports an outage.\n */\nexport async function runWithModelFailover<T>(\n input: RunWithModelFailoverInput<T>,\n): Promise<ModelFailoverResult<T>> {\n const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0)\n if (models.length === 0) throw new Error('runWithModelFailover requires at least one model')\n\n const isUnavailableResult = input.isUnavailableResult ?? ((r: T) => isUpstreamUnavailable(r))\n const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable\n const attempts: ModelFailoverAttempt[] = []\n\n for (let index = 0; index < models.length; index += 1) {\n const model = models[index]!\n let result: T\n try {\n result = await input.run(model)\n } catch (error) {\n if (!isUnavailableError(error)) throw error\n const attempt: ModelFailoverAttempt = { model, ok: false, reason: describe(error) }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n if (isUnavailableResult(result)) {\n const attempt: ModelFailoverAttempt = { model, ok: false, reason: describe(result) }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n attempts.push({ model, ok: true })\n return { value: result, model, attempts, usedFallback: index > 0 }\n }\n\n throw new ModelFailoverExhaustedError(attempts)\n}\n\n/**\n * Build a failover chain: the preferred model first, then `fallbacks`, with\n * duplicates removed so a model is never retried twice in one turn.\n */\nexport function buildModelChain(preferred: string, fallbacks: readonly string[]): string[] {\n const chain: string[] = []\n for (const model of [preferred, ...fallbacks]) {\n const cleaned = typeof model === 'string' ? model.trim() : ''\n if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned)\n }\n return chain\n}\n","/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
@@ -1,5 +1,6 @@
1
1
  import { Part } from '@tangle-network/agent-interface';
2
2
  import { b as ChatInteractionField, c as ChatInteractionStatus, i as InteractionAnswers, N as NoticeKind } from './contract-B3h7peV3.js';
3
+ import { g as WorkProductPersistedPart } from './types-CEchbvgz.js';
3
4
  import { ChatPlanPersistedPart } from './plans/index.js';
4
5
 
5
6
  /**
@@ -300,6 +301,11 @@ declare function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[];
300
301
  * and field shapes.
301
302
  * - `plan`: the durable-plan projection in `/plans`, derived from the sandbox
302
303
  * SDK's authoritative plan lifecycle.
304
+ * - `work_product`: the persisted work-product anchor card in
305
+ * `/work-product`'s contract (`workProductToPersistedPart` /
306
+ * `persistedPartToWorkProduct`) — SYSTEM-authored on the ready transition
307
+ * and updated on a reviewer verdict; no prompt teaches an agent to author
308
+ * one.
303
309
  * - `mention`: an `@`-picked reference to a file that already lives in the
304
310
  * workspace sandbox (`FileMention` in `/chat-routes`'s wire contract, plus
305
311
  * the image/file discriminant). Neither transport lane produces it — the
@@ -439,6 +445,11 @@ interface ChatInteractionPart {
439
445
  }
440
446
  /** Resolve a chat plan part by aliasing it to the persisted chat plan part type */
441
447
  type ChatPlanPart = ChatPlanPersistedPart;
448
+ /** Persisted work-product anchor card — byte-matches
449
+ * `workProductToPersistedPart` in `/work-product`'s contract. Written by the
450
+ * PLATFORM on the ready transition (and updated on a verdict); never
451
+ * authored by a prompt. */
452
+ type ChatWorkProductPart = WorkProductPersistedPart;
442
453
  /** Persisted one-line transcript notice — byte-matches `noticePart` in
443
454
  * `/web-react`'s chat-interactions contract. */
444
455
  interface ChatNoticePart {
@@ -468,7 +479,7 @@ interface ChatMentionPart {
468
479
  turnId?: string;
469
480
  }
470
481
  /** Represent parts of a chat message including text, reasoning, tools, files, images, subtasks, steps, interactions, notices, plans, and mentions */
471
- type ChatMessagePart = ChatTextPart | ChatReasoningPart | ChatToolPart | ChatFilePart | ChatImagePart | ChatSubtaskPart | ChatStepStartPart | ChatStepFinishPart | ChatInteractionPart | ChatNoticePart | ChatPlanPart | ChatMentionPart;
482
+ type ChatMessagePart = ChatTextPart | ChatReasoningPart | ChatToolPart | ChatFilePart | ChatImagePart | ChatSubtaskPart | ChatStepStartPart | ChatStepFinishPart | ChatInteractionPart | ChatNoticePart | ChatPlanPart | ChatWorkProductPart | ChatMentionPart;
472
483
  /** Every canonical harness wire-part kind must be storable — compile-time
473
484
  * guarantee that a new agent-interface part kind cannot silently fall out of
474
485
  * the persisted vocabulary. */
@@ -492,6 +503,8 @@ declare function isChatTextPart(part: ChatMessagePart): part is ChatTextPart;
492
503
  declare function isChatInteractionPart(part: ChatMessagePart): part is ChatInteractionPart;
493
504
  /** Resolve whether a chat message part is a persisted chat plan part */
494
505
  declare function isChatPlanPart(part: ChatMessagePart): part is ChatPlanPart;
506
+ /** Resolve whether a chat message part is a persisted work-product anchor */
507
+ declare function isChatWorkProductPart(part: ChatMessagePart): part is ChatWorkProductPart;
495
508
  /** Determine if a chat message part represents the completion of a chat step */
496
509
  declare function isChatStepFinishPart(part: ChatMessagePart): part is ChatStepFinishPart;
497
510
  /** Widened to `unknown` — unlike its siblings this guard also runs over raw
@@ -594,4 +607,4 @@ declare function historyContentWithAttachments(message: {
594
607
  parts?: ReadonlyArray<Record<string, unknown>> | null;
595
608
  }, header?: string): string;
596
609
 
597
- export { type ProducerPassthroughEvent as $, isChatMentionPart as A, isChatPlanPart as B, type ChatAttachmentKind as C, DEFAULT_ATTACHMENT_PROMPT_HEADER as D, isChatStepFinishPart as E, isChatTextPart as F, isChatToolPart as G, mentionInputToPart as H, mentionPartsFromMessageParts as I, toChatMessageParts as J, type FileMention as K, type ChatTurnRequestPayload as L, type ChatTurnPartInput as M, type ChatTurnFilePartInput as N, type ChatAttachmentInput as O, ChatTurnInputError as P, type ChatTurnTextPartInput as Q, DISPATCH_MAX_MEDIA_PARTS as R, type StorableHarnessPartKind as S, DISPATCH_MAX_PARTS as T, DISPATCH_REQUEST_MAX_BYTES as U, DISPATCH_STRUCTURAL_RESERVE_BYTES as V, type FileMentionsToPartsOptions as W, INLINE_PARTS_MAX_BYTES as X, MENTION_MAX_COUNT as Y, type ProducerErrorEvent as Z, type ProducerNoticeEvent as _, type ChatAttachmentPart as a, type ProducerPassthroughEventType as a0, type ProducerReasoningEvent as a1, type ProducerTextEvent as a2, type ProducerToolCallEvent as a3, type ProducerToolResultEvent as a4, type ProducerUsageEvent as a5, type ProducerWireEvent as a6, type SandboxMentionPathCheck as a7, assertPromptPartsWithinCap as a8, base64WireLen as a9, buildMentionPromptBlock as aa, chatTurnRequestInit as ab, fileMentionsToParts as ac, formatBytes as ad, mediaTypeForMentionPath as ae, mentionKindForPath as af, parseChatTurnParts as ag, parseFileMentions as ah, promptPartsByteSize as ai, validateSandboxMentionPath as aj, type ChatFilePart as b, type ChatImagePart as c, type ChatInteractionPart as d, type ChatMentionKind as e, type ChatMentionPart as f, type ChatMessagePart as g, type ChatNoticePart as h, type ChatPartTime as i, type ChatPlanPart as j, type ChatReasoningPart as k, type ChatStepFinishPart as l, type ChatStepStartPart as m, type ChatSubtaskPart as n, type ChatTextPart as o, type ChatToolPart as p, type ChatToolState as q, type ChatToolStatus as r, type ChatUsageTokens as s, attachmentInputToPart as t, attachmentKindForMime as u, attachmentPartsFromMessageParts as v, buildAttachmentPromptBlock as w, historyContentWithAttachments as x, isChatAttachmentPart as y, isChatInteractionPart as z };
610
+ export { type ProducerErrorEvent as $, isChatInteractionPart as A, isChatMentionPart as B, type ChatAttachmentKind as C, DEFAULT_ATTACHMENT_PROMPT_HEADER as D, isChatPlanPart as E, isChatStepFinishPart as F, isChatTextPart as G, isChatToolPart as H, isChatWorkProductPart as I, mentionInputToPart as J, mentionPartsFromMessageParts as K, toChatMessageParts as L, type FileMention as M, type ChatTurnRequestPayload as N, type ChatTurnPartInput as O, type ChatTurnFilePartInput as P, type ChatAttachmentInput as Q, ChatTurnInputError as R, type StorableHarnessPartKind as S, type ChatTurnTextPartInput as T, DISPATCH_MAX_MEDIA_PARTS as U, DISPATCH_MAX_PARTS as V, DISPATCH_REQUEST_MAX_BYTES as W, DISPATCH_STRUCTURAL_RESERVE_BYTES as X, type FileMentionsToPartsOptions as Y, INLINE_PARTS_MAX_BYTES as Z, MENTION_MAX_COUNT as _, type ChatAttachmentPart as a, type ProducerNoticeEvent as a0, type ProducerPassthroughEvent as a1, type ProducerPassthroughEventType as a2, type ProducerReasoningEvent as a3, type ProducerTextEvent as a4, type ProducerToolCallEvent as a5, type ProducerToolResultEvent as a6, type ProducerUsageEvent as a7, type ProducerWireEvent as a8, type SandboxMentionPathCheck as a9, assertPromptPartsWithinCap as aa, base64WireLen as ab, buildMentionPromptBlock as ac, chatTurnRequestInit as ad, fileMentionsToParts as ae, formatBytes as af, mediaTypeForMentionPath as ag, mentionKindForPath as ah, parseChatTurnParts as ai, parseFileMentions as aj, promptPartsByteSize as ak, validateSandboxMentionPath as al, type ChatFilePart as b, type ChatImagePart as c, type ChatInteractionPart as d, type ChatMentionKind as e, type ChatMentionPart as f, type ChatMessagePart as g, type ChatNoticePart as h, type ChatPartTime as i, type ChatPlanPart as j, type ChatReasoningPart as k, type ChatStepFinishPart as l, type ChatStepStartPart as m, type ChatSubtaskPart as n, type ChatTextPart as o, type ChatToolPart as p, type ChatToolState as q, type ChatToolStatus as r, type ChatUsageTokens as s, type ChatWorkProductPart as t, attachmentInputToPart as u, attachmentKindForMime as v, attachmentPartsFromMessageParts as w, buildAttachmentPromptBlock as x, historyContentWithAttachments as y, isChatAttachmentPart as z };
@@ -0,0 +1,68 @@
1
+ import { i as WorkProductRecord, j as WorkProductRef, h as WorkProductProvenance } from './types-CEchbvgz.js';
2
+
3
+ /**
4
+ * The review queue is a PROJECTION, not a store — a client-safe pure fold of
5
+ * existing sources into queue items (the `/missions` events.ts pattern: pure
6
+ * data, re-validation at JSON boundaries). The only genuinely-new durable
7
+ * state behind it is the {@link WorkProductRecord} row and its status
8
+ * machine; everything else reads what already exists:
9
+ *
10
+ * - intake: a chat thread for the engagement scope with NO record yet
11
+ * - missing_info: the open record's thread has a PENDING `/interactions` ask
12
+ * - working: record status `draft` (the live token tail stays on the chat
13
+ * surface's existing running-turns endpoint — the projection tracks no
14
+ * live runs, per the reuse-the-primitive invariant)
15
+ * - ready_for_review / changes_requested / approved / blocked: read directly
16
+ * off `WorkProductRecord.status` (blocked surfaces its unresolved count)
17
+ */
18
+
19
+ type ReviewQueueState = 'intake' | 'missing_info' | 'working' | 'ready_for_review' | 'changes_requested' | 'approved' | 'blocked';
20
+ /** One row of the review queue projection for an engagement scope */
21
+ interface ReviewQueueItem {
22
+ scopeKey: string;
23
+ state: ReviewQueueState;
24
+ threadId: string | null;
25
+ workProduct?: WorkProductRef & {
26
+ title: string;
27
+ kind: string;
28
+ };
29
+ /** The pending `/interactions` ask parking this scope, when any. */
30
+ pendingAsk?: {
31
+ interactionId: string;
32
+ title: string;
33
+ };
34
+ blockingExceptions: number;
35
+ failedChecks: number;
36
+ provenance?: Pick<WorkProductProvenance, 'profileHash' | 'servingModels'>;
37
+ updatedAt: number;
38
+ }
39
+ /** An engagement-scoped chat thread — the intake candidate source. Products
40
+ * that scope threads already carry a scopeKey-style column. */
41
+ interface ReviewQueueThread {
42
+ scopeKey: string;
43
+ threadId: string;
44
+ updatedAt: number;
45
+ }
46
+ /** A pending `/interactions` ask on a thread (from the existing list
47
+ * endpoint) — the missing_info source. */
48
+ interface ReviewQueuePendingAsk {
49
+ threadId: string;
50
+ interactionId: string;
51
+ title: string;
52
+ }
53
+ /** Existing-source inputs the projection folds — no new stores */
54
+ interface ReviewQueueInputs {
55
+ workProducts: readonly WorkProductRecord[];
56
+ /** Engagement threads with no work product yet → intake items. */
57
+ threads?: readonly ReviewQueueThread[];
58
+ /** Pending asks by thread → missing_info override on open records. */
59
+ pendingAsks?: readonly ReviewQueuePendingAsk[];
60
+ }
61
+ /** Fold the existing sources into queue items, newest first. */
62
+ declare function projectReviewQueue(inputs: ReviewQueueInputs): ReviewQueueItem[];
63
+ /** Re-validate one JSON-boundary row into a queue item; null for junk. The
64
+ * client-side twin of the server projection, for payloads that cross a
65
+ * fetch boundary. */
66
+ declare function parseReviewQueueItem(raw: unknown): ReviewQueueItem | null;
67
+
68
+ export { type ReviewQueueInputs as R, type ReviewQueueItem as a, type ReviewQueuePendingAsk as b, type ReviewQueueState as c, type ReviewQueueThread as d, projectReviewQueue as e, parseReviewQueueItem as p };
@@ -8,7 +8,7 @@ import {
8
8
  runToolLoop,
9
9
  streamToolLoop,
10
10
  toLoopEvents
11
- } from "../chunk-JWBZ74TW.js";
11
+ } from "../chunk-7ESQUSAC.js";
12
12
  import {
13
13
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
14
14
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -24,7 +24,8 @@ import {
24
24
  tangleExecutionKeyHttpError,
25
25
  trimOrNull
26
26
  } from "../chunk-JML7WKWU.js";
27
- import "../chunk-7CTIUCQ4.js";
27
+ import "../chunk-UOAYS72M.js";
28
+ import "../chunk-YEFFHORB.js";
28
29
  import {
29
30
  __resetCatalogCache,
30
31
  buildCatalog,
@@ -60,11 +60,12 @@ import {
60
60
  writeProfileFilesToBox
61
61
  } from "../chunk-3ALFBTIW.js";
62
62
  import "../chunk-CQZSAR77.js";
63
- import "../chunk-YKBDH2UY.js";
63
+ import "../chunk-WL7XHLDK.js";
64
64
  import "../chunk-3EJ6SFJI.js";
65
65
  import "../chunk-S5SRJJQG.js";
66
66
  import "../chunk-JML7WKWU.js";
67
- import "../chunk-7CTIUCQ4.js";
67
+ import "../chunk-UOAYS72M.js";
68
+ import "../chunk-YEFFHORB.js";
68
69
  export {
69
70
  DEFAULT_SANDBOX_RESOURCES,
70
71
  ENV_TOTAL_MAX_BYTES,
@@ -1,3 +1,8 @@
1
+ import {
2
+ generateInviteToken,
3
+ isInviteTokenShape,
4
+ validateInviteToken
5
+ } from "../chunk-DJ4VJIH5.js";
1
6
  import {
2
7
  INVITATION_EXPIRY_DAYS,
3
8
  generateInvitationToken,
@@ -7,11 +12,6 @@ import {
7
12
  parseInvitationPermission,
8
13
  renderInvitationEmail
9
14
  } from "../chunk-2DRYTJHI.js";
10
- import {
11
- generateInviteToken,
12
- isInviteTokenShape,
13
- validateInviteToken
14
- } from "../chunk-DJ4VJIH5.js";
15
15
  import {
16
16
  ASSIGNABLE_WORKSPACE_ROLES,
17
17
  ORGANIZATION_ROLES,
@@ -1,3 +1,7 @@
1
+ import {
2
+ SeatLimitError
3
+ } from "../chunk-MEUNTJL5.js";
4
+ import "../chunk-DJ4VJIH5.js";
1
5
  import {
2
6
  generateInvitationToken,
3
7
  getInvitationExpiresAt,
@@ -5,10 +9,6 @@ import {
5
9
  normalizeInvitationEmail,
6
10
  parseInvitationPermission
7
11
  } from "../chunk-2DRYTJHI.js";
8
- import {
9
- SeatLimitError
10
- } from "../chunk-MEUNTJL5.js";
11
- import "../chunk-DJ4VJIH5.js";
12
12
  import {
13
13
  hasWorkspaceRole
14
14
  } from "../chunk-6XIAPIW6.js";
@@ -1,12 +1,12 @@
1
+ import {
2
+ InviteAcceptPage
3
+ } from "../chunk-VCPZ3HTN.js";
1
4
  import {
2
5
  MembersPanel
3
6
  } from "../chunk-S564OFTL.js";
4
7
  import {
5
8
  InvitationsPanel
6
9
  } from "../chunk-5SXS3YAB.js";
7
- import {
8
- InviteAcceptPage
9
- } from "../chunk-VCPZ3HTN.js";
10
10
  import "../chunk-6XIAPIW6.js";
11
11
  export {
12
12
  InvitationsPanel,
@@ -6,7 +6,7 @@ import {
6
6
  restrictTaxonomy,
7
7
  verifyCapabilityToken,
8
8
  verifyExpiringCapabilityToken
9
- } from "../chunk-YKBDH2UY.js";
9
+ } from "../chunk-WL7XHLDK.js";
10
10
  import {
11
11
  DEFAULT_APP_TOOL_PATHS,
12
12
  DEFAULT_HEADER_NAMES,
@@ -19,18 +19,20 @@ import {
19
19
  readToolArgs
20
20
  } from "../chunk-3EJ6SFJI.js";
21
21
  import "../chunk-S5SRJJQG.js";
22
+ import {
23
+ createAppToolRuntimeExecutor,
24
+ dispatchAppTool,
25
+ outcomeStatus
26
+ } from "../chunk-UOAYS72M.js";
22
27
  import {
23
28
  APP_TOOL_NAMES,
24
29
  ToolInputError,
25
30
  buildAppToolOpenAITools,
26
- createAppToolRuntimeExecutor,
27
31
  customToolToOpenAI,
28
32
  defineAppTool,
29
- dispatchAppTool,
30
33
  findCustomTool,
31
- isAppToolName,
32
- outcomeStatus
33
- } from "../chunk-7CTIUCQ4.js";
34
+ isAppToolName
35
+ } from "../chunk-YEFFHORB.js";
34
36
  export {
35
37
  APP_TOOL_NAMES,
36
38
  DEFAULT_APP_TOOL_PATHS,
@@ -0,0 +1,83 @@
1
+ import { JudgeVerdict } from '@tangle-network/agent-eval';
2
+
3
+ /**
4
+ * Trust gate — decides whether an ensemble's scores are allowed to be BELIEVED,
5
+ * one level up from {@link aggregateJudgeVerdicts} (which only reduces ONE
6
+ * artifact's raters to a composite). A composite is a number; this is the check
7
+ * that the number means anything. It is the code "Enforced by" for the
8
+ * measurement-validation skill's after-gate ("is this result allowed to be
9
+ * believed").
10
+ *
11
+ * Three checks, each fail-loud and named in `trustReasons`:
12
+ * (1) inter-rater reliability over the corpus ≥ `irrFloor` — raters that
13
+ * disagree no better than chance carry no signal to optimize against.
14
+ * (2) per-item rater spread ≤ `spreadCeiling` — for EACH item, raters must
15
+ * converge on THAT item.
16
+ * (3) surviving raters per item ≥ `minSurvivors` — a mean over one or two
17
+ * raters is an anecdote, not an ensemble.
18
+ *
19
+ * CRITICAL metric semantics — per-item spread is rater disagreement about the
20
+ * SAME item: `max(score) − min(score)` across the raters that scored THAT item
21
+ * (max over its dimensions), never pooled across different items or across the
22
+ * baseline/candidate sides. Pooling reads a genuine quality gap BETWEEN items as
23
+ * "the raters split" and so trips the gate exactly when the finding is largest —
24
+ * the failure mode the after-gate exists to prevent. The corpus IRR (check 1)
25
+ * leans on the substrate's `interRaterReliability`, whose expected-disagreement
26
+ * denominator already pools across items, so genuine item-to-item variation
27
+ * RAISES reliability rather than lowering it.
28
+ */
29
+
30
+ /** One item's raters: the per-judge verdicts {@link aggregateJudgeVerdicts}
31
+ * reduces, tagged with the item they scored so spread stays within-item. */
32
+ interface TrustItem<D extends string = string> {
33
+ /** Stable item identifier — surfaces in `perItemSpread` and `trustReasons`. */
34
+ itemId: string;
35
+ /** The raters' verdicts for THIS item (one per judge call). A failed judge
36
+ * (`perDimension: null`) is dropped before spread/IRR, never folded as 0. */
37
+ verdicts: readonly JudgeVerdict<D>[];
38
+ }
39
+ /** Thresholds for {@link trustVerdicts}. All overridable; defaults are the
40
+ * conservative after-gate bar. */
41
+ interface TrustThresholds {
42
+ /** Minimum corpus inter-rater reliability (Krippendorff-style α). Below this
43
+ * the raters agree no better than chance. Default 0.2. */
44
+ irrFloor?: number;
45
+ /** Maximum per-item rater spread (`max − min` over a single item's surviving
46
+ * raters, across its dimensions). Above this the raters split ON THAT ITEM.
47
+ * Default 0.5. */
48
+ spreadCeiling?: number;
49
+ /** Minimum surviving (non-failed) raters required per item. Default 3. */
50
+ minSurvivors?: number;
51
+ }
52
+ /** Result of the trust gate. `trustworthy` iff every check passed; `trustReasons`
53
+ * is empty iff `trustworthy`. */
54
+ interface TrustVerdict {
55
+ /** True iff IRR ≥ floor AND every item's spread ≤ ceiling AND every item has
56
+ * ≥ `minSurvivors` surviving raters. */
57
+ trustworthy: boolean;
58
+ /** One entry per FAILED check, each naming its number + the offending value.
59
+ * Empty iff `trustworthy`. */
60
+ trustReasons: string[];
61
+ /** Corpus inter-rater reliability actually measured (the check-1 value). */
62
+ interRaterReliability: number;
63
+ /** Per-item spread (`max − min` over surviving raters, max over dimensions),
64
+ * keyed by `itemId`. The check-2 input, surfaced for drill-down. */
65
+ perItemSpread: Record<string, number>;
66
+ }
67
+ /**
68
+ * Decide whether an ensemble's per-item verdicts are trustworthy enough to
69
+ * believe a lift computed from them. Pure: no LLM, no I/O, no clock, no random —
70
+ * the same `items` + `thresholds` always yield the same verdict.
71
+ *
72
+ * Sibling to {@link aggregateJudgeVerdicts}: that reduces ONE item's raters to a
73
+ * composite; this audits the raters ACROSS items and reports whether the
74
+ * composites are believable. Run it on the corpus of held-out items before
75
+ * reporting any lift over their scores.
76
+ *
77
+ * @throws if `items` is empty — an empty corpus has no measurable trust, and a
78
+ * silent `trustworthy: true` over zero evidence is the exact lie the gate
79
+ * exists to refuse.
80
+ */
81
+ declare function trustVerdicts<D extends string>(items: readonly TrustItem<D>[], thresholds?: TrustThresholds): TrustVerdict;
82
+
83
+ export { type TrustItem as T, type TrustThresholds as a, type TrustVerdict as b, trustVerdicts as t };