yaml-flow 8.1.1 → 8.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 (108) hide show
  1. package/browser/asset-integrity.json +3 -3
  2. package/browser/board-livecards-client.js +1 -1
  3. package/browser/board-livecards-localstorage.js +4 -6
  4. package/cli/{board-live-cards-lib-tjYsPt5U.d.ts → board-live-cards-lib-Iq_XAC09.d.ts} +1 -1
  5. package/cli/browser-api/board-live-cards-browser-adapter.d.ts +4 -3
  6. package/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
  7. package/cli/browser-api/card-store-browser-api.d.ts +1 -1
  8. package/cli/node/artifacts-store-cli.js +8 -8
  9. package/cli/node/board-live-cards-cli.js +8 -8
  10. package/cli/node/card-store-cli.js +4 -4
  11. package/cli/node/fs-board-adapter.d.ts +6 -33
  12. package/cli/node/fs-board-adapter.js +10 -8
  13. package/cli/node/step-machine-cli.js +3 -3
  14. package/cli/{types-D2XnLbBj.d.ts → types--rXGWbSR.d.ts} +77 -5
  15. package/examples/board/.board-ws/cards/store/_index.json +17 -0
  16. package/examples/board/.board-ws/cards/store/card-market-prices.json +80 -0
  17. package/examples/board/.board-ws/cards/store/card-portfolio-value.json +90 -0
  18. package/examples/board/.board-ws/cards/store/card-portfolio.json +78 -0
  19. package/examples/board/cards/cardT-market-prices.json +6 -4
  20. package/examples/board/cards/cardT-portfolio-value.json +10 -38
  21. package/examples/board/cards/cardT-portfolio.json +9 -4
  22. package/examples/board/demo-shell-with-server.html +3 -3
  23. package/examples/board/server/board-server.js +593 -0
  24. package/examples/board/server/board-worker/source-def-flows/mock-handler/mock-db.js +13 -0
  25. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/compliance.db +0 -0
  26. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/optimus.db +0 -0
  27. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/query.cjs +51 -0
  28. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpm.cjs +197 -0
  29. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpmV2.cjs +128 -0
  30. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-optimus.cjs +352 -0
  31. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-config.json +3 -0
  32. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-handler.js +84 -0
  33. package/examples/board/{source-def-flows/url.flow.json → server/board-worker/source-def-flows/sqlite.flow.json} +7 -7
  34. package/examples/board/{source-def-handlers → server/board-worker/source-def-flows/url-handler}/http-source-handler.js +29 -21
  35. package/examples/board/server/board-worker/source-def-flows/url.flow.json +73 -0
  36. package/examples/board/{source_def_flows.json → server/board-worker/source_def_flows.json} +61 -115
  37. package/examples/board/server/board-worker/task-executor.js +475 -0
  38. package/examples/board/server/chat-flow/chat-clear-processing.js +41 -0
  39. package/examples/board/server/chat-flow/chat-open-turn.js +144 -0
  40. package/examples/board/server/chat-flow/chat-write-assistant.js +44 -0
  41. package/examples/board/server/chat-flow/copilot-chat/assistant.js +253 -0
  42. package/examples/board/server/chat-flow/echo-probe/assistant.js +28 -0
  43. package/examples/board/server/chat-flow/flow-steps.json +167 -0
  44. package/examples/board/server-config.json +22 -0
  45. package/examples/board/test/server-http-test.js +707 -0
  46. package/examples/board/test/{portfolio-tracker-sse-worker.js → sse-worker.js} +9 -8
  47. package/examples/board-local/demo-shell-localstorage.html +3 -3
  48. package/lib/{artifacts-store-lib-public-DBICnGL6.d.cts → artifacts-store-lib-public-C5UL5tyG.d.cts} +3 -31
  49. package/lib/{artifacts-store-lib-public-BWC3YuLa.d.ts → artifacts-store-lib-public-GD4H-fFp.d.ts} +3 -31
  50. package/lib/artifacts-store-public.d.cts +3 -3
  51. package/lib/artifacts-store-public.d.ts +3 -3
  52. package/lib/board-live-cards-node.cjs +10 -8
  53. package/lib/board-live-cards-node.d.cts +9 -8
  54. package/lib/board-live-cards-node.d.ts +9 -8
  55. package/lib/board-live-cards-node.js +10 -8
  56. package/lib/{board-live-cards-public-BF9FP0mL.d.cts → board-live-cards-public-BLXbcBNk.d.cts} +2 -2
  57. package/lib/{board-live-cards-public-dJAl5IL-.d.ts → board-live-cards-public-BZaNb2mi.d.ts} +2 -2
  58. package/lib/board-live-cards-public.cjs +2 -2
  59. package/lib/board-live-cards-public.d.cts +2 -2
  60. package/lib/board-live-cards-public.d.ts +2 -2
  61. package/lib/board-live-cards-public.js +2 -2
  62. package/lib/board-live-cards-server-runtime.cjs +4 -6
  63. package/lib/board-live-cards-server-runtime.d.cts +3 -3
  64. package/lib/board-live-cards-server-runtime.d.ts +3 -3
  65. package/lib/board-live-cards-server-runtime.js +4 -6
  66. package/lib/board-livegraph-runtime/index.cjs +2 -2
  67. package/lib/board-livegraph-runtime/index.js +2 -2
  68. package/lib/card-store-public.d.cts +2 -2
  69. package/lib/card-store-public.d.ts +2 -2
  70. package/lib/execution-refs.cjs +1 -1
  71. package/lib/execution-refs.js +1 -1
  72. package/lib/index.cjs +1 -1
  73. package/lib/index.d.cts +1 -1
  74. package/lib/index.d.ts +1 -1
  75. package/lib/index.js +1 -1
  76. package/lib/server-runtime/index.cjs +4 -6
  77. package/lib/server-runtime/index.d.cts +4 -4
  78. package/lib/server-runtime/index.d.ts +4 -4
  79. package/lib/server-runtime/index.js +4 -6
  80. package/lib/step-machine-public/index.cjs +3 -3
  81. package/lib/step-machine-public/index.d.cts +27 -10
  82. package/lib/step-machine-public/index.d.ts +27 -10
  83. package/lib/step-machine-public/index.js +3 -3
  84. package/lib/{storage-interface-BhAON-gW.d.ts → storage-interface-B6ecOulj.d.cts} +25 -3
  85. package/lib/{storage-interface-BhAON-gW.d.cts → storage-interface-B6ecOulj.d.ts} +25 -3
  86. package/lib/stores/index.d.cts +1 -1
  87. package/lib/stores/index.d.ts +1 -1
  88. package/lib/stores/kv.d.cts +1 -1
  89. package/lib/stores/kv.d.ts +1 -1
  90. package/lib/{types-CXBzvC0s.d.cts → types-Bztd1KoK.d.cts} +75 -3
  91. package/lib/{types-D48hpnTR.d.ts → types-D-xVWPdY.d.ts} +75 -3
  92. package/package.json +1 -1
  93. package/examples/board/demo-chat-handler.js +0 -169
  94. package/examples/board/demo-server-config.json +0 -10
  95. package/examples/board/demo-server.js +0 -580
  96. package/examples/board/demo-task-executor.js +0 -721
  97. package/examples/board/gandalf-cards/card-source-kinds.json +0 -36
  98. package/examples/board/gandalf-cards/cards/_index.json +0 -7
  99. package/examples/board/gandalf-cards/cards/card-source-kinds.json +0 -64
  100. package/examples/board/scripts/copilot_wrapper.bat +0 -157
  101. package/examples/board/scripts/copilot_wrapper_helper.ps1 +0 -190
  102. package/examples/board/scripts/workiq_wrapper.mjs +0 -66
  103. package/examples/board/source-def-flows/copilot.flow.json +0 -33
  104. package/examples/board/source-def-flows/url-list.flow.json +0 -33
  105. package/examples/board/source-def-flows/workiq.flow.json +0 -34
  106. package/examples/board/source-def-handlers/copilot-source-handler.js +0 -141
  107. package/examples/board/test/demo-http-test.js +0 -317
  108. /package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json +0 -0
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * task-executor.js — Registry-driven source executor for the demo board worker.
5
+ *
6
+ * Supported subcommands:
7
+ * run-source-fetch — execute one source_def via its registered flow
8
+ * describe-capabilities — emit registry-derived kinds, schemas, and probe info as JSON
9
+ * validate-source-def — validate one source_def against registry rules from stdin
10
+ * probe-source-preflight — perform a lightweight preflight for one source_def from stdin
11
+ *
12
+ * Runtime invocation shapes used by board-live-cards:
13
+ * run-source-fetch --in-ref <b64ref> --out-ref <b64ref> [--err-ref <b64ref>] [--extra <base64json>]
14
+ * validate-source-def <stdin: source JSON>
15
+ * probe-source-preflight <stdin: source JSON> [--extra <base64json>]
16
+ *
17
+ * Payload read from --in-ref (raw source definition or { source_def, callback } envelope):
18
+ * {
19
+ * "bindTo": "token_name",
20
+ * "outputFile": "relative/path.json",
21
+ * "_projections": { "refKey": <resolvedValue> },
22
+ * // ...plus the authored fields used by the resolved registry kind
23
+ * }
24
+ *
25
+ * Output written to --out-ref:
26
+ * - JSON result produced by the resolved source flow when the flow does not
27
+ * write directly to the destination blob itself.
28
+ *
29
+ * Error text written to --err-ref when provided:
30
+ * - Plain-text failure details for fetch-time errors before process exit.
31
+ *
32
+ * Decoded --extra context (optional; schema comes from source_def_flows.json.extraSchema):
33
+ * {
34
+ * "boardSetupRoot": "<abs path>",
35
+ * "boardId": "<board id>",
36
+ * "boardRuntimeDir": "<relative>",
37
+ * "runtimeStatusDir": "<relative>",
38
+ * "cardsDir": "<relative>",
39
+ * "serverUrl": "<base url>",
40
+ * "boardLiveCardsCliJs": "<abs path>",
41
+ * "stepMachineCliPath": "<abs path>"
42
+ * }
43
+ *
44
+ * Source kinds are resolved from source_def_flows.json in resolveOrder.
45
+ * See that registry for the current set of advertised kinds and capabilities.
46
+ * Flow execution is generic here; kind-specific behavior lives in the registry
47
+ * and in per-kind flow/handler modules under source-def-flows/.
48
+ */
49
+
50
+ import fs from 'node:fs';
51
+ import path from 'node:path';
52
+ import { fileURLToPath, pathToFileURL } from 'node:url';
53
+ import { parseRef, blobStorageForRef, reportComplete, reportFailed } from 'yaml-flow/board-worker-adapter';
54
+ import { loadStepFlow, createStepMachine, MemoryStore, buildStepHandlersForFlow } from 'yaml-flow/step-machine-public';
55
+ import { invokeExecutionRef } from 'yaml-flow/board-live-cards-node';
56
+
57
+ const WORKER_DIR = path.dirname(fileURLToPath(import.meta.url));
58
+ const PROJECT_ROOT = path.resolve(WORKER_DIR, '..', '..');
59
+ const SOURCE_DEF_FLOWS_FILE = path.join(WORKER_DIR, 'source_def_flows.json');
60
+ const EXECUTOR_NAME = 'board-worker';
61
+ const LOG_PREFIX = `[${EXECUTOR_NAME}]`;
62
+
63
+ function readJson(filePath) {
64
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
65
+ }
66
+
67
+ function fail(msg, errFile) {
68
+ if (errFile) {
69
+ try {
70
+ fs.writeFileSync(errFile, msg);
71
+ } catch {}
72
+ }
73
+ console.error(`${LOG_PREFIX} ${msg}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ function loadSourceDefFlowsConfig() {
78
+ try {
79
+ return readJson(SOURCE_DEF_FLOWS_FILE);
80
+ } catch (err) {
81
+ fail(
82
+ `Cannot read source flow registry (${SOURCE_DEF_FLOWS_FILE}): ${String(err && err.message || err)}`,
83
+ );
84
+ }
85
+ }
86
+
87
+ function matchesDetectRule(sourceDef, detect) {
88
+ if (!detect || typeof detect !== 'object') return false;
89
+ if (typeof detect.field === 'string') {
90
+ return sourceDef[detect.field] !== undefined;
91
+ }
92
+ if (Array.isArray(detect.anyOfFields)) {
93
+ return detect.anyOfFields.some((field) => sourceDef[field] !== undefined);
94
+ }
95
+ return false;
96
+ }
97
+
98
+ function resolveSourceKind(sourceDef, registry) {
99
+ const kinds = registry?.kinds && typeof registry.kinds === 'object' ? registry.kinds : {};
100
+ const order = Array.isArray(registry?.resolveOrder) ? registry.resolveOrder : Object.keys(kinds);
101
+ const matched = [];
102
+ for (const kind of order) {
103
+ const spec = kinds[kind];
104
+ if (!spec) continue;
105
+ if (matchesDetectRule(sourceDef, spec.detect)) {
106
+ matched.push(kind);
107
+ }
108
+ }
109
+
110
+ if (matched.length === 0) {
111
+ const knownKinds = Object.keys(kinds);
112
+ throw new Error(`No recognised source kind. Known kinds: ${knownKinds.join(', ')}`);
113
+ }
114
+ if (matched.length > 1) {
115
+ throw new Error(`Multiple source kinds specified: [${matched.join(', ')}]. Use exactly one.`);
116
+ }
117
+ return matched[0];
118
+ }
119
+
120
+ async function executeStepMachineSourceFlow(context) {
121
+ const { kind, registry } = context;
122
+ const spec = registry?.kinds?.[kind];
123
+ if (!spec) {
124
+ throw new Error(`Missing flow registration for kind: ${kind}`);
125
+ }
126
+
127
+ const flowRef = spec.flow;
128
+ if (typeof flowRef !== 'string' || flowRef.length === 0) {
129
+ throw new Error(`Invalid or missing flow for kind: ${kind}`);
130
+ }
131
+
132
+ const flowPath = path.resolve(WORKER_DIR, flowRef);
133
+ const flow = await loadStepFlow(flowPath);
134
+
135
+ const invoke = async (ref, args) => {
136
+ if (ref.howToRun === 'demo-local-module') {
137
+ const whatValue = typeof ref.whatToRun === 'object' ? ref.whatToRun.value : parseRef(ref.whatToRun).value;
138
+ const modulePath = path.resolve(WORKER_DIR, whatValue);
139
+ const mod = await import(pathToFileURL(modulePath).href);
140
+ if (typeof mod.execute !== 'function') {
141
+ throw new Error(`Flow module ${JSON.stringify(ref.whatToRun)} must export execute(context)`);
142
+ }
143
+ return mod.execute(args);
144
+ }
145
+ return invokeExecutionRef(ref, args, { cliDir: PROJECT_ROOT, cwd: process.cwd() });
146
+ };
147
+
148
+ const handlers = buildStepHandlersForFlow(flow, { invoke });
149
+ const machine = createStepMachine(flow, handlers, { store: new MemoryStore() });
150
+ const run = await machine.run({
151
+ ...context,
152
+ extra: context.extra ?? {},
153
+ serverUrl: context.extra?.serverUrl ?? null,
154
+ executorDir: PROJECT_ROOT,
155
+ });
156
+
157
+ if (run.status !== 'completed') {
158
+ const reason = run.error?.message ?? run.intent ?? run.status;
159
+ throw new Error(`flow execution failed: ${reason}`);
160
+ }
161
+
162
+ if (run.intent !== 'success') {
163
+ const reason = typeof run.data?.error === 'string' ? run.data.error : `flow returned intent: ${run.intent}`;
164
+ throw new Error(reason);
165
+ }
166
+
167
+ return {
168
+ resultValue: run.data?.resultValue,
169
+ wroteOutputDirectly: !!run.data?.wroteOutputDirectly,
170
+ };
171
+ }
172
+
173
+ async function resolveAndExecuteSourceFlow(sourceDef, extra, refs = {}) {
174
+ const registry = loadSourceDefFlowsConfig();
175
+ const kind = resolveSourceKind(sourceDef, registry);
176
+ const mockDb = kind === 'mock'
177
+ ? (await import(pathToFileURL(path.join(WORKER_DIR, 'source-def-flows', 'mock-handler', 'mock-db.js')).href)).MOCK_DB
178
+ : undefined;
179
+ const flowResult = await executeStepMachineSourceFlow({
180
+ kind,
181
+ registry,
182
+ sourceDef,
183
+ extra,
184
+ inRef: refs.inRef,
185
+ outRef: refs.outRef,
186
+ errRef: refs.errRef,
187
+ ...(mockDb ? { mockDb } : {}),
188
+ });
189
+ return { kind, flowResult };
190
+ }
191
+
192
+ async function runSourceFetchSubcommand(argv) {
193
+ const inIdx = argv.indexOf('--in-ref');
194
+ const outIdx = argv.indexOf('--out-ref');
195
+ const errIdx = argv.indexOf('--err-ref');
196
+ const extraIdx = argv.indexOf('--extra');
197
+ const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;
198
+ const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;
199
+ const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;
200
+ const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
201
+
202
+ let extra = {};
203
+ if (extraB64) {
204
+ try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
205
+ catch { console.warn(`${LOG_PREFIX} bad --extra base64, ignoring`); }
206
+ }
207
+
208
+ if (!inRefStr || !outRefStr) {
209
+ fail('Usage: run-source-fetch --in-ref <b64:<base64url(json)>> --out-ref <b64:<base64url(json)>> [--err-ref <b64:<base64url(json)>>]');
210
+ }
211
+
212
+ let inRef, outRef, errRef;
213
+ try {
214
+ inRef = parseRef(inRefStr);
215
+ outRef = parseRef(outRefStr);
216
+ if (errRefStr) errRef = parseRef(errRefStr);
217
+ } catch (e) {
218
+ fail(`invalid ref argument: ${e.message}`);
219
+ }
220
+
221
+ const inStorage = blobStorageForRef(inRef);
222
+ const outStorage = blobStorageForRef(outRef);
223
+ const errStorage = errRef ? blobStorageForRef(errRef) : undefined;
224
+
225
+ // Local error reporter — writes to errStorage and calls back to board if callback present.
226
+ const failRef = (msg, callback) => {
227
+ if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch {} }
228
+ console.error(`${LOG_PREFIX} ${msg}`);
229
+ if (callback) { try { reportFailed(callback, msg); } catch {} }
230
+ process.exit(1);
231
+ };
232
+
233
+ const rawIn = inStorage.read(inRef.value);
234
+ if (rawIn === null) {
235
+ failRef(`Input not found: ${inRefStr}`);
236
+ }
237
+
238
+ // Payload may be { source_def, callback } (new protocol) or raw source def (legacy).
239
+ let envelope;
240
+ try {
241
+ envelope = JSON.parse(rawIn);
242
+ } catch (err) {
243
+ failRef(`Cannot parse input: ${String(err && err.message || err)}`);
244
+ }
245
+
246
+ const callback = envelope.source_def ? envelope.callback : undefined;
247
+ let sourceDef;
248
+ try {
249
+ sourceDef = envelope.source_def ?? envelope;
250
+ } catch (err) {
251
+ failRef(`Cannot resolve source_def: ${String(err && err.message || err)}`, callback);
252
+ }
253
+
254
+ let kind;
255
+ let flowResult;
256
+ try {
257
+ const resolved = await resolveAndExecuteSourceFlow(sourceDef, extra, { inRef, outRef, errRef });
258
+ kind = resolved.kind;
259
+ flowResult = resolved.flowResult;
260
+ } catch (err) {
261
+ const detail = (err && (err.stderr || err.stdout)) ? `\n${err.stderr || err.stdout}`.trimEnd() : '';
262
+ failRef(`source invocation failed: ${String(err && err.message || err)}${detail}`, callback);
263
+ }
264
+
265
+ if (!flowResult?.wroteOutputDirectly) {
266
+ try {
267
+ outStorage.write(outRef.value, JSON.stringify(flowResult?.resultValue, null, 2));
268
+ } catch (err) {
269
+ failRef(`Cannot write output: ${String(err && err.message || err)}`, callback);
270
+ }
271
+ }
272
+
273
+ if (callback) {
274
+ try {
275
+ reportComplete(callback, outRef);
276
+ } catch (err) {
277
+ console.error(`${LOG_PREFIX} reportComplete failed: ${String(err && err.message || err)}`);
278
+ process.exit(1);
279
+ }
280
+ }
281
+
282
+ }
283
+
284
+ async function probeSourcePreflightSubcommand(argv) {
285
+ const extraIdx = argv.indexOf('--extra');
286
+ const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
287
+
288
+ let extra = {};
289
+ if (extraB64) {
290
+ try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
291
+ catch { /* ignore malformed extra */ }
292
+ }
293
+
294
+ const startedAt = Date.now();
295
+ try {
296
+ const chunks = [];
297
+ for await (const chunk of process.stdin) {
298
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
299
+ }
300
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
301
+ if (!raw) {
302
+ console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: 'Missing probe input JSON on stdin' }));
303
+ return;
304
+ }
305
+
306
+ let sourceDef;
307
+ try {
308
+ sourceDef = JSON.parse(raw);
309
+ } catch (err) {
310
+ console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: `Invalid probe JSON: ${String(err && err.message || err)}` }));
311
+ return;
312
+ }
313
+
314
+ const projections = sourceDef?._projections;
315
+ const mockProjectionsMissing = !projections || typeof projections !== 'object' || Array.isArray(projections) || Object.keys(projections).length === 0;
316
+ const mockProjectionWarning = mockProjectionsMissing
317
+ ? 'Mock projections / _projections missing. Hence mock run not performed.'
318
+ : undefined;
319
+
320
+ const { flowResult } = await resolveAndExecuteSourceFlow(sourceDef, extra);
321
+ console.log(JSON.stringify({
322
+ ok: true,
323
+ reachable: true,
324
+ latencyMs: Date.now() - startedAt,
325
+ ...(mockProjectionWarning ? { error: mockProjectionWarning } : {}),
326
+ ...(!mockProjectionWarning ? { resultValue: flowResult?.resultValue } : {}),
327
+ }));
328
+ return;
329
+ } catch (err) {
330
+ const detail = (err && (err.stderr || err.stdout)) ? `\n${err.stderr || err.stdout}`.trimEnd() : '';
331
+ console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: `source invocation failed: ${String(err && err.message || err)}${detail}` }));
332
+ return;
333
+ }
334
+ }
335
+
336
+ function getByPath(obj, dottedPath) {
337
+ if (!dottedPath) return undefined;
338
+ return String(dottedPath).split('.').reduce((acc, key) => {
339
+ if (acc == null || typeof acc !== 'object') return undefined;
340
+ return acc[key];
341
+ }, obj);
342
+ }
343
+
344
+ function checkType(value, expectedType) {
345
+ if (expectedType === 'array') return Array.isArray(value);
346
+ if (expectedType === 'object') return value != null && typeof value === 'object' && !Array.isArray(value);
347
+ return typeof value === expectedType;
348
+ }
349
+
350
+ function matchesValidateRule(sourceDef, rule) {
351
+ if (!rule || typeof rule !== 'object') return true;
352
+
353
+ if (Array.isArray(rule.anyOf)) {
354
+ return rule.anyOf.some((entry) => matchesValidateRule(sourceDef, entry));
355
+ }
356
+
357
+ if (Array.isArray(rule.allOf)) {
358
+ return rule.allOf.every((entry) => matchesValidateRule(sourceDef, entry));
359
+ }
360
+
361
+ if (rule.not && typeof rule.not === 'object') {
362
+ return !matchesValidateRule(sourceDef, rule.not);
363
+ }
364
+
365
+ if (typeof rule.field === 'string' && typeof rule.type === 'string') {
366
+ const value = getByPath(sourceDef, rule.field);
367
+ return checkType(value, rule.type);
368
+ }
369
+
370
+ return true;
371
+ }
372
+
373
+ // ---------------------------------------------------------------------------
374
+ // validate-source-def — registry-driven validation of a source definition
375
+ // ---------------------------------------------------------------------------
376
+ function validateSourceDefSubcommand() {
377
+ let rawInput = '';
378
+ try {
379
+ rawInput = fs.readFileSync(0, 'utf-8').trim();
380
+ } catch (err) {
381
+ console.log(JSON.stringify({ ok: false, errors: [`Cannot read stdin: ${err && err.message || err}`] }));
382
+ process.exit(1);
383
+ }
384
+
385
+ if (!rawInput) {
386
+ console.error(`${LOG_PREFIX} Usage: validate-source-def < source.json`);
387
+ process.exit(1);
388
+ }
389
+
390
+ let sourceDef;
391
+ try {
392
+ sourceDef = JSON.parse(rawInput);
393
+ } catch (err) {
394
+ console.log(JSON.stringify({ ok: false, errors: [`Cannot parse source JSON from stdin: ${err && err.message || err}`] }));
395
+ process.exit(1);
396
+ }
397
+
398
+ const errors = [];
399
+ const registry = loadSourceDefFlowsConfig();
400
+
401
+ let kind = '';
402
+ try {
403
+ kind = resolveSourceKind(sourceDef, registry);
404
+ } catch (err) {
405
+ errors.push(String(err && err.message || err));
406
+ }
407
+
408
+ const kindSpec = registry?.kinds?.[kind];
409
+ const validateRules = Array.isArray(kindSpec?.validate) ? kindSpec.validate : [];
410
+ for (const rule of validateRules) {
411
+ if (!rule || typeof rule !== 'object') continue;
412
+ if (!matchesValidateRule(sourceDef, rule)) {
413
+ if (typeof rule.field === 'string' && typeof rule.type === 'string') {
414
+ errors.push(rule.message || `${rule.field} must be of type ${rule.type}.`);
415
+ } else {
416
+ errors.push(rule.message || 'Validation rule failed.');
417
+ }
418
+ }
419
+ }
420
+
421
+ const result = { ok: errors.length === 0, errors };
422
+ console.log(JSON.stringify(result));
423
+ process.exit(errors.length === 0 ? 0 : 1);
424
+ }
425
+
426
+ function describeCapabilities() {
427
+ const registry = loadSourceDefFlowsConfig();
428
+ const sourceKinds = Object.fromEntries(
429
+ Object.entries(registry?.kinds || {}).map(([kind, spec]) => [
430
+ kind,
431
+ {
432
+ ...(spec?.manifest && typeof spec.manifest === 'object' ? spec.manifest : {}),
433
+ ...(spec?.probe && typeof spec.probe === 'object' ? { probe: spec.probe } : {}),
434
+ },
435
+ ]),
436
+ );
437
+ const payload = {
438
+ version: registry?.version || '1.0',
439
+ executor: registry?.executor || EXECUTOR_NAME,
440
+ subcommands: Array.isArray(registry?.subcommands)
441
+ ? registry.subcommands
442
+ : ['run-source-fetch', 'probe-source-preflight', 'describe-capabilities', 'validate-source-def'],
443
+ sourceKinds,
444
+ ...(registry?.extraSchema ? { extraSchema: registry.extraSchema } : {}),
445
+ };
446
+ console.log(JSON.stringify(payload, null, 2));
447
+ }
448
+
449
+ async function main() {
450
+ const sub = process.argv[2];
451
+ if (sub === 'run-source-fetch') {
452
+ await runSourceFetchSubcommand(process.argv.slice(3));
453
+ return;
454
+ }
455
+ if (sub === 'probe-source-preflight') {
456
+ await probeSourcePreflightSubcommand(process.argv.slice(3));
457
+ return;
458
+ }
459
+ if (sub === 'describe' || sub === 'describe-capabilities') {
460
+ describeCapabilities();
461
+ return;
462
+ }
463
+ if (sub === 'validate-source-def') {
464
+ validateSourceDefSubcommand();
465
+ return;
466
+ }
467
+
468
+ console.warn(`${LOG_PREFIX} Unknown subcommand: ${sub}`);
469
+ process.exit(0);
470
+ }
471
+
472
+ main().catch(err => {
473
+ console.error(`${LOG_PREFIX} fatal: ${err && err.message || err}`);
474
+ process.exit(1);
475
+ });
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import { createFsBoardChatStorage } from 'yaml-flow/board-live-cards-node';
6
+
7
+ function readJsonStdin() {
8
+ try {
9
+ const raw = fs.readFileSync(0, 'utf-8').trim();
10
+ if (!raw) return {};
11
+ const parsed = JSON.parse(raw);
12
+ return parsed && typeof parsed === 'object' ? parsed : {};
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+
18
+ const input = readJsonStdin();
19
+ const boardSetupRoot = typeof input.boardSetupRoot === 'string' ? input.boardSetupRoot : '';
20
+ const boardRuntimeDir = typeof input.boardRuntimeDir === 'string' ? input.boardRuntimeDir : 'runtime';
21
+ const cardId = typeof input.cardId === 'string' ? input.cardId : '';
22
+
23
+ try {
24
+ if (!boardSetupRoot || !cardId) {
25
+ process.stderr.write('chat-clear-processing requires boardSetupRoot and cardId\n');
26
+ process.exit(1);
27
+ }
28
+
29
+ const boardDir = path.join(boardSetupRoot, boardRuntimeDir || 'runtime');
30
+ if (!fs.existsSync(boardDir)) {
31
+ process.stdout.write(JSON.stringify({ cleared: true, skipped: true }));
32
+ process.exit(0);
33
+ }
34
+
35
+ const chatStorage = createFsBoardChatStorage(boardDir);
36
+ chatStorage.setProcessing(cardId, false);
37
+ process.stdout.write(JSON.stringify({ cleared: true }));
38
+ } catch (err) {
39
+ process.stderr.write((err instanceof Error ? err.message : String(err)) + '\n');
40
+ process.exit(1);
41
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+
5
+ function readJsonStdin() {
6
+ try {
7
+ const raw = fs.readFileSync(0, 'utf-8').trim();
8
+ if (!raw) return {};
9
+ const parsed = JSON.parse(raw);
10
+ return parsed && typeof parsed === 'object' ? parsed : {};
11
+ } catch {
12
+ return {};
13
+ }
14
+ }
15
+
16
+ function parseBoolean(value, fallback = false) {
17
+ if (value === true || value === 1) return true;
18
+ if (value === false || value === 0) return false;
19
+ if (typeof value === 'string') {
20
+ const normalized = value.trim().toLowerCase();
21
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
22
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
23
+ }
24
+ return fallback;
25
+ }
26
+
27
+ function parsePositiveInt(value, fallback = null) {
28
+ const n = Number(value);
29
+ if (!Number.isFinite(n) || n <= 0) return fallback;
30
+ return Math.floor(n);
31
+ }
32
+
33
+ function parseChatEnvelope(raw) {
34
+ // Chat payload supports either plain text or a JSON envelope.
35
+ // JSON fields: prompt|text|userText|query, probe, chatTimeoutMs|chatCopilotTimeoutMs, chatTimeMs.
36
+ if (!raw) {
37
+ return {
38
+ userText: '',
39
+ probe: false,
40
+ chatHandlerMode: 'copilot',
41
+ chatCopilotTimeoutMs: null,
42
+ chatTimeMs: null,
43
+ };
44
+ }
45
+
46
+ try {
47
+ const parsed = JSON.parse(raw);
48
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
49
+ throw new Error('not-an-object');
50
+ }
51
+ const prompt = [parsed.prompt, parsed.text, parsed.userText, parsed.query]
52
+ .find((value) => typeof value === 'string' && value.trim().length > 0);
53
+ const probe = parseBoolean(parsed.probe, false);
54
+ const modeFromInput = typeof parsed.chatHandlerMode === 'string' ? parsed.chatHandlerMode.trim().toLowerCase() : '';
55
+ return {
56
+ userText: (typeof prompt === 'string' ? prompt : raw).trim(),
57
+ probe,
58
+ chatHandlerMode: modeFromInput || (probe ? 'probe' : 'copilot'),
59
+ chatCopilotTimeoutMs: parsePositiveInt(parsed.chatTimeoutMs ?? parsed.chatCopilotTimeoutMs, null),
60
+ chatTimeMs: parsePositiveInt(parsed.chatTimeMs, null),
61
+ };
62
+ } catch {
63
+ return {
64
+ userText: raw.trim(),
65
+ probe: false,
66
+ chatHandlerMode: 'copilot',
67
+ chatCopilotTimeoutMs: null,
68
+ chatTimeMs: null,
69
+ };
70
+ }
71
+ }
72
+
73
+ const extra = readJsonStdin();
74
+ const boardId = typeof extra.boardId === 'string' ? extra.boardId : '';
75
+ const cardId = typeof extra.cardId === 'string' ? extra.cardId : '';
76
+ const boardSetupRoot = typeof extra.boardSetupRoot === 'string' ? extra.boardSetupRoot : '';
77
+ const boardRuntimeDir = typeof extra.boardRuntimeDir === 'string' ? extra.boardRuntimeDir : 'runtime';
78
+ const runtimeStatusDir = typeof extra.runtimeStatusDir === 'string' ? extra.runtimeStatusDir : 'runtime-out';
79
+ const cardsDir = typeof extra.cardsDir === 'string' ? extra.cardsDir : 'cards';
80
+ const projectRoot = typeof extra.projectRoot === 'string' ? extra.projectRoot : '';
81
+ const chatFlowRoot = typeof extra.chatFlowRoot === 'string' ? extra.chatFlowRoot : '';
82
+ const serverUrl = typeof extra.serverUrl === 'string' ? extra.serverUrl.replace(/\/$/, '') : '';
83
+ const apiBasePath = typeof extra.apiBasePath === 'string' ? extra.apiBasePath : '/api/board';
84
+ const lastChatEntryId = typeof extra.lastChatEntryId === 'string' ? extra.lastChatEntryId : '';
85
+
86
+ if (!cardId || !serverUrl || !apiBasePath || !lastChatEntryId) {
87
+ process.stderr.write('chat-open-turn requires cardId, serverUrl, apiBasePath, and lastChatEntryId\n');
88
+ process.exit(1);
89
+ }
90
+
91
+ let messageText = '';
92
+ try {
93
+ const chatsUrl = `${serverUrl}${apiBasePath}/cards/${encodeURIComponent(cardId)}/chats`;
94
+ const res = await fetch(chatsUrl);
95
+ if (!res.ok) {
96
+ process.stderr.write(`chat-open-turn could not fetch chat history: HTTP ${res.status}\n`);
97
+ process.exit(1);
98
+ }
99
+ const data = await res.json();
100
+ const messages = Array.isArray(data?.messages) ? data.messages : [];
101
+ const currentUser = messages.find((message) =>
102
+ typeof message?.id === 'string'
103
+ && message.id === lastChatEntryId
104
+ && message.role === 'user'
105
+ );
106
+ messageText = typeof currentUser?.text === 'string' ? currentUser.text : '';
107
+ } catch (err) {
108
+ process.stderr.write(`chat-open-turn could not fetch current user turn: ${err instanceof Error ? err.message : String(err)}\n`);
109
+ process.exit(1);
110
+ }
111
+
112
+ if (!messageText) {
113
+ process.stderr.write('chat-open-turn could not resolve user text for lastChatEntryId\n');
114
+ process.exit(1);
115
+ }
116
+
117
+ const envelope = parseChatEnvelope(messageText);
118
+ const userText = envelope.userText;
119
+ const probe = envelope.probe;
120
+ const chatHandlerMode = envelope.chatHandlerMode;
121
+ const chatCopilotTimeoutMs = envelope.chatCopilotTimeoutMs
122
+ ?? (Number.isFinite(Number(extra.chatCopilotTimeoutMs)) && Number(extra.chatCopilotTimeoutMs) > 0
123
+ ? Math.floor(Number(extra.chatCopilotTimeoutMs))
124
+ : 300000);
125
+ const chatTimeMs = envelope.chatTimeMs;
126
+
127
+ process.stdout.write(JSON.stringify({
128
+ boardId,
129
+ cardId,
130
+ boardSetupRoot,
131
+ boardRuntimeDir,
132
+ runtimeStatusDir,
133
+ cardsDir,
134
+ projectRoot,
135
+ chatFlowRoot,
136
+ userText,
137
+ serverUrl,
138
+ apiBasePath,
139
+ lastChatEntryId,
140
+ probe,
141
+ chatHandlerMode,
142
+ chatCopilotTimeoutMs,
143
+ chatTimeMs,
144
+ }));
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+
5
+ function readJsonStdin() {
6
+ try {
7
+ const raw = fs.readFileSync(0, 'utf-8').trim();
8
+ if (!raw) return {};
9
+ const parsed = JSON.parse(raw);
10
+ return parsed && typeof parsed === 'object' ? parsed : {};
11
+ } catch {
12
+ return {};
13
+ }
14
+ }
15
+
16
+ const input = readJsonStdin();
17
+ const cardId = typeof input.cardId === 'string' ? input.cardId : '';
18
+ const serverUrl = typeof input.serverUrl === 'string' ? input.serverUrl.replace(/\/$/, '') : '';
19
+ const apiBasePath = typeof input.apiBasePath === 'string' ? input.apiBasePath : '/api/board';
20
+ const replyText = typeof input.replyText === 'string' ? input.replyText : '';
21
+
22
+ if (!cardId || !serverUrl) {
23
+ process.stderr.write('chat-write-assistant requires cardId and serverUrl\n');
24
+ process.exit(1);
25
+ }
26
+
27
+ try {
28
+ const postUrl = `${serverUrl}${apiBasePath}/cards/${encodeURIComponent(cardId)}/chats`;
29
+ const postRes = await fetch(postUrl, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({ role: 'assistant', text: replyText, files: [], done: true }),
33
+ });
34
+ if (!postRes.ok) {
35
+ const err = await postRes.text();
36
+ process.stderr.write(`chat-write-assistant POST failed: ${err}\n`);
37
+ process.exit(1);
38
+ }
39
+ const postData = await postRes.json();
40
+ process.stdout.write(JSON.stringify({ replyId: postData?.id, replyText }));
41
+ } catch (err) {
42
+ process.stderr.write((err instanceof Error ? err.message : String(err)) + '\n');
43
+ process.exit(1);
44
+ }