@pikku/core 0.12.14 → 0.12.15

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 (123) hide show
  1. package/CHANGELOG.md +30 -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 +1 -2
  11. package/dist/index.js +1 -2
  12. package/dist/middleware/index.d.ts +2 -0
  13. package/dist/middleware/index.js +2 -0
  14. package/dist/middleware/remote-auth.js +5 -2
  15. package/dist/permissions.d.ts +13 -1
  16. package/dist/permissions.js +51 -0
  17. package/dist/pikku-state.d.ts +0 -1
  18. package/dist/pikku-state.js +1 -4
  19. package/dist/remote.d.ts +10 -0
  20. package/dist/remote.js +32 -0
  21. package/dist/schema.js +2 -0
  22. package/dist/services/deployment-service.d.ts +12 -1
  23. package/dist/services/in-memory-queue-service.d.ts +7 -0
  24. package/dist/services/in-memory-queue-service.js +28 -0
  25. package/dist/services/index.d.ts +4 -1
  26. package/dist/services/index.js +2 -1
  27. package/dist/services/logger-console.d.ts +26 -40
  28. package/dist/services/logger-console.js +102 -46
  29. package/dist/services/logger.d.ts +5 -0
  30. package/dist/services/meta-service.d.ts +175 -0
  31. package/dist/services/meta-service.js +310 -0
  32. package/dist/services/workflow-service.d.ts +6 -1
  33. package/dist/testing/service-tests.js +0 -8
  34. package/dist/types/core.types.d.ts +8 -0
  35. package/dist/types/state.types.d.ts +2 -2
  36. package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
  37. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  38. package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
  39. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
  40. package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
  41. package/dist/wirings/channel/channel-runner.js +3 -3
  42. package/dist/wirings/cli/cli-runner.js +3 -2
  43. package/dist/wirings/cli/command-parser.js +7 -3
  44. package/dist/wirings/http/http-runner.d.ts +1 -1
  45. package/dist/wirings/http/http-runner.js +23 -11
  46. package/dist/wirings/http/http.types.d.ts +2 -0
  47. package/dist/wirings/mcp/mcp-runner.js +5 -3
  48. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  49. package/dist/wirings/queue/queue-runner.js +7 -3
  50. package/dist/wirings/rpc/rpc-runner.js +56 -90
  51. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  52. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  53. package/dist/wirings/trigger/trigger-runner.js +4 -2
  54. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  55. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  56. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  57. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  58. package/dist/wirings/workflow/index.d.ts +4 -2
  59. package/dist/wirings/workflow/index.js +4 -2
  60. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  61. package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
  62. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  63. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  64. package/dist/wirings/workflow/workflow.types.d.ts +17 -1
  65. package/package.json +4 -1
  66. package/src/errors/errors.ts +12 -0
  67. package/src/function/function-runner.ts +5 -4
  68. package/src/function/functions.types.ts +1 -0
  69. package/src/function/index.ts +10 -0
  70. package/src/handle-error.test.ts +2 -2
  71. package/src/handle-error.ts +8 -8
  72. package/src/index.ts +1 -2
  73. package/src/middleware/index.ts +2 -0
  74. package/src/middleware/remote-auth.test.ts +4 -4
  75. package/src/middleware/remote-auth.ts +7 -2
  76. package/src/permissions.ts +70 -0
  77. package/src/pikku-state.test.ts +0 -26
  78. package/src/pikku-state.ts +1 -5
  79. package/src/remote.ts +46 -0
  80. package/src/schema.ts +1 -0
  81. package/src/services/deployment-service.ts +17 -1
  82. package/src/services/in-memory-queue-service.ts +46 -0
  83. package/src/services/index.ts +21 -1
  84. package/src/services/logger-console.ts +127 -47
  85. package/src/services/logger.ts +6 -0
  86. package/src/services/meta-service.ts +523 -0
  87. package/src/services/workflow-service.ts +8 -0
  88. package/src/testing/service-tests.ts +0 -10
  89. package/src/types/core.types.ts +8 -0
  90. package/src/types/state.types.ts +2 -2
  91. package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
  92. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  93. package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
  94. package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
  95. package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
  96. package/src/wirings/channel/channel-runner.ts +4 -5
  97. package/src/wirings/cli/cli-runner.test.ts +8 -7
  98. package/src/wirings/cli/cli-runner.ts +4 -3
  99. package/src/wirings/cli/command-parser.ts +8 -3
  100. package/src/wirings/http/http-runner.ts +27 -11
  101. package/src/wirings/http/http.types.ts +2 -0
  102. package/src/wirings/mcp/mcp-runner.ts +7 -9
  103. package/src/wirings/queue/queue-runner.test.ts +5 -11
  104. package/src/wirings/queue/queue-runner.ts +11 -3
  105. package/src/wirings/rpc/rpc-runner.ts +82 -115
  106. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  107. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  108. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  109. package/src/wirings/trigger/trigger-runner.ts +6 -4
  110. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  111. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  112. package/src/wirings/workflow/index.ts +17 -6
  113. package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
  114. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  115. package/src/wirings/workflow/workflow.types.ts +16 -1
  116. package/tsconfig.tsbuildinfo +1 -1
  117. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  118. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  119. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  120. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  121. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  122. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  123. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -11,6 +11,7 @@ import type {
11
11
  } from './ai-agent.types.js'
12
12
  import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
13
13
  import { PikkuError } from '../../errors/error-handler.js'
14
+ import { checkAuthPermissions } from '../../permissions.js'
14
15
  import { AIProviderNotConfiguredError } from '../../errors/errors.js'
15
16
  import { pikkuState, getSingletonServices } from '../../pikku-state.js'
16
17
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
@@ -22,10 +23,6 @@ import {
22
23
  resolveNamespace,
23
24
  ContextAwareRPCService,
24
25
  } from '../../wirings/rpc/rpc-runner.js'
25
- import {
26
- buildDynamicWorkflowInstructions,
27
- buildWorkflowTools,
28
- } from './agent-dynamic-workflow.js'
29
26
  import {
30
27
  resolveMemoryServices,
31
28
  loadContextMessages,
@@ -148,6 +145,13 @@ export type StreamContext = {
148
145
  export const resolveAgent = (
149
146
  agentName: string
150
147
  ): { agent: CoreAIAgent; packageName: string | null; resolvedName: string } => {
148
+ if (!agentName) {
149
+ console.error(
150
+ '[resolveAgent] agentName is undefined/null! Stack:',
151
+ new Error().stack
152
+ )
153
+ throw new Error('resolveAgent called with undefined agentName')
154
+ }
151
155
  const mainAgent = pikkuState(null, 'agent', 'agents').get(agentName)
152
156
  if (mainAgent) {
153
157
  return { agent: mainAgent, packageName: null, resolvedName: agentName }
@@ -181,10 +185,21 @@ export async function buildInstructions(
181
185
  packageName: string | null
182
186
  ): Promise<string> {
183
187
  const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName]
184
- const rawInstructions = meta?.instructions ?? ''
185
- let instructions = Array.isArray(rawInstructions)
186
- ? rawInstructions.join('\n')
187
- : rawInstructions
188
+ const parts: string[] = []
189
+ if (meta?.role) parts.push(meta.role)
190
+ if (meta?.personality) parts.push(meta.personality)
191
+ if (meta?.goal) parts.push(meta.goal)
192
+ let instructions = parts.join('\n\n')
193
+
194
+ if (meta?.tools?.length) {
195
+ instructions +=
196
+ '\n\nTool usage rules:\n' +
197
+ '- Act immediately with the information given. Do NOT ask clarifying questions unless a required field is truly missing.\n' +
198
+ '- Only use fields defined in your tool schemas. Never mention or ask for fields that do not exist.\n' +
199
+ '- Never fill optional fields with placeholder or zero values. Omit them entirely unless the user provides a real value.\n' +
200
+ '- Never stuff unrelated information into the wrong field.\n' +
201
+ '- Keep responses concise.'
202
+ }
188
203
 
189
204
  if (meta?.agents?.length) {
190
205
  instructions +=
@@ -194,13 +209,6 @@ export async function buildInstructions(
194
209
  'When a request involves multiple actions for the same domain, combine them into a single sub-agent call rather than making separate calls.'
195
210
  }
196
211
 
197
- if (meta?.dynamicWorkflows && meta.tools?.length) {
198
- instructions += buildDynamicWorkflowInstructions(
199
- meta.tools,
200
- meta.dynamicWorkflows
201
- )
202
- }
203
-
204
212
  return instructions
205
213
  }
206
214
 
@@ -279,6 +287,11 @@ export async function buildToolDefs(
279
287
  const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName]
280
288
  if (!meta) return { tools, missingRpcs }
281
289
 
290
+ // Get session for permission filtering
291
+ const session = params.sessionService
292
+ ? await params.sessionService.get()
293
+ : null
294
+
282
295
  const metaTools = meta.tools
283
296
  const metaAgents = meta.agents
284
297
 
@@ -315,6 +328,18 @@ export async function buildToolDefs(
315
328
  continue
316
329
  }
317
330
 
331
+ // Filter out tools the user doesn't have auth for
332
+ if (fnMeta.permissions?.length) {
333
+ if (!session) continue
334
+ const allowed = await checkAuthPermissions(
335
+ fnMeta.permissions,
336
+ session,
337
+ singletonServices,
338
+ resolvedPkg
339
+ )
340
+ if (!allowed) continue
341
+ }
342
+
318
343
  const inputSchemaName = fnMeta?.inputSchemaName
319
344
  let inputSchema = inputSchemaName
320
345
  ? schemas.get(inputSchemaName)
@@ -393,6 +418,17 @@ export async function buildToolDefs(
393
418
  continue
394
419
  }
395
420
 
421
+ // Filter out sub-agents the user doesn't have auth for
422
+ if (subMeta.permissions?.length) {
423
+ if (!session) continue
424
+ const allowed = await checkAuthPermissions(
425
+ subMeta.permissions,
426
+ session,
427
+ singletonServices
428
+ )
429
+ if (!allowed) continue
430
+ }
431
+
396
432
  tools.push({
397
433
  name: subAgentName,
398
434
  description: subMeta.description,
@@ -507,18 +543,6 @@ export async function buildToolDefs(
507
543
  }
508
544
  }
509
545
 
510
- if (meta.dynamicWorkflows) {
511
- const workflowTools = buildWorkflowTools(
512
- agentName,
513
- packageName,
514
- meta.tools ?? [],
515
- meta.dynamicWorkflows,
516
- streamContext,
517
- params.sessionService
518
- )
519
- tools.push(...workflowTools)
520
- }
521
-
522
546
  const hasToolHooks = aiMiddlewares?.some(
523
547
  (mw) => mw.beforeToolCall || mw.afterToolCall
524
548
  )
@@ -594,19 +618,6 @@ export async function prepareAgentRun(
594
618
  throw new AIProviderNotConfiguredError()
595
619
  }
596
620
 
597
- if (agent.dynamicWorkflows && singletonServices.workflowService) {
598
- const persisted =
599
- await singletonServices.workflowService.getAIGeneratedWorkflows(
600
- resolvedName
601
- )
602
- const allMeta = pikkuState(null, 'workflows', 'meta')
603
- for (const wf of persisted) {
604
- if (!allMeta[wf.workflowName]) {
605
- allMeta[wf.workflowName] = wf.graph
606
- }
607
- }
608
- }
609
-
610
621
  const { storage } = resolveMemoryServices(agent, singletonServices)
611
622
  const memoryConfig = agent.memory
612
623
  const threadId = input.threadId
@@ -1,7 +1,6 @@
1
1
  import type { CoreAIAgent, AgentRunState } from './ai-agent.types.js'
2
2
  import { pikkuState } from '../../pikku-state.js'
3
3
  import type { AIRunStateService } from '../../services/ai-run-state-service.js'
4
- import { PikkuMissingMetaError } from '../../errors/errors.js'
5
4
 
6
5
  export const addAIAgent = (
7
6
  agentName: string,
@@ -11,9 +10,10 @@ export const addAIAgent = (
11
10
  const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta')
12
11
  const agentMeta = agentsMeta[agentName]
13
12
  if (!agentMeta) {
14
- throw new PikkuMissingMetaError(
15
- `Missing generated metadata for AI agent '${agentName}'`
13
+ console.warn(
14
+ `[pikku] Skipping AI agent '${agentName}' — metadata not found. Consider moving this wiring to its own file.`
16
15
  )
16
+ return
17
17
  }
18
18
  const agents = pikkuState(packageName, 'agent', 'agents')
19
19
  if (agents.has(agentName)) {
@@ -32,6 +32,19 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js'
32
32
  import { pikkuState, getSingletonServices } from '../../pikku-state.js'
33
33
  import { resolveModelConfig } from './ai-agent-model-config.js'
34
34
  import { AIProviderNotConfiguredError } from '../../errors/errors.js'
35
+
36
+ function stripNulls(obj: unknown): unknown {
37
+ if (obj === null) return undefined
38
+ if (Array.isArray(obj)) return obj.map(stripNulls)
39
+ if (typeof obj !== 'object') return obj
40
+ const result: Record<string, unknown> = {}
41
+ for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
42
+ if (value !== null) {
43
+ result[key] = stripNulls(value)
44
+ }
45
+ }
46
+ return result
47
+ }
35
48
  import { randomUUID } from 'crypto'
36
49
 
37
50
  export async function runAIAgent(
@@ -420,10 +433,12 @@ export async function resumeAIAgentSync(
420
433
  `Tool "${pending.toolName}" not found in agent definition`
421
434
  )
422
435
  }
423
- const toolArgs =
436
+ const rawArgs =
424
437
  typeof pending.args === 'string'
425
438
  ? JSON.parse(pending.args)
426
439
  : pending.args
440
+ // Strip null values recursively — LLMs send null for optional fields but Zod expects undefined
441
+ const toolArgs = stripNulls(rawArgs) ?? {}
427
442
  try {
428
443
  const toolResult = await matchingTool.execute(toolArgs)
429
444
  resultStr =
@@ -1166,6 +1166,14 @@ async function continueAfterToolResult(
1166
1166
  ).tools
1167
1167
 
1168
1168
  const resolved = resolveModelConfig(resolvedName, agent)
1169
+ console.log(
1170
+ '[DEBUG resume] resolvedName:',
1171
+ resolvedName,
1172
+ 'agent.model:',
1173
+ agent.model,
1174
+ 'resolved.model:',
1175
+ resolved.model
1176
+ )
1169
1177
  const maxSteps = resolved.maxSteps ?? 10
1170
1178
 
1171
1179
  const runnerParams: AIAgentRunnerParams = {
@@ -195,7 +195,9 @@ export type CoreAIAgent<
195
195
  description: string
196
196
  summary?: string
197
197
  errors?: string[]
198
- instructions: string | string[]
198
+ role?: string
199
+ personality?: string
200
+ goal: string
199
201
  model: string
200
202
  temperature?: number
201
203
  tools?: unknown[]
@@ -204,7 +206,6 @@ export type CoreAIAgent<
204
206
  memory?: AIAgentMemoryConfig
205
207
  maxSteps?: number
206
208
  toolChoice?: 'auto' | 'required' | 'none'
207
- dynamicWorkflows?: 'read' | 'always' | 'ask'
208
209
  input?: unknown
209
210
  output?: unknown
210
211
  tags?: string[]
@@ -398,6 +399,7 @@ export type AIAgentMeta = Record<
398
399
  channelMiddleware?: MiddlewareMetadata[]
399
400
  aiMiddleware?: MiddlewareMetadata[]
400
401
  permissions?: PermissionMetadata[]
401
- dynamicWorkflows?: 'read' | 'always' | 'ask'
402
+ sourceFile?: string
403
+ exportedName?: string
402
404
  }
403
405
  >
@@ -1,4 +1,4 @@
1
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
1
+ import { NotFoundError } from '../../errors/errors.js'
2
2
  import { addFunction } from '../../function/function-runner.js'
3
3
  import type { CorePikkuPermission } from '../../function/functions.types.js'
4
4
  import { pikkuState, getSingletonServices } from '../../pikku-state.js'
@@ -36,13 +36,12 @@ export const wireChannel = <
36
36
  const channelsMeta = pikkuState(null, 'channel', 'meta')
37
37
  const channelMeta = channelsMeta[channel.name]
38
38
  if (!channelMeta) {
39
- throw new PikkuMissingMetaError(
40
- `Missing generated metadata for channel '${channel.name}'`
39
+ console.warn(
40
+ `[pikku] Skipping channel '${channel.name}' — metadata not found. Consider moving this wiring to its own file.`
41
41
  )
42
+ return
42
43
  }
43
44
 
44
- pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
45
-
46
45
  // Register onConnect function if provided
47
46
  if (channel.onConnect && channelMeta.connect) {
48
47
  addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect as any)
@@ -369,15 +369,16 @@ describe('CLI Runner', () => {
369
369
  assert.strictEqual(programs['my-cli'].middleware[0], middleware)
370
370
  })
371
371
 
372
- test('should throw error when CLI metadata not found', () => {
372
+ test('should skip when CLI metadata not found', () => {
373
373
  pikkuState(null, 'cli', 'meta', { programs: {}, renderers: {} })
374
374
 
375
- assert.throws(() => {
376
- wireCLI({
377
- program: 'nonexistent',
378
- commands: {},
379
- })
380
- }, /CLI metadata not found for program 'nonexistent'/)
375
+ wireCLI({
376
+ program: 'nonexistent',
377
+ commands: {},
378
+ })
379
+
380
+ const programs = pikkuState(null, 'cli', 'programs')
381
+ assert.strictEqual(programs['nonexistent'], undefined)
381
382
  })
382
383
  })
383
384
 
@@ -1,4 +1,4 @@
1
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
1
+ import { NotFoundError } from '../../errors/errors.js'
2
2
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
3
3
  import { pikkuState } from '../../pikku-state.js'
4
4
  import type {
@@ -66,9 +66,10 @@ export const wireCLI = <
66
66
  const cliMeta = pikkuState(null, 'cli', 'meta') || {}
67
67
 
68
68
  if (!cliMeta.programs?.[cli.program]) {
69
- throw new PikkuMissingMetaError(
70
- `CLI metadata not found for program '${cli.program}'`
69
+ console.warn(
70
+ `[pikku] Skipping CLI program '${cli.program}' — metadata not found. Consider moving this wiring to its own file.`
71
71
  )
72
+ return
72
73
  }
73
74
 
74
75
  // Get existing programs state and add this program
@@ -6,6 +6,11 @@ import type {
6
6
  CLIOption,
7
7
  } from './cli.types.js'
8
8
 
9
+ /** Convert kebab-case to camelCase: "from-plan" → "fromPlan" */
10
+ function toCamelCase(str: string): string {
11
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
12
+ }
13
+
9
14
  /**
10
15
  * Result of parsing CLI arguments
11
16
  */
@@ -117,11 +122,11 @@ export function parseCLIArguments(
117
122
  const arg = args[currentIndex]
118
123
 
119
124
  if (arg.startsWith('--')) {
120
- // Long option
125
+ // Long option (--from-plan → fromPlan)
121
126
  const equalIndex = arg.indexOf('=')
122
127
  if (equalIndex > 0) {
123
128
  // --option=value format
124
- const key = arg.slice(2, equalIndex)
129
+ const key = toCamelCase(arg.slice(2, equalIndex))
125
130
  const optionDef = availableOptions[key]
126
131
 
127
132
  // Unknown options are allowed for forward compatibility
@@ -129,7 +134,7 @@ export function parseCLIArguments(
129
134
  optionArgs[key] = parseOptionValue(value, optionDef)
130
135
  } else {
131
136
  // --option value format
132
- const key = arg.slice(2)
137
+ const key = toCamelCase(arg.slice(2))
133
138
  const optionDef = availableOptions[key]
134
139
 
135
140
  // Unknown options are allowed for forward compatibility
@@ -21,7 +21,7 @@ import type {
21
21
  PikkuWire,
22
22
  PikkuWiringTypes,
23
23
  } from '../../types/core.types.js'
24
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
24
+ import { NotFoundError } from '../../errors/errors.js'
25
25
  import {
26
26
  closeWireServices,
27
27
  createWeakUID,
@@ -180,9 +180,14 @@ export const wireHTTP = <
180
180
  const httpMeta = pikkuState(null, 'http', 'meta')
181
181
  const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
182
182
  if (!routeMeta) {
183
- throw new PikkuMissingMetaError(
184
- `Missing generated metadata for HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}'`
183
+ // In deploy units with filtered metadata, wiring files may include
184
+ // routes for functions not in this unit. This happens when multiple
185
+ // wirings share a file — split them into separate files for better
186
+ // tree-shaking.
187
+ console.warn(
188
+ `[pikku] Skipping HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}' — metadata not found. Consider moving this wiring to its own file.`
185
189
  )
190
+ return
186
191
  }
187
192
  if (httpWiring.func) {
188
193
  addFunction(routeMeta.pikkuFuncId, httpWiring.func)
@@ -371,6 +376,7 @@ const executeRoute = async (
371
376
  }
372
377
 
373
378
  const wire: PikkuWire = {
379
+ traceId: requestId,
374
380
  http,
375
381
  channel,
376
382
  session: userSession.get() as CoreUserSession | undefined,
@@ -489,6 +495,7 @@ export const fetchData = async <In, Out>(
489
495
  bubbleErrors = false,
490
496
  exposeErrors = false,
491
497
  generateRequestId,
498
+ traceId: externalTraceId,
492
499
  }: RunHTTPWiringOptions = {}
493
500
  ): Promise<Out | void> => {
494
501
  const singletonServices = getSingletonServices()
@@ -499,11 +506,20 @@ export const fetchData = async <In, Out>(
499
506
  // Combine the request and response into one wire object
500
507
  const pikkuRequest =
501
508
  request instanceof Request ? new PikkuFetchHTTPRequest(request) : request
502
- let requestId: string | null = null
503
- try {
504
- requestId = pikkuRequest.header('x-request-id')
505
- } catch {}
509
+
510
+ // Resolve traceId: external (e.g. CF-Ray) > x-request-id header > generated
511
+ let requestId: string | null = externalTraceId ?? null
512
+ if (!requestId) {
513
+ try {
514
+ requestId = pikkuRequest.header('x-request-id')
515
+ } catch {}
516
+ }
506
517
  requestId = requestId || generateRequestId?.() || createWeakUID()
518
+
519
+ // Scoped logger for HTTP runner internal logging (error handling, route matching)
520
+ // Functions/middleware receive singletonServices.logger directly for compatibility
521
+ const scopedLogger =
522
+ singletonServices.logger.scope?.(requestId) ?? singletonServices.logger
507
523
  const http = createHTTPWire(pikkuRequest, response)
508
524
  const apiType = http!.request!.method()
509
525
  const apiRoute = http!.request!.path()
@@ -526,7 +542,7 @@ export const fetchData = async <In, Out>(
526
542
  response.status(204).json(undefined as any)
527
543
  return
528
544
  }
529
- singletonServices.logger.info({
545
+ scopedLogger.info({
530
546
  message: 'Route not found',
531
547
  apiRoute,
532
548
  apiType,
@@ -551,7 +567,7 @@ export const fetchData = async <In, Out>(
551
567
  } catch (e: any) {
552
568
  if (matchedRoute?.route.sse) {
553
569
  // For SSE routes, send error through the stream since the response is already in stream mode
554
- singletonServices.logger.error(e instanceof Error ? e.message : e)
570
+ scopedLogger.error(e instanceof Error ? e.message : e)
555
571
  try {
556
572
  const errorResponse = getErrorResponse(e)
557
573
  response.arrayBuffer(
@@ -562,7 +578,7 @@ export const fetchData = async <In, Out>(
562
578
  )
563
579
  response.arrayBuffer(JSON.stringify({ type: 'done' }))
564
580
  } catch (streamErr: any) {
565
- singletonServices.logger.error(
581
+ scopedLogger.error(
566
582
  `SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`
567
583
  )
568
584
  }
@@ -572,7 +588,7 @@ export const fetchData = async <In, Out>(
572
588
  e,
573
589
  http,
574
590
  requestId,
575
- singletonServices.logger,
591
+ scopedLogger,
576
592
  logWarningsForStatusCodes,
577
593
  respondWith404,
578
594
  bubbleErrors,
@@ -37,6 +37,8 @@ export type RunHTTPWiringOptions = Partial<{
37
37
  bubbleErrors: boolean
38
38
  exposeErrors: boolean
39
39
  generateRequestId: () => string
40
+ /** Pre-resolved trace ID (e.g. CF-Ray). Falls back to x-request-id header or generated ID. */
41
+ traceId: string
40
42
  }>
41
43
 
42
44
  /**
@@ -20,11 +20,7 @@ import {
20
20
  } from '../../pikku-state.js'
21
21
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
22
22
  import { resolveNamespace } from '../rpc/rpc-runner.js'
23
- import {
24
- BadRequestError,
25
- NotFoundError,
26
- PikkuMissingMetaError,
27
- } from '../../errors/errors.js'
23
+ import { BadRequestError, NotFoundError } from '../../errors/errors.js'
28
24
  import {
29
25
  PikkuSessionService,
30
26
  createMiddlewareSessionWireProps,
@@ -57,9 +53,10 @@ export const wireMCPResource = <
57
53
  const resourcesMeta = pikkuState(null, 'mcp', 'resourcesMeta')
58
54
  const mcpResourceMeta = resourcesMeta[mcpResource.uri]
59
55
  if (!mcpResourceMeta) {
60
- throw new PikkuMissingMetaError(
61
- `Missing generated metadata for MCP resource '${mcpResource.uri}'`
56
+ console.warn(
57
+ `[pikku] Skipping MCP resource '${mcpResource.uri}' — metadata not found. Consider moving this wiring to its own file.`
62
58
  )
59
+ return
63
60
  }
64
61
  addFunction(mcpResourceMeta.pikkuFuncId, mcpResource.func as any)
65
62
  const resources = pikkuState(null, 'mcp', 'resources')
@@ -79,9 +76,10 @@ export const wireMCPPrompt = <
79
76
  const promptsMeta = pikkuState(null, 'mcp', 'promptsMeta')
80
77
  const mcpPromptMeta = promptsMeta[mcpPrompt.name]
81
78
  if (!mcpPromptMeta) {
82
- throw new PikkuMissingMetaError(
83
- `Missing generated metadata for MCP prompt '${mcpPrompt.name}'`
79
+ console.warn(
80
+ `[pikku] Skipping MCP prompt '${mcpPrompt.name}' — metadata not found. Consider moving this wiring to its own file.`
84
81
  )
82
+ return
85
83
  }
86
84
  addFunction(mcpPromptMeta.pikkuFuncId, mcpPrompt.func as any)
87
85
  const prompts = pikkuState(null, 'mcp', 'prompts')
@@ -72,7 +72,7 @@ describe('wireQueueWorker', () => {
72
72
  assert.equal(registrations.get('test-queue'), mockWorker)
73
73
  })
74
74
 
75
- test('should throw error when processor metadata not found', () => {
75
+ test('should skip when queue worker metadata not found', () => {
76
76
  const mockWorker: CoreQueueWorker = {
77
77
  name: 'missing-meta-queue',
78
78
  func: {
@@ -81,16 +81,10 @@ describe('wireQueueWorker', () => {
81
81
  },
82
82
  }
83
83
 
84
- assert.throws(
85
- () => wireQueueWorker(mockWorker),
86
- (error: any) => {
87
- assert(
88
- error.message.includes('Missing generated metadata for queue worker')
89
- )
90
- assert(error.message.includes('missing-meta-queue'))
91
- return true
92
- }
93
- )
84
+ wireQueueWorker(mockWorker)
85
+
86
+ const registrations = pikkuState(null, 'queue', 'registrations')
87
+ assert.equal(registrations.has('missing-meta-queue'), false)
94
88
  })
95
89
 
96
90
  test('should wire worker with middleware and tags', () => {
@@ -58,9 +58,10 @@ export const wireQueueWorker = <
58
58
  const meta = pikkuState(null, 'queue', 'meta')
59
59
  const processorMeta = meta[queueWorker.name]
60
60
  if (!processorMeta) {
61
- throw new PikkuMissingMetaError(
62
- `Missing generated metadata for queue worker '${queueWorker.name}'`
61
+ console.warn(
62
+ `[pikku] Skipping queue worker '${queueWorker.name}' — metadata not found. Consider moving this wiring to its own file.`
63
63
  )
64
+ return
64
65
  }
65
66
 
66
67
  // Register the function with pikku
@@ -104,13 +105,19 @@ export async function removeQueueWorker(name: string): Promise<void> {
104
105
  export async function runQueueJob({
105
106
  job,
106
107
  updateProgress,
108
+ traceId,
107
109
  }: {
108
110
  job: QueueJob
109
111
  updateProgress?: (progress: number | string | object) => Promise<void>
112
+ /** Pre-resolved trace ID (e.g. from queue message metadata) */
113
+ traceId?: string
110
114
  }): Promise<void> {
111
115
  const singletonServices = getSingletonServices()
112
116
  const createWireServices = getCreateWireServices()
113
- const logger = singletonServices.logger
117
+ const resolvedTraceId = traceId ?? `q-${job.id}`
118
+ const logger =
119
+ singletonServices.logger.scope?.(resolvedTraceId) ??
120
+ singletonServices.logger
114
121
 
115
122
  const meta = pikkuState(null, 'queue', 'meta')
116
123
  const processorMeta = meta[job.queueName]
@@ -150,6 +157,7 @@ export async function runQueueJob({
150
157
  logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
151
158
 
152
159
  const wire: PikkuWire = {
160
+ traceId: resolvedTraceId,
153
161
  queue,
154
162
  }
155
163