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,135 @@
|
|
|
1
|
+
import { destination, droppedRecord, timestampOf, toolNames } from './helpers.js';
|
|
2
|
+
/** FX legacy session.json schema 1 with presentation execution-memory schema 3. */
|
|
3
|
+
export const writeFx = (records, options, entropy) => {
|
|
4
|
+
const { id, timestamp } = destination(options, entropy);
|
|
5
|
+
const names = toolNames(records);
|
|
6
|
+
const diagnostics = [];
|
|
7
|
+
const portable = records.filter((record) => !droppedRecord(record, diagnostics) && record.role !== 'meta');
|
|
8
|
+
const history = [];
|
|
9
|
+
let index = 0;
|
|
10
|
+
while (index < portable.length) {
|
|
11
|
+
const user = portable[index++];
|
|
12
|
+
if (!user || user.role !== 'user')
|
|
13
|
+
throw new Error('FX completed history must begin each turn with a user record.');
|
|
14
|
+
const steps = [];
|
|
15
|
+
let finalAssistant = null;
|
|
16
|
+
let stepAssistant;
|
|
17
|
+
while (index < portable.length) {
|
|
18
|
+
const record = portable[index];
|
|
19
|
+
if (record.role === 'user')
|
|
20
|
+
break;
|
|
21
|
+
if (record.role !== 'assistant')
|
|
22
|
+
throw new Error('FX completed history contains a tool result outside an assistant tool batch.');
|
|
23
|
+
index += 1;
|
|
24
|
+
if (!('tool_calls' in record)) {
|
|
25
|
+
const next = portable[index];
|
|
26
|
+
if (next?.role === 'assistant') {
|
|
27
|
+
if ('tool_calls' in next) {
|
|
28
|
+
if (stepAssistant !== undefined)
|
|
29
|
+
throw new Error('FX tool step has multiple assistant prefaces.');
|
|
30
|
+
stepAssistant = record.content;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// FX execution-memory steps retain intermediate assistant text even
|
|
34
|
+
// when no tool call followed that message.
|
|
35
|
+
steps.push({ assistant: record.content, tool_calls: [], tool_results: [] });
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (finalAssistant !== null)
|
|
40
|
+
throw new Error('FX completed history contains multiple final assistant replies in one turn.');
|
|
41
|
+
finalAssistant = record.content;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
const callRecords = [record];
|
|
45
|
+
while (index < portable.length) {
|
|
46
|
+
const next = portable[index];
|
|
47
|
+
if (next.role !== 'assistant' || !('tool_calls' in next))
|
|
48
|
+
break;
|
|
49
|
+
callRecords.push(next);
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
const calls = callRecords.flatMap((item) => item.tool_calls);
|
|
53
|
+
const callIds = new Set(calls.map((call) => call.id));
|
|
54
|
+
const results = [];
|
|
55
|
+
while (index < portable.length && portable[index].role === 'tool') {
|
|
56
|
+
const result = portable[index++];
|
|
57
|
+
if (!callIds.delete(result.tool_call_id)) {
|
|
58
|
+
throw new Error(`FX tool result ${result.tool_call_id} does not belong to its assistant tool batch.`);
|
|
59
|
+
}
|
|
60
|
+
if (result.ok === undefined && !diagnostics.some((item) => item.code === 'unknown-tool-status')) {
|
|
61
|
+
diagnostics.push({ code: 'unknown-tool-status', message: 'FX requires a terminal tool status. Unknown source status is represented as failure; the central snapshot retains the source value.' });
|
|
62
|
+
}
|
|
63
|
+
const createdAt = Date.parse(timestampOf(result));
|
|
64
|
+
if (!Number.isSafeInteger(createdAt) || createdAt < 0)
|
|
65
|
+
throw new Error('FX tool result timestamp must be a non-negative millisecond timestamp.');
|
|
66
|
+
const bytes = Buffer.byteLength(result.content);
|
|
67
|
+
results.push({
|
|
68
|
+
tool_call_id: result.tool_call_id,
|
|
69
|
+
tool_name: names.get(result.tool_call_id),
|
|
70
|
+
status: result.ok === true ? 'success' : 'failure',
|
|
71
|
+
output: result.content,
|
|
72
|
+
output_bytes: bytes,
|
|
73
|
+
stored_output_bytes: bytes,
|
|
74
|
+
truncated: false,
|
|
75
|
+
provider_native: false,
|
|
76
|
+
created_at_ms: createdAt,
|
|
77
|
+
permission_feedback: [],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (callIds.size)
|
|
81
|
+
throw new Error(`FX tool batch is missing results: ${[...callIds].join(', ')}`);
|
|
82
|
+
steps.push({
|
|
83
|
+
assistant: stepAssistant ?? null,
|
|
84
|
+
tool_calls: calls.map((call) => ({
|
|
85
|
+
id: call.id,
|
|
86
|
+
name: call.name,
|
|
87
|
+
arguments_json: call.args,
|
|
88
|
+
provider_result: null,
|
|
89
|
+
})),
|
|
90
|
+
tool_results: results,
|
|
91
|
+
});
|
|
92
|
+
stepAssistant = undefined;
|
|
93
|
+
}
|
|
94
|
+
if (finalAssistant === null)
|
|
95
|
+
throw new Error('FX cannot export a turn without a completed final assistant reply.');
|
|
96
|
+
const turn = { kind: 'assistant', user: { text: user.content, images: [] }, assistant: finalAssistant };
|
|
97
|
+
if (steps.length)
|
|
98
|
+
turn.execution = { schema_version: 3, tool_steps: steps, files: [] };
|
|
99
|
+
history.push(turn);
|
|
100
|
+
}
|
|
101
|
+
const timeMs = Date.parse(timestamp);
|
|
102
|
+
const session = {
|
|
103
|
+
schema_version: 1,
|
|
104
|
+
id,
|
|
105
|
+
created_at_ms: timeMs,
|
|
106
|
+
updated_at_ms: timeMs,
|
|
107
|
+
workspace_root: options.cwd,
|
|
108
|
+
conversation_language: 'en',
|
|
109
|
+
history_len: history.length,
|
|
110
|
+
history,
|
|
111
|
+
};
|
|
112
|
+
const entrypoint = `.fx/sessions/${id}/session.json`;
|
|
113
|
+
return {
|
|
114
|
+
harness: 'fx',
|
|
115
|
+
sessionId: id,
|
|
116
|
+
entrypoint,
|
|
117
|
+
files: [{ path: entrypoint, content: `${JSON.stringify(session, null, 2)}\n` }],
|
|
118
|
+
resume: {
|
|
119
|
+
cwd: options.cwd,
|
|
120
|
+
command: 'env',
|
|
121
|
+
args: [
|
|
122
|
+
`HOME=${options.directory}`,
|
|
123
|
+
'fx',
|
|
124
|
+
...(options.provider ? ['--provider', options.provider] : []),
|
|
125
|
+
...(options.model ? ['--model', options.model] : []),
|
|
126
|
+
'resume',
|
|
127
|
+
id,
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
diagnostics: [...diagnostics, {
|
|
131
|
+
code: 'isolated-native-home',
|
|
132
|
+
message: 'FX discovers sessions under $HOME/.fx. Resume with HOME set to the export directory; credentials, settings, permissions, provider replay, images, and file evidence are not imported.',
|
|
133
|
+
}],
|
|
134
|
+
};
|
|
135
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { destination, droppedRecord, jsonl, parseArguments, timestampOf, toolNames } from './helpers.js';
|
|
2
|
+
import { randomNativeEntropy } from './entropy.js';
|
|
3
|
+
/** GitHub Copilot CLI 1.0.91 local session event history. */
|
|
4
|
+
export const writeGitHubCopilot = (records, options, entropy = randomNativeEntropy) => {
|
|
5
|
+
const { id, timestamp } = destination(options, entropy);
|
|
6
|
+
const names = toolNames(records);
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
const events = [];
|
|
9
|
+
let parentId = null;
|
|
10
|
+
const emit = (type, data, at) => {
|
|
11
|
+
const event = { id: entropy.uuid(), timestamp: at, parentId, type, data };
|
|
12
|
+
events.push(event);
|
|
13
|
+
parentId = event.id;
|
|
14
|
+
return event;
|
|
15
|
+
};
|
|
16
|
+
emit('session.start', { sessionId: id, version: 1, producer: 'copilot-agent',
|
|
17
|
+
copilotVersion: '1.0.91', startTime: timestamp, context: { cwd: options.cwd },
|
|
18
|
+
...(options.model ? { selectedModel: options.model } : {}) }, timestamp);
|
|
19
|
+
const calls = new Map();
|
|
20
|
+
for (const record of records) {
|
|
21
|
+
if (droppedRecord(record, diagnostics) || record.role === 'meta')
|
|
22
|
+
continue;
|
|
23
|
+
const at = timestampOf(record);
|
|
24
|
+
if (record.role === 'user') {
|
|
25
|
+
const messageId = entropy.uuid();
|
|
26
|
+
emit('hook.start', { hookInvocationId: entropy.uuid(), hookType: 'userPromptSubmitted',
|
|
27
|
+
input: { prompt: record.content, sessionId: id, cwd: options.cwd, timestamp: at } }, at);
|
|
28
|
+
emit('user.message', { content: record.content, messageId }, at);
|
|
29
|
+
}
|
|
30
|
+
else if (record.role === 'assistant') {
|
|
31
|
+
if ('tool_calls' in record) {
|
|
32
|
+
const requests = record.tool_calls.map(call => {
|
|
33
|
+
const args = parseArguments(call.args);
|
|
34
|
+
calls.set(call.id, { name: call.name, args });
|
|
35
|
+
return { toolCallId: call.id, name: call.name, arguments: args, type: 'function' };
|
|
36
|
+
});
|
|
37
|
+
emit('assistant.message', { messageId: entropy.uuid(), content: '', toolRequests: requests,
|
|
38
|
+
...(options.model ? { model: options.model } : {}) }, at);
|
|
39
|
+
for (const call of record.tool_calls) {
|
|
40
|
+
const request = calls.get(call.id);
|
|
41
|
+
emit('tool.execution_start', { toolCallId: call.id, toolName: request.name,
|
|
42
|
+
arguments: request.args, ...(options.model ? { model: options.model } : {}) }, at);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
emit('assistant.message', { messageId: entropy.uuid(), content: record.content,
|
|
47
|
+
...(options.model ? { model: options.model } : {}) }, at);
|
|
48
|
+
}
|
|
49
|
+
else if (record.role === 'tool') {
|
|
50
|
+
const request = calls.get(record.tool_call_id);
|
|
51
|
+
if (!request || names.get(record.tool_call_id) !== request.name)
|
|
52
|
+
throw new Error(`Missing Copilot tool request: ${record.tool_call_id}`);
|
|
53
|
+
emit('tool.execution_complete', { toolCallId: record.tool_call_id, success: record.ok !== false,
|
|
54
|
+
...(record.ok === false ? { error: { message: record.content } } : { result: { content: record.content } }),
|
|
55
|
+
...(options.model ? { model: options.model } : {}) }, at);
|
|
56
|
+
calls.delete(record.tool_call_id);
|
|
57
|
+
if (record.ok === undefined && !diagnostics.some(item => item.code === 'unknown-tool-status')) {
|
|
58
|
+
diagnostics.push({ code: 'unknown-tool-status', message: 'Unknown source tool status maps to Copilot success without asserting source success; the central snapshot retains unknown status.' });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const prefix = `session-state/${id}`;
|
|
63
|
+
const entrypoint = `${prefix}/events.jsonl`;
|
|
64
|
+
const workspace = { id, cwd: options.cwd, summary_count: 0, created_at: timestamp, updated_at: timestamp };
|
|
65
|
+
return { harness: 'github-copilot', sessionId: id, entrypoint,
|
|
66
|
+
files: [{ path: entrypoint, content: jsonl(events) },
|
|
67
|
+
{ path: `${prefix}/workspace.yaml`, content: JSON.stringify(workspace, null, 2) + '\n' }],
|
|
68
|
+
resume: { command: 'env', args: [`COPILOT_HOME=${options.directory}`, 'copilot', `--resume=${id}`,
|
|
69
|
+
...(options.model ? ['--model', options.model] : [])], cwd: options.cwd },
|
|
70
|
+
diagnostics: [...diagnostics, { code: 'native-home-required', message: 'Set COPILOT_HOME to the export directory before resuming. Authentication, provider credentials, permissions, tools, and workspace files are not transferred.' }] };
|
|
71
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { destination, droppedRecord, jsonl, toolNames } from './helpers.js';
|
|
2
|
+
/** Grok Build's version-1 ConversationItem history and session summary. */
|
|
3
|
+
export const writeGrokBuild = (records, options, entropy) => {
|
|
4
|
+
const { id, timestamp } = destination(options, entropy);
|
|
5
|
+
toolNames(records);
|
|
6
|
+
const diagnostics = [];
|
|
7
|
+
// Native session/load and session/resume restore this journal directly.
|
|
8
|
+
// The caller supplies destination instructions/workspace through the runtime.
|
|
9
|
+
const history = [];
|
|
10
|
+
let promptIndex = 0;
|
|
11
|
+
for (const record of records) {
|
|
12
|
+
if (droppedRecord(record, diagnostics))
|
|
13
|
+
continue;
|
|
14
|
+
if (record.role === 'user') {
|
|
15
|
+
history.push({ type: 'user', content: [{ type: 'text', text: record.content }], prompt_index: promptIndex++ });
|
|
16
|
+
}
|
|
17
|
+
else if (record.role === 'assistant') {
|
|
18
|
+
history.push('tool_calls' in record
|
|
19
|
+
? { type: 'assistant', content: '', tool_calls: record.tool_calls.map(call => ({ id: call.id, name: call.name, arguments: call.args })) }
|
|
20
|
+
: { type: 'assistant', content: record.content });
|
|
21
|
+
}
|
|
22
|
+
else if (record.role === 'tool') {
|
|
23
|
+
history.push({ type: 'tool_result', tool_call_id: record.tool_call_id, content: record.content });
|
|
24
|
+
if (record.ok !== undefined && !diagnostics.some(d => d.code === 'untranslated-tool-status')) {
|
|
25
|
+
diagnostics.push({ code: 'untranslated-tool-status', message: 'Grok ConversationItem tool results have no success-status field. Source status remains in the central snapshot.' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Rust urlencoding::encode percent-encodes every non-unreserved UTF-8 byte.
|
|
30
|
+
const encodedCwd = encodeURIComponent(options.cwd).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
31
|
+
if (Buffer.byteLength(encodedCwd) > 255)
|
|
32
|
+
throw new Error('Grok native cwd encoding exceeds 255 bytes; use a shorter workspace path. Hashed Grok cwd directories are not supported by this writer yet.');
|
|
33
|
+
const prefix = `sessions/${encodedCwd}/${id}`;
|
|
34
|
+
const entrypoint = `${prefix}/chat_history.jsonl`;
|
|
35
|
+
const summary = { info: { id, cwd: options.cwd }, session_summary: 'Imported conversation', created_at: timestamp,
|
|
36
|
+
updated_at: timestamp, num_messages: history.length, num_chat_messages: history.length,
|
|
37
|
+
current_model_id: options.model ?? 'grok-build', chat_format_version: 1, next_trace_turn: 0,
|
|
38
|
+
session_kind: 'headless', grok_home: options.directory };
|
|
39
|
+
return { harness: 'grok-build', sessionId: id, entrypoint,
|
|
40
|
+
files: [{ path: entrypoint, content: jsonl(history) }, { path: `${prefix}/summary.json`, content: JSON.stringify(summary, null, 2) + '\n' }],
|
|
41
|
+
resume: { cwd: options.cwd, command: 'env', args: [`GROK_HOME=${options.directory}`, 'grok', '--resume', id, '--cwd', options.cwd, ...(options.model ? ['--model', options.model] : [])] },
|
|
42
|
+
diagnostics: [...diagnostics, { code: 'native-home-required', message: `Set GROK_HOME to the export directory before resuming. Only conversation history is transferred; runtime permissions, credentials, plans, and rewind state are not imported.` }] };
|
|
43
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NormalizedRecord } from '@letta-ai/trajectory';
|
|
2
|
+
import type { ConversionDiagnostic, NativeEntropy, WriteOptions } from '../types.js';
|
|
3
|
+
export declare function destination(options: WriteOptions, entropy?: NativeEntropy): {
|
|
4
|
+
id: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function jsonl(entries: readonly unknown[]): string;
|
|
8
|
+
/** Refuse incomplete execution history: a writer cannot decide a pending tool's outcome. */
|
|
9
|
+
export declare function toolNames(records: readonly NormalizedRecord[]): Map<string, string>;
|
|
10
|
+
export declare function parseArguments(args: string): Record<string, unknown>;
|
|
11
|
+
export declare function droppedRecord(record: NormalizedRecord, diagnostics: ConversionDiagnostic[]): boolean;
|
|
12
|
+
export declare function timestampOf(record: Exclude<NormalizedRecord, {
|
|
13
|
+
role: 'meta';
|
|
14
|
+
}>): string;
|
|
15
|
+
/** Target files require numeric usage; these placeholders are never measured usage. */
|
|
16
|
+
export declare function placeholderUsage(): Record<string, unknown>;
|
|
17
|
+
export declare function usageDiagnostic(): ConversionDiagnostic;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
import { randomNativeEntropy } from './entropy.js';
|
|
3
|
+
export function destination(options, entropy = randomNativeEntropy) {
|
|
4
|
+
if (!isAbsolute(options.directory) || !isAbsolute(options.cwd)) {
|
|
5
|
+
throw new Error('Native export directory and cwd must be absolute paths.');
|
|
6
|
+
}
|
|
7
|
+
const id = options.sessionId ?? entropy.uuid();
|
|
8
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
|
|
9
|
+
throw new Error('Native sessionId must be a UUID.');
|
|
10
|
+
}
|
|
11
|
+
const timestamp = options.timestamp ?? new Date().toISOString();
|
|
12
|
+
if (!Number.isFinite(Date.parse(timestamp)))
|
|
13
|
+
throw new Error('Invalid export timestamp.');
|
|
14
|
+
return { id, timestamp: new Date(timestamp).toISOString() };
|
|
15
|
+
}
|
|
16
|
+
export function jsonl(entries) {
|
|
17
|
+
return entries.map((entry) => JSON.stringify(entry)).join('\n') + '\n';
|
|
18
|
+
}
|
|
19
|
+
/** Refuse incomplete execution history: a writer cannot decide a pending tool's outcome. */
|
|
20
|
+
export function toolNames(records) {
|
|
21
|
+
const names = new Map();
|
|
22
|
+
const pending = new Set();
|
|
23
|
+
for (const record of records) {
|
|
24
|
+
if (record.role === 'assistant' && 'tool_calls' in record) {
|
|
25
|
+
for (const call of record.tool_calls) {
|
|
26
|
+
if (!call.id || !call.name || names.has(call.id))
|
|
27
|
+
throw new Error(`Invalid or duplicate tool call: ${call.id}`);
|
|
28
|
+
parseArguments(call.args);
|
|
29
|
+
names.set(call.id, call.name);
|
|
30
|
+
pending.add(call.id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (record.role === 'tool') {
|
|
34
|
+
if (!pending.delete(record.tool_call_id))
|
|
35
|
+
throw new Error(`Orphan or duplicate tool result: ${record.tool_call_id}`);
|
|
36
|
+
}
|
|
37
|
+
else if (pending.size && (record.role === 'user' || record.role === 'assistant')) {
|
|
38
|
+
throw new Error('Cannot migrate an unfinished tool batch.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (pending.size)
|
|
42
|
+
throw new Error(`Cannot migrate pending tool calls: ${[...pending].join(', ')}`);
|
|
43
|
+
return names;
|
|
44
|
+
}
|
|
45
|
+
export function parseArguments(args) {
|
|
46
|
+
const parsed = JSON.parse(args);
|
|
47
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
48
|
+
throw new Error('Native tool arguments must be a JSON object.');
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
export function droppedRecord(record, diagnostics) {
|
|
53
|
+
if (record.role === 'meta')
|
|
54
|
+
return true;
|
|
55
|
+
if (record.role === 'reasoning' || record.role === 'system' || record.role === 'observation') {
|
|
56
|
+
if (!diagnostics.some((item) => item.code === `untranslated-${record.role}`)) {
|
|
57
|
+
diagnostics.push({ code: `untranslated-${record.role}`, message: `Source ${record.role} records remain in the central snapshot; they are not installed as target runtime instructions or provider reasoning.` });
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
export function timestampOf(record) {
|
|
64
|
+
if (!Number.isFinite(Date.parse(record.timestamp)))
|
|
65
|
+
throw new Error('Invalid source record timestamp.');
|
|
66
|
+
return new Date(record.timestamp).toISOString();
|
|
67
|
+
}
|
|
68
|
+
/** Target files require numeric usage; these placeholders are never measured usage. */
|
|
69
|
+
export function placeholderUsage() {
|
|
70
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
|
|
71
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
72
|
+
}
|
|
73
|
+
export function usageDiagnostic() {
|
|
74
|
+
return { code: 'synthetic-native-usage', message: 'Imported messages have unknown usage. Native-required usage fields contain zero placeholders; they are not measured token or cost values. Original evidence remains in the central snapshot.' };
|
|
75
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { writePi } from './pi.js';
|
|
2
|
+
/** OMP v3 shares Pi's message subset. No Pi-only entries, cache keys, or blobs are emitted. */
|
|
3
|
+
export const writeOmp = (records, options, entropy) => {
|
|
4
|
+
const exported = writePi(records, options, entropy);
|
|
5
|
+
return { ...exported, harness: 'omp', resume: { cwd: options.cwd, command: 'omp', args: exported.resume.args.map(arg => arg === '--session' ? '--resume' : arg) },
|
|
6
|
+
diagnostics: [...exported.diagnostics, { code: 'omp-portable-subset', message: 'OMP import uses its v3 inline message subset. Native context snapshots, blob references, credential pins, and session_init runtime configuration are not transferred.' }] };
|
|
7
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { destination, droppedRecord, parseArguments, timestampOf, toolNames, usageDiagnostic } from './helpers.js';
|
|
3
|
+
import { randomNativeEntropy } from './entropy.js';
|
|
4
|
+
/** The public `opencode import` JSON format; never writes OpenCode's SQLite database. */
|
|
5
|
+
export const writeOpenCode = (records, options, entropy = randomNativeEntropy) => {
|
|
6
|
+
const { id, timestamp } = destination(options, entropy);
|
|
7
|
+
toolNames(records);
|
|
8
|
+
const diagnostics = [usageDiagnostic()];
|
|
9
|
+
const sessionId = `ses_${id.replaceAll('-', '')}`;
|
|
10
|
+
const modelID = options.model ?? 'unknown';
|
|
11
|
+
const providerID = options.provider ?? 'trajectories-import';
|
|
12
|
+
const at = Date.parse(timestamp);
|
|
13
|
+
let sequence = 0;
|
|
14
|
+
// OpenCode orders messages by its monotonic, 48-bit timestamp-prefixed ids.
|
|
15
|
+
const makeId = (prefix) => `${prefix}_${((BigInt(at) * 4096n + BigInt(++sequence)) & 0xffffffffffffn).toString(16).padStart(12, '0')}${Buffer.from(entropy.bytes(7)).toString('hex')}`;
|
|
16
|
+
const tokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } };
|
|
17
|
+
const messages = [];
|
|
18
|
+
const tools = new Map();
|
|
19
|
+
let parentID;
|
|
20
|
+
for (const record of records) {
|
|
21
|
+
if (droppedRecord(record, diagnostics) || record.role === 'meta')
|
|
22
|
+
continue;
|
|
23
|
+
const created = Date.parse(timestampOf(record));
|
|
24
|
+
if (record.role === 'tool') {
|
|
25
|
+
const pending = tools.get(record.tool_call_id);
|
|
26
|
+
pending.part.state = record.ok === false
|
|
27
|
+
? { status: 'error', input: pending.input, error: record.content, time: { start: pending.start, end: created } }
|
|
28
|
+
: { status: 'completed', input: pending.input, output: record.content, title: String(pending.part.tool), metadata: {}, time: { start: pending.start, end: created } };
|
|
29
|
+
if (record.ok === undefined && !diagnostics.some(d => d.code === 'unknown-tool-status'))
|
|
30
|
+
diagnostics.push({ code: 'unknown-tool-status', message: 'Unknown source tool status maps to OpenCode completed without asserting source success; the source snapshot retains unknown status.' });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (record.role !== 'user' && record.role !== 'assistant')
|
|
34
|
+
continue;
|
|
35
|
+
const messageID = makeId('msg');
|
|
36
|
+
if (record.role === 'user')
|
|
37
|
+
parentID = messageID;
|
|
38
|
+
if (!parentID)
|
|
39
|
+
throw new Error('OpenCode import requires a user message before assistant messages.');
|
|
40
|
+
const info = record.role === 'user'
|
|
41
|
+
? { id: messageID, sessionID: sessionId, role: 'user', time: { created }, agent: 'build', model: { providerID, modelID } }
|
|
42
|
+
: { id: messageID, sessionID: sessionId, role: 'assistant', parentID, modelID, providerID, mode: 'build', agent: 'build',
|
|
43
|
+
path: { cwd: options.cwd, root: options.cwd }, cost: 0, tokens, time: { created, completed: created }, finish: 'tool_calls' in record ? 'tool-calls' : 'stop' };
|
|
44
|
+
const parts = [];
|
|
45
|
+
if (record.role === 'assistant' && 'tool_calls' in record) {
|
|
46
|
+
for (const call of record.tool_calls) {
|
|
47
|
+
const input = parseArguments(call.args);
|
|
48
|
+
const part = { id: makeId('prt'), sessionID: sessionId, messageID, type: 'tool', callID: call.id, tool: call.name, state: {} };
|
|
49
|
+
parts.push(part);
|
|
50
|
+
tools.set(call.id, { part, input, start: created });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
parts.push({ id: makeId('prt'), sessionID: sessionId, messageID, type: 'text', text: record.content });
|
|
55
|
+
messages.push({ info, parts });
|
|
56
|
+
}
|
|
57
|
+
const entrypoint = `${sessionId}.json`;
|
|
58
|
+
const data = { info: { id: sessionId, slug: `import-${id.slice(0, 8)}`, projectID: 'global', directory: options.cwd,
|
|
59
|
+
title: 'Imported conversation', version: '1.18.34', time: { created: at, updated: at } }, messages };
|
|
60
|
+
const isolated = [`XDG_DATA_HOME=${resolve(options.directory, 'data')}`, `XDG_CONFIG_HOME=${resolve(options.directory, 'config')}`,
|
|
61
|
+
`XDG_STATE_HOME=${resolve(options.directory, 'state')}`, `XDG_CACHE_HOME=${resolve(options.directory, 'cache')}`, 'opencode'];
|
|
62
|
+
return { harness: 'opencode', sessionId, entrypoint, files: [{ path: entrypoint, content: JSON.stringify(data, null, 2) + '\n' }],
|
|
63
|
+
prepare: [{ cwd: options.cwd, command: 'env', args: [...isolated, 'import', resolve(options.directory, entrypoint)] }],
|
|
64
|
+
resume: { cwd: options.cwd, command: 'env', args: [...isolated, 'run', '--session', sessionId, '--dir', options.cwd, ...(options.model && options.provider ? ['--model', `${options.provider}/${options.model}`] : [])] }, diagnostics };
|
|
65
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { destination, droppedRecord, jsonl, parseArguments, placeholderUsage, timestampOf, toolNames, usageDiagnostic } from './helpers.js';
|
|
3
|
+
import { randomNativeEntropy } from './entropy.js';
|
|
4
|
+
/** Pi SessionManager v3 JSONL; no runtime dependency is required to produce a file. */
|
|
5
|
+
export const writePi = (records, options, entropy = randomNativeEntropy) => {
|
|
6
|
+
const { id, timestamp } = destination(options, entropy);
|
|
7
|
+
const names = toolNames(records);
|
|
8
|
+
const diagnostics = [usageDiagnostic()];
|
|
9
|
+
const sourceModel = records.find((record) => record.role === 'meta')?.model ?? 'unknown';
|
|
10
|
+
const entries = [{ type: 'session', version: 3, id, timestamp, cwd: options.cwd }];
|
|
11
|
+
let parentId = null;
|
|
12
|
+
for (const record of records) {
|
|
13
|
+
if (droppedRecord(record, diagnostics) || record.role === 'meta')
|
|
14
|
+
continue;
|
|
15
|
+
const at = timestampOf(record);
|
|
16
|
+
let message;
|
|
17
|
+
if (record.role === 'user') {
|
|
18
|
+
message = { role: 'user', content: record.content, timestamp: Date.parse(at) };
|
|
19
|
+
}
|
|
20
|
+
else if (record.role === 'assistant') {
|
|
21
|
+
const content = 'tool_calls' in record
|
|
22
|
+
? record.tool_calls.map((call) => ({ type: 'toolCall', id: call.id, name: call.name, arguments: parseArguments(call.args) }))
|
|
23
|
+
: [{ type: 'text', text: record.content }];
|
|
24
|
+
message = { role: 'assistant', content, api: 'openai-completions', provider: 'trajectories-import', model: sourceModel,
|
|
25
|
+
usage: placeholderUsage(), stopReason: 'tool_calls' in record ? 'toolUse' : 'stop', timestamp: Date.parse(at) };
|
|
26
|
+
}
|
|
27
|
+
else if (record.role === 'tool') {
|
|
28
|
+
message = { role: 'toolResult', toolCallId: record.tool_call_id, toolName: names.get(record.tool_call_id),
|
|
29
|
+
content: [{ type: 'text', text: record.content }], isError: record.ok === false, timestamp: Date.parse(at) };
|
|
30
|
+
if (record.ok === undefined && !diagnostics.some((d) => d.code === 'unknown-tool-status'))
|
|
31
|
+
diagnostics.push({ code: 'unknown-tool-status', message: 'Source tool status is unknown; target isError defaults to false without asserting source success.' });
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
continue;
|
|
35
|
+
const entryId = entropy.uuid();
|
|
36
|
+
entries.push({ type: 'message', id: entryId, parentId, timestamp: at, message });
|
|
37
|
+
parentId = entryId;
|
|
38
|
+
}
|
|
39
|
+
// Pi's CLI migrates root-level *.jsonl before resolving --session. A file
|
|
40
|
+
// at the agent home root would move, then its old path starts an empty session.
|
|
41
|
+
const projectKey = `--${options.cwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
|
|
42
|
+
const entrypoint = `sessions/${projectKey}/${id}.jsonl`;
|
|
43
|
+
return { harness: 'pi', sessionId: id, files: [{ path: entrypoint, content: jsonl(entries) }], entrypoint,
|
|
44
|
+
resume: { cwd: options.cwd, command: 'pi', args: ['--session', resolve(options.directory, entrypoint), ...(options.model ? ['--model', options.model] : []), ...(options.provider ? ['--provider', options.provider] : [])] }, diagnostics };
|
|
45
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Cline native history
|
|
2
|
+
|
|
3
|
+
The codec reads Cline Core version-1 `<id>.messages.json` and the
|
|
4
|
+
`@cline/agents` AgentMessage array persisted as `history.json` by Vercel's
|
|
5
|
+
Cline adapter. Source pins: Cline `39ff2359f7e08231281539696e48a166ce49270c`,
|
|
6
|
+
Core/Agents 0.0.79, compatible adapter 1.0.29. The native CLI probe uses
|
|
7
|
+
`cline@3.0.68`, which bundles Core/Agents 0.0.90; these are distinct receipts.
|
|
8
|
+
|
|
9
|
+
Core stores sessions under `CLINE_SESSION_DATA_DIR`, normally
|
|
10
|
+
`~/.cline/data/sessions/<id>/<id>.messages.json`, with a `<id>.json` manifest
|
|
11
|
+
and optional compaction state. File inputs archive the manifest and contained
|
|
12
|
+
compaction sidecars. A declared compaction path blocks conversion even when its
|
|
13
|
+
state is unavailable or lies outside the session directory.
|
|
14
|
+
|
|
15
|
+
The reader keeps full text, tool arguments/results, native IDs, source order,
|
|
16
|
+
timestamps, reasoning and `system_prompt`. JSON tool outputs project to text;
|
|
17
|
+
their original structure remains in raw history. Core's text-only
|
|
18
|
+
`ToolOperationResult[]` (`query`, string `result`, boolean `success`, optional
|
|
19
|
+
`error`/`duration`) is supported without flattening away its envelope. Media,
|
|
20
|
+
unknown result envelopes and provider replay
|
|
21
|
+
signatures require native context reconstruction and cannot be converted.
|
|
22
|
+
Pending tools, mixed identities, display-only and compacted histories also fail
|
|
23
|
+
portable-context validation. Source instructions and reasoning are diagnosed
|
|
24
|
+
and omitted from the destination.
|
|
25
|
+
|
|
26
|
+
The writer emits a Core payload/manifest, adapter `history.json`, and an explicit
|
|
27
|
+
`prepare-cline.mjs` action. The action calls public `SqliteSessionStore` and
|
|
28
|
+
`CoreSessionService` APIs to index and persist a fresh session; it refuses an
|
|
29
|
+
already indexed UUID. It requires a compatible `@cline/core` in the caller's
|
|
30
|
+
Node resolution path. The SDK never installs or starts that runtime.
|
|
31
|
+
|
|
32
|
+
Zero-inference loader proof uses public `ClineCore.create({backendMode:'local'})`
|
|
33
|
+
and `readMessages(id)`, recovering the user, assistant calls, complete 4 KB tool
|
|
34
|
+
result and final assistant. Native `cline history --json` separately finds the
|
|
35
|
+
indexed UUID. Model continuation is a separate experiment.
|
|
36
|
+
|
|
37
|
+
The live probe uses public Core `readMessages → start` (same ID, loaded history,
|
|
38
|
+
no prompt) `→ send`. CLI `--json --id` rejects continuation. Core selects
|
|
39
|
+
`zai-coding-plan`/`glm-5.3`; the fetch guard requires the exact ZAI coding-plan
|
|
40
|
+
chat-completions URL and model. Its managed credential enters Core configuration
|
|
41
|
+
at runtime, never argv.
|
|
42
|
+
Historical provider measurements are preserved; target model selection does
|
|
43
|
+
not rewrite them or fabricate usage.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# GitHub Copilot CLI native event format
|
|
2
|
+
|
|
3
|
+
This codec targets GitHub Copilot CLI @github/copilot 1.0.91 (commit 659e4652ac910f8046dc64480bd6beda8f0ad2a6). Loader acceptance uses @github/copilot-sdk 1.0.16 with the 1.0.91 CLI runtime.
|
|
4
|
+
|
|
5
|
+
## Export
|
|
6
|
+
|
|
7
|
+
writeGitHubCopilot emits:
|
|
8
|
+
|
|
9
|
+
~~~text
|
|
10
|
+
session-state/<session-id>/events.jsonl
|
|
11
|
+
session-state/<session-id>/workspace.yaml
|
|
12
|
+
~~~
|
|
13
|
+
|
|
14
|
+
The event stream uses the CLI's native event schema:
|
|
15
|
+
|
|
16
|
+
- session.start supplies session identity, version, producer, start time, cwd, and selected model.
|
|
17
|
+
- hook.start with userPromptSubmitted records the prompt submission.
|
|
18
|
+
- user.message preserves the runtime's displayed conversation history.
|
|
19
|
+
- assistant.message carries text or native toolRequests.
|
|
20
|
+
- tool.execution_start and tool.execution_complete carry tool linkage, arguments, result text, and known success state.
|
|
21
|
+
|
|
22
|
+
Every event has a UUID, timestamp, and a linear parentId chain. workspace.yaml is emitted as JSON, which is valid YAML and matches the SDK's own transcript-recovery fixture fields.
|
|
23
|
+
|
|
24
|
+
No prepare command is needed. Resume uses:
|
|
25
|
+
|
|
26
|
+
~~~text
|
|
27
|
+
env COPILOT_HOME=<export> copilot --resume=<session-id>
|
|
28
|
+
~~~
|
|
29
|
+
|
|
30
|
+
An optional model becomes --model <model>. Authentication, provider secrets, permissions, tools, and workspace files are caller-owned and are never copied.
|
|
31
|
+
|
|
32
|
+
## Continuation guard
|
|
33
|
+
|
|
34
|
+
assertGitHubCopilotHistory fails closed on:
|
|
35
|
+
|
|
36
|
+
- compaction events;
|
|
37
|
+
- branched, disconnected, duplicate, or missing event identities;
|
|
38
|
+
- orphan, duplicate, pending, or interrupted tool executions.
|
|
39
|
+
|
|
40
|
+
The local reader extends Letta 0.4.3: direct `user.message` records are authoritative,
|
|
41
|
+
an immediately associated matching hook is consumed once, and hook-only legacy
|
|
42
|
+
prompts remain visible. Repeated identical prompts keep separate native IDs.
|
|
43
|
+
Native text `system.message` records and every assistant/tool component remain
|
|
44
|
+
in order. Old snapshots using the lossy upstream projection must be recaptured
|
|
45
|
+
before conversion.
|
|
46
|
+
|
|
47
|
+
Only the pinned version-1 text context is portable. Unknown event types, provider
|
|
48
|
+
reasoning/replay payloads, model-facing media, notifications and unsupported
|
|
49
|
+
transformed user inputs are archived but block conversion. The runtime's known
|
|
50
|
+
ISO datetime wrapper is metadata; all other added prompt text is unsupported.
|
|
51
|
+
System text blocks must exactly reconstruct the recorded text. Raw journals keep
|
|
52
|
+
UI/cache layout and other native metadata; those are not transferred as runtime
|
|
53
|
+
state. Copilot tool `result.content` is the model-facing body, which may already
|
|
54
|
+
have been shortened by the native runtime; `detailedContent` remains in raw bytes.
|
|
55
|
+
|
|
56
|
+
CLI ingestion uses a bounded complete journal so hook/message pairs cannot split
|
|
57
|
+
across independent chunks. This trades append-path efficiency for complete native
|
|
58
|
+
context. Existing Copilot cursors are invalidated by a source-specific projection
|
|
59
|
+
fingerprint; other readers retain their current cache and append behavior.
|
|
60
|
+
|
|
61
|
+
## Z.AI GLM-5.3 BYOK
|
|
62
|
+
|
|
63
|
+
Copilot CLI 1.0.91 supports an OpenAI-compatible provider through environment variables:
|
|
64
|
+
|
|
65
|
+
~~~text
|
|
66
|
+
COPILOT_PROVIDER_BASE_URL=https://api.z.ai/api/coding/paas/v4
|
|
67
|
+
COPILOT_PROVIDER_API_KEY=<managed secret>
|
|
68
|
+
COPILOT_PROVIDER_MODEL_ID=glm-5.3
|
|
69
|
+
COPILOT_PROVIDER_WIRE_MODEL=glm-5.3
|
|
70
|
+
COPILOT_MODEL=glm-5.3
|
|
71
|
+
COPILOT_OFFLINE=true
|
|
72
|
+
~~~
|
|
73
|
+
|
|
74
|
+
The caller must explicitly supply that provider configuration. The codec does not inspect credentials and does not silently fall back to GitHub-hosted Copilot models.
|
|
75
|
+
|
|
76
|
+
## Native acceptance
|
|
77
|
+
|
|
78
|
+
All nine generated events validated against the session-events.schema.json bundled with Copilot CLI 1.0.91. The public SDK then called resumeSession through the standalone 1.0.91 CLI runtime, loaded all nine events, and disconnected without sending a prompt or making a model request. The recovered types included the session start, user hook/message, assistant messages, two tool starts, and two tool completions. Evidence is under /root/.local/share/harness-parity-implementation-2026-10-05/evidence.
|