@pikku/core 0.12.42 → 0.12.45
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 +39 -0
- package/dist/index.d.ts +1 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/types/core.types.d.ts +32 -0
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +1 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +97 -91
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/types/core.types.ts +33 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +121 -120
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1464,154 +1464,155 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1464
1464
|
data: any,
|
|
1465
1465
|
rpcService: any
|
|
1466
1466
|
): Promise<void> {
|
|
1467
|
-
//
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
if (stepState.status === 'running') {
|
|
1479
|
-
return
|
|
1467
|
+
// Claim the step under the lock ONLY (atomic check-and-mark-running). Do NOT
|
|
1468
|
+
// hold the advisory lock — and its pooled connection — across execution: once
|
|
1469
|
+
// a step is 'running' the guard below makes any concurrent worker return
|
|
1470
|
+
// early, so the work + result persistence run with the lock released. Holding
|
|
1471
|
+
// the lock across executeGraphStep (network I/O + more pool queries) let
|
|
1472
|
+
// concurrent steps exhaust the connection pool and self-deadlock.
|
|
1473
|
+
const claimed = await this.withStepLock(runId, stepName, async () => {
|
|
1474
|
+
const stepState = await this.getStepState(runId, stepName)
|
|
1475
|
+
// Already succeeded, or already claimed by another worker — nothing to do.
|
|
1476
|
+
if (stepState.status === 'succeeded' || stepState.status === 'running') {
|
|
1477
|
+
return null
|
|
1480
1478
|
}
|
|
1481
|
-
|
|
1482
|
-
// If status is 'failed', this is a retry - create new attempt history
|
|
1479
|
+
// A 'failed' status means this is a retry — start a fresh 'running' attempt.
|
|
1483
1480
|
if (stepState.status === 'failed') {
|
|
1484
|
-
|
|
1481
|
+
return this.createRetryAttempt(stepState.stepId, 'running')
|
|
1485
1482
|
}
|
|
1486
|
-
|
|
1487
1483
|
if (stepState.status === 'pending' || stepState.status === 'scheduled') {
|
|
1488
1484
|
await this.setStepRunning(stepState.stepId)
|
|
1489
1485
|
}
|
|
1486
|
+
return stepState
|
|
1487
|
+
})
|
|
1490
1488
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1489
|
+
// Nothing to execute: already succeeded, or another worker owns this step.
|
|
1490
|
+
if (!claimed) {
|
|
1491
|
+
return
|
|
1492
|
+
}
|
|
1493
|
+
const stepState = claimed
|
|
1493
1494
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
throw new Error(`Workflow run not found: ${runId}`)
|
|
1497
|
-
}
|
|
1495
|
+
try {
|
|
1496
|
+
let result: any
|
|
1498
1497
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1498
|
+
const run = await this.getRun(runId)
|
|
1499
|
+
if (!run) {
|
|
1500
|
+
throw new Error(`Workflow run not found: ${runId}`)
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const meta = pikkuState(null, 'workflows', 'meta')
|
|
1504
|
+
const workflowMeta = meta[run.workflow]
|
|
1505
|
+
|
|
1506
|
+
const isGraphWorkflow =
|
|
1507
|
+
workflowMeta?.source === 'graph' ||
|
|
1508
|
+
workflowMeta?.source === 'dynamic-workflow'
|
|
1509
|
+
// Map the physical step key back to its logical node: a revisit instance
|
|
1510
|
+
// is `node#N` (ordinal), which isn't a literal key in `nodes`.
|
|
1511
|
+
let graphNodeId: string | undefined
|
|
1512
|
+
if (isGraphWorkflow && workflowMeta?.nodes) {
|
|
1513
|
+
if (stepName in workflowMeta.nodes) {
|
|
1514
|
+
graphNodeId = stepName
|
|
1515
|
+
} else {
|
|
1516
|
+
const hash = stepName.lastIndexOf('#')
|
|
1517
|
+
const base = hash > 0 ? stepName.slice(0, hash) : undefined
|
|
1518
|
+
if (base && base in workflowMeta.nodes) graphNodeId = base
|
|
1516
1519
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1520
|
+
}
|
|
1521
|
+
if (graphNodeId) {
|
|
1522
|
+
result = await executeGraphStep(
|
|
1523
|
+
this,
|
|
1524
|
+
rpcService,
|
|
1525
|
+
runId,
|
|
1526
|
+
stepState.stepId,
|
|
1527
|
+
graphNodeId,
|
|
1528
|
+
rpcName,
|
|
1529
|
+
data,
|
|
1530
|
+
run.workflow
|
|
1531
|
+
)
|
|
1532
|
+
} else {
|
|
1533
|
+
// Check if rpcName refers to a sub-workflow
|
|
1534
|
+
const subWorkflowMeta = meta[rpcName]
|
|
1535
|
+
if (subWorkflowMeta) {
|
|
1536
|
+
const childWire: WorkflowRunWire = {
|
|
1537
|
+
type: 'workflow',
|
|
1538
|
+
id: rpcName,
|
|
1539
|
+
parentRunId: runId,
|
|
1540
|
+
parentStepId: stepState.stepId,
|
|
1541
|
+
pikkuUserId: rpcService.wire?.pikkuUserId,
|
|
1542
|
+
}
|
|
1543
|
+
const shouldInline = !getSingletonServices()?.queueService
|
|
1544
|
+
const { runId: childRunId } = await this.startWorkflow(
|
|
1524
1545
|
rpcName,
|
|
1525
1546
|
data,
|
|
1526
|
-
|
|
1547
|
+
childWire,
|
|
1548
|
+
rpcService,
|
|
1549
|
+
{ inline: shouldInline }
|
|
1527
1550
|
)
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
type: 'workflow',
|
|
1534
|
-
id: rpcName,
|
|
1535
|
-
parentRunId: runId,
|
|
1536
|
-
parentStepId: stepState.stepId,
|
|
1537
|
-
pikkuUserId: rpcService.wire?.pikkuUserId,
|
|
1551
|
+
await this.setStepChildRunId(stepState.stepId, childRunId)
|
|
1552
|
+
if (shouldInline) {
|
|
1553
|
+
const childRun = await this.getRun(childRunId)
|
|
1554
|
+
if (childRun?.status === 'failed') {
|
|
1555
|
+
throw new Error(childRun.error?.message || 'Sub-workflow failed')
|
|
1538
1556
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
rpcName,
|
|
1542
|
-
data,
|
|
1543
|
-
childWire,
|
|
1544
|
-
rpcService,
|
|
1545
|
-
{ inline: shouldInline }
|
|
1546
|
-
)
|
|
1547
|
-
await this.setStepChildRunId(stepState.stepId, childRunId)
|
|
1548
|
-
if (shouldInline) {
|
|
1549
|
-
const childRun = await this.getRun(childRunId)
|
|
1550
|
-
if (childRun?.status === 'failed') {
|
|
1551
|
-
throw new Error(
|
|
1552
|
-
childRun.error?.message || 'Sub-workflow failed'
|
|
1553
|
-
)
|
|
1554
|
-
}
|
|
1555
|
-
if (childRun?.status === 'cancelled') {
|
|
1556
|
-
throw new Error('Sub-workflow was cancelled')
|
|
1557
|
-
}
|
|
1558
|
-
result = childRun?.output
|
|
1559
|
-
} else {
|
|
1560
|
-
throw new ChildWorkflowStartedException(
|
|
1561
|
-
runId,
|
|
1562
|
-
stepState.stepId,
|
|
1563
|
-
childRunId
|
|
1564
|
-
)
|
|
1557
|
+
if (childRun?.status === 'cancelled') {
|
|
1558
|
+
throw new Error('Sub-workflow was cancelled')
|
|
1565
1559
|
}
|
|
1560
|
+
result = childRun?.output
|
|
1566
1561
|
} else {
|
|
1567
|
-
|
|
1562
|
+
throw new ChildWorkflowStartedException(
|
|
1568
1563
|
runId,
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
rpcName,
|
|
1572
|
-
data,
|
|
1573
|
-
rpcService
|
|
1564
|
+
stepState.stepId,
|
|
1565
|
+
childRunId
|
|
1574
1566
|
)
|
|
1575
1567
|
}
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
if (error instanceof ChildWorkflowStartedException) {
|
|
1585
|
-
this.logger?.debug(
|
|
1586
|
-
`Workflow step '${stepName}': child workflow ${error.childRunId} started, waiting for completion`
|
|
1568
|
+
} else {
|
|
1569
|
+
result = await this.invokeStepRpc(
|
|
1570
|
+
runId,
|
|
1571
|
+
stepName,
|
|
1572
|
+
stepState,
|
|
1573
|
+
rpcName,
|
|
1574
|
+
data,
|
|
1575
|
+
rpcService
|
|
1587
1576
|
)
|
|
1588
|
-
return
|
|
1589
1577
|
}
|
|
1578
|
+
}
|
|
1590
1579
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1580
|
+
// Store result and mark succeeded
|
|
1581
|
+
await this.setStepResult(stepState.stepId, result)
|
|
1582
|
+
|
|
1583
|
+
// Resume orchestrator to continue workflow
|
|
1584
|
+
await this.resumeWorkflow(runId)
|
|
1585
|
+
} catch (error: any) {
|
|
1586
|
+
if (error instanceof ChildWorkflowStartedException) {
|
|
1587
|
+
this.logger?.debug(
|
|
1588
|
+
`Workflow step '${stepName}': child workflow ${error.childRunId} started, waiting for completion`
|
|
1589
|
+
)
|
|
1590
|
+
return
|
|
1591
|
+
}
|
|
1599
1592
|
|
|
1600
|
-
|
|
1593
|
+
if (error instanceof RPCNotFoundError) {
|
|
1601
1594
|
await this.setStepError(stepState.stepId, error)
|
|
1595
|
+
await this.updateRunStatus(runId, 'suspended', undefined, {
|
|
1596
|
+
message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
|
|
1597
|
+
code: 'RPC_NOT_FOUND',
|
|
1598
|
+
})
|
|
1599
|
+
return
|
|
1600
|
+
}
|
|
1602
1601
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1602
|
+
// Store error and mark failed
|
|
1603
|
+
await this.setStepError(stepState.stepId, error)
|
|
1605
1604
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
await this.resumeWorkflow(runId)
|
|
1609
|
-
}
|
|
1605
|
+
const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1
|
|
1606
|
+
const retriesExhausted = stepState.attemptCount >= maxAttempts
|
|
1610
1607
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1608
|
+
if (retriesExhausted) {
|
|
1609
|
+
// No more retries - resume orchestrator to mark workflow as failed
|
|
1610
|
+
await this.resumeWorkflow(runId)
|
|
1613
1611
|
}
|
|
1614
|
-
|
|
1612
|
+
|
|
1613
|
+
// Always throw so queue knows the job failed and can retry if needed
|
|
1614
|
+
throw error
|
|
1615
|
+
}
|
|
1615
1616
|
}
|
|
1616
1617
|
|
|
1617
1618
|
/**
|