@agentswf/engine 0.0.0-stage → 0.1.0-beta.0
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 +39 -4
- package/src/accounting/format.ts +252 -0
- package/src/accounting/plans.ts +38 -0
- package/src/accounting/prices.ts +98 -0
- package/src/accounting/summary.ts +385 -0
- package/src/accounting/tokens.ts +34 -0
- package/src/agent-launcher.ts +127 -0
- package/src/allowance-command.ts +105 -0
- package/src/attempt-ending.ts +198 -0
- package/src/attempt-output.ts +132 -0
- package/src/attempt-view.ts +362 -0
- package/src/canonical-json.ts +12 -0
- package/src/control-plane.ts +424 -0
- package/src/deadlines.ts +106 -0
- package/src/decisions/directory.ts +432 -0
- package/src/decisions/fake.ts +60 -0
- package/src/decisions/openrouter.ts +161 -0
- package/src/decisions/seam.ts +70 -0
- package/src/display-path.ts +16 -0
- package/src/duration.ts +12 -0
- package/src/errors.ts +4 -0
- package/src/files.ts +78 -0
- package/src/herdr-placement.ts +182 -0
- package/src/herdr-run-session.ts +530 -0
- package/src/herdr-workspace-marks.ts +194 -0
- package/src/here.ts +289 -0
- package/src/index.ts +5 -0
- package/src/jsonl.ts +74 -0
- package/src/logs-command.ts +605 -0
- package/src/machine.ts +27 -0
- package/src/message-operation.ts +245 -0
- package/src/messaging/limits.ts +35 -0
- package/src/messaging/messaging.ts +1303 -0
- package/src/messaging/routes.ts +79 -0
- package/src/messaging/text.ts +251 -0
- package/src/operation-events.ts +182 -0
- package/src/operation-liveness.ts +674 -0
- package/src/operator-aliases.ts +10 -0
- package/src/operator-cli.ts +569 -0
- package/src/operator-runtime.ts +503 -0
- package/src/pane-layout.ts +125 -0
- package/src/progress-view.ts +568 -0
- package/src/result-slots.ts +325 -0
- package/src/result-validation.ts +47 -0
- package/src/run-command.ts +241 -0
- package/src/run-commands.ts +215 -0
- package/src/run-event-log.ts +72 -0
- package/src/run-prepare.ts +178 -0
- package/src/run-progress.ts +137 -0
- package/src/run-read.ts +425 -0
- package/src/run-state.ts +120 -0
- package/src/run-usage.ts +393 -0
- package/src/run-view.ts +225 -0
- package/src/runs-command.ts +356 -0
- package/src/runs.ts +645 -0
- package/src/sandbox-homes.ts +211 -0
- package/src/sandboxes.ts +516 -0
- package/src/skills/fetch.ts +305 -0
- package/src/skills/run-skills.ts +181 -0
- package/src/skills/sources.ts +87 -0
- package/src/skills/tree.ts +60 -0
- package/src/stage-ledger.ts +535 -0
- package/src/stage-plan.ts +127 -0
- package/src/stage-schema.ts +88 -0
- package/src/stopped.ts +36 -0
- package/src/test-command.ts +110 -0
- package/src/testing/operation-events.ts +89 -0
- package/src/testing.ts +164 -0
- package/src/workflow-loader.ts +113 -0
- package/src/workflow-runner.ts +3665 -0
- package/src/workflow-test-preload.ts +13 -0
- package/src/workflow-testing/decisions.ts +129 -0
- package/src/workflow-testing/host.ts +811 -0
- package/src/workflow-testing/index.ts +420 -0
- package/src/workflow-testing/script.ts +447 -0
- package/src/workflow-testing/workflow-testing.typecheck.ts +47 -0
- package/README.md +0 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `awf test` preloads this into `bun test`, so a workflow's tests in any folder import the author
|
|
2
|
+
// surface and `agentswf/testing` with nothing installed there.
|
|
3
|
+
import { plugin } from "bun";
|
|
4
|
+
import { serveAuthorSurface } from "./workflow-loader";
|
|
5
|
+
import * as testing from "./workflow-testing";
|
|
6
|
+
|
|
7
|
+
serveAuthorSurface();
|
|
8
|
+
plugin({
|
|
9
|
+
name: "agentswf testing",
|
|
10
|
+
setup(build) {
|
|
11
|
+
build.module("agentswf/testing", () => ({ exports: testing, loader: "object" }));
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Question } from "@agentswf/contract/workflow";
|
|
2
|
+
import { answersOf } from "../decisions/directory";
|
|
3
|
+
import type {
|
|
4
|
+
DecisionProvider,
|
|
5
|
+
ProviderAnswer,
|
|
6
|
+
ProviderRequest,
|
|
7
|
+
ProviderResponse,
|
|
8
|
+
} from "../decisions/seam";
|
|
9
|
+
import { messageOf } from "../errors";
|
|
10
|
+
import { findByKey } from "./script";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One question's answer in the author's terms:
|
|
14
|
+
* - a choice: the option's name, or a probability per option;
|
|
15
|
+
* - a score: the level's index, or a probability per level, lowest first;
|
|
16
|
+
* - a yes-no: `true` or `false`, or the probability of yes.
|
|
17
|
+
*/
|
|
18
|
+
export type DecisionAnswer =
|
|
19
|
+
| string
|
|
20
|
+
| number
|
|
21
|
+
| boolean
|
|
22
|
+
| Readonly<Record<string, number>>
|
|
23
|
+
| readonly number[];
|
|
24
|
+
|
|
25
|
+
/** A decision as the workflow asked it. */
|
|
26
|
+
export type DecisionRequest = Omit<ProviderRequest, "model">;
|
|
27
|
+
|
|
28
|
+
type Answers = Readonly<Record<string, DecisionAnswer>>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What a decision answers, by question id, or a function of the request that returns that. A
|
|
32
|
+
* returned `Error` makes `decide` reject, as a provider failure does; a thrown one fails the test.
|
|
33
|
+
*/
|
|
34
|
+
export type DecisionScript =
|
|
35
|
+
| Answers
|
|
36
|
+
| Error
|
|
37
|
+
| ((request: DecisionRequest) => Answers | Error | Promise<Answers | Error>);
|
|
38
|
+
|
|
39
|
+
/** The provider a workflow's test installs as `jev`: it answers each call from its key's script. */
|
|
40
|
+
export function createScriptedDecisions(
|
|
41
|
+
scripts: Readonly<Record<string, DecisionScript>>,
|
|
42
|
+
events: { onActivity(): void; onScriptError(message: string): void },
|
|
43
|
+
): { provider: DecisionProvider; asked: DecisionRequest[] } {
|
|
44
|
+
const asked: DecisionRequest[] = [];
|
|
45
|
+
const provider: DecisionProvider = {
|
|
46
|
+
async decide(request) {
|
|
47
|
+
events.onActivity();
|
|
48
|
+
try {
|
|
49
|
+
return await scripted(request);
|
|
50
|
+
} finally {
|
|
51
|
+
events.onActivity();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const scripted = async (request: ProviderRequest): Promise<ProviderResponse> => {
|
|
56
|
+
const { model: _model, ...asking } = request;
|
|
57
|
+
const { key } = asking;
|
|
58
|
+
asked.push(asking);
|
|
59
|
+
const fail = (why: string): never => {
|
|
60
|
+
const message = `decision "${key}": ${why}`;
|
|
61
|
+
events.onScriptError(message);
|
|
62
|
+
throw new Error(message);
|
|
63
|
+
};
|
|
64
|
+
const found = findByKey(scripts, key, "decision");
|
|
65
|
+
if ("why" in found) return fail(found.why);
|
|
66
|
+
let given: Answers | Error;
|
|
67
|
+
try {
|
|
68
|
+
const script = found.value;
|
|
69
|
+
given = typeof script === "function" ? await script(asking) : script;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return fail(`its script threw: ${messageOf(error)}`);
|
|
72
|
+
}
|
|
73
|
+
if (given instanceof Error) throw given;
|
|
74
|
+
for (const id of Object.keys(given)) {
|
|
75
|
+
if (!Object.hasOwn(request.questions, id)) {
|
|
76
|
+
return fail(`its script answers "${id}", which it did not ask`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const answers: Record<string, ProviderAnswer> = {};
|
|
80
|
+
for (const [id, question] of Object.entries(request.questions)) {
|
|
81
|
+
if (!Object.hasOwn(given, id)) continue;
|
|
82
|
+
const answer = distribution(question, given[id]!);
|
|
83
|
+
if (!answer) {
|
|
84
|
+
return fail(
|
|
85
|
+
`"${id}" is a ${question.type} question; its script answers ${JSON.stringify(given[id])}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
answers[id] = answer;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
answersOf(request.questions, answers);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return fail(`its script's answer is refused: ${messageOf(error)}`);
|
|
94
|
+
}
|
|
95
|
+
return { snapshot: "scripted", answers };
|
|
96
|
+
};
|
|
97
|
+
return { provider, asked };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The author's shorthand as the distribution a provider returns, or undefined when it is no answer
|
|
102
|
+
* to this kind of question. `answersOf` then checks it as it checks a provider's.
|
|
103
|
+
*/
|
|
104
|
+
function distribution(question: Question, answer: DecisionAnswer): ProviderAnswer | undefined {
|
|
105
|
+
const certain = (names: readonly string[], picked: string) =>
|
|
106
|
+
names.map((name) => [name, name === picked ? 1 : 0] as const);
|
|
107
|
+
if (question.type === "yes-no") {
|
|
108
|
+
if (typeof answer === "boolean") return { type: "yes-no", yes: Number(answer) };
|
|
109
|
+
return typeof answer === "number" ? { type: "yes-no", yes: answer } : undefined;
|
|
110
|
+
}
|
|
111
|
+
if (question.type === "choice") {
|
|
112
|
+
const names = Object.keys(question.options);
|
|
113
|
+
if (typeof answer === "string") {
|
|
114
|
+
return names.includes(answer)
|
|
115
|
+
? { type: "choice", probabilities: Object.fromEntries(certain(names, answer)) }
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
118
|
+
return typeof answer === "object" && !Array.isArray(answer)
|
|
119
|
+
? { type: "choice", probabilities: { ...(answer as Record<string, number>) } }
|
|
120
|
+
: undefined;
|
|
121
|
+
}
|
|
122
|
+
const levels = question.levels.map((_, level) => String(level));
|
|
123
|
+
if (typeof answer === "number") {
|
|
124
|
+
return levels.includes(String(answer))
|
|
125
|
+
? { type: "score", probabilities: certain(levels, String(answer)).map(([, p]) => p) }
|
|
126
|
+
: undefined;
|
|
127
|
+
}
|
|
128
|
+
return Array.isArray(answer) ? { type: "score", probabilities: [...answer] } : undefined;
|
|
129
|
+
}
|