@agentswf/engine 0.1.0-beta.4 → 0.1.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/LICENSE +21 -0
- package/README.md +16 -0
- package/package.json +13 -9
- package/src/accounting/format.ts +4 -3
- package/src/accounting/spent.ts +128 -0
- package/src/accounting/summary.ts +40 -32
- package/src/agent-cli/cli.ts +35 -22
- package/src/agent-cli/client.ts +1 -27
- package/src/allowance-command.ts +8 -10
- package/src/attempt-ending.ts +5 -7
- package/src/attempt-output.ts +15 -32
- package/src/attempt-view.ts +34 -33
- package/src/author-typebox.ts +13 -0
- package/src/client/checks.ts +94 -0
- package/src/client/exported.ts +17 -0
- package/src/client/index.ts +122 -0
- package/src/client/locate.ts +34 -0
- package/src/client/published.ts +3 -0
- package/src/client/runs.ts +337 -0
- package/src/command-line.ts +64 -0
- package/src/commands.ts +7 -1
- package/src/control-plane.ts +41 -11
- package/src/coordinator-prompt.ts +4 -3
- package/src/coordinator-socket.ts +41 -33
- package/src/deadlines.ts +40 -7
- package/src/decisions/directory.ts +51 -40
- package/src/decisions/fake.ts +1 -1
- package/src/decisions/openrouter.ts +4 -2
- package/src/decisions/seam.ts +4 -4
- package/src/display-path.ts +3 -3
- package/src/duration.ts +43 -27
- package/src/emit-command.ts +88 -58
- package/src/emit.ts +254 -56
- package/src/event-ids.ts +31 -0
- package/src/files.ts +44 -4
- package/src/hand-over.ts +150 -0
- package/src/help/allowance.txt +4 -3
- package/src/help/emit.txt +18 -6
- package/src/help/inbox.txt +6 -4
- package/src/help/logs.txt +9 -6
- package/src/help/open.txt +29 -0
- package/src/help/reply.txt +6 -8
- package/src/help/run.txt +30 -11
- package/src/help/runs.txt +4 -2
- package/src/help/tick.txt +9 -4
- package/src/herdr-placement.ts +3 -33
- package/src/herdr-run-session.ts +7 -15
- package/src/herdr-workspace-marks.ts +7 -17
- package/src/holder-command.ts +56 -133
- package/src/in-lines.ts +28 -0
- package/src/inbox-command.ts +40 -57
- package/src/index.ts +1 -1
- package/src/init-command.ts +8 -17
- package/src/init-template/hello/workflow.test.ts.template +3 -3
- package/src/init-template/hello/workflow.ts.template +5 -4
- package/src/jsonl.ts +10 -6
- package/src/keep-alive.ts +15 -12
- package/src/logs-command.ts +128 -47
- package/src/message-operation.ts +6 -3
- package/src/messaging/limits.ts +4 -4
- package/src/messaging/messaging.ts +31 -28
- package/src/messaging/routes.ts +1 -1
- package/src/messaging/text.ts +9 -7
- package/src/open-command.ts +770 -0
- package/src/operation-events.ts +4 -6
- package/src/operation-liveness.ts +19 -4
- package/src/operator-cli.ts +224 -94
- package/src/operator-runtime.ts +29 -7
- package/src/ops.ts +270 -0
- package/src/pane-layout.ts +105 -59
- package/src/progress-view.ts +10 -10
- package/src/project-engine.ts +79 -0
- package/src/projects.ts +13 -2
- package/src/refusals.ts +119 -0
- package/src/result-slots.ts +11 -31
- package/src/result-validation.ts +2 -2
- package/src/run-command.ts +45 -28
- package/src/run-commands.ts +43 -115
- package/src/run-event-log.ts +1 -1
- package/src/run-lines.ts +159 -0
- package/src/run-lock.ts +57 -1
- package/src/{stage-plan.ts → run-policy/stage-plan.ts} +46 -42
- package/src/run-policy/stages.ts +113 -0
- package/src/run-policy/waits.ts +452 -0
- package/src/run-prepare.ts +205 -43
- package/src/run-read.ts +109 -41
- package/src/run-state.ts +170 -50
- package/src/run-text.ts +54 -0
- package/src/run-unreachable.ts +18 -0
- package/src/run-usage.ts +18 -12
- package/src/run-view.ts +81 -17
- package/src/runs-command.ts +83 -63
- package/src/runs.ts +310 -212
- package/src/sandbox-homes.ts +65 -13
- package/src/sandboxes.ts +182 -28
- package/src/session-lines.ts +93 -0
- package/src/skills/run-skills.ts +8 -1
- package/src/sources.ts +28 -29
- package/src/spend.ts +333 -0
- package/src/stage-ledger.ts +119 -131
- package/src/stop-crashed.ts +6 -5
- package/src/stopped.ts +4 -4
- package/src/test-command.ts +36 -14
- package/src/testing/operation-events.ts +3 -23
- package/src/testing.ts +73 -26
- package/src/tick-command.ts +145 -15
- package/src/tools.ts +25 -16
- package/src/turn-ends.ts +37 -0
- package/src/waits.ts +651 -0
- package/src/wake.ts +84 -54
- package/src/workflow-loader.ts +54 -4
- package/src/workflow-runner.ts +805 -580
- package/src/workflow-test-preload.ts +4 -1
- package/src/workflow-testing/coordinators.ts +3 -3
- package/src/workflow-testing/fake-client.ts +387 -0
- package/src/workflow-testing/host.ts +121 -53
- package/src/workflow-testing/index.ts +33 -842
- package/src/workflow-testing/run-test.ts +1128 -0
- package/src/workflow-testing/script.ts +74 -45
- package/src/hold-run-lock.fixture.ts +0 -8
- package/src/mailbox.ts +0 -990
- package/src/workflow-testing/coordinator.typecheck.ts +0 -54
- package/src/workflow-testing/workflow-testing.typecheck.ts +0 -64
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
import type { ParticipantCode, PeerView, ReplyCode, SendCode } from "@agentswf/contract/wire";
|
|
10
10
|
import type { AgentKey, JsonObject } from "@agentswf/contract/workflow";
|
|
11
11
|
import type { AuthoredMessage } from "@agentswf/harness/adapter";
|
|
12
|
-
import { duration } from "../
|
|
12
|
+
import { duration } from "../accounting/format";
|
|
13
13
|
import { MESSAGE_LIMITS } from "./limits";
|
|
14
14
|
import {
|
|
15
15
|
directions,
|
|
@@ -53,6 +53,8 @@ export interface MessagingHost {
|
|
|
53
53
|
changed(key: AgentKey): void;
|
|
54
54
|
schedule(at: number, action: () => void): () => void;
|
|
55
55
|
now(): number;
|
|
56
|
+
/** Question `n`'s id as `awf inbox` prints it, the attempt before it: what a refusal names. */
|
|
57
|
+
question(n: number): string;
|
|
56
58
|
/** Written to the run's event log; `message-sent` is awaited before a send is acknowledged. */
|
|
57
59
|
record(event: MessagingEventBody): Promise<void>;
|
|
58
60
|
}
|
|
@@ -117,7 +119,7 @@ export type HolderRequest = {
|
|
|
117
119
|
/** A holder's result: a reply's `message` is the question's number, a send's the new message's. */
|
|
118
120
|
export type HolderResult =
|
|
119
121
|
| { code: "delivered"; message: number }
|
|
120
|
-
| { code:
|
|
122
|
+
| { code: ParticipantCode; error: string; choices?: string[] };
|
|
121
123
|
|
|
122
124
|
/** A question open to an outside participant, and when it closes unanswered if it has a limit. */
|
|
123
125
|
export type OpenQuestion = {
|
|
@@ -180,7 +182,7 @@ type Obligation = {
|
|
|
180
182
|
/** It has a `--timeout` of its own, which only that fails it at. */
|
|
181
183
|
readonly timed: boolean;
|
|
182
184
|
/**
|
|
183
|
-
* When it closes unanswered
|
|
185
|
+
* When it closes unanswered: the route's `timeoutMs` or the asker's
|
|
184
186
|
* shorter `--timeout`, and which of the two.
|
|
185
187
|
*/
|
|
186
188
|
readonly closes?: { at: number; by: "route" | "own"; ms: number };
|
|
@@ -279,15 +281,13 @@ export class Messaging {
|
|
|
279
281
|
}
|
|
280
282
|
}
|
|
281
283
|
const timeoutMs = options.timeoutMs;
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
290
|
-
}
|
|
284
|
+
if (
|
|
285
|
+
timeoutMs !== undefined &&
|
|
286
|
+
(typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1)
|
|
287
|
+
)
|
|
288
|
+
throw new Error(
|
|
289
|
+
`a route's timeoutMs is a positive whole number of milliseconds: not ${timeoutMs}`,
|
|
290
|
+
);
|
|
291
291
|
const open = pairs.map(([from, to]) => this.#open(from, to));
|
|
292
292
|
if (open.some(Boolean)) {
|
|
293
293
|
if (
|
|
@@ -361,7 +361,7 @@ export class Messaging {
|
|
|
361
361
|
});
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
/** Writes `text` to a participant
|
|
364
|
+
/** Writes `text` to a participant, without a turn: an inbox item for it. */
|
|
365
365
|
async post(key: AgentKey, text: string): Promise<string> {
|
|
366
366
|
if (this.#closed) throw new Error("the attempt has ended; nothing more is posted");
|
|
367
367
|
if (!this.#outside.has(key)) throw new Error(`${key} is not connected`);
|
|
@@ -446,7 +446,7 @@ export class Messaging {
|
|
|
446
446
|
return holderRefusal(
|
|
447
447
|
"ambiguous",
|
|
448
448
|
`${agent} has ${open.length} questions open to ${as.key}; name one with --to.`,
|
|
449
|
-
open.map((each) =>
|
|
449
|
+
open.map((each) => this.#host.question(each.request.n)),
|
|
450
450
|
);
|
|
451
451
|
owed = open[0];
|
|
452
452
|
if (!owed) return holderRefusal("expired", `${agent} has no question open to ${as.key}.`);
|
|
@@ -461,7 +461,7 @@ export class Messaging {
|
|
|
461
461
|
if (!owed)
|
|
462
462
|
return holderRefusal(
|
|
463
463
|
"expired",
|
|
464
|
-
`${request.to} is not a question open from ${agent}${as.key === undefined ? "" : ` to ${as.key}`}.`,
|
|
464
|
+
`${this.#host.question(request.to)} is not a question open from ${agent}${as.key === undefined ? "" : ` to ${as.key}`}.`,
|
|
465
465
|
);
|
|
466
466
|
}
|
|
467
467
|
const route = owed.request.route;
|
|
@@ -506,7 +506,7 @@ export class Messaging {
|
|
|
506
506
|
return holderRefusal("no-route", `the workflow allows no route from ${from} to ${agent}.`);
|
|
507
507
|
if (view.state === "stopped")
|
|
508
508
|
return holderRefusal(
|
|
509
|
-
"stopped",
|
|
509
|
+
"peer-stopped",
|
|
510
510
|
`${agent} has stopped${view.reason ? ` (${JSON.stringify(view.reason)})` : ""}.`,
|
|
511
511
|
);
|
|
512
512
|
const route = initiating.find((each) => each.open);
|
|
@@ -942,18 +942,20 @@ export class Messaging {
|
|
|
942
942
|
return { kind: "ok", operation, owed: latest, bytes };
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
-
/** `awaiting`, `unsupported` and `cycle`, checked for any question. */
|
|
945
|
+
/** `already-awaiting`, `unsupported` and `cycle`, checked for any question. */
|
|
946
946
|
#checkAsk(
|
|
947
947
|
from: AgentKey,
|
|
948
948
|
peer: AgentKey,
|
|
949
949
|
operation: Operation,
|
|
950
950
|
/** The question a reply answers, which counts as answered here. */
|
|
951
951
|
answering?: Obligation,
|
|
952
|
-
):
|
|
952
|
+
):
|
|
953
|
+
| { kind: "refused"; code: "already-awaiting" | "unsupported" | "cycle"; error: string }
|
|
954
|
+
| undefined {
|
|
953
955
|
const wfa = this.#host.launcher(from);
|
|
954
956
|
if (operation.awaits?.state === "open") {
|
|
955
957
|
return refuse(
|
|
956
|
-
"awaiting",
|
|
958
|
+
"already-awaiting",
|
|
957
959
|
`you already await ${operation.awaits.owedBy}. End your turn, or send without --await-reply.`,
|
|
958
960
|
);
|
|
959
961
|
}
|
|
@@ -1045,7 +1047,7 @@ export class Messaging {
|
|
|
1045
1047
|
#ask(message: Message, operation: Operation, timeoutMs?: number): { until?: number } {
|
|
1046
1048
|
message.route.count += 1;
|
|
1047
1049
|
const now = this.#host.now();
|
|
1048
|
-
const routeMs =
|
|
1050
|
+
const routeMs = message.route.timeoutMs;
|
|
1049
1051
|
// The route's limit, or the asker's own when shorter: the shorter wins.
|
|
1050
1052
|
const limit =
|
|
1051
1053
|
routeMs !== undefined && (timeoutMs === undefined || routeMs <= timeoutMs)
|
|
@@ -1069,7 +1071,7 @@ export class Messaging {
|
|
|
1069
1071
|
owedBy: message.to,
|
|
1070
1072
|
...(until === undefined ? {} : { until }),
|
|
1071
1073
|
timed: limitAt < (outside ? Infinity : (operation.deadline ?? Infinity)),
|
|
1072
|
-
...(limit
|
|
1074
|
+
...(limit ? { closes: { ...limit, at: limitAt } } : {}),
|
|
1073
1075
|
state: "open",
|
|
1074
1076
|
};
|
|
1075
1077
|
message.asks = obligation;
|
|
@@ -1375,7 +1377,7 @@ export class Messaging {
|
|
|
1375
1377
|
return {
|
|
1376
1378
|
peer,
|
|
1377
1379
|
can: this.#can(route, agent),
|
|
1378
|
-
|
|
1380
|
+
kind: "participant",
|
|
1379
1381
|
queued: 0,
|
|
1380
1382
|
purpose: route.purpose,
|
|
1381
1383
|
};
|
|
@@ -1388,8 +1390,13 @@ export class Messaging {
|
|
|
1388
1390
|
return {
|
|
1389
1391
|
peer,
|
|
1390
1392
|
can: this.#can(route, agent),
|
|
1393
|
+
kind: "agent",
|
|
1391
1394
|
state:
|
|
1392
|
-
view?.state === "stopped"
|
|
1395
|
+
view?.state === "stopped"
|
|
1396
|
+
? "stopped"
|
|
1397
|
+
: awaiting
|
|
1398
|
+
? "awaiting-reply"
|
|
1399
|
+
: (view?.state ?? "idle"),
|
|
1393
1400
|
...(awaiting && view?.state !== "stopped" ? { awaits: awaiting } : {}),
|
|
1394
1401
|
...(view?.state === "stopped" && view.reason ? { reason: view.reason } : {}),
|
|
1395
1402
|
queued,
|
|
@@ -1676,11 +1683,7 @@ function refuse<C extends SendCode | ReplyCode>(code: C, error: string) {
|
|
|
1676
1683
|
return { kind: "refused" as const, code, error };
|
|
1677
1684
|
}
|
|
1678
1685
|
|
|
1679
|
-
function holderRefusal(
|
|
1680
|
-
code: Exclude<ParticipantCode, "not-found">,
|
|
1681
|
-
error: string,
|
|
1682
|
-
choices?: string[],
|
|
1683
|
-
): HolderResult {
|
|
1686
|
+
function holderRefusal(code: ParticipantCode, error: string, choices?: string[]): HolderResult {
|
|
1684
1687
|
return { code, error, ...(choices ? { choices } : {}) };
|
|
1685
1688
|
}
|
|
1686
1689
|
|
package/src/messaging/routes.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type Route = {
|
|
|
21
21
|
readonly asked?: number;
|
|
22
22
|
/** As the workflow gave it, for telling an identical grant from a conflicting one. */
|
|
23
23
|
readonly requested?: number;
|
|
24
|
-
/**
|
|
24
|
+
/** How long a question on the route waits, to an agent or an outside participant. */
|
|
25
25
|
readonly timeoutMs?: number;
|
|
26
26
|
readonly scope: Scope;
|
|
27
27
|
/** Messages counted against it, answers reserved for included. */
|
package/src/messaging/text.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type Delivered =
|
|
|
31
31
|
}
|
|
32
32
|
| { kind: "no-reply"; from: AgentKey; reason: string };
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
function quoted(purpose: string): string {
|
|
35
35
|
return `“${purpose}”`;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -53,7 +53,7 @@ export function clock(at: number, now: number): string {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** At least eight hex characters, as the design asks; a fresh one per delivery. */
|
|
56
|
-
|
|
56
|
+
function nonce(): string {
|
|
57
57
|
return randomBytes(4).toString("hex");
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -228,11 +228,13 @@ export function peersTable(
|
|
|
228
228
|
const rows = peers.map((peer) => [
|
|
229
229
|
peer.peer,
|
|
230
230
|
peer.can,
|
|
231
|
-
peer.
|
|
232
|
-
?
|
|
233
|
-
: peer.state === "
|
|
234
|
-
? `
|
|
235
|
-
: peer.state
|
|
231
|
+
peer.kind === "participant"
|
|
232
|
+
? "participant"
|
|
233
|
+
: peer.state === "awaiting-reply"
|
|
234
|
+
? `awaiting-reply from ${peer.awaits === viewer ? "you" : peer.awaits}`
|
|
235
|
+
: peer.state === "stopped" && peer.reason
|
|
236
|
+
? `stopped (${peer.reason})`
|
|
237
|
+
: peer.state,
|
|
236
238
|
peer.queued > 0 ? String(peer.queued) : "",
|
|
237
239
|
quoted(peer.purpose),
|
|
238
240
|
]);
|