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.
- 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/card-store-cli.js +4 -4
- 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 +3 -3
- package/cli/{types-D2XnLbBj.d.ts → types--rXGWbSR.d.ts} +77 -5
- 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-DBICnGL6.d.cts → artifacts-store-lib-public-C5UL5tyG.d.cts} +3 -31
- package/lib/{artifacts-store-lib-public-BWC3YuLa.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-BF9FP0mL.d.cts → board-live-cards-public-BLXbcBNk.d.cts} +2 -2
- package/lib/{board-live-cards-public-dJAl5IL-.d.ts → board-live-cards-public-BZaNb2mi.d.ts} +2 -2
- package/lib/board-live-cards-public.cjs +2 -2
- 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-public.js +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.cjs +3 -3
- package/lib/step-machine-public/index.d.cts +27 -10
- package/lib/step-machine-public/index.d.ts +27 -10
- package/lib/step-machine-public/index.js +3 -3
- package/lib/{storage-interface-BhAON-gW.d.ts → storage-interface-B6ecOulj.d.cts} +25 -3
- package/lib/{storage-interface-BhAON-gW.d.cts → 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-CXBzvC0s.d.cts → types-Bztd1KoK.d.cts} +75 -3
- package/lib/{types-D48hpnTR.d.ts → types-D-xVWPdY.d.ts} +75 -3
- package/package.json +1 -1
- package/examples/board/demo-chat-handler.js +0 -169
- package/examples/board/demo-server-config.json +0 -10
- package/examples/board/demo-server.js +0 -580
- 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 -317
- /package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json +0 -0
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { execFileSync } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
function interpolate(template, args) {
|
|
8
|
-
return String(template).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_m, key) => {
|
|
9
|
-
const v = args?.[key];
|
|
10
|
-
if (v === undefined) return '';
|
|
11
|
-
return typeof v === 'string' ? v : JSON.stringify(v);
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const DEFAULT_PROMPT_CONTEXT = {
|
|
16
|
-
view_kind_guidance: [
|
|
17
|
-
'VIEW KIND GUIDANCE (for dynamic ref rendering):',
|
|
18
|
-
'- Return a _view object whenever your output data is meant for a ref element.',
|
|
19
|
-
'- Allowed _view.kind values only: table, editable-table, chart, metric, list, badge, text, narrative, markdown, form, filter, todo, alert.',
|
|
20
|
-
'- If uncertain, use "table".',
|
|
21
|
-
'- For array rows that users should edit, prefer "editable-table" and set _view.data.writeTo to a card_data path.',
|
|
22
|
-
'- For chart, set _view.data.chartType and _view.data.columns with [labelField, valueField].',
|
|
23
|
-
'- Keep _view.data minimal and valid JSON (no comments, no trailing text).',
|
|
24
|
-
].join('\n'),
|
|
25
|
-
card_layout_guidance: [
|
|
26
|
-
'CARD LAYOUT GUIDANCE:',
|
|
27
|
-
'- Prefer compact outputs that fit a card: one primary structure plus concise rationale text.',
|
|
28
|
-
'- Avoid repeating values already present in upstream inputs.',
|
|
29
|
-
'- If you produce both machine-readable and human-readable content, keep machine-readable fields top-level and concise prose in a separate field.',
|
|
30
|
-
].join('\n'),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function resolvePrompt(sourceDef, promptContext) {
|
|
34
|
-
const cfg = sourceDef?.copilot && typeof sourceDef.copilot === 'object' ? sourceDef.copilot : {};
|
|
35
|
-
const template = cfg.prompt_template ?? sourceDef.prompt_template;
|
|
36
|
-
if (!template || typeof template !== 'string') return null;
|
|
37
|
-
const args = {
|
|
38
|
-
...(promptContext || DEFAULT_PROMPT_CONTEXT),
|
|
39
|
-
...(sourceDef?._projections || {}),
|
|
40
|
-
...(cfg.args || sourceDef.args || {}),
|
|
41
|
-
};
|
|
42
|
-
return interpolate(template, args);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function runCopilot(prompt, sourceDef, executorDir, extra) {
|
|
46
|
-
const wrapperPath = path.join(executorDir, 'scripts', 'copilot_wrapper.bat');
|
|
47
|
-
if (process.platform === 'win32' && fs.existsSync(wrapperPath)) {
|
|
48
|
-
const tmpBase = path.join(process.env.TEMP || process.cwd(), `copilot-handler-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
49
|
-
const outFile = `${tmpBase}.out.json`;
|
|
50
|
-
const promptFile = `${tmpBase}.prompt.txt`;
|
|
51
|
-
const sessionDir = path.join(
|
|
52
|
-
extra?.boardSetupRoot || (process.env.TEMP || process.cwd()),
|
|
53
|
-
'copilot-sessions',
|
|
54
|
-
String(sourceDef?.bindTo || 'default').replace(/[^a-zA-Z0-9_-]/g, '_'),
|
|
55
|
-
);
|
|
56
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
57
|
-
fs.writeFileSync(promptFile, prompt, 'utf-8');
|
|
58
|
-
|
|
59
|
-
let shapeFile = '';
|
|
60
|
-
const shape = sourceDef?.copilot?.result_shape ?? sourceDef?.result_shape;
|
|
61
|
-
if (shape && typeof shape === 'object') {
|
|
62
|
-
shapeFile = `${tmpBase}.shape.json`;
|
|
63
|
-
fs.writeFileSync(shapeFile, JSON.stringify(shape), 'utf-8');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
execFileSync('cmd.exe', [
|
|
68
|
-
'/d', '/c',
|
|
69
|
-
wrapperPath,
|
|
70
|
-
outFile,
|
|
71
|
-
sessionDir,
|
|
72
|
-
extra?.boardSetupRoot || process.cwd(),
|
|
73
|
-
`@${promptFile}`,
|
|
74
|
-
'json',
|
|
75
|
-
String(sourceDef?.bindTo || 'executor'),
|
|
76
|
-
'',
|
|
77
|
-
shapeFile,
|
|
78
|
-
], {
|
|
79
|
-
encoding: 'utf-8',
|
|
80
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
81
|
-
windowsHide: true,
|
|
82
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
83
|
-
});
|
|
84
|
-
return JSON.parse(fs.readFileSync(outFile, 'utf-8').replace(/^\uFEFF/, ''));
|
|
85
|
-
} finally {
|
|
86
|
-
try { fs.unlinkSync(promptFile); } catch {}
|
|
87
|
-
try { fs.unlinkSync(outFile); } catch {}
|
|
88
|
-
if (shapeFile) { try { fs.unlinkSync(shapeFile); } catch {} }
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const stdout = execFileSync('copilot', ['--allow-all'], {
|
|
93
|
-
input: prompt,
|
|
94
|
-
encoding: 'utf-8',
|
|
95
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
96
|
-
timeout: 120000,
|
|
97
|
-
cwd: extra?.boardSetupRoot || process.cwd(),
|
|
98
|
-
windowsHide: true,
|
|
99
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const firstBrace = stdout.indexOf('{');
|
|
103
|
-
const firstBracket = stdout.indexOf('[');
|
|
104
|
-
let jsonStart = -1;
|
|
105
|
-
if (firstBrace === -1) jsonStart = firstBracket;
|
|
106
|
-
else if (firstBracket === -1) jsonStart = firstBrace;
|
|
107
|
-
else jsonStart = Math.min(firstBrace, firstBracket);
|
|
108
|
-
|
|
109
|
-
if (jsonStart !== -1) {
|
|
110
|
-
try {
|
|
111
|
-
return JSON.parse(stdout.slice(jsonStart));
|
|
112
|
-
} catch {
|
|
113
|
-
return stdout;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return stdout;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function execute(context) {
|
|
120
|
-
const sourceDef = context?.sourceDef || {};
|
|
121
|
-
const extra = context?.extra || {};
|
|
122
|
-
const executorDir = context?.executorDir || process.cwd();
|
|
123
|
-
const promptContext = context?.promptContext || DEFAULT_PROMPT_CONTEXT;
|
|
124
|
-
|
|
125
|
-
const prompt = resolvePrompt(sourceDef, promptContext);
|
|
126
|
-
if (!prompt) {
|
|
127
|
-
return {
|
|
128
|
-
result: 'failure',
|
|
129
|
-
data: { error: 'Source definition missing copilot.prompt_template (or prompt_template)' },
|
|
130
|
-
error: 'missing prompt_template',
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const resultValue = runCopilot(prompt, sourceDef, executorDir, extra);
|
|
136
|
-
return { result: 'success', data: { resultValue } };
|
|
137
|
-
} catch (err) {
|
|
138
|
-
const msg = String(err?.message || err);
|
|
139
|
-
return { result: 'failure', data: { error: `copilot invocation failed: ${msg}` }, error: msg };
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* demo-http-test.js
|
|
4
|
-
*
|
|
5
|
-
* Smoke test for public-examples/board/demo-server.js over HTTP + SSE.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* node demo-http-test.js [--port 7799]
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { spawn } from 'node:child_process';
|
|
12
|
-
import { Worker } from 'node:worker_threads';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import http from 'node:http';
|
|
16
|
-
import fs from 'node:fs';
|
|
17
|
-
import net from 'node:net';
|
|
18
|
-
|
|
19
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
-
const __dirname = path.dirname(__filename);
|
|
21
|
-
|
|
22
|
-
const cliArgs = process.argv.slice(2);
|
|
23
|
-
const portArg = cliArgs.indexOf('--port');
|
|
24
|
-
const cliPort = portArg !== -1 ? parseInt(cliArgs[portArg + 1], 10) : NaN;
|
|
25
|
-
const RUN_ID = `run-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
26
|
-
|
|
27
|
-
async function pickFreePort() {
|
|
28
|
-
return await new Promise((resolve, reject) => {
|
|
29
|
-
const server = net.createServer();
|
|
30
|
-
server.once('error', reject);
|
|
31
|
-
server.listen(0, '127.0.0.1', () => {
|
|
32
|
-
const addr = server.address();
|
|
33
|
-
const port = addr && typeof addr === 'object' ? addr.port : 0;
|
|
34
|
-
server.close((err) => {
|
|
35
|
-
if (err) {
|
|
36
|
-
reject(err);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
resolve(port);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const PORT = Number.isInteger(cliPort) && cliPort > 0 ? cliPort : await pickFreePort();
|
|
46
|
-
|
|
47
|
-
const BOARD_ID = 'default';
|
|
48
|
-
const BASE = `http://127.0.0.1:${PORT}/api/boards/${BOARD_ID}`;
|
|
49
|
-
const SERVER_SCRIPT = path.join(__dirname, '..', 'demo-server.js');
|
|
50
|
-
const SERVER_DIR = path.dirname(SERVER_SCRIPT);
|
|
51
|
-
const SSE_WORKER_SCRIPT = path.join(__dirname, 'portfolio-tracker-sse-worker.js');
|
|
52
|
-
const CARD_PATTERN = 'cardT*';
|
|
53
|
-
|
|
54
|
-
// Resolve and wipe the setup directory before starting the server so each
|
|
55
|
-
// test run begins from a clean slate. The location is read from
|
|
56
|
-
// demo-server-config.json (setupDir key) with the same fallback the server uses.
|
|
57
|
-
function resolveSetupDirRoot() {
|
|
58
|
-
const configPath = path.join(SERVER_DIR, 'demo-server-config.json');
|
|
59
|
-
try {
|
|
60
|
-
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
61
|
-
if (cfg && typeof cfg.setupDir === 'string' && cfg.setupDir.trim()) {
|
|
62
|
-
return path.resolve(SERVER_DIR, cfg.setupDir.trim());
|
|
63
|
-
}
|
|
64
|
-
} catch { /* ignore */ }
|
|
65
|
-
return path.join(SERVER_DIR, '.demo-setup');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const SETUP_DIR = path.join(resolveSetupDirRoot(), RUN_ID);
|
|
69
|
-
if (fs.existsSync(SETUP_DIR)) {
|
|
70
|
-
fs.rmSync(SETUP_DIR, { recursive: true, force: true });
|
|
71
|
-
console.log(`[demo-http-test] wiped setup dir: ${SETUP_DIR}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const NS = {
|
|
76
|
-
initialPayload: null,
|
|
77
|
-
statusSummary: null,
|
|
78
|
-
statusGeneration: 0,
|
|
79
|
-
computedValues: {},
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
function applyFrame(payload) {
|
|
83
|
-
if (payload && Array.isArray(payload.cardDefinitions)) {
|
|
84
|
-
if (!NS.initialPayload && payload.cardDefinitions.length > 0) {
|
|
85
|
-
NS.initialPayload = payload;
|
|
86
|
-
}
|
|
87
|
-
const summary = payload.statusSnapshot && payload.statusSnapshot.summary;
|
|
88
|
-
if (summary) {
|
|
89
|
-
NS.statusSummary = summary;
|
|
90
|
-
NS.statusGeneration += 1;
|
|
91
|
-
}
|
|
92
|
-
if (payload.cardRuntimeById) {
|
|
93
|
-
for (const [cardId, runtime] of Object.entries(payload.cardRuntimeById)) {
|
|
94
|
-
if (runtime?.computed_values && Object.keys(runtime.computed_values).length > 0) {
|
|
95
|
-
NS.computedValues[cardId] = runtime.computed_values;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (payload && payload.kind === 'notification-batch' && Array.isArray(payload.notifications)) {
|
|
103
|
-
for (const n of payload.notifications) {
|
|
104
|
-
const summary = n && n.kind === 'status' && n.status && n.status.summary;
|
|
105
|
-
if (summary) {
|
|
106
|
-
NS.statusSummary = summary;
|
|
107
|
-
NS.statusGeneration += 1;
|
|
108
|
-
}
|
|
109
|
-
if (n && n.kind === 'computed_values' && n.cardId) {
|
|
110
|
-
NS.computedValues[n.cardId] = n.values;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function assert(condition, message) {
|
|
117
|
-
if (!condition) {
|
|
118
|
-
console.error(`\n[ASSERT FAILED] ${message}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function waitUntil(predicate, timeoutMs, label) {
|
|
124
|
-
return new Promise((resolve, reject) => {
|
|
125
|
-
const deadline = Date.now() + timeoutMs;
|
|
126
|
-
const interval = setInterval(() => {
|
|
127
|
-
let result;
|
|
128
|
-
try { result = predicate(); } catch { /* retry */ }
|
|
129
|
-
if (result !== undefined && result !== null && result !== false) {
|
|
130
|
-
clearInterval(interval);
|
|
131
|
-
resolve(result);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (Date.now() > deadline) {
|
|
135
|
-
clearInterval(interval);
|
|
136
|
-
reject(new Error(`Timeout (${timeoutMs}ms) waiting for: ${label}`));
|
|
137
|
-
}
|
|
138
|
-
}, 150);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const waitForInitialPayload = (ms = 15_000) =>
|
|
143
|
-
waitUntil(() => NS.initialPayload || false, ms, 'initial SSE payload');
|
|
144
|
-
|
|
145
|
-
const waitForAllCompleted = (ms = 60_000, label = 'all completed') =>
|
|
146
|
-
waitUntil(() => {
|
|
147
|
-
const s = NS.statusSummary;
|
|
148
|
-
if (s && s.card_count > 0 && s.completed === s.card_count) return s;
|
|
149
|
-
return false;
|
|
150
|
-
}, ms, label);
|
|
151
|
-
|
|
152
|
-
function httpGet(url) {
|
|
153
|
-
return new Promise((resolve, reject) => {
|
|
154
|
-
http.get(url, (res) => {
|
|
155
|
-
let body = '';
|
|
156
|
-
res.on('data', c => { body += c; });
|
|
157
|
-
res.on('end', () => {
|
|
158
|
-
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
|
|
159
|
-
catch { resolve({ status: res.statusCode, data: body }); }
|
|
160
|
-
});
|
|
161
|
-
}).on('error', reject);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function httpJson(method, url, payload) {
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
const u = new URL(url);
|
|
168
|
-
const data = payload != null ? JSON.stringify(payload) : null;
|
|
169
|
-
const opts = {
|
|
170
|
-
hostname: u.hostname,
|
|
171
|
-
port: u.port,
|
|
172
|
-
path: u.pathname,
|
|
173
|
-
method,
|
|
174
|
-
headers: data ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } : {},
|
|
175
|
-
};
|
|
176
|
-
const req = http.request(opts, (res) => {
|
|
177
|
-
let body = '';
|
|
178
|
-
res.on('data', c => { body += c; });
|
|
179
|
-
res.on('end', () => {
|
|
180
|
-
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
|
|
181
|
-
catch { resolve({ status: res.statusCode, data: body }); }
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
req.on('error', reject);
|
|
185
|
-
if (data) req.write(data);
|
|
186
|
-
req.end();
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function startServer(port) {
|
|
191
|
-
return new Promise((resolve, reject) => {
|
|
192
|
-
const proc = spawn(process.execPath, [SERVER_SCRIPT, '--cards-pattern', CARD_PATTERN], {
|
|
193
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
194
|
-
windowsHide: true,
|
|
195
|
-
env: { ...process.env, DEMO_SERVER_PORT: String(port), DEMO_SETUP_DIR: SETUP_DIR },
|
|
196
|
-
});
|
|
197
|
-
let ready = false;
|
|
198
|
-
|
|
199
|
-
proc.stdout.on('data', (chunk) => {
|
|
200
|
-
const text = chunk.toString('utf-8');
|
|
201
|
-
process.stdout.write(`[server] ${text}`);
|
|
202
|
-
if (!ready && text.includes('listening on')) {
|
|
203
|
-
ready = true;
|
|
204
|
-
resolve(proc);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
proc.stderr.on('data', (chunk) => process.stderr.write(`[server:err] ${chunk}`));
|
|
208
|
-
proc.on('error', reject);
|
|
209
|
-
proc.on('exit', (code) => {
|
|
210
|
-
if (!ready) reject(new Error(`Server exited early: code ${code}`));
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
setTimeout(() => {
|
|
214
|
-
if (!ready) reject(new Error('Server startup timeout (15s)'));
|
|
215
|
-
}, 15_000);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
console.log('\n=== board HTTP+SSE smoke test ===');
|
|
220
|
-
console.log(`target: ${BASE}`);
|
|
221
|
-
console.log(`card pattern: ${CARD_PATTERN}`);
|
|
222
|
-
|
|
223
|
-
const serverProc = await startServer(PORT);
|
|
224
|
-
let sseWorker = null;
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
console.log('\n=== Step 1: init-board ===');
|
|
228
|
-
const initRes = await httpGet(`${BASE}/init-board`);
|
|
229
|
-
assert(initRes.status === 200, `init-board returned ${initRes.status}`);
|
|
230
|
-
console.log('[step1] ok');
|
|
231
|
-
|
|
232
|
-
console.log('\n=== Step 2: start SSE worker ===');
|
|
233
|
-
sseWorker = new Worker(SSE_WORKER_SCRIPT, {
|
|
234
|
-
workerData: { sseUrl: `${BASE}/sse?clientId=e2e-demo-sse` },
|
|
235
|
-
});
|
|
236
|
-
sseWorker.on('message', (msg) => {
|
|
237
|
-
if (msg.type === 'frame') applyFrame(msg.payload);
|
|
238
|
-
else if (msg.type === 'error') console.error(`[sse-worker] ${msg.message}`);
|
|
239
|
-
});
|
|
240
|
-
sseWorker.on('error', (err) => console.error(`[sse-worker] uncaught: ${err.message}`));
|
|
241
|
-
|
|
242
|
-
const initialPayload = await waitForInitialPayload();
|
|
243
|
-
const cardCount = Array.isArray(initialPayload.cardDefinitions) ? initialPayload.cardDefinitions.length : 0;
|
|
244
|
-
assert(cardCount > 0, 'initial SSE payload must include cardDefinitions');
|
|
245
|
-
console.log(`[step2] SSE initial payload received (${cardCount} cards)`);
|
|
246
|
-
|
|
247
|
-
console.log('\n=== Step 3: wait for completion ===');
|
|
248
|
-
const summary = await waitForAllCompleted(20_000, 'initial board completion');
|
|
249
|
-
assert(summary.failed === 0, `expected failed=0, got ${summary.failed}`);
|
|
250
|
-
console.log(`[step3] completed summary: ${JSON.stringify(summary)}`);
|
|
251
|
-
|
|
252
|
-
console.log('\n=== Step 4: board-status cross-check ===');
|
|
253
|
-
const statusRes = await httpGet(`${BASE}/board-status`);
|
|
254
|
-
assert(statusRes.status === 200, `board-status returned ${statusRes.status}`);
|
|
255
|
-
const httpSummary = statusRes.data && statusRes.data.statusSnapshot && statusRes.data.statusSnapshot.summary;
|
|
256
|
-
assert(httpSummary, 'statusSnapshot.summary missing from board-status');
|
|
257
|
-
console.log(`[step4] board-status summary: ${JSON.stringify(httpSummary)}`);
|
|
258
|
-
|
|
259
|
-
// ── T1: PATCH holdings (+1 row) and verify recomputation ──
|
|
260
|
-
console.log('\n=== T1: patch holdings (+1 row) ===');
|
|
261
|
-
|
|
262
|
-
// Read current holdings from card store via GET /cards/:id
|
|
263
|
-
const portfolioCardRes = await httpGet(`${BASE}/cards/card-portfolio`);
|
|
264
|
-
assert(portfolioCardRes.status === 200, `GET card-portfolio returned ${portfolioCardRes.status}`);
|
|
265
|
-
const existingHoldings = (portfolioCardRes.data.card_data || {}).holdings;
|
|
266
|
-
assert(Array.isArray(existingHoldings), 'card-portfolio.card_data.holdings missing');
|
|
267
|
-
const t0HoldingsCount = existingHoldings.length;
|
|
268
|
-
|
|
269
|
-
// Read current positions count from computed_values captured via SSE
|
|
270
|
-
const t0Positions = (NS.computedValues['card-portfolio-value'] || {}).positions;
|
|
271
|
-
const t0PositionsCount = Array.isArray(t0Positions) ? t0Positions.length : 0;
|
|
272
|
-
|
|
273
|
-
// Pick a ticker not already in holdings
|
|
274
|
-
const candidates = ['AAPL', 'MSFT', 'AMZN', 'TSLA', 'META', 'GOOG', 'NVDA', 'NFLX', 'INTC', 'AMD',
|
|
275
|
-
'IBM', 'ORCL', 'ADBE', 'CRM', 'QCOM'];
|
|
276
|
-
const existingTickers = new Set(existingHoldings.map(r => r.ticker));
|
|
277
|
-
const available = candidates.filter(t => !existingTickers.has(t));
|
|
278
|
-
assert(available.length > 0, 'No available ticker to add');
|
|
279
|
-
const newTicker = available[0];
|
|
280
|
-
|
|
281
|
-
const newHoldings = [...existingHoldings, { ticker: newTicker, quantity: 1, cost_basis: 100 }];
|
|
282
|
-
const patchRes = await httpJson('PATCH', `${BASE}/cards/card-portfolio`, { card_data: { holdings: newHoldings } });
|
|
283
|
-
assert(patchRes.status === 200, `PATCH card-portfolio returned ${patchRes.status}`);
|
|
284
|
-
|
|
285
|
-
// Wait for re-completion
|
|
286
|
-
NS.statusSummary = null;
|
|
287
|
-
await new Promise(r => setTimeout(r, 4000));
|
|
288
|
-
const t1Summary = await waitForAllCompleted(30_000, 'T1 holdings patch');
|
|
289
|
-
assert(t1Summary.failed === 0, `T1 failed=${t1Summary.failed}`);
|
|
290
|
-
|
|
291
|
-
// Verify holdings +1 from card store
|
|
292
|
-
const t1PortfolioRes = await httpGet(`${BASE}/cards/card-portfolio`);
|
|
293
|
-
assert(t1PortfolioRes.status === 200, `GET card-portfolio after patch returned ${t1PortfolioRes.status}`);
|
|
294
|
-
const afterHoldings = (t1PortfolioRes.data.card_data || {}).holdings;
|
|
295
|
-
const afterHoldingsCount = Array.isArray(afterHoldings) ? afterHoldings.length : 0;
|
|
296
|
-
|
|
297
|
-
// Verify positions +1 from computed_values captured via SSE
|
|
298
|
-
const afterPositions = (NS.computedValues['card-portfolio-value'] || {}).positions;
|
|
299
|
-
const afterPositionsCount = Array.isArray(afterPositions) ? afterPositions.length : 0;
|
|
300
|
-
|
|
301
|
-
assert(afterHoldingsCount === t0HoldingsCount + 1,
|
|
302
|
-
`Expected holdings rows +1 (before=${t0HoldingsCount}, after=${afterHoldingsCount})`);
|
|
303
|
-
assert(afterPositionsCount === t0PositionsCount + 1,
|
|
304
|
-
`Expected portfolio value rows +1 (before=${t0PositionsCount}, after=${afterPositionsCount})`);
|
|
305
|
-
|
|
306
|
-
console.log(`[T1] ok: holdings ${t0HoldingsCount}->${afterHoldingsCount}, ` +
|
|
307
|
-
`positions ${t0PositionsCount}->${afterPositionsCount}, added=${newTicker}`);
|
|
308
|
-
|
|
309
|
-
console.log('\n=== All smoke checks passed ===\n');
|
|
310
|
-
} finally {
|
|
311
|
-
// Kill server first so the SSE connection closes, then await worker termination.
|
|
312
|
-
// (Terminating the worker while the SSE socket is still open leaves dangling handles.)
|
|
313
|
-
serverProc.kill();
|
|
314
|
-
await new Promise((r) => serverProc.on('exit', r));
|
|
315
|
-
if (sseWorker) await sseWorker.terminate();
|
|
316
|
-
console.log('[demo-http-test] server stopped');
|
|
317
|
-
}
|
/package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json
RENAMED
|
File without changes
|