iteration-cli 0.9.1 → 0.10.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/README.md +4 -0
- package/dist/cli.js +18207 -429
- package/dist/cursor-hook-entry.js +11667 -139
- package/dist/launcher.js +6731 -0
- package/package.json +4 -3
- package/dist/auto-connect.d.ts +0 -32
- package/dist/auto-connect.js +0 -235
- package/dist/background-service.d.ts +0 -7
- package/dist/background-service.js +0 -37
- package/dist/claude-transcript.d.ts +0 -6
- package/dist/claude-transcript.js +0 -256
- package/dist/clerk-device-auth.d.ts +0 -7
- package/dist/clerk-device-auth.js +0 -279
- package/dist/cli.d.ts +0 -2
- package/dist/collector-fetch.d.ts +0 -9
- package/dist/collector-fetch.js +0 -57
- package/dist/collector.d.ts +0 -17
- package/dist/collector.js +0 -268
- package/dist/cursor-hook-entry.d.ts +0 -1
- package/dist/cursor-hooks.d.ts +0 -7
- package/dist/cursor-hooks.js +0 -163
- package/dist/cursor-transcript.d.ts +0 -8
- package/dist/cursor-transcript.js +0 -294
- package/dist/history-import.d.ts +0 -17
- package/dist/history-import.js +0 -63
- package/dist/launch-agent.d.ts +0 -13
- package/dist/launch-agent.js +0 -183
- package/dist/redact-trace.d.ts +0 -2
- package/dist/redact-trace.js +0 -186
- package/dist/rollout.d.ts +0 -11
- package/dist/rollout.js +0 -259
- package/dist/service.d.ts +0 -5
- package/dist/service.js +0 -249
- package/dist/status-view.d.ts +0 -33
- package/dist/status-view.js +0 -257
- package/dist/storage.d.ts +0 -94
- package/dist/storage.js +0 -809
- package/dist/systemd-user.d.ts +0 -7
- package/dist/systemd-user.js +0 -137
- package/dist/turn-summary.d.ts +0 -23
- package/dist/turn-summary.js +0 -365
package/dist/launch-agent.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { promisify } from "node:util";
|
|
2
|
-
import { execFile as execFileCallback } from "node:child_process";
|
|
3
|
-
import { promises as fs } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { setTimeout as sleep } from "node:timers/promises";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
const execFile = promisify(execFileCallback);
|
|
9
|
-
function label(source) {
|
|
10
|
-
return `com.iteration.${source}-collector`;
|
|
11
|
-
}
|
|
12
|
-
const LAUNCHCTL_TIMEOUT_MS = 5_000;
|
|
13
|
-
const RESTART_TIMEOUT_MS = 10_000;
|
|
14
|
-
const RESTART_POLL_MS = 200;
|
|
15
|
-
function xml(value) {
|
|
16
|
-
return value
|
|
17
|
-
.replaceAll("&", "&")
|
|
18
|
-
.replaceAll("<", "<")
|
|
19
|
-
.replaceAll(">", ">")
|
|
20
|
-
.replaceAll('"', """)
|
|
21
|
-
.replaceAll("'", "'");
|
|
22
|
-
}
|
|
23
|
-
function plistPath(source, homeDirectory = os.homedir()) {
|
|
24
|
-
return path.join(homeDirectory, "Library", "LaunchAgents", `${label(source)}.plist`);
|
|
25
|
-
}
|
|
26
|
-
export function launchAgentPlist(cliPath = fileURLToPath(new URL("./cli.js", import.meta.url)), nodePath = process.execPath, logDirectory = path.join(os.homedir(), "Library", "Logs"), dataDirectory = path.join(os.homedir(), ".config", "iteration", "codex-collector"), source = "codex") {
|
|
27
|
-
const argumentsList = [
|
|
28
|
-
nodePath,
|
|
29
|
-
cliPath,
|
|
30
|
-
"run",
|
|
31
|
-
source,
|
|
32
|
-
"--data-directory",
|
|
33
|
-
dataDirectory,
|
|
34
|
-
];
|
|
35
|
-
const argumentsXml = argumentsList
|
|
36
|
-
.map((argument) => `<string>${xml(argument)}</string>`)
|
|
37
|
-
.join("");
|
|
38
|
-
return [
|
|
39
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
40
|
-
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
41
|
-
'<plist version="1.0"><dict>',
|
|
42
|
-
`<key>Label</key><string>${label(source)}</string>`,
|
|
43
|
-
`<key>ProgramArguments</key><array>${argumentsXml}</array>`,
|
|
44
|
-
"<key>RunAtLoad</key><true/>",
|
|
45
|
-
"<key>KeepAlive</key><true/>",
|
|
46
|
-
"<key>ThrottleInterval</key><integer>10</integer>",
|
|
47
|
-
`<key>StandardOutPath</key><string>${xml(path.join(logDirectory, `iteration-${source}-collector.log`))}</string>`,
|
|
48
|
-
`<key>StandardErrorPath</key><string>${xml(path.join(logDirectory, `iteration-${source}-collector.error.log`))}</string>`,
|
|
49
|
-
"</dict></plist>",
|
|
50
|
-
].join("");
|
|
51
|
-
}
|
|
52
|
-
function launchctlTarget() {
|
|
53
|
-
const getUserId = process.getuid;
|
|
54
|
-
if (getUserId === undefined) {
|
|
55
|
-
throw new Error("Could not determine the current macOS user id.");
|
|
56
|
-
}
|
|
57
|
-
const userId = getUserId();
|
|
58
|
-
if (userId === undefined)
|
|
59
|
-
throw new Error("Could not determine the current macOS user id.");
|
|
60
|
-
return `gui/${userId}`;
|
|
61
|
-
}
|
|
62
|
-
function serviceMissing(error) {
|
|
63
|
-
const details = error;
|
|
64
|
-
return (String(details.code) === "113" ||
|
|
65
|
-
details.stderr?.includes("Could not find service") === true);
|
|
66
|
-
}
|
|
67
|
-
async function serviceLoaded(target, source, timeoutMs = LAUNCHCTL_TIMEOUT_MS) {
|
|
68
|
-
try {
|
|
69
|
-
await execFile("launchctl", ["print", `${target}/${label(source)}`], {
|
|
70
|
-
timeout: timeoutMs,
|
|
71
|
-
});
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
if (serviceMissing(error))
|
|
76
|
-
return false;
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function bootoutIfLoaded(target, source) {
|
|
81
|
-
if (!(await serviceLoaded(target, source)))
|
|
82
|
-
return false;
|
|
83
|
-
await execFile("launchctl", ["bootout", `${target}/${label(source)}`], {
|
|
84
|
-
timeout: LAUNCHCTL_TIMEOUT_MS,
|
|
85
|
-
});
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
function transientBootstrapFailure(error) {
|
|
89
|
-
const details = error;
|
|
90
|
-
return (String(details.code) === "5" ||
|
|
91
|
-
/^Bootstrap failed:\s*5\b/m.test(details.stderr ?? ""));
|
|
92
|
-
}
|
|
93
|
-
function restartTimeoutError(lastBootstrapFailure) {
|
|
94
|
-
return new Error(lastBootstrapFailure === undefined
|
|
95
|
-
? "The previous Codex collector did not stop within 10 seconds."
|
|
96
|
-
: "launchctl bootstrap still failed with error 5 after 10 seconds.", { cause: lastBootstrapFailure });
|
|
97
|
-
}
|
|
98
|
-
export async function bootstrapAfterBootout(wasLoaded, control) {
|
|
99
|
-
if (!wasLoaded) {
|
|
100
|
-
await control.bootstrap(LAUNCHCTL_TIMEOUT_MS);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const deadline = control.now() + RESTART_TIMEOUT_MS;
|
|
104
|
-
let lastBootstrapFailure;
|
|
105
|
-
while (true) {
|
|
106
|
-
let remaining = deadline - control.now();
|
|
107
|
-
if (remaining <= 0) {
|
|
108
|
-
throw restartTimeoutError(lastBootstrapFailure);
|
|
109
|
-
}
|
|
110
|
-
if (!(await control.isLoaded(Math.min(remaining, LAUNCHCTL_TIMEOUT_MS)))) {
|
|
111
|
-
remaining = deadline - control.now();
|
|
112
|
-
if (remaining <= 0)
|
|
113
|
-
continue;
|
|
114
|
-
try {
|
|
115
|
-
await control.bootstrap(Math.min(remaining, LAUNCHCTL_TIMEOUT_MS));
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
if (!transientBootstrapFailure(error))
|
|
120
|
-
throw error;
|
|
121
|
-
lastBootstrapFailure = error;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
remaining = deadline - control.now();
|
|
125
|
-
if (remaining > 0) {
|
|
126
|
-
await control.wait(Math.min(remaining, RESTART_POLL_MS));
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
export async function installLaunchAgent(dataDirectory, source = "codex") {
|
|
131
|
-
if (process.platform !== "darwin") {
|
|
132
|
-
throw new Error("Automatic startup is currently supported on macOS only.");
|
|
133
|
-
}
|
|
134
|
-
const file = plistPath(source);
|
|
135
|
-
const launchAgents = path.dirname(file);
|
|
136
|
-
const logs = path.join(os.homedir(), "Library", "Logs");
|
|
137
|
-
await fs.mkdir(launchAgents, { recursive: true, mode: 0o755 });
|
|
138
|
-
await fs.mkdir(logs, { recursive: true, mode: 0o755 });
|
|
139
|
-
await fs.writeFile(file, launchAgentPlist(undefined, undefined, undefined, dataDirectory ??
|
|
140
|
-
path.join(os.homedir(), ".config", "iteration", `${source}-collector`), source), { encoding: "utf8", mode: 0o644 });
|
|
141
|
-
const target = launchctlTarget();
|
|
142
|
-
const wasLoaded = await bootoutIfLoaded(target, source);
|
|
143
|
-
await bootstrapAfterBootout(wasLoaded, {
|
|
144
|
-
isLoaded: (timeoutMs) => serviceLoaded(target, source, timeoutMs),
|
|
145
|
-
bootstrap: async (timeoutMs) => {
|
|
146
|
-
await execFile("launchctl", ["bootstrap", target, file], {
|
|
147
|
-
timeout: timeoutMs,
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
wait: sleep,
|
|
151
|
-
now: Date.now,
|
|
152
|
-
});
|
|
153
|
-
await execFile("launchctl", ["kickstart", "-p", `${target}/${label(source)}`], {
|
|
154
|
-
timeout: LAUNCHCTL_TIMEOUT_MS,
|
|
155
|
-
});
|
|
156
|
-
return file;
|
|
157
|
-
}
|
|
158
|
-
export async function uninstallLaunchAgent(source = "codex") {
|
|
159
|
-
if (process.platform !== "darwin") {
|
|
160
|
-
throw new Error("Automatic startup is currently supported on macOS only.");
|
|
161
|
-
}
|
|
162
|
-
const file = plistPath(source);
|
|
163
|
-
await bootoutIfLoaded(launchctlTarget(), source);
|
|
164
|
-
await fs.rm(file, { force: true });
|
|
165
|
-
}
|
|
166
|
-
export async function launchAgentStatus(source = "codex") {
|
|
167
|
-
if (process.platform !== "darwin")
|
|
168
|
-
return false;
|
|
169
|
-
try {
|
|
170
|
-
const { stdout } = await execFile("launchctl", [
|
|
171
|
-
"print",
|
|
172
|
-
`${launchctlTarget()}/${label(source)}`,
|
|
173
|
-
]);
|
|
174
|
-
return launchAgentRunning(stdout);
|
|
175
|
-
}
|
|
176
|
-
catch {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
export function launchAgentRunning(output) {
|
|
181
|
-
const state = output.match(/^\s*state = ([^\r\n]+)$/m);
|
|
182
|
-
return state?.[1]?.trim() === "running";
|
|
183
|
-
}
|
package/dist/redact-trace.d.ts
DELETED
package/dist/redact-trace.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { lintSource } from "@secretlint/core";
|
|
2
|
-
import { secretLintProfiler } from "@secretlint/profiler";
|
|
3
|
-
import { rules as recommendedRules } from "@secretlint/secretlint-rule-preset-recommend";
|
|
4
|
-
import * as z from "zod";
|
|
5
|
-
// Secretlint profiles every rule run by default and keeps each timing entry,
|
|
6
|
-
// so a long-running collector's memory would grow without limit.
|
|
7
|
-
secretLintProfiler.setEnabled(false);
|
|
8
|
-
const REDACTED = "[REDACTED]";
|
|
9
|
-
const secretlintConfig = {
|
|
10
|
-
rules: recommendedRules
|
|
11
|
-
.filter((rule) => rule.meta.id !== "@secretlint/secretlint-rule-filter-comments")
|
|
12
|
-
.map((rule) => ({ id: rule.meta.id, rule })),
|
|
13
|
-
};
|
|
14
|
-
const assignment = /\b([A-Za-z][A-Za-z0-9_-]{0,79})["']?\s*[:=]\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s"'`,;}&]+))/g;
|
|
15
|
-
const authorization = /\b(?:authorization|proxy-authorization)["']?\s*:\s*["']?(?:Bearer|Basic)\s+([A-Za-z0-9._~+/-]+={0,2})/gi;
|
|
16
|
-
const privateKey = /-----BEGIN ((?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY)-----[\s\S]*?-----END \1-----/g;
|
|
17
|
-
function normalizedKey(key) {
|
|
18
|
-
return key
|
|
19
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
20
|
-
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
21
|
-
.toLowerCase();
|
|
22
|
-
}
|
|
23
|
-
function sensitiveKey(key) {
|
|
24
|
-
const normalized = normalizedKey(key);
|
|
25
|
-
if (/^(?:next_public|public|vite|react_app)_/.test(normalized))
|
|
26
|
-
return false;
|
|
27
|
-
return (/(?:^|_)(?:secret|password|passwd|credential)(?:_|$)/.test(normalized) ||
|
|
28
|
-
/(?:^|_)(?:api_key|private_key|database_url|db_url|dsn|connection_string|authorization|proxy_authorization|auth_header|cookie|set_cookie|token)$/.test(normalized));
|
|
29
|
-
}
|
|
30
|
-
function placeholder(value) {
|
|
31
|
-
const trimmed = value.trim();
|
|
32
|
-
return (trimmed === REDACTED ||
|
|
33
|
-
/^(?:<[^>]+>|\$\{[^}]+\}|\*+|redacted|placeholder|example|changeme|undefined|null)$/i.test(trimmed) ||
|
|
34
|
-
/^(?:process\.env|env)\.[A-Za-z0-9_]+$/.test(trimmed));
|
|
35
|
-
}
|
|
36
|
-
function addRange(ranges, start, end, length) {
|
|
37
|
-
if (!Number.isSafeInteger(start) ||
|
|
38
|
-
!Number.isSafeInteger(end) ||
|
|
39
|
-
start < 0 ||
|
|
40
|
-
end > length ||
|
|
41
|
-
start >= end) {
|
|
42
|
-
throw new Error("Local trace secret scanner returned an invalid location.");
|
|
43
|
-
}
|
|
44
|
-
ranges.push({ start, end });
|
|
45
|
-
}
|
|
46
|
-
function textRanges(value) {
|
|
47
|
-
const ranges = [];
|
|
48
|
-
for (const match of value.matchAll(assignment)) {
|
|
49
|
-
const key = match[1];
|
|
50
|
-
const candidate = match[2] ?? match[3] ?? match[4];
|
|
51
|
-
if (key === undefined ||
|
|
52
|
-
candidate === undefined ||
|
|
53
|
-
candidate.length === 0 ||
|
|
54
|
-
!sensitiveKey(key) ||
|
|
55
|
-
placeholder(candidate) ||
|
|
56
|
-
((normalizedKey(key) === "authorization" ||
|
|
57
|
-
normalizedKey(key) === "proxy_authorization") &&
|
|
58
|
-
/^(?:Bearer|Basic)$/i.test(candidate)))
|
|
59
|
-
continue;
|
|
60
|
-
const start = match.index + match[0].lastIndexOf(candidate);
|
|
61
|
-
addRange(ranges, start, start + candidate.length, value.length);
|
|
62
|
-
}
|
|
63
|
-
for (const match of value.matchAll(authorization)) {
|
|
64
|
-
const candidate = match[1];
|
|
65
|
-
if (candidate === undefined || placeholder(candidate))
|
|
66
|
-
continue;
|
|
67
|
-
const start = match.index + match[0].lastIndexOf(candidate);
|
|
68
|
-
addRange(ranges, start, start + candidate.length, value.length);
|
|
69
|
-
}
|
|
70
|
-
for (const match of value.matchAll(privateKey)) {
|
|
71
|
-
addRange(ranges, match.index, match.index + match[0].length, value.length);
|
|
72
|
-
}
|
|
73
|
-
return ranges;
|
|
74
|
-
}
|
|
75
|
-
function replaceRanges(value, ranges) {
|
|
76
|
-
if (ranges.length === 0)
|
|
77
|
-
return value;
|
|
78
|
-
ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
79
|
-
const merged = [];
|
|
80
|
-
for (const range of ranges) {
|
|
81
|
-
const previous = merged.at(-1);
|
|
82
|
-
if (previous !== undefined && range.start <= previous.end) {
|
|
83
|
-
merged[merged.length - 1] = {
|
|
84
|
-
start: previous.start,
|
|
85
|
-
end: Math.max(previous.end, range.end),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
else
|
|
89
|
-
merged.push(range);
|
|
90
|
-
}
|
|
91
|
-
let result = value;
|
|
92
|
-
for (let index = merged.length - 1; index >= 0; index -= 1) {
|
|
93
|
-
const range = merged[index];
|
|
94
|
-
if (range === undefined)
|
|
95
|
-
continue;
|
|
96
|
-
result = `${result.slice(0, range.start)}${REDACTED}${result.slice(range.end)}`;
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
async function redactText(value) {
|
|
101
|
-
if (value.length === 0 || value === REDACTED)
|
|
102
|
-
return value;
|
|
103
|
-
const ranges = textRanges(value);
|
|
104
|
-
let result;
|
|
105
|
-
try {
|
|
106
|
-
result = await lintSource({
|
|
107
|
-
source: {
|
|
108
|
-
contentType: "text",
|
|
109
|
-
content: value,
|
|
110
|
-
filePath: "trace.txt",
|
|
111
|
-
ext: "txt",
|
|
112
|
-
},
|
|
113
|
-
options: {
|
|
114
|
-
maskSecrets: true,
|
|
115
|
-
noPhysicFilePath: true,
|
|
116
|
-
config: secretlintConfig,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
throw new Error("Local trace secret scanning failed; the event was not queued.");
|
|
122
|
-
}
|
|
123
|
-
for (const message of result.messages) {
|
|
124
|
-
addRange(ranges, message.range[0], message.range[1], value.length);
|
|
125
|
-
}
|
|
126
|
-
return replaceRanges(value, ranges);
|
|
127
|
-
}
|
|
128
|
-
async function redactValue(value, cache, force) {
|
|
129
|
-
const parsedText = z.string().safeParse(value);
|
|
130
|
-
if (parsedText.success) {
|
|
131
|
-
const text = parsedText.data;
|
|
132
|
-
if (force)
|
|
133
|
-
return placeholder(text) ? text : REDACTED;
|
|
134
|
-
let result = cache.get(text);
|
|
135
|
-
if (result === undefined) {
|
|
136
|
-
result = redactText(text);
|
|
137
|
-
cache.set(text, result);
|
|
138
|
-
}
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
if (value === null || value === true || value === false)
|
|
142
|
-
return value;
|
|
143
|
-
const parsedNumber = z.number().safeParse(value);
|
|
144
|
-
if (parsedNumber.success)
|
|
145
|
-
return force ? REDACTED : parsedNumber.data;
|
|
146
|
-
if (Array.isArray(value)) {
|
|
147
|
-
const items = await Promise.all(value.map((item) => redactValue(item, cache, force)));
|
|
148
|
-
return items.every((item, index) => item === value[index]) ? value : items;
|
|
149
|
-
}
|
|
150
|
-
const record = value;
|
|
151
|
-
const entries = await Promise.all(Object.entries(record).map(async ([key, item]) => [
|
|
152
|
-
key,
|
|
153
|
-
await redactValue(item, cache, force || sensitiveKey(key)),
|
|
154
|
-
]));
|
|
155
|
-
return entries.every(([key, item]) => item === record[key])
|
|
156
|
-
? value
|
|
157
|
-
: Object.fromEntries(entries);
|
|
158
|
-
}
|
|
159
|
-
export async function redactTraceEvent(event) {
|
|
160
|
-
const cache = new Map();
|
|
161
|
-
if (event.payload.sourceType === "cursor_subagent_transcript") {
|
|
162
|
-
const parsed = z.string().safeParse(event.payload.base64);
|
|
163
|
-
if (!parsed.success)
|
|
164
|
-
throw new Error("Cursor transcript has invalid encoded content.");
|
|
165
|
-
const bytes = Buffer.from(parsed.data, "base64");
|
|
166
|
-
if (bytes.toString("base64") !== parsed.data)
|
|
167
|
-
throw new Error("Cursor transcript has invalid encoded content.");
|
|
168
|
-
const raw = bytes.toString("latin1");
|
|
169
|
-
const redacted = await redactText(raw);
|
|
170
|
-
const { base64: _encoded, ...remaining } = event.payload;
|
|
171
|
-
const payload = await redactValue(remaining, cache, false);
|
|
172
|
-
return {
|
|
173
|
-
...event,
|
|
174
|
-
payload: {
|
|
175
|
-
...payload,
|
|
176
|
-
base64: redacted === raw
|
|
177
|
-
? parsed.data
|
|
178
|
-
: Buffer.from(redacted, "latin1").toString("base64"),
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const payload = await redactValue(event.payload, cache, false);
|
|
183
|
-
return payload === event.payload
|
|
184
|
-
? event
|
|
185
|
-
: { ...event, payload: payload };
|
|
186
|
-
}
|
package/dist/rollout.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type TraceEvent } from "iteration-trace-contracts";
|
|
2
|
-
export type RolloutParseResult = Readonly<{
|
|
3
|
-
kind: "event";
|
|
4
|
-
event: TraceEvent;
|
|
5
|
-
}> | Readonly<{
|
|
6
|
-
kind: "ignored";
|
|
7
|
-
}> | Readonly<{
|
|
8
|
-
kind: "gap";
|
|
9
|
-
reason: string;
|
|
10
|
-
}>;
|
|
11
|
-
export declare function parseRolloutLine(line: string, filename: string, byteOffset: number): RolloutParseResult;
|
package/dist/rollout.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { parseTracePayload, } from "iteration-trace-contracts";
|
|
4
|
-
import * as z from "zod";
|
|
5
|
-
const recordSchema = z.record(z.string(), z.unknown());
|
|
6
|
-
const CONTEXT_RECORDS = [
|
|
7
|
-
"session_meta",
|
|
8
|
-
"turn_context",
|
|
9
|
-
"compacted",
|
|
10
|
-
"token_usage_record",
|
|
11
|
-
"world_state",
|
|
12
|
-
"retained_context",
|
|
13
|
-
"security_risk_score",
|
|
14
|
-
];
|
|
15
|
-
const RESPONSE_CONTEXT_TYPES = [
|
|
16
|
-
"reasoning",
|
|
17
|
-
"todo_list",
|
|
18
|
-
"compaction",
|
|
19
|
-
"context_compaction",
|
|
20
|
-
"configuration_update",
|
|
21
|
-
"additional_tools",
|
|
22
|
-
];
|
|
23
|
-
const EVENT_CONTEXT_TYPES = [
|
|
24
|
-
"task_complete",
|
|
25
|
-
"task_started",
|
|
26
|
-
"turn_complete",
|
|
27
|
-
"token_count",
|
|
28
|
-
"thread_goal_updated",
|
|
29
|
-
"thread_rolled_back",
|
|
30
|
-
"turn_aborted",
|
|
31
|
-
"thread_settings_applied",
|
|
32
|
-
"agent_reasoning",
|
|
33
|
-
"agent_reasoning_raw_content",
|
|
34
|
-
];
|
|
35
|
-
const COMPLETED_CONTEXT_TYPES = [
|
|
36
|
-
"reasoning",
|
|
37
|
-
"todo_list",
|
|
38
|
-
];
|
|
39
|
-
const IGNORED_EVENT_MESSAGES = ["turn_started"];
|
|
40
|
-
function parseJsonRecord(value) {
|
|
41
|
-
const parsed = recordSchema.safeParse(value);
|
|
42
|
-
return parsed.success ? parsed.data : null;
|
|
43
|
-
}
|
|
44
|
-
function parseJsonString(value) {
|
|
45
|
-
const parsed = z.string().safeParse(value);
|
|
46
|
-
return parsed.success ? parsed.data : null;
|
|
47
|
-
}
|
|
48
|
-
function oneOf(value, allowed) {
|
|
49
|
-
return value !== null && allowed.includes(value);
|
|
50
|
-
}
|
|
51
|
-
const sourceOrdinalSchema = z
|
|
52
|
-
.number()
|
|
53
|
-
.int()
|
|
54
|
-
.min(0)
|
|
55
|
-
.max(Number.MAX_SAFE_INTEGER);
|
|
56
|
-
function sourceOrdinal(value) {
|
|
57
|
-
const parsed = sourceOrdinalSchema.safeParse(value);
|
|
58
|
-
return parsed.success ? parsed.data : null;
|
|
59
|
-
}
|
|
60
|
-
function sessionFromPath(filename) {
|
|
61
|
-
const basename = path.basename(filename, ".jsonl");
|
|
62
|
-
return basename.startsWith("rollout-")
|
|
63
|
-
? basename.slice("rollout-".length)
|
|
64
|
-
: basename;
|
|
65
|
-
}
|
|
66
|
-
function callIdFrom(record) {
|
|
67
|
-
return (parseJsonString(record.call_id) ?? parseJsonString(record.tool_call_id));
|
|
68
|
-
}
|
|
69
|
-
function sha256(value) {
|
|
70
|
-
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
71
|
-
}
|
|
72
|
-
function safePayload(value) {
|
|
73
|
-
try {
|
|
74
|
-
const normalized = JSON.parse(JSON.stringify(value));
|
|
75
|
-
const parsed = parseTracePayload(normalized);
|
|
76
|
-
return parsed.success ? parsed.value : null;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function eventTimestamp(record) {
|
|
83
|
-
const timestamp = parseJsonString(record.timestamp);
|
|
84
|
-
return timestamp !== null && !Number.isNaN(Date.parse(timestamp))
|
|
85
|
-
? timestamp
|
|
86
|
-
: null;
|
|
87
|
-
}
|
|
88
|
-
function capturedEvent(record, filename, byteOffset, eventType, payload, callId, run) {
|
|
89
|
-
const eventPayload = safePayload(payload);
|
|
90
|
-
if (eventPayload === null)
|
|
91
|
-
return { kind: "gap", reason: `invalid_${eventType}_payload` };
|
|
92
|
-
const sessionId = sessionFromPath(filename);
|
|
93
|
-
return {
|
|
94
|
-
kind: "event",
|
|
95
|
-
event: {
|
|
96
|
-
eventId: sha256(`${sessionId}:${byteOffset}`),
|
|
97
|
-
sessionId,
|
|
98
|
-
sequence: byteOffset,
|
|
99
|
-
callId,
|
|
100
|
-
projectId: null,
|
|
101
|
-
occurredAt: eventTimestamp(record),
|
|
102
|
-
eventType,
|
|
103
|
-
payload: eventPayload,
|
|
104
|
-
...(run === undefined ? {} : { run }),
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function mapResponseItem(payload) {
|
|
109
|
-
const type = parseJsonString(payload.type);
|
|
110
|
-
if (type === "message") {
|
|
111
|
-
const role = parseJsonString(payload.role);
|
|
112
|
-
if (role === "user")
|
|
113
|
-
return { eventType: "user_message", callId: null };
|
|
114
|
-
if (role === "assistant")
|
|
115
|
-
return { eventType: "assistant_message", callId: null };
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
if (type === "agent_message") {
|
|
119
|
-
return { eventType: "assistant_message", callId: null };
|
|
120
|
-
}
|
|
121
|
-
if (oneOf(type, RESPONSE_CONTEXT_TYPES)) {
|
|
122
|
-
return { eventType: type, callId: null };
|
|
123
|
-
}
|
|
124
|
-
if (oneOf(type, [
|
|
125
|
-
"function_call",
|
|
126
|
-
"custom_tool_call",
|
|
127
|
-
"mcp_tool_call",
|
|
128
|
-
"web_search_call",
|
|
129
|
-
"tool_search_call",
|
|
130
|
-
"image_generation_call",
|
|
131
|
-
"local_shell_call",
|
|
132
|
-
])) {
|
|
133
|
-
return { eventType: "tool_call", callId: callIdFrom(payload) };
|
|
134
|
-
}
|
|
135
|
-
if (oneOf(type, [
|
|
136
|
-
"function_call_output",
|
|
137
|
-
"custom_tool_call_output",
|
|
138
|
-
"mcp_tool_call_output",
|
|
139
|
-
"web_search_output",
|
|
140
|
-
"tool_search_output",
|
|
141
|
-
])) {
|
|
142
|
-
return { eventType: "tool_result", callId: callIdFrom(payload) };
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
function mapCompletedItem(item) {
|
|
147
|
-
const type = parseJsonString(item.type);
|
|
148
|
-
if (oneOf(type, [
|
|
149
|
-
"command_execution",
|
|
150
|
-
"mcp_tool_call",
|
|
151
|
-
"collab_tool_call",
|
|
152
|
-
"web_search",
|
|
153
|
-
"file_change",
|
|
154
|
-
])) {
|
|
155
|
-
return { eventType: "tool_result", callId: callIdFrom(item) };
|
|
156
|
-
}
|
|
157
|
-
if (type === "agent_message") {
|
|
158
|
-
return { eventType: "assistant_message", callId: null };
|
|
159
|
-
}
|
|
160
|
-
if (oneOf(type, COMPLETED_CONTEXT_TYPES)) {
|
|
161
|
-
return { eventType: type, callId: null };
|
|
162
|
-
}
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
export function parseRolloutLine(line, filename, byteOffset) {
|
|
166
|
-
let decoded;
|
|
167
|
-
try {
|
|
168
|
-
decoded = JSON.parse(line);
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
return { kind: "gap", reason: "malformed_jsonl_record" };
|
|
172
|
-
}
|
|
173
|
-
const record = parseJsonRecord(decoded);
|
|
174
|
-
if (record === null)
|
|
175
|
-
return { kind: "gap", reason: "record_is_not_an_object" };
|
|
176
|
-
const recordType = parseJsonString(record.type);
|
|
177
|
-
const payload = parseJsonRecord(record.payload);
|
|
178
|
-
if (oneOf(recordType, CONTEXT_RECORDS)) {
|
|
179
|
-
if (payload === null)
|
|
180
|
-
return { kind: "gap", reason: `${recordType}_without_payload` };
|
|
181
|
-
let run;
|
|
182
|
-
if (recordType === "session_meta" &&
|
|
183
|
-
parseJsonString(payload.id) === sessionFromPath(filename).slice(-36)) {
|
|
184
|
-
const source = parseJsonRecord(payload.source);
|
|
185
|
-
const subagent = source === null ? null : parseJsonRecord(source.subagent);
|
|
186
|
-
const spawn = subagent === null ? null : parseJsonRecord(subagent.thread_spawn);
|
|
187
|
-
const parentId = parseJsonString(payload.parent_thread_id) ??
|
|
188
|
-
(spawn === null ? null : parseJsonString(spawn.parent_thread_id));
|
|
189
|
-
const startOrdinal = sourceOrdinal(payload.subagent_history_start_ordinal);
|
|
190
|
-
run = {
|
|
191
|
-
kind: "identity",
|
|
192
|
-
id: sessionFromPath(filename).slice(-36),
|
|
193
|
-
parentId,
|
|
194
|
-
ownStart: parentId === null,
|
|
195
|
-
startOrdinal,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
return capturedEvent(record, filename, byteOffset, recordType, payload, null, run);
|
|
199
|
-
}
|
|
200
|
-
if (recordType === "response_item" && payload !== null) {
|
|
201
|
-
const mapped = mapResponseItem(payload);
|
|
202
|
-
if (mapped === null) {
|
|
203
|
-
const payloadType = parseJsonString(payload.type) ?? "unknown";
|
|
204
|
-
return {
|
|
205
|
-
kind: "gap",
|
|
206
|
-
reason: `unsupported_response_item:${payloadType}`,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
return capturedEvent(record, filename, byteOffset, mapped.eventType, payload, mapped.callId);
|
|
210
|
-
}
|
|
211
|
-
if (recordType === "event_msg" && payload !== null) {
|
|
212
|
-
const messageType = parseJsonString(payload.type);
|
|
213
|
-
if (messageType === "user_message") {
|
|
214
|
-
return capturedEvent(record, filename, byteOffset, "user_message", payload, null);
|
|
215
|
-
}
|
|
216
|
-
if (messageType === "agent_message") {
|
|
217
|
-
return capturedEvent(record, filename, byteOffset, "assistant_message", payload, null);
|
|
218
|
-
}
|
|
219
|
-
if (oneOf(messageType, [
|
|
220
|
-
"mcp_tool_call_end",
|
|
221
|
-
"web_search_end",
|
|
222
|
-
"image_generation_end",
|
|
223
|
-
"patch_apply_end",
|
|
224
|
-
])) {
|
|
225
|
-
return capturedEvent(record, filename, byteOffset, "tool_result", payload, callIdFrom(payload));
|
|
226
|
-
}
|
|
227
|
-
if (oneOf(messageType, IGNORED_EVENT_MESSAGES))
|
|
228
|
-
return { kind: "ignored" };
|
|
229
|
-
if (oneOf(messageType, EVENT_CONTEXT_TYPES)) {
|
|
230
|
-
const ordinal = sourceOrdinal(record.ordinal);
|
|
231
|
-
const run = messageType === "thread_settings_applied" &&
|
|
232
|
-
parseJsonString(payload.thread_id) ===
|
|
233
|
-
sessionFromPath(filename).slice(-36) &&
|
|
234
|
-
ordinal !== null
|
|
235
|
-
? { kind: "start", sourceOrdinal: ordinal }
|
|
236
|
-
: undefined;
|
|
237
|
-
return capturedEvent(record, filename, byteOffset, messageType, payload, null, run);
|
|
238
|
-
}
|
|
239
|
-
if (messageType !== "item_completed") {
|
|
240
|
-
return {
|
|
241
|
-
kind: "gap",
|
|
242
|
-
reason: `unsupported_event_message:${messageType ?? "unknown"}`,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
const item = parseJsonRecord(payload.item);
|
|
246
|
-
if (item === null)
|
|
247
|
-
return { kind: "gap", reason: "item_completed_without_item" };
|
|
248
|
-
const mapped = mapCompletedItem(item);
|
|
249
|
-
if (mapped === null) {
|
|
250
|
-
const itemType = parseJsonString(item.type) ?? "unknown";
|
|
251
|
-
return { kind: "gap", reason: `unsupported_completed_item:${itemType}` };
|
|
252
|
-
}
|
|
253
|
-
return capturedEvent(record, filename, byteOffset, mapped.eventType, item, mapped.callId);
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
kind: "gap",
|
|
257
|
-
reason: `unsupported_rollout_record:${recordType ?? "unknown"}`,
|
|
258
|
-
};
|
|
259
|
-
}
|
package/dist/service.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { CollectorSource } from "iteration-trace-contracts";
|
|
2
|
-
import { type CollectorPaths } from "./storage.js";
|
|
3
|
-
export declare function acquireCollectorLock(paths: CollectorPaths): Promise<() => Promise<void>>;
|
|
4
|
-
export declare function waitForCollectorLock(paths: CollectorPaths): Promise<() => Promise<void>>;
|
|
5
|
-
export declare function runCollector(directory?: string, expectedSource?: CollectorSource): Promise<void>;
|