@pikku/core 0.12.63 → 0.12.64

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 (198) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/dev/hot-reload.js +13 -55
  3. package/dist/dev/module-runner.d.ts +14 -0
  4. package/dist/dev/module-runner.js +59 -0
  5. package/dist/dev/reload-meta.d.ts +13 -0
  6. package/dist/dev/reload-meta.js +22 -0
  7. package/dist/errors/errors.d.ts +12 -0
  8. package/dist/errors/errors.js +19 -0
  9. package/dist/function/function-runner.d.ts +5 -5
  10. package/dist/function/function-runner.js +39 -111
  11. package/dist/function/functions.types.d.ts +17 -7
  12. package/dist/index.d.ts +5 -1
  13. package/dist/index.js +3 -1
  14. package/dist/middleware/auth-apikey.d.ts +1 -0
  15. package/dist/middleware/auth-bearer.d.ts +1 -0
  16. package/dist/middleware/auth-cookie.d.ts +1 -0
  17. package/dist/middleware/cors.d.ts +1 -0
  18. package/dist/middleware/index.d.ts +1 -1
  19. package/dist/middleware/index.js +1 -1
  20. package/dist/middleware/remote-auth.d.ts +1 -0
  21. package/dist/middleware/telemetry.d.ts +2 -0
  22. package/dist/middleware/timeout.d.ts +1 -0
  23. package/dist/permissions.d.ts +32 -25
  24. package/dist/permissions.js +104 -185
  25. package/dist/pikku-state.js +0 -2
  26. package/dist/scopes.d.ts +17 -0
  27. package/dist/scopes.js +59 -0
  28. package/dist/services/ai-embedding-service.d.ts +31 -0
  29. package/dist/services/ai-embedding-service.js +1 -0
  30. package/dist/services/credential-wire-service.d.ts +3 -1
  31. package/dist/services/credential-wire-service.js +10 -4
  32. package/dist/services/index.d.ts +4 -0
  33. package/dist/services/index.js +2 -0
  34. package/dist/services/meta-service.d.ts +15 -2
  35. package/dist/services/meta-service.js +51 -1
  36. package/dist/services/queue-webhook-service.d.ts +55 -0
  37. package/dist/services/queue-webhook-service.js +136 -0
  38. package/dist/services/scope-service.d.ts +67 -0
  39. package/dist/services/scope-service.js +1 -0
  40. package/dist/services/typed-secret-service.d.ts +8 -0
  41. package/dist/services/typed-secret-service.js +39 -4
  42. package/dist/services/webhook-service.d.ts +140 -0
  43. package/dist/services/webhook-service.js +44 -0
  44. package/dist/services/workflow-service.d.ts +7 -0
  45. package/dist/types/core.types.d.ts +34 -6
  46. package/dist/types/state.types.d.ts +20 -2
  47. package/dist/utils/hmac.d.ts +16 -0
  48. package/dist/utils/hmac.js +26 -0
  49. package/dist/utils/safe-fetch.d.ts +51 -0
  50. package/dist/utils/safe-fetch.js +192 -0
  51. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
  52. package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
  53. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +41 -1
  54. package/dist/wirings/ai-agent/ai-agent-prepare.js +146 -6
  55. package/dist/wirings/ai-agent/ai-agent-runner.js +6 -1
  56. package/dist/wirings/ai-agent/ai-agent-stream.js +61 -10
  57. package/dist/wirings/ai-agent/ai-agent.types.d.ts +27 -1
  58. package/dist/wirings/ai-agent/index.d.ts +1 -0
  59. package/dist/wirings/ai-agent/index.js +1 -0
  60. package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
  61. package/dist/wirings/ai-agent/voice-input.js +2 -38
  62. package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
  63. package/dist/wirings/channel/channel-common.js +0 -1
  64. package/dist/wirings/channel/channel-handler.js +1 -4
  65. package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
  66. package/dist/wirings/channel/channel-middleware-runner.js +34 -19
  67. package/dist/wirings/channel/channel.types.d.ts +2 -6
  68. package/dist/wirings/cli/cli-runner.js +0 -2
  69. package/dist/wirings/cli/cli.types.d.ts +1 -2
  70. package/dist/wirings/http/http-routes.js +0 -3
  71. package/dist/wirings/http/http-runner.d.ts +9 -29
  72. package/dist/wirings/http/http-runner.js +9 -34
  73. package/dist/wirings/http/http.types.d.ts +1 -9
  74. package/dist/wirings/mcp/mcp-runner.js +0 -2
  75. package/dist/wirings/mcp/mcp.types.d.ts +5 -11
  76. package/dist/wirings/oauth2/index.d.ts +0 -3
  77. package/dist/wirings/oauth2/index.js +1 -2
  78. package/dist/wirings/rpc/addon-runner.d.ts +28 -0
  79. package/dist/wirings/rpc/addon-runner.js +173 -0
  80. package/dist/wirings/rpc/index.d.ts +5 -1
  81. package/dist/wirings/rpc/index.js +3 -1
  82. package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
  83. package/dist/wirings/rpc/remote-addon-auth.js +43 -0
  84. package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
  85. package/dist/wirings/rpc/rpc-runner.js +109 -3
  86. package/dist/wirings/rpc/rpc-types.d.ts +15 -1
  87. package/dist/wirings/rpc/wire-addon.js +9 -0
  88. package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
  89. package/dist/wirings/rpc/wire-remote-addon.js +19 -0
  90. package/dist/wirings/scope/index.d.ts +3 -0
  91. package/dist/wirings/scope/index.js +2 -0
  92. package/dist/wirings/scope/scope.types.d.ts +39 -0
  93. package/dist/wirings/scope/scope.types.js +1 -0
  94. package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
  95. package/dist/wirings/scope/validate-scope-definitions.js +76 -0
  96. package/dist/wirings/scope/wire-scope.d.ts +33 -0
  97. package/dist/wirings/scope/wire-scope.js +32 -0
  98. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  99. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +53 -1
  100. package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
  101. package/dist/wirings/workflow/graph/graph-runner.js +3 -0
  102. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
  103. package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
  104. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
  105. package/dist/wirings/workflow/index.d.ts +2 -2
  106. package/dist/wirings/workflow/index.js +1 -1
  107. package/dist/wirings/workflow/pikku-workflow-service.d.ts +51 -1
  108. package/dist/wirings/workflow/pikku-workflow-service.js +171 -0
  109. package/dist/wirings/workflow/workflow.types.d.ts +1 -1
  110. package/package.json +5 -1
  111. package/run-tests.sh +1 -0
  112. package/src/dev/hot-reload.ts +13 -68
  113. package/src/dev/module-runner.test.ts +169 -0
  114. package/src/dev/module-runner.ts +103 -0
  115. package/src/dev/reload-meta.test.ts +31 -2
  116. package/src/dev/reload-meta.ts +26 -0
  117. package/src/errors/errors.ts +24 -0
  118. package/src/function/function-runner.test.ts +276 -142
  119. package/src/function/function-runner.ts +52 -145
  120. package/src/function/functions.types.ts +28 -38
  121. package/src/index.ts +8 -6
  122. package/src/middleware/index.ts +1 -5
  123. package/src/permissions.test.ts +153 -372
  124. package/src/permissions.ts +139 -259
  125. package/src/pikku-state.ts +0 -2
  126. package/src/scopes.test.ts +167 -0
  127. package/src/scopes.ts +69 -0
  128. package/src/services/ai-embedding-service.ts +31 -0
  129. package/src/services/credential-wire-service.ts +10 -4
  130. package/src/services/index.ts +20 -0
  131. package/src/services/meta-service.ts +68 -3
  132. package/src/services/queue-webhook-service.test.ts +408 -0
  133. package/src/services/queue-webhook-service.ts +182 -0
  134. package/src/services/scope-service.ts +75 -0
  135. package/src/services/typed-secret-service.test.ts +35 -0
  136. package/src/services/typed-secret-service.ts +39 -4
  137. package/src/services/webhook-service.ts +180 -0
  138. package/src/services/workflow-service.ts +7 -0
  139. package/src/types/core.types.ts +42 -14
  140. package/src/types/state.types.ts +23 -3
  141. package/src/utils/hmac.ts +27 -0
  142. package/src/utils/safe-fetch.test.ts +373 -0
  143. package/src/utils/safe-fetch.ts +213 -0
  144. package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
  145. package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
  146. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +308 -0
  147. package/src/wirings/ai-agent/ai-agent-prepare.ts +192 -8
  148. package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
  149. package/src/wirings/ai-agent/ai-agent-runner.ts +21 -0
  150. package/src/wirings/ai-agent/ai-agent-stream.test.ts +198 -10
  151. package/src/wirings/ai-agent/ai-agent-stream.ts +84 -10
  152. package/src/wirings/ai-agent/ai-agent.types.ts +28 -0
  153. package/src/wirings/ai-agent/index.ts +1 -0
  154. package/src/wirings/ai-agent/voice-input.ts +6 -35
  155. package/src/wirings/channel/channel-common.ts +0 -1
  156. package/src/wirings/channel/channel-handler.ts +1 -9
  157. package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
  158. package/src/wirings/channel/channel-middleware-runner.ts +39 -27
  159. package/src/wirings/channel/channel.types.ts +0 -6
  160. package/src/wirings/cli/cli-runner.ts +0 -2
  161. package/src/wirings/cli/cli.types.ts +0 -2
  162. package/src/wirings/http/http-routes.ts +0 -3
  163. package/src/wirings/http/http-runner.test.ts +1 -11
  164. package/src/wirings/http/http-runner.ts +16 -39
  165. package/src/wirings/http/http.types.ts +1 -10
  166. package/src/wirings/mcp/mcp-runner.ts +0 -2
  167. package/src/wirings/mcp/mcp.types.ts +3 -11
  168. package/src/wirings/oauth2/index.ts +0 -3
  169. package/src/wirings/rpc/addon-runner.ts +233 -0
  170. package/src/wirings/rpc/index.ts +17 -1
  171. package/src/wirings/rpc/remote-addon-auth.ts +69 -0
  172. package/src/wirings/rpc/rpc-runner.test.ts +319 -16
  173. package/src/wirings/rpc/rpc-runner.ts +155 -3
  174. package/src/wirings/rpc/rpc-types.ts +16 -5
  175. package/src/wirings/rpc/wire-addon.test.ts +6 -3
  176. package/src/wirings/rpc/wire-addon.ts +9 -0
  177. package/src/wirings/rpc/wire-remote-addon.ts +57 -0
  178. package/src/wirings/scope/index.ts +14 -0
  179. package/src/wirings/scope/scope.test.ts +135 -0
  180. package/src/wirings/scope/scope.types.ts +44 -0
  181. package/src/wirings/scope/validate-scope-definitions.ts +110 -0
  182. package/src/wirings/scope/wire-scope.ts +34 -0
  183. package/src/wirings/workflow/dsl/index.ts +4 -0
  184. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +61 -0
  185. package/src/wirings/workflow/graph/graph-node.ts +2 -0
  186. package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
  187. package/src/wirings/workflow/graph/graph-runner.ts +3 -0
  188. package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
  189. package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
  190. package/src/wirings/workflow/index.ts +5 -0
  191. package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
  192. package/src/wirings/workflow/pikku-workflow-service.ts +236 -0
  193. package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
  194. package/src/wirings/workflow/workflow.types.ts +4 -0
  195. package/tsconfig.tsbuildinfo +1 -1
  196. package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
  197. package/src/wirings/oauth2/oauth2-client.ts +0 -331
  198. package/src/wirings/oauth2/oauth2-routes.ts +0 -234
@@ -0,0 +1,103 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname, resolve } from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { compileFunction } from 'node:vm'
6
+ import type { transformSync as EsbuildTransformSync } from 'esbuild'
7
+
8
+ // Evictable module runner for hot-reload.
9
+ //
10
+ // Re-importing a changed user file through the native ESM loader (a fresh
11
+ // `data:` URL on Node, a fresh temp-file path on Bun) is unbounded: the loader
12
+ // keeps a `Map<url, moduleRecord>` for the realm lifetime with no eviction API,
13
+ // so every reload permanently leaks a module record and the dev server
14
+ // eventually OOMs (worse on Bun). Instead we own the registry: transpile the
15
+ // source to CJS (esbuild), run it as a plain function via `vm.compileFunction`
16
+ // — which produces a garbage-collectable function rather than a permanent
17
+ // loader entry — and store the resulting exports under a STABLE path key. A
18
+ // reload overwrites that one slot, so the previous module becomes unreachable
19
+ // and is collected. `import`s inside the user file are delegated to
20
+ // `createRequire`, whose resolution matches the native loader AND returns the
21
+ // same live singletons (Node/Bun share the require/import module cache), so
22
+ // top-level side effects (wireHTTP et al) mutate the same running services.
23
+ type EsbuildTransform = typeof EsbuildTransformSync
24
+
25
+ let transformSync: EsbuildTransform | undefined
26
+
27
+ const loadTransform = async (): Promise<EsbuildTransform> => {
28
+ if (transformSync) return transformSync
29
+ // esbuild is a dev-only dependency, hoisted from @pikku/cli at runtime — the
30
+ // same lazy pattern the reloader previously used for tsx. It is never loaded
31
+ // in production runtimes (the `./dev` export is dev-only).
32
+ const esbuild = await import('esbuild')
33
+ transformSync = esbuild.transformSync
34
+ return transformSync
35
+ }
36
+
37
+ export interface PikkuModuleRunner {
38
+ /** Run a user module by absolute path, returning its exports. Repeated runs
39
+ * of the same path overwrite a single registry slot (the previous module is
40
+ * collected). Returns `null` if transpile/compile/evaluation fails, so the
41
+ * caller can keep the previously-loaded code. */
42
+ run: (absPath: string) => Promise<Record<string, unknown> | null>
43
+ /** Drop a single module from the registry. */
44
+ evict: (absPath: string) => void
45
+ /** Drop every module from the registry. */
46
+ clear: () => void
47
+ /** Number of modules currently held. */
48
+ readonly size: number
49
+ }
50
+
51
+ export const createModuleRunner = (): PikkuModuleRunner => {
52
+ const registry = new Map<string, Record<string, unknown>>()
53
+
54
+ const run = async (
55
+ filePath: string
56
+ ): Promise<Record<string, unknown> | null> => {
57
+ const absPath = resolve(filePath)
58
+ try {
59
+ const transform = await loadTransform()
60
+ const source = await readFile(absPath, 'utf-8')
61
+ // No sourcemap: an inline sourcemap embeds a base64 copy of the source
62
+ // that the engine retains per compile, reintroducing linear growth on
63
+ // Node — the exact leak this runner exists to remove. `filename` still
64
+ // anchors stack traces to the user file.
65
+ const { code } = transform(source, {
66
+ loader: absPath.endsWith('.ts') ? 'ts' : 'js',
67
+ format: 'cjs',
68
+ sourcefile: absPath,
69
+ })
70
+
71
+ const fn = compileFunction(
72
+ code,
73
+ ['require', 'exports', 'module', '__filename', '__dirname'],
74
+ { filename: absPath }
75
+ )
76
+
77
+ const require = createRequire(pathToFileURL(absPath))
78
+ const moduleObj: { exports: Record<string, unknown> } = { exports: {} }
79
+ fn(require, moduleObj.exports, moduleObj, absPath, dirname(absPath))
80
+
81
+ registry.set(absPath, moduleObj.exports)
82
+ return moduleObj.exports
83
+ } catch {
84
+ // Transpile/compile/evaluate failure (a bad edit, or the one known
85
+ // limitation — a file using top-level `await`, which can't be emitted in
86
+ // `cjs` form). Return null so the caller keeps the previously-loaded code.
87
+ return null
88
+ }
89
+ }
90
+
91
+ return {
92
+ run,
93
+ evict: (filePath: string) => {
94
+ registry.delete(resolve(filePath))
95
+ },
96
+ clear: () => {
97
+ registry.clear()
98
+ },
99
+ get size() {
100
+ return registry.size
101
+ },
102
+ }
103
+ }
@@ -6,7 +6,8 @@ import { tmpdir } from 'node:os'
6
6
 
7
7
  import { pikkuState, resetPikkuState } from '../pikku-state.js'
8
8
  import { getSchema } from '../schema.js'
9
- import { reloadGeneratedMeta } from './reload-meta.js'
9
+ import { wireAddon } from '../wirings/rpc/wire-addon.js'
10
+ import { reloadGeneratedMeta, reconcileAddonRegistry } from './reload-meta.js'
10
11
 
11
12
  const createMockLogger = () => {
12
13
  const logs: Array<{ level: string; message: string }> = []
@@ -96,7 +97,9 @@ describe('reloadGeneratedMeta', { concurrency: false }, () => {
96
97
  )
97
98
  await writeFile(
98
99
  join(tmpDir, 'queue/pikku-queue-workers-wirings-meta.gen.json'),
99
- JSON.stringify({ userQueue: { pikkuFuncId: 'userFunc', name: 'userQueue' } })
100
+ JSON.stringify({
101
+ userQueue: { pikkuFuncId: 'userFunc', name: 'userQueue' },
102
+ })
100
103
  )
101
104
 
102
105
  await reloadGeneratedMeta({
@@ -152,3 +155,29 @@ describe('reloadGeneratedMeta', { concurrency: false }, () => {
152
155
  assert.deepEqual(errors, [])
153
156
  })
154
157
  })
158
+
159
+ describe('reconcileAddonRegistry', { concurrency: false }, () => {
160
+ beforeEach(() => resetPikkuState())
161
+
162
+ test('drops addons the fresh generation no longer declares', () => {
163
+ wireAddon({ name: 'gmail', package: '@pikku/addon-gmail' })
164
+ wireAddon({ name: 'mandrill-e2e', package: '@pikku/addon-mandrill' })
165
+
166
+ // gmail is still wired in source; mandrill-e2e's *.addon.ts was deleted.
167
+ reconcileAddonRegistry(['gmail'])
168
+
169
+ const addons = pikkuState(null, 'addons', 'packages')
170
+ assert.ok(addons.has('gmail'), 'still-declared addon is kept')
171
+ assert.ok(!addons.has('mandrill-e2e'), 'deleted addon is pruned')
172
+ })
173
+
174
+ test('keeps every addon when all are still declared', () => {
175
+ wireAddon({ name: 'gmail', package: '@pikku/addon-gmail' })
176
+ wireAddon({ name: 'mailgun', package: '@pikku/addon-mailgun' })
177
+
178
+ reconcileAddonRegistry(['gmail', 'mailgun'])
179
+
180
+ const addons = pikkuState(null, 'addons', 'packages')
181
+ assert.equal(addons.size, 2)
182
+ })
183
+ })
@@ -136,3 +136,29 @@ export async function reloadGeneratedMeta(
136
136
  )
137
137
  }
138
138
  }
139
+
140
+ /**
141
+ * Prunes the live addon registry down to what the current source still declares.
142
+ *
143
+ * Dev hot-reload re-imports only changed/added files, so a deleted `*.addon.ts`
144
+ * leaves its `wireAddon` entry stranded in `pikkuState(null,'addons','packages')`
145
+ * until a full restart — the reason `getInstalledAddons` reports removed addons.
146
+ * The Map holds definitions (namespace → package config), not runtime state, so
147
+ * dropping any namespace the fresh inspection no longer declares is safe.
148
+ *
149
+ * `declaredNamespaces` is the set of `wireAddon` names from the just-regenerated
150
+ * inspection (`inspectorState.rpc.wireAddonDeclarations.keys()`).
151
+ */
152
+ export function reconcileAddonRegistry(
153
+ declaredNamespaces: Iterable<string>,
154
+ logger?: Logger
155
+ ): void {
156
+ const declared = new Set(declaredNamespaces)
157
+ const addons = pikkuState(null, 'addons', 'packages')
158
+ for (const namespace of [...addons.keys()]) {
159
+ if (!declared.has(namespace)) {
160
+ addons.delete(namespace)
161
+ logger?.info(`• Removed unwired addon "${namespace}"`)
162
+ }
163
+ }
164
+ }
@@ -120,6 +120,30 @@ addError(MissingCredentialError, {
120
120
  message: 'A required credential is not configured.',
121
121
  })
122
122
 
123
+ /**
124
+ * The session does not hold a scope required by the function. The payload
125
+ * names the missing scope so the client can surface what to request.
126
+ * @group Error
127
+ */
128
+ export class MissingScopeError extends PikkuError {
129
+ public payload: {
130
+ error: 'missing_scope'
131
+ scope: string
132
+ }
133
+
134
+ constructor(scope: string) {
135
+ super(`Missing required scope: ${scope}`)
136
+ this.payload = {
137
+ error: 'missing_scope',
138
+ scope,
139
+ }
140
+ }
141
+ }
142
+ addError(MissingScopeError, {
143
+ status: 403,
144
+ message: 'The session does not hold a scope required by this function.',
145
+ })
146
+
123
147
  /**
124
148
  * The session is readonly and cannot access a non-readonly function.
125
149
  * @group Error