@pikku/core 0.12.3 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/dist/dev/hot-reload.d.ts +10 -0
- package/dist/dev/hot-reload.js +158 -0
- package/dist/middleware-runner.d.ts +1 -0
- package/dist/middleware-runner.js +5 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +5 -0
- package/dist/services/in-memory-workflow-service.d.ts +15 -2
- package/dist/services/in-memory-workflow-service.js +56 -11
- package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
- package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
- package/dist/wirings/channel/channel-middleware-runner.js +5 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +7 -8
- package/package.json +3 -2
- package/src/dev/hot-reload.test.ts +484 -0
- package/src/dev/hot-reload.ts +212 -0
- package/src/middleware-runner.ts +6 -0
- package/src/permissions.ts +8 -0
- package/src/services/in-memory-workflow-service.test.ts +5 -5
- package/src/services/in-memory-workflow-service.ts +78 -14
- package/src/wirings/ai-agent/ai-agent-prepare.ts +25 -2
- package/src/wirings/channel/channel-middleware-runner.ts +6 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +12 -9
- package/tsconfig.tsbuildinfo +1 -1
package/src/permissions.ts
CHANGED
|
@@ -133,6 +133,14 @@ const combinedPermissionsCache: Record<
|
|
|
133
133
|
gateway: {},
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
export const clearPermissionsCache = () => {
|
|
137
|
+
for (const key of Object.keys(
|
|
138
|
+
combinedPermissionsCache
|
|
139
|
+
) as PikkuWiringTypes[]) {
|
|
140
|
+
combinedPermissionsCache[key] = {}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
/**
|
|
137
145
|
* Combines wiring-specific permissions with function-level permissions.
|
|
138
146
|
*
|
|
@@ -142,12 +142,12 @@ describe('InMemoryWorkflowService', () => {
|
|
|
142
142
|
assert.ok(updated.failedAt)
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
-
test('should
|
|
145
|
+
test('should throw for non-existent step', async () => {
|
|
146
146
|
const runId = await service.createRun('wf', {}, true, 'h', {} as any)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
await assert.rejects(
|
|
148
|
+
() => service.getStepState(runId, 'non-existent'),
|
|
149
|
+
/Step not found/
|
|
150
|
+
)
|
|
151
151
|
})
|
|
152
152
|
})
|
|
153
153
|
|
|
@@ -3,6 +3,7 @@ import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service
|
|
|
3
3
|
import type { SerializedError } from '../types/core.types.js'
|
|
4
4
|
import type {
|
|
5
5
|
WorkflowRun,
|
|
6
|
+
WorkflowRunService,
|
|
6
7
|
WorkflowRunWire,
|
|
7
8
|
StepState,
|
|
8
9
|
WorkflowStatus,
|
|
@@ -31,7 +32,10 @@ interface InternalStepData {
|
|
|
31
32
|
* await workflowService.startWorkflow('myWorkflow', input, { type: 'cli' }, rpc, { inline: true })
|
|
32
33
|
* ```
|
|
33
34
|
*/
|
|
34
|
-
export class InMemoryWorkflowService
|
|
35
|
+
export class InMemoryWorkflowService
|
|
36
|
+
extends PikkuWorkflowService
|
|
37
|
+
implements WorkflowRunService
|
|
38
|
+
{
|
|
35
39
|
private runs = new Map<string, WorkflowRun>()
|
|
36
40
|
private steps = new Map<string, StepState>() // keyed by `${runId}:${stepName}`
|
|
37
41
|
private stepData = new Map<string, InternalStepData>() // keyed by stepId
|
|
@@ -114,7 +118,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
114
118
|
const stepId = randomUUID()
|
|
115
119
|
const now = new Date()
|
|
116
120
|
|
|
117
|
-
const step: StepState = {
|
|
121
|
+
const step: StepState & { stepName: string } = {
|
|
118
122
|
stepId,
|
|
119
123
|
status: 'pending',
|
|
120
124
|
attemptCount: 1,
|
|
@@ -122,15 +126,16 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
122
126
|
retryDelay: stepOptions?.retryDelay,
|
|
123
127
|
createdAt: now,
|
|
124
128
|
updatedAt: now,
|
|
129
|
+
stepName,
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
const key = `${runId}:${stepName}`
|
|
128
133
|
this.steps.set(key, step)
|
|
129
134
|
this.stepData.set(stepId, { rpcName, data, stepName })
|
|
130
135
|
|
|
131
|
-
// Add to history
|
|
136
|
+
// Add to history (same reference so mutations are reflected)
|
|
132
137
|
const history = this.stepHistory.get(runId) || []
|
|
133
|
-
history.push(
|
|
138
|
+
history.push(step)
|
|
134
139
|
this.stepHistory.set(runId, history)
|
|
135
140
|
|
|
136
141
|
return step
|
|
@@ -140,13 +145,9 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
140
145
|
const key = `${runId}:${stepName}`
|
|
141
146
|
const step = this.steps.get(key)
|
|
142
147
|
if (!step) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
attemptCount: 0,
|
|
147
|
-
createdAt: new Date(),
|
|
148
|
-
updatedAt: new Date(),
|
|
149
|
-
}
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Step not found: runId=${runId}, stepName=${stepName}. Use insertStepState to create it.`
|
|
150
|
+
)
|
|
150
151
|
}
|
|
151
152
|
return step
|
|
152
153
|
}
|
|
@@ -228,7 +229,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
228
229
|
const newStepId = randomUUID()
|
|
229
230
|
const now = new Date()
|
|
230
231
|
|
|
231
|
-
const newStep: StepState = {
|
|
232
|
+
const newStep: StepState & { stepName: string } = {
|
|
232
233
|
stepId: newStepId,
|
|
233
234
|
status,
|
|
234
235
|
attemptCount: failedStep.attemptCount + 1,
|
|
@@ -236,6 +237,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
236
237
|
retryDelay: failedStep.retryDelay,
|
|
237
238
|
createdAt: now,
|
|
238
239
|
updatedAt: now,
|
|
240
|
+
stepName: stepName,
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
if (status === 'running') {
|
|
@@ -250,14 +252,76 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
250
252
|
this.stepData.set(newStepId, { ...failedStepData })
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
// Add to history
|
|
255
|
+
// Add to history (same reference so mutations are reflected)
|
|
254
256
|
const history = this.stepHistory.get(runId) || []
|
|
255
|
-
history.push(
|
|
257
|
+
history.push(newStep)
|
|
256
258
|
this.stepHistory.set(runId, history)
|
|
257
259
|
|
|
258
260
|
return newStep
|
|
259
261
|
}
|
|
260
262
|
|
|
263
|
+
async listRuns(options?: {
|
|
264
|
+
workflowName?: string
|
|
265
|
+
status?: string
|
|
266
|
+
limit?: number
|
|
267
|
+
offset?: number
|
|
268
|
+
}): Promise<WorkflowRun[]> {
|
|
269
|
+
let runs = Array.from(this.runs.values())
|
|
270
|
+
if (options?.workflowName) {
|
|
271
|
+
runs = runs.filter((r) => r.workflow === options.workflowName)
|
|
272
|
+
}
|
|
273
|
+
if (options?.status) {
|
|
274
|
+
runs = runs.filter((r) => r.status === options.status)
|
|
275
|
+
}
|
|
276
|
+
runs.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
|
277
|
+
const offset = options?.offset ?? 0
|
|
278
|
+
const limit = options?.limit ?? runs.length
|
|
279
|
+
return runs.slice(offset, offset + limit)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async getRunSteps(
|
|
283
|
+
runId: string
|
|
284
|
+
): Promise<
|
|
285
|
+
Array<StepState & { stepName: string; rpcName?: string; data?: any }>
|
|
286
|
+
> {
|
|
287
|
+
const history = this.stepHistory.get(runId) || []
|
|
288
|
+
return history.map((step) => {
|
|
289
|
+
const stepDataEntry = this.stepData.get(step.stepId)
|
|
290
|
+
return {
|
|
291
|
+
...step,
|
|
292
|
+
rpcName: stepDataEntry?.rpcName ?? undefined,
|
|
293
|
+
data: stepDataEntry?.data,
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async getDistinctWorkflowNames(): Promise<string[]> {
|
|
299
|
+
const names = new Set<string>()
|
|
300
|
+
for (const run of this.runs.values()) {
|
|
301
|
+
names.add(run.workflow)
|
|
302
|
+
}
|
|
303
|
+
return Array.from(names)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async deleteRun(id: string): Promise<boolean> {
|
|
307
|
+
const existed = this.runs.has(id)
|
|
308
|
+
this.runs.delete(id)
|
|
309
|
+
this.stepHistory.delete(id)
|
|
310
|
+
this.runState.delete(id)
|
|
311
|
+
const prefix = `${id}:`
|
|
312
|
+
for (const key of this.steps.keys()) {
|
|
313
|
+
if (key.startsWith(prefix)) {
|
|
314
|
+
const step = this.steps.get(key)
|
|
315
|
+
if (step) {
|
|
316
|
+
this.stepData.delete(step.stepId)
|
|
317
|
+
this.branchKeys.delete(step.stepId)
|
|
318
|
+
}
|
|
319
|
+
this.steps.delete(key)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return existed
|
|
323
|
+
}
|
|
324
|
+
|
|
261
325
|
async withRunLock<T>(_id: string, fn: () => Promise<T>): Promise<T> {
|
|
262
326
|
// In-memory service doesn't need locking for inline execution
|
|
263
327
|
return fn()
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
resolveNamespace,
|
|
22
22
|
ContextAwareRPCService,
|
|
23
23
|
} from '../../wirings/rpc/rpc-runner.js'
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
buildDynamicWorkflowInstructions,
|
|
26
|
+
buildWorkflowTools,
|
|
27
|
+
} from './agent-dynamic-workflow.js'
|
|
25
28
|
import {
|
|
26
29
|
resolveMemoryServices,
|
|
27
30
|
loadContextMessages,
|
|
@@ -122,7 +125,10 @@ export async function buildInstructions(
|
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
if (meta?.dynamicWorkflows && meta.tools?.length) {
|
|
125
|
-
instructions += buildDynamicWorkflowInstructions(
|
|
128
|
+
instructions += buildDynamicWorkflowInstructions(
|
|
129
|
+
meta.tools,
|
|
130
|
+
meta.dynamicWorkflows
|
|
131
|
+
)
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
return instructions
|
|
@@ -408,6 +414,23 @@ export async function buildToolDefs(
|
|
|
408
414
|
params,
|
|
409
415
|
agentSessionMap
|
|
410
416
|
)
|
|
417
|
+
if (
|
|
418
|
+
result.status === 'suspended' &&
|
|
419
|
+
result.pendingApprovals?.length
|
|
420
|
+
) {
|
|
421
|
+
return {
|
|
422
|
+
__approvalRequired: true,
|
|
423
|
+
toolName: subAgentName,
|
|
424
|
+
args: toolInput,
|
|
425
|
+
agentRunId: result.runId,
|
|
426
|
+
subApprovals: result.pendingApprovals.map((a) => ({
|
|
427
|
+
toolCallId: a.toolCallId,
|
|
428
|
+
toolName: a.toolName,
|
|
429
|
+
args: a.args,
|
|
430
|
+
runId: a.runId,
|
|
431
|
+
})),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
411
434
|
return result.object ?? result.text
|
|
412
435
|
},
|
|
413
436
|
})
|
|
@@ -30,6 +30,12 @@ const channelMiddlewareCache: Record<
|
|
|
30
30
|
readonly CorePikkuChannelMiddleware[]
|
|
31
31
|
> = {}
|
|
32
32
|
|
|
33
|
+
export const clearChannelMiddlewareCache = () => {
|
|
34
|
+
for (const key of Object.keys(channelMiddlewareCache)) {
|
|
35
|
+
delete channelMiddlewareCache[key]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
export const combineChannelMiddleware = (
|
|
34
40
|
wireType: string,
|
|
35
41
|
uid: string,
|
|
@@ -464,25 +464,28 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
464
464
|
throw new Error(`Missing workflow graphHash for '${name}'`)
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
const shouldInline =
|
|
468
|
+
options?.inline || !getSingletonServices()?.queueService
|
|
469
|
+
|
|
467
470
|
const runId = await this.createRun(
|
|
468
471
|
name,
|
|
469
472
|
input,
|
|
470
|
-
|
|
473
|
+
shouldInline,
|
|
471
474
|
workflowMeta.graphHash,
|
|
472
475
|
wire
|
|
473
476
|
)
|
|
474
477
|
|
|
475
|
-
if (
|
|
478
|
+
if (shouldInline) {
|
|
476
479
|
this.inlineRuns.add(runId)
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (options?.inline || !getSingletonServices()?.queueService) {
|
|
480
480
|
this.runWorkflowJob(runId, rpcService)
|
|
481
|
-
.catch(() => {
|
|
481
|
+
.catch((err) => {
|
|
482
|
+
getSingletonServices()!.logger.error(
|
|
483
|
+
`Workflow ${name} (run ${runId}) failed:`,
|
|
484
|
+
err
|
|
485
|
+
)
|
|
486
|
+
})
|
|
482
487
|
.finally(() => {
|
|
483
|
-
|
|
484
|
-
this.inlineRuns.delete(runId)
|
|
485
|
-
}
|
|
488
|
+
this.inlineRuns.delete(runId)
|
|
486
489
|
})
|
|
487
490
|
} else {
|
|
488
491
|
await this.resumeWorkflow(runId)
|