@sagentlab/navarch-runtime 0.1.88 → 0.1.89
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/README.md +5 -1
- package/dist/adapters/acp.cjs +2 -0
- package/dist/adapters/claude.cjs +8 -2
- package/dist/adapters/codex.cjs +8 -2
- package/dist/adapters/gemini.cjs +9 -4
- package/dist/adapters/opencode.cjs +8 -4
- package/dist/adapters/pi.cjs +8 -2
- package/dist/adapters/qoder.cjs +8 -2
- package/dist/live-transcript.cjs +143 -0
- package/dist/session.cjs +31 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,11 @@ machine.
|
|
|
72
72
|
During a connected run, the runtime reports preparation, agent start, output
|
|
73
73
|
activity, and completion checks to the control plane. Project members can see
|
|
74
74
|
these phase updates in the task view. The activity signal contains no prompt
|
|
75
|
-
or CLI output
|
|
75
|
+
or CLI output. The redacted transcript is uploaded to the session's private
|
|
76
|
+
transcript object every 10 seconds while the agent produces output (backing
|
|
77
|
+
off to 30–60 seconds for multi-megabyte transcripts), and once more in full
|
|
78
|
+
when the run finishes, so the task view can follow a connected run live.
|
|
79
|
+
Live snapshots hold the current turn's stderr back until the turn ends.
|
|
76
80
|
|
|
77
81
|
The machine operator selects **Claude Code**, **OpenAI Codex**, **Google
|
|
78
82
|
Gemini CLI**, **OpenCode**, or an **Agent Client Protocol (ACP)** agent when connecting a worker. Any selected agent can
|
package/dist/adapters/acp.cjs
CHANGED
|
@@ -51,6 +51,7 @@ async function runAcpAdapter(options) {
|
|
|
51
51
|
child.stdout.on("data", (chunk) => {
|
|
52
52
|
options.onActivity?.();
|
|
53
53
|
rawStdout.push(chunk);
|
|
54
|
+
options.onOutput?.("stdout", chunk.toString("utf8"));
|
|
54
55
|
protocolInput.write(chunk);
|
|
55
56
|
});
|
|
56
57
|
child.stdout.on("end", () => protocolInput.end());
|
|
@@ -58,6 +59,7 @@ async function runAcpAdapter(options) {
|
|
|
58
59
|
child.stderr.on("data", (chunk) => {
|
|
59
60
|
options.onActivity?.();
|
|
60
61
|
stderr.push(chunk);
|
|
62
|
+
options.onOutput?.("stderr", chunk);
|
|
61
63
|
});
|
|
62
64
|
child.on("error", (error) => {
|
|
63
65
|
stderr.push(`\n${describeError(error)}`);
|
package/dist/adapters/claude.cjs
CHANGED
|
@@ -205,11 +205,15 @@ async function runOnHost(options, args) {
|
|
|
205
205
|
onAbort();
|
|
206
206
|
child.stdout.on("data", (d) => {
|
|
207
207
|
options.onActivity?.();
|
|
208
|
-
|
|
208
|
+
const text = d.toString();
|
|
209
|
+
stdout += text;
|
|
210
|
+
options.onOutput?.("stdout", text);
|
|
209
211
|
});
|
|
210
212
|
child.stderr.on("data", (d) => {
|
|
211
213
|
options.onActivity?.();
|
|
212
|
-
|
|
214
|
+
const text = d.toString();
|
|
215
|
+
stderr += text;
|
|
216
|
+
options.onOutput?.("stderr", text);
|
|
213
217
|
});
|
|
214
218
|
child.on("error", (err) => {
|
|
215
219
|
clearTimeout(timer);
|
|
@@ -245,6 +249,8 @@ async function runViaDocker(options, args) {
|
|
|
245
249
|
input: options.prompt,
|
|
246
250
|
timeoutMs: options.timeoutMs,
|
|
247
251
|
onActivity: options.onActivity,
|
|
252
|
+
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
253
|
+
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
248
254
|
signal: options.signal,
|
|
249
255
|
});
|
|
250
256
|
return {
|
package/dist/adapters/codex.cjs
CHANGED
|
@@ -195,11 +195,15 @@ async function runOnHost(options, args) {
|
|
|
195
195
|
onAbort();
|
|
196
196
|
child.stdout.on("data", (d) => {
|
|
197
197
|
options.onActivity?.();
|
|
198
|
-
|
|
198
|
+
const text = d.toString();
|
|
199
|
+
stdout += text;
|
|
200
|
+
options.onOutput?.("stdout", text);
|
|
199
201
|
});
|
|
200
202
|
child.stderr.on("data", (d) => {
|
|
201
203
|
options.onActivity?.();
|
|
202
|
-
|
|
204
|
+
const text = d.toString();
|
|
205
|
+
stderr += text;
|
|
206
|
+
options.onOutput?.("stderr", text);
|
|
203
207
|
});
|
|
204
208
|
child.on("error", (err) => {
|
|
205
209
|
clearTimeout(timer);
|
|
@@ -237,6 +241,8 @@ async function runViaDocker(options, args) {
|
|
|
237
241
|
input: options.prompt,
|
|
238
242
|
timeoutMs: options.timeoutMs,
|
|
239
243
|
onActivity: options.onActivity,
|
|
244
|
+
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
245
|
+
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
240
246
|
signal: options.signal,
|
|
241
247
|
env: { ...process.env, ...options.env },
|
|
242
248
|
});
|
package/dist/adapters/gemini.cjs
CHANGED
|
@@ -217,11 +217,15 @@ async function runOnHost(options, args) {
|
|
|
217
217
|
onAbort();
|
|
218
218
|
child.stdout.on("data", (data) => {
|
|
219
219
|
options.onActivity?.();
|
|
220
|
-
|
|
220
|
+
const text = data.toString();
|
|
221
|
+
stdout += text;
|
|
222
|
+
options.onOutput?.("stdout", text);
|
|
221
223
|
});
|
|
222
224
|
child.stderr.on("data", (data) => {
|
|
223
225
|
options.onActivity?.();
|
|
224
|
-
|
|
226
|
+
const text = data.toString();
|
|
227
|
+
stderr += text;
|
|
228
|
+
options.onOutput?.("stderr", text);
|
|
225
229
|
});
|
|
226
230
|
child.on("error", (err) => {
|
|
227
231
|
clearTimeout(timer);
|
|
@@ -255,9 +259,10 @@ async function runViaDocker(options, args) {
|
|
|
255
259
|
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], { input: options.prompt,
|
|
256
260
|
timeoutMs: options.timeoutMs,
|
|
257
261
|
onActivity: options.onActivity,
|
|
262
|
+
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
263
|
+
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
258
264
|
signal: options.signal,
|
|
259
|
-
env: { ...process.env, ...options.env },
|
|
260
|
-
});
|
|
265
|
+
env: { ...process.env, ...options.env }, });
|
|
261
266
|
return {
|
|
262
267
|
exitCode: result.code,
|
|
263
268
|
timedOut: false,
|
|
@@ -356,12 +356,16 @@ async function runOnHost(options, args, input = "") {
|
|
|
356
356
|
onAbort();
|
|
357
357
|
child.stdout.on("data", (data) => {
|
|
358
358
|
options.onActivity?.();
|
|
359
|
-
|
|
359
|
+
const text = data.toString();
|
|
360
|
+
stdout += text;
|
|
361
|
+
options.onOutput?.("stdout", text);
|
|
360
362
|
monitor.push("stdout", data.toString());
|
|
361
363
|
});
|
|
362
364
|
child.stderr.on("data", (data) => {
|
|
363
365
|
options.onActivity?.();
|
|
364
|
-
|
|
366
|
+
const text = data.toString();
|
|
367
|
+
stderr += text;
|
|
368
|
+
options.onOutput?.("stderr", text);
|
|
365
369
|
monitor.push("stderr", data.toString());
|
|
366
370
|
});
|
|
367
371
|
child.on("error", (error) => {
|
|
@@ -408,8 +412,8 @@ async function runViaDocker(options, args, input) {
|
|
|
408
412
|
timeoutMs: options.timeoutMs,
|
|
409
413
|
onActivity: options.onActivity,
|
|
410
414
|
signal: controller.signal,
|
|
411
|
-
onStdout: (chunk) => { streamed.stdout = true; monitor.push("stdout", chunk); },
|
|
412
|
-
onStderr: (chunk) => { streamed.stderr = true; monitor.push("stderr", chunk); },
|
|
415
|
+
onStdout: (chunk) => { streamed.stdout = true; monitor.push("stdout", chunk); options.onOutput?.("stdout", chunk); },
|
|
416
|
+
onStderr: (chunk) => { streamed.stderr = true; monitor.push("stderr", chunk); options.onOutput?.("stderr", chunk); },
|
|
413
417
|
env: { ...process.env, ...options.env },
|
|
414
418
|
});
|
|
415
419
|
// Also inspect runners that only return buffered output.
|
package/dist/adapters/pi.cjs
CHANGED
|
@@ -344,11 +344,15 @@ async function runOnHost(options, args, local = false) {
|
|
|
344
344
|
onAbort();
|
|
345
345
|
child.stdout.on("data", (data) => {
|
|
346
346
|
options.onActivity?.();
|
|
347
|
-
|
|
347
|
+
const text = data.toString();
|
|
348
|
+
stdout += text;
|
|
349
|
+
options.onOutput?.("stdout", text);
|
|
348
350
|
});
|
|
349
351
|
child.stderr.on("data", (data) => {
|
|
350
352
|
options.onActivity?.();
|
|
351
|
-
|
|
353
|
+
const text = data.toString();
|
|
354
|
+
stderr += text;
|
|
355
|
+
options.onOutput?.("stderr", text);
|
|
352
356
|
});
|
|
353
357
|
child.on("error", (err) => {
|
|
354
358
|
clearTimeout(timer);
|
|
@@ -408,6 +412,8 @@ async function runViaDocker(options, args) {
|
|
|
408
412
|
input: options.prompt,
|
|
409
413
|
timeoutMs: options.timeoutMs,
|
|
410
414
|
onActivity: options.onActivity,
|
|
415
|
+
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
416
|
+
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
411
417
|
signal: options.signal,
|
|
412
418
|
env: { ...process.env, ...options.env },
|
|
413
419
|
});
|
package/dist/adapters/qoder.cjs
CHANGED
|
@@ -102,11 +102,15 @@ async function runOnHost(options, args) {
|
|
|
102
102
|
onAbort();
|
|
103
103
|
child.stdout.on("data", (d) => {
|
|
104
104
|
options.onActivity?.();
|
|
105
|
-
|
|
105
|
+
const text = d.toString();
|
|
106
|
+
stdout += text;
|
|
107
|
+
options.onOutput?.("stdout", text);
|
|
106
108
|
});
|
|
107
109
|
child.stderr.on("data", (d) => {
|
|
108
110
|
options.onActivity?.();
|
|
109
|
-
|
|
111
|
+
const text = d.toString();
|
|
112
|
+
stderr += text;
|
|
113
|
+
options.onOutput?.("stderr", text);
|
|
110
114
|
});
|
|
111
115
|
child.on("error", (err) => {
|
|
112
116
|
clearTimeout(timer);
|
|
@@ -139,6 +143,8 @@ async function runViaDocker(options, args) {
|
|
|
139
143
|
input: options.prompt,
|
|
140
144
|
timeoutMs: options.timeoutMs,
|
|
141
145
|
onActivity: options.onActivity,
|
|
146
|
+
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
147
|
+
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
142
148
|
signal: options.signal,
|
|
143
149
|
});
|
|
144
150
|
return {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LiveTranscriptPublisher = void 0;
|
|
4
|
+
exports.renderSessionTranscript = renderSessionTranscript;
|
|
5
|
+
exports.renderLiveTranscript = renderLiveTranscript;
|
|
6
|
+
exports.liveUploadIntervalMs = liveUploadIntervalMs;
|
|
7
|
+
/** The shape both the live snapshots and the completion upload use. */
|
|
8
|
+
function renderSessionTranscript(attempts, redact) {
|
|
9
|
+
return attempts
|
|
10
|
+
.flatMap((attempt, index) => [
|
|
11
|
+
`# agent turn ${index + 1} stdout`,
|
|
12
|
+
redact(attempt.stdout),
|
|
13
|
+
"",
|
|
14
|
+
`# agent turn ${index + 1} stderr`,
|
|
15
|
+
redact(attempt.stderr),
|
|
16
|
+
"",
|
|
17
|
+
])
|
|
18
|
+
.join("\n");
|
|
19
|
+
}
|
|
20
|
+
function renderLiveTranscript(attempts, currentStdout, redact, knownSecrets = []) {
|
|
21
|
+
const finished = attempts.length ? `${renderSessionTranscript(attempts, redact)}\n` : "";
|
|
22
|
+
const end = currentStdout.lastIndexOf("\n");
|
|
23
|
+
// A secret split across the cut would escape redaction, so redact whole lines only.
|
|
24
|
+
let complete = end >= 0 ? currentStdout.slice(0, end + 1) : "";
|
|
25
|
+
// Complete lines can still contain the beginning of a multiline secret.
|
|
26
|
+
// Hold that line and everything after it until redaction can see the value.
|
|
27
|
+
let safeEnd = complete.length;
|
|
28
|
+
for (const secret of knownSecrets) {
|
|
29
|
+
const newline = secret.indexOf("\n");
|
|
30
|
+
if (newline < 0)
|
|
31
|
+
continue;
|
|
32
|
+
const start = complete.lastIndexOf(secret.slice(0, newline + 1));
|
|
33
|
+
if (start >= 0 && complete.length - start < secret.length && secret.startsWith(complete.slice(start))) {
|
|
34
|
+
safeEnd = Math.min(safeEnd, complete.lastIndexOf("\n", start - 1) + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const pem = /-----BEGIN ([A-Z0-9 ]*PRIVATE KEY)-----/g;
|
|
38
|
+
for (const match of complete.matchAll(pem)) {
|
|
39
|
+
if (!complete.includes(`-----END ${match[1]}-----`, match.index + match[0].length)) {
|
|
40
|
+
safeEnd = Math.min(safeEnd, complete.lastIndexOf("\n", match.index - 1) + 1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
complete = complete.slice(0, safeEnd);
|
|
44
|
+
return `${finished}# agent turn ${attempts.length + 1} stdout\n${redact(complete)}`;
|
|
45
|
+
}
|
|
46
|
+
/** Signed upload URLs last an hour; refresh well before that. */
|
|
47
|
+
const UPLOAD_URL_TTL_MS = 45 * 60_000;
|
|
48
|
+
const DEFAULT_INTERVAL_MS = 10_000;
|
|
49
|
+
function liveUploadIntervalMs(bytes, baseMs = DEFAULT_INTERVAL_MS) {
|
|
50
|
+
if (bytes > 8 * 1024 * 1024)
|
|
51
|
+
return Math.max(baseMs, 60_000);
|
|
52
|
+
if (bytes > 1024 * 1024)
|
|
53
|
+
return Math.max(baseMs, 30_000);
|
|
54
|
+
return baseMs;
|
|
55
|
+
}
|
|
56
|
+
class LiveTranscriptPublisher {
|
|
57
|
+
options;
|
|
58
|
+
now;
|
|
59
|
+
intervalMs;
|
|
60
|
+
timer = null;
|
|
61
|
+
inFlight = null;
|
|
62
|
+
dirty = false;
|
|
63
|
+
stopped = false;
|
|
64
|
+
paused = false;
|
|
65
|
+
lastUploadedAt = 0;
|
|
66
|
+
lastUploaded = "";
|
|
67
|
+
uploadUrl = null;
|
|
68
|
+
published = null;
|
|
69
|
+
constructor(options) {
|
|
70
|
+
this.options = options;
|
|
71
|
+
this.now = options.now ?? Date.now;
|
|
72
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
73
|
+
}
|
|
74
|
+
/** Call whenever output arrives or a turn ends. */
|
|
75
|
+
markDirty() {
|
|
76
|
+
if (this.stopped)
|
|
77
|
+
return;
|
|
78
|
+
this.paused = false;
|
|
79
|
+
this.dirty = true;
|
|
80
|
+
this.schedule();
|
|
81
|
+
}
|
|
82
|
+
/** Locator of the last live snapshot that reached storage, if any. */
|
|
83
|
+
get publishedUrl() {
|
|
84
|
+
return this.published;
|
|
85
|
+
}
|
|
86
|
+
/** Cancels a pending upload and waits for an in-flight one, so neither can
|
|
87
|
+
* overwrite the completion transcript. A later markDirty() re-arms it. */
|
|
88
|
+
async idle() {
|
|
89
|
+
this.paused = true;
|
|
90
|
+
this.dirty = false;
|
|
91
|
+
if (this.timer)
|
|
92
|
+
clearTimeout(this.timer);
|
|
93
|
+
this.timer = null;
|
|
94
|
+
await this.inFlight;
|
|
95
|
+
}
|
|
96
|
+
async stop() {
|
|
97
|
+
this.stopped = true;
|
|
98
|
+
await this.idle();
|
|
99
|
+
}
|
|
100
|
+
schedule() {
|
|
101
|
+
if (this.timer || this.inFlight || this.stopped || this.paused)
|
|
102
|
+
return;
|
|
103
|
+
const gap = liveUploadIntervalMs(this.lastUploaded.length, this.intervalMs);
|
|
104
|
+
const wait = Math.max(0, this.lastUploadedAt + gap - this.now());
|
|
105
|
+
this.timer = setTimeout(() => {
|
|
106
|
+
this.timer = null;
|
|
107
|
+
this.inFlight = this.flush().finally(() => {
|
|
108
|
+
this.inFlight = null;
|
|
109
|
+
if (this.dirty)
|
|
110
|
+
this.schedule();
|
|
111
|
+
});
|
|
112
|
+
}, wait);
|
|
113
|
+
this.timer.unref?.();
|
|
114
|
+
}
|
|
115
|
+
async flush() {
|
|
116
|
+
if (this.stopped)
|
|
117
|
+
return;
|
|
118
|
+
this.dirty = false;
|
|
119
|
+
const content = this.options.render();
|
|
120
|
+
if (content === this.lastUploaded)
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
if (!this.uploadUrl || this.now() - this.uploadUrl.at > UPLOAD_URL_TTL_MS) {
|
|
124
|
+
const { uploadUrl, publicUrl } = await this.options.requestUploadUrl();
|
|
125
|
+
this.uploadUrl = { url: uploadUrl, publicUrl, at: this.now() };
|
|
126
|
+
}
|
|
127
|
+
if (this.stopped)
|
|
128
|
+
return;
|
|
129
|
+
await this.options.upload(this.uploadUrl.url, content);
|
|
130
|
+
this.lastUploaded = content;
|
|
131
|
+
this.published = this.uploadUrl.publicUrl;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// A rejected signature is refreshed on the next attempt.
|
|
135
|
+
this.uploadUrl = null;
|
|
136
|
+
this.options.onError?.(error);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
this.lastUploadedAt = this.now();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.LiveTranscriptPublisher = LiveTranscriptPublisher;
|
package/dist/session.cjs
CHANGED
|
@@ -15,6 +15,7 @@ const index_cjs_1 = require("./adapters/index.cjs");
|
|
|
15
15
|
const exit_conditions_cjs_1 = require("./exit-conditions.cjs");
|
|
16
16
|
const redact_cjs_1 = require("./redact.cjs");
|
|
17
17
|
const upload_cjs_1 = require("./upload.cjs");
|
|
18
|
+
const live_transcript_cjs_1 = require("./live-transcript.cjs");
|
|
18
19
|
const prompt_cjs_1 = require("./prompt.cjs");
|
|
19
20
|
const mcp_config_cjs_1 = require("./mcp-config.cjs");
|
|
20
21
|
const logger_cjs_1 = require("./logger.cjs");
|
|
@@ -441,6 +442,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
441
442
|
abortForRuntimeShutdown();
|
|
442
443
|
lifecycle.enteredMainSessionTry = true;
|
|
443
444
|
let browserEvidence;
|
|
445
|
+
let liveTranscript;
|
|
444
446
|
try {
|
|
445
447
|
reportActivity("preparing");
|
|
446
448
|
await gitWorktree.prepare();
|
|
@@ -494,6 +496,18 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
494
496
|
const adapter = (0, index_cjs_1.selectAdapter)(runtime);
|
|
495
497
|
const { bin, extraArgs } = adapterCommand(config, runtime);
|
|
496
498
|
const attempts = [];
|
|
499
|
+
// The task view follows the run through periodic redacted snapshots of
|
|
500
|
+
// the same object the completion upload below finally overwrites.
|
|
501
|
+
let liveStdout = "";
|
|
502
|
+
liveTranscript = new live_transcript_cjs_1.LiveTranscriptPublisher({
|
|
503
|
+
requestUploadUrl: async () => {
|
|
504
|
+
const { upload_url, public_url } = await api.getTranscriptUploadUrl(leaseId);
|
|
505
|
+
return { uploadUrl: upload_url, publicUrl: public_url };
|
|
506
|
+
},
|
|
507
|
+
upload: upload_cjs_1.uploadTranscript,
|
|
508
|
+
render: () => (0, live_transcript_cjs_1.renderLiveTranscript)(attempts, liveStdout, (text) => (0, redact_cjs_1.redactText)(text, registry.list()), registry.list()),
|
|
509
|
+
onError: (error) => log.warn(`live transcript upload failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(String(error), registry.list())}`),
|
|
510
|
+
});
|
|
497
511
|
const agentBudgetStartedAt = node_perf_hooks_1.performance.now();
|
|
498
512
|
const maxRuntimeMs = task.max_runtime_ms ?? config.sessionTimeoutMs;
|
|
499
513
|
let nextPrompt = null;
|
|
@@ -544,6 +558,12 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
544
558
|
dockerExec: sandbox ? { containerName: sandbox.name, runner: sandbox_cjs_1.nodeCommandRunner } : undefined,
|
|
545
559
|
signal: activeAbortController.signal,
|
|
546
560
|
onActivity: () => { outputObservedAt = Date.now(); outputUpdates += 1; },
|
|
561
|
+
onOutput: (stream, chunk) => {
|
|
562
|
+
if (stream !== "stdout")
|
|
563
|
+
return;
|
|
564
|
+
liveStdout += chunk;
|
|
565
|
+
liveTranscript?.markDirty();
|
|
566
|
+
},
|
|
547
567
|
}, task.idle_timeout_ms ?? config.sessionIdleTimeoutMs ?? 45 * 60_000, runtime === "opencode" ? config.opencodeStartupTimeoutMs : 0);
|
|
548
568
|
activeAbortController = null;
|
|
549
569
|
adapterRunning = false;
|
|
@@ -554,6 +574,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
554
574
|
executionReport = { ...executionReport, model: turnResult.model };
|
|
555
575
|
}
|
|
556
576
|
attempts.push(turnResult);
|
|
577
|
+
liveStdout = "";
|
|
578
|
+
liveTranscript.markDirty();
|
|
557
579
|
reportActivity("finishing");
|
|
558
580
|
const capacityLimit = (0, adapter_capacity_cjs_1.detectAdapterCapacityLimit)(turnResult);
|
|
559
581
|
if (capacityLimit) {
|
|
@@ -691,16 +713,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
691
713
|
if (mapping.leaseOutcome === "failed") {
|
|
692
714
|
log.warn(`adapter failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets)}`);
|
|
693
715
|
}
|
|
694
|
-
const transcript = attempts
|
|
695
|
-
|
|
696
|
-
`# agent turn ${index + 1} stdout`,
|
|
697
|
-
(0, redact_cjs_1.redactText)(attempt.stdout, knownSecrets),
|
|
698
|
-
"",
|
|
699
|
-
`# agent turn ${index + 1} stderr`,
|
|
700
|
-
(0, redact_cjs_1.redactText)(attempt.stderr, knownSecrets),
|
|
701
|
-
"",
|
|
702
|
-
])
|
|
703
|
-
.join("\n");
|
|
716
|
+
const transcript = (0, live_transcript_cjs_1.renderSessionTranscript)(attempts, (text) => (0, redact_cjs_1.redactText)(text, knownSecrets));
|
|
717
|
+
await liveTranscript.idle();
|
|
704
718
|
let transcriptUrl;
|
|
705
719
|
try {
|
|
706
720
|
const { upload_url, public_url } = await api.getTranscriptUploadUrl(leaseId);
|
|
@@ -711,6 +725,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
711
725
|
// The message carries up to 500 chars of the storage response body, so
|
|
712
726
|
// redact it like every other warn in this block.
|
|
713
727
|
log.warn(`transcript upload failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(String(err), knownSecrets)}`);
|
|
728
|
+
// The last live snapshot is still in storage; keep it reviewable.
|
|
729
|
+
transcriptUrl = liveTranscript.publishedUrl ?? undefined;
|
|
714
730
|
}
|
|
715
731
|
const redactedReport = (0, redact_cjs_1.redactText)(mapping.reportSummary, knownSecrets);
|
|
716
732
|
const completion = {
|
|
@@ -800,6 +816,9 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
800
816
|
}
|
|
801
817
|
}
|
|
802
818
|
catch (err) {
|
|
819
|
+
await liveTranscript?.stop();
|
|
820
|
+
// A live snapshot already in storage is the best transcript a crash has.
|
|
821
|
+
const crashTranscriptUrl = lastCompletion?.transcript_url ?? liveTranscript?.publishedUrl;
|
|
803
822
|
const knownSecrets = registry.list();
|
|
804
823
|
// NavarchApiError's message is only the status line; the control plane's
|
|
805
824
|
// explanation rides in `err.body`. Keep it in the agent log (redacted like
|
|
@@ -820,9 +839,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
820
839
|
failed_before_start: !adapterStarted,
|
|
821
840
|
evidence_urls: lastCompletion?.evidence_urls ?? [],
|
|
822
841
|
cost: lastCompletion?.cost ?? { tokens_in: 0, tokens_out: 0, cost_usd: 0 },
|
|
823
|
-
...(
|
|
824
|
-
? { transcript_url: lastCompletion.transcript_url }
|
|
825
|
-
: {}),
|
|
842
|
+
...(crashTranscriptUrl ? { transcript_url: crashTranscriptUrl } : {}),
|
|
826
843
|
exit_status: "crashed",
|
|
827
844
|
agent_type: runtime,
|
|
828
845
|
...executionReport,
|
|
@@ -830,6 +847,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
830
847
|
.catch((completeErr) => log.warn(`complete() after crash also failed: ${String(completeErr)}`));
|
|
831
848
|
}
|
|
832
849
|
finally {
|
|
850
|
+
await liveTranscript?.stop();
|
|
833
851
|
deps.shutdownSignal?.removeEventListener("abort", abortForRuntimeShutdown);
|
|
834
852
|
sessionClosing = true;
|
|
835
853
|
clearInterval(heartbeatTimer);
|
package/package.json
CHANGED