@runtypelabs/sdk 4.10.0 → 4.12.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
@@ -1094,20 +1094,20 @@ var FlowBuilder = class {
1094
1094
  */
1095
1095
  build() {
1096
1096
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
1097
- const request2 = { flow };
1097
+ const request3 = { flow };
1098
1098
  if (this.recordConfig) {
1099
- request2.record = this.recordConfig;
1099
+ request3.record = this.recordConfig;
1100
1100
  }
1101
1101
  if (this.messagesConfig) {
1102
- request2.messages = this.messagesConfig;
1102
+ request3.messages = this.messagesConfig;
1103
1103
  }
1104
1104
  if (this.inputsConfig) {
1105
- request2.inputs = this.inputsConfig;
1105
+ request3.inputs = this.inputsConfig;
1106
1106
  }
1107
1107
  if (Object.keys(this.optionsConfig).length > 0) {
1108
- request2.options = this.optionsConfig;
1108
+ request3.options = this.optionsConfig;
1109
1109
  }
1110
- return request2;
1110
+ return request3;
1111
1111
  }
1112
1112
  /**
1113
1113
  * Validate this prospective flow against the public validation endpoint
@@ -1430,6 +1430,9 @@ function collectStepNonPortableToolRefs(config, path) {
1430
1430
  scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
1431
1431
  }
1432
1432
  }
1433
+ if (isAccountScoped(config.toolId)) {
1434
+ found.push(`${path}.toolId`);
1435
+ }
1433
1436
  for (const branch of ["trueSteps", "falseSteps"]) {
1434
1437
  const nested = config[branch];
1435
1438
  if (!Array.isArray(nested)) continue;
@@ -1483,7 +1486,7 @@ function defineFlow(input) {
1483
1486
  const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
1484
1487
  if (nonPortable.length > 0) {
1485
1488
  throw new Error(
1486
- `defineFlow: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references instead. Name-based resolution of saved tools is a planned follow-up.`
1489
+ `defineFlow: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved tool by name with tool:<name> instead.`
1487
1490
  );
1488
1491
  }
1489
1492
  }
@@ -2482,15 +2485,15 @@ var RuntypeFlowBuilder = class {
2482
2485
  build() {
2483
2486
  const flowMode = this.mode === "existing" ? "existing" : this.mode;
2484
2487
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
2485
- const request2 = { flow };
2488
+ const request3 = { flow };
2486
2489
  if (this.recordConfig) {
2487
- request2.record = this.recordConfig;
2490
+ request3.record = this.recordConfig;
2488
2491
  }
2489
2492
  if (this.messagesConfig) {
2490
- request2.messages = this.messagesConfig;
2493
+ request3.messages = this.messagesConfig;
2491
2494
  }
2492
2495
  if (this.inputsConfig) {
2493
- request2.inputs = this.inputsConfig;
2496
+ request3.inputs = this.inputsConfig;
2494
2497
  }
2495
2498
  const options = {
2496
2499
  flowMode,
@@ -2508,8 +2511,8 @@ var RuntypeFlowBuilder = class {
2508
2511
  if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
2509
2512
  options.upsertOptions = this.upsertOptions;
2510
2513
  }
2511
- request2.options = options;
2512
- return request2;
2514
+ request3.options = options;
2515
+ return request3;
2513
2516
  }
2514
2517
  /**
2515
2518
  * Validate this prospective flow against the public validation endpoint
@@ -3399,7 +3402,7 @@ function defineAgent(input) {
3399
3402
  const nonPortable = collectNonPortableToolRefs(config);
3400
3403
  if (nonPortable.length > 0) {
3401
3404
  throw new Error(
3402
- `defineAgent: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references instead. Name-based resolution of saved tools is a planned follow-up.`
3405
+ `defineAgent: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved tool by name with tool:<name> instead.`
3403
3406
  );
3404
3407
  }
3405
3408
  return {
@@ -3541,6 +3544,242 @@ var AgentsNamespace = class {
3541
3544
  }
3542
3545
  };
3543
3546
 
3547
+ // src/tools-ensure.ts
3548
+ function isPlainObject3(value) {
3549
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3550
+ }
3551
+ function normalizeValue2(value) {
3552
+ if (Array.isArray(value)) {
3553
+ return value.map((item) => normalizeValue2(item));
3554
+ }
3555
+ if (isPlainObject3(value)) {
3556
+ const normalized = {};
3557
+ for (const key of Object.keys(value).sort()) {
3558
+ const entry = value[key];
3559
+ if (entry === void 0 || entry === null) continue;
3560
+ normalized[key] = normalizeValue2(entry);
3561
+ }
3562
+ return normalized;
3563
+ }
3564
+ return value;
3565
+ }
3566
+ function normalizeToolDefinition(definition) {
3567
+ const parametersSchema = isPlainObject3(definition.parametersSchema) ? normalizeValue2(definition.parametersSchema) : {};
3568
+ const config = isPlainObject3(definition.config) ? normalizeValue2(definition.config) : {};
3569
+ return {
3570
+ toolType: definition.toolType,
3571
+ ...definition.description ? { description: definition.description } : {},
3572
+ parametersSchema,
3573
+ config
3574
+ };
3575
+ }
3576
+ async function computeToolContentHash(definition) {
3577
+ const serialized = JSON.stringify(normalizeToolDefinition(definition));
3578
+ const encoded = new TextEncoder().encode(serialized);
3579
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3580
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3581
+ }
3582
+ var DEFINE_TOOL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
3583
+ "name",
3584
+ "description",
3585
+ "toolType",
3586
+ "parametersSchema",
3587
+ "config"
3588
+ ]);
3589
+ var TOOL_DEFINITION_TYPES = /* @__PURE__ */ new Set([
3590
+ "flow",
3591
+ "custom",
3592
+ "external",
3593
+ "graphql",
3594
+ "mcp",
3595
+ "local",
3596
+ "subagent"
3597
+ ]);
3598
+ function defineTool(input) {
3599
+ if (!input || typeof input !== "object") {
3600
+ throw new Error("defineTool requires a definition object");
3601
+ }
3602
+ if (typeof input.name !== "string" || input.name.length === 0) {
3603
+ throw new Error('defineTool requires a non-empty string "name"');
3604
+ }
3605
+ if (typeof input.description !== "string" || input.description.length === 0) {
3606
+ throw new Error('defineTool requires a non-empty string "description"');
3607
+ }
3608
+ if (typeof input.toolType !== "string" || !TOOL_DEFINITION_TYPES.has(input.toolType)) {
3609
+ throw new Error(
3610
+ `defineTool requires "toolType" to be one of: ${[...TOOL_DEFINITION_TYPES].join(", ")}`
3611
+ );
3612
+ }
3613
+ if (!isPlainObject3(input.parametersSchema)) {
3614
+ throw new Error('defineTool requires a "parametersSchema" object (a JSON Schema)');
3615
+ }
3616
+ if (!isPlainObject3(input.config)) {
3617
+ throw new Error('defineTool requires a "config" object');
3618
+ }
3619
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOOL_TOP_LEVEL_KEYS.has(key));
3620
+ if (unknownKeys.length > 0) {
3621
+ throw new Error(
3622
+ `defineTool: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, toolType, parametersSchema, config.`
3623
+ );
3624
+ }
3625
+ return {
3626
+ name: input.name,
3627
+ description: input.description,
3628
+ toolType: input.toolType,
3629
+ parametersSchema: input.parametersSchema,
3630
+ config: input.config
3631
+ };
3632
+ }
3633
+ var ToolEnsureConflictError = class extends Error {
3634
+ constructor(body) {
3635
+ super(body.error ?? `Tool ensure conflict: ${body.code}`);
3636
+ this.name = "ToolEnsureConflictError";
3637
+ this.code = body.code;
3638
+ this.lastModifiedSource = body.lastModifiedSource;
3639
+ this.modifiedAt = body.modifiedAt;
3640
+ this.currentHash = body.currentHash;
3641
+ }
3642
+ };
3643
+ var ToolDriftError = class extends Error {
3644
+ constructor(plan) {
3645
+ super(
3646
+ `Tool "${plan.toolId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.tools.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
3647
+ );
3648
+ this.name = "ToolDriftError";
3649
+ this.plan = plan;
3650
+ }
3651
+ };
3652
+ function parseRequestError3(err) {
3653
+ if (!(err instanceof Error)) return { status: null, body: null };
3654
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3655
+ if (!match) return { status: null, body: null };
3656
+ try {
3657
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3658
+ } catch {
3659
+ return { status: Number(match[1]), body: null };
3660
+ }
3661
+ }
3662
+ function toConflictError3(err) {
3663
+ const { status, body } = parseRequestError3(err);
3664
+ if (status !== 409 || !isPlainObject3(body)) return null;
3665
+ const code = body.code;
3666
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3667
+ return new ToolEnsureConflictError(
3668
+ body
3669
+ );
3670
+ }
3671
+ var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
3672
+ function memoFor3(client) {
3673
+ let memo = serverHashMemo3.get(client);
3674
+ if (!memo) {
3675
+ memo = /* @__PURE__ */ new Map();
3676
+ serverHashMemo3.set(client, memo);
3677
+ }
3678
+ return memo;
3679
+ }
3680
+ function memoize2(memo, memoKey, result) {
3681
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3682
+ }
3683
+ async function request2(client, body) {
3684
+ try {
3685
+ return await client.post(
3686
+ "/tools/ensure",
3687
+ body
3688
+ );
3689
+ } catch (err) {
3690
+ const conflict = toConflictError3(err);
3691
+ if (conflict) throw conflict;
3692
+ throw err;
3693
+ }
3694
+ }
3695
+ async function ensureTool(client, definition, options = {}) {
3696
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
3697
+ const passthrough = {
3698
+ ...onConflict ? { onConflict } : {},
3699
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3700
+ };
3701
+ if (dryRun || expectNoChanges) {
3702
+ const plan = await request2(client, {
3703
+ name: definition.name,
3704
+ definition,
3705
+ dryRun: true,
3706
+ ...passthrough
3707
+ });
3708
+ if (plan.result !== "plan") {
3709
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3710
+ }
3711
+ if (expectNoChanges && plan.changes !== "none") {
3712
+ throw new ToolDriftError(plan);
3713
+ }
3714
+ return plan;
3715
+ }
3716
+ const memo = memoFor3(client);
3717
+ const localHash = await computeToolContentHash(definition);
3718
+ const memoKey = `${definition.name} ${localHash}`;
3719
+ const contentHash = memo.get(memoKey) ?? localHash;
3720
+ const probe = await request2(client, {
3721
+ name: definition.name,
3722
+ contentHash,
3723
+ ...passthrough
3724
+ });
3725
+ if (probe.result !== "definitionRequired") {
3726
+ memoize2(memo, memoKey, probe);
3727
+ return probe;
3728
+ }
3729
+ const converged = await request2(client, {
3730
+ name: definition.name,
3731
+ definition,
3732
+ ...passthrough
3733
+ });
3734
+ if (converged.result === "definitionRequired") {
3735
+ throw new Error("Server reported definitionRequired for a full-definition request");
3736
+ }
3737
+ memoize2(memo, memoKey, converged);
3738
+ return converged;
3739
+ }
3740
+ async function pullTool(client, name) {
3741
+ return client.get("/tools/pull", { name });
3742
+ }
3743
+
3744
+ // src/tools-namespace.ts
3745
+ var ToolsNamespace = class {
3746
+ constructor(getClient) {
3747
+ this.getClient = getClient;
3748
+ }
3749
+ /**
3750
+ * Idempotently converge a `defineTool` definition onto the platform.
3751
+ * Hash-first: the steady state is one tiny probe request. Creates or updates
3752
+ * the saved tool; never deletes. Identity is name + account scope.
3753
+ *
3754
+ * @example
3755
+ * ```typescript
3756
+ * const weather = defineTool({
3757
+ * name: 'Weather Lookup',
3758
+ * description: 'Fetch the current weather for a city',
3759
+ * toolType: 'external',
3760
+ * parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
3761
+ * config: { url: 'https://api.example.com/weather', method: 'GET' },
3762
+ * })
3763
+ *
3764
+ * // Converge (CI/deploy).
3765
+ * const result = await Runtype.tools.ensure(weather)
3766
+ *
3767
+ * // PR drift gate.
3768
+ * await Runtype.tools.ensure(weather, { expectNoChanges: true })
3769
+ * ```
3770
+ */
3771
+ async ensure(definition, options = {}) {
3772
+ return ensureTool(this.getClient(), definition, options);
3773
+ }
3774
+ /**
3775
+ * Pull the canonical definition + provenance for a tool by name — the
3776
+ * absorb-drift direction of the ensure protocol.
3777
+ */
3778
+ async pull(name) {
3779
+ return pullTool(this.getClient(), name);
3780
+ }
3781
+ };
3782
+
3544
3783
  // src/transform.ts
3545
3784
  function transformResponse(data) {
3546
3785
  return data;
@@ -3929,6 +4168,34 @@ var Runtype = class {
3929
4168
  static get agents() {
3930
4169
  return new AgentsNamespace(() => this.getClient());
3931
4170
  }
4171
+ /**
4172
+ * Tools namespace - Tool config-as-code (define / ensure / pull)
4173
+ *
4174
+ * @example
4175
+ * ```typescript
4176
+ * import { defineTool, Runtype } from '@runtypelabs/sdk'
4177
+ *
4178
+ * const weather = defineTool({
4179
+ * name: 'Weather Lookup',
4180
+ * description: 'Fetch the current weather for a city',
4181
+ * toolType: 'external',
4182
+ * parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
4183
+ * config: { url: 'https://api.example.com/weather', method: 'GET' },
4184
+ * })
4185
+ *
4186
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
4187
+ * await Runtype.tools.ensure(weather)
4188
+ *
4189
+ * // CI drift gate
4190
+ * await Runtype.tools.ensure(weather, { expectNoChanges: true })
4191
+ *
4192
+ * // Absorb a dashboard edit back into the repo
4193
+ * const { definition } = await Runtype.tools.pull('Weather Lookup')
4194
+ * ```
4195
+ */
4196
+ static get tools() {
4197
+ return new ToolsNamespace(() => this.getClient());
4198
+ }
3932
4199
  };
3933
4200
 
3934
4201
  // src/generated-tool-gate.ts
@@ -4151,8 +4418,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
4151
4418
  ...decision.tool ? { tool: decision.tool } : {}
4152
4419
  };
4153
4420
  }
4154
- function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {}) {
4155
- const stepList = request2.flow.steps;
4421
+ function attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options = {}) {
4422
+ const stepList = request3.flow.steps;
4156
4423
  if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
4157
4424
  throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
4158
4425
  }
@@ -4195,9 +4462,9 @@ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {
4195
4462
  }
4196
4463
  };
4197
4464
  return {
4198
- ...request2,
4465
+ ...request3,
4199
4466
  flow: {
4200
- ...request2.flow,
4467
+ ...request3.flow,
4201
4468
  // `clonedSteps` is a structural clone of `request.flow.steps` (already
4202
4469
  // `FlowStepDefinition[]`); only the prompt step's `config.tools` was
4203
4470
  // merged, so every step's `type` discriminant is preserved. The clone is
@@ -4207,12 +4474,12 @@ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {
4207
4474
  }
4208
4475
  };
4209
4476
  }
4210
- function applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options = {}) {
4477
+ function applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options = {}) {
4211
4478
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
4212
4479
  if (!decision.approved || !decision.tool) {
4213
- return { decision, request: request2 };
4480
+ return { decision, request: request3 };
4214
4481
  }
4215
- const nextRequest = attachRuntimeToolsToDispatchRequest(request2, [decision.tool], options.attach);
4482
+ const nextRequest = attachRuntimeToolsToDispatchRequest(request3, [decision.tool], options.attach);
4216
4483
  return {
4217
4484
  decision,
4218
4485
  request: nextRequest
@@ -4310,6 +4577,261 @@ function sanitizeTaskSlug(taskName) {
4310
4577
  return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4311
4578
  }
4312
4579
 
4580
+ // src/workflows/hook-registry.ts
4581
+ var BUILTIN_NAMESPACE = "builtin";
4582
+ var HOOK_REF_PATTERN = /^[a-z0-9_-]+:[a-z0-9_-]+$/;
4583
+ function isWorkflowHookRef(value) {
4584
+ return typeof value === "string" && HOOK_REF_PATTERN.test(value);
4585
+ }
4586
+ var registry = /* @__PURE__ */ new Map();
4587
+ function registerWorkflowHook(name, entry) {
4588
+ if (!isWorkflowHookRef(name)) {
4589
+ throw new Error(
4590
+ `Invalid workflow hook name "${name}": must be "<namespace>:<id>" using lowercase letters, digits, "-" or "_" (e.g. "acme:my-completion").`
4591
+ );
4592
+ }
4593
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) {
4594
+ throw new Error(
4595
+ `Cannot register "${name}": the "builtin:" namespace is reserved. Register under your own namespace and reference it from the workflow config instead.`
4596
+ );
4597
+ }
4598
+ registry.set(name, entry);
4599
+ }
4600
+ function registerBuiltinWorkflowHook(name, entry) {
4601
+ if (!name.startsWith(`${BUILTIN_NAMESPACE}:`) || !isWorkflowHookRef(name)) {
4602
+ throw new Error(`Builtin workflow hooks must be named "builtin:<id>" (got "${name}").`);
4603
+ }
4604
+ if (registry.has(name)) return;
4605
+ registry.set(name, entry);
4606
+ }
4607
+ function resolveWorkflowHook(name, expectedKind) {
4608
+ const entry = registry.get(name);
4609
+ if (!entry) {
4610
+ const known = listWorkflowHooks().filter((hook) => hook.kind === expectedKind).map((hook) => hook.name);
4611
+ throw new Error(
4612
+ `Unknown workflow hook "${name}". ` + (known.length > 0 ? `Registered '${expectedKind}' hooks: ${known.join(", ")}.` : `No '${expectedKind}' hooks are registered.`) + " Custom hooks must be registered (e.g. via a playbook plugin) before the workflow is compiled."
4613
+ );
4614
+ }
4615
+ if (entry.kind !== expectedKind) {
4616
+ throw new Error(
4617
+ `Workflow hook "${name}" is registered as '${entry.kind}' but referenced from a '${expectedKind}' slot.`
4618
+ );
4619
+ }
4620
+ return entry.fn;
4621
+ }
4622
+ function listWorkflowHooks() {
4623
+ return [...registry.entries()].map(([name, entry]) => ({ name, kind: entry.kind }));
4624
+ }
4625
+ function unregisterWorkflowHook(name) {
4626
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) return false;
4627
+ return registry.delete(name);
4628
+ }
4629
+
4630
+ // src/workflows/workflow-config.ts
4631
+ var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
4632
+ "search_repo",
4633
+ "glob_files",
4634
+ "tree_directory",
4635
+ "list_directory"
4636
+ ]);
4637
+ var DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS = 2;
4638
+ function definePlaybook(playbook) {
4639
+ return playbook;
4640
+ }
4641
+ function interpolateWorkflowTemplate(template, state) {
4642
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
4643
+ const value = state[key];
4644
+ if (value === void 0 || value === null) return `{{${key}}}`;
4645
+ return String(value);
4646
+ });
4647
+ }
4648
+ function buildIsComplete(criteria, configName, milestoneName) {
4649
+ if (!criteria) return () => false;
4650
+ switch (criteria.type) {
4651
+ case "evidence":
4652
+ return (ctx) => {
4653
+ const minFiles = criteria.minReadFiles ?? 1;
4654
+ return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
4655
+ };
4656
+ case "sessions": {
4657
+ let baselineSessionCount;
4658
+ return (ctx) => {
4659
+ const minSessions = criteria.minSessions ?? 1;
4660
+ if (baselineSessionCount === void 0) {
4661
+ baselineSessionCount = ctx.state.sessions.length;
4662
+ }
4663
+ return ctx.state.sessions.length - baselineSessionCount >= minSessions;
4664
+ };
4665
+ }
4666
+ case "planWritten":
4667
+ return (ctx) => {
4668
+ return ctx.trace.planWritten;
4669
+ };
4670
+ case "never":
4671
+ return () => false;
4672
+ default: {
4673
+ if (isWorkflowHookRef(criteria.type)) {
4674
+ return resolveWorkflowHook(criteria.type, "completion");
4675
+ }
4676
+ throw new Error(
4677
+ `Workflow config '${configName}': milestone '${milestoneName}' has unknown completionCriteria.type "${criteria.type}" (expected evidence | sessions | planWritten | never, or a 'completion' hook reference).`
4678
+ );
4679
+ }
4680
+ }
4681
+ }
4682
+ function buildPolicyIntercept(policy, configName, deps) {
4683
+ if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
4684
+ return void 0;
4685
+ }
4686
+ const blockedSet = new Set(
4687
+ (policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
4688
+ );
4689
+ const readGlobs = policy.allowedReadGlobs ?? [];
4690
+ const writeGlobs = policy.allowedWriteGlobs ?? [];
4691
+ const matchPathGlobs = deps.matchPathGlobs;
4692
+ if ((readGlobs.length > 0 || writeGlobs.length > 0) && !matchPathGlobs) {
4693
+ throw new Error(
4694
+ `Workflow config '${configName}': policy uses allowedReadGlobs/allowedWriteGlobs but no glob matcher was provided to compileWorkflowConfig (pass deps.matchPathGlobs).`
4695
+ );
4696
+ }
4697
+ return (toolName, args, ctx) => {
4698
+ if (blockedSet.has(toolName)) {
4699
+ return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
4700
+ }
4701
+ if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
4702
+ return `Blocked by playbook policy: discovery tools are disabled for this task.`;
4703
+ }
4704
+ const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
4705
+ if (pathArg) {
4706
+ const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
4707
+ const isRead = toolName === "read_file";
4708
+ if (isRead && readGlobs.length > 0) {
4709
+ const allowed = matchPathGlobs(pathArg, readGlobs);
4710
+ if (!allowed) {
4711
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
4712
+ }
4713
+ }
4714
+ if (isWrite && writeGlobs.length > 0) {
4715
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4716
+ if (planPath && pathArg === planPath) {
4717
+ } else {
4718
+ const allowed = matchPathGlobs(pathArg, writeGlobs);
4719
+ if (!allowed) {
4720
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
4721
+ }
4722
+ }
4723
+ }
4724
+ if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
4725
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4726
+ if (!planPath || pathArg !== planPath) {
4727
+ return `Blocked by playbook policy: write the plan before creating other files.`;
4728
+ }
4729
+ }
4730
+ }
4731
+ return void 0;
4732
+ };
4733
+ }
4734
+ function buildPolicyGuidance(policy) {
4735
+ if (!policy) return [];
4736
+ const lines = [];
4737
+ if (policy.requirePlanBeforeWrite) {
4738
+ lines.push(
4739
+ "Policy: write the plan file before any other file. Once the plan is written, other writes are allowed in the same turn."
4740
+ );
4741
+ }
4742
+ if (policy.allowedWriteGlobs?.length) {
4743
+ lines.push(
4744
+ `Policy: file writes are only allowed for paths matching: ${policy.allowedWriteGlobs.join(", ")} (the plan file is always allowed).`
4745
+ );
4746
+ }
4747
+ if (policy.outputRoot) {
4748
+ lines.push(`Policy: create new files under "${policy.outputRoot.replace(/\/$/, "")}/".`);
4749
+ }
4750
+ if (policy.allowedReadGlobs?.length) {
4751
+ lines.push(
4752
+ `Policy: file reads are only allowed for paths matching: ${policy.allowedReadGlobs.join(", ")}.`
4753
+ );
4754
+ }
4755
+ if (policy.blockDiscoveryTools) {
4756
+ lines.push(
4757
+ "Policy: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are disabled for this task."
4758
+ );
4759
+ }
4760
+ if (policy.blockedTools?.length) {
4761
+ lines.push(`Policy: these tools are disabled for this task: ${policy.blockedTools.join(", ")}.`);
4762
+ }
4763
+ return lines;
4764
+ }
4765
+ function resolveSlotHook(value, kind) {
4766
+ if (value === void 0) return void 0;
4767
+ if (typeof value === "function") return value;
4768
+ return resolveWorkflowHook(value, kind);
4769
+ }
4770
+ function compileMilestone(milestone, config, policyIntercept, policyGuidance) {
4771
+ const buildInstructions = typeof milestone.instructions === "function" ? milestone.instructions : isWorkflowHookRef(milestone.instructions) ? resolveWorkflowHook(milestone.instructions, "instructions") : (state) => {
4772
+ const header = `--- Workflow Phase: ${milestone.name} ---`;
4773
+ const desc = milestone.description ? `
4774
+ ${milestone.description}` : "";
4775
+ const instructions = interpolateWorkflowTemplate(
4776
+ milestone.instructions,
4777
+ state
4778
+ );
4779
+ return `${header}${desc}
4780
+ ${instructions}`;
4781
+ };
4782
+ const guidanceHook = typeof milestone.toolGuidance === "function" ? milestone.toolGuidance : milestone.toolGuidance !== void 0 && isWorkflowHookRef(milestone.toolGuidance) ? resolveWorkflowHook(milestone.toolGuidance, "toolGuidance") : void 0;
4783
+ const buildToolGuidance = (state) => {
4784
+ const base = guidanceHook ? guidanceHook(state) : milestone.toolGuidance ?? [];
4785
+ return policyGuidance.length > 0 ? [...base, ...policyGuidance] : base;
4786
+ };
4787
+ const customIntercept = resolveSlotHook(milestone.intercept, "intercept");
4788
+ const interceptToolCall = policyIntercept && customIntercept ? (toolName, args, ctx) => policyIntercept(toolName, args, ctx) ?? customIntercept(toolName, args, ctx) : policyIntercept ?? customIntercept;
4789
+ const transitionHook = typeof milestone.transitionSummary === "function" ? milestone.transitionSummary : milestone.transitionSummary !== void 0 && isWorkflowHookRef(milestone.transitionSummary) ? resolveWorkflowHook(milestone.transitionSummary, "transitionSummary") : void 0;
4790
+ const buildTransitionSummary = milestone.transitionSummary === void 0 ? void 0 : transitionHook ?? ((state, nextPhaseName) => interpolateWorkflowTemplate(milestone.transitionSummary, state).replace(
4791
+ /\{\{nextPhase\}\}/g,
4792
+ nextPhaseName
4793
+ ));
4794
+ const recoveryHook = typeof milestone.recovery === "function" ? milestone.recovery : milestone.recovery !== void 0 && isWorkflowHookRef(milestone.recovery) ? resolveWorkflowHook(milestone.recovery, "recovery") : void 0;
4795
+ const buildRecoveryMessage = milestone.recovery === void 0 ? void 0 : recoveryHook ?? ((state) => {
4796
+ const inline = milestone.recovery;
4797
+ const threshold = inline.afterEmptySessions ?? DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS;
4798
+ if ((state.consecutiveEmptySessions ?? 0) < threshold) return void 0;
4799
+ return interpolateWorkflowTemplate(inline.message, state);
4800
+ });
4801
+ const canAcceptCompletion = milestone.canAcceptCompletion === void 0 ? void 0 : typeof milestone.canAcceptCompletion === "function" ? milestone.canAcceptCompletion : isWorkflowHookRef(milestone.canAcceptCompletion) ? resolveWorkflowHook(milestone.canAcceptCompletion, "acceptCompletion") : () => milestone.canAcceptCompletion;
4802
+ const isComplete = typeof milestone.completionCriteria === "function" ? milestone.completionCriteria : buildIsComplete(milestone.completionCriteria, config.name, milestone.name);
4803
+ return {
4804
+ name: milestone.name,
4805
+ description: milestone.description,
4806
+ buildInstructions,
4807
+ buildToolGuidance,
4808
+ isComplete,
4809
+ ...interceptToolCall ? { interceptToolCall } : {},
4810
+ ...buildTransitionSummary ? { buildTransitionSummary } : {},
4811
+ ...buildRecoveryMessage ? { buildRecoveryMessage } : {},
4812
+ ...milestone.forceEndTurn ? { shouldForceEndTurn: resolveSlotHook(milestone.forceEndTurn, "forceEndTurn") } : {},
4813
+ ...canAcceptCompletion ? { canAcceptCompletion } : {}
4814
+ };
4815
+ }
4816
+ function compileWorkflowConfig(config, deps = {}) {
4817
+ const policyIntercept = config.policy ? buildPolicyIntercept(config.policy, config.name, deps) : void 0;
4818
+ const policyGuidance = buildPolicyGuidance(config.policy);
4819
+ const phases = config.milestones.map(
4820
+ (milestone) => compileMilestone(milestone, config, policyIntercept, policyGuidance)
4821
+ );
4822
+ const classifyVariant4 = resolveSlotHook(config.classifyVariant, "classify");
4823
+ const generateBootstrapContext2 = resolveSlotHook(config.bootstrap, "bootstrap");
4824
+ const buildCandidateBlock2 = resolveSlotHook(config.candidateBlock, "candidateBlock");
4825
+ return {
4826
+ name: config.name,
4827
+ phases,
4828
+ ...config.stallPolicy ? { stallPolicy: config.stallPolicy } : {},
4829
+ ...classifyVariant4 ? { classifyVariant: classifyVariant4 } : {},
4830
+ ...generateBootstrapContext2 ? { generateBootstrapContext: generateBootstrapContext2 } : {},
4831
+ ...buildCandidateBlock2 ? { buildCandidateBlock: buildCandidateBlock2 } : {}
4832
+ };
4833
+ }
4834
+
4313
4835
  // src/workflows/default-workflow.ts
4314
4836
  function isExternalTask(state) {
4315
4837
  return state.workflowVariant === "external";
@@ -4459,6 +4981,45 @@ function summarizeTextBlock(value, maxLines = 4) {
4459
4981
  if (!text) return "";
4460
4982
  return text.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, maxLines).join(" | ").slice(0, 240);
4461
4983
  }
4984
+ function interceptProductWriteTarget(toolName, normalizedPathArg, ctx, guardLabel) {
4985
+ const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4986
+ const normalizedBestCandidatePath = ctx.state.bestCandidatePath ? ctx.normalizePath(ctx.state.bestCandidatePath) : void 0;
4987
+ if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4988
+ const allowedWriteTargets = new Set(
4989
+ [
4990
+ normalizedPlanPath,
4991
+ normalizedBestCandidatePath,
4992
+ ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4993
+ ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4994
+ ].filter((value) => Boolean(value))
4995
+ );
4996
+ if (!allowedWriteTargets.has(normalizedPathArg)) {
4997
+ return [
4998
+ `Blocked by marathon ${guardLabel}: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4999
+ `Do not create scratch files like "${normalizedPathArg}".`,
5000
+ normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
5001
+ ].join(" ");
5002
+ }
5003
+ }
5004
+ if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
5005
+ const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
5006
+ if (!outputRoot) {
5007
+ return [
5008
+ `Blocked by marathon ${guardLabel}: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
5009
+ `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
5010
+ ].join(" ");
5011
+ }
5012
+ const rootPrefix = outputRoot + "/";
5013
+ const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
5014
+ if (!isUnderRoot) {
5015
+ return [
5016
+ `Blocked by marathon ${guardLabel}: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
5017
+ `"${normalizedPathArg}" is outside the allowed output root.`
5018
+ ].join(" ");
5019
+ }
5020
+ }
5021
+ return void 0;
5022
+ }
4462
5023
  var researchPhase = {
4463
5024
  name: "research",
4464
5025
  description: "Inspect the repo and identify the correct target file",
@@ -4543,11 +5104,15 @@ var researchPhase = {
4543
5104
  const normalizedPathArg2 = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
4544
5105
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4545
5106
  if (normalizedPathArg2 && normalizedPlanPath && normalizedPathArg2 !== normalizedPlanPath) {
4546
- return [
4547
- `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
4548
- "Complete research first, then the system will advance you to planning.",
4549
- `You may write the plan to "${normalizedPlanPath}" once research is complete.`
4550
- ].join(" ");
5107
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
5108
+ if (!planWritten) {
5109
+ return [
5110
+ `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
5111
+ "Complete research first, then the system will advance you to planning.",
5112
+ `You may write the plan to "${normalizedPlanPath}" once research is complete.`
5113
+ ].join(" ");
5114
+ }
5115
+ return interceptProductWriteTarget(toolName, normalizedPathArg2, ctx, "research guard");
4551
5116
  }
4552
5117
  }
4553
5118
  return void 0;
@@ -4670,19 +5235,24 @@ var planningPhase = {
4670
5235
  "Research is complete. Write the implementation plan for building this from scratch.",
4671
5236
  `Write the plan markdown to exactly: ${planPath}`,
4672
5237
  "List the files you will create, their locations, purpose, and any dependencies to install.",
5238
+ ...state.outputRoot ? [
5239
+ `All new files must be created under "${state.outputRoot}" \u2014 writes outside that directory are blocked, so plan every file location inside it.`
5240
+ ] : [],
4673
5241
  'Include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4674
- "If the plan already exists, update that same plan file instead of creating a different one."
5242
+ "If the plan already exists, update that same plan file instead of creating a different one.",
5243
+ "Once the plan is written, you may begin creating the planned files in the same turn."
4675
5244
  ].join("\n");
4676
5245
  }
4677
5246
  return [
4678
5247
  "--- Workflow Phase: Planning ---",
4679
5248
  "Research is complete. Your current job is to write the implementation plan before any product-file edits.",
4680
5249
  `Write the plan markdown to exactly: ${planPath}`,
4681
- "Do NOT edit the target product file yet.",
5250
+ "Do NOT edit the target product file before the plan exists.",
4682
5251
  "The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.",
4683
5252
  'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
4684
5253
  'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4685
- "If the plan already exists, update that same plan file instead of creating a different one."
5254
+ "If the plan already exists, update that same plan file instead of creating a different one.",
5255
+ "Once the plan is written, you may begin editing the target file in the same turn."
4686
5256
  ].join("\n");
4687
5257
  },
4688
5258
  buildToolGuidance(state) {
@@ -4710,10 +5280,14 @@ var planningPhase = {
4710
5280
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4711
5281
  const isWriteLikeTool = toolName === "write_file" || toolName === "edit_file" || toolName === "restore_file_checkpoint";
4712
5282
  if (isWriteLikeTool && normalizedPathArg && normalizedPlanPath && normalizedPathArg !== normalizedPlanPath) {
4713
- return [
4714
- `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
4715
- `Write the plan to "${normalizedPlanPath}" before editing any product files.`
4716
- ].join(" ");
5283
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
5284
+ if (!planWritten) {
5285
+ return [
5286
+ `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
5287
+ `Write the plan to "${normalizedPlanPath}" before editing any product files.`
5288
+ ].join(" ");
5289
+ }
5290
+ return interceptProductWriteTarget(toolName, normalizedPathArg, ctx, "planning guard");
4717
5291
  }
4718
5292
  return void 0;
4719
5293
  },
@@ -4773,6 +5347,9 @@ var executionPhase = {
4773
5347
  },
4774
5348
  buildToolGuidance(state) {
4775
5349
  return [
5350
+ ...state.isCreationTask && state.outputRoot ? [
5351
+ `Creation guard: create new files under "${state.outputRoot}". Writes outside it are blocked \u2014 the plan file is the only exception.`
5352
+ ] : [],
4776
5353
  ...state.bestCandidatePath ? [
4777
5354
  `Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`
4778
5355
  ] : [
@@ -4814,40 +5391,13 @@ var executionPhase = {
4814
5391
  `After that, you may update "${normalizedPlanPath}" with progress.`
4815
5392
  ].join(" ");
4816
5393
  }
4817
- if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4818
- const allowedWriteTargets = new Set(
4819
- [
4820
- normalizedPlanPath,
4821
- normalizedBestCandidatePath,
4822
- ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4823
- ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4824
- ].filter((value) => Boolean(value))
4825
- );
4826
- if (!allowedWriteTargets.has(normalizedPathArg)) {
4827
- return [
4828
- `Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4829
- `Do not create scratch files like "${normalizedPathArg}".`,
4830
- normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
4831
- ].join(" ");
4832
- }
4833
- }
4834
- if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4835
- const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
4836
- if (!outputRoot) {
4837
- return [
4838
- `Blocked by marathon execution guard: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
4839
- `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
4840
- ].join(" ");
4841
- }
4842
- const rootPrefix = outputRoot + "/";
4843
- const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
4844
- if (!isUnderRoot) {
4845
- return [
4846
- `Blocked by marathon execution guard: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
4847
- `"${normalizedPathArg}" is outside the allowed output root.`
4848
- ].join(" ");
4849
- }
4850
- }
5394
+ const writeTargetBlock = interceptProductWriteTarget(
5395
+ toolName,
5396
+ normalizedPathArg,
5397
+ ctx,
5398
+ "execution guard"
5399
+ );
5400
+ if (writeTargetBlock) return writeTargetBlock;
4851
5401
  }
4852
5402
  return void 0;
4853
5403
  },
@@ -5080,13 +5630,163 @@ function buildCandidateBlock(state) {
5080
5630
  ...state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []
5081
5631
  ].join("\n");
5082
5632
  }
5083
- var defaultWorkflow = {
5633
+ var builtinHooksRegistered = false;
5634
+ function ensureDefaultWorkflowHooks() {
5635
+ if (builtinHooksRegistered) return;
5636
+ builtinHooksRegistered = true;
5637
+ registerBuiltinWorkflowHook("builtin:classify-task-variant", {
5638
+ kind: "classify",
5639
+ fn: classifyVariant
5640
+ });
5641
+ registerBuiltinWorkflowHook("builtin:repo-bootstrap-discovery", {
5642
+ kind: "bootstrap",
5643
+ fn: generateBootstrapContext
5644
+ });
5645
+ registerBuiltinWorkflowHook("builtin:best-candidate-block", {
5646
+ kind: "candidateBlock",
5647
+ fn: buildCandidateBlock
5648
+ });
5649
+ registerBuiltinWorkflowHook("builtin:research-instructions", {
5650
+ kind: "instructions",
5651
+ fn: researchPhase.buildInstructions
5652
+ });
5653
+ registerBuiltinWorkflowHook("builtin:research-tool-guidance", {
5654
+ kind: "toolGuidance",
5655
+ fn: researchPhase.buildToolGuidance
5656
+ });
5657
+ registerBuiltinWorkflowHook("builtin:research-complete", {
5658
+ kind: "completion",
5659
+ fn: researchPhase.isComplete
5660
+ });
5661
+ registerBuiltinWorkflowHook("builtin:research-transition-summary", {
5662
+ kind: "transitionSummary",
5663
+ fn: researchPhase.buildTransitionSummary
5664
+ });
5665
+ registerBuiltinWorkflowHook("builtin:research-guard", {
5666
+ kind: "intercept",
5667
+ fn: researchPhase.interceptToolCall
5668
+ });
5669
+ registerBuiltinWorkflowHook("builtin:research-recovery", {
5670
+ kind: "recovery",
5671
+ fn: researchPhase.buildRecoveryMessage
5672
+ });
5673
+ registerBuiltinWorkflowHook("builtin:research-force-end-turn", {
5674
+ kind: "forceEndTurn",
5675
+ fn: researchPhase.shouldForceEndTurn
5676
+ });
5677
+ registerBuiltinWorkflowHook("builtin:research-accept-completion", {
5678
+ kind: "acceptCompletion",
5679
+ fn: researchPhase.canAcceptCompletion
5680
+ });
5681
+ registerBuiltinWorkflowHook("builtin:planning-instructions", {
5682
+ kind: "instructions",
5683
+ fn: planningPhase.buildInstructions
5684
+ });
5685
+ registerBuiltinWorkflowHook("builtin:planning-tool-guidance", {
5686
+ kind: "toolGuidance",
5687
+ fn: planningPhase.buildToolGuidance
5688
+ });
5689
+ registerBuiltinWorkflowHook("builtin:planning-complete", {
5690
+ kind: "completion",
5691
+ fn: planningPhase.isComplete
5692
+ });
5693
+ registerBuiltinWorkflowHook("builtin:planning-transition-summary", {
5694
+ kind: "transitionSummary",
5695
+ fn: planningPhase.buildTransitionSummary
5696
+ });
5697
+ registerBuiltinWorkflowHook("builtin:planning-guard", {
5698
+ kind: "intercept",
5699
+ fn: planningPhase.interceptToolCall
5700
+ });
5701
+ registerBuiltinWorkflowHook("builtin:planning-recovery", {
5702
+ kind: "recovery",
5703
+ fn: planningPhase.buildRecoveryMessage
5704
+ });
5705
+ registerBuiltinWorkflowHook("builtin:planning-force-end-turn", {
5706
+ kind: "forceEndTurn",
5707
+ fn: planningPhase.shouldForceEndTurn
5708
+ });
5709
+ registerBuiltinWorkflowHook("builtin:execution-instructions", {
5710
+ kind: "instructions",
5711
+ fn: executionPhase.buildInstructions
5712
+ });
5713
+ registerBuiltinWorkflowHook("builtin:execution-tool-guidance", {
5714
+ kind: "toolGuidance",
5715
+ fn: executionPhase.buildToolGuidance
5716
+ });
5717
+ registerBuiltinWorkflowHook("builtin:execution-guard", {
5718
+ kind: "intercept",
5719
+ fn: executionPhase.interceptToolCall
5720
+ });
5721
+ registerBuiltinWorkflowHook("builtin:execution-recovery", {
5722
+ kind: "recovery",
5723
+ fn: executionPhase.buildRecoveryMessage
5724
+ });
5725
+ registerBuiltinWorkflowHook("builtin:execution-force-end-turn", {
5726
+ kind: "forceEndTurn",
5727
+ fn: executionPhase.shouldForceEndTurn
5728
+ });
5729
+ registerBuiltinWorkflowHook("builtin:execution-accept-completion", {
5730
+ kind: "acceptCompletion",
5731
+ fn: executionPhase.canAcceptCompletion
5732
+ });
5733
+ }
5734
+ var defaultWorkflowConfig = {
5084
5735
  name: "default",
5085
- phases: [researchPhase, planningPhase, executionPhase],
5086
- classifyVariant,
5087
- generateBootstrapContext,
5088
- buildCandidateBlock
5736
+ // Empty-session escalation. The counter only counts tool actions, so
5737
+ // narration-only sessions ("I'll create the files now" with no tool calls)
5738
+ // escalate here even though the phase recovery conditions keyed on
5739
+ // hadTextOutput skip them: nudge after the first actionless session, signal
5740
+ // model escalation after the second (a no-op unless the caller configured a
5741
+ // fallback model), and stop as 'stalled' after the third — the same total
5742
+ // session budget as before stallPolicy existed.
5743
+ stallPolicy: { nudgeAfter: 1, escalateModelAfter: 2, stopAfter: 3 },
5744
+ classifyVariant: "builtin:classify-task-variant",
5745
+ bootstrap: "builtin:repo-bootstrap-discovery",
5746
+ candidateBlock: "builtin:best-candidate-block",
5747
+ milestones: [
5748
+ {
5749
+ name: "research",
5750
+ description: "Inspect the repo and identify the correct target file",
5751
+ instructions: "builtin:research-instructions",
5752
+ toolGuidance: "builtin:research-tool-guidance",
5753
+ completionCriteria: { type: "builtin:research-complete" },
5754
+ intercept: "builtin:research-guard",
5755
+ transitionSummary: "builtin:research-transition-summary",
5756
+ recovery: "builtin:research-recovery",
5757
+ forceEndTurn: "builtin:research-force-end-turn",
5758
+ canAcceptCompletion: "builtin:research-accept-completion"
5759
+ },
5760
+ {
5761
+ name: "planning",
5762
+ description: "Write the implementation plan before editing product files",
5763
+ instructions: "builtin:planning-instructions",
5764
+ toolGuidance: "builtin:planning-tool-guidance",
5765
+ completionCriteria: { type: "builtin:planning-complete" },
5766
+ intercept: "builtin:planning-guard",
5767
+ transitionSummary: "builtin:planning-transition-summary",
5768
+ recovery: "builtin:planning-recovery",
5769
+ forceEndTurn: "builtin:planning-force-end-turn"
5770
+ // canAcceptCompletion intentionally absent: the hand-written planning
5771
+ // phase never defined it, and the SDK accepts completion when the slot
5772
+ // is undefined. Keep parity.
5773
+ },
5774
+ {
5775
+ name: "execution",
5776
+ description: "Execute the plan by editing target files",
5777
+ instructions: "builtin:execution-instructions",
5778
+ toolGuidance: "builtin:execution-tool-guidance",
5779
+ // Execution never auto-advances; completion is agent-driven via TASK_COMPLETE
5780
+ completionCriteria: { type: "never" },
5781
+ intercept: "builtin:execution-guard",
5782
+ recovery: "builtin:execution-recovery",
5783
+ forceEndTurn: "builtin:execution-force-end-turn",
5784
+ canAcceptCompletion: "builtin:execution-accept-completion"
5785
+ }
5786
+ ]
5089
5787
  };
5788
+ ensureDefaultWorkflowHooks();
5789
+ var defaultWorkflow = compileWorkflowConfig(defaultWorkflowConfig);
5090
5790
 
5091
5791
  // src/workflows/deploy-workflow.ts
5092
5792
  var scaffoldPhase = {
@@ -5498,6 +6198,34 @@ var gameWorkflow = {
5498
6198
  }
5499
6199
  };
5500
6200
 
6201
+ // src/workflows/stall-policy.ts
6202
+ var DEFAULT_STALL_STOP_AFTER = 3;
6203
+ function isPositiveInteger(value) {
6204
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
6205
+ }
6206
+ function resolveStallStopAfter(policy) {
6207
+ return isPositiveInteger(policy?.stopAfter) ? policy.stopAfter : DEFAULT_STALL_STOP_AFTER;
6208
+ }
6209
+ function shouldRequestModelEscalation(policy, consecutiveEmptySessions) {
6210
+ const threshold = policy?.escalateModelAfter;
6211
+ if (!isPositiveInteger(threshold)) return false;
6212
+ return consecutiveEmptySessions === threshold;
6213
+ }
6214
+ function shouldInjectEmptySessionNudge(policy, consecutiveEmptySessions) {
6215
+ const threshold = policy?.nudgeAfter;
6216
+ if (!isPositiveInteger(threshold)) return false;
6217
+ return consecutiveEmptySessions >= threshold;
6218
+ }
6219
+ function buildEmptySessionNudge(consecutiveEmptySessions) {
6220
+ const sessionPhrase = consecutiveEmptySessions === 1 ? "Your previous session ended" : `Your previous ${consecutiveEmptySessions} sessions ended`;
6221
+ return [
6222
+ "Recovery instruction:",
6223
+ `${sessionPhrase} without a single tool call. Describing what you plan to do does nothing \u2014 only tool calls make progress.`,
6224
+ "Your next response MUST include at least one tool call (for example write_file, edit_file, read_file, or run_check) that advances the task.",
6225
+ "If a previous tool call was blocked, re-read the block message and satisfy its requirement instead of ending the turn."
6226
+ ].join("\n");
6227
+ }
6228
+
5501
6229
  // src/endpoints.ts
5502
6230
  var FlowsEndpoint = class {
5503
6231
  constructor(client) {
@@ -5660,6 +6388,19 @@ var RecordsEndpoint = class {
5660
6388
  async getResults(id, params) {
5661
6389
  return this.client.get(`/records/${id}/results`, params);
5662
6390
  }
6391
+ /**
6392
+ * Get unified step-level execution results for a record, with filtering and
6393
+ * pagination.
6394
+ */
6395
+ async getStepResults(id, params) {
6396
+ return this.client.get(`/records/${id}/step-results`, params);
6397
+ }
6398
+ /**
6399
+ * Get the aggregated cost breakdown (by model) for a record's executions.
6400
+ */
6401
+ async getCosts(id) {
6402
+ return this.client.get(`/records/${id}/costs`);
6403
+ }
5663
6404
  /**
5664
6405
  * Delete a specific result for a record
5665
6406
  */
@@ -5988,15 +6729,15 @@ var DispatchEndpoint = class {
5988
6729
  * Attach approved runtime tools to a prompt step in a redispatch request.
5989
6730
  * Returns a new request object and does not mutate the original.
5990
6731
  */
5991
- attachApprovedRuntimeTools(request2, runtimeTools, options) {
5992
- return attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options);
6732
+ attachApprovedRuntimeTools(request3, runtimeTools, options) {
6733
+ return attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options);
5993
6734
  }
5994
6735
  /**
5995
6736
  * Validate a generated runtime tool proposal and attach it to the redispatch
5996
6737
  * request if approved, in one call.
5997
6738
  */
5998
- applyGeneratedRuntimeToolProposal(request2, proposal, options) {
5999
- return applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options);
6739
+ applyGeneratedRuntimeToolProposal(request3, proposal, options) {
6740
+ return applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options);
6000
6741
  }
6001
6742
  };
6002
6743
  var ChatEndpoint = class {
@@ -6548,8 +7289,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
6548
7289
  },
6549
7290
  required: ["name", "description", "toolType", "parametersSchema", "config"]
6550
7291
  };
6551
- function appendRuntimeToolsToAgentRequest(request2, runtimeTools) {
6552
- const existing = request2.tools?.runtimeTools || [];
7292
+ function appendRuntimeToolsToAgentRequest(request3, runtimeTools) {
7293
+ const existing = request3.tools?.runtimeTools || [];
6553
7294
  const existingNames = new Set(existing.map((tool) => tool.name));
6554
7295
  const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
6555
7296
  name: tool.name,
@@ -6559,9 +7300,9 @@ function appendRuntimeToolsToAgentRequest(request2, runtimeTools) {
6559
7300
  ...tool.config ? { config: tool.config } : {}
6560
7301
  }));
6561
7302
  return {
6562
- ...request2,
7303
+ ...request3,
6563
7304
  tools: {
6564
- ...request2.tools,
7305
+ ...request3.tools,
6565
7306
  runtimeTools: [...existing, ...converted]
6566
7307
  }
6567
7308
  };
@@ -6637,21 +7378,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6637
7378
  * Attach approved runtime tools to an agent execute request.
6638
7379
  * Returns a new request object and does not mutate the original.
6639
7380
  */
6640
- attachApprovedRuntimeTools(request2, runtimeTools) {
6641
- return appendRuntimeToolsToAgentRequest(request2, runtimeTools);
7381
+ attachApprovedRuntimeTools(request3, runtimeTools) {
7382
+ return appendRuntimeToolsToAgentRequest(request3, runtimeTools);
6642
7383
  }
6643
7384
  /**
6644
7385
  * Validate a generated runtime tool proposal and append it to an agent execute
6645
7386
  * request if approved, in one call.
6646
7387
  */
6647
- applyGeneratedRuntimeToolProposal(request2, proposal, options) {
7388
+ applyGeneratedRuntimeToolProposal(request3, proposal, options) {
6648
7389
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
6649
7390
  if (!decision.approved || !decision.tool) {
6650
- return { decision, request: request2 };
7391
+ return { decision, request: request3 };
6651
7392
  }
6652
7393
  return {
6653
7394
  decision,
6654
- request: appendRuntimeToolsToAgentRequest(request2, [decision.tool])
7395
+ request: appendRuntimeToolsToAgentRequest(request3, [decision.tool])
6655
7396
  };
6656
7397
  }
6657
7398
  /**
@@ -7410,11 +8151,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7410
8151
  * setting. This never mutates persisted marathon history; masking/offloading
7411
8152
  * is a send-time view over the full-fidelity ledger/history.
7412
8153
  */
7413
- deriveToolContextMessages(messages, taskName, mode, window) {
8154
+ deriveToolContextMessages(messages, taskName, mode, window, offloadRecorder) {
7414
8155
  if (mode === "full-inline") return [...messages];
7415
8156
  const maskMessage = (msg) => ({
7416
8157
  ...msg,
7417
- toolResults: (msg.toolResults ?? []).map((tr) => this.compactOneResult(tr, taskName, mode))
8158
+ toolResults: (msg.toolResults ?? []).map(
8159
+ (tr) => this.compactOneResult(tr, taskName, mode, offloadRecorder)
8160
+ )
7418
8161
  });
7419
8162
  const view = [...messages];
7420
8163
  if (window === "session") {
@@ -7448,12 +8191,18 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7448
8191
  }
7449
8192
  return view;
7450
8193
  }
7451
- compactOneResult(tr, taskName, mode) {
8194
+ compactOneResult(tr, taskName, mode, offloadRecorder) {
7452
8195
  if (typeof tr.result === "string" && tr.result.startsWith("[")) return tr;
7453
8196
  if (mode === "hot-tail") {
7454
8197
  return {
7455
8198
  ...tr,
7456
- result: this.offloadToolResult(taskName, tr.toolCallId, tr.toolName, tr.result)
8199
+ result: this.offloadToolResult(
8200
+ taskName,
8201
+ tr.toolCallId,
8202
+ tr.toolName,
8203
+ tr.result,
8204
+ offloadRecorder
8205
+ )
7457
8206
  };
7458
8207
  }
7459
8208
  return { ...tr, result: `[Output from ${tr.toolName} masked \u2014 re-run the tool if needed]` };
@@ -7469,9 +8218,16 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7469
8218
  return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
7470
8219
  }
7471
8220
  // chars
7472
- offloadToolResult(taskName, toolCallId, toolName, result) {
8221
+ offloadToolResult(taskName, toolCallId, toolName, result, offloadRecorder) {
7473
8222
  const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
7474
8223
  if (resultStr.length <= this.TOOL_OUTPUT_INLINE_THRESHOLD) return result;
8224
+ if (offloadRecorder) {
8225
+ try {
8226
+ const recorded = offloadRecorder({ toolCallId, toolName, content: resultStr });
8227
+ if (recorded?.reference) return recorded.reference;
8228
+ } catch {
8229
+ }
8230
+ }
7475
8231
  let fs;
7476
8232
  try {
7477
8233
  const dynamicRequire = (0, eval)('typeof require !== "undefined" ? require : undefined');
@@ -8044,8 +8800,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8044
8800
  }
8045
8801
  buildStuckTurnRecoveryMessage(state, workflow) {
8046
8802
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
8047
- if (currentPhase?.buildRecoveryMessage) {
8048
- return currentPhase.buildRecoveryMessage(state);
8803
+ const phaseMessage = currentPhase?.buildRecoveryMessage?.(state);
8804
+ if (phaseMessage) return phaseMessage;
8805
+ const emptySessions = state.consecutiveEmptySessions || 0;
8806
+ if (shouldInjectEmptySessionNudge(workflow.stallPolicy, emptySessions)) {
8807
+ return buildEmptySessionNudge(emptySessions);
8049
8808
  }
8050
8809
  return void 0;
8051
8810
  }
@@ -8171,6 +8930,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8171
8930
  state.originalMessage = options.message;
8172
8931
  }
8173
8932
  const queuedSteeringMessages = options.getQueuedUserMessages?.() ?? [];
8933
+ if (queuedSteeringMessages.length > 0) {
8934
+ state.consecutiveEmptySessions = 0;
8935
+ state.stallEscalationRequested = void 0;
8936
+ }
8174
8937
  const preparedSession = await this.prepareSessionContext(
8175
8938
  options.message,
8176
8939
  state,
@@ -8191,7 +8954,8 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8191
8954
  onContextCompaction: options.onContextCompaction,
8192
8955
  onContextNotice: options.onContextNotice,
8193
8956
  toolContextMode: options.toolContextMode || "hot-tail",
8194
- toolWindow: options.toolWindow ?? "session"
8957
+ toolWindow: options.toolWindow ?? "session",
8958
+ offloadRecorder: options.offloadRecorder
8195
8959
  },
8196
8960
  queuedSteeringMessages
8197
8961
  );
@@ -8411,11 +9175,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8411
9175
  } else {
8412
9176
  state.lastCompletionRejectionReason = void 0;
8413
9177
  }
8414
- const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted;
9178
+ const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted || options.hasQueuedUserMessages?.() === true;
8415
9179
  if (sessionHadActions) {
8416
9180
  state.consecutiveEmptySessions = 0;
9181
+ state.stallEscalationRequested = void 0;
8417
9182
  } else {
8418
9183
  state.consecutiveEmptySessions = (state.consecutiveEmptySessions || 0) + 1;
9184
+ if (shouldRequestModelEscalation(workflow.stallPolicy, state.consecutiveEmptySessions)) {
9185
+ state.stallEscalationRequested = true;
9186
+ }
8419
9187
  }
8420
9188
  if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
8421
9189
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
@@ -8444,7 +9212,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8444
9212
  state.status = "budget_exceeded";
8445
9213
  } else if (acceptedTaskCompletion) {
8446
9214
  state.status = "complete";
8447
- } else if ((state.consecutiveEmptySessions || 0) >= 3) {
9215
+ } else if ((state.consecutiveEmptySessions || 0) >= resolveStallStopAfter(workflow.stallPolicy)) {
8448
9216
  state.status = "stalled";
8449
9217
  } else if (maxCost && state.totalCost >= maxCost) {
8450
9218
  state.status = "budget_exceeded";
@@ -9047,7 +9815,8 @@ Do NOT redo any of the above work.`
9047
9815
  sourceReplayHistoryMessages,
9048
9816
  state.taskName,
9049
9817
  compactionOptions?.toolContextMode || "hot-tail",
9050
- compactionOptions?.toolWindow ?? "session"
9818
+ compactionOptions?.toolWindow ?? "session",
9819
+ compactionOptions?.offloadRecorder
9051
9820
  );
9052
9821
  const continuationGuardrail = this.buildContinuationGuardrail(state);
9053
9822
  const defaultContinueMessage = "Continue the task. Review your prior work above and proceed with any remaining work. If everything is already complete, respond with TASK_COMPLETE.";
@@ -9210,7 +9979,8 @@ Do NOT redo any of the above work.`
9210
9979
  sourceHistoryMessages,
9211
9980
  state.taskName,
9212
9981
  compactionOptions?.toolContextMode || "hot-tail",
9213
- compactionOptions?.toolWindow ?? "session"
9982
+ compactionOptions?.toolWindow ?? "session",
9983
+ compactionOptions?.offloadRecorder
9214
9984
  );
9215
9985
  const historyArtifactReferences = this.extractArtifactReferencesFromMessages(historyMessages);
9216
9986
  const summaryText = this.generateCompactSummary(
@@ -9857,7 +10627,7 @@ var RuntypeClient2 = class {
9857
10627
  clearApiKey() {
9858
10628
  delete this.headers.Authorization;
9859
10629
  }
9860
- async runWithLocalTools(request2, localTools, arg3, arg4) {
10630
+ async runWithLocalTools(request3, localTools, arg3, arg4) {
9861
10631
  const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
9862
10632
  const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
9863
10633
  const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
@@ -9871,12 +10641,12 @@ var RuntypeClient2 = class {
9871
10641
  ...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
9872
10642
  })) : [];
9873
10643
  const modifiedRequest = {
9874
- ...request2,
10644
+ ...request3,
9875
10645
  ...derivedClientTools.length > 0 ? {
9876
- clientTools: [...request2.clientTools ?? [], ...derivedClientTools]
10646
+ clientTools: [...request3.clientTools ?? [], ...derivedClientTools]
9877
10647
  } : {},
9878
10648
  options: {
9879
- ...request2.options || {},
10649
+ ...request3.options || {},
9880
10650
  streamResponse: isStreaming
9881
10651
  }
9882
10652
  };
@@ -10314,20 +11084,20 @@ var BatchBuilder = class {
10314
11084
  if (!this.recordType) {
10315
11085
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
10316
11086
  }
10317
- const request2 = {
11087
+ const request3 = {
10318
11088
  flowId: this.flowId,
10319
11089
  recordType: this.recordType
10320
11090
  };
10321
11091
  if (Object.keys(this.batchOptions).length > 0) {
10322
- request2.options = this.batchOptions;
11092
+ request3.options = this.batchOptions;
10323
11093
  }
10324
11094
  if (this.filterConfig) {
10325
- request2.filter = this.filterConfig;
11095
+ request3.filter = this.filterConfig;
10326
11096
  }
10327
11097
  if (this.limitConfig !== void 0) {
10328
- request2.limit = this.limitConfig;
11098
+ request3.limit = this.limitConfig;
10329
11099
  }
10330
- return request2;
11100
+ return request3;
10331
11101
  }
10332
11102
  /**
10333
11103
  * Execute the batch operation
@@ -10484,32 +11254,32 @@ var EvalBuilder = class {
10484
11254
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
10485
11255
  );
10486
11256
  }
10487
- const request2 = {};
11257
+ const request3 = {};
10488
11258
  if (this.flowId) {
10489
- request2.flowId = this.flowId;
11259
+ request3.flowId = this.flowId;
10490
11260
  } else if (this.virtualFlow) {
10491
- request2.flow = this.virtualFlow;
11261
+ request3.flow = this.virtualFlow;
10492
11262
  }
10493
11263
  if (this.recordType) {
10494
- request2.recordType = this.recordType;
11264
+ request3.recordType = this.recordType;
10495
11265
  } else if (this.inlineRecords) {
10496
- request2.records = this.inlineRecords;
11266
+ request3.records = this.inlineRecords;
10497
11267
  }
10498
11268
  if (this.modelOverrides) {
10499
- request2.modelOverrides = this.modelOverrides;
11269
+ request3.modelOverrides = this.modelOverrides;
10500
11270
  } else if (this.modelConfigs) {
10501
- request2.modelConfigs = this.modelConfigs;
11271
+ request3.modelConfigs = this.modelConfigs;
10502
11272
  }
10503
11273
  if (Object.keys(this.evalOptions).length > 0) {
10504
- request2.options = this.evalOptions;
11274
+ request3.options = this.evalOptions;
10505
11275
  }
10506
11276
  if (this.filterConfig) {
10507
- request2.filter = this.filterConfig;
11277
+ request3.filter = this.filterConfig;
10508
11278
  }
10509
11279
  if (this.limitConfig !== void 0) {
10510
- request2.limit = this.limitConfig;
11280
+ request3.limit = this.limitConfig;
10511
11281
  }
10512
- return request2;
11282
+ return request3;
10513
11283
  }
10514
11284
  /**
10515
11285
  * Execute the evaluation
@@ -10993,6 +11763,8 @@ export {
10993
11763
  ClientTokensEndpoint,
10994
11764
  ContextTemplatesEndpoint,
10995
11765
  ConversationsEndpoint,
11766
+ DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
11767
+ DEFAULT_STALL_STOP_AFTER,
10996
11768
  DispatchEndpoint,
10997
11769
  EvalBuilder,
10998
11770
  EvalEndpoint,
@@ -11026,36 +11798,57 @@ export {
11026
11798
  SkillProposalsNamespace,
11027
11799
  SkillsNamespace,
11028
11800
  SurfacesEndpoint,
11801
+ ToolDriftError,
11802
+ ToolEnsureConflictError,
11029
11803
  ToolsEndpoint,
11804
+ ToolsNamespace,
11030
11805
  UsersEndpoint,
11031
11806
  applyGeneratedRuntimeToolProposalToDispatchRequest,
11032
11807
  attachRuntimeToolsToDispatchRequest,
11808
+ buildEmptySessionNudge,
11033
11809
  buildGeneratedRuntimeToolGateOutput,
11034
11810
  buildLedgerOffloadReference,
11811
+ buildPolicyGuidance,
11035
11812
  buildSendViewOffloadMarker,
11813
+ compileWorkflowConfig,
11036
11814
  computeAgentContentHash,
11037
11815
  computeFlowContentHash,
11816
+ computeToolContentHash,
11038
11817
  createClient,
11039
11818
  createExternalTool,
11040
11819
  defaultWorkflow,
11820
+ defaultWorkflowConfig,
11041
11821
  defineAgent,
11042
11822
  defineFlow,
11823
+ definePlaybook,
11824
+ defineTool,
11043
11825
  deployWorkflow,
11826
+ ensureDefaultWorkflowHooks,
11044
11827
  evaluateGeneratedRuntimeToolProposal,
11045
11828
  extractDeclaredToolResultChars,
11046
11829
  gameWorkflow,
11047
11830
  getDefaultPlanPath,
11048
11831
  getLikelySupportingCandidatePaths,
11832
+ interpolateWorkflowTemplate,
11049
11833
  isDiscoveryToolName,
11050
11834
  isMarathonArtifactPath,
11051
11835
  isPreservationSensitiveTask,
11836
+ isWorkflowHookRef,
11837
+ listWorkflowHooks,
11052
11838
  normalizeAgentDefinition,
11053
11839
  normalizeCandidatePath,
11840
+ normalizeToolDefinition,
11054
11841
  parseFinalBuffer,
11055
11842
  parseLedgerArtifactRelativePath,
11056
11843
  parseOffloadedOutputId,
11057
11844
  parseSSEChunk,
11058
11845
  processStream,
11846
+ registerWorkflowHook,
11847
+ resolveStallStopAfter,
11848
+ resolveWorkflowHook,
11059
11849
  sanitizeTaskSlug,
11060
- streamEvents
11850
+ shouldInjectEmptySessionNudge,
11851
+ shouldRequestModelEscalation,
11852
+ streamEvents,
11853
+ unregisterWorkflowHook
11061
11854
  };