@pikku/core 0.12.14 → 0.12.16

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 (136) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/errors/errors.d.ts +4 -0
  3. package/dist/errors/errors.js +12 -0
  4. package/dist/function/function-runner.js +2 -1
  5. package/dist/function/functions.types.js +1 -0
  6. package/dist/function/index.d.ts +2 -1
  7. package/dist/function/index.js +1 -0
  8. package/dist/handle-error.d.ts +2 -2
  9. package/dist/handle-error.js +8 -8
  10. package/dist/index.d.ts +2 -3
  11. package/dist/index.js +1 -2
  12. package/dist/middleware/index.d.ts +3 -0
  13. package/dist/middleware/index.js +3 -0
  14. package/dist/middleware/remote-auth.js +5 -2
  15. package/dist/middleware/telemetry.d.ts +47 -0
  16. package/dist/middleware/telemetry.js +106 -0
  17. package/dist/middleware-runner.js +31 -4
  18. package/dist/permissions.d.ts +13 -1
  19. package/dist/permissions.js +51 -0
  20. package/dist/pikku-state.d.ts +0 -1
  21. package/dist/pikku-state.js +1 -4
  22. package/dist/remote.d.ts +10 -0
  23. package/dist/remote.js +32 -0
  24. package/dist/schema.js +2 -0
  25. package/dist/services/deployment-service.d.ts +12 -1
  26. package/dist/services/in-memory-queue-service.d.ts +7 -0
  27. package/dist/services/in-memory-queue-service.js +28 -0
  28. package/dist/services/index.d.ts +4 -1
  29. package/dist/services/index.js +2 -1
  30. package/dist/services/logger-console.d.ts +26 -40
  31. package/dist/services/logger-console.js +102 -46
  32. package/dist/services/logger.d.ts +5 -0
  33. package/dist/services/meta-service.d.ts +175 -0
  34. package/dist/services/meta-service.js +310 -0
  35. package/dist/services/workflow-service.d.ts +6 -1
  36. package/dist/testing/service-tests.js +0 -8
  37. package/dist/types/core.types.d.ts +21 -0
  38. package/dist/types/core.types.js +7 -1
  39. package/dist/types/state.types.d.ts +2 -2
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
  41. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  42. package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
  43. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
  44. package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
  45. package/dist/wirings/channel/channel-runner.js +3 -3
  46. package/dist/wirings/cli/cli-runner.js +5 -3
  47. package/dist/wirings/cli/command-parser.js +7 -3
  48. package/dist/wirings/http/http-runner.d.ts +1 -1
  49. package/dist/wirings/http/http-runner.js +23 -11
  50. package/dist/wirings/http/http.types.d.ts +3 -0
  51. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  52. package/dist/wirings/http/pikku-fetch-http-response.js +3 -0
  53. package/dist/wirings/mcp/mcp-runner.js +5 -3
  54. package/dist/wirings/mcp/mcp.types.d.ts +1 -1
  55. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  56. package/dist/wirings/queue/queue-runner.js +7 -3
  57. package/dist/wirings/rpc/rpc-runner.js +56 -90
  58. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  59. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  60. package/dist/wirings/trigger/trigger-runner.js +4 -2
  61. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  62. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  63. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  64. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  65. package/dist/wirings/workflow/index.d.ts +4 -2
  66. package/dist/wirings/workflow/index.js +4 -2
  67. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  68. package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
  69. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  70. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  71. package/dist/wirings/workflow/workflow.types.d.ts +17 -1
  72. package/package.json +4 -1
  73. package/src/errors/errors.ts +12 -0
  74. package/src/function/function-runner.ts +5 -4
  75. package/src/function/functions.types.ts +1 -0
  76. package/src/function/index.ts +10 -0
  77. package/src/handle-error.test.ts +2 -2
  78. package/src/handle-error.ts +8 -8
  79. package/src/index.ts +2 -2
  80. package/src/middleware/index.ts +3 -0
  81. package/src/middleware/remote-auth.test.ts +4 -4
  82. package/src/middleware/remote-auth.ts +7 -2
  83. package/src/middleware/telemetry.ts +110 -0
  84. package/src/middleware-runner.test.ts +149 -1
  85. package/src/middleware-runner.ts +48 -4
  86. package/src/permissions.ts +70 -0
  87. package/src/pikku-state.test.ts +0 -26
  88. package/src/pikku-state.ts +1 -5
  89. package/src/remote.ts +46 -0
  90. package/src/schema.ts +1 -0
  91. package/src/services/deployment-service.ts +17 -1
  92. package/src/services/in-memory-queue-service.ts +46 -0
  93. package/src/services/index.ts +21 -1
  94. package/src/services/logger-console.ts +127 -47
  95. package/src/services/logger.ts +6 -0
  96. package/src/services/meta-service.ts +523 -0
  97. package/src/services/workflow-service.ts +8 -0
  98. package/src/testing/service-tests.ts +0 -10
  99. package/src/types/core.types.ts +36 -1
  100. package/src/types/state.types.ts +2 -2
  101. package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
  102. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  103. package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
  104. package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
  105. package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
  106. package/src/wirings/channel/channel-runner.ts +4 -5
  107. package/src/wirings/channel/local/local-channel-runner.test.ts +5 -1
  108. package/src/wirings/cli/cli-runner.test.ts +8 -7
  109. package/src/wirings/cli/cli-runner.ts +6 -4
  110. package/src/wirings/cli/command-parser.ts +8 -3
  111. package/src/wirings/http/http-runner.ts +27 -11
  112. package/src/wirings/http/http.types.ts +3 -0
  113. package/src/wirings/http/pikku-fetch-http-response.ts +4 -0
  114. package/src/wirings/mcp/mcp-runner.ts +7 -9
  115. package/src/wirings/mcp/mcp.types.ts +1 -1
  116. package/src/wirings/queue/queue-runner.test.ts +5 -11
  117. package/src/wirings/queue/queue-runner.ts +11 -3
  118. package/src/wirings/rpc/rpc-runner.ts +82 -115
  119. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  120. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  121. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  122. package/src/wirings/trigger/trigger-runner.ts +6 -4
  123. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  124. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  125. package/src/wirings/workflow/index.ts +17 -6
  126. package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
  127. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  128. package/src/wirings/workflow/workflow.types.ts +16 -1
  129. package/tsconfig.tsbuildinfo +1 -1
  130. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  131. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  132. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  133. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  134. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  135. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  136. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -1,468 +0,0 @@
1
- import { pikkuState, getSingletonServices } from '../../pikku-state.js';
2
- import { canonicalJSON, hashString } from '../../utils/hash.js';
3
- import { runWorkflowGraph } from '../workflow/graph/graph-runner.js';
4
- import { ContextAwareRPCService, resolveNamespace } from '../rpc/rpc-runner.js';
5
- import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
6
- import { validateWorkflowWiring, computeEntryNodeIds, } from '../workflow/graph/graph-validation.js';
7
- function formatSchemaType(schema, depth = 0) {
8
- if (!schema)
9
- return 'any';
10
- if (schema.enum)
11
- return schema.enum.map((v) => JSON.stringify(v)).join(' | ');
12
- if (schema.type === 'array') {
13
- const itemType = schema.items
14
- ? formatSchemaType(schema.items, depth + 1)
15
- : 'any';
16
- return `${itemType}[]`;
17
- }
18
- if (schema.type === 'object' && schema.properties) {
19
- if (depth > 1)
20
- return 'object';
21
- const fields = Object.entries(schema.properties)
22
- .map(([k, v]) => `${k}: ${formatSchemaType(v, depth + 1)}`)
23
- .join(', ');
24
- return `{${fields}}`;
25
- }
26
- return schema.type || 'any';
27
- }
28
- function resolveToolMetaForWorkflow(toolName) {
29
- const resolved = toolName.includes(':') ? resolveNamespace(toolName) : null;
30
- let fnMeta;
31
- let schemas;
32
- if (resolved) {
33
- fnMeta = pikkuState(resolved.package, 'function', 'meta')[resolved.function];
34
- schemas = pikkuState(resolved.package, 'misc', 'schemas');
35
- }
36
- else {
37
- const rpcMeta = pikkuState(null, 'rpc', 'meta');
38
- const pikkuFuncId = rpcMeta[toolName];
39
- if (!pikkuFuncId)
40
- return null;
41
- fnMeta = pikkuState(null, 'function', 'meta')[pikkuFuncId];
42
- schemas = pikkuState(null, 'misc', 'schemas');
43
- }
44
- if (!fnMeta)
45
- return null;
46
- const inputSchema = fnMeta.inputSchemaName
47
- ? schemas.get(fnMeta.inputSchemaName)
48
- : null;
49
- const outputSchema = fnMeta.outputSchemaName
50
- ? schemas.get(fnMeta.outputSchemaName)
51
- : null;
52
- return { fnMeta, inputSchema, outputSchema, schemas };
53
- }
54
- function buildDetailedToolSchemas(toolNames) {
55
- const lines = [];
56
- for (const toolName of toolNames) {
57
- const meta = resolveToolMetaForWorkflow(toolName);
58
- if (!meta)
59
- continue;
60
- const { inputSchema, outputSchema } = meta;
61
- const inputProps = inputSchema?.properties
62
- ? Object.entries(inputSchema.properties)
63
- .map(([k, v]) => `${k}${inputSchema.required?.includes(k) ? '' : '?'}: ${formatSchemaType(v)}`)
64
- .join(', ')
65
- : '';
66
- const outputPaths = outputSchema ? collectOutputPaths(outputSchema) : [];
67
- lines.push(`**${toolName}**\n` +
68
- ` input: {${inputProps}}\n` +
69
- (outputPaths.length > 0
70
- ? ` output paths: ${outputPaths.join(', ')}`
71
- : ` output: void`));
72
- }
73
- return lines.join('\n');
74
- }
75
- function collectOutputPaths(schema, prefix = '') {
76
- if (!schema?.properties)
77
- return [];
78
- const paths = [];
79
- for (const [key, prop] of Object.entries(schema.properties)) {
80
- const path = prefix ? `${prefix}.${key}` : key;
81
- const type = prop.type || 'any';
82
- if (prop.type === 'object' && prop.properties) {
83
- paths.push(...collectOutputPaths(prop, path));
84
- }
85
- else {
86
- paths.push(`${path}: ${type}`);
87
- }
88
- }
89
- return paths;
90
- }
91
- export function buildDynamicWorkflowInstructions(tools, mode) {
92
- if (mode === 'read') {
93
- return ('\n\n## Existing Workflows\n\n' +
94
- 'You can list and execute previously saved workflows using listAgentWorkflows and executeAgentWorkflow.\n' +
95
- "Use listAgentWorkflows to discover what's available, then executeAgentWorkflow to run them.");
96
- }
97
- const modeGuidance = mode === 'always'
98
- ? 'When a user requests a complex multi-step task, prefer creating a workflow over making sequential tool calls.\n' +
99
- 'Check if a suitable workflow already exists with listAgentWorkflows first.\n' +
100
- 'If none exists, create one with createAgentWorkflow, save with saveAgentWorkflow, then execute.\n\n'
101
- : 'When you receive a request that would benefit from a reusable multi-step workflow,\n' +
102
- 'suggest creating one to the user. Explain the benefits (reusability, reliability,\n' +
103
- 'can run in background) and wait for confirmation before creating.\n' +
104
- 'Do NOT create workflows automatically — always propose and get user approval first.\n' +
105
- 'For one-off tasks, just use the tools directly.\n\n';
106
- const toolSummaryLines = [];
107
- for (const toolName of tools) {
108
- const meta = resolveToolMetaForWorkflow(toolName);
109
- if (!meta)
110
- continue;
111
- const { fnMeta, inputSchema, outputSchema } = meta;
112
- const desc = fnMeta.description || fnMeta.title || '';
113
- const inputFields = inputSchema?.properties
114
- ? Object.keys(inputSchema.properties).join(', ')
115
- : '';
116
- const outputFields = outputSchema?.properties
117
- ? Object.keys(outputSchema.properties).join(', ')
118
- : '';
119
- toolSummaryLines.push(`| \`${toolName}\` | ${desc || '-'} | ${inputFields || '-'} | ${outputFields || '-'} |`);
120
- }
121
- const sections = [
122
- '\n\n## Workflow Creation\n',
123
- modeGuidance,
124
- '### When to Create a Workflow\n' +
125
- 'Create a workflow when:\n' +
126
- '- The task has 2+ steps with data dependencies between them\n' +
127
- '- The task will be repeated (workflows are reusable)\n' +
128
- '- Steps can fail independently and need error handling\n' +
129
- '- Steps can run in parallel for better performance\n' +
130
- 'Do NOT create a workflow for single tool calls — just call the tool directly.\n',
131
- '### Process\n' +
132
- '1. Check if a suitable workflow exists: `listAgentWorkflows`\n' +
133
- '2. If the request is too vague to build a reliable workflow, ask the user to be more specific. For example, if a user says "make a workflow that does stuff with my data" — ask them what steps they need and what data flows between them.\n' +
134
- '3. Plan the graph: identify steps, dependencies, parallel paths, and failure modes\n' +
135
- '4. Create and validate: `createAgentWorkflow` — if validation fails, fix the errors and retry\n' +
136
- '5. Present the workflow to the user and get approval\n' +
137
- '6. Activate: `saveAgentWorkflow`\n' +
138
- '7. Run: `executeAgentWorkflow`\n',
139
- '### Available Tools\n' +
140
- '| Tool | Description | Input fields | Output fields |\n' +
141
- '|------|-------------|-------------|---------------|\n' +
142
- toolSummaryLines.join('\n') +
143
- '\n\n' +
144
- 'Full schemas (input types, output paths) are returned by `createAgentWorkflow` after validation.\n',
145
- '### Graph Format Reference\n\n' +
146
- '**Node:** `{ rpcName, input?, next?, onError? }`\n\n' +
147
- '**Data wiring (input field values):**\n' +
148
- '- `{"$ref": "trigger", "path": "fieldName"}` — extract field from trigger input (ALWAYS include `path`)\n' +
149
- '- `{"$ref": "nodeId", "path": "fieldName"}` — extract field from a previous node\'s output (ALWAYS include `path`)\n' +
150
- '- `path` supports dot notation for nested fields — use the "output paths" listed above to find the correct path\n' +
151
- '- Example: if a tool\'s output paths show `todo.id: string`, use `{"$ref": "nodeId", "path": "todo.id"}`\n' +
152
- '- `{"$template": {"parts": ["Hello ", "!"], "expressions": [{"$ref": "trigger", "path": "name"}]}}` — string interpolation\n' +
153
- '- Static values passed directly: `{"count": 10}`\n\n' +
154
- '**Flow control (`next`):**\n' +
155
- '- `"next": "b"` — sequential\n' +
156
- '- `"next": ["b", "c"]` — parallel fan-out\n' +
157
- '- `"next": {"ok": "b", "fail": "c"}` — conditional branch (node calls `graph.branch("ok")` at runtime)\n' +
158
- '- Omit `next` for terminal nodes. Entry nodes are auto-detected.\n\n' +
159
- '**Error handling:**\n' +
160
- '- `"onError": "handler"` — route to error handler on failure\n' +
161
- '- Error handler receives `{ error: { message: string } }` as input\n' +
162
- '- Without `onError`, a node failure fails the entire workflow\n\n' +
163
- '**Sub-workflows:** if `rpcName` matches an existing workflow name, it runs as a child workflow with its own execution context.\n\n' +
164
- '**Trigger input:** the workflow receives input when executed. Entry nodes extract fields via `$ref: "trigger"`. Document what fields your workflow expects in the `description`.\n',
165
- '### Design Principles\n' +
166
- '1. **Map dependencies first** — before writing nodes, identify which steps depend on which outputs\n' +
167
- '2. **Parallelize independent work** — steps with no data dependency should fan out via array `next`\n' +
168
- '3. **Add error handlers for external calls** — any node calling an external service should have `onError`\n' +
169
- "4. **Minimize blast radius** — isolate risky nodes so their failure doesn't cascade\n" +
170
- '5. **Name nodes by intent** — `fetchUsers`, `sendAlert` not `step1`, `step2`\n' +
171
- '6. **Keep graphs shallow** — prefer wide parallel graphs over deep sequential chains when possible\n',
172
- '### Common Mistakes\n' +
173
- '- `{"$ref": "trigger"}` without `path` — passes entire object, use `{"$ref": "trigger", "path": "field"}`\n' +
174
- '- Wrong path depth: if output paths show `todo.id`, use `"path": "todo.id"` not `"path": "id"`\n' +
175
- '- Type mismatch: wiring a string output to a number input\n' +
176
- '- Dangling `onError`/`next` references to nodes not in the graph\n' +
177
- '- Cycles: if A→B→A, there are no entry nodes\n',
178
- '### Examples\n\n' +
179
- '**Sequential with nested output path (if addTodo output paths show `todo.id`):**\n' +
180
- '```json\n{"add":{"rpcName":"addTodo","input":{"title":{"$ref":"trigger","path":"title"}},"next":"complete"},"complete":{"rpcName":"completeTodo","input":{"id":{"$ref":"add","path":"todo.id"}}}}\n```\n\n' +
181
- '**Error handling:**\n' +
182
- '```json\n{"deploy":{"rpcName":"deploy","input":{"env":{"$ref":"trigger","path":"env"}},"next":"notify","onError":"rollback"},"notify":{"rpcName":"alert","input":{"message":"Deployed"}},"rollback":{"rpcName":"rollback","input":{},"next":"alertFail"},"alertFail":{"rpcName":"alert","input":{"message":"Rolled back"}}}\n```\n\n' +
183
- '**Parallel fan-out:**\n' +
184
- '```json\n{"fetchA":{"rpcName":"getA","input":{},"next":"combine"},"fetchB":{"rpcName":"getB","input":{},"next":"combine"},"combine":{"rpcName":"graph:merge","input":{"items":[{"$ref":"fetchA","path":"data"},{"$ref":"fetchB","path":"data"}]}}}\n```',
185
- ];
186
- return sections.join('\n');
187
- }
188
- export function buildWorkflowTools(agentName, packageName, toolNames, mode, streamContext, sessionService) {
189
- const tools = [];
190
- if (mode !== 'read') {
191
- tools.push({
192
- name: 'createAgentWorkflow',
193
- description: 'Validate and create a draft workflow DAG that chains tools together. The graph is validated for wiring correctness and type compatibility. Call saveAgentWorkflow to activate it after the user approves.',
194
- inputSchema: {
195
- type: 'object',
196
- properties: {
197
- name: {
198
- type: 'string',
199
- description: 'Short name for the workflow (will be prefixed with ai:{agentName}:)',
200
- },
201
- description: {
202
- type: 'string',
203
- description: 'What this workflow does',
204
- },
205
- nodes: {
206
- type: 'string',
207
- description: 'JSON string of nodeId→node map. Node fields: rpcName (tool name), input (use $ref+path to wire data), next (flow control), onError (error routing). Must have 2+ nodes.',
208
- },
209
- },
210
- required: ['name', 'nodes'],
211
- },
212
- execute: async (input) => {
213
- const raw = input;
214
- const name = raw.name.replace(/[^a-zA-Z0-9_-]/g, '-');
215
- let nodes;
216
- if (typeof raw.nodes === 'string') {
217
- try {
218
- nodes = JSON.parse(raw.nodes);
219
- }
220
- catch {
221
- return { error: 'Invalid JSON in nodes field' };
222
- }
223
- }
224
- else {
225
- nodes = raw.nodes;
226
- }
227
- if (Object.keys(nodes).length < 2) {
228
- return {
229
- error: 'A workflow must have at least 2 nodes. A single node is just a tool call — use the tool directly instead.',
230
- };
231
- }
232
- const usedTools = [
233
- ...new Set(Object.values(nodes)
234
- .map((n) => n.rpcName)
235
- .filter(Boolean)),
236
- ];
237
- const toolSchemas = buildDetailedToolSchemas(usedTools);
238
- const validationErrors = validateWorkflowWiring(nodes, toolNames);
239
- if (validationErrors.length > 0) {
240
- return {
241
- error: 'Workflow validation failed',
242
- errors: validationErrors,
243
- toolSchemas,
244
- };
245
- }
246
- const entryNodeIds = computeEntryNodeIds(nodes);
247
- if (entryNodeIds.length === 0) {
248
- return {
249
- error: 'No entry nodes found. Every node is referenced by another node, creating a cycle.',
250
- };
251
- }
252
- const fullName = `ai:${agentName}:${name}`;
253
- const graphHash = hashString(canonicalJSON({ nodes, entryNodeIds }));
254
- const graph = {
255
- name: fullName,
256
- pikkuFuncId: fullName,
257
- source: 'ai-agent',
258
- description: raw.description,
259
- nodes,
260
- entryNodeIds,
261
- graphHash,
262
- };
263
- const singletonServices = getSingletonServices();
264
- if (!singletonServices?.workflowService) {
265
- return { error: 'Workflow service not available' };
266
- }
267
- await singletonServices.workflowService.upsertWorkflowVersion(fullName, graphHash, graph, 'ai-agent', 'draft');
268
- return {
269
- valid: true,
270
- workflowName: fullName,
271
- graphHash,
272
- entryNodes: entryNodeIds,
273
- nodeCount: Object.keys(nodes).length,
274
- toolSchemas,
275
- message: `Workflow '${fullName}' validated and saved as draft. Check the toolSchemas to verify your $ref paths match the output paths. Then present this to the user and call saveAgentWorkflow to activate it.`,
276
- };
277
- },
278
- });
279
- tools.push({
280
- name: 'saveAgentWorkflow',
281
- description: 'Activate a previously created draft workflow so it can be executed. Requires user approval.',
282
- inputSchema: {
283
- type: 'object',
284
- properties: {
285
- name: {
286
- type: 'string',
287
- description: 'Full workflow name returned by createAgentWorkflow (e.g. ai:agentName:myWorkflow)',
288
- },
289
- graphHash: {
290
- type: 'string',
291
- description: 'Graph hash returned by createAgentWorkflow',
292
- },
293
- },
294
- required: ['name', 'graphHash'],
295
- },
296
- needsApproval: true,
297
- approvalDescriptionFn: async (input) => {
298
- const raw = input;
299
- const fullName = raw.name.startsWith(`ai:${agentName}:`)
300
- ? raw.name
301
- : `ai:${agentName}:${raw.name}`;
302
- const singletonServices = getSingletonServices();
303
- if (!singletonServices?.workflowService) {
304
- return `Activate workflow '${fullName}'`;
305
- }
306
- const version = await singletonServices.workflowService.getWorkflowVersion(fullName, raw.graphHash);
307
- if (version) {
308
- const graph = version.graph;
309
- if (graph.nodes) {
310
- const desc = graph.description ? `\n${graph.description}` : '';
311
- return `Activate workflow '${fullName}' (${Object.keys(graph.nodes).length} nodes)${desc}`;
312
- }
313
- }
314
- return `Activate workflow '${fullName}'`;
315
- },
316
- execute: async (input) => {
317
- const raw = input;
318
- const fullName = raw.name.startsWith(`ai:${agentName}:`)
319
- ? raw.name
320
- : `ai:${agentName}:${raw.name}`;
321
- const singletonServices = getSingletonServices();
322
- if (!singletonServices?.workflowService) {
323
- return { error: 'Workflow service not available' };
324
- }
325
- const version = await singletonServices.workflowService.getWorkflowVersion(fullName, raw.graphHash);
326
- if (!version) {
327
- return {
328
- error: `Workflow '${fullName}' with hash '${raw.graphHash}' not found. Use createAgentWorkflow first.`,
329
- };
330
- }
331
- await singletonServices.workflowService.updateWorkflowVersionStatus(fullName, raw.graphHash, 'active');
332
- const graph = version.graph;
333
- const allMeta = pikkuState(null, 'workflows', 'meta');
334
- allMeta[fullName] = graph;
335
- if (streamContext) {
336
- streamContext.channel.send({
337
- type: 'workflow-created',
338
- workflowName: fullName,
339
- graph,
340
- });
341
- }
342
- return {
343
- success: true,
344
- workflowName: fullName,
345
- message: `Workflow '${fullName}' activated and ready to execute.`,
346
- };
347
- },
348
- });
349
- } // end if (mode !== 'read')
350
- tools.push({
351
- name: 'listAgentWorkflows',
352
- description: 'List previously saved workflows for this agent that can be executed.',
353
- inputSchema: {
354
- type: 'object',
355
- properties: {},
356
- },
357
- execute: async () => {
358
- const singletonServices = getSingletonServices();
359
- if (!singletonServices?.workflowService) {
360
- return { error: 'Workflow service not available' };
361
- }
362
- const results = [];
363
- const allMeta = pikkuState(null, 'workflows', 'meta');
364
- const prefix = `ai:${agentName}:`;
365
- for (const [name, meta] of Object.entries(allMeta)) {
366
- if (name.startsWith(prefix) && meta.source === 'ai-agent') {
367
- results.push({
368
- name,
369
- description: meta.description,
370
- graphHash: meta.graphHash,
371
- });
372
- }
373
- }
374
- if (results.length === 0) {
375
- const persisted = await singletonServices.workflowService.getAIGeneratedWorkflows(agentName);
376
- for (const wf of persisted) {
377
- results.push({
378
- name: wf.workflowName,
379
- graphHash: wf.graphHash,
380
- });
381
- }
382
- }
383
- return { workflows: results };
384
- },
385
- });
386
- tools.push({
387
- name: 'executeAgentWorkflow',
388
- description: 'Execute a previously saved workflow with the given input.',
389
- inputSchema: {
390
- type: 'object',
391
- properties: {
392
- name: {
393
- type: 'string',
394
- description: 'Workflow name (will be auto-prefixed with ai:{agentName}: if needed)',
395
- },
396
- input: {
397
- type: 'object',
398
- description: 'Input data for the workflow trigger',
399
- },
400
- },
401
- required: ['name'],
402
- },
403
- needsApproval: true,
404
- approvalDescriptionFn: async (input) => {
405
- const { name, input: workflowInput } = input;
406
- const fullName = name.startsWith(`ai:${agentName}:`)
407
- ? name
408
- : `ai:${agentName}:${name}`;
409
- const inputStr = workflowInput
410
- ? `\nInput: ${JSON.stringify(workflowInput)}`
411
- : '';
412
- return `Execute workflow '${fullName}'${inputStr}`;
413
- },
414
- execute: async (toolInput) => {
415
- const { name, input: workflowInput } = toolInput;
416
- const fullName = name.startsWith(`ai:${agentName}:`)
417
- ? name
418
- : `ai:${agentName}:${name}`;
419
- const singletonServices = getSingletonServices();
420
- if (!singletonServices?.workflowService) {
421
- return { error: 'Workflow service not available' };
422
- }
423
- const allMeta = pikkuState(null, 'workflows', 'meta');
424
- if (!allMeta[fullName]) {
425
- return {
426
- error: `Workflow '${fullName}' not found. Use listAgentWorkflows to see available workflows, or createAgentWorkflow to make a new one.`,
427
- };
428
- }
429
- const workflowService = singletonServices.workflowService;
430
- const wire = sessionService
431
- ? { ...createMiddlewareSessionWireProps(sessionService) }
432
- : {};
433
- const rpcService = new ContextAwareRPCService(singletonServices, wire, {
434
- sessionService,
435
- });
436
- const { runId } = await runWorkflowGraph(workflowService, fullName, workflowInput ?? {}, rpcService, true, undefined, { type: 'ai-agent' });
437
- const maxWait = 45000;
438
- const startTime = Date.now();
439
- let pollInterval = 100;
440
- while (Date.now() - startTime < maxWait) {
441
- const run = await workflowService.getRun(runId);
442
- if (!run) {
443
- return { error: `Workflow run '${runId}' not found` };
444
- }
445
- if (run.status === 'completed' ||
446
- run.status === 'failed' ||
447
- run.status === 'cancelled' ||
448
- run.status === 'suspended') {
449
- if (run.status === 'failed') {
450
- return {
451
- error: `Workflow failed: ${run.error?.message || 'Unknown error'}`,
452
- runId,
453
- };
454
- }
455
- return { result: run.output, runId, status: run.status };
456
- }
457
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
458
- pollInterval = Math.min(pollInterval * 1.5, 2000);
459
- }
460
- return {
461
- runId,
462
- status: 'running',
463
- message: 'Workflow is still running after timeout. It will continue in the background.',
464
- };
465
- },
466
- });
467
- return tools;
468
- }
@@ -1,36 +0,0 @@
1
- export declare function workflow<TWorkflowMap extends Record<string, {
2
- input: any;
3
- output: any;
4
- }>>(workflowName: string & keyof TWorkflowMap, options?: {
5
- pollIntervalMs?: number;
6
- }): {
7
- func: (services: any, data: any, wire: any) => Promise<any>;
8
- };
9
- export declare function workflowStart<TWorkflowMap extends Record<string, {
10
- input: any;
11
- output: any;
12
- }>>(workflowName: string & keyof TWorkflowMap): {
13
- func: (services: any, data: any, wire: any) => Promise<{
14
- runId: string;
15
- }>;
16
- };
17
- export declare function workflowStatus<TWorkflowMap extends Record<string, {
18
- input: any;
19
- output: any;
20
- }>>(_workflowName: string & keyof TWorkflowMap): {
21
- func: (services: any, data: {
22
- runId: string;
23
- }) => Promise<{
24
- id: string;
25
- status: 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled';
26
- output?: any;
27
- error?: {
28
- message?: string;
29
- };
30
- }>;
31
- };
32
- export declare function graphStart<TGraphsMap extends Record<string, Record<string, any>>>(graphName: string & keyof TGraphsMap, startNode: string): {
33
- func: (services: any, data: any, wire: any) => Promise<{
34
- runId: string;
35
- }>;
36
- };
@@ -1,38 +0,0 @@
1
- import { WorkflowRunNotFoundError } from './pikku-workflow-service.js';
2
- export function workflow(workflowName, options) {
3
- return {
4
- func: async (services, data, { rpc }) => {
5
- return services.workflowService.runToCompletion(workflowName, data, rpc, options);
6
- },
7
- };
8
- }
9
- export function workflowStart(workflowName) {
10
- return {
11
- func: async (_services, data, { rpc }) => {
12
- return rpc.startWorkflow(workflowName, data);
13
- },
14
- };
15
- }
16
- export function workflowStatus(_workflowName) {
17
- return {
18
- func: async (services, data) => {
19
- const run = await services.workflowService.getRun(data.runId);
20
- if (!run) {
21
- throw new WorkflowRunNotFoundError(data.runId);
22
- }
23
- return {
24
- id: run.id,
25
- status: run.status,
26
- output: run.output,
27
- error: run.error ? { message: run.error.message } : undefined,
28
- };
29
- },
30
- };
31
- }
32
- export function graphStart(graphName, startNode) {
33
- return {
34
- func: async (_services, data, { rpc }) => {
35
- return rpc.startWorkflow(graphName, data, { startNode });
36
- },
37
- };
38
- }