@runtypelabs/sdk 4.9.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,9 +3,6 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __esm = (fn, res) => function __init() {
7
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
- };
9
6
  var __export = (target, all) => {
10
7
  for (var name in all)
11
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -20,17 +17,113 @@ var __copyProps = (to, from, except, desc) => {
20
17
  };
21
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
19
 
23
- // src/stream-utils.ts
24
- var stream_utils_exports = {};
25
- __export(stream_utils_exports, {
26
- flowErrorMessage: () => flowErrorMessage,
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentDriftError: () => AgentDriftError,
24
+ AgentEnsureConflictError: () => AgentEnsureConflictError,
25
+ AgentVersionsEndpoint: () => AgentVersionsEndpoint,
26
+ AgentsEndpoint: () => AgentsEndpoint,
27
+ AgentsNamespace: () => AgentsNamespace,
28
+ AnalyticsEndpoint: () => AnalyticsEndpoint,
29
+ ApiKeysEndpoint: () => ApiKeysEndpoint,
30
+ AppsEndpoint: () => AppsEndpoint,
31
+ BatchBuilder: () => BatchBuilder,
32
+ BatchesNamespace: () => BatchesNamespace,
33
+ BillingEndpoint: () => BillingEndpoint,
34
+ ChatEndpoint: () => ChatEndpoint,
35
+ ClientBatchBuilder: () => ClientBatchBuilder,
36
+ ClientEvalBuilder: () => ClientEvalBuilder,
37
+ ClientFlowBuilder: () => ClientFlowBuilder,
38
+ ClientTokensEndpoint: () => ClientTokensEndpoint,
39
+ ContextTemplatesEndpoint: () => ContextTemplatesEndpoint,
40
+ ConversationsEndpoint: () => ConversationsEndpoint,
41
+ DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS: () => DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
42
+ DEFAULT_STALL_STOP_AFTER: () => DEFAULT_STALL_STOP_AFTER,
43
+ DispatchEndpoint: () => DispatchEndpoint,
44
+ EvalBuilder: () => EvalBuilder,
45
+ EvalEndpoint: () => EvalEndpoint,
46
+ EvalRunner: () => EvalRunner,
47
+ EvalsNamespace: () => EvalsNamespace,
48
+ FlowBuilder: () => FlowBuilder,
49
+ FlowDriftError: () => FlowDriftError,
50
+ FlowEnsureConflictError: () => FlowEnsureConflictError,
51
+ FlowResult: () => FlowResult,
52
+ FlowStepsEndpoint: () => FlowStepsEndpoint,
53
+ FlowVersionsEndpoint: () => FlowVersionsEndpoint,
54
+ FlowsEndpoint: () => FlowsEndpoint,
55
+ FlowsNamespace: () => FlowsNamespace,
56
+ IntegrationsEndpoint: () => IntegrationsEndpoint,
57
+ LEDGER_ARTIFACT_LINE_PREFIX: () => LEDGER_ARTIFACT_LINE_PREFIX,
58
+ LogsEndpoint: () => LogsEndpoint,
59
+ ModelConfigsEndpoint: () => ModelConfigsEndpoint,
60
+ PromptRunner: () => PromptRunner,
61
+ PromptsEndpoint: () => PromptsEndpoint,
62
+ PromptsNamespace: () => PromptsNamespace,
63
+ ProviderKeysEndpoint: () => ProviderKeysEndpoint,
64
+ RecordsEndpoint: () => RecordsEndpoint,
65
+ Runtype: () => Runtype,
66
+ RuntypeApiError: () => RuntypeApiError,
67
+ RuntypeClient: () => RuntypeClient2,
68
+ RuntypeFlowBuilder: () => RuntypeFlowBuilder,
69
+ STEP_FIELD_REGISTRY: () => STEP_FIELD_REGISTRY,
70
+ STEP_TYPE_TO_METHOD: () => STEP_TYPE_TO_METHOD,
71
+ SchedulesEndpoint: () => SchedulesEndpoint,
72
+ SecretsEndpoint: () => SecretsEndpoint,
73
+ SkillProposalsNamespace: () => SkillProposalsNamespace,
74
+ SkillsNamespace: () => SkillsNamespace,
75
+ SurfacesEndpoint: () => SurfacesEndpoint,
76
+ ToolsEndpoint: () => ToolsEndpoint,
77
+ UsersEndpoint: () => UsersEndpoint,
78
+ applyGeneratedRuntimeToolProposalToDispatchRequest: () => applyGeneratedRuntimeToolProposalToDispatchRequest,
79
+ attachRuntimeToolsToDispatchRequest: () => attachRuntimeToolsToDispatchRequest,
80
+ buildEmptySessionNudge: () => buildEmptySessionNudge,
81
+ buildGeneratedRuntimeToolGateOutput: () => buildGeneratedRuntimeToolGateOutput,
82
+ buildLedgerOffloadReference: () => buildLedgerOffloadReference,
83
+ buildPolicyGuidance: () => buildPolicyGuidance,
84
+ buildSendViewOffloadMarker: () => buildSendViewOffloadMarker,
85
+ compileWorkflowConfig: () => compileWorkflowConfig,
86
+ computeAgentContentHash: () => computeAgentContentHash,
87
+ computeFlowContentHash: () => computeFlowContentHash,
88
+ createClient: () => createClient,
89
+ createExternalTool: () => createExternalTool,
90
+ defaultWorkflow: () => defaultWorkflow,
91
+ defaultWorkflowConfig: () => defaultWorkflowConfig,
92
+ defineAgent: () => defineAgent,
93
+ defineFlow: () => defineFlow,
94
+ definePlaybook: () => definePlaybook,
95
+ deployWorkflow: () => deployWorkflow,
96
+ ensureDefaultWorkflowHooks: () => ensureDefaultWorkflowHooks,
97
+ evaluateGeneratedRuntimeToolProposal: () => evaluateGeneratedRuntimeToolProposal,
98
+ extractDeclaredToolResultChars: () => extractDeclaredToolResultChars,
99
+ gameWorkflow: () => gameWorkflow,
100
+ getDefaultPlanPath: () => getDefaultPlanPath,
101
+ getLikelySupportingCandidatePaths: () => getLikelySupportingCandidatePaths,
102
+ interpolateWorkflowTemplate: () => interpolateWorkflowTemplate,
103
+ isDiscoveryToolName: () => isDiscoveryToolName,
104
+ isMarathonArtifactPath: () => isMarathonArtifactPath,
105
+ isPreservationSensitiveTask: () => isPreservationSensitiveTask,
106
+ isWorkflowHookRef: () => isWorkflowHookRef,
107
+ listWorkflowHooks: () => listWorkflowHooks,
108
+ normalizeAgentDefinition: () => normalizeAgentDefinition,
109
+ normalizeCandidatePath: () => normalizeCandidatePath,
27
110
  parseFinalBuffer: () => parseFinalBuffer,
111
+ parseLedgerArtifactRelativePath: () => parseLedgerArtifactRelativePath,
112
+ parseOffloadedOutputId: () => parseOffloadedOutputId,
28
113
  parseSSEChunk: () => parseSSEChunk,
29
114
  processStream: () => processStream,
30
- stepDeltaText: () => stepDeltaText,
31
- stepDisplayName: () => stepDisplayName,
32
- streamEvents: () => streamEvents
115
+ registerWorkflowHook: () => registerWorkflowHook,
116
+ resolveStallStopAfter: () => resolveStallStopAfter,
117
+ resolveWorkflowHook: () => resolveWorkflowHook,
118
+ sanitizeTaskSlug: () => sanitizeTaskSlug,
119
+ shouldInjectEmptySessionNudge: () => shouldInjectEmptySessionNudge,
120
+ shouldRequestModelEscalation: () => shouldRequestModelEscalation,
121
+ streamEvents: () => streamEvents,
122
+ unregisterWorkflowHook: () => unregisterWorkflowHook
33
123
  });
124
+ module.exports = __toCommonJS(index_exports);
125
+
126
+ // src/stream-utils.ts
34
127
  function parseSSEChunk(chunk, buffer) {
35
128
  buffer += chunk;
36
129
  const lines = buffer.split("\n");
@@ -280,86 +373,8 @@ async function* streamEvents(response) {
280
373
  reader.releaseLock();
281
374
  }
282
375
  }
283
- var init_stream_utils = __esm({
284
- "src/stream-utils.ts"() {
285
- "use strict";
286
- }
287
- });
288
-
289
- // src/index.ts
290
- var index_exports = {};
291
- __export(index_exports, {
292
- AgentVersionsEndpoint: () => AgentVersionsEndpoint,
293
- AgentsEndpoint: () => AgentsEndpoint,
294
- AnalyticsEndpoint: () => AnalyticsEndpoint,
295
- ApiKeysEndpoint: () => ApiKeysEndpoint,
296
- BatchBuilder: () => BatchBuilder,
297
- BatchesNamespace: () => BatchesNamespace,
298
- BillingEndpoint: () => BillingEndpoint,
299
- ChatEndpoint: () => ChatEndpoint,
300
- ClientBatchBuilder: () => ClientBatchBuilder,
301
- ClientEvalBuilder: () => ClientEvalBuilder,
302
- ClientFlowBuilder: () => ClientFlowBuilder,
303
- ClientTokensEndpoint: () => ClientTokensEndpoint,
304
- ContextTemplatesEndpoint: () => ContextTemplatesEndpoint,
305
- ConversationsEndpoint: () => ConversationsEndpoint,
306
- DispatchEndpoint: () => DispatchEndpoint,
307
- EvalBuilder: () => EvalBuilder,
308
- EvalEndpoint: () => EvalEndpoint,
309
- EvalRunner: () => EvalRunner,
310
- EvalsNamespace: () => EvalsNamespace,
311
- FlowBuilder: () => FlowBuilder,
312
- FlowResult: () => FlowResult,
313
- FlowStepsEndpoint: () => FlowStepsEndpoint,
314
- FlowVersionsEndpoint: () => FlowVersionsEndpoint,
315
- FlowsEndpoint: () => FlowsEndpoint,
316
- FlowsNamespace: () => FlowsNamespace,
317
- IntegrationsEndpoint: () => IntegrationsEndpoint,
318
- LogsEndpoint: () => LogsEndpoint,
319
- ModelConfigsEndpoint: () => ModelConfigsEndpoint,
320
- PromptRunner: () => PromptRunner,
321
- PromptsEndpoint: () => PromptsEndpoint,
322
- PromptsNamespace: () => PromptsNamespace,
323
- ProviderKeysEndpoint: () => ProviderKeysEndpoint,
324
- RecordsEndpoint: () => RecordsEndpoint,
325
- Runtype: () => Runtype,
326
- RuntypeApiError: () => RuntypeApiError,
327
- RuntypeClient: () => RuntypeClient2,
328
- RuntypeFlowBuilder: () => RuntypeFlowBuilder,
329
- STEP_FIELD_REGISTRY: () => STEP_FIELD_REGISTRY,
330
- STEP_TYPE_TO_METHOD: () => STEP_TYPE_TO_METHOD,
331
- SchedulesEndpoint: () => SchedulesEndpoint,
332
- SecretsEndpoint: () => SecretsEndpoint,
333
- SkillProposalsNamespace: () => SkillProposalsNamespace,
334
- SkillsNamespace: () => SkillsNamespace,
335
- SurfacesEndpoint: () => SurfacesEndpoint,
336
- ToolsEndpoint: () => ToolsEndpoint,
337
- UsersEndpoint: () => UsersEndpoint,
338
- applyGeneratedRuntimeToolProposalToDispatchRequest: () => applyGeneratedRuntimeToolProposalToDispatchRequest,
339
- attachRuntimeToolsToDispatchRequest: () => attachRuntimeToolsToDispatchRequest,
340
- buildGeneratedRuntimeToolGateOutput: () => buildGeneratedRuntimeToolGateOutput,
341
- createClient: () => createClient,
342
- createExternalTool: () => createExternalTool,
343
- defaultWorkflow: () => defaultWorkflow,
344
- deployWorkflow: () => deployWorkflow,
345
- evaluateGeneratedRuntimeToolProposal: () => evaluateGeneratedRuntimeToolProposal,
346
- gameWorkflow: () => gameWorkflow,
347
- getDefaultPlanPath: () => getDefaultPlanPath,
348
- getLikelySupportingCandidatePaths: () => getLikelySupportingCandidatePaths,
349
- isDiscoveryToolName: () => isDiscoveryToolName,
350
- isMarathonArtifactPath: () => isMarathonArtifactPath,
351
- isPreservationSensitiveTask: () => isPreservationSensitiveTask,
352
- normalizeCandidatePath: () => normalizeCandidatePath,
353
- parseFinalBuffer: () => parseFinalBuffer,
354
- parseSSEChunk: () => parseSSEChunk,
355
- processStream: () => processStream,
356
- sanitizeTaskSlug: () => sanitizeTaskSlug,
357
- streamEvents: () => streamEvents
358
- });
359
- module.exports = __toCommonJS(index_exports);
360
376
 
361
377
  // src/flow-result.ts
362
- init_stream_utils();
363
378
  var FlowResult = class {
364
379
  constructor(response, summary) {
365
380
  this.consumed = false;
@@ -495,7 +510,6 @@ var FlowResult = class {
495
510
  };
496
511
 
497
512
  // src/flow-builder.ts
498
- init_stream_utils();
499
513
  async function validateInlineFlow(client, args, savedFlowHint) {
500
514
  if (args.existingFlowId) {
501
515
  throw new Error(
@@ -1205,20 +1219,20 @@ var FlowBuilder = class {
1205
1219
  */
1206
1220
  build() {
1207
1221
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
1208
- const request = { flow };
1222
+ const request2 = { flow };
1209
1223
  if (this.recordConfig) {
1210
- request.record = this.recordConfig;
1224
+ request2.record = this.recordConfig;
1211
1225
  }
1212
1226
  if (this.messagesConfig) {
1213
- request.messages = this.messagesConfig;
1227
+ request2.messages = this.messagesConfig;
1214
1228
  }
1215
1229
  if (this.inputsConfig) {
1216
- request.inputs = this.inputsConfig;
1230
+ request2.inputs = this.inputsConfig;
1217
1231
  }
1218
1232
  if (Object.keys(this.optionsConfig).length > 0) {
1219
- request.options = this.optionsConfig;
1233
+ request2.options = this.optionsConfig;
1220
1234
  }
1221
- return request;
1235
+ return request2;
1222
1236
  }
1223
1237
  /**
1224
1238
  * Validate this prospective flow against the public validation endpoint
@@ -1455,22 +1469,22 @@ function resolveBatchExecutionId(pausedTools) {
1455
1469
  return "";
1456
1470
  }
1457
1471
 
1458
- // src/flows-namespace.ts
1459
- function isRecord(value) {
1472
+ // src/flows-ensure.ts
1473
+ function isPlainObject(value) {
1460
1474
  return value !== null && typeof value === "object" && !Array.isArray(value);
1461
1475
  }
1462
- function normalizeConfig(config) {
1463
- if (!isRecord(config)) return {};
1476
+ function normalizeConfigForHash(config) {
1477
+ if (!isPlainObject(config)) return {};
1464
1478
  const normalized = {};
1465
1479
  for (const key of Object.keys(config).sort()) {
1466
1480
  const value = config[key];
1467
1481
  if (value === void 0) continue;
1468
1482
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
1469
- normalized[key] = normalizeConfig(value);
1483
+ normalized[key] = normalizeConfigForHash(value);
1470
1484
  } else if (Array.isArray(value)) {
1471
1485
  normalized[key] = value.map((item) => {
1472
1486
  if (item !== null && typeof item === "object" && !Array.isArray(item)) {
1473
- return normalizeConfig(item);
1487
+ return normalizeConfigForHash(item);
1474
1488
  }
1475
1489
  return item;
1476
1490
  });
@@ -1481,28 +1495,249 @@ function normalizeConfig(config) {
1481
1495
  return normalized;
1482
1496
  }
1483
1497
  function normalizeStepForHash(step) {
1484
- const stepObj = isRecord(step) ? step : {};
1498
+ const stepObj = isPlainObject(step) ? step : {};
1485
1499
  return {
1486
1500
  type: typeof stepObj.type === "string" ? stepObj.type : "",
1487
1501
  name: typeof stepObj.name === "string" ? stepObj.name : "",
1488
1502
  enabled: stepObj.enabled !== false,
1489
1503
  ...typeof stepObj.when === "string" ? { when: stepObj.when } : {},
1490
- config: normalizeConfig(stepObj.config),
1504
+ config: normalizeConfigForHash(stepObj.config),
1491
1505
  order: typeof stepObj.order === "number" ? stepObj.order : 0
1492
1506
  };
1493
1507
  }
1494
1508
  async function computeFlowContentHash(steps) {
1495
1509
  const normalized = [...steps].sort((a, b) => {
1496
- const orderA = isRecord(a) && typeof a.order === "number" ? a.order : 0;
1497
- const orderB = isRecord(b) && typeof b.order === "number" ? b.order : 0;
1510
+ const orderA = isPlainObject(a) && typeof a.order === "number" ? a.order : 0;
1511
+ const orderB = isPlainObject(b) && typeof b.order === "number" ? b.order : 0;
1498
1512
  return orderA - orderB;
1499
1513
  }).map(normalizeStepForHash);
1500
1514
  const serialized = JSON.stringify(normalized);
1501
1515
  const encoded = new TextEncoder().encode(serialized);
1502
1516
  const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
1503
- const hashArray = new Uint8Array(hashBuffer);
1504
- return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
1517
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
1518
+ }
1519
+ var DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "steps"]);
1520
+ var DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
1521
+ "type",
1522
+ "name",
1523
+ "order",
1524
+ "enabled",
1525
+ "when",
1526
+ "config"
1527
+ ]);
1528
+ function collectStepNonPortableToolRefs(config, path) {
1529
+ const found = [];
1530
+ const tools = config.tools;
1531
+ const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
1532
+ const scanArray = (value, subPath) => {
1533
+ if (!Array.isArray(value)) return;
1534
+ value.forEach((ref, i) => {
1535
+ if (isAccountScoped(ref)) found.push(`${subPath}[${i}]`);
1536
+ });
1537
+ };
1538
+ const scanKeys = (value, subPath) => {
1539
+ if (!isPlainObject(value)) return;
1540
+ for (const key of Object.keys(value)) {
1541
+ if (isAccountScoped(key)) found.push(`${subPath}.${key}`);
1542
+ }
1543
+ };
1544
+ if (isPlainObject(tools)) {
1545
+ scanArray(tools.toolIds, `${path}.tools.toolIds`);
1546
+ scanKeys(tools.toolConfigs, `${path}.tools.toolConfigs`);
1547
+ scanKeys(tools.perToolLimits, `${path}.tools.perToolLimits`);
1548
+ if (isPlainObject(tools.approval)) {
1549
+ scanArray(tools.approval.require, `${path}.tools.approval.require`);
1550
+ }
1551
+ if (isPlainObject(tools.subagentConfig)) {
1552
+ scanArray(tools.subagentConfig.toolPool, `${path}.tools.subagentConfig.toolPool`);
1553
+ }
1554
+ if (isPlainObject(tools.codeModeConfig)) {
1555
+ scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
1556
+ }
1557
+ }
1558
+ for (const branch of ["trueSteps", "falseSteps"]) {
1559
+ const nested = config[branch];
1560
+ if (!Array.isArray(nested)) continue;
1561
+ nested.forEach((nestedStep, i) => {
1562
+ if (isPlainObject(nestedStep) && isPlainObject(nestedStep.config)) {
1563
+ found.push(
1564
+ ...collectStepNonPortableToolRefs(
1565
+ nestedStep.config,
1566
+ `${path}.${branch}[${i}].config`
1567
+ )
1568
+ );
1569
+ }
1570
+ });
1571
+ }
1572
+ return found;
1573
+ }
1574
+ function defineFlow(input) {
1575
+ if (!input || typeof input !== "object") {
1576
+ throw new Error("defineFlow requires a definition object");
1577
+ }
1578
+ if (typeof input.name !== "string" || input.name.length === 0) {
1579
+ throw new Error('defineFlow requires a non-empty string "name"');
1580
+ }
1581
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_FLOW_TOP_LEVEL_KEYS.has(key));
1582
+ if (unknownKeys.length > 0) {
1583
+ throw new Error(
1584
+ `defineFlow: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name and steps. (Description is not part of the v1 ensure surface.)`
1585
+ );
1586
+ }
1587
+ if (!Array.isArray(input.steps) || input.steps.length === 0) {
1588
+ throw new Error('defineFlow requires a non-empty "steps" array');
1589
+ }
1590
+ const steps = input.steps.map((step, index) => {
1591
+ if (!isPlainObject(step)) {
1592
+ throw new Error(`defineFlow: steps[${index}] must be an object`);
1593
+ }
1594
+ if (typeof step.type !== "string" || step.type.length === 0) {
1595
+ throw new Error(`defineFlow: steps[${index}] requires a non-empty string "type"`);
1596
+ }
1597
+ if (typeof step.name !== "string" || step.name.length === 0) {
1598
+ throw new Error(`defineFlow: steps[${index}] requires a non-empty string "name"`);
1599
+ }
1600
+ const unknownStepKeys = Object.keys(step).filter((key) => !DEFINE_FLOW_STEP_KEYS.has(key));
1601
+ if (unknownStepKeys.length > 0) {
1602
+ throw new Error(
1603
+ `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.)`
1604
+ );
1605
+ }
1606
+ const config = isPlainObject(step.config) ? step.config : void 0;
1607
+ if (config) {
1608
+ const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
1609
+ if (nonPortable.length > 0) {
1610
+ throw new Error(
1611
+ `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.`
1612
+ );
1613
+ }
1614
+ }
1615
+ return {
1616
+ type: step.type,
1617
+ name: step.name,
1618
+ // Explicit 1-based order (the flow builder's convention) so the local
1619
+ // probe hash agrees with the server's persisted step order.
1620
+ order: typeof step.order === "number" ? step.order : index + 1,
1621
+ ...step.enabled !== void 0 ? { enabled: step.enabled } : {},
1622
+ ...typeof step.when === "string" ? { when: step.when } : {},
1623
+ ...config ? { config } : {}
1624
+ };
1625
+ });
1626
+ return { name: input.name, steps };
1627
+ }
1628
+ var FlowEnsureConflictError = class extends Error {
1629
+ constructor(body) {
1630
+ super(body.error ?? `Flow ensure conflict: ${body.code}`);
1631
+ this.name = "FlowEnsureConflictError";
1632
+ this.code = body.code;
1633
+ this.lastModifiedSource = body.lastModifiedSource;
1634
+ this.modifiedAt = body.modifiedAt;
1635
+ this.currentHash = body.currentHash;
1636
+ }
1637
+ };
1638
+ var FlowDriftError = class extends Error {
1639
+ constructor(plan) {
1640
+ super(
1641
+ `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.`
1642
+ );
1643
+ this.name = "FlowDriftError";
1644
+ this.plan = plan;
1645
+ }
1646
+ };
1647
+ function parseRequestError(err) {
1648
+ if (!(err instanceof Error)) return { status: null, body: null };
1649
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
1650
+ if (!match) return { status: null, body: null };
1651
+ try {
1652
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
1653
+ } catch {
1654
+ return { status: Number(match[1]), body: null };
1655
+ }
1656
+ }
1657
+ function toConflictError(err) {
1658
+ const { status, body } = parseRequestError(err);
1659
+ if (status !== 409 || !isPlainObject(body)) return null;
1660
+ const code = body.code;
1661
+ if (code !== "external_modification" && code !== "remote_changed") return null;
1662
+ return new FlowEnsureConflictError(
1663
+ body
1664
+ );
1665
+ }
1666
+ var serverHashMemo = /* @__PURE__ */ new WeakMap();
1667
+ function memoFor(client) {
1668
+ let memo = serverHashMemo.get(client);
1669
+ if (!memo) {
1670
+ memo = /* @__PURE__ */ new Map();
1671
+ serverHashMemo.set(client, memo);
1672
+ }
1673
+ return memo;
1674
+ }
1675
+ function memoize(memo, memoKey, result) {
1676
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
1677
+ }
1678
+ async function request(client, body) {
1679
+ try {
1680
+ return await client.post(
1681
+ "/flows/ensure",
1682
+ body
1683
+ );
1684
+ } catch (err) {
1685
+ const conflict = toConflictError(err);
1686
+ if (conflict) throw conflict;
1687
+ throw err;
1688
+ }
1689
+ }
1690
+ async function ensureFlow(client, definition, options = {}) {
1691
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
1692
+ const passthrough = {
1693
+ ...onConflict ? { onConflict } : {},
1694
+ ...release ? { release } : {},
1695
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
1696
+ };
1697
+ if (dryRun || expectNoChanges) {
1698
+ const plan = await request(client, {
1699
+ name: definition.name,
1700
+ definition,
1701
+ dryRun: true,
1702
+ ...passthrough
1703
+ });
1704
+ if (plan.result !== "plan") {
1705
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
1706
+ }
1707
+ if (expectNoChanges && plan.changes !== "none") {
1708
+ throw new FlowDriftError(plan);
1709
+ }
1710
+ return plan;
1711
+ }
1712
+ const memo = memoFor(client);
1713
+ const localHash = await computeFlowContentHash(definition.steps);
1714
+ const memoKey = `${definition.name} ${localHash}`;
1715
+ const contentHash = memo.get(memoKey) ?? localHash;
1716
+ const probe = await request(client, {
1717
+ name: definition.name,
1718
+ contentHash,
1719
+ ...passthrough
1720
+ });
1721
+ if (probe.result !== "definitionRequired") {
1722
+ memoize(memo, memoKey, probe);
1723
+ return probe;
1724
+ }
1725
+ const converged = await request(client, {
1726
+ name: definition.name,
1727
+ definition,
1728
+ ...passthrough
1729
+ });
1730
+ if (converged.result === "definitionRequired") {
1731
+ throw new Error("Server reported definitionRequired for a full-definition request");
1732
+ }
1733
+ memoize(memo, memoKey, converged);
1734
+ return converged;
1735
+ }
1736
+ async function pullFlow(client, name) {
1737
+ return client.get("/flows/pull", { name });
1505
1738
  }
1739
+
1740
+ // src/flows-namespace.ts
1506
1741
  var FlowsNamespace = class {
1507
1742
  constructor(getClient) {
1508
1743
  this.getClient = getClient;
@@ -1510,8 +1745,11 @@ var FlowsNamespace = class {
1510
1745
  /**
1511
1746
  * Create or update a flow by name (upsert mode)
1512
1747
  *
1513
- * The recommended pattern for code-first flow management.
1514
- * Creates the flow if it doesn't exist, updates if steps changed.
1748
+ * The recommended pattern for code-first flow management when you want to
1749
+ * save AND run in one dispatch. For a deploy-time, non-executing converge
1750
+ * (CI/CD config-as-code), use {@link ensure} instead — upsert and ensure
1751
+ * are siblings, not versions of each other: upsert is the runtime verb
1752
+ * (save-and-run), ensure is the deploy verb (converge only).
1515
1753
  *
1516
1754
  * @example
1517
1755
  * ```typescript
@@ -1526,6 +1764,33 @@ var FlowsNamespace = class {
1526
1764
  upsert(config) {
1527
1765
  return new RuntypeFlowBuilder(this.getClient, "upsert", config);
1528
1766
  }
1767
+ /**
1768
+ * Idempotently converge a `defineFlow` definition onto the platform —
1769
+ * the deploy-time, non-executing sibling of {@link upsert}. Hash-first:
1770
+ * the steady state is one tiny probe request. Creates an immutable version
1771
+ * snapshot on every change; never deletes; never executes the flow.
1772
+ *
1773
+ * @example
1774
+ * ```typescript
1775
+ * const def = defineFlow({ name: 'Onboarding Digest', steps: [...] })
1776
+ *
1777
+ * // Converge (CI/deploy).
1778
+ * const result = await Runtype.flows.ensure(def)
1779
+ *
1780
+ * // PR drift gate.
1781
+ * await Runtype.flows.ensure(def, { expectNoChanges: true })
1782
+ * ```
1783
+ */
1784
+ async ensure(definition, options = {}) {
1785
+ return ensureFlow(this.getClient(), definition, options);
1786
+ }
1787
+ /**
1788
+ * Pull the canonical definition + provenance for a flow by name — the
1789
+ * absorb-drift direction of the ensure protocol.
1790
+ */
1791
+ async pull(name) {
1792
+ return pullFlow(this.getClient(), name);
1793
+ }
1529
1794
  /**
1530
1795
  * Create a virtual flow (one-off, not saved)
1531
1796
  *
@@ -2211,9 +2476,8 @@ var RuntypeFlowBuilder = class {
2211
2476
  onFlowComplete: (event) => callbacks?.onFlowComplete?.(event),
2212
2477
  onError: (error) => callbacks?.onError?.(error)
2213
2478
  };
2214
- const { streamEvents: streamEvents2, stepDeltaText: stepDeltaText2, stepDisplayName: stepDisplayName2, flowErrorMessage: flowErrorMessage2 } = await Promise.resolve().then(() => (init_stream_utils(), stream_utils_exports));
2215
2479
  try {
2216
- for await (const event of streamEvents2(response)) {
2480
+ for await (const event of streamEvents(response)) {
2217
2481
  collectLocalToolAwait(pausedTools, event);
2218
2482
  switch (event.type) {
2219
2483
  case "flow_start":
@@ -2223,10 +2487,10 @@ var RuntypeFlowBuilder = class {
2223
2487
  wrappedCallbacks.onStepStart?.(event);
2224
2488
  break;
2225
2489
  case "step_delta":
2226
- wrappedCallbacks.onStepDelta?.(stepDeltaText2(event), event);
2490
+ wrappedCallbacks.onStepDelta?.(stepDeltaText(event), event);
2227
2491
  break;
2228
2492
  case "step_complete": {
2229
- accumulatedSummary.results?.set(stepDisplayName2(event), event.result);
2493
+ accumulatedSummary.results?.set(stepDisplayName(event), event.result);
2230
2494
  wrappedCallbacks.onStepComplete?.(event.result, event);
2231
2495
  break;
2232
2496
  }
@@ -2234,7 +2498,7 @@ var RuntypeFlowBuilder = class {
2234
2498
  wrappedCallbacks.onFlowComplete?.(event);
2235
2499
  break;
2236
2500
  case "flow_error":
2237
- wrappedCallbacks.onError?.(new Error(flowErrorMessage2(event)));
2501
+ wrappedCallbacks.onError?.(new Error(flowErrorMessage(event)));
2238
2502
  break;
2239
2503
  }
2240
2504
  }
@@ -2309,7 +2573,8 @@ var RuntypeFlowBuilder = class {
2309
2573
  return [toolName, await localTools[toolName](parameters)];
2310
2574
  } catch (error) {
2311
2575
  throw new Error(
2312
- `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`
2576
+ `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`,
2577
+ { cause: error }
2313
2578
  );
2314
2579
  }
2315
2580
  })
@@ -2342,15 +2607,15 @@ var RuntypeFlowBuilder = class {
2342
2607
  build() {
2343
2608
  const flowMode = this.mode === "existing" ? "existing" : this.mode;
2344
2609
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
2345
- const request = { flow };
2610
+ const request2 = { flow };
2346
2611
  if (this.recordConfig) {
2347
- request.record = this.recordConfig;
2612
+ request2.record = this.recordConfig;
2348
2613
  }
2349
2614
  if (this.messagesConfig) {
2350
- request.messages = this.messagesConfig;
2615
+ request2.messages = this.messagesConfig;
2351
2616
  }
2352
2617
  if (this.inputsConfig) {
2353
- request.inputs = this.inputsConfig;
2618
+ request2.inputs = this.inputsConfig;
2354
2619
  }
2355
2620
  const options = {
2356
2621
  flowMode,
@@ -2368,8 +2633,8 @@ var RuntypeFlowBuilder = class {
2368
2633
  if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
2369
2634
  options.upsertOptions = this.upsertOptions;
2370
2635
  }
2371
- request.options = options;
2372
- return request;
2636
+ request2.options = options;
2637
+ return request2;
2373
2638
  }
2374
2639
  /**
2375
2640
  * Validate this prospective flow against the public validation endpoint
@@ -3029,6 +3294,8 @@ var SkillsNamespace = class {
3029
3294
  }
3030
3295
  /**
3031
3296
  * List skills for the authenticated owner, optionally filtered by status.
3297
+ * Returns just the rows (one page); pass `cursor`/`limit` to page, or use
3298
+ * {@link listPage} when you need the pagination envelope.
3032
3299
  *
3033
3300
  * @example
3034
3301
  * ```typescript
@@ -3036,10 +3303,23 @@ var SkillsNamespace = class {
3036
3303
  * ```
3037
3304
  */
3038
3305
  async list(params) {
3039
- const client = this.getClient();
3040
- const res = await client.get("/skills", params);
3306
+ const res = await this.listPage(params);
3041
3307
  return res.data;
3042
3308
  }
3309
+ /**
3310
+ * List skills with the cursor-pagination envelope (mirrors the tools list
3311
+ * shape: `{ data, pagination }`).
3312
+ *
3313
+ * @example
3314
+ * ```typescript
3315
+ * const page1 = await Runtype.skills.listPage({ limit: 50, includeCount: true })
3316
+ * const page2 = await Runtype.skills.listPage({ limit: 50, cursor: page1.pagination?.nextCursor ?? undefined })
3317
+ * ```
3318
+ */
3319
+ async listPage(params) {
3320
+ const client = this.getClient();
3321
+ return client.get("/skills", params);
3322
+ }
3043
3323
  /**
3044
3324
  * Get a skill and its full version history.
3045
3325
  *
@@ -3132,6 +3412,260 @@ var SkillsNamespace = class {
3132
3412
  }
3133
3413
  };
3134
3414
 
3415
+ // src/agents-namespace.ts
3416
+ var AGENT_CONFIG_KEYS = [
3417
+ "model",
3418
+ "systemPrompt",
3419
+ "temperature",
3420
+ "topP",
3421
+ "topK",
3422
+ "frequencyPenalty",
3423
+ "presencePenalty",
3424
+ "seed",
3425
+ "tools",
3426
+ "reasoning",
3427
+ "advisor",
3428
+ "loopConfig",
3429
+ "voice",
3430
+ "errorHandling",
3431
+ "artifacts",
3432
+ "loggingPolicy",
3433
+ "temporal",
3434
+ "memory"
3435
+ ];
3436
+ var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
3437
+ function isPlainObject2(value) {
3438
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3439
+ }
3440
+ function normalizeValue(value) {
3441
+ if (Array.isArray(value)) {
3442
+ return value.map((item) => normalizeValue(item));
3443
+ }
3444
+ if (isPlainObject2(value)) {
3445
+ const normalized = {};
3446
+ for (const key of Object.keys(value).sort()) {
3447
+ const entry = value[key];
3448
+ if (entry === void 0 || entry === null) continue;
3449
+ normalized[key] = normalizeValue(entry);
3450
+ }
3451
+ return normalized;
3452
+ }
3453
+ return value;
3454
+ }
3455
+ function normalizeAgentDefinition(definition) {
3456
+ const config = {};
3457
+ const rawConfig = isPlainObject2(definition.config) ? definition.config : {};
3458
+ for (const key of AGENT_CONFIG_KEY_LIST) {
3459
+ const value = rawConfig[key];
3460
+ if (value === void 0 || value === null) continue;
3461
+ config[key] = normalizeValue(value);
3462
+ }
3463
+ return {
3464
+ name: definition.name,
3465
+ ...definition.description ? { description: definition.description } : {},
3466
+ ...definition.icon ? { icon: definition.icon } : {},
3467
+ config
3468
+ };
3469
+ }
3470
+ async function computeAgentContentHash(definition) {
3471
+ const serialized = JSON.stringify(normalizeAgentDefinition(definition));
3472
+ const encoded = new TextEncoder().encode(serialized);
3473
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3474
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3475
+ }
3476
+ var DEFINE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "description", "icon", ...AGENT_CONFIG_KEYS]);
3477
+ function collectNonPortableToolRefs(config) {
3478
+ const tools = config.tools;
3479
+ if (!isPlainObject2(tools)) return [];
3480
+ const found = [];
3481
+ const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
3482
+ const scanArray = (value, path) => {
3483
+ if (!Array.isArray(value)) return;
3484
+ value.forEach((ref, i) => {
3485
+ if (isAccountScoped(ref)) found.push(`${path}[${i}]`);
3486
+ });
3487
+ };
3488
+ const scanKeys = (value, path) => {
3489
+ if (!isPlainObject2(value)) return;
3490
+ for (const key of Object.keys(value)) {
3491
+ if (isAccountScoped(key)) found.push(`${path}.${key}`);
3492
+ }
3493
+ };
3494
+ scanArray(tools.toolIds, "tools.toolIds");
3495
+ scanKeys(tools.toolConfigs, "tools.toolConfigs");
3496
+ scanKeys(tools.perToolLimits, "tools.perToolLimits");
3497
+ if (isPlainObject2(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3498
+ if (isPlainObject2(tools.subagentConfig)) {
3499
+ scanArray(tools.subagentConfig.toolPool, "tools.subagentConfig.toolPool");
3500
+ }
3501
+ if (isPlainObject2(tools.codeModeConfig)) {
3502
+ scanArray(tools.codeModeConfig.toolPool, "tools.codeModeConfig.toolPool");
3503
+ }
3504
+ return found;
3505
+ }
3506
+ function defineAgent(input) {
3507
+ if (!input || typeof input !== "object") {
3508
+ throw new Error("defineAgent requires a definition object");
3509
+ }
3510
+ if (typeof input.name !== "string" || input.name.length === 0) {
3511
+ throw new Error('defineAgent requires a non-empty string "name"');
3512
+ }
3513
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOP_LEVEL_KEYS.has(key));
3514
+ if (unknownKeys.length > 0) {
3515
+ throw new Error(
3516
+ `defineAgent: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, icon, and the agent runtime config surface (${AGENT_CONFIG_KEY_LIST.join(", ")}).`
3517
+ );
3518
+ }
3519
+ const config = {};
3520
+ for (const key of AGENT_CONFIG_KEYS) {
3521
+ const value = input[key];
3522
+ if (value !== void 0) config[key] = value;
3523
+ }
3524
+ const nonPortable = collectNonPortableToolRefs(config);
3525
+ if (nonPortable.length > 0) {
3526
+ throw new Error(
3527
+ `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.`
3528
+ );
3529
+ }
3530
+ return {
3531
+ name: input.name,
3532
+ ...input.description !== void 0 ? { description: input.description } : {},
3533
+ ...input.icon !== void 0 ? { icon: input.icon } : {},
3534
+ config
3535
+ };
3536
+ }
3537
+ var AgentEnsureConflictError = class extends Error {
3538
+ constructor(body) {
3539
+ super(body.error ?? `Agent ensure conflict: ${body.code}`);
3540
+ this.name = "AgentEnsureConflictError";
3541
+ this.code = body.code;
3542
+ this.lastModifiedSource = body.lastModifiedSource;
3543
+ this.modifiedAt = body.modifiedAt;
3544
+ this.currentHash = body.currentHash;
3545
+ }
3546
+ };
3547
+ var AgentDriftError = class extends Error {
3548
+ constructor(plan) {
3549
+ super(
3550
+ `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.`
3551
+ );
3552
+ this.name = "AgentDriftError";
3553
+ this.plan = plan;
3554
+ }
3555
+ };
3556
+ function parseRequestError2(err) {
3557
+ if (!(err instanceof Error)) return { status: null, body: null };
3558
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3559
+ if (!match) return { status: null, body: null };
3560
+ try {
3561
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3562
+ } catch {
3563
+ return { status: Number(match[1]), body: null };
3564
+ }
3565
+ }
3566
+ function toConflictError2(err) {
3567
+ const { status, body } = parseRequestError2(err);
3568
+ if (status !== 409 || !isPlainObject2(body)) return null;
3569
+ const code = body.code;
3570
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3571
+ return new AgentEnsureConflictError(
3572
+ body
3573
+ );
3574
+ }
3575
+ var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3576
+ function memoFor2(client) {
3577
+ let memo = serverHashMemo2.get(client);
3578
+ if (!memo) {
3579
+ memo = /* @__PURE__ */ new Map();
3580
+ serverHashMemo2.set(client, memo);
3581
+ }
3582
+ return memo;
3583
+ }
3584
+ var AgentsNamespace = class {
3585
+ constructor(getClient) {
3586
+ this.getClient = getClient;
3587
+ }
3588
+ /**
3589
+ * Idempotently converge a definition onto the platform. Hash-first: probes
3590
+ * with a content hash, and only ships the full definition when the server
3591
+ * reports a miss (`definitionRequired`). Creates an immutable version
3592
+ * snapshot on every change; never deletes.
3593
+ */
3594
+ async ensure(definition, options = {}) {
3595
+ const client = this.getClient();
3596
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3597
+ const passthrough = {
3598
+ ...onConflict ? { onConflict } : {},
3599
+ ...release ? { release } : {},
3600
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3601
+ };
3602
+ if (dryRun || expectNoChanges) {
3603
+ const plan = await this.request(client, {
3604
+ name: definition.name,
3605
+ definition,
3606
+ dryRun: true,
3607
+ ...passthrough
3608
+ });
3609
+ if (plan.result !== "plan") {
3610
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3611
+ }
3612
+ if (expectNoChanges && plan.changes !== "none") {
3613
+ throw new AgentDriftError(plan);
3614
+ }
3615
+ return plan;
3616
+ }
3617
+ const memo = memoFor2(client);
3618
+ const localHash = await computeAgentContentHash({
3619
+ ...definition,
3620
+ config: definition.config
3621
+ });
3622
+ const memoKey = `${definition.name}\0${localHash}`;
3623
+ const contentHash = memo.get(memoKey) ?? localHash;
3624
+ const probe = await this.request(client, {
3625
+ name: definition.name,
3626
+ contentHash,
3627
+ ...passthrough
3628
+ });
3629
+ if (probe.result !== "definitionRequired") {
3630
+ this.memoize(memo, memoKey, probe);
3631
+ return probe;
3632
+ }
3633
+ const converged = await this.request(client, {
3634
+ name: definition.name,
3635
+ definition,
3636
+ ...passthrough
3637
+ });
3638
+ if (converged.result === "definitionRequired") {
3639
+ throw new Error("Server reported definitionRequired for a full-definition request");
3640
+ }
3641
+ this.memoize(memo, memoKey, converged);
3642
+ return converged;
3643
+ }
3644
+ /**
3645
+ * Pull the canonical definition + provenance for an agent by name — the
3646
+ * absorb-drift direction. The contentHash reflects the live agent state.
3647
+ */
3648
+ async pull(name) {
3649
+ const client = this.getClient();
3650
+ return client.get("/agents/pull", { name });
3651
+ }
3652
+ memoize(memo, memoKey, result) {
3653
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3654
+ }
3655
+ async request(client, body) {
3656
+ try {
3657
+ return await client.post(
3658
+ "/agents/ensure",
3659
+ body
3660
+ );
3661
+ } catch (err) {
3662
+ const conflict = toConflictError2(err);
3663
+ if (conflict) throw conflict;
3664
+ throw err;
3665
+ }
3666
+ }
3667
+ };
3668
+
3135
3669
  // src/transform.ts
3136
3670
  function transformResponse(data) {
3137
3671
  return data;
@@ -3292,7 +3826,7 @@ var RuntypeClient = class {
3292
3826
  } catch (error) {
3293
3827
  clearTimeout(timeoutId);
3294
3828
  if (error instanceof Error && error.name === "AbortError") {
3295
- throw new Error(`Request timeout after ${this.timeout}ms`);
3829
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
3296
3830
  }
3297
3831
  throw error;
3298
3832
  }
@@ -3319,7 +3853,7 @@ var RuntypeClient = class {
3319
3853
  } catch (error) {
3320
3854
  clearTimeout(timeoutId);
3321
3855
  if (error instanceof Error && error.name === "AbortError") {
3322
- throw new Error(`Request timeout after ${this.timeout}ms`);
3856
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
3323
3857
  }
3324
3858
  throw error;
3325
3859
  }
@@ -3494,6 +4028,32 @@ var Runtype = class {
3494
4028
  static get skills() {
3495
4029
  return new SkillsNamespace(() => this.getClient());
3496
4030
  }
4031
+ /**
4032
+ * Agents namespace - Agent config-as-code (define / ensure / pull)
4033
+ *
4034
+ * @example
4035
+ * ```typescript
4036
+ * import { defineAgent, Runtype } from '@runtypelabs/sdk'
4037
+ *
4038
+ * const assistant = defineAgent({
4039
+ * name: 'Pricing Assistant',
4040
+ * model: 'claude-sonnet-4-6',
4041
+ * systemPrompt: renderPrompt(pricingData),
4042
+ * })
4043
+ *
4044
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
4045
+ * await Runtype.agents.ensure(assistant)
4046
+ *
4047
+ * // CI drift gate
4048
+ * await Runtype.agents.ensure(assistant, { expectNoChanges: true })
4049
+ *
4050
+ * // Absorb a dashboard edit back into the repo
4051
+ * const { definition } = await Runtype.agents.pull('Pricing Assistant')
4052
+ * ```
4053
+ */
4054
+ static get agents() {
4055
+ return new AgentsNamespace(() => this.getClient());
4056
+ }
3497
4057
  };
3498
4058
 
3499
4059
  // src/generated-tool-gate.ts
@@ -3716,8 +4276,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
3716
4276
  ...decision.tool ? { tool: decision.tool } : {}
3717
4277
  };
3718
4278
  }
3719
- function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}) {
3720
- const stepList = request.flow.steps;
4279
+ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {}) {
4280
+ const stepList = request2.flow.steps;
3721
4281
  if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
3722
4282
  throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
3723
4283
  }
@@ -3760,9 +4320,9 @@ function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}
3760
4320
  }
3761
4321
  };
3762
4322
  return {
3763
- ...request,
4323
+ ...request2,
3764
4324
  flow: {
3765
- ...request.flow,
4325
+ ...request2.flow,
3766
4326
  // `clonedSteps` is a structural clone of `request.flow.steps` (already
3767
4327
  // `FlowStepDefinition[]`); only the prompt step's `config.tools` was
3768
4328
  // merged, so every step's `type` discriminant is preserved. The clone is
@@ -3772,18 +4332,56 @@ function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}
3772
4332
  }
3773
4333
  };
3774
4334
  }
3775
- function applyGeneratedRuntimeToolProposalToDispatchRequest(request, proposal, options = {}) {
4335
+ function applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options = {}) {
3776
4336
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
3777
4337
  if (!decision.approved || !decision.tool) {
3778
- return { decision, request };
4338
+ return { decision, request: request2 };
3779
4339
  }
3780
- const nextRequest = attachRuntimeToolsToDispatchRequest(request, [decision.tool], options.attach);
4340
+ const nextRequest = attachRuntimeToolsToDispatchRequest(request2, [decision.tool], options.attach);
3781
4341
  return {
3782
4342
  decision,
3783
4343
  request: nextRequest
3784
4344
  };
3785
4345
  }
3786
4346
 
4347
+ // src/offload-markers.ts
4348
+ var LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
4349
+ function formatChars(charLength) {
4350
+ return charLength.toLocaleString("en-US");
4351
+ }
4352
+ function buildSendViewOffloadMarker(details) {
4353
+ return `[${details.toolName} output (${formatChars(details.charLength)} chars) saved to ${details.filePath} \u2014 use read_file to retrieve if needed]`;
4354
+ }
4355
+ function buildLedgerOffloadReference(details) {
4356
+ return [
4357
+ `[Output offloaded as ${details.outputId} \u2014 ${formatChars(details.charLength)} chars stored in the marathon context ledger]`,
4358
+ `${LEDGER_ARTIFACT_LINE_PREFIX}${details.relativePath}`,
4359
+ `Preview: ${details.preview}${details.truncated ? "..." : ""}`,
4360
+ "",
4361
+ `Use read_offloaded_output with id "${details.outputId}" to retrieve the full output if needed.`
4362
+ ].join("\n");
4363
+ }
4364
+ var DECLARED_CHARS_PATTERNS = [
4365
+ /—\s*([\d,]+)\s+chars?\s+(?:stored|saved)/i,
4366
+ /\(([\d,]+)\s+chars?\)\s+saved/i
4367
+ ];
4368
+ function extractDeclaredToolResultChars(value) {
4369
+ if (typeof value !== "string") return void 0;
4370
+ for (const pattern of DECLARED_CHARS_PATTERNS) {
4371
+ const match = pattern.exec(value);
4372
+ if (!match?.[1]) continue;
4373
+ const parsed = Number.parseInt(match[1].replace(/,/g, ""), 10);
4374
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
4375
+ }
4376
+ return void 0;
4377
+ }
4378
+ function parseOffloadedOutputId(value) {
4379
+ return /\bread_offloaded_output\s+with\s+id\s+"([^"]+)"/i.exec(value)?.[1] || /\[Output offloaded as\s+([a-zA-Z0-9_-]+)/i.exec(value)?.[1] || void 0;
4380
+ }
4381
+ function parseLedgerArtifactRelativePath(value) {
4382
+ return value.split("\n").find((line) => line.startsWith(LEDGER_ARTIFACT_LINE_PREFIX))?.slice(LEDGER_ARTIFACT_LINE_PREFIX.length).trim();
4383
+ }
4384
+
3787
4385
  // src/workflow-utils.ts
3788
4386
  function normalizeCandidatePath(candidatePath) {
3789
4387
  return candidatePath.trim().replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+/g, "/");
@@ -3815,26 +4413,281 @@ function isPreservationSensitiveTask(state) {
3815
4413
  "visual"
3816
4414
  ].some((keyword) => prompt.includes(keyword));
3817
4415
  }
3818
- function getLikelySupportingCandidatePaths(bestCandidatePath, candidatePaths) {
3819
- if (!bestCandidatePath || !candidatePaths || candidatePaths.length === 0) return [];
3820
- const normalizedBestCandidatePath = normalizeCandidatePath(bestCandidatePath);
3821
- const bestCandidateSegments = normalizedBestCandidatePath.split("/").filter(Boolean);
3822
- const relatedRoot = bestCandidateSegments.length >= 2 ? `${bestCandidateSegments[0]}/${bestCandidateSegments[1]}/` : bestCandidateSegments.length === 1 ? `${bestCandidateSegments[0]}/` : "";
3823
- const bestCandidateDir = normalizedBestCandidatePath.includes("/") ? `${normalizedBestCandidatePath.slice(0, normalizedBestCandidatePath.lastIndexOf("/"))}/` : "";
3824
- return candidatePaths.map((candidatePath) => normalizeCandidatePath(candidatePath)).filter(
3825
- (candidatePath) => candidatePath && candidatePath !== normalizedBestCandidatePath && !isMarathonArtifactPath(candidatePath) && (bestCandidateDir && candidatePath.startsWith(bestCandidateDir) || relatedRoot && candidatePath.startsWith(relatedRoot))
3826
- );
4416
+ function getLikelySupportingCandidatePaths(bestCandidatePath, candidatePaths) {
4417
+ if (!bestCandidatePath || !candidatePaths || candidatePaths.length === 0) return [];
4418
+ const normalizedBestCandidatePath = normalizeCandidatePath(bestCandidatePath);
4419
+ const bestCandidateSegments = normalizedBestCandidatePath.split("/").filter(Boolean);
4420
+ const relatedRoot = bestCandidateSegments.length >= 2 ? `${bestCandidateSegments[0]}/${bestCandidateSegments[1]}/` : bestCandidateSegments.length === 1 ? `${bestCandidateSegments[0]}/` : "";
4421
+ const bestCandidateDir = normalizedBestCandidatePath.includes("/") ? `${normalizedBestCandidatePath.slice(0, normalizedBestCandidatePath.lastIndexOf("/"))}/` : "";
4422
+ return candidatePaths.map((candidatePath) => normalizeCandidatePath(candidatePath)).filter(
4423
+ (candidatePath) => candidatePath && candidatePath !== normalizedBestCandidatePath && !isMarathonArtifactPath(candidatePath) && (bestCandidateDir && candidatePath.startsWith(bestCandidateDir) || relatedRoot && candidatePath.startsWith(relatedRoot))
4424
+ );
4425
+ }
4426
+ function getDefaultPlanPath(taskName) {
4427
+ const slug = sanitizeTaskSlug(taskName || "task");
4428
+ return `.runtype/marathons/${slug}/plan.md`;
4429
+ }
4430
+ function getDefaultExternalReportPath(taskName) {
4431
+ const slug = sanitizeTaskSlug(taskName || "task");
4432
+ return `${slug}.md`;
4433
+ }
4434
+ function sanitizeTaskSlug(taskName) {
4435
+ return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4436
+ }
4437
+
4438
+ // src/workflows/hook-registry.ts
4439
+ var BUILTIN_NAMESPACE = "builtin";
4440
+ var HOOK_REF_PATTERN = /^[a-z0-9_-]+:[a-z0-9_-]+$/;
4441
+ function isWorkflowHookRef(value) {
4442
+ return typeof value === "string" && HOOK_REF_PATTERN.test(value);
4443
+ }
4444
+ var registry = /* @__PURE__ */ new Map();
4445
+ function registerWorkflowHook(name, entry) {
4446
+ if (!isWorkflowHookRef(name)) {
4447
+ throw new Error(
4448
+ `Invalid workflow hook name "${name}": must be "<namespace>:<id>" using lowercase letters, digits, "-" or "_" (e.g. "acme:my-completion").`
4449
+ );
4450
+ }
4451
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) {
4452
+ throw new Error(
4453
+ `Cannot register "${name}": the "builtin:" namespace is reserved. Register under your own namespace and reference it from the workflow config instead.`
4454
+ );
4455
+ }
4456
+ registry.set(name, entry);
4457
+ }
4458
+ function registerBuiltinWorkflowHook(name, entry) {
4459
+ if (!name.startsWith(`${BUILTIN_NAMESPACE}:`) || !isWorkflowHookRef(name)) {
4460
+ throw new Error(`Builtin workflow hooks must be named "builtin:<id>" (got "${name}").`);
4461
+ }
4462
+ if (registry.has(name)) return;
4463
+ registry.set(name, entry);
4464
+ }
4465
+ function resolveWorkflowHook(name, expectedKind) {
4466
+ const entry = registry.get(name);
4467
+ if (!entry) {
4468
+ const known = listWorkflowHooks().filter((hook) => hook.kind === expectedKind).map((hook) => hook.name);
4469
+ throw new Error(
4470
+ `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."
4471
+ );
4472
+ }
4473
+ if (entry.kind !== expectedKind) {
4474
+ throw new Error(
4475
+ `Workflow hook "${name}" is registered as '${entry.kind}' but referenced from a '${expectedKind}' slot.`
4476
+ );
4477
+ }
4478
+ return entry.fn;
4479
+ }
4480
+ function listWorkflowHooks() {
4481
+ return [...registry.entries()].map(([name, entry]) => ({ name, kind: entry.kind }));
4482
+ }
4483
+ function unregisterWorkflowHook(name) {
4484
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) return false;
4485
+ return registry.delete(name);
4486
+ }
4487
+
4488
+ // src/workflows/workflow-config.ts
4489
+ var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
4490
+ "search_repo",
4491
+ "glob_files",
4492
+ "tree_directory",
4493
+ "list_directory"
4494
+ ]);
4495
+ var DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS = 2;
4496
+ function definePlaybook(playbook) {
4497
+ return playbook;
4498
+ }
4499
+ function interpolateWorkflowTemplate(template, state) {
4500
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
4501
+ const value = state[key];
4502
+ if (value === void 0 || value === null) return `{{${key}}}`;
4503
+ return String(value);
4504
+ });
4505
+ }
4506
+ function buildIsComplete(criteria, configName, milestoneName) {
4507
+ if (!criteria) return () => false;
4508
+ switch (criteria.type) {
4509
+ case "evidence":
4510
+ return (ctx) => {
4511
+ const minFiles = criteria.minReadFiles ?? 1;
4512
+ return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
4513
+ };
4514
+ case "sessions": {
4515
+ let baselineSessionCount;
4516
+ return (ctx) => {
4517
+ const minSessions = criteria.minSessions ?? 1;
4518
+ if (baselineSessionCount === void 0) {
4519
+ baselineSessionCount = ctx.state.sessions.length;
4520
+ }
4521
+ return ctx.state.sessions.length - baselineSessionCount >= minSessions;
4522
+ };
4523
+ }
4524
+ case "planWritten":
4525
+ return (ctx) => {
4526
+ return ctx.trace.planWritten;
4527
+ };
4528
+ case "never":
4529
+ return () => false;
4530
+ default: {
4531
+ if (isWorkflowHookRef(criteria.type)) {
4532
+ return resolveWorkflowHook(criteria.type, "completion");
4533
+ }
4534
+ throw new Error(
4535
+ `Workflow config '${configName}': milestone '${milestoneName}' has unknown completionCriteria.type "${criteria.type}" (expected evidence | sessions | planWritten | never, or a 'completion' hook reference).`
4536
+ );
4537
+ }
4538
+ }
4539
+ }
4540
+ function buildPolicyIntercept(policy, configName, deps) {
4541
+ if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
4542
+ return void 0;
4543
+ }
4544
+ const blockedSet = new Set(
4545
+ (policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
4546
+ );
4547
+ const readGlobs = policy.allowedReadGlobs ?? [];
4548
+ const writeGlobs = policy.allowedWriteGlobs ?? [];
4549
+ const matchPathGlobs = deps.matchPathGlobs;
4550
+ if ((readGlobs.length > 0 || writeGlobs.length > 0) && !matchPathGlobs) {
4551
+ throw new Error(
4552
+ `Workflow config '${configName}': policy uses allowedReadGlobs/allowedWriteGlobs but no glob matcher was provided to compileWorkflowConfig (pass deps.matchPathGlobs).`
4553
+ );
4554
+ }
4555
+ return (toolName, args, ctx) => {
4556
+ if (blockedSet.has(toolName)) {
4557
+ return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
4558
+ }
4559
+ if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
4560
+ return `Blocked by playbook policy: discovery tools are disabled for this task.`;
4561
+ }
4562
+ const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
4563
+ if (pathArg) {
4564
+ const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
4565
+ const isRead = toolName === "read_file";
4566
+ if (isRead && readGlobs.length > 0) {
4567
+ const allowed = matchPathGlobs(pathArg, readGlobs);
4568
+ if (!allowed) {
4569
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
4570
+ }
4571
+ }
4572
+ if (isWrite && writeGlobs.length > 0) {
4573
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4574
+ if (planPath && pathArg === planPath) {
4575
+ } else {
4576
+ const allowed = matchPathGlobs(pathArg, writeGlobs);
4577
+ if (!allowed) {
4578
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
4579
+ }
4580
+ }
4581
+ }
4582
+ if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
4583
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4584
+ if (!planPath || pathArg !== planPath) {
4585
+ return `Blocked by playbook policy: write the plan before creating other files.`;
4586
+ }
4587
+ }
4588
+ }
4589
+ return void 0;
4590
+ };
4591
+ }
4592
+ function buildPolicyGuidance(policy) {
4593
+ if (!policy) return [];
4594
+ const lines = [];
4595
+ if (policy.requirePlanBeforeWrite) {
4596
+ lines.push(
4597
+ "Policy: write the plan file before any other file. Once the plan is written, other writes are allowed in the same turn."
4598
+ );
4599
+ }
4600
+ if (policy.allowedWriteGlobs?.length) {
4601
+ lines.push(
4602
+ `Policy: file writes are only allowed for paths matching: ${policy.allowedWriteGlobs.join(", ")} (the plan file is always allowed).`
4603
+ );
4604
+ }
4605
+ if (policy.outputRoot) {
4606
+ lines.push(`Policy: create new files under "${policy.outputRoot.replace(/\/$/, "")}/".`);
4607
+ }
4608
+ if (policy.allowedReadGlobs?.length) {
4609
+ lines.push(
4610
+ `Policy: file reads are only allowed for paths matching: ${policy.allowedReadGlobs.join(", ")}.`
4611
+ );
4612
+ }
4613
+ if (policy.blockDiscoveryTools) {
4614
+ lines.push(
4615
+ "Policy: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are disabled for this task."
4616
+ );
4617
+ }
4618
+ if (policy.blockedTools?.length) {
4619
+ lines.push(`Policy: these tools are disabled for this task: ${policy.blockedTools.join(", ")}.`);
4620
+ }
4621
+ return lines;
3827
4622
  }
3828
- function getDefaultPlanPath(taskName) {
3829
- const slug = sanitizeTaskSlug(taskName || "task");
3830
- return `.runtype/marathons/${slug}/plan.md`;
4623
+ function resolveSlotHook(value, kind) {
4624
+ if (value === void 0) return void 0;
4625
+ if (typeof value === "function") return value;
4626
+ return resolveWorkflowHook(value, kind);
3831
4627
  }
3832
- function getDefaultExternalReportPath(taskName) {
3833
- const slug = sanitizeTaskSlug(taskName || "task");
3834
- return `${slug}.md`;
4628
+ function compileMilestone(milestone, config, policyIntercept, policyGuidance) {
4629
+ const buildInstructions = typeof milestone.instructions === "function" ? milestone.instructions : isWorkflowHookRef(milestone.instructions) ? resolveWorkflowHook(milestone.instructions, "instructions") : (state) => {
4630
+ const header = `--- Workflow Phase: ${milestone.name} ---`;
4631
+ const desc = milestone.description ? `
4632
+ ${milestone.description}` : "";
4633
+ const instructions = interpolateWorkflowTemplate(
4634
+ milestone.instructions,
4635
+ state
4636
+ );
4637
+ return `${header}${desc}
4638
+ ${instructions}`;
4639
+ };
4640
+ const guidanceHook = typeof milestone.toolGuidance === "function" ? milestone.toolGuidance : milestone.toolGuidance !== void 0 && isWorkflowHookRef(milestone.toolGuidance) ? resolveWorkflowHook(milestone.toolGuidance, "toolGuidance") : void 0;
4641
+ const buildToolGuidance = (state) => {
4642
+ const base = guidanceHook ? guidanceHook(state) : milestone.toolGuidance ?? [];
4643
+ return policyGuidance.length > 0 ? [...base, ...policyGuidance] : base;
4644
+ };
4645
+ const customIntercept = resolveSlotHook(milestone.intercept, "intercept");
4646
+ const interceptToolCall = policyIntercept && customIntercept ? (toolName, args, ctx) => policyIntercept(toolName, args, ctx) ?? customIntercept(toolName, args, ctx) : policyIntercept ?? customIntercept;
4647
+ const transitionHook = typeof milestone.transitionSummary === "function" ? milestone.transitionSummary : milestone.transitionSummary !== void 0 && isWorkflowHookRef(milestone.transitionSummary) ? resolveWorkflowHook(milestone.transitionSummary, "transitionSummary") : void 0;
4648
+ const buildTransitionSummary = milestone.transitionSummary === void 0 ? void 0 : transitionHook ?? ((state, nextPhaseName) => interpolateWorkflowTemplate(milestone.transitionSummary, state).replace(
4649
+ /\{\{nextPhase\}\}/g,
4650
+ nextPhaseName
4651
+ ));
4652
+ const recoveryHook = typeof milestone.recovery === "function" ? milestone.recovery : milestone.recovery !== void 0 && isWorkflowHookRef(milestone.recovery) ? resolveWorkflowHook(milestone.recovery, "recovery") : void 0;
4653
+ const buildRecoveryMessage = milestone.recovery === void 0 ? void 0 : recoveryHook ?? ((state) => {
4654
+ const inline = milestone.recovery;
4655
+ const threshold = inline.afterEmptySessions ?? DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS;
4656
+ if ((state.consecutiveEmptySessions ?? 0) < threshold) return void 0;
4657
+ return interpolateWorkflowTemplate(inline.message, state);
4658
+ });
4659
+ const canAcceptCompletion = milestone.canAcceptCompletion === void 0 ? void 0 : typeof milestone.canAcceptCompletion === "function" ? milestone.canAcceptCompletion : isWorkflowHookRef(milestone.canAcceptCompletion) ? resolveWorkflowHook(milestone.canAcceptCompletion, "acceptCompletion") : () => milestone.canAcceptCompletion;
4660
+ const isComplete = typeof milestone.completionCriteria === "function" ? milestone.completionCriteria : buildIsComplete(milestone.completionCriteria, config.name, milestone.name);
4661
+ return {
4662
+ name: milestone.name,
4663
+ description: milestone.description,
4664
+ buildInstructions,
4665
+ buildToolGuidance,
4666
+ isComplete,
4667
+ ...interceptToolCall ? { interceptToolCall } : {},
4668
+ ...buildTransitionSummary ? { buildTransitionSummary } : {},
4669
+ ...buildRecoveryMessage ? { buildRecoveryMessage } : {},
4670
+ ...milestone.forceEndTurn ? { shouldForceEndTurn: resolveSlotHook(milestone.forceEndTurn, "forceEndTurn") } : {},
4671
+ ...canAcceptCompletion ? { canAcceptCompletion } : {}
4672
+ };
3835
4673
  }
3836
- function sanitizeTaskSlug(taskName) {
3837
- return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4674
+ function compileWorkflowConfig(config, deps = {}) {
4675
+ const policyIntercept = config.policy ? buildPolicyIntercept(config.policy, config.name, deps) : void 0;
4676
+ const policyGuidance = buildPolicyGuidance(config.policy);
4677
+ const phases = config.milestones.map(
4678
+ (milestone) => compileMilestone(milestone, config, policyIntercept, policyGuidance)
4679
+ );
4680
+ const classifyVariant4 = resolveSlotHook(config.classifyVariant, "classify");
4681
+ const generateBootstrapContext2 = resolveSlotHook(config.bootstrap, "bootstrap");
4682
+ const buildCandidateBlock2 = resolveSlotHook(config.candidateBlock, "candidateBlock");
4683
+ return {
4684
+ name: config.name,
4685
+ phases,
4686
+ ...config.stallPolicy ? { stallPolicy: config.stallPolicy } : {},
4687
+ ...classifyVariant4 ? { classifyVariant: classifyVariant4 } : {},
4688
+ ...generateBootstrapContext2 ? { generateBootstrapContext: generateBootstrapContext2 } : {},
4689
+ ...buildCandidateBlock2 ? { buildCandidateBlock: buildCandidateBlock2 } : {}
4690
+ };
3838
4691
  }
3839
4692
 
3840
4693
  // src/workflows/default-workflow.ts
@@ -3986,6 +4839,45 @@ function summarizeTextBlock(value, maxLines = 4) {
3986
4839
  if (!text) return "";
3987
4840
  return text.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, maxLines).join(" | ").slice(0, 240);
3988
4841
  }
4842
+ function interceptProductWriteTarget(toolName, normalizedPathArg, ctx, guardLabel) {
4843
+ const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4844
+ const normalizedBestCandidatePath = ctx.state.bestCandidatePath ? ctx.normalizePath(ctx.state.bestCandidatePath) : void 0;
4845
+ if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4846
+ const allowedWriteTargets = new Set(
4847
+ [
4848
+ normalizedPlanPath,
4849
+ normalizedBestCandidatePath,
4850
+ ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4851
+ ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4852
+ ].filter((value) => Boolean(value))
4853
+ );
4854
+ if (!allowedWriteTargets.has(normalizedPathArg)) {
4855
+ return [
4856
+ `Blocked by marathon ${guardLabel}: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4857
+ `Do not create scratch files like "${normalizedPathArg}".`,
4858
+ normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
4859
+ ].join(" ");
4860
+ }
4861
+ }
4862
+ if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4863
+ const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
4864
+ if (!outputRoot) {
4865
+ return [
4866
+ `Blocked by marathon ${guardLabel}: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
4867
+ `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
4868
+ ].join(" ");
4869
+ }
4870
+ const rootPrefix = outputRoot + "/";
4871
+ const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
4872
+ if (!isUnderRoot) {
4873
+ return [
4874
+ `Blocked by marathon ${guardLabel}: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
4875
+ `"${normalizedPathArg}" is outside the allowed output root.`
4876
+ ].join(" ");
4877
+ }
4878
+ }
4879
+ return void 0;
4880
+ }
3989
4881
  var researchPhase = {
3990
4882
  name: "research",
3991
4883
  description: "Inspect the repo and identify the correct target file",
@@ -4070,11 +4962,15 @@ var researchPhase = {
4070
4962
  const normalizedPathArg2 = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
4071
4963
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4072
4964
  if (normalizedPathArg2 && normalizedPlanPath && normalizedPathArg2 !== normalizedPlanPath) {
4073
- return [
4074
- `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
4075
- "Complete research first, then the system will advance you to planning.",
4076
- `You may write the plan to "${normalizedPlanPath}" once research is complete.`
4077
- ].join(" ");
4965
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
4966
+ if (!planWritten) {
4967
+ return [
4968
+ `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
4969
+ "Complete research first, then the system will advance you to planning.",
4970
+ `You may write the plan to "${normalizedPlanPath}" once research is complete.`
4971
+ ].join(" ");
4972
+ }
4973
+ return interceptProductWriteTarget(toolName, normalizedPathArg2, ctx, "research guard");
4078
4974
  }
4079
4975
  }
4080
4976
  return void 0;
@@ -4197,19 +5093,24 @@ var planningPhase = {
4197
5093
  "Research is complete. Write the implementation plan for building this from scratch.",
4198
5094
  `Write the plan markdown to exactly: ${planPath}`,
4199
5095
  "List the files you will create, their locations, purpose, and any dependencies to install.",
5096
+ ...state.outputRoot ? [
5097
+ `All new files must be created under "${state.outputRoot}" \u2014 writes outside that directory are blocked, so plan every file location inside it.`
5098
+ ] : [],
4200
5099
  'Include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4201
- "If the plan already exists, update that same plan file instead of creating a different one."
5100
+ "If the plan already exists, update that same plan file instead of creating a different one.",
5101
+ "Once the plan is written, you may begin creating the planned files in the same turn."
4202
5102
  ].join("\n");
4203
5103
  }
4204
5104
  return [
4205
5105
  "--- Workflow Phase: Planning ---",
4206
5106
  "Research is complete. Your current job is to write the implementation plan before any product-file edits.",
4207
5107
  `Write the plan markdown to exactly: ${planPath}`,
4208
- "Do NOT edit the target product file yet.",
5108
+ "Do NOT edit the target product file before the plan exists.",
4209
5109
  "The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.",
4210
5110
  'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
4211
5111
  'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4212
- "If the plan already exists, update that same plan file instead of creating a different one."
5112
+ "If the plan already exists, update that same plan file instead of creating a different one.",
5113
+ "Once the plan is written, you may begin editing the target file in the same turn."
4213
5114
  ].join("\n");
4214
5115
  },
4215
5116
  buildToolGuidance(state) {
@@ -4237,10 +5138,14 @@ var planningPhase = {
4237
5138
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4238
5139
  const isWriteLikeTool = toolName === "write_file" || toolName === "edit_file" || toolName === "restore_file_checkpoint";
4239
5140
  if (isWriteLikeTool && normalizedPathArg && normalizedPlanPath && normalizedPathArg !== normalizedPlanPath) {
4240
- return [
4241
- `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
4242
- `Write the plan to "${normalizedPlanPath}" before editing any product files.`
4243
- ].join(" ");
5141
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
5142
+ if (!planWritten) {
5143
+ return [
5144
+ `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
5145
+ `Write the plan to "${normalizedPlanPath}" before editing any product files.`
5146
+ ].join(" ");
5147
+ }
5148
+ return interceptProductWriteTarget(toolName, normalizedPathArg, ctx, "planning guard");
4244
5149
  }
4245
5150
  return void 0;
4246
5151
  },
@@ -4300,6 +5205,9 @@ var executionPhase = {
4300
5205
  },
4301
5206
  buildToolGuidance(state) {
4302
5207
  return [
5208
+ ...state.isCreationTask && state.outputRoot ? [
5209
+ `Creation guard: create new files under "${state.outputRoot}". Writes outside it are blocked \u2014 the plan file is the only exception.`
5210
+ ] : [],
4303
5211
  ...state.bestCandidatePath ? [
4304
5212
  `Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`
4305
5213
  ] : [
@@ -4341,40 +5249,13 @@ var executionPhase = {
4341
5249
  `After that, you may update "${normalizedPlanPath}" with progress.`
4342
5250
  ].join(" ");
4343
5251
  }
4344
- if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4345
- const allowedWriteTargets = new Set(
4346
- [
4347
- normalizedPlanPath,
4348
- normalizedBestCandidatePath,
4349
- ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4350
- ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4351
- ].filter((value) => Boolean(value))
4352
- );
4353
- if (!allowedWriteTargets.has(normalizedPathArg)) {
4354
- return [
4355
- `Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4356
- `Do not create scratch files like "${normalizedPathArg}".`,
4357
- normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
4358
- ].join(" ");
4359
- }
4360
- }
4361
- if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4362
- const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
4363
- if (!outputRoot) {
4364
- return [
4365
- `Blocked by marathon execution guard: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
4366
- `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
4367
- ].join(" ");
4368
- }
4369
- const rootPrefix = outputRoot + "/";
4370
- const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
4371
- if (!isUnderRoot) {
4372
- return [
4373
- `Blocked by marathon execution guard: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
4374
- `"${normalizedPathArg}" is outside the allowed output root.`
4375
- ].join(" ");
4376
- }
4377
- }
5252
+ const writeTargetBlock = interceptProductWriteTarget(
5253
+ toolName,
5254
+ normalizedPathArg,
5255
+ ctx,
5256
+ "execution guard"
5257
+ );
5258
+ if (writeTargetBlock) return writeTargetBlock;
4378
5259
  }
4379
5260
  return void 0;
4380
5261
  },
@@ -4607,13 +5488,163 @@ function buildCandidateBlock(state) {
4607
5488
  ...state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []
4608
5489
  ].join("\n");
4609
5490
  }
4610
- var defaultWorkflow = {
5491
+ var builtinHooksRegistered = false;
5492
+ function ensureDefaultWorkflowHooks() {
5493
+ if (builtinHooksRegistered) return;
5494
+ builtinHooksRegistered = true;
5495
+ registerBuiltinWorkflowHook("builtin:classify-task-variant", {
5496
+ kind: "classify",
5497
+ fn: classifyVariant
5498
+ });
5499
+ registerBuiltinWorkflowHook("builtin:repo-bootstrap-discovery", {
5500
+ kind: "bootstrap",
5501
+ fn: generateBootstrapContext
5502
+ });
5503
+ registerBuiltinWorkflowHook("builtin:best-candidate-block", {
5504
+ kind: "candidateBlock",
5505
+ fn: buildCandidateBlock
5506
+ });
5507
+ registerBuiltinWorkflowHook("builtin:research-instructions", {
5508
+ kind: "instructions",
5509
+ fn: researchPhase.buildInstructions
5510
+ });
5511
+ registerBuiltinWorkflowHook("builtin:research-tool-guidance", {
5512
+ kind: "toolGuidance",
5513
+ fn: researchPhase.buildToolGuidance
5514
+ });
5515
+ registerBuiltinWorkflowHook("builtin:research-complete", {
5516
+ kind: "completion",
5517
+ fn: researchPhase.isComplete
5518
+ });
5519
+ registerBuiltinWorkflowHook("builtin:research-transition-summary", {
5520
+ kind: "transitionSummary",
5521
+ fn: researchPhase.buildTransitionSummary
5522
+ });
5523
+ registerBuiltinWorkflowHook("builtin:research-guard", {
5524
+ kind: "intercept",
5525
+ fn: researchPhase.interceptToolCall
5526
+ });
5527
+ registerBuiltinWorkflowHook("builtin:research-recovery", {
5528
+ kind: "recovery",
5529
+ fn: researchPhase.buildRecoveryMessage
5530
+ });
5531
+ registerBuiltinWorkflowHook("builtin:research-force-end-turn", {
5532
+ kind: "forceEndTurn",
5533
+ fn: researchPhase.shouldForceEndTurn
5534
+ });
5535
+ registerBuiltinWorkflowHook("builtin:research-accept-completion", {
5536
+ kind: "acceptCompletion",
5537
+ fn: researchPhase.canAcceptCompletion
5538
+ });
5539
+ registerBuiltinWorkflowHook("builtin:planning-instructions", {
5540
+ kind: "instructions",
5541
+ fn: planningPhase.buildInstructions
5542
+ });
5543
+ registerBuiltinWorkflowHook("builtin:planning-tool-guidance", {
5544
+ kind: "toolGuidance",
5545
+ fn: planningPhase.buildToolGuidance
5546
+ });
5547
+ registerBuiltinWorkflowHook("builtin:planning-complete", {
5548
+ kind: "completion",
5549
+ fn: planningPhase.isComplete
5550
+ });
5551
+ registerBuiltinWorkflowHook("builtin:planning-transition-summary", {
5552
+ kind: "transitionSummary",
5553
+ fn: planningPhase.buildTransitionSummary
5554
+ });
5555
+ registerBuiltinWorkflowHook("builtin:planning-guard", {
5556
+ kind: "intercept",
5557
+ fn: planningPhase.interceptToolCall
5558
+ });
5559
+ registerBuiltinWorkflowHook("builtin:planning-recovery", {
5560
+ kind: "recovery",
5561
+ fn: planningPhase.buildRecoveryMessage
5562
+ });
5563
+ registerBuiltinWorkflowHook("builtin:planning-force-end-turn", {
5564
+ kind: "forceEndTurn",
5565
+ fn: planningPhase.shouldForceEndTurn
5566
+ });
5567
+ registerBuiltinWorkflowHook("builtin:execution-instructions", {
5568
+ kind: "instructions",
5569
+ fn: executionPhase.buildInstructions
5570
+ });
5571
+ registerBuiltinWorkflowHook("builtin:execution-tool-guidance", {
5572
+ kind: "toolGuidance",
5573
+ fn: executionPhase.buildToolGuidance
5574
+ });
5575
+ registerBuiltinWorkflowHook("builtin:execution-guard", {
5576
+ kind: "intercept",
5577
+ fn: executionPhase.interceptToolCall
5578
+ });
5579
+ registerBuiltinWorkflowHook("builtin:execution-recovery", {
5580
+ kind: "recovery",
5581
+ fn: executionPhase.buildRecoveryMessage
5582
+ });
5583
+ registerBuiltinWorkflowHook("builtin:execution-force-end-turn", {
5584
+ kind: "forceEndTurn",
5585
+ fn: executionPhase.shouldForceEndTurn
5586
+ });
5587
+ registerBuiltinWorkflowHook("builtin:execution-accept-completion", {
5588
+ kind: "acceptCompletion",
5589
+ fn: executionPhase.canAcceptCompletion
5590
+ });
5591
+ }
5592
+ var defaultWorkflowConfig = {
4611
5593
  name: "default",
4612
- phases: [researchPhase, planningPhase, executionPhase],
4613
- classifyVariant,
4614
- generateBootstrapContext,
4615
- buildCandidateBlock
5594
+ // Empty-session escalation. The counter only counts tool actions, so
5595
+ // narration-only sessions ("I'll create the files now" with no tool calls)
5596
+ // escalate here even though the phase recovery conditions keyed on
5597
+ // hadTextOutput skip them: nudge after the first actionless session, signal
5598
+ // model escalation after the second (a no-op unless the caller configured a
5599
+ // fallback model), and stop as 'stalled' after the third — the same total
5600
+ // session budget as before stallPolicy existed.
5601
+ stallPolicy: { nudgeAfter: 1, escalateModelAfter: 2, stopAfter: 3 },
5602
+ classifyVariant: "builtin:classify-task-variant",
5603
+ bootstrap: "builtin:repo-bootstrap-discovery",
5604
+ candidateBlock: "builtin:best-candidate-block",
5605
+ milestones: [
5606
+ {
5607
+ name: "research",
5608
+ description: "Inspect the repo and identify the correct target file",
5609
+ instructions: "builtin:research-instructions",
5610
+ toolGuidance: "builtin:research-tool-guidance",
5611
+ completionCriteria: { type: "builtin:research-complete" },
5612
+ intercept: "builtin:research-guard",
5613
+ transitionSummary: "builtin:research-transition-summary",
5614
+ recovery: "builtin:research-recovery",
5615
+ forceEndTurn: "builtin:research-force-end-turn",
5616
+ canAcceptCompletion: "builtin:research-accept-completion"
5617
+ },
5618
+ {
5619
+ name: "planning",
5620
+ description: "Write the implementation plan before editing product files",
5621
+ instructions: "builtin:planning-instructions",
5622
+ toolGuidance: "builtin:planning-tool-guidance",
5623
+ completionCriteria: { type: "builtin:planning-complete" },
5624
+ intercept: "builtin:planning-guard",
5625
+ transitionSummary: "builtin:planning-transition-summary",
5626
+ recovery: "builtin:planning-recovery",
5627
+ forceEndTurn: "builtin:planning-force-end-turn"
5628
+ // canAcceptCompletion intentionally absent: the hand-written planning
5629
+ // phase never defined it, and the SDK accepts completion when the slot
5630
+ // is undefined. Keep parity.
5631
+ },
5632
+ {
5633
+ name: "execution",
5634
+ description: "Execute the plan by editing target files",
5635
+ instructions: "builtin:execution-instructions",
5636
+ toolGuidance: "builtin:execution-tool-guidance",
5637
+ // Execution never auto-advances; completion is agent-driven via TASK_COMPLETE
5638
+ completionCriteria: { type: "never" },
5639
+ intercept: "builtin:execution-guard",
5640
+ recovery: "builtin:execution-recovery",
5641
+ forceEndTurn: "builtin:execution-force-end-turn",
5642
+ canAcceptCompletion: "builtin:execution-accept-completion"
5643
+ }
5644
+ ]
4616
5645
  };
5646
+ ensureDefaultWorkflowHooks();
5647
+ var defaultWorkflow = compileWorkflowConfig(defaultWorkflowConfig);
4617
5648
 
4618
5649
  // src/workflows/deploy-workflow.ts
4619
5650
  var scaffoldPhase = {
@@ -5025,6 +6056,34 @@ var gameWorkflow = {
5025
6056
  }
5026
6057
  };
5027
6058
 
6059
+ // src/workflows/stall-policy.ts
6060
+ var DEFAULT_STALL_STOP_AFTER = 3;
6061
+ function isPositiveInteger(value) {
6062
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
6063
+ }
6064
+ function resolveStallStopAfter(policy) {
6065
+ return isPositiveInteger(policy?.stopAfter) ? policy.stopAfter : DEFAULT_STALL_STOP_AFTER;
6066
+ }
6067
+ function shouldRequestModelEscalation(policy, consecutiveEmptySessions) {
6068
+ const threshold = policy?.escalateModelAfter;
6069
+ if (!isPositiveInteger(threshold)) return false;
6070
+ return consecutiveEmptySessions === threshold;
6071
+ }
6072
+ function shouldInjectEmptySessionNudge(policy, consecutiveEmptySessions) {
6073
+ const threshold = policy?.nudgeAfter;
6074
+ if (!isPositiveInteger(threshold)) return false;
6075
+ return consecutiveEmptySessions >= threshold;
6076
+ }
6077
+ function buildEmptySessionNudge(consecutiveEmptySessions) {
6078
+ const sessionPhrase = consecutiveEmptySessions === 1 ? "Your previous session ended" : `Your previous ${consecutiveEmptySessions} sessions ended`;
6079
+ return [
6080
+ "Recovery instruction:",
6081
+ `${sessionPhrase} without a single tool call. Describing what you plan to do does nothing \u2014 only tool calls make progress.`,
6082
+ "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.",
6083
+ "If a previous tool call was blocked, re-read the block message and satisfy its requirement instead of ending the turn."
6084
+ ].join("\n");
6085
+ }
6086
+
5028
6087
  // src/endpoints.ts
5029
6088
  var FlowsEndpoint = class {
5030
6089
  constructor(client) {
@@ -5515,15 +6574,15 @@ var DispatchEndpoint = class {
5515
6574
  * Attach approved runtime tools to a prompt step in a redispatch request.
5516
6575
  * Returns a new request object and does not mutate the original.
5517
6576
  */
5518
- attachApprovedRuntimeTools(request, runtimeTools, options) {
5519
- return attachRuntimeToolsToDispatchRequest(request, runtimeTools, options);
6577
+ attachApprovedRuntimeTools(request2, runtimeTools, options) {
6578
+ return attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options);
5520
6579
  }
5521
6580
  /**
5522
6581
  * Validate a generated runtime tool proposal and attach it to the redispatch
5523
6582
  * request if approved, in one call.
5524
6583
  */
5525
- applyGeneratedRuntimeToolProposal(request, proposal, options) {
5526
- return applyGeneratedRuntimeToolProposalToDispatchRequest(request, proposal, options);
6584
+ applyGeneratedRuntimeToolProposal(request2, proposal, options) {
6585
+ return applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options);
5527
6586
  }
5528
6587
  };
5529
6588
  var ChatEndpoint = class {
@@ -6028,6 +7087,22 @@ async function processAgentStream(body, callbacks) {
6028
7087
  reader.releaseLock();
6029
7088
  }
6030
7089
  }
7090
+ function sleepWithAbort(delayMs, signal) {
7091
+ return new Promise((resolve) => {
7092
+ const onAbort = () => {
7093
+ clearTimeout(timer);
7094
+ resolve();
7095
+ };
7096
+ const timer = setTimeout(() => {
7097
+ signal?.removeEventListener("abort", onAbort);
7098
+ resolve();
7099
+ }, delayMs);
7100
+ if (signal) {
7101
+ if (signal.aborted) onAbort();
7102
+ else signal.addEventListener("abort", onAbort, { once: true });
7103
+ }
7104
+ });
7105
+ }
6031
7106
  var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
6032
7107
  type: "object",
6033
7108
  properties: {
@@ -6059,8 +7134,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
6059
7134
  },
6060
7135
  required: ["name", "description", "toolType", "parametersSchema", "config"]
6061
7136
  };
6062
- function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
6063
- const existing = request.tools?.runtimeTools || [];
7137
+ function appendRuntimeToolsToAgentRequest(request2, runtimeTools) {
7138
+ const existing = request2.tools?.runtimeTools || [];
6064
7139
  const existingNames = new Set(existing.map((tool) => tool.name));
6065
7140
  const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
6066
7141
  name: tool.name,
@@ -6070,9 +7145,9 @@ function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
6070
7145
  ...tool.config ? { config: tool.config } : {}
6071
7146
  }));
6072
7147
  return {
6073
- ...request,
7148
+ ...request2,
6074
7149
  tools: {
6075
- ...request.tools,
7150
+ ...request2.tools,
6076
7151
  runtimeTools: [...existing, ...converted]
6077
7152
  }
6078
7153
  };
@@ -6148,21 +7223,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6148
7223
  * Attach approved runtime tools to an agent execute request.
6149
7224
  * Returns a new request object and does not mutate the original.
6150
7225
  */
6151
- attachApprovedRuntimeTools(request, runtimeTools) {
6152
- return appendRuntimeToolsToAgentRequest(request, runtimeTools);
7226
+ attachApprovedRuntimeTools(request2, runtimeTools) {
7227
+ return appendRuntimeToolsToAgentRequest(request2, runtimeTools);
6153
7228
  }
6154
7229
  /**
6155
7230
  * Validate a generated runtime tool proposal and append it to an agent execute
6156
7231
  * request if approved, in one call.
6157
7232
  */
6158
- applyGeneratedRuntimeToolProposal(request, proposal, options) {
7233
+ applyGeneratedRuntimeToolProposal(request2, proposal, options) {
6159
7234
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
6160
7235
  if (!decision.approved || !decision.tool) {
6161
- return { decision, request };
7236
+ return { decision, request: request2 };
6162
7237
  }
6163
7238
  return {
6164
7239
  decision,
6165
- request: appendRuntimeToolsToAgentRequest(request, [decision.tool])
7240
+ request: appendRuntimeToolsToAgentRequest(request2, [decision.tool])
6166
7241
  };
6167
7242
  }
6168
7243
  /**
@@ -6192,13 +7267,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6192
7267
  * // ...
6193
7268
  * ```
6194
7269
  */
6195
- async executeStream(id, data) {
7270
+ async executeStream(id, data, init) {
6196
7271
  return this.client.requestStream(`/agents/${id}/execute`, {
6197
7272
  method: "POST",
6198
7273
  body: JSON.stringify({
6199
7274
  ...data,
6200
7275
  streamResponse: true
6201
- })
7276
+ }),
7277
+ ...init?.signal ? { signal: init.signal } : {}
6202
7278
  });
6203
7279
  }
6204
7280
  /**
@@ -6294,56 +7370,94 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6294
7370
  runtimeTools: [...data.tools?.runtimeTools || [], ...runtimeTools]
6295
7371
  }
6296
7372
  };
6297
- const response = await this.executeStream(id, requestData);
6298
- if (!response.ok) {
6299
- const error = await response.json().catch(() => ({ error: "Unknown error" }));
6300
- throw new Error(error.error || `HTTP ${response.status}`);
6301
- }
6302
- let currentBody = response.body;
7373
+ const abortSignal = options?.abortSignal;
6303
7374
  let accumulatedOutput = "";
6304
7375
  let lastKnownCost = 0;
6305
7376
  let lastKnownTokens;
7377
+ let lastSeenExecutionId = "";
6306
7378
  let pauseCount = 0;
6307
7379
  let discoveryPauseCount = 0;
6308
7380
  let consecutiveDiscoveryPauseCount = 0;
6309
7381
  const toolNameCounts = {};
6310
7382
  let recentActionKeys = [];
6311
7383
  const toolMessages = [];
7384
+ const finishAborted = (executionId) => {
7385
+ const abortCompleteEvent = {
7386
+ type: "agent_complete",
7387
+ executionId,
7388
+ seq: 0,
7389
+ agentId: id,
7390
+ success: true,
7391
+ iterations: 1,
7392
+ stopReason: "end_turn",
7393
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7394
+ totalCost: lastKnownCost,
7395
+ ...lastKnownTokens ? { totalTokens: lastKnownTokens } : {},
7396
+ finalOutput: [accumulatedOutput.trim(), "Session aborted by user request."].filter(Boolean).join("\n\n"),
7397
+ duration: 0
7398
+ };
7399
+ callbacks?.onAgentComplete?.(abortCompleteEvent);
7400
+ return { completeEvent: abortCompleteEvent, toolMessages };
7401
+ };
7402
+ let response;
7403
+ try {
7404
+ response = await this.executeStream(id, requestData, {
7405
+ ...abortSignal ? { signal: abortSignal } : {}
7406
+ });
7407
+ } catch (error) {
7408
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
7409
+ throw error;
7410
+ }
7411
+ if (!response.ok) {
7412
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
7413
+ throw new Error(error.error || `HTTP ${response.status}`);
7414
+ }
7415
+ let currentBody = response.body;
6312
7416
  while (true) {
6313
7417
  let pausedEvent = null;
6314
7418
  let completeEvent = null;
6315
- await processAgentStream(currentBody, {
6316
- ...callbacks,
6317
- onTurnDelta: (event) => {
6318
- if (event.contentType === "text") {
6319
- accumulatedOutput += event.delta;
6320
- }
6321
- callbacks?.onTurnDelta?.(event);
6322
- },
6323
- onTurnComplete: (event) => {
6324
- if (typeof event.cost === "number") {
6325
- lastKnownCost = event.cost;
6326
- }
6327
- if (event.tokens) {
6328
- lastKnownTokens = event.tokens;
6329
- }
6330
- callbacks?.onTurnComplete?.(event);
6331
- },
6332
- onAgentPaused: (event) => {
6333
- pausedEvent = event;
6334
- callbacks?.onAgentPaused?.(event);
6335
- },
6336
- onAgentComplete: (event) => {
6337
- if (!event.finalOutput && accumulatedOutput) {
6338
- event.finalOutput = accumulatedOutput;
7419
+ try {
7420
+ await processAgentStream(currentBody, {
7421
+ ...callbacks,
7422
+ onAgentStart: (event) => {
7423
+ lastSeenExecutionId = event.executionId;
7424
+ callbacks?.onAgentStart?.(event);
7425
+ },
7426
+ onTurnDelta: (event) => {
7427
+ if (event.contentType === "text") {
7428
+ accumulatedOutput += event.delta;
7429
+ }
7430
+ callbacks?.onTurnDelta?.(event);
7431
+ },
7432
+ onTurnComplete: (event) => {
7433
+ if (typeof event.cost === "number") {
7434
+ lastKnownCost = event.cost;
7435
+ }
7436
+ if (event.tokens) {
7437
+ lastKnownTokens = event.tokens;
7438
+ }
7439
+ callbacks?.onTurnComplete?.(event);
7440
+ },
7441
+ onAgentPaused: (event) => {
7442
+ pausedEvent = event;
7443
+ callbacks?.onAgentPaused?.(event);
7444
+ },
7445
+ onAgentComplete: (event) => {
7446
+ if (!event.finalOutput && accumulatedOutput) {
7447
+ event.finalOutput = accumulatedOutput;
7448
+ }
7449
+ completeEvent = event;
7450
+ callbacks?.onAgentComplete?.(event);
6339
7451
  }
6340
- completeEvent = event;
6341
- callbacks?.onAgentComplete?.(event);
6342
- }
6343
- });
7452
+ });
7453
+ } catch (error) {
7454
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
7455
+ throw error;
7456
+ }
6344
7457
  if (completeEvent) return { completeEvent, toolMessages };
6345
7458
  if (pausedEvent) {
6346
7459
  const { toolName, toolId, parameters, executionId } = pausedEvent;
7460
+ lastSeenExecutionId = executionId;
6347
7461
  const toolDef = localTools[toolName];
6348
7462
  if (!toolDef) {
6349
7463
  throw new Error(`Local tool "${toolName}" required but not provided`);
@@ -6458,6 +7572,19 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6458
7572
  callbacks?.onAgentComplete?.(forcedCompleteEvent);
6459
7573
  return { completeEvent: forcedCompleteEvent, toolMessages };
6460
7574
  }
7575
+ if (abortSignal?.aborted) {
7576
+ callbacks?.onLocalToolExecutionComplete?.({
7577
+ executionId,
7578
+ toolCallId: toolId,
7579
+ toolName,
7580
+ parameters: parsedParams,
7581
+ result: toolResult,
7582
+ success: true,
7583
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7584
+ durationMs: Date.now() - localExecutionStartedAtMs
7585
+ });
7586
+ return finishAborted(executionId);
7587
+ }
6461
7588
  if (options?.shouldInterrupt?.()) {
6462
7589
  callbacks?.onLocalToolExecutionComplete?.({
6463
7590
  executionId,
@@ -6491,15 +7618,22 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6491
7618
  callbacks?.onAgentComplete?.(interruptCompleteEvent);
6492
7619
  return { completeEvent: interruptCompleteEvent, toolMessages };
6493
7620
  }
6494
- const resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
6495
- method: "POST",
6496
- body: JSON.stringify({
6497
- executionId,
6498
- toolOutputs: { [toolName]: toolResult },
6499
- streamResponse: true,
6500
- debugMode: data.debugMode
6501
- })
6502
- });
7621
+ let resumeResponse;
7622
+ try {
7623
+ resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
7624
+ method: "POST",
7625
+ body: JSON.stringify({
7626
+ executionId,
7627
+ toolOutputs: { [toolName]: toolResult },
7628
+ streamResponse: true,
7629
+ debugMode: data.debugMode
7630
+ }),
7631
+ ...abortSignal ? { signal: abortSignal } : {}
7632
+ });
7633
+ } catch (error) {
7634
+ if (abortSignal?.aborted) return finishAborted(executionId);
7635
+ throw error;
7636
+ }
6503
7637
  if (!resumeResponse.ok) {
6504
7638
  const error = await resumeResponse.json().catch(() => ({ error: "Unknown error" }));
6505
7639
  throw new Error(error.error || `HTTP ${resumeResponse.status}`);
@@ -6517,6 +7651,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6517
7651
  currentBody = resumeResponse.body;
6518
7652
  continue;
6519
7653
  }
7654
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
6520
7655
  return null;
6521
7656
  }
6522
7657
  }
@@ -6788,7 +7923,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6788
7923
  reasons.push("Best candidate file has not been verified (read back after writing)");
6789
7924
  }
6790
7925
  if (state.verificationRequired && !state.lastVerificationPassed && !trace.verificationPassed) {
6791
- reasons.push("Verification has not passed \u2014 run a verification command (run_check) before completing");
7926
+ reasons.push(
7927
+ "Verification has not passed \u2014 run a verification command (run_check) before completing"
7928
+ );
6792
7929
  }
6793
7930
  return reasons.length > 0 ? reasons.join("; ") : "Completion gates not satisfied for the current workflow phase";
6794
7931
  }
@@ -6831,32 +7968,71 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6831
7968
  );
6832
7969
  }
6833
7970
  /**
6834
- * Compact old tool results based on context mode and window setting.
6835
- * Modifies messages in-place.
7971
+ * Resolve the replay window: the base index sliced off the full history and
7972
+ * the durable summary message that stands in for everything before it.
7973
+ * The base is honored ONLY when the latest summary actually exists —
7974
+ * slicing history with no summary substitute would silently drop prior
7975
+ * context (defensive against states that carried a base but lost their
7976
+ * summaries). The base is also clamped so a stale pointer (fork truncation,
7977
+ * trimmed legacy state) never slices past the end of the array.
6836
7978
  */
6837
- compactToolResults(messages, newToolMessages, taskName, mode, window) {
6838
- if (mode === "full-inline") return;
7979
+ resolveWindowReplay(state, messageCount) {
7980
+ const base = state.contextWindowBaseIndex ?? 0;
7981
+ if (!Number.isFinite(base) || base <= 0) {
7982
+ return { windowBase: 0, windowSummaryMessages: [] };
7983
+ }
7984
+ const summaries = state.contextCompactionSummaries;
7985
+ const latest = summaries?.[summaries.length - 1];
7986
+ if (!latest) {
7987
+ return { windowBase: 0, windowSummaryMessages: [] };
7988
+ }
7989
+ return {
7990
+ windowBase: Math.min(Math.floor(base), messageCount),
7991
+ windowSummaryMessages: [{ role: "system", content: latest.content }]
7992
+ };
7993
+ }
7994
+ /**
7995
+ * Derive the message view sent to the model based on context mode and window
7996
+ * setting. This never mutates persisted marathon history; masking/offloading
7997
+ * is a send-time view over the full-fidelity ledger/history.
7998
+ */
7999
+ deriveToolContextMessages(messages, taskName, mode, window) {
8000
+ if (mode === "full-inline") return [...messages];
8001
+ const maskMessage = (msg) => ({
8002
+ ...msg,
8003
+ toolResults: (msg.toolResults ?? []).map((tr) => this.compactOneResult(tr, taskName, mode))
8004
+ });
8005
+ const view = [...messages];
6839
8006
  if (window === "session") {
6840
- for (const msg of messages) {
8007
+ const lastUserIndex = view.reduce(
8008
+ (lastIndex, message, index) => message.role === "user" ? index : lastIndex,
8009
+ -1
8010
+ );
8011
+ for (let index = 0; index < view.length; index++) {
8012
+ if (lastUserIndex >= 0 && index > lastUserIndex) continue;
8013
+ const msg = view[index];
8014
+ if (!msg) continue;
6841
8015
  if (msg.role === "tool" && msg.toolResults) {
6842
- msg.toolResults = msg.toolResults.map((tr) => this.compactOneResult(tr, taskName, mode));
8016
+ view[index] = maskMessage(msg);
6843
8017
  }
6844
8018
  }
6845
8019
  } else {
6846
- const newToolResultCount = newToolMessages.filter((m) => m.role === "tool").length;
6847
- const keepInlineFromExisting = Math.max(0, window - newToolResultCount);
6848
8020
  const toolResultIndices = [];
6849
- for (let i = 0; i < messages.length; i++) {
6850
- if (messages[i].role === "tool" && messages[i].toolResults) {
8021
+ for (let i = 0; i < view.length; i++) {
8022
+ const message = view[i];
8023
+ if (message?.role === "tool" && message.toolResults) {
6851
8024
  toolResultIndices.push(i);
6852
8025
  }
6853
8026
  }
6854
- const compactUpTo = toolResultIndices.length - keepInlineFromExisting;
8027
+ const compactUpTo = toolResultIndices.length - window;
6855
8028
  for (let j = 0; j < compactUpTo && j < toolResultIndices.length; j++) {
6856
- const msg = messages[toolResultIndices[j]];
6857
- msg.toolResults = msg.toolResults.map((tr) => this.compactOneResult(tr, taskName, mode));
8029
+ const index = toolResultIndices[j];
8030
+ const msg = index === void 0 ? void 0 : view[index];
8031
+ if (!msg) continue;
8032
+ view[index] = maskMessage(msg);
6858
8033
  }
6859
8034
  }
8035
+ return view;
6860
8036
  }
6861
8037
  compactOneResult(tr, taskName, mode) {
6862
8038
  if (typeof tr.result === "string" && tr.result.startsWith("[")) return tr;
@@ -6892,10 +8068,20 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6892
8068
  }
6893
8069
  const slug = this.sanitizeTaskSlug(taskName || "task");
6894
8070
  const dir = `.runtype/marathons/${slug}/tool-outputs`;
6895
- fs.mkdirSync(dir, { recursive: true });
6896
8071
  const filePath = `${dir}/${toolCallId}.txt`;
6897
- fs.writeFileSync(filePath, resultStr, "utf-8");
6898
- return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
8072
+ try {
8073
+ if (!fs.existsSync(filePath)) {
8074
+ fs.mkdirSync(dir, { recursive: true });
8075
+ fs.writeFileSync(filePath, resultStr, "utf-8");
8076
+ }
8077
+ } catch {
8078
+ return result;
8079
+ }
8080
+ return buildSendViewOffloadMarker({
8081
+ toolName,
8082
+ charLength: resultStr.length,
8083
+ filePath
8084
+ });
6899
8085
  }
6900
8086
  getDefaultPlanPath(taskName) {
6901
8087
  return getDefaultPlanPath(taskName);
@@ -7017,6 +8203,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7017
8203
  const migratedPlanPath = workflowVariant === "external" && planPath === this.getDefaultPlanPath(taskName) ? this.getDefaultExternalReportPath(taskName) : planPath;
7018
8204
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
7019
8205
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
8206
+ const contextCompactionSummaries = (resumeState.contextCompactionSummaries ?? []).slice(-20);
7020
8207
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
7021
8208
  const bestCandidatePath = normalizedBestCandidatePath && !this.isMarathonArtifactPath(normalizedBestCandidatePath) ? normalizedBestCandidatePath : [...candidatePaths, ...recentReadPaths].sort(
7022
8209
  (left, right) => this.scoreCandidatePath(right) - this.scoreCandidatePath(left)
@@ -7036,10 +8223,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7036
8223
  bestCandidateVerified: Boolean(resumeState.bestCandidateVerified),
7037
8224
  ...resumeState.verificationRequired !== void 0 ? { verificationRequired: resumeState.verificationRequired } : {},
7038
8225
  lastVerificationPassed: Boolean(resumeState.lastVerificationPassed),
7039
- ...resumeState.consecutiveBlockedVerificationSessions !== void 0 ? { consecutiveBlockedVerificationSessions: resumeState.consecutiveBlockedVerificationSessions } : {},
8226
+ ...resumeState.consecutiveBlockedVerificationSessions !== void 0 ? {
8227
+ consecutiveBlockedVerificationSessions: resumeState.consecutiveBlockedVerificationSessions
8228
+ } : {},
7040
8229
  ...resumeState.isCreationTask !== void 0 ? { isCreationTask: resumeState.isCreationTask } : {},
7041
8230
  ...resumeState.workflowVariant !== void 0 ? { workflowVariant: resumeState.workflowVariant } : {},
7042
8231
  ...resumeState.workflowState !== void 0 ? { workflowState: resumeState.workflowState } : {},
8232
+ ...contextCompactionSummaries.length ? { contextCompactionSummaries } : {},
8233
+ ...typeof resumeState.contextWindowBaseIndex === "number" && Number.isFinite(resumeState.contextWindowBaseIndex) && resumeState.contextWindowBaseIndex > 0 ? { contextWindowBaseIndex: Math.floor(resumeState.contextWindowBaseIndex) } : {},
7043
8234
  ...typeof resumeState.outputRoot === "string" && resumeState.outputRoot.trim() ? { outputRoot: resumeState.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/") } : {}
7044
8235
  };
7045
8236
  }
@@ -7439,8 +8630,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7439
8630
  }
7440
8631
  buildStuckTurnRecoveryMessage(state, workflow) {
7441
8632
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
7442
- if (currentPhase?.buildRecoveryMessage) {
7443
- return currentPhase.buildRecoveryMessage(state);
8633
+ const phaseMessage = currentPhase?.buildRecoveryMessage?.(state);
8634
+ if (phaseMessage) return phaseMessage;
8635
+ const emptySessions = state.consecutiveEmptySessions || 0;
8636
+ if (shouldInjectEmptySessionNudge(workflow.stallPolicy, emptySessions)) {
8637
+ return buildEmptySessionNudge(emptySessions);
7444
8638
  }
7445
8639
  return void 0;
7446
8640
  }
@@ -7505,8 +8699,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7505
8699
  } : {},
7506
8700
  ...seededResumeState?.candidatePaths ? { candidatePaths: seededResumeState.candidatePaths } : {},
7507
8701
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
7508
- ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
8702
+ ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {},
8703
+ ...seededResumeState?.contextCompactionSummaries?.length ? { contextCompactionSummaries: seededResumeState.contextCompactionSummaries } : {},
8704
+ ...typeof seededResumeState?.contextWindowBaseIndex === "number" ? { contextWindowBaseIndex: seededResumeState.contextWindowBaseIndex } : {}
7509
8705
  };
8706
+ if (options.previousMessages && options.previousMessages.length > 0) {
8707
+ state.messages = options.previousMessages.map((message) => structuredClone(message));
8708
+ }
7510
8709
  state.workflowVariant = classifiedVariant;
7511
8710
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
7512
8711
  state.outputRoot = seededResumeState?.outputRoot ?? (state.isCreationTask ? "public/" : void 0);
@@ -7539,6 +8738,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7539
8738
  }
7540
8739
  }
7541
8740
  for (let session = 0; session < maxSessions; session++) {
8741
+ if (options.abortSignal?.aborted) {
8742
+ state.status = "paused";
8743
+ break;
8744
+ }
7542
8745
  const phaseAtSessionStart = state.workflowPhase;
7543
8746
  const sessionTrace = this.createEmptyToolTrace();
7544
8747
  const sessionLocalTools = this.wrapLocalToolsForTrace(
@@ -7557,6 +8760,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7557
8760
  state.originalMessage = options.message;
7558
8761
  }
7559
8762
  const queuedSteeringMessages = options.getQueuedUserMessages?.() ?? [];
8763
+ if (queuedSteeringMessages.length > 0) {
8764
+ state.consecutiveEmptySessions = 0;
8765
+ state.stallEscalationRequested = void 0;
8766
+ }
7560
8767
  const preparedSession = await this.prepareSessionContext(
7561
8768
  options.message,
7562
8769
  state,
@@ -7575,7 +8782,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7575
8782
  localTools: options.localTools,
7576
8783
  builtinToolSchemas,
7577
8784
  onContextCompaction: options.onContextCompaction,
7578
- onContextNotice: options.onContextNotice
8785
+ onContextNotice: options.onContextNotice,
8786
+ toolContextMode: options.toolContextMode || "hot-tail",
8787
+ toolWindow: options.toolWindow ?? "session"
7579
8788
  },
7580
8789
  queuedSteeringMessages
7581
8790
  );
@@ -7605,7 +8814,8 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7605
8814
  sessionCallbacks,
7606
8815
  {
7607
8816
  onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow),
7608
- shouldInterrupt: options.hasQueuedUserMessages
8817
+ shouldInterrupt: options.hasQueuedUserMessages,
8818
+ ...options.abortSignal ? { abortSignal: options.abortSignal } : {}
7609
8819
  },
7610
8820
  state.taskName
7611
8821
  );
@@ -7754,22 +8964,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7754
8964
  }
7755
8965
  }
7756
8966
  if (!state.messages) state.messages = [];
7757
- if (this.isCompactHistoryMessageSet(messages)) {
7758
- state.messages = [...messages];
7759
- } else if (state.messages.length > 0 && messages.length > state.messages.length) {
7760
- const newMessages = messages.slice(state.messages.length);
7761
- state.messages.push(...newMessages);
7762
- } else {
8967
+ const sentUserMessage = messages[messages.length - 1];
8968
+ if (sentUserMessage?.role === "user") {
8969
+ state.messages.push(sentUserMessage);
8970
+ } else if (state.messages.length === 0) {
7763
8971
  state.messages.push(...messages);
7764
8972
  }
7765
8973
  if (sessionToolMessages.length > 0) {
7766
- this.compactToolResults(
7767
- state.messages,
7768
- sessionToolMessages,
7769
- state.taskName,
7770
- options.toolContextMode || "hot-tail",
7771
- options.toolWindow ?? "session"
7772
- );
7773
8974
  state.messages.push(...sessionToolMessages);
7774
8975
  }
7775
8976
  const assistantContent = effectiveSessionOutput || `[Session ${session + 1} completed (${sessionResult.stopReason}). No text output captured.]`;
@@ -7789,7 +8990,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7789
8990
  workflow
7790
8991
  );
7791
8992
  if (detectedTaskCompletion && !acceptedTaskCompletion) {
7792
- state.lastCompletionRejectionReason = this.computeCompletionRejectionReason(state, sessionTrace);
8993
+ state.lastCompletionRejectionReason = this.computeCompletionRejectionReason(
8994
+ state,
8995
+ sessionTrace
8996
+ );
7793
8997
  if (state.verificationRequired && !state.lastVerificationPassed && !sessionTrace.verificationPassed && !sessionTrace.verificationAttempted) {
7794
8998
  state.consecutiveBlockedVerificationSessions = (state.consecutiveBlockedVerificationSessions || 0) + 1;
7795
8999
  if ((state.consecutiveBlockedVerificationSessions || 0) >= 2) {
@@ -7800,32 +9004,36 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7800
9004
  } else {
7801
9005
  state.lastCompletionRejectionReason = void 0;
7802
9006
  }
7803
- const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted;
9007
+ const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted || options.hasQueuedUserMessages?.() === true;
7804
9008
  if (sessionHadActions) {
7805
9009
  state.consecutiveEmptySessions = 0;
9010
+ state.stallEscalationRequested = void 0;
7806
9011
  } else {
7807
9012
  state.consecutiveEmptySessions = (state.consecutiveEmptySessions || 0) + 1;
9013
+ if (shouldRequestModelEscalation(workflow.stallPolicy, state.consecutiveEmptySessions)) {
9014
+ state.stallEscalationRequested = true;
9015
+ }
7808
9016
  }
7809
9017
  if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
7810
9018
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
7811
- const gatesSatisfied = currentPhase?.canAcceptCompletion ? currentPhase.canAcceptCompletion(state, sessionTrace) : true;
9019
+ const gatesSatisfied = currentPhase?.canAcceptCompletion ? currentPhase.canAcceptCompletion(
9020
+ state,
9021
+ sessionTrace
9022
+ ) : true;
7812
9023
  if (gatesSatisfied) {
7813
9024
  state.status = "complete";
7814
9025
  }
7815
9026
  } else if (sessionResult.stopReason === "error") {
7816
9027
  if (_AgentsEndpoint.isRetryableSessionError(sessionResult.error) && consecutiveServerNetworkErrors < maxServerNetworkRetries) {
7817
9028
  consecutiveServerNetworkErrors++;
7818
- const delayMs = Math.min(
7819
- 5e3 * Math.pow(2, consecutiveServerNetworkErrors - 1),
7820
- 3e4
7821
- );
9029
+ const delayMs = Math.min(5e3 * Math.pow(2, consecutiveServerNetworkErrors - 1), 3e4);
7822
9030
  const delaySec = Math.round(delayMs / 1e3);
7823
9031
  await this.emitContextNotice(options.onContextNotice, {
7824
9032
  kind: "server_network_retry",
7825
9033
  sessionIndex: session,
7826
9034
  message: `Server network error: ${sessionResult.error}. Retrying in ${delaySec}s (attempt ${consecutiveServerNetworkErrors}/${maxServerNetworkRetries})...`
7827
9035
  });
7828
- await new Promise((resolve) => setTimeout(resolve, delayMs));
9036
+ await sleepWithAbort(delayMs, options.abortSignal);
7829
9037
  } else {
7830
9038
  state.status = "error";
7831
9039
  }
@@ -7833,13 +9041,16 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7833
9041
  state.status = "budget_exceeded";
7834
9042
  } else if (acceptedTaskCompletion) {
7835
9043
  state.status = "complete";
7836
- } else if ((state.consecutiveEmptySessions || 0) >= 3) {
9044
+ } else if ((state.consecutiveEmptySessions || 0) >= resolveStallStopAfter(workflow.stallPolicy)) {
7837
9045
  state.status = "stalled";
7838
9046
  } else if (maxCost && state.totalCost >= maxCost) {
7839
9047
  state.status = "budget_exceeded";
7840
9048
  } else if (session + 1 >= maxSessions) {
7841
9049
  state.status = "max_sessions";
7842
9050
  }
9051
+ if (options.abortSignal?.aborted) {
9052
+ state.status = "paused";
9053
+ }
7843
9054
  if (options.trackProgress) {
7844
9055
  recordId = await this.syncProgressRecord(state, recordId);
7845
9056
  }
@@ -7916,6 +9127,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7916
9127
  return 0;
7917
9128
  }
7918
9129
  }
9130
+ extractDeclaredToolResultChars(value) {
9131
+ return extractDeclaredToolResultChars(value);
9132
+ }
7919
9133
  estimateMessageContentTokens(content) {
7920
9134
  if (typeof content === "string") return this.estimateTextTokens(content);
7921
9135
  return content.reduce((total, part) => {
@@ -7934,12 +9148,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7934
9148
  0
7935
9149
  );
7936
9150
  }
7937
- estimateToolResultTokens(toolResults) {
9151
+ estimateToolResultTokens(toolResults, options) {
7938
9152
  if (!toolResults || toolResults.length === 0) return 0;
7939
- return toolResults.reduce(
7940
- (sum, toolResult) => sum + 12 + this.estimateTextTokens(toolResult.toolName) + this.estimateUnknownTokens(toolResult.result),
7941
- 0
7942
- );
9153
+ return toolResults.reduce((sum, toolResult) => {
9154
+ const resultTokens = this.estimateUnknownTokens(toolResult.result);
9155
+ const declaredChars = options?.useDeclaredSize ? this.extractDeclaredToolResultChars(toolResult.result) : void 0;
9156
+ const declaredTokens = typeof declaredChars === "number" ? Math.ceil(declaredChars / 4) : 0;
9157
+ return sum + 12 + this.estimateTextTokens(toolResult.toolName) + Math.max(resultTokens, declaredTokens);
9158
+ }, 0);
7943
9159
  }
7944
9160
  estimateMessageTokens(message) {
7945
9161
  return 6 + this.estimateMessageContentTokens(message.content) + this.estimateToolCallTokens(message.toolCalls) + this.estimateToolResultTokens(message.toolResults);
@@ -7947,13 +9163,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7947
9163
  estimateConversationTokens(messages) {
7948
9164
  return messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
7949
9165
  }
7950
- estimateConversationBreakdown(messages) {
9166
+ estimateConversationBreakdown(messages, options) {
7951
9167
  let historyTokens = 0;
7952
9168
  let toolOutputTokens = 0;
7953
9169
  for (const message of messages) {
7954
9170
  const contentTokens = this.estimateMessageContentTokens(message.content);
7955
9171
  const toolCallTokens = this.estimateToolCallTokens(message.toolCalls);
7956
- const toolResultTokens = this.estimateToolResultTokens(message.toolResults);
9172
+ const toolResultTokens = this.estimateToolResultTokens(message.toolResults, {
9173
+ useDeclaredSize: options?.useDeclaredToolResultSizes
9174
+ });
7957
9175
  const messageTotal = 6 + contentTokens + toolCallTokens + toolResultTokens;
7958
9176
  if (message.role === "tool") {
7959
9177
  toolOutputTokens += messageTotal;
@@ -8007,13 +9225,24 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8007
9225
  const compactInstructions = config.compactInstructions;
8008
9226
  return typeof compactInstructions === "string" && compactInstructions.trim() ? compactInstructions.trim() : void 0;
8009
9227
  }
8010
- extractArtifactReferences(state) {
9228
+ extractArtifactReferencesFromMessages(messages = []) {
8011
9229
  const references = /* @__PURE__ */ new Set();
8012
9230
  const offloadPrefix = "[Output saved to ";
8013
- for (const message of state.messages ?? []) {
9231
+ const ledgerArtifactPrefix = LEDGER_ARTIFACT_LINE_PREFIX;
9232
+ const savedToPattern = /saved to\s+([^—\]\n]+?)(?:\s+—|\]|\n|$)/gi;
9233
+ for (const message of messages) {
8014
9234
  if (!message.toolResults) continue;
8015
9235
  for (const toolResult of message.toolResults) {
8016
9236
  if (typeof toolResult.result !== "string") continue;
9237
+ for (const line of toolResult.result.split("\n")) {
9238
+ if (line.startsWith(ledgerArtifactPrefix)) {
9239
+ references.add(line.slice(ledgerArtifactPrefix.length).trim());
9240
+ }
9241
+ }
9242
+ for (const match of toolResult.result.matchAll(savedToPattern)) {
9243
+ const pathText = match[1]?.trim();
9244
+ if (pathText) references.add(pathText);
9245
+ }
8017
9246
  let startIndex = 0;
8018
9247
  while (startIndex < toolResult.result.length) {
8019
9248
  const prefixIndex = toolResult.result.indexOf(offloadPrefix, startIndex);
@@ -8031,6 +9260,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8031
9260
  }
8032
9261
  }
8033
9262
  }
9263
+ return Array.from(references);
9264
+ }
9265
+ extractArtifactReferences(state, additionalReferences = []) {
9266
+ const references = /* @__PURE__ */ new Set([
9267
+ ...this.extractArtifactReferencesFromMessages(state.messages ?? []),
9268
+ ...additionalReferences
9269
+ ]);
8034
9270
  if (state.planPath) {
8035
9271
  references.add(state.planPath);
8036
9272
  }
@@ -8038,6 +9274,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
8038
9274
  }
8039
9275
  buildContextBudgetBreakdown(details) {
8040
9276
  const conversationBreakdown = this.estimateConversationBreakdown(details.historyMessages);
9277
+ const sourceConversationBreakdown = details.sourceHistoryMessages ? this.estimateConversationBreakdown(details.sourceHistoryMessages, {
9278
+ useDeclaredToolResultSizes: true
9279
+ }) : conversationBreakdown;
8041
9280
  const currentTurnTokens = this.estimateTextTokens(details.currentTurnContent);
8042
9281
  const toolDefinitionTokens = this.estimateToolDefinitionTokens(
8043
9282
  details.localTools,
@@ -8054,15 +9293,26 @@ ${details.summaryText}
8054
9293
 
8055
9294
  Do NOT redo any of the above work.`
8056
9295
  ) : void 0;
9296
+ const sendEstimatedInputTokens = conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens;
9297
+ const estimatedInputTokens = sourceConversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens;
9298
+ const toolOutputReductionTokens = Math.max(
9299
+ 0,
9300
+ sourceConversationBreakdown.toolOutputTokens - conversationBreakdown.toolOutputTokens
9301
+ );
8057
9302
  return {
8058
- historyTokens: conversationBreakdown.historyTokens,
8059
- toolOutputTokens: conversationBreakdown.toolOutputTokens,
9303
+ historyTokens: sourceConversationBreakdown.historyTokens,
9304
+ toolOutputTokens: sourceConversationBreakdown.toolOutputTokens,
8060
9305
  currentTurnTokens,
8061
9306
  toolDefinitionTokens,
8062
9307
  ...summaryTokens ? { summaryTokens } : {},
8063
9308
  ...reservedOutputTokens ? { reservedOutputTokens } : {},
8064
9309
  ...effectiveInputBudgetTokens ? { effectiveInputBudgetTokens } : {},
8065
- estimatedInputTokens: conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens
9310
+ ...toolOutputReductionTokens > 0 ? {
9311
+ sendEstimatedInputTokens,
9312
+ sendToolOutputTokens: conversationBreakdown.toolOutputTokens,
9313
+ toolOutputReductionTokens
9314
+ } : {},
9315
+ estimatedInputTokens
8066
9316
  };
8067
9317
  }
8068
9318
  async emitContextCompactionEvent(onContextCompaction, event) {
@@ -8102,16 +9352,33 @@ Do NOT redo any of the above work.`
8102
9352
  state,
8103
9353
  userContent,
8104
9354
  details.compactInstructions,
8105
- details.mode
9355
+ details.mode,
9356
+ details.artifactReferences
8106
9357
  );
9358
+ const summaryMessage = compactMessages[0];
9359
+ if (summaryMessage?.role === "system" && typeof summaryMessage.content === "string") {
9360
+ const existingSummaries = state.contextCompactionSummaries ?? [];
9361
+ state.contextCompactionSummaries = [
9362
+ ...existingSummaries,
9363
+ {
9364
+ id: `ctx_${sessionIndex + 1}_${existingSummaries.length + 1}`,
9365
+ sessionIndex: sessionIndex + 1,
9366
+ mode: details.mode,
9367
+ strategy: details.strategy,
9368
+ content: summaryMessage.content,
9369
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
9370
+ }
9371
+ ].slice(-20);
9372
+ }
9373
+ state.contextWindowBaseIndex = state.messages?.length ?? 0;
8107
9374
  await this.emitContextCompactionEvent(details.onContextCompaction, {
8108
9375
  phase: "complete",
8109
9376
  ...baseEvent
8110
9377
  });
8111
9378
  return compactMessages;
8112
9379
  }
8113
- buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto") {
8114
- const summary = this.generateCompactSummary(state, compactInstructions);
9380
+ buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto", artifactReferences) {
9381
+ const summary = this.generateCompactSummary(state, compactInstructions, artifactReferences);
8115
9382
  const prefix = mode === "forced" ? this.getForcedCompactionSummaryPrefix(state) : _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX;
8116
9383
  return [
8117
9384
  {
@@ -8128,16 +9395,11 @@ Do NOT redo any of the above work.`
8128
9395
  }
8129
9396
  ];
8130
9397
  }
8131
- isCompactHistoryMessageSet(messages) {
8132
- if (messages.length === 0) return false;
8133
- const firstMessage = messages[0];
8134
- return firstMessage?.role === "system" && typeof firstMessage.content === "string" && (firstMessage.content.startsWith(_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX) || firstMessage.content.startsWith(_AgentsEndpoint.RESUMED_COMPACT_SUMMARY_PREFIX) || firstMessage.content.startsWith(_AgentsEndpoint.COMPLETED_COMPACT_SUMMARY_PREFIX));
8135
- }
8136
9398
  /**
8137
9399
  * Generate a compact summary of prior work for continuation context.
8138
9400
  * Used when compact mode is enabled to keep token usage low.
8139
9401
  */
8140
- generateCompactSummary(state, compactInstructions) {
9402
+ generateCompactSummary(state, compactInstructions, additionalArtifactReferences = []) {
8141
9403
  const recentSessions = (state.sessions ?? []).slice(-5);
8142
9404
  const sessionSummaries = recentSessions.map(
8143
9405
  (session) => `- Session ${session.index}: ${session.stopReason} ($${session.cost.toFixed(4)}) ${session.outputPreview.slice(0, 160)}`
@@ -8151,7 +9413,7 @@ Do NOT redo any of the above work.`
8151
9413
  )
8152
9414
  ).slice(0, 8);
8153
9415
  const verificationSummary = state.bestCandidateVerified ? "Latest candidate verified." : state.bestCandidateNeedsVerification ? "Latest candidate still needs verification." : state.lastVerificationPassed ? "Latest verification passed." : state.verificationRequired ? "Verification is still required." : "No verification requirement recorded.";
8154
- const artifactReferences = this.extractArtifactReferences(state);
9416
+ const artifactReferences = this.extractArtifactReferences(state, additionalArtifactReferences);
8155
9417
  const pendingNextStep = state.lastStopReason === "complete" ? "Confirm nothing else remains before declaring the task complete." : `Continue the ${state.workflowPhase || "research"} phase without redoing prior work.`;
8156
9418
  const instructions = compactInstructions || this.buildDefaultCompactInstructions();
8157
9419
  return [
@@ -8366,14 +9628,23 @@ Do NOT redo any of the above work.`
8366
9628
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
8367
9629
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
8368
9630
  const multiSessionInstruction = `This is a multi-session task (session ${sessionIndex + 1}/${maxSessions}). When you have fully completed the task, end your response with TASK_COMPLETE on its own line.`;
8369
- const steeringLines = steeringMessages && steeringMessages.length > 0 ? [
8370
- "--- User steering (queued during the previous session) ---",
8371
- ...steeringMessages,
8372
- ""
8373
- ] : [];
9631
+ const steeringLines = steeringMessages && steeringMessages.length > 0 ? ["--- User steering (queued during the previous session) ---", ...steeringMessages, ""] : [];
8374
9632
  if (continuationContext && sessionIndex === 0) {
8375
- const replayHistoryMessages = this.sanitizeReplayHistoryMessages(
8376
- continuationContext.previousMessages
9633
+ const resumeWindow = this.resolveWindowReplay(
9634
+ state,
9635
+ continuationContext.previousMessages.length
9636
+ );
9637
+ const sourceReplayHistoryMessages = [
9638
+ ...resumeWindow.windowSummaryMessages,
9639
+ ...this.sanitizeReplayHistoryMessages(
9640
+ continuationContext.previousMessages.slice(resumeWindow.windowBase)
9641
+ )
9642
+ ];
9643
+ const replayHistoryMessages = this.deriveToolContextMessages(
9644
+ sourceReplayHistoryMessages,
9645
+ state.taskName,
9646
+ compactionOptions?.toolContextMode || "hot-tail",
9647
+ compactionOptions?.toolWindow ?? "session"
8377
9648
  );
8378
9649
  const continuationGuardrail = this.buildContinuationGuardrail(state);
8379
9650
  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.";
@@ -8397,9 +9668,15 @@ Do NOT redo any of the above work.`
8397
9668
  content: userContent
8398
9669
  }
8399
9670
  ];
8400
- const summaryText = this.generateCompactSummary(state, compactInstructions);
9671
+ const replayArtifactReferences = this.extractArtifactReferencesFromMessages(replayHistoryMessages);
9672
+ const summaryText = this.generateCompactSummary(
9673
+ state,
9674
+ compactInstructions,
9675
+ replayArtifactReferences
9676
+ );
8401
9677
  const breakdown = this.buildContextBudgetBreakdown({
8402
9678
  historyMessages: replayHistoryMessages,
9679
+ sourceHistoryMessages: sourceReplayHistoryMessages,
8403
9680
  currentTurnContent: userContent,
8404
9681
  localTools: compactionOptions?.localTools,
8405
9682
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -8425,7 +9702,8 @@ Do NOT redo any of the above work.`
8425
9702
  reservedOutputTokens: breakdown.reservedOutputTokens,
8426
9703
  breakdown,
8427
9704
  onContextCompaction: compactionOptions?.onContextCompaction,
8428
- compactInstructions
9705
+ compactInstructions,
9706
+ artifactReferences: replayArtifactReferences
8429
9707
  }
8430
9708
  ),
8431
9709
  requestContextManagement
@@ -8449,7 +9727,8 @@ Do NOT redo any of the above work.`
8449
9727
  reservedOutputTokens: breakdown.reservedOutputTokens,
8450
9728
  breakdown,
8451
9729
  onContextCompaction: compactionOptions?.onContextCompaction,
8452
- compactInstructions
9730
+ compactInstructions,
9731
+ artifactReferences: replayArtifactReferences
8453
9732
  }
8454
9733
  ),
8455
9734
  requestContextManagement
@@ -8500,23 +9779,45 @@ Do NOT redo any of the above work.`
8500
9779
  "Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
8501
9780
  ].join("\n");
8502
9781
  const MAX_HISTORY_MESSAGES = 60;
8503
- let historyMessages = this.sanitizeReplayHistoryMessages(state.messages);
8504
- if (historyMessages.length > MAX_HISTORY_MESSAGES) {
8505
- const trimmedHistory = this.trimReplayHistoryMessages(historyMessages, MAX_HISTORY_MESSAGES);
8506
- historyMessages = trimmedHistory.historyMessages;
9782
+ const { windowBase, windowSummaryMessages } = this.resolveWindowReplay(
9783
+ state,
9784
+ state.messages.length
9785
+ );
9786
+ let sourceHistoryMessages = this.sanitizeReplayHistoryMessages(
9787
+ state.messages.slice(windowBase)
9788
+ );
9789
+ if (sourceHistoryMessages.length > MAX_HISTORY_MESSAGES) {
9790
+ const trimmedHistory = this.trimReplayHistoryMessages(
9791
+ sourceHistoryMessages,
9792
+ MAX_HISTORY_MESSAGES
9793
+ );
9794
+ sourceHistoryMessages = trimmedHistory.historyMessages;
8507
9795
  if (trimmedHistory.trimmedCount > 0) {
8508
- historyMessages = [
9796
+ sourceHistoryMessages = [
8509
9797
  {
8510
9798
  role: "system",
8511
9799
  content: `[${trimmedHistory.trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
8512
9800
  },
8513
- ...historyMessages
9801
+ ...sourceHistoryMessages
8514
9802
  ];
8515
9803
  }
8516
9804
  }
8517
- const summaryText = this.generateCompactSummary(state, compactInstructions);
9805
+ sourceHistoryMessages = [...windowSummaryMessages, ...sourceHistoryMessages];
9806
+ const historyMessages = this.deriveToolContextMessages(
9807
+ sourceHistoryMessages,
9808
+ state.taskName,
9809
+ compactionOptions?.toolContextMode || "hot-tail",
9810
+ compactionOptions?.toolWindow ?? "session"
9811
+ );
9812
+ const historyArtifactReferences = this.extractArtifactReferencesFromMessages(historyMessages);
9813
+ const summaryText = this.generateCompactSummary(
9814
+ state,
9815
+ compactInstructions,
9816
+ historyArtifactReferences
9817
+ );
8518
9818
  const breakdown = this.buildContextBudgetBreakdown({
8519
9819
  historyMessages,
9820
+ sourceHistoryMessages,
8520
9821
  currentTurnContent: continuationContent,
8521
9822
  localTools: compactionOptions?.localTools,
8522
9823
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -8543,7 +9844,8 @@ Do NOT redo any of the above work.`
8543
9844
  reservedOutputTokens: breakdown.reservedOutputTokens,
8544
9845
  breakdown,
8545
9846
  onContextCompaction: compactionOptions?.onContextCompaction,
8546
- compactInstructions
9847
+ compactInstructions,
9848
+ artifactReferences: historyArtifactReferences
8547
9849
  }
8548
9850
  ),
8549
9851
  requestContextManagement
@@ -9017,6 +10319,52 @@ var BillingEndpoint = class {
9017
10319
  return this.client.get("/billing/spend-analytics", params);
9018
10320
  }
9019
10321
  };
10322
+ var AppsEndpoint = class {
10323
+ constructor(client) {
10324
+ this.client = client;
10325
+ }
10326
+ /** List apps for the authenticated owner, newest first. */
10327
+ async list() {
10328
+ return this.client.get("/apps");
10329
+ }
10330
+ /** Get an app by id, including its URL and active version pointer. */
10331
+ async get(id) {
10332
+ return this.client.get(`/apps/${id}`);
10333
+ }
10334
+ /** Create an app. A client token scoped to the app origin is auto-provisioned. */
10335
+ async create(data) {
10336
+ return this.client.post("/apps", data);
10337
+ }
10338
+ /** Update name, description, visibility, or status (suspended serves 410). */
10339
+ async update(id, data) {
10340
+ return this.client.patch(`/apps/${id}`, data);
10341
+ }
10342
+ /** Delete an app, its versions, and its hosting. Irreversible. */
10343
+ async delete(id) {
10344
+ return this.client.delete(`/apps/${id}`);
10345
+ }
10346
+ /** List an app's versions, newest first. */
10347
+ async listVersions(id) {
10348
+ return this.client.get(`/apps/${id}/versions`);
10349
+ }
10350
+ /** Upload a zipped bundle (raw application/zip body) as a new version. */
10351
+ async uploadVersion(id, zipBytes) {
10352
+ return this.client.postBinary(`/apps/${id}/versions`, zipBytes, "application/zip");
10353
+ }
10354
+ /**
10355
+ * Upload a bundle from in-memory file maps (the API zips server-side).
10356
+ * Text files in `files`, binary files base64-encoded in `filesBase64`.
10357
+ */
10358
+ async uploadVersionFiles(id, data) {
10359
+ return this.client.post(`/apps/${id}/versions`, data);
10360
+ }
10361
+ /** Activate an uploaded version (deploy or rollback). */
10362
+ async activate(id, versionId) {
10363
+ return this.client.post(`/apps/${id}/activate`, {
10364
+ versionId
10365
+ });
10366
+ }
10367
+ };
9020
10368
 
9021
10369
  // src/client.ts
9022
10370
  function isObjectRecord(value) {
@@ -9059,6 +10407,7 @@ var RuntypeClient2 = class {
9059
10407
  this.clientTokens = new ClientTokensEndpoint(this);
9060
10408
  this.agents = new AgentsEndpoint(this);
9061
10409
  this.secrets = new SecretsEndpoint(this);
10410
+ this.apps = new AppsEndpoint(this);
9062
10411
  this.schedules = new SchedulesEndpoint(this);
9063
10412
  this.surfaces = new SurfacesEndpoint(this);
9064
10413
  this.conversations = new ConversationsEndpoint(this);
@@ -9105,7 +10454,7 @@ var RuntypeClient2 = class {
9105
10454
  clearApiKey() {
9106
10455
  delete this.headers.Authorization;
9107
10456
  }
9108
- async runWithLocalTools(request, localTools, arg3, arg4) {
10457
+ async runWithLocalTools(request2, localTools, arg3, arg4) {
9109
10458
  const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
9110
10459
  const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
9111
10460
  const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
@@ -9119,12 +10468,12 @@ var RuntypeClient2 = class {
9119
10468
  ...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
9120
10469
  })) : [];
9121
10470
  const modifiedRequest = {
9122
- ...request,
10471
+ ...request2,
9123
10472
  ...derivedClientTools.length > 0 ? {
9124
- clientTools: [...request.clientTools ?? [], ...derivedClientTools]
10473
+ clientTools: [...request2.clientTools ?? [], ...derivedClientTools]
9125
10474
  } : {},
9126
10475
  options: {
9127
- ...request.options || {},
10476
+ ...request2.options || {},
9128
10477
  streamResponse: isStreaming
9129
10478
  }
9130
10479
  };
@@ -9140,13 +10489,12 @@ var RuntypeClient2 = class {
9140
10489
  onFlowComplete: (event) => callbacks?.onFlowComplete?.(event),
9141
10490
  onError: (error) => callbacks?.onError?.(error)
9142
10491
  };
9143
- const { streamEvents: streamEvents2, stepDeltaText: stepDeltaText2, stepDisplayName: stepDisplayName2, flowErrorMessage: flowErrorMessage2 } = await Promise.resolve().then(() => (init_stream_utils(), stream_utils_exports));
9144
10492
  const summary = {
9145
10493
  results: /* @__PURE__ */ new Map(),
9146
10494
  success: true
9147
10495
  };
9148
10496
  try {
9149
- for await (const event of streamEvents2(response)) {
10497
+ for await (const event of streamEvents(response)) {
9150
10498
  collectLocalToolAwait(pausedTools, event);
9151
10499
  switch (event.type) {
9152
10500
  case "flow_start":
@@ -9156,10 +10504,10 @@ var RuntypeClient2 = class {
9156
10504
  wrappedCallbacks.onStepStart?.(event);
9157
10505
  break;
9158
10506
  case "step_delta":
9159
- wrappedCallbacks.onStepDelta?.(stepDeltaText2(event), event);
10507
+ wrappedCallbacks.onStepDelta?.(stepDeltaText(event), event);
9160
10508
  break;
9161
10509
  case "step_complete": {
9162
- summary.results?.set(stepDisplayName2(event), event.result);
10510
+ summary.results?.set(stepDisplayName(event), event.result);
9163
10511
  wrappedCallbacks.onStepComplete?.(event.result, event);
9164
10512
  break;
9165
10513
  }
@@ -9167,7 +10515,7 @@ var RuntypeClient2 = class {
9167
10515
  wrappedCallbacks.onFlowComplete?.(event);
9168
10516
  break;
9169
10517
  case "flow_error":
9170
- wrappedCallbacks.onError?.(new Error(flowErrorMessage2(event)));
10518
+ wrappedCallbacks.onError?.(new Error(flowErrorMessage(event)));
9171
10519
  break;
9172
10520
  }
9173
10521
  }
@@ -9246,7 +10594,8 @@ var RuntypeClient2 = class {
9246
10594
  return [toolName, await handler(parameters)];
9247
10595
  } catch (error) {
9248
10596
  throw new Error(
9249
- `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`
10597
+ `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`,
10598
+ { cause: error }
9250
10599
  );
9251
10600
  }
9252
10601
  })
@@ -9310,6 +10659,21 @@ var RuntypeClient2 = class {
9310
10659
  });
9311
10660
  return transformResponse(response);
9312
10661
  }
10662
+ /**
10663
+ * POST request with a raw binary body (e.g. application/zip bundle uploads).
10664
+ */
10665
+ async postBinary(path, body, contentType) {
10666
+ const url = this.buildUrl(path);
10667
+ const headers = { ...this.headers, "Content-Type": contentType };
10668
+ const response = await this.makeRequest(url, {
10669
+ method: "POST",
10670
+ headers,
10671
+ // TS 5.7 types Uint8Array over ArrayBufferLike, which no longer
10672
+ // overlaps DOM BodyInit; the runtime value is a valid fetch body.
10673
+ body
10674
+ });
10675
+ return transformResponse(response);
10676
+ }
9313
10677
  /**
9314
10678
  * Generic request that returns raw Response for streaming
9315
10679
  */
@@ -9410,7 +10774,7 @@ var RuntypeClient2 = class {
9410
10774
  } catch (error) {
9411
10775
  if (timeoutId) clearTimeout(timeoutId);
9412
10776
  if (timeoutId && error instanceof Error && error.name === "AbortError") {
9413
- throw new Error(`Request timeout after ${this.timeout}ms`);
10777
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
9414
10778
  }
9415
10779
  throw error;
9416
10780
  }
@@ -9419,8 +10783,18 @@ var RuntypeClient2 = class {
9419
10783
  * Make HTTP request that returns raw Response (for streaming)
9420
10784
  */
9421
10785
  async makeRawRequest(url, options) {
9422
- const controller = this.timeout === null ? null : new AbortController();
10786
+ const callerSignal = options.signal ?? null;
10787
+ const controller = this.timeout === null && !callerSignal ? null : new AbortController();
9423
10788
  const timeoutId = controller && this.timeout !== null ? setTimeout(() => controller.abort(), this.timeout) : null;
10789
+ if (callerSignal && controller) {
10790
+ if (callerSignal.aborted) {
10791
+ controller.abort(callerSignal.reason);
10792
+ } else {
10793
+ callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason), {
10794
+ once: true
10795
+ });
10796
+ }
10797
+ }
9424
10798
  try {
9425
10799
  const response = await fetch(url, {
9426
10800
  ...options,
@@ -9433,8 +10807,8 @@ var RuntypeClient2 = class {
9433
10807
  return response;
9434
10808
  } catch (error) {
9435
10809
  if (timeoutId) clearTimeout(timeoutId);
9436
- if (timeoutId && error instanceof Error && error.name === "AbortError") {
9437
- throw new Error(`Request timeout after ${this.timeout}ms`);
10810
+ if (timeoutId && error instanceof Error && error.name === "AbortError" && !callerSignal?.aborted) {
10811
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
9438
10812
  }
9439
10813
  throw error;
9440
10814
  }
@@ -9480,9 +10854,6 @@ function createClient(config) {
9480
10854
  return new RuntypeClient2(config);
9481
10855
  }
9482
10856
 
9483
- // src/index.ts
9484
- init_stream_utils();
9485
-
9486
10857
  // src/batch-builder.ts
9487
10858
  var BatchBuilder = class {
9488
10859
  constructor() {
@@ -9540,20 +10911,20 @@ var BatchBuilder = class {
9540
10911
  if (!this.recordType) {
9541
10912
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
9542
10913
  }
9543
- const request = {
10914
+ const request2 = {
9544
10915
  flowId: this.flowId,
9545
10916
  recordType: this.recordType
9546
10917
  };
9547
10918
  if (Object.keys(this.batchOptions).length > 0) {
9548
- request.options = this.batchOptions;
10919
+ request2.options = this.batchOptions;
9549
10920
  }
9550
10921
  if (this.filterConfig) {
9551
- request.filter = this.filterConfig;
10922
+ request2.filter = this.filterConfig;
9552
10923
  }
9553
10924
  if (this.limitConfig !== void 0) {
9554
- request.limit = this.limitConfig;
10925
+ request2.limit = this.limitConfig;
9555
10926
  }
9556
- return request;
10927
+ return request2;
9557
10928
  }
9558
10929
  /**
9559
10930
  * Execute the batch operation
@@ -9710,32 +11081,32 @@ var EvalBuilder = class {
9710
11081
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
9711
11082
  );
9712
11083
  }
9713
- const request = {};
11084
+ const request2 = {};
9714
11085
  if (this.flowId) {
9715
- request.flowId = this.flowId;
11086
+ request2.flowId = this.flowId;
9716
11087
  } else if (this.virtualFlow) {
9717
- request.flow = this.virtualFlow;
11088
+ request2.flow = this.virtualFlow;
9718
11089
  }
9719
11090
  if (this.recordType) {
9720
- request.recordType = this.recordType;
11091
+ request2.recordType = this.recordType;
9721
11092
  } else if (this.inlineRecords) {
9722
- request.records = this.inlineRecords;
11093
+ request2.records = this.inlineRecords;
9723
11094
  }
9724
11095
  if (this.modelOverrides) {
9725
- request.modelOverrides = this.modelOverrides;
11096
+ request2.modelOverrides = this.modelOverrides;
9726
11097
  } else if (this.modelConfigs) {
9727
- request.modelConfigs = this.modelConfigs;
11098
+ request2.modelConfigs = this.modelConfigs;
9728
11099
  }
9729
11100
  if (Object.keys(this.evalOptions).length > 0) {
9730
- request.options = this.evalOptions;
11101
+ request2.options = this.evalOptions;
9731
11102
  }
9732
11103
  if (this.filterConfig) {
9733
- request.filter = this.filterConfig;
11104
+ request2.filter = this.filterConfig;
9734
11105
  }
9735
11106
  if (this.limitConfig !== void 0) {
9736
- request.limit = this.limitConfig;
11107
+ request2.limit = this.limitConfig;
9737
11108
  }
9738
- return request;
11109
+ return request2;
9739
11110
  }
9740
11111
  /**
9741
11112
  * Execute the evaluation
@@ -10202,10 +11573,14 @@ var STEP_TYPE_TO_METHOD = {
10202
11573
  };
10203
11574
  // Annotate the CommonJS export names for ESM import in node:
10204
11575
  0 && (module.exports = {
11576
+ AgentDriftError,
11577
+ AgentEnsureConflictError,
10205
11578
  AgentVersionsEndpoint,
10206
11579
  AgentsEndpoint,
11580
+ AgentsNamespace,
10207
11581
  AnalyticsEndpoint,
10208
11582
  ApiKeysEndpoint,
11583
+ AppsEndpoint,
10209
11584
  BatchBuilder,
10210
11585
  BatchesNamespace,
10211
11586
  BillingEndpoint,
@@ -10216,18 +11591,23 @@ var STEP_TYPE_TO_METHOD = {
10216
11591
  ClientTokensEndpoint,
10217
11592
  ContextTemplatesEndpoint,
10218
11593
  ConversationsEndpoint,
11594
+ DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
11595
+ DEFAULT_STALL_STOP_AFTER,
10219
11596
  DispatchEndpoint,
10220
11597
  EvalBuilder,
10221
11598
  EvalEndpoint,
10222
11599
  EvalRunner,
10223
11600
  EvalsNamespace,
10224
11601
  FlowBuilder,
11602
+ FlowDriftError,
11603
+ FlowEnsureConflictError,
10225
11604
  FlowResult,
10226
11605
  FlowStepsEndpoint,
10227
11606
  FlowVersionsEndpoint,
10228
11607
  FlowsEndpoint,
10229
11608
  FlowsNamespace,
10230
11609
  IntegrationsEndpoint,
11610
+ LEDGER_ARTIFACT_LINE_PREFIX,
10231
11611
  LogsEndpoint,
10232
11612
  ModelConfigsEndpoint,
10233
11613
  PromptRunner,
@@ -10250,22 +11630,47 @@ var STEP_TYPE_TO_METHOD = {
10250
11630
  UsersEndpoint,
10251
11631
  applyGeneratedRuntimeToolProposalToDispatchRequest,
10252
11632
  attachRuntimeToolsToDispatchRequest,
11633
+ buildEmptySessionNudge,
10253
11634
  buildGeneratedRuntimeToolGateOutput,
11635
+ buildLedgerOffloadReference,
11636
+ buildPolicyGuidance,
11637
+ buildSendViewOffloadMarker,
11638
+ compileWorkflowConfig,
11639
+ computeAgentContentHash,
11640
+ computeFlowContentHash,
10254
11641
  createClient,
10255
11642
  createExternalTool,
10256
11643
  defaultWorkflow,
11644
+ defaultWorkflowConfig,
11645
+ defineAgent,
11646
+ defineFlow,
11647
+ definePlaybook,
10257
11648
  deployWorkflow,
11649
+ ensureDefaultWorkflowHooks,
10258
11650
  evaluateGeneratedRuntimeToolProposal,
11651
+ extractDeclaredToolResultChars,
10259
11652
  gameWorkflow,
10260
11653
  getDefaultPlanPath,
10261
11654
  getLikelySupportingCandidatePaths,
11655
+ interpolateWorkflowTemplate,
10262
11656
  isDiscoveryToolName,
10263
11657
  isMarathonArtifactPath,
10264
11658
  isPreservationSensitiveTask,
11659
+ isWorkflowHookRef,
11660
+ listWorkflowHooks,
11661
+ normalizeAgentDefinition,
10265
11662
  normalizeCandidatePath,
10266
11663
  parseFinalBuffer,
11664
+ parseLedgerArtifactRelativePath,
11665
+ parseOffloadedOutputId,
10267
11666
  parseSSEChunk,
10268
11667
  processStream,
11668
+ registerWorkflowHook,
11669
+ resolveStallStopAfter,
11670
+ resolveWorkflowHook,
10269
11671
  sanitizeTaskSlug,
10270
- streamEvents
11672
+ shouldInjectEmptySessionNudge,
11673
+ shouldRequestModelEscalation,
11674
+ streamEvents,
11675
+ unregisterWorkflowHook
10271
11676
  });