@sapiom/harness 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -43
  3. package/dist/cli/auth.d.ts +26 -0
  4. package/dist/cli/auth.d.ts.map +1 -0
  5. package/dist/cli/auth.js +39 -0
  6. package/dist/cli/auth.js.map +1 -0
  7. package/dist/cli/bin.d.ts +3 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +163 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/consent.d.ts +15 -0
  12. package/dist/cli/consent.d.ts.map +1 -0
  13. package/dist/cli/consent.js +81 -0
  14. package/dist/cli/consent.js.map +1 -0
  15. package/dist/cli/doctor.d.ts +27 -0
  16. package/dist/cli/doctor.d.ts.map +1 -0
  17. package/dist/cli/doctor.js +74 -0
  18. package/dist/cli/doctor.js.map +1 -0
  19. package/dist/cli/machine-id.d.ts +19 -0
  20. package/dist/cli/machine-id.d.ts.map +1 -0
  21. package/dist/cli/machine-id.js +75 -0
  22. package/dist/cli/machine-id.js.map +1 -0
  23. package/dist/cli/paths.d.ts +3 -0
  24. package/dist/cli/paths.d.ts.map +1 -0
  25. package/dist/cli/paths.js +11 -0
  26. package/dist/cli/paths.js.map +1 -0
  27. package/dist/cli/settings.d.ts +26 -0
  28. package/dist/cli/settings.d.ts.map +1 -0
  29. package/dist/cli/settings.js +123 -0
  30. package/dist/cli/settings.js.map +1 -0
  31. package/dist/core/adapters/claude-code.d.ts +41 -0
  32. package/dist/core/adapters/claude-code.d.ts.map +1 -0
  33. package/dist/core/adapters/claude-code.js +238 -0
  34. package/dist/core/adapters/claude-code.js.map +1 -0
  35. package/dist/core/adapters/codex.d.ts +59 -0
  36. package/dist/core/adapters/codex.d.ts.map +1 -0
  37. package/dist/core/adapters/codex.js +319 -0
  38. package/dist/core/adapters/codex.js.map +1 -0
  39. package/dist/core/canvas-body.d.ts +45 -0
  40. package/dist/core/canvas-body.d.ts.map +1 -0
  41. package/dist/core/canvas-body.js +94 -0
  42. package/dist/core/canvas-body.js.map +1 -0
  43. package/dist/core/canvas-cache.d.ts +23 -0
  44. package/dist/core/canvas-cache.d.ts.map +1 -0
  45. package/dist/core/canvas-cache.js +64 -0
  46. package/dist/core/canvas-cache.js.map +1 -0
  47. package/dist/core/canvas-enrich.d.ts +106 -0
  48. package/dist/core/canvas-enrich.d.ts.map +1 -0
  49. package/dist/core/canvas-enrich.js +178 -0
  50. package/dist/core/canvas-enrich.js.map +1 -0
  51. package/dist/core/canvas-enrichment.d.ts +144 -0
  52. package/dist/core/canvas-enrichment.d.ts.map +1 -0
  53. package/dist/core/canvas-enrichment.js +192 -0
  54. package/dist/core/canvas-enrichment.js.map +1 -0
  55. package/dist/core/canvas-graph.d.ts +71 -0
  56. package/dist/core/canvas-graph.d.ts.map +1 -0
  57. package/dist/core/canvas-graph.js +111 -0
  58. package/dist/core/canvas-graph.js.map +1 -0
  59. package/dist/core/canvas-index-classify.d.ts +41 -0
  60. package/dist/core/canvas-index-classify.d.ts.map +1 -0
  61. package/dist/core/canvas-index-classify.js +63 -0
  62. package/dist/core/canvas-index-classify.js.map +1 -0
  63. package/dist/core/canvas-interconnections.d.ts +24 -0
  64. package/dist/core/canvas-interconnections.d.ts.map +1 -0
  65. package/dist/core/canvas-interconnections.js +96 -0
  66. package/dist/core/canvas-interconnections.js.map +1 -0
  67. package/dist/core/canvas-manifest-check.d.ts +15 -0
  68. package/dist/core/canvas-manifest-check.d.ts.map +1 -0
  69. package/dist/core/canvas-manifest-check.js +90 -0
  70. package/dist/core/canvas-manifest-check.js.map +1 -0
  71. package/dist/core/canvas-render.d.ts +73 -0
  72. package/dist/core/canvas-render.d.ts.map +1 -0
  73. package/dist/core/canvas-render.js +145 -0
  74. package/dist/core/canvas-render.js.map +1 -0
  75. package/dist/core/canvas-svg.d.ts +50 -0
  76. package/dist/core/canvas-svg.d.ts.map +1 -0
  77. package/dist/core/canvas-svg.js +240 -0
  78. package/dist/core/canvas-svg.js.map +1 -0
  79. package/dist/core/canvas-template.d.ts +43 -0
  80. package/dist/core/canvas-template.d.ts.map +1 -0
  81. package/dist/core/canvas-template.js +365 -0
  82. package/dist/core/canvas-template.js.map +1 -0
  83. package/dist/core/canvas-watcher.d.ts +25 -0
  84. package/dist/core/canvas-watcher.d.ts.map +1 -0
  85. package/dist/core/canvas-watcher.js +144 -0
  86. package/dist/core/canvas-watcher.js.map +1 -0
  87. package/dist/core/collector/batcher.d.ts +57 -0
  88. package/dist/core/collector/batcher.d.ts.map +1 -0
  89. package/dist/core/collector/batcher.js +143 -0
  90. package/dist/core/collector/batcher.js.map +1 -0
  91. package/dist/core/collector/codex-tailer.d.ts +85 -0
  92. package/dist/core/collector/codex-tailer.d.ts.map +1 -0
  93. package/dist/core/collector/codex-tailer.js +270 -0
  94. package/dist/core/collector/codex-tailer.js.map +1 -0
  95. package/dist/core/collector/normalizer.d.ts +33 -0
  96. package/dist/core/collector/normalizer.d.ts.map +1 -0
  97. package/dist/core/collector/normalizer.js +94 -0
  98. package/dist/core/collector/normalizer.js.map +1 -0
  99. package/dist/core/collector/seq.d.ts +14 -0
  100. package/dist/core/collector/seq.d.ts.map +1 -0
  101. package/dist/core/collector/seq.js +20 -0
  102. package/dist/core/collector/seq.js.map +1 -0
  103. package/dist/core/collector/store.d.ts +16 -0
  104. package/dist/core/collector/store.d.ts.map +1 -0
  105. package/dist/core/collector/store.js +30 -0
  106. package/dist/core/collector/store.js.map +1 -0
  107. package/dist/core/collector/transcript.d.ts +38 -0
  108. package/dist/core/collector/transcript.d.ts.map +1 -0
  109. package/dist/core/collector/transcript.js +110 -0
  110. package/dist/core/collector/transcript.js.map +1 -0
  111. package/dist/core/event-bus.d.ts +9 -0
  112. package/dist/core/event-bus.d.ts.map +1 -0
  113. package/dist/core/event-bus.js +25 -0
  114. package/dist/core/event-bus.js.map +1 -0
  115. package/dist/core/example-seed.d.ts +34 -0
  116. package/dist/core/example-seed.d.ts.map +1 -0
  117. package/dist/core/example-seed.js +277 -0
  118. package/dist/core/example-seed.js.map +1 -0
  119. package/dist/core/inject/claude-settings.d.ts +34 -0
  120. package/dist/core/inject/claude-settings.d.ts.map +1 -0
  121. package/dist/core/inject/claude-settings.js +152 -0
  122. package/dist/core/inject/claude-settings.js.map +1 -0
  123. package/dist/core/inject/mcp-config.d.ts +26 -0
  124. package/dist/core/inject/mcp-config.d.ts.map +1 -0
  125. package/dist/core/inject/mcp-config.js +39 -0
  126. package/dist/core/inject/mcp-config.js.map +1 -0
  127. package/dist/core/inject/retention.d.ts +66 -0
  128. package/dist/core/inject/retention.d.ts.map +1 -0
  129. package/dist/core/inject/retention.js +119 -0
  130. package/dist/core/inject/retention.js.map +1 -0
  131. package/dist/core/inject/system-prompt.d.ts +11 -0
  132. package/dist/core/inject/system-prompt.d.ts.map +1 -0
  133. package/dist/core/inject/system-prompt.js +22 -0
  134. package/dist/core/inject/system-prompt.js.map +1 -0
  135. package/dist/core/macro-runner.d.ts +36 -0
  136. package/dist/core/macro-runner.d.ts.map +1 -0
  137. package/dist/core/macro-runner.js +66 -0
  138. package/dist/core/macro-runner.js.map +1 -0
  139. package/dist/core/macros.d.ts +10 -0
  140. package/dist/core/macros.d.ts.map +1 -0
  141. package/dist/core/macros.js +59 -0
  142. package/dist/core/macros.js.map +1 -0
  143. package/dist/core/path-safety.d.ts +33 -0
  144. package/dist/core/path-safety.d.ts.map +1 -0
  145. package/dist/core/path-safety.js +63 -0
  146. package/dist/core/path-safety.js.map +1 -0
  147. package/dist/core/paths.d.ts +27 -0
  148. package/dist/core/paths.d.ts.map +1 -0
  149. package/dist/core/paths.js +43 -0
  150. package/dist/core/paths.js.map +1 -0
  151. package/dist/core/port-detector.d.ts +69 -0
  152. package/dist/core/port-detector.d.ts.map +1 -0
  153. package/dist/core/port-detector.js +121 -0
  154. package/dist/core/port-detector.js.map +1 -0
  155. package/dist/core/session-manager.d.ts +275 -0
  156. package/dist/core/session-manager.d.ts.map +1 -0
  157. package/dist/core/session-manager.js +729 -0
  158. package/dist/core/session-manager.js.map +1 -0
  159. package/dist/core/task-manager.d.ts +138 -0
  160. package/dist/core/task-manager.d.ts.map +1 -0
  161. package/dist/core/task-manager.js +286 -0
  162. package/dist/core/task-manager.js.map +1 -0
  163. package/dist/core/task-stream.d.ts +32 -0
  164. package/dist/core/task-stream.d.ts.map +1 -0
  165. package/dist/core/task-stream.js +87 -0
  166. package/dist/core/task-stream.js.map +1 -0
  167. package/dist/core/workflow-registry.d.ts +36 -0
  168. package/dist/core/workflow-registry.d.ts.map +1 -0
  169. package/dist/core/workflow-registry.js +220 -0
  170. package/dist/core/workflow-registry.js.map +1 -0
  171. package/dist/core/workspace-context.d.ts +43 -0
  172. package/dist/core/workspace-context.d.ts.map +1 -0
  173. package/dist/core/workspace-context.js +108 -0
  174. package/dist/core/workspace-context.js.map +1 -0
  175. package/dist/core/workspace-watcher.d.ts +26 -0
  176. package/dist/core/workspace-watcher.d.ts.map +1 -0
  177. package/dist/core/workspace-watcher.js +174 -0
  178. package/dist/core/workspace-watcher.js.map +1 -0
  179. package/dist/index.d.ts +6 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +6 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/profiles/canvas-guidelines.d.ts +20 -0
  184. package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
  185. package/dist/profiles/canvas-guidelines.js +48 -0
  186. package/dist/profiles/canvas-guidelines.js.map +1 -0
  187. package/dist/profiles/default.d.ts +10 -0
  188. package/dist/profiles/default.d.ts.map +1 -0
  189. package/dist/profiles/default.js +70 -0
  190. package/dist/profiles/default.js.map +1 -0
  191. package/dist/server/auth.d.ts +9 -0
  192. package/dist/server/auth.d.ts.map +1 -0
  193. package/dist/server/auth.js +28 -0
  194. package/dist/server/auth.js.map +1 -0
  195. package/dist/server/canvas-render.d.ts +18 -0
  196. package/dist/server/canvas-render.d.ts.map +1 -0
  197. package/dist/server/canvas-render.js +24 -0
  198. package/dist/server/canvas-render.js.map +1 -0
  199. package/dist/server/canvas.d.ts +12 -0
  200. package/dist/server/canvas.d.ts.map +1 -0
  201. package/dist/server/canvas.js +174 -0
  202. package/dist/server/canvas.js.map +1 -0
  203. package/dist/server/events-ws.d.ts +11 -0
  204. package/dist/server/events-ws.d.ts.map +1 -0
  205. package/dist/server/events-ws.js +24 -0
  206. package/dist/server/events-ws.js.map +1 -0
  207. package/dist/server/fs.d.ts +4 -0
  208. package/dist/server/fs.d.ts.map +1 -0
  209. package/dist/server/fs.js +88 -0
  210. package/dist/server/fs.js.map +1 -0
  211. package/dist/server/index.d.ts +83 -0
  212. package/dist/server/index.d.ts.map +1 -0
  213. package/dist/server/index.js +0 -0
  214. package/dist/server/index.js.map +1 -0
  215. package/dist/server/ingest.d.ts +74 -0
  216. package/dist/server/ingest.d.ts.map +1 -0
  217. package/dist/server/ingest.js +74 -0
  218. package/dist/server/ingest.js.map +1 -0
  219. package/dist/server/macros.d.ts +32 -0
  220. package/dist/server/macros.d.ts.map +1 -0
  221. package/dist/server/macros.js +76 -0
  222. package/dist/server/macros.js.map +1 -0
  223. package/dist/server/rest.d.ts +75 -0
  224. package/dist/server/rest.d.ts.map +1 -0
  225. package/dist/server/rest.js +235 -0
  226. package/dist/server/rest.js.map +1 -0
  227. package/dist/server/static.d.ts +8 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +36 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/terminal-ws.d.ts +9 -0
  232. package/dist/server/terminal-ws.d.ts.map +1 -0
  233. package/dist/server/terminal-ws.js +59 -0
  234. package/dist/server/terminal-ws.js.map +1 -0
  235. package/dist/server/ws-router.d.ts +16 -0
  236. package/dist/server/ws-router.d.ts.map +1 -0
  237. package/dist/server/ws-router.js +26 -0
  238. package/dist/server/ws-router.js.map +1 -0
  239. package/dist/shared/types.d.ts +485 -0
  240. package/dist/shared/types.d.ts.map +1 -0
  241. package/dist/shared/types.js +80 -0
  242. package/dist/shared/types.js.map +1 -0
  243. package/dist/web/assets/index-B9R4TzH7.css +32 -0
  244. package/dist/web/assets/index-Dh97QAgq.js +192 -0
  245. package/dist/web/index.html +27 -0
  246. package/package.json +84 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @sapiom/harness — library entry. The CLI (`sapiom-harness`) is the primary
3
+ * interface; this export exists so other packages can reuse the contract.
4
+ */
5
+ export * from "./shared/types.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Canvas style contract: injected everywhere an agent is asked to write to
3
+ * `.sapiom/canvas/index.html` (the Visualize macro's inject text, and the
4
+ * system prompt's canvas paragraph for freeform "show me X" asks), so every
5
+ * generated canvas looks Sapiom-native regardless of which prompt triggered
6
+ * it. Kept prompt-sized on purpose — this rides in prompts, not docs.
7
+ *
8
+ * Colors are the harness's own dark-theme tokens (web/src/styles.css,
9
+ * `[data-theme="dark"]`) — dark is the canonical canvas look. Structural
10
+ * conventions (rounded nodes with a uniform glow, straight-then-curved
11
+ * arrowed edges, stats header, legend footer) mirror what
12
+ * scripts/seed-example.mjs's reference canvas already renders; this doesn't
13
+ * change that seed, just documents its pattern for agents generating new
14
+ * ones. Edge/glow/legend wording tightened after a live Visualize proof
15
+ * (see PR history) surfaced ambiguities two different agent runs resolved
16
+ * inconsistently — see git blame for the before/after if the wording here
17
+ * ever seems oddly specific.
18
+ */
19
+ export declare const CANVAS_STYLE_GUIDELINES: string;
20
+ //# sourceMappingURL=canvas-guidelines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-guidelines.d.ts","sourceRoot":"","sources":["../../src/profiles/canvas-guidelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QA4B5B,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Canvas style contract: injected everywhere an agent is asked to write to
3
+ * `.sapiom/canvas/index.html` (the Visualize macro's inject text, and the
4
+ * system prompt's canvas paragraph for freeform "show me X" asks), so every
5
+ * generated canvas looks Sapiom-native regardless of which prompt triggered
6
+ * it. Kept prompt-sized on purpose — this rides in prompts, not docs.
7
+ *
8
+ * Colors are the harness's own dark-theme tokens (web/src/styles.css,
9
+ * `[data-theme="dark"]`) — dark is the canonical canvas look. Structural
10
+ * conventions (rounded nodes with a uniform glow, straight-then-curved
11
+ * arrowed edges, stats header, legend footer) mirror what
12
+ * scripts/seed-example.mjs's reference canvas already renders; this doesn't
13
+ * change that seed, just documents its pattern for agents generating new
14
+ * ones. Edge/glow/legend wording tightened after a live Visualize proof
15
+ * (see PR history) surfaced ambiguities two different agent runs resolved
16
+ * inconsistently — see git blame for the before/after if the wording here
17
+ * ever seems oddly specific.
18
+ */
19
+ export const CANVAS_STYLE_GUIDELINES = `
20
+ Canvas style contract (.sapiom/canvas/index.html):
21
+ - One self-contained HTML file: inline all CSS/JS, no external stylesheets/
22
+ scripts or CDN fonts (the CSP blocks them) — no build step, no dependencies.
23
+ - Dark theme, Sapiom palette: background #0f0f0f, panel #1a1a1a, border
24
+ #2e2e2e, text #fafafa, dim text #a1a1aa, accent/success #6be195, escalation
25
+ #f59e0b, failure #f87171.
26
+ - Monospace throughout: ui-monospace, "SF Mono", Menlo, Consolas, monospace.
27
+ - Header: title + short status badge, one-line subtitle, and a stats row
28
+ (e.g. step count, terminal-outcome count, branch count) as label/value pairs.
29
+ - Render the workflow as an inline SVG graph, top-to-bottom: rounded-rect
30
+ nodes (12-16px radius). Every node gets the same subtle glow on its
31
+ border — never reserve the glow for entry/terminal nodes only; role and
32
+ outcome are conveyed by border/stroke color, not by which nodes glow.
33
+ - Edges: straight lines with arrowhead markers for a single-successor step,
34
+ curved paths with arrowhead markers only where a step branches into
35
+ multiple successors — don't curve every edge, that reads as noise on a
36
+ simple linear chain.
37
+ - Color-code terminal outcomes by meaning: accent/green = success, amber =
38
+ escalation/needs-attention, red = failure. Reserve those colors for actual
39
+ terminal branches, not regular steps.
40
+ - Footer legend: one visually distinct marker per node kind used (shape
41
+ and/or fill vs. outline — never reuse the identical marker for two
42
+ different kinds just because they happen to share a color) + label, plus
43
+ a short note that this is a static preview to regenerate after the
44
+ workflow changes.
45
+ - Prefer clarity over decoration: no motion, gradients, or ornamentation that
46
+ doesn't help someone understand the graph's structure in a few seconds.
47
+ `.trim();
48
+ //# sourceMappingURL=canvas-guidelines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-guidelines.js","sourceRoot":"","sources":["../../src/profiles/canvas-guidelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BtC,CAAC,IAAI,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Default system prompt, appended to the coding agent's own instructions via
3
+ * `--append-system-prompt`. Orients a fresh session to the Sapiom-specific
4
+ * conventions the harness adds on top of a stock coding agent. Written to be
5
+ * assertive, not just informative — first-user feedback showed the prompt
6
+ * was being injected (confirmed via ps) but behaviorally invisible, so the
7
+ * closing line asks for one visible signal that it actually loaded.
8
+ */
9
+ export declare const DEFAULT_SYSTEM_PROMPT: string;
10
+ //# sourceMappingURL=default.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default.d.ts","sourceRoot":"","sources":["../../src/profiles/default.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QA2D1B,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { CANVAS_STYLE_GUIDELINES } from "./canvas-guidelines.js";
2
+ /**
3
+ * Default system prompt, appended to the coding agent's own instructions via
4
+ * `--append-system-prompt`. Orients a fresh session to the Sapiom-specific
5
+ * conventions the harness adds on top of a stock coding agent. Written to be
6
+ * assertive, not just informative — first-user feedback showed the prompt
7
+ * was being injected (confirmed via ps) but behaviorally invisible, so the
8
+ * closing line asks for one visible signal that it actually loaded.
9
+ */
10
+ export const DEFAULT_SYSTEM_PROMPT = `
11
+ You are running in the Sapiom Harness. This is not a stock coding session —
12
+ you have two Sapiom MCP servers pre-wired, and the conventions below are
13
+ active for the whole session. Follow them.
14
+
15
+ **The two MCPs, and when to use each:**
16
+ - **sapiom** (remote, HTTP) — the paid capability surface an agent calls at
17
+ *runtime* from inside a deployed agent's step code (ctx.sapiom.*):
18
+ repositories, sandboxes, models, and so on. You don't call this directly
19
+ while authoring.
20
+ - **sapiom-dev** (local, stdio) — the unmetered authoring surface for this
21
+ session. Use its sapiom_dev_agents_* tools to scaffold, validate, and ship
22
+ agents, and sapiom_authenticate / sapiom_status if you need to sign in.
23
+
24
+ **The authoring loop, in order:** scaffold a new agent project → check
25
+ (bundle + manifest + step-graph validation, offline) → run_local (your real
26
+ step code against stub capabilities, no cost) → link (associate the project
27
+ with a hosted agent) → deploy (push, build, go live). Read a project's
28
+ AGENTS.md before touching its steps — it documents that project's specifics.
29
+
30
+ **Canvas convention:** \`.sapiom/canvas/\` already has a prebuilt
31
+ \`_template.html\` (pristine, styles and markup patterns baked in — never
32
+ edit this one) and a live \`index.html\` cloned from it (the harness watches
33
+ this file and renders it live in the app's canvas pane). For "visualize this
34
+ workflow" / "how does everything connect" asks (including the Visualize
35
+ action), clone \`_template.html\` over \`index.html\` and fill in the content
36
+ using the node/edge/stats/legend markup patterns documented in the
37
+ template's own \`<template id="canvas-patterns">\` block — keep the
38
+ \`<style>\` block and structural classes untouched, write no new CSS. Only
39
+ write a full replacement file from scratch, ignoring the template, when
40
+ someone asks for a genuinely custom canvas its patterns can't represent; in
41
+ that case follow this style contract so it still looks Sapiom-native:
42
+ ${CANVAS_STYLE_GUIDELINES}
43
+
44
+ **Your current workspace state:** the harness mirrors what it knows about
45
+ this workspace at \`.sapiom/harness-context.json\`, relative to your working
46
+ directory (\`{"boundWorkflow": {name, path, definitionId} | null,
47
+ "workflows": [{name, path, definitionId}, ...], "session": {id, cwd,
48
+ harness}, "updatedAt": ...}\`). \`boundWorkflow\` is whichever workflow the
49
+ person currently has selected in the app, or \`null\` if none;
50
+ \`workflows\` is every workflow the app has discovered here, selected or
51
+ not. Read it when they say "this workflow," ask what they're working on, or
52
+ ask what workflows exist — both fields can change mid-session (a new
53
+ selection, a newly scanned/connected project), so re-read the file rather
54
+ than assuming it's still what it was earlier in the conversation.
55
+
56
+ **In your very first reply this session**, orient the person before you get
57
+ to their actual request — briefly, 2-4 sentences total, not a lecture:
58
+ 1. Acknowledge that you're running in the Sapiom Harness with these MCPs
59
+ available (one line), so they can see this loaded.
60
+ 2. Say what you can do for them here: visualize a workflow on the canvas
61
+ pane, run it locally against stub capabilities at no cost, and deploy it
62
+ live — all also one click away via the action buttons next to each
63
+ workflow, or ⌘K.
64
+ 3. Suggest ONE concrete first step, picked from the workspace state file
65
+ above: if a workflow is bound or listed (e.g. the bundled order-triage
66
+ sample project), offer to visualize or run that one by name; if none
67
+ exists yet, offer to scaffold a new agent project. Phrase it as an
68
+ invitation ("want me to…?"), then stop — don't act on it unprompted.
69
+ `.trim();
70
+ //# sourceMappingURL=default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default.js","sourceRoot":"","sources":["../../src/profiles/default.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCnC,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BxB,CAAC,IAAI,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Boot-token auth: every /api request (X-Harness-Token header) and every WS
3
+ * upgrade (token query param) is checked against the same per-boot secret.
4
+ */
5
+ import type { RequestHandler } from "express";
6
+ /** Constant-time string comparison — avoids leaking token length/prefix via timing. */
7
+ export declare function timingSafeEqualString(a: string, b: string): boolean;
8
+ export declare function createBootTokenMiddleware(bootToken: string): RequestHandler;
9
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAUnE;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAS3E"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Boot-token auth: every /api request (X-Harness-Token header) and every WS
3
+ * upgrade (token query param) is checked against the same per-boot secret.
4
+ */
5
+ import { timingSafeEqual } from "node:crypto";
6
+ /** Constant-time string comparison — avoids leaking token length/prefix via timing. */
7
+ export function timingSafeEqualString(a, b) {
8
+ const bufA = Buffer.from(a);
9
+ const bufB = Buffer.from(b);
10
+ if (bufA.length !== bufB.length) {
11
+ // Compare something of equal length anyway so this branch doesn't
12
+ // resolve measurably faster than a real (mismatched-content) comparison.
13
+ timingSafeEqual(bufA, bufA);
14
+ return false;
15
+ }
16
+ return timingSafeEqual(bufA, bufB);
17
+ }
18
+ export function createBootTokenMiddleware(bootToken) {
19
+ return (req, res, next) => {
20
+ const provided = req.header("X-Harness-Token") ?? "";
21
+ if (!timingSafeEqualString(provided, bootToken)) {
22
+ res.status(401).json({ error: "unauthorized" });
23
+ return;
24
+ }
25
+ next();
26
+ };
27
+ }
28
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CAAC,CAAS,EAAE,CAAS;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,kEAAkE;QAClE,yEAAyE;QACzE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,SAAiB;IACzD,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `POST /api/canvas/:sessionId/render` — the deterministic, zero-LLM render
3
+ * trigger. Mounted under the same `/api` boot-token middleware as the rest of
4
+ * the REST surface (see server/index.ts). Renders the session's bound
5
+ * workflow to its per-workflow render file (a cheap no-op when unbound);
6
+ * writing the file is enough to hot-reload an already-open canvas pane
7
+ * (CanvasWatcherManager picks up the change on its own).
8
+ */
9
+ import { type Router as ExpressRouter } from "express";
10
+ import { type RenderableSession, type RenderableWorkflow } from "../core/canvas-render.js";
11
+ export interface CanvasRenderRouterDeps {
12
+ /** Look up a session's cwd + current binding; undefined when unknown. */
13
+ getSession(harnessSessionId: string): RenderableSession | undefined;
14
+ /** The live workflow registry snapshot. */
15
+ listWorkflows(): RenderableWorkflow[];
16
+ }
17
+ export declare function createCanvasRenderRouter(deps: CanvasRenderRouterDeps): ExpressRouter;
18
+ //# sourceMappingURL=canvas-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-render.d.ts","sourceRoot":"","sources":["../../src/server/canvas-render.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAU,KAAK,MAAM,IAAI,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAA0B,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnH,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,UAAU,CAAC,gBAAgB,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAAC;IACpE,2CAA2C;IAC3C,aAAa,IAAI,kBAAkB,EAAE,CAAC;CACvC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,sBAAsB,GAAG,aAAa,CAcpF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `POST /api/canvas/:sessionId/render` — the deterministic, zero-LLM render
3
+ * trigger. Mounted under the same `/api` boot-token middleware as the rest of
4
+ * the REST surface (see server/index.ts). Renders the session's bound
5
+ * workflow to its per-workflow render file (a cheap no-op when unbound);
6
+ * writing the file is enough to hot-reload an already-open canvas pane
7
+ * (CanvasWatcherManager picks up the change on its own).
8
+ */
9
+ import { Router } from "express";
10
+ import { renderCanvasForSession } from "../core/canvas-render.js";
11
+ export function createCanvasRenderRouter(deps) {
12
+ const router = Router();
13
+ router.post("/canvas/:sessionId/render", async (req, res) => {
14
+ const session = deps.getSession(req.params.sessionId);
15
+ if (!session) {
16
+ res.status(404).json({ error: `Unknown session '${req.params.sessionId}'` });
17
+ return;
18
+ }
19
+ const outcome = await renderCanvasForSession(session, deps.listWorkflows());
20
+ res.json({ ok: true, ...outcome });
21
+ });
22
+ return router;
23
+ }
24
+ //# sourceMappingURL=canvas-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-render.js","sourceRoot":"","sources":["../../src/server/canvas-render.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAgC,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAmD,MAAM,0BAA0B,CAAC;AASnH,MAAM,UAAU,wBAAwB,CAAC,IAA4B;IACnE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QAC5E,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type Router as ExpressRouter } from "express";
2
+ export interface CanvasSession {
3
+ cwd: string;
4
+ /** The workflow the session is bound to, if any — decides what the canvas
5
+ * root request serves (the workflow's render vs. the legacy index.html). */
6
+ boundWorkflowPath?: string | null;
7
+ }
8
+ /** Looks up a session by harnessSessionId; undefined when unknown (the real
9
+ * implementation is the SessionManager, once workstream W1 lands). */
10
+ export type CanvasSessionLookup = (harnessSessionId: string) => CanvasSession | undefined;
11
+ export declare function createCanvasRouter(getSession: CanvasSessionLookup): ExpressRouter;
12
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/server/canvas.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAU,KAAK,MAAM,IAAI,aAAa,EAA+B,MAAM,SAAS,CAAC;AAkE5F,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ;iFAC6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;uEACuE;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,gBAAgB,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAwF1F,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,GAAG,aAAa,CAejF"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Canvas serving (workstream W5's backend slice).
3
+ *
4
+ * Serves files under `<cwd>/.sapiom/canvas/` (see CANVAS_DIR) at
5
+ * `GET /canvas/:harnessSessionId/*`. The root request resolves by binding:
6
+ * a session bound to a workflow gets that workflow's deterministic render
7
+ * (`renders/<slug>.html` — resolved at request time, so switching the
8
+ * binding is instant, no file rewrite involved); an unbound session gets its
9
+ * agent-authored `index.html` custom canvas — but ONLY when the file is a
10
+ * genuine custom canvas. A machine-generated `index.html` (the seeded
11
+ * template, or a stale legacy deterministic overview a pre-split server wrote
12
+ * there) is NOT served: the unbound session gets the clean empty state
13
+ * instead, so it never opens to a wall of unrelated "render failed" panels
14
+ * (see core/canvas-index-classify.ts). The SPA's CanvasPane embeds this in a sandboxed iframe
15
+ * and probes it with a HEAD request (`fetch('/canvas/<id>/', { method:
16
+ * 'HEAD' })`), so this stays intentionally simple and unauthenticated — the
17
+ * server binds 127.0.0.1 only, and an iframe `src` / HEAD probe can't carry
18
+ * the boot token anyway. The integrator mounts this alongside the
19
+ * SessionManager.
20
+ */
21
+ import * as fs from "node:fs";
22
+ import * as path from "node:path";
23
+ import { Router } from "express";
24
+ import { CANVAS_DIR, CANVAS_INDEX } from "../shared/types.js";
25
+ import { slugForWorkflowPath } from "../core/canvas-render.js";
26
+ import { isMachineGeneratedCanvas } from "../core/canvas-index-classify.js";
27
+ import { resolveWithinRoot } from "../core/path-safety.js";
28
+ const INDEX_FILENAME = path.basename(CANVAS_INDEX);
29
+ const MIME_TYPES = {
30
+ ".html": "text/html; charset=utf-8",
31
+ ".css": "text/css; charset=utf-8",
32
+ ".js": "text/javascript; charset=utf-8",
33
+ ".mjs": "text/javascript; charset=utf-8",
34
+ ".json": "application/json; charset=utf-8",
35
+ ".svg": "image/svg+xml",
36
+ ".png": "image/png",
37
+ ".jpg": "image/jpeg",
38
+ ".jpeg": "image/jpeg",
39
+ ".gif": "image/gif",
40
+ ".ico": "image/x-icon",
41
+ ".txt": "text/plain; charset=utf-8",
42
+ };
43
+ function contentTypeFor(filePath) {
44
+ return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
45
+ }
46
+ // Agent-generated HTML is expected to be self-contained (inline <script>/<style>,
47
+ // no build step — see the canvas convention), so this can't be a locked-down
48
+ // "no inline" policy. It still meaningfully narrows what that content can do:
49
+ // no plugins, no fetch-y content types, and it can only be framed by the
50
+ // harness's own origin (the SPA embeds it same-origin, in a sandboxed iframe
51
+ // with no allow-same-origin — this is defense in depth on top of that, not a
52
+ // replacement for it).
53
+ const CANVAS_CSP = "default-src 'self' data: blob:; script-src 'self' 'unsafe-inline'; " +
54
+ "style-src 'self' 'unsafe-inline'; img-src * data: blob:; connect-src *; " +
55
+ "object-src 'none'; frame-ancestors 'self'";
56
+ const EMPTY_STATE_HTML = `<!doctype html>
57
+ <html>
58
+ <head><meta charset="utf-8"><title>Canvas — nothing here yet</title></head>
59
+ <body style="font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; color: #444; text-align: center; padding: 0 2rem;">
60
+ <div>
61
+ <h1 style="font-size: 1.1rem; margin-bottom: 0.5rem;">Nothing rendered yet</h1>
62
+ <p style="margin: 0;">Select a workflow in the rail to visualize it, or ask your agent to write HTML to
63
+ <code>.sapiom/canvas/index.html</code> in this project — this pane hot-reloads whenever it changes.</p>
64
+ </div>
65
+ </body>
66
+ </html>`;
67
+ /** Served for a BOUND session whose render file hasn't been written yet
68
+ * (render in flight, or a server restart before any re-render). The pane
69
+ * hot-reloads the moment the render lands, so this is only ever briefly
70
+ * visible. */
71
+ const PENDING_RENDER_HTML = `<!doctype html>
72
+ <html>
73
+ <head><meta charset="utf-8"><title>Canvas — rendering</title></head>
74
+ <body style="font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; color: #444; text-align: center; padding: 0 2rem;">
75
+ <div>
76
+ <h1 style="font-size: 1.1rem; margin-bottom: 0.5rem;">Rendering workflow diagram…</h1>
77
+ <p style="margin: 0;">This pane reloads automatically when the diagram is ready.</p>
78
+ </div>
79
+ </body>
80
+ </html>`;
81
+ /** `relative === null` means "the canvas root" — what it resolves to depends
82
+ * on the session's current binding, decided per request so switching the
83
+ * bound workflow needs no file rewrite at all. */
84
+ function serveCanvas(getSession, req, res, relative) {
85
+ res.setHeader("Content-Security-Policy", CANVAS_CSP);
86
+ const session = getSession(req.params.harnessSessionId);
87
+ if (!session) {
88
+ res.status(404).end();
89
+ return;
90
+ }
91
+ const isRoot = relative === null;
92
+ const bound = isRoot ? (session.boundWorkflowPath ?? null) : null;
93
+ const resolved = isRoot
94
+ ? bound
95
+ ? path.join("renders", `${slugForWorkflowPath(bound)}.html`)
96
+ : INDEX_FILENAME
97
+ : relative;
98
+ // The unbound canvas root serves index.html ONLY when it's a genuine
99
+ // agent-authored custom canvas; a machine-generated one (seeded template or
100
+ // stale legacy overview) is shadowed by the empty state (see below).
101
+ const unboundRoot = isRoot && !bound;
102
+ const canvasRoot = path.resolve(session.cwd, CANVAS_DIR);
103
+ // Path-traversal guard, applied to the user-controlled request path
104
+ // (`resolved` is the `*` wildcard for a non-root request). resolveWithinRoot
105
+ // rejects any value — a `..` climb, an absolute path — that would resolve
106
+ // outside canvasRoot; a bound/index root request passes a server-built,
107
+ // already-safe `resolved` (a hashed slug or the index constant).
108
+ const filePath = resolveWithinRoot(canvasRoot, resolved);
109
+ if (!filePath) {
110
+ res.status(400).end();
111
+ return;
112
+ }
113
+ // A friendly explainer, served as a NON-ok 404 so the CanvasPane's HEAD
114
+ // probe reads "no content" and shows its own empty state rather than
115
+ // framing this page — while a direct browser visit still gets the hint.
116
+ const sendEmptyState = () => {
117
+ res.status(404).setHeader("Content-Type", "text/html; charset=utf-8");
118
+ res.end(EMPTY_STATE_HTML);
119
+ };
120
+ fs.stat(filePath, (err, stat) => {
121
+ if (err || !stat.isFile()) {
122
+ // A bound session whose render file isn't on disk yet gets the
123
+ // "rendering…" page — the pane hot-reloads once the render lands.
124
+ if (isRoot && bound) {
125
+ res.status(200).setHeader("Content-Type", "text/html; charset=utf-8");
126
+ res.end(PENDING_RENDER_HTML);
127
+ return;
128
+ }
129
+ // The canvas index specifically missing gets a friendly explainer
130
+ // (useful when this URL is opened directly, e.g. in a new tab during a
131
+ // demo); any other missing file (a referenced asset) stays a plain 404.
132
+ if (resolved === INDEX_FILENAME) {
133
+ sendEmptyState();
134
+ return;
135
+ }
136
+ res.status(404).end();
137
+ return;
138
+ }
139
+ // Unbound root: only serve index.html when it's a genuine custom canvas.
140
+ // A stale machine-generated file (seeded template, or a legacy
141
+ // deterministic overview a pre-split server wrote to index.html) shows the
142
+ // empty state instead — an unbound session must never open to a wall of
143
+ // unrelated "render failed" panels.
144
+ if (unboundRoot) {
145
+ fs.readFile(filePath, "utf8", (readErr, content) => {
146
+ if (readErr || isMachineGeneratedCanvas(content)) {
147
+ sendEmptyState();
148
+ return;
149
+ }
150
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
151
+ res.setHeader("Cache-Control", "no-store");
152
+ res.end(content);
153
+ });
154
+ return;
155
+ }
156
+ res.setHeader("Content-Type", contentTypeFor(filePath));
157
+ res.setHeader("Cache-Control", "no-store");
158
+ fs.createReadStream(filePath).pipe(res);
159
+ });
160
+ }
161
+ export function createCanvasRouter(getSession) {
162
+ const router = Router();
163
+ router.get("/canvas/:harnessSessionId/*", (req, res) => {
164
+ // Express's route-param typing doesn't model the unnamed `*` wildcard —
165
+ // it's there at runtime as params[0], just not in the inferred type.
166
+ const wildcard = req.params[0];
167
+ serveCanvas(getSession, req, res, wildcard || null);
168
+ });
169
+ router.get(["/canvas/:harnessSessionId", "/canvas/:harnessSessionId/"], (req, res) => {
170
+ serveCanvas(getSession, req, res, null);
171
+ });
172
+ return router;
173
+ }
174
+ //# sourceMappingURL=canvas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../src/server/canvas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * /ws/events?token= — server→client bus broadcasting BusMessage. Forwards
3
+ * whatever the shared EventBus publishes (session.status, canvas.reload,
4
+ * port.detected, workflows.changed) — the integrator is responsible for
5
+ * feeding all of those into the bus from their respective sources.
6
+ */
7
+ import type { IncomingMessage } from "node:http";
8
+ import type { WebSocket } from "ws";
9
+ import type { EventBus } from "../core/event-bus.js";
10
+ export declare function createEventsWebSocketHandler(bus: EventBus, bootToken: string): (ws: WebSocket, _req: IncomingMessage, params: URLSearchParams) => void;
11
+ //# sourceMappingURL=events-ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-ws.d.ts","sourceRoot":"","sources":["../../src/server/events-ws.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,IACnE,IAAI,SAAS,EAAE,MAAM,eAAe,EAAE,QAAQ,eAAe,KAAG,IAAI,CAgB7E"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * /ws/events?token= — server→client bus broadcasting BusMessage. Forwards
3
+ * whatever the shared EventBus publishes (session.status, canvas.reload,
4
+ * port.detected, workflows.changed) — the integrator is responsible for
5
+ * feeding all of those into the bus from their respective sources.
6
+ */
7
+ import { timingSafeEqualString } from "./auth.js";
8
+ export function createEventsWebSocketHandler(bus, bootToken) {
9
+ return (ws, _req, params) => {
10
+ const token = params.get("token") ?? "";
11
+ if (!timingSafeEqualString(token, bootToken)) {
12
+ ws.close(4001, "unauthorized");
13
+ return;
14
+ }
15
+ const send = (message) => {
16
+ if (ws.readyState === ws.OPEN)
17
+ ws.send(JSON.stringify(message));
18
+ };
19
+ const unsubscribe = bus.subscribe(send);
20
+ ws.on("close", unsubscribe);
21
+ ws.on("error", unsubscribe);
22
+ };
23
+ }
24
+ //# sourceMappingURL=events-ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-ws.js","sourceRoot":"","sources":["../../src/server/events-ws.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,UAAU,4BAA4B,CAAC,GAAa,EAAE,SAAiB;IAC3E,OAAO,CAAC,EAAa,EAAE,IAAqB,EAAE,MAAuB,EAAQ,EAAE;QAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;YAC7C,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,OAAmB,EAAQ,EAAE;YACzC,IAAI,EAAE,CAAC,UAAU,KAAK,EAAE,CAAC,IAAI;gBAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAExC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5B,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type Router as ExpressRouter } from "express";
2
+ export type { FsDirEntry, FsListResponse } from "../shared/types.js";
3
+ export declare function createFsRouter(): ExpressRouter;
4
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/server/fs.ts"],"names":[],"mappings":"AAUA,OAAO,EAAU,KAAK,MAAM,IAAI,aAAa,EAAE,MAAM,SAAS,CAAC;AAI/D,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUrE,wBAAgB,cAAc,IAAI,aAAa,CAyE9C"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Filesystem browsing for the SPA's path-picker autocomplete: `GET
3
+ * /api/fs/list?path=<abs-or-~>` → directories only, one level deep. This is
4
+ * a self-contained express Router with no dependency on the rest of the
5
+ * server — the integrator mounts it (behind the boot token, like the rest
6
+ * of /api) alongside everything else.
7
+ */
8
+ import * as fs from "node:fs/promises";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+ import { Router } from "express";
12
+ import { hasTraversalSegment } from "../core/path-safety.js";
13
+ const MAX_RESULTS = 200;
14
+ function expandHome(input) {
15
+ if (input === "~")
16
+ return os.homedir();
17
+ if (input.startsWith("~/"))
18
+ return path.join(os.homedir(), input.slice(2));
19
+ return input;
20
+ }
21
+ export function createFsRouter() {
22
+ const router = Router();
23
+ router.get("/api/fs/list", async (req, res) => {
24
+ const rawPath = req.query.path;
25
+ if (typeof rawPath !== "string" || !rawPath.trim()) {
26
+ res.status(400).json({ error: "path query param is required" });
27
+ return;
28
+ }
29
+ const expanded = expandHome(rawPath);
30
+ if (!path.isAbsolute(expanded)) {
31
+ res.status(400).json({ error: `path must be absolute (or start with ~): ${rawPath}` });
32
+ return;
33
+ }
34
+ // Normalizes `..`/`.`/duplicate-slash segments; still absolute since the
35
+ // input already was (checked above), so this can't turn a validated
36
+ // absolute path into a relative one.
37
+ const resolved = path.resolve(expanded);
38
+ // This picker intentionally browses any absolute directory the user names,
39
+ // so there's no single root to confine to — but a fully resolved path must
40
+ // never retain a `..` segment. Assert that explicitly at the sink: it
41
+ // rejects nothing legitimate (resolve() already normalized traversal away)
42
+ // and makes the no-traversal guarantee local to the readdir below.
43
+ if (hasTraversalSegment(resolved)) {
44
+ res.status(400).json({ error: `path must not contain traversal segments: ${rawPath}` });
45
+ return;
46
+ }
47
+ const includeHidden = req.query.hidden === "1";
48
+ let entries;
49
+ try {
50
+ // withFileTypes uses the OS's raw dirent info (not a followed lstat/stat),
51
+ // so a symlink — even one pointing at a directory — reports
52
+ // isDirectory() === false here and is naturally excluded below rather
53
+ // than being traversed into.
54
+ entries = await fs.readdir(resolved, { withFileTypes: true });
55
+ }
56
+ catch (err) {
57
+ const code = err.code;
58
+ if (code === "ENOENT") {
59
+ res.status(404).json({ error: `no such directory: ${resolved}` });
60
+ return;
61
+ }
62
+ if (code === "ENOTDIR") {
63
+ res.status(400).json({ error: `not a directory: ${resolved}` });
64
+ return;
65
+ }
66
+ if (code === "EACCES" || code === "EPERM") {
67
+ res.status(403).json({ error: `permission denied: ${resolved}` });
68
+ return;
69
+ }
70
+ res.status(500).json({ error: err.message });
71
+ return;
72
+ }
73
+ const dirs = entries
74
+ .filter((entry) => entry.isDirectory())
75
+ .filter((entry) => includeHidden || !entry.name.startsWith("."))
76
+ .map((entry) => ({ name: entry.name, path: path.join(resolved, entry.name) }))
77
+ .sort((a, b) => a.name.localeCompare(b.name))
78
+ .slice(0, MAX_RESULTS);
79
+ const response = {
80
+ path: resolved,
81
+ parent: path.dirname(resolved),
82
+ dirs,
83
+ };
84
+ res.json(response);
85
+ });
86
+ return router;
87
+ }
88
+ //# sourceMappingURL=fs.js.map