@pikku/inspector 0.12.42 → 0.12.44
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 +266 -0
- package/dist/add/add-addon-bans.js +1 -0
- package/dist/add/add-ai-agent.d.ts +3 -1
- package/dist/add/add-ai-agent.js +82 -0
- package/dist/add/add-credential.js +5 -1
- package/dist/add/add-functions.js +10 -9
- package/dist/add/add-gateway.js +3 -0
- package/dist/add/add-http-route.js +2 -2
- package/dist/add/add-keyed-wiring.js +2 -0
- package/dist/add/add-mcp-prompt.js +0 -1
- package/dist/add/add-mcp-resource.js +0 -1
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +3 -177
- package/dist/add/add-queue-worker.js +3 -0
- package/dist/add/add-schedule.js +3 -0
- package/dist/add/add-scope.d.ts +2 -0
- package/dist/add/add-scope.js +146 -0
- package/dist/add/add-trigger.js +3 -0
- package/dist/add/add-wire-remote-addon.d.ts +10 -0
- package/dist/add/add-wire-remote-addon.js +66 -0
- package/dist/add/add-workflow-graph.js +32 -3
- package/dist/add/add-workflow.js +9 -4
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +4 -0
- package/dist/inspector.js +11 -5
- package/dist/types.d.ts +19 -13
- package/dist/utils/custom-types-generator.js +2 -1
- package/dist/utils/ensure-function-metadata.d.ts +15 -0
- package/dist/utils/ensure-function-metadata.js +24 -0
- package/dist/utils/filter-inspector-state.js +9 -0
- package/dist/utils/get-property-value.d.ts +7 -2
- package/dist/utils/get-property-value.js +48 -1
- package/dist/utils/load-addon-functions-meta.js +81 -6
- package/dist/utils/permissions.d.ts +1 -21
- package/dist/utils/permissions.js +17 -108
- package/dist/utils/post-process.d.ts +30 -1
- package/dist/utils/post-process.js +150 -72
- package/dist/utils/schema-generator.js +19 -4
- package/dist/utils/serialize-inspector-state.d.ts +13 -8
- package/dist/utils/serialize-inspector-state.js +12 -6
- package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
- package/dist/utils/serialize-permissions-groups-meta.js +0 -26
- package/dist/utils/workflow/derive-workflow-plan.js +9 -3
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +373 -91
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +274 -100
- package/dist/utils/workflow/dsl/patterns.d.ts +4 -0
- package/dist/utils/workflow/dsl/patterns.js +11 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +76 -5
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +8 -4
- package/dist/visit.js +4 -0
- package/package.json +3 -3
- package/run-tests.sh +1 -1
- package/src/add/add-addon-bans.ts +1 -0
- package/src/add/add-ai-agent.test.ts +87 -0
- package/src/add/add-ai-agent.ts +122 -0
- package/src/add/add-credential.ts +8 -0
- package/src/add/add-functions.ts +10 -12
- package/src/add/add-gateway.ts +11 -0
- package/src/add/add-http-route.ts +2 -8
- package/src/add/add-keyed-wiring.ts +2 -0
- package/src/add/add-mcp-prompt.ts +0 -1
- package/src/add/add-mcp-resource.ts +0 -1
- package/src/add/add-permission.ts +4 -242
- package/src/add/add-queue-worker.ts +11 -0
- package/src/add/add-schedule.ts +11 -0
- package/src/add/add-scope.test.ts +346 -0
- package/src/add/add-scope.ts +225 -0
- package/src/add/add-trigger.ts +11 -0
- package/src/add/add-wire-remote-addon.ts +77 -0
- package/src/add/add-workflow-edge-cases.test.ts +201 -0
- package/src/add/add-workflow-fanout.test.ts +60 -0
- package/src/add/add-workflow-graph-input.test.ts +94 -0
- package/src/add/add-workflow-graph-notes.test.ts +74 -0
- package/src/add/add-workflow-graph.ts +35 -3
- package/src/add/add-workflow-on-error.test.ts +118 -0
- package/src/add/add-workflow-return-switch.test.ts +121 -0
- package/src/add/add-workflow-silent-drops.test.ts +163 -0
- package/src/add/add-workflow-sleep-expression.test.ts +97 -0
- package/src/add/add-workflow.ts +9 -4
- package/src/add/config-value-metadata.test.ts +169 -0
- package/src/add/inline-wiring-function.test.ts +104 -0
- package/src/add/validate-workflow-graph-addons.test.ts +102 -0
- package/src/error-codes.ts +5 -0
- package/src/inspector.ts +14 -4
- package/src/types.ts +16 -17
- package/src/utils/custom-types-generator.test.ts +26 -1
- package/src/utils/custom-types-generator.ts +2 -1
- package/src/utils/ensure-function-metadata.ts +42 -0
- package/src/utils/filter-inspector-state.test.ts +0 -2
- package/src/utils/filter-inspector-state.ts +9 -0
- package/src/utils/get-property-value.test.ts +141 -0
- package/src/utils/get-property-value.ts +62 -1
- package/src/utils/load-addon-functions-meta.test.ts +157 -0
- package/src/utils/load-addon-functions-meta.ts +94 -6
- package/src/utils/load-addon-scopes.test.ts +138 -0
- package/src/utils/permissions.test.ts +7 -232
- package/src/utils/permissions.ts +20 -160
- package/src/utils/post-process.test.ts +269 -4
- package/src/utils/post-process.ts +216 -95
- package/src/utils/schema-generator.test.ts +164 -0
- package/src/utils/schema-generator.ts +26 -3
- package/src/utils/serialize-inspector-state.ts +22 -25
- package/src/utils/serialize-permissions-groups-meta.ts +0 -28
- package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
- package/src/utils/workflow/derive-workflow-plan.ts +11 -4
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +480 -119
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +71 -0
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +99 -0
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +161 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +121 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +116 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +145 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +311 -105
- package/src/utils/workflow/dsl/patterns.ts +18 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +82 -10
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +105 -0
- package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +11 -2
- package/src/visit.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -91,6 +91,7 @@ function convertStepToNode(
|
|
|
91
91
|
const node: FunctionNode = {
|
|
92
92
|
nodeId,
|
|
93
93
|
rpcName: step.rpcName,
|
|
94
|
+
stepName: step.stepName,
|
|
94
95
|
next: nextNodeId,
|
|
95
96
|
}
|
|
96
97
|
if (step.actor) {
|
|
@@ -116,9 +117,24 @@ function convertStepToNode(
|
|
|
116
117
|
if (step.options) {
|
|
117
118
|
node.options = {
|
|
118
119
|
retries: step.options.retries,
|
|
119
|
-
retryDelay: step.options.retryDelay
|
|
120
|
+
retryDelay: step.options.retryDelay,
|
|
120
121
|
}
|
|
121
122
|
}
|
|
123
|
+
// An onError handler is written as an rpc name, but the graph routes to
|
|
124
|
+
// node ids — materialise a node for it so the console has something to
|
|
125
|
+
// draw the error edge to, exactly as in a hand-authored graph.
|
|
126
|
+
if (step.options?.onError) {
|
|
127
|
+
const handlerId = `${nodeId}_onError`
|
|
128
|
+
node.onError = handlerId
|
|
129
|
+
return [
|
|
130
|
+
node,
|
|
131
|
+
{
|
|
132
|
+
nodeId: handlerId,
|
|
133
|
+
rpcName: step.options.onError,
|
|
134
|
+
stepName: `${step.stepName} (on error)`,
|
|
135
|
+
} satisfies FunctionNode,
|
|
136
|
+
]
|
|
137
|
+
}
|
|
122
138
|
return [node]
|
|
123
139
|
}
|
|
124
140
|
|
|
@@ -126,7 +142,37 @@ function convertStepToNode(
|
|
|
126
142
|
const node: FlowNode = {
|
|
127
143
|
nodeId,
|
|
128
144
|
flow: 'sleep',
|
|
145
|
+
stepName: step.stepName,
|
|
129
146
|
duration: step.duration,
|
|
147
|
+
expression: step.expression,
|
|
148
|
+
next: nextNodeId,
|
|
149
|
+
}
|
|
150
|
+
return [node]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'suspend': {
|
|
154
|
+
// A suspend yields no value, but it must still be a node: the previous
|
|
155
|
+
// step's `next` points at it, so omitting it dead-ends traversal and
|
|
156
|
+
// silently deletes every step that follows.
|
|
157
|
+
const node: FlowNode = {
|
|
158
|
+
nodeId,
|
|
159
|
+
flow: 'suspend',
|
|
160
|
+
reason: step.reason,
|
|
161
|
+
next: nextNodeId,
|
|
162
|
+
}
|
|
163
|
+
return [node]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
case 'approval': {
|
|
167
|
+
// Unlike `suspend` — which has no output and so is deliberately absent
|
|
168
|
+
// from the graph — an approval yields a value a later step can reference,
|
|
169
|
+
// so it must be a real node for $ref resolution and graphHash to see it.
|
|
170
|
+
const node: FlowNode = {
|
|
171
|
+
nodeId,
|
|
172
|
+
flow: 'approval',
|
|
173
|
+
reason: step.reason,
|
|
174
|
+
outputVar: step.outputVar,
|
|
175
|
+
expiry: step.expiry,
|
|
130
176
|
next: nextNodeId,
|
|
131
177
|
}
|
|
132
178
|
return [node]
|
|
@@ -199,16 +245,32 @@ function convertStepToNode(
|
|
|
199
245
|
entry: string
|
|
200
246
|
}> = []
|
|
201
247
|
|
|
248
|
+
// A case with no statements falls through to the next one. Dropping it
|
|
249
|
+
// would lose the value entirely, so it is held until an entry exists.
|
|
250
|
+
let fallingThrough: Array<{ value?: unknown; expression?: string }> = []
|
|
251
|
+
|
|
202
252
|
for (let i = 0; i < step.cases.length; i++) {
|
|
203
253
|
const caseSteps = convertStepsToNodes(
|
|
204
254
|
step.cases[i].steps,
|
|
205
255
|
`${nodeId}_case${i}`
|
|
206
256
|
)
|
|
207
|
-
if (caseSteps.length
|
|
257
|
+
if (caseSteps.length === 0) {
|
|
258
|
+
fallingThrough.push({
|
|
259
|
+
value: step.cases[i].value,
|
|
260
|
+
expression: step.cases[i].expression,
|
|
261
|
+
})
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
{
|
|
265
|
+
const entry = caseSteps[0].nodeId
|
|
266
|
+
for (const pending of fallingThrough) {
|
|
267
|
+
cases.push({ ...pending, entry })
|
|
268
|
+
}
|
|
269
|
+
fallingThrough = []
|
|
208
270
|
cases.push({
|
|
209
271
|
value: step.cases[i].value,
|
|
210
272
|
expression: step.cases[i].expression,
|
|
211
|
-
entry
|
|
273
|
+
entry,
|
|
212
274
|
})
|
|
213
275
|
// Link last case node to next (unless terminal flow)
|
|
214
276
|
if (nextNodeId) {
|
|
@@ -238,6 +300,14 @@ function convertStepToNode(
|
|
|
238
300
|
}
|
|
239
301
|
}
|
|
240
302
|
|
|
303
|
+
// Cases falling through past the last one land in default, or exit.
|
|
304
|
+
const trailingEntry = defaultEntry ?? nextNodeId
|
|
305
|
+
if (trailingEntry) {
|
|
306
|
+
for (const pending of fallingThrough) {
|
|
307
|
+
cases.push({ ...pending, entry: trailingEntry })
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
241
311
|
const node: FlowNode = {
|
|
242
312
|
nodeId,
|
|
243
313
|
flow: 'switch',
|
|
@@ -279,13 +349,13 @@ function convertStepToNode(
|
|
|
279
349
|
}
|
|
280
350
|
|
|
281
351
|
case 'fanout': {
|
|
282
|
-
// Convert
|
|
283
|
-
const childNodes =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
352
|
+
// Convert the per-iteration body into a `next`-chained sequence
|
|
353
|
+
const childNodes: SerializedGraphNode[] = []
|
|
354
|
+
for (let i = 0; i < step.body.length; i++) {
|
|
355
|
+
childNodes.push(
|
|
356
|
+
...convertStepToNode(step.body[i], i, step.body, `${nodeId}_item`)
|
|
357
|
+
)
|
|
358
|
+
}
|
|
289
359
|
|
|
290
360
|
const node: FlowNode = {
|
|
291
361
|
nodeId,
|
|
@@ -333,6 +403,7 @@ function convertStepToNode(
|
|
|
333
403
|
nodeId,
|
|
334
404
|
flow: 'return',
|
|
335
405
|
outputs: step.outputs,
|
|
406
|
+
spread: step.spread,
|
|
336
407
|
}
|
|
337
408
|
return [node]
|
|
338
409
|
}
|
|
@@ -352,6 +423,7 @@ function convertStepToNode(
|
|
|
352
423
|
flow: 'set',
|
|
353
424
|
variable: step.variable,
|
|
354
425
|
value: step.value,
|
|
426
|
+
expression: step.expression,
|
|
355
427
|
next: nextNodeId,
|
|
356
428
|
}
|
|
357
429
|
return [node]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { strict as assert } from 'assert'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { inspect } from '../../../inspector.js'
|
|
7
|
+
import type { InspectorLogger } from '../../../types.js'
|
|
8
|
+
|
|
9
|
+
const STEP_FILE = [
|
|
10
|
+
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
11
|
+
'export const notify = pikkuSessionlessFunc({',
|
|
12
|
+
" func: async () => ({ id: 'n' }),",
|
|
13
|
+
'})',
|
|
14
|
+
].join('\n')
|
|
15
|
+
|
|
16
|
+
async function nodesFor(body: string): Promise<Record<string, any>> {
|
|
17
|
+
const logger = {
|
|
18
|
+
debug: () => {},
|
|
19
|
+
info: () => {},
|
|
20
|
+
warn: () => {},
|
|
21
|
+
error: () => {},
|
|
22
|
+
diagnostic: () => {},
|
|
23
|
+
critical: () => {},
|
|
24
|
+
hasCriticalErrors: () => false,
|
|
25
|
+
} as unknown as InspectorLogger
|
|
26
|
+
|
|
27
|
+
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-fid-'))
|
|
28
|
+
const stepFile = join(rootDir, 'w.steps.ts')
|
|
29
|
+
const wfFile = join(rootDir, 'w.workflow.ts')
|
|
30
|
+
await writeFile(stepFile, STEP_FILE)
|
|
31
|
+
await writeFile(
|
|
32
|
+
wfFile,
|
|
33
|
+
[
|
|
34
|
+
"import { pikkuWorkflowFunc } from '@pikku/core/workflow'",
|
|
35
|
+
'export const wf = pikkuWorkflowFunc(async (_, data: any, { workflow }) => {',
|
|
36
|
+
body,
|
|
37
|
+
'})',
|
|
38
|
+
].join('\n')
|
|
39
|
+
)
|
|
40
|
+
try {
|
|
41
|
+
const state: any = await inspect(logger, [stepFile, wfFile], { rootDir })
|
|
42
|
+
const gm = state.workflows.graphMeta
|
|
43
|
+
const graph = (
|
|
44
|
+
gm instanceof Map ? [...gm.values()] : Object.values(gm)
|
|
45
|
+
)[0] as any
|
|
46
|
+
return graph?.nodes ?? {}
|
|
47
|
+
} finally {
|
|
48
|
+
await rm(rootDir, { recursive: true, force: true })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('fanout node identity — a body step must not overwrite its own loop', () => {
|
|
53
|
+
test('the fanout node survives alongside its named body step', async () => {
|
|
54
|
+
const nodes = await nodesFor(
|
|
55
|
+
[
|
|
56
|
+
' await Promise.all(',
|
|
57
|
+
' data.users.map(async (u: any) => {',
|
|
58
|
+
" await workflow.do('Notify', 'notify', { id: u.id })",
|
|
59
|
+
' })',
|
|
60
|
+
' )',
|
|
61
|
+
' return { ok: true }',
|
|
62
|
+
].join('\n')
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const fanout = Object.values(nodes).find(
|
|
66
|
+
(n: any) => n.flow === 'fanout'
|
|
67
|
+
) as any
|
|
68
|
+
assert.ok(
|
|
69
|
+
fanout,
|
|
70
|
+
`the loop must be in the graph — without it the workflow renders as a single call, got: ${JSON.stringify(nodes)}`
|
|
71
|
+
)
|
|
72
|
+
assert.ok(
|
|
73
|
+
fanout.childEntry && nodes[fanout.childEntry],
|
|
74
|
+
'the fanout must point at a body entry node that exists'
|
|
75
|
+
)
|
|
76
|
+
assert.notEqual(
|
|
77
|
+
fanout.nodeId,
|
|
78
|
+
fanout.childEntry,
|
|
79
|
+
'a fanout and its body entry must not share a node id'
|
|
80
|
+
)
|
|
81
|
+
assert.equal((nodes[fanout.childEntry] as any).rpcName, 'notify')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('the step following a fanout is still reachable', async () => {
|
|
85
|
+
const nodes = await nodesFor(
|
|
86
|
+
[
|
|
87
|
+
' await Promise.all(',
|
|
88
|
+
' data.users.map(async (u: any) => {',
|
|
89
|
+
" await workflow.do('Notify', 'notify', { id: u.id })",
|
|
90
|
+
' })',
|
|
91
|
+
' )',
|
|
92
|
+
' return { ok: true }',
|
|
93
|
+
].join('\n')
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const fanout = Object.values(nodes).find(
|
|
97
|
+
(n: any) => n.flow === 'fanout'
|
|
98
|
+
) as any
|
|
99
|
+
assert.ok(fanout, 'a fanout node should exist')
|
|
100
|
+
assert.ok(
|
|
101
|
+
fanout.next && nodes[fanout.next],
|
|
102
|
+
`the return after the loop must stay reachable, got: ${JSON.stringify(nodes)}`
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { computeGraphHash } from './finalize-workflows.js'
|
|
4
|
+
import type {
|
|
5
|
+
SerializedWorkflowGraph,
|
|
6
|
+
FunctionNode,
|
|
7
|
+
} from './workflow-graph.types.js'
|
|
8
|
+
|
|
9
|
+
function graph(
|
|
10
|
+
overrides: Partial<SerializedWorkflowGraph> = {},
|
|
11
|
+
nodeOverrides: Partial<FunctionNode> = {}
|
|
12
|
+
): SerializedWorkflowGraph {
|
|
13
|
+
const node: FunctionNode = {
|
|
14
|
+
nodeId: 'a',
|
|
15
|
+
rpcName: 'doThing',
|
|
16
|
+
input: { x: { $ref: 'trigger', path: 'x' } },
|
|
17
|
+
next: 'b',
|
|
18
|
+
...nodeOverrides,
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
name: 'wf',
|
|
22
|
+
pikkuFuncId: 'wf',
|
|
23
|
+
source: 'graph',
|
|
24
|
+
nodes: { a: node, b: { nodeId: 'b', rpcName: 'finish' } },
|
|
25
|
+
entryNodeIds: ['a'],
|
|
26
|
+
...overrides,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('computeGraphHash: node-level notes do not change the hash', () => {
|
|
31
|
+
const without = computeGraphHash(graph())
|
|
32
|
+
const withNote = computeGraphHash(
|
|
33
|
+
graph({}, { notes: 'imported node "Do Thing"' })
|
|
34
|
+
)
|
|
35
|
+
assert.strictEqual(withNote, without)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('computeGraphHash: graph-level notes do not change the hash', () => {
|
|
39
|
+
const without = computeGraphHash(graph())
|
|
40
|
+
const withNotes = computeGraphHash(
|
|
41
|
+
graph({ notes: ['sticky note one', 'sticky note two'] })
|
|
42
|
+
)
|
|
43
|
+
assert.strictEqual(withNotes, without)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('computeGraphHash: a topology change DOES change the hash', () => {
|
|
47
|
+
const base = computeGraphHash(graph())
|
|
48
|
+
const changed = computeGraphHash(graph({}, { next: 'c' }))
|
|
49
|
+
assert.notStrictEqual(changed, base)
|
|
50
|
+
})
|
|
@@ -102,12 +102,23 @@ function computeStepHashes(
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Hash of graph topology only. `notes` (node- and graph-level) is deliberately
|
|
107
|
+
* excluded so editing documentation never changes the hash / marks the workflow
|
|
108
|
+
* as a new version.
|
|
109
|
+
*/
|
|
110
|
+
export function computeGraphHash(graph: SerializedWorkflowGraph): string {
|
|
111
|
+
const nodesForHash = Object.fromEntries(
|
|
112
|
+
Object.entries(graph.nodes).map(([nodeId, node]) => {
|
|
113
|
+
const { notes: _notes, ...rest } = node as { notes?: string }
|
|
114
|
+
return [nodeId, rest]
|
|
115
|
+
})
|
|
116
|
+
)
|
|
106
117
|
return hashString(
|
|
107
118
|
canonicalJSON({
|
|
108
119
|
source: graph.source,
|
|
109
120
|
context: graph.context,
|
|
110
|
-
nodes:
|
|
121
|
+
nodes: nodesForHash,
|
|
111
122
|
entryNodeIds: graph.entryNodeIds,
|
|
112
123
|
}),
|
|
113
124
|
12
|
|
@@ -89,12 +89,14 @@ export function serializeWorkflowGraph(
|
|
|
89
89
|
input?: (ref: any) => Record<string, unknown>
|
|
90
90
|
next?: string | string[] | Record<string, string | string[]>
|
|
91
91
|
onError?: string | string[]
|
|
92
|
+
notes?: string
|
|
92
93
|
}
|
|
93
94
|
>
|
|
94
95
|
},
|
|
95
96
|
options?: {
|
|
96
97
|
description?: string
|
|
97
98
|
tags?: string[]
|
|
99
|
+
notes?: string[]
|
|
98
100
|
}
|
|
99
101
|
): SerializedWorkflowGraph {
|
|
100
102
|
const nodes: Record<string, SerializedGraphNode> = {}
|
|
@@ -149,6 +151,9 @@ export function serializeWorkflowGraph(
|
|
|
149
151
|
next: serializeNext(node.next),
|
|
150
152
|
onError: node.onError,
|
|
151
153
|
}
|
|
154
|
+
if (node.notes !== undefined) {
|
|
155
|
+
funcNode.notes = node.notes
|
|
156
|
+
}
|
|
152
157
|
nodes[nodeId] = funcNode
|
|
153
158
|
|
|
154
159
|
// Entry nodes have no incoming edges
|
|
@@ -163,6 +168,7 @@ export function serializeWorkflowGraph(
|
|
|
163
168
|
source: 'graph' as const,
|
|
164
169
|
description: options?.description,
|
|
165
170
|
tags: options?.tags,
|
|
171
|
+
notes: options?.notes,
|
|
166
172
|
nodes,
|
|
167
173
|
entryNodeIds,
|
|
168
174
|
}
|
|
@@ -76,6 +76,9 @@ export type SerializedNext =
|
|
|
76
76
|
/** Default target if no conditions match */
|
|
77
77
|
default?: string | string[]
|
|
78
78
|
}
|
|
79
|
+
// Key-based branching: the running node picks a key via `graph.branch(key)`,
|
|
80
|
+
// routing to `next[key]`. Keys are branch/output identifiers, not predicates.
|
|
81
|
+
| Record<string, string | string[]>
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
84
|
* Node execution options
|
|
@@ -83,8 +86,8 @@ export type SerializedNext =
|
|
|
83
86
|
export interface NodeOptions {
|
|
84
87
|
/** Number of retry attempts on failure */
|
|
85
88
|
retries?: number
|
|
86
|
-
/** Delay between retries (e.g., '1s', '5s') */
|
|
87
|
-
retryDelay?: string
|
|
89
|
+
/** Delay between retries (e.g., '1s', '5s', or a number of milliseconds) */
|
|
90
|
+
retryDelay?: string | number
|
|
88
91
|
/** Timeout for node execution (e.g., '30s', '5m') */
|
|
89
92
|
timeout?: string
|
|
90
93
|
}
|
|
@@ -94,6 +97,8 @@ export interface NodeOptions {
|
|
|
94
97
|
*/
|
|
95
98
|
export type FlowType =
|
|
96
99
|
| 'sleep'
|
|
100
|
+
| 'suspend'
|
|
101
|
+
| 'approval'
|
|
97
102
|
| 'branch'
|
|
98
103
|
| 'parallel'
|
|
99
104
|
| 'fanout'
|
|
@@ -128,6 +133,8 @@ interface BaseNode {
|
|
|
128
133
|
onError?: string | string[]
|
|
129
134
|
/** Execution options */
|
|
130
135
|
options?: NodeOptions
|
|
136
|
+
/** Free-text node documentation. Non-semantic — excluded from graphHash. */
|
|
137
|
+
notes?: string
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
/**
|
|
@@ -209,6 +216,8 @@ export interface SerializedWorkflowGraph {
|
|
|
209
216
|
context?: WorkflowContext
|
|
210
217
|
/** Serialized nodes */
|
|
211
218
|
nodes: Record<string, SerializedGraphNode>
|
|
219
|
+
/** Graph-level free-text notes (e.g. imported sticky notes). Non-semantic — excluded from graphHash. */
|
|
220
|
+
notes?: string[]
|
|
212
221
|
/** Entry node(s) - first nodes to execute */
|
|
213
222
|
entryNodeIds: string[]
|
|
214
223
|
/** Hash of graph topology (nodes, edges, input mappings) */
|
package/src/visit.ts
CHANGED
|
@@ -20,12 +20,14 @@ import { addChannel } from './add/add-channel.js'
|
|
|
20
20
|
import { addGateway } from './add/add-gateway.js'
|
|
21
21
|
import { addRPCInvocations } from './add/add-rpc-invocations.js'
|
|
22
22
|
import { addWireAddon } from './add/add-wire-addon.js'
|
|
23
|
+
import { addWireRemoteAddon } from './add/add-wire-remote-addon.js'
|
|
23
24
|
import { addMiddleware } from './add/add-middleware.js'
|
|
24
25
|
import { addPermission } from './add/add-permission.js'
|
|
25
26
|
import { addCLI, addCLIRenderers } from './add/add-cli.js'
|
|
26
27
|
import { addAuth } from './add/add-auth.js'
|
|
27
28
|
import { addSecret } from './add/add-secret.js'
|
|
28
29
|
import { addCredential } from './add/add-credential.js'
|
|
30
|
+
import { addScope } from './add/add-scope.js'
|
|
29
31
|
import { addVariable } from './add/add-variable.js'
|
|
30
32
|
import { addWorkflowGraph } from './add/add-workflow-graph.js'
|
|
31
33
|
import { addAIAgent } from './add/add-ai-agent.js'
|
|
@@ -96,6 +98,7 @@ export const visitSetup = (
|
|
|
96
98
|
|
|
97
99
|
addRPCInvocations(node, state, logger)
|
|
98
100
|
addWireAddon(node, state, logger)
|
|
101
|
+
addWireRemoteAddon(node, state, logger)
|
|
99
102
|
addMiddleware(logger, node, checker, state, options)
|
|
100
103
|
addPermission(logger, node, checker, state, options)
|
|
101
104
|
addApprovalDescription(logger, node, checker, state, options)
|
|
@@ -147,6 +150,7 @@ export const visitRoutes = (
|
|
|
147
150
|
addAuth(logger, node, checker, state, nextOptions)
|
|
148
151
|
addSecret(logger, node, checker, state, nextOptions)
|
|
149
152
|
addCredential(logger, node, checker, state, nextOptions)
|
|
153
|
+
addScope(logger, node, checker, state, nextOptions)
|
|
150
154
|
addVariable(logger, node, checker, state, nextOptions)
|
|
151
155
|
|
|
152
156
|
addHTTPRoute(logger, node, checker, state, nextOptions)
|