@pikku/inspector 0.12.41 → 0.12.43

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 (108) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/add/add-addon-bans.js +1 -0
  3. package/dist/add/add-ai-agent.d.ts +3 -1
  4. package/dist/add/add-ai-agent.js +82 -0
  5. package/dist/add/add-credential.js +3 -1
  6. package/dist/add/add-functions.js +10 -9
  7. package/dist/add/add-gateway.js +3 -0
  8. package/dist/add/add-http-route.js +2 -2
  9. package/dist/add/add-mcp-prompt.js +0 -1
  10. package/dist/add/add-mcp-resource.js +0 -1
  11. package/dist/add/add-permission.d.ts +1 -1
  12. package/dist/add/add-permission.js +3 -177
  13. package/dist/add/add-queue-worker.js +3 -0
  14. package/dist/add/add-schedule.js +3 -0
  15. package/dist/add/add-scope.d.ts +2 -0
  16. package/dist/add/add-scope.js +146 -0
  17. package/dist/add/add-trigger.js +3 -0
  18. package/dist/add/add-wire-remote-addon.d.ts +10 -0
  19. package/dist/add/add-wire-remote-addon.js +66 -0
  20. package/dist/add/add-workflow-graph.js +32 -3
  21. package/dist/error-codes.d.ts +3 -0
  22. package/dist/error-codes.js +4 -0
  23. package/dist/inspector.js +19 -7
  24. package/dist/types.d.ts +19 -13
  25. package/dist/utils/custom-types-generator.js +2 -1
  26. package/dist/utils/ensure-function-metadata.d.ts +15 -0
  27. package/dist/utils/ensure-function-metadata.js +24 -0
  28. package/dist/utils/filter-inspector-state.js +9 -0
  29. package/dist/utils/get-property-value.d.ts +7 -2
  30. package/dist/utils/get-property-value.js +48 -1
  31. package/dist/utils/load-addon-functions-meta.js +81 -6
  32. package/dist/utils/nested-project-filter.d.ts +1 -0
  33. package/dist/utils/nested-project-filter.js +29 -0
  34. package/dist/utils/permissions.d.ts +1 -21
  35. package/dist/utils/permissions.js +17 -108
  36. package/dist/utils/post-process.d.ts +30 -1
  37. package/dist/utils/post-process.js +150 -72
  38. package/dist/utils/serialize-inspector-state.d.ts +13 -8
  39. package/dist/utils/serialize-inspector-state.js +12 -6
  40. package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
  41. package/dist/utils/serialize-permissions-groups-meta.js +0 -26
  42. package/dist/utils/workflow/derive-workflow-plan.js +9 -3
  43. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +56 -1
  44. package/dist/utils/workflow/dsl/patterns.d.ts +10 -0
  45. package/dist/utils/workflow/dsl/patterns.js +24 -8
  46. package/dist/utils/workflow/dsl/validation.js +3 -4
  47. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +14 -0
  48. package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
  49. package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
  50. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
  51. package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
  52. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +6 -2
  53. package/dist/visit.js +4 -0
  54. package/package.json +3 -3
  55. package/src/add/add-addon-bans.ts +1 -0
  56. package/src/add/add-ai-agent.test.ts +87 -0
  57. package/src/add/add-ai-agent.ts +122 -0
  58. package/src/add/add-credential.ts +6 -0
  59. package/src/add/add-functions.ts +10 -12
  60. package/src/add/add-gateway.ts +11 -0
  61. package/src/add/add-http-route.ts +2 -8
  62. package/src/add/add-mcp-prompt.ts +0 -1
  63. package/src/add/add-mcp-resource.ts +0 -1
  64. package/src/add/add-permission.ts +4 -242
  65. package/src/add/add-queue-worker.ts +11 -0
  66. package/src/add/add-schedule.ts +11 -0
  67. package/src/add/add-scope.test.ts +346 -0
  68. package/src/add/add-scope.ts +225 -0
  69. package/src/add/add-trigger.ts +11 -0
  70. package/src/add/add-wire-remote-addon.ts +77 -0
  71. package/src/add/add-workflow-graph-input.test.ts +94 -0
  72. package/src/add/add-workflow-graph-notes.test.ts +74 -0
  73. package/src/add/add-workflow-graph.ts +35 -3
  74. package/src/add/inline-wiring-function.test.ts +104 -0
  75. package/src/add/validate-workflow-graph-addons.test.ts +102 -0
  76. package/src/error-codes.ts +5 -0
  77. package/src/inspector.ts +22 -6
  78. package/src/types.ts +16 -17
  79. package/src/utils/custom-types-generator.test.ts +26 -1
  80. package/src/utils/custom-types-generator.ts +2 -1
  81. package/src/utils/ensure-function-metadata.ts +42 -0
  82. package/src/utils/filter-inspector-state.test.ts +0 -2
  83. package/src/utils/filter-inspector-state.ts +9 -0
  84. package/src/utils/get-property-value.test.ts +141 -0
  85. package/src/utils/get-property-value.ts +62 -1
  86. package/src/utils/load-addon-functions-meta.test.ts +157 -0
  87. package/src/utils/load-addon-functions-meta.ts +94 -6
  88. package/src/utils/load-addon-scopes.test.ts +138 -0
  89. package/src/utils/nested-project-filter.test.ts +53 -0
  90. package/src/utils/nested-project-filter.ts +28 -0
  91. package/src/utils/permissions.test.ts +7 -232
  92. package/src/utils/permissions.ts +20 -160
  93. package/src/utils/post-process.test.ts +269 -4
  94. package/src/utils/post-process.ts +216 -95
  95. package/src/utils/serialize-inspector-state.ts +22 -25
  96. package/src/utils/serialize-permissions-groups-meta.ts +0 -28
  97. package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
  98. package/src/utils/workflow/derive-workflow-plan.ts +11 -4
  99. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +66 -0
  100. package/src/utils/workflow/dsl/patterns.ts +34 -8
  101. package/src/utils/workflow/dsl/validation.ts +3 -4
  102. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +15 -0
  103. package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
  104. package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
  105. package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
  106. package/src/utils/workflow/graph/workflow-graph.types.ts +8 -0
  107. package/src/visit.ts +4 -0
  108. package/tsconfig.tsbuildinfo +1 -1
@@ -2,6 +2,15 @@ import * as ts from 'typescript';
2
2
  /**
3
3
  * Pattern detection helpers for DSL workflow extraction
4
4
  */
5
+ /**
6
+ * The wire object handed to a DSL func is named `workflow` in a workflow and
7
+ * `scenario` in a scenario — both carry the same `do`/`sleep`/`suspend` DSL, so
8
+ * step extraction accepts either identifier.
9
+ */
10
+ export function isWorkflowWireIdentifier(expr) {
11
+ return (ts.isIdentifier(expr) &&
12
+ (expr.text === 'workflow' || expr.text === 'scenario'));
13
+ }
5
14
  /**
6
15
  * Check if a call expression is workflow.do() or workflow.expectEventually()
7
16
  * (both are RPC steps; expectEventually is the polling variant used by scenarios)
@@ -13,8 +22,7 @@ export function isWorkflowDoCall(node, checker) {
13
22
  const propAccess = node.expression;
14
23
  return ((propAccess.name.text === 'do' ||
15
24
  propAccess.name.text === 'expectEventually') &&
16
- ts.isIdentifier(propAccess.expression) &&
17
- propAccess.expression.text === 'workflow');
25
+ isWorkflowWireIdentifier(propAccess.expression));
18
26
  }
19
27
  /**
20
28
  * Check if a call expression is workflow.expectEventually()
@@ -22,8 +30,7 @@ export function isWorkflowDoCall(node, checker) {
22
30
  export function isWorkflowExpectEventuallyCall(node) {
23
31
  return (ts.isPropertyAccessExpression(node.expression) &&
24
32
  node.expression.name.text === 'expectEventually' &&
25
- ts.isIdentifier(node.expression.expression) &&
26
- node.expression.expression.text === 'workflow');
33
+ isWorkflowWireIdentifier(node.expression.expression));
27
34
  }
28
35
  /**
29
36
  * Extract the actor NAME from a step options object literal:
@@ -58,8 +65,7 @@ export function isWorkflowSleepCall(node, checker) {
58
65
  }
59
66
  const propAccess = node.expression;
60
67
  return (propAccess.name.text === 'sleep' &&
61
- ts.isIdentifier(propAccess.expression) &&
62
- propAccess.expression.text === 'workflow');
68
+ isWorkflowWireIdentifier(propAccess.expression));
63
69
  }
64
70
  /**
65
71
  * Check if a call expression is workflow.suspend()
@@ -70,8 +76,18 @@ export function isWorkflowSuspendCall(node, _checker) {
70
76
  }
71
77
  const propAccess = node.expression;
72
78
  return (propAccess.name.text === 'suspend' &&
73
- ts.isIdentifier(propAccess.expression) &&
74
- propAccess.expression.text === 'workflow');
79
+ isWorkflowWireIdentifier(propAccess.expression));
80
+ }
81
+ /**
82
+ * Check if a call expression is workflow.approval()
83
+ */
84
+ export function isWorkflowApprovalCall(node, _checker) {
85
+ if (!ts.isPropertyAccessExpression(node.expression)) {
86
+ return false;
87
+ }
88
+ const propAccess = node.expression;
89
+ return (propAccess.name.text === 'approval' &&
90
+ isWorkflowWireIdentifier(propAccess.expression));
75
91
  }
76
92
  /**
77
93
  * Check if a throw statement throws WorkflowCancelledException
@@ -1,4 +1,5 @@
1
1
  import * as ts from 'typescript';
2
+ import { isWorkflowWireIdentifier } from './patterns.js';
2
3
  /**
3
4
  * Check if a node contains only allowed patterns
4
5
  *
@@ -58,8 +59,7 @@ export function validateNoDisallowedPatterns(node, options) {
58
59
  if (ts.isPropertyAccessExpression(node.expression)) {
59
60
  const propAccess = node.expression;
60
61
  if (propAccess.name.text === 'do' &&
61
- ts.isIdentifier(propAccess.expression) &&
62
- propAccess.expression.text === 'workflow') {
62
+ isWorkflowWireIdentifier(propAccess.expression)) {
63
63
  const secondArg = node.arguments[1];
64
64
  if (secondArg &&
65
65
  (ts.isArrowFunction(secondArg) ||
@@ -116,8 +116,7 @@ export function validateAwaitedCalls(node) {
116
116
  if (ts.isPropertyAccessExpression(node.expression)) {
117
117
  const propAccess = node.expression;
118
118
  if ((propAccess.name.text === 'do' || propAccess.name.text === 'sleep') &&
119
- ts.isIdentifier(propAccess.expression) &&
120
- propAccess.expression.text === 'workflow') {
119
+ isWorkflowWireIdentifier(propAccess.expression)) {
121
120
  if (!parentIsAwait && !insidePromiseAll) {
122
121
  errors.push({
123
122
  message: `workflow.${propAccess.name.text}() must be awaited`,
@@ -96,6 +96,20 @@ function convertStepToNode(step, index, steps, nodeIdPrefix = 'step') {
96
96
  };
97
97
  return [node];
98
98
  }
99
+ case 'approval': {
100
+ // Unlike `suspend` — which has no output and so is deliberately absent
101
+ // from the graph — an approval yields a value a later step can reference,
102
+ // so it must be a real node for $ref resolution and graphHash to see it.
103
+ const node = {
104
+ nodeId,
105
+ flow: 'approval',
106
+ reason: step.reason,
107
+ outputVar: step.outputVar,
108
+ expiry: step.expiry,
109
+ next: nextNodeId,
110
+ };
111
+ return [node];
112
+ }
99
113
  case 'inline': {
100
114
  const node = {
101
115
  nodeId,
@@ -1,2 +1,9 @@
1
+ import type { SerializedWorkflowGraph } from './workflow-graph.types.js';
1
2
  import type { InspectorState } from '../../../types.js';
2
3
  export declare function finalizeWorkflows(state: InspectorState): void;
4
+ /**
5
+ * Hash of graph topology only. `notes` (node- and graph-level) is deliberately
6
+ * excluded so editing documentation never changes the hash / marks the workflow
7
+ * as a new version.
8
+ */
9
+ export declare function computeGraphHash(graph: SerializedWorkflowGraph): string;
@@ -80,11 +80,20 @@ function computeStepHashes(graph, functionsMeta) {
80
80
  node.stepHash = hashString(`${node.nodeId}:${meta?.contractHash ?? ''}`, 12);
81
81
  }
82
82
  }
83
- function computeGraphHash(graph) {
83
+ /**
84
+ * Hash of graph topology only. `notes` (node- and graph-level) is deliberately
85
+ * excluded so editing documentation never changes the hash / marks the workflow
86
+ * as a new version.
87
+ */
88
+ export function computeGraphHash(graph) {
89
+ const nodesForHash = Object.fromEntries(Object.entries(graph.nodes).map(([nodeId, node]) => {
90
+ const { notes: _notes, ...rest } = node;
91
+ return [nodeId, rest];
92
+ }));
84
93
  return hashString(canonicalJSON({
85
94
  source: graph.source,
86
95
  context: graph.context,
87
- nodes: graph.nodes,
96
+ nodes: nodesForHash,
88
97
  entryNodeIds: graph.entryNodeIds,
89
98
  }), 12);
90
99
  }
@@ -14,10 +14,12 @@ export declare function serializeWorkflowGraph(definition: {
14
14
  input?: (ref: any) => Record<string, unknown>;
15
15
  next?: string | string[] | Record<string, string | string[]>;
16
16
  onError?: string | string[];
17
+ notes?: string;
17
18
  }>;
18
19
  }, options?: {
19
20
  description?: string;
20
21
  tags?: string[];
22
+ notes?: string[];
21
23
  }): SerializedWorkflowGraph;
22
24
  /**
23
25
  * Deserialize a workflow graph from JSON to runtime format
@@ -108,6 +108,9 @@ export function serializeWorkflowGraph(definition, options) {
108
108
  next: serializeNext(node.next),
109
109
  onError: node.onError,
110
110
  };
111
+ if (node.notes !== undefined) {
112
+ funcNode.notes = node.notes;
113
+ }
111
114
  nodes[nodeId] = funcNode;
112
115
  // Entry nodes have no incoming edges
113
116
  if (!hasIncomingEdge.has(nodeId)) {
@@ -120,6 +123,7 @@ export function serializeWorkflowGraph(definition, options) {
120
123
  source: 'graph',
121
124
  description: options?.description,
122
125
  tags: options?.tags,
126
+ notes: options?.notes,
123
127
  nodes,
124
128
  entryNodeIds,
125
129
  };
@@ -51,7 +51,7 @@ export type SerializedNext = string | string[] | {
51
51
  conditions: BranchCondition[];
52
52
  /** Default target if no conditions match */
53
53
  default?: string | string[];
54
- };
54
+ } | Record<string, string | string[]>;
55
55
  /**
56
56
  * Node execution options
57
57
  */
@@ -66,7 +66,7 @@ export interface NodeOptions {
66
66
  /**
67
67
  * Flow node types for control flow (no RPC call)
68
68
  */
69
- export type FlowType = 'sleep' | 'branch' | 'parallel' | 'fanout' | 'inline' | 'switch' | 'filter' | 'arrayPredicate' | 'return' | 'cancel' | 'set';
69
+ export type FlowType = 'sleep' | 'approval' | 'branch' | 'parallel' | 'fanout' | 'inline' | 'switch' | 'filter' | 'arrayPredicate' | 'return' | 'cancel' | 'set';
70
70
  import type { ContextVariable, WorkflowContext, WorkflowPlannedStep } from '@pikku/core/workflow';
71
71
  export type { ContextVariable, WorkflowContext };
72
72
  /**
@@ -83,6 +83,8 @@ interface BaseNode {
83
83
  onError?: string | string[];
84
84
  /** Execution options */
85
85
  options?: NodeOptions;
86
+ /** Free-text node documentation. Non-semantic — excluded from graphHash. */
87
+ notes?: string;
86
88
  }
87
89
  /**
88
90
  * Function node - calls an RPC
@@ -154,6 +156,8 @@ export interface SerializedWorkflowGraph {
154
156
  context?: WorkflowContext;
155
157
  /** Serialized nodes */
156
158
  nodes: Record<string, SerializedGraphNode>;
159
+ /** Graph-level free-text notes (e.g. imported sticky notes). Non-semantic — excluded from graphHash. */
160
+ notes?: string[];
157
161
  /** Entry node(s) - first nodes to execute */
158
162
  entryNodeIds: string[];
159
163
  /** Hash of graph topology (nodes, edges, input mappings) */
package/dist/visit.js CHANGED
@@ -15,12 +15,14 @@ import { addChannel } from './add/add-channel.js';
15
15
  import { addGateway } from './add/add-gateway.js';
16
16
  import { addRPCInvocations } from './add/add-rpc-invocations.js';
17
17
  import { addWireAddon } from './add/add-wire-addon.js';
18
+ import { addWireRemoteAddon } from './add/add-wire-remote-addon.js';
18
19
  import { addMiddleware } from './add/add-middleware.js';
19
20
  import { addPermission } from './add/add-permission.js';
20
21
  import { addCLI, addCLIRenderers } from './add/add-cli.js';
21
22
  import { addAuth } from './add/add-auth.js';
22
23
  import { addSecret } from './add/add-secret.js';
23
24
  import { addCredential } from './add/add-credential.js';
25
+ import { addScope } from './add/add-scope.js';
24
26
  import { addVariable } from './add/add-variable.js';
25
27
  import { addWorkflowGraph } from './add/add-workflow-graph.js';
26
28
  import { addAIAgent } from './add/add-ai-agent.js';
@@ -36,6 +38,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
36
38
  addFileWithFactory(node, checker, state.serverLifecycleFactories, 'ServerLifecycle');
37
39
  addRPCInvocations(node, state, logger);
38
40
  addWireAddon(node, state, logger);
41
+ addWireRemoteAddon(node, state, logger);
39
42
  addMiddleware(logger, node, checker, state, options);
40
43
  addPermission(logger, node, checker, state, options);
41
44
  addApprovalDescription(logger, node, checker, state, options);
@@ -64,6 +67,7 @@ export const visitRoutes = (logger, checker, node, state, options) => {
64
67
  addAuth(logger, node, checker, state, nextOptions);
65
68
  addSecret(logger, node, checker, state, nextOptions);
66
69
  addCredential(logger, node, checker, state, nextOptions);
70
+ addScope(logger, node, checker, state, nextOptions);
67
71
  addVariable(logger, node, checker, state, nextOptions);
68
72
  addHTTPRoute(logger, node, checker, state, nextOptions);
69
73
  addHTTPRoutes(logger, node, checker, state, nextOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.41",
3
+ "version": "0.12.43",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -44,10 +44,10 @@
44
44
  "zod-to-ts": "^2.1.0"
45
45
  },
46
46
  "peerDependencies": {
47
- "@pikku/core": "^0.12.61"
47
+ "@pikku/core": "^0.12.64"
48
48
  },
49
49
  "devDependencies": {
50
- "@pikku/core": "^0.12.61",
50
+ "@pikku/core": "^0.12.64",
51
51
  "@types/node": "^24.11.0"
52
52
  },
53
53
  "engines": {
@@ -9,6 +9,7 @@ import { ErrorCode } from '../error-codes.js'
9
9
  */
10
10
  const BANNED_WIRINGS = new Set([
11
11
  'wireAddon',
12
+ 'wireRemoteAddon',
12
13
  'wireChannel',
13
14
  'wireCLI',
14
15
  'wireGateway',
@@ -0,0 +1,87 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import * as ts from 'typescript'
4
+
5
+ import { resolveWorkflowReferences } from './add-ai-agent.js'
6
+ import type { InspectorLogger } from '../types.js'
7
+
8
+ function createSourceFile(code: string): ts.SourceFile {
9
+ return ts.createSourceFile('test.ts', code, ts.ScriptTarget.ESNext, true)
10
+ }
11
+
12
+ function getObjectLiteral(
13
+ sourceFile: ts.SourceFile
14
+ ): ts.ObjectLiteralExpression {
15
+ let result: ts.ObjectLiteralExpression | undefined
16
+ ts.forEachChild(sourceFile, function visit(node) {
17
+ if (ts.isObjectLiteralExpression(node) && !result) {
18
+ result = node
19
+ }
20
+ ts.forEachChild(node, visit)
21
+ })
22
+ if (!result) throw new Error('No object literal found')
23
+ return result
24
+ }
25
+
26
+ function createLogger(): {
27
+ logger: InspectorLogger
28
+ criticals: Array<{ code: unknown; message: string }>
29
+ } {
30
+ const criticals: Array<{ code: unknown; message: string }> = []
31
+ const logger = {
32
+ info: () => {},
33
+ error: () => {},
34
+ warn: () => {},
35
+ debug: () => {},
36
+ diagnostic: () => {},
37
+ critical: (code: unknown, message: string) =>
38
+ criticals.push({ code, message }),
39
+ hasCriticalErrors: () => criticals.length > 0,
40
+ } as unknown as InspectorLogger
41
+ return { logger, criticals }
42
+ }
43
+
44
+ describe('resolveWorkflowReferences', () => {
45
+ test('returns null when no workflows property exists', () => {
46
+ const obj = getObjectLiteral(createSourceFile('const x = { name: "a" }'))
47
+ const { logger } = createLogger()
48
+ assert.strictEqual(
49
+ resolveWorkflowReferences(obj, {} as any, 'a', logger),
50
+ null
51
+ )
52
+ })
53
+
54
+ test('returns null for an empty workflows array', () => {
55
+ const obj = getObjectLiteral(
56
+ createSourceFile('const x = { workflows: [] }')
57
+ )
58
+ const { logger } = createLogger()
59
+ assert.strictEqual(
60
+ resolveWorkflowReferences(obj, {} as any, 'a', logger),
61
+ null
62
+ )
63
+ })
64
+
65
+ test('resolves ref() and workflow() string-literal references', () => {
66
+ const obj = getObjectLiteral(
67
+ createSourceFile(
68
+ `const x = { workflows: [ref('buildReport'), workflow('country_capitals')] }`
69
+ )
70
+ )
71
+ const { logger, criticals } = createLogger()
72
+ const result = resolveWorkflowReferences(obj, {} as any, 'planner', logger)
73
+ assert.deepStrictEqual(result, ['buildReport', 'country_capitals'])
74
+ assert.strictEqual(criticals.length, 0)
75
+ })
76
+
77
+ test('flags a raw string literal as a critical error', () => {
78
+ const obj = getObjectLiteral(
79
+ createSourceFile(`const x = { workflows: ['buildReport'] }`)
80
+ )
81
+ const { logger, criticals } = createLogger()
82
+ const result = resolveWorkflowReferences(obj, {} as any, 'planner', logger)
83
+ assert.strictEqual(result, null)
84
+ assert.strictEqual(criticals.length, 1)
85
+ assert.match(criticals[0].message, /string literal/)
86
+ })
87
+ })
@@ -142,6 +142,114 @@ function resolveAgentReferences(
142
142
  return resolved.length > 0 ? resolved : null
143
143
  }
144
144
 
145
+ export function resolveWorkflowReferences(
146
+ obj: ts.ObjectLiteralExpression,
147
+ checker: ts.TypeChecker,
148
+ agentName: string,
149
+ logger: InspectorLogger
150
+ ): string[] | null {
151
+ const property = obj.properties.find(
152
+ (p) =>
153
+ ts.isPropertyAssignment(p) &&
154
+ ts.isIdentifier(p.name) &&
155
+ p.name.text === 'workflows'
156
+ )
157
+
158
+ if (!property || !ts.isPropertyAssignment(property)) {
159
+ return null
160
+ }
161
+
162
+ const initializer = property.initializer
163
+ if (!ts.isArrayLiteralExpression(initializer)) {
164
+ return null
165
+ }
166
+
167
+ const resolved: string[] = []
168
+
169
+ for (const element of initializer.elements) {
170
+ if (ts.isStringLiteral(element)) {
171
+ logger.critical(
172
+ ErrorCode.INVALID_VALUE,
173
+ `AI agent '${agentName}' workflows array contains a string literal '${element.text}'. ` +
174
+ `Use a workflow reference instead (e.g., ref('<workflowName>') or import the pikkuWorkflowGraph variable).`
175
+ )
176
+ continue
177
+ }
178
+
179
+ if (ts.isCallExpression(element) && ts.isIdentifier(element.expression)) {
180
+ const calleeName = element.expression.text
181
+ if (calleeName === 'ref' || calleeName === 'workflow') {
182
+ const [firstArg] = element.arguments
183
+ if (firstArg && ts.isStringLiteral(firstArg)) {
184
+ resolved.push(firstArg.text)
185
+ continue
186
+ }
187
+ }
188
+ }
189
+
190
+ if (ts.isIdentifier(element)) {
191
+ const name = resolveIdentifierToWorkflowName(element, checker)
192
+ if (name) {
193
+ resolved.push(name)
194
+ continue
195
+ }
196
+
197
+ logger.critical(
198
+ ErrorCode.INVALID_VALUE,
199
+ `AI agent '${agentName}' workflows array contains identifier '${element.text}' ` +
200
+ `that could not be resolved to a pikkuWorkflowGraph.`
201
+ )
202
+ }
203
+ }
204
+
205
+ return resolved.length > 0 ? resolved : null
206
+ }
207
+
208
+ function resolveIdentifierToWorkflowName(
209
+ identifier: ts.Identifier,
210
+ checker: ts.TypeChecker
211
+ ): string | null {
212
+ const symbol = checker.getSymbolAtLocation(identifier)
213
+ if (!symbol) return null
214
+
215
+ let resolved = symbol
216
+ if (resolved.flags & ts.SymbolFlags.Alias) {
217
+ resolved = checker.getAliasedSymbol(resolved) ?? resolved
218
+ }
219
+
220
+ const decl = resolved.valueDeclaration ?? resolved.declarations?.[0]
221
+ if (!decl) return null
222
+
223
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
224
+ if (
225
+ ts.isCallExpression(decl.initializer) &&
226
+ ts.isIdentifier(decl.initializer.expression) &&
227
+ (decl.initializer.expression.text === 'pikkuWorkflowGraph' ||
228
+ decl.initializer.expression.text === 'pikkuWorkflow' ||
229
+ decl.initializer.expression.text === 'pikkuWorkflowComplex')
230
+ ) {
231
+ const firstArg = decl.initializer.arguments[0]
232
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
233
+ for (const prop of firstArg.properties) {
234
+ if (
235
+ ts.isPropertyAssignment(prop) &&
236
+ ts.isIdentifier(prop.name) &&
237
+ prop.name.text === 'name' &&
238
+ ts.isStringLiteral(prop.initializer)
239
+ ) {
240
+ return prop.initializer.text
241
+ }
242
+ }
243
+ }
244
+ if (ts.isIdentifier(decl.name)) {
245
+ return decl.name.text
246
+ }
247
+ }
248
+ }
249
+
250
+ return null
251
+ }
252
+
145
253
  function resolveIdentifierToRpcName(
146
254
  identifier: ts.Identifier,
147
255
  checker: ts.TypeChecker
@@ -293,6 +401,19 @@ export const addAIAgent: AddWiring = (
293
401
  logger
294
402
  )
295
403
 
404
+ const workflowsValue = resolveWorkflowReferences(
405
+ obj,
406
+ checker,
407
+ nameValue || '',
408
+ logger
409
+ )
410
+
411
+ if (workflowsValue) {
412
+ for (const workflowName of workflowsValue) {
413
+ state.workflows.invokedWorkflows.add(workflowName)
414
+ }
415
+ }
416
+
296
417
  if (!nameValue) {
297
418
  logger.critical(
298
419
  ErrorCode.MISSING_NAME,
@@ -460,6 +581,7 @@ export const addAIAgent: AddWiring = (
460
581
  }),
461
582
  ...(toolsValue !== null && { tools: toolsValue }),
462
583
  ...(agentsValue !== null && { agents: agentsValue }),
584
+ ...(workflowsValue !== null && { workflows: workflowsValue }),
463
585
  tags,
464
586
  inputSchema,
465
587
  outputSchema,
@@ -2,6 +2,7 @@ import * as ts from 'typescript'
2
2
  import {
3
3
  getPropertyValue,
4
4
  getArrayPropertyValue,
5
+ getRecordPropertyValue,
5
6
  assertStringLiteralProperty,
6
7
  } from '../utils/get-property-value.js'
7
8
  import type { AddWiring } from '../types.js'
@@ -133,6 +134,10 @@ export const addCredential: AddWiring = (
133
134
  const tokenUrl = getPropertyValue(oauth2Obj, 'tokenUrl') as string | null
134
135
  const scopes = getArrayPropertyValue(oauth2Obj, 'scopes')
135
136
  const pkce = getPropertyValue(oauth2Obj, 'pkce') as boolean | null
137
+ const additionalParams = getRecordPropertyValue(
138
+ oauth2Obj,
139
+ 'additionalParams'
140
+ )
136
141
 
137
142
  if (appCredentialSecretId && authorizationUrl && tokenUrl && scopes) {
138
143
  oauth2 = {
@@ -142,6 +147,7 @@ export const addCredential: AddWiring = (
142
147
  tokenUrl,
143
148
  scopes,
144
149
  pkce: pkce || undefined,
150
+ additionalParams: additionalParams || undefined,
145
151
  }
146
152
  }
147
153
  }
@@ -12,6 +12,7 @@ import type { FunctionServicesMeta } from '@pikku/core'
12
12
  import { formatVersionedId, parseVersionedId } from '@pikku/core'
13
13
  import {
14
14
  getPropertyValue,
15
+ getArrayPropertyValue,
15
16
  getCommonWireMetaData,
16
17
  } from '../utils/get-property-value.js'
17
18
  import { canonicalJSON, hashString } from '../utils/hash.js'
@@ -426,6 +427,7 @@ export const addFunctions: AddWiring = (
426
427
  let workflowQueued: boolean | undefined
427
428
  let workflowRetries: number | undefined
428
429
  let workflowTimeout: string | undefined
430
+ let scopes: string[] | undefined
429
431
  let version: number | undefined
430
432
  let objectNode: ts.ObjectLiteralExpression | undefined
431
433
  let nodeDisplayName: string | null = null
@@ -530,6 +532,7 @@ export const addFunctions: AddWiring = (
530
532
  workflowTimeout = getPropertyValue(firstArg, 'workflowTimeout') as
531
533
  | string
532
534
  | undefined
535
+ scopes = getArrayPropertyValue(firstArg, 'scopes') ?? undefined
533
536
 
534
537
  // Extract approvalDescription identifier reference
535
538
  for (const prop of firstArg.properties) {
@@ -1029,17 +1032,6 @@ export const addFunctions: AddWiring = (
1029
1032
  if (newMiddleware.length > 0) {
1030
1033
  middleware = [...(middleware || []), ...newMiddleware]
1031
1034
  }
1032
- const existingPermissionTags = new Set(
1033
- (permissions || [])
1034
- .filter((p) => p.type === 'tag')
1035
- .map((p) => (p as any).tag)
1036
- )
1037
- const newPermissions = tagEntries.filter(
1038
- (e) => !existingPermissionTags.has(e.tag)
1039
- )
1040
- if (newPermissions.length > 0) {
1041
- permissions = [...(permissions || []), ...newPermissions]
1042
- }
1043
1035
  }
1044
1036
 
1045
1037
  const implementationHash = computeImplementationHash({
@@ -1094,6 +1086,7 @@ export const addFunctions: AddWiring = (
1094
1086
  workflowQueued: workflowQueued === true ? true : undefined,
1095
1087
  workflowRetries: workflowRetries ?? undefined,
1096
1088
  workflowTimeout: workflowTimeout ?? undefined,
1089
+ scopes: scopes ?? undefined,
1097
1090
  implementationHash,
1098
1091
  version,
1099
1092
  title,
@@ -1147,7 +1140,6 @@ export const addFunctions: AddWiring = (
1147
1140
  inputSchema: inputNames[0] ?? null,
1148
1141
  outputSchema: outputNames[0] ?? null,
1149
1142
  middleware,
1150
- permissions,
1151
1143
  }
1152
1144
  state.serviceAggregation.usedFunctions.add(pikkuFuncId)
1153
1145
  extractWireNames(middleware).forEach((n) =>
@@ -1192,6 +1184,12 @@ export const addFunctions: AddWiring = (
1192
1184
 
1193
1185
  if (remote) {
1194
1186
  state.rpc.invokedFunctions.add(pikkuFuncId)
1187
+ // The consumer-facing surface a wireRemoteAddon imports (mirrors exposedMeta)
1188
+ state.rpc.remoteMeta[name] = pikkuFuncId
1189
+ state.rpc.remoteFiles.set(name, {
1190
+ path: node.getSourceFile().fileName,
1191
+ exportedName,
1192
+ })
1195
1193
  }
1196
1194
 
1197
1195
  if (expose) {
@@ -9,6 +9,7 @@ import {
9
9
  makeContextBasedId,
10
10
  } from '../utils/extract-function-name.js'
11
11
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
12
+ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js'
12
13
  import { resolveMiddleware } from '../utils/middleware.js'
13
14
  import { extractWireNames } from '../utils/post-process.js'
14
15
  import { resolveAddonName } from '../utils/resolve-addon-package.js'
@@ -83,6 +84,16 @@ export const addGateway: AddWiring = (
83
84
  return
84
85
  }
85
86
 
87
+ // Register metadata for a func inlined into the wiring (see helper).
88
+ ensureInlineWiringFunction(
89
+ state,
90
+ pikkuFuncId,
91
+ nameValue,
92
+ funcInitializer,
93
+ checker,
94
+ extracted.isHelper
95
+ )
96
+
86
97
  const middleware = resolveMiddleware(state, obj, tags, checker)
87
98
 
88
99
  state.serviceAggregation.usedFunctions.add(pikkuFuncId)
@@ -19,7 +19,7 @@ import type {
19
19
  InspectorState,
20
20
  } from '../types.js'
21
21
  import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js'
22
- import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js'
22
+ import { resolvePermissions } from '../utils/permissions.js'
23
23
  import { extractWireNames } from '../utils/post-process.js'
24
24
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
25
25
  import { resolveFunctionMeta } from '../utils/resolve-function-meta.js'
@@ -381,13 +381,7 @@ export function registerHTTPRoute({
381
381
  )
382
382
 
383
383
  // Resolve permissions
384
- const permissions = resolveHTTPPermissionsFromObject(
385
- state,
386
- fullRoute,
387
- obj,
388
- tags,
389
- checker
390
- )
384
+ const permissions = resolvePermissions(state, obj, tags, checker)
391
385
 
392
386
  state.serviceAggregation.usedFunctions.add(funcName)
393
387
  if (refAddonTarget) {
@@ -152,7 +152,6 @@ export const addMCPPrompt: AddWiring = (
152
152
  outputSchema,
153
153
  arguments: [], // Will be populated by CLI during serialization
154
154
  middleware,
155
- permissions,
156
155
  }
157
156
  }
158
157
  }
@@ -170,7 +170,6 @@ export const addMCPResource: AddWiring = (
170
170
  inputSchema,
171
171
  outputSchema,
172
172
  middleware,
173
- permissions,
174
173
  }
175
174
  }
176
175
  }