yaml-flow 8.2.0 → 8.2.2
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.
- package/browser/asset-integrity.json +3 -3
- package/browser/board-livecards-client.js +1 -1
- package/browser/board-livecards-localstorage.js +4 -6
- package/cli/{board-live-cards-lib-tjYsPt5U.d.ts → board-live-cards-lib-Iq_XAC09.d.ts} +1 -1
- package/cli/browser-api/board-live-cards-browser-adapter.d.ts +4 -3
- package/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
- package/cli/browser-api/card-store-browser-api.d.ts +1 -1
- package/cli/node/artifacts-store-cli.js +8 -8
- package/cli/node/board-live-cards-cli.js +8 -8
- package/cli/node/fs-board-adapter.d.ts +6 -33
- package/cli/node/fs-board-adapter.js +10 -8
- package/cli/node/step-machine-cli.js +2 -2
- package/cli/{types-CSiGbY__.d.ts → types--rXGWbSR.d.ts} +76 -4
- package/examples/board/.board-ws/cards/store/_index.json +17 -0
- package/examples/board/.board-ws/cards/store/card-market-prices.json +80 -0
- package/examples/board/.board-ws/cards/store/card-portfolio-value.json +90 -0
- package/examples/board/.board-ws/cards/store/card-portfolio.json +78 -0
- package/examples/board/cards/cardT-market-prices.json +6 -4
- package/examples/board/cards/cardT-portfolio-value.json +10 -38
- package/examples/board/cards/cardT-portfolio.json +9 -4
- package/examples/board/demo-shell-with-server.html +3 -3
- package/examples/board/server/board-server.js +593 -0
- package/examples/board/server/board-worker/source-def-flows/mock-handler/mock-db.js +13 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/compliance.db +0 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/optimus.db +0 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/query.cjs +51 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpm.cjs +197 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpmV2.cjs +128 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-optimus.cjs +352 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-config.json +3 -0
- package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-handler.js +84 -0
- package/examples/board/{source-def-flows/url.flow.json → server/board-worker/source-def-flows/sqlite.flow.json} +7 -7
- package/examples/board/{source-def-handlers → server/board-worker/source-def-flows/url-handler}/http-source-handler.js +29 -21
- package/examples/board/server/board-worker/source-def-flows/url.flow.json +73 -0
- package/examples/board/{source_def_flows.json → server/board-worker/source_def_flows.json} +61 -115
- package/examples/board/server/board-worker/task-executor.js +475 -0
- package/examples/board/server/chat-flow/chat-clear-processing.js +41 -0
- package/examples/board/server/chat-flow/chat-open-turn.js +144 -0
- package/examples/board/server/chat-flow/chat-write-assistant.js +44 -0
- package/examples/board/server/chat-flow/copilot-chat/assistant.js +253 -0
- package/examples/board/server/chat-flow/echo-probe/assistant.js +28 -0
- package/examples/board/server/chat-flow/flow-steps.json +167 -0
- package/examples/board/server-config.json +22 -0
- package/examples/board/test/server-http-test.js +707 -0
- package/examples/board/test/{portfolio-tracker-sse-worker.js → sse-worker.js} +9 -8
- package/examples/board-local/demo-shell-localstorage.html +3 -3
- package/lib/{artifacts-store-lib-public-DfU9t5-S.d.cts → artifacts-store-lib-public-C5UL5tyG.d.cts} +3 -31
- package/lib/{artifacts-store-lib-public-BPW_C15z.d.ts → artifacts-store-lib-public-GD4H-fFp.d.ts} +3 -31
- package/lib/artifacts-store-public.d.cts +3 -3
- package/lib/artifacts-store-public.d.ts +3 -3
- package/lib/board-live-cards-node.cjs +10 -8
- package/lib/board-live-cards-node.d.cts +9 -8
- package/lib/board-live-cards-node.d.ts +9 -8
- package/lib/board-live-cards-node.js +10 -8
- package/lib/{board-live-cards-public-W2zK59m0.d.cts → board-live-cards-public-BLXbcBNk.d.cts} +1 -1
- package/lib/{board-live-cards-public-B8b_0k_j.d.ts → board-live-cards-public-BZaNb2mi.d.ts} +1 -1
- package/lib/board-live-cards-public.d.cts +2 -2
- package/lib/board-live-cards-public.d.ts +2 -2
- package/lib/board-live-cards-server-runtime.cjs +4 -6
- package/lib/board-live-cards-server-runtime.d.cts +3 -3
- package/lib/board-live-cards-server-runtime.d.ts +3 -3
- package/lib/board-live-cards-server-runtime.js +4 -6
- package/lib/board-livegraph-runtime/index.cjs +2 -2
- package/lib/board-livegraph-runtime/index.js +2 -2
- package/lib/card-store-public.d.cts +2 -2
- package/lib/card-store-public.d.ts +2 -2
- package/lib/execution-refs.cjs +1 -1
- package/lib/execution-refs.js +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/server-runtime/index.cjs +4 -6
- package/lib/server-runtime/index.d.cts +4 -4
- package/lib/server-runtime/index.d.ts +4 -4
- package/lib/server-runtime/index.js +4 -6
- package/lib/step-machine-public/index.d.cts +1 -1
- package/lib/step-machine-public/index.d.ts +1 -1
- package/lib/{storage-interface-BhAON-gW.d.cts → storage-interface-B6ecOulj.d.cts} +25 -3
- package/lib/{storage-interface-BhAON-gW.d.ts → storage-interface-B6ecOulj.d.ts} +25 -3
- package/lib/stores/index.d.cts +1 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/kv.d.cts +1 -1
- package/lib/stores/kv.d.ts +1 -1
- package/lib/{types-seTI8zta.d.cts → types-Bztd1KoK.d.cts} +55 -3
- package/lib/{types-Bm7IFD7r.d.ts → types-D-xVWPdY.d.ts} +55 -3
- package/package.json +1 -1
- package/examples/board/demo-chat-copilot.flow.json +0 -38
- package/examples/board/demo-chat-copilot.js +0 -185
- package/examples/board/demo-chat-echo.flow.json +0 -38
- package/examples/board/demo-chat-echo.js +0 -92
- package/examples/board/demo-server-config.copilot-chat.json +0 -10
- package/examples/board/demo-server-config.json +0 -10
- package/examples/board/demo-server.js +0 -629
- package/examples/board/demo-task-executor.js +0 -721
- package/examples/board/gandalf-cards/card-source-kinds.json +0 -36
- package/examples/board/gandalf-cards/cards/_index.json +0 -7
- package/examples/board/gandalf-cards/cards/card-source-kinds.json +0 -64
- package/examples/board/scripts/copilot_wrapper.bat +0 -157
- package/examples/board/scripts/copilot_wrapper_helper.ps1 +0 -190
- package/examples/board/scripts/workiq_wrapper.mjs +0 -66
- package/examples/board/source-def-flows/copilot.flow.json +0 -33
- package/examples/board/source-def-flows/url-list.flow.json +0 -33
- package/examples/board/source-def-flows/workiq.flow.json +0 -34
- package/examples/board/source-def-handlers/copilot-source-handler.js +0 -141
- package/examples/board/test/demo-http-test.js +0 -362
- /package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json +0 -0
|
@@ -1,721 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* demo-task-executor.js — Simple mock source executor for example-board.
|
|
5
|
-
*
|
|
6
|
-
* Subcommands:
|
|
7
|
-
* run-source-fetch — fetch data for one source entry
|
|
8
|
-
* describe-capabilities — print supported source kinds + schemas to stdout (JSON)
|
|
9
|
-
*
|
|
10
|
-
* Subcommand CLI args:
|
|
11
|
-
*
|
|
12
|
-
* run-source-fetch
|
|
13
|
-
* --in-ref <b64ref> Required. Ref to a temp JSON envelope: { source_def, callback? }
|
|
14
|
-
* --out-ref <b64ref> Required. Ref where this executor writes its JSON result.
|
|
15
|
-
* --err-ref <b64ref> Optional. Ref where this executor writes an error message on failure.
|
|
16
|
-
* --extra <base64json> Optional. Base64-encoded JSON with board topology context.
|
|
17
|
-
*
|
|
18
|
-
* validate-source-def — source def JSON on stdin; stdout: { ok, errors[] }
|
|
19
|
-
* validate-card-preflight — card JSON on stdin; stdout: { ok, errors[] }
|
|
20
|
-
* probe-source-preflight — source def JSON (with _projections) on stdin;
|
|
21
|
-
* stdout: { ok, reachable, latencyMs, error? }
|
|
22
|
-
* describe-capabilities — no stdin; stdout: capability manifest JSON
|
|
23
|
-
*
|
|
24
|
-
* run-source-fetch --in-ref envelope payload:
|
|
25
|
-
* { "source_def": { ... }, "callback": { ... } }
|
|
26
|
-
* or legacy: raw source_def object (no callback field)
|
|
27
|
-
*
|
|
28
|
-
* --extra (decoded):
|
|
29
|
-
* {
|
|
30
|
-
* "boardSetupRoot": "<abs path>", // board root (parent of runtime/, surface/, runtime-out/)
|
|
31
|
-
* "boardId": "<board id>", // e.g. "default"
|
|
32
|
-
* "boardRuntimeDir": "<relative>", // e.g. "runtime"
|
|
33
|
-
* "runtimeStatusDir": "<relative>", // e.g. "runtime-out"
|
|
34
|
-
* "cardsDir": "<relative>", // e.g. "surface/tmp-cards"
|
|
35
|
-
* "serverUrl": "<base url>", // optional; e.g. "http://127.0.0.1:7799"
|
|
36
|
-
* "boardLiveCardsCliJs":"<abs path>", // optional; path to board-live-cards-cli.js
|
|
37
|
-
* "stepMachineCliPath":"<abs path>" // optional; path to step-machine-cli.js
|
|
38
|
-
* }
|
|
39
|
-
*
|
|
40
|
-
* Supported source kinds (based on custom fields in --in):
|
|
41
|
-
* - { mock: "key" } → look up key in MOCK_DB (hardcoded below)
|
|
42
|
-
* - { copilot: { prompt_template, args? } } → call Copilot CLI with interpolated prompt
|
|
43
|
-
* - { prompt_template: "..." } → shorthand copilot call (top-level template)
|
|
44
|
-
* - { workiq: { query_template, args? } } → call WorkIQ (M365 Copilot) with interpolated query
|
|
45
|
-
* - { "url": { url, method?, headers?, args?, cacheTimeout? }, tickersFrom? }
|
|
46
|
-
* → single URL fetch via curl with {{key}} interpolation from _projections
|
|
47
|
-
* - { "url-list": { method?, headers?, cacheTimeout? } }
|
|
48
|
-
* → fan-out over _projections.url_list (string[]); returns array of responses.
|
|
49
|
-
* Build url_list in projections: e.g. `requires.holdings.ticker.('https://host/' & $ & '?q=1')`
|
|
50
|
-
* Prefer url-list for multi-URL fan-out sources.
|
|
51
|
-
* A real executor can also handle: graphapi, teams, mail, incidentdb, script, etc.
|
|
52
|
-
*
|
|
53
|
-
* url / url-list notes:
|
|
54
|
-
* - Results cached in os.tmpdir()/demo-executor-cache/ per URL (default 1 hour, override via cacheTimeout)
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
import fs from 'node:fs';
|
|
58
|
-
import path from 'node:path';
|
|
59
|
-
import os from 'node:os';
|
|
60
|
-
import { execFileSync } from 'node:child_process';
|
|
61
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
62
|
-
import { parseRef, blobStorageForRef, reportComplete, reportFailed } from 'yaml-flow/board-worker-adapter';
|
|
63
|
-
import { loadStepFlow, createStepMachine, MemoryStore, buildStepHandlersForFlow } from 'yaml-flow/step-machine-public';
|
|
64
|
-
import { invokeRefSync } from 'yaml-flow/board-live-cards-node';
|
|
65
|
-
|
|
66
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
67
|
-
const SOURCE_DEF_FLOWS_FILE = path.join(__dirname, 'source_def_flows.json');
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Mock data — used when a source has { mock: "key" }.
|
|
71
|
-
// Edit these values to change the demo data without needing a mock.db file.
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
const MOCK_DB = {
|
|
74
|
-
quotes: {
|
|
75
|
-
quoteResponse: {
|
|
76
|
-
result: [
|
|
77
|
-
{ symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 198.15, regularMarketChange: 2.15, regularMarketChangePercent: 1.10 },
|
|
78
|
-
{ symbol: 'MSFT', shortName: 'Microsoft Corp.', regularMarketPrice: 415.32, regularMarketChange: -1.23, regularMarketChangePercent: -0.30 },
|
|
79
|
-
{ symbol: 'GOOGL', shortName: 'Alphabet Inc.', regularMarketPrice: 174.89, regularMarketChange: 0.89, regularMarketChangePercent: 0.51 },
|
|
80
|
-
{ symbol: 'TSLA', shortName: 'Tesla Inc.', regularMarketPrice: 247.12, regularMarketChange: 5.43, regularMarketChangePercent: 2.25 },
|
|
81
|
-
],
|
|
82
|
-
error: null,
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
function readJson(filePath) {
|
|
88
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function interpolatePrompt(template, args) {
|
|
92
|
-
return String(template).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_, key) => {
|
|
93
|
-
const v = args?.[key];
|
|
94
|
-
if (v === undefined) return '';
|
|
95
|
-
if (typeof v === 'string') return v;
|
|
96
|
-
return JSON.stringify(v);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Reusable prompt fragments available to all copilot source templates.
|
|
101
|
-
// Source definitions can interpolate them with {{view_kind_guidance}} and {{card_layout_guidance}}.
|
|
102
|
-
const COPILOT_PROMPT_CONTEXT = {
|
|
103
|
-
view_kind_guidance: [
|
|
104
|
-
'VIEW KIND GUIDANCE (for dynamic ref rendering):',
|
|
105
|
-
'- Return a _view object whenever your output data is meant for a ref element.',
|
|
106
|
-
'- Allowed _view.kind values only: table, editable-table, chart, metric, list, badge, text, narrative, markdown, form, filter, todo, alert.',
|
|
107
|
-
'- If uncertain, use "table".',
|
|
108
|
-
'- For array rows that users should edit, prefer "editable-table" and set _view.data.writeTo to a card_data path.',
|
|
109
|
-
'- For chart, set _view.data.chartType and _view.data.columns with [labelField, valueField].',
|
|
110
|
-
'- Keep _view.data minimal and valid JSON (no comments, no trailing text).',
|
|
111
|
-
].join('\n'),
|
|
112
|
-
card_layout_guidance: [
|
|
113
|
-
'CARD LAYOUT GUIDANCE:',
|
|
114
|
-
'- Prefer compact outputs that fit a card: one primary structure plus concise rationale text.',
|
|
115
|
-
'- Avoid repeating values already present in upstream inputs.',
|
|
116
|
-
'- If you produce both machine-readable and human-readable content, keep machine-readable fields top-level and concise prose in a separate field.',
|
|
117
|
-
].join('\n'),
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
function resolveCopilotPrompt(sourceDef) {
|
|
121
|
-
const cfg = sourceDef?.copilot && typeof sourceDef.copilot === 'object' ? sourceDef.copilot : {};
|
|
122
|
-
const template = cfg.prompt_template ?? sourceDef.prompt_template;
|
|
123
|
-
const args = cfg.args ?? cfg.prompt_args ?? sourceDef.prompt_args ?? sourceDef.args ?? {};
|
|
124
|
-
|
|
125
|
-
// Merge _projections into template interpolation context.
|
|
126
|
-
// _projections contains the named data projections declared in source_defs[].projections,
|
|
127
|
-
// evaluated by the engine from card_data/requires before invoking this executor.
|
|
128
|
-
// Explicit args defined on the source take highest precedence.
|
|
129
|
-
const interpolationContext = {
|
|
130
|
-
...COPILOT_PROMPT_CONTEXT,
|
|
131
|
-
...sourceDef._projections,
|
|
132
|
-
...args,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
if (!template || typeof template !== 'string') return null;
|
|
136
|
-
return interpolatePrompt(template, interpolationContext);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Run a copilot prompt via copilot_wrapper.bat (Windows only).
|
|
141
|
-
*
|
|
142
|
-
* The wrapper handles:
|
|
143
|
-
* - Session management (--resume UUID for multi-turn continuity)
|
|
144
|
-
* - Noise/footer stripping (via copilot_wrapper_helper.ps1)
|
|
145
|
-
* - JSON mode extraction with optional result_shape key matching
|
|
146
|
-
* - Agentic retry: if the first response isn't valid JSON, the wrapper calls
|
|
147
|
-
* copilot again in the same session with a correction prompt, then re-extracts.
|
|
148
|
-
*
|
|
149
|
-
* @param {string} prompt - interpolated prompt string
|
|
150
|
-
* @param {object} sourceDef - source definition (may contain copilot.result_shape)
|
|
151
|
-
* @param {string} wrapperOutFile - path the wrapper writes its JSON output to
|
|
152
|
-
* @param {string} sessionDir - persistent dir for session UUID (enables --resume)
|
|
153
|
-
* @param {string} cwd - working directory for copilot (boardSetupRoot)
|
|
154
|
-
* @returns {unknown} parsed JSON result value
|
|
155
|
-
*/
|
|
156
|
-
function runCopilotViaWrapper(prompt, sourceDef, wrapperOutFile, sessionDir, cwd) {
|
|
157
|
-
const wrapperPath = path.join(__dirname, 'scripts', 'copilot_wrapper.bat');
|
|
158
|
-
|
|
159
|
-
const promptFile = wrapperOutFile + '.prompt.txt';
|
|
160
|
-
fs.writeFileSync(promptFile, prompt, 'utf-8');
|
|
161
|
-
|
|
162
|
-
// Optional result_shape_file: top-level keys the response JSON must contain.
|
|
163
|
-
// Sourced from sourceDef.copilot.result_shape or sourceDef.result_shape.
|
|
164
|
-
let shapeFile = '';
|
|
165
|
-
const shape = sourceDef?.copilot?.result_shape ?? sourceDef?.result_shape;
|
|
166
|
-
if (shape && typeof shape === 'object') {
|
|
167
|
-
shapeFile = wrapperOutFile + '.shape.json';
|
|
168
|
-
fs.writeFileSync(shapeFile, JSON.stringify(shape), 'utf-8');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
execFileSync('cmd.exe', [
|
|
175
|
-
'/d', '/c',
|
|
176
|
-
wrapperPath,
|
|
177
|
-
wrapperOutFile, // OUTPUT_FILE
|
|
178
|
-
sessionDir, // SESSION_DIR
|
|
179
|
-
cwd || process.cwd(), // WORKING_DIR
|
|
180
|
-
'@' + promptFile, // REQUEST_OR_FILE (@ prefix = file path)
|
|
181
|
-
'json', // RESULT_TYPE — wrapper extracts JSON + retries
|
|
182
|
-
sourceDef.bindTo || 'executor', // AGENT_NAME (for log file naming)
|
|
183
|
-
'', // MODEL (empty = wrapper default)
|
|
184
|
-
shapeFile, // RESULT_SHAPE_FILE (empty = accept any JSON)
|
|
185
|
-
], {
|
|
186
|
-
encoding: 'utf-8',
|
|
187
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
188
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
189
|
-
windowsHide: true,
|
|
190
|
-
});
|
|
191
|
-
} finally {
|
|
192
|
-
try { fs.unlinkSync(promptFile); } catch {}
|
|
193
|
-
if (shapeFile) { try { fs.unlinkSync(shapeFile); } catch {} }
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return JSON.parse(fs.readFileSync(wrapperOutFile, 'utf-8').replace(/^\uFEFF/, ''));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function fail(msg, errFile) {
|
|
200
|
-
if (errFile) {
|
|
201
|
-
try {
|
|
202
|
-
fs.writeFileSync(errFile, msg);
|
|
203
|
-
} catch {}
|
|
204
|
-
}
|
|
205
|
-
console.error(`[demo-task-executor] ${msg}`);
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function loadSourceDefFlowsConfig() {
|
|
210
|
-
try {
|
|
211
|
-
return readJson(SOURCE_DEF_FLOWS_FILE);
|
|
212
|
-
} catch (err) {
|
|
213
|
-
fail(
|
|
214
|
-
`Cannot read source flow registry (${SOURCE_DEF_FLOWS_FILE}): ${String(err && err.message || err)}`,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function matchesDetectRule(sourceDef, detect) {
|
|
220
|
-
if (!detect || typeof detect !== 'object') return false;
|
|
221
|
-
if (typeof detect.field === 'string') {
|
|
222
|
-
return sourceDef[detect.field] !== undefined;
|
|
223
|
-
}
|
|
224
|
-
if (Array.isArray(detect.anyOfFields)) {
|
|
225
|
-
return detect.anyOfFields.some((field) => sourceDef[field] !== undefined);
|
|
226
|
-
}
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function resolveSourceKind(sourceDef, registry) {
|
|
231
|
-
const kinds = registry?.kinds && typeof registry.kinds === 'object' ? registry.kinds : {};
|
|
232
|
-
const order = Array.isArray(registry?.resolveOrder) ? registry.resolveOrder : Object.keys(kinds);
|
|
233
|
-
const matched = [];
|
|
234
|
-
for (const kind of order) {
|
|
235
|
-
const spec = kinds[kind];
|
|
236
|
-
if (!spec) continue;
|
|
237
|
-
if (matchesDetectRule(sourceDef, spec.detect)) {
|
|
238
|
-
matched.push(kind);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (matched.length === 0) {
|
|
243
|
-
const knownKinds = Object.keys(kinds);
|
|
244
|
-
throw new Error(`No recognised source kind. Known kinds: ${knownKinds.join(', ')}`);
|
|
245
|
-
}
|
|
246
|
-
if (matched.length > 1) {
|
|
247
|
-
throw new Error(`Multiple source kinds specified: [${matched.join(', ')}]. Use exactly one.`);
|
|
248
|
-
}
|
|
249
|
-
return matched[0];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function executeStepMachineSourceFlow(context) {
|
|
253
|
-
const { kind, registry } = context;
|
|
254
|
-
const spec = registry?.kinds?.[kind];
|
|
255
|
-
if (!spec) {
|
|
256
|
-
throw new Error(`Missing flow registration for kind: ${kind}`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const flowRef = spec.flow;
|
|
260
|
-
if (typeof flowRef !== 'string' || flowRef.length === 0) {
|
|
261
|
-
throw new Error(`Invalid or missing flow for kind: ${kind}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const flowPath = path.resolve(__dirname, flowRef);
|
|
265
|
-
const flow = await loadStepFlow(flowPath);
|
|
266
|
-
|
|
267
|
-
const invokeHttpRef = async (ref, args) => {
|
|
268
|
-
const rawUrl = typeof ref.whatToRun === 'object' ? ref.whatToRun.value : parseRef(ref.whatToRun).value;
|
|
269
|
-
|
|
270
|
-
const base = String(args?.extra?.serverUrl || 'http://127.0.0.1:7799').replace(/\/$/, '');
|
|
271
|
-
const resolvedUrl = /^https?:\/\//i.test(rawUrl)
|
|
272
|
-
? rawUrl
|
|
273
|
-
: `${base}${rawUrl.startsWith('/') ? '' : '/'}${rawUrl}`;
|
|
274
|
-
|
|
275
|
-
let body = args;
|
|
276
|
-
const workiqCfg = args?.sourceDef?.workiq;
|
|
277
|
-
if (workiqCfg && typeof workiqCfg === 'object' && typeof workiqCfg.query_template === 'string') {
|
|
278
|
-
const interpolationContext = {
|
|
279
|
-
...(args?.sourceDef?._projections || {}),
|
|
280
|
-
...(workiqCfg.args || {}),
|
|
281
|
-
};
|
|
282
|
-
body = {
|
|
283
|
-
query: interpolatePrompt(workiqCfg.query_template, interpolationContext),
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const method = ref.howToRun === 'http:get' ? 'GET' : 'POST';
|
|
288
|
-
const response = await fetch(resolvedUrl, {
|
|
289
|
-
method,
|
|
290
|
-
headers: { 'Content-Type': 'application/json' },
|
|
291
|
-
...(method === 'POST' ? { body: JSON.stringify(body) } : {}),
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const text = await response.text();
|
|
295
|
-
let parsed;
|
|
296
|
-
try {
|
|
297
|
-
parsed = text ? JSON.parse(text) : {};
|
|
298
|
-
} catch {
|
|
299
|
-
parsed = { response: text };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!response.ok) {
|
|
303
|
-
const msg = typeof parsed?.error === 'string' ? parsed.error : `HTTP ${response.status}`;
|
|
304
|
-
return { result: 'failure', data: { error: msg }, error: msg };
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (typeof parsed?.error === 'string') {
|
|
308
|
-
return { result: 'failure', data: { error: parsed.error }, error: parsed.error };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
result: 'success',
|
|
313
|
-
data: {
|
|
314
|
-
resultValue: Object.prototype.hasOwnProperty.call(parsed, 'response') ? parsed.response : parsed,
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const invoke = async (ref, args) => {
|
|
320
|
-
if (ref.howToRun === 'http:post' || ref.howToRun === 'http:get') {
|
|
321
|
-
return invokeHttpRef(ref, args);
|
|
322
|
-
}
|
|
323
|
-
if (ref.howToRun === 'demo-local-module') {
|
|
324
|
-
const whatValue = typeof ref.whatToRun === 'object' ? ref.whatToRun.value : parseRef(ref.whatToRun).value;
|
|
325
|
-
const modulePath = path.resolve(__dirname, whatValue);
|
|
326
|
-
const mod = await import(pathToFileURL(modulePath).href);
|
|
327
|
-
if (typeof mod.execute !== 'function') {
|
|
328
|
-
throw new Error(`Flow module ${JSON.stringify(ref.whatToRun)} must export execute(context)`);
|
|
329
|
-
}
|
|
330
|
-
return mod.execute(args);
|
|
331
|
-
}
|
|
332
|
-
return invokeRefSync(ref, args, { cliDir: __dirname, cwd: process.cwd() });
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const handlers = buildStepHandlersForFlow(flow, { invoke });
|
|
336
|
-
const machine = createStepMachine(flow, handlers, { store: new MemoryStore() });
|
|
337
|
-
const run = await machine.run({
|
|
338
|
-
...context,
|
|
339
|
-
promptContext: COPILOT_PROMPT_CONTEXT,
|
|
340
|
-
executorDir: __dirname,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
if (run.status !== 'completed') {
|
|
344
|
-
const reason = run.error?.message ?? run.intent ?? run.status;
|
|
345
|
-
throw new Error(`flow execution failed: ${reason}`);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (run.intent !== 'success') {
|
|
349
|
-
const reason = typeof run.data?.error === 'string' ? run.data.error : `flow returned intent: ${run.intent}`;
|
|
350
|
-
throw new Error(reason);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
resultValue: run.data?.resultValue,
|
|
355
|
-
wroteOutputDirectly: !!run.data?.wroteOutputDirectly,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async function resolveAndExecuteSourceFlow(sourceDef, extra, refs = {}) {
|
|
360
|
-
const registry = loadSourceDefFlowsConfig();
|
|
361
|
-
const kind = resolveSourceKind(sourceDef, registry);
|
|
362
|
-
const flowResult = await executeStepMachineSourceFlow({
|
|
363
|
-
kind,
|
|
364
|
-
registry,
|
|
365
|
-
sourceDef,
|
|
366
|
-
extra,
|
|
367
|
-
inRef: refs.inRef,
|
|
368
|
-
outRef: refs.outRef,
|
|
369
|
-
errRef: refs.errRef,
|
|
370
|
-
mockDb: MOCK_DB,
|
|
371
|
-
});
|
|
372
|
-
return { kind, flowResult };
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
async function runSourceFetchSubcommand(argv) {
|
|
376
|
-
const inIdx = argv.indexOf('--in-ref');
|
|
377
|
-
const outIdx = argv.indexOf('--out-ref');
|
|
378
|
-
const errIdx = argv.indexOf('--err-ref');
|
|
379
|
-
const extraIdx = argv.indexOf('--extra');
|
|
380
|
-
const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;
|
|
381
|
-
const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;
|
|
382
|
-
const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;
|
|
383
|
-
const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
|
|
384
|
-
|
|
385
|
-
let extra = {};
|
|
386
|
-
if (extraB64) {
|
|
387
|
-
try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
|
|
388
|
-
catch { console.warn('[demo-task-executor] bad --extra base64, ignoring'); }
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (!inRefStr || !outRefStr) {
|
|
392
|
-
fail('Usage: run-source-fetch --in-ref <b64:<base64url(json)>> --out-ref <b64:<base64url(json)>> [--err-ref <b64:<base64url(json)>>]');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
let inRef, outRef, errRef;
|
|
396
|
-
try {
|
|
397
|
-
inRef = parseRef(inRefStr);
|
|
398
|
-
outRef = parseRef(outRefStr);
|
|
399
|
-
if (errRefStr) errRef = parseRef(errRefStr);
|
|
400
|
-
} catch (e) {
|
|
401
|
-
fail(`invalid ref argument: ${e.message}`);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const inStorage = blobStorageForRef(inRef);
|
|
405
|
-
const outStorage = blobStorageForRef(outRef);
|
|
406
|
-
const errStorage = errRef ? blobStorageForRef(errRef) : undefined;
|
|
407
|
-
|
|
408
|
-
// Local error reporter — writes to errStorage and calls back to board if callback present.
|
|
409
|
-
const failRef = (msg, callback) => {
|
|
410
|
-
if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch {} }
|
|
411
|
-
console.error(`[demo-task-executor] ${msg}`);
|
|
412
|
-
if (callback) { try { reportFailed(callback, msg); } catch {} }
|
|
413
|
-
process.exit(1);
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const rawIn = inStorage.read(inRef.value);
|
|
417
|
-
if (rawIn === null) {
|
|
418
|
-
failRef(`Input not found: ${inRefStr}`);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Payload may be { source_def, callback } (new protocol) or raw source def (legacy).
|
|
422
|
-
let envelope;
|
|
423
|
-
try {
|
|
424
|
-
envelope = JSON.parse(rawIn);
|
|
425
|
-
} catch (err) {
|
|
426
|
-
failRef(`Cannot parse input: ${String(err && err.message || err)}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const callback = envelope.source_def ? envelope.callback : undefined;
|
|
430
|
-
let sourceDef;
|
|
431
|
-
try {
|
|
432
|
-
sourceDef = envelope.source_def ?? envelope;
|
|
433
|
-
} catch (err) {
|
|
434
|
-
failRef(`Cannot resolve source_def: ${String(err && err.message || err)}`, callback);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
let kind;
|
|
438
|
-
let flowResult;
|
|
439
|
-
try {
|
|
440
|
-
const resolved = await resolveAndExecuteSourceFlow(sourceDef, extra, { inRef, outRef, errRef });
|
|
441
|
-
kind = resolved.kind;
|
|
442
|
-
flowResult = resolved.flowResult;
|
|
443
|
-
} catch (err) {
|
|
444
|
-
const detail = (err && (err.stderr || err.stdout)) ? `\n${err.stderr || err.stdout}`.trimEnd() : '';
|
|
445
|
-
failRef(`source invocation failed: ${String(err && err.message || err)}${detail}`, callback);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (!flowResult?.wroteOutputDirectly) {
|
|
449
|
-
try {
|
|
450
|
-
outStorage.write(outRef.value, JSON.stringify(flowResult?.resultValue, null, 2));
|
|
451
|
-
} catch (err) {
|
|
452
|
-
failRef(`Cannot write output: ${String(err && err.message || err)}`, callback);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (callback) {
|
|
457
|
-
try {
|
|
458
|
-
reportComplete(callback, outRef);
|
|
459
|
-
} catch (err) {
|
|
460
|
-
console.error(`[demo-task-executor] reportComplete failed: ${String(err && err.message || err)}`);
|
|
461
|
-
process.exit(1);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
async function probeSourcePreflightSubcommand(argv) {
|
|
468
|
-
const extraIdx = argv.indexOf('--extra');
|
|
469
|
-
const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
|
|
470
|
-
|
|
471
|
-
let extra = {};
|
|
472
|
-
if (extraB64) {
|
|
473
|
-
try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
|
|
474
|
-
catch { /* ignore malformed extra */ }
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const startedAt = Date.now();
|
|
478
|
-
try {
|
|
479
|
-
const chunks = [];
|
|
480
|
-
for await (const chunk of process.stdin) {
|
|
481
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
482
|
-
}
|
|
483
|
-
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
484
|
-
if (!raw) {
|
|
485
|
-
console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: 'Missing probe input JSON on stdin' }));
|
|
486
|
-
process.exit(0);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
let sourceDef;
|
|
490
|
-
try {
|
|
491
|
-
sourceDef = JSON.parse(raw);
|
|
492
|
-
} catch (err) {
|
|
493
|
-
console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: `Invalid probe JSON: ${String(err && err.message || err)}` }));
|
|
494
|
-
process.exit(0);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
await resolveAndExecuteSourceFlow(sourceDef, extra);
|
|
498
|
-
console.log(JSON.stringify({ ok: true, reachable: true, latencyMs: Date.now() - startedAt }));
|
|
499
|
-
process.exit(0);
|
|
500
|
-
} catch (err) {
|
|
501
|
-
const detail = (err && (err.stderr || err.stdout)) ? `\n${err.stderr || err.stdout}`.trimEnd() : '';
|
|
502
|
-
console.log(JSON.stringify({ ok: false, reachable: false, latencyMs: Date.now() - startedAt, error: `source invocation failed: ${String(err && err.message || err)}${detail}` }));
|
|
503
|
-
process.exit(0);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// ---------------------------------------------------------------------------
|
|
508
|
-
// validate-source-def — structural validation of a source definition.
|
|
509
|
-
// Source def JSON arrives on stdin (board-live-cards-public sends it via input:).
|
|
510
|
-
// ---------------------------------------------------------------------------
|
|
511
|
-
async function validateSourceDefSubcommand() {
|
|
512
|
-
const chunks = [];
|
|
513
|
-
for await (const chunk of process.stdin) {
|
|
514
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
515
|
-
}
|
|
516
|
-
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
517
|
-
if (!raw) {
|
|
518
|
-
console.log(JSON.stringify({ ok: false, errors: ['No input provided on stdin'] }));
|
|
519
|
-
process.exit(1);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
let sourceDef;
|
|
523
|
-
try {
|
|
524
|
-
sourceDef = JSON.parse(raw);
|
|
525
|
-
} catch (err) {
|
|
526
|
-
console.log(JSON.stringify({ ok: false, errors: [`Cannot parse input: ${err && err.message || err}`] }));
|
|
527
|
-
process.exit(1);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const errors = [];
|
|
531
|
-
const registry = loadSourceDefFlowsConfig();
|
|
532
|
-
|
|
533
|
-
let kind = '';
|
|
534
|
-
try {
|
|
535
|
-
kind = resolveSourceKind(sourceDef, registry);
|
|
536
|
-
} catch (err) {
|
|
537
|
-
errors.push(String(err && err.message || err));
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (kind === 'url') {
|
|
541
|
-
if (typeof sourceDef['url'] !== 'object') {
|
|
542
|
-
errors.push('url must be an object.');
|
|
543
|
-
} else if (!sourceDef['url'].url || typeof sourceDef['url'].url !== 'string') {
|
|
544
|
-
errors.push('url.url is required and must be a string.');
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (kind === 'url-list') {
|
|
549
|
-
if (typeof sourceDef['url-list'] !== 'object') {
|
|
550
|
-
errors.push('url-list must be an object.');
|
|
551
|
-
}
|
|
552
|
-
// url_list is supplied via _projections at runtime — no static validation needed.
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (kind === 'copilot') {
|
|
556
|
-
if (typeof sourceDef.copilot !== 'object') {
|
|
557
|
-
if (typeof sourceDef.prompt_template !== 'string') {
|
|
558
|
-
errors.push('copilot must be an object when prompt_template is not provided at top level.');
|
|
559
|
-
}
|
|
560
|
-
} else if (!sourceDef.copilot.prompt_template && typeof sourceDef.prompt_template !== 'string') {
|
|
561
|
-
errors.push('copilot.prompt_template is required (or use top-level prompt_template).');
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (kind === 'workiq') {
|
|
566
|
-
if (typeof sourceDef.workiq !== 'object') {
|
|
567
|
-
errors.push('workiq must be an object.');
|
|
568
|
-
} else if (!sourceDef.workiq.query_template || typeof sourceDef.workiq.query_template !== 'string') {
|
|
569
|
-
errors.push('workiq.query_template is required and must be a string.');
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (kind === 'mock') {
|
|
574
|
-
if (typeof sourceDef.mock !== 'string') {
|
|
575
|
-
errors.push('mock must be a string key.');
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const result = { ok: errors.length === 0, errors };
|
|
580
|
-
console.log(JSON.stringify(result));
|
|
581
|
-
process.exit(errors.length === 0 ? 0 : 1);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// ---------------------------------------------------------------------------
|
|
585
|
-
// describe-capabilities — introspection metadata for this executor
|
|
586
|
-
// ---------------------------------------------------------------------------
|
|
587
|
-
const CAPABILITIES = {
|
|
588
|
-
version: '1.0',
|
|
589
|
-
executor: 'demo-task-executor',
|
|
590
|
-
subcommands: ['run-source-fetch', 'probe-source-preflight', 'describe-capabilities', 'validate-source-def'],
|
|
591
|
-
sourceKinds: {
|
|
592
|
-
mock: {
|
|
593
|
-
description: 'Look up a key in a hardcoded MOCK_DB dictionary.',
|
|
594
|
-
inputSchema: {
|
|
595
|
-
mock: { type: 'string', required: true, description: 'Key in MOCK_DB (e.g. "quotes").' },
|
|
596
|
-
},
|
|
597
|
-
outputShape: 'Arbitrary JSON — depends on the mock key.',
|
|
598
|
-
example: {
|
|
599
|
-
input: { mock: 'quotes' },
|
|
600
|
-
output: { quoteResponse: { result: [{ symbol: 'AAPL', regularMarketPrice: 198.15 }], error: null } },
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
copilot: {
|
|
604
|
-
description: 'Invoke GitHub Copilot CLI with an interpolated prompt template.',
|
|
605
|
-
inputSchema: {
|
|
606
|
-
copilot: {
|
|
607
|
-
type: 'object', required: false,
|
|
608
|
-
description: 'Object with prompt_template (string) and optional args (object).',
|
|
609
|
-
properties: {
|
|
610
|
-
prompt_template: { type: 'string', required: true, description: 'Prompt with {{key}} placeholders.' },
|
|
611
|
-
args: { type: 'object', required: false, description: 'Extra interpolation args (highest precedence).' },
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
prompt_template: { type: 'string', required: false, description: 'Shorthand — top-level prompt template (alternative to copilot.prompt_template).' },
|
|
615
|
-
},
|
|
616
|
-
outputShape: 'string | object — raw Copilot text, or parsed JSON if the response is valid JSON.',
|
|
617
|
-
},
|
|
618
|
-
workiq: {
|
|
619
|
-
description: 'Query WorkIQ (Microsoft 365 Copilot) with an interpolated query template. Returns raw text response.',
|
|
620
|
-
inputSchema: {
|
|
621
|
-
workiq: {
|
|
622
|
-
type: 'object', required: true,
|
|
623
|
-
properties: {
|
|
624
|
-
query_template: { type: 'string', required: true, description: 'Query with {{key}} placeholders interpolated from _projections and args.' },
|
|
625
|
-
args: { type: 'object', required: false, description: 'Extra interpolation args (highest precedence).' },
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
outputShape: 'string — raw M365 Copilot response text.',
|
|
630
|
-
note: 'Requires workiq CLI installed and Azure CLI logged in (az login).',
|
|
631
|
-
},
|
|
632
|
-
'url': {
|
|
633
|
-
description: 'Single URL fetch via curl with {{key}} interpolation from _projections. Supports cacheTimeout.',
|
|
634
|
-
inputSchema: {
|
|
635
|
-
'url': {
|
|
636
|
-
type: 'object', required: true,
|
|
637
|
-
properties: {
|
|
638
|
-
url: { type: 'string', required: true, description: 'URL template with {{key}} placeholders.' },
|
|
639
|
-
method: { type: 'string', required: false, description: 'HTTP method (default: GET).' },
|
|
640
|
-
headers: { type: 'object', required: false, description: 'Request headers.' },
|
|
641
|
-
args: { type: 'object', required: false, description: 'Extra interpolation args (highest precedence).' },
|
|
642
|
-
cacheTimeout: { type: 'number', required: false, description: 'Cache TTL in seconds (default: 3600).' },
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
tickersFrom: { type: 'string', required: false, description: '"refKey.fieldName" — join tickers from _projections into {{tickers}}.' },
|
|
646
|
-
},
|
|
647
|
-
outputShape: 'Arbitrary JSON from the fetched URL.',
|
|
648
|
-
},
|
|
649
|
-
'url-list': {
|
|
650
|
-
description: 'Fan-out over a pre-resolved URL list — calls url logic per URL and returns an array of responses. url_list must be a string[] in _projections.url_list (built via projections JSONata).',
|
|
651
|
-
inputSchema: {
|
|
652
|
-
'url-list': {
|
|
653
|
-
type: 'object', required: true,
|
|
654
|
-
properties: {
|
|
655
|
-
method: { type: 'string', required: false, description: 'HTTP method (default: GET).' },
|
|
656
|
-
headers: { type: 'object', required: false, description: 'Request headers.' },
|
|
657
|
-
cacheTimeout: { type: 'number', required: false, description: 'Cache TTL per URL in seconds (default: 3600).' },
|
|
658
|
-
},
|
|
659
|
-
},
|
|
660
|
-
},
|
|
661
|
-
outputShape: 'Array of raw JSON responses, one per URL in _projections.url_list.',
|
|
662
|
-
urlListNote: 'Declare `"projections": { "url_list": "<JSONata producing string[]>" }` on the source def. Example: `requires.holdings.ticker.(\'https://api.example.com/\' & $ & \'?q=1\')`',
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
extraSchema: {
|
|
666
|
-
description: 'Board topology context passed via --extra (base64-encoded JSON, baked at init).',
|
|
667
|
-
properties: {
|
|
668
|
-
boardSetupRoot: { type: 'string', description: 'Absolute path to board root.' },
|
|
669
|
-
boardId: { type: 'string', description: 'Board identifier.' },
|
|
670
|
-
boardRuntimeDir: { type: 'string', description: 'Relative path to runtime dir.' },
|
|
671
|
-
runtimeStatusDir: { type: 'string', description: 'Relative path to runtime-out dir.' },
|
|
672
|
-
cardsDir: { type: 'string', description: 'Relative path to cards dir.' },
|
|
673
|
-
serverUrl: { type: 'string', description: 'Base URL of the hosting server (e.g. http://127.0.0.1:7799). Used by source kinds that call server-side proxy endpoints.' },
|
|
674
|
-
boardLiveCardsCliJs: { type: 'string', description: 'Absolute path to board-live-cards-cli.js when configured by the runtime.' },
|
|
675
|
-
stepMachineCliPath: { type: 'string', description: 'Absolute path to step-machine-cli.js when configured by the runtime.' },
|
|
676
|
-
},
|
|
677
|
-
},
|
|
678
|
-
};
|
|
679
|
-
|
|
680
|
-
function describeCapabilities() {
|
|
681
|
-
const registry = loadSourceDefFlowsConfig();
|
|
682
|
-
const merged = {
|
|
683
|
-
...CAPABILITIES,
|
|
684
|
-
sourceKinds: Object.fromEntries(
|
|
685
|
-
Object.entries(registry?.kinds || {}).map(([kind, spec]) => {
|
|
686
|
-
const existing = CAPABILITIES.sourceKinds[kind] || {};
|
|
687
|
-
const manifest = spec?.manifest && typeof spec.manifest === 'object' ? spec.manifest : {};
|
|
688
|
-
return [kind, { ...existing, ...manifest }];
|
|
689
|
-
}),
|
|
690
|
-
),
|
|
691
|
-
};
|
|
692
|
-
console.log(JSON.stringify(merged, null, 2));
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
async function main() {
|
|
696
|
-
const sub = process.argv[2];
|
|
697
|
-
if (sub === 'run-source-fetch') {
|
|
698
|
-
await runSourceFetchSubcommand(process.argv.slice(3));
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
if (sub === 'probe-source-preflight') {
|
|
702
|
-
await probeSourcePreflightSubcommand(process.argv.slice(3));
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
if (sub === 'describe' || sub === 'describe-capabilities') {
|
|
706
|
-
describeCapabilities();
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
if (sub === 'validate-source-def') {
|
|
710
|
-
await validateSourceDefSubcommand();
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
console.warn(`[demo-task-executor] Unknown subcommand: ${sub}`);
|
|
715
|
-
process.exit(0);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
main().catch(err => {
|
|
719
|
-
console.error(`[demo-task-executor] fatal: ${err && err.message || err}`);
|
|
720
|
-
process.exit(1);
|
|
721
|
-
});
|