@runtypelabs/sdk 10.8.1 → 10.11.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/dist/index.mjs CHANGED
@@ -2079,21 +2079,6 @@ var AgentAliasNotFoundError = class extends Error {
2079
2079
  this.agentId = body.agentId;
2080
2080
  }
2081
2081
  };
2082
- /**
2083
- * A new preview pointer refused by the active-preview quota (HTTP 429). Archive
2084
- * a preview you no longer need — archiving frees the quota immediately — or wait
2085
- * for one to expire. `live` is never counted.
2086
- */
2087
- var AgentAliasPreviewLimitError = class extends Error {
2088
- constructor(body) {
2089
- super(body.error ?? `This ${body.scope} already has ${body.limit} active preview aliases, the maximum.`);
2090
- this.code = "PREVIEW_ALIAS_LIMIT";
2091
- this.name = "AgentAliasPreviewLimitError";
2092
- this.scope = body.scope;
2093
- this.limit = body.limit;
2094
- this.active = body.active;
2095
- }
2096
- };
2097
2082
  /** A version whose named references no longer resolve in this account (HTTP 422). */
2098
2083
  var AgentAliasDependencyError = class extends Error {
2099
2084
  constructor(body) {
@@ -2113,7 +2098,7 @@ function asRecord(value) {
2113
2098
  * Reading the structured fields FIRST is what makes the typed alias errors fire
2114
2099
  * for `createClient()` and the CLI, not just for `Runtype.agents`.
2115
2100
  */
2116
- function parseRequestError$6(err) {
2101
+ function parseRequestError$7(err) {
2117
2102
  if (!(err instanceof Error)) return {
2118
2103
  status: null,
2119
2104
  body: null
@@ -2148,7 +2133,7 @@ function asString$1(value, fallback) {
2148
2133
  * failure exactly as the transport threw it.
2149
2134
  */
2150
2135
  function toAliasError(err, agentId, alias) {
2151
- const { status, body } = parseRequestError$6(err);
2136
+ const { status, body } = parseRequestError$7(err);
2152
2137
  if (status === null) return null;
2153
2138
  const error = body && typeof body.error === "string" ? body.error : void 0;
2154
2139
  if (status === 412 && body && typeof body.expected === "number") return new AgentAliasRevisionMismatchError({
@@ -2162,12 +2147,6 @@ function toAliasError(err, agentId, alias) {
2162
2147
  alias: asString$1(body.alias, alias),
2163
2148
  agentId: asString$1(body.agentId, agentId)
2164
2149
  });
2165
- if (status === 429 && body?.code === "PREVIEW_ALIAS_LIMIT") return new AgentAliasPreviewLimitError({
2166
- ...error !== void 0 ? { error } : {},
2167
- scope: body.scope === "organization" ? "organization" : "agent",
2168
- limit: typeof body.limit === "number" ? body.limit : 0,
2169
- active: typeof body.active === "number" ? body.active : 0
2170
- });
2171
2150
  if (status === 422 && body?.code === "alias_dependency_unresolved") return new AgentAliasDependencyError({
2172
2151
  ...error !== void 0 ? { error } : {},
2173
2152
  refs: Array.isArray(body.refs) ? body.refs.filter((ref) => typeof ref === "string") : []
@@ -2178,8 +2157,7 @@ const TYPED_ALIAS_ERRORS = [
2178
2157
  AgentAliasRevisionMismatchError,
2179
2158
  AgentAliasRevisionRequiredError,
2180
2159
  AgentAliasNotFoundError,
2181
- AgentAliasDependencyError,
2182
- AgentAliasPreviewLimitError
2160
+ AgentAliasDependencyError
2183
2161
  ];
2184
2162
  /** The refusal code, when this is one of the typed alias errors. */
2185
2163
  function agentAliasErrorCode(err) {
@@ -2338,6 +2316,174 @@ var AgentDeploymentsNamespace = class {
2338
2316
  }
2339
2317
  };
2340
2318
  //#endregion
2319
+ //#region src/content-hash.ts
2320
+ /**
2321
+ * Canonical content-hash primitives shared by the `*.ensure()` config-as-code
2322
+ * surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
2323
+ * those modules computes a SHA-256 content hash for its hash-first `/ensure`
2324
+ * wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
2325
+ * output for any given definition is a WIRE CONTRACT with the server, so this
2326
+ * module must stay dependency-free (the SDK is published to npm and must not
2327
+ * import `@runtypelabs/shared`, a private package) and must not change its
2328
+ * normalization behavior without a matching server-side change.
2329
+ *
2330
+ * `isPlainObject` and `normalizeValue` here are mirrored from
2331
+ * `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
2332
+ * keys and drops `null`/`undefined` entries recursively — this is the
2333
+ * canonical normalization used by products, tools, skills, surfaces, and the
2334
+ * whole-FPO hash.
2335
+ *
2336
+ * NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
2337
+ * (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
2338
+ * (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
2339
+ * in the flow case, use a shallower array-recursion rule. Those are real
2340
+ * semantic divergences from this canonical normalizer, not incidental
2341
+ * duplication — changing either to reuse `normalizeValue` would alter the
2342
+ * content hash for definitions containing explicit `null`s and break the
2343
+ * wire contract with already-converged flows/evals. They stay local by
2344
+ * design; see the divergence comment at each site.
2345
+ */
2346
+ function isPlainObject$1(value) {
2347
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2348
+ }
2349
+ /**
2350
+ * Recursively normalizes a value for canonical hashing: object keys are
2351
+ * sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
2352
+ * and have each item normalized in turn.
2353
+ */
2354
+ function normalizeValue$1(value) {
2355
+ if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
2356
+ if (isPlainObject$1(value)) {
2357
+ const normalized = {};
2358
+ for (const key of Object.keys(value).sort()) {
2359
+ const entry = value[key];
2360
+ if (entry === void 0 || entry === null) continue;
2361
+ normalized[key] = normalizeValue$1(entry);
2362
+ }
2363
+ return normalized;
2364
+ }
2365
+ return value;
2366
+ }
2367
+ /** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
2368
+ async function sha256Hex(serialized) {
2369
+ const encoded = new TextEncoder().encode(serialized);
2370
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
2371
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
2372
+ }
2373
+ //#endregion
2374
+ //#region src/collections-ensure.ts
2375
+ /** Canonical normalized form of a collection definition (slug excluded — identity). */
2376
+ function normalizeRecordCollectionDefinition(definition) {
2377
+ return {
2378
+ displayName: definition.displayName,
2379
+ ...definition.description ? { description: definition.description } : {},
2380
+ ...definition.icon ? { icon: definition.icon } : {},
2381
+ ...isPlainObject$1(definition.schema) ? { schema: normalizeValue$1(definition.schema) } : {},
2382
+ validationMode: definition.validationMode || "off"
2383
+ };
2384
+ }
2385
+ /** SHA-256 (hex) over the canonical normalized collection definition. */
2386
+ async function computeRecordCollectionContentHash(definition) {
2387
+ return sha256Hex(JSON.stringify(normalizeRecordCollectionDefinition(definition)));
2388
+ }
2389
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
2390
+ var CollectionEnsureConflictError = class extends Error {
2391
+ constructor(body) {
2392
+ super(body.error ?? `Collection ensure conflict: ${body.code}`);
2393
+ this.name = "CollectionEnsureConflictError";
2394
+ this.code = body.code;
2395
+ this.lastModifiedSource = body.lastModifiedSource;
2396
+ this.modifiedAt = body.modifiedAt;
2397
+ this.currentHash = body.currentHash;
2398
+ }
2399
+ };
2400
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
2401
+ var CollectionDriftError = class extends Error {
2402
+ constructor(slug, plan) {
2403
+ super(`Collection "${slug}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.collections.pull(slug) to absorb the remote edit into your repo, or re-run ensure to converge.`);
2404
+ this.name = "CollectionDriftError";
2405
+ this.plan = plan;
2406
+ }
2407
+ };
2408
+ function toConflictError$8(err) {
2409
+ if (!(err instanceof Error)) return null;
2410
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
2411
+ if (!match || match[1] !== "409") return null;
2412
+ let body;
2413
+ try {
2414
+ body = JSON.parse(match[2]);
2415
+ } catch {
2416
+ return null;
2417
+ }
2418
+ if (!isPlainObject$1(body)) return null;
2419
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
2420
+ return new CollectionEnsureConflictError(body);
2421
+ }
2422
+ /** Per-transport memo of `{ slug + localHash → server contentHash }`, as in tools.ensure. */
2423
+ const serverHashMemo$9 = /* @__PURE__ */ new WeakMap();
2424
+ function memoFor$9(transport) {
2425
+ let memo = serverHashMemo$9.get(transport);
2426
+ if (!memo) {
2427
+ memo = /* @__PURE__ */ new Map();
2428
+ serverHashMemo$9.set(transport, memo);
2429
+ }
2430
+ return memo;
2431
+ }
2432
+ async function request$7(transport, body) {
2433
+ try {
2434
+ return await transport.post("/collections/ensure", body);
2435
+ } catch (err) {
2436
+ throw toConflictError$8(err) ?? err;
2437
+ }
2438
+ }
2439
+ /**
2440
+ * Idempotently converge a collection definition. Probes with a content hash and
2441
+ * only ships the full definition when the server reports a miss. Never deletes
2442
+ * a collection and never touches records.
2443
+ */
2444
+ async function ensureCollection(transport, definition, options = {}) {
2445
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
2446
+ const passthrough = {
2447
+ ...onConflict ? { onConflict } : {},
2448
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
2449
+ };
2450
+ if (dryRun || expectNoChanges) {
2451
+ const plan = await request$7(transport, {
2452
+ slug: definition.slug,
2453
+ definition,
2454
+ dryRun: true,
2455
+ ...passthrough
2456
+ });
2457
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
2458
+ if (expectNoChanges && plan.changes !== "none") throw new CollectionDriftError(definition.slug, plan);
2459
+ return plan;
2460
+ }
2461
+ const memo = memoFor$9(transport);
2462
+ const localHash = await computeRecordCollectionContentHash(definition);
2463
+ const memoKey = `${definition.slug} ${localHash}`;
2464
+ const probe = await request$7(transport, {
2465
+ slug: definition.slug,
2466
+ contentHash: memo.get(memoKey) ?? localHash,
2467
+ ...passthrough
2468
+ });
2469
+ if (probe.result !== "definitionRequired") {
2470
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
2471
+ return probe;
2472
+ }
2473
+ const converged = await request$7(transport, {
2474
+ slug: definition.slug,
2475
+ definition,
2476
+ ...passthrough
2477
+ });
2478
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
2479
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
2480
+ return converged;
2481
+ }
2482
+ /** Pull the canonical definition and provenance for a collection by slug. */
2483
+ async function pullCollection(transport, slug) {
2484
+ return transport.get(`/collections/${encodeURIComponent(slug)}/pull`);
2485
+ }
2486
+ //#endregion
2341
2487
  //#region src/generated-tool-gate.ts
2342
2488
  const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
2343
2489
  const DEFAULT_MAX_CODE_LENGTH = 12e3;
@@ -2666,6 +2812,239 @@ function normalizeDispatchRequest(request) {
2666
2812
  };
2667
2813
  }
2668
2814
  //#endregion
2815
+ //#region src/schedules-ensure.ts
2816
+ function nonEmptyObject(value) {
2817
+ if (!isPlainObject$1(value)) return void 0;
2818
+ const normalized = normalizeValue$1(value);
2819
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
2820
+ }
2821
+ function nonEmptyString(value) {
2822
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2823
+ }
2824
+ function canonicalRunAt(value) {
2825
+ if (value === null || value === void 0 || value === "") return void 0;
2826
+ const date = value instanceof Date ? value : new Date(value);
2827
+ return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
2828
+ }
2829
+ /** Canonical normalized schedule content (name and target excluded — identity). */
2830
+ function normalizeScheduleContent(definition) {
2831
+ const cron = nonEmptyString(definition.cron);
2832
+ const timezone = nonEmptyString(definition.timezone);
2833
+ const runAt = canonicalRunAt(definition.runAt);
2834
+ const recordIds = Array.isArray(definition.recordIds) && definition.recordIds.length > 0 ? [...definition.recordIds] : void 0;
2835
+ const recordType = nonEmptyString(definition.recordType);
2836
+ const recordFilter = nonEmptyObject(definition.recordFilter);
2837
+ const messages = Array.isArray(definition.messages) && definition.messages.length > 0 ? normalizeValue$1(definition.messages) : void 0;
2838
+ const inputs = nonEmptyObject(definition.inputs);
2839
+ const executionOptions = nonEmptyObject(definition.executionOptions);
2840
+ return {
2841
+ triggerType: definition.triggerType === "one_time" ? "one_time" : "recurring",
2842
+ ...cron !== void 0 ? { cron } : {},
2843
+ ...timezone !== void 0 ? { timezone } : {},
2844
+ ...runAt !== void 0 ? { runAt } : {},
2845
+ ...recordIds !== void 0 ? { recordIds } : {},
2846
+ ...recordType !== void 0 ? { recordType } : {},
2847
+ ...recordFilter !== void 0 ? { recordFilter } : {},
2848
+ ...messages !== void 0 ? { messages } : {},
2849
+ ...inputs !== void 0 ? { inputs } : {},
2850
+ ...executionOptions !== void 0 ? { executionOptions } : {},
2851
+ ...typeof definition.enabled === "boolean" ? { enabled: definition.enabled } : {}
2852
+ };
2853
+ }
2854
+ /** SHA-256 (hex) over canonical schedule content, in the flat content shape. */
2855
+ async function computeScheduleContentHash(definition) {
2856
+ return sha256Hex(JSON.stringify(normalizeScheduleContent(definition)));
2857
+ }
2858
+ /** The content hash of a `defineSchedule` definition. */
2859
+ async function computeScheduleDefinitionHash(definition) {
2860
+ return computeScheduleContentHash({
2861
+ triggerType: definition.trigger.type,
2862
+ cron: definition.trigger.cron,
2863
+ timezone: definition.trigger.timezone,
2864
+ runAt: definition.trigger.runAt,
2865
+ recordIds: definition.recordIds,
2866
+ recordType: definition.recordType,
2867
+ recordFilter: definition.recordFilter,
2868
+ messages: definition.messages,
2869
+ inputs: definition.inputs,
2870
+ executionOptions: definition.executionOptions,
2871
+ enabled: definition.enabled
2872
+ });
2873
+ }
2874
+ const TARGET_KEYS = [
2875
+ "flowId",
2876
+ "agentId",
2877
+ "flowName",
2878
+ "agentName"
2879
+ ];
2880
+ const DEFINE_SCHEDULE_KEYS = /* @__PURE__ */ new Set([
2881
+ "name",
2882
+ "target",
2883
+ "trigger",
2884
+ "recordIds",
2885
+ "recordType",
2886
+ "recordFilter",
2887
+ "messages",
2888
+ "inputs",
2889
+ "executionOptions",
2890
+ "enabled"
2891
+ ]);
2892
+ /**
2893
+ * Pure-local declarative constructor for a standalone schedule. No I/O.
2894
+ * Validates structure; cron, timezone, record filter and plan limits are
2895
+ * checked server-side at ensure time.
2896
+ *
2897
+ * @example
2898
+ * ```typescript
2899
+ * const nightly = defineSchedule({
2900
+ * name: 'Nightly digest',
2901
+ * target: { flowName: 'Digest' },
2902
+ * trigger: { type: 'recurring', cron: '0 2 * * *', timezone: 'UTC' },
2903
+ * })
2904
+ * ```
2905
+ */
2906
+ function defineSchedule(input) {
2907
+ if (!input || typeof input !== "object") throw new Error("defineSchedule requires a definition object");
2908
+ if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSchedule requires a non-empty string \"name\"");
2909
+ if (!isPlainObject$1(input.target)) throw new Error("defineSchedule requires a \"target\" object");
2910
+ const target = input.target;
2911
+ const setKeys = TARGET_KEYS.filter((key) => target[key] !== void 0);
2912
+ const extraKeys = Object.keys(target).filter((key) => !TARGET_KEYS.includes(key));
2913
+ if (setKeys.length !== 1 || extraKeys.length > 0) throw new Error("defineSchedule \"target\" needs exactly one of flowId, agentId, flowName, agentName");
2914
+ const trigger = input.trigger;
2915
+ if (!isPlainObject$1(trigger) || trigger.type !== "recurring" && trigger.type !== "one_time") throw new Error("defineSchedule requires \"trigger.type\" to be \"recurring\" or \"one_time\"");
2916
+ if (trigger.type === "recurring" && (!trigger.cron || !trigger.timezone)) throw new Error("defineSchedule: recurring triggers require \"cron\" and \"timezone\"");
2917
+ if (trigger.type === "one_time" && !trigger.runAt) throw new Error("defineSchedule: one_time triggers require \"runAt\"");
2918
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SCHEDULE_KEYS.has(key));
2919
+ if (unknownKeys.length > 0) throw new Error(`defineSchedule: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are ${[...DEFINE_SCHEDULE_KEYS].join(", ")}.`);
2920
+ return { ...input };
2921
+ }
2922
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
2923
+ var ScheduleEnsureConflictError = class extends Error {
2924
+ constructor(body) {
2925
+ super(body.error ?? `Schedule ensure conflict: ${body.code}`);
2926
+ this.name = "ScheduleEnsureConflictError";
2927
+ this.code = body.code;
2928
+ this.lastModifiedSource = body.lastModifiedSource;
2929
+ this.modifiedAt = body.modifiedAt;
2930
+ this.currentHash = body.currentHash;
2931
+ }
2932
+ };
2933
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
2934
+ var ScheduleDriftError = class extends Error {
2935
+ constructor(plan) {
2936
+ super(`Schedule "${plan.scheduleId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run schedules.pull(name, target) to absorb the remote edit into your repo, or re-run ensure to converge.`);
2937
+ this.name = "ScheduleDriftError";
2938
+ this.plan = plan;
2939
+ }
2940
+ };
2941
+ function errorStatusAndBody(err) {
2942
+ if (!(err instanceof Error)) return {
2943
+ status: null,
2944
+ body: null
2945
+ };
2946
+ const typed = err;
2947
+ if (typeof typed.statusCode === "number") return {
2948
+ status: typed.statusCode,
2949
+ body: typed.data
2950
+ };
2951
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
2952
+ if (!match) return {
2953
+ status: null,
2954
+ body: null
2955
+ };
2956
+ try {
2957
+ return {
2958
+ status: Number(match[1]),
2959
+ body: JSON.parse(match[2])
2960
+ };
2961
+ } catch {
2962
+ return {
2963
+ status: Number(match[1]),
2964
+ body: null
2965
+ };
2966
+ }
2967
+ }
2968
+ function toConflictError$7(err) {
2969
+ const { status, body } = errorStatusAndBody(err);
2970
+ if (status !== 409 || !isPlainObject$1(body)) return null;
2971
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
2972
+ return new ScheduleEnsureConflictError(body);
2973
+ }
2974
+ const serverHashMemo$8 = /* @__PURE__ */ new WeakMap();
2975
+ function memoFor$8(client) {
2976
+ let memo = serverHashMemo$8.get(client);
2977
+ if (!memo) {
2978
+ memo = /* @__PURE__ */ new Map();
2979
+ serverHashMemo$8.set(client, memo);
2980
+ }
2981
+ return memo;
2982
+ }
2983
+ async function request$6(client, body) {
2984
+ try {
2985
+ return await client.post("/schedules/ensure", body);
2986
+ } catch (err) {
2987
+ const conflict = toConflictError$7(err);
2988
+ if (conflict) throw conflict;
2989
+ throw err;
2990
+ }
2991
+ }
2992
+ /**
2993
+ * Idempotently converge a standalone schedule. Hash-first: probes with a
2994
+ * content hash and ships the full definition only on a miss. Never deletes.
2995
+ */
2996
+ async function ensureSchedule(client, definition, options = {}) {
2997
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
2998
+ const identity = {
2999
+ name: definition.name,
3000
+ target: definition.target
3001
+ };
3002
+ const passthrough = {
3003
+ ...onConflict ? { onConflict } : {},
3004
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3005
+ };
3006
+ if (dryRun || expectNoChanges) {
3007
+ const plan = await request$6(client, {
3008
+ ...identity,
3009
+ definition,
3010
+ dryRun: true,
3011
+ ...passthrough
3012
+ });
3013
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3014
+ if (expectNoChanges && plan.changes !== "none") throw new ScheduleDriftError(plan);
3015
+ return plan;
3016
+ }
3017
+ const memo = memoFor$8(client);
3018
+ const localHash = await computeScheduleDefinitionHash(definition);
3019
+ const memoKey = `${JSON.stringify(identity)} ${localHash}`;
3020
+ if (!expectedRemoteHash) {
3021
+ const probe = await request$6(client, {
3022
+ ...identity,
3023
+ contentHash: memo.get(memoKey) ?? localHash,
3024
+ ...passthrough
3025
+ });
3026
+ if (probe.result !== "definitionRequired") {
3027
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
3028
+ return probe;
3029
+ }
3030
+ }
3031
+ const converged = await request$6(client, {
3032
+ ...identity,
3033
+ definition,
3034
+ ...passthrough
3035
+ });
3036
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
3037
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
3038
+ return converged;
3039
+ }
3040
+ /** Pull the canonical definition + provenance for a schedule by name and target. */
3041
+ async function pullSchedule(client, name, target) {
3042
+ return client.get("/schedules/pull", {
3043
+ name,
3044
+ ...target
3045
+ });
3046
+ }
3047
+ //#endregion
2669
3048
  //#region src/offload-markers.ts
2670
3049
  const LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
2671
3050
  const SPILL_MARKER_PATTERN = /\[Output saved to (art_[A-Za-z0-9_-]+) \(([\d,]+) chars?\); read_offloaded_output to view\]/;
@@ -4639,6 +5018,28 @@ var CollectionsEndpoint = class {
4639
5018
  async typegen() {
4640
5019
  return this.client.get("/collections/types.d.ts");
4641
5020
  }
5021
+ /**
5022
+ * Idempotently converge a repo-declared collection (display fields, schema,
5023
+ * validationMode) by slug. Hash-first; `dryRun` / `expectNoChanges` plan
5024
+ * without writing. Never deletes a collection or touches records.
5025
+ *
5026
+ * @example
5027
+ * ```typescript
5028
+ * await client.collections.ensure({
5029
+ * slug: 'customers',
5030
+ * displayName: 'Customers',
5031
+ * schema: { type: 'object', properties: { status: { type: 'string' } } },
5032
+ * validationMode: 'warn',
5033
+ * })
5034
+ * ```
5035
+ */
5036
+ async ensure(definition, options) {
5037
+ return ensureCollection(this.client, definition, options);
5038
+ }
5039
+ /** Pull the canonical definition and provenance for a collection — the absorb-drift direction. */
5040
+ async pull(slug) {
5041
+ return pullCollection(this.client, slug);
5042
+ }
4642
5043
  };
4643
5044
  /**
4644
5045
  * Agent API key REQUESTS — a device-authorization-style grant.
@@ -8171,6 +8572,21 @@ var SchedulesEndpoint = class {
8171
8572
  return this.client.post("/schedules", data);
8172
8573
  }
8173
8574
  /**
8575
+ * Idempotently converge a `defineSchedule` definition on a standalone flow or
8576
+ * agent. Identity is name + target; the steady state is one hash probe. Never
8577
+ * deletes, and an omitted `enabled` leaves a paused schedule paused.
8578
+ */
8579
+ async ensure(definition, options = {}) {
8580
+ return ensureSchedule(this.client, definition, options);
8581
+ }
8582
+ /**
8583
+ * Pull the canonical definition + provenance for a schedule by name and
8584
+ * target — the absorb-drift direction of `ensure`.
8585
+ */
8586
+ async pull(name, target) {
8587
+ return pullSchedule(this.client, name, target);
8588
+ }
8589
+ /**
8174
8590
  * Update a schedule (partial update supported).
8175
8591
  */
8176
8592
  async update(id, data) {
@@ -8666,61 +9082,6 @@ async function followDetachedResume(client, ack, after, carried = {}) {
8666
9082
  };
8667
9083
  }
8668
9084
  //#endregion
8669
- //#region src/content-hash.ts
8670
- /**
8671
- * Canonical content-hash primitives shared by the `*.ensure()` config-as-code
8672
- * surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
8673
- * those modules computes a SHA-256 content hash for its hash-first `/ensure`
8674
- * wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
8675
- * output for any given definition is a WIRE CONTRACT with the server, so this
8676
- * module must stay dependency-free (the SDK is published to npm and must not
8677
- * import `@runtypelabs/shared`, a private package) and must not change its
8678
- * normalization behavior without a matching server-side change.
8679
- *
8680
- * `isPlainObject` and `normalizeValue` here are mirrored from
8681
- * `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
8682
- * keys and drops `null`/`undefined` entries recursively — this is the
8683
- * canonical normalization used by products, tools, skills, surfaces, and the
8684
- * whole-FPO hash.
8685
- *
8686
- * NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
8687
- * (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
8688
- * (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
8689
- * in the flow case, use a shallower array-recursion rule. Those are real
8690
- * semantic divergences from this canonical normalizer, not incidental
8691
- * duplication — changing either to reuse `normalizeValue` would alter the
8692
- * content hash for definitions containing explicit `null`s and break the
8693
- * wire contract with already-converged flows/evals. They stay local by
8694
- * design; see the divergence comment at each site.
8695
- */
8696
- function isPlainObject$1(value) {
8697
- return value !== null && typeof value === "object" && !Array.isArray(value);
8698
- }
8699
- /**
8700
- * Recursively normalizes a value for canonical hashing: object keys are
8701
- * sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
8702
- * and have each item normalized in turn.
8703
- */
8704
- function normalizeValue$1(value) {
8705
- if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
8706
- if (isPlainObject$1(value)) {
8707
- const normalized = {};
8708
- for (const key of Object.keys(value).sort()) {
8709
- const entry = value[key];
8710
- if (entry === void 0 || entry === null) continue;
8711
- normalized[key] = normalizeValue$1(entry);
8712
- }
8713
- return normalized;
8714
- }
8715
- return value;
8716
- }
8717
- /** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
8718
- async function sha256Hex(serialized) {
8719
- const encoded = new TextEncoder().encode(serialized);
8720
- const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
8721
- return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
8722
- }
8723
- //#endregion
8724
9085
  //#region src/evals-ensure.ts
8725
9086
  const CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
8726
9087
  "contains",
@@ -9114,6 +9475,25 @@ async function computeEvalContentHash(definition) {
9114
9475
  };
9115
9476
  return sha256Hex(JSON.stringify(canonical));
9116
9477
  }
9478
+ /** A 409 from the eval ensure protocol (external_modification or remote_changed). */
9479
+ var EvalEnsureConflictError = class extends Error {
9480
+ constructor(body) {
9481
+ super(body.error ?? `Eval ensure conflict: ${body.code}`);
9482
+ this.name = "EvalEnsureConflictError";
9483
+ this.code = body.code;
9484
+ this.lastModifiedSource = body.lastModifiedSource;
9485
+ this.modifiedAt = body.modifiedAt;
9486
+ this.currentHash = body.currentHash;
9487
+ }
9488
+ };
9489
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
9490
+ var EvalDriftError = class extends Error {
9491
+ constructor(plan) {
9492
+ super(`Eval suite "${plan.suiteId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.evals.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
9493
+ this.name = "EvalDriftError";
9494
+ this.plan = plan;
9495
+ }
9496
+ };
9117
9497
  /**
9118
9498
  * Per-client memo of `{ name + localHash → server contentHash }` so a hot
9119
9499
  * process pays the full-definition round trip once per boot (steady state
@@ -9121,44 +9501,75 @@ async function computeEvalContentHash(definition) {
9121
9501
  * hash, so a definition that changes within the process can never false-match a
9122
9502
  * stale server hash. Mirrors the flow ensure memo.
9123
9503
  */
9124
- const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
9125
- function memoFor$6(client) {
9126
- let memo = serverHashMemo$6.get(client);
9504
+ const serverHashMemo$7 = /* @__PURE__ */ new WeakMap();
9505
+ function memoFor$7(client) {
9506
+ let memo = serverHashMemo$7.get(client);
9127
9507
  if (!memo) {
9128
9508
  memo = /* @__PURE__ */ new Map();
9129
- serverHashMemo$6.set(client, memo);
9509
+ serverHashMemo$7.set(client, memo);
9130
9510
  }
9131
9511
  return memo;
9132
9512
  }
9133
- /**
9134
- * Idempotently converge an eval suite definition onto the platform. Hash-first:
9135
- * probes with a content hash, and only ships the full definition when the
9136
- * server reports a miss (`definitionRequired`). Upserts the suite + replaces
9137
- * its cases; never executes the eval (use the `runtype eval` CLI / `/eval`
9138
- * submit surface to run it).
9139
- *
9140
- * `virtual: true` definitions are ephemeral and have nothing durable to
9141
- * converge — ensure rejects them. Run a virtual eval directly instead.
9142
- */
9143
- async function ensureEval(client, definition) {
9513
+ function toEvalConflictError(err) {
9514
+ if (!(err instanceof Error)) return null;
9515
+ const match = err.message.match(/^API request failed: 409 .*? - ([\s\S]*)$/);
9516
+ if (!match) return null;
9517
+ let body;
9518
+ try {
9519
+ body = JSON.parse(match[1]);
9520
+ } catch {
9521
+ return null;
9522
+ }
9523
+ if (!isPlainObject$1(body)) return null;
9524
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
9525
+ return new EvalEnsureConflictError(body);
9526
+ }
9527
+ async function postEvalEnsure(client, body) {
9528
+ try {
9529
+ return await client.post("/eval/ensure", body);
9530
+ } catch (err) {
9531
+ throw toEvalConflictError(err) ?? err;
9532
+ }
9533
+ }
9534
+ async function ensureEval(client, definition, options = {}) {
9144
9535
  if (definition.virtual) throw new Error("Cannot ensure a virtual eval: virtual evals are ephemeral (nothing is persisted to converge). Remove `virtual: true` to converge a durable suite, or run it directly.");
9145
- const memo = memoFor$6(client);
9536
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
9537
+ const passthrough = {
9538
+ ...onConflict ? { onConflict } : {},
9539
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
9540
+ };
9541
+ if (dryRun || expectNoChanges) {
9542
+ const plan = await postEvalEnsure(client, {
9543
+ name: definition.name,
9544
+ definition,
9545
+ dryRun: true,
9546
+ ...passthrough
9547
+ });
9548
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
9549
+ if (expectNoChanges && plan.changes !== "none") throw new EvalDriftError(plan);
9550
+ return plan;
9551
+ }
9552
+ const memo = memoFor$7(client);
9146
9553
  const localHash = await computeEvalContentHash(definition);
9147
9554
  const memoKey = `${definition.name} ${localHash}`;
9148
9555
  const contentHash = memo.get(memoKey) ?? localHash;
9149
- const probe = await client.post("/eval/ensure", {
9556
+ const probe = await postEvalEnsure(client, {
9150
9557
  name: definition.name,
9151
- contentHash
9558
+ contentHash,
9559
+ ...passthrough
9152
9560
  });
9561
+ if (probe.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
9153
9562
  if (probe.result !== "definitionRequired") {
9154
9563
  memo.set(memoKey, probe.contentHash);
9155
9564
  return probe;
9156
9565
  }
9157
- const converged = await client.post("/eval/ensure", {
9566
+ const converged = await postEvalEnsure(client, {
9158
9567
  name: definition.name,
9159
- definition
9568
+ definition,
9569
+ ...passthrough
9160
9570
  });
9161
9571
  if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
9572
+ if (converged.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
9162
9573
  memo.set(memoKey, converged.contentHash);
9163
9574
  return converged;
9164
9575
  }
@@ -9213,9 +9624,11 @@ function normalizeStepForHash(step) {
9213
9624
  }
9214
9625
  /** SHA-256 (hex) over the canonical normalized step list (steps only). */
9215
9626
  async function computeFlowContentHash(steps) {
9216
- const normalized = [...steps].sort((a, b) => {
9217
- return (isPlainObject$1(a) && typeof a.order === "number" ? a.order : 0) - (isPlainObject$1(b) && typeof b.order === "number" ? b.order : 0);
9218
- }).map(normalizeStepForHash);
9627
+ const normalized = steps.map((step) => isPlainObject$1(step) ? step : {}).map((step, index) => ({
9628
+ ...step,
9629
+ name: typeof step.name === "string" ? step.name : `Step ${index + 1}`,
9630
+ order: typeof step.order === "number" ? step.order : index + 1
9631
+ })).sort((a, b) => a.order - b.order).map(normalizeStepForHash);
9219
9632
  return sha256Hex(JSON.stringify(normalized));
9220
9633
  }
9221
9634
  const DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
@@ -9234,8 +9647,8 @@ const DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
9234
9647
  /**
9235
9648
  * Collect account-scoped `tool_…` references from a step config (inlined copy
9236
9649
  * of `collectNonPortableAgentToolRefs` in @runtypelabs/shared, applied to the
9237
- * step's `config.tools` block and recursively to conditional branches). The
9238
- * v1 ensure surface accepts portable tool references only.
9650
+ * step's `config.tools` block, `toolId` and `agentId`). The v1 ensure surface
9651
+ * accepts portable tool references only.
9239
9652
  */
9240
9653
  function collectStepNonPortableToolRefs(config, path) {
9241
9654
  const found = [];
@@ -9269,12 +9682,73 @@ function collectStepNonPortableToolRefs(config, path) {
9269
9682
  }
9270
9683
  if (isAccountScoped(config.toolId)) found.push(`${path}.toolId`);
9271
9684
  if (isRawId(config.agentId, "agent_")) found.push(`${path}.agentId`);
9272
- for (const branch of ["trueSteps", "falseSteps"]) {
9273
- const nested = config[branch];
9274
- if (!Array.isArray(nested)) continue;
9275
- nested.forEach((nestedStep, i) => {
9276
- if (isPlainObject$1(nestedStep) && isPlainObject$1(nestedStep.config)) found.push(...collectStepNonPortableToolRefs(nestedStep.config, `${path}.${branch}[${i}].config`));
9685
+ return found;
9686
+ }
9687
+ const CONDITIONAL_BRANCH_STEP_KEYS = [
9688
+ "trueSteps",
9689
+ "falseSteps",
9690
+ "true_steps",
9691
+ "false_steps",
9692
+ "otherwiseSteps"
9693
+ ];
9694
+ /** Guards a hostile payload whose nesting is otherwise unbounded. */
9695
+ const MAX_REF_SITE_WALK_DEPTH = 100;
9696
+ /**
9697
+ * Collect account-scoped references at every flow step ref site (inlined copy of
9698
+ * `collectNonPortableFlowStepRefs` in @runtypelabs/shared): loop bodies, conditional
9699
+ * branches, inline flow-as-tool payloads and `errorHandling.fallbacks`.
9700
+ */
9701
+ function collectFlowStepsNonPortableRefs(steps, basePath, depth = 0) {
9702
+ if (!Array.isArray(steps) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
9703
+ const found = [];
9704
+ steps.forEach((step, index) => {
9705
+ if (!isPlainObject$1(step) || !isPlainObject$1(step.config)) return;
9706
+ found.push(...collectStepConfigSiteRefs(step.type, step.config, `${basePath}[${index}].config`, depth));
9707
+ });
9708
+ return found;
9709
+ }
9710
+ function collectStepConfigSiteRefs(type, config, path, depth) {
9711
+ const found = collectStepNonPortableToolRefs(config, path);
9712
+ found.push(...collectToolBagFlowRefs(config.tools, `${path}.tools`, depth + 1));
9713
+ const errorHandling = config.errorHandling;
9714
+ if (isPlainObject$1(errorHandling) && Array.isArray(errorHandling.fallbacks)) errorHandling.fallbacks.forEach((fallback, i) => {
9715
+ if (!isPlainObject$1(fallback)) return;
9716
+ const fallbackPath = `${path}.errorHandling.fallbacks[${i}]`;
9717
+ if (fallback.type === "flow") {
9718
+ if (typeof fallback.flowId === "string" && fallback.flowId.startsWith("flow_")) found.push(`${fallbackPath}.flowId`);
9719
+ } else if (fallback.type === "step" && isPlainObject$1(fallback.stepConfig)) {
9720
+ found.push(...collectStepNonPortableToolRefs(fallback.stepConfig, `${fallbackPath}.stepConfig`));
9721
+ found.push(...collectToolBagFlowRefs(fallback.stepConfig.tools, `${fallbackPath}.stepConfig.tools`, depth + 1));
9722
+ }
9723
+ });
9724
+ if (type === "loop") found.push(...collectFlowStepsNonPortableRefs(config.steps, `${path}.steps`, depth + 1));
9725
+ else if (type === "conditional") {
9726
+ if (Array.isArray(config.branches)) config.branches.forEach((branch, i) => {
9727
+ if (!isPlainObject$1(branch)) return;
9728
+ found.push(...collectFlowStepsNonPortableRefs(branch.steps, `${path}.branches[${i}].steps`, depth + 1));
9277
9729
  });
9730
+ for (const key of CONDITIONAL_BRANCH_STEP_KEYS) found.push(...collectFlowStepsNonPortableRefs(config[key], `${path}.${key}`, depth + 1));
9731
+ }
9732
+ return found;
9733
+ }
9734
+ /** Descend the inline flow-as-tool and skill payloads a step's tool bag can carry. */
9735
+ function collectToolBagFlowRefs(tools, path, depth) {
9736
+ if (!isPlainObject$1(tools) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
9737
+ const found = [];
9738
+ const walkBag = (bag, bagPath, bagDepth) => {
9739
+ if (bagDepth > MAX_REF_SITE_WALK_DEPTH) return;
9740
+ bag.forEach((tool, i) => {
9741
+ if (!isPlainObject$1(tool) || !isPlainObject$1(tool.config)) return;
9742
+ const toolPath = `${bagPath}[${i}].config`;
9743
+ const skill = tool.config.skill;
9744
+ if (isPlainObject$1(skill) && Array.isArray(skill.resolvedTools)) walkBag(skill.resolvedTools, `${toolPath}.skill.resolvedTools`, bagDepth + 1);
9745
+ const flow = tool.config.flow;
9746
+ if (isPlainObject$1(flow) && Array.isArray(flow.steps)) found.push(...collectFlowStepsNonPortableRefs(flow.steps, `${toolPath}.flow.steps`, bagDepth + 1));
9747
+ });
9748
+ };
9749
+ for (const key of ["runtimeTools", "inlineTools"]) {
9750
+ const bag = tools[key];
9751
+ if (Array.isArray(bag)) walkBag(bag, `${path}.${key}`, depth);
9278
9752
  }
9279
9753
  return found;
9280
9754
  }
@@ -9315,7 +9789,7 @@ function defineFlow(input) {
9315
9789
  if (unknownStepKeys.length > 0) throw new Error(`defineFlow: steps[${index}] has unknown field(s): ${unknownStepKeys.join(", ")}. Allowed step fields are type, name, order, enabled, when, config. (Step ids are server artifacts and not part of a portable definition.)`);
9316
9790
  const config = isPlainObject$1(step.config) ? step.config : void 0;
9317
9791
  if (config) {
9318
- const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
9792
+ const nonPortable = collectStepConfigSiteRefs(step.type, config, `steps[${index}].config`, 0);
9319
9793
  if (nonPortable.length > 0) throw new Error(`defineFlow: account-scoped reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable — tool_…/agent_…/flow_… IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved resource by name — tool:<name>, agent:<name>, or flow:<name> instead.`);
9320
9794
  }
9321
9795
  return {
@@ -9369,7 +9843,7 @@ var FlowEnsureConflictError = class extends Error {
9369
9843
  /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
9370
9844
  var FlowDriftError = class extends Error {
9371
9845
  constructor(plan) {
9372
- super(`Flow "${plan.flowId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.flows.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
9846
+ super(`Flow "${plan.flowId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[...plan.changedKeys, ...plan.publishPending ? ["publish (pending)"] : []].join(", ") || "n/a"}). Run client.flows.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
9373
9847
  this.name = "FlowDriftError";
9374
9848
  this.plan = plan;
9375
9849
  }
@@ -9378,7 +9852,7 @@ var FlowDriftError = class extends Error {
9378
9852
  * Best-effort extraction of `{ status, body }` from the generic request
9379
9853
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
9380
9854
  */
9381
- function parseRequestError$5(err) {
9855
+ function parseRequestError$6(err) {
9382
9856
  if (!(err instanceof Error)) return {
9383
9857
  status: null,
9384
9858
  body: null
@@ -9400,8 +9874,8 @@ function parseRequestError$5(err) {
9400
9874
  };
9401
9875
  }
9402
9876
  }
9403
- function toConflictError$5(err) {
9404
- const { status, body } = parseRequestError$5(err);
9877
+ function toConflictError$6(err) {
9878
+ const { status, body } = parseRequestError$6(err);
9405
9879
  if (status !== 409 || !isPlainObject$1(body)) return null;
9406
9880
  const code = body.code;
9407
9881
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -9417,23 +9891,23 @@ function toConflictError$5(err) {
9417
9891
  * so a stale server hash from the previous definition version can never be
9418
9892
  * probed (which would false-match and silently skip the update).
9419
9893
  */
9420
- const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
9421
- function memoFor$5(client) {
9422
- let memo = serverHashMemo$5.get(client);
9894
+ const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
9895
+ function memoFor$6(client) {
9896
+ let memo = serverHashMemo$6.get(client);
9423
9897
  if (!memo) {
9424
9898
  memo = /* @__PURE__ */ new Map();
9425
- serverHashMemo$5.set(client, memo);
9899
+ serverHashMemo$6.set(client, memo);
9426
9900
  }
9427
9901
  return memo;
9428
9902
  }
9429
9903
  function memoize$4(memo, memoKey, result) {
9430
9904
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
9431
9905
  }
9432
- async function request$4(client, body) {
9906
+ async function request$5(client, body) {
9433
9907
  try {
9434
9908
  return await client.post("/flows/ensure", body);
9435
9909
  } catch (err) {
9436
- const conflict = toConflictError$5(err);
9910
+ const conflict = toConflictError$6(err);
9437
9911
  if (conflict) throw conflict;
9438
9912
  throw err;
9439
9913
  }
@@ -9458,37 +9932,37 @@ async function ensureFlow(client, definition, options = {}) {
9458
9932
  steps: definition.steps
9459
9933
  };
9460
9934
  if (dryRun || expectNoChanges) {
9461
- const plan = await request$4(client, {
9935
+ const plan = await request$5(client, {
9462
9936
  name: definition.name,
9463
9937
  definition: wireDefinition,
9464
9938
  dryRun: true,
9465
9939
  ...passthrough
9466
9940
  });
9467
9941
  if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
9468
- if (expectNoChanges && plan.changes !== "none") throw new FlowDriftError(plan);
9942
+ if (expectNoChanges && (plan.changes !== "none" || release === "publish" && plan.publishPending === true)) throw new FlowDriftError(plan);
9469
9943
  return plan;
9470
9944
  }
9471
- const memo = memoFor$5(client);
9945
+ const memo = memoFor$6(client);
9472
9946
  const localHash = await computeFlowContentHash(definition.steps);
9473
9947
  const memoKey = `${definition.name} ${localHash}`;
9474
9948
  const contentHash = memo.get(memoKey) ?? localHash;
9475
- const probe = await request$4(client, {
9949
+ const probe = await request$5(client, {
9476
9950
  name: definition.name,
9477
9951
  contentHash,
9478
9952
  ...passthrough
9479
9953
  });
9480
9954
  if (probe.result !== "definitionRequired") {
9481
9955
  memoize$4(memo, memoKey, probe);
9482
- return convergeInlineEvals(client, definition, probe);
9956
+ return convergeInlineEvals(client, definition, probe, onConflict);
9483
9957
  }
9484
- const converged = await request$4(client, {
9958
+ const converged = await request$5(client, {
9485
9959
  name: definition.name,
9486
9960
  definition: wireDefinition,
9487
9961
  ...passthrough
9488
9962
  });
9489
9963
  if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
9490
9964
  memoize$4(memo, memoKey, converged);
9491
- return convergeInlineEvals(client, definition, converged);
9965
+ return convergeInlineEvals(client, definition, converged, onConflict);
9492
9966
  }
9493
9967
  /**
9494
9968
  * After the flow itself converges on the real (non-plan) path, converge each
@@ -9501,10 +9975,10 @@ async function ensureFlow(client, definition, options = {}) {
9501
9975
  * unchanged (no empty `evals` array). The server's `/flows/ensure` surface is
9502
9976
  * never touched by eval semantics — they ride the eval endpoints only.
9503
9977
  */
9504
- async function convergeInlineEvals(client, definition, result) {
9978
+ async function convergeInlineEvals(client, definition, result, onConflict) {
9505
9979
  if (result.result === "plan" || !definition.evals?.length) return result;
9506
9980
  const evals = [];
9507
- for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef));
9981
+ for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef, onConflict ? { onConflict } : {}));
9508
9982
  return {
9509
9983
  ...result,
9510
9984
  evals
@@ -10740,24 +11214,8 @@ var EvalsNamespace = class {
10740
11214
  run(config) {
10741
11215
  return new EvalRunner(this.getClient, config);
10742
11216
  }
10743
- /**
10744
- * Idempotently converge a `defineEval` suite definition onto the platform —
10745
- * the deploy-time, non-executing converge for code-colocated evals. Hash-first:
10746
- * the steady state is one tiny probe request. Upserts the eval suite + replaces
10747
- * its cases; never runs the eval.
10748
- *
10749
- * @example
10750
- * ```typescript
10751
- * const suite = defineEval({
10752
- * target: { flow: 'support-triage' },
10753
- * graders: [noError()],
10754
- * cases: [{ name: 'billing', input: { variables: { message: 'I was double charged' } }, expect: [contains('finance')] }],
10755
- * })
10756
- * const result = await Runtype.evals.ensure(suite)
10757
- * ```
10758
- */
10759
- async ensure(definition) {
10760
- return ensureEval(this.getClient(), definition);
11217
+ async ensure(definition, options = {}) {
11218
+ return ensureEval(this.getClient(), definition, options);
10761
11219
  }
10762
11220
  /**
10763
11221
  * Pull the canonical definition + provenance for an eval suite by name — the
@@ -11024,7 +11482,7 @@ var SkillDriftError = class extends Error {
11024
11482
  * Best-effort extraction of `{ status, body }` from the generic request error
11025
11483
  * thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11026
11484
  */
11027
- function parseRequestError$4(err) {
11485
+ function parseRequestError$5(err) {
11028
11486
  if (!(err instanceof Error)) return {
11029
11487
  status: null,
11030
11488
  body: null
@@ -11046,8 +11504,8 @@ function parseRequestError$4(err) {
11046
11504
  };
11047
11505
  }
11048
11506
  }
11049
- function toConflictError$4(err) {
11050
- const { status, body } = parseRequestError$4(err);
11507
+ function toConflictError$5(err) {
11508
+ const { status, body } = parseRequestError$5(err);
11051
11509
  if (status !== 409 || !isPlainObject$1(body)) return null;
11052
11510
  const code = body.code;
11053
11511
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11059,23 +11517,23 @@ function toConflictError$4(err) {
11059
11517
  * within the process lifetime invalidates a stale server hash (which would
11060
11518
  * false-match and silently skip the update). Same scheme as flows/tools.ensure.
11061
11519
  */
11062
- const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
11063
- function memoFor$4(client) {
11064
- let memo = serverHashMemo$4.get(client);
11520
+ const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
11521
+ function memoFor$5(client) {
11522
+ let memo = serverHashMemo$5.get(client);
11065
11523
  if (!memo) {
11066
11524
  memo = /* @__PURE__ */ new Map();
11067
- serverHashMemo$4.set(client, memo);
11525
+ serverHashMemo$5.set(client, memo);
11068
11526
  }
11069
11527
  return memo;
11070
11528
  }
11071
11529
  function memoize$3(memo, memoKey, result) {
11072
11530
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
11073
11531
  }
11074
- async function request$3(client, body) {
11532
+ async function request$4(client, body) {
11075
11533
  try {
11076
11534
  return await client.post("/skills/ensure", body);
11077
11535
  } catch (err) {
11078
- const conflict = toConflictError$4(err);
11536
+ const conflict = toConflictError$5(err);
11079
11537
  if (conflict) throw conflict;
11080
11538
  throw err;
11081
11539
  }
@@ -11087,14 +11545,15 @@ async function request$3(client, body) {
11087
11545
  * on every change; never deletes.
11088
11546
  */
11089
11547
  async function ensureSkill(client, definition, options = {}) {
11090
- const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
11548
+ const { dryRun, onConflict, release, expectedRemoteHash, version, expectNoChanges } = options;
11091
11549
  const passthrough = {
11092
11550
  ...onConflict ? { onConflict } : {},
11093
11551
  ...release ? { release } : {},
11094
- ...expectedRemoteHash ? { expectedRemoteHash } : {}
11552
+ ...expectedRemoteHash ? { expectedRemoteHash } : {},
11553
+ ...version ? { version } : {}
11095
11554
  };
11096
11555
  if (dryRun || expectNoChanges) {
11097
- const plan = await request$3(client, {
11556
+ const plan = await request$4(client, {
11098
11557
  name: definition.name,
11099
11558
  definition: manifestToWire(definition),
11100
11559
  dryRun: true,
@@ -11104,11 +11563,11 @@ async function ensureSkill(client, definition, options = {}) {
11104
11563
  if (expectNoChanges && plan.changes !== "none") throw new SkillDriftError(plan);
11105
11564
  return plan;
11106
11565
  }
11107
- const memo = memoFor$4(client);
11566
+ const memo = memoFor$5(client);
11108
11567
  const localHash = await computeSkillContentHash(definition);
11109
11568
  const memoKey = `${definition.name} ${localHash}`;
11110
11569
  const contentHash = memo.get(memoKey) ?? localHash;
11111
- const probe = await request$3(client, {
11570
+ const probe = await request$4(client, {
11112
11571
  name: definition.name,
11113
11572
  contentHash,
11114
11573
  ...passthrough
@@ -11117,7 +11576,7 @@ async function ensureSkill(client, definition, options = {}) {
11117
11576
  memoize$3(memo, memoKey, probe);
11118
11577
  return probe;
11119
11578
  }
11120
- const converged = await request$3(client, {
11579
+ const converged = await request$4(client, {
11121
11580
  name: definition.name,
11122
11581
  definition: manifestToWire(definition),
11123
11582
  ...passthrough
@@ -11533,6 +11992,18 @@ function defineAgent(input) {
11533
11992
  config
11534
11993
  };
11535
11994
  }
11995
+ function planHasSkillBindingChanges(plan) {
11996
+ return (plan.skillBindings ?? []).some((binding) => binding.action !== "unchanged");
11997
+ }
11998
+ /** Whether a dry run that asked for a deploy would move the alias or change its binding names. */
11999
+ function planHasDeploymentChanges(plan) {
12000
+ return plan.deployment?.changed === true || plan.deployment?.bindingsChanged === true;
12001
+ }
12002
+ function deploymentChangeLabels(plan) {
12003
+ const deployment = plan.deployment;
12004
+ if (!deployment) return [];
12005
+ return [...deployment.changed ? [`deploy:${deployment.alias} (pointer)`] : [], ...deployment.bindingsChanged ? [`deploy:${deployment.alias} (bindings)`] : []];
12006
+ }
11536
12007
  /** A 409 from the ensure protocol (external_modification or remote_changed). */
11537
12008
  var AgentEnsureConflictError = class extends Error {
11538
12009
  constructor(body) {
@@ -11547,7 +12018,12 @@ var AgentEnsureConflictError = class extends Error {
11547
12018
  /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
11548
12019
  var AgentDriftError = class extends Error {
11549
12020
  constructor(plan) {
11550
- super(`Agent "${plan.agentId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.agents.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
12021
+ const skillChanges = (plan.skillBindings ?? []).filter((binding) => binding.action !== "unchanged").map((binding) => `skill:${binding.name} (${binding.action})`);
12022
+ super(`Agent "${plan.agentId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[
12023
+ ...plan.changedKeys,
12024
+ ...skillChanges,
12025
+ ...deploymentChangeLabels(plan)
12026
+ ].join(", ") || "n/a"}). Run client.agents.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
11551
12027
  this.name = "AgentDriftError";
11552
12028
  this.plan = plan;
11553
12029
  }
@@ -11556,7 +12032,7 @@ var AgentDriftError = class extends Error {
11556
12032
  * Best-effort extraction of `{ status, body }` from the generic request
11557
12033
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11558
12034
  */
11559
- function parseRequestError$3(err) {
12035
+ function parseRequestError$4(err) {
11560
12036
  if (!(err instanceof Error)) return {
11561
12037
  status: null,
11562
12038
  body: null
@@ -11578,8 +12054,8 @@ function parseRequestError$3(err) {
11578
12054
  };
11579
12055
  }
11580
12056
  }
11581
- function toConflictError$3(err) {
11582
- const { status, body } = parseRequestError$3(err);
12057
+ function toConflictError$4(err) {
12058
+ const { status, body } = parseRequestError$4(err);
11583
12059
  if (status !== 409 || !isPlainObject(body)) return null;
11584
12060
  const code = body.code;
11585
12061
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11595,12 +12071,12 @@ function toConflictError$3(err) {
11595
12071
  * so a stale server hash from the previous definition version can never be
11596
12072
  * probed (which would false-match and silently skip the update).
11597
12073
  */
11598
- const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
11599
- function memoFor$3(client) {
11600
- let memo = serverHashMemo$3.get(client);
12074
+ const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
12075
+ function memoFor$4(client) {
12076
+ let memo = serverHashMemo$4.get(client);
11601
12077
  if (!memo) {
11602
12078
  memo = /* @__PURE__ */ new Map();
11603
- serverHashMemo$3.set(client, memo);
12079
+ serverHashMemo$4.set(client, memo);
11604
12080
  }
11605
12081
  return memo;
11606
12082
  }
@@ -11636,14 +12112,16 @@ var AgentsNamespace = class {
11636
12112
  */
11637
12113
  async ensure(definition, options = {}) {
11638
12114
  const client = this.getClient();
11639
- const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges } = options;
12115
+ const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges, skills, capabilities } = options;
11640
12116
  if (release !== void 0 && deploy !== void 0) throw new Error(ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE);
11641
12117
  const passthrough = {
11642
12118
  ...onConflict ? { onConflict } : {},
11643
12119
  ...release ? { release } : {},
11644
12120
  ...deploy ? { deploy } : {},
11645
12121
  ...expectedRemoteHash ? { expectedRemoteHash } : {},
11646
- ...version ? { version } : {}
12122
+ ...version ? { version } : {},
12123
+ ...skills ? { skills } : {},
12124
+ ...capabilities ? { capabilities } : {}
11647
12125
  };
11648
12126
  if (dryRun || expectNoChanges) {
11649
12127
  const plan = await this.request(client, {
@@ -11653,15 +12131,16 @@ var AgentsNamespace = class {
11653
12131
  ...passthrough
11654
12132
  });
11655
12133
  if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
11656
- if (expectNoChanges && plan.changes !== "none") throw new AgentDriftError(plan);
12134
+ const deployRequested = release === "publish" || deploy !== void 0;
12135
+ if (expectNoChanges && (plan.changes !== "none" || planHasSkillBindingChanges(plan) || deployRequested && planHasDeploymentChanges(plan))) throw new AgentDriftError(plan);
11657
12136
  return plan;
11658
12137
  }
11659
- const memo = memoFor$3(client);
12138
+ const memo = memoFor$4(client);
11660
12139
  const localHash = await computeAgentContentHash({
11661
12140
  ...definition,
11662
12141
  config: definition.config
11663
12142
  });
11664
- const memoKey = `${definition.name}\u0000${localHash}`;
12143
+ const memoKey = `${definition.name}\u0000${localHash}${capabilities ? `\u0000${JSON.stringify(capabilities)}` : ""}`;
11665
12144
  const contentHash = memo.get(memoKey) ?? localHash;
11666
12145
  const probe = await this.request(client, {
11667
12146
  name: definition.name,
@@ -11695,7 +12174,7 @@ var AgentsNamespace = class {
11695
12174
  try {
11696
12175
  return await client.post("/agents/ensure", body);
11697
12176
  } catch (err) {
11698
- const conflict = toConflictError$3(err);
12177
+ const conflict = toConflictError$4(err);
11699
12178
  if (conflict) throw conflict;
11700
12179
  throw err;
11701
12180
  }
@@ -11824,7 +12303,7 @@ var ToolDriftError = class extends Error {
11824
12303
  * Best-effort extraction of `{ status, body }` from the generic request
11825
12304
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11826
12305
  */
11827
- function parseRequestError$2(err) {
12306
+ function parseRequestError$3(err) {
11828
12307
  if (!(err instanceof Error)) return {
11829
12308
  status: null,
11830
12309
  body: null
@@ -11846,8 +12325,8 @@ function parseRequestError$2(err) {
11846
12325
  };
11847
12326
  }
11848
12327
  }
11849
- function toConflictError$2(err) {
11850
- const { status, body } = parseRequestError$2(err);
12328
+ function toConflictError$3(err) {
12329
+ const { status, body } = parseRequestError$3(err);
11851
12330
  if (status !== 409 || !isPlainObject$1(body)) return null;
11852
12331
  const code = body.code;
11853
12332
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11859,23 +12338,23 @@ function toConflictError$2(err) {
11859
12338
  * within the process lifetime invalidates a stale server hash (which would
11860
12339
  * false-match and silently skip the update). Same scheme as flows.ensure.
11861
12340
  */
11862
- const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
11863
- function memoFor$2(client) {
11864
- let memo = serverHashMemo$2.get(client);
12341
+ const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
12342
+ function memoFor$3(client) {
12343
+ let memo = serverHashMemo$3.get(client);
11865
12344
  if (!memo) {
11866
12345
  memo = /* @__PURE__ */ new Map();
11867
- serverHashMemo$2.set(client, memo);
12346
+ serverHashMemo$3.set(client, memo);
11868
12347
  }
11869
12348
  return memo;
11870
12349
  }
11871
12350
  function memoize$2(memo, memoKey, result) {
11872
12351
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
11873
12352
  }
11874
- async function request$2(client, body) {
12353
+ async function request$3(client, body) {
11875
12354
  try {
11876
12355
  return await client.post("/tools/ensure", body);
11877
12356
  } catch (err) {
11878
- const conflict = toConflictError$2(err);
12357
+ const conflict = toConflictError$3(err);
11879
12358
  if (conflict) throw conflict;
11880
12359
  throw err;
11881
12360
  }
@@ -11892,7 +12371,7 @@ async function ensureTool(client, definition, options = {}) {
11892
12371
  ...expectedRemoteHash ? { expectedRemoteHash } : {}
11893
12372
  };
11894
12373
  if (dryRun || expectNoChanges) {
11895
- const plan = await request$2(client, {
12374
+ const plan = await request$3(client, {
11896
12375
  name: definition.name,
11897
12376
  definition,
11898
12377
  dryRun: true,
@@ -11902,11 +12381,11 @@ async function ensureTool(client, definition, options = {}) {
11902
12381
  if (expectNoChanges && plan.changes !== "none") throw new ToolDriftError(plan);
11903
12382
  return plan;
11904
12383
  }
11905
- const memo = memoFor$2(client);
12384
+ const memo = memoFor$3(client);
11906
12385
  const localHash = await computeToolContentHash(definition);
11907
12386
  const memoKey = `${definition.name} ${localHash}`;
11908
12387
  const contentHash = memo.get(memoKey) ?? localHash;
11909
- const probe = await request$2(client, {
12388
+ const probe = await request$3(client, {
11910
12389
  name: definition.name,
11911
12390
  contentHash,
11912
12391
  ...passthrough
@@ -11915,7 +12394,7 @@ async function ensureTool(client, definition, options = {}) {
11915
12394
  memoize$2(memo, memoKey, probe);
11916
12395
  return probe;
11917
12396
  }
11918
- const converged = await request$2(client, {
12397
+ const converged = await request$3(client, {
11919
12398
  name: definition.name,
11920
12399
  definition,
11921
12400
  ...passthrough
@@ -11971,10 +12450,197 @@ var ToolsNamespace = class {
11971
12450
  }
11972
12451
  };
11973
12452
  //#endregion
12453
+ //#region src/question-sets-ensure.ts
12454
+ /** Canonical form of a question set definition, without its slug. */
12455
+ function normalizeQuestionSetDefinition(definition) {
12456
+ return normalizeValue$1({
12457
+ name: definition.name,
12458
+ description: definition.description,
12459
+ questions: isPlainObject$1(definition.questions) ? definition.questions : {},
12460
+ inputMapping: definition.inputMapping,
12461
+ model: definition.model
12462
+ });
12463
+ }
12464
+ /** SHA-256 (hex) over the canonical question set definition. */
12465
+ async function computeQuestionSetContentHash(definition) {
12466
+ return sha256Hex(JSON.stringify(normalizeQuestionSetDefinition(definition)));
12467
+ }
12468
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
12469
+ const DEFINE_QUESTION_SET_KEYS = /* @__PURE__ */ new Set([
12470
+ "slug",
12471
+ "name",
12472
+ "description",
12473
+ "questions",
12474
+ "inputMapping",
12475
+ "model"
12476
+ ]);
12477
+ /**
12478
+ * Pure local constructor for a question set definition. Checks the structure
12479
+ * and rejects unknown fields; the server validates the questions at ensure time.
12480
+ *
12481
+ * @example
12482
+ * ```typescript
12483
+ * const triage = defineQuestionSet({
12484
+ * slug: 'ticket-triage',
12485
+ * name: 'Ticket triage',
12486
+ * questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
12487
+ * })
12488
+ * ```
12489
+ */
12490
+ function defineQuestionSet(input) {
12491
+ if (!input || typeof input !== "object") throw new Error("defineQuestionSet requires a definition object");
12492
+ if (typeof input.slug !== "string" || !SLUG_PATTERN.test(input.slug)) throw new Error("defineQuestionSet requires a \"slug\" of lowercase letters, digits, \"-\" and \"_\", up to 64 characters");
12493
+ if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineQuestionSet requires a non-empty string \"name\"");
12494
+ if (!isPlainObject$1(input.questions) || Object.keys(input.questions).length === 0) throw new Error("defineQuestionSet requires a non-empty \"questions\" object");
12495
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_QUESTION_SET_KEYS.has(key));
12496
+ if (unknownKeys.length > 0) throw new Error(`defineQuestionSet: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are slug, name, description, questions, inputMapping and model.`);
12497
+ return { ...input };
12498
+ }
12499
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
12500
+ var QuestionSetEnsureConflictError = class extends Error {
12501
+ constructor(body) {
12502
+ super(body.error ?? `Question set ensure conflict: ${body.code}`);
12503
+ this.name = "QuestionSetEnsureConflictError";
12504
+ this.code = body.code;
12505
+ this.lastModifiedSource = body.lastModifiedSource;
12506
+ this.modifiedAt = body.modifiedAt;
12507
+ this.currentHash = body.currentHash;
12508
+ }
12509
+ };
12510
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when the remote differs. */
12511
+ var QuestionSetDriftError = class extends Error {
12512
+ constructor(slug, plan) {
12513
+ super(`Question set "${slug}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.questionSets.pull(slug) to absorb the remote edit, or re-run ensure to converge.`);
12514
+ this.name = "QuestionSetDriftError";
12515
+ this.plan = plan;
12516
+ }
12517
+ };
12518
+ function toConflictError$2(err) {
12519
+ if (!(err instanceof Error)) return null;
12520
+ const match = err.message.match(/^API request failed: (409) .*? - ([\s\S]*)$/);
12521
+ if (!match) return null;
12522
+ let body;
12523
+ try {
12524
+ body = JSON.parse(match[2]);
12525
+ } catch {
12526
+ return null;
12527
+ }
12528
+ if (!isPlainObject$1(body)) return null;
12529
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
12530
+ return new QuestionSetEnsureConflictError(body);
12531
+ }
12532
+ const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
12533
+ function memoFor$2(client) {
12534
+ let memo = serverHashMemo$2.get(client);
12535
+ if (!memo) {
12536
+ memo = /* @__PURE__ */ new Map();
12537
+ serverHashMemo$2.set(client, memo);
12538
+ }
12539
+ return memo;
12540
+ }
12541
+ async function request$2(client, body) {
12542
+ try {
12543
+ return await client.post("/question-sets/ensure", body);
12544
+ } catch (err) {
12545
+ throw toConflictError$2(err) ?? err;
12546
+ }
12547
+ }
12548
+ function toWireDefinition(definition) {
12549
+ const { slug: _slug, ...rest } = definition;
12550
+ return rest;
12551
+ }
12552
+ /**
12553
+ * Idempotently converge a question set onto the platform. Probes with a
12554
+ * content hash first and sends the full definition only on a miss. Appends a
12555
+ * version when the questions, input mapping or model change; never deletes.
12556
+ */
12557
+ async function ensureQuestionSet(client, definition, options = {}) {
12558
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
12559
+ const passthrough = {
12560
+ ...onConflict ? { onConflict } : {},
12561
+ ...release ? { release } : {},
12562
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
12563
+ };
12564
+ if (dryRun || expectNoChanges) {
12565
+ const plan = await request$2(client, {
12566
+ slug: definition.slug,
12567
+ definition: toWireDefinition(definition),
12568
+ dryRun: true,
12569
+ ...passthrough
12570
+ });
12571
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
12572
+ if (expectNoChanges && plan.changes !== "none") throw new QuestionSetDriftError(definition.slug, plan);
12573
+ return plan;
12574
+ }
12575
+ const memo = memoFor$2(client);
12576
+ const localHash = await computeQuestionSetContentHash(definition);
12577
+ const memoKey = `${definition.slug} ${localHash}`;
12578
+ const probe = await request$2(client, {
12579
+ slug: definition.slug,
12580
+ contentHash: memo.get(memoKey) ?? localHash,
12581
+ ...passthrough
12582
+ });
12583
+ if (probe.result !== "definitionRequired") {
12584
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
12585
+ return probe;
12586
+ }
12587
+ const converged = await request$2(client, {
12588
+ slug: definition.slug,
12589
+ definition: toWireDefinition(definition),
12590
+ ...passthrough
12591
+ });
12592
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
12593
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
12594
+ return converged;
12595
+ }
12596
+ /** Pull the canonical definition and provenance for a question set by slug. */
12597
+ async function pullQuestionSet(client, slug) {
12598
+ return client.get("/question-sets/pull", { slug });
12599
+ }
12600
+ //#endregion
12601
+ //#region src/question-sets-namespace.ts
12602
+ /** Question set config-as-code: `ensure` and `pull`. */
12603
+ var QuestionSetsNamespace = class {
12604
+ constructor(getClient) {
12605
+ this.getClient = getClient;
12606
+ }
12607
+ /**
12608
+ * Converge a `defineQuestionSet` definition. The steady state is one hash
12609
+ * probe. Pass `release: 'publish'` so flows and agents can resolve the
12610
+ * latest version.
12611
+ *
12612
+ * @example
12613
+ * ```typescript
12614
+ * await Runtype.questionSets.ensure(triage, { release: 'publish' })
12615
+ * await Runtype.questionSets.ensure(triage, { expectNoChanges: true })
12616
+ * ```
12617
+ */
12618
+ async ensure(definition, options = {}) {
12619
+ return ensureQuestionSet(this.getClient(), definition, options);
12620
+ }
12621
+ /** Pull the live definition and provenance for a slug, to absorb a dashboard edit. */
12622
+ async pull(slug) {
12623
+ return pullQuestionSet(this.getClient(), slug);
12624
+ }
12625
+ };
12626
+ //#endregion
11974
12627
  //#region src/products-ensure.ts
12628
+ /**
12629
+ * `spec` with its stack as a save stores it: without the server-set fields (`detected`, `declared`,
12630
+ * `hiddenAt`), which their own write paths set, and with `hidden` deduplicated.
12631
+ */
12632
+ function asSavedStack(spec) {
12633
+ if (!isPlainObject$1(spec.stack)) return spec;
12634
+ const { detected: _detected, declared: _declared, hiddenAt: _hiddenAt, ...stack } = spec.stack;
12635
+ if (Array.isArray(stack.hidden)) stack.hidden = [...new Set(stack.hidden)];
12636
+ return {
12637
+ ...spec,
12638
+ stack
12639
+ };
12640
+ }
11975
12641
  /** Canonical normalized form of a product definition (name + canvas excluded). */
11976
12642
  function normalizeProductDefinition(definition) {
11977
- const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(definition.spec) : {};
12643
+ const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(asSavedStack(definition.spec)) : {};
11978
12644
  return {
11979
12645
  ...definition.description ? { description: definition.description } : {},
11980
12646
  ...definition.icon ? { icon: definition.icon } : {},
@@ -12044,7 +12710,7 @@ var ProductDriftError = class extends Error {
12044
12710
  * Best-effort extraction of `{ status, body }` from the generic request
12045
12711
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
12046
12712
  */
12047
- function parseRequestError$1(err) {
12713
+ function parseRequestError$2(err) {
12048
12714
  if (!(err instanceof Error)) return {
12049
12715
  status: null,
12050
12716
  body: null
@@ -12067,7 +12733,7 @@ function parseRequestError$1(err) {
12067
12733
  }
12068
12734
  }
12069
12735
  function toConflictError$1(err) {
12070
- const { status, body } = parseRequestError$1(err);
12736
+ const { status, body } = parseRequestError$2(err);
12071
12737
  if (status !== 409 || !isPlainObject$1(body)) return null;
12072
12738
  const code = body.code;
12073
12739
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -12189,20 +12855,85 @@ function defineFpo(fpo) {
12189
12855
  if (!product || typeof product.name !== "string" || product.name.length === 0) throw new Error("defineFpo requires a non-empty \"product.name\" (the converge identity)");
12190
12856
  return fpo;
12191
12857
  }
12858
+ const FPO_DRIFT_RESULTS = /* @__PURE__ */ new Set([
12859
+ "created",
12860
+ "updated",
12861
+ "pruned",
12862
+ "failed"
12863
+ ]);
12864
+ /** Thrown by `ensureFpo(fpo, { expectNoChanges: true })` when the plan is not clean. */
12865
+ var FpoDriftError = class extends Error {
12866
+ constructor(plan) {
12867
+ const drifted = plan.entities.filter((entity) => FPO_DRIFT_RESULTS.has(entity.result)).map((entity) => `${entity.kind}:${entity.name} (${entity.result})`);
12868
+ super(`Product "${plan.productId ?? "definition"}" drifted (${drifted.join(", ")}). Run client.products.pullFpo(name) to absorb the remote edit into your repo, or re-run ensureFpo to converge.`);
12869
+ this.name = "FpoDriftError";
12870
+ this.plan = plan;
12871
+ }
12872
+ };
12873
+ function fpoPlanHasDrift(plan) {
12874
+ return plan.entities.some((entity) => FPO_DRIFT_RESULTS.has(entity.result));
12875
+ }
12876
+ function parseRequestError$1(err) {
12877
+ if (!(err instanceof Error)) return {
12878
+ status: null,
12879
+ body: null
12880
+ };
12881
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
12882
+ if (!match) return {
12883
+ status: null,
12884
+ body: null
12885
+ };
12886
+ try {
12887
+ return {
12888
+ status: Number(match[1]),
12889
+ body: JSON.parse(match[2])
12890
+ };
12891
+ } catch {
12892
+ return {
12893
+ status: Number(match[1]),
12894
+ body: null
12895
+ };
12896
+ }
12897
+ }
12898
+ async function requestFpoEnsure(client, body) {
12899
+ try {
12900
+ return await client.post("/products/ensure-fpo", body);
12901
+ } catch (err) {
12902
+ const { status, body: errorBody } = parseRequestError$1(err);
12903
+ if (status === 409 && isPlainObject$1(errorBody) && (errorBody.code === "external_modification" || errorBody.code === "remote_changed")) throw new ProductEnsureConflictError(errorBody);
12904
+ throw err;
12905
+ }
12906
+ }
12192
12907
  /**
12193
- * Converge an entire FPO onto the platform. Ships the full FPO (no hash-only
12194
- * probe in this release); the server fans out to the per-entity ensure services
12195
- * and returns the whole-FPO hash + per-entity report. Non-atomic: inspect
12196
- * `result.hasFailures` / `entities[].result` and re-run to self-heal.
12908
+ * Converge an entire FPO onto the platform. Ships the full FPO unless a
12909
+ * drafts-only `hashProbe` finds the live graph already matches; the server fans out to the
12910
+ * per-entity ensure services and returns the whole-FPO hash + per-entity report.
12911
+ * Non-atomic: inspect `result.hasFailures` / `entities[].result` and re-run to self-heal.
12197
12912
  */
12198
12913
  async function ensureFpo(client, fpo, options = {}) {
12199
- const { dryRun, onConflict, prune } = options;
12200
- return client.post("/products/ensure-fpo", {
12914
+ const { dryRun, onConflict, prune, expectNoChanges, expectedRemoteHash, hashProbe, release } = options;
12915
+ const plan = dryRun || expectNoChanges;
12916
+ const guard = expectedRemoteHash ? { expectedRemoteHash } : {};
12917
+ if (hashProbe && release === "none" && !plan && !prune) {
12918
+ const probe = await requestFpoEnsure(client, {
12919
+ name: (isPlainObject$1(fpo.product) ? fpo.product : {}).name,
12920
+ contentHash: await computeFpoContentHash(fpo),
12921
+ ...guard,
12922
+ release
12923
+ });
12924
+ if (probe.result !== "definitionRequired") return probe;
12925
+ }
12926
+ const result = await requestFpoEnsure(client, {
12201
12927
  fpo,
12202
- ...dryRun ? { dryRun: true } : {},
12928
+ ...plan ? { dryRun: true } : {},
12203
12929
  ...onConflict ? { onConflict } : {},
12204
- ...prune ? { prune: true } : {}
12930
+ ...prune ? { prune: true } : {},
12931
+ ...guard,
12932
+ ...release ? { release } : {}
12205
12933
  });
12934
+ if (result.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-FPO request");
12935
+ if (expectNoChanges && fpoPlanHasDrift(result)) throw new FpoDriftError(result);
12936
+ return result;
12206
12937
  }
12207
12938
  /**
12208
12939
  * Reconstruct a self-contained FPO from the live product graph (resolved by
@@ -12377,8 +13108,8 @@ function defineSurface(input) {
12377
13108
  if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSurface requires a non-empty string \"name\"");
12378
13109
  if (typeof input.type !== "string" || !SURFACE_DEFINITION_TYPES.has(input.type)) throw new Error(`defineSurface requires "type" to be one of: ${[...SURFACE_DEFINITION_TYPES].join(", ")}`);
12379
13110
  if (input.behavior !== void 0 && !isPlainObject$1(input.behavior)) throw new Error("defineSurface \"behavior\" must be an object when provided");
12380
- if (input.inbound !== void 0 && !isPlainObject$1(input.inbound)) throw new Error("defineSurface \"inbound\" must be an object when provided");
12381
- if (input.outbound !== void 0 && !isPlainObject$1(input.outbound)) throw new Error("defineSurface \"outbound\" must be an object when provided");
13111
+ if (input.inbound !== void 0 && input.inbound !== null && !isPlainObject$1(input.inbound)) throw new Error("defineSurface \"inbound\" must be an object or null when provided");
13112
+ if (input.outbound !== void 0 && input.outbound !== null && !isPlainObject$1(input.outbound)) throw new Error("defineSurface \"outbound\" must be an object or null when provided");
12382
13113
  if (input.status !== void 0 && ![
12383
13114
  "draft",
12384
13115
  "active",
@@ -12500,14 +13231,16 @@ async function ensureSurface(client, productId, definition, options = {}) {
12500
13231
  const localHash = await computeSurfaceContentHash(definition);
12501
13232
  const memoKey = `${productId} ${definition.name} ${localHash}`;
12502
13233
  const contentHash = memo.get(memoKey) ?? localHash;
12503
- const probe = await request(client, productId, {
12504
- name: definition.name,
12505
- contentHash,
12506
- ...passthrough
12507
- });
12508
- if (probe.result !== "definitionRequired") {
12509
- memoize(memo, memoKey, probe);
12510
- return probe;
13234
+ if (!(definition.inbound !== void 0 || definition.outbound !== void 0)) {
13235
+ const probe = await request(client, productId, {
13236
+ name: definition.name,
13237
+ contentHash,
13238
+ ...passthrough
13239
+ });
13240
+ if (probe.result !== "definitionRequired") {
13241
+ memoize(memo, memoKey, probe);
13242
+ return probe;
13243
+ }
12511
13244
  }
12512
13245
  const converged = await request(client, productId, {
12513
13246
  name: definition.name,
@@ -12520,8 +13253,8 @@ async function ensureSurface(client, productId, definition, options = {}) {
12520
13253
  }
12521
13254
  /**
12522
13255
  * Pull the canonical definition + provenance for a surface by name within a
12523
- * product — the absorb-drift direction. The contentHash reflects the live
12524
- * surface state.
13256
+ * product — the absorb-drift direction. Configured credentials come back as
13257
+ * `'<redacted>'`, which `ensure` accepts to keep the stored value.
12525
13258
  */
12526
13259
  async function pullSurface(client, productId, name) {
12527
13260
  return client.get(`/products/${encodeURIComponent(productId)}/surfaces/pull`, { name });
@@ -13236,6 +13969,25 @@ var Runtype = class {
13236
13969
  return new ToolsNamespace(() => this.getClient());
13237
13970
  }
13238
13971
  /**
13972
+ * Question sets namespace - Question set config-as-code (define / ensure / pull)
13973
+ *
13974
+ * @example
13975
+ * ```typescript
13976
+ * import { defineQuestionSet, Runtype } from '@runtypelabs/sdk'
13977
+ *
13978
+ * const triage = defineQuestionSet({
13979
+ * slug: 'ticket-triage',
13980
+ * name: 'Ticket triage',
13981
+ * questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
13982
+ * })
13983
+ *
13984
+ * await Runtype.questionSets.ensure(triage, { release: 'publish' })
13985
+ * ```
13986
+ */
13987
+ static get questionSets() {
13988
+ return new QuestionSetsNamespace(() => this.getClient());
13989
+ }
13990
+ /**
13239
13991
  * Products namespace - Product config-as-code (define / ensure / pull)
13240
13992
  *
13241
13993
  * Converges the top-level product record (description, icon, spec). Nested
@@ -13524,7 +14276,7 @@ function transformQueryParams(params) {
13524
14276
  }
13525
14277
  //#endregion
13526
14278
  //#region src/version.ts
13527
- const SDK_VERSION = "10.8.1";
14279
+ const SDK_VERSION = "10.11.0";
13528
14280
  /**
13529
14281
  * Canonical client identity the API uses for attribution
13530
14282
  * (`detectActorSource` in apps/api/src/lib/audit-log.ts keys `'sdk'` off the
@@ -15713,5 +16465,5 @@ const STEP_TYPE_TO_METHOD = {
15713
16465
  "memory-summary": "memorySummary"
15714
16466
  };
15715
16467
  //#endregion
15716
- export { AgentAliasDependencyError, AgentAliasNotFoundError, AgentAliasPreviewLimitError, AgentAliasRevisionMismatchError, AgentAliasRevisionRequiredError, AgentAliasesNamespace, AgentDeploymentsNamespace, AgentDriftError, AgentEnsureConflictError, AgentPromotionError, AgentVersionsEndpoint, AgentsEndpoint, AgentsNamespace, AnalyticsEndpoint, ApiKeyRequestsEndpoint, ApiKeysEndpoint, BatchBuilder, BatchesNamespace, BillingEndpoint, ChatEndpoint, ClientBatchBuilder, ClientEvalBuilder, ClientFlowBuilder, ClientTokenDriftError, ClientTokenEnsureError, ClientTokensEndpoint, ClientTokensNamespace, CollectionsEndpoint, ContextTemplatesEndpoint, ConversationsEndpoint, DEFAULT_MAX_DETACHED_RECONNECTS, DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS, DEFAULT_STALL_STOP_AFTER, DispatchEndpoint, ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE, EvalBuilder, EvalEndpoint, EvalRunner, EvalSuitesNamespace, EvalsNamespace, ExecutionsEndpoint, ExecutionsNamespace, FlowBuilder, FlowDriftError, FlowEnsureConflictError, FlowResult, FlowStepsEndpoint, FlowVersionsEndpoint, FlowsEndpoint, FlowsNamespace, IntegrationsEndpoint, LEDGER_ARTIFACT_LINE_PREFIX, LIVE_AGENT_ALIAS, LogsEndpoint, ModelConfigsEndpoint, ProductDriftError, ProductEnsureConflictError, ProductsNamespace, PromptsEndpoint, PromptsNamespace, ProviderKeysEndpoint, RUNTYPE_CLIENT_KIND, RecordsEndpoint, RunsNamespace, Runtype, RuntypeApiError, RuntypeClient, RuntypeFlowBuilder, SDK_USER_AGENT, SDK_VERSION, STEP_FIELD_REGISTRY, STEP_TYPE_TO_METHOD, SchedulesEndpoint, SecretsEndpoint, SkillDriftError, SkillEnsureConflictError, SkillProposalsNamespace, SkillsNamespace, SurfaceDriftError, SurfaceEnsureConflictError, SurfacesEndpoint, SurfacesNamespace, ToolApprovalGrantsEndpoint, ToolDriftError, ToolEnsureConflictError, ToolsEndpoint, ToolsNamespace, TypedRecordsScope, UNIFIED_EVENTS_QUERY, UsersEndpoint, activateAgentPromotion, agentAliasErrorCode, applyGeneratedRuntimeToolProposalToDispatchRequest, attachRuntimeToolsToDispatchRequest, buildAgentAdmissionHeaders, buildEmptySessionNudge, buildExecutionEventsPath, buildGeneratedRuntimeToolGateOutput, buildLedgerOffloadReference, buildObservationMaskMarker, buildPolicyGuidance, buildSendViewOffloadMarker, calledTool, combineAbortSignals, compileWorkflowConfig, completed, computeAgentContentHash, computeEvalContentHash, computeFlowContentHash, computeFpoContentHash, computeProductContentHash, computeSkillContentHash, computeSurfaceContentHash, computeToolContentHash, contains, cost, createAgentEventTranslator, createClient, createExternalTool, createFlowEventTranslator, defaultWorkflow, defaultWorkflowConfig, defineAgent, defineEval, defineFlow, defineFpo, definePlaybook, defineProduct, defineSkill, defineSurface, defineTool, deployWorkflow, ensureDefaultWorkflowHooks, ensureEval, ensureFpo, evaluateGeneratedRuntimeToolProposal, extractDeclaredToolResultChars, gameWorkflow, getDefaultPlanPath, getLikelySupportingCandidatePaths, interpolateWorkflowTemplate, isBuiltinToolExecuteResponse, isCatalogClientToolRef, isDiscoveryToolName, isMarathonArtifactPath, isPreservationSensitiveTask, isUnifiedEventType, isWorkflowHookRef, jsonField, judge, judges, latency, length, listWorkflowHooks, matchesExpected, maxToolCalls, noError, normalizeAgentDefinition, normalizeCandidatePath, normalizeFpoDefinition, normalizeProductDefinition, normalizeSkillDefinition, normalizeSurfaceDefinition, normalizeToolDefinition, notCalledTool, notContains, parseFinalBuffer, parseLedgerArtifactRelativePath, parseOffloadedOutputId, parseSSEChunk, prepareAgentPromotion, processStream, promoteAgent, promotionIdempotencyKey, pullEval, pullFpo, ranStep, regex, registerWorkflowHook, resolveStallStopAfter, resolveWorkflowHook, runEvalSuite, sanitizeTaskSlug, shouldInjectEmptySessionNudge, shouldRequestModelEscalation, stepOrder, streamEvents, toolOrder, unregisterWorkflowHook, usedNoTools, validJson, validateAgentPromotion, withDetachedReconnect, withUnifiedEvents };
16468
+ export { AgentAliasDependencyError, AgentAliasNotFoundError, AgentAliasRevisionMismatchError, AgentAliasRevisionRequiredError, AgentAliasesNamespace, AgentDeploymentsNamespace, AgentDriftError, AgentEnsureConflictError, AgentPromotionError, AgentVersionsEndpoint, AgentsEndpoint, AgentsNamespace, AnalyticsEndpoint, ApiKeyRequestsEndpoint, ApiKeysEndpoint, BatchBuilder, BatchesNamespace, BillingEndpoint, ChatEndpoint, ClientBatchBuilder, ClientEvalBuilder, ClientFlowBuilder, ClientTokenDriftError, ClientTokenEnsureError, ClientTokensEndpoint, ClientTokensNamespace, CollectionDriftError, CollectionEnsureConflictError, CollectionsEndpoint, ContextTemplatesEndpoint, ConversationsEndpoint, DEFAULT_MAX_DETACHED_RECONNECTS, DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS, DEFAULT_STALL_STOP_AFTER, DispatchEndpoint, ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE, EvalBuilder, EvalDriftError, EvalEndpoint, EvalEnsureConflictError, EvalRunner, EvalSuitesNamespace, EvalsNamespace, ExecutionsEndpoint, ExecutionsNamespace, FlowBuilder, FlowDriftError, FlowEnsureConflictError, FlowResult, FlowStepsEndpoint, FlowVersionsEndpoint, FlowsEndpoint, FlowsNamespace, FpoDriftError, IntegrationsEndpoint, LEDGER_ARTIFACT_LINE_PREFIX, LIVE_AGENT_ALIAS, LogsEndpoint, ModelConfigsEndpoint, ProductDriftError, ProductEnsureConflictError, ProductsNamespace, PromptsEndpoint, PromptsNamespace, ProviderKeysEndpoint, QuestionSetDriftError, QuestionSetEnsureConflictError, QuestionSetsNamespace, RUNTYPE_CLIENT_KIND, RecordsEndpoint, RunsNamespace, Runtype, RuntypeApiError, RuntypeClient, RuntypeFlowBuilder, SDK_USER_AGENT, SDK_VERSION, STEP_FIELD_REGISTRY, STEP_TYPE_TO_METHOD, ScheduleDriftError, ScheduleEnsureConflictError, SchedulesEndpoint, SecretsEndpoint, SkillDriftError, SkillEnsureConflictError, SkillProposalsNamespace, SkillsNamespace, SurfaceDriftError, SurfaceEnsureConflictError, SurfacesEndpoint, SurfacesNamespace, ToolApprovalGrantsEndpoint, ToolDriftError, ToolEnsureConflictError, ToolsEndpoint, ToolsNamespace, TypedRecordsScope, UNIFIED_EVENTS_QUERY, UsersEndpoint, activateAgentPromotion, agentAliasErrorCode, applyGeneratedRuntimeToolProposalToDispatchRequest, attachRuntimeToolsToDispatchRequest, buildAgentAdmissionHeaders, buildEmptySessionNudge, buildExecutionEventsPath, buildGeneratedRuntimeToolGateOutput, buildLedgerOffloadReference, buildObservationMaskMarker, buildPolicyGuidance, buildSendViewOffloadMarker, calledTool, combineAbortSignals, compileWorkflowConfig, completed, computeAgentContentHash, computeEvalContentHash, computeFlowContentHash, computeFpoContentHash, computeProductContentHash, computeQuestionSetContentHash, computeRecordCollectionContentHash, computeScheduleContentHash, computeScheduleDefinitionHash, computeSkillContentHash, computeSurfaceContentHash, computeToolContentHash, contains, cost, createAgentEventTranslator, createClient, createExternalTool, createFlowEventTranslator, defaultWorkflow, defaultWorkflowConfig, defineAgent, defineEval, defineFlow, defineFpo, definePlaybook, defineProduct, defineQuestionSet, defineSchedule, defineSkill, defineSurface, defineTool, deployWorkflow, ensureDefaultWorkflowHooks, ensureEval, ensureFpo, ensureSchedule, evaluateGeneratedRuntimeToolProposal, extractDeclaredToolResultChars, fpoPlanHasDrift, gameWorkflow, getDefaultPlanPath, getLikelySupportingCandidatePaths, interpolateWorkflowTemplate, isBuiltinToolExecuteResponse, isCatalogClientToolRef, isDiscoveryToolName, isMarathonArtifactPath, isPreservationSensitiveTask, isUnifiedEventType, isWorkflowHookRef, jsonField, judge, judges, latency, length, listWorkflowHooks, matchesExpected, maxToolCalls, noError, normalizeAgentDefinition, normalizeCandidatePath, normalizeFpoDefinition, normalizeProductDefinition, normalizeQuestionSetDefinition, normalizeRecordCollectionDefinition, normalizeScheduleContent, normalizeSkillDefinition, normalizeSurfaceDefinition, normalizeToolDefinition, notCalledTool, notContains, parseFinalBuffer, parseLedgerArtifactRelativePath, parseOffloadedOutputId, parseSSEChunk, prepareAgentPromotion, processStream, promoteAgent, promotionIdempotencyKey, pullEval, pullFpo, pullSchedule, ranStep, regex, registerWorkflowHook, resolveStallStopAfter, resolveWorkflowHook, runEvalSuite, sanitizeTaskSlug, shouldInjectEmptySessionNudge, shouldRequestModelEscalation, stepOrder, streamEvents, toolOrder, unregisterWorkflowHook, usedNoTools, validJson, validateAgentPromotion, withDetachedReconnect, withUnifiedEvents };
15717
16469