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