@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.
- package/LICENSE +21 -0
- package/README.md +44 -43
- package/dist/cli/auth.d.ts +26 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +39 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +163 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/consent.d.ts +15 -0
- package/dist/cli/consent.d.ts.map +1 -0
- package/dist/cli/consent.js +81 -0
- package/dist/cli/consent.js.map +1 -0
- package/dist/cli/doctor.d.ts +27 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +74 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/machine-id.d.ts +19 -0
- package/dist/cli/machine-id.d.ts.map +1 -0
- package/dist/cli/machine-id.js +75 -0
- package/dist/cli/machine-id.js.map +1 -0
- package/dist/cli/paths.d.ts +3 -0
- package/dist/cli/paths.d.ts.map +1 -0
- package/dist/cli/paths.js +11 -0
- package/dist/cli/paths.js.map +1 -0
- package/dist/cli/settings.d.ts +26 -0
- package/dist/cli/settings.d.ts.map +1 -0
- package/dist/cli/settings.js +123 -0
- package/dist/cli/settings.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +41 -0
- package/dist/core/adapters/claude-code.d.ts.map +1 -0
- package/dist/core/adapters/claude-code.js +238 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/codex.d.ts +59 -0
- package/dist/core/adapters/codex.d.ts.map +1 -0
- package/dist/core/adapters/codex.js +319 -0
- package/dist/core/adapters/codex.js.map +1 -0
- package/dist/core/canvas-body.d.ts +45 -0
- package/dist/core/canvas-body.d.ts.map +1 -0
- package/dist/core/canvas-body.js +94 -0
- package/dist/core/canvas-body.js.map +1 -0
- package/dist/core/canvas-cache.d.ts +23 -0
- package/dist/core/canvas-cache.d.ts.map +1 -0
- package/dist/core/canvas-cache.js +64 -0
- package/dist/core/canvas-cache.js.map +1 -0
- package/dist/core/canvas-enrich.d.ts +106 -0
- package/dist/core/canvas-enrich.d.ts.map +1 -0
- package/dist/core/canvas-enrich.js +178 -0
- package/dist/core/canvas-enrich.js.map +1 -0
- package/dist/core/canvas-enrichment.d.ts +144 -0
- package/dist/core/canvas-enrichment.d.ts.map +1 -0
- package/dist/core/canvas-enrichment.js +192 -0
- package/dist/core/canvas-enrichment.js.map +1 -0
- package/dist/core/canvas-graph.d.ts +71 -0
- package/dist/core/canvas-graph.d.ts.map +1 -0
- package/dist/core/canvas-graph.js +111 -0
- package/dist/core/canvas-graph.js.map +1 -0
- package/dist/core/canvas-index-classify.d.ts +41 -0
- package/dist/core/canvas-index-classify.d.ts.map +1 -0
- package/dist/core/canvas-index-classify.js +63 -0
- package/dist/core/canvas-index-classify.js.map +1 -0
- package/dist/core/canvas-interconnections.d.ts +24 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -0
- package/dist/core/canvas-interconnections.js +96 -0
- package/dist/core/canvas-interconnections.js.map +1 -0
- package/dist/core/canvas-manifest-check.d.ts +15 -0
- package/dist/core/canvas-manifest-check.d.ts.map +1 -0
- package/dist/core/canvas-manifest-check.js +90 -0
- package/dist/core/canvas-manifest-check.js.map +1 -0
- package/dist/core/canvas-render.d.ts +73 -0
- package/dist/core/canvas-render.d.ts.map +1 -0
- package/dist/core/canvas-render.js +145 -0
- package/dist/core/canvas-render.js.map +1 -0
- package/dist/core/canvas-svg.d.ts +50 -0
- package/dist/core/canvas-svg.d.ts.map +1 -0
- package/dist/core/canvas-svg.js +240 -0
- package/dist/core/canvas-svg.js.map +1 -0
- package/dist/core/canvas-template.d.ts +43 -0
- package/dist/core/canvas-template.d.ts.map +1 -0
- package/dist/core/canvas-template.js +365 -0
- package/dist/core/canvas-template.js.map +1 -0
- package/dist/core/canvas-watcher.d.ts +25 -0
- package/dist/core/canvas-watcher.d.ts.map +1 -0
- package/dist/core/canvas-watcher.js +144 -0
- package/dist/core/canvas-watcher.js.map +1 -0
- package/dist/core/collector/batcher.d.ts +57 -0
- package/dist/core/collector/batcher.d.ts.map +1 -0
- package/dist/core/collector/batcher.js +143 -0
- package/dist/core/collector/batcher.js.map +1 -0
- package/dist/core/collector/codex-tailer.d.ts +85 -0
- package/dist/core/collector/codex-tailer.d.ts.map +1 -0
- package/dist/core/collector/codex-tailer.js +270 -0
- package/dist/core/collector/codex-tailer.js.map +1 -0
- package/dist/core/collector/normalizer.d.ts +33 -0
- package/dist/core/collector/normalizer.d.ts.map +1 -0
- package/dist/core/collector/normalizer.js +94 -0
- package/dist/core/collector/normalizer.js.map +1 -0
- package/dist/core/collector/seq.d.ts +14 -0
- package/dist/core/collector/seq.d.ts.map +1 -0
- package/dist/core/collector/seq.js +20 -0
- package/dist/core/collector/seq.js.map +1 -0
- package/dist/core/collector/store.d.ts +16 -0
- package/dist/core/collector/store.d.ts.map +1 -0
- package/dist/core/collector/store.js +30 -0
- package/dist/core/collector/store.js.map +1 -0
- package/dist/core/collector/transcript.d.ts +38 -0
- package/dist/core/collector/transcript.d.ts.map +1 -0
- package/dist/core/collector/transcript.js +110 -0
- package/dist/core/collector/transcript.js.map +1 -0
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/example-seed.d.ts +34 -0
- package/dist/core/example-seed.d.ts.map +1 -0
- package/dist/core/example-seed.js +277 -0
- package/dist/core/example-seed.js.map +1 -0
- package/dist/core/inject/claude-settings.d.ts +34 -0
- package/dist/core/inject/claude-settings.d.ts.map +1 -0
- package/dist/core/inject/claude-settings.js +152 -0
- package/dist/core/inject/claude-settings.js.map +1 -0
- package/dist/core/inject/mcp-config.d.ts +26 -0
- package/dist/core/inject/mcp-config.d.ts.map +1 -0
- package/dist/core/inject/mcp-config.js +39 -0
- package/dist/core/inject/mcp-config.js.map +1 -0
- package/dist/core/inject/retention.d.ts +66 -0
- package/dist/core/inject/retention.d.ts.map +1 -0
- package/dist/core/inject/retention.js +119 -0
- package/dist/core/inject/retention.js.map +1 -0
- package/dist/core/inject/system-prompt.d.ts +11 -0
- package/dist/core/inject/system-prompt.d.ts.map +1 -0
- package/dist/core/inject/system-prompt.js +22 -0
- package/dist/core/inject/system-prompt.js.map +1 -0
- package/dist/core/macro-runner.d.ts +36 -0
- package/dist/core/macro-runner.d.ts.map +1 -0
- package/dist/core/macro-runner.js +66 -0
- package/dist/core/macro-runner.js.map +1 -0
- package/dist/core/macros.d.ts +10 -0
- package/dist/core/macros.d.ts.map +1 -0
- package/dist/core/macros.js +59 -0
- package/dist/core/macros.js.map +1 -0
- package/dist/core/path-safety.d.ts +33 -0
- package/dist/core/path-safety.d.ts.map +1 -0
- package/dist/core/path-safety.js +63 -0
- package/dist/core/path-safety.js.map +1 -0
- package/dist/core/paths.d.ts +27 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/port-detector.d.ts +69 -0
- package/dist/core/port-detector.d.ts.map +1 -0
- package/dist/core/port-detector.js +121 -0
- package/dist/core/port-detector.js.map +1 -0
- package/dist/core/session-manager.d.ts +275 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +729 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/task-manager.d.ts +138 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +286 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/task-stream.d.ts +32 -0
- package/dist/core/task-stream.d.ts.map +1 -0
- package/dist/core/task-stream.js +87 -0
- package/dist/core/task-stream.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +36 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +220 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workspace-context.d.ts +43 -0
- package/dist/core/workspace-context.d.ts.map +1 -0
- package/dist/core/workspace-context.js +108 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-watcher.d.ts +26 -0
- package/dist/core/workspace-watcher.d.ts.map +1 -0
- package/dist/core/workspace-watcher.js +174 -0
- package/dist/core/workspace-watcher.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/canvas-guidelines.d.ts +20 -0
- package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
- package/dist/profiles/canvas-guidelines.js +48 -0
- package/dist/profiles/canvas-guidelines.js.map +1 -0
- package/dist/profiles/default.d.ts +10 -0
- package/dist/profiles/default.d.ts.map +1 -0
- package/dist/profiles/default.js +70 -0
- package/dist/profiles/default.js.map +1 -0
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/canvas-render.d.ts +18 -0
- package/dist/server/canvas-render.d.ts.map +1 -0
- package/dist/server/canvas-render.js +24 -0
- package/dist/server/canvas-render.js.map +1 -0
- package/dist/server/canvas.d.ts +12 -0
- package/dist/server/canvas.d.ts.map +1 -0
- package/dist/server/canvas.js +174 -0
- package/dist/server/canvas.js.map +1 -0
- package/dist/server/events-ws.d.ts +11 -0
- package/dist/server/events-ws.d.ts.map +1 -0
- package/dist/server/events-ws.js +24 -0
- package/dist/server/events-ws.js.map +1 -0
- package/dist/server/fs.d.ts +4 -0
- package/dist/server/fs.d.ts.map +1 -0
- package/dist/server/fs.js +88 -0
- package/dist/server/fs.js.map +1 -0
- package/dist/server/index.d.ts +83 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/ingest.d.ts +74 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +74 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/macros.d.ts +32 -0
- package/dist/server/macros.d.ts.map +1 -0
- package/dist/server/macros.js +76 -0
- package/dist/server/macros.js.map +1 -0
- package/dist/server/rest.d.ts +75 -0
- package/dist/server/rest.d.ts.map +1 -0
- package/dist/server/rest.js +235 -0
- package/dist/server/rest.js.map +1 -0
- package/dist/server/static.d.ts +8 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +36 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/terminal-ws.d.ts +9 -0
- package/dist/server/terminal-ws.d.ts.map +1 -0
- package/dist/server/terminal-ws.js +59 -0
- package/dist/server/terminal-ws.js.map +1 -0
- package/dist/server/ws-router.d.ts +16 -0
- package/dist/server/ws-router.d.ts.map +1 -0
- package/dist/server/ws-router.js +26 -0
- package/dist/server/ws-router.js.map +1 -0
- package/dist/shared/types.d.ts +485 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +80 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/assets/index-B9R4TzH7.css +32 -0
- package/dist/web/assets/index-Dh97QAgq.js +192 -0
- package/dist/web/index.html +27 -0
- 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 @@
|
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAA6D,MAAM,SAAS,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAEnD,MAAM,UAAU,GAA2B;IACzC,OAAO,EAAE,0BAA0B;IACnC,MAAM,EAAE,yBAAyB;IACjC,KAAK,EAAE,gCAAgC;IACvC,MAAM,EAAE,gCAAgC;IACxC,OAAO,EAAE,iCAAiC;IAC1C,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;IACtB,MAAM,EAAE,2BAA2B;CACpC,CAAC;AAEF,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,0BAA0B,CAAC;AACxF,CAAC;AAED,kFAAkF;AAClF,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,6EAA6E;AAC7E,6EAA6E;AAC7E,uBAAuB;AACvB,MAAM,UAAU,GACd,qEAAqE;IACrE,0EAA0E;IAC1E,2CAA2C,CAAC;AAE9C,MAAM,gBAAgB,GAAG;;;;;;;;;;QAUjB,CAAC;AAET;;;eAGe;AACf,MAAM,mBAAmB,GAAG;;;;;;;;;QASpB,CAAC;AAaT;;mDAEmD;AACnD,SAAS,WAAW,CAAC,UAA+B,EAAE,GAAY,EAAE,GAAa,EAAE,QAAuB;IACxG,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE,UAAU,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM;QACrB,CAAC,CAAC,KAAK;YACL,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC;YAC5D,CAAC,CAAC,cAAc;QAClB,CAAC,CAAC,QAAQ,CAAC;IACb,qEAAqE;IACrE,4EAA4E;IAC5E,qEAAqE;IACrE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;IAErC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACzD,oEAAoE;IACpE,6EAA6E;IAC7E,0EAA0E;IAC1E,wEAAwE;IACxE,iEAAiE;IACjE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IAED,wEAAwE;IACxE,qEAAqE;IACrE,wEAAwE;IACxE,MAAM,cAAc,GAAG,GAAS,EAAE;QAChC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;QACtE,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,+DAA+D;YAC/D,kEAAkE;YAClE,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;gBACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;gBACtE,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;gBAC7B,OAAO;YACT,CAAC;YACD,kEAAkE;YAClE,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,QAAQ,KAAK,cAAc,EAAE,CAAC;gBAChC,cAAc,EAAE,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,yEAAyE;QACzE,+DAA+D;QAC/D,2EAA2E;QAC3E,wEAAwE;QACxE,oCAAoC;QACpC,IAAI,WAAW,EAAE,CAAC;YAChB,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;gBACjD,IAAI,OAAO,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;oBACjD,cAAc,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;gBAC1D,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;gBAC3C,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;QACxD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3C,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAA+B;IAChE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,MAAM,CAAC,GAAG,CAAC,6BAA6B,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACrD,wEAAwE;QACxE,qEAAqE;QACrE,MAAM,QAAQ,GAAI,GAAG,CAAC,MAA4C,CAAC,CAAC,CAAC,CAAC;QACtE,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,CAAC,2BAA2B,EAAE,4BAA4B,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnF,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -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 @@
|
|
|
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
|