yaml-flow 5.1.0 → 5.2.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 (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 +14 -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
@@ -102,15 +102,30 @@ function groupTasksByProvides(candidateTaskNames, tasks) {
102
102
  }
103
103
 
104
104
  // src/event-graph/task-transitions.ts
105
- function applyTaskStart(state, taskName) {
105
+ function applyTaskStart(state, taskName, graph) {
106
106
  const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
107
+ const startConsumedHashes = {};
108
+ if (graph) {
109
+ const taskConfig = graph.tasks[taskName];
110
+ const requires = getRequires(taskConfig);
111
+ for (const token of requires) {
112
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
113
+ if (getProvides(otherConfig).includes(token)) {
114
+ const otherState = state.tasks[otherName];
115
+ if (otherState?.lastDataHash) startConsumedHashes[token] = otherState.lastDataHash;
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ }
107
121
  const updatedTask = {
108
122
  ...existingTask,
109
123
  status: "running",
110
124
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
111
125
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
112
126
  progress: 0,
113
- error: void 0
127
+ error: void 0,
128
+ startConsumedHashes
114
129
  };
115
130
  return {
116
131
  ...state,
@@ -130,16 +145,18 @@ function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
130
145
  } else {
131
146
  outputTokens = getProvides(taskConfig);
132
147
  }
133
- const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
134
- const requires = taskConfig.requires ?? [];
135
- for (const token of requires) {
136
- for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
137
- if (getProvides(otherConfig).includes(token)) {
138
- const otherState = state.tasks[otherName];
139
- if (otherState?.lastDataHash) {
140
- lastConsumedHashes[token] = otherState.lastDataHash;
148
+ const lastConsumedHashes = existingTask.startConsumedHashes ? { ...existingTask.startConsumedHashes } : { ...existingTask.lastConsumedHashes };
149
+ if (!existingTask.startConsumedHashes) {
150
+ const requires = taskConfig.requires ?? [];
151
+ for (const token of requires) {
152
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
153
+ if (getProvides(otherConfig).includes(token)) {
154
+ const otherState = state.tasks[otherName];
155
+ if (otherState?.lastDataHash) {
156
+ lastConsumedHashes[token] = otherState.lastDataHash;
157
+ }
158
+ break;
141
159
  }
142
- break;
143
160
  }
144
161
  }
145
162
  }
@@ -284,7 +301,7 @@ function applyEvent(live, event) {
284
301
  switch (event.type) {
285
302
  // --- Execution state transitions ---
286
303
  case "task-started":
287
- return { config, state: applyTaskStart(state, event.taskName) };
304
+ return { config, state: applyTaskStart(state, event.taskName, config) };
288
305
  case "task-completed":
289
306
  return { config, state: applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data) };
290
307
  case "task-failed":
@@ -1112,13 +1129,29 @@ function validateNode(node) {
1112
1129
  if (!Array.isArray(n.sources)) {
1113
1130
  errors.push("sources: must be an array");
1114
1131
  } else {
1132
+ const bindTos = /* @__PURE__ */ new Set();
1133
+ const outputFiles = /* @__PURE__ */ new Set();
1115
1134
  n.sources.forEach((src, i) => {
1116
1135
  if (!src || typeof src !== "object" || Array.isArray(src)) {
1117
1136
  errors.push(`sources[${i}]: must be an object`);
1118
1137
  } else {
1119
1138
  const s = src;
1120
- if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`sources[${i}]: missing required "bindTo" property`);
1121
- if (s.outputFile != null && typeof s.outputFile !== "string") errors.push(`sources[${i}]: outputFile must be a string`);
1139
+ if (typeof s.bindTo !== "string" || !s.bindTo) {
1140
+ errors.push(`sources[${i}]: missing required "bindTo" property`);
1141
+ } else {
1142
+ if (bindTos.has(s.bindTo)) {
1143
+ errors.push(`sources[${i}]: bindTo "${s.bindTo}" is not unique across sources`);
1144
+ }
1145
+ bindTos.add(s.bindTo);
1146
+ }
1147
+ if (typeof s.outputFile !== "string" || !s.outputFile) {
1148
+ errors.push(`sources[${i}]: missing required "outputFile" property`);
1149
+ } else {
1150
+ if (outputFiles.has(s.outputFile)) {
1151
+ errors.push(`sources[${i}]: outputFile "${s.outputFile}" is not unique across sources`);
1152
+ }
1153
+ outputFiles.add(s.outputFile);
1154
+ }
1122
1155
  if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
1123
1156
  errors.push(`sources[${i}]: optionalForCompletionGating must be a boolean`);
1124
1157
  }
@@ -1176,12 +1209,15 @@ var CardCompute = {
1176
1209
  var BOARD_FILE = "board-graph.json";
1177
1210
  var JOURNAL_FILE = "board-journal.jsonl";
1178
1211
  var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
1212
+ var INFERENCE_ADAPTER_LOG_FILE = "inference-adapter.jsonl";
1179
1213
  var INVENTORY_FILE = "cards-inventory.jsonl";
1180
1214
  var RUNTIME_OUT_FILE = ".runtime-out";
1181
1215
  var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
1182
1216
  var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
1183
1217
  var RUNTIME_CARDS_DIR = "cards";
1184
1218
  var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
1219
+ var INFERENCE_ADAPTER_FILE = ".inference-adapter";
1220
+ var DEFAULT_TASK_COMPLETION_RULE = "all_required_sources_fetched";
1185
1221
  var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
1186
1222
  var BoardJournal = class {
1187
1223
  journalPath;
@@ -1389,6 +1425,39 @@ function decodeSourceToken(token) {
1389
1425
  return null;
1390
1426
  }
1391
1427
  }
1428
+ function markRequested(entry, requestedAt) {
1429
+ entry.lastRequestedAt = requestedAt;
1430
+ }
1431
+ function markFetchFailed(entry, reason) {
1432
+ entry.lastError = reason;
1433
+ delete entry.lastFetchedAt;
1434
+ }
1435
+ function markFetchCompleted(entry, fetchedAt) {
1436
+ entry.lastFetchedAt = fetchedAt;
1437
+ delete entry.lastError;
1438
+ }
1439
+ function isSourceInFlight(entry) {
1440
+ if (!entry?.lastRequestedAt) return false;
1441
+ return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;
1442
+ }
1443
+ function decideSourceAction(entry, queueRequestedAt) {
1444
+ if (!entry?.lastRequestedAt) return "dispatch";
1445
+ const inFlight = isSourceInFlight(entry);
1446
+ if (inFlight) return "in-flight";
1447
+ if (!entry.lastFetchedAt) return "dispatch";
1448
+ if (entry.lastFetchedAt < queueRequestedAt) return "dispatch";
1449
+ return "idle";
1450
+ }
1451
+ function nextEntryAfterFetchDelivery(entry, fetchedAt) {
1452
+ const next = { ...entry };
1453
+ markFetchCompleted(next, fetchedAt);
1454
+ return next;
1455
+ }
1456
+ function nextEntryAfterFetchFailure(entry, reason) {
1457
+ const next = { ...entry };
1458
+ markFetchFailed(next, reason);
1459
+ return next;
1460
+ }
1392
1461
  function runtimePath(boardDir, cardId) {
1393
1462
  return path__namespace.join(boardDir, `${cardId}.runtime.json`);
1394
1463
  }
@@ -1543,6 +1612,15 @@ function splitCommandLine(command) {
1543
1612
  if (current) tokens.push(current);
1544
1613
  return tokens;
1545
1614
  }
1615
+ function resolveCommandInvocation(rawCmd, rawArgs) {
1616
+ if (/^(node|node\.exe)$/i.test(rawCmd)) {
1617
+ return { cmd: process.execPath, args: rawArgs };
1618
+ }
1619
+ if (/\.m?js$/i.test(rawCmd)) {
1620
+ return { cmd: process.execPath, args: [rawCmd, ...rawArgs] };
1621
+ }
1622
+ return { cmd: rawCmd, args: rawArgs };
1623
+ }
1546
1624
  function spawnDetachedProcessAccumulatedWorker(boardDir) {
1547
1625
  const { cmd, args: cliArgs } = getCliInvocation("process-accumulated-events", ["--rg", boardDir, "--inline-loop"]);
1548
1626
  try {
@@ -1618,7 +1696,18 @@ function getCliInvocation(command, args) {
1618
1696
  return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
1619
1697
  }
1620
1698
  function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
1621
- const { cmd, args } = getCliInvocation("run-sources-internal", ["--card", cardPath, "--token", callbackToken, "--rg", boardDir]);
1699
+ const args = ["--card", cardPath, "--token", callbackToken, "--rg", boardDir];
1700
+ const { cmd, args: cmdArgs } = getCliInvocation("run-sources-internal", args);
1701
+ try {
1702
+ spawnDetachedCommand(cmd, cmdArgs);
1703
+ callback(null);
1704
+ } catch (err) {
1705
+ callback(err instanceof Error ? err : new Error(String(err)));
1706
+ }
1707
+ }
1708
+ function invokeRunInference(boardDir, cardId, inputFile, callbackToken, checksum, callback) {
1709
+ const inferenceToken = encodeSourceToken({ cbk: callbackToken, rg: boardDir, cid: cardId, b: "", d: "", cs: checksum });
1710
+ const { cmd, args } = getCliInvocation("run-inference-internal", ["--in", inputFile, "--token", inferenceToken]);
1622
1711
  try {
1623
1712
  spawnDetachedCommand(cmd, args);
1624
1713
  callback(null);
@@ -1626,10 +1715,11 @@ function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
1626
1715
  callback(err instanceof Error ? err : new Error(String(err)));
1627
1716
  }
1628
1717
  }
1629
- function appendTaskExecutorLog(boardDir, hydratedSource) {
1718
+ function appendTaskExecutorLog(boardDir, hydratedSource, mode) {
1630
1719
  try {
1631
1720
  const entry = {
1632
1721
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1722
+ mode,
1633
1723
  hydratedSource
1634
1724
  };
1635
1725
  fs__namespace.appendFileSync(path__namespace.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
@@ -1637,6 +1727,18 @@ function appendTaskExecutorLog(boardDir, hydratedSource) {
1637
1727
  console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
1638
1728
  }
1639
1729
  }
1730
+ function appendInferenceAdapterLog(boardDir, cardId, payload) {
1731
+ try {
1732
+ const entry = {
1733
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1734
+ cardId,
1735
+ payload
1736
+ };
1737
+ fs__namespace.appendFileSync(path__namespace.join(boardDir, INFERENCE_ADAPTER_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
1738
+ } catch (logErr) {
1739
+ console.error(`[inference-adapter-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
1740
+ }
1741
+ }
1640
1742
  function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
1641
1743
  const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
1642
1744
  execCommandAsync(cmd, args, (err, stdout, stderr) => {
@@ -1665,22 +1767,33 @@ function createBoardReactiveGraph(boardDir) {
1665
1767
  const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
1666
1768
  const runtime = readRuntimeState(boardDir, cardId);
1667
1769
  let runtimeDirty = false;
1770
+ const currentExecutionCount = input.taskState?.executionCount ?? 0;
1771
+ if (typeof runtime._lastExecutionCount === "number" && runtime._lastExecutionCount !== currentExecutionCount) {
1772
+ runtime._sources = {};
1773
+ runtime._inferenceEntry = void 0;
1774
+ }
1775
+ if (runtime._lastExecutionCount !== currentExecutionCount) {
1776
+ runtime._lastExecutionCount = currentExecutionCount;
1777
+ runtimeDirty = true;
1778
+ }
1668
1779
  if (input.update) {
1669
1780
  const u = input.update;
1670
- const bindTo = u.bindTo;
1671
- if (!runtime._sources[bindTo]) runtime._sources[bindTo] = {};
1672
- if (u.failure) {
1673
- runtime._sources[bindTo].lastError = u.reason ?? "unknown";
1674
- delete runtime._sources[bindTo].lastFetchedAt;
1675
- runtimeDirty = true;
1676
- console.log(`[card-handler] source "${bindTo}" fetch failed: ${runtime._sources[bindTo].lastError}`);
1677
- } else {
1678
- runtime._sources[bindTo].lastFetchedAt = u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1679
- delete runtime._sources[bindTo].lastError;
1680
- runtimeDirty = true;
1681
- console.log(`[card-handler] source "${bindTo}" delivered \u2192 ${u.dest}`);
1781
+ const outputFile = u.outputFile;
1782
+ if (outputFile) {
1783
+ if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
1784
+ const entry = runtime._sources[outputFile];
1785
+ if (u.failure) {
1786
+ runtime._sources[outputFile] = nextEntryAfterFetchFailure(entry, u.reason ?? "unknown");
1787
+ runtimeDirty = true;
1788
+ } else {
1789
+ runtime._sources[outputFile] = nextEntryAfterFetchDelivery(
1790
+ entry,
1791
+ u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString()
1792
+ );
1793
+ runtimeDirty = true;
1794
+ }
1795
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1682
1796
  }
1683
- if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1684
1797
  }
1685
1798
  const sourcesData = {};
1686
1799
  for (const src of allSources) {
@@ -1722,40 +1835,57 @@ function createBoardReactiveGraph(boardDir) {
1722
1835
  card_id: cardId,
1723
1836
  computed_values: computeNode.computed_values ?? {}
1724
1837
  });
1838
+ const enrichedCard = { ...card };
1839
+ const enrichedSources = CardCompute.enrichSources(
1840
+ Array.isArray(card.sources) ? card.sources : void 0,
1841
+ {
1842
+ requires,
1843
+ sourcesData,
1844
+ computed_values: computeNode.computed_values
1845
+ }
1846
+ );
1847
+ const sourceCwd = path__namespace.dirname(cardPath);
1848
+ enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
1849
+ ...src,
1850
+ cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
1851
+ boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
1852
+ })) : enrichedSources;
1853
+ const enrichedByOutput = /* @__PURE__ */ new Map();
1854
+ for (const src of Array.isArray(enrichedCard.sources) ? enrichedCard.sources : []) {
1855
+ const outputFile = src.outputFile;
1856
+ if (typeof outputFile === "string" && outputFile) {
1857
+ enrichedByOutput.set(outputFile, src);
1858
+ }
1859
+ }
1725
1860
  const now = (/* @__PURE__ */ new Date()).toISOString();
1861
+ const runQueuedAt = input.update ? void 0 : now;
1726
1862
  const undeliveredRequired = requiredSources.filter((s) => {
1727
- if (!s.outputFile) return false;
1728
- const entry = runtime._sources[s.bindTo];
1729
- if (!entry?.lastRequestedAt) return true;
1730
- if (!entry.lastFetchedAt) return true;
1731
- return entry.lastFetchedAt <= entry.lastRequestedAt;
1863
+ const outputFile = s.outputFile;
1864
+ if (typeof outputFile !== "string" || !outputFile) return true;
1865
+ if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
1866
+ const entry = runtime._sources[outputFile];
1867
+ if (runQueuedAt) {
1868
+ entry.queueRequestedAt = runQueuedAt;
1869
+ runtimeDirty = true;
1870
+ }
1871
+ const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;
1872
+ const action = decideSourceAction(entry, qrt);
1873
+ if (action === "in-flight") return false;
1874
+ return action === "dispatch";
1732
1875
  });
1876
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1733
1877
  if (undeliveredRequired.length > 0) {
1734
1878
  let stampedAny = false;
1735
1879
  for (const src of undeliveredRequired) {
1736
- const entry = runtime._sources[src.bindTo] ?? {};
1737
- if (!entry.lastRequestedAt || entry.lastFetchedAt && entry.lastFetchedAt >= entry.lastRequestedAt) {
1738
- entry.lastRequestedAt = now;
1739
- runtime._sources[src.bindTo] = entry;
1740
- stampedAny = true;
1741
- }
1880
+ const outputFile = src.outputFile;
1881
+ if (typeof outputFile !== "string" || !outputFile) continue;
1882
+ const entry = runtime._sources[outputFile] ?? {};
1883
+ markRequested(entry, now);
1884
+ runtime._sources[outputFile] = entry;
1885
+ stampedAny = true;
1742
1886
  }
1743
1887
  if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
1744
- const enrichedCard = { ...card };
1745
- const enrichedSources = CardCompute.enrichSources(
1746
- Array.isArray(card.sources) ? card.sources : void 0,
1747
- {
1748
- requires,
1749
- sourcesData,
1750
- computed_values: computeNode.computed_values
1751
- }
1752
- );
1753
- const sourceCwd = path__namespace.dirname(cardPath);
1754
- enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
1755
- ...src,
1756
- cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
1757
- boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
1758
- })) : enrichedSources;
1888
+ if (!stampedAny) return "task-initiated";
1759
1889
  const enrichedCardPath = path__namespace.join(os__namespace.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
1760
1890
  fs__namespace.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
1761
1891
  invokeRunSources(boardDir, enrichedCardPath, input.callbackToken, (err) => {
@@ -1774,10 +1904,79 @@ function createBoardReactiveGraph(boardDir) {
1774
1904
  for (const { bindTo, src } of providesBindings) {
1775
1905
  data[bindTo] = CardCompute.resolve(computeNode, src);
1776
1906
  }
1907
+ 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;
1908
+ const cardData = card.card_data;
1909
+ const llmCompletion = cardData?.llm_task_completion_inference ?? {};
1910
+ const isLlmTaskCompleted = llmCompletion.isTaskCompleted === true;
1911
+ const inferenceEntry = runtime._inferenceEntry ?? {};
1912
+ const inferenceRequestedAt = typeof inferenceEntry.lastRequestedAt === "string" ? inferenceEntry.lastRequestedAt : void 0;
1913
+ const inferenceCompletedAt = typeof llmCompletion.inferenceCompletedAt === "string" ? llmCompletion.inferenceCompletedAt : void 0;
1914
+ const inferencePending = !!inferenceRequestedAt && (!inferenceCompletedAt || inferenceCompletedAt < inferenceRequestedAt);
1915
+ const latestRequiredSourceFetchedAt = requiredSources.reduce((latest, src) => {
1916
+ const fetchedAt = runtime._sources[src.outputFile]?.lastFetchedAt;
1917
+ if (typeof fetchedAt !== "string") return latest;
1918
+ if (!latest || fetchedAt > latest) return fetchedAt;
1919
+ return latest;
1920
+ }, void 0);
1921
+ const shouldRequestInference = !inferenceRequestedAt || !inferenceCompletedAt || !!latestRequiredSourceFetchedAt && latestRequiredSourceFetchedAt > inferenceCompletedAt;
1922
+ if (completionRule !== DEFAULT_TASK_COMPLETION_RULE) {
1923
+ if (isLlmTaskCompleted) ; else if (inferencePending) {
1924
+ return "task-initiated";
1925
+ } else if (!shouldRequestInference) {
1926
+ return "task-initiated";
1927
+ } else {
1928
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
1929
+ const inferencePayload = {
1930
+ cardId,
1931
+ taskName: input.nodeId,
1932
+ completionRule,
1933
+ context: {
1934
+ requires,
1935
+ sourcesData,
1936
+ computed_values: computeNode.computed_values ?? {},
1937
+ provides: data,
1938
+ card_data: computeNode.card_data ?? {}
1939
+ }
1940
+ };
1941
+ if (runQueuedAt) {
1942
+ inferenceEntry.queueRequestedAt = runQueuedAt;
1943
+ runtimeDirty = true;
1944
+ }
1945
+ const inferenceQrt = inferenceEntry.queueRequestedAt ?? inferenceEntry.lastRequestedAt ?? now2;
1946
+ const inferenceAction = decideSourceAction(inferenceEntry, inferenceQrt);
1947
+ if (inferenceAction === "in-flight") {
1948
+ runtime._inferenceEntry = inferenceEntry;
1949
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1950
+ return "task-initiated";
1951
+ }
1952
+ if (inferenceAction === "idle") {
1953
+ return "task-initiated";
1954
+ }
1955
+ const inferenceInFile = path__namespace.join(os__namespace.tmpdir(), `card-inference-${cardId}-${Date.now()}.json`);
1956
+ fs__namespace.writeFileSync(inferenceInFile, JSON.stringify(inferencePayload, null, 2), "utf-8");
1957
+ appendInferenceAdapterLog(boardDir, cardId, inferencePayload);
1958
+ markRequested(inferenceEntry, now2);
1959
+ runtime._inferenceEntry = inferenceEntry;
1960
+ runtimeDirty = true;
1961
+ invokeRunInference(boardDir, cardId, inferenceInFile, input.callbackToken, void 0, (err) => {
1962
+ if (err) {
1963
+ console.error(`[card-handler] ${input.nodeId}:`, err.message);
1964
+ const failedAt = (/* @__PURE__ */ new Date()).toISOString();
1965
+ appendEventToJournal(boardDir, {
1966
+ type: "task-failed",
1967
+ taskName: input.nodeId,
1968
+ error: err.message,
1969
+ timestamp: failedAt
1970
+ });
1971
+ }
1972
+ });
1973
+ return "task-initiated";
1974
+ }
1975
+ }
1777
1976
  writeRuntimeDataObjects(boardDir, data);
1778
1977
  const undeliveredOptional = allSources.filter((s) => {
1779
- if (s.optionalForCompletionGating !== true || !s.outputFile) return false;
1780
- const entry = runtime._sources[s.bindTo];
1978
+ if (s.optionalForCompletionGating !== true) return false;
1979
+ const entry = runtime._sources[s.outputFile];
1781
1980
  if (!entry?.lastRequestedAt) return true;
1782
1981
  if (!entry.lastFetchedAt) return true;
1783
1982
  return entry.lastFetchedAt <= entry.lastRequestedAt;
@@ -1871,16 +2070,18 @@ function cmdAddCards(args) {
1871
2070
  function cmdInit(args) {
1872
2071
  const dir = args[0];
1873
2072
  if (!dir) {
1874
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
2073
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
1875
2074
  }
1876
2075
  const teIdx = args.indexOf("--task-executor");
1877
2076
  const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
1878
2077
  const chIdx = args.indexOf("--chat-handler");
1879
2078
  const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
2079
+ const iaIdx = args.indexOf("--inference-adapter");
2080
+ const inferenceAdapter = iaIdx !== -1 ? args[iaIdx + 1] : void 0;
1880
2081
  const roIdx = args.indexOf("--runtime-out");
1881
2082
  const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
1882
2083
  if (roIdx !== -1 && !runtimeOut) {
1883
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
2084
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
1884
2085
  }
1885
2086
  const result = initBoard(dir);
1886
2087
  if (taskExecutor) {
@@ -1889,6 +2090,9 @@ function cmdInit(args) {
1889
2090
  if (chatHandler) {
1890
2091
  fs__namespace.writeFileSync(path__namespace.join(dir, ".chat-handler"), chatHandler, "utf-8");
1891
2092
  }
2093
+ if (inferenceAdapter) {
2094
+ fs__namespace.writeFileSync(path__namespace.join(dir, INFERENCE_ADAPTER_FILE), inferenceAdapter, "utf-8");
2095
+ }
1892
2096
  const runtimeOutDir = configureRuntimeOutDir(dir, runtimeOut);
1893
2097
  const live = loadBoard(dir);
1894
2098
  writeJsonAtomic(resolveStatusSnapshotPath(dir), buildBoardStatusObject(dir, live));
@@ -2124,7 +2328,7 @@ function cmdSourceDataFetched(args) {
2124
2328
  console.error("Invalid source token");
2125
2329
  process.exit(1);
2126
2330
  }
2127
- const { cbk, rg, cid, b, d } = payload;
2331
+ const { cbk, rg, cid, b, d, cs } = payload;
2128
2332
  const destPath = path__namespace.join(rg, d);
2129
2333
  fs__namespace.mkdirSync(path__namespace.dirname(destPath), { recursive: true });
2130
2334
  fs__namespace.renameSync(tmpFile, destPath);
@@ -2138,7 +2342,7 @@ function cmdSourceDataFetched(args) {
2138
2342
  appendEventToJournal(rg, {
2139
2343
  type: "task-progress",
2140
2344
  taskName: cbkDecoded.taskName,
2141
- update: { bindTo: b, fetchedAt, dest: d },
2345
+ update: { bindTo: b, outputFile: d, fetchedAt, sourceChecksum: cs },
2142
2346
  timestamp: fetchedAt
2143
2347
  });
2144
2348
  void processAccumulatedEventsInfinitePass(rg);
@@ -2157,7 +2361,7 @@ function cmdSourceDataFetchFailure(args) {
2157
2361
  console.error("Invalid source token");
2158
2362
  process.exit(1);
2159
2363
  }
2160
- const { cbk, rg, cid, b } = payload;
2364
+ const { cbk, rg, cid, b, d, cs } = payload;
2161
2365
  console.log(`[source-data-fetch-failure] ${cid}.${b}: ${reason}`);
2162
2366
  const cbkDecoded = decodeCallbackToken2(cbk);
2163
2367
  if (!cbkDecoded) {
@@ -2168,7 +2372,7 @@ function cmdSourceDataFetchFailure(args) {
2168
2372
  appendEventToJournal(rg, {
2169
2373
  type: "task-progress",
2170
2374
  taskName: cbkDecoded.taskName,
2171
- update: { bindTo: b, failure: true, reason },
2375
+ update: { bindTo: b, outputFile: d, failure: true, reason, sourceChecksum: cs },
2172
2376
  timestamp
2173
2377
  });
2174
2378
  void processAccumulatedEventsInfinitePass(rg);
@@ -2177,11 +2381,14 @@ function cmdRunSources(args) {
2177
2381
  const cardIdx = args.indexOf("--card");
2178
2382
  const tokenIdx = args.indexOf("--token");
2179
2383
  const rgIdx = args.indexOf("--rg");
2384
+ const sourceChecksumsIdx = args.indexOf("--source-checksums");
2180
2385
  const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
2181
2386
  const callbackToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2182
2387
  const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2388
+ const sourceChecksumsJson = sourceChecksumsIdx !== -1 ? args[sourceChecksumsIdx + 1] : void 0;
2389
+ const sourceChecksums = sourceChecksumsJson ? JSON.parse(sourceChecksumsJson) : void 0;
2183
2390
  if (!cardFilePath || !callbackToken || !boardDir) {
2184
- console.error("Usage: board-live-cards run-sources-internal --card <path> --token <token> --rg <dir>");
2391
+ console.error("Usage: board-live-cards run-sources-internal --card <path> --token <token> --rg <dir> [--source-checksums <json>]");
2185
2392
  process.exit(1);
2186
2393
  }
2187
2394
  const card = JSON.parse(fs__namespace.readFileSync(cardFilePath, "utf-8"));
@@ -2195,12 +2402,14 @@ function cmdRunSources(args) {
2195
2402
  const executorFile = path__namespace.join(boardDir, ".task-executor");
2196
2403
  const taskExecutor = fs__namespace.existsSync(executorFile) ? fs__namespace.readFileSync(executorFile, "utf-8").trim() : void 0;
2197
2404
  function runSource(src) {
2405
+ const sourceChecksumForInvoke = src.outputFile ? sourceChecksums?.[src.outputFile] : void 0;
2198
2406
  const sourceToken = encodeSourceToken({
2199
2407
  cbk: callbackToken,
2200
2408
  rg: boardDir,
2201
2409
  cid: card.id,
2202
2410
  b: src.bindTo,
2203
- d: src.outputFile ?? ""
2411
+ d: src.outputFile ?? "",
2412
+ cs: sourceChecksumForInvoke
2204
2413
  });
2205
2414
  function reportFailure(reason) {
2206
2415
  invokeSourceDataFetchFailure(sourceToken, reason, (err) => {
@@ -2224,7 +2433,7 @@ function cmdRunSources(args) {
2224
2433
  cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path__namespace.dirname(cardFilePath || ""),
2225
2434
  boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
2226
2435
  };
2227
- appendTaskExecutorLog(boardDir, sourceForExecutor);
2436
+ appendTaskExecutorLog(boardDir, sourceForExecutor, "external-task-executor");
2228
2437
  fs__namespace.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
2229
2438
  console.log(`[run-sources-internal] task-executor: ${taskExecutor} run-source-fetch --in ${inFile} --out ${outFile2} --err ${errFile}`);
2230
2439
  try {
@@ -2262,6 +2471,12 @@ function cmdRunSources(args) {
2262
2471
  const timeout = src.timeout ?? 12e4;
2263
2472
  const sourceCwd = typeof src.cwd === "string" ? src.cwd : path__namespace.dirname(cardFilePath || "");
2264
2473
  const sourceBoardDir = typeof src.boardDir === "string" ? src.boardDir : boardDir;
2474
+ const sourceForBuiltInExecutor = {
2475
+ ...src,
2476
+ cwd: sourceCwd,
2477
+ boardDir: sourceBoardDir
2478
+ };
2479
+ appendTaskExecutorLog(boardDir, sourceForBuiltInExecutor, "built-in-run-source-fetch");
2265
2480
  const cmdParts = splitCommandLine(src.cli);
2266
2481
  if (cmdParts.length === 0) {
2267
2482
  const errMsg = "source.cli command is empty";
@@ -2270,8 +2485,7 @@ function cmdRunSources(args) {
2270
2485
  return;
2271
2486
  }
2272
2487
  const rawCmd = cmdParts[0];
2273
- const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2274
- const cliArgs = cmdParts.slice(1);
2488
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
2275
2489
  let stdout;
2276
2490
  try {
2277
2491
  stdout = execCommandSync(cmd, cliArgs, {
@@ -2298,6 +2512,181 @@ function cmdRunSources(args) {
2298
2512
  runSource(src);
2299
2513
  }
2300
2514
  }
2515
+ function cmdTaskProgress(args) {
2516
+ const rgIdx = args.indexOf("--rg");
2517
+ const tokenIdx = args.indexOf("--token");
2518
+ const updateIdx = args.indexOf("--update");
2519
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2520
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2521
+ const updateJson = updateIdx !== -1 ? args[updateIdx + 1] : "{}";
2522
+ if (!dir || !token) {
2523
+ console.error("Usage: board-live-cards task-progress --rg <dir> --token <token> [--update <json>]");
2524
+ process.exit(1);
2525
+ }
2526
+ const decoded = decodeCallbackToken2(token);
2527
+ if (!decoded) {
2528
+ console.error("Invalid callback token");
2529
+ process.exit(1);
2530
+ }
2531
+ const update = updateJson ? JSON.parse(updateJson) : {};
2532
+ appendEventToJournal(dir, {
2533
+ type: "task-progress",
2534
+ taskName: decoded.taskName,
2535
+ update,
2536
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2537
+ });
2538
+ void processAccumulatedEventsInfinitePass(dir);
2539
+ }
2540
+ function cmdRunInference(args) {
2541
+ const inIdx = args.indexOf("--in");
2542
+ const tokenIdx = args.indexOf("--token");
2543
+ const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
2544
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2545
+ if (!inFile || !inferenceToken) {
2546
+ console.error("Usage: board-live-cards run-inference-internal --in <input.json> --token <inference-token>");
2547
+ process.exit(1);
2548
+ }
2549
+ const decodedToken = decodeSourceToken(inferenceToken);
2550
+ if (!decodedToken) {
2551
+ console.error("Invalid inference token");
2552
+ process.exit(1);
2553
+ }
2554
+ const callbackToken = decodedToken.cbk;
2555
+ const boardDir = decodedToken.rg;
2556
+ const cbkDecoded = decodeCallbackToken2(callbackToken);
2557
+ if (!cbkDecoded) {
2558
+ console.error("Invalid callback token embedded in inference token");
2559
+ process.exit(1);
2560
+ }
2561
+ function spawnInferenceDone(tmpFile) {
2562
+ const { cmd, args: cliArgs } = getCliInvocation("inference-done", ["--tmp", tmpFile, "--token", inferenceToken]);
2563
+ spawnDetachedCommand(cmd, cliArgs);
2564
+ }
2565
+ function spawnInferenceDoneError(reason) {
2566
+ const tmpFile = path__namespace.join(os__namespace.tmpdir(), `card-inference-err-${Date.now()}.json`);
2567
+ fs__namespace.writeFileSync(tmpFile, JSON.stringify({ isTaskCompleted: false, reason }), "utf-8");
2568
+ spawnInferenceDone(tmpFile);
2569
+ }
2570
+ if (!fs__namespace.existsSync(inFile)) {
2571
+ spawnInferenceDoneError(`inference input not found: ${inFile}`);
2572
+ return;
2573
+ }
2574
+ const adapterFile = path__namespace.join(boardDir, INFERENCE_ADAPTER_FILE);
2575
+ const inferenceAdapter = fs__namespace.existsSync(adapterFile) ? fs__namespace.readFileSync(adapterFile, "utf-8").trim() : void 0;
2576
+ if (!inferenceAdapter) {
2577
+ spawnInferenceDoneError(`inference adapter is not configured (${INFERENCE_ADAPTER_FILE})`);
2578
+ return;
2579
+ }
2580
+ const outFile = path__namespace.join(os__namespace.tmpdir(), `card-inference-out-${Date.now()}.json`);
2581
+ const errFile = path__namespace.join(os__namespace.tmpdir(), `card-inference-err-${Date.now()}.txt`);
2582
+ const adapterParts = splitCommandLine(inferenceAdapter);
2583
+ if (adapterParts.length === 0) {
2584
+ spawnInferenceDoneError("inference adapter command is empty");
2585
+ return;
2586
+ }
2587
+ const adapterRawCmd = adapterParts[0];
2588
+ const adapterRawArgs = adapterParts.slice(1);
2589
+ const { cmd: adapterCmd, args: adapterArgsPrefix } = resolveCommandInvocation(adapterRawCmd, adapterRawArgs);
2590
+ const adapterArgs = [...adapterArgsPrefix, "run-inference", "--in", inFile, "--out", outFile, "--err", errFile];
2591
+ try {
2592
+ execCommandSync(adapterCmd, adapterArgs, {
2593
+ shell: false,
2594
+ timeout: 12e4,
2595
+ cwd: boardDir,
2596
+ env: {
2597
+ ...process.env,
2598
+ BOARD_DIR: boardDir
2599
+ }
2600
+ });
2601
+ } catch (err) {
2602
+ const reason = err.message ?? String(err);
2603
+ spawnInferenceDoneError(reason);
2604
+ return;
2605
+ }
2606
+ if (!fs__namespace.existsSync(outFile)) {
2607
+ const errMsg = fs__namespace.existsSync(errFile) ? fs__namespace.readFileSync(errFile, "utf-8").trim() : "inference adapter produced no output file";
2608
+ spawnInferenceDoneError(errMsg);
2609
+ return;
2610
+ }
2611
+ spawnInferenceDone(outFile);
2612
+ }
2613
+ function cmdInferenceDone(args) {
2614
+ const tmpIdx = args.indexOf("--tmp");
2615
+ const tokenIdx = args.indexOf("--token");
2616
+ const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
2617
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
2618
+ if (!tmpFile || !inferenceToken) {
2619
+ console.error("Usage: board-live-cards inference-done --tmp <result.json> --token <inference-token>");
2620
+ process.exit(1);
2621
+ }
2622
+ const decodedToken = decodeSourceToken(inferenceToken);
2623
+ if (!decodedToken) {
2624
+ console.error("Invalid inference token");
2625
+ process.exit(1);
2626
+ }
2627
+ const { cbk: callbackToken, rg: dir, cs: inputChecksum } = decodedToken;
2628
+ const decoded = decodeCallbackToken2(callbackToken);
2629
+ if (!decoded) {
2630
+ console.error("Invalid callback token embedded in inference token");
2631
+ process.exit(1);
2632
+ }
2633
+ const taskName = decoded.taskName;
2634
+ const cardPath = lookupCardPath(dir, taskName);
2635
+ if (!cardPath) {
2636
+ console.error(`Card file for task "${taskName}" not found in inventory`);
2637
+ process.exit(1);
2638
+ }
2639
+ let result = {};
2640
+ if (fs__namespace.existsSync(tmpFile)) {
2641
+ try {
2642
+ result = JSON.parse(fs__namespace.readFileSync(tmpFile, "utf-8").trim());
2643
+ } catch (err) {
2644
+ result = { isTaskCompleted: false, reason: `failed to parse inference result: ${err instanceof Error ? err.message : String(err)}` };
2645
+ }
2646
+ try {
2647
+ fs__namespace.unlinkSync(tmpFile);
2648
+ } catch {
2649
+ }
2650
+ } else {
2651
+ result = { isTaskCompleted: false, reason: `inference result file not found: ${tmpFile}` };
2652
+ }
2653
+ const isTaskCompletedFlag = result.isTaskCompleted === true;
2654
+ const inferenceCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
2655
+ const card = JSON.parse(fs__namespace.readFileSync(cardPath, "utf-8"));
2656
+ if (!card.card_data) card.card_data = {};
2657
+ const cardData = card.card_data;
2658
+ const existingInference = cardData.llm_task_completion_inference && typeof cardData.llm_task_completion_inference === "object" ? cardData.llm_task_completion_inference : {};
2659
+ cardData.llm_task_completion_inference = {
2660
+ ...existingInference,
2661
+ isTaskCompleted: isTaskCompletedFlag,
2662
+ reason: typeof result.reason === "string" ? result.reason : "",
2663
+ evidence: typeof result.evidence === "string" ? result.evidence : "",
2664
+ inferenceCompletedAt
2665
+ };
2666
+ fs__namespace.writeFileSync(cardPath, JSON.stringify(card, null, 2), "utf-8");
2667
+ const runtimePath2 = path__namespace.join(dir, `${taskName}.runtime.json`);
2668
+ let runtime = { _sources: {} };
2669
+ if (fs__namespace.existsSync(runtimePath2)) {
2670
+ try {
2671
+ runtime = JSON.parse(fs__namespace.readFileSync(runtimePath2, "utf-8"));
2672
+ } catch {
2673
+ }
2674
+ }
2675
+ const inferenceEntry = runtime._inferenceEntry ?? {};
2676
+ runtime._inferenceEntry = nextEntryAfterFetchDelivery(inferenceEntry, inferenceCompletedAt);
2677
+ fs__namespace.writeFileSync(runtimePath2, JSON.stringify(runtime, null, 2), "utf-8");
2678
+ appendEventToJournal(dir, {
2679
+ type: "task-progress",
2680
+ taskName,
2681
+ update: {
2682
+ kind: "inference-done",
2683
+ isTaskCompleted: isTaskCompletedFlag,
2684
+ inputChecksum
2685
+ },
2686
+ timestamp: inferenceCompletedAt
2687
+ });
2688
+ void processAccumulatedEventsInfinitePass(dir);
2689
+ }
2301
2690
  function cmdRunSourceFetch(args) {
2302
2691
  const inIdx = args.indexOf("--in");
2303
2692
  const outIdx = args.indexOf("--out");
@@ -2343,8 +2732,7 @@ function cmdRunSourceFetch(args) {
2343
2732
  process.exit(1);
2344
2733
  }
2345
2734
  const rawCmd = cmdParts[0];
2346
- const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2347
- const cliArgs = cmdParts.slice(1);
2735
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
2348
2736
  let stdout;
2349
2737
  try {
2350
2738
  stdout = execCommandSync(cmd, cliArgs, {
@@ -2576,20 +2964,183 @@ async function cli(argv) {
2576
2964
  return cmdTaskCompleted(rest);
2577
2965
  case "task-failed":
2578
2966
  return cmdTaskFailed(rest);
2967
+ case "task-progress":
2968
+ return cmdTaskProgress(rest);
2579
2969
  case "source-data-fetched":
2580
2970
  return cmdSourceDataFetched(rest);
2581
2971
  case "source-data-fetch-failure":
2582
2972
  return cmdSourceDataFetchFailure(rest);
2583
2973
  case "run-sources-internal":
2584
2974
  return cmdRunSources(rest);
2975
+ case "run-inference-internal":
2976
+ return cmdRunInference(rest);
2977
+ case "inference-done":
2978
+ return cmdInferenceDone(rest);
2585
2979
  case "run-source-fetch":
2586
2980
  return cmdRunSourceFetch(rest);
2981
+ case "probe-source":
2982
+ return await cmdProbeSource(rest);
2587
2983
  case "process-accumulated-events":
2588
2984
  return await cmdTryDrain(rest);
2589
2985
  default:
2590
2986
  throw new Error(`Unknown command: ${cmd ?? "(none)"}`);
2591
2987
  }
2592
2988
  }
2989
+ async function cmdProbeSource(args) {
2990
+ const cardIdx = args.indexOf("--card");
2991
+ const sourceIdxArg = args.indexOf("--source-idx");
2992
+ const sourceBindArg = args.indexOf("--source-bind");
2993
+ const mockReqIdx = args.indexOf("--mock-requires");
2994
+ const rgIdx = args.indexOf("--rg");
2995
+ const outIdx = args.indexOf("--out");
2996
+ const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
2997
+ const sourceIdxVal = sourceIdxArg !== -1 ? parseInt(args[sourceIdxArg + 1], 10) : 0;
2998
+ const sourceBindVal = sourceBindArg !== -1 ? args[sourceBindArg + 1] : void 0;
2999
+ const mockReqRaw = mockReqIdx !== -1 ? args[mockReqIdx + 1] : void 0;
3000
+ const boardDirArg = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
3001
+ const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
3002
+ if (!cardFilePath) {
3003
+ 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>]");
3004
+ process.exit(1);
3005
+ }
3006
+ let card;
3007
+ try {
3008
+ card = JSON.parse(fs__namespace.readFileSync(path__namespace.resolve(cardFilePath), "utf-8"));
3009
+ } catch (e) {
3010
+ console.error(`[probe-source] Cannot read card: ${e.message}`);
3011
+ process.exit(1);
3012
+ }
3013
+ const sources = card.sources ?? [];
3014
+ if (sources.length === 0) {
3015
+ console.error(`[probe-source] Card "${card.id}" has no sources`);
3016
+ process.exit(1);
3017
+ }
3018
+ let sourceIdx;
3019
+ if (sourceBindVal) {
3020
+ sourceIdx = sources.findIndex((s) => s.bindTo === sourceBindVal);
3021
+ if (sourceIdx === -1) {
3022
+ console.error(`[probe-source] No source with bindTo="${sourceBindVal}" in card "${card.id}"`);
3023
+ process.exit(1);
3024
+ }
3025
+ } else {
3026
+ sourceIdx = sourceIdxVal;
3027
+ if (isNaN(sourceIdx) || sourceIdx < 0 || sourceIdx >= sources.length) {
3028
+ console.error(`[probe-source] --source-idx ${sourceIdxVal} out of range (card has ${sources.length} source(s))`);
3029
+ process.exit(1);
3030
+ }
3031
+ }
3032
+ const sourceDef = sources[sourceIdx];
3033
+ const cardDir = path__namespace.resolve(path__namespace.dirname(cardFilePath));
3034
+ const boardDir = boardDirArg ? path__namespace.resolve(boardDirArg) : cardDir;
3035
+ let mockRequires = {};
3036
+ if (mockReqRaw) {
3037
+ const raw = mockReqRaw.startsWith("@") ? fs__namespace.readFileSync(path__namespace.resolve(mockReqRaw.slice(1)), "utf-8") : mockReqRaw;
3038
+ try {
3039
+ mockRequires = JSON.parse(raw);
3040
+ } catch (e) {
3041
+ console.error(`[probe-source] --mock-requires is not valid JSON: ${e.message}`);
3042
+ process.exit(1);
3043
+ }
3044
+ }
3045
+ const executorFile = path__namespace.join(boardDir, ".task-executor");
3046
+ const taskExecutor = fs__namespace.existsSync(executorFile) ? fs__namespace.readFileSync(executorFile, "utf-8").trim() : void 0;
3047
+ const inPayload = {
3048
+ ...sourceDef,
3049
+ cwd: typeof sourceDef.cwd === "string" && sourceDef.cwd ? sourceDef.cwd : cardDir,
3050
+ boardDir: typeof sourceDef.boardDir === "string" && sourceDef.boardDir ? sourceDef.boardDir : boardDir,
3051
+ _requires: mockRequires,
3052
+ _sourcesData: {},
3053
+ _computed_values: {}
3054
+ };
3055
+ const sourceKind = sourceDef.chartApi ? "chartApi" : sourceDef.http ? "http" : sourceDef.copilot || sourceDef.prompt_template ? "copilot" : sourceDef.cli ? "cli" : "mock";
3056
+ console.log(`[probe-source] card: ${card.id}`);
3057
+ console.log(`[probe-source] source[${sourceIdx}]: bindTo="${sourceDef.bindTo}" kind=${sourceKind}`);
3058
+ console.log(`[probe-source] _requires: ${JSON.stringify(mockRequires)}`);
3059
+ console.log(`[probe-source] executor: ${taskExecutor ?? "built-in (source.cli only)"}`);
3060
+ console.log(`[probe-source] running fetch...`);
3061
+ const ts = Date.now();
3062
+ const inFile = path__namespace.join(os__namespace.tmpdir(), `probe-in-${sourceDef.bindTo}-${ts}.json`);
3063
+ const tmpOut = path__namespace.join(os__namespace.tmpdir(), `probe-out-${sourceDef.bindTo}-${ts}.json`);
3064
+ const errFile = path__namespace.join(os__namespace.tmpdir(), `probe-err-${sourceDef.bindTo}-${ts}.txt`);
3065
+ fs__namespace.writeFileSync(inFile, JSON.stringify(inPayload, null, 2), "utf-8");
3066
+ let passed = false;
3067
+ let errorMsg;
3068
+ let resultRaw;
3069
+ try {
3070
+ if (taskExecutor) {
3071
+ execCommandSync(taskExecutor, ["run-source-fetch", "--in", inFile, "--out", tmpOut, "--err", errFile], {
3072
+ shell: true,
3073
+ timeout: sourceDef.timeout ?? 3e4
3074
+ });
3075
+ } else {
3076
+ if (!inPayload.cli) {
3077
+ throw new Error("No task-executor registered and source has no cli field \u2014 cannot probe with built-in executor");
3078
+ }
3079
+ const cmdParts = splitCommandLine(inPayload.cli);
3080
+ const rawCmd = cmdParts[0];
3081
+ const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
3082
+ const stdout = execCommandSync(cmd, cliArgs, {
3083
+ shell: false,
3084
+ encoding: "utf-8",
3085
+ timeout: sourceDef.timeout ?? 3e4,
3086
+ cwd: inPayload.cwd
3087
+ });
3088
+ fs__namespace.writeFileSync(tmpOut, stdout.trim(), "utf-8");
3089
+ }
3090
+ passed = fs__namespace.existsSync(tmpOut);
3091
+ if (passed) {
3092
+ resultRaw = fs__namespace.readFileSync(tmpOut, "utf-8");
3093
+ } else {
3094
+ errorMsg = fs__namespace.existsSync(errFile) ? fs__namespace.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
3095
+ }
3096
+ } catch (e) {
3097
+ errorMsg = e.message ?? String(e);
3098
+ if (!errorMsg && fs__namespace.existsSync(errFile)) {
3099
+ errorMsg = fs__namespace.readFileSync(errFile, "utf-8").trim();
3100
+ }
3101
+ }
3102
+ for (const f of [inFile, errFile]) {
3103
+ try {
3104
+ fs__namespace.unlinkSync(f);
3105
+ } catch {
3106
+ }
3107
+ }
3108
+ if (passed && resultRaw !== void 0) {
3109
+ const resultSize = resultRaw.length;
3110
+ const sample = resultRaw.slice(0, 300);
3111
+ console.log(`[probe-source] STATUS: PROBE_PASS`);
3112
+ console.log(`[probe-source] result size: ${resultSize} bytes`);
3113
+ console.log(`[probe-source] sample: ${sample}${resultSize > 300 ? "..." : ""}`);
3114
+ if (outFile) {
3115
+ fs__namespace.writeFileSync(path__namespace.resolve(outFile), resultRaw);
3116
+ console.log(`[probe-source] result written to: ${outFile}`);
3117
+ } else {
3118
+ try {
3119
+ fs__namespace.unlinkSync(tmpOut);
3120
+ } catch {
3121
+ }
3122
+ }
3123
+ } else {
3124
+ console.log(`[probe-source] STATUS: PROBE_FAIL`);
3125
+ if (errorMsg) console.log(`[probe-source] error: ${errorMsg}`);
3126
+ try {
3127
+ if (fs__namespace.existsSync(tmpOut)) fs__namespace.unlinkSync(tmpOut);
3128
+ } catch {
3129
+ }
3130
+ }
3131
+ const summary = {
3132
+ status: passed ? "PROBE_PASS" : "PROBE_FAIL",
3133
+ cardId: card.id,
3134
+ sourceIdx,
3135
+ bindTo: sourceDef.bindTo,
3136
+ sourceKind,
3137
+ mockRequiresKeys: Object.keys(mockRequires),
3138
+ resultSizeBytes: resultRaw !== void 0 ? resultRaw.length : 0,
3139
+ error: errorMsg ?? void 0
3140
+ };
3141
+ console.log(`[probe-source:result] ${JSON.stringify(summary)}`);
3142
+ process.exit(passed ? 0 : 1);
3143
+ }
2593
3144
  function cmdHelp() {
2594
3145
  console.log(`
2595
3146
  board-live-cards-cli \u2014 LiveCards board CLI
@@ -2598,14 +3149,16 @@ USAGE
2598
3149
  board-live-cards-cli <command> [options]
2599
3150
 
2600
3151
  BOARD MANAGEMENT
2601
- init <dir> [--task-executor <script>] [--runtime-out <dir>]
3152
+ init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]
2602
3153
  Create a new board in <dir>.
2603
3154
  If --task-executor is given, writes <dir>/.task-executor with the script path.
3155
+ If --chat-handler is given, writes <dir>/.chat-handler with the script path.
3156
+ If --inference-adapter is given, writes <dir>/.inference-adapter with the script path.
2604
3157
  Writes <dir>/.runtime-out (default: <dir>/runtime-out).
2605
3158
  Published runtime files:
2606
3159
  <runtime-out>/board-livegraph-status.json
2607
3160
  <runtime-out>/cards/<card-id>.computed.json
2608
- Re-running init on an existing board is safe; --task-executor updates the registration.
3161
+ Re-running init on an existing board is safe; handler registrations are updated.
2609
3162
 
2610
3163
  status --rg <dir> [--json]
2611
3164
  Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
@@ -2645,13 +3198,16 @@ TASK CALLBACKS (called by task executor scripts)
2645
3198
  task-failed --token <callbackToken> [--error <message>]
2646
3199
  Signal task failure with an optional error message.
2647
3200
 
3201
+ task-progress --rg <dir> --token <callbackToken> [--update <json>]
3202
+ Signal task progress with optional update payload (for waiting on more evidence, etc.).
3203
+
2648
3204
  SOURCE CALLBACKS (called internally by run-sources-internal)
2649
3205
  source-data-fetched --tmp <file> --token <sourceToken>
2650
3206
  Atomically rename <file> into the outputFile destination and record delivery
2651
- in runtime.json. Appends a task-progress event to re-invoke the card handler.
3207
+ via journal events. Appends a task-progress event to re-invoke the card handler.
2652
3208
 
2653
3209
  source-data-fetch-failure --token <sourceToken> [--reason <message>]
2654
- Record a source fetch failure in runtime.json and append a task-progress event.
3210
+ Record a source fetch failure via journal events and append a task-progress event.
2655
3211
 
2656
3212
  INTERNAL COMMANDS
2657
3213
  process-accumulated-events --rg <dir>
@@ -2666,7 +3222,7 @@ INTERNAL COMMANDS
2666
3222
  3) lock stays healthy,
2667
3223
  4) event production eventually quiesces.
2668
3224
 
2669
- run-sources-internal-internal --card <card.json> --token <callbackToken> --rg <dir>
3225
+ run-sources-internal --card <card.json> --token <callbackToken> --rg <dir>
2670
3226
  Execute all source[] entries for a card, then report delivery or failure.
2671
3227
  (Internal command \u2014 invoked by the card-handler. Not intended for direct use.)
2672
3228
 
@@ -2679,6 +3235,34 @@ INTERNAL COMMANDS
2679
3235
  Execute a source definition. Board-live-cards reads source.cli and executes it.
2680
3236
  Writes result to --out. Presence of --out after exit indicates success.
2681
3237
 
3238
+ probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>]
3239
+ [--mock-requires <json>] [--rg <boardDir>] [--out <result.json>]
3240
+ Validate that a card source can be fetched successfully.
3241
+ Reads the card file, extracts the chosen source (default: index 0), builds the
3242
+ run-source-fetch --in payload with the supplied _requires data, invokes the
3243
+ registered task-executor (or built-in executor for source.cli), and reports pass/fail.
3244
+ --mock-requires: JSON string (or @file.json) providing the _requires token values
3245
+ the source needs. Craft the minimal payload that exercises the
3246
+ source \u2014 e.g. '{"holdings":[{"ticker":"AAPL","quantity":10}]}'.
3247
+ If omitted, _requires is passed as empty ({}).
3248
+ --source-idx: 0-based index into card.sources[]. Default: 0.
3249
+ --source-bind: Select source by its bindTo name instead of index.
3250
+ --rg: Board directory used to find .task-executor. Defaults to the
3251
+ directory containing the card file.
3252
+ --out: Optional path to write the raw fetch result JSON.
3253
+ Prints a structured report ending with a [probe-source:result] JSON line.
3254
+ Exits 0 on PROBE_PASS, 1 on PROBE_FAIL.
3255
+
3256
+ run-inference-internal --in <input.json> --token <inferenceToken>
3257
+ Execute inference via registered .inference-adapter and forward result to inference-done.
3258
+ inferenceToken encodes boardDir (rg), cardId (cid), callbackToken (cbk), checksum (cs).
3259
+ (Internal command \u2014 invoked by the card-handler when custom completion rule is used.)
3260
+
3261
+ inference-done --tmp <result.json> --token <inferenceToken>
3262
+ Persist llm_task_completion_inference on the card and append a task-progress event.
3263
+ Reads boardDir/callbackToken/checksum from decoded inferenceToken; deletes --tmp file after reading.
3264
+ (Internal command \u2014 invoked by run-inference-internal.)
3265
+
2682
3266
  RUN-SOURCE-FETCH PROTOCOL
2683
3267
  External task-executors implement:
2684
3268
  <executor> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
@@ -2715,6 +3299,7 @@ EXAMPLES
2715
3299
  board-live-cards-cli add-cards --rg ./my-board --card cards/prices.json
2716
3300
  board-live-cards-cli status --rg ./my-board
2717
3301
  board-live-cards-cli retrigger --rg ./my-board --task price-fetch
3302
+ 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}]}'
2718
3303
  `.trimStart());
2719
3304
  }
2720
3305
  var isMain = process.argv[1] && path__namespace.resolve(process.argv[1]) === path__namespace.resolve(new URL((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('board-live-cards-cli.cjs', document.baseURI).href))).pathname.replace(/^\/([A-Z]:)/, "$1"));
@@ -2732,14 +3317,18 @@ exports.appendEventToJournal = appendEventToJournal;
2732
3317
  exports.buildCardInventoryIndex = buildCardInventoryIndex;
2733
3318
  exports.cli = cli;
2734
3319
  exports.createBoardReactiveGraph = createBoardReactiveGraph;
3320
+ exports.decideSourceAction = decideSourceAction;
2735
3321
  exports.decodeSourceToken = decodeSourceToken;
2736
3322
  exports.encodeSourceToken = encodeSourceToken;
2737
3323
  exports.getUndrainedEntries = getUndrainedEntries;
2738
3324
  exports.initBoard = initBoard;
3325
+ exports.isSourceInFlight = isSourceInFlight;
2739
3326
  exports.liveCardToTaskConfig = liveCardToTaskConfig;
2740
3327
  exports.loadBoard = loadBoard;
2741
3328
  exports.loadBoardEnvelope = loadBoardEnvelope;
2742
3329
  exports.lookupCardPath = lookupCardPath;
3330
+ exports.nextEntryAfterFetchDelivery = nextEntryAfterFetchDelivery;
3331
+ exports.nextEntryAfterFetchFailure = nextEntryAfterFetchFailure;
2743
3332
  exports.processAccumulatedEvents = processAccumulatedEvents;
2744
3333
  exports.processAccumulatedEventsForced = processAccumulatedEventsForced;
2745
3334
  exports.processAccumulatedEventsInfinitePass = processAccumulatedEventsInfinitePass;