@pikku/core 0.12.70 → 0.12.72
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 +143 -0
- package/LICENSE +21 -0
- package/dist/function/function-runner.js +7 -2
- package/dist/handle-error.d.ts +2 -0
- package/dist/handle-error.js +8 -2
- package/dist/schema.d.ts +24 -0
- package/dist/schema.js +46 -0
- package/dist/services/in-memory-queue-service.d.ts +6 -0
- package/dist/services/in-memory-queue-service.js +8 -1
- package/dist/services/in-memory-workflow-service.d.ts +3 -5
- package/dist/services/in-memory-workflow-service.js +10 -19
- package/dist/services/workflow-service.d.ts +7 -5
- package/dist/types/core.types.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +0 -8
- package/dist/wirings/ai-agent/ai-agent-prepare.js +1 -2
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +0 -6
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +4 -2
- package/dist/wirings/http/http.types.d.ts +2 -0
- package/dist/wirings/http/index.d.ts +2 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/http/pikku-fetch-http-request.d.ts +11 -1
- package/dist/wirings/http/pikku-fetch-http-request.js +67 -3
- package/dist/wirings/mcp/mcp-runner.d.ts +5 -0
- package/dist/wirings/mcp/mcp-runner.js +5 -2
- package/dist/wirings/workflow/graph/graph-runner.js +3 -2
- package/dist/wirings/workflow/graph/graph-validation.d.ts +0 -2
- package/dist/wirings/workflow/graph/graph-validation.js +0 -142
- package/dist/wirings/workflow/graph/index.d.ts +1 -1
- package/dist/wirings/workflow/graph/index.js +1 -1
- package/dist/wirings/workflow/index.d.ts +0 -1
- package/dist/wirings/workflow/index.js +0 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +69 -15
- package/dist/wirings/workflow/pikku-workflow-service.js +260 -164
- package/dist/wirings/workflow/workflow.types.d.ts +1 -6
- package/package.json +1 -2
- package/src/function/function-runner.test.ts +75 -0
- package/src/function/function-runner.ts +15 -2
- package/src/gopass-secrets-removed.test.ts +51 -0
- package/src/handle-error.test.ts +108 -1
- package/src/handle-error.ts +10 -2
- package/src/schema.test.ts +103 -0
- package/src/schema.ts +51 -0
- package/src/services/in-memory-queue-service.test.ts +66 -1
- package/src/services/in-memory-queue-service.ts +13 -2
- package/src/services/in-memory-workflow-service.ts +12 -25
- package/src/services/workflow-service.ts +7 -4
- package/src/types/core.types.ts +7 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +0 -16
- package/src/wirings/ai-agent/ai-agent-agui.ts +0 -9
- package/src/wirings/ai-agent/ai-agent-prepare.ts +1 -2
- package/src/wirings/ai-agent/ai-agent.types.ts +0 -7
- package/src/wirings/http/http-runner.ts +4 -1
- package/src/wirings/http/http.types.ts +2 -0
- package/src/wirings/http/index.ts +5 -1
- package/src/wirings/http/pikku-fetch-http-request.test.ts +89 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +85 -3
- package/src/wirings/mcp/mcp-runner.test.ts +40 -0
- package/src/wirings/mcp/mcp-runner.ts +11 -2
- package/src/wirings/workflow/graph/graph-runner.ts +3 -2
- package/src/wirings/workflow/graph/graph-validation.test.ts +1 -144
- package/src/wirings/workflow/graph/graph-validation.ts +0 -196
- package/src/wirings/workflow/graph/index.ts +1 -5
- package/src/wirings/workflow/index.ts +0 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +377 -212
- package/src/wirings/workflow/scenario-expectations.test.ts +153 -0
- package/src/wirings/workflow/scenario-step.test.ts +1 -1
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-dispatch-payload.test.ts +59 -0
- package/src/wirings/workflow/workflow-mirror.test.ts +178 -0
- package/src/wirings/workflow/workflow-replay-snapshot.test.ts +139 -0
- package/src/wirings/workflow/workflow-run-context.test.ts +177 -0
- package/src/wirings/workflow/workflow-run-polling.test.ts +132 -0
- package/src/wirings/workflow/workflow-step-ordinal.test.ts +4 -4
- package/src/wirings/workflow/workflow.types.ts +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/src/services/gopass-secrets.ts +0 -78
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { parse as parseQuery } from 'picoquery';
|
|
2
2
|
import { parse as parseCookie } from 'cookie';
|
|
3
|
-
import { UnprocessableContentError } from '../../errors/errors.js';
|
|
3
|
+
import { PayloadTooLargeError, UnprocessableContentError, } from '../../errors/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* The largest request body read into memory when no limit is configured. Ample
|
|
6
|
+
* for JSON APIs and typical uploads while keeping a single request's memory
|
|
7
|
+
* footprint bounded.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
4
10
|
/**
|
|
5
11
|
* Abstract class representing a pikku request.
|
|
6
12
|
* @template In - The type of the request body.
|
|
@@ -14,9 +20,11 @@ export class PikkuFetchHTTPRequest {
|
|
|
14
20
|
#rawBodyText;
|
|
15
21
|
#rawBodyBuffer;
|
|
16
22
|
#rawBufferPromise;
|
|
17
|
-
|
|
23
|
+
#maxBodySize;
|
|
24
|
+
constructor(request, { maxBodySize = DEFAULT_MAX_BODY_SIZE } = {}) {
|
|
18
25
|
this.request = request;
|
|
19
26
|
this.#url = new URL(request.url);
|
|
27
|
+
this.#maxBodySize = maxBodySize;
|
|
20
28
|
}
|
|
21
29
|
method() {
|
|
22
30
|
return this.request.method.toLowerCase();
|
|
@@ -68,12 +76,65 @@ export class PikkuFetchHTTPRequest {
|
|
|
68
76
|
`toWebRequest just for headers) should be removed.`);
|
|
69
77
|
return this.#rawBufferPromise;
|
|
70
78
|
}
|
|
71
|
-
this.#rawBufferPromise = this
|
|
79
|
+
this.#rawBufferPromise = this.#readBoundedBuffer().then((buf) => {
|
|
72
80
|
this.#rawBodyBuffer = buf;
|
|
73
81
|
return buf;
|
|
74
82
|
});
|
|
75
83
|
return this.#rawBufferPromise;
|
|
76
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Reads the body while refusing to buffer more than `maxBodySize` bytes. The
|
|
87
|
+
* declared `content-length` is rejected up front so an oversized body is never
|
|
88
|
+
* transferred, and the stream is measured as it arrives because that header is
|
|
89
|
+
* both optional and attacker-controlled.
|
|
90
|
+
*/
|
|
91
|
+
async #readBoundedBuffer() {
|
|
92
|
+
const contentLength = this.request.headers.get('content-length');
|
|
93
|
+
if (contentLength !== null) {
|
|
94
|
+
const declaredSize = Number(contentLength);
|
|
95
|
+
if (Number.isFinite(declaredSize) && declaredSize > this.#maxBodySize) {
|
|
96
|
+
throw this.#payloadTooLarge();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const stream = this.request.body;
|
|
100
|
+
if (stream === null) {
|
|
101
|
+
const buffer = await this.request.arrayBuffer();
|
|
102
|
+
if (buffer.byteLength > this.#maxBodySize) {
|
|
103
|
+
throw this.#payloadTooLarge();
|
|
104
|
+
}
|
|
105
|
+
return buffer;
|
|
106
|
+
}
|
|
107
|
+
const reader = stream.getReader();
|
|
108
|
+
const chunks = [];
|
|
109
|
+
let size = 0;
|
|
110
|
+
try {
|
|
111
|
+
while (true) {
|
|
112
|
+
const { done, value } = await reader.read();
|
|
113
|
+
if (done) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
size += value.byteLength;
|
|
117
|
+
if (size > this.#maxBodySize) {
|
|
118
|
+
await reader.cancel();
|
|
119
|
+
throw this.#payloadTooLarge();
|
|
120
|
+
}
|
|
121
|
+
chunks.push(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
reader.releaseLock();
|
|
126
|
+
}
|
|
127
|
+
const body = new Uint8Array(size);
|
|
128
|
+
let offset = 0;
|
|
129
|
+
for (const chunk of chunks) {
|
|
130
|
+
body.set(chunk, offset);
|
|
131
|
+
offset += chunk.byteLength;
|
|
132
|
+
}
|
|
133
|
+
return body.buffer;
|
|
134
|
+
}
|
|
135
|
+
#payloadTooLarge() {
|
|
136
|
+
return new PayloadTooLargeError(`Request body exceeds the maximum size of ${this.#maxBodySize} bytes`);
|
|
137
|
+
}
|
|
77
138
|
async #readRawText() {
|
|
78
139
|
if (this.#rawBodyText !== undefined) {
|
|
79
140
|
return this.#rawBodyText;
|
|
@@ -191,6 +252,9 @@ export class PikkuFetchHTTPRequest {
|
|
|
191
252
|
}
|
|
192
253
|
}
|
|
193
254
|
catch (e) {
|
|
255
|
+
if (e instanceof PayloadTooLargeError) {
|
|
256
|
+
throw e;
|
|
257
|
+
}
|
|
194
258
|
throw new UnprocessableContentError(`Error parsing body: ${e}`);
|
|
195
259
|
}
|
|
196
260
|
return body;
|
|
@@ -6,6 +6,11 @@ export declare class MCPError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export type RunMCPEndpointParams<Tools extends string = any> = {
|
|
8
8
|
mcp?: PikkuMCP<Tools>;
|
|
9
|
+
/**
|
|
10
|
+
* Surface the error message + stack on unexpected internal errors.
|
|
11
|
+
* Defaults to enabled outside of production.
|
|
12
|
+
*/
|
|
13
|
+
exposeErrors?: boolean;
|
|
9
14
|
};
|
|
10
15
|
export type JsonRpcError = {
|
|
11
16
|
code: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getErrorResponse } from '../../errors/error-handler.js';
|
|
2
|
+
import { isProduction } from '../../env.js';
|
|
2
3
|
import { closeWireServices } from '../../utils.js';
|
|
3
4
|
import { pikkuState, getSingletonServices, getCreateWireServices, } from '../../pikku-state.js';
|
|
4
5
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
@@ -88,7 +89,7 @@ export async function runMCPPrompt(request, params, name) {
|
|
|
88
89
|
/**
|
|
89
90
|
* JSON-RPC 2.0 compatible MCP endpoint runner
|
|
90
91
|
*/
|
|
91
|
-
async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcpWire }) {
|
|
92
|
+
async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcpWire, exposeErrors = !isProduction() }) {
|
|
92
93
|
const singletonServices = getSingletonServices();
|
|
93
94
|
const createWireServices = getCreateWireServices();
|
|
94
95
|
let wireServices;
|
|
@@ -165,7 +166,9 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcp
|
|
|
165
166
|
id: request.id,
|
|
166
167
|
code: -32603,
|
|
167
168
|
message: 'Internal error',
|
|
168
|
-
data:
|
|
169
|
+
data: exposeErrors && !isProduction() && e instanceof Error
|
|
170
|
+
? { message: e.message, stack: e.stack }
|
|
171
|
+
: undefined,
|
|
169
172
|
});
|
|
170
173
|
}
|
|
171
174
|
}
|
|
@@ -399,8 +399,9 @@ export async function continueGraph(workflowService, runId, graphName, overrideM
|
|
|
399
399
|
}
|
|
400
400
|
return;
|
|
401
401
|
}
|
|
402
|
-
|
|
403
|
-
|
|
402
|
+
// The same run read a few lines up: nothing between here and there writes it,
|
|
403
|
+
// and `input` is fixed at creation anyway.
|
|
404
|
+
const triggerInput = currentRun?.input;
|
|
404
405
|
for (const fire of plan.toFire) {
|
|
405
406
|
const node = nodes[fire.logical];
|
|
406
407
|
if (!node?.rpcName)
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export declare function computeEntryNodeIds(nodes: Record<string, any>): string[];
|
|
2
|
-
export declare function validateWorkflowWiring(nodes: Record<string, any>, toolNames: string[]): string[];
|
|
3
1
|
export declare function generateMermaidDiagram(workflowName: string, nodes: Record<string, any>, entryNodeIds: string[]): string;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { pikkuState } from '../../../pikku-state.js';
|
|
2
|
-
import { resolveNamespace } from '../../rpc/rpc-runner.js';
|
|
3
1
|
function normalizeTargets(value) {
|
|
4
2
|
if (!value)
|
|
5
3
|
return [];
|
|
@@ -16,146 +14,6 @@ function normalizeTargets(value) {
|
|
|
16
14
|
}
|
|
17
15
|
return [];
|
|
18
16
|
}
|
|
19
|
-
function collectRefs(value, refs) {
|
|
20
|
-
if (typeof value === 'object' &&
|
|
21
|
-
value !== null &&
|
|
22
|
-
'$ref' in value &&
|
|
23
|
-
typeof value.$ref === 'string') {
|
|
24
|
-
refs.add(value.$ref);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (Array.isArray(value)) {
|
|
28
|
-
for (const item of value)
|
|
29
|
-
collectRefs(item, refs);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (typeof value === 'object' && value !== null) {
|
|
33
|
-
for (const v of Object.values(value))
|
|
34
|
-
collectRefs(v, refs);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function resolveToolMeta(toolName) {
|
|
38
|
-
const resolved = toolName.includes(':') ? resolveNamespace(toolName) : null;
|
|
39
|
-
if (resolved) {
|
|
40
|
-
const fnMeta = pikkuState(resolved.package, 'function', 'meta')[resolved.function];
|
|
41
|
-
const schemas = pikkuState(resolved.package, 'misc', 'schemas');
|
|
42
|
-
return fnMeta ? { fnMeta, schemas } : null;
|
|
43
|
-
}
|
|
44
|
-
const rpcMeta = pikkuState(null, 'rpc', 'meta');
|
|
45
|
-
const pikkuFuncId = rpcMeta[toolName];
|
|
46
|
-
if (!pikkuFuncId)
|
|
47
|
-
return null;
|
|
48
|
-
const fnMeta = pikkuState(null, 'function', 'meta')[pikkuFuncId];
|
|
49
|
-
const schemas = pikkuState(null, 'misc', 'schemas');
|
|
50
|
-
return fnMeta ? { fnMeta, schemas } : null;
|
|
51
|
-
}
|
|
52
|
-
export function computeEntryNodeIds(nodes) {
|
|
53
|
-
const referenced = new Set();
|
|
54
|
-
for (const node of Object.values(nodes)) {
|
|
55
|
-
if (node.next) {
|
|
56
|
-
for (const target of normalizeTargets(node.next)) {
|
|
57
|
-
referenced.add(target);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (node.onError) {
|
|
61
|
-
for (const target of normalizeTargets(node.onError)) {
|
|
62
|
-
referenced.add(target);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return Object.keys(nodes).filter((id) => !referenced.has(id));
|
|
67
|
-
}
|
|
68
|
-
export function validateWorkflowWiring(nodes, toolNames) {
|
|
69
|
-
const errors = [];
|
|
70
|
-
const nodeIds = new Set(Object.keys(nodes));
|
|
71
|
-
const toolSet = new Set(toolNames);
|
|
72
|
-
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
73
|
-
if (!node.rpcName) {
|
|
74
|
-
errors.push(`Node '${nodeId}' is missing 'rpcName'`);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (!toolSet.has(node.rpcName)) {
|
|
78
|
-
errors.push(`Node '${nodeId}' references unknown tool '${node.rpcName}'. Available tools: ${toolNames.join(', ')}`);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
const nextTargets = normalizeTargets(node.next);
|
|
82
|
-
for (const target of nextTargets) {
|
|
83
|
-
if (!nodeIds.has(target)) {
|
|
84
|
-
errors.push(`Node '${nodeId}' routes to unknown node '${target}' in 'next'`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
const errorTargets = normalizeTargets(node.onError);
|
|
88
|
-
for (const target of errorTargets) {
|
|
89
|
-
if (!nodeIds.has(target)) {
|
|
90
|
-
errors.push(`Node '${nodeId}' routes to unknown node '${target}' in 'onError'`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (!node.input)
|
|
94
|
-
continue;
|
|
95
|
-
const refs = new Set();
|
|
96
|
-
collectRefs(node.input, refs);
|
|
97
|
-
for (const ref of refs) {
|
|
98
|
-
if (ref === 'trigger' || ref === '$item') {
|
|
99
|
-
const targetMeta = resolveToolMeta(node.rpcName);
|
|
100
|
-
if (targetMeta?.fnMeta?.inputSchemaName) {
|
|
101
|
-
const targetSchema = targetMeta.schemas.get(targetMeta.fnMeta.inputSchemaName);
|
|
102
|
-
if (targetSchema?.properties) {
|
|
103
|
-
for (const [field, fieldValue] of Object.entries(node.input)) {
|
|
104
|
-
if (typeof fieldValue === 'object' &&
|
|
105
|
-
fieldValue !== null &&
|
|
106
|
-
fieldValue.$ref === ref &&
|
|
107
|
-
!fieldValue.path) {
|
|
108
|
-
const targetType = targetSchema.properties[field]?.type;
|
|
109
|
-
if (targetType && targetType !== 'object') {
|
|
110
|
-
errors.push(`Node '${nodeId}' input field '${field}' expects type '${targetType}', but references the whole ${ref} object without a path. Use { $ref: "${ref}", path: "${field}" } to extract the field.`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (!nodeIds.has(ref)) {
|
|
119
|
-
errors.push(`Node '${nodeId}' references unknown node '${ref}' in input`);
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const sourceNode = nodes[ref];
|
|
123
|
-
if (!sourceNode?.rpcName)
|
|
124
|
-
continue;
|
|
125
|
-
const sourceMeta = resolveToolMeta(sourceNode.rpcName);
|
|
126
|
-
if (!sourceMeta?.fnMeta?.outputSchemaName)
|
|
127
|
-
continue;
|
|
128
|
-
const sourceSchema = sourceMeta.schemas.get(sourceMeta.fnMeta.outputSchemaName);
|
|
129
|
-
if (!sourceSchema?.properties)
|
|
130
|
-
continue;
|
|
131
|
-
for (const [field, fieldValue] of Object.entries(node.input)) {
|
|
132
|
-
if (typeof fieldValue === 'object' &&
|
|
133
|
-
fieldValue !== null &&
|
|
134
|
-
fieldValue.$ref === ref &&
|
|
135
|
-
fieldValue.path) {
|
|
136
|
-
const pathRoot = fieldValue.path.split('.')[0];
|
|
137
|
-
if (sourceSchema.properties &&
|
|
138
|
-
!(pathRoot in sourceSchema.properties)) {
|
|
139
|
-
errors.push(`Node '${nodeId}' input field '${field}' references path '${fieldValue.path}' but node '${ref}' (${sourceNode.rpcName}) output has no property '${pathRoot}'`);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
const targetMeta = resolveToolMeta(node.rpcName);
|
|
143
|
-
if (!targetMeta?.fnMeta?.inputSchemaName)
|
|
144
|
-
continue;
|
|
145
|
-
const targetSchema = targetMeta.schemas.get(targetMeta.fnMeta.inputSchemaName);
|
|
146
|
-
if (!targetSchema?.properties?.[field])
|
|
147
|
-
continue;
|
|
148
|
-
const sourceType = sourceSchema.properties[pathRoot]?.type;
|
|
149
|
-
const targetType = targetSchema.properties[field].type;
|
|
150
|
-
if (sourceType && targetType && sourceType !== targetType) {
|
|
151
|
-
errors.push(`Node '${nodeId}' input field '${field}' expects type '${targetType}', but node '${ref}' output field '${pathRoot}' is type '${sourceType}'`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return errors;
|
|
158
|
-
}
|
|
159
17
|
export function generateMermaidDiagram(workflowName, nodes, entryNodeIds) {
|
|
160
18
|
const lines = ['graph TD'];
|
|
161
19
|
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
@@ -2,4 +2,4 @@ export { continueGraph, executeGraphStep, onGraphNodeComplete, runFromMeta, runW
|
|
|
2
2
|
export { template } from './template.js';
|
|
3
3
|
export type { TemplateString } from './template.js';
|
|
4
4
|
export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './wire-workflow-graph.js';
|
|
5
|
-
export {
|
|
5
|
+
export { generateMermaidDiagram } from './graph-validation.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { continueGraph, executeGraphStep, onGraphNodeComplete, runFromMeta, runWorkflowGraph, } from './graph-runner.js';
|
|
2
2
|
export { template } from './template.js';
|
|
3
3
|
export { pikkuWorkflowGraph, } from './wire-workflow-graph.js';
|
|
4
|
-
export {
|
|
4
|
+
export { generateMermaidDiagram } from './graph-validation.js';
|
|
@@ -10,7 +10,6 @@ export { addWorkflow } from './dsl/workflow-runner.js';
|
|
|
10
10
|
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
11
11
|
export { template, type TemplateString } from './graph/template.js';
|
|
12
12
|
export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './graph/wire-workflow-graph.js';
|
|
13
|
-
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
14
13
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
15
14
|
export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
|
|
16
15
|
export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, CoreFeatureScenario, FeatureMeta, FeatureMetaEntry, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
@@ -11,8 +11,6 @@ export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
|
11
11
|
// Graph helpers (template, pikkuWorkflowGraph)
|
|
12
12
|
export { template } from './graph/template.js';
|
|
13
13
|
export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
|
|
14
|
-
// Graph validation and dynamic workflow utilities
|
|
15
|
-
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
16
14
|
// Queue worker functions (registered by codegen, executed at runtime)
|
|
17
15
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
18
16
|
// Narrows the optional halves of the step wire, with a message that says what to do
|
|
@@ -154,14 +154,12 @@ export interface WorkflowRunExtension {
|
|
|
154
154
|
*/
|
|
155
155
|
onAfterRunFunc(context: RunLifecycleContext, outcome: 'completed' | 'failed' | 'interrupted', failure: unknown): Promise<void>;
|
|
156
156
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Abstract workflow state service
|
|
159
|
-
* Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
|
|
160
|
-
* Combines orchestration and step execution
|
|
161
|
-
*/
|
|
162
157
|
export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
163
|
-
private inlineRuns;
|
|
164
158
|
private runExtension?;
|
|
159
|
+
private runContexts;
|
|
160
|
+
private contextFor;
|
|
161
|
+
/** Drop a run's context once nothing is holding it open. */
|
|
162
|
+
private releaseContext;
|
|
165
163
|
protected get logger(): import("../../services/logger.js").Logger;
|
|
166
164
|
protected mirror?: WorkflowRunMirror;
|
|
167
165
|
protected readonly queueStrategy: 'per-workflow' | 'shared-groups';
|
|
@@ -171,7 +169,18 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
171
169
|
wireQueues?: boolean;
|
|
172
170
|
mirror?: WorkflowRunMirror;
|
|
173
171
|
} & WorkflowQueueOptions);
|
|
174
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Perform a state write, then shadow it to the mirror.
|
|
174
|
+
*
|
|
175
|
+
* The mirror is an observability sink, never a second source of truth, and
|
|
176
|
+
* both halves of that follow from this one shape: it is only ever told about
|
|
177
|
+
* a write that already landed, and a mirror that is down or throwing cannot
|
|
178
|
+
* fail — or even be seen by — the workflow it is watching.
|
|
179
|
+
*
|
|
180
|
+
* @param write - the authoritative write; its result is what the caller gets
|
|
181
|
+
* @param mirror - shadows the write, given the live mirror and what was written
|
|
182
|
+
*/
|
|
183
|
+
private mirrored;
|
|
175
184
|
/**
|
|
176
185
|
* Wire the queue-based orchestrator/step/sleeper workers.
|
|
177
186
|
* Subclasses that orchestrate without queues (e.g. Durable Objects) should
|
|
@@ -392,11 +401,6 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
392
401
|
graph: any;
|
|
393
402
|
source: string;
|
|
394
403
|
} | null>;
|
|
395
|
-
abstract getAIGeneratedWorkflows(agentName?: string): Promise<Array<{
|
|
396
|
-
workflowName: string;
|
|
397
|
-
graphHash: string;
|
|
398
|
-
graph: any;
|
|
399
|
-
}>>;
|
|
400
404
|
/**
|
|
401
405
|
* Resume a paused workflow by triggering the orchestrator
|
|
402
406
|
* @param runId - Run ID
|
|
@@ -480,9 +484,59 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
480
484
|
pollIntervalMs?: number;
|
|
481
485
|
wire?: WorkflowRunWire;
|
|
482
486
|
}): Promise<any>;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Read a run until it reaches an end state, backing off as it drags on.
|
|
489
|
+
*
|
|
490
|
+
* A fixed interval is wrong at both ends: it makes a workflow that finished
|
|
491
|
+
* in milliseconds wait out the whole interval anyway, and it keeps reading a
|
|
492
|
+
* long-running one at full rate for as long as it lasts. Starting short and
|
|
493
|
+
* growing to `maxIntervalMs` returns quick runs promptly while a slow run's
|
|
494
|
+
* read cost grows logarithmically rather than linearly with its duration.
|
|
495
|
+
*/
|
|
496
|
+
protected awaitRunEnd(runId: string, maxIntervalMs: number): Promise<WorkflowRun>;
|
|
497
|
+
/**
|
|
498
|
+
* Wait between two reads of a run.
|
|
499
|
+
*
|
|
500
|
+
* Its own method so the backoff schedule can be asserted on directly. Timing
|
|
501
|
+
* a poll loop by the clock measures the host's scheduler as much as the
|
|
502
|
+
* policy — `setTimeout(40)` routinely returns late on a loaded runner — which
|
|
503
|
+
* makes the obvious test both slow and flaky.
|
|
504
|
+
*/
|
|
505
|
+
protected waitBeforeNextRead(ms: number): Promise<void>;
|
|
506
|
+
/**
|
|
507
|
+
* Every step of a run in one read, or `null` if this backend has no bulk read.
|
|
508
|
+
*
|
|
509
|
+
* A replay walks the DSL body from the top, and each step it passes asks for
|
|
510
|
+
* its own row — so a run of N steps costs N reads per replay and O(N^2) over
|
|
511
|
+
* its lifetime. Backends that can answer this in a single query collapse that
|
|
512
|
+
* to one read per replay.
|
|
513
|
+
*/
|
|
514
|
+
protected listStepStates(_runId: string): Promise<Array<StepState & {
|
|
515
|
+
stepName: string;
|
|
516
|
+
}> | null>;
|
|
517
|
+
/**
|
|
518
|
+
* Begin a replay pass: fresh ordinal counters, and one read of the steps the
|
|
519
|
+
* run has already taken so the walk back to where it left off is served from
|
|
520
|
+
* memory. Safe because a pass reaches each step key at most once, and the
|
|
521
|
+
* steps it replays past are `succeeded` and therefore immutable.
|
|
522
|
+
*/
|
|
523
|
+
private beginReplay;
|
|
524
|
+
private endReplay;
|
|
525
|
+
/**
|
|
526
|
+
* The step row for `stepName`, creating it if the run has not reached it
|
|
527
|
+
* before. Served from the replay snapshot when one is loaded.
|
|
528
|
+
*/
|
|
529
|
+
private loadOrCreateStep;
|
|
530
|
+
/**
|
|
531
|
+
* The run's immutable half — which workflow it is, the wire it was started
|
|
532
|
+
* on, its input. `getRun` is otherwise called several times per step for
|
|
533
|
+
* answers that were all fixed at creation, so a replay reads it once and
|
|
534
|
+
* hands the same object to everyone who only needs that half.
|
|
535
|
+
*
|
|
536
|
+
* Anyone who needs `status`, `output`, `error` or `state` must call `getRun`:
|
|
537
|
+
* those move while the run executes, and a cached copy would be a lie.
|
|
538
|
+
*/
|
|
539
|
+
private getRunIdentity;
|
|
486
540
|
/** The step the DSL walk last reached (the predecessor for the next step). */
|
|
487
541
|
private lastStepName;
|
|
488
542
|
/**
|