@pikku/core 0.12.17 → 0.12.19
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 +20 -13
- package/dist/function/function-runner.js +22 -1
- package/dist/function/functions.types.d.ts +1 -0
- package/dist/services/in-memory-session-store.d.ts +8 -0
- package/dist/services/in-memory-session-store.js +12 -0
- package/dist/services/in-memory-workflow-service.d.ts +5 -2
- package/dist/services/in-memory-workflow-service.js +3 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +1 -0
- package/dist/services/session-store.d.ts +6 -0
- package/dist/services/session-store.js +1 -0
- package/dist/services/user-session-service.d.ts +12 -10
- package/dist/services/user-session-service.js +18 -21
- package/dist/services/workflow-service.d.ts +5 -2
- package/dist/testing/service-tests.d.ts +2 -0
- package/dist/testing/service-tests.js +50 -11
- package/dist/types/core.types.d.ts +3 -0
- package/dist/wirings/channel/channel-store.d.ts +5 -6
- package/dist/wirings/channel/local/local-channel-runner.js +7 -4
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +25 -8
- package/dist/wirings/cli/channel/cli-channel-runner.js +4 -2
- package/dist/wirings/cli/cli-runner.js +5 -3
- package/dist/wirings/http/http-runner.js +1 -1
- package/dist/wirings/mcp/mcp-runner.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +4 -1
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +5 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +6 -2
- package/dist/wirings/workflow/workflow.types.d.ts +14 -0
- package/package.json +1 -1
- package/src/function/function-runner.ts +36 -1
- package/src/function/functions.types.ts +5 -14
- package/src/services/in-memory-session-store.test.ts +58 -0
- package/src/services/in-memory-session-store.ts +21 -0
- package/src/services/in-memory-workflow-service.test.ts +21 -0
- package/src/services/in-memory-workflow-service.ts +8 -1
- package/src/services/index.ts +2 -0
- package/src/services/session-store.ts +9 -0
- package/src/services/user-session-service.test.ts +53 -19
- package/src/services/user-session-service.ts +21 -22
- package/src/services/workflow-service.ts +6 -1
- package/src/testing/service-tests.ts +64 -11
- package/src/types/core.types.ts +3 -0
- package/src/wirings/channel/channel-store.ts +5 -8
- package/src/wirings/channel/local/local-channel-runner.ts +10 -4
- package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -5
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +30 -9
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -2
- package/src/wirings/cli/cli-runner.ts +7 -3
- package/src/wirings/http/http-runner.ts +3 -1
- package/src/wirings/mcp/mcp-runner.ts +3 -1
- package/src/wirings/scheduler/scheduler-runner.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.ts +5 -1
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +13 -3
- package/src/wirings/workflow/workflow.types.ts +15 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
## 0.12.19
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- b9ed73e: Add deterministic workflow planned-step metadata support and SSE init stream payload generation.
|
|
6
|
+
- Persist `deterministic` and `plannedSteps` on workflow runs in core and service adapters.
|
|
7
|
+
- Expose planned-step metadata on workflow run status responses.
|
|
8
|
+
- Emit an initial `type: 'init'` SSE event for deterministic workflow streams before incremental updates.
|
|
9
|
+
- Add CLI tests covering serialized stream route output for init/update/done event behavior.
|
|
10
|
+
|
|
1
11
|
## 0.12.4
|
|
2
12
|
|
|
13
|
+
## 0.12.18
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 311c0c4: Unify session persistence through SessionStore, remove session blob from ChannelStore
|
|
18
|
+
- PikkuSessionService now persists sessions via SessionStore on set()/clear() instead of every function call
|
|
19
|
+
- ChannelStore no longer stores session data — maps channelId to pikkuUserId only
|
|
20
|
+
- ChannelStore API: setUserSession/getChannelAndSession replaced with setPikkuUserId/getChannel
|
|
21
|
+
- Serverless channel runner resolves sessions from SessionStore using pikkuUserId from ChannelStore
|
|
22
|
+
|
|
3
23
|
## 0.12.17
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -39,14 +59,12 @@
|
|
|
39
59
|
### Patch Changes
|
|
40
60
|
|
|
41
61
|
- 9e8605f: Add Workers for Platforms dispatch namespace support and AI agent fixes.
|
|
42
|
-
|
|
43
62
|
- deploy-cloudflare: Thread dispatchNamespace through deploy pipeline, reads CF_DISPATCH_NAMESPACE env var
|
|
44
63
|
- core: Fix auth-gated tools visible to unauthenticated sessions (null session now hides permission-gated items)
|
|
45
64
|
- core: Recursive null stripping in AI agent tool call resume path
|
|
46
65
|
- ai-vercel: Handle anyOf/oneOf/array types when making optional fields nullable for strict providers
|
|
47
66
|
|
|
48
67
|
- 624097e: Add deploy pipeline with provider-agnostic architecture
|
|
49
|
-
|
|
50
68
|
- Add MetaService with explicit typed API, absorb WiringService reads
|
|
51
69
|
- Add deployment service, traceId propagation, scoped logger
|
|
52
70
|
- Rewrite analyzer: one function = one worker, gateways dispatch via RPC
|
|
@@ -69,7 +87,6 @@
|
|
|
69
87
|
### Patch Changes
|
|
70
88
|
|
|
71
89
|
- f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
|
|
72
|
-
|
|
73
90
|
- Unified CredentialService with lazy loading per user via pikkuUserId
|
|
74
91
|
- wire.getCredential() for typed single credential lookup
|
|
75
92
|
- MissingCredentialError with structured payload for client-side connect flows
|
|
@@ -176,7 +193,6 @@
|
|
|
176
193
|
- 7d369f3: Fix agent sub-agent tool execution failures: use UUID for sub-agent thread IDs (was exceeding varchar(36) DB column), and synthesize error results for failed tool calls in non-streaming run() to prevent "Tool result is missing" cascading errors.
|
|
177
194
|
- 508a796: Fix MCP server not exposing addon tools: resolve namespaced function IDs in MCP runner, load addon schemas after schema generation, and use resolveFunctionMeta for MCP JSON serialization
|
|
178
195
|
- ffe83af: Add Web Response passthrough support and fix close() flushing
|
|
179
|
-
|
|
180
196
|
- HTTP runner detects when a function returns a Web `Response` object and applies it directly via `applyWebResponse()`, enabling seamless integration with libraries like Auth.js
|
|
181
197
|
- Add `send()` method to `PikkuHTTPResponse` for setting body without Content-Type headers
|
|
182
198
|
- Add `headers()` method to `PikkuHTTPRequest` for retrieving all headers as a record
|
|
@@ -190,7 +206,6 @@
|
|
|
190
206
|
### Patch Changes
|
|
191
207
|
|
|
192
208
|
- cc4c9e9: Add gateway meta-wiring for messaging platforms:
|
|
193
|
-
|
|
194
209
|
- New `wireGateway()` API with three transport types: webhook, websocket, listener
|
|
195
210
|
- `GatewayAdapter` interface for platform-specific parse/send logic
|
|
196
211
|
- `PikkuGateway` wire object (`wire.gateway`) with senderId, platform, and send()
|
|
@@ -207,7 +222,6 @@
|
|
|
207
222
|
|
|
208
223
|
- 62a8725: Rename 'external' to 'addon' throughout the codebase. All types, functions, config keys, and CLI options previously named `external` or `External` are now named `addon` or `Addon` (e.g. `ExternalPackageConfig` → `AddonConfig`, `externalPackages` → `addons`, `function-external` → `function-addon`).
|
|
209
224
|
- a3bdb0d: Add AI middleware hooks for per-tool-call lifecycle and post-step observability:
|
|
210
|
-
|
|
211
225
|
- `beforeToolCall` / `afterToolCall`: per-tool-call hooks for logging, caching, input sanitization, and result transformation
|
|
212
226
|
- `afterStep`: post-step observation hook with full step context (text, toolCalls, toolResults, usage, finishReason)
|
|
213
227
|
- `onError`: error-specific hook for alerting and diagnostics (non-throwing, won't affect error flow)
|
|
@@ -306,18 +320,15 @@
|
|
|
306
320
|
- ea652dc: Refactor channel middleware handling and add lifecycle middleware support
|
|
307
321
|
|
|
308
322
|
**Breaking Changes:**
|
|
309
|
-
|
|
310
323
|
- Improved middleware resolution for channel message handlers to properly combine channel-level and message-level middleware
|
|
311
324
|
- Fixed cache key collisions when multiple message handlers use the same function
|
|
312
325
|
|
|
313
326
|
**New Features:**
|
|
314
|
-
|
|
315
327
|
- Add `runChannelLifecycleWithMiddleware` helper in `channel-common.ts` for consistent lifecycle function execution
|
|
316
328
|
- Support middleware on `onConnect` and `onDisconnect` lifecycle functions
|
|
317
329
|
- Channel-level middleware now properly applies to all messages in the channel
|
|
318
330
|
|
|
319
331
|
**Bug Fixes:**
|
|
320
|
-
|
|
321
332
|
- Fix middleware ordering: channel middleware → message middleware → inherited middleware
|
|
322
333
|
- Fix cache key generation to include routing information (prevents cache collisions)
|
|
323
334
|
- Properly detect wrapper objects vs direct function configs for message handlers
|
|
@@ -325,13 +336,11 @@
|
|
|
325
336
|
- 4349ec5: Add file-based storage implementations for serverless environments
|
|
326
337
|
|
|
327
338
|
**New Services:**
|
|
328
|
-
|
|
329
339
|
- Add `FileChannelStore` for file-based channel storage (suitable for AWS Lambda /tmp)
|
|
330
340
|
- Add `FileEventHubStore` for file-based event hub subscriptions
|
|
331
341
|
- Export new services in package.json for use in serverless runtimes
|
|
332
342
|
|
|
333
343
|
**Bug Fixes:**
|
|
334
|
-
|
|
335
344
|
- Fix serverless channel runner to handle disconnect gracefully when channel is already cleaned up
|
|
336
345
|
- Fix MCP runner to pass `mcp` service to functions and use correct function type
|
|
337
346
|
|
|
@@ -407,7 +416,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
407
416
|
### Ordered Execution System
|
|
408
417
|
|
|
409
418
|
Both middleware and permissions now execute in a specific hierarchical order:
|
|
410
|
-
|
|
411
419
|
1. **Wiring Tags** - Tag-based middleware/permissions from wiring level (e.g., HTTP route tags)
|
|
412
420
|
2. **Wiring Middleware/Permissions** - Direct wiring-level middleware/permissions
|
|
413
421
|
3. **Function Middleware** - Function-level middleware
|
|
@@ -472,7 +480,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
472
480
|
- 7c592b8: feat: support for required services and improved service configuration
|
|
473
481
|
|
|
474
482
|
This release includes several enhancements to service management and configuration:
|
|
475
|
-
|
|
476
483
|
- Added support for required services configuration
|
|
477
484
|
- Improved service discovery and registration
|
|
478
485
|
- Added typed RPC clients for service communication
|
|
@@ -4,11 +4,31 @@ import { runPermissions } from '../permissions.js';
|
|
|
4
4
|
import { pikkuState } from '../pikku-state.js';
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
|
|
6
6
|
import { parseVersionedId } from '../version.js';
|
|
7
|
-
import { createFunctionSessionWireProps } from '../services/user-session-service.js';
|
|
7
|
+
import { PikkuSessionService, createFunctionSessionWireProps, } from '../services/user-session-service.js';
|
|
8
8
|
import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
|
|
9
9
|
import { PikkuCredentialWireService, createWireServicesCredentialWireProps, } from '../services/credential-wire-service.js';
|
|
10
|
+
import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js';
|
|
10
11
|
import { rpcService } from '../wirings/rpc/rpc-runner.js';
|
|
11
12
|
import { closeWireServices } from '../utils.js';
|
|
13
|
+
async function resolveSession(wire, singletonServices, sessionService) {
|
|
14
|
+
const pikkuUserId = defaultPikkuUserIdResolver(wire);
|
|
15
|
+
if (pikkuUserId) {
|
|
16
|
+
wire.pikkuUserId = pikkuUserId;
|
|
17
|
+
if (sessionService instanceof PikkuSessionService) {
|
|
18
|
+
sessionService.setPikkuUserId(pikkuUserId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const { sessionStore } = singletonServices;
|
|
22
|
+
if (!sessionStore || !pikkuUserId)
|
|
23
|
+
return;
|
|
24
|
+
if (!wire.session) {
|
|
25
|
+
const stored = await sessionStore.get(pikkuUserId);
|
|
26
|
+
if (stored) {
|
|
27
|
+
wire.session = stored;
|
|
28
|
+
sessionService?.setInitial(stored);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* Get or create singleton services for an addon package.
|
|
14
34
|
* Services are cached in pikkuState to avoid recreation on each call.
|
|
@@ -134,6 +154,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
134
154
|
}
|
|
135
155
|
// Helper function to run permissions and execute the function
|
|
136
156
|
const executeFunction = async () => {
|
|
157
|
+
await resolveSession(resolvedWire, resolvedSingletonServices, sessionService);
|
|
137
158
|
if (sessionService) {
|
|
138
159
|
resolvedWire.session = sessionService.freezeInitial();
|
|
139
160
|
resolvedWire.setSession = (s) => sessionService.set(s);
|
|
@@ -146,6 +146,7 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
|
|
|
146
146
|
remote?: boolean;
|
|
147
147
|
mcp?: boolean;
|
|
148
148
|
readonly?: boolean;
|
|
149
|
+
deploy?: 'serverless' | 'server' | 'auto';
|
|
149
150
|
approvalRequired?: boolean;
|
|
150
151
|
approvalDescription?: any;
|
|
151
152
|
func: PikkuFunction;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
import type { SessionStore } from './session-store.js';
|
|
3
|
+
export declare class InMemorySessionStore<UserSession extends CoreUserSession = CoreUserSession> implements SessionStore<UserSession> {
|
|
4
|
+
private sessions;
|
|
5
|
+
get(pikkuUserId: string): Promise<UserSession | undefined>;
|
|
6
|
+
set(pikkuUserId: string, session: UserSession): Promise<void>;
|
|
7
|
+
clear(pikkuUserId: string): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class InMemorySessionStore {
|
|
2
|
+
sessions = new Map();
|
|
3
|
+
async get(pikkuUserId) {
|
|
4
|
+
return this.sessions.get(pikkuUserId);
|
|
5
|
+
}
|
|
6
|
+
async set(pikkuUserId, session) {
|
|
7
|
+
this.sessions.set(pikkuUserId, session);
|
|
8
|
+
}
|
|
9
|
+
async clear(pikkuUserId) {
|
|
10
|
+
this.sessions.delete(pikkuUserId);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service.js';
|
|
2
2
|
import type { SerializedError } from '../types/core.types.js';
|
|
3
|
-
import type { WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
3
|
+
import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
4
4
|
/**
|
|
5
5
|
* In-memory implementation of WorkflowService for inline-only execution
|
|
6
6
|
*
|
|
@@ -24,7 +24,10 @@ export declare class InMemoryWorkflowService extends PikkuWorkflowService implem
|
|
|
24
24
|
private runState;
|
|
25
25
|
private branchKeys;
|
|
26
26
|
private workflowVersions;
|
|
27
|
-
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire
|
|
27
|
+
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
|
|
28
|
+
deterministic?: boolean;
|
|
29
|
+
plannedSteps?: WorkflowPlannedStep[];
|
|
30
|
+
}): Promise<string>;
|
|
28
31
|
getRun(id: string): Promise<WorkflowRun | null>;
|
|
29
32
|
getRunHistory(runId: string): Promise<Array<StepState & {
|
|
30
33
|
stepName: string;
|
|
@@ -23,7 +23,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
23
23
|
runState = new Map(); // keyed by runId
|
|
24
24
|
branchKeys = new Map(); // keyed by stepId
|
|
25
25
|
workflowVersions = new Map(); // keyed by `${name}:${graphHash}`
|
|
26
|
-
async createRun(workflowName, input, inline, graphHash, wire) {
|
|
26
|
+
async createRun(workflowName, input, inline, graphHash, wire, options) {
|
|
27
27
|
const runId = randomUUID();
|
|
28
28
|
const now = new Date();
|
|
29
29
|
const run = {
|
|
@@ -33,6 +33,8 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
33
33
|
input,
|
|
34
34
|
inline,
|
|
35
35
|
graphHash,
|
|
36
|
+
deterministic: options?.deterministic,
|
|
37
|
+
plannedSteps: options?.plannedSteps,
|
|
36
38
|
wire,
|
|
37
39
|
createdAt: now,
|
|
38
40
|
updatedAt: now,
|
package/dist/services/index.d.ts
CHANGED
|
@@ -36,4 +36,6 @@ export { TypedCredentialService } from './typed-credential-service.js';
|
|
|
36
36
|
export type { CredentialStatusInfo, CredentialMetaInfo, } from './typed-credential-service.js';
|
|
37
37
|
export type { VariableStatus, VariableMeta } from './typed-variables-service.js';
|
|
38
38
|
export type { MetaService } from './meta-service.js';
|
|
39
|
+
export type { SessionStore } from './session-store.js';
|
|
40
|
+
export { InMemorySessionStore } from './in-memory-session-store.js';
|
|
39
41
|
export type { MCPMeta, RPCMetaRecord, ServiceMeta, ServicesMetaRecord, MiddlewareDefinitionMeta, MiddlewareInstanceMeta, GroupMeta, MiddlewareGroupsMeta, PermissionDefinitionMeta, PermissionsGroupsMeta, FunctionsMeta, FunctionMeta, MiddlewareMeta, PermissionMeta, AgentsMeta, AgentMeta, } from './meta-service.js';
|
package/dist/services/index.js
CHANGED
|
@@ -17,3 +17,4 @@ export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
|
17
17
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
18
18
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
19
19
|
export { TypedCredentialService } from './typed-credential-service.js';
|
|
20
|
+
export { InMemorySessionStore } from './in-memory-session-store.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
export interface SessionStore<UserSession extends CoreUserSession = CoreUserSession> {
|
|
3
|
+
get(pikkuUserId: string): Promise<UserSession | undefined>;
|
|
4
|
+
set(pikkuUserId: string, session: UserSession): Promise<void>;
|
|
5
|
+
clear(pikkuUserId: string): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChannelStore } from '../wirings/channel/channel-store.js';
|
|
2
1
|
import type { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
import type { SessionStore } from './session-store.js';
|
|
3
3
|
export interface SessionService<UserSession extends CoreUserSession> {
|
|
4
4
|
sessionChanged: boolean;
|
|
5
5
|
initial: UserSession | undefined;
|
|
@@ -7,31 +7,33 @@ export interface SessionService<UserSession extends CoreUserSession> {
|
|
|
7
7
|
freezeInitial(): UserSession | undefined;
|
|
8
8
|
set(session: UserSession): Promise<void> | void;
|
|
9
9
|
clear(): Promise<void> | void;
|
|
10
|
-
get():
|
|
10
|
+
get(): UserSession | undefined;
|
|
11
11
|
}
|
|
12
12
|
export declare class PikkuSessionService<UserSession extends CoreUserSession> implements SessionService<UserSession> {
|
|
13
|
-
private
|
|
14
|
-
private channelId?;
|
|
13
|
+
private sessionStore?;
|
|
15
14
|
sessionChanged: boolean;
|
|
16
15
|
initial: UserSession | undefined;
|
|
17
16
|
private session;
|
|
18
|
-
|
|
17
|
+
private pikkuUserId?;
|
|
18
|
+
constructor(sessionStore?: SessionStore<UserSession> | undefined);
|
|
19
|
+
setPikkuUserId(id: string): void;
|
|
20
|
+
getPikkuUserId(): string | undefined;
|
|
19
21
|
setInitial(session: UserSession): void;
|
|
20
22
|
freezeInitial(): UserSession | undefined;
|
|
21
|
-
set(session: UserSession):
|
|
22
|
-
clear():
|
|
23
|
-
get():
|
|
23
|
+
set(session: UserSession): Promise<void>;
|
|
24
|
+
clear(): Promise<void>;
|
|
25
|
+
get(): UserSession | undefined;
|
|
24
26
|
}
|
|
25
27
|
export declare function createMiddlewareSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
|
|
26
28
|
session: UserSession | undefined;
|
|
27
29
|
setSession: (s: UserSession) => void;
|
|
28
|
-
getSession: () => UserSession |
|
|
30
|
+
getSession: () => UserSession | undefined;
|
|
29
31
|
hasSessionChanged: () => boolean;
|
|
30
32
|
};
|
|
31
33
|
export declare function createFunctionSessionWireProps<UserSession extends CoreUserSession>(session: SessionService<UserSession>): {
|
|
32
34
|
session: UserSession | undefined;
|
|
33
35
|
setSession: (s: UserSession) => void | Promise<void>;
|
|
34
36
|
clearSession: () => void | Promise<void>;
|
|
35
|
-
getSession: () => UserSession |
|
|
37
|
+
getSession: () => UserSession | undefined;
|
|
36
38
|
hasSessionChanged: () => boolean;
|
|
37
39
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export class PikkuSessionService {
|
|
2
|
-
|
|
3
|
-
channelId;
|
|
2
|
+
sessionStore;
|
|
4
3
|
sessionChanged = false;
|
|
5
4
|
initial;
|
|
6
5
|
session;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
pikkuUserId;
|
|
7
|
+
constructor(sessionStore) {
|
|
8
|
+
this.sessionStore = sessionStore;
|
|
9
|
+
}
|
|
10
|
+
setPikkuUserId(id) {
|
|
11
|
+
this.pikkuUserId = id;
|
|
12
|
+
}
|
|
13
|
+
getPikkuUserId() {
|
|
14
|
+
return this.pikkuUserId;
|
|
13
15
|
}
|
|
14
16
|
setInitial(session) {
|
|
15
17
|
this.session = session;
|
|
@@ -20,26 +22,21 @@ export class PikkuSessionService {
|
|
|
20
22
|
}
|
|
21
23
|
return this.initial;
|
|
22
24
|
}
|
|
23
|
-
set(session) {
|
|
25
|
+
async set(session) {
|
|
24
26
|
this.sessionChanged = true;
|
|
25
27
|
this.session = session;
|
|
26
|
-
|
|
28
|
+
if (this.sessionStore && this.pikkuUserId) {
|
|
29
|
+
await this.sessionStore.set(this.pikkuUserId, session);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
|
-
clear() {
|
|
32
|
+
async clear() {
|
|
29
33
|
this.sessionChanged = true;
|
|
30
34
|
this.session = undefined;
|
|
31
|
-
|
|
35
|
+
if (this.sessionStore && this.pikkuUserId) {
|
|
36
|
+
await this.sessionStore.clear(this.pikkuUserId);
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
get() {
|
|
34
|
-
if (this.channelStore) {
|
|
35
|
-
const channel = this.channelStore.getChannelAndSession(this.channelId);
|
|
36
|
-
if (channel instanceof Promise) {
|
|
37
|
-
return channel.then(({ session }) => session);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
return channel.session;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
40
|
return this.session;
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { SerializedError } from '../types/core.types.js';
|
|
2
|
-
import type { WorkflowRun, WorkflowRunWire, WorkflowRunStatus, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
|
|
2
|
+
import type { WorkflowRun, WorkflowPlannedStep, WorkflowRunWire, WorkflowRunStatus, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Interface for workflow orchestration
|
|
5
5
|
* Handles workflow execution, replay, orchestration logic, and run-level state
|
|
6
6
|
*/
|
|
7
7
|
export interface WorkflowService {
|
|
8
|
-
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire
|
|
8
|
+
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
|
|
9
|
+
deterministic?: boolean;
|
|
10
|
+
plannedSteps?: WorkflowPlannedStep[];
|
|
11
|
+
}): Promise<string>;
|
|
9
12
|
getRun(id: string): Promise<WorkflowRun | null>;
|
|
10
13
|
getRunStatus(id: string): Promise<WorkflowRunStatus | null>;
|
|
11
14
|
getRunHistory(runId: string): Promise<Array<StepState & {
|
|
@@ -8,6 +8,7 @@ import type { AIRunStateService } from '../services/ai-run-state-service.js';
|
|
|
8
8
|
import type { SecretService } from '../services/secret-service.js';
|
|
9
9
|
import type { CredentialService } from '../services/credential-service.js';
|
|
10
10
|
import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
|
|
11
|
+
import type { SessionStore } from '../services/session-store.js';
|
|
11
12
|
export interface ServiceTestConfig {
|
|
12
13
|
name: string;
|
|
13
14
|
services: {
|
|
@@ -34,6 +35,7 @@ export interface ServiceTestConfig {
|
|
|
34
35
|
}) => Promise<CredentialService & {
|
|
35
36
|
rotateKEK?(): Promise<number>;
|
|
36
37
|
}>;
|
|
38
|
+
sessionStore?: () => Promise<SessionStore>;
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
export declare function defineServiceTests(config: ServiceTestConfig): void;
|
|
@@ -9,27 +9,31 @@ export function defineServiceTests(config) {
|
|
|
9
9
|
before(async () => {
|
|
10
10
|
store = await factory();
|
|
11
11
|
});
|
|
12
|
-
test('addChannel and
|
|
12
|
+
test('addChannel and getChannel', async () => {
|
|
13
13
|
await store.addChannel({
|
|
14
14
|
channelId: 'ch-1',
|
|
15
15
|
channelName: 'test-channel',
|
|
16
16
|
openingData: { foo: 'bar' },
|
|
17
17
|
});
|
|
18
|
-
const result = await store.
|
|
18
|
+
const result = await store.getChannel('ch-1');
|
|
19
19
|
assert.equal(result.channelId, 'ch-1');
|
|
20
20
|
assert.equal(result.channelName, 'test-channel');
|
|
21
21
|
assert.deepEqual(result.openingData, { foo: 'bar' });
|
|
22
|
-
assert.
|
|
22
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
23
23
|
});
|
|
24
|
-
test('
|
|
25
|
-
|
|
26
|
-
await store.
|
|
27
|
-
|
|
28
|
-
assert.deepEqual(result.session, session);
|
|
24
|
+
test('setPikkuUserId', async () => {
|
|
25
|
+
await store.setPikkuUserId('ch-1', 'user-1');
|
|
26
|
+
const result = await store.getChannel('ch-1');
|
|
27
|
+
assert.equal(result.pikkuUserId, 'user-1');
|
|
29
28
|
});
|
|
30
|
-
test('
|
|
29
|
+
test('setPikkuUserId to null', async () => {
|
|
30
|
+
await store.setPikkuUserId('ch-1', null);
|
|
31
|
+
const result = await store.getChannel('ch-1');
|
|
32
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
33
|
+
});
|
|
34
|
+
test('getChannel throws for missing channel', async () => {
|
|
31
35
|
await assert.rejects(async () => {
|
|
32
|
-
await store.
|
|
36
|
+
await store.getChannel('missing');
|
|
33
37
|
}, { message: 'Channel not found: missing' });
|
|
34
38
|
});
|
|
35
39
|
test('removeChannels', async () => {
|
|
@@ -39,7 +43,7 @@ export function defineServiceTests(config) {
|
|
|
39
43
|
});
|
|
40
44
|
await store.removeChannels(['ch-2']);
|
|
41
45
|
await assert.rejects(async () => {
|
|
42
|
-
await store.
|
|
46
|
+
await store.getChannel('ch-2');
|
|
43
47
|
});
|
|
44
48
|
});
|
|
45
49
|
test('removeChannels with empty array is no-op', async () => {
|
|
@@ -693,4 +697,39 @@ export function defineServiceTests(config) {
|
|
|
693
697
|
});
|
|
694
698
|
});
|
|
695
699
|
}
|
|
700
|
+
if (services.sessionStore) {
|
|
701
|
+
const factory = services.sessionStore;
|
|
702
|
+
describe(`SessionStore [${name}]`, () => {
|
|
703
|
+
let store;
|
|
704
|
+
before(async () => {
|
|
705
|
+
store = await factory();
|
|
706
|
+
});
|
|
707
|
+
test('get returns undefined for unknown user', async () => {
|
|
708
|
+
const result = await store.get('unknown-user');
|
|
709
|
+
assert.equal(result, undefined);
|
|
710
|
+
});
|
|
711
|
+
test('set and get round-trip', async () => {
|
|
712
|
+
const session = { userId: 'user-1', organizationId: 'org-1' };
|
|
713
|
+
await store.set('user-1', session);
|
|
714
|
+
const result = await store.get('user-1');
|
|
715
|
+
assert.deepEqual(result, session);
|
|
716
|
+
});
|
|
717
|
+
test('set overwrites previous session', async () => {
|
|
718
|
+
await store.set('user-2', { userId: 'user-2', role: 'admin' });
|
|
719
|
+
await store.set('user-2', { userId: 'user-2', role: 'member' });
|
|
720
|
+
const result = await store.get('user-2');
|
|
721
|
+
assert.deepEqual(result, { userId: 'user-2', role: 'member' });
|
|
722
|
+
});
|
|
723
|
+
test('clear removes session', async () => {
|
|
724
|
+
await store.set('user-3', { userId: 'user-3' });
|
|
725
|
+
assert.ok(await store.get('user-3'));
|
|
726
|
+
await store.clear('user-3');
|
|
727
|
+
const result = await store.get('user-3');
|
|
728
|
+
assert.equal(result, undefined);
|
|
729
|
+
});
|
|
730
|
+
test('clear is no-op for unknown user', async () => {
|
|
731
|
+
await store.clear('nonexistent');
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
}
|
|
696
735
|
}
|
|
@@ -25,6 +25,7 @@ import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.
|
|
|
25
25
|
import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
|
|
26
26
|
import type { CredentialService } from '../services/credential-service.js';
|
|
27
27
|
import type { MetaService } from '../services/meta-service.js';
|
|
28
|
+
import type { SessionStore } from '../services/session-store.js';
|
|
28
29
|
export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
|
|
29
30
|
export interface FunctionServicesMeta {
|
|
30
31
|
optimized: boolean;
|
|
@@ -181,6 +182,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
181
182
|
credentialService?: CredentialService;
|
|
182
183
|
/** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
|
|
183
184
|
metaService?: MetaService;
|
|
185
|
+
/** Session store for persisting user sessions keyed by pikkuUserId */
|
|
186
|
+
sessionStore?: SessionStore;
|
|
184
187
|
}
|
|
185
188
|
/**
|
|
186
189
|
* Represents different forms of wire within Pikku and the outside world.
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import type { CoreUserSession } from '../../types/core.types.js';
|
|
2
1
|
export type Channel<ChannelType = unknown, OpeningData = unknown> = {
|
|
3
2
|
channelId: string;
|
|
4
3
|
channelName: string;
|
|
5
4
|
channelObject?: ChannelType;
|
|
6
5
|
openingData?: OpeningData;
|
|
7
6
|
};
|
|
8
|
-
export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown,
|
|
7
|
+
export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown, TypedChannel = Channel<ChannelType, OpeningData>> {
|
|
9
8
|
abstract addChannel(channel: Channel<ChannelType, OpeningData>): Promise<void> | void;
|
|
10
9
|
abstract removeChannels(channelId: string[]): Promise<void> | void;
|
|
11
|
-
abstract
|
|
12
|
-
abstract
|
|
13
|
-
|
|
10
|
+
abstract setPikkuUserId(channelId: string, pikkuUserId: string | null): Promise<void> | void;
|
|
11
|
+
abstract getChannel(channelId: string): Promise<TypedChannel & {
|
|
12
|
+
pikkuUserId?: string;
|
|
14
13
|
}> | (TypedChannel & {
|
|
15
|
-
|
|
14
|
+
pikkuUserId?: string;
|
|
16
15
|
});
|
|
17
16
|
}
|
|
@@ -15,7 +15,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
15
15
|
let wireServices;
|
|
16
16
|
let closedWireServices = false;
|
|
17
17
|
let channelHandler;
|
|
18
|
-
const userSession = new PikkuSessionService();
|
|
18
|
+
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
19
19
|
let http;
|
|
20
20
|
if (request) {
|
|
21
21
|
http = createHTTPWire(request, response);
|
|
@@ -79,7 +79,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
79
79
|
singletonServices.logger.error(`Error handling onConnect: ${e}`);
|
|
80
80
|
const errorResponse = getErrorResponse(e);
|
|
81
81
|
channel.send({
|
|
82
|
-
error: errorResponse?.message ??
|
|
82
|
+
error: errorResponse?.message ??
|
|
83
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
83
84
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
84
85
|
});
|
|
85
86
|
}
|
|
@@ -102,7 +103,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
102
103
|
singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
|
|
103
104
|
const errorResponse = getErrorResponse(e);
|
|
104
105
|
channel.send({
|
|
105
|
-
error: errorResponse?.message ??
|
|
106
|
+
error: errorResponse?.message ??
|
|
107
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
106
108
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
107
109
|
});
|
|
108
110
|
}
|
|
@@ -119,7 +121,8 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
119
121
|
singletonServices.logger.error(e);
|
|
120
122
|
const errorResponse = getErrorResponse(e);
|
|
121
123
|
channel.send({
|
|
122
|
-
error: errorResponse?.message ??
|
|
124
|
+
error: errorResponse?.message ??
|
|
125
|
+
(isProduction() ? 'Internal server error' : e.message),
|
|
123
126
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
124
127
|
});
|
|
125
128
|
setTimeout(() => channel.close(), 200);
|