@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,36 @@
1
+ /**
2
+ * Workflow registry (workstream W2's backend slice).
3
+ *
4
+ * Discovers orchestration projects by scanning a directory tree (bounded
5
+ * depth) for `sapiom.json` marker files, tracks manually-connected paths,
6
+ * and persists the combined list to HARNESS_PATHS.workflows. Exposes an
7
+ * Express router implementing the /api/workflows surface from
8
+ * src/shared/types.ts; the integrator mounts it (and express.json()) on the
9
+ * shared app.
10
+ */
11
+ import { type Router as ExpressRouter } from "express";
12
+ import { type WorkflowInfo } from "../shared/types.js";
13
+ export declare class WorkflowRegistry {
14
+ private readonly registryPath;
15
+ private workflows;
16
+ private loaded;
17
+ constructor(registryPath?: string);
18
+ private ensureLoaded;
19
+ private persist;
20
+ list(): Promise<WorkflowInfo[]>;
21
+ /**
22
+ * Drops entries whose `path` no longer exists on disk and persists the
23
+ * result — users delete projects, and a crashed run can leave a temp
24
+ * directory registered. Deliberately narrow: only a confirmed-missing
25
+ * path (ENOENT/ENOTDIR) is pruned. A directory that exists but is
26
+ * merely unbuilt, unreadable (permissions), or temporarily unstattable
27
+ * stays registered. Returns what was pruned so the caller can log it.
28
+ */
29
+ prune(): Promise<WorkflowInfo[]>;
30
+ /** Scans `root` and merges discovered projects into the persisted registry. */
31
+ scan(root: string): Promise<WorkflowInfo[]>;
32
+ /** Registers an arbitrary path (the "+ Connect" flow); marker is optional at connect time. */
33
+ connectPath(inputPath: string): Promise<WorkflowInfo>;
34
+ }
35
+ export declare function createWorkflowsRouter(registry: WorkflowRegistry): ExpressRouter;
36
+ //# sourceMappingURL=workflow-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-registry.d.ts","sourceRoot":"","sources":["../../src/core/workflow-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAU,KAAK,MAAM,IAAI,aAAa,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAmFtE,qBAAa,gBAAgB;IAIf,OAAO,CAAC,QAAQ,CAAC,YAAY;IAHzC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,MAAM,CAAS;gBAEM,YAAY,GAAE,MAA4C;YAEzE,YAAY;YAWZ,OAAO;IAKf,IAAI,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKrC;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAqBtC,+EAA+E;IACzE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAiBjD,8FAA8F;IACxF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAgB5D;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,aAAa,CAkC/E"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Workflow registry (workstream W2's backend slice).
3
+ *
4
+ * Discovers orchestration projects by scanning a directory tree (bounded
5
+ * depth) for `sapiom.json` marker files, tracks manually-connected paths,
6
+ * and persists the combined list to HARNESS_PATHS.workflows. Exposes an
7
+ * Express router implementing the /api/workflows surface from
8
+ * src/shared/types.ts; the integrator mounts it (and express.json()) on the
9
+ * shared app.
10
+ */
11
+ import * as fs from "node:fs/promises";
12
+ import * as os from "node:os";
13
+ import * as path from "node:path";
14
+ import { Router } from "express";
15
+ import { HARNESS_PATHS } from "../shared/types.js";
16
+ import { hasTraversalSegment, resolveWithinRoot } from "./path-safety.js";
17
+ const MAX_SCAN_DEPTH = 3;
18
+ const SKIP_DIR_NAMES = new Set(["node_modules", ".git"]);
19
+ function expandHome(inputPath) {
20
+ if (inputPath === "~")
21
+ return os.homedir();
22
+ if (inputPath.startsWith("~/"))
23
+ return path.join(os.homedir(), inputPath.slice(2));
24
+ return inputPath;
25
+ }
26
+ // `dir` reaching these sinks is always a resolved absolute path (from
27
+ // path.resolve in scan/connectPath, or a confined descent in scanDir), so a
28
+ // `..` segment can never survive. Asserting it anyway keeps the no-traversal
29
+ // guarantee explicit and local to each fs read, and covers the arbitrary
30
+ // path connectPath accepts (which has no scan root to confine it to).
31
+ async function readMarker(dir) {
32
+ if (hasTraversalSegment(dir))
33
+ return null;
34
+ try {
35
+ const raw = await fs.readFile(path.join(dir, "sapiom.json"), "utf8");
36
+ return JSON.parse(raw);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ async function nameFor(dir) {
43
+ if (hasTraversalSegment(dir))
44
+ return path.basename(dir);
45
+ try {
46
+ const raw = await fs.readFile(path.join(dir, "package.json"), "utf8");
47
+ const pkg = JSON.parse(raw);
48
+ if (pkg.name)
49
+ return pkg.name;
50
+ }
51
+ catch {
52
+ // No package.json (or it doesn't parse) — fall back to the directory name.
53
+ }
54
+ return path.basename(dir);
55
+ }
56
+ /**
57
+ * Depth-first scan; a directory carrying a marker is registered and not
58
+ * descended into. Every directory touched is confined to `root` (the tree the
59
+ * caller asked to scan): the initial call is `root` itself, and recursion only
60
+ * ever descends into a direct child, so no crafted entry name can walk the
61
+ * scan outside `root`. Symlinked entries report `isDirectory() === false`
62
+ * (withFileTypes uses raw dirent info, not a followed stat) and so are never
63
+ * descended into either.
64
+ */
65
+ async function scanDir(root, dir, depth, found) {
66
+ if (depth > MAX_SCAN_DEPTH)
67
+ return;
68
+ const safeDir = resolveWithinRoot(root, dir);
69
+ if (!safeDir)
70
+ return;
71
+ const marker = await readMarker(safeDir);
72
+ if (marker) {
73
+ found.push({
74
+ name: await nameFor(safeDir),
75
+ path: safeDir,
76
+ definitionId: marker.definitionId ?? null,
77
+ source: "scan",
78
+ });
79
+ return;
80
+ }
81
+ let entries;
82
+ try {
83
+ entries = await fs.readdir(safeDir, { withFileTypes: true });
84
+ }
85
+ catch {
86
+ return;
87
+ }
88
+ for (const entry of entries) {
89
+ if (!entry.isDirectory() || SKIP_DIR_NAMES.has(entry.name))
90
+ continue;
91
+ await scanDir(root, path.join(safeDir, entry.name), depth + 1, found);
92
+ }
93
+ }
94
+ export class WorkflowRegistry {
95
+ constructor(registryPath = expandHome(HARNESS_PATHS.workflows)) {
96
+ this.registryPath = registryPath;
97
+ this.workflows = [];
98
+ this.loaded = false;
99
+ }
100
+ async ensureLoaded() {
101
+ if (this.loaded)
102
+ return;
103
+ try {
104
+ const raw = await fs.readFile(this.registryPath, "utf8");
105
+ this.workflows = JSON.parse(raw);
106
+ }
107
+ catch {
108
+ this.workflows = [];
109
+ }
110
+ this.loaded = true;
111
+ }
112
+ async persist() {
113
+ await fs.mkdir(path.dirname(this.registryPath), { recursive: true });
114
+ await fs.writeFile(this.registryPath, JSON.stringify(this.workflows, null, 2));
115
+ }
116
+ async list() {
117
+ await this.ensureLoaded();
118
+ return this.workflows;
119
+ }
120
+ /**
121
+ * Drops entries whose `path` no longer exists on disk and persists the
122
+ * result — users delete projects, and a crashed run can leave a temp
123
+ * directory registered. Deliberately narrow: only a confirmed-missing
124
+ * path (ENOENT/ENOTDIR) is pruned. A directory that exists but is
125
+ * merely unbuilt, unreadable (permissions), or temporarily unstattable
126
+ * stays registered. Returns what was pruned so the caller can log it.
127
+ */
128
+ async prune() {
129
+ await this.ensureLoaded();
130
+ const kept = [];
131
+ const pruned = [];
132
+ for (const workflow of this.workflows) {
133
+ try {
134
+ await fs.stat(workflow.path);
135
+ kept.push(workflow);
136
+ }
137
+ catch (err) {
138
+ const code = err.code;
139
+ if (code === "ENOENT" || code === "ENOTDIR")
140
+ pruned.push(workflow);
141
+ else
142
+ kept.push(workflow);
143
+ }
144
+ }
145
+ if (pruned.length > 0) {
146
+ this.workflows = kept;
147
+ await this.persist();
148
+ }
149
+ return pruned;
150
+ }
151
+ /** Scans `root` and merges discovered projects into the persisted registry. */
152
+ async scan(root) {
153
+ await this.ensureLoaded();
154
+ const absoluteRoot = path.resolve(expandHome(root));
155
+ const found = [];
156
+ await scanDir(absoluteRoot, absoluteRoot, 0, found);
157
+ const byPath = new Map(this.workflows.map((workflow) => [workflow.path, workflow]));
158
+ for (const workflow of found) {
159
+ const existing = byPath.get(workflow.path);
160
+ // A manually-connected entry keeps its `source`; a scan only refreshes name/definitionId.
161
+ byPath.set(workflow.path, existing ? { ...existing, ...workflow, source: existing.source } : workflow);
162
+ }
163
+ this.workflows = Array.from(byPath.values());
164
+ await this.persist();
165
+ return found;
166
+ }
167
+ /** Registers an arbitrary path (the "+ Connect" flow); marker is optional at connect time. */
168
+ async connectPath(inputPath) {
169
+ await this.ensureLoaded();
170
+ const absolutePath = path.resolve(expandHome(inputPath));
171
+ const marker = await readMarker(absolutePath);
172
+ const info = {
173
+ name: await nameFor(absolutePath),
174
+ path: absolutePath,
175
+ definitionId: marker?.definitionId ?? null,
176
+ source: "connect",
177
+ };
178
+ const idx = this.workflows.findIndex((workflow) => workflow.path === absolutePath);
179
+ if (idx >= 0)
180
+ this.workflows[idx] = info;
181
+ else
182
+ this.workflows.push(info);
183
+ await this.persist();
184
+ return info;
185
+ }
186
+ }
187
+ export function createWorkflowsRouter(registry) {
188
+ const router = Router();
189
+ router.get("/api/workflows", async (_req, res) => {
190
+ res.json(await registry.list());
191
+ });
192
+ router.post("/api/workflows/connect", async (req, res) => {
193
+ const inputPath = req.body?.path;
194
+ if (typeof inputPath !== "string" || !inputPath.trim()) {
195
+ res.status(400).json({ error: "path is required" });
196
+ return;
197
+ }
198
+ try {
199
+ res.json(await registry.connectPath(inputPath));
200
+ }
201
+ catch (err) {
202
+ res.status(500).json({ error: err.message });
203
+ }
204
+ });
205
+ router.post("/api/workflows/scan", async (req, res) => {
206
+ const root = req.body?.root;
207
+ if (typeof root !== "string" || !root.trim()) {
208
+ res.status(400).json({ error: "root is required" });
209
+ return;
210
+ }
211
+ try {
212
+ res.json(await registry.scan(root));
213
+ }
214
+ catch (err) {
215
+ res.status(500).json({ error: err.message });
216
+ }
217
+ });
218
+ return router;
219
+ }
220
+ //# sourceMappingURL=workflow-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-registry.js","sourceRoot":"","sources":["../../src/core/workflow-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Writes HARNESS_CONTEXT_FILE (`.sapiom/harness-context.json`) in a
3
+ * session's cwd — the agent-legible mirror of this workspace's UI state:
4
+ * which workflow (if any) the session is bound to, every workflow the
5
+ * registry currently knows about, and the session's own identity. Called
6
+ * unconditionally from `SessionManager.create()` so the file exists for
7
+ * every session regardless of entry point (REST, `autoCreateSession`), as a
8
+ * backfill from `SessionManager.resume()` when it's missing entirely, on
9
+ * every `PATCH /api/sessions/:id/workflow`, and whenever the workflow
10
+ * registry changes (scan/connect) — see server/index.ts's
11
+ * `writeSessionContext`/`scanWorkflowsAndBroadcast` for how those call
12
+ * sites are wired, and both of which can legitimately fire concurrent
13
+ * writes to the *same* destination (a scan's rewrite-all-open-sessions step
14
+ * racing a user's live bind click, for instance) — see `withPerPathQueue`.
15
+ * Unbinding writes `boundWorkflow: null` rather than deleting the file, so a
16
+ * concurrent read from the agent never races a momentary ENOENT.
17
+ */
18
+ import { type HarnessKind, type WorkflowInfo } from "../shared/types.js";
19
+ export interface WorkspaceContextSession {
20
+ id: string;
21
+ cwd: string;
22
+ harness: HarnessKind;
23
+ }
24
+ /**
25
+ * Atomically writes `<session.cwd>/.sapiom/harness-context.json`.
26
+ * Best-effort: a session's cwd could in principle be unwritable
27
+ * (permissions, deleted out from under the session) — that must never fail
28
+ * the caller that triggered it, so errors are logged, not thrown.
29
+ *
30
+ * `workflows` is sorted by path before writing (deterministic, independent
31
+ * of registry scan/insertion order) so an agent re-reading the file across
32
+ * turns can diff it cheaply instead of re-parsing a reordered blob every
33
+ * time.
34
+ */
35
+ export declare function writeHarnessContext(session: WorkspaceContextSession, boundWorkflow: WorkflowInfo | null, workflows: WorkflowInfo[]): Promise<void>;
36
+ /**
37
+ * True if `<cwd>/.sapiom/harness-context.json` already exists. Used by
38
+ * `SessionManager.resume()` to decide whether a backfill write is needed —
39
+ * resume must never clobber a file that could already reflect a real
40
+ * binding.
41
+ */
42
+ export declare function harnessContextFileExists(cwd: string): Promise<boolean>;
43
+ //# sourceMappingURL=workspace-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-context.d.ts","sourceRoot":"","sources":["../../src/core/workspace-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,EAEL,KAAK,WAAW,EAGhB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAM5B,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;CACtB;AAiCD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,uBAAuB,EAChC,aAAa,EAAE,YAAY,GAAG,IAAI,EAClC,SAAS,EAAE,YAAY,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO5E"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Writes HARNESS_CONTEXT_FILE (`.sapiom/harness-context.json`) in a
3
+ * session's cwd — the agent-legible mirror of this workspace's UI state:
4
+ * which workflow (if any) the session is bound to, every workflow the
5
+ * registry currently knows about, and the session's own identity. Called
6
+ * unconditionally from `SessionManager.create()` so the file exists for
7
+ * every session regardless of entry point (REST, `autoCreateSession`), as a
8
+ * backfill from `SessionManager.resume()` when it's missing entirely, on
9
+ * every `PATCH /api/sessions/:id/workflow`, and whenever the workflow
10
+ * registry changes (scan/connect) — see server/index.ts's
11
+ * `writeSessionContext`/`scanWorkflowsAndBroadcast` for how those call
12
+ * sites are wired, and both of which can legitimately fire concurrent
13
+ * writes to the *same* destination (a scan's rewrite-all-open-sessions step
14
+ * racing a user's live bind click, for instance) — see `withPerPathQueue`.
15
+ * Unbinding writes `boundWorkflow: null` rather than deleting the file, so a
16
+ * concurrent read from the agent never races a momentary ENOENT.
17
+ */
18
+ import * as crypto from "node:crypto";
19
+ import * as fs from "node:fs/promises";
20
+ import * as path from "node:path";
21
+ import { HARNESS_CONTEXT_FILE, } from "../shared/types.js";
22
+ function toContextWorkflowEntry(workflow) {
23
+ return { name: workflow.name, path: workflow.path, definitionId: workflow.definitionId };
24
+ }
25
+ /**
26
+ * Serializes writes per destination path. Two independent triggers can
27
+ * legitimately race on the same `harness-context.json` (a workflow scan's
28
+ * rewrite-all-open-sessions step, a bind/unbind, a fresh session's initial
29
+ * write) — without this, concurrent writers could both compute the same
30
+ * `Date.now()`-based tmp filename (confirmed via repro: a tight burst of
31
+ * concurrent writes to one destination reliably collides within the same
32
+ * millisecond) and steal each other's tmp file out from under a pending
33
+ * `rename`, which fails with ENOENT. Serializing per path also fixes a
34
+ * subtler issue beyond the crash: without it, whichever concurrent write's
35
+ * disk I/O happens to finish first wins, which can silently apply writes
36
+ * out of the order they were actually triggered in. Keyed by absolute
37
+ * path; an entry is removed once nothing else has chained onto it, so this
38
+ * never grows unbounded over a long-running server's lifetime.
39
+ */
40
+ const writeQueues = new Map();
41
+ async function withPerPathQueue(filePath, task) {
42
+ const previous = writeQueues.get(filePath) ?? Promise.resolve();
43
+ // `task` never throws (its own try/catch logs and swallows) — chaining
44
+ // onto both branches of `previous` is defense in depth, so one write's
45
+ // failure can never wedge the queue for the next one to this same path.
46
+ const current = previous.then(task, task);
47
+ writeQueues.set(filePath, current);
48
+ try {
49
+ await current;
50
+ }
51
+ finally {
52
+ if (writeQueues.get(filePath) === current)
53
+ writeQueues.delete(filePath);
54
+ }
55
+ }
56
+ /**
57
+ * Atomically writes `<session.cwd>/.sapiom/harness-context.json`.
58
+ * Best-effort: a session's cwd could in principle be unwritable
59
+ * (permissions, deleted out from under the session) — that must never fail
60
+ * the caller that triggered it, so errors are logged, not thrown.
61
+ *
62
+ * `workflows` is sorted by path before writing (deterministic, independent
63
+ * of registry scan/insertion order) so an agent re-reading the file across
64
+ * turns can diff it cheaply instead of re-parsing a reordered blob every
65
+ * time.
66
+ */
67
+ export async function writeHarnessContext(session, boundWorkflow, workflows) {
68
+ const filePath = path.join(session.cwd, HARNESS_CONTEXT_FILE);
69
+ const sortedWorkflows = [...workflows].sort((a, b) => a.path.localeCompare(b.path)).map(toContextWorkflowEntry);
70
+ const context = {
71
+ boundWorkflow: boundWorkflow ? toContextWorkflowEntry(boundWorkflow) : null,
72
+ workflows: sortedWorkflows,
73
+ session: { id: session.id, cwd: session.cwd, harness: session.harness },
74
+ updatedAt: new Date().toISOString(),
75
+ };
76
+ await withPerPathQueue(filePath, async () => {
77
+ try {
78
+ const dir = path.dirname(filePath);
79
+ await fs.mkdir(dir, { recursive: true });
80
+ // A random suffix, not just pid+Date.now(): millisecond resolution is
81
+ // not fine enough to stay unique across a burst of concurrent writes
82
+ // to the same destination (confirmed via repro). Collision-proof
83
+ // regardless of timing, independent of the queue above.
84
+ const tmpPath = path.join(dir, `.harness-context.json.tmp-${process.pid}-${crypto.randomUUID()}`);
85
+ await fs.writeFile(tmpPath, JSON.stringify(context, null, 2) + "\n", "utf8");
86
+ await fs.rename(tmpPath, filePath);
87
+ }
88
+ catch (err) {
89
+ console.error(`[harness] failed to write ${filePath}:`, err);
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * True if `<cwd>/.sapiom/harness-context.json` already exists. Used by
95
+ * `SessionManager.resume()` to decide whether a backfill write is needed —
96
+ * resume must never clobber a file that could already reflect a real
97
+ * binding.
98
+ */
99
+ export async function harnessContextFileExists(cwd) {
100
+ try {
101
+ await fs.access(path.join(cwd, HARNESS_CONTEXT_FILE));
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ //# sourceMappingURL=workspace-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-context.js","sourceRoot":"","sources":["../../src/core/workspace-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Fingerprint of the set of workflow-marker directories under `root` (sorted,
3
+ * bounded depth, ignored subtrees skipped). Changes exactly when a workflow is
4
+ * added, removed, or renamed — not when unrelated files are edited. Exported
5
+ * for the polling fallback and for direct testing.
6
+ */
7
+ export declare function snapshotWorkspaceWorkflows(root: string): string;
8
+ export interface WorkspaceWatcherManagerDeps {
9
+ /** Debounced per-session notification that the workspace's workflow set may
10
+ * have changed — the integrator re-scans that session's cwd and broadcasts
11
+ * `workflows.changed` if the list actually changed. */
12
+ onChange(harnessSessionId: string): void;
13
+ }
14
+ /** Registry of one SessionWorkspaceWatcher per active harness session. */
15
+ export declare class WorkspaceWatcherManager {
16
+ private readonly deps;
17
+ private readonly watchers;
18
+ constructor(deps: WorkspaceWatcherManagerDeps);
19
+ /** Idempotent: replaces any existing watcher for this session. */
20
+ start(harnessSessionId: string, cwd: string): void;
21
+ stop(harnessSessionId: string): void;
22
+ stopAll(): void;
23
+ /** Test/debug helper — how many sessions currently have an active watcher. */
24
+ get size(): number;
25
+ }
26
+ //# sourceMappingURL=workspace-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-watcher.d.ts","sourceRoot":"","sources":["../../src/core/workspace-watcher.ts"],"names":[],"mappings":"AAkDA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyB/D;AAsED,MAAM,WAAW,2BAA2B;IAC1C;;4DAEwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,0EAA0E;AAC1E,qBAAa,uBAAuB;IAGtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8C;gBAE1C,IAAI,EAAE,2BAA2B;IAE9D,kEAAkE;IAClE,KAAK,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAQlD,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAKpC,OAAO,IAAI,IAAI;IAIf,8EAA8E;IAC9E,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Workspace watcher: notices when workflows are added to / removed from a
3
+ * session's workspace directory DURING a session, so the rail can be re-scanned
4
+ * and re-broadcast instead of staying frozen at whatever the boot/session-create
5
+ * scan found. Users scaffold new workflows (and delete old ones) mid-session and
6
+ * expect the rail to keep up.
7
+ *
8
+ * Modeled on core/canvas-watcher.ts (same recursive-fs.watch-with-polling-
9
+ * fallback shape, same per-session lifecycle), but tuned to STRUCTURAL change
10
+ * rather than content change. It fires only when the SET of workflow-marker
11
+ * directories under the workspace actually changes (a workflow appearing,
12
+ * disappearing, or being renamed) — never for ordinary file edits:
13
+ *
14
+ * - A raw watch event only *arms* a check; the debounced check recomputes
15
+ * the workflow-marker fingerprint and fires `onChange` iff it differs from
16
+ * the last one. This is deliberately NOT keyed off the `fs.watch` event
17
+ * type: recursive `fs.watch` on macOS reports `rename` for plain content
18
+ * writes too, so an event-type filter would spam rescans on every save.
19
+ * The fingerprint diff is the reliable, cross-platform signal.
20
+ * - High-churn / irrelevant subtrees (`node_modules`, `.git`, `.sapiom`,
21
+ * build output) are skipped when arming AND when fingerprinting: a
22
+ * workflow marker never lives there, and `.sapiom/canvas/renders` in
23
+ * particular is rewritten on every render.
24
+ *
25
+ * The polling fallback (Linux, or a watcher runtime error) diffs the same
26
+ * fingerprint on an interval, so both paths share one notion of "changed".
27
+ */
28
+ import * as fs from "node:fs";
29
+ import * as path from "node:path";
30
+ const DEBOUNCE_MS = 250;
31
+ const POLL_INTERVAL_MS = 1000;
32
+ /** Kept in sync with core/workflow-registry.ts's scan: the marker file that
33
+ * makes a directory a workflow, and how deep the scan looks for it. */
34
+ const WORKFLOW_MARKER = "sapiom.json";
35
+ const MAX_SCAN_DEPTH = 3;
36
+ /** Directories a workflow marker never lives in — skipped both when
37
+ * fingerprinting and when deciding whether a watch event is relevant.
38
+ * `.sapiom` covers the canvas renders that are rewritten on every render. */
39
+ const IGNORED_DIR_NAMES = new Set(["node_modules", ".git", ".sapiom", "dist", "build", ".next"]);
40
+ function firstSegmentIgnored(relPath) {
41
+ for (const segment of relPath.split(path.sep)) {
42
+ if (segment && IGNORED_DIR_NAMES.has(segment))
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Fingerprint of the set of workflow-marker directories under `root` (sorted,
49
+ * bounded depth, ignored subtrees skipped). Changes exactly when a workflow is
50
+ * added, removed, or renamed — not when unrelated files are edited. Exported
51
+ * for the polling fallback and for direct testing.
52
+ */
53
+ export function snapshotWorkspaceWorkflows(root) {
54
+ const markerDirs = [];
55
+ const walk = (dir, depth) => {
56
+ if (depth > MAX_SCAN_DEPTH)
57
+ return;
58
+ let entries;
59
+ try {
60
+ entries = fs.readdirSync(dir, { withFileTypes: true });
61
+ }
62
+ catch {
63
+ return;
64
+ }
65
+ // A directory carrying the marker is itself a workflow — record it and
66
+ // don't descend (matches the registry's scan semantics).
67
+ if (entries.some((entry) => entry.isFile() && entry.name === WORKFLOW_MARKER)) {
68
+ markerDirs.push(dir);
69
+ return;
70
+ }
71
+ for (const entry of entries) {
72
+ if (!entry.isDirectory() || IGNORED_DIR_NAMES.has(entry.name))
73
+ continue;
74
+ walk(path.join(dir, entry.name), depth + 1);
75
+ }
76
+ };
77
+ walk(root, 0);
78
+ return markerDirs.sort().join("|");
79
+ }
80
+ /** One session's workspace watcher. */
81
+ class SessionWorkspaceWatcher {
82
+ constructor(cwd, harnessSessionId, onChange) {
83
+ this.cwd = cwd;
84
+ this.harnessSessionId = harnessSessionId;
85
+ this.onChange = onChange;
86
+ this.watcher = null;
87
+ this.pollTimer = null;
88
+ this.debounceTimer = null;
89
+ this.closed = false;
90
+ this.lastSnapshot = "";
91
+ this.lastSnapshot = snapshotWorkspaceWorkflows(this.cwd);
92
+ this.arm();
93
+ }
94
+ /** Debounced check: recompute the fingerprint and fire only on a real change. */
95
+ scheduleCheck() {
96
+ if (this.closed)
97
+ return;
98
+ if (this.debounceTimer)
99
+ clearTimeout(this.debounceTimer);
100
+ this.debounceTimer = setTimeout(() => this.checkNow(), DEBOUNCE_MS);
101
+ }
102
+ checkNow() {
103
+ if (this.closed)
104
+ return;
105
+ const snapshot = snapshotWorkspaceWorkflows(this.cwd);
106
+ if (snapshot === this.lastSnapshot)
107
+ return;
108
+ this.lastSnapshot = snapshot;
109
+ this.onChange(this.harnessSessionId);
110
+ }
111
+ isRelevantPath(filename) {
112
+ // A `null` filename means the platform couldn't say what changed — check
113
+ // to be safe rather than miss a new/removed workflow.
114
+ if (!filename)
115
+ return true;
116
+ return !firstSegmentIgnored(filename);
117
+ }
118
+ arm() {
119
+ if (this.closed)
120
+ return;
121
+ try {
122
+ this.watcher = fs.watch(this.cwd, { recursive: true }, (_event, filename) => {
123
+ if (this.isRelevantPath(filename))
124
+ this.scheduleCheck();
125
+ });
126
+ this.watcher.on("error", () => this.fallBackToPolling());
127
+ }
128
+ catch {
129
+ // `recursive` isn't supported on this platform (notably Linux).
130
+ this.fallBackToPolling();
131
+ }
132
+ }
133
+ fallBackToPolling() {
134
+ if (this.closed || this.pollTimer)
135
+ return;
136
+ this.watcher?.close();
137
+ this.watcher = null;
138
+ this.pollTimer = setInterval(() => this.checkNow(), POLL_INTERVAL_MS);
139
+ }
140
+ close() {
141
+ this.closed = true;
142
+ if (this.debounceTimer)
143
+ clearTimeout(this.debounceTimer);
144
+ if (this.pollTimer)
145
+ clearInterval(this.pollTimer);
146
+ this.watcher?.close();
147
+ this.watcher = null;
148
+ }
149
+ }
150
+ /** Registry of one SessionWorkspaceWatcher per active harness session. */
151
+ export class WorkspaceWatcherManager {
152
+ constructor(deps) {
153
+ this.deps = deps;
154
+ this.watchers = new Map();
155
+ }
156
+ /** Idempotent: replaces any existing watcher for this session. */
157
+ start(harnessSessionId, cwd) {
158
+ this.stop(harnessSessionId);
159
+ this.watchers.set(harnessSessionId, new SessionWorkspaceWatcher(cwd, harnessSessionId, (id) => this.deps.onChange(id)));
160
+ }
161
+ stop(harnessSessionId) {
162
+ this.watchers.get(harnessSessionId)?.close();
163
+ this.watchers.delete(harnessSessionId);
164
+ }
165
+ stopAll() {
166
+ for (const harnessSessionId of [...this.watchers.keys()])
167
+ this.stop(harnessSessionId);
168
+ }
169
+ /** Test/debug helper — how many sessions currently have an active watcher. */
170
+ get size() {
171
+ return this.watchers.size;
172
+ }
173
+ }
174
+ //# sourceMappingURL=workspace-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-watcher.js","sourceRoot":"","sources":["../../src/core/workspace-watcher.ts"],"names":[],"mappings":"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"}
@@ -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.d.ts.map