yaml-flow 5.1.0 → 5.2.1

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 (105) hide show
  1. package/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +42 -20
  2. package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
  3. package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
  4. package/browser/board-livegraph-engine.js.map +1 -0
  5. package/browser/live-cards.js +362 -38
  6. package/browser/live-cards.schema.json +20 -4
  7. package/dist/board-livegraph-runtime/index.cjs +210 -14
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.d.cts +49 -5
  10. package/dist/board-livegraph-runtime/index.d.ts +49 -5
  11. package/dist/board-livegraph-runtime/index.js +209 -15
  12. package/dist/board-livegraph-runtime/index.js.map +1 -1
  13. package/dist/card-compute/index.cjs +63 -7
  14. package/dist/card-compute/index.cjs.map +1 -1
  15. package/dist/card-compute/index.d.cts +2 -2
  16. package/dist/card-compute/index.d.ts +2 -2
  17. package/dist/card-compute/index.js +63 -7
  18. package/dist/card-compute/index.js.map +1 -1
  19. package/dist/cli/board-live-cards-cli.cjs +664 -75
  20. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  21. package/dist/cli/board-live-cards-cli.d.cts +33 -5
  22. package/dist/cli/board-live-cards-cli.d.ts +33 -5
  23. package/dist/cli/board-live-cards-cli.js +661 -76
  24. package/dist/cli/board-live-cards-cli.js.map +1 -1
  25. package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
  26. package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
  27. package/dist/continuous-event-graph/index.cjs +47 -14
  28. package/dist/continuous-event-graph/index.cjs.map +1 -1
  29. package/dist/continuous-event-graph/index.d.cts +9 -9
  30. package/dist/continuous-event-graph/index.d.ts +9 -9
  31. package/dist/continuous-event-graph/index.js +47 -14
  32. package/dist/continuous-event-graph/index.js.map +1 -1
  33. package/dist/event-graph/index.cjs +29 -12
  34. package/dist/event-graph/index.cjs.map +1 -1
  35. package/dist/event-graph/index.d.cts +5 -5
  36. package/dist/event-graph/index.d.ts +5 -5
  37. package/dist/event-graph/index.js +29 -12
  38. package/dist/event-graph/index.js.map +1 -1
  39. package/dist/index.cjs +93 -20
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +7 -7
  42. package/dist/index.d.ts +7 -7
  43. package/dist/index.js +93 -20
  44. package/dist/index.js.map +1 -1
  45. package/dist/inference/index.cjs +29 -12
  46. package/dist/inference/index.cjs.map +1 -1
  47. package/dist/inference/index.d.cts +2 -2
  48. package/dist/inference/index.d.ts +2 -2
  49. package/dist/inference/index.js +29 -12
  50. package/dist/inference/index.js.map +1 -1
  51. package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
  52. package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
  53. package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
  54. package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
  55. package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
  56. package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
  57. package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
  58. package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
  59. package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
  60. package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
  61. package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
  62. package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
  63. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
  64. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
  65. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
  66. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
  67. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  68. package/examples/example-board/agent-instructions.md +603 -0
  69. package/examples/example-board/cards/card-concentration.json +42 -0
  70. package/examples/example-board/cards/card-market-prices.json +51 -0
  71. package/examples/example-board/cards/card-portfolio-action.json +19 -0
  72. package/examples/example-board/cards/card-portfolio-risks.json +19 -0
  73. package/examples/example-board/cards/card-portfolio-value.json +62 -0
  74. package/examples/example-board/cards/card-portfolio.json +44 -0
  75. package/examples/example-board/demo-chat-handler.js +373 -33
  76. package/examples/example-board/demo-server-config.json +0 -2
  77. package/examples/example-board/demo-server.js +46 -7
  78. package/examples/example-board/demo-shell-browser.html +75 -207
  79. package/examples/example-board/demo-shell-with-server.html +15 -9
  80. package/examples/example-board/demo-shell.html +1 -1
  81. package/examples/example-board/demo-task-executor.js +259 -41
  82. package/package.json +6 -2
  83. package/schema/live-cards.schema.json +20 -4
  84. package/browser/board-livegraph-runtime.js.map +0 -1
  85. package/examples/example-board/board.yaml +0 -23
  86. package/examples/example-board/bootstrap_payload.json +0 -1
  87. package/examples/example-board/cards/card-chain-region-alert.json +0 -39
  88. package/examples/example-board/cards/card-chain-region-totals.json +0 -26
  89. package/examples/example-board/cards/card-chain-top-region.json +0 -24
  90. package/examples/example-board/cards/card-ex-actions.json +0 -32
  91. package/examples/example-board/cards/card-ex-chart.json +0 -30
  92. package/examples/example-board/cards/card-ex-filter.json +0 -36
  93. package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
  94. package/examples/example-board/cards/card-ex-form.json +0 -91
  95. package/examples/example-board/cards/card-ex-list.json +0 -22
  96. package/examples/example-board/cards/card-ex-markdown.json +0 -17
  97. package/examples/example-board/cards/card-ex-metric.json +0 -19
  98. package/examples/example-board/cards/card-ex-narrative.json +0 -36
  99. package/examples/example-board/cards/card-ex-source-http.json +0 -28
  100. package/examples/example-board/cards/card-ex-source.json +0 -21
  101. package/examples/example-board/cards/card-ex-status.json +0 -35
  102. package/examples/example-board/cards/card-ex-table.json +0 -30
  103. package/examples/example-board/cards/card-ex-todo.json +0 -29
  104. package/examples/example-board/mock.db +0 -15
  105. package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
@@ -73,15 +73,30 @@ function groupTasksByProvides(candidateTaskNames, tasks) {
73
73
  }
74
74
 
75
75
  // src/event-graph/task-transitions.ts
76
- function applyTaskStart(state, taskName) {
76
+ function applyTaskStart(state, taskName, graph) {
77
77
  const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
78
+ const startConsumedHashes = {};
79
+ if (graph) {
80
+ const taskConfig = graph.tasks[taskName];
81
+ const requires = getRequires(taskConfig);
82
+ for (const token of requires) {
83
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
84
+ if (getProvides(otherConfig).includes(token)) {
85
+ const otherState = state.tasks[otherName];
86
+ if (otherState?.lastDataHash) startConsumedHashes[token] = otherState.lastDataHash;
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }
78
92
  const updatedTask = {
79
93
  ...existingTask,
80
94
  status: "running",
81
95
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
82
96
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
83
97
  progress: 0,
84
- error: void 0
98
+ error: void 0,
99
+ startConsumedHashes
85
100
  };
86
101
  return {
87
102
  ...state,
@@ -101,16 +116,18 @@ function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
101
116
  } else {
102
117
  outputTokens = getProvides(taskConfig);
103
118
  }
104
- const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
105
- const requires = taskConfig.requires ?? [];
106
- for (const token of requires) {
107
- for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
108
- if (getProvides(otherConfig).includes(token)) {
109
- const otherState = state.tasks[otherName];
110
- if (otherState?.lastDataHash) {
111
- lastConsumedHashes[token] = otherState.lastDataHash;
119
+ const lastConsumedHashes = existingTask.startConsumedHashes ? { ...existingTask.startConsumedHashes } : { ...existingTask.lastConsumedHashes };
120
+ if (!existingTask.startConsumedHashes) {
121
+ const requires = taskConfig.requires ?? [];
122
+ for (const token of requires) {
123
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
124
+ if (getProvides(otherConfig).includes(token)) {
125
+ const otherState = state.tasks[otherName];
126
+ if (otherState?.lastDataHash) {
127
+ lastConsumedHashes[token] = otherState.lastDataHash;
128
+ }
129
+ break;
112
130
  }
113
- break;
114
131
  }
115
132
  }
116
133
  }
@@ -255,7 +272,7 @@ function applyEvent(live, event) {
255
272
  switch (event.type) {
256
273
  // --- Execution state transitions ---
257
274
  case "task-started":
258
- return { config, state: applyTaskStart(state, event.taskName) };
275
+ return { config, state: applyTaskStart(state, event.taskName, config) };
259
276
  case "task-completed":
260
277
  return { config, state: applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data) };
261
278
  case "task-failed":
@@ -1083,13 +1100,29 @@ function validateNode(node) {
1083
1100
  if (!Array.isArray(n.sources)) {
1084
1101
  errors.push("sources: must be an array");
1085
1102
  } else {
1103
+ const bindTos = /* @__PURE__ */ new Set();
1104
+ const outputFiles = /* @__PURE__ */ new Set();
1086
1105
  n.sources.forEach((src, i) => {
1087
1106
  if (!src || typeof src !== "object" || Array.isArray(src)) {
1088
1107
  errors.push(`sources[${i}]: must be an object`);
1089
1108
  } else {
1090
1109
  const s = src;
1091
- if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`sources[${i}]: missing required "bindTo" property`);
1092
- if (s.outputFile != null && typeof s.outputFile !== "string") errors.push(`sources[${i}]: outputFile must be a string`);
1110
+ if (typeof s.bindTo !== "string" || !s.bindTo) {
1111
+ errors.push(`sources[${i}]: missing required "bindTo" property`);
1112
+ } else {
1113
+ if (bindTos.has(s.bindTo)) {
1114
+ errors.push(`sources[${i}]: bindTo "${s.bindTo}" is not unique across sources`);
1115
+ }
1116
+ bindTos.add(s.bindTo);
1117
+ }
1118
+ if (typeof s.outputFile !== "string" || !s.outputFile) {
1119
+ errors.push(`sources[${i}]: missing required "outputFile" property`);
1120
+ } else {
1121
+ if (outputFiles.has(s.outputFile)) {
1122
+ errors.push(`sources[${i}]: outputFile "${s.outputFile}" is not unique across sources`);
1123
+ }
1124
+ outputFiles.add(s.outputFile);
1125
+ }
1093
1126
  if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
1094
1127
  errors.push(`sources[${i}]: optionalForCompletionGating must be a boolean`);
1095
1128
  }
@@ -1147,12 +1180,15 @@ var CardCompute = {
1147
1180
  var BOARD_FILE = "board-graph.json";
1148
1181
  var JOURNAL_FILE = "board-journal.jsonl";
1149
1182
  var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
1183
+ var INFERENCE_ADAPTER_LOG_FILE = "inference-adapter.jsonl";
1150
1184
  var INVENTORY_FILE = "cards-inventory.jsonl";
1151
1185
  var RUNTIME_OUT_FILE = ".runtime-out";
1152
1186
  var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
1153
1187
  var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
1154
1188
  var RUNTIME_CARDS_DIR = "cards";
1155
1189
  var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
1190
+ var INFERENCE_ADAPTER_FILE = ".inference-adapter";
1191
+ var DEFAULT_TASK_COMPLETION_RULE = "all_required_sources_fetched";
1156
1192
  var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
1157
1193
  var BoardJournal = class {
1158
1194
  journalPath;
@@ -1360,6 +1396,39 @@ function decodeSourceToken(token) {
1360
1396
  return null;
1361
1397
  }
1362
1398
  }
1399
+ function markRequested(entry, requestedAt) {
1400
+ entry.lastRequestedAt = requestedAt;
1401
+ }
1402
+ function markFetchFailed(entry, reason) {
1403
+ entry.lastError = reason;
1404
+ delete entry.lastFetchedAt;
1405
+ }
1406
+ function markFetchCompleted(entry, fetchedAt) {
1407
+ entry.lastFetchedAt = fetchedAt;
1408
+ delete entry.lastError;
1409
+ }
1410
+ function isSourceInFlight(entry) {
1411
+ if (!entry?.lastRequestedAt) return false;
1412
+ return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;
1413
+ }
1414
+ function decideSourceAction(entry, queueRequestedAt) {
1415
+ if (!entry?.lastRequestedAt) return "dispatch";
1416
+ const inFlight = isSourceInFlight(entry);
1417
+ if (inFlight) return "in-flight";
1418
+ if (!entry.lastFetchedAt) return "dispatch";
1419
+ if (entry.lastFetchedAt < queueRequestedAt) return "dispatch";
1420
+ return "idle";
1421
+ }
1422
+ function nextEntryAfterFetchDelivery(entry, fetchedAt) {
1423
+ const next = { ...entry };
1424
+ markFetchCompleted(next, fetchedAt);
1425
+ return next;
1426
+ }
1427
+ function nextEntryAfterFetchFailure(entry, reason) {
1428
+ const next = { ...entry };
1429
+ markFetchFailed(next, reason);
1430
+ return next;
1431
+ }
1363
1432
  function runtimePath(boardDir, cardId) {
1364
1433
  return path.join(boardDir, `${cardId}.runtime.json`);
1365
1434
  }
@@ -1514,6 +1583,15 @@ function splitCommandLine(command) {
1514
1583
  if (current) tokens.push(current);
1515
1584
  return tokens;
1516
1585
  }
1586
+ function resolveCommandInvocation(rawCmd, rawArgs) {
1587
+ if (/^(node|node\.exe)$/i.test(rawCmd)) {
1588
+ return { cmd: process.execPath, args: rawArgs };
1589
+ }
1590
+ if (/\.m?js$/i.test(rawCmd)) {
1591
+ return { cmd: process.execPath, args: [rawCmd, ...rawArgs] };
1592
+ }
1593
+ return { cmd: rawCmd, args: rawArgs };
1594
+ }
1517
1595
  function spawnDetachedProcessAccumulatedWorker(boardDir) {
1518
1596
  const { cmd, args: cliArgs } = getCliInvocation("process-accumulated-events", ["--rg", boardDir, "--inline-loop"]);
1519
1597
  try {
@@ -1589,7 +1667,18 @@ function getCliInvocation(command, args) {
1589
1667
  return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
1590
1668
  }
1591
1669
  function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
1592
- const { cmd, args } = getCliInvocation("run-sources-internal", ["--card", cardPath, "--token", callbackToken, "--rg", boardDir]);
1670
+ const args = ["--card", cardPath, "--token", callbackToken, "--rg", boardDir];
1671
+ const { cmd, args: cmdArgs } = getCliInvocation("run-sources-internal", args);
1672
+ try {
1673
+ spawnDetachedCommand(cmd, cmdArgs);
1674
+ callback(null);
1675
+ } catch (err) {
1676
+ callback(err instanceof Error ? err : new Error(String(err)));
1677
+ }
1678
+ }
1679
+ function invokeRunInference(boardDir, cardId, inputFile, callbackToken, checksum, callback) {
1680
+ const inferenceToken = encodeSourceToken({ cbk: callbackToken, rg: boardDir, cid: cardId, b: "", d: "", cs: checksum });
1681
+ const { cmd, args } = getCliInvocation("run-inference-internal", ["--in", inputFile, "--token", inferenceToken]);
1593
1682
  try {
1594
1683
  spawnDetachedCommand(cmd, args);
1595
1684
  callback(null);
@@ -1597,10 +1686,11 @@ function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
1597
1686
  callback(err instanceof Error ? err : new Error(String(err)));
1598
1687
  }
1599
1688
  }
1600
- function appendTaskExecutorLog(boardDir, hydratedSource) {
1689
+ function appendTaskExecutorLog(boardDir, hydratedSource, mode) {
1601
1690
  try {
1602
1691
  const entry = {
1603
1692
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1693
+ mode,
1604
1694
  hydratedSource
1605
1695
  };
1606
1696
  fs.appendFileSync(path.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
@@ -1608,6 +1698,18 @@ function appendTaskExecutorLog(boardDir, hydratedSource) {
1608
1698
  console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
1609
1699
  }
1610
1700
  }
1701
+ function appendInferenceAdapterLog(boardDir, cardId, payload) {
1702
+ try {
1703
+ const entry = {
1704
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1705
+ cardId,
1706
+ payload
1707
+ };
1708
+ fs.appendFileSync(path.join(boardDir, INFERENCE_ADAPTER_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
1709
+ } catch (logErr) {
1710
+ console.error(`[inference-adapter-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
1711
+ }
1712
+ }
1611
1713
  function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
1612
1714
  const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
1613
1715
  execCommandAsync(cmd, args, (err, stdout, stderr) => {
@@ -1636,22 +1738,33 @@ function createBoardReactiveGraph(boardDir) {
1636
1738
  const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
1637
1739
  const runtime = readRuntimeState(boardDir, cardId);
1638
1740
  let runtimeDirty = false;
1741
+ const currentExecutionCount = input.taskState?.executionCount ?? 0;
1742
+ if (typeof runtime._lastExecutionCount === "number" && runtime._lastExecutionCount !== currentExecutionCount) {
1743
+ runtime._sources = {};
1744
+ runtime._inferenceEntry = void 0;
1745
+ }
1746
+ if (runtime._lastExecutionCount !== currentExecutionCount) {
1747
+ runtime._lastExecutionCount = currentExecutionCount;
1748
+ runtimeDirty = true;
1749
+ }
1639
1750
  if (input.update) {
1640
1751
  const u = input.update;
1641
- const bindTo = u.bindTo;
1642
- if (!runtime._sources[bindTo]) runtime._sources[bindTo] = {};
1643
- if (u.failure) {
1644
- runtime._sources[bindTo].lastError = u.reason ?? "unknown";
1645
- delete runtime._sources[bindTo].lastFetchedAt;
1646
- runtimeDirty = true;
1647
- console.log(`[card-handler] source "${bindTo}" fetch failed: ${runtime._sources[bindTo].lastError}`);
1648
- } else {
1649
- runtime._sources[bindTo].lastFetchedAt = u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1650
- delete runtime._sources[bindTo].lastError;
1651
- runtimeDirty = true;
1652
- console.log(`[card-handler] source "${bindTo}" delivered \u2192 ${u.dest}`);
1752
+ const outputFile = u.outputFile;
1753
+ if (outputFile) {
1754
+ if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
1755
+ const entry = runtime._sources[outputFile];
1756
+ if (u.failure) {
1757
+ runtime._sources[outputFile] = nextEntryAfterFetchFailure(entry, u.reason ?? "unknown");
1758
+ runtimeDirty = true;
1759
+ } else {
1760
+ runtime._sources[outputFile] = nextEntryAfterFetchDelivery(
1761
+ entry,
1762
+ u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString()
1763
+ );
1764
+ runtimeDirty = true;
1765
+ }
1766
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1653
1767
  }
1654
- if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1655
1768
  }
1656
1769
  const sourcesData = {};
1657
1770
  for (const src of allSources) {
@@ -1693,40 +1806,57 @@ function createBoardReactiveGraph(boardDir) {
1693
1806
  card_id: cardId,
1694
1807
  computed_values: computeNode.computed_values ?? {}
1695
1808
  });
1809
+ const enrichedCard = { ...card };
1810
+ const enrichedSources = CardCompute.enrichSources(
1811
+ Array.isArray(card.sources) ? card.sources : void 0,
1812
+ {
1813
+ requires,
1814
+ sourcesData,
1815
+ computed_values: computeNode.computed_values
1816
+ }
1817
+ );
1818
+ const sourceCwd = path.dirname(cardPath);
1819
+ enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
1820
+ ...src,
1821
+ cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
1822
+ boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
1823
+ })) : enrichedSources;
1824
+ const enrichedByOutput = /* @__PURE__ */ new Map();
1825
+ for (const src of Array.isArray(enrichedCard.sources) ? enrichedCard.sources : []) {
1826
+ const outputFile = src.outputFile;
1827
+ if (typeof outputFile === "string" && outputFile) {
1828
+ enrichedByOutput.set(outputFile, src);
1829
+ }
1830
+ }
1696
1831
  const now = (/* @__PURE__ */ new Date()).toISOString();
1832
+ const runQueuedAt = input.update ? void 0 : now;
1697
1833
  const undeliveredRequired = requiredSources.filter((s) => {
1698
- if (!s.outputFile) return false;
1699
- const entry = runtime._sources[s.bindTo];
1700
- if (!entry?.lastRequestedAt) return true;
1701
- if (!entry.lastFetchedAt) return true;
1702
- return entry.lastFetchedAt <= entry.lastRequestedAt;
1834
+ const outputFile = s.outputFile;
1835
+ if (typeof outputFile !== "string" || !outputFile) return true;
1836
+ if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
1837
+ const entry = runtime._sources[outputFile];
1838
+ if (runQueuedAt) {
1839
+ entry.queueRequestedAt = runQueuedAt;
1840
+ runtimeDirty = true;
1841
+ }
1842
+ const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;
1843
+ const action = decideSourceAction(entry, qrt);
1844
+ if (action === "in-flight") return false;
1845
+ return action === "dispatch";
1703
1846
  });
1847
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1704
1848
  if (undeliveredRequired.length > 0) {
1705
1849
  let stampedAny = false;
1706
1850
  for (const src of undeliveredRequired) {
1707
- const entry = runtime._sources[src.bindTo] ?? {};
1708
- if (!entry.lastRequestedAt || entry.lastFetchedAt && entry.lastFetchedAt >= entry.lastRequestedAt) {
1709
- entry.lastRequestedAt = now;
1710
- runtime._sources[src.bindTo] = entry;
1711
- stampedAny = true;
1712
- }
1851
+ const outputFile = src.outputFile;
1852
+ if (typeof outputFile !== "string" || !outputFile) continue;
1853
+ const entry = runtime._sources[outputFile] ?? {};
1854
+ markRequested(entry, now);
1855
+ runtime._sources[outputFile] = entry;
1856
+ stampedAny = true;
1713
1857
  }
1714
1858
  if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
1715
- const enrichedCard = { ...card };
1716
- const enrichedSources = CardCompute.enrichSources(
1717
- Array.isArray(card.sources) ? card.sources : void 0,
1718
- {
1719
- requires,
1720
- sourcesData,
1721
- computed_values: computeNode.computed_values
1722
- }
1723
- );
1724
- const sourceCwd = path.dirname(cardPath);
1725
- enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
1726
- ...src,
1727
- cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
1728
- boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
1729
- })) : enrichedSources;
1859
+ if (!stampedAny) return "task-initiated";
1730
1860
  const enrichedCardPath = path.join(os.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
1731
1861
  fs.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
1732
1862
  invokeRunSources(boardDir, enrichedCardPath, input.callbackToken, (err) => {
@@ -1745,10 +1875,79 @@ function createBoardReactiveGraph(boardDir) {
1745
1875
  for (const { bindTo, src } of providesBindings) {
1746
1876
  data[bindTo] = CardCompute.resolve(computeNode, src);
1747
1877
  }
1878
+ const completionRule = typeof card.when_is_task_completed === "string" && card.when_is_task_completed.trim() ? card.when_is_task_completed.trim() : DEFAULT_TASK_COMPLETION_RULE;
1879
+ const cardData = card.card_data;
1880
+ const llmCompletion = cardData?.llm_task_completion_inference ?? {};
1881
+ const isLlmTaskCompleted = llmCompletion.isTaskCompleted === true;
1882
+ const inferenceEntry = runtime._inferenceEntry ?? {};
1883
+ const inferenceRequestedAt = typeof inferenceEntry.lastRequestedAt === "string" ? inferenceEntry.lastRequestedAt : void 0;
1884
+ const inferenceCompletedAt = typeof llmCompletion.inferenceCompletedAt === "string" ? llmCompletion.inferenceCompletedAt : void 0;
1885
+ const inferencePending = !!inferenceRequestedAt && (!inferenceCompletedAt || inferenceCompletedAt < inferenceRequestedAt);
1886
+ const latestRequiredSourceFetchedAt = requiredSources.reduce((latest, src) => {
1887
+ const fetchedAt = runtime._sources[src.outputFile]?.lastFetchedAt;
1888
+ if (typeof fetchedAt !== "string") return latest;
1889
+ if (!latest || fetchedAt > latest) return fetchedAt;
1890
+ return latest;
1891
+ }, void 0);
1892
+ const shouldRequestInference = !inferenceRequestedAt || !inferenceCompletedAt || !!latestRequiredSourceFetchedAt && latestRequiredSourceFetchedAt > inferenceCompletedAt;
1893
+ if (completionRule !== DEFAULT_TASK_COMPLETION_RULE) {
1894
+ if (isLlmTaskCompleted) ; else if (inferencePending) {
1895
+ return "task-initiated";
1896
+ } else if (!shouldRequestInference) {
1897
+ return "task-initiated";
1898
+ } else {
1899
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
1900
+ const inferencePayload = {
1901
+ cardId,
1902
+ taskName: input.nodeId,
1903
+ completionRule,
1904
+ context: {
1905
+ requires,
1906
+ sourcesData,
1907
+ computed_values: computeNode.computed_values ?? {},
1908
+ provides: data,
1909
+ card_data: computeNode.card_data ?? {}
1910
+ }
1911
+ };
1912
+ if (runQueuedAt) {
1913
+ inferenceEntry.queueRequestedAt = runQueuedAt;
1914
+ runtimeDirty = true;
1915
+ }
1916
+ const inferenceQrt = inferenceEntry.queueRequestedAt ?? inferenceEntry.lastRequestedAt ?? now2;
1917
+ const inferenceAction = decideSourceAction(inferenceEntry, inferenceQrt);
1918
+ if (inferenceAction === "in-flight") {
1919
+ runtime._inferenceEntry = inferenceEntry;
1920
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1921
+ return "task-initiated";
1922
+ }
1923
+ if (inferenceAction === "idle") {
1924
+ return "task-initiated";
1925
+ }
1926
+ const inferenceInFile = path.join(os.tmpdir(), `card-inference-${cardId}-${Date.now()}.json`);
1927
+ fs.writeFileSync(inferenceInFile, JSON.stringify(inferencePayload, null, 2), "utf-8");
1928
+ appendInferenceAdapterLog(boardDir, cardId, inferencePayload);
1929
+ markRequested(inferenceEntry, now2);
1930
+ runtime._inferenceEntry = inferenceEntry;
1931
+ runtimeDirty = true;
1932
+ invokeRunInference(boardDir, cardId, inferenceInFile, input.callbackToken, void 0, (err) => {
1933
+ if (err) {
1934
+ console.error(`[card-handler] ${input.nodeId}:`, err.message);
1935
+ const failedAt = (/* @__PURE__ */ new Date()).toISOString();
1936
+ appendEventToJournal(boardDir, {
1937
+ type: "task-failed",
1938
+ taskName: input.nodeId,
1939
+ error: err.message,
1940
+ timestamp: failedAt
1941
+ });
1942
+ }
1943
+ });
1944
+ return "task-initiated";
1945
+ }
1946
+ }
1748
1947
  writeRuntimeDataObjects(boardDir, data);
1749
1948
  const undeliveredOptional = allSources.filter((s) => {
1750
- if (s.optionalForCompletionGating !== true || !s.outputFile) return false;
1751
- const entry = runtime._sources[s.bindTo];
1949
+ if (s.optionalForCompletionGating !== true) return false;
1950
+ const entry = runtime._sources[s.outputFile];
1752
1951
  if (!entry?.lastRequestedAt) return true;
1753
1952
  if (!entry.lastFetchedAt) return true;
1754
1953
  return entry.lastFetchedAt <= entry.lastRequestedAt;
@@ -1842,16 +2041,18 @@ function cmdAddCards(args) {
1842
2041
  function cmdInit(args) {
1843
2042
  const dir = args[0];
1844
2043
  if (!dir) {
1845
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
2044
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
1846
2045
  }
1847
2046
  const teIdx = args.indexOf("--task-executor");
1848
2047
  const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
1849
2048
  const chIdx = args.indexOf("--chat-handler");
1850
2049
  const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
2050
+ const iaIdx = args.indexOf("--inference-adapter");
2051
+ const inferenceAdapter = iaIdx !== -1 ? args[iaIdx + 1] : void 0;
1851
2052
  const roIdx = args.indexOf("--runtime-out");
1852
2053
  const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
1853
2054
  if (roIdx !== -1 && !runtimeOut) {
1854
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
2055
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
1855
2056
  }
1856
2057
  const result = initBoard(dir);
1857
2058
  if (taskExecutor) {
@@ -1860,6 +2061,9 @@ function cmdInit(args) {
1860
2061
  if (chatHandler) {
1861
2062
  fs.writeFileSync(path.join(dir, ".chat-handler"), chatHandler, "utf-8");
1862
2063
  }
2064
+ if (inferenceAdapter) {
2065
+ fs.writeFileSync(path.join(dir, INFERENCE_ADAPTER_FILE), inferenceAdapter, "utf-8");
2066
+ }
1863
2067
  const runtimeOutDir = configureRuntimeOutDir(dir, runtimeOut);
1864
2068
  const live = loadBoard(dir);
1865
2069
  writeJsonAtomic(resolveStatusSnapshotPath(dir), buildBoardStatusObject(dir, live));
@@ -2095,7 +2299,7 @@ function cmdSourceDataFetched(args) {
2095
2299
  console.error("Invalid source token");
2096
2300
  process.exit(1);
2097
2301
  }
2098
- const { cbk, rg, cid, b, d } = payload;
2302
+ const { cbk, rg, cid, b, d, cs } = payload;
2099
2303
  const destPath = path.join(rg, d);
2100
2304
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
2101
2305
  fs.renameSync(tmpFile, destPath);
@@ -2109,7 +2313,7 @@ function cmdSourceDataFetched(args) {
2109
2313
  appendEventToJournal(rg, {
2110
2314
  type: "task-progress",
2111
2315
  taskName: cbkDecoded.taskName,
2112
- update: { bindTo: b, fetchedAt, dest: d },
2316
+ update: { bindTo: b, outputFile: d, fetchedAt, sourceChecksum: cs },
2113
2317
  timestamp: fetchedAt
2114
2318
  });
2115
2319
  void processAccumulatedEventsInfinitePass(rg);
@@ -2128,7 +2332,7 @@ function cmdSourceDataFetchFailure(args) {
2128
2332
  console.error("Invalid source token");
2129
2333
  process.exit(1);
2130
2334
  }
2131
- const { cbk, rg, cid, b } = payload;
2335
+ const { cbk, rg, cid, b, d, cs } = payload;
2132
2336
  console.log(`[source-data-fetch-failure] ${cid}.${b}: ${reason}`);
2133
2337
  const cbkDecoded = decodeCallbackToken2(cbk);
2134
2338
  if (!cbkDecoded) {
@@ -2139,7 +2343,7 @@ function cmdSourceDataFetchFailure(args) {
2139
2343
  appendEventToJournal(rg, {
2140
2344
  type: "task-progress",
2141
2345
  taskName: cbkDecoded.taskName,
2142
- update: { bindTo: b, failure: true, reason },
2346
+ update: { bindTo: b, outputFile: d, failure: true, reason, sourceChecksum: cs },
2143
2347
  timestamp
2144
2348
  });
2145
2349
  void processAccumulatedEventsInfinitePass(rg);
@@ -2148,11 +2352,14 @@ function cmdRunSources(args) {
2148
2352
  const cardIdx = args.indexOf("--card");
2149
2353
  const tokenIdx = args.indexOf("--token");
2150
2354
  const rgIdx = args.indexOf("--rg");
2355
+ const sourceChecksumsIdx = args.indexOf("--source-checksums");
2151
2356
  const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
2152
2357
  const callbackToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2153
2358
  const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2359
+ const sourceChecksumsJson = sourceChecksumsIdx !== -1 ? args[sourceChecksumsIdx + 1] : void 0;
2360
+ const sourceChecksums = sourceChecksumsJson ? JSON.parse(sourceChecksumsJson) : void 0;
2154
2361
  if (!cardFilePath || !callbackToken || !boardDir) {
2155
- console.error("Usage: board-live-cards run-sources-internal --card <path> --token <token> --rg <dir>");
2362
+ console.error("Usage: board-live-cards run-sources-internal --card <path> --token <token> --rg <dir> [--source-checksums <json>]");
2156
2363
  process.exit(1);
2157
2364
  }
2158
2365
  const card = JSON.parse(fs.readFileSync(cardFilePath, "utf-8"));
@@ -2166,12 +2373,14 @@ function cmdRunSources(args) {
2166
2373
  const executorFile = path.join(boardDir, ".task-executor");
2167
2374
  const taskExecutor = fs.existsSync(executorFile) ? fs.readFileSync(executorFile, "utf-8").trim() : void 0;
2168
2375
  function runSource(src) {
2376
+ const sourceChecksumForInvoke = src.outputFile ? sourceChecksums?.[src.outputFile] : void 0;
2169
2377
  const sourceToken = encodeSourceToken({
2170
2378
  cbk: callbackToken,
2171
2379
  rg: boardDir,
2172
2380
  cid: card.id,
2173
2381
  b: src.bindTo,
2174
- d: src.outputFile ?? ""
2382
+ d: src.outputFile ?? "",
2383
+ cs: sourceChecksumForInvoke
2175
2384
  });
2176
2385
  function reportFailure(reason) {
2177
2386
  invokeSourceDataFetchFailure(sourceToken, reason, (err) => {
@@ -2195,7 +2404,7 @@ function cmdRunSources(args) {
2195
2404
  cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path.dirname(cardFilePath || ""),
2196
2405
  boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
2197
2406
  };
2198
- appendTaskExecutorLog(boardDir, sourceForExecutor);
2407
+ appendTaskExecutorLog(boardDir, sourceForExecutor, "external-task-executor");
2199
2408
  fs.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
2200
2409
  console.log(`[run-sources-internal] task-executor: ${taskExecutor} run-source-fetch --in ${inFile} --out ${outFile2} --err ${errFile}`);
2201
2410
  try {
@@ -2233,6 +2442,12 @@ function cmdRunSources(args) {
2233
2442
  const timeout = src.timeout ?? 12e4;
2234
2443
  const sourceCwd = typeof src.cwd === "string" ? src.cwd : path.dirname(cardFilePath || "");
2235
2444
  const sourceBoardDir = typeof src.boardDir === "string" ? src.boardDir : boardDir;
2445
+ const sourceForBuiltInExecutor = {
2446
+ ...src,
2447
+ cwd: sourceCwd,
2448
+ boardDir: sourceBoardDir
2449
+ };
2450
+ appendTaskExecutorLog(boardDir, sourceForBuiltInExecutor, "built-in-run-source-fetch");
2236
2451
  const cmdParts = splitCommandLine(src.cli);
2237
2452
  if (cmdParts.length === 0) {
2238
2453
  const errMsg = "source.cli command is empty";
@@ -2241,8 +2456,7 @@ function cmdRunSources(args) {
2241
2456
  return;
2242
2457
  }
2243
2458
  const rawCmd = cmdParts[0];
2244
- const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2245
- const cliArgs = cmdParts.slice(1);
2459
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
2246
2460
  let stdout;
2247
2461
  try {
2248
2462
  stdout = execCommandSync(cmd, cliArgs, {
@@ -2269,6 +2483,181 @@ function cmdRunSources(args) {
2269
2483
  runSource(src);
2270
2484
  }
2271
2485
  }
2486
+ function cmdTaskProgress(args) {
2487
+ const rgIdx = args.indexOf("--rg");
2488
+ const tokenIdx = args.indexOf("--token");
2489
+ const updateIdx = args.indexOf("--update");
2490
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2491
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2492
+ const updateJson = updateIdx !== -1 ? args[updateIdx + 1] : "{}";
2493
+ if (!dir || !token) {
2494
+ console.error("Usage: board-live-cards task-progress --rg <dir> --token <token> [--update <json>]");
2495
+ process.exit(1);
2496
+ }
2497
+ const decoded = decodeCallbackToken2(token);
2498
+ if (!decoded) {
2499
+ console.error("Invalid callback token");
2500
+ process.exit(1);
2501
+ }
2502
+ const update = updateJson ? JSON.parse(updateJson) : {};
2503
+ appendEventToJournal(dir, {
2504
+ type: "task-progress",
2505
+ taskName: decoded.taskName,
2506
+ update,
2507
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2508
+ });
2509
+ void processAccumulatedEventsInfinitePass(dir);
2510
+ }
2511
+ function cmdRunInference(args) {
2512
+ const inIdx = args.indexOf("--in");
2513
+ const tokenIdx = args.indexOf("--token");
2514
+ const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
2515
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2516
+ if (!inFile || !inferenceToken) {
2517
+ console.error("Usage: board-live-cards run-inference-internal --in <input.json> --token <inference-token>");
2518
+ process.exit(1);
2519
+ }
2520
+ const decodedToken = decodeSourceToken(inferenceToken);
2521
+ if (!decodedToken) {
2522
+ console.error("Invalid inference token");
2523
+ process.exit(1);
2524
+ }
2525
+ const callbackToken = decodedToken.cbk;
2526
+ const boardDir = decodedToken.rg;
2527
+ const cbkDecoded = decodeCallbackToken2(callbackToken);
2528
+ if (!cbkDecoded) {
2529
+ console.error("Invalid callback token embedded in inference token");
2530
+ process.exit(1);
2531
+ }
2532
+ function spawnInferenceDone(tmpFile) {
2533
+ const { cmd, args: cliArgs } = getCliInvocation("inference-done", ["--tmp", tmpFile, "--token", inferenceToken]);
2534
+ spawnDetachedCommand(cmd, cliArgs);
2535
+ }
2536
+ function spawnInferenceDoneError(reason) {
2537
+ const tmpFile = path.join(os.tmpdir(), `card-inference-err-${Date.now()}.json`);
2538
+ fs.writeFileSync(tmpFile, JSON.stringify({ isTaskCompleted: false, reason }), "utf-8");
2539
+ spawnInferenceDone(tmpFile);
2540
+ }
2541
+ if (!fs.existsSync(inFile)) {
2542
+ spawnInferenceDoneError(`inference input not found: ${inFile}`);
2543
+ return;
2544
+ }
2545
+ const adapterFile = path.join(boardDir, INFERENCE_ADAPTER_FILE);
2546
+ const inferenceAdapter = fs.existsSync(adapterFile) ? fs.readFileSync(adapterFile, "utf-8").trim() : void 0;
2547
+ if (!inferenceAdapter) {
2548
+ spawnInferenceDoneError(`inference adapter is not configured (${INFERENCE_ADAPTER_FILE})`);
2549
+ return;
2550
+ }
2551
+ const outFile = path.join(os.tmpdir(), `card-inference-out-${Date.now()}.json`);
2552
+ const errFile = path.join(os.tmpdir(), `card-inference-err-${Date.now()}.txt`);
2553
+ const adapterParts = splitCommandLine(inferenceAdapter);
2554
+ if (adapterParts.length === 0) {
2555
+ spawnInferenceDoneError("inference adapter command is empty");
2556
+ return;
2557
+ }
2558
+ const adapterRawCmd = adapterParts[0];
2559
+ const adapterRawArgs = adapterParts.slice(1);
2560
+ const { cmd: adapterCmd, args: adapterArgsPrefix } = resolveCommandInvocation(adapterRawCmd, adapterRawArgs);
2561
+ const adapterArgs = [...adapterArgsPrefix, "run-inference", "--in", inFile, "--out", outFile, "--err", errFile];
2562
+ try {
2563
+ execCommandSync(adapterCmd, adapterArgs, {
2564
+ shell: false,
2565
+ timeout: 12e4,
2566
+ cwd: boardDir,
2567
+ env: {
2568
+ ...process.env,
2569
+ BOARD_DIR: boardDir
2570
+ }
2571
+ });
2572
+ } catch (err) {
2573
+ const reason = err.message ?? String(err);
2574
+ spawnInferenceDoneError(reason);
2575
+ return;
2576
+ }
2577
+ if (!fs.existsSync(outFile)) {
2578
+ const errMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "inference adapter produced no output file";
2579
+ spawnInferenceDoneError(errMsg);
2580
+ return;
2581
+ }
2582
+ spawnInferenceDone(outFile);
2583
+ }
2584
+ function cmdInferenceDone(args) {
2585
+ const tmpIdx = args.indexOf("--tmp");
2586
+ const tokenIdx = args.indexOf("--token");
2587
+ const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
2588
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2589
+ if (!tmpFile || !inferenceToken) {
2590
+ console.error("Usage: board-live-cards inference-done --tmp <result.json> --token <inference-token>");
2591
+ process.exit(1);
2592
+ }
2593
+ const decodedToken = decodeSourceToken(inferenceToken);
2594
+ if (!decodedToken) {
2595
+ console.error("Invalid inference token");
2596
+ process.exit(1);
2597
+ }
2598
+ const { cbk: callbackToken, rg: dir, cs: inputChecksum } = decodedToken;
2599
+ const decoded = decodeCallbackToken2(callbackToken);
2600
+ if (!decoded) {
2601
+ console.error("Invalid callback token embedded in inference token");
2602
+ process.exit(1);
2603
+ }
2604
+ const taskName = decoded.taskName;
2605
+ const cardPath = lookupCardPath(dir, taskName);
2606
+ if (!cardPath) {
2607
+ console.error(`Card file for task "${taskName}" not found in inventory`);
2608
+ process.exit(1);
2609
+ }
2610
+ let result = {};
2611
+ if (fs.existsSync(tmpFile)) {
2612
+ try {
2613
+ result = JSON.parse(fs.readFileSync(tmpFile, "utf-8").trim());
2614
+ } catch (err) {
2615
+ result = { isTaskCompleted: false, reason: `failed to parse inference result: ${err instanceof Error ? err.message : String(err)}` };
2616
+ }
2617
+ try {
2618
+ fs.unlinkSync(tmpFile);
2619
+ } catch {
2620
+ }
2621
+ } else {
2622
+ result = { isTaskCompleted: false, reason: `inference result file not found: ${tmpFile}` };
2623
+ }
2624
+ const isTaskCompletedFlag = result.isTaskCompleted === true;
2625
+ const inferenceCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
2626
+ const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
2627
+ if (!card.card_data) card.card_data = {};
2628
+ const cardData = card.card_data;
2629
+ const existingInference = cardData.llm_task_completion_inference && typeof cardData.llm_task_completion_inference === "object" ? cardData.llm_task_completion_inference : {};
2630
+ cardData.llm_task_completion_inference = {
2631
+ ...existingInference,
2632
+ isTaskCompleted: isTaskCompletedFlag,
2633
+ reason: typeof result.reason === "string" ? result.reason : "",
2634
+ evidence: typeof result.evidence === "string" ? result.evidence : "",
2635
+ inferenceCompletedAt
2636
+ };
2637
+ fs.writeFileSync(cardPath, JSON.stringify(card, null, 2), "utf-8");
2638
+ const runtimePath2 = path.join(dir, `${taskName}.runtime.json`);
2639
+ let runtime = { _sources: {} };
2640
+ if (fs.existsSync(runtimePath2)) {
2641
+ try {
2642
+ runtime = JSON.parse(fs.readFileSync(runtimePath2, "utf-8"));
2643
+ } catch {
2644
+ }
2645
+ }
2646
+ const inferenceEntry = runtime._inferenceEntry ?? {};
2647
+ runtime._inferenceEntry = nextEntryAfterFetchDelivery(inferenceEntry, inferenceCompletedAt);
2648
+ fs.writeFileSync(runtimePath2, JSON.stringify(runtime, null, 2), "utf-8");
2649
+ appendEventToJournal(dir, {
2650
+ type: "task-progress",
2651
+ taskName,
2652
+ update: {
2653
+ kind: "inference-done",
2654
+ isTaskCompleted: isTaskCompletedFlag,
2655
+ inputChecksum
2656
+ },
2657
+ timestamp: inferenceCompletedAt
2658
+ });
2659
+ void processAccumulatedEventsInfinitePass(dir);
2660
+ }
2272
2661
  function cmdRunSourceFetch(args) {
2273
2662
  const inIdx = args.indexOf("--in");
2274
2663
  const outIdx = args.indexOf("--out");
@@ -2314,8 +2703,7 @@ function cmdRunSourceFetch(args) {
2314
2703
  process.exit(1);
2315
2704
  }
2316
2705
  const rawCmd = cmdParts[0];
2317
- const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2318
- const cliArgs = cmdParts.slice(1);
2706
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
2319
2707
  let stdout;
2320
2708
  try {
2321
2709
  stdout = execCommandSync(cmd, cliArgs, {
@@ -2547,20 +2935,183 @@ async function cli(argv) {
2547
2935
  return cmdTaskCompleted(rest);
2548
2936
  case "task-failed":
2549
2937
  return cmdTaskFailed(rest);
2938
+ case "task-progress":
2939
+ return cmdTaskProgress(rest);
2550
2940
  case "source-data-fetched":
2551
2941
  return cmdSourceDataFetched(rest);
2552
2942
  case "source-data-fetch-failure":
2553
2943
  return cmdSourceDataFetchFailure(rest);
2554
2944
  case "run-sources-internal":
2555
2945
  return cmdRunSources(rest);
2946
+ case "run-inference-internal":
2947
+ return cmdRunInference(rest);
2948
+ case "inference-done":
2949
+ return cmdInferenceDone(rest);
2556
2950
  case "run-source-fetch":
2557
2951
  return cmdRunSourceFetch(rest);
2952
+ case "probe-source":
2953
+ return await cmdProbeSource(rest);
2558
2954
  case "process-accumulated-events":
2559
2955
  return await cmdTryDrain(rest);
2560
2956
  default:
2561
2957
  throw new Error(`Unknown command: ${cmd ?? "(none)"}`);
2562
2958
  }
2563
2959
  }
2960
+ async function cmdProbeSource(args) {
2961
+ const cardIdx = args.indexOf("--card");
2962
+ const sourceIdxArg = args.indexOf("--source-idx");
2963
+ const sourceBindArg = args.indexOf("--source-bind");
2964
+ const mockReqIdx = args.indexOf("--mock-requires");
2965
+ const rgIdx = args.indexOf("--rg");
2966
+ const outIdx = args.indexOf("--out");
2967
+ const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
2968
+ const sourceIdxVal = sourceIdxArg !== -1 ? parseInt(args[sourceIdxArg + 1], 10) : 0;
2969
+ const sourceBindVal = sourceBindArg !== -1 ? args[sourceBindArg + 1] : void 0;
2970
+ const mockReqRaw = mockReqIdx !== -1 ? args[mockReqIdx + 1] : void 0;
2971
+ const boardDirArg = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2972
+ const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
2973
+ if (!cardFilePath) {
2974
+ console.error("Usage: board-live-cards probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>] [--mock-requires <json>] [--rg <boardDir>] [--out <result.json>]");
2975
+ process.exit(1);
2976
+ }
2977
+ let card;
2978
+ try {
2979
+ card = JSON.parse(fs.readFileSync(path.resolve(cardFilePath), "utf-8"));
2980
+ } catch (e) {
2981
+ console.error(`[probe-source] Cannot read card: ${e.message}`);
2982
+ process.exit(1);
2983
+ }
2984
+ const sources = card.sources ?? [];
2985
+ if (sources.length === 0) {
2986
+ console.error(`[probe-source] Card "${card.id}" has no sources`);
2987
+ process.exit(1);
2988
+ }
2989
+ let sourceIdx;
2990
+ if (sourceBindVal) {
2991
+ sourceIdx = sources.findIndex((s) => s.bindTo === sourceBindVal);
2992
+ if (sourceIdx === -1) {
2993
+ console.error(`[probe-source] No source with bindTo="${sourceBindVal}" in card "${card.id}"`);
2994
+ process.exit(1);
2995
+ }
2996
+ } else {
2997
+ sourceIdx = sourceIdxVal;
2998
+ if (isNaN(sourceIdx) || sourceIdx < 0 || sourceIdx >= sources.length) {
2999
+ console.error(`[probe-source] --source-idx ${sourceIdxVal} out of range (card has ${sources.length} source(s))`);
3000
+ process.exit(1);
3001
+ }
3002
+ }
3003
+ const sourceDef = sources[sourceIdx];
3004
+ const cardDir = path.resolve(path.dirname(cardFilePath));
3005
+ const boardDir = boardDirArg ? path.resolve(boardDirArg) : cardDir;
3006
+ let mockRequires = {};
3007
+ if (mockReqRaw) {
3008
+ const raw = mockReqRaw.startsWith("@") ? fs.readFileSync(path.resolve(mockReqRaw.slice(1)), "utf-8") : mockReqRaw;
3009
+ try {
3010
+ mockRequires = JSON.parse(raw);
3011
+ } catch (e) {
3012
+ console.error(`[probe-source] --mock-requires is not valid JSON: ${e.message}`);
3013
+ process.exit(1);
3014
+ }
3015
+ }
3016
+ const executorFile = path.join(boardDir, ".task-executor");
3017
+ const taskExecutor = fs.existsSync(executorFile) ? fs.readFileSync(executorFile, "utf-8").trim() : void 0;
3018
+ const inPayload = {
3019
+ ...sourceDef,
3020
+ cwd: typeof sourceDef.cwd === "string" && sourceDef.cwd ? sourceDef.cwd : cardDir,
3021
+ boardDir: typeof sourceDef.boardDir === "string" && sourceDef.boardDir ? sourceDef.boardDir : boardDir,
3022
+ _requires: mockRequires,
3023
+ _sourcesData: {},
3024
+ _computed_values: {}
3025
+ };
3026
+ const sourceKind = sourceDef.chartApi ? "chartApi" : sourceDef.http ? "http" : sourceDef.copilot || sourceDef.prompt_template ? "copilot" : sourceDef.cli ? "cli" : "mock";
3027
+ console.log(`[probe-source] card: ${card.id}`);
3028
+ console.log(`[probe-source] source[${sourceIdx}]: bindTo="${sourceDef.bindTo}" kind=${sourceKind}`);
3029
+ console.log(`[probe-source] _requires: ${JSON.stringify(mockRequires)}`);
3030
+ console.log(`[probe-source] executor: ${taskExecutor ?? "built-in (source.cli only)"}`);
3031
+ console.log(`[probe-source] running fetch...`);
3032
+ const ts = Date.now();
3033
+ const inFile = path.join(os.tmpdir(), `probe-in-${sourceDef.bindTo}-${ts}.json`);
3034
+ const tmpOut = path.join(os.tmpdir(), `probe-out-${sourceDef.bindTo}-${ts}.json`);
3035
+ const errFile = path.join(os.tmpdir(), `probe-err-${sourceDef.bindTo}-${ts}.txt`);
3036
+ fs.writeFileSync(inFile, JSON.stringify(inPayload, null, 2), "utf-8");
3037
+ let passed = false;
3038
+ let errorMsg;
3039
+ let resultRaw;
3040
+ try {
3041
+ if (taskExecutor) {
3042
+ execCommandSync(taskExecutor, ["run-source-fetch", "--in", inFile, "--out", tmpOut, "--err", errFile], {
3043
+ shell: true,
3044
+ timeout: sourceDef.timeout ?? 3e4
3045
+ });
3046
+ } else {
3047
+ if (!inPayload.cli) {
3048
+ throw new Error("No task-executor registered and source has no cli field \u2014 cannot probe with built-in executor");
3049
+ }
3050
+ const cmdParts = splitCommandLine(inPayload.cli);
3051
+ const rawCmd = cmdParts[0];
3052
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
3053
+ const stdout = execCommandSync(cmd, cliArgs, {
3054
+ shell: false,
3055
+ encoding: "utf-8",
3056
+ timeout: sourceDef.timeout ?? 3e4,
3057
+ cwd: inPayload.cwd
3058
+ });
3059
+ fs.writeFileSync(tmpOut, stdout.trim(), "utf-8");
3060
+ }
3061
+ passed = fs.existsSync(tmpOut);
3062
+ if (passed) {
3063
+ resultRaw = fs.readFileSync(tmpOut, "utf-8");
3064
+ } else {
3065
+ errorMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
3066
+ }
3067
+ } catch (e) {
3068
+ errorMsg = e.message ?? String(e);
3069
+ if (!errorMsg && fs.existsSync(errFile)) {
3070
+ errorMsg = fs.readFileSync(errFile, "utf-8").trim();
3071
+ }
3072
+ }
3073
+ for (const f of [inFile, errFile]) {
3074
+ try {
3075
+ fs.unlinkSync(f);
3076
+ } catch {
3077
+ }
3078
+ }
3079
+ if (passed && resultRaw !== void 0) {
3080
+ const resultSize = resultRaw.length;
3081
+ const sample = resultRaw.slice(0, 300);
3082
+ console.log(`[probe-source] STATUS: PROBE_PASS`);
3083
+ console.log(`[probe-source] result size: ${resultSize} bytes`);
3084
+ console.log(`[probe-source] sample: ${sample}${resultSize > 300 ? "..." : ""}`);
3085
+ if (outFile) {
3086
+ fs.writeFileSync(path.resolve(outFile), resultRaw);
3087
+ console.log(`[probe-source] result written to: ${outFile}`);
3088
+ } else {
3089
+ try {
3090
+ fs.unlinkSync(tmpOut);
3091
+ } catch {
3092
+ }
3093
+ }
3094
+ } else {
3095
+ console.log(`[probe-source] STATUS: PROBE_FAIL`);
3096
+ if (errorMsg) console.log(`[probe-source] error: ${errorMsg}`);
3097
+ try {
3098
+ if (fs.existsSync(tmpOut)) fs.unlinkSync(tmpOut);
3099
+ } catch {
3100
+ }
3101
+ }
3102
+ const summary = {
3103
+ status: passed ? "PROBE_PASS" : "PROBE_FAIL",
3104
+ cardId: card.id,
3105
+ sourceIdx,
3106
+ bindTo: sourceDef.bindTo,
3107
+ sourceKind,
3108
+ mockRequiresKeys: Object.keys(mockRequires),
3109
+ resultSizeBytes: resultRaw !== void 0 ? resultRaw.length : 0,
3110
+ error: errorMsg ?? void 0
3111
+ };
3112
+ console.log(`[probe-source:result] ${JSON.stringify(summary)}`);
3113
+ process.exit(passed ? 0 : 1);
3114
+ }
2564
3115
  function cmdHelp() {
2565
3116
  console.log(`
2566
3117
  board-live-cards-cli \u2014 LiveCards board CLI
@@ -2569,14 +3120,16 @@ USAGE
2569
3120
  board-live-cards-cli <command> [options]
2570
3121
 
2571
3122
  BOARD MANAGEMENT
2572
- init <dir> [--task-executor <script>] [--runtime-out <dir>]
3123
+ init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]
2573
3124
  Create a new board in <dir>.
2574
3125
  If --task-executor is given, writes <dir>/.task-executor with the script path.
3126
+ If --chat-handler is given, writes <dir>/.chat-handler with the script path.
3127
+ If --inference-adapter is given, writes <dir>/.inference-adapter with the script path.
2575
3128
  Writes <dir>/.runtime-out (default: <dir>/runtime-out).
2576
3129
  Published runtime files:
2577
3130
  <runtime-out>/board-livegraph-status.json
2578
3131
  <runtime-out>/cards/<card-id>.computed.json
2579
- Re-running init on an existing board is safe; --task-executor updates the registration.
3132
+ Re-running init on an existing board is safe; handler registrations are updated.
2580
3133
 
2581
3134
  status --rg <dir> [--json]
2582
3135
  Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
@@ -2616,13 +3169,16 @@ TASK CALLBACKS (called by task executor scripts)
2616
3169
  task-failed --token <callbackToken> [--error <message>]
2617
3170
  Signal task failure with an optional error message.
2618
3171
 
3172
+ task-progress --rg <dir> --token <callbackToken> [--update <json>]
3173
+ Signal task progress with optional update payload (for waiting on more evidence, etc.).
3174
+
2619
3175
  SOURCE CALLBACKS (called internally by run-sources-internal)
2620
3176
  source-data-fetched --tmp <file> --token <sourceToken>
2621
3177
  Atomically rename <file> into the outputFile destination and record delivery
2622
- in runtime.json. Appends a task-progress event to re-invoke the card handler.
3178
+ via journal events. Appends a task-progress event to re-invoke the card handler.
2623
3179
 
2624
3180
  source-data-fetch-failure --token <sourceToken> [--reason <message>]
2625
- Record a source fetch failure in runtime.json and append a task-progress event.
3181
+ Record a source fetch failure via journal events and append a task-progress event.
2626
3182
 
2627
3183
  INTERNAL COMMANDS
2628
3184
  process-accumulated-events --rg <dir>
@@ -2637,7 +3193,7 @@ INTERNAL COMMANDS
2637
3193
  3) lock stays healthy,
2638
3194
  4) event production eventually quiesces.
2639
3195
 
2640
- run-sources-internal-internal --card <card.json> --token <callbackToken> --rg <dir>
3196
+ run-sources-internal --card <card.json> --token <callbackToken> --rg <dir>
2641
3197
  Execute all source[] entries for a card, then report delivery or failure.
2642
3198
  (Internal command \u2014 invoked by the card-handler. Not intended for direct use.)
2643
3199
 
@@ -2650,6 +3206,34 @@ INTERNAL COMMANDS
2650
3206
  Execute a source definition. Board-live-cards reads source.cli and executes it.
2651
3207
  Writes result to --out. Presence of --out after exit indicates success.
2652
3208
 
3209
+ probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>]
3210
+ [--mock-requires <json>] [--rg <boardDir>] [--out <result.json>]
3211
+ Validate that a card source can be fetched successfully.
3212
+ Reads the card file, extracts the chosen source (default: index 0), builds the
3213
+ run-source-fetch --in payload with the supplied _requires data, invokes the
3214
+ registered task-executor (or built-in executor for source.cli), and reports pass/fail.
3215
+ --mock-requires: JSON string (or @file.json) providing the _requires token values
3216
+ the source needs. Craft the minimal payload that exercises the
3217
+ source \u2014 e.g. '{"holdings":[{"ticker":"AAPL","quantity":10}]}'.
3218
+ If omitted, _requires is passed as empty ({}).
3219
+ --source-idx: 0-based index into card.sources[]. Default: 0.
3220
+ --source-bind: Select source by its bindTo name instead of index.
3221
+ --rg: Board directory used to find .task-executor. Defaults to the
3222
+ directory containing the card file.
3223
+ --out: Optional path to write the raw fetch result JSON.
3224
+ Prints a structured report ending with a [probe-source:result] JSON line.
3225
+ Exits 0 on PROBE_PASS, 1 on PROBE_FAIL.
3226
+
3227
+ run-inference-internal --in <input.json> --token <inferenceToken>
3228
+ Execute inference via registered .inference-adapter and forward result to inference-done.
3229
+ inferenceToken encodes boardDir (rg), cardId (cid), callbackToken (cbk), checksum (cs).
3230
+ (Internal command \u2014 invoked by the card-handler when custom completion rule is used.)
3231
+
3232
+ inference-done --tmp <result.json> --token <inferenceToken>
3233
+ Persist llm_task_completion_inference on the card and append a task-progress event.
3234
+ Reads boardDir/callbackToken/checksum from decoded inferenceToken; deletes --tmp file after reading.
3235
+ (Internal command \u2014 invoked by run-inference-internal.)
3236
+
2653
3237
  RUN-SOURCE-FETCH PROTOCOL
2654
3238
  External task-executors implement:
2655
3239
  <executor> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
@@ -2686,6 +3270,7 @@ EXAMPLES
2686
3270
  board-live-cards-cli add-cards --rg ./my-board --card cards/prices.json
2687
3271
  board-live-cards-cli status --rg ./my-board
2688
3272
  board-live-cards-cli retrigger --rg ./my-board --task price-fetch
3273
+ board-live-cards-cli probe-source --card cards/card-market-prices.json --source-idx 0 --rg ./my-board --mock-requires '{"holdings":[{"ticker":"AAPL","quantity":10}]}'
2689
3274
  `.trimStart());
2690
3275
  }
2691
3276
  var isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
@@ -2697,6 +3282,6 @@ if (isMain) {
2697
3282
  });
2698
3283
  }
2699
3284
 
2700
- export { BoardJournal, appendCardInventory, appendEventToJournal, buildCardInventoryIndex, cli, createBoardReactiveGraph, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
3285
+ export { BoardJournal, appendCardInventory, appendEventToJournal, buildCardInventoryIndex, cli, createBoardReactiveGraph, decideSourceAction, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, isSourceInFlight, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, nextEntryAfterFetchDelivery, nextEntryAfterFetchFailure, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
2701
3286
  //# sourceMappingURL=board-live-cards-cli.js.map
2702
3287
  //# sourceMappingURL=board-live-cards-cli.js.map