@skaile/workspaces 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{asset-feeds-23CP6YUY.js → asset-feeds-EO2QDJ4C.js} +4 -4
  3. package/dist/{asset-feeds-23CP6YUY.js.map → asset-feeds-EO2QDJ4C.js.map} +1 -1
  4. package/dist/asset-manager/index.js +1 -1
  5. package/dist/{chunk-6AYGXPPF.js → chunk-4PSDPZUG.js} +2 -2
  6. package/dist/{chunk-6AYGXPPF.js.map → chunk-4PSDPZUG.js.map} +1 -1
  7. package/dist/{chunk-OAUKA7VT.js → chunk-5LA4XI4Y.js} +8 -8
  8. package/dist/{chunk-OAUKA7VT.js.map → chunk-5LA4XI4Y.js.map} +1 -1
  9. package/dist/{chunk-JIA2S3T2.js → chunk-AFUOORJE.js} +6 -6
  10. package/dist/{chunk-JIA2S3T2.js.map → chunk-AFUOORJE.js.map} +1 -1
  11. package/dist/{chunk-TUYN2HDT.js → chunk-AGFAOSCZ.js} +10 -4
  12. package/dist/chunk-AGFAOSCZ.js.map +1 -0
  13. package/dist/{chunk-AZ44F3AT.js → chunk-BIKTLLLD.js} +6 -6
  14. package/dist/{chunk-AZ44F3AT.js.map → chunk-BIKTLLLD.js.map} +1 -1
  15. package/dist/{chunk-HVQWLYZB.js → chunk-E5VGAW4E.js} +508 -83
  16. package/dist/chunk-E5VGAW4E.js.map +1 -0
  17. package/dist/{chunk-XJ74XFIZ.js → chunk-INHE5U74.js} +21 -8
  18. package/dist/chunk-INHE5U74.js.map +1 -0
  19. package/dist/{chunk-SCR3WGW7.js → chunk-OHLKWUO3.js} +3 -3
  20. package/dist/{chunk-SCR3WGW7.js.map → chunk-OHLKWUO3.js.map} +1 -1
  21. package/dist/{chunk-XTFZOYNY.js → chunk-QVFCDSRH.js} +3 -3
  22. package/dist/{chunk-XTFZOYNY.js.map → chunk-QVFCDSRH.js.map} +1 -1
  23. package/dist/{chunk-ONUDDQGS.js → chunk-TU7JDHKS.js} +2 -2
  24. package/dist/{chunk-ONUDDQGS.js.map → chunk-TU7JDHKS.js.map} +1 -1
  25. package/dist/{chunk-E4AVJMOJ.js → chunk-UAIR3K6X.js} +85 -5
  26. package/dist/chunk-UAIR3K6X.js.map +1 -0
  27. package/dist/{chunk-ZJOEU5GP.js → chunk-UTHMTZEH.js} +2 -2
  28. package/dist/{chunk-ZJOEU5GP.js.map → chunk-UTHMTZEH.js.map} +1 -1
  29. package/dist/{chunk-P3DUVOAW.js → chunk-XHSS3YHV.js} +6 -6
  30. package/dist/{chunk-P3DUVOAW.js.map → chunk-XHSS3YHV.js.map} +1 -1
  31. package/dist/{chunk-FW2C6JJP.js → chunk-XXDQHZJY.js} +3 -3
  32. package/dist/{chunk-FW2C6JJP.js.map → chunk-XXDQHZJY.js.map} +1 -1
  33. package/dist/{chunk-ZJ7MRR2R.js → chunk-YFAED6OB.js} +4 -4
  34. package/dist/{chunk-ZJ7MRR2R.js.map → chunk-YFAED6OB.js.map} +1 -1
  35. package/dist/{chunk-IWUIXNMF.js → chunk-ZXJKWA44.js} +3 -3
  36. package/dist/{chunk-IWUIXNMF.js.map → chunk-ZXJKWA44.js.map} +1 -1
  37. package/dist/cli/index.js +31 -31
  38. package/dist/connectors/index.js +5 -5
  39. package/dist/{ensure-sources-N7B7657V.js → ensure-sources-6ZOD45TO.js} +4 -4
  40. package/dist/{ensure-sources-N7B7657V.js.map → ensure-sources-6ZOD45TO.js.map} +1 -1
  41. package/dist/factory-assets/connectors/box.js +5 -5
  42. package/dist/factory-assets/connectors/deploy.js +5 -5
  43. package/dist/factory-assets/connectors/devserver.js +5 -5
  44. package/dist/factory-assets/connectors/flow/CONNECTOR.md +39 -11
  45. package/dist/factory-assets/connectors/flow/adapter.d.ts +92 -5
  46. package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
  47. package/dist/factory-assets/connectors/flow/adapter.js +5 -5
  48. package/dist/factory-assets/connectors/flow/contract/expression.d.ts +2 -24
  49. package/dist/factory-assets/connectors/flow/contract/expression.d.ts.map +1 -1
  50. package/dist/factory-assets/connectors/flow/contract/schema.d.ts.map +1 -1
  51. package/dist/factory-assets/connectors/flow/contract.js +2 -2
  52. package/dist/factory-assets/connectors/flow/engine/check-provenance.d.ts +47 -0
  53. package/dist/factory-assets/connectors/flow/engine/check-provenance.d.ts.map +1 -0
  54. package/dist/factory-assets/connectors/flow/engine/index.d.ts +3 -0
  55. package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
  56. package/dist/factory-assets/connectors/flow/engine/node-attempts.d.ts +68 -0
  57. package/dist/factory-assets/connectors/flow/engine/node-attempts.d.ts.map +1 -0
  58. package/dist/factory-assets/connectors/flow/engine/node-deadline.d.ts +44 -0
  59. package/dist/factory-assets/connectors/flow/engine/node-deadline.d.ts.map +1 -0
  60. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts +8 -0
  61. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts.map +1 -1
  62. package/dist/factory-assets/connectors/flow/engine/types.d.ts +13 -12
  63. package/dist/factory-assets/connectors/flow/engine/types.d.ts.map +1 -1
  64. package/dist/factory-assets/connectors/flow/engine.js +3 -3
  65. package/dist/factory-assets/connectors/flow/index.d.ts +1 -1
  66. package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
  67. package/dist/factory-assets/connectors/flow/node-executor.d.ts +10 -4
  68. package/dist/factory-assets/connectors/flow/node-executor.d.ts.map +1 -1
  69. package/dist/factory-assets/connectors/flow/prompt-fragments.js +2 -2
  70. package/dist/factory-assets/connectors/flow/run-flow.js +6 -6
  71. package/dist/factory-assets/connectors/flow.js +5 -5
  72. package/dist/factory-assets/connectors/git.js +5 -5
  73. package/dist/factory-assets/connectors/gmail.js +5 -5
  74. package/dist/factory-assets/connectors/googledrive.js +5 -5
  75. package/dist/factory-assets/connectors/local.js +5 -5
  76. package/dist/factory-assets/connectors/mattermost.js +5 -5
  77. package/dist/factory-assets/connectors/memory.js +5 -5
  78. package/dist/factory-assets/connectors/minio.js +5 -5
  79. package/dist/factory-assets/connectors/postgres.js +5 -5
  80. package/dist/factory-assets/connectors/s3.js +5 -5
  81. package/dist/factory-assets/connectors/sharepoint.js +5 -5
  82. package/dist/factory-assets/connectors/sqlite.js +5 -5
  83. package/dist/factory-assets/connectors/static-server.js +5 -5
  84. package/dist/factory-assets/connectors/tunnel.js +5 -5
  85. package/dist/factory-assets/connectors/webdav.js +5 -5
  86. package/dist/factory-assets/connectors/xstate-store.js +5 -5
  87. package/dist/factory-assets/connectors/xstate.js +5 -5
  88. package/dist/{flows-K7B253U6.js → flows-7N5AUUIS.js} +3 -3
  89. package/dist/{flows-K7B253U6.js.map → flows-7N5AUUIS.js.map} +1 -1
  90. package/dist/{helpers-PXEHIKF7.js → helpers-HWLCANAK.js} +4 -4
  91. package/dist/{helpers-PXEHIKF7.js.map → helpers-HWLCANAK.js.map} +1 -1
  92. package/dist/{library-publish-3Z33J2ZY.js → library-publish-GSAIZCO3.js} +9 -9
  93. package/dist/{library-publish-3Z33J2ZY.js.map → library-publish-GSAIZCO3.js.map} +1 -1
  94. package/dist/{open-library-Y5SKFAMK.js → open-library-ZB2STYC6.js} +8 -8
  95. package/dist/{open-library-Y5SKFAMK.js.map → open-library-ZB2STYC6.js.map} +1 -1
  96. package/dist/{project-DQRC6J6M.js → project-EJFCLSHS.js} +5 -5
  97. package/dist/{project-DQRC6J6M.js.map → project-EJFCLSHS.js.map} +1 -1
  98. package/dist/runner/index.js +9 -9
  99. package/dist/sdk/asset-manager.js +1 -1
  100. package/dist/sdk/flow.js +3 -3
  101. package/dist/sdk/index.js +9 -9
  102. package/dist/sdk/runner.js +9 -9
  103. package/dist/{source-XBJ5KTAV.js → source-ILV6ZKWM.js} +9 -9
  104. package/dist/{source-XBJ5KTAV.js.map → source-ILV6ZKWM.js.map} +1 -1
  105. package/dist/{source-manifest-publish-Q7TXBMCW.js → source-manifest-publish-QE2IFDRW.js} +10 -10
  106. package/dist/{source-manifest-publish-Q7TXBMCW.js.map → source-manifest-publish-QE2IFDRW.js.map} +1 -1
  107. package/dist/tui/index.js +9 -9
  108. package/dist/types/src/flow.d.ts +128 -0
  109. package/dist/types/src/flow.d.ts.map +1 -1
  110. package/dist/types/src/index.d.ts +1 -1
  111. package/dist/types/src/index.d.ts.map +1 -1
  112. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  113. package/dist/workspace-plugin/adapters/omp.js +3 -3
  114. package/dist/workspace-plugin/index.js +1 -1
  115. package/package.json +1 -1
  116. package/dist/chunk-E4AVJMOJ.js.map +0 -1
  117. package/dist/chunk-HVQWLYZB.js.map +0 -1
  118. package/dist/chunk-TUYN2HDT.js.map +0 -1
  119. package/dist/chunk-XJ74XFIZ.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { resolveAuthRef } from './chunk-ZNIHTT22.js';
2
2
  import { pluginRegistry } from './chunk-6E6PKKAD.js';
3
3
  import { computeStimulus } from './chunk-IFGTRL2I.js';
4
- import { getConsumedOutputSchema, validateConsumedNodeOutput, resolveNodeBindings, computeFlowStateFromSnapshots, availableRouterFrontier, evaluateRouterSelection, routerSelectionOutput, planRouterSkips, unsettledRouterBranchIds, interpolateNodeInstruction, persistedRouterSelection } from './chunk-E4AVJMOJ.js';
5
- import { parseFlowDefinition } from './chunk-XJ74XFIZ.js';
6
- import { renderStimulusPrompt } from './chunk-SCR3WGW7.js';
7
- import { flowDefaults, isAgentNode, isGateNode, nodeRequiresApproval, isSubFlowNode, isFunctionNode, nodeIsOptional, isRouterNode, isActiveRuntimeNode, nodeLabel, nodeInstruction, nodeAssets } from './chunk-TUYN2HDT.js';
4
+ import { getConsumedOutputSchema, validateConsumedNodeOutput, appendAttempt, remainingActiveExecutionMs, resolveNodeBindings, deriveCheckEvidence, computeFlowStateFromSnapshots, availableRouterFrontier, evaluateRouterSelection, routerSelectionOutput, planRouterSkips, unsettledRouterBranchIds, countFailedAttempts, hasAutomaticRetryBudget, interpolateNodeInstruction, persistedRouterSelection } from './chunk-UAIR3K6X.js';
5
+ import { parseFlowDefinition } from './chunk-INHE5U74.js';
6
+ import { renderStimulusPrompt } from './chunk-OHLKWUO3.js';
7
+ import { deterministicNodeKindLabel, flowDefaults, isAgentNode, isGateNode, isCheckNode, nodeRequiresApproval, isSubFlowNode, isFunctionNode, nodeIsOptional, isRouterNode, isActiveRuntimeNode, nodeLabel, nodeInstruction, nodeAssets } from './chunk-AGFAOSCZ.js';
8
8
  import { portableSpawn } from './chunk-P43HBWKK.js';
9
9
  import { withFsDeadline, tryWithFsDeadline, FsDeadlineError, fsDeadlineMs } from './chunk-DYMWJAL4.js';
10
10
  import { createLogger } from './chunk-7GOCNYUV.js';
@@ -324,11 +324,11 @@ function stableJsonStringify(value) {
324
324
  );
325
325
  });
326
326
  }
327
- function serializeEnvironmentInput(name, value) {
327
+ function serializeEnvironmentInput(name, value, nodeLabel2) {
328
328
  if (typeof value === "string") return value;
329
329
  const serialized = stableJsonStringify(value);
330
330
  if (serialized === void 0) {
331
- throw new Error(`Function node input ${name} is not JSON-serializable`);
331
+ throw new Error(`${nodeLabel2} input ${name} is not JSON-serializable`);
332
332
  }
333
333
  return serialized;
334
334
  }
@@ -348,19 +348,18 @@ var SessionNodeExecutor = class {
348
348
  constructor(options) {
349
349
  this.workspaceDir = resolve(options.workspaceDir);
350
350
  }
351
- /** Runs one function node process with session environment and binding overrides. */
351
+ /** Runs one function or check node process with session environment and binding overrides. */
352
352
  async execute(request) {
353
+ const nodeLabel2 = `${deterministicNodeKindLabel(request.node.run.kind)} node ${request.node.id}`;
353
354
  const cwd = resolve(this.workspaceDir, request.node.run.cwd ?? ".");
354
355
  const workspaceRelative = relative(this.workspaceDir, cwd);
355
356
  if (workspaceRelative === ".." || workspaceRelative.startsWith(`..${sep}`) || isAbsolute(workspaceRelative)) {
356
- throw new Error(
357
- `Function node ${request.node.id} cwd escapes the session workspace: ${request.node.run.cwd}`
358
- );
357
+ throw new Error(`${nodeLabel2} cwd escapes the session workspace: ${request.node.run.cwd}`);
359
358
  }
360
359
  const boundEnvironment = Object.fromEntries(
361
360
  Object.entries(request.inputs).map(([name, value]) => [
362
361
  name,
363
- serializeEnvironmentInput(name, value)
362
+ serializeEnvironmentInput(name, value, nodeLabel2)
364
363
  ])
365
364
  );
366
365
  const process2 = portableSpawn(commandFor(request.node), {
@@ -7487,6 +7486,28 @@ var AGENT_INLINE_OPERATIONS = /* @__PURE__ */ new Set([
7487
7486
  "skip_node",
7488
7487
  "fail_node"
7489
7488
  ]);
7489
+ var NodeTimeoutError = class extends Error {
7490
+ };
7491
+ function raceWithTimeout(promise, ms, timeoutMessage) {
7492
+ return new Promise((resolve6, reject) => {
7493
+ const timer = setTimeout(() => {
7494
+ reject(new NodeTimeoutError(timeoutMessage));
7495
+ promise.catch(() => {
7496
+ });
7497
+ }, ms);
7498
+ timer.unref?.();
7499
+ promise.then(
7500
+ (value) => {
7501
+ clearTimeout(timer);
7502
+ resolve6(value);
7503
+ },
7504
+ (err) => {
7505
+ clearTimeout(timer);
7506
+ reject(err);
7507
+ }
7508
+ );
7509
+ });
7510
+ }
7490
7511
  function mergeInteractionHistory(history, interaction) {
7491
7512
  if (!interaction) return history;
7492
7513
  const next = [...history];
@@ -7507,7 +7528,8 @@ function emptyNode(nodeId) {
7507
7528
  status: "not_started",
7508
7529
  approvalHistory: [],
7509
7530
  inputHistory: [],
7510
- errorHistory: []
7531
+ errorHistory: [],
7532
+ outputHistory: []
7511
7533
  };
7512
7534
  }
7513
7535
  function nodeExecutionRecord(entries = []) {
@@ -7648,14 +7670,18 @@ var FlowAdapter = class extends AbstractConnector {
7648
7670
  inlineOperationTargets: /* @__PURE__ */ new Map(),
7649
7671
  flowInputOrigins: options.flowInputOrigins,
7650
7672
  inlineSubFlowAncestry: options.inlineSubFlowAncestry ?? [flowDef.id],
7651
- executionGeneration: 0
7673
+ executionGeneration: 0,
7674
+ wakeTimer: null
7652
7675
  };
7653
7676
  state.execution = this.recompute(state);
7654
- return this.makeHandle(declaration, state);
7677
+ const handle = this.makeHandle(declaration, state);
7678
+ this.rearmWakeTimer(handle);
7679
+ return handle;
7655
7680
  }
7656
7681
  async disconnect(handle) {
7657
7682
  const state = this.s(handle);
7658
7683
  state.executionGeneration += 1;
7684
+ this.clearWakeTimer(state);
7659
7685
  await this.cancelInlineTargets(state);
7660
7686
  handle.status = "disconnected";
7661
7687
  }
@@ -7703,6 +7729,7 @@ var FlowAdapter = class extends AbstractConnector {
7703
7729
  const changed = JSON.stringify(prepared) !== JSON.stringify(execution);
7704
7730
  s.executionGeneration += 1;
7705
7731
  s.execution = prepared;
7732
+ this.rearmWakeTimer(handle);
7706
7733
  if (changed && s.onChange) s.onChange(s.execution);
7707
7734
  }
7708
7735
  // ── Base operations (private — wired into tools face in constructor) ──
@@ -8315,17 +8342,27 @@ var FlowAdapter = class extends AbstractConnector {
8315
8342
  this.mutate(handle, (current) => {
8316
8343
  const execution = current.execution.nodes[nodeId];
8317
8344
  assertLegal(execution, `node not in execution: ${nodeId}`);
8345
+ const completedAt = now();
8318
8346
  const error = {
8319
8347
  message: `Node ${nodeId} output failed schema validation after one correction: ${issueText}`,
8320
8348
  recoverable: false,
8321
- at: now()
8349
+ at: completedAt
8322
8350
  };
8351
+ const outputHistory = execution.startedAt ? appendAttempt(
8352
+ execution.outputHistory,
8353
+ "failed",
8354
+ execution.startedAt,
8355
+ completedAt,
8356
+ execution.output,
8357
+ error
8358
+ ) : execution.outputHistory;
8323
8359
  current.execution.nodes[nodeId] = {
8324
8360
  ...execution,
8325
8361
  status: "failed",
8326
- completedAt: now(),
8362
+ completedAt,
8327
8363
  error,
8328
- errorHistory: [...execution.errorHistory, error]
8364
+ errorHistory: [...execution.errorHistory, error],
8365
+ outputHistory
8329
8366
  };
8330
8367
  });
8331
8368
  throw new Error(
@@ -8335,11 +8372,21 @@ var FlowAdapter = class extends AbstractConnector {
8335
8372
  return this.mutate(handle, (current) => {
8336
8373
  const execution = current.execution.nodes[nodeId];
8337
8374
  assertLegal(execution, `node not in execution: ${nodeId}`);
8375
+ const completedAt = now();
8376
+ const outputHistory = execution.startedAt ? appendAttempt(
8377
+ execution.outputHistory,
8378
+ "complete",
8379
+ execution.startedAt,
8380
+ completedAt,
8381
+ output,
8382
+ void 0
8383
+ ) : execution.outputHistory;
8338
8384
  current.execution.nodes[nodeId] = {
8339
8385
  ...execution,
8340
8386
  status: "complete",
8341
- completedAt: now(),
8342
- output
8387
+ completedAt,
8388
+ output,
8389
+ outputHistory
8343
8390
  };
8344
8391
  return JSON.stringify({ ok: true, status: "complete" });
8345
8392
  });
@@ -8357,10 +8404,20 @@ var FlowAdapter = class extends AbstractConnector {
8357
8404
  `cannot skip node ${nodeId}: it is a mandatory approval gate \u2014 call request_approval and wait for an approved decision`
8358
8405
  );
8359
8406
  assertLegal(isOptional(s.flowDef, nodeId), `cannot skip required node ${nodeId}`);
8407
+ const completedAt = now();
8408
+ const outputHistory = node.startedAt ? appendAttempt(
8409
+ node.outputHistory,
8410
+ "skipped",
8411
+ node.startedAt,
8412
+ completedAt,
8413
+ node.output,
8414
+ void 0
8415
+ ) : node.outputHistory;
8360
8416
  s.execution.nodes[nodeId] = {
8361
8417
  ...node,
8362
8418
  status: "skipped",
8363
- completedAt: now()
8419
+ completedAt,
8420
+ outputHistory
8364
8421
  };
8365
8422
  return JSON.stringify({ ok: true, status: "skipped" });
8366
8423
  });
@@ -8375,19 +8432,40 @@ var FlowAdapter = class extends AbstractConnector {
8375
8432
  assertLegal(node, `node not in execution: ${nodeId}`);
8376
8433
  const error = { message, recoverable, at: now() };
8377
8434
  if (recoverable) {
8435
+ const completedAt = error.at;
8436
+ const outputHistory = node.startedAt ? appendAttempt(
8437
+ node.outputHistory,
8438
+ "failed",
8439
+ node.startedAt,
8440
+ completedAt,
8441
+ node.output,
8442
+ error,
8443
+ { selfRetry: true }
8444
+ ) : node.outputHistory;
8378
8445
  s.execution.nodes[nodeId] = {
8379
8446
  ...node,
8380
8447
  status: "available",
8381
8448
  error,
8382
- errorHistory: [...node.errorHistory, error]
8449
+ errorHistory: [...node.errorHistory, error],
8450
+ outputHistory
8383
8451
  };
8384
8452
  } else {
8453
+ const completedAt = now();
8454
+ const outputHistory = node.startedAt ? appendAttempt(
8455
+ node.outputHistory,
8456
+ "failed",
8457
+ node.startedAt,
8458
+ completedAt,
8459
+ node.output,
8460
+ error
8461
+ ) : node.outputHistory;
8385
8462
  s.execution.nodes[nodeId] = {
8386
8463
  ...node,
8387
8464
  status: "failed",
8388
- completedAt: now(),
8465
+ completedAt,
8389
8466
  error,
8390
- errorHistory: [...node.errorHistory, error]
8467
+ errorHistory: [...node.errorHistory, error],
8468
+ outputHistory
8391
8469
  };
8392
8470
  }
8393
8471
  return JSON.stringify({ ok: true, status: s.execution.nodes[nodeId].status });
@@ -8397,7 +8475,9 @@ var FlowAdapter = class extends AbstractConnector {
8397
8475
  /**
8398
8476
  * Record an approval decision on a node currently `awaiting_approval`.
8399
8477
  * A first-class gate completes directly when approved and remains parked when rejected.
8400
- * Agent-node approvals keep their existing resume behavior.
8478
+ * Agent-node approvals keep their existing resume behavior. A check's escalation is a
8479
+ * one-way door: only `rejected` is legal, and it ends the node terminally `failed` —
8480
+ * there is no decision that resumes or passes a check.
8401
8481
  */
8402
8482
  applyApproval(handle, nodeId, decision, feedback, decidedBy) {
8403
8483
  this.mutate(handle, (s) => {
@@ -8414,6 +8494,12 @@ var FlowAdapter = class extends AbstractConnector {
8414
8494
  `applyApproval: gate ${nodeId} approval attempt is already decided; request approval again before applying another decision`
8415
8495
  );
8416
8496
  }
8497
+ if (isCheckNode(definition)) {
8498
+ assertLegal(
8499
+ decision === "rejected",
8500
+ `applyApproval: check ${nodeId} cannot be approved \u2014 a failed check has no passing state to approve into; only "rejected" is a legal decision`
8501
+ );
8502
+ }
8417
8503
  const base = node.approval ?? { requestedAt: now(), summary: "" };
8418
8504
  const approval = {
8419
8505
  ...base,
@@ -8440,6 +8526,14 @@ var FlowAdapter = class extends AbstractConnector {
8440
8526
  approval,
8441
8527
  approvalHistory: history
8442
8528
  };
8529
+ } else if (isCheckNode(definition)) {
8530
+ s.execution.nodes[nodeId] = {
8531
+ ...node,
8532
+ status: "failed",
8533
+ completedAt: now(),
8534
+ approval,
8535
+ approvalHistory: history
8536
+ };
8443
8537
  } else if (decision === "approved") {
8444
8538
  s.execution.nodes[nodeId] = { ...node, approval, approvalHistory: history };
8445
8539
  } else {
@@ -8513,10 +8607,17 @@ var FlowAdapter = class extends AbstractConnector {
8513
8607
  return void 0;
8514
8608
  });
8515
8609
  }
8516
- /** Cancel the flow run. Status transitions to `cancelled`; dependents stay put. */
8610
+ /**
8611
+ * Cancel the flow run. Status transitions to `cancelled`; dependents stay
8612
+ * put. Bumps `executionGeneration` — the sole late-settlement rejection
8613
+ * mechanism every in-flight await (function executor, sub-flow child)
8614
+ * checks after resolving, so a result that arrives after cancellation can
8615
+ * never write state.
8616
+ */
8517
8617
  cancel(handle) {
8518
8618
  this.mutate(handle, (s) => {
8519
8619
  s.execution = { ...s.execution, status: "cancelled" };
8620
+ s.executionGeneration += 1;
8520
8621
  return void 0;
8521
8622
  });
8522
8623
  }
@@ -8586,9 +8687,11 @@ var FlowAdapter = class extends AbstractConnector {
8586
8687
  return this.drainAvailableRuntimeNodes(handle, true);
8587
8688
  }
8588
8689
  /**
8589
- * Run every currently available function node through the configured
8590
- * NodeExecutor. Newly-unblocked function nodes are drained in definition
8591
- * order before returning control to the runner's agent-turn decision.
8690
+ * Run every currently available function and check node through the
8691
+ * configured NodeExecutor. Newly-unblocked function/check nodes are
8692
+ * drained in definition order before returning control to the runner's
8693
+ * agent-turn decision. Routers and gates are excluded — see
8694
+ * {@link drainRuntimeNodes} for the full drain.
8592
8695
  */
8593
8696
  async executeAvailableFunctionNodes(handle) {
8594
8697
  return this.drainAvailableRuntimeNodes(handle, false);
@@ -8604,7 +8707,7 @@ var FlowAdapter = class extends AbstractConnector {
8604
8707
  if (isSubFlowNode(candidate) && !advancedSubFlows.has(candidate.id) && (status === "available" || status === "running")) {
8605
8708
  return true;
8606
8709
  }
8607
- return (isFunctionNode(candidate) || includeRoutersAndGates && isGateNode(candidate)) && status === "available";
8710
+ return (isFunctionNode(candidate) || isCheckNode(candidate) || includeRoutersAndGates && isGateNode(candidate)) && status === "available";
8608
8711
  });
8609
8712
  if (!node) break;
8610
8713
  if (isGateNode(node)) {
@@ -8653,6 +8756,9 @@ var FlowAdapter = class extends AbstractConnector {
8653
8756
  );
8654
8757
  }
8655
8758
  const executionGeneration = state.executionGeneration;
8759
+ const timeoutSec = node.control?.timeoutSec;
8760
+ const runningExecution = state.execution.nodes[node.id];
8761
+ const remainingMs = runningExecution ? remainingActiveExecutionMs(runningExecution, timeoutSec, Date.now()) : void 0;
8656
8762
  let result;
8657
8763
  try {
8658
8764
  const runner = state.inlineSubFlowRunner;
@@ -8665,7 +8771,7 @@ var FlowAdapter = class extends AbstractConnector {
8665
8771
  snapshotMap(state.execution),
8666
8772
  state.flowInputOrigins
8667
8773
  );
8668
- result = await runner.run({
8774
+ const runPromise = runner.run({
8669
8775
  node,
8670
8776
  flowId: node.run.flow,
8671
8777
  inputs: resolved.values,
@@ -8682,50 +8788,86 @@ var FlowAdapter = class extends AbstractConnector {
8682
8788
  else state.inlineOperationTargets.delete(node.id);
8683
8789
  }
8684
8790
  });
8791
+ result = remainingMs !== void 0 ? await raceWithTimeout(
8792
+ runPromise,
8793
+ Math.max(0, remainingMs),
8794
+ `Sub-flow node ${node.id} timed out after ${timeoutSec}s`
8795
+ ) : await runPromise;
8685
8796
  } catch (error) {
8686
- result = {
8687
- status: "failed",
8688
- error: {
8689
- message: `Sub-flow node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`,
8690
- recoverable: false,
8691
- at: now()
8797
+ if (error instanceof NodeTimeoutError) {
8798
+ const target = state.inlineOperationTargets.get(node.id);
8799
+ if (target) {
8800
+ state.inlineOperationTargets.delete(node.id);
8801
+ void target.cancel().catch(() => {
8802
+ });
8692
8803
  }
8693
- };
8804
+ result = {
8805
+ status: "failed",
8806
+ error: { message: error.message, recoverable: false, at: now() }
8807
+ };
8808
+ } else {
8809
+ result = {
8810
+ status: "failed",
8811
+ error: {
8812
+ message: `Sub-flow node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`,
8813
+ recoverable: false,
8814
+ at: now()
8815
+ }
8816
+ };
8817
+ }
8694
8818
  }
8695
8819
  if (this.s(handle).executionGeneration !== executionGeneration) break;
8696
8820
  if (result.status === "paused") {
8697
8821
  this.projectInlineInteraction(handle, node.id, result.projection);
8698
8822
  continue;
8699
8823
  }
8700
- if (this.s(handle).execution.status === "cancelled") break;
8824
+ let subFlowRetrying = false;
8701
8825
  this.mutate(
8702
8826
  handle,
8703
8827
  (current) => {
8704
8828
  const execution2 = current.execution.nodes[node.id];
8705
8829
  assertLegal(execution2, `node not in execution: ${node.id}`);
8830
+ const completedAt = now();
8706
8831
  if (result.status === "complete") {
8832
+ const outputHistory = appendAttempt(
8833
+ execution2.outputHistory,
8834
+ "complete",
8835
+ execution2.startedAt ?? completedAt,
8836
+ completedAt,
8837
+ result.output,
8838
+ void 0
8839
+ );
8707
8840
  current.execution.nodes[node.id] = {
8708
8841
  ...execution2,
8709
8842
  status: "complete",
8710
- completedAt: now(),
8711
- output: result.output
8843
+ completedAt,
8844
+ output: result.output,
8845
+ outputHistory
8712
8846
  };
8713
8847
  return;
8714
8848
  }
8715
- current.execution.nodes[node.id] = {
8849
+ const { retry, patch } = this.planAutomaticRetry(
8850
+ node,
8851
+ execution2,
8852
+ result.error.message,
8853
+ completedAt,
8854
+ result.output,
8855
+ result.error.at
8856
+ );
8857
+ subFlowRetrying = retry;
8858
+ current.execution.nodes[node.id] = retry ? { ...execution2, ...patch, status: "available", error: void 0 } : {
8716
8859
  ...execution2,
8860
+ ...patch,
8717
8861
  status: nodeIsOptional(node) ? "skipped" : "failed",
8718
- completedAt: now(),
8719
- output: result.output,
8720
- error: result.error,
8721
- errorHistory: [...execution2.errorHistory, result.error]
8862
+ completedAt
8722
8863
  };
8723
8864
  },
8724
8865
  false
8725
8866
  );
8867
+ if (subFlowRetrying) advancedSubFlows.delete(node.id);
8726
8868
  continue;
8727
8869
  }
8728
- if (!isFunctionNode(node)) continue;
8870
+ if (!isFunctionNode(node) && !isCheckNode(node)) continue;
8729
8871
  this.mutate(
8730
8872
  handle,
8731
8873
  (current) => {
@@ -8733,7 +8875,7 @@ var FlowAdapter = class extends AbstractConnector {
8733
8875
  assertLegal(execution, `node not in execution: ${node.id}`);
8734
8876
  assertLegal(
8735
8877
  execution.status === "available",
8736
- `cannot execute function node ${node.id}: status is ${execution.status}`
8878
+ `cannot execute ${node.run.kind} node ${node.id}: status is ${execution.status}`
8737
8879
  );
8738
8880
  current.execution.nodes[node.id] = {
8739
8881
  ...execution,
@@ -8743,73 +8885,178 @@ var FlowAdapter = class extends AbstractConnector {
8743
8885
  },
8744
8886
  false
8745
8887
  );
8888
+ const deterministicExecutionGeneration = state.executionGeneration;
8889
+ const deterministicTimeoutSec = node.control?.timeoutSec;
8890
+ const deterministicRunningExecution = state.execution.nodes[node.id];
8891
+ const deterministicRemainingMs = deterministicRunningExecution ? remainingActiveExecutionMs(
8892
+ deterministicRunningExecution,
8893
+ deterministicTimeoutSec,
8894
+ Date.now()
8895
+ ) : void 0;
8896
+ const deterministicKindLabel = deterministicNodeKindLabel(node.run.kind);
8746
8897
  let capturedOutput;
8898
+ let checkEvidence;
8899
+ let deterministicOutcome;
8747
8900
  try {
8748
8901
  const executor = state.nodeExecutor;
8749
- if (!executor) throw new Error(`no NodeExecutor configured for function node ${node.id}`);
8902
+ if (!executor)
8903
+ throw new Error(`no NodeExecutor configured for ${node.run.kind} node ${node.id}`);
8750
8904
  const resolved = resolveNodeBindings(
8751
8905
  state.flowDef,
8752
8906
  node,
8753
8907
  snapshotMap(state.execution),
8754
8908
  state.flowInputOrigins
8755
8909
  );
8756
- const result = await executor.execute({
8910
+ const runPromise = executor.execute({
8757
8911
  node,
8758
8912
  inputs: resolved.values,
8759
8913
  inputOrigins: resolved.origins
8760
8914
  });
8761
- capturedOutput = functionNodeOutput(node, result);
8915
+ const result = deterministicRemainingMs !== void 0 ? await raceWithTimeout(
8916
+ runPromise,
8917
+ Math.max(0, deterministicRemainingMs),
8918
+ `${deterministicKindLabel} node ${node.id} timed out after ${deterministicTimeoutSec}s`
8919
+ ) : await runPromise;
8762
8920
  const successfulStatuses = node.run.successExit ?? [0];
8763
- if (!successfulStatuses.includes(result.exitStatus)) {
8764
- const detail = result.stderr.trim() || result.stdout.trim();
8765
- throw new Error(
8766
- `Function node ${node.id} exited with unsuccessful exit status ${result.exitStatus}${detail ? `: ${detail}` : ""}`
8921
+ const passed = successfulStatuses.includes(result.exitStatus);
8922
+ capturedOutput = deterministicNodeOutput(node, result, passed);
8923
+ const detail = passed ? "" : result.stderr.trim() || result.stdout.trim();
8924
+ const detailSuffix = detail ? `: ${detail}` : "";
8925
+ if (passed) {
8926
+ const validation = validateConsumedNodeOutput(
8927
+ state.flowDef,
8928
+ node.id,
8929
+ capturedOutput.fields
8767
8930
  );
8931
+ if (!validation.valid) {
8932
+ throw new Error(
8933
+ `output does not match its consumed schema: ${validation.issues.join("; ")}`
8934
+ );
8935
+ }
8768
8936
  }
8769
- const validation = validateConsumedNodeOutput(
8770
- state.flowDef,
8771
- node.id,
8772
- capturedOutput.fields
8773
- );
8774
- if (!validation.valid) {
8937
+ if (isCheckNode(node)) {
8938
+ checkEvidence = deriveCheckEvidence(
8939
+ resolved,
8940
+ {
8941
+ runtime: node.run.runtime ?? "shell",
8942
+ command: node.run.command,
8943
+ cwd: node.run.cwd,
8944
+ successExit: successfulStatuses,
8945
+ exitStatus: result.exitStatus,
8946
+ passed
8947
+ },
8948
+ now()
8949
+ );
8950
+ deterministicOutcome = passed ? { kind: "success" } : {
8951
+ kind: "check-fail",
8952
+ message: `Check node ${node.id} failed with exit status ${result.exitStatus}${detailSuffix}`,
8953
+ onFail: node.run.onFail
8954
+ };
8955
+ } else if (!passed) {
8775
8956
  throw new Error(
8776
- `output does not match its consumed schema: ${validation.issues.join("; ")}`
8957
+ `Function node ${node.id} exited with unsuccessful exit status ${result.exitStatus}${detailSuffix}`
8777
8958
  );
8959
+ } else {
8960
+ deterministicOutcome = { kind: "success" };
8778
8961
  }
8962
+ } catch (error) {
8963
+ deterministicOutcome = error instanceof NodeTimeoutError ? { kind: "failure", message: error.message } : {
8964
+ kind: "failure",
8965
+ message: `${deterministicKindLabel} node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`
8966
+ };
8967
+ }
8968
+ if (this.s(handle).executionGeneration !== deterministicExecutionGeneration) break;
8969
+ if (deterministicOutcome.kind === "success") {
8779
8970
  this.mutate(
8780
8971
  handle,
8781
8972
  (current) => {
8782
8973
  const execution = current.execution.nodes[node.id];
8783
8974
  assertLegal(execution, `node not in execution: ${node.id}`);
8975
+ const completedAt = now();
8976
+ const outputHistory = appendAttempt(
8977
+ execution.outputHistory,
8978
+ "complete",
8979
+ execution.startedAt ?? completedAt,
8980
+ completedAt,
8981
+ capturedOutput,
8982
+ void 0,
8983
+ { check: checkEvidence }
8984
+ );
8784
8985
  current.execution.nodes[node.id] = {
8785
8986
  ...execution,
8786
8987
  status: "complete",
8787
- completedAt: now(),
8788
- output: capturedOutput
8988
+ completedAt,
8989
+ output: capturedOutput,
8990
+ outputHistory
8789
8991
  };
8790
8992
  },
8791
8993
  false
8792
8994
  );
8793
- } catch (error) {
8794
- const message = error instanceof Error ? error.message : String(error);
8995
+ } else if (deterministicOutcome.kind === "check-fail") {
8996
+ const { message: failureMessage, onFail } = deterministicOutcome;
8795
8997
  this.mutate(
8796
8998
  handle,
8797
8999
  (current) => {
8798
9000
  const execution = current.execution.nodes[node.id];
8799
9001
  assertLegal(execution, `node not in execution: ${node.id}`);
8800
- const nodeError = {
8801
- message: `Function node ${node.id} failed: ${message}`,
8802
- recoverable: false,
8803
- at: now()
8804
- };
8805
- current.execution.nodes[node.id] = {
8806
- ...execution,
8807
- status: "failed",
8808
- completedAt: now(),
8809
- output: capturedOutput,
8810
- error: nodeError,
8811
- errorHistory: [...execution.errorHistory, nodeError]
8812
- };
9002
+ assertLegal(checkEvidence, `check node ${node.id} completed without evidence`);
9003
+ const completedAt = now();
9004
+ const { retry, patch } = this.planAutomaticRetry(
9005
+ node,
9006
+ execution,
9007
+ failureMessage,
9008
+ completedAt,
9009
+ capturedOutput,
9010
+ void 0,
9011
+ checkEvidence
9012
+ );
9013
+ if (retry) {
9014
+ current.execution.nodes[node.id] = {
9015
+ ...execution,
9016
+ ...patch,
9017
+ status: "available",
9018
+ error: void 0
9019
+ };
9020
+ } else if (onFail === "fail") {
9021
+ current.execution.nodes[node.id] = {
9022
+ ...execution,
9023
+ ...patch,
9024
+ status: "failed",
9025
+ completedAt
9026
+ };
9027
+ } else {
9028
+ const provenanceNote = checkEvidence.provenance ? ` (${checkEvidence.provenance})` : "";
9029
+ const approval = {
9030
+ requestedAt: now(),
9031
+ summary: `Check node ${node.id} failed with exit status ${checkEvidence.exitStatus}${provenanceNote}.`
9032
+ };
9033
+ current.execution.nodes[node.id] = {
9034
+ ...execution,
9035
+ ...patch,
9036
+ status: "awaiting_approval",
9037
+ approval,
9038
+ approvalHistory: [...execution.approvalHistory, approval]
9039
+ };
9040
+ }
9041
+ },
9042
+ false
9043
+ );
9044
+ } else {
9045
+ const failureMessage = deterministicOutcome.message;
9046
+ this.mutate(
9047
+ handle,
9048
+ (current) => {
9049
+ const execution = current.execution.nodes[node.id];
9050
+ assertLegal(execution, `node not in execution: ${node.id}`);
9051
+ const completedAt = now();
9052
+ const { retry, patch } = this.planAutomaticRetry(
9053
+ node,
9054
+ execution,
9055
+ failureMessage,
9056
+ completedAt,
9057
+ capturedOutput
9058
+ );
9059
+ current.execution.nodes[node.id] = retry ? { ...execution, ...patch, status: "available", error: void 0 } : { ...execution, ...patch, status: "failed", completedAt };
8813
9060
  },
8814
9061
  false
8815
9062
  );
@@ -8955,6 +9202,7 @@ var FlowAdapter = class extends AbstractConnector {
8955
9202
  s.lastSignaledExecution = s.execution;
8956
9203
  }
8957
9204
  }
9205
+ this.rearmWakeTimer(handle);
8958
9206
  return typeof result === "string" ? result : JSON.stringify({ ok: true });
8959
9207
  }
8960
9208
  /**
@@ -9000,6 +9248,181 @@ var FlowAdapter = class extends AbstractConnector {
9000
9248
  done: computed.done && flowStatus === "complete"
9001
9249
  };
9002
9250
  }
9251
+ // ── Agent-node deadline wake timer ────────────────────────────────────────
9252
+ //
9253
+ // Agent nodes have no `await` the adapter can wrap — it marks the node
9254
+ // started and waits for the model to call `complete_node`, which may never
9255
+ // come. The derived deadline (see `engine/node-deadline.ts`) is the source
9256
+ // of truth: it is a pure function of `startedAt` + `approvalHistory` +
9257
+ // `inputHistory`, so it is correct on its own after `hydrate()` or a
9258
+ // reconnect with no timer required. This wake timer exists only so a
9259
+ // genuinely hung run — by definition nothing else happening — still gets
9260
+ // *some* event that triggers a re-evaluation of that deadline.
9261
+ clearWakeTimer(s) {
9262
+ if (s.wakeTimer !== null) {
9263
+ clearTimeout(s.wakeTimer);
9264
+ s.wakeTimer = null;
9265
+ }
9266
+ }
9267
+ /**
9268
+ * Whether `execution` counts as "actively running" for `control.timeoutSec`
9269
+ * evaluation. Ordinarily this is just `status === "running"`. The one
9270
+ * exception: a non-gate agent node whose approval was just decided
9271
+ * `approved` stays at `awaiting_approval` — `applyApproval` deliberately
9272
+ * does not restore `running` there, because `doCompleteNode`'s
9273
+ * `approvedPrior` check requires exactly that status (a mandatory-approval
9274
+ * node has no `runningAutonomous` fallback). Without this second clause the
9275
+ * timeout would pause for the approval wait (correctly, via
9276
+ * `remainingActiveExecutionMs` reading the now-resolved `approvalHistory`
9277
+ * entry) and then never resume, since nothing re-evaluates a node that
9278
+ * never looks "running" again.
9279
+ */
9280
+ isDeadlineEligible(execution) {
9281
+ if (execution.status === "running") return true;
9282
+ return execution.status === "awaiting_approval" && execution.approval?.decision === "approved";
9283
+ }
9284
+ /**
9285
+ * Recomputes the pending wake timer against the current state: cancels any
9286
+ * previously-armed timer, then arms a fresh one for the earliest deadline
9287
+ * among currently `running` agent nodes with a `control.timeoutSec`. A
9288
+ * no-op when nothing is running against a timeout, or the flow is already
9289
+ * terminal. Called after every {@link mutate}, plus `connect()` and
9290
+ * `hydrate()` (which bypass `mutate()`).
9291
+ *
9292
+ * `disconnect()` clears the timer and bumps `executionGeneration`, but a
9293
+ * `mutate()` already in flight (or one that lands after teardown for any
9294
+ * other reason) still calls this on its way out — bail here on
9295
+ * `handle.status` so a post-disconnect mutation can never re-arm a timer
9296
+ * that would fire against a handle nothing is watching anymore.
9297
+ */
9298
+ rearmWakeTimer(handle) {
9299
+ const s = this.s(handle);
9300
+ this.clearWakeTimer(s);
9301
+ if (handle.status === "disconnected") return;
9302
+ if (s.execution.done || s.execution.status === "failed" || s.execution.status === "cancelled") {
9303
+ return;
9304
+ }
9305
+ const nowMs = Date.now();
9306
+ let earliestRemainingMs;
9307
+ for (const node of s.flowDef.nodes) {
9308
+ if (!isAgentNode(node)) continue;
9309
+ const timeoutSec = node.control?.timeoutSec;
9310
+ if (timeoutSec === void 0) continue;
9311
+ const execution = s.execution.nodes[node.id];
9312
+ if (!execution || !this.isDeadlineEligible(execution)) continue;
9313
+ const remaining = remainingActiveExecutionMs(execution, timeoutSec, nowMs);
9314
+ if (remaining === void 0) continue;
9315
+ if (earliestRemainingMs === void 0 || remaining < earliestRemainingMs) {
9316
+ earliestRemainingMs = remaining;
9317
+ }
9318
+ }
9319
+ if (earliestRemainingMs === void 0) return;
9320
+ const delayMs = Math.max(0, earliestRemainingMs);
9321
+ const timer = setTimeout(() => {
9322
+ s.wakeTimer = null;
9323
+ void this.evaluateAgentNodeDeadlines(handle);
9324
+ }, delayMs);
9325
+ timer.unref?.();
9326
+ s.wakeTimer = timer;
9327
+ }
9328
+ /**
9329
+ * Re-checks every `running` agent node's derived deadline and, for any that
9330
+ * have expired, either auto-retries (bounded by `control.retries`, composing
9331
+ * with the existing `doFailNode(recoverable: true)` and `retryNode`
9332
+ * pathways) or terminally fails the node. Safe to call at any time — the
9333
+ * derived deadline means this is a no-op unless something has actually
9334
+ * expired. Goes through {@link mutate}, so a state change here republishes
9335
+ * `onChange`/stimulus exactly like any other mutation, waking a fresh turn
9336
+ * when the node retries or the flow rolls up to `failed`.
9337
+ */
9338
+ async evaluateAgentNodeDeadlines(handle) {
9339
+ const s = this.s(handle);
9340
+ const nowMs = Date.now();
9341
+ const expired = s.flowDef.nodes.filter((node) => {
9342
+ if (!isAgentNode(node)) return false;
9343
+ const timeoutSec = node.control?.timeoutSec;
9344
+ if (timeoutSec === void 0) return false;
9345
+ const execution = s.execution.nodes[node.id];
9346
+ if (!execution || !this.isDeadlineEligible(execution)) return false;
9347
+ const remaining = remainingActiveExecutionMs(execution, timeoutSec, nowMs);
9348
+ return remaining !== void 0 && remaining <= 0;
9349
+ });
9350
+ for (const node of expired) {
9351
+ this.mutate(
9352
+ handle,
9353
+ (current) => {
9354
+ const execution = current.execution.nodes[node.id];
9355
+ if (!execution || !this.isDeadlineEligible(execution)) return;
9356
+ const completedAt = now();
9357
+ const message = `Agent node ${node.id} timed out after ${node.control?.timeoutSec}s`;
9358
+ const { retry, patch } = this.planAutomaticRetry(
9359
+ node,
9360
+ execution,
9361
+ message,
9362
+ completedAt,
9363
+ execution.output
9364
+ );
9365
+ current.execution.nodes[node.id] = retry ? { ...execution, ...patch, status: "available", error: void 0 } : { ...execution, ...patch, status: "failed", completedAt };
9366
+ },
9367
+ true
9368
+ );
9369
+ }
9370
+ }
9371
+ /**
9372
+ * Records one failed attempt on `nodeId`'s execution and decides whether
9373
+ * the automatic bounded retry (`control.retries`) still has budget. Shared
9374
+ * by every automatic-failure seam (agent-node timeout, function/check
9375
+ * execution/timeout failure, sub-flow execution/timeout failure, and a
9376
+ * check's fail verdict) so the budget — derived from prior `'failed'`
9377
+ * entries in `outputHistory`, not a separate durable counter — is computed
9378
+ * once, the same way, everywhere.
9379
+ *
9380
+ * Returns the `NodeExecution` patch (error/errorHistory/outputHistory/
9381
+ * output) plus whether budget remains; the caller decides the resulting
9382
+ * `status` because that differs by kind (e.g. sub-flow degrades an
9383
+ * optional node's exhausted failure to `skipped`; agent/function do not,
9384
+ * preserving their existing behavior). A terminal failure whose node has
9385
+ * any `control.retries` configured carries `recoverable: true` so the
9386
+ * manual `retryNode` operator override — which sits outside this budget
9387
+ * entirely — remains usable once the automatic budget runs out.
9388
+ *
9389
+ * @param sourceErrorAtIso - The failure's own timestamp, when the caller has
9390
+ * one more precise than `completedAtIso` (e.g. a sub-flow child reports
9391
+ * its own `error.at`, which can predate this node's parent-side
9392
+ * `completedAt` by however long the result took to propagate). Defaults
9393
+ * to `completedAtIso` for callers (agent-timeout, function-node) whose
9394
+ * failure message is synthesized at the moment of detection.
9395
+ * @param checkEvidence - Derived evidence for a check's fail verdict, so it
9396
+ * rides on the same appended attempt as the failure it explains.
9397
+ */
9398
+ planAutomaticRetry(node, execution, message, completedAtIso, capturedOutput, sourceErrorAtIso, checkEvidence) {
9399
+ const retries = node.control?.retries;
9400
+ const priorFailures = countFailedAttempts(execution.outputHistory);
9401
+ const retry = hasAutomaticRetryBudget(priorFailures, retries);
9402
+ const error = {
9403
+ message,
9404
+ recoverable: !retry && !!retries,
9405
+ at: sourceErrorAtIso ?? completedAtIso
9406
+ };
9407
+ const outputHistory = appendAttempt(
9408
+ execution.outputHistory,
9409
+ "failed",
9410
+ execution.startedAt ?? completedAtIso,
9411
+ completedAtIso,
9412
+ capturedOutput,
9413
+ error,
9414
+ { check: checkEvidence }
9415
+ );
9416
+ return {
9417
+ retry,
9418
+ patch: {
9419
+ error,
9420
+ errorHistory: [...execution.errorHistory, error],
9421
+ outputHistory,
9422
+ output: capturedOutput ?? execution.output
9423
+ }
9424
+ };
9425
+ }
9003
9426
  /**
9004
9427
  * Build the selected agent node's complete turn briefing plus upstream context.
9005
9428
  */
@@ -9087,7 +9510,8 @@ function routerSelectionHandoffSummary(router, execution) {
9087
9510
  if (!selection) return void 0;
9088
9511
  return selection.selectedTarget === null ? "Router completed without selecting a target." : `Router selected ${selection.selectedTarget}.`;
9089
9512
  }
9090
- function functionNodeOutput(node, result) {
9513
+ function deterministicNodeOutput(node, result, passed) {
9514
+ const kindLabel = deterministicNodeKindLabel(node.run.kind);
9091
9515
  const capturedFields = [];
9092
9516
  for (const channel of ["stdout", "stderr"]) {
9093
9517
  const destination = node.run.capture?.[channel];
@@ -9095,14 +9519,15 @@ function functionNodeOutput(node, result) {
9095
9519
  const match = /^fields\.([^.]+)$/.exec(destination);
9096
9520
  if (!match?.[1]) {
9097
9521
  throw new Error(
9098
- `Function node ${node.id} run.capture.${channel} destination "${destination}" is invalid; expected fields.<name>`
9522
+ `${kindLabel} node ${node.id} run.capture.${channel} destination "${destination}" is invalid; expected fields.<name>`
9099
9523
  );
9100
9524
  }
9101
9525
  capturedFields.push([match[1], result[channel]]);
9102
9526
  }
9103
9527
  const fields = Object.fromEntries(capturedFields);
9528
+ const summary = node.run.kind === "check" ? `Check node ${node.id} ${passed ? "passed" : "failed"} with exit status ${result.exitStatus}.` : `Function node ${node.id} exited with status ${result.exitStatus}.`;
9104
9529
  return {
9105
- summary: `Function node ${node.id} exited with status ${result.exitStatus}.`,
9530
+ summary,
9106
9531
  artifacts: [],
9107
9532
  ...Object.keys(fields).length > 0 ? { fields } : {}
9108
9533
  };
@@ -9176,5 +9601,5 @@ function createConnector() {
9176
9601
  }
9177
9602
 
9178
9603
  export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, BoxConnector, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, PortPool, PostgresConnector, S3Connector, SQLiteConnector, SessionInlineSubFlowRunner, SessionNodeExecutor, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildSdkConnectorTools, buildSdkFlowTools, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector20, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createFsWatcher, createWorktree, defaultSpawn, ensureDirMode, ensureFleetMounted, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
9179
- //# sourceMappingURL=chunk-HVQWLYZB.js.map
9180
- //# sourceMappingURL=chunk-HVQWLYZB.js.map
9604
+ //# sourceMappingURL=chunk-E5VGAW4E.js.map
9605
+ //# sourceMappingURL=chunk-E5VGAW4E.js.map