yaml-flow 5.2.6 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/board-livecards-server-runtime.js +260 -35
- package/browser/board-livegraph-engine.js +61 -33
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/card-compute.js +18 -18
- package/browser/live-cards.js +317 -156
- package/browser/live-cards.schema.json +15 -10
- package/dist/board-livegraph-runtime/index.cjs +61 -33
- 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 +61 -33
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +101 -39
- 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 +101 -39
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +7205 -202
- 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 +7204 -202
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +59 -31
- 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 +59 -31
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +126 -54
- 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 +126 -54
- 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 +29 -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 +67 -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 +91 -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 +457 -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 +15 -10
|
@@ -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,67 @@
|
|
|
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\n{{view_kind_guidance}}\n\n{{card_layout_guidance}}\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- Include a top-level _view object for a ref element with this shape:\n { \"kind\": \"editable-table\"|\"table\"|\"chart\", \"data\": { ... } }\n- If kind is editable-table or table, include data.columns with the exact proposed_trades fields in desired order.\n- If kind is editable-table, also include data.writeTo = \"card_data.proposed_trades\".\n- If kind is chart, include data.chartType and data.columns as [\"ticker\", \"trade_value\"].\n- If unsure, choose editable-table.\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 \"_view\": {\n \"kind\": \"editable-table\",\n \"data\": {\n \"writeTo\": \"card_data.proposed_trades\",\n \"columns\": [\"ticker\", \"direction\", \"current_qty\", \"change\", \"proposed_qty\", \"price\", \"trade_value\", \"reason\"]\n }\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": "ref",
|
|
33
|
+
"label": "Proposed Trades",
|
|
34
|
+
"data": {
|
|
35
|
+
"bind": "fetched_sources.rebalance.proposed_trades",
|
|
36
|
+
"viewBind": "fetched_sources.rebalance._view",
|
|
37
|
+
"fallbackKind": "editable-table",
|
|
38
|
+
"writeTo": "card_data.proposed_trades",
|
|
39
|
+
"columns": ["ticker", "direction", "current_qty", "change", "proposed_qty", "price", "trade_value", "reason"],
|
|
40
|
+
"schema": {
|
|
41
|
+
"properties": {
|
|
42
|
+
"current_qty": { "type": "number" },
|
|
43
|
+
"change": { "type": "number" },
|
|
44
|
+
"proposed_qty": { "type": "number" },
|
|
45
|
+
"price": { "type": "number" },
|
|
46
|
+
"trade_value": { "type": "number" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"kind": "notes",
|
|
53
|
+
"label": "Notes",
|
|
54
|
+
"data": {
|
|
55
|
+
"bind": "card_data.notes",
|
|
56
|
+
"writeTo": "card_data.notes"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"layout": {
|
|
61
|
+
"board": { "col": 6, "order": 7 },
|
|
62
|
+
"canvas": { "x": 840, "y": 480, "w": 500, "h": 280 }
|
|
63
|
+
},
|
|
64
|
+
"features": { "refresh": true }
|
|
65
|
+
},
|
|
66
|
+
"card_data": {}
|
|
67
|
+
}
|
|
@@ -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,69 @@ 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
|
+
let responded = false;
|
|
172
|
+
const child = spawn(process.execPath, [workiqJs, 'ask', '-q', query], {
|
|
173
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
174
|
+
});
|
|
175
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
176
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
177
|
+
child.on('error', (err) => {
|
|
178
|
+
if (!responded) {
|
|
179
|
+
responded = true;
|
|
180
|
+
clearTimeout(timeoutId);
|
|
181
|
+
jsonReply(res, 500, { error: `workiq spawn error: ${err.message}` });
|
|
182
|
+
}
|
|
183
|
+
resolve();
|
|
184
|
+
});
|
|
185
|
+
child.on('close', (code) => {
|
|
186
|
+
if (!responded) {
|
|
187
|
+
responded = true;
|
|
188
|
+
clearTimeout(timeoutId);
|
|
189
|
+
if (code !== 0) {
|
|
190
|
+
jsonReply(res, 500, { error: `workiq exited ${code}`, stderr });
|
|
191
|
+
} else {
|
|
192
|
+
jsonReply(res, 200, { response: stdout });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
resolve();
|
|
196
|
+
});
|
|
197
|
+
const timeoutId = setTimeout(() => {
|
|
198
|
+
if (!responded) {
|
|
199
|
+
responded = true;
|
|
200
|
+
child.kill();
|
|
201
|
+
jsonReply(res, 504, { error: 'workiq timed out after 60s' });
|
|
202
|
+
}
|
|
203
|
+
resolve();
|
|
204
|
+
}, 60_000);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
132
208
|
const server = http.createServer((req, res) => {
|
|
133
209
|
const method = req.method || 'GET';
|
|
134
210
|
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
|
@@ -139,6 +215,12 @@ const server = http.createServer((req, res) => {
|
|
|
139
215
|
return;
|
|
140
216
|
}
|
|
141
217
|
|
|
218
|
+
// Route: POST /api/workiq/ask — proxy to WorkIQ (M365 Copilot) from server TTY
|
|
219
|
+
if (method === 'POST' && pathname === '/api/workiq/ask') {
|
|
220
|
+
void handleWorkiqAsk(req, res);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
142
224
|
// Route: demo-setup is handled here in demo-server (host concern)
|
|
143
225
|
const boardSegMatch = pathname.match(BOARD_SEG_RE);
|
|
144
226
|
if (boardSegMatch && boardSegMatch[2] === 'demo-setup') {
|
|
@@ -166,4 +248,5 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
166
248
|
console.log(` POST ${runtime.apiBasePath}/:boardId/cards/:id/files`);
|
|
167
249
|
console.log(` GET ${runtime.apiBasePath}/:boardId/cards/:id/files/:idx`);
|
|
168
250
|
console.log(` GET ${runtime.apiBasePath}/:boardId/cards/:id/chats`);
|
|
251
|
+
console.log(` POST /api/workiq/ask {query} <- WorkIQ (M365 Copilot) proxy`);
|
|
169
252
|
});
|
|
@@ -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.
|
|
10
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.
|
|
11
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/card-compute.js"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/live-cards.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/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.
|
|
20
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.
|
|
22
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/card-compute.js"></script>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/live-cards.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/board-livegraph-engine.js"></script>
|
|
22
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.3.0/browser/board-livecards-runtime-client.js"></script>
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-light">
|
|
25
25
|
<div class="container-fluid py-3">
|