@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SandboxRecord } from "@agentswf/contract/records";
|
|
1
|
+
import type { SandboxRecord, TurnRecord } from "@agentswf/contract/records";
|
|
2
2
|
import {
|
|
3
3
|
type MessageResponse,
|
|
4
4
|
REPLY_CODES,
|
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
type Effort,
|
|
17
17
|
type JsonObject,
|
|
18
18
|
type KeepPane,
|
|
19
|
-
type
|
|
19
|
+
type PaneSpec,
|
|
20
|
+
type PaneWorkspace,
|
|
20
21
|
placementOf,
|
|
22
|
+
positiveDurationMilliseconds,
|
|
21
23
|
} from "@agentswf/contract/workflow";
|
|
22
24
|
import {
|
|
23
25
|
createPlacementHostFactory,
|
|
@@ -37,28 +39,28 @@ import {
|
|
|
37
39
|
type FakeFork,
|
|
38
40
|
type FakeSet,
|
|
39
41
|
} from "@agentswf/harness/testing";
|
|
40
|
-
import { submitControl
|
|
42
|
+
import { submitControl } from "../agent-cli/client";
|
|
41
43
|
import {
|
|
42
44
|
type Message,
|
|
43
45
|
type MessageOutcome,
|
|
44
46
|
type MessageScripts,
|
|
47
|
+
outcomeOf,
|
|
45
48
|
type ReceivedMessage,
|
|
46
|
-
reply,
|
|
47
49
|
type Scripts,
|
|
48
50
|
type SendOptions,
|
|
49
51
|
type Step,
|
|
52
|
+
type TestTurnOutcome,
|
|
50
53
|
type ToolOutcome,
|
|
51
54
|
type Turn,
|
|
52
|
-
type TurnOutcome,
|
|
53
55
|
} from "./script";
|
|
54
56
|
|
|
55
57
|
/**
|
|
56
58
|
* A turn as the workflow wrote it, and how it ended. `messages` are those it was shown, absent when
|
|
57
59
|
* none were.
|
|
58
60
|
*/
|
|
59
|
-
export type
|
|
61
|
+
export type TestTurn = Omit<Turn, "signal" | "messages" | "send" | "reply" | "tool"> & {
|
|
60
62
|
messages?: readonly Message[];
|
|
61
|
-
outcome:
|
|
63
|
+
outcome: TestTurnOutcome;
|
|
62
64
|
};
|
|
63
65
|
|
|
64
66
|
/** A compaction as the workflow asked for it, and how it ended. */
|
|
@@ -69,7 +71,7 @@ export type CompactionRecord = {
|
|
|
69
71
|
id: string;
|
|
70
72
|
/** The spec's prompt: what the harness is to keep and drop. */
|
|
71
73
|
focus: string;
|
|
72
|
-
outcome:
|
|
74
|
+
outcome: TestTurnOutcome;
|
|
73
75
|
};
|
|
74
76
|
|
|
75
77
|
/** A switch of an agent's model or effort, as the host was asked for it. */
|
|
@@ -87,7 +89,6 @@ export type OpenedAgent = {
|
|
|
87
89
|
key: string;
|
|
88
90
|
execution: AgentExecution;
|
|
89
91
|
instructions?: string;
|
|
90
|
-
labels?: JsonObject;
|
|
91
92
|
/** The skills it was given by name; absent when the workflow named none. */
|
|
92
93
|
skills?: readonly string[];
|
|
93
94
|
/** The sandbox it ran in; absent on the host. */
|
|
@@ -98,28 +99,37 @@ export type OpenedAgent = {
|
|
|
98
99
|
*/
|
|
99
100
|
forkedFrom?: { key: string; turns: number } | { caller: true; turns: number };
|
|
100
101
|
/**
|
|
101
|
-
* Its pane
|
|
102
|
-
* pane landed or whether a `beside` fell back.
|
|
102
|
+
* Its `pane` as the host was asked to place it: its `tab`, or `beside` another, and `front`. This
|
|
103
|
+
* host has no screen, so nothing says where a pane landed or whether a `beside` fell back.
|
|
103
104
|
*/
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
pane?: PaneSpec;
|
|
106
|
+
/**
|
|
107
|
+
* The run's `panes` as the host was asked to apply them to this pane: the `workspace` its tab
|
|
108
|
+
* opens in, which a `beside` takes from the pane it splits, and whether it is `keep`.
|
|
109
|
+
*/
|
|
110
|
+
panes?: { workspace?: PaneWorkspace; keep?: KeepPane };
|
|
111
|
+
/** Why the engine already knew `pane` could not be used: a `beside` an agent with no pane. */
|
|
112
|
+
paneFallback?: string;
|
|
113
|
+
/** Its pane was kept when it was done: `panes.keep` asked, and its last operation's end let it. */
|
|
109
114
|
keptPane?: true;
|
|
110
115
|
};
|
|
111
116
|
|
|
112
117
|
/** The sandbox an agent ran in, as the run's record keeps it. */
|
|
113
118
|
export type AgentSandbox = Pick<SandboxRecord, "key" | "provider" | "spec" | "domains">;
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
type ScriptedHost = {
|
|
116
121
|
factory: AgentRunHostFactory;
|
|
117
|
-
turns:
|
|
122
|
+
turns: TestTurn[];
|
|
118
123
|
compactions: CompactionRecord[];
|
|
119
124
|
sets: SetRecord[];
|
|
120
125
|
agents: OpenedAgent[];
|
|
121
126
|
/** Turns started and not yet ended, for a stalled run's message. */
|
|
122
|
-
inFlight():
|
|
127
|
+
inFlight(): TestTurn[];
|
|
128
|
+
/**
|
|
129
|
+
* Once the run has ended, gives each operation's last turn, and each compaction, the outcome the
|
|
130
|
+
* engine recorded for it in `turns`: a turn before that, a nudge's or a wait's, keeps its own.
|
|
131
|
+
*/
|
|
132
|
+
settle(recorded: readonly TurnRecord[]): void;
|
|
123
133
|
};
|
|
124
134
|
|
|
125
135
|
/**
|
|
@@ -138,7 +148,7 @@ export function createScriptedHost(
|
|
|
138
148
|
/** The script could not meet a turn: the test fails. */
|
|
139
149
|
onScriptError(message: string): void;
|
|
140
150
|
/** A turn began, before its script answers: what a test sends during it goes now. */
|
|
141
|
-
onTurnStarted?(turn:
|
|
151
|
+
onTurnStarted?(turn: TestTurn): Promise<void>;
|
|
142
152
|
},
|
|
143
153
|
/**
|
|
144
154
|
* The session the run is started from, as `awf run --here` finds one, and the model its files
|
|
@@ -146,12 +156,14 @@ export function createScriptedHost(
|
|
|
146
156
|
*/
|
|
147
157
|
caller?: { harness: string; cwd: string; model?: string; here?: boolean },
|
|
148
158
|
): ScriptedHost {
|
|
149
|
-
const turns:
|
|
159
|
+
const turns: TestTurn[] = [];
|
|
150
160
|
const compactions: CompactionRecord[] = [];
|
|
151
161
|
const compactionCounts = new Map<string, number>();
|
|
152
162
|
const sets: SetRecord[] = [];
|
|
153
163
|
const agents: OpenedAgent[] = [];
|
|
154
|
-
const open = new Set<
|
|
164
|
+
const open = new Set<TestTurn>();
|
|
165
|
+
/** Each turn's operation, which the engine's record of it names. */
|
|
166
|
+
const operationOf = new Map<TestTurn, string>();
|
|
155
167
|
const counts = new Map<string, number>();
|
|
156
168
|
/** Each fork's session, by the agent it copied and that agent's turns so far. */
|
|
157
169
|
const forks = new Map<string, NonNullable<OpenedAgent["forkedFrom"]>>();
|
|
@@ -172,7 +184,7 @@ export function createScriptedHost(
|
|
|
172
184
|
turns: callerKey === undefined ? 0 : (counts.get(callerKey) ?? 0),
|
|
173
185
|
}),
|
|
174
186
|
}));
|
|
175
|
-
const end = (record:
|
|
187
|
+
const end = (record: TestTurn, outcome: TestTurnOutcome) => {
|
|
176
188
|
record.outcome = outcome;
|
|
177
189
|
open.delete(record);
|
|
178
190
|
events.onActivity();
|
|
@@ -206,13 +218,17 @@ export function createScriptedHost(
|
|
|
206
218
|
failTest(why);
|
|
207
219
|
throw new Error(why);
|
|
208
220
|
}
|
|
221
|
+
const timeoutMs =
|
|
222
|
+
options.timeout == null
|
|
223
|
+
? undefined
|
|
224
|
+
: positiveDurationMilliseconds(options.timeout, `a ${command}'s timeout`);
|
|
209
225
|
const call = submitControl(operation.binding.endpoint, {
|
|
210
226
|
version: WIRE_VERSION,
|
|
211
227
|
command,
|
|
212
228
|
peer,
|
|
213
229
|
text,
|
|
214
230
|
...(options.awaitReply ? { awaitReply: true as const } : {}),
|
|
215
|
-
...(
|
|
231
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
216
232
|
});
|
|
217
233
|
invocation.calls.add(call);
|
|
218
234
|
let response: MessageResponse;
|
|
@@ -304,7 +320,7 @@ export function createScriptedHost(
|
|
|
304
320
|
const invoke = (
|
|
305
321
|
context: FakeAdapterTurnContext,
|
|
306
322
|
operation: Operation,
|
|
307
|
-
record:
|
|
323
|
+
record: TestTurn,
|
|
308
324
|
shown: readonly Message[],
|
|
309
325
|
): Invocation => {
|
|
310
326
|
const { activation, authored } = context;
|
|
@@ -338,8 +354,6 @@ export function createScriptedHost(
|
|
|
338
354
|
reply: (text, options) => turn.reply(woke!.from, text, options),
|
|
339
355
|
send: turn.send,
|
|
340
356
|
tool: turn.tool,
|
|
341
|
-
silent: reply.silent,
|
|
342
|
-
fail: (reason) => reply.failed(reason),
|
|
343
357
|
} satisfies ReceivedMessage,
|
|
344
358
|
operation.message!,
|
|
345
359
|
);
|
|
@@ -353,7 +367,9 @@ export function createScriptedHost(
|
|
|
353
367
|
if (was === "running") invocation.stopped.resolve({ step: taken });
|
|
354
368
|
else if (was === "suspended") {
|
|
355
369
|
failTest(
|
|
356
|
-
|
|
370
|
+
taken.kind === "script-error"
|
|
371
|
+
? taken.message
|
|
372
|
+
: `agent "${turn.agent}" turn ${turn.n}: its script went on after its turn ended awaiting a reply; await only the host's calls`,
|
|
357
373
|
);
|
|
358
374
|
}
|
|
359
375
|
});
|
|
@@ -364,7 +380,7 @@ export function createScriptedHost(
|
|
|
364
380
|
const until = async (
|
|
365
381
|
invocation: Invocation,
|
|
366
382
|
context: FakeAdapterTurnContext,
|
|
367
|
-
record:
|
|
383
|
+
record: TestTurn,
|
|
368
384
|
): Promise<FakeAdapterTurn> => {
|
|
369
385
|
const { operation } = invocation;
|
|
370
386
|
const stopped = await Promise.race([invocation.stopped.promise, whenAborted(context.signal)]);
|
|
@@ -400,7 +416,7 @@ export function createScriptedHost(
|
|
|
400
416
|
const perform = (
|
|
401
417
|
step: Step,
|
|
402
418
|
context: FakeAdapterTurnContext,
|
|
403
|
-
record:
|
|
419
|
+
record: TestTurn,
|
|
404
420
|
invocation?: Invocation,
|
|
405
421
|
): FakeAdapterTurn => {
|
|
406
422
|
if (step.kind === "script-error") {
|
|
@@ -411,12 +427,12 @@ export function createScriptedHost(
|
|
|
411
427
|
return {
|
|
412
428
|
act: async () => {
|
|
413
429
|
// Submitted is answered, even though the engine then releases the turn.
|
|
414
|
-
let outcome:
|
|
430
|
+
let outcome: TestTurnOutcome = "cancelled";
|
|
415
431
|
try {
|
|
416
432
|
// As an agent's `wfa send` returns before it can run `wfa result`.
|
|
417
433
|
if (invocation) await Promise.allSettled([...invocation.calls]);
|
|
418
434
|
const { endpoint, operationId } = context.binding!;
|
|
419
|
-
const response = await
|
|
435
|
+
const response = await submitControl(endpoint, {
|
|
420
436
|
version: WIRE_VERSION,
|
|
421
437
|
command: "result",
|
|
422
438
|
operationId,
|
|
@@ -447,7 +463,7 @@ export function createScriptedHost(
|
|
|
447
463
|
return {
|
|
448
464
|
act: async () => {
|
|
449
465
|
const { endpoint, operationId } = context.binding!;
|
|
450
|
-
const response = await
|
|
466
|
+
const response = await submitControl(endpoint, {
|
|
451
467
|
version: WIRE_VERSION,
|
|
452
468
|
command: "waiting",
|
|
453
469
|
operationId,
|
|
@@ -465,7 +481,7 @@ export function createScriptedHost(
|
|
|
465
481
|
return {
|
|
466
482
|
act: async (turn) => {
|
|
467
483
|
await whenAborted(turn.signal);
|
|
468
|
-
end(record, "
|
|
484
|
+
end(record, "cancelled");
|
|
469
485
|
},
|
|
470
486
|
};
|
|
471
487
|
}
|
|
@@ -476,10 +492,10 @@ export function createScriptedHost(
|
|
|
476
492
|
`agent "${record.agent}" turn ${record.n}: only the calling session can be interrupted by the operator`,
|
|
477
493
|
);
|
|
478
494
|
}
|
|
479
|
-
end(record, "
|
|
495
|
+
end(record, "cancelled");
|
|
480
496
|
return { state: "cancelled", detail: "interrupted by the operator" };
|
|
481
497
|
}
|
|
482
|
-
end(record, ending.kind);
|
|
498
|
+
end(record, outcomeOf(ending.kind));
|
|
483
499
|
return ended(ending, context.activation.execution.harness);
|
|
484
500
|
};
|
|
485
501
|
|
|
@@ -492,10 +508,11 @@ export function createScriptedHost(
|
|
|
492
508
|
n,
|
|
493
509
|
id: context.id,
|
|
494
510
|
focus: context.prompt,
|
|
495
|
-
|
|
511
|
+
// Until it ends: one the run leaves running is cancelled with it.
|
|
512
|
+
outcome: "cancelled",
|
|
496
513
|
};
|
|
497
514
|
compactions.push(record);
|
|
498
|
-
const done = (outcome:
|
|
515
|
+
const done = (outcome: TestTurnOutcome) => {
|
|
499
516
|
record.outcome = outcome;
|
|
500
517
|
events.onActivity();
|
|
501
518
|
};
|
|
@@ -546,7 +563,7 @@ export function createScriptedHost(
|
|
|
546
563
|
return {
|
|
547
564
|
act: async (turn) => {
|
|
548
565
|
await whenAborted(turn.signal);
|
|
549
|
-
done("
|
|
566
|
+
done("cancelled");
|
|
550
567
|
},
|
|
551
568
|
};
|
|
552
569
|
}
|
|
@@ -555,7 +572,7 @@ export function createScriptedHost(
|
|
|
555
572
|
`agent "${activation.key}" compaction ${n}: a compaction is not interrupted by the operator`,
|
|
556
573
|
);
|
|
557
574
|
}
|
|
558
|
-
done(ending.kind);
|
|
575
|
+
done(outcomeOf(ending.kind));
|
|
559
576
|
return ended(ending, activation.execution.harness);
|
|
560
577
|
};
|
|
561
578
|
|
|
@@ -591,7 +608,7 @@ export function createScriptedHost(
|
|
|
591
608
|
]
|
|
592
609
|
: [],
|
|
593
610
|
);
|
|
594
|
-
const record:
|
|
611
|
+
const record: TestTurn = {
|
|
595
612
|
agent: activation.key,
|
|
596
613
|
n,
|
|
597
614
|
// A continuation that delivers nothing is a reminder of a reply owed: a nudge.
|
|
@@ -605,9 +622,11 @@ export function createScriptedHost(
|
|
|
605
622
|
...(context.settings.effort === undefined ? {} : { effort: context.settings.effort }),
|
|
606
623
|
...(authored.cause === undefined ? {} : { cause: authored.cause }),
|
|
607
624
|
...(shown.length === 0 ? {} : { messages: shown }),
|
|
608
|
-
|
|
625
|
+
// Until it ends: one the run leaves running is cancelled with it.
|
|
626
|
+
outcome: "cancelled",
|
|
609
627
|
};
|
|
610
628
|
turns.push(record);
|
|
629
|
+
operationOf.set(record, binding.operationId);
|
|
611
630
|
open.add(record);
|
|
612
631
|
if (!record.nudge && context.kind === "turn") await events.onTurnStarted?.(record);
|
|
613
632
|
|
|
@@ -657,7 +676,7 @@ export function createScriptedHost(
|
|
|
657
676
|
createFakeAdapter({
|
|
658
677
|
harnesses: PLACEMENT_HARNESSES[placement],
|
|
659
678
|
supportsWaiting: (activation) =>
|
|
660
|
-
placement === "
|
|
679
|
+
placement === "tui" &&
|
|
661
680
|
activation.execution.harness === "claude" &&
|
|
662
681
|
!activation.execution.caller,
|
|
663
682
|
placement,
|
|
@@ -690,7 +709,7 @@ export function createScriptedHost(
|
|
|
690
709
|
}),
|
|
691
710
|
);
|
|
692
711
|
const placed = createPlacementHostFactory({
|
|
693
|
-
|
|
712
|
+
tui: side("tui"),
|
|
694
713
|
headless: side("headless"),
|
|
695
714
|
...(caller && caller.here !== false
|
|
696
715
|
? {
|
|
@@ -701,7 +720,7 @@ export function createScriptedHost(
|
|
|
701
720
|
createSingleSessionHostFactory(
|
|
702
721
|
createFakeAdapter({
|
|
703
722
|
harnesses: [caller.harness],
|
|
704
|
-
placement: "
|
|
723
|
+
placement: "tui",
|
|
705
724
|
script: (context) => {
|
|
706
725
|
callerKey = context.activation.key;
|
|
707
726
|
return script(context);
|
|
@@ -731,14 +750,13 @@ export function createScriptedHost(
|
|
|
731
750
|
key: request.key,
|
|
732
751
|
execution: request.execution,
|
|
733
752
|
...(request.instructions ? { instructions: request.instructions } : {}),
|
|
734
|
-
...(request.labels ? { labels: request.labels } : {}),
|
|
735
753
|
...(request.skills ? { skills: request.skills.names } : {}),
|
|
736
754
|
...(request.continues && forks.has(request.continues.sessionRef)
|
|
737
755
|
? { forkedFrom: forks.get(request.continues.sessionRef)! }
|
|
738
756
|
: {}),
|
|
739
|
-
...(request
|
|
740
|
-
...(request
|
|
741
|
-
...(request.
|
|
757
|
+
...paneOf(request),
|
|
758
|
+
...panesOf(request),
|
|
759
|
+
...(request.layoutFallback ? { paneFallback: request.layoutFallback } : {}),
|
|
742
760
|
};
|
|
743
761
|
agents.push(opened);
|
|
744
762
|
return {
|
|
@@ -758,6 +776,26 @@ export function createScriptedHost(
|
|
|
758
776
|
sets,
|
|
759
777
|
agents,
|
|
760
778
|
inFlight: () => [...open],
|
|
779
|
+
settle(recorded) {
|
|
780
|
+
const engine = new Map(
|
|
781
|
+
recorded.flatMap((line) =>
|
|
782
|
+
line.kind === "turn" ? [[line.operationId, line.outcome]] : [],
|
|
783
|
+
),
|
|
784
|
+
);
|
|
785
|
+
const last = new Map<string, TestTurn>();
|
|
786
|
+
for (const turn of turns) last.set(operationOf.get(turn)!, turn);
|
|
787
|
+
for (const [operation, turn] of last) turn.outcome = engine.get(operation) ?? turn.outcome;
|
|
788
|
+
// Matched by place: the harness's compact() carries no operation id, and an agent's compactions run in order,
|
|
789
|
+
// one `compact` line per compaction the host saw.
|
|
790
|
+
const compacted = recorded.filter((line) => line.kind === "compact");
|
|
791
|
+
const seen = new Map<string, number>();
|
|
792
|
+
for (const compaction of compactions) {
|
|
793
|
+
const n = seen.get(compaction.agent) ?? 0;
|
|
794
|
+
seen.set(compaction.agent, n + 1);
|
|
795
|
+
const line = compacted.filter((each) => each.agent === compaction.agent)[n];
|
|
796
|
+
if (line) compaction.outcome = line.outcome;
|
|
797
|
+
}
|
|
798
|
+
},
|
|
761
799
|
};
|
|
762
800
|
}
|
|
763
801
|
|
|
@@ -816,12 +854,42 @@ async function noTools(): Promise<ToolOutcome> {
|
|
|
816
854
|
throw new Error("a compaction calls no tools");
|
|
817
855
|
}
|
|
818
856
|
|
|
857
|
+
/** The workflow's part of what the host was asked for a pane, as `pane` writes it. */
|
|
858
|
+
function paneOf({ layout, front }: HarnessActivation): { pane?: PaneSpec } {
|
|
859
|
+
if (layout?.beside !== undefined) {
|
|
860
|
+
const { beside, side, share } = layout;
|
|
861
|
+
return {
|
|
862
|
+
pane: {
|
|
863
|
+
beside,
|
|
864
|
+
side,
|
|
865
|
+
...(share === undefined ? {} : { share }),
|
|
866
|
+
...(front ? { front } : {}),
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const tab = layout?.tab;
|
|
871
|
+
if (tab === undefined && !front) return {};
|
|
872
|
+
return { pane: { ...(tab === undefined ? {} : { tab }), ...(front ? { front } : {}) } };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** The run's `panes` as the host was asked to apply them to this pane. */
|
|
876
|
+
function panesOf({ layout, keepPane }: HarnessActivation): Pick<OpenedAgent, "panes"> {
|
|
877
|
+
const workspace = layout?.beside === undefined ? layout?.workspace : undefined;
|
|
878
|
+
if (workspace === undefined && keepPane === undefined) return {};
|
|
879
|
+
return {
|
|
880
|
+
panes: {
|
|
881
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
882
|
+
...(keepPane === undefined ? {} : { keep: keepPane }),
|
|
883
|
+
},
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
819
887
|
/** What the pane host refuses before it opens a pane. */
|
|
820
888
|
function paneRefusal(activation: HarnessActivation): string | undefined {
|
|
821
889
|
const { harness } = activation.execution;
|
|
822
890
|
const spec = findHarness(harness);
|
|
823
891
|
if (activation.continues && spec && !spec.interactiveResume) {
|
|
824
|
-
return `${harness}
|
|
892
|
+
return `${harness} tui agents cannot continue a forked session: ${spec.absent.interactiveResume}`;
|
|
825
893
|
}
|
|
826
894
|
return undefined;
|
|
827
895
|
}
|
|
@@ -839,7 +907,7 @@ function refusedCompaction(activation: HarnessActivation, hasRun: boolean): stri
|
|
|
839
907
|
}
|
|
840
908
|
const spec = findHarness(activation.execution.harness);
|
|
841
909
|
const capability =
|
|
842
|
-
placementOf(activation.execution) === "
|
|
910
|
+
placementOf(activation.execution) === "tui" ? "compactPane" : "compactHeadless";
|
|
843
911
|
if (spec && !spec[capability]) {
|
|
844
912
|
return `${activation.execution.harness} has no compaction of its own: ${spec.absent[capability]}`;
|
|
845
913
|
}
|
|
@@ -848,7 +916,7 @@ function refusedCompaction(activation: HarnessActivation, hasRun: boolean): stri
|
|
|
848
916
|
}
|
|
849
917
|
|
|
850
918
|
type Ended = Exclude<
|
|
851
|
-
Extract<Step, { kind: "
|
|
919
|
+
Extract<Step, { kind: "no-answer" }>["ending"],
|
|
852
920
|
{ kind: "waiting" | "hang" | "interrupted" }
|
|
853
921
|
>;
|
|
854
922
|
|
|
@@ -861,7 +929,7 @@ function ended(ending: Ended, harness: string): FakeAdapterTurn {
|
|
|
861
929
|
return check
|
|
862
930
|
? {
|
|
863
931
|
state: "failed",
|
|
864
|
-
...loginFailure(harness, check, { ...provider, said: "
|
|
932
|
+
...loginFailure(harness, check, { ...provider, said: "noAnswer.needsLogin()" }),
|
|
865
933
|
}
|
|
866
934
|
: {
|
|
867
935
|
state: "failed",
|