@skaile/workspaces 2.8.0 → 3.0.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 (161) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +17 -6
  3. package/dist/{asset-feeds-23CP6YUY.js → asset-feeds-XKPJZ6QT.js} +4 -4
  4. package/dist/{asset-feeds-23CP6YUY.js.map → asset-feeds-XKPJZ6QT.js.map} +1 -1
  5. package/dist/asset-manager/index.js +1 -1
  6. package/dist/bridge/drivers/claude-sdk.js +2 -2
  7. package/dist/bridge/drivers/codex.js +2 -2
  8. package/dist/bridge/drivers/continue.js +2 -2
  9. package/dist/bridge/drivers/echo.js +1 -1
  10. package/dist/bridge/drivers/gemini.js +4 -4
  11. package/dist/bridge/drivers/goose.js +2 -2
  12. package/dist/bridge/drivers/omp.js +1 -1
  13. package/dist/bridge/drivers/opencode.js +2 -2
  14. package/dist/bridge/drivers/qwen.js +4 -4
  15. package/dist/bridge/index.js +3 -3
  16. package/dist/bridge/src/models.d.ts +59 -0
  17. package/dist/bridge/src/models.d.ts.map +1 -1
  18. package/dist/{chunk-XJ74XFIZ.js → chunk-5QGMEPUX.js} +27 -18
  19. package/dist/chunk-5QGMEPUX.js.map +1 -0
  20. package/dist/{chunk-ONUDDQGS.js → chunk-7HXS7WZF.js} +2 -2
  21. package/dist/{chunk-ONUDDQGS.js.map → chunk-7HXS7WZF.js.map} +1 -1
  22. package/dist/{chunk-SCR3WGW7.js → chunk-7KFN2EPR.js} +12 -4
  23. package/dist/chunk-7KFN2EPR.js.map +1 -0
  24. package/dist/{chunk-L3TS7ZO4.js → chunk-BJDACPWZ.js} +3 -3
  25. package/dist/{chunk-L3TS7ZO4.js.map → chunk-BJDACPWZ.js.map} +1 -1
  26. package/dist/{chunk-JIA2S3T2.js → chunk-EH7BCJF4.js} +6 -6
  27. package/dist/{chunk-JIA2S3T2.js.map → chunk-EH7BCJF4.js.map} +1 -1
  28. package/dist/{chunk-TUYN2HDT.js → chunk-F5VREBV2.js} +14 -5
  29. package/dist/chunk-F5VREBV2.js.map +1 -0
  30. package/dist/{chunk-OAUKA7VT.js → chunk-JQMULHO6.js} +24 -12
  31. package/dist/chunk-JQMULHO6.js.map +1 -0
  32. package/dist/{chunk-ZR2X4SIG.js → chunk-KXWUCKP5.js} +5 -5
  33. package/dist/{chunk-ZR2X4SIG.js.map → chunk-KXWUCKP5.js.map} +1 -1
  34. package/dist/{chunk-IWUIXNMF.js → chunk-N4GFUWWC.js} +3 -3
  35. package/dist/{chunk-IWUIXNMF.js.map → chunk-N4GFUWWC.js.map} +1 -1
  36. package/dist/{chunk-ZJOEU5GP.js → chunk-NMKTM55X.js} +2 -2
  37. package/dist/{chunk-ZJOEU5GP.js.map → chunk-NMKTM55X.js.map} +1 -1
  38. package/dist/{chunk-XTFZOYNY.js → chunk-P2J3MLTW.js} +3 -3
  39. package/dist/{chunk-XTFZOYNY.js.map → chunk-P2J3MLTW.js.map} +1 -1
  40. package/dist/{chunk-VNIKF66R.js → chunk-P5W6JVD4.js} +4 -4
  41. package/dist/{chunk-VNIKF66R.js.map → chunk-P5W6JVD4.js.map} +1 -1
  42. package/dist/{chunk-6AYGXPPF.js → chunk-Q6O53D5Q.js} +2 -2
  43. package/dist/{chunk-6AYGXPPF.js.map → chunk-Q6O53D5Q.js.map} +1 -1
  44. package/dist/{chunk-ZJ7MRR2R.js → chunk-QGDQHNYC.js} +4 -4
  45. package/dist/{chunk-ZJ7MRR2R.js.map → chunk-QGDQHNYC.js.map} +1 -1
  46. package/dist/{chunk-E4AVJMOJ.js → chunk-S5FV6ISY.js} +89 -6
  47. package/dist/chunk-S5FV6ISY.js.map +1 -0
  48. package/dist/{chunk-HY3MBSNC.js → chunk-SR6NBBWT.js} +2 -2
  49. package/dist/{chunk-HY3MBSNC.js.map → chunk-SR6NBBWT.js.map} +1 -1
  50. package/dist/{chunk-AZ44F3AT.js → chunk-TXTCW7V6.js} +6 -6
  51. package/dist/{chunk-AZ44F3AT.js.map → chunk-TXTCW7V6.js.map} +1 -1
  52. package/dist/{chunk-BMQKMOQY.js → chunk-V34ZEXSE.js} +61 -3
  53. package/dist/chunk-V34ZEXSE.js.map +1 -0
  54. package/dist/{chunk-FW2C6JJP.js → chunk-WECBTWWZ.js} +3 -3
  55. package/dist/{chunk-FW2C6JJP.js.map → chunk-WECBTWWZ.js.map} +1 -1
  56. package/dist/{chunk-P3DUVOAW.js → chunk-XJECOBCO.js} +16 -7
  57. package/dist/chunk-XJECOBCO.js.map +1 -0
  58. package/dist/{chunk-HVQWLYZB.js → chunk-ZO7KL7GN.js} +731 -85
  59. package/dist/chunk-ZO7KL7GN.js.map +1 -0
  60. package/dist/cli/index.js +38 -38
  61. package/dist/connectors/index.js +11 -5
  62. package/dist/{ensure-sources-N7B7657V.js → ensure-sources-D6R57GAP.js} +4 -4
  63. package/dist/{ensure-sources-N7B7657V.js.map → ensure-sources-D6R57GAP.js.map} +1 -1
  64. package/dist/factory-assets/connectors/box.js +11 -5
  65. package/dist/factory-assets/connectors/deploy.js +11 -5
  66. package/dist/factory-assets/connectors/devserver.js +11 -5
  67. package/dist/factory-assets/connectors/flow/CONNECTOR.md +80 -11
  68. package/dist/factory-assets/connectors/flow/adapter.d.ts +97 -5
  69. package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
  70. package/dist/factory-assets/connectors/flow/adapter.js +11 -5
  71. package/dist/factory-assets/connectors/flow/contract/expression.d.ts +2 -24
  72. package/dist/factory-assets/connectors/flow/contract/expression.d.ts.map +1 -1
  73. package/dist/factory-assets/connectors/flow/contract/flow.v2.schema.json +0 -6
  74. package/dist/factory-assets/connectors/flow/contract/normalize.d.ts.map +1 -1
  75. package/dist/factory-assets/connectors/flow/contract/schema.d.ts +0 -4
  76. package/dist/factory-assets/connectors/flow/contract/schema.d.ts.map +1 -1
  77. package/dist/factory-assets/connectors/flow/contract.js +2 -2
  78. package/dist/factory-assets/connectors/flow/engine/bindings.d.ts.map +1 -1
  79. package/dist/factory-assets/connectors/flow/engine/check-provenance.d.ts +47 -0
  80. package/dist/factory-assets/connectors/flow/engine/check-provenance.d.ts.map +1 -0
  81. package/dist/factory-assets/connectors/flow/engine/index.d.ts +3 -0
  82. package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
  83. package/dist/factory-assets/connectors/flow/engine/node-attempts.d.ts +68 -0
  84. package/dist/factory-assets/connectors/flow/engine/node-attempts.d.ts.map +1 -0
  85. package/dist/factory-assets/connectors/flow/engine/node-deadline.d.ts +44 -0
  86. package/dist/factory-assets/connectors/flow/engine/node-deadline.d.ts.map +1 -0
  87. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts +15 -1
  88. package/dist/factory-assets/connectors/flow/engine/node-shape.d.ts.map +1 -1
  89. package/dist/factory-assets/connectors/flow/engine/types.d.ts +16 -15
  90. package/dist/factory-assets/connectors/flow/engine/types.d.ts.map +1 -1
  91. package/dist/factory-assets/connectors/flow/engine.js +3 -3
  92. package/dist/factory-assets/connectors/flow/index.d.ts +2 -1
  93. package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
  94. package/dist/factory-assets/connectors/flow/inline-sub-flow-host.d.ts +3 -0
  95. package/dist/factory-assets/connectors/flow/inline-sub-flow-host.d.ts.map +1 -1
  96. package/dist/factory-assets/connectors/flow/node-executor.d.ts +10 -4
  97. package/dist/factory-assets/connectors/flow/node-executor.d.ts.map +1 -1
  98. package/dist/factory-assets/connectors/flow/prompt-fragments.d.ts +14 -0
  99. package/dist/factory-assets/connectors/flow/prompt-fragments.d.ts.map +1 -1
  100. package/dist/factory-assets/connectors/flow/prompt-fragments.js +2 -2
  101. package/dist/factory-assets/connectors/flow/run-flow.d.ts.map +1 -1
  102. package/dist/factory-assets/connectors/flow/run-flow.js +12 -6
  103. package/dist/factory-assets/connectors/flow/subprompt-runner.d.ts +99 -0
  104. package/dist/factory-assets/connectors/flow/subprompt-runner.d.ts.map +1 -0
  105. package/dist/factory-assets/connectors/flow.js +11 -5
  106. package/dist/factory-assets/connectors/git.js +11 -5
  107. package/dist/factory-assets/connectors/gmail.js +11 -5
  108. package/dist/factory-assets/connectors/googledrive.js +11 -5
  109. package/dist/factory-assets/connectors/local.js +11 -5
  110. package/dist/factory-assets/connectors/mattermost.js +11 -5
  111. package/dist/factory-assets/connectors/memory.js +11 -5
  112. package/dist/factory-assets/connectors/minio.js +11 -5
  113. package/dist/factory-assets/connectors/postgres.js +11 -5
  114. package/dist/factory-assets/connectors/s3.js +11 -5
  115. package/dist/factory-assets/connectors/sharepoint.js +11 -5
  116. package/dist/factory-assets/connectors/sqlite.js +11 -5
  117. package/dist/factory-assets/connectors/static-server.js +11 -5
  118. package/dist/factory-assets/connectors/tunnel.js +11 -5
  119. package/dist/factory-assets/connectors/webdav.js +11 -5
  120. package/dist/factory-assets/connectors/xstate-store.js +11 -5
  121. package/dist/factory-assets/connectors/xstate.js +11 -5
  122. package/dist/{flows-K7B253U6.js → flows-LQSVM45J.js} +3 -3
  123. package/dist/{flows-K7B253U6.js.map → flows-LQSVM45J.js.map} +1 -1
  124. package/dist/{helpers-PXEHIKF7.js → helpers-IS4MSEOQ.js} +4 -4
  125. package/dist/{helpers-PXEHIKF7.js.map → helpers-IS4MSEOQ.js.map} +1 -1
  126. package/dist/{library-publish-3Z33J2ZY.js → library-publish-QJACP6FW.js} +9 -9
  127. package/dist/{library-publish-3Z33J2ZY.js.map → library-publish-QJACP6FW.js.map} +1 -1
  128. package/dist/{open-library-Y5SKFAMK.js → open-library-3FMLAU3F.js} +8 -8
  129. package/dist/{open-library-Y5SKFAMK.js.map → open-library-3FMLAU3F.js.map} +1 -1
  130. package/dist/{project-DQRC6J6M.js → project-KEAAGHSX.js} +5 -5
  131. package/dist/{project-DQRC6J6M.js.map → project-KEAAGHSX.js.map} +1 -1
  132. package/dist/runner/index.js +15 -15
  133. package/dist/runner/src/serve.d.ts.map +1 -1
  134. package/dist/sdk/asset-manager.js +1 -1
  135. package/dist/sdk/bridge.js +3 -3
  136. package/dist/sdk/flow.js +3 -3
  137. package/dist/sdk/index.js +15 -15
  138. package/dist/sdk/runner.js +15 -15
  139. package/dist/{setup-RHWARBBB.js → setup-VX6EPF5P.js} +5 -5
  140. package/dist/{setup-RHWARBBB.js.map → setup-VX6EPF5P.js.map} +1 -1
  141. package/dist/{source-XBJ5KTAV.js → source-45XHQSBF.js} +9 -9
  142. package/dist/{source-XBJ5KTAV.js.map → source-45XHQSBF.js.map} +1 -1
  143. package/dist/{source-manifest-publish-Q7TXBMCW.js → source-manifest-publish-M34SKD46.js} +10 -10
  144. package/dist/{source-manifest-publish-Q7TXBMCW.js.map → source-manifest-publish-M34SKD46.js.map} +1 -1
  145. package/dist/tui/index.js +15 -15
  146. package/dist/types/src/flow.d.ts +128 -0
  147. package/dist/types/src/flow.d.ts.map +1 -1
  148. package/dist/types/src/index.d.ts +1 -1
  149. package/dist/types/src/index.d.ts.map +1 -1
  150. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  151. package/dist/workspace-plugin/adapters/omp.js +3 -3
  152. package/dist/workspace-plugin/index.js +1 -1
  153. package/package.json +1 -1
  154. package/dist/chunk-BMQKMOQY.js.map +0 -1
  155. package/dist/chunk-E4AVJMOJ.js.map +0 -1
  156. package/dist/chunk-HVQWLYZB.js.map +0 -1
  157. package/dist/chunk-OAUKA7VT.js.map +0 -1
  158. package/dist/chunk-P3DUVOAW.js.map +0 -1
  159. package/dist/chunk-SCR3WGW7.js.map +0 -1
  160. package/dist/chunk-TUYN2HDT.js.map +0 -1
  161. package/dist/chunk-XJ74XFIZ.js.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { resolveAuthRef } from './chunk-ZNIHTT22.js';
2
+ import { EventNormalizer } from './chunk-P5W6JVD4.js';
3
+ import { resolveModelTier } from './chunk-V34ZEXSE.js';
4
+ import { createDriver } from './chunk-SR6NBBWT.js';
2
5
  import { pluginRegistry } from './chunk-6E6PKKAD.js';
3
6
  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';
7
+ import { getConsumedOutputSchema, validateConsumedNodeOutput, appendAttempt, remainingActiveExecutionMs, resolveNodeBindings, interpolateNodeInstruction, deriveCheckEvidence, computeFlowStateFromSnapshots, availableRouterFrontier, evaluateRouterSelection, routerSelectionOutput, planRouterSkips, unsettledRouterBranchIds, countFailedAttempts, hasAutomaticRetryBudget, persistedRouterSelection } from './chunk-S5FV6ISY.js';
8
+ import { parseFlowDefinition } from './chunk-5QGMEPUX.js';
9
+ import { renderSubpromptPrompt, renderStimulusPrompt } from './chunk-7KFN2EPR.js';
10
+ import { deterministicNodeKindLabel, flowDefaults, isAgentNode, isGateNode, isCheckNode, nodeRequiresApproval, isSubFlowNode, isFunctionNode, isSubpromptNode, nodeIsOptional, nodeInstruction, isRouterNode, isActiveRuntimeNode, nodeLabel, nodeAssets } from './chunk-F5VREBV2.js';
8
11
  import { portableSpawn } from './chunk-P43HBWKK.js';
9
12
  import { withFsDeadline, tryWithFsDeadline, FsDeadlineError, fsDeadlineMs } from './chunk-DYMWJAL4.js';
10
13
  import { createLogger } from './chunk-7GOCNYUV.js';
@@ -185,6 +188,7 @@ var SessionInlineSubFlowRunner = class {
185
188
  inlineSubFlowAncestry: [...request.ancestry, flow.id],
186
189
  inlineSubFlowRunner: this,
187
190
  nodeExecutor: this.options.nodeExecutor,
191
+ subpromptRunner: this.options.subpromptRunner,
188
192
  seed: {
189
193
  runId: `${request.parentExecution.runId}:${request.node.id}`,
190
194
  startedBy: request.parentExecution.startedBy,
@@ -324,11 +328,11 @@ function stableJsonStringify(value) {
324
328
  );
325
329
  });
326
330
  }
327
- function serializeEnvironmentInput(name, value) {
331
+ function serializeEnvironmentInput(name, value, nodeLabel2) {
328
332
  if (typeof value === "string") return value;
329
333
  const serialized = stableJsonStringify(value);
330
334
  if (serialized === void 0) {
331
- throw new Error(`Function node input ${name} is not JSON-serializable`);
335
+ throw new Error(`${nodeLabel2} input ${name} is not JSON-serializable`);
332
336
  }
333
337
  return serialized;
334
338
  }
@@ -348,19 +352,18 @@ var SessionNodeExecutor = class {
348
352
  constructor(options) {
349
353
  this.workspaceDir = resolve(options.workspaceDir);
350
354
  }
351
- /** Runs one function node process with session environment and binding overrides. */
355
+ /** Runs one function or check node process with session environment and binding overrides. */
352
356
  async execute(request) {
357
+ const nodeLabel2 = `${deterministicNodeKindLabel(request.node.run.kind)} node ${request.node.id}`;
353
358
  const cwd = resolve(this.workspaceDir, request.node.run.cwd ?? ".");
354
359
  const workspaceRelative = relative(this.workspaceDir, cwd);
355
360
  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
- );
361
+ throw new Error(`${nodeLabel2} cwd escapes the session workspace: ${request.node.run.cwd}`);
359
362
  }
360
363
  const boundEnvironment = Object.fromEntries(
361
364
  Object.entries(request.inputs).map(([name, value]) => [
362
365
  name,
363
- serializeEnvironmentInput(name, value)
366
+ serializeEnvironmentInput(name, value, nodeLabel2)
364
367
  ])
365
368
  );
366
369
  const process2 = portableSpawn(commandFor(request.node), {
@@ -375,6 +378,78 @@ var SessionNodeExecutor = class {
375
378
  return { exitStatus, stdout, stderr };
376
379
  }
377
380
  };
381
+
382
+ // factory-assets/connectors/flow/subprompt-runner.ts
383
+ var SessionSubpromptRunner = class {
384
+ constructor(options) {
385
+ this.options = options;
386
+ }
387
+ options;
388
+ async execute(request) {
389
+ const model = resolveModelTier(request.node.run.model, this.options.sessionModel, {
390
+ provider: this.options.provider,
391
+ cloud: this.options.cloud,
392
+ overrides: this.options.modelTierOverrides
393
+ });
394
+ const config = {
395
+ cwd: this.options.workspaceDir,
396
+ provider: this.options.provider,
397
+ model,
398
+ // A subprompt is one model call by definition. Backends default to a
399
+ // multi-turn agentic loop (claude-sdk: 15), which would let a node that
400
+ // was asked to classify a string go off and work instead.
401
+ maxTurns: 1,
402
+ ...this.options.apiKey ? { apiKeys: { [this.options.provider ?? "anthropic"]: this.options.apiKey } } : {},
403
+ ...this.options.cloud && this.options.cloud !== "default" ? { cloud: this.options.cloud } : {},
404
+ ...this.options.cloudConfig ? { cloudConfig: this.options.cloudConfig } : {},
405
+ ...this.options.cloudSecrets ? { cloudSecrets: this.options.cloudSecrets } : {}
406
+ };
407
+ const driver = await createDriver(this.options.driverType, config);
408
+ try {
409
+ await driver.start();
410
+ const text = await runIsolatedPrompt(
411
+ driver,
412
+ request.prompt,
413
+ this.options.timeoutMs ?? DEFAULT_SUBPROMPT_TIMEOUT_MS
414
+ );
415
+ return { text };
416
+ } finally {
417
+ driver.kill();
418
+ }
419
+ }
420
+ };
421
+ var DEFAULT_SUBPROMPT_TIMEOUT_MS = 5 * 6e4;
422
+ function runIsolatedPrompt(driver, prompt, timeoutMs) {
423
+ return new Promise((resolve6, reject) => {
424
+ const normalizer = new EventNormalizer();
425
+ let text = "";
426
+ let settled = false;
427
+ const done = (err) => {
428
+ if (settled) return;
429
+ settled = true;
430
+ clearTimeout(timer);
431
+ driver.off("agent-event", onEvent);
432
+ if (err) reject(err);
433
+ else resolve6(text);
434
+ };
435
+ const timer = setTimeout(
436
+ () => done(new Error(`isolated subprompt call exceeded ${timeoutMs}ms`)),
437
+ timeoutMs
438
+ );
439
+ const onEvent = (event) => {
440
+ for (const normalized of normalizer.normalize(event)) {
441
+ if (normalized.type === "text") text += normalized.content;
442
+ }
443
+ if (event.type === "agent_end") {
444
+ done();
445
+ } else if (event.type === "error") {
446
+ done(new Error(event.error));
447
+ }
448
+ };
449
+ driver.on("agent-event", onEvent);
450
+ driver.prompt(prompt).catch(done);
451
+ });
452
+ }
378
453
  var execFileAsync = promisify(execFile);
379
454
  var GIT_REV_PARSE_TIMEOUT_MS = 15e3;
380
455
  var DeployConnector = class extends AbstractConnector {
@@ -7487,6 +7562,28 @@ var AGENT_INLINE_OPERATIONS = /* @__PURE__ */ new Set([
7487
7562
  "skip_node",
7488
7563
  "fail_node"
7489
7564
  ]);
7565
+ var NodeTimeoutError = class extends Error {
7566
+ };
7567
+ function raceWithTimeout(promise, ms, timeoutMessage) {
7568
+ return new Promise((resolve6, reject) => {
7569
+ const timer = setTimeout(() => {
7570
+ reject(new NodeTimeoutError(timeoutMessage));
7571
+ promise.catch(() => {
7572
+ });
7573
+ }, ms);
7574
+ timer.unref?.();
7575
+ promise.then(
7576
+ (value) => {
7577
+ clearTimeout(timer);
7578
+ resolve6(value);
7579
+ },
7580
+ (err) => {
7581
+ clearTimeout(timer);
7582
+ reject(err);
7583
+ }
7584
+ );
7585
+ });
7586
+ }
7490
7587
  function mergeInteractionHistory(history, interaction) {
7491
7588
  if (!interaction) return history;
7492
7589
  const next = [...history];
@@ -7507,7 +7604,8 @@ function emptyNode(nodeId) {
7507
7604
  status: "not_started",
7508
7605
  approvalHistory: [],
7509
7606
  inputHistory: [],
7510
- errorHistory: []
7607
+ errorHistory: [],
7608
+ outputHistory: []
7511
7609
  };
7512
7610
  }
7513
7611
  function nodeExecutionRecord(entries = []) {
@@ -7580,7 +7678,7 @@ function prepareExecutionSnapshot(flowDef, execution) {
7580
7678
  const nodes = nodeExecutionRecord(Object.entries(snapshot.nodes ?? {}));
7581
7679
  for (const node of flowDef.nodes) {
7582
7680
  const existing = nodes[node.id];
7583
- if (isSubFlowNode(node) && existing && ["running", "awaiting_approval", "awaiting_input"].includes(existing.status)) {
7681
+ if ((isSubFlowNode(node) || isSubpromptNode(node)) && existing && ["running", "awaiting_approval", "awaiting_input"].includes(existing.status)) {
7584
7682
  nodes[node.id] = {
7585
7683
  ...existing,
7586
7684
  status: "not_started",
@@ -7644,18 +7742,23 @@ var FlowAdapter = class extends AbstractConnector {
7644
7742
  connectorId: declaration.id,
7645
7743
  useBusForStimulus: options.useBusForStimulus ?? false,
7646
7744
  nodeExecutor: options.nodeExecutor,
7745
+ subpromptRunner: options.subpromptRunner,
7647
7746
  inlineSubFlowRunner: options.inlineSubFlowRunner,
7648
7747
  inlineOperationTargets: /* @__PURE__ */ new Map(),
7649
7748
  flowInputOrigins: options.flowInputOrigins,
7650
7749
  inlineSubFlowAncestry: options.inlineSubFlowAncestry ?? [flowDef.id],
7651
- executionGeneration: 0
7750
+ executionGeneration: 0,
7751
+ wakeTimer: null
7652
7752
  };
7653
7753
  state.execution = this.recompute(state);
7654
- return this.makeHandle(declaration, state);
7754
+ const handle = this.makeHandle(declaration, state);
7755
+ this.rearmWakeTimer(handle);
7756
+ return handle;
7655
7757
  }
7656
7758
  async disconnect(handle) {
7657
7759
  const state = this.s(handle);
7658
7760
  state.executionGeneration += 1;
7761
+ this.clearWakeTimer(state);
7659
7762
  await this.cancelInlineTargets(state);
7660
7763
  handle.status = "disconnected";
7661
7764
  }
@@ -7703,6 +7806,7 @@ var FlowAdapter = class extends AbstractConnector {
7703
7806
  const changed = JSON.stringify(prepared) !== JSON.stringify(execution);
7704
7807
  s.executionGeneration += 1;
7705
7808
  s.execution = prepared;
7809
+ this.rearmWakeTimer(handle);
7706
7810
  if (changed && s.onChange) s.onChange(s.execution);
7707
7811
  }
7708
7812
  // ── Base operations (private — wired into tools face in constructor) ──
@@ -8315,17 +8419,27 @@ var FlowAdapter = class extends AbstractConnector {
8315
8419
  this.mutate(handle, (current) => {
8316
8420
  const execution = current.execution.nodes[nodeId];
8317
8421
  assertLegal(execution, `node not in execution: ${nodeId}`);
8422
+ const completedAt = now();
8318
8423
  const error = {
8319
8424
  message: `Node ${nodeId} output failed schema validation after one correction: ${issueText}`,
8320
8425
  recoverable: false,
8321
- at: now()
8426
+ at: completedAt
8322
8427
  };
8428
+ const outputHistory = execution.startedAt ? appendAttempt(
8429
+ execution.outputHistory,
8430
+ "failed",
8431
+ execution.startedAt,
8432
+ completedAt,
8433
+ execution.output,
8434
+ error
8435
+ ) : execution.outputHistory;
8323
8436
  current.execution.nodes[nodeId] = {
8324
8437
  ...execution,
8325
8438
  status: "failed",
8326
- completedAt: now(),
8439
+ completedAt,
8327
8440
  error,
8328
- errorHistory: [...execution.errorHistory, error]
8441
+ errorHistory: [...execution.errorHistory, error],
8442
+ outputHistory
8329
8443
  };
8330
8444
  });
8331
8445
  throw new Error(
@@ -8335,11 +8449,21 @@ var FlowAdapter = class extends AbstractConnector {
8335
8449
  return this.mutate(handle, (current) => {
8336
8450
  const execution = current.execution.nodes[nodeId];
8337
8451
  assertLegal(execution, `node not in execution: ${nodeId}`);
8452
+ const completedAt = now();
8453
+ const outputHistory = execution.startedAt ? appendAttempt(
8454
+ execution.outputHistory,
8455
+ "complete",
8456
+ execution.startedAt,
8457
+ completedAt,
8458
+ output,
8459
+ void 0
8460
+ ) : execution.outputHistory;
8338
8461
  current.execution.nodes[nodeId] = {
8339
8462
  ...execution,
8340
8463
  status: "complete",
8341
- completedAt: now(),
8342
- output
8464
+ completedAt,
8465
+ output,
8466
+ outputHistory
8343
8467
  };
8344
8468
  return JSON.stringify({ ok: true, status: "complete" });
8345
8469
  });
@@ -8357,10 +8481,20 @@ var FlowAdapter = class extends AbstractConnector {
8357
8481
  `cannot skip node ${nodeId}: it is a mandatory approval gate \u2014 call request_approval and wait for an approved decision`
8358
8482
  );
8359
8483
  assertLegal(isOptional(s.flowDef, nodeId), `cannot skip required node ${nodeId}`);
8484
+ const completedAt = now();
8485
+ const outputHistory = node.startedAt ? appendAttempt(
8486
+ node.outputHistory,
8487
+ "skipped",
8488
+ node.startedAt,
8489
+ completedAt,
8490
+ node.output,
8491
+ void 0
8492
+ ) : node.outputHistory;
8360
8493
  s.execution.nodes[nodeId] = {
8361
8494
  ...node,
8362
8495
  status: "skipped",
8363
- completedAt: now()
8496
+ completedAt,
8497
+ outputHistory
8364
8498
  };
8365
8499
  return JSON.stringify({ ok: true, status: "skipped" });
8366
8500
  });
@@ -8375,19 +8509,40 @@ var FlowAdapter = class extends AbstractConnector {
8375
8509
  assertLegal(node, `node not in execution: ${nodeId}`);
8376
8510
  const error = { message, recoverable, at: now() };
8377
8511
  if (recoverable) {
8512
+ const completedAt = error.at;
8513
+ const outputHistory = node.startedAt ? appendAttempt(
8514
+ node.outputHistory,
8515
+ "failed",
8516
+ node.startedAt,
8517
+ completedAt,
8518
+ node.output,
8519
+ error,
8520
+ { selfRetry: true }
8521
+ ) : node.outputHistory;
8378
8522
  s.execution.nodes[nodeId] = {
8379
8523
  ...node,
8380
8524
  status: "available",
8381
8525
  error,
8382
- errorHistory: [...node.errorHistory, error]
8526
+ errorHistory: [...node.errorHistory, error],
8527
+ outputHistory
8383
8528
  };
8384
8529
  } else {
8530
+ const completedAt = now();
8531
+ const outputHistory = node.startedAt ? appendAttempt(
8532
+ node.outputHistory,
8533
+ "failed",
8534
+ node.startedAt,
8535
+ completedAt,
8536
+ node.output,
8537
+ error
8538
+ ) : node.outputHistory;
8385
8539
  s.execution.nodes[nodeId] = {
8386
8540
  ...node,
8387
8541
  status: "failed",
8388
- completedAt: now(),
8542
+ completedAt,
8389
8543
  error,
8390
- errorHistory: [...node.errorHistory, error]
8544
+ errorHistory: [...node.errorHistory, error],
8545
+ outputHistory
8391
8546
  };
8392
8547
  }
8393
8548
  return JSON.stringify({ ok: true, status: s.execution.nodes[nodeId].status });
@@ -8397,7 +8552,9 @@ var FlowAdapter = class extends AbstractConnector {
8397
8552
  /**
8398
8553
  * Record an approval decision on a node currently `awaiting_approval`.
8399
8554
  * A first-class gate completes directly when approved and remains parked when rejected.
8400
- * Agent-node approvals keep their existing resume behavior.
8555
+ * Agent-node approvals keep their existing resume behavior. A check's escalation is a
8556
+ * one-way door: only `rejected` is legal, and it ends the node terminally `failed` —
8557
+ * there is no decision that resumes or passes a check.
8401
8558
  */
8402
8559
  applyApproval(handle, nodeId, decision, feedback, decidedBy) {
8403
8560
  this.mutate(handle, (s) => {
@@ -8414,6 +8571,12 @@ var FlowAdapter = class extends AbstractConnector {
8414
8571
  `applyApproval: gate ${nodeId} approval attempt is already decided; request approval again before applying another decision`
8415
8572
  );
8416
8573
  }
8574
+ if (isCheckNode(definition)) {
8575
+ assertLegal(
8576
+ decision === "rejected",
8577
+ `applyApproval: check ${nodeId} cannot be approved \u2014 a failed check has no passing state to approve into; only "rejected" is a legal decision`
8578
+ );
8579
+ }
8417
8580
  const base = node.approval ?? { requestedAt: now(), summary: "" };
8418
8581
  const approval = {
8419
8582
  ...base,
@@ -8440,6 +8603,14 @@ var FlowAdapter = class extends AbstractConnector {
8440
8603
  approval,
8441
8604
  approvalHistory: history
8442
8605
  };
8606
+ } else if (isCheckNode(definition)) {
8607
+ s.execution.nodes[nodeId] = {
8608
+ ...node,
8609
+ status: "failed",
8610
+ completedAt: now(),
8611
+ approval,
8612
+ approvalHistory: history
8613
+ };
8443
8614
  } else if (decision === "approved") {
8444
8615
  s.execution.nodes[nodeId] = { ...node, approval, approvalHistory: history };
8445
8616
  } else {
@@ -8513,10 +8684,17 @@ var FlowAdapter = class extends AbstractConnector {
8513
8684
  return void 0;
8514
8685
  });
8515
8686
  }
8516
- /** Cancel the flow run. Status transitions to `cancelled`; dependents stay put. */
8687
+ /**
8688
+ * Cancel the flow run. Status transitions to `cancelled`; dependents stay
8689
+ * put. Bumps `executionGeneration` — the sole late-settlement rejection
8690
+ * mechanism every in-flight await (function executor, sub-flow child)
8691
+ * checks after resolving, so a result that arrives after cancellation can
8692
+ * never write state.
8693
+ */
8517
8694
  cancel(handle) {
8518
8695
  this.mutate(handle, (s) => {
8519
8696
  s.execution = { ...s.execution, status: "cancelled" };
8697
+ s.executionGeneration += 1;
8520
8698
  return void 0;
8521
8699
  });
8522
8700
  }
@@ -8586,9 +8764,11 @@ var FlowAdapter = class extends AbstractConnector {
8586
8764
  return this.drainAvailableRuntimeNodes(handle, true);
8587
8765
  }
8588
8766
  /**
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.
8767
+ * Run every currently available function and check node through the
8768
+ * configured NodeExecutor. Newly-unblocked function/check nodes are
8769
+ * drained in definition order before returning control to the runner's
8770
+ * agent-turn decision. Routers and gates are excluded — see
8771
+ * {@link drainRuntimeNodes} for the full drain.
8592
8772
  */
8593
8773
  async executeAvailableFunctionNodes(handle) {
8594
8774
  return this.drainAvailableRuntimeNodes(handle, false);
@@ -8604,7 +8784,7 @@ var FlowAdapter = class extends AbstractConnector {
8604
8784
  if (isSubFlowNode(candidate) && !advancedSubFlows.has(candidate.id) && (status === "available" || status === "running")) {
8605
8785
  return true;
8606
8786
  }
8607
- return (isFunctionNode(candidate) || includeRoutersAndGates && isGateNode(candidate)) && status === "available";
8787
+ return (isFunctionNode(candidate) || isCheckNode(candidate) || isSubpromptNode(candidate) || includeRoutersAndGates && isGateNode(candidate)) && status === "available";
8608
8788
  });
8609
8789
  if (!node) break;
8610
8790
  if (isGateNode(node)) {
@@ -8653,6 +8833,9 @@ var FlowAdapter = class extends AbstractConnector {
8653
8833
  );
8654
8834
  }
8655
8835
  const executionGeneration = state.executionGeneration;
8836
+ const timeoutSec = node.control?.timeoutSec;
8837
+ const runningExecution = state.execution.nodes[node.id];
8838
+ const remainingMs = runningExecution ? remainingActiveExecutionMs(runningExecution, timeoutSec, Date.now()) : void 0;
8656
8839
  let result;
8657
8840
  try {
8658
8841
  const runner = state.inlineSubFlowRunner;
@@ -8665,7 +8848,7 @@ var FlowAdapter = class extends AbstractConnector {
8665
8848
  snapshotMap(state.execution),
8666
8849
  state.flowInputOrigins
8667
8850
  );
8668
- result = await runner.run({
8851
+ const runPromise = runner.run({
8669
8852
  node,
8670
8853
  flowId: node.run.flow,
8671
8854
  inputs: resolved.values,
@@ -8682,50 +8865,210 @@ var FlowAdapter = class extends AbstractConnector {
8682
8865
  else state.inlineOperationTargets.delete(node.id);
8683
8866
  }
8684
8867
  });
8868
+ result = remainingMs !== void 0 ? await raceWithTimeout(
8869
+ runPromise,
8870
+ Math.max(0, remainingMs),
8871
+ `Sub-flow node ${node.id} timed out after ${timeoutSec}s`
8872
+ ) : await runPromise;
8685
8873
  } 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()
8874
+ if (error instanceof NodeTimeoutError) {
8875
+ const target = state.inlineOperationTargets.get(node.id);
8876
+ if (target) {
8877
+ state.inlineOperationTargets.delete(node.id);
8878
+ void target.cancel().catch(() => {
8879
+ });
8692
8880
  }
8693
- };
8881
+ result = {
8882
+ status: "failed",
8883
+ error: { message: error.message, recoverable: false, at: now() }
8884
+ };
8885
+ } else {
8886
+ result = {
8887
+ status: "failed",
8888
+ error: {
8889
+ message: `Sub-flow node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`,
8890
+ recoverable: false,
8891
+ at: now()
8892
+ }
8893
+ };
8894
+ }
8694
8895
  }
8695
8896
  if (this.s(handle).executionGeneration !== executionGeneration) break;
8696
8897
  if (result.status === "paused") {
8697
8898
  this.projectInlineInteraction(handle, node.id, result.projection);
8698
8899
  continue;
8699
8900
  }
8700
- if (this.s(handle).execution.status === "cancelled") break;
8901
+ let subFlowRetrying = false;
8701
8902
  this.mutate(
8702
8903
  handle,
8703
8904
  (current) => {
8704
8905
  const execution2 = current.execution.nodes[node.id];
8705
8906
  assertLegal(execution2, `node not in execution: ${node.id}`);
8907
+ const completedAt = now();
8706
8908
  if (result.status === "complete") {
8909
+ const outputHistory = appendAttempt(
8910
+ execution2.outputHistory,
8911
+ "complete",
8912
+ execution2.startedAt ?? completedAt,
8913
+ completedAt,
8914
+ result.output,
8915
+ void 0
8916
+ );
8707
8917
  current.execution.nodes[node.id] = {
8708
8918
  ...execution2,
8709
8919
  status: "complete",
8710
- completedAt: now(),
8711
- output: result.output
8920
+ completedAt,
8921
+ output: result.output,
8922
+ outputHistory
8712
8923
  };
8713
8924
  return;
8714
8925
  }
8715
- current.execution.nodes[node.id] = {
8926
+ const { retry, patch } = this.planAutomaticRetry(
8927
+ node,
8928
+ execution2,
8929
+ result.error.message,
8930
+ completedAt,
8931
+ result.output,
8932
+ result.error.at
8933
+ );
8934
+ subFlowRetrying = retry;
8935
+ current.execution.nodes[node.id] = retry ? { ...execution2, ...patch, status: "available", error: void 0 } : {
8716
8936
  ...execution2,
8937
+ ...patch,
8717
8938
  status: nodeIsOptional(node) ? "skipped" : "failed",
8718
- completedAt: now(),
8719
- output: result.output,
8720
- error: result.error,
8721
- errorHistory: [...execution2.errorHistory, result.error]
8939
+ completedAt
8940
+ };
8941
+ },
8942
+ false
8943
+ );
8944
+ if (subFlowRetrying) advancedSubFlows.delete(node.id);
8945
+ continue;
8946
+ }
8947
+ if (isSubpromptNode(node)) {
8948
+ this.mutate(
8949
+ handle,
8950
+ (current) => {
8951
+ const execution = current.execution.nodes[node.id];
8952
+ assertLegal(execution, `node not in execution: ${node.id}`);
8953
+ assertLegal(
8954
+ execution.status === "available",
8955
+ `cannot execute subprompt node ${node.id}: status is ${execution.status}`
8956
+ );
8957
+ current.execution.nodes[node.id] = {
8958
+ ...execution,
8959
+ status: "running",
8960
+ startedAt: now()
8722
8961
  };
8723
8962
  },
8724
8963
  false
8725
8964
  );
8965
+ const subpromptExecutionGeneration = state.executionGeneration;
8966
+ const subpromptTimeoutSec = node.control?.timeoutSec;
8967
+ const subpromptRunningExecution = state.execution.nodes[node.id];
8968
+ const subpromptRemainingMs = subpromptRunningExecution ? remainingActiveExecutionMs(subpromptRunningExecution, subpromptTimeoutSec, Date.now()) : void 0;
8969
+ let subpromptOutput;
8970
+ let subpromptOutcome;
8971
+ try {
8972
+ const runner = state.subpromptRunner;
8973
+ if (!runner) {
8974
+ throw new Error(`no SubpromptRunner configured for subprompt node ${node.id}`);
8975
+ }
8976
+ const resolved = resolveNodeBindings(
8977
+ state.flowDef,
8978
+ node,
8979
+ snapshotMap(state.execution),
8980
+ state.flowInputOrigins
8981
+ );
8982
+ const instruction = nodeInstruction(node);
8983
+ assertLegal(instruction, `subprompt node ${node.id} has no instruction`);
8984
+ const outputSchema = node.contract?.output?.fields;
8985
+ const prompt = renderSubpromptPrompt(
8986
+ interpolateNodeInstruction(instruction, resolved.values),
8987
+ outputSchema
8988
+ );
8989
+ const runPromise = runner.execute({
8990
+ node,
8991
+ prompt,
8992
+ inputs: resolved.values,
8993
+ inputOrigins: resolved.origins
8994
+ });
8995
+ const result = subpromptRemainingMs !== void 0 ? await raceWithTimeout(
8996
+ runPromise,
8997
+ Math.max(0, subpromptRemainingMs),
8998
+ `Subprompt node ${node.id} timed out after ${subpromptTimeoutSec}s`
8999
+ ) : await runPromise;
9000
+ subpromptOutput = subpromptNodeOutput(node, result.text);
9001
+ const validation = validateConsumedNodeOutput(
9002
+ state.flowDef,
9003
+ node.id,
9004
+ subpromptOutput.fields
9005
+ );
9006
+ if (!validation.valid) {
9007
+ throw new Error(
9008
+ `output does not match its consumed schema: ${validation.issues.join("; ")}`
9009
+ );
9010
+ }
9011
+ subpromptOutcome = { kind: "success" };
9012
+ } catch (error) {
9013
+ subpromptOutcome = error instanceof NodeTimeoutError ? { kind: "failure", message: error.message } : {
9014
+ kind: "failure",
9015
+ message: `Subprompt node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`
9016
+ };
9017
+ }
9018
+ if (this.s(handle).executionGeneration !== subpromptExecutionGeneration) break;
9019
+ if (subpromptOutcome.kind === "success") {
9020
+ this.mutate(
9021
+ handle,
9022
+ (current) => {
9023
+ const execution = current.execution.nodes[node.id];
9024
+ assertLegal(execution, `node not in execution: ${node.id}`);
9025
+ const completedAt = now();
9026
+ const outputHistory = appendAttempt(
9027
+ execution.outputHistory,
9028
+ "complete",
9029
+ execution.startedAt ?? completedAt,
9030
+ completedAt,
9031
+ subpromptOutput,
9032
+ void 0
9033
+ );
9034
+ current.execution.nodes[node.id] = {
9035
+ ...execution,
9036
+ status: "complete",
9037
+ completedAt,
9038
+ output: subpromptOutput,
9039
+ outputHistory
9040
+ };
9041
+ },
9042
+ false
9043
+ );
9044
+ } else {
9045
+ const failureMessage = subpromptOutcome.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
+ subpromptOutput
9058
+ );
9059
+ current.execution.nodes[node.id] = retry ? { ...execution, ...patch, status: "available", error: void 0 } : {
9060
+ ...execution,
9061
+ ...patch,
9062
+ status: nodeIsOptional(node) ? "skipped" : "failed",
9063
+ completedAt
9064
+ };
9065
+ },
9066
+ false
9067
+ );
9068
+ }
8726
9069
  continue;
8727
9070
  }
8728
- if (!isFunctionNode(node)) continue;
9071
+ if (!isFunctionNode(node) && !isCheckNode(node)) continue;
8729
9072
  this.mutate(
8730
9073
  handle,
8731
9074
  (current) => {
@@ -8733,7 +9076,7 @@ var FlowAdapter = class extends AbstractConnector {
8733
9076
  assertLegal(execution, `node not in execution: ${node.id}`);
8734
9077
  assertLegal(
8735
9078
  execution.status === "available",
8736
- `cannot execute function node ${node.id}: status is ${execution.status}`
9079
+ `cannot execute ${node.run.kind} node ${node.id}: status is ${execution.status}`
8737
9080
  );
8738
9081
  current.execution.nodes[node.id] = {
8739
9082
  ...execution,
@@ -8743,73 +9086,178 @@ var FlowAdapter = class extends AbstractConnector {
8743
9086
  },
8744
9087
  false
8745
9088
  );
9089
+ const deterministicExecutionGeneration = state.executionGeneration;
9090
+ const deterministicTimeoutSec = node.control?.timeoutSec;
9091
+ const deterministicRunningExecution = state.execution.nodes[node.id];
9092
+ const deterministicRemainingMs = deterministicRunningExecution ? remainingActiveExecutionMs(
9093
+ deterministicRunningExecution,
9094
+ deterministicTimeoutSec,
9095
+ Date.now()
9096
+ ) : void 0;
9097
+ const deterministicKindLabel = deterministicNodeKindLabel(node.run.kind);
8746
9098
  let capturedOutput;
9099
+ let checkEvidence;
9100
+ let deterministicOutcome;
8747
9101
  try {
8748
9102
  const executor = state.nodeExecutor;
8749
- if (!executor) throw new Error(`no NodeExecutor configured for function node ${node.id}`);
9103
+ if (!executor)
9104
+ throw new Error(`no NodeExecutor configured for ${node.run.kind} node ${node.id}`);
8750
9105
  const resolved = resolveNodeBindings(
8751
9106
  state.flowDef,
8752
9107
  node,
8753
9108
  snapshotMap(state.execution),
8754
9109
  state.flowInputOrigins
8755
9110
  );
8756
- const result = await executor.execute({
9111
+ const runPromise = executor.execute({
8757
9112
  node,
8758
9113
  inputs: resolved.values,
8759
9114
  inputOrigins: resolved.origins
8760
9115
  });
8761
- capturedOutput = functionNodeOutput(node, result);
9116
+ const result = deterministicRemainingMs !== void 0 ? await raceWithTimeout(
9117
+ runPromise,
9118
+ Math.max(0, deterministicRemainingMs),
9119
+ `${deterministicKindLabel} node ${node.id} timed out after ${deterministicTimeoutSec}s`
9120
+ ) : await runPromise;
8762
9121
  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}` : ""}`
9122
+ const passed = successfulStatuses.includes(result.exitStatus);
9123
+ capturedOutput = deterministicNodeOutput(node, result, passed);
9124
+ const detail = passed ? "" : result.stderr.trim() || result.stdout.trim();
9125
+ const detailSuffix = detail ? `: ${detail}` : "";
9126
+ if (passed) {
9127
+ const validation = validateConsumedNodeOutput(
9128
+ state.flowDef,
9129
+ node.id,
9130
+ capturedOutput.fields
8767
9131
  );
9132
+ if (!validation.valid) {
9133
+ throw new Error(
9134
+ `output does not match its consumed schema: ${validation.issues.join("; ")}`
9135
+ );
9136
+ }
8768
9137
  }
8769
- const validation = validateConsumedNodeOutput(
8770
- state.flowDef,
8771
- node.id,
8772
- capturedOutput.fields
8773
- );
8774
- if (!validation.valid) {
9138
+ if (isCheckNode(node)) {
9139
+ checkEvidence = deriveCheckEvidence(
9140
+ resolved,
9141
+ {
9142
+ runtime: node.run.runtime ?? "shell",
9143
+ command: node.run.command,
9144
+ cwd: node.run.cwd,
9145
+ successExit: successfulStatuses,
9146
+ exitStatus: result.exitStatus,
9147
+ passed
9148
+ },
9149
+ now()
9150
+ );
9151
+ deterministicOutcome = passed ? { kind: "success" } : {
9152
+ kind: "check-fail",
9153
+ message: `Check node ${node.id} failed with exit status ${result.exitStatus}${detailSuffix}`,
9154
+ onFail: node.run.onFail
9155
+ };
9156
+ } else if (!passed) {
8775
9157
  throw new Error(
8776
- `output does not match its consumed schema: ${validation.issues.join("; ")}`
9158
+ `Function node ${node.id} exited with unsuccessful exit status ${result.exitStatus}${detailSuffix}`
8777
9159
  );
9160
+ } else {
9161
+ deterministicOutcome = { kind: "success" };
8778
9162
  }
9163
+ } catch (error) {
9164
+ deterministicOutcome = error instanceof NodeTimeoutError ? { kind: "failure", message: error.message } : {
9165
+ kind: "failure",
9166
+ message: `${deterministicKindLabel} node ${node.id} failed: ${error instanceof Error ? error.message : String(error)}`
9167
+ };
9168
+ }
9169
+ if (this.s(handle).executionGeneration !== deterministicExecutionGeneration) break;
9170
+ if (deterministicOutcome.kind === "success") {
8779
9171
  this.mutate(
8780
9172
  handle,
8781
9173
  (current) => {
8782
9174
  const execution = current.execution.nodes[node.id];
8783
9175
  assertLegal(execution, `node not in execution: ${node.id}`);
9176
+ const completedAt = now();
9177
+ const outputHistory = appendAttempt(
9178
+ execution.outputHistory,
9179
+ "complete",
9180
+ execution.startedAt ?? completedAt,
9181
+ completedAt,
9182
+ capturedOutput,
9183
+ void 0,
9184
+ { check: checkEvidence }
9185
+ );
8784
9186
  current.execution.nodes[node.id] = {
8785
9187
  ...execution,
8786
9188
  status: "complete",
8787
- completedAt: now(),
8788
- output: capturedOutput
9189
+ completedAt,
9190
+ output: capturedOutput,
9191
+ outputHistory
8789
9192
  };
8790
9193
  },
8791
9194
  false
8792
9195
  );
8793
- } catch (error) {
8794
- const message = error instanceof Error ? error.message : String(error);
9196
+ } else if (deterministicOutcome.kind === "check-fail") {
9197
+ const { message: failureMessage, onFail } = deterministicOutcome;
8795
9198
  this.mutate(
8796
9199
  handle,
8797
9200
  (current) => {
8798
9201
  const execution = current.execution.nodes[node.id];
8799
9202
  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
- };
9203
+ assertLegal(checkEvidence, `check node ${node.id} completed without evidence`);
9204
+ const completedAt = now();
9205
+ const { retry, patch } = this.planAutomaticRetry(
9206
+ node,
9207
+ execution,
9208
+ failureMessage,
9209
+ completedAt,
9210
+ capturedOutput,
9211
+ void 0,
9212
+ checkEvidence
9213
+ );
9214
+ if (retry) {
9215
+ current.execution.nodes[node.id] = {
9216
+ ...execution,
9217
+ ...patch,
9218
+ status: "available",
9219
+ error: void 0
9220
+ };
9221
+ } else if (onFail === "fail") {
9222
+ current.execution.nodes[node.id] = {
9223
+ ...execution,
9224
+ ...patch,
9225
+ status: "failed",
9226
+ completedAt
9227
+ };
9228
+ } else {
9229
+ const provenanceNote = checkEvidence.provenance ? ` (${checkEvidence.provenance})` : "";
9230
+ const approval = {
9231
+ requestedAt: now(),
9232
+ summary: `Check node ${node.id} failed with exit status ${checkEvidence.exitStatus}${provenanceNote}.`
9233
+ };
9234
+ current.execution.nodes[node.id] = {
9235
+ ...execution,
9236
+ ...patch,
9237
+ status: "awaiting_approval",
9238
+ approval,
9239
+ approvalHistory: [...execution.approvalHistory, approval]
9240
+ };
9241
+ }
9242
+ },
9243
+ false
9244
+ );
9245
+ } else {
9246
+ const failureMessage = deterministicOutcome.message;
9247
+ this.mutate(
9248
+ handle,
9249
+ (current) => {
9250
+ const execution = current.execution.nodes[node.id];
9251
+ assertLegal(execution, `node not in execution: ${node.id}`);
9252
+ const completedAt = now();
9253
+ const { retry, patch } = this.planAutomaticRetry(
9254
+ node,
9255
+ execution,
9256
+ failureMessage,
9257
+ completedAt,
9258
+ capturedOutput
9259
+ );
9260
+ current.execution.nodes[node.id] = retry ? { ...execution, ...patch, status: "available", error: void 0 } : { ...execution, ...patch, status: "failed", completedAt };
8813
9261
  },
8814
9262
  false
8815
9263
  );
@@ -8955,6 +9403,7 @@ var FlowAdapter = class extends AbstractConnector {
8955
9403
  s.lastSignaledExecution = s.execution;
8956
9404
  }
8957
9405
  }
9406
+ this.rearmWakeTimer(handle);
8958
9407
  return typeof result === "string" ? result : JSON.stringify({ ok: true });
8959
9408
  }
8960
9409
  /**
@@ -9000,6 +9449,181 @@ var FlowAdapter = class extends AbstractConnector {
9000
9449
  done: computed.done && flowStatus === "complete"
9001
9450
  };
9002
9451
  }
9452
+ // ── Agent-node deadline wake timer ────────────────────────────────────────
9453
+ //
9454
+ // Agent nodes have no `await` the adapter can wrap — it marks the node
9455
+ // started and waits for the model to call `complete_node`, which may never
9456
+ // come. The derived deadline (see `engine/node-deadline.ts`) is the source
9457
+ // of truth: it is a pure function of `startedAt` + `approvalHistory` +
9458
+ // `inputHistory`, so it is correct on its own after `hydrate()` or a
9459
+ // reconnect with no timer required. This wake timer exists only so a
9460
+ // genuinely hung run — by definition nothing else happening — still gets
9461
+ // *some* event that triggers a re-evaluation of that deadline.
9462
+ clearWakeTimer(s) {
9463
+ if (s.wakeTimer !== null) {
9464
+ clearTimeout(s.wakeTimer);
9465
+ s.wakeTimer = null;
9466
+ }
9467
+ }
9468
+ /**
9469
+ * Whether `execution` counts as "actively running" for `control.timeoutSec`
9470
+ * evaluation. Ordinarily this is just `status === "running"`. The one
9471
+ * exception: a non-gate agent node whose approval was just decided
9472
+ * `approved` stays at `awaiting_approval` — `applyApproval` deliberately
9473
+ * does not restore `running` there, because `doCompleteNode`'s
9474
+ * `approvedPrior` check requires exactly that status (a mandatory-approval
9475
+ * node has no `runningAutonomous` fallback). Without this second clause the
9476
+ * timeout would pause for the approval wait (correctly, via
9477
+ * `remainingActiveExecutionMs` reading the now-resolved `approvalHistory`
9478
+ * entry) and then never resume, since nothing re-evaluates a node that
9479
+ * never looks "running" again.
9480
+ */
9481
+ isDeadlineEligible(execution) {
9482
+ if (execution.status === "running") return true;
9483
+ return execution.status === "awaiting_approval" && execution.approval?.decision === "approved";
9484
+ }
9485
+ /**
9486
+ * Recomputes the pending wake timer against the current state: cancels any
9487
+ * previously-armed timer, then arms a fresh one for the earliest deadline
9488
+ * among currently `running` agent nodes with a `control.timeoutSec`. A
9489
+ * no-op when nothing is running against a timeout, or the flow is already
9490
+ * terminal. Called after every {@link mutate}, plus `connect()` and
9491
+ * `hydrate()` (which bypass `mutate()`).
9492
+ *
9493
+ * `disconnect()` clears the timer and bumps `executionGeneration`, but a
9494
+ * `mutate()` already in flight (or one that lands after teardown for any
9495
+ * other reason) still calls this on its way out — bail here on
9496
+ * `handle.status` so a post-disconnect mutation can never re-arm a timer
9497
+ * that would fire against a handle nothing is watching anymore.
9498
+ */
9499
+ rearmWakeTimer(handle) {
9500
+ const s = this.s(handle);
9501
+ this.clearWakeTimer(s);
9502
+ if (handle.status === "disconnected") return;
9503
+ if (s.execution.done || s.execution.status === "failed" || s.execution.status === "cancelled") {
9504
+ return;
9505
+ }
9506
+ const nowMs = Date.now();
9507
+ let earliestRemainingMs;
9508
+ for (const node of s.flowDef.nodes) {
9509
+ if (!isAgentNode(node)) continue;
9510
+ const timeoutSec = node.control?.timeoutSec;
9511
+ if (timeoutSec === void 0) continue;
9512
+ const execution = s.execution.nodes[node.id];
9513
+ if (!execution || !this.isDeadlineEligible(execution)) continue;
9514
+ const remaining = remainingActiveExecutionMs(execution, timeoutSec, nowMs);
9515
+ if (remaining === void 0) continue;
9516
+ if (earliestRemainingMs === void 0 || remaining < earliestRemainingMs) {
9517
+ earliestRemainingMs = remaining;
9518
+ }
9519
+ }
9520
+ if (earliestRemainingMs === void 0) return;
9521
+ const delayMs = Math.max(0, earliestRemainingMs);
9522
+ const timer = setTimeout(() => {
9523
+ s.wakeTimer = null;
9524
+ void this.evaluateAgentNodeDeadlines(handle);
9525
+ }, delayMs);
9526
+ timer.unref?.();
9527
+ s.wakeTimer = timer;
9528
+ }
9529
+ /**
9530
+ * Re-checks every `running` agent node's derived deadline and, for any that
9531
+ * have expired, either auto-retries (bounded by `control.retries`, composing
9532
+ * with the existing `doFailNode(recoverable: true)` and `retryNode`
9533
+ * pathways) or terminally fails the node. Safe to call at any time — the
9534
+ * derived deadline means this is a no-op unless something has actually
9535
+ * expired. Goes through {@link mutate}, so a state change here republishes
9536
+ * `onChange`/stimulus exactly like any other mutation, waking a fresh turn
9537
+ * when the node retries or the flow rolls up to `failed`.
9538
+ */
9539
+ async evaluateAgentNodeDeadlines(handle) {
9540
+ const s = this.s(handle);
9541
+ const nowMs = Date.now();
9542
+ const expired = s.flowDef.nodes.filter((node) => {
9543
+ if (!isAgentNode(node)) return false;
9544
+ const timeoutSec = node.control?.timeoutSec;
9545
+ if (timeoutSec === void 0) return false;
9546
+ const execution = s.execution.nodes[node.id];
9547
+ if (!execution || !this.isDeadlineEligible(execution)) return false;
9548
+ const remaining = remainingActiveExecutionMs(execution, timeoutSec, nowMs);
9549
+ return remaining !== void 0 && remaining <= 0;
9550
+ });
9551
+ for (const node of expired) {
9552
+ this.mutate(
9553
+ handle,
9554
+ (current) => {
9555
+ const execution = current.execution.nodes[node.id];
9556
+ if (!execution || !this.isDeadlineEligible(execution)) return;
9557
+ const completedAt = now();
9558
+ const message = `Agent node ${node.id} timed out after ${node.control?.timeoutSec}s`;
9559
+ const { retry, patch } = this.planAutomaticRetry(
9560
+ node,
9561
+ execution,
9562
+ message,
9563
+ completedAt,
9564
+ execution.output
9565
+ );
9566
+ current.execution.nodes[node.id] = retry ? { ...execution, ...patch, status: "available", error: void 0 } : { ...execution, ...patch, status: "failed", completedAt };
9567
+ },
9568
+ true
9569
+ );
9570
+ }
9571
+ }
9572
+ /**
9573
+ * Records one failed attempt on `nodeId`'s execution and decides whether
9574
+ * the automatic bounded retry (`control.retries`) still has budget. Shared
9575
+ * by every automatic-failure seam (agent-node timeout, function/check
9576
+ * execution/timeout failure, sub-flow execution/timeout failure, and a
9577
+ * check's fail verdict) so the budget — derived from prior `'failed'`
9578
+ * entries in `outputHistory`, not a separate durable counter — is computed
9579
+ * once, the same way, everywhere.
9580
+ *
9581
+ * Returns the `NodeExecution` patch (error/errorHistory/outputHistory/
9582
+ * output) plus whether budget remains; the caller decides the resulting
9583
+ * `status` because that differs by kind (e.g. sub-flow degrades an
9584
+ * optional node's exhausted failure to `skipped`; agent/function do not,
9585
+ * preserving their existing behavior). A terminal failure whose node has
9586
+ * any `control.retries` configured carries `recoverable: true` so the
9587
+ * manual `retryNode` operator override — which sits outside this budget
9588
+ * entirely — remains usable once the automatic budget runs out.
9589
+ *
9590
+ * @param sourceErrorAtIso - The failure's own timestamp, when the caller has
9591
+ * one more precise than `completedAtIso` (e.g. a sub-flow child reports
9592
+ * its own `error.at`, which can predate this node's parent-side
9593
+ * `completedAt` by however long the result took to propagate). Defaults
9594
+ * to `completedAtIso` for callers (agent-timeout, function-node) whose
9595
+ * failure message is synthesized at the moment of detection.
9596
+ * @param checkEvidence - Derived evidence for a check's fail verdict, so it
9597
+ * rides on the same appended attempt as the failure it explains.
9598
+ */
9599
+ planAutomaticRetry(node, execution, message, completedAtIso, capturedOutput, sourceErrorAtIso, checkEvidence) {
9600
+ const retries = node.control?.retries;
9601
+ const priorFailures = countFailedAttempts(execution.outputHistory);
9602
+ const retry = hasAutomaticRetryBudget(priorFailures, retries);
9603
+ const error = {
9604
+ message,
9605
+ recoverable: !retry && !!retries,
9606
+ at: sourceErrorAtIso ?? completedAtIso
9607
+ };
9608
+ const outputHistory = appendAttempt(
9609
+ execution.outputHistory,
9610
+ "failed",
9611
+ execution.startedAt ?? completedAtIso,
9612
+ completedAtIso,
9613
+ capturedOutput,
9614
+ error,
9615
+ { check: checkEvidence }
9616
+ );
9617
+ return {
9618
+ retry,
9619
+ patch: {
9620
+ error,
9621
+ errorHistory: [...execution.errorHistory, error],
9622
+ outputHistory,
9623
+ output: capturedOutput ?? execution.output
9624
+ }
9625
+ };
9626
+ }
9003
9627
  /**
9004
9628
  * Build the selected agent node's complete turn briefing plus upstream context.
9005
9629
  */
@@ -9087,7 +9711,8 @@ function routerSelectionHandoffSummary(router, execution) {
9087
9711
  if (!selection) return void 0;
9088
9712
  return selection.selectedTarget === null ? "Router completed without selecting a target." : `Router selected ${selection.selectedTarget}.`;
9089
9713
  }
9090
- function functionNodeOutput(node, result) {
9714
+ function deterministicNodeOutput(node, result, passed) {
9715
+ const kindLabel = deterministicNodeKindLabel(node.run.kind);
9091
9716
  const capturedFields = [];
9092
9717
  for (const channel of ["stdout", "stderr"]) {
9093
9718
  const destination = node.run.capture?.[channel];
@@ -9095,18 +9720,39 @@ function functionNodeOutput(node, result) {
9095
9720
  const match = /^fields\.([^.]+)$/.exec(destination);
9096
9721
  if (!match?.[1]) {
9097
9722
  throw new Error(
9098
- `Function node ${node.id} run.capture.${channel} destination "${destination}" is invalid; expected fields.<name>`
9723
+ `${kindLabel} node ${node.id} run.capture.${channel} destination "${destination}" is invalid; expected fields.<name>`
9099
9724
  );
9100
9725
  }
9101
9726
  capturedFields.push([match[1], result[channel]]);
9102
9727
  }
9103
9728
  const fields = Object.fromEntries(capturedFields);
9729
+ 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
9730
  return {
9105
- summary: `Function node ${node.id} exited with status ${result.exitStatus}.`,
9731
+ summary,
9106
9732
  artifacts: [],
9107
9733
  ...Object.keys(fields).length > 0 ? { fields } : {}
9108
9734
  };
9109
9735
  }
9736
+ function extractSubpromptJsonPayload(text) {
9737
+ const trimmed = text.trim();
9738
+ const fenceMatch = /^```(?:json)?\s*([\s\S]*?)\s*```$/.exec(trimmed);
9739
+ return fenceMatch?.[1] ?? trimmed;
9740
+ }
9741
+ function subpromptNodeOutput(node, text) {
9742
+ const outputSchema = node.contract?.output?.fields;
9743
+ if (!outputSchema) return { summary: text.trim(), artifacts: [] };
9744
+ const parsed = safeParseFields(extractSubpromptJsonPayload(text));
9745
+ if (!parsed.fields) {
9746
+ throw new Error(
9747
+ `Subprompt node ${node.id} response is not valid JSON: ${parsed.issues.join("; ")}`
9748
+ );
9749
+ }
9750
+ return {
9751
+ summary: text.trim(),
9752
+ artifacts: [],
9753
+ fields: parsed.fields
9754
+ };
9755
+ }
9110
9756
  function safeParseStringArray(value) {
9111
9757
  if (!value) return [];
9112
9758
  if (Array.isArray(value)) return value.map(String);
@@ -9175,6 +9821,6 @@ function createConnector() {
9175
9821
  return new FlowAdapter();
9176
9822
  }
9177
9823
 
9178
- 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
9824
+ 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, SessionSubpromptRunner, 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 };
9825
+ //# sourceMappingURL=chunk-ZO7KL7GN.js.map
9826
+ //# sourceMappingURL=chunk-ZO7KL7GN.js.map