@pikku/core 0.12.63 → 0.12.66

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 (225) hide show
  1. package/CHANGELOG.md +368 -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 +42 -27
  24. package/dist/permissions.js +101 -203
  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/testing/service-tests.js +37 -0
  46. package/dist/types/core.types.d.ts +34 -6
  47. package/dist/types/state.types.d.ts +20 -2
  48. package/dist/utils/hmac.d.ts +16 -0
  49. package/dist/utils/hmac.js +26 -0
  50. package/dist/utils/safe-fetch.d.ts +51 -0
  51. package/dist/utils/safe-fetch.js +192 -0
  52. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
  53. package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
  54. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +105 -1
  55. package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
  56. package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
  57. package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
  58. package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
  59. package/dist/wirings/ai-agent/index.d.ts +2 -1
  60. package/dist/wirings/ai-agent/index.js +2 -1
  61. package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
  62. package/dist/wirings/ai-agent/voice-input.js +5 -41
  63. package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
  64. package/dist/wirings/channel/channel-common.js +0 -1
  65. package/dist/wirings/channel/channel-handler.js +1 -4
  66. package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
  67. package/dist/wirings/channel/channel-middleware-runner.js +34 -19
  68. package/dist/wirings/channel/channel.types.d.ts +2 -6
  69. package/dist/wirings/cli/cli-runner.js +3 -2
  70. package/dist/wirings/cli/cli.types.d.ts +1 -2
  71. package/dist/wirings/cli/command-parser.d.ts +2 -0
  72. package/dist/wirings/cli/command-parser.js +59 -2
  73. package/dist/wirings/credential/credential.types.d.ts +14 -0
  74. package/dist/wirings/credential/validate-credential-definitions.js +1 -0
  75. package/dist/wirings/gateway/gateway-runner.js +100 -50
  76. package/dist/wirings/gateway/gateway.types.d.ts +8 -5
  77. package/dist/wirings/http/http-routes.js +0 -3
  78. package/dist/wirings/http/http-runner.d.ts +9 -29
  79. package/dist/wirings/http/http-runner.js +9 -34
  80. package/dist/wirings/http/http.types.d.ts +4 -12
  81. package/dist/wirings/mcp/mcp-runner.js +0 -2
  82. package/dist/wirings/mcp/mcp.types.d.ts +5 -11
  83. package/dist/wirings/oauth2/index.d.ts +0 -3
  84. package/dist/wirings/oauth2/index.js +1 -2
  85. package/dist/wirings/rpc/addon-runner.d.ts +28 -0
  86. package/dist/wirings/rpc/addon-runner.js +173 -0
  87. package/dist/wirings/rpc/index.d.ts +5 -1
  88. package/dist/wirings/rpc/index.js +3 -1
  89. package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
  90. package/dist/wirings/rpc/remote-addon-auth.js +43 -0
  91. package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
  92. package/dist/wirings/rpc/rpc-runner.js +109 -3
  93. package/dist/wirings/rpc/rpc-types.d.ts +15 -1
  94. package/dist/wirings/rpc/wire-addon.js +9 -0
  95. package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
  96. package/dist/wirings/rpc/wire-remote-addon.js +19 -0
  97. package/dist/wirings/scope/index.d.ts +3 -0
  98. package/dist/wirings/scope/index.js +2 -0
  99. package/dist/wirings/scope/scope.types.d.ts +39 -0
  100. package/dist/wirings/scope/scope.types.js +1 -0
  101. package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
  102. package/dist/wirings/scope/validate-scope-definitions.js +76 -0
  103. package/dist/wirings/scope/wire-scope.d.ts +33 -0
  104. package/dist/wirings/scope/wire-scope.js +32 -0
  105. package/dist/wirings/secret/secret.types.d.ts +14 -0
  106. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  107. package/dist/wirings/variable/validate-variable-definitions.js +2 -0
  108. package/dist/wirings/variable/variable.types.d.ts +14 -0
  109. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  110. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
  111. package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
  112. package/dist/wirings/workflow/graph/graph-runner.js +3 -0
  113. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
  114. package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
  115. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
  116. package/dist/wirings/workflow/index.d.ts +2 -2
  117. package/dist/wirings/workflow/index.js +1 -1
  118. package/dist/wirings/workflow/pikku-workflow-service.d.ts +59 -1
  119. package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
  120. package/dist/wirings/workflow/workflow.types.d.ts +1 -3
  121. package/package.json +6 -1
  122. package/run-tests.sh +1 -0
  123. package/src/dev/hot-reload.ts +13 -68
  124. package/src/dev/module-runner.test.ts +169 -0
  125. package/src/dev/module-runner.ts +103 -0
  126. package/src/dev/reload-meta.test.ts +31 -2
  127. package/src/dev/reload-meta.ts +26 -0
  128. package/src/errors/errors.ts +24 -0
  129. package/src/function/function-runner.test.ts +276 -142
  130. package/src/function/function-runner.ts +52 -145
  131. package/src/function/functions.types.ts +28 -38
  132. package/src/index.ts +8 -6
  133. package/src/middleware/index.ts +1 -5
  134. package/src/permissions.test.ts +160 -373
  135. package/src/permissions.ts +137 -279
  136. package/src/pikku-state.ts +0 -2
  137. package/src/scopes.test.ts +167 -0
  138. package/src/scopes.ts +69 -0
  139. package/src/services/ai-embedding-service.ts +31 -0
  140. package/src/services/credential-wire-service.ts +10 -4
  141. package/src/services/index.ts +20 -0
  142. package/src/services/meta-service.ts +68 -3
  143. package/src/services/queue-webhook-service.test.ts +408 -0
  144. package/src/services/queue-webhook-service.ts +182 -0
  145. package/src/services/scope-service.ts +75 -0
  146. package/src/services/typed-secret-service.test.ts +35 -0
  147. package/src/services/typed-secret-service.ts +39 -4
  148. package/src/services/webhook-service.ts +180 -0
  149. package/src/services/workflow-service.ts +7 -0
  150. package/src/testing/service-tests.ts +49 -0
  151. package/src/types/core.types.ts +42 -14
  152. package/src/types/state.types.ts +23 -3
  153. package/src/utils/hmac.ts +27 -0
  154. package/src/utils/safe-fetch.test.ts +373 -0
  155. package/src/utils/safe-fetch.ts +213 -0
  156. package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
  157. package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
  158. package/src/wirings/ai-agent/ai-agent-authorization.test.ts +204 -0
  159. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
  160. package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
  161. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
  162. package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
  163. package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
  164. package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
  165. package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
  166. package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
  167. package/src/wirings/ai-agent/index.ts +5 -0
  168. package/src/wirings/ai-agent/voice-input.test.ts +90 -0
  169. package/src/wirings/ai-agent/voice-input.ts +9 -40
  170. package/src/wirings/channel/channel-common.ts +0 -1
  171. package/src/wirings/channel/channel-handler.ts +1 -9
  172. package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
  173. package/src/wirings/channel/channel-middleware-runner.ts +39 -27
  174. package/src/wirings/channel/channel.types.ts +0 -6
  175. package/src/wirings/cli/cli-runner.ts +4 -2
  176. package/src/wirings/cli/cli.types.ts +0 -2
  177. package/src/wirings/cli/command-parser.test.ts +130 -0
  178. package/src/wirings/cli/command-parser.ts +80 -2
  179. package/src/wirings/credential/credential.types.ts +14 -0
  180. package/src/wirings/credential/validate-credential-definitions.ts +1 -0
  181. package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
  182. package/src/wirings/gateway/gateway-runner.ts +114 -68
  183. package/src/wirings/gateway/gateway.types.ts +7 -9
  184. package/src/wirings/http/http-routes.ts +0 -3
  185. package/src/wirings/http/http-runner.test.ts +1 -11
  186. package/src/wirings/http/http-runner.ts +16 -39
  187. package/src/wirings/http/http.types.ts +7 -14
  188. package/src/wirings/mcp/mcp-runner.ts +0 -2
  189. package/src/wirings/mcp/mcp.types.ts +3 -11
  190. package/src/wirings/oauth2/index.ts +0 -3
  191. package/src/wirings/rpc/addon-runner.ts +233 -0
  192. package/src/wirings/rpc/index.ts +17 -1
  193. package/src/wirings/rpc/remote-addon-auth.ts +69 -0
  194. package/src/wirings/rpc/rpc-runner.test.ts +319 -16
  195. package/src/wirings/rpc/rpc-runner.ts +155 -3
  196. package/src/wirings/rpc/rpc-types.ts +16 -5
  197. package/src/wirings/rpc/wire-addon.test.ts +6 -3
  198. package/src/wirings/rpc/wire-addon.ts +9 -0
  199. package/src/wirings/rpc/wire-remote-addon.ts +57 -0
  200. package/src/wirings/scope/index.ts +14 -0
  201. package/src/wirings/scope/scope.test.ts +135 -0
  202. package/src/wirings/scope/scope.types.ts +44 -0
  203. package/src/wirings/scope/validate-scope-definitions.ts +110 -0
  204. package/src/wirings/scope/wire-scope.ts +34 -0
  205. package/src/wirings/secret/secret.types.ts +14 -0
  206. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  207. package/src/wirings/variable/validate-variable-definitions.ts +2 -0
  208. package/src/wirings/variable/variable.types.ts +14 -0
  209. package/src/wirings/workflow/dsl/index.ts +4 -0
  210. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
  211. package/src/wirings/workflow/graph/graph-node.ts +2 -0
  212. package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
  213. package/src/wirings/workflow/graph/graph-runner.ts +3 -0
  214. package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
  215. package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
  216. package/src/wirings/workflow/index.ts +5 -0
  217. package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
  218. package/src/wirings/workflow/pikku-workflow-service.ts +272 -0
  219. package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
  220. package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
  221. package/src/wirings/workflow/workflow.types.ts +4 -2
  222. package/tsconfig.tsbuildinfo +1 -1
  223. package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
  224. package/src/wirings/oauth2/oauth2-client.ts +0 -331
  225. package/src/wirings/oauth2/oauth2-routes.ts +0 -234
@@ -0,0 +1,167 @@
1
+ import { describe, test } from 'node:test'
2
+ import * as assert from 'node:assert'
3
+ import { verifyScopes } from './scopes.js'
4
+ import { MissingScopeError } from './errors/errors.js'
5
+ import type { CoreUserSession } from './types/core.types.js'
6
+
7
+ const session = (scopes?: string[]): CoreUserSession =>
8
+ ({ userId: 'test-user', scopes }) as CoreUserSession
9
+
10
+ describe('verifyScopes', () => {
11
+ test('passes on an exact match', () => {
12
+ verifyScopes(['invoices:create'], session(['invoices:create']))
13
+ })
14
+
15
+ test('passes when no scopes are required', () => {
16
+ verifyScopes([], session())
17
+ })
18
+
19
+ test('throws when the session holds none', () => {
20
+ assert.throws(
21
+ () => verifyScopes(['invoices:create'], session([])),
22
+ MissingScopeError
23
+ )
24
+ })
25
+
26
+ test('holding an unrelated scope does not grant', () => {
27
+ assert.throws(
28
+ () => verifyScopes(['invoices:create'], session(['billing:read'])),
29
+ MissingScopeError
30
+ )
31
+ })
32
+
33
+ describe('wildcards', () => {
34
+ test('admin:* satisfies a descendant', () => {
35
+ verifyScopes(['admin:invoices:create'], session(['admin:*']))
36
+ })
37
+
38
+ test('admin:* satisfies an intermediate descendant', () => {
39
+ verifyScopes(['admin:invoices'], session(['admin:*']))
40
+ })
41
+
42
+ test('admin:* satisfies the parent itself', () => {
43
+ verifyScopes(['admin'], session(['admin:*']))
44
+ })
45
+
46
+ test('a bare * satisfies anything', () => {
47
+ verifyScopes(['admin:invoices:create'], session(['*']))
48
+ })
49
+
50
+ test('a deep wildcard satisfies its own descendants', () => {
51
+ verifyScopes(['admin:invoices:create'], session(['admin:invoices:*']))
52
+ })
53
+
54
+ test('does not leak across siblings', () => {
55
+ assert.throws(
56
+ () => verifyScopes(['billing:read'], session(['admin:*'])),
57
+ MissingScopeError
58
+ )
59
+ })
60
+
61
+ test('a required wildcard is not satisfied by one narrow grant', () => {
62
+ assert.throws(
63
+ () => verifyScopes(['admin:*'], session(['admin:invoices:create'])),
64
+ MissingScopeError
65
+ )
66
+ })
67
+
68
+ test('a required wildcard is satisfied by a broader grant', () => {
69
+ verifyScopes(['admin:*'], session(['*']))
70
+ })
71
+ })
72
+
73
+ describe('a plain parent grant covers its descendants', () => {
74
+ test('holding a parent satisfies a deep descendant', () => {
75
+ verifyScopes(['admin:invoices:create'], session(['admin']))
76
+ })
77
+
78
+ test('holding a parent satisfies an intermediate descendant', () => {
79
+ verifyScopes(['admin:invoices'], session(['admin']))
80
+ })
81
+
82
+ test('holding an intermediate parent satisfies its child', () => {
83
+ verifyScopes(['admin:invoices:create'], session(['admin:invoices']))
84
+ })
85
+
86
+ test('a parent does not leak across siblings', () => {
87
+ assert.throws(
88
+ () => verifyScopes(['billing:read'], session(['admin'])),
89
+ MissingScopeError
90
+ )
91
+ })
92
+ })
93
+
94
+ describe('AND semantics', () => {
95
+ test('passes only when every required scope is held', () => {
96
+ verifyScopes(
97
+ ['invoices:read', 'invoices:write'],
98
+ session(['invoices:read', 'invoices:write'])
99
+ )
100
+ })
101
+
102
+ test('throws when only one of two is held', () => {
103
+ assert.throws(
104
+ () =>
105
+ verifyScopes(
106
+ ['invoices:read', 'invoices:write'],
107
+ session(['invoices:read'])
108
+ ),
109
+ MissingScopeError
110
+ )
111
+ })
112
+
113
+ test('one wildcard can satisfy several requirements', () => {
114
+ verifyScopes(['admin:read', 'admin:write'], session(['admin:*']))
115
+ })
116
+ })
117
+
118
+ describe('fails closed', () => {
119
+ test('throws when the session has no scopes field', () => {
120
+ assert.throws(
121
+ () => verifyScopes(['invoices:create'], session(undefined)),
122
+ MissingScopeError
123
+ )
124
+ })
125
+
126
+ test('throws when there is no session at all', () => {
127
+ assert.throws(
128
+ () => verifyScopes(['invoices:create'], undefined),
129
+ MissingScopeError
130
+ )
131
+ })
132
+
133
+ test('a narrower grant does not satisfy a broader requirement', () => {
134
+ assert.throws(
135
+ () => verifyScopes(['admin'], session(['admin:invoices'])),
136
+ MissingScopeError
137
+ )
138
+ })
139
+ })
140
+
141
+ describe('a parent grant coexists with a child grant', () => {
142
+ test('the parent still grants when a child is also held', () => {
143
+ verifyScopes(
144
+ ['admin:invoices'],
145
+ session(['admin:invoices', 'admin:invoices:create'])
146
+ )
147
+ })
148
+
149
+ test('the child still grants when a parent is also held', () => {
150
+ verifyScopes(
151
+ ['admin:invoices:create'],
152
+ session(['admin:invoices', 'admin:invoices:create'])
153
+ )
154
+ })
155
+ })
156
+
157
+ test('names the missing scope', () => {
158
+ assert.throws(
159
+ () => verifyScopes(['a:b', 'c:d'], session(['a:b'])),
160
+ (err: MissingScopeError) => {
161
+ assert.equal(err.payload.scope, 'c:d')
162
+ assert.match(err.message, /c:d/)
163
+ return true
164
+ }
165
+ )
166
+ })
167
+ })
package/src/scopes.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type { CoreUserSession } from './types/core.types.js'
2
+ import { MissingScopeError } from './errors/errors.js'
3
+
4
+ const SEPARATOR = ':'
5
+ const WILDCARD = '*'
6
+
7
+ /**
8
+ * Builds every grant that would satisfy `scope`: the scope itself, a wildcard
9
+ * directly beneath it, a wildcard at each ancestor level, and each plain
10
+ * ancestor id — holding a parent scope grants everything nested beneath it.
11
+ *
12
+ * For `admin:invoices:create` that is:
13
+ * admin:invoices:create, admin:invoices:create:*, *, admin:*, admin:invoices:*,
14
+ * admin, admin:invoices
15
+ */
16
+ const satisfyingGrants = (scope: string): string[] => {
17
+ const segments = scope.split(SEPARATOR)
18
+ const grants = [scope, `${scope}${SEPARATOR}${WILDCARD}`]
19
+ for (let i = 0; i < segments.length; i++) {
20
+ grants.push([...segments.slice(0, i), WILDCARD].join(SEPARATOR))
21
+ }
22
+ for (let i = 1; i < segments.length; i++) {
23
+ grants.push(segments.slice(0, i).join(SEPARATOR))
24
+ }
25
+ return grants
26
+ }
27
+
28
+ /**
29
+ * Checks whether `held` satisfies a single required scope.
30
+ *
31
+ * A grant satisfies a requirement when it is the scope itself, a plain ancestor
32
+ * (`admin` covers `admin:invoices:create`), a wildcard at or above it (`admin:*`
33
+ * covers `admin` and `admin:invoices:create`), or the bare `*`. A narrower grant
34
+ * never satisfies a broader requirement — `admin:invoices` does not grant
35
+ * `admin`.
36
+ */
37
+ const holds = (held: ReadonlySet<string>, scope: string): boolean =>
38
+ satisfyingGrants(scope).some((grant) => held.has(grant))
39
+
40
+ /**
41
+ * Verifies that a session holds every required scope, throwing on the first
42
+ * one it does not.
43
+ *
44
+ * Scopes are an AND gate: every entry in `required` must be satisfied. This is
45
+ * deliberately distinct from `permissions`, which OR together — a scope can
46
+ * only ever narrow access, so adding one to a function can never widen it.
47
+ *
48
+ * Fails closed: a session without a `scopes` field, or no session at all,
49
+ * satisfies nothing.
50
+ *
51
+ * @param required - Scopes the function declares. Empty means no gate.
52
+ * @param session - The session to check. May be undefined.
53
+ * @throws {MissingScopeError} Naming the first unsatisfied scope.
54
+ */
55
+ export const verifyScopes = (
56
+ required: readonly string[] | undefined,
57
+ session: CoreUserSession | undefined
58
+ ): void => {
59
+ if (!required || required.length === 0) {
60
+ return
61
+ }
62
+
63
+ const held = new Set(session?.scopes ?? [])
64
+ for (const scope of required) {
65
+ if (!holds(held, scope)) {
66
+ throw new MissingScopeError(scope)
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A narrow, dedicated embedding service.
3
+ *
4
+ * Vector stores (Qdrant, Pinecone, Supabase/pgvector, …) depend on this to turn
5
+ * text into vectors, at BOTH index time (ingest) and query time (search). The
6
+ * embedding model is fixed at construction so those two moments cannot drift
7
+ * into different vector spaces — a mismatch would silently break similarity
8
+ * search. Provider addons (e.g. `@pikku/addon-openai`) implement it; the app
9
+ * wires a single instance into `singletonServices.aiEmbedding`.
10
+ *
11
+ * This is deliberately smaller than {@link AIAgentRunnerService} (whose optional
12
+ * `embed`/`embedMany` take a per-call model): a vector store should not have to
13
+ * pull in the whole agent-runner tool loop just to embed, and pinning the model
14
+ * to the service is what guarantees index/query consistency.
15
+ *
16
+ * Documents (ingested corpus) and queries (search text) are embedded through
17
+ * separate methods because some models are asymmetric — Cohere's `input_type`,
18
+ * E5's `query:`/`passage:` prefixes, BGE's query instruction — and must know
19
+ * which side they are embedding to produce comparable vectors. Symmetric
20
+ * providers (e.g. OpenAI) point both methods at the same call.
21
+ */
22
+ export interface AIEmbeddingService {
23
+ /** The embedding model backing this service. Index and query share it. */
24
+ readonly model: string
25
+ /** The vector dimensionality this model produces, when known. */
26
+ readonly dimensions?: number
27
+ /** Embed corpus documents (index time), preserving input order. */
28
+ embedDocuments(values: string[]): Promise<number[][]>
29
+ /** Embed a search query (query time) into a vector. */
30
+ embedQuery(value: string): Promise<number[]>
31
+ }
@@ -9,16 +9,22 @@ export class PikkuCredentialWireService {
9
9
 
10
10
  constructor(
11
11
  private credentialService?: CredentialService,
12
- private wire?: PikkuRawWire
12
+ private wire?: PikkuRawWire,
13
+ private aliases?: Record<string, string>
13
14
  ) {}
14
15
 
16
+ private resolveName(name: string): string {
17
+ return this.aliases?.[name] ?? name
18
+ }
19
+
15
20
  set(name: string, value: unknown): void {
16
- this.credentials[name] = value
21
+ this.credentials[this.resolveName(name)] = value
17
22
  }
18
23
 
19
24
  get<T = unknown>(name: string): T | null | Promise<T | null> {
20
- if (this.loaded) return (this.credentials[name] as T) ?? null
21
- return this.lazyLoad().then(() => (this.credentials[name] as T) ?? null)
25
+ const key = this.resolveName(name)
26
+ if (this.loaded) return (this.credentials[key] as T) ?? null
27
+ return this.lazyLoad().then(() => (this.credentials[key] as T) ?? null)
22
28
  }
23
29
 
24
30
  getAll(): Record<string, unknown> | Promise<Record<string, unknown>> {
@@ -22,6 +22,10 @@ export { LocalCredentialService } from './local-credential-service.js'
22
22
  export { LocalVariablesService } from './local-variables.js'
23
23
  export { ConsoleLogger, JsonConsoleLogger } from './logger-console.js'
24
24
  export { InMemoryWorkflowService } from './in-memory-workflow-service.js'
25
+ export {
26
+ QueueWebhookService,
27
+ pikkuWebhookWorkerFunc,
28
+ } from './queue-webhook-service.js'
25
29
  export { InMemoryQueueService } from './in-memory-queue-service.js'
26
30
  export { InMemoryTriggerService } from './in-memory-trigger-service.js'
27
31
  export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js'
@@ -56,6 +60,20 @@ export type {
56
60
  SendTemplateEmailInput,
57
61
  SendTextEmailInput,
58
62
  } from './email-service.js'
63
+ export {
64
+ DEFAULT_WEBHOOK_RETRIES,
65
+ DEFAULT_WEBHOOK_SIGNATURE_HEADER,
66
+ PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME,
67
+ WebhookService,
68
+ type SendWebhookInput,
69
+ type SendWebhookResult,
70
+ type WebhookAttemptRecord,
71
+ type WebhookAttemptResult,
72
+ type WebhookDeliveryRecord,
73
+ type WebhookDeliveryWithAttempts,
74
+ type WebhookJobData,
75
+ type WebhookServiceConfig,
76
+ } from './webhook-service.js'
59
77
  export type { Logger } from './logger.js'
60
78
  export type { SecretService } from './secret-service.js'
61
79
  export type { VariablesService } from './variables-service.js'
@@ -95,6 +113,7 @@ export type {
95
113
  AITranscriptionResult,
96
114
  AIAgentRunnerService,
97
115
  } from './ai-agent-runner-service.js'
116
+ export type { AIEmbeddingService } from './ai-embedding-service.js'
98
117
  export type {
99
118
  CreateRunInput,
100
119
  AIRunStateService,
@@ -112,6 +131,7 @@ export type {
112
131
  export type { VariableStatus, VariableMeta } from './typed-variables-service.js'
113
132
  export type { MetaService } from './meta-service.js'
114
133
  export type { SessionStore } from './session-store.js'
134
+ export type { ScopeService, Role } from './scope-service.js'
115
135
  export {
116
136
  NoopAuditService,
117
137
  createInvocationAudit,
@@ -1,4 +1,6 @@
1
1
  import { readFile, readdir } from 'node:fs/promises'
2
+ import { existsSync } from 'node:fs'
3
+ import { createRequire } from 'node:module'
2
4
  import { join } from 'node:path'
3
5
  import type { JSONSchema7 } from 'json-schema'
4
6
  import type { HTTPWiringsMeta } from '../wirings/http/http.types.js'
@@ -107,8 +109,6 @@ export interface PermissionDefinitionMeta {
107
109
 
108
110
  export interface PermissionsGroupsMeta {
109
111
  definitions: Record<string, PermissionDefinitionMeta>
110
- httpGroups: Record<string, GroupMeta>
111
- tagGroups: Record<string, GroupMeta>
112
112
  }
113
113
 
114
114
  export interface EmailTemplateLocaleMeta {
@@ -159,6 +159,20 @@ export interface MetaService {
159
159
  readDir(relativePath: string): Promise<string[]>
160
160
  /** Read a project source file relative to the project root (one level above .pikku). */
161
161
  readProjectFile(relativePath: string): Promise<string | null>
162
+ /**
163
+ * Read a file from an *installed addon package's* own `.pikku` directory
164
+ * (resolved from node_modules), NOT the app's `.pikku`. `relativePath` is
165
+ * relative to that package's `.pikku` root (use `../` to reach the package
166
+ * root itself, e.g. `../README.md`). Returns null if the package or file is
167
+ * absent. Only local/Node implementations resolve packages; remote impls omit
168
+ * this and callers guard with `?.`.
169
+ */
170
+ readPackageFile?(
171
+ packageName: string,
172
+ relativePath: string
173
+ ): Promise<string | null>
174
+ /** List a directory inside an installed addon package's `.pikku`. */
175
+ readPackageDir?(packageName: string, relativePath: string): Promise<string[]>
162
176
 
163
177
  // -- Typed metadata accessors --
164
178
 
@@ -252,6 +266,57 @@ export class LocalMetaService implements MetaService {
252
266
  }
253
267
  }
254
268
 
269
+ // Resolve an installed addon package's root dir from the app's node_modules.
270
+ // The addon packages have no main `exports` entry, so `require.resolve(pkg)`
271
+ // throws; instead we walk the node_modules search paths (anchored at the app
272
+ // root, one level above `.pikku`) and pick the first that contains the package.
273
+ private packageRootCache = new Map<string, string | null>()
274
+ private resolvePackageRoot(packageName: string): string | null {
275
+ const cached = this.packageRootCache.get(packageName)
276
+ if (cached !== undefined) return cached
277
+ let root: string | null = null
278
+ try {
279
+ const require = createRequire(join(this.basePath, '..', 'index.js'))
280
+ for (const dir of require.resolve.paths(packageName) ?? []) {
281
+ const candidate = join(dir, packageName)
282
+ if (existsSync(join(candidate, 'package.json'))) {
283
+ root = candidate
284
+ break
285
+ }
286
+ }
287
+ } catch {
288
+ root = null
289
+ }
290
+ this.packageRootCache.set(packageName, root)
291
+ return root
292
+ }
293
+
294
+ async readPackageFile(
295
+ packageName: string,
296
+ relativePath: string
297
+ ): Promise<string | null> {
298
+ const root = this.resolvePackageRoot(packageName)
299
+ if (!root) return null
300
+ try {
301
+ return await readFile(join(root, '.pikku', relativePath), 'utf-8')
302
+ } catch {
303
+ return null
304
+ }
305
+ }
306
+
307
+ async readPackageDir(
308
+ packageName: string,
309
+ relativePath: string
310
+ ): Promise<string[]> {
311
+ const root = this.resolvePackageRoot(packageName)
312
+ if (!root) return []
313
+ try {
314
+ return await readdir(join(root, '.pikku', relativePath))
315
+ } catch {
316
+ return []
317
+ }
318
+ }
319
+
255
320
  clearCache(): void {
256
321
  this.httpMetaCache = null
257
322
  this.channelsMetaCache = null
@@ -489,7 +554,7 @@ export class LocalMetaService implements MetaService {
489
554
  )
490
555
  this.permissionsGroupsMetaCache = content
491
556
  ? JSON.parse(content)
492
- : { definitions: {}, httpGroups: {}, tagGroups: {} }
557
+ : { definitions: {} }
493
558
  return this.permissionsGroupsMetaCache!
494
559
  }
495
560