@pikku/core 0.12.66 → 0.12.69
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 +153 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/scopes.d.ts +14 -0
- package/dist/scopes.js +39 -8
- package/dist/services/in-memory-workflow-service.d.ts +2 -2
- package/dist/services/in-memory-workflow-service.js +2 -2
- package/dist/types/core.types.d.ts +22 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +18 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +26 -4
- package/dist/wirings/queue/index.d.ts +1 -1
- package/dist/wirings/queue/queue.types.d.ts +30 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +8 -0
- package/dist/wirings/scope/validate-scope-definitions.js +16 -1
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -3
- package/dist/wirings/workflow/pikku-workflow-service.js +86 -18
- package/dist/wirings/workflow/workflow.types.d.ts +38 -0
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/scopes.test.ts +37 -1
- package/src/scopes.ts +48 -9
- package/src/services/in-memory-workflow-service.test.ts +50 -1
- package/src/services/in-memory-workflow-service.ts +3 -2
- package/src/types/core.types.ts +23 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +29 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +38 -4
- package/src/wirings/queue/index.ts +2 -0
- package/src/wirings/queue/queue.types.ts +32 -0
- package/src/wirings/scope/scope.test.ts +25 -0
- package/src/wirings/scope/validate-scope-definitions.ts +16 -1
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +71 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +110 -20
- package/src/wirings/workflow/workflow.types.ts +39 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,156 @@
|
|
|
1
|
+
## 0.12.69
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 24252b8: Emit queue meta for workflow-only projects, so per-workflow orchestrator queues actually work.
|
|
6
|
+
|
|
7
|
+
Workflows synthesise their own `wf-orchestrator-*` / `wf-step-*` queue meta during
|
|
8
|
+
post-processing, and those entries have no declaring source file. The queue codegen
|
|
9
|
+
bailed early on `queueWorkers.files.size === 0`, so a project that uses workflows but
|
|
10
|
+
hand-declares no `wireQueueWorker` wrote no queue meta at all — and the generated
|
|
11
|
+
bootstrap therefore never imported it.
|
|
12
|
+
|
|
13
|
+
With `queue.meta` empty at runtime, `getOrchestratorQueueName()` never found a
|
|
14
|
+
per-workflow queue and every workflow silently fell back to the single shared
|
|
15
|
+
`pikku-workflow-orchestrator` queue. Nothing failed, but the isolation was gone: one
|
|
16
|
+
long-running workflow step head-of-line-blocked every other workflow queued behind it.
|
|
17
|
+
|
|
18
|
+
The codegen now gates on the meta alone. `@pikku/core` additionally warns at wiring
|
|
19
|
+
time when workflows are registered but no per-workflow orchestrator queue is present,
|
|
20
|
+
so this degradation can't recur silently.
|
|
21
|
+
|
|
22
|
+
- e3d4454: Add job groups, so one shared queue can stay fair without splitting into one
|
|
23
|
+
queue per producer.
|
|
24
|
+
|
|
25
|
+
A job may now carry `group: { id, tier }`, and a worker may cap how many jobs
|
|
26
|
+
of any one group run at once via `groupConcurrency`. On pg-boss this maps to
|
|
27
|
+
`localGroupConcurrency`, which excludes at-capacity groups from the fetch query
|
|
28
|
+
itself, so a capped group costs nothing rather than being fetched and restored.
|
|
29
|
+
BullMQ declares it unsupported (groups are a BullMQ Pro feature) — being
|
|
30
|
+
push-based, it can simply use a queue per group at no polling cost.
|
|
31
|
+
|
|
32
|
+
Workflow services accept a `queueStrategy`. The default `'per-workflow'` is
|
|
33
|
+
unchanged: every workflow gets its own `wf-orchestrator-*` / `wf-step-*` queue,
|
|
34
|
+
which is also what lets serverless providers deploy one unit per workflow. The
|
|
35
|
+
new `'shared-groups'` routes every workflow through the shared
|
|
36
|
+
orchestrator/step-worker queues and isolates them by group instead, so a
|
|
37
|
+
monolith runs one set of pollers rather than one per workflow — on a
|
|
38
|
+
pull-based backend with dozens of workflows that is the difference between
|
|
39
|
+
hundreds of poll loops and twenty. It is for single-process runtimes only; a
|
|
40
|
+
per-unit serverless deploy still needs the per-workflow queues to route to its
|
|
41
|
+
units.
|
|
42
|
+
|
|
43
|
+
## 0.12.68
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- f11675f: Forward the parent run's `context` into delegated sub-agent invocations.
|
|
48
|
+
|
|
49
|
+
A supervisor agent's injected `context` (the "Current context" block holding the
|
|
50
|
+
authoritative identifiers — organizationId, project/stage ids) was appended only
|
|
51
|
+
to the supervisor's own instructions. When it delegated, the sub-agent tool's
|
|
52
|
+
input schema carries just `{ message, session }`, and `buildToolDefs` invoked the
|
|
53
|
+
sub-agent with `{ message, threadId, resourceId }` — dropping the context. The
|
|
54
|
+
sub-agent therefore never saw the real ids and depended on the model re-typing
|
|
55
|
+
them into the free-text `message`, which weaker models routinely botch, producing
|
|
56
|
+
schema-validation and permission rejections that the agent then retries — burning
|
|
57
|
+
steps and ballooning the transcript.
|
|
58
|
+
|
|
59
|
+
`buildToolDefs` now takes the parent `context` and forwards it (via the new
|
|
60
|
+
`buildSubAgentRunInput` helper) into both the streaming and non-streaming
|
|
61
|
+
sub-agent invocations, so a specialist inherits the same identifier block in its
|
|
62
|
+
instructions.
|
|
63
|
+
|
|
64
|
+
## 0.12.67
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- ae4f59a: Gate admin capabilities on scopes, and scaffold user management
|
|
69
|
+
|
|
70
|
+
Admin capabilities were gated on `user.role === 'admin'` — a single text column
|
|
71
|
+
meaning "can do everything". Impersonating a user, rebinding a shared
|
|
72
|
+
credential and reading the user directory are distinct capabilities that one
|
|
73
|
+
user can hold independently, so they are now scopes on an `admin` tree:
|
|
74
|
+
|
|
75
|
+
| Gate | Scope |
|
|
76
|
+
| -------------------------------------- | ------------------------ |
|
|
77
|
+
| impersonation | `admin:impersonate` |
|
|
78
|
+
| `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link` |
|
|
79
|
+
| reading the user directory | `admin:users:list` |
|
|
80
|
+
| creating a user out of band | `admin:users:create` |
|
|
81
|
+
| ban / unban | `admin:users:ban` |
|
|
82
|
+
| delete a user | `admin:users:remove` |
|
|
83
|
+
| revoke a user's sessions | `admin:users:sessions` |
|
|
84
|
+
| set a user's password | `admin:users:password` |
|
|
85
|
+
|
|
86
|
+
Holding the bare `admin` scope satisfies all of them via pikku's existing
|
|
87
|
+
parent-grant rule, so it is a one-for-one replacement for the old role.
|
|
88
|
+
|
|
89
|
+
better-auth's `admin()` plugin is still what implements ban, delete,
|
|
90
|
+
session-revocation and set-password, so it stays. Its `user.role` column is no
|
|
91
|
+
longer something pikku grants: it is _projected_ from the scope store when a
|
|
92
|
+
session is built, and only from the scopes whose capability better-auth's own
|
|
93
|
+
endpoints gate on the caller's role. Someone granted `admin:users:list` can read
|
|
94
|
+
the directory — which goes straight to the auth adapter — without gaining the
|
|
95
|
+
power to ban, and revoking a scope demotes the role on the next sign-in. Scopes
|
|
96
|
+
remain the single source of truth.
|
|
97
|
+
|
|
98
|
+
New `scaffold.userAdmin` in `pikku.config.json` generates the whole set —
|
|
99
|
+
`pikkuAdminListUsers`, `pikkuAdminCreateUser`, `pikkuAdminSetUserBanned`,
|
|
100
|
+
`pikkuAdminRemoveUser`, `pikkuAdminRevokeUserSessions` and
|
|
101
|
+
`pikkuAdminSetUserPassword` — into your project. Listing or banning a user is
|
|
102
|
+
ordinary application behaviour and must not require installing the console.
|
|
103
|
+
Codegen fails with an actionable error if better-auth is wired without
|
|
104
|
+
`admin()`. The console's Users page calls these same functions, showing each
|
|
105
|
+
action only where the caller holds its scope.
|
|
106
|
+
|
|
107
|
+
Every scaffold now emits a directory named for its domain — `scaffold/admin/`,
|
|
108
|
+
`scaffold/rpc/`, `scaffold/agent/`, `scaffold/auth/`, `scaffold/console/`,
|
|
109
|
+
`scaffold/graph/`, `scaffold/realtime/`, `scaffold/scenarios/`,
|
|
110
|
+
`scaffold/webhook/`, `scaffold/workflow/` — holding its wiring file beside a
|
|
111
|
+
`*.schemas.gen.ts` sibling, and every generated payload is a zod schema instead
|
|
112
|
+
of a TypeScript generic. The schemas have to stand alone: the inspector reads a
|
|
113
|
+
zod schema by importing the module that declares it, which it cannot do for a
|
|
114
|
+
wiring file whose relative pikku-types import per-unit deploy codegen rewrites.
|
|
115
|
+
|
|
116
|
+
Resolving a schema by reference rather than by name also fixes the agent HTTP
|
|
117
|
+
surface. `agentCaller` and `agentStreamCaller` take the same payload but had to
|
|
118
|
+
repeat the type literal verbatim in each generic position, because the extractor
|
|
119
|
+
synthesised the schema name from the _function_ name and so recorded an
|
|
120
|
+
`inputSchemaName` with no schema behind it whenever the two shared a named
|
|
121
|
+
alias — every agent call through that alias failed with `MissingSchemaError`.
|
|
122
|
+
One `AgentCall` schema now backs both.
|
|
123
|
+
|
|
124
|
+
Where a payload's shape belongs to `@pikku/core` (`WorkflowRunStatus`,
|
|
125
|
+
`FunctionCoverageReport`, `StubCall[]`) the generated function carries no
|
|
126
|
+
`output` schema and the inspector infers it from the handler's return type;
|
|
127
|
+
re-declaring a core type in zod would be a second definition free to drift.
|
|
128
|
+
|
|
129
|
+
Upgrading rewrites the layout in place: codegen prunes the pre-directory copy of
|
|
130
|
+
each scaffold file before it inspects the source tree, since the old flat file
|
|
131
|
+
still wires the same routes and leaving it behind would wire everything twice.
|
|
132
|
+
|
|
133
|
+
`@pikku/core` gains `hasScopes(required, held)`, the non-throwing counterpart to
|
|
134
|
+
`verifyScopes`, and declares `auth` on `CoreSingletonServices` — the auth
|
|
135
|
+
instance the generated `pikkuServices` wrapper already injected but never typed.
|
|
136
|
+
A scope root declared twice (an addon and its host both contributing the same
|
|
137
|
+
`admin` tree) now flattens to one entry per id instead of emitting it twice.
|
|
138
|
+
|
|
139
|
+
BREAKING: there is no role fallback for the scope-gated capabilities. An app
|
|
140
|
+
that relied on the old default must register a `ScopeService` and grant `admin`
|
|
141
|
+
(or a narrower `admin:*` scope). Every gate fails closed and warns when no
|
|
142
|
+
`ScopeService` is registered. `delegatedAuth`'s `defaultRole`/`mapRole` now
|
|
143
|
+
grant a pikku role through the `ScopeService` instead of writing better-auth's
|
|
144
|
+
`role` column, and the `credentialOAuth` platform user no longer sets `banned`.
|
|
145
|
+
|
|
146
|
+
BREAKING: the console reads its user directory over the scaffolded
|
|
147
|
+
`pikkuAdminListUsers` RPC (gated on `admin:users:list`, backed by better-auth's
|
|
148
|
+
`$context.adapter`) instead of `client.admin.listUsers`, and
|
|
149
|
+
`UsersTableUser`/`UsersTableLabels` no longer carry `role` — there is no role
|
|
150
|
+
column to render. `@pikku/addon-console` no longer ships a `console:listUsers`
|
|
151
|
+
function: user management is not the console's job, so a host that wants the
|
|
152
|
+
Users page must enable `scaffold.userAdmin`.
|
|
153
|
+
|
|
1
154
|
## 0.12.66
|
|
2
155
|
|
|
3
156
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @pikku/core
|
|
3
3
|
*/
|
|
4
|
-
export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, ServerLifecycle, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuRawWire, PikkuWiringTypes, PostgresConfig, RequireAtLeastOne, SecurityAuditIssue, SecurityAuditReport, SecurityAuditSummary, SecurityAuditUpdate, SecuritySeverity, SecurityUpdateLevel, SerializedError, WireServices, } from './types/core.types.js';
|
|
4
|
+
export type { AuthInstance, CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, ServerLifecycle, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuRawWire, PikkuWiringTypes, PostgresConfig, RequireAtLeastOne, SecurityAuditIssue, SecurityAuditReport, SecurityAuditSummary, SecurityAuditUpdate, SecuritySeverity, SecurityUpdateLevel, SerializedError, WireServices, } from './types/core.types.js';
|
|
5
5
|
export { pikkuAIMiddleware, pikkuChannelMiddleware, pikkuChannelMiddlewareFactory, pikkuMiddleware, pikkuMiddlewareFactory, } from './types/core.types.js';
|
|
6
6
|
export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
|
|
7
7
|
export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
|
|
@@ -50,7 +50,7 @@ export type { WireRemoteAddonConfig, RemoteAddonAuth, } from './wirings/rpc/wire
|
|
|
50
50
|
export type { PikkuPackageState } from './types/state.types.js';
|
|
51
51
|
export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
|
|
52
52
|
export { addGlobalPermission, checkAuthPermissions } from './permissions.js';
|
|
53
|
-
export { verifyScopes } from './scopes.js';
|
|
53
|
+
export { hasScopes, verifyScopes } from './scopes.js';
|
|
54
54
|
export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
|
|
55
55
|
export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
|
|
56
56
|
export { clearPikkuRuntimeState } from './test-utils.js';
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export { wireAddon } from './wirings/rpc/wire-addon.js';
|
|
|
18
18
|
export { wireRemoteAddon } from './wirings/rpc/wire-remote-addon.js';
|
|
19
19
|
export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
|
|
20
20
|
export { addGlobalPermission, checkAuthPermissions } from './permissions.js';
|
|
21
|
-
export { verifyScopes } from './scopes.js';
|
|
21
|
+
export { hasScopes, verifyScopes } from './scopes.js';
|
|
22
22
|
export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
|
|
23
23
|
export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
|
|
24
24
|
export { clearPikkuRuntimeState } from './test-utils.js';
|
package/dist/scopes.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { CoreUserSession } from './types/core.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a set of held grants satisfies every required scope.
|
|
4
|
+
*
|
|
5
|
+
* The non-throwing counterpart to {@link verifyScopes}, for deciding rather
|
|
6
|
+
* than enforcing — an authorization gate that falls back to another check when
|
|
7
|
+
* it is not satisfied, rather than rejecting the request outright.
|
|
8
|
+
*
|
|
9
|
+
* Fails closed: an absent or empty `held` satisfies nothing. An empty
|
|
10
|
+
* `required` is satisfied by anything.
|
|
11
|
+
*
|
|
12
|
+
* @param required - Scopes to check for. Empty means no gate.
|
|
13
|
+
* @param held - The grants held, e.g. `session.scopes`. May be undefined.
|
|
14
|
+
*/
|
|
15
|
+
export declare const hasScopes: (required: readonly string[] | undefined, held: Iterable<string> | undefined) => boolean;
|
|
2
16
|
/**
|
|
3
17
|
* Verifies that a session holds every required scope, throwing on the first
|
|
4
18
|
* one it does not.
|
package/dist/scopes.js
CHANGED
|
@@ -31,6 +31,42 @@ const satisfyingGrants = (scope) => {
|
|
|
31
31
|
* `admin`.
|
|
32
32
|
*/
|
|
33
33
|
const holds = (held, scope) => satisfyingGrants(scope).some((grant) => held.has(grant));
|
|
34
|
+
/**
|
|
35
|
+
* The first required scope a set of held grants does not satisfy, or `null`
|
|
36
|
+
* when every one is satisfied.
|
|
37
|
+
*
|
|
38
|
+
* Scopes are an AND gate: every entry in `required` must be satisfied. This is
|
|
39
|
+
* deliberately distinct from `permissions`, which OR together — a scope can
|
|
40
|
+
* only ever narrow access, so adding one to a function can never widen it.
|
|
41
|
+
*
|
|
42
|
+
* Fails closed: an absent `held` satisfies nothing.
|
|
43
|
+
*/
|
|
44
|
+
const firstUnsatisfied = (required, held) => {
|
|
45
|
+
if (!required || required.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const grants = new Set(held ?? []);
|
|
49
|
+
for (const scope of required) {
|
|
50
|
+
if (!holds(grants, scope)) {
|
|
51
|
+
return scope;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Whether a set of held grants satisfies every required scope.
|
|
58
|
+
*
|
|
59
|
+
* The non-throwing counterpart to {@link verifyScopes}, for deciding rather
|
|
60
|
+
* than enforcing — an authorization gate that falls back to another check when
|
|
61
|
+
* it is not satisfied, rather than rejecting the request outright.
|
|
62
|
+
*
|
|
63
|
+
* Fails closed: an absent or empty `held` satisfies nothing. An empty
|
|
64
|
+
* `required` is satisfied by anything.
|
|
65
|
+
*
|
|
66
|
+
* @param required - Scopes to check for. Empty means no gate.
|
|
67
|
+
* @param held - The grants held, e.g. `session.scopes`. May be undefined.
|
|
68
|
+
*/
|
|
69
|
+
export const hasScopes = (required, held) => firstUnsatisfied(required, held) === null;
|
|
34
70
|
/**
|
|
35
71
|
* Verifies that a session holds every required scope, throwing on the first
|
|
36
72
|
* one it does not.
|
|
@@ -47,13 +83,8 @@ const holds = (held, scope) => satisfyingGrants(scope).some((grant) => held.has(
|
|
|
47
83
|
* @throws {MissingScopeError} Naming the first unsatisfied scope.
|
|
48
84
|
*/
|
|
49
85
|
export const verifyScopes = (required, session) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const held = new Set(session?.scopes ?? []);
|
|
54
|
-
for (const scope of required) {
|
|
55
|
-
if (!holds(held, scope)) {
|
|
56
|
-
throw new MissingScopeError(scope);
|
|
57
|
-
}
|
|
86
|
+
const missing = firstUnsatisfied(required, session?.scopes);
|
|
87
|
+
if (missing !== null) {
|
|
88
|
+
throw new MissingScopeError(missing);
|
|
58
89
|
}
|
|
59
90
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service.js';
|
|
2
2
|
import type { SerializedError } from '../types/core.types.js';
|
|
3
|
-
import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
3
|
+
import type { WorkflowPlannedStep, WorkflowQueueOptions, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
4
4
|
/**
|
|
5
5
|
* In-memory implementation of WorkflowService for inline-only execution
|
|
6
6
|
*
|
|
@@ -17,7 +17,7 @@ import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunW
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export declare class InMemoryWorkflowService extends PikkuWorkflowService implements WorkflowRunService {
|
|
20
|
-
constructor();
|
|
20
|
+
constructor(options?: WorkflowQueueOptions);
|
|
21
21
|
private sleepTimers;
|
|
22
22
|
private runs;
|
|
23
23
|
private steps;
|
|
@@ -17,8 +17,8 @@ import { isExpectedError } from '../errors/error-handler.js';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
20
|
-
constructor() {
|
|
21
|
-
super({ wireQueues: false });
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super({ ...options, wireQueues: false });
|
|
22
22
|
}
|
|
23
23
|
sleepTimers = new Set();
|
|
24
24
|
runs = new Map();
|
|
@@ -209,6 +209,21 @@ export interface CoreUserSession {
|
|
|
209
209
|
*/
|
|
210
210
|
scopes?: string[];
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* The shape pikku needs from whatever auth library a project wires: something
|
|
214
|
+
* that can answer an HTTP request and expose its own endpoints as callable
|
|
215
|
+
* methods. Kept structural so core stays independent of any one auth package —
|
|
216
|
+
* `@pikku/better-auth`'s `BetterAuthInstance` is this type.
|
|
217
|
+
*/
|
|
218
|
+
export interface AuthInstance {
|
|
219
|
+
handler: (request: Request) => Promise<Response>;
|
|
220
|
+
api: Record<string, any>;
|
|
221
|
+
/**
|
|
222
|
+
* The auth library's resolved context. Optional because a hand-built instance
|
|
223
|
+
* may omit it.
|
|
224
|
+
*/
|
|
225
|
+
$context?: Promise<any>;
|
|
226
|
+
}
|
|
212
227
|
/**
|
|
213
228
|
* Interface for core singleton services provided by Pikku.
|
|
214
229
|
*/
|
|
@@ -280,6 +295,13 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
280
295
|
* better-auth's `mapSession`), never by the function runner.
|
|
281
296
|
*/
|
|
282
297
|
scopeService?: ScopeService;
|
|
298
|
+
/**
|
|
299
|
+
* The project's resolved auth instance, built once by the factory an auth
|
|
300
|
+
* package registers (e.g. `pikkuBetterAuth`) and injected by the generated
|
|
301
|
+
* `pikkuServices` wrapper — which is why service factories are forbidden from
|
|
302
|
+
* returning it themselves. Absent when the project wires no auth.
|
|
303
|
+
*/
|
|
304
|
+
auth?: () => Promise<AuthInstance>;
|
|
283
305
|
}
|
|
284
306
|
/**
|
|
285
307
|
* Represents different forms of wire within Pikku and the outside world.
|
|
@@ -168,7 +168,24 @@ export type ScopedChannel = AIStreamChannel & {
|
|
|
168
168
|
}>;
|
|
169
169
|
};
|
|
170
170
|
export declare function createScopedChannel(parent: AIStreamChannel, agentName: string, session: string): ScopedChannel;
|
|
171
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Build the run input for a delegated sub-agent.
|
|
173
|
+
*
|
|
174
|
+
* `context` is the PARENT run's identifier block (the "Current context" text
|
|
175
|
+
* with organizationId, project/stage ids). A sub-agent's tool-call schema only
|
|
176
|
+
* carries { message, session }, so unless the sub-agent inherits the parent's
|
|
177
|
+
* context it never sees the authoritative ids — it depends on the model
|
|
178
|
+
* re-typing them into `message`, which weak models botch, causing
|
|
179
|
+
* schema/permission rejections and retry loops. Forwarding it here is the
|
|
180
|
+
* regression this seam guards.
|
|
181
|
+
*/
|
|
182
|
+
export declare function buildSubAgentRunInput(message: string, threadId: string, resourceId: string, parentContext?: string): {
|
|
183
|
+
message: string;
|
|
184
|
+
threadId: string;
|
|
185
|
+
resourceId: string;
|
|
186
|
+
context?: string;
|
|
187
|
+
};
|
|
188
|
+
export declare function buildToolDefs(params: RunAIAgentParams, agentSessionMap: Map<string, string>, resourceId: string, agentName: string, packageName: string | null, streamContext?: StreamContext, aiMiddlewares?: PikkuAIMiddlewareHooks[], agentMode?: 'delegate' | 'supervise', parentContext?: string): Promise<{
|
|
172
189
|
tools: AIAgentToolDef[];
|
|
173
190
|
missingRpcs: string[];
|
|
174
191
|
}>;
|
|
@@ -325,7 +325,29 @@ export function createScopedChannel(parent, agentName, session) {
|
|
|
325
325
|
clearState: () => parent.clearState(),
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Build the run input for a delegated sub-agent.
|
|
330
|
+
*
|
|
331
|
+
* `context` is the PARENT run's identifier block (the "Current context" text
|
|
332
|
+
* with organizationId, project/stage ids). A sub-agent's tool-call schema only
|
|
333
|
+
* carries { message, session }, so unless the sub-agent inherits the parent's
|
|
334
|
+
* context it never sees the authoritative ids — it depends on the model
|
|
335
|
+
* re-typing them into `message`, which weak models botch, causing
|
|
336
|
+
* schema/permission rejections and retry loops. Forwarding it here is the
|
|
337
|
+
* regression this seam guards.
|
|
338
|
+
*/
|
|
339
|
+
export function buildSubAgentRunInput(message, threadId, resourceId, parentContext) {
|
|
340
|
+
return { message, threadId, resourceId, context: parentContext };
|
|
341
|
+
}
|
|
342
|
+
export async function buildToolDefs(params, agentSessionMap, resourceId, agentName, packageName, streamContext, aiMiddlewares, agentMode,
|
|
343
|
+
// The parent run's `context` (the "Current context" identifier block). A
|
|
344
|
+
// delegated sub-agent's tool-call input schema only carries { message,
|
|
345
|
+
// session }, so without inheriting this the sub-agent never sees the
|
|
346
|
+
// authoritative ids (organizationId, project/stage ids) — it depends on the
|
|
347
|
+
// model re-typing them into `message`, which weak models botch, causing
|
|
348
|
+
// schema/permission rejections and retry loops. Forward it so the sub-agent
|
|
349
|
+
// gets the same context block in its instructions.
|
|
350
|
+
parentContext) {
|
|
329
351
|
const singletonServices = getSingletonServices();
|
|
330
352
|
const tools = [];
|
|
331
353
|
const missingRpcs = [];
|
|
@@ -505,7 +527,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
505
527
|
subChannel.send(event);
|
|
506
528
|
},
|
|
507
529
|
};
|
|
508
|
-
const resultText = await streamAIAgent(subAgentName,
|
|
530
|
+
const resultText = await streamAIAgent(subAgentName, buildSubAgentRunInput(message, threadId, resourceId, parentContext), effectiveChannel, params, agentSessionMap, streamContext.options);
|
|
509
531
|
if (subChannel.approvals.length > 0) {
|
|
510
532
|
return {
|
|
511
533
|
[APPROVAL_REQUIRED]: true,
|
|
@@ -525,7 +547,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
525
547
|
return resultText;
|
|
526
548
|
}
|
|
527
549
|
// No stream context: sub-agent runs non-streaming
|
|
528
|
-
const result = await runAIAgent(subAgentName,
|
|
550
|
+
const result = await runAIAgent(subAgentName, buildSubAgentRunInput(message, threadId, resourceId, parentContext), params, agentSessionMap);
|
|
529
551
|
if (result.status === 'suspended' &&
|
|
530
552
|
result.pendingApprovals?.length) {
|
|
531
553
|
return {
|
|
@@ -731,7 +753,7 @@ export async function prepareAgentRun(agentName, input, params, agentSessionMap,
|
|
|
731
753
|
const allMessages = [...contextMessages, ...messages, userMessage];
|
|
732
754
|
const trimmedMessages = trimMessages(allMessages);
|
|
733
755
|
const aiMiddlewares = agent.aiMiddleware ?? [];
|
|
734
|
-
const { tools, missingRpcs } = await buildToolDefs(params, agentSessionMap, input.resourceId, resolvedName, packageName, streamContext, aiMiddlewares, agent.agentMode);
|
|
756
|
+
const { tools, missingRpcs } = await buildToolDefs(params, agentSessionMap, input.resourceId, resolvedName, packageName, streamContext, aiMiddlewares, agent.agentMode, input.context);
|
|
735
757
|
let instructions = await buildInstructions(resolvedName, packageName);
|
|
736
758
|
if (input.context) {
|
|
737
759
|
instructions = `${instructions}\n\nCurrent context (use these identifiers directly in tool calls — do not ask the user for them):\n${input.context}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { ConfigValidationResult, CoreQueueWorker, JobOptions, PikkuJobConfig, PikkuWorkerConfig, PikkuQueue, QueueJob, QueueJobStatus, QueueService, QueueWorkers, QueueWorkersMeta, } from './queue.types.js';
|
|
1
|
+
export type { ConfigValidationResult, CoreQueueWorker, GroupConcurrencyConfig, JobGroup, JobOptions, PikkuJobConfig, PikkuWorkerConfig, PikkuQueue, QueueJob, QueueJobStatus, QueueService, QueueWorkers, QueueWorkersMeta, } from './queue.types.js';
|
|
2
2
|
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
3
3
|
export { validateWorkerConfig } from './validate-worker-config.js';
|
|
4
4
|
export type { QueueConfigMapping } from './validate-worker-config.js';
|
|
@@ -30,6 +30,34 @@ export interface PikkuWorkerConfig {
|
|
|
30
30
|
maxStalledCount?: number;
|
|
31
31
|
/** Condition to start processor at instance creation */
|
|
32
32
|
autorun?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Cap how many jobs of any one group ({@link JobOptions.group}) may run at
|
|
35
|
+
* once, so a single group can't occupy the whole worker. Lets one shared
|
|
36
|
+
* queue stay fair across producers instead of splitting it into one queue
|
|
37
|
+
* per producer — which multiplies polling cost on pull-based backends.
|
|
38
|
+
* Must not exceed {@link batchSize}.
|
|
39
|
+
*/
|
|
40
|
+
groupConcurrency?: number | GroupConcurrencyConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Per-group concurrency limits, optionally varied by tier so slow groups can
|
|
44
|
+
* be allowed more (or fewer) slots than the default.
|
|
45
|
+
*/
|
|
46
|
+
export interface GroupConcurrencyConfig {
|
|
47
|
+
/** Limit applied to any group without a matching tier */
|
|
48
|
+
default: number;
|
|
49
|
+
/** Per-tier overrides, keyed by {@link JobGroup.tier} */
|
|
50
|
+
tiers?: Record<string, number>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fairness key for a job. Jobs sharing an `id` count against the same
|
|
54
|
+
* {@link PikkuWorkerConfig.groupConcurrency} limit.
|
|
55
|
+
*/
|
|
56
|
+
export interface JobGroup {
|
|
57
|
+
/** Group this job belongs to (e.g. a workflow name) */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional tier selecting a per-tier limit */
|
|
60
|
+
tier?: string;
|
|
33
61
|
}
|
|
34
62
|
/**
|
|
35
63
|
* Configuration for individual jobs - how jobs behave
|
|
@@ -106,6 +134,8 @@ export interface JobOptions {
|
|
|
106
134
|
jobId?: string;
|
|
107
135
|
/** Pikku user ID to propagate to the queue worker for credential resolution */
|
|
108
136
|
pikkuUserId?: string;
|
|
137
|
+
/** Fairness key — counts against the worker's {@link PikkuWorkerConfig.groupConcurrency} */
|
|
138
|
+
group?: JobGroup;
|
|
109
139
|
}
|
|
110
140
|
/**
|
|
111
141
|
* Queue provider interface for job publishing operations
|
|
@@ -3,6 +3,14 @@ import type { FlatScope, ScopeDefinitions, ScopeDefinitionsMeta } from './scope.
|
|
|
3
3
|
* Flattens declared scope trees into the full list of grantable scope ids,
|
|
4
4
|
* depth-first. Every node is emitted, including intermediate ones.
|
|
5
5
|
*
|
|
6
|
+
* Ids are unique. A root may legitimately be declared more than once — an addon
|
|
7
|
+
* and its host app both contributing the same `admin` tree, say — and
|
|
8
|
+
* {@link validateAndBuildScopeDefinitionsMeta} already guarantees those
|
|
9
|
+
* declarations are identical, so the second one is redundant rather than
|
|
10
|
+
* conflicting. Collapsing it here keeps every consumer honest: codegen emits an
|
|
11
|
+
* object literal keyed by id (duplicates are a TypeScript error), and a
|
|
12
|
+
* ScopeService syncs one row per scope instead of re-writing the same one.
|
|
13
|
+
*
|
|
6
14
|
* Used by codegen to build the `ScopeId` union, and by a ScopeService to sync
|
|
7
15
|
* the declared set into its store.
|
|
8
16
|
*/
|
|
@@ -30,6 +30,14 @@ const flattenNodes = (nodes, prefix, out) => {
|
|
|
30
30
|
* Flattens declared scope trees into the full list of grantable scope ids,
|
|
31
31
|
* depth-first. Every node is emitted, including intermediate ones.
|
|
32
32
|
*
|
|
33
|
+
* Ids are unique. A root may legitimately be declared more than once — an addon
|
|
34
|
+
* and its host app both contributing the same `admin` tree, say — and
|
|
35
|
+
* {@link validateAndBuildScopeDefinitionsMeta} already guarantees those
|
|
36
|
+
* declarations are identical, so the second one is redundant rather than
|
|
37
|
+
* conflicting. Collapsing it here keeps every consumer honest: codegen emits an
|
|
38
|
+
* object literal keyed by id (duplicates are a TypeScript error), and a
|
|
39
|
+
* ScopeService syncs one row per scope instead of re-writing the same one.
|
|
40
|
+
*
|
|
33
41
|
* Used by codegen to build the `ScopeId` union, and by a ScopeService to sync
|
|
34
42
|
* the declared set into its store.
|
|
35
43
|
*/
|
|
@@ -39,7 +47,14 @@ export const flattenScopeDefinitions = (definitions) => {
|
|
|
39
47
|
out.push({ id: def.name, description: def.description });
|
|
40
48
|
flattenNodes(def.scopes, def.name, out);
|
|
41
49
|
}
|
|
42
|
-
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
return out.filter((scope) => {
|
|
52
|
+
if (seen.has(scope.id)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
seen.add(scope.id);
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
43
58
|
};
|
|
44
59
|
/**
|
|
45
60
|
* Validates declared scopes and keys them by name.
|
|
@@ -11,5 +11,5 @@ export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGr
|
|
|
11
11
|
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
12
12
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
13
13
|
export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
|
|
14
|
-
export type { WorkflowService, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
14
|
+
export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
15
15
|
export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SerializedError } from '../../types/core.types.js';
|
|
2
|
-
import type { ApprovalOutcome, PikkuScenarioWire, StepState, StepStatus, WorkflowPlannedStep, WorkflowRun, WorkflowRunMirror, WorkflowRunStatus, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from './workflow.types.js';
|
|
2
|
+
import type { ApprovalOutcome, PikkuScenarioWire, StepState, StepStatus, WorkflowPlannedStep, WorkflowRun, WorkflowRunMirror, WorkflowRunStatus, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, WorkflowQueueOptions, WorkflowStepOptions } from './workflow.types.js';
|
|
3
3
|
import type { WorkflowService } from '../../services/workflow-service.js';
|
|
4
4
|
import type { ScenarioActors } from '../../services/scenario-actors-service.js';
|
|
5
5
|
import { PikkuError } from '../../errors/error-handler.js';
|
|
6
6
|
import { type RunTimeline, type ReconstructedRunState } from './run-timeline.js';
|
|
7
|
-
import type { JobOptions } from '../queue/queue.types.js';
|
|
7
|
+
import type { GroupConcurrencyConfig, JobGroup, JobOptions } from '../queue/queue.types.js';
|
|
8
8
|
/**
|
|
9
9
|
* Default number of retries for a workflow step when none is specified. The
|
|
10
10
|
* workflow — not the queue — owns retry policy; a step inherits this unless it
|
|
@@ -98,10 +98,13 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
98
98
|
private runActors;
|
|
99
99
|
protected get logger(): import("../../services/logger.js").Logger;
|
|
100
100
|
protected mirror?: WorkflowRunMirror;
|
|
101
|
+
protected readonly queueStrategy: 'per-workflow' | 'shared-groups';
|
|
102
|
+
protected readonly queueConcurrency: number;
|
|
103
|
+
protected readonly queueGroupConcurrency: number | GroupConcurrencyConfig;
|
|
101
104
|
constructor(options?: {
|
|
102
105
|
wireQueues?: boolean;
|
|
103
106
|
mirror?: WorkflowRunMirror;
|
|
104
|
-
});
|
|
107
|
+
} & WorkflowQueueOptions);
|
|
105
108
|
private safeMirror;
|
|
106
109
|
/**
|
|
107
110
|
* Wire the queue-based orchestrator/step/sleeper workers.
|
|
@@ -519,4 +522,14 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
519
522
|
*/
|
|
520
523
|
protected getOrchestratorQueueName(workflowName?: string): string;
|
|
521
524
|
protected getStepWorkerQueueName(rpcName?: string): string;
|
|
525
|
+
/**
|
|
526
|
+
* Fairness key for a job on a shared queue. Under `'per-workflow'` the queue
|
|
527
|
+
* name already isolates workflows, so no group is needed — returning one
|
|
528
|
+
* anyway would cap a workflow inside its own dedicated queue.
|
|
529
|
+
*
|
|
530
|
+
* The tier repeats the id so a workflow can be given its own limit purely
|
|
531
|
+
* from config, with no per-workflow wiring; an unmatched tier falls back to
|
|
532
|
+
* the default limit.
|
|
533
|
+
*/
|
|
534
|
+
protected getJobGroup(id?: string): JobGroup | undefined;
|
|
522
535
|
}
|