@runtypelabs/sdk 10.8.0 → 10.9.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.cjs CHANGED
@@ -2114,7 +2114,7 @@ function asRecord(value) {
2114
2114
  * Reading the structured fields FIRST is what makes the typed alias errors fire
2115
2115
  * for `createClient()` and the CLI, not just for `Runtype.agents`.
2116
2116
  */
2117
- function parseRequestError$6(err) {
2117
+ function parseRequestError$7(err) {
2118
2118
  if (!(err instanceof Error)) return {
2119
2119
  status: null,
2120
2120
  body: null
@@ -2149,7 +2149,7 @@ function asString$1(value, fallback) {
2149
2149
  * failure exactly as the transport threw it.
2150
2150
  */
2151
2151
  function toAliasError(err, agentId, alias) {
2152
- const { status, body } = parseRequestError$6(err);
2152
+ const { status, body } = parseRequestError$7(err);
2153
2153
  if (status === null) return null;
2154
2154
  const error = body && typeof body.error === "string" ? body.error : void 0;
2155
2155
  if (status === 412 && body && typeof body.expected === "number") return new AgentAliasRevisionMismatchError({
@@ -2339,6 +2339,174 @@ var AgentDeploymentsNamespace = class {
2339
2339
  }
2340
2340
  };
2341
2341
  //#endregion
2342
+ //#region src/content-hash.ts
2343
+ /**
2344
+ * Canonical content-hash primitives shared by the `*.ensure()` config-as-code
2345
+ * surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
2346
+ * those modules computes a SHA-256 content hash for its hash-first `/ensure`
2347
+ * wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
2348
+ * output for any given definition is a WIRE CONTRACT with the server, so this
2349
+ * module must stay dependency-free (the SDK is published to npm and must not
2350
+ * import `@runtypelabs/shared`, a private package) and must not change its
2351
+ * normalization behavior without a matching server-side change.
2352
+ *
2353
+ * `isPlainObject` and `normalizeValue` here are mirrored from
2354
+ * `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
2355
+ * keys and drops `null`/`undefined` entries recursively — this is the
2356
+ * canonical normalization used by products, tools, skills, surfaces, and the
2357
+ * whole-FPO hash.
2358
+ *
2359
+ * NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
2360
+ * (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
2361
+ * (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
2362
+ * in the flow case, use a shallower array-recursion rule. Those are real
2363
+ * semantic divergences from this canonical normalizer, not incidental
2364
+ * duplication — changing either to reuse `normalizeValue` would alter the
2365
+ * content hash for definitions containing explicit `null`s and break the
2366
+ * wire contract with already-converged flows/evals. They stay local by
2367
+ * design; see the divergence comment at each site.
2368
+ */
2369
+ function isPlainObject$1(value) {
2370
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2371
+ }
2372
+ /**
2373
+ * Recursively normalizes a value for canonical hashing: object keys are
2374
+ * sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
2375
+ * and have each item normalized in turn.
2376
+ */
2377
+ function normalizeValue$1(value) {
2378
+ if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
2379
+ if (isPlainObject$1(value)) {
2380
+ const normalized = {};
2381
+ for (const key of Object.keys(value).sort()) {
2382
+ const entry = value[key];
2383
+ if (entry === void 0 || entry === null) continue;
2384
+ normalized[key] = normalizeValue$1(entry);
2385
+ }
2386
+ return normalized;
2387
+ }
2388
+ return value;
2389
+ }
2390
+ /** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
2391
+ async function sha256Hex(serialized) {
2392
+ const encoded = new TextEncoder().encode(serialized);
2393
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
2394
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
2395
+ }
2396
+ //#endregion
2397
+ //#region src/collections-ensure.ts
2398
+ /** Canonical normalized form of a collection definition (slug excluded — identity). */
2399
+ function normalizeRecordCollectionDefinition(definition) {
2400
+ return {
2401
+ displayName: definition.displayName,
2402
+ ...definition.description ? { description: definition.description } : {},
2403
+ ...definition.icon ? { icon: definition.icon } : {},
2404
+ ...isPlainObject$1(definition.schema) ? { schema: normalizeValue$1(definition.schema) } : {},
2405
+ validationMode: definition.validationMode || "off"
2406
+ };
2407
+ }
2408
+ /** SHA-256 (hex) over the canonical normalized collection definition. */
2409
+ async function computeRecordCollectionContentHash(definition) {
2410
+ return sha256Hex(JSON.stringify(normalizeRecordCollectionDefinition(definition)));
2411
+ }
2412
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
2413
+ var CollectionEnsureConflictError = class extends Error {
2414
+ constructor(body) {
2415
+ super(body.error ?? `Collection ensure conflict: ${body.code}`);
2416
+ this.name = "CollectionEnsureConflictError";
2417
+ this.code = body.code;
2418
+ this.lastModifiedSource = body.lastModifiedSource;
2419
+ this.modifiedAt = body.modifiedAt;
2420
+ this.currentHash = body.currentHash;
2421
+ }
2422
+ };
2423
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
2424
+ var CollectionDriftError = class extends Error {
2425
+ constructor(slug, plan) {
2426
+ 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.`);
2427
+ this.name = "CollectionDriftError";
2428
+ this.plan = plan;
2429
+ }
2430
+ };
2431
+ function toConflictError$8(err) {
2432
+ if (!(err instanceof Error)) return null;
2433
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
2434
+ if (!match || match[1] !== "409") return null;
2435
+ let body;
2436
+ try {
2437
+ body = JSON.parse(match[2]);
2438
+ } catch {
2439
+ return null;
2440
+ }
2441
+ if (!isPlainObject$1(body)) return null;
2442
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
2443
+ return new CollectionEnsureConflictError(body);
2444
+ }
2445
+ /** Per-transport memo of `{ slug + localHash → server contentHash }`, as in tools.ensure. */
2446
+ const serverHashMemo$9 = /* @__PURE__ */ new WeakMap();
2447
+ function memoFor$9(transport) {
2448
+ let memo = serverHashMemo$9.get(transport);
2449
+ if (!memo) {
2450
+ memo = /* @__PURE__ */ new Map();
2451
+ serverHashMemo$9.set(transport, memo);
2452
+ }
2453
+ return memo;
2454
+ }
2455
+ async function request$7(transport, body) {
2456
+ try {
2457
+ return await transport.post("/collections/ensure", body);
2458
+ } catch (err) {
2459
+ throw toConflictError$8(err) ?? err;
2460
+ }
2461
+ }
2462
+ /**
2463
+ * Idempotently converge a collection definition. Probes with a content hash and
2464
+ * only ships the full definition when the server reports a miss. Never deletes
2465
+ * a collection and never touches records.
2466
+ */
2467
+ async function ensureCollection(transport, definition, options = {}) {
2468
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
2469
+ const passthrough = {
2470
+ ...onConflict ? { onConflict } : {},
2471
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
2472
+ };
2473
+ if (dryRun || expectNoChanges) {
2474
+ const plan = await request$7(transport, {
2475
+ slug: definition.slug,
2476
+ definition,
2477
+ dryRun: true,
2478
+ ...passthrough
2479
+ });
2480
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
2481
+ if (expectNoChanges && plan.changes !== "none") throw new CollectionDriftError(definition.slug, plan);
2482
+ return plan;
2483
+ }
2484
+ const memo = memoFor$9(transport);
2485
+ const localHash = await computeRecordCollectionContentHash(definition);
2486
+ const memoKey = `${definition.slug} ${localHash}`;
2487
+ const probe = await request$7(transport, {
2488
+ slug: definition.slug,
2489
+ contentHash: memo.get(memoKey) ?? localHash,
2490
+ ...passthrough
2491
+ });
2492
+ if (probe.result !== "definitionRequired") {
2493
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
2494
+ return probe;
2495
+ }
2496
+ const converged = await request$7(transport, {
2497
+ slug: definition.slug,
2498
+ definition,
2499
+ ...passthrough
2500
+ });
2501
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
2502
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
2503
+ return converged;
2504
+ }
2505
+ /** Pull the canonical definition and provenance for a collection by slug. */
2506
+ async function pullCollection(transport, slug) {
2507
+ return transport.get(`/collections/${encodeURIComponent(slug)}/pull`);
2508
+ }
2509
+ //#endregion
2342
2510
  //#region src/generated-tool-gate.ts
2343
2511
  const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
2344
2512
  const DEFAULT_MAX_CODE_LENGTH = 12e3;
@@ -2667,6 +2835,239 @@ function normalizeDispatchRequest(request) {
2667
2835
  };
2668
2836
  }
2669
2837
  //#endregion
2838
+ //#region src/schedules-ensure.ts
2839
+ function nonEmptyObject(value) {
2840
+ if (!isPlainObject$1(value)) return void 0;
2841
+ const normalized = normalizeValue$1(value);
2842
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
2843
+ }
2844
+ function nonEmptyString(value) {
2845
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2846
+ }
2847
+ function canonicalRunAt(value) {
2848
+ if (value === null || value === void 0 || value === "") return void 0;
2849
+ const date = value instanceof Date ? value : new Date(value);
2850
+ return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
2851
+ }
2852
+ /** Canonical normalized schedule content (name and target excluded — identity). */
2853
+ function normalizeScheduleContent(definition) {
2854
+ const cron = nonEmptyString(definition.cron);
2855
+ const timezone = nonEmptyString(definition.timezone);
2856
+ const runAt = canonicalRunAt(definition.runAt);
2857
+ const recordIds = Array.isArray(definition.recordIds) && definition.recordIds.length > 0 ? [...definition.recordIds] : void 0;
2858
+ const recordType = nonEmptyString(definition.recordType);
2859
+ const recordFilter = nonEmptyObject(definition.recordFilter);
2860
+ const messages = Array.isArray(definition.messages) && definition.messages.length > 0 ? normalizeValue$1(definition.messages) : void 0;
2861
+ const inputs = nonEmptyObject(definition.inputs);
2862
+ const executionOptions = nonEmptyObject(definition.executionOptions);
2863
+ return {
2864
+ triggerType: definition.triggerType === "one_time" ? "one_time" : "recurring",
2865
+ ...cron !== void 0 ? { cron } : {},
2866
+ ...timezone !== void 0 ? { timezone } : {},
2867
+ ...runAt !== void 0 ? { runAt } : {},
2868
+ ...recordIds !== void 0 ? { recordIds } : {},
2869
+ ...recordType !== void 0 ? { recordType } : {},
2870
+ ...recordFilter !== void 0 ? { recordFilter } : {},
2871
+ ...messages !== void 0 ? { messages } : {},
2872
+ ...inputs !== void 0 ? { inputs } : {},
2873
+ ...executionOptions !== void 0 ? { executionOptions } : {},
2874
+ ...typeof definition.enabled === "boolean" ? { enabled: definition.enabled } : {}
2875
+ };
2876
+ }
2877
+ /** SHA-256 (hex) over canonical schedule content, in the flat content shape. */
2878
+ async function computeScheduleContentHash(definition) {
2879
+ return sha256Hex(JSON.stringify(normalizeScheduleContent(definition)));
2880
+ }
2881
+ /** The content hash of a `defineSchedule` definition. */
2882
+ async function computeScheduleDefinitionHash(definition) {
2883
+ return computeScheduleContentHash({
2884
+ triggerType: definition.trigger.type,
2885
+ cron: definition.trigger.cron,
2886
+ timezone: definition.trigger.timezone,
2887
+ runAt: definition.trigger.runAt,
2888
+ recordIds: definition.recordIds,
2889
+ recordType: definition.recordType,
2890
+ recordFilter: definition.recordFilter,
2891
+ messages: definition.messages,
2892
+ inputs: definition.inputs,
2893
+ executionOptions: definition.executionOptions,
2894
+ enabled: definition.enabled
2895
+ });
2896
+ }
2897
+ const TARGET_KEYS = [
2898
+ "flowId",
2899
+ "agentId",
2900
+ "flowName",
2901
+ "agentName"
2902
+ ];
2903
+ const DEFINE_SCHEDULE_KEYS = /* @__PURE__ */ new Set([
2904
+ "name",
2905
+ "target",
2906
+ "trigger",
2907
+ "recordIds",
2908
+ "recordType",
2909
+ "recordFilter",
2910
+ "messages",
2911
+ "inputs",
2912
+ "executionOptions",
2913
+ "enabled"
2914
+ ]);
2915
+ /**
2916
+ * Pure-local declarative constructor for a standalone schedule. No I/O.
2917
+ * Validates structure; cron, timezone, record filter and plan limits are
2918
+ * checked server-side at ensure time.
2919
+ *
2920
+ * @example
2921
+ * ```typescript
2922
+ * const nightly = defineSchedule({
2923
+ * name: 'Nightly digest',
2924
+ * target: { flowName: 'Digest' },
2925
+ * trigger: { type: 'recurring', cron: '0 2 * * *', timezone: 'UTC' },
2926
+ * })
2927
+ * ```
2928
+ */
2929
+ function defineSchedule(input) {
2930
+ if (!input || typeof input !== "object") throw new Error("defineSchedule requires a definition object");
2931
+ if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSchedule requires a non-empty string \"name\"");
2932
+ if (!isPlainObject$1(input.target)) throw new Error("defineSchedule requires a \"target\" object");
2933
+ const target = input.target;
2934
+ const setKeys = TARGET_KEYS.filter((key) => target[key] !== void 0);
2935
+ const extraKeys = Object.keys(target).filter((key) => !TARGET_KEYS.includes(key));
2936
+ if (setKeys.length !== 1 || extraKeys.length > 0) throw new Error("defineSchedule \"target\" needs exactly one of flowId, agentId, flowName, agentName");
2937
+ const trigger = input.trigger;
2938
+ if (!isPlainObject$1(trigger) || trigger.type !== "recurring" && trigger.type !== "one_time") throw new Error("defineSchedule requires \"trigger.type\" to be \"recurring\" or \"one_time\"");
2939
+ if (trigger.type === "recurring" && (!trigger.cron || !trigger.timezone)) throw new Error("defineSchedule: recurring triggers require \"cron\" and \"timezone\"");
2940
+ if (trigger.type === "one_time" && !trigger.runAt) throw new Error("defineSchedule: one_time triggers require \"runAt\"");
2941
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SCHEDULE_KEYS.has(key));
2942
+ if (unknownKeys.length > 0) throw new Error(`defineSchedule: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are ${[...DEFINE_SCHEDULE_KEYS].join(", ")}.`);
2943
+ return { ...input };
2944
+ }
2945
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
2946
+ var ScheduleEnsureConflictError = class extends Error {
2947
+ constructor(body) {
2948
+ super(body.error ?? `Schedule ensure conflict: ${body.code}`);
2949
+ this.name = "ScheduleEnsureConflictError";
2950
+ this.code = body.code;
2951
+ this.lastModifiedSource = body.lastModifiedSource;
2952
+ this.modifiedAt = body.modifiedAt;
2953
+ this.currentHash = body.currentHash;
2954
+ }
2955
+ };
2956
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
2957
+ var ScheduleDriftError = class extends Error {
2958
+ constructor(plan) {
2959
+ 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.`);
2960
+ this.name = "ScheduleDriftError";
2961
+ this.plan = plan;
2962
+ }
2963
+ };
2964
+ function errorStatusAndBody(err) {
2965
+ if (!(err instanceof Error)) return {
2966
+ status: null,
2967
+ body: null
2968
+ };
2969
+ const typed = err;
2970
+ if (typeof typed.statusCode === "number") return {
2971
+ status: typed.statusCode,
2972
+ body: typed.data
2973
+ };
2974
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
2975
+ if (!match) return {
2976
+ status: null,
2977
+ body: null
2978
+ };
2979
+ try {
2980
+ return {
2981
+ status: Number(match[1]),
2982
+ body: JSON.parse(match[2])
2983
+ };
2984
+ } catch {
2985
+ return {
2986
+ status: Number(match[1]),
2987
+ body: null
2988
+ };
2989
+ }
2990
+ }
2991
+ function toConflictError$7(err) {
2992
+ const { status, body } = errorStatusAndBody(err);
2993
+ if (status !== 409 || !isPlainObject$1(body)) return null;
2994
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
2995
+ return new ScheduleEnsureConflictError(body);
2996
+ }
2997
+ const serverHashMemo$8 = /* @__PURE__ */ new WeakMap();
2998
+ function memoFor$8(client) {
2999
+ let memo = serverHashMemo$8.get(client);
3000
+ if (!memo) {
3001
+ memo = /* @__PURE__ */ new Map();
3002
+ serverHashMemo$8.set(client, memo);
3003
+ }
3004
+ return memo;
3005
+ }
3006
+ async function request$6(client, body) {
3007
+ try {
3008
+ return await client.post("/schedules/ensure", body);
3009
+ } catch (err) {
3010
+ const conflict = toConflictError$7(err);
3011
+ if (conflict) throw conflict;
3012
+ throw err;
3013
+ }
3014
+ }
3015
+ /**
3016
+ * Idempotently converge a standalone schedule. Hash-first: probes with a
3017
+ * content hash and ships the full definition only on a miss. Never deletes.
3018
+ */
3019
+ async function ensureSchedule(client, definition, options = {}) {
3020
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
3021
+ const identity = {
3022
+ name: definition.name,
3023
+ target: definition.target
3024
+ };
3025
+ const passthrough = {
3026
+ ...onConflict ? { onConflict } : {},
3027
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3028
+ };
3029
+ if (dryRun || expectNoChanges) {
3030
+ const plan = await request$6(client, {
3031
+ ...identity,
3032
+ definition,
3033
+ dryRun: true,
3034
+ ...passthrough
3035
+ });
3036
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3037
+ if (expectNoChanges && plan.changes !== "none") throw new ScheduleDriftError(plan);
3038
+ return plan;
3039
+ }
3040
+ const memo = memoFor$8(client);
3041
+ const localHash = await computeScheduleDefinitionHash(definition);
3042
+ const memoKey = `${JSON.stringify(identity)} ${localHash}`;
3043
+ if (!expectedRemoteHash) {
3044
+ const probe = await request$6(client, {
3045
+ ...identity,
3046
+ contentHash: memo.get(memoKey) ?? localHash,
3047
+ ...passthrough
3048
+ });
3049
+ if (probe.result !== "definitionRequired") {
3050
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
3051
+ return probe;
3052
+ }
3053
+ }
3054
+ const converged = await request$6(client, {
3055
+ ...identity,
3056
+ definition,
3057
+ ...passthrough
3058
+ });
3059
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
3060
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
3061
+ return converged;
3062
+ }
3063
+ /** Pull the canonical definition + provenance for a schedule by name and target. */
3064
+ async function pullSchedule(client, name, target) {
3065
+ return client.get("/schedules/pull", {
3066
+ name,
3067
+ ...target
3068
+ });
3069
+ }
3070
+ //#endregion
2670
3071
  //#region src/offload-markers.ts
2671
3072
  const LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
2672
3073
  const SPILL_MARKER_PATTERN = /\[Output saved to (art_[A-Za-z0-9_-]+) \(([\d,]+) chars?\); read_offloaded_output to view\]/;
@@ -4640,6 +5041,28 @@ var CollectionsEndpoint = class {
4640
5041
  async typegen() {
4641
5042
  return this.client.get("/collections/types.d.ts");
4642
5043
  }
5044
+ /**
5045
+ * Idempotently converge a repo-declared collection (display fields, schema,
5046
+ * validationMode) by slug. Hash-first; `dryRun` / `expectNoChanges` plan
5047
+ * without writing. Never deletes a collection or touches records.
5048
+ *
5049
+ * @example
5050
+ * ```typescript
5051
+ * await client.collections.ensure({
5052
+ * slug: 'customers',
5053
+ * displayName: 'Customers',
5054
+ * schema: { type: 'object', properties: { status: { type: 'string' } } },
5055
+ * validationMode: 'warn',
5056
+ * })
5057
+ * ```
5058
+ */
5059
+ async ensure(definition, options) {
5060
+ return ensureCollection(this.client, definition, options);
5061
+ }
5062
+ /** Pull the canonical definition and provenance for a collection — the absorb-drift direction. */
5063
+ async pull(slug) {
5064
+ return pullCollection(this.client, slug);
5065
+ }
4643
5066
  };
4644
5067
  /**
4645
5068
  * Agent API key REQUESTS — a device-authorization-style grant.
@@ -8172,6 +8595,21 @@ var SchedulesEndpoint = class {
8172
8595
  return this.client.post("/schedules", data);
8173
8596
  }
8174
8597
  /**
8598
+ * Idempotently converge a `defineSchedule` definition on a standalone flow or
8599
+ * agent. Identity is name + target; the steady state is one hash probe. Never
8600
+ * deletes, and an omitted `enabled` leaves a paused schedule paused.
8601
+ */
8602
+ async ensure(definition, options = {}) {
8603
+ return ensureSchedule(this.client, definition, options);
8604
+ }
8605
+ /**
8606
+ * Pull the canonical definition + provenance for a schedule by name and
8607
+ * target — the absorb-drift direction of `ensure`.
8608
+ */
8609
+ async pull(name, target) {
8610
+ return pullSchedule(this.client, name, target);
8611
+ }
8612
+ /**
8175
8613
  * Update a schedule (partial update supported).
8176
8614
  */
8177
8615
  async update(id, data) {
@@ -8560,6 +8998,17 @@ var ToolApprovalGrantsEndpoint = class {
8560
8998
  };
8561
8999
  //#endregion
8562
9000
  //#region src/detached-buffered-follow.ts
9001
+ /** The release fields a response body carries, dropping any it does not name. */
9002
+ function releaseFieldsOf(data) {
9003
+ if (typeof data !== "object" || data === null) return {};
9004
+ const record = data;
9005
+ const resolution = record.agentTargetResolution;
9006
+ return {
9007
+ ...typeof record.agentVersionId === "string" ? { agentVersionId: record.agentVersionId } : {},
9008
+ ...typeof record.alias === "string" ? { alias: record.alias } : {},
9009
+ ...resolution === "alias" || resolution === "version" || resolution === "legacy-live-row" ? { agentTargetResolution: resolution } : {}
9010
+ };
9011
+ }
8563
9012
  function isDetachedToolOutputAck(data) {
8564
9013
  if (typeof data !== "object" || data === null) return false;
8565
9014
  const record = data;
@@ -8587,9 +9036,12 @@ function isLocalToolAwait(event) {
8587
9036
  * buffered continuation envelope a non-streaming local-tool loop expects.
8588
9037
  *
8589
9038
  * `after` is the last `seq` the loop had already consumed, so the replay
8590
- * never re-delivers the await the outputs just answered.
9039
+ * never re-delivers the await the outputs just answered. Version fields from
9040
+ * earlier responses (`carried`) and the ack land on the envelope, since the
9041
+ * events stream never repeats them.
8591
9042
  */
8592
- async function followDetachedResume(client, executionId, after) {
9043
+ async function followDetachedResume(client, ack, after, carried = {}) {
9044
+ const { executionId } = ack;
8593
9045
  const open = (cursor, signal) => client.requestStream(buildExecutionEventsPath(executionId, cursor ? { after: cursor } : {}), {
8594
9046
  method: "GET",
8595
9047
  ...signal ? { signal } : {}
@@ -8647,65 +9099,12 @@ async function followDetachedResume(client, executionId, after) {
8647
9099
  executionId,
8648
9100
  events,
8649
9101
  status,
8650
- ...error ? { error } : {}
9102
+ ...error ? { error } : {},
9103
+ ...carried,
9104
+ ...releaseFieldsOf(ack)
8651
9105
  };
8652
9106
  }
8653
9107
  //#endregion
8654
- //#region src/content-hash.ts
8655
- /**
8656
- * Canonical content-hash primitives shared by the `*.ensure()` config-as-code
8657
- * surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
8658
- * those modules computes a SHA-256 content hash for its hash-first `/ensure`
8659
- * wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
8660
- * output for any given definition is a WIRE CONTRACT with the server, so this
8661
- * module must stay dependency-free (the SDK is published to npm and must not
8662
- * import `@runtypelabs/shared`, a private package) and must not change its
8663
- * normalization behavior without a matching server-side change.
8664
- *
8665
- * `isPlainObject` and `normalizeValue` here are mirrored from
8666
- * `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
8667
- * keys and drops `null`/`undefined` entries recursively — this is the
8668
- * canonical normalization used by products, tools, skills, surfaces, and the
8669
- * whole-FPO hash.
8670
- *
8671
- * NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
8672
- * (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
8673
- * (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
8674
- * in the flow case, use a shallower array-recursion rule. Those are real
8675
- * semantic divergences from this canonical normalizer, not incidental
8676
- * duplication — changing either to reuse `normalizeValue` would alter the
8677
- * content hash for definitions containing explicit `null`s and break the
8678
- * wire contract with already-converged flows/evals. They stay local by
8679
- * design; see the divergence comment at each site.
8680
- */
8681
- function isPlainObject$1(value) {
8682
- return value !== null && typeof value === "object" && !Array.isArray(value);
8683
- }
8684
- /**
8685
- * Recursively normalizes a value for canonical hashing: object keys are
8686
- * sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
8687
- * and have each item normalized in turn.
8688
- */
8689
- function normalizeValue$1(value) {
8690
- if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
8691
- if (isPlainObject$1(value)) {
8692
- const normalized = {};
8693
- for (const key of Object.keys(value).sort()) {
8694
- const entry = value[key];
8695
- if (entry === void 0 || entry === null) continue;
8696
- normalized[key] = normalizeValue$1(entry);
8697
- }
8698
- return normalized;
8699
- }
8700
- return value;
8701
- }
8702
- /** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
8703
- async function sha256Hex(serialized) {
8704
- const encoded = new TextEncoder().encode(serialized);
8705
- const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
8706
- return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
8707
- }
8708
- //#endregion
8709
9108
  //#region src/evals-ensure.ts
8710
9109
  const CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
8711
9110
  "contains",
@@ -9099,6 +9498,25 @@ async function computeEvalContentHash(definition) {
9099
9498
  };
9100
9499
  return sha256Hex(JSON.stringify(canonical));
9101
9500
  }
9501
+ /** A 409 from the eval ensure protocol (external_modification or remote_changed). */
9502
+ var EvalEnsureConflictError = class extends Error {
9503
+ constructor(body) {
9504
+ super(body.error ?? `Eval ensure conflict: ${body.code}`);
9505
+ this.name = "EvalEnsureConflictError";
9506
+ this.code = body.code;
9507
+ this.lastModifiedSource = body.lastModifiedSource;
9508
+ this.modifiedAt = body.modifiedAt;
9509
+ this.currentHash = body.currentHash;
9510
+ }
9511
+ };
9512
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
9513
+ var EvalDriftError = class extends Error {
9514
+ constructor(plan) {
9515
+ 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.`);
9516
+ this.name = "EvalDriftError";
9517
+ this.plan = plan;
9518
+ }
9519
+ };
9102
9520
  /**
9103
9521
  * Per-client memo of `{ name + localHash → server contentHash }` so a hot
9104
9522
  * process pays the full-definition round trip once per boot (steady state
@@ -9106,44 +9524,75 @@ async function computeEvalContentHash(definition) {
9106
9524
  * hash, so a definition that changes within the process can never false-match a
9107
9525
  * stale server hash. Mirrors the flow ensure memo.
9108
9526
  */
9109
- const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
9110
- function memoFor$6(client) {
9111
- let memo = serverHashMemo$6.get(client);
9527
+ const serverHashMemo$7 = /* @__PURE__ */ new WeakMap();
9528
+ function memoFor$7(client) {
9529
+ let memo = serverHashMemo$7.get(client);
9112
9530
  if (!memo) {
9113
9531
  memo = /* @__PURE__ */ new Map();
9114
- serverHashMemo$6.set(client, memo);
9532
+ serverHashMemo$7.set(client, memo);
9115
9533
  }
9116
9534
  return memo;
9117
9535
  }
9118
- /**
9119
- * Idempotently converge an eval suite definition onto the platform. Hash-first:
9120
- * probes with a content hash, and only ships the full definition when the
9121
- * server reports a miss (`definitionRequired`). Upserts the suite + replaces
9122
- * its cases; never executes the eval (use the `runtype eval` CLI / `/eval`
9123
- * submit surface to run it).
9124
- *
9125
- * `virtual: true` definitions are ephemeral and have nothing durable to
9126
- * converge — ensure rejects them. Run a virtual eval directly instead.
9127
- */
9128
- async function ensureEval(client, definition) {
9536
+ function toEvalConflictError(err) {
9537
+ if (!(err instanceof Error)) return null;
9538
+ const match = err.message.match(/^API request failed: 409 .*? - ([\s\S]*)$/);
9539
+ if (!match) return null;
9540
+ let body;
9541
+ try {
9542
+ body = JSON.parse(match[1]);
9543
+ } catch {
9544
+ return null;
9545
+ }
9546
+ if (!isPlainObject$1(body)) return null;
9547
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
9548
+ return new EvalEnsureConflictError(body);
9549
+ }
9550
+ async function postEvalEnsure(client, body) {
9551
+ try {
9552
+ return await client.post("/eval/ensure", body);
9553
+ } catch (err) {
9554
+ throw toEvalConflictError(err) ?? err;
9555
+ }
9556
+ }
9557
+ async function ensureEval(client, definition, options = {}) {
9129
9558
  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.");
9130
- const memo = memoFor$6(client);
9559
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
9560
+ const passthrough = {
9561
+ ...onConflict ? { onConflict } : {},
9562
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
9563
+ };
9564
+ if (dryRun || expectNoChanges) {
9565
+ const plan = await postEvalEnsure(client, {
9566
+ name: definition.name,
9567
+ definition,
9568
+ dryRun: true,
9569
+ ...passthrough
9570
+ });
9571
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
9572
+ if (expectNoChanges && plan.changes !== "none") throw new EvalDriftError(plan);
9573
+ return plan;
9574
+ }
9575
+ const memo = memoFor$7(client);
9131
9576
  const localHash = await computeEvalContentHash(definition);
9132
9577
  const memoKey = `${definition.name} ${localHash}`;
9133
9578
  const contentHash = memo.get(memoKey) ?? localHash;
9134
- const probe = await client.post("/eval/ensure", {
9579
+ const probe = await postEvalEnsure(client, {
9135
9580
  name: definition.name,
9136
- contentHash
9581
+ contentHash,
9582
+ ...passthrough
9137
9583
  });
9584
+ if (probe.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
9138
9585
  if (probe.result !== "definitionRequired") {
9139
9586
  memo.set(memoKey, probe.contentHash);
9140
9587
  return probe;
9141
9588
  }
9142
- const converged = await client.post("/eval/ensure", {
9589
+ const converged = await postEvalEnsure(client, {
9143
9590
  name: definition.name,
9144
- definition
9591
+ definition,
9592
+ ...passthrough
9145
9593
  });
9146
9594
  if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
9595
+ if (converged.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
9147
9596
  memo.set(memoKey, converged.contentHash);
9148
9597
  return converged;
9149
9598
  }
@@ -9198,9 +9647,11 @@ function normalizeStepForHash(step) {
9198
9647
  }
9199
9648
  /** SHA-256 (hex) over the canonical normalized step list (steps only). */
9200
9649
  async function computeFlowContentHash(steps) {
9201
- const normalized = [...steps].sort((a, b) => {
9202
- return (isPlainObject$1(a) && typeof a.order === "number" ? a.order : 0) - (isPlainObject$1(b) && typeof b.order === "number" ? b.order : 0);
9203
- }).map(normalizeStepForHash);
9650
+ const normalized = steps.map((step) => isPlainObject$1(step) ? step : {}).map((step, index) => ({
9651
+ ...step,
9652
+ name: typeof step.name === "string" ? step.name : `Step ${index + 1}`,
9653
+ order: typeof step.order === "number" ? step.order : index + 1
9654
+ })).sort((a, b) => a.order - b.order).map(normalizeStepForHash);
9204
9655
  return sha256Hex(JSON.stringify(normalized));
9205
9656
  }
9206
9657
  const DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
@@ -9219,8 +9670,8 @@ const DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
9219
9670
  /**
9220
9671
  * Collect account-scoped `tool_…` references from a step config (inlined copy
9221
9672
  * of `collectNonPortableAgentToolRefs` in @runtypelabs/shared, applied to the
9222
- * step's `config.tools` block and recursively to conditional branches). The
9223
- * v1 ensure surface accepts portable tool references only.
9673
+ * step's `config.tools` block, `toolId` and `agentId`). The v1 ensure surface
9674
+ * accepts portable tool references only.
9224
9675
  */
9225
9676
  function collectStepNonPortableToolRefs(config, path) {
9226
9677
  const found = [];
@@ -9254,12 +9705,73 @@ function collectStepNonPortableToolRefs(config, path) {
9254
9705
  }
9255
9706
  if (isAccountScoped(config.toolId)) found.push(`${path}.toolId`);
9256
9707
  if (isRawId(config.agentId, "agent_")) found.push(`${path}.agentId`);
9257
- for (const branch of ["trueSteps", "falseSteps"]) {
9258
- const nested = config[branch];
9259
- if (!Array.isArray(nested)) continue;
9260
- nested.forEach((nestedStep, i) => {
9261
- if (isPlainObject$1(nestedStep) && isPlainObject$1(nestedStep.config)) found.push(...collectStepNonPortableToolRefs(nestedStep.config, `${path}.${branch}[${i}].config`));
9708
+ return found;
9709
+ }
9710
+ const CONDITIONAL_BRANCH_STEP_KEYS = [
9711
+ "trueSteps",
9712
+ "falseSteps",
9713
+ "true_steps",
9714
+ "false_steps",
9715
+ "otherwiseSteps"
9716
+ ];
9717
+ /** Guards a hostile payload whose nesting is otherwise unbounded. */
9718
+ const MAX_REF_SITE_WALK_DEPTH = 100;
9719
+ /**
9720
+ * Collect account-scoped references at every flow step ref site (inlined copy of
9721
+ * `collectNonPortableFlowStepRefs` in @runtypelabs/shared): loop bodies, conditional
9722
+ * branches, inline flow-as-tool payloads and `errorHandling.fallbacks`.
9723
+ */
9724
+ function collectFlowStepsNonPortableRefs(steps, basePath, depth = 0) {
9725
+ if (!Array.isArray(steps) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
9726
+ const found = [];
9727
+ steps.forEach((step, index) => {
9728
+ if (!isPlainObject$1(step) || !isPlainObject$1(step.config)) return;
9729
+ found.push(...collectStepConfigSiteRefs(step.type, step.config, `${basePath}[${index}].config`, depth));
9730
+ });
9731
+ return found;
9732
+ }
9733
+ function collectStepConfigSiteRefs(type, config, path, depth) {
9734
+ const found = collectStepNonPortableToolRefs(config, path);
9735
+ found.push(...collectToolBagFlowRefs(config.tools, `${path}.tools`, depth + 1));
9736
+ const errorHandling = config.errorHandling;
9737
+ if (isPlainObject$1(errorHandling) && Array.isArray(errorHandling.fallbacks)) errorHandling.fallbacks.forEach((fallback, i) => {
9738
+ if (!isPlainObject$1(fallback)) return;
9739
+ const fallbackPath = `${path}.errorHandling.fallbacks[${i}]`;
9740
+ if (fallback.type === "flow") {
9741
+ if (typeof fallback.flowId === "string" && fallback.flowId.startsWith("flow_")) found.push(`${fallbackPath}.flowId`);
9742
+ } else if (fallback.type === "step" && isPlainObject$1(fallback.stepConfig)) {
9743
+ found.push(...collectStepNonPortableToolRefs(fallback.stepConfig, `${fallbackPath}.stepConfig`));
9744
+ found.push(...collectToolBagFlowRefs(fallback.stepConfig.tools, `${fallbackPath}.stepConfig.tools`, depth + 1));
9745
+ }
9746
+ });
9747
+ if (type === "loop") found.push(...collectFlowStepsNonPortableRefs(config.steps, `${path}.steps`, depth + 1));
9748
+ else if (type === "conditional") {
9749
+ if (Array.isArray(config.branches)) config.branches.forEach((branch, i) => {
9750
+ if (!isPlainObject$1(branch)) return;
9751
+ found.push(...collectFlowStepsNonPortableRefs(branch.steps, `${path}.branches[${i}].steps`, depth + 1));
9262
9752
  });
9753
+ for (const key of CONDITIONAL_BRANCH_STEP_KEYS) found.push(...collectFlowStepsNonPortableRefs(config[key], `${path}.${key}`, depth + 1));
9754
+ }
9755
+ return found;
9756
+ }
9757
+ /** Descend the inline flow-as-tool and skill payloads a step's tool bag can carry. */
9758
+ function collectToolBagFlowRefs(tools, path, depth) {
9759
+ if (!isPlainObject$1(tools) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
9760
+ const found = [];
9761
+ const walkBag = (bag, bagPath, bagDepth) => {
9762
+ if (bagDepth > MAX_REF_SITE_WALK_DEPTH) return;
9763
+ bag.forEach((tool, i) => {
9764
+ if (!isPlainObject$1(tool) || !isPlainObject$1(tool.config)) return;
9765
+ const toolPath = `${bagPath}[${i}].config`;
9766
+ const skill = tool.config.skill;
9767
+ if (isPlainObject$1(skill) && Array.isArray(skill.resolvedTools)) walkBag(skill.resolvedTools, `${toolPath}.skill.resolvedTools`, bagDepth + 1);
9768
+ const flow = tool.config.flow;
9769
+ if (isPlainObject$1(flow) && Array.isArray(flow.steps)) found.push(...collectFlowStepsNonPortableRefs(flow.steps, `${toolPath}.flow.steps`, bagDepth + 1));
9770
+ });
9771
+ };
9772
+ for (const key of ["runtimeTools", "inlineTools"]) {
9773
+ const bag = tools[key];
9774
+ if (Array.isArray(bag)) walkBag(bag, `${path}.${key}`, depth);
9263
9775
  }
9264
9776
  return found;
9265
9777
  }
@@ -9300,7 +9812,7 @@ function defineFlow(input) {
9300
9812
  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.)`);
9301
9813
  const config = isPlainObject$1(step.config) ? step.config : void 0;
9302
9814
  if (config) {
9303
- const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
9815
+ const nonPortable = collectStepConfigSiteRefs(step.type, config, `steps[${index}].config`, 0);
9304
9816
  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.`);
9305
9817
  }
9306
9818
  return {
@@ -9354,7 +9866,7 @@ var FlowEnsureConflictError = class extends Error {
9354
9866
  /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
9355
9867
  var FlowDriftError = class extends Error {
9356
9868
  constructor(plan) {
9357
- 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.`);
9869
+ 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.`);
9358
9870
  this.name = "FlowDriftError";
9359
9871
  this.plan = plan;
9360
9872
  }
@@ -9363,7 +9875,7 @@ var FlowDriftError = class extends Error {
9363
9875
  * Best-effort extraction of `{ status, body }` from the generic request
9364
9876
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
9365
9877
  */
9366
- function parseRequestError$5(err) {
9878
+ function parseRequestError$6(err) {
9367
9879
  if (!(err instanceof Error)) return {
9368
9880
  status: null,
9369
9881
  body: null
@@ -9385,8 +9897,8 @@ function parseRequestError$5(err) {
9385
9897
  };
9386
9898
  }
9387
9899
  }
9388
- function toConflictError$5(err) {
9389
- const { status, body } = parseRequestError$5(err);
9900
+ function toConflictError$6(err) {
9901
+ const { status, body } = parseRequestError$6(err);
9390
9902
  if (status !== 409 || !isPlainObject$1(body)) return null;
9391
9903
  const code = body.code;
9392
9904
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -9402,23 +9914,23 @@ function toConflictError$5(err) {
9402
9914
  * so a stale server hash from the previous definition version can never be
9403
9915
  * probed (which would false-match and silently skip the update).
9404
9916
  */
9405
- const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
9406
- function memoFor$5(client) {
9407
- let memo = serverHashMemo$5.get(client);
9917
+ const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
9918
+ function memoFor$6(client) {
9919
+ let memo = serverHashMemo$6.get(client);
9408
9920
  if (!memo) {
9409
9921
  memo = /* @__PURE__ */ new Map();
9410
- serverHashMemo$5.set(client, memo);
9922
+ serverHashMemo$6.set(client, memo);
9411
9923
  }
9412
9924
  return memo;
9413
9925
  }
9414
9926
  function memoize$4(memo, memoKey, result) {
9415
9927
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
9416
9928
  }
9417
- async function request$4(client, body) {
9929
+ async function request$5(client, body) {
9418
9930
  try {
9419
9931
  return await client.post("/flows/ensure", body);
9420
9932
  } catch (err) {
9421
- const conflict = toConflictError$5(err);
9933
+ const conflict = toConflictError$6(err);
9422
9934
  if (conflict) throw conflict;
9423
9935
  throw err;
9424
9936
  }
@@ -9443,37 +9955,37 @@ async function ensureFlow(client, definition, options = {}) {
9443
9955
  steps: definition.steps
9444
9956
  };
9445
9957
  if (dryRun || expectNoChanges) {
9446
- const plan = await request$4(client, {
9958
+ const plan = await request$5(client, {
9447
9959
  name: definition.name,
9448
9960
  definition: wireDefinition,
9449
9961
  dryRun: true,
9450
9962
  ...passthrough
9451
9963
  });
9452
9964
  if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
9453
- if (expectNoChanges && plan.changes !== "none") throw new FlowDriftError(plan);
9965
+ if (expectNoChanges && (plan.changes !== "none" || release === "publish" && plan.publishPending === true)) throw new FlowDriftError(plan);
9454
9966
  return plan;
9455
9967
  }
9456
- const memo = memoFor$5(client);
9968
+ const memo = memoFor$6(client);
9457
9969
  const localHash = await computeFlowContentHash(definition.steps);
9458
9970
  const memoKey = `${definition.name} ${localHash}`;
9459
9971
  const contentHash = memo.get(memoKey) ?? localHash;
9460
- const probe = await request$4(client, {
9972
+ const probe = await request$5(client, {
9461
9973
  name: definition.name,
9462
9974
  contentHash,
9463
9975
  ...passthrough
9464
9976
  });
9465
9977
  if (probe.result !== "definitionRequired") {
9466
9978
  memoize$4(memo, memoKey, probe);
9467
- return convergeInlineEvals(client, definition, probe);
9979
+ return convergeInlineEvals(client, definition, probe, onConflict);
9468
9980
  }
9469
- const converged = await request$4(client, {
9981
+ const converged = await request$5(client, {
9470
9982
  name: definition.name,
9471
9983
  definition: wireDefinition,
9472
9984
  ...passthrough
9473
9985
  });
9474
9986
  if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
9475
9987
  memoize$4(memo, memoKey, converged);
9476
- return convergeInlineEvals(client, definition, converged);
9988
+ return convergeInlineEvals(client, definition, converged, onConflict);
9477
9989
  }
9478
9990
  /**
9479
9991
  * After the flow itself converges on the real (non-plan) path, converge each
@@ -9486,10 +9998,10 @@ async function ensureFlow(client, definition, options = {}) {
9486
9998
  * unchanged (no empty `evals` array). The server's `/flows/ensure` surface is
9487
9999
  * never touched by eval semantics — they ride the eval endpoints only.
9488
10000
  */
9489
- async function convergeInlineEvals(client, definition, result) {
10001
+ async function convergeInlineEvals(client, definition, result, onConflict) {
9490
10002
  if (result.result === "plan" || !definition.evals?.length) return result;
9491
10003
  const evals = [];
9492
- for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef));
10004
+ for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef, onConflict ? { onConflict } : {}));
9493
10005
  return {
9494
10006
  ...result,
9495
10007
  evals
@@ -10220,9 +10732,11 @@ var RuntypeFlowBuilder = class {
10220
10732
  }
10221
10733
  };
10222
10734
  let currentResponse;
10735
+ let carriedRelease = {};
10223
10736
  if (isStreaming) currentResponse = await client.dispatch(config);
10224
10737
  else {
10225
10738
  const data = await client.dispatchJson(config);
10739
+ carriedRelease = releaseFieldsOf(data);
10226
10740
  currentResponse = new Response(JSON.stringify(data), { headers: { "content-type": "application/json" } });
10227
10741
  }
10228
10742
  while (true) {
@@ -10257,7 +10771,11 @@ var RuntypeFlowBuilder = class {
10257
10771
  }).catch(() => null));
10258
10772
  else {
10259
10773
  const data = await client.post("/dispatch/resume", resumeData);
10260
- const envelope = isDetachedToolOutputAck(data) ? await followDetachedResume(client, data.executionId, pausedResult.resumeCursor) : data;
10774
+ carriedRelease = {
10775
+ ...carriedRelease,
10776
+ ...releaseFieldsOf(data)
10777
+ };
10778
+ const envelope = isDetachedToolOutputAck(data) ? await followDetachedResume(client, data, pausedResult.resumeCursor, carriedRelease) : data;
10261
10779
  currentResponse = new Response(JSON.stringify(envelope), { headers: { "content-type": "application/json" } });
10262
10780
  }
10263
10781
  } catch (error) {
@@ -10719,24 +11237,8 @@ var EvalsNamespace = class {
10719
11237
  run(config) {
10720
11238
  return new EvalRunner(this.getClient, config);
10721
11239
  }
10722
- /**
10723
- * Idempotently converge a `defineEval` suite definition onto the platform —
10724
- * the deploy-time, non-executing converge for code-colocated evals. Hash-first:
10725
- * the steady state is one tiny probe request. Upserts the eval suite + replaces
10726
- * its cases; never runs the eval.
10727
- *
10728
- * @example
10729
- * ```typescript
10730
- * const suite = defineEval({
10731
- * target: { flow: 'support-triage' },
10732
- * graders: [noError()],
10733
- * cases: [{ name: 'billing', input: { variables: { message: 'I was double charged' } }, expect: [contains('finance')] }],
10734
- * })
10735
- * const result = await Runtype.evals.ensure(suite)
10736
- * ```
10737
- */
10738
- async ensure(definition) {
10739
- return ensureEval(this.getClient(), definition);
11240
+ async ensure(definition, options = {}) {
11241
+ return ensureEval(this.getClient(), definition, options);
10740
11242
  }
10741
11243
  /**
10742
11244
  * Pull the canonical definition + provenance for an eval suite by name — the
@@ -11003,7 +11505,7 @@ var SkillDriftError = class extends Error {
11003
11505
  * Best-effort extraction of `{ status, body }` from the generic request error
11004
11506
  * thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11005
11507
  */
11006
- function parseRequestError$4(err) {
11508
+ function parseRequestError$5(err) {
11007
11509
  if (!(err instanceof Error)) return {
11008
11510
  status: null,
11009
11511
  body: null
@@ -11025,8 +11527,8 @@ function parseRequestError$4(err) {
11025
11527
  };
11026
11528
  }
11027
11529
  }
11028
- function toConflictError$4(err) {
11029
- const { status, body } = parseRequestError$4(err);
11530
+ function toConflictError$5(err) {
11531
+ const { status, body } = parseRequestError$5(err);
11030
11532
  if (status !== 409 || !isPlainObject$1(body)) return null;
11031
11533
  const code = body.code;
11032
11534
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11038,23 +11540,23 @@ function toConflictError$4(err) {
11038
11540
  * within the process lifetime invalidates a stale server hash (which would
11039
11541
  * false-match and silently skip the update). Same scheme as flows/tools.ensure.
11040
11542
  */
11041
- const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
11042
- function memoFor$4(client) {
11043
- let memo = serverHashMemo$4.get(client);
11543
+ const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
11544
+ function memoFor$5(client) {
11545
+ let memo = serverHashMemo$5.get(client);
11044
11546
  if (!memo) {
11045
11547
  memo = /* @__PURE__ */ new Map();
11046
- serverHashMemo$4.set(client, memo);
11548
+ serverHashMemo$5.set(client, memo);
11047
11549
  }
11048
11550
  return memo;
11049
11551
  }
11050
11552
  function memoize$3(memo, memoKey, result) {
11051
11553
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
11052
11554
  }
11053
- async function request$3(client, body) {
11555
+ async function request$4(client, body) {
11054
11556
  try {
11055
11557
  return await client.post("/skills/ensure", body);
11056
11558
  } catch (err) {
11057
- const conflict = toConflictError$4(err);
11559
+ const conflict = toConflictError$5(err);
11058
11560
  if (conflict) throw conflict;
11059
11561
  throw err;
11060
11562
  }
@@ -11066,14 +11568,15 @@ async function request$3(client, body) {
11066
11568
  * on every change; never deletes.
11067
11569
  */
11068
11570
  async function ensureSkill(client, definition, options = {}) {
11069
- const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
11571
+ const { dryRun, onConflict, release, expectedRemoteHash, version, expectNoChanges } = options;
11070
11572
  const passthrough = {
11071
11573
  ...onConflict ? { onConflict } : {},
11072
11574
  ...release ? { release } : {},
11073
- ...expectedRemoteHash ? { expectedRemoteHash } : {}
11575
+ ...expectedRemoteHash ? { expectedRemoteHash } : {},
11576
+ ...version ? { version } : {}
11074
11577
  };
11075
11578
  if (dryRun || expectNoChanges) {
11076
- const plan = await request$3(client, {
11579
+ const plan = await request$4(client, {
11077
11580
  name: definition.name,
11078
11581
  definition: manifestToWire(definition),
11079
11582
  dryRun: true,
@@ -11083,11 +11586,11 @@ async function ensureSkill(client, definition, options = {}) {
11083
11586
  if (expectNoChanges && plan.changes !== "none") throw new SkillDriftError(plan);
11084
11587
  return plan;
11085
11588
  }
11086
- const memo = memoFor$4(client);
11589
+ const memo = memoFor$5(client);
11087
11590
  const localHash = await computeSkillContentHash(definition);
11088
11591
  const memoKey = `${definition.name} ${localHash}`;
11089
11592
  const contentHash = memo.get(memoKey) ?? localHash;
11090
- const probe = await request$3(client, {
11593
+ const probe = await request$4(client, {
11091
11594
  name: definition.name,
11092
11595
  contentHash,
11093
11596
  ...passthrough
@@ -11096,7 +11599,7 @@ async function ensureSkill(client, definition, options = {}) {
11096
11599
  memoize$3(memo, memoKey, probe);
11097
11600
  return probe;
11098
11601
  }
11099
- const converged = await request$3(client, {
11602
+ const converged = await request$4(client, {
11100
11603
  name: definition.name,
11101
11604
  definition: manifestToWire(definition),
11102
11605
  ...passthrough
@@ -11433,6 +11936,8 @@ const DEFINE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
11433
11936
  "icon",
11434
11937
  ...AGENT_CONFIG_KEYS
11435
11938
  ]);
11939
+ /** A minted tool typeid; `alwaysLoaded` also holds tool names, which may begin with `tool_`. */
11940
+ const TOOL_TYPEID_PATTERN = /^tool_[0-7][0-9a-hjkmnp-tv-z]{25}$/;
11436
11941
  /**
11437
11942
  * Collect account-scoped `tool_…` references (inlined copy of
11438
11943
  * `collectNonPortableAgentToolRefs` in @runtypelabs/shared). The v1 ensure
@@ -11459,6 +11964,9 @@ function collectNonPortableToolRefs(config) {
11459
11964
  if (isPlainObject(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
11460
11965
  if (isPlainObject(tools.subagentConfig)) scanArray(tools.subagentConfig.toolPool, "tools.subagentConfig.toolPool");
11461
11966
  if (isPlainObject(tools.codeModeConfig)) scanArray(tools.codeModeConfig.toolPool, "tools.codeModeConfig.toolPool");
11967
+ if (isPlainObject(tools.toolSearch) && Array.isArray(tools.toolSearch.alwaysLoaded)) tools.toolSearch.alwaysLoaded.forEach((ref, i) => {
11968
+ if (typeof ref === "string" && TOOL_TYPEID_PATTERN.test(ref)) found.push(`tools.toolSearch.alwaysLoaded[${i}]`);
11969
+ });
11462
11970
  if (Array.isArray(tools.runtimeTools)) tools.runtimeTools.forEach((runtimeTool, i) => {
11463
11971
  if (!isPlainObject(runtimeTool) || !isPlainObject(runtimeTool.config)) return;
11464
11972
  const base = `tools.runtimeTools[${i}].config`;
@@ -11507,6 +12015,18 @@ function defineAgent(input) {
11507
12015
  config
11508
12016
  };
11509
12017
  }
12018
+ function planHasSkillBindingChanges(plan) {
12019
+ return (plan.skillBindings ?? []).some((binding) => binding.action !== "unchanged");
12020
+ }
12021
+ /** Whether a dry run that asked for a deploy would move the alias or change its binding names. */
12022
+ function planHasDeploymentChanges(plan) {
12023
+ return plan.deployment?.changed === true || plan.deployment?.bindingsChanged === true;
12024
+ }
12025
+ function deploymentChangeLabels(plan) {
12026
+ const deployment = plan.deployment;
12027
+ if (!deployment) return [];
12028
+ return [...deployment.changed ? [`deploy:${deployment.alias} (pointer)`] : [], ...deployment.bindingsChanged ? [`deploy:${deployment.alias} (bindings)`] : []];
12029
+ }
11510
12030
  /** A 409 from the ensure protocol (external_modification or remote_changed). */
11511
12031
  var AgentEnsureConflictError = class extends Error {
11512
12032
  constructor(body) {
@@ -11521,7 +12041,12 @@ var AgentEnsureConflictError = class extends Error {
11521
12041
  /** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
11522
12042
  var AgentDriftError = class extends Error {
11523
12043
  constructor(plan) {
11524
- 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.`);
12044
+ const skillChanges = (plan.skillBindings ?? []).filter((binding) => binding.action !== "unchanged").map((binding) => `skill:${binding.name} (${binding.action})`);
12045
+ super(`Agent "${plan.agentId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[
12046
+ ...plan.changedKeys,
12047
+ ...skillChanges,
12048
+ ...deploymentChangeLabels(plan)
12049
+ ].join(", ") || "n/a"}). Run client.agents.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
11525
12050
  this.name = "AgentDriftError";
11526
12051
  this.plan = plan;
11527
12052
  }
@@ -11530,7 +12055,7 @@ var AgentDriftError = class extends Error {
11530
12055
  * Best-effort extraction of `{ status, body }` from the generic request
11531
12056
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11532
12057
  */
11533
- function parseRequestError$3(err) {
12058
+ function parseRequestError$4(err) {
11534
12059
  if (!(err instanceof Error)) return {
11535
12060
  status: null,
11536
12061
  body: null
@@ -11552,8 +12077,8 @@ function parseRequestError$3(err) {
11552
12077
  };
11553
12078
  }
11554
12079
  }
11555
- function toConflictError$3(err) {
11556
- const { status, body } = parseRequestError$3(err);
12080
+ function toConflictError$4(err) {
12081
+ const { status, body } = parseRequestError$4(err);
11557
12082
  if (status !== 409 || !isPlainObject(body)) return null;
11558
12083
  const code = body.code;
11559
12084
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11569,12 +12094,12 @@ function toConflictError$3(err) {
11569
12094
  * so a stale server hash from the previous definition version can never be
11570
12095
  * probed (which would false-match and silently skip the update).
11571
12096
  */
11572
- const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
11573
- function memoFor$3(client) {
11574
- let memo = serverHashMemo$3.get(client);
12097
+ const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
12098
+ function memoFor$4(client) {
12099
+ let memo = serverHashMemo$4.get(client);
11575
12100
  if (!memo) {
11576
12101
  memo = /* @__PURE__ */ new Map();
11577
- serverHashMemo$3.set(client, memo);
12102
+ serverHashMemo$4.set(client, memo);
11578
12103
  }
11579
12104
  return memo;
11580
12105
  }
@@ -11610,14 +12135,16 @@ var AgentsNamespace = class {
11610
12135
  */
11611
12136
  async ensure(definition, options = {}) {
11612
12137
  const client = this.getClient();
11613
- const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges } = options;
12138
+ const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges, skills, capabilities } = options;
11614
12139
  if (release !== void 0 && deploy !== void 0) throw new Error(ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE);
11615
12140
  const passthrough = {
11616
12141
  ...onConflict ? { onConflict } : {},
11617
12142
  ...release ? { release } : {},
11618
12143
  ...deploy ? { deploy } : {},
11619
12144
  ...expectedRemoteHash ? { expectedRemoteHash } : {},
11620
- ...version ? { version } : {}
12145
+ ...version ? { version } : {},
12146
+ ...skills ? { skills } : {},
12147
+ ...capabilities ? { capabilities } : {}
11621
12148
  };
11622
12149
  if (dryRun || expectNoChanges) {
11623
12150
  const plan = await this.request(client, {
@@ -11627,15 +12154,16 @@ var AgentsNamespace = class {
11627
12154
  ...passthrough
11628
12155
  });
11629
12156
  if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
11630
- if (expectNoChanges && plan.changes !== "none") throw new AgentDriftError(plan);
12157
+ const deployRequested = release === "publish" || deploy !== void 0;
12158
+ if (expectNoChanges && (plan.changes !== "none" || planHasSkillBindingChanges(plan) || deployRequested && planHasDeploymentChanges(plan))) throw new AgentDriftError(plan);
11631
12159
  return plan;
11632
12160
  }
11633
- const memo = memoFor$3(client);
12161
+ const memo = memoFor$4(client);
11634
12162
  const localHash = await computeAgentContentHash({
11635
12163
  ...definition,
11636
12164
  config: definition.config
11637
12165
  });
11638
- const memoKey = `${definition.name}\u0000${localHash}`;
12166
+ const memoKey = `${definition.name}\u0000${localHash}${capabilities ? `\u0000${JSON.stringify(capabilities)}` : ""}`;
11639
12167
  const contentHash = memo.get(memoKey) ?? localHash;
11640
12168
  const probe = await this.request(client, {
11641
12169
  name: definition.name,
@@ -11669,7 +12197,7 @@ var AgentsNamespace = class {
11669
12197
  try {
11670
12198
  return await client.post("/agents/ensure", body);
11671
12199
  } catch (err) {
11672
- const conflict = toConflictError$3(err);
12200
+ const conflict = toConflictError$4(err);
11673
12201
  if (conflict) throw conflict;
11674
12202
  throw err;
11675
12203
  }
@@ -11798,7 +12326,7 @@ var ToolDriftError = class extends Error {
11798
12326
  * Best-effort extraction of `{ status, body }` from the generic request
11799
12327
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
11800
12328
  */
11801
- function parseRequestError$2(err) {
12329
+ function parseRequestError$3(err) {
11802
12330
  if (!(err instanceof Error)) return {
11803
12331
  status: null,
11804
12332
  body: null
@@ -11820,8 +12348,8 @@ function parseRequestError$2(err) {
11820
12348
  };
11821
12349
  }
11822
12350
  }
11823
- function toConflictError$2(err) {
11824
- const { status, body } = parseRequestError$2(err);
12351
+ function toConflictError$3(err) {
12352
+ const { status, body } = parseRequestError$3(err);
11825
12353
  if (status !== 409 || !isPlainObject$1(body)) return null;
11826
12354
  const code = body.code;
11827
12355
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -11833,23 +12361,23 @@ function toConflictError$2(err) {
11833
12361
  * within the process lifetime invalidates a stale server hash (which would
11834
12362
  * false-match and silently skip the update). Same scheme as flows.ensure.
11835
12363
  */
11836
- const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
11837
- function memoFor$2(client) {
11838
- let memo = serverHashMemo$2.get(client);
12364
+ const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
12365
+ function memoFor$3(client) {
12366
+ let memo = serverHashMemo$3.get(client);
11839
12367
  if (!memo) {
11840
12368
  memo = /* @__PURE__ */ new Map();
11841
- serverHashMemo$2.set(client, memo);
12369
+ serverHashMemo$3.set(client, memo);
11842
12370
  }
11843
12371
  return memo;
11844
12372
  }
11845
12373
  function memoize$2(memo, memoKey, result) {
11846
12374
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
11847
12375
  }
11848
- async function request$2(client, body) {
12376
+ async function request$3(client, body) {
11849
12377
  try {
11850
12378
  return await client.post("/tools/ensure", body);
11851
12379
  } catch (err) {
11852
- const conflict = toConflictError$2(err);
12380
+ const conflict = toConflictError$3(err);
11853
12381
  if (conflict) throw conflict;
11854
12382
  throw err;
11855
12383
  }
@@ -11866,7 +12394,7 @@ async function ensureTool(client, definition, options = {}) {
11866
12394
  ...expectedRemoteHash ? { expectedRemoteHash } : {}
11867
12395
  };
11868
12396
  if (dryRun || expectNoChanges) {
11869
- const plan = await request$2(client, {
12397
+ const plan = await request$3(client, {
11870
12398
  name: definition.name,
11871
12399
  definition,
11872
12400
  dryRun: true,
@@ -11876,11 +12404,11 @@ async function ensureTool(client, definition, options = {}) {
11876
12404
  if (expectNoChanges && plan.changes !== "none") throw new ToolDriftError(plan);
11877
12405
  return plan;
11878
12406
  }
11879
- const memo = memoFor$2(client);
12407
+ const memo = memoFor$3(client);
11880
12408
  const localHash = await computeToolContentHash(definition);
11881
12409
  const memoKey = `${definition.name} ${localHash}`;
11882
12410
  const contentHash = memo.get(memoKey) ?? localHash;
11883
- const probe = await request$2(client, {
12411
+ const probe = await request$3(client, {
11884
12412
  name: definition.name,
11885
12413
  contentHash,
11886
12414
  ...passthrough
@@ -11889,7 +12417,7 @@ async function ensureTool(client, definition, options = {}) {
11889
12417
  memoize$2(memo, memoKey, probe);
11890
12418
  return probe;
11891
12419
  }
11892
- const converged = await request$2(client, {
12420
+ const converged = await request$3(client, {
11893
12421
  name: definition.name,
11894
12422
  definition,
11895
12423
  ...passthrough
@@ -11945,10 +12473,197 @@ var ToolsNamespace = class {
11945
12473
  }
11946
12474
  };
11947
12475
  //#endregion
12476
+ //#region src/question-sets-ensure.ts
12477
+ /** Canonical form of a question set definition, without its slug. */
12478
+ function normalizeQuestionSetDefinition(definition) {
12479
+ return normalizeValue$1({
12480
+ name: definition.name,
12481
+ description: definition.description,
12482
+ questions: isPlainObject$1(definition.questions) ? definition.questions : {},
12483
+ inputMapping: definition.inputMapping,
12484
+ model: definition.model
12485
+ });
12486
+ }
12487
+ /** SHA-256 (hex) over the canonical question set definition. */
12488
+ async function computeQuestionSetContentHash(definition) {
12489
+ return sha256Hex(JSON.stringify(normalizeQuestionSetDefinition(definition)));
12490
+ }
12491
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
12492
+ const DEFINE_QUESTION_SET_KEYS = /* @__PURE__ */ new Set([
12493
+ "slug",
12494
+ "name",
12495
+ "description",
12496
+ "questions",
12497
+ "inputMapping",
12498
+ "model"
12499
+ ]);
12500
+ /**
12501
+ * Pure local constructor for a question set definition. Checks the structure
12502
+ * and rejects unknown fields; the server validates the questions at ensure time.
12503
+ *
12504
+ * @example
12505
+ * ```typescript
12506
+ * const triage = defineQuestionSet({
12507
+ * slug: 'ticket-triage',
12508
+ * name: 'Ticket triage',
12509
+ * questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
12510
+ * })
12511
+ * ```
12512
+ */
12513
+ function defineQuestionSet(input) {
12514
+ if (!input || typeof input !== "object") throw new Error("defineQuestionSet requires a definition object");
12515
+ 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");
12516
+ if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineQuestionSet requires a non-empty string \"name\"");
12517
+ if (!isPlainObject$1(input.questions) || Object.keys(input.questions).length === 0) throw new Error("defineQuestionSet requires a non-empty \"questions\" object");
12518
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_QUESTION_SET_KEYS.has(key));
12519
+ if (unknownKeys.length > 0) throw new Error(`defineQuestionSet: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are slug, name, description, questions, inputMapping and model.`);
12520
+ return { ...input };
12521
+ }
12522
+ /** A 409 from the ensure protocol (external_modification or remote_changed). */
12523
+ var QuestionSetEnsureConflictError = class extends Error {
12524
+ constructor(body) {
12525
+ super(body.error ?? `Question set ensure conflict: ${body.code}`);
12526
+ this.name = "QuestionSetEnsureConflictError";
12527
+ this.code = body.code;
12528
+ this.lastModifiedSource = body.lastModifiedSource;
12529
+ this.modifiedAt = body.modifiedAt;
12530
+ this.currentHash = body.currentHash;
12531
+ }
12532
+ };
12533
+ /** Thrown by `ensure(def, { expectNoChanges: true })` when the remote differs. */
12534
+ var QuestionSetDriftError = class extends Error {
12535
+ constructor(slug, plan) {
12536
+ 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.`);
12537
+ this.name = "QuestionSetDriftError";
12538
+ this.plan = plan;
12539
+ }
12540
+ };
12541
+ function toConflictError$2(err) {
12542
+ if (!(err instanceof Error)) return null;
12543
+ const match = err.message.match(/^API request failed: (409) .*? - ([\s\S]*)$/);
12544
+ if (!match) return null;
12545
+ let body;
12546
+ try {
12547
+ body = JSON.parse(match[2]);
12548
+ } catch {
12549
+ return null;
12550
+ }
12551
+ if (!isPlainObject$1(body)) return null;
12552
+ if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
12553
+ return new QuestionSetEnsureConflictError(body);
12554
+ }
12555
+ const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
12556
+ function memoFor$2(client) {
12557
+ let memo = serverHashMemo$2.get(client);
12558
+ if (!memo) {
12559
+ memo = /* @__PURE__ */ new Map();
12560
+ serverHashMemo$2.set(client, memo);
12561
+ }
12562
+ return memo;
12563
+ }
12564
+ async function request$2(client, body) {
12565
+ try {
12566
+ return await client.post("/question-sets/ensure", body);
12567
+ } catch (err) {
12568
+ throw toConflictError$2(err) ?? err;
12569
+ }
12570
+ }
12571
+ function toWireDefinition(definition) {
12572
+ const { slug: _slug, ...rest } = definition;
12573
+ return rest;
12574
+ }
12575
+ /**
12576
+ * Idempotently converge a question set onto the platform. Probes with a
12577
+ * content hash first and sends the full definition only on a miss. Appends a
12578
+ * version when the questions, input mapping or model change; never deletes.
12579
+ */
12580
+ async function ensureQuestionSet(client, definition, options = {}) {
12581
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
12582
+ const passthrough = {
12583
+ ...onConflict ? { onConflict } : {},
12584
+ ...release ? { release } : {},
12585
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
12586
+ };
12587
+ if (dryRun || expectNoChanges) {
12588
+ const plan = await request$2(client, {
12589
+ slug: definition.slug,
12590
+ definition: toWireDefinition(definition),
12591
+ dryRun: true,
12592
+ ...passthrough
12593
+ });
12594
+ if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
12595
+ if (expectNoChanges && plan.changes !== "none") throw new QuestionSetDriftError(definition.slug, plan);
12596
+ return plan;
12597
+ }
12598
+ const memo = memoFor$2(client);
12599
+ const localHash = await computeQuestionSetContentHash(definition);
12600
+ const memoKey = `${definition.slug} ${localHash}`;
12601
+ const probe = await request$2(client, {
12602
+ slug: definition.slug,
12603
+ contentHash: memo.get(memoKey) ?? localHash,
12604
+ ...passthrough
12605
+ });
12606
+ if (probe.result !== "definitionRequired") {
12607
+ if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
12608
+ return probe;
12609
+ }
12610
+ const converged = await request$2(client, {
12611
+ slug: definition.slug,
12612
+ definition: toWireDefinition(definition),
12613
+ ...passthrough
12614
+ });
12615
+ if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
12616
+ if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
12617
+ return converged;
12618
+ }
12619
+ /** Pull the canonical definition and provenance for a question set by slug. */
12620
+ async function pullQuestionSet(client, slug) {
12621
+ return client.get("/question-sets/pull", { slug });
12622
+ }
12623
+ //#endregion
12624
+ //#region src/question-sets-namespace.ts
12625
+ /** Question set config-as-code: `ensure` and `pull`. */
12626
+ var QuestionSetsNamespace = class {
12627
+ constructor(getClient) {
12628
+ this.getClient = getClient;
12629
+ }
12630
+ /**
12631
+ * Converge a `defineQuestionSet` definition. The steady state is one hash
12632
+ * probe. Pass `release: 'publish'` so flows and agents can resolve the
12633
+ * latest version.
12634
+ *
12635
+ * @example
12636
+ * ```typescript
12637
+ * await Runtype.questionSets.ensure(triage, { release: 'publish' })
12638
+ * await Runtype.questionSets.ensure(triage, { expectNoChanges: true })
12639
+ * ```
12640
+ */
12641
+ async ensure(definition, options = {}) {
12642
+ return ensureQuestionSet(this.getClient(), definition, options);
12643
+ }
12644
+ /** Pull the live definition and provenance for a slug, to absorb a dashboard edit. */
12645
+ async pull(slug) {
12646
+ return pullQuestionSet(this.getClient(), slug);
12647
+ }
12648
+ };
12649
+ //#endregion
11948
12650
  //#region src/products-ensure.ts
12651
+ /**
12652
+ * `spec` with its stack as a save stores it: without the server-set fields (`detected`, `declared`,
12653
+ * `hiddenAt`), which their own write paths set, and with `hidden` deduplicated.
12654
+ */
12655
+ function asSavedStack(spec) {
12656
+ if (!isPlainObject$1(spec.stack)) return spec;
12657
+ const { detected: _detected, declared: _declared, hiddenAt: _hiddenAt, ...stack } = spec.stack;
12658
+ if (Array.isArray(stack.hidden)) stack.hidden = [...new Set(stack.hidden)];
12659
+ return {
12660
+ ...spec,
12661
+ stack
12662
+ };
12663
+ }
11949
12664
  /** Canonical normalized form of a product definition (name + canvas excluded). */
11950
12665
  function normalizeProductDefinition(definition) {
11951
- const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(definition.spec) : {};
12666
+ const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(asSavedStack(definition.spec)) : {};
11952
12667
  return {
11953
12668
  ...definition.description ? { description: definition.description } : {},
11954
12669
  ...definition.icon ? { icon: definition.icon } : {},
@@ -12018,7 +12733,7 @@ var ProductDriftError = class extends Error {
12018
12733
  * Best-effort extraction of `{ status, body }` from the generic request
12019
12734
  * error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
12020
12735
  */
12021
- function parseRequestError$1(err) {
12736
+ function parseRequestError$2(err) {
12022
12737
  if (!(err instanceof Error)) return {
12023
12738
  status: null,
12024
12739
  body: null
@@ -12041,7 +12756,7 @@ function parseRequestError$1(err) {
12041
12756
  }
12042
12757
  }
12043
12758
  function toConflictError$1(err) {
12044
- const { status, body } = parseRequestError$1(err);
12759
+ const { status, body } = parseRequestError$2(err);
12045
12760
  if (status !== 409 || !isPlainObject$1(body)) return null;
12046
12761
  const code = body.code;
12047
12762
  if (code !== "external_modification" && code !== "remote_changed") return null;
@@ -12163,20 +12878,85 @@ function defineFpo(fpo) {
12163
12878
  if (!product || typeof product.name !== "string" || product.name.length === 0) throw new Error("defineFpo requires a non-empty \"product.name\" (the converge identity)");
12164
12879
  return fpo;
12165
12880
  }
12881
+ const FPO_DRIFT_RESULTS = /* @__PURE__ */ new Set([
12882
+ "created",
12883
+ "updated",
12884
+ "pruned",
12885
+ "failed"
12886
+ ]);
12887
+ /** Thrown by `ensureFpo(fpo, { expectNoChanges: true })` when the plan is not clean. */
12888
+ var FpoDriftError = class extends Error {
12889
+ constructor(plan) {
12890
+ const drifted = plan.entities.filter((entity) => FPO_DRIFT_RESULTS.has(entity.result)).map((entity) => `${entity.kind}:${entity.name} (${entity.result})`);
12891
+ 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.`);
12892
+ this.name = "FpoDriftError";
12893
+ this.plan = plan;
12894
+ }
12895
+ };
12896
+ function fpoPlanHasDrift(plan) {
12897
+ return plan.entities.some((entity) => FPO_DRIFT_RESULTS.has(entity.result));
12898
+ }
12899
+ function parseRequestError$1(err) {
12900
+ if (!(err instanceof Error)) return {
12901
+ status: null,
12902
+ body: null
12903
+ };
12904
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
12905
+ if (!match) return {
12906
+ status: null,
12907
+ body: null
12908
+ };
12909
+ try {
12910
+ return {
12911
+ status: Number(match[1]),
12912
+ body: JSON.parse(match[2])
12913
+ };
12914
+ } catch {
12915
+ return {
12916
+ status: Number(match[1]),
12917
+ body: null
12918
+ };
12919
+ }
12920
+ }
12921
+ async function requestFpoEnsure(client, body) {
12922
+ try {
12923
+ return await client.post("/products/ensure-fpo", body);
12924
+ } catch (err) {
12925
+ const { status, body: errorBody } = parseRequestError$1(err);
12926
+ if (status === 409 && isPlainObject$1(errorBody) && (errorBody.code === "external_modification" || errorBody.code === "remote_changed")) throw new ProductEnsureConflictError(errorBody);
12927
+ throw err;
12928
+ }
12929
+ }
12166
12930
  /**
12167
- * Converge an entire FPO onto the platform. Ships the full FPO (no hash-only
12168
- * probe in this release); the server fans out to the per-entity ensure services
12169
- * and returns the whole-FPO hash + per-entity report. Non-atomic: inspect
12170
- * `result.hasFailures` / `entities[].result` and re-run to self-heal.
12931
+ * Converge an entire FPO onto the platform. Ships the full FPO unless a
12932
+ * drafts-only `hashProbe` finds the live graph already matches; the server fans out to the
12933
+ * per-entity ensure services and returns the whole-FPO hash + per-entity report.
12934
+ * Non-atomic: inspect `result.hasFailures` / `entities[].result` and re-run to self-heal.
12171
12935
  */
12172
12936
  async function ensureFpo(client, fpo, options = {}) {
12173
- const { dryRun, onConflict, prune } = options;
12174
- return client.post("/products/ensure-fpo", {
12937
+ const { dryRun, onConflict, prune, expectNoChanges, expectedRemoteHash, hashProbe, release } = options;
12938
+ const plan = dryRun || expectNoChanges;
12939
+ const guard = expectedRemoteHash ? { expectedRemoteHash } : {};
12940
+ if (hashProbe && release === "none" && !plan && !prune) {
12941
+ const probe = await requestFpoEnsure(client, {
12942
+ name: (isPlainObject$1(fpo.product) ? fpo.product : {}).name,
12943
+ contentHash: await computeFpoContentHash(fpo),
12944
+ ...guard,
12945
+ release
12946
+ });
12947
+ if (probe.result !== "definitionRequired") return probe;
12948
+ }
12949
+ const result = await requestFpoEnsure(client, {
12175
12950
  fpo,
12176
- ...dryRun ? { dryRun: true } : {},
12951
+ ...plan ? { dryRun: true } : {},
12177
12952
  ...onConflict ? { onConflict } : {},
12178
- ...prune ? { prune: true } : {}
12953
+ ...prune ? { prune: true } : {},
12954
+ ...guard,
12955
+ ...release ? { release } : {}
12179
12956
  });
12957
+ if (result.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-FPO request");
12958
+ if (expectNoChanges && fpoPlanHasDrift(result)) throw new FpoDriftError(result);
12959
+ return result;
12180
12960
  }
12181
12961
  /**
12182
12962
  * Reconstruct a self-contained FPO from the live product graph (resolved by
@@ -12351,8 +13131,8 @@ function defineSurface(input) {
12351
13131
  if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSurface requires a non-empty string \"name\"");
12352
13132
  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(", ")}`);
12353
13133
  if (input.behavior !== void 0 && !isPlainObject$1(input.behavior)) throw new Error("defineSurface \"behavior\" must be an object when provided");
12354
- if (input.inbound !== void 0 && !isPlainObject$1(input.inbound)) throw new Error("defineSurface \"inbound\" must be an object when provided");
12355
- if (input.outbound !== void 0 && !isPlainObject$1(input.outbound)) throw new Error("defineSurface \"outbound\" must be an object when provided");
13134
+ 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");
13135
+ 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");
12356
13136
  if (input.status !== void 0 && ![
12357
13137
  "draft",
12358
13138
  "active",
@@ -12474,14 +13254,16 @@ async function ensureSurface(client, productId, definition, options = {}) {
12474
13254
  const localHash = await computeSurfaceContentHash(definition);
12475
13255
  const memoKey = `${productId} ${definition.name} ${localHash}`;
12476
13256
  const contentHash = memo.get(memoKey) ?? localHash;
12477
- const probe = await request(client, productId, {
12478
- name: definition.name,
12479
- contentHash,
12480
- ...passthrough
12481
- });
12482
- if (probe.result !== "definitionRequired") {
12483
- memoize(memo, memoKey, probe);
12484
- return probe;
13257
+ if (!(definition.inbound !== void 0 || definition.outbound !== void 0)) {
13258
+ const probe = await request(client, productId, {
13259
+ name: definition.name,
13260
+ contentHash,
13261
+ ...passthrough
13262
+ });
13263
+ if (probe.result !== "definitionRequired") {
13264
+ memoize(memo, memoKey, probe);
13265
+ return probe;
13266
+ }
12485
13267
  }
12486
13268
  const converged = await request(client, productId, {
12487
13269
  name: definition.name,
@@ -12494,8 +13276,8 @@ async function ensureSurface(client, productId, definition, options = {}) {
12494
13276
  }
12495
13277
  /**
12496
13278
  * Pull the canonical definition + provenance for a surface by name within a
12497
- * product — the absorb-drift direction. The contentHash reflects the live
12498
- * surface state.
13279
+ * product — the absorb-drift direction. Configured credentials come back as
13280
+ * `'<redacted>'`, which `ensure` accepts to keep the stored value.
12499
13281
  */
12500
13282
  async function pullSurface(client, productId, name) {
12501
13283
  return client.get(`/products/${encodeURIComponent(productId)}/surfaces/pull`, { name });
@@ -13210,6 +13992,25 @@ var Runtype = class {
13210
13992
  return new ToolsNamespace(() => this.getClient());
13211
13993
  }
13212
13994
  /**
13995
+ * Question sets namespace - Question set config-as-code (define / ensure / pull)
13996
+ *
13997
+ * @example
13998
+ * ```typescript
13999
+ * import { defineQuestionSet, Runtype } from '@runtypelabs/sdk'
14000
+ *
14001
+ * const triage = defineQuestionSet({
14002
+ * slug: 'ticket-triage',
14003
+ * name: 'Ticket triage',
14004
+ * questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
14005
+ * })
14006
+ *
14007
+ * await Runtype.questionSets.ensure(triage, { release: 'publish' })
14008
+ * ```
14009
+ */
14010
+ static get questionSets() {
14011
+ return new QuestionSetsNamespace(() => this.getClient());
14012
+ }
14013
+ /**
13213
14014
  * Products namespace - Product config-as-code (define / ensure / pull)
13214
14015
  *
13215
14016
  * Converges the top-level product record (description, icon, spec). Nested
@@ -13498,7 +14299,7 @@ function transformQueryParams(params) {
13498
14299
  }
13499
14300
  //#endregion
13500
14301
  //#region src/version.ts
13501
- const SDK_VERSION = "10.8.0";
14302
+ const SDK_VERSION = "10.9.0";
13502
14303
  /**
13503
14304
  * Canonical client identity the API uses for attribution
13504
14305
  * (`detectActorSource` in apps/api/src/lib/audit-log.ts keys `'sdk'` off the
@@ -13709,9 +14510,11 @@ var RuntypeClient = class {
13709
14510
  }
13710
14511
  };
13711
14512
  let currentResponse;
14513
+ let carriedRelease = {};
13712
14514
  if (isStreaming) currentResponse = await this.dispatch.executeStream(modifiedRequest);
13713
14515
  else {
13714
14516
  const data = await this.dispatch.execute(modifiedRequest);
14517
+ carriedRelease = releaseFieldsOf(data);
13715
14518
  currentResponse = new Response(JSON.stringify(data), { headers: { "content-type": "application/json" } });
13716
14519
  }
13717
14520
  while (true) {
@@ -13745,7 +14548,11 @@ var RuntypeClient = class {
13745
14548
  if (isStreaming) currentResponse = await this.dispatch.resume(resumeData);
13746
14549
  else {
13747
14550
  const data = await this.dispatch.resume(resumeData);
13748
- const envelope = isDetachedToolOutputAck(data) ? await followDetachedResume(this, data.executionId, result.resumeCursor) : data;
14551
+ carriedRelease = {
14552
+ ...carriedRelease,
14553
+ ...releaseFieldsOf(data)
14554
+ };
14555
+ const envelope = isDetachedToolOutputAck(data) ? await followDetachedResume(this, data, result.resumeCursor, carriedRelease) : data;
13749
14556
  currentResponse = new Response(JSON.stringify(envelope), { headers: { "content-type": "application/json" } });
13750
14557
  }
13751
14558
  } catch (error) {
@@ -15708,6 +16515,8 @@ exports.ClientTokenDriftError = ClientTokenDriftError;
15708
16515
  exports.ClientTokenEnsureError = ClientTokenEnsureError;
15709
16516
  exports.ClientTokensEndpoint = ClientTokensEndpoint;
15710
16517
  exports.ClientTokensNamespace = ClientTokensNamespace;
16518
+ exports.CollectionDriftError = CollectionDriftError;
16519
+ exports.CollectionEnsureConflictError = CollectionEnsureConflictError;
15711
16520
  exports.CollectionsEndpoint = CollectionsEndpoint;
15712
16521
  exports.ContextTemplatesEndpoint = ContextTemplatesEndpoint;
15713
16522
  exports.ConversationsEndpoint = ConversationsEndpoint;
@@ -15717,7 +16526,9 @@ exports.DEFAULT_STALL_STOP_AFTER = DEFAULT_STALL_STOP_AFTER;
15717
16526
  exports.DispatchEndpoint = DispatchEndpoint;
15718
16527
  exports.ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE = ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE;
15719
16528
  exports.EvalBuilder = EvalBuilder;
16529
+ exports.EvalDriftError = EvalDriftError;
15720
16530
  exports.EvalEndpoint = EvalEndpoint;
16531
+ exports.EvalEnsureConflictError = EvalEnsureConflictError;
15721
16532
  exports.EvalRunner = EvalRunner;
15722
16533
  exports.EvalSuitesNamespace = EvalSuitesNamespace;
15723
16534
  exports.EvalsNamespace = EvalsNamespace;
@@ -15731,6 +16542,7 @@ exports.FlowStepsEndpoint = FlowStepsEndpoint;
15731
16542
  exports.FlowVersionsEndpoint = FlowVersionsEndpoint;
15732
16543
  exports.FlowsEndpoint = FlowsEndpoint;
15733
16544
  exports.FlowsNamespace = FlowsNamespace;
16545
+ exports.FpoDriftError = FpoDriftError;
15734
16546
  exports.IntegrationsEndpoint = IntegrationsEndpoint;
15735
16547
  exports.LEDGER_ARTIFACT_LINE_PREFIX = LEDGER_ARTIFACT_LINE_PREFIX;
15736
16548
  exports.LIVE_AGENT_ALIAS = LIVE_AGENT_ALIAS;
@@ -15742,6 +16554,9 @@ exports.ProductsNamespace = ProductsNamespace;
15742
16554
  exports.PromptsEndpoint = PromptsEndpoint;
15743
16555
  exports.PromptsNamespace = PromptsNamespace;
15744
16556
  exports.ProviderKeysEndpoint = ProviderKeysEndpoint;
16557
+ exports.QuestionSetDriftError = QuestionSetDriftError;
16558
+ exports.QuestionSetEnsureConflictError = QuestionSetEnsureConflictError;
16559
+ exports.QuestionSetsNamespace = QuestionSetsNamespace;
15745
16560
  exports.RUNTYPE_CLIENT_KIND = RUNTYPE_CLIENT_KIND;
15746
16561
  exports.RecordsEndpoint = RecordsEndpoint;
15747
16562
  exports.RunsNamespace = RunsNamespace;
@@ -15753,6 +16568,8 @@ exports.SDK_USER_AGENT = SDK_USER_AGENT;
15753
16568
  exports.SDK_VERSION = SDK_VERSION;
15754
16569
  exports.STEP_FIELD_REGISTRY = STEP_FIELD_REGISTRY;
15755
16570
  exports.STEP_TYPE_TO_METHOD = STEP_TYPE_TO_METHOD;
16571
+ exports.ScheduleDriftError = ScheduleDriftError;
16572
+ exports.ScheduleEnsureConflictError = ScheduleEnsureConflictError;
15756
16573
  exports.SchedulesEndpoint = SchedulesEndpoint;
15757
16574
  exports.SecretsEndpoint = SecretsEndpoint;
15758
16575
  exports.SkillDriftError = SkillDriftError;
@@ -15792,6 +16609,10 @@ exports.computeEvalContentHash = computeEvalContentHash;
15792
16609
  exports.computeFlowContentHash = computeFlowContentHash;
15793
16610
  exports.computeFpoContentHash = computeFpoContentHash;
15794
16611
  exports.computeProductContentHash = computeProductContentHash;
16612
+ exports.computeQuestionSetContentHash = computeQuestionSetContentHash;
16613
+ exports.computeRecordCollectionContentHash = computeRecordCollectionContentHash;
16614
+ exports.computeScheduleContentHash = computeScheduleContentHash;
16615
+ exports.computeScheduleDefinitionHash = computeScheduleDefinitionHash;
15795
16616
  exports.computeSkillContentHash = computeSkillContentHash;
15796
16617
  exports.computeSurfaceContentHash = computeSurfaceContentHash;
15797
16618
  exports.computeToolContentHash = computeToolContentHash;
@@ -15809,6 +16630,8 @@ exports.defineFlow = defineFlow;
15809
16630
  exports.defineFpo = defineFpo;
15810
16631
  exports.definePlaybook = definePlaybook;
15811
16632
  exports.defineProduct = defineProduct;
16633
+ exports.defineQuestionSet = defineQuestionSet;
16634
+ exports.defineSchedule = defineSchedule;
15812
16635
  exports.defineSkill = defineSkill;
15813
16636
  exports.defineSurface = defineSurface;
15814
16637
  exports.defineTool = defineTool;
@@ -15816,8 +16639,10 @@ exports.deployWorkflow = deployWorkflow;
15816
16639
  exports.ensureDefaultWorkflowHooks = ensureDefaultWorkflowHooks;
15817
16640
  exports.ensureEval = ensureEval;
15818
16641
  exports.ensureFpo = ensureFpo;
16642
+ exports.ensureSchedule = ensureSchedule;
15819
16643
  exports.evaluateGeneratedRuntimeToolProposal = evaluateGeneratedRuntimeToolProposal;
15820
16644
  exports.extractDeclaredToolResultChars = extractDeclaredToolResultChars;
16645
+ exports.fpoPlanHasDrift = fpoPlanHasDrift;
15821
16646
  exports.gameWorkflow = gameWorkflow;
15822
16647
  exports.getDefaultPlanPath = getDefaultPlanPath;
15823
16648
  exports.getLikelySupportingCandidatePaths = getLikelySupportingCandidatePaths;
@@ -15842,6 +16667,9 @@ exports.normalizeAgentDefinition = normalizeAgentDefinition;
15842
16667
  exports.normalizeCandidatePath = normalizeCandidatePath;
15843
16668
  exports.normalizeFpoDefinition = normalizeFpoDefinition;
15844
16669
  exports.normalizeProductDefinition = normalizeProductDefinition;
16670
+ exports.normalizeQuestionSetDefinition = normalizeQuestionSetDefinition;
16671
+ exports.normalizeRecordCollectionDefinition = normalizeRecordCollectionDefinition;
16672
+ exports.normalizeScheduleContent = normalizeScheduleContent;
15845
16673
  exports.normalizeSkillDefinition = normalizeSkillDefinition;
15846
16674
  exports.normalizeSurfaceDefinition = normalizeSurfaceDefinition;
15847
16675
  exports.normalizeToolDefinition = normalizeToolDefinition;
@@ -15857,6 +16685,7 @@ exports.promoteAgent = promoteAgent;
15857
16685
  exports.promotionIdempotencyKey = promotionIdempotencyKey;
15858
16686
  exports.pullEval = pullEval;
15859
16687
  exports.pullFpo = pullFpo;
16688
+ exports.pullSchedule = pullSchedule;
15860
16689
  exports.ranStep = ranStep;
15861
16690
  exports.regex = regex;
15862
16691
  exports.registerWorkflowHook = registerWorkflowHook;