yaml-flow 5.2.6 → 5.2.8
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/README.md +6 -6
- package/board-livecards-server-runtime.js +260 -35
- package/browser/board-livegraph-engine.js +57 -32
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/card-compute.js +17 -17
- package/browser/live-cards.js +139 -12
- package/browser/live-cards.schema.json +14 -9
- package/dist/board-livegraph-runtime/index.cjs +57 -32
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +1 -1
- package/dist/board-livegraph-runtime/index.d.ts +1 -1
- package/dist/board-livegraph-runtime/index.js +57 -32
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +96 -38
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +13 -8
- package/dist/card-compute/index.d.ts +13 -8
- package/dist/card-compute/index.js +96 -38
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +7200 -201
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +6 -6
- package/dist/cli/board-live-cards-cli.d.ts +6 -6
- package/dist/cli/board-live-cards-cli.js +7199 -201
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +55 -30
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +2 -2
- package/dist/continuous-event-graph/index.d.ts +2 -2
- package/dist/continuous-event-graph/index.js +55 -30
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +121 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +121 -53
- package/dist/index.js.map +1 -1
- package/dist/{live-cards-bridge-CeNxiVcm.d.ts → live-cards-bridge-EQjytzI_.d.ts} +10 -5
- package/dist/{live-cards-bridge-z_rJCSbi.d.cts → live-cards-bridge-x5XREkXm.d.cts} +10 -5
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +2 -2
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +10 -10
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +2 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
- package/examples/example-board/agent-instructions-cardlayout.md +1 -1
- package/examples/example-board/agent-instructions.md +271 -45
- package/examples/example-board/cards/card-concentration.json +8 -5
- package/examples/example-board/cards/card-market-prices.json +14 -9
- package/examples/example-board/cards/card-my-identity.json +28 -0
- package/examples/example-board/cards/card-portfolio-value.json +1 -1
- package/examples/example-board/cards/card-portfolio.json +1 -1
- package/examples/example-board/cards/card-rebalance-impact.json +65 -0
- package/examples/example-board/cards/card-rebalance-sim.json +57 -0
- package/examples/example-board/demo-chat-handler.js +2 -1
- package/examples/example-board/demo-server-config.json +6 -1
- package/examples/example-board/demo-server.js +79 -8
- package/examples/example-board/demo-shell-browser.html +6 -6
- package/examples/example-board/demo-shell-with-server.html +4 -4
- package/examples/example-board/demo-task-executor.js +436 -246
- package/examples/example-board/scripts/copilot_wrapper.bat +16 -0
- package/examples/example-board/scripts/copilot_wrapper_helper.ps1 +19 -10
- package/examples/example-board/scripts/workiq_wrapper.mjs +66 -0
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +5 -5
- package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +3 -3
- package/examples/npm-libs/event-graph/research-pipeline.ts +5 -5
- package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +9 -9
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
- package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
- package/package.json +2 -2
- package/schema/live-cards.schema.json +14 -9
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "card-rebalance-impact",
|
|
3
|
+
"meta": {
|
|
4
|
+
"title": "Rebalance Impact",
|
|
5
|
+
"tags": ["portfolio", "simulation"],
|
|
6
|
+
"simulation": true,
|
|
7
|
+
"desc": "Before/after impact summary of the proposed rebalance scenario."
|
|
8
|
+
},
|
|
9
|
+
"requires": ["positions", "proposed_trades"],
|
|
10
|
+
"compute": [
|
|
11
|
+
{
|
|
12
|
+
"bindTo": "total_current_value",
|
|
13
|
+
"expr": "$round($sum(requires.positions.value), 2)"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"bindTo": "trade_count",
|
|
17
|
+
"expr": "$count(requires.proposed_trades)"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"bindTo": "total_trade_value",
|
|
21
|
+
"expr": "$round($sum(requires.proposed_trades.trade_value), 2)"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"bindTo": "buys",
|
|
25
|
+
"expr": "$count($filter(requires.proposed_trades, function($t){ $t.direction = 'increase' }))"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"bindTo": "sells",
|
|
29
|
+
"expr": "$count($filter(requires.proposed_trades, function($t){ $t.direction = 'reduce' }))"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"bindTo": "max_weight_before",
|
|
33
|
+
"expr": "($tv := $sum(requires.positions.value); $round($max(requires.positions.value) / $tv * 100, 1))"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"bindTo": "post_positions",
|
|
37
|
+
"expr": "($tMap := $merge(requires.proposed_trades.{ticker: $}); $map(requires.positions, function($p) { ($t := $lookup($tMap, $p.ticker); $newQty := $t ? $t.proposed_qty : $p.quantity; {\"ticker\": $p.ticker, \"value\": $round($newQty * $p.price, 2)}) }))"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"bindTo": "max_weight_after",
|
|
41
|
+
"expr": "($tv := $sum(computed_values.post_positions.value); $round($max(computed_values.post_positions.value) / $tv * 100, 1))"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"bindTo": "est_tax_impact",
|
|
45
|
+
"expr": "$round($sum($filter(requires.proposed_trades, function($t){ $t.direction = 'reduce' }).trade_value) * 0.15, 2)"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"bindTo": "summary_md",
|
|
49
|
+
"expr": "'| Metric | Before | After |\\n|---|---|---|\\n| Top holding weight | ' & $string(computed_values.max_weight_before) & '% | ' & $string(computed_values.max_weight_after) & '% |\\n| Portfolio value | $' & $string(computed_values.total_current_value) & ' | ~$' & $string($round(computed_values.total_current_value - computed_values.est_tax_impact, 2)) & ' |\\n\\n**Trades:** ' & $string(computed_values.trade_count) & ' (' & $string(computed_values.buys) & ' buys, ' & $string(computed_values.sells) & ' sells) \\n**Total trade value:** $' & $string(computed_values.total_trade_value) & ' \\n**Est. tax impact:** ~$' & $string(computed_values.est_tax_impact)"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"view": {
|
|
53
|
+
"elements": [
|
|
54
|
+
{
|
|
55
|
+
"kind": "markdown",
|
|
56
|
+
"data": { "bind": "computed_values.summary_md" }
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"layout": {
|
|
60
|
+
"board": { "col": 6, "order": 8 },
|
|
61
|
+
"canvas": { "x": 1360, "y": 480, "w": 400, "h": 280 }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"card_data": {}
|
|
65
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "card-rebalance-sim",
|
|
3
|
+
"meta": {
|
|
4
|
+
"title": "Rebalance Proposal",
|
|
5
|
+
"tags": ["portfolio", "simulation"],
|
|
6
|
+
"simulation": true,
|
|
7
|
+
"desc": "AI-proposed rebalance trades based on portfolio intelligence. Edit quantities before pinning."
|
|
8
|
+
},
|
|
9
|
+
"requires": ["holdings", "portfolio_mix", "portfolio_risks", "portfolio_action", "positions"],
|
|
10
|
+
"source_defs": [
|
|
11
|
+
{
|
|
12
|
+
"bindTo": "rebalance",
|
|
13
|
+
"outputFile": "card-rebalance-proposal.json",
|
|
14
|
+
"projections": {
|
|
15
|
+
"holdings": "requires.holdings",
|
|
16
|
+
"positions": "requires.positions",
|
|
17
|
+
"portfolio_mix": "requires.portfolio_mix",
|
|
18
|
+
"portfolio_risks": "requires.portfolio_risks",
|
|
19
|
+
"portfolio_action": "requires.portfolio_action"
|
|
20
|
+
},
|
|
21
|
+
"copilot": {
|
|
22
|
+
"prompt_template": "You are a portfolio rebalance advisor. Using the intelligence analysis and current holdings below, propose specific trades.\n\nCurrent holdings:\n{{holdings}}\nFields: ticker, quantity, cost_basis ($).\n\nPositions (with live prices):\n{{positions}}\nFields: ticker, quantity, cost_basis, price ($), value ($), gain_$, gain_%, chg_$, chg_pct.\n\nPortfolio intelligence:\n- Mix: {{portfolio_mix}}\n- Risks: {{portfolio_risks}}\n- Action signal: {{portfolio_action}}\n\nIMPORTANT OUTPUT RULES:\n- Return ONLY a valid JSON object. No markdown, no preamble, no trailing text.\n- Start with { and end with }.\n- proposed_trades must be an array of objects with EXACTLY these fields per entry:\n ticker (string), direction (\"reduce\" or \"increase\"), current_qty (number, current shares held), change (number, shares to trade), proposed_qty (number, resulting shares after trade), price (number, current price per share), trade_value (number, change * price rounded to 2 decimals), reason (string, one short sentence).\n- Include one entry per ticker you recommend changing. Do not include tickers with no change.\n\n{\n \"proposed_trades\": [\n {\"ticker\": \"AAPL\", \"direction\": \"reduce\", \"current_qty\": 15, \"change\": 5, \"proposed_qty\": 10, \"price\": 210.50, \"trade_value\": 1052.50, \"reason\": \"overweight at 41%, trim before earnings\"}\n ]\n}"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"provides": [
|
|
27
|
+
{ "bindTo": "proposed_trades", "ref": "fetched_sources.rebalance.proposed_trades" }
|
|
28
|
+
],
|
|
29
|
+
"view": {
|
|
30
|
+
"elements": [
|
|
31
|
+
{
|
|
32
|
+
"kind": "editable-table",
|
|
33
|
+
"label": "Proposed Trades",
|
|
34
|
+
"data": {
|
|
35
|
+
"bind": "fetched_sources.rebalance.proposed_trades",
|
|
36
|
+
"writeTo": "card_data.proposed_trades",
|
|
37
|
+
"columns": ["ticker", "direction", "current_qty", "change", "proposed_qty", "price", "trade_value", "reason"],
|
|
38
|
+
"schema": {
|
|
39
|
+
"properties": {
|
|
40
|
+
"current_qty": { "type": "number" },
|
|
41
|
+
"change": { "type": "number" },
|
|
42
|
+
"proposed_qty": { "type": "number" },
|
|
43
|
+
"price": { "type": "number" },
|
|
44
|
+
"trade_value": { "type": "number" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"layout": {
|
|
51
|
+
"board": { "col": 6, "order": 7 },
|
|
52
|
+
"canvas": { "x": 840, "y": 480, "w": 500, "h": 280 }
|
|
53
|
+
},
|
|
54
|
+
"features": { "refresh": true, "notes": true }
|
|
55
|
+
},
|
|
56
|
+
"card_data": {}
|
|
57
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* cardsDir — relative subdir: 'surface/tmp-cards'
|
|
12
12
|
* chatDir — absolute path to the card's chats directory
|
|
13
13
|
* lastChatFile — filename of the just-written user message, e.g. '001_user.txt'
|
|
14
|
+
* serverUrl — base URL of hosting server (e.g. http://127.0.0.1:7799), optional
|
|
14
15
|
*
|
|
15
16
|
* Invokes copilot_wrapper.bat with a prompt built from conversation history.
|
|
16
17
|
* Session dir is per-card: os.tmpdir()/demo-chat-handler-sessions/<boardId>_<cardId>
|
|
@@ -42,7 +43,7 @@ let extra = {};
|
|
|
42
43
|
try { extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8')); }
|
|
43
44
|
catch { console.error('[demo-chat-handler] bad --extraEncJson'); process.exit(0); }
|
|
44
45
|
|
|
45
|
-
const { boardSetupRoot, boardRuntimeDir, runtimeStatusDir, cardsDir, chatDir, lastChatFile } = extra;
|
|
46
|
+
const { boardSetupRoot, boardRuntimeDir, runtimeStatusDir, cardsDir, chatDir, lastChatFile, serverUrl } = extra;
|
|
46
47
|
if (!boardSetupRoot || !chatDir || !lastChatFile) {
|
|
47
48
|
console.error('[demo-chat-handler] missing boardSetupRoot/chatDir/lastChatFile');
|
|
48
49
|
process.exit(0);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"port": 7799,
|
|
3
|
+
"cardsDir": "./cards",
|
|
3
4
|
"taskExecutorPath": "./demo-task-executor.js",
|
|
4
|
-
"chatHandlerPath": "./demo-chat-handler.js"
|
|
5
|
+
"chatHandlerPath": "./demo-chat-handler.js",
|
|
6
|
+
"chatSessionsDir": "",
|
|
7
|
+
"gandalfCardsDir": "./gandalf-cards",
|
|
8
|
+
"gandalfTaskExecutorPath": "./demo-task-executor.js",
|
|
9
|
+
"gandalfChatHandlerPath": "./demo-chat-handler.js"
|
|
5
10
|
}
|
|
@@ -4,6 +4,7 @@ import http from 'node:http';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import os from 'node:os';
|
|
7
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { createRequire } from 'node:module';
|
|
9
10
|
|
|
@@ -48,10 +49,15 @@ function resolveFromConfig(configValue) {
|
|
|
48
49
|
|
|
49
50
|
const serverConfig = loadServerConfig();
|
|
50
51
|
const configuredCliJs = resolveFromConfig(serverConfig.boardLiveCardsCliJs) || _pkgCliJs;
|
|
52
|
+
const configuredCardsDir = resolveFromConfig(serverConfig.cardsDir);
|
|
51
53
|
const configuredTaskExecutorPath = resolveFromConfig(serverConfig.taskExecutorPath || serverConfig.demoTaskExecutorPath);
|
|
52
54
|
const configuredStepMachineCliPath = resolveFromConfig(serverConfig.stepMachineCliPath) || _pkgStepMachineCli;
|
|
53
55
|
const configuredChatHandlerPath = resolveFromConfig(serverConfig.chatHandlerPath);
|
|
54
56
|
const configuredInferenceAdapterPath = resolveFromConfig(serverConfig.inferenceAdapterPath);
|
|
57
|
+
const configuredGandalfCardsDir = resolveFromConfig(serverConfig.gandalfCardsDir);
|
|
58
|
+
const configuredGandalfTaskExecutorPath = resolveFromConfig(serverConfig.gandalfTaskExecutorPath);
|
|
59
|
+
const configuredGandalfChatHandlerPath = resolveFromConfig(serverConfig.gandalfChatHandlerPath);
|
|
60
|
+
const configuredGandalfInferenceAdapterPath = resolveFromConfig(serverConfig.gandalfInferenceAdapterPath);
|
|
55
61
|
|
|
56
62
|
if (!process.env.BOARD_LIVE_CARDS_CLI_JS && configuredCliJs) {
|
|
57
63
|
process.env.BOARD_LIVE_CARDS_CLI_JS = configuredCliJs;
|
|
@@ -77,11 +83,16 @@ const CORS_HEADERS = {
|
|
|
77
83
|
|
|
78
84
|
const runtime = createMultiBoardServerRuntime({
|
|
79
85
|
apiBasePath: '/api/boards',
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
serverUrl: `http://127.0.0.1:${PORT}`,
|
|
87
|
+
defaultCardsDir: process.env.DEMO_CARDS_DIR || configuredCardsDir || null,
|
|
88
|
+
defaultTaskExecutorPath: process.env.DEMO_TASK_EXECUTOR_PATH || configuredTaskExecutorPath || null,
|
|
82
89
|
defaultStepMachineCliPath: process.env.DEMO_STEP_MACHINE_CLI_PATH || configuredStepMachineCliPath,
|
|
83
|
-
defaultChatHandlerPath: process.env.DEMO_CHAT_HANDLER_PATH || configuredChatHandlerPath ||
|
|
90
|
+
defaultChatHandlerPath: process.env.DEMO_CHAT_HANDLER_PATH || configuredChatHandlerPath || null,
|
|
84
91
|
defaultInferenceAdapterPath: process.env.DEMO_INFERENCE_ADAPTER_PATH || configuredInferenceAdapterPath || null,
|
|
92
|
+
defaultGandalfCardsDir: process.env.DEMO_GANDALF_CARDS_DIR || configuredGandalfCardsDir || null,
|
|
93
|
+
defaultGandalfTaskExecutorPath: process.env.DEMO_GANDALF_TASK_EXECUTOR_PATH || configuredGandalfTaskExecutorPath || null,
|
|
94
|
+
defaultGandalfChatHandlerPath: process.env.DEMO_GANDALF_CHAT_HANDLER_PATH || configuredGandalfChatHandlerPath || null,
|
|
95
|
+
defaultGandalfInferenceAdapterPath: process.env.DEMO_GANDALF_INFERENCE_ADAPTER_PATH || configuredGandalfInferenceAdapterPath || null,
|
|
85
96
|
boardLiveCardsCliJs: process.env.BOARD_LIVE_CARDS_CLI_JS || configuredCliJs,
|
|
86
97
|
});
|
|
87
98
|
|
|
@@ -91,10 +102,12 @@ function resetRuntime() {
|
|
|
91
102
|
fs.rmSync(setupDir, { recursive: true, force: true });
|
|
92
103
|
console.log(`[demo-server] reset: wiped ${setupDir}`);
|
|
93
104
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
const chatSessionsDir = serverConfig.chatSessionsDir
|
|
106
|
+
? path.resolve(__dirname, serverConfig.chatSessionsDir)
|
|
107
|
+
: path.join(os.tmpdir(), 'demo-chat-handler-sessions');
|
|
108
|
+
if (fs.existsSync(chatSessionsDir)) {
|
|
109
|
+
fs.rmSync(chatSessionsDir, { recursive: true, force: true });
|
|
110
|
+
console.log(`[demo-server] reset: wiped ${chatSessionsDir}`);
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
113
|
|
|
@@ -118,7 +131,7 @@ async function handleDemoSetup(req, res, boardId) {
|
|
|
118
131
|
const { service, boardRoot } = runtime.requireBoardService(boardId);
|
|
119
132
|
let setupPerformed = false;
|
|
120
133
|
|
|
121
|
-
if (!
|
|
134
|
+
if (!service.isDemoSetupDone()) {
|
|
122
135
|
service.ensureDemoSetup();
|
|
123
136
|
setupPerformed = true;
|
|
124
137
|
}
|
|
@@ -129,6 +142,57 @@ async function handleDemoSetup(req, res, boardId) {
|
|
|
129
142
|
}
|
|
130
143
|
}
|
|
131
144
|
|
|
145
|
+
async function handleWorkiqAsk(req, res) {
|
|
146
|
+
let body = '';
|
|
147
|
+
for await (const chunk of req) body += chunk;
|
|
148
|
+
let query;
|
|
149
|
+
try {
|
|
150
|
+
query = JSON.parse(body).query;
|
|
151
|
+
} catch {
|
|
152
|
+
return jsonReply(res, 400, { error: 'Invalid JSON body' });
|
|
153
|
+
}
|
|
154
|
+
if (!query || typeof query !== 'string') {
|
|
155
|
+
return jsonReply(res, 400, { error: '{ query } string is required' });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const workiqJs = path.join(
|
|
159
|
+
process.env.APPDATA || os.homedir(),
|
|
160
|
+
'npm', 'node_modules', '@microsoft', 'workiq', 'bin', 'workiq.js'
|
|
161
|
+
);
|
|
162
|
+
if (!fs.existsSync(workiqJs)) {
|
|
163
|
+
return jsonReply(res, 503, { error: `WorkIQ CLI not found at: ${workiqJs}` });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Server has TTY on stdin — workiq can produce output.
|
|
167
|
+
// Use async spawn (not spawnSync) to avoid blocking the event loop during the call.
|
|
168
|
+
await new Promise((resolve) => {
|
|
169
|
+
let stdout = '';
|
|
170
|
+
let stderr = '';
|
|
171
|
+
const child = spawn(process.execPath, [workiqJs, 'ask', '-q', query], {
|
|
172
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
173
|
+
});
|
|
174
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
175
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
176
|
+
child.on('error', (err) => {
|
|
177
|
+
jsonReply(res, 500, { error: `workiq spawn error: ${err.message}` });
|
|
178
|
+
resolve();
|
|
179
|
+
});
|
|
180
|
+
child.on('close', (code) => {
|
|
181
|
+
if (code !== 0) {
|
|
182
|
+
jsonReply(res, 500, { error: `workiq exited ${code}`, stderr });
|
|
183
|
+
} else {
|
|
184
|
+
jsonReply(res, 200, { response: stdout });
|
|
185
|
+
}
|
|
186
|
+
resolve();
|
|
187
|
+
});
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
child.kill();
|
|
190
|
+
jsonReply(res, 504, { error: 'workiq timed out after 60s' });
|
|
191
|
+
resolve();
|
|
192
|
+
}, 60_000);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
132
196
|
const server = http.createServer((req, res) => {
|
|
133
197
|
const method = req.method || 'GET';
|
|
134
198
|
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
|
@@ -139,6 +203,12 @@ const server = http.createServer((req, res) => {
|
|
|
139
203
|
return;
|
|
140
204
|
}
|
|
141
205
|
|
|
206
|
+
// Route: POST /api/workiq/ask — proxy to WorkIQ (M365 Copilot) from server TTY
|
|
207
|
+
if (method === 'POST' && pathname === '/api/workiq/ask') {
|
|
208
|
+
void handleWorkiqAsk(req, res);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
142
212
|
// Route: demo-setup is handled here in demo-server (host concern)
|
|
143
213
|
const boardSegMatch = pathname.match(BOARD_SEG_RE);
|
|
144
214
|
if (boardSegMatch && boardSegMatch[2] === 'demo-setup') {
|
|
@@ -166,4 +236,5 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
166
236
|
console.log(` POST ${runtime.apiBasePath}/:boardId/cards/:id/files`);
|
|
167
237
|
console.log(` GET ${runtime.apiBasePath}/:boardId/cards/:id/files/:idx`);
|
|
168
238
|
console.log(` GET ${runtime.apiBasePath}/:boardId/cards/:id/chats`);
|
|
239
|
+
console.log(` POST /api/workiq/ask {query} <- WorkIQ (M365 Copilot) proxy`);
|
|
169
240
|
});
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<title>Example Board Demo (Browser Runtime)</title>
|
|
7
7
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
10
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
11
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/card-compute.js"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/live-cards.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livegraph-engine.js"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body class="bg-light">
|
|
14
14
|
<div class="container-fluid py-3">
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
<div class="alert alert-info small py-2 mb-3">
|
|
34
|
-
Browser runtime mode:
|
|
34
|
+
Browser runtime mode: source_defs are executed in-browser through an opaque task executor.
|
|
35
35
|
Card source definitions are treated as task-executor-owned metadata.
|
|
36
36
|
</div>
|
|
37
37
|
|
|
@@ -341,7 +341,7 @@
|
|
|
341
341
|
return cards.every((card) => {
|
|
342
342
|
const artifact = cardRuntimeById[card.id];
|
|
343
343
|
if (!artifact || typeof artifact !== 'object') return false;
|
|
344
|
-
if (card.
|
|
344
|
+
if (card.source_defs && card.source_defs.length > 0) {
|
|
345
345
|
if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources))) return false;
|
|
346
346
|
}
|
|
347
347
|
if (card.requires && card.requires.length > 0) {
|
|
@@ -517,7 +517,7 @@
|
|
|
517
517
|
runtime = createBoardLiveGraphRuntime(loaded.cards, {
|
|
518
518
|
taskExecutor: async function ({ card }) {
|
|
519
519
|
const out = {};
|
|
520
|
-
for (const sourceDef of (card.
|
|
520
|
+
for (const sourceDef of (card.source_defs || [])) {
|
|
521
521
|
const payload = await mockServer.fetchSource(card, sourceDef);
|
|
522
522
|
if (payload !== null && payload !== undefined) out[sourceDef.bindTo] = payload;
|
|
523
523
|
}
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
</style>
|
|
17
17
|
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
18
18
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
19
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
20
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
22
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/card-compute.js"></script>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/live-cards.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livegraph-engine.js"></script>
|
|
22
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livecards-runtime-client.js"></script>
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-light">
|
|
25
25
|
<div class="container-fluid py-3">
|