@pikku/core 0.12.80 → 0.12.83

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.
Files changed (237) hide show
  1. package/CHANGELOG.md +345 -0
  2. package/dist/errors/index.d.ts +1 -1
  3. package/dist/errors/index.js +1 -1
  4. package/dist/function/function-runner.js +2 -5
  5. package/dist/function/index.d.ts +1 -1
  6. package/dist/index.d.ts +11 -11
  7. package/dist/index.js +3 -3
  8. package/dist/pikku-state.js +4 -0
  9. package/dist/services/ai-agent-runner-service.d.ts +7 -0
  10. package/dist/services/ai-run-state-service.d.ts +10 -0
  11. package/dist/services/in-memory-ai-run-state-service.d.ts +5 -1
  12. package/dist/services/in-memory-ai-run-state-service.js +9 -0
  13. package/dist/services/index.d.ts +15 -16
  14. package/dist/services/index.js +5 -5
  15. package/dist/services/meta-service.d.ts +2 -1
  16. package/dist/services/scoped-credential-service.d.ts +21 -0
  17. package/dist/services/scoped-credential-service.js +53 -0
  18. package/dist/testing/service-tests/ai-storage-service-tests.js +76 -0
  19. package/dist/types/core.types.d.ts +2 -3
  20. package/dist/types/state.types.d.ts +19 -1
  21. package/dist/wirings/actor-flow/index.d.ts +1 -1
  22. package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
  23. package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
  24. package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
  25. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
  26. package/dist/wirings/ai-agent/ai-agent-memory.js +34 -17
  27. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
  28. package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
  29. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -0
  30. package/dist/wirings/ai-agent/ai-agent-runner.js +61 -40
  31. package/dist/wirings/ai-agent/ai-agent-stream.js +89 -36
  32. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +1 -0
  33. package/dist/wirings/ai-agent/ai-agent-turn.js +1 -0
  34. package/dist/wirings/ai-agent/ai-agent.types.d.ts +46 -1
  35. package/dist/wirings/ai-agent/index.d.ts +8 -7
  36. package/dist/wirings/ai-agent/index.js +5 -4
  37. package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
  38. package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
  39. package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
  40. package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
  41. package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
  42. package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
  43. package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
  44. package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
  45. package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
  46. package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
  47. package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
  48. package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
  49. package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
  50. package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
  51. package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
  52. package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
  53. package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
  54. package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
  55. package/dist/wirings/ai-scorer/index.d.ts +6 -0
  56. package/dist/wirings/ai-scorer/index.js +5 -0
  57. package/dist/wirings/channel/index.d.ts +5 -6
  58. package/dist/wirings/channel/index.js +3 -4
  59. package/dist/wirings/channel/local/local-channel-runner.js +8 -1
  60. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
  61. package/dist/wirings/cli/channel/index.d.ts +1 -2
  62. package/dist/wirings/cli/channel/index.js +0 -1
  63. package/dist/wirings/cli/cli-runner.js +13 -1
  64. package/dist/wirings/credential/index.d.ts +1 -1
  65. package/dist/wirings/gateway/index.d.ts +1 -1
  66. package/dist/wirings/http/http-runner.js +8 -2
  67. package/dist/wirings/http/index.d.ts +1 -2
  68. package/dist/wirings/mcp/index.d.ts +1 -1
  69. package/dist/wirings/mcp/mcp-runner.d.ts +15 -0
  70. package/dist/wirings/mcp/mcp-runner.js +18 -5
  71. package/dist/wirings/persona/index.d.ts +3 -4
  72. package/dist/wirings/persona/index.js +2 -3
  73. package/dist/wirings/queue/index.d.ts +1 -3
  74. package/dist/wirings/queue/index.js +1 -3
  75. package/dist/wirings/rpc/addon-runner.d.ts +8 -0
  76. package/dist/wirings/rpc/addon-runner.js +31 -3
  77. package/dist/wirings/rpc/rpc-runner.js +4 -0
  78. package/dist/wirings/rpc/rpc-types.d.ts +8 -0
  79. package/dist/wirings/rpc/wire-addon.d.ts +25 -0
  80. package/dist/wirings/rpc/wire-addon.js +8 -0
  81. package/dist/wirings/scheduler/index.d.ts +1 -1
  82. package/dist/wirings/trigger/index.d.ts +1 -1
  83. package/dist/wirings/virtual-user/index.d.ts +5 -6
  84. package/dist/wirings/virtual-user/index.js +2 -4
  85. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +85 -15
  86. package/dist/wirings/workflow/feature.d.ts +2 -1
  87. package/dist/wirings/workflow/index.d.ts +5 -16
  88. package/dist/wirings/workflow/index.js +1 -9
  89. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -7
  90. package/dist/wirings/workflow/pikku-scenario-service.js +48 -13
  91. package/dist/wirings/workflow/pikku-workflow-service.js +17 -3
  92. package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
  93. package/dist/wirings/workflow/scenario.types.d.ts +37 -0
  94. package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
  95. package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
  96. package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
  97. package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
  98. package/dist/wirings/workflow/workflow-approval.d.ts +29 -1
  99. package/dist/wirings/workflow/workflow-approval.js +65 -2
  100. package/dist/wirings/workflow/workflow-run-ownership.d.ts +2 -1
  101. package/dist/wirings/workflow/workflow-run-ownership.js +2 -1
  102. package/dist/wirings/workflow/workflow.types.d.ts +2 -37
  103. package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
  104. package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
  105. package/knowledge/decisions/internals/index.md +30 -3
  106. package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
  107. package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
  108. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +30 -14
  109. package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
  110. package/knowledge/decisions/security/index.md +3 -1
  111. package/knowledge/questions/index.md +1 -1
  112. package/package.json +3 -2
  113. package/scripts/generate-api-report.mts +143 -18
  114. package/src/api-report.test.ts +2 -2
  115. package/src/errors/index.ts +1 -1
  116. package/src/function/function-runner.test.ts +52 -0
  117. package/src/function/function-runner.ts +5 -9
  118. package/src/function/index.ts +0 -2
  119. package/src/index.ts +0 -35
  120. package/src/pikku-state.ts +5 -0
  121. package/src/public-surface.json +81 -118
  122. package/src/services/ai-agent-runner-service.ts +12 -1
  123. package/src/services/ai-run-state-service.ts +11 -0
  124. package/src/services/in-memory-ai-run-state-service.ts +13 -0
  125. package/src/services/index.ts +7 -58
  126. package/src/services/meta-service.ts +2 -4
  127. package/src/services/scoped-credential-service.test.ts +86 -0
  128. package/src/services/scoped-credential-service.ts +63 -0
  129. package/src/testing/service-tests/ai-storage-service-tests.ts +93 -0
  130. package/src/types/core.types.ts +4 -7
  131. package/src/types/state.types.ts +21 -1
  132. package/src/wirings/actor-flow/index.ts +0 -3
  133. package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
  134. package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
  135. package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
  136. package/src/wirings/ai-agent/ai-agent-memory.ts +54 -38
  137. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
  138. package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
  139. package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -0
  140. package/src/wirings/ai-agent/ai-agent-runner.ts +71 -40
  141. package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
  142. package/src/wirings/ai-agent/ai-agent-stream.ts +116 -54
  143. package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
  144. package/src/wirings/ai-agent/ai-agent-turn.ts +1 -0
  145. package/src/wirings/ai-agent/ai-agent.types.ts +64 -4
  146. package/src/wirings/ai-agent/index.ts +2 -16
  147. package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
  148. package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
  149. package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
  150. package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
  151. package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
  152. package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
  153. package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
  154. package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
  155. package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
  156. package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
  157. package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
  158. package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
  159. package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
  160. package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
  161. package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
  162. package/src/wirings/ai-scorer/index.ts +24 -0
  163. package/src/wirings/channel/index.ts +1 -20
  164. package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
  165. package/src/wirings/channel/local/local-channel-runner.ts +8 -1
  166. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
  167. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
  168. package/src/wirings/cli/channel/index.ts +0 -7
  169. package/src/wirings/cli/cli-runner.test.ts +68 -0
  170. package/src/wirings/cli/cli-runner.ts +18 -1
  171. package/src/wirings/credential/index.ts +0 -1
  172. package/src/wirings/gateway/index.ts +0 -3
  173. package/src/wirings/http/http-runner.test.ts +66 -0
  174. package/src/wirings/http/http-runner.ts +10 -2
  175. package/src/wirings/http/index.ts +1 -1
  176. package/src/wirings/mcp/index.ts +0 -1
  177. package/src/wirings/mcp/mcp-runner.test.ts +181 -0
  178. package/src/wirings/mcp/mcp-runner.ts +35 -5
  179. package/src/wirings/persona/index.ts +0 -8
  180. package/src/wirings/queue/index.ts +0 -14
  181. package/src/wirings/rpc/addon-runner.ts +62 -3
  182. package/src/wirings/rpc/addon-secrets.test.ts +391 -0
  183. package/src/wirings/rpc/rpc-runner.test.ts +2 -0
  184. package/src/wirings/rpc/rpc-runner.ts +4 -0
  185. package/src/wirings/rpc/rpc-types.ts +8 -0
  186. package/src/wirings/rpc/wire-addon.ts +33 -0
  187. package/src/wirings/scheduler/index.ts +0 -1
  188. package/src/wirings/trigger/index.ts +0 -1
  189. package/src/wirings/virtual-user/index.ts +0 -16
  190. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +96 -16
  191. package/src/wirings/workflow/feature.ts +2 -5
  192. package/src/wirings/workflow/graph/graph-runner.test.ts +72 -0
  193. package/src/wirings/workflow/index.ts +2 -68
  194. package/src/wirings/workflow/pikku-scenario-service.ts +81 -16
  195. package/src/wirings/workflow/pikku-workflow-service.test.ts +13 -12
  196. package/src/wirings/workflow/pikku-workflow-service.ts +28 -4
  197. package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
  198. package/src/wirings/workflow/scenario-hooks.test.ts +3 -2
  199. package/src/wirings/workflow/scenario-step.types.ts +8 -0
  200. package/src/wirings/workflow/scenario.types.ts +63 -0
  201. package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
  202. package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
  203. package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
  204. package/src/wirings/workflow/workflow-approval.ts +113 -9
  205. package/src/wirings/workflow/workflow-run-authority.test.ts +12 -15
  206. package/src/wirings/workflow/workflow-run-ownership.ts +2 -1
  207. package/src/wirings/workflow/workflow.types.ts +1 -63
  208. package/src/wirings-stay-decoupled.test.ts +6 -2
  209. package/tsconfig.tsbuildinfo +1 -1
  210. package/dist/internal.d.ts +0 -3
  211. package/dist/internal.js +0 -2
  212. package/dist/middleware/timeout.d.ts +0 -9
  213. package/dist/middleware/timeout.js +0 -15
  214. package/dist/pikku-response.d.ts +0 -6
  215. package/dist/pikku-response.js +0 -6
  216. package/dist/services/gopass-secrets.d.ts +0 -15
  217. package/dist/services/gopass-secrets.js +0 -76
  218. package/dist/services/http-scenario-actors.d.ts +0 -75
  219. package/dist/services/http-scenario-actors.js +0 -195
  220. package/dist/services/http-user-flow-actors.d.ts +0 -67
  221. package/dist/services/http-user-flow-actors.js +0 -193
  222. package/dist/services/scenario-actors-service.d.ts +0 -127
  223. package/dist/services/scenario-actors-service.js +0 -40
  224. package/dist/services/user-flow-actors-service.d.ts +0 -39
  225. package/dist/wirings/credential/wire-credential.d.ts +0 -48
  226. package/dist/wirings/credential/wire-credential.js +0 -47
  227. package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
  228. package/dist/wirings/oauth2/oauth2-client.js +0 -263
  229. package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
  230. package/dist/wirings/oauth2/oauth2-routes.js +0 -146
  231. package/dist/wirings/scope/wire-scope.d.ts +0 -33
  232. package/dist/wirings/scope/wire-scope.js +0 -32
  233. package/dist/wirings/workflow/dsl/index.d.ts +0 -5
  234. package/dist/wirings/workflow/dsl/index.js +0 -4
  235. package/dist/wirings/workflow/graph/index.d.ts +0 -5
  236. package/dist/wirings/workflow/graph/index.js +0 -4
  237. /package/dist/{services/user-flow-actors-service.js → wirings/workflow/scenario.types.js} +0 -0
@@ -1,127 +0,0 @@
1
- import type { ConverseOptions, ActorFlowVerdict } from '../wirings/actor-flow/actor-flow.types.js';
2
- /**
3
- * What the transport answered, for a step that treats the status as data.
4
- *
5
- * An HTTP response with its body already drained: the stream can only be read
6
- * once, and a step's return value crosses into the run record, so the response
7
- * object itself cannot travel. This is the shape every caller ends up with.
8
- */
9
- export interface ScenarioHttpResponse<T = unknown> {
10
- status: number;
11
- ok: boolean;
12
- /**
13
- * The parsed JSON body — or, when the body was not JSON, the raw text it was
14
- * parsed from, so an HTML error page is still readable rather than lost.
15
- * `undefined` for an empty response.
16
- *
17
- * `T` is a claim the caller makes, not one the transport checked: a step that
18
- * knows the route's payload names it here instead of casting at every use.
19
- */
20
- body: T;
21
- /**
22
- * The whole body as text, so an assertion can search it without knowing the
23
- * payload's shape — and so an error body that is HTML rather than JSON still
24
- * says what went wrong.
25
- */
26
- serialized: string;
27
- }
28
- /**
29
- * Drain a response into the shape a step can carry: the parsed body (an empty
30
- * one counting as no body at all) alongside the text it was parsed from.
31
- *
32
- * `invokeRaw` returns this, and a step that has to reach past an actor — a
33
- * route with no RPC, an identity no actor can hold — reaches for this rather
34
- * than writing the same record by hand.
35
- */
36
- export declare const readScenarioHttpResponse: <T = unknown>(res: Response) => Promise<ScenarioHttpResponse<T>>;
37
- /** How to send one JSON request, for `postScenarioJson`. */
38
- export interface ScenarioJsonRequest {
39
- /** Serialised as the JSON body. Omit for a request that carries none. */
40
- body?: unknown;
41
- /** Sent alongside `content-type: application/json`, and may override it. */
42
- headers?: Record<string, string>;
43
- /** Defaults to `POST` — the method every scenario route here answers. */
44
- method?: string;
45
- /**
46
- * The `fetch` to send it with. Pass a `ScenarioCookieJar`'s to keep the
47
- * session; the global `fetch` otherwise, which is what a step asserting on a
48
- * sessionless call wants.
49
- */
50
- fetch?: typeof fetch;
51
- }
52
- /**
53
- * POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
54
- *
55
- * Every scenario that reaches past an actor was writing this by hand — the same
56
- * `content-type`, the same `JSON.stringify`, the same drain — and the copies had
57
- * drifted: some returned `res.json()`, which loses the status and throws
58
- * outright when the target answers an empty body or an HTML error page. A
59
- * refusal is the expected outcome of a permissions scenario, so it has to
60
- * survive as data.
61
- */
62
- export declare const postScenarioJson: <T = unknown>(url: string, { body, headers, method, fetch: send, }?: ScenarioJsonRequest) => Promise<ScenarioHttpResponse<T>>;
63
- /** Per-call transport options. */
64
- export interface ScenarioInvokeOptions {
65
- /**
66
- * Headers to send alongside the actor's own session. This is how a step
67
- * expresses an identity the actor registry cannot — an impersonation header,
68
- * or one of the header-shim principals a credential scenario invents.
69
- */
70
- headers?: Record<string, string>;
71
- }
72
- /**
73
- * The RPC surface an actor can reach, as name → input/output. A project binds
74
- * its generated exposed RPC map here; the default leaves every name open, which
75
- * is what an actor built by hand (or by a third-party driver) gets.
76
- */
77
- export type ScenarioRpcMap = Record<string, {
78
- input: any;
79
- output: any;
80
- }>;
81
- /**
82
- * The actor a step wire carries, for a project whose actor registry is known.
83
- * An empty registry keeps the open actor type rather than collapsing to
84
- * `never` — a project may still build actors itself.
85
- */
86
- export type ScenarioActorOf<TActors> = [keyof TActors] extends [never] ? ScenarioActor : TActors[keyof TActors];
87
- /** A synthetic user (a user row flagged `actor`) that workflow steps run as over the real transport */
88
- export interface ScenarioActor<TAgentName extends string = string, TRpcMap extends ScenarioRpcMap = ScenarioRpcMap> {
89
- /** Stable actor name (the key in pikku.config.json's actor registry). */
90
- readonly name: string;
91
- /** The actor's user email — flows use it for invites/lookups. */
92
- readonly email: string;
93
- /** Invoke an exposed RPC as this actor over the real transport. */
94
- invoke<TName extends keyof TRpcMap & string>(rpcName: TName, data: TRpcMap[TName]['input']): Promise<TRpcMap[TName]['output']>;
95
- /**
96
- * The same call, reporting what the transport answered rather than throwing.
97
- * A refusal is the expected outcome of a permissions or scopes scenario, and
98
- * `invoke`'s error truncates the body that names which scope was missing.
99
- */
100
- invokeRaw<TName extends keyof TRpcMap & string>(rpcName: TName, data: TRpcMap[TName]['input'], options?: ScenarioInvokeOptions): Promise<ScenarioHttpResponse>;
101
- /** Converse with a Pikku AI agent in this actor's persona and return its verdict */
102
- converse(options: ConverseOptions<TAgentName>): Promise<ActorFlowVerdict>;
103
- }
104
- /** Display/config metadata for an actor (from pikku.config.json) */
105
- export interface ScenarioActorConfig {
106
- email: string;
107
- name?: string;
108
- jobTitle?: string;
109
- personality?: string;
110
- /**
111
- * The persona this body is one of — the KIND of person, declared in
112
- * `scenarios.personas`. Most personas have exactly one actor and it is
113
- * materialised for them; a second body of the same persona is what tenant
114
- * isolation and peer-sharing scenarios are made of.
115
- */
116
- persona?: string;
117
- /**
118
- * Scopes this actor holds, granted directly rather than through a role, and
119
- * the roles it belongs to. Pikku carries them; it never applies them — which
120
- * scope store exists and which roles have been created is the app's own, so
121
- * the app's seed reads these back off `scenarioActorConfigs` and grants them.
122
- */
123
- scopes?: readonly string[];
124
- roles?: readonly string[];
125
- }
126
- /** The injected `actors` service: actor name → actor. */
127
- export type ScenarioActors = Record<string, ScenarioActor>;
@@ -1,40 +0,0 @@
1
- /**
2
- * Drain a response into the shape a step can carry: the parsed body (an empty
3
- * one counting as no body at all) alongside the text it was parsed from.
4
- *
5
- * `invokeRaw` returns this, and a step that has to reach past an actor — a
6
- * route with no RPC, an identity no actor can hold — reaches for this rather
7
- * than writing the same record by hand.
8
- */
9
- export const readScenarioHttpResponse = async (res) => {
10
- const text = res.status === 204 ? '' : await res.text().catch(() => '');
11
- return {
12
- status: res.status,
13
- ok: res.ok,
14
- body: (text ? parseJsonBody(text) : undefined),
15
- serialized: text,
16
- };
17
- };
18
- const parseJsonBody = (text) => {
19
- try {
20
- return JSON.parse(text);
21
- }
22
- catch {
23
- return text;
24
- }
25
- };
26
- /**
27
- * POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
28
- *
29
- * Every scenario that reaches past an actor was writing this by hand — the same
30
- * `content-type`, the same `JSON.stringify`, the same drain — and the copies had
31
- * drifted: some returned `res.json()`, which loses the status and throws
32
- * outright when the target answers an empty body or an HTML error page. A
33
- * refusal is the expected outcome of a permissions scenario, so it has to
34
- * survive as data.
35
- */
36
- export const postScenarioJson = async (url, { body, headers, method = 'POST', fetch: send = fetch, } = {}) => readScenarioHttpResponse(await send(url, {
37
- method,
38
- headers: { 'content-type': 'application/json', ...headers },
39
- ...(body === undefined ? {} : { body: JSON.stringify(body) }),
40
- }));
@@ -1,39 +0,0 @@
1
- import type { ConverseOptions, ActorFlowVerdict } from '../wirings/actor-flow/actor-flow.types.js';
2
- /**
3
- * A user-flow actor: a synthetic user (a normal user row flagged `actor`) that
4
- * workflow steps can run as. Passed to `workflow.do(step, rpc, data, { actor })`
5
- * — the step then goes through the actor's authenticated client over the REAL
6
- * transport (auth middleware, permissions, serialization all exercised),
7
- * never through internal dispatch. Login is lazy: the first `invoke` signs the
8
- * actor in and the session is cached for the actor's lifetime.
9
- */
10
- export interface UserFlowActor<TAgentName extends string = string> {
11
- /** Stable actor name (the key in pikku.config.json's actor registry). */
12
- readonly name: string;
13
- /** The actor's user email — flows use it for invites/lookups. */
14
- readonly email: string;
15
- /** Invoke an exposed RPC as this actor over the real transport. */
16
- invoke(rpcName: string, data: unknown): Promise<unknown>;
17
- /**
18
- * Hold a dynamic conversation with a target Pikku AI agent, in THIS actor's
19
- * persona (personality/jobTitle). Drives the target over the real transport
20
- * as the signed-in actor, answers its tool-approval requests in-persona, and
21
- * returns the actor's verdict on whether the task was met. Deterministic
22
- * checks are the caller's job — use `invoke` afterwards. In a typed project
23
- * `agent` is constrained to the generated union of agent names.
24
- */
25
- converse(options: ConverseOptions<TAgentName>): Promise<ActorFlowVerdict>;
26
- }
27
- /**
28
- * Display/config metadata for an actor (from pikku.config.json). The email
29
- * identifies the actor's user row; personality/jobTitle exist for the console
30
- * screen and for agent-driven flows (the agent plays the persona).
31
- */
32
- export interface UserFlowActorConfig {
33
- email: string;
34
- name?: string;
35
- jobTitle?: string;
36
- personality?: string;
37
- }
38
- /** The injected `actors` service: actor name → actor. */
39
- export type UserFlowActors = Record<string, UserFlowActor>;
@@ -1,48 +0,0 @@
1
- import type { CoreCredential } from './credential.types.js';
2
- /**
3
- * No-op function for declaring credentials.
4
- * This exists purely for TypeScript type checking and will be tree-shaken.
5
- * The CLI extracts metadata via AST parsing.
6
- *
7
- * @example
8
- * ```typescript
9
- * // Per-user API key
10
- * wireCredential({
11
- * name: 'stripe',
12
- * displayName: 'Stripe API Key',
13
- * type: 'wire',
14
- * schema: z.object({ apiKey: z.string() }),
15
- * })
16
- *
17
- * // Per-user OAuth
18
- * wireCredential({
19
- * name: 'google-sheets',
20
- * displayName: 'Google Sheets',
21
- * type: 'wire',
22
- * schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
23
- * oauth2: {
24
- * appCredentialSecretId: 'GOOGLE_OAUTH_APP',
25
- * authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
26
- * tokenUrl: 'https://oauth2.googleapis.com/token',
27
- * scopes: ['https://www.googleapis.com/auth/spreadsheets'],
28
- * tokenSecretId: 'GOOGLE_OAUTH_TOKENS',
29
- * }
30
- * })
31
- *
32
- * // Platform-level OAuth (singleton)
33
- * wireCredential({
34
- * name: 'slack',
35
- * displayName: 'Slack',
36
- * type: 'singleton',
37
- * schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
38
- * oauth2: {
39
- * appCredentialSecretId: 'SLACK_OAUTH_APP',
40
- * authorizationUrl: 'https://slack.com/oauth/v2/authorize',
41
- * tokenUrl: 'https://slack.com/api/oauth.v2.access',
42
- * scopes: ['chat:write', 'channels:read'],
43
- * tokenSecretId: 'SLACK_OAUTH_TOKENS',
44
- * }
45
- * })
46
- * ```
47
- */
48
- export declare const wireCredential: <T>(_config: CoreCredential<T>) => void;
@@ -1,47 +0,0 @@
1
- /**
2
- * No-op function for declaring credentials.
3
- * This exists purely for TypeScript type checking and will be tree-shaken.
4
- * The CLI extracts metadata via AST parsing.
5
- *
6
- * @example
7
- * ```typescript
8
- * // Per-user API key
9
- * wireCredential({
10
- * name: 'stripe',
11
- * displayName: 'Stripe API Key',
12
- * type: 'wire',
13
- * schema: z.object({ apiKey: z.string() }),
14
- * })
15
- *
16
- * // Per-user OAuth
17
- * wireCredential({
18
- * name: 'google-sheets',
19
- * displayName: 'Google Sheets',
20
- * type: 'wire',
21
- * schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
22
- * oauth2: {
23
- * appCredentialSecretId: 'GOOGLE_OAUTH_APP',
24
- * authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
25
- * tokenUrl: 'https://oauth2.googleapis.com/token',
26
- * scopes: ['https://www.googleapis.com/auth/spreadsheets'],
27
- * tokenSecretId: 'GOOGLE_OAUTH_TOKENS',
28
- * }
29
- * })
30
- *
31
- * // Platform-level OAuth (singleton)
32
- * wireCredential({
33
- * name: 'slack',
34
- * displayName: 'Slack',
35
- * type: 'singleton',
36
- * schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
37
- * oauth2: {
38
- * appCredentialSecretId: 'SLACK_OAUTH_APP',
39
- * authorizationUrl: 'https://slack.com/oauth/v2/authorize',
40
- * tokenUrl: 'https://slack.com/api/oauth.v2.access',
41
- * scopes: ['chat:write', 'channels:read'],
42
- * tokenSecretId: 'SLACK_OAUTH_TOKENS',
43
- * }
44
- * })
45
- * ```
46
- */
47
- export const wireCredential = (_config) => { };
@@ -1,47 +0,0 @@
1
- import type { OAuth2Token } from './oauth2.types.js';
2
- import type { OAuth2CredentialConfig } from '../secret/secret.types.js';
3
- import type { SecretService } from '../../services/secret-service.js';
4
- export declare class OAuth2Client {
5
- private oauth2Config;
6
- private appCredentialSecretId;
7
- private secrets;
8
- private cachedToken;
9
- private cachedAppCredential;
10
- private refreshPromise;
11
- constructor(oauth2Config: OAuth2CredentialConfig, appCredentialSecretId: string, secrets: SecretService);
12
- /**
13
- * Make an authenticated request. Handles token caching and 401 refresh.
14
- */
15
- request(url: string, options?: RequestInit, timeoutMs?: number): Promise<Response>;
16
- /**
17
- * Get cached token or load from secrets.
18
- */
19
- getAccessToken(): Promise<string>;
20
- /**
21
- * Refresh token and update cache.
22
- * Uses a promise lock to prevent concurrent refresh attempts.
23
- */
24
- private refreshAndGetToken;
25
- /**
26
- * Perform the actual token refresh.
27
- */
28
- private doRefreshToken;
29
- /**
30
- * Check if token is valid (not expired).
31
- */
32
- private isTokenValid;
33
- /**
34
- * Get app credentials (cached).
35
- */
36
- private getAppCredential;
37
- /**
38
- * Generate OAuth2 authorization URL.
39
- * Used by CLI during `pikku oauth connect`.
40
- */
41
- getAuthorizationUrl(state: string, redirectUri: string): Promise<string>;
42
- /**
43
- * Exchange authorization code for tokens.
44
- * Used by CLI during `pikku oauth connect`.
45
- */
46
- exchangeCode(code: string, redirectUri: string): Promise<OAuth2Token>;
47
- }
@@ -1,263 +0,0 @@
1
- /**
2
- * OAuth2 client that acts as a service.
3
- * Created once in createSingletonServices, handles token caching and refresh internally.
4
- * Functions use it directly via oauth.request().
5
- *
6
- * @example
7
- * ```typescript
8
- * // In services.ts
9
- * export const createSingletonServices = async (config, { secrets }) => {
10
- * return {
11
- * slackOAuth: new OAuth2Client(
12
- * { tokenSecretId: 'SLACK_TOKENS', authorizationUrl: '...', tokenUrl: '...', scopes: [] },
13
- * 'SLACK_APP_CREDS',
14
- * secrets
15
- * ),
16
- * }
17
- * }
18
- *
19
- * // In function
20
- * export const postMessage = pikkuFunc(async ({ slackOAuth }, { channel, text }) => {
21
- * const response = await slackOAuth.request('https://slack.com/api/chat.postMessage', {
22
- * method: 'POST',
23
- * body: JSON.stringify({ channel, text }),
24
- * })
25
- * return response.json()
26
- * })
27
- * ```
28
- */
29
- const DEFAULT_TIMEOUT_MS = 30_000;
30
- const TOKEN_EXPIRY_BUFFER_MS = 60_000;
31
- /**
32
- * Helper to fetch with a timeout using AbortController.
33
- */
34
- async function fetchWithTimeout(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
35
- const controller = new AbortController();
36
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
37
- try {
38
- return await fetch(url, {
39
- ...options,
40
- signal: controller.signal,
41
- });
42
- }
43
- finally {
44
- clearTimeout(timeoutId);
45
- }
46
- }
47
- export class OAuth2Client {
48
- oauth2Config;
49
- appCredentialSecretId;
50
- secrets;
51
- cachedToken = null;
52
- cachedAppCredential = null;
53
- refreshPromise = null;
54
- constructor(oauth2Config, appCredentialSecretId, secrets) {
55
- this.oauth2Config = oauth2Config;
56
- this.appCredentialSecretId = appCredentialSecretId;
57
- this.secrets = secrets;
58
- }
59
- /**
60
- * Make an authenticated request. Handles token caching and 401 refresh.
61
- */
62
- async request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
63
- const token = await this.getAccessToken();
64
- const response = await fetchWithTimeout(url, {
65
- ...options,
66
- headers: {
67
- ...options?.headers,
68
- Authorization: `Bearer ${token}`,
69
- },
70
- }, timeoutMs);
71
- // Auto-retry on 401 after refresh
72
- if (response.status === 401) {
73
- const newToken = await this.refreshAndGetToken();
74
- return fetchWithTimeout(url, {
75
- ...options,
76
- headers: {
77
- ...options?.headers,
78
- Authorization: `Bearer ${newToken}`,
79
- },
80
- }, timeoutMs);
81
- }
82
- return response;
83
- }
84
- /**
85
- * Get cached token or load from secrets.
86
- */
87
- async getAccessToken() {
88
- // Return cached token if valid
89
- if (this.cachedToken) {
90
- if (this.isTokenValid(this.cachedToken)) {
91
- return this.cachedToken.accessToken;
92
- }
93
- // Token expired, try to refresh
94
- if (this.cachedToken.refreshToken) {
95
- return this.refreshAndGetToken();
96
- }
97
- // Token expired and no refresh token available
98
- throw new Error('OAuth2 token expired and no refresh token available');
99
- }
100
- // Load from secrets
101
- const token = await this.secrets.getSecret(this.oauth2Config.tokenSecretId);
102
- this.cachedToken = token;
103
- // Check if loaded token is expired and needs refresh
104
- if (!this.isTokenValid(token)) {
105
- if (token.refreshToken) {
106
- return this.refreshAndGetToken();
107
- }
108
- // Token expired and no refresh token available
109
- throw new Error('OAuth2 token expired and no refresh token available');
110
- }
111
- return token.accessToken;
112
- }
113
- /**
114
- * Refresh token and update cache.
115
- * Uses a promise lock to prevent concurrent refresh attempts.
116
- */
117
- async refreshAndGetToken() {
118
- if (this.refreshPromise) {
119
- const token = await this.refreshPromise;
120
- return token.accessToken;
121
- }
122
- this.refreshPromise = this.doRefreshToken();
123
- try {
124
- const token = await this.refreshPromise;
125
- return token.accessToken;
126
- }
127
- finally {
128
- this.refreshPromise = null;
129
- }
130
- }
131
- /**
132
- * Perform the actual token refresh.
133
- */
134
- async doRefreshToken() {
135
- if (!this.cachedToken?.refreshToken) {
136
- throw new Error('No refresh token available');
137
- }
138
- const appCredential = await this.getAppCredential();
139
- const params = new URLSearchParams({
140
- grant_type: 'refresh_token',
141
- refresh_token: this.cachedToken.refreshToken,
142
- client_id: appCredential.clientId,
143
- });
144
- if (appCredential.clientSecret) {
145
- params.set('client_secret', appCredential.clientSecret);
146
- }
147
- const response = await fetchWithTimeout(this.oauth2Config.tokenUrl, {
148
- method: 'POST',
149
- headers: {
150
- 'Content-Type': 'application/x-www-form-urlencoded',
151
- },
152
- body: params.toString(),
153
- });
154
- if (!response.ok) {
155
- throw new Error(`Token refresh failed: ${response.status}`);
156
- }
157
- const data = await response.json();
158
- if (!data.access_token || typeof data.access_token !== 'string') {
159
- throw new Error('Invalid token response: missing access_token');
160
- }
161
- const token = {
162
- accessToken: data.access_token,
163
- refreshToken: data.refresh_token || this.cachedToken.refreshToken,
164
- expiresAt: data.expires_in
165
- ? Date.now() + data.expires_in * 1000
166
- : undefined,
167
- tokenType: data.token_type || 'Bearer',
168
- scope: data.scope,
169
- };
170
- await this.secrets.setSecret(this.oauth2Config.tokenSecretId, token);
171
- this.cachedToken = token;
172
- return token;
173
- }
174
- /**
175
- * Check if token is valid (not expired).
176
- */
177
- isTokenValid(token) {
178
- if (!token.expiresAt) {
179
- return true; // No expiry info, assume valid
180
- }
181
- return token.expiresAt > Date.now() + TOKEN_EXPIRY_BUFFER_MS;
182
- }
183
- /**
184
- * Get app credentials (cached).
185
- */
186
- async getAppCredential() {
187
- if (this.cachedAppCredential) {
188
- return this.cachedAppCredential;
189
- }
190
- this.cachedAppCredential =
191
- await this.secrets.getSecret(this.appCredentialSecretId);
192
- return this.cachedAppCredential;
193
- }
194
- // ========================================
195
- // CLI-only methods (for pikku oauth connect)
196
- // ========================================
197
- /**
198
- * Generate OAuth2 authorization URL.
199
- * Used by CLI during `pikku oauth connect`.
200
- */
201
- async getAuthorizationUrl(state, redirectUri) {
202
- const appCredential = await this.getAppCredential();
203
- const params = new URLSearchParams({
204
- response_type: 'code',
205
- client_id: appCredential.clientId,
206
- redirect_uri: redirectUri,
207
- scope: this.oauth2Config.scopes.join(' '),
208
- state,
209
- });
210
- // Add PKCE if enabled
211
- // Note: code_verifier should be stored and passed to exchangeCode
212
- // For now, PKCE implementation is simplified
213
- // Add any additional params
214
- if (this.oauth2Config.additionalParams) {
215
- for (const [key, value] of Object.entries(this.oauth2Config.additionalParams)) {
216
- params.set(key, value);
217
- }
218
- }
219
- return `${this.oauth2Config.authorizationUrl}?${params.toString()}`;
220
- }
221
- /**
222
- * Exchange authorization code for tokens.
223
- * Used by CLI during `pikku oauth connect`.
224
- */
225
- async exchangeCode(code, redirectUri) {
226
- const appCredential = await this.getAppCredential();
227
- const params = new URLSearchParams({
228
- grant_type: 'authorization_code',
229
- code,
230
- redirect_uri: redirectUri,
231
- client_id: appCredential.clientId,
232
- });
233
- if (appCredential.clientSecret) {
234
- params.set('client_secret', appCredential.clientSecret);
235
- }
236
- const response = await fetchWithTimeout(this.oauth2Config.tokenUrl, {
237
- method: 'POST',
238
- headers: {
239
- 'Content-Type': 'application/x-www-form-urlencoded',
240
- },
241
- body: params.toString(),
242
- });
243
- if (!response.ok) {
244
- throw new Error(`Token exchange failed: ${response.status}`);
245
- }
246
- const data = await response.json();
247
- if (!data.access_token || typeof data.access_token !== 'string') {
248
- throw new Error('Invalid token response: missing access_token');
249
- }
250
- const token = {
251
- accessToken: data.access_token,
252
- refreshToken: data.refresh_token,
253
- expiresAt: data.expires_in
254
- ? Date.now() + data.expires_in * 1000
255
- : undefined,
256
- tokenType: data.token_type || 'Bearer',
257
- scope: data.scope,
258
- };
259
- // Cache the token
260
- this.cachedToken = token;
261
- return token;
262
- }
263
- }
@@ -1,35 +0,0 @@
1
- import type { CredentialDefinitionsMeta } from '../credential/credential.types.js';
2
- export type CreateOAuth2HandlerOptions = {
3
- credentialsMeta: CredentialDefinitionsMeta;
4
- basePath?: string;
5
- };
6
- /**
7
- * Creates OAuth2 route handlers for user credential management.
8
- *
9
- * Returns individual handler functions for connect/callback/disconnect/status
10
- * that handle the OAuth2 authorization code flow and store tokens in CredentialService.
11
- *
12
- * @example
13
- * ```typescript
14
- * const oauth2 = createOAuth2Handler({ credentialsMeta })
15
- *
16
- * const oauth2Routes = defineHTTPRoutes({
17
- * auth: true,
18
- * basePath: '/credentials',
19
- * routes: {
20
- * connect: { method: 'get', route: '/:name/connect', func: oauth2.connect },
21
- * callback: { method: 'get', route: '/:name/callback', func: oauth2.callback, auth: false },
22
- * disconnect: { method: 'delete', route: '/:name', func: oauth2.disconnect },
23
- * status: { method: 'get', route: '/:name/status', func: oauth2.status },
24
- * },
25
- * })
26
- *
27
- * wireHTTPRoutes({ routes: { credentials: oauth2Routes } })
28
- * ```
29
- */
30
- export declare const createOAuth2Handler: (options: CreateOAuth2HandlerOptions) => {
31
- connect: any;
32
- callback: any;
33
- disconnect: any;
34
- status: any;
35
- };