yaml-flow 2.8.0 → 3.1.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 (41) hide show
  1. package/README.md +189 -3
  2. package/dist/{constants-BEbO2_OK.d.ts → constants-B2zqu10b.d.ts} +32 -52
  3. package/dist/{constants-BNjeIlZ8.d.cts → constants-DJZU1pwJ.d.cts} +32 -52
  4. package/dist/continuous-event-graph/index.cjs +1388 -52
  5. package/dist/continuous-event-graph/index.cjs.map +1 -1
  6. package/dist/continuous-event-graph/index.d.cts +643 -5
  7. package/dist/continuous-event-graph/index.d.ts +643 -5
  8. package/dist/continuous-event-graph/index.js +1374 -53
  9. package/dist/continuous-event-graph/index.js.map +1 -1
  10. package/dist/event-graph/index.cjs +6817 -53
  11. package/dist/event-graph/index.cjs.map +1 -1
  12. package/dist/event-graph/index.d.cts +15 -6
  13. package/dist/event-graph/index.d.ts +15 -6
  14. package/dist/event-graph/index.js +6808 -51
  15. package/dist/event-graph/index.js.map +1 -1
  16. package/dist/index.cjs +1827 -441
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +6 -5
  19. package/dist/index.d.ts +6 -5
  20. package/dist/index.js +1808 -440
  21. package/dist/index.js.map +1 -1
  22. package/dist/inference/index.cjs +46 -13
  23. package/dist/inference/index.cjs.map +1 -1
  24. package/dist/inference/index.d.cts +2 -2
  25. package/dist/inference/index.d.ts +2 -2
  26. package/dist/inference/index.js +46 -13
  27. package/dist/inference/index.js.map +1 -1
  28. package/dist/step-machine/index.cjs +6600 -0
  29. package/dist/step-machine/index.cjs.map +1 -1
  30. package/dist/step-machine/index.d.cts +26 -1
  31. package/dist/step-machine/index.d.ts +26 -1
  32. package/dist/step-machine/index.js +6596 -1
  33. package/dist/step-machine/index.js.map +1 -1
  34. package/dist/{types-C2lOwquM.d.cts → types-BwvgvlOO.d.cts} +2 -2
  35. package/dist/{types-mS_pPftm.d.ts → types-ClRA8hzC.d.ts} +2 -2
  36. package/dist/{types-DAI_a2as.d.ts → types-DEj7OakX.d.cts} +29 -10
  37. package/dist/{types-DAI_a2as.d.cts → types-DEj7OakX.d.ts} +29 -10
  38. package/dist/validate-DEZ2Ymdb.d.ts +53 -0
  39. package/dist/validate-DqKTZg_o.d.cts +53 -0
  40. package/package.json +1 -1
  41. package/schema/event-graph.schema.json +254 -0
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
- import { execFile } from 'child_process';
2
1
  import addFormats from 'ajv-formats';
2
+ import { existsSync, writeFileSync, appendFileSync, readFileSync } from 'fs';
3
+ import { createHash } from 'crypto';
4
+ import { exec, execFile } from 'child_process';
3
5
 
4
6
  var __create = Object.create;
5
7
  var __defProp = Object.defineProperty;
@@ -3911,7 +3913,7 @@ var require_core = __commonJS({
3911
3913
  uriResolver
3912
3914
  };
3913
3915
  }
3914
- var Ajv2 = class {
3916
+ var Ajv4 = class {
3915
3917
  constructor(opts = {}) {
3916
3918
  this.schemas = {};
3917
3919
  this.refs = {};
@@ -4281,9 +4283,9 @@ var require_core = __commonJS({
4281
4283
  }
4282
4284
  }
4283
4285
  };
4284
- Ajv2.ValidationError = validation_error_1.default;
4285
- Ajv2.MissingRefError = ref_error_1.default;
4286
- exports$1.default = Ajv2;
4286
+ Ajv4.ValidationError = validation_error_1.default;
4287
+ Ajv4.MissingRefError = ref_error_1.default;
4288
+ exports$1.default = Ajv4;
4287
4289
  function checkOptions(checkOpts, options, msg, log = "error") {
4288
4290
  for (const key in checkOpts) {
4289
4291
  const opt = key;
@@ -6354,7 +6356,7 @@ var require_ajv = __commonJS({
6354
6356
  var draft7MetaSchema = require_json_schema_draft_07();
6355
6357
  var META_SUPPORT_DATA = ["/properties"];
6356
6358
  var META_SCHEMA_ID = "http://json-schema.org/draft-07/schema";
6357
- var Ajv2 = class extends core_1.default {
6359
+ var Ajv4 = class extends core_1.default {
6358
6360
  _addVocabularies() {
6359
6361
  super._addVocabularies();
6360
6362
  draft7_1.default.forEach((v) => this.addVocabulary(v));
@@ -6373,11 +6375,11 @@ var require_ajv = __commonJS({
6373
6375
  return this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : void 0);
6374
6376
  }
6375
6377
  };
6376
- exports$1.Ajv = Ajv2;
6377
- module.exports = exports$1 = Ajv2;
6378
- module.exports.Ajv = Ajv2;
6378
+ exports$1.Ajv = Ajv4;
6379
+ module.exports = exports$1 = Ajv4;
6380
+ module.exports.Ajv = Ajv4;
6379
6381
  Object.defineProperty(exports$1, "__esModule", { value: true });
6380
- exports$1.default = Ajv2;
6382
+ exports$1.default = Ajv4;
6381
6383
  var validate_1 = require_validate();
6382
6384
  Object.defineProperty(exports$1, "KeywordCxt", { enumerable: true, get: function() {
6383
6385
  return validate_1.KeywordCxt;
@@ -6911,6 +6913,188 @@ async function loadStepFlow(source) {
6911
6913
  return flow;
6912
6914
  }
6913
6915
 
6916
+ // schema/flow.schema.json
6917
+ var flow_schema_default = {
6918
+ $schema: "http://json-schema.org/draft-07/schema#",
6919
+ $id: "https://github.com/yaml-flow/schema/flow.json",
6920
+ title: "YamlFlow Configuration",
6921
+ description: "Schema for yaml-flow workflow definitions",
6922
+ type: "object",
6923
+ required: ["settings", "steps", "terminal_states"],
6924
+ properties: {
6925
+ id: {
6926
+ type: "string",
6927
+ description: "Optional flow identifier"
6928
+ },
6929
+ settings: {
6930
+ type: "object",
6931
+ description: "Flow-level settings",
6932
+ required: ["start_step"],
6933
+ properties: {
6934
+ start_step: {
6935
+ type: "string",
6936
+ description: "Step to start execution from"
6937
+ },
6938
+ max_total_steps: {
6939
+ type: "integer",
6940
+ minimum: 1,
6941
+ default: 100,
6942
+ description: "Maximum steps before forced termination"
6943
+ },
6944
+ timeout_ms: {
6945
+ type: "integer",
6946
+ minimum: 0,
6947
+ description: "Flow timeout in milliseconds"
6948
+ }
6949
+ },
6950
+ additionalProperties: false
6951
+ },
6952
+ steps: {
6953
+ type: "object",
6954
+ description: "Step definitions",
6955
+ minProperties: 1,
6956
+ additionalProperties: {
6957
+ $ref: "#/definitions/step"
6958
+ }
6959
+ },
6960
+ terminal_states: {
6961
+ type: "object",
6962
+ description: "Terminal state definitions",
6963
+ minProperties: 1,
6964
+ additionalProperties: {
6965
+ $ref: "#/definitions/terminal_state"
6966
+ }
6967
+ }
6968
+ },
6969
+ additionalProperties: false,
6970
+ definitions: {
6971
+ step: {
6972
+ type: "object",
6973
+ description: "Individual step configuration",
6974
+ required: ["transitions"],
6975
+ properties: {
6976
+ description: {
6977
+ type: "string",
6978
+ description: "Human-readable description"
6979
+ },
6980
+ expects_data: {
6981
+ type: "array",
6982
+ items: { type: "string" },
6983
+ description: "Data keys this step expects as input"
6984
+ },
6985
+ produces_data: {
6986
+ type: "array",
6987
+ items: { type: "string" },
6988
+ description: "Data keys this step produces as output"
6989
+ },
6990
+ transitions: {
6991
+ type: "object",
6992
+ description: "Mapping of result -> next step name",
6993
+ additionalProperties: { type: "string" },
6994
+ minProperties: 1
6995
+ },
6996
+ retry: {
6997
+ $ref: "#/definitions/retry_config"
6998
+ },
6999
+ circuit_breaker: {
7000
+ $ref: "#/definitions/circuit_breaker_config"
7001
+ }
7002
+ },
7003
+ additionalProperties: false
7004
+ },
7005
+ terminal_state: {
7006
+ type: "object",
7007
+ description: "Terminal state configuration",
7008
+ required: ["return_intent"],
7009
+ properties: {
7010
+ description: {
7011
+ type: "string",
7012
+ description: "Human-readable description"
7013
+ },
7014
+ return_intent: {
7015
+ type: "string",
7016
+ description: "Intent/status to return (e.g., 'success', 'error')"
7017
+ },
7018
+ return_artifacts: {
7019
+ oneOf: [
7020
+ { type: "string" },
7021
+ { type: "array", items: { type: "string" } },
7022
+ { type: "boolean", const: false }
7023
+ ],
7024
+ description: "Data key(s) to include in result, or false to exclude"
7025
+ },
7026
+ expects_data: {
7027
+ type: "array",
7028
+ items: { type: "string" },
7029
+ description: "Data keys this terminal state expects"
7030
+ }
7031
+ },
7032
+ additionalProperties: false
7033
+ },
7034
+ retry_config: {
7035
+ type: "object",
7036
+ description: "Retry configuration for step failures",
7037
+ required: ["max_attempts"],
7038
+ properties: {
7039
+ max_attempts: {
7040
+ type: "integer",
7041
+ minimum: 1,
7042
+ description: "Maximum retry attempts"
7043
+ },
7044
+ delay_ms: {
7045
+ type: "integer",
7046
+ minimum: 0,
7047
+ description: "Delay between retries in ms"
7048
+ },
7049
+ backoff_multiplier: {
7050
+ type: "number",
7051
+ minimum: 1,
7052
+ description: "Backoff multiplier (e.g., 2 for exponential)"
7053
+ }
7054
+ },
7055
+ additionalProperties: false
7056
+ },
7057
+ circuit_breaker_config: {
7058
+ type: "object",
7059
+ description: "Circuit breaker configuration for loops",
7060
+ required: ["max_iterations", "on_open"],
7061
+ properties: {
7062
+ max_iterations: {
7063
+ type: "integer",
7064
+ minimum: 1,
7065
+ description: "Maximum iterations before circuit opens"
7066
+ },
7067
+ on_open: {
7068
+ type: "string",
7069
+ description: "Step to transition to when circuit opens"
7070
+ }
7071
+ },
7072
+ additionalProperties: false
7073
+ }
7074
+ }
7075
+ };
7076
+
7077
+ // src/step-machine/schema-validator.ts
7078
+ var import_ajv = __toESM(require_ajv());
7079
+ var _compiled = null;
7080
+ function getValidator() {
7081
+ if (_compiled) return _compiled;
7082
+ const ajv = new import_ajv.default({ allErrors: true });
7083
+ addFormats(ajv);
7084
+ _compiled = ajv.compile(flow_schema_default);
7085
+ return _compiled;
7086
+ }
7087
+ function validateFlowSchema(config) {
7088
+ const validate = getValidator();
7089
+ const valid = validate(config);
7090
+ if (valid) return { ok: true, errors: [] };
7091
+ const errors = (validate.errors ?? []).map((e) => {
7092
+ const path = e.instancePath || "/";
7093
+ return `${path}: ${e.message ?? "unknown error"}`;
7094
+ });
7095
+ return { ok: false, errors };
7096
+ }
7097
+
6914
7098
  // src/event-graph/constants.ts
6915
7099
  var TASK_STATUS = {
6916
7100
  NOT_STARTED: "not-started",
@@ -6987,15 +7171,14 @@ function isTaskCompleted(taskState) {
6987
7171
  function isTaskRunning(taskState) {
6988
7172
  return taskState?.status === TASK_STATUS.RUNNING;
6989
7173
  }
6990
- function isRepeatableTask(taskConfig) {
6991
- return taskConfig.repeatable === true || typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null;
7174
+ function getRefreshStrategy(taskConfig, graphSettings) {
7175
+ return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
6992
7176
  }
6993
- function getRepeatableMax(taskConfig) {
6994
- if (taskConfig.repeatable === true) return void 0;
6995
- if (typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null) {
6996
- return taskConfig.repeatable.max;
6997
- }
6998
- return void 0;
7177
+ function isRerunnable(taskConfig, graphSettings) {
7178
+ return getRefreshStrategy(taskConfig, graphSettings) !== "once";
7179
+ }
7180
+ function getMaxExecutions(taskConfig) {
7181
+ return taskConfig.maxExecutions;
6999
7182
  }
7000
7183
  function computeAvailableOutputs(graph, taskStates) {
7001
7184
  const outputs = /* @__PURE__ */ new Set();
@@ -7043,7 +7226,7 @@ function addDynamicTask(graph, taskName, taskConfig) {
7043
7226
  }
7044
7227
  };
7045
7228
  }
7046
- function createDefaultTaskState() {
7229
+ function createDefaultGraphEngineStore() {
7047
7230
  return {
7048
7231
  status: "not-started",
7049
7232
  executionCount: 0,
@@ -7056,7 +7239,7 @@ function createDefaultTaskState() {
7056
7239
  function createInitialExecutionState(graph, executionId) {
7057
7240
  const tasks = {};
7058
7241
  for (const taskName of Object.keys(graph.tasks)) {
7059
- tasks[taskName] = createDefaultTaskState();
7242
+ tasks[taskName] = createDefaultGraphEngineStore();
7060
7243
  }
7061
7244
  return {
7062
7245
  status: "running",
@@ -7427,48 +7610,88 @@ function getCandidateTasks(graph, state) {
7427
7610
  const candidates = [];
7428
7611
  for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
7429
7612
  const taskState = state.tasks[taskName];
7430
- if (!isRepeatableTask(taskConfig)) {
7431
- if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
7432
- continue;
7433
- }
7434
- } else {
7435
- if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
7436
- continue;
7437
- }
7438
- const maxExec = getRepeatableMax(taskConfig);
7439
- if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
7613
+ const strategy = getRefreshStrategy(taskConfig, graph.settings);
7614
+ const rerunnable = strategy !== "once";
7615
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
7616
+ continue;
7617
+ }
7618
+ const maxExec = getMaxExecutions(taskConfig);
7619
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
7620
+ continue;
7621
+ }
7622
+ if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
7623
+ continue;
7624
+ }
7625
+ if (!rerunnable) {
7626
+ if (taskState?.status === TASK_STATUS.COMPLETED) {
7440
7627
  continue;
7441
7628
  }
7442
- if (taskConfig.circuit_breaker) {
7443
- if (taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
7444
- continue;
7629
+ }
7630
+ if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
7631
+ const requires2 = getRequires(taskConfig);
7632
+ switch (strategy) {
7633
+ case "data-changed": {
7634
+ if (requires2.length > 0) {
7635
+ const hasChangedData = requires2.some((req) => {
7636
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
7637
+ if (getProvides(otherConfig).includes(req)) {
7638
+ const otherState = state.tasks[otherName];
7639
+ if (!otherState) continue;
7640
+ const consumed = taskState.lastConsumedHashes?.[req];
7641
+ if (otherState.lastDataHash == null) {
7642
+ return otherState.executionCount > taskState.lastEpoch;
7643
+ }
7644
+ return otherState.lastDataHash !== consumed;
7645
+ }
7646
+ }
7647
+ return false;
7648
+ });
7649
+ if (!hasChangedData) continue;
7650
+ } else {
7651
+ continue;
7652
+ }
7653
+ break;
7445
7654
  }
7446
- }
7447
- if (taskState?.status === TASK_STATUS.COMPLETED) {
7448
- const requires2 = getRequires(taskConfig);
7449
- if (requires2.length > 0) {
7450
- const hasRefreshedInputs = requires2.some((req) => {
7451
- for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
7452
- if (getProvides(otherConfig).includes(req)) {
7453
- const otherState = state.tasks[otherName];
7454
- if (otherState && otherState.executionCount > taskState.lastEpoch) {
7455
- return true;
7655
+ case "epoch-changed": {
7656
+ if (requires2.length > 0) {
7657
+ const hasRefreshedInputs = requires2.some((req) => {
7658
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
7659
+ if (getProvides(otherConfig).includes(req)) {
7660
+ const otherState = state.tasks[otherName];
7661
+ if (otherState && otherState.executionCount > taskState.lastEpoch) {
7662
+ return true;
7663
+ }
7456
7664
  }
7457
7665
  }
7458
- }
7459
- return false;
7460
- });
7461
- if (!hasRefreshedInputs) continue;
7462
- } else {
7666
+ return false;
7667
+ });
7668
+ if (!hasRefreshedInputs) continue;
7669
+ } else {
7670
+ continue;
7671
+ }
7672
+ break;
7673
+ }
7674
+ case "time-based": {
7675
+ const interval = taskConfig.refreshInterval ?? 0;
7676
+ if (interval <= 0) continue;
7677
+ const completedAt = taskState.completedAt;
7678
+ if (!completedAt) continue;
7679
+ const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
7680
+ if (elapsedSec < interval) continue;
7681
+ break;
7682
+ }
7683
+ case "manual": {
7463
7684
  continue;
7464
7685
  }
7686
+ default:
7687
+ continue;
7465
7688
  }
7466
7689
  }
7467
7690
  const requires = getRequires(taskConfig);
7468
7691
  if (!requires.every((req) => availableOutputs.includes(req))) {
7469
7692
  continue;
7470
7693
  }
7471
- if (!isRepeatableTask(taskConfig)) {
7694
+ if (!rerunnable) {
7472
7695
  const provides = getProvides(taskConfig);
7473
7696
  const allAlreadyAvailable = provides.length > 0 && provides.every((output) => availableOutputs.includes(output));
7474
7697
  if (allAlreadyAvailable) continue;
@@ -7537,7 +7760,7 @@ function selectOptimalTasks(candidates, graph, state, conflictStrategy) {
7537
7760
 
7538
7761
  // src/event-graph/task-transitions.ts
7539
7762
  function applyTaskStart(state, taskName) {
7540
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
7763
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore2();
7541
7764
  const updatedTask = {
7542
7765
  ...existingTask,
7543
7766
  status: "running",
@@ -7552,8 +7775,8 @@ function applyTaskStart(state, taskName) {
7552
7775
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
7553
7776
  };
7554
7777
  }
7555
- function applyTaskCompletion(state, graph, taskName, result) {
7556
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
7778
+ function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
7779
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore2();
7557
7780
  const taskConfig = graph.tasks[taskName];
7558
7781
  if (!taskConfig) {
7559
7782
  throw new Error(`Task "${taskName}" not found in graph`);
@@ -7564,6 +7787,19 @@ function applyTaskCompletion(state, graph, taskName, result) {
7564
7787
  } else {
7565
7788
  outputTokens = getProvides(taskConfig);
7566
7789
  }
7790
+ const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
7791
+ const requires = taskConfig.requires ?? [];
7792
+ for (const token of requires) {
7793
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
7794
+ if (getProvides(otherConfig).includes(token)) {
7795
+ const otherState = state.tasks[otherName];
7796
+ if (otherState?.lastDataHash) {
7797
+ lastConsumedHashes[token] = otherState.lastDataHash;
7798
+ }
7799
+ break;
7800
+ }
7801
+ }
7802
+ }
7567
7803
  const updatedTask = {
7568
7804
  ...existingTask,
7569
7805
  status: "completed",
@@ -7571,11 +7807,11 @@ function applyTaskCompletion(state, graph, taskName, result) {
7571
7807
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
7572
7808
  executionCount: existingTask.executionCount + 1,
7573
7809
  lastEpoch: existingTask.executionCount + 1,
7810
+ lastDataHash: dataHash,
7811
+ data,
7812
+ lastConsumedHashes,
7574
7813
  error: void 0
7575
7814
  };
7576
- if (isRepeatableTask(taskConfig)) {
7577
- updatedTask.status = "not-started";
7578
- }
7579
7815
  const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
7580
7816
  return {
7581
7817
  ...state,
@@ -7585,7 +7821,7 @@ function applyTaskCompletion(state, graph, taskName, result) {
7585
7821
  };
7586
7822
  }
7587
7823
  function applyTaskFailure(state, graph, taskName, error) {
7588
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
7824
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore2();
7589
7825
  const taskConfig = graph.tasks[taskName];
7590
7826
  if (taskConfig?.retry) {
7591
7827
  const retryCount = existingTask.retryCount + 1;
@@ -7628,7 +7864,7 @@ function applyTaskFailure(state, graph, taskName, error) {
7628
7864
  };
7629
7865
  }
7630
7866
  function applyTaskProgress(state, taskName, message, progress) {
7631
- const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
7867
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore2();
7632
7868
  const updatedTask = {
7633
7869
  ...existingTask,
7634
7870
  progress: typeof progress === "number" ? progress : existingTask.progress,
@@ -7644,7 +7880,27 @@ function applyTaskProgress(state, taskName, message, progress) {
7644
7880
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
7645
7881
  };
7646
7882
  }
7647
- function createDefaultTaskState2() {
7883
+ function applyTaskRestart(state, taskName) {
7884
+ const existingTask = state.tasks[taskName];
7885
+ if (!existingTask) return state;
7886
+ const updatedTask = {
7887
+ ...existingTask,
7888
+ status: "not-started",
7889
+ startedAt: void 0,
7890
+ completedAt: void 0,
7891
+ failedAt: void 0,
7892
+ error: void 0,
7893
+ data: void 0,
7894
+ progress: null,
7895
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
7896
+ };
7897
+ return {
7898
+ ...state,
7899
+ tasks: { ...state.tasks, [taskName]: updatedTask },
7900
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
7901
+ };
7902
+ }
7903
+ function createDefaultGraphEngineStore2() {
7648
7904
  return {
7649
7905
  status: "not-started",
7650
7906
  executionCount: 0,
@@ -7664,11 +7920,13 @@ function apply(state, event, graph) {
7664
7920
  case "task-started":
7665
7921
  return applyTaskStart(state, event.taskName);
7666
7922
  case "task-completed":
7667
- return applyTaskCompletion(state, graph, event.taskName, event.result);
7923
+ return applyTaskCompletion(state, graph, event.taskName, event.result, event.dataHash, event.data);
7668
7924
  case "task-failed":
7669
7925
  return applyTaskFailure(state, graph, event.taskName, event.error);
7670
7926
  case "task-progress":
7671
7927
  return applyTaskProgress(state, event.taskName, event.message, event.progress);
7928
+ case "task-restart":
7929
+ return applyTaskRestart(state, event.taskName);
7672
7930
  case "inject-tokens":
7673
7931
  return applyInjectTokens(state, event.tokens);
7674
7932
  case "agent-action":
@@ -7739,7 +7997,7 @@ function applyTaskCreation(state, taskName, taskConfig) {
7739
7997
  ...state,
7740
7998
  tasks: {
7741
7999
  ...state.tasks,
7742
- [taskName]: createDefaultTaskState()
8000
+ [taskName]: createDefaultGraphEngineStore()
7743
8001
  },
7744
8002
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
7745
8003
  };
@@ -8354,107 +8612,377 @@ function buildResult(issues) {
8354
8612
  };
8355
8613
  }
8356
8614
 
8357
- // src/stores/localStorage.ts
8358
- var LocalStorageStore = class {
8359
- prefix;
8360
- constructor(options = {}) {
8361
- this.prefix = options.prefix ?? "yamlflow";
8362
- if (typeof localStorage === "undefined") {
8363
- throw new Error("LocalStorageStore requires localStorage (browser environment)");
8364
- }
8365
- }
8366
- runKey(runId) {
8367
- return `${this.prefix}:run:${runId}`;
8368
- }
8369
- dataKey(runId) {
8370
- return `${this.prefix}:data:${runId}`;
8371
- }
8372
- indexKey() {
8373
- return `${this.prefix}:runs`;
8374
- }
8375
- async saveRunState(runId, state) {
8376
- localStorage.setItem(this.runKey(runId), JSON.stringify(state));
8377
- const runs = await this.listRuns();
8378
- if (!runs.includes(runId)) {
8379
- runs.push(runId);
8380
- localStorage.setItem(this.indexKey(), JSON.stringify(runs));
8381
- }
8382
- }
8383
- async loadRunState(runId) {
8384
- const raw = localStorage.getItem(this.runKey(runId));
8385
- return raw ? JSON.parse(raw) : null;
8386
- }
8387
- async deleteRunState(runId) {
8388
- localStorage.removeItem(this.runKey(runId));
8389
- localStorage.removeItem(this.dataKey(runId));
8390
- const runs = await this.listRuns();
8391
- const filtered = runs.filter((id) => id !== runId);
8392
- localStorage.setItem(this.indexKey(), JSON.stringify(filtered));
8393
- }
8394
- async setData(runId, key, value) {
8395
- const allData = await this.getAllData(runId);
8396
- allData[key] = value;
8397
- localStorage.setItem(this.dataKey(runId), JSON.stringify(allData));
8398
- }
8399
- async getData(runId, key) {
8400
- const allData = await this.getAllData(runId);
8401
- return allData[key];
8402
- }
8403
- async getAllData(runId) {
8404
- const raw = localStorage.getItem(this.dataKey(runId));
8405
- return raw ? JSON.parse(raw) : {};
8406
- }
8407
- async clearData(runId) {
8408
- localStorage.removeItem(this.dataKey(runId));
8409
- }
8410
- async listRuns() {
8411
- const raw = localStorage.getItem(this.indexKey());
8412
- return raw ? JSON.parse(raw) : [];
8413
- }
8414
- /**
8415
- * Clear all flow data from localStorage
8416
- */
8417
- clearAll() {
8418
- const keysToRemove = [];
8419
- for (let i = 0; i < localStorage.length; i++) {
8420
- const key = localStorage.key(i);
8421
- if (key?.startsWith(this.prefix + ":")) {
8422
- keysToRemove.push(key);
8615
+ // schema/event-graph.schema.json
8616
+ var event_graph_schema_default = {
8617
+ $schema: "http://json-schema.org/draft-07/schema#",
8618
+ $id: "https://github.com/yaml-flow/schema/event-graph.json",
8619
+ title: "Event Graph Configuration",
8620
+ description: "Schema for stateless event-graph (DAG) workflow definitions in yaml-flow",
8621
+ type: "object",
8622
+ required: ["settings", "tasks"],
8623
+ additionalProperties: false,
8624
+ properties: {
8625
+ id: {
8626
+ type: "string",
8627
+ description: "Optional graph identifier"
8628
+ },
8629
+ settings: {
8630
+ $ref: "#/definitions/settings"
8631
+ },
8632
+ tasks: {
8633
+ type: "object",
8634
+ description: "Task definitions keyed by name",
8635
+ minProperties: 1,
8636
+ additionalProperties: {
8637
+ $ref: "#/definitions/task"
8423
8638
  }
8424
8639
  }
8425
- keysToRemove.forEach((key) => localStorage.removeItem(key));
8426
- }
8427
- };
8428
-
8429
- // src/stores/file.ts
8430
- var FileStore = class {
8431
- directory;
8432
- fs = null;
8433
- path = null;
8434
- constructor(options) {
8435
- this.directory = options.directory;
8436
- }
8437
- async ensureModules() {
8438
- if (!this.fs || !this.path) {
8439
- this.fs = await import('fs/promises');
8440
- this.path = await import('path');
8441
- await this.fs.mkdir(this.directory, { recursive: true });
8442
- }
8443
- }
8444
- runPath(runId) {
8445
- return this.path.join(this.directory, `${runId}.run.json`);
8446
- }
8447
- dataPath(runId) {
8448
- return this.path.join(this.directory, `${runId}.data.json`);
8449
- }
8450
- async saveRunState(runId, state) {
8451
- await this.ensureModules();
8452
- await this.fs.writeFile(
8453
- this.runPath(runId),
8454
- JSON.stringify(state, null, 2),
8455
- "utf-8"
8456
- );
8457
- }
8640
+ },
8641
+ definitions: {
8642
+ settings: {
8643
+ type: "object",
8644
+ required: ["completion"],
8645
+ properties: {
8646
+ completion: {
8647
+ type: "string",
8648
+ enum: [
8649
+ "all-tasks-done",
8650
+ "all-outputs-done",
8651
+ "only-resolved",
8652
+ "goal-reached",
8653
+ "manual"
8654
+ ],
8655
+ description: "Completion strategy"
8656
+ },
8657
+ conflict_strategy: {
8658
+ type: "string",
8659
+ enum: [
8660
+ "alphabetical",
8661
+ "priority-first",
8662
+ "duration-first",
8663
+ "cost-optimized",
8664
+ "resource-aware",
8665
+ "random-select",
8666
+ "user-choice",
8667
+ "parallel-all",
8668
+ "skip-conflicts",
8669
+ "round-robin"
8670
+ ],
8671
+ description: "Conflict resolution strategy"
8672
+ },
8673
+ execution_mode: {
8674
+ type: "string",
8675
+ enum: ["dependency-mode", "eligibility-mode"],
8676
+ description: "Execution mode"
8677
+ },
8678
+ goal: {
8679
+ type: "array",
8680
+ items: { type: "string" },
8681
+ minItems: 1,
8682
+ description: "Goal outputs \u2014 required when completion is 'goal-reached'"
8683
+ },
8684
+ max_iterations: {
8685
+ type: "integer",
8686
+ minimum: 1,
8687
+ description: "Max scheduler iterations (safety limit, default: 1000)"
8688
+ },
8689
+ timeout_ms: {
8690
+ type: "integer",
8691
+ minimum: 0,
8692
+ description: "Timeout in ms (declared for drivers, not enforced by pure engine)"
8693
+ },
8694
+ refreshStrategy: {
8695
+ $ref: "#/definitions/refresh_strategy",
8696
+ description: "Default refresh strategy for all tasks (default: 'data-changed')"
8697
+ }
8698
+ },
8699
+ additionalProperties: false,
8700
+ if: {
8701
+ properties: { completion: { const: "goal-reached" } }
8702
+ },
8703
+ then: {
8704
+ required: ["completion", "goal"]
8705
+ }
8706
+ },
8707
+ task: {
8708
+ type: "object",
8709
+ required: ["provides"],
8710
+ properties: {
8711
+ requires: {
8712
+ type: "array",
8713
+ items: { type: "string" },
8714
+ description: "Tokens this task needs to become eligible"
8715
+ },
8716
+ provides: {
8717
+ type: "array",
8718
+ items: { type: "string" },
8719
+ description: "Tokens this task produces on successful completion"
8720
+ },
8721
+ on: {
8722
+ type: "object",
8723
+ description: "Conditional provides based on handler result key",
8724
+ additionalProperties: {
8725
+ type: "array",
8726
+ items: { type: "string" }
8727
+ }
8728
+ },
8729
+ on_failure: {
8730
+ type: "array",
8731
+ items: { type: "string" },
8732
+ description: "Tokens to inject when this task fails"
8733
+ },
8734
+ method: {
8735
+ type: "string",
8736
+ description: "Task execution method (informational \u2014 driver concern)"
8737
+ },
8738
+ config: {
8739
+ type: "object",
8740
+ description: "Arbitrary task configuration (driver concern)"
8741
+ },
8742
+ priority: {
8743
+ type: "number",
8744
+ description: "Higher = preferred in conflict resolution"
8745
+ },
8746
+ estimatedDuration: {
8747
+ type: "number",
8748
+ minimum: 0,
8749
+ description: "Estimated duration in ms (used by duration-first strategy)"
8750
+ },
8751
+ estimatedCost: {
8752
+ type: "number",
8753
+ minimum: 0,
8754
+ description: "Estimated cost (used by cost-optimized strategy)"
8755
+ },
8756
+ estimatedResources: {
8757
+ type: "object",
8758
+ additionalProperties: { type: "number" },
8759
+ description: "Resource requirements (used by resource-aware strategy)"
8760
+ },
8761
+ retry: {
8762
+ $ref: "#/definitions/task_retry"
8763
+ },
8764
+ refreshStrategy: {
8765
+ $ref: "#/definitions/refresh_strategy",
8766
+ description: "Task-level refresh strategy (overrides settings default)"
8767
+ },
8768
+ refreshInterval: {
8769
+ type: "number",
8770
+ minimum: 0,
8771
+ description: "Interval in seconds for time-based refresh strategy"
8772
+ },
8773
+ maxExecutions: {
8774
+ type: "integer",
8775
+ minimum: 1,
8776
+ description: "Maximum number of times this task can execute"
8777
+ },
8778
+ circuit_breaker: {
8779
+ $ref: "#/definitions/task_circuit_breaker"
8780
+ },
8781
+ description: {
8782
+ type: "string",
8783
+ description: "Human-readable description"
8784
+ },
8785
+ inference: {
8786
+ $ref: "#/definitions/inference_hints"
8787
+ }
8788
+ },
8789
+ additionalProperties: false
8790
+ },
8791
+ task_retry: {
8792
+ type: "object",
8793
+ required: ["max_attempts"],
8794
+ properties: {
8795
+ max_attempts: {
8796
+ type: "integer",
8797
+ minimum: 1,
8798
+ description: "Maximum retry attempts"
8799
+ },
8800
+ delay_ms: {
8801
+ type: "integer",
8802
+ minimum: 0,
8803
+ description: "Delay between retries in ms"
8804
+ },
8805
+ backoff_multiplier: {
8806
+ type: "number",
8807
+ minimum: 1,
8808
+ description: "Backoff multiplier (e.g., 2 for exponential)"
8809
+ }
8810
+ },
8811
+ additionalProperties: false
8812
+ },
8813
+ refresh_strategy: {
8814
+ type: "string",
8815
+ enum: ["data-changed", "epoch-changed", "time-based", "manual", "once"],
8816
+ description: "Strategy for determining when a completed task should re-run"
8817
+ },
8818
+ task_circuit_breaker: {
8819
+ type: "object",
8820
+ required: ["max_executions", "on_break"],
8821
+ properties: {
8822
+ max_executions: {
8823
+ type: "integer",
8824
+ minimum: 1,
8825
+ description: "Max executions before breaker trips"
8826
+ },
8827
+ on_break: {
8828
+ type: "array",
8829
+ items: { type: "string" },
8830
+ minItems: 1,
8831
+ description: "Tokens to inject when breaker trips"
8832
+ }
8833
+ },
8834
+ additionalProperties: false
8835
+ },
8836
+ inference_hints: {
8837
+ type: "object",
8838
+ description: "LLM inference hints \u2014 opt-in metadata for AI-assisted completion detection",
8839
+ properties: {
8840
+ criteria: {
8841
+ type: "string",
8842
+ description: "Human-readable completion criteria"
8843
+ },
8844
+ keywords: {
8845
+ type: "array",
8846
+ items: { type: "string" },
8847
+ description: "Keywords to help the LLM understand the domain"
8848
+ },
8849
+ suggestedChecks: {
8850
+ type: "array",
8851
+ items: { type: "string" },
8852
+ description: "Suggested checks for verification"
8853
+ },
8854
+ autoDetectable: {
8855
+ type: "boolean",
8856
+ description: "Whether the LLM should attempt to auto-detect completion (default: false)"
8857
+ }
8858
+ },
8859
+ additionalProperties: false
8860
+ }
8861
+ }
8862
+ };
8863
+
8864
+ // src/event-graph/schema-validator.ts
8865
+ var import_ajv2 = __toESM(require_ajv());
8866
+ var _compiled2 = null;
8867
+ function getValidator2() {
8868
+ if (_compiled2) return _compiled2;
8869
+ const ajv = new import_ajv2.default({ allErrors: true });
8870
+ addFormats(ajv);
8871
+ _compiled2 = ajv.compile(event_graph_schema_default);
8872
+ return _compiled2;
8873
+ }
8874
+ function validateGraphSchema(config) {
8875
+ const validate = getValidator2();
8876
+ const valid = validate(config);
8877
+ if (valid) return { ok: true, errors: [] };
8878
+ const errors = (validate.errors ?? []).map((e) => {
8879
+ const path = e.instancePath || "/";
8880
+ return `${path}: ${e.message ?? "unknown error"}`;
8881
+ });
8882
+ return { ok: false, errors };
8883
+ }
8884
+
8885
+ // src/stores/localStorage.ts
8886
+ var LocalStorageStore = class {
8887
+ prefix;
8888
+ constructor(options = {}) {
8889
+ this.prefix = options.prefix ?? "yamlflow";
8890
+ if (typeof localStorage === "undefined") {
8891
+ throw new Error("LocalStorageStore requires localStorage (browser environment)");
8892
+ }
8893
+ }
8894
+ runKey(runId) {
8895
+ return `${this.prefix}:run:${runId}`;
8896
+ }
8897
+ dataKey(runId) {
8898
+ return `${this.prefix}:data:${runId}`;
8899
+ }
8900
+ indexKey() {
8901
+ return `${this.prefix}:runs`;
8902
+ }
8903
+ async saveRunState(runId, state) {
8904
+ localStorage.setItem(this.runKey(runId), JSON.stringify(state));
8905
+ const runs = await this.listRuns();
8906
+ if (!runs.includes(runId)) {
8907
+ runs.push(runId);
8908
+ localStorage.setItem(this.indexKey(), JSON.stringify(runs));
8909
+ }
8910
+ }
8911
+ async loadRunState(runId) {
8912
+ const raw = localStorage.getItem(this.runKey(runId));
8913
+ return raw ? JSON.parse(raw) : null;
8914
+ }
8915
+ async deleteRunState(runId) {
8916
+ localStorage.removeItem(this.runKey(runId));
8917
+ localStorage.removeItem(this.dataKey(runId));
8918
+ const runs = await this.listRuns();
8919
+ const filtered = runs.filter((id) => id !== runId);
8920
+ localStorage.setItem(this.indexKey(), JSON.stringify(filtered));
8921
+ }
8922
+ async setData(runId, key, value) {
8923
+ const allData = await this.getAllData(runId);
8924
+ allData[key] = value;
8925
+ localStorage.setItem(this.dataKey(runId), JSON.stringify(allData));
8926
+ }
8927
+ async getData(runId, key) {
8928
+ const allData = await this.getAllData(runId);
8929
+ return allData[key];
8930
+ }
8931
+ async getAllData(runId) {
8932
+ const raw = localStorage.getItem(this.dataKey(runId));
8933
+ return raw ? JSON.parse(raw) : {};
8934
+ }
8935
+ async clearData(runId) {
8936
+ localStorage.removeItem(this.dataKey(runId));
8937
+ }
8938
+ async listRuns() {
8939
+ const raw = localStorage.getItem(this.indexKey());
8940
+ return raw ? JSON.parse(raw) : [];
8941
+ }
8942
+ /**
8943
+ * Clear all flow data from localStorage
8944
+ */
8945
+ clearAll() {
8946
+ const keysToRemove = [];
8947
+ for (let i = 0; i < localStorage.length; i++) {
8948
+ const key = localStorage.key(i);
8949
+ if (key?.startsWith(this.prefix + ":")) {
8950
+ keysToRemove.push(key);
8951
+ }
8952
+ }
8953
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
8954
+ }
8955
+ };
8956
+
8957
+ // src/stores/file.ts
8958
+ var FileStore = class {
8959
+ directory;
8960
+ fs = null;
8961
+ path = null;
8962
+ constructor(options) {
8963
+ this.directory = options.directory;
8964
+ }
8965
+ async ensureModules() {
8966
+ if (!this.fs || !this.path) {
8967
+ this.fs = await import('fs/promises');
8968
+ this.path = await import('path');
8969
+ await this.fs.mkdir(this.directory, { recursive: true });
8970
+ }
8971
+ }
8972
+ runPath(runId) {
8973
+ return this.path.join(this.directory, `${runId}.run.json`);
8974
+ }
8975
+ dataPath(runId) {
8976
+ return this.path.join(this.directory, `${runId}.data.json`);
8977
+ }
8978
+ async saveRunState(runId, state) {
8979
+ await this.ensureModules();
8980
+ await this.fs.writeFile(
8981
+ this.runPath(runId),
8982
+ JSON.stringify(state, null, 2),
8983
+ "utf-8"
8984
+ );
8985
+ }
8458
8986
  async loadRunState(runId) {
8459
8987
  await this.ensureModules();
8460
8988
  try {
@@ -8719,7 +9247,7 @@ function createLiveGraph(config, executionId) {
8719
9247
  const id = executionId ?? `live-${Date.now()}`;
8720
9248
  const tasks = {};
8721
9249
  for (const taskName of Object.keys(config.tasks)) {
8722
- tasks[taskName] = createDefaultTaskState3();
9250
+ tasks[taskName] = createDefaultGraphEngineStore3();
8723
9251
  }
8724
9252
  const state = {
8725
9253
  status: "running",
@@ -8747,7 +9275,7 @@ function applyEvent(live, event) {
8747
9275
  newState = applyTaskStart(state, event.taskName);
8748
9276
  break;
8749
9277
  case "task-completed":
8750
- newState = applyTaskCompletion(state, config, event.taskName, event.result);
9278
+ newState = applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data);
8751
9279
  break;
8752
9280
  case "task-failed":
8753
9281
  newState = applyTaskFailure(state, config, event.taskName, event.error);
@@ -8755,6 +9283,9 @@ function applyEvent(live, event) {
8755
9283
  case "task-progress":
8756
9284
  newState = applyTaskProgress(state, event.taskName, event.message, event.progress);
8757
9285
  break;
9286
+ case "task-restart":
9287
+ newState = applyTaskRestart(state, event.taskName);
9288
+ break;
8758
9289
  case "inject-tokens":
8759
9290
  newState = {
8760
9291
  ...state,
@@ -8770,6 +9301,9 @@ function applyEvent(live, event) {
8770
9301
  }
8771
9302
  return { config, state: newState };
8772
9303
  }
9304
+ function applyEvents(live, events) {
9305
+ return events.reduce((current, event) => applyEvent(current, event), live);
9306
+ }
8773
9307
  function addNode(live, name, taskConfig) {
8774
9308
  if (live.config.tasks[name]) return live;
8775
9309
  return {
@@ -8779,7 +9313,7 @@ function addNode(live, name, taskConfig) {
8779
9313
  },
8780
9314
  state: {
8781
9315
  ...live.state,
8782
- tasks: { ...live.state.tasks, [name]: createDefaultTaskState3() },
9316
+ tasks: { ...live.state.tasks, [name]: createDefaultGraphEngineStore3() },
8783
9317
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
8784
9318
  }
8785
9319
  };
@@ -8896,7 +9430,7 @@ function resetNode(live, name) {
8896
9430
  ...live.state,
8897
9431
  tasks: {
8898
9432
  ...live.state.tasks,
8899
- [name]: createDefaultTaskState3()
9433
+ [name]: createDefaultGraphEngineStore3()
8900
9434
  },
8901
9435
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
8902
9436
  }
@@ -8935,7 +9469,7 @@ function enableNode(live, name) {
8935
9469
  function getNode(live, name) {
8936
9470
  const config = live.config.tasks[name];
8937
9471
  if (!config) return void 0;
8938
- const state = live.state.tasks[name] ?? createDefaultTaskState3();
9472
+ const state = live.state.tasks[name] ?? createDefaultGraphEngineStore3();
8939
9473
  return { name, config, state };
8940
9474
  }
8941
9475
  function snapshot(live) {
@@ -8973,7 +9507,7 @@ function restore(data) {
8973
9507
  }
8974
9508
  return { config, state };
8975
9509
  }
8976
- function createDefaultTaskState3() {
9510
+ function createDefaultGraphEngineStore3() {
8977
9511
  return {
8978
9512
  status: "not-started",
8979
9513
  executionCount: 0,
@@ -9014,55 +9548,101 @@ function schedule(live) {
9014
9548
  const blocked = [];
9015
9549
  for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
9016
9550
  const taskState = state.tasks[taskName];
9017
- if (!isRepeatableTask(taskConfig)) {
9018
- if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
9019
- continue;
9020
- }
9021
- } else {
9022
- if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
9023
- continue;
9024
- }
9025
- const maxExec = getRepeatableMax(taskConfig);
9026
- if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
9027
- continue;
9028
- }
9029
- if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
9030
- continue;
9031
- }
9032
- if (taskState?.status === TASK_STATUS.COMPLETED) {
9033
- const requires2 = getRequires(taskConfig);
9034
- if (requires2.length > 0) {
9035
- const hasRefreshed = requires2.some((req) => {
9036
- for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
9037
- if (getProvides(otherConfig).includes(req)) {
9038
- const otherState = state.tasks[otherName];
9039
- if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
9040
- }
9041
- }
9042
- return false;
9043
- });
9044
- if (!hasRefreshed) continue;
9045
- } else {
9046
- continue;
9047
- }
9048
- }
9551
+ const strategy = getRefreshStrategy(taskConfig, config.settings);
9552
+ const rerunnable = strategy !== "once";
9553
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
9554
+ continue;
9049
9555
  }
9050
- const requires = getRequires(taskConfig);
9051
- if (requires.length === 0) {
9052
- eligible.push(taskName);
9556
+ const maxExec = getMaxExecutions(taskConfig);
9557
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
9053
9558
  continue;
9054
9559
  }
9055
- const missingTokens = [];
9056
- const pendingTokens = [];
9057
- const failedTokenInfo = [];
9058
- for (const token of requires) {
9059
- if (availableOutputs.has(token)) continue;
9060
- const producers = producerMap[token] || [];
9061
- if (producers.length === 0) {
9062
- missingTokens.push(token);
9063
- } else {
9064
- const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
9065
- if (allFailed) {
9560
+ if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
9561
+ continue;
9562
+ }
9563
+ if (!rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
9564
+ continue;
9565
+ }
9566
+ if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
9567
+ const requires2 = getRequires(taskConfig);
9568
+ let shouldSkip = false;
9569
+ switch (strategy) {
9570
+ case "data-changed": {
9571
+ if (requires2.length > 0) {
9572
+ const hasChangedData = requires2.some((req) => {
9573
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
9574
+ if (getProvides(otherConfig).includes(req)) {
9575
+ const otherState = state.tasks[otherName];
9576
+ if (!otherState) continue;
9577
+ const consumed = taskState.lastConsumedHashes?.[req];
9578
+ if (otherState.lastDataHash == null) {
9579
+ return otherState.executionCount > taskState.lastEpoch;
9580
+ }
9581
+ return otherState.lastDataHash !== consumed;
9582
+ }
9583
+ }
9584
+ return false;
9585
+ });
9586
+ if (!hasChangedData) shouldSkip = true;
9587
+ } else {
9588
+ shouldSkip = true;
9589
+ }
9590
+ break;
9591
+ }
9592
+ case "epoch-changed": {
9593
+ if (requires2.length > 0) {
9594
+ const hasRefreshed = requires2.some((req) => {
9595
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
9596
+ if (getProvides(otherConfig).includes(req)) {
9597
+ const otherState = state.tasks[otherName];
9598
+ if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
9599
+ }
9600
+ }
9601
+ return false;
9602
+ });
9603
+ if (!hasRefreshed) shouldSkip = true;
9604
+ } else {
9605
+ shouldSkip = true;
9606
+ }
9607
+ break;
9608
+ }
9609
+ case "time-based": {
9610
+ const interval = taskConfig.refreshInterval ?? 0;
9611
+ if (interval <= 0) {
9612
+ shouldSkip = true;
9613
+ break;
9614
+ }
9615
+ const completedAt = taskState.completedAt;
9616
+ if (!completedAt) {
9617
+ shouldSkip = true;
9618
+ break;
9619
+ }
9620
+ const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
9621
+ if (elapsedSec < interval) shouldSkip = true;
9622
+ break;
9623
+ }
9624
+ case "manual":
9625
+ shouldSkip = true;
9626
+ break;
9627
+ }
9628
+ if (shouldSkip) continue;
9629
+ }
9630
+ const requires = getRequires(taskConfig);
9631
+ if (requires.length === 0) {
9632
+ eligible.push(taskName);
9633
+ continue;
9634
+ }
9635
+ const missingTokens = [];
9636
+ const pendingTokens = [];
9637
+ const failedTokenInfo = [];
9638
+ for (const token of requires) {
9639
+ if (availableOutputs.has(token)) continue;
9640
+ const producers = producerMap[token] || [];
9641
+ if (producers.length === 0) {
9642
+ missingTokens.push(token);
9643
+ } else {
9644
+ const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
9645
+ if (allFailed) {
9066
9646
  failedTokenInfo.push({ token, failedProducer: producers[0] });
9067
9647
  } else {
9068
9648
  pendingTokens.push(token);
@@ -9440,255 +10020,644 @@ function getDownstream(live, nodeName) {
9440
10020
  }));
9441
10021
  return { nodeName, nodes, tokens: [...tokenSet] };
9442
10022
  }
9443
-
9444
- // src/inference/core.ts
9445
- var DEFAULT_THRESHOLD = 0.5;
9446
- var DEFAULT_SYSTEM_PROMPT = `You are a workflow completion analyzer. Given a graph of tasks with their current states, evidence, and inference hints, determine which tasks appear to be completed based on the available evidence.
9447
-
9448
- For each task you analyze, provide a JSON response. Be conservative \u2014 only mark tasks as completed when the evidence strongly supports it.`;
9449
- function buildInferencePrompt(live, options = {}) {
9450
- const { scope, context, systemPrompt } = options;
9451
- const graphTasks = getAllTasks(live.config);
9452
- const { state } = live;
9453
- const candidates = getAnalyzableCandidates(live, scope);
9454
- if (candidates.length === 0) {
9455
- return "";
10023
+ var MemoryJournal = class {
10024
+ buffer = [];
10025
+ append(event) {
10026
+ this.buffer.push(event);
9456
10027
  }
9457
- const lines = [];
9458
- lines.push(systemPrompt || DEFAULT_SYSTEM_PROMPT);
9459
- lines.push("");
9460
- lines.push("## Graph State");
9461
- lines.push("");
9462
- lines.push(`Available tokens: ${state.availableOutputs.length > 0 ? state.availableOutputs.join(", ") : "(none)"}`);
9463
- lines.push("");
9464
- const completedTasks = Object.entries(state.tasks).filter(([_, ts]) => ts.status === "completed").map(([name]) => name);
9465
- if (completedTasks.length > 0) {
9466
- lines.push(`Completed tasks: ${completedTasks.join(", ")}`);
9467
- lines.push("");
10028
+ drain() {
10029
+ const events = this.buffer;
10030
+ this.buffer = [];
10031
+ return events;
9468
10032
  }
9469
- lines.push("## Tasks to Analyze");
9470
- lines.push("");
9471
- for (const taskName of candidates) {
9472
- const taskConfig = graphTasks[taskName];
9473
- const taskState = state.tasks[taskName];
9474
- lines.push(`### ${taskName}`);
9475
- if (taskConfig.description) {
9476
- lines.push(`Description: ${taskConfig.description}`);
9477
- }
9478
- const requires = getRequires(taskConfig);
9479
- const provides = getProvides(taskConfig);
9480
- if (requires.length > 0) lines.push(`Requires: ${requires.join(", ")}`);
9481
- if (provides.length > 0) lines.push(`Provides: ${provides.join(", ")}`);
9482
- lines.push(`Current status: ${taskState?.status || "not-started"}`);
9483
- const hints = taskConfig.inference;
9484
- if (hints) {
9485
- if (hints.criteria) lines.push(`Completion criteria: ${hints.criteria}`);
9486
- if (hints.keywords?.length) lines.push(`Keywords: ${hints.keywords.join(", ")}`);
9487
- if (hints.suggestedChecks?.length) lines.push(`Suggested checks: ${hints.suggestedChecks.join("; ")}`);
10033
+ get size() {
10034
+ return this.buffer.length;
10035
+ }
10036
+ };
10037
+ var FileJournal = class {
10038
+ constructor(path) {
10039
+ this.path = path;
10040
+ if (!existsSync(path)) {
10041
+ writeFileSync(path, "", "utf-8");
10042
+ }
10043
+ }
10044
+ path;
10045
+ pending = 0;
10046
+ append(event) {
10047
+ appendFileSync(this.path, JSON.stringify(event) + "\n", "utf-8");
10048
+ this.pending++;
10049
+ }
10050
+ drain() {
10051
+ const content = readFileSync(this.path, "utf-8").trim();
10052
+ writeFileSync(this.path, "", "utf-8");
10053
+ this.pending = 0;
10054
+ if (!content) return [];
10055
+ return content.split("\n").map((line) => JSON.parse(line));
10056
+ }
10057
+ get size() {
10058
+ try {
10059
+ const content = readFileSync(this.path, "utf-8").trim();
10060
+ if (!content) return 0;
10061
+ return content.split("\n").length;
10062
+ } catch {
10063
+ return this.pending;
9488
10064
  }
9489
- lines.push("");
9490
10065
  }
9491
- if (context) {
9492
- lines.push("## Additional Context / Evidence");
9493
- lines.push("");
9494
- lines.push(context);
9495
- lines.push("");
10066
+ };
10067
+ function computeDataHash(data) {
10068
+ const json = stableStringify(data);
10069
+ return createHash("sha256").update(json).digest("hex").slice(0, 16);
10070
+ }
10071
+ function stableStringify(value) {
10072
+ if (value === null || value === void 0 || typeof value !== "object") {
10073
+ return JSON.stringify(value);
9496
10074
  }
9497
- lines.push("## Response Format");
9498
- lines.push("");
9499
- lines.push("Respond with a JSON array of objects, one per task you have evidence for:");
9500
- lines.push("```json");
9501
- lines.push("[");
9502
- lines.push(" {");
9503
- lines.push(' "taskName": "task-name",');
9504
- lines.push(' "confidence": 0.0 to 1.0,');
9505
- lines.push(' "reasoning": "explanation of why you believe this task is complete or not"');
9506
- lines.push(" }");
9507
- lines.push("]");
9508
- lines.push("```");
9509
- lines.push("");
9510
- lines.push("Rules:");
9511
- lines.push('- Only include tasks from the "Tasks to Analyze" section');
9512
- lines.push("- confidence 0.0 = no evidence of completion, 1.0 = certain it is complete");
9513
- lines.push("- If you have no evidence for a task, omit it from the array");
9514
- lines.push("- Be conservative \u2014 require clear evidence before high confidence");
9515
- lines.push("- Respond ONLY with the JSON array, no additional text");
9516
- return lines.join("\n");
10075
+ if (Array.isArray(value)) {
10076
+ return "[" + value.map(stableStringify).join(",") + "]";
10077
+ }
10078
+ const obj = value;
10079
+ const keys = Object.keys(obj).sort();
10080
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
9517
10081
  }
9518
- async function inferCompletions(live, adapter, options = {}) {
9519
- options.threshold ?? DEFAULT_THRESHOLD;
9520
- const analyzedNodes = getAnalyzableCandidates(live, options.scope);
9521
- if (analyzedNodes.length === 0) {
9522
- return { suggestions: [], promptUsed: "", rawResponse: "", analyzedNodes: [] };
10082
+ function encodeCallbackToken(taskName) {
10083
+ const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
10084
+ return Buffer.from(payload).toString("base64url");
10085
+ }
10086
+ function decodeCallbackToken(token) {
10087
+ try {
10088
+ const payload = JSON.parse(Buffer.from(token, "base64url").toString());
10089
+ if (typeof payload?.t === "string") return { taskName: payload.t };
10090
+ return null;
10091
+ } catch {
10092
+ return null;
9523
10093
  }
9524
- const prompt = buildInferencePrompt(live, options);
9525
- const rawResponse = await adapter.analyze(prompt);
9526
- const suggestions = parseInferenceResponse(rawResponse, analyzedNodes);
9527
- return {
9528
- suggestions,
9529
- promptUsed: prompt,
9530
- rawResponse,
9531
- analyzedNodes
9532
- };
9533
10094
  }
9534
- function applyInferences(live, result, threshold = DEFAULT_THRESHOLD) {
9535
- let current = live;
9536
- for (const suggestion of result.suggestions) {
9537
- if (suggestion.confidence < threshold) continue;
9538
- const taskState = current.state.tasks[suggestion.taskName];
9539
- if (!taskState) continue;
9540
- if (taskState.status === "completed" || taskState.status === "running") continue;
9541
- const now = (/* @__PURE__ */ new Date()).toISOString();
9542
- current = applyEvent(current, {
10095
+ function createReactiveGraph(config, options, executionId) {
10096
+ const {
10097
+ handlers: initialHandlers,
10098
+ journal = new MemoryJournal(),
10099
+ onDrain
10100
+ } = options;
10101
+ let live = createLiveGraph(config, executionId);
10102
+ let disposed = false;
10103
+ const handlers = new Map(Object.entries(initialHandlers));
10104
+ let draining = false;
10105
+ let drainQueued = false;
10106
+ function drain() {
10107
+ if (disposed) return;
10108
+ if (draining) {
10109
+ drainQueued = true;
10110
+ return;
10111
+ }
10112
+ draining = true;
10113
+ try {
10114
+ do {
10115
+ drainQueued = false;
10116
+ drainOnce();
10117
+ } while (drainQueued);
10118
+ } finally {
10119
+ draining = false;
10120
+ }
10121
+ }
10122
+ function drainOnce() {
10123
+ const events = journal.drain();
10124
+ if (events.length > 0) {
10125
+ live = applyEvents(live, events);
10126
+ }
10127
+ const result = schedule(live);
10128
+ if (events.length > 0) {
10129
+ onDrain?.(events, live, result);
10130
+ }
10131
+ for (const taskName of result.eligible) {
10132
+ dispatchTask(taskName);
10133
+ }
10134
+ }
10135
+ function resolveUpstreamState(taskName) {
10136
+ const taskConfig = live.config.tasks[taskName];
10137
+ const requires = taskConfig.requires ?? [];
10138
+ const tokenToTask = /* @__PURE__ */ new Map();
10139
+ for (const [name, cfg] of Object.entries(live.config.tasks)) {
10140
+ for (const token of cfg.provides ?? []) {
10141
+ tokenToTask.set(token, name);
10142
+ }
10143
+ }
10144
+ const state = {};
10145
+ for (const token of requires) {
10146
+ const producerTask = tokenToTask.get(token);
10147
+ if (producerTask) {
10148
+ state[token] = live.state.tasks[producerTask]?.data;
10149
+ } else {
10150
+ state[token] = void 0;
10151
+ }
10152
+ }
10153
+ return state;
10154
+ }
10155
+ async function runPipeline(taskName, callbackToken) {
10156
+ const taskConfig = live.config.tasks[taskName];
10157
+ const handlerNames = taskConfig.taskHandlers ?? [];
10158
+ const upstreamState = resolveUpstreamState(taskName);
10159
+ for (const handlerName of handlerNames) {
10160
+ const handler = handlers.get(handlerName);
10161
+ if (!handler) {
10162
+ throw new Error(`Handler '${handlerName}' not found in registry (task '${taskName}')`);
10163
+ }
10164
+ const input = {
10165
+ nodeId: taskName,
10166
+ state: upstreamState,
10167
+ taskState: live.state.tasks[taskName],
10168
+ config: taskConfig,
10169
+ callbackToken
10170
+ };
10171
+ const status = await handler(input);
10172
+ if (status === "task-initiate-failure") {
10173
+ throw new Error(`Handler '${handlerName}' returned task-initiate-failure (task '${taskName}')`);
10174
+ }
10175
+ }
10176
+ }
10177
+ function dispatchTask(taskName) {
10178
+ const taskConfig = live.config.tasks[taskName];
10179
+ const handlerNames = taskConfig?.taskHandlers;
10180
+ if (!handlerNames || handlerNames.length === 0) {
10181
+ return;
10182
+ }
10183
+ journal.append({
9543
10184
  type: "task-started",
9544
- taskName: suggestion.taskName,
9545
- timestamp: now
10185
+ taskName,
10186
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9546
10187
  });
9547
- current = applyEvent(current, {
9548
- type: "task-completed",
9549
- taskName: suggestion.taskName,
9550
- timestamp: now,
9551
- result: "llm-inferred"
10188
+ const callbackToken = encodeCallbackToken(taskName);
10189
+ runPipeline(taskName, callbackToken).catch((error) => {
10190
+ if (disposed) return;
10191
+ journal.append({
10192
+ type: "task-failed",
10193
+ taskName,
10194
+ error: error.message ?? String(error),
10195
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10196
+ });
10197
+ drain();
9552
10198
  });
9553
10199
  }
9554
- return current;
9555
- }
9556
- async function inferAndApply(live, adapter, options = {}) {
9557
- const threshold = options.threshold ?? DEFAULT_THRESHOLD;
9558
- const inference = await inferCompletions(live, adapter, options);
9559
- const updated = applyInferences(live, inference, threshold);
9560
- const applied = inference.suggestions.filter((s) => s.confidence >= threshold);
9561
- const skipped = inference.suggestions.filter((s) => s.confidence < threshold);
9562
10200
  return {
9563
- live: updated,
9564
- inference,
9565
- applied,
9566
- skipped
10201
+ push(event) {
10202
+ if (disposed) return;
10203
+ if (event.type === "task-completed" && event.data && !event.dataHash) {
10204
+ event = { ...event, dataHash: computeDataHash(event.data) };
10205
+ }
10206
+ journal.append(event);
10207
+ drain();
10208
+ },
10209
+ pushAll(events) {
10210
+ if (disposed) return;
10211
+ for (const event of events) {
10212
+ if (event.type === "task-completed" && event.data && !event.dataHash) {
10213
+ journal.append({ ...event, dataHash: computeDataHash(event.data) });
10214
+ } else {
10215
+ journal.append(event);
10216
+ }
10217
+ }
10218
+ drain();
10219
+ },
10220
+ resolveCallback(callbackToken, data, errors) {
10221
+ if (disposed) return;
10222
+ const decoded = decodeCallbackToken(callbackToken);
10223
+ if (!decoded) return;
10224
+ const { taskName } = decoded;
10225
+ if (!live.config.tasks[taskName]) return;
10226
+ if (errors && errors.length > 0) {
10227
+ journal.append({
10228
+ type: "task-failed",
10229
+ taskName,
10230
+ error: errors.join("; "),
10231
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10232
+ });
10233
+ } else {
10234
+ const dataHash = data && Object.keys(data).length > 0 ? computeDataHash(data) : void 0;
10235
+ journal.append({
10236
+ type: "task-completed",
10237
+ taskName,
10238
+ data,
10239
+ dataHash,
10240
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10241
+ });
10242
+ }
10243
+ drain();
10244
+ },
10245
+ addNode(name, taskConfig) {
10246
+ if (disposed) return;
10247
+ live = addNode(live, name, taskConfig);
10248
+ drain();
10249
+ },
10250
+ removeNode(name) {
10251
+ if (disposed) return;
10252
+ live = removeNode(live, name);
10253
+ },
10254
+ addRequires(nodeName, tokens) {
10255
+ if (disposed) return;
10256
+ live = addRequires(live, nodeName, tokens);
10257
+ drain();
10258
+ },
10259
+ removeRequires(nodeName, tokens) {
10260
+ if (disposed) return;
10261
+ live = removeRequires(live, nodeName, tokens);
10262
+ drain();
10263
+ },
10264
+ addProvides(nodeName, tokens) {
10265
+ if (disposed) return;
10266
+ live = addProvides(live, nodeName, tokens);
10267
+ drain();
10268
+ },
10269
+ removeProvides(nodeName, tokens) {
10270
+ if (disposed) return;
10271
+ live = removeProvides(live, nodeName, tokens);
10272
+ },
10273
+ registerHandler(name, fn) {
10274
+ handlers.set(name, fn);
10275
+ },
10276
+ unregisterHandler(name) {
10277
+ handlers.delete(name);
10278
+ },
10279
+ retrigger(taskName) {
10280
+ if (disposed) return;
10281
+ if (!live.config.tasks[taskName]) return;
10282
+ journal.append({
10283
+ type: "task-restart",
10284
+ taskName,
10285
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10286
+ });
10287
+ drain();
10288
+ },
10289
+ retriggerAll(taskNames) {
10290
+ if (disposed) return;
10291
+ for (const name of taskNames) {
10292
+ if (!live.config.tasks[name]) continue;
10293
+ journal.append({
10294
+ type: "task-restart",
10295
+ taskName: name,
10296
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10297
+ });
10298
+ }
10299
+ drain();
10300
+ },
10301
+ getState() {
10302
+ return live;
10303
+ },
10304
+ getSchedule() {
10305
+ return schedule(live);
10306
+ },
10307
+ dispose() {
10308
+ disposed = true;
10309
+ }
9567
10310
  };
9568
10311
  }
9569
- function getAnalyzableCandidates(live, scope) {
9570
- const graphTasks = getAllTasks(live.config);
9571
- const { state } = live;
9572
- const candidates = [];
9573
- for (const [name, config] of Object.entries(graphTasks)) {
9574
- const taskState = state.tasks[name];
9575
- if (taskState?.status === "completed" || taskState?.status === "running") continue;
9576
- if (scope) {
9577
- if (scope.includes(name)) candidates.push(name);
9578
- } else {
9579
- if (config.inference?.autoDetectable) candidates.push(name);
10312
+
10313
+ // src/continuous-event-graph/validate.ts
10314
+ function validateLiveGraph(live) {
10315
+ const issues = [];
10316
+ const { config, state } = live;
10317
+ const tasks = getAllTasks(config);
10318
+ const taskNames = Object.keys(tasks);
10319
+ for (const name of taskNames) {
10320
+ if (!state.tasks[name]) {
10321
+ issues.push({
10322
+ severity: "error",
10323
+ code: "MISSING_STATE",
10324
+ message: `Task "${name}" exists in config but has no state entry`,
10325
+ tasks: [name]
10326
+ });
9580
10327
  }
9581
10328
  }
9582
- return candidates;
9583
- }
9584
- function parseInferenceResponse(rawResponse, validNodes, _threshold) {
9585
- const validSet = new Set(validNodes);
9586
- try {
9587
- const jsonStr = extractJson(rawResponse);
9588
- if (!jsonStr) return [];
9589
- const parsed = JSON.parse(jsonStr);
9590
- if (!Array.isArray(parsed)) return [];
9591
- const suggestions = [];
9592
- for (const item of parsed) {
9593
- if (!item || typeof item !== "object") continue;
9594
- if (typeof item.taskName !== "string") continue;
9595
- if (typeof item.confidence !== "number") continue;
9596
- if (!validSet.has(item.taskName)) continue;
9597
- const confidence = Math.max(0, Math.min(1, item.confidence));
9598
- suggestions.push({
9599
- taskName: item.taskName,
9600
- confidence,
9601
- reasoning: typeof item.reasoning === "string" ? item.reasoning : "",
9602
- detectionMethod: "llm-inferred"
10329
+ for (const name of Object.keys(state.tasks)) {
10330
+ if (!tasks[name]) {
10331
+ issues.push({
10332
+ severity: "warning",
10333
+ code: "ORPHAN_STATE",
10334
+ message: `State entry "${name}" has no corresponding task config`,
10335
+ tasks: [name]
9603
10336
  });
9604
10337
  }
9605
- return suggestions;
9606
- } catch {
9607
- return [];
9608
10338
  }
9609
- }
9610
- function extractJson(text) {
9611
- if (!text || typeof text !== "string") return null;
9612
- const trimmed = text.trim();
9613
- const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
9614
- if (fenceMatch) return fenceMatch[1].trim();
9615
- const firstBracket = trimmed.indexOf("[");
9616
- const lastBracket = trimmed.lastIndexOf("]");
9617
- if (firstBracket !== -1 && lastBracket > firstBracket) {
9618
- return trimmed.slice(firstBracket, lastBracket + 1);
10339
+ for (const name of taskNames) {
10340
+ const ts = state.tasks[name];
10341
+ if (!ts) continue;
10342
+ if (ts.status === TASK_STATUS.RUNNING && !ts.startedAt) {
10343
+ issues.push({
10344
+ severity: "warning",
10345
+ code: "RUNNING_WITHOUT_START",
10346
+ message: `Task "${name}" is running but has no startedAt timestamp`,
10347
+ tasks: [name]
10348
+ });
10349
+ }
10350
+ if (ts.status === TASK_STATUS.COMPLETED && !ts.completedAt) {
10351
+ issues.push({
10352
+ severity: "warning",
10353
+ code: "COMPLETED_WITHOUT_TIMESTAMP",
10354
+ message: `Task "${name}" is completed but has no completedAt timestamp`,
10355
+ tasks: [name]
10356
+ });
10357
+ }
10358
+ if (ts.status === TASK_STATUS.FAILED) {
10359
+ if (!ts.failedAt) {
10360
+ issues.push({
10361
+ severity: "warning",
10362
+ code: "FAILED_WITHOUT_INFO",
10363
+ message: `Task "${name}" is failed but has no failedAt timestamp`,
10364
+ tasks: [name]
10365
+ });
10366
+ }
10367
+ if (!ts.error) {
10368
+ issues.push({
10369
+ severity: "info",
10370
+ code: "FAILED_WITHOUT_INFO",
10371
+ message: `Task "${name}" is failed but has no error message`,
10372
+ tasks: [name]
10373
+ });
10374
+ }
10375
+ }
9619
10376
  }
9620
- if (trimmed.startsWith("[")) return trimmed;
9621
- return null;
10377
+ const expectedOutputs = /* @__PURE__ */ new Set();
10378
+ for (const name of taskNames) {
10379
+ const ts = state.tasks[name];
10380
+ if (ts?.status === TASK_STATUS.COMPLETED) {
10381
+ for (const token of getProvides(tasks[name])) {
10382
+ expectedOutputs.add(token);
10383
+ }
10384
+ }
10385
+ }
10386
+ const actualOutputs = new Set(state.availableOutputs);
10387
+ const allProducible = /* @__PURE__ */ new Set();
10388
+ for (const taskConfig of Object.values(tasks)) {
10389
+ for (const t of getProvides(taskConfig)) allProducible.add(t);
10390
+ if (taskConfig.on) {
10391
+ for (const tokens of Object.values(taskConfig.on)) {
10392
+ for (const t of tokens) allProducible.add(t);
10393
+ }
10394
+ }
10395
+ if (taskConfig.on_failure) {
10396
+ for (const t of taskConfig.on_failure) allProducible.add(t);
10397
+ }
10398
+ }
10399
+ for (const token of actualOutputs) {
10400
+ if (!expectedOutputs.has(token) && !allProducible.has(token)) {
10401
+ issues.push({
10402
+ severity: "info",
10403
+ code: "INJECTED_TOKEN",
10404
+ message: `Token "${token}" is available but no task in the graph can produce it (likely injected)`,
10405
+ tokens: [token]
10406
+ });
10407
+ }
10408
+ }
10409
+ for (const token of expectedOutputs) {
10410
+ if (!actualOutputs.has(token)) {
10411
+ issues.push({
10412
+ severity: "warning",
10413
+ code: "MISSING_OUTPUT",
10414
+ message: `Token "${token}" should be available (its producer completed) but is not in availableOutputs`,
10415
+ tokens: [token]
10416
+ });
10417
+ }
10418
+ }
10419
+ for (const name of taskNames) {
10420
+ const ts = state.tasks[name];
10421
+ if (!ts) continue;
10422
+ if (ts.executionCount < 0) {
10423
+ issues.push({
10424
+ severity: "error",
10425
+ code: "INVALID_EXECUTION_COUNT",
10426
+ message: `Task "${name}" has negative execution count: ${ts.executionCount}`,
10427
+ tasks: [name]
10428
+ });
10429
+ }
10430
+ const maxExec = tasks[name].maxExecutions;
10431
+ if (maxExec !== void 0 && ts.executionCount > maxExec) {
10432
+ issues.push({
10433
+ severity: "error",
10434
+ code: "EXCEEDED_MAX_EXECUTIONS",
10435
+ message: `Task "${name}" executed ${ts.executionCount} times, exceeding maxExecutions of ${maxExec}`,
10436
+ tasks: [name]
10437
+ });
10438
+ }
10439
+ }
10440
+ return buildResult2(issues);
9622
10441
  }
9623
- function createCliAdapter(opts) {
9624
- const timeout = opts.timeout ?? 6e4;
9625
- return {
9626
- analyze: (prompt) => {
9627
- return new Promise((resolve2, reject) => {
9628
- const args = opts.args(prompt);
9629
- const child = execFile(
9630
- opts.command,
9631
- opts.stdin ? opts.args("") : args,
9632
- {
9633
- timeout,
9634
- cwd: opts.cwd,
9635
- env: opts.env ? { ...process.env, ...opts.env } : void 0,
9636
- maxBuffer: 10 * 1024 * 1024
9637
- // 10MB
9638
- },
9639
- (error, stdout, stderr) => {
9640
- if (error) {
9641
- reject(new Error(
9642
- `CLI adapter failed: ${opts.command} exited with ${error.code ?? "error"}` + (stderr ? `
9643
- stderr: ${stderr.slice(0, 500)}` : "") + `
9644
- ${error.message}`
9645
- ));
9646
- } else {
9647
- resolve2(stdout);
9648
- }
9649
- }
9650
- );
9651
- if (opts.stdin && child.stdin) {
9652
- child.stdin.write(prompt);
9653
- child.stdin.end();
9654
- }
10442
+ function validateReactiveGraph(input) {
10443
+ const { graph, handlers } = input;
10444
+ const live = graph.getState();
10445
+ const issues = [];
10446
+ const tasks = getAllTasks(live.config);
10447
+ const taskNames = Object.keys(tasks);
10448
+ const handlerNames = new Set(Object.keys(handlers));
10449
+ const referencedHandlers = /* @__PURE__ */ new Set();
10450
+ for (const name of taskNames) {
10451
+ const taskHandlers = tasks[name].taskHandlers;
10452
+ if (taskHandlers) {
10453
+ for (const h of taskHandlers) {
10454
+ referencedHandlers.add(h);
10455
+ }
10456
+ }
10457
+ }
10458
+ for (const name of taskNames) {
10459
+ const taskHandlers = tasks[name].taskHandlers;
10460
+ if (!taskHandlers) continue;
10461
+ for (const h of taskHandlers) {
10462
+ if (!handlers[h]) {
10463
+ issues.push({
10464
+ severity: "error",
10465
+ code: "MISSING_HANDLER",
10466
+ message: `Task "${name}" references handler "${h}" but it is not in the registry`,
10467
+ tasks: [name]
10468
+ });
10469
+ }
10470
+ }
10471
+ }
10472
+ for (const name of handlerNames) {
10473
+ if (!referencedHandlers.has(name)) {
10474
+ issues.push({
10475
+ severity: "warning",
10476
+ code: "ORPHAN_HANDLER",
10477
+ message: `Handler "${name}" is registered but not referenced by any task's taskHandlers`,
10478
+ tasks: [name]
9655
10479
  });
9656
10480
  }
9657
- };
10481
+ }
10482
+ const liveResult = validateLiveGraph(live);
10483
+ issues.push(...liveResult.issues);
10484
+ return buildResult2(issues);
9658
10485
  }
9659
- function createHttpAdapter(opts) {
9660
- const timeout = opts.timeout ?? 6e4;
10486
+ function buildResult2(issues) {
10487
+ const errors = issues.filter((i) => i.severity === "error");
10488
+ const warnings = issues.filter((i) => i.severity === "warning");
9661
10489
  return {
9662
- analyze: async (prompt) => {
9663
- const body = opts.buildBody ? opts.buildBody(prompt) : { prompt };
9664
- const controller = new AbortController();
9665
- const timer = setTimeout(() => controller.abort(), timeout);
9666
- try {
9667
- const response = await fetch(opts.url, {
9668
- method: "POST",
9669
- headers: {
9670
- "Content-Type": "application/json",
9671
- ...opts.headers ?? {}
9672
- },
9673
- body: JSON.stringify(body),
9674
- signal: controller.signal
9675
- });
9676
- if (!response.ok) {
9677
- const text = await response.text().catch(() => "");
9678
- throw new Error(`HTTP ${response.status}: ${text.slice(0, 500)}`);
10490
+ valid: errors.length === 0,
10491
+ issues,
10492
+ errors,
10493
+ warnings
10494
+ };
10495
+ }
10496
+
10497
+ // src/continuous-event-graph/mutate.ts
10498
+ function mutateGraph(live, mutations) {
10499
+ let current = live;
10500
+ for (const mutation of mutations) {
10501
+ current = applySingleMutation(current, mutation);
10502
+ }
10503
+ return current;
10504
+ }
10505
+ function applySingleMutation(live, mutation) {
10506
+ switch (mutation.type) {
10507
+ case "add-node":
10508
+ return addNode(live, mutation.name, mutation.config);
10509
+ case "remove-node":
10510
+ return removeNode(live, mutation.name);
10511
+ case "add-requires":
10512
+ return addRequires(live, mutation.taskName, mutation.tokens);
10513
+ case "remove-requires":
10514
+ return removeRequires(live, mutation.taskName, mutation.tokens);
10515
+ case "add-provides":
10516
+ return addProvides(live, mutation.taskName, mutation.tokens);
10517
+ case "remove-provides":
10518
+ return removeProvides(live, mutation.taskName, mutation.tokens);
10519
+ case "inject-tokens":
10520
+ return injectTokens(live, mutation.tokens);
10521
+ case "drain-tokens":
10522
+ return drainTokens(live, mutation.tokens);
10523
+ case "reset-node":
10524
+ return resetNode(live, mutation.name);
10525
+ case "disable-node":
10526
+ return disableNode(live, mutation.name);
10527
+ case "enable-node":
10528
+ return enableNode(live, mutation.name);
10529
+ case "apply-events":
10530
+ return applyEvents(live, mutation.events);
10531
+ default:
10532
+ throw new Error(`Unknown mutation type: ${mutation.type}`);
10533
+ }
10534
+ }
10535
+ function createCallbackHandler(fn, getResolve) {
10536
+ return async (input) => {
10537
+ const { callbackToken } = input;
10538
+ Promise.resolve(fn(input)).then((data) => getResolve()(callbackToken, data)).catch((err) => getResolve()(callbackToken, {}, [err instanceof Error ? err.message : String(err)]));
10539
+ return "task-initiated";
10540
+ };
10541
+ }
10542
+ function createFireAndForgetHandler(fn, getResolve) {
10543
+ return async (input) => {
10544
+ const { callbackToken } = input;
10545
+ Promise.resolve(fn(input)).then(() => getResolve()(callbackToken, {})).catch(() => getResolve()(callbackToken, {}));
10546
+ return "task-initiated";
10547
+ };
10548
+ }
10549
+ function createShellHandler(options) {
10550
+ const {
10551
+ command: commandTemplate,
10552
+ cwd,
10553
+ env,
10554
+ timeoutMs = 3e4,
10555
+ exitCodeMap,
10556
+ captureOutput = false,
10557
+ getResolve
10558
+ } = options;
10559
+ return async (input) => {
10560
+ const { callbackToken, nodeId } = input;
10561
+ const command = commandTemplate.replace(/\$\{taskName\}/g, nodeId);
10562
+ exec(
10563
+ command,
10564
+ {
10565
+ cwd,
10566
+ env: env ? { ...process.env, ...env } : void 0,
10567
+ timeout: timeoutMs,
10568
+ maxBuffer: 10 * 1024 * 1024
10569
+ // 10MB
10570
+ },
10571
+ (error, stdout, stderr) => {
10572
+ const exitCode = error?.code ?? (error ? 1 : 0);
10573
+ if (exitCode !== 0 && !exitCodeMap?.[exitCode]) {
10574
+ getResolve()(callbackToken, {}, [`Command exited with code ${exitCode}: ${stderr || error?.message}`]);
10575
+ return;
9679
10576
  }
9680
- const json = await response.json();
9681
- if (opts.extractResponse) {
9682
- return opts.extractResponse(json);
10577
+ const data = {};
10578
+ if (captureOutput) {
10579
+ data.stdout = stdout;
10580
+ data.stderr = stderr;
10581
+ data.exitCode = exitCode;
9683
10582
  }
9684
- if (typeof json.response === "string") return json.response;
9685
- if (typeof json.text === "string") return json.text;
9686
- if (typeof json.content === "string") return json.content;
9687
- return JSON.stringify(json);
9688
- } finally {
9689
- clearTimeout(timer);
10583
+ getResolve()(callbackToken, data);
9690
10584
  }
9691
- }
10585
+ );
10586
+ return "task-initiated";
10587
+ };
10588
+ }
10589
+ function detectRuntime(scriptPath) {
10590
+ if (scriptPath.endsWith(".js") || scriptPath.endsWith(".mjs") || scriptPath.endsWith(".ts")) return "node";
10591
+ if (scriptPath.endsWith(".py")) return "python3";
10592
+ if (scriptPath.endsWith(".sh")) return "bash";
10593
+ return "bash";
10594
+ }
10595
+ function createScriptHandler(options) {
10596
+ const {
10597
+ scriptPath,
10598
+ runtime,
10599
+ args = [],
10600
+ cwd,
10601
+ timeoutMs = 6e4,
10602
+ captureOutput = false,
10603
+ getResolve
10604
+ } = options;
10605
+ const resolvedRuntime = runtime ?? detectRuntime(scriptPath);
10606
+ const shellArgs = [ctx_taskName_placeholder, ...args].join(" ");
10607
+ const command = `${resolvedRuntime} ${scriptPath} ${shellArgs}`;
10608
+ return createShellHandler({
10609
+ command: command.replace(ctx_taskName_placeholder, "${taskName}"),
10610
+ cwd,
10611
+ timeoutMs,
10612
+ captureOutput,
10613
+ getResolve
10614
+ });
10615
+ }
10616
+ var ctx_taskName_placeholder = "__TASK_NAME__";
10617
+ function createWebhookHandler(options) {
10618
+ const {
10619
+ url: urlTemplate,
10620
+ method = "POST",
10621
+ headers = {},
10622
+ timeoutMs = 3e4,
10623
+ failOnNon2xx = true,
10624
+ getResolve
10625
+ } = options;
10626
+ return async (input) => {
10627
+ const { callbackToken, nodeId, config } = input;
10628
+ const url = urlTemplate.replace(/\$\{taskName\}/g, nodeId);
10629
+ const body = JSON.stringify({
10630
+ taskName: nodeId,
10631
+ callbackToken,
10632
+ config
10633
+ });
10634
+ const controller = new AbortController();
10635
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
10636
+ fetch(url, {
10637
+ method,
10638
+ headers: { "Content-Type": "application/json", ...headers },
10639
+ body,
10640
+ signal: controller.signal
10641
+ }).then(async (response) => {
10642
+ clearTimeout(timer);
10643
+ if (failOnNon2xx && !response.ok) {
10644
+ const text = await response.text().catch(() => "");
10645
+ getResolve()(callbackToken, {}, [`HTTP ${response.status}: ${text}`]);
10646
+ return;
10647
+ }
10648
+ const data = await response.json().catch(() => ({}));
10649
+ getResolve()(callbackToken, data);
10650
+ }).catch((err) => {
10651
+ clearTimeout(timer);
10652
+ getResolve()(callbackToken, {}, [err instanceof Error ? err.message : String(err)]);
10653
+ });
10654
+ return "task-initiated";
10655
+ };
10656
+ }
10657
+ function createNoopHandler(getResolve, staticData) {
10658
+ return async (input) => {
10659
+ getResolve()(input.callbackToken, staticData ?? {});
10660
+ return "task-initiated";
9692
10661
  };
9693
10662
  }
9694
10663
 
@@ -10005,17 +10974,17 @@ var live_cards_schema_default = {
10005
10974
  };
10006
10975
 
10007
10976
  // src/card-compute/schema-validator.ts
10008
- var import_ajv = __toESM(require_ajv());
10009
- var _compiled = null;
10010
- function getValidator() {
10011
- if (_compiled) return _compiled;
10012
- const ajv = new import_ajv.default({ allErrors: true });
10977
+ var import_ajv3 = __toESM(require_ajv());
10978
+ var _compiled3 = null;
10979
+ function getValidator3() {
10980
+ if (_compiled3) return _compiled3;
10981
+ const ajv = new import_ajv3.default({ allErrors: true });
10013
10982
  addFormats(ajv);
10014
- _compiled = ajv.compile(live_cards_schema_default);
10015
- return _compiled;
10983
+ _compiled3 = ajv.compile(live_cards_schema_default);
10984
+ return _compiled3;
10016
10985
  }
10017
10986
  function validateLiveCardSchema(node) {
10018
- const validate = getValidator();
10987
+ const validate = getValidator3();
10019
10988
  const valid = validate(node);
10020
10989
  if (valid) return { ok: true, errors: [] };
10021
10990
  const errors = (validate.errors ?? []).map((e) => {
@@ -10472,6 +11441,405 @@ var CardCompute = {
10472
11441
  }
10473
11442
  };
10474
11443
 
10475
- export { COMPLETION_STRATEGIES, CONFLICT_STRATEGIES, CardCompute, DEFAULTS, EXECUTION_MODES, EXECUTION_STATUS, FileStore, StepMachine as FlowEngine, LocalStorageStore, MemoryStore, StepMachine, TASK_STATUS, addDynamicTask, addNode, addProvides, addRequires, apply, applyAll, applyEvent, applyInferences, applyStepResult, batch, buildInferencePrompt, checkCircuitBreaker, computeAvailableOutputs, computeStepInput, createCliAdapter, createDefaultTaskState, createStepMachine as createEngine, createHttpAdapter, createInitialExecutionState, createInitialState, createLiveGraph, createStepMachine, detectStuckState, disableNode, drainTokens, enableNode, exportGraphConfig, exportGraphConfigToFile, extractReturnData, flowToMermaid, getAllTasks, getCandidateTasks, getDownstream, getNode, getProvides, getRequires, getTask, getUnreachableNodes, getUnreachableTokens, getUpstream, graphToMermaid, hasTask, inferAndApply, inferCompletions, injectTokens, inspect, isExecutionComplete, isNonActiveTask, isRepeatableTask, isTaskCompleted, isTaskRunning, loadGraphConfig, loadStepFlow, next, planExecution, removeNode, removeProvides, removeRequires, resetNode, resolveConfigTemplates, resolveVariables, restore, schedule, snapshot, validateGraph, validateGraphConfig, validateLiveCardSchema, validateStepFlowConfig };
11444
+ // src/continuous-event-graph/live-cards-bridge.ts
11445
+ function liveCardsToReactiveGraph(input, options = {}) {
11446
+ let cards;
11447
+ let boardSettings = {};
11448
+ let boardId;
11449
+ if (!Array.isArray(input) && "nodes" in input) {
11450
+ const board = input;
11451
+ cards = board.nodes;
11452
+ boardId = board.id;
11453
+ boardSettings = board.settings ?? {};
11454
+ } else {
11455
+ cards = input;
11456
+ }
11457
+ const {
11458
+ sourceHandlers = {},
11459
+ defaultSourceHandler,
11460
+ cardHandlers = {},
11461
+ reactiveOptions = {},
11462
+ graphSettings = {},
11463
+ executionId
11464
+ } = options;
11465
+ const cardMap = /* @__PURE__ */ new Map();
11466
+ for (const card of cards) {
11467
+ if (cardMap.has(card.id)) {
11468
+ throw new Error(`Duplicate card ID: "${card.id}"`);
11469
+ }
11470
+ cardMap.set(card.id, card);
11471
+ }
11472
+ const sharedState = options.sharedState ?? /* @__PURE__ */ new Map();
11473
+ const tasks = {};
11474
+ for (const card of cards) {
11475
+ const requires = card.data?.requires ?? [];
11476
+ for (const req of requires) {
11477
+ if (!cardMap.has(req)) {
11478
+ throw new Error(`Card "${card.id}" requires "${req}" but no card with that ID exists`);
11479
+ }
11480
+ }
11481
+ tasks[card.id] = {
11482
+ requires: requires.length > 0 ? requires : void 0,
11483
+ provides: [card.id],
11484
+ taskHandlers: [card.id],
11485
+ // each card has a named handler matching its ID
11486
+ description: card.meta?.title ?? `${card.type}: ${card.id}`
11487
+ };
11488
+ }
11489
+ const config = {
11490
+ id: boardId ?? `live-cards-${Date.now()}`,
11491
+ settings: {
11492
+ completion: "manual",
11493
+ execution_mode: "eligibility-mode",
11494
+ ...boardSettings,
11495
+ ...graphSettings
11496
+ },
11497
+ tasks
11498
+ };
11499
+ const handlers = {};
11500
+ let graphRef = null;
11501
+ const getResolve = () => (token, data, errors) => {
11502
+ graphRef.resolveCallback(token, data, errors);
11503
+ };
11504
+ for (const card of cards) {
11505
+ if (card.type === "source") {
11506
+ handlers[card.id] = buildSourceHandler(card, sourceHandlers, defaultSourceHandler, sharedState, getResolve);
11507
+ } else {
11508
+ handlers[card.id] = buildCardHandler(card, cardHandlers, sharedState, cardMap, getResolve);
11509
+ }
11510
+ }
11511
+ const graph = createReactiveGraph(
11512
+ config,
11513
+ {
11514
+ ...reactiveOptions,
11515
+ handlers
11516
+ },
11517
+ executionId
11518
+ );
11519
+ graphRef = graph;
11520
+ return { graph, config, handlers, cards: cardMap, sharedState };
11521
+ }
11522
+ function buildSourceHandler(card, sourceHandlers, defaultSourceHandler, sharedState, getResolve) {
11523
+ if (sourceHandlers[card.id]) {
11524
+ const userHandler = sourceHandlers[card.id];
11525
+ return async (input) => {
11526
+ return userHandler(input);
11527
+ };
11528
+ }
11529
+ if (defaultSourceHandler) {
11530
+ const factoryHandler = defaultSourceHandler(card);
11531
+ return async (input) => {
11532
+ return factoryHandler(input);
11533
+ };
11534
+ }
11535
+ return async (input) => {
11536
+ const state = { ...card.state };
11537
+ sharedState.set(card.id, state);
11538
+ getResolve()(input.callbackToken, state);
11539
+ return "task-initiated";
11540
+ };
11541
+ }
11542
+ function buildCardHandler(card, cardHandlers, sharedState, cardMap, getResolve) {
11543
+ if (cardHandlers[card.id]) {
11544
+ const userHandler = cardHandlers[card.id];
11545
+ return async (input) => {
11546
+ return userHandler(input);
11547
+ };
11548
+ }
11549
+ return async (input) => {
11550
+ const computeNode = {
11551
+ id: card.id,
11552
+ state: { ...card.state },
11553
+ compute: card.compute
11554
+ };
11555
+ const requires = card.data?.requires ?? [];
11556
+ for (const upstreamId of requires) {
11557
+ const upstreamState = sharedState.get(upstreamId);
11558
+ if (upstreamState) {
11559
+ computeNode.state[upstreamId] = upstreamState;
11560
+ }
11561
+ const upstreamCard = cardMap.get(upstreamId);
11562
+ if (upstreamCard?.data?.provides && upstreamState) {
11563
+ for (const [key, bindRef] of Object.entries(upstreamCard.data.provides)) {
11564
+ if (typeof bindRef === "string" && bindRef.startsWith("state.")) {
11565
+ const path = bindRef.slice(6);
11566
+ const value = deepGet2(upstreamState, path);
11567
+ if (value !== void 0) {
11568
+ computeNode.state[key] = value;
11569
+ }
11570
+ }
11571
+ }
11572
+ }
11573
+ }
11574
+ CardCompute.run(computeNode);
11575
+ const resultState = { ...computeNode.state };
11576
+ sharedState.set(card.id, resultState);
11577
+ getResolve()(input.callbackToken, resultState);
11578
+ return "task-initiated";
11579
+ };
11580
+ }
11581
+ function deepGet2(obj, path) {
11582
+ if (!path || !obj) return void 0;
11583
+ const parts = path.split(".");
11584
+ let cur = obj;
11585
+ for (const part of parts) {
11586
+ if (cur == null) return void 0;
11587
+ cur = cur[part];
11588
+ }
11589
+ return cur;
11590
+ }
11591
+
11592
+ // src/inference/core.ts
11593
+ var DEFAULT_THRESHOLD = 0.5;
11594
+ var DEFAULT_SYSTEM_PROMPT = `You are a workflow completion analyzer. Given a graph of tasks with their current states, evidence, and inference hints, determine which tasks appear to be completed based on the available evidence.
11595
+
11596
+ For each task you analyze, provide a JSON response. Be conservative \u2014 only mark tasks as completed when the evidence strongly supports it.`;
11597
+ function buildInferencePrompt(live, options = {}) {
11598
+ const { scope, context, systemPrompt } = options;
11599
+ const graphTasks = getAllTasks(live.config);
11600
+ const { state } = live;
11601
+ const candidates = getAnalyzableCandidates(live, scope);
11602
+ if (candidates.length === 0) {
11603
+ return "";
11604
+ }
11605
+ const lines = [];
11606
+ lines.push(systemPrompt || DEFAULT_SYSTEM_PROMPT);
11607
+ lines.push("");
11608
+ lines.push("## Graph State");
11609
+ lines.push("");
11610
+ lines.push(`Available tokens: ${state.availableOutputs.length > 0 ? state.availableOutputs.join(", ") : "(none)"}`);
11611
+ lines.push("");
11612
+ const completedTasks = Object.entries(state.tasks).filter(([_, ts]) => ts.status === "completed").map(([name]) => name);
11613
+ if (completedTasks.length > 0) {
11614
+ lines.push(`Completed tasks: ${completedTasks.join(", ")}`);
11615
+ lines.push("");
11616
+ }
11617
+ lines.push("## Tasks to Analyze");
11618
+ lines.push("");
11619
+ for (const taskName of candidates) {
11620
+ const taskConfig = graphTasks[taskName];
11621
+ const taskState = state.tasks[taskName];
11622
+ lines.push(`### ${taskName}`);
11623
+ if (taskConfig.description) {
11624
+ lines.push(`Description: ${taskConfig.description}`);
11625
+ }
11626
+ const requires = getRequires(taskConfig);
11627
+ const provides = getProvides(taskConfig);
11628
+ if (requires.length > 0) lines.push(`Requires: ${requires.join(", ")}`);
11629
+ if (provides.length > 0) lines.push(`Provides: ${provides.join(", ")}`);
11630
+ lines.push(`Current status: ${taskState?.status || "not-started"}`);
11631
+ const hints = taskConfig.inference;
11632
+ if (hints) {
11633
+ if (hints.criteria) lines.push(`Completion criteria: ${hints.criteria}`);
11634
+ if (hints.keywords?.length) lines.push(`Keywords: ${hints.keywords.join(", ")}`);
11635
+ if (hints.suggestedChecks?.length) lines.push(`Suggested checks: ${hints.suggestedChecks.join("; ")}`);
11636
+ }
11637
+ lines.push("");
11638
+ }
11639
+ if (context) {
11640
+ lines.push("## Additional Context / Evidence");
11641
+ lines.push("");
11642
+ lines.push(context);
11643
+ lines.push("");
11644
+ }
11645
+ lines.push("## Response Format");
11646
+ lines.push("");
11647
+ lines.push("Respond with a JSON array of objects, one per task you have evidence for:");
11648
+ lines.push("```json");
11649
+ lines.push("[");
11650
+ lines.push(" {");
11651
+ lines.push(' "taskName": "task-name",');
11652
+ lines.push(' "confidence": 0.0 to 1.0,');
11653
+ lines.push(' "reasoning": "explanation of why you believe this task is complete or not"');
11654
+ lines.push(" }");
11655
+ lines.push("]");
11656
+ lines.push("```");
11657
+ lines.push("");
11658
+ lines.push("Rules:");
11659
+ lines.push('- Only include tasks from the "Tasks to Analyze" section');
11660
+ lines.push("- confidence 0.0 = no evidence of completion, 1.0 = certain it is complete");
11661
+ lines.push("- If you have no evidence for a task, omit it from the array");
11662
+ lines.push("- Be conservative \u2014 require clear evidence before high confidence");
11663
+ lines.push("- Respond ONLY with the JSON array, no additional text");
11664
+ return lines.join("\n");
11665
+ }
11666
+ async function inferCompletions(live, adapter, options = {}) {
11667
+ options.threshold ?? DEFAULT_THRESHOLD;
11668
+ const analyzedNodes = getAnalyzableCandidates(live, options.scope);
11669
+ if (analyzedNodes.length === 0) {
11670
+ return { suggestions: [], promptUsed: "", rawResponse: "", analyzedNodes: [] };
11671
+ }
11672
+ const prompt = buildInferencePrompt(live, options);
11673
+ const rawResponse = await adapter.analyze(prompt);
11674
+ const suggestions = parseInferenceResponse(rawResponse, analyzedNodes);
11675
+ return {
11676
+ suggestions,
11677
+ promptUsed: prompt,
11678
+ rawResponse,
11679
+ analyzedNodes
11680
+ };
11681
+ }
11682
+ function applyInferences(live, result, threshold = DEFAULT_THRESHOLD) {
11683
+ let current = live;
11684
+ for (const suggestion of result.suggestions) {
11685
+ if (suggestion.confidence < threshold) continue;
11686
+ const taskState = current.state.tasks[suggestion.taskName];
11687
+ if (!taskState) continue;
11688
+ if (taskState.status === "completed" || taskState.status === "running") continue;
11689
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11690
+ current = applyEvent(current, {
11691
+ type: "task-started",
11692
+ taskName: suggestion.taskName,
11693
+ timestamp: now
11694
+ });
11695
+ current = applyEvent(current, {
11696
+ type: "task-completed",
11697
+ taskName: suggestion.taskName,
11698
+ timestamp: now,
11699
+ result: "llm-inferred"
11700
+ });
11701
+ }
11702
+ return current;
11703
+ }
11704
+ async function inferAndApply(live, adapter, options = {}) {
11705
+ const threshold = options.threshold ?? DEFAULT_THRESHOLD;
11706
+ const inference = await inferCompletions(live, adapter, options);
11707
+ const updated = applyInferences(live, inference, threshold);
11708
+ const applied = inference.suggestions.filter((s) => s.confidence >= threshold);
11709
+ const skipped = inference.suggestions.filter((s) => s.confidence < threshold);
11710
+ return {
11711
+ live: updated,
11712
+ inference,
11713
+ applied,
11714
+ skipped
11715
+ };
11716
+ }
11717
+ function getAnalyzableCandidates(live, scope) {
11718
+ const graphTasks = getAllTasks(live.config);
11719
+ const { state } = live;
11720
+ const candidates = [];
11721
+ for (const [name, config] of Object.entries(graphTasks)) {
11722
+ const taskState = state.tasks[name];
11723
+ if (taskState?.status === "completed" || taskState?.status === "running") continue;
11724
+ if (scope) {
11725
+ if (scope.includes(name)) candidates.push(name);
11726
+ } else {
11727
+ if (config.inference?.autoDetectable) candidates.push(name);
11728
+ }
11729
+ }
11730
+ return candidates;
11731
+ }
11732
+ function parseInferenceResponse(rawResponse, validNodes, _threshold) {
11733
+ const validSet = new Set(validNodes);
11734
+ try {
11735
+ const jsonStr = extractJson(rawResponse);
11736
+ if (!jsonStr) return [];
11737
+ const parsed = JSON.parse(jsonStr);
11738
+ if (!Array.isArray(parsed)) return [];
11739
+ const suggestions = [];
11740
+ for (const item of parsed) {
11741
+ if (!item || typeof item !== "object") continue;
11742
+ if (typeof item.taskName !== "string") continue;
11743
+ if (typeof item.confidence !== "number") continue;
11744
+ if (!validSet.has(item.taskName)) continue;
11745
+ const confidence = Math.max(0, Math.min(1, item.confidence));
11746
+ suggestions.push({
11747
+ taskName: item.taskName,
11748
+ confidence,
11749
+ reasoning: typeof item.reasoning === "string" ? item.reasoning : "",
11750
+ detectionMethod: "llm-inferred"
11751
+ });
11752
+ }
11753
+ return suggestions;
11754
+ } catch {
11755
+ return [];
11756
+ }
11757
+ }
11758
+ function extractJson(text) {
11759
+ if (!text || typeof text !== "string") return null;
11760
+ const trimmed = text.trim();
11761
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
11762
+ if (fenceMatch) return fenceMatch[1].trim();
11763
+ const firstBracket = trimmed.indexOf("[");
11764
+ const lastBracket = trimmed.lastIndexOf("]");
11765
+ if (firstBracket !== -1 && lastBracket > firstBracket) {
11766
+ return trimmed.slice(firstBracket, lastBracket + 1);
11767
+ }
11768
+ if (trimmed.startsWith("[")) return trimmed;
11769
+ return null;
11770
+ }
11771
+ function createCliAdapter(opts) {
11772
+ const timeout = opts.timeout ?? 6e4;
11773
+ return {
11774
+ analyze: (prompt) => {
11775
+ return new Promise((resolve2, reject) => {
11776
+ const args = opts.args(prompt);
11777
+ const child = execFile(
11778
+ opts.command,
11779
+ opts.stdin ? opts.args("") : args,
11780
+ {
11781
+ timeout,
11782
+ cwd: opts.cwd,
11783
+ env: opts.env ? { ...process.env, ...opts.env } : void 0,
11784
+ maxBuffer: 10 * 1024 * 1024
11785
+ // 10MB
11786
+ },
11787
+ (error, stdout, stderr) => {
11788
+ if (error) {
11789
+ reject(new Error(
11790
+ `CLI adapter failed: ${opts.command} exited with ${error.code ?? "error"}` + (stderr ? `
11791
+ stderr: ${stderr.slice(0, 500)}` : "") + `
11792
+ ${error.message}`
11793
+ ));
11794
+ } else {
11795
+ resolve2(stdout);
11796
+ }
11797
+ }
11798
+ );
11799
+ if (opts.stdin && child.stdin) {
11800
+ child.stdin.write(prompt);
11801
+ child.stdin.end();
11802
+ }
11803
+ });
11804
+ }
11805
+ };
11806
+ }
11807
+ function createHttpAdapter(opts) {
11808
+ const timeout = opts.timeout ?? 6e4;
11809
+ return {
11810
+ analyze: async (prompt) => {
11811
+ const body = opts.buildBody ? opts.buildBody(prompt) : { prompt };
11812
+ const controller = new AbortController();
11813
+ const timer = setTimeout(() => controller.abort(), timeout);
11814
+ try {
11815
+ const response = await fetch(opts.url, {
11816
+ method: "POST",
11817
+ headers: {
11818
+ "Content-Type": "application/json",
11819
+ ...opts.headers ?? {}
11820
+ },
11821
+ body: JSON.stringify(body),
11822
+ signal: controller.signal
11823
+ });
11824
+ if (!response.ok) {
11825
+ const text = await response.text().catch(() => "");
11826
+ throw new Error(`HTTP ${response.status}: ${text.slice(0, 500)}`);
11827
+ }
11828
+ const json = await response.json();
11829
+ if (opts.extractResponse) {
11830
+ return opts.extractResponse(json);
11831
+ }
11832
+ if (typeof json.response === "string") return json.response;
11833
+ if (typeof json.text === "string") return json.text;
11834
+ if (typeof json.content === "string") return json.content;
11835
+ return JSON.stringify(json);
11836
+ } finally {
11837
+ clearTimeout(timer);
11838
+ }
11839
+ }
11840
+ };
11841
+ }
11842
+
11843
+ export { COMPLETION_STRATEGIES, CONFLICT_STRATEGIES, CardCompute, DEFAULTS, EXECUTION_MODES, EXECUTION_STATUS, FileJournal, FileStore, StepMachine as FlowEngine, LocalStorageStore, MemoryJournal, MemoryStore, StepMachine, TASK_STATUS, addDynamicTask, addNode, addProvides, addRequires, apply, applyAll, applyEvent, applyEvents, applyInferences, applyStepResult, batch, buildInferencePrompt, checkCircuitBreaker, computeAvailableOutputs, computeStepInput, createCallbackHandler, createCliAdapter, createDefaultGraphEngineStore, createStepMachine as createEngine, createFireAndForgetHandler, createHttpAdapter, createInitialExecutionState, createInitialState, createLiveGraph, createNoopHandler, createReactiveGraph, createScriptHandler, createShellHandler, createStepMachine, createWebhookHandler, detectStuckState, disableNode, drainTokens, enableNode, exportGraphConfig, exportGraphConfigToFile, extractReturnData, flowToMermaid, getAllTasks, getCandidateTasks, getDownstream, getMaxExecutions, getNode, getProvides, getRefreshStrategy, getRequires, getTask, getUnreachableNodes, getUnreachableTokens, getUpstream, graphToMermaid, hasTask, inferAndApply, inferCompletions, injectTokens, inspect, isExecutionComplete, isNonActiveTask, isRerunnable, isTaskCompleted, isTaskRunning, liveCardsToReactiveGraph, loadGraphConfig, loadStepFlow, mutateGraph, next, planExecution, removeNode, removeProvides, removeRequires, resetNode, resolveConfigTemplates, resolveVariables, restore, schedule, snapshot, validateFlowSchema, validateGraph, validateGraphConfig, validateGraphSchema, validateLiveCardSchema, validateLiveGraph, validateReactiveGraph, validateStepFlowConfig };
10476
11844
  //# sourceMappingURL=index.js.map
10477
11845
  //# sourceMappingURL=index.js.map