@pikku/core 0.12.67 → 0.12.70
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 +397 -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-workflow-service.d.ts +2 -2
- package/dist/services/in-memory-workflow-service.js +2 -2
- 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/types/core.types.d.ts +21 -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-prepare.d.ts +18 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +26 -4
- package/dist/wirings/cli/command-parser.js +11 -1
- package/dist/wirings/queue/index.d.ts +1 -1
- package/dist/wirings/queue/queue.types.d.ts +30 -0
- 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/index.d.ts +13 -2
- package/dist/wirings/workflow/index.js +15 -0
- 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 +118 -12
- package/dist/wirings/workflow/pikku-workflow-service.js +166 -153
- 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 +119 -2
- 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-workflow-service.test.ts +50 -1
- package/src/services/in-memory-workflow-service.ts +3 -2
- 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/types/core.types.ts +27 -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-prepare.test.ts +29 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +38 -4
- package/src/wirings/cli/command-parser.test.ts +60 -0
- package/src/wirings/cli/command-parser.ts +12 -1
- package/src/wirings/queue/index.ts +2 -0
- package/src/wirings/queue/queue.types.ts +32 -0
- 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/index.ts +74 -0
- package/src/wirings/workflow/pikku-scenario-service.ts +682 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +126 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +306 -228
- 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-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 +441 -8
- package/src/wirings/workflow/scenario-step.types.ts +157 -0
- package/src/wirings/workflow/workflow.types.ts +137 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readScenarioHttpResponse } from './scenario-actors-service.js';
|
|
1
2
|
import { runConversation } from '../wirings/actor-flow/run-conversation.js';
|
|
3
|
+
import { createCookieJar, } from '../wirings/workflow/scenario-cookie-jar.js';
|
|
2
4
|
import { getSingletonServices } from '../pikku-state.js';
|
|
3
5
|
import { AIProviderNotConfiguredError } from '../errors/errors.js';
|
|
4
6
|
/**
|
|
@@ -13,26 +15,42 @@ export class HttpScenarioActor {
|
|
|
13
15
|
name;
|
|
14
16
|
actorConfig;
|
|
15
17
|
config;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
jar;
|
|
19
|
+
/**
|
|
20
|
+
* Whether `login()` has succeeded since the last time the session was
|
|
21
|
+
* dropped. The jar cannot answer this — a target may set a cookie before
|
|
22
|
+
* anyone signs in, and it would then look like a session that was never
|
|
23
|
+
* established.
|
|
24
|
+
*/
|
|
25
|
+
signedIn = false;
|
|
18
26
|
constructor(name, actorConfig, config) {
|
|
19
27
|
this.name = name;
|
|
20
28
|
this.actorConfig = actorConfig;
|
|
21
29
|
this.config = config;
|
|
22
|
-
this.
|
|
30
|
+
this.jar = createCookieJar(config.apiUrl);
|
|
23
31
|
}
|
|
24
32
|
get email() {
|
|
25
33
|
return this.actorConfig.email;
|
|
26
34
|
}
|
|
27
35
|
async invoke(rpcName, data) {
|
|
28
|
-
const
|
|
29
|
-
|
|
36
|
+
const res = await this.invokeRaw(rpcName, data);
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`[scenario] '${rpcName}' as '${this.name}' returned ${res.status}: ${res.serialized.slice(0, 300)}`);
|
|
39
|
+
}
|
|
40
|
+
return res.body;
|
|
41
|
+
}
|
|
42
|
+
async invokeRaw(rpcName, data, options) {
|
|
43
|
+
if (!this.signedIn) {
|
|
44
|
+
await this.login();
|
|
45
|
+
}
|
|
46
|
+
let res = await this.postRpc(rpcName, data, options?.headers);
|
|
30
47
|
if (res.status === 401) {
|
|
31
48
|
// Session expired mid-run — re-login once and retry.
|
|
32
|
-
this.
|
|
33
|
-
|
|
49
|
+
this.signOut();
|
|
50
|
+
await this.login();
|
|
51
|
+
res = await this.postRpc(rpcName, data, options?.headers);
|
|
34
52
|
}
|
|
35
|
-
return
|
|
53
|
+
return readScenarioHttpResponse(res);
|
|
36
54
|
}
|
|
37
55
|
async converse(options) {
|
|
38
56
|
const { aiAgentRunner } = getSingletonServices();
|
|
@@ -46,8 +64,8 @@ export class HttpScenarioActor {
|
|
|
46
64
|
const threadId = globalThis.crypto.randomUUID();
|
|
47
65
|
const resourceId = `actor:${this.name}`;
|
|
48
66
|
return runConversation({
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
actor: this.actorConfig,
|
|
68
|
+
actorName: this.actorConfig.name ?? this.name,
|
|
51
69
|
agentName: options.agent,
|
|
52
70
|
task: options.task,
|
|
53
71
|
evaluate: options.evaluate,
|
|
@@ -88,19 +106,16 @@ export class HttpScenarioActor {
|
|
|
88
106
|
async postAgent(subPath, body) {
|
|
89
107
|
const rpcPath = this.config.rpcPath ?? '/rpc';
|
|
90
108
|
const url = `${this.config.apiUrl}${rpcPath}/${subPath}`;
|
|
91
|
-
const send = (
|
|
109
|
+
const send = () => this.jar.fetch(url, {
|
|
92
110
|
method: 'POST',
|
|
93
|
-
headers: {
|
|
94
|
-
'content-type': 'application/json',
|
|
95
|
-
origin: this.origin,
|
|
96
|
-
...(cookie ? { cookie } : {}),
|
|
97
|
-
},
|
|
111
|
+
headers: { 'content-type': 'application/json' },
|
|
98
112
|
body: JSON.stringify(body),
|
|
99
113
|
});
|
|
100
|
-
let res = await send(
|
|
114
|
+
let res = await send();
|
|
101
115
|
if (res.status === 401) {
|
|
102
|
-
this.
|
|
103
|
-
|
|
116
|
+
this.signOut();
|
|
117
|
+
await this.login();
|
|
118
|
+
res = await send();
|
|
104
119
|
}
|
|
105
120
|
if (!res.ok) {
|
|
106
121
|
const text = (await res.text().catch(() => '')).slice(0, 300);
|
|
@@ -111,33 +126,24 @@ export class HttpScenarioActor {
|
|
|
111
126
|
const text = await res.text();
|
|
112
127
|
return text ? JSON.parse(text) : undefined;
|
|
113
128
|
}
|
|
114
|
-
async postRpc(rpcName, data,
|
|
129
|
+
async postRpc(rpcName, data, extraHeaders) {
|
|
115
130
|
const rpcPath = this.config.rpcPath ?? '/rpc';
|
|
116
|
-
return fetch(`${this.config.apiUrl}${rpcPath}/${rpcName}`, {
|
|
131
|
+
return this.jar.fetch(`${this.config.apiUrl}${rpcPath}/${rpcName}`, {
|
|
117
132
|
method: 'POST',
|
|
118
|
-
headers: {
|
|
119
|
-
'content-type': 'application/json',
|
|
120
|
-
origin: this.origin,
|
|
121
|
-
cookie,
|
|
122
|
-
},
|
|
133
|
+
headers: { 'content-type': 'application/json', ...extraHeaders },
|
|
123
134
|
body: JSON.stringify({ data }),
|
|
124
135
|
});
|
|
125
136
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
if (res.status === 204)
|
|
132
|
-
return undefined;
|
|
133
|
-
const text = await res.text();
|
|
134
|
-
return text ? JSON.parse(text) : undefined;
|
|
137
|
+
/** Drop the session, so the next call signs in again before it goes out. */
|
|
138
|
+
signOut() {
|
|
139
|
+
this.jar.clear();
|
|
140
|
+
this.signedIn = false;
|
|
135
141
|
}
|
|
136
142
|
async login() {
|
|
137
143
|
const signInPath = this.config.signInPath ?? '/auth/sign-in/actor';
|
|
138
|
-
const res = await fetch(`${this.config.apiUrl}${signInPath}`, {
|
|
144
|
+
const res = await this.jar.fetch(`${this.config.apiUrl}${signInPath}`, {
|
|
139
145
|
method: 'POST',
|
|
140
|
-
headers: { 'content-type': 'application/json'
|
|
146
|
+
headers: { 'content-type': 'application/json' },
|
|
141
147
|
body: JSON.stringify({
|
|
142
148
|
email: this.actorConfig.email,
|
|
143
149
|
name: this.actorConfig.name ?? this.name,
|
|
@@ -148,16 +154,12 @@ export class HttpScenarioActor {
|
|
|
148
154
|
const body = (await res.text().catch(() => '')).slice(0, 300);
|
|
149
155
|
throw new Error(`[scenario] actor sign-in failed for '${this.name}' (${res.status}): ${body}`);
|
|
150
156
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.filter(Boolean)
|
|
155
|
-
.join('; ');
|
|
156
|
-
if (!cookie) {
|
|
157
|
+
// What proves a session was established is this response setting a cookie,
|
|
158
|
+
// not the jar being non-empty — the target may have set one earlier.
|
|
159
|
+
if (res.headers.getSetCookie().length === 0) {
|
|
157
160
|
throw new Error(`[scenario] actor sign-in for '${this.name}' returned no session cookie`);
|
|
158
161
|
}
|
|
159
|
-
this.
|
|
160
|
-
return cookie;
|
|
162
|
+
this.signedIn = true;
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
/** Normalize an agentRun/agentApprove HTTP response into a TargetAgentReply. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service.js';
|
|
2
2
|
import type { SerializedError } from '../types/core.types.js';
|
|
3
|
-
import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
3
|
+
import type { WorkflowPlannedStep, WorkflowQueueOptions, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
4
4
|
/**
|
|
5
5
|
* In-memory implementation of WorkflowService for inline-only execution
|
|
6
6
|
*
|
|
@@ -17,7 +17,7 @@ import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunW
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export declare class InMemoryWorkflowService extends PikkuWorkflowService implements WorkflowRunService {
|
|
20
|
-
constructor();
|
|
20
|
+
constructor(options?: WorkflowQueueOptions);
|
|
21
21
|
private sleepTimers;
|
|
22
22
|
private runs;
|
|
23
23
|
private steps;
|
|
@@ -17,8 +17,8 @@ import { isExpectedError } from '../errors/error-handler.js';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
20
|
-
constructor() {
|
|
21
|
-
super({ wireQueues: false });
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super({ ...options, wireQueues: false });
|
|
22
22
|
}
|
|
23
23
|
sleepTimers = new Set();
|
|
24
24
|
runs = new Map();
|
package/dist/services/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
|
19
19
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
20
20
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
21
21
|
export type { ContentService, SignContentKeyArgs, SignURLArgs, GetUploadURLArgs, UploadURLResult, BucketKeyArgs, WriteFileArgs, CopyFileArgs, } from './content-service.js';
|
|
22
|
-
export type { ScenarioActor, ScenarioActorConfig, ScenarioActors, } from './scenario-actors-service.js';
|
|
22
|
+
export type { ScenarioActor, ScenarioActorConfig, ScenarioActorOf, ScenarioActors, ScenarioInvokeOptions, ScenarioRpcMap, ScenarioHttpResponse, } from './scenario-actors-service.js';
|
|
23
|
+
export { readScenarioHttpResponse } from './scenario-actors-service.js';
|
|
23
24
|
export { HttpScenarioActor, createHttpScenarioActors, type HttpScenarioActorsConfig, } from './http-scenario-actors.js';
|
|
24
25
|
export type { JWTService } from './jwt-service.js';
|
|
25
26
|
export type { EmailService, EmailTemplateReference, SendEmailInput, SendEmailResult, SendHTMLEmailInput, SendTemplateEmailInput, SendTextEmailInput, } from './email-service.js';
|
package/dist/services/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export { InMemoryQueueService } from './in-memory-queue-service.js';
|
|
|
18
18
|
export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
19
19
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
20
20
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
21
|
+
export { readScenarioHttpResponse } from './scenario-actors-service.js';
|
|
21
22
|
export { HttpScenarioActor, createHttpScenarioActors, } from './http-scenario-actors.js';
|
|
22
23
|
export { DEFAULT_WEBHOOK_RETRIES, DEFAULT_WEBHOOK_SIGNATURE_HEADER, PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME, WebhookService, } from './webhook-service.js';
|
|
23
24
|
export { TypedCredentialService } from './typed-credential-service.js';
|
|
@@ -5,7 +5,7 @@ import type { ScheduledTasksMeta } from '../wirings/scheduler/scheduler.types.js
|
|
|
5
5
|
import type { QueueWorkersMeta } from '../wirings/queue/queue.types.js';
|
|
6
6
|
import type { CLIMeta } from '../wirings/cli/cli.types.js';
|
|
7
7
|
import type { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '../wirings/mcp/mcp.types.js';
|
|
8
|
-
import type { WorkflowsMeta } from '../wirings/workflow/workflow.types.js';
|
|
8
|
+
import type { FeaturesMeta, WorkflowsMeta } from '../wirings/workflow/workflow.types.js';
|
|
9
9
|
import type { ScenarioActorConfig } from './scenario-actors-service.js';
|
|
10
10
|
import type { TriggerMeta, TriggerSourceMeta } from '../wirings/trigger/trigger.types.js';
|
|
11
11
|
import type { SecretDefinitionsMeta } from '../wirings/secret/secret.types.js';
|
|
@@ -151,6 +151,7 @@ export interface MetaService {
|
|
|
151
151
|
getRpcMeta(): Promise<RPCMetaRecord>;
|
|
152
152
|
getWorkflowMeta(): Promise<WorkflowsMeta>;
|
|
153
153
|
getScenarioActorsMeta(): Promise<Record<string, ScenarioActorConfig>>;
|
|
154
|
+
getFeaturesMeta(): Promise<FeaturesMeta>;
|
|
154
155
|
getTriggerMeta(): Promise<TriggerMeta>;
|
|
155
156
|
getTriggerSourceMeta(): Promise<TriggerSourceMeta>;
|
|
156
157
|
getFunctionsMeta(): Promise<FunctionsMeta>;
|
|
@@ -183,6 +184,7 @@ export declare class LocalMetaService implements MetaService {
|
|
|
183
184
|
private rpcMetaCache;
|
|
184
185
|
private workflowMetaCache;
|
|
185
186
|
private scenarioActorsMetaCache;
|
|
187
|
+
private featuresMetaCache;
|
|
186
188
|
private triggerMetaCache;
|
|
187
189
|
private triggerSourceMetaCache;
|
|
188
190
|
private functionsMetaCache;
|
|
@@ -212,8 +214,10 @@ export declare class LocalMetaService implements MetaService {
|
|
|
212
214
|
getMcpMeta(): Promise<MCPMeta>;
|
|
213
215
|
getGatewayMeta(): Promise<GatewaysMeta>;
|
|
214
216
|
getRpcMeta(): Promise<RPCMetaRecord>;
|
|
217
|
+
private readWorkflowMetaDir;
|
|
215
218
|
getWorkflowMeta(): Promise<WorkflowsMeta>;
|
|
216
219
|
getScenarioActorsMeta(): Promise<Record<string, ScenarioActorConfig>>;
|
|
220
|
+
getFeaturesMeta(): Promise<FeaturesMeta>;
|
|
217
221
|
getTriggerMeta(): Promise<TriggerMeta>;
|
|
218
222
|
getTriggerSourceMeta(): Promise<TriggerSourceMeta>;
|
|
219
223
|
getFunctionsMeta(): Promise<FunctionsMeta>;
|
|
@@ -18,6 +18,7 @@ export class LocalMetaService {
|
|
|
18
18
|
rpcMetaCache = null;
|
|
19
19
|
workflowMetaCache = null;
|
|
20
20
|
scenarioActorsMetaCache = null;
|
|
21
|
+
featuresMetaCache = null;
|
|
21
22
|
triggerMetaCache = null;
|
|
22
23
|
triggerSourceMetaCache = null;
|
|
23
24
|
functionsMetaCache = null;
|
|
@@ -115,6 +116,7 @@ export class LocalMetaService {
|
|
|
115
116
|
this.rpcMetaCache = null;
|
|
116
117
|
this.workflowMetaCache = null;
|
|
117
118
|
this.scenarioActorsMetaCache = null;
|
|
119
|
+
this.featuresMetaCache = null;
|
|
118
120
|
this.triggerMetaCache = null;
|
|
119
121
|
this.triggerSourceMetaCache = null;
|
|
120
122
|
this.functionsMetaCache = null;
|
|
@@ -223,27 +225,36 @@ export class LocalMetaService {
|
|
|
223
225
|
return this.rpcMetaCache;
|
|
224
226
|
}
|
|
225
227
|
}
|
|
228
|
+
async readWorkflowMetaDir(dir, into) {
|
|
229
|
+
const files = await this.readDir(dir);
|
|
230
|
+
const jsonFiles = files.filter((f) => f.endsWith('.gen.json'));
|
|
231
|
+
const verboseFiles = jsonFiles.filter((f) => f.includes('-verbose'));
|
|
232
|
+
const minimalFiles = jsonFiles.filter((f) => !f.includes('-verbose'));
|
|
233
|
+
const verboseNames = new Set(verboseFiles.map((f) => f.replace('-verbose.gen.json', '')));
|
|
234
|
+
const filesToRead = [
|
|
235
|
+
...verboseFiles,
|
|
236
|
+
...minimalFiles.filter((f) => !verboseNames.has(f.replace('.gen.json', ''))),
|
|
237
|
+
];
|
|
238
|
+
await Promise.all(filesToRead.map(async (file) => {
|
|
239
|
+
const content = await this.readFile(`${dir}/${file}`);
|
|
240
|
+
if (content) {
|
|
241
|
+
const meta = JSON.parse(content);
|
|
242
|
+
into[meta.name] = meta;
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
226
246
|
async getWorkflowMeta() {
|
|
227
247
|
if (this.workflowMetaCache)
|
|
228
248
|
return this.workflowMetaCache;
|
|
229
249
|
try {
|
|
230
|
-
const files = await this.readDir('workflow/meta');
|
|
231
|
-
const jsonFiles = files.filter((f) => f.endsWith('.gen.json'));
|
|
232
|
-
const verboseFiles = jsonFiles.filter((f) => f.includes('-verbose'));
|
|
233
|
-
const minimalFiles = jsonFiles.filter((f) => !f.includes('-verbose'));
|
|
234
|
-
const verboseNames = new Set(verboseFiles.map((f) => f.replace('-verbose.gen.json', '')));
|
|
235
|
-
const filesToRead = [
|
|
236
|
-
...verboseFiles,
|
|
237
|
-
...minimalFiles.filter((f) => !verboseNames.has(f.replace('.gen.json', ''))),
|
|
238
|
-
];
|
|
239
250
|
const result = {};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
// Scenarios keep their meta in `scenarios/meta` so nothing app-facing
|
|
252
|
+
// imports them, but they are still workflows to anything reading meta off
|
|
253
|
+
// disk — the console's scenario list among them.
|
|
254
|
+
await Promise.all([
|
|
255
|
+
this.readWorkflowMetaDir('workflow/meta', result),
|
|
256
|
+
this.readWorkflowMetaDir('scenarios/meta', result),
|
|
257
|
+
]);
|
|
247
258
|
this.workflowMetaCache = result;
|
|
248
259
|
return this.workflowMetaCache;
|
|
249
260
|
}
|
|
@@ -260,6 +271,13 @@ export class LocalMetaService {
|
|
|
260
271
|
this.scenarioActorsMetaCache = content ? JSON.parse(content) : {};
|
|
261
272
|
return this.scenarioActorsMetaCache;
|
|
262
273
|
}
|
|
274
|
+
async getFeaturesMeta() {
|
|
275
|
+
if (this.featuresMetaCache)
|
|
276
|
+
return this.featuresMetaCache;
|
|
277
|
+
const content = await this.readFile('scenarios/features.gen.json');
|
|
278
|
+
this.featuresMetaCache = content ? JSON.parse(content) : {};
|
|
279
|
+
return this.featuresMetaCache;
|
|
280
|
+
}
|
|
263
281
|
async getTriggerMeta() {
|
|
264
282
|
if (this.triggerMetaCache)
|
|
265
283
|
return this.triggerMetaCache;
|
|
@@ -277,8 +295,16 @@ export class LocalMetaService {
|
|
|
277
295
|
async getFunctionsMeta() {
|
|
278
296
|
if (this.functionsMetaCache)
|
|
279
297
|
return this.functionsMetaCache;
|
|
280
|
-
const content = await
|
|
281
|
-
|
|
298
|
+
const [content, scenarioContent] = await Promise.all([
|
|
299
|
+
this.readMetaJson('function', 'pikku-functions-meta'),
|
|
300
|
+
// Scenario steps register only into the scenario bootstrap, but they are
|
|
301
|
+
// still functions to anything reading meta off disk.
|
|
302
|
+
this.readMetaJson('scenarios', 'pikku-scenario-functions-meta'),
|
|
303
|
+
]);
|
|
304
|
+
this.functionsMetaCache = {
|
|
305
|
+
...(content ? JSON.parse(content) : {}),
|
|
306
|
+
...(scenarioContent ? JSON.parse(scenarioContent) : {}),
|
|
307
|
+
};
|
|
282
308
|
return this.functionsMetaCache;
|
|
283
309
|
}
|
|
284
310
|
async getMiddlewareGroupsMeta() {
|
|
@@ -1,12 +1,103 @@
|
|
|
1
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];
|
|
2
87
|
/** A synthetic user (a user row flagged `actor`) that workflow steps run as over the real transport */
|
|
3
|
-
export interface ScenarioActor<TAgentName extends string = string> {
|
|
88
|
+
export interface ScenarioActor<TAgentName extends string = string, TRpcMap extends ScenarioRpcMap = ScenarioRpcMap> {
|
|
4
89
|
/** Stable actor name (the key in pikku.config.json's actor registry). */
|
|
5
90
|
readonly name: string;
|
|
6
91
|
/** The actor's user email — flows use it for invites/lookups. */
|
|
7
92
|
readonly email: string;
|
|
8
93
|
/** Invoke an exposed RPC as this actor over the real transport. */
|
|
9
|
-
invoke(rpcName:
|
|
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>;
|
|
10
101
|
/** Converse with a Pikku AI agent in this actor's persona and return its verdict */
|
|
11
102
|
converse(options: ConverseOptions<TAgentName>): Promise<ActorFlowVerdict>;
|
|
12
103
|
}
|
|
@@ -16,6 +107,21 @@ export interface ScenarioActorConfig {
|
|
|
16
107
|
name?: string;
|
|
17
108
|
jobTitle?: string;
|
|
18
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[];
|
|
19
125
|
}
|
|
20
126
|
/** The injected `actors` service: actor name → actor. */
|
|
21
127
|
export type ScenarioActors = Record<string, ScenarioActor>;
|
|
@@ -1 +1,40 @@
|
|
|
1
|
-
|
|
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
|
+
}));
|
|
@@ -12,6 +12,7 @@ import type { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js
|
|
|
12
12
|
import type { PikkuQueue, QueueService } from '../wirings/queue/queue.types.js';
|
|
13
13
|
import type { PikkuCLI } from '../wirings/cli/cli.types.js';
|
|
14
14
|
import type { PikkuWorkflowWire, PikkuScenarioWire, WorkflowService, WorkflowServiceConfig, WorkflowStepWire } from '../wirings/workflow/workflow.types.js';
|
|
15
|
+
import type { PikkuBrowserWire, PikkuScenarioStepWire } from '../wirings/workflow/scenario-step.types.js';
|
|
15
16
|
import type { PikkuGraphWire } from '../wirings/workflow/graph/workflow-graph.types.js';
|
|
16
17
|
import type { PikkuTrigger } from '../wirings/trigger/trigger.types.js';
|
|
17
18
|
import type { PikkuGateway } from '../wirings/gateway/gateway.types.js';
|
|
@@ -19,7 +20,7 @@ import type { SchedulerService } from '../services/scheduler-service.js';
|
|
|
19
20
|
import type { DeploymentService } from '../services/deployment-service.js';
|
|
20
21
|
import type { AIStorageService } from '../services/ai-storage-service.js';
|
|
21
22
|
import type { ContentService } from '../services/content-service.js';
|
|
22
|
-
import type { ScenarioActors } from '../services/scenario-actors-service.js';
|
|
23
|
+
import type { ScenarioActorOf, ScenarioActors } from '../services/scenario-actors-service.js';
|
|
23
24
|
import type { AIAgentRunnerService } from '../services/ai-agent-runner-service.js';
|
|
24
25
|
import type { AIEmbeddingService } from '../services/ai-embedding-service.js';
|
|
25
26
|
import type { AIRunStateService } from '../services/ai-run-state-service.js';
|
|
@@ -85,6 +86,15 @@ export type FunctionRuntimeMeta = {
|
|
|
85
86
|
scopes?: string[];
|
|
86
87
|
expose?: boolean;
|
|
87
88
|
remote?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* A step RPC: a name dispatched by a scenario run and refused everywhere
|
|
91
|
+
* else. It sits alongside `expose` (public) and `remote` as a kind of RPC
|
|
92
|
+
* rather than a separate concept — a step is invoked by name exactly as an
|
|
93
|
+
* RPC is, which is why a run records the step function in its `rpcName`.
|
|
94
|
+
* What makes it its own kind is that it is never network-callable: a step
|
|
95
|
+
* may drive a browser or assert against fixtures.
|
|
96
|
+
*/
|
|
97
|
+
scenarioStep?: boolean;
|
|
88
98
|
mcp?: boolean;
|
|
89
99
|
readonly?: boolean;
|
|
90
100
|
deploy?: 'serverless' | 'server' | 'auto';
|
|
@@ -95,6 +105,10 @@ export type FunctionRuntimeMeta = {
|
|
|
95
105
|
workflowRetries?: number;
|
|
96
106
|
/** Timeout when this function is used as a workflow step (e.g. '30s', '5m'). */
|
|
97
107
|
workflowTimeout?: string;
|
|
108
|
+
/** Scenario steps only: this step drives a browser, so the runner must provision one and an actor is mandatory. */
|
|
109
|
+
scenarioStepBrowser?: boolean;
|
|
110
|
+
/** Scenario steps only: the prose a reporter renders, with `{placeholders}` filled from the step's recorded input. */
|
|
111
|
+
scenarioStepTemplate?: string;
|
|
98
112
|
version?: number;
|
|
99
113
|
approvalRequired?: boolean;
|
|
100
114
|
approvalDescription?: string;
|
|
@@ -306,7 +320,7 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
306
320
|
/**
|
|
307
321
|
* Represents different forms of wire within Pikku and the outside world.
|
|
308
322
|
*/
|
|
309
|
-
export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown, TypedScenario extends PikkuScenarioWire | never = PikkuScenarioWire> = {
|
|
323
|
+
export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown, TypedScenario extends PikkuScenarioWire | never = PikkuScenarioWire, TypedActors extends ScenarioActors = ScenarioActors> = {
|
|
310
324
|
/** Always present — lazily initialised on first access for every function invocation */
|
|
311
325
|
rpc: TypedRPC;
|
|
312
326
|
} & Partial<{
|
|
@@ -326,7 +340,11 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
326
340
|
cli: PikkuCLI;
|
|
327
341
|
workflow: TypedWorkflow;
|
|
328
342
|
scenario: TypedScenario;
|
|
329
|
-
actors:
|
|
343
|
+
actors: TypedActors;
|
|
344
|
+
/** Present on every scenario step invocation */
|
|
345
|
+
scenarioStep: PikkuScenarioStepWire<ScenarioActorOf<TypedActors>>;
|
|
346
|
+
/** Present only when the runner provisioned a browser for this step */
|
|
347
|
+
browser: PikkuBrowserWire;
|
|
330
348
|
workflowStep: WorkflowStepWire;
|
|
331
349
|
graph: PikkuGraphWire;
|
|
332
350
|
trigger: PikkuTrigger<TriggerOutput>;
|
|
@@ -9,7 +9,7 @@ import type { CoreAIAgent, AIAgentMeta } from '../wirings/ai-agent/ai-agent.type
|
|
|
9
9
|
import type { CoreGateway, GatewaysMeta } from '../wirings/gateway/gateway.types.js';
|
|
10
10
|
import type { CoreQueueWorker, QueueWorkersMeta } from '../wirings/queue/queue.types.js';
|
|
11
11
|
import type { CoreScheduledTask, ScheduledTasksMeta } from '../wirings/scheduler/scheduler.types.js';
|
|
12
|
-
import type { CoreWorkflow, WorkflowsRuntimeMeta } from '../wirings/workflow/workflow.types.js';
|
|
12
|
+
import type { CoreWorkflow, CoreFeature, WorkflowsRuntimeMeta } from '../wirings/workflow/workflow.types.js';
|
|
13
13
|
import type { CoreTrigger, CoreTriggerSource, TriggerMeta, TriggerSourceMeta } from '../wirings/trigger/trigger.types.js';
|
|
14
14
|
import type { FunctionsMeta, CorePikkuMiddleware, CorePikkuMiddlewareGroup, CreateConfig, CreateSingletonServices, CreateWireServices, CoreConfig, CoreSingletonServices, CoreServices, CoreUserSession } from './core.types.js';
|
|
15
15
|
import type { CorePikkuChannelMiddleware } from '../wirings/channel/channel.types.js';
|
|
@@ -77,6 +77,8 @@ export interface PikkuPackageState {
|
|
|
77
77
|
};
|
|
78
78
|
workflows: {
|
|
79
79
|
registrations: Map<string, CoreWorkflow>;
|
|
80
|
+
/** Scenario groups declared with `pikkuFeature`, keyed by export name. */
|
|
81
|
+
features: Map<string, CoreFeature>;
|
|
80
82
|
meta: WorkflowsRuntimeMeta;
|
|
81
83
|
};
|
|
82
84
|
trigger: {
|
|
@@ -20,7 +20,7 @@ export interface ConverseOptions<TAgentName extends string = string> {
|
|
|
20
20
|
evaluate: string;
|
|
21
21
|
/** How the actor answers the agent's tool-approval requests. Default `'in-persona'`. */
|
|
22
22
|
approvals?: ActorFlowApprovalPolicy;
|
|
23
|
-
/** Model the
|
|
23
|
+
/** Model the actor uses for its own turns/decisions. Falls back to the actor service default. */
|
|
24
24
|
model?: string;
|
|
25
25
|
/** Hard cap on conversation turns before forcing evaluation. Default 12. */
|
|
26
26
|
maxTurns?: number;
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* over HTTP.
|
|
9
9
|
*/
|
|
10
10
|
export type { ActorFlowApprovalPolicy, ActorFlowVerdict, ConverseOptions, TargetAgentReply, TargetPendingApproval, TargetAgentDriver, } from './actor-flow.types.js';
|
|
11
|
-
export { runConversation, type RunConversationParams, type
|
|
11
|
+
export { runConversation, type RunConversationParams, type ActorLLM, } from './run-conversation.js';
|