@pikku/core 0.12.20 → 0.12.21

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 (138) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/dev/hot-reload.js +20 -6
  3. package/dist/errors/error-handler.d.ts +1 -1
  4. package/dist/errors/error-handler.js +2 -2
  5. package/dist/function/functions.types.d.ts +3 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/middleware/index.d.ts +2 -2
  9. package/dist/middleware/index.js +2 -2
  10. package/dist/middleware/telemetry.d.ts +2 -2
  11. package/dist/middleware/telemetry.js +2 -2
  12. package/dist/middleware-runner.d.ts +15 -27
  13. package/dist/middleware-runner.js +25 -30
  14. package/dist/permissions.d.ts +15 -22
  15. package/dist/permissions.js +30 -25
  16. package/dist/pikku-request.js +1 -1
  17. package/dist/pikku-state.js +2 -3
  18. package/dist/services/ai-agent-runner-service.d.ts +1 -0
  19. package/dist/services/in-memory-queue-service.js +1 -1
  20. package/dist/services/in-memory-workflow-service.d.ts +15 -13
  21. package/dist/services/in-memory-workflow-service.js +31 -13
  22. package/dist/services/local-content.js +8 -1
  23. package/dist/services/user-session-service.d.ts +1 -1
  24. package/dist/testing/service-tests.js +24 -0
  25. package/dist/types/core.types.d.ts +4 -0
  26. package/dist/types/state.types.d.ts +2 -18
  27. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
  28. package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
  29. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
  30. package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
  31. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
  32. package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
  33. package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
  34. package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
  35. package/dist/wirings/channel/channel-handler.js +1 -1
  36. package/dist/wirings/channel/channel-store.d.ts +13 -0
  37. package/dist/wirings/channel/channel.types.d.ts +3 -0
  38. package/dist/wirings/channel/local/local-channel-runner.js +23 -5
  39. package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
  40. package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
  41. package/dist/wirings/cli/cli-runner.js +24 -5
  42. package/dist/wirings/cli/command-parser.js +19 -4
  43. package/dist/wirings/http/http-runner.js +72 -36
  44. package/dist/wirings/http/http.types.d.ts +1 -0
  45. package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
  46. package/dist/wirings/http/web-request.js +32 -0
  47. package/dist/wirings/rpc/rpc-runner.js +13 -3
  48. package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
  49. package/dist/wirings/workflow/graph/graph-node.js +4 -0
  50. package/dist/wirings/workflow/graph/graph-runner.js +12 -10
  51. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
  52. package/dist/wirings/workflow/index.d.ts +1 -1
  53. package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
  54. package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
  55. package/dist/wirings/workflow/workflow.types.d.ts +34 -0
  56. package/package.json +1 -1
  57. package/run-tests.sh +4 -1
  58. package/src/dev/hot-reload.test.ts +62 -11
  59. package/src/dev/hot-reload.ts +28 -7
  60. package/src/errors/error-handler.ts +8 -2
  61. package/src/errors/error.test.ts +2 -0
  62. package/src/function/function-runner.test.ts +500 -10
  63. package/src/function/functions.types.ts +3 -2
  64. package/src/index.ts +12 -2
  65. package/src/middleware/index.ts +11 -2
  66. package/src/middleware/telemetry.ts +2 -2
  67. package/src/middleware-runner.test.ts +16 -16
  68. package/src/middleware-runner.ts +42 -30
  69. package/src/permissions.test.ts +27 -24
  70. package/src/permissions.ts +41 -25
  71. package/src/pikku-request.test.ts +35 -0
  72. package/src/pikku-request.ts +1 -1
  73. package/src/pikku-state.ts +2 -3
  74. package/src/run-tests-script.test.ts +18 -0
  75. package/src/services/ai-agent-runner-service.ts +1 -0
  76. package/src/services/in-memory-queue-service.ts +1 -1
  77. package/src/services/in-memory-session-store.ts +1 -2
  78. package/src/services/in-memory-workflow-service.test.ts +33 -11
  79. package/src/services/in-memory-workflow-service.ts +49 -13
  80. package/src/services/local-content.test.ts +54 -0
  81. package/src/services/local-content.ts +12 -1
  82. package/src/services/typed-credential-service.ts +3 -3
  83. package/src/services/typed-secret-service.ts +3 -3
  84. package/src/services/typed-variables-service.ts +3 -3
  85. package/src/services/user-session-service.ts +4 -4
  86. package/src/testing/service-tests.ts +30 -0
  87. package/src/types/core.types.ts +6 -2
  88. package/src/types/state.types.ts +2 -13
  89. package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
  90. package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
  91. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
  92. package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
  93. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
  94. package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
  95. package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
  96. package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
  97. package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
  98. package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
  99. package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
  100. package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
  101. package/src/wirings/channel/channel-handler.test.ts +272 -0
  102. package/src/wirings/channel/channel-handler.ts +1 -1
  103. package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
  104. package/src/wirings/channel/channel-store.ts +19 -0
  105. package/src/wirings/channel/channel.types.ts +4 -0
  106. package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
  107. package/src/wirings/channel/local/local-channel-runner.ts +41 -5
  108. package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
  109. package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
  110. package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
  111. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
  112. package/src/wirings/cli/cli-runner.test.ts +255 -2
  113. package/src/wirings/cli/cli-runner.ts +31 -10
  114. package/src/wirings/cli/cli.types.ts +4 -8
  115. package/src/wirings/cli/command-parser.test.ts +83 -0
  116. package/src/wirings/cli/command-parser.ts +23 -4
  117. package/src/wirings/http/http-runner.test.ts +296 -1
  118. package/src/wirings/http/http-runner.ts +87 -57
  119. package/src/wirings/http/http.types.ts +11 -26
  120. package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
  121. package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
  122. package/src/wirings/http/web-request.test.ts +115 -0
  123. package/src/wirings/http/web-request.ts +43 -5
  124. package/src/wirings/mcp/mcp-runner.test.ts +367 -0
  125. package/src/wirings/queue/queue.types.ts +2 -5
  126. package/src/wirings/rpc/rpc-runner.test.ts +511 -0
  127. package/src/wirings/rpc/rpc-runner.ts +15 -3
  128. package/src/wirings/rpc/wire-addon.test.ts +57 -0
  129. package/src/wirings/workflow/graph/graph-node.ts +12 -0
  130. package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
  131. package/src/wirings/workflow/graph/graph-runner.ts +28 -10
  132. package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
  133. package/src/wirings/workflow/index.ts +1 -0
  134. package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
  135. package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
  136. package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
  137. package/src/wirings/workflow/workflow.types.ts +68 -5
  138. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.12.21
2
+
3
+ ### Patch Changes
4
+
5
+ - 9060165: Agents now declare their model directly as `<provider>/<model>` (e.g. `openai/gpt-4o`). The `models`, `agentDefaults`, and `agentOverrides` config blocks have been removed.
6
+
7
+ **Migration:** replace any bare `model: 'alias'` values with the full provider-qualified form and remove those blocks from `pikku.config.json`.
8
+
9
+ - 9060165: WebSocket channels now expose `setState`, `getState`, and `clearState` — channel state and session lifecycle are managed independently.
10
+ - 9060165: Workflow steps now support per-step `retries` and `retryDelay` configuration. Cloudflare deployments gain Workflow Durable Object bindings for graph-DSL workflows on Workers-for-Platforms, and the deploy bundle now boots cleanly on the Cloudflare Workers runtime.
11
+
1
12
  ## 0.12.20
2
13
 
3
14
  ### Patch Changes
@@ -1,6 +1,8 @@
1
1
  import { watch } from 'node:fs';
2
2
  import { stat, readFile } from 'node:fs/promises';
3
3
  import { join, resolve, relative } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { register } from 'tsx/esm/api';
4
6
  import { pikkuState } from '../pikku-state.js';
5
7
  import { addFunction } from '../function/function-runner.js';
6
8
  import { clearMiddlewareCache } from '../middleware-runner.js';
@@ -35,8 +37,19 @@ const findCompiledFile = async (tsFile, srcDir, pikkuDir) => {
35
37
  // Use data: URLs to import modules. This bypasses TypeScript loaders
36
38
  // (e.g. tsx) that intercept file:// imports and break dynamic ESM loading.
37
39
  // Each import gets unique content so there's no module cache to worry about.
38
- const reimportModule = async (filePath) => {
40
+ let tsxRegistered = false;
41
+ const ensureTsxRegistered = () => {
42
+ if (tsxRegistered)
43
+ return;
44
+ register();
45
+ tsxRegistered = true;
46
+ };
47
+ const reimportModule = async (filePath, useTsx = false) => {
39
48
  try {
49
+ if (useTsx) {
50
+ ensureTsxRegistered();
51
+ return await import(`${pathToFileURL(resolve(filePath)).href}?t=${Date.now()}`);
52
+ }
40
53
  const content = await readFile(resolve(filePath), 'utf-8');
41
54
  const dataUrl = 'data:text/javascript;base64,' + Buffer.from(content).toString('base64');
42
55
  return await import(dataUrl);
@@ -64,13 +77,14 @@ export async function pikkuDevReloader(options) {
64
77
  if (!srcDir)
65
78
  return;
66
79
  const compiledFile = await findCompiledFile(changedTsFile, srcDir, absPikkuDir);
67
- if (!compiledFile) {
68
- logger.warn(`Could not find compiled JS for: ${relative(process.cwd(), changedTsFile)}`);
69
- return;
80
+ const importPath = compiledFile ?? changedTsFile;
81
+ const usedTsxFallback = !compiledFile;
82
+ if (usedTsxFallback) {
83
+ logger.debug(`Hot-reload using tsx fallback for: ${relative(process.cwd(), changedTsFile)}`);
70
84
  }
71
- const mod = await reimportModule(compiledFile);
85
+ const mod = await reimportModule(importPath, usedTsxFallback);
72
86
  if (!mod) {
73
- logger.error(`Failed to import: ${relative(process.cwd(), compiledFile)} (keeping old code)`);
87
+ logger.error(`Failed to import: ${relative(process.cwd(), importPath)} (keeping old code)`);
74
88
  return;
75
89
  }
76
90
  let schemasChanged = false;
@@ -22,7 +22,7 @@ export interface ErrorDetails {
22
22
  * @param error - The error to add.
23
23
  * @param details - The details of the error.
24
24
  */
25
- export declare const addError: (error: any, { status, message }: ErrorDetails) => void;
25
+ export declare const addError: (error: any, { status, message, mcpCode }: ErrorDetails) => void;
26
26
  /**
27
27
  * Adds multiple errors to the API errors map.
28
28
  * @param errors - An array of errors and their details.
@@ -18,8 +18,8 @@ export class PikkuError extends Error {
18
18
  * @param error - The error to add.
19
19
  * @param details - The details of the error.
20
20
  */
21
- export const addError = (error, { status, message }) => {
22
- pikkuState(null, 'misc', 'errors').set(error, { status, message });
21
+ export const addError = (error, { status, message, mcpCode }) => {
22
+ pikkuState(null, 'misc', 'errors').set(error, mcpCode === undefined ? { status, message } : { status, message, mcpCode });
23
23
  };
24
24
  /**
25
25
  * Adds multiple errors to the API errors map.
@@ -135,10 +135,11 @@ export type CorePikkuAuthConfig<Services extends CoreSingletonServices = CoreSer
135
135
  export declare const pikkuAuth: <Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(auth: CorePikkuAuth<Services, Session> | CorePikkuAuthConfig<Services, Session>) => CorePikkuPermission<any, Services, any>;
136
136
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
137
137
  export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any, any>, InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined> = {
138
- /** Optional human-readable title for the function */
138
+ /** Short human-readable name (e.g. "Create Todo") */
139
139
  title?: string;
140
- /** Optional description of what the function does */
140
+ /** Longer-form description of what the function does */
141
141
  description?: string;
142
+ /** Explicit logical name override; lets multiple exports share a versioned base */
142
143
  override?: string;
143
144
  version?: number;
144
145
  tags?: string[];
package/dist/index.d.ts CHANGED
@@ -40,8 +40,8 @@ export { createGraph } from './wirings/workflow/graph/graph-node.js';
40
40
  export { wireAddon } from './wirings/rpc/wire-addon.js';
41
41
  export type { WireAddonConfig } from './wirings/rpc/wire-addon.js';
42
42
  export type { PikkuPackageState } from './types/state.types.js';
43
- export { runMiddleware, addMiddleware } from './middleware-runner.js';
44
- export { addPermission, checkAuthPermissions } from './permissions.js';
43
+ export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
44
+ export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
45
45
  export { isSerializable, stopSingletonServices } from './utils.js';
46
46
  export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
47
47
  export { type ScheduledTaskInfo, type ScheduledTaskSummary, } from './services/scheduler-service.js';
package/dist/index.js CHANGED
@@ -13,8 +13,8 @@ export { runScheduledTask } from './wirings/scheduler/scheduler-runner.js';
13
13
  export { NotFoundError } from './errors/errors.js';
14
14
  export { createGraph } from './wirings/workflow/graph/graph-node.js';
15
15
  export { wireAddon } from './wirings/rpc/wire-addon.js';
16
- export { runMiddleware, addMiddleware } from './middleware-runner.js';
17
- export { addPermission, checkAuthPermissions } from './permissions.js';
16
+ export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
17
+ export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
18
18
  export { isSerializable, stopSingletonServices } from './utils.js';
19
19
  export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
20
20
  export { SchedulerService } from './services/scheduler-service.js';
@@ -4,5 +4,5 @@ export { authBearer } from './auth-bearer.js';
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
6
  export { telemetryOuter, telemetryInner } from './telemetry.js';
7
- export { addMiddleware, runMiddleware } from '../middleware-runner.js';
8
- export { addPermission } from '../permissions.js';
7
+ export { addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, runMiddleware, } from '../middleware-runner.js';
8
+ export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, } from '../permissions.js';
@@ -4,5 +4,5 @@ export { authBearer } from './auth-bearer.js';
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
6
  export { telemetryOuter, telemetryInner } from './telemetry.js';
7
- export { addMiddleware, runMiddleware } from '../middleware-runner.js';
8
- export { addPermission } from '../permissions.js';
7
+ export { addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, runMiddleware, } from '../middleware-runner.js';
8
+ export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, } from '../permissions.js';
@@ -11,7 +11,7 @@
11
11
  * @example
12
12
  * ```typescript
13
13
  * import { telemetryOuter } from '@pikku/core/middleware'
14
- * addMiddleware('myTag', [telemetryOuter()])
14
+ * addTagMiddleware('myTag', [telemetryOuter()])
15
15
  * ```
16
16
  */
17
17
  export declare const telemetryOuter: import("../types/core.types.js").CorePikkuMiddlewareFactory<void | {
@@ -34,7 +34,7 @@ export declare const telemetryOuter: import("../types/core.types.js").CorePikkuM
34
34
  * @example
35
35
  * ```typescript
36
36
  * import { telemetryInner } from '@pikku/core/middleware'
37
- * addMiddleware('myTag', [telemetryInner()])
37
+ * addTagMiddleware('myTag', [telemetryInner()])
38
38
  * ```
39
39
  */
40
40
  export declare const telemetryInner: import("../types/core.types.js").CorePikkuMiddlewareFactory<void | {
@@ -12,7 +12,7 @@ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
12
12
  * @example
13
13
  * ```typescript
14
14
  * import { telemetryOuter } from '@pikku/core/middleware'
15
- * addMiddleware('myTag', [telemetryOuter()])
15
+ * addTagMiddleware('myTag', [telemetryOuter()])
16
16
  * ```
17
17
  */
18
18
  export const telemetryOuter = pikkuMiddlewareFactory(({ environmentId, orgId } = {}) => {
@@ -67,7 +67,7 @@ export const telemetryOuter = pikkuMiddlewareFactory(({ environmentId, orgId } =
67
67
  * @example
68
68
  * ```typescript
69
69
  * import { telemetryInner } from '@pikku/core/middleware'
70
- * addMiddleware('myTag', [telemetryInner()])
70
+ * addTagMiddleware('myTag', [telemetryInner()])
71
71
  * ```
72
72
  */
73
73
  export const telemetryInner = pikkuMiddlewareFactory(({ environmentId, orgId } = {}) => {
@@ -18,39 +18,27 @@ import type { CoreSingletonServices, PikkuWire, CorePikkuMiddleware, CorePikkuMi
18
18
  */
19
19
  export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(services: CoreSingletonServices, wire: PikkuWire, middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
20
20
  /**
21
- * Registers global middleware for a specific tag.
21
+ * Registers tag-scoped middleware. Applies to any wiring (HTTP, Channel,
22
+ * Queue, Scheduler, MCP, CLI, Workflow, Agent) that carries the matching tag.
22
23
  *
23
- * This function registers middleware at runtime that will be applied to
24
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP, CLI) that includes the matching tag.
24
+ * For tree-shaking, wrap in a factory:
25
+ * `export const x = () => addTagMiddleware('tag', [...])`
25
26
  *
26
- * For tree-shaking benefits, wrap in a factory function:
27
- * `export const x = () => addMiddleware('tag', [...])`
28
- *
29
- * Accepts an array that can contain:
30
- * - Direct middleware functions (CorePikkuMiddleware)
31
- * - Factory middleware functions (CorePikkuMiddlewareFactory)
32
- *
33
- * @template PikkuMiddleware The middleware type.
34
- * @param {string} tag - The tag that the middleware should apply to.
35
- * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
27
+ * @example
28
+ * export const adminMiddleware = () => addTagMiddleware('admin', [authMiddleware])
29
+ */
30
+ export declare const addTagMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(tag: string, middleware: CorePikkuMiddlewareGroup, packageName?: string | null) => CorePikkuMiddlewareGroup;
31
+ /**
32
+ * Registers wire-agnostic global middleware. Runs at the top of every
33
+ * wiring's middleware chain — before wire-, tag-, and function-level
34
+ * entries across HTTP, Channel, Queue, Scheduler, MCP, CLI, Workflow, Agent.
36
35
  *
37
- * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
36
+ * Resolution order: global wire tag → function.
38
37
  *
39
38
  * @example
40
- * ```typescript
41
- * // Recommended: tree-shakeable
42
- * export const adminMiddleware = () => addMiddleware('admin', [
43
- * authMiddleware,
44
- * loggingMiddleware({ level: 'info' })
45
- * ])
46
- *
47
- * // Also works: no tree-shaking
48
- * export const apiMiddleware = addMiddleware('api', [
49
- * rateLimitMiddleware
50
- * ])
51
- * ```
39
+ * addGlobalMiddleware([telemetryMiddleware])
52
40
  */
53
- export declare const addMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(tag: string, middleware: CorePikkuMiddlewareGroup, packageName?: string | null) => CorePikkuMiddlewareGroup;
41
+ export declare const addGlobalMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(middleware: CorePikkuMiddlewareGroup, packageName?: string | null) => CorePikkuMiddlewareGroup;
54
42
  export declare const clearMiddlewareCache: () => void;
55
43
  /**
56
44
  * Combines wiring-specific middleware with function-level middleware.
@@ -58,43 +58,35 @@ const isSortedByPriority = (middlewares) => {
58
58
  return true;
59
59
  };
60
60
  /**
61
- * Registers global middleware for a specific tag.
61
+ * Registers tag-scoped middleware. Applies to any wiring (HTTP, Channel,
62
+ * Queue, Scheduler, MCP, CLI, Workflow, Agent) that carries the matching tag.
62
63
  *
63
- * This function registers middleware at runtime that will be applied to
64
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP, CLI) that includes the matching tag.
65
- *
66
- * For tree-shaking benefits, wrap in a factory function:
67
- * `export const x = () => addMiddleware('tag', [...])`
68
- *
69
- * Accepts an array that can contain:
70
- * - Direct middleware functions (CorePikkuMiddleware)
71
- * - Factory middleware functions (CorePikkuMiddlewareFactory)
72
- *
73
- * @template PikkuMiddleware The middleware type.
74
- * @param {string} tag - The tag that the middleware should apply to.
75
- * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
76
- *
77
- * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
64
+ * For tree-shaking, wrap in a factory:
65
+ * `export const x = () => addTagMiddleware('tag', [...])`
78
66
  *
79
67
  * @example
80
- * ```typescript
81
- * // Recommended: tree-shakeable
82
- * export const adminMiddleware = () => addMiddleware('admin', [
83
- * authMiddleware,
84
- * loggingMiddleware({ level: 'info' })
85
- * ])
86
- *
87
- * // Also works: no tree-shaking
88
- * export const apiMiddleware = addMiddleware('api', [
89
- * rateLimitMiddleware
90
- * ])
91
- * ```
68
+ * export const adminMiddleware = () => addTagMiddleware('admin', [authMiddleware])
92
69
  */
93
- export const addMiddleware = (tag, middleware, packageName = null) => {
70
+ export const addTagMiddleware = (tag, middleware, packageName = null) => {
94
71
  const tagGroups = pikkuState(packageName, 'middleware', 'tagGroup');
95
72
  tagGroups[tag] = middleware;
96
73
  return middleware;
97
74
  };
75
+ /**
76
+ * Registers wire-agnostic global middleware. Runs at the top of every
77
+ * wiring's middleware chain — before wire-, tag-, and function-level
78
+ * entries — across HTTP, Channel, Queue, Scheduler, MCP, CLI, Workflow, Agent.
79
+ *
80
+ * Resolution order: global → wire → tag → function.
81
+ *
82
+ * @example
83
+ * addGlobalMiddleware([telemetryMiddleware])
84
+ */
85
+ export const addGlobalMiddleware = (middleware, packageName = null) => {
86
+ const state = pikkuState(packageName, 'middleware', 'global');
87
+ state.push(...middleware);
88
+ return middleware;
89
+ };
98
90
  /**
99
91
  * Retrieves a registered middleware function by its name.
100
92
  *
@@ -156,7 +148,10 @@ export const combineMiddleware = (wireType, uid, { wireInheritedMiddleware, wire
156
148
  return middlewareCache[wireType][uid];
157
149
  }
158
150
  const resolved = [];
159
- // 1. Resolve wire inherited middleware (HTTP + tag groups + individual wire middleware)
151
+ const globals = pikkuState(packageName, 'middleware', 'global');
152
+ if (globals && globals.length > 0) {
153
+ resolved.push(...globals);
154
+ }
160
155
  if (wireInheritedMiddleware) {
161
156
  for (const meta of wireInheritedMiddleware) {
162
157
  if (meta.type === 'http') {
@@ -1,34 +1,27 @@
1
1
  import type { CoreServices, CoreUserSession, PikkuWiringTypes, PermissionMetadata, PikkuWire } from './types/core.types.js';
2
2
  import type { CorePermissionGroup, CorePikkuPermission } from './function/functions.types.js';
3
3
  /**
4
- * Adds global permissions for a specific tag.
4
+ * Registers tag-scoped permissions. Applies to any wiring that carries the
5
+ * matching tag.
5
6
  *
6
- * This function allows you to register permissions that will be applied to
7
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
7
+ * For tree-shaking, wrap in a factory:
8
+ * `export const x = () => addTagPermission('tag', [...])`
8
9
  *
9
- * For tree-shaking benefits, wrap in a factory function:
10
- * `export const x = () => addPermission('tag', [...])`
11
- *
12
- * @param {string} tag - The tag that the permissions should apply to.
13
- * @param {any[]} permissions - The permissions array to apply for the specified tag.
10
+ * @example
11
+ * export const adminPermissions = () => addTagPermission('admin', [adminPermission])
12
+ */
13
+ export declare const addTagPermission: (tag: string, permissions: CorePermissionGroup | CorePikkuPermission[], packageName?: string | null) => CorePermissionGroup | CorePikkuPermission[];
14
+ /**
15
+ * Registers wire-agnostic global permissions. Runs at the top of every
16
+ * wiring's permission resolution — before wire-, tag-, and function-level
17
+ * entries — across all wire types.
14
18
  *
15
- * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
19
+ * Resolution order: global wire tag → function.
16
20
  *
17
21
  * @example
18
- * ```typescript
19
- * // Recommended: tree-shakeable
20
- * export const adminPermissions = () => addPermission('admin', [
21
- * adminPermission,
22
- * rolePermission({ role: 'admin' })
23
- * ])
24
- *
25
- * // Also works: no tree-shaking
26
- * export const apiPermissions = addPermission('api', [
27
- * readPermission
28
- * ])
29
- * ```
22
+ * addGlobalPermission([signedInUser])
30
23
  */
31
- export declare const addPermission: (tag: string, permissions: CorePermissionGroup | CorePikkuPermission[], packageName?: string | null) => CorePermissionGroup | CorePikkuPermission[];
24
+ export declare const addGlobalPermission: (permissions: CorePermissionGroup | CorePikkuPermission[], packageName?: string | null) => CorePermissionGroup | CorePikkuPermission[];
32
25
  export declare const clearPermissionsCache: () => void;
33
26
  /**
34
27
  * Runs permission checks using combined permissions from all sources.
@@ -53,34 +53,16 @@ const getPermissionByName = (name) => {
53
53
  return undefined;
54
54
  };
55
55
  /**
56
- * Adds global permissions for a specific tag.
56
+ * Registers tag-scoped permissions. Applies to any wiring that carries the
57
+ * matching tag.
57
58
  *
58
- * This function allows you to register permissions that will be applied to
59
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
60
- *
61
- * For tree-shaking benefits, wrap in a factory function:
62
- * `export const x = () => addPermission('tag', [...])`
63
- *
64
- * @param {string} tag - The tag that the permissions should apply to.
65
- * @param {any[]} permissions - The permissions array to apply for the specified tag.
66
- *
67
- * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
59
+ * For tree-shaking, wrap in a factory:
60
+ * `export const x = () => addTagPermission('tag', [...])`
68
61
  *
69
62
  * @example
70
- * ```typescript
71
- * // Recommended: tree-shakeable
72
- * export const adminPermissions = () => addPermission('admin', [
73
- * adminPermission,
74
- * rolePermission({ role: 'admin' })
75
- * ])
76
- *
77
- * // Also works: no tree-shaking
78
- * export const apiPermissions = addPermission('api', [
79
- * readPermission
80
- * ])
81
- * ```
63
+ * export const adminPermissions = () => addTagPermission('admin', [adminPermission])
82
64
  */
83
- export const addPermission = (tag, permissions, packageName = null) => {
65
+ export const addTagPermission = (tag, permissions, packageName = null) => {
84
66
  const tagGroups = pikkuState(packageName, 'permissions', 'tagGroup');
85
67
  if (tagGroups[tag]) {
86
68
  throw new Error(`Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`);
@@ -88,6 +70,26 @@ export const addPermission = (tag, permissions, packageName = null) => {
88
70
  tagGroups[tag] = permissions;
89
71
  return permissions;
90
72
  };
73
+ /**
74
+ * Registers wire-agnostic global permissions. Runs at the top of every
75
+ * wiring's permission resolution — before wire-, tag-, and function-level
76
+ * entries — across all wire types.
77
+ *
78
+ * Resolution order: global → wire → tag → function.
79
+ *
80
+ * @example
81
+ * addGlobalPermission([signedInUser])
82
+ */
83
+ export const addGlobalPermission = (permissions, packageName = null) => {
84
+ const state = pikkuState(packageName, 'permissions', 'global');
85
+ if (Array.isArray(permissions)) {
86
+ state.push(...permissions);
87
+ }
88
+ else {
89
+ state.push(permissions);
90
+ }
91
+ return permissions;
92
+ };
91
93
  const combinedPermissionsCache = {
92
94
  http: {},
93
95
  rpc: {},
@@ -135,7 +137,10 @@ const combinePermissions = (wireType, uid, { wireInheritedPermissions, wirePermi
135
137
  return combinedPermissionsCache[wireType][uid];
136
138
  }
137
139
  const resolved = [];
138
- // 1. Resolve wire inherited permissions (HTTP + tag groups + individual wire permissions)
140
+ const globals = pikkuState(packageName, 'permissions', 'global');
141
+ if (globals && globals.length > 0) {
142
+ resolved.push(...globals);
143
+ }
139
144
  if (wireInheritedPermissions) {
140
145
  for (const meta of wireInheritedPermissions) {
141
146
  if (meta.type === 'http') {
@@ -13,7 +13,7 @@ export class PikkuRequest {
13
13
  * @returns A promise that resolves to an object containing the combined data.
14
14
  */
15
15
  async data() {
16
- if (!this.#data) {
16
+ if (this.#data === undefined) {
17
17
  throw new Error('Data not found');
18
18
  }
19
19
  return this.#data;
@@ -105,6 +105,7 @@ const createEmptyPackageState = () => ({
105
105
  middleware: {
106
106
  tagGroup: {},
107
107
  httpGroup: {},
108
+ global: [],
108
109
  },
109
110
  channelMiddleware: {
110
111
  tagGroup: {},
@@ -112,6 +113,7 @@ const createEmptyPackageState = () => ({
112
113
  permissions: {
113
114
  tagGroup: {},
114
115
  httpGroup: {},
116
+ global: [],
115
117
  },
116
118
  misc: {
117
119
  errors: new Map(),
@@ -120,9 +122,6 @@ const createEmptyPackageState = () => ({
120
122
  channelMiddleware: {},
121
123
  permissions: {},
122
124
  },
123
- models: {
124
- config: null,
125
- },
126
125
  package: {
127
126
  factories: null,
128
127
  singletonServices: null,
@@ -36,6 +36,7 @@ export type AIAgentStepResult = {
36
36
  outputTokens: number;
37
37
  };
38
38
  finishReason: 'stop' | 'tool-calls' | 'length' | 'error' | 'unknown';
39
+ reasoningContent?: string;
39
40
  };
40
41
  export interface AIAgentRunnerService {
41
42
  stream(params: AIAgentRunnerParams, channel: AIStreamChannel): Promise<AIAgentStepResult>;
@@ -11,7 +11,7 @@ export class InMemoryQueueService {
11
11
  status: () => 'active',
12
12
  pikkuUserId: options?.pikkuUserId,
13
13
  };
14
- const delay = options?.delay ?? 0;
14
+ const delay = options?.delay ?? 100 + Math.floor(Math.random() * 201);
15
15
  setTimeout(async () => {
16
16
  try {
17
17
  await runQueueJob({ job });
@@ -17,6 +17,7 @@ import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunW
17
17
  * ```
18
18
  */
19
19
  export declare class InMemoryWorkflowService extends PikkuWorkflowService implements WorkflowRunService {
20
+ private sleepTimers;
20
21
  private runs;
21
22
  private steps;
22
23
  private stepData;
@@ -24,23 +25,24 @@ export declare class InMemoryWorkflowService extends PikkuWorkflowService implem
24
25
  private runState;
25
26
  private branchKeys;
26
27
  private workflowVersions;
27
- createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
28
+ protected createRunImpl(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
28
29
  deterministic?: boolean;
29
30
  plannedSteps?: WorkflowPlannedStep[];
30
31
  }): Promise<string>;
32
+ protected scheduleSleep(runId: string, stepId: string, duration: number): Promise<boolean>;
31
33
  getRun(id: string): Promise<WorkflowRun | null>;
32
34
  getRunHistory(runId: string): Promise<Array<StepState & {
33
35
  stepName: string;
34
36
  }>>;
35
- updateRunStatus(id: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
36
- insertStepState(runId: string, stepName: string, rpcName: string | null, data: any, stepOptions?: WorkflowStepOptions): Promise<StepState>;
37
+ protected updateRunStatusImpl(id: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
38
+ protected insertStepStateImpl(runId: string, stepName: string, rpcName: string | null, data: any, stepOptions?: WorkflowStepOptions): Promise<StepState>;
37
39
  getStepState(runId: string, stepName: string): Promise<StepState>;
38
- setStepRunning(stepId: string): Promise<void>;
39
- setStepScheduled(stepId: string): Promise<void>;
40
- setStepResult(stepId: string, result: any): Promise<void>;
41
- setStepError(stepId: string, error: Error): Promise<void>;
42
- setStepChildRunId(stepId: string, childRunId: string): Promise<void>;
43
- createRetryAttempt(failedStepId: string, status: 'pending' | 'running'): Promise<StepState>;
40
+ protected setStepRunningImpl(stepId: string): Promise<void>;
41
+ protected setStepScheduledImpl(stepId: string): Promise<void>;
42
+ protected setStepResultImpl(stepId: string, result: any): Promise<void>;
43
+ protected setStepErrorImpl(stepId: string, error: Error): Promise<void>;
44
+ protected setStepChildRunIdImpl(stepId: string, childRunId: string): Promise<void>;
45
+ protected createRetryAttemptImpl(failedStepId: string, status: 'pending' | 'running'): Promise<StepState>;
44
46
  listRuns(options?: {
45
47
  workflowName?: string;
46
48
  status?: string;
@@ -65,11 +67,11 @@ export declare class InMemoryWorkflowService extends PikkuWorkflowService implem
65
67
  getNodesWithoutSteps(runId: string, nodeIds: string[]): Promise<string[]>;
66
68
  getNodeResults(runId: string, nodeIds: string[]): Promise<Record<string, any>>;
67
69
  setBranchKey(runId: string, nodeId: string, branchKey: string): Promise<void>;
68
- setBranchTaken(stepId: string, branchKey: string): Promise<void>;
69
- updateRunState(runId: string, name: string, value: unknown): Promise<void>;
70
+ protected setBranchTakenImpl(stepId: string, branchKey: string): Promise<void>;
71
+ protected updateRunStateImpl(runId: string, name: string, value: unknown): Promise<void>;
70
72
  getRunState(runId: string): Promise<Record<string, unknown>>;
71
- upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
72
- updateWorkflowVersionStatus(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
73
+ protected upsertWorkflowVersionImpl(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
74
+ protected updateWorkflowVersionStatusImpl(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
73
75
  getWorkflowVersion(name: string, graphHash: string): Promise<{
74
76
  graph: any;
75
77
  source: string;