yaml-flow 5.4.2 → 7.0.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.
Files changed (252) hide show
  1. package/board-live-cards-cli.js +6 -6
  2. package/browser/asset-integrity.json +10 -0
  3. package/browser/board-livecards-client.js +2 -0
  4. package/browser/board-livecards-client.js.map +1 -0
  5. package/browser/board-livecards-localstorage.js +10 -0
  6. package/browser/board-livecards-localstorage.js.map +1 -0
  7. package/browser/board-livegraph-engine.js +2 -1676
  8. package/browser/board-livegraph-engine.js.map +1 -1
  9. package/browser/card-compute.js +28 -28
  10. package/browser/compute-jsonata.js +5 -0
  11. package/browser/compute-jsonata.js.map +1 -0
  12. package/browser/live-cards.js +561 -129
  13. package/browser/live-cards.schema.json +418 -132
  14. package/card-store.js +37 -0
  15. package/dist/batch/index.cjs +1 -108
  16. package/dist/batch/index.cjs.map +1 -1
  17. package/dist/batch/index.js +1 -106
  18. package/dist/batch/index.js.map +1 -1
  19. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  20. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  21. package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
  22. package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
  23. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  24. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  25. package/dist/board-livegraph-runtime/index.d.cts +12 -11
  26. package/dist/board-livegraph-runtime/index.d.ts +12 -11
  27. package/dist/board-livegraph-runtime/index.js +2 -1662
  28. package/dist/board-livegraph-runtime/index.js.map +1 -1
  29. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
  30. package/dist/card-compute/index.cjs +9 -7159
  31. package/dist/card-compute/index.cjs.map +1 -1
  32. package/dist/card-compute/index.d.cts +27 -1
  33. package/dist/card-compute/index.d.ts +27 -1
  34. package/dist/card-compute/index.js +9 -7145
  35. package/dist/card-compute/index.js.map +1 -1
  36. package/dist/card-compute/jsonata-sync.cjs +7623 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
  38. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  39. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
  40. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
  41. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
  42. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  43. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  44. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  45. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  46. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  47. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  48. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  49. package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
  50. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  51. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  52. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  53. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  54. package/dist/cli/node/artifacts-store-cli.js +11 -0
  55. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  56. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  57. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  58. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  59. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  60. package/dist/cli/node/board-live-cards-cli.js +15 -0
  61. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  62. package/dist/cli/node/card-store-cli.cjs +8 -0
  63. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  64. package/dist/cli/node/card-store-cli.d.cts +15 -0
  65. package/dist/cli/node/card-store-cli.d.ts +15 -0
  66. package/dist/cli/node/card-store-cli.js +8 -0
  67. package/dist/cli/node/card-store-cli.js.map +1 -0
  68. package/dist/cli/node/execution-adapter.cjs +3 -0
  69. package/dist/cli/node/execution-adapter.cjs.map +1 -0
  70. package/dist/cli/node/execution-adapter.d.cts +174 -0
  71. package/dist/cli/node/execution-adapter.d.ts +174 -0
  72. package/dist/cli/node/execution-adapter.js +3 -0
  73. package/dist/cli/node/execution-adapter.js.map +1 -0
  74. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  75. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  76. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  77. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  78. package/dist/cli/node/fs-board-adapter.js +14 -0
  79. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  80. package/dist/cli/node/jsonata-sync.cjs +7623 -0
  81. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  82. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  83. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  84. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  85. package/dist/cli/node/source-cli-task-executor.js +11 -0
  86. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  87. package/dist/config/index.cjs +1 -79
  88. package/dist/config/index.cjs.map +1 -1
  89. package/dist/config/index.js +1 -76
  90. package/dist/config/index.js.map +1 -1
  91. package/dist/continuous-event-graph/index.cjs +2 -2129
  92. package/dist/continuous-event-graph/index.cjs.map +1 -1
  93. package/dist/continuous-event-graph/index.d.cts +81 -5
  94. package/dist/continuous-event-graph/index.d.ts +81 -5
  95. package/dist/continuous-event-graph/index.js +2 -2088
  96. package/dist/continuous-event-graph/index.js.map +1 -1
  97. package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
  98. package/dist/event-graph/index.cjs +22 -8292
  99. package/dist/event-graph/index.cjs.map +1 -1
  100. package/dist/event-graph/index.js +22 -8237
  101. package/dist/event-graph/index.js.map +1 -1
  102. package/dist/execution-refs.cjs +3 -0
  103. package/dist/execution-refs.cjs.map +1 -0
  104. package/dist/execution-refs.d.cts +260 -0
  105. package/dist/execution-refs.d.ts +260 -0
  106. package/dist/execution-refs.js +3 -0
  107. package/dist/execution-refs.js.map +1 -0
  108. package/dist/index.cjs +29 -13221
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +2 -4
  111. package/dist/index.d.ts +2 -4
  112. package/dist/index.js +29 -13112
  113. package/dist/index.js.map +1 -1
  114. package/dist/inference/index.cjs +5 -617
  115. package/dist/inference/index.cjs.map +1 -1
  116. package/dist/inference/index.js +5 -610
  117. package/dist/inference/index.js.map +1 -1
  118. package/dist/jsonata-sync.cjs +7623 -0
  119. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  120. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  121. package/dist/server-runtime/index.cjs +9 -0
  122. package/dist/server-runtime/index.cjs.map +1 -0
  123. package/dist/server-runtime/index.d.cts +31 -0
  124. package/dist/server-runtime/index.d.ts +31 -0
  125. package/dist/server-runtime/index.js +9 -0
  126. package/dist/server-runtime/index.js.map +1 -0
  127. package/dist/server-runtime/jsonata-sync.cjs +7623 -0
  128. package/dist/step-machine/index.cjs +11 -7129
  129. package/dist/step-machine/index.cjs.map +1 -1
  130. package/dist/step-machine/index.js +11 -7113
  131. package/dist/step-machine/index.js.map +1 -1
  132. package/dist/step-machine-public/index.cjs +2 -0
  133. package/dist/step-machine-public/index.cjs.map +1 -0
  134. package/dist/step-machine-public/index.d.cts +159 -0
  135. package/dist/step-machine-public/index.d.ts +159 -0
  136. package/dist/step-machine-public/index.js +2 -0
  137. package/dist/step-machine-public/index.js.map +1 -0
  138. package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
  139. package/dist/storage-refs.cjs +10 -0
  140. package/dist/storage-refs.cjs.map +1 -0
  141. package/dist/storage-refs.d.cts +93 -0
  142. package/dist/storage-refs.d.ts +93 -0
  143. package/dist/storage-refs.js +10 -0
  144. package/dist/storage-refs.js.map +1 -0
  145. package/dist/stores/file.cjs +1 -114
  146. package/dist/stores/file.cjs.map +1 -1
  147. package/dist/stores/file.js +1 -112
  148. package/dist/stores/file.js.map +1 -1
  149. package/dist/stores/index.cjs +1 -231
  150. package/dist/stores/index.cjs.map +1 -1
  151. package/dist/stores/index.js +1 -227
  152. package/dist/stores/index.js.map +1 -1
  153. package/dist/stores/localStorage.cjs +1 -76
  154. package/dist/stores/localStorage.cjs.map +1 -1
  155. package/dist/stores/localStorage.js +1 -74
  156. package/dist/stores/localStorage.js.map +1 -1
  157. package/dist/stores/memory.cjs +1 -47
  158. package/dist/stores/memory.cjs.map +1 -1
  159. package/dist/stores/memory.js +1 -45
  160. package/dist/stores/memory.js.map +1 -1
  161. package/dist/types-B1ZRa4aI.d.ts +147 -0
  162. package/dist/types-BxEFcVK9.d.cts +147 -0
  163. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +291 -0
  164. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  165. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  166. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
  167. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  168. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
  169. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
  170. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
  171. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
  172. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +366 -0
  173. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  174. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  175. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
  176. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
  177. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
  178. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
  179. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
  180. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
  181. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
  182. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +9 -8
  183. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
  184. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
  185. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +107 -0
  186. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
  187. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
  188. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
  189. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  190. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  191. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  192. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  193. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
  194. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  195. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
  196. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  197. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  198. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
  199. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
  200. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
  201. package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
  202. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
  203. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
  204. package/examples/example-board/agent-instructions.md +11 -5
  205. package/examples/example-board/cards/_index.json +47 -0
  206. package/examples/example-board/cards/card-market-prices.json +33 -9
  207. package/examples/example-board/cards/card-my-identity.json +30 -6
  208. package/examples/example-board/cards/card-portfolio-action.json +24 -6
  209. package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
  210. package/examples/example-board/cards/card-portfolio-risks.json +24 -6
  211. package/examples/example-board/cards/card-portfolio-value.json +38 -10
  212. package/examples/example-board/cards/card-portfolio.json +57 -13
  213. package/examples/example-board/cards/card-rebalance-impact.json +22 -6
  214. package/examples/example-board/cards/card-rebalance-sim.json +66 -15
  215. package/examples/example-board/demo-chat-handler.js +14 -4
  216. package/examples/example-board/demo-server-config.json +1 -0
  217. package/examples/example-board/demo-server.js +366 -68
  218. package/examples/example-board/demo-shell-localstorage.html +774 -0
  219. package/examples/example-board/demo-shell-with-server.html +20 -37
  220. package/examples/example-board/demo-shell.html +5 -4
  221. package/examples/example-board/demo-task-executor.js +273 -275
  222. package/examples/index.html +0 -14
  223. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  224. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  225. package/package.json +46 -8
  226. package/schema/live-cards.schema.json +418 -132
  227. package/step-machine-cli.js +43 -310
  228. package/board-livecards-server-runtime.js +0 -1574
  229. package/browser/board-livecards-runtime-client.js +0 -263
  230. package/dist/cli/board-live-cards-cli.cjs +0 -10650
  231. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  232. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  233. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  234. package/dist/cli/board-live-cards-cli.js +0 -10598
  235. package/dist/cli/board-live-cards-cli.js.map +0 -1
  236. package/dist/journal-9HEgs7dU.d.ts +0 -28
  237. package/dist/journal-B-JCfQnh.d.cts +0 -28
  238. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  239. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  240. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  241. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  242. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  243. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  244. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  245. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  246. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  247. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  248. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  249. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
  250. package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
  251. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
  252. package/examples/example-board/demo-shell-browser.html +0 -674
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python3
2
+ """portfolio-tracker-fetch-prices.py
3
+
4
+ Task executor for the portfolio board demo.
5
+ Handles run-source-fetch requests for source_defs with kind: "mock-quotes".
6
+ Generates random prices (2dp, 10.00-999.99) for each projected ticker.
7
+
8
+ Subcommands:
9
+ run-source-fetch --in-ref <::kind::value> --out-ref <::kind::value> --err-ref <::kind::value>
10
+ validate-source-def --in <source.json>
11
+ describe-capabilities
12
+
13
+ Uses the public storage adapter for all storage and callback operations.
14
+ The executor does NOT contain transport-specific callback logic.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import os
22
+ import random
23
+ import sys
24
+ import time
25
+ from typing import Any
26
+
27
+ # Add pycli to path so we can import the public storage adapter.
28
+ _REPO_ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..', '..'))
29
+ if _REPO_ROOT not in sys.path:
30
+ sys.path.insert(0, _REPO_ROOT)
31
+
32
+ from pycli.sub.public_storage_adapter import ( # noqa: E402
33
+ parse_ref,
34
+ serialize_ref,
35
+ blob_storage_for_ref,
36
+ report_complete,
37
+ report_failed,
38
+ KindValueRef,
39
+ )
40
+
41
+
42
+ def _parse_ref_str(ref: str) -> KindValueRef:
43
+ """Convenience: parse a CLI ref string."""
44
+ return parse_ref(ref)
45
+
46
+
47
+ def validate_source_def(source_def: dict[str, Any]) -> dict[str, Any]:
48
+ errors: list[str] = []
49
+
50
+ if source_def.get("kind") != "mock-quotes":
51
+ errors.append(f"kind must be \"mock-quotes\"; got \"{source_def.get('kind')}\".")
52
+ if not isinstance(source_def.get("bindTo"), str) or not source_def.get("bindTo"):
53
+ errors.append("bindTo is required and must be a string.")
54
+ if not isinstance(source_def.get("outputFile"), str) or not source_def.get("outputFile"):
55
+ errors.append("outputFile is required and must be a string.")
56
+ projections = source_def.get("projections")
57
+ if not isinstance(projections, dict) or not isinstance(projections.get("tickers"), str):
58
+ errors.append("projections.tickers is required and must be a JSONata expression string.")
59
+
60
+ return {"ok": len(errors) == 0, "errors": errors}
61
+
62
+
63
+ def cmd_validate_source_def(args: argparse.Namespace) -> int:
64
+ if not os.path.exists(args.input):
65
+ print(json.dumps({"ok": False, "errors": [f"Input file not found: {args.input}"]}))
66
+ return 1
67
+
68
+ try:
69
+ with open(args.input, "r", encoding="utf-8") as f:
70
+ source_def = json.load(f)
71
+ except Exception as e:
72
+ print(json.dumps({"ok": False, "errors": [f"Cannot parse source file: {e}"]}))
73
+ return 1
74
+
75
+ result = validate_source_def(source_def if isinstance(source_def, dict) else {})
76
+ print(json.dumps(result))
77
+ return 0 if result["ok"] else 1
78
+
79
+
80
+ def cmd_describe_capabilities(_: argparse.Namespace) -> int:
81
+ capabilities = {
82
+ "version": "1.0",
83
+ "executor": "portfolio-tracker-fetch-prices",
84
+ "subcommands": ["run-source-fetch", "validate-source-def", "describe-capabilities"],
85
+ "sourceKinds": {
86
+ "mock-quotes": {
87
+ "description": "Generates random mock market prices (10.00-999.99) for each ticker in _projections.tickers.",
88
+ "inputSchema": {
89
+ "kind": {"type": "string", "required": True, "description": "Must be \"mock-quotes\"."},
90
+ "bindTo": {"type": "string", "required": True, "description": "Token name for the output binding."},
91
+ "outputFile": {"type": "string", "required": True, "description": "Relative path to write prices JSON."},
92
+ "projections": {
93
+ "type": "object",
94
+ "required": True,
95
+ "properties": {
96
+ "tickers": {
97
+ "type": "string",
98
+ "required": True,
99
+ "description": "JSONata expression resolving to a string[] of ticker symbols.",
100
+ }
101
+ },
102
+ },
103
+ },
104
+ "outputShape": "{ [ticker: string]: number }",
105
+ }
106
+ },
107
+ }
108
+ print(json.dumps(capabilities, indent=2, ensure_ascii=True))
109
+ return 0
110
+
111
+
112
+ def cmd_run_source_fetch(args: argparse.Namespace) -> int:
113
+ in_ref = _parse_ref_str(args.in_ref)
114
+ out_ref = _parse_ref_str(args.out_ref)
115
+ err_ref = _parse_ref_str(args.err_ref)
116
+
117
+ in_storage = blob_storage_for_ref(in_ref)
118
+ out_storage = blob_storage_for_ref(out_ref)
119
+ err_storage = blob_storage_for_ref(err_ref)
120
+
121
+ raw_in = in_storage.read(in_ref.value)
122
+ if not raw_in:
123
+ print(f"[portfolio-tracker-fetch-prices] input envelope not found at: {args.in_ref}", file=sys.stderr)
124
+ return 1
125
+
126
+ envelope = json.loads(raw_in)
127
+ callback = envelope.get("callback") if isinstance(envelope, dict) else None
128
+
129
+ def safe_fail(msg: str) -> int:
130
+ try:
131
+ err_storage.write(err_ref.value, msg)
132
+ except Exception:
133
+ pass
134
+ if isinstance(callback, dict):
135
+ try:
136
+ report_failed(callback, msg)
137
+ return 0
138
+ except Exception as e:
139
+ print(f"[portfolio-tracker-fetch-prices] callback fail: {e}", file=sys.stderr)
140
+ return 1
141
+ return 1
142
+
143
+ try:
144
+ source_def = envelope.get("source_def") if isinstance(envelope, dict) else None
145
+ if not isinstance(source_def, dict):
146
+ source_def = envelope if isinstance(envelope, dict) else {}
147
+
148
+ if source_def.get("kind") != "mock-quotes":
149
+ raise ValueError(f"Unsupported source kind: expected \"mock-quotes\", got \"{source_def.get('kind')}\"")
150
+
151
+ projections = source_def.get("_projections")
152
+ tickers = projections.get("tickers") if isinstance(projections, dict) else None
153
+ if not isinstance(tickers, list):
154
+ raise ValueError("sourceDef._projections.tickers is missing or not an array")
155
+
156
+ time.sleep(0.2 + random.random() * 0.1)
157
+
158
+ prices: dict[str, float] = {}
159
+ for ticker in tickers:
160
+ prices[str(ticker)] = round(10 + random.random() * 989.99, 2)
161
+
162
+ out_storage.write(out_ref.value, json.dumps(prices, ensure_ascii=True))
163
+ print(f"[portfolio-tracker-fetch-prices] wrote prices for: {', '.join([str(t) for t in tickers])}")
164
+
165
+ if isinstance(callback, dict):
166
+ report_complete(callback, out_ref)
167
+ return 0
168
+ except Exception as e:
169
+ msg = str(e)
170
+ print(f"[portfolio-tracker-fetch-prices] error: {msg}", file=sys.stderr)
171
+ return safe_fail(msg)
172
+
173
+
174
+ def build_parser() -> argparse.ArgumentParser:
175
+ parser = argparse.ArgumentParser(prog="portfolio-tracker-fetch-prices")
176
+ sub = parser.add_subparsers(dest="command", required=True)
177
+
178
+ run_cmd = sub.add_parser("run-source-fetch")
179
+ run_cmd.add_argument("--in-ref", required=True)
180
+ run_cmd.add_argument("--out-ref", required=True)
181
+ run_cmd.add_argument("--err-ref", required=True)
182
+ run_cmd.set_defaults(handler=cmd_run_source_fetch)
183
+
184
+ val_cmd = sub.add_parser("validate-source-def")
185
+ val_cmd.add_argument("--in", dest="input", required=True)
186
+ val_cmd.set_defaults(handler=cmd_validate_source_def)
187
+
188
+ cap_cmd = sub.add_parser("describe-capabilities")
189
+ cap_cmd.set_defaults(handler=cmd_describe_capabilities)
190
+
191
+ return parser
192
+
193
+
194
+ def main(argv: list[str] | None = None) -> int:
195
+ parser = build_parser()
196
+ args = parser.parse_args(argv)
197
+ return args.handler(args)
198
+
199
+
200
+ if __name__ == "__main__":
201
+ raise SystemExit(main())
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * portfolio-tracker-http-test.js
4
+ *
5
+ * E2E test for the portfolio-tracker board via HTTP + SSE.
6
+ *
7
+ * Two parallel tracks:
8
+ *
9
+ * Worker thread (portfolio-tracker-sse-worker.js) — SSE consumer
10
+ * Opens the board's /sse endpoint, parses every frame, and forwards it
11
+ * to the main thread via parentPort.postMessage({ type: 'frame', payload }).
12
+ *
13
+ * Main thread (this file) — Test driver
14
+ * Accumulates state from worker messages into NotificationState (NS).
15
+ * Drives sequential test steps (T1–T5) via HTTP PATCH/GET.
16
+ * All "wait for X" helpers poll NS with setInterval — no callbacks needed.
17
+ *
18
+ * Usage:
19
+ * node portfolio-tracker-http-test.js [--port 7800] [--server node|py]
20
+ */
21
+
22
+ import { Worker } from 'node:worker_threads';
23
+ import { spawn, spawnSync } from 'node:child_process';
24
+ import { fileURLToPath } from 'node:url';
25
+ import path from 'node:path';
26
+ import http from 'node:http';
27
+
28
+ const __filename = fileURLToPath(import.meta.url);
29
+ const __dirname = path.dirname(__filename);
30
+
31
+ const cliArgs = process.argv.slice(2);
32
+ const portArg = cliArgs.indexOf('--port');
33
+ const serverArg = cliArgs.indexOf('--server');
34
+ const SERVER_TYPE = serverArg !== -1 ? cliArgs[serverArg + 1] : 'node'; // 'node' | 'py'
35
+ const PORT = portArg !== -1 ? parseInt(cliArgs[portArg + 1], 10) : (SERVER_TYPE === 'py' ? 7801 : 7800);
36
+ const BASE = `http://127.0.0.1:${PORT}/api/board`;
37
+ const SERVER_SCRIPT = path.join(__dirname, 'portfolio-tracker-server.js');
38
+ const PY_SERVER_SCRIPT = path.join(__dirname, 'portfolio-tracker-server.py');
39
+ const SSE_WORKER_SCRIPT = path.join(__dirname, 'portfolio-tracker-sse-worker.js');
40
+
41
+ /** Find a working Python interpreter. Returns null if none found. */
42
+ function findPython() {
43
+ const candidates = ['python3', 'python'];
44
+ for (const cmd of candidates) {
45
+ try {
46
+ const r = spawnSync(cmd, ['--version'], { stdio: 'pipe', timeout: 3000 });
47
+ if (r.status === 0 && r.stdout?.toString().startsWith('Python ')) return cmd;
48
+ } catch { /* next */ }
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // =============================================================================
54
+ // NOTIFICATION STATE — accumulated by the main thread from worker SSE frames
55
+ // =============================================================================
56
+ const NS = {
57
+ initialPayload: null, // first full snapshot frame
58
+ statusSummary: null, // latest { card_count, completed, failed, ... }
59
+ statusGeneration: 0, // bumped on every status notification received
60
+ dataObjects: {}, // token → payload (e.g. 'prices' → { AAPL: 142.5, ... })
61
+ computedValues: {}, // cardId → values (e.g. 'holdings-table' → { table: { rows: [...] } })
62
+ };
63
+
64
+ // Apply a parsed SSE frame into NS (called from worker message handler)
65
+ function applyFrame(payload) {
66
+ // Initial full snapshot — has cardDefinitions
67
+ if (payload.cardDefinitions) {
68
+ NS.initialPayload = payload;
69
+ if (payload.statusSnapshot?.summary) {
70
+ NS.statusSummary = payload.statusSnapshot.summary;
71
+ NS.statusGeneration++;
72
+ }
73
+ if (payload.dataObjectsByToken) {
74
+ Object.assign(NS.dataObjects, payload.dataObjectsByToken);
75
+ }
76
+ if (payload.cardRuntimeById) {
77
+ for (const [cardId, runtime] of Object.entries(payload.cardRuntimeById)) {
78
+ if (runtime?.computed_values && Object.keys(runtime.computed_values).length > 0) {
79
+ NS.computedValues[cardId] = runtime.computed_values;
80
+ }
81
+ }
82
+ }
83
+ return;
84
+ }
85
+ // Subsequent frames — notification-batch
86
+ if (payload.kind === 'notification-batch' && Array.isArray(payload.notifications)) {
87
+ for (const n of payload.notifications) {
88
+ if (n.kind === 'status' && n.status?.summary) {
89
+ NS.statusSummary = n.status.summary;
90
+ NS.statusGeneration++;
91
+ } else if (n.kind === 'data_object' && n.key) {
92
+ NS.dataObjects[n.key] = n.payload;
93
+ } else if (n.kind === 'computed_values' && n.cardId) {
94
+ NS.computedValues[n.cardId] = n.values;
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ // ── Polling helpers (poll NS, never block the event loop) ───────────────────
101
+
102
+ function assert(condition, message) {
103
+ if (!condition) {
104
+ console.error(`\n[ASSERT FAILED] ${message}`);
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ function waitUntil(predicate, timeoutMs, label) {
110
+ return new Promise((resolve, reject) => {
111
+ const deadline = Date.now() + timeoutMs;
112
+ const interval = setInterval(() => {
113
+ let result;
114
+ try { result = predicate(); } catch { /* retry */ }
115
+ if (result !== undefined && result !== null && result !== false) {
116
+ clearInterval(interval);
117
+ resolve(result);
118
+ return;
119
+ }
120
+ if (Date.now() > deadline) {
121
+ clearInterval(interval);
122
+ reject(new Error(`Timeout (${timeoutMs}ms) waiting for: ${label}\n NS.statusSummary=${JSON.stringify(NS.statusSummary)}\n dataObjects=${JSON.stringify(Object.keys(NS.dataObjects))}`));
123
+ }
124
+ }, 150);
125
+ });
126
+ }
127
+
128
+ // Waits for first full payload frame from SSE worker
129
+ const waitForInitialPayload = (ms = 15_000) =>
130
+ waitUntil(() => NS.initialPayload || false, ms, 'initial SSE payload');
131
+
132
+ // Waits for all cards to reach completed status
133
+ const waitForAllCompleted = (ms = 60_000, label = 'all completed') =>
134
+ waitUntil(() => {
135
+ const s = NS.statusSummary;
136
+ return (s && s.card_count > 0 && s.completed === s.card_count) ? s : false;
137
+ }, ms, label);
138
+
139
+ // Waits until prices data object has exactly the expected set of symbols
140
+ function waitForPriceSymbols(expectedSymbols, ms = 30_000, label = 'price symbols') {
141
+ const expected = [...expectedSymbols].sort().join(',');
142
+ return waitUntil(() => {
143
+ const prices = NS.dataObjects['prices'];
144
+ if (!prices || typeof prices !== 'object') return false;
145
+ const actual = Object.keys(prices).sort().join(',');
146
+ return actual === expected ? prices : false;
147
+ }, ms, `${label}: expected [${expected}]`);
148
+ }
149
+
150
+ // ── HTTP helpers ─────────────────────────────────────────────────────────────
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 httpPatch(url, payload) {
166
+ return new Promise((resolve, reject) => {
167
+ const body = JSON.stringify(payload);
168
+ const req = http.request(url, {
169
+ method: 'PATCH',
170
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
171
+ }, (res) => {
172
+ let data = '';
173
+ res.on('data', c => { data += c; });
174
+ res.on('end', () => {
175
+ try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
176
+ catch { resolve({ status: res.statusCode, data }); }
177
+ });
178
+ });
179
+ req.on('error', reject);
180
+ req.write(body);
181
+ req.end();
182
+ });
183
+ }
184
+
185
+ function makeHoldingsPatch(holdingsMap) {
186
+ return {
187
+ card_data: {
188
+ holdings: Object.entries(holdingsMap).map(([symbol, qty]) => ({ symbol, qty })),
189
+ },
190
+ };
191
+ }
192
+
193
+ // ── Server process ────────────────────────────────────────────────────────────
194
+
195
+ function startServer(port) {
196
+ const isPy = SERVER_TYPE === 'py';
197
+ let cmd, cmdArgs;
198
+ if (isPy) {
199
+ const python = findPython();
200
+ if (!python) throw new Error('Python interpreter not found on PATH');
201
+ cmd = python;
202
+ cmdArgs = [PY_SERVER_SCRIPT, '--port', String(port), '--reset'];
203
+ } else {
204
+ cmd = process.execPath;
205
+ cmdArgs = [SERVER_SCRIPT, '--port', String(port), '--reset'];
206
+ }
207
+ return new Promise((resolve, reject) => {
208
+ const proc = spawn(cmd, cmdArgs, {
209
+ stdio: ['ignore', 'pipe', 'pipe'],
210
+ windowsHide: true,
211
+ });
212
+ let ready = false;
213
+ proc.stdout.on('data', (chunk) => {
214
+ const text = chunk.toString('utf-8');
215
+ process.stdout.write(`[server] ${text}`);
216
+ if (!ready && text.includes('listening on')) { ready = true; resolve(proc); }
217
+ });
218
+ proc.stderr.on('data', (chunk) => process.stderr.write(`[server:err] ${chunk}`));
219
+ proc.on('error', reject);
220
+ proc.on('exit', (code) => { if (!ready) reject(new Error(`Server exited early: code ${code}`)); });
221
+ setTimeout(() => { if (!ready) reject(new Error('Server startup timeout (15s)')); }, 15_000);
222
+ });
223
+ }
224
+
225
+ // ── Main ──────────────────────────────────────────────────────────────────────
226
+
227
+ console.log('\n=== portfolio-tracker HTTP E2E test ===');
228
+ console.log(`target: ${BASE} [server: ${SERVER_TYPE}]`);
229
+ console.log(`architecture: main-thread (test driver) + worker-thread (SSE consumer)\n`);
230
+
231
+ const serverProc = await startServer(PORT);
232
+ await new Promise(r => setTimeout(r, 300)); // brief settle
233
+
234
+ let sseWorker = null;
235
+ try {
236
+ // ── Step 1: init-board ──────────────────────────────────────────────────────
237
+ console.log('\n=== Step 1: init-board ===');
238
+ const initRes = await httpGet(`${BASE}/init-board`);
239
+ assert(initRes.status === 200, `init-board returned ${initRes.status}`);
240
+ console.log('[step1] ok');
241
+
242
+ // ── Step 2: Start SSE consumer worker ───────────────────────────────────────
243
+ // The worker opens /sse and forwards every parsed frame here via postMessage.
244
+ // Main thread accumulates frames into NS via applyFrame().
245
+ console.log('\n=== Step 2: Start SSE consumer worker ===');
246
+ sseWorker = new Worker(SSE_WORKER_SCRIPT, {
247
+ workerData: { sseUrl: `${BASE}/sse` },
248
+ });
249
+ sseWorker.on('message', (msg) => {
250
+ if (msg.type === 'frame') {
251
+ applyFrame(msg.payload);
252
+ } else if (msg.type === 'error') {
253
+ console.error(`[sse-worker] error: ${msg.message}`);
254
+ } else if (msg.type === 'closed') {
255
+ console.log('[sse-worker] SSE stream closed by server');
256
+ }
257
+ });
258
+ sseWorker.on('error', (err) => console.error(`[sse-worker] uncaught: ${err.message}`));
259
+
260
+ const initialPayload = await waitForInitialPayload();
261
+ console.log(`[step2] SSE worker online — initial payload (${initialPayload.cardDefinitions?.length ?? 0} cards)`);
262
+ console.log(` statusGen=${NS.statusGeneration}, dataObjects=${JSON.stringify(Object.keys(NS.dataObjects))}`);
263
+
264
+ // ── T1: Wait for initial drain ──────────────────────────────────────────────
265
+ console.log('\n=== T1: Wait for initial completion ===');
266
+ const t1Summary = await waitForAllCompleted(60_000, 'T1 initial drain');
267
+ console.log(`[T1] board completed — ${JSON.stringify(t1Summary)}`);
268
+
269
+ const t1Prices = await waitForPriceSymbols(['AAPL', 'MSFT'], 30_000, 'T1 prices');
270
+ assert(Object.values(t1Prices).every(v => typeof v === 'number'), 'T1: all prices must be numbers');
271
+ const t1Table = NS.computedValues['holdings-table']?.table;
272
+ assert(Array.isArray(t1Table?.rows) && t1Table.rows.length === 2, `T1: expected 2 rows, got ${t1Table?.rows?.length}`);
273
+ const t1Total = NS.computedValues['portfolio-value']?.totalValue;
274
+ assert(typeof t1Total === 'number' && t1Total > 0, `T1: totalValue must be positive, got ${t1Total}`);
275
+ console.log(`[T1] passed: prices=[AAPL,MSFT], rows=2, totalValue=${t1Total.toFixed(2)}`);
276
+
277
+ // ── T2a: Add GOOG to holdings ────────────────────────────────────────────────
278
+ console.log('\n=== T2a: Update holdings — add GOOG ===');
279
+ const t2Patch = await httpPatch(
280
+ `${BASE}/cards/portfolio-form`,
281
+ makeHoldingsPatch({ AAPL: 50, MSFT: 30, GOOG: 100 }),
282
+ );
283
+ assert(t2Patch.status === 200, `PATCH portfolio-form returned ${t2Patch.status}`);
284
+ console.log('[T2a] PATCH ok — worker will receive SSE notifications independently');
285
+
286
+ // ── T2b: Wait for 3-ticker completion ───────────────────────────────────────
287
+ console.log('\n=== T2b: Wait for 3-ticker completion ===');
288
+ const t2Summary = await waitForAllCompleted(60_000, 'T2b 3-ticker drain');
289
+ console.log(`[T2b] completed — ${JSON.stringify(t2Summary)}`);
290
+
291
+ const t2Prices = await waitForPriceSymbols(['AAPL', 'GOOG', 'MSFT'], 30_000, 'T2b prices');
292
+ const t2Table = NS.computedValues['holdings-table']?.table;
293
+ assert(Array.isArray(t2Table?.rows) && t2Table.rows.length === 3, `T2b: expected 3 rows, got ${t2Table?.rows?.length}`);
294
+ const t2Total = NS.computedValues['portfolio-value']?.totalValue;
295
+ assert(typeof t2Total === 'number' && t2Total > 0, 'T2b: totalValue must be positive');
296
+ console.log(`[T2b] passed: prices=[AAPL,GOOG,MSFT], rows=3, totalValue=${t2Total.toFixed(2)}`);
297
+
298
+ // ── T3: Rapid 3× holdings updates (queue stress) ─────────────────────────────
299
+ // The worker independently streams all SSE notifications while the driver
300
+ // fires rapid PATCHes. NS accumulates state continuously in both cases.
301
+ console.log('\n=== T3: Rapid 3× holdings updates ===');
302
+ const rapidUpdates = [
303
+ { AAPL: 45, MSFT: 30, GOOG: 110, TSLA: 60 },
304
+ { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 100 }, // intermediate — not expected to be final
305
+ { AAPL: 40, MSFT: 35, GOOG: 120, TSLA: 70 }, // V5 — expected final state
306
+ ];
307
+ for (const holdings of rapidUpdates) {
308
+ await httpPatch(`${BASE}/cards/portfolio-form`, makeHoldingsPatch(holdings));
309
+ }
310
+ console.log('[T3] rapid PATCHes sent — worker accumulates SSE state in parallel');
311
+
312
+ await waitForAllCompleted(60_000, 'T3 rapid-update drain');
313
+ const t3Prices = await waitForPriceSymbols(['AAPL', 'GOOG', 'MSFT', 'TSLA'], 30_000, 'T3 final prices');
314
+ const t3Table = NS.computedValues['holdings-table']?.table;
315
+ assert(Array.isArray(t3Table?.rows) && t3Table.rows.length === 4, `T3: expected 4 rows, got ${t3Table?.rows?.length}`);
316
+ assert(!Object.keys(t3Prices).includes('AMZN'), `T3: AMZN must not be present (got ${JSON.stringify(Object.keys(t3Prices))})`);
317
+ console.log(`[T3] passed: prices=${JSON.stringify(Object.keys(t3Prices).sort())}, rows=4, AMZN absent`);
318
+
319
+ // ── T4: Cross-verify portfolio-value totalValue ───────────────────────────────
320
+ console.log('\n=== T4: Cross-verify totalValue ===');
321
+ const t4Total = NS.computedValues['portfolio-value']?.totalValue;
322
+ assert(typeof t4Total === 'number' && t4Total > 0, `T4: totalValue must be positive, got ${t4Total}`);
323
+ const sumRows = t3Table.rows.reduce((acc, r) => acc + (r.value || 0), 0);
324
+ assert(Math.abs(sumRows - t4Total) < 0.01, `T4: mismatch: sumRows=${sumRows}, totalValue=${t4Total}`);
325
+ console.log(`[T4] passed: totalValue=${t4Total.toFixed(2)}, sumRows=${sumRows.toFixed(2)}`);
326
+
327
+ // ── T5: board-status HTTP cross-check ────────────────────────────────────────
328
+ // Compare the HTTP board-status endpoint response against what the worker
329
+ // accumulated via SSE — the two sources must agree.
330
+ console.log('\n=== T5: board-status HTTP cross-check ===');
331
+ const t5Res = await httpGet(`${BASE}/board-status`);
332
+ assert(t5Res.status === 200, `board-status returned ${t5Res.status}`);
333
+ const t5Summary = t5Res.data?.statusSnapshot?.summary;
334
+ assert(t5Summary, 'T5: statusSnapshot.summary missing from board-status');
335
+ assert(t5Summary.completed === t5Summary.card_count,
336
+ `T5: completed=${t5Summary.completed} !== card_count=${t5Summary.card_count}`);
337
+ assert(t5Summary.failed === 0, `T5: failed=${t5Summary.failed} (expected 0)`);
338
+
339
+ // Cross-check: dataObjects from HTTP response matches what worker accumulated
340
+ const httpDataObjKeys = Object.keys(t5Res.data.dataObjectsByToken || {}).sort().join(',');
341
+ const workerDataObjKeys = Object.keys(NS.dataObjects).sort().join(',');
342
+ assert(httpDataObjKeys === workerDataObjKeys,
343
+ `T5: HTTP dataObjects keys [${httpDataObjKeys}] differ from worker-accumulated [${workerDataObjKeys}]`);
344
+
345
+ console.log(`[T5] summary: ${JSON.stringify(t5Summary)}`);
346
+ console.log(`[T5] HTTP vs worker dataObjects agree: [${workerDataObjKeys}]`);
347
+ console.log(`[T5] statusGen at end: ${NS.statusGeneration}`);
348
+ console.log('[T5] all assertions passed');
349
+
350
+ console.log('\n=== All tests passed ✓ ===\n');
351
+
352
+ } finally {
353
+ sseWorker?.terminate();
354
+ serverProc.kill();
355
+ await new Promise(r => serverProc.on('exit', r));
356
+ console.log(`[portfolio-tracker-http-test] server stopped (${SERVER_TYPE})`);
357
+ }
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import * as fs from 'node:fs';
4
3
  import * as path from 'node:path';
4
+ import * as fs from 'node:fs';
5
+ import { parseRef, blobStorageForRef } from 'yaml-flow/storage-refs';
5
6
 
6
7
  function parseArgs(argv) {
7
- const inIdx = argv.indexOf('--in');
8
- const outIdx = argv.indexOf('--out');
9
- const errIdx = argv.indexOf('--err');
10
- const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
11
- const outFile = outIdx !== -1 ? argv[outIdx + 1] : undefined;
12
- const errFile = errIdx !== -1 ? argv[errIdx + 1] : undefined;
13
- if (!inFile || !outFile || !errFile) {
14
- console.error('Usage: <adapter> run-inference --in <input.json> --out <output.json> --err <error.txt>');
8
+ const inIdx = argv.indexOf('--in-ref');
9
+ const outIdx = argv.indexOf('--out-ref');
10
+ const errIdx = argv.indexOf('--err-ref');
11
+ const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;
12
+ const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;
13
+ const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;
14
+ if (!inRefStr || !outRefStr || !errRefStr) {
15
+ console.error('Usage: <adapter> run-inference --in-ref <::kind::value> --out-ref <::kind::value> --err-ref <::kind::value>');
15
16
  process.exit(1);
16
17
  }
17
- return { inFile, outFile, errFile };
18
+ const inRef = parseRef(inRefStr);
19
+ const outRef = parseRef(outRefStr);
20
+ const errRef = parseRef(errRefStr);
21
+ const inStorage = blobStorageForRef(inRef);
22
+ const outStorage = blobStorageForRef(outRef);
23
+ const errStorage = blobStorageForRef(errRef);
24
+ return { inRef, outRef, errRef, inStorage, outStorage, errStorage };
18
25
  }
19
26
 
20
27
  const envBoardDir = (process.env.BOARD_DIR ?? '').trim();
@@ -144,10 +151,12 @@ async function main() {
144
151
  process.exit(1);
145
152
  }
146
153
 
147
- const { inFile, outFile, errFile } = parseArgs(process.argv.slice(3));
154
+ const { inRef, outRef, errRef, inStorage, outStorage, errStorage } = parseArgs(process.argv.slice(3));
148
155
 
149
156
  try {
150
- const payload = JSON.parse(fs.readFileSync(inFile, 'utf-8'));
157
+ const rawIn = inStorage.read(inRef.value);
158
+ if (rawIn === null) throw new Error(`Input not found: ${inRef.value}`);
159
+ const payload = JSON.parse(rawIn);
151
160
  const tmpCandidates = resolveSyncTmpFileCandidates(payload);
152
161
  if (tmpCandidates.length > 0) {
153
162
  await waitForTmpSyncInput(tmpCandidates);
@@ -174,12 +183,12 @@ async function main() {
174
183
  };
175
184
  }
176
185
 
177
- fs.writeFileSync(outFile, JSON.stringify(result), 'utf-8');
178
- fs.writeFileSync(errFile, '', 'utf-8');
186
+ outStorage.write(outRef.value, JSON.stringify(result));
187
+ errStorage.write(errRef.value, '');
179
188
  } catch (err) {
180
189
  const message = err instanceof Error ? err.message : String(err);
181
- fs.writeFileSync(errFile, message, 'utf-8');
182
- fs.writeFileSync(outFile, JSON.stringify({ isTaskCompleted: false, reason: message, evidence: '' }), 'utf-8');
190
+ errStorage.write(errRef.value, message);
191
+ outStorage.write(outRef.value, JSON.stringify({ isTaskCompleted: false, reason: message, evidence: '' }));
183
192
  process.exit(1);
184
193
  }
185
194
  }