@openpond/evals 0.11.18 → 0.11.20

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.
Files changed (140) hide show
  1. package/dist/connected-evidence/contracts.js +15 -5
  2. package/dist/connected-evidence/imports.js +78 -17
  3. package/dist/connected-evidence/normalize.js +18 -7
  4. package/dist/connected-evidence/sources/claude-branches.js +52 -0
  5. package/dist/connected-evidence/sources/claude-code.js +7 -29
  6. package/dist/connected-evidence/sources/codex-authorization.js +40 -0
  7. package/dist/connected-evidence/sources/codex.js +7 -3
  8. package/dist/connected-evidence/sources/grok-build.js +137 -0
  9. package/dist/connected-evidence/sources/opencode.js +101 -0
  10. package/dist/connected-evidence/sources/pi.js +187 -0
  11. package/dist/graders.js +11 -1
  12. package/dist/learning/budgeted-reward-fixtures.js +1 -0
  13. package/dist/learning/contracts.js +10 -0
  14. package/dist/learning/index.js +1 -0
  15. package/dist/learning/model-judge.js +22 -5
  16. package/dist/learning/reward-calibration-closure.js +110 -0
  17. package/dist/learning/reward-calibration.js +20 -10
  18. package/dist/learning/reward-checks.js +1 -0
  19. package/dist/native-conversations/appimage-runtime.js +131 -0
  20. package/dist/native-conversations/collector-branches.js +79 -0
  21. package/dist/native-conversations/collector-contracts.js +9 -0
  22. package/dist/native-conversations/collector-destinations.js +27 -0
  23. package/dist/native-conversations/collector-errors.js +42 -0
  24. package/dist/native-conversations/collector-progress.js +114 -0
  25. package/dist/native-conversations/collector-store.js +251 -0
  26. package/dist/native-conversations/collector-sync-requests.js +52 -0
  27. package/dist/native-conversations/collector.js +329 -0
  28. package/dist/native-conversations/contracts.js +11 -0
  29. package/dist/native-conversations/database.js +149 -0
  30. package/dist/native-conversations/discovery.js +135 -0
  31. package/dist/native-conversations/history.js +327 -0
  32. package/dist/native-conversations/index.js +11 -0
  33. package/dist/native-conversations/location.js +29 -0
  34. package/dist/native-conversations/openclaw-cold-archive.js +91 -0
  35. package/dist/native-conversations/openclaw-database.js +130 -0
  36. package/dist/native-conversations/readiness.js +4 -0
  37. package/dist/native-conversations/source-locations.js +112 -0
  38. package/dist/native-conversations/supervisor.js +208 -0
  39. package/dist/native-conversations/titles.js +38 -0
  40. package/dist/rewards.js +1 -1
  41. package/dist/types/connected-evidence/contracts.d.ts +15 -0
  42. package/dist/types/connected-evidence/contracts.d.ts.map +1 -1
  43. package/dist/types/connected-evidence/imports.d.ts +7 -0
  44. package/dist/types/connected-evidence/imports.d.ts.map +1 -1
  45. package/dist/types/connected-evidence/normalize.d.ts +7 -0
  46. package/dist/types/connected-evidence/normalize.d.ts.map +1 -1
  47. package/dist/types/connected-evidence/sources/claude-branches.d.ts +19 -0
  48. package/dist/types/connected-evidence/sources/claude-branches.d.ts.map +1 -0
  49. package/dist/types/connected-evidence/sources/claude-code.d.ts +5 -1
  50. package/dist/types/connected-evidence/sources/claude-code.d.ts.map +1 -1
  51. package/dist/types/connected-evidence/sources/codex-authorization.d.ts +18 -0
  52. package/dist/types/connected-evidence/sources/codex-authorization.d.ts.map +1 -0
  53. package/dist/types/connected-evidence/sources/codex.d.ts +5 -1
  54. package/dist/types/connected-evidence/sources/codex.d.ts.map +1 -1
  55. package/dist/types/connected-evidence/sources/grok-build.d.ts +79 -0
  56. package/dist/types/connected-evidence/sources/grok-build.d.ts.map +1 -0
  57. package/dist/types/connected-evidence/sources/history.d.ts +10 -2
  58. package/dist/types/connected-evidence/sources/history.d.ts.map +1 -1
  59. package/dist/types/connected-evidence/sources/opencode.d.ts +78 -0
  60. package/dist/types/connected-evidence/sources/opencode.d.ts.map +1 -0
  61. package/dist/types/connected-evidence/sources/pi.d.ts +79 -0
  62. package/dist/types/connected-evidence/sources/pi.d.ts.map +1 -0
  63. package/dist/types/graders.d.ts +81 -0
  64. package/dist/types/graders.d.ts.map +1 -1
  65. package/dist/types/learning/budgeted-reward-fixtures.d.ts +2 -1
  66. package/dist/types/learning/budgeted-reward-fixtures.d.ts.map +1 -1
  67. package/dist/types/learning/contracts.d.ts +167 -0
  68. package/dist/types/learning/contracts.d.ts.map +1 -1
  69. package/dist/types/learning/index.d.ts +1 -0
  70. package/dist/types/learning/index.d.ts.map +1 -1
  71. package/dist/types/learning/judge-budget.d.ts +3 -0
  72. package/dist/types/learning/judge-budget.d.ts.map +1 -1
  73. package/dist/types/learning/model-judge.d.ts +6 -1
  74. package/dist/types/learning/model-judge.d.ts.map +1 -1
  75. package/dist/types/learning/operations.d.ts +44 -0
  76. package/dist/types/learning/operations.d.ts.map +1 -1
  77. package/dist/types/learning/repository.d.ts +81 -0
  78. package/dist/types/learning/repository.d.ts.map +1 -1
  79. package/dist/types/learning/reward-calibration-closure.d.ts +2323 -0
  80. package/dist/types/learning/reward-calibration-closure.d.ts.map +1 -0
  81. package/dist/types/learning/reward-calibration.d.ts +1 -1
  82. package/dist/types/learning/reward-calibration.d.ts.map +1 -1
  83. package/dist/types/learning/reward-checks.d.ts +35 -0
  84. package/dist/types/learning/reward-checks.d.ts.map +1 -1
  85. package/dist/types/learning/transport.d.ts +103 -0
  86. package/dist/types/learning/transport.d.ts.map +1 -1
  87. package/dist/types/native-conversations/appimage-runtime.d.ts +12 -0
  88. package/dist/types/native-conversations/appimage-runtime.d.ts.map +1 -0
  89. package/dist/types/native-conversations/collector-branches.d.ts +22 -0
  90. package/dist/types/native-conversations/collector-branches.d.ts.map +1 -0
  91. package/dist/types/native-conversations/collector-contracts.d.ts +92 -0
  92. package/dist/types/native-conversations/collector-contracts.d.ts.map +1 -0
  93. package/dist/types/native-conversations/collector-destinations.d.ts +18 -0
  94. package/dist/types/native-conversations/collector-destinations.d.ts.map +1 -0
  95. package/dist/types/native-conversations/collector-errors.d.ts +13 -0
  96. package/dist/types/native-conversations/collector-errors.d.ts.map +1 -0
  97. package/dist/types/native-conversations/collector-progress.d.ts +24 -0
  98. package/dist/types/native-conversations/collector-progress.d.ts.map +1 -0
  99. package/dist/types/native-conversations/collector-store.d.ts +31 -0
  100. package/dist/types/native-conversations/collector-store.d.ts.map +1 -0
  101. package/dist/types/native-conversations/collector-sync-requests.d.ts +25 -0
  102. package/dist/types/native-conversations/collector-sync-requests.d.ts.map +1 -0
  103. package/dist/types/native-conversations/collector.d.ts +8 -0
  104. package/dist/types/native-conversations/collector.d.ts.map +1 -0
  105. package/dist/types/native-conversations/contracts.d.ts +47 -0
  106. package/dist/types/native-conversations/contracts.d.ts.map +1 -0
  107. package/dist/types/native-conversations/database.d.ts +14 -0
  108. package/dist/types/native-conversations/database.d.ts.map +1 -0
  109. package/dist/types/native-conversations/discovery.d.ts +9 -0
  110. package/dist/types/native-conversations/discovery.d.ts.map +1 -0
  111. package/dist/types/native-conversations/history.d.ts +23 -0
  112. package/dist/types/native-conversations/history.d.ts.map +1 -0
  113. package/dist/types/native-conversations/index.d.ts +12 -0
  114. package/dist/types/native-conversations/index.d.ts.map +1 -0
  115. package/dist/types/native-conversations/location.d.ts +3 -0
  116. package/dist/types/native-conversations/location.d.ts.map +1 -0
  117. package/dist/types/native-conversations/openclaw-cold-archive.d.ts +8 -0
  118. package/dist/types/native-conversations/openclaw-cold-archive.d.ts.map +1 -0
  119. package/dist/types/native-conversations/openclaw-database.d.ts +14 -0
  120. package/dist/types/native-conversations/openclaw-database.d.ts.map +1 -0
  121. package/dist/types/native-conversations/readiness.d.ts +5 -0
  122. package/dist/types/native-conversations/readiness.d.ts.map +1 -0
  123. package/dist/types/native-conversations/source-locations.d.ts +8 -0
  124. package/dist/types/native-conversations/source-locations.d.ts.map +1 -0
  125. package/dist/types/native-conversations/supervisor.d.ts +21 -0
  126. package/dist/types/native-conversations/supervisor.d.ts.map +1 -0
  127. package/dist/types/native-conversations/titles.d.ts +4 -0
  128. package/dist/types/native-conversations/titles.d.ts.map +1 -0
  129. package/dist/types/rewards.d.ts +50 -0
  130. package/dist/types/rewards.d.ts.map +1 -1
  131. package/dist/types/telemetry-analysis.d.ts +1 -1
  132. package/dist/types/telemetry-bundle.d.ts +2 -2
  133. package/dist/types/telemetry-catalog.d.ts +21 -21
  134. package/dist/types/telemetry.d.ts +2 -2
  135. package/package.json +11 -3
  136. package/schemas/learning/v1/command-request.schema.json +122 -91
  137. package/schemas/learning/v1/decision.schema.json +63 -0
  138. package/schemas/learning/v1/grade.schema.json +91 -0
  139. package/schemas/learning/v1/reward_check.schema.json +91 -0
  140. package/schemas/learning/v1/source.schema.json +28 -0
@@ -3,12 +3,12 @@ import { contentHash } from "@openpond/harness";
3
3
  export const CONNECTED_EVIDENCE_VERSION = "openpond.connectedEvidence.v1";
4
4
  export const CONNECTED_NORMALIZER_VERSION = "connected-evidence-1";
5
5
  export const CONNECTED_EVIDENCE_LIMITS = { files: 100, sourceBytes: 32 * 1024 * 1024, decodedBytes: 64 * 1024 * 1024,
6
- lineBytes: 2 * 1024 * 1024, events: 100_000, sessions: 1_000, boundaries: 5_000, evaluatorBytes: 2 * 1024 * 1024 };
6
+ lineBytes: 64 * 1024 * 1024, events: 100_000, sessions: 1_000, boundaries: 5_000, evaluatorBytes: 2 * 1024 * 1024 };
7
7
  const Id = z.string().min(1).max(500);
8
8
  const Hash = z.string().regex(/^[a-f0-9]{64}$/);
9
9
  const Time = z.string().datetime().nullable();
10
10
  const Tokens = z.number().int().nonnegative().nullable();
11
- export const ConnectedSourceKindSchema = z.enum(["native_chat", "native_work", "codex", "claude_code", "hermes", "openclaw"]);
11
+ export const ConnectedSourceKindSchema = z.enum(["native_chat", "native_work", "codex", "claude_code", "hermes", "openclaw", "opencode", "grok_build", "pi", "oh_my_pi"]);
12
12
  export const ConnectedUsageSchema = z.object({
13
13
  invocationId: Id, inputTokens: Tokens, outputTokens: Tokens, cachedInputTokens: Tokens,
14
14
  totalTokens: Tokens, provenance: z.enum(["billed_invocation", "reported_invocation", "reported_cumulative"]),
@@ -33,6 +33,7 @@ export const ConnectedSessionSchema = z.object({
33
33
  schemaVersion: z.literal(CONNECTED_EVIDENCE_VERSION), normalizerVersion: z.literal(CONNECTED_NORMALIZER_VERSION),
34
34
  origin: ConnectedSourceKindSchema, sessionId: Id, branchId: Id.nullable(), parentSessionId: Id.nullable(),
35
35
  exporterVersion: z.string().max(200).nullable(),
36
+ acquisition: z.object({ machineId: Id, sourceInstanceId: Id }).strict().optional(),
36
37
  sourceFiles: z.array(z.object({ path: Id, contentHash: Hash, sizeBytes: z.number().int().nonnegative() }).strict()).max(CONNECTED_EVIDENCE_LIMITS.files),
37
38
  events: z.array(ConnectedEventSchema).max(CONNECTED_EVIDENCE_LIMITS.events),
38
39
  unmappedEvents: z.array(ConnectedEventSchema).max(CONNECTED_EVIDENCE_LIMITS.events),
@@ -48,8 +49,8 @@ export function hasRecordedConnectedAnswer(event) {
48
49
  return event.content.length > 0;
49
50
  return typeof event.content !== "object" || !("text" in event.content) || typeof event.content.text === "string" && event.content.text.trim().length > 0;
50
51
  }
51
- /** Bytes stay in one immutable snapshot; cases retain cutoffs, not copied history. */
52
- export function resolveConnectedBoundary(sessionValue, boundaryId) {
52
+ /** Shared integrity verification for metadata and evaluator evidence. */
53
+ function verifiedConnectedBoundary(sessionValue, boundaryId) {
53
54
  const session = ConnectedSessionSchema.parse(sessionValue);
54
55
  const { contentHash: actual, ...body } = session;
55
56
  if (contentHash(body) !== actual)
@@ -65,7 +66,16 @@ export function resolveConnectedBoundary(sessionValue, boundaryId) {
65
66
  const answer = boundary.projection === "conversation" ? answers.map(event => event.content) : answers.at(-1)?.content ?? null;
66
67
  if ((answers.length ? contentHash(answer) : null) !== boundary.outputHash)
67
68
  throw new Error("connected_output_hash_mismatch");
68
- const value = { input, observed, answer, boundary };
69
+ return { input, observed, answer, boundary };
70
+ }
71
+ /** Status/discovery returns no evaluator payload. Large retained histories still
72
+ * require the exact session, input, observed-output and answer hashes to match. */
73
+ export function resolveConnectedBoundaryMetadata(sessionValue, boundaryId) {
74
+ return verifiedConnectedBoundary(sessionValue, boundaryId).boundary;
75
+ }
76
+ /** Bytes stay in one immutable snapshot; cases retain cutoffs, not copied history. */
77
+ export function resolveConnectedBoundary(sessionValue, boundaryId) {
78
+ const value = verifiedConnectedBoundary(sessionValue, boundaryId);
69
79
  if (new TextEncoder().encode(JSON.stringify(value)).length > CONNECTED_EVIDENCE_LIMITS.evaluatorBytes)
70
80
  throw new Error("connected_evaluator_context_too_large");
71
81
  return value;
@@ -1,17 +1,32 @@
1
1
  import { contentHash } from "@openpond/harness";
2
- import { CONNECTED_EVIDENCE_LIMITS } from "./contracts.js";
3
- import { connectedJsonLines } from "./normalize.js";
2
+ import { CONNECTED_EVIDENCE_LIMITS, CONNECTED_NORMALIZER_VERSION, } from "./contracts.js";
3
+ import { connectedJsonLines, normalizeConnectedSession, ConnectedSessionWithoutUserRequestError } from "./normalize.js";
4
+ import { parseGrokBuildSession } from "./sources/grok-build.js";
5
+ import { parsePiSession } from "./sources/pi.js";
6
+ import { parseOpenCodeSession } from "./sources/opencode.js";
7
+ import { CODEX_AUTHORIZATION_PROJECTION_POLICY } from "./sources/codex-authorization.js";
4
8
  import { parseCodexSession } from "./sources/codex.js";
5
9
  import { parseClaudeSession } from "./sources/claude-code.js";
6
- import { parseHermesEvidence, parseOpenClawEvidence } from "./sources/history.js";
10
+ import { parseHermesEvidence, parseOpenClawEvidence, } from "./sources/history.js";
11
+ /** Cache identity includes source projection policy, independent of file mtimes. */
12
+ export function agentImportNormalizerRevision(source) {
13
+ return source === "codex" ? `${CONNECTED_NORMALIZER_VERSION}/${CODEX_AUTHORIZATION_PROJECTION_POLICY}` : CONNECTED_NORMALIZER_VERSION;
14
+ }
7
15
  /** Never admits malformed files partly or executes referenced paths/commands. */
8
16
  export function previewAgentImport(input) {
9
- if (!input.files.length || input.files.length > CONNECTED_EVIDENCE_LIMITS.files)
17
+ if (!input.files.length ||
18
+ input.files.length > CONNECTED_EVIDENCE_LIMITS.files)
10
19
  throw new Error("Choose between 1 and 100 source files.");
11
20
  const paths = new Set();
12
21
  let bytes = 0;
13
22
  for (const file of input.files) {
14
- if (file.path.length > 500 || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some(part => !part || part === "." || part === "..") || paths.has(file.path))
23
+ if (file.path.length > 500 ||
24
+ file.path.startsWith("/") ||
25
+ file.path.includes("\\") ||
26
+ file.path
27
+ .split("/")
28
+ .some((part) => !part || part === "." || part === "..") ||
29
+ paths.has(file.path))
15
30
  throw new Error("Unsafe or duplicate source file path.");
16
31
  paths.add(file.path);
17
32
  bytes += new TextEncoder().encode(file.text).length;
@@ -21,22 +36,48 @@ export function previewAgentImport(input) {
21
36
  const sessions = [], issues = [];
22
37
  const attempt = (file, parse) => {
23
38
  try {
24
- sessions.push(...parse());
39
+ sessions.push(...parse().map((session) => input.acquisition
40
+ ? normalizeConnectedSession({
41
+ origin: session.origin,
42
+ sessionId: session.sessionId,
43
+ branchId: session.branchId,
44
+ parentSessionId: session.parentSessionId,
45
+ exporterVersion: session.exporterVersion,
46
+ files: input.files.filter((item) => session.sourceFiles.some((file) => file.path === item.path)),
47
+ events: session.events,
48
+ unmappedEvents: session.unmappedEvents,
49
+ warnings: session.warnings,
50
+ contextComplete: !session.boundaries.some((boundary) => boundary.coverage.context === "unknown"),
51
+ acquisition: input.acquisition,
52
+ })
53
+ : session));
25
54
  }
26
55
  catch (error) {
27
- issues.push({ file, message: error instanceof Error ? error.message : "Unsupported session." });
56
+ issues.push({
57
+ file,
58
+ ...(error instanceof ConnectedSessionWithoutUserRequestError ? { code: "no_user_request" } : {}),
59
+ message: error instanceof Error ? error.message : "Unsupported session.",
60
+ });
28
61
  }
29
62
  };
30
- if (input.source === "openclaw")
63
+ if (input.source === "openclaw" &&
64
+ input.files.some((file) => file.path.endsWith("manifest.json")))
31
65
  attempt("manifest.json", () => {
32
66
  const root = input.files[0].path.split("/")[0];
33
- const files = !input.files.some(file => file.path === "manifest.json") && input.files.every(file => file.path.startsWith(`${root}/`))
34
- ? input.files.map(file => ({ ...file, path: file.path.slice(root.length + 1) })) : input.files;
67
+ const files = !input.files.some((file) => file.path === "manifest.json") &&
68
+ input.files.every((file) => file.path.startsWith(`${root}/`))
69
+ ? input.files.map((file) => ({
70
+ ...file,
71
+ path: file.path.slice(root.length + 1),
72
+ }))
73
+ : input.files;
35
74
  return [parseOpenClawEvidence(files)];
36
75
  });
37
76
  else
38
77
  for (const file of input.files)
39
78
  attempt(file.path, () => {
79
+ if (input.source === "opencode")
80
+ return [parseOpenCodeSession(file, JSON.parse(file.text))];
40
81
  let rows;
41
82
  if (input.source === "hermes" && file.path.endsWith(".json")) {
42
83
  const value = JSON.parse(file.text.replace(/^\uFEFF/u, ""));
@@ -46,26 +87,46 @@ export function previewAgentImport(input) {
46
87
  }
47
88
  else
48
89
  rows = connectedJsonLines(file);
90
+ if (input.source === "grok_build")
91
+ return [parseGrokBuildSession(file, rows)];
92
+ if (input.source === "pi" ||
93
+ input.source === "oh_my_pi" ||
94
+ input.source === "openclaw")
95
+ return [parsePiSession(file, rows, input.source, input.branchLeafId)];
49
96
  if (input.source === "codex")
50
97
  return [parseCodexSession(file, rows)];
51
98
  if (input.source === "claude_code")
52
99
  return [parseClaudeSession(file, rows, input.branchLeafId)];
53
100
  if (input.source === "hermes")
54
- return rows.map(row => parseHermesEvidence(file, row));
101
+ return rows.map((row) => parseHermesEvidence(file, row));
55
102
  throw new Error("Unsupported source adapter.");
56
103
  });
57
- if (sessions.length > CONNECTED_EVIDENCE_LIMITS.sessions || sessions.reduce((total, session) => total + session.events.length + session.unmappedEvents.length, 0) > CONNECTED_EVIDENCE_LIMITS.events
58
- || sessions.reduce((total, session) => total + session.boundaries.length, 0) > CONNECTED_EVIDENCE_LIMITS.boundaries)
104
+ if (sessions.length > CONNECTED_EVIDENCE_LIMITS.sessions ||
105
+ sessions.reduce((total, session) => total + session.events.length + session.unmappedEvents.length, 0) > CONNECTED_EVIDENCE_LIMITS.events ||
106
+ sessions.reduce((total, session) => total + session.boundaries.length, 0) >
107
+ CONNECTED_EVIDENCE_LIMITS.boundaries)
59
108
  throw new Error("Import exceeds the session/event/boundary limit.");
60
109
  const identities = new Map();
61
110
  for (const session of sessions) {
62
- const key = JSON.stringify([session.origin, session.sessionId, session.branchId]);
111
+ const key = JSON.stringify([
112
+ session.origin,
113
+ session.sessionId,
114
+ session.branchId,
115
+ ]);
63
116
  const prior = identities.get(key);
64
117
  if (prior && prior !== session.contentHash)
65
118
  throw new Error("The same session has different contents in this selection. Choose one export.");
66
119
  identities.set(key, session.contentHash);
67
120
  }
68
- return { schemaVersion: "openpond.agentImportPreview.v1", source: input.source,
69
- sourceHash: contentHash({ source: input.source, files: [...input.files].sort((a, b) => a.path.localeCompare(b.path)), branchLeafId: input.branchLeafId ?? null }),
70
- sessions: sessions.filter((session, index) => sessions.findIndex(other => other.contentHash === session.contentHash) === index), issues };
121
+ return {
122
+ schemaVersion: "openpond.agentImportPreview.v1",
123
+ source: input.source,
124
+ sourceHash: contentHash({
125
+ source: input.source,
126
+ files: [...input.files].sort((a, b) => a.path.localeCompare(b.path)),
127
+ branchLeafId: input.branchLeafId ?? null,
128
+ }),
129
+ sessions: sessions.filter((session, index) => sessions.findIndex((other) => other.contentHash === session.contentHash) === index),
130
+ issues,
131
+ };
71
132
  }
@@ -1,5 +1,8 @@
1
- import { contentHash } from "@openpond/harness";
1
+ import { contentHash, contentHashArrayPrefixes } from "@openpond/harness";
2
2
  import { CONNECTED_EVIDENCE_LIMITS, CONNECTED_EVIDENCE_VERSION, CONNECTED_NORMALIZER_VERSION, ConnectedSessionSchema, hasRecordedConnectedAnswer } from "./contracts.js";
3
+ export class ConnectedSessionWithoutUserRequestError extends Error {
4
+ constructor() { super("The session has no retained user request."); }
5
+ }
3
6
  export function connectedTimestamp(value, units = "milliseconds") {
4
7
  if (typeof value !== "number" && typeof value !== "string")
5
8
  return null;
@@ -17,10 +20,12 @@ export function normalizeConnectedSession(input) {
17
20
  throw new Error("connected_duplicate_event");
18
21
  const starts = events.flatMap((event, index) => event.kind === "message" && event.role === "user" ? [index] : []);
19
22
  if (!starts.length)
20
- throw new Error("The session has no retained user request.");
23
+ throw new ConnectedSessionWithoutUserRequestError();
21
24
  if (starts.length + (input.includeConversation === false ? 0 : 1) > CONNECTED_EVIDENCE_LIMITS.boundaries)
22
25
  throw new Error("connected_boundary_limit");
23
- const familyKey = `family-${contentHash([input.origin, input.parentSessionId ?? input.sessionId]).slice(0, 32)}`;
26
+ const familyKey = `family-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.parentSessionId ?? input.sessionId]).slice(0, 32)}`;
27
+ const inputHashes = contentHashArrayPrefixes(events, starts.map(start => start + 1));
28
+ const firstCompaction = events.findIndex(event => event.kind === "compaction");
24
29
  const boundaries = starts.map((start, position) => {
25
30
  const end = starts[position + 1] ?? events.length;
26
31
  const observed = events.slice(start + 1, end);
@@ -32,12 +37,12 @@ export function normalizeConnectedSession(input) {
32
37
  const terminal = [...observed].reverse().find(event => event.kind === "terminal");
33
38
  const status = terminal && typeof terminal.content === "object" && terminal.content !== null && !Array.isArray(terminal.content)
34
39
  ? terminal.content.status : null;
35
- const compacted = events.slice(0, start + 1).some(event => event.kind === "compaction");
40
+ const compacted = firstCompaction >= 0 && firstCompaction <= start;
36
41
  const unknown = observed.some(event => event.kind === "unknown");
37
42
  return {
38
- id: `case-${contentHash([input.origin, input.sessionId, input.branchId ?? null, events[start].id]).slice(0, 40)}`,
43
+ id: `case-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.sessionId, input.branchId ?? null, events[start].id]).slice(0, 40)}`,
39
44
  familyKey, projection: "turn", requestEventId: events[start].id, start, end,
40
- inputHash: contentHash(events.slice(0, start + 1)), outputHash: answer ? contentHash(answer.content) : null,
45
+ inputHash: inputHashes[position], outputHash: answer ? contentHash(answer.content) : null,
41
46
  revisionHash: contentHash(observed), terminal: status === "completed" || status === "failed" || status === "cancelled" ? status : "unknown",
42
47
  coverage: { answer: Boolean(answer), context: input.contextComplete === false ? "unknown" : compacted ? "compacted" : "retained",
43
48
  process: !tools.length ? unknown ? "partial" : "absent" : incomplete || unknown ? "partial" : "retained",
@@ -50,7 +55,7 @@ export function normalizeConnectedSession(input) {
50
55
  if (input.includeConversation !== false) {
51
56
  const first = boundaries[0], last = boundaries.at(-1);
52
57
  const observed = events.slice(first.start + 1), answers = observed.filter(hasRecordedConnectedAnswer);
53
- boundaries.push({ ...first, id: `conversation-${contentHash([input.origin, input.sessionId, input.branchId ?? null]).slice(0, 40)}`,
58
+ boundaries.push({ ...first, id: `conversation-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.sessionId, input.branchId ?? null]).slice(0, 40)}`,
54
59
  projection: "conversation", end: events.length, outputHash: answers.length ? contentHash(answers.map(event => event.content)) : null,
55
60
  revisionHash: contentHash(observed), terminal: last.terminal, coverage: { ...first.coverage,
56
61
  answer: answers.length > 0, process: boundaries.some(item => item.coverage.process === "partial") ? "partial" : boundaries.some(item => item.coverage.process === "retained") ? "retained" : "absent",
@@ -61,6 +66,7 @@ export function normalizeConnectedSession(input) {
61
66
  const body = { schemaVersion: CONNECTED_EVIDENCE_VERSION, normalizerVersion: CONNECTED_NORMALIZER_VERSION,
62
67
  origin: input.origin, sessionId: input.sessionId, branchId: input.branchId ?? null, parentSessionId: input.parentSessionId ?? null,
63
68
  exporterVersion: input.exporterVersion ?? null, sourceFiles: input.files.map(connectedFileIdentity).sort((a, b) => a.path.localeCompare(b.path)),
69
+ ...(input.acquisition ? { acquisition: input.acquisition } : {}),
64
70
  events, unmappedEvents: (input.unmappedEvents ?? []).map((event, sequence) => ({ ...event, sequence })), boundaries, warnings: input.warnings ?? [] };
65
71
  const session = ConnectedSessionSchema.parse({ ...body, contentHash: contentHash(body) });
66
72
  if (new TextEncoder().encode(JSON.stringify(session)).length > CONNECTED_EVIDENCE_LIMITS.decodedBytes)
@@ -68,6 +74,11 @@ export function normalizeConnectedSession(input) {
68
74
  return session;
69
75
  }
70
76
  export function connectedJsonLines(file) {
77
+ // Native tool results and image blocks can occupy one large JSONL record.
78
+ // Bound the whole decoded input before parsing, rather than rejecting valid
79
+ // evidence solely because its exporter uses a single physical line.
80
+ if (new TextEncoder().encode(file.text).length > CONNECTED_EVIDENCE_LIMITS.decodedBytes)
81
+ throw new Error("Decoded source files exceed 64 MiB.");
71
82
  const lines = file.text.replace(/^\uFEFF/u, "").split(/\r?\n/u);
72
83
  if (lines.length > CONNECTED_EVIDENCE_LIMITS.events + 1)
73
84
  throw new Error("connected_event_limit");
@@ -0,0 +1,52 @@
1
+ import { contentHash } from "@openpond/harness";
2
+ import { connectedTimestamp, object } from "../normalize.js";
3
+ /** One native graph owns selection, import and Desktop inspection semantics. */
4
+ export function claudeGraph(rows) {
5
+ const messages = rows.filter(row => (row.type === "user" || row.type === "assistant") && typeof row.uuid === "string");
6
+ const sessions = new Set(messages.flatMap(row => typeof row.sessionId === "string" ? [row.sessionId] : []));
7
+ if (sessions.size !== 1 || !messages.length)
8
+ throw new Error("A supported Claude Code transcript must identify one session with UUID messages.");
9
+ const sessionId = [...sessions][0];
10
+ const nodes = rows.filter(row => typeof row.uuid === "string" && row.sessionId === sessionId);
11
+ const byId = new Map(nodes.map(row => [row.uuid, row]));
12
+ if (byId.size !== nodes.length)
13
+ throw new Error("Claude Code transcript contains duplicate message UUIDs.");
14
+ const parents = new Set(nodes.flatMap(row => typeof row.parentUuid === "string" ? [row.parentUuid] : []));
15
+ const leaves = nodes.filter(row => !parents.has(row.uuid));
16
+ if (!leaves.length)
17
+ throw new Error("Claude Code transcript has no selectable branch leaf.");
18
+ function select(leafId) {
19
+ let current = byId.get(leafId);
20
+ if (!current)
21
+ throw new Error("The selected Claude Code branch leaf is missing.");
22
+ const chain = [], seen = new Set();
23
+ let missingParent = false;
24
+ while (current) {
25
+ const id = current.uuid;
26
+ if (seen.has(id))
27
+ throw new Error("Claude Code branch contains a parent cycle.");
28
+ seen.add(id);
29
+ chain.unshift(current);
30
+ const parent = current.parentUuid;
31
+ if (typeof parent !== "string")
32
+ break;
33
+ current = byId.get(parent);
34
+ if (!current)
35
+ missingParent = true;
36
+ }
37
+ return { chain, missingParent, chainHash: contentHash(chain) };
38
+ }
39
+ function branches() {
40
+ if (leaves.length > 256)
41
+ throw new Error("This transcript exceeds the 256-branch inspection limit.");
42
+ return leaves.map(leaf => {
43
+ const selected = select(leaf.uuid);
44
+ const messages = selected.chain.filter(row => row.type === "user" || row.type === "assistant");
45
+ const last = messages.at(-1);
46
+ const content = object(last?.message).content;
47
+ const title = typeof content === "string" ? content : Array.isArray(content) ? content.flatMap(block => typeof object(block).text === "string" ? [object(block).text] : []).join(" ") : "";
48
+ return { leafId: leaf.uuid, chainHash: selected.chainHash, title: title.slice(0, 180), updatedAt: connectedTimestamp(last?.timestamp), messages: messages.length };
49
+ });
50
+ }
51
+ return { sessionId, leaves, select, branches };
52
+ }
@@ -1,38 +1,16 @@
1
+ import { claudeGraph } from "./claude-branches.js";
1
2
  import { contentHash } from "@openpond/harness";
2
3
  import { connectedTimestamp, event, json, normalizeConnectedSession, object } from "../normalize.js";
3
4
  import { sourceUsage } from "./usage.js";
4
5
  export function parseClaudeSession(file, rows, leafId) {
5
- const messages = rows.filter(row => (row.type === "user" || row.type === "assistant") && typeof row.uuid === "string");
6
- const sessionIds = new Set(messages.flatMap(row => typeof row.sessionId === "string" ? [row.sessionId] : []));
7
- if (sessionIds.size !== 1 || !messages.length)
8
- throw new Error("A supported Claude Code transcript must identify one session with UUID messages.");
9
- const byId = new Map(messages.map(row => [row.uuid, row]));
10
- if (byId.size !== messages.length)
11
- throw new Error("Claude Code transcript contains duplicate message UUIDs.");
12
- const parents = new Set(messages.flatMap(row => typeof row.parentUuid === "string" ? [row.parentUuid] : []));
13
- const leaves = messages.filter(row => !parents.has(row.uuid));
14
- if (!leafId && leaves.length !== 1)
6
+ const graph = claudeGraph(rows);
7
+ if (!leafId && graph.leaves.length !== 1)
15
8
  throw new Error("This transcript has multiple branches. Select a branch leaf UUID before importing.");
16
- let current = byId.get(leafId ?? leaves[0].uuid);
17
- if (!current)
18
- throw new Error("The selected Claude Code branch leaf is missing.");
19
- const chain = [], seen = new Set();
20
- let missingParent = false;
21
- while (current) {
22
- const id = current.uuid;
23
- if (seen.has(id))
24
- throw new Error("Claude Code branch contains a parent cycle.");
25
- seen.add(id);
26
- chain.unshift(current);
27
- const parent = current.parentUuid;
28
- if (typeof parent !== "string")
29
- break;
30
- current = byId.get(parent);
31
- if (!current)
32
- missingParent = true;
33
- }
9
+ const { chain, missingParent } = graph.select(leafId ?? graph.leaves[0].uuid);
34
10
  const events = [];
35
11
  for (const row of chain) {
12
+ if (row.type !== "user" && row.type !== "assistant")
13
+ continue;
36
14
  const message = object(row.message), content = message.content;
37
15
  const base = { occurredAt: connectedTimestamp(row.timestamp), parentId: typeof row.parentUuid === "string" ? row.parentUuid : null };
38
16
  const role = row.type === "user" ? "user" : "assistant";
@@ -61,7 +39,7 @@ export function parseClaudeSession(file, rows, leafId) {
61
39
  }
62
40
  if (rows.some(row => row.type === "system" && row.subtype === "compact_boundary"))
63
41
  events.unshift(event({ id: `compaction-${contentHash(rows.filter(row => row.subtype === "compact_boundary")).slice(0, 32)}`, kind: "compaction", content: { source: "claude_compact_boundary" } }));
64
- return normalizeConnectedSession({ origin: "claude_code", sessionId: [...sessionIds][0],
42
+ return normalizeConnectedSession({ origin: "claude_code", sessionId: graph.sessionId,
65
43
  // A chosen leaf is a cutoff, not a new family or the identity of every turn.
66
44
  branchId: null, parentSessionId: typeof chain[0]?.parentSessionId === "string" ? chain[0].parentSessionId : null,
67
45
  exporterVersion: typeof chain.at(-1)?.version === "string" ? chain.at(-1).version : null,
@@ -0,0 +1,40 @@
1
+ import { contentHash } from "@openpond/harness";
2
+ import { object } from "../normalize.js";
3
+ export const CODEX_AUTHORIZATION_PROJECTION_POLICY = "codex-native-authorization-v1";
4
+ const POLICY = CODEX_AUTHORIZATION_PROJECTION_POLICY;
5
+ /** Native approved-command rules are account authority, not observed tool calls.
6
+ * Retain all other context and the original file/event identity. Unknown formats
7
+ * remain untouched for ordinary privacy admission; this is not secret scrubbing.
8
+ */
9
+ export function projectCodexAuthorization(row) {
10
+ const omissions = [];
11
+ const omit = (sourcePath, value, entries) => {
12
+ const receipt = { policy: POLICY, sourcePath, sourceHash: contentHash(value), entries };
13
+ omissions.push(receipt);
14
+ return receipt;
15
+ };
16
+ const permissionsText = (text, path) => text.replace(/<permissions instructions>[\s\S]*?<\/permissions instructions>/gu, block => block.replace(/^## Approved command prefixes\r?\n[\s\S]*?(?=^## |<\/permissions instructions>)/gmu, section => {
17
+ const receipt = omit(path, section, null);
18
+ return `## Approved command prefixes\n[Native authorization rules omitted; policy ${receipt.policy}; source hash ${receipt.sourceHash}]\n`;
19
+ }));
20
+ const payload = object(row.payload);
21
+ if (row.type === "response_item" && payload.type === "message" && ["system", "developer"].includes(String(payload.role))) {
22
+ const content = typeof payload.content === "string"
23
+ ? permissionsText(payload.content, "/payload/content")
24
+ : Array.isArray(payload.content) ? payload.content.map((value, index) => {
25
+ const block = object(value);
26
+ return typeof block.text === "string"
27
+ ? { ...block, text: permissionsText(block.text, `/payload/content/${index}/text`) }
28
+ : value;
29
+ }) : payload.content;
30
+ return { row: { ...row, payload: { ...payload, content } }, omissions };
31
+ }
32
+ if (row.type === "world_state") {
33
+ const state = object(payload.state), permissions = object(state.permissions);
34
+ if (Array.isArray(permissions.approved_command_prefixes)) {
35
+ const receipt = omit("/payload/state/permissions/approved_command_prefixes", permissions.approved_command_prefixes, permissions.approved_command_prefixes.length);
36
+ return { row: { ...row, payload: { ...payload, state: { ...state, permissions: { ...permissions, approved_command_prefixes: { omitted: "native_authorization_state", ...receipt } } } } }, omissions };
37
+ }
38
+ }
39
+ return { row, omissions };
40
+ }
@@ -1,5 +1,6 @@
1
1
  import { contentHash } from "@openpond/harness";
2
2
  import { connectedTimestamp, event, json, normalizeConnectedSession, object } from "../normalize.js";
3
+ import { projectCodexAuthorization } from "./codex-authorization.js";
3
4
  import { sourceUsage } from "./usage.js";
4
5
  export function parseCodexSession(file, rows) {
5
6
  const metadata = object(rows.find(row => row.type === "session_meta")?.payload);
@@ -7,8 +8,11 @@ export function parseCodexSession(file, rows) {
7
8
  throw new Error("This is not a supported Codex rollout: session_meta.id is missing.");
8
9
  const events = [];
9
10
  let turnId = null;
11
+ let authorizationOmissions = 0;
10
12
  for (const [ordinal, row] of rows.entries()) {
11
- const payload = object(row.payload);
13
+ const projected = projectCodexAuthorization(row);
14
+ authorizationOmissions += projected.omissions.length;
15
+ const payload = object(projected.row.payload);
12
16
  if (row.type === "session_meta")
13
17
  continue;
14
18
  if (row.type === "turn_context") {
@@ -44,9 +48,9 @@ export function parseCodexSession(file, rows) {
44
48
  events.push(event({ ...base, kind: "unknown", content: json(payload) }));
45
49
  }
46
50
  else
47
- events.push(event({ ...base, kind: "unknown", content: json(row) }));
51
+ events.push(event({ ...base, kind: "unknown", content: json(projected.row) }));
48
52
  }
49
53
  return normalizeConnectedSession({ origin: "codex", sessionId: metadata.id, parentSessionId: typeof metadata.forked_from_id === "string" ? metadata.forked_from_id : null,
50
54
  exporterVersion: typeof metadata.cli_version === "string" ? metadata.cli_version : null, files: [file], events,
51
- warnings: ["Codex cumulative usage is retained as reported totals and is not summed as billed invocations.", "Referenced files are unavailable unless separately admitted."] });
55
+ warnings: ["Codex cumulative usage is retained as reported totals and is not summed as billed invocations.", "Referenced files are unavailable unless separately admitted.", ...(authorizationOmissions ? [`Codex native authorization state excluded by codex-native-authorization-v1 from ${authorizationOmissions} locations. Omission markers retain source-section hashes; sourceFiles retains original byte hashes. Other conversation evidence is unchanged and still requires privacy admission.`] : [])] });
52
56
  }
@@ -0,0 +1,137 @@
1
+ import { connectedTimestamp, event, json, normalizeConnectedSession, object, } from "../normalize.js";
2
+ import { sourceUsage } from "./usage.js";
3
+ /** Retained ACP updates are native Grok history; Markdown exports are not accepted. */
4
+ export function parseGrokBuildSession(file, rows) {
5
+ const sessions = new Set(rows
6
+ .map((row) => object(row.params).sessionId)
7
+ .filter((value) => typeof value === "string"));
8
+ if (sessions.size !== 1)
9
+ throw new Error("Grok Build updates must identify exactly one retained session.");
10
+ const events = [], ids = new Set(), calls = new Map();
11
+ let user = null, answer = null, completed = true;
12
+ for (const row of rows) {
13
+ const params = object(row.params), update = object(params.update), metadata = object(params._meta), kind = update.sessionUpdate;
14
+ if (typeof metadata.eventId !== "string" || ids.has(metadata.eventId))
15
+ throw new Error("Grok Build updates require unique native event IDs.");
16
+ ids.add(metadata.eventId);
17
+ const id = metadata.eventId, time = connectedTimestamp(metadata.agentTimestampMs ??
18
+ (typeof row.timestamp === "number" ? row.timestamp * 1000 : null));
19
+ const content = object(update.content);
20
+ if (kind === "user_message_chunk") {
21
+ if (content.type !== "text" || typeof content.text !== "string")
22
+ throw new Error("Grok user content type requires a qualified adapter.");
23
+ if (!user || completed) {
24
+ user = event({
25
+ id,
26
+ kind: "message",
27
+ role: "user",
28
+ occurredAt: time,
29
+ content: content.text,
30
+ });
31
+ events.push(user);
32
+ answer = null;
33
+ completed = false;
34
+ }
35
+ else
36
+ user.content = String(user.content) + content.text;
37
+ }
38
+ else if (kind === "agent_message_chunk") {
39
+ if (content.type !== "text" || typeof content.text !== "string") {
40
+ events.push(event({
41
+ id,
42
+ kind: "unknown",
43
+ occurredAt: time,
44
+ content: json(update),
45
+ }));
46
+ continue;
47
+ }
48
+ if (!answer) {
49
+ answer = event({
50
+ id,
51
+ kind: "message",
52
+ role: "assistant",
53
+ occurredAt: time,
54
+ content: content.text,
55
+ });
56
+ events.push(answer);
57
+ }
58
+ else
59
+ answer.content = String(answer.content) + content.text;
60
+ }
61
+ else if (kind === "tool_call" || kind === "tool_call_update") {
62
+ const callId = typeof update.toolCallId === "string" ? update.toolCallId : null;
63
+ if (!callId) {
64
+ events.push(event({
65
+ id,
66
+ kind: "unknown",
67
+ occurredAt: time,
68
+ content: json(update),
69
+ }));
70
+ continue;
71
+ }
72
+ let call = calls.get(callId);
73
+ if (!call) {
74
+ call = event({
75
+ id: `${id}:call`,
76
+ kind: "tool_call",
77
+ callId,
78
+ occurredAt: time,
79
+ content: json({ title: update.title, input: update.rawInput }),
80
+ });
81
+ calls.set(callId, call);
82
+ events.push(call);
83
+ }
84
+ else if (update.rawInput !== undefined)
85
+ call.content = json({ title: update.title, input: update.rawInput });
86
+ if (update.status === "completed" || update.status === "failed")
87
+ events.push(event({
88
+ id: `${id}:result`,
89
+ kind: "tool_result",
90
+ role: "tool",
91
+ callId,
92
+ occurredAt: time,
93
+ content: json({
94
+ status: update.status,
95
+ output: update.rawOutput,
96
+ content: update.content,
97
+ }),
98
+ }));
99
+ }
100
+ else if (kind === "turn_completed") {
101
+ const usage = object(update.usage), invocationId = typeof update.prompt_id === "string" ? update.prompt_id : id;
102
+ events.push(event({
103
+ id,
104
+ kind: "terminal",
105
+ occurredAt: time,
106
+ content: {
107
+ status: update.stop_reason === "end_turn"
108
+ ? "completed"
109
+ : update.stop_reason === "cancelled"
110
+ ? "cancelled"
111
+ : "failed",
112
+ },
113
+ usage: sourceUsage({
114
+ input_tokens: usage.inputTokens,
115
+ output_tokens: usage.outputTokens,
116
+ cached_input_tokens: usage.cachedReadTokens,
117
+ total_tokens: usage.totalTokens,
118
+ }, invocationId),
119
+ }));
120
+ completed = true;
121
+ answer = null;
122
+ user = null;
123
+ }
124
+ else if (kind !== "agent_thought_chunk")
125
+ events.push(event({ id, kind: "unknown", occurredAt: time, content: json(update) }));
126
+ }
127
+ return normalizeConnectedSession({
128
+ origin: "grok_build",
129
+ sessionId: String([...sessions][0]),
130
+ files: [file],
131
+ events,
132
+ contextComplete: false,
133
+ warnings: [
134
+ "Grok retained ACP updates omit system context and encrypted reasoning. Tool coverage reflects only logged updates.",
135
+ ],
136
+ });
137
+ }