@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.mjs CHANGED
@@ -1,24 +1,4 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
-
11
1
  // src/stream-utils.ts
12
- var stream_utils_exports = {};
13
- __export(stream_utils_exports, {
14
- flowErrorMessage: () => flowErrorMessage,
15
- parseFinalBuffer: () => parseFinalBuffer,
16
- parseSSEChunk: () => parseSSEChunk,
17
- processStream: () => processStream,
18
- stepDeltaText: () => stepDeltaText,
19
- stepDisplayName: () => stepDisplayName,
20
- streamEvents: () => streamEvents
21
- });
22
2
  function parseSSEChunk(chunk, buffer) {
23
3
  buffer += chunk;
24
4
  const lines = buffer.split("\n");
@@ -268,14 +248,8 @@ async function* streamEvents(response) {
268
248
  reader.releaseLock();
269
249
  }
270
250
  }
271
- var init_stream_utils = __esm({
272
- "src/stream-utils.ts"() {
273
- "use strict";
274
- }
275
- });
276
251
 
277
252
  // src/flow-result.ts
278
- init_stream_utils();
279
253
  var FlowResult = class {
280
254
  constructor(response, summary) {
281
255
  this.consumed = false;
@@ -411,7 +385,6 @@ var FlowResult = class {
411
385
  };
412
386
 
413
387
  // src/flow-builder.ts
414
- init_stream_utils();
415
388
  async function validateInlineFlow(client, args, savedFlowHint) {
416
389
  if (args.existingFlowId) {
417
390
  throw new Error(
@@ -1121,20 +1094,20 @@ var FlowBuilder = class {
1121
1094
  */
1122
1095
  build() {
1123
1096
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
1124
- const request = { flow };
1097
+ const request2 = { flow };
1125
1098
  if (this.recordConfig) {
1126
- request.record = this.recordConfig;
1099
+ request2.record = this.recordConfig;
1127
1100
  }
1128
1101
  if (this.messagesConfig) {
1129
- request.messages = this.messagesConfig;
1102
+ request2.messages = this.messagesConfig;
1130
1103
  }
1131
1104
  if (this.inputsConfig) {
1132
- request.inputs = this.inputsConfig;
1105
+ request2.inputs = this.inputsConfig;
1133
1106
  }
1134
1107
  if (Object.keys(this.optionsConfig).length > 0) {
1135
- request.options = this.optionsConfig;
1108
+ request2.options = this.optionsConfig;
1136
1109
  }
1137
- return request;
1110
+ return request2;
1138
1111
  }
1139
1112
  /**
1140
1113
  * Validate this prospective flow against the public validation endpoint
@@ -1371,22 +1344,22 @@ function resolveBatchExecutionId(pausedTools) {
1371
1344
  return "";
1372
1345
  }
1373
1346
 
1374
- // src/flows-namespace.ts
1375
- function isRecord(value) {
1347
+ // src/flows-ensure.ts
1348
+ function isPlainObject(value) {
1376
1349
  return value !== null && typeof value === "object" && !Array.isArray(value);
1377
1350
  }
1378
- function normalizeConfig(config) {
1379
- if (!isRecord(config)) return {};
1351
+ function normalizeConfigForHash(config) {
1352
+ if (!isPlainObject(config)) return {};
1380
1353
  const normalized = {};
1381
1354
  for (const key of Object.keys(config).sort()) {
1382
1355
  const value = config[key];
1383
1356
  if (value === void 0) continue;
1384
1357
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
1385
- normalized[key] = normalizeConfig(value);
1358
+ normalized[key] = normalizeConfigForHash(value);
1386
1359
  } else if (Array.isArray(value)) {
1387
1360
  normalized[key] = value.map((item) => {
1388
1361
  if (item !== null && typeof item === "object" && !Array.isArray(item)) {
1389
- return normalizeConfig(item);
1362
+ return normalizeConfigForHash(item);
1390
1363
  }
1391
1364
  return item;
1392
1365
  });
@@ -1397,28 +1370,249 @@ function normalizeConfig(config) {
1397
1370
  return normalized;
1398
1371
  }
1399
1372
  function normalizeStepForHash(step) {
1400
- const stepObj = isRecord(step) ? step : {};
1373
+ const stepObj = isPlainObject(step) ? step : {};
1401
1374
  return {
1402
1375
  type: typeof stepObj.type === "string" ? stepObj.type : "",
1403
1376
  name: typeof stepObj.name === "string" ? stepObj.name : "",
1404
1377
  enabled: stepObj.enabled !== false,
1405
1378
  ...typeof stepObj.when === "string" ? { when: stepObj.when } : {},
1406
- config: normalizeConfig(stepObj.config),
1379
+ config: normalizeConfigForHash(stepObj.config),
1407
1380
  order: typeof stepObj.order === "number" ? stepObj.order : 0
1408
1381
  };
1409
1382
  }
1410
1383
  async function computeFlowContentHash(steps) {
1411
1384
  const normalized = [...steps].sort((a, b) => {
1412
- const orderA = isRecord(a) && typeof a.order === "number" ? a.order : 0;
1413
- const orderB = isRecord(b) && typeof b.order === "number" ? b.order : 0;
1385
+ const orderA = isPlainObject(a) && typeof a.order === "number" ? a.order : 0;
1386
+ const orderB = isPlainObject(b) && typeof b.order === "number" ? b.order : 0;
1414
1387
  return orderA - orderB;
1415
1388
  }).map(normalizeStepForHash);
1416
1389
  const serialized = JSON.stringify(normalized);
1417
1390
  const encoded = new TextEncoder().encode(serialized);
1418
1391
  const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
1419
- const hashArray = new Uint8Array(hashBuffer);
1420
- return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
1392
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
1393
+ }
1394
+ var DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "steps"]);
1395
+ var DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
1396
+ "type",
1397
+ "name",
1398
+ "order",
1399
+ "enabled",
1400
+ "when",
1401
+ "config"
1402
+ ]);
1403
+ function collectStepNonPortableToolRefs(config, path) {
1404
+ const found = [];
1405
+ const tools = config.tools;
1406
+ const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
1407
+ const scanArray = (value, subPath) => {
1408
+ if (!Array.isArray(value)) return;
1409
+ value.forEach((ref, i) => {
1410
+ if (isAccountScoped(ref)) found.push(`${subPath}[${i}]`);
1411
+ });
1412
+ };
1413
+ const scanKeys = (value, subPath) => {
1414
+ if (!isPlainObject(value)) return;
1415
+ for (const key of Object.keys(value)) {
1416
+ if (isAccountScoped(key)) found.push(`${subPath}.${key}`);
1417
+ }
1418
+ };
1419
+ if (isPlainObject(tools)) {
1420
+ scanArray(tools.toolIds, `${path}.tools.toolIds`);
1421
+ scanKeys(tools.toolConfigs, `${path}.tools.toolConfigs`);
1422
+ scanKeys(tools.perToolLimits, `${path}.tools.perToolLimits`);
1423
+ if (isPlainObject(tools.approval)) {
1424
+ scanArray(tools.approval.require, `${path}.tools.approval.require`);
1425
+ }
1426
+ if (isPlainObject(tools.subagentConfig)) {
1427
+ scanArray(tools.subagentConfig.toolPool, `${path}.tools.subagentConfig.toolPool`);
1428
+ }
1429
+ if (isPlainObject(tools.codeModeConfig)) {
1430
+ scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
1431
+ }
1432
+ }
1433
+ for (const branch of ["trueSteps", "falseSteps"]) {
1434
+ const nested = config[branch];
1435
+ if (!Array.isArray(nested)) continue;
1436
+ nested.forEach((nestedStep, i) => {
1437
+ if (isPlainObject(nestedStep) && isPlainObject(nestedStep.config)) {
1438
+ found.push(
1439
+ ...collectStepNonPortableToolRefs(
1440
+ nestedStep.config,
1441
+ `${path}.${branch}[${i}].config`
1442
+ )
1443
+ );
1444
+ }
1445
+ });
1446
+ }
1447
+ return found;
1448
+ }
1449
+ function defineFlow(input) {
1450
+ if (!input || typeof input !== "object") {
1451
+ throw new Error("defineFlow requires a definition object");
1452
+ }
1453
+ if (typeof input.name !== "string" || input.name.length === 0) {
1454
+ throw new Error('defineFlow requires a non-empty string "name"');
1455
+ }
1456
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_FLOW_TOP_LEVEL_KEYS.has(key));
1457
+ if (unknownKeys.length > 0) {
1458
+ throw new Error(
1459
+ `defineFlow: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name and steps. (Description is not part of the v1 ensure surface.)`
1460
+ );
1461
+ }
1462
+ if (!Array.isArray(input.steps) || input.steps.length === 0) {
1463
+ throw new Error('defineFlow requires a non-empty "steps" array');
1464
+ }
1465
+ const steps = input.steps.map((step, index) => {
1466
+ if (!isPlainObject(step)) {
1467
+ throw new Error(`defineFlow: steps[${index}] must be an object`);
1468
+ }
1469
+ if (typeof step.type !== "string" || step.type.length === 0) {
1470
+ throw new Error(`defineFlow: steps[${index}] requires a non-empty string "type"`);
1471
+ }
1472
+ if (typeof step.name !== "string" || step.name.length === 0) {
1473
+ throw new Error(`defineFlow: steps[${index}] requires a non-empty string "name"`);
1474
+ }
1475
+ const unknownStepKeys = Object.keys(step).filter((key) => !DEFINE_FLOW_STEP_KEYS.has(key));
1476
+ if (unknownStepKeys.length > 0) {
1477
+ throw new Error(
1478
+ `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.)`
1479
+ );
1480
+ }
1481
+ const config = isPlainObject(step.config) ? step.config : void 0;
1482
+ if (config) {
1483
+ const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
1484
+ if (nonPortable.length > 0) {
1485
+ throw new Error(
1486
+ `defineFlow: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references instead. Name-based resolution of saved tools is a planned follow-up.`
1487
+ );
1488
+ }
1489
+ }
1490
+ return {
1491
+ type: step.type,
1492
+ name: step.name,
1493
+ // Explicit 1-based order (the flow builder's convention) so the local
1494
+ // probe hash agrees with the server's persisted step order.
1495
+ order: typeof step.order === "number" ? step.order : index + 1,
1496
+ ...step.enabled !== void 0 ? { enabled: step.enabled } : {},
1497
+ ...typeof step.when === "string" ? { when: step.when } : {},
1498
+ ...config ? { config } : {}
1499
+ };
1500
+ });
1501
+ return { name: input.name, steps };
1502
+ }
1503
+ var FlowEnsureConflictError = class extends Error {
1504
+ constructor(body) {
1505
+ super(body.error ?? `Flow ensure conflict: ${body.code}`);
1506
+ this.name = "FlowEnsureConflictError";
1507
+ this.code = body.code;
1508
+ this.lastModifiedSource = body.lastModifiedSource;
1509
+ this.modifiedAt = body.modifiedAt;
1510
+ this.currentHash = body.currentHash;
1511
+ }
1512
+ };
1513
+ var FlowDriftError = class extends Error {
1514
+ constructor(plan) {
1515
+ super(
1516
+ `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.`
1517
+ );
1518
+ this.name = "FlowDriftError";
1519
+ this.plan = plan;
1520
+ }
1521
+ };
1522
+ function parseRequestError(err) {
1523
+ if (!(err instanceof Error)) return { status: null, body: null };
1524
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
1525
+ if (!match) return { status: null, body: null };
1526
+ try {
1527
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
1528
+ } catch {
1529
+ return { status: Number(match[1]), body: null };
1530
+ }
1531
+ }
1532
+ function toConflictError(err) {
1533
+ const { status, body } = parseRequestError(err);
1534
+ if (status !== 409 || !isPlainObject(body)) return null;
1535
+ const code = body.code;
1536
+ if (code !== "external_modification" && code !== "remote_changed") return null;
1537
+ return new FlowEnsureConflictError(
1538
+ body
1539
+ );
1540
+ }
1541
+ var serverHashMemo = /* @__PURE__ */ new WeakMap();
1542
+ function memoFor(client) {
1543
+ let memo = serverHashMemo.get(client);
1544
+ if (!memo) {
1545
+ memo = /* @__PURE__ */ new Map();
1546
+ serverHashMemo.set(client, memo);
1547
+ }
1548
+ return memo;
1421
1549
  }
1550
+ function memoize(memo, memoKey, result) {
1551
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
1552
+ }
1553
+ async function request(client, body) {
1554
+ try {
1555
+ return await client.post(
1556
+ "/flows/ensure",
1557
+ body
1558
+ );
1559
+ } catch (err) {
1560
+ const conflict = toConflictError(err);
1561
+ if (conflict) throw conflict;
1562
+ throw err;
1563
+ }
1564
+ }
1565
+ async function ensureFlow(client, definition, options = {}) {
1566
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
1567
+ const passthrough = {
1568
+ ...onConflict ? { onConflict } : {},
1569
+ ...release ? { release } : {},
1570
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
1571
+ };
1572
+ if (dryRun || expectNoChanges) {
1573
+ const plan = await request(client, {
1574
+ name: definition.name,
1575
+ definition,
1576
+ dryRun: true,
1577
+ ...passthrough
1578
+ });
1579
+ if (plan.result !== "plan") {
1580
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
1581
+ }
1582
+ if (expectNoChanges && plan.changes !== "none") {
1583
+ throw new FlowDriftError(plan);
1584
+ }
1585
+ return plan;
1586
+ }
1587
+ const memo = memoFor(client);
1588
+ const localHash = await computeFlowContentHash(definition.steps);
1589
+ const memoKey = `${definition.name} ${localHash}`;
1590
+ const contentHash = memo.get(memoKey) ?? localHash;
1591
+ const probe = await request(client, {
1592
+ name: definition.name,
1593
+ contentHash,
1594
+ ...passthrough
1595
+ });
1596
+ if (probe.result !== "definitionRequired") {
1597
+ memoize(memo, memoKey, probe);
1598
+ return probe;
1599
+ }
1600
+ const converged = await request(client, {
1601
+ name: definition.name,
1602
+ definition,
1603
+ ...passthrough
1604
+ });
1605
+ if (converged.result === "definitionRequired") {
1606
+ throw new Error("Server reported definitionRequired for a full-definition request");
1607
+ }
1608
+ memoize(memo, memoKey, converged);
1609
+ return converged;
1610
+ }
1611
+ async function pullFlow(client, name) {
1612
+ return client.get("/flows/pull", { name });
1613
+ }
1614
+
1615
+ // src/flows-namespace.ts
1422
1616
  var FlowsNamespace = class {
1423
1617
  constructor(getClient) {
1424
1618
  this.getClient = getClient;
@@ -1426,8 +1620,11 @@ var FlowsNamespace = class {
1426
1620
  /**
1427
1621
  * Create or update a flow by name (upsert mode)
1428
1622
  *
1429
- * The recommended pattern for code-first flow management.
1430
- * Creates the flow if it doesn't exist, updates if steps changed.
1623
+ * The recommended pattern for code-first flow management when you want to
1624
+ * save AND run in one dispatch. For a deploy-time, non-executing converge
1625
+ * (CI/CD config-as-code), use {@link ensure} instead — upsert and ensure
1626
+ * are siblings, not versions of each other: upsert is the runtime verb
1627
+ * (save-and-run), ensure is the deploy verb (converge only).
1431
1628
  *
1432
1629
  * @example
1433
1630
  * ```typescript
@@ -1442,6 +1639,33 @@ var FlowsNamespace = class {
1442
1639
  upsert(config) {
1443
1640
  return new RuntypeFlowBuilder(this.getClient, "upsert", config);
1444
1641
  }
1642
+ /**
1643
+ * Idempotently converge a `defineFlow` definition onto the platform —
1644
+ * the deploy-time, non-executing sibling of {@link upsert}. Hash-first:
1645
+ * the steady state is one tiny probe request. Creates an immutable version
1646
+ * snapshot on every change; never deletes; never executes the flow.
1647
+ *
1648
+ * @example
1649
+ * ```typescript
1650
+ * const def = defineFlow({ name: 'Onboarding Digest', steps: [...] })
1651
+ *
1652
+ * // Converge (CI/deploy).
1653
+ * const result = await Runtype.flows.ensure(def)
1654
+ *
1655
+ * // PR drift gate.
1656
+ * await Runtype.flows.ensure(def, { expectNoChanges: true })
1657
+ * ```
1658
+ */
1659
+ async ensure(definition, options = {}) {
1660
+ return ensureFlow(this.getClient(), definition, options);
1661
+ }
1662
+ /**
1663
+ * Pull the canonical definition + provenance for a flow by name — the
1664
+ * absorb-drift direction of the ensure protocol.
1665
+ */
1666
+ async pull(name) {
1667
+ return pullFlow(this.getClient(), name);
1668
+ }
1445
1669
  /**
1446
1670
  * Create a virtual flow (one-off, not saved)
1447
1671
  *
@@ -2127,9 +2351,8 @@ var RuntypeFlowBuilder = class {
2127
2351
  onFlowComplete: (event) => callbacks?.onFlowComplete?.(event),
2128
2352
  onError: (error) => callbacks?.onError?.(error)
2129
2353
  };
2130
- const { streamEvents: streamEvents2, stepDeltaText: stepDeltaText2, stepDisplayName: stepDisplayName2, flowErrorMessage: flowErrorMessage2 } = await Promise.resolve().then(() => (init_stream_utils(), stream_utils_exports));
2131
2354
  try {
2132
- for await (const event of streamEvents2(response)) {
2355
+ for await (const event of streamEvents(response)) {
2133
2356
  collectLocalToolAwait(pausedTools, event);
2134
2357
  switch (event.type) {
2135
2358
  case "flow_start":
@@ -2139,10 +2362,10 @@ var RuntypeFlowBuilder = class {
2139
2362
  wrappedCallbacks.onStepStart?.(event);
2140
2363
  break;
2141
2364
  case "step_delta":
2142
- wrappedCallbacks.onStepDelta?.(stepDeltaText2(event), event);
2365
+ wrappedCallbacks.onStepDelta?.(stepDeltaText(event), event);
2143
2366
  break;
2144
2367
  case "step_complete": {
2145
- accumulatedSummary.results?.set(stepDisplayName2(event), event.result);
2368
+ accumulatedSummary.results?.set(stepDisplayName(event), event.result);
2146
2369
  wrappedCallbacks.onStepComplete?.(event.result, event);
2147
2370
  break;
2148
2371
  }
@@ -2150,7 +2373,7 @@ var RuntypeFlowBuilder = class {
2150
2373
  wrappedCallbacks.onFlowComplete?.(event);
2151
2374
  break;
2152
2375
  case "flow_error":
2153
- wrappedCallbacks.onError?.(new Error(flowErrorMessage2(event)));
2376
+ wrappedCallbacks.onError?.(new Error(flowErrorMessage(event)));
2154
2377
  break;
2155
2378
  }
2156
2379
  }
@@ -2225,7 +2448,8 @@ var RuntypeFlowBuilder = class {
2225
2448
  return [toolName, await localTools[toolName](parameters)];
2226
2449
  } catch (error) {
2227
2450
  throw new Error(
2228
- `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`
2451
+ `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`,
2452
+ { cause: error }
2229
2453
  );
2230
2454
  }
2231
2455
  })
@@ -2258,15 +2482,15 @@ var RuntypeFlowBuilder = class {
2258
2482
  build() {
2259
2483
  const flowMode = this.mode === "existing" ? "existing" : this.mode;
2260
2484
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
2261
- const request = { flow };
2485
+ const request2 = { flow };
2262
2486
  if (this.recordConfig) {
2263
- request.record = this.recordConfig;
2487
+ request2.record = this.recordConfig;
2264
2488
  }
2265
2489
  if (this.messagesConfig) {
2266
- request.messages = this.messagesConfig;
2490
+ request2.messages = this.messagesConfig;
2267
2491
  }
2268
2492
  if (this.inputsConfig) {
2269
- request.inputs = this.inputsConfig;
2493
+ request2.inputs = this.inputsConfig;
2270
2494
  }
2271
2495
  const options = {
2272
2496
  flowMode,
@@ -2284,8 +2508,8 @@ var RuntypeFlowBuilder = class {
2284
2508
  if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
2285
2509
  options.upsertOptions = this.upsertOptions;
2286
2510
  }
2287
- request.options = options;
2288
- return request;
2511
+ request2.options = options;
2512
+ return request2;
2289
2513
  }
2290
2514
  /**
2291
2515
  * Validate this prospective flow against the public validation endpoint
@@ -2945,6 +3169,8 @@ var SkillsNamespace = class {
2945
3169
  }
2946
3170
  /**
2947
3171
  * List skills for the authenticated owner, optionally filtered by status.
3172
+ * Returns just the rows (one page); pass `cursor`/`limit` to page, or use
3173
+ * {@link listPage} when you need the pagination envelope.
2948
3174
  *
2949
3175
  * @example
2950
3176
  * ```typescript
@@ -2952,10 +3178,23 @@ var SkillsNamespace = class {
2952
3178
  * ```
2953
3179
  */
2954
3180
  async list(params) {
2955
- const client = this.getClient();
2956
- const res = await client.get("/skills", params);
3181
+ const res = await this.listPage(params);
2957
3182
  return res.data;
2958
3183
  }
3184
+ /**
3185
+ * List skills with the cursor-pagination envelope (mirrors the tools list
3186
+ * shape: `{ data, pagination }`).
3187
+ *
3188
+ * @example
3189
+ * ```typescript
3190
+ * const page1 = await Runtype.skills.listPage({ limit: 50, includeCount: true })
3191
+ * const page2 = await Runtype.skills.listPage({ limit: 50, cursor: page1.pagination?.nextCursor ?? undefined })
3192
+ * ```
3193
+ */
3194
+ async listPage(params) {
3195
+ const client = this.getClient();
3196
+ return client.get("/skills", params);
3197
+ }
2959
3198
  /**
2960
3199
  * Get a skill and its full version history.
2961
3200
  *
@@ -3048,6 +3287,260 @@ var SkillsNamespace = class {
3048
3287
  }
3049
3288
  };
3050
3289
 
3290
+ // src/agents-namespace.ts
3291
+ var AGENT_CONFIG_KEYS = [
3292
+ "model",
3293
+ "systemPrompt",
3294
+ "temperature",
3295
+ "topP",
3296
+ "topK",
3297
+ "frequencyPenalty",
3298
+ "presencePenalty",
3299
+ "seed",
3300
+ "tools",
3301
+ "reasoning",
3302
+ "advisor",
3303
+ "loopConfig",
3304
+ "voice",
3305
+ "errorHandling",
3306
+ "artifacts",
3307
+ "loggingPolicy",
3308
+ "temporal",
3309
+ "memory"
3310
+ ];
3311
+ var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
3312
+ function isPlainObject2(value) {
3313
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3314
+ }
3315
+ function normalizeValue(value) {
3316
+ if (Array.isArray(value)) {
3317
+ return value.map((item) => normalizeValue(item));
3318
+ }
3319
+ if (isPlainObject2(value)) {
3320
+ const normalized = {};
3321
+ for (const key of Object.keys(value).sort()) {
3322
+ const entry = value[key];
3323
+ if (entry === void 0 || entry === null) continue;
3324
+ normalized[key] = normalizeValue(entry);
3325
+ }
3326
+ return normalized;
3327
+ }
3328
+ return value;
3329
+ }
3330
+ function normalizeAgentDefinition(definition) {
3331
+ const config = {};
3332
+ const rawConfig = isPlainObject2(definition.config) ? definition.config : {};
3333
+ for (const key of AGENT_CONFIG_KEY_LIST) {
3334
+ const value = rawConfig[key];
3335
+ if (value === void 0 || value === null) continue;
3336
+ config[key] = normalizeValue(value);
3337
+ }
3338
+ return {
3339
+ name: definition.name,
3340
+ ...definition.description ? { description: definition.description } : {},
3341
+ ...definition.icon ? { icon: definition.icon } : {},
3342
+ config
3343
+ };
3344
+ }
3345
+ async function computeAgentContentHash(definition) {
3346
+ const serialized = JSON.stringify(normalizeAgentDefinition(definition));
3347
+ const encoded = new TextEncoder().encode(serialized);
3348
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3349
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3350
+ }
3351
+ var DEFINE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "description", "icon", ...AGENT_CONFIG_KEYS]);
3352
+ function collectNonPortableToolRefs(config) {
3353
+ const tools = config.tools;
3354
+ if (!isPlainObject2(tools)) return [];
3355
+ const found = [];
3356
+ const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
3357
+ const scanArray = (value, path) => {
3358
+ if (!Array.isArray(value)) return;
3359
+ value.forEach((ref, i) => {
3360
+ if (isAccountScoped(ref)) found.push(`${path}[${i}]`);
3361
+ });
3362
+ };
3363
+ const scanKeys = (value, path) => {
3364
+ if (!isPlainObject2(value)) return;
3365
+ for (const key of Object.keys(value)) {
3366
+ if (isAccountScoped(key)) found.push(`${path}.${key}`);
3367
+ }
3368
+ };
3369
+ scanArray(tools.toolIds, "tools.toolIds");
3370
+ scanKeys(tools.toolConfigs, "tools.toolConfigs");
3371
+ scanKeys(tools.perToolLimits, "tools.perToolLimits");
3372
+ if (isPlainObject2(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3373
+ if (isPlainObject2(tools.subagentConfig)) {
3374
+ scanArray(tools.subagentConfig.toolPool, "tools.subagentConfig.toolPool");
3375
+ }
3376
+ if (isPlainObject2(tools.codeModeConfig)) {
3377
+ scanArray(tools.codeModeConfig.toolPool, "tools.codeModeConfig.toolPool");
3378
+ }
3379
+ return found;
3380
+ }
3381
+ function defineAgent(input) {
3382
+ if (!input || typeof input !== "object") {
3383
+ throw new Error("defineAgent requires a definition object");
3384
+ }
3385
+ if (typeof input.name !== "string" || input.name.length === 0) {
3386
+ throw new Error('defineAgent requires a non-empty string "name"');
3387
+ }
3388
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOP_LEVEL_KEYS.has(key));
3389
+ if (unknownKeys.length > 0) {
3390
+ throw new Error(
3391
+ `defineAgent: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, icon, and the agent runtime config surface (${AGENT_CONFIG_KEY_LIST.join(", ")}).`
3392
+ );
3393
+ }
3394
+ const config = {};
3395
+ for (const key of AGENT_CONFIG_KEYS) {
3396
+ const value = input[key];
3397
+ if (value !== void 0) config[key] = value;
3398
+ }
3399
+ const nonPortable = collectNonPortableToolRefs(config);
3400
+ if (nonPortable.length > 0) {
3401
+ throw new Error(
3402
+ `defineAgent: account-scoped tool reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references instead. Name-based resolution of saved tools is a planned follow-up.`
3403
+ );
3404
+ }
3405
+ return {
3406
+ name: input.name,
3407
+ ...input.description !== void 0 ? { description: input.description } : {},
3408
+ ...input.icon !== void 0 ? { icon: input.icon } : {},
3409
+ config
3410
+ };
3411
+ }
3412
+ var AgentEnsureConflictError = class extends Error {
3413
+ constructor(body) {
3414
+ super(body.error ?? `Agent ensure conflict: ${body.code}`);
3415
+ this.name = "AgentEnsureConflictError";
3416
+ this.code = body.code;
3417
+ this.lastModifiedSource = body.lastModifiedSource;
3418
+ this.modifiedAt = body.modifiedAt;
3419
+ this.currentHash = body.currentHash;
3420
+ }
3421
+ };
3422
+ var AgentDriftError = class extends Error {
3423
+ constructor(plan) {
3424
+ super(
3425
+ `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.`
3426
+ );
3427
+ this.name = "AgentDriftError";
3428
+ this.plan = plan;
3429
+ }
3430
+ };
3431
+ function parseRequestError2(err) {
3432
+ if (!(err instanceof Error)) return { status: null, body: null };
3433
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3434
+ if (!match) return { status: null, body: null };
3435
+ try {
3436
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3437
+ } catch {
3438
+ return { status: Number(match[1]), body: null };
3439
+ }
3440
+ }
3441
+ function toConflictError2(err) {
3442
+ const { status, body } = parseRequestError2(err);
3443
+ if (status !== 409 || !isPlainObject2(body)) return null;
3444
+ const code = body.code;
3445
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3446
+ return new AgentEnsureConflictError(
3447
+ body
3448
+ );
3449
+ }
3450
+ var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3451
+ function memoFor2(client) {
3452
+ let memo = serverHashMemo2.get(client);
3453
+ if (!memo) {
3454
+ memo = /* @__PURE__ */ new Map();
3455
+ serverHashMemo2.set(client, memo);
3456
+ }
3457
+ return memo;
3458
+ }
3459
+ var AgentsNamespace = class {
3460
+ constructor(getClient) {
3461
+ this.getClient = getClient;
3462
+ }
3463
+ /**
3464
+ * Idempotently converge a definition onto the platform. Hash-first: probes
3465
+ * with a content hash, and only ships the full definition when the server
3466
+ * reports a miss (`definitionRequired`). Creates an immutable version
3467
+ * snapshot on every change; never deletes.
3468
+ */
3469
+ async ensure(definition, options = {}) {
3470
+ const client = this.getClient();
3471
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3472
+ const passthrough = {
3473
+ ...onConflict ? { onConflict } : {},
3474
+ ...release ? { release } : {},
3475
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3476
+ };
3477
+ if (dryRun || expectNoChanges) {
3478
+ const plan = await this.request(client, {
3479
+ name: definition.name,
3480
+ definition,
3481
+ dryRun: true,
3482
+ ...passthrough
3483
+ });
3484
+ if (plan.result !== "plan") {
3485
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3486
+ }
3487
+ if (expectNoChanges && plan.changes !== "none") {
3488
+ throw new AgentDriftError(plan);
3489
+ }
3490
+ return plan;
3491
+ }
3492
+ const memo = memoFor2(client);
3493
+ const localHash = await computeAgentContentHash({
3494
+ ...definition,
3495
+ config: definition.config
3496
+ });
3497
+ const memoKey = `${definition.name}\0${localHash}`;
3498
+ const contentHash = memo.get(memoKey) ?? localHash;
3499
+ const probe = await this.request(client, {
3500
+ name: definition.name,
3501
+ contentHash,
3502
+ ...passthrough
3503
+ });
3504
+ if (probe.result !== "definitionRequired") {
3505
+ this.memoize(memo, memoKey, probe);
3506
+ return probe;
3507
+ }
3508
+ const converged = await this.request(client, {
3509
+ name: definition.name,
3510
+ definition,
3511
+ ...passthrough
3512
+ });
3513
+ if (converged.result === "definitionRequired") {
3514
+ throw new Error("Server reported definitionRequired for a full-definition request");
3515
+ }
3516
+ this.memoize(memo, memoKey, converged);
3517
+ return converged;
3518
+ }
3519
+ /**
3520
+ * Pull the canonical definition + provenance for an agent by name — the
3521
+ * absorb-drift direction. The contentHash reflects the live agent state.
3522
+ */
3523
+ async pull(name) {
3524
+ const client = this.getClient();
3525
+ return client.get("/agents/pull", { name });
3526
+ }
3527
+ memoize(memo, memoKey, result) {
3528
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3529
+ }
3530
+ async request(client, body) {
3531
+ try {
3532
+ return await client.post(
3533
+ "/agents/ensure",
3534
+ body
3535
+ );
3536
+ } catch (err) {
3537
+ const conflict = toConflictError2(err);
3538
+ if (conflict) throw conflict;
3539
+ throw err;
3540
+ }
3541
+ }
3542
+ };
3543
+
3051
3544
  // src/transform.ts
3052
3545
  function transformResponse(data) {
3053
3546
  return data;
@@ -3208,7 +3701,7 @@ var RuntypeClient = class {
3208
3701
  } catch (error) {
3209
3702
  clearTimeout(timeoutId);
3210
3703
  if (error instanceof Error && error.name === "AbortError") {
3211
- throw new Error(`Request timeout after ${this.timeout}ms`);
3704
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
3212
3705
  }
3213
3706
  throw error;
3214
3707
  }
@@ -3235,7 +3728,7 @@ var RuntypeClient = class {
3235
3728
  } catch (error) {
3236
3729
  clearTimeout(timeoutId);
3237
3730
  if (error instanceof Error && error.name === "AbortError") {
3238
- throw new Error(`Request timeout after ${this.timeout}ms`);
3731
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
3239
3732
  }
3240
3733
  throw error;
3241
3734
  }
@@ -3410,6 +3903,32 @@ var Runtype = class {
3410
3903
  static get skills() {
3411
3904
  return new SkillsNamespace(() => this.getClient());
3412
3905
  }
3906
+ /**
3907
+ * Agents namespace - Agent config-as-code (define / ensure / pull)
3908
+ *
3909
+ * @example
3910
+ * ```typescript
3911
+ * import { defineAgent, Runtype } from '@runtypelabs/sdk'
3912
+ *
3913
+ * const assistant = defineAgent({
3914
+ * name: 'Pricing Assistant',
3915
+ * model: 'claude-sonnet-4-6',
3916
+ * systemPrompt: renderPrompt(pricingData),
3917
+ * })
3918
+ *
3919
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
3920
+ * await Runtype.agents.ensure(assistant)
3921
+ *
3922
+ * // CI drift gate
3923
+ * await Runtype.agents.ensure(assistant, { expectNoChanges: true })
3924
+ *
3925
+ * // Absorb a dashboard edit back into the repo
3926
+ * const { definition } = await Runtype.agents.pull('Pricing Assistant')
3927
+ * ```
3928
+ */
3929
+ static get agents() {
3930
+ return new AgentsNamespace(() => this.getClient());
3931
+ }
3413
3932
  };
3414
3933
 
3415
3934
  // src/generated-tool-gate.ts
@@ -3632,8 +4151,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
3632
4151
  ...decision.tool ? { tool: decision.tool } : {}
3633
4152
  };
3634
4153
  }
3635
- function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}) {
3636
- const stepList = request.flow.steps;
4154
+ function attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options = {}) {
4155
+ const stepList = request2.flow.steps;
3637
4156
  if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
3638
4157
  throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
3639
4158
  }
@@ -3676,9 +4195,9 @@ function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}
3676
4195
  }
3677
4196
  };
3678
4197
  return {
3679
- ...request,
4198
+ ...request2,
3680
4199
  flow: {
3681
- ...request.flow,
4200
+ ...request2.flow,
3682
4201
  // `clonedSteps` is a structural clone of `request.flow.steps` (already
3683
4202
  // `FlowStepDefinition[]`); only the prompt step's `config.tools` was
3684
4203
  // merged, so every step's `type` discriminant is preserved. The clone is
@@ -3688,18 +4207,56 @@ function attachRuntimeToolsToDispatchRequest(request, runtimeTools, options = {}
3688
4207
  }
3689
4208
  };
3690
4209
  }
3691
- function applyGeneratedRuntimeToolProposalToDispatchRequest(request, proposal, options = {}) {
4210
+ function applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options = {}) {
3692
4211
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
3693
4212
  if (!decision.approved || !decision.tool) {
3694
- return { decision, request };
4213
+ return { decision, request: request2 };
3695
4214
  }
3696
- const nextRequest = attachRuntimeToolsToDispatchRequest(request, [decision.tool], options.attach);
4215
+ const nextRequest = attachRuntimeToolsToDispatchRequest(request2, [decision.tool], options.attach);
3697
4216
  return {
3698
4217
  decision,
3699
4218
  request: nextRequest
3700
4219
  };
3701
4220
  }
3702
4221
 
4222
+ // src/offload-markers.ts
4223
+ var LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
4224
+ function formatChars(charLength) {
4225
+ return charLength.toLocaleString("en-US");
4226
+ }
4227
+ function buildSendViewOffloadMarker(details) {
4228
+ return `[${details.toolName} output (${formatChars(details.charLength)} chars) saved to ${details.filePath} \u2014 use read_file to retrieve if needed]`;
4229
+ }
4230
+ function buildLedgerOffloadReference(details) {
4231
+ return [
4232
+ `[Output offloaded as ${details.outputId} \u2014 ${formatChars(details.charLength)} chars stored in the marathon context ledger]`,
4233
+ `${LEDGER_ARTIFACT_LINE_PREFIX}${details.relativePath}`,
4234
+ `Preview: ${details.preview}${details.truncated ? "..." : ""}`,
4235
+ "",
4236
+ `Use read_offloaded_output with id "${details.outputId}" to retrieve the full output if needed.`
4237
+ ].join("\n");
4238
+ }
4239
+ var DECLARED_CHARS_PATTERNS = [
4240
+ /—\s*([\d,]+)\s+chars?\s+(?:stored|saved)/i,
4241
+ /\(([\d,]+)\s+chars?\)\s+saved/i
4242
+ ];
4243
+ function extractDeclaredToolResultChars(value) {
4244
+ if (typeof value !== "string") return void 0;
4245
+ for (const pattern of DECLARED_CHARS_PATTERNS) {
4246
+ const match = pattern.exec(value);
4247
+ if (!match?.[1]) continue;
4248
+ const parsed = Number.parseInt(match[1].replace(/,/g, ""), 10);
4249
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
4250
+ }
4251
+ return void 0;
4252
+ }
4253
+ function parseOffloadedOutputId(value) {
4254
+ 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;
4255
+ }
4256
+ function parseLedgerArtifactRelativePath(value) {
4257
+ return value.split("\n").find((line) => line.startsWith(LEDGER_ARTIFACT_LINE_PREFIX))?.slice(LEDGER_ARTIFACT_LINE_PREFIX.length).trim();
4258
+ }
4259
+
3703
4260
  // src/workflow-utils.ts
3704
4261
  function normalizeCandidatePath(candidatePath) {
3705
4262
  return candidatePath.trim().replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+/g, "/");
@@ -3753,6 +4310,261 @@ function sanitizeTaskSlug(taskName) {
3753
4310
  return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
3754
4311
  }
3755
4312
 
4313
+ // src/workflows/hook-registry.ts
4314
+ var BUILTIN_NAMESPACE = "builtin";
4315
+ var HOOK_REF_PATTERN = /^[a-z0-9_-]+:[a-z0-9_-]+$/;
4316
+ function isWorkflowHookRef(value) {
4317
+ return typeof value === "string" && HOOK_REF_PATTERN.test(value);
4318
+ }
4319
+ var registry = /* @__PURE__ */ new Map();
4320
+ function registerWorkflowHook(name, entry) {
4321
+ if (!isWorkflowHookRef(name)) {
4322
+ throw new Error(
4323
+ `Invalid workflow hook name "${name}": must be "<namespace>:<id>" using lowercase letters, digits, "-" or "_" (e.g. "acme:my-completion").`
4324
+ );
4325
+ }
4326
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) {
4327
+ throw new Error(
4328
+ `Cannot register "${name}": the "builtin:" namespace is reserved. Register under your own namespace and reference it from the workflow config instead.`
4329
+ );
4330
+ }
4331
+ registry.set(name, entry);
4332
+ }
4333
+ function registerBuiltinWorkflowHook(name, entry) {
4334
+ if (!name.startsWith(`${BUILTIN_NAMESPACE}:`) || !isWorkflowHookRef(name)) {
4335
+ throw new Error(`Builtin workflow hooks must be named "builtin:<id>" (got "${name}").`);
4336
+ }
4337
+ if (registry.has(name)) return;
4338
+ registry.set(name, entry);
4339
+ }
4340
+ function resolveWorkflowHook(name, expectedKind) {
4341
+ const entry = registry.get(name);
4342
+ if (!entry) {
4343
+ const known = listWorkflowHooks().filter((hook) => hook.kind === expectedKind).map((hook) => hook.name);
4344
+ throw new Error(
4345
+ `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."
4346
+ );
4347
+ }
4348
+ if (entry.kind !== expectedKind) {
4349
+ throw new Error(
4350
+ `Workflow hook "${name}" is registered as '${entry.kind}' but referenced from a '${expectedKind}' slot.`
4351
+ );
4352
+ }
4353
+ return entry.fn;
4354
+ }
4355
+ function listWorkflowHooks() {
4356
+ return [...registry.entries()].map(([name, entry]) => ({ name, kind: entry.kind }));
4357
+ }
4358
+ function unregisterWorkflowHook(name) {
4359
+ if (name.startsWith(`${BUILTIN_NAMESPACE}:`)) return false;
4360
+ return registry.delete(name);
4361
+ }
4362
+
4363
+ // src/workflows/workflow-config.ts
4364
+ var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
4365
+ "search_repo",
4366
+ "glob_files",
4367
+ "tree_directory",
4368
+ "list_directory"
4369
+ ]);
4370
+ var DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS = 2;
4371
+ function definePlaybook(playbook) {
4372
+ return playbook;
4373
+ }
4374
+ function interpolateWorkflowTemplate(template, state) {
4375
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
4376
+ const value = state[key];
4377
+ if (value === void 0 || value === null) return `{{${key}}}`;
4378
+ return String(value);
4379
+ });
4380
+ }
4381
+ function buildIsComplete(criteria, configName, milestoneName) {
4382
+ if (!criteria) return () => false;
4383
+ switch (criteria.type) {
4384
+ case "evidence":
4385
+ return (ctx) => {
4386
+ const minFiles = criteria.minReadFiles ?? 1;
4387
+ return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
4388
+ };
4389
+ case "sessions": {
4390
+ let baselineSessionCount;
4391
+ return (ctx) => {
4392
+ const minSessions = criteria.minSessions ?? 1;
4393
+ if (baselineSessionCount === void 0) {
4394
+ baselineSessionCount = ctx.state.sessions.length;
4395
+ }
4396
+ return ctx.state.sessions.length - baselineSessionCount >= minSessions;
4397
+ };
4398
+ }
4399
+ case "planWritten":
4400
+ return (ctx) => {
4401
+ return ctx.trace.planWritten;
4402
+ };
4403
+ case "never":
4404
+ return () => false;
4405
+ default: {
4406
+ if (isWorkflowHookRef(criteria.type)) {
4407
+ return resolveWorkflowHook(criteria.type, "completion");
4408
+ }
4409
+ throw new Error(
4410
+ `Workflow config '${configName}': milestone '${milestoneName}' has unknown completionCriteria.type "${criteria.type}" (expected evidence | sessions | planWritten | never, or a 'completion' hook reference).`
4411
+ );
4412
+ }
4413
+ }
4414
+ }
4415
+ function buildPolicyIntercept(policy, configName, deps) {
4416
+ if (!policy.blockedTools?.length && !policy.blockDiscoveryTools && !policy.allowedReadGlobs?.length && !policy.allowedWriteGlobs?.length && !policy.requirePlanBeforeWrite) {
4417
+ return void 0;
4418
+ }
4419
+ const blockedSet = new Set(
4420
+ (policy.blockedTools ?? []).map((t) => t.trim()).filter(Boolean)
4421
+ );
4422
+ const readGlobs = policy.allowedReadGlobs ?? [];
4423
+ const writeGlobs = policy.allowedWriteGlobs ?? [];
4424
+ const matchPathGlobs = deps.matchPathGlobs;
4425
+ if ((readGlobs.length > 0 || writeGlobs.length > 0) && !matchPathGlobs) {
4426
+ throw new Error(
4427
+ `Workflow config '${configName}': policy uses allowedReadGlobs/allowedWriteGlobs but no glob matcher was provided to compileWorkflowConfig (pass deps.matchPathGlobs).`
4428
+ );
4429
+ }
4430
+ return (toolName, args, ctx) => {
4431
+ if (blockedSet.has(toolName)) {
4432
+ return `Blocked by playbook policy: ${toolName} is not allowed for this task.`;
4433
+ }
4434
+ if (policy.blockDiscoveryTools && DISCOVERY_TOOLS.has(toolName)) {
4435
+ return `Blocked by playbook policy: discovery tools are disabled for this task.`;
4436
+ }
4437
+ const pathArg = typeof args.path === "string" && args.path.trim() ? ctx.normalizePath(String(args.path)) : void 0;
4438
+ if (pathArg) {
4439
+ const isWrite = toolName === "write_file" || toolName === "restore_file_checkpoint";
4440
+ const isRead = toolName === "read_file";
4441
+ if (isRead && readGlobs.length > 0) {
4442
+ const allowed = matchPathGlobs(pathArg, readGlobs);
4443
+ if (!allowed) {
4444
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed read globs: ${readGlobs.join(", ")}`;
4445
+ }
4446
+ }
4447
+ if (isWrite && writeGlobs.length > 0) {
4448
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4449
+ if (planPath && pathArg === planPath) {
4450
+ } else {
4451
+ const allowed = matchPathGlobs(pathArg, writeGlobs);
4452
+ if (!allowed) {
4453
+ return `Blocked by playbook policy: ${toolName} path "${pathArg}" is outside allowed write globs: ${writeGlobs.join(", ")}`;
4454
+ }
4455
+ }
4456
+ }
4457
+ if (isWrite && policy.requirePlanBeforeWrite && !ctx.state.planWritten && !ctx.trace.planWritten) {
4458
+ const planPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4459
+ if (!planPath || pathArg !== planPath) {
4460
+ return `Blocked by playbook policy: write the plan before creating other files.`;
4461
+ }
4462
+ }
4463
+ }
4464
+ return void 0;
4465
+ };
4466
+ }
4467
+ function buildPolicyGuidance(policy) {
4468
+ if (!policy) return [];
4469
+ const lines = [];
4470
+ if (policy.requirePlanBeforeWrite) {
4471
+ lines.push(
4472
+ "Policy: write the plan file before any other file. Once the plan is written, other writes are allowed in the same turn."
4473
+ );
4474
+ }
4475
+ if (policy.allowedWriteGlobs?.length) {
4476
+ lines.push(
4477
+ `Policy: file writes are only allowed for paths matching: ${policy.allowedWriteGlobs.join(", ")} (the plan file is always allowed).`
4478
+ );
4479
+ }
4480
+ if (policy.outputRoot) {
4481
+ lines.push(`Policy: create new files under "${policy.outputRoot.replace(/\/$/, "")}/".`);
4482
+ }
4483
+ if (policy.allowedReadGlobs?.length) {
4484
+ lines.push(
4485
+ `Policy: file reads are only allowed for paths matching: ${policy.allowedReadGlobs.join(", ")}.`
4486
+ );
4487
+ }
4488
+ if (policy.blockDiscoveryTools) {
4489
+ lines.push(
4490
+ "Policy: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are disabled for this task."
4491
+ );
4492
+ }
4493
+ if (policy.blockedTools?.length) {
4494
+ lines.push(`Policy: these tools are disabled for this task: ${policy.blockedTools.join(", ")}.`);
4495
+ }
4496
+ return lines;
4497
+ }
4498
+ function resolveSlotHook(value, kind) {
4499
+ if (value === void 0) return void 0;
4500
+ if (typeof value === "function") return value;
4501
+ return resolveWorkflowHook(value, kind);
4502
+ }
4503
+ function compileMilestone(milestone, config, policyIntercept, policyGuidance) {
4504
+ const buildInstructions = typeof milestone.instructions === "function" ? milestone.instructions : isWorkflowHookRef(milestone.instructions) ? resolveWorkflowHook(milestone.instructions, "instructions") : (state) => {
4505
+ const header = `--- Workflow Phase: ${milestone.name} ---`;
4506
+ const desc = milestone.description ? `
4507
+ ${milestone.description}` : "";
4508
+ const instructions = interpolateWorkflowTemplate(
4509
+ milestone.instructions,
4510
+ state
4511
+ );
4512
+ return `${header}${desc}
4513
+ ${instructions}`;
4514
+ };
4515
+ const guidanceHook = typeof milestone.toolGuidance === "function" ? milestone.toolGuidance : milestone.toolGuidance !== void 0 && isWorkflowHookRef(milestone.toolGuidance) ? resolveWorkflowHook(milestone.toolGuidance, "toolGuidance") : void 0;
4516
+ const buildToolGuidance = (state) => {
4517
+ const base = guidanceHook ? guidanceHook(state) : milestone.toolGuidance ?? [];
4518
+ return policyGuidance.length > 0 ? [...base, ...policyGuidance] : base;
4519
+ };
4520
+ const customIntercept = resolveSlotHook(milestone.intercept, "intercept");
4521
+ const interceptToolCall = policyIntercept && customIntercept ? (toolName, args, ctx) => policyIntercept(toolName, args, ctx) ?? customIntercept(toolName, args, ctx) : policyIntercept ?? customIntercept;
4522
+ const transitionHook = typeof milestone.transitionSummary === "function" ? milestone.transitionSummary : milestone.transitionSummary !== void 0 && isWorkflowHookRef(milestone.transitionSummary) ? resolveWorkflowHook(milestone.transitionSummary, "transitionSummary") : void 0;
4523
+ const buildTransitionSummary = milestone.transitionSummary === void 0 ? void 0 : transitionHook ?? ((state, nextPhaseName) => interpolateWorkflowTemplate(milestone.transitionSummary, state).replace(
4524
+ /\{\{nextPhase\}\}/g,
4525
+ nextPhaseName
4526
+ ));
4527
+ const recoveryHook = typeof milestone.recovery === "function" ? milestone.recovery : milestone.recovery !== void 0 && isWorkflowHookRef(milestone.recovery) ? resolveWorkflowHook(milestone.recovery, "recovery") : void 0;
4528
+ const buildRecoveryMessage = milestone.recovery === void 0 ? void 0 : recoveryHook ?? ((state) => {
4529
+ const inline = milestone.recovery;
4530
+ const threshold = inline.afterEmptySessions ?? DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS;
4531
+ if ((state.consecutiveEmptySessions ?? 0) < threshold) return void 0;
4532
+ return interpolateWorkflowTemplate(inline.message, state);
4533
+ });
4534
+ const canAcceptCompletion = milestone.canAcceptCompletion === void 0 ? void 0 : typeof milestone.canAcceptCompletion === "function" ? milestone.canAcceptCompletion : isWorkflowHookRef(milestone.canAcceptCompletion) ? resolveWorkflowHook(milestone.canAcceptCompletion, "acceptCompletion") : () => milestone.canAcceptCompletion;
4535
+ const isComplete = typeof milestone.completionCriteria === "function" ? milestone.completionCriteria : buildIsComplete(milestone.completionCriteria, config.name, milestone.name);
4536
+ return {
4537
+ name: milestone.name,
4538
+ description: milestone.description,
4539
+ buildInstructions,
4540
+ buildToolGuidance,
4541
+ isComplete,
4542
+ ...interceptToolCall ? { interceptToolCall } : {},
4543
+ ...buildTransitionSummary ? { buildTransitionSummary } : {},
4544
+ ...buildRecoveryMessage ? { buildRecoveryMessage } : {},
4545
+ ...milestone.forceEndTurn ? { shouldForceEndTurn: resolveSlotHook(milestone.forceEndTurn, "forceEndTurn") } : {},
4546
+ ...canAcceptCompletion ? { canAcceptCompletion } : {}
4547
+ };
4548
+ }
4549
+ function compileWorkflowConfig(config, deps = {}) {
4550
+ const policyIntercept = config.policy ? buildPolicyIntercept(config.policy, config.name, deps) : void 0;
4551
+ const policyGuidance = buildPolicyGuidance(config.policy);
4552
+ const phases = config.milestones.map(
4553
+ (milestone) => compileMilestone(milestone, config, policyIntercept, policyGuidance)
4554
+ );
4555
+ const classifyVariant4 = resolveSlotHook(config.classifyVariant, "classify");
4556
+ const generateBootstrapContext2 = resolveSlotHook(config.bootstrap, "bootstrap");
4557
+ const buildCandidateBlock2 = resolveSlotHook(config.candidateBlock, "candidateBlock");
4558
+ return {
4559
+ name: config.name,
4560
+ phases,
4561
+ ...config.stallPolicy ? { stallPolicy: config.stallPolicy } : {},
4562
+ ...classifyVariant4 ? { classifyVariant: classifyVariant4 } : {},
4563
+ ...generateBootstrapContext2 ? { generateBootstrapContext: generateBootstrapContext2 } : {},
4564
+ ...buildCandidateBlock2 ? { buildCandidateBlock: buildCandidateBlock2 } : {}
4565
+ };
4566
+ }
4567
+
3756
4568
  // src/workflows/default-workflow.ts
3757
4569
  function isExternalTask(state) {
3758
4570
  return state.workflowVariant === "external";
@@ -3902,6 +4714,45 @@ function summarizeTextBlock(value, maxLines = 4) {
3902
4714
  if (!text) return "";
3903
4715
  return text.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, maxLines).join(" | ").slice(0, 240);
3904
4716
  }
4717
+ function interceptProductWriteTarget(toolName, normalizedPathArg, ctx, guardLabel) {
4718
+ const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4719
+ const normalizedBestCandidatePath = ctx.state.bestCandidatePath ? ctx.normalizePath(ctx.state.bestCandidatePath) : void 0;
4720
+ if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4721
+ const allowedWriteTargets = new Set(
4722
+ [
4723
+ normalizedPlanPath,
4724
+ normalizedBestCandidatePath,
4725
+ ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4726
+ ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4727
+ ].filter((value) => Boolean(value))
4728
+ );
4729
+ if (!allowedWriteTargets.has(normalizedPathArg)) {
4730
+ return [
4731
+ `Blocked by marathon ${guardLabel}: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4732
+ `Do not create scratch files like "${normalizedPathArg}".`,
4733
+ normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
4734
+ ].join(" ");
4735
+ }
4736
+ }
4737
+ if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4738
+ const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
4739
+ if (!outputRoot) {
4740
+ return [
4741
+ `Blocked by marathon ${guardLabel}: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
4742
+ `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
4743
+ ].join(" ");
4744
+ }
4745
+ const rootPrefix = outputRoot + "/";
4746
+ const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
4747
+ if (!isUnderRoot) {
4748
+ return [
4749
+ `Blocked by marathon ${guardLabel}: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
4750
+ `"${normalizedPathArg}" is outside the allowed output root.`
4751
+ ].join(" ");
4752
+ }
4753
+ }
4754
+ return void 0;
4755
+ }
3905
4756
  var researchPhase = {
3906
4757
  name: "research",
3907
4758
  description: "Inspect the repo and identify the correct target file",
@@ -3986,11 +4837,15 @@ var researchPhase = {
3986
4837
  const normalizedPathArg2 = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
3987
4838
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
3988
4839
  if (normalizedPathArg2 && normalizedPlanPath && normalizedPathArg2 !== normalizedPlanPath) {
3989
- return [
3990
- `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
3991
- "Complete research first, then the system will advance you to planning.",
3992
- `You may write the plan to "${normalizedPlanPath}" once research is complete.`
3993
- ].join(" ");
4840
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
4841
+ if (!planWritten) {
4842
+ return [
4843
+ `Blocked by marathon research guard: ${toolName} cannot create product files during the research phase.`,
4844
+ "Complete research first, then the system will advance you to planning.",
4845
+ `You may write the plan to "${normalizedPlanPath}" once research is complete.`
4846
+ ].join(" ");
4847
+ }
4848
+ return interceptProductWriteTarget(toolName, normalizedPathArg2, ctx, "research guard");
3994
4849
  }
3995
4850
  }
3996
4851
  return void 0;
@@ -4113,19 +4968,24 @@ var planningPhase = {
4113
4968
  "Research is complete. Write the implementation plan for building this from scratch.",
4114
4969
  `Write the plan markdown to exactly: ${planPath}`,
4115
4970
  "List the files you will create, their locations, purpose, and any dependencies to install.",
4971
+ ...state.outputRoot ? [
4972
+ `All new files must be created under "${state.outputRoot}" \u2014 writes outside that directory are blocked, so plan every file location inside it.`
4973
+ ] : [],
4116
4974
  'Include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4117
- "If the plan already exists, update that same plan file instead of creating a different one."
4975
+ "If the plan already exists, update that same plan file instead of creating a different one.",
4976
+ "Once the plan is written, you may begin creating the planned files in the same turn."
4118
4977
  ].join("\n");
4119
4978
  }
4120
4979
  return [
4121
4980
  "--- Workflow Phase: Planning ---",
4122
4981
  "Research is complete. Your current job is to write the implementation plan before any product-file edits.",
4123
4982
  `Write the plan markdown to exactly: ${planPath}`,
4124
- "Do NOT edit the target product file yet.",
4983
+ "Do NOT edit the target product file before the plan exists.",
4125
4984
  "The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.",
4126
4985
  'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
4127
4986
  'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
4128
- "If the plan already exists, update that same plan file instead of creating a different one."
4987
+ "If the plan already exists, update that same plan file instead of creating a different one.",
4988
+ "Once the plan is written, you may begin editing the target file in the same turn."
4129
4989
  ].join("\n");
4130
4990
  },
4131
4991
  buildToolGuidance(state) {
@@ -4153,10 +5013,14 @@ var planningPhase = {
4153
5013
  const normalizedPlanPath = ctx.state.planPath ? ctx.normalizePath(ctx.state.planPath) : void 0;
4154
5014
  const isWriteLikeTool = toolName === "write_file" || toolName === "edit_file" || toolName === "restore_file_checkpoint";
4155
5015
  if (isWriteLikeTool && normalizedPathArg && normalizedPlanPath && normalizedPathArg !== normalizedPlanPath) {
4156
- return [
4157
- `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
4158
- `Write the plan to "${normalizedPlanPath}" before editing any product files.`
4159
- ].join(" ");
5016
+ const planWritten = ctx.trace.planWritten || Boolean(ctx.state.planWritten);
5017
+ if (!planWritten) {
5018
+ return [
5019
+ `Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
5020
+ `Write the plan to "${normalizedPlanPath}" before editing any product files.`
5021
+ ].join(" ");
5022
+ }
5023
+ return interceptProductWriteTarget(toolName, normalizedPathArg, ctx, "planning guard");
4160
5024
  }
4161
5025
  return void 0;
4162
5026
  },
@@ -4216,6 +5080,9 @@ var executionPhase = {
4216
5080
  },
4217
5081
  buildToolGuidance(state) {
4218
5082
  return [
5083
+ ...state.isCreationTask && state.outputRoot ? [
5084
+ `Creation guard: create new files under "${state.outputRoot}". Writes outside it are blocked \u2014 the plan file is the only exception.`
5085
+ ] : [],
4219
5086
  ...state.bestCandidatePath ? [
4220
5087
  `Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`
4221
5088
  ] : [
@@ -4257,40 +5124,13 @@ var executionPhase = {
4257
5124
  `After that, you may update "${normalizedPlanPath}" with progress.`
4258
5125
  ].join(" ");
4259
5126
  }
4260
- if (!ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4261
- const allowedWriteTargets = new Set(
4262
- [
4263
- normalizedPlanPath,
4264
- normalizedBestCandidatePath,
4265
- ...(ctx.state.recentReadPaths || []).map((readPath) => ctx.normalizePath(readPath)),
4266
- ...ctx.trace.readPaths.map((readPath) => ctx.normalizePath(readPath))
4267
- ].filter((value) => Boolean(value))
4268
- );
4269
- if (!allowedWriteTargets.has(normalizedPathArg)) {
4270
- return [
4271
- `Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
4272
- `Do not create scratch files like "${normalizedPathArg}".`,
4273
- normalizedBestCandidatePath ? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.` : "Read the current target file before writing."
4274
- ].join(" ");
4275
- }
4276
- }
4277
- if (ctx.state.isCreationTask && normalizedPathArg && normalizedPathArg !== normalizedPlanPath) {
4278
- const outputRoot = ctx.state.outputRoot ? ctx.state.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "") || void 0 : void 0;
4279
- if (!outputRoot) {
4280
- return [
4281
- `Blocked by marathon execution guard: creation tasks require outputRoot. Writes outside the plan are not allowed.`,
4282
- `Plan path: "${normalizedPlanPath}". Create files only under the configured output root.`
4283
- ].join(" ");
4284
- }
4285
- const rootPrefix = outputRoot + "/";
4286
- const isUnderRoot = normalizedPathArg === outputRoot || normalizedPathArg.startsWith(rootPrefix);
4287
- if (!isUnderRoot) {
4288
- return [
4289
- `Blocked by marathon execution guard: ${toolName} must target the plan or paths under outputRoot "${outputRoot}/".`,
4290
- `"${normalizedPathArg}" is outside the allowed output root.`
4291
- ].join(" ");
4292
- }
4293
- }
5127
+ const writeTargetBlock = interceptProductWriteTarget(
5128
+ toolName,
5129
+ normalizedPathArg,
5130
+ ctx,
5131
+ "execution guard"
5132
+ );
5133
+ if (writeTargetBlock) return writeTargetBlock;
4294
5134
  }
4295
5135
  return void 0;
4296
5136
  },
@@ -4523,13 +5363,163 @@ function buildCandidateBlock(state) {
4523
5363
  ...state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []
4524
5364
  ].join("\n");
4525
5365
  }
4526
- var defaultWorkflow = {
5366
+ var builtinHooksRegistered = false;
5367
+ function ensureDefaultWorkflowHooks() {
5368
+ if (builtinHooksRegistered) return;
5369
+ builtinHooksRegistered = true;
5370
+ registerBuiltinWorkflowHook("builtin:classify-task-variant", {
5371
+ kind: "classify",
5372
+ fn: classifyVariant
5373
+ });
5374
+ registerBuiltinWorkflowHook("builtin:repo-bootstrap-discovery", {
5375
+ kind: "bootstrap",
5376
+ fn: generateBootstrapContext
5377
+ });
5378
+ registerBuiltinWorkflowHook("builtin:best-candidate-block", {
5379
+ kind: "candidateBlock",
5380
+ fn: buildCandidateBlock
5381
+ });
5382
+ registerBuiltinWorkflowHook("builtin:research-instructions", {
5383
+ kind: "instructions",
5384
+ fn: researchPhase.buildInstructions
5385
+ });
5386
+ registerBuiltinWorkflowHook("builtin:research-tool-guidance", {
5387
+ kind: "toolGuidance",
5388
+ fn: researchPhase.buildToolGuidance
5389
+ });
5390
+ registerBuiltinWorkflowHook("builtin:research-complete", {
5391
+ kind: "completion",
5392
+ fn: researchPhase.isComplete
5393
+ });
5394
+ registerBuiltinWorkflowHook("builtin:research-transition-summary", {
5395
+ kind: "transitionSummary",
5396
+ fn: researchPhase.buildTransitionSummary
5397
+ });
5398
+ registerBuiltinWorkflowHook("builtin:research-guard", {
5399
+ kind: "intercept",
5400
+ fn: researchPhase.interceptToolCall
5401
+ });
5402
+ registerBuiltinWorkflowHook("builtin:research-recovery", {
5403
+ kind: "recovery",
5404
+ fn: researchPhase.buildRecoveryMessage
5405
+ });
5406
+ registerBuiltinWorkflowHook("builtin:research-force-end-turn", {
5407
+ kind: "forceEndTurn",
5408
+ fn: researchPhase.shouldForceEndTurn
5409
+ });
5410
+ registerBuiltinWorkflowHook("builtin:research-accept-completion", {
5411
+ kind: "acceptCompletion",
5412
+ fn: researchPhase.canAcceptCompletion
5413
+ });
5414
+ registerBuiltinWorkflowHook("builtin:planning-instructions", {
5415
+ kind: "instructions",
5416
+ fn: planningPhase.buildInstructions
5417
+ });
5418
+ registerBuiltinWorkflowHook("builtin:planning-tool-guidance", {
5419
+ kind: "toolGuidance",
5420
+ fn: planningPhase.buildToolGuidance
5421
+ });
5422
+ registerBuiltinWorkflowHook("builtin:planning-complete", {
5423
+ kind: "completion",
5424
+ fn: planningPhase.isComplete
5425
+ });
5426
+ registerBuiltinWorkflowHook("builtin:planning-transition-summary", {
5427
+ kind: "transitionSummary",
5428
+ fn: planningPhase.buildTransitionSummary
5429
+ });
5430
+ registerBuiltinWorkflowHook("builtin:planning-guard", {
5431
+ kind: "intercept",
5432
+ fn: planningPhase.interceptToolCall
5433
+ });
5434
+ registerBuiltinWorkflowHook("builtin:planning-recovery", {
5435
+ kind: "recovery",
5436
+ fn: planningPhase.buildRecoveryMessage
5437
+ });
5438
+ registerBuiltinWorkflowHook("builtin:planning-force-end-turn", {
5439
+ kind: "forceEndTurn",
5440
+ fn: planningPhase.shouldForceEndTurn
5441
+ });
5442
+ registerBuiltinWorkflowHook("builtin:execution-instructions", {
5443
+ kind: "instructions",
5444
+ fn: executionPhase.buildInstructions
5445
+ });
5446
+ registerBuiltinWorkflowHook("builtin:execution-tool-guidance", {
5447
+ kind: "toolGuidance",
5448
+ fn: executionPhase.buildToolGuidance
5449
+ });
5450
+ registerBuiltinWorkflowHook("builtin:execution-guard", {
5451
+ kind: "intercept",
5452
+ fn: executionPhase.interceptToolCall
5453
+ });
5454
+ registerBuiltinWorkflowHook("builtin:execution-recovery", {
5455
+ kind: "recovery",
5456
+ fn: executionPhase.buildRecoveryMessage
5457
+ });
5458
+ registerBuiltinWorkflowHook("builtin:execution-force-end-turn", {
5459
+ kind: "forceEndTurn",
5460
+ fn: executionPhase.shouldForceEndTurn
5461
+ });
5462
+ registerBuiltinWorkflowHook("builtin:execution-accept-completion", {
5463
+ kind: "acceptCompletion",
5464
+ fn: executionPhase.canAcceptCompletion
5465
+ });
5466
+ }
5467
+ var defaultWorkflowConfig = {
4527
5468
  name: "default",
4528
- phases: [researchPhase, planningPhase, executionPhase],
4529
- classifyVariant,
4530
- generateBootstrapContext,
4531
- buildCandidateBlock
5469
+ // Empty-session escalation. The counter only counts tool actions, so
5470
+ // narration-only sessions ("I'll create the files now" with no tool calls)
5471
+ // escalate here even though the phase recovery conditions keyed on
5472
+ // hadTextOutput skip them: nudge after the first actionless session, signal
5473
+ // model escalation after the second (a no-op unless the caller configured a
5474
+ // fallback model), and stop as 'stalled' after the third — the same total
5475
+ // session budget as before stallPolicy existed.
5476
+ stallPolicy: { nudgeAfter: 1, escalateModelAfter: 2, stopAfter: 3 },
5477
+ classifyVariant: "builtin:classify-task-variant",
5478
+ bootstrap: "builtin:repo-bootstrap-discovery",
5479
+ candidateBlock: "builtin:best-candidate-block",
5480
+ milestones: [
5481
+ {
5482
+ name: "research",
5483
+ description: "Inspect the repo and identify the correct target file",
5484
+ instructions: "builtin:research-instructions",
5485
+ toolGuidance: "builtin:research-tool-guidance",
5486
+ completionCriteria: { type: "builtin:research-complete" },
5487
+ intercept: "builtin:research-guard",
5488
+ transitionSummary: "builtin:research-transition-summary",
5489
+ recovery: "builtin:research-recovery",
5490
+ forceEndTurn: "builtin:research-force-end-turn",
5491
+ canAcceptCompletion: "builtin:research-accept-completion"
5492
+ },
5493
+ {
5494
+ name: "planning",
5495
+ description: "Write the implementation plan before editing product files",
5496
+ instructions: "builtin:planning-instructions",
5497
+ toolGuidance: "builtin:planning-tool-guidance",
5498
+ completionCriteria: { type: "builtin:planning-complete" },
5499
+ intercept: "builtin:planning-guard",
5500
+ transitionSummary: "builtin:planning-transition-summary",
5501
+ recovery: "builtin:planning-recovery",
5502
+ forceEndTurn: "builtin:planning-force-end-turn"
5503
+ // canAcceptCompletion intentionally absent: the hand-written planning
5504
+ // phase never defined it, and the SDK accepts completion when the slot
5505
+ // is undefined. Keep parity.
5506
+ },
5507
+ {
5508
+ name: "execution",
5509
+ description: "Execute the plan by editing target files",
5510
+ instructions: "builtin:execution-instructions",
5511
+ toolGuidance: "builtin:execution-tool-guidance",
5512
+ // Execution never auto-advances; completion is agent-driven via TASK_COMPLETE
5513
+ completionCriteria: { type: "never" },
5514
+ intercept: "builtin:execution-guard",
5515
+ recovery: "builtin:execution-recovery",
5516
+ forceEndTurn: "builtin:execution-force-end-turn",
5517
+ canAcceptCompletion: "builtin:execution-accept-completion"
5518
+ }
5519
+ ]
4532
5520
  };
5521
+ ensureDefaultWorkflowHooks();
5522
+ var defaultWorkflow = compileWorkflowConfig(defaultWorkflowConfig);
4533
5523
 
4534
5524
  // src/workflows/deploy-workflow.ts
4535
5525
  var scaffoldPhase = {
@@ -4941,6 +5931,34 @@ var gameWorkflow = {
4941
5931
  }
4942
5932
  };
4943
5933
 
5934
+ // src/workflows/stall-policy.ts
5935
+ var DEFAULT_STALL_STOP_AFTER = 3;
5936
+ function isPositiveInteger(value) {
5937
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
5938
+ }
5939
+ function resolveStallStopAfter(policy) {
5940
+ return isPositiveInteger(policy?.stopAfter) ? policy.stopAfter : DEFAULT_STALL_STOP_AFTER;
5941
+ }
5942
+ function shouldRequestModelEscalation(policy, consecutiveEmptySessions) {
5943
+ const threshold = policy?.escalateModelAfter;
5944
+ if (!isPositiveInteger(threshold)) return false;
5945
+ return consecutiveEmptySessions === threshold;
5946
+ }
5947
+ function shouldInjectEmptySessionNudge(policy, consecutiveEmptySessions) {
5948
+ const threshold = policy?.nudgeAfter;
5949
+ if (!isPositiveInteger(threshold)) return false;
5950
+ return consecutiveEmptySessions >= threshold;
5951
+ }
5952
+ function buildEmptySessionNudge(consecutiveEmptySessions) {
5953
+ const sessionPhrase = consecutiveEmptySessions === 1 ? "Your previous session ended" : `Your previous ${consecutiveEmptySessions} sessions ended`;
5954
+ return [
5955
+ "Recovery instruction:",
5956
+ `${sessionPhrase} without a single tool call. Describing what you plan to do does nothing \u2014 only tool calls make progress.`,
5957
+ "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.",
5958
+ "If a previous tool call was blocked, re-read the block message and satisfy its requirement instead of ending the turn."
5959
+ ].join("\n");
5960
+ }
5961
+
4944
5962
  // src/endpoints.ts
4945
5963
  var FlowsEndpoint = class {
4946
5964
  constructor(client) {
@@ -5431,15 +6449,15 @@ var DispatchEndpoint = class {
5431
6449
  * Attach approved runtime tools to a prompt step in a redispatch request.
5432
6450
  * Returns a new request object and does not mutate the original.
5433
6451
  */
5434
- attachApprovedRuntimeTools(request, runtimeTools, options) {
5435
- return attachRuntimeToolsToDispatchRequest(request, runtimeTools, options);
6452
+ attachApprovedRuntimeTools(request2, runtimeTools, options) {
6453
+ return attachRuntimeToolsToDispatchRequest(request2, runtimeTools, options);
5436
6454
  }
5437
6455
  /**
5438
6456
  * Validate a generated runtime tool proposal and attach it to the redispatch
5439
6457
  * request if approved, in one call.
5440
6458
  */
5441
- applyGeneratedRuntimeToolProposal(request, proposal, options) {
5442
- return applyGeneratedRuntimeToolProposalToDispatchRequest(request, proposal, options);
6459
+ applyGeneratedRuntimeToolProposal(request2, proposal, options) {
6460
+ return applyGeneratedRuntimeToolProposalToDispatchRequest(request2, proposal, options);
5443
6461
  }
5444
6462
  };
5445
6463
  var ChatEndpoint = class {
@@ -5944,6 +6962,22 @@ async function processAgentStream(body, callbacks) {
5944
6962
  reader.releaseLock();
5945
6963
  }
5946
6964
  }
6965
+ function sleepWithAbort(delayMs, signal) {
6966
+ return new Promise((resolve) => {
6967
+ const onAbort = () => {
6968
+ clearTimeout(timer);
6969
+ resolve();
6970
+ };
6971
+ const timer = setTimeout(() => {
6972
+ signal?.removeEventListener("abort", onAbort);
6973
+ resolve();
6974
+ }, delayMs);
6975
+ if (signal) {
6976
+ if (signal.aborted) onAbort();
6977
+ else signal.addEventListener("abort", onAbort, { once: true });
6978
+ }
6979
+ });
6980
+ }
5947
6981
  var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
5948
6982
  type: "object",
5949
6983
  properties: {
@@ -5975,8 +7009,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
5975
7009
  },
5976
7010
  required: ["name", "description", "toolType", "parametersSchema", "config"]
5977
7011
  };
5978
- function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
5979
- const existing = request.tools?.runtimeTools || [];
7012
+ function appendRuntimeToolsToAgentRequest(request2, runtimeTools) {
7013
+ const existing = request2.tools?.runtimeTools || [];
5980
7014
  const existingNames = new Set(existing.map((tool) => tool.name));
5981
7015
  const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
5982
7016
  name: tool.name,
@@ -5986,9 +7020,9 @@ function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
5986
7020
  ...tool.config ? { config: tool.config } : {}
5987
7021
  }));
5988
7022
  return {
5989
- ...request,
7023
+ ...request2,
5990
7024
  tools: {
5991
- ...request.tools,
7025
+ ...request2.tools,
5992
7026
  runtimeTools: [...existing, ...converted]
5993
7027
  }
5994
7028
  };
@@ -6064,21 +7098,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6064
7098
  * Attach approved runtime tools to an agent execute request.
6065
7099
  * Returns a new request object and does not mutate the original.
6066
7100
  */
6067
- attachApprovedRuntimeTools(request, runtimeTools) {
6068
- return appendRuntimeToolsToAgentRequest(request, runtimeTools);
7101
+ attachApprovedRuntimeTools(request2, runtimeTools) {
7102
+ return appendRuntimeToolsToAgentRequest(request2, runtimeTools);
6069
7103
  }
6070
7104
  /**
6071
7105
  * Validate a generated runtime tool proposal and append it to an agent execute
6072
7106
  * request if approved, in one call.
6073
7107
  */
6074
- applyGeneratedRuntimeToolProposal(request, proposal, options) {
7108
+ applyGeneratedRuntimeToolProposal(request2, proposal, options) {
6075
7109
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
6076
7110
  if (!decision.approved || !decision.tool) {
6077
- return { decision, request };
7111
+ return { decision, request: request2 };
6078
7112
  }
6079
7113
  return {
6080
7114
  decision,
6081
- request: appendRuntimeToolsToAgentRequest(request, [decision.tool])
7115
+ request: appendRuntimeToolsToAgentRequest(request2, [decision.tool])
6082
7116
  };
6083
7117
  }
6084
7118
  /**
@@ -6108,13 +7142,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6108
7142
  * // ...
6109
7143
  * ```
6110
7144
  */
6111
- async executeStream(id, data) {
7145
+ async executeStream(id, data, init) {
6112
7146
  return this.client.requestStream(`/agents/${id}/execute`, {
6113
7147
  method: "POST",
6114
7148
  body: JSON.stringify({
6115
7149
  ...data,
6116
7150
  streamResponse: true
6117
- })
7151
+ }),
7152
+ ...init?.signal ? { signal: init.signal } : {}
6118
7153
  });
6119
7154
  }
6120
7155
  /**
@@ -6210,56 +7245,94 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6210
7245
  runtimeTools: [...data.tools?.runtimeTools || [], ...runtimeTools]
6211
7246
  }
6212
7247
  };
6213
- const response = await this.executeStream(id, requestData);
6214
- if (!response.ok) {
6215
- const error = await response.json().catch(() => ({ error: "Unknown error" }));
6216
- throw new Error(error.error || `HTTP ${response.status}`);
6217
- }
6218
- let currentBody = response.body;
7248
+ const abortSignal = options?.abortSignal;
6219
7249
  let accumulatedOutput = "";
6220
7250
  let lastKnownCost = 0;
6221
7251
  let lastKnownTokens;
7252
+ let lastSeenExecutionId = "";
6222
7253
  let pauseCount = 0;
6223
7254
  let discoveryPauseCount = 0;
6224
7255
  let consecutiveDiscoveryPauseCount = 0;
6225
7256
  const toolNameCounts = {};
6226
7257
  let recentActionKeys = [];
6227
7258
  const toolMessages = [];
7259
+ const finishAborted = (executionId) => {
7260
+ const abortCompleteEvent = {
7261
+ type: "agent_complete",
7262
+ executionId,
7263
+ seq: 0,
7264
+ agentId: id,
7265
+ success: true,
7266
+ iterations: 1,
7267
+ stopReason: "end_turn",
7268
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7269
+ totalCost: lastKnownCost,
7270
+ ...lastKnownTokens ? { totalTokens: lastKnownTokens } : {},
7271
+ finalOutput: [accumulatedOutput.trim(), "Session aborted by user request."].filter(Boolean).join("\n\n"),
7272
+ duration: 0
7273
+ };
7274
+ callbacks?.onAgentComplete?.(abortCompleteEvent);
7275
+ return { completeEvent: abortCompleteEvent, toolMessages };
7276
+ };
7277
+ let response;
7278
+ try {
7279
+ response = await this.executeStream(id, requestData, {
7280
+ ...abortSignal ? { signal: abortSignal } : {}
7281
+ });
7282
+ } catch (error) {
7283
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
7284
+ throw error;
7285
+ }
7286
+ if (!response.ok) {
7287
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
7288
+ throw new Error(error.error || `HTTP ${response.status}`);
7289
+ }
7290
+ let currentBody = response.body;
6228
7291
  while (true) {
6229
7292
  let pausedEvent = null;
6230
7293
  let completeEvent = null;
6231
- await processAgentStream(currentBody, {
6232
- ...callbacks,
6233
- onTurnDelta: (event) => {
6234
- if (event.contentType === "text") {
6235
- accumulatedOutput += event.delta;
6236
- }
6237
- callbacks?.onTurnDelta?.(event);
6238
- },
6239
- onTurnComplete: (event) => {
6240
- if (typeof event.cost === "number") {
6241
- lastKnownCost = event.cost;
6242
- }
6243
- if (event.tokens) {
6244
- lastKnownTokens = event.tokens;
6245
- }
6246
- callbacks?.onTurnComplete?.(event);
6247
- },
6248
- onAgentPaused: (event) => {
6249
- pausedEvent = event;
6250
- callbacks?.onAgentPaused?.(event);
6251
- },
6252
- onAgentComplete: (event) => {
6253
- if (!event.finalOutput && accumulatedOutput) {
6254
- event.finalOutput = accumulatedOutput;
7294
+ try {
7295
+ await processAgentStream(currentBody, {
7296
+ ...callbacks,
7297
+ onAgentStart: (event) => {
7298
+ lastSeenExecutionId = event.executionId;
7299
+ callbacks?.onAgentStart?.(event);
7300
+ },
7301
+ onTurnDelta: (event) => {
7302
+ if (event.contentType === "text") {
7303
+ accumulatedOutput += event.delta;
7304
+ }
7305
+ callbacks?.onTurnDelta?.(event);
7306
+ },
7307
+ onTurnComplete: (event) => {
7308
+ if (typeof event.cost === "number") {
7309
+ lastKnownCost = event.cost;
7310
+ }
7311
+ if (event.tokens) {
7312
+ lastKnownTokens = event.tokens;
7313
+ }
7314
+ callbacks?.onTurnComplete?.(event);
7315
+ },
7316
+ onAgentPaused: (event) => {
7317
+ pausedEvent = event;
7318
+ callbacks?.onAgentPaused?.(event);
7319
+ },
7320
+ onAgentComplete: (event) => {
7321
+ if (!event.finalOutput && accumulatedOutput) {
7322
+ event.finalOutput = accumulatedOutput;
7323
+ }
7324
+ completeEvent = event;
7325
+ callbacks?.onAgentComplete?.(event);
6255
7326
  }
6256
- completeEvent = event;
6257
- callbacks?.onAgentComplete?.(event);
6258
- }
6259
- });
7327
+ });
7328
+ } catch (error) {
7329
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
7330
+ throw error;
7331
+ }
6260
7332
  if (completeEvent) return { completeEvent, toolMessages };
6261
7333
  if (pausedEvent) {
6262
7334
  const { toolName, toolId, parameters, executionId } = pausedEvent;
7335
+ lastSeenExecutionId = executionId;
6263
7336
  const toolDef = localTools[toolName];
6264
7337
  if (!toolDef) {
6265
7338
  throw new Error(`Local tool "${toolName}" required but not provided`);
@@ -6374,6 +7447,19 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6374
7447
  callbacks?.onAgentComplete?.(forcedCompleteEvent);
6375
7448
  return { completeEvent: forcedCompleteEvent, toolMessages };
6376
7449
  }
7450
+ if (abortSignal?.aborted) {
7451
+ callbacks?.onLocalToolExecutionComplete?.({
7452
+ executionId,
7453
+ toolCallId: toolId,
7454
+ toolName,
7455
+ parameters: parsedParams,
7456
+ result: toolResult,
7457
+ success: true,
7458
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7459
+ durationMs: Date.now() - localExecutionStartedAtMs
7460
+ });
7461
+ return finishAborted(executionId);
7462
+ }
6377
7463
  if (options?.shouldInterrupt?.()) {
6378
7464
  callbacks?.onLocalToolExecutionComplete?.({
6379
7465
  executionId,
@@ -6407,15 +7493,22 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6407
7493
  callbacks?.onAgentComplete?.(interruptCompleteEvent);
6408
7494
  return { completeEvent: interruptCompleteEvent, toolMessages };
6409
7495
  }
6410
- const resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
6411
- method: "POST",
6412
- body: JSON.stringify({
6413
- executionId,
6414
- toolOutputs: { [toolName]: toolResult },
6415
- streamResponse: true,
6416
- debugMode: data.debugMode
6417
- })
6418
- });
7496
+ let resumeResponse;
7497
+ try {
7498
+ resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
7499
+ method: "POST",
7500
+ body: JSON.stringify({
7501
+ executionId,
7502
+ toolOutputs: { [toolName]: toolResult },
7503
+ streamResponse: true,
7504
+ debugMode: data.debugMode
7505
+ }),
7506
+ ...abortSignal ? { signal: abortSignal } : {}
7507
+ });
7508
+ } catch (error) {
7509
+ if (abortSignal?.aborted) return finishAborted(executionId);
7510
+ throw error;
7511
+ }
6419
7512
  if (!resumeResponse.ok) {
6420
7513
  const error = await resumeResponse.json().catch(() => ({ error: "Unknown error" }));
6421
7514
  throw new Error(error.error || `HTTP ${resumeResponse.status}`);
@@ -6433,6 +7526,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6433
7526
  currentBody = resumeResponse.body;
6434
7527
  continue;
6435
7528
  }
7529
+ if (abortSignal?.aborted) return finishAborted(lastSeenExecutionId);
6436
7530
  return null;
6437
7531
  }
6438
7532
  }
@@ -6704,7 +7798,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6704
7798
  reasons.push("Best candidate file has not been verified (read back after writing)");
6705
7799
  }
6706
7800
  if (state.verificationRequired && !state.lastVerificationPassed && !trace.verificationPassed) {
6707
- reasons.push("Verification has not passed \u2014 run a verification command (run_check) before completing");
7801
+ reasons.push(
7802
+ "Verification has not passed \u2014 run a verification command (run_check) before completing"
7803
+ );
6708
7804
  }
6709
7805
  return reasons.length > 0 ? reasons.join("; ") : "Completion gates not satisfied for the current workflow phase";
6710
7806
  }
@@ -6747,32 +7843,71 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6747
7843
  );
6748
7844
  }
6749
7845
  /**
6750
- * Compact old tool results based on context mode and window setting.
6751
- * Modifies messages in-place.
7846
+ * Resolve the replay window: the base index sliced off the full history and
7847
+ * the durable summary message that stands in for everything before it.
7848
+ * The base is honored ONLY when the latest summary actually exists —
7849
+ * slicing history with no summary substitute would silently drop prior
7850
+ * context (defensive against states that carried a base but lost their
7851
+ * summaries). The base is also clamped so a stale pointer (fork truncation,
7852
+ * trimmed legacy state) never slices past the end of the array.
7853
+ */
7854
+ resolveWindowReplay(state, messageCount) {
7855
+ const base = state.contextWindowBaseIndex ?? 0;
7856
+ if (!Number.isFinite(base) || base <= 0) {
7857
+ return { windowBase: 0, windowSummaryMessages: [] };
7858
+ }
7859
+ const summaries = state.contextCompactionSummaries;
7860
+ const latest = summaries?.[summaries.length - 1];
7861
+ if (!latest) {
7862
+ return { windowBase: 0, windowSummaryMessages: [] };
7863
+ }
7864
+ return {
7865
+ windowBase: Math.min(Math.floor(base), messageCount),
7866
+ windowSummaryMessages: [{ role: "system", content: latest.content }]
7867
+ };
7868
+ }
7869
+ /**
7870
+ * Derive the message view sent to the model based on context mode and window
7871
+ * setting. This never mutates persisted marathon history; masking/offloading
7872
+ * is a send-time view over the full-fidelity ledger/history.
6752
7873
  */
6753
- compactToolResults(messages, newToolMessages, taskName, mode, window) {
6754
- if (mode === "full-inline") return;
7874
+ deriveToolContextMessages(messages, taskName, mode, window) {
7875
+ if (mode === "full-inline") return [...messages];
7876
+ const maskMessage = (msg) => ({
7877
+ ...msg,
7878
+ toolResults: (msg.toolResults ?? []).map((tr) => this.compactOneResult(tr, taskName, mode))
7879
+ });
7880
+ const view = [...messages];
6755
7881
  if (window === "session") {
6756
- for (const msg of messages) {
7882
+ const lastUserIndex = view.reduce(
7883
+ (lastIndex, message, index) => message.role === "user" ? index : lastIndex,
7884
+ -1
7885
+ );
7886
+ for (let index = 0; index < view.length; index++) {
7887
+ if (lastUserIndex >= 0 && index > lastUserIndex) continue;
7888
+ const msg = view[index];
7889
+ if (!msg) continue;
6757
7890
  if (msg.role === "tool" && msg.toolResults) {
6758
- msg.toolResults = msg.toolResults.map((tr) => this.compactOneResult(tr, taskName, mode));
7891
+ view[index] = maskMessage(msg);
6759
7892
  }
6760
7893
  }
6761
7894
  } else {
6762
- const newToolResultCount = newToolMessages.filter((m) => m.role === "tool").length;
6763
- const keepInlineFromExisting = Math.max(0, window - newToolResultCount);
6764
7895
  const toolResultIndices = [];
6765
- for (let i = 0; i < messages.length; i++) {
6766
- if (messages[i].role === "tool" && messages[i].toolResults) {
7896
+ for (let i = 0; i < view.length; i++) {
7897
+ const message = view[i];
7898
+ if (message?.role === "tool" && message.toolResults) {
6767
7899
  toolResultIndices.push(i);
6768
7900
  }
6769
7901
  }
6770
- const compactUpTo = toolResultIndices.length - keepInlineFromExisting;
7902
+ const compactUpTo = toolResultIndices.length - window;
6771
7903
  for (let j = 0; j < compactUpTo && j < toolResultIndices.length; j++) {
6772
- const msg = messages[toolResultIndices[j]];
6773
- msg.toolResults = msg.toolResults.map((tr) => this.compactOneResult(tr, taskName, mode));
7904
+ const index = toolResultIndices[j];
7905
+ const msg = index === void 0 ? void 0 : view[index];
7906
+ if (!msg) continue;
7907
+ view[index] = maskMessage(msg);
6774
7908
  }
6775
7909
  }
7910
+ return view;
6776
7911
  }
6777
7912
  compactOneResult(tr, taskName, mode) {
6778
7913
  if (typeof tr.result === "string" && tr.result.startsWith("[")) return tr;
@@ -6808,10 +7943,20 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6808
7943
  }
6809
7944
  const slug = this.sanitizeTaskSlug(taskName || "task");
6810
7945
  const dir = `.runtype/marathons/${slug}/tool-outputs`;
6811
- fs.mkdirSync(dir, { recursive: true });
6812
7946
  const filePath = `${dir}/${toolCallId}.txt`;
6813
- fs.writeFileSync(filePath, resultStr, "utf-8");
6814
- return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
7947
+ try {
7948
+ if (!fs.existsSync(filePath)) {
7949
+ fs.mkdirSync(dir, { recursive: true });
7950
+ fs.writeFileSync(filePath, resultStr, "utf-8");
7951
+ }
7952
+ } catch {
7953
+ return result;
7954
+ }
7955
+ return buildSendViewOffloadMarker({
7956
+ toolName,
7957
+ charLength: resultStr.length,
7958
+ filePath
7959
+ });
6815
7960
  }
6816
7961
  getDefaultPlanPath(taskName) {
6817
7962
  return getDefaultPlanPath(taskName);
@@ -6933,6 +8078,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6933
8078
  const migratedPlanPath = workflowVariant === "external" && planPath === this.getDefaultPlanPath(taskName) ? this.getDefaultExternalReportPath(taskName) : planPath;
6934
8079
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
6935
8080
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
8081
+ const contextCompactionSummaries = (resumeState.contextCompactionSummaries ?? []).slice(-20);
6936
8082
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
6937
8083
  const bestCandidatePath = normalizedBestCandidatePath && !this.isMarathonArtifactPath(normalizedBestCandidatePath) ? normalizedBestCandidatePath : [...candidatePaths, ...recentReadPaths].sort(
6938
8084
  (left, right) => this.scoreCandidatePath(right) - this.scoreCandidatePath(left)
@@ -6952,10 +8098,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
6952
8098
  bestCandidateVerified: Boolean(resumeState.bestCandidateVerified),
6953
8099
  ...resumeState.verificationRequired !== void 0 ? { verificationRequired: resumeState.verificationRequired } : {},
6954
8100
  lastVerificationPassed: Boolean(resumeState.lastVerificationPassed),
6955
- ...resumeState.consecutiveBlockedVerificationSessions !== void 0 ? { consecutiveBlockedVerificationSessions: resumeState.consecutiveBlockedVerificationSessions } : {},
8101
+ ...resumeState.consecutiveBlockedVerificationSessions !== void 0 ? {
8102
+ consecutiveBlockedVerificationSessions: resumeState.consecutiveBlockedVerificationSessions
8103
+ } : {},
6956
8104
  ...resumeState.isCreationTask !== void 0 ? { isCreationTask: resumeState.isCreationTask } : {},
6957
8105
  ...resumeState.workflowVariant !== void 0 ? { workflowVariant: resumeState.workflowVariant } : {},
6958
8106
  ...resumeState.workflowState !== void 0 ? { workflowState: resumeState.workflowState } : {},
8107
+ ...contextCompactionSummaries.length ? { contextCompactionSummaries } : {},
8108
+ ...typeof resumeState.contextWindowBaseIndex === "number" && Number.isFinite(resumeState.contextWindowBaseIndex) && resumeState.contextWindowBaseIndex > 0 ? { contextWindowBaseIndex: Math.floor(resumeState.contextWindowBaseIndex) } : {},
6959
8109
  ...typeof resumeState.outputRoot === "string" && resumeState.outputRoot.trim() ? { outputRoot: resumeState.outputRoot.trim().replace(/\\/g, "/").replace(/\/+/g, "/") } : {}
6960
8110
  };
6961
8111
  }
@@ -7355,8 +8505,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7355
8505
  }
7356
8506
  buildStuckTurnRecoveryMessage(state, workflow) {
7357
8507
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
7358
- if (currentPhase?.buildRecoveryMessage) {
7359
- return currentPhase.buildRecoveryMessage(state);
8508
+ const phaseMessage = currentPhase?.buildRecoveryMessage?.(state);
8509
+ if (phaseMessage) return phaseMessage;
8510
+ const emptySessions = state.consecutiveEmptySessions || 0;
8511
+ if (shouldInjectEmptySessionNudge(workflow.stallPolicy, emptySessions)) {
8512
+ return buildEmptySessionNudge(emptySessions);
7360
8513
  }
7361
8514
  return void 0;
7362
8515
  }
@@ -7421,8 +8574,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7421
8574
  } : {},
7422
8575
  ...seededResumeState?.candidatePaths ? { candidatePaths: seededResumeState.candidatePaths } : {},
7423
8576
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
7424
- ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
8577
+ ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {},
8578
+ ...seededResumeState?.contextCompactionSummaries?.length ? { contextCompactionSummaries: seededResumeState.contextCompactionSummaries } : {},
8579
+ ...typeof seededResumeState?.contextWindowBaseIndex === "number" ? { contextWindowBaseIndex: seededResumeState.contextWindowBaseIndex } : {}
7425
8580
  };
8581
+ if (options.previousMessages && options.previousMessages.length > 0) {
8582
+ state.messages = options.previousMessages.map((message) => structuredClone(message));
8583
+ }
7426
8584
  state.workflowVariant = classifiedVariant;
7427
8585
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
7428
8586
  state.outputRoot = seededResumeState?.outputRoot ?? (state.isCreationTask ? "public/" : void 0);
@@ -7455,6 +8613,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7455
8613
  }
7456
8614
  }
7457
8615
  for (let session = 0; session < maxSessions; session++) {
8616
+ if (options.abortSignal?.aborted) {
8617
+ state.status = "paused";
8618
+ break;
8619
+ }
7458
8620
  const phaseAtSessionStart = state.workflowPhase;
7459
8621
  const sessionTrace = this.createEmptyToolTrace();
7460
8622
  const sessionLocalTools = this.wrapLocalToolsForTrace(
@@ -7473,6 +8635,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7473
8635
  state.originalMessage = options.message;
7474
8636
  }
7475
8637
  const queuedSteeringMessages = options.getQueuedUserMessages?.() ?? [];
8638
+ if (queuedSteeringMessages.length > 0) {
8639
+ state.consecutiveEmptySessions = 0;
8640
+ state.stallEscalationRequested = void 0;
8641
+ }
7476
8642
  const preparedSession = await this.prepareSessionContext(
7477
8643
  options.message,
7478
8644
  state,
@@ -7491,7 +8657,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7491
8657
  localTools: options.localTools,
7492
8658
  builtinToolSchemas,
7493
8659
  onContextCompaction: options.onContextCompaction,
7494
- onContextNotice: options.onContextNotice
8660
+ onContextNotice: options.onContextNotice,
8661
+ toolContextMode: options.toolContextMode || "hot-tail",
8662
+ toolWindow: options.toolWindow ?? "session"
7495
8663
  },
7496
8664
  queuedSteeringMessages
7497
8665
  );
@@ -7521,7 +8689,8 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7521
8689
  sessionCallbacks,
7522
8690
  {
7523
8691
  onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow),
7524
- shouldInterrupt: options.hasQueuedUserMessages
8692
+ shouldInterrupt: options.hasQueuedUserMessages,
8693
+ ...options.abortSignal ? { abortSignal: options.abortSignal } : {}
7525
8694
  },
7526
8695
  state.taskName
7527
8696
  );
@@ -7670,22 +8839,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7670
8839
  }
7671
8840
  }
7672
8841
  if (!state.messages) state.messages = [];
7673
- if (this.isCompactHistoryMessageSet(messages)) {
7674
- state.messages = [...messages];
7675
- } else if (state.messages.length > 0 && messages.length > state.messages.length) {
7676
- const newMessages = messages.slice(state.messages.length);
7677
- state.messages.push(...newMessages);
7678
- } else {
8842
+ const sentUserMessage = messages[messages.length - 1];
8843
+ if (sentUserMessage?.role === "user") {
8844
+ state.messages.push(sentUserMessage);
8845
+ } else if (state.messages.length === 0) {
7679
8846
  state.messages.push(...messages);
7680
8847
  }
7681
8848
  if (sessionToolMessages.length > 0) {
7682
- this.compactToolResults(
7683
- state.messages,
7684
- sessionToolMessages,
7685
- state.taskName,
7686
- options.toolContextMode || "hot-tail",
7687
- options.toolWindow ?? "session"
7688
- );
7689
8849
  state.messages.push(...sessionToolMessages);
7690
8850
  }
7691
8851
  const assistantContent = effectiveSessionOutput || `[Session ${session + 1} completed (${sessionResult.stopReason}). No text output captured.]`;
@@ -7705,7 +8865,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7705
8865
  workflow
7706
8866
  );
7707
8867
  if (detectedTaskCompletion && !acceptedTaskCompletion) {
7708
- state.lastCompletionRejectionReason = this.computeCompletionRejectionReason(state, sessionTrace);
8868
+ state.lastCompletionRejectionReason = this.computeCompletionRejectionReason(
8869
+ state,
8870
+ sessionTrace
8871
+ );
7709
8872
  if (state.verificationRequired && !state.lastVerificationPassed && !sessionTrace.verificationPassed && !sessionTrace.verificationAttempted) {
7710
8873
  state.consecutiveBlockedVerificationSessions = (state.consecutiveBlockedVerificationSessions || 0) + 1;
7711
8874
  if ((state.consecutiveBlockedVerificationSessions || 0) >= 2) {
@@ -7716,32 +8879,36 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7716
8879
  } else {
7717
8880
  state.lastCompletionRejectionReason = void 0;
7718
8881
  }
7719
- const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted;
8882
+ const sessionHadActions = sessionTrace.wroteFiles || sessionTrace.readFiles || sessionTrace.discoveryPerformed || sessionTrace.verificationAttempted || options.hasQueuedUserMessages?.() === true;
7720
8883
  if (sessionHadActions) {
7721
8884
  state.consecutiveEmptySessions = 0;
8885
+ state.stallEscalationRequested = void 0;
7722
8886
  } else {
7723
8887
  state.consecutiveEmptySessions = (state.consecutiveEmptySessions || 0) + 1;
8888
+ if (shouldRequestModelEscalation(workflow.stallPolicy, state.consecutiveEmptySessions)) {
8889
+ state.stallEscalationRequested = true;
8890
+ }
7724
8891
  }
7725
8892
  if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
7726
8893
  const currentPhase = workflow.phases.find((p) => p.name === state.workflowPhase);
7727
- const gatesSatisfied = currentPhase?.canAcceptCompletion ? currentPhase.canAcceptCompletion(state, sessionTrace) : true;
8894
+ const gatesSatisfied = currentPhase?.canAcceptCompletion ? currentPhase.canAcceptCompletion(
8895
+ state,
8896
+ sessionTrace
8897
+ ) : true;
7728
8898
  if (gatesSatisfied) {
7729
8899
  state.status = "complete";
7730
8900
  }
7731
8901
  } else if (sessionResult.stopReason === "error") {
7732
8902
  if (_AgentsEndpoint.isRetryableSessionError(sessionResult.error) && consecutiveServerNetworkErrors < maxServerNetworkRetries) {
7733
8903
  consecutiveServerNetworkErrors++;
7734
- const delayMs = Math.min(
7735
- 5e3 * Math.pow(2, consecutiveServerNetworkErrors - 1),
7736
- 3e4
7737
- );
8904
+ const delayMs = Math.min(5e3 * Math.pow(2, consecutiveServerNetworkErrors - 1), 3e4);
7738
8905
  const delaySec = Math.round(delayMs / 1e3);
7739
8906
  await this.emitContextNotice(options.onContextNotice, {
7740
8907
  kind: "server_network_retry",
7741
8908
  sessionIndex: session,
7742
8909
  message: `Server network error: ${sessionResult.error}. Retrying in ${delaySec}s (attempt ${consecutiveServerNetworkErrors}/${maxServerNetworkRetries})...`
7743
8910
  });
7744
- await new Promise((resolve) => setTimeout(resolve, delayMs));
8911
+ await sleepWithAbort(delayMs, options.abortSignal);
7745
8912
  } else {
7746
8913
  state.status = "error";
7747
8914
  }
@@ -7749,13 +8916,16 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7749
8916
  state.status = "budget_exceeded";
7750
8917
  } else if (acceptedTaskCompletion) {
7751
8918
  state.status = "complete";
7752
- } else if ((state.consecutiveEmptySessions || 0) >= 3) {
8919
+ } else if ((state.consecutiveEmptySessions || 0) >= resolveStallStopAfter(workflow.stallPolicy)) {
7753
8920
  state.status = "stalled";
7754
8921
  } else if (maxCost && state.totalCost >= maxCost) {
7755
8922
  state.status = "budget_exceeded";
7756
8923
  } else if (session + 1 >= maxSessions) {
7757
8924
  state.status = "max_sessions";
7758
8925
  }
8926
+ if (options.abortSignal?.aborted) {
8927
+ state.status = "paused";
8928
+ }
7759
8929
  if (options.trackProgress) {
7760
8930
  recordId = await this.syncProgressRecord(state, recordId);
7761
8931
  }
@@ -7832,6 +9002,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7832
9002
  return 0;
7833
9003
  }
7834
9004
  }
9005
+ extractDeclaredToolResultChars(value) {
9006
+ return extractDeclaredToolResultChars(value);
9007
+ }
7835
9008
  estimateMessageContentTokens(content) {
7836
9009
  if (typeof content === "string") return this.estimateTextTokens(content);
7837
9010
  return content.reduce((total, part) => {
@@ -7850,12 +9023,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7850
9023
  0
7851
9024
  );
7852
9025
  }
7853
- estimateToolResultTokens(toolResults) {
9026
+ estimateToolResultTokens(toolResults, options) {
7854
9027
  if (!toolResults || toolResults.length === 0) return 0;
7855
- return toolResults.reduce(
7856
- (sum, toolResult) => sum + 12 + this.estimateTextTokens(toolResult.toolName) + this.estimateUnknownTokens(toolResult.result),
7857
- 0
7858
- );
9028
+ return toolResults.reduce((sum, toolResult) => {
9029
+ const resultTokens = this.estimateUnknownTokens(toolResult.result);
9030
+ const declaredChars = options?.useDeclaredSize ? this.extractDeclaredToolResultChars(toolResult.result) : void 0;
9031
+ const declaredTokens = typeof declaredChars === "number" ? Math.ceil(declaredChars / 4) : 0;
9032
+ return sum + 12 + this.estimateTextTokens(toolResult.toolName) + Math.max(resultTokens, declaredTokens);
9033
+ }, 0);
7859
9034
  }
7860
9035
  estimateMessageTokens(message) {
7861
9036
  return 6 + this.estimateMessageContentTokens(message.content) + this.estimateToolCallTokens(message.toolCalls) + this.estimateToolResultTokens(message.toolResults);
@@ -7863,13 +9038,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7863
9038
  estimateConversationTokens(messages) {
7864
9039
  return messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
7865
9040
  }
7866
- estimateConversationBreakdown(messages) {
9041
+ estimateConversationBreakdown(messages, options) {
7867
9042
  let historyTokens = 0;
7868
9043
  let toolOutputTokens = 0;
7869
9044
  for (const message of messages) {
7870
9045
  const contentTokens = this.estimateMessageContentTokens(message.content);
7871
9046
  const toolCallTokens = this.estimateToolCallTokens(message.toolCalls);
7872
- const toolResultTokens = this.estimateToolResultTokens(message.toolResults);
9047
+ const toolResultTokens = this.estimateToolResultTokens(message.toolResults, {
9048
+ useDeclaredSize: options?.useDeclaredToolResultSizes
9049
+ });
7873
9050
  const messageTotal = 6 + contentTokens + toolCallTokens + toolResultTokens;
7874
9051
  if (message.role === "tool") {
7875
9052
  toolOutputTokens += messageTotal;
@@ -7923,13 +9100,24 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7923
9100
  const compactInstructions = config.compactInstructions;
7924
9101
  return typeof compactInstructions === "string" && compactInstructions.trim() ? compactInstructions.trim() : void 0;
7925
9102
  }
7926
- extractArtifactReferences(state) {
9103
+ extractArtifactReferencesFromMessages(messages = []) {
7927
9104
  const references = /* @__PURE__ */ new Set();
7928
9105
  const offloadPrefix = "[Output saved to ";
7929
- for (const message of state.messages ?? []) {
9106
+ const ledgerArtifactPrefix = LEDGER_ARTIFACT_LINE_PREFIX;
9107
+ const savedToPattern = /saved to\s+([^—\]\n]+?)(?:\s+—|\]|\n|$)/gi;
9108
+ for (const message of messages) {
7930
9109
  if (!message.toolResults) continue;
7931
9110
  for (const toolResult of message.toolResults) {
7932
9111
  if (typeof toolResult.result !== "string") continue;
9112
+ for (const line of toolResult.result.split("\n")) {
9113
+ if (line.startsWith(ledgerArtifactPrefix)) {
9114
+ references.add(line.slice(ledgerArtifactPrefix.length).trim());
9115
+ }
9116
+ }
9117
+ for (const match of toolResult.result.matchAll(savedToPattern)) {
9118
+ const pathText = match[1]?.trim();
9119
+ if (pathText) references.add(pathText);
9120
+ }
7933
9121
  let startIndex = 0;
7934
9122
  while (startIndex < toolResult.result.length) {
7935
9123
  const prefixIndex = toolResult.result.indexOf(offloadPrefix, startIndex);
@@ -7947,6 +9135,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7947
9135
  }
7948
9136
  }
7949
9137
  }
9138
+ return Array.from(references);
9139
+ }
9140
+ extractArtifactReferences(state, additionalReferences = []) {
9141
+ const references = /* @__PURE__ */ new Set([
9142
+ ...this.extractArtifactReferencesFromMessages(state.messages ?? []),
9143
+ ...additionalReferences
9144
+ ]);
7950
9145
  if (state.planPath) {
7951
9146
  references.add(state.planPath);
7952
9147
  }
@@ -7954,6 +9149,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7954
9149
  }
7955
9150
  buildContextBudgetBreakdown(details) {
7956
9151
  const conversationBreakdown = this.estimateConversationBreakdown(details.historyMessages);
9152
+ const sourceConversationBreakdown = details.sourceHistoryMessages ? this.estimateConversationBreakdown(details.sourceHistoryMessages, {
9153
+ useDeclaredToolResultSizes: true
9154
+ }) : conversationBreakdown;
7957
9155
  const currentTurnTokens = this.estimateTextTokens(details.currentTurnContent);
7958
9156
  const toolDefinitionTokens = this.estimateToolDefinitionTokens(
7959
9157
  details.localTools,
@@ -7970,15 +9168,26 @@ ${details.summaryText}
7970
9168
 
7971
9169
  Do NOT redo any of the above work.`
7972
9170
  ) : void 0;
9171
+ const sendEstimatedInputTokens = conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens;
9172
+ const estimatedInputTokens = sourceConversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens;
9173
+ const toolOutputReductionTokens = Math.max(
9174
+ 0,
9175
+ sourceConversationBreakdown.toolOutputTokens - conversationBreakdown.toolOutputTokens
9176
+ );
7973
9177
  return {
7974
- historyTokens: conversationBreakdown.historyTokens,
7975
- toolOutputTokens: conversationBreakdown.toolOutputTokens,
9178
+ historyTokens: sourceConversationBreakdown.historyTokens,
9179
+ toolOutputTokens: sourceConversationBreakdown.toolOutputTokens,
7976
9180
  currentTurnTokens,
7977
9181
  toolDefinitionTokens,
7978
9182
  ...summaryTokens ? { summaryTokens } : {},
7979
9183
  ...reservedOutputTokens ? { reservedOutputTokens } : {},
7980
9184
  ...effectiveInputBudgetTokens ? { effectiveInputBudgetTokens } : {},
7981
- estimatedInputTokens: conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens
9185
+ ...toolOutputReductionTokens > 0 ? {
9186
+ sendEstimatedInputTokens,
9187
+ sendToolOutputTokens: conversationBreakdown.toolOutputTokens,
9188
+ toolOutputReductionTokens
9189
+ } : {},
9190
+ estimatedInputTokens
7982
9191
  };
7983
9192
  }
7984
9193
  async emitContextCompactionEvent(onContextCompaction, event) {
@@ -8018,16 +9227,33 @@ Do NOT redo any of the above work.`
8018
9227
  state,
8019
9228
  userContent,
8020
9229
  details.compactInstructions,
8021
- details.mode
9230
+ details.mode,
9231
+ details.artifactReferences
8022
9232
  );
9233
+ const summaryMessage = compactMessages[0];
9234
+ if (summaryMessage?.role === "system" && typeof summaryMessage.content === "string") {
9235
+ const existingSummaries = state.contextCompactionSummaries ?? [];
9236
+ state.contextCompactionSummaries = [
9237
+ ...existingSummaries,
9238
+ {
9239
+ id: `ctx_${sessionIndex + 1}_${existingSummaries.length + 1}`,
9240
+ sessionIndex: sessionIndex + 1,
9241
+ mode: details.mode,
9242
+ strategy: details.strategy,
9243
+ content: summaryMessage.content,
9244
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
9245
+ }
9246
+ ].slice(-20);
9247
+ }
9248
+ state.contextWindowBaseIndex = state.messages?.length ?? 0;
8023
9249
  await this.emitContextCompactionEvent(details.onContextCompaction, {
8024
9250
  phase: "complete",
8025
9251
  ...baseEvent
8026
9252
  });
8027
9253
  return compactMessages;
8028
9254
  }
8029
- buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto") {
8030
- const summary = this.generateCompactSummary(state, compactInstructions);
9255
+ buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto", artifactReferences) {
9256
+ const summary = this.generateCompactSummary(state, compactInstructions, artifactReferences);
8031
9257
  const prefix = mode === "forced" ? this.getForcedCompactionSummaryPrefix(state) : _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX;
8032
9258
  return [
8033
9259
  {
@@ -8044,16 +9270,11 @@ Do NOT redo any of the above work.`
8044
9270
  }
8045
9271
  ];
8046
9272
  }
8047
- isCompactHistoryMessageSet(messages) {
8048
- if (messages.length === 0) return false;
8049
- const firstMessage = messages[0];
8050
- 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));
8051
- }
8052
9273
  /**
8053
9274
  * Generate a compact summary of prior work for continuation context.
8054
9275
  * Used when compact mode is enabled to keep token usage low.
8055
9276
  */
8056
- generateCompactSummary(state, compactInstructions) {
9277
+ generateCompactSummary(state, compactInstructions, additionalArtifactReferences = []) {
8057
9278
  const recentSessions = (state.sessions ?? []).slice(-5);
8058
9279
  const sessionSummaries = recentSessions.map(
8059
9280
  (session) => `- Session ${session.index}: ${session.stopReason} ($${session.cost.toFixed(4)}) ${session.outputPreview.slice(0, 160)}`
@@ -8067,7 +9288,7 @@ Do NOT redo any of the above work.`
8067
9288
  )
8068
9289
  ).slice(0, 8);
8069
9290
  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.";
8070
- const artifactReferences = this.extractArtifactReferences(state);
9291
+ const artifactReferences = this.extractArtifactReferences(state, additionalArtifactReferences);
8071
9292
  const pendingNextStep = state.lastStopReason === "complete" ? "Confirm nothing else remains before declaring the task complete." : `Continue the ${state.workflowPhase || "research"} phase without redoing prior work.`;
8072
9293
  const instructions = compactInstructions || this.buildDefaultCompactInstructions();
8073
9294
  return [
@@ -8282,14 +9503,23 @@ Do NOT redo any of the above work.`
8282
9503
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
8283
9504
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
8284
9505
  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.`;
8285
- const steeringLines = steeringMessages && steeringMessages.length > 0 ? [
8286
- "--- User steering (queued during the previous session) ---",
8287
- ...steeringMessages,
8288
- ""
8289
- ] : [];
9506
+ const steeringLines = steeringMessages && steeringMessages.length > 0 ? ["--- User steering (queued during the previous session) ---", ...steeringMessages, ""] : [];
8290
9507
  if (continuationContext && sessionIndex === 0) {
8291
- const replayHistoryMessages = this.sanitizeReplayHistoryMessages(
8292
- continuationContext.previousMessages
9508
+ const resumeWindow = this.resolveWindowReplay(
9509
+ state,
9510
+ continuationContext.previousMessages.length
9511
+ );
9512
+ const sourceReplayHistoryMessages = [
9513
+ ...resumeWindow.windowSummaryMessages,
9514
+ ...this.sanitizeReplayHistoryMessages(
9515
+ continuationContext.previousMessages.slice(resumeWindow.windowBase)
9516
+ )
9517
+ ];
9518
+ const replayHistoryMessages = this.deriveToolContextMessages(
9519
+ sourceReplayHistoryMessages,
9520
+ state.taskName,
9521
+ compactionOptions?.toolContextMode || "hot-tail",
9522
+ compactionOptions?.toolWindow ?? "session"
8293
9523
  );
8294
9524
  const continuationGuardrail = this.buildContinuationGuardrail(state);
8295
9525
  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.";
@@ -8313,9 +9543,15 @@ Do NOT redo any of the above work.`
8313
9543
  content: userContent
8314
9544
  }
8315
9545
  ];
8316
- const summaryText = this.generateCompactSummary(state, compactInstructions);
9546
+ const replayArtifactReferences = this.extractArtifactReferencesFromMessages(replayHistoryMessages);
9547
+ const summaryText = this.generateCompactSummary(
9548
+ state,
9549
+ compactInstructions,
9550
+ replayArtifactReferences
9551
+ );
8317
9552
  const breakdown = this.buildContextBudgetBreakdown({
8318
9553
  historyMessages: replayHistoryMessages,
9554
+ sourceHistoryMessages: sourceReplayHistoryMessages,
8319
9555
  currentTurnContent: userContent,
8320
9556
  localTools: compactionOptions?.localTools,
8321
9557
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -8341,7 +9577,8 @@ Do NOT redo any of the above work.`
8341
9577
  reservedOutputTokens: breakdown.reservedOutputTokens,
8342
9578
  breakdown,
8343
9579
  onContextCompaction: compactionOptions?.onContextCompaction,
8344
- compactInstructions
9580
+ compactInstructions,
9581
+ artifactReferences: replayArtifactReferences
8345
9582
  }
8346
9583
  ),
8347
9584
  requestContextManagement
@@ -8365,7 +9602,8 @@ Do NOT redo any of the above work.`
8365
9602
  reservedOutputTokens: breakdown.reservedOutputTokens,
8366
9603
  breakdown,
8367
9604
  onContextCompaction: compactionOptions?.onContextCompaction,
8368
- compactInstructions
9605
+ compactInstructions,
9606
+ artifactReferences: replayArtifactReferences
8369
9607
  }
8370
9608
  ),
8371
9609
  requestContextManagement
@@ -8416,23 +9654,45 @@ Do NOT redo any of the above work.`
8416
9654
  "Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
8417
9655
  ].join("\n");
8418
9656
  const MAX_HISTORY_MESSAGES = 60;
8419
- let historyMessages = this.sanitizeReplayHistoryMessages(state.messages);
8420
- if (historyMessages.length > MAX_HISTORY_MESSAGES) {
8421
- const trimmedHistory = this.trimReplayHistoryMessages(historyMessages, MAX_HISTORY_MESSAGES);
8422
- historyMessages = trimmedHistory.historyMessages;
9657
+ const { windowBase, windowSummaryMessages } = this.resolveWindowReplay(
9658
+ state,
9659
+ state.messages.length
9660
+ );
9661
+ let sourceHistoryMessages = this.sanitizeReplayHistoryMessages(
9662
+ state.messages.slice(windowBase)
9663
+ );
9664
+ if (sourceHistoryMessages.length > MAX_HISTORY_MESSAGES) {
9665
+ const trimmedHistory = this.trimReplayHistoryMessages(
9666
+ sourceHistoryMessages,
9667
+ MAX_HISTORY_MESSAGES
9668
+ );
9669
+ sourceHistoryMessages = trimmedHistory.historyMessages;
8423
9670
  if (trimmedHistory.trimmedCount > 0) {
8424
- historyMessages = [
9671
+ sourceHistoryMessages = [
8425
9672
  {
8426
9673
  role: "system",
8427
9674
  content: `[${trimmedHistory.trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
8428
9675
  },
8429
- ...historyMessages
9676
+ ...sourceHistoryMessages
8430
9677
  ];
8431
9678
  }
8432
9679
  }
8433
- const summaryText = this.generateCompactSummary(state, compactInstructions);
9680
+ sourceHistoryMessages = [...windowSummaryMessages, ...sourceHistoryMessages];
9681
+ const historyMessages = this.deriveToolContextMessages(
9682
+ sourceHistoryMessages,
9683
+ state.taskName,
9684
+ compactionOptions?.toolContextMode || "hot-tail",
9685
+ compactionOptions?.toolWindow ?? "session"
9686
+ );
9687
+ const historyArtifactReferences = this.extractArtifactReferencesFromMessages(historyMessages);
9688
+ const summaryText = this.generateCompactSummary(
9689
+ state,
9690
+ compactInstructions,
9691
+ historyArtifactReferences
9692
+ );
8434
9693
  const breakdown = this.buildContextBudgetBreakdown({
8435
9694
  historyMessages,
9695
+ sourceHistoryMessages,
8436
9696
  currentTurnContent: continuationContent,
8437
9697
  localTools: compactionOptions?.localTools,
8438
9698
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -8459,7 +9719,8 @@ Do NOT redo any of the above work.`
8459
9719
  reservedOutputTokens: breakdown.reservedOutputTokens,
8460
9720
  breakdown,
8461
9721
  onContextCompaction: compactionOptions?.onContextCompaction,
8462
- compactInstructions
9722
+ compactInstructions,
9723
+ artifactReferences: historyArtifactReferences
8463
9724
  }
8464
9725
  ),
8465
9726
  requestContextManagement
@@ -8933,6 +10194,52 @@ var BillingEndpoint = class {
8933
10194
  return this.client.get("/billing/spend-analytics", params);
8934
10195
  }
8935
10196
  };
10197
+ var AppsEndpoint = class {
10198
+ constructor(client) {
10199
+ this.client = client;
10200
+ }
10201
+ /** List apps for the authenticated owner, newest first. */
10202
+ async list() {
10203
+ return this.client.get("/apps");
10204
+ }
10205
+ /** Get an app by id, including its URL and active version pointer. */
10206
+ async get(id) {
10207
+ return this.client.get(`/apps/${id}`);
10208
+ }
10209
+ /** Create an app. A client token scoped to the app origin is auto-provisioned. */
10210
+ async create(data) {
10211
+ return this.client.post("/apps", data);
10212
+ }
10213
+ /** Update name, description, visibility, or status (suspended serves 410). */
10214
+ async update(id, data) {
10215
+ return this.client.patch(`/apps/${id}`, data);
10216
+ }
10217
+ /** Delete an app, its versions, and its hosting. Irreversible. */
10218
+ async delete(id) {
10219
+ return this.client.delete(`/apps/${id}`);
10220
+ }
10221
+ /** List an app's versions, newest first. */
10222
+ async listVersions(id) {
10223
+ return this.client.get(`/apps/${id}/versions`);
10224
+ }
10225
+ /** Upload a zipped bundle (raw application/zip body) as a new version. */
10226
+ async uploadVersion(id, zipBytes) {
10227
+ return this.client.postBinary(`/apps/${id}/versions`, zipBytes, "application/zip");
10228
+ }
10229
+ /**
10230
+ * Upload a bundle from in-memory file maps (the API zips server-side).
10231
+ * Text files in `files`, binary files base64-encoded in `filesBase64`.
10232
+ */
10233
+ async uploadVersionFiles(id, data) {
10234
+ return this.client.post(`/apps/${id}/versions`, data);
10235
+ }
10236
+ /** Activate an uploaded version (deploy or rollback). */
10237
+ async activate(id, versionId) {
10238
+ return this.client.post(`/apps/${id}/activate`, {
10239
+ versionId
10240
+ });
10241
+ }
10242
+ };
8936
10243
 
8937
10244
  // src/client.ts
8938
10245
  function isObjectRecord(value) {
@@ -8975,6 +10282,7 @@ var RuntypeClient2 = class {
8975
10282
  this.clientTokens = new ClientTokensEndpoint(this);
8976
10283
  this.agents = new AgentsEndpoint(this);
8977
10284
  this.secrets = new SecretsEndpoint(this);
10285
+ this.apps = new AppsEndpoint(this);
8978
10286
  this.schedules = new SchedulesEndpoint(this);
8979
10287
  this.surfaces = new SurfacesEndpoint(this);
8980
10288
  this.conversations = new ConversationsEndpoint(this);
@@ -9021,7 +10329,7 @@ var RuntypeClient2 = class {
9021
10329
  clearApiKey() {
9022
10330
  delete this.headers.Authorization;
9023
10331
  }
9024
- async runWithLocalTools(request, localTools, arg3, arg4) {
10332
+ async runWithLocalTools(request2, localTools, arg3, arg4) {
9025
10333
  const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
9026
10334
  const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
9027
10335
  const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
@@ -9035,12 +10343,12 @@ var RuntypeClient2 = class {
9035
10343
  ...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
9036
10344
  })) : [];
9037
10345
  const modifiedRequest = {
9038
- ...request,
10346
+ ...request2,
9039
10347
  ...derivedClientTools.length > 0 ? {
9040
- clientTools: [...request.clientTools ?? [], ...derivedClientTools]
10348
+ clientTools: [...request2.clientTools ?? [], ...derivedClientTools]
9041
10349
  } : {},
9042
10350
  options: {
9043
- ...request.options || {},
10351
+ ...request2.options || {},
9044
10352
  streamResponse: isStreaming
9045
10353
  }
9046
10354
  };
@@ -9056,13 +10364,12 @@ var RuntypeClient2 = class {
9056
10364
  onFlowComplete: (event) => callbacks?.onFlowComplete?.(event),
9057
10365
  onError: (error) => callbacks?.onError?.(error)
9058
10366
  };
9059
- const { streamEvents: streamEvents2, stepDeltaText: stepDeltaText2, stepDisplayName: stepDisplayName2, flowErrorMessage: flowErrorMessage2 } = await Promise.resolve().then(() => (init_stream_utils(), stream_utils_exports));
9060
10367
  const summary = {
9061
10368
  results: /* @__PURE__ */ new Map(),
9062
10369
  success: true
9063
10370
  };
9064
10371
  try {
9065
- for await (const event of streamEvents2(response)) {
10372
+ for await (const event of streamEvents(response)) {
9066
10373
  collectLocalToolAwait(pausedTools, event);
9067
10374
  switch (event.type) {
9068
10375
  case "flow_start":
@@ -9072,10 +10379,10 @@ var RuntypeClient2 = class {
9072
10379
  wrappedCallbacks.onStepStart?.(event);
9073
10380
  break;
9074
10381
  case "step_delta":
9075
- wrappedCallbacks.onStepDelta?.(stepDeltaText2(event), event);
10382
+ wrappedCallbacks.onStepDelta?.(stepDeltaText(event), event);
9076
10383
  break;
9077
10384
  case "step_complete": {
9078
- summary.results?.set(stepDisplayName2(event), event.result);
10385
+ summary.results?.set(stepDisplayName(event), event.result);
9079
10386
  wrappedCallbacks.onStepComplete?.(event.result, event);
9080
10387
  break;
9081
10388
  }
@@ -9083,7 +10390,7 @@ var RuntypeClient2 = class {
9083
10390
  wrappedCallbacks.onFlowComplete?.(event);
9084
10391
  break;
9085
10392
  case "flow_error":
9086
- wrappedCallbacks.onError?.(new Error(flowErrorMessage2(event)));
10393
+ wrappedCallbacks.onError?.(new Error(flowErrorMessage(event)));
9087
10394
  break;
9088
10395
  }
9089
10396
  }
@@ -9162,7 +10469,8 @@ var RuntypeClient2 = class {
9162
10469
  return [toolName, await handler(parameters)];
9163
10470
  } catch (error) {
9164
10471
  throw new Error(
9165
- `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`
10472
+ `Error executing local tool "${toolName}": ${error instanceof Error ? error.message : String(error)}`,
10473
+ { cause: error }
9166
10474
  );
9167
10475
  }
9168
10476
  })
@@ -9226,6 +10534,21 @@ var RuntypeClient2 = class {
9226
10534
  });
9227
10535
  return transformResponse(response);
9228
10536
  }
10537
+ /**
10538
+ * POST request with a raw binary body (e.g. application/zip bundle uploads).
10539
+ */
10540
+ async postBinary(path, body, contentType) {
10541
+ const url = this.buildUrl(path);
10542
+ const headers = { ...this.headers, "Content-Type": contentType };
10543
+ const response = await this.makeRequest(url, {
10544
+ method: "POST",
10545
+ headers,
10546
+ // TS 5.7 types Uint8Array over ArrayBufferLike, which no longer
10547
+ // overlaps DOM BodyInit; the runtime value is a valid fetch body.
10548
+ body
10549
+ });
10550
+ return transformResponse(response);
10551
+ }
9229
10552
  /**
9230
10553
  * Generic request that returns raw Response for streaming
9231
10554
  */
@@ -9326,7 +10649,7 @@ var RuntypeClient2 = class {
9326
10649
  } catch (error) {
9327
10650
  if (timeoutId) clearTimeout(timeoutId);
9328
10651
  if (timeoutId && error instanceof Error && error.name === "AbortError") {
9329
- throw new Error(`Request timeout after ${this.timeout}ms`);
10652
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
9330
10653
  }
9331
10654
  throw error;
9332
10655
  }
@@ -9335,8 +10658,18 @@ var RuntypeClient2 = class {
9335
10658
  * Make HTTP request that returns raw Response (for streaming)
9336
10659
  */
9337
10660
  async makeRawRequest(url, options) {
9338
- const controller = this.timeout === null ? null : new AbortController();
10661
+ const callerSignal = options.signal ?? null;
10662
+ const controller = this.timeout === null && !callerSignal ? null : new AbortController();
9339
10663
  const timeoutId = controller && this.timeout !== null ? setTimeout(() => controller.abort(), this.timeout) : null;
10664
+ if (callerSignal && controller) {
10665
+ if (callerSignal.aborted) {
10666
+ controller.abort(callerSignal.reason);
10667
+ } else {
10668
+ callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason), {
10669
+ once: true
10670
+ });
10671
+ }
10672
+ }
9340
10673
  try {
9341
10674
  const response = await fetch(url, {
9342
10675
  ...options,
@@ -9349,8 +10682,8 @@ var RuntypeClient2 = class {
9349
10682
  return response;
9350
10683
  } catch (error) {
9351
10684
  if (timeoutId) clearTimeout(timeoutId);
9352
- if (timeoutId && error instanceof Error && error.name === "AbortError") {
9353
- throw new Error(`Request timeout after ${this.timeout}ms`);
10685
+ if (timeoutId && error instanceof Error && error.name === "AbortError" && !callerSignal?.aborted) {
10686
+ throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });
9354
10687
  }
9355
10688
  throw error;
9356
10689
  }
@@ -9396,9 +10729,6 @@ function createClient(config) {
9396
10729
  return new RuntypeClient2(config);
9397
10730
  }
9398
10731
 
9399
- // src/index.ts
9400
- init_stream_utils();
9401
-
9402
10732
  // src/batch-builder.ts
9403
10733
  var BatchBuilder = class {
9404
10734
  constructor() {
@@ -9456,20 +10786,20 @@ var BatchBuilder = class {
9456
10786
  if (!this.recordType) {
9457
10787
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
9458
10788
  }
9459
- const request = {
10789
+ const request2 = {
9460
10790
  flowId: this.flowId,
9461
10791
  recordType: this.recordType
9462
10792
  };
9463
10793
  if (Object.keys(this.batchOptions).length > 0) {
9464
- request.options = this.batchOptions;
10794
+ request2.options = this.batchOptions;
9465
10795
  }
9466
10796
  if (this.filterConfig) {
9467
- request.filter = this.filterConfig;
10797
+ request2.filter = this.filterConfig;
9468
10798
  }
9469
10799
  if (this.limitConfig !== void 0) {
9470
- request.limit = this.limitConfig;
10800
+ request2.limit = this.limitConfig;
9471
10801
  }
9472
- return request;
10802
+ return request2;
9473
10803
  }
9474
10804
  /**
9475
10805
  * Execute the batch operation
@@ -9626,32 +10956,32 @@ var EvalBuilder = class {
9626
10956
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
9627
10957
  );
9628
10958
  }
9629
- const request = {};
10959
+ const request2 = {};
9630
10960
  if (this.flowId) {
9631
- request.flowId = this.flowId;
10961
+ request2.flowId = this.flowId;
9632
10962
  } else if (this.virtualFlow) {
9633
- request.flow = this.virtualFlow;
10963
+ request2.flow = this.virtualFlow;
9634
10964
  }
9635
10965
  if (this.recordType) {
9636
- request.recordType = this.recordType;
10966
+ request2.recordType = this.recordType;
9637
10967
  } else if (this.inlineRecords) {
9638
- request.records = this.inlineRecords;
10968
+ request2.records = this.inlineRecords;
9639
10969
  }
9640
10970
  if (this.modelOverrides) {
9641
- request.modelOverrides = this.modelOverrides;
10971
+ request2.modelOverrides = this.modelOverrides;
9642
10972
  } else if (this.modelConfigs) {
9643
- request.modelConfigs = this.modelConfigs;
10973
+ request2.modelConfigs = this.modelConfigs;
9644
10974
  }
9645
10975
  if (Object.keys(this.evalOptions).length > 0) {
9646
- request.options = this.evalOptions;
10976
+ request2.options = this.evalOptions;
9647
10977
  }
9648
10978
  if (this.filterConfig) {
9649
- request.filter = this.filterConfig;
10979
+ request2.filter = this.filterConfig;
9650
10980
  }
9651
10981
  if (this.limitConfig !== void 0) {
9652
- request.limit = this.limitConfig;
10982
+ request2.limit = this.limitConfig;
9653
10983
  }
9654
- return request;
10984
+ return request2;
9655
10985
  }
9656
10986
  /**
9657
10987
  * Execute the evaluation
@@ -10117,10 +11447,14 @@ var STEP_TYPE_TO_METHOD = {
10117
11447
  "memory-summary": "memorySummary"
10118
11448
  };
10119
11449
  export {
11450
+ AgentDriftError,
11451
+ AgentEnsureConflictError,
10120
11452
  AgentVersionsEndpoint,
10121
11453
  AgentsEndpoint,
11454
+ AgentsNamespace,
10122
11455
  AnalyticsEndpoint,
10123
11456
  ApiKeysEndpoint,
11457
+ AppsEndpoint,
10124
11458
  BatchBuilder,
10125
11459
  BatchesNamespace,
10126
11460
  BillingEndpoint,
@@ -10131,18 +11465,23 @@ export {
10131
11465
  ClientTokensEndpoint,
10132
11466
  ContextTemplatesEndpoint,
10133
11467
  ConversationsEndpoint,
11468
+ DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
11469
+ DEFAULT_STALL_STOP_AFTER,
10134
11470
  DispatchEndpoint,
10135
11471
  EvalBuilder,
10136
11472
  EvalEndpoint,
10137
11473
  EvalRunner,
10138
11474
  EvalsNamespace,
10139
11475
  FlowBuilder,
11476
+ FlowDriftError,
11477
+ FlowEnsureConflictError,
10140
11478
  FlowResult,
10141
11479
  FlowStepsEndpoint,
10142
11480
  FlowVersionsEndpoint,
10143
11481
  FlowsEndpoint,
10144
11482
  FlowsNamespace,
10145
11483
  IntegrationsEndpoint,
11484
+ LEDGER_ARTIFACT_LINE_PREFIX,
10146
11485
  LogsEndpoint,
10147
11486
  ModelConfigsEndpoint,
10148
11487
  PromptRunner,
@@ -10165,22 +11504,47 @@ export {
10165
11504
  UsersEndpoint,
10166
11505
  applyGeneratedRuntimeToolProposalToDispatchRequest,
10167
11506
  attachRuntimeToolsToDispatchRequest,
11507
+ buildEmptySessionNudge,
10168
11508
  buildGeneratedRuntimeToolGateOutput,
11509
+ buildLedgerOffloadReference,
11510
+ buildPolicyGuidance,
11511
+ buildSendViewOffloadMarker,
11512
+ compileWorkflowConfig,
11513
+ computeAgentContentHash,
11514
+ computeFlowContentHash,
10169
11515
  createClient,
10170
11516
  createExternalTool,
10171
11517
  defaultWorkflow,
11518
+ defaultWorkflowConfig,
11519
+ defineAgent,
11520
+ defineFlow,
11521
+ definePlaybook,
10172
11522
  deployWorkflow,
11523
+ ensureDefaultWorkflowHooks,
10173
11524
  evaluateGeneratedRuntimeToolProposal,
11525
+ extractDeclaredToolResultChars,
10174
11526
  gameWorkflow,
10175
11527
  getDefaultPlanPath,
10176
11528
  getLikelySupportingCandidatePaths,
11529
+ interpolateWorkflowTemplate,
10177
11530
  isDiscoveryToolName,
10178
11531
  isMarathonArtifactPath,
10179
11532
  isPreservationSensitiveTask,
11533
+ isWorkflowHookRef,
11534
+ listWorkflowHooks,
11535
+ normalizeAgentDefinition,
10180
11536
  normalizeCandidatePath,
10181
11537
  parseFinalBuffer,
11538
+ parseLedgerArtifactRelativePath,
11539
+ parseOffloadedOutputId,
10182
11540
  parseSSEChunk,
10183
11541
  processStream,
11542
+ registerWorkflowHook,
11543
+ resolveStallStopAfter,
11544
+ resolveWorkflowHook,
10184
11545
  sanitizeTaskSlug,
10185
- streamEvents
11546
+ shouldInjectEmptySessionNudge,
11547
+ shouldRequestModelEscalation,
11548
+ streamEvents,
11549
+ unregisterWorkflowHook
10186
11550
  };