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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iteration-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,14 +16,15 @@
|
|
|
16
16
|
"bin": {
|
|
17
17
|
"iteration": "./dist/cli.js"
|
|
18
18
|
},
|
|
19
|
-
"
|
|
19
|
+
"devDependencies": {
|
|
20
20
|
"@secretlint/core": "13.0.6",
|
|
21
21
|
"@secretlint/profiler": "13.0.6",
|
|
22
22
|
"@secretlint/secretlint-rule-preset-recommend": "13.0.6",
|
|
23
|
+
"esbuild": "0.28.2",
|
|
23
24
|
"zod": "4.6.4",
|
|
24
25
|
"iteration-trace-contracts": "0.7.0"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
27
|
-
"build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json"
|
|
28
|
+
"build": "pnpm --filter iteration-trace-contracts build && tsc -p tsconfig.json --noEmit && node build.mjs"
|
|
28
29
|
}
|
|
29
30
|
}
|
package/dist/auto-connect.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type CollectorSource } from "iteration-trace-contracts";
|
|
2
|
-
import { type CollectorFetcher } from "./collector-fetch.js";
|
|
3
|
-
import { type CollectorConfig, type CollectorState } from "./storage.js";
|
|
4
|
-
export type MachineConsent = Readonly<{
|
|
5
|
-
version: 1;
|
|
6
|
-
endpoint: string;
|
|
7
|
-
organizationId: string;
|
|
8
|
-
anchorSource: CollectorSource;
|
|
9
|
-
anchorDataDirectory: string;
|
|
10
|
-
sourceHomes: Readonly<{
|
|
11
|
-
codex: string;
|
|
12
|
-
claude: string;
|
|
13
|
-
cursor: string;
|
|
14
|
-
}>;
|
|
15
|
-
}>;
|
|
16
|
-
export declare function sourceHome(source: CollectorSource): string;
|
|
17
|
-
export declare function detectedSources(sourceHomes: MachineConsent["sourceHomes"]): Promise<CollectorSource[]>;
|
|
18
|
-
export declare function loadMachineConsent(): Promise<MachineConsent | null>;
|
|
19
|
-
export declare function saveMachineConsent(consent: MachineConsent): Promise<void>;
|
|
20
|
-
export type SourceEnrollment = Readonly<{
|
|
21
|
-
token: string;
|
|
22
|
-
organizationId: string;
|
|
23
|
-
organizationName: string;
|
|
24
|
-
}>;
|
|
25
|
-
export declare function readCollectorScope(anchor: CollectorConfig): Promise<Readonly<{
|
|
26
|
-
organizationId: string;
|
|
27
|
-
organizationName: string;
|
|
28
|
-
}>>;
|
|
29
|
-
export declare function expandSource(anchor: CollectorConfig, source: CollectorSource, organizationId: string | null, request?: CollectorFetcher): Promise<SourceEnrollment>;
|
|
30
|
-
export declare function activateSource(source: CollectorSource, home: string, endpoint: string, enrollment: SourceEnrollment, state: CollectorState): Promise<CollectorConfig>;
|
|
31
|
-
export declare function connectNewDetectedSources(anchor: CollectorConfig, consent: MachineConsent): Promise<CollectorSource[]>;
|
|
32
|
-
export declare function connectedConfig(source: CollectorSource): Promise<CollectorConfig | null>;
|
package/dist/auto-connect.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import { createHmac } from "node:crypto";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import * as z from "zod";
|
|
6
|
-
import { COLLECTOR_SOURCES, parseCollectorEnrollmentResponse, } from "iteration-trace-contracts";
|
|
7
|
-
import { installBackgroundService } from "./background-service.js";
|
|
8
|
-
import { collectorFetch } from "./collector-fetch.js";
|
|
9
|
-
import { installTurnSummary } from "./turn-summary.js";
|
|
10
|
-
import { installCursorHooks } from "./cursor-hooks.js";
|
|
11
|
-
import { assertFreshCollectorDirectory, collectorPaths, defaultCollectorDirectory, ensurePrivateDirectory, loadConfig, prepareSourceInitialState, saveConfig, saveState, validateCollectorEndpoint, } from "./storage.js";
|
|
12
|
-
const consentSchema = z
|
|
13
|
-
.object({
|
|
14
|
-
version: z.literal(1),
|
|
15
|
-
endpoint: z.url(),
|
|
16
|
-
organizationId: z.uuid(),
|
|
17
|
-
anchorSource: z.enum(COLLECTOR_SOURCES),
|
|
18
|
-
anchorDataDirectory: z.string(),
|
|
19
|
-
sourceHomes: z
|
|
20
|
-
.object({
|
|
21
|
-
codex: z.string(),
|
|
22
|
-
claude: z.string(),
|
|
23
|
-
cursor: z.string().default(path.join(os.homedir(), ".cursor")),
|
|
24
|
-
})
|
|
25
|
-
.strict(),
|
|
26
|
-
})
|
|
27
|
-
.strict();
|
|
28
|
-
function consentPath() {
|
|
29
|
-
return path.join(os.homedir(), ".config", "iteration", "auto-connect.json");
|
|
30
|
-
}
|
|
31
|
-
export function sourceHome(source) {
|
|
32
|
-
return path.resolve(source === "codex"
|
|
33
|
-
? (process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"))
|
|
34
|
-
: source === "claude"
|
|
35
|
-
? (process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"))
|
|
36
|
-
: path.join(os.homedir(), ".cursor"));
|
|
37
|
-
}
|
|
38
|
-
export async function detectedSources(sourceHomes) {
|
|
39
|
-
const detected = [];
|
|
40
|
-
for (const source of COLLECTOR_SOURCES) {
|
|
41
|
-
const directory = path.join(sourceHomes[source], source === "codex" ? "sessions" : "projects");
|
|
42
|
-
let stats;
|
|
43
|
-
try {
|
|
44
|
-
stats = await fs.stat(directory);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
48
|
-
continue;
|
|
49
|
-
throw new Error(`Could not inspect ${source} sessions at ${directory}.`, {
|
|
50
|
-
cause: error,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (!stats.isDirectory()) {
|
|
54
|
-
throw new Error(`${source} session path is not a directory: ${directory}.`);
|
|
55
|
-
}
|
|
56
|
-
detected.push(source);
|
|
57
|
-
}
|
|
58
|
-
return detected;
|
|
59
|
-
}
|
|
60
|
-
export async function loadMachineConsent() {
|
|
61
|
-
let text;
|
|
62
|
-
try {
|
|
63
|
-
text = await fs.readFile(consentPath(), "utf8");
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
67
|
-
return null;
|
|
68
|
-
throw new Error("Could not read automatic source connection settings.", {
|
|
69
|
-
cause: error,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
let decoded;
|
|
73
|
-
try {
|
|
74
|
-
decoded = JSON.parse(text);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
throw new Error("Automatic source connection settings are invalid.", {
|
|
78
|
-
cause: error,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
const parsed = consentSchema.safeParse(decoded);
|
|
82
|
-
if (!parsed.success)
|
|
83
|
-
throw new Error("Automatic source connection settings are invalid.");
|
|
84
|
-
validateCollectorEndpoint(parsed.data.endpoint);
|
|
85
|
-
return parsed.data;
|
|
86
|
-
}
|
|
87
|
-
export async function saveMachineConsent(consent) {
|
|
88
|
-
const parsed = consentSchema.parse(consent);
|
|
89
|
-
validateCollectorEndpoint(parsed.endpoint);
|
|
90
|
-
const filename = consentPath();
|
|
91
|
-
await ensurePrivateDirectory(path.dirname(filename));
|
|
92
|
-
const temporary = `${filename}.${process.pid}.tmp`;
|
|
93
|
-
const handle = await fs.open(temporary, "w", 0o600);
|
|
94
|
-
try {
|
|
95
|
-
await handle.writeFile(`${JSON.stringify(parsed)}\n`, "utf8");
|
|
96
|
-
await handle.sync();
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
await handle.close();
|
|
100
|
-
}
|
|
101
|
-
await fs.rename(temporary, filename);
|
|
102
|
-
}
|
|
103
|
-
export async function readCollectorScope(anchor) {
|
|
104
|
-
const endpoint = validateCollectorEndpoint(anchor.endpoint);
|
|
105
|
-
const response = await collectorFetch(new URL("./collector-auth/expand", endpoint), {
|
|
106
|
-
headers: { Authorization: `Bearer ${anchor.token}` },
|
|
107
|
-
signal: AbortSignal.timeout(10_000),
|
|
108
|
-
});
|
|
109
|
-
const payload = await response.json();
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const error = z.object({ error: z.string() }).safeParse(payload);
|
|
112
|
-
throw new Error(`Could not identify the collector workspace: ${error.success ? error.data.error : `HTTP ${response.status}`}`);
|
|
113
|
-
}
|
|
114
|
-
const parsed = z
|
|
115
|
-
.object({ organizationId: z.uuid(), organizationName: z.string().min(1) })
|
|
116
|
-
.strict()
|
|
117
|
-
.safeParse(payload);
|
|
118
|
-
if (!parsed.success)
|
|
119
|
-
throw new Error("Iteration returned an invalid collector workspace.");
|
|
120
|
-
return parsed.data;
|
|
121
|
-
}
|
|
122
|
-
export async function expandSource(anchor, source, organizationId, request = collectorFetch) {
|
|
123
|
-
const endpoint = validateCollectorEndpoint(anchor.endpoint);
|
|
124
|
-
const token = createHmac("sha256", anchor.token)
|
|
125
|
-
.update(`iteration-source:${source}`, "utf8")
|
|
126
|
-
.digest("base64url");
|
|
127
|
-
const response = await request(new URL("./collector-auth/expand", endpoint), {
|
|
128
|
-
method: "POST",
|
|
129
|
-
headers: {
|
|
130
|
-
Authorization: `Bearer ${anchor.token}`,
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
source,
|
|
135
|
-
token,
|
|
136
|
-
...(organizationId === null ? {} : { organizationId }),
|
|
137
|
-
}),
|
|
138
|
-
signal: AbortSignal.timeout(15_000),
|
|
139
|
-
});
|
|
140
|
-
const payload = await response.json();
|
|
141
|
-
if (!response.ok) {
|
|
142
|
-
const error = z.object({ error: z.string() }).safeParse(payload);
|
|
143
|
-
throw new Error(`Could not connect ${source}: ${error.success ? error.data.error : `HTTP ${response.status}`}`);
|
|
144
|
-
}
|
|
145
|
-
const parsed = parseCollectorEnrollmentResponse(payload);
|
|
146
|
-
if (!parsed.success || parsed.value.status !== "enrolled") {
|
|
147
|
-
throw new Error(`Iteration returned an invalid ${source} connection response.`);
|
|
148
|
-
}
|
|
149
|
-
if (organizationId !== null &&
|
|
150
|
-
parsed.value.organizationId !== organizationId) {
|
|
151
|
-
throw new Error(`Iteration connected ${source} to an unexpected workspace.`);
|
|
152
|
-
}
|
|
153
|
-
return parsed.value;
|
|
154
|
-
}
|
|
155
|
-
export async function activateSource(source, home, endpoint, enrollment, state) {
|
|
156
|
-
const directory = defaultCollectorDirectory(source);
|
|
157
|
-
const paths = collectorPaths(directory);
|
|
158
|
-
await assertFreshCollectorDirectory(paths, source);
|
|
159
|
-
const common = {
|
|
160
|
-
endpoint,
|
|
161
|
-
token: enrollment.token,
|
|
162
|
-
dataDirectory: directory,
|
|
163
|
-
organizationId: enrollment.organizationId,
|
|
164
|
-
organizationName: enrollment.organizationName,
|
|
165
|
-
};
|
|
166
|
-
const config = source === "codex"
|
|
167
|
-
? { ...common, codexHome: home }
|
|
168
|
-
: source === "claude"
|
|
169
|
-
? { ...common, source: "claude", claudeHome: home }
|
|
170
|
-
: { ...common, source: "cursor", cursorHome: home };
|
|
171
|
-
await saveConfig(config, paths);
|
|
172
|
-
try {
|
|
173
|
-
await saveState(state, paths);
|
|
174
|
-
if (source === "cursor")
|
|
175
|
-
await installCursorHooks(home, directory);
|
|
176
|
-
await installBackgroundService(directory, source);
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
throw new Error(`${source} was connected, but local startup failed. Run iteration start ${source} to repair it.`, { cause: error });
|
|
180
|
-
}
|
|
181
|
-
return config;
|
|
182
|
-
}
|
|
183
|
-
export async function connectNewDetectedSources(anchor, consent) {
|
|
184
|
-
if (anchor.dataDirectory !== consent.anchorDataDirectory ||
|
|
185
|
-
anchor.endpoint !== consent.endpoint) {
|
|
186
|
-
throw new Error("Automatic connection settings do not match the running collector.");
|
|
187
|
-
}
|
|
188
|
-
const detected = await detectedSources(consent.sourceHomes);
|
|
189
|
-
const connected = [];
|
|
190
|
-
for (const source of detected) {
|
|
191
|
-
if (source === consent.anchorSource)
|
|
192
|
-
continue;
|
|
193
|
-
const paths = collectorPaths(defaultCollectorDirectory(source));
|
|
194
|
-
try {
|
|
195
|
-
await fs.access(paths.config);
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
200
|
-
throw error;
|
|
201
|
-
}
|
|
202
|
-
await assertFreshCollectorDirectory(paths, source);
|
|
203
|
-
const state = await prepareSourceInitialState(source, consent.sourceHomes[source], true);
|
|
204
|
-
const enrollment = await expandSource(anchor, source, consent.organizationId);
|
|
205
|
-
const config = await activateSource(source, consent.sourceHomes[source], consent.endpoint, enrollment, state);
|
|
206
|
-
connected.push(source);
|
|
207
|
-
try {
|
|
208
|
-
if (source !== "cursor")
|
|
209
|
-
await installTurnSummary(config);
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
console.error(JSON.stringify({
|
|
213
|
-
level: "warning",
|
|
214
|
-
source,
|
|
215
|
-
operation: "install_turn_summary",
|
|
216
|
-
message: error instanceof Error
|
|
217
|
-
? error.message
|
|
218
|
-
: "The turn summary could not be installed.",
|
|
219
|
-
}));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return connected;
|
|
223
|
-
}
|
|
224
|
-
export async function connectedConfig(source) {
|
|
225
|
-
const paths = collectorPaths(defaultCollectorDirectory(source));
|
|
226
|
-
try {
|
|
227
|
-
await fs.access(paths.config);
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
231
|
-
return null;
|
|
232
|
-
throw error;
|
|
233
|
-
}
|
|
234
|
-
return loadConfig(paths);
|
|
235
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { CollectorSource } from "iteration-trace-contracts";
|
|
2
|
-
export declare function requireCollectorPlatform(): void;
|
|
3
|
-
export declare function prepareBackgroundPersistence(): Promise<void>;
|
|
4
|
-
export declare function collectorLogPath(source: CollectorSource): string;
|
|
5
|
-
export declare function installBackgroundService(directory: string, source: CollectorSource): Promise<string>;
|
|
6
|
-
export declare function uninstallBackgroundService(source: CollectorSource): Promise<void>;
|
|
7
|
-
export declare function backgroundServiceStatus(source: CollectorSource): Promise<boolean>;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { installLaunchAgent, launchAgentStatus, uninstallLaunchAgent, } from "./launch-agent.js";
|
|
4
|
-
import { installSystemdUserService, ensureSystemdUserLinger, systemdLogPath, systemdUserServiceStatus, uninstallSystemdUserService, } from "./systemd-user.js";
|
|
5
|
-
export function requireCollectorPlatform() {
|
|
6
|
-
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
7
|
-
throw new Error("Automatic startup is supported on macOS and Linux only.");
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export async function prepareBackgroundPersistence() {
|
|
11
|
-
requireCollectorPlatform();
|
|
12
|
-
if (process.platform === "linux")
|
|
13
|
-
await ensureSystemdUserLinger();
|
|
14
|
-
}
|
|
15
|
-
export function collectorLogPath(source) {
|
|
16
|
-
if (process.platform === "linux")
|
|
17
|
-
return systemdLogPath(source);
|
|
18
|
-
return path.join(os.homedir(), "Library", "Logs", `iteration-${source}-collector.error.log`);
|
|
19
|
-
}
|
|
20
|
-
export async function installBackgroundService(directory, source) {
|
|
21
|
-
requireCollectorPlatform();
|
|
22
|
-
return process.platform === "darwin"
|
|
23
|
-
? installLaunchAgent(directory, source)
|
|
24
|
-
: installSystemdUserService(directory, source);
|
|
25
|
-
}
|
|
26
|
-
export async function uninstallBackgroundService(source) {
|
|
27
|
-
requireCollectorPlatform();
|
|
28
|
-
return process.platform === "darwin"
|
|
29
|
-
? uninstallLaunchAgent(source)
|
|
30
|
-
: uninstallSystemdUserService(source);
|
|
31
|
-
}
|
|
32
|
-
export async function backgroundServiceStatus(source) {
|
|
33
|
-
requireCollectorPlatform();
|
|
34
|
-
return process.platform === "darwin"
|
|
35
|
-
? launchAgentStatus(source)
|
|
36
|
-
: systemdUserServiceStatus(source);
|
|
37
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type TraceEvent } from "iteration-trace-contracts";
|
|
2
|
-
export type ClaudeParseResult = Readonly<{
|
|
3
|
-
events: readonly TraceEvent[];
|
|
4
|
-
gaps: readonly string[];
|
|
5
|
-
}>;
|
|
6
|
-
export declare function parseClaudeLine(line: string, filename: string, byteOffset: number): ClaudeParseResult;
|
|
@@ -1,256 +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 CONTEXT_RECORD_TYPES = new Set([
|
|
6
|
-
"attachment",
|
|
7
|
-
"system",
|
|
8
|
-
"last-prompt",
|
|
9
|
-
"mode",
|
|
10
|
-
"permission-mode",
|
|
11
|
-
"atis-latch",
|
|
12
|
-
"ai-title",
|
|
13
|
-
"file-history-snapshot",
|
|
14
|
-
"file-history-delta",
|
|
15
|
-
"cost-state",
|
|
16
|
-
"queue-operation",
|
|
17
|
-
"pr-link",
|
|
18
|
-
]);
|
|
19
|
-
function object(value) {
|
|
20
|
-
const parsed = z.record(z.string(), z.json()).safeParse(value);
|
|
21
|
-
return parsed.success ? parsed.data : null;
|
|
22
|
-
}
|
|
23
|
-
function string(value) {
|
|
24
|
-
const parsed = z.string().safeParse(value);
|
|
25
|
-
return parsed.success ? parsed.data : null;
|
|
26
|
-
}
|
|
27
|
-
function digest(value) {
|
|
28
|
-
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
29
|
-
}
|
|
30
|
-
const URL_NAMESPACE = Buffer.from("6ba7b8119dad11d180b400c04fd430c8", "hex");
|
|
31
|
-
function childThreadId(parentId, agentId) {
|
|
32
|
-
const bytes = createHash("sha1")
|
|
33
|
-
.update(URL_NAMESPACE)
|
|
34
|
-
.update(`https://github.com/Thytu/Iteration/claude/${parentId}/agent/${agentId}`)
|
|
35
|
-
.digest()
|
|
36
|
-
.subarray(0, 16);
|
|
37
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x50;
|
|
38
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
39
|
-
const hex = bytes.toString("hex");
|
|
40
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
41
|
-
}
|
|
42
|
-
const SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
43
|
-
function claudeSessionIdentity(filename) {
|
|
44
|
-
if (!filename.endsWith(".jsonl"))
|
|
45
|
-
return null;
|
|
46
|
-
const basename = path.basename(filename, ".jsonl");
|
|
47
|
-
if (path.basename(path.dirname(filename)) === "subagents") {
|
|
48
|
-
const parent = path.basename(path.dirname(path.dirname(filename)));
|
|
49
|
-
if (!basename.startsWith("agent-") || !SESSION_UUID.test(parent))
|
|
50
|
-
return null;
|
|
51
|
-
const agentId = basename.slice("agent-".length);
|
|
52
|
-
if (agentId.length === 0)
|
|
53
|
-
return null;
|
|
54
|
-
const threadId = childThreadId(parent, agentId);
|
|
55
|
-
const sessionId = `claude:${threadId}`;
|
|
56
|
-
const runId = `${parent}/agent/${agentId}`;
|
|
57
|
-
if (sessionId.length > 200 || runId.length > 200)
|
|
58
|
-
return null;
|
|
59
|
-
return {
|
|
60
|
-
sessionId,
|
|
61
|
-
parentSessionId: `claude:${parent}`,
|
|
62
|
-
agentId,
|
|
63
|
-
runId,
|
|
64
|
-
parentRunId: parent,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const sessionId = `claude:${basename}`;
|
|
68
|
-
return sessionId.length <= 200 && SESSION_UUID.test(basename)
|
|
69
|
-
? {
|
|
70
|
-
sessionId,
|
|
71
|
-
parentSessionId: null,
|
|
72
|
-
agentId: null,
|
|
73
|
-
runId: basename,
|
|
74
|
-
parentRunId: null,
|
|
75
|
-
}
|
|
76
|
-
: null;
|
|
77
|
-
}
|
|
78
|
-
function timestamp(record) {
|
|
79
|
-
const value = string(record.timestamp);
|
|
80
|
-
return value !== null &&
|
|
81
|
-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) &&
|
|
82
|
-
!Number.isNaN(Date.parse(value))
|
|
83
|
-
? value
|
|
84
|
-
: null;
|
|
85
|
-
}
|
|
86
|
-
function event(identity, record, byteOffset, index, eventType, payload, callId = null, stableKey) {
|
|
87
|
-
return {
|
|
88
|
-
eventId: `${String(index).padStart(4, "0")}:${digest(`${identity.sessionId}:${stableKey ?? string(record.uuid) ?? `${byteOffset}:${JSON.stringify(record)}`}`)}`,
|
|
89
|
-
sessionId: identity.sessionId,
|
|
90
|
-
sequence: byteOffset,
|
|
91
|
-
callId,
|
|
92
|
-
projectId: null,
|
|
93
|
-
occurredAt: timestamp(record),
|
|
94
|
-
eventType,
|
|
95
|
-
payload,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function contentText(value) {
|
|
99
|
-
const literal = string(value);
|
|
100
|
-
if (literal !== null)
|
|
101
|
-
return literal;
|
|
102
|
-
if (!Array.isArray(value))
|
|
103
|
-
return null;
|
|
104
|
-
const parts = value.flatMap((part) => {
|
|
105
|
-
const block = object(part);
|
|
106
|
-
const text = block === null ? null : string(block.text);
|
|
107
|
-
return text === null ? [] : [text];
|
|
108
|
-
});
|
|
109
|
-
return parts.length > 0 ? parts.join("\n") : null;
|
|
110
|
-
}
|
|
111
|
-
export function parseClaudeLine(line, filename, byteOffset) {
|
|
112
|
-
const identity = claudeSessionIdentity(filename);
|
|
113
|
-
if (identity === null)
|
|
114
|
-
return { events: [], gaps: ["invalid_claude_session_path"] };
|
|
115
|
-
let decoded;
|
|
116
|
-
try {
|
|
117
|
-
decoded = JSON.parse(line);
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return { events: [], gaps: ["malformed_jsonl_record"] };
|
|
121
|
-
}
|
|
122
|
-
const record = object(decoded);
|
|
123
|
-
if (record === null)
|
|
124
|
-
return { events: [], gaps: ["record_is_not_an_object"] };
|
|
125
|
-
const recordType = string(record.type);
|
|
126
|
-
const sourceSessionId = string(record.sessionId);
|
|
127
|
-
if ((identity.agentId !== null && sourceSessionId === null) ||
|
|
128
|
-
(sourceSessionId !== null &&
|
|
129
|
-
`claude:${sourceSessionId}` !==
|
|
130
|
-
(identity.parentSessionId ?? identity.sessionId))) {
|
|
131
|
-
return { events: [], gaps: ["session_id_does_not_match_path"] };
|
|
132
|
-
}
|
|
133
|
-
if (identity.agentId !== null && string(record.agentId) !== identity.agentId)
|
|
134
|
-
return { events: [], gaps: ["agent_id_does_not_match_path"] };
|
|
135
|
-
const gaps = [];
|
|
136
|
-
if (record.timestamp !== undefined && timestamp(record) === null)
|
|
137
|
-
gaps.push("invalid_source_timestamp");
|
|
138
|
-
const events = [];
|
|
139
|
-
const meta = {
|
|
140
|
-
id: identity.runId,
|
|
141
|
-
parent_thread_id: identity.parentRunId,
|
|
142
|
-
cwd: string(record.cwd),
|
|
143
|
-
git: { branch: string(record.gitBranch) },
|
|
144
|
-
version: string(record.version),
|
|
145
|
-
agentId: identity.agentId,
|
|
146
|
-
};
|
|
147
|
-
events.push({
|
|
148
|
-
...event(identity, record, 0, 0, "session_meta", meta, null, "session_meta"),
|
|
149
|
-
run: {
|
|
150
|
-
kind: "identity",
|
|
151
|
-
id: identity.runId,
|
|
152
|
-
parentId: identity.parentRunId,
|
|
153
|
-
ownStart: true,
|
|
154
|
-
startOrdinal: null,
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
if (recordType === "user") {
|
|
158
|
-
const message = object(record.message);
|
|
159
|
-
const content = message === null ? null : string(message.content);
|
|
160
|
-
if (message === null)
|
|
161
|
-
gaps.push("user_without_message");
|
|
162
|
-
else if (content !== null) {
|
|
163
|
-
events.push(event(identity, record, byteOffset, 1, "user_message", {
|
|
164
|
-
text: content,
|
|
165
|
-
record,
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
else if (Array.isArray(message.content)) {
|
|
169
|
-
for (const [index, value] of message.content.entries()) {
|
|
170
|
-
const block = object(value);
|
|
171
|
-
if (block === null) {
|
|
172
|
-
gaps.push("invalid_user_content_block");
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (block.type === "tool_result") {
|
|
176
|
-
const callId = string(block.tool_use_id);
|
|
177
|
-
if (callId === null) {
|
|
178
|
-
gaps.push("tool_result_without_call_id");
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
events.push(event(identity, record, byteOffset, index + 1, "tool_result", {
|
|
182
|
-
text: contentText(block.content),
|
|
183
|
-
record,
|
|
184
|
-
}, callId));
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
events.push(event(identity, record, byteOffset, index + 1, "user_message", {
|
|
188
|
-
text: string(block.text) ?? contentText(block.content),
|
|
189
|
-
record,
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
else
|
|
195
|
-
gaps.push("unsupported_user_content");
|
|
196
|
-
}
|
|
197
|
-
else if (recordType === "assistant") {
|
|
198
|
-
const message = object(record.message);
|
|
199
|
-
if (message === null || !Array.isArray(message.content))
|
|
200
|
-
gaps.push("assistant_without_content_blocks");
|
|
201
|
-
else {
|
|
202
|
-
for (const [index, value] of message.content.entries()) {
|
|
203
|
-
const block = object(value);
|
|
204
|
-
if (block === null) {
|
|
205
|
-
gaps.push("invalid_assistant_content_block");
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const blockType = string(block.type);
|
|
209
|
-
const eventType = blockType === "text"
|
|
210
|
-
? "assistant_message"
|
|
211
|
-
: blockType === "thinking" || blockType === "redacted_thinking"
|
|
212
|
-
? "reasoning"
|
|
213
|
-
: blockType === "tool_use"
|
|
214
|
-
? "tool_call"
|
|
215
|
-
: null;
|
|
216
|
-
if (eventType === null) {
|
|
217
|
-
gaps.push(`unsupported_assistant_block:${blockType ?? "unknown"}`);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
const callId = eventType === "tool_call" ? string(block.id) : null;
|
|
221
|
-
if (eventType === "tool_call" && callId === null) {
|
|
222
|
-
gaps.push("tool_call_without_call_id");
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
events.push(event(identity, record, byteOffset, index + 1, eventType, {
|
|
226
|
-
text: string(block.text) ?? string(block.thinking),
|
|
227
|
-
name: string(block.name),
|
|
228
|
-
record,
|
|
229
|
-
}, callId));
|
|
230
|
-
}
|
|
231
|
-
if (object(message.usage) !== null && string(message.id) !== null) {
|
|
232
|
-
events.push(event(identity, record, byteOffset, 9999, "token_usage_record", { model: string(message.model), usage: message.usage }, null, `usage:${message.id}`));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
else if (recordType !== null && CONTEXT_RECORD_TYPES.has(recordType)) {
|
|
237
|
-
const attachment = object(record.attachment);
|
|
238
|
-
events.push(event(identity, record, byteOffset, 1, "source_record", {
|
|
239
|
-
sourceType: recordType,
|
|
240
|
-
subtype: string(record.subtype) ??
|
|
241
|
-
(attachment === null ? null : string(attachment.type)),
|
|
242
|
-
record,
|
|
243
|
-
}));
|
|
244
|
-
}
|
|
245
|
-
else
|
|
246
|
-
gaps.push(`unsupported_claude_record:${recordType ?? "unknown"}`);
|
|
247
|
-
const valid = [];
|
|
248
|
-
for (const candidate of events) {
|
|
249
|
-
const checked = parseTraceEventBatch({ events: [candidate] });
|
|
250
|
-
if (checked.success)
|
|
251
|
-
valid.push(candidate);
|
|
252
|
-
else
|
|
253
|
-
gaps.push(`invalid_normalized_event:${candidate.eventType}`);
|
|
254
|
-
}
|
|
255
|
-
return { events: valid, gaps };
|
|
256
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type CollectorSource } from "iteration-trace-contracts";
|
|
2
|
-
export declare function authenticateAndEnrollCollector(endpoint: URL, deviceNameValue: string, organizationId: string | null, source?: CollectorSource): Promise<Readonly<{
|
|
3
|
-
token: string;
|
|
4
|
-
deviceId: string;
|
|
5
|
-
organizationId: string;
|
|
6
|
-
organizationName: string;
|
|
7
|
-
}>>;
|