@songsid/agend 2.2.0-beta.7 → 2.2.0-beta.9
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/dist/agent-whats-new.js +9 -3
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/backend/grok.d.ts +1 -0
- package/dist/backend/grok.js +5 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +24 -0
- package/dist/backend/kiro.js +51 -5
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +17 -0
- package/dist/backend/muse.js +86 -16
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +16 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/config-validator.js +3 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +6 -1
- package/dist/daemon.js +12 -4
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +13 -0
- package/dist/fleet-manager.js +52 -0
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +2 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +3 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +235 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.js +15 -3
- package/dist/public-web-link.js.map +1 -1
- package/dist/settings-api.js +4 -0
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +2 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +80 -3
- package/dist/transcript-jsonl.js +147 -10
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +11 -0
- package/dist/transcript-monitor.js +61 -13
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +24 -0
- package/dist/transcript-sources.js +205 -55
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-settings.js +6 -1
- package/dist/ui/settings-strings.js +2 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-route.js +6 -1
- package/dist/ui/shared/app-shell.js +3 -3
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +40 -5
- package/dist/ui/shared/app.js +5 -0
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/instance-nav.js +9 -6
- package/dist/ui/shared/instance-switch.js +4 -4
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/unrecognised-prompt.js +2 -2
- package/dist/unrecognised-prompt.js.map +1 -1
- package/dist/web-api.d.ts +3 -0
- package/dist/web-api.js +41 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +2 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -0
- package/dist/web-shell-routes.js +9 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { lastLineBoundary, readNewLines } from "./transcript-jsonl.js";
|
|
4
|
+
import { LOOKS_PER_POLL, isClaudeAssistantHead, lastLineBoundary, readNewLines, resolveLongLines } from "./transcript-jsonl.js";
|
|
5
5
|
/**
|
|
6
6
|
* Emits tool_use / tool_result / assistant_text events off the CLI's own
|
|
7
7
|
* conversation record.
|
|
@@ -18,6 +18,10 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
18
18
|
logger;
|
|
19
19
|
source;
|
|
20
20
|
byteOffset = 0;
|
|
21
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
22
|
+
skipping = false;
|
|
23
|
+
/** #1218 P0: a long assistant line being skipped, to be read whole once its end is found. */
|
|
24
|
+
pendingLong = null;
|
|
21
25
|
transcriptPath = null;
|
|
22
26
|
pollTimer = null;
|
|
23
27
|
offsetFile;
|
|
@@ -40,6 +44,8 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
40
44
|
if (existsSync(this.offsetFile)) {
|
|
41
45
|
const data = JSON.parse(readFileSync(this.offsetFile, "utf-8"));
|
|
42
46
|
this.byteOffset = data.offset ?? 0;
|
|
47
|
+
this.skipping = false;
|
|
48
|
+
this.pendingLong = null;
|
|
43
49
|
this.transcriptPath = data.path ?? null;
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -60,6 +66,8 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
60
66
|
// Non-critical — will re-read some entries on restart
|
|
61
67
|
}
|
|
62
68
|
}
|
|
69
|
+
/** #1218: the transcript followed now — the source's, or claude-code's own — for the Log tab's on-demand read. */
|
|
70
|
+
currentPath() { return this.source ? this.source.currentPath?.() ?? null : this.transcriptPath; }
|
|
63
71
|
async resolveTranscriptPath() {
|
|
64
72
|
const statusFile = join(this.instanceDir, "statusline.json");
|
|
65
73
|
if (existsSync(statusFile)) {
|
|
@@ -158,6 +166,8 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
158
166
|
// Work resumed during our own startup must be observable → read the
|
|
159
167
|
// replacement from the top.
|
|
160
168
|
this.byteOffset = 0;
|
|
169
|
+
this.skipping = false;
|
|
170
|
+
this.pendingLong = null;
|
|
161
171
|
this.saveOffset();
|
|
162
172
|
}
|
|
163
173
|
else {
|
|
@@ -167,6 +177,8 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
167
177
|
// replay the whole history.
|
|
168
178
|
try {
|
|
169
179
|
this.byteOffset = await lastLineBoundary(current);
|
|
180
|
+
this.skipping = false;
|
|
181
|
+
this.pendingLong = null;
|
|
170
182
|
}
|
|
171
183
|
catch {
|
|
172
184
|
this.transcriptPath = null;
|
|
@@ -179,25 +191,59 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
179
191
|
if (!this.transcriptPath || !existsSync(this.transcriptPath))
|
|
180
192
|
return;
|
|
181
193
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
// #1218 P0: bounded looks (READ_MAX_BYTES each), a few per poll, each awaited; the rest waits for the next poll.
|
|
195
|
+
const generation = this.generation;
|
|
196
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
197
|
+
const read = await readNewLines(this.transcriptPath, this.byteOffset, { skipping: this.skipping });
|
|
198
|
+
if (generation !== this.generation)
|
|
199
|
+
return;
|
|
200
|
+
this.skipping = read.skipping;
|
|
201
|
+
if (read.newOffset === this.byteOffset)
|
|
202
|
+
break;
|
|
203
|
+
this.byteOffset = read.newOffset;
|
|
204
|
+
// An assistant line over the line limit (a long reply, a Write of a big file) is read whole (bounded):
|
|
205
|
+
// its text and tool calls matter. A long tool result is known by its head.
|
|
206
|
+
const resolved = await resolveLongLines(this.transcriptPath, read, this.pendingLong, isClaudeAssistantHead);
|
|
207
|
+
if (generation !== this.generation)
|
|
208
|
+
return;
|
|
209
|
+
this.pendingLong = resolved.pending;
|
|
210
|
+
for (const record of resolved.records) {
|
|
211
|
+
if ("long" in record) {
|
|
212
|
+
this.processLongHead(record.long.head);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const entry = JSON.parse(record.line);
|
|
217
|
+
this.processEntry(entry);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Malformed complete JSONL line in transcript — skip
|
|
221
|
+
}
|
|
193
222
|
}
|
|
223
|
+
this.saveOffset();
|
|
224
|
+
if (!read.more)
|
|
225
|
+
break;
|
|
194
226
|
}
|
|
195
|
-
this.saveOffset();
|
|
196
227
|
}
|
|
197
228
|
catch (err) {
|
|
198
229
|
this.logger.debug({ err }, "TranscriptMonitor poll error");
|
|
199
230
|
}
|
|
200
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* #1218 P0: a line too long to parse (a tool result of MBs, a Write with a huge input) — what its head still says:
|
|
234
|
+
* the results it carries (by tool_use_id) and the tools it calls (by name), so the bubble does not lose the step.
|
|
235
|
+
*/
|
|
236
|
+
processLongHead(head) {
|
|
237
|
+
if (head.includes('"type":"tool_result"')) {
|
|
238
|
+
for (const m of head.matchAll(/"tool_use_id":"([^"]{1,200})"/g))
|
|
239
|
+
this.emit("tool_result", m[1], undefined);
|
|
240
|
+
}
|
|
241
|
+
for (const m of head.matchAll(/"type":"tool_use"/g)) {
|
|
242
|
+
const name = /"name":"([^"]{1,200})"/.exec(head.slice(m.index, m.index + 400));
|
|
243
|
+
if (name)
|
|
244
|
+
this.emit("tool_use", name[1], {});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
201
247
|
processEntry(entry) {
|
|
202
248
|
const msg = entry.message;
|
|
203
249
|
if (!msg?.role || !msg?.content)
|
|
@@ -255,6 +301,8 @@ export class TranscriptMonitor extends EventEmitter {
|
|
|
255
301
|
resetOffset() {
|
|
256
302
|
this.generation++;
|
|
257
303
|
this.byteOffset = 0;
|
|
304
|
+
this.skipping = false;
|
|
305
|
+
this.pendingLong = null;
|
|
258
306
|
this.transcriptPath = null;
|
|
259
307
|
this.source?.reset();
|
|
260
308
|
this.saveOffset();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-monitor.js","sourceRoot":"","sources":["../src/transcript-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"transcript-monitor.js","sourceRoot":"","sources":["../src/transcript-monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,7 +44,16 @@ export interface TranscriptSource {
|
|
|
44
44
|
turnStateKnown?(): boolean;
|
|
45
45
|
/** #1641 R3: turn records a checkpoint decided (it moved the source past them), for the monitor to hand on at once. */
|
|
46
46
|
takeTurns?(): TranscriptTurnEvent[];
|
|
47
|
+
/** #1218: the transcript file being followed now (for the Log tab's on-demand read), or null. */
|
|
48
|
+
currentPath?(): string | null;
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* #1641 R3: the complete records in [from, to) that `keep` accepts — `to` is a line boundary (a checkpoint's), so
|
|
52
|
+
* nothing is cut. #1218 P0: read in READ_MAX_BYTES chunks (the range a checkpoint skips can be many MB), and only kept
|
|
53
|
+
* lines are held. A line over LINE_MAX_BYTES is judged by its head: kept, it is read whole (≤ WHOLE_LINE_MAX_BYTES —
|
|
54
|
+
* a turn's record must not be lost to the limit, #1786 review); otherwise it is skipped without being decoded.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readLineRange(path: string, from: number, to: number, keep?: (line: string) => boolean): Promise<string[]>;
|
|
48
57
|
/** #1641 R2: how far back from the baseline a rollout is scanned for the last turn boundary (a turn writes a lot). */
|
|
49
58
|
export declare const ROLLOUT_TURN_SCAN_MAX_BYTES: number;
|
|
50
59
|
/**
|
|
@@ -70,7 +79,13 @@ export declare class CodexRolloutSource implements TranscriptSource {
|
|
|
70
79
|
private workingDirectory;
|
|
71
80
|
private sessionsDir;
|
|
72
81
|
private currentFile;
|
|
82
|
+
/** #1218: the rollout followed now. */
|
|
83
|
+
currentPath(): string | null;
|
|
73
84
|
private byteOffset;
|
|
85
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
86
|
+
private skipping;
|
|
87
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
88
|
+
private pendingLong;
|
|
74
89
|
/** EOF snapshots taken when the monitor attaches; existing history is skipped. */
|
|
75
90
|
private initialOffsets;
|
|
76
91
|
/** Files whose session_meta was read and did NOT match our cwd. */
|
|
@@ -126,6 +141,8 @@ export declare class CodexRolloutSource implements TranscriptSource {
|
|
|
126
141
|
* Baselines and checkpoints ask for a fresh listing; the 2 s poll takes the shared one.
|
|
127
142
|
*/
|
|
128
143
|
private candidateFiles;
|
|
144
|
+
/** The first of `files` that is ours (in order), or undefined. Verdicts are cached, so a known file costs no read. */
|
|
145
|
+
private firstOwned;
|
|
129
146
|
private fileBelongsToUs;
|
|
130
147
|
poll(): Promise<TranscriptEvents>;
|
|
131
148
|
}
|
|
@@ -161,6 +178,9 @@ export declare class MuseSessionSource implements TranscriptSource {
|
|
|
161
178
|
private baselineComplete;
|
|
162
179
|
private rejected;
|
|
163
180
|
private sizes;
|
|
181
|
+
/** #1218 P0: per log, whether its offset is inside a long line being skipped, and a long turn record held for its end. */
|
|
182
|
+
private skipping;
|
|
183
|
+
private pendingLong;
|
|
164
184
|
private listedAt;
|
|
165
185
|
private baselined;
|
|
166
186
|
/** intent_id → its text and time, until it materializes (bounded). */
|
|
@@ -225,6 +245,10 @@ export declare class KiroSessionSource implements TranscriptSource {
|
|
|
225
245
|
private lane;
|
|
226
246
|
private currentFile;
|
|
227
247
|
private byteOffset;
|
|
248
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
249
|
+
private skipping;
|
|
250
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
251
|
+
private pendingLong;
|
|
228
252
|
private readonly createdAt;
|
|
229
253
|
private generation;
|
|
230
254
|
private cursor;
|
|
@@ -26,7 +26,7 @@ import { measureSyncWork } from "./sync-work-attribution.js";
|
|
|
26
26
|
* - Every poll re-resolves which transcript is active. Pinning to the first
|
|
27
27
|
* one found silently goes blind when the CLI starts a new session.
|
|
28
28
|
*/
|
|
29
|
-
import {
|
|
29
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
30
30
|
import { open as openFile, readdir, readFile, stat } from "node:fs/promises";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { join, resolve } from "node:path";
|
|
@@ -34,21 +34,63 @@ import { sharedRolloutIndex } from "./rollout-index.js";
|
|
|
34
34
|
import Database from "better-sqlite3";
|
|
35
35
|
import { sharedKiroTranscriptLane } from "./kiro-transcript-lane.js";
|
|
36
36
|
export { extractKiroAssistantStrings } from "./kiro-db-reader.js";
|
|
37
|
-
import { lastLineBoundary, readNewLines } from "./transcript-jsonl.js";
|
|
37
|
+
import { LINE_MAX_BYTES, LONG_HEAD_BYTES, LOOKS_PER_POLL, READ_MAX_BYTES, lastLineBoundary, readNewLines, readWholeLine, resolveLongLines } from "./transcript-jsonl.js";
|
|
38
38
|
import { museSessionCwd, readFileHeadSync } from "./backend/muse.js";
|
|
39
39
|
import { performance } from "node:perf_hooks";
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* #1641 R3: the complete records in [from, to) that `keep` accepts — `to` is a line boundary (a checkpoint's), so
|
|
42
|
+
* nothing is cut. #1218 P0: read in READ_MAX_BYTES chunks (the range a checkpoint skips can be many MB), and only kept
|
|
43
|
+
* lines are held. A line over LINE_MAX_BYTES is judged by its head: kept, it is read whole (≤ WHOLE_LINE_MAX_BYTES —
|
|
44
|
+
* a turn's record must not be lost to the limit, #1786 review); otherwise it is skipped without being decoded.
|
|
45
|
+
*/
|
|
46
|
+
export async function readLineRange(path, from, to, keep = () => true) {
|
|
42
47
|
const fh = await openFile(path, "r");
|
|
48
|
+
const out = [];
|
|
49
|
+
const whole = async (offset, bytes, head) => {
|
|
50
|
+
if (!keep(head))
|
|
51
|
+
return;
|
|
52
|
+
const line = await readWholeLine(path, offset, bytes);
|
|
53
|
+
if (line !== null && line.trim() && keep(line))
|
|
54
|
+
out.push(line);
|
|
55
|
+
};
|
|
43
56
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
let pos = from;
|
|
58
|
+
let long = null; // a long line that started in an earlier chunk
|
|
59
|
+
while (pos < to) {
|
|
60
|
+
const buffer = Buffer.alloc(Math.min(READ_MAX_BYTES, to - pos));
|
|
61
|
+
for (let filled = 0; filled < buffer.length;) {
|
|
62
|
+
const { bytesRead } = await fh.read(buffer, filled, buffer.length - filled, pos + filled);
|
|
63
|
+
if (bytesRead <= 0)
|
|
64
|
+
throw new Error(`readLineRange: no progress reading ${path} at ${pos + filled}`);
|
|
65
|
+
filled += bytesRead;
|
|
66
|
+
}
|
|
67
|
+
// Up to the last line end in this chunk (the range's end is one); a chunk with none is inside one long line.
|
|
68
|
+
const complete = buffer.lastIndexOf(0x0a) + 1;
|
|
69
|
+
if (complete === 0) {
|
|
70
|
+
long ??= { start: pos, head: buffer.toString("utf8", 0, Math.min(LONG_HEAD_BYTES, buffer.length)) };
|
|
71
|
+
pos += buffer.length;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
let start = 0;
|
|
75
|
+
while (start < complete) {
|
|
76
|
+
const end = buffer.indexOf(0x0a, start);
|
|
77
|
+
if (long) {
|
|
78
|
+
await whole(long.start, pos + end - long.start, long.head);
|
|
79
|
+
long = null;
|
|
80
|
+
}
|
|
81
|
+
else if (end - start > LINE_MAX_BYTES) {
|
|
82
|
+
await whole(pos + start, end - start, buffer.toString("utf8", start, start + LONG_HEAD_BYTES));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const line = buffer.toString("utf8", start, end);
|
|
86
|
+
if (line.trim() && keep(line))
|
|
87
|
+
out.push(line);
|
|
88
|
+
}
|
|
89
|
+
start = end + 1;
|
|
90
|
+
}
|
|
91
|
+
pos += complete;
|
|
50
92
|
}
|
|
51
|
-
return
|
|
93
|
+
return out;
|
|
52
94
|
}
|
|
53
95
|
finally {
|
|
54
96
|
await fh.close();
|
|
@@ -117,7 +159,13 @@ export class CodexRolloutSource {
|
|
|
117
159
|
workingDirectory;
|
|
118
160
|
sessionsDir;
|
|
119
161
|
currentFile = null;
|
|
162
|
+
/** #1218: the rollout followed now. */
|
|
163
|
+
currentPath() { return this.currentFile; }
|
|
120
164
|
byteOffset = 0;
|
|
165
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
166
|
+
skipping = false;
|
|
167
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
168
|
+
pendingLong = null;
|
|
121
169
|
/** EOF snapshots taken when the monitor attaches; existing history is skipped. */
|
|
122
170
|
initialOffsets = new Map();
|
|
123
171
|
/** Files whose session_meta was read and did NOT match our cwd. */
|
|
@@ -161,13 +209,15 @@ export class CodexRolloutSource {
|
|
|
161
209
|
this.pendingTurns = [];
|
|
162
210
|
this.currentFile = null;
|
|
163
211
|
this.byteOffset = 0;
|
|
212
|
+
this.skipping = false;
|
|
213
|
+
this.pendingLong = null;
|
|
164
214
|
this.openTurnId = "";
|
|
165
215
|
this.rejected.clear();
|
|
166
216
|
this.accepted.clear();
|
|
167
217
|
this.snapshotExistingFiles();
|
|
168
218
|
}
|
|
169
219
|
async checkpoint() {
|
|
170
|
-
const active = this.
|
|
220
|
+
const active = await this.firstOwned(this.candidateFiles(true));
|
|
171
221
|
if (!active)
|
|
172
222
|
return null;
|
|
173
223
|
try {
|
|
@@ -178,7 +228,7 @@ export class CodexRolloutSource {
|
|
|
178
228
|
else if (offset > this.byteOffset) {
|
|
179
229
|
// #1641 R3: the range this checkpoint skips is never polled — its turn boundaries (and user records) are taken
|
|
180
230
|
// now, so the turn state stays the file's. Tool and text records stay skipped, as before.
|
|
181
|
-
for (const line of await readLineRange(active.path, this.byteOffset, offset)) {
|
|
231
|
+
for (const line of await readLineRange(active.path, this.byteOffset, offset, (l) => l.includes('"event_msg"') || l.includes('"role":"user"'))) {
|
|
182
232
|
let entry;
|
|
183
233
|
try {
|
|
184
234
|
entry = JSON.parse(line);
|
|
@@ -191,6 +241,8 @@ export class CodexRolloutSource {
|
|
|
191
241
|
}
|
|
192
242
|
this.currentFile = active.path;
|
|
193
243
|
this.byteOffset = offset;
|
|
244
|
+
this.skipping = false;
|
|
245
|
+
this.pendingLong = null;
|
|
194
246
|
return { path: active.path, offset, sessionId: active.path };
|
|
195
247
|
}
|
|
196
248
|
catch {
|
|
@@ -224,6 +276,8 @@ export class CodexRolloutSource {
|
|
|
224
276
|
this.turnKnown = known;
|
|
225
277
|
this.currentFile = path;
|
|
226
278
|
this.byteOffset = offset;
|
|
279
|
+
this.skipping = false;
|
|
280
|
+
this.pendingLong = null;
|
|
227
281
|
this.openTurnId = this.openTurn ?? ""; // a steer after the baseline belongs to the carried-in turn
|
|
228
282
|
}
|
|
229
283
|
/**
|
|
@@ -279,23 +333,31 @@ export class CodexRolloutSource {
|
|
|
279
333
|
candidateFiles(fresh = false) {
|
|
280
334
|
return this.index.list(fresh);
|
|
281
335
|
}
|
|
282
|
-
|
|
336
|
+
/** The first of `files` that is ours (in order), or undefined. Verdicts are cached, so a known file costs no read. */
|
|
337
|
+
async firstOwned(files) {
|
|
338
|
+
for (const file of files)
|
|
339
|
+
if (await this.fileBelongsToUs(file.path))
|
|
340
|
+
return file;
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
async fileBelongsToUs(path) {
|
|
283
344
|
if (this.rejected.has(path))
|
|
284
345
|
return false;
|
|
285
346
|
if (this.accepted.has(path))
|
|
286
347
|
return true;
|
|
287
348
|
try {
|
|
288
349
|
// session_meta is the first line. It can carry long instructions, so
|
|
289
|
-
// give it headroom — but never read the whole rollout.
|
|
290
|
-
|
|
350
|
+
// give it headroom — but never read the whole rollout. #1218 P0: read asynchronously, off the fleet loop's
|
|
351
|
+
// critical path (it was a synchronous 64 KiB read per new rollout).
|
|
352
|
+
const fh = await openFile(path, "r");
|
|
291
353
|
let head;
|
|
292
354
|
try {
|
|
293
355
|
const buf = Buffer.alloc(65536);
|
|
294
|
-
const
|
|
295
|
-
head = buf.toString("utf-8", 0,
|
|
356
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
357
|
+
head = buf.toString("utf-8", 0, bytesRead);
|
|
296
358
|
}
|
|
297
359
|
finally {
|
|
298
|
-
|
|
360
|
+
await fh.close();
|
|
299
361
|
}
|
|
300
362
|
const firstLine = head.split("\n")[0];
|
|
301
363
|
const meta = JSON.parse(firstLine);
|
|
@@ -316,7 +378,7 @@ export class CodexRolloutSource {
|
|
|
316
378
|
// Re-resolve every poll: a restarted codex writes a NEW rollout, and
|
|
317
379
|
// staying pinned to the old one goes silently blind (#528 trap 1).
|
|
318
380
|
const candidates = this.candidateFiles();
|
|
319
|
-
const active =
|
|
381
|
+
const active = await this.firstOwned(candidates);
|
|
320
382
|
// Cleared only by a real loss (none found, an adoption or read that failed) — never for the length of a poll's
|
|
321
383
|
// await, when a capture reading following() would drop the veto mid-turn.
|
|
322
384
|
if (!active) {
|
|
@@ -343,20 +405,37 @@ export class CodexRolloutSource {
|
|
|
343
405
|
}
|
|
344
406
|
await this.adopt(active.path, offset);
|
|
345
407
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
408
|
+
// #1218 P0: bounded looks (READ_MAX_BYTES each), a few per poll, each awaited (a sampled rollout was 322 MB).
|
|
409
|
+
const records = [];
|
|
410
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
411
|
+
let read;
|
|
412
|
+
try {
|
|
413
|
+
read = await readNewLines(active.path, this.byteOffset, { skipping: this.skipping });
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
this.lastPollFollowed = false;
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
this.skipping = read.skipping;
|
|
420
|
+
const moved = read.newOffset !== this.byteOffset;
|
|
421
|
+
this.byteOffset = read.newOffset;
|
|
422
|
+
// #1786 review: a long turn record (task_complete with a long final reply, a pasted prompt) is read whole — a
|
|
423
|
+
// turn's end must never be lost to the line limit. Long outputs stay head-only.
|
|
424
|
+
const resolved = await resolveLongLines(active.path, read, this.pendingLong, codexWholeLine);
|
|
425
|
+
this.pendingLong = resolved.pending;
|
|
426
|
+
records.push(...resolved.records);
|
|
427
|
+
if (!moved || !read.more)
|
|
428
|
+
break;
|
|
353
429
|
}
|
|
354
|
-
const { lines, newOffset } = read;
|
|
355
|
-
this.byteOffset = newOffset;
|
|
356
430
|
this.lastPollFollowed = true;
|
|
357
431
|
const events = emptyEvents();
|
|
358
432
|
const turns = this.pendingTurns.splice(0);
|
|
359
|
-
for (const
|
|
433
|
+
for (const record of records) {
|
|
434
|
+
if ("long" in record) {
|
|
435
|
+
codexLongHead(record.long.head, events);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const line = record.line;
|
|
360
439
|
let entry;
|
|
361
440
|
try {
|
|
362
441
|
entry = JSON.parse(line);
|
|
@@ -405,6 +484,25 @@ export class CodexRolloutSource {
|
|
|
405
484
|
return events;
|
|
406
485
|
}
|
|
407
486
|
}
|
|
487
|
+
/** #1786 review: the long rollout lines read whole — turn records (event_msg) and messages (a pasted prompt, a long reply). */
|
|
488
|
+
function codexWholeLine(head) {
|
|
489
|
+
return head.includes('"type":"event_msg"') || (head.includes('"type":"response_item"') && /"payload":\{"type":"message"/.test(head));
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* #1218 P0: a rollout line too long to parse (an output of MBs, a huge call input) — what its head still says, so the
|
|
493
|
+
* bubble keeps the step: a call by its name, or an output. Turn records and messages are read whole (codexWholeLine).
|
|
494
|
+
*/
|
|
495
|
+
function codexLongHead(head, events) {
|
|
496
|
+
if (!head.includes('"type":"response_item"'))
|
|
497
|
+
return;
|
|
498
|
+
const type = /"payload":\{[^{}]{0,400}?"type":"(function_call|custom_tool_call|function_call_output|custom_tool_call_output)"/.exec(head)?.[1];
|
|
499
|
+
if (type === "function_call" || type === "custom_tool_call") {
|
|
500
|
+
const name = /"name":"([^"]{1,200})"/.exec(head)?.[1];
|
|
501
|
+
events.toolUses.push({ name: name ?? "unknown", input: undefined });
|
|
502
|
+
}
|
|
503
|
+
else if (type)
|
|
504
|
+
events.toolResults.push({ name: "unknown" });
|
|
505
|
+
}
|
|
408
506
|
/* -------------------------------------------------------------------- muse */
|
|
409
507
|
/**
|
|
410
508
|
* #1510: muse's turn boundaries, from its session logs (`<root>/<YYYY>/<MM>/<DD>/<id>/session.jsonl`, recorded on
|
|
@@ -438,6 +536,9 @@ export class MuseSessionSource {
|
|
|
438
536
|
baselineComplete = false;
|
|
439
537
|
rejected = new Set();
|
|
440
538
|
sizes = new Map();
|
|
539
|
+
/** #1218 P0: per log, whether its offset is inside a long line being skipped, and a long turn record held for its end. */
|
|
540
|
+
skipping = new Map();
|
|
541
|
+
pendingLong = new Map();
|
|
441
542
|
listedAt = Number.NEGATIVE_INFINITY;
|
|
442
543
|
baselined = false;
|
|
443
544
|
/** intent_id → its text and time, until it materializes (bounded). */
|
|
@@ -454,6 +555,8 @@ export class MuseSessionSource {
|
|
|
454
555
|
this.rejected.clear();
|
|
455
556
|
this.sizes.clear();
|
|
456
557
|
this.intents.clear();
|
|
558
|
+
this.skipping.clear();
|
|
559
|
+
this.pendingLong.clear();
|
|
457
560
|
this.listedAt = Number.NEGATIVE_INFINITY;
|
|
458
561
|
this.baselined = false;
|
|
459
562
|
this.baselineComplete = false;
|
|
@@ -513,22 +616,45 @@ export class MuseSessionSource {
|
|
|
513
616
|
continue;
|
|
514
617
|
}
|
|
515
618
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
this.files.set(path, read.newOffset);
|
|
524
|
-
this.sizes.set(path, size);
|
|
525
|
-
// One record at a time: a record of an unexpected shape is skipped, never the batch it came in.
|
|
526
|
-
for (const line of read.lines) {
|
|
619
|
+
// #1218 P0 (#1786 review): bounded looks, as the other sources; the size is remembered only once the log has been
|
|
620
|
+
// read to it, so the rest of a delta over the look budget is read next poll, not when the log grows again.
|
|
621
|
+
let caughtUp = false;
|
|
622
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
623
|
+
let read;
|
|
527
624
|
try {
|
|
528
|
-
this.
|
|
625
|
+
read = await readNewLines(path, from, { skipping: this.skipping.get(path) ?? false });
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
break;
|
|
529
629
|
}
|
|
530
|
-
|
|
630
|
+
this.skipping.set(path, read.skipping);
|
|
631
|
+
const moved = read.newOffset !== from;
|
|
632
|
+
from = read.newOffset;
|
|
633
|
+
this.files.set(path, from);
|
|
634
|
+
// A long turn record (a pasted prompt in user_intent.accepted) is read whole, bounded.
|
|
635
|
+
const resolved = await resolveLongLines(path, read, this.pendingLong.get(path) ?? null, (head) => head.includes('"payload_type":"runtime.'));
|
|
636
|
+
if (resolved.pending)
|
|
637
|
+
this.pendingLong.set(path, resolved.pending);
|
|
638
|
+
else
|
|
639
|
+
this.pendingLong.delete(path);
|
|
640
|
+
// One record at a time: a record of an unexpected shape is skipped, never the batch it came in.
|
|
641
|
+
for (const record of resolved.records) {
|
|
642
|
+
if (!("line" in record))
|
|
643
|
+
continue;
|
|
644
|
+
try {
|
|
645
|
+
this.parse(record.line, turns);
|
|
646
|
+
}
|
|
647
|
+
catch { /* skipped */ }
|
|
648
|
+
}
|
|
649
|
+
if (!read.more) {
|
|
650
|
+
caughtUp = !read.skipping;
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
if (!moved)
|
|
654
|
+
break;
|
|
531
655
|
}
|
|
656
|
+
if (caughtUp)
|
|
657
|
+
this.sizes.set(path, size);
|
|
532
658
|
}
|
|
533
659
|
const events = emptyEvents();
|
|
534
660
|
if (turns.length)
|
|
@@ -725,6 +851,10 @@ export class KiroSessionSource {
|
|
|
725
851
|
lane;
|
|
726
852
|
currentFile = null;
|
|
727
853
|
byteOffset = 0;
|
|
854
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
855
|
+
skipping = false;
|
|
856
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
857
|
+
pendingLong = null;
|
|
728
858
|
createdAt;
|
|
729
859
|
generation = 0;
|
|
730
860
|
cursor;
|
|
@@ -758,6 +888,8 @@ export class KiroSessionSource {
|
|
|
758
888
|
this.close();
|
|
759
889
|
this.currentFile = null;
|
|
760
890
|
this.byteOffset = 0;
|
|
891
|
+
this.skipping = false;
|
|
892
|
+
this.pendingLong = null;
|
|
761
893
|
this.cursor = undefined;
|
|
762
894
|
this.needsBaseline = true;
|
|
763
895
|
void this.initialize();
|
|
@@ -895,6 +1027,8 @@ export class KiroSessionSource {
|
|
|
895
1027
|
if (active.createdAtMs >= this.createdAt) {
|
|
896
1028
|
this.currentFile = active.jsonlPath;
|
|
897
1029
|
this.byteOffset = 0; // our own fresh session — observable from the start
|
|
1030
|
+
this.skipping = false;
|
|
1031
|
+
this.pendingLong = null;
|
|
898
1032
|
}
|
|
899
1033
|
else {
|
|
900
1034
|
// An older session is attached at its last line boundary. If that cannot be read now, nothing is adopted
|
|
@@ -910,23 +1044,39 @@ export class KiroSessionSource {
|
|
|
910
1044
|
return EMPTY;
|
|
911
1045
|
this.currentFile = active.jsonlPath;
|
|
912
1046
|
this.byteOffset = offset;
|
|
1047
|
+
this.skipping = false;
|
|
1048
|
+
this.pendingLong = null;
|
|
913
1049
|
return EMPTY;
|
|
914
1050
|
}
|
|
915
1051
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return EMPTY;
|
|
919
|
-
this.byteOffset = newOffset;
|
|
1052
|
+
// #1218 P0: bounded looks, as the other JSONL sources. Kiro's nesting needs the whole record: a long line is read
|
|
1053
|
+
// whole up to WHOLE_LINE_MAX_BYTES (as every line was before), and skipped beyond it.
|
|
920
1054
|
const events = emptyEvents();
|
|
921
|
-
for (
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1055
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
1056
|
+
const read = await readNewLines(this.currentFile, this.byteOffset, { skipping: this.skipping });
|
|
1057
|
+
if (generation !== this.generation)
|
|
1058
|
+
return EMPTY;
|
|
1059
|
+
this.skipping = read.skipping;
|
|
1060
|
+
const moved = read.newOffset !== this.byteOffset;
|
|
1061
|
+
this.byteOffset = read.newOffset;
|
|
1062
|
+
const resolved = await resolveLongLines(this.currentFile, read, this.pendingLong, () => true);
|
|
1063
|
+
if (generation !== this.generation)
|
|
1064
|
+
return EMPTY;
|
|
1065
|
+
this.pendingLong = resolved.pending;
|
|
1066
|
+
for (const record of resolved.records) {
|
|
1067
|
+
if (!("line" in record))
|
|
1068
|
+
continue;
|
|
1069
|
+
let entry;
|
|
1070
|
+
try {
|
|
1071
|
+
entry = JSON.parse(record.line);
|
|
1072
|
+
}
|
|
1073
|
+
catch {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
collectKiroEvents(entry, events);
|
|
928
1077
|
}
|
|
929
|
-
|
|
1078
|
+
if (!moved || !read.more)
|
|
1079
|
+
break;
|
|
930
1080
|
}
|
|
931
1081
|
return events;
|
|
932
1082
|
}
|