@pikku/skills 0.12.39 → 0.12.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.39",
3
+ "version": "0.12.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -102,6 +102,13 @@ await rpc.agent.resume(runId, { toolCallId, approved })
102
102
  await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
103
103
  ```
104
104
 
105
+ `rpc.agent` is built from a factory that `@pikku/core/agent` registers when it
106
+ is imported, so the agent runtime lands only in a deployment unit that actually
107
+ holds an agent. A unit that declares one imports it through its own agent file
108
+ and needs nothing extra; a unit that reaches `rpc.agent` while holding no agent
109
+ throws `Agent runtime not available` rather than silently paying ~52 KB for a
110
+ runtime it never uses.
111
+
105
112
  `context` is a string injected into the system prompt for this request only —
106
113
  use it for upfront state (current org, project, deployment) so the agent stops
107
114
  asking the user for identifiers it could have been handed.
@@ -115,21 +115,17 @@ generated functions through `ref()`.
115
115
 
116
116
  A person who has to go hunting for the port assumes the app did not start.
117
117
 
118
- ## Keep a BUILD-REPORT.md
118
+ ## Report what pikku got wrong, as it happens
119
119
 
120
120
  Whenever pikku or a skill costs you time — a command that failed on a fresh
121
121
  tree, a skill that described a flag the CLI does not have, generated code you
122
- had to fix by hand — add an entry to `BUILD-REPORT.md` at the repo root as it
123
- happens: what you ran, what you expected, what happened, and the workaround.
124
- Leave out secrets, tokens and customer data.
125
-
126
- At hand-over, show the file and ask the user whether to send it. Only with
127
- their okay, send each entry with `pikku fabric report --stdin` (JSON on stdin;
128
- `"kind": "product"` when pikku behaved wrongly, `"kind": "harness"` with
129
- `"skill"` and `"passage"` when a skill misled you). The `pikku-report` skill
130
- has the fields. When the CLI is not signed in to Fabric, the report is queued
131
- locally rather than sent: say so, and that `pikku fabric findings flush` sends
132
- the queue once they sign in. Do not retry or file it twice.
122
+ had to fix by hand — file it then with `pikku fabric report --stdin`. The
123
+ `pikku-report` skill has the fields. Leave out secrets, tokens and customer data.
124
+
125
+ Findings are held until hand-over. The last thing in the hand-over, after the
126
+ app runs (and is deployed, if they chose to), is `pikku fabric report`: if it
127
+ lists findings and asks, ask the user — Yes, No, Always or Never — and run it
128
+ again with `--consent <answer>`. If it says reporting is off, do not ask.
133
129
 
134
130
  ## Who you are talking to
135
131
 
@@ -515,9 +515,11 @@ Rules that are not optional:
515
515
  - Surface errors. No empty catch, no swallowed promise. If a mutation can fail,
516
516
  render the failure inline next to the control that triggered it — not a toast.
517
517
  - An exposed function with no session and no permission is reachable by anyone
518
- over `POST /rpc/:rpcName` (PKU574). Either gate it or drop `expose: true`.
518
+ over `POST /rpc/:rpcName` (PKU574). Either gate it, drop `expose: true`, or —
519
+ when public is the point — write `auth: false` on it to say so.
519
520
  - A public, signed-out read (a homepage's programme, a price list) is a
520
- `pikkuSessionlessFunc`. `pikkuFunc` with `auth: false` still answers
521
+ `pikkuSessionlessFunc` with `auth: false` written out, which is what keeps
522
+ PKU574 quiet for it. `pikkuFunc` with `auth: false` still answers
521
523
  `MissingSessionError` over `/rpc` to a caller with no session.
522
524
  - Better Auth already owns the `user`, `session`, `account` and `verification`
523
525
  tables. A domain table with one of those names — a class _session_, a drop-in
@@ -259,14 +259,14 @@ Do not push without explicit confirmation. Do not merge.
259
259
  When pikku itself is what cost you time — a wrong generated type, a check that
260
260
  passed when it should not, a skill that misled you — file it with `pikku fabric
261
261
  report`. The `pikku-report` skill owns the ladder, the two kinds, the JSON-on-
262
- stdin form and the local spool; read it before filing.
262
+ stdin form and asking the user at hand-over; read it before filing.
263
263
 
264
- Reporting at all is permitted here (see **Hard constraints**) and is the one
265
- network call a build may make. Nothing is written to the repo. Never patch pikku
264
+ Filing is permitted here (see **Hard constraints**), and sending what was filed
265
+ is the one network call a build may make, once the user agrees. Nothing is
266
+ written to the repo. Never patch pikku
266
267
  itself — not `node_modules`, not a linked checkout — work around it in the app,
267
268
  report it, and let the fix happen once.
268
269
 
269
-
270
270
  ## Hard constraints
271
271
 
272
272
  The skill's `allowed-tools` does **not** permit:
@@ -135,7 +135,7 @@ than they save:
135
135
  type params, never an inline return type. The schema is the type.
136
136
  - Permission checks go in the `permissions` field, never the function body. An
137
137
  exposed function with no session and no permission is reachable by anyone over
138
- `POST /rpc/:rpcName` (PKU574).
138
+ `POST /rpc/:rpcName` (PKU574). If that is the point, write `auth: false` on it.
139
139
  - No `process.env` inside a function — use the injected `variables` / `secrets`
140
140
  services.
141
141
  - A `z.date()` **input** arrives over RPC as an ISO string, not a `Date`.
@@ -3,12 +3,11 @@ name: pikku-report
3
3
  description: >-
4
4
  Use when pikku itself cost you time — wrong generated types, a check that passes when it
5
5
  should not, output that is quietly wrong, a skill that misled you — or when the user asks you
6
- to report a framework bug, file a finding, or look at what is queued. Owns `pikku fabric
7
- report` (a finding is about pikku, not the app), the product-vs-harness kinds, the
8
- workaround-first ladder, and the local findings spool. TRIGGER when: the framework fought you,
6
+ to report a framework bug or file a finding. Owns `pikku fabric report` (a finding is about
7
+ pikku, not the app), the product-vs-harness kinds, the workaround-first ladder, and asking the
8
+ user at hand-over whether to send what was filed. TRIGGER when: the framework fought you,
9
9
  codegen produced something broken, a skill told you to run something that does not exist, the
10
- user says "report this to pikku" / "file a finding" / "check the findings queue", or a finding
11
- was queued and never sent. DO NOT TRIGGER when: the bug is in the app you are building (fix it
10
+ user says "report this to pikku" / "file a finding", or you are handing over a build. DO NOT TRIGGER when: the bug is in the app you are building (fix it
12
11
  there), or you are tempted to patch pikku's source (never do that from an app).
13
12
  installGroups: [core]
14
13
  ---
@@ -19,9 +18,9 @@ A finding is about **pikku**, not about the app you are building. It is how the
19
18
  framework learns what cost its users time — the bug, the misleading skill, the
20
19
  silence where a check should have complained.
21
20
 
22
- Nothing is written to the repository. The terminal receipt shows exactly what
23
- left your machine, and the command is spelled `pikku fabric report` — there is
24
- no top-level report command.
21
+ File each one the moment it happens. Nothing leaves the machine until the user
22
+ says so at hand-over, and nothing is written to the repository. The command is
23
+ spelled `pikku fabric report` — there is no top-level report command.
25
24
 
26
25
  ## Report at the moment it happens
27
26
 
@@ -62,7 +61,7 @@ baseline noise that was already failing before you started.
62
61
  - `--kind harness` — a skill misled you: it told you to run something that does
63
62
  not exist, described a flag that is spelled differently, or contradicted what
64
63
  the CLI actually did. Pass `--skill <name>` and `--passage "<the line or
65
- section>"`. This is the most useful kind to file, because it is fixable
64
+ section>"`. This is the most useful kind to file, because it is fixable
66
65
  immediately — so file it even when the cost was small.
67
66
 
68
67
  ## The command
@@ -102,24 +101,44 @@ before anything is sent:
102
101
  Add whichever of these you actually have: `error` (the error's message line,
103
102
  verbatim), `repro` (the shortest way to reach it again), `proposal`, `area`,
104
103
  `surface`, `cost` (measured if you measured it — "98s vs 20s steady" ranks;
105
- "slow" does not), `run` (an id shared by every finding from this build),
106
- `deployTarget`.
104
+ "slow" does not), `deployTarget`.
107
105
 
108
106
  Versions, platform and package manager are read off the installed tree for you.
109
107
  Do not pass them and do not ask the user for them.
110
108
 
111
- ## When it cannot send
109
+ ## At hand-over
112
110
 
113
- Reporting never fails a build. A finding that cannot be sent — logged out, or
114
- fabric unreachable — is held on the machine and goes out with the next report
115
- that succeeds, so nothing you file is lost:
111
+ Filing holds the finding on the machine, tied to this build by a run id the CLI
112
+ makes for the checkout. The terminal says `held until hand-over`; carry on.
116
113
 
117
- - `pikku fabric findings list` shows what is queued.
118
- - `pikku fabric findings flush` sends everything queued.
119
- - `pikku fabric findings clear` discards it.
114
+ The last thing in the hand-over — after the app runs, and is deployed if they
115
+ chose to — is:
120
116
 
121
- If the terminal says the finding was queued, carry on with what you were doing.
122
- Do not try to fix the send, and do not file the same thing again.
117
+ ```bash
118
+ pikku fabric report
119
+ ```
120
+
121
+ With no finding, it lists what this build filed. What happens next depends on
122
+ what the user said before, which the CLI keeps on their machine:
123
+
124
+ - **Always** — every finding was sent the moment you filed it. Tell the user in
125
+ one line.
126
+ - **Never** — nothing was kept. Do not ask and do not mention it.
127
+ - **Nothing saved** — show the user the titles and ask once: _"Send these to the
128
+ Pikku team so they can fix them?"_ — **Yes**, **No**, **Always** or
129
+ **Never**. Then run it again with their answer:
130
+
131
+ ```bash
132
+ pikku fabric report --consent yes|no|always|never
133
+ ```
134
+
135
+ Yes sends and No discards what is held now; Always and Never are saved and the
136
+ question is not asked again. If nobody answers, leave them held and say that
137
+ `pikku fabric report` sends them later.
138
+
139
+ Findings are anonymous: no account, no project. The receipt printed when you
140
+ filed each one is exactly what leaves the machine. A send that fails keeps what
141
+ was not sent; do not retry or file it twice.
123
142
 
124
143
  ## Never fix pikku itself
125
144
 
@@ -159,6 +159,9 @@ export const processOrder = pikkuWorkflowFunc({
159
159
  // RPC step — run a registered Pikku function as a step (opts: retries, retryDelay, description)
160
160
  const result = await workflow.do('Step name', 'rpcFunctionName', { ...data }, { retries: 3, retryDelay: '1s' })
161
161
 
162
+ // Sub-workflow step — name another workflow instead of an RPC (see "Sub-workflows")
163
+ const onboarded = await workflow.do('Onboard', 'onboardUserWorkflow', { userId })
164
+
162
165
  // Inline closure step — immediate execution, cached for replay
163
166
  const msg = await workflow.do('Generate', async () => `Welcome, ${data.email}!`)
164
167
 
@@ -277,9 +280,59 @@ const users = await Promise.all(
277
280
  )
278
281
  ```
279
282
 
283
+ ### Sub-workflows
284
+
285
+ A step whose second argument names a **workflow** rather than an RPC starts that
286
+ workflow as a child run and resolves to its output. There is no separate API —
287
+ it is the same `workflow.do`, and the generated `TypedWorkflow` has an overload
288
+ keyed on `FlattenedWorkflowMap`, so the child's input and output are type-checked
289
+ exactly like an RPC step's.
290
+
291
+ ```typescript
292
+ export const signupWorkflow = pikkuWorkflowFunc<
293
+ { email: string },
294
+ { userId: string }
295
+ >({
296
+ func: async (_services, data, { workflow }) => {
297
+ const user = await workflow.do('Create user', 'createUser', data)
298
+ // runs onboardUserWorkflow as a child run; awaits its output
299
+ await workflow.do('Onboard', 'onboardUserWorkflow', { userId: user.id })
300
+ await workflow.do('Send welcome', 'sendWelcomeEmail', { userId: user.id })
301
+ return { userId: user.id }
302
+ },
303
+ })
304
+ ```
305
+
306
+ How the child runs:
307
+
308
+ - **It is its own run.** It gets its own `runId`, its own steps and its own
309
+ history; the parent step records it as `childRunId` and the child's wire
310
+ carries `parentRunId`/`parentStepId`. Inspect the child's steps on the child
311
+ run, not the parent.
312
+ - **Identity is inherited.** The child's wire copies the parent's
313
+ `pikkuUserId`, so the child runs as whoever started the parent.
314
+ - **Inline vs queued follows the deployment.** Without a `queueService` the
315
+ child runs inline and the parent step returns its output directly. With one,
316
+ the child is queued, the parent parks on that step, and the child's
317
+ completion writes the parent step's result and resumes the parent.
318
+ - **Failure propagates.** A child that fails or is cancelled fails the parent
319
+ step (`'Sub-workflow failed'` / `'Sub-workflow was cancelled'` when the child
320
+ left no message). Inline, the step's `retries` start a fresh child run per
321
+ attempt; queued, the child's failure lands on the parent step once and is
322
+ not retried — put retries on the child's own steps instead.
323
+
324
+ Use a sub-workflow when the child is a real orchestration you also start on
325
+ its own, or reuse from several parents. A child that would be a single
326
+ `workflow.do` is a function — call the RPC directly (see the single-RPC rule
327
+ above).
328
+
329
+ To start a workflow **without waiting** for it, that is not a sub-workflow:
330
+ have an RPC step call `rpc.startWorkflow(name, input)`, which returns
331
+ `{ runId }` immediately, and the new run has no parent link.
332
+
280
333
  ### Graph workflow (DAG)
281
334
 
282
- `pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name`; `config.<node>.next` lists nodes to run after it (in parallel); `config.<node>.input: (ref) => ...` transforms input using refs to prior node outputs.
335
+ `pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name` — or a workflow name, which runs that workflow as a sub-workflow node; `config.<node>.next` lists nodes to run after it (in parallel); `config.<node>.input: (ref) => ...` transforms input using refs to prior node outputs.
283
336
 
284
337
  ```typescript
285
338
  import { pikkuWorkflowGraph } from '#pikku/workflow/pikku-workflow-types.gen.js'