@pikku/core 0.12.19 → 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 (146) hide show
  1. package/CHANGELOG.md +77 -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/function-runner.js +53 -3
  6. package/dist/function/functions.types.d.ts +3 -2
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +2 -2
  9. package/dist/middleware/index.d.ts +2 -2
  10. package/dist/middleware/index.js +2 -2
  11. package/dist/middleware/telemetry.d.ts +2 -2
  12. package/dist/middleware/telemetry.js +2 -2
  13. package/dist/middleware-runner.d.ts +15 -27
  14. package/dist/middleware-runner.js +25 -30
  15. package/dist/permissions.d.ts +15 -22
  16. package/dist/permissions.js +30 -25
  17. package/dist/pikku-request.js +1 -1
  18. package/dist/pikku-state.js +2 -3
  19. package/dist/services/ai-agent-runner-service.d.ts +1 -0
  20. package/dist/services/content-service.d.ts +66 -37
  21. package/dist/services/in-memory-queue-service.js +1 -1
  22. package/dist/services/in-memory-workflow-service.d.ts +15 -13
  23. package/dist/services/in-memory-workflow-service.js +31 -13
  24. package/dist/services/index.d.ts +1 -1
  25. package/dist/services/local-content.d.ts +10 -12
  26. package/dist/services/local-content.js +54 -38
  27. package/dist/services/user-session-service.d.ts +1 -1
  28. package/dist/testing/service-tests.js +24 -0
  29. package/dist/types/core.types.d.ts +4 -0
  30. package/dist/types/state.types.d.ts +2 -18
  31. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
  32. package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
  33. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
  34. package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
  35. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
  36. package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
  37. package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
  38. package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
  39. package/dist/wirings/channel/channel-handler.js +1 -1
  40. package/dist/wirings/channel/channel-store.d.ts +13 -0
  41. package/dist/wirings/channel/channel.types.d.ts +3 -0
  42. package/dist/wirings/channel/local/local-channel-runner.js +23 -5
  43. package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
  44. package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
  45. package/dist/wirings/cli/cli-runner.js +24 -5
  46. package/dist/wirings/cli/command-parser.js +19 -4
  47. package/dist/wirings/http/http-runner.js +72 -36
  48. package/dist/wirings/http/http.types.d.ts +1 -0
  49. package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
  50. package/dist/wirings/http/web-request.js +32 -0
  51. package/dist/wirings/rpc/rpc-runner.d.ts +3 -2
  52. package/dist/wirings/rpc/rpc-runner.js +45 -11
  53. package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
  54. package/dist/wirings/workflow/graph/graph-node.js +4 -0
  55. package/dist/wirings/workflow/graph/graph-runner.js +12 -10
  56. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
  57. package/dist/wirings/workflow/index.d.ts +1 -1
  58. package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
  59. package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
  60. package/dist/wirings/workflow/workflow.types.d.ts +34 -0
  61. package/package.json +1 -1
  62. package/run-tests.sh +4 -1
  63. package/src/dev/hot-reload.test.ts +62 -11
  64. package/src/dev/hot-reload.ts +28 -7
  65. package/src/errors/error-handler.ts +8 -2
  66. package/src/errors/error.test.ts +2 -0
  67. package/src/function/function-runner.test.ts +500 -10
  68. package/src/function/function-runner.ts +68 -3
  69. package/src/function/functions.types.ts +3 -2
  70. package/src/index.ts +22 -3
  71. package/src/middleware/index.ts +11 -2
  72. package/src/middleware/telemetry.ts +2 -2
  73. package/src/middleware-runner.test.ts +16 -16
  74. package/src/middleware-runner.ts +42 -30
  75. package/src/permissions.test.ts +27 -24
  76. package/src/permissions.ts +41 -25
  77. package/src/pikku-request.test.ts +35 -0
  78. package/src/pikku-request.ts +1 -1
  79. package/src/pikku-state.ts +2 -3
  80. package/src/run-tests-script.test.ts +18 -0
  81. package/src/services/ai-agent-runner-service.ts +1 -0
  82. package/src/services/content-service.ts +79 -51
  83. package/src/services/in-memory-queue-service.ts +1 -1
  84. package/src/services/in-memory-session-store.ts +1 -2
  85. package/src/services/in-memory-workflow-service.test.ts +33 -11
  86. package/src/services/in-memory-workflow-service.ts +49 -13
  87. package/src/services/index.ts +10 -1
  88. package/src/services/local-content.test.ts +54 -0
  89. package/src/services/local-content.ts +80 -53
  90. package/src/services/typed-credential-service.ts +3 -3
  91. package/src/services/typed-secret-service.ts +3 -3
  92. package/src/services/typed-variables-service.ts +3 -3
  93. package/src/services/user-session-service.ts +4 -4
  94. package/src/testing/service-tests.ts +30 -0
  95. package/src/types/core.types.ts +6 -2
  96. package/src/types/state.types.ts +2 -13
  97. package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
  98. package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
  99. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
  100. package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
  101. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
  102. package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
  103. package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
  104. package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
  105. package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
  106. package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
  107. package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
  108. package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
  109. package/src/wirings/channel/channel-handler.test.ts +272 -0
  110. package/src/wirings/channel/channel-handler.ts +1 -1
  111. package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
  112. package/src/wirings/channel/channel-store.ts +19 -0
  113. package/src/wirings/channel/channel.types.ts +4 -0
  114. package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
  115. package/src/wirings/channel/local/local-channel-runner.ts +41 -5
  116. package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
  117. package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
  118. package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
  119. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
  120. package/src/wirings/cli/cli-runner.test.ts +255 -2
  121. package/src/wirings/cli/cli-runner.ts +31 -10
  122. package/src/wirings/cli/cli.types.ts +4 -8
  123. package/src/wirings/cli/command-parser.test.ts +83 -0
  124. package/src/wirings/cli/command-parser.ts +23 -4
  125. package/src/wirings/http/http-runner.test.ts +296 -1
  126. package/src/wirings/http/http-runner.ts +87 -57
  127. package/src/wirings/http/http.types.ts +11 -26
  128. package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
  129. package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
  130. package/src/wirings/http/web-request.test.ts +115 -0
  131. package/src/wirings/http/web-request.ts +43 -5
  132. package/src/wirings/mcp/mcp-runner.test.ts +367 -0
  133. package/src/wirings/queue/queue.types.ts +2 -5
  134. package/src/wirings/rpc/rpc-runner.test.ts +511 -0
  135. package/src/wirings/rpc/rpc-runner.ts +60 -21
  136. package/src/wirings/rpc/wire-addon.test.ts +57 -0
  137. package/src/wirings/workflow/graph/graph-node.ts +12 -0
  138. package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
  139. package/src/wirings/workflow/graph/graph-runner.ts +28 -10
  140. package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
  141. package/src/wirings/workflow/index.ts +1 -0
  142. package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
  143. package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
  144. package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
  145. package/src/wirings/workflow/workflow.types.ts +68 -5
  146. package/tsconfig.tsbuildinfo +1 -1
@@ -1,60 +1,89 @@
1
- export interface ContentService {
1
+ /**
2
+ * Arguments for signing a content key into a time-limited URL.
3
+ */
4
+ export interface SignContentKeyArgs<TBucket extends string = string> {
5
+ bucket: TBucket;
6
+ contentKey: string;
7
+ dateLessThan: Date;
8
+ dateGreaterThan?: Date;
9
+ }
10
+ /**
11
+ * Arguments for signing an arbitrary URL.
12
+ */
13
+ export interface SignURLArgs {
14
+ url: string;
15
+ dateLessThan: Date;
16
+ dateGreaterThan?: Date;
17
+ }
18
+ /**
19
+ * Arguments for minting a presigned upload URL.
20
+ */
21
+ export interface GetUploadURLArgs<TBucket extends string = string> {
22
+ bucket: TBucket;
23
+ fileKey: string;
24
+ contentType: string;
25
+ size?: number;
26
+ }
27
+ /**
28
+ * Result of minting a presigned upload URL.
29
+ */
30
+ export interface UploadURLResult {
31
+ uploadUrl: string;
32
+ assetKey: string;
33
+ uploadHeaders?: Record<string, string>;
34
+ uploadMethod?: 'PUT' | 'POST';
35
+ }
36
+ /**
37
+ * Arguments for an operation that targets a single object by key.
38
+ */
39
+ export interface BucketKeyArgs<TBucket extends string = string> {
40
+ bucket: TBucket;
41
+ key: string;
42
+ }
43
+ /**
44
+ * Arguments for writing a stream to storage.
45
+ */
46
+ export interface WriteFileArgs<TBucket extends string = string> extends BucketKeyArgs<TBucket> {
47
+ stream: ReadableStream | NodeJS.ReadableStream;
48
+ }
49
+ /**
50
+ * Arguments for copying a local file into storage.
51
+ */
52
+ export interface CopyFileArgs<TBucket extends string = string> extends BucketKeyArgs<TBucket> {
53
+ fromAbsolutePath: string;
54
+ }
55
+ export interface ContentService<TBucket extends string = string> {
2
56
  /**
3
57
  * Signs a content key to generate a secure, time-limited access URL.
4
- * @param contentKey - The key representing the content object.
5
- * @param dateLessThan - The expiration time for the signed URL.
6
- * @param dateGreaterThan - (Optional) Start time before which access is denied.
7
58
  */
8
- signContentKey(contentKey: string, dateLessThan: Date, dateGreaterThan?: Date): Promise<string>;
59
+ signContentKey(args: SignContentKeyArgs<TBucket>): Promise<string>;
9
60
  /**
10
61
  * Signs an arbitrary URL to generate a secure, time-limited access URL.
11
- * @param url - The full URL that needs signing.
12
- * @param dateLessThan - The expiration time for the signed URL.
13
- * @param dateGreaterThan - (Optional) Start time before which access is denied.
14
62
  */
15
- signURL(url: string, dateLessThan: Date, dateGreaterThan?: Date): Promise<string>;
63
+ signURL(args: SignURLArgs): Promise<string>;
16
64
  /**
17
65
  * Generates a signed URL for uploading a file directly to storage.
18
- * @param fileKey - The desired key/location of the uploaded file.
19
- * @param contentType - The MIME type of the file.
20
- * @returns A signed upload URL and the finalized asset key.
66
+ * Bucket policy (size limits, MIME allowlist) is enforced by the implementation.
21
67
  */
22
- getUploadURL(fileKey: string, contentType: string): Promise<{
23
- uploadUrl: string;
24
- assetKey: string;
25
- uploadHeaders?: Record<string, string>;
26
- uploadMethod?: 'PUT' | 'POST';
27
- }>;
68
+ getUploadURL(args: GetUploadURLArgs<TBucket>): Promise<UploadURLResult>;
28
69
  /**
29
70
  * Deletes a file from the storage backend.
30
- * @param fileName - The name or key of the file to delete.
31
- * @returns A boolean indicating success.
32
71
  */
33
- deleteFile(fileName: string): Promise<boolean>;
72
+ deleteFile(args: BucketKeyArgs<TBucket>): Promise<boolean>;
34
73
  /**
35
- * Uploads a file stream to storage under a specified asset key.
36
- * @param assetKey - The key where the file will be saved.
37
- * @param stream - A readable stream of the file contents.
38
- * @returns A boolean indicating success.
74
+ * Uploads a file stream to storage under the specified bucket + key.
39
75
  */
40
- writeFile(assetKey: string, stream: ReadableStream | NodeJS.ReadableStream): Promise<boolean>;
76
+ writeFile(args: WriteFileArgs<TBucket>): Promise<boolean>;
41
77
  /**
42
- * Copies a file from a local absolute path into storage under a new asset key.
43
- * @param assetKey - The destination key.
44
- * @param fromAbsolutePath - The local absolute file path.
45
- * @returns A boolean indicating success.
78
+ * Copies a file from a local absolute path into storage.
46
79
  */
47
- copyFile(assetKey: string, fromAbsolutePath: string): Promise<boolean>;
80
+ copyFile(args: CopyFileArgs<TBucket>): Promise<boolean>;
48
81
  /**
49
82
  * Reads a file from storage as a readable stream.
50
- * @param assetKey - The key of the file to read.
51
- * @returns A readable file stream.
52
83
  */
53
- readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>;
84
+ readFile(args: BucketKeyArgs<TBucket>): Promise<ReadableStream | NodeJS.ReadableStream>;
54
85
  /**
55
86
  * Reads an entire file from storage into a Buffer.
56
- * @param assetKey - The key of the file to read.
57
- * @returns The file contents as a Buffer.
58
87
  */
59
- readFileAsBuffer(assetKey: string): Promise<Buffer>;
88
+ readFileAsBuffer(args: BucketKeyArgs<TBucket>): Promise<Buffer>;
60
89
  }
@@ -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;
@@ -16,6 +16,7 @@ import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service
16
16
  * ```
17
17
  */
18
18
  export class InMemoryWorkflowService extends PikkuWorkflowService {
19
+ sleepTimers = new Set();
19
20
  runs = new Map();
20
21
  steps = new Map(); // keyed by `${runId}:${stepName}`
21
22
  stepData = new Map(); // keyed by stepId
@@ -23,7 +24,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
23
24
  runState = new Map(); // keyed by runId
24
25
  branchKeys = new Map(); // keyed by stepId
25
26
  workflowVersions = new Map(); // keyed by `${name}:${graphHash}`
26
- async createRun(workflowName, input, inline, graphHash, wire, options) {
27
+ async createRunImpl(workflowName, input, inline, graphHash, wire, options) {
27
28
  const runId = randomUUID();
28
29
  const now = new Date();
29
30
  const run = {
@@ -47,13 +48,26 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
47
48
  }
48
49
  return runId;
49
50
  }
51
+ async scheduleSleep(runId, stepId, duration) {
52
+ const timer = setTimeout(async () => {
53
+ this.sleepTimers.delete(timer);
54
+ try {
55
+ await this.executeWorkflowSleepCompleted(runId, stepId);
56
+ }
57
+ catch (error) {
58
+ this.logger?.error(`Failed to resume workflow sleep for runId ${runId}: ${error?.message ?? error}`);
59
+ }
60
+ }, duration);
61
+ this.sleepTimers.add(timer);
62
+ return true;
63
+ }
50
64
  async getRun(id) {
51
65
  return this.runs.get(id) || null;
52
66
  }
53
67
  async getRunHistory(runId) {
54
68
  return this.stepHistory.get(runId) || [];
55
69
  }
56
- async updateRunStatus(id, status, output, error) {
70
+ async updateRunStatusImpl(id, status, output, error) {
57
71
  const run = this.runs.get(id);
58
72
  if (run) {
59
73
  run.status = status;
@@ -64,7 +78,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
64
78
  run.error = error;
65
79
  }
66
80
  }
67
- async insertStepState(runId, stepName, rpcName, data, stepOptions) {
81
+ async insertStepStateImpl(runId, stepName, rpcName, data, stepOptions) {
68
82
  const stepId = randomUUID();
69
83
  const now = new Date();
70
84
  const step = {
@@ -94,7 +108,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
94
108
  }
95
109
  return step;
96
110
  }
97
- async setStepRunning(stepId) {
111
+ async setStepRunningImpl(stepId) {
98
112
  for (const step of this.steps.values()) {
99
113
  if (step.stepId === stepId) {
100
114
  step.status = 'running';
@@ -104,7 +118,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
104
118
  }
105
119
  }
106
120
  }
107
- async setStepScheduled(stepId) {
121
+ async setStepScheduledImpl(stepId) {
108
122
  for (const step of this.steps.values()) {
109
123
  if (step.stepId === stepId) {
110
124
  step.status = 'scheduled';
@@ -114,7 +128,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
114
128
  }
115
129
  }
116
130
  }
117
- async setStepResult(stepId, result) {
131
+ async setStepResultImpl(stepId, result) {
118
132
  for (const step of this.steps.values()) {
119
133
  if (step.stepId === stepId) {
120
134
  step.status = 'succeeded';
@@ -125,7 +139,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
125
139
  }
126
140
  }
127
141
  }
128
- async setStepError(stepId, error) {
142
+ async setStepErrorImpl(stepId, error) {
129
143
  for (const step of this.steps.values()) {
130
144
  if (step.stepId === stepId) {
131
145
  step.status = 'failed';
@@ -140,7 +154,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
140
154
  }
141
155
  }
142
156
  }
143
- async setStepChildRunId(stepId, childRunId) {
157
+ async setStepChildRunIdImpl(stepId, childRunId) {
144
158
  for (const step of this.steps.values()) {
145
159
  if (step.stepId === stepId) {
146
160
  step.childRunId = childRunId;
@@ -149,7 +163,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
149
163
  }
150
164
  }
151
165
  }
152
- async createRetryAttempt(failedStepId, status) {
166
+ async createRetryAttemptImpl(failedStepId, status) {
153
167
  // Find the failed step
154
168
  let failedStep;
155
169
  let runId;
@@ -252,6 +266,10 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
252
266
  return fn();
253
267
  }
254
268
  async close() {
269
+ for (const timer of this.sleepTimers) {
270
+ clearTimeout(timer);
271
+ }
272
+ this.sleepTimers.clear();
255
273
  // Clear all in-memory state
256
274
  this.runs.clear();
257
275
  this.steps.clear();
@@ -317,10 +335,10 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
317
335
  this.branchKeys.set(step.stepId, branchKey);
318
336
  }
319
337
  }
320
- async setBranchTaken(stepId, branchKey) {
338
+ async setBranchTakenImpl(stepId, branchKey) {
321
339
  this.branchKeys.set(stepId, branchKey);
322
340
  }
323
- async updateRunState(runId, name, value) {
341
+ async updateRunStateImpl(runId, name, value) {
324
342
  const state = this.runState.get(runId) || {};
325
343
  state[name] = value;
326
344
  this.runState.set(runId, state);
@@ -328,14 +346,14 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
328
346
  async getRunState(runId) {
329
347
  return this.runState.get(runId) || {};
330
348
  }
331
- async upsertWorkflowVersion(name, graphHash, graph, source, status) {
349
+ async upsertWorkflowVersionImpl(name, graphHash, graph, source, status) {
332
350
  this.workflowVersions.set(`${name}:${graphHash}`, {
333
351
  graph,
334
352
  source,
335
353
  status: status ?? 'active',
336
354
  });
337
355
  }
338
- async updateWorkflowVersionStatus(name, graphHash, status) {
356
+ async updateWorkflowVersionStatusImpl(name, graphHash, status) {
339
357
  const key = `${name}:${graphHash}`;
340
358
  const version = this.workflowVersions.get(key);
341
359
  if (version) {
@@ -16,7 +16,7 @@ export { InMemoryQueueService } from './in-memory-queue-service.js';
16
16
  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
- export type { ContentService } from './content-service.js';
19
+ export type { ContentService, SignContentKeyArgs, SignURLArgs, GetUploadURLArgs, UploadURLResult, BucketKeyArgs, WriteFileArgs, CopyFileArgs, } from './content-service.js';
20
20
  export type { JWTService } from './jwt-service.js';
21
21
  export type { Logger } from './logger.js';
22
22
  export type { SecretService } from './secret-service.js';
@@ -1,4 +1,4 @@
1
- import type { ContentService, JWTService, Logger } from '@pikku/core/services';
1
+ import type { BucketKeyArgs, ContentService, CopyFileArgs, GetUploadURLArgs, JWTService, Logger, SignContentKeyArgs, SignURLArgs, UploadURLResult, WriteFileArgs } from '@pikku/core/services';
2
2
  export interface LocalContentConfig {
3
3
  localFileUploadPath: string;
4
4
  uploadUrlPrefix: string;
@@ -12,18 +12,16 @@ export declare class LocalContent implements ContentService {
12
12
  private jwt?;
13
13
  constructor(config: LocalContentConfig, logger: Logger, jwt?: JWTService | undefined);
14
14
  private safePath;
15
+ private joinKey;
15
16
  init(): Promise<void>;
16
17
  private signParams;
17
- signURL(url: string, dateLessThan: Date, dateGreaterThan?: Date): Promise<string>;
18
- signContentKey(assetKey: string, dateLessThan: Date, dateGreaterThan?: Date): Promise<string>;
19
- getUploadURL(assetKey: string): Promise<{
20
- uploadUrl: string;
21
- assetKey: string;
22
- }>;
23
- writeFile(assetKey: string, stream: ReadableStream | NodeJS.ReadableStream): Promise<boolean>;
24
- copyFile(assetKey: string, fromAbsolutePath: string): Promise<boolean>;
25
- readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>;
26
- readFileAsBuffer(assetKey: string): Promise<Buffer>;
27
- deleteFile(assetKey: string): Promise<boolean>;
18
+ signURL(args: SignURLArgs): Promise<string>;
19
+ signContentKey(args: SignContentKeyArgs): Promise<string>;
20
+ getUploadURL(args: GetUploadURLArgs): Promise<UploadURLResult>;
21
+ writeFile(args: WriteFileArgs): Promise<boolean>;
22
+ copyFile(args: CopyFileArgs): Promise<boolean>;
23
+ readFile(args: BucketKeyArgs): Promise<ReadableStream | NodeJS.ReadableStream>;
24
+ readFileAsBuffer(args: BucketKeyArgs): Promise<Buffer>;
25
+ deleteFile(args: BucketKeyArgs): Promise<boolean>;
28
26
  private createDirectoryForFile;
29
27
  }
@@ -11,14 +11,18 @@ export class LocalContent {
11
11
  this.logger = logger;
12
12
  this.jwt = jwt;
13
13
  }
14
- safePath(assetKey) {
14
+ safePath(bucket, key) {
15
15
  const base = resolve(this.config.localFileUploadPath);
16
- const target = resolve(base, normalize(assetKey));
16
+ const scoped = resolve(base, normalize(bucket));
17
+ const target = resolve(scoped, normalize(key));
17
18
  if (!target.startsWith(base + '/') && target !== base) {
18
19
  throw new Error('Invalid asset key');
19
20
  }
20
21
  return target;
21
22
  }
23
+ joinKey(bucket, key) {
24
+ return `${bucket}/${key}`;
25
+ }
22
26
  async init() { }
23
27
  async signParams(dateLessThan, dateGreaterThan) {
24
28
  const signedAt = Date.now();
@@ -32,86 +36,98 @@ export class LocalContent {
32
36
  }
33
37
  if (this.jwt) {
34
38
  const expiresInSeconds = Math.max(1, Math.floor((expiresAt - signedAt) / 1000));
35
- const signature = await this.jwt.encode({ value: expiresInSeconds, unit: 'second' }, { signedAt, expiresAt });
39
+ const payload = {
40
+ signedAt,
41
+ expiresAt,
42
+ };
43
+ if (dateGreaterThan) {
44
+ payload.notBefore = dateGreaterThan.getTime();
45
+ }
46
+ const signature = await this.jwt.encode({ value: expiresInSeconds, unit: 'second' }, payload);
36
47
  params.set('signature', signature);
37
48
  }
38
49
  return params.toString();
39
50
  }
40
- async signURL(url, dateLessThan, dateGreaterThan) {
41
- const params = await this.signParams(dateLessThan, dateGreaterThan);
42
- return `${url}?${params}`;
51
+ async signURL(args) {
52
+ const params = await this.signParams(args.dateLessThan, args.dateGreaterThan);
53
+ return `${args.url}?${params}`;
43
54
  }
44
- async signContentKey(assetKey, dateLessThan, dateGreaterThan) {
55
+ async signContentKey(args) {
56
+ const fullKey = this.joinKey(args.bucket, args.contentKey);
45
57
  const base = this.config.server
46
- ? `${this.config.server}${this.config.assetUrlPrefix}/${assetKey}`
47
- : `${this.config.assetUrlPrefix}/${assetKey}`;
48
- return this.signURL(base, dateLessThan, dateGreaterThan);
58
+ ? `${this.config.server}${this.config.assetUrlPrefix}/${fullKey}`
59
+ : `${this.config.assetUrlPrefix}/${fullKey}`;
60
+ return this.signURL({
61
+ url: base,
62
+ dateLessThan: args.dateLessThan,
63
+ dateGreaterThan: args.dateGreaterThan,
64
+ });
49
65
  }
50
- async getUploadURL(assetKey) {
51
- this.logger.debug(`Going to upload with key: ${assetKey}`);
66
+ async getUploadURL(args) {
67
+ const fullKey = this.joinKey(args.bucket, args.fileKey);
68
+ this.logger.debug(`Going to upload with key: ${fullKey}`);
52
69
  return {
53
- uploadUrl: `${this.config.uploadUrlPrefix}/${assetKey}`,
54
- assetKey,
70
+ uploadUrl: `${this.config.uploadUrlPrefix}/${fullKey}`,
71
+ assetKey: fullKey,
55
72
  };
56
73
  }
57
- async writeFile(assetKey, stream) {
58
- this.logger.debug(`Writing file: ${assetKey}`);
59
- const path = this.safePath(assetKey);
74
+ async writeFile(args) {
75
+ this.logger.debug(`Writing file: ${args.bucket}/${args.key}`);
76
+ const path = this.safePath(args.bucket, args.key);
60
77
  try {
61
78
  await this.createDirectoryForFile(path);
62
79
  const fileStream = createWriteStream(path);
63
- // Use pipeline to properly manage stream piping and errors
64
- await pipeline(stream, fileStream);
80
+ await pipeline(args.stream, fileStream);
65
81
  return true;
66
82
  }
67
83
  catch (e) {
68
84
  console.error(e);
69
- this.logger.error(`Error writing content ${assetKey}`, e);
85
+ this.logger.error(`Error writing content ${args.bucket}/${args.key}`, e);
70
86
  return false;
71
87
  }
72
88
  }
73
- async copyFile(assetKey, fromAbsolutePath) {
74
- this.logger.debug(`Writing file: ${assetKey}`);
89
+ async copyFile(args) {
90
+ this.logger.debug(`Writing file: ${args.bucket}/${args.key}`);
75
91
  try {
76
- const path = this.safePath(assetKey);
92
+ const path = this.safePath(args.bucket, args.key);
77
93
  await this.createDirectoryForFile(path);
78
- await promises.copyFile(fromAbsolutePath, path);
94
+ await promises.copyFile(args.fromAbsolutePath, path);
95
+ return true;
79
96
  }
80
97
  catch (e) {
81
98
  console.error(e);
82
- this.logger.error(`Error inserting content ${assetKey}`, e);
99
+ this.logger.error(`Error inserting content ${args.bucket}/${args.key}`, e);
83
100
  }
84
101
  return false;
85
102
  }
86
- async readFile(assetKey) {
87
- this.logger.debug(`Getting key: ${assetKey}`);
88
- const filePath = this.safePath(assetKey);
103
+ async readFile(args) {
104
+ this.logger.debug(`Getting key: ${args.bucket}/${args.key}`);
105
+ const filePath = this.safePath(args.bucket, args.key);
89
106
  try {
90
107
  const stream = createReadStream(filePath);
91
- // Handle early stream errors (like file not found, permission denied, etc.)
92
108
  stream.on('error', (err) => {
93
- this.logger.error(`Error getting content ${assetKey}`, err);
109
+ this.logger.error(`Error getting content ${args.bucket}/${args.key}`, err);
94
110
  });
95
111
  return stream;
96
112
  }
97
113
  catch (e) {
98
- this.logger.error(`Error setting up stream for ${assetKey}`, e);
114
+ this.logger.error(`Error setting up stream for ${args.bucket}/${args.key}`, e);
99
115
  throw e;
100
116
  }
101
117
  }
102
- async readFileAsBuffer(assetKey) {
103
- const filePath = this.safePath(assetKey);
104
- this.logger.debug(`Reading file as buffer: ${assetKey}`);
118
+ async readFileAsBuffer(args) {
119
+ const filePath = this.safePath(args.bucket, args.key);
120
+ this.logger.debug(`Reading file as buffer: ${args.bucket}/${args.key}`);
105
121
  return readFile(filePath);
106
122
  }
107
- async deleteFile(assetKey) {
108
- this.logger.debug(`deleting key: ${assetKey}`);
123
+ async deleteFile(args) {
124
+ this.logger.debug(`deleting key: ${args.bucket}/${args.key}`);
109
125
  try {
110
- await promises.unlink(this.safePath(assetKey));
126
+ await promises.unlink(this.safePath(args.bucket, args.key));
111
127
  return true;
112
128
  }
113
129
  catch (e) {
114
- this.logger.error(`Error deleting content ${assetKey}`, e);
130
+ this.logger.error(`Error deleting content ${args.bucket}/${args.key}`, e);
115
131
  }
116
132
  return false;
117
133
  }
@@ -13,7 +13,7 @@ export declare class PikkuSessionService<UserSession extends CoreUserSession> im
13
13
  private sessionStore?;
14
14
  sessionChanged: boolean;
15
15
  initial: UserSession | undefined;
16
- private session;
16
+ protected session: UserSession | undefined;
17
17
  private pikkuUserId?;
18
18
  constructor(sessionStore?: SessionStore<UserSession> | undefined);
19
19
  setPikkuUserId(id: string): void;
@@ -49,6 +49,30 @@ export function defineServiceTests(config) {
49
49
  test('removeChannels with empty array is no-op', async () => {
50
50
  await store.removeChannels([]);
51
51
  });
52
+ test('session round-trip — set / get / clear', async () => {
53
+ await store.addChannel({
54
+ channelId: 'ch-state',
55
+ channelName: 'test-channel',
56
+ });
57
+ // initially undefined
58
+ const empty = await store.getState('ch-state');
59
+ assert.equal(empty, undefined);
60
+ // set + get
61
+ const payload = { userId: 'u-7', meta: { foo: 1 } };
62
+ await store.setState('ch-state', payload);
63
+ const got = await store.getState('ch-state');
64
+ assert.deepEqual(got, payload);
65
+ // overwrite
66
+ const next = { userId: 'u-8' };
67
+ await store.setState('ch-state', next);
68
+ const got2 = await store.getState('ch-state');
69
+ assert.deepEqual(got2, next);
70
+ // clear
71
+ await store.clearState('ch-state');
72
+ const after = await store.getState('ch-state');
73
+ assert.equal(after, undefined);
74
+ await store.removeChannels(['ch-state']);
75
+ });
52
76
  });
53
77
  }
54
78
  if (services.eventHubStore) {
@@ -5,6 +5,7 @@ import type { SchemaService } from '../services/schema-service.js';
5
5
  import type { JWTService } from '../services/jwt-service.js';
6
6
  import type { PikkuHTTP } from '../wirings/http/http.types.js';
7
7
  import type { PikkuChannel, CorePikkuChannelMiddleware, CorePikkuChannelMiddlewareFactory } from '../wirings/channel/channel.types.js';
8
+ import type { EventHubService } from '../wirings/channel/eventhub-service.js';
8
9
  import type { PikkuRPC } from '../wirings/rpc/rpc-types.js';
9
10
  import type { PikkuMCP } from '../wirings/mcp/mcp.types.js';
10
11
  import type { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js';
@@ -82,6 +83,7 @@ export type FunctionRuntimeMeta = {
82
83
  version?: number;
83
84
  approvalRequired?: boolean;
84
85
  approvalDescription?: string;
86
+ implementationHash?: string;
85
87
  contractHash?: string;
86
88
  inputHash?: string;
87
89
  outputHash?: string;
@@ -162,6 +164,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
162
164
  workflowService?: WorkflowService;
163
165
  /** The queue service */
164
166
  queueService?: QueueService;
167
+ /** Event hub for realtime pub/sub across channels */
168
+ eventHub?: EventHubService<Record<string, any>>;
165
169
  /** The scheduler service */
166
170
  schedulerService?: SchedulerService;
167
171
  /** The deployment service for service discovery */
@@ -88,6 +88,7 @@ export interface PikkuPackageState {
88
88
  middleware: {
89
89
  tagGroup: Record<string, CorePikkuMiddlewareGroup>;
90
90
  httpGroup: Record<string, CorePikkuMiddlewareGroup>;
91
+ global: CorePikkuMiddlewareGroup;
91
92
  };
92
93
  channelMiddleware: {
93
94
  tagGroup: Record<string, CorePikkuChannelMiddleware[]>;
@@ -95,6 +96,7 @@ export interface PikkuPackageState {
95
96
  permissions: {
96
97
  tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
97
98
  httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
99
+ global: (CorePermissionGroup | CorePikkuPermission)[];
98
100
  };
99
101
  misc: {
100
102
  errors: Map<PikkuError, ErrorDetails>;
@@ -103,24 +105,6 @@ export interface PikkuPackageState {
103
105
  channelMiddleware: Record<string, CorePikkuChannelMiddleware[]>;
104
106
  permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
105
107
  };
106
- models: {
107
- config: {
108
- models?: Record<string, string | {
109
- model: string;
110
- temperature?: number;
111
- maxSteps?: number;
112
- }>;
113
- agentDefaults?: {
114
- temperature?: number;
115
- maxSteps?: number;
116
- };
117
- agentOverrides?: Record<string, {
118
- model?: string;
119
- temperature?: number;
120
- maxSteps?: number;
121
- }>;
122
- } | null;
123
- };
124
108
  package: {
125
109
  /** Service factory functions for addon packages */
126
110
  factories: {