@sapiom/harness 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/dist/cli/args.d.ts +16 -0
  3. package/dist/cli/args.d.ts.map +1 -0
  4. package/dist/cli/args.js +80 -0
  5. package/dist/cli/args.js.map +1 -0
  6. package/dist/cli/bin.js +30 -66
  7. package/dist/cli/bin.js.map +1 -1
  8. package/dist/cli/consent.js +1 -1
  9. package/dist/cli/settings.d.ts.map +1 -1
  10. package/dist/cli/settings.js +33 -1
  11. package/dist/cli/settings.js.map +1 -1
  12. package/dist/core/adapters/adapter.d.ts +2 -2
  13. package/dist/core/adapters/claude-code.d.ts +60 -2
  14. package/dist/core/adapters/claude-code.d.ts.map +1 -1
  15. package/dist/core/adapters/claude-code.js +119 -32
  16. package/dist/core/adapters/claude-code.js.map +1 -1
  17. package/dist/core/adapters/codex.d.ts +21 -2
  18. package/dist/core/adapters/codex.d.ts.map +1 -1
  19. package/dist/core/adapters/codex.js +53 -2
  20. package/dist/core/adapters/codex.js.map +1 -1
  21. package/dist/core/adapters/detect.d.ts +9 -0
  22. package/dist/core/adapters/detect.d.ts.map +1 -1
  23. package/dist/core/adapters/detect.js +9 -0
  24. package/dist/core/adapters/detect.js.map +1 -1
  25. package/dist/core/api-key-provider.d.ts +79 -0
  26. package/dist/core/api-key-provider.d.ts.map +1 -0
  27. package/dist/core/api-key-provider.js +91 -0
  28. package/dist/core/api-key-provider.js.map +1 -0
  29. package/dist/core/canvas-body.d.ts +28 -28
  30. package/dist/core/canvas-body.d.ts.map +1 -1
  31. package/dist/core/canvas-body.js +117 -55
  32. package/dist/core/canvas-body.js.map +1 -1
  33. package/dist/core/canvas-derive.d.ts +43 -0
  34. package/dist/core/canvas-derive.d.ts.map +1 -0
  35. package/dist/core/canvas-derive.js +95 -0
  36. package/dist/core/canvas-derive.js.map +1 -0
  37. package/dist/core/canvas-enrichment.d.ts +52 -127
  38. package/dist/core/canvas-enrichment.d.ts.map +1 -1
  39. package/dist/core/canvas-enrichment.js +17 -178
  40. package/dist/core/canvas-enrichment.js.map +1 -1
  41. package/dist/core/canvas-graph.d.ts +40 -1
  42. package/dist/core/canvas-graph.d.ts.map +1 -1
  43. package/dist/core/canvas-graph.js +71 -9
  44. package/dist/core/canvas-graph.js.map +1 -1
  45. package/dist/core/canvas-interconnections.d.ts +26 -8
  46. package/dist/core/canvas-interconnections.d.ts.map +1 -1
  47. package/dist/core/canvas-interconnections.js +132 -24
  48. package/dist/core/canvas-interconnections.js.map +1 -1
  49. package/dist/core/canvas-manifest-check.d.ts.map +1 -1
  50. package/dist/core/canvas-manifest-check.js +15 -2
  51. package/dist/core/canvas-manifest-check.js.map +1 -1
  52. package/dist/core/canvas-render.d.ts +8 -15
  53. package/dist/core/canvas-render.d.ts.map +1 -1
  54. package/dist/core/canvas-render.js +29 -41
  55. package/dist/core/canvas-render.js.map +1 -1
  56. package/dist/core/canvas-run-state.d.ts +50 -5
  57. package/dist/core/canvas-run-state.d.ts.map +1 -1
  58. package/dist/core/canvas-run-state.js +176 -11
  59. package/dist/core/canvas-run-state.js.map +1 -1
  60. package/dist/core/canvas-svg.d.ts +9 -4
  61. package/dist/core/canvas-svg.d.ts.map +1 -1
  62. package/dist/core/canvas-svg.js +84 -13
  63. package/dist/core/canvas-svg.js.map +1 -1
  64. package/dist/core/canvas-template.d.ts +9 -10
  65. package/dist/core/canvas-template.d.ts.map +1 -1
  66. package/dist/core/canvas-template.js +57 -20
  67. package/dist/core/canvas-template.js.map +1 -1
  68. package/dist/core/canvas-watcher.d.ts +22 -1
  69. package/dist/core/canvas-watcher.d.ts.map +1 -1
  70. package/dist/core/canvas-watcher.js +111 -30
  71. package/dist/core/canvas-watcher.js.map +1 -1
  72. package/dist/core/collector/normalizer.d.ts +8 -0
  73. package/dist/core/collector/normalizer.d.ts.map +1 -1
  74. package/dist/core/collector/normalizer.js +8 -0
  75. package/dist/core/collector/normalizer.js.map +1 -1
  76. package/dist/core/collector/store.d.ts +75 -1
  77. package/dist/core/collector/store.d.ts.map +1 -1
  78. package/dist/core/collector/store.js +334 -2
  79. package/dist/core/collector/store.js.map +1 -1
  80. package/dist/core/definition-slug-resolver.d.ts +13 -0
  81. package/dist/core/definition-slug-resolver.d.ts.map +1 -1
  82. package/dist/core/definition-slug-resolver.js +29 -0
  83. package/dist/core/definition-slug-resolver.js.map +1 -1
  84. package/dist/core/errors.d.ts +10 -4
  85. package/dist/core/errors.d.ts.map +1 -1
  86. package/dist/core/errors.js +11 -5
  87. package/dist/core/errors.js.map +1 -1
  88. package/dist/core/example-seed.d.ts +8 -0
  89. package/dist/core/example-seed.d.ts.map +1 -1
  90. package/dist/core/example-seed.js +47 -10
  91. package/dist/core/example-seed.js.map +1 -1
  92. package/dist/core/inject/system-prompt.d.ts +8 -0
  93. package/dist/core/inject/system-prompt.d.ts.map +1 -1
  94. package/dist/core/inject/system-prompt.js +3 -1
  95. package/dist/core/inject/system-prompt.js.map +1 -1
  96. package/dist/core/macros.d.ts.map +1 -1
  97. package/dist/core/macros.js +5 -6
  98. package/dist/core/macros.js.map +1 -1
  99. package/dist/core/paths.d.ts +1 -0
  100. package/dist/core/paths.d.ts.map +1 -1
  101. package/dist/core/paths.js +1 -0
  102. package/dist/core/paths.js.map +1 -1
  103. package/dist/core/record-archive.d.ts +187 -0
  104. package/dist/core/record-archive.d.ts.map +1 -0
  105. package/dist/core/record-archive.js +465 -0
  106. package/dist/core/record-archive.js.map +1 -0
  107. package/dist/core/rehydration.d.ts +72 -0
  108. package/dist/core/rehydration.d.ts.map +1 -0
  109. package/dist/core/rehydration.js +72 -0
  110. package/dist/core/rehydration.js.map +1 -0
  111. package/dist/core/render-local-run.d.ts +82 -0
  112. package/dist/core/render-local-run.d.ts.map +1 -0
  113. package/dist/core/render-local-run.js +105 -0
  114. package/dist/core/render-local-run.js.map +1 -0
  115. package/dist/core/render-log-slice.d.ts +30 -0
  116. package/dist/core/render-log-slice.d.ts.map +1 -0
  117. package/dist/core/render-log-slice.js +49 -0
  118. package/dist/core/render-log-slice.js.map +1 -0
  119. package/dist/core/render-run-state.d.ts.map +1 -1
  120. package/dist/core/render-run-state.js +30 -49
  121. package/dist/core/render-run-state.js.map +1 -1
  122. package/dist/core/resume-brief.d.ts +130 -0
  123. package/dist/core/resume-brief.d.ts.map +1 -0
  124. package/dist/core/resume-brief.js +404 -0
  125. package/dist/core/resume-brief.js.map +1 -0
  126. package/dist/core/rolling-summary.d.ts +130 -0
  127. package/dist/core/rolling-summary.d.ts.map +1 -0
  128. package/dist/core/rolling-summary.js +227 -0
  129. package/dist/core/rolling-summary.js.map +1 -0
  130. package/dist/core/run-local-bootstrap.d.ts +93 -0
  131. package/dist/core/run-local-bootstrap.d.ts.map +1 -0
  132. package/dist/core/run-local-bootstrap.js +155 -0
  133. package/dist/core/run-local-bootstrap.js.map +1 -0
  134. package/dist/core/run-state.d.ts +16 -1
  135. package/dist/core/run-state.d.ts.map +1 -1
  136. package/dist/core/run-state.js +75 -25
  137. package/dist/core/run-state.js.map +1 -1
  138. package/dist/core/session-manager.d.ts +11 -3
  139. package/dist/core/session-manager.d.ts.map +1 -1
  140. package/dist/core/session-manager.js +56 -7
  141. package/dist/core/session-manager.js.map +1 -1
  142. package/dist/core/session-record.d.ts +165 -0
  143. package/dist/core/session-record.d.ts.map +1 -0
  144. package/dist/core/session-record.js +470 -0
  145. package/dist/core/session-record.js.map +1 -0
  146. package/dist/core/spawn-target.d.ts +12 -0
  147. package/dist/core/spawn-target.d.ts.map +1 -0
  148. package/dist/core/spawn-target.js +187 -0
  149. package/dist/core/spawn-target.js.map +1 -0
  150. package/dist/core/stub-feedback.d.ts +52 -0
  151. package/dist/core/stub-feedback.d.ts.map +1 -0
  152. package/dist/core/stub-feedback.js +33 -0
  153. package/dist/core/stub-feedback.js.map +1 -0
  154. package/dist/core/task-manager.d.ts.map +1 -1
  155. package/dist/core/task-manager.js +9 -1
  156. package/dist/core/task-manager.js.map +1 -1
  157. package/dist/core/template-catalog.d.ts +48 -0
  158. package/dist/core/template-catalog.d.ts.map +1 -0
  159. package/dist/core/template-catalog.js +338 -0
  160. package/dist/core/template-catalog.js.map +1 -0
  161. package/dist/core/workflow-registry.d.ts.map +1 -1
  162. package/dist/core/workflow-registry.js +2 -2
  163. package/dist/core/workflow-registry.js.map +1 -1
  164. package/dist/core/workspace-watcher.d.ts.map +1 -1
  165. package/dist/core/workspace-watcher.js +4 -3
  166. package/dist/core/workspace-watcher.js.map +1 -1
  167. package/dist/index.d.ts +12 -0
  168. package/dist/index.d.ts.map +1 -1
  169. package/dist/index.js +21 -0
  170. package/dist/index.js.map +1 -1
  171. package/dist/profiles/default.d.ts.map +1 -1
  172. package/dist/profiles/default.js +9 -14
  173. package/dist/profiles/default.js.map +1 -1
  174. package/dist/server/actions.d.ts +139 -0
  175. package/dist/server/actions.d.ts.map +1 -0
  176. package/dist/server/actions.js +430 -0
  177. package/dist/server/actions.js.map +1 -0
  178. package/dist/server/auth-routes.d.ts +92 -0
  179. package/dist/server/auth-routes.d.ts.map +1 -0
  180. package/dist/server/auth-routes.js +151 -0
  181. package/dist/server/auth-routes.js.map +1 -0
  182. package/dist/server/canvas.d.ts.map +1 -1
  183. package/dist/server/canvas.js +7 -7
  184. package/dist/server/canvas.js.map +1 -1
  185. package/dist/server/fs.d.ts.map +1 -1
  186. package/dist/server/fs.js +17 -1
  187. package/dist/server/fs.js.map +1 -1
  188. package/dist/server/index.d.ts +12 -3
  189. package/dist/server/index.d.ts.map +1 -1
  190. package/dist/server/index.js +0 -0
  191. package/dist/server/index.js.map +1 -1
  192. package/dist/server/ingest.d.ts +11 -0
  193. package/dist/server/ingest.d.ts.map +1 -1
  194. package/dist/server/ingest.js +1 -0
  195. package/dist/server/ingest.js.map +1 -1
  196. package/dist/server/macros.d.ts +4 -6
  197. package/dist/server/macros.d.ts.map +1 -1
  198. package/dist/server/macros.js.map +1 -1
  199. package/dist/server/rest.d.ts +18 -6
  200. package/dist/server/rest.d.ts.map +1 -1
  201. package/dist/server/rest.js +185 -50
  202. package/dist/server/rest.js.map +1 -1
  203. package/dist/server/runs.d.ts +16 -15
  204. package/dist/server/runs.d.ts.map +1 -1
  205. package/dist/server/runs.js +15 -80
  206. package/dist/server/runs.js.map +1 -1
  207. package/dist/server/static.d.ts +15 -1
  208. package/dist/server/static.d.ts.map +1 -1
  209. package/dist/server/static.js +54 -4
  210. package/dist/server/static.js.map +1 -1
  211. package/dist/server/templates.d.ts +24 -0
  212. package/dist/server/templates.d.ts.map +1 -0
  213. package/dist/server/templates.js +54 -0
  214. package/dist/server/templates.js.map +1 -0
  215. package/dist/shared/types.d.ts +558 -66
  216. package/dist/shared/types.d.ts.map +1 -1
  217. package/dist/shared/types.js +19 -11
  218. package/dist/shared/types.js.map +1 -1
  219. package/dist/web/assets/index-DC65cskq.js +446 -0
  220. package/dist/web/assets/index-xtD-3FFa.css +32 -0
  221. package/dist/web/canvas/sess-boot/index.html +305 -0
  222. package/dist/web/index.html +4 -9
  223. package/package.json +8 -4
  224. package/dist/core/canvas-enrich.d.ts +0 -115
  225. package/dist/core/canvas-enrich.d.ts.map +0 -1
  226. package/dist/core/canvas-enrich.js +0 -186
  227. package/dist/core/canvas-enrich.js.map +0 -1
  228. package/dist/core/run-spend.d.ts +0 -55
  229. package/dist/core/run-spend.d.ts.map +0 -1
  230. package/dist/core/run-spend.js +0 -109
  231. package/dist/core/run-spend.js.map +0 -1
  232. package/dist/core/run-transactions.d.ts +0 -60
  233. package/dist/core/run-transactions.d.ts.map +0 -1
  234. package/dist/core/run-transactions.js +0 -129
  235. package/dist/core/run-transactions.js.map +0 -1
  236. package/dist/profiles/canvas-guidelines.d.ts +0 -20
  237. package/dist/profiles/canvas-guidelines.d.ts.map +0 -1
  238. package/dist/profiles/canvas-guidelines.js +0 -48
  239. package/dist/profiles/canvas-guidelines.js.map +0 -1
  240. package/dist/server/skills.d.ts +0 -51
  241. package/dist/server/skills.d.ts.map +0 -1
  242. package/dist/server/skills.js +0 -312
  243. package/dist/server/skills.js.map +0 -1
  244. package/dist/web/assets/index-DM84J83Y.js +0 -234
  245. package/dist/web/assets/index-DemtWW4L.css +0 -32
@@ -0,0 +1,79 @@
1
+ /**
2
+ * api-key-provider — the single source of truth for the Sapiom API key that
3
+ * Studio actions authenticate with server-side.
4
+ *
5
+ * WHY this exists: the key is resolved ONCE at CLI boot (from the cached
6
+ * credential or a fresh browser login) and then handed to the server as a plain
7
+ * string. Two problems fell out of that snapshot:
8
+ *
9
+ * 1. It never refreshes. If the key is rotated/revoked — or the user runs the
10
+ * shared login again in another process, rewriting the credential store —
11
+ * the running Studio server keeps sending the stale key and every upstream
12
+ * call 401s until the whole server is restarted. There was no re-auth path.
13
+ * 2. It conflated two different secrets. The per-boot *boot token*
14
+ * (`X-Harness-Token`) only gates the LOCAL `/api` surface; the *API key*
15
+ * (`sk_…`) is what authenticates upstream Sapiom calls. Studio actions must
16
+ * authenticate with the held API key, never the boot token.
17
+ *
18
+ * This provider holds the current key behind a getter (so consumers read the
19
+ * live value, not a boot-time copy) and can `refresh()` it by re-reading the
20
+ * shared credential store the CLI/MCP login writes to
21
+ * (`~/.sapiom/credentials.json`, via `@sapiom/mcp/auth`). A 401 from an
22
+ * upstream call can therefore recover in place — refresh, then retry — instead
23
+ * of locking the Studio.
24
+ *
25
+ * NOT in scope (deliberately, per the ticket): triggering an interactive
26
+ * browser re-login from the server, or a broader session-auth redesign. Refresh
27
+ * here is a silent re-read of already-cached credentials; if the store has no
28
+ * newer key, the call surfaces the auth error honestly.
29
+ */
30
+ /**
31
+ * Reads the currently-held API key and can refresh it from the shared
32
+ * credential store. Consumers should call {@link getKey} per request (never
33
+ * cache the returned string across requests) and {@link refresh} exactly once
34
+ * when an upstream call returns 401/403, retrying only if refresh yields a
35
+ * different, non-null key.
36
+ */
37
+ export interface ApiKeyProvider {
38
+ /** The current API key, or null when the harness is not signed in. */
39
+ getKey(): string | null;
40
+ /**
41
+ * Re-read the shared credential store and adopt any newer key found there.
42
+ * Returns the (possibly updated) current key, or null if none is available.
43
+ * Never throws — a read failure leaves the current key untouched and is
44
+ * reported by returning the existing value.
45
+ */
46
+ refresh(): Promise<string | null>;
47
+ /**
48
+ * Unconditionally zero the in-memory key (sets it to null). Called on
49
+ * disconnect so that {@link getKey} returns null immediately — distinct from
50
+ * {@link refresh}, whose `if (latest)` guard deliberately preserves the
51
+ * cached key when the credential store has nothing to offer.
52
+ */
53
+ clear(): void;
54
+ }
55
+ /** Overridable reads for the credential store — a test seam. Defaults hit the
56
+ * real `@sapiom/mcp/auth` store the CLI login writes to. */
57
+ export interface ApiKeyProviderDeps {
58
+ /** Resolve the active environment name (governs which cached entry to read). */
59
+ resolveEnvironmentName?: () => Promise<string>;
60
+ /** Read the cached API key for a given environment, or null if absent. */
61
+ readApiKeyForEnv?: (envName: string) => Promise<string | null>;
62
+ /** Overrides SAPIOM_ENVIRONMENT for environment resolution. */
63
+ environment?: string;
64
+ }
65
+ /**
66
+ * Build an {@link ApiKeyProvider} seeded with the boot-time key. `refresh()`
67
+ * re-reads the shared credential store for the active environment and adopts a
68
+ * newer key when one is present — the reconciliation point between the key the
69
+ * CLI captured at boot and any key rotated/re-logged-in afterward.
70
+ */
71
+ export declare function createApiKeyProvider(initialKey: string | null, deps?: ApiKeyProviderDeps): ApiKeyProvider;
72
+ /**
73
+ * Adapt a plain `string | null` API key into an {@link ApiKeyProvider} whose
74
+ * `refresh()` is a no-op and `clear()` is a no-op. Lets call sites that only
75
+ * ever have a static key (tests, callers with no credential store) share the
76
+ * one provider-shaped contract without special-casing.
77
+ */
78
+ export declare function staticApiKeyProvider(key: string | null): ApiKeyProvider;
79
+ //# sourceMappingURL=api-key-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-provider.d.ts","sourceRoot":"","sources":["../../src/core/api-key-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,MAAM,IAAI,MAAM,GAAG,IAAI,CAAC;IACxB;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClC;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAC;CACf;AAED;6DAC6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/D,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAkBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,IAAI,GAAE,kBAAuB,GAC5B,cAAc,CA8BhB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAQvE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * api-key-provider — the single source of truth for the Sapiom API key that
3
+ * Studio actions authenticate with server-side.
4
+ *
5
+ * WHY this exists: the key is resolved ONCE at CLI boot (from the cached
6
+ * credential or a fresh browser login) and then handed to the server as a plain
7
+ * string. Two problems fell out of that snapshot:
8
+ *
9
+ * 1. It never refreshes. If the key is rotated/revoked — or the user runs the
10
+ * shared login again in another process, rewriting the credential store —
11
+ * the running Studio server keeps sending the stale key and every upstream
12
+ * call 401s until the whole server is restarted. There was no re-auth path.
13
+ * 2. It conflated two different secrets. The per-boot *boot token*
14
+ * (`X-Harness-Token`) only gates the LOCAL `/api` surface; the *API key*
15
+ * (`sk_…`) is what authenticates upstream Sapiom calls. Studio actions must
16
+ * authenticate with the held API key, never the boot token.
17
+ *
18
+ * This provider holds the current key behind a getter (so consumers read the
19
+ * live value, not a boot-time copy) and can `refresh()` it by re-reading the
20
+ * shared credential store the CLI/MCP login writes to
21
+ * (`~/.sapiom/credentials.json`, via `@sapiom/mcp/auth`). A 401 from an
22
+ * upstream call can therefore recover in place — refresh, then retry — instead
23
+ * of locking the Studio.
24
+ *
25
+ * NOT in scope (deliberately, per the ticket): triggering an interactive
26
+ * browser re-login from the server, or a broader session-auth redesign. Refresh
27
+ * here is a silent re-read of already-cached credentials; if the store has no
28
+ * newer key, the call surfaces the auth error honestly.
29
+ */
30
+ import { readCredentials, resolveEnvironment } from "@sapiom/mcp/auth";
31
+ async function defaultResolveEnvironmentName(environment) {
32
+ const env = await resolveEnvironment(environment ?? process.env.SAPIOM_ENVIRONMENT);
33
+ return env.name;
34
+ }
35
+ async function defaultReadApiKeyForEnv(envName) {
36
+ const entry = await readCredentials(envName);
37
+ return entry?.apiKey ?? null;
38
+ }
39
+ /**
40
+ * Build an {@link ApiKeyProvider} seeded with the boot-time key. `refresh()`
41
+ * re-reads the shared credential store for the active environment and adopts a
42
+ * newer key when one is present — the reconciliation point between the key the
43
+ * CLI captured at boot and any key rotated/re-logged-in afterward.
44
+ */
45
+ export function createApiKeyProvider(initialKey, deps = {}) {
46
+ let current = initialKey;
47
+ const resolveEnvName = deps.resolveEnvironmentName ??
48
+ (() => defaultResolveEnvironmentName(deps.environment));
49
+ const readApiKey = deps.readApiKeyForEnv ?? defaultReadApiKeyForEnv;
50
+ return {
51
+ getKey() {
52
+ return current;
53
+ },
54
+ async refresh() {
55
+ try {
56
+ const envName = await resolveEnvName();
57
+ const latest = await readApiKey(envName);
58
+ // Only adopt a real, non-empty key. A missing/emptied credential must
59
+ // not clobber a key that's simply been rotated out from under us but is
60
+ // still the best value we have to report an honest auth error with.
61
+ if (latest)
62
+ current = latest;
63
+ }
64
+ catch {
65
+ // Store unreadable (HOME missing, malformed file, …) — keep the current
66
+ // key. The caller's retry will simply hit the same auth error, which is
67
+ // the correct, honest outcome.
68
+ }
69
+ return current;
70
+ },
71
+ clear() {
72
+ current = null;
73
+ },
74
+ };
75
+ }
76
+ /**
77
+ * Adapt a plain `string | null` API key into an {@link ApiKeyProvider} whose
78
+ * `refresh()` is a no-op and `clear()` is a no-op. Lets call sites that only
79
+ * ever have a static key (tests, callers with no credential store) share the
80
+ * one provider-shaped contract without special-casing.
81
+ */
82
+ export function staticApiKeyProvider(key) {
83
+ return {
84
+ getKey: () => key,
85
+ refresh: () => Promise.resolve(key),
86
+ clear: () => {
87
+ /* static key — clear is a no-op */
88
+ },
89
+ };
90
+ }
91
+ //# sourceMappingURL=api-key-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-provider.js","sourceRoot":"","sources":["../../src/core/api-key-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAuCvE,KAAK,UAAU,6BAA6B,CAC1C,WAAoB;IAEpB,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAClC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAC9C,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,uBAAuB,CACpC,OAAe;IAEf,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,KAAK,EAAE,MAAM,IAAI,IAAI,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAyB,EACzB,OAA2B,EAAE;IAE7B,IAAI,OAAO,GAAG,UAAU,CAAC;IACzB,MAAM,cAAc,GAClB,IAAI,CAAC,sBAAsB;QAC3B,CAAC,GAAG,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,IAAI,uBAAuB,CAAC;IAEpE,OAAO;QACL,MAAM;YACJ,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,OAAO;YACX,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,cAAc,EAAE,CAAC;gBACvC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;gBACzC,sEAAsE;gBACtE,wEAAwE;gBACxE,oEAAoE;gBACpE,IAAI,MAAM;oBAAE,OAAO,GAAG,MAAM,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,wEAAwE;gBACxE,wEAAwE;gBACxE,+BAA+B;YACjC,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK;YACH,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAkB;IACrD,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG;QACjB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QACnC,KAAK,EAAE,GAAG,EAAE;YACV,mCAAmC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,10 +1,19 @@
1
1
  /**
2
2
  * Assembles the `<div id="canvas-root">` body — the bound workflow's
3
- * `<section class="canvas-panel">` (header + stats + SVG diagram) and a
4
- * legend footer — entirely from the classes `core/canvas-template.ts`'s shell
5
- * already defines. This is the HTML half of the deterministic render;
6
- * `core/canvas-render.ts` wraps the result through `renderCanvasDocument()`
7
- * and writes it to the workflow's render file.
3
+ * `<section class="canvas-panel">` (title/summary header + SVG diagram),
4
+ * entirely from the classes `core/canvas-template.ts`'s shell already defines.
5
+ * This is the HTML half of the deterministic render; `core/canvas-render.ts`
6
+ * wraps the result through `renderCanvasDocument()` and writes it to the
7
+ * workflow's render file.
8
+ *
9
+ * The panel ends with the color/shape legend (only the node kinds actually
10
+ * used, plus a cross-workflow row when relevant) so the diagram reads on its
11
+ * own. It sits at the bottom of `#canvas-root` INSIDE the iframe — safe from
12
+ * the SPA's floating zoom/fit controls because the pane reserves a strip below
13
+ * the iframe for them (see `.canvas-visual { padding-bottom }` in
14
+ * web/src/styles.css), so the two never sit "l'un sur l'autre". The only thing
15
+ * deliberately dropped is the old prose footer note ("Static preview —
16
+ * regenerate…"), which was pure noise.
8
17
  */
9
18
  import type { CanvasEdgeKind, CanvasGraph, CanvasNodeKind } from "./canvas-graph.js";
10
19
  import type { CanvasEnrichment } from "./canvas-enrichment.js";
@@ -12,34 +21,25 @@ export interface WorkflowPanelMeta {
12
21
  title: string;
13
22
  badges?: string[];
14
23
  }
15
- /** How enrichment content reaches the panel/footer builders: the validated
16
- * content itself plus whether its source fingerprint no longer matches the
17
- * workflow's current sources (kept displayed, marked stale). */
18
- export interface PanelEnrichment {
19
- enrichment: CanvasEnrichment;
20
- stale: boolean;
21
- }
22
- /** One workflow's full panel: title/badges header, step/entry stats, SVG
23
- * diagram with any enrichment merged in (summary line, node/edge
24
- * annotations, a "stale" chip when its fingerprint no longer matches). */
25
- export declare function buildWorkflowPanelHtml(graph: CanvasGraph, meta: WorkflowPanelMeta, panelEnrichment?: PanelEnrichment | null): string;
24
+ /**
25
+ * The color/shape key for one diagram: only the node kinds that actually appear
26
+ * (in stable order), plus a cross-workflow row when the graph has cross edges.
27
+ * Returns "" when nothing is worth keying, so the panel omits the footer.
28
+ */
29
+ export declare function buildLegendHtml(nodeKinds: Set<CanvasNodeKind>, edgeKinds: Set<CanvasEdgeKind>): string;
30
+ /** One workflow's full panel: title/badges + summary header and the SVG
31
+ * diagram, with the deterministic enrichment merged in (summary line and
32
+ * node/edge annotations), plus the embedded step-graph payload the Steps tab
33
+ * reads. */
34
+ export declare function buildWorkflowPanelHtml(graph: CanvasGraph, meta: WorkflowPanelMeta, enrichment?: CanvasEnrichment | null): string;
26
35
  /** A degraded panel for a workflow whose graph couldn't be extracted — never
27
36
  * a crash, never a silent fallback to the LLM path, just an honest reason
28
37
  * styled through the same shell. */
29
38
  export declare function buildErrorPanelHtml(title: string, reason: string): string;
30
- /** Legend footer covering every node/edge kind actually used across every
31
- * rendered panel (aggregate, not per-panel matches the template's shape). */
32
- export declare function buildLegendHtml(nodeKinds: Set<CanvasNodeKind>, edgeKinds: Set<CanvasEdgeKind>): string;
33
- /** Joins panels + a footer (enrichment notes/cross-workflow tie, legend,
34
- * note) into the final `#canvas-root` body — the string
35
- * `renderCanvasDocument()` wraps. */
39
+ /** Joins the workflow panel(s) into the final `#canvas-root` body — the string
40
+ * `renderCanvasDocument()` wraps. Each panel already carries its own legend
41
+ * footer (see `buildWorkflowPanelHtml`); there is no document-level footer. */
36
42
  export declare function assembleCanvasBody(input: {
37
43
  panels: string[];
38
- legend: string;
39
- note: string;
40
- /** Enrichment footer facts (already schema-bounded), rendered as a list. */
41
- notes?: string[];
42
- /** Enrichment's "how this ties into the other workflows" line. */
43
- crossWorkflow?: string;
44
44
  }): string;
45
45
  //# sourceMappingURL=canvas-body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-body.d.ts","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;iEAEiE;AACjE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;2EAE2E;AAC3E,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,iBAAiB,EACvB,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,GACvC,MAAM,CA4BR;AAED;;qCAEqC;AACrC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYzE;AAmBD;gFACgF;AAChF,wBAAgB,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAWtG;AAED;;sCAEsC;AACtC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,CAcT"}
1
+ {"version":3,"file":"canvas-body.d.ts","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAoBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAYtG;AAwED;;;aAGa;AACb,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,iBAAiB,EACvB,UAAU,CAAC,EAAE,gBAAgB,GAAG,IAAI,GACnC,MAAM,CA2BR;AAED;;qCAEqC;AACrC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYzE;AAED;;gFAEgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,CAEtE"}
@@ -1,36 +1,134 @@
1
- import { renderGraphSvg } from "./canvas-svg.js";
1
+ import { renderGraphSvg, usedKinds } from "./canvas-svg.js";
2
2
  function esc(s) {
3
3
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4
4
  }
5
- /** One workflow's full panel: title/badges header, step/entry stats, SVG
6
- * diagram with any enrichment merged in (summary line, node/edge
7
- * annotations, a "stale" chip when its fingerprint no longer matches). */
8
- export function buildWorkflowPanelHtml(graph, meta, panelEnrichment) {
5
+ const NODE_KIND_LABEL = {
6
+ entry: "entry / active step",
7
+ step: "step",
8
+ pause: "pause / waits for input",
9
+ "terminal-success": "terminal · success",
10
+ "terminal-warn": "terminal · escalation",
11
+ "launched-workflow": "launches another workflow",
12
+ };
13
+ const NODE_KIND_ORDER = [
14
+ "entry",
15
+ "step",
16
+ "pause",
17
+ "terminal-success",
18
+ "terminal-warn",
19
+ "launched-workflow",
20
+ ];
21
+ /**
22
+ * The color/shape key for one diagram: only the node kinds that actually appear
23
+ * (in stable order), plus a cross-workflow row when the graph has cross edges.
24
+ * Returns "" when nothing is worth keying, so the panel omits the footer.
25
+ */
26
+ export function buildLegendHtml(nodeKinds, edgeKinds) {
27
+ const items = NODE_KIND_ORDER.filter((k) => nodeKinds.has(k)).map((k) => `<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--${k}"></span>${NODE_KIND_LABEL[k]}</span>`);
28
+ if (edgeKinds.has("cross")) {
29
+ items.push(`<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--cross"></span>cross-workflow signal/handoff</span>`);
30
+ }
31
+ if (items.length === 0)
32
+ return "";
33
+ return `<footer class="canvas-legend">${items.join("")}</footer>`;
34
+ }
35
+ /**
36
+ * Serializes the graph (enrichment merged) into the JSON payload the app's
37
+ * Steps tab / inspector consumes, embedded as a `<script id="sapiom-graph">`
38
+ * data block that `bootCanvasGraph` (canvas-run-state.ts) posts to the parent.
39
+ *
40
+ * The projection mirrors `renderGraphSvg`'s merge EXACTLY so the Steps tab and
41
+ * the drawn board never disagree: `role` = the node's sublabel (enrichment
42
+ * override first), `description` = the enrichment hover text, and each edge's
43
+ * `label` is keyed `from->to`. Web-only fields the server graph doesn't carry
44
+ * (timeout / input schema / capabilities) are omitted — `parseCanvasGraph`
45
+ * defaults them, so the drill-down degrades to names + roles + branch facts.
46
+ *
47
+ * `<` is escaped to `<` so a label containing `</script>` can't break out
48
+ * of the JSON block; the result is still valid JSON.
49
+ */
50
+ function buildGraphPayload(graph, enrichment) {
51
+ const payload = {
52
+ name: graph.manifestName,
53
+ entry: graph.entry,
54
+ nodes: graph.nodes.map((n) => {
55
+ const details = enrichment?.nodeDetails?.[n.id];
56
+ return {
57
+ id: n.id,
58
+ kind: n.kind,
59
+ label: n.label,
60
+ role: details?.sublabel ?? n.sublabel ?? "",
61
+ // Manifest-authored description (Option A) wins; enrichment is the
62
+ // legacy fallback (currently always undefined).
63
+ description: details?.description ?? n.description ?? "",
64
+ inputSchema: n.inputSchema ?? null,
65
+ capabilities: n.capabilities ?? [],
66
+ timeoutMs: n.timeoutMs ?? null,
67
+ };
68
+ }),
69
+ edges: graph.edges.map((e) => ({
70
+ from: e.from,
71
+ to: e.to,
72
+ kind: e.kind,
73
+ label: enrichment?.edgeLabels?.[`${e.from}->${e.to}`] ?? e.label ?? "",
74
+ })),
75
+ warnings: graph.warnings,
76
+ };
77
+ return JSON.stringify(payload).replace(/</g, "\\u003c");
78
+ }
79
+ /** The workflow-level overview the Canvas tab's bottom panel shows: a
80
+ * deterministic stats line ("N steps · M exits · <entry> entry"), the shape
81
+ * summary as the description, and the graph's validation notes. Embedded as a
82
+ * JSON data block (read by bootCanvasOverview), the same pattern as the step
83
+ * graph — everything here is derived from the graph, no LLM. */
84
+ function buildOverviewPayload(graph, enrichment) {
85
+ const exits = graph.nodes.filter((n) => n.kind === "terminal-success" || n.kind === "terminal-warn").length;
86
+ const steps = graph.nodes.length - exits;
87
+ const entryLabel = graph.nodes.find((n) => n.id === graph.entry)?.label ?? graph.entry;
88
+ const parts = [`${steps} ${steps === 1 ? "step" : "steps"}`];
89
+ if (exits > 0)
90
+ parts.push(`${exits} ${exits === 1 ? "exit" : "exits"}`);
91
+ if (entryLabel)
92
+ parts.push(`${entryLabel} entry`);
93
+ const overview = {
94
+ // Human-authored workflow description from the manifest (Option A); "" when
95
+ // the workflow declares none (the shape summary still renders on the board).
96
+ description: graph.description ?? "",
97
+ stats: parts.join(" · "),
98
+ notes: enrichment?.notes ?? [],
99
+ };
100
+ // JSON in a <script> block: neutralize any "</script>"/"<" in derived text.
101
+ return JSON.stringify(overview).replace(/</g, "\\u003c");
102
+ }
103
+ /** One workflow's full panel: title/badges + summary header and the SVG
104
+ * diagram, with the deterministic enrichment merged in (summary line and
105
+ * node/edge annotations), plus the embedded step-graph payload the Steps tab
106
+ * reads. */
107
+ export function buildWorkflowPanelHtml(graph, meta, enrichment) {
9
108
  const badges = (meta.badges ?? [])
10
109
  .map((b) => `<span class="canvas-badge">${esc(b)}</span>`)
11
110
  .join("");
12
111
  const warningBadge = graph.warnings.length > 0
13
112
  ? `<span class="canvas-badge">${graph.warnings.length} warning${graph.warnings.length === 1 ? "" : "s"}</span>`
14
113
  : "";
15
- const enrichment = panelEnrichment?.enrichment ?? null;
16
- const staleBadge = panelEnrichment?.stale
17
- ? `<span class="canvas-badge canvas-badge--stale">stale — Refresh</span>`
18
- : "";
19
114
  const summary = enrichment?.summary ? `\n <p class="canvas-subtitle">${esc(enrichment.summary)}</p>` : "";
115
+ const used = usedKinds(graph);
116
+ const legend = buildLegendHtml(used.nodeKinds, used.edgeKinds);
117
+ const legendHtml = legend ? `\n ${legend}` : "";
118
+ const graphData = buildGraphPayload(graph, enrichment);
119
+ const overviewData = buildOverviewPayload(graph, enrichment);
20
120
  return `<section class="canvas-panel">
21
121
  <header class="canvas-header">
22
122
  <div class="canvas-title-row">
23
123
  <h1 class="canvas-title">${esc(meta.title)}</h1>
24
- ${badges}${warningBadge}${staleBadge}
124
+ ${badges}${warningBadge}
25
125
  </div>${summary}
26
- <div class="canvas-stats">
27
- <div class="canvas-stat"><span class="canvas-stat-value">${graph.nodes.length}</span><span class="canvas-stat-label">steps</span></div>
28
- <div class="canvas-stat"><span class="canvas-stat-value">${esc(graph.entry)}</span><span class="canvas-stat-label">entry</span></div>
29
- </div>
30
126
  </header>
31
127
  <div class="canvas-diagram-panel">
32
128
  ${renderGraphSvg(graph, enrichment)}
33
- </div>
129
+ </div>${legendHtml}
130
+ <script type="application/json" id="sapiom-graph">${graphData}</script>
131
+ <script type="application/json" id="sapiom-overview">${overviewData}</script>
34
132
  </section>`;
35
133
  }
36
134
  /** A degraded panel for a workflow whose graph couldn't be extracted — never
@@ -49,46 +147,10 @@ export function buildErrorPanelHtml(title, reason) {
49
147
  </div>
50
148
  </section>`;
51
149
  }
52
- const NODE_KIND_LABEL = {
53
- entry: "entry / active step",
54
- step: "step",
55
- pause: "pause / waits for input",
56
- "terminal-success": "terminal · success",
57
- "terminal-warn": "terminal · escalation",
58
- "launched-workflow": "launches another workflow",
59
- };
60
- const NODE_KIND_ORDER = [
61
- "entry",
62
- "step",
63
- "pause",
64
- "terminal-success",
65
- "terminal-warn",
66
- "launched-workflow",
67
- ];
68
- /** Legend footer covering every node/edge kind actually used across every
69
- * rendered panel (aggregate, not per-panel — matches the template's shape). */
70
- export function buildLegendHtml(nodeKinds, edgeKinds) {
71
- const items = NODE_KIND_ORDER.filter((k) => nodeKinds.has(k)).map((k) => `<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--${k}"></span>${NODE_KIND_LABEL[k]}</span>`);
72
- if (edgeKinds.has("cross")) {
73
- items.push(`<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--cross"></span>cross-workflow signal/handoff</span>`);
74
- }
75
- return `<div class="canvas-legend">${items.join("")}</div>`;
76
- }
77
- /** Joins panels + a footer (enrichment notes/cross-workflow tie, legend,
78
- * note) into the final `#canvas-root` body — the string
79
- * `renderCanvasDocument()` wraps. */
150
+ /** Joins the workflow panel(s) into the final `#canvas-root` body — the string
151
+ * `renderCanvasDocument()` wraps. Each panel already carries its own legend
152
+ * footer (see `buildWorkflowPanelHtml`); there is no document-level footer. */
80
153
  export function assembleCanvasBody(input) {
81
- const parts = [...input.panels];
82
- const notesHtml = input.notes && input.notes.length > 0
83
- ? `\n <ul class="canvas-notes">${input.notes.map((n) => `<li>${esc(n)}</li>`).join("")}</ul>`
84
- : "";
85
- const crossHtml = input.crossWorkflow
86
- ? `\n <p class="canvas-cross-workflow">${esc(input.crossWorkflow)}</p>`
87
- : "";
88
- parts.push(`<footer class="canvas-footer">${notesHtml}${crossHtml}
89
- ${input.legend}
90
- <p class="canvas-note">${esc(input.note)}</p>
91
- </footer>`);
92
- return parts.join("\n\n");
154
+ return input.panels.join("\n\n");
93
155
  }
94
156
  //# sourceMappingURL=canvas-body.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-body.js","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"canvas-body.js","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Deterministic canvas enrichment (no LLM, no user token, no file reads, no
3
+ * cache). Given the already-extracted CanvasGraph (core/canvas-graph.ts), it
4
+ * computes the same CanvasEnrichment shape the old AI task returned
5
+ * (core/canvas-enrichment.ts) — a summary line, footer notes, and a
6
+ * cross-workflow tie — entirely from the graph's own structure. Pure and
7
+ * total: the same graph in always yields the same enrichment out, and every
8
+ * string is kept within ENRICHMENT_LIMITS so the layout can't break.
9
+ *
10
+ * What it deliberately does NOT reproduce from the retired AI pass: free-prose
11
+ * per-step descriptions and condition-named edge labels, which needed a reading
12
+ * of the step bodies. The base render already conveys role (node border +
13
+ * classify-derived sublabel) and outcome (edge color), so their absence
14
+ * degrades to "less prose", never "less structure".
15
+ */
16
+ import { type CanvasEnrichment } from "./canvas-enrichment.js";
17
+ import type { CanvasGraph } from "./canvas-graph.js";
18
+ /**
19
+ * A one-line description of the workflow's shape — step count, branch points,
20
+ * and terminal outcomes — the deterministic stand-in for the AI summary. Reads
21
+ * the same graph the diagram is drawn from, so it can never disagree with it.
22
+ */
23
+ export declare function deriveSummary(graph: CanvasGraph): string;
24
+ /**
25
+ * Footer notes: the graph's own validation warnings (unreachable steps, no path
26
+ * to a terminal) — facts worth surfacing that the diagram can't show on its own.
27
+ * Undefined when the graph is clean, capped to the contract's count/length.
28
+ */
29
+ export declare function deriveNotes(graph: CanvasGraph): string[] | undefined;
30
+ /**
31
+ * The cross-workflow tie, derived from the dashed launched-workflow nodes the
32
+ * extraction already merged in (core/canvas-graph.ts's mergeLaunchesIntoGraph,
33
+ * fed by the grep in core/canvas-interconnections.ts). Undefined when this
34
+ * workflow launches none.
35
+ */
36
+ export declare function deriveCrossWorkflow(graph: CanvasGraph): string | undefined;
37
+ /**
38
+ * Builds the deterministic enrichment for a graph. Only sets a field when it
39
+ * has real content, so an annotation-light but valid workflow renders its clean
40
+ * base — matching the "every field optional" contract the AI enrichment had.
41
+ */
42
+ export declare function deriveEnrichment(graph: CanvasGraph): CanvasEnrichment;
43
+ //# sourceMappingURL=canvas-derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-derive.d.ts","sourceRoot":"","sources":["../../src/core/canvas-derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAA8B,MAAM,mBAAmB,CAAC;AAgBjF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAuBxD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,EAAE,GAAG,SAAS,CAMpE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAK1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,CAOrE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Deterministic canvas enrichment (no LLM, no user token, no file reads, no
3
+ * cache). Given the already-extracted CanvasGraph (core/canvas-graph.ts), it
4
+ * computes the same CanvasEnrichment shape the old AI task returned
5
+ * (core/canvas-enrichment.ts) — a summary line, footer notes, and a
6
+ * cross-workflow tie — entirely from the graph's own structure. Pure and
7
+ * total: the same graph in always yields the same enrichment out, and every
8
+ * string is kept within ENRICHMENT_LIMITS so the layout can't break.
9
+ *
10
+ * What it deliberately does NOT reproduce from the retired AI pass: free-prose
11
+ * per-step descriptions and condition-named edge labels, which needed a reading
12
+ * of the step bodies. The base render already conveys role (node border +
13
+ * classify-derived sublabel) and outcome (edge color), so their absence
14
+ * degrades to "less prose", never "less structure".
15
+ */
16
+ import { ENRICHMENT_LIMITS } from "./canvas-enrichment.js";
17
+ /** Truncates to a hard cap with an ellipsis. The derivations below stay well
18
+ * under their caps; this just makes the same guarantee the contract does. */
19
+ function clamp(value, max) {
20
+ return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
21
+ }
22
+ function countKind(nodes, kind) {
23
+ return nodes.filter((n) => n.kind === kind).length;
24
+ }
25
+ function plural(n, one, many = `${one}s`) {
26
+ return n === 1 ? one : many;
27
+ }
28
+ /**
29
+ * A one-line description of the workflow's shape — step count, branch points,
30
+ * and terminal outcomes — the deterministic stand-in for the AI summary. Reads
31
+ * the same graph the diagram is drawn from, so it can never disagree with it.
32
+ */
33
+ export function deriveSummary(graph) {
34
+ // Real steps only — exclude the dashed launched-workflow placeholders.
35
+ const stepCount = graph.nodes.filter((n) => n.kind !== "launched-workflow").length;
36
+ // A branch point is a step that fans out to multiple successors; the graph
37
+ // marks exactly those edges "branching" (core/canvas-graph.ts's edgesForStep).
38
+ const branchPoints = new Set(graph.edges.filter((e) => e.kind === "branching").map((e) => e.from)).size;
39
+ const success = countKind(graph.nodes, "terminal-success");
40
+ const warn = countKind(graph.nodes, "terminal-warn");
41
+ const parts = [`${stepCount} ${plural(stepCount, "step")}`];
42
+ if (branchPoints > 0)
43
+ parts.push(`${branchPoints} ${plural(branchPoints, "branch point")}`);
44
+ const outcomes = [];
45
+ if (success > 0)
46
+ outcomes.push(`${success} success`);
47
+ if (warn > 0)
48
+ outcomes.push(`${warn} escalation`);
49
+ if (outcomes.length > 0) {
50
+ parts.push(`${outcomes.join(" / ")} ${plural(success + warn, "outcome")}`);
51
+ }
52
+ return clamp(parts.join(" · "), ENRICHMENT_LIMITS.summary);
53
+ }
54
+ /**
55
+ * Footer notes: the graph's own validation warnings (unreachable steps, no path
56
+ * to a terminal) — facts worth surfacing that the diagram can't show on its own.
57
+ * Undefined when the graph is clean, capped to the contract's count/length.
58
+ */
59
+ export function deriveNotes(graph) {
60
+ if (graph.warnings.length === 0)
61
+ return undefined;
62
+ const notes = graph.warnings
63
+ .slice(0, ENRICHMENT_LIMITS.noteCount)
64
+ .map((w) => clamp(w, ENRICHMENT_LIMITS.note));
65
+ return notes.length > 0 ? notes : undefined;
66
+ }
67
+ /**
68
+ * The cross-workflow tie, derived from the dashed launched-workflow nodes the
69
+ * extraction already merged in (core/canvas-graph.ts's mergeLaunchesIntoGraph,
70
+ * fed by the grep in core/canvas-interconnections.ts). Undefined when this
71
+ * workflow launches none.
72
+ */
73
+ export function deriveCrossWorkflow(graph) {
74
+ const launched = graph.nodes.filter((n) => n.kind === "launched-workflow").map((n) => n.label);
75
+ if (launched.length === 0)
76
+ return undefined;
77
+ const unique = [...new Set(launched)].sort();
78
+ return clamp(`Launches ${unique.join(", ")}.`, ENRICHMENT_LIMITS.crossWorkflow);
79
+ }
80
+ /**
81
+ * Builds the deterministic enrichment for a graph. Only sets a field when it
82
+ * has real content, so an annotation-light but valid workflow renders its clean
83
+ * base — matching the "every field optional" contract the AI enrichment had.
84
+ */
85
+ export function deriveEnrichment(graph) {
86
+ const enrichment = { summary: deriveSummary(graph) };
87
+ const notes = deriveNotes(graph);
88
+ if (notes)
89
+ enrichment.notes = notes;
90
+ const crossWorkflow = deriveCrossWorkflow(graph);
91
+ if (crossWorkflow)
92
+ enrichment.crossWorkflow = crossWorkflow;
93
+ return enrichment;
94
+ }
95
+ //# sourceMappingURL=canvas-derive.js.map