@pikku/core 0.12.69 → 0.12.71
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 +422 -0
- package/LICENSE +21 -0
- package/README.md +34 -2
- package/dist/function/functions.types.d.ts +27 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/pikku-state.js +1 -0
- package/dist/services/http-scenario-actors.d.ts +12 -4
- package/dist/services/http-scenario-actors.js +47 -45
- package/dist/services/in-memory-queue-service.d.ts +6 -0
- package/dist/services/in-memory-queue-service.js +8 -1
- package/dist/services/in-memory-workflow-service.d.ts +3 -5
- package/dist/services/in-memory-workflow-service.js +10 -19
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +1 -0
- package/dist/services/meta-service.d.ts +5 -1
- package/dist/services/meta-service.js +44 -18
- package/dist/services/scenario-actors-service.d.ts +108 -2
- package/dist/services/scenario-actors-service.js +40 -1
- package/dist/services/workflow-service.d.ts +7 -5
- package/dist/types/core.types.d.ts +28 -3
- package/dist/types/state.types.d.ts +3 -1
- package/dist/wirings/actor-flow/actor-flow.types.d.ts +1 -1
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/actor-flow/run-conversation.d.ts +10 -10
- package/dist/wirings/actor-flow/run-conversation.js +27 -27
- package/dist/wirings/ai-agent/ai-agent-agui.js +0 -8
- package/dist/wirings/ai-agent/ai-agent-prepare.js +1 -2
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +0 -6
- package/dist/wirings/cli/command-parser.js +11 -1
- package/dist/wirings/rpc/rpc-runner.js +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +52 -3
- package/dist/wirings/workflow/feature.d.ts +28 -0
- package/dist/wirings/workflow/feature.js +57 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -2
- package/dist/wirings/workflow/graph/graph-validation.d.ts +0 -2
- package/dist/wirings/workflow/graph/graph-validation.js +0 -142
- package/dist/wirings/workflow/graph/index.d.ts +1 -1
- package/dist/wirings/workflow/graph/index.js +1 -1
- package/dist/wirings/workflow/index.d.ts +13 -3
- package/dist/wirings/workflow/index.js +15 -2
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +121 -0
- package/dist/wirings/workflow/pikku-scenario-service.js +419 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +170 -23
- package/dist/wirings/workflow/pikku-workflow-service.js +338 -297
- package/dist/wirings/workflow/scenario-cookie-jar.d.ts +29 -0
- package/dist/wirings/workflow/scenario-cookie-jar.js +51 -0
- package/dist/wirings/workflow/scenario-poll.d.ts +20 -0
- package/dist/wirings/workflow/scenario-poll.js +25 -0
- package/dist/wirings/workflow/scenario-prose.d.ts +38 -0
- package/dist/wirings/workflow/scenario-prose.js +45 -0
- package/dist/wirings/workflow/scenario-step-guards.d.ts +16 -0
- package/dist/wirings/workflow/scenario-step-guards.js +29 -0
- package/dist/wirings/workflow/scenario-step.types.d.ts +148 -0
- package/dist/wirings/workflow/scenario-step.types.js +1 -0
- package/dist/wirings/workflow/workflow.types.d.ts +82 -8
- package/package.json +3 -1
- package/src/function/functions.types.ts +32 -0
- package/src/index.ts +1 -0
- package/src/internal.ts +5 -1
- package/src/pikku-state.ts +1 -0
- package/src/services/http-scenario-actors.test.ts +85 -1
- package/src/services/http-scenario-actors.ts +65 -51
- package/src/services/in-memory-queue-service.test.ts +66 -1
- package/src/services/in-memory-queue-service.ts +13 -2
- package/src/services/in-memory-workflow-service.ts +12 -25
- package/src/services/index.ts +5 -0
- package/src/services/meta-service.test.ts +79 -0
- package/src/services/meta-service.ts +61 -26
- package/src/services/scenario-actors-service.ts +157 -2
- package/src/services/workflow-service.ts +7 -4
- package/src/types/core.types.ts +34 -2
- package/src/types/state.types.ts +3 -0
- package/src/wirings/actor-flow/actor-flow.types.ts +1 -1
- package/src/wirings/actor-flow/index.ts +1 -1
- package/src/wirings/actor-flow/run-conversation.test.ts +12 -6
- package/src/wirings/actor-flow/run-conversation.ts +36 -41
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +0 -16
- package/src/wirings/ai-agent/ai-agent-agui.ts +0 -9
- package/src/wirings/ai-agent/ai-agent-prepare.ts +1 -2
- package/src/wirings/ai-agent/ai-agent.types.ts +0 -7
- package/src/wirings/cli/command-parser.test.ts +60 -0
- package/src/wirings/cli/command-parser.ts +12 -1
- package/src/wirings/rpc/rpc-runner.test.ts +28 -5
- package/src/wirings/rpc/rpc-runner.ts +1 -1
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +86 -2
- package/src/wirings/workflow/feature.test.ts +131 -0
- package/src/wirings/workflow/feature.ts +78 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -2
- package/src/wirings/workflow/graph/graph-validation.test.ts +1 -144
- package/src/wirings/workflow/graph/graph-validation.ts +0 -196
- package/src/wirings/workflow/graph/index.ts +1 -5
- package/src/wirings/workflow/index.ts +73 -6
- package/src/wirings/workflow/pikku-scenario-service.ts +682 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +55 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +572 -419
- package/src/wirings/workflow/scenario-cookie-jar.test.ts +108 -0
- package/src/wirings/workflow/scenario-cookie-jar.ts +65 -0
- package/src/wirings/workflow/scenario-expectations.test.ts +153 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +212 -0
- package/src/wirings/workflow/scenario-poll.test.ts +66 -0
- package/src/wirings/workflow/scenario-poll.ts +36 -0
- package/src/wirings/workflow/scenario-prose.test.ts +152 -0
- package/src/wirings/workflow/scenario-prose.ts +79 -0
- package/src/wirings/workflow/scenario-service.test.ts +155 -0
- package/src/wirings/workflow/scenario-step-guards.ts +43 -0
- package/src/wirings/workflow/scenario-step.test.ts +442 -9
- package/src/wirings/workflow/scenario-step.types.ts +157 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-dispatch-payload.test.ts +59 -0
- package/src/wirings/workflow/workflow-mirror.test.ts +178 -0
- package/src/wirings/workflow/workflow-replay-snapshot.test.ts +139 -0
- package/src/wirings/workflow/workflow-run-context.test.ts +177 -0
- package/src/wirings/workflow/workflow-run-polling.test.ts +132 -0
- package/src/wirings/workflow/workflow-step-ordinal.test.ts +4 -4
- package/src/wirings/workflow/workflow.types.ts +99 -5
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,425 @@
|
|
|
1
|
+
## 0.12.71
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 8a2c993: Make the workflow service cheaper to run, and fix two ways it lost state.
|
|
6
|
+
|
|
7
|
+
The SQL workflow tables had no indexes at all, so every step read, every
|
|
8
|
+
history walk and every orchestrator tick was a sequential scan; five indexes
|
|
9
|
+
now cover the columns the engine actually queries by. A replay used to ask for
|
|
10
|
+
each step's row individually — O(N) reads per replay, O(N^2) over a run — and
|
|
11
|
+
now takes one read of the run's steps and serves the walk from it. A step
|
|
12
|
+
transition wrote the step row and its history row as two separate statements,
|
|
13
|
+
so a crash between them left a step saying `succeeded` whose history still said
|
|
14
|
+
`running`; both halves are now one transaction, and the history row is found by
|
|
15
|
+
attempt number rather than by sorting on `created_at`, which two attempts can
|
|
16
|
+
share. Resolving a dynamic workflow read and parsed every AI-generated workflow
|
|
17
|
+
in the deployment to `.find()` one by name; it is a point lookup now.
|
|
18
|
+
|
|
19
|
+
Waiting on a run no longer polls at a fixed interval. `pollIntervalMs` became a
|
|
20
|
+
ceiling rather than a cadence: polling starts at 10ms and widens towards it, so
|
|
21
|
+
a workflow that finishes in milliseconds is no longer held for a full second,
|
|
22
|
+
and a long-running one is not read at full rate for its whole life.
|
|
23
|
+
|
|
24
|
+
Two backend-specific defects: Redis kept a run's state as one JSON blob and
|
|
25
|
+
read-modified-wrote it, so parallel branches setting different variables
|
|
26
|
+
overwrote each other — state is a field per variable now, with the old blob
|
|
27
|
+
still read underneath so runs in flight keep what they had. Mongo's
|
|
28
|
+
`setStepScheduled` never wrote history, leaving a queued step reading as never
|
|
29
|
+
dispatched.
|
|
30
|
+
|
|
31
|
+
Also: dispatch no longer JSON round-trips every step payload before handing it
|
|
32
|
+
to a queue that serialises it anyway — the in-process dev queue, which is the
|
|
33
|
+
only one that was relying on it, does it itself now.
|
|
34
|
+
|
|
35
|
+
Two more defects. A transition whose step had no live attempt wrote its status
|
|
36
|
+
to the step row and silently nothing to history — the exact divergence the
|
|
37
|
+
transaction exists to prevent — and now repairs the step and writes the
|
|
38
|
+
missing row. And resolving a dynamic workflow was non-deterministic on all
|
|
39
|
+
three backends: a name can hold several active versions, and none of them
|
|
40
|
+
ordered the candidates, so which one ran could change between two calls
|
|
41
|
+
reading identical data. The newest version wins, with the graph hash breaking
|
|
42
|
+
a tie.
|
|
43
|
+
|
|
44
|
+
The two attempt columns and the five indexes are declared in the workflow
|
|
45
|
+
schema, so a fresh database gets them at boot. An existing one gets them from
|
|
46
|
+
a migration — `pikku db generate` writes the declaration down — rather than
|
|
47
|
+
from DDL issued at boot.
|
|
48
|
+
|
|
49
|
+
- a261006: **Breaking:** removed dynamic workflows — runtime-defined workflow graphs stored in the database and resolved by name instead of by codegen.
|
|
50
|
+
|
|
51
|
+
The feature was already half-gone. Its authoring surface (`createAgentWorkflow`, `saveAgentWorkflow`, `listAgentWorkflows`, `executeAgentWorkflow`, and the AI-agent instruction builder) was deleted in April 2026 along with its entire e2e suite, and nothing has written a dynamic workflow since. What remained could not execute one either: `executeAgentWorkflow` gated on `pikkuState('workflows', 'meta')`, which only codegen ever populates, so a graph that existed solely in the database was never findable. The two backend families had also drifted onto different `source` sentinels (`'ai-agent'` vs `'dynamic-workflow'`), and the two Redis implementations disagreed on key escaping — so at least one of them matched nothing. Rather than keep shipping plumbing for a path no caller could complete, it is removed until it can be reintroduced deliberately.
|
|
52
|
+
|
|
53
|
+
Removed:
|
|
54
|
+
- `getAIGeneratedWorkflows` from `WorkflowService` and `WorkflowRunService`, and from every backend (in-memory, Redis, MongoDB, Kysely, and the Cloudflare Durable Object service and client — the last two were already a `return []` stub and a rejection).
|
|
55
|
+
- The database-lookup fallbacks in `startWorkflow` and `runWorkflowJob` that resolved a workflow name against stored graphs when static meta had no match.
|
|
56
|
+
- `'dynamic-workflow'` from the `WorkflowRuntimeMeta['source']` union.
|
|
57
|
+
- `validateWorkflowWiring` and `computeEntryNodeIds` from `@pikku/core/workflow`. These validated AI-authored graphs and had no callers in core; the inspector keeps its own private entry-node computation for static graph wiring, which is unaffected.
|
|
58
|
+
- The `workflow-created` AI stream event and its AG-UI `pikku:workflow-created` custom event. Its only emitter went with the April deletion, so it could never fire.
|
|
59
|
+
- The console's `console:getAIWorkflows` RPC, the `useAIWorkflows` hook, the "Dynamic" workflow filter and badge, and the trigger-schema scraper that derived an input form from a stored graph's `$ref` bindings.
|
|
60
|
+
|
|
61
|
+
Kept, because static graph workflows depend on them and this is not a change to versioning:
|
|
62
|
+
- `upsertWorkflowVersion`, `getWorkflowVersion`, `updateWorkflowVersionStatus`, and the `workflowVersions` storage in every backend. These back version-mismatch replay: when a deployed graph's hash changes, in-flight runs continue against the exact graph they started on. No schema migration is needed — the table, its columns, and its `(workflowName, graphHash)` upsert key are unchanged.
|
|
63
|
+
- `generateMermaidDiagram`, which renders any workflow graph and is not specific to dynamic ones.
|
|
64
|
+
|
|
65
|
+
Static `pikkuWorkflowGraph` and DSL workflows are entirely unaffected: they resolve from codegen'd meta, which was always the only path that worked.
|
|
66
|
+
|
|
67
|
+
To revive this post-MVP, the deleted authoring code is recoverable in full — its prompt engineering (a compact tool table upfront, full schemas with flattened dotted output paths returned only after a validation failure) is worth reading before rewriting:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
git show f52f3308b^:packages/core/src/wirings/ai-agent/agent-dynamic-workflow.ts
|
|
71
|
+
git show f52f3308b^:packages/core/src/wirings/workflow/graph/graph-validation.ts
|
|
72
|
+
git show f52f3308b --stat # the April removal, incl. the three e2e feature files
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Note that reviving it needs more than restoring those files: the queued-step path (`executeWorkflowStep`), `onError` compensation, and sub-workflow resolution all read static meta only and would need a fallback for a graph that exists solely in the database.
|
|
76
|
+
|
|
77
|
+
- 09973b9: Scenarios, features and steps no longer reach a deployment.
|
|
78
|
+
|
|
79
|
+
Steps were already held back from the app bootstrap, so a deployed server never imported a step body. Everything _about_ a scenario still travelled with the application: a `pikkuScenario(...)` is a function, so its name, schemas and hashes sat in the app function meta; the schemas it and its steps validate against sat in the app's `register.gen.ts` — on one project 458 of the 582 registered schemas belonged to tests; its name sat in the internal RPC meta; and because a scenario is _also_ a workflow, the inspector synthesised a `wf-orchestrator-<scenario>` queue worker for each one. The deploy analyzer, which reads inspector state rather than the partitioned codegen output, then read all of it back as application code: a unit per scenario, a `WorkflowDefinition` per scenario, and a real queue per scenario. A 13-scenario suite turned into 13 production queues named after tests, waiting for a provider to create them.
|
|
80
|
+
|
|
81
|
+
The existing scenario/app partition is now applied everywhere it was missing. `FunctionRuntimeMeta` gains a `scenario` marker (the counterpart of `scenarioStep`) so a scenario body is recognisable without walking the workflow graph; scenario bodies join their steps on the scenario side of the function-meta and registration split; schemas only a scenario or step needs are written and registered under `.pikku/scenarios/schemas/` and imported by the scenario bootstrap alone; scenario names are dropped from the internal RPC meta; no orchestrator queue worker is synthesised for a scenario; and the deploy analyzer drops both scenario functions and scenario workflows before it decides what a deployment contains.
|
|
82
|
+
|
|
83
|
+
The MCP metas are keyed by wiring rather than by function, so a scenario wired as an MCP tool, resource or prompt was the one id that still reached the manifest after the function and workflow filters — as an endpoint on the gateway plus a gateway dependency on a unit that was never emitted. Those ids are now filtered too.
|
|
84
|
+
|
|
85
|
+
`scenarioSchemaDirectory` is rejected when it resolves to the same directory as `schemaDirectory`. A schema write owns its directory — it emits `register.gen.ts` and prunes every schema file its own required-set does not name — so sharing one would replace the application register with the scenario-only one and delete the app's schema files, which nothing downstream can detect.
|
|
86
|
+
|
|
87
|
+
Nothing changes for `pikku scenario run` — the scenario bootstrap still registers every scenario, feature, step, meta and schema. What changes is that a bundle stops carrying them.
|
|
88
|
+
|
|
89
|
+
## 0.12.70
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- 539ee0b: Give browser scenario steps a shared way to name an element: `browser.locate(selector)`. `TestIdSelector` (test id, `prefix`, `where` data attributes, `containing` text, `within` scope) is declared in core so a step's input stays structural, and `@pikku/playwright` resolves it against the page — applying `:visible` by default, since Mantine layouts routinely mount a hidden copy of a control.
|
|
94
|
+
- a1a6816: Let a scenario actor declare the scopes and roles it holds
|
|
95
|
+
|
|
96
|
+
`scenarios.actors.<name>` in `pikku.config.json` now takes optional `scopes` and
|
|
97
|
+
`roles`, carried through to `scenarioActorConfigs`. Pikku never applies them —
|
|
98
|
+
which scope store exists and which roles have been created is the app's own — so
|
|
99
|
+
the generated actors file also exports `scenarioActorList`, the registry widened
|
|
100
|
+
to `ScenarioActorConfig`, which is what a seed needs to read an optional field
|
|
101
|
+
off every actor.
|
|
102
|
+
|
|
103
|
+
- dc3e11e: Generate scenarios, features and scenario steps into `.pikku/scenarios/` with their own bootstrap, so a deployed server never imports a step body.
|
|
104
|
+
|
|
105
|
+
A `pikkuScenarioStep` body is an ordinary pikku function and a `pikkuScenario` is an ordinary workflow, so codegen wired both into `pikku-functions.gen.ts` and `pikku-workflow-wirings.gen.ts` — the files every server's bootstrap imports. A project's steps, and whatever a step imports (Playwright, fixtures, assertion helpers), therefore shipped in production. The e2e project's app bootstrap pulled in 20 step modules and 7 scenarios this way.
|
|
106
|
+
|
|
107
|
+
Codegen now partitions on the flags that already existed — `scenarioStep: true` in function meta and `source: 'scenario'` in workflow meta — and emits:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
.pikku/scenarios/pikku-scenario-functions.gen.ts addFunction for every step
|
|
111
|
+
.pikku/scenarios/pikku-scenario-functions-meta.gen.ts step meta, merged onto the app's
|
|
112
|
+
.pikku/scenarios/pikku-scenario-wirings.gen.ts addWorkflow + addFeature
|
|
113
|
+
.pikku/scenarios/pikku-scenario-wirings-meta.gen.ts scenario meta, merged onto the app's
|
|
114
|
+
.pikku/scenarios/meta/*.gen.json per-scenario graph meta
|
|
115
|
+
.pikku/pikku-bootstrap-scenarios.gen.ts imports the app bootstrap, then the above
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`pikku scenario run` is the only thing that loads `pikku-bootstrap-scenarios.gen.ts`; `pikku dev` and `pikku serve` keep loading `pikku-bootstrap.gen.ts`. Bundling the e2e app bootstrap now resolves **zero** scenario or step modules.
|
|
119
|
+
|
|
120
|
+
Both meta files _merge_ rather than replace — `pikkuState(…, 'meta', value)` is a wholesale setter — and each imports the app meta file it merges onto, so the ordering holds regardless of entry point. Features move wholesale to the scenario side: `serializeWorkflowRegistration` no longer emits `addFeature` at all.
|
|
121
|
+
|
|
122
|
+
`LocalMetaService` reads the new locations alongside the old ones (`scenarios/meta` in `getWorkflowMeta()`, `pikku-scenario-functions-meta.gen.json` in `getFunctionsMeta()`), so the console's scenario list and function meta are unchanged — those read from disk, not from the bundle. Scenario meta left behind in `workflow/meta` by an earlier CLI is removed on the next codegen, so it cannot be served as a stale duplicate.
|
|
123
|
+
|
|
124
|
+
**Not included:** a scenario step's input/output JSON schemas still register in the app's `schemas/register.gen.ts`. They are inert data rather than a module edge, and splitting them safely means deriving "required only by a step" across every other schema consumer — a wrong answer there unregisters a schema the server validates against.
|
|
125
|
+
|
|
126
|
+
- 24da616: `createCookieJar` is now the one place a scenario keeps a session. `HttpScenarioActor` is built on it rather than tracking a single cookie string of its own, which means it follows a cookie the target rotates on any response — previously only the sign-in response was read, so a rotated session cookie was dropped and the only recovery was the 401 re-login.
|
|
127
|
+
|
|
128
|
+
It is exported from `@pikku/core/workflow` because a step driving a real auth client SDK needs the same thing an actor does.
|
|
129
|
+
|
|
130
|
+
Two fixes to what the jar holds. A `Set-Cookie` with an empty value is how a target **deletes** a cookie, so the name is now dropped rather than held with a value that says it is gone; and a `cookie` header the caller already set is merged with the jar's rather than silently replaced, which matters when the jar is handed to an SDK as its `customFetchImpl`.
|
|
131
|
+
|
|
132
|
+
`HttpScenarioActor` no longer reads `jar.empty` to decide whether it is signed in — it tracks the sign-in. `empty` is a fact about the jar, not about the session: a target that sets a CSRF or locale cookie before anyone signs in filled it, which made the actor skip its first `login()` and send that call unauthenticated, and made the "sign-in returned no session cookie" guard pass without a session ever being established. That guard now checks the sign-in response's own `Set-Cookie`.
|
|
133
|
+
|
|
134
|
+
- 04bfe3f: Scenarios get a fresh browser each time, a failure report worth reading, and a formatter that owns the output.
|
|
135
|
+
|
|
136
|
+
Three changes that only make sense together.
|
|
137
|
+
|
|
138
|
+
**A scenario no longer inherits the last one's browser.** `ScenarioBrowserProvider` gains an optional `reset()`, called between scenarios: every actor's context — cookies, storage, open pages, in-page listeners — is discarded, while the browser itself stays up. Before this, one browser context per actor lived for the whole run, so scenario 2 started signed in as whoever scenario 1 left behind. The boundary is the context rather than the browser because that is where the isolation actually lives, and re-opening one costs milliseconds instead of a relaunch. `reset()` runs _before_ each scenario, so the last one's window is still there to look at when a headed run stops.
|
|
139
|
+
|
|
140
|
+
**A failure says what happened.** The runner reported `run.error.message` and nothing else — which for a browser step is "Timed out waiting for selector" with every useful detail removed. `ScenarioBrowserProvider` gains an optional `captureFailure(label)`, and the driver's page diagnostics (console errors, uncaught exceptions, failed requests, 4xx/5xx API responses) — collected all along and until now thrown away — are reported under the failing step, with a screenshot written to `<outDir>/scenario-failures`:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
✗ failed at: Then the admin sees the edit button
|
|
144
|
+
Timed out waiting for selector button[title="Edit function"]
|
|
145
|
+
browser (admin): http://localhost:4077/console/functions
|
|
146
|
+
console: TypeError: x is not a function
|
|
147
|
+
api: 500 /api/rpc/console:readFunctionSource
|
|
148
|
+
screenshot: .pikku/scenario-failures/code-editor-admin.png
|
|
149
|
+
at readsFunctionSource (…/code-editor.steps.ts:71:5)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Stacks are trimmed to the project's own frames, because the framework's are never the bug; `--trace` keeps all of them. An expected failure (a `PikkuError`) prints its message alone — a stack adds nothing to a deliberate one.
|
|
153
|
+
|
|
154
|
+
**A failed scenario now shows its ladder at all.** It did not before, for a reason that took a live run to find: an inline run that fails throws out of `startWorkflow` instead of returning `{ runId }`, so the runner never learned the id of the one run whose steps were worth reading — and fell back to the run error alone. `startWorkflow` gains an `onRunCreated` option, called the moment the run exists, which is the only point guaranteed to happen whether the run goes on to pass, fail or suspend. A failure now prints every step that ran, marks the one that didn't, and names it in `✗ failed at:`.
|
|
155
|
+
|
|
156
|
+
A browser timeout's `message` carries its entire call log, so the summary line and the ladder row take its first line only — the block underneath still prints all of it. Three copies of the same paragraph, one of them wrapping mid-table, is not a report.
|
|
157
|
+
|
|
158
|
+
**All of that output now goes through one formatter.** `formatScenarioReport(report)` takes a plain serialisable report — no Maps, no meta handles — and returns the lines to print, the way `deploy plan` already works. Joining a run to the prose that declared it stays in `scenario-ladder.ts`, where the inspector state is; laying it out is the formatter's job. A second reporter (JSON, JUnit) is now a function rather than an excavation.
|
|
159
|
+
|
|
160
|
+
**Browser drivers are pluggable.** `scenarios.browserDriver` in `pikku.config.json` names the package that drives `browser: true` steps; it defaults to `@pikku/playwright` but nothing requires it. A driver is any package exporting `createScenarioBrowserProvider(options)` — or a provider class — returning an object with `sessionFor()` and `close()`. `reset()` and `captureFailure()` are optional, so a driver written against the earlier interface keeps working: it simply offers no isolation and no diagnostics. A package that is neither says so, instead of failing later in a way nobody can read.
|
|
161
|
+
|
|
162
|
+
- 5962e51: Add `pikkuFeature`, a grouping primitive for scenarios.
|
|
163
|
+
|
|
164
|
+
A feature groups scenarios the way gherkin's `Feature:` groups `Scenario:`, and gets `Examples:` for free as an ordinary loop:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
export const credentialFeature = pikkuFeature({
|
|
168
|
+
name: 'Credential API',
|
|
169
|
+
tags: ['credential'],
|
|
170
|
+
before: startsMockOAuthServer,
|
|
171
|
+
after: stopsMockOAuthServer,
|
|
172
|
+
scenarios: [
|
|
173
|
+
credentialLazyLoadScenario,
|
|
174
|
+
...['stripe', 'google', 'hmac-key'].map((name) => ({
|
|
175
|
+
scenario: credentialRoundTripScenario,
|
|
176
|
+
data: { name },
|
|
177
|
+
})),
|
|
178
|
+
],
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- Scenarios are referenced by **imported identifier**, not by string name, so a renamed or deleted scenario is a compile error rather than a silent skip. A `{ scenario, data }` entry's `data` is typed against that scenario's own input.
|
|
183
|
+
- Feature hooks run **once around the whole group** (`before → a → b → c → after`), not per scenario, and `after` runs in a `finally`. Per-scenario setup stays the scenario's own `before`; gherkin's `Background:` is deliberately not expressible.
|
|
184
|
+
- A scenario's effective tags are its own plus its feature's, so `--tags credential` selects through the feature.
|
|
185
|
+
- New `--features` selector on `pikku scenario run`, and `pikku scenario list` now prints features with their scenarios indented. Every filter narrows the same plan, so narrowing a feature to two of its five scenarios still runs its hooks exactly once around those two.
|
|
186
|
+
- The **feature is the run unit**: `--flows` on a scenario whose every feature entry carries `data` errors and names the features containing it, because the feature is what supplies that data. A scenario referenced bare anywhere, or in no feature at all, still runs standalone.
|
|
187
|
+
|
|
188
|
+
`pikkuFeature` infers its scenario list with a `const` generic, so `CoreFeature['scenarios']` is `readonly` — otherwise the emitted `addFeature(id, feature)` call does not typecheck.
|
|
189
|
+
|
|
190
|
+
Membership is resolved at runtime by object identity — `pikkuScenario` returns its config verbatim, so a feature holds the very object that was registered. That is what lets the scenario list be built by a loop, which no static analysis could enumerate. It also means a scenario constructed inline inside a feature is never registered, and is reported as unresolved rather than silently running as something else.
|
|
191
|
+
|
|
192
|
+
- 5962e51: Add `before` / `after` hooks to `pikkuScenario`, and make an unextractable scenario a hard error.
|
|
193
|
+
|
|
194
|
+
A scenario config now takes `before` and `after`. Both have the same signature as `func` — `(services, data, wire)` — with the return value discarded, so there is no new type to learn and a hook reaches the app the same way the body does, through `wire.actors`:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
export const credentialScenario = pikkuScenario({
|
|
198
|
+
title: 'A credential is loaded on first use',
|
|
199
|
+
tags: ['scenario', 'credential'],
|
|
200
|
+
before: resetsCredentials,
|
|
201
|
+
after: removesInstalledAddon,
|
|
202
|
+
func: async (services, data, { scenario, actors }) => { ... },
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `before` throwing skips the body and fails the run, but `after` still runs.
|
|
207
|
+
- `after` always runs, in a `finally`. Throwing fails a run that would otherwise have passed; on an already-failed run it attaches as the `cause` and never replaces the original error.
|
|
208
|
+
- Neither runs when the run is suspended or waiting — teardown only fires at a terminal outcome.
|
|
209
|
+
- Hooks are not ladder rows: the runner records nothing for them, and a failure is labelled by phase via the new `ScenarioHookError`.
|
|
210
|
+
- Hooks are scenario-only. A `before`/`after` on a `pikkuWorkflowFunc` never runs — a workflow is durable and resumable, so a callback that reran on every replay would have no honest meaning.
|
|
211
|
+
|
|
212
|
+
Two fixes that scenarios needed to be safe to write:
|
|
213
|
+
- A closure in a complex-workflow or scenario body is no longer held to the DSL statement whitelist. A single `try`/`catch` inside any callback previously failed extraction, and the fallback path understands `do`/`sleep` but not `step`/`given`/`when`/`then` — so the scenario registered with **zero steps** and passed vacuously, with no diagnostic. Plain DSL workflows still descend into callbacks, which is what validates fanout bodies.
|
|
214
|
+
- New `PKU679`: a scenario that fails DSL extraction is now a critical error and refuses to register, instead of silently registering empty. A scenario that declares no input parameter at all is legitimate and still extracts.
|
|
215
|
+
|
|
216
|
+
- cd6453c: `ScenarioHttpResponse` is what an actor's transport answers with.
|
|
217
|
+
|
|
218
|
+
Nothing about the shape (status, ok, body) is RPC-specific — it is an HTTP response with its body already drained — so it is not named for RPC, and it carries `serialized`, the body as text. `readScenarioHttpResponse(res)` is exported so a step that has to reach past `invokeRaw` for a non-RPC route drains the response the same way instead of inventing its own record, and `invoke`'s refusal error quotes the raw text, so an HTML or plain-text error body says what went wrong instead of `"undefined"`.
|
|
219
|
+
|
|
220
|
+
Both are generic in the body — `readScenarioHttpResponse<{ runId?: string }>(res)` — defaulting to `unknown`. A body that will not parse as JSON is carried as its raw text rather than dropped.
|
|
221
|
+
|
|
222
|
+
The whole scenario-actor surface is new and unreleased, so there is nothing here to migrate from.
|
|
223
|
+
|
|
224
|
+
- a436645: Redesign the console's scenarios screen as living documentation of a project's BDD features.
|
|
225
|
+
|
|
226
|
+
The inspector now statically extracts `pikkuFeature` declarations — name, description, tags, the scenarios each one groups (including `{ scenario, data }` examples), and whether it declares `before`/`after` — and the CLI writes them to `<outDir>/scenarios/features.gen.json`, which `MetaService.getFeaturesMeta()` reads and the console addon returns from `getAllMeta`.
|
|
227
|
+
|
|
228
|
+
The scenarios page reads that back as a document: features on the left, and on the right the selected feature's scenarios, each rendered as the given/when/then ladder of prose its author actually wrote, with repeats shown as `for each x in xs`, `Examples:` tables for parameterised entries, skip reasons stated rather than hidden, and each scenario's cast of personas inline. The Flows/Personas segmented control is gone; tags filter the document the same way `pikku scenario run --tags` filters a run.
|
|
229
|
+
|
|
230
|
+
- 46cf63e: Scenario personas — the KIND of person, separate from the body that signs in
|
|
231
|
+
|
|
232
|
+
`scenarios.actors` conflated two things: who a kind of person is, and which
|
|
233
|
+
synthetic user a step runs as. That works until a scenario needs two of the same
|
|
234
|
+
kind — tenant isolation, peer sharing, a member hitting another member's row —
|
|
235
|
+
at which point the registry grows two near-identical entries and neither says
|
|
236
|
+
they are the same kind of person.
|
|
237
|
+
|
|
238
|
+
`scenarios.personas` now declares the kinds:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
"scenarios": {
|
|
242
|
+
"personas": {
|
|
243
|
+
"owner": { "description": "Owns their own entries", "primary": true },
|
|
244
|
+
"viewer": { "description": "Someone the owner shares with", "proficiency": "casual" },
|
|
245
|
+
"reminders": { "description": "The app sending reminders", "kind": "system" }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
A persona carries only what is true of that kind of person for the app's whole
|
|
251
|
+
lifetime — `description`, `primary` (whose experience the product is), `kind`
|
|
252
|
+
(`person` or `system`), `proficiency` (`casual` or `power`). What someone is
|
|
253
|
+
trying to get done, and the circumstances they are doing it in, belong to the
|
|
254
|
+
scenario, not to them.
|
|
255
|
+
|
|
256
|
+
Actors are materialised from personas, so the common case — one body per kind —
|
|
257
|
+
needs no `actors` block at all. Declare an actor by hand only for a second body
|
|
258
|
+
of one persona:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
"actors": { "ownerB": { "persona": "owner", "email": "owner-b@actors.local" } }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
A `system` persona mints no actor: there is nobody to sign in.
|
|
265
|
+
|
|
266
|
+
Resolution is shared by codegen and `pikku scenario run` (previously three
|
|
267
|
+
independent reads of `config.scenarios.actors`), so the generated
|
|
268
|
+
`scenarioActorConfigs` — and therefore the `ScenarioActorName` union that types
|
|
269
|
+
`wire.scenarioStep.actor` — always matches the registry a run builds. Two actors
|
|
270
|
+
sharing an email is now an error rather than a silently-shared user row, which
|
|
271
|
+
is exactly the bug a second body exists to catch.
|
|
272
|
+
|
|
273
|
+
Fully backwards compatible: an actor with no `persona` resolves as its own
|
|
274
|
+
implicit persona, and a project with no `personas` block is untouched.
|
|
275
|
+
|
|
276
|
+
Because "persona" now names a config entity, actor-flow no longer uses it for
|
|
277
|
+
"the actor config the LLM plays": `RunConversationParams.persona`/`personaName`
|
|
278
|
+
are now `actor`/`actorName`, and the exported `PersonaLLM` type is `ActorLLM`.
|
|
279
|
+
The `'in-persona'` approval policy value is unchanged — it is the English idiom
|
|
280
|
+
("stay in character"), not a reference to a declared persona.
|
|
281
|
+
|
|
282
|
+
- 9e666bc: `postScenarioJson(url, { body, headers })` — one way for a scenario step to POST JSON at a route and keep what came back.
|
|
283
|
+
|
|
284
|
+
Every step that reaches past an actor was writing this by hand, and the copies had drifted. Two of them answered `response.json()`, which discards the status and **throws outright** when the target answers an empty body or an HTML error page — so a refusal, which is the expected outcome of a permissions scenario, surfaced as a parse error instead of as data. It returns a `ScenarioHttpResponse`, never throws on a non-2xx, and takes an optional `fetch` so a call that has to keep a session can be sent through a `ScenarioCookieJar`.
|
|
285
|
+
|
|
286
|
+
`ScenarioHttpResponse` and `readScenarioHttpResponse` are now generic in the body: `postScenarioJson<{ runId?: string }>(…)` types `body` at the call site instead of casting at every use. The default is still `unknown`, so nothing that omits the parameter changes.
|
|
287
|
+
|
|
288
|
+
`body`'s doc now says what it always did: a body that will not parse as JSON is carried as its raw text, not dropped.
|
|
289
|
+
|
|
290
|
+
- 1c841d8: Move the scenario engine off `PikkuWorkflowService` onto a `PikkuScenarioService` the runner constructs, so no production bundle carries it.
|
|
291
|
+
|
|
292
|
+
Scenario support was built as members of `PikkuWorkflowService` — the class every Pikku server instantiates. A bundler drops an unused _module_, never an unused class _member_, so every deployed app was shipping the step runner, the lifecycle-hook runner, the actor registry, the browser-provider hooks and the `expectEventually`/`expectError`/`expectService` assertion wire, whether or not it had a single scenario. `resolveScenarioActors` pulled the HTTP actor client — and the AI persona conversation loop behind it — in with them.
|
|
293
|
+
|
|
294
|
+
All of it now lives in `PikkuScenarioService`, exported from a new `@pikku/core/scenario` entry point and reached only by `pikku scenario run`:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { createScenarioRunner } from '@pikku/core/scenario'
|
|
298
|
+
|
|
299
|
+
const { workflowService, scenarioService } = createScenarioRunner()
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Measured with esbuild against `InMemoryWorkflowService`: the production bundle drops 35 KB and every `sign-in/actor`, `runConversation`, `expectEventually` and `ScenarioHookError` occurrence, along with the scheduler runner that `wire.runScheduledTask` pulled in. The one remaining `scenarioStep` reference in a production bundle is the RPC guard that refuses to expose a step over `/rpc` — a security check, not scenario machinery.
|
|
303
|
+
|
|
304
|
+
`PikkuScenarioService` is **not** a workflow service. A scenario is not a different kind of run — it is the same durable run with a step vocabulary on top — so it is installed onto one rather than subclassing it. `PikkuWorkflowService` gains a single `setRunExtension(create)` slot, and calls the installed `WorkflowRunExtension` at six points: `attachRunContext`, `detachRunContext`, `decorateRunWire`, `decorateWorkflowWire`, `onBeforeRunFunc`, `onAfterRunFunc`. Nothing on that interface names scenarios.
|
|
305
|
+
|
|
306
|
+
The extension is built from a `WorkflowRunEngine` handle the service hands it — `inlineStep`, `updateRunStatus`, `onChildWorkflowFailed`, `verifyStepName` — which is what lets a scenario record a durable step without any of those becoming public API on the service every production app instantiates.
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const workflowService = new InMemoryWorkflowService()
|
|
310
|
+
const scenarioService = workflowService.setRunExtension(
|
|
311
|
+
(engine) => new PikkuScenarioService(engine)
|
|
312
|
+
)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`{ actor }` on a workflow step is deliberately **not** part of the move: `scenario.do(name, rpc, data, { actor })` dispatches through the base wire's `do`, so the actor branch stays in `rpcStep`.
|
|
316
|
+
|
|
317
|
+
**Behaviour change:** a scenario started on a _server_ rather than through the runner (the console can start any registered workflow by name) no longer resolves actors or runs `before`/`after` hooks — a server's workflow service is not a scenario service. Run scenarios with `pikku scenario run`.
|
|
318
|
+
|
|
319
|
+
- 47478a4: Let a scenario declare why it is held out of a default run.
|
|
320
|
+
|
|
321
|
+
`pikkuScenario({ skip: 'why' })` keeps the scenario in the plan and reports it as `SKIP <name> (<reason>)` on the ladder, instead of the alternatives available until now: deleting it, commenting it out, or leaving it red. Naming it directly with `--flows` clears the quarantine and runs it; selecting the feature it belongs to does not, because a feature is a group and running the group should not silently drag a quarantined member in.
|
|
322
|
+
|
|
323
|
+
The run report's `skipped` list now carries a reason per scenario rather than assuming `--no-browser`, so a browser scenario held back on a machine with no browser reads differently from one the project quarantined itself.
|
|
324
|
+
|
|
325
|
+
`@pikku/console` gains a test id on the addon detail page's Setup tab, which was previously only reachable through its translated label.
|
|
326
|
+
|
|
327
|
+
- 9e666bc: Settle what a scenario step imports from `@pikku/core/workflow`.
|
|
328
|
+
|
|
329
|
+
Core carries what the scenario runtime contract needs — the step wire, the browser-driver interface, the transport's response shape — and what core itself implements. Two helpers that had been promoted alongside them are neither, and are not exported: `describeValue`, a one-line formatter for an assertion message, and `readScenarioSseEvents`, a general SSE reader with a scenario-flavoured name. Both are a test suite's own vocabulary, with no consumer inside the framework; a project that wants them owns them, at three and twenty lines. Neither shipped, so nothing to migrate.
|
|
330
|
+
|
|
331
|
+
What stays, and why:
|
|
332
|
+
- `requireActor(scenarioStep)` / `requireScenarioEnv(scenarioStep)` — narrow the optional halves of the step wire, naming the step and what to pass.
|
|
333
|
+
- `pollUntil(attempt, { timeoutMs, intervalMs })` — retries until `attempt` answers anything but `undefined`, then answers with it. Reaching the deadline answers `undefined` rather than throwing, because only the caller knows what was being waited for and can say so. `@pikku/playwright` waits on it too.
|
|
334
|
+
- `createCookieJar` and `readScenarioHttpResponse` / `postScenarioJson` — `HttpScenarioActor` is built on all three, so a step producing the same record reaches the same function.
|
|
335
|
+
- The browser-driver interface, and the reporter's `composeStepProse` / `renderStepTemplate`.
|
|
336
|
+
|
|
337
|
+
The export list is now grouped by who imports it — writing a step, driving a browser, reporting a run — rather than by the order the exports were added.
|
|
338
|
+
|
|
339
|
+
- 5962e51: Add `template` to `pikkuScenarioStep`, so a step's reported prose names the values it was called with.
|
|
340
|
+
|
|
341
|
+
`description` documents what a step does, for the console and for whoever reads the source. `template` is what a reader of the report sees, with `{placeholders}` filled from the input the step was actually called with:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
export const seesAddonCard = pikkuScenarioStep<
|
|
345
|
+
{ packageName: string; state?: 'installed' | 'available' },
|
|
346
|
+
{ visible: true },
|
|
347
|
+
true
|
|
348
|
+
>({
|
|
349
|
+
name: 'seesAddonCard',
|
|
350
|
+
description: 'sees an addon in the gallery',
|
|
351
|
+
template: 'sees {state} addon {packageName}',
|
|
352
|
+
browser: true,
|
|
353
|
+
func: async (_services, { packageName, state }, { browser }) => { … },
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
Then the admin sees at least 10 addons on offer ✓ 3ms
|
|
359
|
+
When the admin searches for stripe ✓ 10ms
|
|
360
|
+
Then the admin sees available addon @pikku/addon-stripe ✓ 77ms
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Previously the only way to get that was a `description` at every call site, which meant writing the sentence once per call rather than once per step — and a call site that forgot it reported the same sentence three times in a row.
|
|
364
|
+
- A placeholder with no recorded value renders as nothing and the surrounding whitespace collapses, so an omitted optional input reads as a shorter sentence rather than leaking a literal `{state}` into the report. Type placeholder values so they read as words (`state?: 'installed' | 'available'`, not `installed?: boolean`).
|
|
365
|
+
- A call-site `description` still wins, the same way it already won over the step's `description`.
|
|
366
|
+
- `renderStepTemplate` is exported from `@pikku/core/workflow` alongside `composeStepProse`, so the CLI reporter and the console render identically.
|
|
367
|
+
|
|
368
|
+
Scenario steps now record their input on the run (`inlineStep` persisted `null` for every inline step, so there was nothing for a reporter to interpolate). This is what `getRunSteps` already exposes as `data` for RPC steps.
|
|
369
|
+
|
|
370
|
+
A step called from a loop gets its template too. Its durable name is built at runtime (`sees @pikku/addon-todos`) from a declaration the static meta records verbatim (`sees ${packageName}`), so the two can never match by name — it used to fall back to the bare name, with no keyword, actor or template:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
sees @pikku/addon-console ✓ 85ms
|
|
374
|
+
Then the admin sees installed addon @pikku/addon-console ✓ 92ms
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The join is by **step function**. A scenario step is dispatched by name exactly as an RPC is, so it now records that name in the run's existing `rpcName` slot — no new field, no schema change in any workflow store. Nothing dispatches off that value anywhere; step identity always comes from the code being replayed.
|
|
378
|
+
|
|
379
|
+
To keep the slot honest, a scenario step is now its own **kind of RPC**, alongside public / private / remote: `FunctionMeta.scenarioStep` marks it, and `rpcExposed` refuses it even if something marks it `expose: true`. Steps were already left out of the RPC registry; this makes "never network-callable" a property the runtime enforces rather than one the registration path happens to produce.
|
|
380
|
+
|
|
381
|
+
`collectScenarioStepProse` now returns `{ byStepName, byStepFunc }` rather than a bare `Map`, and `buildStepLadder` takes that. The step name still wins; the function index only decides steps recorded under a name no declaration carries, and a function called from several sites that disagree on their prose is left out rather than guessed at.
|
|
382
|
+
|
|
383
|
+
- 5962e51: Add `pikkuScenarioStep` — named, typed scenario steps whose body is an ordinary pikku function.
|
|
384
|
+
|
|
385
|
+
A scenario step is referenced by typed string name, the same way `workflow.do` references an RPC, and is checked against a generated `FlattenedScenarioStepMap`:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
export const buysAnApple = pikkuScenarioStep({
|
|
389
|
+
name: 'buysAnApple',
|
|
390
|
+
description: 'buys an apple',
|
|
391
|
+
func: async (services, data: { qty: number }) => { ... },
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
await scenario.given('buys an apple', 'buysAnApple', { qty: 1 }, { actor: actors.shopper })
|
|
395
|
+
// renders: Given the shopper buys an apple
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- `given`/`when`/`then` are sugar over `step`, setting only the prose prefix. The runner renders a step ladder from the recorded run.
|
|
399
|
+
- Steps default to `retries: 0` — a failed assertion is not retried.
|
|
400
|
+
- Steps are deliberately **not** registered as RPCs, so a browser-driving step is never network-callable.
|
|
401
|
+
- `browser: true` steps receive a browser handle on the wire. `@pikku/playwright` is a new package providing the Playwright-backed provider, signing each actor's browser context in through the same actor path the HTTP actors use. Without a provider, `pikku scenario run --no-browser` **skips** browser scenarios instead of failing them.
|
|
402
|
+
- New diagnostics: PKU677 (a `browser: true` step called without an actor) and PKU678 (a step target that is not a static string literal).
|
|
403
|
+
- Fixes `--no-<flag>` boolean negation in the CLI command parser, which previously parsed as an unknown option.
|
|
404
|
+
- Fixes PKU673 (a scenario func destructuring services), which never fired because it ran before function meta existed; it now runs in post-processing.
|
|
405
|
+
- Fixes scenario/workflow steps nested in `for...of` and `Promise.all` being dropped from workflow meta.
|
|
406
|
+
|
|
407
|
+
- 61b9bf8: Type a scenario actor's `invoke` over the project's exposed RPC map, and give a step the environment it targets.
|
|
408
|
+
|
|
409
|
+
`ScenarioActor` is now generic in the RPC surface it can reach, and the generated `pikku-scenario-actors.gen.ts` binds it to `FlattenedRPCMap` — exactly the `/rpc/:name` surface an HTTP actor can reach. An unknown RPC name or a payload of the wrong shape is a compile error rather than a 400 mid-run, and the result is narrowed instead of `unknown`:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const listed = await actor.invoke('todos:listTodos', { limit: 5 })
|
|
413
|
+
const todos: string[] = listed.todos
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
`wire.scenarioStep.actor` stops being `any`: `PikkuWire` takes the project's actor registry as a type argument, threaded through the generated function types. The actors file is now written even for an empty registry, so `TypedScenarioActors` is always a resolvable import.
|
|
417
|
+
|
|
418
|
+
Alongside it:
|
|
419
|
+
- **`invokeRaw(rpcName, data, { headers })`** on `ScenarioActor`, reporting `{ status, ok, body }` rather than throwing. A refusal is the expected outcome of a permissions or scopes scenario, and `invoke`'s error truncates the body naming which scope was missing. `invoke` is now `invokeRaw` plus a throw on `!ok`. The `headers` option is how a step expresses an identity the actor registry cannot.
|
|
420
|
+
- **`scenarioStep.env`** — `{ apiUrl, appUrl? }`, from `scenarios.environments[<environment>]`. Steps run in the CLI process, where there is no `variables` service, so without this every raw-HTTP step would reach for `process.env`. A run started on a server falls back to its own `API_URL`/`APP_URL`.
|
|
421
|
+
- **`requireActor(scenarioStep)` and `requireScenarioEnv(scenarioStep)`** exported from `@pikku/core/workflow`, replacing the hand-rolled `actorOf(...)` guard each step file was writing. Both name the step and say what to pass.
|
|
422
|
+
|
|
1
423
|
## 0.12.69
|
|
2
424
|
|
|
3
425
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 - present Yasser Fadl and Pikku contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @pikku/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Pikku runtime. Defines functions, wirings, services, middleware and the
|
|
4
|
+
types every other Pikku package builds on.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
You rarely install this alone — a runtime package (`@pikku/express`,
|
|
7
|
+
`@pikku/lambda`, …) takes it as a peer dependency.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @pikku/core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Write functions against the types the CLI generates for your project:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { pikkuFunc } from '../.pikku/pikku-types.gen.js'
|
|
21
|
+
|
|
22
|
+
export const getTodo = pikkuFunc({
|
|
23
|
+
input: GetTodoInput,
|
|
24
|
+
output: TodoOutput,
|
|
25
|
+
func: async (services, data) => services.db.getTodo(data.id),
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Wire them to HTTP, queues, cron, channels or MCP, then run `npx pikku` to
|
|
30
|
+
regenerate the bootstrap files and typed clients.
|
|
31
|
+
|
|
32
|
+
Subpath exports cover the individual wiring types — `@pikku/core/http`,
|
|
33
|
+
`@pikku/core/workflow`, `@pikku/core/channel`, `@pikku/core/ai-agent` and more.
|
|
34
|
+
|
|
35
|
+
## Docs
|
|
36
|
+
|
|
37
|
+
https://pikku.dev/docs
|
|
@@ -135,6 +135,12 @@ export type CorePikkuAuthConfig<Services extends CoreSingletonServices = CoreSer
|
|
|
135
135
|
};
|
|
136
136
|
export declare const pikkuAuth: <Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(auth: CorePikkuAuth<Services, Session> | CorePikkuAuthConfig<Services, Session>) => CorePikkuPermission<any, Services, any>;
|
|
137
137
|
export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* A lifecycle hook: the same call signature as the function it hangs off, but
|
|
140
|
+
* its return value is discarded. A hook is setup/teardown, not a step — it has
|
|
141
|
+
* no id, no meta and no schema, so it is never recorded and never replayed.
|
|
142
|
+
*/
|
|
143
|
+
export type CorePikkuFunctionHook<Services = any, Data = any, Wire = any> = (services: Services, data: Data, wire: Wire) => Promise<void> | void;
|
|
138
144
|
export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any, any>, InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined, Scope extends string = string> = {
|
|
139
145
|
/** Short human-readable name (e.g. "Create Todo") */
|
|
140
146
|
title?: string;
|
|
@@ -156,11 +162,32 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
|
|
|
156
162
|
workflowRetries?: number;
|
|
157
163
|
/** Timeout for this function when used as a workflow step (e.g. '30s', '5m'). */
|
|
158
164
|
workflowTimeout?: string;
|
|
165
|
+
/** Scenario steps only: this step drives a browser, so the runner must provision one before calling it. */
|
|
166
|
+
browser?: boolean;
|
|
159
167
|
audit?: boolean | {
|
|
160
168
|
durability?: 'best-effort' | 'transactional';
|
|
161
169
|
};
|
|
162
170
|
approvalDescription?: any;
|
|
163
171
|
func: PikkuFunction;
|
|
172
|
+
/**
|
|
173
|
+
* Scenarios only: runs before the scenario body, with the scenario's own
|
|
174
|
+
* signature. Throwing skips the body and fails the run, but `after` still
|
|
175
|
+
* runs.
|
|
176
|
+
*/
|
|
177
|
+
before?: CorePikkuFunctionHook;
|
|
178
|
+
/**
|
|
179
|
+
* Scenarios only: always runs after the scenario body, in a `finally`.
|
|
180
|
+
* Throwing fails a run that would otherwise have passed; on an
|
|
181
|
+
* already-failed run it attaches as the `cause` and never replaces the
|
|
182
|
+
* original error.
|
|
183
|
+
*/
|
|
184
|
+
after?: CorePikkuFunctionHook;
|
|
185
|
+
/**
|
|
186
|
+
* Scenarios only: why this scenario is held out of a default run. It is
|
|
187
|
+
* reported as skipped rather than quietly omitted, and naming it directly
|
|
188
|
+
* with `--flows` runs it anyway.
|
|
189
|
+
*/
|
|
190
|
+
skip?: string;
|
|
164
191
|
auth?: boolean;
|
|
165
192
|
/**
|
|
166
193
|
* Scopes the session must hold to run this function. All of them are
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
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
|
-
export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
|
|
6
|
+
export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionHook, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
|
|
7
7
|
export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
|
|
8
8
|
export { addFunction, getAllFunctionNames } from './function/index.js';
|
|
9
9
|
export type { ListInput, ListOutput, Filter, LeafFilter, LeafValue, } from './function/list.types.js';
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { pikkuState, resetPikkuState } from './pikku-state.js';
|
|
1
|
+
export { pikkuState, resetPikkuState, getAllPackageStates, } from './pikku-state.js';
|
|
2
2
|
export { httpRouter } from './wirings/http/routers/http-router.js';
|
|
3
3
|
export type { CreateSingletonServices, CreateWireServices, } from './types/core.types.js';
|
package/dist/internal.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { pikkuState, resetPikkuState } from './pikku-state.js';
|
|
1
|
+
export { pikkuState, resetPikkuState, getAllPackageStates, } from './pikku-state.js';
|
|
2
2
|
export { httpRouter } from './wirings/http/routers/http-router.js';
|
package/dist/pikku-state.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScenarioActor, ScenarioActorConfig, ScenarioActors } from './scenario-actors-service.js';
|
|
1
|
+
import type { ScenarioActor, ScenarioActorConfig, ScenarioActors, ScenarioInvokeOptions, ScenarioHttpResponse } from './scenario-actors-service.js';
|
|
2
2
|
import type { ConverseOptions, ActorFlowVerdict } from '../wirings/actor-flow/actor-flow.types.js';
|
|
3
3
|
export interface HttpScenarioActorsConfig {
|
|
4
4
|
/**
|
|
@@ -38,11 +38,18 @@ export declare class HttpScenarioActor implements ScenarioActor {
|
|
|
38
38
|
readonly name: string;
|
|
39
39
|
private actorConfig;
|
|
40
40
|
private config;
|
|
41
|
-
private
|
|
42
|
-
|
|
41
|
+
private jar;
|
|
42
|
+
/**
|
|
43
|
+
* Whether `login()` has succeeded since the last time the session was
|
|
44
|
+
* dropped. The jar cannot answer this — a target may set a cookie before
|
|
45
|
+
* anyone signs in, and it would then look like a session that was never
|
|
46
|
+
* established.
|
|
47
|
+
*/
|
|
48
|
+
private signedIn;
|
|
43
49
|
constructor(name: string, actorConfig: ScenarioActorConfig, config: HttpScenarioActorsConfig);
|
|
44
50
|
get email(): string;
|
|
45
51
|
invoke(rpcName: string, data: unknown): Promise<unknown>;
|
|
52
|
+
invokeRaw(rpcName: string, data: unknown, options?: ScenarioInvokeOptions): Promise<ScenarioHttpResponse>;
|
|
46
53
|
converse(options: ConverseOptions): Promise<ActorFlowVerdict>;
|
|
47
54
|
/** Start/continue the target agent's run over HTTP as this actor. */
|
|
48
55
|
private agentRun;
|
|
@@ -57,7 +64,8 @@ export declare class HttpScenarioActor implements ScenarioActor {
|
|
|
57
64
|
*/
|
|
58
65
|
private postAgent;
|
|
59
66
|
private postRpc;
|
|
60
|
-
|
|
67
|
+
/** Drop the session, so the next call signs in again before it goes out. */
|
|
68
|
+
private signOut;
|
|
61
69
|
private login;
|
|
62
70
|
}
|
|
63
71
|
/**
|