@agentswf/engine 0.0.0-stage → 0.1.0-beta.1
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/init-command.ts +240 -0
- package/src/init-template/AGENTS.md.template +16 -0
- package/src/init-template/hello/workflow.test.ts.template +17 -0
- package/src/init-template/hello/workflow.ts.template +26 -0
- package/src/init-template/tsconfig.json.template +11 -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 +600 -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 +242 -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 +521 -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 +3666 -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,356 @@
|
|
|
1
|
+
import type { RunJson, RunsJson } from "@agentswf/contract/records";
|
|
2
|
+
import { messageOf } from "./errors";
|
|
3
|
+
import {
|
|
4
|
+
clockTime,
|
|
5
|
+
command,
|
|
6
|
+
continueOf,
|
|
7
|
+
estimateOf,
|
|
8
|
+
failed,
|
|
9
|
+
masked,
|
|
10
|
+
money,
|
|
11
|
+
nextLogs,
|
|
12
|
+
type Out,
|
|
13
|
+
parsePlace,
|
|
14
|
+
type ReadPlace,
|
|
15
|
+
said,
|
|
16
|
+
shellLine,
|
|
17
|
+
span,
|
|
18
|
+
stateWord,
|
|
19
|
+
} from "./run-commands";
|
|
20
|
+
import { encodeCursor } from "./run-read";
|
|
21
|
+
import { findRun, listRuns, type RunStatus, runStatus } from "./run-state";
|
|
22
|
+
import { type AgentView, describeActivity, duration, type RunView, viewRun } from "./run-view";
|
|
23
|
+
|
|
24
|
+
export const runsUsage = [
|
|
25
|
+
"usage: awf runs [--all] [--cwd <directory>] [--run-root <directory>] [--json]",
|
|
26
|
+
" awf runs <run> [--cwd <directory>] [--run-root <directory>] [--json]",
|
|
27
|
+
"",
|
|
28
|
+
"The runs here, newest first, or one run in depth: its attempts, the stages of the last, what",
|
|
29
|
+
"each agent is doing, and what to run next. A run is <workflow>/<id>, or an <id> only one",
|
|
30
|
+
"workflow here has. Runs are read under --cwd's .awf/runs, or --run-root, as awf run keeps them.",
|
|
31
|
+
"",
|
|
32
|
+
"--json prints one document, the interface for scripts. Exits 0 on any read that worked, 1 when",
|
|
33
|
+
"there is no such run, 2 on a usage error, 3 when the run's files could not be read.",
|
|
34
|
+
].join("\n");
|
|
35
|
+
|
|
36
|
+
const LISTED = 20;
|
|
37
|
+
|
|
38
|
+
export async function runsCommand(
|
|
39
|
+
argv: readonly string[],
|
|
40
|
+
shellCwd: string,
|
|
41
|
+
out: Out,
|
|
42
|
+
now: number = Date.now(),
|
|
43
|
+
): Promise<number> {
|
|
44
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
45
|
+
out.stdout(runsUsage);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
let parsed: ReturnType<typeof parsePlace>;
|
|
49
|
+
let all = false;
|
|
50
|
+
let ref: string | undefined;
|
|
51
|
+
try {
|
|
52
|
+
parsed = parsePlace(argv, shellCwd);
|
|
53
|
+
for (const arg of parsed.rest) {
|
|
54
|
+
if (arg === "--all") all = true;
|
|
55
|
+
else if (arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
|
|
56
|
+
else if (ref === undefined) ref = arg;
|
|
57
|
+
else throw new Error(`one run at a time: ${ref} and ${arg}`);
|
|
58
|
+
}
|
|
59
|
+
if (ref !== undefined && all) throw new Error("--all lists runs; it takes no run");
|
|
60
|
+
} catch (error) {
|
|
61
|
+
out.stderr(`awf: ${messageOf(error)}\n\n${runsUsage}`);
|
|
62
|
+
return 2;
|
|
63
|
+
}
|
|
64
|
+
const { place, json } = parsed;
|
|
65
|
+
try {
|
|
66
|
+
if (ref === undefined) return await list(place, all, json, out, now);
|
|
67
|
+
const view = await viewRun((await findRun(place.runRoot, ref)).status);
|
|
68
|
+
out.stdout(
|
|
69
|
+
json ? JSON.stringify(runJson(view, place, now), null, 2) : describe(view, place, now),
|
|
70
|
+
);
|
|
71
|
+
return 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return failed(error, place, out, messageOf(error));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A run listed, or why it could not be read: one bad run does not hide the others. */
|
|
78
|
+
type Listed = { status: RunStatus } | { ref: string; unreadable: string };
|
|
79
|
+
|
|
80
|
+
async function list(place: ReadPlace, all: boolean, json: boolean, out: Out, now: number) {
|
|
81
|
+
const runs = await listRuns(place.runRoot);
|
|
82
|
+
const shown = all ? runs : runs.slice(0, LISTED);
|
|
83
|
+
const listed: Listed[] = await Promise.all(
|
|
84
|
+
shown.map((run) =>
|
|
85
|
+
runStatus(run, undefined, now).then(
|
|
86
|
+
(status) => ({ status }),
|
|
87
|
+
(error: unknown) => ({
|
|
88
|
+
ref: `${run.record.workflow}/${run.record.id}`,
|
|
89
|
+
unreadable: said(messageOf(error), 400),
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
const older = runs.length - shown.length;
|
|
95
|
+
if (json) {
|
|
96
|
+
const runsJson: RunsJson["runs"] = listed.map((each) => {
|
|
97
|
+
if (!("status" in each)) return each;
|
|
98
|
+
const { status } = each;
|
|
99
|
+
const { started, ended } = span(status, now);
|
|
100
|
+
const estimate = estimateOf(status.attempts);
|
|
101
|
+
return {
|
|
102
|
+
ref: status.ref,
|
|
103
|
+
state: status.state,
|
|
104
|
+
checked: status.checked,
|
|
105
|
+
started: new Date(started).toISOString(),
|
|
106
|
+
...(ended === undefined ? {} : { duration: ended - started }),
|
|
107
|
+
...(estimate === undefined ? {} : { estimate }),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const document: RunsJson = { v: 1, runs: runsJson, more: { older: older > 0 } };
|
|
111
|
+
out.stdout(JSON.stringify(document, null, 2));
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
if (listed.length === 0) {
|
|
115
|
+
out.stdout(`no runs under ${place.runRoot}; started elsewhere? awf runs --cwd {dir}`);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
const refOf = (each: Listed) => ("status" in each ? each.status.ref : each.ref);
|
|
119
|
+
const width = Math.max(...listed.map((each) => refOf(each).length)) + 2;
|
|
120
|
+
const states = listed.map((each) => ("status" in each ? stateWord(each.status) : ""));
|
|
121
|
+
const stateWidth = Math.max(13, ...states.map((each) => each.length + 2));
|
|
122
|
+
const lines = listed.map((each, index) => {
|
|
123
|
+
if (!("status" in each)) return `${each.ref.padEnd(width)}unreadable: ${each.unreadable}`;
|
|
124
|
+
const { status } = each;
|
|
125
|
+
const { started, ms } = span(status, now);
|
|
126
|
+
const estimate = estimateOf(status.attempts);
|
|
127
|
+
return [
|
|
128
|
+
status.ref.padEnd(width),
|
|
129
|
+
states[index]!.padEnd(stateWidth),
|
|
130
|
+
dayTime(started).padEnd(13),
|
|
131
|
+
duration(ms).padEnd(8),
|
|
132
|
+
money(estimate).join(""),
|
|
133
|
+
]
|
|
134
|
+
.join("")
|
|
135
|
+
.trimEnd();
|
|
136
|
+
});
|
|
137
|
+
if (older > 0) lines.push(`… ${older} older (--all)`);
|
|
138
|
+
out.stdout(lines.join("\n"));
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function dayTime(at: number): string {
|
|
143
|
+
const date = new Date(at);
|
|
144
|
+
const two = (n: number) => String(n).padStart(2, "0");
|
|
145
|
+
return `${two(date.getMonth() + 1)}-${two(date.getDate())} ${clockTime(at).slice(0, 5)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const busy = (agent: AgentView) => agent.state === "working" || agent.state === "was working";
|
|
149
|
+
|
|
150
|
+
function agentLine(agent: AgentView, indent: string, now: number, width: number): string {
|
|
151
|
+
const { live } = agent;
|
|
152
|
+
const parts = [
|
|
153
|
+
said(agent.key, 60).padEnd(width),
|
|
154
|
+
(live?.phase === "waiting" ? "waiting" : live ? "awaiting" : agent.state).padEnd(11),
|
|
155
|
+
busy(agent) ? `turn ${agent.turns + 1}` : `${agent.turns} turn${agent.turns === 1 ? "" : "s"}`,
|
|
156
|
+
...(live?.phase === "waiting"
|
|
157
|
+
? [
|
|
158
|
+
[
|
|
159
|
+
live.reason ? `"${said(live.reason)}"` : "",
|
|
160
|
+
live.until ? `(check-in due ${clockTime(live.until).slice(0, 5)})` : "",
|
|
161
|
+
]
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.join(" "),
|
|
164
|
+
]
|
|
165
|
+
: live
|
|
166
|
+
? [
|
|
167
|
+
`${said(live.peer, 60)}'s reply${live.until ? ` (until ${clockTime(live.until).slice(0, 5)})` : ""}`,
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
...(agent.since && agent.state === "working" ? [duration(now - agent.since)] : []),
|
|
171
|
+
...(agent.last
|
|
172
|
+
? [`last: ${describeActivity(agent.last.activity)} (${clockTime(agent.last.at)})`]
|
|
173
|
+
: []),
|
|
174
|
+
];
|
|
175
|
+
return `${indent} ${parts.filter(Boolean).join(" ")}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Past this many, agents numbered alike, `fix:lens-1` to `fix:lens-8`, take one line. */
|
|
179
|
+
const GROUPED = 4;
|
|
180
|
+
|
|
181
|
+
function agentLines(agents: readonly AgentView[], indent: string, now: number): string[] {
|
|
182
|
+
const prefix = (agent: AgentView) => (/\d$/.test(agent.key) ? agent.key.replace(/\d+$/, "") : "");
|
|
183
|
+
const width = Math.max(16, ...agents.map((agent) => said(agent.key, 60).length + 1));
|
|
184
|
+
const groups = new Map<string, AgentView[]>();
|
|
185
|
+
for (const agent of agents) {
|
|
186
|
+
if (prefix(agent)) groups.set(prefix(agent), [...(groups.get(prefix(agent)) ?? []), agent]);
|
|
187
|
+
}
|
|
188
|
+
const lines: string[] = [];
|
|
189
|
+
const done = new Set<string>();
|
|
190
|
+
for (const agent of agents) {
|
|
191
|
+
const group = groups.get(prefix(agent)) ?? [agent];
|
|
192
|
+
if (group.length <= GROUPED) {
|
|
193
|
+
lines.push(agentLine(agent, indent, now, width));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (done.has(prefix(agent))) continue;
|
|
197
|
+
done.add(prefix(agent));
|
|
198
|
+
const states = new Map<string, number>();
|
|
199
|
+
for (const each of group) states.set(each.state, (states.get(each.state) ?? 0) + 1);
|
|
200
|
+
const counts = [...states].map(([state, n]) => `${n} ${state}`).join(", ");
|
|
201
|
+
lines.push(
|
|
202
|
+
`${indent} ${said(group[0]!.key, 60)} … ${said(group.at(-1)!.key, 60)} ${group.length} agents: ${counts}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return lines;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function describe(view: RunView, place: ReadPlace, now: number): string {
|
|
209
|
+
const { status, attempt } = view;
|
|
210
|
+
const lines: string[] = [
|
|
211
|
+
[
|
|
212
|
+
status.ref,
|
|
213
|
+
stateWord(status, view.lastEventAt),
|
|
214
|
+
...(attempt && status.attempts.length > 1
|
|
215
|
+
? [
|
|
216
|
+
`attempt ${attempt.attempt}${attempt.flags.fromStage ? ` (from stage ${attempt.flags.fromStage})` : ""}`,
|
|
217
|
+
]
|
|
218
|
+
: []),
|
|
219
|
+
...(view.stage ? [`stage ${view.stage}`] : []),
|
|
220
|
+
status.state === "interrupted" && view.lastEventAt
|
|
221
|
+
? `last event ${clockTime(view.lastEventAt)}`
|
|
222
|
+
: duration(span(status, now).ms),
|
|
223
|
+
...money(view.estimate),
|
|
224
|
+
].join(" "),
|
|
225
|
+
];
|
|
226
|
+
for (const earlier of status.attempts.slice(0, -1)) {
|
|
227
|
+
const took = earlier.ended
|
|
228
|
+
? ` ${duration(Date.parse(earlier.ended) - Date.parse(earlier.started))}`
|
|
229
|
+
: "";
|
|
230
|
+
const how = `${earlier.outcome ?? "interrupted"}${earlier.stage ? ` in ${earlier.stage}` : ""}`;
|
|
231
|
+
lines.push(` attempt ${earlier.attempt} ${how}${took}`);
|
|
232
|
+
}
|
|
233
|
+
const indent = status.attempts.length > 1 ? " " : "";
|
|
234
|
+
if (indent && attempt) lines.push(` attempt ${attempt.attempt}`);
|
|
235
|
+
const shown = new Set<string>();
|
|
236
|
+
for (const stage of view.stages) {
|
|
237
|
+
const took =
|
|
238
|
+
stage.state === "reused" || stage.state === "provided" || stage.startedAt === undefined
|
|
239
|
+
? ""
|
|
240
|
+
: duration((stage.endedAt ?? now) - stage.startedAt);
|
|
241
|
+
lines.push(
|
|
242
|
+
`${indent} stage ${stage.stage.padEnd(8)} ${stage.state.padEnd(10)} ${took}`.trimEnd(),
|
|
243
|
+
);
|
|
244
|
+
const own = view.agents.filter((each) => each.stage === stage.stage);
|
|
245
|
+
for (const agent of own) shown.add(agent.key);
|
|
246
|
+
lines.push(...agentLines(own, indent, now));
|
|
247
|
+
}
|
|
248
|
+
lines.push(
|
|
249
|
+
...agentLines(
|
|
250
|
+
view.agents.filter((each) => !shown.has(each.key)),
|
|
251
|
+
indent,
|
|
252
|
+
now,
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
for (const decision of view.decisions) {
|
|
256
|
+
lines.push(
|
|
257
|
+
`${indent} decision "${said(decision.key)}" asked ${clockTime(decision.askedAt).slice(0, 5)}, ${decision.outcome ?? "open"}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
lines.push(...trailer(view, place));
|
|
261
|
+
return lines.join("\n");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function trailer(view: RunView, place: ReadPlace): string[] {
|
|
265
|
+
const { status } = view;
|
|
266
|
+
if (status.state === "running") {
|
|
267
|
+
return [`next: ${shellLine(nextLogs(status, place, view.cursor))}`];
|
|
268
|
+
}
|
|
269
|
+
const go = status.state === "completed" ? undefined : continueOf(status, view.lines, place);
|
|
270
|
+
if (status.state === "interrupted") return go ? [`continue: ${go}`] : [];
|
|
271
|
+
const lines: string[] = [];
|
|
272
|
+
if (view.attempt?.reason) lines.push(`reason: ${said(view.attempt.reason, 400)}`);
|
|
273
|
+
lines.push(`records: ${status.run.dir} (report.md, output.json)`);
|
|
274
|
+
if (status.state === "failed") {
|
|
275
|
+
const stuck = view.agents.find((each) => each.state === "failed" || each.state === "timed-out");
|
|
276
|
+
const look = command(place, "awf", "logs", status.ref);
|
|
277
|
+
lines.push(
|
|
278
|
+
// A key a shell line cannot carry on one line is left out.
|
|
279
|
+
`see: ${shellLine([...look, ...(stuck && !/[\r\n]/.test(stuck.key) ? ["--agent", stuck.key] : []), "--limit", "30"])}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (go) lines.push(`continue: ${go}`);
|
|
283
|
+
return lines;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** `awf runs {ref} --json`: durable fields at the top, best-effort ones under `live`. */
|
|
287
|
+
function runJson(view: RunView, place: ReadPlace, now: number): RunJson {
|
|
288
|
+
const { status } = view;
|
|
289
|
+
const { ended, started } = span(status, now);
|
|
290
|
+
const go =
|
|
291
|
+
status.state === "completed" || status.state === "running"
|
|
292
|
+
? undefined
|
|
293
|
+
: continueOf(status, view.lines, place);
|
|
294
|
+
return {
|
|
295
|
+
v: 1,
|
|
296
|
+
run: {
|
|
297
|
+
ref: status.ref,
|
|
298
|
+
state: status.state,
|
|
299
|
+
checked: status.checked,
|
|
300
|
+
...(view.stage ? { stage: view.stage } : {}),
|
|
301
|
+
...(status.state !== "running" && ended !== undefined ? { duration: ended - started } : {}),
|
|
302
|
+
...(view.estimate === undefined ? {} : { estimate: view.estimate }),
|
|
303
|
+
dir: status.run.dir,
|
|
304
|
+
},
|
|
305
|
+
attempts: status.attempts.map(({ attempt, started, ended, outcome, stage, reason }) => ({
|
|
306
|
+
attempt,
|
|
307
|
+
started,
|
|
308
|
+
...(ended ? { ended } : {}),
|
|
309
|
+
state:
|
|
310
|
+
outcome ?? (attempt === status.attempts.at(-1)?.attempt ? status.state : "interrupted"),
|
|
311
|
+
...(stage ? { stage } : {}),
|
|
312
|
+
...(reason ? { reason: masked(reason, 1_000) as string } : {}),
|
|
313
|
+
})),
|
|
314
|
+
stages: view.stages.map(({ stage, state, startedAt, endedAt }) => ({
|
|
315
|
+
stage,
|
|
316
|
+
state,
|
|
317
|
+
...(startedAt ? { started: new Date(startedAt).toISOString() } : {}),
|
|
318
|
+
...(endedAt ? { ended: new Date(endedAt).toISOString() } : {}),
|
|
319
|
+
})),
|
|
320
|
+
agents: view.agents.map((agent) => ({
|
|
321
|
+
key: agent.key,
|
|
322
|
+
...(agent.harness ? { harness: agent.harness } : {}),
|
|
323
|
+
...(agent.caller ? { caller: true } : {}),
|
|
324
|
+
...(agent.stage ? { stage: agent.stage } : {}),
|
|
325
|
+
state: agent.state,
|
|
326
|
+
turns: agent.turns,
|
|
327
|
+
sessions: agent.sessions,
|
|
328
|
+
live: {
|
|
329
|
+
...(agent.live
|
|
330
|
+
? {
|
|
331
|
+
phase: agent.live.phase,
|
|
332
|
+
...(agent.live.phase === "awaiting-reply"
|
|
333
|
+
? { peer: agent.live.peer }
|
|
334
|
+
: agent.live.reason === undefined
|
|
335
|
+
? {}
|
|
336
|
+
: { reason: masked(agent.live.reason, 1_000) as string }),
|
|
337
|
+
...(agent.live.until === undefined
|
|
338
|
+
? {}
|
|
339
|
+
: { until: new Date(agent.live.until).toISOString() }),
|
|
340
|
+
}
|
|
341
|
+
: {}),
|
|
342
|
+
...(agent.last ? { lastActivity: agent.last } : {}),
|
|
343
|
+
observedAt: new Date(now).toISOString(),
|
|
344
|
+
},
|
|
345
|
+
})),
|
|
346
|
+
decisions: view.decisions.map(({ decision, key, askedAt, outcome }) => ({
|
|
347
|
+
decision,
|
|
348
|
+
key,
|
|
349
|
+
asked: new Date(askedAt).toISOString(),
|
|
350
|
+
...(outcome ? { outcome } : {}),
|
|
351
|
+
})),
|
|
352
|
+
cursor: encodeCursor(view.cursor),
|
|
353
|
+
...(status.state === "running" ? { next: nextLogs(status, place, view.cursor) } : {}),
|
|
354
|
+
...(go ? { continue: go } : {}),
|
|
355
|
+
};
|
|
356
|
+
}
|