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/cursor-hooks.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import * as z from "zod";
|
|
5
|
-
import { ensurePrivateDirectory } from "./storage.js";
|
|
6
|
-
export const CURSOR_HOOKS = [
|
|
7
|
-
"sessionStart",
|
|
8
|
-
"beforeSubmitPrompt",
|
|
9
|
-
"preToolUse",
|
|
10
|
-
"postToolUse",
|
|
11
|
-
"postToolUseFailure",
|
|
12
|
-
"afterAgentThought",
|
|
13
|
-
"afterAgentResponse",
|
|
14
|
-
"preCompact",
|
|
15
|
-
"stop",
|
|
16
|
-
"subagentStart",
|
|
17
|
-
"subagentStop",
|
|
18
|
-
"sessionEnd",
|
|
19
|
-
];
|
|
20
|
-
export function cursorHookDirectory(dataDirectory) {
|
|
21
|
-
return path.join(dataDirectory, "cursor-hooks");
|
|
22
|
-
}
|
|
23
|
-
export function cursorHookFailurePath(dataDirectory) {
|
|
24
|
-
return path.join(dataDirectory, "cursor-hook-failure.json");
|
|
25
|
-
}
|
|
26
|
-
export async function cursorHookFailureAtMs(dataDirectory) {
|
|
27
|
-
let contents;
|
|
28
|
-
try {
|
|
29
|
-
contents = await fs.readFile(cursorHookFailurePath(dataDirectory), "utf8");
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
33
|
-
return null;
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
36
|
-
let decoded;
|
|
37
|
-
try {
|
|
38
|
-
decoded = JSON.parse(contents);
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
throw new Error("Cursor hook failure status is invalid.", { cause: error });
|
|
42
|
-
}
|
|
43
|
-
const parsed = z
|
|
44
|
-
.object({ atMs: z.number().int().min(0) })
|
|
45
|
-
.strict()
|
|
46
|
-
.safeParse(decoded);
|
|
47
|
-
if (!parsed.success)
|
|
48
|
-
throw new Error("Cursor hook failure status is invalid.");
|
|
49
|
-
return parsed.data.atMs;
|
|
50
|
-
}
|
|
51
|
-
function shellQuote(value) {
|
|
52
|
-
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
53
|
-
}
|
|
54
|
-
function hookCommand(hook, dataDirectory) {
|
|
55
|
-
const entry = fileURLToPath(new URL("./cursor-hook-entry.js", import.meta.url));
|
|
56
|
-
return `${shellQuote(process.execPath)} ${shellQuote(entry)} ${shellQuote(hook)} ${shellQuote(dataDirectory)}`;
|
|
57
|
-
}
|
|
58
|
-
const hookEntrySchema = z.object({ command: z.string().optional() }).loose();
|
|
59
|
-
const hookFileSchema = z
|
|
60
|
-
.object({
|
|
61
|
-
version: z.literal(1).optional(),
|
|
62
|
-
hooks: z.record(z.string(), z.array(hookEntrySchema)).optional(),
|
|
63
|
-
})
|
|
64
|
-
.loose();
|
|
65
|
-
async function readHooks(filename) {
|
|
66
|
-
let contents;
|
|
67
|
-
let mode = 0o600;
|
|
68
|
-
try {
|
|
69
|
-
contents = await fs.readFile(filename, "utf8");
|
|
70
|
-
mode = (await fs.stat(filename)).mode & 0o777;
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
74
|
-
return { file: {}, mode };
|
|
75
|
-
throw new Error(`Could not read Cursor hooks at ${filename}.`, {
|
|
76
|
-
cause: error,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
let parsed;
|
|
80
|
-
try {
|
|
81
|
-
parsed = JSON.parse(contents);
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
throw new Error(`Cursor hooks at ${filename} are not valid JSON.`, {
|
|
85
|
-
cause: error,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const checked = hookFileSchema.safeParse(parsed);
|
|
89
|
-
if (!checked.success)
|
|
90
|
-
throw new Error(`Cursor hooks at ${filename} have an unexpected shape.`);
|
|
91
|
-
return { file: checked.data, mode };
|
|
92
|
-
}
|
|
93
|
-
async function writeHooks(filename, file, mode) {
|
|
94
|
-
await ensurePrivateDirectory(path.dirname(filename));
|
|
95
|
-
const target = await fs.realpath(filename).catch((error) => {
|
|
96
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
97
|
-
return filename;
|
|
98
|
-
throw error;
|
|
99
|
-
});
|
|
100
|
-
const temporary = `${target}.iteration-${process.pid}.tmp`;
|
|
101
|
-
const handle = await fs.open(temporary, "w", mode);
|
|
102
|
-
try {
|
|
103
|
-
await handle.writeFile(`${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
104
|
-
await handle.sync();
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await handle.close();
|
|
108
|
-
}
|
|
109
|
-
await fs.rename(temporary, target);
|
|
110
|
-
}
|
|
111
|
-
function entries(value) {
|
|
112
|
-
return value ?? [];
|
|
113
|
-
}
|
|
114
|
-
export async function installCursorHooks(cursorHome, dataDirectory) {
|
|
115
|
-
await ensurePrivateDirectory(cursorHookDirectory(dataDirectory));
|
|
116
|
-
const filename = path.join(cursorHome, "hooks.json");
|
|
117
|
-
const { file, mode } = await readHooks(filename);
|
|
118
|
-
const hooks = { ...file.hooks };
|
|
119
|
-
for (const hook of CURSOR_HOOKS) {
|
|
120
|
-
const command = hookCommand(hook, dataDirectory);
|
|
121
|
-
const existing = entries(hooks[hook]);
|
|
122
|
-
if (!existing.some((entry) => entry.command === command))
|
|
123
|
-
hooks[hook] = [...existing, { command }];
|
|
124
|
-
}
|
|
125
|
-
await writeHooks(filename, { ...file, version: 1, hooks }, mode);
|
|
126
|
-
}
|
|
127
|
-
export async function uninstallCursorHooks(cursorHome, dataDirectory) {
|
|
128
|
-
const filename = path.join(cursorHome, "hooks.json");
|
|
129
|
-
const { file, mode } = await readHooks(filename);
|
|
130
|
-
const hooks = { ...file.hooks };
|
|
131
|
-
let changed = false;
|
|
132
|
-
for (const hook of CURSOR_HOOKS) {
|
|
133
|
-
const existing = entries(hooks[hook]);
|
|
134
|
-
const remaining = existing.filter((entry) => entry.command !== hookCommand(hook, dataDirectory));
|
|
135
|
-
if (remaining.length !== existing.length) {
|
|
136
|
-
changed = true;
|
|
137
|
-
if (remaining.length === 0)
|
|
138
|
-
delete hooks[hook];
|
|
139
|
-
else
|
|
140
|
-
hooks[hook] = remaining;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (changed)
|
|
144
|
-
await writeHooks(filename, { ...file, hooks }, mode);
|
|
145
|
-
}
|
|
146
|
-
export async function cursorHookFiles(dataDirectory) {
|
|
147
|
-
const directory = cursorHookDirectory(dataDirectory);
|
|
148
|
-
let names;
|
|
149
|
-
try {
|
|
150
|
-
names = await fs.readdir(directory);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
154
|
-
return [];
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
if (names.length > 10_000)
|
|
158
|
-
throw new Error("Cursor hook queue exceeds the scan limit.");
|
|
159
|
-
return names
|
|
160
|
-
.filter((name) => /^[0-9a-f-]+\.jsonl$/i.test(name))
|
|
161
|
-
.sort()
|
|
162
|
-
.map((name) => path.join(directory, name));
|
|
163
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type TraceEvent } from "iteration-trace-contracts";
|
|
2
|
-
type ParseResult = Readonly<{
|
|
3
|
-
events: readonly TraceEvent[];
|
|
4
|
-
gaps: readonly string[];
|
|
5
|
-
}>;
|
|
6
|
-
export declare function parseCursorTranscriptLine(line: string, filename: string, byteOffset: number): ParseResult;
|
|
7
|
-
export declare function parseCursorHookLine(line: string, filename: string): ParseResult;
|
|
8
|
-
export {};
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import * as z from "zod";
|
|
4
|
-
import { parseTraceEventBatch, } from "iteration-trace-contracts";
|
|
5
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
-
function record(value) {
|
|
7
|
-
const parsed = z.record(z.string(), z.json()).safeParse(value);
|
|
8
|
-
return parsed.success ? parsed.data : null;
|
|
9
|
-
}
|
|
10
|
-
function string(value) {
|
|
11
|
-
const parsed = z.string().safeParse(value);
|
|
12
|
-
return parsed.success ? parsed.data : null;
|
|
13
|
-
}
|
|
14
|
-
function nonnegativeInteger(value) {
|
|
15
|
-
const parsed = z
|
|
16
|
-
.number()
|
|
17
|
-
.int()
|
|
18
|
-
.min(0)
|
|
19
|
-
.max(Number.MAX_SAFE_INTEGER)
|
|
20
|
-
.safeParse(value);
|
|
21
|
-
return parsed.success ? parsed.data : null;
|
|
22
|
-
}
|
|
23
|
-
function event(conversationId, filename, byteOffset, index, eventType, payload, callId = null, occurredAt = null) {
|
|
24
|
-
const hash = createHash("sha256")
|
|
25
|
-
.update(`${filename}:${byteOffset}:${index}:${eventType}`)
|
|
26
|
-
.digest("hex");
|
|
27
|
-
return {
|
|
28
|
-
eventId: `${String(index).padStart(4, "0")}:${hash}`,
|
|
29
|
-
sessionId: `cursor:${conversationId}`,
|
|
30
|
-
sequence: byteOffset * 1000 + index,
|
|
31
|
-
callId,
|
|
32
|
-
projectId: null,
|
|
33
|
-
occurredAt,
|
|
34
|
-
eventType,
|
|
35
|
-
payload,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export function parseCursorTranscriptLine(line, filename, byteOffset) {
|
|
39
|
-
const conversationId = path.basename(filename, ".jsonl");
|
|
40
|
-
if (!UUID.test(conversationId) ||
|
|
41
|
-
path.basename(path.dirname(filename)) !== conversationId)
|
|
42
|
-
return { events: [], gaps: ["invalid_cursor_transcript_path"] };
|
|
43
|
-
let decoded;
|
|
44
|
-
try {
|
|
45
|
-
decoded = JSON.parse(line);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return { events: [], gaps: ["malformed_jsonl_record"] };
|
|
49
|
-
}
|
|
50
|
-
const source = record(decoded);
|
|
51
|
-
if (source === null)
|
|
52
|
-
return { events: [], gaps: ["record_is_not_an_object"] };
|
|
53
|
-
const events = [];
|
|
54
|
-
const gaps = [];
|
|
55
|
-
if (byteOffset === 0) {
|
|
56
|
-
events.push({
|
|
57
|
-
...event(conversationId, filename, 0, 0, "session_meta", {
|
|
58
|
-
id: conversationId,
|
|
59
|
-
transcript_path: filename,
|
|
60
|
-
}),
|
|
61
|
-
run: {
|
|
62
|
-
kind: "identity",
|
|
63
|
-
id: conversationId,
|
|
64
|
-
parentId: null,
|
|
65
|
-
ownStart: true,
|
|
66
|
-
startOrdinal: null,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
const role = string(source.role);
|
|
71
|
-
if (role === "user" || role === "assistant") {
|
|
72
|
-
const message = record(source.message);
|
|
73
|
-
if (message === null || !Array.isArray(message.content))
|
|
74
|
-
gaps.push("cursor_message_without_content");
|
|
75
|
-
else {
|
|
76
|
-
for (const [index, value] of message.content.entries()) {
|
|
77
|
-
const block = record(value);
|
|
78
|
-
if (block === null) {
|
|
79
|
-
gaps.push("invalid_cursor_content_block");
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
const type = string(block.type);
|
|
83
|
-
const eventType = type === "tool_use"
|
|
84
|
-
? "tool_call"
|
|
85
|
-
: type === "thinking"
|
|
86
|
-
? "reasoning"
|
|
87
|
-
: type === "text"
|
|
88
|
-
? role === "user"
|
|
89
|
-
? "user_message"
|
|
90
|
-
: "assistant_message"
|
|
91
|
-
: "source_record";
|
|
92
|
-
events.push(event(conversationId, filename, byteOffset, index + 1, eventType, {
|
|
93
|
-
text: string(block.text) ?? string(block.thinking),
|
|
94
|
-
name: string(block.name),
|
|
95
|
-
input: block.input ?? null,
|
|
96
|
-
sourceType: type,
|
|
97
|
-
record: source,
|
|
98
|
-
}, string(block.id)));
|
|
99
|
-
if (type !== "text" && type !== "tool_use" && type !== "thinking")
|
|
100
|
-
gaps.push(`unsupported_cursor_content_block:${type ?? "unknown"}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else if (source.type === "turn_ended") {
|
|
105
|
-
events.push(event(conversationId, filename, byteOffset, 1, source.status === "success" ? "turn_complete" : "turn_aborted", { record: source }));
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
gaps.push(`unsupported_cursor_record:${string(source.type) ?? "unknown"}`);
|
|
109
|
-
events.push(event(conversationId, filename, byteOffset, 1, "source_record", {
|
|
110
|
-
sourceType: string(source.type),
|
|
111
|
-
record: source,
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
return validated(events, gaps);
|
|
115
|
-
}
|
|
116
|
-
export function parseCursorHookLine(line, filename) {
|
|
117
|
-
let decoded;
|
|
118
|
-
try {
|
|
119
|
-
decoded = JSON.parse(line);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return { events: [], gaps: ["malformed_cursor_hook_record"] };
|
|
123
|
-
}
|
|
124
|
-
const wrapper = record(decoded);
|
|
125
|
-
const input = wrapper === null ? null : record(wrapper.input);
|
|
126
|
-
const conversationId = input === null ? null : string(input.conversation_id);
|
|
127
|
-
const hook = wrapper === null ? null : string(wrapper.hook);
|
|
128
|
-
const capturedAt = wrapper === null ? null : string(wrapper.capturedAt);
|
|
129
|
-
if (wrapper?.kind === "subagent_transcript_chunk") {
|
|
130
|
-
const subagent = record(wrapper.subagent);
|
|
131
|
-
const part = nonnegativeInteger(subagent?.part);
|
|
132
|
-
const totalParts = nonnegativeInteger(subagent?.totalParts);
|
|
133
|
-
if (conversationId === null ||
|
|
134
|
-
!UUID.test(conversationId) ||
|
|
135
|
-
capturedAt === null ||
|
|
136
|
-
Number.isNaN(Date.parse(capturedAt)) ||
|
|
137
|
-
subagent === null ||
|
|
138
|
-
part === null ||
|
|
139
|
-
totalParts === null ||
|
|
140
|
-
part < 1 ||
|
|
141
|
-
part > totalParts ||
|
|
142
|
-
string(subagent.base64) === null)
|
|
143
|
-
return { events: [], gaps: ["invalid_cursor_subagent_transcript_chunk"] };
|
|
144
|
-
const transcriptBytes = nonnegativeInteger(wrapper.transcriptBytes) ?? 0;
|
|
145
|
-
const candidate = event(conversationId, filename, transcriptBytes, part, "source_record", {
|
|
146
|
-
sourceType: "cursor_subagent_transcript",
|
|
147
|
-
text: string(subagent.text),
|
|
148
|
-
base64: string(subagent.base64),
|
|
149
|
-
path: string(subagent.path),
|
|
150
|
-
part,
|
|
151
|
-
totalParts,
|
|
152
|
-
parent_conversation_id: string(input?.parent_conversation_id),
|
|
153
|
-
subagent_id: string(input?.subagent_id),
|
|
154
|
-
}, null, capturedAt);
|
|
155
|
-
const hash = candidate.eventId.split(":").at(-1);
|
|
156
|
-
return validated([
|
|
157
|
-
{
|
|
158
|
-
...candidate,
|
|
159
|
-
sequence: transcriptBytes * 1000 + 500,
|
|
160
|
-
eventId: `${String(Date.parse(capturedAt)).padStart(13, "0")}:${String(2000 + part).padStart(4, "0")}:${hash}`,
|
|
161
|
-
},
|
|
162
|
-
], []);
|
|
163
|
-
}
|
|
164
|
-
if (input === null ||
|
|
165
|
-
conversationId === null ||
|
|
166
|
-
!UUID.test(conversationId) ||
|
|
167
|
-
hook === null ||
|
|
168
|
-
capturedAt === null ||
|
|
169
|
-
Number.isNaN(Date.parse(capturedAt)))
|
|
170
|
-
return { events: [], gaps: ["invalid_cursor_hook_record"] };
|
|
171
|
-
const transcriptBytes = wrapper?.transcriptBytes;
|
|
172
|
-
const sequence = nonnegativeInteger(transcriptBytes) ?? 0;
|
|
173
|
-
const events = [];
|
|
174
|
-
if (hook === "beforeSubmitPrompt" || hook === "sessionStart") {
|
|
175
|
-
const roots = Array.isArray(input.workspace_roots)
|
|
176
|
-
? input.workspace_roots
|
|
177
|
-
: [];
|
|
178
|
-
events.push({
|
|
179
|
-
...event(conversationId, filename, sequence, 0, "session_meta", {
|
|
180
|
-
id: conversationId,
|
|
181
|
-
cwd: string(roots[0]),
|
|
182
|
-
version: string(input.cursor_version),
|
|
183
|
-
model: string(input.model),
|
|
184
|
-
transcript_path: string(input.transcript_path),
|
|
185
|
-
}, null, capturedAt),
|
|
186
|
-
run: {
|
|
187
|
-
kind: "identity",
|
|
188
|
-
id: conversationId,
|
|
189
|
-
parentId: null,
|
|
190
|
-
ownStart: true,
|
|
191
|
-
startOrdinal: null,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
const rawText = JSON.stringify(input);
|
|
196
|
-
if (Buffer.byteLength(rawText, "utf8") <= 500_000) {
|
|
197
|
-
events.push(event(conversationId, filename, sequence, 1, "source_record", {
|
|
198
|
-
sourceType: `cursor_hook:${hook}`,
|
|
199
|
-
record: input,
|
|
200
|
-
}, null, capturedAt));
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
const chunks = splitText(rawText);
|
|
204
|
-
for (const [index, text] of chunks.entries())
|
|
205
|
-
events.push(event(conversationId, filename, sequence, index + 1, "source_record", {
|
|
206
|
-
sourceType: `cursor_hook:${hook}`,
|
|
207
|
-
text,
|
|
208
|
-
part: index + 1,
|
|
209
|
-
totalParts: chunks.length,
|
|
210
|
-
}, null, capturedAt));
|
|
211
|
-
}
|
|
212
|
-
const next = events.length + 1;
|
|
213
|
-
if (hook === "preToolUse") {
|
|
214
|
-
events.push(event(conversationId, filename, sequence, next, "tool_call", {
|
|
215
|
-
name: string(input.tool_name),
|
|
216
|
-
input: input.tool_input ?? null,
|
|
217
|
-
}, string(input.tool_use_id), capturedAt));
|
|
218
|
-
}
|
|
219
|
-
if (hook === "postToolUse" || hook === "postToolUseFailure") {
|
|
220
|
-
const output = string(input.tool_output) ??
|
|
221
|
-
string(input.error_message) ??
|
|
222
|
-
string(input.error) ??
|
|
223
|
-
(input.tool_output === undefined && input.error === undefined
|
|
224
|
-
? null
|
|
225
|
-
: JSON.stringify(input.tool_output ?? input.error));
|
|
226
|
-
const chunks = output === null ? [null] : splitText(output);
|
|
227
|
-
for (const [index, text] of chunks.entries())
|
|
228
|
-
events.push(event(conversationId, filename, sequence, next + index, "tool_result", {
|
|
229
|
-
text,
|
|
230
|
-
name: string(input.tool_name),
|
|
231
|
-
part: index + 1,
|
|
232
|
-
totalParts: chunks.length,
|
|
233
|
-
}, string(input.tool_use_id), capturedAt));
|
|
234
|
-
}
|
|
235
|
-
else if (hook === "afterAgentThought") {
|
|
236
|
-
events.push(event(conversationId, filename, sequence, next, "reasoning", {
|
|
237
|
-
text: string(input.text) ?? string(input.thought),
|
|
238
|
-
}, null, capturedAt));
|
|
239
|
-
}
|
|
240
|
-
else if (hook === "afterAgentResponse" &&
|
|
241
|
-
(input.usage !== undefined ||
|
|
242
|
-
input.input_tokens !== undefined ||
|
|
243
|
-
input.output_tokens !== undefined)) {
|
|
244
|
-
events.push(event(conversationId, filename, sequence, next, "token_usage_record", {
|
|
245
|
-
model: string(input.model),
|
|
246
|
-
usage: input.usage ?? {
|
|
247
|
-
input_tokens: input.input_tokens ?? null,
|
|
248
|
-
output_tokens: input.output_tokens ?? null,
|
|
249
|
-
cache_read_tokens: input.cache_read_tokens ?? null,
|
|
250
|
-
cache_write_tokens: input.cache_write_tokens ?? null,
|
|
251
|
-
},
|
|
252
|
-
}, null, capturedAt));
|
|
253
|
-
}
|
|
254
|
-
const capturedAtMs = Date.parse(capturedAt);
|
|
255
|
-
const ordered = events.map((candidate, index) => {
|
|
256
|
-
const digest = candidate.eventId.split(":").at(-1);
|
|
257
|
-
const thought = candidate.eventType === "reasoning"
|
|
258
|
-
? createHash("sha256")
|
|
259
|
-
.update(`${conversationId}:${string(input.generation_id)?.slice(0, 36) ?? ""}:${candidate.payload.text}`)
|
|
260
|
-
.digest("hex")
|
|
261
|
-
: null;
|
|
262
|
-
return {
|
|
263
|
-
...candidate,
|
|
264
|
-
sequence: sequence * 1000 + 500,
|
|
265
|
-
eventId: thought === null
|
|
266
|
-
? `${String(capturedAtMs).padStart(13, "0")}:${String(index).padStart(4, "0")}:${digest}`
|
|
267
|
-
: `thought:${thought}`,
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
return validated(ordered, []);
|
|
271
|
-
}
|
|
272
|
-
function splitText(value) {
|
|
273
|
-
if (value.length === 0)
|
|
274
|
-
return [""];
|
|
275
|
-
const chunks = [];
|
|
276
|
-
for (let start = 0; start < value.length;) {
|
|
277
|
-
let end = Math.min(start + 250_000, value.length);
|
|
278
|
-
if (end < value.length && /[\uD800-\uDBFF]/u.test(value[end - 1]))
|
|
279
|
-
end -= 1;
|
|
280
|
-
chunks.push(value.slice(start, end));
|
|
281
|
-
start = end;
|
|
282
|
-
}
|
|
283
|
-
return chunks;
|
|
284
|
-
}
|
|
285
|
-
function validated(events, gaps) {
|
|
286
|
-
const valid = [];
|
|
287
|
-
for (const candidate of events) {
|
|
288
|
-
if (parseTraceEventBatch({ events: [candidate] }).success)
|
|
289
|
-
valid.push(candidate);
|
|
290
|
-
else
|
|
291
|
-
gaps.push(`invalid_normalized_event:${candidate.eventType}`);
|
|
292
|
-
}
|
|
293
|
-
return { events: valid, gaps };
|
|
294
|
-
}
|
package/dist/history-import.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type CollectorConfig, type CollectorPaths } from "./storage.js";
|
|
2
|
-
type HistoryImportSize = Readonly<{
|
|
3
|
-
files: number;
|
|
4
|
-
bytes: number;
|
|
5
|
-
}>;
|
|
6
|
-
export type HistoryImportControl = Readonly<{
|
|
7
|
-
isRunning: () => Promise<boolean>;
|
|
8
|
-
stop: () => Promise<void>;
|
|
9
|
-
start: (directory: string) => Promise<unknown>;
|
|
10
|
-
acquireLock: (paths: CollectorPaths) => Promise<() => Promise<void>>;
|
|
11
|
-
}>;
|
|
12
|
-
export declare function importSourceHistory(config: CollectorConfig, paths: CollectorPaths, confirmed: boolean, control?: HistoryImportControl): Promise<Readonly<{
|
|
13
|
-
imported: boolean;
|
|
14
|
-
size: HistoryImportSize;
|
|
15
|
-
backgroundRunning: boolean | null;
|
|
16
|
-
}>>;
|
|
17
|
-
export {};
|
package/dist/history-import.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { backgroundServiceStatus, installBackgroundService, uninstallBackgroundService, } from "./background-service.js";
|
|
3
|
-
import { waitForCollectorLock } from "./service.js";
|
|
4
|
-
import { collectorSource, collectorSourceDirectory, historyImportSize, loadState, resetHistoryCursors, } from "./storage.js";
|
|
5
|
-
function backgroundControl(source) {
|
|
6
|
-
return {
|
|
7
|
-
isRunning: () => backgroundServiceStatus(source),
|
|
8
|
-
stop: () => uninstallBackgroundService(source),
|
|
9
|
-
start: (directory) => installBackgroundService(directory, source),
|
|
10
|
-
acquireLock: waitForCollectorLock,
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export async function importSourceHistory(config, paths, confirmed, control = backgroundControl(collectorSource(config))) {
|
|
14
|
-
const source = collectorSource(config);
|
|
15
|
-
const directory = collectorSourceDirectory(config);
|
|
16
|
-
const state = await loadState(paths);
|
|
17
|
-
if (!state.initialized || state.sourceDirectory !== path.resolve(directory)) {
|
|
18
|
-
throw new Error(`Connect the ${source} source before importing its history.`);
|
|
19
|
-
}
|
|
20
|
-
const estimate = await historyImportSize(directory, source);
|
|
21
|
-
if (!confirmed)
|
|
22
|
-
return { imported: false, size: estimate, backgroundRunning: null };
|
|
23
|
-
const wasRunning = await control.isRunning();
|
|
24
|
-
let size = null;
|
|
25
|
-
let stopAttempted = false;
|
|
26
|
-
let rewound = false;
|
|
27
|
-
let failure = null;
|
|
28
|
-
try {
|
|
29
|
-
if (wasRunning) {
|
|
30
|
-
stopAttempted = true;
|
|
31
|
-
await control.stop();
|
|
32
|
-
}
|
|
33
|
-
const releaseLock = await control.acquireLock(paths);
|
|
34
|
-
try {
|
|
35
|
-
size = await resetHistoryCursors(directory, paths, source);
|
|
36
|
-
rewound = true;
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
await releaseLock();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
failure = { error };
|
|
44
|
-
}
|
|
45
|
-
if (stopAttempted) {
|
|
46
|
-
try {
|
|
47
|
-
await control.start(config.dataDirectory);
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
throw new Error(rewound
|
|
51
|
-
? `History checkpoints were rewound, but the collector could not restart. Run iteration start ${source} to resume uploads.`
|
|
52
|
-
: `The collector could not be restored after a failed history import. Run iteration start ${source} to resume uploads.`, {
|
|
53
|
-
cause: failure ? new AggregateError([failure.error, error]) : error,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (failure)
|
|
58
|
-
throw failure.error;
|
|
59
|
-
if (size === null) {
|
|
60
|
-
throw new Error("History import did not update checkpoints.");
|
|
61
|
-
}
|
|
62
|
-
return { imported: true, size, backgroundRunning: wasRunning };
|
|
63
|
-
}
|
package/dist/launch-agent.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { CollectorSource } from "iteration-trace-contracts";
|
|
2
|
-
export type LaunchAgentRestart = {
|
|
3
|
-
isLoaded(timeoutMs: number): Promise<boolean>;
|
|
4
|
-
bootstrap(timeoutMs: number): Promise<void>;
|
|
5
|
-
wait(delayMs: number): Promise<void>;
|
|
6
|
-
now(): number;
|
|
7
|
-
};
|
|
8
|
-
export declare function launchAgentPlist(cliPath?: string, nodePath?: string, logDirectory?: string, dataDirectory?: string, source?: CollectorSource): string;
|
|
9
|
-
export declare function bootstrapAfterBootout(wasLoaded: boolean, control: LaunchAgentRestart): Promise<void>;
|
|
10
|
-
export declare function installLaunchAgent(dataDirectory?: string, source?: CollectorSource): Promise<string>;
|
|
11
|
-
export declare function uninstallLaunchAgent(source?: CollectorSource): Promise<void>;
|
|
12
|
-
export declare function launchAgentStatus(source?: CollectorSource): Promise<boolean>;
|
|
13
|
-
export declare function launchAgentRunning(output: string): boolean;
|