trajectories 0.0.0 → 0.1.1
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/CHANGELOG.md +23 -0
- package/LICENSE +201 -0
- package/README.md +274 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +8542 -0
- package/dist/convert.d.ts +31 -0
- package/dist/convert.js +431 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8402 -0
- package/dist/log-baseline.d.ts +26 -0
- package/dist/log-baseline.js +104 -0
- package/dist/log-content.d.ts +16 -0
- package/dist/log-content.js +80 -0
- package/dist/log-io.d.ts +10 -0
- package/dist/log-io.js +121 -0
- package/dist/log-read.d.ts +24 -0
- package/dist/log-read.js +117 -0
- package/dist/log-reconcile.d.ts +5 -0
- package/dist/log-reconcile.js +38 -0
- package/dist/log-records.d.ts +35 -0
- package/dist/log-records.js +85 -0
- package/dist/log-recovery.d.ts +18 -0
- package/dist/log-recovery.js +123 -0
- package/dist/log-stage.d.ts +14 -0
- package/dist/log-stage.js +138 -0
- package/dist/log-state.d.ts +89 -0
- package/dist/log-state.js +92 -0
- package/dist/log-switch.d.ts +3 -0
- package/dist/log-switch.js +98 -0
- package/dist/log-types.d.ts +129 -0
- package/dist/log-types.js +8 -0
- package/dist/log.d.ts +8 -0
- package/dist/log.js +62 -0
- package/dist/migrate.d.ts +29 -0
- package/dist/migrate.js +173 -0
- package/dist/normalization-runtime.d.ts +1 -0
- package/dist/normalization-runtime.js +3150 -0
- package/dist/portable.d.ts +9 -0
- package/dist/portable.js +26 -0
- package/dist/readers/canonical.d.ts +13 -0
- package/dist/readers/canonical.js +54 -0
- package/dist/readers/cline.d.ts +10 -0
- package/dist/readers/cline.js +107 -0
- package/dist/readers/cursor.d.ts +20 -0
- package/dist/readers/cursor.js +374 -0
- package/dist/readers/deepagents.d.ts +41 -0
- package/dist/readers/deepagents.js +163 -0
- package/dist/readers/fx.d.ts +11 -0
- package/dist/readers/fx.js +573 -0
- package/dist/readers/github-copilot.d.ts +8 -0
- package/dist/readers/github-copilot.js +216 -0
- package/dist/readers/grok-build.d.ts +12 -0
- package/dist/readers/grok-build.js +391 -0
- package/dist/readers/guards.d.ts +4 -0
- package/dist/readers/guards.js +121 -0
- package/dist/readers/pi-compaction.d.ts +7 -0
- package/dist/readers/pi-compaction.js +112 -0
- package/dist/readers/pi-context.d.ts +13 -0
- package/dist/readers/pi-context.js +64 -0
- package/dist/store.d.ts +25 -0
- package/dist/store.js +218 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/dist/writers/claude-code.d.ts +5 -0
- package/dist/writers/claude-code.js +51 -0
- package/dist/writers/cline.d.ts +3 -0
- package/dist/writers/cline.js +77 -0
- package/dist/writers/codex.d.ts +3 -0
- package/dist/writers/codex.js +32 -0
- package/dist/writers/cursor.d.ts +3 -0
- package/dist/writers/cursor.js +197 -0
- package/dist/writers/deepagents.d.ts +3 -0
- package/dist/writers/deepagents.js +116 -0
- package/dist/writers/entropy.d.ts +5 -0
- package/dist/writers/entropy.js +33 -0
- package/dist/writers/fx.d.ts +3 -0
- package/dist/writers/fx.js +135 -0
- package/dist/writers/github-copilot.d.ts +3 -0
- package/dist/writers/github-copilot.js +71 -0
- package/dist/writers/grok-build.d.ts +3 -0
- package/dist/writers/grok-build.js +43 -0
- package/dist/writers/helpers.d.ts +17 -0
- package/dist/writers/helpers.js +75 -0
- package/dist/writers/omp.d.ts +3 -0
- package/dist/writers/omp.js +7 -0
- package/dist/writers/opencode.d.ts +3 -0
- package/dist/writers/opencode.js +65 -0
- package/dist/writers/pi.d.ts +3 -0
- package/dist/writers/pi.js +45 -0
- package/docs/cline-native-format.md +43 -0
- package/docs/copilot-native-format.md +78 -0
- package/docs/cursor-native-format.md +103 -0
- package/docs/deepagents-native-format.md +98 -0
- package/docs/evidence-api.md +170 -0
- package/docs/fx-native-format.md +87 -0
- package/docs/harness-parity-bootstrap.md +78 -0
- package/docs/harness-support-parity.md +107 -0
- package/docs/inspirations.md +103 -0
- package/docs/ledger-migration.md +141 -0
- package/docs/pi-compaction-context.md +75 -0
- package/docs/pi-provider-context.md +76 -0
- package/docs/research/2026-10-06-huihua-and-oar.md +176 -0
- package/docs/research/ledger/README.md +96 -0
- package/docs/research/ledger/confer/2026-09-13-round-10/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-2/cass.md +72 -0
- package/docs/research/ledger/confer/2026-09-13-round-3/README.md +70 -0
- package/docs/research/ledger/confer/2026-09-13-round-5/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-6/README.md +68 -0
- package/docs/research/ledger/confer/2026-09-13-round-7/README.md +64 -0
- package/docs/research/ledger/confer/2026-09-13-round-9/README.md +59 -0
- package/docs/research/ledger/confer/2026-09-14-wake/wake.md +146 -0
- package/docs/research/ledger/docs/inspirations.md +320 -0
- package/docs/research/ledger/docs/pr2-pr6-integration.md +53 -0
- package/docs/research/ledger/docs/research/2026-09-07-confer-inspirations.md +163 -0
- package/docs/research/ledger/docs/research/2026-09-07-letta-continuous-service.md +104 -0
- package/docs/research/ledger/docs/research/2026-09-08-claude-history-retrieval.md +117 -0
- package/docs/research/ledger/docs/research/2026-09-09-agentsview-native-discovery.md +87 -0
- package/docs/research/ledger/docs/research/2026-09-09-daemon-observation.md +231 -0
- package/docs/research/ledger/docs/research/2026-09-09-discovery-pagination.md +114 -0
- package/docs/research/ledger/docs/research/2026-09-09-harness-transcript-contract.md +145 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-discovery.md +180 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-implementation.md +137 -0
- package/docs/research/ledger/docs/research/2026-09-09-other-inspirations.md +279 -0
- package/docs/research/ledger/docs/research/2026-09-09-trajectory-and-letta-code.md +128 -0
- package/docs/research/ledger/docs/research/2026-09-13-diagnostics-coverage.md +116 -0
- package/docs/research/ledger/docs/research/2026-09-13-fts-rebuild.md +83 -0
- package/docs/research/ledger/docs/research/2026-09-13-read-path-split.md +74 -0
- package/docs/research/ledger/docs/research/2026-09-13-runtime-rollout.md +102 -0
- package/docs/research/ledger/docs/research/2026-09-13-scan-outcomes.md +80 -0
- package/docs/research/ledger/docs/research/2026-09-13-search-quality.md +105 -0
- package/docs/research/ledger/docs/research/2026-09-13-source-coverage.md +64 -0
- package/docs/research/ledger/docs/research/2026-09-13-sync-wall-budget.md +82 -0
- package/docs/research/ledger/docs/research/2026-09-13-typed-read-gate.md +81 -0
- package/docs/research/ledger/docs/research/2026-09-13-writer-lease.md +84 -0
- package/docs/research/ledger/docs/research/2026-09-16-c1-copy-fallback.md +227 -0
- package/docs/research/ledger/docs/research/2026-09-16-c10-kimi.md +414 -0
- package/docs/research/ledger/docs/research/2026-09-16-c2-derive-counter.md +216 -0
- package/docs/research/ledger/docs/research/2026-09-16-c3-echo-filter.md +305 -0
- package/docs/research/ledger/docs/research/2026-09-16-c4-title-index.md +349 -0
- package/docs/research/ledger/docs/research/2026-09-16-c5-trigram.md +268 -0
- package/docs/research/ledger/docs/research/2026-09-16-c6-recency-prior.md +438 -0
- package/docs/research/ledger/docs/research/2026-09-16-c7-tombstones.md +467 -0
- package/docs/research/ledger/docs/research/2026-09-16-c8-remote-truth.md +390 -0
- package/docs/research/ledger/docs/research/2026-09-16-c9-grok.md +380 -0
- package/docs/research/ledger/docs/research/2026-09-16-wake-port-program.md +38 -0
- package/docs/research/ledger/docs/research/2026-09-17-cursor-blobstore.md +157 -0
- package/docs/research/ledger/manifest.json +540 -0
- package/legacy/NOTICE.md +50 -0
- package/legacy/bin/ledger-core.ts +7 -0
- package/legacy/bin/ledger.ts +7 -0
- package/legacy/bin/traj-bridge.ts +7 -0
- package/legacy/scripts/import-claude-history.ts +130 -0
- package/legacy/scripts/native-history-smoke.ts +52 -0
- package/legacy/scripts/observe-daemon.ts +570 -0
- package/legacy/scripts/retitle-sessions.md +19 -0
- package/legacy/scripts/verify-runtime-rollout.ts +151 -0
- package/legacy/src/canonical.ts +108 -0
- package/legacy/src/cli.ts +1293 -0
- package/legacy/src/db.ts +2254 -0
- package/legacy/src/diagnostics.ts +87 -0
- package/legacy/src/discovery.ts +56 -0
- package/legacy/src/embeddings.ts +382 -0
- package/legacy/src/filter.ts +602 -0
- package/legacy/src/gemini_embeddings.ts +103 -0
- package/legacy/src/identity.ts +9 -0
- package/legacy/src/index.ts +10 -0
- package/legacy/src/ingest.ts +791 -0
- package/legacy/src/managed.ts +178 -0
- package/legacy/src/native/common.ts +68 -0
- package/legacy/src/native/cursor.ts +647 -0
- package/legacy/src/native/grok.ts +540 -0
- package/legacy/src/native/kimi.ts +706 -0
- package/legacy/src/native/opencode.ts +470 -0
- package/legacy/src/native/sqlite-ro.ts +281 -0
- package/legacy/src/native.ts +97 -0
- package/legacy/src/normalizer.ts +457 -0
- package/legacy/src/parentage.ts +562 -0
- package/legacy/src/pressure.ts +162 -0
- package/legacy/src/private-storage.ts +74 -0
- package/legacy/src/reader.ts +1255 -0
- package/legacy/src/scanner.ts +591 -0
- package/legacy/src/schema-gate.ts +263 -0
- package/legacy/src/schema.ts +829 -0
- package/legacy/src/semantic-top-k.ts +71 -0
- package/legacy/src/session-hash.ts +49 -0
- package/legacy/src/store-status.ts +218 -0
- package/legacy/src/sync-preparation.ts +45 -0
- package/legacy/src/sync-truth.ts +843 -0
- package/legacy/src/sync.ts +1219 -0
- package/legacy/src/types.ts +436 -0
- package/legacy/src/vocab.txt +30522 -0
- package/legacy/src/watch.ts +673 -0
- package/legacy/src/writer-lease.ts +310 -0
- package/ops/README.md +120 -0
- package/ops/install-cli.sh +63 -0
- package/ops/install-release.sh +49 -0
- package/ops/linux/install-systemd-user.sh +103 -0
- package/ops/macos/install-launchd.sh +72 -0
- package/ops/sync-settings.sh +37 -0
- package/package.json +19 -5
- package/skills/trajectories/SKILL.md +138 -0
- package/skills/trajectories/scripts/trajectories_recent.sh +26 -0
- package/skills/trajectories/scripts/trajectories_search.sh +26 -0
- package/index.js +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { normalizeToCanonical } from '@letta-ai/trajectory';
|
|
2
|
+
import { canonicalFromRecords } from './canonical.js';
|
|
3
|
+
function object(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function unsupported(reason) {
|
|
7
|
+
throw new Error(`Unsupported github-copilot continuation history: ${reason}. Native bytes can be archived, but this bootstrap cannot safely export their active context.`);
|
|
8
|
+
}
|
|
9
|
+
function parseRows(raw) {
|
|
10
|
+
return raw.split('\n').filter(line => line.trim()).map((line, index) => {
|
|
11
|
+
let value;
|
|
12
|
+
try {
|
|
13
|
+
value = JSON.parse(line);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`Invalid GitHub Copilot JSON at line ${index + 1}.`);
|
|
17
|
+
}
|
|
18
|
+
if (!object(value))
|
|
19
|
+
throw new Error(`Invalid GitHub Copilot event at line ${index + 1}.`);
|
|
20
|
+
return value;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const transportTypes = new Set([
|
|
24
|
+
'abort', 'assistant.turn_end', 'assistant.turn_start', 'hook.end', 'hook.start',
|
|
25
|
+
'session.info', 'session.resume', 'session.shutdown', 'session.start',
|
|
26
|
+
'session.task_complete', 'tool.execution_start',
|
|
27
|
+
'permission.requested', 'permission.completed',
|
|
28
|
+
]);
|
|
29
|
+
const contextTypes = new Set(['user.message', 'system.message', 'assistant.message', 'tool.execution_complete']);
|
|
30
|
+
const userKeys = new Set(['content', 'messageId', 'transformedContent', 'attachments', 'delivery',
|
|
31
|
+
'interactionId', 'turnId', 'parentAgentTaskId', 'agentMode', 'source', 'isAutopilotContinuation',
|
|
32
|
+
'nativeDocumentPathFallbackPaths', 'supportedNativeDocumentMimeTypes', 'responsesReasoning']);
|
|
33
|
+
const assistantKeys = new Set(['content', 'messageId', 'model', 'reasoningText', 'toolRequests',
|
|
34
|
+
'originatingMessageId', 'interactionId', 'turnId', 'rte', 'apiCallId', 'outputTokens',
|
|
35
|
+
'clientRequestId', 'requestId', 'serviceRequestId', 'chunkCount', 'chunkIndex']);
|
|
36
|
+
const systemKeys = new Set(['role', 'content', 'contentBlocks', 'interactionId', 'metadata', 'name']);
|
|
37
|
+
/** Extend Letta's archive projection with native direct prompts and system text.
|
|
38
|
+
* A hook is only a duplicate when its next submission carries the same prompt.
|
|
39
|
+
* Repeated user messages remain distinct and keep their own native identities.
|
|
40
|
+
*/
|
|
41
|
+
export function readGitHubCopilot(raw) {
|
|
42
|
+
const rows = parseRows(raw);
|
|
43
|
+
const base = normalizeToCanonical({ source: 'copilot-cli', transcript: raw,
|
|
44
|
+
bounds: { toolArguments: { maxCharacters: null }, toolResults: { maxCharacters: null } },
|
|
45
|
+
filters: { systemMessages: 'include', toolResults: 'include' } });
|
|
46
|
+
const meta = base.records.find(record => record.record_type === 'meta');
|
|
47
|
+
const entries = [{ record: JSON.parse(meta.record_json) }];
|
|
48
|
+
const byId = new Map();
|
|
49
|
+
for (const record of base.records.filter(record => record.record_type !== 'meta')) {
|
|
50
|
+
const group = byId.get(record.stable_source_record_id) ?? [];
|
|
51
|
+
group.push(record);
|
|
52
|
+
byId.set(record.stable_source_record_id, group);
|
|
53
|
+
}
|
|
54
|
+
const appendBase = (row, index) => {
|
|
55
|
+
for (const canonical of byId.get(String(row.id)) ?? []) {
|
|
56
|
+
const record = JSON.parse(canonical.record_json);
|
|
57
|
+
const previous = entries.at(-1);
|
|
58
|
+
if (record.role === 'assistant' && 'tool_calls' in record && previous && previous.nativeId === row.id &&
|
|
59
|
+
previous.record.role === 'assistant' && 'tool_calls' in previous.record) {
|
|
60
|
+
previous.record.tool_calls.push(...record.tool_calls);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
entries.push({ record, nativeId: typeof row.id === 'string' ? row.id : undefined, sourceSequence: index,
|
|
64
|
+
...(canonical.source_timestamp ? { sourceTimestamp: canonical.source_timestamp } : {}) });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
let pendingHook;
|
|
68
|
+
const flushHook = () => {
|
|
69
|
+
if (pendingHook)
|
|
70
|
+
appendBase(pendingHook.row, pendingHook.index);
|
|
71
|
+
pendingHook = undefined;
|
|
72
|
+
};
|
|
73
|
+
for (const [index, row] of rows.entries()) {
|
|
74
|
+
const data = object(row.data) ? row.data : {};
|
|
75
|
+
if (row.type === 'hook.start' && data.hookType === 'userPromptSubmitted') {
|
|
76
|
+
flushHook();
|
|
77
|
+
pendingHook = { row, index };
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (row.type === 'user.message') {
|
|
81
|
+
const hookData = pendingHook && object(pendingHook.row.data) ? pendingHook.row.data : {};
|
|
82
|
+
const input = object(hookData.input) ? hookData.input : {};
|
|
83
|
+
if (typeof data.content === 'string' && input.prompt === data.content)
|
|
84
|
+
pendingHook = undefined;
|
|
85
|
+
else
|
|
86
|
+
flushHook();
|
|
87
|
+
}
|
|
88
|
+
else if (contextTypes.has(String(row.type)) || String(row.type).startsWith('session.') ||
|
|
89
|
+
row.type === 'assistant.turn_start' || row.type === 'assistant.turn_end') {
|
|
90
|
+
flushHook();
|
|
91
|
+
}
|
|
92
|
+
if ((row.type === 'user.message' || row.type === 'system.message') && typeof data.content === 'string') {
|
|
93
|
+
const timestamp = row.timestamp;
|
|
94
|
+
if (typeof timestamp !== 'string' || !Number.isFinite(Date.parse(timestamp)))
|
|
95
|
+
throw new Error('Invalid Copilot native message timestamp.');
|
|
96
|
+
entries.push({ record: { role: row.type === 'user.message' ? 'user' : 'system', content: data.content, timestamp },
|
|
97
|
+
nativeId: typeof row.id === 'string' ? row.id : undefined, sourceSequence: index, sourceTimestamp: timestamp });
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
appendBase(row, index);
|
|
101
|
+
}
|
|
102
|
+
flushHook();
|
|
103
|
+
const diagnostics = base.diagnostics.filter(diagnostic => diagnostic.code !== 'noise_record_dropped' ||
|
|
104
|
+
!['system.message', 'permission.requested', 'permission.completed'].includes(String(rows[(diagnostic.inputLine ?? 0) - 1]?.type)));
|
|
105
|
+
const canonical = canonicalFromRecords('copilot-cli', meta.source_group_id, entries, diagnostics);
|
|
106
|
+
const components = new Map();
|
|
107
|
+
for (const record of canonical.records) {
|
|
108
|
+
const index = components.get(record.source_order_id) ?? 0;
|
|
109
|
+
record.component_index = index;
|
|
110
|
+
components.set(record.source_order_id, index + 1);
|
|
111
|
+
}
|
|
112
|
+
return canonical;
|
|
113
|
+
}
|
|
114
|
+
/** Reject native state that the supported text projection cannot continue. */
|
|
115
|
+
export function assertGitHubCopilotHistory(raw) {
|
|
116
|
+
const rows = parseRows(raw);
|
|
117
|
+
if (!rows.length)
|
|
118
|
+
throw new Error('GitHub Copilot event history is empty.');
|
|
119
|
+
const starts = rows.filter(row => row.type === 'session.start');
|
|
120
|
+
const start = starts[0];
|
|
121
|
+
if (starts.length !== 1 || rows[0] !== start || !start || !object(start.data) || start.data.version !== 1 ||
|
|
122
|
+
typeof start.data.sessionId !== 'string' || !start.data.sessionId)
|
|
123
|
+
unsupported('requires one identified leading version-1 session.start');
|
|
124
|
+
const ids = new Set();
|
|
125
|
+
let previous = null;
|
|
126
|
+
const pending = new Set();
|
|
127
|
+
for (const [index, row] of rows.entries()) {
|
|
128
|
+
const id = typeof row.id === 'string' ? row.id : '';
|
|
129
|
+
if (!id || ids.has(id))
|
|
130
|
+
unsupported('events require unique native ids');
|
|
131
|
+
const parent = row.parentId;
|
|
132
|
+
if (index === 0 ? parent !== null : parent !== previous)
|
|
133
|
+
unsupported('branched or disconnected native event chain');
|
|
134
|
+
ids.add(id);
|
|
135
|
+
previous = id;
|
|
136
|
+
const type = String(row.type);
|
|
137
|
+
const data = object(row.data) ? row.data : {};
|
|
138
|
+
if (row.agentId !== undefined)
|
|
139
|
+
unsupported('subagent history needs separate context reconstruction');
|
|
140
|
+
if (!transportTypes.has(type) && !contextTypes.has(type))
|
|
141
|
+
unsupported(`unsupported context event ${type}`);
|
|
142
|
+
if (type === 'user.message') {
|
|
143
|
+
if (typeof data.content !== 'string' || (Array.isArray(data.attachments) && data.attachments.length) ||
|
|
144
|
+
(data.attachments !== undefined && !Array.isArray(data.attachments)))
|
|
145
|
+
unsupported('user attachments or nontext content');
|
|
146
|
+
if (Object.keys(data).some(key => !userKeys.has(key)) || data.responsesReasoning != null ||
|
|
147
|
+
['nativeDocumentPathFallbackPaths', 'supportedNativeDocumentMimeTypes'].some(key => data[key] !== undefined &&
|
|
148
|
+
(!Array.isArray(data[key]) || data[key].length > 0)))
|
|
149
|
+
unsupported('unrepresented user context fields');
|
|
150
|
+
if (data.transformedContent !== undefined && data.transformedContent !== data.content) {
|
|
151
|
+
const transformed = typeof data.transformedContent === 'string' ? data.transformedContent : '';
|
|
152
|
+
const time = /^<current_datetime>([^<>\r\n]+)<\/current_datetime>\n\n/.exec(transformed)?.[1];
|
|
153
|
+
if (!time || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(time) || !Number.isFinite(Date.parse(time)) ||
|
|
154
|
+
transformed.replace(/^<current_datetime>[^<>\r\n]+<\/current_datetime>\n\n/, '') !== data.content)
|
|
155
|
+
unsupported('unrepresented transformed user context');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (type === 'system.message') {
|
|
159
|
+
if (data.role !== 'system' || typeof data.content !== 'string' ||
|
|
160
|
+
Object.keys(data).some(key => !systemKeys.has(key)))
|
|
161
|
+
unsupported('nontext or unrepresented system message');
|
|
162
|
+
if (data.contentBlocks !== undefined && (!Array.isArray(data.contentBlocks) ||
|
|
163
|
+
data.contentBlocks.some(block => !object(block) || typeof block.content !== 'string' ||
|
|
164
|
+
Object.keys(block).some(key => !['content', 'isStatic', 'cacheBreakpoint'].includes(key))) ||
|
|
165
|
+
data.contentBlocks.map(block => block.content).join('\n\n') !== data.content))
|
|
166
|
+
unsupported('unrepresented system content blocks');
|
|
167
|
+
}
|
|
168
|
+
if (type === 'hook.start' && data.hookType === 'userPromptSubmitted' &&
|
|
169
|
+
(!object(data.input) || typeof data.input.prompt !== 'string'))
|
|
170
|
+
unsupported('nontext submitted prompt');
|
|
171
|
+
if (type === 'assistant.message' && ((data.content !== undefined && typeof data.content !== 'string') ||
|
|
172
|
+
(data.reasoningText !== undefined && typeof data.reasoningText !== 'string') ||
|
|
173
|
+
(data.toolRequests !== undefined && !Array.isArray(data.toolRequests)) ||
|
|
174
|
+
Object.keys(data).some(key => !assistantKeys.has(key))))
|
|
175
|
+
unsupported('unrepresented assistant content');
|
|
176
|
+
if (type === 'session.compaction_start' || type === 'session.compaction_complete') {
|
|
177
|
+
unsupported('compaction requires native context reconstruction');
|
|
178
|
+
}
|
|
179
|
+
if (type === 'assistant.message' && Array.isArray(data.toolRequests)) {
|
|
180
|
+
for (const request of data.toolRequests) {
|
|
181
|
+
if (!object(request) || typeof request.toolCallId !== 'string' || !request.toolCallId || pending.has(request.toolCallId) ||
|
|
182
|
+
typeof request.name !== 'string' || !request.name ||
|
|
183
|
+
(typeof request.arguments !== 'string' && !object(request.arguments)) ||
|
|
184
|
+
Object.keys(request).some(key => !['toolCallId', 'name', 'arguments', 'type', 'intentionSummary', 'toolTitle', 'mcpServerName', 'mcpToolName'].includes(key)) ||
|
|
185
|
+
(request.type !== undefined && request.type !== 'function')) {
|
|
186
|
+
unsupported('malformed or duplicate pending tool request');
|
|
187
|
+
}
|
|
188
|
+
if (typeof request.arguments === 'string') {
|
|
189
|
+
try {
|
|
190
|
+
if (!object(JSON.parse(request.arguments)))
|
|
191
|
+
unsupported('tool arguments must be a JSON object');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
unsupported('tool arguments must be a JSON object');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
pending.add(request.toolCallId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (type === 'tool.execution_complete') {
|
|
201
|
+
if (typeof data.toolCallId !== 'string' || !pending.delete(data.toolCallId)) {
|
|
202
|
+
unsupported('orphan or duplicate tool completion');
|
|
203
|
+
}
|
|
204
|
+
if ((!object(data.result) || !Object.hasOwn(data.result, 'content')) && data.error === undefined)
|
|
205
|
+
unsupported('unrepresented tool result');
|
|
206
|
+
if (object(data.result) && (typeof data.result.content !== 'string' ||
|
|
207
|
+
Object.keys(data.result).some(key => !['content', 'detailedContent'].includes(key))))
|
|
208
|
+
unsupported('nontext or provider-native tool result');
|
|
209
|
+
}
|
|
210
|
+
else if ((type === 'abort' || type === 'session.shutdown') && pending.size) {
|
|
211
|
+
unsupported('interrupted tool execution');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (pending.size)
|
|
215
|
+
unsupported(`pending tool execution: ${[...pending].join(', ')}`);
|
|
216
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** ACP replay ported from archeism/ledger 43081aef54c23b4bd017b6c0e11fc42a9a1cb381.
|
|
2
|
+
* See legacy/NOTICE.md. Pure in-memory projection; native bytes stay in Snapshot.raw.
|
|
3
|
+
*/
|
|
4
|
+
import { type CanonicalResult } from '@letta-ai/trajectory';
|
|
5
|
+
/** Normalize native ConversationItem history or ACP updates without reading a store.
|
|
6
|
+
* Native chat_history.jsonl has no header: callers may provide summary.json metadata.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeGrokBuild(transcript: string, metadata?: {
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
}): CanonicalResult;
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/** ACP replay ported from archeism/ledger 43081aef54c23b4bd017b6c0e11fc42a9a1cb381.
|
|
2
|
+
* See legacy/NOTICE.md. Pure in-memory projection; native bytes stay in Snapshot.raw.
|
|
3
|
+
*/
|
|
4
|
+
import { normalizeToCanonical } from '@letta-ai/trajectory';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
class NativeInputError extends Error {
|
|
7
|
+
}
|
|
8
|
+
function object(value) {
|
|
9
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function safeNativeId(value) {
|
|
12
|
+
return typeof value === 'string' && value.trim().length > 0 && value !== '.' && value !== '..' && !/[\x00-\x1f/\\]/.test(value);
|
|
13
|
+
}
|
|
14
|
+
const LIFECYCLE_KINDS = new Set([
|
|
15
|
+
"hook_execution",
|
|
16
|
+
"turn_completed",
|
|
17
|
+
"session_recap",
|
|
18
|
+
"retry_state",
|
|
19
|
+
"task_backgrounded",
|
|
20
|
+
"task_completed",
|
|
21
|
+
"auto_compact_started",
|
|
22
|
+
"auto_compact_completed",
|
|
23
|
+
"compaction_checkpoint",
|
|
24
|
+
"plan",
|
|
25
|
+
"current_mode_update",
|
|
26
|
+
]);
|
|
27
|
+
function tally(values) {
|
|
28
|
+
let total = 0;
|
|
29
|
+
for (const count of values.values())
|
|
30
|
+
total += count;
|
|
31
|
+
return total;
|
|
32
|
+
}
|
|
33
|
+
function named(values, limit = 4) {
|
|
34
|
+
return [...values.keys()].sort().slice(0, limit).join(", ");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Replay the ACP stream.
|
|
38
|
+
*
|
|
39
|
+
* A run of same-role chunks becomes one record: chunk text is concatenated
|
|
40
|
+
* without a separator, because Grok splits tokens (including the spaces between
|
|
41
|
+
* them) across rows. A run flushes on a role change or when tool activity
|
|
42
|
+
* interrupts it, so one record's text never spans two speakers or straddles a
|
|
43
|
+
* tool call. A `tool_call_update` is a result only in its terminal state:
|
|
44
|
+
* in_progress and status-less progress rows are superseded by the terminal row
|
|
45
|
+
* that follows, and are counted rather than projected.
|
|
46
|
+
*
|
|
47
|
+
* Grok appends while it runs, so a torn last line or a line from a newer
|
|
48
|
+
* version is counted, never fatal: the transcript is what the prefix holds.
|
|
49
|
+
*/
|
|
50
|
+
function replayUpdates(text, sessionId, summary, limit) {
|
|
51
|
+
const rows = [];
|
|
52
|
+
const skips = {
|
|
53
|
+
lifecycle: new Map(), progress: 0, unknown: new Map(), content: 0, invalidLines: 0, malformedLines: 0,
|
|
54
|
+
repeatedTerminals: 0,
|
|
55
|
+
};
|
|
56
|
+
// One result per call id: a repeated terminal row (a retry after a failure, a
|
|
57
|
+
// re-emitted update) must not project a second result for the same call.
|
|
58
|
+
const terminals = new Set();
|
|
59
|
+
let run = null;
|
|
60
|
+
let model = summary.model;
|
|
61
|
+
let written = 0;
|
|
62
|
+
function emit(row) {
|
|
63
|
+
written += Buffer.byteLength(JSON.stringify(row)) + 1;
|
|
64
|
+
if (written > limit)
|
|
65
|
+
throw new NativeInputError("Grok transcript exceeds the native byte limit.");
|
|
66
|
+
rows.push(row);
|
|
67
|
+
}
|
|
68
|
+
function flush() {
|
|
69
|
+
const pending = run;
|
|
70
|
+
run = null;
|
|
71
|
+
if (!pending || !pending.text.length)
|
|
72
|
+
return;
|
|
73
|
+
if (pending.role === "user") {
|
|
74
|
+
emit({
|
|
75
|
+
type: "hook.start",
|
|
76
|
+
id: pending.id,
|
|
77
|
+
...(pending.timestamp ? { timestamp: pending.timestamp } : {}),
|
|
78
|
+
data: {
|
|
79
|
+
hookType: "userPromptSubmitted",
|
|
80
|
+
input: { prompt: pending.text, sessionId, ...(summary.cwd ? { cwd: summary.cwd } : {}) },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const eventModel = pending.model ?? model;
|
|
86
|
+
emit({
|
|
87
|
+
type: "assistant.message",
|
|
88
|
+
id: pending.id,
|
|
89
|
+
...(pending.timestamp ? { timestamp: pending.timestamp } : {}),
|
|
90
|
+
data: {
|
|
91
|
+
...(pending.role === "thought" ? { reasoningText: pending.text } : { content: pending.text }),
|
|
92
|
+
...(eventModel ? { model: eventModel } : {}),
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const lines = text.split("\n");
|
|
97
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
98
|
+
const line = lines[index];
|
|
99
|
+
if (!line || !line.trim())
|
|
100
|
+
continue;
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = JSON.parse(line);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
skips.invalidLines += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const row = object(parsed) ? parsed : null;
|
|
110
|
+
const params = row && object(row.params) ? row.params : null;
|
|
111
|
+
const update = params && object(params.update) ? params.update : null;
|
|
112
|
+
if (!row || !params || !update) {
|
|
113
|
+
skips.malformedLines += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const meta = object(params._meta) ? params._meta : {};
|
|
117
|
+
const agentTimestampMs = typeof meta.agentTimestampMs === "number" && Number.isFinite(meta.agentTimestampMs)
|
|
118
|
+
? meta.agentTimestampMs
|
|
119
|
+
: typeof update._meta === "object" && object(update._meta) && typeof update._meta.agentTimestampMs === "number"
|
|
120
|
+
&& Number.isFinite(update._meta.agentTimestampMs)
|
|
121
|
+
? update._meta.agentTimestampMs
|
|
122
|
+
: null;
|
|
123
|
+
const timestamp = agentTimestampMs !== null
|
|
124
|
+
? new Date(agentTimestampMs).toISOString()
|
|
125
|
+
: typeof row.timestamp === "number" && Number.isFinite(row.timestamp) && row.timestamp > 0
|
|
126
|
+
? new Date(row.timestamp * 1000).toISOString()
|
|
127
|
+
: null;
|
|
128
|
+
const chunkModel = object(update._meta) && typeof update._meta.modelId === "string" ? update._meta.modelId : undefined;
|
|
129
|
+
if (chunkModel)
|
|
130
|
+
model = chunkModel;
|
|
131
|
+
// A row without a usable update id keeps a stable identity from its own
|
|
132
|
+
// line: the file is append-only, so a line number never moves.
|
|
133
|
+
const id = typeof meta.eventId === "string" && safeNativeId(meta.eventId) ? meta.eventId : `grok-line-${index + 1}`;
|
|
134
|
+
const kind = update.sessionUpdate;
|
|
135
|
+
if (kind === "user_message_chunk" || kind === "agent_thought_chunk" || kind === "agent_message_chunk") {
|
|
136
|
+
const content = update.content;
|
|
137
|
+
const chunk = object(content) && content.type === "text" && typeof content.text === "string" ? content.text : null;
|
|
138
|
+
if (chunk === null) {
|
|
139
|
+
skips.content += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const role = kind === "user_message_chunk" ? "user" : kind === "agent_thought_chunk" ? "thought" : "message";
|
|
143
|
+
if (run && run.role !== role)
|
|
144
|
+
flush();
|
|
145
|
+
run ??= { role, text: "", id, timestamp, model: chunkModel };
|
|
146
|
+
run.text += chunk;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (kind === "tool_call") {
|
|
150
|
+
const callId = update.toolCallId;
|
|
151
|
+
if (!safeNativeId(callId)) {
|
|
152
|
+
skips.unknown.set("tool_call_without_id", (skips.unknown.get("tool_call_without_id") ?? 0) + 1);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
flush();
|
|
156
|
+
const tool = object(update._meta) && object(update._meta["x.ai/tool"]) ? update._meta["x.ai/tool"] : null;
|
|
157
|
+
const name = typeof update.title === "string" && update.title.trim()
|
|
158
|
+
? update.title
|
|
159
|
+
: tool && typeof tool.name === "string" && tool.name.trim() ? tool.name : undefined;
|
|
160
|
+
const eventModel = chunkModel ?? model;
|
|
161
|
+
emit({
|
|
162
|
+
type: "assistant.message",
|
|
163
|
+
id,
|
|
164
|
+
...(timestamp ? { timestamp } : {}),
|
|
165
|
+
data: {
|
|
166
|
+
toolRequests: [{ toolCallId: callId, ...(name ? { name } : {}), arguments: update.rawInput ?? {} }],
|
|
167
|
+
...(eventModel ? { model: eventModel } : {}),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (kind === "tool_call_update") {
|
|
173
|
+
const callId = update.toolCallId;
|
|
174
|
+
if (!safeNativeId(callId)) {
|
|
175
|
+
skips.unknown.set("tool_call_update_without_id", (skips.unknown.get("tool_call_update_without_id") ?? 0) + 1);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const status = update.status;
|
|
179
|
+
if (status !== "completed" && status !== "failed") {
|
|
180
|
+
skips.progress += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (terminals.has(callId)) {
|
|
184
|
+
skips.repeatedTerminals += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
terminals.add(callId);
|
|
188
|
+
flush();
|
|
189
|
+
// ACP tool output is content blocks; a structured rawOutput is the only
|
|
190
|
+
// text a result can carry when no content block was written.
|
|
191
|
+
const blocks = update.content;
|
|
192
|
+
const parts = Array.isArray(blocks)
|
|
193
|
+
? blocks
|
|
194
|
+
.map((block) => (object(block) && object(block.content) && block.content.type === "text" && typeof block.content.text === "string"
|
|
195
|
+
? block.content.text
|
|
196
|
+
: null))
|
|
197
|
+
.filter((part) => part !== null && part.length > 0)
|
|
198
|
+
: [];
|
|
199
|
+
const raw = update.rawOutput;
|
|
200
|
+
const output = parts.length
|
|
201
|
+
? parts.join("\n")
|
|
202
|
+
: raw === undefined || raw === null ? "" : typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
203
|
+
emit({
|
|
204
|
+
type: "tool.execution_complete",
|
|
205
|
+
id,
|
|
206
|
+
...(timestamp ? { timestamp } : {}),
|
|
207
|
+
data: {
|
|
208
|
+
toolCallId: callId,
|
|
209
|
+
success: status !== "failed",
|
|
210
|
+
...(status === "failed" ? { error: { message: output } } : { result: { content: output } }),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (typeof kind === "string" && LIFECYCLE_KINDS.has(kind)) {
|
|
216
|
+
skips.lifecycle.set(kind, (skips.lifecycle.get(kind) ?? 0) + 1);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const label = typeof kind === "string" && kind ? kind : "missing_kind";
|
|
220
|
+
skips.unknown.set(label, (skips.unknown.get(label) ?? 0) + 1);
|
|
221
|
+
}
|
|
222
|
+
flush();
|
|
223
|
+
return { rows, skips, model };
|
|
224
|
+
}
|
|
225
|
+
/** Every skip is typed and counted, so a dropped update never vanishes silently. */
|
|
226
|
+
function skipDiagnostics(skips) {
|
|
227
|
+
const diagnostics = [];
|
|
228
|
+
if (skips.lifecycle.size) {
|
|
229
|
+
diagnostics.push({
|
|
230
|
+
code: "noise_record_dropped",
|
|
231
|
+
message: `Skipped ${tally(skips.lifecycle)} Grok lifecycle update(s) (${named(skips.lifecycle)}).`,
|
|
232
|
+
count: tally(skips.lifecycle),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (skips.progress) {
|
|
236
|
+
diagnostics.push({
|
|
237
|
+
code: "noise_record_dropped",
|
|
238
|
+
message: `Skipped ${skips.progress} non-terminal Grok tool progress update(s); the terminal update carries the result.`,
|
|
239
|
+
count: skips.progress,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (skips.repeatedTerminals) {
|
|
243
|
+
diagnostics.push({
|
|
244
|
+
code: "noise_record_dropped",
|
|
245
|
+
message: `Skipped ${skips.repeatedTerminals} repeated Grok tool terminal update(s); the call's first terminal update is its result.`,
|
|
246
|
+
count: skips.repeatedTerminals,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (skips.unknown.size) {
|
|
250
|
+
diagnostics.push({
|
|
251
|
+
code: "noise_record_dropped",
|
|
252
|
+
message: `Skipped ${tally(skips.unknown)} Grok update(s) with no supported transcript shape (${named(skips.unknown)}).`,
|
|
253
|
+
count: tally(skips.unknown),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (skips.content) {
|
|
257
|
+
diagnostics.push({
|
|
258
|
+
code: "noise_record_dropped",
|
|
259
|
+
message: `Skipped ${skips.content} Grok message update(s) whose content is not a text block.`,
|
|
260
|
+
count: skips.content,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (skips.invalidLines) {
|
|
264
|
+
diagnostics.push({
|
|
265
|
+
code: "invalid_json_line",
|
|
266
|
+
message: `Skipped ${skips.invalidLines} invalid JSON line(s) in the Grok update stream.`,
|
|
267
|
+
count: skips.invalidLines,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (skips.malformedLines) {
|
|
271
|
+
diagnostics.push({
|
|
272
|
+
code: "invalid_json_line",
|
|
273
|
+
message: `Skipped ${skips.malformedLines} Grok line(s) with no session update object.`,
|
|
274
|
+
count: skips.malformedLines,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return diagnostics;
|
|
278
|
+
}
|
|
279
|
+
/** Normalize native ConversationItem history or ACP updates without reading a store.
|
|
280
|
+
* Native chat_history.jsonl has no header: callers may provide summary.json metadata.
|
|
281
|
+
*/
|
|
282
|
+
export function normalizeGrokBuild(transcript, metadata = {}) {
|
|
283
|
+
const input = [];
|
|
284
|
+
for (const [index, line] of transcript.split('\n').entries()) {
|
|
285
|
+
if (!line.trim())
|
|
286
|
+
continue;
|
|
287
|
+
let parsed;
|
|
288
|
+
try {
|
|
289
|
+
parsed = JSON.parse(line);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
throw new NativeInputError(`Malformed Grok JSON on line ${index + 1}.`);
|
|
293
|
+
}
|
|
294
|
+
if (!object(parsed))
|
|
295
|
+
throw new NativeInputError(`Grok line ${index + 1} is not an object.`);
|
|
296
|
+
input.push(parsed);
|
|
297
|
+
}
|
|
298
|
+
if (!input.length)
|
|
299
|
+
throw new NativeInputError('Grok transcript is empty.');
|
|
300
|
+
const ids = new Set();
|
|
301
|
+
if (metadata.sessionId)
|
|
302
|
+
ids.add(metadata.sessionId);
|
|
303
|
+
for (const row of input) {
|
|
304
|
+
if (object(row.params) && typeof row.params.sessionId === 'string')
|
|
305
|
+
ids.add(row.params.sessionId);
|
|
306
|
+
}
|
|
307
|
+
if ([...ids].some(id => !safeNativeId(id)))
|
|
308
|
+
throw new NativeInputError('Invalid Grok native session id.');
|
|
309
|
+
if (ids.size > 1)
|
|
310
|
+
throw new NativeInputError('Grok transcript contains multiple native session ids.');
|
|
311
|
+
const id = [...ids][0] ?? `grok-${createHash('sha256').update(transcript).digest('hex')}`;
|
|
312
|
+
const acp = input.every(row => object(row.params) && object(row.params.update));
|
|
313
|
+
let rows;
|
|
314
|
+
let diagnostics = [];
|
|
315
|
+
if (acp) {
|
|
316
|
+
const replay = replayUpdates(transcript, id, metadata, Number.MAX_SAFE_INTEGER);
|
|
317
|
+
rows = replay.rows;
|
|
318
|
+
diagnostics = skipDiagnostics(replay.skips);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
if (input.some(row => object(row.params)))
|
|
322
|
+
throw new NativeInputError('Mixed Grok native and ACP formats.');
|
|
323
|
+
rows = [{ type: 'session_meta', payload: { id, cwd: metadata.cwd } }];
|
|
324
|
+
const model = metadata.model ?? input.find(row => typeof row.model_id === 'string')?.model_id;
|
|
325
|
+
if (model)
|
|
326
|
+
rows.push({ type: 'turn_context', payload: { model } });
|
|
327
|
+
const response = (payload) => rows.push({ type: 'response_item', payload });
|
|
328
|
+
for (const [index, row] of input.entries()) {
|
|
329
|
+
if (row.type === 'user' || row.type === 'assistant' || row.type === 'system') {
|
|
330
|
+
let content;
|
|
331
|
+
if (typeof row.content === 'string')
|
|
332
|
+
content = row.content;
|
|
333
|
+
else if (Array.isArray(row.content) && row.content.every(part => object(part) && part.type === 'text' && typeof part.text === 'string')) {
|
|
334
|
+
content = row.content.map(part => part.text).join('\n');
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
throw new NativeInputError(`Unsupported Grok content on line ${index + 1}.`);
|
|
338
|
+
// Grok stores runtime instructions in user-shaped items before the first
|
|
339
|
+
// indexed human prompt. Retain their content as system context so a
|
|
340
|
+
// target writer does not promote old runtime rules into user intent.
|
|
341
|
+
const generatedContext = row.type === 'user' && row.prompt_index === undefined &&
|
|
342
|
+
((typeof row.synthetic_reason === 'string' && row.synthetic_reason.length > 0) || content.trimStart().startsWith('<user_info>'));
|
|
343
|
+
const role = generatedContext ? 'system' : row.type;
|
|
344
|
+
if (content.length)
|
|
345
|
+
response({ type: 'message', role, content: [{ type: row.type === 'assistant' ? 'output_text' : 'input_text', text: content }] });
|
|
346
|
+
if (row.tool_calls !== undefined) {
|
|
347
|
+
if (row.type !== 'assistant' || !Array.isArray(row.tool_calls))
|
|
348
|
+
throw new NativeInputError('Invalid Grok tool calls.');
|
|
349
|
+
for (const call of row.tool_calls) {
|
|
350
|
+
if (!object(call) || !safeNativeId(call.id) || typeof call.name !== 'string' || typeof call.arguments !== 'string')
|
|
351
|
+
throw new NativeInputError('Invalid Grok tool call.');
|
|
352
|
+
try {
|
|
353
|
+
JSON.parse(call.arguments);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
throw new NativeInputError('Malformed Grok tool arguments.');
|
|
357
|
+
}
|
|
358
|
+
response({ type: 'function_call', call_id: call.id, name: call.name, arguments: call.arguments });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else if (row.type === 'reasoning') {
|
|
363
|
+
if (!Array.isArray(row.summary) || !row.summary.every(part => object(part) && part.type === 'summary_text' && typeof part.text === 'string')) {
|
|
364
|
+
throw new NativeInputError(`Unsupported Grok reasoning summary on line ${index + 1}.`);
|
|
365
|
+
}
|
|
366
|
+
const text = row.summary.map(part => part.text).join('\n');
|
|
367
|
+
if (text)
|
|
368
|
+
rows.push({ type: 'event_msg', payload: { type: 'agent_reasoning', text } });
|
|
369
|
+
}
|
|
370
|
+
else if (row.type === 'tool_result') {
|
|
371
|
+
if (!safeNativeId(row.tool_call_id) || typeof row.content !== 'string')
|
|
372
|
+
throw new NativeInputError('Invalid Grok tool result.');
|
|
373
|
+
response({ type: 'function_call_output', call_id: row.tool_call_id, output: row.content });
|
|
374
|
+
}
|
|
375
|
+
else
|
|
376
|
+
throw new NativeInputError(`Unsupported Grok native item on line ${index + 1}.`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const canonical = normalizeToCanonical({
|
|
380
|
+
source: acp ? 'copilot-cli' : 'codex',
|
|
381
|
+
transcript: rows.map(row => JSON.stringify(row)).join('\n'),
|
|
382
|
+
sourceContext: { groupId: id, partial: true },
|
|
383
|
+
bounds: { toolArguments: { maxCharacters: null }, toolResults: { maxCharacters: null } },
|
|
384
|
+
filters: { systemMessages: 'include', toolResults: 'include' },
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
...canonical,
|
|
388
|
+
records: canonical.records.map(record => ({ ...record, source_type: 'grok-build' })),
|
|
389
|
+
diagnostics: [...canonical.diagnostics, ...diagnostics],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Harness } from '../types.js';
|
|
2
|
+
import type { FxNativeCompanions } from './fx.js';
|
|
3
|
+
/** The upstream normalizer is an archive reader, not a native active-branch resolver. */
|
|
4
|
+
export declare function assertPortableHistory(harness: Harness, raw: string, fxCompanions?: FxNativeCompanions): void;
|