@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,20 +1,20 @@
|
|
|
1
1
|
import type { ActorFlowApprovalPolicy, ActorFlowVerdict, TargetAgentDriver } from './actor-flow.types.js';
|
|
2
2
|
import type { ScenarioActorConfig } from '../../services/scenario-actors-service.js';
|
|
3
3
|
import type { AIAgentRunnerParams, AIAgentStepResult } from '../../services/ai-agent-runner-service.js';
|
|
4
|
-
/** The LLM call the
|
|
5
|
-
export type
|
|
4
|
+
/** The LLM call the actor uses for its own turns/decisions/evaluation. */
|
|
5
|
+
export type ActorLLM = (params: AIAgentRunnerParams) => Promise<AIAgentStepResult>;
|
|
6
6
|
export interface RunConversationParams {
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
/** Stable
|
|
10
|
-
|
|
7
|
+
/** The actor's own config (personality/jobTitle/name), which shapes how it talks. */
|
|
8
|
+
actor: ScenarioActorConfig;
|
|
9
|
+
/** Stable actor name (for transcript labelling). */
|
|
10
|
+
actorName: string;
|
|
11
11
|
/** What the actor is trying to get the target agent to accomplish. */
|
|
12
12
|
task: string;
|
|
13
13
|
/** Natural-language success criterion the actor evaluates at the end. */
|
|
14
14
|
evaluate: string;
|
|
15
15
|
/** How the actor answers the target agent's tool-approval requests. */
|
|
16
16
|
approvals?: ActorFlowApprovalPolicy;
|
|
17
|
-
/** Model the
|
|
17
|
+
/** Model the actor uses for its own turns/decisions. */
|
|
18
18
|
model: string;
|
|
19
19
|
/** Hard cap on conversation turns. Default 12. */
|
|
20
20
|
maxTurns?: number;
|
|
@@ -22,13 +22,13 @@ export interface RunConversationParams {
|
|
|
22
22
|
maxApprovalRounds?: number;
|
|
23
23
|
/** Transport that drives the target agent (HTTP in production). */
|
|
24
24
|
target: TargetAgentDriver;
|
|
25
|
-
/** The
|
|
26
|
-
llm:
|
|
25
|
+
/** The actor's own LLM. */
|
|
26
|
+
llm: ActorLLM;
|
|
27
27
|
/** Display name of the target agent (transcript labelling). */
|
|
28
28
|
agentName: string;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Run a conversation: an LLM-driven
|
|
31
|
+
* Run a conversation: an LLM-driven actor holds a real multi-turn exchange
|
|
32
32
|
* with a target agent (driven via the injected transport), answers the target's
|
|
33
33
|
* tool-approval requests in-persona, then evaluates whether the task was met.
|
|
34
34
|
* Deterministic checks are the caller's responsibility.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** One turn the
|
|
2
|
-
const
|
|
1
|
+
/** One turn the actor takes: the message to send and whether it's finished. */
|
|
2
|
+
const ACTOR_TURN_SCHEMA = {
|
|
3
3
|
type: 'object',
|
|
4
4
|
properties: {
|
|
5
5
|
message: { type: 'string' },
|
|
@@ -7,7 +7,7 @@ const PERSONA_TURN_SCHEMA = {
|
|
|
7
7
|
},
|
|
8
8
|
required: ['message', 'done'],
|
|
9
9
|
};
|
|
10
|
-
/** The
|
|
10
|
+
/** The actor's approve/deny decision for each pending tool request. */
|
|
11
11
|
const APPROVAL_DECISION_SCHEMA = {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
@@ -26,7 +26,7 @@ const APPROVAL_DECISION_SCHEMA = {
|
|
|
26
26
|
},
|
|
27
27
|
required: ['decisions'],
|
|
28
28
|
};
|
|
29
|
-
/** The
|
|
29
|
+
/** The actor's final verdict on whether the task was accomplished. */
|
|
30
30
|
const EVALUATION_SCHEMA = {
|
|
31
31
|
type: 'object',
|
|
32
32
|
properties: {
|
|
@@ -60,13 +60,13 @@ function readObject(result) {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function actorInstructions(actor, task) {
|
|
64
64
|
return [
|
|
65
65
|
`You are role-playing a real user interacting with an AI assistant. Stay in character at all times — you are the user, not the assistant.`,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? `Your personality and communication style: ${
|
|
66
|
+
actor.name ? `Your name is ${actor.name}.` : '',
|
|
67
|
+
actor.jobTitle ? `Your role: ${actor.jobTitle}.` : '',
|
|
68
|
+
actor.personality
|
|
69
|
+
? `Your personality and communication style: ${actor.personality}. Match this tone, vocabulary, and level of detail exactly.`
|
|
70
70
|
: '',
|
|
71
71
|
`Your goal in this conversation: ${task}.`,
|
|
72
72
|
`Send one message at a time. Set "done" to true only once your goal is clearly accomplished, or clearly impossible.`,
|
|
@@ -74,7 +74,7 @@ function personaInstructions(persona, task) {
|
|
|
74
74
|
.filter(Boolean)
|
|
75
75
|
.join('\n');
|
|
76
76
|
}
|
|
77
|
-
/** Route the target agent's pending tool approvals through the
|
|
77
|
+
/** Route the target agent's pending tool approvals through the actor. */
|
|
78
78
|
async function decideApprovals(params, instructions, pending) {
|
|
79
79
|
const policy = params.approvals ?? 'in-persona';
|
|
80
80
|
if (policy === 'always') {
|
|
@@ -88,7 +88,7 @@ async function decideApprovals(params, instructions, pending) {
|
|
|
88
88
|
.join('\n');
|
|
89
89
|
const result = await params.llm({
|
|
90
90
|
model: params.model,
|
|
91
|
-
instructions: `${instructions}\nThe assistant is asking permission to run tools on your behalf. Decide whether YOU, as this
|
|
91
|
+
instructions: `${instructions}\nThe assistant is asking permission to run tools on your behalf. Decide whether YOU, as this actor, would allow each one.`,
|
|
92
92
|
messages: [
|
|
93
93
|
msg('user', `The assistant wants to run these tools:\n${summary}\nApprove or deny each toolCallId.`),
|
|
94
94
|
],
|
|
@@ -104,7 +104,7 @@ async function decideApprovals(params, instructions, pending) {
|
|
|
104
104
|
return { toolCallId: p.toolCallId, approved: match?.approved ?? false };
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
/** Drive the target to a non-suspended reply, routing approvals to the
|
|
107
|
+
/** Drive the target to a non-suspended reply, routing approvals to the actor. */
|
|
108
108
|
async function converseWithTarget(params, instructions, message) {
|
|
109
109
|
const maxRounds = params.maxApprovalRounds ?? DEFAULT_MAX_APPROVAL_ROUNDS;
|
|
110
110
|
let reply = await params.target.run(message);
|
|
@@ -122,40 +122,40 @@ async function converseWithTarget(params, instructions, message) {
|
|
|
122
122
|
return reply;
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* Run a conversation: an LLM-driven
|
|
125
|
+
* Run a conversation: an LLM-driven actor holds a real multi-turn exchange
|
|
126
126
|
* with a target agent (driven via the injected transport), answers the target's
|
|
127
127
|
* tool-approval requests in-persona, then evaluates whether the task was met.
|
|
128
128
|
* Deterministic checks are the caller's responsibility.
|
|
129
129
|
*/
|
|
130
130
|
export async function runConversation(params) {
|
|
131
131
|
const maxTurns = params.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
132
|
-
const instructions =
|
|
133
|
-
// Seed a kickoff so the very first
|
|
134
|
-
// (providers reject an empty prompt). It's an instruction TO the
|
|
132
|
+
const instructions = actorInstructions(params.actor, params.task);
|
|
133
|
+
// Seed a kickoff so the very first actor turn has a non-empty message list
|
|
134
|
+
// (providers reject an empty prompt). It's an instruction TO the actor, so
|
|
135
135
|
// it never appears in the transcript.
|
|
136
|
-
const
|
|
136
|
+
const actorMessages = [
|
|
137
137
|
msg('user', 'Begin the conversation now — send your first message to the assistant to work towards your goal.'),
|
|
138
138
|
];
|
|
139
139
|
const transcript = [];
|
|
140
140
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
141
|
-
const
|
|
141
|
+
const actorResult = await params.llm({
|
|
142
142
|
model: params.model,
|
|
143
143
|
instructions,
|
|
144
|
-
messages:
|
|
144
|
+
messages: actorMessages,
|
|
145
145
|
tools: [],
|
|
146
146
|
maxSteps: 1,
|
|
147
147
|
toolChoice: 'none',
|
|
148
|
-
outputSchema:
|
|
148
|
+
outputSchema: ACTOR_TURN_SCHEMA,
|
|
149
149
|
});
|
|
150
|
-
const turnData = readObject(
|
|
151
|
-
const
|
|
152
|
-
if (!
|
|
150
|
+
const turnData = readObject(actorResult);
|
|
151
|
+
const actorMessage = turnData?.message?.trim();
|
|
152
|
+
if (!actorMessage) {
|
|
153
153
|
break;
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
transcript.push(`${params.
|
|
157
|
-
const reply = await converseWithTarget(params, instructions,
|
|
158
|
-
|
|
155
|
+
actorMessages.push(msg('assistant', actorMessage));
|
|
156
|
+
transcript.push(`${params.actorName}: ${actorMessage}`);
|
|
157
|
+
const reply = await converseWithTarget(params, instructions, actorMessage);
|
|
158
|
+
actorMessages.push(msg('user', reply.text ?? ''));
|
|
159
159
|
transcript.push(`${params.agentName}: ${reply.text ?? ''}`);
|
|
160
160
|
if (turnData?.done) {
|
|
161
161
|
break;
|
|
@@ -168,7 +168,24 @@ export type ScopedChannel = AIStreamChannel & {
|
|
|
168
168
|
}>;
|
|
169
169
|
};
|
|
170
170
|
export declare function createScopedChannel(parent: AIStreamChannel, agentName: string, session: string): ScopedChannel;
|
|
171
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Build the run input for a delegated sub-agent.
|
|
173
|
+
*
|
|
174
|
+
* `context` is the PARENT run's identifier block (the "Current context" text
|
|
175
|
+
* with organizationId, project/stage ids). A sub-agent's tool-call schema only
|
|
176
|
+
* carries { message, session }, so unless the sub-agent inherits the parent's
|
|
177
|
+
* context it never sees the authoritative ids — it depends on the model
|
|
178
|
+
* re-typing them into `message`, which weak models botch, causing
|
|
179
|
+
* schema/permission rejections and retry loops. Forwarding it here is the
|
|
180
|
+
* regression this seam guards.
|
|
181
|
+
*/
|
|
182
|
+
export declare function buildSubAgentRunInput(message: string, threadId: string, resourceId: string, parentContext?: string): {
|
|
183
|
+
message: string;
|
|
184
|
+
threadId: string;
|
|
185
|
+
resourceId: string;
|
|
186
|
+
context?: string;
|
|
187
|
+
};
|
|
188
|
+
export declare function buildToolDefs(params: RunAIAgentParams, agentSessionMap: Map<string, string>, resourceId: string, agentName: string, packageName: string | null, streamContext?: StreamContext, aiMiddlewares?: PikkuAIMiddlewareHooks[], agentMode?: 'delegate' | 'supervise', parentContext?: string): Promise<{
|
|
172
189
|
tools: AIAgentToolDef[];
|
|
173
190
|
missingRpcs: string[];
|
|
174
191
|
}>;
|
|
@@ -325,7 +325,29 @@ export function createScopedChannel(parent, agentName, session) {
|
|
|
325
325
|
clearState: () => parent.clearState(),
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Build the run input for a delegated sub-agent.
|
|
330
|
+
*
|
|
331
|
+
* `context` is the PARENT run's identifier block (the "Current context" text
|
|
332
|
+
* with organizationId, project/stage ids). A sub-agent's tool-call schema only
|
|
333
|
+
* carries { message, session }, so unless the sub-agent inherits the parent's
|
|
334
|
+
* context it never sees the authoritative ids — it depends on the model
|
|
335
|
+
* re-typing them into `message`, which weak models botch, causing
|
|
336
|
+
* schema/permission rejections and retry loops. Forwarding it here is the
|
|
337
|
+
* regression this seam guards.
|
|
338
|
+
*/
|
|
339
|
+
export function buildSubAgentRunInput(message, threadId, resourceId, parentContext) {
|
|
340
|
+
return { message, threadId, resourceId, context: parentContext };
|
|
341
|
+
}
|
|
342
|
+
export async function buildToolDefs(params, agentSessionMap, resourceId, agentName, packageName, streamContext, aiMiddlewares, agentMode,
|
|
343
|
+
// The parent run's `context` (the "Current context" identifier block). A
|
|
344
|
+
// delegated sub-agent's tool-call input schema only carries { message,
|
|
345
|
+
// session }, so without inheriting this the sub-agent never sees the
|
|
346
|
+
// authoritative ids (organizationId, project/stage ids) — it depends on the
|
|
347
|
+
// model re-typing them into `message`, which weak models botch, causing
|
|
348
|
+
// schema/permission rejections and retry loops. Forward it so the sub-agent
|
|
349
|
+
// gets the same context block in its instructions.
|
|
350
|
+
parentContext) {
|
|
329
351
|
const singletonServices = getSingletonServices();
|
|
330
352
|
const tools = [];
|
|
331
353
|
const missingRpcs = [];
|
|
@@ -505,7 +527,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
505
527
|
subChannel.send(event);
|
|
506
528
|
},
|
|
507
529
|
};
|
|
508
|
-
const resultText = await streamAIAgent(subAgentName,
|
|
530
|
+
const resultText = await streamAIAgent(subAgentName, buildSubAgentRunInput(message, threadId, resourceId, parentContext), effectiveChannel, params, agentSessionMap, streamContext.options);
|
|
509
531
|
if (subChannel.approvals.length > 0) {
|
|
510
532
|
return {
|
|
511
533
|
[APPROVAL_REQUIRED]: true,
|
|
@@ -525,7 +547,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
525
547
|
return resultText;
|
|
526
548
|
}
|
|
527
549
|
// No stream context: sub-agent runs non-streaming
|
|
528
|
-
const result = await runAIAgent(subAgentName,
|
|
550
|
+
const result = await runAIAgent(subAgentName, buildSubAgentRunInput(message, threadId, resourceId, parentContext), params, agentSessionMap);
|
|
529
551
|
if (result.status === 'suspended' &&
|
|
530
552
|
result.pendingApprovals?.length) {
|
|
531
553
|
return {
|
|
@@ -731,7 +753,7 @@ export async function prepareAgentRun(agentName, input, params, agentSessionMap,
|
|
|
731
753
|
const allMessages = [...contextMessages, ...messages, userMessage];
|
|
732
754
|
const trimmedMessages = trimMessages(allMessages);
|
|
733
755
|
const aiMiddlewares = agent.aiMiddleware ?? [];
|
|
734
|
-
const { tools, missingRpcs } = await buildToolDefs(params, agentSessionMap, input.resourceId, resolvedName, packageName, streamContext, aiMiddlewares, agent.agentMode);
|
|
756
|
+
const { tools, missingRpcs } = await buildToolDefs(params, agentSessionMap, input.resourceId, resolvedName, packageName, streamContext, aiMiddlewares, agent.agentMode, input.context);
|
|
735
757
|
let instructions = await buildInstructions(resolvedName, packageName);
|
|
736
758
|
if (input.context) {
|
|
737
759
|
instructions = `${instructions}\n\nCurrent context (use these identifiers directly in tool calls — do not ask the user for them):\n${input.context}`;
|
|
@@ -150,8 +150,18 @@ export function parseCLIArguments(args, programName, allMeta) {
|
|
|
150
150
|
const arg = args[currentIndex];
|
|
151
151
|
if (arg.startsWith('--')) {
|
|
152
152
|
// Long option (--from-plan → fromPlan)
|
|
153
|
+
const negatedKey = arg.startsWith('--no-')
|
|
154
|
+
? toCamelCase(arg.slice(5))
|
|
155
|
+
: undefined;
|
|
153
156
|
const equalIndex = arg.indexOf('=');
|
|
154
|
-
if (
|
|
157
|
+
if (negatedKey &&
|
|
158
|
+
typeof availableOptions[negatedKey]?.default === 'boolean') {
|
|
159
|
+
// --no-<flag> turns a boolean option off. Only options that declare a
|
|
160
|
+
// boolean default can be negated, so a literal `--no-something` option
|
|
161
|
+
// name still parses as itself.
|
|
162
|
+
optionArgs[negatedKey] = false;
|
|
163
|
+
}
|
|
164
|
+
else if (equalIndex > 0) {
|
|
155
165
|
// --option=value format
|
|
156
166
|
const key = toCamelCase(arg.slice(2, equalIndex));
|
|
157
167
|
const optionDef = availableOptions[key];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { ConfigValidationResult, CoreQueueWorker, JobOptions, PikkuJobConfig, PikkuWorkerConfig, PikkuQueue, QueueJob, QueueJobStatus, QueueService, QueueWorkers, QueueWorkersMeta, } from './queue.types.js';
|
|
1
|
+
export type { ConfigValidationResult, CoreQueueWorker, GroupConcurrencyConfig, JobGroup, JobOptions, PikkuJobConfig, PikkuWorkerConfig, PikkuQueue, QueueJob, QueueJobStatus, QueueService, QueueWorkers, QueueWorkersMeta, } from './queue.types.js';
|
|
2
2
|
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
3
3
|
export { validateWorkerConfig } from './validate-worker-config.js';
|
|
4
4
|
export type { QueueConfigMapping } from './validate-worker-config.js';
|
|
@@ -30,6 +30,34 @@ export interface PikkuWorkerConfig {
|
|
|
30
30
|
maxStalledCount?: number;
|
|
31
31
|
/** Condition to start processor at instance creation */
|
|
32
32
|
autorun?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Cap how many jobs of any one group ({@link JobOptions.group}) may run at
|
|
35
|
+
* once, so a single group can't occupy the whole worker. Lets one shared
|
|
36
|
+
* queue stay fair across producers instead of splitting it into one queue
|
|
37
|
+
* per producer — which multiplies polling cost on pull-based backends.
|
|
38
|
+
* Must not exceed {@link batchSize}.
|
|
39
|
+
*/
|
|
40
|
+
groupConcurrency?: number | GroupConcurrencyConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Per-group concurrency limits, optionally varied by tier so slow groups can
|
|
44
|
+
* be allowed more (or fewer) slots than the default.
|
|
45
|
+
*/
|
|
46
|
+
export interface GroupConcurrencyConfig {
|
|
47
|
+
/** Limit applied to any group without a matching tier */
|
|
48
|
+
default: number;
|
|
49
|
+
/** Per-tier overrides, keyed by {@link JobGroup.tier} */
|
|
50
|
+
tiers?: Record<string, number>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fairness key for a job. Jobs sharing an `id` count against the same
|
|
54
|
+
* {@link PikkuWorkerConfig.groupConcurrency} limit.
|
|
55
|
+
*/
|
|
56
|
+
export interface JobGroup {
|
|
57
|
+
/** Group this job belongs to (e.g. a workflow name) */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional tier selecting a per-tier limit */
|
|
60
|
+
tier?: string;
|
|
33
61
|
}
|
|
34
62
|
/**
|
|
35
63
|
* Configuration for individual jobs - how jobs behave
|
|
@@ -106,6 +134,8 @@ export interface JobOptions {
|
|
|
106
134
|
jobId?: string;
|
|
107
135
|
/** Pikku user ID to propagate to the queue worker for credential resolution */
|
|
108
136
|
pikkuUserId?: string;
|
|
137
|
+
/** Fairness key — counts against the worker's {@link PikkuWorkerConfig.groupConcurrency} */
|
|
138
|
+
group?: JobGroup;
|
|
109
139
|
}
|
|
110
140
|
/**
|
|
111
141
|
* Queue provider interface for job publishing operations
|
|
@@ -129,7 +129,7 @@ export class ContextAwareRPCService {
|
|
|
129
129
|
if (!functionMeta) {
|
|
130
130
|
throw new RPCNotFoundError(funcName);
|
|
131
131
|
}
|
|
132
|
-
if (!functionMeta.expose) {
|
|
132
|
+
if (!functionMeta.expose || functionMeta.scenarioStep) {
|
|
133
133
|
throw new RPCNotFoundError(funcName);
|
|
134
134
|
}
|
|
135
135
|
return await this.rpc(funcName, data);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
6
6
|
import type { WorkflowRun } from '../workflow.types.js';
|
|
7
7
|
import type { ScenarioActor } from '../../../services/scenario-actors-service.js';
|
|
8
|
+
import type { ScenarioStepOptions, ScenarioStepPhase } from '../scenario-step.types.js';
|
|
8
9
|
/**
|
|
9
10
|
* Workflow step options
|
|
10
11
|
*/
|
|
@@ -60,6 +61,13 @@ export type WorkflowWireDoRPC = <TOutput = any, TInput = any>(stepName: string,
|
|
|
60
61
|
* Type signature for workflow.do() inline form - used by inspector
|
|
61
62
|
*/
|
|
62
63
|
export type WorkflowWireDoInline = <T>(stepName: string, fn: () => Promise<T> | T, options?: WorkflowStepOptions) => Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Type signature for scenario.step/given/when/then - used by inspector.
|
|
66
|
+
*
|
|
67
|
+
* Deliberately mirrors WorkflowWireDoRPC's shape: the target is a string, not
|
|
68
|
+
* an imported symbol, so the extractor reads it as a literal.
|
|
69
|
+
*/
|
|
70
|
+
export type ScenarioStepInvocation = <TOutput = any, TInput = any>(stepName: string, stepFunc: string, data?: TInput, options?: ScenarioStepOptions) => Promise<TOutput>;
|
|
63
71
|
/**
|
|
64
72
|
* Type signature for workflow.sleep() - used by inspector
|
|
65
73
|
*/
|
|
@@ -172,6 +180,33 @@ export interface RpcStepMeta {
|
|
|
172
180
|
/** True for workflow.expectEventually polling steps */
|
|
173
181
|
expectEventually?: boolean;
|
|
174
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Scenario step metadata — a call to `scenario.step/given/when/then`.
|
|
185
|
+
*
|
|
186
|
+
* Distinct from RpcStepMeta on purpose: a step runs locally through
|
|
187
|
+
* runPikkuFunc and must never be treated as dispatchable on the queue/replay
|
|
188
|
+
* path, nor registered as a callable RPC (a browser-driving step must not be
|
|
189
|
+
* network-invocable).
|
|
190
|
+
*/
|
|
191
|
+
export interface ScenarioStepMeta {
|
|
192
|
+
type: 'scenarioStep';
|
|
193
|
+
/** Cache key (first argument), ordinal-suffixed by the engine when repeated */
|
|
194
|
+
stepName: string;
|
|
195
|
+
/** Registered name of the step function being run */
|
|
196
|
+
stepFunc: string;
|
|
197
|
+
/** Which keyword the reporter prefixes — given/when/then, or none for `step` */
|
|
198
|
+
phase: ScenarioStepPhase;
|
|
199
|
+
/** Output variable name (if assigned) */
|
|
200
|
+
outputVar?: string;
|
|
201
|
+
/** Input source mappings, or 'passthrough' when entire data is passed */
|
|
202
|
+
inputs?: Record<string, InputSource> | 'passthrough';
|
|
203
|
+
/** Step options */
|
|
204
|
+
options?: WorkflowStepOptions;
|
|
205
|
+
/** Scenario actor name this step runs as ({ actor: actors.x }) */
|
|
206
|
+
actor?: string;
|
|
207
|
+
/** Mirrors the step function's `browser: true` declaration */
|
|
208
|
+
browser?: boolean;
|
|
209
|
+
}
|
|
175
210
|
/**
|
|
176
211
|
* Simple condition expression (leaf node)
|
|
177
212
|
*/
|
|
@@ -214,7 +249,7 @@ export interface BranchStepMeta {
|
|
|
214
249
|
export interface ParallelGroupStepMeta {
|
|
215
250
|
type: 'parallel';
|
|
216
251
|
/** Child steps to execute in parallel */
|
|
217
|
-
children: RpcStepMeta
|
|
252
|
+
children: Array<RpcStepMeta | ScenarioStepMeta>;
|
|
218
253
|
}
|
|
219
254
|
/**
|
|
220
255
|
* Fanout step metadata (parallel or sequential iteration)
|
|
@@ -234,7 +269,7 @@ export interface FanoutStepMeta {
|
|
|
234
269
|
/** Execution mode */
|
|
235
270
|
mode: 'parallel' | 'sequential';
|
|
236
271
|
/** Steps to execute inline per iteration, in order */
|
|
237
|
-
body: Array<RpcStepMeta | SleepStepMeta | SuspendStepMeta>;
|
|
272
|
+
body: Array<RpcStepMeta | SleepStepMeta | SuspendStepMeta | ScenarioStepMeta>;
|
|
238
273
|
/** Time between iterations (sequential mode only) */
|
|
239
274
|
timeBetween?: string;
|
|
240
275
|
}
|
|
@@ -385,7 +420,7 @@ export interface ArrayPredicateStepMeta {
|
|
|
385
420
|
/**
|
|
386
421
|
* Workflow step metadata (extracted by inspector)
|
|
387
422
|
*/
|
|
388
|
-
export type WorkflowStepMeta = RpcStepMeta | BranchStepMeta | ParallelGroupStepMeta | FanoutStepMeta | ReturnStepMeta | InlineStepMeta | SleepStepMeta | CancelStepMeta | SuspendStepMeta | ApprovalStepMeta | SwitchStepMeta | FilterStepMeta | ArrayPredicateStepMeta | SetStepMeta;
|
|
423
|
+
export type WorkflowStepMeta = RpcStepMeta | ScenarioStepMeta | BranchStepMeta | ParallelGroupStepMeta | FanoutStepMeta | ReturnStepMeta | InlineStepMeta | SleepStepMeta | CancelStepMeta | SuspendStepMeta | ApprovalStepMeta | SwitchStepMeta | FilterStepMeta | ArrayPredicateStepMeta | SetStepMeta;
|
|
389
424
|
/**
|
|
390
425
|
* Workflow step wire context for RPC functions
|
|
391
426
|
* Provides step-level metadata including retry attempt tracking
|
|
@@ -448,5 +483,19 @@ export interface PikkuScenarioWire extends PikkuWorkflowWire {
|
|
|
448
483
|
expectError: <TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowExpectErrorOptions) => Promise<string>;
|
|
449
484
|
/** Stub-assertion step: asserts `service.method` was called on the target server */
|
|
450
485
|
expectService: (stepName: string, serviceMethod: string, options?: WorkflowExpectServiceOptions) => Promise<void>;
|
|
486
|
+
/**
|
|
487
|
+
* Run a registered scenario step. Shaped exactly like `do`'s RPC form —
|
|
488
|
+
* `(stepName, target, data, options)` — so the inspector reads the target as
|
|
489
|
+
* a string literal rather than resolving an imported symbol.
|
|
490
|
+
*
|
|
491
|
+
* The generated `TypedScenario` narrows these over `FlattenedScenarioStepMap`.
|
|
492
|
+
*/
|
|
493
|
+
step(stepName: string, stepFunc: string, data?: any, options?: ScenarioStepOptions): Promise<any>;
|
|
494
|
+
/** `step` with a "Given" prefix in the rendered prose */
|
|
495
|
+
given(stepName: string, stepFunc: string, data?: any, options?: ScenarioStepOptions): Promise<any>;
|
|
496
|
+
/** `step` with a "When" prefix in the rendered prose */
|
|
497
|
+
when(stepName: string, stepFunc: string, data?: any, options?: ScenarioStepOptions): Promise<any>;
|
|
498
|
+
/** `step` with a "Then" prefix in the rendered prose */
|
|
499
|
+
then(stepName: string, stepFunc: string, data?: any, options?: ScenarioStepOptions): Promise<any>;
|
|
451
500
|
runScheduledTask: (name: string) => Promise<unknown>;
|
|
452
501
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CoreFeature, CoreWorkflow, FeaturePlanEntry } from './workflow.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Register a feature under the name it is exported as. Called from generated
|
|
4
|
+
* wiring, the same way `addWorkflow` is.
|
|
5
|
+
*/
|
|
6
|
+
export declare const addFeature: (featureId: string, feature: CoreFeature, packageName?: string | null) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve every feature's scenario references back to the names their
|
|
9
|
+
* scenarios are registered under.
|
|
10
|
+
*
|
|
11
|
+
* Matching is by **object identity**: `pikkuScenario` returns its config
|
|
12
|
+
* verbatim and `addWorkflow` registers that same object, so a feature holding
|
|
13
|
+
* the imported identifier holds the very object that was registered. Nothing
|
|
14
|
+
* is matched by shape, by name, or by any other guess — which is also why a
|
|
15
|
+
* scenario built inline inside a feature (and therefore never registered)
|
|
16
|
+
* comes back as unresolved rather than silently running as something else.
|
|
17
|
+
*
|
|
18
|
+
* Entries are returned in declaration order, features in registration order.
|
|
19
|
+
* A scenario's effective tags are its own plus the containing feature's, so
|
|
20
|
+
* `--tags credential` selects through the feature.
|
|
21
|
+
*/
|
|
22
|
+
export declare const resolveFeatureScenarios: (features: Map<string, CoreFeature>, registrations: Map<string, CoreWorkflow>) => {
|
|
23
|
+
entries: FeaturePlanEntry[];
|
|
24
|
+
unresolved: Array<{
|
|
25
|
+
featureId: string;
|
|
26
|
+
index: number;
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Register a feature under the name it is exported as. Called from generated
|
|
4
|
+
* wiring, the same way `addWorkflow` is.
|
|
5
|
+
*/
|
|
6
|
+
export const addFeature = (featureId, feature, packageName = null) => {
|
|
7
|
+
pikkuState(packageName, 'workflows', 'features').set(featureId, feature);
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Resolve every feature's scenario references back to the names their
|
|
11
|
+
* scenarios are registered under.
|
|
12
|
+
*
|
|
13
|
+
* Matching is by **object identity**: `pikkuScenario` returns its config
|
|
14
|
+
* verbatim and `addWorkflow` registers that same object, so a feature holding
|
|
15
|
+
* the imported identifier holds the very object that was registered. Nothing
|
|
16
|
+
* is matched by shape, by name, or by any other guess — which is also why a
|
|
17
|
+
* scenario built inline inside a feature (and therefore never registered)
|
|
18
|
+
* comes back as unresolved rather than silently running as something else.
|
|
19
|
+
*
|
|
20
|
+
* Entries are returned in declaration order, features in registration order.
|
|
21
|
+
* A scenario's effective tags are its own plus the containing feature's, so
|
|
22
|
+
* `--tags credential` selects through the feature.
|
|
23
|
+
*/
|
|
24
|
+
export const resolveFeatureScenarios = (features, registrations) => {
|
|
25
|
+
const nameByConfig = new Map();
|
|
26
|
+
for (const [name, registration] of registrations) {
|
|
27
|
+
nameByConfig.set(registration.func, name);
|
|
28
|
+
}
|
|
29
|
+
const entries = [];
|
|
30
|
+
const unresolved = [];
|
|
31
|
+
for (const [featureId, feature] of features) {
|
|
32
|
+
const scenarios = feature.scenarios ?? [];
|
|
33
|
+
for (let index = 0; index < scenarios.length; index++) {
|
|
34
|
+
const entry = scenarios[index];
|
|
35
|
+
const paired = typeof entry === 'object' && entry !== null && 'scenario' in entry;
|
|
36
|
+
const config = paired ? entry.scenario : entry;
|
|
37
|
+
const scenarioName = nameByConfig.get(config);
|
|
38
|
+
if (!scenarioName) {
|
|
39
|
+
unresolved.push({ featureId, index });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
entries.push({
|
|
43
|
+
featureId,
|
|
44
|
+
featureName: feature.name ?? featureId,
|
|
45
|
+
scenarioName,
|
|
46
|
+
data: paired ? entry.data : undefined,
|
|
47
|
+
tags: [
|
|
48
|
+
...new Set([
|
|
49
|
+
...(registrations.get(scenarioName)?.func?.tags ?? []),
|
|
50
|
+
...(feature.tags ?? []),
|
|
51
|
+
]),
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { entries, unresolved };
|
|
57
|
+
};
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
* Workflow module exports
|
|
3
3
|
*/
|
|
4
4
|
export { PikkuWorkflowService, WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, DEFAULT_STEP_RETRIES, } from './pikku-workflow-service.js';
|
|
5
|
+
export type { RunLifecycleContext, WorkflowRunEngine, WorkflowRunExtension, } from './pikku-workflow-service.js';
|
|
5
6
|
export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
|
|
6
7
|
export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
|
|
7
8
|
export type { RunTimeline, RunTimelineEvent, ReconstructedRunState, ReconstructedStep, RunPhase, } from './run-timeline.js';
|
|
8
9
|
export { addWorkflow } from './dsl/workflow-runner.js';
|
|
10
|
+
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
9
11
|
export { template, type TemplateString } from './graph/template.js';
|
|
10
12
|
export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './graph/wire-workflow-graph.js';
|
|
11
13
|
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
12
14
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
13
15
|
export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
|
|
14
|
-
export type { WorkflowService, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
15
|
-
export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
|
|
16
|
+
export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, CoreFeatureScenario, FeatureMeta, FeatureMetaEntry, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
17
|
+
export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
|
|
18
|
+
export type { ScenarioStepPhase, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, } from './scenario-step.types.js';
|
|
19
|
+
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
20
|
+
export { pollUntil, type PollOptions } from './scenario-poll.js';
|
|
21
|
+
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
22
|
+
export type { ScenarioCookieJar } from './scenario-cookie-jar.js';
|
|
23
|
+
export type { ScenarioHttpResponse, ScenarioJsonRequest, } from '../../services/scenario-actors-service.js';
|
|
24
|
+
export { readScenarioHttpResponse, postScenarioJson, } from '../../services/scenario-actors-service.js';
|
|
25
|
+
export type { PikkuBrowserWire, TestIdSelector, ScenarioBrowserProvider, ScenarioBrowserFailure, } from './scenario-step.types.js';
|
|
26
|
+
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|
|
@@ -7,6 +7,7 @@ export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
|
|
|
7
7
|
export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
|
|
8
8
|
// Internal registration functions (used by generated code)
|
|
9
9
|
export { addWorkflow } from './dsl/workflow-runner.js';
|
|
10
|
+
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
10
11
|
// Graph helpers (template, pikkuWorkflowGraph)
|
|
11
12
|
export { template } from './graph/template.js';
|
|
12
13
|
export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
|
|
@@ -14,3 +15,17 @@ export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
|
|
|
14
15
|
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
15
16
|
// Queue worker functions (registered by codegen, executed at runtime)
|
|
16
17
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
18
|
+
// Narrows the optional halves of the step wire, with a message that says what to do
|
|
19
|
+
export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
|
|
20
|
+
// Waits for the target to catch up, without every step writing the loop again
|
|
21
|
+
export { pollUntil } from './scenario-poll.js';
|
|
22
|
+
// Persists cookies across a step's requests, the way a browser would
|
|
23
|
+
export { createCookieJar } from './scenario-cookie-jar.js';
|
|
24
|
+
export { readScenarioHttpResponse, postScenarioJson, } from '../../services/scenario-actors-service.js';
|
|
25
|
+
/* ------------------------------------------------------------------ *
|
|
26
|
+
* Scenarios — reporting a run
|
|
27
|
+
*
|
|
28
|
+
* Used by the CLI reporter and the console, so the two render the same
|
|
29
|
+
* sentence for the same step. A step body needs none of it.
|
|
30
|
+
* ------------------------------------------------------------------ */
|
|
31
|
+
export { composeStepProse, renderStepTemplate } from './scenario-prose.js';
|