yaml-flow 5.2.5 → 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.
Files changed (84) hide show
  1. package/README.md +6 -6
  2. package/board-livecards-server-runtime.js +260 -35
  3. package/browser/board-livegraph-engine.js +57 -32
  4. package/browser/board-livegraph-engine.js.map +1 -1
  5. package/browser/card-compute.js +17 -17
  6. package/browser/live-cards.js +139 -12
  7. package/browser/live-cards.schema.json +14 -9
  8. package/dist/board-livegraph-runtime/index.cjs +57 -32
  9. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  10. package/dist/board-livegraph-runtime/index.d.cts +1 -1
  11. package/dist/board-livegraph-runtime/index.d.ts +1 -1
  12. package/dist/board-livegraph-runtime/index.js +57 -32
  13. package/dist/board-livegraph-runtime/index.js.map +1 -1
  14. package/dist/card-compute/index.cjs +96 -38
  15. package/dist/card-compute/index.cjs.map +1 -1
  16. package/dist/card-compute/index.d.cts +13 -8
  17. package/dist/card-compute/index.d.ts +13 -8
  18. package/dist/card-compute/index.js +96 -38
  19. package/dist/card-compute/index.js.map +1 -1
  20. package/dist/cli/board-live-cards-cli.cjs +7200 -201
  21. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  22. package/dist/cli/board-live-cards-cli.d.cts +6 -6
  23. package/dist/cli/board-live-cards-cli.d.ts +6 -6
  24. package/dist/cli/board-live-cards-cli.js +7199 -201
  25. package/dist/cli/board-live-cards-cli.js.map +1 -1
  26. package/dist/continuous-event-graph/index.cjs +55 -30
  27. package/dist/continuous-event-graph/index.cjs.map +1 -1
  28. package/dist/continuous-event-graph/index.d.cts +2 -2
  29. package/dist/continuous-event-graph/index.d.ts +2 -2
  30. package/dist/continuous-event-graph/index.js +55 -30
  31. package/dist/continuous-event-graph/index.js.map +1 -1
  32. package/dist/index.cjs +121 -53
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +121 -53
  37. package/dist/index.js.map +1 -1
  38. package/dist/{live-cards-bridge-CeNxiVcm.d.ts → live-cards-bridge-EQjytzI_.d.ts} +10 -5
  39. package/dist/{live-cards-bridge-z_rJCSbi.d.cts → live-cards-bridge-x5XREkXm.d.cts} +10 -5
  40. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
  41. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +1 -1
  42. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +1 -1
  43. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
  44. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +2 -2
  45. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +1 -1
  46. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +10 -10
  47. package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  48. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
  49. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  50. package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +2 -2
  51. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
  52. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
  53. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
  54. package/examples/example-board/agent-instructions-cardlayout.md +1 -1
  55. package/examples/example-board/agent-instructions.md +271 -45
  56. package/examples/example-board/cards/card-concentration.json +8 -5
  57. package/examples/example-board/cards/card-market-prices.json +14 -9
  58. package/examples/example-board/cards/card-my-identity.json +28 -0
  59. package/examples/example-board/cards/card-portfolio-value.json +1 -1
  60. package/examples/example-board/cards/card-portfolio.json +1 -1
  61. package/examples/example-board/cards/card-rebalance-impact.json +65 -0
  62. package/examples/example-board/cards/card-rebalance-sim.json +57 -0
  63. package/examples/example-board/demo-chat-handler.js +2 -1
  64. package/examples/example-board/demo-server-config.json +6 -1
  65. package/examples/example-board/demo-server.js +79 -8
  66. package/examples/example-board/demo-shell-browser.html +6 -6
  67. package/examples/example-board/demo-shell-with-server.html +4 -4
  68. package/examples/example-board/demo-task-executor.js +436 -246
  69. package/examples/example-board/scripts/copilot_wrapper.bat +157 -0
  70. package/examples/example-board/scripts/copilot_wrapper_helper.ps1 +190 -0
  71. package/examples/example-board/scripts/workiq_wrapper.mjs +66 -0
  72. package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +5 -5
  73. package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +3 -3
  74. package/examples/npm-libs/event-graph/research-pipeline.ts +5 -5
  75. package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +9 -9
  76. package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  77. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
  78. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  79. package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
  80. package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
  81. package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
  82. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
  83. package/package.json +2 -2
  84. package/schema/live-cards.schema.json +14 -9
@@ -0,0 +1,157 @@
1
+ @echo off
2
+ setlocal enabledelayedexpansion
3
+
4
+ REM Copilot Wrapper - Manages session isolation for GitHub Copilot CLI
5
+ REM Usage: copilot_wrapper.bat <output_file> <session_dir> <working_dir> <request_or_file> <result_type> [agent_name] [model] [result_shape_file]
6
+ REM
7
+ REM If request_or_file starts with @, it's treated as a file path containing the prompt.
8
+ REM Otherwise, it's treated as the prompt string directly.
9
+ REM result_type: "raw" to return plain text, "json" to extract JSON (passed to clean_copilot_output.ps1)
10
+ REM agent_name: name of the agent for log files (optional)
11
+ REM model: passed to copilot with --model flag (optional)
12
+
13
+ SET "OUTPUT_FILE=%~1"
14
+ SET "SESSION_DIR=%~2"
15
+ SET "WORKING_DIR=%~3"
16
+ SET "REQUEST_OR_FILE=%~4"
17
+ SET "RESULT_TYPE=%~5"
18
+ SET "AGENT_NAME=%~6"
19
+ SET "MODEL=%~7"
20
+ SET "RESULT_SHAPE_FILE=%~8"
21
+
22
+ if not defined RESULT_TYPE SET "RESULT_TYPE=raw"
23
+
24
+ SET "PROMPT_FILE="
25
+ SET "REQUEST="
26
+ echo !REQUEST_OR_FILE! | findstr /b "@" >nul
27
+ if !errorlevel! equ 0 (
28
+ SET "PROMPT_FILE=!REQUEST_OR_FILE:~1!"
29
+ ) else (
30
+ SET "REQUEST=!REQUEST_OR_FILE!"
31
+ )
32
+
33
+ SET "_WD_HASH=!WORKING_DIR:\=!"
34
+ SET "_WD_HASH=!_WD_HASH:/=!"
35
+ SET "_WD_HASH=!_WD_HASH::=!"
36
+ SET "_WD_HASH=!_WD_HASH:.=!"
37
+ SET "_WD_HASH=!_WD_HASH: =!"
38
+ SET "COPILOT_BASE=%TEMP%\copilot-sessions\!_WD_HASH!"
39
+ SET "COPILOT_CACHE=%COPILOT_BASE%\session-state"
40
+ SET "LOCK_FILE=%COPILOT_BASE%\copilot.lock"
41
+ SET "UUID_FILE=%SESSION_DIR%\session.uuid"
42
+
43
+ if not exist "%COPILOT_BASE%" mkdir "%COPILOT_BASE%"
44
+ if not exist "%COPILOT_CACHE%" mkdir "%COPILOT_CACHE%"
45
+ if not exist "%SESSION_DIR%" mkdir "%SESSION_DIR%"
46
+
47
+ if exist "%LOCK_FILE%" (
48
+ for /f "tokens=*" %%a in ('powershell -NoProfile -Command "if ((Get-Item '%LOCK_FILE%').LastWriteTime -lt (Get-Date).AddMinutes(-20)) { Write-Output 'STALE' }"') do (
49
+ if "%%a"=="STALE" (
50
+ del "%LOCK_FILE%" 2>nul
51
+ )
52
+ )
53
+ )
54
+ :acquire_lock
55
+ 2>nul (
56
+ >"%LOCK_FILE%" (
57
+ echo %DATE% %TIME%
58
+ )
59
+ ) || (
60
+ timeout /t 1 /nobreak >nul
61
+ goto acquire_lock
62
+ )
63
+
64
+ SET "SESSION_UUID="
65
+ if exist "%UUID_FILE%" (
66
+ set /p SESSION_UUID=<"%UUID_FILE%"
67
+ ) else (
68
+ for /f "tokens=*" %%a in ('powershell -NoProfile -Command "[guid]::NewGuid().ToString()"') do (
69
+ SET "SESSION_UUID=%%a"
70
+ )
71
+ echo !SESSION_UUID!>"%UUID_FILE%"
72
+ )
73
+
74
+ SET "CACHE_SESSION_PATH=%COPILOT_CACHE%\!SESSION_UUID!"
75
+
76
+ if exist "%SESSION_DIR%\workspace.yaml" (
77
+ if exist "!CACHE_SESSION_PATH!" rmdir /s /q "!CACHE_SESSION_PATH!" 2>nul
78
+ mkdir "!CACHE_SESSION_PATH!" 2>nul
79
+ for %%f in ("%SESSION_DIR%\*") do (
80
+ if /i not "%%~nxf"=="session.uuid" (
81
+ move /y "%%f" "!CACHE_SESSION_PATH!\" >nul 2>&1
82
+ )
83
+ )
84
+ for /d %%d in ("%SESSION_DIR%\*") do (
85
+ robocopy "%%d" "!CACHE_SESSION_PATH!\%%~nxd" /E /MOVE /NFL /NDL /NJH /NJS >nul 2>&1
86
+ )
87
+ )
88
+
89
+ cd /d "%WORKING_DIR%"
90
+
91
+ SET "MODEL_FLAG="
92
+ if defined MODEL (
93
+ SET "MODEL_FLAG=--model !MODEL!"
94
+ )
95
+
96
+ if defined PROMPT_FILE (
97
+ type "!PROMPT_FILE!" | call copilot --allow-all --resume !SESSION_UUID! !MODEL_FLAG! > "%OUTPUT_FILE%" 2>&1
98
+ ) else (
99
+ call copilot -p "%REQUEST%" --allow-all --resume !SESSION_UUID! !MODEL_FLAG! > "%OUTPUT_FILE%" 2>&1
100
+ )
101
+
102
+ SET "LOG_DIR=%COPILOT_BASE%\copilot-logs"
103
+ if not exist "!LOG_DIR!" mkdir "!LOG_DIR!"
104
+ SET "LOG_AGENT=unknown"
105
+ if defined AGENT_NAME SET "LOG_AGENT=!AGENT_NAME!"
106
+ for /f "tokens=*" %%t in ('powershell -NoProfile -Command "Get-Date -Format 'yyyyMMdd-HHmmss'"') do SET "LOG_TS=%%t"
107
+ SET "LOG_FILE=!LOG_DIR!\!LOG_AGENT!_!LOG_TS!.log"
108
+ echo === PROMPT (!LOG_TS!) === > "!LOG_FILE!"
109
+ echo Agent: !LOG_AGENT! >> "!LOG_FILE!"
110
+ echo ResultType: !RESULT_TYPE! >> "!LOG_FILE!"
111
+ echo Working Dir: %WORKING_DIR% >> "!LOG_FILE!"
112
+ echo --- >> "!LOG_FILE!"
113
+ if defined PROMPT_FILE (
114
+ type "!PROMPT_FILE!" >> "!LOG_FILE!" 2>nul
115
+ ) else (
116
+ echo %REQUEST% >> "!LOG_FILE!"
117
+ )
118
+ echo. >> "!LOG_FILE!"
119
+ echo === RESPONSE === >> "!LOG_FILE!"
120
+ type "%OUTPUT_FILE%" >> "!LOG_FILE!" 2>nul
121
+ echo. >> "!LOG_FILE!"
122
+ echo === END === >> "!LOG_FILE!"
123
+ for /f "skip=50 tokens=*" %%f in ('dir /b /o-d "!LOG_DIR!\!LOG_AGENT!_*.log" 2^>nul') do (
124
+ del "!LOG_DIR!\%%f" 2>nul
125
+ )
126
+
127
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0copilot_wrapper_helper.ps1" "%OUTPUT_FILE%" "!RESULT_TYPE!" "!RESULT_SHAPE_FILE!"
128
+
129
+ REM If JSON extraction failed (exit 2) and result_type is json, retry once with a
130
+ REM correction prompt in the same session (--resume SESSION_UUID continues the conversation).
131
+ SET "PS1_EXIT=!ERRORLEVEL!"
132
+ if "!PS1_EXIT!"=="2" (
133
+ SET "RETRY_PROMPT_FILE=%TEMP%\copilot-retry-!RANDOM!.txt"
134
+ (
135
+ echo Your previous response did not contain a valid JSON object.
136
+ echo Please respond with ONLY the JSON object — no markdown, no explanation, no preamble.
137
+ echo Start your response with { and end with }.
138
+ ) > "!RETRY_PROMPT_FILE!"
139
+ type "!RETRY_PROMPT_FILE!" | call copilot --allow-all --resume !SESSION_UUID! !MODEL_FLAG! > "%OUTPUT_FILE%" 2>&1
140
+ del "!RETRY_PROMPT_FILE!" 2>nul
141
+ REM Re-run PS1 with -IsRetry so it writes skeleton on second failure rather than exit 2
142
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0copilot_wrapper_helper.ps1" "%OUTPUT_FILE%" "!RESULT_TYPE!" "!RESULT_SHAPE_FILE!" "-IsRetry"
143
+ )
144
+
145
+ if exist "!CACHE_SESSION_PATH!" (
146
+ for %%f in ("!CACHE_SESSION_PATH!\*") do (
147
+ move /y "%%f" "%SESSION_DIR%\" >nul 2>&1
148
+ )
149
+ for /d %%d in ("!CACHE_SESSION_PATH!\*") do (
150
+ robocopy "%%d" "%SESSION_DIR%\%%~nxd" /E /MOVE /NFL /NDL /NJH /NJS >nul 2>&1
151
+ )
152
+ rmdir "!CACHE_SESSION_PATH!" 2>nul
153
+ )
154
+
155
+ del "%LOCK_FILE%" 2>nul
156
+
157
+ endlocal
@@ -0,0 +1,190 @@
1
+ # clean_copilot_output.ps1
2
+ # Cleans copilot CLI output: filters noise lines and stats footer.
3
+ # Called by copilot_wrapper.bat after copilot runs and after logging (raw log preserved).
4
+ #
5
+ # Usage: clean_copilot_output.ps1 <output_file> <result_type> [result_shape_file]
6
+ # output_file - file containing raw copilot output; overwritten with cleaned result
7
+ # result_type=raw - strip noise + stats, write plain text back to output_file
8
+ # result_type=json - extract first JSON object whose keys match result_shape;
9
+ # if result_shape_file is absent, accepts any valid JSON object
10
+ # result_shape_file - (json result_type only) JSON file whose top-level keys are required in output
11
+ #
12
+ # raw result_type: right for chat responses and task executor source_defs.
13
+ # json result_type: right for structured calls where the input contained {prompt, result_shape}.
14
+
15
+ param(
16
+ [Parameter(Mandatory)][string]$OutputFile,
17
+ [Parameter(Mandatory)][string]$ResultType,
18
+ [string]$ResultShapeFile = '',
19
+ # When $true (retry pass), write shape-skeleton fallback instead of exiting with code 2.
20
+ # On first pass, exit 2 signals the caller (.bat) to retry with a correction prompt.
21
+ [switch]$IsRetry
22
+ )
23
+
24
+ if (-not (Test-Path $OutputFile)) { exit 0 }
25
+
26
+ $raw = [IO.File]::ReadAllText($OutputFile, [Text.Encoding]::UTF8)
27
+ if ([string]::IsNullOrWhiteSpace($raw)) { exit 0 }
28
+
29
+ # --- Step 1: Filter noise lines ---
30
+ $lines = $raw -split "`r?`n" | Where-Object {
31
+ $_ -notmatch "^error: unknown option '--no-warnings'" -and
32
+ $_ -notmatch "^Try 'copilot --help' for more information"
33
+ }
34
+ $cleaned = ($lines -join "`n").Trim()
35
+
36
+ # --- Step 1b: Strip copilot-cli tool operation lines ---
37
+ # These are internal tool invocations that leak into output:
38
+ # ● Create/Read/Edit/List directory/Glob/Check ...
39
+ # X Read ... (failed tool ops)
40
+ # $ Get-Content/Set-Content ... (PowerShell invocations)
41
+ # └ N lines/files found
42
+ # ├ ... (tree lines)
43
+ # "The agent decision has been simulated and saved to ..."
44
+ # session-state file paths
45
+ $noiseLines = New-Object System.Collections.Generic.List[string]
46
+ $contentLines2 = New-Object System.Collections.Generic.List[string]
47
+ foreach ($line in ($cleaned -split "`n")) {
48
+ $t = $line.TrimStart()
49
+ if ($t -match '^[\u25cf\u2022] ' -or # ● bullet tool ops
50
+ $t -match '^X ' -or # X failed tool ops
51
+ $t -match '^\$ ' -or # $ shell commands
52
+ $t -match '^[\u2514\u251c]' -or # └ ├ tree lines
53
+ $t -match 'session-state.*\.json' -or # session-state file projections
54
+ $t -match 'agent.decision has been simulated' -or
55
+ $t -match 'has been simulated and saved' -or
56
+ $t -match '^\d+ (files?|lines?|matches?) found$' -or # "3 files found"
57
+ $t -match '^No matches found$' -or
58
+ $t -match '^Path does not exist$' -or
59
+ $t -match '^\d+ lines?( read)?$') { # "1 line read"
60
+ $noiseLines.Add($line)
61
+ } else {
62
+ $contentLines2.Add($line)
63
+ }
64
+ }
65
+ $cleaned = ($contentLines2 -join "`n").Trim()
66
+
67
+ # Write noise to sidecar file for upstream visibility
68
+ $NoiseFile = $OutputFile + '.noise'
69
+ if ($noiseLines.Count -gt 0) {
70
+ $noiseContent = "STRIPPED_LINES=$($noiseLines.Count)`n" + ($noiseLines -join "`n")
71
+ [IO.File]::WriteAllText($NoiseFile, $noiseContent, [Text.Encoding]::UTF8)
72
+ } elseif (Test-Path $NoiseFile) {
73
+ Remove-Item $NoiseFile -Force
74
+ }
75
+
76
+ # --- Step 2: Strip trailing usage stats ---
77
+ $statsPrefixes = @('Total usage est:', 'API time spent:', 'Total session time:',
78
+ 'Total code changes:', 'Breakdown by AI model:', 'Session:',
79
+ 'Changes', 'Requests', 'Tokens')
80
+ $resultLines = New-Object System.Collections.Generic.List[string]
81
+ $hitStats = $false
82
+ foreach ($line in $cleaned -split "`n") {
83
+ if (-not $hitStats) {
84
+ foreach ($sp in $statsPrefixes) {
85
+ if ($line.TrimStart().StartsWith($sp)) { $hitStats = $true; break }
86
+ }
87
+ }
88
+ if (-not $hitStats) { $resultLines.Add($line) }
89
+ }
90
+ $cleaned = ($resultLines -join "`n").Trim()
91
+
92
+ # --- raw result_type: write cleaned plain text and exit ---
93
+ if ($ResultType -eq 'raw') {
94
+ if ([string]::IsNullOrWhiteSpace($cleaned)) {
95
+ [IO.File]::WriteAllText($OutputFile, '', [Text.Encoding]::UTF8)
96
+ } else {
97
+ [IO.File]::WriteAllText($OutputFile, $cleaned, [Text.Encoding]::UTF8)
98
+ }
99
+ exit 0
100
+ }
101
+
102
+ # --- json result_type: extract JSON object matching result_shape ---
103
+
104
+ # Load result_shape keys (if provided) to use as required-key filter
105
+ $shapeKeys = @()
106
+ if ($ResultShapeFile -and (Test-Path $ResultShapeFile)) {
107
+ try {
108
+ $shape = [IO.File]::ReadAllText($ResultShapeFile, [Text.Encoding]::UTF8) | ConvertFrom-Json -ErrorAction Stop
109
+ $shapeKeys = @($shape.PSObject.Properties.Name)
110
+ } catch {}
111
+ }
112
+
113
+ if ([string]::IsNullOrWhiteSpace($cleaned)) {
114
+ $fallback = if ($shapeKeys.Count -gt 0) {
115
+ $obj = [ordered]@{}
116
+ foreach ($k in $shapeKeys) { $obj[$k] = $null }
117
+ $obj | ConvertTo-Json -Depth 2 -Compress
118
+ } else { '{}' }
119
+ [IO.File]::WriteAllText($OutputFile, $fallback, [Text.Encoding]::UTF8)
120
+ exit 0
121
+ }
122
+
123
+ # Helper: check if a parsed object has all required shape keys
124
+ function Test-ShapeMatch($obj) {
125
+ if ($shapeKeys.Count -eq 0) { return $true } # no shape constraint — accept any JSON object
126
+ foreach ($k in $shapeKeys) {
127
+ if (-not $obj.PSObject.Properties[$k]) { return $false }
128
+ }
129
+ return $true
130
+ }
131
+
132
+ $foundJson = $null
133
+
134
+ # 1: Look in ```json fenced blocks first
135
+ if ($cleaned -match '(?s)```json\s*(.*?)```') {
136
+ try {
137
+ $obj = $Matches[1].Trim() | ConvertFrom-Json -ErrorAction Stop
138
+ if (Test-ShapeMatch $obj) { $foundJson = $Matches[1].Trim() }
139
+ } catch {}
140
+ }
141
+
142
+ # 2: Scan for bare JSON objects
143
+ if (-not $foundJson) {
144
+ $depth = 0; $start = -1
145
+ for ($i = 0; $i -lt $cleaned.Length; $i++) {
146
+ if ($cleaned[$i] -eq '{') {
147
+ if ($depth -eq 0) { $start = $i }
148
+ $depth++
149
+ } elseif ($cleaned[$i] -eq '}') {
150
+ $depth--
151
+ if ($depth -eq 0 -and $start -ge 0) {
152
+ $candidate = $cleaned.Substring($start, $i - $start + 1)
153
+ try {
154
+ $obj = $candidate | ConvertFrom-Json -ErrorAction Stop
155
+ if (Test-ShapeMatch $obj) {
156
+ $foundJson = $candidate
157
+ break
158
+ }
159
+ } catch {}
160
+ $start = -1
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ if ($foundJson) {
167
+ [IO.File]::WriteAllText($OutputFile, $foundJson, [Text.Encoding]::UTF8)
168
+ } else {
169
+ # No matching JSON found — record raw in noise file for upstream visibility
170
+ $NoiseFile = $OutputFile + '.noise'
171
+ $fallbackNoise = "FALLBACK=no_json_match`nSHAPE_KEYS=$($shapeKeys -join ',')`nRAW_LENGTH=$($cleaned.Length)`n---`n$cleaned"
172
+ if (Test-Path $NoiseFile) {
173
+ $existing = [IO.File]::ReadAllText($NoiseFile, [Text.Encoding]::UTF8)
174
+ [IO.File]::WriteAllText($NoiseFile, "$existing`n$fallbackNoise", [Text.Encoding]::UTF8)
175
+ } else {
176
+ [IO.File]::WriteAllText($NoiseFile, $fallbackNoise, [Text.Encoding]::UTF8)
177
+ }
178
+ if ($IsRetry) {
179
+ # Second pass — write shape-skeleton so the card gets a structured (empty) result
180
+ $fallback = if ($shapeKeys.Count -gt 0) {
181
+ $obj = [ordered]@{}
182
+ foreach ($k in $shapeKeys) { $obj[$k] = $null }
183
+ $obj | ConvertTo-Json -Depth 2 -Compress
184
+ } else { '{}' }
185
+ [IO.File]::WriteAllText($OutputFile, $fallback, [Text.Encoding]::UTF8)
186
+ } else {
187
+ # First pass — exit 2 to signal the caller (.bat) to retry with a correction prompt
188
+ exit 2
189
+ }
190
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * workiq_wrapper.mjs — Calls the demo-server /api/workiq/ask proxy endpoint.
4
+ *
5
+ * Usage: node workiq_wrapper.mjs <out_file>
6
+ * WORKIQ_QUERY env var: the interpolated query string
7
+ * WORKIQ_SERVER_URL env var: base URL of demo-server (default: http://127.0.0.1:7799)
8
+ *
9
+ * The demo-server has a TTY so workiq can produce output there.
10
+ * Writes raw WorkIQ response text to <out_file>.
11
+ */
12
+
13
+ import http from 'node:http';
14
+ import fs from 'node:fs';
15
+
16
+ const outFile = process.argv[2];
17
+ const query = process.env.WORKIQ_QUERY;
18
+ const serverBase = (process.env.WORKIQ_SERVER_URL || 'http://127.0.0.1:7799').replace(/\/$/, '');
19
+
20
+ if (!outFile) { console.error('workiq_wrapper: missing <out_file> argument'); process.exit(1); }
21
+ if (!query) { console.error('workiq_wrapper: WORKIQ_QUERY env var not set'); process.exit(1); }
22
+
23
+ const body = JSON.stringify({ query });
24
+ const url = new URL('/api/workiq/ask', serverBase);
25
+
26
+ const reqOptions = {
27
+ hostname: url.hostname,
28
+ port: url.port || 80,
29
+ path: url.pathname,
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
32
+ };
33
+
34
+ const req = http.request(reqOptions, (res) => {
35
+ let data = '';
36
+ res.on('data', chunk => { data += chunk; });
37
+ res.on('end', () => {
38
+ try {
39
+ const json = JSON.parse(data);
40
+ if (json.error) {
41
+ fs.writeFileSync(outFile, `workiq error: ${json.error}`, 'utf8');
42
+ process.exit(1);
43
+ }
44
+ fs.writeFileSync(outFile, json.response || '', 'utf8');
45
+ process.exit(0);
46
+ } catch {
47
+ fs.writeFileSync(outFile, data, 'utf8');
48
+ process.exit(0);
49
+ }
50
+ });
51
+ });
52
+
53
+ req.on('error', (err) => {
54
+ fs.writeFileSync(outFile, `workiq proxy error: ${err.message}\nIs demo-server running at ${serverBase}?`, 'utf8');
55
+ process.exit(1);
56
+ });
57
+
58
+ req.setTimeout(60_000, () => {
59
+ req.destroy();
60
+ fs.writeFileSync(outFile, 'workiq proxy timeout after 60s', 'utf8');
61
+ process.exit(1);
62
+ });
63
+
64
+ req.write(body);
65
+ req.end();
66
+
@@ -46,9 +46,9 @@ const cards: LiveCard[] = [
46
46
  card_data: {},
47
47
  data: { requires: ['market-feed'] },
48
48
  compute: {
49
- total: { fn: 'sum', input: 'sources.market-feed.prices' },
50
- avg: { fn: 'avg', input: 'sources.market-feed.prices' },
51
- count: { fn: 'count', input: 'sources.market-feed.prices' },
49
+ total: { fn: 'sum', input: 'source_defs.market-feed.prices' },
50
+ avg: { fn: 'avg', input: 'source_defs.market-feed.prices' },
51
+ count: { fn: 'count', input: 'source_defs.market-feed.prices' },
52
52
  },
53
53
  },
54
54
  {
@@ -132,8 +132,8 @@ const board: LiveBoard = {
132
132
  card_data: {},
133
133
  data: { requires: ['equity-feed', 'bond-feed'] },
134
134
  compute: {
135
- equity_total: { fn: 'sum', input: 'sources.equity-feed.prices' },
136
- bond_total: { fn: 'sum', input: 'sources.bond-feed.yields' },
135
+ equity_total: { fn: 'sum', input: 'source_defs.equity-feed.prices' },
136
+ bond_total: { fn: 'sum', input: 'source_defs.bond-feed.yields' },
137
137
  },
138
138
  },
139
139
  {
@@ -2,7 +2,7 @@
2
2
  * SOC Incident Board — Correct Model
3
3
  *
4
4
  * A Security Operations Center dashboard demonstrating:
5
- * • External data sources (alert-feed, threat-intel) — NO handlers
5
+ * • External data source_defs (alert-feed, threat-intel) — NO handlers
6
6
  * • Handler-driven compute (severity-score, blast-radius)
7
7
  * • Fire-and-forget side effects (slack-alert, create-ticket)
8
8
  * • Per-task refreshStrategy: 'once' on create-ticket (no duplicate tickets)
@@ -76,7 +76,7 @@ const config: GraphConfig = {
76
76
  refreshStrategy: 'data-changed',
77
77
  },
78
78
  tasks: {
79
- // External sources — no handler, data pushed externally
79
+ // External source_defs — no handler, data pushed externally
80
80
  'alert-feed': {
81
81
  provides: ['alert-feed'],
82
82
  description: 'SIEM alert feed (external push)',
@@ -203,7 +203,7 @@ console.log(`create-ticket: refreshStrategy 'once' (no duplicate tickets)\n`);
203
203
 
204
204
  console.log(`--- T0: Initial incident (${siemAlerts.length} alerts, ${threatIntelV1.length} IOCs) ---\n`);
205
205
 
206
- // Push both sources simultaneously — engine stores data, auto-hash computed
206
+ // Push both source_defs simultaneously — engine stores data, auto-hash computed
207
207
  graph.pushAll([
208
208
  {
209
209
  type: 'task-completed',
@@ -29,18 +29,18 @@ const graph: GraphConfig = {
29
29
  },
30
30
  tasks: {
31
31
  fetch_sources: {
32
- provides: ['raw-sources'],
32
+ provides: ['raw-asources'],
33
33
  description: 'Fetch source documents from the web',
34
34
  },
35
35
  analyse_sentiment: {
36
- requires: ['raw-sources'],
36
+ requires: ['raw-asources'],
37
37
  provides: ['sentiment-result'],
38
- description: 'Run sentiment analysis on sources',
38
+ description: 'Run sentiment analysis on source_defs',
39
39
  },
40
40
  analyse_entities: {
41
- requires: ['raw-sources'],
41
+ requires: ['raw-asources'],
42
42
  provides: ['entity-result'],
43
- description: 'Extract named entities from sources',
43
+ description: 'Extract named entities from source_defs',
44
44
  },
45
45
  merge_analysis: {
46
46
  requires: ['sentiment-result', 'entity-result'],
@@ -13,7 +13,7 @@
13
13
  * - Fan-out / fan-in in the outer graph
14
14
  *
15
15
  * Outer graph:
16
- * discover-sources → extract-batch → transform-batch → [load ∥ validate] → finalize
16
+ * discover-bsources → extract-batch → transform-batch → [load ∥ validate] → finalize
17
17
  *
18
18
  * Inner extract graph (event-graph, per source):
19
19
  * connect → [fetch-metadata ∥ fetch-schema] → snapshot-data
@@ -129,11 +129,11 @@ const outerGraph: GraphConfig = {
129
129
  id: 'etl-pipeline',
130
130
  settings: { completion: 'all-tasks-complete' },
131
131
  tasks: {
132
- 'discover-sources': {
133
- provides: ['sources-discovered'],
132
+ 'discover-bsources': {
133
+ provides: ['source_defs-discovered'],
134
134
  },
135
135
  'extract-batch': {
136
- requires: ['sources-discovered'],
136
+ requires: ['source_defs-discovered'],
137
137
  provides: ['extraction-complete'],
138
138
  },
139
139
  'transform-batch': {
@@ -191,7 +191,7 @@ async function runTransformFlow(record: { id: string; raw_data: string }) {
191
191
  // 4. Sample data
192
192
  // ============================================================================
193
193
 
194
- const sources = [
194
+ const source_defs = [
195
195
  { id: 'src-orders', db: 'orders_db' },
196
196
  { id: 'src-users', db: 'users_db' },
197
197
  { id: 'src-products', db: 'products_db' },
@@ -211,13 +211,13 @@ const records = [
211
211
  // ============================================================================
212
212
 
213
213
  const outerHandlers: Record<string, () => Promise<void>> = {
214
- 'discover-sources': async () => {
215
- console.log(` Found ${sources.length} data sources`);
214
+ 'discover-bsources': async () => {
215
+ console.log(` Found ${source_defs.length} data source_defs`);
216
216
  },
217
217
 
218
218
  'extract-batch': async () => {
219
- console.log(` Extracting from ${sources.length} sources (concurrency: 2, mode: event-graph)`);
220
- const result = await batch(sources, {
219
+ console.log(` Extracting from ${source_defs.length} source_defs (concurrency: 2, mode: event-graph)`);
220
+ const result = await batch(source_defs, {
221
221
  concurrency: 2,
222
222
  processor: runExtractGraph,
223
223
  onItemComplete: (src, res) => {
@@ -2,7 +2,7 @@
2
2
  "id": "holdings-table",
3
3
  "meta": { "title": "Holdings Table" },
4
4
  "requires": ["holdings", "prices"],
5
- "provides": [{ "bindTo": "table", "src": "computed_values.table" }],
5
+ "provides": [{ "bindTo": "table", "ref": "computed_values.table" }],
6
6
  "card_data": {},
7
7
  "compute": [
8
8
  {
@@ -6,7 +6,7 @@
6
6
  "provides": [
7
7
  {
8
8
  "bindTo": "holdings",
9
- "src": "card_data.holdings"
9
+ "ref": "card_data.holdings"
10
10
  }
11
11
  ],
12
12
  "card_data": {
@@ -2,7 +2,7 @@
2
2
  "id": "portfolio-value",
3
3
  "meta": { "title": "Portfolio Total Value" },
4
4
  "requires": ["table"],
5
- "provides": [{ "bindTo": "totalValue", "src": "computed_values.totalValue" }],
5
+ "provides": [{ "bindTo": "totalValue", "ref": "computed_values.totalValue" }],
6
6
  "card_data": {},
7
7
  "compute": [
8
8
  { "bindTo": "totalValue", "expr": "$sum(requires.table.rows.value)" }
@@ -2,14 +2,14 @@
2
2
  "id": "price-fetch",
3
3
  "meta": { "title": "Fetch Market Prices" },
4
4
  "requires": ["holdings"],
5
- "provides": [{ "bindTo": "prices", "src": "sources.prices" }],
5
+ "provides": [{ "bindTo": "prices", "ref": "fetched_sources.prices" }],
6
6
  "card_data": {},
7
- "sources": [
7
+ "source_defs": [
8
8
  { "cli": "node ../fetch-prices.js --tmp-file-name tmp_file1", "bindTo": "prices", "outputFile": "prices.json" }
9
9
  ],
10
10
  "view": {
11
11
  "elements": [
12
- { "kind": "table", "label": "Market Prices", "data": { "bind": "sources.prices" } }
12
+ { "kind": "table", "label": "Market Prices", "data": { "bind": "fetched_sources.prices" } }
13
13
  ]
14
14
  }
15
15
  }
@@ -12,7 +12,7 @@ try {
12
12
  process.exit(0);
13
13
  }
14
14
 
15
- runBoardCli(['add-cards', '--rg', boardDir, '--card-glob', cardsGlob]);
15
+ runBoardCli(['upsert-card', '--rg', boardDir, '--card-glob', cardsGlob]);
16
16
 
17
17
  writeResult({
18
18
  result: 'success',
@@ -22,7 +22,7 @@ try {
22
22
  card.state.holdings = holdings;
23
23
  fs.writeFileSync(cardPath, `${JSON.stringify(card, null, 2)}\n`, 'utf-8');
24
24
 
25
- runBoardCli(['update-card', '--rg', boardDir, '--card-id', 'portfolio-form', '--restart']);
25
+ runBoardCli(['upsert-card', '--rg', boardDir, '--card', cardPath, '--restart']);
26
26
 
27
27
  writeResult({
28
28
  result: 'success',
@@ -44,7 +44,7 @@ steps:
44
44
  expects_data: [board_dir, cards_template_dir]
45
45
  produces_data: [cards_dir]
46
46
  handler:
47
- cli: node %%BOARDCLI_CMD%% add-cards --rg "%%BOARD_DIR%%" --card-glob "%%CARDS_GLOB%%"
47
+ cli: node %%BOARDCLI_CMD%% upsert-card --rg "%%BOARD_DIR%%" --card-glob "%%CARDS_GLOB%%"
48
48
  result-mode: exit-code
49
49
  input-transforms:
50
50
  BOARD_DIR: board_dir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml-flow",
3
- "version": "5.2.5",
3
+ "version": "5.2.8",
4
4
  "description": "Unified workflow engine: step-machine (sequential) + event-graph (stateless DAG) with pluggable storage",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -101,7 +101,7 @@
101
101
  "scripts": {
102
102
  "build": "tsup",
103
103
  "build:browser": "tsup --config tsup.browser.config.ts",
104
- "build:example": "node example-board-src/build.js",
104
+ "build:example": "node demo-src/example-board/build.js",
105
105
  "dev": "tsup --watch",
106
106
  "test": "vitest",
107
107
  "test:run": "vitest run",