@agentswf/engine 0.1.0-beta.4 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/agent-cli/cli.ts +57 -17
- package/src/attempt-ending.ts +2 -4
- package/src/attempt-output.ts +2 -1
- package/src/attempt-view.ts +1 -1
- package/src/control-plane.ts +37 -5
- package/src/coordinator-socket.ts +15 -6
- package/src/deadlines.ts +69 -2
- package/src/decisions/directory.ts +9 -11
- package/src/deprecations.ts +177 -0
- package/src/duration.ts +50 -20
- package/src/emit-command.ts +55 -41
- package/src/emit.ts +124 -15
- package/src/help/emit.txt +12 -4
- package/src/help/inbox.txt +2 -2
- package/src/help/logs.txt +5 -4
- package/src/help/reply.txt +5 -5
- package/src/help/run.txt +21 -7
- package/src/herdr-run-session.ts +2 -2
- package/src/holder-command.ts +36 -31
- package/src/inbox-command.ts +7 -10
- package/src/init-template/hello/workflow.test.ts.template +3 -3
- package/src/init-template/hello/workflow.ts.template +2 -2
- package/src/keep-alive.ts +28 -13
- package/src/logs-command.ts +12 -12
- package/src/mailbox.ts +2 -20
- package/src/messaging/limits.ts +4 -4
- package/src/messaging/messaging.ts +26 -21
- package/src/messaging/routes.ts +1 -1
- package/src/messaging/text.ts +7 -5
- package/src/operator-cli.ts +23 -9
- package/src/operator-runtime.ts +5 -5
- package/src/pane-layout.ts +125 -32
- package/src/progress-view.ts +8 -8
- package/src/run-command.ts +25 -4
- package/src/run-commands.ts +1 -1
- package/src/run-prepare.ts +46 -8
- package/src/run-usage.ts +1 -1
- package/src/run-view.ts +9 -4
- package/src/runs-command.ts +12 -10
- package/src/runs.ts +44 -8
- package/src/sources.ts +42 -6
- package/src/stage-ledger.ts +42 -12
- package/src/stage-plan.ts +37 -1
- package/src/test-command.ts +7 -3
- package/src/testing.ts +7 -8
- package/src/tools.ts +46 -18
- package/src/workflow-loader.ts +5 -2
- package/src/workflow-runner.ts +553 -235
- package/src/workflow-testing/coordinator.typecheck.ts +7 -7
- package/src/workflow-testing/coordinators.ts +2 -2
- package/src/workflow-testing/host.ts +107 -38
- package/src/workflow-testing/index.ts +45 -836
- package/src/workflow-testing/run-test.ts +1213 -0
- package/src/workflow-testing/script.ts +102 -41
- package/src/workflow-testing/workflow-testing.typecheck.ts +11 -8
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ToolFailure,
|
|
7
7
|
} from "@agentswf/contract/wire";
|
|
8
8
|
import type {
|
|
9
|
+
Duration,
|
|
9
10
|
Effort,
|
|
10
11
|
JsonObject,
|
|
11
12
|
JsonValue,
|
|
@@ -14,6 +15,7 @@ import type {
|
|
|
14
15
|
} from "@agentswf/contract/workflow";
|
|
15
16
|
import type Type from "typebox";
|
|
16
17
|
import { canonical } from "../canonical-json";
|
|
18
|
+
import type { Deprecated } from "../deprecations";
|
|
17
19
|
import { messageOf } from "../errors";
|
|
18
20
|
|
|
19
21
|
/** One turn of a scripted agent, as the workflow wrote it. */
|
|
@@ -78,7 +80,13 @@ export type Message = {
|
|
|
78
80
|
answers?: true;
|
|
79
81
|
};
|
|
80
82
|
|
|
81
|
-
export type SendOptions = {
|
|
83
|
+
export type SendOptions = {
|
|
84
|
+
awaitReply?: boolean;
|
|
85
|
+
/** How long an awaited reply is waited for, as `wfa send --timeout` gives it. */
|
|
86
|
+
timeout?: Duration;
|
|
87
|
+
/** @deprecated Give `timeout`, a `Duration` such as `"30s"`. */
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
};
|
|
82
90
|
|
|
83
91
|
/** How a send or reply ended; a refusal is a value to test, not a throw. */
|
|
84
92
|
export type MessageOutcome =
|
|
@@ -89,7 +97,8 @@ export type MessageOutcome =
|
|
|
89
97
|
|
|
90
98
|
/**
|
|
91
99
|
* A message that woke the agent, and what its script may do in that turn: `reply` and `send` as
|
|
92
|
-
* the turn's own
|
|
100
|
+
* the turn's own. Returning nothing ends the turn without a reply, and one owed is reminded once,
|
|
101
|
+
* then fails; returning `noAnswer.failed(reason)` fails it as a failed harness does.
|
|
93
102
|
*/
|
|
94
103
|
export type ReceivedMessage = Message & {
|
|
95
104
|
/** The turn it woke: whether it is a reminder (`nudge`), and every message shown in it. */
|
|
@@ -97,20 +106,22 @@ export type ReceivedMessage = Message & {
|
|
|
97
106
|
reply(text: string, options?: SendOptions): Promise<MessageOutcome>;
|
|
98
107
|
send(peer: string, text: string, options?: SendOptions): Promise<MessageOutcome>;
|
|
99
108
|
tool(name: string, input?: JsonObject): Promise<ToolOutcome>;
|
|
100
|
-
/**
|
|
101
|
-
silent():
|
|
102
|
-
/**
|
|
103
|
-
fail(reason?: string):
|
|
109
|
+
/** @deprecated Return nothing, which ends the turn without a reply. */
|
|
110
|
+
silent(): NoAnswer;
|
|
111
|
+
/** @deprecated Return `noAnswer.failed(reason)`. */
|
|
112
|
+
fail(reason?: string): NoAnswer;
|
|
104
113
|
};
|
|
105
114
|
|
|
106
115
|
/**
|
|
107
116
|
* A message operation's script: called with the message that woke the agent, and again with the
|
|
108
117
|
* next one when held messages continue the operation, or with the same one, `turn.nudge` set, when
|
|
109
|
-
* it is reminded of a reply it owes.
|
|
118
|
+
* it is reminded of a reply it owes. A `noAnswer` it returns ends the turn as that says; nothing,
|
|
119
|
+
* or what a `reply` or `send` resolved to, ends it without a reply. A throw fails the test.
|
|
110
120
|
*/
|
|
111
121
|
export type MessageScript = (
|
|
112
122
|
message: ReceivedMessage,
|
|
113
|
-
|
|
123
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: an async script that returns nothing is Promise<void>.
|
|
124
|
+
) => void | NoAnswer | MessageOutcome | Promise<void | NoAnswer | MessageOutcome>;
|
|
114
125
|
|
|
115
126
|
/**
|
|
116
127
|
* How a participant's script's `reply` or `send` ended, as `awf reply --json` reports it: a reply's
|
|
@@ -157,7 +168,7 @@ export type ParticipantScript = (event: ParticipantRoute | ParticipantMessage) =
|
|
|
157
168
|
|
|
158
169
|
const ENTRY: unique symbol = Symbol("agentswf.script-entry");
|
|
159
170
|
|
|
160
|
-
type Respond = (turn: Turn) => JsonValue |
|
|
171
|
+
type Respond = (turn: Turn) => JsonValue | NoAnswer | Promise<JsonValue | NoAnswer>;
|
|
161
172
|
|
|
162
173
|
/** An answer to a turn that asks for its schema, or for text. Made by `answer`. */
|
|
163
174
|
export type Answer = {
|
|
@@ -172,11 +183,13 @@ type Ending =
|
|
|
172
183
|
| { kind: "hang" }
|
|
173
184
|
| { kind: "interrupted" };
|
|
174
185
|
|
|
175
|
-
/** A turn ended without an answer. Made by `
|
|
176
|
-
export type
|
|
186
|
+
/** A turn ended without an answer. Made by `noAnswer`. */
|
|
187
|
+
export type NoAnswer = {
|
|
188
|
+
readonly [ENTRY]: { kind: "no-answer"; ending: Ending; deprecated?: Deprecated };
|
|
189
|
+
};
|
|
177
190
|
|
|
178
191
|
/** Every turn the agent is asked, alike; or a list, one entry per turn in order. */
|
|
179
|
-
export type Script = Answer |
|
|
192
|
+
export type Script = Answer | NoAnswer | readonly (Answer | NoAnswer)[];
|
|
180
193
|
|
|
181
194
|
/** What a schema's answer is: the carrier the author surface types with, else TypeBox's `Static`. */
|
|
182
195
|
export type AnswerOf<S> = S extends { readonly "~output"?: infer O }
|
|
@@ -186,11 +199,11 @@ export type AnswerOf<S> = S extends { readonly "~output"?: infer O }
|
|
|
186
199
|
: StaticOf<S>;
|
|
187
200
|
type StaticOf<S> = S extends Type.TSchema ? Type.Static<S> : JsonValue;
|
|
188
201
|
|
|
189
|
-
type Answering<A> = A | ((turn: Turn) => A |
|
|
202
|
+
type Answering<A> = A | ((turn: Turn) => A | NoAnswer | Promise<A | NoAnswer>);
|
|
190
203
|
|
|
191
204
|
/**
|
|
192
205
|
* An answer to a turn that asks for `schema`: a value of its type, or a function of the turn that
|
|
193
|
-
* returns one or a `
|
|
206
|
+
* returns one or a `noAnswer`. A function that returns no answer is asked again for the nudge, with
|
|
194
207
|
* `turn.nudge` set.
|
|
195
208
|
*/
|
|
196
209
|
export function answer<const S extends OutputSchema>(
|
|
@@ -206,46 +219,94 @@ export function answer(...args: [unknown] | [unknown, unknown]): Answer {
|
|
|
206
219
|
return { [ENTRY]: { kind: "answer", schema: schema as OutputSchema | undefined, respond } };
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
const ending = (value: Ending):
|
|
222
|
+
const ending = (value: Ending, deprecated?: Deprecated): NoAnswer => ({
|
|
223
|
+
[ENTRY]: {
|
|
224
|
+
kind: "no-answer",
|
|
225
|
+
ending: value,
|
|
226
|
+
...(deprecated === undefined ? {} : { deprecated }),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
210
229
|
|
|
211
|
-
/** Ends a turn without an answer
|
|
212
|
-
export const
|
|
213
|
-
/** Reports a cooperative wait; later check-ins invoke the same answer function again. */
|
|
214
|
-
waiting: (reason: string, timeoutMs?: number): Reply =>
|
|
215
|
-
ending({ kind: "waiting", reason, ...(timeoutMs === undefined ? {} : { timeoutMs }) }),
|
|
230
|
+
/** Ends a turn without an answer, so it takes no schema. */
|
|
231
|
+
export const noAnswer = {
|
|
216
232
|
/** Ends without a result; recovery follows the placement's production support. */
|
|
217
|
-
silent: ():
|
|
233
|
+
silent: (): NoAnswer => ending({ kind: "silent" }),
|
|
218
234
|
/** The agent is stuck, as on a permission prompt. */
|
|
219
|
-
blocked: (reason = "waiting on a permission prompt"):
|
|
235
|
+
blocked: (reason = "waiting on a permission prompt"): NoAnswer =>
|
|
220
236
|
ending({ kind: "blocked", reason }),
|
|
221
237
|
/** The harness failed. */
|
|
222
|
-
failed: (reason = "harness crashed"):
|
|
238
|
+
failed: (reason = "harness crashed"): NoAnswer => ending({ kind: "failed", reason }),
|
|
223
239
|
/**
|
|
224
240
|
* The agent's harness cannot sign in, as with an expired login: the turn fails with `login` set,
|
|
225
241
|
* naming the agent's harness, `provider` where it logs in to several, and what to run.
|
|
226
242
|
*/
|
|
227
|
-
needsLogin: (provider?: string):
|
|
243
|
+
needsLogin: (provider?: string): NoAnswer =>
|
|
228
244
|
ending({ kind: "needs-login", ...(provider === undefined ? {} : { provider }) }),
|
|
229
245
|
/** The harness reported the turn timed out; it ends at once. */
|
|
230
|
-
timedOut: (reason = "turn timed out"):
|
|
246
|
+
timedOut: (reason = "turn timed out"): NoAnswer => ending({ kind: "timed-out", reason }),
|
|
231
247
|
/** Holds the turn until the engine cancels it. */
|
|
232
|
-
hang: ():
|
|
248
|
+
hang: (): NoAnswer => ending({ kind: "hang" }),
|
|
233
249
|
/**
|
|
234
250
|
* The operator stopped the turn in their pane, as only the calling session's can be: it settles
|
|
235
251
|
* `cancelled`, is never nudged, and the session takes the next turn.
|
|
236
252
|
*/
|
|
237
|
-
interrupted: ():
|
|
253
|
+
interrupted: (): NoAnswer => ending({ kind: "interrupted" }),
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/** What `make` ends a turn with, marked as made through a deprecated name for the run to log. */
|
|
257
|
+
export function deprecatedAs<A extends unknown[]>(
|
|
258
|
+
name: Deprecated,
|
|
259
|
+
make: (...args: A) => NoAnswer,
|
|
260
|
+
): (...args: A) => NoAnswer {
|
|
261
|
+
return (...args) => ending(make(...args)[ENTRY].ending, name);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** @deprecated Use `noAnswer`, the same calls: a reply answers, these end a turn without one. */
|
|
265
|
+
export const reply: typeof noAnswer = {
|
|
266
|
+
silent: deprecatedAs("test-reply", noAnswer.silent),
|
|
267
|
+
blocked: deprecatedAs("test-reply", noAnswer.blocked),
|
|
268
|
+
failed: deprecatedAs("test-reply", noAnswer.failed),
|
|
269
|
+
needsLogin: deprecatedAs("test-reply", noAnswer.needsLogin),
|
|
270
|
+
timedOut: deprecatedAs("test-reply", noAnswer.timedOut),
|
|
271
|
+
hang: deprecatedAs("test-reply", noAnswer.hang),
|
|
272
|
+
interrupted: deprecatedAs("test-reply", noAnswer.interrupted),
|
|
238
273
|
};
|
|
239
274
|
|
|
240
275
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
276
|
+
* Reports a cooperative wait, `wfa waiting`; later check-ins invoke the same answer function again.
|
|
277
|
+
* Only the engine's own tests use it: whether an agent may wait is its harness's, not a workflow's.
|
|
243
278
|
*/
|
|
244
|
-
export
|
|
279
|
+
export const waiting = (reason: string, timeoutMs?: number): NoAnswer =>
|
|
280
|
+
ending({ kind: "waiting", reason, ...(timeoutMs === undefined ? {} : { timeoutMs }) });
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* How a turn ended, as `run.turns` keeps it: the engine's word, but for `waiting`, a check-in, and
|
|
284
|
+
* `awaiting-reply`, a script awaiting a reply, which a later turn of the same call resumes it with.
|
|
285
|
+
*/
|
|
286
|
+
export type TestTurnOutcome =
|
|
287
|
+
| "answered"
|
|
288
|
+
| "unanswered"
|
|
289
|
+
| "blocked"
|
|
290
|
+
| "failed"
|
|
291
|
+
| "timed-out"
|
|
292
|
+
| "cancelled"
|
|
293
|
+
| "waiting"
|
|
294
|
+
| "awaiting-reply";
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* A turn's outcome until the engine's record replaces it: what a turn before its operation's last,
|
|
298
|
+
* a nudged one, keeps.
|
|
299
|
+
*/
|
|
300
|
+
export function outcomeOf(kind: Exclude<Ending["kind"], "waiting">): TestTurnOutcome {
|
|
301
|
+
if (kind === "silent") return "unanswered";
|
|
302
|
+
if (kind === "needs-login") return "failed";
|
|
303
|
+
if (kind === "hang" || kind === "interrupted") return "cancelled";
|
|
304
|
+
return kind;
|
|
305
|
+
}
|
|
245
306
|
|
|
246
307
|
export type Step =
|
|
247
308
|
| { kind: "answer"; value: JsonValue }
|
|
248
|
-
| { kind: "
|
|
309
|
+
| { kind: "no-answer"; ending: Ending; deprecated?: Deprecated }
|
|
249
310
|
/** The script cannot meet this turn: the test's mistake, not the workflow's. */
|
|
250
311
|
| { kind: "script-error"; message: string };
|
|
251
312
|
|
|
@@ -333,7 +394,7 @@ class Lists<E> {
|
|
|
333
394
|
type Of = { noun: string; option: string; whose: string; expected: string };
|
|
334
395
|
|
|
335
396
|
/** Each agent's turn scripts, or its compactions'. */
|
|
336
|
-
export class Scripts extends Lists<Answer |
|
|
397
|
+
export class Scripts extends Lists<Answer | NoAnswer> {
|
|
337
398
|
readonly #noun: string;
|
|
338
399
|
|
|
339
400
|
constructor(
|
|
@@ -345,7 +406,7 @@ export class Scripts extends Lists<Answer | Reply> {
|
|
|
345
406
|
{
|
|
346
407
|
...of,
|
|
347
408
|
whose: of.noun === "turn" ? "script" : `${of.noun} script`,
|
|
348
|
-
expected: "answer(…),
|
|
409
|
+
expected: "answer(…), noAnswer.…()",
|
|
349
410
|
},
|
|
350
411
|
isEntry,
|
|
351
412
|
);
|
|
@@ -357,14 +418,14 @@ export class Scripts extends Lists<Answer | Reply> {
|
|
|
357
418
|
const found = this.entry(turn.agent, index);
|
|
358
419
|
if ("why" in found) return this.error(turn, found.why);
|
|
359
420
|
const meant = found.entry[ENTRY];
|
|
360
|
-
if (meant.kind === "
|
|
421
|
+
if (meant.kind === "no-answer") return { ...meant };
|
|
361
422
|
if (!sameSchema(meant.schema, turn.schema)) {
|
|
362
423
|
return this.error(
|
|
363
424
|
turn,
|
|
364
425
|
`it asks for ${describeSchema(turn.schema)}; its script answers ${describeSchema(meant.schema)}`,
|
|
365
426
|
);
|
|
366
427
|
}
|
|
367
|
-
let out: JsonValue |
|
|
428
|
+
let out: JsonValue | NoAnswer | undefined;
|
|
368
429
|
try {
|
|
369
430
|
out = await meant.respond(turn);
|
|
370
431
|
} catch (error) {
|
|
@@ -373,7 +434,7 @@ export class Scripts extends Lists<Answer | Reply> {
|
|
|
373
434
|
if (out === undefined) return this.error(turn, "its script returned nothing");
|
|
374
435
|
if (isEntry(out)) {
|
|
375
436
|
const given = out[ENTRY];
|
|
376
|
-
if (given.kind === "
|
|
437
|
+
if (given.kind === "no-answer") return { ...given };
|
|
377
438
|
return this.error(turn, "its script returned answer(…); a function returns the value itself");
|
|
378
439
|
}
|
|
379
440
|
return { kind: "answer", value: out };
|
|
@@ -403,8 +464,8 @@ export class MessageScripts extends Lists<MessageScript> {
|
|
|
403
464
|
}
|
|
404
465
|
|
|
405
466
|
/**
|
|
406
|
-
* Runs the script for the agent's `index`th message operation with `message
|
|
407
|
-
* ends the turn
|
|
467
|
+
* Runs the script for the agent's `index`th message operation with `message`: what it returns
|
|
468
|
+
* ends the turn silent unless it is a `noAnswer`.
|
|
408
469
|
*/
|
|
409
470
|
async step(message: ReceivedMessage, index: number): Promise<Step> {
|
|
410
471
|
const { turn } = message;
|
|
@@ -427,13 +488,13 @@ export class MessageScripts extends Lists<MessageScript> {
|
|
|
427
488
|
}
|
|
428
489
|
if (isEntry(out)) {
|
|
429
490
|
const given = out[ENTRY];
|
|
430
|
-
if (given.kind === "
|
|
491
|
+
if (given.kind === "no-answer") return { ...given };
|
|
431
492
|
return {
|
|
432
493
|
kind: "script-error",
|
|
433
494
|
message: `${where}: a message script ends a turn, it answers no call`,
|
|
434
495
|
};
|
|
435
496
|
}
|
|
436
|
-
return { kind: "
|
|
497
|
+
return { kind: "no-answer", ending: { kind: "silent" } };
|
|
437
498
|
}
|
|
438
499
|
}
|
|
439
500
|
|
|
@@ -459,7 +520,7 @@ export function findByKey<T>(
|
|
|
459
520
|
};
|
|
460
521
|
}
|
|
461
522
|
|
|
462
|
-
function isEntry(value: unknown): value is Answer |
|
|
523
|
+
function isEntry(value: unknown): value is Answer | NoAnswer {
|
|
463
524
|
return typeof value === "object" && value !== null && ENTRY in value;
|
|
464
525
|
}
|
|
465
526
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Checked by `tsc --noEmit`, never run: each wrong answer is refused where it is written.
|
|
2
2
|
import { defineWorkflow, type OutputSchema } from "@agentswf/contract/workflow";
|
|
3
3
|
import Type from "typebox";
|
|
4
|
-
import { answer,
|
|
4
|
+
import { answer, noAnswer, type Script, testWorkflow } from ".";
|
|
5
5
|
|
|
6
6
|
const VERDICT = Type.Union([
|
|
7
7
|
Type.Object({ kind: Type.Literal("ready"), summary: Type.String() }),
|
|
@@ -17,13 +17,15 @@ export const valid: Script[] = [
|
|
|
17
17
|
? { kind: "changes-requested", feedback: ["more"] }
|
|
18
18
|
: { kind: "ready", summary: "ok" },
|
|
19
19
|
),
|
|
20
|
-
answer(VERDICT, async (turn) =>
|
|
20
|
+
answer(VERDICT, async (turn) =>
|
|
21
|
+
turn.nudge ? noAnswer.failed() : { kind: "ready", summary: "" },
|
|
22
|
+
),
|
|
21
23
|
answer(CARRIED, { count: 1 }),
|
|
22
24
|
answer(CARRIED, () => ({ count: 2 })),
|
|
23
25
|
answer("text"),
|
|
24
26
|
answer((turn) => `turn ${turn.n}`),
|
|
25
|
-
|
|
26
|
-
[answer(VERDICT, { kind: "ready", summary: "ok" }),
|
|
27
|
+
noAnswer.silent(),
|
|
28
|
+
[answer(VERDICT, { kind: "ready", summary: "ok" }), noAnswer.hang(), answer("done")],
|
|
27
29
|
];
|
|
28
30
|
|
|
29
31
|
export const invalid: Script[] = [
|
|
@@ -37,8 +39,8 @@ export const invalid: Script[] = [
|
|
|
37
39
|
answer(VERDICT, () => ({ kind: "approved", summary: "ok" })),
|
|
38
40
|
// @ts-expect-error the carrier's type
|
|
39
41
|
answer(CARRIED, { count: "one" }),
|
|
40
|
-
// @ts-expect-error
|
|
41
|
-
answer(VERDICT,
|
|
42
|
+
// @ts-expect-error no answer is a script entry of its own, not an answer's value
|
|
43
|
+
answer(VERDICT, noAnswer.silent()),
|
|
42
44
|
// @ts-expect-error text answers a turn with no schema
|
|
43
45
|
answer(42),
|
|
44
46
|
];
|
|
@@ -53,12 +55,13 @@ const withEvents = defineWorkflow({
|
|
|
53
55
|
run: async (wf) => (await wf.receive()).length,
|
|
54
56
|
});
|
|
55
57
|
export const tested = () => testWorkflow(withEvents, { id: "k" });
|
|
56
|
-
// @ts-expect-error a workflow with events
|
|
58
|
+
// @ts-expect-error a workflow with events takes its args among its options
|
|
57
59
|
export const byArgs = () => testWorkflow(withEvents, null);
|
|
58
60
|
const plain = defineWorkflow({
|
|
59
61
|
meta: { name: "p", description: "p" },
|
|
60
62
|
prepare: ({ argv }) => ({ n: argv.length }),
|
|
61
63
|
run: async (_wf, args) => args.n,
|
|
62
64
|
});
|
|
63
|
-
// @ts-expect-error a workflow
|
|
65
|
+
// @ts-expect-error a workflow that takes args is given them
|
|
64
66
|
export const byOptions = () => testWorkflow(plain, { id: "k" });
|
|
67
|
+
export const argued = () => testWorkflow(plain, { id: "k", args: { n: 1 } });
|