@pikku/core 0.12.62 → 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.
- package/CHANGELOG.md +153 -0
- package/dist/dev/hot-reload.js +13 -55
- package/dist/dev/module-runner.d.ts +14 -0
- package/dist/dev/module-runner.js +59 -0
- package/dist/dev/reload-meta.d.ts +13 -0
- package/dist/dev/reload-meta.js +22 -0
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +19 -0
- package/dist/function/function-runner.d.ts +5 -5
- package/dist/function/function-runner.js +39 -111
- package/dist/function/functions.types.d.ts +17 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/permissions.d.ts +32 -25
- package/dist/permissions.js +104 -185
- package/dist/pikku-state.js +0 -2
- package/dist/scopes.d.ts +17 -0
- package/dist/scopes.js +59 -0
- package/dist/services/ai-embedding-service.d.ts +31 -0
- package/dist/services/ai-embedding-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +3 -1
- package/dist/services/credential-wire-service.js +10 -4
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +2 -0
- package/dist/services/meta-service.d.ts +15 -2
- package/dist/services/meta-service.js +51 -1
- package/dist/services/queue-webhook-service.d.ts +55 -0
- package/dist/services/queue-webhook-service.js +136 -0
- package/dist/services/scope-service.d.ts +67 -0
- package/dist/services/scope-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +8 -0
- package/dist/services/typed-secret-service.js +39 -4
- package/dist/services/webhook-service.d.ts +140 -0
- package/dist/services/webhook-service.js +44 -0
- package/dist/services/workflow-service.d.ts +7 -0
- package/dist/types/core.types.d.ts +34 -6
- package/dist/types/state.types.d.ts +20 -2
- package/dist/utils/hmac.d.ts +16 -0
- package/dist/utils/hmac.js +26 -0
- package/dist/utils/safe-fetch.d.ts +51 -0
- package/dist/utils/safe-fetch.js +192 -0
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +41 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +146 -6
- package/dist/wirings/ai-agent/ai-agent-runner.js +6 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +61 -10
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +27 -1
- package/dist/wirings/ai-agent/index.d.ts +1 -0
- package/dist/wirings/ai-agent/index.js +1 -0
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +2 -38
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/channel/channel-common.js +0 -1
- package/dist/wirings/channel/channel-handler.js +1 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
- package/dist/wirings/channel/channel-middleware-runner.js +34 -19
- package/dist/wirings/channel/channel.types.d.ts +2 -6
- package/dist/wirings/cli/cli-runner.js +0 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/http/http-routes.js +0 -3
- package/dist/wirings/http/http-runner.d.ts +9 -29
- package/dist/wirings/http/http-runner.js +13 -35
- package/dist/wirings/http/http.types.d.ts +1 -9
- package/dist/wirings/mcp/mcp-runner.js +0 -2
- package/dist/wirings/mcp/mcp.types.d.ts +5 -11
- package/dist/wirings/oauth2/index.d.ts +0 -3
- package/dist/wirings/oauth2/index.js +1 -2
- package/dist/wirings/rpc/addon-runner.d.ts +28 -0
- package/dist/wirings/rpc/addon-runner.js +173 -0
- package/dist/wirings/rpc/index.d.ts +5 -1
- package/dist/wirings/rpc/index.js +3 -1
- package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
- package/dist/wirings/rpc/remote-addon-auth.js +43 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
- package/dist/wirings/rpc/rpc-runner.js +109 -3
- package/dist/wirings/rpc/rpc-types.d.ts +15 -1
- package/dist/wirings/rpc/wire-addon.js +9 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
- package/dist/wirings/rpc/wire-remote-addon.js +19 -0
- package/dist/wirings/scope/index.d.ts +3 -0
- package/dist/wirings/scope/index.js +2 -0
- package/dist/wirings/scope/scope.types.d.ts +39 -0
- package/dist/wirings/scope/scope.types.js +1 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
- package/dist/wirings/scope/validate-scope-definitions.js +76 -0
- package/dist/wirings/scope/wire-scope.d.ts +33 -0
- package/dist/wirings/scope/wire-scope.js +32 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +53 -1
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
- package/dist/wirings/workflow/index.d.ts +2 -2
- package/dist/wirings/workflow/index.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +51 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +171 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -1
- package/package.json +5 -1
- package/run-tests.sh +1 -0
- package/src/dev/hot-reload.ts +13 -68
- package/src/dev/module-runner.test.ts +169 -0
- package/src/dev/module-runner.ts +103 -0
- package/src/dev/reload-meta.test.ts +31 -2
- package/src/dev/reload-meta.ts +26 -0
- package/src/errors/errors.ts +24 -0
- package/src/function/function-runner.test.ts +276 -142
- package/src/function/function-runner.ts +52 -145
- package/src/function/functions.types.ts +28 -38
- package/src/index.ts +8 -6
- package/src/middleware/index.ts +1 -5
- package/src/permissions.test.ts +153 -372
- package/src/permissions.ts +139 -259
- package/src/pikku-state.ts +0 -2
- package/src/scopes.test.ts +167 -0
- package/src/scopes.ts +69 -0
- package/src/services/ai-embedding-service.ts +31 -0
- package/src/services/credential-wire-service.ts +10 -4
- package/src/services/index.ts +20 -0
- package/src/services/meta-service.ts +68 -3
- package/src/services/queue-webhook-service.test.ts +408 -0
- package/src/services/queue-webhook-service.ts +182 -0
- package/src/services/scope-service.ts +75 -0
- package/src/services/typed-secret-service.test.ts +35 -0
- package/src/services/typed-secret-service.ts +39 -4
- package/src/services/webhook-service.ts +180 -0
- package/src/services/workflow-service.ts +7 -0
- package/src/types/core.types.ts +42 -14
- package/src/types/state.types.ts +23 -3
- package/src/utils/hmac.ts +27 -0
- package/src/utils/safe-fetch.test.ts +373 -0
- package/src/utils/safe-fetch.ts +213 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +308 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +192 -8
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +21 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +198 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +84 -10
- package/src/wirings/ai-agent/ai-agent.types.ts +28 -0
- package/src/wirings/ai-agent/index.ts +1 -0
- package/src/wirings/ai-agent/voice-input.ts +6 -35
- package/src/wirings/channel/channel-common.ts +0 -1
- package/src/wirings/channel/channel-handler.ts +1 -9
- package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
- package/src/wirings/channel/channel-middleware-runner.ts +39 -27
- package/src/wirings/channel/channel.types.ts +0 -6
- package/src/wirings/cli/cli-runner.ts +0 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/http/http-routes.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +1 -11
- package/src/wirings/http/http-runner.ts +19 -40
- package/src/wirings/http/http.types.ts +1 -10
- package/src/wirings/mcp/mcp-runner.ts +0 -2
- package/src/wirings/mcp/mcp.types.ts +3 -11
- package/src/wirings/oauth2/index.ts +0 -3
- package/src/wirings/rpc/addon-runner.ts +233 -0
- package/src/wirings/rpc/index.ts +17 -1
- package/src/wirings/rpc/remote-addon-auth.ts +69 -0
- package/src/wirings/rpc/rpc-runner.test.ts +319 -16
- package/src/wirings/rpc/rpc-runner.ts +155 -3
- package/src/wirings/rpc/rpc-types.ts +16 -5
- package/src/wirings/rpc/wire-addon.test.ts +6 -3
- package/src/wirings/rpc/wire-addon.ts +9 -0
- package/src/wirings/rpc/wire-remote-addon.ts +57 -0
- package/src/wirings/scope/index.ts +14 -0
- package/src/wirings/scope/scope.test.ts +135 -0
- package/src/wirings/scope/scope.types.ts +44 -0
- package/src/wirings/scope/validate-scope-definitions.ts +110 -0
- package/src/wirings/scope/wire-scope.ts +34 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +61 -0
- package/src/wirings/workflow/graph/graph-node.ts +2 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/wirings/workflow/index.ts +5 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +236 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
- package/src/wirings/oauth2/oauth2-client.ts +0 -331
- package/src/wirings/oauth2/oauth2-routes.ts +0 -234
|
@@ -17,6 +17,15 @@ export type CredentialMeta = {
|
|
|
17
17
|
export class TypedSecretService<
|
|
18
18
|
TMap = Record<string, unknown>,
|
|
19
19
|
> implements SecretService {
|
|
20
|
+
/**
|
|
21
|
+
* In-process cache of resolved secrets, so callers can read naively without
|
|
22
|
+
* re-hitting the underlying service on every call. Only successful reads are
|
|
23
|
+
* cached (a miss throws and isn't stored); `setSecret`/`deleteSecret`
|
|
24
|
+
* invalidate the key. No TTL — a secret rotated out-of-band won't refresh
|
|
25
|
+
* until restart (see pikkujs/pikku#964).
|
|
26
|
+
*/
|
|
27
|
+
private cache = new Map<string, unknown>()
|
|
28
|
+
|
|
20
29
|
constructor(
|
|
21
30
|
private secrets: SecretService,
|
|
22
31
|
private credentialsMeta: Record<string, CredentialMeta>
|
|
@@ -25,10 +34,18 @@ export class TypedSecretService<
|
|
|
25
34
|
async getSecret<K extends keyof TMap & string>(key: K): Promise<TMap[K]>
|
|
26
35
|
async getSecret<T = string>(key: string): Promise<T>
|
|
27
36
|
async getSecret(key: string): Promise<unknown> {
|
|
28
|
-
|
|
37
|
+
if (this.cache.has(key)) {
|
|
38
|
+
return this.cache.get(key)
|
|
39
|
+
}
|
|
40
|
+
const value = await this.secrets.getSecret(key)
|
|
41
|
+
this.cache.set(key, value)
|
|
42
|
+
return value
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
async hasSecret(key: string): Promise<boolean> {
|
|
46
|
+
if (this.cache.has(key)) {
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
32
49
|
return this.secrets.hasSecret(key)
|
|
33
50
|
}
|
|
34
51
|
|
|
@@ -36,17 +53,35 @@ export class TypedSecretService<
|
|
|
36
53
|
key: K,
|
|
37
54
|
value: K extends keyof TMap ? TMap[K] : unknown
|
|
38
55
|
): Promise<void> {
|
|
39
|
-
|
|
56
|
+
await this.secrets.setSecret(key, value)
|
|
57
|
+
this.cache.delete(key)
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
async deleteSecret(key: string): Promise<void> {
|
|
43
|
-
|
|
61
|
+
await this.secrets.deleteSecret(key)
|
|
62
|
+
this.cache.delete(key)
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
async getSecrets<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
47
66
|
keys: (keyof T & string)[]
|
|
48
67
|
): Promise<Partial<T>> {
|
|
49
|
-
|
|
68
|
+
const result: Partial<T> = {}
|
|
69
|
+
const missing: (keyof T & string)[] = []
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
if (this.cache.has(key)) {
|
|
72
|
+
result[key] = this.cache.get(key) as T[keyof T & string]
|
|
73
|
+
} else {
|
|
74
|
+
missing.push(key)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (missing.length > 0) {
|
|
78
|
+
const fetched = await this.secrets.getSecrets<T>(missing)
|
|
79
|
+
for (const [key, value] of Object.entries(fetched)) {
|
|
80
|
+
this.cache.set(key, value)
|
|
81
|
+
result[key as keyof T & string] = value as T[keyof T & string]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result
|
|
50
85
|
}
|
|
51
86
|
|
|
52
87
|
async getAllStatus(): Promise<CredentialStatus[]> {
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { NotImplementedError } from '../errors/errors.js'
|
|
2
|
+
import { hmacSha256Hex, timingSafeStringEqual } from '../utils/hmac.js'
|
|
3
|
+
|
|
4
|
+
export interface SendWebhookInput {
|
|
5
|
+
/** The endpoint the webhook is delivered to via HTTP POST. */
|
|
6
|
+
url: string
|
|
7
|
+
/** Optional event name, sent as the `X-Pikku-Event` header. */
|
|
8
|
+
event?: string
|
|
9
|
+
/** The payload, JSON-serialized into the request body. */
|
|
10
|
+
data: unknown
|
|
11
|
+
/** Extra headers merged over the defaults. */
|
|
12
|
+
headers?: Record<string, string>
|
|
13
|
+
/**
|
|
14
|
+
* Raw HMAC signing key for this delivery. Overrides the config-level
|
|
15
|
+
* secret. When present, the body is signed into the signature header.
|
|
16
|
+
*/
|
|
17
|
+
secret?: string
|
|
18
|
+
/** Retry count for failed deliveries. Overrides `config.webhook.retries`. */
|
|
19
|
+
retries?: number
|
|
20
|
+
/**
|
|
21
|
+
* Delay between retries (`30000` or `'30s'`). A concrete value selects a
|
|
22
|
+
* fixed backoff; when omitted, backoff is exponential.
|
|
23
|
+
*/
|
|
24
|
+
retryDelay?: string | number
|
|
25
|
+
/**
|
|
26
|
+
* Organization this delivery belongs to. Persisted by a store-backed
|
|
27
|
+
* implementation (e.g. `KyselyWebhookService`) so deliveries can be
|
|
28
|
+
* queried per org; ignored by the queue-only default.
|
|
29
|
+
*/
|
|
30
|
+
organizationId?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SendWebhookResult {
|
|
34
|
+
jobId: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WebhookServiceConfig {
|
|
38
|
+
/** Default retry count for failed deliveries. */
|
|
39
|
+
retries?: number
|
|
40
|
+
/** Default delay between retries; omitted means exponential backoff. */
|
|
41
|
+
retryDelay?: string | number
|
|
42
|
+
/**
|
|
43
|
+
* Name of a secret (resolved through the secret service) used as the
|
|
44
|
+
* default HMAC signing key for all outgoing webhooks.
|
|
45
|
+
*/
|
|
46
|
+
secret?: string
|
|
47
|
+
/**
|
|
48
|
+
* Header the body signature is sent in. Defaults to
|
|
49
|
+
* {@link DEFAULT_WEBHOOK_SIGNATURE_HEADER}.
|
|
50
|
+
*/
|
|
51
|
+
signatureHeader?: string
|
|
52
|
+
/**
|
|
53
|
+
* SSRF allowlist of hostnames outgoing webhooks may be delivered to. When set,
|
|
54
|
+
* a delivery target must be on this list. When omitted, obvious private and
|
|
55
|
+
* internal hosts (loopback, private ranges, cloud metadata) are blocked and
|
|
56
|
+
* all other public hosts are permitted.
|
|
57
|
+
*/
|
|
58
|
+
allowedHosts?: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WebhookJobData {
|
|
62
|
+
url: string
|
|
63
|
+
event?: string
|
|
64
|
+
body: string
|
|
65
|
+
headers: Record<string, string>
|
|
66
|
+
/**
|
|
67
|
+
* Delivery-record id, present when a store-backed implementation enqueued
|
|
68
|
+
* the job. The worker keys its per-attempt records off this (it doubles as
|
|
69
|
+
* the queue `jobId`), so it's stable across retries.
|
|
70
|
+
*/
|
|
71
|
+
deliveryId?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Outcome of one delivery attempt, recorded against a delivery. */
|
|
75
|
+
export interface WebhookAttemptResult {
|
|
76
|
+
/** HTTP status of the response, absent if the request never completed. */
|
|
77
|
+
statusCode?: number
|
|
78
|
+
/** Response body (truncated) — captured on failure for debugging. */
|
|
79
|
+
responseBody?: string
|
|
80
|
+
/** Network/timeout error message, when the request threw. */
|
|
81
|
+
error?: string
|
|
82
|
+
/** Whether this attempt was a 2xx success. */
|
|
83
|
+
delivered: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A persisted webhook delivery, as surfaced to the console. */
|
|
87
|
+
export interface WebhookDeliveryRecord {
|
|
88
|
+
deliveryId: string
|
|
89
|
+
organizationId: string | null
|
|
90
|
+
url: string
|
|
91
|
+
event: string | null
|
|
92
|
+
status: 'pending' | 'delivered' | 'failed'
|
|
93
|
+
attempts: number
|
|
94
|
+
createdAt: Date
|
|
95
|
+
updatedAt: Date
|
|
96
|
+
deliveredAt: Date | null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A single persisted delivery attempt. */
|
|
100
|
+
export interface WebhookAttemptRecord {
|
|
101
|
+
attemptId: string
|
|
102
|
+
deliveryId: string
|
|
103
|
+
attemptNumber: number
|
|
104
|
+
statusCode: number | null
|
|
105
|
+
responseBody: string | null
|
|
106
|
+
error: string | null
|
|
107
|
+
createdAt: Date
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A delivery with its full attempt history. */
|
|
111
|
+
export interface WebhookDeliveryWithAttempts {
|
|
112
|
+
delivery: WebhookDeliveryRecord
|
|
113
|
+
attempts: WebhookAttemptRecord[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME = 'pikku-outgoing-webhooks'
|
|
117
|
+
|
|
118
|
+
export const DEFAULT_WEBHOOK_SIGNATURE_HEADER = 'X-Pikku-Signature'
|
|
119
|
+
|
|
120
|
+
/** Fallback when neither the per-call nor `config.webhook.retries` is set. */
|
|
121
|
+
export const DEFAULT_WEBHOOK_RETRIES = 3
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Outgoing webhook delivery. {@link QueueWebhookService} is the default
|
|
125
|
+
* implementation; apps can extend this to deliver directly, or through a
|
|
126
|
+
* provider such as Svix.
|
|
127
|
+
*/
|
|
128
|
+
export abstract class WebhookService {
|
|
129
|
+
abstract send(input: SendWebhookInput): Promise<SendWebhookResult>
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Sign a body with HMAC-SHA256, producing the signature header value
|
|
133
|
+
* (e.g. `sha256=abc123...`).
|
|
134
|
+
*/
|
|
135
|
+
protected sign(secret: string, body: string): string {
|
|
136
|
+
return `sha256=${hmacSha256Hex(secret, body)}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Verify a signature produced by {@link WebhookService.sign} — for
|
|
141
|
+
* receivers, which share the signing scheme.
|
|
142
|
+
*/
|
|
143
|
+
public verify(secret: string, signature: string, body: string): boolean {
|
|
144
|
+
return timingSafeStringEqual(this.sign(secret, body), signature)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Record a delivery attempt and roll the delivery's status/attempt-count
|
|
149
|
+
* forward, keyed by the delivery id carried in {@link WebhookJobData}. The
|
|
150
|
+
* default queue-only service keeps no history, so this throws — a store-backed
|
|
151
|
+
* implementation (e.g. `KyselyWebhookService` in `@pikku/kysely`) overrides it.
|
|
152
|
+
*/
|
|
153
|
+
public recordAttempt(
|
|
154
|
+
_deliveryId: string,
|
|
155
|
+
_result: WebhookAttemptResult
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
throw new NotImplementedError(
|
|
158
|
+
'webhook delivery persistence is not configured'
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** List deliveries, most recent first, optionally scoped to an org. */
|
|
163
|
+
public listDeliveries(_opts?: {
|
|
164
|
+
organizationId?: string
|
|
165
|
+
limit?: number
|
|
166
|
+
}): Promise<WebhookDeliveryRecord[]> {
|
|
167
|
+
throw new NotImplementedError(
|
|
168
|
+
'webhook delivery persistence is not configured'
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A single delivery with its attempt history, or null if unknown. */
|
|
173
|
+
public getDelivery(
|
|
174
|
+
_deliveryId: string
|
|
175
|
+
): Promise<WebhookDeliveryWithAttempts | null> {
|
|
176
|
+
throw new NotImplementedError(
|
|
177
|
+
'webhook delivery persistence is not configured'
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -50,6 +50,13 @@ export interface WorkflowService {
|
|
|
50
50
|
|
|
51
51
|
// Orchestration operations
|
|
52
52
|
resumeWorkflow(runId: string): Promise<void>
|
|
53
|
+
/**
|
|
54
|
+
* Record a human decision against a `workflow.approval()` gate and wake the
|
|
55
|
+
* run. The payload is stored as given and validated on replay, inside the
|
|
56
|
+
* workflow body — so an invalid one re-closes the gate rather than failing the
|
|
57
|
+
* run. `reason` addresses the gate's first reach.
|
|
58
|
+
*/
|
|
59
|
+
approveStep(runId: string, reason: string, decision: unknown): Promise<void>
|
|
53
60
|
startWorkflow<I>(
|
|
54
61
|
name: string,
|
|
55
62
|
input: I,
|
package/src/types/core.types.ts
CHANGED
|
@@ -32,15 +32,21 @@ import type { AIStorageService } from '../services/ai-storage-service.js'
|
|
|
32
32
|
import type { ContentService } from '../services/content-service.js'
|
|
33
33
|
import type { ScenarioActors } from '../services/scenario-actors-service.js'
|
|
34
34
|
import type { AIAgentRunnerService } from '../services/ai-agent-runner-service.js'
|
|
35
|
+
import type { AIEmbeddingService } from '../services/ai-embedding-service.js'
|
|
35
36
|
import type { AIRunStateService } from '../services/ai-run-state-service.js'
|
|
36
37
|
import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js'
|
|
37
38
|
import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.js'
|
|
38
39
|
import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js'
|
|
39
40
|
import type { CredentialService } from '../services/credential-service.js'
|
|
40
41
|
import type { EmailService } from '../services/email-service.js'
|
|
42
|
+
import type {
|
|
43
|
+
WebhookService,
|
|
44
|
+
WebhookServiceConfig,
|
|
45
|
+
} from '../services/webhook-service.js'
|
|
41
46
|
import type { MetaService } from '../services/meta-service.js'
|
|
42
47
|
import type { CoverageService } from '../services/v8-coverage-service.js'
|
|
43
48
|
import type { SessionStore } from '../services/session-store.js'
|
|
49
|
+
import type { ScopeService } from '../services/scope-service.js'
|
|
44
50
|
import type {
|
|
45
51
|
AuditDurability,
|
|
46
52
|
AuditLog,
|
|
@@ -97,25 +103,24 @@ export type MiddlewareMetadata =
|
|
|
97
103
|
* - type: 'tag' = Tag-based permission group (references tagGroup in pikkuState)
|
|
98
104
|
* - type: 'wire' = Wire-level individual permission
|
|
99
105
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
name: string
|
|
112
|
-
inline?: boolean // true if inline permission
|
|
113
|
-
}
|
|
106
|
+
/**
|
|
107
|
+
* A reference to a permission function a function declares, resolved by name.
|
|
108
|
+
* Used at filter time (e.g. listing agent tools) to run the session-only
|
|
109
|
+
* (pikkuAuth) predicates without request data. Permissions are function-scoped
|
|
110
|
+
* only — there are no wire- or tag-level permission references.
|
|
111
|
+
*/
|
|
112
|
+
export type PermissionMetadata = {
|
|
113
|
+
type: 'wire'
|
|
114
|
+
name: string
|
|
115
|
+
inline?: boolean // true if inline permission
|
|
116
|
+
}
|
|
114
117
|
|
|
115
118
|
export type FunctionRuntimeMeta = {
|
|
116
119
|
pikkuFuncId: string
|
|
117
120
|
inputSchemaName: string | null
|
|
118
121
|
outputSchemaName: string | null
|
|
122
|
+
/** Scopes the session must hold to run this function. All are required (AND). */
|
|
123
|
+
scopes?: string[]
|
|
119
124
|
expose?: boolean
|
|
120
125
|
remote?: boolean
|
|
121
126
|
mcp?: boolean
|
|
@@ -242,6 +247,8 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
|
|
|
242
247
|
secrets?: {}
|
|
243
248
|
|
|
244
249
|
workflow?: WorkflowServiceConfig
|
|
250
|
+
/** Default retry and signing settings for outgoing webhooks. */
|
|
251
|
+
webhook?: WebhookServiceConfig
|
|
245
252
|
/** Runtime Postgres adapter options (pool sizing). */
|
|
246
253
|
postgres?: PostgresConfig
|
|
247
254
|
} & Config
|
|
@@ -254,6 +261,12 @@ export interface CoreUserSession {
|
|
|
254
261
|
orgId?: string
|
|
255
262
|
/** True when the session belongs to a synthetic scenario actor — lets audits/analytics address synthetic traffic */
|
|
256
263
|
actor?: boolean
|
|
264
|
+
/**
|
|
265
|
+
* Scopes granted to this session, checked against a function's `scopes`.
|
|
266
|
+
* Populated by whoever builds the session (e.g. better-auth's `mapSession`
|
|
267
|
+
* resolving them via a ScopeService) — core reads them and never fetches.
|
|
268
|
+
*/
|
|
269
|
+
scopes?: string[]
|
|
257
270
|
}
|
|
258
271
|
|
|
259
272
|
/**
|
|
@@ -289,6 +302,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
289
302
|
content?: ContentService
|
|
290
303
|
/** AI agent runner service (model calls + tool loop) */
|
|
291
304
|
aiAgentRunner?: AIAgentRunnerService
|
|
305
|
+
/** Dedicated embedding service (vector stores use it at index & query time) */
|
|
306
|
+
aiEmbedding?: AIEmbeddingService
|
|
292
307
|
/** AI run state service (run lifecycle + approval persistence) */
|
|
293
308
|
aiRunState?: AIRunStateService
|
|
294
309
|
/** Agent run service (listing threads, runs, steps) */
|
|
@@ -299,6 +314,12 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
299
314
|
credentialService?: CredentialService
|
|
300
315
|
/** Email service for outbound messages and template-backed delivery */
|
|
301
316
|
emailService?: EmailService
|
|
317
|
+
/**
|
|
318
|
+
* Webhook service for outgoing webhook delivery via a queue. A store-backed
|
|
319
|
+
* implementation (e.g. `KyselyWebhookService`) additionally records delivery
|
|
320
|
+
* history; the queue-only default throws on the delivery-read methods.
|
|
321
|
+
*/
|
|
322
|
+
webhookService?: WebhookService
|
|
302
323
|
/** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
|
|
303
324
|
metaService?: MetaService
|
|
304
325
|
/** V8 precise-coverage collector (`pikku dev --coverage` only) */
|
|
@@ -315,6 +336,11 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
315
336
|
auditLog?: AuditLog
|
|
316
337
|
/** Session store for persisting user sessions keyed by pikkuUserId */
|
|
317
338
|
sessionStore?: SessionStore
|
|
339
|
+
/**
|
|
340
|
+
* Resolves and administers user scopes. Called when building a session (e.g.
|
|
341
|
+
* better-auth's `mapSession`), never by the function runner.
|
|
342
|
+
*/
|
|
343
|
+
scopeService?: ScopeService
|
|
318
344
|
}
|
|
319
345
|
|
|
320
346
|
/**
|
|
@@ -341,6 +367,8 @@ export type PikkuWire<
|
|
|
341
367
|
traceId: string
|
|
342
368
|
/** Function id for the current invocation */
|
|
343
369
|
functionId: string
|
|
370
|
+
/** The addon instance namespace (wireAddon name) currently executing, if any */
|
|
371
|
+
addonNamespace: string
|
|
344
372
|
http: PikkuHTTP<In>
|
|
345
373
|
mcp: PikkuMCP<MCPTools>
|
|
346
374
|
channel: [IsChannel] extends [null]
|
package/src/types/state.types.ts
CHANGED
|
@@ -84,7 +84,29 @@ export interface PikkuPackageState {
|
|
|
84
84
|
addons: {
|
|
85
85
|
packages: Map<
|
|
86
86
|
string,
|
|
87
|
-
{
|
|
87
|
+
{
|
|
88
|
+
package: string
|
|
89
|
+
rpcEndpoint?: string
|
|
90
|
+
auth?: boolean
|
|
91
|
+
tags?: string[]
|
|
92
|
+
/** Per-instance name-aliases: logical name the addon reads -> actual project secret name */
|
|
93
|
+
secretOverrides?: Record<string, string>
|
|
94
|
+
/** Per-instance name-aliases: logical name the addon reads -> actual project variable name */
|
|
95
|
+
variableOverrides?: Record<string, string>
|
|
96
|
+
/** Per-instance name-aliases: logical name the addon reads -> actual project credential name */
|
|
97
|
+
credentialOverrides?: Record<string, string>
|
|
98
|
+
/** Set by `wireRemoteAddon`: this namespace is consumed remotely over HTTP, not bundled */
|
|
99
|
+
remote?: boolean
|
|
100
|
+
/** Remote host base URL (wireRemoteAddon) — string or resolver over singleton services */
|
|
101
|
+
serverUrl?: string | ((services: any) => string | Promise<string>)
|
|
102
|
+
/** Consumer-side auth binding for the hosted addon (wireRemoteAddon) */
|
|
103
|
+
remoteAuth?:
|
|
104
|
+
| { credentialId: string }
|
|
105
|
+
| { secretId: string }
|
|
106
|
+
| { resolve: (services: any, wire: any) => string | Promise<string> }
|
|
107
|
+
/** Map consumer-facing fn name → remote fn name (wireRemoteAddon) */
|
|
108
|
+
remoteName?: (fn: string) => string
|
|
109
|
+
}
|
|
88
110
|
>
|
|
89
111
|
}
|
|
90
112
|
http: {
|
|
@@ -144,8 +166,6 @@ export interface PikkuPackageState {
|
|
|
144
166
|
tagGroup: Record<string, CorePikkuChannelMiddleware[]>
|
|
145
167
|
}
|
|
146
168
|
permissions: {
|
|
147
|
-
tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
148
|
-
httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
149
169
|
global: (CorePermissionGroup | CorePikkuPermission)[]
|
|
150
170
|
}
|
|
151
171
|
misc: {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HMAC-SHA256 a payload, hex-encoded.
|
|
5
|
+
*
|
|
6
|
+
* Senders wrap this in their own scheme prefix (`sha256=`, `v0=`, …).
|
|
7
|
+
*
|
|
8
|
+
* @param secret - The signing key shared with the other side
|
|
9
|
+
* @param payload - The exact bytes that were signed
|
|
10
|
+
*/
|
|
11
|
+
export function hmacSha256Hex(secret: string, payload: string): string {
|
|
12
|
+
return createHmac('sha256', secret).update(payload).digest('hex')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compare two signatures without leaking their contents through timing.
|
|
17
|
+
*
|
|
18
|
+
* Returns false rather than throwing on a length mismatch, which is what
|
|
19
|
+
* `timingSafeEqual` does when the buffers differ in size.
|
|
20
|
+
*/
|
|
21
|
+
export function timingSafeStringEqual(a: string, b: string): boolean {
|
|
22
|
+
try {
|
|
23
|
+
return timingSafeEqual(Buffer.from(a), Buffer.from(b))
|
|
24
|
+
} catch {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
}
|