@pikku/inspector 0.11.2 → 0.12.0

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 (182) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +250 -75
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +152 -66
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.js +14 -0
  23. package/dist/add/add-schedule.js +11 -5
  24. package/dist/add/add-secret.d.ts +3 -0
  25. package/dist/add/add-secret.js +82 -0
  26. package/dist/add/add-trigger.d.ts +2 -0
  27. package/dist/add/add-trigger.js +87 -0
  28. package/dist/add/add-variable.d.ts +1 -0
  29. package/dist/add/add-variable.js +8 -0
  30. package/dist/add/add-workflow-graph.d.ts +3 -2
  31. package/dist/add/add-workflow-graph.js +143 -406
  32. package/dist/add/add-workflow.js +6 -4
  33. package/dist/error-codes.d.ts +14 -1
  34. package/dist/error-codes.js +19 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.js +5 -4
  37. package/dist/inspector.d.ts +1 -1
  38. package/dist/inspector.js +91 -14
  39. package/dist/schema-generator.d.ts +1 -0
  40. package/dist/schema-generator.js +1 -0
  41. package/dist/types-map.js +10 -1
  42. package/dist/types.d.ts +163 -39
  43. package/dist/utils/compute-required-schemas.d.ts +4 -0
  44. package/dist/utils/compute-required-schemas.js +41 -0
  45. package/dist/utils/contract-hashes.d.ts +35 -0
  46. package/dist/utils/contract-hashes.js +202 -0
  47. package/dist/utils/custom-types-generator.d.ts +9 -0
  48. package/dist/utils/custom-types-generator.js +71 -0
  49. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  50. package/dist/utils/detect-schema-vendor.js +76 -0
  51. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  52. package/dist/utils/ensure-function-metadata.js +220 -6
  53. package/dist/utils/extract-function-name.d.ts +5 -16
  54. package/dist/utils/extract-function-name.js +86 -291
  55. package/dist/utils/extract-services.d.ts +2 -1
  56. package/dist/utils/extract-services.js +25 -1
  57. package/dist/utils/filter-inspector-state.js +107 -23
  58. package/dist/utils/get-property-value.d.ts +6 -1
  59. package/dist/utils/get-property-value.js +28 -3
  60. package/dist/utils/hash.d.ts +2 -0
  61. package/dist/utils/hash.js +23 -0
  62. package/dist/utils/middleware.d.ts +7 -30
  63. package/dist/utils/middleware.js +80 -66
  64. package/dist/utils/permissions.d.ts +2 -2
  65. package/dist/utils/permissions.js +10 -10
  66. package/dist/utils/post-process.d.ts +9 -10
  67. package/dist/utils/post-process.js +231 -24
  68. package/dist/utils/resolve-external-package.d.ts +12 -0
  69. package/dist/utils/resolve-external-package.js +34 -0
  70. package/dist/utils/resolve-function-types.d.ts +6 -0
  71. package/dist/utils/resolve-function-types.js +29 -0
  72. package/dist/utils/resolve-identifier.d.ts +10 -0
  73. package/dist/utils/resolve-identifier.js +36 -0
  74. package/dist/utils/resolve-versions.d.ts +2 -0
  75. package/dist/utils/resolve-versions.js +78 -0
  76. package/dist/utils/schema-generator.d.ts +9 -0
  77. package/dist/utils/schema-generator.js +209 -0
  78. package/dist/utils/serialize-inspector-state.d.ts +59 -22
  79. package/dist/utils/serialize-inspector-state.js +92 -20
  80. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  81. package/dist/utils/serialize-mcp-json.js +99 -0
  82. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  83. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  84. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  85. package/dist/utils/serialize-openapi-json.js +151 -0
  86. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  87. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  88. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  89. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  90. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  91. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  92. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  93. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  94. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  95. package/dist/utils/workflow/graph/index.d.ts +2 -0
  96. package/dist/utils/workflow/graph/index.js +2 -0
  97. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  98. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  99. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  100. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  101. package/dist/visit.js +11 -6
  102. package/package.json +14 -4
  103. package/src/add/add-ai-agent.ts +468 -0
  104. package/src/add/add-channel.ts +82 -79
  105. package/src/add/add-cli.ts +49 -20
  106. package/src/add/add-file-with-factory.ts +2 -0
  107. package/src/add/add-functions.ts +330 -86
  108. package/src/add/add-http-route.ts +245 -88
  109. package/src/add/add-http-routes.ts +228 -0
  110. package/src/add/add-keyed-wiring.ts +151 -0
  111. package/src/add/add-mcp-prompt.ts +26 -15
  112. package/src/add/add-mcp-resource.ts +27 -15
  113. package/src/add/add-middleware.ts +482 -80
  114. package/src/add/add-permission.ts +199 -40
  115. package/src/add/add-queue-worker.ts +24 -19
  116. package/src/add/add-rpc-invocations.ts +17 -0
  117. package/src/add/add-schedule.ts +16 -11
  118. package/src/add/add-secret.ts +140 -0
  119. package/src/add/add-trigger.ts +154 -0
  120. package/src/add/add-variable.ts +9 -0
  121. package/src/add/add-workflow-graph.ts +180 -522
  122. package/src/add/add-workflow.ts +5 -4
  123. package/src/error-codes.ts +24 -1
  124. package/src/index.ts +22 -13
  125. package/src/inspector.ts +129 -17
  126. package/src/schema-generator.ts +1 -0
  127. package/src/types-map.ts +12 -1
  128. package/src/types.ts +175 -58
  129. package/src/utils/compute-required-schemas.ts +49 -0
  130. package/src/utils/contract-hashes.test.ts +528 -0
  131. package/src/utils/contract-hashes.ts +290 -0
  132. package/src/utils/custom-types-generator.ts +88 -0
  133. package/src/utils/detect-schema-vendor.ts +90 -0
  134. package/src/utils/ensure-function-metadata.ts +324 -7
  135. package/src/utils/extract-function-name.ts +101 -351
  136. package/src/utils/extract-services.ts +35 -2
  137. package/src/utils/filter-inspector-state.test.ts +34 -20
  138. package/src/utils/filter-inspector-state.ts +140 -31
  139. package/src/utils/get-property-value.ts +42 -4
  140. package/src/utils/hash.ts +26 -0
  141. package/src/utils/middleware.test.ts +204 -0
  142. package/src/utils/middleware.ts +129 -67
  143. package/src/utils/permissions.test.ts +35 -12
  144. package/src/utils/permissions.ts +10 -10
  145. package/src/utils/post-process.ts +283 -43
  146. package/src/utils/resolve-external-package.ts +42 -0
  147. package/src/utils/resolve-function-types.ts +42 -0
  148. package/src/utils/resolve-identifier.ts +46 -0
  149. package/src/utils/resolve-versions.test.ts +249 -0
  150. package/src/utils/resolve-versions.ts +105 -0
  151. package/src/utils/schema-generator.ts +329 -0
  152. package/src/utils/serialize-inspector-state.ts +163 -40
  153. package/src/utils/serialize-mcp-json.ts +145 -0
  154. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  155. package/src/utils/serialize-openapi-json.ts +277 -0
  156. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  157. package/src/utils/test-data/inspector-state.json +69 -66
  158. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  159. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
  160. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  161. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  162. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  163. package/src/utils/workflow/graph/index.ts +5 -0
  164. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  165. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  166. package/src/visit.ts +12 -6
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/dist/add/add-forge-credential.d.ts +0 -8
  169. package/dist/add/add-forge-credential.js +0 -77
  170. package/dist/add/add-forge-node.d.ts +0 -7
  171. package/dist/add/add-forge-node.js +0 -77
  172. package/dist/add/add-mcp-tool.d.ts +0 -2
  173. package/dist/add/add-mcp-tool.js +0 -81
  174. package/dist/utils/extract-service-metadata.d.ts +0 -19
  175. package/dist/utils/extract-service-metadata.js +0 -244
  176. package/dist/utils/write-service-metadata.d.ts +0 -13
  177. package/dist/utils/write-service-metadata.js +0 -37
  178. package/src/add/add-forge-credential.ts +0 -119
  179. package/src/add/add-forge-node.ts +0 -132
  180. package/src/add/add-mcp-tool.ts +0 -141
  181. package/src/utils/extract-service-metadata.ts +0 -353
  182. package/src/utils/write-service-metadata.ts +0 -51
@@ -134,6 +134,15 @@ function valueToCode(value: unknown, itemVar?: string): string {
134
134
  if (isTemplateRef(value)) {
135
135
  return templateRefToCode(value, itemVar)
136
136
  }
137
+ if (Array.isArray(value)) {
138
+ const elements = value.map((v) => valueToCode(v, itemVar))
139
+ return `[${elements.join(', ')}]`
140
+ }
141
+ if (typeof value === 'object' && value !== null) {
142
+ const entries = Object.entries(value)
143
+ const props = entries.map(([k, v]) => `${k}: ${valueToCode(v, itemVar)}`)
144
+ return `{ ${props.join(', ')} }`
145
+ }
137
146
  return JSON.stringify(value)
138
147
  }
139
148
 
@@ -845,6 +854,35 @@ function templateRefToGraphCode(
845
854
  return `template('${templateStr}', [${refs.join(', ')}])`
846
855
  }
847
856
 
857
+ function valueToGraphCode(
858
+ value: unknown,
859
+ outputVarToNodeId: Map<string, string>,
860
+ refTracker: { hasRefs: boolean }
861
+ ): string {
862
+ if (isDataRef(value)) {
863
+ refTracker.hasRefs = true
864
+ return dataRefToGraphRef(value, outputVarToNodeId)
865
+ }
866
+ if (isTemplateRef(value)) {
867
+ refTracker.hasRefs = true
868
+ return templateRefToGraphCode(value, outputVarToNodeId)
869
+ }
870
+ if (Array.isArray(value)) {
871
+ const elements = value.map((v) =>
872
+ valueToGraphCode(v, outputVarToNodeId, refTracker)
873
+ )
874
+ return `[${elements.join(', ')}]`
875
+ }
876
+ if (typeof value === 'object' && value !== null) {
877
+ const entries = Object.entries(value)
878
+ const props = entries.map(
879
+ ([k, v]) => `${k}: ${valueToGraphCode(v, outputVarToNodeId, refTracker)}`
880
+ )
881
+ return `{ ${props.join(', ')} }`
882
+ }
883
+ return JSON.stringify(value)
884
+ }
885
+
848
886
  /**
849
887
  * Convert input object to graph input code using ref()
850
888
  * @param input - The input mapping
@@ -860,87 +898,17 @@ function inputToGraphCode(
860
898
  const entries = Object.entries(input)
861
899
  if (entries.length === 0) return { hasRefs: false, code: '{}' }
862
900
 
863
- let hasRefs = false
901
+ const refTracker = { hasRefs: false }
864
902
  const lines = entries.map(([key, value]) => {
865
- if (isDataRef(value)) {
866
- hasRefs = true
867
- return ` ${key}: ${dataRefToGraphRef(value, outputVarToNodeId)},`
868
- }
869
- if (isTemplateRef(value)) {
870
- hasRefs = true
871
- return ` ${key}: ${templateRefToGraphCode(value, outputVarToNodeId)},`
872
- }
873
- return ` ${key}: ${JSON.stringify(value)},`
903
+ return ` ${key}: ${valueToGraphCode(value, outputVarToNodeId, refTracker)},`
874
904
  })
875
905
 
876
906
  return {
877
- hasRefs,
907
+ hasRefs: refTracker.hasRefs,
878
908
  code: `{\n${lines.join('\n')}\n }`,
879
909
  }
880
910
  }
881
911
 
882
- /**
883
- * Serialize wires to code
884
- */
885
- function wiresToCode(wires: SerializedWorkflowGraph['wires']): string {
886
- if (!wires || Object.keys(wires).length === 0) return '{}'
887
-
888
- const parts: string[] = []
889
-
890
- if (wires.http && wires.http.length > 0) {
891
- const httpItems = wires.http.map(
892
- (h) =>
893
- `{ route: '${h.route}', method: '${h.method}', startNode: '${h.startNode}' }`
894
- )
895
- parts.push(`http: [${httpItems.join(', ')}]`)
896
- }
897
-
898
- if (wires.channel && wires.channel.length > 0) {
899
- const channelItems = wires.channel.map((c) => {
900
- const channelParts: string[] = [`name: '${c.name}'`]
901
- if (c.onConnect) channelParts.push(`onConnect: '${c.onConnect}'`)
902
- if (c.onDisconnect) channelParts.push(`onDisconnect: '${c.onDisconnect}'`)
903
- if (c.onMessage) channelParts.push(`onMessage: '${c.onMessage}'`)
904
- return `{ ${channelParts.join(', ')} }`
905
- })
906
- parts.push(`channel: [${channelItems.join(', ')}]`)
907
- }
908
-
909
- if (wires.queue && wires.queue.length > 0) {
910
- const queueItems = wires.queue.map(
911
- (q) => `{ name: '${q.name}', startNode: '${q.startNode}' }`
912
- )
913
- parts.push(`queue: [${queueItems.join(', ')}]`)
914
- }
915
-
916
- if (wires.cli && wires.cli.length > 0) {
917
- const cliItems = wires.cli.map(
918
- (c) => `{ command: '${c.command}', startNode: '${c.startNode}' }`
919
- )
920
- parts.push(`cli: [${cliItems.join(', ')}]`)
921
- }
922
-
923
- if (wires.schedule && wires.schedule.length > 0) {
924
- const scheduleItems = wires.schedule.map((s) => {
925
- const scheduleParts: string[] = []
926
- if (s.cron) scheduleParts.push(`cron: '${s.cron}'`)
927
- if (s.interval) scheduleParts.push(`interval: '${s.interval}'`)
928
- scheduleParts.push(`startNode: '${s.startNode}'`)
929
- return `{ ${scheduleParts.join(', ')} }`
930
- })
931
- parts.push(`schedule: [${scheduleItems.join(', ')}]`)
932
- }
933
-
934
- if (wires.trigger && wires.trigger.length > 0) {
935
- const triggerItems = wires.trigger.map(
936
- (t) => `{ name: '${t.name}', startNode: '${t.startNode}' }`
937
- )
938
- parts.push(`trigger: [${triggerItems.join(', ')}]`)
939
- }
940
-
941
- return `{ ${parts.join(', ')} }`
942
- }
943
-
944
912
  /**
945
913
  * Check if a node is a flow node (non-RPC control flow)
946
914
  */
@@ -995,9 +963,7 @@ export function deserializeGraphWorkflow(
995
963
  const lines: string[] = []
996
964
 
997
965
  // Import statement
998
- lines.push(
999
- `import { pikkuWorkflowGraph, wireWorkflow } from '${pikkuImportPath}'`
1000
- )
966
+ lines.push(`import { pikkuWorkflowGraph } from '${pikkuImportPath}'`)
1001
967
  lines.push('')
1002
968
 
1003
969
  // Add description as comment if present
@@ -1082,28 +1048,7 @@ export function deserializeGraphWorkflow(
1082
1048
  }
1083
1049
  }
1084
1050
 
1085
- // Compute entry node (first node with no incoming edges from RPC nodes)
1086
- const rpcNodeIds = new Set(Object.keys(nodeRpcMap))
1087
- const nodesWithIncomingEdges = new Set<string>()
1088
- for (const [nodeId, node] of Object.entries(workflow.nodes)) {
1089
- if (!rpcNodeIds.has(nodeId)) continue
1090
- if ('next' in node && node.next) {
1091
- const nextId = node.next as string
1092
- // Follow through flow nodes to find the actual next RPC node
1093
- const actualNextId = flowNodeIds.has(nextId)
1094
- ? findNextRpcNode(nextId, workflow.nodes, flowNodeIds)
1095
- : nextId
1096
- if (actualNextId && rpcNodeIds.has(actualNextId)) {
1097
- nodesWithIncomingEdges.add(actualNextId)
1098
- }
1099
- }
1100
- }
1101
- // Entry node is the first RPC node with no incoming edges
1102
- const entryNode = Object.keys(nodeRpcMap).find(
1103
- (id) => !nodesWithIncomingEdges.has(id)
1104
- )
1105
-
1106
- // Generate the pikkuWorkflowGraph call
1051
+ // Generate the pikkuWorkflowGraph call (builds graph and registers with core)
1107
1052
  lines.push(`export const ${workflow.name} = pikkuWorkflowGraph({`)
1108
1053
  lines.push(` name: '${workflow.name}',`)
1109
1054
  if (workflow.description) {
@@ -1125,13 +1070,6 @@ export function deserializeGraphWorkflow(
1125
1070
  lines.push(` nodes: {},`)
1126
1071
  }
1127
1072
 
1128
- // Generate wires with api entry point
1129
- if (entryNode) {
1130
- lines.push(` wires: {`)
1131
- lines.push(` api: '${entryNode}',`)
1132
- lines.push(` },`)
1133
- }
1134
-
1135
1073
  // Generate config (node configurations)
1136
1074
  if (nodeConfigs.length > 0) {
1137
1075
  lines.push(` config: {`)
@@ -1142,20 +1080,6 @@ export function deserializeGraphWorkflow(
1142
1080
  lines.push(`})`)
1143
1081
  lines.push('')
1144
1082
 
1145
- // Always generate wireWorkflow to register the graph workflow
1146
- // (needed for testing even without explicit wires)
1147
- if (workflow.wires && Object.keys(workflow.wires).length > 0) {
1148
- lines.push(`wireWorkflow({`)
1149
- lines.push(` wires: ${wiresToCode(workflow.wires)},`)
1150
- lines.push(` graph: ${workflow.name},`)
1151
- lines.push(`})`)
1152
- } else {
1153
- lines.push(`wireWorkflow({`)
1154
- lines.push(` graph: ${workflow.name},`)
1155
- lines.push(`})`)
1156
- }
1157
- lines.push('')
1158
-
1159
1083
  return lines.join('\n')
1160
1084
  }
1161
1085
 
@@ -1426,6 +1426,26 @@ function extractInputSources(
1426
1426
  return Object.keys(inputs).length > 0 ? inputs : undefined
1427
1427
  }
1428
1428
 
1429
+ function inputSourceToInlineValue(source: InputSource): unknown {
1430
+ switch (source.from) {
1431
+ case 'literal':
1432
+ return source.value
1433
+ case 'input':
1434
+ return { $ref: 'trigger', path: source.path }
1435
+ case 'outputVar':
1436
+ return { $ref: source.name, path: source.path }
1437
+ case 'item':
1438
+ return { $ref: '$item', path: source.path }
1439
+ case 'template':
1440
+ return {
1441
+ $template: {
1442
+ parts: source.parts,
1443
+ expressions: source.expressions.map(inputSourceToInlineValue),
1444
+ },
1445
+ }
1446
+ }
1447
+ }
1448
+
1429
1449
  /**
1430
1450
  * Extract a single input source
1431
1451
  */
@@ -1498,8 +1518,8 @@ function extractInputSource(
1498
1518
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
1499
1519
  const propName = prop.name.text
1500
1520
  const propSource = extractInputSource(prop.initializer, context)
1501
- if (propSource && propSource.from === 'literal') {
1502
- obj[propName] = propSource.value
1521
+ if (propSource) {
1522
+ obj[propName] = inputSourceToInlineValue(propSource)
1503
1523
  }
1504
1524
  }
1505
1525
  }
@@ -1511,8 +1531,8 @@ function extractInputSource(
1511
1531
  const arr: unknown[] = []
1512
1532
  for (const elem of node.elements) {
1513
1533
  const elemSource = extractInputSource(elem, context)
1514
- if (elemSource && elemSource.from === 'literal') {
1515
- arr.push(elemSource.value)
1534
+ if (elemSource) {
1535
+ arr.push(inputSourceToInlineValue(elemSource))
1516
1536
  }
1517
1537
  }
1518
1538
  return { from: 'literal', value: arr }
@@ -10,6 +10,17 @@ import type {
10
10
  DataRef,
11
11
  } from './workflow-graph.types.js'
12
12
 
13
+ function makeNodeId(
14
+ step: WorkflowStepMeta,
15
+ index: number,
16
+ prefix: string
17
+ ): string {
18
+ if ('stepName' in step && step.stepName) {
19
+ return step.stepName
20
+ }
21
+ return `${prefix}_${index}`
22
+ }
23
+
13
24
  /**
14
25
  * Check if a node is a terminal flow (no next step should follow)
15
26
  */
@@ -69,16 +80,17 @@ function convertStepToNode(
69
80
  steps: WorkflowStepMeta[],
70
81
  nodeIdPrefix: string = 'step'
71
82
  ): SerializedGraphNode[] {
72
- const nodeId = `${nodeIdPrefix}_${index}`
83
+ const nodeId = makeNodeId(step, index, nodeIdPrefix)
73
84
  const nextNodeId =
74
- index < steps.length - 1 ? `${nodeIdPrefix}_${index + 1}` : undefined
85
+ index < steps.length - 1
86
+ ? makeNodeId(steps[index + 1], index + 1, nodeIdPrefix)
87
+ : undefined
75
88
 
76
89
  switch (step.type) {
77
90
  case 'rpc': {
78
91
  const node: FunctionNode = {
79
92
  nodeId,
80
93
  rpcName: step.rpcName,
81
- stepName: step.stepName,
82
94
  next: nextNodeId,
83
95
  }
84
96
  if (step.inputs) {
@@ -108,7 +120,6 @@ function convertStepToNode(
108
120
  const node: FlowNode = {
109
121
  nodeId,
110
122
  flow: 'sleep',
111
- stepName: step.stepName,
112
123
  duration: step.duration,
113
124
  next: nextNodeId,
114
125
  }
@@ -119,7 +130,6 @@ function convertStepToNode(
119
130
  const node: FlowNode = {
120
131
  nodeId,
121
132
  flow: 'inline',
122
- stepName: step.stepName,
123
133
  description: step.description,
124
134
  next: nextNodeId,
125
135
  }
@@ -274,7 +284,6 @@ function convertStepToNode(
274
284
  const node: FlowNode = {
275
285
  nodeId,
276
286
  flow: 'fanout',
277
- stepName: step.stepName,
278
287
  sourceVar: step.sourceVar,
279
288
  itemVar: step.itemVar,
280
289
  mode: step.mode,
@@ -378,8 +387,7 @@ export function convertDslToGraph(
378
387
  nodesRecord[node.nodeId] = node
379
388
  }
380
389
 
381
- // Find entry nodes (step_0 is always entry for sequential workflows)
382
- const entryNodeIds = nodes.length > 0 ? ['step_0'] : []
390
+ const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : []
383
391
 
384
392
  // Determine source type based on dsl flag:
385
393
  // - dsl === true: pure DSL workflow, can be serialized
@@ -388,12 +396,11 @@ export function convertDslToGraph(
388
396
 
389
397
  return {
390
398
  name: workflowName,
391
- pikkuFuncName: meta.pikkuFuncName,
399
+ pikkuFuncId: meta.pikkuFuncId,
392
400
  source,
393
401
  description: meta.description,
394
402
  tags: meta.tags,
395
403
  context: meta.context,
396
- wires: {}, // DSL workflows don't have explicit wires in meta
397
404
  nodes: nodesRecord,
398
405
  entryNodeIds,
399
406
  }
@@ -0,0 +1,310 @@
1
+ import type { InspectorState } from '../../../types.js'
2
+ import type {
3
+ SerializedWorkflowGraph,
4
+ WorkflowWires,
5
+ } from './workflow-graph.types.js'
6
+ import type { CLICommandMeta } from '@pikku/core/cli'
7
+
8
+ function parseWorkflowFuncId(
9
+ pikkuFuncId: string
10
+ ): { workflowName: string; startNode?: string } | null {
11
+ for (const prefix of ['workflowStart:', 'workflow:']) {
12
+ if (pikkuFuncId.startsWith(prefix)) {
13
+ return { workflowName: pikkuFuncId.slice(prefix.length) }
14
+ }
15
+ }
16
+ if (pikkuFuncId.startsWith('graphStart:')) {
17
+ const rest = pikkuFuncId.slice('graphStart:'.length)
18
+ const colonIdx = rest.indexOf(':')
19
+ if (colonIdx !== -1) {
20
+ return {
21
+ workflowName: rest.slice(0, colonIdx),
22
+ startNode: rest.slice(colonIdx + 1),
23
+ }
24
+ }
25
+ }
26
+ return null
27
+ }
28
+
29
+ function resolveStartNode(
30
+ parsed: { workflowName: string; startNode?: string },
31
+ graph: SerializedWorkflowGraph
32
+ ): string {
33
+ return parsed.startNode ?? graph.entryNodeIds[0]
34
+ }
35
+
36
+ function getOrCreateWires(graph: SerializedWorkflowGraph): WorkflowWires {
37
+ if (!graph.wires) {
38
+ graph.wires = {}
39
+ }
40
+ return graph.wires
41
+ }
42
+
43
+ export function finalizeWorkflowHelperTypes(state: InspectorState): void {
44
+ const { functions, workflows } = state
45
+ const graphMeta = workflows.graphMeta
46
+
47
+ for (const meta of Object.values(functions.meta)) {
48
+ if (meta.functionType !== 'helper') continue
49
+ if (meta.pikkuFuncId.startsWith('workflowStatus:')) continue
50
+
51
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
52
+ if (!parsed) continue
53
+
54
+ const graph = graphMeta[parsed.workflowName]
55
+ if (!graph) continue
56
+
57
+ const startNodeId = resolveStartNode(parsed, graph)
58
+ const startNode = graph.nodes[startNodeId]
59
+ if (!startNode || !('rpcName' in startNode)) continue
60
+
61
+ const rpcMeta = functions.meta[startNode.rpcName as string]
62
+ if (!rpcMeta) continue
63
+
64
+ if (rpcMeta.inputSchemaName) {
65
+ meta.inputSchemaName = rpcMeta.inputSchemaName
66
+ }
67
+ if (rpcMeta.inputs && rpcMeta.inputs.length > 0) {
68
+ meta.inputs = rpcMeta.inputs
69
+ }
70
+ }
71
+ }
72
+
73
+ export function finalizeWorkflowWires(state: InspectorState): void {
74
+ const { workflows } = state
75
+ const graphMeta = workflows.graphMeta
76
+
77
+ scanHTTP(state, graphMeta)
78
+ scanScheduledTasks(state, graphMeta)
79
+ scanTriggers(state, graphMeta)
80
+ scanQueueWorkers(state, graphMeta)
81
+ scanChannels(state, graphMeta)
82
+ scanMCPEndpoints(state, graphMeta)
83
+ scanCLI(state, graphMeta)
84
+ }
85
+
86
+ function scanHTTP(
87
+ state: InspectorState,
88
+ graphMeta: Record<string, SerializedWorkflowGraph>
89
+ ): void {
90
+ for (const [method, routes] of Object.entries(state.http.meta)) {
91
+ for (const [route, meta] of Object.entries(routes)) {
92
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
93
+ if (!parsed) continue
94
+ const graph = graphMeta[parsed.workflowName]
95
+ if (!graph) continue
96
+ const wires = getOrCreateWires(graph)
97
+ if (!wires.http) wires.http = []
98
+ wires.http.push({
99
+ route,
100
+ method,
101
+ startNode: resolveStartNode(parsed, graph),
102
+ })
103
+ }
104
+ }
105
+ }
106
+
107
+ function scanScheduledTasks(
108
+ state: InspectorState,
109
+ graphMeta: Record<string, SerializedWorkflowGraph>
110
+ ): void {
111
+ for (const meta of Object.values(state.scheduledTasks.meta)) {
112
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
113
+ if (!parsed) continue
114
+ const graph = graphMeta[parsed.workflowName]
115
+ if (!graph) continue
116
+ const wires = getOrCreateWires(graph)
117
+ if (!wires.schedule) wires.schedule = []
118
+ wires.schedule.push({
119
+ cron: meta.schedule,
120
+ startNode: resolveStartNode(parsed, graph),
121
+ })
122
+ }
123
+ }
124
+
125
+ function scanTriggers(
126
+ state: InspectorState,
127
+ graphMeta: Record<string, SerializedWorkflowGraph>
128
+ ): void {
129
+ for (const meta of Object.values(state.triggers.meta)) {
130
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
131
+ if (!parsed) continue
132
+ const graph = graphMeta[parsed.workflowName]
133
+ if (!graph) continue
134
+ const wires = getOrCreateWires(graph)
135
+ if (!wires.trigger) wires.trigger = []
136
+ wires.trigger.push({
137
+ name: meta.name,
138
+ startNode: resolveStartNode(parsed, graph),
139
+ })
140
+ }
141
+ }
142
+
143
+ function scanQueueWorkers(
144
+ state: InspectorState,
145
+ graphMeta: Record<string, SerializedWorkflowGraph>
146
+ ): void {
147
+ for (const meta of Object.values(state.queueWorkers.meta)) {
148
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
149
+ if (!parsed) continue
150
+ const graph = graphMeta[parsed.workflowName]
151
+ if (!graph) continue
152
+ const wires = getOrCreateWires(graph)
153
+ if (!wires.queue) wires.queue = []
154
+ wires.queue.push({
155
+ name: meta.name,
156
+ startNode: resolveStartNode(parsed, graph),
157
+ })
158
+ }
159
+ }
160
+
161
+ function scanChannels(
162
+ state: InspectorState,
163
+ graphMeta: Record<string, SerializedWorkflowGraph>
164
+ ): void {
165
+ for (const channelMeta of Object.values(state.channels.meta)) {
166
+ const wire: NonNullable<WorkflowWires['channel']>[number] = {
167
+ name: channelMeta.name,
168
+ route: channelMeta.route,
169
+ }
170
+ let targetWorkflow: SerializedWorkflowGraph | undefined
171
+
172
+ if (channelMeta.connect) {
173
+ const parsed = parseWorkflowFuncId(channelMeta.connect.pikkuFuncId)
174
+ if (parsed) {
175
+ const graph = graphMeta[parsed.workflowName]
176
+ if (graph) {
177
+ targetWorkflow = graph
178
+ wire.onConnect = resolveStartNode(parsed, graph)
179
+ }
180
+ }
181
+ }
182
+
183
+ if (channelMeta.disconnect) {
184
+ const parsed = parseWorkflowFuncId(channelMeta.disconnect.pikkuFuncId)
185
+ if (parsed) {
186
+ const graph = graphMeta[parsed.workflowName]
187
+ if (graph) {
188
+ targetWorkflow = targetWorkflow ?? graph
189
+ wire.onDisconnect = resolveStartNode(parsed, graph)
190
+ }
191
+ }
192
+ }
193
+
194
+ if (channelMeta.message) {
195
+ const parsed = parseWorkflowFuncId(channelMeta.message.pikkuFuncId)
196
+ if (parsed) {
197
+ const graph = graphMeta[parsed.workflowName]
198
+ if (graph) {
199
+ targetWorkflow = targetWorkflow ?? graph
200
+ wire.onMessage = resolveStartNode(parsed, graph)
201
+ }
202
+ }
203
+ }
204
+
205
+ for (const [routingProp, routeMap] of Object.entries(
206
+ channelMeta.messageWirings
207
+ )) {
208
+ for (const [routeValue, messageMeta] of Object.entries(routeMap)) {
209
+ const parsed = parseWorkflowFuncId(messageMeta.pikkuFuncId)
210
+ if (!parsed) continue
211
+ const graph = graphMeta[parsed.workflowName]
212
+ if (!graph) continue
213
+ targetWorkflow = targetWorkflow ?? graph
214
+ if (!wire.onMessageRoute) wire.onMessageRoute = {}
215
+ wire.onMessageRoute[`${routingProp}:${routeValue}`] = resolveStartNode(
216
+ parsed,
217
+ graph
218
+ )
219
+ }
220
+ }
221
+
222
+ if (targetWorkflow) {
223
+ const wires = getOrCreateWires(targetWorkflow)
224
+ if (!wires.channel) wires.channel = []
225
+ wires.channel.push(wire)
226
+ }
227
+ }
228
+ }
229
+
230
+ function scanMCPEndpoints(
231
+ state: InspectorState,
232
+ graphMeta: Record<string, SerializedWorkflowGraph>
233
+ ): void {
234
+ for (const meta of Object.values(state.mcpEndpoints.toolsMeta)) {
235
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
236
+ if (!parsed) continue
237
+ const graph = graphMeta[parsed.workflowName]
238
+ if (!graph) continue
239
+ const wires = getOrCreateWires(graph)
240
+ if (!wires.mcp) wires.mcp = {}
241
+ if (!wires.mcp.tool) wires.mcp.tool = []
242
+ wires.mcp.tool.push({
243
+ name: meta.name,
244
+ startNode: resolveStartNode(parsed, graph),
245
+ })
246
+ }
247
+
248
+ for (const meta of Object.values(state.mcpEndpoints.promptsMeta)) {
249
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
250
+ if (!parsed) continue
251
+ const graph = graphMeta[parsed.workflowName]
252
+ if (!graph) continue
253
+ const wires = getOrCreateWires(graph)
254
+ if (!wires.mcp) wires.mcp = {}
255
+ if (!wires.mcp.prompt) wires.mcp.prompt = []
256
+ wires.mcp.prompt.push({
257
+ name: meta.name,
258
+ startNode: resolveStartNode(parsed, graph),
259
+ })
260
+ }
261
+
262
+ for (const meta of Object.values(state.mcpEndpoints.resourcesMeta)) {
263
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId)
264
+ if (!parsed) continue
265
+ const graph = graphMeta[parsed.workflowName]
266
+ if (!graph) continue
267
+ const wires = getOrCreateWires(graph)
268
+ if (!wires.mcp) wires.mcp = {}
269
+ if (!wires.mcp.resource) wires.mcp.resource = []
270
+ wires.mcp.resource.push({
271
+ uri: meta.uri,
272
+ startNode: resolveStartNode(parsed, graph),
273
+ })
274
+ }
275
+ }
276
+
277
+ function visitCLICommands(
278
+ commands: Record<string, CLICommandMeta>,
279
+ programName: string,
280
+ path: string[],
281
+ graphMeta: Record<string, SerializedWorkflowGraph>
282
+ ): void {
283
+ for (const [name, command] of Object.entries(commands)) {
284
+ const currentPath = [...path, name]
285
+ const parsed = parseWorkflowFuncId(command.pikkuFuncId)
286
+ if (parsed) {
287
+ const graph = graphMeta[parsed.workflowName]
288
+ if (graph) {
289
+ const wires = getOrCreateWires(graph)
290
+ if (!wires.cli) wires.cli = []
291
+ wires.cli.push({
292
+ command: `${programName} ${currentPath.join(' ')}`,
293
+ startNode: resolveStartNode(parsed, graph),
294
+ })
295
+ }
296
+ }
297
+ if (command.subcommands) {
298
+ visitCLICommands(command.subcommands, programName, currentPath, graphMeta)
299
+ }
300
+ }
301
+ }
302
+
303
+ function scanCLI(
304
+ state: InspectorState,
305
+ graphMeta: Record<string, SerializedWorkflowGraph>
306
+ ): void {
307
+ for (const program of Object.values(state.cli.meta.programs)) {
308
+ visitCLICommands(program.commands, program.program, [], graphMeta)
309
+ }
310
+ }