@relayflows/sdk 2.0.24 → 2.0.26

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 (167) hide show
  1. package/dist/authored-flow-executor.d.ts +8 -0
  2. package/dist/authored-flow-executor.d.ts.map +1 -1
  3. package/dist/authored-flow-executor.js +40 -1
  4. package/dist/authored-flow-executor.js.map +1 -1
  5. package/dist/authored-flow-loader.d.ts +15 -2
  6. package/dist/authored-flow-loader.d.ts.map +1 -1
  7. package/dist/authored-flow-loader.js +33 -4
  8. package/dist/authored-flow-loader.js.map +1 -1
  9. package/dist/authored-hooks.d.ts +29 -0
  10. package/dist/authored-hooks.d.ts.map +1 -0
  11. package/dist/authored-hooks.js +117 -0
  12. package/dist/authored-hooks.js.map +1 -0
  13. package/dist/authored-node-entry.js +1 -0
  14. package/dist/authored-node-entry.js.map +1 -1
  15. package/dist/authored-root.d.ts +7 -0
  16. package/dist/authored-root.d.ts.map +1 -1
  17. package/dist/authored-root.js +14 -1
  18. package/dist/authored-root.js.map +1 -1
  19. package/dist/authored-source-authority.d.ts.map +1 -1
  20. package/dist/authored-source-authority.js +6 -0
  21. package/dist/authored-source-authority.js.map +1 -1
  22. package/dist/babysitter-catalog-export.d.ts +9 -0
  23. package/dist/babysitter-catalog-export.d.ts.map +1 -0
  24. package/dist/babysitter-catalog-export.js +36 -0
  25. package/dist/babysitter-catalog-export.js.map +1 -0
  26. package/dist/bundle-extensions.d.ts +19 -0
  27. package/dist/bundle-extensions.d.ts.map +1 -0
  28. package/dist/bundle-extensions.js +95 -0
  29. package/dist/bundle-extensions.js.map +1 -0
  30. package/dist/bundle-typescript.js +1 -1
  31. package/dist/bundle-typescript.js.map +1 -1
  32. package/dist/bundle.d.ts +11 -0
  33. package/dist/bundle.d.ts.map +1 -1
  34. package/dist/bundle.js +13 -4
  35. package/dist/bundle.js.map +1 -1
  36. package/dist/cli/add-extension.d.ts +28 -0
  37. package/dist/cli/add-extension.d.ts.map +1 -0
  38. package/dist/cli/add-extension.js +135 -0
  39. package/dist/cli/add-extension.js.map +1 -0
  40. package/dist/cli/add.d.ts +3 -0
  41. package/dist/cli/add.d.ts.map +1 -1
  42. package/dist/cli/add.js +8 -0
  43. package/dist/cli/add.js.map +1 -1
  44. package/dist/cli/build.d.ts.map +1 -1
  45. package/dist/cli/build.js +7 -2
  46. package/dist/cli/build.js.map +1 -1
  47. package/dist/cli/check-helper-body.d.ts.map +1 -1
  48. package/dist/cli/check-helper-body.js +4 -0
  49. package/dist/cli/check-helper-body.js.map +1 -1
  50. package/dist/cli/check-triggers.d.ts.map +1 -1
  51. package/dist/cli/check-triggers.js +25 -2
  52. package/dist/cli/check-triggers.js.map +1 -1
  53. package/dist/cli/check-typescript.d.ts.map +1 -1
  54. package/dist/cli/check-typescript.js +5 -1
  55. package/dist/cli/check-typescript.js.map +1 -1
  56. package/dist/cli/check.d.ts +24 -0
  57. package/dist/cli/check.d.ts.map +1 -1
  58. package/dist/cli/check.js.map +1 -1
  59. package/dist/cli/cloud-deploy.d.ts +1 -0
  60. package/dist/cli/cloud-deploy.d.ts.map +1 -1
  61. package/dist/cli/cloud-deploy.js +11 -1
  62. package/dist/cli/cloud-deploy.js.map +1 -1
  63. package/dist/cli/plugin.d.ts +50 -0
  64. package/dist/cli/plugin.d.ts.map +1 -0
  65. package/dist/cli/plugin.js +295 -0
  66. package/dist/cli/plugin.js.map +1 -0
  67. package/dist/cli-commands.d.ts +46 -3
  68. package/dist/cli-commands.d.ts.map +1 -1
  69. package/dist/cli-commands.js +32 -2
  70. package/dist/cli-commands.js.map +1 -1
  71. package/dist/cli.d.ts +2 -1
  72. package/dist/cli.d.ts.map +1 -1
  73. package/dist/cli.js +26 -1
  74. package/dist/cli.js.map +1 -1
  75. package/dist/cloud-deploy.d.ts +5 -0
  76. package/dist/cloud-deploy.d.ts.map +1 -1
  77. package/dist/cloud-deploy.js +7 -6
  78. package/dist/cloud-deploy.js.map +1 -1
  79. package/dist/cloud-run.d.ts +2 -0
  80. package/dist/cloud-run.d.ts.map +1 -1
  81. package/dist/cloud-run.js +14 -4
  82. package/dist/cloud-run.js.map +1 -1
  83. package/dist/failure-kinds.d.ts +2 -2
  84. package/dist/failure-kinds.d.ts.map +1 -1
  85. package/dist/flow-extension-compat.d.ts +18 -0
  86. package/dist/flow-extension-compat.d.ts.map +1 -0
  87. package/dist/flow-extension-compat.js +33 -0
  88. package/dist/flow-extension-compat.js.map +1 -0
  89. package/dist/flow-extension-loader.d.ts +92 -0
  90. package/dist/flow-extension-loader.d.ts.map +1 -0
  91. package/dist/flow-extension-loader.js +241 -0
  92. package/dist/flow-extension-loader.js.map +1 -0
  93. package/dist/flow-extension-manifest.d.ts +61 -0
  94. package/dist/flow-extension-manifest.d.ts.map +1 -0
  95. package/dist/flow-extension-manifest.js +188 -0
  96. package/dist/flow-extension-manifest.js.map +1 -0
  97. package/dist/flow-extension-submit.d.ts +38 -0
  98. package/dist/flow-extension-submit.d.ts.map +1 -0
  99. package/dist/flow-extension-submit.js +94 -0
  100. package/dist/flow-extension-submit.js.map +1 -0
  101. package/dist/flow-requirements.d.ts +13 -3
  102. package/dist/flow-requirements.d.ts.map +1 -1
  103. package/dist/flow-requirements.js +30 -0
  104. package/dist/flow-requirements.js.map +1 -1
  105. package/dist/plugin-github.d.ts +35 -0
  106. package/dist/plugin-github.d.ts.map +1 -0
  107. package/dist/plugin-github.js +92 -0
  108. package/dist/plugin-github.js.map +1 -0
  109. package/dist/plugin-loader.d.ts.map +1 -1
  110. package/dist/plugin-loader.js +5 -1
  111. package/dist/plugin-loader.js.map +1 -1
  112. package/dist/plugin-lock.d.ts +79 -0
  113. package/dist/plugin-lock.d.ts.map +1 -0
  114. package/dist/plugin-lock.js +203 -0
  115. package/dist/plugin-lock.js.map +1 -0
  116. package/dist/plugin-manifest.d.ts +5 -1
  117. package/dist/plugin-manifest.d.ts.map +1 -1
  118. package/dist/plugin-manifest.js +18 -0
  119. package/dist/plugin-manifest.js.map +1 -1
  120. package/dist/plugin-source.d.ts +38 -0
  121. package/dist/plugin-source.d.ts.map +1 -0
  122. package/dist/plugin-source.js +84 -0
  123. package/dist/plugin-source.js.map +1 -0
  124. package/dist/plugin-store.d.ts +30 -0
  125. package/dist/plugin-store.d.ts.map +1 -0
  126. package/dist/plugin-store.js +133 -0
  127. package/dist/plugin-store.js.map +1 -0
  128. package/dist/semver-range.d.ts +10 -0
  129. package/dist/semver-range.d.ts.map +1 -0
  130. package/dist/semver-range.js +88 -0
  131. package/dist/semver-range.js.map +1 -0
  132. package/package.json +9 -4
  133. package/src/authored-flow-executor.ts +60 -1
  134. package/src/authored-flow-loader.ts +49 -6
  135. package/src/authored-hooks.ts +136 -0
  136. package/src/authored-node-entry.ts +1 -0
  137. package/src/authored-root.ts +22 -1
  138. package/src/authored-source-authority.ts +6 -0
  139. package/src/babysitter-catalog-export.ts +44 -0
  140. package/src/bundle-extensions.ts +85 -0
  141. package/src/bundle-typescript.ts +1 -1
  142. package/src/bundle.ts +14 -4
  143. package/src/cli/add-extension.ts +134 -0
  144. package/src/cli/add.ts +9 -0
  145. package/src/cli/build.ts +7 -2
  146. package/src/cli/check-helper-body.ts +4 -0
  147. package/src/cli/check-triggers.ts +27 -2
  148. package/src/cli/check-typescript.ts +5 -1
  149. package/src/cli/check.ts +23 -0
  150. package/src/cli/cloud-deploy.ts +11 -1
  151. package/src/cli/plugin.ts +286 -0
  152. package/src/cli-commands.ts +32 -2
  153. package/src/cli.ts +23 -1
  154. package/src/cloud-deploy.ts +18 -8
  155. package/src/cloud-run.ts +16 -6
  156. package/src/flow-extension-compat.ts +35 -0
  157. package/src/flow-extension-loader.ts +304 -0
  158. package/src/flow-extension-manifest.ts +190 -0
  159. package/src/flow-extension-submit.ts +120 -0
  160. package/src/flow-requirements.ts +44 -3
  161. package/src/plugin-github.ts +98 -0
  162. package/src/plugin-loader.ts +5 -1
  163. package/src/plugin-lock.ts +194 -0
  164. package/src/plugin-manifest.ts +18 -0
  165. package/src/plugin-source.ts +91 -0
  166. package/src/plugin-store.ts +105 -0
  167. package/src/semver-range.ts +74 -0
@@ -0,0 +1,84 @@
1
+ import { safePath } from './bundle.js';
2
+ import { PluginError } from './plugin-manifest.js';
3
+ const OWNER = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
4
+ const REPO = /^[A-Za-z0-9_.-]{1,100}$/;
5
+ const REF = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$/;
6
+ export const SHA = /^[0-9a-f]{40}$/;
7
+ function invalid(message) {
8
+ throw new PluginError('plugin_source_invalid', message);
9
+ }
10
+ export function isGithubPluginRef(value) {
11
+ return value.startsWith('github:') || /^https:\/\/github\.com\//.test(value);
12
+ }
13
+ /**
14
+ * Accepts `github:<owner>/<repo>@<ref>#<path>`,
15
+ * `https://github.com/<owner>/<repo>/tree/<ref>/<path>` (or `/blob/`), and
16
+ * `github:<owner>/<repo>@<ref>` for a root-level plugin.
17
+ *
18
+ * In the URL form the ref is the single segment after `tree/`: GitHub itself
19
+ * disambiguates `tree/feat/x/dir` against the repository's refs, which an
20
+ * offline parser cannot. A ref containing `/` must use the `github:` form,
21
+ * where `@ref#path` is unambiguous.
22
+ */
23
+ export function parsePluginSource(input) {
24
+ if (input.length > 2048)
25
+ return invalid('Plugin source is too long.');
26
+ let owner, repo, ref, path = '';
27
+ if (input.startsWith('github:')) {
28
+ const m = /^github:([^/@#]+)\/([^/@#]+)@([^#]+)(?:#(.*))?$/.exec(input);
29
+ if (!m)
30
+ return invalid('Expected github:<owner>/<repo>@<ref>[#<path>].');
31
+ [, owner, repo, ref] = m;
32
+ path = m[4] ?? '';
33
+ }
34
+ else if (/^https:\/\/github\.com\//.test(input)) {
35
+ let url;
36
+ try {
37
+ url = new URL(input);
38
+ }
39
+ catch {
40
+ return invalid('Plugin source is not a valid URL.');
41
+ }
42
+ if (url.username || url.password || url.search || url.hash)
43
+ return invalid('Plugin URL must not carry credentials, a query, or a fragment.');
44
+ const m = /^\/([^/]+)\/([^/]+)\/(?:tree|blob)\/([^/]+)(?:\/(.*))?$/.exec(url.pathname);
45
+ if (!m)
46
+ return invalid('Expected https://github.com/<owner>/<repo>/tree/<ref>/<path>.');
47
+ try {
48
+ [, owner, repo, ref] = m.map(part => part === undefined ? part : decodeURIComponent(part));
49
+ path = m[4] === undefined ? '' : decodeURIComponent(m[4]);
50
+ }
51
+ catch {
52
+ return invalid('Plugin URL contains invalid percent-encoding.');
53
+ }
54
+ }
55
+ else
56
+ return invalid('Expected a github: reference or a https://github.com/ URL.');
57
+ if (owner === undefined || !OWNER.test(owner))
58
+ return invalid('Invalid GitHub owner.');
59
+ if (repo === undefined || !REPO.test(repo) || repo === '.' || repo === '..')
60
+ return invalid('Invalid GitHub repository name.');
61
+ if (repo.endsWith('.git'))
62
+ repo = repo.slice(0, -4);
63
+ if (ref === undefined || !REF.test(ref) || ref.includes('..') || ref.endsWith('/') || ref.endsWith('.lock'))
64
+ return invalid('Invalid git ref.');
65
+ path = path.replace(/\/+$/, '');
66
+ if (path !== '' && !safePath(path))
67
+ return invalid('Plugin path must be repository-relative without traversal.');
68
+ if (path.split('/').some(part => part === 'flows-plugin.json'))
69
+ return invalid('Plugin path names the directory holding flows-plugin.json, not the file.');
70
+ return Object.freeze({ host: 'github', owner, repo, ref, path });
71
+ }
72
+ /** The one spelling that is ever persisted. */
73
+ export function canonicalPluginRef(source) {
74
+ return `github:${source.owner}/${source.repo}@${source.sha}${source.path === '' ? '' : `#${source.path}`}`;
75
+ }
76
+ /** Parses a persisted reference; refuses anything but the canonical sha form. */
77
+ export function parseCanonicalPluginRef(value) {
78
+ const parsed = parsePluginSource(value);
79
+ if (!value.startsWith('github:') || !SHA.test(parsed.ref)) {
80
+ throw new PluginError('plugin_source_invalid', `Persisted plugin reference must be github:<owner>/<repo>@<sha>[#<path>], got ${value}.`);
81
+ }
82
+ return Object.freeze({ ...parsed, sha: parsed.ref });
83
+ }
84
+ //# sourceMappingURL=plugin-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-source.js","sourceRoot":"","sources":["../src/plugin-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAuBnD,MAAM,KAAK,GAAG,qCAAqC,CAAC;AACpD,MAAM,IAAI,GAAG,yBAAyB,CAAC;AACvC,MAAM,GAAG,GAAG,qCAAqC,CAAC;AAClD,MAAM,CAAC,MAAM,GAAG,GAAG,gBAAgB,CAAC;AAEpC,SAAS,OAAO,CAAC,OAAe;IAC9B,MAAM,IAAI,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;QAAE,OAAO,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACtE,IAAI,KAAyB,EAAE,IAAwB,EAAE,GAAuB,EAAE,IAAI,GAAG,EAAE,CAAC;IAC5F,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,iDAAiD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,gDAAgD,CAAC,CAAC;QACzE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;SAAM,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YAAC,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;QAAC,CAAC;QAC5F,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,gEAAgE,CAAC,CAAC;QAC7I,MAAM,CAAC,GAAG,yDAAyD,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvF,IAAI,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,+DAA+D,CAAC,CAAC;QACxF,IAAI,CAAC;YACH,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3F,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,OAAO,CAAC,+CAA+C,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;;QAAM,OAAO,OAAO,CAAC,4DAA4D,CAAC,CAAC;IACpF,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACvF,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC,iCAAiC,CAAC,CAAC;IAC/H,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChJ,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,IAAI,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,4DAA4D,CAAC,CAAC;IACjH,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,mBAAmB,CAAC;QAAE,OAAO,OAAO,CAAC,0EAA0E,CAAC,CAAC;IAC3J,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,UAAU,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;AAC7G,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,WAAW,CAAC,uBAAuB,EAAE,gFAAgF,KAAK,GAAG,CAAC,CAAC;IAC3I,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Where a flow-extension plugin's bytes live inside a project:
3
+ * `<project>/.flows/plugins/<name>@sha256:<digest>/`, content-addressed like
4
+ * the bundle cache and never `node_modules`. `manifest.json` in that directory
5
+ * is the payload manifest whose sha256 is the digest, so a directory can be
6
+ * re-verified without the network — and, because the lockfile records the
7
+ * same digest, drift between what was installed and what is on disk is a
8
+ * refusal, not a surprise.
9
+ */
10
+ export declare const PLUGIN_STORE = ".flows/plugins";
11
+ export declare function pluginStoreDirectory(root: string, name: string, digest: string): string;
12
+ export interface StoredPluginFile {
13
+ readonly path: string;
14
+ readonly data: Uint8Array;
15
+ }
16
+ /** Write the files atomically; an existing directory is verified instead of overwritten. */
17
+ export declare function materializePlugin(root: string, name: string, files: readonly StoredPluginFile[]): Promise<{
18
+ directory: string;
19
+ digest: string;
20
+ }>;
21
+ /** Re-hash a materialized plugin and compare with the digest the lockfile recorded. */
22
+ export declare function verifyStoredPlugin(directory: string, expectedDigest: string): Promise<void>;
23
+ /** Re-verify, then return every stored file including the payload `manifest.json`. */
24
+ export declare function readStoredPluginFiles(directory: string, expectedDigest: string): Promise<readonly {
25
+ path: string;
26
+ data: Buffer;
27
+ }[]>;
28
+ /** Drop a materialized plugin directory. Missing is a no-op. */
29
+ export declare function removeStoredPlugin(directory: string): Promise<void>;
30
+ //# sourceMappingURL=plugin-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-store.d.ts","sourceRoot":"","sources":["../src/plugin-store.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAE7C,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvF;AAED,MAAM,WAAW,gBAAgB;IAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE;AAEtF,4FAA4F;AAC5F,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBtJ;AAWD,uFAAuF;AACvF,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBjG;AAED,sFAAsF;AACtF,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAOzI;AAED,gEAAgE;AAChE,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE"}
@@ -0,0 +1,133 @@
1
+ import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { payloadManifest, safePath, sha256 } from './bundle.js';
4
+ import { PluginError } from './plugin-manifest.js';
5
+ /**
6
+ * Where a flow-extension plugin's bytes live inside a project:
7
+ * `<project>/.flows/plugins/<name>@sha256:<digest>/`, content-addressed like
8
+ * the bundle cache and never `node_modules`. `manifest.json` in that directory
9
+ * is the payload manifest whose sha256 is the digest, so a directory can be
10
+ * re-verified without the network — and, because the lockfile records the
11
+ * same digest, drift between what was installed and what is on disk is a
12
+ * refusal, not a surprise.
13
+ */
14
+ export const PLUGIN_STORE = '.flows/plugins';
15
+ export function pluginStoreDirectory(root, name, digest) {
16
+ return join(resolve(root), PLUGIN_STORE, `${name}@sha256:${digest}`);
17
+ }
18
+ /** Write the files atomically; an existing directory is verified instead of overwritten. */
19
+ export async function materializePlugin(root, name, files) {
20
+ const manifest = payloadManifest(files);
21
+ const digest = sha256(manifest);
22
+ const directory = pluginStoreDirectory(root, name, digest);
23
+ let exists = false;
24
+ try {
25
+ await lstat(directory);
26
+ exists = true;
27
+ }
28
+ catch (error) {
29
+ if (error.code !== 'ENOENT')
30
+ throw error;
31
+ }
32
+ if (exists) {
33
+ await verifyStoredPlugin(directory, digest);
34
+ return { directory, digest };
35
+ }
36
+ const parent = dirname(directory);
37
+ await mkdir(parent, { recursive: true });
38
+ const staging = await mkdtemp(join(parent, '.install-'));
39
+ try {
40
+ for (const file of files) {
41
+ if (!safePath(file.path) || file.path === 'manifest.json')
42
+ throw new PluginError('plugin_path_invalid', `${file.path}: invalid plugin path.`);
43
+ await mkdir(dirname(join(staging, file.path)), { recursive: true });
44
+ await writeFile(join(staging, file.path), file.data, { mode: 0o644 });
45
+ }
46
+ await writeFile(join(staging, 'manifest.json'), manifest);
47
+ try {
48
+ await rename(staging, directory);
49
+ }
50
+ catch (error) {
51
+ if (!['EEXIST', 'ENOTEMPTY'].includes(error.code ?? ''))
52
+ throw error;
53
+ await verifyStoredPlugin(directory, digest);
54
+ }
55
+ }
56
+ finally {
57
+ await rm(staging, { recursive: true, force: true });
58
+ }
59
+ return { directory, digest };
60
+ }
61
+ async function regularFile(root, path) {
62
+ const parts = path.split('/');
63
+ for (let i = 1; i <= parts.length; i++) {
64
+ const stat = await lstat(join(root, ...parts.slice(0, i)));
65
+ if (i === parts.length ? !stat.isFile() : !stat.isDirectory())
66
+ throw new PluginError('plugin_source_drift', `${path}: expected a regular file, without symlinks.`);
67
+ }
68
+ return readFile(join(root, path));
69
+ }
70
+ /** Re-hash a materialized plugin and compare with the digest the lockfile recorded. */
71
+ export async function verifyStoredPlugin(directory, expectedDigest) {
72
+ const drift = (message) => { throw new PluginError('plugin_source_drift', `${directory}: ${message}`); };
73
+ let raw;
74
+ try {
75
+ if (!(await lstat(directory)).isDirectory())
76
+ return drift('not a directory');
77
+ raw = (await regularFile(directory, 'manifest.json')).toString('utf8');
78
+ }
79
+ catch (error) {
80
+ return drift(error instanceof PluginError ? error.message : 'manifest.json is missing');
81
+ }
82
+ if (sha256(raw) !== expectedDigest)
83
+ return drift('manifest.json digest differs from the lockfile');
84
+ let entries;
85
+ try {
86
+ entries = JSON.parse(raw);
87
+ if (!Array.isArray(entries))
88
+ throw new Error();
89
+ }
90
+ catch {
91
+ return drift('manifest.json is not a manifest');
92
+ }
93
+ const paths = new Set();
94
+ for (const entry of entries) {
95
+ if (typeof entry?.path !== 'string' || !safePath(entry.path))
96
+ return drift('manifest.json lists an invalid path');
97
+ let data;
98
+ try {
99
+ data = await regularFile(directory, entry.path);
100
+ }
101
+ catch (error) {
102
+ return drift(error instanceof PluginError ? error.message : `${entry.path} is missing`);
103
+ }
104
+ if (data.length !== entry.bytes || sha256(data) !== entry.sha256)
105
+ return drift(`${entry.path} changed since installation`);
106
+ paths.add(entry.path);
107
+ }
108
+ await rejectExtras(directory, '', new Set([...paths, 'manifest.json']), drift);
109
+ }
110
+ /** Re-verify, then return every stored file including the payload `manifest.json`. */
111
+ export async function readStoredPluginFiles(directory, expectedDigest) {
112
+ await verifyStoredPlugin(directory, expectedDigest);
113
+ const manifest = await regularFile(directory, 'manifest.json');
114
+ const entries = JSON.parse(manifest.toString('utf8'));
115
+ const files = [{ path: 'manifest.json', data: manifest }];
116
+ for (const entry of entries)
117
+ files.push({ path: entry.path, data: await regularFile(directory, entry.path) });
118
+ return files;
119
+ }
120
+ /** Drop a materialized plugin directory. Missing is a no-op. */
121
+ export async function removeStoredPlugin(directory) {
122
+ await rm(directory, { recursive: true, force: true });
123
+ }
124
+ async function rejectExtras(root, prefix, paths, drift) {
125
+ for (const entry of await readdir(join(root, prefix), { withFileTypes: true })) {
126
+ const path = prefix + entry.name;
127
+ if (entry.isDirectory() && [...paths].some(file => file.startsWith(`${path}/`)))
128
+ await rejectExtras(root, `${path}/`, paths, drift);
129
+ else if (!entry.isFile() || !paths.has(path))
130
+ drift(`${path}: unlisted file or unsupported file type`);
131
+ }
132
+ }
133
+ //# sourceMappingURL=plugin-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-store.js","sourceRoot":"","sources":["../src/plugin-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ interface Parsed {
2
+ readonly triple: readonly [number, number, number];
3
+ readonly pre?: string;
4
+ }
5
+ export declare function parseVersion(value: string): Parsed | undefined;
6
+ export declare function isVersionRange(value: string): boolean;
7
+ /** Whether `version` satisfies `range`; false for malformed input rather than a throw. */
8
+ export declare function satisfiesRange(version: string, range: string): boolean;
9
+ export {};
10
+ //# sourceMappingURL=semver-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver-range.d.ts","sourceRoot":"","sources":["../src/semver-range.ts"],"names":[],"mappings":"AAYA,UAAU,MAAM;IAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE;AAE9F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM9D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAgCD,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAgBtE"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The small semver subset plugin `compat` ranges may use. Deliberately not a
3
+ * dependency on `semver`: a plugin's compatibility claim has to be checkable by
4
+ * every reader — Cloud, the CLI, a catalog — from one short, obvious rule.
5
+ *
6
+ * Accepted: `*`, `x.y.z`, `^x.y.z`, `~x.y.z`, `>=x.y.z`, `>=x.y.z <a.b.c`.
7
+ * Prerelease tags are ordered lexically after the numeric triple and only
8
+ * compare against a range whose bounds name the same triple.
9
+ */
10
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
11
+ const RANGE = /^(\*|(?:[\^~]|>=)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?: <\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)?)$/;
12
+ export function parseVersion(value) {
13
+ const m = VERSION.exec(value);
14
+ if (!m)
15
+ return undefined;
16
+ const triple = [Number(m[1]), Number(m[2]), Number(m[3])];
17
+ if (triple.some(n => !Number.isSafeInteger(n)))
18
+ return undefined;
19
+ return m[4] === undefined ? { triple } : { triple, pre: m[4] };
20
+ }
21
+ export function isVersionRange(value) {
22
+ return RANGE.test(value);
23
+ }
24
+ function compare(a, b) {
25
+ for (let i = 0; i < 3; i++)
26
+ if (a.triple[i] !== b.triple[i])
27
+ return a.triple[i] - b.triple[i];
28
+ if (a.pre === b.pre)
29
+ return 0;
30
+ if (a.pre === undefined)
31
+ return 1;
32
+ if (b.pre === undefined)
33
+ return -1;
34
+ return comparePrerelease(a.pre, b.pre);
35
+ }
36
+ function comparePrerelease(a, b) {
37
+ const as = a.split('.');
38
+ const bs = b.split('.');
39
+ const n = Math.max(as.length, bs.length);
40
+ for (let i = 0; i < n; i++) {
41
+ const left = as[i];
42
+ const right = bs[i];
43
+ if (left === undefined)
44
+ return -1;
45
+ if (right === undefined)
46
+ return 1;
47
+ const leftNum = /^\d+$/.test(left) ? Number(left) : undefined;
48
+ const rightNum = /^\d+$/.test(right) ? Number(right) : undefined;
49
+ if (leftNum !== undefined && rightNum !== undefined) {
50
+ if (leftNum !== rightNum)
51
+ return leftNum - rightNum;
52
+ continue;
53
+ }
54
+ if (leftNum !== undefined)
55
+ return -1;
56
+ if (rightNum !== undefined)
57
+ return 1;
58
+ if (left !== right)
59
+ return left < right ? -1 : 1;
60
+ }
61
+ return 0;
62
+ }
63
+ /** Whether `version` satisfies `range`; false for malformed input rather than a throw. */
64
+ export function satisfiesRange(version, range) {
65
+ const v = parseVersion(version);
66
+ if (v === undefined || !isVersionRange(range))
67
+ return false;
68
+ if (range === '*')
69
+ return true;
70
+ const [lowerText, upperText] = range.split(' ');
71
+ const operator = /^[\^~]|^>=/.exec(lowerText)?.[0] ?? '';
72
+ const lower = parseVersion(lowerText.slice(operator.length));
73
+ if (compare(v, lower) < 0)
74
+ return false;
75
+ if (operator === '')
76
+ return compare(v, lower) === 0;
77
+ let upper;
78
+ if (upperText !== undefined)
79
+ upper = parseVersion(upperText.slice(1));
80
+ else if (operator === '^') {
81
+ const [major, minor] = lower.triple;
82
+ upper = major > 0 ? { triple: [major + 1, 0, 0] } : minor > 0 ? { triple: [0, minor + 1, 0] } : { triple: [0, 0, lower.triple[2] + 1] };
83
+ }
84
+ else if (operator === '~')
85
+ upper = { triple: [lower.triple[0], lower.triple[1] + 1, 0] };
86
+ return upper === undefined || compare(v, upper) < 0;
87
+ }
88
+ //# sourceMappingURL=semver-range.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver-range.js","sourceRoot":"","sources":["../src/semver-range.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayflows/sdk",
3
- "version": "2.0.24",
3
+ "version": "2.0.26",
4
4
  "description": "TypeScript-first authoring SDK for Relayflows. Compiles specs; speaks the journal protocol.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,9 +47,9 @@
47
47
  "license": "Apache-2.0",
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^1.30.0",
50
- "@relayfile/adapter-core": "0.5.26",
51
- "@relayfile/relay-helpers": "0.4.11",
52
- "@relayflows/surface": "2.0.24",
50
+ "@relayfile/adapter-core": "0.6.2",
51
+ "@relayfile/relay-helpers": "0.4.12",
52
+ "@relayflows/surface": "2.0.26",
53
53
  "@types/js-yaml": "^4.0.9",
54
54
  "ai-hist": "0.4.1",
55
55
  "ajv": "^8.17.1",
@@ -80,5 +80,10 @@
80
80
  "@agent-relay/sdk": {
81
81
  "optional": true
82
82
  }
83
+ },
84
+ "overrides": {
85
+ "@relayflows/surface": {
86
+ "@relayfile/relay-helpers": "$@relayfile/relay-helpers"
87
+ }
83
88
  }
84
89
  }
@@ -44,6 +44,14 @@ import {
44
44
  } from './authored-flow-operation.js';
45
45
  import { AuthoredFlowLifecycle } from './authored-flow-lifecycle.js';
46
46
  import { JournalClient } from './journal-client.js';
47
+ import { PluginError } from './plugin-manifest.js';
48
+ import { createHookEvaluator } from './authored-hooks.js';
49
+ import {
50
+ extensionHandlerForHostedDispatch,
51
+ probeFlowExtension,
52
+ type HostedExtensionDispatch,
53
+ type LoadedFlowExtension,
54
+ } from './flow-extension-loader.js';
47
55
  import type {
48
56
  CompletionReason as ProtocolCompletionReason,
49
57
  RunCompletionReason as ProtocolRunCompletionReason,
@@ -148,6 +156,13 @@ export interface ExecuteAuthoredFlowOptions {
148
156
  readonly localAgentStream?: string;
149
157
  /** Durable kernel root that owns this body's child admission identities. */
150
158
  readonly rootRunId?: string;
159
+ /** Installed flow-extension plugins, in lock order, so `f.hook` can AND-compose them. */
160
+ readonly extensions?: readonly LoadedFlowExtension[];
161
+ /**
162
+ * Server-authenticated integration delivery authority. Never derive this
163
+ * from authored input: direct `/workflows/run` callers control that JSON.
164
+ */
165
+ readonly extensionDispatch?: HostedExtensionDispatch;
151
166
  }
152
167
 
153
168
  export async function executeAuthoredFlow<Input = undefined>(
@@ -171,7 +186,8 @@ export async function executeAuthoredFlow<Input = undefined>(
171
186
  ...(options.onWait !== undefined ? { onWait: options.onWait } : {}),
172
187
  };
173
188
  const definition = getDefinition<Input>(handle);
174
- const headerFields = Object.keys(definition.header).filter(key => key !== 'tools' && key !== 'budget' && key !== 'memory');
189
+ const hostedHandler = extensionHandlerForHostedDispatch(options.extensionDispatch, options.extensions ?? []);
190
+ const headerFields = Object.keys(definition.header).filter(key => key !== 'tools' && key !== 'budget' && key !== 'memory' && key !== 'version' && key !== 'hooks');
175
191
  if (definition.header.tools && Object.keys(definition.header.tools).some(key => !['mcp', ...helperProviders.map(p => p.namespace)].includes(key))) headerFields.push('tools');
176
192
  if (definition.header.tools?.relayfile !== undefined) headerFields.push('tools.relayfile');
177
193
  const helperPreflight = checkSlackHelpers(definition);
@@ -188,6 +204,21 @@ export async function executeAuthoredFlow<Input = undefined>(
188
204
 
189
205
  const checkedMcp = await checkMcpHeader(definition, flowPath);
190
206
  if (!checkedMcp.report.ok) throw new McpPreflightError(checkedMcp.report);
207
+ for (const extension of options.extensions ?? []) {
208
+ if (extension.manifest !== undefined) await probeFlowExtension(extension.manifest);
209
+ }
210
+ if (hostedHandler !== undefined) {
211
+ // A schema-2 entry is ordinary authored JavaScript. Passing it the base
212
+ // context would not constrain direct Node access, f.run, helpers, MCP or
213
+ // agent harnesses to manifest.permissions; those declarations are still
214
+ // explicitly unenforced by gate 8 / #442. Refuse before either body runs.
215
+ // A later isolated runtime may replace this gate only when it can prove
216
+ // the manifest boundary, not merely proxy selected Ctx properties.
217
+ throw new PluginError(
218
+ 'plugin_unsupported',
219
+ `${hostedHandler.extension.name}: hosted extension handlers require enforced manifest permission isolation (gate 8 / #442).`,
220
+ );
221
+ }
191
222
 
192
223
  const budget = new AuthoredBudget(definition.header.budget);
193
224
  if (definition.header.memory?.agent === true) {
@@ -349,6 +380,17 @@ export async function executeAuthoredFlow<Input = undefined>(
349
380
  ));
350
381
  }
351
382
 
383
+ const evaluateHook = createHookEvaluator({
384
+ journal,
385
+ ...(options.rootRunId === undefined ? {} : { rootRunId: options.rootRunId }),
386
+ flowName: definition.name,
387
+ declared: definition.header?.hooks ?? [],
388
+ extensions: options.extensions ?? [],
389
+ peekStep: () => nextStep,
390
+ restoreStep: (step) => { nextStep = step; },
391
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
392
+ });
393
+
352
394
  const context: Ctx = {
353
395
  ...createHelpers(<T>(call: HelperCall): Step<T> => {
354
396
  const verb = `${call.provider}.${call.verb}`;
@@ -477,6 +519,23 @@ export async function executeAuthoredFlow<Input = undefined>(
477
519
  assertOperationAllowed('dispatch', definition.name, requestedCompletion);
478
520
  throw unsupportedVerb('dispatch');
479
521
  },
522
+ hook(name, input) {
523
+ assertOperationAllowed('hook', definition.name, requestedCompletion);
524
+ const id = `hook-${nextStep++}`;
525
+ const snapshot = snapshotJsonValue(input, 'f.hook input');
526
+ return trackStep(authoredSteps, new AuthoredFlowOperation<boolean>(
527
+ id, 'hook',
528
+ () => assertOperationAllowed('hook', definition.name, requestedCompletion),
529
+ async () => {
530
+ const verdict = await evaluateHook(id, name, snapshot, context);
531
+ const record = { hook: name, step: id, verdict: verdict ? 'pass' : 'fail' };
532
+ const literal = `'${JSON.stringify(record).replaceAll("'", "'\\''")}'`;
533
+ await observeStep(id, 'deterministic', () => lowerDeterministic(id, `printf '%s' ${literal}`, false), options.onProgress);
534
+ return verdict;
535
+ },
536
+ lifecycle,
537
+ ));
538
+ },
480
539
  done(reason) {
481
540
  if (!isSurfaceFlowCompletionReason(reason)) {
482
541
  throw new AuthoredFlowExecutionError(
@@ -9,6 +9,9 @@ import {
9
9
  type AuthoredFlowDefinition,
10
10
  type FlowHandle,
11
11
  } from './authored-flow.js';
12
+ import { canonicalize } from './canonical.js';
13
+ import { composeDefinition, loadFlowExtensions, parseHooksExport, type ImportedFlow, type LoadedFlowExtension } from './flow-extension-loader.js';
14
+ import type { RuntimeVersions } from './flow-extension-compat.js';
12
15
 
13
16
  export class AuthoredFlowLoadError extends Error {
14
17
  constructor(message: string, readonly kind: 'invalid_spec' | 'use_not_found' | 'use_invalid' | 'use_cycle' = 'invalid_spec') {
@@ -40,8 +43,20 @@ export interface LoadedAuthoredFlow {
40
43
  readonly getDefinition: GetFlowDefinition;
41
44
  /** Exact Surface package/runtime that owns the handle's WeakMap identity. */
42
45
  readonly surfaceAuthority: SurfaceModuleAuthority;
43
- /** Dependency-first load order, each canonical absolute path appearing once. */
46
+ /**
47
+ * Dependency-first load order, each canonical absolute path appearing once.
48
+ * Flow-extension entries follow the root, so a graph of one node still
49
+ * means "one self-contained source" to the hosted paths that require it.
50
+ */
44
51
  readonly graph: readonly LoadedAuthoredFlowNode[];
52
+ /** Schema-2 flow extensions composed onto the root, in lock order; empty when the project declares none. */
53
+ readonly extensions: readonly LoadedFlowExtension[];
54
+ }
55
+
56
+ export interface LoadAuthoredFlowOptions {
57
+ /** `compose` (default) verifies and appends the project's flow extensions; `none` loads the root alone. */
58
+ readonly extensions?: 'compose' | 'none';
59
+ readonly versions?: RuntimeVersions;
45
60
  }
46
61
 
47
62
  export interface LoadedAuthoredFlowNode {
@@ -53,7 +68,7 @@ export interface LoadedAuthoredFlowNode {
53
68
  }
54
69
 
55
70
  /** Import and validate a direct-run module without executing its authored body. */
56
- export async function loadAuthoredFlow(path: string): Promise<LoadedAuthoredFlow> {
71
+ export async function loadAuthoredFlow(path: string, options: LoadAuthoredFlowOptions = {}): Promise<LoadedAuthoredFlow> {
57
72
  const loaded = new Map<string, LoadedAuthoredFlowNode>();
58
73
  const visiting = new Set<string>();
59
74
  async function visit(sourcePath: string, isRoot = false): Promise<LoadedAuthoredFlowNode> {
@@ -98,11 +113,39 @@ export async function loadAuthoredFlow(path: string): Promise<LoadedAuthoredFlow
98
113
  }
99
114
  }
100
115
  const root = await visit(resolve(path), true);
101
- return Object.freeze({ sourcePath: root.path, handle: root.handle, getDefinition: root.getDefinition,
102
- surfaceAuthority: root.surfaceAuthority, graph: Object.freeze([...loaded.values()]) });
116
+ const graph = [...loaded.values()];
117
+ if (options.extensions === 'none') {
118
+ return Object.freeze({ sourcePath: root.path, handle: root.handle, getDefinition: root.getDefinition,
119
+ surfaceAuthority: root.surfaceAuthority, graph: Object.freeze(graph), extensions: Object.freeze([]) });
120
+ }
121
+ // Extensions are verified (lock, digest, manifest hash, compat) before their
122
+ // entry is imported, and are composed after the root in lock order. The
123
+ // composed definition is served for the root handle only; every other
124
+ // handle keeps the surface copy's own answer.
125
+ const baseDefinition = root.getDefinition(root.handle);
126
+ const extensions = await loadFlowExtensions(root.path, { definition: baseDefinition, surfaceAuthority: root.surfaceAuthority }, {
127
+ importFlow: importAuthoredFlow,
128
+ sameAuthority: (a, b) => canonicalize(a) === canonicalize(b),
129
+ ...(options.versions === undefined ? {} : { versions: options.versions }),
130
+ });
131
+ if (extensions.length === 0) {
132
+ return Object.freeze({ sourcePath: root.path, handle: root.handle, getDefinition: root.getDefinition,
133
+ surfaceAuthority: root.surfaceAuthority, graph: Object.freeze(graph), extensions });
134
+ }
135
+ const composed = composeDefinition(baseDefinition, extensions);
136
+ const getDefinition: GetFlowDefinition = <Input = unknown>(handle: FlowHandle) =>
137
+ (handle === root.handle ? composed : root.getDefinition(handle)) as AuthoredFlowDefinition<Input>;
138
+ for (const extension of extensions) {
139
+ // The node carries the accessor the entry's own surface copy handed back:
140
+ // the root's accessor answers for the root's WeakMap only.
141
+ graph.push(Object.freeze({ path: extension.entryPath, handle: extension.handle, getDefinition: extension.getDefinition,
142
+ surfaceAuthority: root.surfaceAuthority, use: Object.freeze([]) }));
143
+ }
144
+ return Object.freeze({ sourcePath: root.path, handle: root.handle, getDefinition,
145
+ surfaceAuthority: root.surfaceAuthority, graph: Object.freeze(graph), extensions });
103
146
  }
104
147
 
105
- async function importAuthoredFlow(path: string): Promise<Pick<LoadedAuthoredFlow, 'handle' | 'getDefinition' | 'surfaceAuthority'>> {
148
+ async function importAuthoredFlow(path: string): Promise<ImportedFlow<SurfaceModuleAuthority>> {
106
149
  const absolutePath = resolve(path);
107
150
  try {
108
151
  accessSync(absolutePath, constants.R_OK);
@@ -128,7 +171,7 @@ async function importAuthoredFlow(path: string): Promise<Pick<LoadedAuthoredFlow
128
171
  `Flow "${path}" must default-export flow(...): ${errorMessage(error)}`,
129
172
  );
130
173
  }
131
- return { handle, getDefinition, surfaceAuthority };
174
+ return { handle, getDefinition, surfaceAuthority, hooks: parseHooksExport(authoredModule, path) };
132
175
  }
133
176
 
134
177
  /**